diff --git a/src/internal.c b/src/internal.c index 75c007c6572..f39fbeb588c 100644 --- a/src/internal.c +++ b/src/internal.c @@ -535,6 +535,12 @@ void wolfssl_priv_der_unblind_free(DerBuffer* key) #define SSC_TLS13_EES "EARLY_EXPORTER_SECRET" /* Label string for exporter secret. */ #define SSC_TLS13_ES "EXPORTER_SECRET" +#ifdef HAVE_ECH + /* Label string for ECH KEM shared secret. */ + #define SSC_TLS13_ECH_S "ECH_SECRET" + /* Label string for ECHConfig used to construct ECH. */ + #define SSC_TLS13_ECH_C "ECH_CONFIG" +#endif /* * This function builds up string for key-logging then call user's @@ -594,6 +600,18 @@ void wolfssl_priv_der_unblind_free(DerBuffer* key) label = SSC_TLS13_ES; break; +#ifdef HAVE_ECH + case ECH_SECRET: + labelSz = sizeof(SSC_TLS13_ECH_S); + label = SSC_TLS13_ECH_S; + break; + + case ECH_CONFIG: + labelSz = sizeof(SSC_TLS13_ECH_C); + label = SSC_TLS13_ECH_C; + break; +#endif + default: return BAD_FUNC_ARG; } diff --git a/src/tls.c b/src/tls.c index 62118d0678b..5f5ed8eecc5 100644 --- a/src/tls.c +++ b/src/tls.c @@ -13952,6 +13952,42 @@ static int TLSX_ECH_GetSize(WOLFSSL_ECH* ech, byte msgType) return (int)size; } +#ifdef HAVE_SECRET_CALLBACK +/* log ECH_SECRET and ECH_CONFIG + * returns 0 on success, TLS13_SECRET_CB_E otherwise */ +static int EchWriteKeyLog(WOLFSSL* ssl, const byte* secret, word32 secretSz, + const byte* config, word32 configSz) +{ + int ret = 0; + if (ssl->tls13SecretCb != NULL) { + ret = ssl->tls13SecretCb(ssl, ECH_SECRET, secret, (int)secretSz, + ssl->tls13SecretCtx); + if (ret == 0) { + ret = ssl->tls13SecretCb(ssl, ECH_CONFIG, config, (int)configSz, + ssl->tls13SecretCtx); + } + if (ret != 0) { + WOLFSSL_ERROR_VERBOSE(TLS13_SECRET_CB_E); + ret = TLS13_SECRET_CB_E; + } + } +#ifdef OPENSSL_EXTRA + if (ret == 0 && ssl->tls13KeyLogCb != NULL) { + ret = ssl->tls13KeyLogCb(ssl, ECH_SECRET, secret, (int)secretSz, NULL); + if (ret == 0) { + ret = ssl->tls13KeyLogCb(ssl, ECH_CONFIG, config, (int)configSz, + NULL); + } + if (ret != 0) { + WOLFSSL_ERROR_VERBOSE(TLS13_SECRET_CB_E); + ret = TLS13_SECRET_CB_E; + } + } +#endif /* OPENSSL_EXTRA */ + return ret; +} +#endif /* HAVE_SECRET_CALLBACK */ + /* rough check that inner hello fields do not exceed length of decrypted * information. Additionally, this function will check that all padding bytes * are zero and decrease the innerHelloLen accordingly if so. @@ -14361,8 +14397,8 @@ static int TLSX_ECH_ExpandOuterExtensions(WOLFSSL* ssl, WOLFSSL_ECH* ech, /* return status after attempting to open the hpke encrypted ech extension, if * successful the inner client hello will be stored in * ech->innerClientHelloLen */ -static int TLSX_ExtractEch(WOLFSSL_ECH* ech, WOLFSSL_EchConfig* echConfig, - byte* aad, word32 aadLen, void* heap) +static int TLSX_ExtractEch(WOLFSSL* ssl, WOLFSSL_ECH* ech, + WOLFSSL_EchConfig* echConfig, byte* aad, word32 aadLen) { int ret = 0; int i; @@ -14370,7 +14406,7 @@ static int TLSX_ExtractEch(WOLFSSL_ECH* ech, WOLFSSL_EchConfig* echConfig, word32 rawConfigLen = 0; byte* info = NULL; word32 infoLen = 0; - if (ech == NULL || echConfig == NULL || aad == NULL) + if (ssl == NULL || ech == NULL || echConfig == NULL || aad == NULL) return BAD_FUNC_ARG; /* verify the kem and key len */ if (wc_HpkeKemGetEncLen(echConfig->kemId) != ech->encLen) @@ -14388,13 +14424,14 @@ static int TLSX_ExtractEch(WOLFSSL_ECH* ech, WOLFSSL_EchConfig* echConfig, /* check if hpke already exists, may if HelloRetryRequest */ if (ech->hpke == NULL) { allocatedHpke = 1; - ech->hpke = (Hpke*)XMALLOC(sizeof(Hpke), heap, DYNAMIC_TYPE_TMP_BUFFER); + ech->hpke = (Hpke*)XMALLOC(sizeof(Hpke), ssl->heap, + DYNAMIC_TYPE_TMP_BUFFER); if (ech->hpke == NULL) ret = MEMORY_E; /* init the hpke struct */ if (ret == 0) { ret = wc_HpkeInit(ech->hpke, echConfig->kemId, - ech->cipherSuite.kdfId, ech->cipherSuite.aeadId, heap); + ech->cipherSuite.kdfId, ech->cipherSuite.aeadId, ssl->heap); } if (ret == 0) { /* allocate hpkeContext */ @@ -14412,7 +14449,7 @@ static int TLSX_ExtractEch(WOLFSSL_ECH* ech, WOLFSSL_EchConfig* echConfig, /* create info */ if (ret == 0) { infoLen = TLS_INFO_CONST_STRING_SZ + 1 + rawConfigLen; - info = (byte*)XMALLOC(infoLen, heap, DYNAMIC_TYPE_TMP_BUFFER); + info = (byte*)XMALLOC(infoLen, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); if (info == NULL) ret = MEMORY_E; @@ -14423,6 +14460,16 @@ static int TLSX_ExtractEch(WOLFSSL_ECH* ech, WOLFSSL_EchConfig* echConfig, TLS_INFO_CONST_STRING_SZ + 1, &rawConfigLen); } } +#ifdef HAVE_SECRET_CALLBACK + /* allocate secret buffer for wc_HpkeInitOpenContext to copy into */ + if (ret == 0 && (ssl->tls13SecretCb != NULL +#ifdef OPENSSL_EXTRA + || ssl->tls13KeyLogCb != NULL +#endif + )) { + ret = wc_HpkeInitEchSecret(ech->hpke); + } +#endif /* HAVE_SECRET_CALLBACK */ /* init the context for opening */ if (ret == 0) { ret = wc_HpkeInitOpenContext(ech->hpke, ech->hpkeContext, @@ -14436,17 +14483,29 @@ static int TLSX_ExtractEch(WOLFSSL_ECH* ech, WOLFSSL_EchConfig* echConfig, ech->outerClientPayload, ech->innerClientHelloLen, ech->innerClientHello + HANDSHAKE_HEADER_SZ); } + +#ifdef HAVE_SECRET_CALLBACK + if (ret == 0 && ech->hpke->echSecret != NULL) { + ret = EchWriteKeyLog(ssl, ech->hpke->echSecret, ech->hpke->Nsecret, + info + TLS_INFO_CONST_STRING_SZ + 1, rawConfigLen); + } + wc_HpkeFreeEchSecret(ech->hpke); +#endif /* HAVE_SECRET_CALLBACK */ + /* only free hpke/hpkeContext if allocated in this call; otherwise preserve * them for clientHello2 */ if (ret != 0 && allocatedHpke) { - XFREE(ech->hpke, heap, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(ech->hpke, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); ech->hpke = NULL; - XFREE(ech->hpkeContext, heap, DYNAMIC_TYPE_TMP_BUFFER); - ech->hpkeContext = NULL; + if (ech->hpkeContext != NULL) { + ForceZero(ech->hpkeContext, sizeof(HpkeBaseContext)); + XFREE(ech->hpkeContext, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + ech->hpkeContext = NULL; + } } if (info != NULL) - XFREE(info, heap, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(info, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); return ret; } @@ -14650,9 +14709,9 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size, echConfig = ssl->ctx->echConfigs; while (echConfig != NULL) { if (echConfig->configId == ech->configId) { - ret = TLSX_ExtractEch(ech, echConfig, aadCopy, ech->aadLen, - ssl->heap); - if (ret == 0) + ret = TLSX_ExtractEch(ssl, ech, echConfig, aadCopy, + ech->aadLen); + if (ret == 0 || ret == WC_NO_ERR_TRACE(TLS13_SECRET_CB_E)) break; } echConfig = echConfig->next; @@ -14662,42 +14721,46 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size, echConfig = ssl->ctx->echConfigs; while (echConfig != NULL) { if (echConfig->configId != ech->configId) { - ret = TLSX_ExtractEch(ech, echConfig, aadCopy, ech->aadLen, - ssl->heap); - if (ret == 0) + ret = TLSX_ExtractEch(ssl, ech, echConfig, aadCopy, + ech->aadLen); + if (ret == 0 || ret == WC_NO_ERR_TRACE(TLS13_SECRET_CB_E)) break; } echConfig = echConfig->next; } } - /* if we failed to extract/expand */ - if (ret != 0 || echConfig == NULL) { - WOLFSSL_MSG("ECH rejected"); + /* TLS13_SECRET_CB_E isn't correlated with ECH acceptance so skip both + * paths */ + if (ret != WC_NO_ERR_TRACE(TLS13_SECRET_CB_E)) { + /* if we failed to extract/expand */ + if (ret != 0 || echConfig == NULL) { + WOLFSSL_MSG("ECH rejected"); - if (ssl->options.echAccepted == 1) { - /* on SH2 this is fatal */ - SendAlert(ssl, alert_fatal, decrypt_error); - WOLFSSL_ERROR_VERBOSE(DECRYPT_ERROR); - ret = DECRYPT_ERROR; + if (ssl->options.echAccepted == 0) { + /* on SH1 prepare to write retry configs */ + XFREE(ech->innerClientHello, ssl->heap, + DYNAMIC_TYPE_TMP_BUFFER); + ech->innerClientHello = NULL; + ech->state = ECH_WRITE_RETRY_CONFIGS; + ret = 0; + } + else { + /* on SH2 failure to decrypt is fatal */ + SendAlert(ssl, alert_fatal, decrypt_error); + WOLFSSL_ERROR_VERBOSE(DECRYPT_ERROR); + ret = DECRYPT_ERROR; + } } else { - /* on SH1 prepare to write retry configs */ - XFREE(ech->innerClientHello, ssl->heap, - DYNAMIC_TYPE_TMP_BUFFER); - ech->innerClientHello = NULL; - ech->state = ECH_WRITE_RETRY_CONFIGS; - ret = 0; - } - } - else { - WOLFSSL_MSG("ECH accepted"); - ssl->options.echAccepted = 1; + WOLFSSL_MSG("ECH accepted"); + ssl->options.echAccepted = 1; - ret = TLSX_ECH_CheckInnerPadding(ssl, ech); - if (ret == 0) { - /* expand EchOuterExtensions if present. - * Also, if it exists, copy sessionID from outer hello */ - ret = TLSX_ECH_ExpandOuterExtensions(ssl, ech, ssl->heap); + ret = TLSX_ECH_CheckInnerPadding(ssl, ech); + if (ret == 0) { + /* expand EchOuterExtensions if present. + * Also, if it exists, copy sessionID from outer hello */ + ret = TLSX_ECH_ExpandOuterExtensions(ssl, ech, ssl->heap); + } } } if (ret != 0) { @@ -14719,10 +14782,14 @@ static void TLSX_ECH_Free(WOLFSSL_ECH* ech, void* heap) if (ech->ephemeralKey != NULL) wc_HpkeFreeKey(ech->hpke, ech->hpke->kem, ech->ephemeralKey, ech->hpke->heap); + /* wc_HpkeFreeEchSecret is intentionally not here, free it in + * TLSX_ExtractEch / TLSX_FinalizeEch */ XFREE(ech->hpke, heap, DYNAMIC_TYPE_TMP_BUFFER); } - if (ech->hpkeContext != NULL) + if (ech->hpkeContext != NULL) { + ForceZero(ech->hpkeContext, sizeof(HpkeBaseContext)); XFREE(ech->hpkeContext, heap, DYNAMIC_TYPE_TMP_BUFFER); + } if (ech->privateName != NULL) XFREE((char*)ech->privateName, heap, DYNAMIC_TYPE_TMP_BUFFER); @@ -14732,13 +14799,15 @@ static void TLSX_ECH_Free(WOLFSSL_ECH* ech, void* heap) /* encrypt the client hello and store it in ech->outerClientPayload, return * status */ -int TLSX_FinalizeEch(WOLFSSL_ECH* ech, byte* aad, word32 aadLen) +int TLSX_FinalizeEch(WOLFSSL* ssl, WOLFSSL_ECH* ech, byte* aad, word32 aadLen) { int ret = 0; void* receiverPubkey = NULL; byte* info = NULL; int infoLen = 0; byte* aadCopy = NULL; + if (ssl == NULL || ech == NULL || aad == NULL) + return BAD_FUNC_ARG; /* setup hpke context to seal, should be done at most once per connection */ if (ech->hpkeContext == NULL) { /* import the server public key */ @@ -14766,6 +14835,18 @@ int TLSX_FinalizeEch(WOLFSSL_ECH* ech, byte* aad, word32 aadLen) TLS_INFO_CONST_STRING_SZ + 1); XMEMCPY(info + TLS_INFO_CONST_STRING_SZ + 1, ech->echConfig->raw, ech->echConfig->rawLen); + } +#ifdef HAVE_SECRET_CALLBACK + /* allocate secret buffer for wc_HpkeInitSealContext to copy into */ + if (ret == 0 && (ssl->tls13SecretCb != NULL +#ifdef OPENSSL_EXTRA + || ssl->tls13KeyLogCb != NULL +#endif + )) { + ret = wc_HpkeInitEchSecret(ech->hpke); + } +#endif /* HAVE_SECRET_CALLBACK */ + if (ret == 0) { /* init the context for seal with info and keys */ ret = wc_HpkeInitSealContext(ech->hpke, ech->hpkeContext, ech->ephemeralKey, receiverPubkey, info, infoLen); @@ -14786,6 +14867,15 @@ int TLSX_FinalizeEch(WOLFSSL_ECH* ech, byte* aad, word32 aadLen) aadLen, ech->innerClientHello, ech->innerClientHelloLen - ech->hpke->Nt, ech->outerClientPayload); } + +#ifdef HAVE_SECRET_CALLBACK + if (ret == 0 && ech->hpke->echSecret != NULL) { + ret = EchWriteKeyLog(ssl, ech->hpke->echSecret, ech->hpke->Nsecret, + ech->echConfig->raw, ech->echConfig->rawLen); + } + wc_HpkeFreeEchSecret(ech->hpke); +#endif /* HAVE_SECRET_CALLBACK */ + if (info != NULL) XFREE(info, ech->hpke->heap, DYNAMIC_TYPE_TMP_BUFFER); if (aadCopy != NULL) @@ -14804,7 +14894,7 @@ int TLSX_FinalizeEch(WOLFSSL_ECH* ech, byte* aad, word32 aadLen) #define ECH_PARSE TLSX_ECH_Parse #define ECH_FREE TLSX_ECH_Free -#endif +#endif /* WOLFSSL_TLS13 && HAVE_ECH */ /** Releases all extensions in the provided list. */ void TLSX_FreeAll(TLSX* list, void* heap) diff --git a/src/tls13.c b/src/tls13.c index 8d7efb9df40..85a7434899c 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -5097,7 +5097,7 @@ int SendTls13ClientHello(WOLFSSL* ssl) return ret; } #endif - ret = TLSX_FinalizeEch(args->ech, + ret = TLSX_FinalizeEch(ssl, args->ech, args->output + RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ, (word32)(args->sendSz - (RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ))); @@ -5879,7 +5879,8 @@ int DoTls13ServerHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, if (ret != 0) return ret; /* use the inner random for client random */ - if (args->extMsgType != hello_retry_request) { + if (args->extMsgType != hello_retry_request && + ssl->options.echAccepted) { XMEMCPY(ssl->arrays->clientRandom, ssl->arrays->clientRandomInner, RAN_LEN); } @@ -7452,7 +7453,8 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, /* From here on we are a TLS 1.3 ClientHello. */ - /* Client random */ + /* Client random + * ECH Accepted -> This will fill with the innerClientRandom */ XMEMCPY(ssl->arrays->clientRandom, input + args->idx, RAN_LEN); args->idx += RAN_LEN; @@ -13686,8 +13688,7 @@ int DoTls13HandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx, * Only on first inner ClientHello (before HRR), not CH2. */ if (copyRandom) { XMEMCPY(ssl->arrays->clientRandomInner, - ((WOLFSSL_ECH*)echX->data)->innerClientHello + - HANDSHAKE_HEADER_SZ + VERSION_SZ, RAN_LEN); + ssl->arrays->clientRandom, RAN_LEN); } *inOutIdx += size; } @@ -16099,6 +16100,12 @@ int tls13ShowSecrets(WOLFSSL* ssl, int id, const unsigned char* secret, str = "SERVER_TRAFFIC_SECRET_0"; break; case EXPORTER_SECRET: str = "EXPORTER_SECRET"; break; +#ifdef HAVE_ECH + case ECH_SECRET: + str = "ECH_SECRET"; break; + case ECH_CONFIG: + str = "ECH_CONFIG"; break; +#endif default: #ifdef WOLFSSL_SSLKEYLOGFILE_OUTPUT XFCLOSE(fp); diff --git a/tests/api.c b/tests/api.c index fe92bdee85e..830f464178b 100644 --- a/tests/api.c +++ b/tests/api.c @@ -14534,7 +14534,7 @@ static int test_wolfSSL_Tls12_Key_Logging_test(void) XMEMSET(buff, 0, sizeof(buff)); while (EXPECT_SUCCESS() && XFGETS(buff, (int)sizeof(buff), fp) != NULL) { - if (0 == strncmp(buff,"CLIENT_RANDOM ", sizeof("CLIENT_RANDOM ")-1)) { + if (0 == XSTRNCMP(buff,"CLIENT_RANDOM ", sizeof("CLIENT_RANDOM ")-1)) { found = 1; break; } @@ -14552,12 +14552,57 @@ static int test_wolfSSL_Tls12_Key_Logging_test(void) #if defined(WOLFSSL_TLS13) && defined(OPENSSL_EXTRA) && \ defined(HAVE_SECRET_CALLBACK) +#ifdef HAVE_ECH +static int test_ech_server_ctx_ready(WOLFSSL_CTX* ctx); +static int test_ech_server_ssl_ready(WOLFSSL* ssl); +static int test_ech_client_ssl_ready(WOLFSSL* ssl); +#endif + static int test_wolfSSL_Tls13_Key_Logging_client_ctx_ready(WOLFSSL_CTX* ctx) { /* set keylog callback */ wolfSSL_CTX_set_keylog_callback(ctx, keyLog_callback); return TEST_SUCCESS; } + +static int test_wolfSSL_Tls13_Key_Logging_server_ctx_ready(WOLFSSL_CTX* ctx) +{ +#ifdef HAVE_ECH + if (test_ech_server_ctx_ready(ctx) != TEST_SUCCESS) + return TEST_FAIL; +#endif + /* set keylog callback */ + wolfSSL_CTX_set_keylog_callback(ctx, keyLog_callback); + return TEST_SUCCESS; +} + +static int test_wolfSSL_Tls13_Key_Logging_client_ssl_ready(WOLFSSL* ssl) +{ +#ifdef HAVE_KEYING_MATERIAL + /* retain arrays so EXPORTER_SECRET is logged */ + wolfSSL_KeepArrays(ssl); +#endif +#ifdef HAVE_ECH + return test_ech_client_ssl_ready(ssl); +#else + (void)ssl; + return TEST_SUCCESS; +#endif +} + +static int test_wolfSSL_Tls13_Key_Logging_server_ssl_ready(WOLFSSL* ssl) +{ +#ifdef HAVE_KEYING_MATERIAL + /* retain arrays so EXPORTER_SECRET is logged */ + wolfSSL_KeepArrays(ssl); +#endif +#ifdef HAVE_ECH + return test_ech_server_ssl_ready(ssl); +#else + (void)ssl; + return TEST_SUCCESS; +#endif +} #endif static int test_wolfSSL_Tls13_Key_Logging_test(void) @@ -14566,7 +14611,7 @@ static int test_wolfSSL_Tls13_Key_Logging_test(void) #if defined(WOLFSSL_TLS13) && defined(OPENSSL_EXTRA) && \ defined(HAVE_SECRET_CALLBACK) /* This test is intended for checking whether keylog callback is called - * in client during TLS handshake between the client and a server. + * in the client/server during a TLS handshake. */ test_ssl_cbf server_cbf; test_ssl_cbf client_cbf; @@ -14576,6 +14621,9 @@ static int test_wolfSSL_Tls13_Key_Logging_test(void) XMEMSET(&client_cbf, 0, sizeof(test_ssl_cbf)); server_cbf.method = wolfTLSv1_3_server_method; /* TLS1.3 */ client_cbf.ctx_ready = &test_wolfSSL_Tls13_Key_Logging_client_ctx_ready; + client_cbf.ssl_ready = &test_wolfSSL_Tls13_Key_Logging_client_ssl_ready; + server_cbf.ctx_ready = &test_wolfSSL_Tls13_Key_Logging_server_ctx_ready; + server_cbf.ssl_ready = &test_wolfSSL_Tls13_Key_Logging_server_ssl_ready; /* clean up keylog file */ ExpectTrue((fp = XFOPEN("./MyKeyLog.txt", "w")) != XBADFILE); @@ -14590,47 +14638,184 @@ static int test_wolfSSL_Tls13_Key_Logging_test(void) /* check if the keylog file exists */ { char buff[300] = {0}; - int found[4] = {0}; - int numfnd = 0; - int i; + int found[7] = {0}; +#ifdef HAVE_ECH + char echRandom[RAN_LEN * 2] = {0}; + char chtsRandom[RAN_LEN * 2] = {0}; +#endif ExpectTrue((fp = XFOPEN("./MyKeyLog.txt", "rb")) != XBADFILE); while (EXPECT_SUCCESS() && XFGETS(buff, (int)sizeof(buff), fp) != NULL) { - if (0 == strncmp(buff, "CLIENT_HANDSHAKE_TRAFFIC_SECRET ", + if (0 == XSTRNCMP(buff, "CLIENT_HANDSHAKE_TRAFFIC_SECRET ", sizeof("CLIENT_HANDSHAKE_TRAFFIC_SECRET ")-1)) { - found[0] = 1; + found[0]++; +#ifdef HAVE_ECH + XMEMCPY(chtsRandom, + buff + sizeof("CLIENT_HANDSHAKE_TRAFFIC_SECRET ")-1, + RAN_LEN * 2); +#endif continue; } - else if (0 == strncmp(buff, "SERVER_HANDSHAKE_TRAFFIC_SECRET ", + else if (0 == XSTRNCMP(buff, "SERVER_HANDSHAKE_TRAFFIC_SECRET ", sizeof("SERVER_HANDSHAKE_TRAFFIC_SECRET ")-1)) { - found[1] = 1; + found[1]++; continue; } - else if (0 == strncmp(buff, "CLIENT_TRAFFIC_SECRET_0 ", + else if (0 == XSTRNCMP(buff, "CLIENT_TRAFFIC_SECRET_0 ", sizeof("CLIENT_TRAFFIC_SECRET_0 ")-1)) { - found[2] = 1; + found[2]++; continue; } - else if (0 == strncmp(buff, "SERVER_TRAFFIC_SECRET_0 ", + else if (0 == XSTRNCMP(buff, "SERVER_TRAFFIC_SECRET_0 ", sizeof("SERVER_TRAFFIC_SECRET_0 ")-1)) { - found[3] = 1; + found[3]++; + continue; + } +#ifdef HAVE_KEYING_MATERIAL + else if (0 == XSTRNCMP(buff, "EXPORTER_SECRET ", + sizeof("EXPORTER_SECRET ")-1)) { + found[4]++; + continue; + } +#endif +#ifdef HAVE_ECH + else if (0 == XSTRNCMP(buff, "ECH_SECRET ", + sizeof("ECH_SECRET ")-1)) { + found[5]++; + XMEMCPY(echRandom, buff + sizeof("ECH_SECRET ")-1, + RAN_LEN * 2); + continue; + } + else if (0 == XSTRNCMP(buff, "ECH_CONFIG ", + sizeof("ECH_CONFIG ")-1)) { + found[6]++; continue; } +#endif } if (fp != XBADFILE) XFCLOSE(fp); - for (i = 0; i < 4; i++) { - if (found[i] != 0) - numfnd++; - } - ExpectIntEQ(numfnd, 4); + /* the four traffic secrets are derived by both client and server, so + * each label should appear twice with the callback set on both sides */ + ExpectIntEQ(found[0], 2); + ExpectIntEQ(found[1], 2); + ExpectIntEQ(found[2], 2); + ExpectIntEQ(found[3], 2); +#ifdef HAVE_KEYING_MATERIAL + /* both sides retain arrays via KeepArrays, so EXPORTER_SECRET fires + * on each */ + ExpectIntEQ(found[4], 2); +#endif +#ifdef HAVE_ECH + /* both sides also log ECH_SECRET and ECH_CONFIG (seal on client, + * open on server) */ + ExpectIntEQ(found[5], 2); + ExpectIntEQ(found[6], 2); + /* both lines must have been logged */ + ExpectIntNE(echRandom[0], 0); + ExpectIntNE(chtsRandom[0], 0); + /* ECH_SECRET MUST be logged against the outer random while + * CLIENT_HANDSHAKE_TRAFFIC_SECRET uses the inner random when ECH is + * accepted */ + ExpectIntNE(XSTRNCMP(echRandom, chtsRandom, RAN_LEN * 2), 0); +#endif } #endif /* OPENSSL_EXTRA && HAVE_SECRET_CALLBACK && WOLFSSL_TLS13 */ return EXPECT_RESULT(); } +/* When ECH is rejected the inner random is never swapped in, so ECH_SECRET and + * CLIENT_HANDSHAKE_TRAFFIC_SECRET are both logged against the outer random. */ +static int test_wolfSSL_Tls13_Key_Logging_ech_rejected(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(OPENSSL_EXTRA) && \ + defined(HAVE_SECRET_CALLBACK) && defined(HAVE_ECH) && \ + defined(HAVE_SSL_MEMIO_TESTS_DEPENDENCIES) + test_ssl_memio_ctx test_ctx; + WOLFSSL_CTX* tempCtx = NULL; + byte badConfig[128]; + word32 badConfigLen = sizeof(badConfig); + XFILE fp = XBADFILE; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + test_ctx.s_cb.method = wolfTLSv1_3_server_method; + test_ctx.c_cb.method = wolfTLSv1_3_client_method; + /* server generates its real ECH config and sets the keylog callback */ + test_ctx.s_cb.ctx_ready = test_wolfSSL_Tls13_Key_Logging_server_ctx_ready; + /* client sets the keylog callback */ + test_ctx.c_cb.ctx_ready = test_wolfSSL_Tls13_Key_Logging_client_ctx_ready; + + ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS); + + /* generate a throwaway ECH config the server cannot decrypt */ + ExpectNotNull(tempCtx = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + ExpectIntEQ(wolfSSL_CTX_GenerateEchConfig(tempCtx, "ech-public-name.com", + 0, 0, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_GetEchConfigs(tempCtx, badConfig, &badConfigLen), + WOLFSSL_SUCCESS); + wolfSSL_CTX_free(tempCtx); + tempCtx = NULL; + + /* client uses the bad config so the server rejects ECH */ + ExpectIntEQ(wolfSSL_SetEchConfigs(test_ctx.c_ssl, badConfig, badConfigLen), + WOLFSSL_SUCCESS); + /* set inner SNI */ + ExpectIntEQ(wolfSSL_UseSNI(test_ctx.c_ssl, WOLFSSL_SNI_HOST_NAME, + "ech-private-name.com", (word16)XSTRLEN("ech-private-name.com")), + WOLFSSL_SUCCESS); + /* client sends empty cert on rejection, server should not ask for one */ + wolfSSL_set_verify(test_ctx.s_ssl, WOLFSSL_VERIFY_NONE, NULL); + + /* clean up keylog file */ + ExpectTrue((fp = XFOPEN("./MyKeyLog.txt", "w")) != XBADFILE); + if (fp != XBADFILE) { + XFCLOSE(fp); + fp = XBADFILE; + } + + /* handshake fails because ECH was rejected */ + ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.c_ssl), + WOLFSSL_ECH_STATUS_REJECTED); + ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.s_ssl), + WOLFSSL_ECH_STATUS_REJECTED); + + test_ssl_memio_cleanup(&test_ctx); + + { + char buff[300] = {0}; + char echRandom[RAN_LEN * 2] = {0}; + char chtsRandom[RAN_LEN * 2] = {0}; + + ExpectTrue((fp = XFOPEN("./MyKeyLog.txt", "rb")) != XBADFILE); + while (EXPECT_SUCCESS() && + XFGETS(buff, (int)sizeof(buff), fp) != NULL) { + if (0 == XSTRNCMP(buff, "CLIENT_HANDSHAKE_TRAFFIC_SECRET ", + sizeof("CLIENT_HANDSHAKE_TRAFFIC_SECRET ")-1)) { + XMEMCPY(chtsRandom, + buff + sizeof("CLIENT_HANDSHAKE_TRAFFIC_SECRET ")-1, + RAN_LEN * 2); + } + else if (0 == XSTRNCMP(buff, "ECH_SECRET ", + sizeof("ECH_SECRET ")-1)) { + XMEMCPY(echRandom, buff + sizeof("ECH_SECRET ")-1, RAN_LEN * 2); + } + } + if (fp != XBADFILE) + XFCLOSE(fp); + /* both lines must have been logged */ + ExpectIntNE(echRandom[0], 0); + ExpectIntNE(chtsRandom[0], 0); + /* ECH was rejected so both should be the outer random */ + ExpectIntEQ(XSTRNCMP(echRandom, chtsRandom, RAN_LEN * 2), 0); + } +#endif + return EXPECT_RESULT(); +} + #if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) #if defined(HAVE_IO_TESTS_DEPENDENCIES) static int test_wolfSSL_Tls13_ECH_params(void) @@ -40709,6 +40894,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_wolfSSL_Tls12_Key_Logging_test), /* Can't memory test as server hangs. */ TEST_DECL(test_wolfSSL_Tls13_Key_Logging_test), + TEST_DECL(test_wolfSSL_Tls13_Key_Logging_ech_rejected), TEST_DECL(test_wolfSSL_Tls13_postauth), TEST_DECL(test_wolfSSL_set_ecdh_auto), TEST_DECL(test_wolfSSL_CTX_set_ecdh_auto), diff --git a/wolfcrypt/src/hpke.c b/wolfcrypt/src/hpke.c index b490dea6957..e117e9e813a 100644 --- a/wolfcrypt/src/hpke.c +++ b/wolfcrypt/src/hpke.c @@ -906,6 +906,11 @@ static int wc_HpkeSetupBaseSender(Hpke* hpke, HpkeBaseContext* context, infoSz); } +#if defined(HAVE_SECRET_CALLBACK) && defined(HAVE_ECH) + if (ret == 0 && hpke->echSecret != NULL) { + XMEMCPY(hpke->echSecret, sharedSecret, hpke->Nsecret); + } +#endif ForceZero(sharedSecret, hpke->Nsecret); WC_FREE_VAR_EX(sharedSecret, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER); @@ -1148,6 +1153,11 @@ static int wc_HpkeSetupBaseReceiver(Hpke* hpke, HpkeBaseContext* context, infoSz); } +#if defined(HAVE_SECRET_CALLBACK) && defined(HAVE_ECH) + if (ret == 0 && hpke->echSecret != NULL) { + XMEMCPY(hpke->echSecret, sharedSecret, hpke->Nsecret); + } +#endif ForceZero(sharedSecret, hpke->Nsecret); WC_FREE_VAR_EX(sharedSecret, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER); @@ -1345,4 +1355,26 @@ WOLFSSL_LOCAL int wc_HpkeAeadIsSupported(word16 aeadId) } } +#if defined(HAVE_SECRET_CALLBACK) && defined(HAVE_ECH) +WOLFSSL_LOCAL int wc_HpkeInitEchSecret(Hpke* hpke) +{ + if (hpke == NULL) + return BAD_FUNC_ARG; + hpke->echSecret = (byte*)XMALLOC(hpke->Nsecret, hpke->heap, + DYNAMIC_TYPE_SECRET); + if (hpke->echSecret == NULL) + return MEMORY_E; + return 0; +} + +WOLFSSL_LOCAL void wc_HpkeFreeEchSecret(Hpke* hpke) +{ + if (hpke == NULL || hpke->echSecret == NULL) + return; + ForceZero(hpke->echSecret, hpke->Nsecret); + XFREE(hpke->echSecret, hpke->heap, DYNAMIC_TYPE_SECRET); + hpke->echSecret = NULL; +} +#endif /* HAVE_SECRET_CALLBACK && HAVE_ECH */ + #endif /* HAVE_HPKE && (HAVE_ECC || HAVE_CURVE25519) && HAVE_AESGCM */ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 025878a8d3b..a08595eb5ef 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -3178,7 +3178,8 @@ typedef struct WOLFSSL_ECH { WOLFSSL_LOCAL int EchConfigGetSupportedCipherSuite(WOLFSSL_EchConfig* config); -WOLFSSL_LOCAL int TLSX_FinalizeEch(WOLFSSL_ECH* ech, byte* aad, word32 aadLen); +WOLFSSL_LOCAL int TLSX_FinalizeEch(WOLFSSL* ssl, WOLFSSL_ECH* ech, byte* aad, + word32 aadLen); WOLFSSL_LOCAL int SetEchConfigsEx(WOLFSSL_EchConfig** outputConfigs, void* heap, diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index 0081cfc494d..b258d96de6a 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -1064,7 +1064,11 @@ enum Tls13Secret { CLIENT_TRAFFIC_SECRET, SERVER_TRAFFIC_SECRET, EARLY_EXPORTER_SECRET, - EXPORTER_SECRET + EXPORTER_SECRET, +#if defined(HAVE_ECH) + ECH_SECRET, + ECH_CONFIG, +#endif }; #endif diff --git a/wolfssl/wolfcrypt/hpke.h b/wolfssl/wolfcrypt/hpke.h index c71619ccf79..6b1f75edee2 100644 --- a/wolfssl/wolfcrypt/hpke.h +++ b/wolfssl/wolfcrypt/hpke.h @@ -96,6 +96,9 @@ typedef struct { word16 aead; byte kem_suite_id[KEM_SUITE_ID_LEN]; byte hpke_suite_id[HPKE_SUITE_ID_LEN]; +#if defined(HAVE_SECRET_CALLBACK) && defined(HAVE_ECH) + byte* echSecret; +#endif } Hpke; typedef struct { @@ -135,6 +138,11 @@ WOLFSSL_LOCAL int wc_HpkeKemIsSupported(word16 kemId); WOLFSSL_LOCAL int wc_HpkeKdfIsSupported(word16 kdfId); WOLFSSL_LOCAL int wc_HpkeAeadIsSupported(word16 aeadId); +#if defined(HAVE_SECRET_CALLBACK) && defined(HAVE_ECH) +WOLFSSL_LOCAL int wc_HpkeInitEchSecret(Hpke* hpke); +WOLFSSL_LOCAL void wc_HpkeFreeEchSecret(Hpke* hpke); +#endif + #endif #endif /* HAVE_HPKE && (HAVE_ECC || HAVE_CURVE25519) && HAVE_AESGCM */