diff --git a/android/lint.xml b/android/lint.xml index 5d43d7ff6..8ad971986 100644 --- a/android/lint.xml +++ b/android/lint.xml @@ -28,4 +28,10 @@ + + + + + + diff --git a/common/src/jni/main/cpp/conscrypt/native_crypto.cc b/common/src/jni/main/cpp/conscrypt/native_crypto.cc index b4a1010b7..54dfbcdab 100644 --- a/common/src/jni/main/cpp/conscrypt/native_crypto.cc +++ b/common/src/jni/main/cpp/conscrypt/native_crypto.cc @@ -126,6 +126,16 @@ 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) { @@ -7825,7 +7835,12 @@ static int sslSelect(JNIEnv* env, int type, jobject fdObject, AppData* appData, if (fds[1].revents & POLLIN) { char token; do { - (void)read(appData->fdsEmergency[0], &token, 1); + // TEMP - fixes build error + int foo = 0; + foo = read(appData->fdsEmergency[0], &token, 1); + if (foo > 0) { + CONSCRYPT_LOG_VERBOSE("FOO: %d", foo); + } } while (errno == EINTR); } } @@ -7856,7 +7871,12 @@ static void sslNotify(AppData* appData) { char token = '*'; do { errno = 0; - (void)write(appData->fdsEmergency[1], &token, 1); + // TEMP - fixes build error + int foo = 0; + foo = write(appData->fdsEmergency[1], &token, 1); + if (foo > 0) { + CONSCRYPT_LOG_VERBOSE("FOO: %d", foo); + } } while (errno == EINTR); errno = errnoBackup; #endif @@ -11815,6 +11835,198 @@ 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) { + conscrypt::jniutil::throwNullPointerException(env, "Null pointer, ssl address"); + ERR_clear_error(); + return JNI_FALSE; + } + ScopedByteArrayRO configBytes(env, configJavaBytes); + if (configBytes.get() == nullptr) { + conscrypt::jniutil::throwNullPointerException(env, "Null pointer, ech config"); + ERR_clear_error(); + JNI_TRACE("NativeCrypto_SSL_set1_ech_config_list => could not read config bytes"); + return JNI_FALSE; + } + int ret = SSL_set1_ech_config_list(ssl, reinterpret_cast(configBytes.get()), + configBytes.size()); + if (!ret) { + conscrypt::jniutil::throwParsingException(env, "Error parsing ECH config"); + ERR_clear_error(); + JNI_TRACE("ssl=%p NativeCrypto_SSL_set1_ech_config_list(%p) => threw exception", ssl, + configJavaBytes); + return JNI_FALSE; + } + + JNI_TRACE("ssl=%p NativeCrypto_SSL_set1_ech_config_list(%p) => %d", ssl, configJavaBytes, ret); + 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); + if (retry_configs_len <= 0) { + return nullptr; + } + 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), + reinterpret_cast(retry_configs)); + JNI_TRACE("ssl=%p NativeCrypto_SSL_get0_ech_retry_configs() => %p", ssl, result); + return result; +} + +static jlong NativeCrypto_SSL_ECH_KEYS_new(JNIEnv* env, jclass) { + CHECK_ERROR_QUEUE_ON_RETURN; + bssl::UniquePtr sslEchKeys(SSL_ECH_KEYS_new()); + if (sslEchKeys.get() == nullptr) { + conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "SSL_ECH_KEYS_new"); + return 0; + } + JNI_TRACE("NativeCrypto_SSL_ECH_KEYS_new => %p", sslEchKeys.get()); + return (jlong)sslEchKeys.release(); +} + +static void NativeCrypto_SSL_ECH_KEYS_up_ref(JNIEnv* env, jclass, jlong ssl_ech_keys_address) { + CHECK_ERROR_QUEUE_ON_RETURN; + SSL_ECH_KEYS* ssl_ech_keys = to_SSL_ECH_KEYS(env, ssl_ech_keys_address, true); + JNI_TRACE("ssl_ech_keys=%p NativeCrypto_SSL_ECH_KEYS_up_ref", ssl_ech_keys); + if (ssl_ech_keys == nullptr) { + return; + } + SSL_ECH_KEYS_up_ref(ssl_ech_keys); +} + +static void NativeCrypto_SSL_ECH_KEYS_free(JNIEnv* env, jclass, jlong ssl_ech_keys_address) { + CHECK_ERROR_QUEUE_ON_RETURN; + SSL_ECH_KEYS* ssl_ech_keys = to_SSL_ECH_KEYS(env, ssl_ech_keys_address, true); + JNI_TRACE("ssl_ech_keys=%p NativeCrypto_SSL_ECH_KEYS_free", ssl_ech_keys); + if (ssl_ech_keys == nullptr) { + return; + } + SSL_ECH_KEYS_free(ssl_ech_keys); +} + +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) { + conscrypt::jniutil::throwNullPointerException(env, "Null pointer, ssl address"); + ERR_clear_error(); + return JNI_FALSE; + } + jboolean accepted = SSL_ech_accepted(ssl); + + if (!accepted) { + conscrypt::jniutil::throwParsingException(env, "Invalid ECH config list"); + ERR_clear_error(); + JNI_TRACE("ssl=%p NativeCrypto_SSL_ech_accepted => threw exception", ssl); + return JNI_FALSE; + } + + 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) { + conscrypt::jniutil::throwNullPointerException(env, "Null pointer, key bytes"); + ERR_clear_error(); + 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) { + conscrypt::jniutil::throwNullPointerException(env, "Null pointer, config bytes"); + ERR_clear_error(); + 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())) { + conscrypt::jniutil::throwInvalidKeyException(env, "Key config error"); + ERR_clear_error(); + 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) \ @@ -12172,6 +12384,17 @@ static JNINativeMethod sNativeCryptoMethods[] = { CONSCRYPT_NATIVE_METHOD(Scrypt_generate_key, "([B[BIIII)[B"), CONSCRYPT_NATIVE_METHOD(SSL_CTX_set_spake_credential, "([B[B[B[BZIJ" REF_SSL_CTX ")V"), + // FOR ECH TESTING + 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_KEYS_new, "()J"), + CONSCRYPT_NATIVE_METHOD(SSL_ECH_KEYS_up_ref, "(J)V"), + CONSCRYPT_NATIVE_METHOD(SSL_ECH_KEYS_free, "(J)V"), + 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"), CONSCRYPT_NATIVE_METHOD(BIO_write, "(J[BII)V"), diff --git a/common/src/main/java/org/conscrypt/NativeCrypto.java b/common/src/main/java/org/conscrypt/NativeCrypto.java index d2222f7b5..2b8ad17b0 100644 --- a/common/src/main/java/org/conscrypt/NativeCrypto.java +++ b/common/src/main/java/org/conscrypt/NativeCrypto.java @@ -1623,6 +1623,32 @@ static native byte[] Scrypt_generate_key( */ static native boolean usesBoringSsl_FIPS_mode(); + /* ECH */ + + 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 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 byte[] SSL_marshal_ech_config(short configId, byte[] key, String publicName); + + static native long SSL_ECH_KEYS_new(); + + static native void SSL_ECH_KEYS_up_ref(long sslEchKeys); + + static native void SSL_ECH_KEYS_free(long sslEchKeys); + + static native byte[] SSL_ECH_KEYS_marshal_retry_configs(byte[] key); + + 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/openjdk/build.gradle b/openjdk/build.gradle index b63b4c8de..4092c6e70 100644 --- a/openjdk/build.gradle +++ b/openjdk/build.gradle @@ -350,6 +350,15 @@ def testInterop = tasks.register("testInterop", Test) { } check.dependsOn testInterop +// Added to see results of new ECH tests when running tests from the command line +tasks.withType(Test).configureEach { + testLogging { + exceptionFormat "full" + events "started", "skipped", "passed", "failed" + showStandardStreams true + } +} + jacocoTestReport { additionalSourceDirs.from files("$rootDir/openjdk/src/test/java", "$rootDir/common/src/main/java") executionData tasks.withType(Test) diff --git a/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java b/openjdk/src/test/java/org/conscrypt/NativeCryptoTest.java index f46e133b8..8ad685064 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.decodeHex; import static org.conscrypt.TestUtils.isWindows; @@ -66,6 +67,7 @@ import java.net.SocketException; import java.net.SocketTimeoutException; import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; @@ -94,6 +96,8 @@ import javax.net.ssl.SSLException; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLProtocolException; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; import javax.security.auth.x500.X500Principal; @RunWith(JUnit4.class) @@ -117,7 +121,6 @@ public class NativeCryptoTest { private static OpenSSLKey CHANNEL_ID_PRIVATE_KEY; private static byte[] CHANNEL_ID; private static Method m_Platform_getFileDescriptor; - private static RSAPrivateCrtKey TEST_RSA_KEY; @BeforeClass @@ -477,6 +480,298 @@ 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 { + 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 { + 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); + } + }; + Hooks sHooks = new ServerHooks(SERVER_PRIVATE_KEY, ENCODED_SERVER_CERTIFICATES) { + @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(SERVER_CERTIFICATE_REFS, 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); + } + + /** Convenient debug print for ECH Config Lists */ + private void printEchConfigList(String msg, byte[] buf) { + int blen = buf.length; + System.out.print(msg + " (" + blen + "):\n "); + for (int i = 0; i < blen; i++) { + if ((i != 0) && (i % 16 == 0)) + System.out.print("\n "); + System.out.print(String.format("%02x:", Byte.toUnsignedInt(buf[i]))); + } + System.out.print("\n"); + } + + @Test + public void test_SSL_do_handshake_ech_client_server() 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[] 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(SERVER_PRIVATE_KEY, ENCODED_SERVER_CERTIFICATES) { + @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(SERVER_CERTIFICATE_REFS, 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 + public void test_SSL_set1_ech_valid_config_list() throws Exception { + long c = NativeCrypto.SSL_CTX_new(); + long s = NativeCrypto.SSL_new(c, null); + + final byte[] configList = readTestFile("boringssl-ech-config-list.bin"); + assertTrue(NativeCrypto.SSL_set1_ech_config_list(s, null, configList)); + } + + @Test + public void test_SSL_set1_ech_invalid_config_list() throws Exception { + long c = NativeCrypto.SSL_CTX_new(); + long s = NativeCrypto.SSL_new(c, null); + + byte[] badConfigList = { + 0x00, 0x05, (byte) 0xfe, 0x0d, (byte) 0xff, (byte) 0xff, (byte) 0xff}; + boolean set = false; + assertThrows(ParsingException.class, + () -> NativeCrypto.SSL_set1_ech_config_list(s, null, badConfigList)); + NativeCrypto.SSL_free(s, null); + NativeCrypto.SSL_CTX_free(c, null); + } + + @Test + public void test_SSL_set1_ech_config_list_withNull() throws Exception { + long c = NativeCrypto.SSL_CTX_new(); + long s = NativeCrypto.SSL_new(c, null); + assertThrows(NullPointerException.class, + () -> NativeCrypto.SSL_set1_ech_config_list(s, null, null)); + } + + @Test + public void test_SSL_ECH_KEYS_new() throws Exception { + long k = NativeCrypto.SSL_ECH_KEYS_new(); + NativeCrypto.SSL_ECH_KEYS_up_ref(k); + assertTrue(k != NULL); + long k2 = NativeCrypto.SSL_ECH_KEYS_new(); + NativeCrypto.SSL_ECH_KEYS_up_ref(k2); + assertTrue(k != k2); + NativeCrypto.SSL_ECH_KEYS_free(k); + NativeCrypto.SSL_ECH_KEYS_free(k2); + } + + @Test + public void test_SSL_ech_accepted() throws Exception { + long c = NativeCrypto.SSL_CTX_new(); + long s = NativeCrypto.SSL_new(c, null); + + assertThrows( + ParsingException.class, () -> 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 + public void test_SSL_get0_ech_retry_configs_withNullShouldThrow() throws Exception { + assertThrows(NullPointerException.class, + () -> NativeCrypto.SSL_get0_ech_retry_configs(NULL, null)); + } + + @Test + public void test_SSL_CTX_ech_enable_server_NULL_SSL_CTX() throws Exception { + assertThrows(NullPointerException.class, + () -> NativeCrypto.SSL_CTX_ech_enable_server(NULL, null, null, null)); + } + + @Test + public void test_SSL_CTX_ech_enable_server_ssl_withNullsShouldThrow() { + long c = NativeCrypto.SSL_CTX_new(); + try { + NativeCrypto.SSL_CTX_ech_enable_server(c, null, null, null); + } catch (NullPointerException | AssertionError e) { + // AssertionError when running with checkErrorQueue + return; + } + fail(); + } + + @Test + public void test_SSL_CTX_ech_enable_server_ssl_withNullConfigShouldThrow() throws Exception { + long c = NativeCrypto.SSL_CTX_new(); + // TODO running this with checkErrorQueue after + // test_SSL_CTX_ech_enable_server_ssl_with_bad_config fails here + final byte[] serverConfig = readTestFile("boringssl-server-ech-config.bin"); + try { + NativeCrypto.SSL_CTX_ech_enable_server(c, null, null, serverConfig); + } catch (NullPointerException | AssertionError e) { + // AssertionError when running with checkErrorQueue + return; + } + fail(); + } + + @Test + public void test_SSL_CTX_ech_enable_server_ssl_withNullKeyShouldThrow() throws Exception { + long c = NativeCrypto.SSL_CTX_new(); + final byte[] key = readTestFile("boringssl-ech-private-key.bin"); + try { + NativeCrypto.SSL_CTX_ech_enable_server(c, null, key, null); + } catch (NullPointerException | AssertionError e) { + // AssertionError when running with checkErrorQueue + return; + } + fail(); + } + + @Test + public void test_SSL_CTX_ech_enable_server_ssl_with_bad_key() throws Exception { + long c = NativeCrypto.SSL_CTX_new(); + final byte[] badKey = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; + final byte[] serverConfig = readTestFile("boringssl-server-ech-config.bin"); + assertThrows(InvalidKeyException.class, + () + -> assertFalse(NativeCrypto.SSL_CTX_ech_enable_server( + c, null, badKey, serverConfig))); + } + + @Test + public void test_SSL_CTX_ech_enable_server_ssl_with_bad_config() throws Exception { + long c = NativeCrypto.SSL_CTX_new(); + final byte[] key = readTestFile("boringssl-ech-private-key.bin"); + byte[] badConfig = {(byte) 0xfe, (byte) 0x0d, (byte) 0xff, (byte) 0xff, (byte) 0xff}; + assertThrows(InvalidKeyException.class, + () -> assertFalse(NativeCrypto.SSL_CTX_ech_enable_server(c, null, key, badConfig))); + } + + @Test + public void test_SSL_CTX_ech_enable_server_ssl_with_bad_key_config() throws Exception { + long c = NativeCrypto.SSL_CTX_new(); + final byte[] badKey = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; + byte[] badConfig = {(byte) 0xfe, (byte) 0x0d, (byte) 0xff, (byte) 0xff, (byte) 0xff}; + assertThrows(InvalidKeyException.class, + () + -> assertFalse(NativeCrypto.SSL_CTX_ech_enable_server( + c, null, badKey, badConfig))); + } + @Test public void SSL_get_options_withNullShouldThrow() throws Exception { assertThrows(NullPointerException.class, () -> NativeCrypto.SSL_get_options(NULL, null)); @@ -660,6 +955,8 @@ public static class Hooks { boolean pskEnabled; byte[] pskKey; List enabledCipherSuites; + byte[] echRetryConfigs; + String echNameOverride; /** * @throws SSLException if an error occurs creating the context. @@ -976,6 +1273,7 @@ public void clientCertificateRequested(long s) { } } + // wrapper method added for ECH testing public static Future handshake(final ServerSocket listener, final int timeout, final boolean client, final Hooks hooks, final byte[] alpnProtocols, final ApplicationProtocolSelectorAdapter alpnSelector) { @@ -1020,7 +1318,9 @@ public TestSSLHandshakeCallbacks call() throws Exception { if (!client && alpnSelector != null) { NativeCrypto.setHasApplicationProtocolSelector(s, null, true); } + 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) + " handshake" 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 000000000..b2d4c4baf Binary files /dev/null and b/openjdk/src/test/resources/boringssl-ech-config-list.bin differ diff --git a/openjdk/src/test/resources/boringssl-ech-private-key.bin b/openjdk/src/test/resources/boringssl-ech-private-key.bin new file mode 100644 index 000000000..f391fb419 --- /dev/null +++ b/openjdk/src/test/resources/boringssl-ech-private-key.bin @@ -0,0 +1 @@ + EE��W�~�|�@Ũ��G8d����+�|� \ No newline at end of file diff --git a/openjdk/src/test/resources/boringssl-server-ech-config.bin b/openjdk/src/test/resources/boringssl-server-ech-config.bin new file mode 100644 index 000000000..ddf593ba5 Binary files /dev/null and b/openjdk/src/test/resources/boringssl-server-ech-config.bin differ