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
76 changes: 62 additions & 14 deletions .github/scripts/openssl-ech.sh
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,13 @@ if [ "$FORCE_HRR" -ne 0 ] && [ -n "$PQC" ]; then
exit 1
fi

# corrupt_ech_config() flips the first public-key byte, this is valid only for
# the default X25519 KEM so consider --suite and --reject mutually exclusive
if [ "$REJECT" -ne 0 ] && [ -n "$SUITE" ]; then
echo "ERROR: --reject only supports the default X25519 suite"
exit 1
fi

# Pick exactly one test variant. The variant decides which -groups go to
# each side and any extra flags needed to drive the desired handshake.
# default - both sides use secp256r1 (no HRR)
Expand All @@ -89,9 +96,6 @@ WOLFSSL_CLIENT=${WOLFSSL_CLIENT:-"$WORKSPACE/examples/client/client"}
WOLFSSL_SERVER=${WOLFSSL_SERVER:-"$WORKSPACE/examples/server/server"}
CERT_DIR=${CERT_DIR:-"$WORKSPACE/certs"}

# correct ECH config, but it's old, ECH will be rejected
REJECT_ECH_CONFIG="AD7+DQA6rAAgACCATZdDlHed6GlDeiYsu3r7sdWUkLVHZuTa3lbOf+hIbAAEAAEAAQALZXhhbXBsZS5jb20AAA=="

TMP_LOG="$WORKSPACE/tmp_file.log"
# Will need to look into validating the name against the cert for the OSSL cli.
# This is fine, but should be upgraded to use a second cert in the future.
Expand All @@ -101,13 +105,35 @@ PRIV_NAME="ech-private-name.com"
PUB_NAME="example.com"
MAX_WAIT=50

# --------------------------------------------------------------------------
# Flip a bit in the HPKE public key of the config the server published. The
# client will offer ECH, but the server can't decrypt so ECH is rejected.
# --------------------------------------------------------------------------
corrupt_ech_config() {
local config="$1"
local bytes=()
local b

mapfile -t bytes < <(printf '%s' "$config" | base64 -d | od -An -tx1 -v \
| tr -s ' ' '\n' | grep -v '^$')

# list len (2) + version (2) + config len (2) + config_id (1) +
# kem_id (2) + public key len (2), so byte 11 is the first key byte
bytes[11]=$(printf '%02x' $(( 0x${bytes[11]} ^ 0x01 )))

for b in "${bytes[@]}"; do
printf "\\x$b"
done | base64 -w 0
}

