blob: 78ad3167d40e0e11960552ad6025cd2a38702c45 [file] [log] [blame]
/* 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
*
* @author Mladen Turk
* @version $Id: sslcontext.c 1649733 2015-01-06 04:42:24Z billbarker $
*/
#include "tcn.h"
#include "apr_file_io.h"
#include "apr_thread_mutex.h"
#include "apr_thread_rwlock.h"
#include "apr_poll.h"
#ifdef HAVE_OPENSSL
#include "ssl_private.h"
static jclass byteArrayClass;
static apr_status_t ssl_context_cleanup(void *data)
{
tcn_ssl_ctxt_t *c = (tcn_ssl_ctxt_t *)data;
JNIEnv *e;
if (c) {
int i;
if (c->crl)
X509_STORE_free(c->crl);
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) {
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;
apr_thread_rwlock_destroy(c->mutex);
if (c->ticket_keys) {
free(c->ticket_keys);
c->ticket_keys = NULL;
}
c->ticket_keys_len = 0;
}
return APR_SUCCESS;
}
/* 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;
UNREFERENCED(o);
if (protocol == SSL_PROTOCOL_TLSV1_2) {
#ifdef SSL_OP_NO_TLSv1_2
if (mode == SSL_MODE_CLIENT)
ctx = SSL_CTX_new(TLSv1_2_client_method());
else if (mode == SSL_MODE_SERVER)
ctx = SSL_CTX_new(TLSv1_2_server_method());
else
ctx = SSL_CTX_new(TLSv1_2_method());
#endif
} else if (protocol == SSL_PROTOCOL_TLSV1_1) {
#ifdef SSL_OP_NO_TLSv1_1
if (mode == SSL_MODE_CLIENT)
ctx = SSL_CTX_new(TLSv1_1_client_method());
else if (mode == SSL_MODE_SERVER)
ctx = SSL_CTX_new(TLSv1_1_server_method());
else
ctx = SSL_CTX_new(TLSv1_1_method());
#endif
} else if (protocol == SSL_PROTOCOL_TLSV1) {
if (mode == SSL_MODE_CLIENT)
ctx = SSL_CTX_new(TLSv1_client_method());
else if (mode == SSL_MODE_SERVER)
ctx = SSL_CTX_new(TLSv1_server_method());
else
ctx = SSL_CTX_new(TLSv1_method());
#ifndef OPENSSL_NO_SSL3
} else if (protocol == SSL_PROTOCOL_SSLV3) {
if (mode == SSL_MODE_CLIENT)
ctx = SSL_CTX_new(SSLv3_client_method());
else if (mode == SSL_MODE_SERVER)
ctx = SSL_CTX_new(SSLv3_server_method());
else
ctx = SSL_CTX_new(SSLv3_method());
#endif
#if (OPENSSL_VERSION_NUMBER < 0x10100000L) && !defined(OPENSSL_NO_SSL2)
} else if (protocol == SSL_PROTOCOL_SSLV2) {
if (mode == SSL_MODE_CLIENT)
ctx = SSL_CTX_new(SSLv2_client_method());
else if (mode == SSL_MODE_SERVER)
ctx = SSL_CTX_new(SSLv2_server_method());
else
ctx = SSL_CTX_new(SSLv2_method());
#endif
#ifndef SSL_OP_NO_TLSv1_2
} else if (protocol & SSL_PROTOCOL_TLSV1_2) {
/* requested but not supported */
#endif
#ifndef SSL_OP_NO_TLSv1_1
} else if (protocol & SSL_PROTOCOL_TLSV1_1) {
/* requested but not supported */
#endif
#ifndef OPENSSL_NO_SSL3
} else {
if (mode == SSL_MODE_CLIENT)
ctx = SSL_CTX_new(SSLv23_client_method());
else if (mode == SSL_MODE_SERVER)
ctx = SSL_CTX_new(SSLv23_server_method());
else
ctx = SSL_CTX_new(SSLv23_method());
#endif
}
if (!ctx) {
char err[256];
ERR_error_string(ERR_get_error(), 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;
}
c->protocol = protocol;
c->mode = mode;
c->ctx = ctx;
c->pool = p;
c->bio_os = NULL;
SSL_CTX_set_options(c->ctx, SSL_OP_ALL);
if (!(protocol & SSL_PROTOCOL_SSLV2))
SSL_CTX_set_options(c->ctx, SSL_OP_NO_SSLv2);
if (!(protocol & SSL_PROTOCOL_SSLV3))
SSL_CTX_set_options(c->ctx, SSL_OP_NO_SSLv3);
if (!(protocol & SSL_PROTOCOL_TLSV1))
SSL_CTX_set_options(c->ctx, SSL_OP_NO_TLSv1);
#ifdef SSL_OP_NO_TLSv1_1
if (!(protocol & SSL_PROTOCOL_TLSV1_1))
SSL_CTX_set_options(c->ctx, SSL_OP_NO_TLSv1_1);
#endif
#ifdef SSL_OP_NO_TLSv1_2
if (!(protocol & SSL_PROTOCOL_TLSV1_2))
SSL_CTX_set_options(c->ctx, SSL_OP_NO_TLSv1_2);
#endif
/*
* 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
SSL_CTX_set_options(c->ctx, SSL_OP_NO_COMPRESSION);
#else
#error "SSL_OP_NO_COMPRESSION not supported in your version of OpenSSL"
#endif
#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);
#else
#error "SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION not supported in your version of OpenSSL"
#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);
#else
#error "SSL_MODE_RELEASE_BUFFERS not supported in your version of OpenSSL"
#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);
if (mode) {
#ifdef HAVE_ECC
/* Set default (nistp256) elliptic curve for ephemeral ECDH keys */
EC_KEY *ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
SSL_CTX_set_tmp_ecdh(c->ctx, ecdh);
EC_KEY_free(ecdh);
#endif
// This method should not be used when using libressl as it will result in
// error:14085042:SSL routines:SSL3_CTX_CTRL:called a function you should not call
//
// See also http://forum.nginx.org/read.php?2,256381,257336#msg-257336
#ifndef LIBRESSL_VERSION_NUMBER
SSL_CTX_set_tmp_rsa_callback(c->ctx, SSL_callback_tmp_RSA);
#endif
SSL_CTX_set_tmp_dh_callback(c->ctx, SSL_callback_tmp_DH);
}
/* 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);
apr_thread_rwlock_create(&c->mutex, p);
/*
* 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
clazz = (*e)->FindClass(e, "[B");
byteArrayClass = (jclass) (*e)->NewGlobalRef(e, clazz);
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;
UNREFERENCED(o);
TCN_ASSERT(ctx != 0);
if (!J2S(ciphers))
return JNI_FALSE;
if (!SSL_CTX_set_cipher_list(c->ctx, J2S(ciphers))) {
char err[256];
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(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 = X509_STORE_new()) == NULL)
goto cleanup;
}
if (J2S(file)) {
lookup = X509_STORE_add_lookup(c->crl, X509_LOOKUP_file());
if (lookup == NULL) {
ERR_error_string(ERR_get_error(), err);
X509_STORE_free(c->crl);
c->crl = NULL;
tcn_Throw(e, "Lookup failed for file %s (%s)", J2S(file), err);
goto cleanup;
}
X509_LOOKUP_load_file(lookup, J2S(file), X509_FILETYPE_PEM);
}
if (J2S(path)) {
lookup = X509_STORE_add_lookup(c->crl, X509_LOOKUP_hash_dir());
if (lookup == NULL) {
ERR_error_string(ERR_get_error(), err);
X509_STORE_free(c->crl);
c->crl = NULL;
tcn_Throw(e, "Lookup failed for path %s (%s)", J2S(file), err);
goto cleanup;
}
X509_LOOKUP_add_dir(lookup, J2S(path), X509_FILETYPE_PEM);
}
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, setCertificateChainBio)(TCN_STDARGS, jlong ctx,
jlong chain,
jboolean skipfirst)
{
tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
BIO *b = J2P(chain, BIO *);
UNREFERENCED(o);
TCN_ASSERT(ctx != 0);
if (b == NULL)
return JNI_FALSE;
if (SSL_CTX_use_certificate_chain_bio(c->ctx, b, skipfirst) > 0) {
return JNI_TRUE;
}
return JNI_FALSE;
}
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(ERR_get_error(), 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) {
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(ERR_get_error(), 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(ERR_get_error(), 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(ERR_get_error(), 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(ERR_get_error(), 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 != NULL)
break;
cb_data->password[0] = '\0';
BIO_ctrl(bio, BIO_CTRL_RESET, 0, NULL);
}
BIO_free(bio);
return key;
}
static EVP_PKEY *load_pem_key_bio(tcn_ssl_ctxt_t *c, const BIO *bio)
{
EVP_PKEY *key = NULL;
tcn_pass_cb_t *cb_data = c->cb_data;
int i;
if (cb_data == NULL)
cb_data = &tcn_password_callback;
for (i = 0; i < 3; i++) {
key = PEM_read_bio_PrivateKey((BIO*) bio, NULL,
(pem_password_cb *)SSL_password_callback,
(void *)cb_data);
if (key)
break;
cb_data->password[0] = '\0';
BIO_ctrl((BIO*) bio, BIO_CTRL_RESET, 0, NULL);
}
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)) {
ERR_clear_error();
BIO_ctrl(bio, BIO_CTRL_RESET, 0, NULL);
cert = d2i_X509_bio(bio, NULL);
}
BIO_free(bio);
return cert;
}
static X509 *load_pem_cert_bio(tcn_ssl_ctxt_t *c, const BIO *bio)
{
X509 *cert = NULL;
tcn_pass_cb_t *cb_data = c->cb_data;
if (cb_data == NULL)
cb_data = &tcn_password_callback;
cert = PEM_read_bio_X509_AUX((BIO*) 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)) {
ERR_clear_error();
BIO_ctrl((BIO*) bio, BIO_CTRL_RESET, 0, NULL);
cert = d2i_X509_bio((BIO*) bio, NULL);
}
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];
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);
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(ERR_get_error(), 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) {
ERR_error_string(ERR_get_error(), 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(ERR_get_error(), 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(ERR_get_error(), 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(ERR_get_error(), 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(ERR_get_error(), err);
tcn_Throw(e, "Private key does not match the certificate public key (%s)",
err);
rv = JNI_FALSE;
goto cleanup;
}
cleanup:
TCN_FREE_CSTRING(cert);
TCN_FREE_CSTRING(key);
TCN_FREE_CSTRING(password);
return rv;
}
TCN_IMPLEMENT_CALL(jboolean, SSLContext, setCertificateBio)(TCN_STDARGS, jlong ctx,
jlong cert, jlong key,
jstring password, jint idx)
{
tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
BIO *cert_bio = J2P(cert, BIO *);
BIO *key_bio = J2P(key, BIO *);
jboolean rv = JNI_TRUE;
TCN_ALLOC_CSTRING(password);
char err[256];
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);
c->cb_data->password[SSL_MAX_PASSWORD_LEN-1] = '\0';
}
if (!key)
key = cert;
if (!cert || !key) {
tcn_Throw(e, "No Certificate file specified or invalid file format");
rv = JNI_FALSE;
goto cleanup;
}
if ((c->keys[idx] = load_pem_key_bio(c, key_bio)) == NULL) {
ERR_error_string(ERR_get_error(), err);
ERR_clear_error();
tcn_Throw(e, "Unable to load certificate key (%s)",err);
rv = JNI_FALSE;
goto cleanup;
}
if ((c->certs[idx] = load_pem_cert_bio(c, cert_bio)) == NULL) {
ERR_error_string(ERR_get_error(), err);
ERR_clear_error();
tcn_Throw(e, "Unable to load certificate (%s) ", err);
rv = JNI_FALSE;
goto cleanup;
}
if (SSL_CTX_use_certificate(c->ctx, c->certs[idx]) <= 0) {
ERR_error_string(ERR_get_error(), err);
ERR_clear_error();
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(ERR_get_error(), err);
ERR_clear_error();
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(ERR_get_error(), err);
ERR_clear_error();
tcn_Throw(e, "Private key does not match the certificate public key (%s)",
err);
rv = JNI_FALSE;
goto cleanup;
}
cleanup:
TCN_FREE_CSTRING(password);
return rv;
}
// Convert protos to wire format
static int initProtocols(JNIEnv *e, 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_NPN_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, setNpnProtos)(TCN_STDARGS, jlong ctx, jobjectArray next_protos,
jint selectorFailureBehavior)
{
tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
TCN_ASSERT(ctx != 0);
UNREFERENCED(o);
if (initProtocols(e, &c->next_proto_data, &c->next_proto_len, next_protos) == 0) {
c->next_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_next_proto_select_cb(c->ctx, SSL_callback_select_next_proto, (void *)c);
} else {
SSL_CTX_set_next_protos_advertised_cb(c->ctx, SSL_callback_next_protos, (void *)c);
}
}
}
TCN_IMPLEMENT_CALL(void, SSLContext, setAlpnProtos)(TCN_STDARGS, jlong ctx, jobjectArray alpn_protos,
jint selectorFailureBehavior)
{
// Only supported with GCC
#if defined(__GNUC__) || defined(__GNUG__)
if (!SSL_CTX_set_alpn_protos || !SSL_CTX_set_alpn_select_cb) {
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
UNREFERENCED(alpn_protos);
return;
}
#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__)
tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
TCN_ASSERT(ctx != 0);
UNREFERENCED(o);
if (initProtocols(e, &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);
}
}
#else
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
UNREFERENCED(alpn_protos);
#endif
}
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;
}
static int current_session_key(tcn_ssl_ctxt_t *c, tcn_ssl_ticket_key_t *key) {
int result = JNI_FALSE;
apr_thread_rwlock_rdlock(c->mutex);
if (c->ticket_keys_len > 0) {
*key = c->ticket_keys[0];
result = JNI_TRUE;
}
apr_thread_rwlock_unlock(c->mutex);
return result;
}
static int find_session_key(tcn_ssl_ctxt_t *c, unsigned char key_name[16], tcn_ssl_ticket_key_t *key, int *is_current_key) {
int result = JNI_FALSE;
int i;
apr_thread_rwlock_rdlock(c->mutex);
for (i = 0; i < c->ticket_keys_len; ++i) {
// Check if we have a match for tickets.
if (memcmp(c->ticket_keys[i].key_name, key_name, 16) == 0) {
*key = c->ticket_keys[i];
result = JNI_TRUE;
*is_current_key = (i == 0);
break;
}
}
apr_thread_rwlock_unlock(c->mutex);
return result;
}
static int ssl_tlsext_ticket_key_cb(SSL *s, unsigned char key_name[16], unsigned char *iv, EVP_CIPHER_CTX *ctx, HMAC_CTX *hctx, int enc) {
tcn_ssl_ctxt_t *c = SSL_get_app_data2(s);
tcn_ssl_ticket_key_t key;
int is_current_key;
if (enc) { /* create new session */
if (current_session_key(c, &key)) {
if (RAND_bytes(iv, EVP_MAX_IV_LENGTH) <= 0) {
return -1; /* insufficient random */
}
memcpy(key_name, key.key_name, 16);
EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key.aes_key, iv);
HMAC_Init_ex(hctx, key.hmac_key, 16, EVP_sha256(), NULL);
return 1;
}
// No ticket configured
return 0;
} else { /* retrieve session */
if (find_session_key(c, key_name, &key, &is_current_key)) {
HMAC_Init_ex(hctx, key.hmac_key, 16, EVP_sha256(), NULL);
EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key.aes_key, iv );
if (!is_current_key) {
return 2;
}
return 1;
}
// No ticket
return 0;
}
}
TCN_IMPLEMENT_CALL(void, SSLContext, setSessionTicketKeys0)(TCN_STDARGS, jlong ctx, jbyteArray keys)
{
tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);
jbyte* b;
jbyte* key;
tcn_ssl_ticket_key_t* ticket_keys;
int i;
int cnt;
cnt = (*e)->GetArrayLength(e, keys) / SSL_SESSION_TICKET_KEY_SIZE;
b = (*e)->GetByteArrayElements(e, keys, NULL);
ticket_keys = malloc(sizeof(tcn_ssl_ticket_key_t) * cnt);
for (i = 0; i < cnt; ++i) {
key = b + (SSL_SESSION_TICKET_KEY_SIZE * i);
memcpy(ticket_keys[i].key_name, key, 16);
memcpy(ticket_keys[i].hmac_key, key + 16, 16);
memcpy(ticket_keys[i].aes_key, key + 32, 16);
}
(*e)->ReleaseByteArrayElements(e, keys, b, 0);
apr_thread_rwlock_wrlock(c->mutex);
if (c->ticket_keys) {
free(c->ticket_keys);
}
c->ticket_keys_len = cnt;
c->ticket_keys = ticket_keys;
apr_thread_rwlock_unlock(c->mutex);
SSL_CTX_set_tlsext_ticket_key_cb(c->ctx, ssl_tlsext_ticket_key_cb);
}
/*
* Adapted from OpenSSL:
* http://osxr.org/openssl/source/ssl/ssl_locl.h#0291
*/
/* Bits for algorithm_mkey (key exchange algorithm) */
#define SSL_kRSA 0x00000001L /* RSA key exchange */
#define SSL_kDHr 0x00000002L /* DH cert, RSA CA cert */ /* no such ciphersuites supported! */
#define SSL_kDHd 0x00000004L /* DH cert, DSA CA cert */ /* no such ciphersuite supported! */
#define SSL_kEDH 0x00000008L /* tmp DH key no DH cert */
#define SSL_kKRB5 0x00000010L /* Kerberos5 key exchange */
#define SSL_kECDHr 0x00000020L /* ECDH cert, RSA CA cert */
#define SSL_kECDHe 0x00000040L /* ECDH cert, ECDSA CA cert */
#define SSL_kEECDH 0x00000080L /* ephemeral ECDH */
#define SSL_kPSK 0x00000100L /* PSK */
#define SSL_kGOST 0x00000200L /* GOST key exchange */
#define SSL_kSRP 0x00000400L /* SRP */
/* Bits for algorithm_auth (server authentication) */
#define SSL_aRSA 0x00000001L /* RSA auth */
#define SSL_aDSS 0x00000002L /* DSS auth */
#define SSL_aNULL 0x00000004L /* no auth (i.e. use ADH or AECDH) */
#define SSL_aDH 0x00000008L /* Fixed DH auth (kDHd or kDHr) */ /* no such ciphersuites supported! */
#define SSL_aECDH 0x00000010L /* Fixed ECDH auth (kECDHe or kECDHr) */
#define SSL_aKRB5 0x00000020L /* KRB5 auth */
#define SSL_aECDSA 0x00000040L /* ECDSA auth*/
#define SSL_aPSK 0x00000080L /* PSK auth */
#define SSL_aGOST94 0x00000100L /* GOST R 34.10-94 signature auth */
#define SSL_aGOST01 0x00000200L /* GOST R 34.10-2001 signature auth */
/* OpenSSL end */
/*
* Adapted from Android:
* https://android.googlesource.com/platform/external/openssl/+/master/patches/0003-jsse.patch
*/
const char* cipher_authentication_method(const SSL_CIPHER* cipher){
#ifndef OPENSSL_IS_BORINGSSL
switch (cipher->algorithm_mkey)
{
case SSL_kRSA:
return SSL_TXT_RSA;
case SSL_kDHr:
return SSL_TXT_DH "_" SSL_TXT_RSA;
case SSL_kDHd:
return SSL_TXT_DH "_" SSL_TXT_DSS;
case SSL_kEDH:
switch (cipher->algorithm_auth)
{
case SSL_aDSS:
return "DHE_" SSL_TXT_DSS;
case SSL_aRSA:
return "DHE_" SSL_TXT_RSA;
case SSL_aNULL:
return SSL_TXT_DH "_anon";
default:
return "UNKNOWN";
}
case SSL_kKRB5:
return SSL_TXT_KRB5;
case SSL_kECDHr:
return SSL_TXT_ECDH "_" SSL_TXT_RSA;
case SSL_kECDHe:
return SSL_TXT_ECDH "_" SSL_TXT_ECDSA;
case SSL_kEECDH:
switch (cipher->algorithm_auth)
{
case SSL_aECDSA:
return "ECDHE_" SSL_TXT_ECDSA;
case SSL_aRSA:
return "ECDHE_" SSL_TXT_RSA;
case SSL_aNULL:
return SSL_TXT_ECDH "_anon";
default:
return "UNKNOWN";
}
default:
return "UNKNOWN";
}
#else
return SSL_CIPHER_get_kx_name(cipher);
#endif
}
static const char* authentication_method(const SSL* ssl) {
{
switch (SSL_version(ssl))
{
case SSL2_VERSION:
return SSL_TXT_RSA;
default:
#if defined(OPENSSL_IS_BORINGSSL)
return cipher_authentication_method(SSL_get_pending_cipher(ssl));
#else
return cipher_authentication_method(ssl->s3->tmp.new_cipher);
#endif
}
}
}
/* 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 = ctx->untrusted;
int len = sk_num((_STACK*) 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_value((_STACK*) 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 = 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;
}
#else
/* OpenSSL is not supported.
* Create empty stubs.
*/
TCN_IMPLEMENT_CALL(jlong, SSLContext, make)(TCN_STDARGS, jlong pool,
jint protocol, jint mode)
{
UNREFERENCED_STDARGS;
UNREFERENCED(pool);
UNREFERENCED(protocol);
UNREFERENCED(mode);
return 0;
}
TCN_IMPLEMENT_CALL(jint, SSLContext, free)(TCN_STDARGS, jlong ctx)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
return APR_ENOTIMPL;
}
TCN_IMPLEMENT_CALL(void, SSLContext, setContextId)(TCN_STDARGS, jlong ctx,
jstring id)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
UNREFERENCED(id);
}
TCN_IMPLEMENT_CALL(void, SSLContext, setBIO)(TCN_STDARGS, jlong ctx,
jlong bio, jint dir)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
UNREFERENCED(bio);
UNREFERENCED(dir);
}
TCN_IMPLEMENT_CALL(void, SSLContext, setOptions)(TCN_STDARGS, jlong ctx,
jint opt)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
UNREFERENCED(opt);
}
TCN_IMPLEMENT_CALL(jint, SSLContext, getOptions)(TCN_STDARGS, jlong ctx)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
}
TCN_IMPLEMENT_CALL(void, SSLContext, clearOptions)(TCN_STDARGS, jlong ctx,
jint opt)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
UNREFERENCED(opt);
}
TCN_IMPLEMENT_CALL(void, SSLContext, setQuietShutdown)(TCN_STDARGS, jlong ctx,
jboolean mode)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
UNREFERENCED(mode);
}
TCN_IMPLEMENT_CALL(jboolean, SSLContext, setCipherSuite)(TCN_STDARGS, jlong ctx,
jstring ciphers)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
UNREFERENCED(ciphers);
return JNI_FALSE;
}
TCN_IMPLEMENT_CALL(jboolean, SSLContext, setCARevocation)(TCN_STDARGS, jlong ctx,
jstring file,
jstring path)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
UNREFERENCED(file);
UNREFERENCED(path);
return JNI_FALSE;
}
TCN_IMPLEMENT_CALL(jboolean, SSLContext, setCertificateChainFile)(TCN_STDARGS, jlong ctx,
jstring file,
jboolean skipfirst)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
UNREFERENCED(file);
UNREFERENCED(skipfirst);
return JNI_FALSE;
}
TCN_IMPLEMENT_CALL(jboolean, SSLContext, setCertificateChainBio)(TCN_STDARGS, jlong ctx,
jlong chain,
jboolean skipfirst)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
UNREFERENCED(chain);
UNREFERENCED(skipfirst);
return JNI_FALSE;
}
TCN_IMPLEMENT_CALL(jboolean, SSLContext, setCACertificate)(TCN_STDARGS,
jlong ctx,
jstring file,
jstring path)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
UNREFERENCED(file);
UNREFERENCED(path);
return JNI_FALSE;
}
TCN_IMPLEMENT_CALL(void, SSLContext, setShutdownType)(TCN_STDARGS, jlong ctx,
jint type)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
UNREFERENCED(type);
}
TCN_IMPLEMENT_CALL(void, SSLContext, setVerify)(TCN_STDARGS, jlong ctx,
jint level, jint depth)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
UNREFERENCED(level);
UNREFERENCED(depth);
}
TCN_IMPLEMENT_CALL(void, SSLContext, setRandom)(TCN_STDARGS, jlong ctx,
jstring file)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
UNREFERENCED(file);
}
TCN_IMPLEMENT_CALL(jboolean, SSLContext, setCertificate)(TCN_STDARGS, jlong ctx,
jstring cert, jstring key,
jstring password, jint idx)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
UNREFERENCED(cert);
UNREFERENCED(key);
UNREFERENCED(password);
UNREFERENCED(idx);
return JNI_FALSE;
}
TCN_IMPLEMENT_CALL(jboolean, SSLContext, setCertificateBio)(TCN_STDARGS, jlong ctx,
jlong cert, jlong key,
jstring password, jint idx)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
UNREFERENCED(cert);
UNREFERENCED(key);
UNREFERENCED(password);
UNREFERENCED(idx);
return JNI_FALSE;
}
TCN_IMPLEMENT_CALL(void, SSLContext, setNpnProtos)(TCN_STDARGS, jlong ctx, jobjectArray next_protos,
jint selectorFailureBehavior)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
UNREFERENCED(next_protos);
}
TCN_IMPLEMENT_CALL(void, SSLContext, setAlpnProtos)(TCN_STDARGS, jlong ctx, jobjectArray alpn_protos,
jint selectorFailureBehavior)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
UNREFERENCED(alpn_protos);
}
TCN_IMPLEMENT_CALL(jlong, SSLContext, setSessionCacheMode)(TCN_STDARGS, jlong ctx, jlong mode)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
UNREFERENCED(mode);
return -1;
}
TCN_IMPLEMENT_CALL(jlong, SSLContext, getSessionCacheMode)(TCN_STDARGS, jlong ctx)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
return -1;
}
TCN_IMPLEMENT_CALL(jlong, SSLContext, setSessionCacheTimeout)(TCN_STDARGS, jlong ctx, jlong timeout)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
UNREFERENCED(timeout);
return -1;
}
TCN_IMPLEMENT_CALL(jlong, SSLContext, getSessionCacheTimeout)(TCN_STDARGS, jlong ctx)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
return -1;
}
TCN_IMPLEMENT_CALL(jlong, SSLContext, setSessionCacheSize)(TCN_STDARGS, jlong ctx, jlong size)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
UNREFERENCED(size);
return -1;
}
TCN_IMPLEMENT_CALL(jlong, SSLContext, getSessionCacheSize)(TCN_STDARGS, jlong ctx)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
return -1;
}
TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionNumber)(TCN_STDARGS, jlong ctx)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
return 0;
}
TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionConnect)(TCN_STDARGS, jlong ctx)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
return 0;
}
TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionConnectGood)(TCN_STDARGS, jlong ctx)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
return 0;
}
TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionConnectRenegotiate)(TCN_STDARGS, jlong ctx)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
return 0;
}
TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionAccept)(TCN_STDARGS, jlong ctx)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
return 0;
}
TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionAcceptGood)(TCN_STDARGS, jlong ctx)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
return 0;
}
TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionAcceptRenegotiate)(TCN_STDARGS, jlong ctx)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
return 0;
}
TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionHits)(TCN_STDARGS, jlong ctx)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
return 0;
}
TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionCbHits)(TCN_STDARGS, jlong ctx)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
return 0;
}
TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionTimeouts)(TCN_STDARGS, jlong ctx)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
return 0;
}
TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionCacheFull)(TCN_STDARGS, jlong ctx)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
return 0;
}
TCN_IMPLEMENT_CALL(jlong, SSLContext, sessionMisses)(TCN_STDARGS, jlong ctx)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
return 0;
}
TCN_IMPLEMENT_CALL(void, SSLContext, setSessionTicketKeys0)(TCN_STDARGS, jlong ctx, jbyteArray keys)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
UNREFERENCED(keys);
}
TCN_IMPLEMENT_CALL(void, SSLContext, setCertVerifyCallback)(TCN_STDARGS, jlong ctx, jobject verifier)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
UNREFERENCED(verifier);
}
TCN_IMPLEMENT_CALL(jboolean, SSLContext, setSessionIdContext)(TCN_STDARGS, jlong ctx, jbyteArray sidCtx)
{
UNREFERENCED_STDARGS;
UNREFERENCED(ctx);
UNREFERENCED(sidCtx);
return JNI_FALSE;
}
#endif