From c218d94c206d125e3a924eadb1b06e7e20432154 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Sat, 6 Jun 2026 01:39:40 +0000 Subject: [PATCH] Add DTLS secondary cookie secret for app-driven rotation Add verify-only secondary cookie secrets for both DTLS 1.2 (HelloVerifyRequest) and DTLS 1.3 (HelloRetryRequest). A secondary secret is used only during cookie verification, and only if the primary secret fails to verify the cookie; it is never used to issue cookies. This lets an application rotate the cookie secret on a stateless DTLS server without rejecting clients that received a cookie under the previous secret: after rotating the primary, the application installs the old secret as the secondary so in-flight cookies still verify for an overlap window (RFC 9147 Section 5.1). Two APIs rather than one. The 1.2 and 1.3 cookie mechanisms keep separate secrets and separate verify routines, set their primary secret through different APIs (wolfSSL_DTLS_SetCookieSecret for 1.2, wolfSSL_send_hrr_cookie for 1.3), and live under different build guards (a DTLS-1.2-only build has none of the TLS 1.3 / HRR machinery). So each version gets its own sibling API: - wolfSSL_set_hrr_cookie_secret_secondary() - DTLS 1.3. Per-connection and DTLS-only; returns BAD_FUNC_ARG for a non-DTLS or non-TLS-1.3 object, since TLS 1.3 over a reliable transport does not verify cookies statelessly across the HelloRetryRequest exchange. - wolfSSL_DTLS_SetCookieSecretSecondary() - DTLS 1.2, the counterpart of the above. NULL/0 clears it. A server handling both versions sets both secondary secrets. Details: - New per-SSL buffers tls13CookieSecretSecondary and dtlsCookieSecretSecondary, freed on SSL teardown (the 1.3 buffer is also freed in wolfSSL_disable_hrr_cookie()). - TlsCheckCookie() factors its HMAC compute/compare into TlsCheckCookieMac() and tries the primary secret, then the secondary on mismatch. CreateDtls12Cookie() now takes the secret as parameters; CheckDtlsCookie() computes with the primary and retries with the secondary on mismatch. Issuance (CreateCookieExt, SendHelloVerifyRequest) always uses the primary. Tests cover, for both versions: a handshake where the cookie is accepted via the secondary after a rotation; a negative case where the signing secret has been rotated out of both slots; secondary clear behavior; primary still wins on the fast path; replacing the secondary activates the new value; secondary equal to primary is harmless; and issuance uses the primary, not the verify-only secondary. The 1.3 API additionally has NULL/side/version argument-validation coverage. --- doc/dox_comments/header_files/ssl.h | 84 +++ src/dtls.c | 32 +- src/internal.c | 20 + src/ssl.c | 67 +++ src/tls13.c | 206 +++++-- tests/api/test_dtls.c | 860 ++++++++++++++++++++++++++++ tests/api/test_dtls.h | 32 +- wolfssl/internal.h | 11 + wolfssl/ssl.h | 3 + 9 files changed, 1271 insertions(+), 44 deletions(-) diff --git a/doc/dox_comments/header_files/ssl.h b/doc/dox_comments/header_files/ssl.h index 1a56a4ae7c7..264c25f1af9 100644 --- a/doc/dox_comments/header_files/ssl.h +++ b/doc/dox_comments/header_files/ssl.h @@ -8933,6 +8933,45 @@ int wolfSSL_DTLS_SetCookieSecret(WOLFSSL* ssl, const byte* secret, word32 secretSz); +/*! + \brief This function sets a secondary DTLS 1.2 cookie secret used only when + verifying a received HelloVerifyRequest cookie, and only if the primary + secret (set with wolfSSL_DTLS_SetCookieSecret()) fails to verify it. This + lets an application rotate the cookie secret on a stateless server without + rejecting clients whose cookie was issued under the previous secret: install + the new secret as the primary and the previous secret here for an overlap + window. The secondary secret is never used to issue cookies. It is the + DTLS 1.2 counterpart of wolfSSL_set_hrr_cookie_secret_secondary(). + + \return 0 returned if the function executed without an error. + \return BAD_FUNC_ARG returned if ssl is NULL. + \return MEMORY_ERROR returned if there was a problem allocating + memory for the secondary cookie secret. + + \param ssl a pointer to a WOLFSSL structure, created using wolfSSL_new(). + \param secret a constant byte pointer representing the secret buffer. + Passing NULL (or a secretSz of 0) clears any previously set secondary secret. + \param secretSz the size of the buffer. + + _Example_ + \code + WOLFSSL* ssl = wolfSSL_new(ctx); + const byte* oldSecret; + word32 oldSecretSz; // size of oldSecret + … + if(wolfSSL_DTLS_SetCookieSecretSecondary(ssl, oldSecret, oldSecretSz) != 0){ + // Code block for failure to set secondary DTLS cookie secret + } else { + // Success! Secondary cookie secret is set. + } + \endcode + + \sa wolfSSL_DTLS_SetCookieSecret +*/ +int wolfSSL_DTLS_SetCookieSecretSecondary(WOLFSSL* ssl, + const byte* secret, + word32 secretSz); + /*! \brief This function retrieves the random number. @@ -13995,6 +14034,51 @@ int wolfSSL_connect(WOLFSSL* ssl); int wolfSSL_send_hrr_cookie(WOLFSSL* ssl, const unsigned char* secret, unsigned int secretSz); +/*! + \ingroup Setup + + \brief This function sets a secondary HelloRetryRequest cookie secret on a + DTLS 1.3 server. It is used only when verifying a received cookie, and only + if the primary secret (set with wolfSSL_send_hrr_cookie()) fails to verify + it. This lets an application rotate the cookie secret on a stateless DTLS + 1.3 server without rejecting clients whose cookie was issued under the + previous secret: install the new secret as the primary and the previous + secret here for an overlap window. The secondary secret is never used to + issue cookies. This API is DTLS only; TLS 1.3 over a reliable transport + does not verify cookies statelessly across the HelloRetryRequest exchange. + + \param [in,out] ssl a pointer to a WOLFSSL structure, created using wolfSSL_new(). + \param [in] secret a pointer to a buffer holding the secondary secret. + Passing NULL (or a secretSz of 0) clears any previously set secondary secret. + \param [in] secretSz Size of the secret in bytes. + + \return BAD_FUNC_ARG if ssl is NULL, not using TLS v1.3, or not using DTLS. + \return SIDE_ERROR if called with a client. + \return WOLFSSL_SUCCESS if successful. + \return MEMORY_ERROR if allocating dynamic memory for storing secret failed. + + _Example_ + \code + int ret; + WOLFSSL* ssl; + char newSecret[32]; + char oldSecret[32]; + ... + // rotate: new secret becomes primary, previous secret stays valid + wolfSSL_send_hrr_cookie(ssl, newSecret, sizeof(newSecret)); + ret = wolfSSL_set_hrr_cookie_secret_secondary(ssl, oldSecret, + sizeof(oldSecret)); + if (ret != WOLFSSL_SUCCESS) { + // failed to set the secondary cookie secret + } + \endcode + + \sa wolfSSL_send_hrr_cookie + \sa wolfSSL_disable_hrr_cookie +*/ +int wolfSSL_set_hrr_cookie_secret_secondary(WOLFSSL* ssl, + const unsigned char* secret, unsigned int secretSz); + /*! \ingroup Setup diff --git a/src/dtls.c b/src/dtls.c index 61929cd7a86..94d490ca318 100644 --- a/src/dtls.c +++ b/src/dtls.c @@ -209,13 +209,12 @@ static word32 ReadVector16(const byte* input, WolfSSL_ConstVector* v) } static int CreateDtls12Cookie(const WOLFSSL* ssl, const WolfSSL_CH* ch, - byte* cookie) + const byte* secret, word32 secretSz, byte* cookie) { int ret; WC_DECLARE_VAR(cookieHmac, Hmac, 1, ssl->heap); - if (ssl->buffers.dtlsCookieSecret.buffer == NULL || - ssl->buffers.dtlsCookieSecret.length == 0) { + if (secret == NULL || secretSz == 0) { WOLFSSL_MSG("Missing DTLS 1.2 cookie secret"); return COOKIE_ERROR; } @@ -225,9 +224,7 @@ static int CreateDtls12Cookie(const WOLFSSL* ssl, const WolfSSL_CH* ch, ret = wc_HmacInit(cookieHmac, ssl->heap, ssl->devId); if (ret == 0) { - ret = wc_HmacSetKey(cookieHmac, DTLS_COOKIE_TYPE, - ssl->buffers.dtlsCookieSecret.buffer, - ssl->buffers.dtlsCookieSecret.length); + ret = wc_HmacSetKey(cookieHmac, DTLS_COOKIE_TYPE, secret, secretSz); if (ret == 0) { /* peerLock not necessary. Still in handshake phase. */ ret = wc_HmacUpdate(cookieHmac, @@ -288,13 +285,30 @@ static int CheckDtlsCookie(const WOLFSSL* ssl, WolfSSL_CH* ch, if (ch->cookie.size != DTLS_COOKIE_SZ) return 0; if (!ch->dtls12cookieSet) { - ret = CreateDtls12Cookie(ssl, ch, ch->dtls12cookie); + ret = CreateDtls12Cookie(ssl, ch, + ssl->buffers.dtlsCookieSecret.buffer, + ssl->buffers.dtlsCookieSecret.length, ch->dtls12cookie); if (ret != 0) return ret; ch->dtls12cookieSet = 1; } *cookieGood = ConstantCompare(ch->cookie.elements, ch->dtls12cookie, DTLS_COOKIE_SZ) == 0; + /* If the primary secret didn't match, try the secondary (verify-only) + * secret. This lets a stateless server keep accepting cookies issued + * under the secret it held before an application-driven rotation. */ + if (!*cookieGood && + ssl->buffers.dtlsCookieSecretSecondary.buffer != NULL && + ssl->buffers.dtlsCookieSecretSecondary.length > 0) { + byte altCookie[DTLS_COOKIE_SZ]; + ret = CreateDtls12Cookie(ssl, ch, + ssl->buffers.dtlsCookieSecretSecondary.buffer, + ssl->buffers.dtlsCookieSecretSecondary.length, altCookie); + if (ret != 0) + return ret; + *cookieGood = ConstantCompare(ch->cookie.elements, altCookie, + DTLS_COOKIE_SZ) == 0; + } } return ret; } @@ -907,7 +921,9 @@ static int SendStatelessReply(const WOLFSSL* ssl, WolfSSL_CH* ch, byte isTls13) { #if !defined(WOLFSSL_NO_TLS12) if (!ch->dtls12cookieSet) { - ret = CreateDtls12Cookie(ssl, ch, ch->dtls12cookie); + ret = CreateDtls12Cookie(ssl, ch, + ssl->buffers.dtlsCookieSecret.buffer, + ssl->buffers.dtlsCookieSecret.length, ch->dtls12cookie); if (ret != 0) return ret; ch->dtls12cookieSet = 1; diff --git a/src/internal.c b/src/internal.c index a3be7ee4484..c7aacadc57a 100644 --- a/src/internal.c +++ b/src/internal.c @@ -8959,6 +8959,16 @@ void wolfSSL_ResourceFree(WOLFSSL* ssl) } XFREE(ssl->buffers.tls13CookieSecret.buffer, ssl->heap, DYNAMIC_TYPE_COOKIE_PWD); + ssl->buffers.tls13CookieSecret.buffer = NULL; + ssl->buffers.tls13CookieSecret.length = 0; + if (ssl->buffers.tls13CookieSecretSecondary.buffer != NULL) { + ForceZero(ssl->buffers.tls13CookieSecretSecondary.buffer, + ssl->buffers.tls13CookieSecretSecondary.length); + } + XFREE(ssl->buffers.tls13CookieSecretSecondary.buffer, ssl->heap, + DYNAMIC_TYPE_COOKIE_PWD); + ssl->buffers.tls13CookieSecretSecondary.buffer = NULL; + ssl->buffers.tls13CookieSecretSecondary.length = 0; #endif #ifdef WOLFSSL_DTLS DtlsMsgPoolReset(ssl); @@ -8984,6 +8994,16 @@ void wolfSSL_ResourceFree(WOLFSSL* ssl) } XFREE(ssl->buffers.dtlsCookieSecret.buffer, ssl->heap, DYNAMIC_TYPE_COOKIE_PWD); + ssl->buffers.dtlsCookieSecret.buffer = NULL; + ssl->buffers.dtlsCookieSecret.length = 0; + if (ssl->buffers.dtlsCookieSecretSecondary.buffer != NULL) { + ForceZero(ssl->buffers.dtlsCookieSecretSecondary.buffer, + ssl->buffers.dtlsCookieSecretSecondary.length); + } + XFREE(ssl->buffers.dtlsCookieSecretSecondary.buffer, ssl->heap, + DYNAMIC_TYPE_COOKIE_PWD); + ssl->buffers.dtlsCookieSecretSecondary.buffer = NULL; + ssl->buffers.dtlsCookieSecretSecondary.length = 0; #endif #ifdef WOLFSSL_DTLS13 diff --git a/src/ssl.c b/src/ssl.c index c215101175c..919776beae2 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -6389,6 +6389,73 @@ int wolfSSL_DTLS_SetCookieSecret(WOLFSSL* ssl, return ret; } +/* Set a secondary DTLS 1.2 cookie secret used only when verifying a received + * HelloVerifyRequest cookie, and only if the primary secret (set by + * wolfSSL_DTLS_SetCookieSecret()) fails to verify it. + * + * This supports an application-driven cookie-secret rotation on a stateless + * server: after rotating the primary secret, install the previous secret here + * so that cookies already issued under it are still accepted for an overlap + * window. It is never used to issue cookies. + * + * This is the DTLS 1.2 counterpart of wolfSSL_set_hrr_cookie_secret_secondary() + * (which covers the DTLS 1.3 HelloRetryRequest cookie); the two cookie + * mechanisms keep separate secrets, so a server that handles both versions sets + * both secondary secrets. + * + * ssl SSL/TLS object. + * secret Secondary secret to verify cookies against. A value of NULL (or a + * secretSz of 0) clears any previously set secondary secret. + * secretSz Size of secret data in bytes. + * returns BAD_FUNC_ARG when ssl is NULL, 0 on success and otherwise failure. + */ +int wolfSSL_DTLS_SetCookieSecretSecondary(WOLFSSL* ssl, + const byte* secret, word32 secretSz) +{ + WOLFSSL_ENTER("wolfSSL_DTLS_SetCookieSecretSecondary"); + + if (ssl == NULL) { + WOLFSSL_MSG("need a SSL object"); + return BAD_FUNC_ARG; + } + + /* Clear any existing secondary secret. */ + if (ssl->buffers.dtlsCookieSecretSecondary.buffer != NULL) { + ForceZero(ssl->buffers.dtlsCookieSecretSecondary.buffer, + ssl->buffers.dtlsCookieSecretSecondary.length); + XFREE(ssl->buffers.dtlsCookieSecretSecondary.buffer, + ssl->heap, DYNAMIC_TYPE_COOKIE_PWD); + ssl->buffers.dtlsCookieSecretSecondary.buffer = NULL; + ssl->buffers.dtlsCookieSecretSecondary.length = 0; + } + + /* A NULL/empty secret just clears the secondary secret. */ + if (secret == NULL || secretSz == 0) { + WOLFSSL_LEAVE("wolfSSL_DTLS_SetCookieSecretSecondary", 0); + return 0; + } + + { + byte* newSecret = (byte*)XMALLOC(secretSz, ssl->heap, + DYNAMIC_TYPE_COOKIE_PWD); + if (newSecret == NULL) { + WOLFSSL_MSG("couldn't allocate secondary cookie secret"); + return MEMORY_ERROR; + } + XMEMCPY(newSecret, secret, secretSz); + ssl->buffers.dtlsCookieSecretSecondary.buffer = newSecret; + ssl->buffers.dtlsCookieSecretSecondary.length = secretSz; + #ifdef WOLFSSL_CHECK_MEM_ZERO + wc_MemZero_Add("wolfSSL_DTLS_SetCookieSecretSecondary secret", + ssl->buffers.dtlsCookieSecretSecondary.buffer, + ssl->buffers.dtlsCookieSecretSecondary.length); + #endif + } + + WOLFSSL_LEAVE("wolfSSL_DTLS_SetCookieSecretSecondary", 0); + return 0; +} + #endif /* WOLFSSL_DTLS && !NO_WOLFSSL_SERVER */ diff --git a/src/tls13.c b/src/tls13.c index 5e377f40ef8..c495ffc82e0 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -6935,19 +6935,65 @@ static int CheckPreSharedKeys(WOLFSSL* ssl, const byte* input, word32 helloSz, * cookieSz The length of the cookie data in bytes. * returns Length of the hash on success, otherwise failure. */ -int TlsCheckCookie(const WOLFSSL* ssl, const byte* cookie, word16 cookieSz) +/* Compute the cookie integrity HMAC over the cookie data (and, for DTLS, the + * peer address) using the given secret and compare it in constant time against + * the MAC trailing the cookie. + * + * ssl SSL/TLS object. + * cookie The cookie data - hash and MAC. + * dataSz Length of the cookie data preceding the trailing MAC. + * secret Secret to key the HMAC with. + * secretSz Length of the secret in bytes. + * cookieType Digest type to use for the HMAC. + * macSz Length of the MAC in bytes. + * returns 0 on match, HRR_COOKIE_ERROR on mismatch, otherwise a negative error. + */ +static int TlsCheckCookieMac(const WOLFSSL* ssl, const byte* cookie, + word16 dataSz, const byte* secret, word32 secretSz, byte cookieType, + byte macSz) { int ret; byte mac[WC_MAX_DIGEST_SIZE] = {0}; WC_DECLARE_VAR(cookieHmac, Hmac, 1, ssl->heap); - byte cookieType = 0; - byte macSz = 0; - if (ssl->buffers.tls13CookieSecret.buffer == NULL || - ssl->buffers.tls13CookieSecret.length == 0) { - WOLFSSL_MSG("Missing DTLS 1.3 cookie secret"); - return COOKIE_ERROR; + WC_ALLOC_VAR_EX(cookieHmac, Hmac, 1, ssl->heap, DYNAMIC_TYPE_HMAC, + return MEMORY_E); + + ret = wc_HmacInit(cookieHmac, ssl->heap, ssl->devId); + if (ret == 0) + ret = wc_HmacSetKey(cookieHmac, cookieType, secret, secretSz); + if (ret == 0) + ret = wc_HmacUpdate(cookieHmac, cookie, dataSz); +#ifdef WOLFSSL_DTLS13 + /* Tie cookie to peer address */ + if (ret == 0) { + /* peerLock not necessary. Still in handshake phase. */ + if (ssl->options.dtls && ssl->buffers.dtlsCtx.peer.sz > 0) { + ret = wc_HmacUpdate(cookieHmac, + (byte*)ssl->buffers.dtlsCtx.peer.sa, + ssl->buffers.dtlsCtx.peer.sz); + } } +#endif + if (ret == 0) + ret = wc_HmacFinal(cookieHmac, mac); + + wc_HmacFree(cookieHmac); + WC_FREE_VAR_EX(cookieHmac, ssl->heap, DYNAMIC_TYPE_HMAC); + if (ret != 0) + return ret; + + if (ConstantCompare(cookie + dataSz, mac, macSz) != 0) + return HRR_COOKIE_ERROR; + + return 0; +} + +int TlsCheckCookie(const WOLFSSL* ssl, const byte* cookie, word16 cookieSz) +{ + int ret; + byte cookieType = 0; + byte macSz = 0; #ifndef NO_SHA256 cookieType = WC_SHA256; @@ -6965,44 +7011,46 @@ int TlsCheckCookie(const WOLFSSL* ssl, const byte* cookie, word16 cookieSz) #error "No digest to available to use with HMAC for cookies." #endif /* NO_SHA */ + if ((ssl->buffers.tls13CookieSecret.buffer == NULL || + ssl->buffers.tls13CookieSecret.length == 0) && + (ssl->buffers.tls13CookieSecretSecondary.buffer == NULL || + ssl->buffers.tls13CookieSecretSecondary.length == 0)) { + WOLFSSL_MSG("Missing DTLS 1.3 cookie secret"); + return COOKIE_ERROR; + } + if (cookieSz < ssl->specs.hash_size + macSz) return HRR_COOKIE_ERROR; cookieSz -= macSz; - WC_ALLOC_VAR_EX(cookieHmac, Hmac, 1, ssl->heap, DYNAMIC_TYPE_HMAC, - return MEMORY_E); - - ret = wc_HmacInit(cookieHmac, ssl->heap, ssl->devId); - if (ret == 0) { - ret = wc_HmacSetKey(cookieHmac, cookieType, - ssl->buffers.tls13CookieSecret.buffer, - ssl->buffers.tls13CookieSecret.length); + /* Verify against the primary secret first. If that fails and a secondary + * (verify-only) secret is configured, try that too. This lets a stateless + * DTLS 1.3 server keep accepting cookies issued under the secret it held + * before an application-driven secret rotation. */ + ret = HRR_COOKIE_ERROR; + if (ssl->buffers.tls13CookieSecret.buffer != NULL && + ssl->buffers.tls13CookieSecret.length > 0) { + ret = TlsCheckCookieMac(ssl, cookie, cookieSz, + ssl->buffers.tls13CookieSecret.buffer, + ssl->buffers.tls13CookieSecret.length, cookieType, macSz); + if (ret != 0 && ret != HRR_COOKIE_ERROR) + return ret; } - if (ret == 0) - ret = wc_HmacUpdate(cookieHmac, cookie, cookieSz); -#ifdef WOLFSSL_DTLS13 - /* Tie cookie to peer address */ - if (ret == 0) { - /* peerLock not necessary. Still in handshake phase. */ - if (ssl->options.dtls && ssl->buffers.dtlsCtx.peer.sz > 0) { - ret = wc_HmacUpdate(cookieHmac, - (byte*)ssl->buffers.dtlsCtx.peer.sa, - ssl->buffers.dtlsCtx.peer.sz); - } + if (ret == HRR_COOKIE_ERROR && + ssl->buffers.tls13CookieSecretSecondary.buffer != NULL && + ssl->buffers.tls13CookieSecretSecondary.length > 0) { + ret = TlsCheckCookieMac(ssl, cookie, cookieSz, + ssl->buffers.tls13CookieSecretSecondary.buffer, + ssl->buffers.tls13CookieSecretSecondary.length, cookieType, macSz); + if (ret != 0 && ret != HRR_COOKIE_ERROR) + return ret; } -#endif - if (ret == 0) - ret = wc_HmacFinal(cookieHmac, mac); - - wc_HmacFree(cookieHmac); - WC_FREE_VAR_EX(cookieHmac, ssl->heap, DYNAMIC_TYPE_HMAC); - if (ret != 0) - return ret; - if (ConstantCompare(cookie + cookieSz, mac, macSz) != 0) { + if (ret != 0) { WOLFSSL_ERROR_VERBOSE(HRR_COOKIE_ERROR); return HRR_COOKIE_ERROR; } + return cookieSz; } @@ -14722,11 +14770,99 @@ int wolfSSL_disable_hrr_cookie(WOLFSSL* ssl) ssl->buffers.tls13CookieSecret.length = 0; } + if (ssl->buffers.tls13CookieSecretSecondary.buffer != NULL) { + ForceZero(ssl->buffers.tls13CookieSecretSecondary.buffer, + ssl->buffers.tls13CookieSecretSecondary.length); + XFREE(ssl->buffers.tls13CookieSecretSecondary.buffer, ssl->heap, + DYNAMIC_TYPE_COOKIE_PWD); + ssl->buffers.tls13CookieSecretSecondary.buffer = NULL; + ssl->buffers.tls13CookieSecretSecondary.length = 0; + } + ssl->options.sendCookie = 0; return WOLFSSL_SUCCESS; #endif /* NO_WOLFSSL_SERVER */ } +/* Set a secondary HelloRetryRequest cookie secret used only when verifying a + * received cookie, and only if the primary secret (set by + * wolfSSL_send_hrr_cookie()) fails to verify it. + * + * This supports an application-driven cookie-secret rotation on a stateless + * DTLS 1.3 server: after rotating the primary secret, install the previous + * secret here so that cookies already issued under it are still accepted for + * an overlap window. It is never used to issue cookies. + * + * This API is DTLS only - TLS 1.3 over a reliable transport does not operate + * statelessly across the HelloRetryRequest exchange, so a secondary cookie + * secret has no use there. + * + * ssl SSL/TLS object. + * secret Secondary secret to verify cookies against. A value of NULL (or a + * secretSz of 0) clears any previously set secondary secret. + * secretSz Size of secret data in bytes. + * returns BAD_FUNC_ARG when ssl is NULL, not TLS v1.3 or not DTLS; SIDE_ERROR + * when called on a client; WOLFSSL_SUCCESS on success and otherwise failure. + */ +int wolfSSL_set_hrr_cookie_secret_secondary(WOLFSSL* ssl, + const unsigned char* secret, unsigned int secretSz) +{ + int ret; + + if (ssl == NULL || !IsAtLeastTLSv1_3(ssl->version)) + return BAD_FUNC_ARG; +#ifndef NO_WOLFSSL_SERVER + if (ssl->options.side == WOLFSSL_CLIENT_END) + return SIDE_ERROR; + /* DTLS only - TLS 1.3 does not verify cookies statelessly. */ + if (!ssl->options.dtls) { + WOLFSSL_MSG("Secondary HRR cookie secret is DTLS only"); + return BAD_FUNC_ARG; + } + + /* Clear any existing secondary secret. */ + if (ssl->buffers.tls13CookieSecretSecondary.buffer != NULL) { + ForceZero(ssl->buffers.tls13CookieSecretSecondary.buffer, + ssl->buffers.tls13CookieSecretSecondary.length); + XFREE(ssl->buffers.tls13CookieSecretSecondary.buffer, ssl->heap, + DYNAMIC_TYPE_COOKIE_PWD); + ssl->buffers.tls13CookieSecretSecondary.buffer = NULL; + ssl->buffers.tls13CookieSecretSecondary.length = 0; + } + + /* A NULL/empty secret just clears the secondary secret. */ + if (secret == NULL || secretSz == 0) { + ret = WOLFSSL_SUCCESS; + } + else { + byte* newSecret = (byte*)XMALLOC(secretSz, ssl->heap, + DYNAMIC_TYPE_COOKIE_PWD); + if (newSecret == NULL) { + WOLFSSL_MSG("couldn't allocate secondary cookie secret"); + ret = MEMORY_ERROR; + } + else { + XMEMCPY(newSecret, secret, secretSz); + ssl->buffers.tls13CookieSecretSecondary.buffer = newSecret; + ssl->buffers.tls13CookieSecretSecondary.length = secretSz; + #ifdef WOLFSSL_CHECK_MEM_ZERO + wc_MemZero_Add("wolfSSL_set_hrr_cookie_secret_secondary secret", + ssl->buffers.tls13CookieSecretSecondary.buffer, + ssl->buffers.tls13CookieSecretSecondary.length); + #endif + ret = WOLFSSL_SUCCESS; + } + } +#else + (void)secret; + (void)secretSz; + + ret = SIDE_ERROR; +#endif + + return ret; +} + #endif /* defined(WOLFSSL_SEND_HRR_COOKIE) */ #ifdef HAVE_SUPPORTED_CURVES diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index 572dda6c946..d4dd90f5241 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -5278,6 +5278,866 @@ int test_dtls_old_seq_number(void) } /*-- dtls12_missing_finished (api.c lines 32007,32068) ---*/ +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) && \ + defined(WOLFSSL_SEND_HRR_COOKIE) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) +/* Drive a DTLS 1.3 handshake up to the point where the client has sent its + * second ClientHello (carrying the HRR cookie), with the cookie signed by + * secret s1. Leaves ssl_s about to process that ClientHello. */ +static int test_dtls13_hrr_cookie_pump_to_ch2(struct test_memio_ctx* test_ctx, + WOLFSSL_CTX** ctx_c, WOLFSSL_CTX** ctx_s, WOLFSSL** ssl_c, WOLFSSL** ssl_s, + const byte* s1, word32 s1Sz) +{ + EXPECT_DECLS; + int group = WOLFSSL_ECC_SECP256R1; + + ExpectIntEQ(test_memio_setup(test_ctx, ctx_c, ctx_s, ssl_c, ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + + /* Force a single ECC key share so the ClientHello is not fragmented. */ + ExpectIntEQ(wolfSSL_set_groups(*ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(*ssl_s, &group, 1), WOLFSSL_SUCCESS); + + /* Server signs the HRR cookie with the primary secret s1. */ + ExpectIntEQ(wolfSSL_send_hrr_cookie(*ssl_s, s1, s1Sz), WOLFSSL_SUCCESS); + + /* CH1 */ + ExpectIntEQ(wolfSSL_connect(*ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(*ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + /* HRR (cookie signed with s1) */ + ExpectIntEQ(wolfSSL_accept(*ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(*ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + /* CH2 carrying the s1 cookie */ + ExpectIntEQ(wolfSSL_connect(*ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(*ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + + return EXPECT_RESULT(); +} +#endif + +/* A cookie issued under the previous (now secondary) secret is still accepted + * after an application-driven secret rotation on a stateless DTLS 1.3 server. */ +int test_dtls13_hrr_cookie_secret_secondary(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) && \ + defined(WOLFSSL_SEND_HRR_COOKIE) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte s1[32]; + byte s2[32]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(s1, 0x5A, sizeof(s1)); + XMEMSET(s2, 0xA5, sizeof(s2)); + + ExpectIntEQ(test_dtls13_hrr_cookie_pump_to_ch2(&test_ctx, &ctx_c, &ctx_s, + &ssl_c, &ssl_s, s1, sizeof(s1)), TEST_SUCCESS); + + /* The secondary-secret API is server side only. */ + ExpectIntEQ(wolfSSL_set_hrr_cookie_secret_secondary(ssl_c, s1, sizeof(s1)), + WC_NO_ERR_TRACE(SIDE_ERROR)); + + /* Application rotates the cookie secret before CH2 is processed: s2 becomes + * the primary, and s1 is installed as the secondary (verify-only) secret. */ + ExpectIntEQ(wolfSSL_send_hrr_cookie(ssl_s, s2, sizeof(s2)), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_hrr_cookie_secret_secondary(ssl_s, s1, sizeof(s1)), + WOLFSSL_SUCCESS); + + /* The cookie (signed with s1) must verify against the secondary secret and + * the handshake must complete. */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* A cookie whose signing secret is neither the primary nor the secondary secret + * (rotated out entirely) is rejected with HRR_COOKIE_ERROR. */ +int test_dtls13_hrr_cookie_secret_secondary_dropped(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) && \ + defined(WOLFSSL_SEND_HRR_COOKIE) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte s1[32]; + byte s2[32]; + byte s3[32]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(s1, 0x5A, sizeof(s1)); + XMEMSET(s2, 0x22, sizeof(s2)); + XMEMSET(s3, 0x11, sizeof(s3)); + + ExpectIntEQ(test_dtls13_hrr_cookie_pump_to_ch2(&test_ctx, &ctx_c, &ctx_s, + &ssl_c, &ssl_s, s1, sizeof(s1)), TEST_SUCCESS); + + /* Two rotations have happened: s1 is now neither the primary (s3) nor the + * secondary (s2) secret. */ + ExpectIntEQ(wolfSSL_send_hrr_cookie(ssl_s, s3, sizeof(s3)), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_hrr_cookie_secret_secondary(ssl_s, s2, sizeof(s2)), + WOLFSSL_SUCCESS); + + /* Server drops the ClientHello carrying the unverifiable cookie and sends + * an illegal_parameter alert; the handshake cannot complete. */ + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + { + WOLFSSL_ALERT_HISTORY h; + XMEMSET(&h, 0, sizeof(h)); + ExpectIntEQ(wolfSSL_get_alert_history(ssl_c, &h), WOLFSSL_SUCCESS); + ExpectIntEQ(h.last_rx.code, illegal_parameter); + } + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) && \ + defined(WOLFSSL_SEND_HRR_COOKIE) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) +/* Assert that the pumped CH2 cookie is rejected: the handshake does not + * complete and the client receives an illegal_parameter alert. */ +static int test_dtls13_hrr_assert_rejected(WOLFSSL* ssl_c, WOLFSSL* ssl_s) +{ + EXPECT_DECLS; + WOLFSSL_ALERT_HISTORY h; + + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + XMEMSET(&h, 0, sizeof(h)); + ExpectIntEQ(wolfSSL_get_alert_history(ssl_c, &h), WOLFSSL_SUCCESS); + ExpectIntEQ(h.last_rx.code, illegal_parameter); + + return EXPECT_RESULT(); +} + +/* Set primary=p and secondary=q on a fresh DTLS 1.3 server, then drive the + * handshake until the client has sent CH2. The cookie is issued during the + * HRR step (and per the contract must be signed with the primary p). Leaves + * ssl_s about to process CH2. */ +static int test_dtls13_hrr_issue_with_secondary(struct test_memio_ctx* test_ctx, + WOLFSSL_CTX** ctx_c, WOLFSSL_CTX** ctx_s, WOLFSSL** ssl_c, WOLFSSL** ssl_s, + const byte* p, word32 pSz, const byte* q, word32 qSz) +{ + EXPECT_DECLS; + int group = WOLFSSL_ECC_SECP256R1; + + ExpectIntEQ(test_memio_setup(test_ctx, ctx_c, ctx_s, ssl_c, ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_set_groups(*ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(*ssl_s, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_send_hrr_cookie(*ssl_s, p, pSz), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_hrr_cookie_secret_secondary(*ssl_s, q, qSz), + WOLFSSL_SUCCESS); + + /* CH1 */ + ExpectIntEQ(wolfSSL_connect(*ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(*ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + /* HRR - cookie issued here */ + ExpectIntEQ(wolfSSL_accept(*ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(*ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + /* CH2 carrying the issued cookie */ + ExpectIntEQ(wolfSSL_connect(*ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(*ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + + return EXPECT_RESULT(); +} +#endif + +/* Clearing the secondary secret (NULL/0) makes a cookie issued under it stop + * verifying again. */ +int test_dtls13_hrr_cookie_secret_secondary_cleared(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) && \ + defined(WOLFSSL_SEND_HRR_COOKIE) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte s1[32]; + byte s2[32]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(s1, 0x5A, sizeof(s1)); + XMEMSET(s2, 0xA5, sizeof(s2)); + + ExpectIntEQ(test_dtls13_hrr_cookie_pump_to_ch2(&test_ctx, &ctx_c, &ctx_s, + &ssl_c, &ssl_s, s1, sizeof(s1)), TEST_SUCCESS); + + /* Rotate primary to s2 and install s1 as the secondary - this alone would + * make the s1 cookie verify. */ + ExpectIntEQ(wolfSSL_send_hrr_cookie(ssl_s, s2, sizeof(s2)), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_hrr_cookie_secret_secondary(ssl_s, s1, sizeof(s1)), + WOLFSSL_SUCCESS); + /* But clearing it again must drop it. */ + ExpectIntEQ(wolfSSL_set_hrr_cookie_secret_secondary(ssl_s, NULL, 0), + WOLFSSL_SUCCESS); + + /* The s1 cookie is no longer accepted (only primary s2 remains). */ + ExpectIntEQ(test_dtls13_hrr_assert_rejected(ssl_c, ssl_s), TEST_SUCCESS); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* A cookie that matches the primary secret is accepted on the normal fast path + * even when a (different) secondary secret is configured. */ +int test_dtls13_hrr_cookie_secret_primary_wins(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) && \ + defined(WOLFSSL_SEND_HRR_COOKIE) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte s1[32]; + byte s2[32]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(s1, 0x5A, sizeof(s1)); + XMEMSET(s2, 0xA5, sizeof(s2)); + + ExpectIntEQ(test_dtls13_hrr_cookie_pump_to_ch2(&test_ctx, &ctx_c, &ctx_s, + &ssl_c, &ssl_s, s1, sizeof(s1)), TEST_SUCCESS); + + /* Primary still s1 (matches the cookie); a different secondary is present + * but must not be needed. */ + ExpectIntEQ(wolfSSL_set_hrr_cookie_secret_secondary(ssl_s, s2, sizeof(s2)), + WOLFSSL_SUCCESS); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* Setting the secondary secret equal to the primary is harmless (guards against + * duplication/double-free regressions). */ +int test_dtls13_hrr_cookie_secret_same_as_primary(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) && \ + defined(WOLFSSL_SEND_HRR_COOKIE) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte s1[32]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(s1, 0x5A, sizeof(s1)); + + ExpectIntEQ(test_dtls13_hrr_cookie_pump_to_ch2(&test_ctx, &ctx_c, &ctx_s, + &ssl_c, &ssl_s, s1, sizeof(s1)), TEST_SUCCESS); + + ExpectIntEQ(wolfSSL_set_hrr_cookie_secret_secondary(ssl_s, s1, sizeof(s1)), + WOLFSSL_SUCCESS); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* Replacing the secondary secret keeps only the most recent value: the old + * secondary is discarded and the new one is active (single overlap window). */ +int test_dtls13_hrr_cookie_secret_secondary_replaced(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) && \ + defined(WOLFSSL_SEND_HRR_COOKIE) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte sP[32]; + byte sA[32]; + byte sB[32]; + + XMEMSET(sP, 0x11, sizeof(sP)); + XMEMSET(sA, 0x5A, sizeof(sA)); + XMEMSET(sB, 0xA5, sizeof(sB)); + + /* Replacing secondary sA with sB discards sA: a cookie signed with sA is + * then rejected (neither primary sP nor secondary sB). */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_dtls13_hrr_cookie_pump_to_ch2(&test_ctx, &ctx_c, &ctx_s, + &ssl_c, &ssl_s, sA, sizeof(sA)), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_send_hrr_cookie(ssl_s, sP, sizeof(sP)), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_hrr_cookie_secret_secondary(ssl_s, sA, sizeof(sA)), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_hrr_cookie_secret_secondary(ssl_s, sB, sizeof(sB)), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_hrr_assert_rejected(ssl_c, ssl_s), TEST_SUCCESS); + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); + ssl_c = NULL; ctx_c = NULL; ssl_s = NULL; ctx_s = NULL; + + /* The replacement value sB is active: a cookie signed with sB verifies. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_dtls13_hrr_cookie_pump_to_ch2(&test_ctx, &ctx_c, &ctx_s, + &ssl_c, &ssl_s, sB, sizeof(sB)), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_send_hrr_cookie(ssl_s, sP, sizeof(sP)), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_hrr_cookie_secret_secondary(ssl_s, sA, sizeof(sA)), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_hrr_cookie_secret_secondary(ssl_s, sB, sizeof(sB)), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* The secondary secret is verify-only: cookies are always issued with the + * primary, never the secondary. */ +int test_dtls13_hrr_cookie_secret_issue_uses_primary(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) && \ + defined(WOLFSSL_SEND_HRR_COOKIE) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte p[32]; + byte q[32]; + + XMEMSET(p, 0x5A, sizeof(p)); + XMEMSET(q, 0xA5, sizeof(q)); + + /* Control: with primary=p, secondary=q at issue time, dropping the + * secondary and keeping only the primary p still verifies the cookie - + * confirming the cookie is valid under p. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_dtls13_hrr_issue_with_secondary(&test_ctx, &ctx_c, &ctx_s, + &ssl_c, &ssl_s, p, sizeof(p), q, sizeof(q)), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_set_hrr_cookie_secret_secondary(ssl_s, NULL, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); + ssl_c = NULL; ctx_c = NULL; ssl_s = NULL; ctx_s = NULL; + + /* Test: making the secondary value q the only secret rejects the cookie. + * If issuance had (incorrectly) used the secondary q, the cookie would now + * verify - so rejection proves the cookie was signed with the primary p. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_dtls13_hrr_issue_with_secondary(&test_ctx, &ctx_c, &ctx_s, + &ssl_c, &ssl_s, p, sizeof(p), q, sizeof(q)), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_send_hrr_cookie(ssl_s, q, sizeof(q)), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_hrr_cookie_secret_secondary(ssl_s, NULL, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_hrr_assert_rejected(ssl_c, ssl_s), TEST_SUCCESS); + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* Argument validation for the secondary cookie-secret APIs. */ +int test_dtls13_hrr_cookie_secret_secondary_args(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) && \ + defined(WOLFSSL_SEND_HRR_COOKIE) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte s[16]; + + XMEMSET(s, 0x5A, sizeof(s)); + + /* NULL ssl is rejected by both APIs. */ + ExpectIntEQ(wolfSSL_set_hrr_cookie_secret_secondary(NULL, s, sizeof(s)), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecretSecondary(NULL, s, sizeof(s)), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* DTLS 1.3 objects: client side is rejected, server side accepts, and the + * NULL/0 clear forms both succeed. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_set_hrr_cookie_secret_secondary(ssl_c, s, sizeof(s)), + WC_NO_ERR_TRACE(SIDE_ERROR)); + ExpectIntEQ(wolfSSL_set_hrr_cookie_secret_secondary(ssl_s, s, sizeof(s)), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_hrr_cookie_secret_secondary(ssl_s, NULL, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_hrr_cookie_secret_secondary(ssl_s, s, 0), + WOLFSSL_SUCCESS); + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); + ssl_c = NULL; ctx_c = NULL; ssl_s = NULL; ctx_s = NULL; + + /* The DTLS 1.3 secondary API is DTLS only: a (non-DTLS) TLS 1.3 server is + * rejected. */ + 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(wolfSSL_set_hrr_cookie_secret_secondary(ssl_s, s, sizeof(s)), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); + ssl_c = NULL; ctx_c = NULL; ssl_s = NULL; ctx_s = NULL; + +#ifndef WOLFSSL_NO_TLS12 + /* A non-TLS-1.3 object (DTLS 1.2 server) is rejected by the 1.3 API. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_2_client_method, wolfDTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_set_hrr_cookie_secret_secondary(ssl_s, s, sizeof(s)), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif +#endif + return EXPECT_RESULT(); +} + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS) && \ + !defined(WOLFSSL_NO_TLS12) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) +/* Drive a DTLS 1.2 handshake up to the point where the client has sent its + * second ClientHello (carrying the HelloVerifyRequest cookie), with the cookie + * signed by secret s1. Leaves ssl_s about to process that ClientHello. */ +static int test_dtls12_cookie_pump_to_ch2(struct test_memio_ctx* test_ctx, + WOLFSSL_CTX** ctx_c, WOLFSSL_CTX** ctx_s, WOLFSSL** ssl_c, WOLFSSL** ssl_s, + const byte* s1, word32 s1Sz) +{ + EXPECT_DECLS; + + ExpectIntEQ(test_memio_setup(test_ctx, ctx_c, ctx_s, ssl_c, ssl_s, + wolfDTLSv1_2_client_method, wolfDTLSv1_2_server_method), 0); + + /* Server signs the HelloVerifyRequest cookie with the primary secret s1. */ + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecret(*ssl_s, s1, s1Sz), 0); + + /* CH1 */ + ExpectIntEQ(wolfSSL_negotiate(*ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(*ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + /* HVR (cookie signed with s1) */ + ExpectIntEQ(wolfSSL_negotiate(*ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(*ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + /* CH2 carrying the s1 cookie */ + ExpectIntEQ(wolfSSL_negotiate(*ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(*ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + + return EXPECT_RESULT(); +} + +/* Set primary=p and secondary=q on a fresh DTLS 1.2 server, then drive the + * handshake until the client has sent CH2. The cookie is issued in the + * HelloVerifyRequest (and per the contract must be signed with the primary p). + * Leaves ssl_s about to process CH2. */ +static int test_dtls12_cookie_issue_with_secondary( + struct test_memio_ctx* test_ctx, WOLFSSL_CTX** ctx_c, WOLFSSL_CTX** ctx_s, + WOLFSSL** ssl_c, WOLFSSL** ssl_s, const byte* p, word32 pSz, + const byte* q, word32 qSz) +{ + EXPECT_DECLS; + + ExpectIntEQ(test_memio_setup(test_ctx, ctx_c, ctx_s, ssl_c, ssl_s, + wolfDTLSv1_2_client_method, wolfDTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecret(*ssl_s, p, pSz), 0); + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecretSecondary(*ssl_s, q, qSz), 0); + + /* CH1 */ + ExpectIntEQ(wolfSSL_negotiate(*ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(*ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + /* HVR - cookie issued here */ + ExpectIntEQ(wolfSSL_negotiate(*ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(*ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + /* CH2 carrying the issued cookie */ + ExpectIntEQ(wolfSSL_negotiate(*ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(*ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + + return EXPECT_RESULT(); +} +#endif + +/* A DTLS 1.2 cookie issued under the previous (now secondary) secret is still + * accepted after an application-driven secret rotation: the server becomes + * stateful immediately instead of issuing another HelloVerifyRequest. */ +int test_dtls12_cookie_secret_secondary(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS) && \ + !defined(WOLFSSL_NO_TLS12) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte s1[16]; + byte s2[16]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(s1, 0x5A, sizeof(s1)); + XMEMSET(s2, 0xA5, sizeof(s2)); + + ExpectIntEQ(test_dtls12_cookie_pump_to_ch2(&test_ctx, &ctx_c, &ctx_s, + &ssl_c, &ssl_s, s1, sizeof(s1)), TEST_SUCCESS); + + /* Application rotates the cookie secret before CH2 is processed: s2 becomes + * the primary, and s1 is installed as the secondary (verify-only) secret. */ + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecret(ssl_s, s2, sizeof(s2)), 0); + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecretSecondary(ssl_s, s1, sizeof(s1)), 0); + + /* Server processes CH2: the cookie verifies against the secondary secret, + * so the server enters stateful processing rather than re-issuing an HVR. */ + ExpectIntEQ(wolfSSL_negotiate(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + + /* And the handshake completes. */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* When the signing secret has been rotated out of both the primary and the + * secondary slot, the DTLS 1.2 cookie no longer verifies, so the server stays + * stateless and re-issues a HelloVerifyRequest instead of accepting CH2. */ +int test_dtls12_cookie_secret_secondary_dropped(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS) && \ + !defined(WOLFSSL_NO_TLS12) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte s1[16]; + byte s2[16]; + byte s3[16]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(s1, 0x5A, sizeof(s1)); + XMEMSET(s2, 0x22, sizeof(s2)); + XMEMSET(s3, 0x11, sizeof(s3)); + + ExpectIntEQ(test_dtls12_cookie_pump_to_ch2(&test_ctx, &ctx_c, &ctx_s, + &ssl_c, &ssl_s, s1, sizeof(s1)), TEST_SUCCESS); + + /* Two rotations have happened: s1 is now neither the primary (s3) nor the + * secondary (s2) secret. */ + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecret(ssl_s, s3, sizeof(s3)), 0); + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecretSecondary(ssl_s, s2, sizeof(s2)), 0); + + /* Server processes CH2: the cookie does not verify, so it stays stateless + * (re-issuing a HelloVerifyRequest) rather than accepting the ClientHello. */ + test_memio_clear_buffer(&test_ctx, 1); /* drop anything queued to client */ + ExpectIntEQ(wolfSSL_negotiate(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + /* And it actually emitted a fresh HelloVerifyRequest to the client. */ + ExpectIntGT(test_ctx.c_len, DTLS_RECORD_HEADER_SZ); + ExpectIntEQ(test_ctx.c_buff[0], handshake); + ExpectIntEQ(test_ctx.c_buff[DTLS_RECORD_HEADER_SZ], hello_verify_request); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* Clearing the DTLS 1.2 secondary secret (NULL/0) makes a cookie issued under + * it stop verifying again. */ +int test_dtls12_cookie_secret_secondary_cleared(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS) && \ + !defined(WOLFSSL_NO_TLS12) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte s1[16]; + byte s2[16]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(s1, 0x5A, sizeof(s1)); + XMEMSET(s2, 0xA5, sizeof(s2)); + + ExpectIntEQ(test_dtls12_cookie_pump_to_ch2(&test_ctx, &ctx_c, &ctx_s, + &ssl_c, &ssl_s, s1, sizeof(s1)), TEST_SUCCESS); + + /* Rotate primary to s2 and install s1 as the secondary - this alone would + * make the s1 cookie verify - then clear the secondary again. */ + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecret(ssl_s, s2, sizeof(s2)), 0); + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecretSecondary(ssl_s, s1, sizeof(s1)), 0); + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecretSecondary(ssl_s, NULL, 0), 0); + + /* Server processes CH2: the s1 cookie no longer verifies (only primary s2 + * remains), so the server stays stateless. */ + ExpectIntEQ(wolfSSL_negotiate(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* A DTLS 1.2 cookie that matches the primary secret is accepted on the normal + * fast path even when a (different) secondary secret is configured. */ +int test_dtls12_cookie_secret_primary_wins(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS) && \ + !defined(WOLFSSL_NO_TLS12) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte s1[16]; + byte s2[16]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(s1, 0x5A, sizeof(s1)); + XMEMSET(s2, 0xA5, sizeof(s2)); + + ExpectIntEQ(test_dtls12_cookie_pump_to_ch2(&test_ctx, &ctx_c, &ctx_s, + &ssl_c, &ssl_s, s1, sizeof(s1)), TEST_SUCCESS); + + /* Primary still s1 (matches the cookie); a different secondary is present + * but must not be needed. */ + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecretSecondary(ssl_s, s2, sizeof(s2)), 0); + + ExpectIntEQ(wolfSSL_negotiate(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* Setting the DTLS 1.2 secondary secret equal to the primary is harmless + * (guards against duplication/double-free regressions). */ +int test_dtls12_cookie_secret_same_as_primary(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS) && \ + !defined(WOLFSSL_NO_TLS12) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte s1[16]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(s1, 0x5A, sizeof(s1)); + + ExpectIntEQ(test_dtls12_cookie_pump_to_ch2(&test_ctx, &ctx_c, &ctx_s, + &ssl_c, &ssl_s, s1, sizeof(s1)), TEST_SUCCESS); + + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecretSecondary(ssl_s, s1, sizeof(s1)), 0); + + ExpectIntEQ(wolfSSL_negotiate(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* Replacing the DTLS 1.2 secondary secret keeps only the most recent value: + * the old secondary is discarded and the new one is active. */ +int test_dtls12_cookie_secret_secondary_replaced(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS) && \ + !defined(WOLFSSL_NO_TLS12) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte sP[16]; + byte sA[16]; + byte sB[16]; + + XMEMSET(sP, 0x11, sizeof(sP)); + XMEMSET(sA, 0x5A, sizeof(sA)); + XMEMSET(sB, 0xA5, sizeof(sB)); + + /* Replacing secondary sA with sB discards sA: a cookie signed with sA is + * then not accepted (neither primary sP nor secondary sB). */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_dtls12_cookie_pump_to_ch2(&test_ctx, &ctx_c, &ctx_s, + &ssl_c, &ssl_s, sA, sizeof(sA)), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecret(ssl_s, sP, sizeof(sP)), 0); + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecretSecondary(ssl_s, sA, sizeof(sA)), 0); + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecretSecondary(ssl_s, sB, sizeof(sB)), 0); + ExpectIntEQ(wolfSSL_negotiate(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); + ssl_c = NULL; ctx_c = NULL; ssl_s = NULL; ctx_s = NULL; + + /* The replacement value sB is active: a cookie signed with sB verifies. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_dtls12_cookie_pump_to_ch2(&test_ctx, &ctx_c, &ctx_s, + &ssl_c, &ssl_s, sB, sizeof(sB)), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecret(ssl_s, sP, sizeof(sP)), 0); + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecretSecondary(ssl_s, sA, sizeof(sA)), 0); + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecretSecondary(ssl_s, sB, sizeof(sB)), 0); + ExpectIntEQ(wolfSSL_negotiate(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* The DTLS 1.2 secondary secret is verify-only: cookies are always issued with + * the primary, never the secondary. */ +int test_dtls12_cookie_secret_issue_uses_primary(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS) && \ + !defined(WOLFSSL_NO_TLS12) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte p[16]; + byte q[16]; + + XMEMSET(p, 0x5A, sizeof(p)); + XMEMSET(q, 0xA5, sizeof(q)); + + /* Control: with primary=p, secondary=q at issue time, dropping the + * secondary and keeping only the primary p still verifies the cookie - + * confirming the cookie is valid under p. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_dtls12_cookie_issue_with_secondary(&test_ctx, &ctx_c, + &ctx_s, &ssl_c, &ssl_s, p, sizeof(p), q, sizeof(q)), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecretSecondary(ssl_s, NULL, 0), 0); + ExpectIntEQ(wolfSSL_negotiate(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); + ssl_c = NULL; ctx_c = NULL; ssl_s = NULL; ctx_s = NULL; + + /* Test: making the secondary value q the only secret stops the cookie from + * verifying. If issuance had (incorrectly) used the secondary q, the + * cookie would now verify - so rejection proves it was signed with the + * primary p. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_dtls12_cookie_issue_with_secondary(&test_ctx, &ctx_c, + &ctx_s, &ssl_c, &ssl_s, p, sizeof(p), q, sizeof(q)), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecret(ssl_s, q, sizeof(q)), 0); + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecretSecondary(ssl_s, NULL, 0), 0); + ExpectIntEQ(wolfSSL_negotiate(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + int test_dtls12_missing_finished(void) { EXPECT_DECLS; diff --git a/tests/api/test_dtls.h b/tests/api/test_dtls.h index fd22d125bfc..a64529fd69e 100644 --- a/tests/api/test_dtls.h +++ b/tests/api/test_dtls.h @@ -59,6 +59,21 @@ int test_dtls_dropped_ccs(void); int test_dtls_seq_num_downgrade(void); int test_dtls_old_seq_number(void); int test_dtls12_missing_finished(void); +int test_dtls12_cookie_secret_secondary(void); +int test_dtls12_cookie_secret_secondary_dropped(void); +int test_dtls12_cookie_secret_secondary_cleared(void); +int test_dtls12_cookie_secret_primary_wins(void); +int test_dtls12_cookie_secret_same_as_primary(void); +int test_dtls12_cookie_secret_secondary_replaced(void); +int test_dtls12_cookie_secret_issue_uses_primary(void); +int test_dtls13_hrr_cookie_secret_secondary(void); +int test_dtls13_hrr_cookie_secret_secondary_dropped(void); +int test_dtls13_hrr_cookie_secret_secondary_cleared(void); +int test_dtls13_hrr_cookie_secret_primary_wins(void); +int test_dtls13_hrr_cookie_secret_same_as_primary(void); +int test_dtls13_hrr_cookie_secret_secondary_replaced(void); +int test_dtls13_hrr_cookie_secret_issue_uses_primary(void); +int test_dtls13_hrr_cookie_secret_secondary_args(void); int test_wolfSSL_dtls_export(void); int test_wolfSSL_dtls_export_peers(void); int test_wolfSSL_dtls_import_state_extra_window_words(void); @@ -135,5 +150,20 @@ int test_WOLFSSL_dtls_version_alert(void); TEST_DECL_GROUP("dtls", test_dtls_seq_num_downgrade), \ TEST_DECL_GROUP("dtls", test_dtls_old_seq_number), \ TEST_DECL_GROUP("dtls", test_dtls12_missing_finished), \ - TEST_DECL_GROUP("dtls", test_dtls12_export_import_etm) + TEST_DECL_GROUP("dtls", test_dtls12_export_import_etm), \ + TEST_DECL_GROUP("dtls", test_dtls12_cookie_secret_secondary), \ + TEST_DECL_GROUP("dtls", test_dtls12_cookie_secret_secondary_dropped), \ + TEST_DECL_GROUP("dtls", test_dtls12_cookie_secret_secondary_cleared), \ + TEST_DECL_GROUP("dtls", test_dtls12_cookie_secret_primary_wins), \ + TEST_DECL_GROUP("dtls", test_dtls12_cookie_secret_same_as_primary), \ + TEST_DECL_GROUP("dtls", test_dtls12_cookie_secret_secondary_replaced), \ + TEST_DECL_GROUP("dtls", test_dtls12_cookie_secret_issue_uses_primary), \ + TEST_DECL_GROUP("dtls", test_dtls13_hrr_cookie_secret_secondary), \ + TEST_DECL_GROUP("dtls", test_dtls13_hrr_cookie_secret_secondary_dropped), \ + TEST_DECL_GROUP("dtls", test_dtls13_hrr_cookie_secret_secondary_cleared), \ + TEST_DECL_GROUP("dtls", test_dtls13_hrr_cookie_secret_primary_wins), \ + TEST_DECL_GROUP("dtls", test_dtls13_hrr_cookie_secret_same_as_primary), \ + TEST_DECL_GROUP("dtls", test_dtls13_hrr_cookie_secret_secondary_replaced), \ + TEST_DECL_GROUP("dtls", test_dtls13_hrr_cookie_secret_issue_uses_primary), \ + TEST_DECL_GROUP("dtls", test_dtls13_hrr_cookie_secret_secondary_args) #endif /* TESTS_API_DTLS_H */ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 8b3231227ba..510ac4446a7 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -5029,11 +5029,22 @@ typedef struct Buffers { #endif #ifdef WOLFSSL_SEND_HRR_COOKIE buffer tls13CookieSecret; /* HRR cookie secret */ + /* Secondary HRR cookie secret, used only when verifying a cookie if the + * primary secret fails. Lets a stateless DTLS 1.3 server keep accepting + * cookies issued under the secret it had before an application-driven + * rotation. DTLS only - never used to issue cookies. */ + buffer tls13CookieSecretSecondary; #endif #ifdef WOLFSSL_DTLS WOLFSSL_DTLS_CTX dtlsCtx; /* DTLS connection context */ #ifndef NO_WOLFSSL_SERVER buffer dtlsCookieSecret; /* DTLS cookie secret */ + /* Secondary DTLS 1.2 cookie secret, used only when verifying a + * received HelloVerifyRequest cookie if the primary secret fails. + * Lets a stateless server keep accepting cookies issued under the + * secret it had before an application-driven rotation. Never used to + * issue cookies. */ + buffer dtlsCookieSecretSecondary; #endif /* NO_WOLFSSL_SERVER */ #endif #ifdef HAVE_PK_CALLBACKS diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index 713c5a55c07..bea0bcc1e26 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -1488,6 +1488,8 @@ WOLFSSL_API int wolfSSL_set1_groups_list(WOLFSSL *ssl, const char *list); #ifdef WOLFSSL_TLS13 WOLFSSL_API int wolfSSL_send_hrr_cookie(WOLFSSL* ssl, const unsigned char* secret, unsigned int secretSz); +WOLFSSL_API int wolfSSL_set_hrr_cookie_secret_secondary(WOLFSSL* ssl, + const unsigned char* secret, unsigned int secretSz); WOLFSSL_API int wolfSSL_disable_hrr_cookie(WOLFSSL * ssl); WOLFSSL_API int wolfSSL_CTX_no_ticket_TLSv13(WOLFSSL_CTX* ctx); WOLFSSL_API int wolfSSL_no_ticket_TLSv13(WOLFSSL* ssl); @@ -3953,6 +3955,7 @@ WOLFSSL_API void wolfSSL_SetFuzzerCb(WOLFSSL* ssl, CallbackFuzzer cbf, void* fCt WOLFSSL_API int wolfSSL_DTLS_SetCookieSecret(WOLFSSL* ssl, const byte* secret, word32 secretSz); +WOLFSSL_API int wolfSSL_DTLS_SetCookieSecretSecondary(WOLFSSL* ssl, const byte* secret, word32 secretSz); /* CA cache callbacks */