Skip to content

Commit 354957b

Browse files
authored
Basic TLS Encrypted ClientHello (ECH) support (native layer) (#1374)
Native classes are updated to provide access to ECH parameters in BoringSSL. Unit tests are also added.
1 parent b7fd8fe commit 354957b

8 files changed

Lines changed: 568 additions & 3 deletions

File tree

android/lint.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,10 @@
2828
<issue id="Assert">
2929
<ignore path="**/common/src/main/java/org/conscrypt/OpenSSLCipherChaCha20.java" />
3030
</issue>
31+
32+
<!-- Workaround for "Unexpected failure during lint analysis". -->
33+
<issue id="LintError">
34+
<ignore regexp=".*module-info\.class.*"/>
35+
</issue>
36+
3137
</lint>

common/src/jni/main/cpp/conscrypt/native_crypto.cc

Lines changed: 225 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,16 @@ static SSL_CIPHER* to_SSL_CIPHER(JNIEnv* env, jlong ssl_cipher_address, bool thr
126126
return ssl_cipher;
127127
}
128128

129+
static SSL_ECH_KEYS* to_SSL_ECH_KEYS(JNIEnv* env, jlong ssl_ech_keys_address, bool throwIfNull) {
130+
SSL_ECH_KEYS* ssl_ech_keys =
131+
reinterpret_cast<SSL_ECH_KEYS*>(static_cast<uintptr_t>(ssl_ech_keys_address));
132+
if ((ssl_ech_keys == nullptr) && throwIfNull) {
133+
JNI_TRACE("ssl_ech_keys == null");
134+
conscrypt::jniutil::throwNullPointerException(env, "ssl_ech_keys == null");
135+
}
136+
return ssl_ech_keys;
137+
}
138+
129139
template <typename T>
130140
static T* fromContextObject(JNIEnv* env, jobject contextObject) {
131141
if (contextObject == nullptr) {
@@ -7825,7 +7835,12 @@ static int sslSelect(JNIEnv* env, int type, jobject fdObject, AppData* appData,
78257835
if (fds[1].revents & POLLIN) {
78267836
char token;
78277837
do {
7828-
(void)read(appData->fdsEmergency[0], &token, 1);
7838+
// TEMP - fixes build error
7839+
int foo = 0;
7840+
foo = read(appData->fdsEmergency[0], &token, 1);
7841+
if (foo > 0) {
7842+
CONSCRYPT_LOG_VERBOSE("FOO: %d", foo);
7843+
}
78297844
} while (errno == EINTR);
78307845
}
78317846
}
@@ -7856,7 +7871,12 @@ static void sslNotify(AppData* appData) {
78567871
char token = '*';
78577872
do {
78587873
errno = 0;
7859-
(void)write(appData->fdsEmergency[1], &token, 1);
7874+
// TEMP - fixes build error
7875+
int foo = 0;
7876+
foo = write(appData->fdsEmergency[1], &token, 1);
7877+
if (foo > 0) {
7878+
CONSCRYPT_LOG_VERBOSE("FOO: %d", foo);
7879+
}
78607880
} while (errno == EINTR);
78617881
errno = errnoBackup;
78627882
#endif
@@ -11815,6 +11835,198 @@ static jlong NativeCrypto_SSL_get1_session(JNIEnv* env, jclass, jlong ssl_addres
1181511835
return reinterpret_cast<uintptr_t>(SSL_get1_session(ssl));
1181611836
}
1181711837

11838+
static void NativeCrypto_SSL_set_enable_ech_grease(JNIEnv* env, jclass, jlong ssl_address,
11839+
CONSCRYPT_UNUSED jobject ssl_holder,
11840+
jboolean enable) {
11841+
CHECK_ERROR_QUEUE_ON_RETURN;
11842+
SSL* ssl = to_SSL(env, ssl_address, true);
11843+
JNI_TRACE("ssl=%p NativeCrypto_SSL_set_enable_ech_grease(%d)", ssl, enable);
11844+
if (ssl == nullptr) {
11845+
return;
11846+
}
11847+
SSL_set_enable_ech_grease(ssl, enable ? 1 : 0);
11848+
JNI_TRACE("ssl=%p NativeCrypto_SSL_set_enable_ech_grease(%d) => success", ssl, enable);
11849+
}
11850+
11851+
static jboolean NativeCrypto_SSL_set1_ech_config_list(JNIEnv* env, jclass, jlong ssl_address,
11852+
CONSCRYPT_UNUSED jobject ssl_holder,
11853+
jbyteArray configJavaBytes) {
11854+
CHECK_ERROR_QUEUE_ON_RETURN;
11855+
SSL* ssl = to_SSL(env, ssl_address, true);
11856+
JNI_TRACE("ssl=%p NativeCrypto_SSL_set1_ech_config_list(%p)", ssl, configJavaBytes);
11857+
if (ssl == nullptr) {
11858+
conscrypt::jniutil::throwNullPointerException(env, "Null pointer, ssl address");
11859+
ERR_clear_error();
11860+
return JNI_FALSE;
11861+
}
11862+
ScopedByteArrayRO configBytes(env, configJavaBytes);
11863+
if (configBytes.get() == nullptr) {
11864+
conscrypt::jniutil::throwNullPointerException(env, "Null pointer, ech config");
11865+
ERR_clear_error();
11866+
JNI_TRACE("NativeCrypto_SSL_set1_ech_config_list => could not read config bytes");
11867+
return JNI_FALSE;
11868+
}
11869+
int ret = SSL_set1_ech_config_list(ssl, reinterpret_cast<const uint8_t*>(configBytes.get()),
11870+
configBytes.size());
11871+
if (!ret) {
11872+
conscrypt::jniutil::throwParsingException(env, "Error parsing ECH config");
11873+
ERR_clear_error();
11874+
JNI_TRACE("ssl=%p NativeCrypto_SSL_set1_ech_config_list(%p) => threw exception", ssl,
11875+
configJavaBytes);
11876+
return JNI_FALSE;
11877+
}
11878+
11879+
JNI_TRACE("ssl=%p NativeCrypto_SSL_set1_ech_config_list(%p) => %d", ssl, configJavaBytes, ret);
11880+
return ret;
11881+
}
11882+
11883+
static jstring NativeCrypto_SSL_get0_ech_name_override(JNIEnv* env, jclass, jlong ssl_address,
11884+
CONSCRYPT_UNUSED jobject ssl_holder) {
11885+
CHECK_ERROR_QUEUE_ON_RETURN;
11886+
SSL* ssl = to_SSL(env, ssl_address, true);
11887+
JNI_TRACE("ssl=%p NativeCrypto_SSL_get0_ech_name_override()", ssl);
11888+
if (ssl == nullptr) {
11889+
JNI_TRACE("ssl=%p NativeCrypto_SSL_get0_ech_name_override() => nullptr", ssl);
11890+
return nullptr;
11891+
}
11892+
const char* ech_name_override;
11893+
size_t ech_name_override_len;
11894+
SSL_get0_ech_name_override(ssl, &ech_name_override, &ech_name_override_len);
11895+
if (ech_name_override_len > 0) {
11896+
jstring name = env->NewStringUTF(ech_name_override);
11897+
return name;
11898+
}
11899+
return nullptr;
11900+
}
11901+
11902+
static jbyteArray NativeCrypto_SSL_get0_ech_retry_configs(JNIEnv* env, jclass, jlong ssl_address,
11903+
CONSCRYPT_UNUSED jobject ssl_holder) {
11904+
CHECK_ERROR_QUEUE_ON_RETURN;
11905+
SSL* ssl = to_SSL(env, ssl_address, true);
11906+
JNI_TRACE("ssl=%p NativeCrypto_SSL_get0_ech_retry_configs()", ssl);
11907+
if (ssl == nullptr) {
11908+
return nullptr;
11909+
}
11910+
const uint8_t* retry_configs;
11911+
size_t retry_configs_len;
11912+
SSL_get0_ech_retry_configs(ssl, &retry_configs, &retry_configs_len);
11913+
if (retry_configs_len <= 0) {
11914+
return nullptr;
11915+
}
11916+
jbyteArray result = env->NewByteArray(static_cast<jsize>(retry_configs_len));
11917+
if (result == nullptr) {
11918+
JNI_TRACE("ssl=%p NativeCrypto_SSL_get0_ech_retry_configs() => creating byte array failed",
11919+
ssl);
11920+
return nullptr;
11921+
}
11922+
env->SetByteArrayRegion(result, 0, static_cast<jsize>(retry_configs_len),
11923+
reinterpret_cast<const jbyte*>(retry_configs));
11924+
JNI_TRACE("ssl=%p NativeCrypto_SSL_get0_ech_retry_configs() => %p", ssl, result);
11925+
return result;
11926+
}
11927+
11928+
static jlong NativeCrypto_SSL_ECH_KEYS_new(JNIEnv* env, jclass) {
11929+
CHECK_ERROR_QUEUE_ON_RETURN;
11930+
bssl::UniquePtr<SSL_ECH_KEYS> sslEchKeys(SSL_ECH_KEYS_new());
11931+
if (sslEchKeys.get() == nullptr) {
11932+
conscrypt::jniutil::throwExceptionFromBoringSSLError(env, "SSL_ECH_KEYS_new");
11933+
return 0;
11934+
}
11935+
JNI_TRACE("NativeCrypto_SSL_ECH_KEYS_new => %p", sslEchKeys.get());
11936+
return (jlong)sslEchKeys.release();
11937+
}
11938+
11939+
static void NativeCrypto_SSL_ECH_KEYS_up_ref(JNIEnv* env, jclass, jlong ssl_ech_keys_address) {
11940+
CHECK_ERROR_QUEUE_ON_RETURN;
11941+
SSL_ECH_KEYS* ssl_ech_keys = to_SSL_ECH_KEYS(env, ssl_ech_keys_address, true);
11942+
JNI_TRACE("ssl_ech_keys=%p NativeCrypto_SSL_ECH_KEYS_up_ref", ssl_ech_keys);
11943+
if (ssl_ech_keys == nullptr) {
11944+
return;
11945+
}
11946+
SSL_ECH_KEYS_up_ref(ssl_ech_keys);
11947+
}
11948+
11949+
static void NativeCrypto_SSL_ECH_KEYS_free(JNIEnv* env, jclass, jlong ssl_ech_keys_address) {
11950+
CHECK_ERROR_QUEUE_ON_RETURN;
11951+
SSL_ECH_KEYS* ssl_ech_keys = to_SSL_ECH_KEYS(env, ssl_ech_keys_address, true);
11952+
JNI_TRACE("ssl_ech_keys=%p NativeCrypto_SSL_ECH_KEYS_free", ssl_ech_keys);
11953+
if (ssl_ech_keys == nullptr) {
11954+
return;
11955+
}
11956+
SSL_ECH_KEYS_free(ssl_ech_keys);
11957+
}
11958+
11959+
static jboolean NativeCrypto_SSL_ech_accepted(JNIEnv* env, jclass, jlong ssl_address,
11960+
CONSCRYPT_UNUSED jobject ssl_holder) {
11961+
JNI_TRACE("NativeCrypto_SSL_ech_accepted");
11962+
CHECK_ERROR_QUEUE_ON_RETURN;
11963+
SSL* ssl = to_SSL(env, ssl_address, true);
11964+
JNI_TRACE("ssl=%p NativeCrypto_SSL_ech_accepted", ssl);
11965+
if (ssl == nullptr) {
11966+
conscrypt::jniutil::throwNullPointerException(env, "Null pointer, ssl address");
11967+
ERR_clear_error();
11968+
return JNI_FALSE;
11969+
}
11970+
jboolean accepted = SSL_ech_accepted(ssl);
11971+
11972+
if (!accepted) {
11973+
conscrypt::jniutil::throwParsingException(env, "Invalid ECH config list");
11974+
ERR_clear_error();
11975+
JNI_TRACE("ssl=%p NativeCrypto_SSL_ech_accepted => threw exception", ssl);
11976+
return JNI_FALSE;
11977+
}
11978+
11979+
JNI_TRACE("ssl=%p NativeCrypto_SSL_ech_accepted => %d", ssl, accepted);
11980+
return accepted;
11981+
}
11982+
11983+
static jboolean NativeCrypto_SSL_CTX_ech_enable_server(JNIEnv* env, jclass, jlong ssl_ctx_address,
11984+
CONSCRYPT_UNUSED jobject holder,
11985+
jbyteArray keyJavaBytes,
11986+
jbyteArray configJavaBytes) {
11987+
CHECK_ERROR_QUEUE_ON_RETURN;
11988+
SSL_CTX* ssl_ctx = to_SSL_CTX(env, ssl_ctx_address, true);
11989+
JNI_TRACE("NativeCrypto_SSL_CTX_ech_enable_server(keyJavaBytes=%p, configJavaBytes=%p)",
11990+
keyJavaBytes, configJavaBytes);
11991+
ScopedByteArrayRO keyBytes(env, keyJavaBytes);
11992+
if (keyBytes.get() == nullptr) {
11993+
conscrypt::jniutil::throwNullPointerException(env, "Null pointer, key bytes");
11994+
ERR_clear_error();
11995+
JNI_TRACE(
11996+
"NativeCrypto_SSL_CTX_ech_enable_server => threw exception: "
11997+
"could not read key bytes");
11998+
return JNI_FALSE;
11999+
}
12000+
ScopedByteArrayRO configBytes(env, configJavaBytes);
12001+
if (configBytes.get() == nullptr) {
12002+
conscrypt::jniutil::throwNullPointerException(env, "Null pointer, config bytes");
12003+
ERR_clear_error();
12004+
JNI_TRACE(
12005+
"NativeCrypto_SSL_CTX_ech_enable_server => threw exception: "
12006+
"could not read config bytes");
12007+
return JNI_FALSE;
12008+
}
12009+
const uint8_t* ech_key = reinterpret_cast<const uint8_t*>(keyBytes.get());
12010+
size_t ech_key_size = keyBytes.size();
12011+
const uint8_t* ech_config = reinterpret_cast<const uint8_t*>(configBytes.get());
12012+
size_t ech_config_size = configBytes.size();
12013+
bssl::UniquePtr<SSL_ECH_KEYS> keys(SSL_ECH_KEYS_new());
12014+
bssl::ScopedEVP_HPKE_KEY key;
12015+
if (!keys ||
12016+
!EVP_HPKE_KEY_init(key.get(), EVP_hpke_x25519_hkdf_sha256(), ech_key, ech_key_size) ||
12017+
!SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/1, ech_config, ech_config_size,
12018+
key.get()) ||
12019+
!SSL_CTX_set1_ech_keys(ssl_ctx, keys.get())) {
12020+
conscrypt::jniutil::throwInvalidKeyException(env, "Key config error");
12021+
ERR_clear_error();
12022+
JNI_TRACE(
12023+
"NativeCrypto_SSL_CTX_ech_enable_server: "
12024+
"Error setting server's ECHConfig and private key\n");
12025+
return JNI_FALSE;
12026+
}
12027+
return JNI_TRUE;
12028+
}
12029+
1181812030
// TESTING METHODS END
1181912031

1182012032
#define CONSCRYPT_NATIVE_METHOD(functionName, signature) \
@@ -12172,6 +12384,17 @@ static JNINativeMethod sNativeCryptoMethods[] = {
1217212384
CONSCRYPT_NATIVE_METHOD(Scrypt_generate_key, "([B[BIIII)[B"),
1217312385
CONSCRYPT_NATIVE_METHOD(SSL_CTX_set_spake_credential, "([B[B[B[BZIJ" REF_SSL_CTX ")V"),
1217412386
12387+
// FOR ECH TESTING
12388+
CONSCRYPT_NATIVE_METHOD(SSL_set_enable_ech_grease, "(J" REF_SSL "Z)V"),
12389+
CONSCRYPT_NATIVE_METHOD(SSL_set1_ech_config_list, "(J" REF_SSL "[B)Z"),
12390+
CONSCRYPT_NATIVE_METHOD(SSL_get0_ech_name_override, "(J" REF_SSL ")Ljava/lang/String;"),
12391+
CONSCRYPT_NATIVE_METHOD(SSL_get0_ech_retry_configs, "(J" REF_SSL ")[B"),
12392+
CONSCRYPT_NATIVE_METHOD(SSL_ECH_KEYS_new, "()J"),
12393+
CONSCRYPT_NATIVE_METHOD(SSL_ECH_KEYS_up_ref, "(J)V"),
12394+
CONSCRYPT_NATIVE_METHOD(SSL_ECH_KEYS_free, "(J)V"),
12395+
CONSCRYPT_NATIVE_METHOD(SSL_ech_accepted, "(J" REF_SSL ")Z"),
12396+
CONSCRYPT_NATIVE_METHOD(SSL_CTX_ech_enable_server, "(J" REF_SSL_CTX "[B[B)Z"),
12397+
1217512398
// Used for testing only.
1217612399
CONSCRYPT_NATIVE_METHOD(BIO_read, "(J[B)I"),
1217712400
CONSCRYPT_NATIVE_METHOD(BIO_write, "(J[BII)V"),

common/src/main/java/org/conscrypt/NativeCrypto.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1623,6 +1623,32 @@ static native byte[] Scrypt_generate_key(
16231623
*/
16241624
static native boolean usesBoringSsl_FIPS_mode();
16251625

1626+
/* ECH */
1627+
1628+
static native void SSL_set_enable_ech_grease(long ssl, NativeSsl ssl_holder, boolean enable);
1629+
1630+
static native boolean SSL_set1_ech_config_list(
1631+
long ssl, NativeSsl ssl_holder, byte[] echConfig);
1632+
1633+
static native String SSL_get0_ech_name_override(long ssl, NativeSsl ssl_holder);
1634+
1635+
static native byte[] SSL_get0_ech_retry_configs(long ssl, NativeSsl ssl_holder);
1636+
1637+
static native byte[] SSL_marshal_ech_config(short configId, byte[] key, String publicName);
1638+
1639+
static native long SSL_ECH_KEYS_new();
1640+
1641+
static native void SSL_ECH_KEYS_up_ref(long sslEchKeys);
1642+
1643+
static native void SSL_ECH_KEYS_free(long sslEchKeys);
1644+
1645+
static native byte[] SSL_ECH_KEYS_marshal_retry_configs(byte[] key);
1646+
1647+
static native boolean SSL_ech_accepted(long ssl, NativeSsl ssl_holder);
1648+
1649+
static native boolean SSL_CTX_ech_enable_server(
1650+
long sslCtx, AbstractSessionContext holder, byte[] key, byte[] config);
1651+
16261652
/**
16271653
* Used for testing only.
16281654
*/

openjdk/build.gradle

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,15 @@ def testInterop = tasks.register("testInterop", Test) {
350350
}
351351
check.dependsOn testInterop
352352

353+
// Added to see results of new ECH tests when running tests from the command line
354+
tasks.withType(Test).configureEach {
355+
testLogging {
356+
exceptionFormat "full"
357+
events "started", "skipped", "passed", "failed"
358+
showStandardStreams true
359+
}
360+
}
361+
353362
jacocoTestReport {
354363
additionalSourceDirs.from files("$rootDir/openjdk/src/test/java", "$rootDir/common/src/main/java")
355364
executionData tasks.withType(Test)

0 commit comments

Comments
 (0)