Skip to content
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
Initial draft of java layer of ECH support for Conscrypt.
  • Loading branch information
mnbogner committed Oct 17, 2025
commit 7e8bac9bc9a048674f86159b49c60e4732ab7069
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,14 @@ public abstract SSLEngineResult wrap(
@SuppressWarnings("MissingOverride") // For compiling pre Java 9.
public abstract String getHandshakeApplicationProtocol();

public abstract void setEchParameters(EchParameters parameters);

public abstract EchParameters getEchParameters();

public abstract String getEchNameOverride();

public abstract boolean echAccepted();

/**
* Sets an application-provided ALPN protocol selector. If provided, this will override
* the list of protocols set by {@link #setApplicationProtocols(String[])}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -738,4 +738,12 @@ private boolean isDelegating() {
*/
abstract byte[] exportKeyingMaterial(String label, byte[] context, int length)
throws SSLException;

public abstract void setEchParameters(EchParameters parameters);

public abstract EchParameters getEchParameters();

public abstract String getEchNameOverride();

public abstract boolean echAccepted();
}
185 changes: 185 additions & 0 deletions common/src/main/java/org/conscrypt/Conscrypt.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@
*/
package org.conscrypt;

import org.conscrypt.com.android.net.module.util.DnsPacket;
import org.conscrypt.io.IoUtils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
Expand Down Expand Up @@ -533,6 +535,47 @@ public static byte[] exportKeyingMaterial(
return toConscrypt(socket).exportKeyingMaterial(label, context, length);
}

/**
* Casts a socket to a Conscrypt socket if possible, and sets the parameters
* required to configure ECH for that socket.
* Throws an IllegalArgumentException if the socket is not a ConscryptSocket
* @param socket the socket (instance of ConscryptSocket)
* @param parameters parameters required to configure ECH
*/
public static void setEchParameters(SSLSocket socket, EchParameters parameters) {
toConscrypt(socket).setEchParameters(parameters);
}

/**
* Casts a socket to a Conscrypt socket if possible, and returns the parameters
* used to configure ECH for that socket.
* Throws an IllegalArgumentException if the socket is not a ConscryptSocket
* @param socket the socket (instance of ConscryptSocket)
*/
public static EchParameters getEchParameters(SSLSocket socket) {
return toConscrypt(socket).getEchParameters();
}

/**
* Casts a socket to a Conscrypt socket if possible, and returns the string used
* to replace the hostname as the public name.
* Throws an IllegalArgumentException if the socket is not a ConscryptSocket
* @param socket the socket (instance of ConscryptSocket)
*/
public static String getEchNameOverride(SSLSocket socket) {
return toConscrypt(socket).getEchNameOverride();
}

