Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic TLS Encrypted ClientHello (ECH) support #1044

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions common/src/jni/main/cpp/conscrypt/native_crypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/hpke.h>
#include <openssl/pkcs7.h>
#include <openssl/pkcs8.h>
#include <openssl/rand.h>
Expand Down Expand Up @@ -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<SSL_ECH_KEYS*>(static_cast<uintptr_t>(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 <typename T>
static T* fromContextObject(JNIEnv* env, jobject contextObject) {
if (contextObject == nullptr) {
Expand Down Expand Up @@ -10429,6 +10439,140 @@ static jlong NativeCrypto_SSL_get1_session(JNIEnv* env, jclass, jlong ssl_addres
return reinterpret_cast<uintptr_t>(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<const uint8_t*>(configBytes.get());
int ret = SSL_set1_ech_config_list(ssl, reinterpret_cast<const uint8_t*>(configBytes.get()),
configBytes.size());
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);

jbyteArray result = env->NewByteArray(static_cast<jsize>(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<jsize>(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);
*/
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<const uint8_t*>(keyBytes.get());
size_t ech_key_size = keyBytes.size();
const uint8_t* ech_config = reinterpret_cast<const uint8_t*>(configBytes.get());
size_t ech_config_size = configBytes.size();
bssl::UniquePtr<SSL_ECH_KEYS> 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) \
Expand Down Expand Up @@ -10749,6 +10893,12 @@ 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_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"),

// Used for testing only.
CONSCRYPT_NATIVE_METHOD(BIO_read, "(J[B)I"),
Expand Down
14 changes: 14 additions & 0 deletions common/src/main/java/org/conscrypt/AbstractConscryptEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,20 @@ 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 String getEchNameOverride();

public abstract byte[] getEchRetryConfigList();

public abstract boolean echAccepted();

/* @Override */
@SuppressWarnings("MissingOverride") // For compilation with Java 6.
public final SSLSession getHandshakeSession() {
Expand Down
12 changes: 12 additions & 0 deletions common/src/main/java/org/conscrypt/AbstractConscryptSocket.java
Original file line number Diff line number Diff line change
Expand Up @@ -754,4 +754,16 @@ 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 String getEchNameOverride();

public abstract byte[] getEchRetryConfigList();

public abstract boolean echAccepted();
}
61 changes: 61 additions & 0 deletions common/src/main/java/org/conscrypt/Conscrypt.java
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,35 @@ 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 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();
}

/**
* Indicates whether the given {@link SSLEngine} was created by this distribution of Conscrypt.
*/
Expand Down Expand Up @@ -737,6 +766,38 @@ 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 <a href="https://www.ietf.org/archive/id/draft-ietf-tls-esni-13.html#section-6.2">TLS Encrypted Client Hello 6.2. GREASE ECH</a>
*/
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 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();
}

/**
* Indicates whether the given {@link TrustManager} was created by this distribution of
* Conscrypt.
Expand Down
35 changes: 35 additions & 0 deletions common/src/main/java/org/conscrypt/ConscryptEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,41 @@ 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 String getEchNameOverride() {
return ssl.getEchNameOverride();
}

@Override
public byte[] getEchRetryConfigList() {
return ssl.getEchRetryConfigList();
}

@Override
public boolean echAccepted() {
return ssl.echAccepted();
}

@Override
public void beginHandshake() throws SSLException {
synchronized (ssl) {
Expand Down
30 changes: 30 additions & 0 deletions common/src/main/java/org/conscrypt/ConscryptEngineSocket.java
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,36 @@ 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 String getEchNameOverride() {
return engine.getEchNameOverride();
}

@Override
public byte[] getEchRetryConfigList() {
return engine.getEchRetryConfigList();
}

@Override
public boolean echAccepted() {
return engine.echAccepted();
}

@Override
public final boolean getUseClientMode() {
return engine.getUseClientMode();
Expand Down
Loading