From 9d3425f064ab0521ae588667ec29b2270a0fcf9c Mon Sep 17 00:00:00 2001 From: Andrey Pangin Date: Mon, 17 Aug 2020 00:43:31 +0300 Subject: [PATCH] SSL improvements: rdrand, sslPeerCertificateChain, autoupdate certificates --- src/one/nio/net/NativeSslContext.java | 7 ++- src/one/nio/net/NativeSslSocket.java | 13 +++-- src/one/nio/net/SslConfig.java | 1 + src/one/nio/net/SslContext.java | 39 ++++++++++++++ src/one/nio/net/SslOption.java | 13 ++--- src/one/nio/net/native/ssl.c | 76 +++++++++++++++++++++++---- src/one/nio/net/native/sslcompat.c | 18 +++++++ src/one/nio/net/native/sslcompat.h | 11 ++++ 8 files changed, 156 insertions(+), 22 deletions(-) diff --git a/src/one/nio/net/NativeSslContext.java b/src/one/nio/net/NativeSslContext.java index 7635cfb..74d7ae1 100755 --- a/src/one/nio/net/NativeSslContext.java +++ b/src/one/nio/net/NativeSslContext.java @@ -121,6 +121,9 @@ public void setProtocols(String protocols) { setOptions(all - enabled); } + @Override + public native void setRdrand(boolean rdrand) throws SSLException; + @Override public native void setCiphers(String ciphers) throws SSLException; @@ -136,9 +139,9 @@ public void setProtocols(String protocols) { @Override public native void setCA(String caFile) throws SSLException; - @Override + @Override public native void setVerify(int verifyMode) throws SSLException; - + @Override public native void setTicketKeys(byte[] keys) throws SSLException; diff --git a/src/one/nio/net/NativeSslSocket.java b/src/one/nio/net/NativeSslSocket.java index 3d94ae4..c1da41e 100755 --- a/src/one/nio/net/NativeSslSocket.java +++ b/src/one/nio/net/NativeSslSocket.java @@ -66,16 +66,18 @@ public Object getSslOption(SslOption option) { case 1: return sslPeerCertificate(); case 2: - return sslCertName(0); + return sslPeerCertificateChain(); case 3: - return sslCertName(1); + return sslCertName(0); case 4: - return sslVerifyResult(); + return sslCertName(1); case 5: - return sslSessionReused(); + return sslVerifyResult(); case 6: - return sslSessionTicket(); + return sslSessionReused(); case 7: + return sslSessionTicket(); + case 8: return sslCurrentCipher(); } return null; @@ -108,6 +110,7 @@ public long sendFile(RandomAccessFile file, long offset, long count) throws IOEx public synchronized native void readFully(byte[] data, int offset, int count) throws IOException; private synchronized native byte[] sslPeerCertificate(); + private synchronized native Object[] sslPeerCertificateChain(); private synchronized native String sslCertName(int which); private synchronized native String sslVerifyResult(); diff --git a/src/one/nio/net/SslConfig.java b/src/one/nio/net/SslConfig.java index a30a719..4226319 100644 --- a/src/one/nio/net/SslConfig.java +++ b/src/one/nio/net/SslConfig.java @@ -30,6 +30,7 @@ public class SslConfig { static final long DEFAULT_REFRESH_INTERVAL = 300_000; public boolean debug; + public boolean rdrand; public String protocols; public String ciphers; public String[] certFile; diff --git a/src/one/nio/net/SslContext.java b/src/one/nio/net/SslContext.java index 5556ff4..504c696 100755 --- a/src/one/nio/net/SslContext.java +++ b/src/one/nio/net/SslContext.java @@ -42,6 +42,7 @@ public abstract class SslContext { public static final int VERIFY_ONCE = 4; // do not verify certs on renegotiations private final AtomicLong nextRefresh = new AtomicLong(); + private long lastCertUpdate; private long lastTicketsUpdate; private long lastOCSPUpdate; @@ -72,6 +73,10 @@ public synchronized SslContext configure(SslConfig config) throws IOException { setDebug(config.debug); + if (config.rdrand != currentConfig.rdrand) { + setRdrand(config.rdrand); + } + if (changed(config.protocols, currentConfig.protocols)) { setProtocols(config.protocols); } @@ -83,9 +88,12 @@ public synchronized SslContext configure(SslConfig config) throws IOException { } if (changed(config.certFile, currentConfig.certFile)) { + long lastCertUpdate = 0; for (String certFile : config.certFile) { setCertificate(certFile); + lastCertUpdate = Math.max(lastCertUpdate, new File(certFile).lastModified()); } + this.lastCertUpdate = lastCertUpdate; } if (changed(config.privateKeyFile, currentConfig.privateKeyFile)) { @@ -171,6 +179,28 @@ private void inherit(SslConfig parent, SslConfig[] children) { } } + void updateCertificates(String[] certFiles, String[] privateKeyFiles) throws IOException { + long maxLastModified = 0; + for (String certFile : certFiles) { + long lastModified = new File(certFile).lastModified(); + if (lastModified > lastCertUpdate) { + setCertificate(certFile); + maxLastModified = Math.max(maxLastModified, lastModified); + } + } + + if (maxLastModified == 0) { + return; + } + + for (String privateKeyFile : privateKeyFiles) { + setPrivateKey(privateKeyFile); + } + + log.info("Certificates updated: " + new Date(maxLastModified)); + lastCertUpdate = maxLastModified; + } + void updateTicketKeys(String ticketDir, boolean force) throws IOException { File[] files = new File(ticketDir).listFiles(); if (files == null || files.length == 0) { @@ -223,6 +253,14 @@ void refresh() { return; } + if (currentConfig.certFile != null && currentConfig.privateKeyFile != null) { + try { + updateCertificates(currentConfig.certFile, currentConfig.privateKeyFile); + } catch (IOException e) { + log.error("Failed to update certificates", e); + } + } + if (currentConfig.ticketDir != null) { try { updateTicketKeys(currentConfig.ticketDir, false); @@ -243,6 +281,7 @@ void refresh() { public abstract void setDebug(boolean debug); public abstract boolean getDebug(); + public abstract void setRdrand(boolean rdrand) throws SSLException; public abstract void setProtocols(String protocols) throws SSLException; public abstract void setCiphers(String ciphers) throws SSLException; public abstract void setCertificate(String certFile) throws SSLException; diff --git a/src/one/nio/net/SslOption.java b/src/one/nio/net/SslOption.java index ee144e2..e374637 100644 --- a/src/one/nio/net/SslOption.java +++ b/src/one/nio/net/SslOption.java @@ -18,14 +18,15 @@ public class SslOption { public static final SslOption PEER_CERTIFICATE = new SslOption<>(1, byte[].class); - public static final SslOption PEER_SUBJECT = new SslOption<>(2, String.class); - public static final SslOption PEER_ISSUER = new SslOption<>(3, String.class); - public static final SslOption VERIFY_RESULT = new SslOption<>(4, String.class); + public static final SslOption PEER_CERTIFICATE_CHAIN = new SslOption<>(2, Object[].class); + public static final SslOption PEER_SUBJECT = new SslOption<>(3, String.class); + public static final SslOption PEER_ISSUER = new SslOption<>(4, String.class); + public static final SslOption VERIFY_RESULT = new SslOption<>(5, String.class); - public static final SslOption SESSION_REUSED = new SslOption<>(5, Boolean.class); - public static final SslOption SESSION_TICKET = new SslOption<>(6, Integer.class); + public static final SslOption SESSION_REUSED = new SslOption<>(6, Boolean.class); + public static final SslOption SESSION_TICKET = new SslOption<>(7, Integer.class); - public static final SslOption CURRENT_CIPHER = new SslOption<>(7, String.class); + public static final SslOption CURRENT_CIPHER = new SslOption<>(8, String.class); final int id; final Class type; diff --git a/src/one/nio/net/native/ssl.c b/src/one/nio/net/native/ssl.c index 4eed6c9..0b5261f 100755 --- a/src/one/nio/net/native/ssl.c +++ b/src/one/nio/net/native/ssl.c @@ -16,8 +16,10 @@ #include #include +#include #include #include +#include #include #include #include @@ -427,6 +429,22 @@ static void ssl_info_callback(const SSL* ssl, int cb, int ret) { } } +static jbyteArray X509_cert_to_jbyteArray(JNIEnv* env, X509* cert) { + jbyteArray result = NULL; + + unsigned char* buf = NULL; + int len = i2d_X509(cert, &buf); + if (buf != NULL) { + result = (*env)->NewByteArray(env, len); + if (result != NULL) { + (*env)->SetByteArrayRegion(env, result, 0, len, (jbyte*)buf); + } + OPENSSL_free(buf); + } + + return result; +} + JNIEXPORT void JNICALL Java_one_nio_net_NativeSslContext_init(JNIEnv* env, jclass cls) { if (dlopen("libssl.so", RTLD_LAZY | RTLD_GLOBAL) == NULL && @@ -513,6 +531,24 @@ Java_one_nio_net_NativeSslContext_clearOptions(JNIEnv* env, jobject self, jint o SSL_CTX_clear_options(ctx, options); } +JNIEXPORT void JNICALL +Java_one_nio_net_NativeSslContext_setRdrand(JNIEnv* env, jobject self, jboolean rdrand) { + if (rdrand) { + OPENSSL_init_crypto(/* OPENSSL_INIT_ENGINE_RDRAND */ 0x200L, NULL); + ENGINE* e = ENGINE_by_id("rdrand"); + if (e == NULL || !ENGINE_init(e) || !ENGINE_set_default_RAND(e)) { + throw_ssl_exception(env); + } + RAND_set_rand_method(ENGINE_get_RAND(e)); + } else { + ENGINE* e = ENGINE_by_id("rdrand"); + if (e != NULL) { + ENGINE_unregister_RAND(e); + } + ERR_clear_error(); + } +} + JNIEXPORT void JNICALL Java_one_nio_net_NativeSslContext_setCiphers(JNIEnv* env, jobject self, jstring ciphers) { SSL_CTX* ctx = (SSL_CTX*)(intptr_t)(*env)->GetLongField(env, self, f_ctx); @@ -976,19 +1012,41 @@ Java_one_nio_net_NativeSslSocket_sslPeerCertificate(JNIEnv* env, jobject self) { return NULL; } - jbyteArray result = NULL; + jbyteArray result = X509_cert_to_jbyteArray(env, cert); - unsigned char* buf = NULL; - int len = i2d_X509(cert, &buf); - if (buf != NULL) { - result = (*env)->NewByteArray(env, len); - if (result != NULL) { - (*env)->SetByteArrayRegion(env, result, 0, len, (jbyte*)buf); + X509_free(cert); + return result; +} + +JNIEXPORT jobjectArray JNICALL +Java_one_nio_net_NativeSslSocket_sslPeerCertificateChain(JNIEnv* env, jobject self) { + SSL* ssl = (SSL*)(intptr_t) (*env)->GetLongField(env, self, f_ssl); + if (ssl == NULL) { + return NULL; + } + + STACK_OF(X509)* chain = SSL_get_peer_cert_chain(ssl); + if (chain == NULL) { + return NULL; + } + + int len = OPENSSL_sk_num((OPENSSL_STACK*)chain); + jclass jbyteArrayClass = (*env)->FindClass(env, "[B"); + + jobjectArray result = (*env)->NewObjectArray(env, len, jbyteArrayClass, NULL); + if (result != NULL) { + int i; + for (i = 0; i < len; i++) { + X509* cert = (X509*)OPENSSL_sk_value((OPENSSL_STACK*)chain, i); + if (cert != NULL) { + jbyteArray element = X509_cert_to_jbyteArray(env, cert); + if (element != NULL) { + (*env)->SetObjectArrayElement(env, result, i, element); + } + } } - OPENSSL_free(buf); } - X509_free(cert); return result; } diff --git a/src/one/nio/net/native/sslcompat.c b/src/one/nio/net/native/sslcompat.c index 0c9abeb..8a0fc72 100644 --- a/src/one/nio/net/native/sslcompat.c +++ b/src/one/nio/net/native/sslcompat.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include "sslcompat.h" @@ -94,6 +95,13 @@ int OPENSSL_init_ssl(unsigned long long opts, const void* settings) { return 1; } +// ENGINE_load_rdrand was replaced with OPENSSL_init_crypto in OpenSSL 1.1.0 + +int OPENSSL_init_crypto(unsigned long long opts, const void* settings) { + ENGINE_load_rdrand(); + return 1; +} + // SSLv23_method() was renamed to TLS_method() in OpenSSL 1.1.0 @@ -137,4 +145,14 @@ void SSL_CTX_set_alpn_select_cb(SSL_CTX* ctx, fprintf(stderr, "[WARNING] symbol not found: SSL_CTX_set_alpn_select_cb. ALPN disabled\n"); } +// The following symbols was renamed in OpenSSL 1.1.0, see openssl/stack.h for details + +int OPENSSL_sk_num(const OPENSSL_STACK* st) { + return sk_num(st); +} + +void* OPENSSL_sk_value(const OPENSSL_STACK* st, int i) { + return sk_value(st, i); +} + #endif // OPENSSL_VERSION_NUMBER < 0x10100000L diff --git a/src/one/nio/net/native/sslcompat.h b/src/one/nio/net/native/sslcompat.h index 18853a5..15e546d 100644 --- a/src/one/nio/net/native/sslcompat.h +++ b/src/one/nio/net/native/sslcompat.h @@ -22,6 +22,7 @@ #define WEAK __attribute__((weak)) #undef OPENSSL_init_ssl +#undef OPENSSL_init_crypto #undef TLS_method #undef DH_set0_pqg #undef SSL_in_init @@ -29,7 +30,14 @@ #undef SSL_CTX_clear_options #undef SSL_CTX_set_alpn_select_cb +#undef OPENSSL_STACK +#undef OPENSSL_sk_num +#undef OPENSSL_sk_value + +typedef struct stack_st OPENSSL_STACK; + int OPENSSL_init_ssl(unsigned long long opts, const void* settings) WEAK; +int OPENSSL_init_crypto(unsigned long long opts, const void* settings) WEAK; const SSL_METHOD* TLS_method() WEAK; int DH_set0_pqg(DH* dh, BIGNUM* p, BIGNUM* q, BIGNUM* g) WEAK; int SSL_in_init(SSL* ssl) WEAK; @@ -39,4 +47,7 @@ void SSL_CTX_set_alpn_select_cb(SSL_CTX* ctx, int (*cb)(SSL* ssl, const unsigned char** out, unsigned char* outlen, const unsigned char* in, unsigned int inlen, void* arg), void* arg) WEAK; +int OPENSSL_sk_num(const OPENSSL_STACK* st) WEAK; +void* OPENSSL_sk_value(const OPENSSL_STACK* st, int i) WEAK; + #endif // OPENSSL_VERSION_NUMBER < 0x10100000L