diff options
28 files changed, 2393 insertions, 2202 deletions
diff --git a/luni/src/main/java/java/net/MulticastSocket.java b/luni/src/main/java/java/net/MulticastSocket.java index 22cefb9..7815b01 100644 --- a/luni/src/main/java/java/net/MulticastSocket.java +++ b/luni/src/main/java/java/net/MulticastSocket.java @@ -31,9 +31,6 @@ import org.apache.harmony.luni.util.Msg; * @see DatagramSocket */ public class MulticastSocket extends DatagramSocket { - - final static int SO_REUSEPORT = 512; - private InetAddress interfaceSet; /** diff --git a/luni/src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpConnection.java b/luni/src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpConnection.java index 3491ccc..2ef1a9b 100644 --- a/luni/src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpConnection.java +++ b/luni/src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpConnection.java @@ -160,8 +160,6 @@ public class HttpConnection { int port = config.getHostPort(); // create the wrapper over connected socket sslSocket = (SSLSocket) sslSocketFactory.createSocket(socket, hostName, port, true); - sslSocket.setUseClientMode(true); - sslSocket.startHandshake(); if (!hostnameVerifier.verify(hostName, sslSocket.getSession())) { throw new IOException("Hostname '" + hostName + "' was not verified"); } diff --git a/luni/src/main/java/org/apache/harmony/luni/net/PlainDatagramSocketImpl.java b/luni/src/main/java/org/apache/harmony/luni/net/PlainDatagramSocketImpl.java index 7721cbb..d4f8e6f 100644 --- a/luni/src/main/java/org/apache/harmony/luni/net/PlainDatagramSocketImpl.java +++ b/luni/src/main/java/org/apache/harmony/luni/net/PlainDatagramSocketImpl.java @@ -158,12 +158,12 @@ public class PlainDatagramSocketImpl extends DatagramSocketImpl { @Override public int getTimeToLive() throws IOException { - return getTTL() & 0xff; + return ((Integer) getOption(IP_MULTICAST_TTL)).intValue(); } @Override public byte getTTL() throws IOException { - return ((Byte) getOption(IP_MULTICAST_TTL)).byteValue(); + return (byte) getTimeToLive(); } @Override @@ -296,7 +296,7 @@ public class PlainDatagramSocketImpl extends DatagramSocketImpl { @Override public void setTTL(byte ttl) throws IOException { - setTimeToLive(ttl); + setTimeToLive((int) ttl & 0xff); // Avoid sign extension. } @Override diff --git a/luni/src/main/native/org_apache_harmony_luni_platform_OSNetworkSystem.cpp b/luni/src/main/native/org_apache_harmony_luni_platform_OSNetworkSystem.cpp index 11c056d..ffb271a 100644 --- a/luni/src/main/native/org_apache_harmony_luni_platform_OSNetworkSystem.cpp +++ b/luni/src/main/native/org_apache_harmony_luni_platform_OSNetworkSystem.cpp @@ -123,7 +123,7 @@ #define JAVASOCKOPT_SO_REUSEADDR 4 #define JAVASOCKOPT_SO_KEEPALIVE 8 #define JAVASOCKOPT_IP_MULTICAST_IF 16 -#define JAVASOCKOPT_MCAST_TTL 17 +#define JAVASOCKOPT_MULTICAST_TTL 17 #define JAVASOCKOPT_IP_MULTICAST_LOOP 18 #define JAVASOCKOPT_MCAST_ADD_MEMBERSHIP 19 #define JAVASOCKOPT_MCAST_DROP_MEMBERSHIP 20 @@ -141,10 +141,6 @@ #define SOCKET_STEP_CHECK 20 #define SOCKET_STEP_DONE 30 -#define BROKEN_MULTICAST_IF 1 -#define BROKEN_MULTICAST_TTL 2 -#define BROKEN_TCP_NODELAY 4 - #define SOCKET_CONNECT_STEP_START 0 #define SOCKET_CONNECT_STEP_CHECK 1 @@ -154,11 +150,7 @@ #define SOCKET_NOFLAGS 0 -// Local constants for getOrSetSocketOption -#define SOCKOPT_GET 1 -#define SOCKOPT_SET 2 - -struct CachedFields { +static struct CachedFields { jfieldID fd_descriptor; jclass iaddr_class; jmethodID iaddr_getbyaddress; @@ -173,7 +165,6 @@ struct CachedFields { jmethodID boolean_class_init; jfieldID boolean_class_value; jclass byte_class; - jmethodID byte_class_init; jfieldID byte_class_value; jclass socketimpl_class; jfieldID socketimpl_address; @@ -308,7 +299,7 @@ static int getSocketAddressFamily(int socket) { } } -jobject byteArrayToInetAddress(JNIEnv* env, jbyteArray byteArray) { +static jobject byteArrayToInetAddress(JNIEnv* env, jbyteArray byteArray) { if (byteArray == NULL) { return NULL; } @@ -596,23 +587,6 @@ static jobject newJavaLangBoolean(JNIEnv * env, jint anInt) { } /** - * Answer a new java.lang.Byte object. - * - * @param env pointer to the JNI library - * @param anInt the Byte constructor argument - * - * @return the new Byte - */ -static jobject newJavaLangByte(JNIEnv * env, jbyte val) { - jclass tempClass; - jmethodID tempMethod; - - tempClass = gCachedFields.byte_class; - tempMethod = gCachedFields.byte_class_init; - return env->NewObject(tempClass, tempMethod, val); -} - -/** * Answer a new java.lang.Integer object. * * @param env pointer to the JNI library @@ -1143,7 +1117,7 @@ static const char *sockoptLevelToString(int level) { * * @note on internal failure, the errno variable will be set appropriately */ -static int getOrSetSocketOption(int action, int socket, int ipv4Option, +static int getSocketOption(int socket, int ipv4Option, int ipv6Option, void *optionValue, socklen_t *optionLength) { int option; int protocol; @@ -1164,28 +1138,12 @@ static int getOrSetSocketOption(int action, int socket, int ipv4Option, return -1; } - int ret; - if (action == SOCKOPT_GET) { - ret = getsockopt(socket, protocol, option, optionValue, optionLength); + int ret = getsockopt(socket, protocol, option, optionValue, optionLength); #if LOG_SOCKOPT - LOGI("getsockopt(%d, %s, %d, %p, [%d]) = %d %s", + LOGI("getsockopt(%d, %s, %d, %p, [%d]) = %d %s", socket, sockoptLevelToString(protocol), option, optionValue, *optionLength, ret, (ret == -1) ? strerror(errno) : ""); #endif - } else if (action == SOCKOPT_SET) { - ret = setsockopt(socket, protocol, option, optionValue, *optionLength); -#if LOG_SOCKOPT - LOGI("setsockopt(%d, %s, %d, [%d], %d) = %d %s", - socket, sockoptLevelToString(protocol), option, - // Note: this only works for integer options. - // TODO: Use dvmPrintHexDump() to log non-integer options. - *(int *)optionValue, *optionLength, ret, - (ret == -1) ? strerror(errno) : ""); -#endif - } else { - errno = EINVAL; - ret = -1; - } return ret; } @@ -1202,29 +1160,22 @@ static int getOrSetSocketOption(int action, int socket, int ipv4Option, */ static int interfaceIndexFromMulticastSocket(int socket) { int family = getSocketAddressFamily(socket); - int interfaceIndex; - int result; if (family == AF_INET) { // IP_MULTICAST_IF returns a pointer to a struct ip_mreqn. struct ip_mreqn tempRequest; socklen_t requestLength = sizeof(tempRequest); - result = getsockopt(socket, IPPROTO_IP, IP_MULTICAST_IF, &tempRequest, - &requestLength); - interfaceIndex = tempRequest.imr_ifindex; + int rc = getsockopt(socket, IPPROTO_IP, IP_MULTICAST_IF, &tempRequest, &requestLength); + return (rc == -1) ? -1 : tempRequest.imr_ifindex; } else if (family == AF_INET6) { // IPV6_MULTICAST_IF returns a pointer to an integer. + int interfaceIndex; socklen_t requestLength = sizeof(interfaceIndex); - result = getsockopt(socket, IPPROTO_IPV6, IPV6_MULTICAST_IF, - &interfaceIndex, &requestLength); + int rc = getsockopt(socket, IPPROTO_IPV6, IPV6_MULTICAST_IF, &interfaceIndex, &requestLength); + return (rc == -1) ? -1 : interfaceIndex; } else { errno = EAFNOSUPPORT; return -1; } - - if (result == 0) - return interfaceIndex; - else - return -1; } /** @@ -1248,8 +1199,7 @@ static int interfaceIndexFromMulticastSocket(int socket) { * * @exception SocketException if an error occurs during the call */ -static void mcastAddDropMembership(JNIEnv *env, int handle, jobject optVal, - int ignoreIF, int setSockOptVal) { +static void mcastAddDropMembership(JNIEnv *env, int handle, jobject optVal, int setSockOptVal) { struct sockaddr_storage sockaddrP; int result; // By default, let the system decide which interface to use. @@ -1261,7 +1211,7 @@ static void mcastAddDropMembership(JNIEnv *env, int handle, jobject optVal, * is passed in, only support IPv4 as obtaining an interface from an * InetAddress is complex and should be done by the Java caller. */ - if (env->IsInstanceOf (optVal, gCachedFields.iaddr_class)) { + if (env->IsInstanceOf(optVal, gCachedFields.iaddr_class)) { /* * optVal is an InetAddress. Construct a multicast request structure * from this address. Support IPv4 only. @@ -1270,14 +1220,11 @@ static void mcastAddDropMembership(JNIEnv *env, int handle, jobject optVal, socklen_t length = sizeof(multicastRequest); memset(&multicastRequest, 0, length); - // If ignoreIF is false, determine the index of the interface to use. - if (!ignoreIF) { - interfaceIndex = interfaceIndexFromMulticastSocket(handle); - multicastRequest.imr_ifindex = interfaceIndex; - if (interfaceIndex == -1) { - jniThrowSocketException(env, errno); - return; - } + interfaceIndex = interfaceIndexFromMulticastSocket(handle); + multicastRequest.imr_ifindex = interfaceIndex; + if (interfaceIndex == -1) { + jniThrowSocketException(env, errno); + return; } // Convert the inetAddress to an IPv4 address structure. @@ -1303,21 +1250,15 @@ static void mcastAddDropMembership(JNIEnv *env, int handle, jobject optVal, * it and construct a multicast request structure from these. Support * both IPv4 and IPv6. */ - jclass cls; - jfieldID multiaddrID; - jfieldID interfaceIdxID; - jobject multiaddr; // Get the multicast address to join or leave. - cls = env->GetObjectClass(optVal); - multiaddrID = env->GetFieldID(cls, "multiaddr", "Ljava/net/InetAddress;"); - multiaddr = env->GetObjectField(optVal, multiaddrID); + jclass cls = env->GetObjectClass(optVal); + jfieldID multiaddrID = env->GetFieldID(cls, "multiaddr", "Ljava/net/InetAddress;"); + jobject multiaddr = env->GetObjectField(optVal, multiaddrID); // Get the interface index to use. - if (! ignoreIF) { - interfaceIdxID = env->GetFieldID(cls, "interfaceIdx", "I"); - interfaceIndex = env->GetIntField(optVal, interfaceIdxID); - } + jfieldID interfaceIdxID = env->GetFieldID(cls, "interfaceIdx", "I"); + interfaceIndex = env->GetIntField(optVal, interfaceIdxID); LOGI("mcastAddDropMembership interfaceIndex=%i", interfaceIndex); if (!inetAddressToSocketAddress(env, multiaddr, 0, &sockaddrP)) { @@ -1412,7 +1353,6 @@ static bool initCachedFields(JNIEnv* env) { {&c->i4addr_class_init, c->i4addr_class, "<init>", "([B)V", false}, {&c->integer_class_init, c->integer_class, "<init>", "(I)V", false}, {&c->boolean_class_init, c->boolean_class, "<init>", "(Z)V", false}, - {&c->byte_class_init, c->byte_class, "<init>", "(B)V", false}, {&c->iaddr_getbyaddress, c->iaddr_class, "getByAddress", "([B)Ljava/net/InetAddress;", true} }; @@ -1489,8 +1429,19 @@ static void osNetworkSystem_createStreamSocket(JNIEnv* env, jobject, jobject fil createSocketFileDescriptor(env, fileDescriptor, SOCK_STREAM); } -static void osNetworkSystem_createDatagramSocket(JNIEnv* env, jobject, jobject fd, jboolean) { - createSocketFileDescriptor(env, fd, SOCK_DGRAM); +static void osNetworkSystem_createDatagramSocket(JNIEnv* env, jobject, jobject fileDescriptor, jboolean) { + int fd = createSocketFileDescriptor(env, fileDescriptor, SOCK_DGRAM); +#ifdef __linux__ + // The RFC (http://tools.ietf.org/rfc/rfc3493.txt) says that IPV6_MULTICAST_HOPS defaults to 1. + // The Linux kernel (at least up to 2.6.32) accidentally defaults to 64 (which would be correct + // for the *unicast* hop limit). See http://www.spinics.net/lists/netdev/msg129022.html. + // When that's fixed, we can remove this code. Until then, we manually set the hop limit on + // IPv6 datagram sockets. (IPv4 is already correct.) + if (fd != -1 && getSocketAddressFamily(fd) == AF_INET6) { + int ttl = 1; + setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(int)); + } +#endif } static jint osNetworkSystem_readDirect(JNIEnv* env, jobject, @@ -2353,389 +2304,225 @@ static jint osNetworkSystem_getSocketLocalPort(JNIEnv* env, jobject, return getSocketAddressPort(&addr); } -static jobject osNetworkSystem_getSocketOption(JNIEnv* env, jobject, - jobject fileDescriptor, jint anOption) { - int intValue = 0; - socklen_t intSize = sizeof(int); - int result; - struct sockaddr_storage sockVal; - socklen_t sockSize = sizeof(sockVal); - - int handle; - if (!jniGetFd(env, fileDescriptor, handle)) { - return 0; +template <typename T> +static bool getSocketOption(JNIEnv* env, int fd, int level, int option, T* value) { + socklen_t size = sizeof(*value); + int rc = getsockopt(fd, level, option, value, &size); + if (rc == -1) { + LOGE("getSocketOption(fd=%i, level=%i, option=%i) failed: %s (errno=%i)", + fd, level, option, strerror(errno), errno); + jniThrowSocketException(env, errno); + return false; } + return true; +} - switch ((int) anOption & 0xffff) { - case JAVASOCKOPT_SO_LINGER: { - struct linger lingr; - socklen_t size = sizeof(struct linger); - result = getsockopt(handle, SOL_SOCKET, SO_LINGER, &lingr, &size); - if (0 != result) { - jniThrowSocketException(env, errno); - return NULL; - } - if (!lingr.l_onoff) { - intValue = -1; - } else { - intValue = lingr.l_linger; - } - return newJavaLangInteger(env, intValue); - } - - case JAVASOCKOPT_TCP_NODELAY: { - if ((anOption >> 16) & BROKEN_TCP_NODELAY) { - return NULL; - } - result = getsockopt(handle, IPPROTO_TCP, TCP_NODELAY, &intValue, &intSize); - if (0 != result) { - jniThrowSocketException(env, errno); - return NULL; - } - return newJavaLangBoolean(env, intValue); - } - - case JAVASOCKOPT_SO_SNDBUF: { - result = getsockopt(handle, SOL_SOCKET, SO_SNDBUF, &intValue, &intSize); - if (0 != result) { - jniThrowSocketException(env, errno); - return NULL; - } - return newJavaLangInteger(env, intValue); - } - - case JAVASOCKOPT_SO_RCVBUF: { - result = getsockopt(handle, SOL_SOCKET, SO_RCVBUF, &intValue, &intSize); - if (0 != result) { - jniThrowSocketException(env, errno); - return NULL; - } - return newJavaLangInteger(env, intValue); - } +static jobject getSocketOption_Boolean(JNIEnv* env, int fd, int level, int option) { + int value; + return getSocketOption(env, fd, level, option, &value) ? newJavaLangBoolean(env, value) : NULL; +} - case JAVASOCKOPT_SO_BROADCAST: { - result = getsockopt(handle, SOL_SOCKET, SO_BROADCAST, &intValue, &intSize); - if (0 != result) { - jniThrowSocketException(env, errno); - return NULL; - } - return newJavaLangBoolean(env, intValue); - } +static jobject getSocketOption_Integer(JNIEnv* env, int fd, int level, int option) { + int value; + return getSocketOption(env, fd, level, option, &value) ? newJavaLangInteger(env, value) : NULL; +} - case JAVASOCKOPT_SO_REUSEADDR: { - result = getsockopt(handle, SOL_SOCKET, SO_REUSEADDR, &intValue, &intSize); - if (0 != result) { - jniThrowSocketException(env, errno); - return NULL; - } - return newJavaLangBoolean(env, intValue); - } +static jobject osNetworkSystem_getSocketOption(JNIEnv* env, jobject, jobject fileDescriptor, jint option) { + int fd; + if (!jniGetFd(env, fileDescriptor, fd)) { + return NULL; + } - case JAVASOCKOPT_SO_KEEPALIVE: { - result = getsockopt(handle, SOL_SOCKET, SO_KEEPALIVE, &intValue, &intSize); - if (0 != result) { - jniThrowSocketException(env, errno); - return NULL; - } - return newJavaLangBoolean(env, intValue); - } + int family = getSocketAddressFamily(fd); + if (family != AF_INET && family != AF_INET6) { + jniThrowSocketException(env, EAFNOSUPPORT); + return NULL; + } - case JAVASOCKOPT_SO_OOBINLINE: { - result = getsockopt(handle, SOL_SOCKET, SO_OOBINLINE, &intValue, &intSize); - if (0 != result) { - jniThrowSocketException(env, errno); - return NULL; - } - return newJavaLangBoolean(env, intValue); + switch (option) { + case JAVASOCKOPT_TCP_NODELAY: + return getSocketOption_Boolean(env, fd, IPPROTO_TCP, TCP_NODELAY); + case JAVASOCKOPT_SO_SNDBUF: + return getSocketOption_Integer(env, fd, SOL_SOCKET, SO_SNDBUF); + case JAVASOCKOPT_SO_RCVBUF: + return getSocketOption_Integer(env, fd, SOL_SOCKET, SO_RCVBUF); + case JAVASOCKOPT_SO_BROADCAST: + return getSocketOption_Boolean(env, fd, SOL_SOCKET, SO_BROADCAST); + case JAVASOCKOPT_SO_REUSEADDR: + return getSocketOption_Boolean(env, fd, SOL_SOCKET, SO_REUSEADDR); + case JAVASOCKOPT_SO_KEEPALIVE: + return getSocketOption_Boolean(env, fd, SOL_SOCKET, SO_KEEPALIVE); + case JAVASOCKOPT_SO_OOBINLINE: + return getSocketOption_Boolean(env, fd, SOL_SOCKET, SO_OOBINLINE); + case JAVASOCKOPT_IP_TOS: + if (family == AF_INET) { + return getSocketOption_Boolean(env, fd, IPPROTO_IP, IP_TOS); + } else { + return getSocketOption_Boolean(env, fd, IPPROTO_IPV6, IPV6_TCLASS); } - - case JAVASOCKOPT_IP_TOS: { - result = getOrSetSocketOption(SOCKOPT_GET, handle, IP_TOS, - IPV6_TCLASS, &intValue, &intSize); - if (0 != result) { - jniThrowSocketException(env, errno); - return NULL; - } - return newJavaLangInteger(env, intValue); + case JAVASOCKOPT_SO_LINGER: + { + linger lingr; + bool ok = getSocketOption(env, fd, SOL_SOCKET, SO_LINGER, &lingr); + return ok ? newJavaLangInteger(env, !lingr.l_onoff ? -1 : lingr.l_linger) : NULL; } - - case JAVASOCKOPT_SO_RCVTIMEOUT: { - struct timeval timeout; - socklen_t size = sizeof(timeout); - result = getsockopt(handle, SOL_SOCKET, SO_RCVTIMEO, &timeout, &size); - if (0 != result) { - jniThrowSocketException(env, errno); - return NULL; - } - return newJavaLangInteger(env, toMs(timeout)); + case JAVASOCKOPT_SO_RCVTIMEOUT: + { + timeval timeout; + bool ok = getSocketOption(env, fd, SOL_SOCKET, SO_RCVTIMEO, &timeout); + return ok ? newJavaLangInteger(env, toMs(timeout)) : NULL; } - #ifdef ENABLE_MULTICAST - case JAVASOCKOPT_MCAST_TTL: { - if ((anOption >> 16) & BROKEN_MULTICAST_TTL) { - return newJavaLangByte(env, 0); - } - // Java uses a byte to store the TTL, but the kernel uses an int. - result = getOrSetSocketOption(SOCKOPT_GET, handle, IP_MULTICAST_TTL, - IPV6_MULTICAST_HOPS, &intValue, - &intSize); - if (0 != result) { - jniThrowSocketException(env, errno); - return NULL; - } - return newJavaLangByte(env, (jbyte)(intValue & 0xFF)); - } - - case JAVASOCKOPT_IP_MULTICAST_IF: { - if ((anOption >> 16) & BROKEN_MULTICAST_IF) { - return NULL; - } - result = getsockopt(handle, IPPROTO_IP, IP_MULTICAST_IF, - &sockVal, &sockSize); - if (result == -1) { - jniThrowSocketException(env, errno); + case JAVASOCKOPT_IP_MULTICAST_IF: + { + struct sockaddr_storage sockVal; + if (!getSocketOption(env, fd, IPPROTO_IP, IP_MULTICAST_IF, &sockVal)) { return NULL; } if (sockVal.ss_family != AF_INET) { + LOGE("sockVal.ss_family != AF_INET (%i)", sockVal.ss_family); // Java expects an AF_INET INADDR_ANY, but Linux just returns AF_UNSPEC. jbyteArray inAddrAny = env->NewByteArray(4); // { 0, 0, 0, 0 } return byteArrayToInetAddress(env, inAddrAny); } return socketAddressToInetAddress(env, &sockVal); } - - case JAVASOCKOPT_IP_MULTICAST_IF2: { - if ((anOption >> 16) & BROKEN_MULTICAST_IF) { - return NULL; - } + case JAVASOCKOPT_IP_MULTICAST_IF2: + if (family == AF_INET) { struct ip_mreqn multicastRequest; - int interfaceIndex = 0; - socklen_t optionLength; - int addressFamily = getSocketAddressFamily(handle); - switch (addressFamily) { - case AF_INET: - optionLength = sizeof(multicastRequest); - result = getsockopt(handle, IPPROTO_IP, IP_MULTICAST_IF, - &multicastRequest, &optionLength); - if (result == 0) - interfaceIndex = multicastRequest.imr_ifindex; - break; - case AF_INET6: - optionLength = sizeof(interfaceIndex); - result = getsockopt(handle, IPPROTO_IPV6, IPV6_MULTICAST_IF, - &interfaceIndex, &optionLength); - break; - default: - jniThrowSocketException(env, EAFNOSUPPORT); - return NULL; - } - - if (0 != result) { - jniThrowSocketException(env, errno); - return NULL; - } - return newJavaLangInteger(env, interfaceIndex); - } - - case JAVASOCKOPT_IP_MULTICAST_LOOP: { - result = getOrSetSocketOption(SOCKOPT_GET, handle, - IP_MULTICAST_LOOP, - IPV6_MULTICAST_LOOP, &intValue, - &intSize); - if (0 != result) { - jniThrowSocketException(env, errno); - return NULL; - } - return newJavaLangBoolean(env, intValue); + bool ok = getSocketOption(env, fd, IPPROTO_IP, IP_MULTICAST_IF, &multicastRequest); + return ok ? newJavaLangInteger(env, multicastRequest.imr_ifindex) : NULL; + } else { + return getSocketOption_Integer(env, fd, IPPROTO_IPV6, IPV6_MULTICAST_IF); + } + case JAVASOCKOPT_IP_MULTICAST_LOOP: + if (family == AF_INET) { + // Although IPv6 was cleaned up to use int, IPv4 multicast loopback uses a byte. + u_char loopback; + bool ok = getSocketOption(env, fd, IPPROTO_IP, IP_MULTICAST_LOOP, &loopback); + return ok ? newJavaLangBoolean(env, loopback) : NULL; + } else { + return getSocketOption_Boolean(env, fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP); + } + case JAVASOCKOPT_MULTICAST_TTL: + if (family == AF_INET) { + // Although IPv6 was cleaned up to use int, and IPv4 non-multicast TTL uses int, + // IPv4 multicast TTL uses a byte. + u_char ttl; + bool ok = getSocketOption(env, fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl); + return ok ? newJavaLangInteger(env, ttl) : NULL; + } else { + return getSocketOption_Integer(env, fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS); } #else - case JAVASOCKOPT_MCAST_TTL: - case JAVASOCKOPT_IP_MULTICAST_IF: - case JAVASOCKOPT_IP_MULTICAST_IF2: - case JAVASOCKOPT_IP_MULTICAST_LOOP: { - jniThrowException(env, "java/lang/UnsupportedOperationException", NULL); - return NULL; - } + case JAVASOCKOPT_MULTICAST_TTL: + case JAVASOCKOPT_IP_MULTICAST_IF: + case JAVASOCKOPT_IP_MULTICAST_IF2: + case JAVASOCKOPT_IP_MULTICAST_LOOP: + jniThrowException(env, "java/lang/UnsupportedOperationException", NULL); + return NULL; #endif // def ENABLE_MULTICAST - - default: { - jniThrowSocketException(env, ENOPROTOOPT); - return NULL; - } + default: + jniThrowSocketException(env, ENOPROTOOPT); + return NULL; } +} +template <typename T> +static void setSocketOption(JNIEnv* env, int fd, int level, int option, T* value) { + int rc = setsockopt(fd, level, option, value, sizeof(*value)); + if (rc == -1) { + LOGE("setSocketOption(fd=%i, level=%i, option=%i) failed: %s (errno=%i)", + fd, level, option, strerror(errno), errno); + jniThrowSocketException(env, errno); + } } -static void osNetworkSystem_setSocketOption(JNIEnv* env, jobject, - jobject fileDescriptor, jint anOption, jobject optVal) { - int result; - int intVal; - socklen_t intSize = sizeof(int); - struct sockaddr_storage sockVal; - int sockSize = sizeof(sockVal); +static void osNetworkSystem_setSocketOption(JNIEnv* env, jobject, jobject fileDescriptor, jint option, jobject optVal) { + int fd; + if (!jniGetFd(env, fileDescriptor, fd)) { + return; + } + int intVal; if (env->IsInstanceOf(optVal, gCachedFields.integer_class)) { intVal = (int) env->GetIntField(optVal, gCachedFields.integer_class_value); } else if (env->IsInstanceOf(optVal, gCachedFields.boolean_class)) { intVal = (int) env->GetBooleanField(optVal, gCachedFields.boolean_class_value); } else if (env->IsInstanceOf(optVal, gCachedFields.byte_class)) { - // TTL uses a byte in Java, but the kernel still wants an int. intVal = (int) env->GetByteField(optVal, gCachedFields.byte_class_value); - } else if (env->IsInstanceOf(optVal, gCachedFields.iaddr_class)) { - if (!inetAddressToSocketAddress(env, optVal, 0, &sockVal)) { - return; - } - } else if (env->IsInstanceOf(optVal, gCachedFields.genericipmreq_class)) { + } else if (env->IsInstanceOf(optVal, gCachedFields.genericipmreq_class) || env->IsInstanceOf(optVal, gCachedFields.iaddr_class)) { // we'll use optVal directly } else { - jniThrowSocketException(env, ENOPROTOOPT); + jniThrowSocketException(env, EINVAL); return; } - int handle; - if (!jniGetFd(env, fileDescriptor, handle)) { + int family = getSocketAddressFamily(fd); + if (family != AF_INET && family != AF_INET6) { + jniThrowSocketException(env, EAFNOSUPPORT); return; } - switch ((int) anOption & 0xffff) { - case JAVASOCKOPT_SO_LINGER: { - struct linger lingr; + switch (option) { + case JAVASOCKOPT_SO_LINGER: + { + linger lingr; lingr.l_onoff = intVal > 0 ? 1 : 0; lingr.l_linger = intVal; - result = setsockopt(handle, SOL_SOCKET, SO_LINGER, &lingr, - sizeof(struct linger)); - if (0 != result) { - jniThrowSocketException(env, errno); - return; - } - break; - } - - case JAVASOCKOPT_TCP_NODELAY: { - if ((anOption >> 16) & BROKEN_TCP_NODELAY) { - return; - } - result = setsockopt(handle, IPPROTO_TCP, TCP_NODELAY, &intVal, intSize); - if (0 != result) { - jniThrowSocketException(env, errno); - return; - } - break; - } - - case JAVASOCKOPT_SO_SNDBUF: { - result = setsockopt(handle, SOL_SOCKET, SO_SNDBUF, &intVal, intSize); - if (0 != result) { - jniThrowSocketException(env, errno); - return; - } - break; - } - - case JAVASOCKOPT_SO_RCVBUF: { - result = setsockopt(handle, SOL_SOCKET, SO_RCVBUF, &intVal, intSize); - if (0 != result) { - jniThrowSocketException(env, errno); - return; - } - break; - } - - case JAVASOCKOPT_SO_BROADCAST: { - result = setsockopt(handle, SOL_SOCKET, SO_BROADCAST, &intVal, intSize); - if (0 != result) { - jniThrowSocketException(env, errno); - return; - } - break; - } - - case JAVASOCKOPT_SO_REUSEADDR: { - result = setsockopt(handle, SOL_SOCKET, SO_REUSEADDR, &intVal, intSize); - if (0 != result) { - jniThrowSocketException(env, errno); - return; - } - break; - } - case JAVASOCKOPT_SO_KEEPALIVE: { - result = setsockopt(handle, SOL_SOCKET, SO_KEEPALIVE, &intVal, intSize); - if (0 != result) { - jniThrowSocketException(env, errno); - return; - } - break; - } - - case JAVASOCKOPT_SO_OOBINLINE: { - result = setsockopt(handle, SOL_SOCKET, SO_OOBINLINE, &intVal, intSize); - if (0 != result) { - jniThrowSocketException(env, errno); - return; - } - break; - } - - case JAVASOCKOPT_IP_TOS: { - result = getOrSetSocketOption(SOCKOPT_SET, handle, IP_TOS, - IPV6_TCLASS, &intVal, &intSize); - if (0 != result) { - jniThrowSocketException(env, errno); - return; - } - break; - } - - case JAVASOCKOPT_REUSEADDR_AND_REUSEPORT: { - // SO_REUSEPORT doesn't need to get set on this System - result = setsockopt(handle, SOL_SOCKET, SO_REUSEADDR, &intVal, intSize); - if (0 != result) { - jniThrowSocketException(env, errno); - return; - } - break; + setSocketOption(env, fd, SOL_SOCKET, SO_LINGER, &lingr); + return; } - - case JAVASOCKOPT_SO_RCVTIMEOUT: { + case JAVASOCKOPT_SO_SNDBUF: + setSocketOption(env, fd, SOL_SOCKET, SO_SNDBUF, &intVal); + return; + case JAVASOCKOPT_SO_RCVBUF: + setSocketOption(env, fd, SOL_SOCKET, SO_RCVBUF, &intVal); + return; + case JAVASOCKOPT_SO_BROADCAST: + setSocketOption(env, fd, SOL_SOCKET, SO_BROADCAST, &intVal); + return; + case JAVASOCKOPT_SO_REUSEADDR: + setSocketOption(env, fd, SOL_SOCKET, SO_REUSEADDR, &intVal); + return; + case JAVASOCKOPT_SO_KEEPALIVE: + setSocketOption(env, fd, SOL_SOCKET, SO_KEEPALIVE, &intVal); + return; + case JAVASOCKOPT_SO_OOBINLINE: + setSocketOption(env, fd, SOL_SOCKET, SO_OOBINLINE, &intVal); + return; + case JAVASOCKOPT_REUSEADDR_AND_REUSEPORT: + // SO_REUSEPORT doesn't need to get set on this System + setSocketOption(env, fd, SOL_SOCKET, SO_REUSEADDR, &intVal); + return; + case JAVASOCKOPT_SO_RCVTIMEOUT: + { timeval timeout(toTimeval(intVal)); - result = setsockopt(handle, SOL_SOCKET, SO_RCVTIMEO, &timeout, - sizeof(struct timeval)); - if (0 != result) { - jniThrowSocketException(env, errno); - return; - } - break; - } - -#ifdef ENABLE_MULTICAST - case JAVASOCKOPT_MCAST_TTL: { - if ((anOption >> 16) & BROKEN_MULTICAST_TTL) { - return; - } - result = getOrSetSocketOption(SOCKOPT_SET, handle, IP_MULTICAST_TTL, - IPV6_MULTICAST_HOPS, &intVal, - &intSize); - if (0 != result) { - jniThrowSocketException(env, errno); - return; - } - break; - } - - case JAVASOCKOPT_MCAST_ADD_MEMBERSHIP: { - mcastAddDropMembership(env, handle, optVal, - (anOption >> 16) & BROKEN_MULTICAST_IF, IP_ADD_MEMBERSHIP); - break; + setSocketOption(env, fd, SOL_SOCKET, SO_RCVTIMEO, &timeout); + return; } - - case JAVASOCKOPT_MCAST_DROP_MEMBERSHIP: { - mcastAddDropMembership(env, handle, optVal, - (anOption >> 16) & BROKEN_MULTICAST_IF, IP_DROP_MEMBERSHIP); - break; + case JAVASOCKOPT_IP_TOS: + if (family == AF_INET) { + setSocketOption(env, fd, IPPROTO_IP, IP_TOS, &intVal); + } else { + setSocketOption(env, fd, IPPROTO_IPV6, IPV6_TCLASS, &intVal); } - - case JAVASOCKOPT_IP_MULTICAST_IF: { - if ((anOption >> 16) & BROKEN_MULTICAST_IF) { + return; + case JAVASOCKOPT_TCP_NODELAY: + setSocketOption(env, fd, IPPROTO_TCP, TCP_NODELAY, &intVal); + return; +#ifdef ENABLE_MULTICAST + case JAVASOCKOPT_MCAST_ADD_MEMBERSHIP: + mcastAddDropMembership(env, fd, optVal, IP_ADD_MEMBERSHIP); + return; + case JAVASOCKOPT_MCAST_DROP_MEMBERSHIP: + mcastAddDropMembership(env, fd, optVal, IP_DROP_MEMBERSHIP); + return; + case JAVASOCKOPT_IP_MULTICAST_IF: + { + struct sockaddr_storage sockVal; + if (!env->IsInstanceOf(optVal, gCachedFields.iaddr_class) || + !inetAddressToSocketAddress(env, optVal, 0, &sockVal)) { return; } // This call is IPv4 only. The socket may be IPv6, but the address @@ -2746,79 +2533,53 @@ static void osNetworkSystem_setSocketOption(JNIEnv* env, jobject, } struct ip_mreqn mcast_req; memset(&mcast_req, 0, sizeof(mcast_req)); - struct sockaddr_in *sin = (struct sockaddr_in *) &sockVal; - mcast_req.imr_address = sin->sin_addr; - result = setsockopt(handle, IPPROTO_IP, IP_MULTICAST_IF, - &mcast_req, sizeof(mcast_req)); - if (0 != result) { - jniThrowSocketException(env, errno); - return; - } - break; + mcast_req.imr_address = reinterpret_cast<sockaddr_in*>(&sockVal)->sin_addr; + setSocketOption(env, fd, IPPROTO_IP, IP_MULTICAST_IF, &mcast_req); + return; } - - case JAVASOCKOPT_IP_MULTICAST_IF2: { - if ((anOption >> 16) & BROKEN_MULTICAST_IF) { - return; - } - int addressFamily = getSocketAddressFamily(handle); - int interfaceIndex = intVal; - void *optionValue; - socklen_t optionLength; + case JAVASOCKOPT_IP_MULTICAST_IF2: + if (family == AF_INET) { + // IP_MULTICAST_IF expects a pointer to a struct ip_mreqn. struct ip_mreqn multicastRequest; - switch (addressFamily) { - case AF_INET: - // IP_MULTICAST_IF expects a pointer to a struct ip_mreqn. - memset(&multicastRequest, 0, sizeof(multicastRequest)); - multicastRequest.imr_ifindex = interfaceIndex; - optionValue = &multicastRequest; - optionLength = sizeof(multicastRequest); - break; - case AF_INET6: - // IPV6_MULTICAST_IF expects a pointer to an integer. - optionValue = &interfaceIndex; - optionLength = sizeof(interfaceIndex); - break; - default: - jniThrowSocketException(env, EAFNOSUPPORT); - return; - } - result = getOrSetSocketOption(SOCKOPT_SET, handle, - IP_MULTICAST_IF, IPV6_MULTICAST_IF, optionValue, - &optionLength); - if (0 != result) { - jniThrowSocketException(env, errno); - return; - } - break; + memset(&multicastRequest, 0, sizeof(multicastRequest)); + multicastRequest.imr_ifindex = intVal; + setSocketOption(env, fd, IPPROTO_IP, IP_MULTICAST_IF, &multicastRequest); + } else { + // IPV6_MULTICAST_IF expects a pointer to an integer. + setSocketOption(env, fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &intVal); } - - case JAVASOCKOPT_IP_MULTICAST_LOOP: { - result = getOrSetSocketOption(SOCKOPT_SET, handle, - IP_MULTICAST_LOOP, - IPV6_MULTICAST_LOOP, &intVal, - &intSize); - if (0 != result) { - jniThrowSocketException(env, errno); - return; - } - break; + return; + case JAVASOCKOPT_MULTICAST_TTL: + if (family == AF_INET) { + // Although IPv6 was cleaned up to use int, and IPv4 non-multicast TTL uses int, + // IPv4 multicast TTL uses a byte. + u_char ttl = intVal; + setSocketOption(env, fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl); + } else { + setSocketOption(env, fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &intVal); } -#else - case JAVASOCKOPT_MCAST_TTL: - case JAVASOCKOPT_MCAST_ADD_MEMBERSHIP: - case JAVASOCKOPT_MCAST_DROP_MEMBERSHIP: - case JAVASOCKOPT_IP_MULTICAST_IF: - case JAVASOCKOPT_IP_MULTICAST_IF2: - case JAVASOCKOPT_IP_MULTICAST_LOOP: { - jniThrowException(env, "java/lang/UnsupportedOperationException", NULL); - return; + return; + case JAVASOCKOPT_IP_MULTICAST_LOOP: + if (family == AF_INET) { + // Although IPv6 was cleaned up to use int, IPv4 multicast loopback uses a byte. + u_char loopback = intVal; + setSocketOption(env, fd, IPPROTO_IP, IP_MULTICAST_LOOP, &loopback); + } else { + setSocketOption(env, fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &intVal); } + return; +#else + case JAVASOCKOPT_MULTICAST_TTL: + case JAVASOCKOPT_MCAST_ADD_MEMBERSHIP: + case JAVASOCKOPT_MCAST_DROP_MEMBERSHIP: + case JAVASOCKOPT_IP_MULTICAST_IF: + case JAVASOCKOPT_IP_MULTICAST_IF2: + case JAVASOCKOPT_IP_MULTICAST_LOOP: + jniThrowException(env, "java/lang/UnsupportedOperationException", NULL); + return; #endif // def ENABLE_MULTICAST - - default: { - jniThrowSocketException(env, ENOPROTOOPT); - } + default: + jniThrowSocketException(env, ENOPROTOOPT); } } diff --git a/luni/src/test/java/java/net/URLConnectionTest.java b/luni/src/test/java/java/net/URLConnectionTest.java index 6050c7d..9563256 100644 --- a/luni/src/test/java/java/net/URLConnectionTest.java +++ b/luni/src/test/java/java/net/URLConnectionTest.java @@ -16,12 +16,12 @@ package java.net; -import java.net.*; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.util.Arrays; +import java.util.UUID; import java.util.concurrent.atomic.AtomicInteger; import tests.support.Support_TestWebServer; @@ -99,7 +99,7 @@ public class URLConnectionTest extends junit.framework.TestCase { int n = 512*1024; AtomicInteger total = new AtomicInteger(0); ServerSocket ss = startSinkServer(total); - URL url = new URL("http://localhost:" + ss.getLocalPort() + "/test1"); + URL url = new URL("http://localhost:" + ss.getLocalPort() + "/" + UUID.randomUUID()); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setDoOutput(true); conn.setRequestMethod("POST"); @@ -121,7 +121,7 @@ public class URLConnectionTest extends junit.framework.TestCase { } } out.close(); - assertTrue(conn.getResponseCode() > 0); + assertEquals(200, conn.getResponseCode()); assertEquals(uploadKind == UploadKind.CHUNKED ? -1 : n, total.get()); } diff --git a/luni/src/test/java/javax/net/ssl/SSLContextTest.java b/luni/src/test/java/javax/net/ssl/SSLContextTest.java index 508aaaf..53ffe9c 100644 --- a/luni/src/test/java/javax/net/ssl/SSLContextTest.java +++ b/luni/src/test/java/javax/net/ssl/SSLContextTest.java @@ -17,32 +17,13 @@ package javax.net.ssl; import dalvik.annotation.KnownFailure; -import java.math.BigInteger; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.KeyStore; import java.security.Provider; -import java.security.SecureRandom; -import java.security.Security; -import java.security.cert.X509Certificate; -import java.security.interfaces.RSAPrivateKey; -import java.security.interfaces.RSAPublicKey; -import java.util.Date; -import java.util.Hashtable; import javax.net.ServerSocketFactory; import javax.net.SocketFactory; import junit.framework.TestCase; -import org.bouncycastle.jce.X509Principal; -import org.bouncycastle.jce.provider.BouncyCastleProvider; -import org.bouncycastle.x509.X509V3CertificateGenerator; public class SSLContextTest extends TestCase { - public static final boolean IS_RI = !"Dalvik Core Library".equals(System.getProperty("java.specification.name")); - public static final String PROVIDER_NAME = (IS_RI) ? "SunJSSE" : "HarmonyJSSE"; - public void test_SSLContext_getInstance() throws Exception { try { SSLContext.getInstance(null); @@ -73,7 +54,7 @@ public class SSLContextTest extends TestCase { } catch (IllegalArgumentException e) { } try { - SSLContext.getInstance(null, PROVIDER_NAME); + SSLContext.getInstance(null, TestSSLContext.PROVIDER_NAME); fail(); } catch (NullPointerException e) { } @@ -93,7 +74,7 @@ public class SSLContextTest extends TestCase { public void test_SSLContext_getProvider() throws Exception { Provider provider = SSLContext.getInstance("TLS").getProvider(); assertNotNull(provider); - assertEquals(PROVIDER_NAME, provider.getName()); + assertEquals(TestSSLContext.PROVIDER_NAME, provider.getName()); } public void test_SSLContext_init() throws Exception { @@ -176,186 +157,16 @@ public class SSLContextTest extends TestCase { sessionContext); } - /** - * SSLContextTest.Helper is a convenience class for other tests that - * want a canned SSLContext and related state for testing so they - * don't have to duplicate the logic. - */ - public static final class Helper { - - static { - if (SSLContextTest.IS_RI) { - Security.addProvider(new BouncyCastleProvider()); - } - } - - public final KeyStore keyStore; - public final char[] keyStorePassword; - public final String publicAlias; - public final String privateAlias; - public final SSLContext sslContext; - public final SSLServerSocket serverSocket; - public final InetAddress host; - public final int port; - - private Helper(KeyStore keyStore, - char[] keyStorePassword, - String publicAlias, - String privateAlias, - SSLContext sslContext, - SSLServerSocket serverSocket, - InetAddress host, - int port) { - this.keyStore = keyStore; - this.keyStorePassword = keyStorePassword; - this.publicAlias = publicAlias; - this.privateAlias = privateAlias; - this.sslContext = sslContext; - this.serverSocket = serverSocket; - this.host = host; - this.port = port; - } - - public static Helper create() { - try { - char[] keyStorePassword = null; - String publicAlias = "public"; - String privateAlias = "private"; - return create(createKeyStore(keyStorePassword, publicAlias, privateAlias), - null, - publicAlias, - privateAlias); - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public static Helper create(KeyStore keyStore, - char[] keyStorePassword, - String publicAlias, - String privateAlias) { - try { - SSLContext sslContext = createSSLContext(keyStore, keyStorePassword); - - SSLServerSocket serverSocket = (SSLServerSocket) - sslContext.getServerSocketFactory().createServerSocket(0); - InetSocketAddress sa = (InetSocketAddress) serverSocket.getLocalSocketAddress(); - InetAddress host = sa.getAddress(); - int port = sa.getPort(); - - return new Helper(keyStore, keyStorePassword, publicAlias, privateAlias, - sslContext, serverSocket, host, port); - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - /** - * Create a BKS KeyStore containing an RSAPrivateKey with alias - * "private" and a X509Certificate based on the matching - * RSAPublicKey stored under the alias name publicAlias. - * - * The private key will have a certificate chain including the - * certificate stored under the alias name privateAlias. The - * certificate will be signed by the private key. The certificate - * Subject and Issuer Common-Name will be the local host's - * canonical hostname. The certificate will be valid for one day - * before and one day after the time of creation. - * - * The KeyStore is optionally password protected by the - * keyStorePassword argument, which can be null if a password is - * not desired. - * - * Based on: - * org.bouncycastle.jce.provider.test.SigTest - * org.bouncycastle.jce.provider.test.CertTest - */ - public static KeyStore createKeyStore(char[] keyStorePassword, - String publicAlias, - String privateAlias) - throws Exception { - - // 1.) we make the keys - int keysize = 1024; - KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); - kpg.initialize(keysize, new SecureRandom()); - KeyPair kp = kpg.generateKeyPair(); - RSAPrivateKey privateKey = (RSAPrivateKey)kp.getPrivate(); - RSAPublicKey publicKey = (RSAPublicKey)kp.getPublic(); - - // 2.) use keys to make certficate - - // note that there doesn't seem to be a standard way to make a - // certificate using java.* or javax.*. The CertificateFactory - // interface assumes you want to read in a stream of bytes a - // factory specific format. So here we use Bouncy Castle's - // X509V3CertificateGenerator and related classes. - - Hashtable attributes = new Hashtable(); - attributes.put(X509Principal.CN, InetAddress.getLocalHost().getCanonicalHostName()); - X509Principal dn = new X509Principal(attributes); - - long millisPerDay = 24 * 60 * 60 * 1000; - long now = System.currentTimeMillis(); - Date start = new Date(now - millisPerDay); - Date end = new Date(now + millisPerDay); - BigInteger serial = BigInteger.valueOf(1); - - X509V3CertificateGenerator x509cg = new X509V3CertificateGenerator(); - x509cg.setSubjectDN(dn); - x509cg.setIssuerDN(dn); - x509cg.setNotBefore(start); - x509cg.setNotAfter(end); - x509cg.setPublicKey(publicKey); - x509cg.setSignatureAlgorithm("sha1WithRSAEncryption"); - x509cg.setSerialNumber(serial); - X509Certificate x509c = x509cg.generateX509Certificate(privateKey); - X509Certificate[] x509cc = new X509Certificate[] { x509c }; - - - // 3.) put certificate and private key to make a key store - KeyStore ks = KeyStore.getInstance("BKS"); - ks.load(null, null); - ks.setKeyEntry(privateAlias, privateKey, keyStorePassword, x509cc); - ks.setCertificateEntry(publicAlias, x509c); - return ks; - } - - /** - * Create a SSLContext with a KeyManager using the private key and - * certificate chain from the given KeyStore and a TrustManager - * using the certificates authorities from the same KeyStore. - */ - public static final SSLContext createSSLContext(final KeyStore keyStore, final char[] keyStorePassword) - throws Exception { - String kmfa = KeyManagerFactory.getDefaultAlgorithm(); - KeyManagerFactory kmf = KeyManagerFactory.getInstance(kmfa); - kmf.init(keyStore, keyStorePassword); - - String tmfa = TrustManagerFactory.getDefaultAlgorithm(); - TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfa); - tmf.init(keyStore); - - SSLContext context = SSLContext.getInstance("TLS"); - context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom()); - return context; - } - } - - public void test_SSLContextTest_Helper_create() { - Helper helper = Helper.create(); - assertNotNull(helper); - assertNotNull(helper.keyStore); - assertNull(helper.keyStorePassword); - assertNotNull(helper.publicAlias); - assertNotNull(helper.privateAlias); - assertNotNull(helper.sslContext); - assertNotNull(helper.serverSocket); - assertNotNull(helper.host); - assertTrue(helper.port != 0); + public void test_SSLContextTest_TestSSLContext_create() { + TestSSLContext testContext = TestSSLContext.create(); + assertNotNull(testContext); + assertNotNull(testContext.keyStore); + assertNull(testContext.keyStorePassword); + assertNotNull(testContext.publicAlias); + assertNotNull(testContext.privateAlias); + assertNotNull(testContext.sslContext); + assertNotNull(testContext.serverSocket); + assertNotNull(testContext.host); + assertTrue(testContext.port != 0); } } diff --git a/luni/src/test/java/javax/net/ssl/SSLSessionContextTest.java b/luni/src/test/java/javax/net/ssl/SSLSessionContextTest.java index 83ed9c9..cb0f852 100644 --- a/luni/src/test/java/javax/net/ssl/SSLSessionContextTest.java +++ b/luni/src/test/java/javax/net/ssl/SSLSessionContextTest.java @@ -31,25 +31,33 @@ public class SSLSessionContextTest extends TestCase { } public void test_SSLSessionContext_getIds() { - SSLContextTest.Helper c = SSLContextTest.Helper.create(); + TestSSLContext c = TestSSLContext.create(); assertSSLSessionContextSize(0, c.sslContext.getClientSessionContext()); assertSSLSessionContextSize(0, c.sslContext.getServerSessionContext()); - SSLSocketTest.Helper s = SSLSocketTest.Helper.create_workaround(); + TestSSLSocketPair s = TestSSLSocketPair.create(); assertSSLSessionContextSize(1, s.c.sslContext.getClientSessionContext()); - assertSSLSessionContextSize(1, s.c.sslContext.getServerSessionContext()); + if (TestSSLContext.sslServerSocketSupportsSessionTickets()) { + assertSSLSessionContextSize(0, s.c.sslContext.getServerSessionContext()); + } else { + assertSSLSessionContextSize(1, s.c.sslContext.getServerSessionContext()); + } Enumeration clientIds = s.c.sslContext.getClientSessionContext().getIds(); Enumeration serverIds = s.c.sslContext.getServerSessionContext().getIds(); byte[] clientId = (byte[]) clientIds.nextElement(); - byte[] serverId = (byte[]) serverIds.nextElement(); assertEquals(32, clientId.length); - assertEquals(32, serverId.length); - assertTrue(Arrays.equals(clientId, serverId)); + if (TestSSLContext.sslServerSocketSupportsSessionTickets()) { + assertFalse(serverIds.hasMoreElements()); + } else { + byte[] serverId = (byte[]) serverIds.nextElement(); + assertEquals(32, serverId.length); + assertTrue(Arrays.equals(clientId, serverId)); + } } @KnownFailure("Should throw NullPointerException on getSession(null)") public void test_SSLSessionContext_getSession() { - SSLContextTest.Helper c = SSLContextTest.Helper.create(); + TestSSLContext c = TestSSLContext.create(); try { c.sslContext.getClientSessionContext().getSession(null); fail(); @@ -58,7 +66,7 @@ public class SSLSessionContextTest extends TestCase { assertNull(c.sslContext.getClientSessionContext().getSession(new byte[0])); assertNull(c.sslContext.getClientSessionContext().getSession(new byte[1])); - SSLSocketTest.Helper s = SSLSocketTest.Helper.create_workaround(); + TestSSLSocketPair s = TestSSLSocketPair.create(); SSLSessionContext client = s.c.sslContext.getClientSessionContext(); SSLSessionContext server = s.c.sslContext.getServerSessionContext(); byte[] clientId = (byte[]) client.getIds().nextElement(); @@ -71,18 +79,18 @@ public class SSLSessionContextTest extends TestCase { @KnownFailure("Should return 0 for unlimited, not 10 entries") public void test_SSLSessionContext_getSessionCacheSize() { - SSLContextTest.Helper c = SSLContextTest.Helper.create(); + TestSSLContext c = TestSSLContext.create(); assertEquals(0, c.sslContext.getClientSessionContext().getSessionCacheSize()); assertEquals(0, c.sslContext.getServerSessionContext().getSessionCacheSize()); - SSLSocketTest.Helper s = SSLSocketTest.Helper.create_workaround(); + TestSSLSocketPair s = TestSSLSocketPair.create(); assertEquals(0, s.c.sslContext.getClientSessionContext().getSessionCacheSize()); assertEquals(0, s.c.sslContext.getServerSessionContext().getSessionCacheSize()); } @KnownFailure("Should return 0 for unlimited, not 10 entries") public void test_SSLSessionContext_setSessionCacheSize_basic() { - SSLContextTest.Helper c = SSLContextTest.Helper.create(); + TestSSLContext c = TestSSLContext.create(); assertBasicSetSessionCacheSizeBehavior(c.sslContext.getClientSessionContext()); assertBasicSetSessionCacheSizeBehavior(c.sslContext.getServerSessionContext()); } @@ -101,7 +109,7 @@ public class SSLSessionContextTest extends TestCase { @KnownFailure("Should return 0 for unlimited, not 10 entries") public void test_SSLSessionContext_setSessionCacheSize_dynamic() { - SSLSocketTest.Helper s = SSLSocketTest.Helper.create_workaround(); + TestSSLSocketPair s = TestSSLSocketPair.create(); SSLSessionContext client = s.c.sslContext.getClientSessionContext(); SSLSessionContext server = s.c.sslContext.getServerSessionContext(); assertEquals(0, client.getSessionCacheSize()); @@ -144,12 +152,10 @@ public class SSLSessionContextTest extends TestCase { */ assertTrue(uniqueCipherSuites.size() > 5); - SSLSocketTest.Helper.connect_workaround(s.c, - new String[] { uniqueCipherSuites.remove() }); // 1 + TestSSLSocketPair.connect(s.c, new String[] { uniqueCipherSuites.remove() }); // 1 assertSSLSessionContextSize(2, client); assertSSLSessionContextSize(2, server); - SSLSocketTest.Helper.connect_workaround(s.c, - new String[] { uniqueCipherSuites.remove() }); // 2 + TestSSLSocketPair.connect(s.c, new String[] { uniqueCipherSuites.remove() }); // 2 assertSSLSessionContextSize(3, client); assertSSLSessionContextSize(3, server); @@ -159,37 +165,34 @@ public class SSLSessionContextTest extends TestCase { assertEquals(1, server.getSessionCacheSize()); assertSSLSessionContextSize(1, client); assertSSLSessionContextSize(1, server); - SSLSocketTest.Helper.connect_workaround(s.c, - new String[] { uniqueCipherSuites.remove() }); // 3 + TestSSLSocketPair.connect(s.c, new String[] { uniqueCipherSuites.remove() }); // 3 assertSSLSessionContextSize(1, client); assertSSLSessionContextSize(1, server); client.setSessionCacheSize(2); server.setSessionCacheSize(2); - SSLSocketTest.Helper.connect_workaround(s.c, - new String[] { uniqueCipherSuites.remove() }); // 4 + TestSSLSocketPair.connect(s.c, new String[] { uniqueCipherSuites.remove() }); // 4 assertSSLSessionContextSize(2, client); assertSSLSessionContextSize(2, server); - SSLSocketTest.Helper.connect_workaround(s.c, - new String[] { uniqueCipherSuites.remove() }); // 5 + TestSSLSocketPair.connect(s.c, new String[] { uniqueCipherSuites.remove() }); // 5 assertSSLSessionContextSize(2, client); assertSSLSessionContextSize(2, server); } @KnownFailure("Should return 86400 seconds (1 day), not 0 for unlimited") public void test_SSLSessionContext_getSessionTimeout() { - SSLContextTest.Helper c = SSLContextTest.Helper.create(); + TestSSLContext c = TestSSLContext.create(); assertEquals(86400, c.sslContext.getClientSessionContext().getSessionTimeout()); assertEquals(86400, c.sslContext.getServerSessionContext().getSessionTimeout()); - SSLSocketTest.Helper s = SSLSocketTest.Helper.create_workaround(); + TestSSLSocketPair s = TestSSLSocketPair.create(); assertEquals(86400, s.c.sslContext.getClientSessionContext().getSessionTimeout()); assertEquals(86400, s.c.sslContext.getServerSessionContext().getSessionTimeout()); } @KnownFailure("Should return 86400 seconds (1 day), not 0 for unlimited") public void test_SSLSessionContext_setSessionTimeout() throws Exception { - SSLContextTest.Helper c = SSLContextTest.Helper.create(); + TestSSLContext c = TestSSLContext.create(); assertEquals(86400, c.sslContext.getClientSessionContext().getSessionTimeout()); assertEquals(86400, c.sslContext.getServerSessionContext().getSessionTimeout()); c.sslContext.getClientSessionContext().setSessionTimeout(0); @@ -208,7 +211,7 @@ public class SSLSessionContextTest extends TestCase { } catch (IllegalArgumentException e) { } - SSLSocketTest.Helper s = SSLSocketTest.Helper.create_workaround(); + TestSSLSocketPair s = TestSSLSocketPair.create(); assertSSLSessionContextSize(1, s.c.sslContext.getClientSessionContext()); assertSSLSessionContextSize(1, s.c.sslContext.getServerSessionContext()); Thread.sleep(1 * 1000); diff --git a/luni/src/test/java/javax/net/ssl/SSLSessionTest.java b/luni/src/test/java/javax/net/ssl/SSLSessionTest.java index 020cd41..36b565b 100644 --- a/luni/src/test/java/javax/net/ssl/SSLSessionTest.java +++ b/luni/src/test/java/javax/net/ssl/SSLSessionTest.java @@ -23,55 +23,8 @@ import junit.framework.TestCase; public class SSLSessionTest extends TestCase { - public static final class Helper { - - /** - * An invalid session that is not connected - */ - public final SSLSession invalid; - - /** - * The server side of a connected session - */ - public final SSLSession server; - - /** - * The client side of a connected session - */ - public final SSLSession client; - - /** - * The associated SSLSocketTest.Helper that is the source of - * the client and server SSLSessions. - */ - public final SSLSocketTest.Helper s; - - private Helper(SSLSession invalid, - SSLSession server, - SSLSession client, - SSLSocketTest.Helper s) { - this.invalid = invalid; - this.server = server; - this.client = client; - this.s = s; - } - - public static final Helper create() { - try { - SSLSocketFactory sf = (SSLSocketFactory) SSLSocketFactory.getDefault(); - SSLSocket ssl = (SSLSocket) sf.createSocket(); - SSLSession invalid = ssl.getSession(); - SSLSocketTest.Helper s = SSLSocketTest.Helper.create_workaround(); - return new Helper(invalid, s.server.getSession(), s.client.getSession(), s); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - } - - - public void test_SSLSocket_Helper_create() { - Helper s = Helper.create(); + public void test_SSLSocket_TestSSLSessions_create() { + TestSSLSessions s = TestSSLSessions.create(); assertNotNull(s.invalid); assertFalse(s.invalid.isValid()); assertTrue(s.server.isValid()); @@ -79,7 +32,7 @@ public class SSLSessionTest extends TestCase { } public void test_SSLSession_getApplicationBufferSize() { - Helper s = Helper.create(); + TestSSLSessions s = TestSSLSessions.create(); assertTrue(s.invalid.getApplicationBufferSize() > 0); assertTrue(s.server.getApplicationBufferSize() > 0); assertTrue(s.client.getApplicationBufferSize() > 0); @@ -87,7 +40,7 @@ public class SSLSessionTest extends TestCase { @KnownFailure("Expected SSL_NULL_WITH_NULL_NULL but received TLS_NULL_WITH_NULL_NULL") public void test_SSLSession_getCipherSuite() { - Helper s = Helper.create(); + TestSSLSessions s = TestSSLSessions.create(); assertNotNull(s.invalid.getCipherSuite()); assertEquals("SSL_NULL_WITH_NULL_NULL", s.invalid.getCipherSuite()); assertNotNull(s.server.getCipherSuite()); @@ -97,7 +50,7 @@ public class SSLSessionTest extends TestCase { } public void test_SSLSession_getCreationTime() { - Helper s = Helper.create(); + TestSSLSessions s = TestSSLSessions.create(); assertTrue(s.invalid.getCreationTime() > 0); assertTrue(s.server.getCreationTime() > 0); assertTrue(s.client.getCreationTime() > 0); @@ -105,18 +58,22 @@ public class SSLSessionTest extends TestCase { } public void test_SSLSession_getId() { - Helper s = Helper.create(); + TestSSLSessions s = TestSSLSessions.create(); assertNotNull(s.invalid.getId()); assertNotNull(s.server.getId()); assertNotNull(s.client.getId()); assertEquals(0, s.invalid.getId().length); - assertEquals(32, s.server.getId().length); + if (TestSSLContext.sslServerSocketSupportsSessionTickets()) { + assertEquals(0, s.server.getId().length); + } else { + assertEquals(32, s.server.getId().length); + assertTrue(Arrays.equals(s.server.getId(), s.client.getId())); + } assertEquals(32, s.client.getId().length); - assertTrue(Arrays.equals(s.server.getId(), s.client.getId())); } public void test_SSLSession_getLastAccessedTime() { - Helper s = Helper.create(); + TestSSLSessions s = TestSSLSessions.create(); assertTrue(s.invalid.getLastAccessedTime() > 0); assertTrue(s.server.getLastAccessedTime() > 0); assertTrue(s.client.getLastAccessedTime() > 0); @@ -128,9 +85,12 @@ public class SSLSessionTest extends TestCase { s.client.getCreationTime()); } + @KnownFailure("client local certificates should be null as it should not have been requested by server") public void test_SSLSession_getLocalCertificates() throws Exception { - Helper s = Helper.create(); + TestSSLSessions s = TestSSLSessions.create(); assertNull(s.invalid.getLocalCertificates()); + // TODO Fix Known Failure + // Need to fix NativeCrypto.SSL_new to not use SSL_use_certificate assertNull(s.client.getLocalCertificates()); assertNotNull(s.server.getLocalCertificates()); assertEquals(1, s.server.getLocalCertificates().length); @@ -138,9 +98,12 @@ public class SSLSessionTest extends TestCase { s.server.getLocalCertificates()[0]); } + @KnownFailure("client local principal should be null as it should not have been requested by server") public void test_SSLSession_getLocalPrincipal() throws Exception { - Helper s = Helper.create(); + TestSSLSessions s = TestSSLSessions.create(); assertNull(s.invalid.getLocalPrincipal()); + // TODO Fix Known Failure + // Need to fix NativeCrypto.SSL_new to not use SSL_use_certificate assertNull(s.client.getLocalPrincipal()); assertNotNull(s.server.getLocalPrincipal()); assertNotNull(s.server.getLocalPrincipal().getName()); @@ -151,14 +114,14 @@ public class SSLSessionTest extends TestCase { } public void test_SSLSession_getPacketBufferSize() { - Helper s = Helper.create(); + TestSSLSessions s = TestSSLSessions.create(); assertTrue(s.invalid.getPacketBufferSize() > 0); assertTrue(s.server.getPacketBufferSize() > 0); assertTrue(s.client.getPacketBufferSize() > 0); } public void test_SSLSession_getPeerCertificateChain() throws Exception { - Helper s = Helper.create(); + TestSSLSessions s = TestSSLSessions.create(); try { s.invalid.getPeerCertificateChain(); fail(); @@ -176,7 +139,7 @@ public class SSLSessionTest extends TestCase { } public void test_SSLSession_getPeerCertificates() throws Exception { - Helper s = Helper.create(); + TestSSLSessions s = TestSSLSessions.create(); try { s.invalid.getPeerCertificates(); fail(); @@ -194,21 +157,21 @@ public class SSLSessionTest extends TestCase { } public void test_SSLSession_getPeerHost() { - Helper s = Helper.create(); + TestSSLSessions s = TestSSLSessions.create(); assertNull(s.invalid.getPeerHost()); assertNotNull(s.server.getPeerHost()); assertNotNull(s.client.getPeerHost()); } public void test_SSLSession_getPeerPort() { - Helper s = Helper.create(); + TestSSLSessions s = TestSSLSessions.create(); assertEquals(-1, s.invalid.getPeerPort()); assertTrue(s.server.getPeerPort() > 0); assertEquals(s.s.c.port, s.client.getPeerPort()); } public void test_SSLSession_getPeerPrincipal() throws Exception { - Helper s = Helper.create(); + TestSSLSessions s = TestSSLSessions.create(); try { s.invalid.getPeerPrincipal(); fail(); @@ -229,7 +192,7 @@ public class SSLSessionTest extends TestCase { } public void test_SSLSession_getProtocol() { - Helper s = Helper.create(); + TestSSLSessions s = TestSSLSessions.create(); assertNotNull(s.invalid.getProtocol()); assertEquals("NONE", s.invalid.getProtocol()); assertNotNull(s.server.getProtocol()); @@ -239,7 +202,7 @@ public class SSLSessionTest extends TestCase { } public void test_SSLSession_getSessionContext() { - Helper s = Helper.create(); + TestSSLSessions s = TestSSLSessions.create(); assertNull(s.invalid.getSessionContext()); assertNotNull(s.server.getSessionContext()); assertNotNull(s.client.getSessionContext()); @@ -252,7 +215,7 @@ public class SSLSessionTest extends TestCase { } public void test_SSLSession_getValue() { - Helper s = Helper.create(); + TestSSLSessions s = TestSSLSessions.create(); try { s.invalid.getValue(null); } catch (IllegalArgumentException e) { @@ -261,13 +224,13 @@ public class SSLSessionTest extends TestCase { } public void test_SSLSession_getValueNames() { - Helper s = Helper.create(); + TestSSLSessions s = TestSSLSessions.create(); assertNotNull(s.invalid.getValueNames()); assertEquals(0, s.invalid.getValueNames().length); } public void test_SSLSession_invalidate() { - Helper s = Helper.create(); + TestSSLSessions s = TestSSLSessions.create(); assertFalse(s.invalid.isValid()); s.invalid.invalidate(); assertFalse(s.invalid.isValid()); @@ -285,14 +248,14 @@ public class SSLSessionTest extends TestCase { } public void test_SSLSession_isValid() { - Helper s = Helper.create(); + TestSSLSessions s = TestSSLSessions.create(); assertFalse(s.invalid.isValid()); assertTrue(s.server.isValid()); assertTrue(s.client.isValid()); } public void test_SSLSession_putValue() { - Helper s = Helper.create(); + TestSSLSessions s = TestSSLSessions.create(); String key = "KEY"; String value = "VALUE"; assertNull(s.invalid.getValue(key)); @@ -304,7 +267,7 @@ public class SSLSessionTest extends TestCase { } public void test_SSLSession_removeValue() { - Helper s = Helper.create(); + TestSSLSessions s = TestSSLSessions.create(); String key = "KEY"; String value = "VALUE"; s.invalid.putValue(key, value); diff --git a/luni/src/test/java/javax/net/ssl/SSLSocketFactoryTest.java b/luni/src/test/java/javax/net/ssl/SSLSocketFactoryTest.java index 5ccae7f..bb76390 100644 --- a/luni/src/test/java/javax/net/ssl/SSLSocketFactoryTest.java +++ b/luni/src/test/java/javax/net/ssl/SSLSocketFactoryTest.java @@ -22,6 +22,9 @@ import java.net.SocketException; import java.net.ServerSocket; import java.net.InetAddress; import java.net.InetSocketAddress; +import java.util.Collections; +import java.util.Set; +import java.util.HashSet; import javax.net.ServerSocketFactory; import javax.net.SocketFactory; import junit.framework.TestCase; @@ -33,18 +36,50 @@ public class SSLSocketFactoryTest extends TestCase { assertTrue(SSLSocketFactory.class.isAssignableFrom(sf.getClass())); } + @KnownFailure("Using OpenSSL cipher suite names") public void test_SSLSocketFactory_getDefaultCipherSuites() { SSLSocketFactory sf = (SSLSocketFactory) SSLSocketFactory.getDefault(); - String[] cs = sf.getDefaultCipherSuites(); - assertNotNull(cs); - assertTrue(cs.length != 0); + String[] cipherSuites = sf.getDefaultCipherSuites(); + assertNotNull(cipherSuites); + assertTrue(cipherSuites.length != 0); + + // Make sure modifying the result is not observable + String savedCipherSuite = cipherSuites[0]; + assertNotNull(savedCipherSuite); + cipherSuites[0] = null; + assertNotNull(sf.getSupportedCipherSuites()[0]); + cipherSuites[0] = savedCipherSuite; + + // Make sure all cipherSuites names are expected + for (String cipherSuite : cipherSuites) { + // TODO Fix Known Failure + // Need to fix CipherSuites methods to use JSSE names + assertTrue(StandardNames.CIPHER_SUITES.contains(cipherSuite)); + } } + @KnownFailure("Using OpenSSL cipher suite names") public void test_SSLSocketFactory_getSupportedCipherSuites() { SSLSocketFactory sf = (SSLSocketFactory) SSLSocketFactory.getDefault(); - String[] cs = sf.getSupportedCipherSuites(); - assertNotNull(cs); - assertTrue(cs.length != 0); + String[] cipherSuites = sf.getSupportedCipherSuites(); + assertNotNull(cipherSuites); + assertTrue(cipherSuites.length != 0); + + // Make sure modifying the result is not observable + String savedCipherSuite = cipherSuites[0]; + assertNotNull(savedCipherSuite); + cipherSuites[0] = null; + assertNotNull(sf.getSupportedCipherSuites()[0]); + cipherSuites[0] = savedCipherSuite; + + // Make sure all cipherSuites names are expected + Set remainingCipherSuites = new HashSet<String>(StandardNames.CIPHER_SUITES); + for (String cipherSuite : cipherSuites) { + assertNotNull(remainingCipherSuites.remove(cipherSuite)); + } + // TODO Fix Known Failure + // Need to fix CipherSuites methods to use JSSE names + assertEquals(Collections.EMPTY_SET, remainingCipherSuites); } @KnownFailure("Should not parse bogus port number -1 during createSocket") diff --git a/luni/src/test/java/javax/net/ssl/SSLSocketTest.java b/luni/src/test/java/javax/net/ssl/SSLSocketTest.java index d02aeee..9e2cb63 100644 --- a/luni/src/test/java/javax/net/ssl/SSLSocketTest.java +++ b/luni/src/test/java/javax/net/ssl/SSLSocketTest.java @@ -17,51 +17,61 @@ package javax.net.ssl; import dalvik.annotation.KnownFailure; -import java.math.BigInteger; -import java.net.InetAddress; -import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketTimeoutException; import java.security.Key; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.KeyStore; import java.security.Principal; -import java.security.SecureRandom; -import java.security.SignatureException; import java.security.cert.Certificate; import java.security.cert.X509Certificate; -import java.security.interfaces.RSAPrivateKey; -import java.security.interfaces.RSAPublicKey; import java.util.Arrays; -import java.util.ArrayList; -import java.util.Date; +import java.util.Collections; import java.util.Enumeration; -import java.util.Hashtable; -import javax.net.ServerSocketFactory; -import javax.net.SocketFactory; +import java.util.HashSet; +import java.util.Set; import junit.framework.TestCase; public class SSLSocketTest extends TestCase { + @KnownFailure("Using OpenSSL cipher suite names") public void test_SSLSocket_getSupportedCipherSuites() throws Exception { SSLSocketFactory sf = (SSLSocketFactory) SSLSocketFactory.getDefault(); SSLSocket ssl = (SSLSocket) sf.createSocket(); - String[] cs = ssl.getSupportedCipherSuites(); - assertNotNull(cs); - assertTrue(cs.length != 0); + String[] cipherSuites = ssl.getSupportedCipherSuites(); + assertNotNull(cipherSuites); + assertTrue(cipherSuites.length != 0); + Set remainingCipherSuites = new HashSet<String>(StandardNames.CIPHER_SUITES); + for (String cipherSuite : cipherSuites) { + assertNotNull(remainingCipherSuites.remove(cipherSuite)); + } + // TODO Fix Known Failure + // Need to fix CipherSuites methods to use JSSE names + assertEquals(Collections.EMPTY_SET, remainingCipherSuites); } + @KnownFailure("Using OpenSSL cipher suite names") public void test_SSLSocket_getEnabledCipherSuites() throws Exception { SSLSocketFactory sf = (SSLSocketFactory) SSLSocketFactory.getDefault(); SSLSocket ssl = (SSLSocket) sf.createSocket(); - String[] cs = ssl.getEnabledCipherSuites(); - assertNotNull(cs); - assertTrue(cs.length != 0); + String[] cipherSuites = ssl.getEnabledCipherSuites(); + assertNotNull(cipherSuites); + assertTrue(cipherSuites.length != 0); + + // Make sure modifying the result is not observable + String savedCipherSuite = cipherSuites[0]; + assertNotNull(savedCipherSuite); + cipherSuites[0] = null; + assertNotNull(ssl.getEnabledCipherSuites()[0]); + cipherSuites[0] = savedCipherSuite; + + // Make sure all cipherSuites names are expected + for (String cipherSuite : cipherSuites) { + // TODO Fix Known Failure + // Need to fix CipherSuites methods to use JSSE names + assertTrue(StandardNames.CIPHER_SUITES.contains(cipherSuite)); + } } - @KnownFailure("Should support disabling all cipher suites") public void test_SSLSocket_setEnabledCipherSuites() throws Exception { SSLSocketFactory sf = (SSLSocketFactory) SSLSocketFactory.getDefault(); SSLSocket ssl = (SSLSocket) sf.createSocket(); @@ -90,20 +100,43 @@ public class SSLSocketTest extends TestCase { public void test_SSLSocket_getSupportedProtocols() throws Exception { SSLSocketFactory sf = (SSLSocketFactory) SSLSocketFactory.getDefault(); SSLSocket ssl = (SSLSocket) sf.createSocket(); - String[] p = ssl.getSupportedProtocols(); - assertNotNull(p); - assertTrue(p.length != 0); + String[] protocols = ssl.getSupportedProtocols(); + assertNotNull(protocols); + assertTrue(protocols.length != 0); + + // Make sure modifying the result is not observable + String savedProtocol = protocols[0]; + assertNotNull(savedProtocol); + protocols[0] = null; + assertNotNull(ssl.getSupportedProtocols()[0]); + protocols[0] = savedProtocol; + + // Make sure all protocol names are expected + for (String protocol : protocols) { + assertNotNull(StandardNames.SSL_SOCKET_PROTOCOLS.contains(protocol)); + } } public void test_SSLSocket_getEnabledProtocols() throws Exception { SSLSocketFactory sf = (SSLSocketFactory) SSLSocketFactory.getDefault(); SSLSocket ssl = (SSLSocket) sf.createSocket(); - String[] p = ssl.getEnabledProtocols(); - assertNotNull(p); - assertTrue(p.length != 0); + String[] protocols = ssl.getEnabledProtocols(); + assertNotNull(protocols); + assertTrue(protocols.length != 0); + + // Make sure modifying the result is not observable + String savedProtocol = protocols[0]; + assertNotNull(savedProtocol); + protocols[0] = null; + assertNotNull(ssl.getEnabledProtocols()[0]); + protocols[0] = savedProtocol; + + // Make sure all protocol names are expected + for (String protocol : protocols) { + assertNotNull(StandardNames.SSL_SOCKET_PROTOCOLS.contains(protocol)); + } } - @KnownFailure("Should thow IllegalArgumentException not NullPointerException on null enabled protocols argument") public void test_SSLSocket_setEnabledProtocols() throws Exception { SSLSocketFactory sf = (SSLSocketFactory) SSLSocketFactory.getDefault(); SSLSocket ssl = (SSLSocket) sf.createSocket(); @@ -128,7 +161,6 @@ public class SSLSocketTest extends TestCase { ssl.setEnabledProtocols(ssl.getSupportedProtocols()); } - @KnownFailure("session of unconnected socket should not be valid") public void test_SSLSocket_getSession() throws Exception { SSLSocketFactory sf = (SSLSocketFactory) SSLSocketFactory.getDefault(); SSLSocket ssl = (SSLSocket) sf.createSocket(); @@ -137,26 +169,10 @@ public class SSLSocketTest extends TestCase { assertFalse(session.isValid()); } - @KnownFailure("Implementation should not start handshake in ServerSocket.accept") + @KnownFailure("local certificates should be null as it should not have been requested by server") public void test_SSLSocket_startHandshake() throws Exception { - final SSLContextTest.Helper c = SSLContextTest.Helper.create(); + final TestSSLContext c = TestSSLContext.create(); SSLSocket client = (SSLSocket) c.sslContext.getSocketFactory().createSocket(c.host, c.port); - if (!SSLContextTest.IS_RI) { - /* - * The following hangs in accept in the Dalvik - * implementation because accept is also incorrectly - * starting the handhake. - */ - c.serverSocket.setSoTimeout(1 * 1000); - /* - * That workaround doesn't seem to work so... - * - * See test_SSLSocket_startHandshake_workaround for - * redundant version of this test that works around this - * issue. - */ - fail(); - } final SSLSocket server = (SSLSocket) c.serverSocket.accept(); Thread thread = new Thread(new Runnable () { public void run() { @@ -192,79 +208,27 @@ public class SSLSocketTest extends TestCase { thread.join(); } - @KnownFailure("local certificates should be null as it should not have been requested by server") - public void test_SSLSocket_startHandshake_workaround() throws Exception { - final SSLContextTest.Helper c = SSLContextTest.Helper.create(); - Thread thread = new Thread(new Runnable () { - public void run() { - try { - SSLSocket server = (SSLSocket) c.serverSocket.accept(); - server.startHandshake(); - assertNotNull(server.getSession()); - try { - server.getSession().getPeerCertificates(); - fail(); - } catch (SSLPeerUnverifiedException e) { - } - Certificate[] localCertificates = server.getSession().getLocalCertificates(); - assertNotNull(localCertificates); - assertEquals(1, localCertificates.length); - assertNotNull(localCertificates[0]); - assertNotNull(localCertificates[0].equals(c.keyStore.getCertificate(c.privateAlias))); - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { - throw new RuntimeException(e); - } - } - }); - thread.start(); - SSLSocket client = (SSLSocket) c.sslContext.getSocketFactory().createSocket(c.host, c.port); - client.startHandshake(); - assertNotNull(client.getSession()); - assertNull(client.getSession().getLocalCertificates()); - Certificate[] peerCertificates = client.getSession().getPeerCertificates(); - assertNotNull(peerCertificates); - assertEquals(1, peerCertificates.length); - assertNotNull(peerCertificates[0]); - assertNotNull(peerCertificates[0].equals(c.keyStore.getCertificate(c.publicAlias))); - thread.join(); - } - - @KnownFailure("Should throw SSLException on server, not IOException on client") - public void test_SSLSocket_startHandshake_noKeyStore_workaround() throws Exception { - final SSLContextTest.Helper c = SSLContextTest.Helper.create(null, null, null, null); - Thread thread = new Thread(new Runnable () { - public void run() { - try { - c.serverSocket.accept(); - fail(); - } catch (SSLException e) { - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { - throw new RuntimeException(e); - } - } - }); - thread.start(); + @KnownFailure("Should throw SSLException from SSLServerSocket.accept with no private key configured") + public void test_SSLSocket_startHandshake_noKeyStore() throws Exception { + TestSSLContext c = TestSSLContext.create(null, null, null, null); SSLSocket client = (SSLSocket) c.sslContext.getSocketFactory().createSocket(c.host, c.port); - if (!SSLContextTest.IS_RI) { - client.startHandshake(); + try { + SSLSocket server = (SSLSocket) c.serverSocket.accept(); + // TODO Fix Known Failure + // Need to make SSLServerSocket.accept check if necessary private keys for enabled cipher suites are available + fail(); + } catch (SSLException e) { } - thread.join(); } - /** - * Marked workaround because it avoid accepting on main thread like test_SSLSocket_startHandshake_workaround - */ @KnownFailure("local certificates should be null as it should not have been requested by server") - public void test_SSLSocket_HandshakeCompletedListener_workaround() throws Exception { - final SSLContextTest.Helper c = SSLContextTest.Helper.create(); + public void test_SSLSocket_HandshakeCompletedListener() throws Exception { + final TestSSLContext c = TestSSLContext.create(); + final SSLSocket client = (SSLSocket) c.sslContext.getSocketFactory().createSocket(c.host, c.port); + final SSLSocket server = (SSLSocket) c.serverSocket.accept(); Thread thread = new Thread(new Runnable () { public void run() { try { - SSLSocket server = (SSLSocket) c.serverSocket.accept(); server.startHandshake(); } catch (RuntimeException e) { throw e; @@ -274,7 +238,6 @@ public class SSLSocketTest extends TestCase { } }); thread.start(); - final SSLSocket client = (SSLSocket) c.sslContext.getSocketFactory().createSocket(c.host, c.port); final boolean[] handshakeCompletedListenerCalled = new boolean[1]; client.addHandshakeCompletedListener(new HandshakeCompletedListener() { public void handshakeCompleted(HandshakeCompletedEvent event) { @@ -304,7 +267,9 @@ public class SSLSocketTest extends TestCase { assertNotNull(id); assertEquals(32, id.length); assertNotNull(c.sslContext.getClientSessionContext().getSession(id)); - assertNotNull(c.sslContext.getServerSessionContext().getSession(id)); + if (!TestSSLContext.sslServerSocketSupportsSessionTickets()) { + assertNotNull(c.sslContext.getServerSessionContext().getSession(id)); + } assertNotNull(cipherSuite); assertTrue(Arrays.asList(client.getEnabledCipherSuites()).contains(cipherSuite)); @@ -331,6 +296,8 @@ public class SSLSocketTest extends TestCase { assertTrue(X509Certificate.class.isAssignableFrom(certificate.getClass())); X509Certificate x509certificate = (X509Certificate) certificate; + // TODO Fix Known Failure + // Need to fix NativeCrypto.SSL_new to not use SSL_use_certificate assertNull(localCertificates); assertNotNull(peerCertificates); @@ -374,74 +341,51 @@ public class SSLSocketTest extends TestCase { } } - /** - * Marked workaround because it avoid accepting on main thread like test_SSLSocket_startHandshake_workaround. - * Technically this test shouldn't even need a second thread. - */ - public void test_SSLSocket_getUseClientMode_workaround() throws Exception { - final SSLContextTest.Helper c = SSLContextTest.Helper.create(); - Thread thread = new Thread(new Runnable () { - public void run() { - try { - SSLSocket server = (SSLSocket) c.serverSocket.accept(); - assertFalse(server.getUseClientMode()); - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { - throw new RuntimeException(e); - } - } - }); - thread.start(); + public void test_SSLSocket_getUseClientMode() throws Exception { + TestSSLContext c = TestSSLContext.create(); SSLSocket client = (SSLSocket) c.sslContext.getSocketFactory().createSocket(c.host, c.port); - if (!SSLContextTest.IS_RI) { - client.startHandshake(); - } + SSLSocket server = (SSLSocket) c.serverSocket.accept(); assertTrue(client.getUseClientMode()); - thread.join(); + assertFalse(server.getUseClientMode()); } - /** - * Marked workaround because it avoid accepting on main thread like test_SSLSocket_startHandshake_workaround. - * Technically this test shouldn't even need a second thread. - */ - @KnownFailure("This test relies on socket timeouts which are not working. It also fails because ServerSocket.accept is handshaking") - public void test_SSLSocket_setUseClientMode_workaround() throws Exception { + @KnownFailure("This test relies on socket timeouts which are not working. It also fails because SSLException is thrown instead of SSLProtocolException") + public void test_SSLSocket_setUseClientMode() throws Exception { // client is client, server is server - test_SSLSocket_setUseClientMode_workaround(true, false); + test_SSLSocket_setUseClientMode(true, false); // client is server, server is client - test_SSLSocket_setUseClientMode_workaround(true, false); + test_SSLSocket_setUseClientMode(true, false); // both are client try { - test_SSLSocket_setUseClientMode_workaround(true, true); + test_SSLSocket_setUseClientMode(true, true); fail(); } catch (SSLProtocolException e) { + // TODO Fix [Un]KnownFailure + // The more generic SSLException is thrown instead of SSLProtocolException } // both are server try { - test_SSLSocket_setUseClientMode_workaround(false, false); + test_SSLSocket_setUseClientMode(false, false); fail(); } catch (SocketTimeoutException e) { } } - private void test_SSLSocket_setUseClientMode_workaround(final boolean clientClientMode, - final boolean serverClientMode) + private void test_SSLSocket_setUseClientMode(final boolean clientClientMode, + final boolean serverClientMode) throws Exception { - final SSLContextTest.Helper c = SSLContextTest.Helper.create(); + TestSSLContext c = TestSSLContext.create(); + SSLSocket client = (SSLSocket) c.sslContext.getSocketFactory().createSocket(c.host, c.port); + final SSLSocket server = (SSLSocket) c.serverSocket.accept(); + final SSLProtocolException[] sslProtocolException = new SSLProtocolException[1]; final SocketTimeoutException[] socketTimeoutException = new SocketTimeoutException[1]; Thread thread = new Thread(new Runnable () { public void run() { try { - SSLSocket server = (SSLSocket) c.serverSocket.accept(); if (!serverClientMode) { server.setSoTimeout(1 * 1000); - if (!SSLContextTest.IS_RI) { - /* as above setSoTimeout isn't working in dalvikvm */ - fail(); - } } server.setUseClientMode(serverClientMode); server.startHandshake(); @@ -457,13 +401,8 @@ public class SSLSocketTest extends TestCase { } }); thread.start(); - SSLSocket client = (SSLSocket) c.sslContext.getSocketFactory().createSocket(c.host, c.port); if (!clientClientMode) { client.setSoTimeout(1 * 1000); - if (!SSLContextTest.IS_RI) { - /* as above setSoTimeout isn't working in dalvikvm */ - fail(); - } } client.setUseClientMode(clientClientMode); client.startHandshake(); @@ -476,15 +415,13 @@ public class SSLSocketTest extends TestCase { } } - /** - * Marked workaround because it avoid accepting on main thread like test_SSLSocket_startHandshake_workaround - */ - public void test_SSLSocket_clientAuth_workaround() throws Exception { - final SSLContextTest.Helper c = SSLContextTest.Helper.create(); + public void test_SSLSocket_clientAuth() throws Exception { + TestSSLContext c = TestSSLContext.create(); + SSLSocket client = (SSLSocket) c.sslContext.getSocketFactory().createSocket(c.host, c.port); + final SSLSocket server = (SSLSocket) c.serverSocket.accept(); Thread thread = new Thread(new Runnable () { public void run() { try { - SSLSocket server = (SSLSocket) c.serverSocket.accept(); assertFalse(server.getWantClientAuth()); assertFalse(server.getNeedClientAuth()); @@ -513,50 +450,27 @@ public class SSLSocketTest extends TestCase { } }); thread.start(); - SSLSocket client = (SSLSocket) c.sslContext.getSocketFactory().createSocket(c.host, c.port); client.startHandshake(); assertNotNull(client.getSession().getLocalCertificates()); assertEquals(1, client.getSession().getLocalCertificates().length); thread.join(); } - /** - * Marked workaround because it avoid accepting on main thread like test_SSLSocket_startHandshake_workaround - * Technically this test shouldn't even need a second thread. - */ - public void test_SSLSocket_getEnableSessionCreation_workaround() throws Exception { - final SSLContextTest.Helper c = SSLContextTest.Helper.create(); - Thread thread = new Thread(new Runnable () { - public void run() { - try { - SSLSocket server = (SSLSocket) c.serverSocket.accept(); - assertTrue(server.getEnableSessionCreation()); - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { - throw new RuntimeException(e); - } - } - }); - thread.start(); + public void test_SSLSocket_getEnableSessionCreation() throws Exception { + TestSSLContext c = TestSSLContext.create(); SSLSocket client = (SSLSocket) c.sslContext.getSocketFactory().createSocket(c.host, c.port); + SSLSocket server = (SSLSocket) c.serverSocket.accept(); assertTrue(client.getEnableSessionCreation()); - if (!SSLContextTest.IS_RI) { - client.startHandshake(); - } - thread.join(); + assertTrue(server.getEnableSessionCreation()); } - /** - * Marked workaround because it avoid accepting on main thread like test_SSLSocket_startHandshake_workaround - */ - @KnownFailure("Server side session creation disabling does not work, should throw SSLException, not fail") - public void test_SSLSocket_setEnableSessionCreation_server_workaround() throws Exception { - final SSLContextTest.Helper c = SSLContextTest.Helper.create(); + public void test_SSLSocket_setEnableSessionCreation_server() throws Exception { + TestSSLContext c = TestSSLContext.create(); + SSLSocket client = (SSLSocket) c.sslContext.getSocketFactory().createSocket(c.host, c.port); + final SSLSocket server = (SSLSocket) c.serverSocket.accept(); Thread thread = new Thread(new Runnable () { public void run() { try { - SSLSocket server = (SSLSocket) c.serverSocket.accept(); server.setEnableSessionCreation(false); try { server.startHandshake(); @@ -571,7 +485,6 @@ public class SSLSocketTest extends TestCase { } }); thread.start(); - SSLSocket client = (SSLSocket) c.sslContext.getSocketFactory().createSocket(c.host, c.port); try { client.startHandshake(); fail(); @@ -580,16 +493,13 @@ public class SSLSocketTest extends TestCase { thread.join(); } - /** - * Marked workaround because it avoid accepting on main thread like test_SSLSocket_startHandshake_workaround - */ - @KnownFailure("Should throw SSLException on server, not IOException") - public void test_SSLSocket_setEnableSessionCreation_client_workaround() throws Exception { - final SSLContextTest.Helper c = SSLContextTest.Helper.create(); + public void test_SSLSocket_setEnableSessionCreation_client() throws Exception { + TestSSLContext c = TestSSLContext.create(); + SSLSocket client = (SSLSocket) c.sslContext.getSocketFactory().createSocket(c.host, c.port); + final SSLSocket server = (SSLSocket) c.serverSocket.accept(); Thread thread = new Thread(new Runnable () { public void run() { try { - SSLSocket server = (SSLSocket) c.serverSocket.accept(); try { server.startHandshake(); fail(); @@ -603,94 +513,44 @@ public class SSLSocketTest extends TestCase { } }); thread.start(); - SSLSocket client = (SSLSocket) c.sslContext.getSocketFactory().createSocket(c.host, c.port); client.setEnableSessionCreation(false); try { client.startHandshake(); fail(); } catch (SSLException e) { - if (!SSLContextTest.IS_RI) { - fail(); - } } thread.join(); } - /** - * SSLSocketTest.Helper is a convenience class for other tests that - * want a pair of connected and handshaked client and server - * SSLSocketsfor testing so they don't have to duplicate the - * logic. - */ - public static final class Helper { - public final SSLContextTest.Helper c; - public final SSLSocket server; - public final SSLSocket client; - - private Helper (SSLContextTest.Helper c, - SSLSocket server, - SSLSocket client) { - this.c = c; - this.server = server; - this.client = client; - } - - /** - * based on test_SSLSocket_startHandshake_workaround, should - * be written to non-workaround form when possible - */ - public static Helper create_workaround () { - SSLContextTest.Helper c = SSLContextTest.Helper.create(); - SSLSocket[] sockets = connect_workaround(c, null); - return new Helper(c, sockets[0], sockets[1]); - } - - /** - * Create a new connected server/client socket pair within a - * existing SSLContext. Optional clientCipherSuites allows - * forcing new SSLSession to test SSLSessionContext caching - */ - public static SSLSocket[] connect_workaround (final SSLContextTest.Helper c, - String[] clientCipherSuites) { - try { - final SSLSocket[] server = new SSLSocket[1]; - Thread thread = new Thread(new Runnable () { - public void run() { - try { - server[0] = (SSLSocket) c.serverSocket.accept(); - server[0].startHandshake(); - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { - throw new RuntimeException(e); - } - } - }); - thread.start(); - SSLSocket client = (SSLSocket) - c.sslContext.getSocketFactory().createSocket(c.host, c.port); - if (clientCipherSuites != null) { - client.setEnabledCipherSuites(clientCipherSuites); - } - client.startHandshake(); - thread.join(); - return new SSLSocket[] { server[0], client }; - } catch (RuntimeException e) { - throw e; - } catch (Exception e) { - throw new RuntimeException(e); - } - } - } - public void test_SSLSocketTest_Test_create() { - Helper test = Helper.create_workaround(); + TestSSLSocketPair test = TestSSLSocketPair.create(); assertNotNull(test.c); assertNotNull(test.server); assertNotNull(test.client); - assertNotNull(test.server.isConnected()); - assertNotNull(test.client.isConnected()); + assertTrue(test.server.isConnected()); + assertTrue(test.client.isConnected()); assertNotNull(test.server.getSession()); assertNotNull(test.client.getSession()); } + + /** + * Not run by default by JUnit, but can be run by Vogar by + * specifying it explictly (or with main method below) + */ + public void stress_test_SSLSocketTest_Test_create() { + final boolean verbose = true; + while (true) { + TestSSLSocketPair test = TestSSLSocketPair.create(); + if (verbose) { + System.out.println("client=" + test.client.getLocalPort() + + " server=" + test.server.getLocalPort()); + } else { + System.out.print("X"); + } + } + } + + public static final void main (String[] args) { + new SSLSocketTest().stress_test_SSLSocketTest_Test_create(); + } } diff --git a/luni/src/test/java/tests/AllTests.java b/luni/src/test/java/tests/AllTests.java index fdea653..4b3a484 100644 --- a/luni/src/test/java/tests/AllTests.java +++ b/luni/src/test/java/tests/AllTests.java @@ -65,8 +65,7 @@ public class AllTests suite.addTest(java.text.AllTests.suite()); suite.addTest(java.util.AllTests.suite()); suite.addTest(javax.xml.parsers.AllTests.suite()); - // disable until hangs are resolved in our JSSE implementation - // suite.addTest(javax.net.ssl.AllTests.suite()); + suite.addTest(javax.net.ssl.AllTests.suite()); suite.addTest(org.apache.harmony.luni.platform.AllTests.suite()); suite.addTest(org.json.AllTests.suite()); suite.addTest(tests.api.org.apache.harmony.kernel.dalvik.AllTests.suite()); diff --git a/run-core-tests b/run-core-tests index 95e034e..b1df621 100755 --- a/run-core-tests +++ b/run-core-tests @@ -25,9 +25,7 @@ chmod 777 $tmp # Build the classpath by putting together the jar file for each module. classpath="/system/framework/sqlite-jdbc.jar" # Bonus item for jdbc testing. -modules="annotation archive concurrent crypto dom json \ - logging luni-kernel luni math nio_char prefs regex security sql \ - suncompat support text x-net xml" +modules="dom json luni-kernel luni prefs support x-net xml" for module in $modules; do classpath="$classpath:/system/framework/core-tests-$module.jar" done diff --git a/support/src/test/java/javax/net/ssl/StandardNames.java b/support/src/test/java/javax/net/ssl/StandardNames.java new file mode 100644 index 0000000..ccd3ee1 --- /dev/null +++ b/support/src/test/java/javax/net/ssl/StandardNames.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package javax.net.ssl; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +/** + * This class defines expected string names for protocols, key types, client and server auth types, cipher suites. + * Based on documentation from http://java.sun.com/j2se/1.5.0/docs/guide/security/jsse/JSSERefGuide.html#AppA + */ +public final class StandardNames { + + public static final Set<String> SSL_CONTEXT_PROTOCOLS = new HashSet<String>(Arrays.asList( + "SSL", + "SSLv2", + "SSLv3", + "TLS", + "TLSv1")); + + public static final Set<String> KEY_TYPES = new HashSet<String>(Arrays.asList( + "RSA", + "DSA", + "DH_RSA", + "DH_DSA")); + + public static final Set<String> SSL_SOCKET_PROTOCOLS = new HashSet<String>(Arrays.asList( + "SSLv2", + "SSLv3", + "TLSv1", + "SSLv2Hello")); + + public static final Set<String> CLIENT_AUTH_TYPES = new HashSet<String>(KEY_TYPES); + + public static final Set<String> SERVER_AUTH_TYPES = new HashSet<String>(Arrays.asList( + "DHE_DSS", + "DHE_DSS_EXPORT", + "DHE_RSA", + "DHE_RSA_EXPORT", + "DH_DSS_EXPORT", + "DH_RSA_EXPORT", + "DH_anon", + "DH_anon_EXPORT", + "KRB5", + "KRB5_EXPORT", + "RSA", + "RSA_EXPORT", + "RSA_EXPORT1024", + "UNKNOWN")); + + // removed cipher suites not actually found in RI + public static final Set<String> CIPHER_SUITES = new HashSet<String>(Arrays.asList( + "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", + "SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA", + "SSL_DHE_DSS_WITH_DES_CBC_SHA", + "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", + "SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA", + "SSL_DHE_RSA_WITH_DES_CBC_SHA", + //"SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA", + //"SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA", + "SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA", + "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5", + "SSL_DH_anon_WITH_3DES_EDE_CBC_SHA", + "SSL_DH_anon_WITH_DES_CBC_SHA", + "SSL_DH_anon_WITH_RC4_128_MD5", + //"SSL_RSA_EXPORT1024_WITH_DES_CBC_SHA", + //"SSL_RSA_EXPORT1024_WITH_RC4_56_SHA", + "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA", + //"SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5", + "SSL_RSA_EXPORT_WITH_RC4_40_MD5", + "SSL_RSA_WITH_3DES_EDE_CBC_SHA", + "SSL_RSA_WITH_DES_CBC_SHA", + "SSL_RSA_WITH_NULL_MD5", + "SSL_RSA_WITH_NULL_SHA", + "SSL_RSA_WITH_RC4_128_MD5", + "SSL_RSA_WITH_RC4_128_SHA", + "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", + //"TLS_DHE_DSS_WITH_AES_256_CBC_SHA", + "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", + //"TLS_DHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_DH_anon_WITH_AES_128_CBC_SHA", + //"TLS_DH_anon_WITH_AES_256_CBC_SHA", + "TLS_KRB5_EXPORT_WITH_DES_CBC_40_MD5", + "TLS_KRB5_EXPORT_WITH_DES_CBC_40_SHA", + //"TLS_KRB5_EXPORT_WITH_RC2_CBC_40_MD5", + //"TLS_KRB5_EXPORT_WITH_RC2_CBC_40_SHA", + "TLS_KRB5_EXPORT_WITH_RC4_40_MD5", + "TLS_KRB5_EXPORT_WITH_RC4_40_SHA", + "TLS_KRB5_WITH_3DES_EDE_CBC_MD5", + "TLS_KRB5_WITH_3DES_EDE_CBC_SHA", + "TLS_KRB5_WITH_DES_CBC_MD5", + "TLS_KRB5_WITH_DES_CBC_SHA", + "TLS_KRB5_WITH_RC4_128_MD5", + "TLS_KRB5_WITH_RC4_128_SHA", + "TLS_RSA_WITH_AES_128_CBC_SHA")); + //"TLS_RSA_WITH_AES_256_CBC_SHA")); +} diff --git a/support/src/test/java/javax/net/ssl/TestSSLContext.java b/support/src/test/java/javax/net/ssl/TestSSLContext.java new file mode 100644 index 0000000..44b21c9 --- /dev/null +++ b/support/src/test/java/javax/net/ssl/TestSSLContext.java @@ -0,0 +1,226 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package javax.net.ssl; + +import java.math.BigInteger; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.SecureRandom; +import java.security.Security; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.util.Date; +import java.util.Hashtable; +import org.bouncycastle.jce.X509Principal; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.x509.X509V3CertificateGenerator; + +/** + * TestSSLContext is a convenience class for other tests that + * want a canned SSLContext and related state for testing so they + * don't have to duplicate the logic. + */ +public final class TestSSLContext { + + public static final boolean IS_RI = !"Dalvik Core Library".equals(System.getProperty("java.specification.name")); + public static final String PROVIDER_NAME = (IS_RI) ? "SunJSSE" : "HarmonyJSSE"; + + static { + if (IS_RI) { + Security.addProvider(new BouncyCastleProvider()); + } + } + + /** + * The Android SSLSocket and SSLServerSocket implementations are + * based on a version of OpenSSL which includes support for RFC + * 4507 session tickets. When using session tickets, the server + * does not need to keep a cache mapping session IDs to SSL + * sessions for reuse. Instead, the client presents the server + * with a session ticket it received from the server earlier, + * which is an SSL session encrypted by the server's secret + * key. Since in this case the server does not need to keep a + * cache, some tests may find different results depending on + * whether or not the session tickets are in use. These tests can + * use this function to determine if loopback SSL connections are + * expected to use session tickets and conditionalize their + * results appropriately. + */ + public static boolean sslServerSocketSupportsSessionTickets () { + return !IS_RI; + } + + public final KeyStore keyStore; + public final char[] keyStorePassword; + public final String publicAlias; + public final String privateAlias; + public final SSLContext sslContext; + public final SSLServerSocket serverSocket; + public final InetAddress host; + public final int port; + + private TestSSLContext(KeyStore keyStore, + char[] keyStorePassword, + String publicAlias, + String privateAlias, + SSLContext sslContext, + SSLServerSocket serverSocket, + InetAddress host, + int port) { + this.keyStore = keyStore; + this.keyStorePassword = keyStorePassword; + this.publicAlias = publicAlias; + this.privateAlias = privateAlias; + this.sslContext = sslContext; + this.serverSocket = serverSocket; + this.host = host; + this.port = port; + } + + public static TestSSLContext create() { + try { + char[] keyStorePassword = null; + String publicAlias = "public"; + String privateAlias = "private"; + return create(createKeyStore(keyStorePassword, publicAlias, privateAlias), + null, + publicAlias, + privateAlias); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static TestSSLContext create(KeyStore keyStore, + char[] keyStorePassword, + String publicAlias, + String privateAlias) { + try { + SSLContext sslContext = createSSLContext(keyStore, keyStorePassword); + + SSLServerSocket serverSocket = (SSLServerSocket) + sslContext.getServerSocketFactory().createServerSocket(0); + InetSocketAddress sa = (InetSocketAddress) serverSocket.getLocalSocketAddress(); + InetAddress host = sa.getAddress(); + int port = sa.getPort(); + + return new TestSSLContext(keyStore, keyStorePassword, publicAlias, privateAlias, + sslContext, serverSocket, host, port); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Create a BKS KeyStore containing an RSAPrivateKey with alias + * "private" and a X509Certificate based on the matching + * RSAPublicKey stored under the alias name publicAlias. + * + * The private key will have a certificate chain including the + * certificate stored under the alias name privateAlias. The + * certificate will be signed by the private key. The certificate + * Subject and Issuer Common-Name will be the local host's + * canonical hostname. The certificate will be valid for one day + * before and one day after the time of creation. + * + * The KeyStore is optionally password protected by the + * keyStorePassword argument, which can be null if a password is + * not desired. + * + * Based on: + * org.bouncycastle.jce.provider.test.SigTest + * org.bouncycastle.jce.provider.test.CertTest + */ + public static KeyStore createKeyStore(char[] keyStorePassword, + String publicAlias, + String privateAlias) + throws Exception { + + // 1.) we make the keys + int keysize = 1024; + KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); + kpg.initialize(keysize, new SecureRandom()); + KeyPair kp = kpg.generateKeyPair(); + RSAPrivateKey privateKey = (RSAPrivateKey)kp.getPrivate(); + RSAPublicKey publicKey = (RSAPublicKey)kp.getPublic(); + + // 2.) use keys to make certficate + + // note that there doesn't seem to be a standard way to make a + // certificate using java.* or javax.*. The CertificateFactory + // interface assumes you want to read in a stream of bytes a + // factory specific format. So here we use Bouncy Castle's + // X509V3CertificateGenerator and related classes. + + Hashtable attributes = new Hashtable(); + attributes.put(X509Principal.CN, InetAddress.getLocalHost().getCanonicalHostName()); + X509Principal dn = new X509Principal(attributes); + + long millisPerDay = 24 * 60 * 60 * 1000; + long now = System.currentTimeMillis(); + Date start = new Date(now - millisPerDay); + Date end = new Date(now + millisPerDay); + BigInteger serial = BigInteger.valueOf(1); + + X509V3CertificateGenerator x509cg = new X509V3CertificateGenerator(); + x509cg.setSubjectDN(dn); + x509cg.setIssuerDN(dn); + x509cg.setNotBefore(start); + x509cg.setNotAfter(end); + x509cg.setPublicKey(publicKey); + x509cg.setSignatureAlgorithm("sha1WithRSAEncryption"); + x509cg.setSerialNumber(serial); + X509Certificate x509c = x509cg.generateX509Certificate(privateKey); + X509Certificate[] x509cc = new X509Certificate[] { x509c }; + + + // 3.) put certificate and private key to make a key store + KeyStore ks = KeyStore.getInstance("BKS"); + ks.load(null, null); + ks.setKeyEntry(privateAlias, privateKey, keyStorePassword, x509cc); + ks.setCertificateEntry(publicAlias, x509c); + return ks; + } + + /** + * Create a SSLContext with a KeyManager using the private key and + * certificate chain from the given KeyStore and a TrustManager + * using the certificates authorities from the same KeyStore. + */ + public static final SSLContext createSSLContext(final KeyStore keyStore, final char[] keyStorePassword) + throws Exception { + String kmfa = KeyManagerFactory.getDefaultAlgorithm(); + KeyManagerFactory kmf = KeyManagerFactory.getInstance(kmfa); + kmf.init(keyStore, keyStorePassword); + + String tmfa = TrustManagerFactory.getDefaultAlgorithm(); + TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfa); + tmf.init(keyStore); + + SSLContext context = SSLContext.getInstance("TLS"); + context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom()); + return context; + } +} diff --git a/support/src/test/java/javax/net/ssl/TestSSLSessions.java b/support/src/test/java/javax/net/ssl/TestSSLSessions.java new file mode 100644 index 0000000..23b8ed5 --- /dev/null +++ b/support/src/test/java/javax/net/ssl/TestSSLSessions.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package javax.net.ssl; + +/** + * TestSSLSessions is a convenience class for other tests that want + * precreated SSLSessions for testing. It contains a connected + * client/server pair of SSLSession as well as an invalid SSLSession. + */ +public final class TestSSLSessions { + + /** + * An invalid session that is not connected + */ + public final SSLSession invalid; + + /** + * The server side of a connected session + */ + public final SSLSession server; + + /** + * The client side of a connected session + */ + public final SSLSession client; + + /** + * The associated SSLSocketTest.Helper that is the source of + * the client and server SSLSessions. + */ + public final TestSSLSocketPair s; + + private TestSSLSessions(SSLSession invalid, + SSLSession server, + SSLSession client, + TestSSLSocketPair s) { + this.invalid = invalid; + this.server = server; + this.client = client; + this.s = s; + } + + public static final TestSSLSessions create() { + try { + SSLSocketFactory sf = (SSLSocketFactory) SSLSocketFactory.getDefault(); + SSLSocket ssl = (SSLSocket) sf.createSocket(); + SSLSession invalid = ssl.getSession(); + TestSSLSocketPair s = TestSSLSocketPair.create(); + return new TestSSLSessions(invalid, s.server.getSession(), s.client.getSession(), s); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/support/src/test/java/javax/net/ssl/TestSSLSocketPair.java b/support/src/test/java/javax/net/ssl/TestSSLSocketPair.java new file mode 100644 index 0000000..4409183 --- /dev/null +++ b/support/src/test/java/javax/net/ssl/TestSSLSocketPair.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package javax.net.ssl; + +/** + * TestSSLSocketPair is a convenience class for other tests that want + * a pair of connected and handshaked client and server SSLSockets for + * testing. + */ +public final class TestSSLSocketPair { + public final TestSSLContext c; + public final SSLSocket server; + public final SSLSocket client; + + private TestSSLSocketPair (TestSSLContext c, + SSLSocket server, + SSLSocket client) { + this.c = c; + this.server = server; + this.client = client; + } + + /** + * based on test_SSLSocket_startHandshake + */ + public static TestSSLSocketPair create () { + TestSSLContext c = TestSSLContext.create(); + SSLSocket[] sockets = connect(c, null); + return new TestSSLSocketPair(c, sockets[0], sockets[1]); + } + + /** + * Create a new connected server/client socket pair within a + * existing SSLContext. Optional clientCipherSuites allows + * forcing new SSLSession to test SSLSessionContext caching + */ + public static SSLSocket[] connect (final TestSSLContext c, + String[] clientCipherSuites) { + try { + SSLSocket client = (SSLSocket) + c.sslContext.getSocketFactory().createSocket(c.host, c.port); + final SSLSocket server = (SSLSocket) c.serverSocket.accept(); + Thread thread = new Thread(new Runnable () { + public void run() { + try { + server.startHandshake(); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + }); + thread.start(); + if (clientCipherSuites != null) { + client.setEnabledCipherSuites(clientCipherSuites); + } + client.startHandshake(); + thread.join(); + return new SSLSocket[] { server, client }; + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} + diff --git a/x-net/src/main/java/javax/net/ssl/DefaultHostnameVerifier.java b/x-net/src/main/java/javax/net/ssl/DefaultHostnameVerifier.java index 6adca42..779c46a 100644 --- a/x-net/src/main/java/javax/net/ssl/DefaultHostnameVerifier.java +++ b/x-net/src/main/java/javax/net/ssl/DefaultHostnameVerifier.java @@ -89,44 +89,7 @@ class DefaultHostnameVerifier implements HostnameVerifier { throw new NullPointerException("host to verify is null"); } - ssl.startHandshake(); SSLSession session = ssl.getSession(); - if(session == null) { - // In our experience this only happens under IBM 1.4.x when - // spurious (unrelated) certificates show up in the server' - // chain. Hopefully this will unearth the real problem: - InputStream in = ssl.getInputStream(); - in.available(); - /* - If you're looking at the 2 lines of code above because - you're running into a problem, you probably have two - options: - - #1. Clean up the certificate chain that your server - is presenting (e.g. edit "/etc/apache2/server.crt" - or wherever it is your server's certificate chain - is defined). - - OR - - #2. Upgrade to an IBM 1.5.x or greater JVM, or switch - to a non-IBM JVM. - */ - - // If ssl.getInputStream().available() didn't cause an - // exception, maybe at least now the session is available? - session = ssl.getSession(); - if(session == null) { - // If it's still null, probably a startHandshake() will - // unearth the real problem. - ssl.startHandshake(); - - // Okay, if we still haven't managed to cause an exception, - // might as well go for the NPE. Or maybe we're okay now? - session = ssl.getSession(); - } - } - Certificate[] certs = session.getPeerCertificates(); X509Certificate x509 = (X509Certificate) certs[0]; verify(host, x509); diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/AbstractSessionContext.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/AbstractSessionContext.java index b780943..7a0985e 100644 --- a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/AbstractSessionContext.java +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/AbstractSessionContext.java @@ -34,7 +34,6 @@ abstract class AbstractSessionContext implements SSLSessionContext { volatile int maximumSize; volatile int timeout; - final SSLParameters parameters; final int sslCtxNativePointer; /** Identifies OpenSSL sessions. */ @@ -43,13 +42,12 @@ abstract class AbstractSessionContext implements SSLSessionContext { /** * Constructs a new session context. * - * @param parameters + * @param sslCtxNativePointer Associated native SSL_CTX * @param maximumSize of cache * @param timeout for cache entries */ - AbstractSessionContext(SSLParameters parameters, int sslCtxNativePointer, + AbstractSessionContext(int sslCtxNativePointer, int maximumSize, int timeout) { - this.parameters = parameters; this.sslCtxNativePointer = sslCtxNativePointer; this.maximumSize = maximumSize; this.timeout = timeout; @@ -133,6 +131,7 @@ abstract class AbstractSessionContext implements SSLSessionContext { daos.writeInt(data.length); daos.write(data); } + // TODO: local certificates? return baos.toByteArray(); } catch (IOException e) { @@ -172,8 +171,7 @@ abstract class AbstractSessionContext implements SSLSessionContext { certs[i] = X509Certificate.getInstance(certData); } - return new OpenSSLSessionImpl(sessionData, parameters, host, port, - certs, this); + return new OpenSSLSessionImpl(sessionData, host, port, certs, this); } catch (IOException e) { log(e); return null; diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientSessionContext.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientSessionContext.java index 338fc39..66e8d03 100644 --- a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientSessionContext.java +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ClientSessionContext.java @@ -63,10 +63,9 @@ public class ClientSessionContext extends AbstractSessionContext { final SSLClientSessionCache persistentCache; - public ClientSessionContext(SSLParameters parameters, - int sslCtxNativePointer, - SSLClientSessionCache persistentCache) { - super(parameters, sslCtxNativePointer, 10, 0); + public ClientSessionContext(int sslCtxNativePointer, + SSLClientSessionCache persistentCache) { + super(sslCtxNativePointer, 10, 0); this.persistentCache = persistentCache; } diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java index ad6ae15..2220d36 100644 --- a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/NativeCrypto.java @@ -17,11 +17,13 @@ package org.apache.harmony.xnet.provider.jsse; import java.io.ByteArrayOutputStream; -import java.io.OutputStreamWriter; import java.io.IOException; -import java.util.ArrayList; +import java.io.OutputStreamWriter; +import java.net.Socket; import java.security.PrivateKey; +import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import java.util.ArrayList; import org.bouncycastle.openssl.PEMWriter; @@ -78,6 +80,10 @@ public class NativeCrypto { private static final String SUPPORTED_PROTOCOL_SSLV3 = "SSLv3"; private static final String SUPPORTED_PROTOCOL_TLSV1 = "TLSv1"; + // SSL mode + public static long SSL_MODE_HANDSHAKE_CUTTHROUGH = 0x00000040L; + + // SSL options public static long SSL_OP_NO_SSLv3 = 0x02000000L; public static long SSL_OP_NO_TLSv1 = 0x04000000L; @@ -115,7 +121,7 @@ public class NativeCrypto { // TODO support more than RSA certificates? non-openssl // SSLEngine implementation did these callbacks during // handshake after selecting cipher suite, not before - // handshake. + // handshake. Should do the same via SSL_CTX_set_client_cert_cb final String alias = (client) ? sslParameters.getKeyManager().chooseClientAlias(new String[] { "RSA" }, null, null) : sslParameters.getKeyManager().chooseServerAlias("RSA", null, null); @@ -156,10 +162,18 @@ public class NativeCrypto { } + public static native long SSL_get_mode(int ssl); + + public static native long SSL_set_mode(int ssl, long options); + + public static native long SSL_clear_mode(int ssl, long options); + public static native long SSL_get_options(int ssl); public static native long SSL_set_options(int ssl, long options); + public static native long SSL_clear_options(int ssl, long options); + public static String[] getSupportedProtocols() { return new String[] { SUPPORTED_PROTOCOL_SSLV3, SUPPORTED_PROTOCOL_TLSV1 }; } @@ -178,72 +192,139 @@ public class NativeCrypto { public static void setEnabledProtocols(int ssl, String[] protocols) { if (protocols == null) { - throw new IllegalArgumentException("Provided parameter is null"); + throw new IllegalArgumentException("protocols == null"); } + // openssl uses negative logic letting you disable protocols. - // so first, lets turn them all off, and in the loop selectively enable - long options = SSL_get_options(ssl); - options |= (SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1); + // so first, assume we need to set all (disable all ) and clear none (enable none). + // in the loop, selectively move bits from set to clear (from disable to enable) + long optionsToSet = (SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1); + long optionsToClear = 0; for (int i = 0; i < protocols.length; i++) { - if (protocols[i].equals(SUPPORTED_PROTOCOL_SSLV3)) { - options ^= SSL_OP_NO_SSLv3; - } else if (protocols[i].equals(SUPPORTED_PROTOCOL_TLSV1)) { - options ^= SSL_OP_NO_TLSv1; + String protocol = protocols[i]; + if (protocol == null) { + throw new IllegalArgumentException("protocols[" + i + "] == null"); + } + if (protocol.equals(SUPPORTED_PROTOCOL_SSLV3)) { + optionsToSet &= ~SSL_OP_NO_SSLv3; + optionsToClear |= SSL_OP_NO_SSLv3; + } else if (protocol.equals(SUPPORTED_PROTOCOL_TLSV1)) { + optionsToSet &= ~SSL_OP_NO_TLSv1; + optionsToClear |= SSL_OP_NO_TLSv1; } else { - throw new IllegalArgumentException("Protocol " + protocols[i] + + throw new IllegalArgumentException("Protocol " + protocol + " is not supported"); } } - SSL_set_options(ssl, options); + + SSL_set_options(ssl, optionsToSet); + SSL_clear_options(ssl, optionsToClear); + } + + public static String[] checkEnabledProtocols(String[] protocols) { + if (protocols == null) { + throw new IllegalArgumentException("protocols parameter is null"); + } + for (int i = 0; i < protocols.length; i++) { + String protocol = protocols[i]; + if (protocol == null) { + throw new IllegalArgumentException("protocols[" + i + "] == null"); + } + if ((!protocol.equals(SUPPORTED_PROTOCOL_SSLV3)) + && (!protocol.equals(SUPPORTED_PROTOCOL_TLSV1))) { + throw new IllegalArgumentException("Protocol " + protocol + + " is not supported"); + } + } + return protocols; } public static native String[] SSL_get_ciphers(int ssl); public static native void SSL_set_cipher_list(int ssl, String ciphers); - public static void setEnabledCipherSuites(int ssl, String[] suites) { - if (suites == null) { - throw new IllegalArgumentException("Provided parameter is null"); - } - - // makes sure all suites are valid, throwing on error - String[] supportedCipherSuites = getSupportedCipherSuites(); - for (String suite : suites) { - findSuite(supportedCipherSuites, suite); - } - + public static void setEnabledCipherSuites(int ssl, String[] cipherSuites) { + checkEnabledCipherSuites(cipherSuites); String controlString = ""; - for (int i = 0; i < suites.length; i++) { + for (int i = 0; i < cipherSuites.length; i++) { + String cipherSuite = cipherSuites[i]; if (i == 0) { - controlString = suites[i]; + controlString = cipherSuite; } else { - controlString += ":" + suites[i]; + controlString += ":" + cipherSuite; } } SSL_set_cipher_list(ssl, controlString); } + public static String[] checkEnabledCipherSuites(String[] cipherSuites) { + if (cipherSuites == null) { + throw new IllegalArgumentException("cipherSuites == null"); + } + // makes sure all suites are valid, throwing on error + String[] supportedCipherSuites = getSupportedCipherSuites(); + for (int i = 0; i < cipherSuites.length; i++) { + String cipherSuite = cipherSuites[i]; + if (cipherSuite == null) { + throw new IllegalArgumentException("cipherSuites[" + i + "] == null"); + } + findSuite(supportedCipherSuites, cipherSuite); + } + return cipherSuites; + } + private static void findSuite(String[] supportedCipherSuites, String suite) { - for(int i = 0; i < supportedCipherSuites.length; i++) { - if (supportedCipherSuites[i].equals(suite)) { + for (String supportedCipherSuite : supportedCipherSuites) { + if (supportedCipherSuite.equals(suite)) { return; } } throw new IllegalArgumentException("Protocol " + suite + " is not supported."); } - public static native void SSL_free(int ssl); + /* + * See the OpenSSL ssl.h header file for more information. + */ + public static final int SSL_VERIFY_NONE = 0x00; + public static final int SSL_VERIFY_PEER = 0x01; + public static final int SSL_VERIFY_FAIL_IF_NO_PEER_CERT = 0x02; + public static final int SSL_VERIFY_CLIENT_ONCE = 0x04; + + public static native void SSL_set_verify(int sslNativePointer, int mode) throws IOException; + + public static native void SSL_set_session(int sslNativePointer, int sslSessionNativePointer) throws IOException; + + public static native void SSL_set_session_creation_enabled(int sslNativePointer, boolean creationEnabled) throws IOException; + + /** + * Returns the sslSessionNativePointer of the negotiated session + */ + public static native int SSL_do_handshake(int sslNativePointer, Socket sock, + CertificateChainVerifier ccv, HandshakeCompletedCallback hcc, + int timeout, boolean client_mode) throws IOException, CertificateException; + + public static native byte[][] SSL_get_certificate(int sslNativePointer); + + public static native void SSL_free(int sslNativePointer); public interface CertificateChainVerifier { /** * Verify that we trust the certificate chain is trusted. * - * @param bytes An array of certficates in byte form + * @param bytes An array of certficates in PEM encode bytes + * @param authMethod auth algorithm name * - * @throws AlertException if the certificate is untrusted - * @return false if there are other problems verifying the certificate chain + * @throws CertificateException if the certificate is untrusted + */ + public void verifyCertificateChain(byte[][] bytes, String authMethod) throws CertificateException; + } + + public interface HandshakeCompletedCallback { + /** + * Called when SSL handshake is completed. Note that this can + * be after SSL_do_handshake returns when handshake cutthrough + * is enabled. */ - // TODO throw on error in all cases instead of returning false - public boolean verifyCertificateChain(byte[][] bytes); + public void handshakeCompleted(); } } diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketImpl.java index c79dccf..8d5a43e 100644 --- a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketImpl.java +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketImpl.java @@ -28,34 +28,31 @@ import java.net.Socket; */ public class OpenSSLServerSocketImpl extends javax.net.ssl.SSLServerSocket { private final SSLParameters sslParameters; - private int sslNativePointer; + private String[] enabledProtocols = NativeCrypto.getSupportedProtocols(); + private String[] enabledCipherSuites = NativeCrypto.getDefaultCipherSuites(); protected OpenSSLServerSocketImpl(SSLParameters sslParameters) throws IOException { super(); this.sslParameters = sslParameters; - this.sslNativePointer = NativeCrypto.SSL_new(sslParameters); } protected OpenSSLServerSocketImpl(int port, SSLParameters sslParameters) throws IOException { super(port); this.sslParameters = sslParameters; - this.sslNativePointer = NativeCrypto.SSL_new(sslParameters); } protected OpenSSLServerSocketImpl(int port, int backlog, SSLParameters sslParameters) throws IOException { super(port, backlog); this.sslParameters = sslParameters; - this.sslNativePointer = NativeCrypto.SSL_new(sslParameters); } protected OpenSSLServerSocketImpl(int port, int backlog, InetAddress iAddress, SSLParameters sslParameters) throws IOException { super(port, backlog, iAddress); this.sslParameters = sslParameters; - this.sslNativePointer = NativeCrypto.SSL_new(sslParameters); } @Override @@ -85,7 +82,7 @@ public class OpenSSLServerSocketImpl extends javax.net.ssl.SSLServerSocket { */ @Override public String[] getEnabledProtocols() { - return NativeCrypto.getEnabledProtocols(sslNativePointer); + return enabledProtocols.clone(); } /** @@ -99,7 +96,7 @@ public class OpenSSLServerSocketImpl extends javax.net.ssl.SSLServerSocket { */ @Override public void setEnabledProtocols(String[] protocols) { - NativeCrypto.setEnabledProtocols(sslNativePointer, protocols); + enabledProtocols = NativeCrypto.checkEnabledProtocols(protocols); } @Override @@ -109,7 +106,7 @@ public class OpenSSLServerSocketImpl extends javax.net.ssl.SSLServerSocket { @Override public String[] getEnabledCipherSuites() { - return NativeCrypto.SSL_get_ciphers(sslNativePointer); + return enabledCipherSuites.clone(); } /** @@ -122,33 +119,7 @@ public class OpenSSLServerSocketImpl extends javax.net.ssl.SSLServerSocket { */ @Override public void setEnabledCipherSuites(String[] suites) { - NativeCrypto.setEnabledCipherSuites(sslNativePointer, suites); - } - - /** - * See the OpenSSL ssl.h header file for more information. - */ - static private int SSL_VERIFY_NONE = 0x00; - static private int SSL_VERIFY_PEER = 0x01; - static private int SSL_VERIFY_FAIL_IF_NO_PEER_CERT = 0x02; - static private int SSL_VERIFY_CLIENT_ONCE = 0x04; - - /** - * Calls the SSL_set_verify(...) OpenSSL function with the passed int - * value. - */ - private static native void nativesetclientauth(int sslNativePointer, int value); - - private void setClientAuth() { - int value = SSL_VERIFY_NONE; - - if (sslParameters.getNeedClientAuth()) { - value |= SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT|SSL_VERIFY_CLIENT_ONCE; - } else if (sslParameters.getWantClientAuth()) { - value |= SSL_VERIFY_PEER|SSL_VERIFY_CLIENT_ONCE; - } - - nativesetclientauth(sslNativePointer, value); + enabledCipherSuites = NativeCrypto.checkEnabledCipherSuites(suites); } @Override @@ -159,7 +130,6 @@ public class OpenSSLServerSocketImpl extends javax.net.ssl.SSLServerSocket { @Override public void setWantClientAuth(boolean want) { sslParameters.setWantClientAuth(want); - setClientAuth(); } @Override @@ -170,7 +140,6 @@ public class OpenSSLServerSocketImpl extends javax.net.ssl.SSLServerSocket { @Override public void setNeedClientAuth(boolean need) { sslParameters.setNeedClientAuth(need); - setClientAuth(); } @Override @@ -185,26 +154,10 @@ public class OpenSSLServerSocketImpl extends javax.net.ssl.SSLServerSocket { @Override public Socket accept() throws IOException { - OpenSSLSocketImpl socket = new OpenSSLSocketImpl(sslParameters, null); + OpenSSLSocketImpl socket = new OpenSSLSocketImpl(sslParameters, + enabledProtocols.clone(), + enabledCipherSuites.clone()); implAccept(socket); - socket.accept(sslNativePointer); return socket; } - - /** - * Unbinds the port if the socket is open. - */ - @Override - protected void finalize() throws Throwable { - if (!isClosed()) close(); - } - - @Override - public synchronized void close() throws IOException { - if (sslNativePointer != 0) { - NativeCrypto.SSL_free(sslNativePointer); - sslNativePointer = 0; - } - super.close(); - } } diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl.java index 2a3908c..f42bcae 100644 --- a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl.java +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl.java @@ -53,10 +53,10 @@ public class OpenSSLSessionImpl implements SSLSession { private TwoKeyHashMap values = new TwoKeyHashMap(); private javax.security.cert.X509Certificate[] peerCertificateChain; protected int sslSessionNativePointer; - private SSLParameters sslParameters; private String peerHost; private int peerPort; - private final AbstractSessionContext sessionContext; + private AbstractSessionContext sessionContext; + private byte[] id; /** * Class constructor creates an SSL session context given the appropriate @@ -65,10 +65,10 @@ public class OpenSSLSessionImpl implements SSLSession { * @param session the Identifier for SSL session * @param sslParameters the SSL parameters like ciphers' suites etc. */ - protected OpenSSLSessionImpl(int sslSessionNativePointer, SSLParameters sslParameters, + protected OpenSSLSessionImpl(int sslSessionNativePointer, X509Certificate[] localCertificates, String peerHost, int peerPort, AbstractSessionContext sessionContext) { this.sslSessionNativePointer = sslSessionNativePointer; - this.sslParameters = sslParameters; + this.localCertificates = localCertificates; this.peerHost = peerHost; this.peerPort = peerPort; this.sessionContext = sessionContext; @@ -79,13 +79,13 @@ public class OpenSSLSessionImpl implements SSLSession { * allows loading the saved session. * @throws IOException */ - OpenSSLSessionImpl(byte[] derData, SSLParameters sslParameters, + OpenSSLSessionImpl(byte[] derData, String peerHost, int peerPort, javax.security.cert.X509Certificate[] peerCertificateChain, AbstractSessionContext sessionContext) throws IOException { this(initializeNativeImpl(derData, derData.length), - sslParameters, + null, peerHost, peerPort, sessionContext); @@ -103,10 +103,17 @@ public class OpenSSLSessionImpl implements SSLSession { * @return array of sessions' identifiers. */ public byte[] getId() { - return getId(sslSessionNativePointer); + if (id == null) { + resetId(); + } + return id; } - private static native byte[] getId(int sslSessionNativePointer); + public static native byte[] getId(int sslSessionNativePointer); + + void resetId() { + id = getId(sslSessionNativePointer); + } /** * Get the session object in DER format. This allows saving the session @@ -179,12 +186,6 @@ public class OpenSSLSessionImpl implements SSLSession { * were used during the handshaking phase. */ public Certificate[] getLocalCertificates() { - X509Certificate[] localCertificates = null; - // This implementation only supports RSA certificates. - String alias = sslParameters.getKeyManager().chooseClientAlias(new String[] { "RSA" }, null, null); - if (alias != null) { - localCertificates = sslParameters.getKeyManager().getCertificateChain(alias); - } return localCertificates; } @@ -271,6 +272,7 @@ public class OpenSSLSessionImpl implements SSLSession { * */ public Principal getPeerPrincipal() throws SSLPeerUnverifiedException { + getPeerCertificates(); if (peerCertificates == null) { throw new SSLPeerUnverifiedException("No peer certificate"); } @@ -371,6 +373,7 @@ public class OpenSSLSessionImpl implements SSLSession { */ public void invalidate() { isValid = false; + sessionContext = null; } /** @@ -476,5 +479,5 @@ public class OpenSSLSessionImpl implements SSLSession { freeImpl(sslSessionNativePointer); } - private static native void freeImpl(int session); + public static native void freeImpl(int session); } diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl.java index 58d2110..edef590 100644 --- a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl.java +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl.java @@ -36,6 +36,7 @@ import javax.net.ssl.HandshakeCompletedEvent; import javax.net.ssl.HandshakeCompletedListener; import javax.net.ssl.SSLException; import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLPeerUnverifiedException; import javax.net.ssl.SSLSession; import org.apache.harmony.security.provider.cert.X509CertImpl; @@ -51,7 +52,7 @@ import org.apache.harmony.security.provider.cert.X509CertImpl; */ public class OpenSSLSocketImpl extends javax.net.ssl.SSLSocket - implements NativeCrypto.CertificateChainVerifier { + implements NativeCrypto.CertificateChainVerifier, NativeCrypto.HandshakeCompletedCallback { private int sslNativePointer; private InputStream is; private OutputStream os; @@ -59,10 +60,21 @@ public class OpenSSLSocketImpl private final Object readLock = new Object(); private final Object writeLock = new Object(); private SSLParameters sslParameters; + private String[] enabledProtocols; + private String[] enabledCipherSuites; private OpenSSLSessionImpl sslSession; private Socket socket; private boolean autoClose; private boolean handshakeStarted = false; + + /** + * Not set to true until the update from native that tells us the + * full handshake is complete, since SSL_do_handshake can return + * before the handshake is completely done due to + * handshake_cutthrough support. + */ + private boolean handshakeCompleted = false; + private ArrayList<HandshakeCompletedListener> listeners; private int timeout = 0; // BEGIN android-added @@ -93,6 +105,20 @@ public class OpenSSLSocketImpl } /** + * Create an OpenSSLSocketImpl from an OpenSSLServerSocketImpl + * + * @param sslParameters Parameters for the SSL + * context + * @throws IOException if network fails + */ + protected OpenSSLSocketImpl(SSLParameters sslParameters, + String[] enabledProtocols, + String[] enabledCipherSuites) throws IOException { + super(); + init(sslParameters, enabledProtocols, enabledCipherSuites); + } + + /** * Class constructor with 3 parameters * * @throws IOException if network fails @@ -166,47 +192,35 @@ public class OpenSSLSocketImpl * future handshaking. */ private void init(SSLParameters sslParameters) throws IOException { - this.sslParameters = sslParameters; - this.sslNativePointer = NativeCrypto.SSL_new(sslParameters); - updateInstanceCount(1); + init(sslParameters, + NativeCrypto.getSupportedProtocols(), + NativeCrypto.getDefaultCipherSuites()); } /** - * Construct a OpenSSLSocketImpl from an SSLParameters and an - * existing SSL native pointer. Used for transitioning accepting - * the OpenSSLSocketImpl within OpenSSLServerSocketImpl. + * Initialize the SSL socket and set the certificates for the + * future handshaking. */ - protected OpenSSLSocketImpl(SSLParameters sslParameters, - OpenSSLServerSocketImpl dummy) { - super(); - this.sslParameters = (SSLParameters) sslParameters.clone(); + private void init(SSLParameters sslParameters, + String[] enabledProtocols, + String[] enabledCipherSuites) throws IOException { + this.sslParameters = sslParameters; + this.enabledProtocols = enabledProtocols; + this.enabledCipherSuites = enabledCipherSuites; updateInstanceCount(1); } /** - * Adds OpenSSL functionality to the existing socket and starts the SSL - * handshaking. - */ - private native boolean nativeconnect(int sslNativePointer, Socket sock, int timeout, boolean client_mode, int sslSessionNativePointer) throws IOException; - private native int nativegetsslsession(int sslNativePointer); - private native String nativecipherauthenticationmethod(int sslNativePointer); - - /** * Gets the suitable session reference from the session cache container. * * @return OpenSSLSessionImpl */ - private OpenSSLSessionImpl getCachedClientSession() { - if (!sslParameters.getUseClientMode()) { - return null; - } + private OpenSSLSessionImpl getCachedClientSession(ClientSessionContext sessionContext) { if (super.getInetAddress() == null || super.getInetAddress().getHostAddress() == null || super.getInetAddress().getHostName() == null) { return null; } - ClientSessionContext sessionContext - = sslParameters.getClientSessionContext(); return (OpenSSLSessionImpl) sessionContext.getSession( super.getInetAddress().getHostName(), super.getPort()); @@ -229,7 +243,15 @@ public class OpenSSLSocketImpl * * @throws <code>IOException</code> if network fails */ - public synchronized void startHandshake() throws IOException { + public void startHandshake() throws IOException { + startHandshake(true); + } + + /** + * Perform the handshake + * @param full If true, disable handshake cutthrough for a fully synchronous handshake + */ + public synchronized void startHandshake(boolean full) throws IOException { synchronized (handshakeLock) { if (!handshakeStarted) { handshakeStarted = true; @@ -238,11 +260,56 @@ public class OpenSSLSocketImpl } } - OpenSSLSessionImpl session = getCachedClientSession(); + this.sslNativePointer = NativeCrypto.SSL_new(sslParameters); + // TODO move more code out of NativeCrypto.SSL_new + NativeCrypto.setEnabledProtocols(sslNativePointer, enabledProtocols); + NativeCrypto.setEnabledCipherSuites(sslNativePointer, enabledCipherSuites); + + boolean enableSessionCreation = sslParameters.getEnableSessionCreation(); + if (!enableSessionCreation) { + NativeCrypto.SSL_set_session_creation_enabled(sslNativePointer, + enableSessionCreation); + } + + boolean client = sslParameters.getUseClientMode(); - // Check if it's allowed to create a new session (default is true) - if (session == null && !sslParameters.getEnableSessionCreation()) { - throw new SSLHandshakeException("SSL Session may not be created"); + AbstractSessionContext sessionContext; + OpenSSLSessionImpl session; + if (client) { + // look for client session to reuse + ClientSessionContext clientSessionContext = sslParameters.getClientSessionContext(); + sessionContext = clientSessionContext; + session = getCachedClientSession(clientSessionContext); + if (session != null) { + NativeCrypto.SSL_set_session(sslNativePointer, session.sslSessionNativePointer); + } + } else { + sessionContext = sslParameters.getServerSessionContext(); + session = null; + } + + // setup peer certificate verification + if (client) { + // TODO support for anonymous cipher would require us to conditionally use SSL_VERIFY_NONE + } else { + // needing client auth takes priority... + if (sslParameters.getNeedClientAuth()) { + NativeCrypto.SSL_set_verify(sslNativePointer, + NativeCrypto.SSL_VERIFY_PEER| + NativeCrypto.SSL_VERIFY_FAIL_IF_NO_PEER_CERT| + NativeCrypto.SSL_VERIFY_CLIENT_ONCE); + // ... over just wanting it... + } else if (sslParameters.getWantClientAuth()) { + NativeCrypto.SSL_set_verify(sslNativePointer, + NativeCrypto.SSL_VERIFY_PEER| + NativeCrypto.SSL_VERIFY_CLIENT_ONCE); + } + // ... and it defaults properly so we don't need call SSL_set_verify in the common case. + } + + if (client && full) { + // we want to do a full synchronous handshake, so turn off cutthrough + NativeCrypto.SSL_clear_mode(sslNativePointer, NativeCrypto.SSL_MODE_HANDSHAKE_CUTTHROUGH); } // BEGIN android-added @@ -253,58 +320,60 @@ public class OpenSSLSocketImpl } // END android-added + Socket socket = this.socket != null ? this.socket : this; - int sslSessionNativePointer = session != null ? session.sslSessionNativePointer : 0; - boolean reusedSession = nativeconnect(sslNativePointer, socket, timeout, - sslParameters.getUseClientMode(), sslSessionNativePointer); - if (reusedSession) { - // nativeconnect shouldn't return true if the session is not - // done + int sslSessionNativePointer; + try { + sslSessionNativePointer = NativeCrypto.SSL_do_handshake(sslNativePointer, socket, this, this, timeout, client); + } catch (CertificateException e) { + throw new SSLPeerUnverifiedException(e.getMessage()); + } + byte[] sessionId = OpenSSLSessionImpl.getId(sslSessionNativePointer); + sslSession = (OpenSSLSessionImpl) sessionContext.getSession(sessionId); + if (sslSession != null) { session.lastAccessedTime = System.currentTimeMillis(); - sslSession = session; - LoggerHolder.logger.fine("Reused cached session for " - + getInetAddress().getHostName() + "."); + + getInetAddress() + "."); + OpenSSLSessionImpl.freeImpl(sslSessionNativePointer); } else { - if (session != null) { - LoggerHolder.logger.fine("Reuse of cached session for " - + getInetAddress().getHostName() + " failed."); + if (!enableSessionCreation) { + // Should have been prevented by NativeCrypto.SSL_set_session_creation_enabled + throw new IllegalStateException("SSL Session may not be created"); + } + byte[][] localCertificatesBytes = NativeCrypto.SSL_get_certificate(sslNativePointer); + X509Certificate[] localCertificates; + if (localCertificatesBytes == null) { + localCertificates = null; } else { - LoggerHolder.logger.fine("Created new session for " - + getInetAddress().getHostName() + "."); + localCertificates = new X509Certificate[localCertificatesBytes.length]; + for (int i = 0; i < localCertificatesBytes.length; i++) { + try { + // TODO do not go through PEM decode, DER encode, DER decode + localCertificates[i] + = new X509CertImpl( + javax.security.cert.X509Certificate.getInstance( + localCertificatesBytes[i]).getEncoded()); + } catch (javax.security.cert.CertificateException e) { + throw new IOException("Problem decoding local certificate", e); + } + } } - AbstractSessionContext sessionContext = - (sslParameters.getUseClientMode()) ? - sslParameters.getClientSessionContext() : - sslParameters.getServerSessionContext(); - sslSessionNativePointer = nativegetsslsession(sslNativePointer); if (address == null) { - sslSession = new OpenSSLSessionImpl(sslSessionNativePointer, sslParameters, + sslSession = new OpenSSLSessionImpl(sslSessionNativePointer, localCertificates, super.getInetAddress().getHostName(), super.getPort(), sessionContext); } else { - sslSession = new OpenSSLSessionImpl(sslSessionNativePointer, sslParameters, + sslSession = new OpenSSLSessionImpl(sslSessionNativePointer, localCertificates, address.getHostName(), address.getPort(), sessionContext); } - - try { - X509Certificate[] peerCertificates = (X509Certificate[]) - sslSession.getPeerCertificates(); - - if (peerCertificates == null - || peerCertificates.length == 0) { - throw new SSLException("Server sends no certificate"); - } - - String authMethod = nativecipherauthenticationmethod(sslNativePointer); - sslParameters.getTrustManager().checkServerTrusted(peerCertificates, - authMethod); + // putSession will be done later in handshakeCompleted() callback + if (handshakeCompleted) { sessionContext.putSession(sslSession); - } catch (CertificateException e) { - throw new SSLException("Not trusted server certificate", e); } + LoggerHolder.logger.fine("Created new session for " + + getInetAddress().getHostName() + "."); } // BEGIN android-added @@ -314,74 +383,91 @@ public class OpenSSLSocketImpl } // END android-added - if (listeners != null) { - // notify the listeners - HandshakeCompletedEvent event = - new HandshakeCompletedEvent(this, sslSession); - int size = listeners.size(); - for (int i = 0; i < size; i++) { - listeners.get(i).handshakeCompleted(event); - } + // notifyHandshakeCompletedListeners will be done later in handshakeCompleted() callback + if (handshakeCompleted) { + notifyHandshakeCompletedListeners(); } - } - // To be synchronized because of the verify_callback - native synchronized int nativeaccept(int sslNativePointer, Socket socketObject); + } /** - * Performs the first part of a SSL/TLS handshaking process with a given - * 'host' connection and initializes the SSLSession. + * Implementation of NativeCrypto.HandshakeCompletedCallback + * invoked via JNI from info_callback */ - protected void accept(int serverSslNativePointer) throws IOException { - // Must be set because no handshaking is necessary - // in this situation - handshakeStarted = true; + public void handshakeCompleted() { + handshakeCompleted = true; - sslNativePointer = nativeaccept(serverSslNativePointer, this); + // If sslSession is null, the handshake was completed during + // the call to NativeCrypto.SSL_do_handshake and not during a + // later read operation. That means we do not need to fixup + // the SSLSession and session cache or notify + // HandshakeCompletedListeners, it will be done in + // startHandshake. + if (sslSession == null) { + return; + } - ServerSessionContext sessionContext - = sslParameters.getServerSessionContext(); - sslSession = new OpenSSLSessionImpl(nativegetsslsession(sslNativePointer), - sslParameters, super.getInetAddress().getHostName(), - super.getPort(), sessionContext); - sslSession.lastAccessedTime = System.currentTimeMillis(); + // reset session id from the native pointer and update the + // appropriate cache. + sslSession.resetId(); + AbstractSessionContext sessionContext = + (sslParameters.getUseClientMode()) + ? sslParameters.getClientSessionContext() + : sslParameters.getServerSessionContext(); sessionContext.putSession(sslSession); + + // let listeners know we are finally done + notifyHandshakeCompletedListeners(); + } + + private void notifyHandshakeCompletedListeners() { + if (listeners != null && !listeners.isEmpty()) { + // notify the listeners + HandshakeCompletedEvent event = + new HandshakeCompletedEvent(this, sslSession); + for (HandshakeCompletedListener listener : listeners) { + try { + listener.handshakeCompleted(event); + } catch (RuntimeException e) { + // TODO log? + } + } + } } /** * Implementation of NativeCrypto.CertificateChainVerifier. * - * Callback method for the OpenSSL native certificate verification process. + * @param bytes An array of certficates in PEM encode bytes + * @param authMethod auth algorithm name * - * @param bytes Byte array containing the cert's - * information. - * @return false if the certificate verification fails or true if OK + * @throws CertificateException if the certificate is untrusted */ @SuppressWarnings("unused") - public boolean verifyCertificateChain(byte[][] bytes) { + public void verifyCertificateChain(byte[][] bytes, String authMethod) throws CertificateException { try { - X509Certificate[] peerCertificateChain - = new X509Certificate[bytes.length]; - for(int i = 0; i < bytes.length; i++) { + X509Certificate[] peerCertificateChain = new X509Certificate[bytes.length]; + for (int i = 0; i < bytes.length; i++) { peerCertificateChain[i] = new X509CertImpl(javax.security.cert.X509Certificate.getInstance(bytes[i]).getEncoded()); } - try { - // TODO "null" String - sslParameters.getTrustManager().checkClientTrusted(peerCertificateChain, "null"); - } catch (CertificateException e) { - throw new AlertException(AlertProtocol.BAD_CERTIFICATE, - new SSLException("Not trusted server certificate", e)); + boolean client = sslParameters.getUseClientMode(); + if (client) { + if (peerCertificateChain == null + || peerCertificateChain.length == 0) { + throw new SSLException("Server sends no certificate"); + } + sslParameters.getTrustManager().checkServerTrusted(peerCertificateChain, authMethod); + } else { + sslParameters.getTrustManager().checkClientTrusted(peerCertificateChain, authMethod); } - } catch (javax.security.cert.CertificateException e) { - // TODO throw in all cases for consistency - return false; - } catch (IOException e) { - // TODO throw in all cases for consistency - return false; + + } catch (CertificateException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e); } - return true; } /** @@ -421,20 +507,22 @@ public class OpenSSLSocketImpl } } + /** + * This method is not supported for this SSLSocket implementation + * because reading from an SSLSocket may involve writing to the + * network. + */ public void shutdownInput() throws IOException { - if (socket == null) { - super.shutdownInput(); - return; - } - socket.shutdownInput(); + throw new UnsupportedOperationException(); } + /** + * This method is not supported for this SSLSocket implementation + * because writing to an SSLSocket may involve reading from the + * network. + */ public void shutdownOutput() throws IOException { - if (socket == null) { - super.shutdownOutput(); - return; - } - socket.shutdownOutput(); + throw new UnsupportedOperationException(); } /** @@ -455,7 +543,7 @@ public class OpenSSLSocketImpl /* Note: When startHandshake() throws an exception, no * SSLInputStream object will be created. */ - OpenSSLSocketImpl.this.startHandshake(); + OpenSSLSocketImpl.this.startHandshake(false); } /** @@ -499,7 +587,7 @@ public class OpenSSLSocketImpl /* Note: When startHandshake() throws an exception, no * SSLOutputStream object will be created. */ - OpenSSLSocketImpl.this.startHandshake(); + OpenSSLSocketImpl.this.startHandshake(false); } /** @@ -534,7 +622,7 @@ public class OpenSSLSocketImpl */ public SSLSession getSession() { try { - startHandshake(); + startHandshake(true); } catch (IOException e) { // return an invalid session with // invalid cipher suite of "SSL_NULL_WITH_NULL_NULL" @@ -616,7 +704,7 @@ public class OpenSSLSocketImpl * @return an array of cipher suite names */ public String[] getEnabledCipherSuites() { - return NativeCrypto.SSL_get_ciphers(sslNativePointer); + return enabledCipherSuites.clone(); } /** @@ -630,7 +718,7 @@ public class OpenSSLSocketImpl * is null. */ public void setEnabledCipherSuites(String[] suites) { - NativeCrypto.setEnabledCipherSuites(sslNativePointer, suites); + enabledCipherSuites = NativeCrypto.checkEnabledCipherSuites(suites); } /** @@ -650,7 +738,7 @@ public class OpenSSLSocketImpl */ @Override public String[] getEnabledProtocols() { - return NativeCrypto.getEnabledProtocols(sslNativePointer); + return enabledProtocols.clone(); } /** @@ -664,7 +752,7 @@ public class OpenSSLSocketImpl */ @Override public synchronized void setEnabledProtocols(String[] protocols) { - NativeCrypto.setEnabledProtocols(sslNativePointer, protocols); + enabledProtocols = NativeCrypto.checkEnabledProtocols(protocols); } /** @@ -793,6 +881,7 @@ public class OpenSSLSocketImpl synchronized (handshakeLock) { if (!handshakeStarted) { + // prevent further attemps to start handshake handshakeStarted = true; synchronized (this) { @@ -862,75 +951,24 @@ public class OpenSSLSocketImpl } protected void finalize() throws IOException { - updateInstanceCount(-1); - - if (sslNativePointer == 0) { - /* - * It's already been closed, so there's no need to do anything - * more at this point. - */ - return; - } - - // Note the underlying socket up-front, for possible later use. - Socket underlyingSocket = socket; - - // Fire up a thread to (hopefully) do all the real work. - Finalizer f = new Finalizer(); - f.setDaemon(true); - f.start(); - /* - * Give the finalizer thread one second to run. If it fails to - * terminate in that time, interrupt it (which may help if it - * is blocked on an interruptible I/O operation), make a note - * in the log, and go ahead and close the underlying socket if - * possible. + * Just worry about our own state. Notably we do not try and + * close anything. The SocketImpl, either our own + * PlainSocketImpl, or the Socket we are wrapping, will do + * that. This might mean we do not properly SSL_shutdown, but + * if you want to do that, properly close the socket yourself. + * + * The reason why we don't try to SSL_shutdown, is that there + * can be a race between finalizers where the PlainSocketImpl + * finalizer runs first and closes the socket. However, in the + * meanwhile, the underlying file descriptor could be reused + * for another purpose. If we call SSL_shutdown, the + * underlying socket BIOs still have the old file descriptor + * and will write the close notify to some unsuspecting + * reader. */ - try { - f.join(1000); - } catch (InterruptedException ex) { - // Reassert interrupted status. - Thread.currentThread().interrupt(); - } - - if (f.isAlive()) { - f.interrupt(); - Logger.global.log(Level.SEVERE, - "Slow finalization of SSL socket (" + this + ", for " + - underlyingSocket + ")"); - if ((underlyingSocket != null) && !underlyingSocket.isClosed()) { - underlyingSocket.close(); - } - } - } - - /** - * Helper class for a thread that knows how to call - * {@link OpenSSLSocketImpl#close} on behalf of instances being finalized, - * since that call can take arbitrarily long (e.g., due to a slow network), - * and an overly long-running finalizer will cause the process to be - * totally aborted. - */ - private class Finalizer extends Thread { - public void run() { - Socket underlyingSocket = socket; // for error reporting - try { - close(); - } catch (IOException ex) { - /* - * Clear interrupted status, so that the Logger call - * immediately below won't get spuriously interrupted. - */ - Thread.interrupted(); - - Logger.global.log(Level.SEVERE, - "Trouble finalizing SSL socket (" + - OpenSSLSocketImpl.this + ", for " + underlyingSocket + - ")", - ex); - } - } + updateInstanceCount(-1); + free(); } /** diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLParameters.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLParameters.java index 525756d..9c6f0a0 100644 --- a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLParameters.java +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLParameters.java @@ -17,22 +17,27 @@ package org.apache.harmony.xnet.provider.jsse; +import java.security.InvalidAlgorithmParameterException; import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.UnrecoverableKeyException; -import java.security.InvalidAlgorithmParameterException; import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLException; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509KeyManager; import javax.net.ssl.X509TrustManager; +import org.apache.harmony.security.provider.cert.X509CertImpl; + /** * The instances of this class incapsulate all the info * about enabled cipher suites and protocols, @@ -109,9 +114,9 @@ public class SSLParameters implements Cloneable { SSLServerSessionCache serverCache) throws KeyManagementException { this.serverSessionContext - = new ServerSessionContext(this, NativeCrypto.SSL_CTX_new(), serverCache); + = new ServerSessionContext(NativeCrypto.SSL_CTX_new(), serverCache); this.clientSessionContext - = new ClientSessionContext(this, NativeCrypto.SSL_CTX_new(), clientCache); + = new ClientSessionContext(NativeCrypto.SSL_CTX_new(), clientCache); // END android-changed try { // initialize key manager diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSessionImpl.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSessionImpl.java index cd74d57..922de2b 100644 --- a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSessionImpl.java +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/SSLSessionImpl.java @@ -177,6 +177,7 @@ public class SSLSessionImpl implements SSLSession, Cloneable { this.cipherSuite = CipherSuite.TLS_NULL_WITH_NULL_NULL; id = new byte[0]; isServer = false; + isValid = false; } else { this.cipherSuite = cipher_suite; id = new byte[32]; @@ -275,7 +276,7 @@ public class SSLSessionImpl implements SSLSession, Cloneable { } public String getProtocol() { - return protocol.name; + return (protocol == null) ? "NONE" : protocol.name; } public SSLSessionContext getSessionContext() { @@ -307,6 +308,7 @@ public class SSLSessionImpl implements SSLSession, Cloneable { public void invalidate() { isValid = false; + context = null; } public boolean isValid() { diff --git a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerSessionContext.java b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerSessionContext.java index c379fee..160188d 100644 --- a/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerSessionContext.java +++ b/x-net/src/main/java/org/apache/harmony/xnet/provider/jsse/ServerSessionContext.java @@ -45,11 +45,18 @@ public class ServerSessionContext extends AbstractSessionContext { private final SSLServerSessionCache persistentCache; - public ServerSessionContext(SSLParameters parameters, - int sslCtxNativePointer, - SSLServerSessionCache persistentCache) { - super(parameters, sslCtxNativePointer, 100, 0); + public ServerSessionContext(int sslCtxNativePointer, + SSLServerSessionCache persistentCache) { + super(sslCtxNativePointer, 100, 0); this.persistentCache = persistentCache; + + // TODO make sure SSL_CTX does not automaticaly clear sessions we want it to cache + // SSL_CTX_set_session_cache_mode(sslCtxNativePointer, SSL_SESS_CACHE_NO_AUTO_CLEAR); + + // TODO remove SSL_CTX session cache limit so we can manage it + // SSL_CTX_sess_set_cache_size(sslCtxNativePointer, 0); + + // TODO override trimToSize to use SSL_CTX_sessions to remove from native cache } Iterator<SSLSession> sessionIterator() { diff --git a/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp b/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp index 8af33aa..86eaadf 100644 --- a/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp +++ b/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp @@ -36,14 +36,16 @@ #include <openssl/rsa.h> #include <openssl/ssl.h> -/** - * Structure to hold JNI state for openssl callback - */ -struct jsse_ssl_app_data_t { - JNIEnv* env; - jobject object; -}; - +#undef WITH_JNI_TRACE +#ifdef WITH_JNI_TRACE +#define JNI_TRACE(...) \ + ((void)LOG(LOG_INFO, LOG_TAG "-jni", __VA_ARGS__)); \ + ((void)printf("I/" LOG_TAG "-jni:")); \ + ((void)printf(__VA_ARGS__)); \ + ((void)printf("\n")) +#else +#define JNI_TRACE(...) ((void)0) +#endif /** * Frees the SSL error state. * @@ -90,16 +92,16 @@ static void throwSocketTimeoutException(JNIEnv* env, const char* message) { } /** - * Throws a java.io.IOException with the given string as a message. + * Throws a javax.net.ssl.SSLException with the given string as a message. */ -static void throwIOExceptionStr(JNIEnv* env, const char* message) { - if (jniThrowException(env, "java/io/IOException", message)) { +static void throwSSLExceptionStr(JNIEnv* env, const char* message) { + if (jniThrowException(env, "javax/net/ssl/SSLException", message)) { LOGE("Unable to throw"); } } /** - * Throws an IOException with a message constructed from the current + * Throws an SSLException with a message constructed from the current * SSL errors. This will also log the errors. * * @param env the JNI environment @@ -107,7 +109,7 @@ static void throwIOExceptionStr(JNIEnv* env, const char* message) { * @param sslErrorCode error code returned from SSL_get_error() * @param message null-ok; general error message */ -static void throwIOExceptionWithSslErrors(JNIEnv* env, int sslReturnCode, +static void throwSSLExceptionWithSslErrors(JNIEnv* env, int sslReturnCode, int sslErrorCode, const char* message) { const char* messageStr = NULL; char* str; @@ -148,8 +150,9 @@ static void throwIOExceptionWithSslErrors(JNIEnv* env, int sslReturnCode, // Prepend either our explicit message or a default one. if (asprintf(&str, "%s: %s", - (message != NULL) ? message : "SSL error", messageStr) == 0) { - throwIOExceptionStr(env, messageStr); + (message != NULL) ? message : "SSL error", messageStr) <= 0) { + // problem with asprintf + throwSSLExceptionStr(env, messageStr); LOGV("%s", messageStr); freeSslErrorState(); return; @@ -203,7 +206,7 @@ static void throwIOExceptionWithSslErrors(JNIEnv* env, int sslReturnCode, } } - throwIOExceptionStr(env, allocStr); + throwSSLExceptionStr(env, allocStr); LOGV("%s", allocStr); free(allocStr); @@ -214,7 +217,7 @@ static void throwIOExceptionWithSslErrors(JNIEnv* env, int sslReturnCode, * Helper function that grabs the casts an ssl pointer and then checks for nullness. * If this function returns NULL and <code>throwIfNull</code> is * passed as <code>true</code>, then this function will call - * <code>throwIOExceptionStr</code> before returning, so in this case of + * <code>throwSSLExceptionStr</code> before returning, so in this case of * NULL, a caller of this function should simply return and allow JNI * to do its thing. * @@ -226,7 +229,7 @@ static void throwIOExceptionWithSslErrors(JNIEnv* env, int sslReturnCode, static SSL* getSslPointer(JNIEnv* env, int ssl_address, bool throwIfNull) { SSL* ssl = reinterpret_cast<SSL*>(static_cast<uintptr_t>(ssl_address)); if ((ssl == NULL) && throwIfNull) { - throwIOExceptionStr(env, "null SSL pointer"); + throwSSLExceptionStr(env, "null SSL pointer"); } return ssl; @@ -640,14 +643,479 @@ static const char* get_content_type(int content_type) { */ static void ssl_msg_callback_LOG(int write_p, int ssl_version, int content_type, const void *buf, size_t len, SSL* ssl, void* arg) { - LOGD("SSL %p %s %s %s %p %d %p", - ssl, - (write_p) ? "send" : "recv", - get_ssl_version(ssl_version), - get_content_type(content_type), - buf, - len, - arg); + JNI_TRACE("ssl=%p SSL msg %s %s %s %p %d %p", + ssl, + (write_p) ? "send" : "recv", + get_ssl_version(ssl_version), + get_content_type(content_type), + buf, + len, + arg); +} + +/** + * Based on example logging call back from SSL_CTX_set_info_callback man page + */ +static void info_callback_LOG(const SSL *s, int where, int ret) +{ + int w = where & ~SSL_ST_MASK; + const char* str; + if (w & SSL_ST_CONNECT) { + str = "SSL_connect"; + } else if (w & SSL_ST_ACCEPT) { + str = "SSL_accept"; + } else { + str = "undefined"; + } + + if (where & SSL_CB_LOOP) { + JNI_TRACE("ssl=%p %s:%s %s", s, str, SSL_state_string(s), SSL_state_string_long(s)); + } else if (where & SSL_CB_ALERT) { + str = (where & SSL_CB_READ) ? "read" : "write"; + JNI_TRACE("ssl=%p SSL3 alert %s:%s:%s %s %s", + s, + str, + SSL_alert_type_string(ret), + SSL_alert_desc_string(ret), + SSL_alert_type_string_long(ret), + SSL_alert_desc_string_long(ret)); + } else if (where & SSL_CB_EXIT) { + if (ret == 0) { + JNI_TRACE("ssl=%p %s:failed exit in %s %s", + s, str, SSL_state_string(s), SSL_state_string_long(s)); + } else if (ret < 0) { + JNI_TRACE("ssl=%p %s:error exit in %s %s", + s, str, SSL_state_string(s), SSL_state_string_long(s)); + } else if (ret == 1) { + JNI_TRACE("ssl=%p %s:ok exit in %s %s", + s, str, SSL_state_string(s), SSL_state_string_long(s)); + } else { + JNI_TRACE("ssl=%p %s:unknown exit %d in %s %s", + s, str, ret, SSL_state_string(s), SSL_state_string_long(s)); + } + } else if (where & SSL_CB_HANDSHAKE_START) { + JNI_TRACE("ssl=%p handshake start in %s %s", + s, SSL_state_string(s), SSL_state_string_long(s)); + } else if (where & SSL_CB_HANDSHAKE_DONE) { + JNI_TRACE("ssl=%p handshake done in %s %s", + s, SSL_state_string(s), SSL_state_string_long(s)); + } else { + JNI_TRACE("ssl=%p %s:unknown where %d in %s %s", + s, str, where, SSL_state_string(s), SSL_state_string_long(s)); + } +} + +/** + * Returns an array containing all the X509 certificate's bytes. + */ +static jobjectArray getCertificateBytes(JNIEnv* env, + const STACK_OF(X509) *chain) +{ + if (chain == NULL) { + // Chain can be NULL if the associated cipher doesn't do certs. + return NULL; + } + + int count = sk_X509_num(chain); + if (count <= 0) { + NULL; + } + + jobjectArray joa = env->NewObjectArray(count, env->FindClass("[B"), NULL); + if (joa == NULL) { + return NULL; + } + + BIO* bio = BIO_new(BIO_s_mem()); + + // LOGD("Start fetching the certificates"); + for (int i = 0; i < count; i++) { + X509* cert = sk_X509_value(chain, i); + + BIO_reset(bio); + PEM_write_bio_X509(bio, cert); + + BUF_MEM* bptr; + BIO_get_mem_ptr(bio, &bptr); + jbyteArray bytes = env->NewByteArray(bptr->length); + + if (bytes == NULL) { + /* + * Indicate an error by resetting joa to NULL. It will + * eventually get gc'ed. + */ + joa = NULL; + break; + } + jbyte* src = reinterpret_cast<jbyte*>(bptr->data); + env->SetByteArrayRegion(bytes, 0, bptr->length, src); + env->SetObjectArrayElement(joa, i, bytes); + } + + // LOGD("Certificate fetching complete"); + BIO_free(bio); + return joa; +} + +/** + * Our additional application data needed for getting synchronization right. + * This maybe warrants a bit of lengthy prose: + * + * (1) We use a flag to reflect whether we consider the SSL connection alive. + * Any read or write attempt loops will be cancelled once this flag becomes 0. + * + * (2) We use an int to count the number of threads that are blocked by the + * underlying socket. This may be at most two (one reader and one writer), since + * the Java layer ensures that no more threads will enter the native code at the + * same time. + * + * (3) The pipe is used primarily as a means of cancelling a blocking select() + * when we want to close the connection (aka "emergency button"). It is also + * necessary for dealing with a possible race condition situation: There might + * be cases where both threads see an SSL_ERROR_WANT_READ or + * SSL_ERROR_WANT_WRITE. Both will enter a select() with the proper argument. + * If one leaves the select() successfully before the other enters it, the + * "success" event is already consumed and the second thread will be blocked, + * possibly forever (depending on network conditions). + * + * The idea for solving the problem looks like this: Whenever a thread is + * successful in moving around data on the network, and it knows there is + * another thread stuck in a select(), it will write a byte to the pipe, waking + * up the other thread. A thread that returned from select(), on the other hand, + * knows whether it's been woken up by the pipe. If so, it will consume the + * byte, and the original state of affairs has been restored. + * + * The pipe may seem like a bit of overhead, but it fits in nicely with the + * other file descriptors of the select(), so there's only one condition to wait + * for. + * + * (4) Finally, a mutex is needed to make sure that at most one thread is in + * either SSL_read() or SSL_write() at any given time. This is an OpenSSL + * requirement. We use the same mutex to guard the field for counting the + * waiting threads. + * + * Note: The current implementation assumes that we don't have to deal with + * problems induced by multiple cores or processors and their respective + * memory caches. One possible problem is that of inconsistent views on the + * "aliveAndKicking" field. This could be worked around by also enclosing all + * accesses to that field inside a lock/unlock sequence of our mutex, but + * currently this seems a bit like overkill. Marking volatile at the very least. + * + * During handshaking, three additional fields are used to up-call into + * Java to perform certificate verification and handshake completion. + * + * (5) the JNIEnv so we can invoke the Java callback + * + * (6) a NativeCrypto.CertificateChainVerifier to call with the peer certificate chain + * + * (7) a NativeCrypto.HandshakeCompletedCallback to call back when handshake is done + * + * These fields are cleared by the info_callback the handshake has + * completed. SSL_VERIFY_CLIENT_ONCE is currently used to disable + * renegotiation but if that changes, care would need to be taken to + * maintain an appropriate JNIEnv on any downcall to openssl that + * could result in an upcall to Java. The current code does try to + * cover these cases by conditionally setting the JNIenv on calls that + * can read and write to the SSL such as SSL_do_handshake, SSL_read, + * SSL_write, and SSL_shutdown if handshaking is not complete. + * + */ +class AppData { + public: + volatile int aliveAndKicking; + int waitingThreads; + int fdsEmergency[2]; + MUTEX_TYPE mutex; + JNIEnv* env; + jobject certificateChainVerifier; + jobject handshakeCompletedCallback; + + /** + * Creates our application data and attaches it to a given SSL connection. + * + * @param ssl The SSL connection to attach the data to. + * @param env The JNIEnv + * @param ccv The CertificateChainVerifier + * @param hcc The HandshakeCompletedCallback + */ + public: + static AppData* create(JNIEnv* e, jobject ccv, jobject hcc) { + AppData* appData = new AppData(e, ccv, hcc); + appData->fdsEmergency[0] = -1; + appData->fdsEmergency[1] = -1; + if (pipe(appData->fdsEmergency) == -1) { + return NULL; + } + if (MUTEX_SETUP(appData->mutex) == -1) { + return NULL; + } + return appData; + } + + private: + AppData(JNIEnv* e, jobject ccv, jobject hcc) : + aliveAndKicking(1), + waitingThreads(0), + env(e), + certificateChainVerifier(ccv), + handshakeCompletedCallback(hcc) {} + + /** + * Destroys our application data, cleaning up everything in the process. + */ + public: + ~AppData() { + aliveAndKicking = 0; + if (fdsEmergency[0] != -1) { + close(fdsEmergency[0]); + } + if (fdsEmergency[1] != -1) { + close(fdsEmergency[1]); + } + MUTEX_CLEANUP(mutex); + } + + void setEnv(JNIEnv* e) { + if (handshakeCompletedCallback == NULL) { + return; + } + env = e; + } + void clearEnv() { + env = NULL; + } +}; + +/** + * Dark magic helper function that checks, for a given SSL session, whether it + * can SSL_read() or SSL_write() without blocking. Takes into account any + * concurrent attempts to close the SSL session from the Java side. This is + * needed to get rid of the hangs that occur when thread #1 closes the SSLSocket + * while thread #2 is sitting in a blocking read or write. The type argument + * specifies whether we are waiting for readability or writability. It expects + * to be passed either SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE, since we + * only need to wait in case one of these problems occurs. + * + * @param type Either SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE + * @param fd The file descriptor to wait for (the underlying socket) + * @param data The application data structure with mutex info etc. + * @param timeout The timeout value for select call, with the special value + * 0 meaning no timeout at all (wait indefinitely). Note: This is + * the Java semantics of the timeout value, not the usual + * select() semantics. + * @return The result of the inner select() call, -1 on additional errors + */ +static int sslSelect(int type, int fd, AppData* appData, int timeout) { + fd_set rfds; + fd_set wfds; + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + + if (type == SSL_ERROR_WANT_READ) { + FD_SET(fd, &rfds); + } else { + FD_SET(fd, &wfds); + } + + FD_SET(appData->fdsEmergency[0], &rfds); + + int max = fd > appData->fdsEmergency[0] ? fd : appData->fdsEmergency[0]; + + // Build a struct for the timeout data if we actually want a timeout. + struct timeval tv; + struct timeval *ptv; + if (timeout > 0) { + tv.tv_sec = timeout / 1000; + tv.tv_usec = 0; + ptv = &tv; + } else { + ptv = NULL; + } + + // LOGD("Doing select() for SSL_ERROR_WANT_%s...", type == SSL_ERROR_WANT_READ ? "READ" : "WRITE"); + int result = select(max + 1, &rfds, &wfds, NULL, ptv); + // LOGD("Returned from select(), result is %d", result); + + // Lock + if (MUTEX_LOCK(appData->mutex) == -1) { + return -1; + } + + // If we have been woken up by the emergency pipe, there must be a token in + // it. Thus we can safely read it (even in a blocking way). + if (FD_ISSET(appData->fdsEmergency[0], &rfds)) { + char token; + do { + read(appData->fdsEmergency[0], &token, 1); + } while (errno == EINTR); + } + + // Tell the world that there is now one thread less waiting for the + // underlying network. + appData->waitingThreads--; + + // Unlock + MUTEX_UNLOCK(appData->mutex); + // LOGD("leave sslSelect"); + return result; +} + +/** + * Helper function that wakes up a thread blocked in select(), in case there is + * one. Is being called by sslRead() and sslWrite() as well as by JNI glue + * before closing the connection. + * + * @param data The application data structure with mutex info etc. + */ +static void sslNotify(AppData* appData) { + // Write a byte to the emergency pipe, so a concurrent select() can return. + // Note we have to restore the errno of the original system call, since the + // caller relies on it for generating error messages. + int errnoBackup = errno; + char token = '*'; + do { + errno = 0; + write(appData->fdsEmergency[1], &token, 1); + } while (errno == EINTR); + errno = errnoBackup; +} + +// From private header file external/openssl/ssl_locl.h +#define SSL_aRSA 0x00000001L +#define SSL_aDSS 0x00000002L +#define SSL_aNULL 0x00000004L +#define SSL_aDH 0x00000008L +#define SSL_aECDH 0x00000010L +#define SSL_aKRB5 0x00000020L +#define SSL_aECDSA 0x00000040L +#define SSL_aPSK 0x00000080L + +/** + * Converts an SSL_CIPHER's algorithms field to a TrustManager auth argument + */ +static const char* SSL_CIPHER_authentication_method(const SSL_CIPHER* cipher) +{ + unsigned long alg_auth = cipher->algorithm_auth; + + const char *au; + switch (alg_auth) { + case SSL_aRSA: + au="RSA"; + break; + case SSL_aDSS: + au="DSS"; + break; + case SSL_aDH: + au="DH"; + break; + case SSL_aKRB5: + au="KRB5"; + break; + case SSL_aECDH: + au = "ECDH"; + break; + case SSL_aNULL: + au="None"; + break; + case SSL_aECDSA: + au="ECDSA"; + break; + case SSL_aPSK: + au="PSK"; + break; + default: + au="unknown"; + break; + } + return au; +} + +/** + * Verify the X509 certificate via SSL_CTX_set_cert_verify_callback + */ +static int cert_verify_callback(X509_STORE_CTX* x509_store_ctx, void* arg) +{ + /* Get the correct index to the SSLobject stored into X509_STORE_CTX. */ + SSL* ssl = (SSL*)X509_STORE_CTX_get_ex_data(x509_store_ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); + JNI_TRACE("ssl=%p cert_verify_callback x509_store_ctx=%p arg=%p", ssl, x509_store_ctx, arg); + + AppData* appData = (AppData*) SSL_get_app_data(ssl); + JNIEnv* env = appData->env; + if (env == NULL) { + LOGE("AppData->env missing in cert_verify_callback"); + JNI_TRACE("ssl=%p cert_verify_callback => 0", ssl, result); + return 0; + } + jobject certificateChainVerifier = appData->certificateChainVerifier; + + jclass cls = env->GetObjectClass(certificateChainVerifier); + jmethodID methodID = env->GetMethodID(cls, "verifyCertificateChain", "([[BLjava/lang/String;)V"); + + jobjectArray objectArray = getCertificateBytes(env, x509_store_ctx->untrusted); + + const char* authMethod; + switch (ssl->version) { + case SSL2_VERSION: + authMethod = "RSA"; + break; + case SSL3_VERSION: + case TLS1_VERSION: + case DTLS1_VERSION: + authMethod = SSL_CIPHER_authentication_method(ssl->s3->tmp.new_cipher); + break; + default: + authMethod = "unknown"; + break; + } + jstring authMethodString = env->NewStringUTF(authMethod); + + env->CallVoidMethod(certificateChainVerifier, methodID, objectArray, authMethodString); + + int result = (env->ExceptionCheck()) ? 0 : 1; + JNI_TRACE("ssl=%p cert_verify_callback => %d", ssl, result); + return result; +} + +/** + * Call back to watch for handshake to be completed. This is necessary + * for SSL_MODE_HANDSHAKE_CUTTHROUGH support, since SSL_do_handshake + * returns before the handshake is completed in this case. + */ +static void info_callback(const SSL *ssl, int where, int ret) { + JNI_TRACE("ssl=%p info_callback where=0x%x ret=%d", ssl, where, ret); +#ifdef WITH_JNI_TRACE + info_callback_LOG(ssl, where, ret); +#endif + if (!(where & SSL_CB_HANDSHAKE_DONE)) { + JNI_TRACE("ssl=%p info_callback ignored", ssl); + return; + } + + AppData* appData = (AppData*) SSL_get_app_data(ssl); + JNIEnv* env = appData->env; + if (env == NULL) { + LOGE("AppData->env missing in info_callback"); + JNI_TRACE("ssl=%p info_callback env error", ssl, result); + return; + } + jobject handshakeCompletedCallback = appData->handshakeCompletedCallback; + + jclass cls = env->GetObjectClass(handshakeCompletedCallback); + jmethodID methodID = env->GetMethodID(cls, "handshakeCompleted", "()V"); + + JNI_TRACE("ssl=%p info_callback calling handshakeCompleted", ssl); + env->CallVoidMethod(handshakeCompletedCallback, methodID); + + if (env->ExceptionCheck()) { + JNI_TRACE("ssl=%p info_callback exception", ssl); + } + + // no longer needed after handshake is complete + appData->env = NULL; + appData->certificateChainVerifier = NULL; + appData->handshakeCompletedCallback = NULL; + JNI_TRACE("ssl=%p info_callback completed", ssl); } /* @@ -679,7 +1147,13 @@ static int NativeCrypto_SSL_CTX_new(JNIEnv* env, jclass clazz) { #endif SSL_CTX_set_mode(sslCtx, mode); - // SSL_CTX_set_msg_callback(sslCtx, ssl_msg_callback_LOG); /* enable for handshake debug */ + SSL_CTX_set_cert_verify_callback(sslCtx, cert_verify_callback, NULL); + SSL_CTX_set_info_callback(sslCtx, info_callback); + +#ifdef WITH_JNI_TRACE + SSL_CTX_set_msg_callback(sslCtx, ssl_msg_callback_LOG); /* enable for message debug */ +#endif + JNI_TRACE("NativeCrypto_SSL_CTX_new => %p", sslCtx); return (jint) sslCtx; } @@ -698,6 +1172,7 @@ static jobjectArray makeCipherList(JNIEnv* env, STACK_OF(SSL_CIPHER)* cipher_lis // Fill in the cipher names. for (int i = 0; i < cipherCount; ++i) { const char* c = sk_SSL_CIPHER_value(cipher_list, i)->name; + JNI_TRACE("makeCipherList[i=%d]=%s", i, c); env->SetObjectArrayElement(array, i, env->NewStringUTF(c)); } return array; @@ -711,6 +1186,7 @@ static jobjectArray NativeCrypto_SSL_CTX_get_ciphers(JNIEnv* env, jclass, jint ssl_ctx_address) { SSL_CTX* ssl_ctx = reinterpret_cast<SSL_CTX*>(static_cast<uintptr_t>(ssl_ctx_address)); + JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_CTX_get_ciphers", ssl_ctx); if (ssl_ctx == NULL) { jniThrowNullPointerException(env, "SSL_CTX is null"); return NULL; @@ -725,10 +1201,12 @@ static void NativeCrypto_SSL_CTX_free(JNIEnv* env, jclass, jint ssl_ctx_address) { SSL_CTX* ssl_ctx = reinterpret_cast<SSL_CTX*>(static_cast<uintptr_t>(ssl_ctx_address)); + JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_CTX_free", ssl_ctx); if (ssl_ctx == NULL) { jniThrowNullPointerException(env, "SSL_CTX is null"); return; } + env->DeleteGlobalRef((jobject) ssl_ctx->app_verify_arg); SSL_CTX_free(ssl_ctx); } @@ -736,7 +1214,7 @@ static void NativeCrypto_SSL_CTX_free(JNIEnv* env, * Gets the chars of a String object as a '\0'-terminated UTF-8 string, * stored in a freshly-allocated BIO memory buffer. */ -static BIO *stringToMemBuf(JNIEnv* env, jstring string) { +static BIO* stringToMemBuf(JNIEnv* env, jstring string) { jsize byteCount = env->GetStringUTFLength(string); LocalArray<1024> buf(byteCount + 1); env->GetStringUTFRegion(string, 0, env->GetStringLength(string), &buf[0]); @@ -747,15 +1225,19 @@ static BIO *stringToMemBuf(JNIEnv* env, jstring string) { } /** - * public static native int SSL_new(int ssl_ctx, String privatekey, String certificate, byte[] seed) throws IOException; + * public static native int SSL_new(int ssl_ctx, String privatekey, String certificate, byte[] seed, + * CertificateChainVerifier ccv) throws SSLException; */ static jint NativeCrypto_SSL_new(JNIEnv* env, jclass, - jint ssl_ctx_address, jstring privatekey, jstring certificates, jbyteArray seed) + jint ssl_ctx_address, jstring privatekey, jstring certificates, jbyteArray seed, jobject ccv) { SSL_CTX* ssl_ctx = reinterpret_cast<SSL_CTX*>(static_cast<uintptr_t>(ssl_ctx_address)); + JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_new privatekey=%p certificates=%p seed=%p ccv=%p", + ssl_ctx, privatekey, certificates, seed, ccv); if (ssl_ctx == NULL) { jniThrowNullPointerException(env, "SSL_CTX is null"); - return 0; + JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_new => NULL", ssl_ctx); + return NULL; } // 'seed == null' when no SecureRandom Object is set @@ -770,8 +1252,9 @@ static jint NativeCrypto_SSL_new(JNIEnv* env, jclass, SSL* ssl = SSL_new(ssl_ctx); if (ssl == NULL) { - throwIOExceptionWithSslErrors(env, 0, 0, + throwSSLExceptionWithSslErrors(env, 0, 0, "Unable to create SSL structure"); + JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_new => NULL", ssl_ctx); return NULL; } @@ -793,9 +1276,10 @@ static jint NativeCrypto_SSL_new(JNIEnv* env, jclass, if (privatekeyevp == NULL) { LOGE(ERR_error_string(ERR_get_error(), NULL)); - throwIOExceptionWithSslErrors(env, 0, 0, + throwSSLExceptionWithSslErrors(env, 0, 0, "Error parsing the private key"); SSL_free(ssl); + JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_new => NULL", ssl_ctx); return NULL; } @@ -806,58 +1290,113 @@ static jint NativeCrypto_SSL_new(JNIEnv* env, jclass, if (certificatesx509 == NULL) { LOGE(ERR_error_string(ERR_get_error(), NULL)); - throwIOExceptionWithSslErrors(env, 0, 0, + throwSSLExceptionWithSslErrors(env, 0, 0, "Error parsing the certificates"); EVP_PKEY_free(privatekeyevp); SSL_free(ssl); + JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_new => NULL", ssl_ctx); return NULL; } int ret = SSL_use_certificate(ssl, certificatesx509); if (ret != 1) { LOGE(ERR_error_string(ERR_get_error(), NULL)); - throwIOExceptionWithSslErrors(env, ret, 0, + throwSSLExceptionWithSslErrors(env, ret, 0, "Error setting the certificates"); X509_free(certificatesx509); EVP_PKEY_free(privatekeyevp); SSL_free(ssl); + JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_new => NULL", ssl_ctx); return NULL; } ret = SSL_use_PrivateKey(ssl, privatekeyevp); if (ret != 1) { LOGE(ERR_error_string(ERR_get_error(), NULL)); - throwIOExceptionWithSslErrors(env, ret, 0, + throwSSLExceptionWithSslErrors(env, ret, 0, "Error setting the private key"); X509_free(certificatesx509); EVP_PKEY_free(privatekeyevp); SSL_free(ssl); + JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_new => NULL", ssl_ctx); return NULL; } ret = SSL_check_private_key(ssl); if (ret != 1) { - throwIOExceptionWithSslErrors(env, ret, 0, + throwSSLExceptionWithSslErrors(env, ret, 0, "Error checking the private key"); X509_free(certificatesx509); EVP_PKEY_free(privatekeyevp); SSL_free(ssl); + JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_new => NULL", ssl_ctx); return NULL; } } + JNI_TRACE("ssl_ctx=%p NativeCrypto_SSL_new => ssl=%p", ssl_ctx, ssl); return (jint)ssl; } /** + * public static native long SSL_get_mode(int ssl); + */ +static jlong NativeCrypto_SSL_get_mode(JNIEnv* env, jclass, + jint ssl_address) { + SSL* ssl = getSslPointer(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_get_mode", ssl); + if (ssl == NULL) { + JNI_TRACE("ssl=%p NativeCrypto_SSL_get_mode => 0", ssl); + return 0; + } + long mode = SSL_get_mode(ssl); + JNI_TRACE("ssl=%p NativeCrypto_SSL_get_mode => 0x%lx", ssl, mode); + return mode; +} + +/** + * public static native long SSL_set_mode(int ssl, long mode); + */ +static jlong NativeCrypto_SSL_set_mode(JNIEnv* env, jclass, + jint ssl_address, jlong mode) { + SSL* ssl = getSslPointer(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_mode mode=0x%llx", ssl, mode); + if (ssl == NULL) { + return 0; + } + long result = SSL_set_mode(ssl, mode); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_mode => 0x%lx", ssl, result); + return result; +} + +/** + * public static native long SSL_clear_mode(int ssl, long mode); + */ +static jlong NativeCrypto_SSL_clear_mode(JNIEnv* env, jclass, + jint ssl_address, jlong mode) { + SSL* ssl = getSslPointer(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_clear_mode mode=0x%llx", ssl, mode); + if (ssl == NULL) { + return 0; + } + long result = SSL_clear_mode(ssl, mode); + JNI_TRACE("ssl=%p NativeCrypto_SSL_clear_mode => 0x%lx", ssl, result); + return result; +} + +/** * public static native long SSL_get_options(int ssl); */ static jlong NativeCrypto_SSL_get_options(JNIEnv* env, jclass, jint ssl_address) { SSL* ssl = getSslPointer(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_get_options", ssl); if (ssl == NULL) { + JNI_TRACE("ssl=%p NativeCrypto_SSL_get_options => 0", ssl); return 0; } - return SSL_get_options(ssl); + long options = SSL_get_options(ssl); + JNI_TRACE("ssl=%p NativeCrypto_SSL_get_options => 0x%lx", ssl, options); + return options; } /** @@ -866,10 +1405,28 @@ static jlong NativeCrypto_SSL_get_options(JNIEnv* env, jclass, static jlong NativeCrypto_SSL_set_options(JNIEnv* env, jclass, jint ssl_address, jlong options) { SSL* ssl = getSslPointer(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_options options=0x%llx", ssl, options); + if (ssl == NULL) { + return 0; + } + long result = SSL_set_options(ssl, options); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_options => 0x%lx", ssl, result); + return result; +} + +/** + * public static native long SSL_clear_options(int ssl, long options); + */ +static jlong NativeCrypto_SSL_clear_options(JNIEnv* env, jclass, + jint ssl_address, jlong options) { + SSL* ssl = getSslPointer(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_clear_options options=0x%llx", ssl, options); if (ssl == NULL) { - return 0 ; + return 0; } - return SSL_set_options(ssl, options); + long result = SSL_clear_options(ssl, options); + JNI_TRACE("ssl=%p NativeCrypto_SSL_clear_options => 0x%lx", ssl, result); + return result; } /** @@ -880,6 +1437,7 @@ static jobjectArray NativeCrypto_SSL_get_ciphers(JNIEnv* env, jclass, jint ssl_address) { SSL* ssl = getSslPointer(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_get_ciphers", ssl); if (ssl == NULL) { return NULL; } @@ -893,10 +1451,12 @@ static void NativeCrypto_SSL_set_cipher_list(JNIEnv* env, jclass, jint ssl_address, jstring controlString) { SSL* ssl = getSslPointer(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_cipher_list controlString=%p", ssl, controlString); if (ssl == NULL) { return; } const char* str = env->GetStringUTFChars(controlString, NULL); + JNI_TRACE("ssl=%p NativeCrypto_SSL_controlString str=%s", ssl, str); int rc = SSL_set_cipher_list(ssl, str); env->ReleaseStringUTFChars(controlString, str); if (rc == 0) { @@ -907,124 +1467,290 @@ static void NativeCrypto_SSL_set_cipher_list(JNIEnv* env, jclass, } /** - * Our additional application data needed for getting synchronization right. - * This maybe warrants a bit of lengthy prose: - * - * (1) We use a flag to reflect whether we consider the SSL connection alive. - * Any read or write attempt loops will be cancelled once this flag becomes 0. - * - * (2) We use an int to count the number of threads that are blocked by the - * underlying socket. This may be at most two (one reader and one writer), since - * the Java layer ensures that no more threads will enter the native code at the - * same time. - * - * (3) The pipe is used primarily as a means of cancelling a blocking select() - * when we want to close the connection (aka "emergency button"). It is also - * necessary for dealing with a possible race condition situation: There might - * be cases where both threads see an SSL_ERROR_WANT_READ or - * SSL_ERROR_WANT_WRITE. Both will enter a select() with the proper argument. - * If one leaves the select() successfully before the other enters it, the - * "success" event is already consumed and the second thread will be blocked, - * possibly forever (depending on network conditions). - * - * The idea for solving the problem looks like this: Whenever a thread is - * successful in moving around data on the network, and it knows there is - * another thread stuck in a select(), it will write a byte to the pipe, waking - * up the other thread. A thread that returned from select(), on the other hand, - * knows whether it's been woken up by the pipe. If so, it will consume the - * byte, and the original state of affairs has been restored. - * - * The pipe may seem like a bit of overhead, but it fits in nicely with the - * other file descriptors of the select(), so there's only one condition to wait - * for. - * - * (4) Finally, a mutex is needed to make sure that at most one thread is in - * either SSL_read() or SSL_write() at any given time. This is an OpenSSL - * requirement. We use the same mutex to guard the field for counting the - * waiting threads. - * - * Note: The current implementation assumes that we don't have to deal with - * problems induced by multiple cores or processors and their respective - * memory caches. One possible problem is that of inconsistent views on the - * "aliveAndKicking" field. This could be worked around by also enclosing all - * accesses to that field inside a lock/unlock sequence of our mutex, but - * currently this seems a bit like overkill. + * Sets certificate expectations, especially for server to request client auth */ -typedef struct app_data { - volatile int aliveAndKicking; - int waitingThreads; - int fdsEmergency[2]; - MUTEX_TYPE mutex; -} APP_DATA; +static void NativeCrypto_SSL_set_verify(JNIEnv* env, + jclass, jint ssl_address, jint mode) +{ + SSL* ssl = getSslPointer(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_verify", ssl); + if (ssl == NULL) { + return; + } + SSL_set_verify(ssl, (int)mode, NULL); +} /** - * Creates our application data and attaches it to a given SSL connection. - * - * @param ssl The SSL connection to attach the data to. - * @return 0 on success, -1 on failure. + * Sets the ciphers suites that are enabled in the SSL */ -static int sslCreateAppData(SSL* ssl) { - APP_DATA* data = (APP_DATA*) malloc(sizeof(APP_DATA)); +static void NativeCrypto_SSL_set_session(JNIEnv* env, jclass, + jint ssl_address, jint ssl_session_address) +{ + SSL* ssl = getSslPointer(env, ssl_address, true); + SSL_SESSION* ssl_session = reinterpret_cast<SSL_SESSION*>(static_cast<uintptr_t>(ssl_session_address)); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_session ssl_session=%p", ssl, ssl_session); + if (ssl == NULL) { + return; + } - memset(data, 0, sizeof(APP_DATA)); + int ret = SSL_set_session(ssl, ssl_session); + if (ret != 1) { + /* + * Translate the error, and throw if it turns out to be a real + * problem. + */ + int sslErrorCode = SSL_get_error(ssl, ret); + if (sslErrorCode != SSL_ERROR_ZERO_RETURN) { + throwSSLExceptionWithSslErrors(env, ret, sslErrorCode, + "SSL session set"); + SSL_clear(ssl); + } + } +} - data->aliveAndKicking = 1; - data->waitingThreads = 0; - data->fdsEmergency[0] = -1; - data->fdsEmergency[1] = -1; +/** + * Sets the ciphers suites that are enabled in the SSL + */ +static void NativeCrypto_SSL_set_session_creation_enabled(JNIEnv* env, jclass, + jint ssl_address, jboolean creation_enabled) +{ + SSL* ssl = getSslPointer(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_session_creation_enabled creation_enabled=%d", ssl, creation_enabled); + if (ssl == NULL) { + return; + } + SSL_set_session_creation_enabled(ssl, creation_enabled); +} - if (pipe(data->fdsEmergency) == -1) { - free(data); - return -1; +/** + * Module scope variables initialized during JNI registration. + */ +static jfieldID field_Socket_mImpl; +static jfieldID field_Socket_mFD; + +/** + * Perform SSL handshake + */ +static jint NativeCrypto_SSL_do_handshake(JNIEnv* env, jclass, + jint ssl_address, jobject socketObject, jobject ccv, jobject hcc, jint timeout, jboolean client_mode) +{ + SSL* ssl = getSslPointer(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake socketObject=%p ccv=%p timeout=%d client_mode=%d", + ssl, socketObject, ccv, timeout, client_mode); + if (ssl == NULL) { + JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake => 0", ssl); + return 0; } - if (MUTEX_SETUP(data->mutex) == -1) { - free(data); - return -1; + if (socketObject == NULL) { + jniThrowNullPointerException(env, "Socket is null"); + JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake => 0", ssl); + return 0; + } + if (ccv == NULL) { + jniThrowNullPointerException(env, "CertificateChainVerifier is null"); + JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake => 0", ssl); + return 0; } - SSL_set_app_data(ssl, (char*) data); + jobject socketImplObject = env->GetObjectField(socketObject, field_Socket_mImpl); + if (socketImplObject == NULL) { + throwSSLExceptionStr(env, + "couldn't get the socket impl from the socket"); + JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake => 0", ssl); + return 0; + } - return 0; -} + jobject fdObject = env->GetObjectField(socketImplObject, field_Socket_mFD); + if (fdObject == NULL) { + throwSSLExceptionStr(env, + "couldn't get the file descriptor from the socket impl"); + JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake => 0", ssl); + return 0; + } -/** - * Destroys our application data, cleaning up everything in the process. - * - * @param ssl The SSL connection to take the data from. - */ -static void sslDestroyAppData(SSL* ssl) { - APP_DATA* data = (APP_DATA*) SSL_get_app_data(ssl); + int fd = jniGetFDFromFileDescriptor(env, fdObject); + if (fd == -1) { + throwSSLExceptionStr(env, "Invalid file descriptor"); + SSL_clear(ssl); + JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake => 0", ssl); + return 0; + } + + int ret = SSL_set_fd(ssl, fd); + JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake s=%d", ssl, fd); - if (data != NULL) { - SSL_set_app_data(ssl, NULL); + if (ret != 1) { + throwSSLExceptionWithSslErrors(env, ret, 0, + "Error setting the file descriptor"); + SSL_clear(ssl); + JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake => 0", ssl); + return 0; + } - data->aliveAndKicking = 0; + /* + * Make socket non-blocking, so SSL_connect SSL_read() and SSL_write() don't hang + * forever and we can use select() to find out if the socket is ready. + */ + int mode = fcntl(fd, F_GETFL); + if (mode == -1 || fcntl(fd, F_SETFL, mode | O_NONBLOCK) == -1) { + throwSSLExceptionStr(env, "Unable to make socket non blocking"); + SSL_clear(ssl); + JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake => 0", ssl); + return 0; + } - if (data->fdsEmergency[0] != -1) { - close(data->fdsEmergency[0]); + /* + * Create our special application data. + */ + AppData* appData = AppData::create(env, ccv, hcc); + if (appData == NULL) { + throwSSLExceptionStr(env, "Unable to create application data"); + SSL_clear(ssl); + JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake => 0", ssl); + return 0; + } + SSL_set_app_data(ssl, (char*) appData); + + if (client_mode) { + SSL_set_connect_state(ssl); + } else { + SSL_set_accept_state(ssl); + } + + while (appData->aliveAndKicking) { + errno = 0; + appData->setEnv(env); + ret = SSL_do_handshake(ssl); + appData->clearEnv(); + // cert_verify_callback threw exception + if (env->ExceptionCheck()) { + SSL_clear(ssl); + JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake => 0", ssl); + return 0; } + if (ret == 1) { + break; + } else if (errno == EINTR) { + continue; + } else { + // LOGD("SSL_connect: result %d, errno %d, timeout %d", ret, errno, timeout); + int error = SSL_get_error(ssl, ret); - if (data->fdsEmergency[1] != -1) { - close(data->fdsEmergency[1]); + /* + * If SSL_connect doesn't succeed due to the socket being + * either unreadable or unwritable, we use sslSelect to + * wait for it to become ready. If that doesn't happen + * before the specified timeout or an error occurs, we + * cancel the handshake. Otherwise we try the SSL_connect + * again. + */ + if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) { + appData->waitingThreads++; + int selectResult = sslSelect(error, fd, appData, timeout); + + if (selectResult == -1) { + throwSSLExceptionWithSslErrors(env, -1, error, + "handshake error"); + SSL_clear(ssl); + JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake => 0", ssl); + return 0; + } else if (selectResult == 0) { + throwSocketTimeoutException(env, "SSL handshake timed out"); + SSL_clear(ssl); + freeSslErrorState(); + JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake => 0", ssl); + return 0; + } + } else { + LOGE("Unknown error %d during handshake", error); + break; + } } + } - MUTEX_CLEANUP(data->mutex); + if (ret == 0) { + /* + * The other side closed the socket before the handshake could be + * completed, but everything is within the bounds of the TLS protocol. + * We still might want to find out the real reason of the failure. + */ + int sslErrorCode = SSL_get_error(ssl, ret); + if (sslErrorCode == SSL_ERROR_NONE || + (sslErrorCode == SSL_ERROR_SYSCALL && errno == 0)) { + throwSSLExceptionStr(env, "Connection closed by peer"); + } else { + throwSSLExceptionWithSslErrors(env, ret, sslErrorCode, + "Trouble accepting connection"); + } + SSL_clear(ssl); + JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake => 0", ssl); + return 0; + } + if (ret < 0) { + /* + * Translate the error and throw exception. We are sure it is an error + * at this point. + */ + int sslErrorCode = SSL_get_error(ssl, ret); + throwSSLExceptionWithSslErrors(env, ret, sslErrorCode, + "Trouble accepting connection"); + SSL_clear(ssl); + JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake => 0", ssl); + return 0; + } + SSL_SESSION* ssl_session = SSL_get1_session(ssl); + JNI_TRACE("ssl=%p NativeCrypto_SSL_do_handshake => ssl_session=%p", ssl, ssl_session); + return (jint) ssl_session; +} - free(data); +/** + * public static native byte[][] SSL_get_certificate(int ssl); + */ +static jobjectArray NativeCrypto_SSL_get_certificate(JNIEnv* env, jclass, jint ssl_address) +{ + SSL* ssl = getSslPointer(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_get_certificate", ssl); + if (ssl == NULL) { + JNI_TRACE("ssl=%p NativeCrypto_SSL_get_certificate => NULL", ssl); + return NULL; } + X509* certificate = SSL_get_certificate(ssl); + if (certificate == NULL) { + JNI_TRACE("ssl=%p NativeCrypto_SSL_get_certificate => NULL", ssl); + return NULL; + } + // TODO convert from single certificate to chain properly. One + // option would be to have the chain remembered where + // SSL_use_certificate is used. Another would be to save the + // intermediate CAs with SSL_CTX SSL_CTX_add_extra_chain_cert. + STACK_OF(X509)* chain = sk_X509_new_null(); + if (chain == NULL) { + jniThrowRuntimeException(env, "Unable to allocate local certificate chain"); + JNI_TRACE("ssl=%p NativeCrypto_SSL_get_certificate => NULL", ssl); + return NULL; + } + sk_X509_push(chain, certificate); + jobjectArray objectArray = getCertificateBytes(env, chain); + sk_X509_free(chain); + JNI_TRACE("ssl=%p NativeCrypto_SSL_get_certificate => %p", ssl, objectArray); + return objectArray; } + /** * public static native void SSL_free(int ssl); */ static void NativeCrypto_SSL_free(JNIEnv* env, jclass, jint ssl_address) { SSL* ssl = getSslPointer(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_free", ssl); if (ssl == NULL) { return; } - sslDestroyAppData(ssl); + AppData* appData = (AppData*) SSL_get_app_data(ssl); + delete appData; + SSL_set_app_data(ssl, NULL); SSL_free(ssl); } @@ -1052,159 +1778,26 @@ static JNINativeMethod sNativeCryptoMethods[] = { { "SSL_CTX_get_ciphers", "(I)[Ljava/lang/String;", (void*)NativeCrypto_SSL_CTX_get_ciphers}, { "SSL_CTX_free", "(I)V", (void*)NativeCrypto_SSL_CTX_free }, { "SSL_new", "(ILjava/lang/String;Ljava/lang/String;[B)I", (void*)NativeCrypto_SSL_new}, + { "SSL_get_mode", "(I)J", (void*)NativeCrypto_SSL_get_mode }, + { "SSL_set_mode", "(IJ)J", (void*)NativeCrypto_SSL_set_mode }, + { "SSL_clear_mode", "(IJ)J", (void*)NativeCrypto_SSL_clear_mode }, { "SSL_get_options", "(I)J", (void*)NativeCrypto_SSL_get_options }, { "SSL_set_options", "(IJ)J", (void*)NativeCrypto_SSL_set_options }, - { "SSL_get_ciphers", "(I)[Ljava/lang/String;", (void*)NativeCrypto_SSL_get_ciphers}, - { "SSL_set_cipher_list", "(ILjava/lang/String;)V", (void*)NativeCrypto_SSL_set_cipher_list}, + { "SSL_clear_options", "(IJ)J", (void*)NativeCrypto_SSL_clear_options }, + { "SSL_get_ciphers", "(I)[Ljava/lang/String;", (void*)NativeCrypto_SSL_get_ciphers }, + { "SSL_set_cipher_list", "(ILjava/lang/String;)V", (void*)NativeCrypto_SSL_set_cipher_list }, + { "SSL_set_verify", "(II)V", (void*)NativeCrypto_SSL_set_verify}, + { "SSL_set_session", "(II)V", (void*)NativeCrypto_SSL_set_session }, + { "SSL_set_session_creation_enabled", "(IZ)V", (void*)NativeCrypto_SSL_set_session_creation_enabled }, + { "SSL_do_handshake", "(ILjava/net/Socket;Lorg/apache/harmony/xnet/provider/jsse/NativeCrypto$CertificateChainVerifier;Lorg/apache/harmony/xnet/provider/jsse/NativeCrypto$HandshakeCompletedCallback;IZ)I",(void*)NativeCrypto_SSL_do_handshake}, + { "SSL_get_certificate", "(I)[[B", (void*)NativeCrypto_SSL_get_certificate}, { "SSL_free", "(I)V", (void*)NativeCrypto_SSL_free}, }; -/** - * Module scope variables initialized during JNI registration. - */ -static jfieldID field_Socket_mImpl; -static jfieldID field_Socket_mFD; - // ============================================================================ // === OpenSSL-related helper stuff begins here. ============================== // ============================================================================ -int get_socket_timeout(int type, int sd) { - struct timeval tv; - socklen_t len = sizeof(tv); - if (getsockopt(sd, SOL_SOCKET, type, &tv, &len) < 0) { - LOGE("getsockopt(%d, SOL_SOCKET): %s (%d)", - sd, - strerror(errno), - errno); - return 0; - } - // LOGI("Current socket timeout (%d(s), %d(us))!", - // (int)tv.tv_sec, (int)tv.tv_usec); - int timeout = tv.tv_sec * 1000 + tv.tv_usec / 1000; - return timeout; -} - -#ifdef TIMEOUT_DEBUG_SSL - -void print_socket_timeout(const char* name, int type, int sd) { - struct timeval tv; - int len = sizeof(tv); - if (getsockopt(sd, SOL_SOCKET, type, &tv, &len) < 0) { - LOGE("getsockopt(%d, SOL_SOCKET, %s): %s (%d)", - sd, - name, - strerror(errno), - errno); - } - LOGI("Current socket %s is (%d(s), %d(us))!", - name, (int)tv.tv_sec, (int)tv.tv_usec); -} - -void print_timeout(const char* method, SSL* ssl) { - LOGI("SSL_get_default_timeout %d in %s", SSL_get_default_timeout(ssl), method); - int fd = SSL_get_fd(ssl); - print_socket_timeout("SO_RCVTIMEO", SO_RCVTIMEO, fd); - print_socket_timeout("SO_SNDTIMEO", SO_SNDTIMEO, fd); -} - -#endif - -/** - * Dark magic helper function that checks, for a given SSL session, whether it - * can SSL_read() or SSL_write() without blocking. Takes into account any - * concurrent attempts to close the SSL session from the Java side. This is - * needed to get rid of the hangs that occur when thread #1 closes the SSLSocket - * while thread #2 is sitting in a blocking read or write. The type argument - * specifies whether we are waiting for readability or writability. It expects - * to be passed either SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE, since we - * only need to wait in case one of these problems occurs. - * - * @param type Either SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE - * @param fd The file descriptor to wait for (the underlying socket) - * @param data The application data structure with mutex info etc. - * @param timeout The timeout value for select call, with the special value - * 0 meaning no timeout at all (wait indefinitely). Note: This is - * the Java semantics of the timeout value, not the usual - * select() semantics. - * @return The result of the inner select() call, -1 on additional errors - */ -static int sslSelect(int type, int fd, APP_DATA *data, int timeout) { - fd_set rfds; - fd_set wfds; - - FD_ZERO(&rfds); - FD_ZERO(&wfds); - - if (type == SSL_ERROR_WANT_READ) { - FD_SET(fd, &rfds); - } else { - FD_SET(fd, &wfds); - } - - FD_SET(data->fdsEmergency[0], &rfds); - - int max = fd > data->fdsEmergency[0] ? fd : data->fdsEmergency[0]; - - // Build a struct for the timeout data if we actually want a timeout. - struct timeval tv; - struct timeval *ptv; - if (timeout > 0) { - tv.tv_sec = timeout / 1000; - tv.tv_usec = 0; - ptv = &tv; - } else { - ptv = NULL; - } - - // LOGD("Doing select() for SSL_ERROR_WANT_%s...", type == SSL_ERROR_WANT_READ ? "READ" : "WRITE"); - int result = select(max + 1, &rfds, &wfds, NULL, ptv); - // LOGD("Returned from select(), result is %d", result); - - // Lock - if (MUTEX_LOCK(data->mutex) == -1) { - return -1; - } - - // If we have been woken up by the emergency pipe, there must be a token in - // it. Thus we can safely read it (even in a blocking way). - if (FD_ISSET(data->fdsEmergency[0], &rfds)) { - char token; - do { - read(data->fdsEmergency[0], &token, 1); - } while (errno == EINTR); - } - - // Tell the world that there is now one thread less waiting for the - // underlying network. - data->waitingThreads--; - - // Unlock - MUTEX_UNLOCK(data->mutex); - // LOGD("leave sslSelect"); - return result; -} - -/** - * Helper function that wakes up a thread blocked in select(), in case there is - * one. Is being called by sslRead() and sslWrite() as well as by JNI glue - * before closing the connection. - * - * @param data The application data structure with mutex info etc. - */ -static void sslNotify(APP_DATA *data) { - // Write a byte to the emergency pipe, so a concurrent select() can return. - // Note we have to restore the errno of the original system call, since the - // caller relies on it for generating error messages. - int errnoBackup = errno; - char token = '*'; - do { - errno = 0; - write(data->fdsEmergency[1], &token, 1); - } while (errno == EINTR); - errno = errnoBackup; -} - /** * Helper function which does the actual reading. The Java layer guarantees that * at most one thread will enter this function at any given time. @@ -1217,7 +1810,7 @@ static void sslNotify(APP_DATA *data) { * @return number of bytes read on success, -1 if the connection was * cleanly shut down, or THROW_EXCEPTION if an exception should be thrown. */ -static int sslRead(SSL* ssl, char* buf, jint len, int* sslReturnCode, +static int sslRead(JNIEnv* env, SSL* ssl, char* buf, jint len, int* sslReturnCode, int* sslErrorCode, int timeout) { // LOGD("Entering sslRead, caller requests to read %d bytes...", len); @@ -1228,22 +1821,25 @@ static int sslRead(SSL* ssl, char* buf, jint len, int* sslReturnCode, } int fd = SSL_get_fd(ssl); - BIO *bio = SSL_get_rbio(ssl); + BIO* bio = SSL_get_rbio(ssl); - APP_DATA* data = (APP_DATA*) SSL_get_app_data(ssl); + AppData* appData = (AppData*) SSL_get_app_data(ssl); - while (data->aliveAndKicking) { + while (appData->aliveAndKicking) { errno = 0; // Lock - if (MUTEX_LOCK(data->mutex) == -1) { + if (MUTEX_LOCK(appData->mutex) == -1) { return -1; } unsigned int bytesMoved = BIO_number_read(bio) + BIO_number_written(bio); // LOGD("Doing SSL_Read()"); + AppData* appData = (AppData*) SSL_get_app_data(ssl); + appData->setEnv(env); int result = SSL_read(ssl, buf, len); + appData->clearEnv(); int error = SSL_ERROR_NONE; if (result <= 0) { error = SSL_get_error(ssl, result); @@ -1254,18 +1850,18 @@ static int sslRead(SSL* ssl, char* buf, jint len, int* sslReturnCode, // If we have been successful in moving data around, check whether it // might make sense to wake up other blocked threads, so they can give // it a try, too. - if (BIO_number_read(bio) + BIO_number_written(bio) != bytesMoved && data->waitingThreads > 0) { - sslNotify(data); + if (BIO_number_read(bio) + BIO_number_written(bio) != bytesMoved && appData->waitingThreads > 0) { + sslNotify(appData); } // If we are blocked by the underlying socket, tell the world that // there will be one more waiting thread now. if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) { - data->waitingThreads++; + appData->waitingThreads++; } // Unlock - MUTEX_UNLOCK(data->mutex); + MUTEX_UNLOCK(appData->mutex); switch (error) { // Sucessfully read at least one byte. @@ -1281,7 +1877,7 @@ static int sslRead(SSL* ssl, char* buf, jint len, int* sslReturnCode, // Need to wait for availability of underlying layer, then retry. case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: { - int selectResult = sslSelect(error, fd, data, timeout); + int selectResult = sslSelect(error, fd, appData, timeout); if (selectResult == -1) { *sslReturnCode = -1; *sslErrorCode = error; @@ -1335,7 +1931,7 @@ static int sslRead(SSL* ssl, char* buf, jint len, int* sslReturnCode, * @return number of bytes read on success, -1 if the connection was * cleanly shut down, or THROW_EXCEPTION if an exception should be thrown. */ -static int sslWrite(SSL* ssl, const char* buf, jint len, int* sslReturnCode, +static int sslWrite(JNIEnv* env, SSL* ssl, const char* buf, jint len, int* sslReturnCode, int* sslErrorCode) { // LOGD("Entering sslWrite(), caller requests to write %d bytes...", len); @@ -1346,22 +1942,24 @@ static int sslWrite(SSL* ssl, const char* buf, jint len, int* sslReturnCode, } int fd = SSL_get_fd(ssl); - BIO *bio = SSL_get_wbio(ssl); + BIO* bio = SSL_get_wbio(ssl); - APP_DATA* data = (APP_DATA*) SSL_get_app_data(ssl); + AppData* appData = (AppData*) SSL_get_app_data(ssl); int count = len; - while (data->aliveAndKicking && len > 0) { + while (appData->aliveAndKicking && len > 0) { errno = 0; - if (MUTEX_LOCK(data->mutex) == -1) { + if (MUTEX_LOCK(appData->mutex) == -1) { return -1; } unsigned int bytesMoved = BIO_number_read(bio) + BIO_number_written(bio); // LOGD("Doing SSL_write() with %d bytes to go", len); + appData->setEnv(env); int result = SSL_write(ssl, buf, len); + appData->clearEnv(); int error = SSL_ERROR_NONE; if (result <= 0) { error = SSL_get_error(ssl, result); @@ -1372,17 +1970,17 @@ static int sslWrite(SSL* ssl, const char* buf, jint len, int* sslReturnCode, // If we have been successful in moving data around, check whether it // might make sense to wake up other blocked threads, so they can give // it a try, too. - if (BIO_number_read(bio) + BIO_number_written(bio) != bytesMoved && data->waitingThreads > 0) { - sslNotify(data); + if (BIO_number_read(bio) + BIO_number_written(bio) != bytesMoved && appData->waitingThreads > 0) { + sslNotify(appData); } // If we are blocked by the underlying socket, tell the world that // there will be one more waiting thread now. if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) { - data->waitingThreads++; + appData->waitingThreads++; } - MUTEX_UNLOCK(data->mutex); + MUTEX_UNLOCK(appData->mutex); switch (error) { // Sucessfully write at least one byte. @@ -1402,7 +2000,7 @@ static int sslWrite(SSL* ssl, const char* buf, jint len, int* sslReturnCode, // it's also not standard Java behavior, so we wait forever here. case SSL_ERROR_WANT_READ: case SSL_ERROR_WANT_WRITE: { - int selectResult = sslSelect(error, fd, data, 0); + int selectResult = sslSelect(error, fd, appData, 0); if (selectResult == -1) { *sslReturnCode = -1; *sslErrorCode = error; @@ -1538,329 +2136,18 @@ static int rsaVerify(unsigned char* msg, unsigned int msgLen, unsigned char* sig // === OpenSSL-related helper stuff ends here. JNI glue follows. ============== // ============================================================================ -/** - * A connection within an OpenSSL context is established. (1) A new socket is - * constructed, (2) the TLS/SSL handshake with a server is initiated. - */ -static jboolean org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_connect(JNIEnv* env, jclass, - jint ssl_address, jobject socketObject, jint timeout, jboolean client_mode, jint ssl_session_address) -{ - // LOGD("ENTER connect"); - SSL* ssl = getSslPointer(env, ssl_address, true); - if (ssl == NULL) { - return (jboolean) false; - } - SSL_SESSION* ssl_session = reinterpret_cast<SSL_SESSION*>(static_cast<uintptr_t>(ssl_session_address)); - - jobject socketImplObject = env->GetObjectField(socketObject, field_Socket_mImpl); - if (socketImplObject == NULL) { - throwIOExceptionStr(env, - "couldn't get the socket impl from the socket"); - return (jboolean) false; - } - - jobject fdObject = env->GetObjectField(socketImplObject, field_Socket_mFD); - if (fdObject == NULL) { - throwIOExceptionStr(env, - "couldn't get the file descriptor from the socket impl"); - return (jboolean) false; - } - - int fd = jniGetFDFromFileDescriptor(env, fdObject); - - int ret = SSL_set_fd(ssl, fd); - - if (ret != 1) { - throwIOExceptionWithSslErrors(env, ret, 0, - "Error setting the file descriptor"); - SSL_clear(ssl); - return (jboolean) false; - } - - if (ssl_session != NULL) { - // LOGD("Trying to reuse session %p", ssl_session); - ret = SSL_set_session(ssl, ssl_session); - if (ret != 1) { - /* - * Translate the error, and throw if it turns out to be a real - * problem. - */ - int sslErrorCode = SSL_get_error(ssl, ret); - if (sslErrorCode != SSL_ERROR_ZERO_RETURN) { - throwIOExceptionWithSslErrors(env, ret, sslErrorCode, - "SSL session set"); - SSL_clear(ssl); - return (jboolean) false; - } - } - } - - /* - * Make socket non-blocking, so SSL_connect SSL_read() and SSL_write() don't hang - * forever and we can use select() to find out if the socket is ready. - */ - int mode = fcntl(fd, F_GETFL); - if (mode == -1 || fcntl(fd, F_SETFL, mode | O_NONBLOCK) == -1) { - throwIOExceptionStr(env, "Unable to make socket non blocking"); - SSL_clear(ssl); - return (jboolean) false; - } - - /* - * Create our special application data. - */ - if (sslCreateAppData(ssl) == -1) { - throwIOExceptionStr(env, "Unable to create application data"); - SSL_clear(ssl); - // TODO - return (jboolean) false; - } - - APP_DATA* data = (APP_DATA*) SSL_get_app_data(ssl); - - while (data->aliveAndKicking) { - errno = 0; - ret = SSL_connect(ssl); - if (ret == 1) { - break; - } else if (errno == EINTR) { - continue; - } else { - // LOGD("SSL_connect: result %d, errno %d, timeout %d", ret, errno, timeout); - int error = SSL_get_error(ssl, ret); - - /* - * If SSL_connect doesn't succeed due to the socket being - * either unreadable or unwritable, we use sslSelect to - * wait for it to become ready. If that doesn't happen - * before the specified timeout or an error occurs, we - * cancel the handshake. Otherwise we try the SSL_connect - * again. - */ - if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) { - data->waitingThreads++; - int selectResult = sslSelect(error, fd, data, timeout); - - if (selectResult == -1) { - throwIOExceptionWithSslErrors(env, -1, error, - "Connect error"); - SSL_clear(ssl); - return (jboolean) false; - } else if (selectResult == 0) { - throwSocketTimeoutException(env, "SSL handshake timed out"); - SSL_clear(ssl); - freeSslErrorState(); - return (jboolean) false; - } - } else { - LOGE("Unknown error %d during connect", error); - break; - } - } - } - - if (ret != 1) { - /* - * Translate the error, and throw if it turns out to be a real - * problem. - */ - int sslErrorCode = SSL_get_error(ssl, ret); - if (sslErrorCode != SSL_ERROR_ZERO_RETURN) { - throwIOExceptionWithSslErrors(env, ret, sslErrorCode, - "SSL handshake failure"); - SSL_clear(ssl); - return (jboolean) false; - } - } - - if (ssl_session != NULL) { - ret = SSL_session_reused(ssl); - // if (ret == 1) LOGD("Session %p was reused", ssl_session); - // else LOGD("Session %p was not reused, using new session %p", ssl_session, SSL_get_session(ssl)); - return (jboolean) ret; - } else { - // LOGD("New session %p was negotiated", SSL_get_session(ssl)); - return (jboolean) 0; - } - // LOGD("LEAVE connect"); -} - static jint org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_getsslsession(JNIEnv* env, jclass, - jint jssl) -{ - return (jint) SSL_get1_session((SSL*) jssl); -} - -static jint org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_accept(JNIEnv* env, jclass, - jint ssl_address, jobject socketObject) -{ - SSL* serverSocketSsl = reinterpret_cast<SSL*>(static_cast<uintptr_t>(ssl_address)); - if (serverSocketSsl == NULL) { - throwIOExceptionWithSslErrors(env, 0, 0, - "Unusable SSL structure"); - return NULL; - } - - SSL* ssl = SSL_dup(serverSocketSsl); - if (ssl == NULL) { - throwIOExceptionWithSslErrors(env, 0, 0, - "Unable to create SSL structure"); - return NULL; - } - - jobject socketImplObject = env->GetObjectField(socketObject, field_Socket_mImpl); - if (socketImplObject == NULL) { - throwIOExceptionStr(env, "couldn't get the socket impl from the socket"); - return NULL; - } - - jobject fdObject = env->GetObjectField(socketImplObject, field_Socket_mFD); - if (fdObject == NULL) { - throwIOExceptionStr(env, "couldn't get the file descriptor from the socket impl"); - return NULL; - } - - - int sd = jniGetFDFromFileDescriptor(env, fdObject); - - BIO* bio = BIO_new_socket(sd, BIO_NOCLOSE); - SSL_set_bio(ssl, bio, bio); - - /* - * Fill in the stack allocated appdata structure needed for the - * certificate callback and store this in the SSL application data - * slot. - */ - jsse_ssl_app_data_t appdata; - appdata.env = env; - appdata.object = socketObject; - SSL_set_app_data(ssl, &appdata); - - /* - * Do the actual SSL_accept(). It is possible this code is insufficient. - * Maybe we need to deal with all the special SSL error cases (WANT_*), - * just like we do for SSL_connect(). But currently it is looking ok. - */ - int ret = SSL_accept(ssl); - - /* - * Clear the SSL application data slot again, so we can safely use it for - * our ordinary synchronization structure afterwards. Also, we don't want - * sslDestroyAppData() to think that there is something that needs to be - * freed right now (in case of an error). - */ - SSL_set_app_data(ssl, NULL); - - if (ret == 0) { - /* - * The other side closed the socket before the handshake could be - * completed, but everything is within the bounds of the TLS protocol. - * We still might want to find out the real reason of the failure. - */ - int sslErrorCode = SSL_get_error(ssl, ret); - if (sslErrorCode == SSL_ERROR_NONE || - (sslErrorCode == SSL_ERROR_SYSCALL && errno == 0)) { - throwIOExceptionStr(env, "Connection closed by peer"); - } else { - throwIOExceptionWithSslErrors(env, ret, sslErrorCode, - "Trouble accepting connection"); - } - SSL_clear(ssl); - return NULL; - } else if (ret < 0) { - /* - * Translate the error and throw exception. We are sure it is an error - * at this point. - */ - int sslErrorCode = SSL_get_error(ssl, ret); - throwIOExceptionWithSslErrors(env, ret, sslErrorCode, - "Trouble accepting connection"); - SSL_clear(ssl); - return NULL; - } - - /* - * Make socket non-blocking, so SSL_read() and SSL_write() don't hang - * forever and we can use select() to find out if the socket is ready. - */ - int fd = SSL_get_fd(ssl); - int mode = fcntl(fd, F_GETFL); - if (mode == -1 || fcntl(fd, F_SETFL, mode | O_NONBLOCK) == -1) { - throwIOExceptionStr(env, "Unable to make socket non blocking"); - SSL_clear(ssl); - return NULL; - } - - /* - * Create our special application data. - */ - if (sslCreateAppData(ssl) == -1) { - throwIOExceptionStr(env, "Unable to create application data"); - SSL_clear(ssl); - return NULL; - } - - return (jint) ssl; -} - -#define SSL_aRSA 0x00000001L -#define SSL_aDSS 0x00000002L -#define SSL_aNULL 0x00000004L -#define SSL_aDH 0x00000008L -#define SSL_aECDH 0x00000010L -#define SSL_aKRB5 0x00000020L -#define SSL_aECDSA 0x00000040L -#define SSL_aPSK 0x00000080L - -/** - * Sets the client's crypto algorithms and authentication methods. - */ -static jstring org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_cipherauthenticationmethod(JNIEnv* env, - jclass, jint ssl_address) + jint ssl_address) { SSL* ssl = getSslPointer(env, ssl_address, true); + JNI_TRACE("ssl=%p OpenSSLSocketImpl_getsslsession", ssl); if (ssl == NULL) { - return NULL; - } - - const SSL_CIPHER* cipher = SSL_get_current_cipher(ssl); - - unsigned long alg_auth = cipher->algorithm_auth; - - const char *au; - switch (alg_auth) { - case SSL_aRSA: - au="RSA"; - break; - case SSL_aDSS: - au="DSS"; - break; - case SSL_aDH: - au="DH"; - break; - case SSL_aKRB5: - au="KRB5"; - break; - case SSL_aECDH: - au = "ECDH"; - break; - case SSL_aNULL: - au="None"; - break; - case SSL_aECDSA: - au="ECDSA"; - break; - case SSL_aPSK: - au="PSK"; - break; - default: - au="unknown"; - break; + JNI_TRACE("ssl=%p OpenSSLSocketImpl_getsslsession => NULL", ssl); + return NULL; } - - jstring ret = env->NewStringUTF(au); - - return ret; + SSL_SESSION* ssl_session = SSL_get1_session(ssl); + JNI_TRACE("ssl=%p OpenSSLSocketImpl_getsslsession => ssl_session=%p", ssl, ssl_session); + return (jint) ssl_session; } /** @@ -1869,6 +2156,7 @@ static jstring org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_cipheraut static jint org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_read(JNIEnv* env, jclass, jint ssl_address, jint timeout) { SSL* ssl = getSslPointer(env, ssl_address, true); + JNI_TRACE("ssl=%p OpenSSLSocketImpl_readba timeout=%d", ssl, timeout); if (ssl == NULL) { return 0; } @@ -1877,24 +2165,31 @@ static jint org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_read(JNIEnv* int returnCode = 0; int errorCode = 0; - int ret = sslRead(ssl, (char *) &byteRead, 1, &returnCode, &errorCode, timeout); + int ret = sslRead(env, ssl, (char *) &byteRead, 1, &returnCode, &errorCode, timeout); + int result; switch (ret) { case THROW_EXCEPTION: // See sslRead() regarding improper failure to handle normal cases. - throwIOExceptionWithSslErrors(env, returnCode, errorCode, + throwSSLExceptionWithSslErrors(env, returnCode, errorCode, "Read error"); - return -1; + result = -1; + break; case THROW_SOCKETTIMEOUTEXCEPTION: throwSocketTimeoutException(env, "Read timed out"); - return -1; + result = -1; + break; case -1: // Propagate EOF upwards. - return -1; + result = -1; + break; default: // Return the actual char read, make sure it stays 8 bits wide. - return ((jint) byteRead) & 0xFF; + result = ((jint) byteRead) & 0xFF; + break; } + JNI_TRACE("ssl=%p OpenSSLSocketImpl_read => %d", ssl, result); + return result; } /** @@ -1904,6 +2199,7 @@ static jint org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_read(JNIEnv* static jint org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_readba(JNIEnv* env, jclass, jint ssl_address, jbyteArray dest, jint offset, jint len, jint timeout) { SSL* ssl = getSslPointer(env, ssl_address, true); + JNI_TRACE("ssl=%p OpenSSLSocketImpl_readba dest=%p offset=%d len=%d timeout=%d", ssl, dest, offset, len, timeout); if (ssl == NULL) { return 0; } @@ -1912,22 +2208,25 @@ static jint org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_readba(JNIEn int returnCode = 0; int errorCode = 0; - int ret = - sslRead(ssl, (char*) (bytes + offset), len, &returnCode, &errorCode, timeout); + int ret = sslRead(env, ssl, (char*) (bytes + offset), len, &returnCode, &errorCode, timeout); env->ReleaseByteArrayElements(dest, bytes, 0); + int result; if (ret == THROW_EXCEPTION) { // See sslRead() regarding improper failure to handle normal cases. - throwIOExceptionWithSslErrors(env, returnCode, errorCode, + throwSSLExceptionWithSslErrors(env, returnCode, errorCode, "Read error"); - return -1; + result = -1; } else if(ret == THROW_SOCKETTIMEOUTEXCEPTION) { throwSocketTimeoutException(env, "Read timed out"); - return -1; + result = -1; + } else { + result = ret; } - return ret; + JNI_TRACE("ssl=%p OpenSSLSocketImpl_readba => %d", ssl, result); + return result; } /** @@ -1936,6 +2235,7 @@ static jint org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_readba(JNIEn static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_write(JNIEnv* env, jclass, jint ssl_address, jint b) { SSL* ssl = getSslPointer(env, ssl_address, true); + JNI_TRACE("ssl=%p OpenSSLSocketImpl_write b=%d", ssl, b); if (ssl == NULL) { return; } @@ -1943,11 +2243,11 @@ static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_write(JNIEnv int returnCode = 0; int errorCode = 0; char buf[1] = { (char) b }; - int ret = sslWrite(ssl, buf, 1, &returnCode, &errorCode); + int ret = sslWrite(env, ssl, buf, 1, &returnCode, &errorCode); if (ret == THROW_EXCEPTION) { // See sslWrite() regarding improper failure to handle normal cases. - throwIOExceptionWithSslErrors(env, returnCode, errorCode, + throwSSLExceptionWithSslErrors(env, returnCode, errorCode, "Write error"); } else if(ret == THROW_SOCKETTIMEOUTEXCEPTION) { throwSocketTimeoutException(env, "Write timed out"); @@ -1961,6 +2261,7 @@ static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_writeba(JNIE jint ssl_address, jbyteArray dest, jint offset, jint len) { SSL* ssl = getSslPointer(env, ssl_address, true); + JNI_TRACE("ssl=%p OpenSSLSocketImpl_writeba dest=%p offset=%d len=%d", ssl, dest, offset, len); if (ssl == NULL) { return; } @@ -1968,14 +2269,13 @@ static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_writeba(JNIE jbyte* bytes = env->GetByteArrayElements(dest, NULL); int returnCode = 0; int errorCode = 0; - int ret = sslWrite(ssl, (const char *) (bytes + offset), len, - &returnCode, &errorCode); + int ret = sslWrite(env, ssl, (const char *) (bytes + offset), len, &returnCode, &errorCode); env->ReleaseByteArrayElements(dest, bytes, 0); if (ret == THROW_EXCEPTION) { // See sslWrite() regarding improper failure to handle normal cases. - throwIOExceptionWithSslErrors(env, returnCode, errorCode, + throwSSLExceptionWithSslErrors(env, returnCode, errorCode, "Write error"); } else if(ret == THROW_SOCKETTIMEOUTEXCEPTION) { throwSocketTimeoutException(env, "Write timed out"); @@ -1988,6 +2288,7 @@ static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_writeba(JNIE static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_interrupt( JNIEnv* env, jclass, jint ssl_address) { SSL* ssl = getSslPointer(env, ssl_address, false); + JNI_TRACE("ssl=%p OpenSSLSocketImpl_interrupt", ssl); if (ssl == NULL) { return; } @@ -1996,13 +2297,13 @@ static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_interrupt( * Mark the connection as quasi-dead, then send something to the emergency * file descriptor, so any blocking select() calls are woken up. */ - APP_DATA* data = (APP_DATA*) SSL_get_app_data(ssl); - if (data != NULL) { - data->aliveAndKicking = 0; + AppData* appData = (AppData*) SSL_get_app_data(ssl); + if (appData != NULL) { + appData->aliveAndKicking = 0; // At most two threads can be waiting. - sslNotify(data); - sslNotify(data); + sslNotify(appData); + sslNotify(appData); } } @@ -2012,23 +2313,27 @@ static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_interrupt( static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_close( JNIEnv* env, jclass, jint ssl_address) { SSL* ssl = getSslPointer(env, ssl_address, false); + JNI_TRACE("ssl=%p OpenSSLSocketImpl_close", ssl); if (ssl == NULL) { return; } - /* * Try to make socket blocking again. OpenSSL literature recommends this. */ int fd = SSL_get_fd(ssl); + JNI_TRACE("ssl=%p OpenSSLSocketImpl_close s=%d", ssl, fd); if (fd != -1) { int mode = fcntl(fd, F_GETFL); if (mode == -1 || fcntl(fd, F_SETFL, mode & ~O_NONBLOCK) == -1) { -// throwIOExceptionStr(env, "Unable to make socket blocking again"); +// throwSSLExceptionStr(env, "Unable to make socket blocking again"); // LOGW("Unable to make socket blocking again"); } } + AppData* appData = (AppData*) SSL_get_app_data(ssl); + appData->setEnv(env); int ret = SSL_shutdown(ssl); + appData->clearEnv(); switch (ret) { case 0: /* @@ -2053,7 +2358,7 @@ static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_close( * exception. */ int sslErrorCode = SSL_get_error(ssl, ret); - throwIOExceptionWithSslErrors(env, ret, sslErrorCode, "SSL shutdown failed"); + throwSSLExceptionWithSslErrors(env, ret, sslErrorCode, "SSL shutdown failed"); break; } @@ -2067,10 +2372,12 @@ static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_close( static int org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_verifysignature(JNIEnv* env, jclass clazz, jbyteArray msg, jbyteArray sig, jstring algorithm, jbyteArray mod, jbyteArray exp) { - // LOGD("Entering verifysignature()"); + JNI_TRACE("OpenSSLSocketImpl_verifysignature msg=%p sig=%p algorithm=%p mod=%p exp%p", + msg, sig, algorithm, mod, exp); if (msg == NULL || sig == NULL || algorithm == NULL || mod == NULL || exp == NULL) { jniThrowNullPointerException(env, NULL); + JNI_TRACE("OpenSSLSocketImpl_verifysignature => -1"); return -1; } @@ -2089,6 +2396,7 @@ static int org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_verifysignatu jint expLength = env->GetArrayLength(exp); const char* algorithmChars = env->GetStringUTFChars(algorithm, NULL); + JNI_TRACE("OpenSSLSocketImpl_verifysignature algorithmChars=%s", algorithmChars); RSA* rsa = rsaCreateKey((unsigned char*) modBytes, modLength, (unsigned char*) expBytes, expLength); if (rsa != NULL) { @@ -2116,123 +2424,22 @@ static int org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_verifysignatu freeSslErrorState(); } + JNI_TRACE("OpenSSLSocketImpl_verifysignature => %d", result); return result; } static JNINativeMethod sSocketImplMethods[] = { - {"nativeconnect", "(ILjava/net/Socket;IZI)Z", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_connect}, - {"nativegetsslsession", "(I)I", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_getsslsession}, {"nativeread", "(II)I", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_read}, {"nativeread", "(I[BIII)I", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_readba}, {"nativewrite", "(II)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_write}, {"nativewrite", "(I[BII)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_writeba}, - {"nativeaccept", "(ILjava/net/Socket;)I", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_accept}, - {"nativecipherauthenticationmethod", "(I)Ljava/lang/String;", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_cipherauthenticationmethod}, {"nativeinterrupt", "(I)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_interrupt}, {"nativeclose", "(I)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_close}, {"nativeverifysignature", "([B[BLjava/lang/String;[B[B)I", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_verifysignature}, }; /** - * Gives an array back containing all the X509 certificate's bytes. - */ -static jobjectArray getcertificatebytes(JNIEnv* env, - const STACK_OF(X509) *chain) -{ - BUF_MEM *bptr; - int count, i; - jbyteArray bytes; - jobjectArray joa; - - if (chain == NULL) { - // Chain can be NULL if the associated cipher doesn't do certs. - return NULL; - } - - count = sk_X509_num(chain); - - if (count > 0) { - joa = env->NewObjectArray(count, env->FindClass("[B"), NULL); - - if (joa == NULL) { - return NULL; - } - - BIO *bio = BIO_new(BIO_s_mem()); - - // LOGD("Start fetching the certificates"); - for (i = 0; i < count; i++) { - X509 *cert = sk_X509_value(chain, i); - - BIO_reset(bio); - PEM_write_bio_X509(bio, cert); - - BIO_get_mem_ptr(bio, &bptr); - bytes = env->NewByteArray(bptr->length); - - if (bytes == NULL) { - /* - * Indicate an error by resetting joa to NULL. It will - * eventually get gc'ed. - */ - joa = NULL; - break; - } else { - jbyte* src = reinterpret_cast<jbyte*>(bptr->data); - env->SetByteArrayRegion(bytes, 0, bptr->length, src); - env->SetObjectArrayElement(joa, i, bytes); - } - } - - // LOGD("Certificate fetching complete"); - BIO_free(bio); - return joa; - } else { - return NULL; - } -} - -/** - * Verify the X509 certificate. - */ -static int verify_callback(int preverify_ok, X509_STORE_CTX *x509_store_ctx) -{ - /* Get the correct index to the SSLobject stored into X509_STORE_CTX. */ - SSL* ssl = (SSL*)X509_STORE_CTX_get_ex_data(x509_store_ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); - - jsse_ssl_app_data_t* appdata = (jsse_ssl_app_data_t*)SSL_get_app_data(ssl); - - jclass cls = appdata->env->GetObjectClass(appdata->object); - - jmethodID methodID = appdata->env->GetMethodID(cls, "verifyCertificateChain", "([[B)Z"); - - jobjectArray objectArray = getcertificatebytes(appdata->env, x509_store_ctx->untrusted); - - jboolean verified = appdata->env->CallBooleanMethod(appdata->object, methodID, objectArray); - - return (verified) ? 1 : 0; -} - -/** - * Sets the client's credentials and the depth of theirs verification. - */ -static void org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_nativesetclientauth(JNIEnv* env, - jclass, jint ssl_address, jint value) -{ - SSL* ssl = getSslPointer(env, ssl_address, true); - if (ssl == NULL) { - return; - } - SSL_set_verify(ssl, (int)value, verify_callback); -} - -static JNINativeMethod sServerSocketImplMethods[] = -{ - {"nativesetclientauth", "(II)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_nativesetclientauth}, -}; - -/** * Our implementation of what might be considered * SSL_SESSION_get_peer_cert_chain */ @@ -2249,13 +2456,16 @@ static jobjectArray OpenSSLSessionImpl_getPeerCertificatesImpl(JNIEnv* env, jclass, jint ssl_ctx_address, jint ssl_session_address) { SSL_CTX* ssl_ctx = reinterpret_cast<SSL_CTX*>(static_cast<uintptr_t>(ssl_ctx_address)); + SSL_SESSION* ssl_session = reinterpret_cast<SSL_SESSION*>(static_cast<uintptr_t>(ssl_session_address)); + JNI_TRACE("ssl_session=%p OpenSSLSessionImpl_getPeerCertificatesImpl ssl_ctx=%p", ssl_session, ssl_ctx); if (ssl_ctx == NULL) { jniThrowNullPointerException(env, "SSL_CTX is null"); + JNI_TRACE("ssl_session=%p OpenSSLSessionImpl_getPeerCertificatesImpl => NULL", ssl_session); return NULL; } - SSL_SESSION* ssl_session = reinterpret_cast<SSL_SESSION*>(static_cast<uintptr_t>(ssl_session_address)); STACK_OF(X509)* chain = SSL_SESSION_get_peer_cert_chain(ssl_ctx, ssl_session); - jobjectArray objectArray = getcertificatebytes(env, chain); + jobjectArray objectArray = getCertificateBytes(env, chain); + JNI_TRACE("ssl_session=%p OpenSSLSessionImpl_getPeerCertificatesImpl => %p", ssl_session, objectArray); return objectArray; } @@ -2266,13 +2476,16 @@ static jobjectArray OpenSSLSessionImpl_getPeerCertificatesImpl(JNIEnv* env, */ static jbyteArray OpenSSLSessionImpl_getEncoded(JNIEnv* env, jclass, jint ssl_session_address) { SSL_SESSION* ssl_session = reinterpret_cast<SSL_SESSION*>(static_cast<uintptr_t>(ssl_session_address)); + JNI_TRACE("ssl_session=%p OpenSSLSessionImpl_getEncoded", ssl_session); if (ssl_session == NULL) { + JNI_TRACE("ssl_session=%p OpenSSLSessionImpl_getEncoded => NULL", ssl_session); return NULL; } // Compute the size of the DER data int size = i2d_SSL_SESSION(ssl_session, NULL); if (size == 0) { + JNI_TRACE("ssl_session=%p OpenSSLSessionImpl_getEncoded => NULL", ssl_session); return NULL; } @@ -2284,6 +2497,7 @@ static jbyteArray OpenSSLSessionImpl_getEncoded(JNIEnv* env, jclass, jint ssl_se env->ReleaseByteArrayElements(bytes, tmp, 0); } + JNI_TRACE("ssl_session=%p OpenSSLSessionImpl_getEncoded => size=%d", ssl_session, size); return bytes; } @@ -2291,7 +2505,9 @@ static jbyteArray OpenSSLSessionImpl_getEncoded(JNIEnv* env, jclass, jint ssl_se * Deserialize the session. */ static jint OpenSSLSessionImpl_initializeNativeImpl(JNIEnv* env, jclass, jbyteArray bytes, jint size) { + JNI_TRACE("OpenSSLSessionImpl_initializeNativeImpl bytes=%p size=%d", bytes, size); if (bytes == NULL) { + JNI_TRACE("OpenSSLSessionImpl_initializeNativeImpl => 0"); return 0; } @@ -2300,6 +2516,7 @@ static jint OpenSSLSessionImpl_initializeNativeImpl(JNIEnv* env, jclass, jbyteAr SSL_SESSION* ssl_session = d2i_SSL_SESSION(NULL, &ucp, size); env->ReleaseByteArrayElements(bytes, tmp, 0); + JNI_TRACE("OpenSSLSessionImpl_initializeNativeImpl => %p", ssl_session); return static_cast<jint>(reinterpret_cast<uintptr_t>(ssl_session)); } @@ -2308,13 +2525,14 @@ static jint OpenSSLSessionImpl_initializeNativeImpl(JNIEnv* env, jclass, jbyteAr */ static jbyteArray OpenSSLSessionImpl_getId(JNIEnv* env, jclass, jint ssl_session_address) { SSL_SESSION* ssl_session = reinterpret_cast<SSL_SESSION*>(static_cast<uintptr_t>(ssl_session_address)); - + JNI_TRACE("ssl_session=%p OpenSSLSessionImpl_getId", ssl_session); jbyteArray result = env->NewByteArray(ssl_session->session_id_length); if (result != NULL) { jbyte* src = reinterpret_cast<jbyte*>(ssl_session->session_id); env->SetByteArrayRegion(result, 0, ssl_session->session_id_length, src); } - + JNI_TRACE("ssl_session=%p OpenSSLSessionImpl_getId => %p session_id_length=%d", + ssl_session, result, ssl_session->session_id_length); return result; } @@ -2324,8 +2542,10 @@ static jbyteArray OpenSSLSessionImpl_getId(JNIEnv* env, jclass, jint ssl_session */ static jlong OpenSSLSessionImpl_getCreationTime(JNIEnv* env, jclass, jint ssl_session_address) { SSL_SESSION* ssl_session = reinterpret_cast<SSL_SESSION*>(static_cast<uintptr_t>(ssl_session_address)); - jlong result = SSL_SESSION_get_time(ssl_session); + JNI_TRACE("ssl_session=%p OpenSSLSessionImpl_getCreationTime", ssl_session); + jlong result = SSL_SESSION_get_time(ssl_session); // must be jlong, not long or *1000 will overflow result *= 1000; // OpenSSL uses seconds, Java uses milliseconds. + JNI_TRACE("ssl_session=%p OpenSSLSessionImpl_getCreationTime => %lld", ssl_session, result); return result; } @@ -2344,7 +2564,9 @@ static const char* SSL_SESSION_get_version(SSL_SESSION* ssl_session) { */ static jstring OpenSSLSessionImpl_getProtocol(JNIEnv* env, jclass, jint ssl_session_address) { SSL_SESSION* ssl_session = reinterpret_cast<SSL_SESSION*>(static_cast<uintptr_t>(ssl_session_address)); + JNI_TRACE("ssl_session=%p OpenSSLSessionImpl_getProtocol", ssl_session); const char* protocol = SSL_SESSION_get_version(ssl_session); + JNI_TRACE("ssl_session=%p OpenSSLSessionImpl_getProtocol => %s", ssl_session, protocol); jstring result = env->NewStringUTF(protocol); return result; } @@ -2354,9 +2576,11 @@ static jstring OpenSSLSessionImpl_getProtocol(JNIEnv* env, jclass, jint ssl_sess */ static jstring OpenSSLSessionImpl_getCipherSuite(JNIEnv* env, jclass, jint ssl_session_address) { SSL_SESSION* ssl_session = reinterpret_cast<SSL_SESSION*>(static_cast<uintptr_t>(ssl_session_address)); + JNI_TRACE("ssl_session=%p OpenSSLSessionImpl_getCipherSuite", ssl_session); const SSL_CIPHER* cipher = ssl_session->cipher; - jstring result = env->NewStringUTF(SSL_CIPHER_get_name(cipher)); - return result; + const char* name = SSL_CIPHER_get_name(cipher); + JNI_TRACE("ssl_session=%p OpenSSLSessionImpl_getCipherSuite => %s", ssl_session, name); + return env->NewStringUTF(name); } /** @@ -2364,19 +2588,19 @@ static jstring OpenSSLSessionImpl_getCipherSuite(JNIEnv* env, jclass, jint ssl_s */ static void OpenSSLSessionImpl_freeImpl(JNIEnv* env, jclass, jint session) { SSL_SESSION* ssl_session = reinterpret_cast<SSL_SESSION*>(session); - // LOGD("Freeing OpenSSL session %p", session); + JNI_TRACE("ssl_session=%p OpenSSLSessionImpl_freeImpl", ssl_session); SSL_SESSION_free(ssl_session); } static JNINativeMethod sSessionImplMethods[] = { - { "freeImpl", "(I)V", (void*) OpenSSLSessionImpl_freeImpl }, - { "getCipherSuite", "(I)Ljava/lang/String;", (void*) OpenSSLSessionImpl_getCipherSuite }, - { "getCreationTime", "(I)J", (void*) OpenSSLSessionImpl_getCreationTime }, - { "getEncoded", "(I)[B", (void*) OpenSSLSessionImpl_getEncoded }, - { "getId", "(I)[B", (void*) OpenSSLSessionImpl_getId }, - { "getPeerCertificatesImpl", "(II)[[B", (void*) OpenSSLSessionImpl_getPeerCertificatesImpl }, - { "getProtocol", "(I)Ljava/lang/String;", (void*) OpenSSLSessionImpl_getProtocol }, - { "initializeNativeImpl", "([BI)I", (void*) OpenSSLSessionImpl_initializeNativeImpl }, + { "freeImpl", "(I)V", (void*) OpenSSLSessionImpl_freeImpl }, + { "getCipherSuite", "(I)Ljava/lang/String;", (void*) OpenSSLSessionImpl_getCipherSuite }, + { "getCreationTime", "(I)J", (void*) OpenSSLSessionImpl_getCreationTime }, + { "getEncoded", "(I)[B", (void*) OpenSSLSessionImpl_getEncoded }, + { "getId", "(I)[B", (void*) OpenSSLSessionImpl_getId }, + { "getPeerCertificatesImpl", "(II)[[B", (void*) OpenSSLSessionImpl_getPeerCertificatesImpl }, + { "getProtocol", "(I)Ljava/lang/String;", (void*) OpenSSLSessionImpl_getProtocol }, + { "initializeNativeImpl", "([BI)I", (void*) OpenSSLSessionImpl_initializeNativeImpl }, }; typedef struct { @@ -2388,10 +2612,10 @@ typedef struct { static JNINativeClass sClasses[] = { { "org/apache/harmony/xnet/provider/jsse/NativeCrypto", sNativeCryptoMethods, NELEM(sNativeCryptoMethods) }, { "org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl", sSocketImplMethods, NELEM(sSocketImplMethods) }, - { "org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketImpl", sServerSocketImplMethods, NELEM(sServerSocketImplMethods) }, { "org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl", sSessionImplMethods, NELEM(sSessionImplMethods) }, }; int register_org_apache_harmony_xnet_provider_jsse_NativeCrypto(JNIEnv* env) { + JNI_TRACE("register_org_apache_harmony_xnet_provider_jsse_NativeCrypto"); // Register org.apache.harmony.xnet.provider.jsse.* methods for (int i = 0; i < NELEM(sClasses); i++) { int result = jniRegisterNativeMethods(env, diff --git a/x-net/src/test/java/tests/api/javax/net/ssl/SSLSessionTest.java b/x-net/src/test/java/tests/api/javax/net/ssl/SSLSessionTest.java index 384084f..6f3b61d 100644 --- a/x-net/src/test/java/tests/api/javax/net/ssl/SSLSessionTest.java +++ b/x-net/src/test/java/tests/api/javax/net/ssl/SSLSessionTest.java @@ -197,8 +197,10 @@ public class SSLSessionTest extends TestCase { public void test_getCreationTime() { try { // check if creation time was in the last 10 seconds - long diff = new Date().getTime() - clientSession.getCreationTime(); - assertTrue (diff < 10000); + long currentTime = System.currentTimeMillis(); + long sessionTime = clientSession.getCreationTime(); + long diff = currentTime - sessionTime; + assertTrue("diff between " + currentTime + " and " + sessionTime + " should be < 10000", diff < 10000); } catch (Exception ex) { fail("Unexpected exception " + ex); } @@ -219,6 +221,7 @@ public class SSLSessionTest extends TestCase { try { SSLSession sess = clientSslContext.getClientSessionContext().getSession(id); + assertNotNull("Could not find session for id " + id, sess); assertEquals(clientSession, sess); } catch (Exception ex) { fail("Unexpected exception " + ex); @@ -238,8 +241,11 @@ public class SSLSessionTest extends TestCase { public void test_getLastAccessedTime() { try { // check if last access time was in the last 10 seconds - long diff = new Date().getTime() - clientSession.getLastAccessedTime(); - assertTrue (diff < 10000); + long currentTime = System.currentTimeMillis(); + long sessionTime = clientSession.getLastAccessedTime(); + long diff = currentTime - sessionTime; + assertTrue("diff between " + currentTime + " and " + sessionTime + " should be < 10000", diff < 10000); + assertTrue ("diff should be < 10000 but is " + diff, diff < 10000); } catch (Exception ex) { fail("Unexpected exception " + ex); } @@ -275,7 +281,6 @@ public class SSLSessionTest extends TestCase { method = "getLocalPrincipal", args = {} ) - @KnownFailure("getLocalPrincipal returns null") @AndroidOnly("Uses bks key store. Change useBKS to false to run on the RI") public void test_getLocalPrincipal() { try { |