From c9968c0654a4f6fdd609cb541eec4eb59a4213a3 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Fri, 17 Sep 2021 15:43:41 +0200 Subject: [PATCH 1/2] wire up ECH functions from boringssl --- .../jni/main/cpp/conscrypt/native_crypto.cc | 104 +++++++++++ .../conscrypt/AbstractConscryptEngine.java | 10 ++ .../conscrypt/AbstractConscryptSocket.java | 8 + .../main/java/org/conscrypt/Conscrypt.java | 45 +++++ .../java/org/conscrypt/ConscryptEngine.java | 25 +++ .../org/conscrypt/ConscryptEngineSocket.java | 20 +++ .../ConscryptFileDescriptorSocket.java | 20 +++ .../org/conscrypt/Java8EngineWrapper.java | 25 +++ .../main/java/org/conscrypt/NativeCrypto.java | 11 ++ .../main/java/org/conscrypt/NativeSsl.java | 9 + .../java/org/conscrypt/SSLParametersImpl.java | 30 ++++ .../java/org/conscrypt/NativeCryptoTest.java | 168 ++++++++++++++++++ .../resources/boringssl-ech-config-list.bin | Bin 0 -> 68 bytes .../resources/boringssl-ech-private-key.bin | 1 + .../resources/boringssl-server-ech-config.bin | Bin 0 -> 66 bytes 15 files changed, 476 insertions(+) create mode 100644 openjdk/src/test/resources/boringssl-ech-config-list.bin create mode 100644 openjdk/src/test/resources/boringssl-ech-private-key.bin create mode 100644 openjdk/src/test/resources/boringssl-server-ech-config.bin diff --git a/common/src/jni/main/cpp/conscrypt/native_crypto.cc b/common/src/jni/main/cpp/conscrypt/native_crypto.cc index 57ed55d95..7cbe5b4be 100644 --- a/common/src/jni/main/cpp/conscrypt/native_crypto.cc +++ b/common/src/jni/main/cpp/conscrypt/native_crypto.cc @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -123,6 +124,15 @@ static SSL_CIPHER* to_SSL_CIPHER(JNIEnv* env, jlong ssl_cipher_address, bool thr return ssl_cipher; } +static SSL_ECH_KEYS* to_SSL_ECH_KEYS(JNIEnv* env, jlong ssl_ech_keys_address, bool throwIfNull) { + SSL_ECH_KEYS* ssl_ech_keys = reinterpret_cast(static_cast(ssl_ech_keys_address)); + if ((ssl_ech_keys == nullptr) && throwIfNull) { + JNI_TRACE("ssl_ech_keys == null"); + conscrypt::jniutil::throwNullPointerException(env, "ssl_ech_keys == null"); + } + return ssl_ech_keys; +} + template static T* fromContextObject(JNIEnv* env, jobject contextObject) { if (contextObject == nullptr) { @@ -10429,6 +10439,96 @@ static jlong NativeCrypto_SSL_get1_session(JNIEnv* env, jclass, jlong ssl_addres return reinterpret_cast(SSL_get1_session(ssl)); } +static void NativeCrypto_SSL_set_enable_ech_grease(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder, + jboolean enable) { + CHECK_ERROR_QUEUE_ON_RETURN; + SSL* ssl = to_SSL(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_enable_ech_grease(%d)", ssl, enable); + if (ssl == nullptr) { + return; + } + SSL_set_enable_ech_grease(ssl, enable ? 1 : 0); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set_enable_ech_grease(%d) => success", ssl, enable); +} + +static jboolean NativeCrypto_SSL_set1_ech_config_list(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder, + jbyteArray configJavaBytes) { + CHECK_ERROR_QUEUE_ON_RETURN; + SSL* ssl = to_SSL(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set1_ech_config_list(%p)", ssl, configJavaBytes); + if (ssl == nullptr) { + return JNI_FALSE; + } + ScopedByteArrayRO configBytes(env, configJavaBytes); + if (configBytes.get() == nullptr) { + JNI_TRACE("NativeCrypto_SSL_set1_ech_config_list => threw exception:" + " could not read config bytes"); + return JNI_FALSE; + } + const uint8_t* bs = reinterpret_cast(configBytes.get()); + int ret = SSL_set1_ech_config_list(ssl, reinterpret_cast(configBytes.get()), + configBytes.size()); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set1_ech_config_list(%p) => %d", ssl, configJavaBytes, ret); + return !!ret; +} + +/** + * public static native long SSL_ech_accepted(long ssl); + */ +static jboolean NativeCrypto_SSL_ech_accepted(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder) { + JNI_TRACE("NativeCrypto_SSL_ech_accepted"); + CHECK_ERROR_QUEUE_ON_RETURN; + SSL* ssl = to_SSL(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_ech_accepted", ssl); + if (ssl == nullptr) { + return 0; + } + jboolean accepted = SSL_ech_accepted(ssl); + JNI_TRACE("ssl=%p NativeCrypto_SSL_ech_accepted => %d", ssl, accepted); + return accepted; +} + +static jboolean NativeCrypto_SSL_CTX_ech_enable_server(JNIEnv* env, jclass, jlong ssl_ctx_address, + CONSCRYPT_UNUSED jobject holder, + jbyteArray keyJavaBytes, + jbyteArray configJavaBytes) { + CHECK_ERROR_QUEUE_ON_RETURN; + SSL_CTX* ssl_ctx = to_SSL_CTX(env, ssl_ctx_address, true); + JNI_TRACE("NativeCrypto_SSL_CTX_ech_enable_server(keyJavaBytes=%p, configJavaBytes=%p)", + keyJavaBytes, configJavaBytes); + ScopedByteArrayRO keyBytes(env, keyJavaBytes); + if (keyBytes.get() == nullptr) { + JNI_TRACE("NativeCrypto_SSL_CTX_ech_enable_server => threw exception: " + "could not read key bytes"); + return JNI_FALSE; + } + ScopedByteArrayRO configBytes(env, configJavaBytes); + if (configBytes.get() == nullptr) { + JNI_TRACE("NativeCrypto_SSL_CTX_ech_enable_server => threw exception: " + "could not read config bytes"); + return JNI_FALSE; + } + const uint8_t* ech_key = reinterpret_cast(keyBytes.get()); + size_t ech_key_size = keyBytes.size(); + const uint8_t* ech_config = reinterpret_cast(configBytes.get()); + size_t ech_config_size = configBytes.size(); + bssl::UniquePtr keys(SSL_ECH_KEYS_new()); + bssl::ScopedEVP_HPKE_KEY key; + if (!keys || + !EVP_HPKE_KEY_init(key.get(), EVP_hpke_x25519_hkdf_sha256(), ech_key, ech_key_size) || + !SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/1, + ech_config, ech_config_size, key.get()) || + !SSL_CTX_set1_ech_keys(ssl_ctx, keys.get())) { + JNI_TRACE("NativeCrypto_SSL_CTX_ech_enable_server: " + "Error setting server's ECHConfig and private key\n"); + return JNI_FALSE; + } + return JNI_TRUE; +} + // TESTING METHODS END #define CONSCRYPT_NATIVE_METHOD(functionName, signature) \ @@ -10749,6 +10849,10 @@ static JNINativeMethod sNativeCryptoMethods[] = { CONSCRYPT_NATIVE_METHOD(ENGINE_SSL_force_read, "(J" REF_SSL SSL_CALLBACKS ")V"), CONSCRYPT_NATIVE_METHOD(ENGINE_SSL_shutdown, "(J" REF_SSL SSL_CALLBACKS ")V"), CONSCRYPT_NATIVE_METHOD(usesBoringSsl_FIPS_mode, "()Z"), + CONSCRYPT_NATIVE_METHOD(SSL_set_enable_ech_grease, "(J" REF_SSL "Z)V"), + CONSCRYPT_NATIVE_METHOD(SSL_set1_ech_config_list, "(J" REF_SSL "[B)Z"), + CONSCRYPT_NATIVE_METHOD(SSL_ech_accepted, "(J" REF_SSL ")Z"), + CONSCRYPT_NATIVE_METHOD(SSL_CTX_ech_enable_server, "(J" REF_SSL_CTX "[B[B)Z"), // Used for testing only. CONSCRYPT_NATIVE_METHOD(BIO_read, "(J[B)I"), diff --git a/common/src/main/java/org/conscrypt/AbstractConscryptEngine.java b/common/src/main/java/org/conscrypt/AbstractConscryptEngine.java index 0f1354a93..dffac8b8f 100644 --- a/common/src/main/java/org/conscrypt/AbstractConscryptEngine.java +++ b/common/src/main/java/org/conscrypt/AbstractConscryptEngine.java @@ -92,6 +92,16 @@ abstract class AbstractConscryptEngine extends SSLEngine { @Override public abstract int getPeerPort(); + public abstract void setUseEchGrease(boolean enabled); + + public abstract boolean getUseEchGrease(); + + public abstract void setEchConfigList(byte[] echConfigList); + + public abstract byte[] getEchConfigList(); + + public abstract boolean echAccepted(); + /* @Override */ @SuppressWarnings("MissingOverride") // For compilation with Java 6. public final SSLSession getHandshakeSession() { diff --git a/common/src/main/java/org/conscrypt/AbstractConscryptSocket.java b/common/src/main/java/org/conscrypt/AbstractConscryptSocket.java index 1fe7a2384..f5bef073f 100644 --- a/common/src/main/java/org/conscrypt/AbstractConscryptSocket.java +++ b/common/src/main/java/org/conscrypt/AbstractConscryptSocket.java @@ -754,4 +754,12 @@ void setNpnProtocols(byte[] npnProtocols) {} */ abstract byte[] exportKeyingMaterial(String label, byte[] context, int length) throws SSLException; + + public abstract void setUseEchGrease(boolean enabled); + + public abstract void setEchConfigList(byte[] echConfigList); + + public abstract byte[] getEchConfigList(); + + public abstract boolean echAccepted(); } diff --git a/common/src/main/java/org/conscrypt/Conscrypt.java b/common/src/main/java/org/conscrypt/Conscrypt.java index 66595114f..4f9c72308 100644 --- a/common/src/main/java/org/conscrypt/Conscrypt.java +++ b/common/src/main/java/org/conscrypt/Conscrypt.java @@ -493,6 +493,27 @@ public static byte[] exportKeyingMaterial(SSLSocket socket, String label, byte[] return toConscrypt(socket).exportKeyingMaterial(label, context, length); } + /** + * + * @param socket the socket + * @param enabled whether ECH GREASE is enabled or not + */ + public static void setUseEchGrease(SSLSocket socket, boolean enabled) { + toConscrypt(socket).setUseEchGrease(enabled); + } + + public static void setEchConfigList(SSLSocket socket, byte[] echConfigList) { + toConscrypt(socket).setEchConfigList(echConfigList); + } + + public static byte[] getEchConfigList(SSLSocket socket) { + return toConscrypt(socket).getEchConfigList(); + } + + public static boolean echAccepted(SSLSocket socket) { + return toConscrypt(socket).echAccepted(); + } + /** * Indicates whether the given {@link SSLEngine} was created by this distribution of Conscrypt. */ @@ -737,6 +758,30 @@ public static byte[] exportKeyingMaterial(SSLEngine engine, String label, byte[] return toConscrypt(engine).exportKeyingMaterial(label, context, length); } + /** + * This method enables or disables Encrypted Client Hello (ECH) GREASE. + * + * @param engine the engine + * @param enabled Whether to enable TLSv1.3 ECH GREASE + * + * @see TLS Encrypted Client Hello 6.2. GREASE ECH + */ + public static void setUseEchGrease(SSLEngine engine, boolean enabled) { + toConscrypt(engine).setUseEchGrease(enabled); + } + + public static void setEchConfigList(SSLEngine engine, byte[] echConfigList) { + toConscrypt(engine).setEchConfigList(echConfigList); + } + + public static byte[] getEchConfigList(SSLEngine engine) { + return toConscrypt(engine).getEchConfigList(); + } + + public static boolean echAccepted(SSLEngine engine) { + return toConscrypt(engine).echAccepted(); + } + /** * Indicates whether the given {@link TrustManager} was created by this distribution of * Conscrypt. diff --git a/common/src/main/java/org/conscrypt/ConscryptEngine.java b/common/src/main/java/org/conscrypt/ConscryptEngine.java index 2296b90fb..6be176747 100644 --- a/common/src/main/java/org/conscrypt/ConscryptEngine.java +++ b/common/src/main/java/org/conscrypt/ConscryptEngine.java @@ -395,6 +395,31 @@ public int getPeerPort() { return peerInfoProvider.getPort(); } + @Override + public void setUseEchGrease(boolean enabled) { + sslParameters.setUseEchGrease(enabled); + } + + @Override + public boolean getUseEchGrease() { + return sslParameters.getUseEchGrease(); + } + + @Override + public void setEchConfigList(byte[] echConfigList) { + sslParameters.setEchConfigList(echConfigList); + } + + @Override + public byte[] getEchConfigList() { + return sslParameters.getEchConfigList(); + } + + @Override + public boolean echAccepted() { + return ssl.echAccepted(); + } + @Override public void beginHandshake() throws SSLException { synchronized (ssl) { diff --git a/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java b/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java index 4baebbe8e..40d3f8752 100644 --- a/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java +++ b/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java @@ -405,6 +405,26 @@ byte[] exportKeyingMaterial(String label, byte[] context, int length) throws SSL return engine.exportKeyingMaterial(label, context, length); } + @Override + public void setUseEchGrease(boolean enabled) { + engine.setUseEchGrease(enabled); + } + + @Override + public void setEchConfigList(byte[] echConfigList) { + engine.setEchConfigList(echConfigList); + } + + @Override + public byte[] getEchConfigList() { + return engine.getEchConfigList(); + } + + @Override + public boolean echAccepted() { + return engine.echAccepted(); + } + @Override public final boolean getUseClientMode() { return engine.getUseClientMode(); diff --git a/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java b/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java index 65b3c29ad..c30309347 100644 --- a/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java +++ b/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java @@ -916,6 +916,26 @@ byte[] exportKeyingMaterial(String label, byte[] context, int length) throws SSL return ssl.exportKeyingMaterial(label, context, length); } + @Override + public void setUseEchGrease(boolean enabled) { + sslParameters.setUseEchGrease(enabled); + } + + @Override + public void setEchConfigList(byte[] echConfigList) { + sslParameters.setEchConfigList(echConfigList); + } + + @Override + public byte[] getEchConfigList() { + return sslParameters.getEchConfigList(); + } + + @Override + public boolean echAccepted() { + return ssl.echAccepted(); + } + @Override public final boolean getUseClientMode() { return sslParameters.getUseClientMode(); diff --git a/common/src/main/java/org/conscrypt/Java8EngineWrapper.java b/common/src/main/java/org/conscrypt/Java8EngineWrapper.java index 5cf135d4f..7c330633c 100644 --- a/common/src/main/java/org/conscrypt/Java8EngineWrapper.java +++ b/common/src/main/java/org/conscrypt/Java8EngineWrapper.java @@ -116,6 +116,31 @@ public int getPeerPort() { return delegate.getPeerPort(); } + @Override + public void setUseEchGrease(boolean enabled) { + delegate.setUseEchGrease(enabled); + } + + @Override + public boolean getUseEchGrease() { + return delegate.getUseEchGrease(); + } + + @Override + public void setEchConfigList(byte[] echConfigList) { + delegate.setEchConfigList(echConfigList); + } + + @Override + public byte[] getEchConfigList() { + return delegate.getEchConfigList(); + } + + @Override + public boolean echAccepted() { + return delegate.echAccepted(); + } + @Override public void beginHandshake() throws SSLException { delegate.beginHandshake(); diff --git a/common/src/main/java/org/conscrypt/NativeCrypto.java b/common/src/main/java/org/conscrypt/NativeCrypto.java index 7e6ad21eb..df005eb25 100644 --- a/common/src/main/java/org/conscrypt/NativeCrypto.java +++ b/common/src/main/java/org/conscrypt/NativeCrypto.java @@ -1448,6 +1448,17 @@ static native void ENGINE_SSL_shutdown(long ssl, NativeSsl ssl_holder, SSLHandsh */ static native boolean usesBoringSsl_FIPS_mode(); + /* Encrypted Client Hello */ + + static native void SSL_set_enable_ech_grease(long ssl, NativeSsl ssl_holder, boolean enable); + + static native boolean SSL_set1_ech_config_list(long ssl, NativeSsl ssl_holder, byte[] echConfig); + + static native boolean SSL_ech_accepted(long ssl, NativeSsl ssl_holder); + + static native boolean SSL_CTX_ech_enable_server(long sslCtx, AbstractSessionContext holder, + byte[] key, byte[] config); + /** * Used for testing only. */ diff --git a/common/src/main/java/org/conscrypt/NativeSsl.java b/common/src/main/java/org/conscrypt/NativeSsl.java index b7b02635f..1f40f5858 100644 --- a/common/src/main/java/org/conscrypt/NativeSsl.java +++ b/common/src/main/java/org/conscrypt/NativeSsl.java @@ -279,6 +279,10 @@ byte[] getTlsChannelId() throws SSLException { return NativeCrypto.SSL_get_tls_channel_id(ssl, this); } + boolean echAccepted() { + return NativeCrypto.SSL_ech_accepted(ssl, this); + } + void initialize(String hostname, OpenSSLKey channelIdPrivateKey) throws IOException { boolean enableSessionCreation = parameters.getEnableSessionCreation(); if (!enableSessionCreation) { @@ -298,6 +302,11 @@ void initialize(String hostname, OpenSSLKey channelIdPrivateKey) throws IOExcept if (parameters.isCTVerificationEnabled(hostname)) { NativeCrypto.SSL_enable_signed_cert_timestamps(ssl, this); } + NativeCrypto.SSL_set_enable_ech_grease(ssl, this, parameters.getUseEchGrease()); + if (parameters.echConfigList != null + && !NativeCrypto.SSL_set1_ech_config_list(ssl, this, parameters.echConfigList)) { + throw new SSLHandshakeException("Error setting ECHConfigList"); + } } else { NativeCrypto.SSL_set_accept_state(ssl, this); diff --git a/common/src/main/java/org/conscrypt/SSLParametersImpl.java b/common/src/main/java/org/conscrypt/SSLParametersImpl.java index 1c7cf984b..bc4ec1b6e 100644 --- a/common/src/main/java/org/conscrypt/SSLParametersImpl.java +++ b/common/src/main/java/org/conscrypt/SSLParametersImpl.java @@ -105,6 +105,9 @@ final class SSLParametersImpl implements Cloneable { boolean useSessionTickets; private Boolean useSni; + private boolean useEchGrease; + byte[] echConfigList; + /** * Whether the TLS Channel ID extension is enabled. This field is * server-side only. @@ -191,6 +194,9 @@ private SSLParametersImpl(ClientSessionContext clientSessionContext, this.useSessionTickets = sslParams.useSessionTickets; this.useSni = sslParams.useSni; this.channelIdEnabled = sslParams.channelIdEnabled; + this.useEchGrease = sslParams.useEchGrease; + this.echConfigList = + (sslParams.echConfigList == null) ? null : sslParams.echConfigList.clone(); } static SSLParametersImpl getDefault() throws KeyManagementException { @@ -401,6 +407,30 @@ boolean getUseSni() { return useSni != null ? useSni : isSniEnabledByDefault(); } + /** + * Whether connections using this SSL connection should use the TLS + * extension ECH GREASE. + */ + void setUseEchGrease(boolean flag) { + useEchGrease = flag; + } + + /** + * Returns whether connections using this SSL connection should use the TLS + * extension ECH GREASE. + */ + boolean getUseEchGrease() { + return useEchGrease; + } + + void setEchConfigList(byte[] echConfigList) { + this.echConfigList = echConfigList; + } + + byte[] getEchConfigList() { + return this.echConfigList; + } + /** * For testing only. */ diff --git a/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java b/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java index 0a02e91de..7f0df1e50 100644 --- a/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java +++ b/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java @@ -25,6 +25,7 @@ import static org.conscrypt.NativeConstants.SSL_VERIFY_PEER; import static org.conscrypt.NativeConstants.TLS1_1_VERSION; import static org.conscrypt.NativeConstants.TLS1_2_VERSION; +import static org.conscrypt.NativeConstants.TLS1_3_VERSION; import static org.conscrypt.NativeConstants.TLS1_VERSION; import static org.conscrypt.TestUtils.openTestFile; import static org.conscrypt.TestUtils.readTestFile; @@ -72,6 +73,8 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.SSLEngine; import javax.net.ssl.SSLException; import javax.net.ssl.SSLHandshakeException; @@ -505,6 +508,170 @@ public void test_SSL_set_mode_and_clear_mode() throws Exception { NativeCrypto.SSL_CTX_free(c, null); } + @Test + public void test_SSL_do_handshake_ech_grease_only() throws Exception { + System.out.println("test_SSL_ech_accepted_exchange"); + final ServerSocket listener = newServerSocket(); + + final byte[] key = readTestFile("boringssl-ech-private-key.bin"); + final byte[] serverConfig = readTestFile("boringssl-server-ech-config.bin"); + Hooks cHooks = new ClientHooks() { + @Override + public long beforeHandshake(long c) throws SSLException { + long ssl = super.beforeHandshake(c); + assertEquals(1, NativeCrypto.SSL_set_protocol_versions(ssl, null, TLS1_VERSION, TLS1_3_VERSION)); + NativeCrypto.SSL_set_enable_ech_grease(ssl, null, true); + return ssl; + } + + @Override + public void afterHandshake(long session, long ssl, long context, Socket socket, + FileDescriptor fd, SSLHandshakeCallbacks callback) throws Exception { + assertFalse(NativeCrypto.SSL_ech_accepted(ssl, null)); + super.afterHandshake(session, ssl, context, socket, fd, callback); + } + }; + Hooks sHooks = new ServerHooks(getServerPrivateKey(), getEncodedServerCertificates()) { + @Override + public long beforeHandshake(long c) throws SSLException { + long ssl = super.beforeHandshake(c); + assertEquals(1, NativeCrypto.SSL_set_protocol_versions(ssl, null, TLS1_VERSION, TLS1_3_VERSION)); + assertTrue(NativeCrypto.SSL_CTX_ech_enable_server(c, null, key, serverConfig)); + return ssl; + } + }; + Future client = handshake(listener, 0, true, cHooks, null, null); + Future server = + handshake(listener, 0, false, sHooks, null, null); + TestSSLHandshakeCallbacks clientCallback = + client.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + TestSSLHandshakeCallbacks serverCallback = + server.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + assertTrue(clientCallback.verifyCertificateChainCalled); + assertEqualCertificateChains( + getServerCertificateRefs(), clientCallback.certificateChainRefs); + assertFalse(serverCallback.verifyCertificateChainCalled); + assertFalse(clientCallback.clientCertificateRequestedCalled); + assertFalse(serverCallback.clientCertificateRequestedCalled); + assertFalse(clientCallback.clientPSKKeyRequestedInvoked); + assertFalse(serverCallback.clientPSKKeyRequestedInvoked); + assertFalse(clientCallback.serverPSKKeyRequestedInvoked); + assertFalse(serverCallback.serverPSKKeyRequestedInvoked); + assertTrue(clientCallback.handshakeCompletedCalled); + assertTrue(serverCallback.handshakeCompletedCalled); + assertFalse(clientCallback.serverCertificateRequestedInvoked); + assertTrue(serverCallback.serverCertificateRequestedInvoked); + } + + @Test + public void test_SSL_do_handshake_ech_client_server() throws Exception { + System.out.println("test_SSL_do_handshake_ech_client_server"); + final ServerSocket listener = newServerSocket(); + + final byte[] key = readTestFile("boringssl-ech-private-key.bin"); + final byte[] serverConfig = readTestFile("boringssl-server-ech-config.bin"); + final byte[] clientConfigList = readTestFile("boringssl-ech-config-list.bin"); + Hooks cHooks = new ClientHooks() { + @Override + public long beforeHandshake(long c) throws SSLException { + long ssl = super.beforeHandshake(c); + assertEquals(1, NativeCrypto.SSL_set_protocol_versions(ssl, null, TLS1_VERSION, TLS1_3_VERSION)); + assertTrue(NativeCrypto.SSL_set1_ech_config_list(ssl, null, clientConfigList)); + return ssl; + } + + @Override + public void afterHandshake(long session, long ssl, long context, Socket socket, + FileDescriptor fd, SSLHandshakeCallbacks callback) throws Exception { + assertTrue(NativeCrypto.SSL_ech_accepted(ssl, null)); + super.afterHandshake(session, ssl, context, socket, fd, callback); + } + }; + Hooks sHooks = new ServerHooks(getServerPrivateKey(), getEncodedServerCertificates()) { + @Override + public long beforeHandshake(long c) throws SSLException { + long ssl = super.beforeHandshake(c); + assertEquals(1, NativeCrypto.SSL_set_protocol_versions(ssl, null, TLS1_VERSION, TLS1_3_VERSION)); + assertTrue(NativeCrypto.SSL_CTX_ech_enable_server(c, null, key, serverConfig)); + return ssl; + } + + @Override + public void afterHandshake(long session, long ssl, long context, Socket socket, + FileDescriptor fd, SSLHandshakeCallbacks callback) throws Exception { + assertTrue(NativeCrypto.SSL_ech_accepted(ssl, null)); + super.afterHandshake(session, ssl, context, socket, fd, callback); + } + }; + Future client = handshake(listener, 0, true, cHooks, null, null); + Future server = + handshake(listener, 0, false, sHooks, null, null); + TestSSLHandshakeCallbacks clientCallback = + client.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + TestSSLHandshakeCallbacks serverCallback = + server.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + assertTrue(clientCallback.verifyCertificateChainCalled); + assertEqualCertificateChains( + getServerCertificateRefs(), clientCallback.certificateChainRefs); + assertFalse(serverCallback.verifyCertificateChainCalled); + assertFalse(clientCallback.clientCertificateRequestedCalled); + assertFalse(serverCallback.clientCertificateRequestedCalled); + assertFalse(clientCallback.clientPSKKeyRequestedInvoked); + assertFalse(serverCallback.clientPSKKeyRequestedInvoked); + assertFalse(clientCallback.serverPSKKeyRequestedInvoked); + assertFalse(serverCallback.serverPSKKeyRequestedInvoked); + assertTrue(clientCallback.handshakeCompletedCalled); + assertTrue(serverCallback.handshakeCompletedCalled); + assertFalse(clientCallback.serverCertificateRequestedInvoked); + assertTrue(serverCallback.serverCertificateRequestedInvoked); + } + + @Test + public void test_SSL_set_enable_ech_grease() throws Exception { + long c = NativeCrypto.SSL_CTX_new(); + long s = NativeCrypto.SSL_new(c, null); + + NativeCrypto.SSL_set_enable_ech_grease(s, null, true); + NativeCrypto.SSL_set_enable_ech_grease(s, null, false); + + NativeCrypto.SSL_free(s, null); + NativeCrypto.SSL_CTX_free(c, null); + } + + @Test(expected = NullPointerException.class) + public void test_SSL_set1_ech_config_list_withNull() throws Exception { + long c = NativeCrypto.SSL_CTX_new(); + long s = NativeCrypto.SSL_new(c, null); + NativeCrypto.SSL_set1_ech_config_list(s, null, null); + } + + @Test + public void test_SSL_ech_accepted() throws Exception { + long c = NativeCrypto.SSL_CTX_new(); + long s = NativeCrypto.SSL_new(c, null); + + assertFalse(NativeCrypto.SSL_ech_accepted(s, null)); + + NativeCrypto.SSL_free(s, null); + NativeCrypto.SSL_CTX_free(c, null); + } + + @Test + public void test_SSL_CTX_ech_enable_server() throws Exception { + long c = NativeCrypto.SSL_CTX_new(); + + final byte[] key = readTestFile("boringssl-ech-private-key.bin"); + final byte[] serverConfig = readTestFile("boringssl-server-ech-config.bin"); + assertTrue(NativeCrypto.SSL_CTX_ech_enable_server(c, null, key, serverConfig)); + + NativeCrypto.SSL_CTX_free(c, null); + } + + @Test(expected = NullPointerException.class) + public void test_SSL_CTX_ech_enable_server_NULL_SSL_CTX() throws Exception { + NativeCrypto.SSL_CTX_ech_enable_server(NULL, null, null, null); + } + @Test(expected = NullPointerException.class) public void SSL_get_options_withNullShouldThrow() throws Exception { NativeCrypto.SSL_get_options(NULL, null); @@ -564,6 +731,7 @@ public void SSL_set_protocol_versions() throws Exception { long s = NativeCrypto.SSL_new(c, null); assertEquals(1, NativeCrypto.SSL_set_protocol_versions(s, null, TLS1_VERSION, TLS1_1_VERSION)); assertEquals(1, NativeCrypto.SSL_set_protocol_versions(s, null, TLS1_2_VERSION, TLS1_2_VERSION)); + assertEquals(1, NativeCrypto.SSL_set_protocol_versions(s, null, TLS1_3_VERSION, TLS1_3_VERSION)); assertEquals(0, NativeCrypto.SSL_set_protocol_versions(s, null, TLS1_2_VERSION + 413, TLS1_1_VERSION)); assertEquals(0, NativeCrypto.SSL_set_protocol_versions(s, null, TLS1_1_VERSION, TLS1_2_VERSION + 413)); NativeCrypto.SSL_free(s, null); diff --git a/openjdk/src/test/resources/boringssl-ech-config-list.bin b/openjdk/src/test/resources/boringssl-ech-config-list.bin new file mode 100644 index 0000000000000000000000000000000000000000..b2d4c4baf7754006cc0b715ef4a7c0f8da8f5547 GIT binary patch literal 68 zcmZQ@`p3&)caK4VLE*5S>P71%+s+(uPna|*xM WV93nCom!EYTac5gmzm}RH9C1&WG%YEKqvqD}6QMu8$nFrEG;_kK>2(Yo42)pN U%)p&mk(gVMld6}TpUc1i0EgWeJOBUy literal 0 HcmV?d00001 From 3ae5bd789714f3635537152f7f1b2d42a54052aa Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Wed, 10 Nov 2021 16:39:55 +0100 Subject: [PATCH 2/2] implement ECH Retry Config handling This introduces a new Exception so that clients can respond only to the ECH retry request without having to parse SSLHandshakeExceptions in general. This exception should probably be implemented in boringssl or native_crypto.cc. --- .../jni/main/cpp/conscrypt/native_crypto.cc | 46 +++++++ .../conscrypt/AbstractConscryptEngine.java | 4 + .../conscrypt/AbstractConscryptSocket.java | 4 + .../main/java/org/conscrypt/Conscrypt.java | 16 +++ .../java/org/conscrypt/ConscryptEngine.java | 10 ++ .../org/conscrypt/ConscryptEngineSocket.java | 10 ++ .../ConscryptFileDescriptorSocket.java | 10 ++ .../org/conscrypt/Java8EngineWrapper.java | 10 ++ .../main/java/org/conscrypt/NativeCrypto.java | 4 + .../main/java/org/conscrypt/NativeSsl.java | 8 ++ .../java/org/conscrypt/NativeCryptoTest.java | 128 +++++++++++++++++- 11 files changed, 249 insertions(+), 1 deletion(-) diff --git a/common/src/jni/main/cpp/conscrypt/native_crypto.cc b/common/src/jni/main/cpp/conscrypt/native_crypto.cc index 7cbe5b4be..936693e60 100644 --- a/common/src/jni/main/cpp/conscrypt/native_crypto.cc +++ b/common/src/jni/main/cpp/conscrypt/native_crypto.cc @@ -10474,6 +10474,50 @@ static jboolean NativeCrypto_SSL_set1_ech_config_list(JNIEnv* env, jclass, jlong return !!ret; } +static jstring NativeCrypto_SSL_get0_ech_name_override(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder) { + CHECK_ERROR_QUEUE_ON_RETURN; + SSL* ssl = to_SSL(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_get0_ech_name_override()", ssl); + if (ssl == nullptr) { + JNI_TRACE("ssl=%p NativeCrypto_SSL_get0_ech_name_override() => nullptr", ssl); + return nullptr; + } + const char* ech_name_override; + size_t ech_name_override_len; + SSL_get0_ech_name_override(ssl, &ech_name_override, &ech_name_override_len); + if (ech_name_override_len > 0) { + jstring name = env->NewStringUTF(ech_name_override); + return name; + } + return nullptr; +} + +static jbyteArray NativeCrypto_SSL_get0_ech_retry_configs(JNIEnv* env, jclass, jlong ssl_address, + CONSCRYPT_UNUSED jobject ssl_holder) { + CHECK_ERROR_QUEUE_ON_RETURN; + SSL* ssl = to_SSL(env, ssl_address, true); + JNI_TRACE("ssl=%p NativeCrypto_SSL_get0_ech_retry_configs()", ssl); + if (ssl == nullptr) { + return nullptr; + } + + const uint8_t *retry_configs; + size_t retry_configs_len; + SSL_get0_ech_retry_configs(ssl, &retry_configs, &retry_configs_len); + + jbyteArray result = env->NewByteArray(static_cast(retry_configs_len)); + if (result == nullptr) { + JNI_TRACE("ssl=%p NativeCrypto_SSL_get0_ech_retry_configs() => creating byte array failed", + ssl); + return nullptr; + } + env->SetByteArrayRegion(result, 0, static_cast(retry_configs_len), + (const jbyte*) retry_configs); + JNI_TRACE("ssl=%p NativeCrypto_SSL_get0_ech_retry_configs(%p) => %p", ssl, ssl, result); + return result; +} + /** * public static native long SSL_ech_accepted(long ssl); */ @@ -10851,6 +10895,8 @@ static JNINativeMethod sNativeCryptoMethods[] = { CONSCRYPT_NATIVE_METHOD(usesBoringSsl_FIPS_mode, "()Z"), CONSCRYPT_NATIVE_METHOD(SSL_set_enable_ech_grease, "(J" REF_SSL "Z)V"), CONSCRYPT_NATIVE_METHOD(SSL_set1_ech_config_list, "(J" REF_SSL "[B)Z"), + CONSCRYPT_NATIVE_METHOD(SSL_get0_ech_name_override, "(J" REF_SSL ")Ljava/lang/String;"), + CONSCRYPT_NATIVE_METHOD(SSL_get0_ech_retry_configs, "(J" REF_SSL ")[B"), CONSCRYPT_NATIVE_METHOD(SSL_ech_accepted, "(J" REF_SSL ")Z"), CONSCRYPT_NATIVE_METHOD(SSL_CTX_ech_enable_server, "(J" REF_SSL_CTX "[B[B)Z"), diff --git a/common/src/main/java/org/conscrypt/AbstractConscryptEngine.java b/common/src/main/java/org/conscrypt/AbstractConscryptEngine.java index dffac8b8f..34a222504 100644 --- a/common/src/main/java/org/conscrypt/AbstractConscryptEngine.java +++ b/common/src/main/java/org/conscrypt/AbstractConscryptEngine.java @@ -100,6 +100,10 @@ abstract class AbstractConscryptEngine extends SSLEngine { public abstract byte[] getEchConfigList(); + public abstract String getEchNameOverride(); + + public abstract byte[] getEchRetryConfigList(); + public abstract boolean echAccepted(); /* @Override */ diff --git a/common/src/main/java/org/conscrypt/AbstractConscryptSocket.java b/common/src/main/java/org/conscrypt/AbstractConscryptSocket.java index f5bef073f..136197301 100644 --- a/common/src/main/java/org/conscrypt/AbstractConscryptSocket.java +++ b/common/src/main/java/org/conscrypt/AbstractConscryptSocket.java @@ -761,5 +761,9 @@ abstract byte[] exportKeyingMaterial(String label, byte[] context, int length) public abstract byte[] getEchConfigList(); + public abstract String getEchNameOverride(); + + public abstract byte[] getEchRetryConfigList(); + public abstract boolean echAccepted(); } diff --git a/common/src/main/java/org/conscrypt/Conscrypt.java b/common/src/main/java/org/conscrypt/Conscrypt.java index 4f9c72308..01ae41a6e 100644 --- a/common/src/main/java/org/conscrypt/Conscrypt.java +++ b/common/src/main/java/org/conscrypt/Conscrypt.java @@ -510,6 +510,14 @@ public static byte[] getEchConfigList(SSLSocket socket) { return toConscrypt(socket).getEchConfigList(); } + public static String getEchNameOverride(SSLSocket socket) { + return toConscrypt(socket).getEchNameOverride(); + } + + public static byte[] getEchRetryConfigList(SSLSocket socket) { + return toConscrypt(socket).getEchRetryConfigList(); + } + public static boolean echAccepted(SSLSocket socket) { return toConscrypt(socket).echAccepted(); } @@ -778,6 +786,14 @@ public static byte[] getEchConfigList(SSLEngine engine) { return toConscrypt(engine).getEchConfigList(); } + public static String getEchNameOverride(SSLEngine engine) { + return toConscrypt(engine).getEchNameOverride(); + } + + public static byte[] getEchRetryConfigList(SSLEngine engine) { + return toConscrypt(engine).getEchRetryConfigList(); + } + public static boolean echAccepted(SSLEngine engine) { return toConscrypt(engine).echAccepted(); } diff --git a/common/src/main/java/org/conscrypt/ConscryptEngine.java b/common/src/main/java/org/conscrypt/ConscryptEngine.java index 6be176747..e748ba7a4 100644 --- a/common/src/main/java/org/conscrypt/ConscryptEngine.java +++ b/common/src/main/java/org/conscrypt/ConscryptEngine.java @@ -415,6 +415,16 @@ public byte[] getEchConfigList() { return sslParameters.getEchConfigList(); } + @Override + public String getEchNameOverride() { + return ssl.getEchNameOverride(); + } + + @Override + public byte[] getEchRetryConfigList() { + return ssl.getEchRetryConfigList(); + } + @Override public boolean echAccepted() { return ssl.echAccepted(); diff --git a/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java b/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java index 40d3f8752..05c4aea92 100644 --- a/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java +++ b/common/src/main/java/org/conscrypt/ConscryptEngineSocket.java @@ -420,6 +420,16 @@ public byte[] getEchConfigList() { return engine.getEchConfigList(); } + @Override + public String getEchNameOverride() { + return engine.getEchNameOverride(); + } + + @Override + public byte[] getEchRetryConfigList() { + return engine.getEchRetryConfigList(); + } + @Override public boolean echAccepted() { return engine.echAccepted(); diff --git a/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java b/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java index c30309347..a4c7f495a 100644 --- a/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java +++ b/common/src/main/java/org/conscrypt/ConscryptFileDescriptorSocket.java @@ -931,6 +931,16 @@ public byte[] getEchConfigList() { return sslParameters.getEchConfigList(); } + @Override + public String getEchNameOverride() { + return ssl.getEchNameOverride(); + } + + @Override + public byte[] getEchRetryConfigList() { + return ssl.getEchRetryConfigList(); + } + @Override public boolean echAccepted() { return ssl.echAccepted(); diff --git a/common/src/main/java/org/conscrypt/Java8EngineWrapper.java b/common/src/main/java/org/conscrypt/Java8EngineWrapper.java index 7c330633c..96d9ae6ca 100644 --- a/common/src/main/java/org/conscrypt/Java8EngineWrapper.java +++ b/common/src/main/java/org/conscrypt/Java8EngineWrapper.java @@ -136,6 +136,16 @@ public byte[] getEchConfigList() { return delegate.getEchConfigList(); } + @Override + public String getEchNameOverride() { + return delegate.getEchNameOverride(); + } + + @Override + public byte[] getEchRetryConfigList() { + return delegate.getEchRetryConfigList(); + } + @Override public boolean echAccepted() { return delegate.echAccepted(); diff --git a/common/src/main/java/org/conscrypt/NativeCrypto.java b/common/src/main/java/org/conscrypt/NativeCrypto.java index df005eb25..21b2cac43 100644 --- a/common/src/main/java/org/conscrypt/NativeCrypto.java +++ b/common/src/main/java/org/conscrypt/NativeCrypto.java @@ -1454,6 +1454,10 @@ static native void ENGINE_SSL_shutdown(long ssl, NativeSsl ssl_holder, SSLHandsh static native boolean SSL_set1_ech_config_list(long ssl, NativeSsl ssl_holder, byte[] echConfig); + static native String SSL_get0_ech_name_override(long ssl, NativeSsl ssl_holder); + + static native byte[] SSL_get0_ech_retry_configs(long ssl, NativeSsl ssl_holder); + static native boolean SSL_ech_accepted(long ssl, NativeSsl ssl_holder); static native boolean SSL_CTX_ech_enable_server(long sslCtx, AbstractSessionContext holder, diff --git a/common/src/main/java/org/conscrypt/NativeSsl.java b/common/src/main/java/org/conscrypt/NativeSsl.java index 1f40f5858..49c4e8e41 100644 --- a/common/src/main/java/org/conscrypt/NativeSsl.java +++ b/common/src/main/java/org/conscrypt/NativeSsl.java @@ -279,6 +279,14 @@ byte[] getTlsChannelId() throws SSLException { return NativeCrypto.SSL_get_tls_channel_id(ssl, this); } + String getEchNameOverride() { + return NativeCrypto.SSL_get0_ech_name_override(ssl, this); + } + + byte[] getEchRetryConfigList() { + return NativeCrypto.SSL_get0_ech_retry_configs(ssl, this); + } + boolean echAccepted() { return NativeCrypto.SSL_ech_accepted(ssl, this); } diff --git a/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java b/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java index 7f0df1e50..ee64d3ce0 100644 --- a/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java +++ b/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java @@ -29,6 +29,7 @@ import static org.conscrypt.NativeConstants.TLS1_VERSION; import static org.conscrypt.TestUtils.openTestFile; import static org.conscrypt.TestUtils.readTestFile; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -528,6 +529,9 @@ public long beforeHandshake(long c) throws SSLException { public void afterHandshake(long session, long ssl, long context, Socket socket, FileDescriptor fd, SSLHandshakeCallbacks callback) throws Exception { assertFalse(NativeCrypto.SSL_ech_accepted(ssl, null)); + assertNull(NativeCrypto.SSL_get0_ech_name_override(ssl, null)); + byte[] retryConfigs = NativeCrypto.SSL_get0_ech_retry_configs(ssl, null); + assertEquals(5, retryConfigs.length); // should be the invalid ECH Config List super.afterHandshake(session, ssl, context, socket, fd, callback); } }; @@ -626,6 +630,103 @@ public void afterHandshake(long session, long ssl, long context, Socket socket, assertTrue(serverCallback.serverCertificateRequestedInvoked); } + @Test + public void test_SSL_do_handshake_ech_retry_configs() throws Exception { + final ServerSocket listener = newServerSocket(); + + final byte[] key = readTestFile("boringssl-ech-private-key.bin"); + final byte[] serverConfig = readTestFile("boringssl-server-ech-config.bin"); + final byte[] originalClientConfigList = readTestFile("boringssl-ech-config-list.bin"); + final byte[] clientConfigList = originalClientConfigList.clone(); + clientConfigList[20] = (byte) (clientConfigList[20] % 255 + 1); // corrupt it + + Hooks cHooks = new ClientHooks() { + @Override + public long beforeHandshake(long c) throws SSLException { + long ssl = super.beforeHandshake(c); + assertEquals(1, NativeCrypto.SSL_set_protocol_versions(ssl, null, TLS1_VERSION, TLS1_3_VERSION)); + assertTrue(NativeCrypto.SSL_set1_ech_config_list(ssl, null, clientConfigList)); + return ssl; + } + + @Override + public void afterHandshake(long session, long ssl, long context, Socket socket, + FileDescriptor fd, SSLHandshakeCallbacks callback) { + fail(); + } + }; + Hooks sHooks = new ServerHooks(getServerPrivateKey(), getEncodedServerCertificates()) { + @Override + public long beforeHandshake(long c) throws SSLException { + long ssl = super.beforeHandshake(c); + assertEquals(1, NativeCrypto.SSL_set_protocol_versions(ssl, null, TLS1_VERSION, TLS1_3_VERSION)); + assertTrue(NativeCrypto.SSL_CTX_ech_enable_server(c, null, key, serverConfig)); + return ssl; + } + + @Override + public void afterHandshake(long session, long ssl, long context, Socket socket, + FileDescriptor fd, SSLHandshakeCallbacks callback) throws Exception { + assertTrue(NativeCrypto.SSL_ech_accepted(ssl, null)); + super.afterHandshake(session, ssl, context, socket, fd, callback); + } + }; + Future client = handshake(listener, 0, true, cHooks, null, null, true); + Future server = handshake(listener, 0, false, sHooks, null, null, true); + TestSSLHandshakeCallbacks clientCallback = null; + TestSSLHandshakeCallbacks serverCallback = null; + try { + clientCallback = client.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + serverCallback = server.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + } catch (ExecutionException e) { + // caused by SSLProtocolException + } + assertNull(clientCallback); + assertNull(serverCallback); + assertArrayEquals(originalClientConfigList, cHooks.echRetryConfigs); + assertEquals("example.com", cHooks.echNameOverride); + assertNotNull(cHooks.echRetryConfigs); + assertNull(sHooks.echNameOverride); + assertNull(sHooks.echRetryConfigs); + + final byte[] echRetryConfigsFromPrevious = cHooks.echRetryConfigs; + cHooks = new ClientHooks() { + @Override + public long beforeHandshake(long c) throws SSLException { + long ssl = super.beforeHandshake(c); + assertEquals(1, NativeCrypto.SSL_set_protocol_versions(ssl, null, TLS1_VERSION, TLS1_3_VERSION)); + assertTrue(NativeCrypto.SSL_set1_ech_config_list(ssl, null, echRetryConfigsFromPrevious)); + return ssl; + } + + @Override + public void afterHandshake(long session, long ssl, long context, Socket socket, + FileDescriptor fd, SSLHandshakeCallbacks callback) throws Exception { + assertTrue(NativeCrypto.SSL_ech_accepted(ssl, null)); + super.afterHandshake(session, ssl, context, socket, fd, callback); + } + }; + + client = handshake(listener, 0, true, cHooks, null, null); + server = handshake(listener, 0, false, sHooks, null, null); + clientCallback = client.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + serverCallback = server.get(TIMEOUT_SECONDS, TimeUnit.SECONDS); + assertTrue(clientCallback.verifyCertificateChainCalled); + assertEqualCertificateChains( + getServerCertificateRefs(), clientCallback.certificateChainRefs); + assertFalse(serverCallback.verifyCertificateChainCalled); + assertFalse(clientCallback.clientCertificateRequestedCalled); + assertFalse(serverCallback.clientCertificateRequestedCalled); + assertFalse(clientCallback.clientPSKKeyRequestedInvoked); + assertFalse(serverCallback.clientPSKKeyRequestedInvoked); + assertFalse(clientCallback.serverPSKKeyRequestedInvoked); + assertFalse(serverCallback.serverPSKKeyRequestedInvoked); + assertTrue(clientCallback.handshakeCompletedCalled); + assertTrue(serverCallback.handshakeCompletedCalled); + assertFalse(clientCallback.serverCertificateRequestedInvoked); + assertTrue(serverCallback.serverCertificateRequestedInvoked); + } + @Test public void test_SSL_set_enable_ech_grease() throws Exception { long c = NativeCrypto.SSL_CTX_new(); @@ -667,6 +768,11 @@ public void test_SSL_CTX_ech_enable_server() throws Exception { NativeCrypto.SSL_CTX_free(c, null); } + @Test(expected = NullPointerException.class) + public void test_SSL_get0_ech_retry_configs_withNullShouldThrow() throws Exception { + NativeCrypto.SSL_get0_ech_retry_configs(NULL, null); + } + @Test(expected = NullPointerException.class) public void test_SSL_CTX_ech_enable_server_NULL_SSL_CTX() throws Exception { NativeCrypto.SSL_CTX_ech_enable_server(NULL, null, null, null); @@ -843,6 +949,8 @@ public static class Hooks { boolean pskEnabled; byte[] pskKey; List enabledCipherSuites; + byte[] echRetryConfigs; + String echNameOverride; /** * @throws SSLException if an error occurs creating the context. @@ -1164,6 +1272,12 @@ public void clientCertificateRequested(long s) { public static Future handshake(final ServerSocket listener, final int timeout, final boolean client, final Hooks hooks, final byte[] alpnProtocols, final ApplicationProtocolSelectorAdapter alpnSelector) { + return handshake(listener, timeout, client, hooks, alpnProtocols, alpnSelector, false); + } + + public static Future handshake(final ServerSocket listener, + final int timeout, final boolean client, final Hooks hooks, final byte[] alpnProtocols, + final ApplicationProtocolSelectorAdapter alpnSelector, final boolean useEchRetryConfig) { ExecutorService executor = Executors.newSingleThreadExecutor(); Future future = executor.submit(new Callable() { @@ -1204,7 +1318,19 @@ public TestSSLHandshakeCallbacks call() throws Exception { if (!client && alpnSelector != null) { NativeCrypto.setHasApplicationProtocolSelector(s, null, true); } - NativeCrypto.SSL_do_handshake(s, null, fd, callback, timeout); + if (useEchRetryConfig) { + try { + NativeCrypto.SSL_do_handshake(s, null, fd, callback, timeout); + } catch (SSLProtocolException e) { + hooks.echRetryConfigs = + NativeCrypto.SSL_get0_ech_retry_configs(s, null); + hooks.echNameOverride = + NativeCrypto.SSL_get0_ech_name_override(s, null); + throw e; + } + } else { + NativeCrypto.SSL_do_handshake(s, null, fd, callback, timeout); + } session = NativeCrypto.SSL_get1_session(s, null); if (DEBUG) { System.out.println("ssl=0x" + Long.toString(s, 16)