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
1 change: 1 addition & 0 deletions .wolfssl_known_macro_extras
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,7 @@ WOLFSSL_SNIFFER_NO_RECOVERY
WOLFSSL_SP_ARM32_UDIV
WOLFSSL_SP_FAST_NCT_EXPTMOD
WOLFSSL_SP_INT_SQR_VOLATILE
WOLFSSL_SSLKEYLOGFILE_USE_ENV
WOLFSSL_STACK_CHECK
WOLFSSL_STM32C5
WOLFSSL_STM32F3
Expand Down
2 changes: 1 addition & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -5490,7 +5490,7 @@ then
AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_PSK_ONE_ID"
fi

# Certificate Authentication with External PSK (RFC 8773bis)
# Certificate Authentication with External PSK (RFC 9973, obsoletes RFC 8773)
AC_ARG_ENABLE([cert-with-extern-psk],
[AS_HELP_STRING([--enable-cert-with-extern-psk],[Enable Certificate Authentication with External PSKs for TLS 1.3 (default: disabled)])],
[ ENABLED_CERT_WITH_EXTERN_PSK=$enableval ],
Expand Down
4 changes: 2 additions & 2 deletions doc/dox_comments/header_files/ssl.h
Original file line number Diff line number Diff line change
Expand Up @@ -15313,7 +15313,7 @@ void wolfSSL_set_psk_server_tls13_callback(WOLFSSL* ssl,
\ingroup Setup

\brief Enable or disable TLS 1.3 certificate authentication with external
PSK (RFC8773bis) on a context.
PSK (RFC 9973) on a context.

When enabled, wolfSSL advertises and accepts the
`tls_cert_with_extern_psk` extension for TLS 1.3 handshakes using external
Expand Down Expand Up @@ -15348,7 +15348,7 @@ int wolfSSL_CTX_set_cert_with_extern_psk(WOLFSSL_CTX* ctx, int state);
\ingroup Setup

\brief Enable or disable TLS 1.3 certificate authentication with external
PSK (RFC8773bis) on a connection.
PSK (RFC 9973) on a connection.

This call applies to a single WOLFSSL object. Any non-zero \p state value
enables the feature and zero disables it.
Expand Down
4 changes: 4 additions & 0 deletions scripts/sniffer-tls13-keylog.sslkeylog
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# RFC 9850: reader must ignore comment lines, blank lines, and malformed
# lines such as the INCOMPLETE_KEYLOG_LINE entry below

INCOMPLETE_KEYLOG_LINE deadbeef
CLIENT_HANDSHAKE_TRAFFIC_SECRET b28ffa242be7ecc3ef669ea686dc842c0de9efe938a0f047c28cb359710991f8 cfe4b2e9ef157938936ed15627e65338e6b5f5b251b1718e28b028eabecf3633
CLIENT_HANDSHAKE_TRAFFIC_SECRET b28ffa242be7ecc3ef669ea686dc842c0de9efe938a0f047c28cb359710991f8 cfe4b2e9ef157938936ed15627e65338e6b5f5b251b1718e28b028eabecf3633
SERVER_HANDSHAKE_TRAFFIC_SECRET b28ffa242be7ecc3ef669ea686dc842c0de9efe938a0f047c28cb359710991f8 737fd782b2e50c4982011f0ec7249d3f91b7e9e25529631c4794cfd4a83ceaf1
Expand Down
7 changes: 7 additions & 0 deletions src/internal.c
Original file line number Diff line number Diff line change
Expand Up @@ -23002,6 +23002,13 @@ const char* AlertTypeToString(int type)
return certificate_required_str;
}

case general_error:
{
static const char general_error_str[] =
"general_error";
return general_error_str;
}

case no_application_protocol:
{
static const char no_application_protocol_str[] =
Expand Down
40 changes: 35 additions & 5 deletions src/sniffer.c
Original file line number Diff line number Diff line change
Expand Up @@ -7582,7 +7582,10 @@ static int parseKeyLogFile(const char* fileName, char* error)
/* 2 chars for Hexadecimal representation, plus null terminator */
char clientRandomHex[2 * CLIENT_RANDOM_LENGTH + 1] = {0};
char secretHex[2 * SECRET_LENGTH + 1] = {0};

/* One keylog record. RFC 9850 requires line based parsing so that comment
* and malformed lines cannot desynchronize the fields. Sized to hold the
* longest valid line plus separators and terminator. */
char line[256];

file = fopen(fileName, "r");
if (file == NULL) {
Expand All @@ -7591,10 +7594,27 @@ static int parseKeyLogFile(const char* fileName, char* error)
return WOLFSSL_SNIFFER_ERROR;
}

/* Format specifiers for each column should be:
* MAX_PREFIX_LENGTH, 2*CLIENT_RANDOM_LENGTH, and 2*SECRET_LENGTH */
while (fscanf(file, "%31s %64s %96s", prefix, clientRandomHex, secretHex)
== 3) {
while (fgets(line, (int)sizeof(line), file) != NULL) {
/* RFC 9850 Section 1: ignore empty lines and lines whose first
* character is the octothorpe ('#') comment marker. */
if (line[0] == '#' || line[0] == '\n' || line[0] == '\r' ||
line[0] == '\0') {
continue;
}

/* Clear the field buffers so that a short field on one line cannot
* pick up stale bytes left by a previous line. */
XMEMSET(prefix, 0, sizeof(prefix));
XMEMSET(clientRandomHex, 0, sizeof(clientRandomHex));
XMEMSET(secretHex, 0, sizeof(secretHex));

/* RFC 9850 Section 2: silently ignore lines that do not conform to the
* "label SP random SP secret" format so that secrets can still be
* recovered from a corrupted file. */
if (sscanf(line, "%31s %64s %96s", prefix, clientRandomHex, secretHex)
!= 3) {
continue;
}

if (XSTRCMP(prefix, "CLIENT_RANDOM") == 0) {
type = SNIFFER_SECRET_TLS12_MASTER_SECRET;
Expand All @@ -7621,6 +7641,14 @@ static int parseKeyLogFile(const char* fileName, char* error)
continue;
}

/* The client random is always CLIENT_RANDOM_LENGTH bytes. Reject a
* line whose random field is not the exact expected length. The secret
* length is left variable because it depends on the negotiated hash. */
if (XSTRLEN(clientRandomHex) != (size_t)(2 * CLIENT_RANDOM_LENGTH)) {
fprintf(stderr, "malformed client random for prefix: %s\n", prefix);
continue;
}

hexToBin(clientRandomHex, clientRandom, CLIENT_RANDOM_LENGTH);
hexToBin(secretHex, secret, SECRET_LENGTH);
ret = addSecretNode(clientRandom, type, secret, error);
Expand All @@ -7629,13 +7657,15 @@ static int parseKeyLogFile(const char* fileName, char* error)
fclose(file);
ForceZero(secret, SECRET_LENGTH);
ForceZero(secretHex, sizeof(secretHex));
ForceZero(line, sizeof(line));
return ret;
}
}
fclose(file);

ForceZero(secret, SECRET_LENGTH);
ForceZero(secretHex, sizeof(secretHex));
ForceZero(line, sizeof(line));
return 0;
}

Expand Down
13 changes: 12 additions & 1 deletion src/ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -3722,7 +3722,18 @@ int tlsShowSecrets(WOLFSSL* ssl, void* secret, int secretSz,

#if !defined(NO_FILESYSTEM) && defined(WOLFSSL_SSLKEYLOGFILE)
{
FILE* f = XFOPEN(WOLFSSL_SSLKEYLOGFILE_OUTPUT, "a");
const char* keyLogFile = WOLFSSL_SSLKEYLOGFILE_OUTPUT;
FILE* f;
#ifdef WOLFSSL_SSLKEYLOGFILE_USE_ENV
/* RFC 9850: prefer the SSLKEYLOGFILE environment variable so other
* tools can share the path, else use the compile-time path. XGETENV is
* NULL where environment access is unavailable. Opt-in so a build with
* the variable exported for other applications is not affected. */
const char* keyLogEnv = XGETENV("SSLKEYLOGFILE");
if (keyLogEnv != NULL && keyLogEnv[0] != '\0')
keyLogFile = keyLogEnv;
#endif
f = XFOPEN(keyLogFile, "a");
if (f != XBADFILE) {
XFWRITE(pmsBuf, 1, pmsPos, f);
XFCLOSE(f);
Expand Down
11 changes: 6 additions & 5 deletions src/tls.c
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
* WOLFSSL_SNIFFER: Enable TLS packet sniffing support default: off
* WOLFSSL_SNIFFER_KEYLOGFILE: Sniffer keylog file support default: off
* WOLFSSL_SSLKEYLOGFILE: Enable SSL key log file output default: off
* WOLFSSL_SSLKEYLOGFILE_USE_ENV: Use SSLKEYLOGFILE env var path default: off
* WOLFSSL_SRTP: Enable SRTP extension support default: off
* WOLFSSL_DUAL_ALG_CERTS: Enable dual algorithm certificates default: off
* WOLFSSL_HAVE_PRF: Enable TLS PRF function access default: off
Expand Down Expand Up @@ -16694,7 +16695,7 @@ int TLSX_PopulateExtensions(WOLFSSL* ssl, byte isServer)
#endif
#if defined(WOLFSSL_CERT_WITH_EXTERN_PSK)
if (ssl->options.certWithExternPsk) {
/* RFC8773bis requires psk_dhe_ke with cert_with_extern_psk. */
/* RFC 9973 requires psk_dhe_ke with cert_with_extern_psk. */
modes |= 1 << PSK_DHE_KE;
}
#endif
Expand Down Expand Up @@ -18753,12 +18754,12 @@ WOLFSSL_TEST_VIS int TLSX_Parse(WOLFSSL* ssl, const byte* input, word16 length,

if (msgType == client_hello && isRequest) {
TLSX* pskm;
/* RFC8773bis: CH2 after HRR must keep CH1's extension set. */
/* RFC 9973: CH2 after HRR must keep CH1's extension set. */
if (secondClientHello && !prevHasPskWithCert) {
WOLFSSL_ERROR_VERBOSE(EXT_NOT_ALLOWED);
return EXT_NOT_ALLOWED;
}
/* RFC8773bis: cert_with_extern_psk depends on these extensions. */
/* RFC 9973: cert_with_extern_psk depends on these extensions. */
if (!hasPsk || !hasPskModes || !hasKeyShare || !hasSg ||
!hasSigAlg) {
WOLFSSL_ERROR_VERBOSE(EXT_MISSING);
Expand All @@ -18776,7 +18777,7 @@ WOLFSSL_TEST_VIS int TLSX_Parse(WOLFSSL* ssl, const byte* input, word16 length,
}
#endif
pskm = TLSX_Find(ssl->extensions, TLSX_PSK_KEY_EXCHANGE_MODES);
/* RFC8773bis requires client support for psk_dhe_ke mode. */
/* RFC 9973 requires client support for psk_dhe_ke mode. */
if (pskm == NULL || (pskm->val & (1 << PSK_DHE_KE)) == 0) {
WOLFSSL_ERROR_VERBOSE(EXT_NOT_ALLOWED);
return EXT_NOT_ALLOWED;
Expand All @@ -18792,7 +18793,7 @@ WOLFSSL_TEST_VIS int TLSX_Parse(WOLFSSL* ssl, const byte* input, word16 length,
}
else if (msgType == client_hello && isRequest && secondClientHello &&
prevHasPskWithCert) {
/* RFC8773bis: reject dropping the extension in CH2 after HRR. */
/* RFC 9973: reject dropping the extension in CH2 after HRR. */
WOLFSSL_ERROR_VERBOSE(EXT_NOT_ALLOWED);
return EXT_NOT_ALLOWED;
}
Expand Down
48 changes: 41 additions & 7 deletions src/tls13.c
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
* (no ciphersuite requires it currently)
* WOLFSSL_ERROR_CODE_OPENSSL: Use OpenSSL-compatible error codes default: off
* WOLFSSL_SSLKEYLOGFILE_OUTPUT: Set key log output file path default: off
* WOLFSSL_SSLKEYLOGFILE_USE_ENV: Use SSLKEYLOGFILE env var path default: off
* WOLFSSL_RW_THREADED: Enable read/write threading support default: off
* WOLFSSL_ASYNC_IO: Enable async I/O operations default: off
* WOLFSSL_NONBLOCK_OCSP: Non-blocking OCSP processing default: off
Expand Down Expand Up @@ -6004,12 +6005,12 @@ int DoTls13ServerHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
#endif
#ifdef WOLFSSL_CERT_WITH_EXTERN_PSK
if (ssl->options.certWithExternPsk && psk->resumption) {
/* RFC8773bis mode requires external PSK, not ticket resumption. */
/* RFC 9973 mode requires external PSK, not ticket resumption. */
WOLFSSL_ERROR_VERBOSE(PSK_KEY_ERROR);
return PSK_KEY_ERROR;
}
if (ssl->options.certWithExternPsk && ssl->options.shSentKeyShare == 0) {
/* RFC8773bis Sec. 3: cert_with_extern_psk requires psk_dhe_ke;
/* RFC 9973 Sect. 3: cert_with_extern_psk requires psk_dhe_ke;
* a ServerHello without a key_share confirms only psk_ke. */
WOLFSSL_MSG("cert_with_extern_psk: ServerHello missing key_share");
WOLFSSL_ERROR_VERBOSE(EXT_MISSING);
Expand Down Expand Up @@ -6244,8 +6245,10 @@ static int DoTls13CertificateRequest(WOLFSSL* ssl, const byte* input,
*inOutIdx += OPAQUE16_LEN;
if ((*inOutIdx - begin) + len > size)
return BUFFER_ERROR;
if (len == 0)
return INVALID_PARAMETER;
/* RFC 9846 Section 4.4.2: CertificateRequest.extensions has a lower bound of
* 0, so an empty extensions block is parsed rather than rejected here. A
* request missing the mandatory signature_algorithms extension is caught by
* the check below. */
if ((ret = TLSX_Parse(ssl, input + *inOutIdx, len, certificate_request,
&peerSuites))) {
return ret;
Expand Down Expand Up @@ -6554,7 +6557,7 @@ static int DoPreSharedKeys(WOLFSSL* ssl, const byte* input, word32 inputSz,
}
if (ret == WOLFSSL_TICKET_RET_OK) {
#if defined(WOLFSSL_CERT_WITH_EXTERN_PSK) && defined(HAVE_SESSION_TICKET)
/* RFC 8773bis Sect. 5.1: all PSKs listed alongside
/* RFC 9973 Sect. 5.1: all PSKs listed alongside
* tls_cert_with_extern_psk MUST be external PSKs. A successfully
* decrypted session ticket identity is a resumption PSK, so the
* server MUST abort with illegal_parameter regardless of whether
Expand Down Expand Up @@ -6879,7 +6882,7 @@ static int CheckPreSharedKeys(WOLFSSL* ssl, const byte* input, word32 helloSz,
extEarlyData = TLSX_Find(ssl->extensions, TLSX_EARLY_DATA);
if (extEarlyData != NULL) {
/* Check if accepting early data and first PSK.
* RFC 8773bis: early_data is not compatible with
* RFC 9973: early_data is not compatible with
* cert_with_extern_psk, so skip key derivation in that case. */
if (ssl->earlyData != no_early_data && first
&& ssl->options.maxEarlyDataSz > 0
Expand Down Expand Up @@ -6949,7 +6952,7 @@ static int CheckPreSharedKeys(WOLFSSL* ssl, const byte* input, word32 helloSz,
ssl->options.sendVerify = SEND_CERT;
certExt->resp = 1;
#ifdef WOLFSSL_EARLY_DATA
/* RFC 8773bis: early_data is not compatible with
/* RFC 9973: early_data is not compatible with
* cert_with_extern_psk. TLSX_Parse already rejects the
* combination in the ClientHello, but clear the response flag
* here as a defense-in-depth measure. */
Expand Down Expand Up @@ -12401,6 +12404,19 @@ int SendTls13KeyUpdate(WOLFSSL* ssl)
}
#endif /* WOLFSSL_DTLS13 */

if (!ssl->options.dtls) {
/* RFC 9846 Section 4.7.3: a sending implementation MUST NOT allow its
* number of key updates to exceed 2^48-1. Receivers MUST NOT enforce
* this on the peer. */
if (w64GTE(ssl->keys.keyUpdateCount,
w64From32(TLS13_KEY_UPDATE_MAX_HI32,
TLS13_KEY_UPDATE_MAX_LO32))) {
WOLFSSL_MSG("TLS 1.3 key update count at maximum; refusing "
"KeyUpdate");
return BAD_STATE_E;
}
}

outputSz = OPAQUE8_LEN + MAX_MSG_EXTRA;
/* Check buffers are big enough and grow if needed. */
if ((ret = CheckAvailableSize(ssl, outputSz)) != 0)
Expand Down Expand Up @@ -12472,6 +12488,9 @@ int SendTls13KeyUpdate(WOLFSSL* ssl)
return ret;
if ((ret = SetKeysSide(ssl, ENCRYPT_SIDE_ONLY)) != 0)
return ret;

/* Count this key update against the RFC 9846 sender limit. */
w64Increment(&ssl->keys.keyUpdateCount);
}


Expand Down Expand Up @@ -16419,10 +16438,25 @@ int tls13ShowSecrets(WOLFSSL* ssl, int id, const unsigned char* secret,
byte clientRandom[RAN_LEN];
int clientRandomSz;
XFILE fp;
#if defined(WOLFSSL_SSLKEYLOGFILE_OUTPUT) && defined(WOLFSSL_SSLKEYLOGFILE_USE_ENV)
const char* keyLogFile;
#endif

(void) ctx;
#ifdef WOLFSSL_SSLKEYLOGFILE_OUTPUT
#ifdef WOLFSSL_SSLKEYLOGFILE_USE_ENV
/* RFC 9850: prefer the SSLKEYLOGFILE environment variable so tools such as
* curl and Wireshark can share the path, else use the compile-time path.
* XGETENV resolves to NULL where environment access is unavailable. Opt-in
* so a build with the variable exported for other applications is not
* affected. */
keyLogFile = XGETENV("SSLKEYLOGFILE");
if (keyLogFile == NULL || keyLogFile[0] == '\0')
keyLogFile = WOLFSSL_SSLKEYLOGFILE_OUTPUT;
fp = XFOPEN(keyLogFile, "ab");
#else
fp = XFOPEN(WOLFSSL_SSLKEYLOGFILE_OUTPUT, "ab");
#endif
if (fp == XBADFILE) {
return BAD_FUNC_ARG;
}
Expand Down
51 changes: 50 additions & 1 deletion tests/api/test_tls13.c
Original file line number Diff line number Diff line change
Expand Up @@ -1206,7 +1206,7 @@ int test_tls13_cert_with_extern_psk_rejects_resumption(void)
ssl_s = NULL;

/* Step 2: attempt to resume while also offering cert_with_extern_psk.
* RFC 8773bis Sect. 5.1 requires all PSKs offered alongside
* RFC 9973 Sect. 5.1 requires all PSKs offered alongside
* cert_with_extern_psk to be external PSKs. The client MUST therefore
* suppress the resumption ticket identity from the pre_shared_key
* extension. The handshake succeeds as a cert_with_extern_psk handshake
Expand Down Expand Up @@ -8652,3 +8652,52 @@ int test_tls13_AEAD_limit_KU_aes128_ccm_8_sha256(void)
return EXPECT_RESULT();
}

/* RFC 9846 Section 4.7.3: a sending implementation MUST NOT allow its number of
* key updates to exceed 2^48-1. Establish a TLS 1.3 connection and exercise both
* sides of the boundary: one update seeded just below the ceiling must succeed
* and advance the sender count to exactly 2^48-1, and a further update once the
* count has reached the ceiling must be refused with BAD_STATE_E without
* advancing the count. The count is inspected directly because
* SendTls13KeyUpdate enforces the limit before touching the transport. */
int test_tls13_KeyUpdate_sender_limit(void)
{
EXPECT_DECLS;
#if defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) && \
!defined(NO_WOLFSSL_SERVER) && \
defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES)
struct test_memio_ctx test_ctx;
WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
WOLFSSL *ssl_c = NULL, *ssl_s = NULL;

XMEMSET(&test_ctx, 0, sizeof(test_ctx));

ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s,
wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0);
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);

if (EXPECT_SUCCESS() && ssl_c != NULL) {
w64wrapper ceiling;

ceiling = w64From32(TLS13_KEY_UPDATE_MAX_HI32, TLS13_KEY_UPDATE_MAX_LO32);

/* One below the ceiling: the update is allowed and must advance the
* sender count by one, reaching exactly 2^48-1. */
ssl_c->keys.keyUpdateCount = w64From32(TLS13_KEY_UPDATE_MAX_HI32,
TLS13_KEY_UPDATE_MAX_LO32 - 1);
ExpectIntEQ(wolfSSL_update_keys(ssl_c), WOLFSSL_SUCCESS);
ExpectTrue(w64Equal(ssl_c->keys.keyUpdateCount, ceiling));

/* At the ceiling: a further update must be refused rather than
* advancing the count past 2^48-1. */
ExpectIntEQ(wolfSSL_update_keys(ssl_c), WC_NO_ERR_TRACE(BAD_STATE_E));
ExpectTrue(w64Equal(ssl_c->keys.keyUpdateCount, ceiling));
}

wolfSSL_free(ssl_c);
wolfSSL_free(ssl_s);
wolfSSL_CTX_free(ctx_c);
wolfSSL_CTX_free(ctx_s);
#endif
return EXPECT_RESULT();
}

2 changes: 2 additions & 0 deletions tests/api/test_tls13.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ int test_tls13_AEAD_limit_KU_aes128_gcm_sha256(void);
int test_tls13_AEAD_limit_KU_aes256_gcm_sha384(void);
int test_tls13_AEAD_limit_KU_aes128_ccm_sha256(void);
int test_tls13_AEAD_limit_KU_aes128_ccm_8_sha256(void);
int test_tls13_KeyUpdate_sender_limit(void);
int test_tls13_pqc_hybrid_async_server(void);

#define TEST_TLS13_DECLS \
Expand Down Expand Up @@ -183,6 +184,7 @@ int test_tls13_pqc_hybrid_async_server(void);
TEST_DECL_GROUP("tls13", test_tls13_AEAD_limit_KU_aes256_gcm_sha384), \
TEST_DECL_GROUP("tls13", test_tls13_AEAD_limit_KU_aes128_ccm_sha256), \
TEST_DECL_GROUP("tls13", test_tls13_AEAD_limit_KU_aes128_ccm_8_sha256), \
TEST_DECL_GROUP("tls13", test_tls13_KeyUpdate_sender_limit), \
TEST_DECL_GROUP("tls13", test_tls13_pqc_hybrid_async_server)

#endif /* WOLFCRYPT_TEST_TLS13_H */
Loading
Loading