diff options
Diffstat (limited to 'x-net/src')
7 files changed, 2293 insertions, 2559 deletions
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 508df3a..7698035 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 @@ -38,30 +38,6 @@ public class NativeCrypto { public static native void EVP_PKEY_free(int pkey); - // --- RSA public/private key handling functions --------------------------- - -// public static native int rsaCreatePublicKey(byte[] n, byte[] e); -// -// public static native int rsaCreatePrivateKey(byte[] n, byte[] e, byte[] d, byte[] p, byte[] q); -// -// public static native void rsaDestroyKey(int rsa); - - // --- DSA public/private key handling functions --------------------------- - -// public static native int dsaCreatePublicKey(byte[] p, byte[] q, byte[] g, byte[] pub_key); -// -// public static native int dsaCreatePrivateKey(byte[] p, byte[] q, byte[] g, byte[] priv_key, byte[] pub_key); -// -// public static native void dsaDestroyKey(int dsa); - - // --- RSA public/private key handling functions --------------------------- - -// public static native int rsaCreatePublicKey(byte[] n, byte[] e); -// -// public static native int rsaCreatePrivateKey(byte[] n, byte[] e, byte[] d, byte[] p, byte[] q); -// -// public static native void rsaDestroyKey(int rsa); - // --- General context handling functions (despite the names) -------------- public static native int EVP_new(); 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 69ab7fe..407ca0d 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 The Android Open Source Project + * Copyright (C) 2007-2008 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. @@ -20,12 +20,29 @@ #define LOG_TAG "NativeCrypto" +#include <fcntl.h> +#include <sys/socket.h> +#include <unistd.h> + #include <jni.h> + #include <JNIHelp.h> +#include <LocalArray.h> + +#include <openssl/dsa.h> #include <openssl/err.h> #include <openssl/evp.h> -#include <openssl/dsa.h> +#include <openssl/rand.h> #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; +}; /** * Frees the SSL error state. @@ -76,65 +93,6 @@ static BIGNUM* arrayToBignum(JNIEnv* env, jbyteArray source) { return bignum; } -static void rsaDestroyKey(JNIEnv* env, jclass clazz, RSA* rsa); - -/** - * private static native int rsaCreatePublicKey(byte[] n, byte[] e); - */ -static RSA* rsaCreatePublicKey(JNIEnv* env, jclass clazz, jbyteArray n, jbyteArray e) { - // LOGD("Entering rsaCreatePublicKey()"); - - RSA* rsa = RSA_new(); - - rsa->n = arrayToBignum(env, n); - rsa->e = arrayToBignum(env, e); - - if (rsa->n == NULL || rsa->e == NULL) { - rsaDestroyKey(env, clazz, rsa); - jniThrowRuntimeException(env, "Unable to convert BigInteger to BIGNUM"); - return NULL; - } - - return rsa; -} - -/** - * private static native int rsaCreatePrivateKey(byte[] n, byte[] e, byte[] d, byte[] p, byte[] q); - */ -static RSA* rsaCreatePrivateKey(JNIEnv* env, jclass clazz, jbyteArray n, jbyteArray e, jbyteArray d, jbyteArray p, jbyteArray q) { - // LOGD("Entering rsaCreatePrivateKey()"); - - RSA* rsa = RSA_new(); - - rsa->n = arrayToBignum(env, n); - rsa->e = arrayToBignum(env, e); - rsa->d = arrayToBignum(env, d); - rsa->p = arrayToBignum(env, p); - rsa->q = arrayToBignum(env, q); - - int check = RSA_check_key(rsa); - LOGI("RSA_check_key returns %d", check); - - if (rsa->n == NULL || rsa->e == NULL || rsa->d == NULL || rsa->p == NULL || rsa->q == NULL) { - rsaDestroyKey(env, clazz, rsa); - jniThrowRuntimeException(env, "Unable to convert BigInteger to BIGNUM"); - return NULL; - } - - return rsa; -} - -/** - * private static native void rsaDestroyKey(int rsa); - */ -static void rsaDestroyKey(JNIEnv* env, jclass clazz, RSA* rsa) { - // LOGD("Entering rsaDestroyKey()"); - - if (rsa != NULL) { - RSA_free(rsa); - } -} - /** * private static native int EVP_PKEY_new_DSA(byte[] p, byte[] q, byte[] g, byte[] pub_key, byte[] priv_key); */ @@ -404,15 +362,7 @@ static int NativeCrypto_EVP_VerifyFinal(JNIEnv* env, jclass clazz, EVP_MD_CTX* c * to native functions. Order is (1) Java name, (2) signature, * (3) pointer to C function. */ -static JNINativeMethod methods[] = { -/* - { "dsaCreatePublicKey", "([B[B[B[B)I", (void*)dsaCreatePublicKey }, - { "dsaCreatePrivateKey", "([B[B[B[B[B)I", (void*)dsaCreatePrivateKey }, - { "dsaDestroyKey", "(I)V", (void*)dsaDestroyKey }, - { "rsaCreatePublicKey", "([B[B)I", (void*)rsaCreatePublicKey }, - { "rsaCreatePrivateKey", "([B[B[B[B[B)I", (void*)rsaCreatePrivateKey }, - { "rsaDestroyKey", "(I)V", (void*)rsaDestroyKey }, -*/ +static JNINativeMethod sNativeCryptoMethods[] = { { "EVP_PKEY_new_DSA", "([B[B[B[B[B)I", (void*)NativeCrypto_EVP_PKEY_new_DSA }, { "EVP_PKEY_new_RSA", "([B[B[B[B[B)I", (void*)NativeCrypto_EVP_PKEY_new_RSA }, { "EVP_PKEY_free", "(I)V", (void*)NativeCrypto_EVP_PKEY_free }, @@ -428,6 +378,2181 @@ static JNINativeMethod methods[] = { { "EVP_VerifyFinal", "(I[BIII)I", (void*)NativeCrypto_EVP_VerifyFinal } }; +/** + * Module scope variables initialized during JNI registration. + */ +static jfieldID field_Socket_ssl_ctx; +static jfieldID field_Socket_ssl; +static jfieldID field_FileDescriptor_descriptor; +static jfieldID field_Socket_mImpl; +static jfieldID field_Socket_mFD; +static jfieldID field_Socket_timeout; + +/** + * 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) { + jsize byteCount = env->GetStringUTFLength(string); + LocalArray<1024> buf(byteCount + 1); + env->GetStringUTFRegion(string, 0, env->GetStringLength(string), &buf[0]); + + BIO* result = BIO_new(BIO_s_mem()); + BIO_puts(result, &buf[0]); + return result; +} + +/** + * Throws an SocketTimeoutException with the given string as a message. + */ +static void throwSocketTimeoutException(JNIEnv* env, const char* message) { + if (jniThrowException(env, "java/net/SocketTimeoutException", message)) { + LOGE("Unable to throw"); + } +} + +/** + * Throws a java.io.IOException with the given string as a message. + */ +static void throwIOExceptionStr(JNIEnv* env, const char* message) { + if (jniThrowException(env, "java/io/IOException", message)) { + LOGE("Unable to throw"); + } +} + +/** + * Throws an IOException with a message constructed from the current + * SSL errors. This will also log the errors. + * + * @param env the JNI environment + * @param sslReturnCode return code from failing SSL function + * @param sslErrorCode error code returned from SSL_get_error() + * @param message null-ok; general error message + */ +static void throwIOExceptionWithSslErrors(JNIEnv* env, int sslReturnCode, + int sslErrorCode, const char* message) { + const char* messageStr = NULL; + char* str; + int ret; + + // First consult the SSL error code for the general message. + switch (sslErrorCode) { + case SSL_ERROR_NONE: + messageStr = "Ok"; + break; + case SSL_ERROR_SSL: + messageStr = "Failure in SSL library, usually a protocol error"; + break; + case SSL_ERROR_WANT_READ: + messageStr = "SSL_ERROR_WANT_READ occured. You should never see this."; + break; + case SSL_ERROR_WANT_WRITE: + messageStr = "SSL_ERROR_WANT_WRITE occured. You should never see this."; + break; + case SSL_ERROR_WANT_X509_LOOKUP: + messageStr = "SSL_ERROR_WANT_X509_LOOKUP occured. You should never see this."; + break; + case SSL_ERROR_SYSCALL: + messageStr = "I/O error during system call"; + break; + case SSL_ERROR_ZERO_RETURN: + messageStr = "SSL_ERROR_ZERO_RETURN occured. You should never see this."; + break; + case SSL_ERROR_WANT_CONNECT: + messageStr = "SSL_ERROR_WANT_CONNECT occured. You should never see this."; + break; + case SSL_ERROR_WANT_ACCEPT: + messageStr = "SSL_ERROR_WANT_ACCEPT occured. You should never see this."; + break; + default: + messageStr = "Unknown SSL error"; + } + + // Prepend either our explicit message or a default one. + if (asprintf(&str, "%s: %s", + (message != NULL) ? message : "SSL error", messageStr) == 0) { + throwIOExceptionStr(env, messageStr); + LOGV("%s", messageStr); + freeSslErrorState(); + return; + } + + char* allocStr = str; + + // For SSL protocol errors, SSL might have more information. + if (sslErrorCode == SSL_ERROR_SSL) { + // Append each error as an additional line to the message. + for (;;) { + char errStr[256]; + const char* file; + int line; + const char* data; + int flags; + unsigned long err = + ERR_get_error_line_data(&file, &line, &data, &flags); + if (err == 0) { + break; + } + + ERR_error_string_n(err, errStr, sizeof(errStr)); + + ret = asprintf(&str, "%s\n%s (%s:%d %p:0x%08x)", + (allocStr == NULL) ? "" : allocStr, + errStr, + file, + line, + data, + flags); + LOGD("XXX bdc str %s", str); + + if (ret < 0) { + break; + } + + free(allocStr); + allocStr = str; + } + // For errors during system calls, errno might be our friend. + } else if (sslErrorCode == SSL_ERROR_SYSCALL) { + if (asprintf(&str, "%s, %s", allocStr, strerror(errno)) >= 0) { + free(allocStr); + allocStr = str; + } + // If the error code is invalid, print it. + } else if (sslErrorCode > SSL_ERROR_WANT_ACCEPT) { + if (asprintf(&str, ", error code is %d", sslErrorCode) >= 0) { + free(allocStr); + allocStr = str; + } + } + + throwIOExceptionStr(env, allocStr); + + LOGV("%s", allocStr); + free(allocStr); + freeSslErrorState(); +} + +/** + * Helper function that grabs the ssl pointer out of the given object. + * 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 + * NULL, a caller of this function should simply return and allow JNI + * to do its thing. + * + * @param env non-null; the JNI environment + * @param obj non-null; socket object + * @param throwIfNull whether to throw if the SSL pointer is NULL + * @returns the pointer, which may be NULL + */ +static SSL *getSslPointer(JNIEnv* env, jobject obj, bool throwIfNull) { + SSL *ssl = (SSL *)env->GetIntField(obj, field_Socket_ssl); + + if ((ssl == NULL) && throwIfNull) { + throwIOExceptionStr(env, "null SSL pointer"); + } + + return ssl; +} + +// ============================================================================ +// === OpenSSL-related helper stuff begins here. ============================== +// ============================================================================ + +/** + * OpenSSL locking support. Taken from the O'Reilly book by Viega et al., but I + * suppose there are not many other ways to do this on a Linux system (modulo + * isomorphism). + */ +#define MUTEX_TYPE pthread_mutex_t +#define MUTEX_SETUP(x) pthread_mutex_init(&(x), NULL) +#define MUTEX_CLEANUP(x) pthread_mutex_destroy(&(x)) +#define MUTEX_LOCK(x) pthread_mutex_lock(&(x)) +#define MUTEX_UNLOCK(x) pthread_mutex_unlock(&(x)) +#define THREAD_ID pthread_self() +#define THROW_EXCEPTION (-2) +#define THROW_SOCKETTIMEOUTEXCEPTION (-3) + +static MUTEX_TYPE *mutex_buf = NULL; + +static void locking_function(int mode, int n, const char * file, int line) { + if (mode & CRYPTO_LOCK) { + MUTEX_LOCK(mutex_buf[n]); + } else { + MUTEX_UNLOCK(mutex_buf[n]); + } +} + +static unsigned long id_function(void) { + return ((unsigned long)THREAD_ID); +} + +int THREAD_setup(void) { + int i; + + mutex_buf = (MUTEX_TYPE *)malloc(CRYPTO_num_locks( ) * sizeof(MUTEX_TYPE)); + + if(!mutex_buf) { + return 0; + } + + for (i = 0; i < CRYPTO_num_locks( ); i++) { + MUTEX_SETUP(mutex_buf[i]); + } + + CRYPTO_set_id_callback(id_function); + CRYPTO_set_locking_callback(locking_function); + + return 1; +} + +int THREAD_cleanup(void) { + int i; + + if (!mutex_buf) { + return 0; + } + + CRYPTO_set_id_callback(NULL); + CRYPTO_set_locking_callback(NULL); + + for (i = 0; i < CRYPTO_num_locks( ); i++) { + MUTEX_CLEANUP(mutex_buf[i]); + } + + free(mutex_buf); + mutex_buf = NULL; + + return 1; +} + +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 + +/** + * 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. + */ +typedef struct app_data { + int aliveAndKicking; + int waitingThreads; + int fdsEmergency[2]; + MUTEX_TYPE mutex; +} APP_DATA; + +/** + * 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. + */ +static int sslCreateAppData(SSL* ssl) { + APP_DATA* data = (APP_DATA*) malloc(sizeof(APP_DATA)); + + memset(data, 0, sizeof(APP_DATA)); + + data->aliveAndKicking = 1; + data->waitingThreads = 0; + data->fdsEmergency[0] = -1; + data->fdsEmergency[1] = -1; + + if (pipe(data->fdsEmergency) == -1) { + return -1; + } + + if (MUTEX_SETUP(data->mutex) == -1) { + return -1; + } + + SSL_set_app_data(ssl, (char*) data); + + 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); + + if (data != NULL) { + SSL_set_app_data(ssl, NULL); + + data -> aliveAndKicking = 0; + + if (data->fdsEmergency[0] != -1) { + close(data->fdsEmergency[0]); + } + + if (data->fdsEmergency[1] != -1) { + close(data->fdsEmergency[1]); + } + + MUTEX_CLEANUP(data->mutex); + + free(data); + } +} + + +/** + * Frees the SSL_CTX struct for the given instance. + */ +static void free_ssl_ctx(JNIEnv* env, jobject object) { + /* + * Preserve and restore the exception state around this call, so + * that GetIntField and SetIntField will operate without complaint. + */ + jthrowable exception = env->ExceptionOccurred(); + + if (exception != NULL) { + env->ExceptionClear(); + } + + SSL_CTX *ctx = (SSL_CTX *)env->GetIntField(object, field_Socket_ssl_ctx); + + if (ctx != NULL) { + SSL_CTX_free(ctx); + env->SetIntField(object, field_Socket_ssl_ctx, (int) NULL); + } + + if (exception != NULL) { + env->Throw(exception); + } +} + +/** + * Frees the SSL struct for the given instance. + */ +static void free_ssl(JNIEnv* env, jobject object) { + /* + * Preserve and restore the exception state around this call, so + * that GetIntField and SetIntField will operate without complaint. + */ + jthrowable exception = env->ExceptionOccurred(); + + if (exception != NULL) { + env->ExceptionClear(); + } + + SSL *ssl = (SSL *)env->GetIntField(object, field_Socket_ssl); + + if (ssl != NULL) { + sslDestroyAppData(ssl); + SSL_free(ssl); + env->SetIntField(object, field_Socket_ssl, (int) NULL); + } + + if (exception != NULL) { + env->Throw(exception); + } +} + +/** + * Constructs the SSL struct for the given instance, replacing one + * that was already made, if any. + */ +static SSL* create_ssl(JNIEnv* env, jobject object, SSL_CTX* ssl_ctx) { + free_ssl(env, object); + + SSL *ssl = SSL_new(ssl_ctx); + env->SetIntField(object, field_Socket_ssl, (int) ssl); + return ssl; +} + +/** + * 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. + * + * @param ssl non-null; the SSL context + * @param buf non-null; buffer to read into + * @param len length of the buffer, in bytes + * @param sslReturnCode original SSL return code + * @param sslErrorCode filled in with the SSL error code in case of error + * @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, + int* sslErrorCode, int timeout) { + + // LOGD("Entering sslRead, caller requests to read %d bytes...", len); + + if (len == 0) { + // Don't bother doing anything in this case. + return 0; + } + + int fd = SSL_get_fd(ssl); + BIO *bio = SSL_get_rbio(ssl); + + APP_DATA* data = (APP_DATA*) SSL_get_app_data(ssl); + + while (data->aliveAndKicking) { + errno = 0; + + // Lock + if (MUTEX_LOCK(data->mutex) == -1) { + return -1; + } + + unsigned int bytesMoved = BIO_number_read(bio) + BIO_number_written(bio); + + // LOGD("Doing SSL_Read()"); + int result = SSL_read(ssl, buf, len); + int error = SSL_ERROR_NONE; + if (result <= 0) { + error = SSL_get_error(ssl, result); + freeSslErrorState(); + } + // LOGD("Returned from SSL_Read() with result %d, error code %d", result, error); + + // 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 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++; + } + + // Unlock + MUTEX_UNLOCK(data->mutex); + + switch (error) { + // Sucessfully read at least one byte. + case SSL_ERROR_NONE: { + return result; + } + + // Read zero bytes. End of stream reached. + case SSL_ERROR_ZERO_RETURN: { + return -1; + } + + // 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); + if (selectResult == -1) { + *sslReturnCode = -1; + *sslErrorCode = error; + return THROW_EXCEPTION; + } else if (selectResult == 0) { + return THROW_SOCKETTIMEOUTEXCEPTION; + } + + break; + } + + // A problem occured during a system call, but this is not + // necessarily an error. + case SSL_ERROR_SYSCALL: { + // Connection closed without proper shutdown. Tell caller we + // have reached end-of-stream. + if (result == 0) { + return -1; + } + + // System call has been interrupted. Simply retry. + if (errno == EINTR) { + break; + } + + // Note that for all other system call errors we fall through + // to the default case, which results in an Exception. + } + + // Everything else is basically an error. + default: { + *sslReturnCode = result; + *sslErrorCode = error; + return THROW_EXCEPTION; + } + } + } + + return -1; +} + +/** + * Helper function which does the actual writing. The Java layer guarantees that + * at most one thread will enter this function at any given time. + * + * @param ssl non-null; the SSL context + * @param buf non-null; buffer to write + * @param len length of the buffer, in bytes + * @param sslReturnCode original SSL return code + * @param sslErrorCode filled in with the SSL error code in case of error + * @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, + int* sslErrorCode) { + + // LOGD("Entering sslWrite(), caller requests to write %d bytes...", len); + + if (len == 0) { + // Don't bother doing anything in this case. + return 0; + } + + int fd = SSL_get_fd(ssl); + BIO *bio = SSL_get_wbio(ssl); + + APP_DATA* data = (APP_DATA*) SSL_get_app_data(ssl); + + int count = len; + + while(data->aliveAndKicking && len > 0) { + errno = 0; + if (MUTEX_LOCK(data->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); + int result = SSL_write(ssl, buf, len); + int error = SSL_ERROR_NONE; + if (result <= 0) { + error = SSL_get_error(ssl, result); + freeSslErrorState(); + } + // LOGD("Returned from SSL_write() with result %d, error code %d", result, error); + + // 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 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++; + } + + MUTEX_UNLOCK(data->mutex); + + switch (error) { + // Sucessfully write at least one byte. + case SSL_ERROR_NONE: { + buf += result; + len -= result; + break; + } + + // Wrote zero bytes. End of stream reached. + case SSL_ERROR_ZERO_RETURN: { + return -1; + } + + // Need to wait for availability of underlying layer, then retry. + // The concept of a write timeout doesn't really make sense, and + // 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); + if (selectResult == -1) { + *sslReturnCode = -1; + *sslErrorCode = error; + return THROW_EXCEPTION; + } else if (selectResult == 0) { + return THROW_SOCKETTIMEOUTEXCEPTION; + } + + break; + } + + // An problem occured during a system call, but this is not + // necessarily an error. + case SSL_ERROR_SYSCALL: { + // Connection closed without proper shutdown. Tell caller we + // have reached end-of-stream. + if (result == 0) { + return -1; + } + + // System call has been interrupted. Simply retry. + if (errno == EINTR) { + break; + } + + // Note that for all other system call errors we fall through + // to the default case, which results in an Exception. + } + + // Everything else is basically an error. + default: { + *sslReturnCode = result; + *sslErrorCode = error; + return THROW_EXCEPTION; + } + } + } + // LOGD("Successfully wrote %d bytes", count); + + return count; +} + +/** + * Helper function that creates an RSA public key from two buffers containing + * the big-endian bit representation of the modulus and the public exponent. + * + * @param mod The data of the modulus + * @param modLen The length of the modulus data + * @param exp The data of the exponent + * @param expLen The length of the exponent data + * + * @return A pointer to the new RSA structure, or NULL on error + */ +static RSA* rsaCreateKey(unsigned char* mod, int modLen, unsigned char* exp, int expLen) { + // LOGD("Entering rsaCreateKey()"); + + RSA* rsa = RSA_new(); + + rsa->n = BN_bin2bn((unsigned char*) mod, modLen, NULL); + rsa->e = BN_bin2bn((unsigned char*) exp, expLen, NULL); + + if (rsa->n == NULL || rsa->e == NULL) { + RSA_free(rsa); + return NULL; + } + + return rsa; +} + +/** + * Helper function that frees an RSA key. Just calls the corresponding OpenSSL + * function. + * + * @param rsa The pointer to the new RSA structure to free. + */ +static void rsaFreeKey(RSA* rsa) { + // LOGD("Entering rsaFreeKey()"); + + if (rsa != NULL) { + RSA_free(rsa); + } +} + +/** + * Helper function that verifies a given RSA signature for a given message. + * + * @param msg The message to verify + * @param msgLen The length of the message + * @param sig The signature to verify + * @param sigLen The length of the signature + * @param algorithm The name of the hash/sign algorithm to use, e.g. "RSA-SHA1" + * @param rsa The RSA public key to use + * + * @return 1 on success, 0 on failure, -1 on error (check SSL errors then) + * + */ +static int rsaVerify(unsigned char* msg, unsigned int msgLen, unsigned char* sig, + unsigned int sigLen, char* algorithm, RSA* rsa) { + + // LOGD("Entering rsaVerify(%x, %d, %x, %d, %s, %x)", msg, msgLen, sig, sigLen, algorithm, rsa); + + int result = -1; + + EVP_PKEY* key = EVP_PKEY_new(); + EVP_PKEY_set1_RSA(key, rsa); + + const EVP_MD *type = EVP_get_digestbyname(algorithm); + if (type == NULL) { + goto cleanup; + } + + EVP_MD_CTX ctx; + + EVP_MD_CTX_init(&ctx); + if (EVP_VerifyInit_ex(&ctx, type, NULL) == 0) { + goto cleanup; + } + + EVP_VerifyUpdate(&ctx, msg, msgLen); + result = EVP_VerifyFinal(&ctx, sig, sigLen, key); + EVP_MD_CTX_cleanup(&ctx); + + cleanup: + + if (key != NULL) { + EVP_PKEY_free(key); + } + + return result; +} + +// ============================================================================ +// === OpenSSL-related helper stuff ends here. JNI glue follows. ============== +// ============================================================================ + +/** + * Initialization phase for every OpenSSL job: Loads the Error strings, the + * crypto algorithms and reset the OpenSSL library + */ +static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_initstatic(JNIEnv* env, jobject obj) +{ + SSL_load_error_strings(); + ERR_load_crypto_strings(); + SSL_library_init(); + OpenSSL_add_all_algorithms(); + THREAD_setup(); +} + +/** + * Initialization phase for a socket with OpenSSL. The server's private key + * and X509 certificate are read and the Linux /dev/urandom file is loaded + * as RNG for the session keys. + * + */ +static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_init(JNIEnv* env, jobject object, + jstring privatekey, jstring certificates, jbyteArray seed) +{ + SSL_CTX* ssl_ctx; + + // 'seed == null' when no SecureRandom Object is set + // in the SSLContext. + if (seed != NULL) { + jbyte* randseed = env->GetByteArrayElements(seed, NULL); + RAND_seed((unsigned char*) randseed, 1024); + env->ReleaseByteArrayElements(seed, randseed, 0); + } else { + RAND_load_file("/dev/urandom", 1024); + } + + ssl_ctx = SSL_CTX_new(SSLv23_client_method()); + + // Note: We explicitly do not allow SSLv2 to be used. It + SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2); + + /* Java code in class OpenSSLSocketImpl does the verification. Meaning of + * SSL_VERIFY_NONE flag in client mode: if not using an anonymous cipher + * (by default disabled), the server will send a certificate which will + * be checked. The result of the certificate verification process can be + * checked after the TLS/SSL handshake using the SSL_get_verify_result(3) + * function. The handshake will be continued regardless of the + * verification result. + */ + SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL); + + int mode = SSL_CTX_get_mode(ssl_ctx); + /* + * Turn on "partial write" mode. This means that SSL_write() will + * behave like Posix write() and possibly return after only + * writing a partial buffer. Note: The alternative, perhaps + * surprisingly, is not that SSL_write() always does full writes + * but that it will force you to retry write calls having + * preserved the full state of the original call. (This is icky + * and undesirable.) + */ + mode |= SSL_MODE_ENABLE_PARTIAL_WRITE; +#if defined(SSL_MODE_SMALL_BUFFERS) /* not all SSL versions have this */ + mode |= SSL_MODE_SMALL_BUFFERS; /* lazily allocate record buffers; usually saves + * 44k over the default */ +#endif +#if defined(SSL_MODE_HANDSHAKE_CUTTHROUGH) /* not all SSL versions have this */ + mode |= SSL_MODE_HANDSHAKE_CUTTHROUGH; /* enable sending of client data as soon as + * ClientCCS and ClientFinished are sent */ +#endif + + SSL_CTX_set_mode(ssl_ctx, mode); + + if (privatekey != NULL) { + BIO* privatekeybio = stringToMemBuf(env, (jstring) privatekey); + EVP_PKEY* privatekeyevp = + PEM_read_bio_PrivateKey(privatekeybio, NULL, 0, NULL); + BIO_free(privatekeybio); + + if (privatekeyevp == NULL) { + throwIOExceptionWithSslErrors(env, 0, 0, + "Error parsing the private key"); + SSL_CTX_free(ssl_ctx); + return; + } + + BIO* certificatesbio = stringToMemBuf(env, (jstring) certificates); + X509* certificatesx509 = + PEM_read_bio_X509(certificatesbio, NULL, 0, NULL); + BIO_free(certificatesbio); + + if (certificatesx509 == NULL) { + throwIOExceptionWithSslErrors(env, 0, 0, + "Error parsing the certificates"); + EVP_PKEY_free(privatekeyevp); + SSL_CTX_free(ssl_ctx); + return; + } + + int ret = SSL_CTX_use_certificate(ssl_ctx, certificatesx509); + if (ret != 1) { + throwIOExceptionWithSslErrors(env, ret, 0, + "Error setting the certificates"); + X509_free(certificatesx509); + EVP_PKEY_free(privatekeyevp); + SSL_CTX_free(ssl_ctx); + return; + } + + ret = SSL_CTX_use_PrivateKey(ssl_ctx, privatekeyevp); + if (ret != 1) { + throwIOExceptionWithSslErrors(env, ret, 0, + "Error setting the private key"); + X509_free(certificatesx509); + EVP_PKEY_free(privatekeyevp); + SSL_CTX_free(ssl_ctx); + return; + } + + ret = SSL_CTX_check_private_key(ssl_ctx); + if (ret != 1) { + throwIOExceptionWithSslErrors(env, ret, 0, + "Error checking the private key"); + X509_free(certificatesx509); + EVP_PKEY_free(privatekeyevp); + SSL_CTX_free(ssl_ctx); + return; + } + } + + env->SetIntField(object, field_Socket_ssl_ctx, (int)ssl_ctx); +} + +/** + * 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, jobject object, + jint ctx, jobject socketObject, jboolean client_mode, jint session) +{ + // LOGD("ENTER connect"); + int ret, fd; + SSL_CTX* ssl_ctx; + SSL* ssl; + SSL_SESSION* ssl_session; + + ssl_ctx = (SSL_CTX*)env->GetIntField(object, field_Socket_ssl_ctx); + + ssl = create_ssl(env, object, ssl_ctx); + if (ssl == NULL) { + throwIOExceptionWithSslErrors(env, 0, 0, + "Unable to create SSL structure"); + free_ssl_ctx(env, object); + return (jboolean) false; + } + + jobject socketImplObject = env->GetObjectField(socketObject, field_Socket_mImpl); + if (socketImplObject == NULL) { + free_ssl(env, object); + free_ssl_ctx(env, object); + 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) { + free_ssl(env, object); + free_ssl_ctx(env, object); + throwIOExceptionStr(env, + "couldn't get the file descriptor from the socket impl"); + return (jboolean) false; + } + + fd = jniGetFDFromFileDescriptor(env, fdObject); + + ssl_session = (SSL_SESSION *) session; + + ret = SSL_set_fd(ssl, fd); + + if (ret != 1) { + throwIOExceptionWithSslErrors(env, ret, 0, + "Error setting the file descriptor"); + free_ssl(env, object); + free_ssl_ctx(env, object); + return (jboolean) false; + } + + if (ssl_session != NULL) { + 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"); + free_ssl(env, object); + free_ssl_ctx(env, object); + 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"); + free_ssl(env, object); + free_ssl_ctx(env, object); + return (jboolean) false; + } + + /* + * Create our special application data. + */ + if (sslCreateAppData(ssl) == -1) { + throwIOExceptionStr(env, "Unable to create application data"); + free_ssl(env, object); + free_ssl_ctx(env, object); + // TODO + return (jboolean) false; + } + + APP_DATA* data = (APP_DATA*) SSL_get_app_data(ssl); + env->SetIntField(object, field_Socket_ssl, (int)ssl); + + int timeout = (int)env->GetIntField(object, field_Socket_timeout); + + 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"); + free_ssl(env, object); + free_ssl_ctx(env, object); + return (jboolean) false; + } else if (selectResult == 0) { + throwSocketTimeoutException(env, "SSL handshake timed out"); + freeSslErrorState(); + free_ssl(env, object); + free_ssl_ctx(env, object); + 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"); + free_ssl(env, object); + free_ssl_ctx(env, object); + return (jboolean) false; + } + } + + if (ssl_session != NULL) { + ret = SSL_session_reused(ssl); + // if (ret == 1) LOGD("A session was reused"); + // else LOGD("A new session was negotiated"); + return (jboolean) ret; + } else { + // LOGD("A new session was negotiated"); + return (jboolean) 0; + } + // LOGD("LEAVE connect"); +} + +static jint org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_getsslsession(JNIEnv* env, jobject object, + jint jssl) +{ + return (jint) SSL_get1_session((SSL *) jssl); +} + +static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_accept(JNIEnv* env, jobject object, + jobject socketObject, jint jssl_ctx, jboolean client_mode) +{ + int sd, ret; + BIO *bio; + SSL *ssl; + SSL_CTX *ssl_ctx; + jsse_ssl_app_data_t appdata; + + ssl_ctx = (SSL_CTX *)jssl_ctx; + + ssl = create_ssl(env, object, ssl_ctx); + if (ssl == NULL) { + throwIOExceptionWithSslErrors(env, 0, 0, + "Unable to create SSL structure"); + return; + } + + jobject socketImplObject = env->GetObjectField(socketObject, field_Socket_mImpl); + if (socketImplObject == NULL) { + free_ssl(env, object); + throwIOExceptionStr(env, "couldn't get the socket impl from the socket"); + return; + } + + jobject fdObject = env->GetObjectField(socketImplObject, field_Socket_mFD); + if (fdObject == NULL) { + free_ssl(env, object); + throwIOExceptionStr(env, "couldn't get the file descriptor from the socket impl"); + return; + } + + + sd = jniGetFDFromFileDescriptor(env, fdObject); + + bio = BIO_new_socket(sd, BIO_NOCLOSE); + + /* The parameter client_mode must be 1 */ + if (client_mode != 0) + client_mode = 1; + BIO_set_ssl_mode(bio, client_mode); + + SSL_set_bio(ssl, bio, bio); + + /* + * Fill in the appdata structure needed for the certificate callback and + * store this in the SSL application data slot. + */ + appdata.env = env; + appdata.object = object; + 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. + */ + 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"); + } + free_ssl(env, object); + return; + } 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"); + free_ssl(env, object); + return; + } + + /* + * 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"); + free_ssl(env, object); + return; + } + + /* + * Create our special application data. + */ + if (sslCreateAppData(ssl) == -1) { + throwIOExceptionStr(env, "Unable to create application data"); + free_ssl(env, object); + return; + } +} + +/** + * Loads the desired protocol for the OpenSSL client and enables it. + * For example SSL_OP_NO_TLSv1 means do not use TLS v. 1. + */ +static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_setenabledprotocols(JNIEnv* env, jobject object, + jlong protocol) +{ + if (protocol != 0x00000000L) { + if (protocol & SSL_OP_NO_SSLv3) + LOGD("SSL_OP_NO_SSLv3 is set"); + if (protocol & SSL_OP_NO_TLSv1) + LOGD("SSL_OP_NO_TLSv1 is set"); + + SSL_CTX* ctx = (SSL_CTX*)env->GetIntField(object, field_Socket_ssl_ctx); + int options = SSL_CTX_get_options(ctx); + options |= protocol; // Note: SSLv2 disabled earlier. + SSL_CTX_set_options(ctx, options); + } +} + +static jobjectArray makeCipherList(JNIEnv* env, SSL* ssl) { + // Count the ciphers. + int cipherCount = 0; + while (SSL_get_cipher_list(ssl, cipherCount) != NULL) { + ++cipherCount; + } + + // Create a String[]. + jclass stringClass = env->FindClass("java/lang/String"); + if (stringClass == NULL) { + return NULL; + } + jobjectArray array = env->NewObjectArray(cipherCount, stringClass, NULL); + if (array == NULL) { + return NULL; + } + + // Fill in the cipher names. + for (int i = 0; i < cipherCount; ++i) { + const char* c = SSL_get_cipher_list(ssl, i); + env->SetObjectArrayElement(array, i, env->NewStringUTF(c)); + } + return array; +} + +jobjectArray makeCipherList(JNIEnv* env, SSL_CTX* ssl_ctx) { + SSL* ssl = SSL_new(ssl_ctx); + if (ssl == NULL) { + return NULL; + } + jobjectArray result = makeCipherList(env, ssl); + SSL_free(ssl); + return result; +} + +/** + * Loads the ciphers suites that are supported by the OpenSSL client + * and returns them in a string array. + */ +static jobjectArray org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_getsupportedciphersuites(JNIEnv* env, + jobject object) +{ + SSL_CTX* ssl_ctx = SSL_CTX_new(SSLv23_client_method()); + if (ssl_ctx == NULL) { + return NULL; + } + jobjectArray result = makeCipherList(env, ssl_ctx); + SSL_CTX_free(ssl_ctx); + return result; +} + +/** + * Loads the ciphers suites that are enabled in the OpenSSL client + * and returns them in a string array. + */ +static jobjectArray OpenSSLSocketImpl_nativeGetEnabledCipherSuites(JNIEnv* env, + jclass, jint ssl_ctx_address) +{ + SSL_CTX* ssl_ctx = + reinterpret_cast<SSL_CTX*>(static_cast<uintptr_t>(ssl_ctx_address)); + return makeCipherList(env, ssl_ctx); +} + +void setEnabledCipherSuites(JNIEnv* env, jstring controlString, SSL_CTX* ssl_ctx) { + const char* str = env->GetStringUTFChars(controlString, NULL); + int rc = SSL_CTX_set_cipher_list(ssl_ctx, str); + env->ReleaseStringUTFChars(controlString, str); + if (rc == 0) { + freeSslErrorState(); + jniThrowException(env, "java/lang/IllegalArgumentException", + "Illegal cipher suite strings."); + } +} + +/** + * Sets the ciphers suites that are enabled in the OpenSSL client. + */ +static void OpenSSLSocketImpl_nativeSetEnabledCipherSuites(JNIEnv* env, jclass, + jint ssl_ctx_address, jstring controlString) +{ + SSL_CTX* ssl_ctx = + reinterpret_cast<SSL_CTX*>(static_cast<uintptr_t>(ssl_ctx_address)); + setEnabledCipherSuites(env, controlString, ssl_ctx); +} + +#define SSL_AUTH_MASK 0x00007F00L +#define SSL_aRSA 0x00000100L /* Authenticate with RSA */ +#define SSL_aDSS 0x00000200L /* Authenticate with DSS */ +#define SSL_DSS SSL_aDSS +#define SSL_aFZA 0x00000400L +#define SSL_aNULL 0x00000800L /* no Authenticate, ADH */ +#define SSL_aDH 0x00001000L /* no Authenticate, ADH */ +#define SSL_aKRB5 0x00002000L /* Authenticate with KRB5 */ +#define SSL_aECDSA 0x00004000L /* Authenticate with ECDSA */ + +/** + * Sets the client's crypto algorithms and authentication methods. + */ +static jstring org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_cipherauthenticationmethod(JNIEnv* env, + jobject object) +{ + SSL* ssl; + SSL_CIPHER *cipher; + jstring ret; + char buf[512]; + unsigned long alg; + const char *au; + + ssl = getSslPointer(env, object, true); + if (ssl == NULL) { + return NULL; + } + + cipher = SSL_get_current_cipher(ssl); + + alg = cipher->algorithms; + + switch (alg&SSL_AUTH_MASK) { + case SSL_aRSA: + au="RSA"; + break; + case SSL_aDSS: + au="DSS"; + break; + case SSL_aDH: + au="DH"; + break; + case SSL_aFZA: + au = "FZA"; + break; + case SSL_aNULL: + au="None"; + break; + case SSL_aECDSA: + au="ECDSA"; + break; + default: + au="unknown"; + break; + } + + ret = env->NewStringUTF(au); + + return ret; +} + +/** + * OpenSSL read function (1): only one chunk is read (returned as jint). + */ +static jint org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_read(JNIEnv* env, jobject object, jint timeout) +{ + SSL *ssl = getSslPointer(env, object, true); + if (ssl == NULL) { + return 0; + } + + unsigned char byteRead; + int returnCode = 0; + int errorCode = 0; + + int ret = sslRead(ssl, (char *) &byteRead, 1, &returnCode, &errorCode, timeout); + + switch (ret) { + case THROW_EXCEPTION: + // See sslRead() regarding improper failure to handle normal cases. + throwIOExceptionWithSslErrors(env, returnCode, errorCode, + "Read error"); + return -1; + case THROW_SOCKETTIMEOUTEXCEPTION: + throwSocketTimeoutException(env, "Read timed out"); + return -1; + case -1: + // Propagate EOF upwards. + return -1; + default: + // Return the actual char read, make sure it stays 8 bits wide. + return ((jint) byteRead) & 0xFF; + } +} + +/** + * OpenSSL read function (2): read into buffer at offset n chunks. + * Returns 1 (success) or value <= 0 (failure). + */ +static jint org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_readba(JNIEnv* env, jobject obj, jbyteArray dest, jint offset, jint len, jint timeout) +{ + SSL *ssl = getSslPointer(env, obj, true); + if (ssl == NULL) { + return 0; + } + + jbyte* bytes = env->GetByteArrayElements(dest, NULL); + int returnCode = 0; + int errorCode = 0; + + int ret = + sslRead(ssl, (char*) (bytes + offset), len, &returnCode, &errorCode, timeout); + + env->ReleaseByteArrayElements(dest, bytes, 0); + + if (ret == THROW_EXCEPTION) { + // See sslRead() regarding improper failure to handle normal cases. + throwIOExceptionWithSslErrors(env, returnCode, errorCode, + "Read error"); + return -1; + } else if(ret == THROW_SOCKETTIMEOUTEXCEPTION) { + throwSocketTimeoutException(env, "Read timed out"); + return -1; + } + + return ret; +} + +/** + * OpenSSL write function (1): only one chunk is written. + */ +static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_write(JNIEnv* env, jobject object, jint b) +{ + SSL *ssl = getSslPointer(env, object, true); + if (ssl == NULL) { + return; + } + + int returnCode = 0; + int errorCode = 0; + char buf[1] = { (char) b }; + int ret = sslWrite(ssl, buf, 1, &returnCode, &errorCode); + + if (ret == THROW_EXCEPTION) { + // See sslWrite() regarding improper failure to handle normal cases. + throwIOExceptionWithSslErrors(env, returnCode, errorCode, + "Write error"); + } else if(ret == THROW_SOCKETTIMEOUTEXCEPTION) { + throwSocketTimeoutException(env, "Write timed out"); + } +} + +/** + * OpenSSL write function (2): write into buffer at offset n chunks. + */ +static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_writeba(JNIEnv* env, jobject obj, + jbyteArray dest, jint offset, jint len) +{ + SSL *ssl = getSslPointer(env, obj, true); + if (ssl == NULL) { + return; + } + + jbyte* bytes = env->GetByteArrayElements(dest, NULL); + int returnCode = 0; + int errorCode = 0; + int timeout = (int)env->GetIntField(obj, field_Socket_timeout); + int ret = sslWrite(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, + "Write error"); + } else if(ret == THROW_SOCKETTIMEOUTEXCEPTION) { + throwSocketTimeoutException(env, "Write timed out"); + } +} + +/** + * Interrupt any pending IO before closing the socket. + */ +static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_interrupt( + JNIEnv* env, jobject object) { + SSL *ssl = getSslPointer(env, object, false); + if (ssl == NULL) { + return; + } + + /* + * 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; + + // At most two threads can be waiting. + sslNotify(data); + sslNotify(data); + } +} + +/** + * OpenSSL close SSL socket function. + */ +static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_close( + JNIEnv* env, jobject object) { + SSL *ssl = getSslPointer(env, object, false); + if (ssl == NULL) { + return; + } + + /* + * Try to make socket blocking again. OpenSSL literature recommends this. + */ + int fd = SSL_get_fd(ssl); + 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"); +// LOGW("Unable to make socket blocking again"); + } + } + + int ret = SSL_shutdown(ssl); + switch (ret) { + case 0: + /* + * Shutdown was not successful (yet), but there also + * is no error. Since we can't know whether the remote + * server is actually still there, and we don't want to + * get stuck forever in a second SSL_shutdown() call, we + * simply return. This is not security a problem as long + * as we close the underlying socket, which we actually + * do, because that's where we are just coming from. + */ + break; + case 1: + /* + * Shutdown was sucessful. We can safely return. Hooray! + */ + break; + default: + /* + * Everything else is a real error condition. We should + * let the Java layer know about this by throwing an + * exception. + */ + throwIOExceptionWithSslErrors(env, ret, 0, "SSL shutdown failed."); + break; + } + + freeSslErrorState(); + free_ssl(env, object); + free_ssl_ctx(env, object); +} + +/** + * OpenSSL free SSL socket function. + */ +static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_free(JNIEnv* env, jobject object) +{ + free_ssl(env, object); + free_ssl_ctx(env, object); +} + +/** + * Verifies an RSA signature. + */ +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()"); + + if (msg == NULL || sig == NULL || algorithm == NULL || mod == NULL || exp == NULL) { + jniThrowNullPointerException(env, NULL); + return -1; + } + + int result = -1; + + jbyte* msgBytes = env->GetByteArrayElements(msg, NULL); + jint msgLength = env->GetArrayLength(msg); + + jbyte* sigBytes = env->GetByteArrayElements(sig, NULL); + jint sigLength = env->GetArrayLength(sig); + + jbyte* modBytes = env->GetByteArrayElements(mod, NULL); + jint modLength = env->GetArrayLength(mod); + + jbyte* expBytes = env->GetByteArrayElements(exp, NULL); + jint expLength = env->GetArrayLength(exp); + + const char* algorithmChars = env->GetStringUTFChars(algorithm, NULL); + + RSA* rsa = rsaCreateKey((unsigned char*) modBytes, modLength, (unsigned char*) expBytes, expLength); + if (rsa != NULL) { + result = rsaVerify((unsigned char*) msgBytes, msgLength, (unsigned char*) sigBytes, sigLength, + (char*) algorithmChars, rsa); + rsaFreeKey(rsa); + } + + env->ReleaseStringUTFChars(algorithm, algorithmChars); + + env->ReleaseByteArrayElements(exp, expBytes, JNI_ABORT); + env->ReleaseByteArrayElements(mod, modBytes, JNI_ABORT); + env->ReleaseByteArrayElements(sig, sigBytes, JNI_ABORT); + env->ReleaseByteArrayElements(msg, msgBytes, JNI_ABORT); + + if (result == -1) { + int error = ERR_get_error(); + if (error != 0) { + char message[50]; + ERR_error_string_n(error, message, sizeof(message)); + jniThrowRuntimeException(env, message); + } else { + jniThrowRuntimeException(env, "Internal error during verification"); + } + freeSslErrorState(); + } + + return result; +} + +static JNINativeMethod sSocketImplMethods[] = +{ + {"nativeinitstatic", "()V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_initstatic}, + {"nativeinit", "(Ljava/lang/String;Ljava/lang/String;[B)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_init}, + {"nativeconnect", "(ILjava/net/Socket;ZI)Z", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_connect}, + {"nativegetsslsession", "(I)I", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_getsslsession}, + {"nativeread", "(I)I", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_read}, + {"nativeread", "([BIII)I", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_readba}, + {"nativewrite", "(I)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_write}, + {"nativewrite", "([BII)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_writeba}, + {"nativeaccept", "(Ljava/net/Socket;IZ)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_accept}, + {"nativesetenabledprotocols", "(J)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_setenabledprotocols}, + {"nativegetsupportedciphersuites", "()[Ljava/lang/String;", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_getsupportedciphersuites}, + {"nativeGetEnabledCipherSuites", "(I)[Ljava/lang/String;", (void*) OpenSSLSocketImpl_nativeGetEnabledCipherSuites}, + {"nativeSetEnabledCipherSuites", "(ILjava/lang/String;)V", (void*) OpenSSLSocketImpl_nativeSetEnabledCipherSuites}, + {"nativecipherauthenticationmethod", "()Ljava/lang/String;", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_cipherauthenticationmethod}, + {"nativeinterrupt", "()V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_interrupt}, + {"nativeclose", "()V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_close}, + {"nativefree", "()V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_free}, + {"nativeverifysignature", "([B[BLjava/lang/String;[B[B)I", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_verifysignature}, +}; + +/** + * Module scope variables initialized during JNI registration. + */ +static jfieldID field_ServerSocket_ssl_ctx; + +/** + * Initialization phase of OpenSSL: Loads the Error strings, the crypto algorithms and reset the OpenSSL library + */ +static void org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_initstatic(JNIEnv* env, jobject obj) +{ + SSL_load_error_strings(); + ERR_load_crypto_strings(); + SSL_library_init(); + OpenSSL_add_all_algorithms(); +} + +/** + * Initialization phase for a server socket with OpenSSL. The server's private key and X509 certificate are read and + * the Linux /dev/random file is loaded as RNG for the session keys. + * + */ +static void org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_init(JNIEnv* env, jobject object, + jstring privatekey, jstring certificates, jbyteArray seed) +{ + SSL_CTX *ssl_ctx; + const char *privatekeychar; + const char *certificateschar; + EVP_PKEY * privatekeyevp; + + BIO *privatekeybio; + BIO *certificatesbio; + + // 'seed == null' when no SecureRandom Object is set + // in the SSLContext. + if (seed != NULL) { + jbyte* randseed = env->GetByteArrayElements(seed, NULL); + RAND_seed((unsigned char*) randseed, 1024); + env->ReleaseByteArrayElements(seed, randseed, 0); + } else { + RAND_load_file("/dev/urandom", 1024); + } + + ssl_ctx = SSL_CTX_new(SSLv23_server_method()); + SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL|SSL_OP_NO_SSLv2); + + privatekeychar = env->GetStringUTFChars((jstring)privatekey, NULL); + privatekeybio = BIO_new_mem_buf((void*)privatekeychar, -1); + + privatekeyevp = PEM_read_bio_PrivateKey(privatekeybio, NULL, 0, NULL); + env->ReleaseStringUTFChars(privatekey, privatekeychar); + + if (privatekeyevp == NULL) { + LOGE(ERR_error_string(ERR_get_error(), NULL)); + throwIOExceptionStr(env, "Error parsing the private key"); + return; + } + + certificateschar = env->GetStringUTFChars((jstring)certificates, NULL); + certificatesbio = BIO_new_mem_buf((void*)certificateschar, -1); + + X509 * certificatesx509 = PEM_read_bio_X509(certificatesbio, NULL, 0, NULL); + env->ReleaseStringUTFChars(certificates, certificateschar); + + if (certificatesx509 == NULL) { + LOGE(ERR_error_string(ERR_get_error(), NULL)); + throwIOExceptionStr(env, "Error parsing the certificates"); + return; + } + + if (!SSL_CTX_use_certificate(ssl_ctx, certificatesx509)) { + LOGE(ERR_error_string(ERR_get_error(), NULL)); + throwIOExceptionStr(env, "Error setting the certificates"); + return; + } + + if (!SSL_CTX_use_PrivateKey(ssl_ctx, privatekeyevp)) { + LOGE(ERR_error_string(ERR_get_error(), NULL)); + throwIOExceptionStr(env, "Error setting the private key"); + return; + } + + if (!SSL_CTX_check_private_key(ssl_ctx)) { + LOGE(ERR_error_string(ERR_get_error(), NULL)); + throwIOExceptionStr(env, "Error checking private key"); + return; + } + + env->SetIntField(object, field_ServerSocket_ssl_ctx, (int)ssl_ctx); +} + +/** + * Loads the desired protocol for the OpenSSL server and enables it. + * For example SSL_OP_NO_TLSv1 means do not use TLS v. 1. + */ +static void org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_setenabledprotocols(JNIEnv* env, + jobject object, jlong protocol) +{ + if (protocol != 0x00000000L) { + if (protocol & SSL_OP_NO_SSLv3) + LOGD("SSL_OP_NO_SSLv3 is set"); + if (protocol & SSL_OP_NO_TLSv1) + LOGD("SSL_OP_NO_TLSv1 is set"); + + SSL_CTX* ctx = (SSL_CTX*)env->GetIntField(object, field_ServerSocket_ssl_ctx); + SSL_CTX_set_options((SSL_CTX*)ctx, SSL_OP_ALL|SSL_OP_NO_SSLv2|(long)protocol); + } +} + +/** + * Loads the ciphers suites that are supported by the OpenSSL server + * and returns them in a string array. + */ +static jobjectArray org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_getsupportedciphersuites(JNIEnv* env, + jobject object) +{ + SSL_CTX* ssl_ctx = SSL_CTX_new(SSLv23_server_method()); + if (ssl_ctx == NULL) { + return NULL; + } + jobjectArray result = makeCipherList(env, ssl_ctx); + SSL_CTX_free(ssl_ctx); + return result; +} + +/** + * 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) +{ + SSL *ssl; + jsse_ssl_app_data_t *appdata; + jclass cls; + + jobjectArray objectArray; + + /* Get the correct index to the SSLobject stored into X509_STORE_CTX. */ + ssl = (SSL*)X509_STORE_CTX_get_ex_data(x509_store_ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); + + appdata = (jsse_ssl_app_data_t*)SSL_get_app_data(ssl); + + cls = appdata->env->GetObjectClass(appdata->object); + + jmethodID methodID = appdata->env->GetMethodID(cls, "verify_callback", "([[B)I"); + + objectArray = getcertificatebytes(appdata->env, x509_store_ctx->untrusted); + + appdata->env->CallIntMethod(appdata->object, methodID, objectArray); + + return 1; +} + +/** + * Sets the client's credentials and the depth of theirs verification. + */ +static void org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_nativesetclientauth(JNIEnv* env, + jobject object, jint value) +{ + SSL_CTX *ssl_ctx = (SSL_CTX *)env->GetIntField(object, field_ServerSocket_ssl_ctx); + SSL_CTX_set_verify(ssl_ctx, (int)value, verify_callback); +} + +/** + * The actual SSL context is reset. + */ +static void org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_nativefree(JNIEnv* env, jobject object) +{ + SSL_CTX *ctx = (SSL_CTX *)env->GetIntField(object, field_ServerSocket_ssl_ctx); + SSL_CTX_free(ctx); + env->SetIntField(object, field_ServerSocket_ssl_ctx, 0); +} + +static JNINativeMethod sServerSocketImplMethods[] = +{ + {"nativeinitstatic", "()V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_initstatic}, + {"nativeinit", "(Ljava/lang/String;Ljava/lang/String;[B)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_init}, + {"nativesetenabledprotocols", "(J)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_setenabledprotocols}, + {"nativegetsupportedciphersuites", "()[Ljava/lang/String;", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_getsupportedciphersuites}, + {"nativesetclientauth", "(I)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_nativesetclientauth}, + {"nativefree", "()V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_nativefree} +}; + +static jfieldID field_Session_session; + +static SSL_SESSION* getSslSessionPointer(JNIEnv* env, jobject object) { + return reinterpret_cast<SSL_SESSION*>(env->GetIntField(object, field_Session_session)); +} + +// Fills a byte[][] with the peer certificates in the chain. +static jobjectArray OpenSSLSessionImpl_getPeerCertificatesImpl(JNIEnv* env, + jobject object, jint jssl) +{ + SSL_SESSION* ssl_session = getSslSessionPointer(env, object); + SSL_CTX* ssl_ctx = SSL_CTX_new(SSLv23_client_method()); + SSL* ssl = SSL_new(ssl_ctx); + + SSL_set_session(ssl, ssl_session); + + STACK_OF(X509)* chain = SSL_get_peer_cert_chain(ssl); + jobjectArray objectArray = getcertificatebytes(env, chain); + + SSL_free(ssl); + SSL_CTX_free(ssl_ctx); + return objectArray; +} + +/** + * Serializes the native state of the session (ID, cipher, and keys but + * not certificates). Returns a byte[] containing the DER-encoded state. + * See apache mod_ssl. + */ +static jbyteArray OpenSSLSessionImpl_getEncoded(JNIEnv* env, jobject object) { + SSL_SESSION* ssl_session = getSslSessionPointer(env, object); + if (ssl_session == NULL) { + return NULL; + } + + // Compute the size of the DER data + int size = i2d_SSL_SESSION(ssl_session, NULL); + if (size == 0) { + return NULL; + } + + jbyteArray bytes = env->NewByteArray(size); + if (bytes != NULL) { + jbyte* tmp = env->GetByteArrayElements(bytes, NULL); + unsigned char* ucp = reinterpret_cast<unsigned char*>(tmp); + i2d_SSL_SESSION(ssl_session, &ucp); + env->ReleaseByteArrayElements(bytes, tmp, 0); + } + + return bytes; +} + +/** + * Deserialize the session. + */ +static jint OpenSSLSessionImpl_initializeNativeImpl(JNIEnv* env, jobject object, jbyteArray bytes, jint size) { + if (bytes == NULL) { + return 0; + } + + jbyte* tmp = env->GetByteArrayElements(bytes, NULL); + const unsigned char* ucp = reinterpret_cast<const unsigned char*>(tmp); + SSL_SESSION* ssl_session = d2i_SSL_SESSION(NULL, &ucp, size); + env->ReleaseByteArrayElements(bytes, tmp, 0); + + return static_cast<jint>(reinterpret_cast<uintptr_t>(ssl_session)); +} + +/** + * Gets and returns in a byte array the ID of the actual SSL session. + */ +static jbyteArray OpenSSLSessionImpl_getId(JNIEnv* env, jobject object) { + SSL_SESSION* ssl_session = getSslSessionPointer(env, object); + + 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); + } + + return result; +} + +/** + * Gets and returns in a long integer the creation's time of the + * actual SSL session. + */ +static jlong OpenSSLSessionImpl_getCreationTime(JNIEnv* env, jobject object) { + SSL_SESSION* ssl_session = getSslSessionPointer(env, object); + jlong result = SSL_SESSION_get_time(ssl_session); + result *= 1000; // OpenSSL uses seconds, Java uses milliseconds. + return result; +} + +/** + * Gets and returns in a string the version of the SSL protocol. If it + * returns the string "unknown" it means that no connection is established. + */ +static jstring OpenSSLSessionImpl_getProtocol(JNIEnv* env, jobject object) { + SSL_SESSION* ssl_session = getSslSessionPointer(env, object); + SSL_CTX* ssl_ctx = SSL_CTX_new(SSLv23_client_method()); + SSL* ssl = SSL_new(ssl_ctx); + + SSL_set_session(ssl, ssl_session); + + const char* protocol = SSL_get_version(ssl); + jstring result = env->NewStringUTF(protocol); + + SSL_free(ssl); + SSL_CTX_free(ssl_ctx); + return result; +} + +/** + * Gets and returns in a string the set of ciphers the actual SSL session uses. + */ +static jstring OpenSSLSessionImpl_getCipherSuite(JNIEnv* env, jobject object) { + SSL_SESSION* ssl_session = getSslSessionPointer(env, object); + SSL_CTX* ssl_ctx = SSL_CTX_new(SSLv23_client_method()); + SSL* ssl = SSL_new(ssl_ctx); + + SSL_set_session(ssl, ssl_session); + + SSL_CIPHER* cipher = SSL_get_current_cipher(ssl); + jstring result = env->NewStringUTF(SSL_CIPHER_get_name(cipher)); + + SSL_free(ssl); + SSL_CTX_free(ssl_ctx); + return result; +} + +/** + * Frees the SSL session. + */ +static void OpenSSLSessionImpl_freeImpl(JNIEnv* env, jobject object, jint session) { + LOGD("Freeing OpenSSL session"); + SSL_SESSION* ssl_session = reinterpret_cast<SSL_SESSION*>(session); + SSL_SESSION_free(ssl_session); +} + +static JNINativeMethod sSessionImplMethods[] = { + { "freeImpl", "(I)V", (void*) OpenSSLSessionImpl_freeImpl }, + { "getCipherSuite", "()Ljava/lang/String;", (void*) OpenSSLSessionImpl_getCipherSuite }, + { "getCreationTime", "()J", (void*) OpenSSLSessionImpl_getCreationTime }, + { "getEncoded", "()[B", (void*) OpenSSLSessionImpl_getEncoded }, + { "getId", "()[B", (void*) OpenSSLSessionImpl_getId }, + { "getPeerCertificatesImpl", "()[[B", (void*) OpenSSLSessionImpl_getPeerCertificatesImpl }, + { "getProtocol", "()Ljava/lang/String;", (void*) OpenSSLSessionImpl_getProtocol }, + { "initializeNativeImpl", "([BI)I", (void*) OpenSSLSessionImpl_initializeNativeImpl } +}; + +typedef struct { + const char* name; + const JNINativeMethod* methods; + jint nMethods; +} JNINativeClass; + +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) }, +}; + /* * Peforms the actual registration of the native methods. * Also looks up the fields that belong to the class (if @@ -435,17 +2560,106 @@ static JNINativeMethod methods[] = { * don't need. */ extern "C" int register_org_apache_harmony_xnet_provider_jsse_NativeCrypto(JNIEnv* env) { - int result; - result = jniRegisterNativeMethods(env, "org/apache/harmony/xnet/provider/jsse/NativeCrypto", methods, NELEM(methods)); - if (result == -1) { + + // Register org.apache.harmony.xnet.provider.jsse.* methods + for (int i = 0; i < NELEM(sClasses); i++) { + int result = jniRegisterNativeMethods(env, + sClasses[i].name, + sClasses[i].methods, + sClasses[i].nMethods); + if (result == -1) { + return -1; + } + } + + // java.io.FileDescriptor + jclass fileDescriptor = env->FindClass("java/io/FileDescriptor"); + if (fileDescriptor == NULL) { + LOGE("Can't find java/io/FileDescriptor"); + return -1; + } + field_FileDescriptor_descriptor = env->GetFieldID(fileDescriptor, "descriptor", "I"); + if (field_FileDescriptor_descriptor == NULL) { + LOGE("Can't find FileDescriptor.descriptor"); + return -1; + } + + // java.net.Socket + jclass socket = env->FindClass("java/net/Socket"); + if (socket == NULL) { + LOGE("Can't find class java.net.Socket"); + return -1; + } + field_Socket_mImpl = env->GetFieldID(socket, "impl", "Ljava/net/SocketImpl;"); + if (field_Socket_mImpl == NULL) { + LOGE("Can't find field impl in class java.net.Socket"); + return -1; + } + + // java.net.SocketImpl + jclass socketImplClass = env->FindClass("java/net/SocketImpl"); + if (socketImplClass == NULL) { + LOGE("Can't find class java.net.SocketImpl"); + return -1; + } + field_Socket_mFD = env->GetFieldID(socketImplClass, "fd", "Ljava/io/FileDescriptor;"); + if (field_Socket_mFD == NULL) { + LOGE("Can't find field fd in java.net.SocketImpl"); return -1; } - jclass clazz; - clazz = env->FindClass("org/apache/harmony/xnet/provider/jsse/NativeCrypto"); - if (clazz == NULL) { + // org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl + jclass socketImpl = env->FindClass("org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl"); + if (socketImpl == NULL) { + LOGE("Can't find org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl"); return -1; } + // Note: do these after the registration of native methods, because + // there is a static method "initstatic" that's called when the + // OpenSSLSocketImpl class is first loaded, and that required + // a native method to be associated with it. + field_Socket_ssl_ctx = env->GetFieldID(socketImpl, "ssl_ctx", "I"); + if (field_Socket_ssl_ctx == NULL) { + LOGE("Can't find OpenSSLSocketImpl.ssl_ctx"); + return -1; + } + field_Socket_ssl = env->GetFieldID(socketImpl, "ssl", "I"); + if (field_Socket_ssl == NULL) { + LOGE("Can't find OpenSSLSocketImpl.ssl"); + return -1; + } + field_Socket_timeout = env->GetFieldID(socketImpl, "timeout", "I"); + if (field_Socket_timeout == NULL) { + LOGE("Can't find OpenSSLSocketImpl.timeout"); + return -1; + } + + // org.apache.harmony.xnet.provider.jsse.OpenSSLServerSocketImpl + jclass serverSocketImpl = env->FindClass("org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketImpl"); + if (serverSocketImpl == NULL) { + LOGE("Can't find org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketImpl"); + return -1; + } + // Note: do these after the registration of native methods, because + // there is a static method "initstatic" that's called when the + // OpenSSLServerSocketImpl class is first loaded, and that required + // a native method to be associated with it. + field_ServerSocket_ssl_ctx = env->GetFieldID(serverSocketImpl, "ssl_ctx", "I"); + if (field_ServerSocket_ssl_ctx == NULL) { + LOGE("Can't find OpenSSLServerSocketImpl.ssl_ctx"); + return -1; + } + + // org.apache.harmony.xnet.provider.jsse.OpenSSLSessionImpl + jclass sessionImpl = env->FindClass("org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl"); + if (sessionImpl == NULL) { + return -1; + } + field_Session_session = env->GetFieldID(sessionImpl, "session", "I"); + if (field_Session_session == NULL) { + LOGE("Can't find OpenSSLSessionImpl.session"); + return -1; + } return 0; } diff --git a/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl.cpp b/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl.cpp deleted file mode 100644 index 1e73041..0000000 --- a/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl.cpp +++ /dev/null @@ -1,227 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - -#define LOG_TAG "OpenSSLServerSocketImpl" - -#include <jni.h> -#include <JNIHelp.h> - -#include <stdio.h> -#include <string.h> -#include <sys/types.h> -#include <sys/socket.h> -#include <arpa/inet.h> -#include <netinet/in.h> -#include <stdlib.h> -#include <errno.h> -#include <unistd.h> - -#include <openssl/err.h> -#include <openssl/rand.h> -#include <openssl/ssl.h> - -#include "org_apache_harmony_xnet_provider_jsse_common.h" - -/** - * Module scope variables initialized during JNI registration. - */ -static jfieldID field_ssl_ctx; - -/** - * Throws java.io.IOException with the provided message. - */ -static void throwIOExceptionStr(JNIEnv* env, const char* message) { - jniThrowException(env, "java/io/IOException", message); -} - -/** - * Initialization phase of OpenSSL: Loads the Error strings, the crypto algorithms and reset the OpenSSL library - */ -static void org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_initstatic(JNIEnv* env, jobject obj) -{ - SSL_load_error_strings(); - ERR_load_crypto_strings(); - SSL_library_init(); - OpenSSL_add_all_algorithms(); -} - -/** - * Initialization phase for a server socket with OpenSSL. The server's private key and X509 certificate are read and - * the Linux /dev/random file is loaded as RNG for the session keys. - * - */ -static void org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_init(JNIEnv* env, jobject object, - jstring privatekey, jstring certificates, jbyteArray seed) -{ - SSL_CTX *ssl_ctx; - const char *privatekeychar; - const char *certificateschar; - EVP_PKEY * privatekeyevp; - - BIO *privatekeybio; - BIO *certificatesbio; - - // 'seed == null' when no SecureRandom Object is set - // in the SSLContext. - if (seed != NULL) { - jbyte* randseed = env->GetByteArrayElements(seed, NULL); - RAND_seed((unsigned char*) randseed, 1024); - env->ReleaseByteArrayElements(seed, randseed, 0); - } else { - RAND_load_file("/dev/urandom", 1024); - } - - ssl_ctx = SSL_CTX_new(SSLv23_server_method()); - SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL|SSL_OP_NO_SSLv2); - - privatekeychar = env->GetStringUTFChars((jstring)privatekey, NULL); - privatekeybio = BIO_new_mem_buf((void*)privatekeychar, -1); - - privatekeyevp = PEM_read_bio_PrivateKey(privatekeybio, NULL, 0, NULL); - env->ReleaseStringUTFChars(privatekey, privatekeychar); - - if (privatekeyevp == NULL) { - LOGE(ERR_error_string(ERR_get_error(), NULL)); - throwIOExceptionStr(env, "Error parsing the private key"); - return; - } - - certificateschar = env->GetStringUTFChars((jstring)certificates, NULL); - certificatesbio = BIO_new_mem_buf((void*)certificateschar, -1); - - X509 * certificatesx509 = PEM_read_bio_X509(certificatesbio, NULL, 0, NULL); - env->ReleaseStringUTFChars(certificates, certificateschar); - - if (certificatesx509 == NULL) { - LOGE(ERR_error_string(ERR_get_error(), NULL)); - throwIOExceptionStr(env, "Error parsing the certificates"); - return; - } - - if (!SSL_CTX_use_certificate(ssl_ctx, certificatesx509)) { - LOGE(ERR_error_string(ERR_get_error(), NULL)); - throwIOExceptionStr(env, "Error setting the certificates"); - return; - } - - if (!SSL_CTX_use_PrivateKey(ssl_ctx, privatekeyevp)) { - LOGE(ERR_error_string(ERR_get_error(), NULL)); - throwIOExceptionStr(env, "Error setting the private key"); - return; - } - - if (!SSL_CTX_check_private_key(ssl_ctx)) { - LOGE(ERR_error_string(ERR_get_error(), NULL)); - throwIOExceptionStr(env, "Error checking private key"); - return; - } - - env->SetIntField(object, field_ssl_ctx, (int)ssl_ctx); -} - -/** - * Loads the desired protocol for the OpenSSL server and enables it. - * For example SSL_OP_NO_TLSv1 means do not use TLS v. 1. - */ -static void org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_setenabledprotocols(JNIEnv* env, - jobject object, jlong protocol) -{ - if (protocol != 0x00000000L) { - if (protocol & SSL_OP_NO_SSLv3) - LOGD("SSL_OP_NO_SSLv3 is set"); - if (protocol & SSL_OP_NO_TLSv1) - LOGD("SSL_OP_NO_TLSv1 is set"); - - SSL_CTX* ctx = (SSL_CTX*)env->GetIntField(object, field_ssl_ctx); - SSL_CTX_set_options((SSL_CTX*)ctx, SSL_OP_ALL|SSL_OP_NO_SSLv2|(long)protocol); - } -} - -/** - * Loads the ciphers suites that are supported by the OpenSSL server - * and returns them in a string array. - */ -static jobjectArray org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_getsupportedciphersuites(JNIEnv* env, - jobject object) -{ - SSL_CTX* ssl_ctx = SSL_CTX_new(SSLv23_server_method()); - if (ssl_ctx == NULL) { - return NULL; - } - jobjectArray result = makeCipherList(env, ssl_ctx); - SSL_CTX_free(ssl_ctx); - return result; -} - -/** - * Sets the client's credentials and the depth of theirs verification. - */ -static void org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_nativesetclientauth(JNIEnv* env, - jobject object, jint value) -{ - SSL_CTX *ssl_ctx = (SSL_CTX *)env->GetIntField(object, field_ssl_ctx); - SSL_CTX_set_verify(ssl_ctx, (int)value, verify_callback); -} - -/** - * The actual SSL context is reset. - */ -static void org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_nativefree(JNIEnv* env, jobject object) -{ - SSL_CTX *ctx = (SSL_CTX *)env->GetIntField(object, field_ssl_ctx); - SSL_CTX_free(ctx); - env->SetIntField(object, field_ssl_ctx, 0); -} - -/** - * The actual JNI methods' mapping table for the class OpenSSLServerSocketImpl. - */ -static JNINativeMethod sMethods[] = -{ - {"nativeinitstatic", "()V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_initstatic}, - {"nativeinit", "(Ljava/lang/String;Ljava/lang/String;[B)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_init}, - {"nativesetenabledprotocols", "(J)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_setenabledprotocols}, - {"nativegetsupportedciphersuites", "()[Ljava/lang/String;", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_getsupportedciphersuites}, - {"nativesetclientauth", "(I)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_nativesetclientauth}, - {"nativefree", "()V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl_nativefree} -}; - -/** - * Register the native methods with JNI for the class OpenSSLServerSocketImpl. - */ -extern "C" int register_org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl(JNIEnv* env) -{ - jclass clazz = env->FindClass("org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketImpl"); - if (clazz == NULL) { - LOGE("Can't find org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketImpl"); - return -1; - } - - int rc = jniRegisterNativeMethods(env, "org/apache/harmony/xnet/provider/jsse/OpenSSLServerSocketImpl", - sMethods, NELEM(sMethods)); - if (rc >= 0) { - // Note: do these after the registration of native methods, because - // there is a static method "initstatic" that's called when the - // OpenSSLServerSocketImpl class is first loaded, and that required - // a native method to be associated with it. - field_ssl_ctx = env->GetFieldID(clazz, "ssl_ctx", "I"); - if (field_ssl_ctx == NULL) { - LOGE("Can't find OpenSSLServerSocketImpl.ssl_ctx"); - return -1; - } - } - return rc; -} diff --git a/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl.cpp b/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl.cpp deleted file mode 100644 index e0ea0b8..0000000 --- a/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl.cpp +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - -#define LOG_TAG "OpenSSLSessionImpl" - -#include "AndroidSystemNatives.h" -#include "JNIHelp.h" - -#include <jni.h> - -#include <stdio.h> -#include <string.h> -#include <sys/types.h> -#include <sys/socket.h> -#include <arpa/inet.h> -#include <netinet/in.h> -#include <stdlib.h> -#include <errno.h> -#include <unistd.h> - -#include <openssl/err.h> -#include <openssl/rand.h> -#include <openssl/ssl.h> - -#include "org_apache_harmony_xnet_provider_jsse_common.h" - -static jfieldID field_session; - -static SSL_SESSION* getSslSessionPointer(JNIEnv* env, jobject object) { - return reinterpret_cast<SSL_SESSION*>(env->GetIntField(object, field_session)); -} - -// Fills a byte[][] with the peer certificates in the chain. -static jobjectArray OpenSSLSessionImpl_getPeerCertificatesImpl(JNIEnv* env, - jobject object, jint jssl) -{ - SSL_SESSION* ssl_session = getSslSessionPointer(env, object); - SSL_CTX* ssl_ctx = SSL_CTX_new(SSLv23_client_method()); - SSL* ssl = SSL_new(ssl_ctx); - - SSL_set_session(ssl, ssl_session); - - STACK_OF(X509)* chain = SSL_get_peer_cert_chain(ssl); - jobjectArray objectArray = getcertificatebytes(env, chain); - - SSL_free(ssl); - SSL_CTX_free(ssl_ctx); - return objectArray; -} - -/** - * Serializes the native state of the session (ID, cipher, and keys but - * not certificates). Returns a byte[] containing the DER-encoded state. - * See apache mod_ssl. - */ -static jbyteArray OpenSSLSessionImpl_getEncoded(JNIEnv* env, jobject object) { - SSL_SESSION* ssl_session = getSslSessionPointer(env, object); - if (ssl_session == NULL) { - return NULL; - } - - // Compute the size of the DER data - int size = i2d_SSL_SESSION(ssl_session, NULL); - if (size == 0) { - return NULL; - } - - jbyteArray bytes = env->NewByteArray(size); - if (bytes != NULL) { - jbyte* tmp = env->GetByteArrayElements(bytes, NULL); - unsigned char* ucp = reinterpret_cast<unsigned char*>(tmp); - i2d_SSL_SESSION(ssl_session, &ucp); - env->ReleaseByteArrayElements(bytes, tmp, 0); - } - - return bytes; -} - -/** - * Deserialize the session. - */ -static jint OpenSSLSessionImpl_initializeNativeImpl(JNIEnv* env, jobject object, jbyteArray bytes, jint size) { - if (bytes == NULL) { - return 0; - } - - jbyte* tmp = env->GetByteArrayElements(bytes, NULL); - const unsigned char* ucp = reinterpret_cast<const unsigned char*>(tmp); - SSL_SESSION* ssl_session = d2i_SSL_SESSION(NULL, &ucp, size); - env->ReleaseByteArrayElements(bytes, tmp, 0); - - return static_cast<jint>(reinterpret_cast<uintptr_t>(ssl_session)); -} - -/** - * Gets and returns in a byte array the ID of the actual SSL session. - */ -static jbyteArray OpenSSLSessionImpl_getId(JNIEnv* env, jobject object) { - SSL_SESSION* ssl_session = getSslSessionPointer(env, object); - - 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); - } - - return result; -} - -/** - * Gets and returns in a long integer the creation's time of the - * actual SSL session. - */ -static jlong OpenSSLSessionImpl_getCreationTime(JNIEnv* env, jobject object) { - SSL_SESSION* ssl_session = getSslSessionPointer(env, object); - jlong result = SSL_SESSION_get_time(ssl_session); - result *= 1000; // OpenSSL uses seconds, Java uses milliseconds. - return result; -} - -/** - * Gets and returns in a string the version of the SSL protocol. If it - * returns the string "unknown" it means that no connection is established. - */ -static jstring OpenSSLSessionImpl_getProtocol(JNIEnv* env, jobject object) { - SSL_SESSION* ssl_session = getSslSessionPointer(env, object); - SSL_CTX* ssl_ctx = SSL_CTX_new(SSLv23_client_method()); - SSL* ssl = SSL_new(ssl_ctx); - - SSL_set_session(ssl, ssl_session); - - const char* protocol = SSL_get_version(ssl); - jstring result = env->NewStringUTF(protocol); - - SSL_free(ssl); - SSL_CTX_free(ssl_ctx); - return result; -} - -/** - * Gets and returns in a string the set of ciphers the actual SSL session uses. - */ -static jstring OpenSSLSessionImpl_getCipherSuite(JNIEnv* env, jobject object) { - SSL_SESSION* ssl_session = getSslSessionPointer(env, object); - SSL_CTX* ssl_ctx = SSL_CTX_new(SSLv23_client_method()); - SSL* ssl = SSL_new(ssl_ctx); - - SSL_set_session(ssl, ssl_session); - - SSL_CIPHER* cipher = SSL_get_current_cipher(ssl); - jstring result = env->NewStringUTF(SSL_CIPHER_get_name(cipher)); - - SSL_free(ssl); - SSL_CTX_free(ssl_ctx); - return result; -} - -/** - * Frees the SSL session. - */ -static void OpenSSLSessionImpl_freeImpl(JNIEnv* env, jobject object, jint session) { - LOGD("Freeing OpenSSL session"); - SSL_SESSION* ssl_session = reinterpret_cast<SSL_SESSION*>(session); - SSL_SESSION_free(ssl_session); -} - -static JNINativeMethod sMethods[] = { - { "freeImpl", "(I)V", (void*) OpenSSLSessionImpl_freeImpl }, - { "getCipherSuite", "()Ljava/lang/String;", (void*) OpenSSLSessionImpl_getCipherSuite }, - { "getCreationTime", "()J", (void*) OpenSSLSessionImpl_getCreationTime }, - { "getEncoded", "()[B", (void*) OpenSSLSessionImpl_getEncoded }, - { "getId", "()[B", (void*) OpenSSLSessionImpl_getId }, - { "getPeerCertificatesImpl", "()[[B", (void*) OpenSSLSessionImpl_getPeerCertificatesImpl }, - { "getProtocol", "()Ljava/lang/String;", (void*) OpenSSLSessionImpl_getProtocol }, - { "initializeNativeImpl", "([BI)I", (void*) OpenSSLSessionImpl_initializeNativeImpl } -}; - -int register_org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl(JNIEnv* env) { - jclass clazz = env->FindClass("org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl"); - if (clazz == NULL) { - return -1; - } - - field_session = env->GetFieldID(clazz, "session", "I"); - - return jniRegisterNativeMethods(env, "org/apache/harmony/xnet/provider/jsse/OpenSSLSessionImpl", - sMethods, NELEM(sMethods)); -} diff --git a/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl.cpp b/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl.cpp deleted file mode 100644 index a3c86d6..0000000 --- a/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl.cpp +++ /dev/null @@ -1,1902 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - -#define LOG_TAG "OpenSSLSocketImpl" - -#include "JNIHelp.h" -#include "LocalArray.h" -#include "cutils/log.h" -#include "jni.h" - -#include <stdio.h> -#include <string.h> -#include <sys/types.h> -#include <sys/socket.h> -#include <arpa/inet.h> -#include <netinet/in.h> -#include <stdlib.h> -#include <errno.h> -#include <unistd.h> -#include <fcntl.h> -#include <sys/select.h> - -#include <openssl/err.h> -#include <openssl/rand.h> -#include <openssl/ssl.h> - -#include "org_apache_harmony_xnet_provider_jsse_common.h" - -/** - * Module scope variables initialized during JNI registration. - */ -static jfieldID field_ssl_ctx; -static jfieldID field_ssl; -static jfieldID field_descriptor; -static jfieldID field_mImpl; -static jfieldID field_mFD; -static jfieldID field_timeout; - -/** - * 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) { - jsize byteCount = env->GetStringUTFLength(string); - LocalArray<1024> buf(byteCount + 1); - env->GetStringUTFRegion(string, 0, env->GetStringLength(string), &buf[0]); - - BIO* result = BIO_new(BIO_s_mem()); - BIO_puts(result, &buf[0]); - return result; -} - -/** - * Throws an SocketTimeoutException with the given string as a message. - */ -static void throwSocketTimeoutException(JNIEnv* env, const char* message) { - if (jniThrowException(env, "java/net/SocketTimeoutException", message)) { - LOGE("Unable to throw"); - } -} - -/** - * Throws an IOException with the given string as a message. - */ -static void throwIOExceptionStr(JNIEnv* env, const char* message) { - if (jniThrowException(env, "java/io/IOException", message)) { - LOGE("Unable to throw"); - } -} - -/** - * Frees the SSL error state. - * - * OpenSSL keeps an "error stack" per thread, and given that this code - * can be called from arbitrary threads that we don't keep track of, - * we err on the side of freeing the error state promptly (instead of, - * say, at thread death). - */ -static void freeSslErrorState(void) { - ERR_clear_error(); - ERR_remove_state(0); -} - -/** - * Throws an IOException with a message constructed from the current - * SSL errors. This will also log the errors. - * - * @param env the JNI environment - * @param sslReturnCode return code from failing SSL function - * @param sslErrorCode error code returned from SSL_get_error() - * @param message null-ok; general error message - */ -static void throwIOExceptionWithSslErrors(JNIEnv* env, int sslReturnCode, - int sslErrorCode, const char* message) { - const char* messageStr = NULL; - char* str; - int ret; - - // First consult the SSL error code for the general message. - switch (sslErrorCode) { - case SSL_ERROR_NONE: - messageStr = "Ok"; - break; - case SSL_ERROR_SSL: - messageStr = "Failure in SSL library, usually a protocol error"; - break; - case SSL_ERROR_WANT_READ: - messageStr = "SSL_ERROR_WANT_READ occured. You should never see this."; - break; - case SSL_ERROR_WANT_WRITE: - messageStr = "SSL_ERROR_WANT_WRITE occured. You should never see this."; - break; - case SSL_ERROR_WANT_X509_LOOKUP: - messageStr = "SSL_ERROR_WANT_X509_LOOKUP occured. You should never see this."; - break; - case SSL_ERROR_SYSCALL: - messageStr = "I/O error during system call"; - break; - case SSL_ERROR_ZERO_RETURN: - messageStr = "SSL_ERROR_ZERO_RETURN occured. You should never see this."; - break; - case SSL_ERROR_WANT_CONNECT: - messageStr = "SSL_ERROR_WANT_CONNECT occured. You should never see this."; - break; - case SSL_ERROR_WANT_ACCEPT: - messageStr = "SSL_ERROR_WANT_ACCEPT occured. You should never see this."; - break; - default: - messageStr = "Unknown SSL error"; - } - - // Prepend either our explicit message or a default one. - if (asprintf(&str, "%s: %s", - (message != NULL) ? message : "SSL error", messageStr) == 0) { - throwIOExceptionStr(env, messageStr); - LOGV("%s", messageStr); - freeSslErrorState(); - return; - } - - char* allocStr = str; - - // For SSL protocol errors, SSL might have more information. - if (sslErrorCode == SSL_ERROR_SSL) { - // Append each error as an additional line to the message. - for (;;) { - char errStr[256]; - const char* file; - int line; - const char* data; - int flags; - unsigned long err = - ERR_get_error_line_data(&file, &line, &data, &flags); - - if (err == 0) { - break; - } - - ERR_error_string_n(err, errStr, sizeof(errStr)); - - ret = asprintf(&str, "%s\n%s (%s:%d %p:0x%08x)", - (allocStr == NULL) ? "" : allocStr, - errStr, - file, - line, - data, - flags); - - if (ret < 0) { - break; - } - - free(allocStr); - allocStr = str; - } - // For errors during system calls, errno might be our friend. - } else if (sslErrorCode == SSL_ERROR_SYSCALL) { - if (asprintf(&str, "%s, %s", allocStr, strerror(errno)) >= 0) { - free(allocStr); - allocStr = str; - } - // If the error code is invalid, print it. - } else if (sslErrorCode > SSL_ERROR_WANT_ACCEPT) { - if (asprintf(&str, ", error code is %d", sslErrorCode) >= 0) { - free(allocStr); - allocStr = str; - } - } - - throwIOExceptionStr(env, allocStr); - - LOGV("%s", allocStr); - free(allocStr); - freeSslErrorState(); -} - -/** - * Helper function that grabs the ssl pointer out of the given object. - * 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 - * NULL, a caller of this function should simply return and allow JNI - * to do its thing. - * - * @param env non-null; the JNI environment - * @param obj non-null; socket object - * @param throwIfNull whether to throw if the SSL pointer is NULL - * @returns the pointer, which may be NULL - */ -static SSL *getSslPointer(JNIEnv* env, jobject obj, bool throwIfNull) { - SSL *ssl = (SSL *)env->GetIntField(obj, field_ssl); - - if ((ssl == NULL) && throwIfNull) { - throwIOExceptionStr(env, "null SSL pointer"); - } - - return ssl; -} - -// ============================================================================ -// === OpenSSL-related helper stuff begins here. ============================== -// ============================================================================ - -/** - * OpenSSL locking support. Taken from the O'Reilly book by Viega et al., but I - * suppose there are not many other ways to do this on a Linux system (modulo - * isomorphism). - */ -#define MUTEX_TYPE pthread_mutex_t -#define MUTEX_SETUP(x) pthread_mutex_init(&(x), NULL) -#define MUTEX_CLEANUP(x) pthread_mutex_destroy(&(x)) -#define MUTEX_LOCK(x) pthread_mutex_lock(&(x)) -#define MUTEX_UNLOCK(x) pthread_mutex_unlock(&(x)) -#define THREAD_ID pthread_self() -#define THROW_EXCEPTION (-2) -#define THROW_SOCKETTIMEOUTEXCEPTION (-3) - -static MUTEX_TYPE *mutex_buf = NULL; - -static void locking_function(int mode, int n, const char * file, int line) { - if (mode & CRYPTO_LOCK) { - MUTEX_LOCK(mutex_buf[n]); - } else { - MUTEX_UNLOCK(mutex_buf[n]); - } -} - -static unsigned long id_function(void) { - return ((unsigned long)THREAD_ID); -} - -int THREAD_setup(void) { - int i; - - mutex_buf = (MUTEX_TYPE *)malloc(CRYPTO_num_locks( ) * sizeof(MUTEX_TYPE)); - - if(!mutex_buf) { - return 0; - } - - for (i = 0; i < CRYPTO_num_locks( ); i++) { - MUTEX_SETUP(mutex_buf[i]); - } - - CRYPTO_set_id_callback(id_function); - CRYPTO_set_locking_callback(locking_function); - - return 1; -} - -int THREAD_cleanup(void) { - int i; - - if (!mutex_buf) { - return 0; - } - - CRYPTO_set_id_callback(NULL); - CRYPTO_set_locking_callback(NULL); - - for (i = 0; i < CRYPTO_num_locks( ); i++) { - MUTEX_CLEANUP(mutex_buf[i]); - } - - free(mutex_buf); - mutex_buf = NULL; - - return 1; -} - -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 - -/** - * 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. - */ -typedef struct app_data { - int aliveAndKicking; - int waitingThreads; - int fdsEmergency[2]; - MUTEX_TYPE mutex; -} APP_DATA; - -/** - * 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. - */ -static int sslCreateAppData(SSL* ssl) { - APP_DATA* data = (APP_DATA*) malloc(sizeof(APP_DATA)); - - memset(data, 0, sizeof(APP_DATA)); - - data->aliveAndKicking = 1; - data->waitingThreads = 0; - data->fdsEmergency[0] = -1; - data->fdsEmergency[1] = -1; - - if (pipe(data->fdsEmergency) == -1) { - return -1; - } - - if (MUTEX_SETUP(data->mutex) == -1) { - return -1; - } - - SSL_set_app_data(ssl, (char*) data); - - 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); - - if (data != NULL) { - SSL_set_app_data(ssl, NULL); - - data -> aliveAndKicking = 0; - - if (data->fdsEmergency[0] != -1) { - close(data->fdsEmergency[0]); - } - - if (data->fdsEmergency[1] != -1) { - close(data->fdsEmergency[1]); - } - - MUTEX_CLEANUP(data->mutex); - - free(data); - } -} - - -/** - * Frees the SSL_CTX struct for the given instance. - */ -static void free_ssl_ctx(JNIEnv* env, jobject object) { - /* - * Preserve and restore the exception state around this call, so - * that GetIntField and SetIntField will operate without complaint. - */ - jthrowable exception = env->ExceptionOccurred(); - - if (exception != NULL) { - env->ExceptionClear(); - } - - SSL_CTX *ctx = (SSL_CTX *)env->GetIntField(object, field_ssl_ctx); - - if (ctx != NULL) { - SSL_CTX_free(ctx); - env->SetIntField(object, field_ssl_ctx, (int) NULL); - } - - if (exception != NULL) { - env->Throw(exception); - } -} - -/** - * Frees the SSL struct for the given instance. - */ -static void free_ssl(JNIEnv* env, jobject object) { - /* - * Preserve and restore the exception state around this call, so - * that GetIntField and SetIntField will operate without complaint. - */ - jthrowable exception = env->ExceptionOccurred(); - - if (exception != NULL) { - env->ExceptionClear(); - } - - SSL *ssl = (SSL *)env->GetIntField(object, field_ssl); - - if (ssl != NULL) { - sslDestroyAppData(ssl); - SSL_free(ssl); - env->SetIntField(object, field_ssl, (int) NULL); - } - - if (exception != NULL) { - env->Throw(exception); - } -} - -/** - * Constructs the SSL struct for the given instance, replacing one - * that was already made, if any. - */ -static SSL* create_ssl(JNIEnv* env, jobject object, SSL_CTX* ssl_ctx) { - free_ssl(env, object); - - SSL *ssl = SSL_new(ssl_ctx); - env->SetIntField(object, field_ssl, (int) ssl); - return ssl; -} - -/** - * 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. - * - * @param ssl non-null; the SSL context - * @param buf non-null; buffer to read into - * @param len length of the buffer, in bytes - * @param sslReturnCode original SSL return code - * @param sslErrorCode filled in with the SSL error code in case of error - * @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, - int* sslErrorCode, int timeout) { - - // LOGD("Entering sslRead, caller requests to read %d bytes...", len); - - if (len == 0) { - // Don't bother doing anything in this case. - return 0; - } - - int fd = SSL_get_fd(ssl); - BIO *bio = SSL_get_rbio(ssl); - - APP_DATA* data = (APP_DATA*) SSL_get_app_data(ssl); - - while (data->aliveAndKicking) { - errno = 0; - - // Lock - if (MUTEX_LOCK(data->mutex) == -1) { - return -1; - } - - unsigned int bytesMoved = BIO_number_read(bio) + BIO_number_written(bio); - - // LOGD("Doing SSL_Read()"); - int result = SSL_read(ssl, buf, len); - int error = SSL_ERROR_NONE; - if (result <= 0) { - error = SSL_get_error(ssl, result); - freeSslErrorState(); - } - // LOGD("Returned from SSL_Read() with result %d, error code %d", result, error); - - // 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 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++; - } - - // Unlock - MUTEX_UNLOCK(data->mutex); - - switch (error) { - // Sucessfully read at least one byte. - case SSL_ERROR_NONE: { - return result; - } - - // Read zero bytes. End of stream reached. - case SSL_ERROR_ZERO_RETURN: { - return -1; - } - - // 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); - if (selectResult == -1) { - *sslReturnCode = -1; - *sslErrorCode = error; - return THROW_EXCEPTION; - } else if (selectResult == 0) { - return THROW_SOCKETTIMEOUTEXCEPTION; - } - - break; - } - - // A problem occured during a system call, but this is not - // necessarily an error. - case SSL_ERROR_SYSCALL: { - // Connection closed without proper shutdown. Tell caller we - // have reached end-of-stream. - if (result == 0) { - return -1; - } - - // System call has been interrupted. Simply retry. - if (errno == EINTR) { - break; - } - - // Note that for all other system call errors we fall through - // to the default case, which results in an Exception. - } - - // Everything else is basically an error. - default: { - *sslReturnCode = result; - *sslErrorCode = error; - return THROW_EXCEPTION; - } - } - } - - return -1; -} - -/** - * Helper function which does the actual writing. The Java layer guarantees that - * at most one thread will enter this function at any given time. - * - * @param ssl non-null; the SSL context - * @param buf non-null; buffer to write - * @param len length of the buffer, in bytes - * @param sslReturnCode original SSL return code - * @param sslErrorCode filled in with the SSL error code in case of error - * @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, - int* sslErrorCode) { - - // LOGD("Entering sslWrite(), caller requests to write %d bytes...", len); - - if (len == 0) { - // Don't bother doing anything in this case. - return 0; - } - - int fd = SSL_get_fd(ssl); - BIO *bio = SSL_get_wbio(ssl); - - APP_DATA* data = (APP_DATA*) SSL_get_app_data(ssl); - - int count = len; - - while(data->aliveAndKicking && len > 0) { - errno = 0; - if (MUTEX_LOCK(data->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); - int result = SSL_write(ssl, buf, len); - int error = SSL_ERROR_NONE; - if (result <= 0) { - error = SSL_get_error(ssl, result); - freeSslErrorState(); - } - // LOGD("Returned from SSL_write() with result %d, error code %d", result, error); - - // 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 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++; - } - - MUTEX_UNLOCK(data->mutex); - - switch (error) { - // Sucessfully write at least one byte. - case SSL_ERROR_NONE: { - buf += result; - len -= result; - break; - } - - // Wrote zero bytes. End of stream reached. - case SSL_ERROR_ZERO_RETURN: { - return -1; - } - - // Need to wait for availability of underlying layer, then retry. - // The concept of a write timeout doesn't really make sense, and - // 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); - if (selectResult == -1) { - *sslReturnCode = -1; - *sslErrorCode = error; - return THROW_EXCEPTION; - } else if (selectResult == 0) { - return THROW_SOCKETTIMEOUTEXCEPTION; - } - - break; - } - - // An problem occured during a system call, but this is not - // necessarily an error. - case SSL_ERROR_SYSCALL: { - // Connection closed without proper shutdown. Tell caller we - // have reached end-of-stream. - if (result == 0) { - return -1; - } - - // System call has been interrupted. Simply retry. - if (errno == EINTR) { - break; - } - - // Note that for all other system call errors we fall through - // to the default case, which results in an Exception. - } - - // Everything else is basically an error. - default: { - *sslReturnCode = result; - *sslErrorCode = error; - return THROW_EXCEPTION; - } - } - } - // LOGD("Successfully wrote %d bytes", count); - - return count; -} - -/** - * Helper function that creates an RSA public key from two buffers containing - * the big-endian bit representation of the modulus and the public exponent. - * - * @param mod The data of the modulus - * @param modLen The length of the modulus data - * @param exp The data of the exponent - * @param expLen The length of the exponent data - * - * @return A pointer to the new RSA structure, or NULL on error - */ -static RSA* rsaCreateKey(unsigned char* mod, int modLen, unsigned char* exp, int expLen) { - // LOGD("Entering rsaCreateKey()"); - - RSA* rsa = RSA_new(); - - rsa->n = BN_bin2bn((unsigned char*) mod, modLen, NULL); - rsa->e = BN_bin2bn((unsigned char*) exp, expLen, NULL); - - if (rsa->n == NULL || rsa->e == NULL) { - RSA_free(rsa); - return NULL; - } - - return rsa; -} - -/** - * Helper function that frees an RSA key. Just calls the corresponding OpenSSL - * function. - * - * @param rsa The pointer to the new RSA structure to free. - */ -static void rsaFreeKey(RSA* rsa) { - // LOGD("Entering rsaFreeKey()"); - - if (rsa != NULL) { - RSA_free(rsa); - } -} - -/** - * Helper function that verifies a given RSA signature for a given message. - * - * @param msg The message to verify - * @param msgLen The length of the message - * @param sig The signature to verify - * @param sigLen The length of the signature - * @param algorithm The name of the hash/sign algorithm to use, e.g. "RSA-SHA1" - * @param rsa The RSA public key to use - * - * @return 1 on success, 0 on failure, -1 on error (check SSL errors then) - * - */ -static int rsaVerify(unsigned char* msg, unsigned int msgLen, unsigned char* sig, - unsigned int sigLen, char* algorithm, RSA* rsa) { - - // LOGD("Entering rsaVerify(%x, %d, %x, %d, %s, %x)", msg, msgLen, sig, sigLen, algorithm, rsa); - - int result = -1; - - EVP_PKEY* key = EVP_PKEY_new(); - EVP_PKEY_set1_RSA(key, rsa); - - const EVP_MD *type = EVP_get_digestbyname(algorithm); - if (type == NULL) { - goto cleanup; - } - - EVP_MD_CTX ctx; - - EVP_MD_CTX_init(&ctx); - if (EVP_VerifyInit_ex(&ctx, type, NULL) == 0) { - goto cleanup; - } - - EVP_VerifyUpdate(&ctx, msg, msgLen); - result = EVP_VerifyFinal(&ctx, sig, sigLen, key); - EVP_MD_CTX_cleanup(&ctx); - - cleanup: - - if (key != NULL) { - EVP_PKEY_free(key); - } - - return result; -} - -// ============================================================================ -// === OpenSSL-related helper stuff ends here. JNI glue follows. ============== -// ============================================================================ - -/** - * Initialization phase for every OpenSSL job: Loads the Error strings, the - * crypto algorithms and reset the OpenSSL library - */ -static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_initstatic(JNIEnv* env, jobject obj) -{ - SSL_load_error_strings(); - ERR_load_crypto_strings(); - SSL_library_init(); - OpenSSL_add_all_algorithms(); - THREAD_setup(); -} - -/** - * Initialization phase for a socket with OpenSSL. The server's private key - * and X509 certificate are read and the Linux /dev/urandom file is loaded - * as RNG for the session keys. - * - */ -static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_init(JNIEnv* env, jobject object, - jstring privatekey, jstring certificates, jbyteArray seed) -{ - SSL_CTX* ssl_ctx; - - // 'seed == null' when no SecureRandom Object is set - // in the SSLContext. - if (seed != NULL) { - jbyte* randseed = env->GetByteArrayElements(seed, NULL); - RAND_seed((unsigned char*) randseed, 1024); - env->ReleaseByteArrayElements(seed, randseed, 0); - } else { - RAND_load_file("/dev/urandom", 1024); - } - - ssl_ctx = SSL_CTX_new(SSLv23_client_method()); - - // Note: We explicitly do not allow SSLv2 to be used. It - SSL_CTX_set_options(ssl_ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2); - - /* Java code in class OpenSSLSocketImpl does the verification. Meaning of - * SSL_VERIFY_NONE flag in client mode: if not using an anonymous cipher - * (by default disabled), the server will send a certificate which will - * be checked. The result of the certificate verification process can be - * checked after the TLS/SSL handshake using the SSL_get_verify_result(3) - * function. The handshake will be continued regardless of the - * verification result. - */ - SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, NULL); - - int mode = SSL_CTX_get_mode(ssl_ctx); - /* - * Turn on "partial write" mode. This means that SSL_write() will - * behave like Posix write() and possibly return after only - * writing a partial buffer. Note: The alternative, perhaps - * surprisingly, is not that SSL_write() always does full writes - * but that it will force you to retry write calls having - * preserved the full state of the original call. (This is icky - * and undesirable.) - */ - mode |= SSL_MODE_ENABLE_PARTIAL_WRITE; -#if defined(SSL_MODE_SMALL_BUFFERS) /* not all SSL versions have this */ - mode |= SSL_MODE_SMALL_BUFFERS; /* lazily allocate record buffers; usually saves - * 44k over the default */ -#endif -#if defined(SSL_MODE_HANDSHAKE_CUTTHROUGH) /* not all SSL versions have this */ - mode |= SSL_MODE_HANDSHAKE_CUTTHROUGH; /* enable sending of client data as soon as - * ClientCCS and ClientFinished are sent */ -#endif - - SSL_CTX_set_mode(ssl_ctx, mode); - - if (privatekey != NULL) { - BIO* privatekeybio = stringToMemBuf(env, (jstring) privatekey); - EVP_PKEY* privatekeyevp = - PEM_read_bio_PrivateKey(privatekeybio, NULL, 0, NULL); - BIO_free(privatekeybio); - - if (privatekeyevp == NULL) { - throwIOExceptionWithSslErrors(env, 0, 0, - "Error parsing the private key"); - SSL_CTX_free(ssl_ctx); - return; - } - - BIO* certificatesbio = stringToMemBuf(env, (jstring) certificates); - X509* certificatesx509 = - PEM_read_bio_X509(certificatesbio, NULL, 0, NULL); - BIO_free(certificatesbio); - - if (certificatesx509 == NULL) { - throwIOExceptionWithSslErrors(env, 0, 0, - "Error parsing the certificates"); - EVP_PKEY_free(privatekeyevp); - SSL_CTX_free(ssl_ctx); - return; - } - - int ret = SSL_CTX_use_certificate(ssl_ctx, certificatesx509); - if (ret != 1) { - throwIOExceptionWithSslErrors(env, ret, 0, - "Error setting the certificates"); - X509_free(certificatesx509); - EVP_PKEY_free(privatekeyevp); - SSL_CTX_free(ssl_ctx); - return; - } - - ret = SSL_CTX_use_PrivateKey(ssl_ctx, privatekeyevp); - if (ret != 1) { - throwIOExceptionWithSslErrors(env, ret, 0, - "Error setting the private key"); - X509_free(certificatesx509); - EVP_PKEY_free(privatekeyevp); - SSL_CTX_free(ssl_ctx); - return; - } - - ret = SSL_CTX_check_private_key(ssl_ctx); - if (ret != 1) { - throwIOExceptionWithSslErrors(env, ret, 0, - "Error checking the private key"); - X509_free(certificatesx509); - EVP_PKEY_free(privatekeyevp); - SSL_CTX_free(ssl_ctx); - return; - } - } - - env->SetIntField(object, field_ssl_ctx, (int)ssl_ctx); -} - -/** - * 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, jobject object, - jint ctx, jobject socketObject, jboolean client_mode, jint session) -{ - // LOGD("ENTER connect"); - int ret, fd; - SSL_CTX* ssl_ctx; - SSL* ssl; - SSL_SESSION* ssl_session; - - ssl_ctx = (SSL_CTX*)env->GetIntField(object, field_ssl_ctx); - - ssl = create_ssl(env, object, ssl_ctx); - if (ssl == NULL) { - throwIOExceptionWithSslErrors(env, 0, 0, - "Unable to create SSL structure"); - free_ssl_ctx(env, object); - return (jboolean) false; - } - - jobject socketImplObject = env->GetObjectField(socketObject, field_mImpl); - if (socketImplObject == NULL) { - free_ssl(env, object); - free_ssl_ctx(env, object); - throwIOExceptionStr(env, - "couldn't get the socket impl from the socket"); - return (jboolean) false; - } - - jobject fdObject = env->GetObjectField(socketImplObject, field_mFD); - if (fdObject == NULL) { - free_ssl(env, object); - free_ssl_ctx(env, object); - throwIOExceptionStr(env, - "couldn't get the file descriptor from the socket impl"); - return (jboolean) false; - } - - fd = jniGetFDFromFileDescriptor(env, fdObject); - - ssl_session = (SSL_SESSION *) session; - - ret = SSL_set_fd(ssl, fd); - - if (ret != 1) { - throwIOExceptionWithSslErrors(env, ret, 0, - "Error setting the file descriptor"); - free_ssl(env, object); - free_ssl_ctx(env, object); - return (jboolean) false; - } - - if (ssl_session != NULL) { - 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"); - free_ssl(env, object); - free_ssl_ctx(env, object); - 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"); - free_ssl(env, object); - free_ssl_ctx(env, object); - return (jboolean) false; - } - - /* - * Create our special application data. - */ - if (sslCreateAppData(ssl) == -1) { - throwIOExceptionStr(env, "Unable to create application data"); - free_ssl(env, object); - free_ssl_ctx(env, object); - // TODO - return (jboolean) false; - } - - APP_DATA* data = (APP_DATA*) SSL_get_app_data(ssl); - env->SetIntField(object, field_ssl, (int)ssl); - - int timeout = (int)env->GetIntField(object, field_timeout); - - 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"); - free_ssl(env, object); - free_ssl_ctx(env, object); - return (jboolean) false; - } else if (selectResult == 0) { - throwSocketTimeoutException(env, "SSL handshake timed out"); - freeSslErrorState(); - free_ssl(env, object); - free_ssl_ctx(env, object); - 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"); - free_ssl(env, object); - free_ssl_ctx(env, object); - return (jboolean) false; - } - } - - if (ssl_session != NULL) { - ret = SSL_session_reused(ssl); - // if (ret == 1) LOGD("A session was reused"); - // else LOGD("A new session was negotiated"); - return (jboolean) ret; - } else { - // LOGD("A new session was negotiated"); - return (jboolean) 0; - } - // LOGD("LEAVE connect"); -} - -static jint org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_getsslsession(JNIEnv* env, jobject object, - jint jssl) -{ - return (jint) SSL_get1_session((SSL *) jssl); -} - -static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_accept(JNIEnv* env, jobject object, - jobject socketObject, jint jssl_ctx, jboolean client_mode) -{ - int sd, ret; - BIO *bio; - SSL *ssl; - SSL_CTX *ssl_ctx; - mydata_t mydata; - - ssl_ctx = (SSL_CTX *)jssl_ctx; - - ssl = create_ssl(env, object, ssl_ctx); - if (ssl == NULL) { - throwIOExceptionWithSslErrors(env, 0, 0, - "Unable to create SSL structure"); - return; - } - - jobject socketImplObject = env->GetObjectField(socketObject, field_mImpl); - if (socketImplObject == NULL) { - free_ssl(env, object); - throwIOExceptionStr(env, "couldn't get the socket impl from the socket"); - return; - } - - jobject fdObject = env->GetObjectField(socketImplObject, field_mFD); - if (fdObject == NULL) { - free_ssl(env, object); - throwIOExceptionStr(env, "couldn't get the file descriptor from the socket impl"); - return; - } - - - sd = jniGetFDFromFileDescriptor(env, fdObject); - - bio = BIO_new_socket(sd, BIO_NOCLOSE); - - /* The parameter client_mode must be 1 */ - if (client_mode != 0) - client_mode = 1; - BIO_set_ssl_mode(bio, client_mode); - - SSL_set_bio(ssl, bio, bio); - - /* - * Fill in the mydata structure needed for the certificate callback and - * store this in the SSL application data slot. - */ - mydata.env = env; - mydata.object = object; - SSL_set_app_data(ssl, &mydata); - - /* - * 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. - */ - 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"); - } - free_ssl(env, object); - return; - } 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"); - free_ssl(env, object); - return; - } - - /* - * 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"); - free_ssl(env, object); - return; - } - - /* - * Create our special application data. - */ - if (sslCreateAppData(ssl) == -1) { - throwIOExceptionStr(env, "Unable to create application data"); - free_ssl(env, object); - return; - } -} - -/** - * Loads the desired protocol for the OpenSSL client and enables it. - * For example SSL_OP_NO_TLSv1 means do not use TLS v. 1. - */ -static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_setenabledprotocols(JNIEnv* env, jobject object, - jlong protocol) -{ - if (protocol != 0x00000000L) { - if (protocol & SSL_OP_NO_SSLv3) - LOGD("SSL_OP_NO_SSLv3 is set"); - if (protocol & SSL_OP_NO_TLSv1) - LOGD("SSL_OP_NO_TLSv1 is set"); - - SSL_CTX* ctx = (SSL_CTX*)env->GetIntField(object, field_ssl_ctx); - int options = SSL_CTX_get_options(ctx); - options |= protocol; // Note: SSLv2 disabled earlier. - SSL_CTX_set_options(ctx, options); - } -} - -/** - * Loads the ciphers suites that are supported by the OpenSSL client - * and returns them in a string array. - */ -static jobjectArray org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_getsupportedciphersuites(JNIEnv* env, - jobject object) -{ - SSL_CTX* ssl_ctx = SSL_CTX_new(SSLv23_client_method()); - if (ssl_ctx == NULL) { - return NULL; - } - jobjectArray result = makeCipherList(env, ssl_ctx); - SSL_CTX_free(ssl_ctx); - return result; -} - -/** - * Loads the ciphers suites that are enabled in the OpenSSL client - * and returns them in a string array. - */ -static jobjectArray OpenSSLSocketImpl_nativeGetEnabledCipherSuites(JNIEnv* env, - jclass, jint ssl_ctx_address) -{ - SSL_CTX* ssl_ctx = - reinterpret_cast<SSL_CTX*>(static_cast<uintptr_t>(ssl_ctx_address)); - return makeCipherList(env, ssl_ctx); -} - -/** - * Sets the ciphers suites that are enabled in the OpenSSL client. - */ -static void OpenSSLSocketImpl_nativeSetEnabledCipherSuites(JNIEnv* env, jclass, - jint ssl_ctx_address, jstring controlString) -{ - SSL_CTX* ssl_ctx = - reinterpret_cast<SSL_CTX*>(static_cast<uintptr_t>(ssl_ctx_address)); - setEnabledCipherSuites(env, controlString, ssl_ctx); -} - -static jobjectArray makeCipherList(JNIEnv* env, SSL* ssl) { - // Count the ciphers. - int cipherCount = 0; - while (SSL_get_cipher_list(ssl, cipherCount) != NULL) { - ++cipherCount; - } - - // Create a String[]. - jclass stringClass = env->FindClass("java/lang/String"); - if (stringClass == NULL) { - return NULL; - } - jobjectArray array = env->NewObjectArray(cipherCount, stringClass, NULL); - if (array == NULL) { - return NULL; - } - - // Fill in the cipher names. - for (int i = 0; i < cipherCount; ++i) { - const char* c = SSL_get_cipher_list(ssl, i); - env->SetObjectArrayElement(array, i, env->NewStringUTF(c)); - } - return array; -} - -jobjectArray makeCipherList(JNIEnv* env, SSL_CTX* ssl_ctx) { - SSL* ssl = SSL_new(ssl_ctx); - if (ssl == NULL) { - return NULL; - } - jobjectArray result = makeCipherList(env, ssl); - SSL_free(ssl); - return result; -} - -void setEnabledCipherSuites(JNIEnv* env, jstring controlString, SSL_CTX* ssl_ctx) { - const char* str = env->GetStringUTFChars(controlString, NULL); - int rc = SSL_CTX_set_cipher_list(ssl_ctx, str); - env->ReleaseStringUTFChars(controlString, str); - if (rc == 0) { - freeSslErrorState(); - jniThrowException(env, "java/lang/IllegalArgumentException", - "Illegal cipher suite strings."); - } -} - -#define SSL_AUTH_MASK 0x00007F00L -#define SSL_aRSA 0x00000100L /* Authenticate with RSA */ -#define SSL_aDSS 0x00000200L /* Authenticate with DSS */ -#define SSL_DSS SSL_aDSS -#define SSL_aFZA 0x00000400L -#define SSL_aNULL 0x00000800L /* no Authenticate, ADH */ -#define SSL_aDH 0x00001000L /* no Authenticate, ADH */ -#define SSL_aKRB5 0x00002000L /* Authenticate with KRB5 */ -#define SSL_aECDSA 0x00004000L /* Authenticate with ECDSA */ - -/** - * Sets the client's crypto algorithms and authentication methods. - */ -static jstring org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_cipherauthenticationmethod(JNIEnv* env, - jobject object) -{ - SSL* ssl; - SSL_CIPHER *cipher; - jstring ret; - char buf[512]; - unsigned long alg; - const char *au; - - ssl = getSslPointer(env, object, true); - if (ssl == NULL) { - return NULL; - } - - cipher = SSL_get_current_cipher(ssl); - - alg = cipher->algorithms; - - switch (alg&SSL_AUTH_MASK) { - case SSL_aRSA: - au="RSA"; - break; - case SSL_aDSS: - au="DSS"; - break; - case SSL_aDH: - au="DH"; - break; - case SSL_aFZA: - au = "FZA"; - break; - case SSL_aNULL: - au="None"; - break; - case SSL_aECDSA: - au="ECDSA"; - break; - default: - au="unknown"; - break; - } - - ret = env->NewStringUTF(au); - - return ret; -} - -/** - * OpenSSL read function (1): only one chunk is read (returned as jint). - */ -static jint org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_read(JNIEnv* env, jobject object, jint timeout) -{ - SSL *ssl = getSslPointer(env, object, true); - if (ssl == NULL) { - return 0; - } - - unsigned char byteRead; - int returnCode = 0; - int errorCode = 0; - - int ret = sslRead(ssl, (char *) &byteRead, 1, &returnCode, &errorCode, timeout); - - switch (ret) { - case THROW_EXCEPTION: - // See sslRead() regarding improper failure to handle normal cases. - throwIOExceptionWithSslErrors(env, returnCode, errorCode, - "Read error"); - return -1; - case THROW_SOCKETTIMEOUTEXCEPTION: - throwSocketTimeoutException(env, "Read timed out"); - return -1; - case -1: - // Propagate EOF upwards. - return -1; - default: - // Return the actual char read, make sure it stays 8 bits wide. - return ((jint) byteRead) & 0xFF; - } -} - -/** - * OpenSSL read function (2): read into buffer at offset n chunks. - * Returns 1 (success) or value <= 0 (failure). - */ -static jint org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_readba(JNIEnv* env, jobject obj, jbyteArray dest, jint offset, jint len, jint timeout) -{ - SSL *ssl = getSslPointer(env, obj, true); - if (ssl == NULL) { - return 0; - } - - jbyte* bytes = env->GetByteArrayElements(dest, NULL); - int returnCode = 0; - int errorCode = 0; - - int ret = - sslRead(ssl, (char*) (bytes + offset), len, &returnCode, &errorCode, timeout); - - env->ReleaseByteArrayElements(dest, bytes, 0); - - if (ret == THROW_EXCEPTION) { - // See sslRead() regarding improper failure to handle normal cases. - throwIOExceptionWithSslErrors(env, returnCode, errorCode, - "Read error"); - return -1; - } else if(ret == THROW_SOCKETTIMEOUTEXCEPTION) { - throwSocketTimeoutException(env, "Read timed out"); - return -1; - } - - return ret; -} - -/** - * OpenSSL write function (1): only one chunk is written. - */ -static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_write(JNIEnv* env, jobject object, jint b) -{ - SSL *ssl = getSslPointer(env, object, true); - if (ssl == NULL) { - return; - } - - int returnCode = 0; - int errorCode = 0; - char buf[1] = { (char) b }; - int ret = sslWrite(ssl, buf, 1, &returnCode, &errorCode); - - if (ret == THROW_EXCEPTION) { - // See sslWrite() regarding improper failure to handle normal cases. - throwIOExceptionWithSslErrors(env, returnCode, errorCode, - "Write error"); - } else if(ret == THROW_SOCKETTIMEOUTEXCEPTION) { - throwSocketTimeoutException(env, "Write timed out"); - } -} - -/** - * OpenSSL write function (2): write into buffer at offset n chunks. - */ -static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_writeba(JNIEnv* env, jobject obj, - jbyteArray dest, jint offset, jint len) -{ - SSL *ssl = getSslPointer(env, obj, true); - if (ssl == NULL) { - return; - } - - jbyte* bytes = env->GetByteArrayElements(dest, NULL); - int returnCode = 0; - int errorCode = 0; - int timeout = (int)env->GetIntField(obj, field_timeout); - int ret = sslWrite(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, - "Write error"); - } else if(ret == THROW_SOCKETTIMEOUTEXCEPTION) { - throwSocketTimeoutException(env, "Write timed out"); - } -} - -/** - * Interrupt any pending IO before closing the socket. - */ -static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_interrupt( - JNIEnv* env, jobject object) { - SSL *ssl = getSslPointer(env, object, false); - if (ssl == NULL) { - return; - } - - /* - * 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; - - // At most two threads can be waiting. - sslNotify(data); - sslNotify(data); - } -} - -/** - * OpenSSL close SSL socket function. - */ -static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_close( - JNIEnv* env, jobject object) { - SSL *ssl = getSslPointer(env, object, false); - if (ssl == NULL) { - return; - } - - /* - * Try to make socket blocking again. OpenSSL literature recommends this. - */ - int fd = SSL_get_fd(ssl); - 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"); -// LOGW("Unable to make socket blocking again"); - } - } - - int ret = SSL_shutdown(ssl); - switch (ret) { - case 0: - /* - * Shutdown was not successful (yet), but there also - * is no error. Since we can't know whether the remote - * server is actually still there, and we don't want to - * get stuck forever in a second SSL_shutdown() call, we - * simply return. This is not security a problem as long - * as we close the underlying socket, which we actually - * do, because that's where we are just coming from. - */ - break; - case 1: - /* - * Shutdown was sucessful. We can safely return. Hooray! - */ - break; - default: - /* - * Everything else is a real error condition. We should - * let the Java layer know about this by throwing an - * exception. - */ - throwIOExceptionWithSslErrors(env, ret, 0, "SSL shutdown failed."); - break; - } - - freeSslErrorState(); - free_ssl(env, object); - free_ssl_ctx(env, object); -} - -/** - * OpenSSL free SSL socket function. - */ -static void org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_free(JNIEnv* env, jobject object) -{ - free_ssl(env, object); - free_ssl_ctx(env, object); -} - -/** - * Verifies an RSA signature. - */ -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()"); - - if (msg == NULL || sig == NULL || algorithm == NULL || mod == NULL || exp == NULL) { - jniThrowNullPointerException(env, NULL); - return -1; - } - - int result = -1; - - jbyte* msgBytes = env->GetByteArrayElements(msg, NULL); - jint msgLength = env->GetArrayLength(msg); - - jbyte* sigBytes = env->GetByteArrayElements(sig, NULL); - jint sigLength = env->GetArrayLength(sig); - - jbyte* modBytes = env->GetByteArrayElements(mod, NULL); - jint modLength = env->GetArrayLength(mod); - - jbyte* expBytes = env->GetByteArrayElements(exp, NULL); - jint expLength = env->GetArrayLength(exp); - - const char* algorithmChars = env->GetStringUTFChars(algorithm, NULL); - - RSA* rsa = rsaCreateKey((unsigned char*) modBytes, modLength, (unsigned char*) expBytes, expLength); - if (rsa != NULL) { - result = rsaVerify((unsigned char*) msgBytes, msgLength, (unsigned char*) sigBytes, sigLength, - (char*) algorithmChars, rsa); - rsaFreeKey(rsa); - } - - env->ReleaseStringUTFChars(algorithm, algorithmChars); - - env->ReleaseByteArrayElements(exp, expBytes, JNI_ABORT); - env->ReleaseByteArrayElements(mod, modBytes, JNI_ABORT); - env->ReleaseByteArrayElements(sig, sigBytes, JNI_ABORT); - env->ReleaseByteArrayElements(msg, msgBytes, JNI_ABORT); - - if (result == -1) { - int error = ERR_get_error(); - if (error != 0) { - char message[50]; - ERR_error_string_n(error, message, sizeof(message)); - jniThrowRuntimeException(env, message); - } else { - jniThrowRuntimeException(env, "Internal error during verification"); - } - freeSslErrorState(); - } - - return result; -} - -/** - * The actual JNI methods' mapping table for the class OpenSSLSocketImpl. - */ -static JNINativeMethod sMethods[] = -{ - {"nativeinitstatic", "()V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_initstatic}, - {"nativeinit", "(Ljava/lang/String;Ljava/lang/String;[B)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_init}, - {"nativeconnect", "(ILjava/net/Socket;ZI)Z", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_connect}, - {"nativegetsslsession", "(I)I", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_getsslsession}, - {"nativeread", "(I)I", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_read}, - {"nativeread", "([BIII)I", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_readba}, - {"nativewrite", "(I)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_write}, - {"nativewrite", "([BII)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_writeba}, - {"nativeaccept", "(Ljava/net/Socket;IZ)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_accept}, - {"nativesetenabledprotocols", "(J)V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_setenabledprotocols}, - {"nativegetsupportedciphersuites", "()[Ljava/lang/String;", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_getsupportedciphersuites}, - {"nativeGetEnabledCipherSuites", "(I)[Ljava/lang/String;", (void*) OpenSSLSocketImpl_nativeGetEnabledCipherSuites}, - {"nativeSetEnabledCipherSuites", "(ILjava/lang/String;)V", (void*) OpenSSLSocketImpl_nativeSetEnabledCipherSuites}, - {"nativecipherauthenticationmethod", "()Ljava/lang/String;", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_cipherauthenticationmethod}, - {"nativeinterrupt", "()V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_interrupt}, - {"nativeclose", "()V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_close}, - {"nativefree", "()V", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_free}, - {"nativeverifysignature", "([B[BLjava/lang/String;[B[B)I", (void*)org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl_verifysignature}, -}; - -/** - * Register the native methods with JNI for the class OpenSSLSocketImpl. - */ -extern "C" int register_org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl(JNIEnv* env) -{ - jclass clazz = env->FindClass("org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl"); - if (clazz == NULL) { - LOGE("Can't find org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl"); - return -1; - } - - jclass socketClass = env->FindClass("java/net/Socket"); - if (socketClass == NULL) { - LOGE("Can't find class java.net.Socket"); - return -1; - } - - field_mImpl = env->GetFieldID(socketClass, "impl", "Ljava/net/SocketImpl;"); - if (field_mImpl == NULL) { - LOGE("Can't find field impl in class java.net.Socket"); - return -1; - } - - jclass socketImplClass = env->FindClass("java/net/SocketImpl"); - if (socketImplClass == NULL) { - LOGE("Can't find class java.net.SocketImpl"); - return -1; - } - - field_mFD = env->GetFieldID(socketImplClass, "fd", "Ljava/io/FileDescriptor;"); - if (field_mFD == NULL) { - LOGE("Can't find field fd in java.net.SocketImpl"); - return -1; - } - - jclass fdclazz = env->FindClass("java/io/FileDescriptor"); - if (fdclazz == NULL) { - LOGE("Can't find java/io/FileDescriptor"); - return -1; - } - - field_descriptor = env->GetFieldID(fdclazz, "descriptor", "I"); - if (field_descriptor == NULL) { - LOGE("Can't find FileDescriptor.descriptor"); - return -1; - } - - int rc = jniRegisterNativeMethods(env, "org/apache/harmony/xnet/provider/jsse/OpenSSLSocketImpl", - sMethods, NELEM(sMethods)); - if (rc >= 0) { - // Note: do these after the registration of native methods, because - // there is a static method "initstatic" that's called when the - // OpenSSLSocketImpl class is first loaded, and that required - // a native method to be associated with it. - field_ssl_ctx = env->GetFieldID(clazz, "ssl_ctx", "I"); - if (field_ssl_ctx == NULL) { - LOGE("Can't find OpenSSLSocketImpl.ssl_ctx"); - return -1; - } - - field_ssl = env->GetFieldID(clazz, "ssl", "I"); - if (field_ssl == NULL) { - LOGE("Can't find OpenSSLSocketImpl.ssl"); - return -1; - } - - field_timeout = env->GetFieldID(clazz, "timeout", "I"); - if (field_timeout == NULL) { - LOGE("Can't find OpenSSLSocketImpl.timeout"); - return -1; - } - } - return rc; -} diff --git a/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_common.h b/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_common.h deleted file mode 100644 index e78cdd8..0000000 --- a/x-net/src/main/native/org_apache_harmony_xnet_provider_jsse_common.h +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (C) 2007 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. - */ - -#ifndef org_apache_harmony_xnet_provider_jsse_common_h -#define org_apache_harmony_xnet_provider_jsse_common_h - -#include <openssl/err.h> -#include <openssl/rand.h> -#include <openssl/ssl.h> - -#include <stdio.h> - -/** - * Structure to hold together useful JNI variables. - */ -struct mydata_t { - JNIEnv* env; - jobject object; -}; - -/** - * 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) -{ - SSL *ssl; - mydata_t *mydata; - jclass cls; - - jobjectArray objectArray; - - /* Get the correct index to the SSLobject stored into X509_STORE_CTX. */ - ssl = (SSL*)X509_STORE_CTX_get_ex_data(x509_store_ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); - - mydata = (mydata_t*)SSL_get_app_data(ssl); - - cls = mydata->env->GetObjectClass(mydata->object); - - jmethodID methodID = mydata->env->GetMethodID(cls, "verify_callback", "([[B)I"); - - objectArray = getcertificatebytes(mydata->env, x509_store_ctx->untrusted); - - mydata->env->CallIntMethod(mydata->object, methodID, objectArray); - - return 1; -} - -extern jobjectArray makeCipherList(JNIEnv* env, SSL_CTX* ssl); -extern void setEnabledCipherSuites(JNIEnv* env, jstring controlString, SSL_CTX* ssl_ctx); - -#endif diff --git a/x-net/src/main/native/sub.mk b/x-net/src/main/native/sub.mk index 4aeb41e..f5aa84a 100644 --- a/x-net/src/main/native/sub.mk +++ b/x-net/src/main/native/sub.mk @@ -3,10 +3,7 @@ # or BUILD_*_LIBRARY. LOCAL_SRC_FILES := \ - org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp \ - org_apache_harmony_xnet_provider_jsse_OpenSSLSessionImpl.cpp \ - org_apache_harmony_xnet_provider_jsse_OpenSSLServerSocketImpl.cpp \ - org_apache_harmony_xnet_provider_jsse_OpenSSLSocketImpl.cpp + org_apache_harmony_xnet_provider_jsse_NativeCrypto.cpp LOCAL_C_INCLUDES += \ external/openssl/include @@ -19,6 +16,6 @@ LOCAL_C_INCLUDES += \ LOCAL_SHARED_LIBRARIES += \ libcrypto \ libssl \ - libutils - + libutils + LOCAL_STATIC_LIBRARIES += |