/**
* Casts a socket to a Conscrypt socket if possible, and returns whether the native
* SSL/crypto implementation detects that the connection supports ECH.
* Throws an IllegalArgumentException if the socket is not a ConscryptSocket
* @param socket the socket (instance of ConscryptSocket)
*/
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 @@ -791,6 +834,148 @@ public static byte[] exportKeyingMaterial(
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 setEchParameters(SSLEngine engine, EchParameters parameters) {
toConscrypt(engine).setEchParameters(parameters);
}

public static EchParameters getEchParameters(SSLEngine engine) {
return toConscrypt(engine).getEchParameters();
}

public static String getEchNameOverride(SSLEngine engine) {
return toConscrypt(engine).getEchNameOverride();
}

public static boolean echAccepted(SSLEngine engine) {
return toConscrypt(engine).echAccepted();
}


/**
* < Max RR value size, as given to API
*/
private static int ECH_MAX_RRVALUE_LEN = 2000;
/**
* < Max for an ECHConfig extension
*/
private static int ECH_MAX_ECHCONFIGEXT_LEN = 100;
/**
* < just for a sanity check
*/
private static int ECH_MIN_ECHCONFIG_LEN = 32;
/**
* < for a sanity check
*/
private static int ECH_MAX_ECHCONFIG_LEN = ECH_MAX_RRVALUE_LEN;

/**
* One or more catenated binary ECHConfigs
*/
public static int ECH_FMT_BIN = 1;
/**
* < presentation form of HTTPSSVC
*/
public static int ECH_FMT_HTTPSSVC = 4;
/**
* the wire-format code for ECH within an SVCB or HTTPS RData
*/
private static int ECH_PCODE_ECH = 0x0005;

/**
* Decode SVCB/HTTPS RR value provided as binary or ascii-hex.
* <p>
* The rrval may be the catenation of multiple encoded ECHConfigs.
* We internally try decode and handle those and (later)
* use whichever is relevant/best.
* <p>
* Note that we "succeed" even if there is no ECHConfigs in the input - some
* callers might download the RR from DNS and pass it here without looking
* inside, and there are valid uses of such RRs. The caller can check though
* using the num_echs output.
*
* @param rrval is the binary encoded RData
* @return is 1 for success, error otherwise
*/
public static byte[] getEchConfigListFromDnsRR(byte[] rrval) {
int rv = 0;
int binlen = 0; /* the RData */
byte[] binbuf = null;
int pos = 0;
int remaining = rrval.length;;
String dnsname = null;
int plen = 0;
boolean done = false;

/*
* skip 2 octet priority and TargetName as those are the
* application's responsibility, not the library's
*/
if (remaining <= 2) {
return null;
}
pos += 2;
remaining -= 2;
pos++;
int clen = DnsPacket.byteToUnsignedInt(rrval[pos]);
ByteArrayOutputStream thename = new ByteArrayOutputStream();
if (clen == 0) {
// special case - return "." as name
thename.write('.');
rv = 1;
}
while (clen != 0) {
if (clen > remaining) {
rv = 1;
break;
}
for (int i =pos; i < clen; i++) {
thename.write(DnsPacket.byteToUnsignedInt(rrval[pos + i]));
}
thename.write('.');
pos += clen;
remaining -= clen + 1;
clen = DnsPacket.byteToUnsignedInt(rrval[pos]);
}
if (rv != 1) {
return null;
}

int echStart = 0;
while (!done && remaining >= 4) {
int pcode = (rrval[pos] << 8) + rrval[pos + 1];
pos += 2;
plen = (rrval[pos] << 8) + rrval[pos + 1];
pos += 2;
remaining -= 4;
if (pcode == ECH_PCODE_ECH) {
echStart = pos;
done = true;
}
if (plen != 0 && plen <= remaining) {
pos += plen;
remaining -= plen;
}
}
if (!done) {
return null;
}
if (plen <=0) {
return null;
}
byte[] ret = new byte[plen];
System.arraycopy(rrval, echStart, ret, 0, plen);
return ret;
}

/**
* Indicates whether the given {@link TrustManager} was created by this distribution of
* Conscrypt.
Expand Down
18 changes: 18 additions & 0 deletions common/src/main/java/org/conscrypt/ConscryptEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,24 @@ public int getPeerPort() {
return peerInfoProvider.getPort();
}

public void setEchParameters(EchParameters parameters) {
sslParameters.setEchParameters(parameters);
}

public EchParameters getEchParameters() {
return sslParameters.getEchParameters();
}

@Override
public String getEchNameOverride() {
return ssl.getEchNameOverride();
}

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

@Override
public void beginHandshake() throws SSLException {
synchronized (ssl) {
Expand Down
20 changes: 20 additions & 0 deletions common/src/main/java/org/conscrypt/ConscryptEngineSocket.java
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,26 @@ byte[] exportKeyingMaterial(String label, byte[] context, int length) throws SSL
return engine.exportKeyingMaterial(label, context, length);
}

@Override
public void setEchParameters(EchParameters parameters) {
engine.setEchParameters(parameters);
}

@Override
public EchParameters getEchParameters() {
return engine.getEchParameters();
}

@Override
public String getEchNameOverride() {
return engine.getEchNameOverride();
}

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

@Override
public final boolean getUseClientMode() {
return engine.getUseClientMode();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -895,6 +895,27 @@ byte[] exportKeyingMaterial(String label, byte[] context, int length) throws SSL
return ssl.exportKeyingMaterial(label, context, length);
}


@Override
public void setEchParameters(EchParameters parameters) {
sslParameters.setEchParameters(parameters);
}

@Override
public EchParameters getEchParameters() {
return sslParameters.getEchParameters();
}

@Override
public String getEchNameOverride() {
return ssl.getEchNameOverride();
}

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

@Override
public final boolean getUseClientMode() {
return sslParameters.getUseClientMode();
Expand Down
29 changes: 29 additions & 0 deletions common/src/main/java/org/conscrypt/EchParameters.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.conscrypt;

public class EchParameters {

public boolean useEchGrease;

public byte[] configList;

public EchParameters() {
this.useEchGrease = false;
this.configList = null;
}

public EchParameters(boolean useEchGrease) {
this.useEchGrease = useEchGrease;
this.configList = null;
}

public EchParameters(byte[] configList) {
this.useEchGrease = false;
this.configList = configList;
}

public EchParameters(boolean useEchGrease, byte[] configList) {
this.useEchGrease = useEchGrease;
this.configList = configList;
}

}
17 changes: 17 additions & 0 deletions common/src/main/java/org/conscrypt/EchRejectedException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.conscrypt;

import javax.net.ssl.SSLHandshakeException;

/**
* The server rejected the ECH Config List, and might have supplied an ECH
* Retry Config.
*
* @see NativeCrypto#SSL_get0_ech_retry_configs(long, NativeSsl)
*/
public class EchRejectedException extends SSLHandshakeException {
private static final long serialVersionUID = 98723498273473923L;

EchRejectedException(String message) {
super(message);
}
}
18 changes: 18 additions & 0 deletions common/src/main/java/org/conscrypt/Java8EngineWrapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,24 @@ public int getPeerPort() {
return delegate.getPeerPort();
}

public void setEchParameters(EchParameters parameters) {
delegate.setEchParameters(parameters);
}

public EchParameters getEchParameters() {
return delegate.getEchParameters();
}

@Override
public String getEchNameOverride() {
return delegate.getEchNameOverride();
}

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

@Override
public void beginHandshake() throws SSLException {
delegate.beginHandshake();
Expand Down
Loading