Skip to content

Commit

Permalink
Expose way to use SSL_CTX_set_cert_cb as general replacement of SSL_C…
Browse files Browse the repository at this point in the history
…TX_set_client_cert_cb and expose some more functions. (netty#388)

Motivation:

Currently we only support SSL_CTX_set_client_cert_cb which only works for client side and so we have no good way to setup certificates on the server-side in a proper way that is "safe". Beside this SSL_CTX_set_client_cert_cb should be considered as "deprecated" and SSL_CTX_set_cert_cb should be use as its replacement.

By using SSL_CTX_set_cert_cb we can also ensure the SSL Hello was send and so the SNI / algorithms etc can be accessed in all cases. Methods for doing so were also mssing.

Modifications:

- Add method to make use of SSL_CTX_set_cert_cb and mark the old methods that uses SSL_CTX_set_client_cert_cb as deprecated (+ the old callback interface).
- Add method to retrieve requested SNI hostname
- Add methods to retrieve the supported signature algorithms by the peer.

Result:

More correct way to hook in validation / keymaterial selection when using client / server mode and ways to retrieve addional infos.
  • Loading branch information
normanmaurer committed Sep 14, 2018
1 parent 0a13a3f commit e47724e
Show file tree
Hide file tree
Showing 7 changed files with 318 additions and 27 deletions.
72 changes: 69 additions & 3 deletions openssl-dynamic/src/main/c/ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -1910,7 +1910,7 @@ TCN_IMPLEMENT_CALL(void, SSL, freeX509Chain)(TCN_STDARGS, jlong x509Chain)
sk_X509_pop_free(chain, X509_free);
}

TCN_IMPLEMENT_CALL(void, SSL, setKeyMaterialServerSide)(TCN_STDARGS, jlong ssl, jlong chain, jlong key)
TCN_IMPLEMENT_CALL(void, SSL, setKeyMaterial)(TCN_STDARGS, jlong ssl, jlong chain, jlong key)
{
#if defined(LIBRESSL_VERSION_NUMBER)
tcn_Throw(e, "Not supported with LibreSSL");
Expand Down Expand Up @@ -2196,6 +2196,70 @@ TCN_IMPLEMENT_CALL(void, SSL, fipsModeSet)(TCN_STDARGS, jint mode)
#endif
}

TCN_IMPLEMENT_CALL(jstring, SSL, getSniHostname)(TCN_STDARGS, jlong ssl)
{
SSL *ssl_ = J2P(ssl, SSL *);
TCN_CHECK_NULL(ssl_, ssl, 0);

const char *servername = SSL_get_servername(ssl_, TLSEXT_NAMETYPE_host_name);
if (servername == NULL) {
return NULL;
}
return tcn_new_string(e, servername);
}

TCN_IMPLEMENT_CALL(jobjectArray, SSL, getSigAlgs)(TCN_STDARGS, jlong ssl) {
SSL *ssl_ = J2P(ssl, SSL *);
TCN_CHECK_NULL(ssl_, ssl, NULL);

// Not supported by BoringSSL and LibreSSL
// https://boringssl.googlesource.com/boringssl/+/ba16a1e405c617f4179bd780ad15522fb25b0a65%5E%21/
#if defined(OPENSSL_IS_BORINGSSL) || defined(LIBRESSL_VERSION_NUMBER)
return NULL;
#else

// Use weak linking with GCC as this will alow us to run the same packaged version with multiple
// version of openssl.
#if defined(__GNUC__) || defined(__GNUG__)
if (!SSL_get_sigalgs) {
UNREFERENCED(o);
return NULL;
}
#endif

// We can only support it when either use openssl version >= 1.0.2 or GCC as this way we can use weak linking
#if OPENSSL_VERSION_NUMBER >= 0x10002000L || defined(__GNUC__) || defined(__GNUG__)
int i;
int nsig;
int psignhash;
jobjectArray array;
jstring algString;

UNREFERENCED(o);

nsig = SSL_get_sigalgs(ssl_, 0, NULL, NULL, NULL, NULL, NULL);
if (nsig <= 0) {
return NULL;
}
array = (*e)->NewObjectArray(e, nsig, tcn_get_string_class(), NULL);

if (array == NULL) {
return NULL;
}

for (i = 0; i < nsig; i++) {
SSL_get_sigalgs(ssl_, i, NULL, NULL, &psignhash, NULL, NULL);
algString = (*e)->NewStringUTF(e, OBJ_nid2ln(psignhash));
if (algString == NULL) {
// something is wrong we should better just return here
return NULL;
}
(*e)->SetObjectArrayElement(e, array, i, algString);
}
return array;
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L || defined(__GNUC__) || defined(__GNUG__)
#endif // defined(OPENSSL_IS_BORINGSSL) || defined(LIBRESSL_VERSION_NUMBER)
}

