native/src/sslcontext.c (1,453 lines of code) (raw):

/* 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. */ /** SSL Context wrapper */ #include "tcn.h" #include "apr_file_io.h" #include "apr_thread_mutex.h" #include "apr_poll.h" #include "apr_pools.h" #include "ssl_private.h" static jclass byteArrayClass; static jclass stringClass; static apr_status_t ssl_context_cleanup(void *data) { tcn_ssl_ctxt_t *c = (tcn_ssl_ctxt_t *)data; if (c) { int i; c->crl = NULL; if (c->ctx) SSL_CTX_free(c->ctx); c->ctx = NULL; for (i = 0; i < SSL_AIDX_MAX; i++) { if (c->certs[i]) { X509_free(c->certs[i]); c->certs[i] = NULL; } if (c->keys[i]) { EVP_PKEY_free(c->keys[i]); c->keys[i] = NULL; } } if (c->bio_is) { SSL_BIO_close(c->bio_is); c->bio_is = NULL; } if (c->bio_os) { SSL_BIO_close(c->bio_os); c->bio_os = NULL; } if (c->verifier) { JNIEnv *e; tcn_get_java_env(&e); (*e)->DeleteGlobalRef(e, c->verifier); c->verifier = NULL; } c->verifier_method = NULL; if (c->next_proto_data) { free(c->next_proto_data); c->next_proto_data = NULL; } c->next_proto_len = 0; if (c->alpn_proto_data) { free(c->alpn_proto_data); c->alpn_proto_data = NULL; } c->alpn_proto_len = 0; } return APR_SUCCESS; } static jclass ssl_context_class; static jmethodID sni_java_callback; /* Callback used when OpenSSL receives a client hello with a Server Name * Indication extension. */ int ssl_callback_ServerNameIndication(SSL *ssl, int *al, tcn_ssl_ctxt_t *c) { /* TODO: Is it better to cache the JNIEnv* during the call to handshake? */ /* Get the JNI environment for this callback */ JavaVM *javavm = tcn_get_java_vm(); JNIEnv *env; const char *servername; jstring hostname; jlong original_ssl_context, new_ssl_context; tcn_ssl_ctxt_t *new_c; // Continue only if the static method exists if (sni_java_callback == NULL) { return SSL_TLSEXT_ERR_OK; } (*javavm)->AttachCurrentThread(javavm, (void **)&env, NULL); // Get the host name presented by the client servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); // Convert to Java compatible parameters ready for the method call hostname = (*env)->NewStringUTF(env, servername); original_ssl_context = P2J(c); new_ssl_context = (*env)->CallStaticLongMethod(env, ssl_context_class, sni_java_callback, original_ssl_context, hostname); // Delete the local reference as this method is called via callback. // Otherwise local references are only freed once jni method returns. (*env)->DeleteLocalRef(env, hostname); if (new_ssl_context != 0 && new_ssl_context != original_ssl_context) { new_c = J2P(new_ssl_context, tcn_ssl_ctxt_t *); SSL_set_SSL_CTX(ssl, new_c->ctx); } return SSL_TLSEXT_ERR_OK; } #if !defined(LIBRESSL_VERSION_NUMBER) /* * This callback function is called when the ClientHello is received. */ int ssl_callback_ClientHello(SSL *ssl, int *al, void *arg) { JavaVM *javavm = tcn_get_java_vm(); JNIEnv *env; char *servername = NULL; const unsigned char *pos; size_t len, remaining; tcn_ssl_ctxt_t *c = (tcn_ssl_ctxt_t *) arg; apr_pool_t *subpool = NULL; (*javavm)->AttachCurrentThread(javavm, (void **)&env, NULL); // Continue only if the static method exists if (sni_java_callback == NULL) { return SSL_CLIENT_HELLO_SUCCESS; } /* We can't use SSL_get_servername() at this earliest OpenSSL connection * stage, and there is no SSL_client_hello_get0_servername() provided as * of OpenSSL 1.1.1. So the code below, that extracts the SNI from the * ClientHello's TLS extensions, is taken from some test code in OpenSSL, * i.e. client_hello_select_server_ctx() in "test/handshake_helper.c". */ /* * The server_name extension was given too much extensibility when it * was written, so parsing the normal case is a bit complex. */ if (!SSL_client_hello_get0_ext(ssl, TLSEXT_TYPE_server_name, &pos, &remaining) || remaining <= 2) goto give_up; /* Extract the length of the supplied list of names. */ len = (*(pos++) << 8); len += *(pos++); if (len + 2 != remaining) goto give_up; remaining = len; /* * The list in practice only has a single element, so we only consider * the first one. */ if (remaining <= 3 || *pos++ != TLSEXT_NAMETYPE_host_name) goto give_up; remaining--; /* Now we can finally pull out the byte array with the actual hostname. */ len = (*(pos++) << 8); len += *(pos++); if (len + 2 != remaining) goto give_up; /* Use the SNI to switch to the relevant vhost, should it differ from * c->base_server. */ if (apr_pool_create(&subpool, c->pool) != APR_SUCCESS) { goto give_up; } servername = apr_pstrmemdup(subpool, (const char *)pos, len); give_up: if (servername != NULL) { jstring hostname; jlong original_ssl_context, new_ssl_context; tcn_ssl_ctxt_t *new_c; hostname = (*env)->NewStringUTF(env, servername); original_ssl_context = P2J(c); new_ssl_context = (*env)->CallStaticLongMethod(env, ssl_context_class, sni_java_callback, original_ssl_context, hostname); (*env)->DeleteLocalRef(env, hostname); if (new_ssl_context != 0 && new_ssl_context != original_ssl_context) { SSL_CTX *ctx; new_c = J2P(new_ssl_context, tcn_ssl_ctxt_t *); ctx = SSL_set_SSL_CTX(ssl, new_c->ctx); /* Copied from httpd (modules/ssl/ssl_engine_kernel.c) */ SSL_set_options(ssl, SSL_CTX_get_options(ctx)); SSL_set_min_proto_version(ssl, SSL_CTX_get_min_proto_version(ctx)); SSL_set_max_proto_version(ssl, SSL_CTX_get_max_proto_version(ctx)); if ((SSL_get_verify_mode(ssl) == SSL_VERIFY_NONE) || (SSL_num_renegotiations(ssl) == 0)) { SSL_set_verify(ssl, SSL_CTX_get_verify_mode(ctx), SSL_CTX_get_verify_callback(ctx)); } if (SSL_num_renegotiations(ssl) == 0) { SSL_set_session_id_context(ssl, &(c->context_id[0]), sizeof c->context_id); } } } if (subpool != NULL) { apr_pool_destroy(subpool); } return SSL_CLIENT_HELLO_SUCCESS; } #endif /* Initialize server context */ TCN_IMPLEMENT_CALL(jlong, SSLContext, make)(TCN_STDARGS, jlong pool, jint protocol, jint mode) { apr_pool_t *p = J2P(pool, apr_pool_t *); tcn_ssl_ctxt_t *c = NULL; SSL_CTX *ctx = NULL; jclass clazz; jclass sClazz; jint prot; UNREFERENCED(o); if (protocol == SSL_PROTOCOL_NONE) { tcn_Throw(e, "No SSL protocols requested"); goto init_failed; } if (mode == SSL_MODE_CLIENT) ctx = SSL_CTX_new(TLS_client_method()); else if (mode == SSL_MODE_SERVER) ctx = SSL_CTX_new(TLS_server_method()); else ctx = SSL_CTX_new(TLS_method()); if (!ctx) { char err[256]; ERR_error_string(SSL_ERR_get(), err); tcn_Throw(e, "Invalid Server SSL Protocol (%s)", err); goto init_failed; } if ((c = apr_pcalloc(p, sizeof(tcn_ssl_ctxt_t))) == NULL) { tcn_ThrowAPRException(e, apr_get_os_error()); goto init_failed; } SSL_callback_add_keylog(ctx); c->protocol = protocol; c->mode = mode; c->ctx = ctx; c->pool = p; c->bio_os = BIO_new(BIO_s_file()); if (c->bio_os != NULL) BIO_set_fp(c->bio_os, stderr, BIO_NOCLOSE | BIO_FP_TEXT); SSL_CTX_set_options(c->ctx, SSL_OP_ALL); /* We first determine the maximum protocol version we should provide */ #ifdef HAVE_TLSV1_3 if (protocol & SSL_PROTOCOL_TLSV1_3) { prot = TLS1_3_VERSION; } else /* NOTE the dangling else above: take care to preserve it */ #endif if (protocol & SSL_PROTOCOL_TLSV1_2) { prot = TLS1_2_VERSION; } else if (protocol & SSL_PROTOCOL_TLSV1_1) { prot = TLS1_1_VERSION; } else if (protocol & SSL_PROTOCOL_TLSV1) { prot = TLS1_VERSION; } else if (protocol & SSL_PROTOCOL_SSLV3) { prot = SSL3_VERSION; } else { SSL_CTX_free(ctx); tcn_Throw(e, "Invalid Server SSL Protocol (%d)", protocol); goto init_failed; } SSL_CTX_set_max_proto_version(ctx, prot); /* Next we scan for the minimal protocol version we should provide, * but we do not allow holes between max and min */ #ifdef HAVE_TLSV1_3 if (prot == TLS1_3_VERSION && protocol & SSL_PROTOCOL_TLSV1_2) { prot = TLS1_2_VERSION; } #endif if (prot == TLS1_2_VERSION && protocol & SSL_PROTOCOL_TLSV1_1) { prot = TLS1_1_VERSION; } if (prot == TLS1_1_VERSION && protocol & SSL_PROTOCOL_TLSV1) { prot = TLS1_VERSION; } if (prot == TLS1_VERSION && protocol & SSL_PROTOCOL_SSLV3) { prot = SSL3_VERSION; } SSL_CTX_set_min_proto_version(ctx, prot); /* * Configure additional context ingredients */ SSL_CTX_set_options(c->ctx, SSL_OP_SINGLE_DH_USE); #ifdef HAVE_ECC SSL_CTX_set_options(c->ctx, SSL_OP_SINGLE_ECDH_USE); #endif #ifdef SSL_OP_NO_COMPRESSION /* Disable SSL compression to be safe */ SSL_CTX_set_options(c->ctx, SSL_OP_NO_COMPRESSION); #endif /** To get back the tomcat wrapper from CTX */ SSL_CTX_set_app_data(c->ctx, (char *)c); #ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION /* * Disallow a session from being resumed during a renegotiation, * so that an acceptable cipher suite can be negotiated. */ SSL_CTX_set_options(c->ctx, SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); #endif #ifdef SSL_MODE_RELEASE_BUFFERS /* Release idle buffers to the SSL_CTX free list */ SSL_CTX_set_mode(c->ctx, SSL_MODE_RELEASE_BUFFERS); #endif /* Default session context id and cache size */ SSL_CTX_sess_set_cache_size(c->ctx, SSL_DEFAULT_CACHE_SIZE); /* Session cache is disabled by default */ SSL_CTX_set_session_cache_mode(c->ctx, SSL_SESS_CACHE_OFF); /* Longer session timeout */ SSL_CTX_set_timeout(c->ctx, 14400); EVP_Digest((const unsigned char *)SSL_DEFAULT_VHOST_NAME, (unsigned long)((sizeof SSL_DEFAULT_VHOST_NAME) - 1), &(c->context_id[0]), NULL, EVP_sha1(), NULL); /* Set default Certificate verification level * and depth for the Client Authentication */ c->verify_depth = 1; c->verify_mode = SSL_CVERIFY_UNSET; c->shutdown_type = SSL_SHUTDOWN_TYPE_UNSET; /* Set default password callback */ SSL_CTX_set_default_passwd_cb(c->ctx, (pem_password_cb *)SSL_password_callback); SSL_CTX_set_default_passwd_cb_userdata(c->ctx, (void *)(&tcn_password_callback)); SSL_CTX_set_info_callback(c->ctx, SSL_callback_handshake); /* Cache Java side SNI callback if not already cached */ if (ssl_context_class == NULL) { ssl_context_class = (*e)->NewGlobalRef(e, o); sni_java_callback = (*e)->GetStaticMethodID(e, ssl_context_class, "sniCallBack", "(JLjava/lang/String;)J"); /* Older Tomcat versions may not have this static method */ if ( (*e)->ExceptionCheck(e) ) { (*e)->ExceptionClear(e); } } /* Set up OpenSSL call back if SNI is provided by the client */ SSL_CTX_set_tlsext_servername_callback(c->ctx, ssl_callback_ServerNameIndication); SSL_CTX_set_tlsext_servername_arg(c->ctx, c); #if !defined(LIBRESSL_VERSION_NUMBER) /* * The ClientHello callback also allows to retrieve the SNI, but since it * runs at the earliest possible connection stage we can even set the TLS * protocol version(s) according to the selected (name-based-)vhost, which * is not possible at the SNI callback stage (due to OpenSSL internals). */ SSL_CTX_set_client_hello_cb(c->ctx, ssl_callback_ClientHello, c); #endif /* * Let us cleanup the ssl context when the pool is destroyed */ apr_pool_cleanup_register(p, (const void *)c, ssl_context_cleanup, apr_pool_cleanup_null); /* Cache the byte[].class for performance reasons */ if (stringClass == NULL) { clazz = (*e)->FindClass(e, "[B"); byteArrayClass = (jclass) (*e)->NewGlobalRef(e, clazz); sClazz = (*e)->FindClass(e, "java/lang/String"); stringClass = (jclass) (*e)->NewGlobalRef(e, sClazz); } return P2J(c); init_failed: return 0; } TCN_IMPLEMENT_CALL(jint, SSLContext, free)(TCN_STDARGS, jlong ctx) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); UNREFERENCED_STDARGS; TCN_ASSERT(ctx != 0); /* Run and destroy the cleanup callback */ return apr_pool_cleanup_run(c->pool, c, ssl_context_cleanup); } TCN_IMPLEMENT_CALL(void, SSLContext, setContextId)(TCN_STDARGS, jlong ctx, jstring id) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); TCN_ALLOC_CSTRING(id); TCN_ASSERT(ctx != 0); UNREFERENCED(o); if (J2S(id)) { EVP_Digest((const unsigned char *)J2S(id), (unsigned long)strlen(J2S(id)), &(c->context_id[0]), NULL, EVP_sha1(), NULL); } TCN_FREE_CSTRING(id); } TCN_IMPLEMENT_CALL(void, SSLContext, setBIO)(TCN_STDARGS, jlong ctx, jlong bio, jint dir) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); BIO *bio_handle = J2P(bio, BIO *); UNREFERENCED_STDARGS; TCN_ASSERT(ctx != 0); if (dir == 0) { if (c->bio_os && c->bio_os != bio_handle) SSL_BIO_close(c->bio_os); c->bio_os = bio_handle; } else if (dir == 1) { if (c->bio_is && c->bio_is != bio_handle) SSL_BIO_close(c->bio_is); c->bio_is = bio_handle; } else return; SSL_BIO_doref(bio_handle); } TCN_IMPLEMENT_CALL(void, SSLContext, setOptions)(TCN_STDARGS, jlong ctx, jint opt) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); UNREFERENCED_STDARGS; TCN_ASSERT(ctx != 0); #ifndef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION /* Clear the flag if not supported */ if (opt & 0x00040000) opt &= ~0x00040000; #endif SSL_CTX_set_options(c->ctx, opt); } TCN_IMPLEMENT_CALL(jint, SSLContext, getOptions)(TCN_STDARGS, jlong ctx) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); UNREFERENCED_STDARGS; TCN_ASSERT(ctx != 0); return SSL_CTX_get_options(c->ctx); } TCN_IMPLEMENT_CALL(void, SSLContext, clearOptions)(TCN_STDARGS, jlong ctx, jint opt) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); UNREFERENCED_STDARGS; TCN_ASSERT(ctx != 0); SSL_CTX_clear_options(c->ctx, opt); } TCN_IMPLEMENT_CALL(void, SSLContext, setQuietShutdown)(TCN_STDARGS, jlong ctx, jboolean mode) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); UNREFERENCED_STDARGS; TCN_ASSERT(ctx != 0); SSL_CTX_set_quiet_shutdown(c->ctx, mode ? 1 : 0); } TCN_IMPLEMENT_CALL(jboolean, SSLContext, setCipherSuite)(TCN_STDARGS, jlong ctx, jstring ciphers) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); TCN_ALLOC_CSTRING(ciphers); jboolean rv = JNI_TRUE; #ifndef HAVE_EXPORT_CIPHERS size_t len; char *buf; #endif UNREFERENCED(o); TCN_ASSERT(ctx != 0); if (!J2S(ciphers)) return JNI_FALSE; #ifndef HAVE_EXPORT_CIPHERS /* * Always disable NULL and export ciphers, * no matter what was given in the config. */ len = strlen(J2S(ciphers)) + strlen(SSL_CIPHERS_ALWAYS_DISABLED) + 1; buf = malloc(len * sizeof(char *)); if (buf == NULL) return JNI_FALSE; memcpy(buf, SSL_CIPHERS_ALWAYS_DISABLED, strlen(SSL_CIPHERS_ALWAYS_DISABLED)); memcpy(buf + strlen(SSL_CIPHERS_ALWAYS_DISABLED), J2S(ciphers), strlen(J2S(ciphers))); buf[len - 1] = '\0'; if (!SSL_CTX_set_cipher_list(c->ctx, buf)) { #else if (!SSL_CTX_set_cipher_list(c->ctx, J2S(ciphers))) { #endif char err[256]; ERR_error_string(SSL_ERR_get(), err); tcn_Throw(e, "Unable to configure permitted SSL ciphers (%s)", err); rv = JNI_FALSE; } #ifndef HAVE_EXPORT_CIPHERS free(buf); #endif TCN_FREE_CSTRING(ciphers); return rv; } TCN_IMPLEMENT_CALL(jobjectArray, SSLContext, getCiphers)(TCN_STDARGS, jlong ctx) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); STACK_OF(SSL_CIPHER) *sk; int len; jobjectArray array; SSL_CIPHER *cipher; const char *name; int i; jstring c_name; UNREFERENCED_STDARGS; if (c->ctx == NULL) { tcn_ThrowException(e, "ssl context is null"); return NULL; } /* For LibreSSL get_ciphers() iss only available * on an SSL, not for an SSL_CTX. */ sk = SSL_CTX_get_ciphers(c->ctx); len = sk_SSL_CIPHER_num(sk); if (len <= 0) { return NULL; } array = (*e)->NewObjectArray(e, len, stringClass, NULL); for (i = 0; i < len; i++) { cipher = (SSL_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, SSLContext, setCARevocation)(TCN_STDARGS, jlong ctx, jstring file, jstring path) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); TCN_ALLOC_CSTRING(file); TCN_ALLOC_CSTRING(path); jboolean rv = JNI_FALSE; X509_LOOKUP *lookup; char err[256]; UNREFERENCED(o); TCN_ASSERT(ctx != 0); if (J2S(file) == NULL && J2S(path) == NULL) { return JNI_FALSE; } if (!c->crl) { if ((c->crl = SSL_CTX_get_cert_store(c->ctx)) == NULL) goto cleanup; } if (J2S(file)) { lookup = X509_STORE_add_lookup(c->crl, X509_LOOKUP_file()); if (lookup == NULL) { ERR_error_string(SSL_ERR_get(), err); c->crl = NULL; tcn_Throw(e, "Lookup failed for file %s (%s)", J2S(file), err); goto cleanup; } if (!X509_LOOKUP_load_file(lookup, J2S(file), X509_FILETYPE_PEM)) { ERR_error_string(SSL_ERR_get(), err); c->crl = NULL; tcn_Throw(e, "Load failed for file %s (%s)", J2S(file), err); goto cleanup; } } if (J2S(path)) { lookup = X509_STORE_add_lookup(c->crl, X509_LOOKUP_hash_dir()); if (lookup == NULL) { ERR_error_string(SSL_ERR_get(), err); c->crl = NULL; tcn_Throw(e, "Lookup failed for path %s (%s)", J2S(file), err); goto cleanup; } if (!X509_LOOKUP_add_dir(lookup, J2S(path), X509_FILETYPE_PEM)) { ERR_error_string(SSL_ERR_get(), err); c->crl = NULL; tcn_Throw(e, "Load failed for path %s (%s)", J2S(file), err); goto cleanup; } } X509_STORE_set_flags(c->crl, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL); rv = JNI_TRUE; cleanup: TCN_FREE_CSTRING(file); TCN_FREE_CSTRING(path); return rv; } TCN_IMPLEMENT_CALL(jboolean, SSLContext, setCertificateChainFile)(TCN_STDARGS, jlong ctx, jstring file, jboolean skipfirst) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); jboolean rv = JNI_FALSE; TCN_ALLOC_CSTRING(file); UNREFERENCED(o); TCN_ASSERT(ctx != 0); if (!J2S(file)) return JNI_FALSE; if (SSL_CTX_use_certificate_chain(c->ctx, J2S(file), skipfirst) > 0) rv = JNI_TRUE; TCN_FREE_CSTRING(file); return rv; } TCN_IMPLEMENT_CALL(jboolean, SSLContext, setCACertificate)(TCN_STDARGS, jlong ctx, jstring file, jstring path) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); jboolean rv = JNI_TRUE; TCN_ALLOC_CSTRING(file); TCN_ALLOC_CSTRING(path); UNREFERENCED(o); TCN_ASSERT(ctx != 0); if (file == NULL && path == NULL) return JNI_FALSE; /* * Configure Client Authentication details */ if (!SSL_CTX_load_verify_locations(c->ctx, J2S(file), J2S(path))) { char err[256]; ERR_error_string(SSL_ERR_get(), err); tcn_Throw(e, "Unable to configure locations " "for client authentication (%s)", err); rv = JNI_FALSE; goto cleanup; } c->store = SSL_CTX_get_cert_store(c->ctx); if (c->mode) { STACK_OF(X509_NAME) *ca_certs; c->ca_certs++; ca_certs = SSL_CTX_get_client_CA_list(c->ctx); if (ca_certs == NULL) { ca_certs = SSL_load_client_CA_file(J2S(file)); if (ca_certs != NULL) SSL_CTX_set_client_CA_list(c->ctx, ca_certs); } else { if (!SSL_add_file_cert_subjects_to_stack(ca_certs, J2S(file))) ca_certs = NULL; } if (ca_certs == NULL && c->verify_mode == SSL_CVERIFY_REQUIRE) { /* * Give a warning when no CAs were configured but client authentication * should take place. This cannot work. */ if (c->bio_os) { BIO_printf(c->bio_os, "[WARN] Oops, you want to request client " "authentication, but no CAs are known for " "verification!?"); } else { fprintf(stderr, "[WARN] Oops, you want to request client " "authentication, but no CAs are known for " "verification!?"); } } } cleanup: TCN_FREE_CSTRING(file); TCN_FREE_CSTRING(path); return rv; } TCN_IMPLEMENT_CALL(void, SSLContext, setTmpDH)(TCN_STDARGS, jlong ctx, jstring file) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); BIO *bio = NULL; DH *dh = NULL; TCN_ALLOC_CSTRING(file); UNREFERENCED(o); TCN_ASSERT(ctx != 0); TCN_ASSERT(file); if (!J2S(file)) { tcn_Throw(e, "Error while configuring DH: no dh param file given"); return; } bio = BIO_new_file(J2S(file), "r"); if (!bio) { char err[256]; ERR_error_string(SSL_ERR_get(), err); tcn_Throw(e, "Error while configuring DH using %s: %s", J2S(file), err); TCN_FREE_CSTRING(file); return; } dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL); BIO_free(bio); if (!dh) { char err[256]; ERR_error_string(SSL_ERR_get(), err); tcn_Throw(e, "Error while configuring DH: no DH parameter found in %s (%s)", J2S(file), err); TCN_FREE_CSTRING(file); return; } if (1 != SSL_CTX_set_tmp_dh(c->ctx, dh)) { char err[256]; DH_free(dh); ERR_error_string(SSL_ERR_get(), err); tcn_Throw(e, "Error while configuring DH with file %s: %s", J2S(file), err); TCN_FREE_CSTRING(file); return; } DH_free(dh); TCN_FREE_CSTRING(file); } TCN_IMPLEMENT_CALL(void, SSLContext, setTmpECDHByCurveName)(TCN_STDARGS, jlong ctx, jstring curveName) { #ifdef HAVE_ECC tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); int i; EC_KEY *ecdh; TCN_ALLOC_CSTRING(curveName); UNREFERENCED(o); TCN_ASSERT(ctx != 0); TCN_ASSERT(curveName); /* First try to get curve by name */ i = OBJ_sn2nid(J2S(curveName)); if (!i) { tcn_Throw(e, "Can't configure elliptic curve: unknown curve name %s", J2S(curveName)); TCN_FREE_CSTRING(curveName); return; } ecdh = EC_KEY_new_by_curve_name(i); if (!ecdh) { tcn_Throw(e, "Can't configure elliptic curve: unknown curve name %s", J2S(curveName)); TCN_FREE_CSTRING(curveName); return; } /* Setting found curve to context */ if (1 != SSL_CTX_set_tmp_ecdh(c->ctx, ecdh)) { char err[256]; EC_KEY_free(ecdh); ERR_error_string(SSL_ERR_get(), err); tcn_Throw(e, "Error while configuring elliptic curve %s: %s", J2S(curveName), err); TCN_FREE_CSTRING(curveName); return; } EC_KEY_free(ecdh); TCN_FREE_CSTRING(curveName); #else tcn_Throw(e, "Cant't configure elliptic curve: unsupported by this OpenSSL version"); return; #endif } TCN_IMPLEMENT_CALL(void, SSLContext, setShutdownType)(TCN_STDARGS, jlong ctx, jint type) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); UNREFERENCED_STDARGS; TCN_ASSERT(ctx != 0); c->shutdown_type = type; } TCN_IMPLEMENT_CALL(void, SSLContext, setVerify)(TCN_STDARGS, jlong ctx, jint level, jint depth) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); int verify = SSL_VERIFY_NONE; UNREFERENCED(o); TCN_ASSERT(ctx != 0); c->verify_mode = level; if (c->verify_mode == SSL_CVERIFY_UNSET) c->verify_mode = SSL_CVERIFY_NONE; if (depth > 0) c->verify_depth = depth; /* * Configure callbacks for SSL context */ if (c->verify_mode == SSL_CVERIFY_REQUIRE) verify |= SSL_VERIFY_PEER_STRICT; if ((c->verify_mode == SSL_CVERIFY_OPTIONAL) || (c->verify_mode == SSL_CVERIFY_OPTIONAL_NO_CA)) verify |= SSL_VERIFY_PEER; if (!c->store) { if (SSL_CTX_set_default_verify_paths(c->ctx)) { c->store = SSL_CTX_get_cert_store(c->ctx); X509_STORE_set_flags(c->store, 0); } else { /* XXX: See if this is fatal */ } } SSL_CTX_set_verify(c->ctx, verify, SSL_callback_SSL_verify); } static EVP_PKEY *load_pem_key(tcn_ssl_ctxt_t *c, const char *file) { BIO *bio = NULL; EVP_PKEY *key = NULL; tcn_pass_cb_t *cb_data = c->cb_data; int i; if ((bio = BIO_new(BIO_s_file())) == NULL) { return NULL; } if (BIO_read_filename(bio, file) <= 0) { BIO_free(bio); return NULL; } if (!cb_data) cb_data = &tcn_password_callback; for (i = 0; i < 3; i++) { key = PEM_read_bio_PrivateKey(bio, NULL, (pem_password_cb *)SSL_password_callback, (void *)cb_data); if (key) break; cb_data->password[0] = '\0'; BIO_ctrl(bio, BIO_CTRL_RESET, 0, NULL); } BIO_free(bio); return key; } static X509 *load_pem_cert(tcn_ssl_ctxt_t *c, const char *file) { BIO *bio = NULL; X509 *cert = NULL; tcn_pass_cb_t *cb_data = c->cb_data; if ((bio = BIO_new(BIO_s_file())) == NULL) { return NULL; } if (BIO_read_filename(bio, file) <= 0) { BIO_free(bio); return NULL; } if (!cb_data) cb_data = &tcn_password_callback; cert = PEM_read_bio_X509_AUX(bio, NULL, (pem_password_cb *)SSL_password_callback, (void *)cb_data); if (cert == NULL && (ERR_GET_REASON(ERR_peek_last_error()) == PEM_R_NO_START_LINE)) { SSL_ERR_clear(); BIO_ctrl(bio, BIO_CTRL_RESET, 0, NULL); cert = d2i_X509_bio(bio, NULL); } BIO_free(bio); return cert; } static int ssl_load_pkcs12(tcn_ssl_ctxt_t *c, const char *file, EVP_PKEY **pkey, X509 **cert, STACK_OF(X509) **ca) { const char *pass; char buff[PEM_BUFSIZE]; int len, rc = 0; PKCS12 *p12; BIO *in; tcn_pass_cb_t *cb_data = c->cb_data; if ((in = BIO_new(BIO_s_file())) == 0) return 0; if (BIO_read_filename(in, file) <= 0) { BIO_free(in); return 0; } p12 = d2i_PKCS12_bio(in, 0); if (p12 == 0) { /* Error loading PKCS12 file */ goto cleanup; } /* See if an empty password will do */ if (PKCS12_verify_mac(p12, "", 0) || PKCS12_verify_mac(p12, 0, 0)) { pass = ""; } else { if (!cb_data) cb_data = &tcn_password_callback; len = SSL_password_callback(buff, PEM_BUFSIZE, 0, cb_data); if (len < 0) { /* Passpharse callback error */ goto cleanup; } if (!PKCS12_verify_mac(p12, buff, len)) { /* Mac verify error (wrong password?) in PKCS12 file */ goto cleanup; } pass = buff; } rc = PKCS12_parse(p12, pass, pkey, cert, ca); cleanup: if (p12 != 0) PKCS12_free(p12); BIO_free(in); return rc; } TCN_IMPLEMENT_CALL(void, SSLContext, setRandom)(TCN_STDARGS, jlong ctx, jstring file) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); TCN_ALLOC_CSTRING(file); TCN_ASSERT(ctx != 0); UNREFERENCED(o); if (J2S(file)) c->rand_file = apr_pstrdup(c->pool, J2S(file)); TCN_FREE_CSTRING(file); } TCN_IMPLEMENT_CALL(jboolean, SSLContext, setCertificate)(TCN_STDARGS, jlong ctx, jstring cert, jstring key, jstring password, jint idx) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); jboolean rv = JNI_TRUE; TCN_ALLOC_CSTRING(cert); TCN_ALLOC_CSTRING(key); TCN_ALLOC_CSTRING(password); const char *key_file, *cert_file; const char *p; char err[256]; #ifdef HAVE_ECC EC_GROUP *ecparams = NULL; int nid; EC_KEY *eckey = NULL; #endif DH *dhparams; UNREFERENCED(o); TCN_ASSERT(ctx != 0); if (idx < 0 || idx >= SSL_AIDX_MAX) { /* TODO: Throw something */ rv = JNI_FALSE; goto cleanup; } if (J2S(password)) { if (!c->cb_data) c->cb_data = &tcn_password_callback; strncpy(c->cb_data->password, J2S(password), SSL_MAX_PASSWORD_LEN - 1); c->cb_data->password[SSL_MAX_PASSWORD_LEN-1] = '\0'; } key_file = J2S(key); cert_file = J2S(cert); if (!key_file) key_file = cert_file; if (!key_file || !cert_file) { tcn_Throw(e, "No Certificate file specified or invalid file format"); rv = JNI_FALSE; goto cleanup; } if ((p = strrchr(cert_file, '.')) != NULL && strcmp(p, ".pkcs12") == 0) { if (!ssl_load_pkcs12(c, cert_file, &c->keys[idx], &c->certs[idx], 0)) { ERR_error_string(SSL_ERR_get(), err); tcn_Throw(e, "Unable to load certificate %s (%s)", cert_file, err); rv = JNI_FALSE; goto cleanup; } } else { if ((c->keys[idx] = load_pem_key(c, key_file)) == NULL #ifndef OPENSSL_NO_ENGINE && (tcn_ssl_engine == NULL || (c->keys[idx] = ENGINE_load_private_key(tcn_ssl_engine, key_file, NULL, NULL)) == NULL) #endif ) { ERR_error_string(SSL_ERR_get(), err); tcn_Throw(e, "Unable to load certificate key %s (%s)", key_file, err); rv = JNI_FALSE; goto cleanup; } if ((c->certs[idx] = load_pem_cert(c, cert_file)) == NULL) { ERR_error_string(SSL_ERR_get(), err); tcn_Throw(e, "Unable to load certificate %s (%s)", cert_file, err); rv = JNI_FALSE; goto cleanup; } } if (SSL_CTX_use_certificate(c->ctx, c->certs[idx]) <= 0) { ERR_error_string(SSL_ERR_get(), err); tcn_Throw(e, "Error setting certificate (%s)", err); rv = JNI_FALSE; goto cleanup; } if (SSL_CTX_use_PrivateKey(c->ctx, c->keys[idx]) <= 0) { ERR_error_string(SSL_ERR_get(), err); tcn_Throw(e, "Error setting private key (%s)", err); rv = JNI_FALSE; goto cleanup; } if (SSL_CTX_check_private_key(c->ctx) <= 0) { ERR_error_string(SSL_ERR_get(), err); tcn_Throw(e, "Private key does not match the certificate public key (%s)", err); rv = JNI_FALSE; goto cleanup; } /* * Try to read DH parameters from the (first) SSLCertificateFile */ /* XXX Does this also work for pkcs12 or only for PEM files? * If only for PEM files move above to the PEM handling */ if ((idx == 0) && (dhparams = SSL_dh_GetParamFromFile(cert_file))) { SSL_CTX_set_tmp_dh(c->ctx, dhparams); DH_free(dhparams); } #ifdef HAVE_ECC /* * Similarly, try to read the ECDH curve name from SSLCertificateFile... */ /* XXX Does this also work for pkcs12 or only for PEM files? * If only for PEM files move above to the PEM handling */ if ((ecparams = SSL_ec_GetParamFromFile(cert_file)) && (nid = EC_GROUP_get_curve_name(ecparams)) && (eckey = EC_KEY_new_by_curve_name(nid))) { SSL_CTX_set_tmp_ecdh(c->ctx, eckey); } /* OpenSSL assures us that _free() is NULL-safe */ EC_KEY_free(eckey); EC_GROUP_free(ecparams); #endif SSL_CTX_set_tmp_dh_callback(c->ctx, SSL_callback_tmp_DH); cleanup: TCN_FREE_CSTRING(cert); TCN_FREE_CSTRING(key); TCN_FREE_CSTRING(password); return rv; } TCN_IMPLEMENT_CALL(jboolean, SSLContext, setCertificateRaw)(TCN_STDARGS, jlong ctx, jbyteArray javaCert, jbyteArray javaKey, jint idx) { #ifdef HAVE_ECC #ifndef SSL_CTX_set_ecdh_auto EC_KEY *eckey = NULL; #endif #endif jsize lengthOfCert; unsigned char* cert; X509 * certs; EVP_PKEY * evp; const unsigned char *tmp; BIO * bio; tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); jboolean rv = JNI_TRUE; char err[256]; /* we get the key contents into a byte array */ jbyte* bufferPtr = (*e)->GetByteArrayElements(e, javaKey, NULL); jsize lengthOfKey = (*e)->GetArrayLength(e, javaKey); unsigned char* key = malloc(lengthOfKey); memcpy(key, bufferPtr, lengthOfKey); (*e)->ReleaseByteArrayElements(e, javaKey, bufferPtr, 0); bufferPtr = (*e)->GetByteArrayElements(e, javaCert, NULL); lengthOfCert = (*e)->GetArrayLength(e, javaCert); cert = malloc(lengthOfCert); memcpy(cert, bufferPtr, lengthOfCert); (*e)->ReleaseByteArrayElements(e, javaCert, bufferPtr, 0); UNREFERENCED(o); TCN_ASSERT(ctx != 0); if (idx < 0 || idx >= SSL_AIDX_MAX) { tcn_Throw(e, "Invalid key type"); rv = JNI_FALSE; goto cleanup; } tmp = (const unsigned char *)cert; certs = d2i_X509(NULL, &tmp, lengthOfCert); if (certs == NULL) { ERR_error_string(SSL_ERR_get(), err); tcn_Throw(e, "Error reading certificate (%s)", err); rv = JNI_FALSE; goto cleanup; } if(c->certs[idx] != NULL) { free(c->certs[idx]); } c->certs[idx] = certs; bio = BIO_new(BIO_s_mem()); BIO_write(bio, key, lengthOfKey); evp = PEM_read_bio_PrivateKey(bio, NULL, 0, NULL); if (evp == NULL) { BIO_free(bio); ERR_error_string(SSL_ERR_get(), err); tcn_Throw(e, "Error reading private key (%s)", err); rv = JNI_FALSE; goto cleanup; } BIO_free(bio); if(c->keys[idx] != NULL) { free(c->keys[idx]); } c->keys[idx] = evp; if (SSL_CTX_use_certificate(c->ctx, c->certs[idx]) <= 0) { ERR_error_string(SSL_ERR_get(), err); tcn_Throw(e, "Error setting certificate (%s)", err); rv = JNI_FALSE; goto cleanup; } if (SSL_CTX_use_PrivateKey(c->ctx, c->keys[idx]) <= 0) { ERR_error_string(SSL_ERR_get(), err); tcn_Throw(e, "Error setting private key (%s)", err); rv = JNI_FALSE; goto cleanup; } if (SSL_CTX_check_private_key(c->ctx) <= 0) { ERR_error_string(SSL_ERR_get(), err); tcn_Throw(e, "Private key does not match the certificate public key (%s)", err); rv = JNI_FALSE; goto cleanup; } /* * TODO Try to read DH parameters from somewhere... */ #ifdef HAVE_ECC /* * TODO try to read the ECDH curve name from somewhere... */ #endif SSL_CTX_set_tmp_dh_callback(c->ctx, SSL_callback_tmp_DH); cleanup: free(key); free(cert); return rv; } TCN_IMPLEMENT_CALL(jboolean, SSLContext, addChainCertificateRaw)(TCN_STDARGS, jlong ctx, jbyteArray javaCert) { jsize lengthOfCert; unsigned char* cert; X509 * certs; const unsigned char *tmp; tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); jboolean rv = JNI_TRUE; char err[256]; /* we get the cert contents into a byte array */ jbyte* bufferPtr = (*e)->GetByteArrayElements(e, javaCert, NULL); lengthOfCert = (*e)->GetArrayLength(e, javaCert); cert = malloc(lengthOfCert); memcpy(cert, bufferPtr, lengthOfCert); (*e)->ReleaseByteArrayElements(e, javaCert, bufferPtr, 0); UNREFERENCED(o); TCN_ASSERT(ctx != 0); tmp = (const unsigned char *)cert; certs = d2i_X509(NULL, &tmp, lengthOfCert); if (certs == NULL) { ERR_error_string(SSL_ERR_get(), err); tcn_Throw(e, "Error reading certificate (%s)", err); rv = JNI_FALSE; } else if (SSL_CTX_add0_chain_cert(c->ctx, certs) <= 0) { ERR_error_string(SSL_ERR_get(), err); tcn_Throw(e, "Error adding certificate to chain (%s)", err); rv = JNI_FALSE; } free(cert); return rv; } TCN_IMPLEMENT_CALL(jboolean, SSLContext, addClientCACertificateRaw)(TCN_STDARGS, jlong ctx, jbyteArray javaCert) { jsize lengthOfCert; unsigned char *charCert; X509 *cert; const unsigned char *tmp; tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); jboolean rv = JNI_TRUE; char err[256]; /* we get the cert contents into a byte array */ jbyte* bufferPtr = (*e)->GetByteArrayElements(e, javaCert, NULL); lengthOfCert = (*e)->GetArrayLength(e, javaCert); charCert = malloc(lengthOfCert); memcpy(charCert, bufferPtr, lengthOfCert); (*e)->ReleaseByteArrayElements(e, javaCert, bufferPtr, 0); UNREFERENCED(o); TCN_ASSERT(ctx != 0); tmp = (const unsigned char *)charCert; cert = d2i_X509(NULL, &tmp, lengthOfCert); if (cert == NULL) { ERR_error_string(SSL_ERR_get(), err); tcn_Throw(e, "Error encoding allowed peer CA certificate (%s)", err); rv = JNI_FALSE; } else if (SSL_CTX_add_client_CA(c->ctx, cert) <= 0) { ERR_error_string(SSL_ERR_get(), err); tcn_Throw(e, "Error adding allowed peer CA certificate (%s)", err); rv = JNI_FALSE; } free(charCert); return rv; } static int ssl_array_index(apr_array_header_t *array, const char *s) { int i; for (i = 0; i < array->nelts; i++) { const char *p = APR_ARRAY_IDX(array, i, const char*); if (!strcmp(p, s)) { return i; } } return -1; } static int ssl_cmp_alpn_protos(apr_array_header_t *array, const char *proto1, const char *proto2) { int index1 = ssl_array_index(array, proto1); int index2 = ssl_array_index(array, proto2); if (index2 > index1) { return (index1 >= 0)? 1 : -1; } else if (index1 > index2) { return (index2 >= 0)? -1 : 1; } /* Both have the same index (-1 so neither listed by cient) compare * the names so that spdy3 gets precedence over spdy2. That makes * the outcome at least deterministic. */ return strcmp((const char *)proto1, (const char *)proto2); } /* * This callback function is executed when the TLS Application Layer * Protocol Negotiate Extension (ALPN, RFC 7301) is triggered by the client * hello, giving a list of desired protocol names (in descending preference) * to the server. * The callback has to select a protocol name or return an error if none of * the clients preferences is supported. * The selected protocol does not have to be on the client list, according * to RFC 7301, so no checks are performed. * The client protocol list is serialized as length byte followed by ascii * characters (not null-terminated), followed by the next protocol name. */ int cb_server_alpn(SSL *ssl, const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg) { tcn_ssl_ctxt_t *tcsslctx = (tcn_ssl_ctxt_t *)arg; tcn_ssl_conn_t *con = (tcn_ssl_conn_t *)SSL_get_app_data(ssl); apr_array_header_t *client_protos; apr_array_header_t *proposed_protos; int i; size_t len; if (inlen == 0) { // Client specified an empty protocol list. Nothing to negotiate. return SSL_TLSEXT_ERR_ALERT_FATAL; } client_protos = apr_array_make(con->pool , 0, sizeof(char *)); for (i = 0; i < inlen; /**/) { /* Grab length of next item from leading length byte */ unsigned int plen = in[i++]; if (plen + i > inlen) { // The protocol name extends beyond the declared length // of the protocol list. return SSL_TLSEXT_ERR_ALERT_FATAL; } APR_ARRAY_PUSH(client_protos, char*) = apr_pstrndup(con->pool, (const char *)in+i, plen); i += plen; } if (tcsslctx->alpn == NULL) { // Server supported protocol names not set. return SSL_TLSEXT_ERR_ALERT_FATAL; } if (tcsslctx->alpnlen == 0) { // Server supported protocols is an empty list return SSL_TLSEXT_ERR_ALERT_FATAL; } proposed_protos = apr_array_make(con->pool, 0, sizeof(char *)); for (i = 0; i < tcsslctx->alpnlen; /**/) { /* Grab length of next item from leading length byte */ unsigned int plen = tcsslctx->alpn[i++]; if (plen + i > tcsslctx->alpnlen) { // The protocol name extends beyond the declared length // of the protocol list. return SSL_TLSEXT_ERR_ALERT_FATAL; } APR_ARRAY_PUSH(proposed_protos, char*) = apr_pstrndup(con->pool, (const char *)tcsslctx->alpn+i, plen); i += plen; } if (proposed_protos->nelts <= 0) { // Should never happen. The server did not specify any protocols. return SSL_TLSEXT_ERR_ALERT_FATAL; } /* Now select the most preferred protocol from the proposals. */ *out = APR_ARRAY_IDX(proposed_protos, 0, const unsigned char *); for (i = 1; i < proposed_protos->nelts; ++i) { const char *proto = APR_ARRAY_IDX(proposed_protos, i, const char*); /* Do we prefer it over existing candidate? */ if (ssl_cmp_alpn_protos(client_protos, (const char *)*out, proto) < 0) { *out = (const unsigned char*)proto; } } len = strlen((const char*)*out); if (len > 255) { // Agreed protocol name too long return SSL_TLSEXT_ERR_ALERT_FATAL; } *outlen = (unsigned char)len; return SSL_TLSEXT_ERR_OK; } TCN_IMPLEMENT_CALL(jint, SSLContext, setALPN)(TCN_STDARGS, jlong ctx, jbyteArray buf, jint len) { tcn_ssl_ctxt_t *sslctx = J2P(ctx, tcn_ssl_ctxt_t *); sslctx->alpn = apr_pcalloc(sslctx->pool, len); (*e)->GetByteArrayRegion(e, buf, 0, len, (jbyte *)sslctx->alpn); sslctx->alpnlen = len; if (sslctx->mode == SSL_MODE_SERVER) { SSL_CTX_set_alpn_select_cb(sslctx->ctx, cb_server_alpn, sslctx); } else { /* * TODO: Implement client side call-back * SSL_CTX_set_next_proto_select_cb(sslctx->ctx, cb_request_alpn, sslctx); */ return APR_ENOTIMPL; } return 0; } /* Start of netty-tc-native add */ /* Convert protos to wire format */ static int initProtocols(JNIEnv *e, const tcn_ssl_ctxt_t *c, unsigned char **proto_data, unsigned int *proto_len, jobjectArray protos) { int i; unsigned char *p_data; /* * We start with allocate 128 bytes which should be good enough for most use-cases while still be pretty low. * We will call realloc to increate this if needed. */ size_t p_data_size = 128; size_t p_data_len = 0; jstring proto_string; const char *proto_chars; size_t proto_chars_len; int cnt; if (protos == NULL) { // Guard against NULL protos. return -1; } cnt = (*e)->GetArrayLength(e, protos); if (cnt == 0) { // if cnt is 0 we not need to continue and can just fail fast. return -1; } p_data = (unsigned char *) malloc(p_data_size); if (p_data == NULL) { // Not enough memory? return -1; } for (i = 0; i < cnt; ++i) { proto_string = (jstring) (*e)->GetObjectArrayElement(e, protos, i); proto_chars = (*e)->GetStringUTFChars(e, proto_string, 0); proto_chars_len = strlen(proto_chars); if (proto_chars_len > 0 && proto_chars_len <= MAX_ALPN_PROTO_SIZE) { // We need to add +1 as each protocol is prefixed by it's length (unsigned char). // For all except of the last one we already have the extra space as everything is // delimited by ','. p_data_len += 1 + proto_chars_len; if (p_data_len > p_data_size) { // double size p_data_size <<= 1; p_data = realloc(p_data, p_data_size); if (p_data == NULL) { // Not enough memory? (*e)->ReleaseStringUTFChars(e, proto_string, proto_chars); break; } } // Write the length of the protocol and then increment before memcpy the protocol itself. *p_data = proto_chars_len; ++p_data; memcpy(p_data, proto_chars, proto_chars_len); p_data += proto_chars_len; } // Release the string to prevent memory leaks (*e)->ReleaseStringUTFChars(e, proto_string, proto_chars); } if (p_data == NULL) { // Something went wrong so update the proto_len and return -1 *proto_len = 0; return -1; } else { if (*proto_data != NULL) { // Free old data free(*proto_data); } // Decrement pointer again as we incremented it while creating the protocols in wire format. p_data -= p_data_len; *proto_data = p_data; *proto_len = p_data_len; return 0; } } TCN_IMPLEMENT_CALL(void, SSLContext, setAlpnProtos)(TCN_STDARGS, jlong ctx, jobjectArray alpn_protos, jint selectorFailureBehavior) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); TCN_ASSERT(ctx != 0); UNREFERENCED(o); if (initProtocols(e, c, &c->alpn_proto_data, &c->alpn_proto_len, alpn_protos) == 0) { c->alpn_selector_failure_behavior = selectorFailureBehavior; // depending on if it's client mode or not we need to call different functions. if (c->mode == SSL_MODE_CLIENT) { SSL_CTX_set_alpn_protos(c->ctx, c->alpn_proto_data, c->alpn_proto_len); } else { SSL_CTX_set_alpn_select_cb(c->ctx, SSL_callback_alpn_select_proto, (void *) c); } } } TCN_IMPLEMENT_CALL(jlong, SSLContext, setSessionCacheMode)(TCN_STDARGS, jlong ctx, jlong mode) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); return SSL_CTX_set_session_cache_mode(c->ctx, mode); } TCN_IMPLEMENT_CALL(jlong, SSLContext, getSessionCacheMode)(TCN_STDARGS, jlong ctx) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); return SSL_CTX_get_session_cache_mode(c->ctx); } TCN_IMPLEMENT_CALL(jlong, SSLContext, setSessionCacheTimeout)(TCN_STDARGS, jlong ctx, jlong timeout) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); jlong rv = SSL_CTX_set_timeout(c->ctx, timeout); return rv; } TCN_IMPLEMENT_CALL(jlong, SSLContext, getSessionCacheTimeout)(TCN_STDARGS, jlong ctx) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); return SSL_CTX_get_timeout(c->ctx); } TCN_IMPLEMENT_CALL(jlong, SSLContext, setSessionCacheSize)(TCN_STDARGS, jlong ctx, jlong size) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); jlong rv = 0; // Also allow size of 0 which is unlimited if (size >= 0) { SSL_CTX_set_session_cache_mode(c->ctx, SSL_SESS_CACHE_SERVER); rv = SSL_CTX_sess_set_cache_size(c->ctx, size); } return rv; } TCN_IMPLEMENT_CALL(jlong, SSLContext, getSessionCacheSize)(TCN_STDARGS, jlong ctx) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); return SSL_CTX_sess_get_cache_size(c->ctx); } TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionNumber)(TCN_STDARGS, jlong ctx) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); jlong rv = SSL_CTX_sess_number(c->ctx); return rv; } TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionConnect)(TCN_STDARGS, jlong ctx) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); jlong rv = SSL_CTX_sess_connect(c->ctx); return rv; } TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionConnectGood)(TCN_STDARGS, jlong ctx) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); jlong rv = SSL_CTX_sess_connect_good(c->ctx); return rv; } TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionConnectRenegotiate)(TCN_STDARGS, jlong ctx) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); jlong rv = SSL_CTX_sess_connect_renegotiate(c->ctx); return rv; } TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionAccept)(TCN_STDARGS, jlong ctx) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); jlong rv = SSL_CTX_sess_accept(c->ctx); return rv; } TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionAcceptGood)(TCN_STDARGS, jlong ctx) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); jlong rv = SSL_CTX_sess_accept_good(c->ctx); return rv; } TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionAcceptRenegotiate)(TCN_STDARGS, jlong ctx) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); jlong rv = SSL_CTX_sess_accept_renegotiate(c->ctx); return rv; } TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionHits)(TCN_STDARGS, jlong ctx) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); jlong rv = SSL_CTX_sess_hits(c->ctx); return rv; } TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionCbHits)(TCN_STDARGS, jlong ctx) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); jlong rv = SSL_CTX_sess_cb_hits(c->ctx); return rv; } TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionMisses)(TCN_STDARGS, jlong ctx) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); jlong rv = SSL_CTX_sess_misses(c->ctx); return rv; } TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionTimeouts)(TCN_STDARGS, jlong ctx) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); jlong rv = SSL_CTX_sess_timeouts(c->ctx); return rv; } TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionCacheFull)(TCN_STDARGS, jlong ctx) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); jlong rv = SSL_CTX_sess_cache_full(c->ctx); return rv; } #define TICKET_KEYS_SIZE 48 TCN_IMPLEMENT_CALL(void, SSLContext, setSessionTicketKeys)(TCN_STDARGS, jlong ctx, jbyteArray keys) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); jbyte* b; if ((*e)->GetArrayLength(e, keys) != TICKET_KEYS_SIZE) { if (c->bio_os) { BIO_printf(c->bio_os, "[ERROR] Session ticket keys provided were wrong size."); } else { fprintf(stderr, "[ERROR] Session ticket keys provided were wrong size."); } exit(1); } b = (*e)->GetByteArrayElements(e, keys, NULL); SSL_CTX_set_tlsext_ticket_keys(c->ctx, b, TICKET_KEYS_SIZE); (*e)->ReleaseByteArrayElements(e, keys, b, 0); } #if defined(LIBRESSL_VERSION_NUMBER) /* * Adapted from OpenSSL: * https://github.com/openssl/openssl/blob/OpenSSL_1_0_2-stable/ssl/ssl_locl.h#L318 */ /* Bits for algorithm_auth (server authentication) */ /* DSS auth */ # define SSL_aDSS 0x00000002L /* OpenSSL end */ #define TCN_SSL_aDSS SSL_aDSS #else #define TCN_SSL_aDSS NID_auth_dss #endif #define TCN_SSL_kRSA NID_kx_rsa #define TCN_SSL_kDHE NID_kx_dhe #define TCN_SSL_kECDHE NID_kx_ecdhe #define TCN_SSL_aRSA NID_auth_rsa #define TCN_SSL_aNULL NID_auth_null #define TCN_SSL_aECDSA NID_auth_ecdsa /* * Adapted from Android: * https://android.googlesource.com/platform/external/openssl/+/master/patches/0003-jsse.patch */ static const char* SSL_CIPHER_authentication_method(const SSL_CIPHER* cipher){ int auth; int kx; if (cipher == NULL) { return "UNKNOWN"; } kx = SSL_CIPHER_get_kx_nid(cipher); auth = SSL_CIPHER_get_auth_nid(cipher); switch (kx) { case TCN_SSL_kRSA: return SSL_TXT_RSA; case TCN_SSL_kDHE: switch (auth) { case TCN_SSL_aDSS: return "DHE_" SSL_TXT_DSS; case TCN_SSL_aRSA: return "DHE_" SSL_TXT_RSA; case TCN_SSL_aNULL: return SSL_TXT_DH "_anon"; default: return "UNKNOWN"; } case TCN_SSL_kECDHE: switch (auth) { case TCN_SSL_aECDSA: return "ECDHE_" SSL_TXT_ECDSA; case TCN_SSL_aRSA: return "ECDHE_" SSL_TXT_RSA; case TCN_SSL_aNULL: return SSL_TXT_ECDH "_anon"; default: return "UNKNOWN"; } default: return "UNKNOWN"; } } static const char* SSL_authentication_method(const SSL* ssl) { /* XXX ssl->s3->tmp.new_cipher is no longer available in OpenSSL 1.1.0 */ /* https://github.com/netty/netty-tcnative/blob/1.1.33/openssl-dynamic/src/main/c/sslcontext.c * contains a different method, but I think this is not correct. * Instead of choosing the cipher used for the current handshake it simply * uses the first cipher available during the handshake. */ /* Not sure whether SSL_get_current_cipher(ssl) returns something useful * at the point in time we call it. */ return SSL_CIPHER_authentication_method(SSL_get_current_cipher(ssl)); } /* Android end */ static int SSL_cert_verify(X509_STORE_CTX *ctx, void *arg) { /* Get Apache context back through OpenSSL context */ SSL *ssl = X509_STORE_CTX_get_ex_data(ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); tcn_ssl_ctxt_t *c = SSL_get_app_data2(ssl); // Get a stack of all certs in the chain STACK_OF(X509) *sk = X509_STORE_CTX_get0_untrusted(ctx); int len = sk_X509_num(sk); unsigned i; X509 *cert; int length; unsigned char *buf; JNIEnv *e; jbyteArray array; jbyteArray bArray; const char *authMethod; jstring authMethodString; jboolean result; int r; tcn_get_java_env(&e); // Create the byte[][] array that holds all the certs array = (*e)->NewObjectArray(e, len, byteArrayClass, NULL); for(i = 0; i < len; i++) { cert = (X509*) sk_X509_value(sk, i); buf = NULL; length = i2d_X509(cert, &buf); if (length < 0) { // In case of error just return an empty byte[][] array = (*e)->NewObjectArray(e, 0, byteArrayClass, NULL); // We need to delete the local references so we not leak memory as this method is called via callback. OPENSSL_free(buf); break; } 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); } authMethod = SSL_authentication_method(ssl); authMethodString = (*e)->NewStringUTF(e, authMethod); result = (*e)->CallBooleanMethod(e, c->verifier, c->verifier_method, P2J(ssl), array, authMethodString); r = result == JNI_TRUE ? 1 : 0; // We need to delete the local references so we not leak memory as this method is called via callback. (*e)->DeleteLocalRef(e, authMethodString); (*e)->DeleteLocalRef(e, array); return r; } TCN_IMPLEMENT_CALL(void, SSLContext, setCertVerifyCallback)(TCN_STDARGS, jlong ctx, jobject verifier) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); UNREFERENCED(o); TCN_ASSERT(ctx != 0); if (verifier == NULL) { SSL_CTX_set_cert_verify_callback(c->ctx, NULL, NULL); } else { jclass verifier_class = (*e)->GetObjectClass(e, verifier); jmethodID method = (*e)->GetMethodID(e, verifier_class, "verify", "(J[[BLjava/lang/String;)Z"); if (method == NULL) { return; } // Delete the reference to the previous specified verifier if needed. if (c->verifier != NULL) { (*e)->DeleteLocalRef(e, c->verifier); } c->verifier = (*e)->NewGlobalRef(e, verifier); c->verifier_method = method; SSL_CTX_set_cert_verify_callback(c->ctx, SSL_cert_verify, NULL); } } TCN_IMPLEMENT_CALL(jboolean, SSLContext, setSessionIdContext)(TCN_STDARGS, jlong ctx, jbyteArray sidCtx) { tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *); int len = (*e)->GetArrayLength(e, sidCtx); unsigned char *buf; int res; UNREFERENCED(o); TCN_ASSERT(ctx != 0); buf = malloc(len); (*e)->GetByteArrayRegion(e, sidCtx, 0, len, (jbyte*) buf); res = SSL_CTX_set_session_id_context(c->ctx, buf, len); free(buf); if (res == 1) { return JNI_TRUE; } return JNI_FALSE; } /* End of netty-tc-native add */