# --------------------------------------------------------------------------
# server mode -- OpenSSL is the server, wolfSSL is the client
# --------------------------------------------------------------------------
openssl_server(){
local ech_file="$WORKSPACE/ech_config.pem"
local ech_config=""
local port=""
local server_pid=""

# Per-variant args.
# openssl_groups : -groups passed to OpenSSL s_server
Expand Down Expand Up @@ -140,13 +166,17 @@ openssl_server(){

# parse ECH config from file
ech_config=$(sed -n '/BEGIN ECHCONFIG/,/END ECHCONFIG/{/BEGIN ECHCONFIG\|END ECHCONFIG/d;p}' "$ech_file" | tr -d '\n')
# reject overrides the config the client connects with
[ "$REJECT" -ne 0 ] && ech_config="$REJECT_ECH_CONFIG"
echo "parsed ech config: $ech_config" &>> "$TMP_LOG"

# reject: corrupt the config so the server can't decrypt the client's ECH
if [ "$REJECT" -ne 0 ]; then
ech_config=$(corrupt_ech_config "$ech_config")
echo "bad ech config : $ech_config" &>> "$TMP_LOG"
fi

# start OpenSSL ECH server with ephemeral port; line-buffer so the
# log can be grepped
stdbuf -oL $OPENSSL s_server \
timeout 30 stdbuf -oL $OPENSSL s_server \
-tls1_3 \
-cert "$CERT_DIR/server-cert.pem" \
-key "$CERT_DIR/server-key.pem" \
Expand All @@ -158,6 +188,7 @@ openssl_server(){
-accept 0 \
-naccept 1 \
&>> "$TMP_LOG" <<< "wolfssl!" &
server_pid=$!

# wait for server port to be ready and capture it
counter=0
Expand All @@ -184,11 +215,16 @@ openssl_server(){
$wolfssl_extra \
&>> "$TMP_LOG" || [ "$REJECT" -ne 0 ]

# let s_server finish writing its ech_success= line before grepping;
# on reject it sees a fatal alert, so tolerate a nonzero exit
wait "$server_pid" || [ "$REJECT" -ne 0 ]

if [ "$REJECT" -ne 0 ]; then
grep -q "ECH offered but rejected by server" "$TMP_LOG" && \
grep -q "ech_success=0" "$TMP_LOG"
grep -q "ech_success=0" "$TMP_LOG" && \
grep -q "ECH status: rejected" "$TMP_LOG"
else
grep -q "ech_success=1" "$TMP_LOG"
grep -q "ech_success=1" "$TMP_LOG" && \
grep -q "ECH status: accepted" "$TMP_LOG"
fi
}

Expand All @@ -199,6 +235,7 @@ openssl_client(){
local ready_file="$WORKSPACE/wolfssl_tls13_ready$$"
local ech_config=""
local port=0
local server_pid=""

# Per-variant args.
# openssl_groups : -groups passed to OpenSSL s_client
Expand Down Expand Up @@ -231,7 +268,7 @@ openssl_client(){

# start server with ephemeral port + ready file; line-buffer so the
# log can be grepped
stdbuf -oL $WOLFSSL_SERVER \
timeout 30 stdbuf -oL $WOLFSSL_SERVER \
-v 4 \
-R "$ready_file" \
-p "$port" \
Expand All @@ -240,6 +277,7 @@ openssl_client(){
$wolfssl_suite \
$wolfssl_extra \
&>> "$TMP_LOG" &
server_pid=$!

# wait for server to be ready, then get port
counter=0
Expand Down Expand Up @@ -267,10 +305,14 @@ openssl_client(){
exit 1
fi
done
# reject overrides the config the client connects with
[ "$REJECT" -ne 0 ] && ech_config="$REJECT_ECH_CONFIG"
echo "parsed ech config: $ech_config" &>> "$TMP_LOG"

# reject: corrupt the config so the server can't decrypt the client's ECH
if [ "$REJECT" -ne 0 ]; then
ech_config=$(corrupt_ech_config "$ech_config")
echo "bad ech config : $ech_config" &>> "$TMP_LOG"
fi

# test with OpenSSL s_client using ECH
# in reject mode the s_client is expected to error out, so tolerate a
# nonzero exit
Expand All @@ -285,10 +327,16 @@ openssl_client(){
$openssl_groups \
&>> "$TMP_LOG" || [ "$REJECT" -ne 0 ]

# let the wolfSSL server finish writing its ECH status line before
# grepping; on reject it errors out, so tolerate a nonzero exit
wait "$server_pid" || [ "$REJECT" -ne 0 ]

if [ "$REJECT" -ne 0 ]; then
grep -q "ECH: Got 1 retry-configs" "$TMP_LOG"
grep -q "ECH: Got 1 retry-configs" "$TMP_LOG" && \
grep -q "ECH status: rejected" "$TMP_LOG"
else
grep -q "ECH: success: 1" "$TMP_LOG"
grep -q "ECH: success: 1" "$TMP_LOG" && \
grep -q "ECH status: accepted" "$TMP_LOG"
fi
}

Expand Down
6 changes: 5 additions & 1 deletion examples/client/client.c
Original file line number Diff line number Diff line change
Expand Up @@ -1869,7 +1869,6 @@ static void showPeerPEM(WOLFSSL* ssl)
(void)ssl;
}


static void Usage(void)
{
int msgid = 0;
Expand Down Expand Up @@ -4418,6 +4417,11 @@ THREAD_RETURN WOLFSSL_THREAD client_test(void* args)
timeoutConnect.tv_sec = DEFAULT_TIMEOUT_SEC;
timeoutConnect.tv_usec = 0;
ret = NonBlockingSSL_Connect(ssl); /* will keep retrying on timeout */
#endif
#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH)
/* print before ret is checked: ECH status is always significant */
if (echConfigs64 != NULL)
PrintEchStatus(ssl);
#endif
if (ret != WOLFSSL_SUCCESS) {
err = wolfSSL_get_error(ssl, 0);
Expand Down
5 changes: 5 additions & 0 deletions examples/server/server.c
Original file line number Diff line number Diff line change
Expand Up @@ -3804,6 +3804,11 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args)
#endif
#ifdef WOLFSSL_EARLY_DATA
EarlyDataStatus(ssl);
#endif
#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH)
/* print before ret is checked: ECH status is always significant */
if (echPublicName != NULL)
PrintEchStatus(ssl);
#endif
if (ret != WOLFSSL_SUCCESS) {
err = wolfSSL_get_error(ssl, ret);
Expand Down
44 changes: 18 additions & 26 deletions src/tls.c
Original file line number Diff line number Diff line change
Expand Up @@ -2736,8 +2736,8 @@ int TLSX_UseSNI(TLSX** extensions, byte type, const void* data, word16 size,
/* client-side needs this function when ECH is enabled */
#if !defined(NO_WOLFSSL_SERVER) || defined(HAVE_ECH)
/** Tells the SNI requested by the client. */
word16 TLSX_SNI_GetRequest(TLSX* extensions, byte type, void** data,
byte ignoreStatus)
WOLFSSL_TEST_VIS word16 TLSX_SNI_GetRequest(TLSX* extensions, byte type,
void** data, byte ignoreStatus)
{
TLSX* extension = TLSX_Find(extensions, TLSX_SERVER_NAME);
SNI* sni = TLSX_SNI_Find(extension ? (SNI*)extension->data : NULL, type);
Expand Down Expand Up @@ -15179,18 +15179,18 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size,

/* get extension */
echX = TLSX_Find(ssl->extensions, TLSX_ECH);
if (echX == NULL)
if (echX == NULL || echX->data == NULL)
return BAD_FUNC_ARG;
ech = (WOLFSSL_ECH*)echX->data;

ech->confBuf = (byte*)readBuf;
}
else if (msgType == client_hello && ssl->ctx->echConfigs != NULL) {
/* get extension */
echX = TLSX_Find(ssl->extensions, TLSX_ECH);
if (echX == NULL)
return BAD_FUNC_ARG;
else if (msgType == client_hello &&
(echX = TLSX_Find(ssl->extensions, TLSX_ECH)) != NULL &&
echX->data != NULL &&
((WOLFSSL_ECH*)echX->data)->echConfig != NULL) {
ech = (WOLFSSL_ECH*)echX->data;
TLSX_SetResponse(ssl, TLSX_ECH);

/* if the first ECH was rejected or CH1 did not have ECH then there is
* no need to decrypt this one */
Expand All @@ -15215,6 +15215,7 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size,
/* MUST process INNER in inner hello and OUTER in outer hello */
return INVALID_PARAMETER;
}

/* Must have kdfId, aeadId, configId, enc len and payload len. */
if (size < offset + 2 + 2 + 1 + 2 + 2) {
return BUFFER_ERROR;
Expand Down Expand Up @@ -15316,7 +15317,7 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size,
return MEMORY_E;
}
/* try to decrypt with matching configId */
echConfig = ssl->ctx->echConfigs;
echConfig = ech->echConfig;
while (echConfig != NULL) {
if (echConfig->configId == ech->configId) {
ret = TLSX_ExtractEch(ssl, ech, echConfig, aadCopy,
Expand All @@ -15328,7 +15329,7 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size,
}
/* otherwise, try to decrypt with all configs (trial decryption) */
if (echConfig == NULL && ssl->options.enableEchTrialDecrypt) {
echConfig = ssl->ctx->echConfigs;
echConfig = ech->echConfig;
while (echConfig != NULL) {
if (echConfig->configId != ech->configId) {
ret = TLSX_ExtractEch(ssl, ech, echConfig, aadCopy,
Expand Down Expand Up @@ -15368,7 +15369,7 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size,
ret = TLSX_ECH_CheckInnerPadding(ssl, ech);
if (ret == 0) {
/* expand EchOuterExtensions if present.
* Also, if it exists, copy sessionID from outer hello */
* Also, if it exists, copy sessionID from outer hello */
ret = TLSX_ECH_ExpandOuterExtensions(ssl, ech, ssl->heap);
}
}
Expand Down Expand Up @@ -15399,7 +15400,6 @@ static void TLSX_ECH_Free(WOLFSSL_ECH* ech, void* heap)
ForceZero(ech->hpkeContext, sizeof(HpkeBaseContext));
XFREE(ech->hpkeContext, heap, DYNAMIC_TYPE_TMP_BUFFER);
}

XFREE(ech, heap, DYNAMIC_TYPE_TMP_BUFFER);
(void)heap;
}
Expand Down Expand Up @@ -17138,22 +17138,18 @@ int TLSX_PopulateExtensions(WOLFSSL* ssl, byte isServer)
ret = GREASE_ECH_USE(&(ssl->extensions), ssl->heap,
ssl->rng);
}
else if (ssl->echConfigs != NULL) {
else {
ret = ECH_USE(ssl->echConfigs, &(ssl->extensions),
ssl->heap, ssl->rng);
}
}
#endif
}
#if defined(HAVE_ECH)
else if (IsAtLeastTLSv1_3(ssl->version)) {
if (ssl->ctx->echConfigs != NULL && !ssl->options.disableECH) {
ret = SERVER_ECH_USE(&(ssl->extensions), ssl->heap,
ssl->ctx->echConfigs);

if (ret == 0)
TLSX_SetResponse(ssl, TLSX_ECH);
}
else if (IsAtLeastTLSv1_3(ssl->version) && !ssl->options.disableECH &&
ssl->ctx->echConfigs != NULL) {
ret = SERVER_ECH_USE(&(ssl->extensions), ssl->heap,
ssl->ctx->echConfigs);
}
#endif

Expand Down Expand Up @@ -19707,8 +19703,7 @@ WOLFSSL_TEST_VIS int TLSX_Parse(WOLFSSL* ssl, const byte* input, word16 length,
/* Reconcile ECH inner/outer extensions before verifying SNI so the verify
* pass sees the authoritative list */
if (ret == 0 && msgType == client_hello && isRequest &&
!ssl->options.echProcessingInner &&
ssl->ctx->echConfigs != NULL && !ssl->options.disableECH) {
!ssl->options.disableECH && !ssl->options.echProcessingInner) {
TLSX* echX = TLSX_Find(ssl->extensions, TLSX_ECH);
WOLFSSL_ECH* ech = NULL;
if (echX != NULL)
Expand Down Expand Up @@ -19736,9 +19731,6 @@ WOLFSSL_TEST_VIS int TLSX_Parse(WOLFSSL* ssl, const byte* input, word16 length,
ech->state == ECH_WRITE_RETRY_CONFIGS) {
ret = TLSX_EchReplaceExtensions(ssl,
ssl->options.echAccepted);
if (ret == 0 && ech->state == ECH_WRITE_NONE) {
echX->resp = 0;
}
}
}
}
Expand Down
Loading
Loading