// JNI Method Registration Table Begin
static const JNINativeMethod method_table[] = {
Expand Down Expand Up @@ -2257,12 +2321,14 @@ static const JNINativeMethod method_table[] = {
{ TCN_METHOD_TABLE_ENTRY(freePrivateKey, (J)V, SSL) },
{ TCN_METHOD_TABLE_ENTRY(parseX509Chain, (J)J, SSL) },
{ TCN_METHOD_TABLE_ENTRY(freeX509Chain, (J)V, SSL) },
{ TCN_METHOD_TABLE_ENTRY(setKeyMaterialServerSide, (JJJ)V, SSL) },
{ TCN_METHOD_TABLE_ENTRY(setKeyMaterial, (JJJ)V, SSL) },
{ TCN_METHOD_TABLE_ENTRY(setKeyMaterialClientSide, (JJJJJ)V, SSL) },
{ TCN_METHOD_TABLE_ENTRY(enableOcsp, (J)V, SSL) },
{ TCN_METHOD_TABLE_ENTRY(setOcspResponse, (J[B)V, SSL) },
{ TCN_METHOD_TABLE_ENTRY(getOcspResponse, (J)[B, SSL) },
{ TCN_METHOD_TABLE_ENTRY(fipsModeSet, (I)V, SSL) }
{ TCN_METHOD_TABLE_ENTRY(fipsModeSet, (I)V, SSL) },
{ TCN_METHOD_TABLE_ENTRY(getSniHostname, (J)Ljava/lang/String;, SSL) },
{ TCN_METHOD_TABLE_ENTRY(getSigAlgs, (J)[Ljava/lang/String;, SSL) }
};

static const jint method_table_size = sizeof(method_table) / sizeof(method_table[0]);
Expand Down
6 changes: 6 additions & 0 deletions openssl-dynamic/src/main/c/ssl_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,9 @@ struct tcn_ssl_ctxt_t {
jobject cert_requested_callback;
jmethodID cert_requested_callback_method;

jobject certificate_callback;
jmethodID certificate_callback_method;

jobject sni_hostname_matcher;
jmethodID sni_hostname_matcher_method;

Expand Down Expand Up @@ -317,6 +320,9 @@ const char *tcn_SSL_cipher_authentication_method(const SSL_CIPHER *);
extern X509_VERIFY_PARAM *SSL_get0_param(SSL *ssl) __attribute__((weak));
extern void X509_VERIFY_PARAM_set_hostflags(X509_VERIFY_PARAM *param, unsigned int flags) __attribute__((weak));
extern int X509_VERIFY_PARAM_set1_host(X509_VERIFY_PARAM *param, const char *name, size_t namelen) __attribute__((weak));

extern int SSL_get_sigalgs(SSL *s, int idx, int *psign, int *phash, int *psignhash, unsigned char *rsig, unsigned char *rhash) __attribute__((weak));
extern void SSL_CTX_set_cert_cb(SSL_CTX *c, int (*cert_cb)(SSL *ssl, void *arg), void *arg) __attribute__((weak));
#endif

#endif /* SSL_PRIVATE_H */
153 changes: 138 additions & 15 deletions openssl-dynamic/src/main/c/sslcontext.c
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ static apr_status_t ssl_context_cleanup(void *data)
}
c->cert_requested_callback_method = NULL;

if (c->certificate_callback != NULL) {
tcn_get_java_env(&e);
(*e)->DeleteGlobalRef(e, c->certificate_callback);
c->certificate_callback = NULL;
}
c->certificate_callback_method = NULL;

if (c->sni_hostname_matcher != NULL) {
tcn_get_java_env(&e);
(*e)->DeleteGlobalRef(e, c->sni_hostname_matcher);
Expand Down Expand Up @@ -1427,6 +1434,24 @@ TCN_IMPLEMENT_CALL(void, SSLContext, setCertVerifyCallback)(TCN_STDARGS, jlong c
}
}


static jbyteArray keyTypes(JNIEnv* e, SSL* ssl) {
jbyte* ctype_bytes;
jbyteArray types;
int ctype_num = tcn_SSL_get0_certificate_types(ssl, (const uint8_t **) &ctype_bytes);
if (ctype_num <= 0) {
// Use no certificate
return NULL;
}
types = (*e)->NewByteArray(e, ctype_num);
if (types == NULL) {
return NULL;
}
(*e)->SetByteArrayRegion(e, types, 0, ctype_num, ctype_bytes);
return types;
}


/**
* Returns an array containing all the X500 principal's bytes.
*
Expand Down Expand Up @@ -1495,31 +1520,22 @@ static int cert_requested(SSL* ssl, X509** x509Out, EVP_PKEY** pkeyOut) {
#endif // OPENSSL_IS_BORINGSSL

tcn_ssl_ctxt_t *c = tcn_SSL_get_app_data2(ssl);
int ctype_num;
jbyte* ctype_bytes;
jobjectArray issuers;
JNIEnv *e;
jbyteArray keyTypes;
jbyteArray types;

tcn_get_java_env(&e);

ctype_num = tcn_SSL_get0_certificate_types(ssl, (const uint8_t **) &ctype_bytes);
if (ctype_num <= 0) {
// Use no certificate
types = keyTypes(e, ssl);
if (types == NULL) {
return 0;
}
keyTypes = (*e)->NewByteArray(e, ctype_num);
if (keyTypes == NULL) {
// Something went seriously wrong, bail out!
return -1;
}
(*e)->SetByteArrayRegion(e, keyTypes, 0, ctype_num, ctype_bytes);

issuers = principalBytes(e, SSL_get_client_CA_list(ssl));

// Execute the java callback
(*e)->CallVoidMethod(e, c->cert_requested_callback, c->cert_requested_callback_method,
P2J(ssl), P2J(x509Out), P2J(pkeyOut), keyTypes, issuers);
P2J(ssl), P2J(x509Out), P2J(pkeyOut), types, issuers);

// Check if java threw an exception and if so signal back that we should not continue with the handshake.
if ((*e)->ExceptionCheck(e)) {
Expand Down Expand Up @@ -1564,6 +1580,105 @@ TCN_IMPLEMENT_CALL(void, SSLContext, setCertRequestedCallback)(TCN_STDARGS, jlon
}
}


static int certificate_cb(SSL* ssl, void* arg) {
#if defined(LIBRESSL_VERSION_NUMBER)
// Not supported with LibreSSL
return -1;
#else
#ifndef OPENSSL_IS_BORINGSSL
if (OpenSSL_version_num() < 0x10002000L) {
// Only supported on openssl 1.0.2+
return -1;
}
#endif // OPENSSL_IS_BORINGSSL

tcn_ssl_ctxt_t *c = tcn_SSL_get_app_data2(ssl);
TCN_ASSERT(c != NULL);

jobjectArray issuers;
JNIEnv *e;
jbyteArray types;

tcn_get_java_env(&e);

if (c->mode == SSL_MODE_SERVER) {
// TODO: Consider filling these somehow.
types = NULL;
issuers = NULL;
} else {
types = keyTypes(e, ssl);
if (types == NULL) {
return 0;
}

issuers = principalBytes(e, SSL_get_client_CA_list(ssl));
}

// Execute the java callback
(*e)->CallVoidMethod(e, c->certificate_callback, c->certificate_callback_method,
P2J(ssl), types, issuers);

// Check if java threw an exception and if so signal back that we should not continue with the handshake.
if ((*e)->ExceptionCheck(e)) {
return -1;
}

// Everything good...
return 1;
#endif /* defined(LIBRESSL_VERSION_NUMBER) */
}

TCN_IMPLEMENT_CALL(void, SSLContext, setCertificateCallback)(TCN_STDARGS, jlong ctx, jobject callback)
{
tcn_ssl_ctxt_t *c = J2P(ctx, tcn_ssl_ctxt_t *);

TCN_CHECK_NULL(c, ctx, /* void */);

#if defined(LIBRESSL_VERSION_NUMBER)
tcn_Throw(e, "Not supported with LibreSSL");
#else
UNREFERENCED(o);

// Use weak linking with GCC as this will alow us to run the same packaged version with multiple
// version of openssl.
#if defined(__GNUC__) || defined(__GNUG__)
if (!SSL_CTX_set_cert_cb) {
UNREFERENCED(o);
tcn_ThrowException(e, "Requires OpenSSL 1.0.2+");
}
#endif // defined(__GNUC__) || defined(__GNUG__)

// 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__)
if (callback == NULL) {
SSL_CTX_set_cert_cb(c->ctx, NULL, NULL);
} else {
jclass callback_class = (*e)->GetObjectClass(e, callback);
if (callback_class == NULL) {
tcn_Throw(e, "Unable to retrieve callback class");
return;
}

jmethodID method = (*e)->GetMethodID(e, callback_class, "handle", "(J[B[[B)V");

if (method == NULL) {
tcn_Throw(e, "Unable to retrieve callback method");
return;
}
if (c->certificate_callback != NULL) {
(*e)->DeleteGlobalRef(e, c->certificate_callback);
}
c->certificate_callback = (*e)->NewGlobalRef(e, callback);
c->certificate_callback_method = method;

SSL_CTX_set_cert_cb(c->ctx, certificate_cb, NULL);
}
#endif // OPENSSL_VERSION_NUMBER >= 0x10002000L || defined(__GNUC__) || defined(__GNUG__)

#endif // defined(LIBRESSL_VERSION_NUMBER)
}

static int ssl_servername_cb(SSL *ssl, int *ad, void *arg)
{
JNIEnv *e = NULL;
Expand Down Expand Up @@ -1791,6 +1906,7 @@ static const JNINativeMethod fixed_method_table[] = {

// setCertVerifyCallback -> needs dynamic method table
// setCertRequestedCallback -> needs dynamic method table
// setCertificateCallback -> needs dynamic method table
// setSniHostnameMatcher -> needs dynamic method table

{ TCN_METHOD_TABLE_ENTRY(setSessionIdContext, (J[B)Z, SSLContext) },
Expand All @@ -1803,7 +1919,7 @@ static const JNINativeMethod fixed_method_table[] = {
static const jint fixed_method_table_size = sizeof(fixed_method_table) / sizeof(fixed_method_table[0]);

static jint dynamicMethodsTableSize() {
return fixed_method_table_size + 3;
return fixed_method_table_size + 4;
}

static JNINativeMethod* createDynamicMethodsTable(const char* packagePrefix) {
Expand All @@ -1823,8 +1939,15 @@ static JNINativeMethod* createDynamicMethodsTable(const char* packagePrefix) {
dynamicMethod->fnPtr = (void *) TCN_FUNCTION_NAME(SSLContext, setCertRequestedCallback);
free(dynamicTypeName);

dynamicTypeName = netty_internal_tcnative_util_prepend(packagePrefix, "io/netty/internal/tcnative/SniHostNameMatcher;)V");
dynamicTypeName = netty_internal_tcnative_util_prepend(packagePrefix, "io/netty/internal/tcnative/CertificateCallback;)V");
dynamicMethod = &dynamicMethods[fixed_method_table_size + 2];
dynamicMethod->name = "setCertificateCallback";
dynamicMethod->signature = netty_internal_tcnative_util_prepend("(JL", dynamicTypeName);
dynamicMethod->fnPtr = (void *) TCN_FUNCTION_NAME(SSLContext, setCertificateCallback);
free(dynamicTypeName);

dynamicTypeName = netty_internal_tcnative_util_prepend(packagePrefix, "io/netty/internal/tcnative/SniHostNameMatcher;)V");
dynamicMethod = &dynamicMethods[fixed_method_table_size + 3];
dynamicMethod->name = "setSniHostnameMatcher";
dynamicMethod->signature = netty_internal_tcnative_util_prepend("(JL", dynamicTypeName);
dynamicMethod->fnPtr = (void *) TCN_FUNCTION_NAME(SSLContext, setSniHostnameMatcher);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright 2018 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.internal.tcnative;

/**
* Is called during handshake and hooked into openssl via {@code SSL_CTX_set_cert_cb}.
*
* IMPORTANT: Implementations of this interface should be static as it is stored as a global reference via JNI. This
* means if you use an inner / anonymous class to implement this and also depend on the finalizer of the
* class to free up the SSLContext the finalizer will never run as the object is never GC, due the hard
* reference to the enclosing class. This will most likely result in a memory leak.
*/
public interface CertificateCallback {

/**
* The types contained in the {@code keyTypeBytes} array.
*/
// Extracted from https://github.com/openssl/openssl/blob/master/include/openssl/tls1.h
byte TLS_CT_RSA_SIGN = 1;
byte TLS_CT_DSS_SIGN = 2;
byte TLS_CT_RSA_FIXED_DH = 3;
byte TLS_CT_DSS_FIXED_DH = 4;
byte TLS_CT_ECDSA_SIGN = 64;
byte TLS_CT_RSA_FIXED_ECDH = 65;
byte TLS_CT_ECDSA_FIXED_ECDH = 66;

/**
* Called during cert selection. If a certificate chain / key should be used
* {@link SSL#setKeyMaterial(long, long, long)} must be called from this callback after
* all preparations / validations were completed.
*
* @param ssl the SSL instance
* @param keyTypeBytes an array of the key types on client-mode or {@code null} on server-mode.
* @param asn1DerEncodedPrincipals the principals or {@code null}.
*
*/
void handle(long ssl, byte[] keyTypeBytes, byte[][] asn1DerEncodedPrincipals) throws Exception;
}

0 comments on commit e47724e

Please sign in to comment.