/* * Copyright 2016 The Netty Project * * The Netty Project licenses this file to you 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. */ /* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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. */ #include #include #include "tcn.h" #include "apr_file_io.h" #include "apr_thread_mutex.h" #include "apr_atomic.h" #include "apr_strings.h" #include "apr_portable.h" #include "ssl_private.h" static int ssl_initialized = 0; extern apr_pool_t *tcn_global_pool; ENGINE *tcn_ssl_engine = NULL; void *SSL_temp_keys[SSL_TMP_KEY_MAX]; /* Global reference to the pool used by the dynamic mutexes */ static apr_pool_t *dynlockpool = NULL; /* Dynamic lock structure */ struct CRYPTO_dynlock_value { apr_pool_t *pool; const char* file; int line; apr_thread_mutex_t *mutex; }; struct TCN_bio_bytebuffer { // Pointer arithmetic is done on this variable. The type must correspond to a "byte" size. char* buffer; char* nonApplicationBuffer; jint nonApplicationBufferSize; jint nonApplicationBufferOffset; jint nonApplicationBufferLength; jint bufferLength; bool bufferIsSSLWriteSink; }; /* * Handle the Temporary RSA Keys and DH Params */ #define SSL_TMP_KEY_FREE(type, idx) \ if (SSL_temp_keys[idx]) { \ type##_free((type *)SSL_temp_keys[idx]); \ SSL_temp_keys[idx] = NULL; \ } else (void)(0) #define SSL_TMP_KEYS_FREE(type) \ SSL_TMP_KEY_FREE(type, SSL_TMP_KEY_##type##_512); \ SSL_TMP_KEY_FREE(type, SSL_TMP_KEY_##type##_1024); \ SSL_TMP_KEY_FREE(type, SSL_TMP_KEY_##type##_2048); \ SSL_TMP_KEY_FREE(type, SSL_TMP_KEY_##type##_4096) #define SSL_TMP_KEY_INIT_DH(bits) \ ssl_tmp_key_init_dh(bits, SSL_TMP_KEY_DH_##bits) #define SSL_TMP_KEYS_INIT(R) \ R |= SSL_TMP_KEY_INIT_DH(512); \ R |= SSL_TMP_KEY_INIT_DH(1024); \ R |= SSL_TMP_KEY_INIT_DH(2048); \ R |= SSL_TMP_KEY_INIT_DH(4096) /* * supported_ssl_opts is a bitmask that contains all supported SSL_OP_* * options at compile-time. This is used in hasOp to determine which * SSL_OP_* options are available at runtime. * * Note that at least up through OpenSSL 0.9.8o, checking SSL_OP_ALL will * return JNI_FALSE because SSL_OP_ALL is a mask that covers all bug * workarounds for OpenSSL including future workarounds that are defined * to be in the least-significant 3 nibbles of the SSL_OP_* bit space. * * This implementation has chosen NOT to simply set all those lower bits * so that the return value for SSL_OP_FUTURE_WORKAROUND will only be * reported by versions that actually support that specific workaround. */ static const jint supported_ssl_opts = 0 /* Specifically skip SSL_OP_ALL #ifdef SSL_OP_ALL | SSL_OP_ALL #endif */ #ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION | SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION #endif #ifdef SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_CIPHER_SERVER_PREFERENCE #endif #ifdef SSL_OP_CRYPTOPRO_TLSEXT_BUG | SSL_OP_CRYPTOPRO_TLSEXT_BUG #endif #ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS | SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS #endif #ifdef SSL_OP_LEGACY_SERVER_CONNECT | SSL_OP_LEGACY_SERVER_CONNECT #endif #ifdef SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER | SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER #endif #ifdef SSL_OP_MICROSOFT_SESS_ID_BUG | SSL_OP_MICROSOFT_SESS_ID_BUG #endif #ifdef SSL_OP_MSIE_SSLV2_RSA_PADDING | SSL_OP_MSIE_SSLV2_RSA_PADDING #endif #ifdef SSL_OP_NETSCAPE_CA_DN_BUG | SSL_OP_NETSCAPE_CA_DN_BUG #endif #ifdef SSL_OP_NETSCAPE_CHALLENGE_BUG | SSL_OP_NETSCAPE_CHALLENGE_BUG #endif #ifdef SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG | SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG #endif #ifdef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG | SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG #endif #ifdef SSL_OP_NO_COMPRESSION | SSL_OP_NO_COMPRESSION #endif #ifdef SSL_OP_NO_QUERY_MTU | SSL_OP_NO_QUERY_MTU #endif #ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION | SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION #endif #ifdef SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv2 #endif #ifdef SSL_OP_NO_SSLv3 | SSL_OP_NO_SSLv3 #endif #ifdef SSL_OP_NO_TICKET | SSL_OP_NO_TICKET #endif #ifdef SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1 #endif #ifdef SSL_OP_PKCS1_CHECK_1 | SSL_OP_PKCS1_CHECK_1 #endif #ifdef SSL_OP_PKCS1_CHECK_2 | SSL_OP_PKCS1_CHECK_2 #endif #ifdef SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1_1 #endif #ifdef SSL_OP_NO_TLSv1_2 | SSL_OP_NO_TLSv1_2 #endif #ifdef SSL_OP_SINGLE_DH_USE | SSL_OP_SINGLE_DH_USE #endif #ifdef SSL_OP_SINGLE_ECDH_USE | SSL_OP_SINGLE_ECDH_USE #endif #ifdef SSL_OP_SSLEAY_080_CLIENT_DH_BUG | SSL_OP_SSLEAY_080_CLIENT_DH_BUG #endif #ifdef SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG | SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG #endif #ifdef SSL_OP_TLS_BLOCK_PADDING_BUG | SSL_OP_TLS_BLOCK_PADDING_BUG #endif #ifdef SSL_OP_TLS_D5_BUG | SSL_OP_TLS_D5_BUG #endif #ifdef SSL_OP_TLS_ROLLBACK_BUG | SSL_OP_TLS_ROLLBACK_BUG #endif | 0; static jint tcn_flush_sslbuffer_to_bytebuffer(struct TCN_bio_bytebuffer* bioUserData) { jint writeAmount = TCN_MIN(bioUserData->bufferLength, bioUserData->nonApplicationBufferLength) * sizeof(char); jint writeChunk = bioUserData->nonApplicationBufferSize - bioUserData->nonApplicationBufferOffset; #ifdef NETTY_TCNATIVE_BIO_DEBUG fprintf(stderr, "tcn_flush_sslbuffer_to_bytebuffer1 bioUserData->nonApplicationBufferLength %d bioUserData->nonApplicationBufferOffset %d writeChunk %d writeAmount %d\n", bioUserData->nonApplicationBufferLength, bioUserData->nonApplicationBufferOffset, writeChunk, writeAmount); #endif // check if we need to account for wrap around when draining the internal SSL buffer. if (writeAmount > writeChunk) { jint newnonApplicationBufferOffset = writeAmount - writeChunk; memcpy(bioUserData->buffer, &bioUserData->nonApplicationBuffer[bioUserData->nonApplicationBufferOffset], (size_t) writeChunk); memcpy(&bioUserData->buffer[writeChunk], bioUserData->nonApplicationBuffer, (size_t) newnonApplicationBufferOffset); bioUserData->nonApplicationBufferOffset = newnonApplicationBufferOffset; } else { memcpy(bioUserData->buffer, &bioUserData->nonApplicationBuffer[bioUserData->nonApplicationBufferOffset], (size_t) writeAmount); bioUserData->nonApplicationBufferOffset += writeAmount; } bioUserData->nonApplicationBufferLength -= writeAmount; bioUserData->bufferLength -= writeAmount; bioUserData->buffer += writeAmount; // Pointer arithmetic based on char* type if (bioUserData->nonApplicationBufferLength == 0) { bioUserData->nonApplicationBufferOffset = 0; } #ifdef NETTY_TCNATIVE_BIO_DEBUG fprintf(stderr, "tcn_flush_sslbuffer_to_bytebuffer2 bioUserData->nonApplicationBufferLength %d bioUserData->nonApplicationBufferOffset %d\n", bioUserData->nonApplicationBufferLength, bioUserData->nonApplicationBufferOffset); #endif return writeAmount; } static jint tcn_write_to_bytebuffer(BIO* bio, const char* in, int inl) { jint writeAmount = 0; jint writeChunk; struct TCN_bio_bytebuffer* bioUserData = (struct TCN_bio_bytebuffer*) BIO_get_data(bio); TCN_ASSERT(bioUserData != NULL); #ifdef NETTY_TCNATIVE_BIO_DEBUG fprintf(stderr, "tcn_write_to_bytebuffer bioUserData->bufferIsSSLWriteSink %d inl %d [%.*s]\n", bioUserData->bufferIsSSLWriteSink, inl, inl, in); #endif if (in == NULL || inl <= 0) { return 0; } // If the buffer is currently being used for reading then we have to use the internal SSL buffer to queue the data. if (!bioUserData->bufferIsSSLWriteSink) { jint nonApplicationBufferFreeSpace = bioUserData->nonApplicationBufferSize - bioUserData->nonApplicationBufferLength; jint startIndex; #ifdef NETTY_TCNATIVE_BIO_DEBUG fprintf(stderr, "tcn_write_to_bytebuffer nonApplicationBufferFreeSpace %d\n", nonApplicationBufferFreeSpace); #endif if (nonApplicationBufferFreeSpace == 0) { BIO_set_retry_write(bio); /* buffer is full */ return -1; } writeAmount = TCN_MIN(nonApplicationBufferFreeSpace, (jint) inl) * sizeof(char); startIndex = bioUserData->nonApplicationBufferOffset + bioUserData->nonApplicationBufferLength; writeChunk = bioUserData->nonApplicationBufferSize - startIndex; #ifdef NETTY_TCNATIVE_BIO_DEBUG fprintf(stderr, "tcn_write_to_bytebuffer bioUserData->nonApplicationBufferLength %d bioUserData->nonApplicationBufferOffset %d startIndex %d writeChunk %d writeAmount %d\n", bioUserData->nonApplicationBufferLength, bioUserData->nonApplicationBufferOffset, startIndex, writeChunk, writeAmount); #endif // check if the write will wrap around the buffer. if (writeAmount > writeChunk) { memcpy(&bioUserData->nonApplicationBuffer[startIndex], in, (size_t) writeChunk); memcpy(bioUserData->nonApplicationBuffer, &in[writeChunk], (size_t) (writeAmount - writeChunk)); } else { memcpy(&bioUserData->nonApplicationBuffer[startIndex], in, (size_t) writeAmount); } bioUserData->nonApplicationBufferLength += writeAmount; // This write amount will not be used by Java, and doesn't correlate to the ByteBuffer source. // The internal SSL buffer exists because a SSL_read operation may actually write data (e.g. handshake). return writeAmount; } if (bioUserData->buffer == NULL || bioUserData->bufferLength == 0) { BIO_set_retry_write(bio); /* no buffer to write into */ return -1; } // First check if we need to drain data queued in the internal SSL buffer. if (bioUserData->nonApplicationBufferLength != 0) { writeAmount = tcn_flush_sslbuffer_to_bytebuffer(bioUserData); } // Next write "in" into what ever space the ByteBuffer has available. writeChunk = TCN_MIN(bioUserData->bufferLength, (jint) inl) * sizeof(char); #ifdef NETTY_TCNATIVE_BIO_DEBUG fprintf(stderr, "tcn_write_to_bytebuffer2 writeChunk %d\n", writeChunk); #endif memcpy(bioUserData->buffer, in, (size_t) writeChunk); bioUserData->bufferLength -= writeChunk; bioUserData->buffer += writeChunk; // Pointer arithmetic based on char* type return writeAmount + writeChunk; } static jint tcn_read_from_bytebuffer(BIO* bio, char *out, int outl) { jint readAmount; struct TCN_bio_bytebuffer* bioUserData = (struct TCN_bio_bytebuffer*) BIO_get_data(bio); TCN_ASSERT(bioUserData != NULL); #ifdef NETTY_TCNATIVE_BIO_DEBUG fprintf(stderr, "tcn_read_from_bytebuffer bioUserData->bufferIsSSLWriteSink %d outl %d [%.*s]\n", bioUserData->bufferIsSSLWriteSink, outl, outl, out); #endif if (out == NULL || outl <= 0) { return 0; } if (bioUserData->bufferIsSSLWriteSink || bioUserData->buffer == NULL || bioUserData->bufferLength == 0) { // During handshake this may happen, and it means we are not setup to read yet. BIO_set_retry_read(bio); return -1; } readAmount = TCN_MIN(bioUserData->bufferLength, (jint) outl) * sizeof(char); #ifdef NETTY_TCNATIVE_BIO_DEBUG fprintf(stderr, "tcn_read_from_bytebuffer readAmount %d\n", readAmount); #endif memcpy(out, bioUserData->buffer, (size_t) readAmount); bioUserData->bufferLength -= readAmount; bioUserData->buffer += readAmount; // Pointer arithmetic based on char* type return readAmount; } static int bio_java_bytebuffer_create(BIO* bio) { struct TCN_bio_bytebuffer* bioUserData = (struct TCN_bio_bytebuffer*) OPENSSL_malloc(sizeof(struct TCN_bio_bytebuffer)); if (bioUserData == NULL) { return 0; } // The actual ByteBuffer is set from java and may be swapped out for each operation. bioUserData->buffer = NULL; bioUserData->bufferLength = 0; bioUserData->bufferIsSSLWriteSink = false; bioUserData->nonApplicationBuffer = NULL; bioUserData->nonApplicationBufferSize = 0; bioUserData->nonApplicationBufferOffset = 0; bioUserData->nonApplicationBufferLength = 0; BIO_set_data(bio, bioUserData); // In order to for OpenSSL to properly manage the lifetime of a BIO it relies on some shutdown and init state. // The behavior expected by OpenSSL can be found here: https://www.openssl.org/docs/man1.1.0/crypto/BIO_set_data.html BIO_set_shutdown(bio, 1); BIO_set_init(bio, 1); return 1; } static int bio_java_bytebuffer_destroy(BIO* bio) { struct TCN_bio_bytebuffer* bioUserData; if (bio == NULL) { return 0; } bioUserData = (struct TCN_bio_bytebuffer*) BIO_get_data(bio); if (bioUserData == NULL) { return 1; } if (bioUserData->nonApplicationBuffer != NULL) { OPENSSL_free(bioUserData->nonApplicationBuffer); bioUserData->nonApplicationBuffer = NULL; } // The buffer is not owned by tcn, so just free the native memory. OPENSSL_free(bioUserData); BIO_set_data(bio, NULL); return 1; } static int bio_java_bytebuffer_write(BIO* bio, const char* in, int inl) { BIO_clear_retry_flags(bio); return (int) tcn_write_to_bytebuffer(bio, in, inl); } static int bio_java_bytebuffer_read(BIO* bio, char* out, int outl) { BIO_clear_retry_flags(bio); return (int) tcn_read_from_bytebuffer(bio, out, outl); } static int bio_java_bytebuffer_puts(BIO* bio, const char *in) { BIO_clear_retry_flags(bio); return (int) tcn_write_to_bytebuffer(bio, in, strlen(in)); } static int bio_java_bytebuffer_gets(BIO* b, char* out, int outl) { // Not supported https://www.openssl.org/docs/man1.0.2/crypto/BIO_write.html return -2; } static long bio_java_bytebuffer_ctrl(BIO* bio, int cmd, long num, void* ptr) { // see https://www.openssl.org/docs/man1.0.1/crypto/BIO_ctrl.html switch (cmd) { case BIO_CTRL_GET_CLOSE: return (long) BIO_get_shutdown(bio); case BIO_CTRL_SET_CLOSE: BIO_set_shutdown(bio, (int) num); return 1; case BIO_CTRL_FLUSH: return 1; default: return 0; } } TCN_IMPLEMENT_CALL(jint, SSL, bioLengthByteBuffer)(TCN_STDARGS, jlong bioAddress) { BIO* bio = J2P(bioAddress, BIO*); struct TCN_bio_bytebuffer* bioUserData; if (bio == NULL) { tcn_ThrowException(e, "bio is null"); return 0; } bioUserData = (struct TCN_bio_bytebuffer*) BIO_get_data(bio); return bioUserData == NULL ? 0 : bioUserData->bufferLength; } TCN_IMPLEMENT_CALL(jint, SSL, bioLengthNonApplication)(TCN_STDARGS, jlong bioAddress) { BIO* bio = J2P(bioAddress, BIO*); struct TCN_bio_bytebuffer* bioUserData; if (bio == NULL) { tcn_ThrowException(e, "bio is null"); return 0; } bioUserData = (struct TCN_bio_bytebuffer*) BIO_get_data(bio); return bioUserData == NULL ? 0 : bioUserData->nonApplicationBufferLength; } #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) static BIO_METHOD bio_java_bytebuffer_methods = { BIO_TYPE_MEM, "Java ByteBuffer", bio_java_bytebuffer_write, bio_java_bytebuffer_read, bio_java_bytebuffer_puts, bio_java_bytebuffer_gets, bio_java_bytebuffer_ctrl, bio_java_bytebuffer_create, bio_java_bytebuffer_destroy, NULL }; #else static BIO_METHOD* bio_java_bytebuffer_methods = NULL; static void init_bio_methods(void) { bio_java_bytebuffer_methods = BIO_meth_new(BIO_TYPE_MEM, "Java ByteBuffer"); BIO_meth_set_write(bio_java_bytebuffer_methods, &bio_java_bytebuffer_write); BIO_meth_set_read(bio_java_bytebuffer_methods, &bio_java_bytebuffer_read); BIO_meth_set_puts(bio_java_bytebuffer_methods, &bio_java_bytebuffer_puts); BIO_meth_set_gets(bio_java_bytebuffer_methods, &bio_java_bytebuffer_gets); BIO_meth_set_ctrl(bio_java_bytebuffer_methods, &bio_java_bytebuffer_ctrl); BIO_meth_set_create(bio_java_bytebuffer_methods, &bio_java_bytebuffer_create); BIO_meth_set_destroy(bio_java_bytebuffer_methods, &bio_java_bytebuffer_destroy); } static void free_bio_methods(void) { BIO_meth_free(bio_java_bytebuffer_methods); } #endif static BIO_METHOD* BIO_java_bytebuffer() { #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) return &bio_java_bytebuffer_methods; #else return bio_java_bytebuffer_methods; #endif } static int ssl_tmp_key_init_dh(int bits, int idx) { #if (OPENSSL_VERSION_NUMBER < 0x10100000L) || defined(OPENSSL_USE_DEPRECATED) || defined(LIBRESSL_VERSION_NUMBER) return (SSL_temp_keys[idx] = SSL_dh_get_tmp_param(bits)) ? 0 : 1; #else return 0; #endif } TCN_IMPLEMENT_CALL(jint, SSL, version)(TCN_STDARGS) { UNREFERENCED_STDARGS; return OpenSSL_version_num(); } TCN_IMPLEMENT_CALL(jstring, SSL, versionString)(TCN_STDARGS) { UNREFERENCED(o); return AJP_TO_JSTRING(OpenSSL_version(OPENSSL_VERSION)); } /* * the various processing hooks */ static apr_status_t ssl_init_cleanup(void *data) { UNREFERENCED(data); if (!ssl_initialized) return APR_SUCCESS; ssl_initialized = 0; SSL_TMP_KEYS_FREE(DH); /* * Try to kill the internals of the SSL library. */ #if OPENSSL_VERSION_NUMBER >= 0x00907001 && !defined(OPENSSL_IS_BORINGSSL) /* Corresponds to OPENSSL_load_builtin_modules(): * XXX: borrowed from apps.h, but why not CONF_modules_free() * which also invokes CONF_modules_finish()? */ CONF_modules_unload(1); #endif /* Corresponds to SSL_library_init: */ EVP_cleanup(); #if HAVE_ENGINE_LOAD_BUILTIN_ENGINES ENGINE_cleanup(); #endif #if OPENSSL_VERSION_NUMBER >= 0x00907001 CRYPTO_cleanup_all_ex_data(); #endif #if OPENSSL_VERSION_NUMBER < 0x10100000L ERR_remove_thread_state(NULL); #endif #if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) free_bio_methods(); #endif /* Don't call ERR_free_strings here; ERR_load_*_strings only * actually load the error strings once per process due to static * variable abuse in OpenSSL. */ /* * TODO: determine somewhere we can safely shove out diagnostics * (when enabled) at this late stage in the game: * CRYPTO_mem_leaks_fp(stderr); */ return APR_SUCCESS; } #ifndef OPENSSL_NO_ENGINE /* Try to load an engine in a shareable library */ static ENGINE *ssl_try_load_engine(const char *engine) { ENGINE *e = ENGINE_by_id("dynamic"); if (e) { if (!ENGINE_ctrl_cmd_string(e, "SO_PATH", engine, 0) || !ENGINE_ctrl_cmd_string(e, "LOAD", NULL, 0)) { ENGINE_free(e); e = NULL; } } return e; } #endif /* * To ensure thread-safetyness in OpenSSL */ static apr_thread_mutex_t **ssl_lock_cs; static int ssl_lock_num_locks; static void ssl_thread_lock(int mode, int type, const char *file, int line) { UNREFERENCED(file); UNREFERENCED(line); if (type < ssl_lock_num_locks) { if (mode & CRYPTO_LOCK) { apr_thread_mutex_lock(ssl_lock_cs[type]); } else { apr_thread_mutex_unlock(ssl_lock_cs[type]); } } } static unsigned long ssl_thread_id(void) { /* OpenSSL needs this to return an unsigned long. On OS/390, the pthread * id is a structure twice that big. Use the TCB pointer instead as a * unique unsigned long. */ #ifdef __MVS__ struct PSA { char unmapped[540]; unsigned long PSATOLD; } *psaptr = 0; return psaptr->PSATOLD; #elif defined(WIN32) return (unsigned long)GetCurrentThreadId(); #else return (unsigned long)(apr_os_thread_current()); #endif } static void ssl_set_thread_id(CRYPTO_THREADID *id) { CRYPTO_THREADID_set_numeric(id, ssl_thread_id()); } static apr_status_t ssl_thread_cleanup(void *data) { UNREFERENCED(data); CRYPTO_set_locking_callback(NULL); CRYPTO_THREADID_set_callback(NULL); CRYPTO_set_dynlock_create_callback(NULL); CRYPTO_set_dynlock_lock_callback(NULL); CRYPTO_set_dynlock_destroy_callback(NULL); dynlockpool = NULL; /* Let the registered mutex cleanups do their own thing */ return APR_SUCCESS; } /* * Dynamic lock creation callback */ static struct CRYPTO_dynlock_value *ssl_dyn_create_function(const char *file, int line) { struct CRYPTO_dynlock_value *value; apr_pool_t *p; apr_status_t rv; /* * We need a pool to allocate our mutex. Since we can't clear * allocated memory from a pool, create a subpool that we can blow * away in the destruction callback. */ rv = apr_pool_create(&p, dynlockpool); if (rv != APR_SUCCESS) { /* TODO log that fprintf(stderr, "Failed to create subpool for dynamic lock"); */ return NULL; } value = (struct CRYPTO_dynlock_value *)apr_palloc(p, sizeof(struct CRYPTO_dynlock_value)); if (!value) { /* TODO log that fprintf(stderr, "Failed to allocate dynamic lock structure"); */ return NULL; } value->pool = p; /* Keep our own copy of the place from which we were created, using our own pool. */ value->file = apr_pstrdup(p, file); value->line = line; rv = apr_thread_mutex_create(&(value->mutex), APR_THREAD_MUTEX_DEFAULT, p); if (rv != APR_SUCCESS) { /* TODO log that fprintf(stderr, "Failed to create thread mutex for dynamic lock"); */ apr_pool_destroy(p); return NULL; } return value; } /* * Dynamic locking and unlocking function */ static void ssl_dyn_lock_function(int mode, struct CRYPTO_dynlock_value *l, const char *file, int line) { if (mode & CRYPTO_LOCK) { apr_thread_mutex_lock(l->mutex); } else { apr_thread_mutex_unlock(l->mutex); } } /* * Dynamic lock destruction callback */ static void ssl_dyn_destroy_function(struct CRYPTO_dynlock_value *l, const char *file, int line) { apr_status_t rv; rv = apr_thread_mutex_destroy(l->mutex); if (rv != APR_SUCCESS) { /* TODO log that fprintf(stderr, "Failed to destroy mutex for dynamic lock %s:%d", l->file, l->line); */ } /* Trust that whomever owned the CRYPTO_dynlock_value we were * passed has no future use for it... */ apr_pool_destroy(l->pool); } static void ssl_thread_setup(apr_pool_t *p) { int i; ssl_lock_num_locks = CRYPTO_num_locks(); ssl_lock_cs = apr_palloc(p, ssl_lock_num_locks * sizeof(*ssl_lock_cs)); for (i = 0; i < ssl_lock_num_locks; i++) { apr_thread_mutex_create(&(ssl_lock_cs[i]), APR_THREAD_MUTEX_DEFAULT, p); } CRYPTO_THREADID_set_callback(ssl_set_thread_id); CRYPTO_set_locking_callback(ssl_thread_lock); /* Set up dynamic locking scaffolding for OpenSSL to use at its * convenience. */ dynlockpool = p; CRYPTO_set_dynlock_create_callback(ssl_dyn_create_function); CRYPTO_set_dynlock_lock_callback(ssl_dyn_lock_function); CRYPTO_set_dynlock_destroy_callback(ssl_dyn_destroy_function); apr_pool_cleanup_register(p, NULL, ssl_thread_cleanup, apr_pool_cleanup_null); } TCN_IMPLEMENT_CALL(jint, SSL, initialize)(TCN_STDARGS, jstring engine) { int r = 0; TCN_ALLOC_CSTRING(engine); UNREFERENCED(o); if (!tcn_global_pool) { TCN_FREE_CSTRING(engine); tcn_ThrowAPRException(e, APR_EINVAL); return (jint)APR_EINVAL; } /* Check if already initialized */ if (ssl_initialized++) { TCN_FREE_CSTRING(engine); return (jint)APR_SUCCESS; } #if OPENSSL_VERSION_NUMBER < 0x10100000L if (SSLeay() < 0x0090700L) { TCN_FREE_CSTRING(engine); tcn_ThrowAPRException(e, APR_EINVAL); ssl_initialized = 0; return (jint)APR_EINVAL; } #endif #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined(LIBRESSL_VERSION_NUMBER) /* We must register the library in full, to ensure our configuration * code can successfully test the SSL environment. */ OPENSSL_malloc_init(); #endif ERR_load_crypto_strings(); SSL_load_error_strings(); SSL_library_init(); OpenSSL_add_all_algorithms(); #if HAVE_ENGINE_LOAD_BUILTIN_ENGINES ENGINE_load_builtin_engines(); #endif #if OPENSSL_VERSION_NUMBER >= 0x00907001 OPENSSL_load_builtin_modules(); #endif /* Initialize thread support */ ssl_thread_setup(tcn_global_pool); #ifndef OPENSSL_NO_ENGINE if (J2S(engine)) { ENGINE *ee = NULL; apr_status_t err = APR_SUCCESS; if(strcmp(J2S(engine), "auto") == 0) { ENGINE_register_all_complete(); } else { if ((ee = ENGINE_by_id(J2S(engine))) == NULL && (ee = ssl_try_load_engine(J2S(engine))) == NULL) err = APR_ENOTIMPL; else { #ifdef ENGINE_CTRL_CHIL_SET_FORKCHECK if (strcmp(J2S(engine), "chil") == 0) ENGINE_ctrl(ee, ENGINE_CTRL_CHIL_SET_FORKCHECK, 1, 0, 0); #endif if (!ENGINE_set_default(ee, ENGINE_METHOD_ALL)) err = APR_ENOTIMPL; } /* Free our "structural" reference. */ if (ee) ENGINE_free(ee); } if (err != APR_SUCCESS) { TCN_FREE_CSTRING(engine); ssl_init_cleanup(NULL); tcn_ThrowAPRException(e, err); return (jint)err; } tcn_ssl_engine = ee; } #endif // For SSL_get_app_data*() at request time SSL_init_app_data_idx(); #if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) init_bio_methods(); #endif SSL_TMP_KEYS_INIT(r); if (r) { ERR_clear_error(); TCN_FREE_CSTRING(engine); ssl_init_cleanup(NULL); tcn_ThrowAPRException(e, APR_ENOTIMPL); return APR_ENOTIMPL; } /* * Let us cleanup the ssl library when the library is unloaded */ apr_pool_cleanup_register(tcn_global_pool, NULL, ssl_init_cleanup, apr_pool_cleanup_null); TCN_FREE_CSTRING(engine); return (jint)APR_SUCCESS; } TCN_IMPLEMENT_CALL(jlong, SSL, newMemBIO)(TCN_STDARGS) { BIO *bio = NULL; UNREFERENCED(o); // TODO: Use BIO_s_secmem() once included in stable release if ((bio = BIO_new(BIO_s_mem())) == NULL) { tcn_ThrowException(e, "Create BIO failed"); return 0; } return P2J(bio); } TCN_IMPLEMENT_CALL(jstring, SSL, getLastError)(TCN_STDARGS) { char buf[ERR_LEN]; UNREFERENCED(o); ERR_error_string(ERR_get_error(), buf); return tcn_new_string(e, buf); } TCN_IMPLEMENT_CALL(jboolean, SSL, hasOp)(TCN_STDARGS, jint op) { return op == (op & supported_ssl_opts) ? JNI_TRUE : JNI_FALSE; } /*** Begin Twitter 1:1 API addition ***/ TCN_IMPLEMENT_CALL(jint, SSL, getLastErrorNumber)(TCN_STDARGS) { UNREFERENCED_STDARGS; return ERR_get_error(); } static void ssl_info_callback(const SSL *ssl, int where, int ret) { int *handshakeCount = NULL; if (0 != (where & SSL_CB_HANDSHAKE_START)) { handshakeCount = (int*) SSL_get_app_data3((SSL*) ssl); if (handshakeCount != NULL) { ++(*handshakeCount); } } } TCN_IMPLEMENT_CALL(jlong /* SSL * */, SSL, newSSL)(TCN_STDARGS, jlong ctx /* tcn_ssl_ctxt_t * */, jboolean server) { SSL *ssl = NULL; int *handshakeCount = NULL; tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); if (c == NULL) { tcn_ThrowException(e, "ssl ctx is null"); return 0; } if (c->ctx == NULL) { tcn_ThrowException(e, "ctx is null"); return 0; } UNREFERENCED_STDARGS; ssl = SSL_new(c->ctx); if (ssl == NULL) { tcn_ThrowException(e, "cannot create new ssl"); return 0; } // Set the app_data2 before all the others because it may be used in SSL_free. SSL_set_app_data2(ssl, c); // Initially we will share the configuration from the SSLContext. // Set this before other app_data because there is no chance of failure, and if other app_data initialization fails // SSL_free maybe called and the state of this variable is assumed to be initalized. SSL_set_app_data4(ssl, &c->verify_config); // Store the handshakeCount in the SSL instance. handshakeCount = (int*) OPENSSL_malloc(sizeof(int)); if (handshakeCount == NULL) { SSL_free(ssl); tcn_ThrowException(e, "cannot create handshakeCount user data"); return 0; } *handshakeCount = 0; SSL_set_app_data3(ssl, handshakeCount); // Add callback to keep track of handshakes. SSL_CTX_set_info_callback(c->ctx, ssl_info_callback); if (server) { SSL_set_accept_state(ssl); } else { SSL_set_connect_state(ssl); } return P2J(ssl); } TCN_IMPLEMENT_CALL(jint, SSL, getError)(TCN_STDARGS, jlong ssl /* SSL * */, jint ret) { SSL *ssl_ = J2P(ssl, SSL *); if (ssl_ == NULL) { tcn_ThrowException(e, "ssl is null"); return 0; } UNREFERENCED_STDARGS; return SSL_get_error(ssl_, ret); } // Write wlen bytes from wbuf into bio TCN_IMPLEMENT_CALL(jint /* status */, SSL, bioWrite)(TCN_STDARGS, jlong bioAddress /* BIO* */, jlong wbufAddress /* char* */, jint wlen /* sizeof(wbuf) */) { BIO* bio = J2P(bioAddress, BIO*); void* wbuf = J2P(wbufAddress, void*); if (bio == NULL) { tcn_ThrowException(e, "bio is null"); return 0; } if (wbuf == NULL) { tcn_ThrowException(e, "wbuf is null"); return 0; } UNREFERENCED_STDARGS; return BIO_write(bio, wbuf, wlen); } TCN_IMPLEMENT_CALL(void, SSL, bioSetByteBuffer)(TCN_STDARGS, jlong bioAddress /* BIO* */, jlong bufferAddress /* Address for direct memory */, jint maxUsableBytes /* max number of bytes to use */, jboolean isSSLWriteSink) { BIO* bio = J2P(bioAddress, BIO*); char* buffer = J2P(bufferAddress, char*); struct TCN_bio_bytebuffer* bioUserData = NULL; TCN_ASSERT(bio != NULL); TCN_ASSERT(buffer != NULL); bioUserData = (struct TCN_bio_bytebuffer*) BIO_get_data(bio); TCN_ASSERT(bioUserData != NULL); bioUserData->buffer = buffer; bioUserData->bufferLength = maxUsableBytes; bioUserData->bufferIsSSLWriteSink = (bool) isSSLWriteSink; } TCN_IMPLEMENT_CALL(void, SSL, bioClearByteBuffer)(TCN_STDARGS, jlong bioAddress) { BIO* bio = J2P(bioAddress, BIO*); struct TCN_bio_bytebuffer* bioUserData = NULL; if (bio == NULL || (bioUserData = (struct TCN_bio_bytebuffer*) BIO_get_data(bio)) == NULL) { return; } bioUserData->buffer = NULL; bioUserData->bufferLength = 0; bioUserData->bufferIsSSLWriteSink = false; } TCN_IMPLEMENT_CALL(jint, SSL, bioFlushByteBuffer)(TCN_STDARGS, jlong bioAddress) { BIO* bio = J2P(bioAddress, BIO*); struct TCN_bio_bytebuffer* bioUserData; return (bio == NULL || (bioUserData = (struct TCN_bio_bytebuffer*) BIO_get_data(bio)) == NULL || bioUserData->nonApplicationBufferLength == 0 || bioUserData->buffer == NULL || !bioUserData->bufferIsSSLWriteSink) ? 0 : tcn_flush_sslbuffer_to_bytebuffer(bioUserData); } // Write up to wlen bytes of application data to the ssl BIO (encrypt) TCN_IMPLEMENT_CALL(jint /* status */, SSL, writeToSSL)(TCN_STDARGS, jlong ssl /* SSL * */, jlong wbuf /* char * */, jint wlen /* sizeof(wbuf) */) { SSL *ssl_ = J2P(ssl, SSL *); void *w = J2P(wbuf, void *); if (ssl_ == NULL) { tcn_ThrowException(e, "ssl is null"); return 0; } if (w == NULL) { tcn_ThrowException(e, "wbuf is null"); return 0; } UNREFERENCED_STDARGS; return SSL_write(ssl_, w, wlen); } // Read up to rlen bytes of application data from the given SSL BIO (decrypt) TCN_IMPLEMENT_CALL(jint /* status */, SSL, readFromSSL)(TCN_STDARGS, jlong ssl /* SSL * */, jlong rbuf /* char * */, jint rlen /* sizeof(rbuf) - 1 */) { SSL *ssl_ = J2P(ssl, SSL *); void *r = J2P(rbuf, void *); if (ssl_ == NULL) { tcn_ThrowException(e, "ssl is null"); return 0; } if (r == NULL) { tcn_ThrowException(e, "rbuf is null"); return 0; } UNREFERENCED_STDARGS; return SSL_read(ssl_, r, rlen); } // Get the shutdown status of the engine TCN_IMPLEMENT_CALL(jint /* status */, SSL, getShutdown)(TCN_STDARGS, jlong ssl /* SSL * */) { SSL *ssl_ = J2P(ssl, SSL *); if (ssl_ == NULL) { tcn_ThrowException(e, "ssl is null"); return 0; } UNREFERENCED_STDARGS; return SSL_get_shutdown(ssl_); } // Called when the peer closes the connection TCN_IMPLEMENT_CALL(void, SSL, setShutdown)(TCN_STDARGS, jlong ssl /* SSL * */, jint mode) { SSL *ssl_ = J2P(ssl, SSL *); if (ssl_ == NULL) { tcn_ThrowException(e, "ssl is null"); return; } UNREFERENCED_STDARGS; SSL_set_shutdown(ssl_, mode); } // Free the SSL * and its associated internal BIO TCN_IMPLEMENT_CALL(void, SSL, freeSSL)(TCN_STDARGS, jlong ssl /* SSL * */) { int *handshakeCount = NULL; tcn_ssl_ctxt_t* c = NULL; tcn_ssl_verify_config_t* verify_config = NULL; SSL *ssl_ = J2P(ssl, SSL *); if (ssl_ == NULL) { tcn_ThrowException(e, "ssl is null"); return; } c = SSL_get_app_data2(ssl_); handshakeCount = SSL_get_app_data3(ssl_); verify_config = SSL_get_app_data4(ssl_); UNREFERENCED_STDARGS; TCN_ASSERT(c != NULL); if (handshakeCount != NULL) { OPENSSL_free(handshakeCount); SSL_set_app_data3(ssl_, NULL); } // Only free the verify_config if it is not shared with the SSLContext. if (verify_config != NULL && verify_config != &c->verify_config) { OPENSSL_free(verify_config); SSL_set_app_data4(ssl_, &c->verify_config); } SSL_free(ssl_); } TCN_IMPLEMENT_CALL(jlong, SSL, bioNewByteBuffer)(TCN_STDARGS, jlong ssl /* SSL* */, jint nonApplicationBufferSize) { SSL* ssl_ = J2P(ssl, SSL*); BIO* bio; struct TCN_bio_bytebuffer* bioUserData; if (ssl_ == NULL) { tcn_ThrowException(e, "ssl is null"); return 0; } if (nonApplicationBufferSize <= 0) { tcn_ThrowException(e, "nonApplicationBufferSize <= 0"); return 0; } bio = BIO_new(BIO_java_bytebuffer()); if (bio == NULL) { tcn_ThrowException(e, "BIO_new failed"); return 0; } bioUserData = BIO_get_data(bio); if (bioUserData == NULL) { BIO_free(bio); tcn_ThrowException(e, "BIO_get_data failed"); return 0; } bioUserData->nonApplicationBuffer = (char*) OPENSSL_malloc(nonApplicationBufferSize * sizeof(char)); if (bioUserData->nonApplicationBuffer == NULL) { BIO_free(bio); tcn_Throw(e, "Failed to allocate internal buffer of size %d", nonApplicationBufferSize); return 0; } bioUserData->nonApplicationBufferSize = nonApplicationBufferSize; SSL_set_bio(ssl_, bio, bio); return P2J(bio); } // Free a BIO * (typically, the network BIO) TCN_IMPLEMENT_CALL(void, SSL, freeBIO)(TCN_STDARGS, jlong bio /* BIO * */) { BIO *bio_ = J2P(bio, BIO *); UNREFERENCED_STDARGS; if (bio_ != NULL) { BIO_free(bio_); } } // Send CLOSE_NOTIFY to peer TCN_IMPLEMENT_CALL(jint /* status */, SSL, shutdownSSL)(TCN_STDARGS, jlong ssl /* SSL * */) { SSL *ssl_ = J2P(ssl, SSL *); if (ssl_ == NULL) { tcn_ThrowException(e, "ssl is null"); return 0; } UNREFERENCED_STDARGS; return SSL_shutdown(ssl_); } // Read which cipher was negotiated for the given SSL *. TCN_IMPLEMENT_CALL(jstring, SSL, getCipherForSSL)(TCN_STDARGS, jlong ssl /* SSL * */) { SSL *ssl_ = J2P(ssl, SSL *); if (ssl_ == NULL) { tcn_ThrowException(e, "ssl is null"); return NULL; } UNREFERENCED_STDARGS; return AJP_TO_JSTRING(SSL_get_cipher(ssl_)); } // Read which protocol was negotiated for the given SSL *. TCN_IMPLEMENT_CALL(jstring, SSL, getVersion)(TCN_STDARGS, jlong ssl /* SSL * */) { SSL *ssl_ = J2P(ssl, SSL *); if (ssl_ == NULL) { tcn_ThrowException(e, "ssl is null"); return NULL; } UNREFERENCED_STDARGS; return AJP_TO_JSTRING(SSL_get_version(ssl_)); } // Is the handshake over yet? TCN_IMPLEMENT_CALL(jint, SSL, isInInit)(TCN_STDARGS, jlong ssl /* SSL * */) { SSL *ssl_ = J2P(ssl, SSL *); if (ssl_ == NULL) { tcn_ThrowException(e, "ssl is null"); return 0; } UNREFERENCED(o); return SSL_in_init(ssl_); } TCN_IMPLEMENT_CALL(jint, SSL, doHandshake)(TCN_STDARGS, jlong ssl /* SSL * */) { SSL *ssl_ = J2P(ssl, SSL *); if (ssl_ == NULL) { tcn_ThrowException(e, "ssl is null"); return 0; } UNREFERENCED(o); return SSL_do_handshake(ssl_); } // Read which protocol was negotiated for the given SSL *. TCN_IMPLEMENT_CALL(jstring, SSL, getNextProtoNegotiated)(TCN_STDARGS, jlong ssl /* SSL * */) { SSL *ssl_ = J2P(ssl, SSL *); const unsigned char *proto; unsigned int proto_len; if (ssl_ == NULL) { tcn_ThrowException(e, "ssl is null"); return NULL; } UNREFERENCED(o); SSL_get0_next_proto_negotiated(ssl_, &proto, &proto_len); return tcn_new_stringn(e, (char*) proto, proto_len); } /*** End Twitter API Additions ***/ /*** Apple API Additions ***/ TCN_IMPLEMENT_CALL(jstring, SSL, getAlpnSelected)(TCN_STDARGS, jlong ssl /* SSL * */) { // Use weak linking with GCC as this will alow us to run the same packaged version with multiple // version of openssl. #if defined(__GNUC__) || defined(__GNUG__) if (!SSL_get0_alpn_selected) { UNREFERENCED(o); UNREFERENCED(ssl); return NULL; } #endif // We can only support it when either use openssl version >= 1.0.2 or GCC as this way we can use weak linking #if OPENSSL_VERSION_NUMBER >= 0x10002000L || defined(__GNUC__) || defined(__GNUG__) SSL *ssl_ = J2P(ssl, SSL *); const unsigned char *proto; unsigned int proto_len; if (ssl_ == NULL) { tcn_ThrowException(e, "ssl is null"); return NULL; } UNREFERENCED(o); SSL_get0_alpn_selected(ssl_, &proto, &proto_len); return tcn_new_stringn(e, (char*) proto, proto_len); #else UNREFERENCED(o); UNREFERENCED(ssl); return NULL; #endif } TCN_IMPLEMENT_CALL(jobjectArray, SSL, getPeerCertChain)(TCN_STDARGS, jlong ssl /* SSL * */) { STACK_OF(X509) *sk; int len; int i; X509 *cert; int length; unsigned char *buf; jobjectArray array; jbyteArray bArray; jclass byteArrayClass = tcn_get_byte_array_class(); SSL *ssl_ = J2P(ssl, SSL *); if (ssl_ == NULL) { tcn_ThrowException(e, "ssl is null"); return NULL; } UNREFERENCED(o); // Get a stack of all certs in the chain. sk = SSL_get_peer_cert_chain(ssl_); len = sk_X509_num(sk); if (len <= 0) { // No peer certificate chain as no auth took place yet, or the auth was not successful. return NULL; } // Create the byte[][] array that holds all the certs array = (*e)->NewObjectArray(e, len, byteArrayClass, NULL); for(i = 0; i < len; i++) { cert = sk_X509_value(sk, i); buf = NULL; length = i2d_X509(cert, &buf); if (length < 0) { if (buf != NULL) { OPENSSL_free(buf); } // In case of error just return an empty byte[][] return (*e)->NewObjectArray(e, 0, byteArrayClass, NULL); } bArray = (*e)->NewByteArray(e, length); (*e)->SetByteArrayRegion(e, bArray, 0, length, (jbyte*) buf); (*e)->SetObjectArrayElement(e, array, i, bArray); // Delete the local reference as we not know how long the chain is and local references are otherwise // only freed once jni method returns. (*e)->DeleteLocalRef(e, bArray); OPENSSL_free(buf); } return array; } TCN_IMPLEMENT_CALL(jbyteArray, SSL, getPeerCertificate)(TCN_STDARGS, jlong ssl /* SSL * */) { X509 *cert; int length; unsigned char *buf = NULL; jbyteArray bArray; SSL *ssl_ = J2P(ssl, SSL *); if (ssl_ == NULL) { tcn_ThrowException(e, "ssl is null"); return NULL; } UNREFERENCED(o); // Get a stack of all certs in the chain cert = SSL_get_peer_certificate(ssl_); if (cert == NULL) { return NULL; } length = i2d_X509(cert, &buf); bArray = (*e)->NewByteArray(e, length); (*e)->SetByteArrayRegion(e, bArray, 0, length, (jbyte*) buf); // We need to free the cert as the reference count is incremented by one and it is not destroyed when the // session is freed. // See https://www.openssl.org/docs/ssl/SSL_get_peer_certificate.html X509_free(cert); OPENSSL_free(buf); return bArray; } TCN_IMPLEMENT_CALL(jstring, SSL, getErrorString)(TCN_STDARGS, jlong number) { char buf[ERR_LEN]; UNREFERENCED(o); ERR_error_string(number, buf); return tcn_new_string(e, buf); } TCN_IMPLEMENT_CALL(jlong, SSL, getTime)(TCN_STDARGS, jlong ssl) { SSL *ssl_ = J2P(ssl, SSL *); SSL_SESSION *session = NULL; if (ssl_ == NULL) { tcn_ThrowException(e, "ssl is null"); return 0; } session = SSL_get_session(ssl_); if (session == NULL) { // BoringSSL does not protect against a NULL session. OpenSSL // returns 0 if the session is NULL, so do that here. return 0; } UNREFERENCED(o); return SSL_get_time(session); } TCN_IMPLEMENT_CALL(jlong, SSL, getTimeout)(TCN_STDARGS, jlong ssl) { SSL *ssl_ = J2P(ssl, SSL *); SSL_SESSION *session = NULL; if (ssl_ == NULL) { tcn_ThrowException(e, "ssl is null"); return 0; } session = SSL_get_session(ssl_); if (session == NULL) { // BoringSSL does not protect against a NULL session. OpenSSL // returns 0 if the session is NULL, so do that here. return 0; } UNREFERENCED(o); return SSL_get_timeout(session); } TCN_IMPLEMENT_CALL(jlong, SSL, setTimeout)(TCN_STDARGS, jlong ssl, jlong seconds) { SSL *ssl_ = J2P(ssl, SSL *); SSL_SESSION *session = NULL; if (ssl_ == NULL) { tcn_ThrowException(e, "ssl is null"); return 0; } session = SSL_get_session(ssl_); if (session == NULL) { // BoringSSL does not protect against a NULL session. OpenSSL // returns 0 if the session is NULL, so do that here. return 0; } UNREFERENCED(o); return SSL_set_timeout(session, seconds); } TCN_IMPLEMENT_CALL(void, SSL, setVerify)(TCN_STDARGS, jlong ssl, jint level, jint depth) { tcn_ssl_verify_config_t* verify_config; tcn_ssl_ctxt_t* c; SSL *ssl_ = J2P(ssl, SSL *); if (ssl_ == NULL) { tcn_ThrowException(e, "ssl is null"); return; } c = SSL_get_app_data2(ssl_); verify_config = SSL_get_app_data4(ssl_); UNREFERENCED(o); TCN_ASSERT(c != NULL); TCN_ASSERT(verify_config != NULL); // If we are sharing the configuration from the SSLContext we now need to create a new configuration just for this SSL. if (verify_config == &c->verify_config) { verify_config = (tcn_ssl_verify_config_t*) OPENSSL_malloc(sizeof(tcn_ssl_verify_config_t)); if (verify_config == NULL) { tcn_ThrowException(e, "failed to allocate tcn_ssl_verify_config_t"); return; } // Copy the verify depth form the context in case depth is <0. verify_config->verify_depth = c->verify_config.verify_depth; SSL_set_app_data4(ssl_, verify_config); } // No need to specify a callback for SSL_set_verify because we override the default certificate verification via SSL_CTX_set_cert_verify_callback. SSL_set_verify(ssl_, tcn_set_verify_config(verify_config, level, depth), NULL); SSL_set_verify_depth(ssl_, verify_config->verify_depth); } TCN_IMPLEMENT_CALL(void, SSL, setOptions)(TCN_STDARGS, jlong ssl, jint opt) { SSL *ssl_ = J2P(ssl, SSL *); if (ssl_ == NULL) { tcn_ThrowException(e, "ssl is null"); return; } UNREFERENCED_STDARGS; SSL_set_options(ssl_, opt); } TCN_IMPLEMENT_CALL(void, SSL, clearOptions)(TCN_STDARGS, jlong ssl, jint opt) { SSL *ssl_ = J2P(ssl, SSL *); if (ssl_ == NULL) { tcn_ThrowException(e, "ssl is null"); return; } UNREFERENCED_STDARGS; SSL_clear_options(ssl_, opt); } TCN_IMPLEMENT_CALL(jint, SSL, getOptions)(TCN_STDARGS, jlong ssl) { SSL *ssl_ = J2P(ssl, SSL *); if (ssl_ == NULL) { tcn_ThrowException(e, "ssl is null"); return 0; } UNREFERENCED_STDARGS; return SSL_get_options(ssl_); } TCN_IMPLEMENT_CALL(jobjectArray, SSL, getCiphers)(TCN_STDARGS, jlong ssl) { STACK_OF(SSL_CIPHER) *sk; int len; jobjectArray array; const SSL_CIPHER *cipher; const char *name; int i; jstring c_name; SSL *ssl_ = J2P(ssl, SSL *); if (ssl_ == NULL) { tcn_ThrowException(e, "ssl is null"); return NULL; } UNREFERENCED_STDARGS; sk = SSL_get_ciphers(ssl_); len = sk_SSL_CIPHER_num(sk); if (len <= 0) { // No peer certificate chain as no auth took place yet, or the auth was not successful. return NULL; } // Create the byte[][] array that holds all the certs array = (*e)->NewObjectArray(e, len, tcn_get_string_class(), NULL); for (i = 0; i < len; i++) { cipher = sk_SSL_CIPHER_value(sk, i); name = SSL_CIPHER_get_name(cipher); c_name = (*e)->NewStringUTF(e, name); (*e)->SetObjectArrayElement(e, array, i, c_name); } return array; } TCN_IMPLEMENT_CALL(jboolean, SSL, setCipherSuites)(TCN_STDARGS, jlong ssl, jstring ciphers) { jboolean rv = JNI_TRUE; TCN_ALLOC_CSTRING(ciphers); SSL *ssl_ = J2P(ssl, SSL *); if (ssl_ == NULL) { tcn_ThrowException(e, "ssl is null"); return JNI_FALSE; } UNREFERENCED(o); if (!J2S(ciphers)) { return JNI_FALSE; } if (!SSL_set_cipher_list(ssl_, J2S(ciphers))) { char err[ERR_LEN]; ERR_error_string(ERR_get_error(), err); tcn_Throw(e, "Unable to configure permitted SSL ciphers (%s)", err); rv = JNI_FALSE; } TCN_FREE_CSTRING(ciphers); return rv; } TCN_IMPLEMENT_CALL(jbyteArray, SSL, getSessionId)(TCN_STDARGS, jlong ssl) { unsigned int len; const unsigned char *session_id; SSL_SESSION *session; jbyteArray bArray; SSL *ssl_ = J2P(ssl, SSL *); if (ssl_ == NULL) { tcn_ThrowException(e, "ssl is null"); return NULL; } UNREFERENCED(o); session = SSL_get_session(ssl_); if (session == NULL) { return NULL; } session_id = SSL_SESSION_get_id(session, &len); if (len == 0 || session_id == NULL) { return NULL; } bArray = (*e)->NewByteArray(e, len); (*e)->SetByteArrayRegion(e, bArray, 0, len, (jbyte*) session_id); return bArray; } TCN_IMPLEMENT_CALL(jint, SSL, getHandshakeCount)(TCN_STDARGS, jlong ssl) { int *handshakeCount = NULL; SSL *ssl_ = J2P(ssl, SSL *); if (ssl_ == NULL) { tcn_ThrowException(e, "ssl is null"); return -1; } UNREFERENCED(o); handshakeCount = SSL_get_app_data3(ssl_); return handshakeCount != NULL ? *handshakeCount : 0; } TCN_IMPLEMENT_CALL(void, SSL, clearError)(TCN_STDARGS) { UNREFERENCED(o); ERR_clear_error(); } TCN_IMPLEMENT_CALL(jint, SSL, renegotiate)(TCN_STDARGS, jlong ssl /* SSL * */) { SSL *ssl_ = J2P(ssl, SSL *); if (ssl_ == NULL) { tcn_ThrowException(e, "ssl is null"); return 0; } UNREFERENCED(o); return SSL_renegotiate(ssl_); } TCN_IMPLEMENT_CALL(void, SSL, setState)(TCN_STDARGS, jlong ssl, /* SSL * */ jint state) { SSL *ssl_ = J2P(ssl, SSL *); if (ssl_ == NULL) { tcn_ThrowException(e, "ssl is null"); return; } UNREFERENCED(o); SSL_set_state(ssl_, state); } TCN_IMPLEMENT_CALL(void, SSL, setTlsExtHostName)(TCN_STDARGS, jlong ssl, jstring hostname) { TCN_ALLOC_CSTRING(hostname); SSL *ssl_ = J2P(ssl, SSL *); if (ssl_ == NULL) { tcn_ThrowException(e, "ssl is null"); } else { UNREFERENCED(o); if (SSL_set_tlsext_host_name(ssl_, J2S(hostname)) != 1) { char err[ERR_LEN]; ERR_error_string(ERR_get_error(), err); tcn_Throw(e, "Unable to set TLS servername extension (%s)", err); } } TCN_FREE_CSTRING(hostname); } TCN_IMPLEMENT_CALL(void, SSL, setHostNameValidation)(TCN_STDARGS, jlong sslAddress, jint flags, jstring hostnameString) { SSL* ssl = J2P(sslAddress, SSL*); if (ssl == NULL) { tcn_ThrowException(e, "ssl is null"); } else { const char* hostname = hostnameString == NULL ? NULL : (*e)->GetStringUTFChars(e, hostnameString, 0); #if OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined(LIBRESSL_VERSION_NUMBER) X509_VERIFY_PARAM* param = SSL_get0_param(ssl); X509_VERIFY_PARAM_set_hostflags(param, flags); if (X509_VERIFY_PARAM_set1_host(param, hostname, 0) != 1) { char err[ERR_LEN]; ERR_error_string(ERR_get_error(), err); tcn_Throw(e, "X509_VERIFY_PARAM_set1_host error (%s)", err); } #else if (hostname != NULL && hostname[0] != '\0') { tcn_ThrowException(e, "hostname verification requires OpenSSL 1.0.2+"); } #endif (*e)->ReleaseStringUTFChars(e, hostnameString, hostname); } } TCN_IMPLEMENT_CALL(jobjectArray, SSL, authenticationMethods)(TCN_STDARGS, jlong ssl) { SSL *ssl_ = J2P(ssl, SSL *); const STACK_OF(SSL_CIPHER) *ciphers = NULL; int len; int i; jobjectArray array; TCN_ASSERT(ssl_ != NULL); UNREFERENCED(o); ciphers = SSL_get_ciphers(ssl_); len = sk_SSL_CIPHER_num(ciphers); array = (*e)->NewObjectArray(e, len, tcn_get_string_class(), NULL); for (i = 0; i < len; i++) { (*e)->SetObjectArrayElement(e, array, i, (*e)->NewStringUTF(e, SSL_cipher_authentication_method((SSL_CIPHER*) sk_value((_STACK*) ciphers, i)))); } return array; } TCN_IMPLEMENT_CALL(void, SSL, setCertificateBio)(TCN_STDARGS, jlong ssl, jlong cert, jlong key, jstring password) { SSL *ssl_ = J2P(ssl, SSL *); BIO *cert_bio = J2P(cert, BIO *); BIO *key_bio = J2P(key, BIO *); EVP_PKEY* pkey = NULL; X509* xcert = NULL; TCN_ALLOC_CSTRING(password); char err[ERR_LEN]; UNREFERENCED(o); TCN_ASSERT(ssl != NULL); if (key <= 0) { key = cert; } if (cert <= 0 || key <= 0) { tcn_Throw(e, "No Certificate file specified or invalid file format"); goto cleanup; } if ((pkey = load_pem_key_bio(cpassword, key_bio)) == NULL) { ERR_error_string_n(ERR_get_error(), err, ERR_LEN); ERR_clear_error(); tcn_Throw(e, "Unable to load certificate key (%s)",err); goto cleanup; } if ((xcert = load_pem_cert_bio(cpassword, cert_bio)) == NULL) { ERR_error_string_n(ERR_get_error(), err, ERR_LEN); ERR_clear_error(); tcn_Throw(e, "Unable to load certificate (%s) ", err); goto cleanup; } if (SSL_use_certificate(ssl_, xcert) <= 0) { ERR_error_string_n(ERR_get_error(), err, ERR_LEN); ERR_clear_error(); tcn_Throw(e, "Error setting certificate (%s)", err); goto cleanup; } if (SSL_use_PrivateKey(ssl_, pkey) <= 0) { ERR_error_string_n(ERR_get_error(), err, ERR_LEN); ERR_clear_error(); tcn_Throw(e, "Error setting private key (%s)", err); goto cleanup; } if (SSL_check_private_key(ssl_) <= 0) { ERR_error_string_n(ERR_get_error(), err, ERR_LEN); ERR_clear_error(); tcn_Throw(e, "Private key does not match the certificate public key (%s)", err); goto cleanup; } cleanup: TCN_FREE_CSTRING(password); EVP_PKEY_free(pkey); // this function is safe to call with NULL X509_free(xcert); // this function is safe to call with NULL } TCN_IMPLEMENT_CALL(void, SSL, setCertificateChainBio)(TCN_STDARGS, jlong ssl, jlong chain, jboolean skipfirst) { SSL *ssl_ = J2P(ssl, SSL *); BIO *b = J2P(chain, BIO *); char err[ERR_LEN]; UNREFERENCED(o); TCN_ASSERT(ssl_ != NULL); TCN_ASSERT(b != NULL); if (SSL_use_certificate_chain_bio(ssl_, b, skipfirst) < 0) { ERR_error_string_n(ERR_get_error(), err, ERR_LEN); ERR_clear_error(); tcn_Throw(e, "Error setting certificate chain (%s)", err); } } TCN_IMPLEMENT_CALL(long, SSL, parsePrivateKey)(TCN_STDARGS, jlong privateKeyBio, jstring password) { EVP_PKEY* pkey = NULL; BIO *bio = J2P(privateKeyBio, BIO *); TCN_ALLOC_CSTRING(password); char err[ERR_LEN]; UNREFERENCED(o); if (bio == NULL) { tcn_Throw(e, "Unable to load certificate key"); goto cleanup; } if ((pkey = load_pem_key_bio(cpassword, bio)) == NULL) { ERR_error_string_n(ERR_get_error(), err, ERR_LEN); ERR_clear_error(); tcn_Throw(e, "Unable to load certificate key (%s)",err); goto cleanup; } cleanup: TCN_FREE_CSTRING(password); return P2J(pkey); } TCN_IMPLEMENT_CALL(void, SSL, freePrivateKey)(TCN_STDARGS, jlong privateKey) { EVP_PKEY *key = J2P(privateKey, EVP_PKEY *); UNREFERENCED(o); EVP_PKEY_free(key); // Safe to call with NULL as well. } TCN_IMPLEMENT_CALL(long, SSL, parseX509Chain)(TCN_STDARGS, jlong x509ChainBio) { BIO *cert_bio = J2P(x509ChainBio, BIO *); X509* cert = NULL; STACK_OF(X509) *chain = NULL; char err[ERR_LEN]; unsigned long error; int n = 0; UNREFERENCED(o); if (cert_bio == NULL) { tcn_Throw(e, "No Certificate specified or invalid format"); goto cleanup; } chain = sk_X509_new_null(); while ((cert = PEM_read_bio_X509(cert_bio, NULL, NULL, NULL)) != NULL) { if (sk_X509_push(chain, cert) <= 0) { tcn_Throw(e, "No Certificate specified or invalid format"); goto cleanup; } cert = NULL; n++; } // ensure that if we have an error its just for EOL. if ((error = ERR_peek_error()) > 0) { if (!(ERR_GET_LIB(error) == ERR_LIB_PEM && ERR_GET_REASON(error) == PEM_R_NO_START_LINE)) { ERR_error_string_n(ERR_get_error(), err, ERR_LEN); tcn_Throw(e, "Invalid format (%s)", err); goto cleanup; } ERR_clear_error(); } return P2J(chain); cleanup: ERR_clear_error(); sk_X509_pop_free(chain, X509_free); X509_free(cert); return 0; } TCN_IMPLEMENT_CALL(void, SSL, freeX509Chain)(TCN_STDARGS, jlong x509Chain) { STACK_OF(X509) *chain = J2P(x509Chain, STACK_OF(X509) *); UNREFERENCED(o); sk_X509_pop_free(chain, X509_free); }