diff --git a/src/one/nio/net/JavaDatagramSocket.java b/src/one/nio/net/JavaDatagramSocket.java index dafa6e6..4e37af6 100644 --- a/src/one/nio/net/JavaDatagramSocket.java +++ b/src/one/nio/net/JavaDatagramSocket.java @@ -335,6 +335,11 @@ public SslContext getSslContext() { return null; } + @Override + public T getSslOption(SslOption option) { + return null; + } + @Override public SelectableChannel getSelectableChannel() { return ch; diff --git a/src/one/nio/net/JavaServerSocket.java b/src/one/nio/net/JavaServerSocket.java index 90d8ba8..141114d 100755 --- a/src/one/nio/net/JavaServerSocket.java +++ b/src/one/nio/net/JavaServerSocket.java @@ -303,6 +303,11 @@ public SslContext getSslContext() { return null; } + @Override + public T getSslOption(SslOption option) { + return null; + } + @Override public SelectableChannel getSelectableChannel() { return ch; diff --git a/src/one/nio/net/JavaSocket.java b/src/one/nio/net/JavaSocket.java index e49ee2b..c9912db 100755 --- a/src/one/nio/net/JavaSocket.java +++ b/src/one/nio/net/JavaSocket.java @@ -353,6 +353,11 @@ public SslContext getSslContext() { return null; } + @Override + public T getSslOption(SslOption option) { + return null; + } + @Override public SelectableChannel getSelectableChannel() { return ch; diff --git a/src/one/nio/net/NativeSocket.java b/src/one/nio/net/NativeSocket.java index 80d1d64..33c35bd 100755 --- a/src/one/nio/net/NativeSocket.java +++ b/src/one/nio/net/NativeSocket.java @@ -103,6 +103,11 @@ public SslContext getSslContext() { return null; } + @Override + public T getSslOption(SslOption option) { + return null; + } + @Override public final void connect(InetAddress address, int port) throws IOException { connect0(address.getAddress(), port); diff --git a/src/one/nio/net/NativeSslContext.java b/src/one/nio/net/NativeSslContext.java index e3a712e..7635cfb 100755 --- a/src/one/nio/net/NativeSslContext.java +++ b/src/one/nio/net/NativeSslContext.java @@ -110,10 +110,13 @@ public void setProtocols(String protocols) { case "tlsv1.2": enabled |= 0x08000000; break; + case "tlsv1.3": + enabled |= 0x20000000; + break; } } - int all = 0x00020000 + 0x01000000 + 0x02000000 + 0x04000000 + 0x08000000 + 0x10000000; + int all = 0x00020000 + 0x01000000 + 0x02000000 + 0x04000000 + 0x08000000 + 0x10000000 + 0x20000000; clearOptions(enabled); setOptions(all - enabled); } diff --git a/src/one/nio/net/NativeSslSocket.java b/src/one/nio/net/NativeSslSocket.java index 0c0fdb4..5ac85ff 100755 --- a/src/one/nio/net/NativeSslSocket.java +++ b/src/one/nio/net/NativeSslSocket.java @@ -60,11 +60,25 @@ public SslContext getSslContext() { } @Override - public byte[] getOption(int level, int option) { - if (level == SOL_SSL) { - return sslGetOption(option); + @SuppressWarnings("unchecked") + public Object getSslOption(SslOption option) { + switch (option.id) { + case 1: + return sslPeerCertificate(); + case 2: + return sslCertName(0); + case 3: + return sslCertName(1); + case 4: + return sslVerifyResult(); + case 5: + return sslSessionReused(); + case 6: + return sslSessionTicket(); + case 7: + return sslCurrentCipher(); } - return super.getOption(level, option); + return null; } @Override @@ -90,7 +104,14 @@ public long sendFile(RandomAccessFile file, long offset, long count) throws IOEx @Override public synchronized native void readFully(byte[] data, int offset, int count) throws IOException; - private synchronized native byte[] sslGetOption(int option); + private synchronized native byte[] sslPeerCertificate(); + private synchronized native String sslCertName(int which); + private synchronized native String sslVerifyResult(); + + private synchronized native boolean sslSessionReused(); + private synchronized native int sslSessionTicket(); + + private synchronized native String sslCurrentCipher(); static native long sslNew(int fd, long ctx, boolean serverMode) throws IOException; static native void sslFree(long ssl); diff --git a/src/one/nio/net/Socket.java b/src/one/nio/net/Socket.java index e8381b1..e65e678 100755 --- a/src/one/nio/net/Socket.java +++ b/src/one/nio/net/Socket.java @@ -33,16 +33,6 @@ public abstract class Socket implements ByteChannel { public static final int SOL_IPV6 = 41; public static final int SOL_TCP = 6; public static final int SOL_UDP = 17; - public static final int SOL_SSL = 1024; - - // Options to use with SOL_SSL - - // Session ID as a byte array - public static final int SSL_SESSION = 1; - // 0 = new SSL session; 1 = reused SSL session - public static final int SSL_SESSION_REUSED = 2; - // 0 = no ticket; 1 = reused ticket; 2 = reused older ticket; 3 = newly issued ticket - public static final int SSL_SESSION_TICKET = 3; // Flags for readRaw / writeRaw public static final int MSG_OOB = 0x01; @@ -54,7 +44,6 @@ public abstract class Socket implements ByteChannel { public static final int MSG_MORE = 0x8000; // Options for setTos - public static final int IPTOS_MINCOST = 0x02; public static final int IPTOS_RELIABILITY = 0x04; public static final int IPTOS_THROUGHPUT = 0x08; @@ -103,6 +92,7 @@ public abstract class Socket implements ByteChannel { public abstract Socket sslWrap(SslContext context) throws IOException; public abstract Socket sslUnwrap(); public abstract SslContext getSslContext(); + public abstract T getSslOption(SslOption option); public Socket acceptNonBlocking() throws IOException { Socket s = accept(); diff --git a/src/one/nio/net/SslConfig.java b/src/one/nio/net/SslConfig.java index be21762..a30a719 100644 --- a/src/one/nio/net/SslConfig.java +++ b/src/one/nio/net/SslConfig.java @@ -23,8 +23,8 @@ @Config public class SslConfig { - // Intermediate compatibility ciphersuite according to https://wiki.mozilla.org/Security/Server_Side_TLS - static final String DEFAULT_CIPHERS = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS"; + // Conservative ciphersuite according to https://wiki.mozilla.org/Security/Server_Side_TLS + static final String DEFAULT_CIPHERS = "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA"; static final int DEFAULT_CACHE_SIZE = 262144; static final long DEFAULT_TIMEOUT_SEC = 300; static final long DEFAULT_REFRESH_INTERVAL = 300_000; @@ -58,6 +58,8 @@ public static SslConfig from(Properties props) { config.ciphers = props.getProperty("one.nio.ssl.ciphers"); config.certFile = toArray(props.getProperty("one.nio.ssl.certFile")); config.privateKeyFile = toArray(props.getProperty("one.nio.ssl.privateKeyFile")); + config.passphrase = props.getProperty("one.nio.ssl.passphrase"); + config.caFile = props.getProperty("one.nio.ssl.caFile"); config.ticketKeyFile = props.getProperty("one.nio.ssl.ticketKeyFile"); return config; } diff --git a/src/one/nio/net/SslOption.java b/src/one/nio/net/SslOption.java new file mode 100644 index 0000000..ee144e2 --- /dev/null +++ b/src/one/nio/net/SslOption.java @@ -0,0 +1,47 @@ +/* + * Copyright 2019 Odnoklassniki Ltd, Mail.Ru Group + * + * Licensed 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 one.nio.net; + +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 SESSION_REUSED = new SslOption<>(5, Boolean.class); + public static final SslOption SESSION_TICKET = new SslOption<>(6, Integer.class); + + public static final SslOption CURRENT_CIPHER = new SslOption<>(7, String.class); + + final int id; + final Class type; + + private SslOption(int id, Class type) { + this.id = id; + this.type = type; + } + + @Override + public int hashCode() { + return id; + } + + @Override + public String toString() { + return "SslOption(" + id + ")"; + } +} diff --git a/src/one/nio/net/native/ssl.c b/src/one/nio/net/native/ssl.c index 73a1fec..091965b 100755 --- a/src/one/nio/net/native/ssl.c +++ b/src/one/nio/net/native/ssl.c @@ -15,12 +15,14 @@ */ #include +#include #include #include #include #include #include #include +#include #include #include #include @@ -40,13 +42,6 @@ #define MAX_COUNTERS 32 -// Constants from Socket.java -enum SOL_SSL { - SOL_SSL_SESSION = 1, - SOL_SSL_SESSION_REUSED = 2, - SOL_SSL_SESSION_TICKET = 3 -}; - enum SSLFlags { SF_SERVER = 1, SF_HANDSHAKED = 2, @@ -166,14 +161,6 @@ static int check_ssl_error(JNIEnv* env, SSL* ssl, int ret) { } } -static jbyteArray int_to_bytes(JNIEnv* env, int value) { - jbyteArray result = (*env)->NewByteArray(env, sizeof(value)); - if (result != NULL) { - (*env)->SetByteArrayRegion(env, result, 0, sizeof(value), (jbyte*)&value); - } - return result; -} - static char* ssl_get_peer_ip(const SSL* ssl, char* buf, size_t len) { int fd = SSL_get_fd(ssl); if (fd == -1) { @@ -319,12 +306,13 @@ static int ticket_key_callback(SSL* ssl, unsigned char key_name[16], unsigned ch if (ticket == NULL) { // No ticket keys set } else if (new_session) { - RAND_pseudo_bytes(iv, 16); - memcpy(key_name, ticket->name, 16); - EVP_EncryptInit_ex(evp_ctx, EVP_aes_128_cbc(), NULL, ticket->aes_key, iv); - HMAC_Init_ex(hmac_ctx, ticket->hmac_key, 16, EVP_sha256(), NULL); - SSL_set_app_data(ssl, (char*)(SF_SERVER | SF_NEW_TICKET)); - result = 1; + if (RAND_bytes(iv, EVP_MAX_IV_LENGTH)) { + memcpy(key_name, ticket->name, 16); + EVP_EncryptInit_ex(evp_ctx, EVP_aes_128_cbc(), NULL, ticket->aes_key, iv); + HMAC_Init_ex(hmac_ctx, ticket->hmac_key, 16, EVP_sha256(), NULL); + SSL_set_app_data(ssl, (char*)(SF_SERVER | SF_NEW_TICKET)); + result = 1; + } } else { unsigned int i; for (i = 0; i < tickets->len; i++, ticket++) { @@ -819,7 +807,9 @@ Java_one_nio_net_NativeSslContext_getSessionCounters(JNIEnv* env, jobject self, } jlongArray values = (*env)->NewLongArray(env, MAX_COUNTERS); - (*env)->SetLongArrayRegion(env, values, 0, MAX_COUNTERS, raw_values); + if (values != NULL) { + (*env)->SetLongArrayRegion(env, values, 0, MAX_COUNTERS, raw_values); + } return values; } @@ -850,23 +840,6 @@ Java_one_nio_net_NativeSslSocket_sslFree(JNIEnv* env, jclass cls, jlong sslptr) SSL_free(ssl); } -JNIEXPORT jbyteArray JNICALL -Java_one_nio_net_NativeSslSocket_sslGetOption(JNIEnv* env, jobject self, jint option) { - SSL* ssl = (SSL*)(intptr_t) (*env)->GetLongField(env, self, f_ssl); - if (ssl == NULL) { - return NULL; - } - - switch (option) { - case SOL_SSL_SESSION_REUSED: - return int_to_bytes(env, SSL_session_reused(ssl)); - case SOL_SSL_SESSION_TICKET: - return int_to_bytes(env, ((intptr_t)SSL_get_app_data(ssl) & SF_NEW_TICKET) >> 2); - default: - return NULL; - } -} - JNIEXPORT jint JNICALL Java_one_nio_net_NativeSslSocket_writeRaw(JNIEnv* env, jobject self, jlong buf, jint count, jint flags) { SSL* ssl = (SSL*)(intptr_t) (*env)->GetLongField(env, self, f_ssl); @@ -977,3 +950,107 @@ Java_one_nio_net_NativeSslSocket_readFully(JNIEnv* env, jobject self, jbyteArray } } } + +JNIEXPORT jbyteArray JNICALL +Java_one_nio_net_NativeSslSocket_sslPeerCertificate(JNIEnv* env, jobject self) { + SSL* ssl = (SSL*)(intptr_t) (*env)->GetLongField(env, self, f_ssl); + if (ssl == NULL) { + return NULL; + } + + X509* cert = SSL_get_peer_certificate(ssl); + if (cert == NULL) { + return NULL; + } + + 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); + } + + X509_free(cert); + return result; +} + +JNIEXPORT jstring JNICALL +Java_one_nio_net_NativeSslSocket_sslCertName(JNIEnv* env, jobject self, jint which) { + SSL* ssl = (SSL*)(intptr_t) (*env)->GetLongField(env, self, f_ssl); + if (ssl == NULL) { + return NULL; + } + + X509* cert = SSL_get_peer_certificate(ssl); + if (cert == NULL) { + return NULL; + } + + jstring result = NULL; + + X509_NAME* name = which == 0 ? X509_get_subject_name(cert) : X509_get_issuer_name(cert); + if (name != NULL) { + BIO* mem = BIO_new(BIO_s_mem()); + if (mem != NULL) { + X509_NAME_print_ex(mem, name, 0, XN_FLAG_SEP_CPLUS_SPC | XN_FLAG_FN_SN | + (ASN1_STRFLGS_RFC2253 & ~ASN1_STRFLGS_ESC_MSB)); + char zero = 0; + BIO_write(mem, &zero, 1); + + char* data; + long len = BIO_get_mem_data(mem, &data); + if (len > 1) { + result = (*env)->NewStringUTF(env, data); + } + + BIO_free(mem); + } + } + + X509_free(cert); + return result; +} + +JNIEXPORT jstring JNICALL +Java_one_nio_net_NativeSslSocket_sslVerifyResult(JNIEnv* env, jobject self) { + SSL* ssl = (SSL*)(intptr_t) (*env)->GetLongField(env, self, f_ssl); + if (ssl == NULL) { + return NULL; + } + + long err = SSL_get_verify_result(ssl); + if (err == 0) { + return NULL; + } + + const char* str = X509_verify_cert_error_string(err); + return str == NULL ? NULL : (*env)->NewStringUTF(env, str); +} + +JNIEXPORT jboolean JNICALL +Java_one_nio_net_NativeSslSocket_sslSessionReused(JNIEnv* env, jobject self) { + SSL* ssl = (SSL*)(intptr_t) (*env)->GetLongField(env, self, f_ssl); + return ssl != NULL && SSL_session_reused(ssl) ? JNI_TRUE : JNI_FALSE; +} + +JNIEXPORT jint JNICALL +Java_one_nio_net_NativeSslSocket_sslSessionTicket(JNIEnv* env, jobject self) { + SSL* ssl = (SSL*)(intptr_t) (*env)->GetLongField(env, self, f_ssl); + return ssl == NULL ? 0 : ((intptr_t)SSL_get_app_data(ssl) & SF_NEW_TICKET) >> 2; +} + +JNIEXPORT jstring JNICALL +Java_one_nio_net_NativeSslSocket_sslCurrentCipher(JNIEnv* env, jobject self) { + SSL* ssl = (SSL*)(intptr_t) (*env)->GetLongField(env, self, f_ssl); + if (ssl == NULL) { + return NULL; + } + + const char* name = SSL_CIPHER_get_name(SSL_get_current_cipher(ssl)); + return name == NULL ? NULL : (*env)->NewStringUTF(env, name); +} diff --git a/src/one/nio/util/JavaInternals.java b/src/one/nio/util/JavaInternals.java index 8e22c07..27ff6a8 100755 --- a/src/one/nio/util/JavaInternals.java +++ b/src/one/nio/util/JavaInternals.java @@ -152,8 +152,14 @@ public static void setStaticField(Class cls, String name, Object value) { // Useful for patching final fields public static void setObjectField(Object obj, String name, Object value) { + Class cls = obj.getClass(); + setObjectField(obj, cls, name, value); + } + + // Useful for patching final fields + public static void setObjectField(Object obj, Class cls, String name, Object value) { try { - Field field = obj.getClass().getDeclaredField(name); + Field field = cls.getDeclaredField(name); if (Modifier.isStatic(field.getModifiers())) { throw new IllegalArgumentException("Object field expected"); } diff --git a/test/one/nio/http/HttpServerTest.java b/test/one/nio/http/HttpServerTest.java index 6f6368c..59db83b 100755 --- a/test/one/nio/http/HttpServerTest.java +++ b/test/one/nio/http/HttpServerTest.java @@ -17,6 +17,7 @@ package one.nio.http; import one.nio.net.Socket; +import one.nio.net.SslOption; import one.nio.util.Utf8; import java.io.IOException; @@ -60,22 +61,36 @@ public Response handleParam(@Param("i") int i, return response; } + @Path("/cert") + public Response handleCert(HttpSession session) { + System.out.println("handleCert"); + + Socket socket = session.socket(); + byte[] cert = socket.getSslOption(SslOption.PEER_CERTIFICATE); + String subject = socket.getSslOption(SslOption.PEER_SUBJECT); + String issuer = socket.getSslOption(SslOption.PEER_ISSUER); + + return Response.ok("Client certificate: " + (cert == null ? "none" : cert.length + " bytes") + "\n" + + "Subject: " + subject + "\n" + + "Issuer: " + issuer + "\n"); + } + @Path("/session") public Response handleSession(HttpSession session) { Socket socket = session.socket(); - byte[] reused = socket.getOption(Socket.SOL_SSL, Socket.SSL_SESSION_REUSED); - byte[] ticket = socket.getOption(Socket.SOL_SSL, Socket.SSL_SESSION_TICKET); + Boolean reused = socket.getSslOption(SslOption.SESSION_REUSED); + Integer ticket = socket.getSslOption(SslOption.SESSION_TICKET); StringBuilder result = new StringBuilder("SSL session flags:"); - if (reused != null && reused.length > 0 && reused[0] == 1) { + if (reused != null && reused) { result.append(" SESSION_REUSED"); } - if (ticket != null && ticket.length > 0) { - if (ticket[0] == 1) { + if (ticket != null) { + if (ticket == 1) { result.append(" TICKET_REUSED"); - } else if (ticket[0] == 2) { + } else if (ticket == 2) { result.append(" OLD_TICKET_REUSED"); - } else if (ticket[0] == 3) { + } else if (ticket == 3) { result.append(" NEW_TICKET"); } }