From c5c37ffa360a2a340c00e83bb68f5bf42084d3b8 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Thu, 22 Oct 2026 11:22:14 +0000 Subject: [PATCH 01/10] Fix data races on the default ticket key context DefTicketEncCb() checked whether the key context was set up, and chose an encryption key, without holding the context's lock. A thread that found the keys ready then used the name, keys and expiry with nothing ordering it after the thread that set them up. The same reads raced with wolfSSL_CTX_set_tlsext_ticket_keys(), which replaces them under the lock. Only decryption held the lock, for the whole AEAD. Make the lock a wolfSSL_RwLock. The IV is made before any lock. Readers choose the key under the read lock, copy it, and run the AEAD on the copy outside the lock. Set up, key generation and wolfSSL_CTX_set_tlsext_ticket_keys() take the write lock. A read lock can't be upgraded, so a reader that needs a change takes the write lock and checks again: another thread may have done it. The copy is zeroized after use. Tickets only run in parallel with --enable-rwlock. Otherwise the lock is a mutex, held just for the copy. Tests: threads use one context's callback while another sets the keys, and threads that find no key valid for encryption race for the write lock: one key is made and every ticket decrypts. --- ChangeLog.md | 9 ++ src/internal.c | 301 +++++++++++++++++++---------------- src/ssl_api_ext.c | 16 +- tests/api/test_session.c | 330 +++++++++++++++++++++++++++++++++++++++ tests/api/test_session.h | 4 + wolfssl/internal.h | 7 +- 6 files changed, 519 insertions(+), 148 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 8b6fa169658..9143853e8eb 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -130,6 +130,15 @@ ## Bug Fixes +* **Fix (data races on the default session ticket key context)**: + `DefTicketEncCb()` read whether the context's ticket keys were set up, and + chose the encryption key, without the context's lock, racing the thread + that set them up and `wolfSSL_CTX_set_tlsext_ticket_keys()` replacing them. + The lock is now a `wolfSSL_RwLock`: readers copy the key under the read + lock and run the AEAD on the copy, writers take the write lock. Tickets run + in parallel with `--enable-rwlock`; otherwise the lock is a mutex held only + for the copy. + * Fixed `wc_PKCS7_DecodeEnvelopedData()` and `wc_PKCS7_DecodeAuthEnvelopedData()` failing on a message addressed to more than one recipient; AuthEnvelopedData never supported it at all. A message carrying no recipient for the reader now reports `PKCS7_RECIP_E` rather than a parse error. Streaming an AuthEnvelopedData now buffers the whole RecipientInfo set, as the EnvelopedData decoder already did, so peak memory rises by the size of that set. by @Frauschi (PR 11350) * Fixed `wc_AsconAEAD128_SetAD` called more than once: the function diff --git a/src/internal.c b/src/internal.c index 34fc49a735b..515a9269c19 100644 --- a/src/internal.c +++ b/src/internal.c @@ -46932,7 +46932,7 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) * @param [in] ctx SSL context. * @param [in] keyCtx Context for session ticket encryption. * @return 0 on success. - * @return BAD_MUTEX_E when initializing mutex fails. + * @return BAD_MUTEX_E when initializing the lock fails. */ static int TicketEncCbCtx_Init(WOLFSSL_CTX* ctx, TicketEncCbCtx* keyCtx) { @@ -46951,7 +46951,7 @@ static int TicketEncCbCtx_Init(WOLFSSL_CTX* ctx, TicketEncCbCtx* keyCtx) #endif #ifndef SINGLE_THREADED - ret = wc_InitMutex(&keyCtx->mutex); + ret = wc_InitRwLock(&keyCtx->lock); #endif return ret; @@ -46960,7 +46960,7 @@ static int TicketEncCbCtx_Init(WOLFSSL_CTX* ctx, TicketEncCbCtx* keyCtx) /* Setup the session ticket encryption context for this. * * Initialize RNG, generate name, generate primary key and set primary key - * expirary. + * expirary. Call with the write lock held. * * @param [in] keyCtx Context for session ticket encryption. * @param [in] heap Dynamic memory allocation hint. @@ -46972,29 +46972,21 @@ static int TicketEncCbCtx_Setup(TicketEncCbCtx* keyCtx, void* heap, int devId) { int ret; -#ifndef SINGLE_THREADED - ret = 0; - - /* Check that key wasn't set up while waiting. */ - if (keyCtx->expirary[0] == 0) -#endif - { - ret = wc_InitRng_ex(&keyCtx->rng, heap, devId); - if (ret == 0) { - ret = wc_RNG_GenerateBlock(&keyCtx->rng, keyCtx->name, - sizeof(keyCtx->name)); - } - if (ret == 0) { - /* Mask of the bottom bit - used for index of key. */ - keyCtx->name[WOLFSSL_TICKET_NAME_SZ - 1] &= 0xfe; + ret = wc_InitRng_ex(&keyCtx->rng, heap, devId); + if (ret == 0) { + ret = wc_RNG_GenerateBlock(&keyCtx->rng, keyCtx->name, + sizeof(keyCtx->name)); + } + if (ret == 0) { + /* Mask of the bottom bit - used for index of key. */ + keyCtx->name[WOLFSSL_TICKET_NAME_SZ - 1] &= 0xfe; - /* Generate initial primary key. */ - ret = wc_RNG_GenerateBlock(&keyCtx->rng, keyCtx->key[0], - WOLFSSL_TICKET_KEY_SZ); - } - if (ret == 0) { - keyCtx->expirary[0] = LowResTimer() + WOLFSSL_TICKET_KEY_LIFETIME; - } + /* Generate initial primary key. */ + ret = wc_RNG_GenerateBlock(&keyCtx->rng, keyCtx->key[0], + WOLFSSL_TICKET_KEY_SZ); + } + if (ret == 0) { + keyCtx->expirary[0] = LowResTimer() + WOLFSSL_TICKET_KEY_LIFETIME; } return ret; @@ -47019,7 +47011,7 @@ static void TicketEncCbCtx_Free(TicketEncCbCtx* keyCtx) #endif #ifndef SINGLE_THREADED - wc_FreeMutex(&keyCtx->mutex); + wc_FreeRwLock(&keyCtx->lock); #endif wc_FreeRng(&keyCtx->rng); } @@ -47308,7 +47300,7 @@ static int TicketEncDec(byte* key, int keyLen, byte* iv, byte* aad, int aadSz, * * Generate a new key if the current ones are expired. * If the secondary key has not been used and the primary key has expired then - * generate a new primary key. + * generate a new primary key. Call with the write lock held. * * @param [in] Ticket encryption callback context. * @param [in] Session ticket lifetime. @@ -47360,6 +47352,119 @@ static int TicketEncCbCtx_ChooseKey(TicketEncCbCtx* keyCtx, int ticketHint, return ret; } +/* Find a key valid for encryption, without generating one. Call with the key + * context locked. Returns its index, or -1. */ +static int TicketEncCbCtx_FindKey(const TicketEncCbCtx* keyCtx, int ticketHint) +{ + word32 now = LowResTimer(); + + /* Use a key while its encryption expirary isn't imminent. */ + if (keyCtx->expirary[0] > now + ticketHint) { + return 0; + } + if (keyCtx->expirary[1] > now + ticketHint) { + return 1; + } + return -1; +} + +/* Copy key keyIdx. Call with the key context locked. Encrypting: also return + * the key name. Decrypting: check the name and expiry first. */ +static int TicketEncCbCtx_CopyKey(const TicketEncCbCtx* keyCtx, int enc, + byte* key_name, int keyIdx, byte* key) +{ + if (enc) { + XMEMCPY(key_name, keyCtx->name, WOLFSSL_TICKET_NAME_SZ); + } + else { + /* Mask of last bit that is the key index. */ + byte lastByte = key_name[WOLFSSL_TICKET_NAME_SZ - 1] & 0xfe; + + /* For decryption, see if we know this key - check all but last byte. */ + if (XMEMCMP(key_name, keyCtx->name, WOLFSSL_TICKET_NAME_SZ - 1) != 0) { + return WOLFSSL_TICKET_RET_FATAL; + } + /* Ensure last byte without index bit matches too. */ + if (lastByte != keyCtx->name[WOLFSSL_TICKET_NAME_SZ - 1]) { + return WOLFSSL_TICKET_RET_FATAL; + } + /* Check expirary */ + if (keyCtx->expirary[keyIdx] <= LowResTimer()) { + return WOLFSSL_TICKET_RET_REJECT; + } + } + + XMEMCPY(key, keyCtx->key[keyIdx], WOLFSSL_TICKET_KEY_SZ); + + return 0; +} + +/* Get a copy of the key for a ticket, under the read lock. The write lock is + * taken only to set up the keys or generate one, and the state checked again + * under it: another thread may have done it. */ +static int TicketEncCbCtx_GetKey(WOLFSSL* ssl, TicketEncCbCtx* keyCtx, + int enc, byte* key_name, int* keyIdx, + byte* key) +{ + int ret = 0; + int found = 0; + WOLFSSL_CTX* ctx = keyCtx->ctx; + +#ifndef SINGLE_THREADED + if (wc_LockRwLock_Rd(&keyCtx->lock) != 0) { + WOLFSSL_MSG("Couldn't lock key context"); + return WOLFSSL_TICKET_RET_REJECT; + } +#endif + /* Keys are set up once the primary key has an expirary. */ + if (keyCtx->expirary[0] != 0) { + if (enc) { + *keyIdx = TicketEncCbCtx_FindKey(keyCtx, ctx->ticketHint); + } + if (*keyIdx >= 0) { + ret = TicketEncCbCtx_CopyKey(keyCtx, enc, key_name, *keyIdx, key); + found = 1; + } + } +#ifndef SINGLE_THREADED + wc_UnLockRwLock(&keyCtx->lock); +#endif + if (found) { + return ret; + } + +#ifndef SINGLE_THREADED + if (wc_LockRwLock_Wr(&keyCtx->lock) != 0) { + WOLFSSL_MSG("Couldn't lock key context"); + return WOLFSSL_TICKET_RET_REJECT; + } +#endif + ret = 0; + /* Check again: another thread may have set up the keys or generated a + * key while no lock was held. */ + if (keyCtx->expirary[0] == 0) { + /* Sets expirary of primary key in setup. */ + ret = TicketEncCbCtx_Setup(keyCtx, ssl->ctx->heap, ssl->ctx->devId); + } + if ((ret == 0) && enc) { + *keyIdx = TicketEncCbCtx_FindKey(keyCtx, ctx->ticketHint); + if (*keyIdx < 0) { + ret = TicketEncCbCtx_ChooseKey(keyCtx, ctx->ticketHint, keyIdx); + if (ret != 0) { + ret = WOLFSSL_TICKET_RET_REJECT; + } + } + } + if (ret == 0) { + ret = TicketEncCbCtx_CopyKey(keyCtx, enc, key_name, *keyIdx, key); + } +#ifndef SINGLE_THREADED + wc_UnLockRwLock(&keyCtx->lock); +#endif + + return ret; +} + /* Default Session Ticket encryption/decryption callback. * * Use ChaCha20-Poly1305, AES-GCM or SM4-GCM to encrypt/decrypt the ticket. @@ -47379,6 +47484,8 @@ static int TicketEncCbCtx_ChooseKey(TicketEncCbCtx* keyCtx, int ticketHint, * that if one ticket is only valid for decryption, then the other will be * valid for encryption. * AAD = key_name | iv | ticket len (16-bits network order) + * The key is copied under the key context's lock. The ticket is encrypted or + * decrypted with the copy, outside the lock. * * @param [in] ssl SSL connection. * @param [in,out] key_name Name of key from client. @@ -47406,11 +47513,11 @@ static int DefTicketEncCb(WOLFSSL* ssl, byte key_name[WOLFSSL_TICKET_NAME_SZ], { int ret; TicketEncCbCtx* keyCtx = (TicketEncCbCtx*)userCtx; - WOLFSSL_CTX* ctx = keyCtx->ctx; word16 sLen = XHTONS((word16)inLen); byte aad[WOLFSSL_TICKET_NAME_SZ + WOLFSSL_TICKET_IV_SZ + sizeof(sLen)]; int aadSz = WOLFSSL_TICKET_NAME_SZ + WOLFSSL_TICKET_IV_SZ + sizeof(sLen); byte* p = aad; + byte key[WOLFSSL_TICKET_KEY_SZ]; int keyIdx = 0; WOLFSSL_ENTER("DefTicketEncCb"); @@ -47420,29 +47527,7 @@ static int DefTicketEncCb(WOLFSSL* ssl, byte key_name[WOLFSSL_TICKET_NAME_SZ], return BUFFER_E; } - /* Check we have setup the RNG, name and primary key. */ - if (keyCtx->expirary[0] == 0) { -#ifndef SINGLE_THREADED - /* Lock around access to expirary and key - stop initial key being - * generated twice at the same time. */ - if (wc_LockMutex(&keyCtx->mutex) != 0) { - WOLFSSL_MSG("Couldn't lock key context mutex"); - return WOLFSSL_TICKET_RET_REJECT; - } -#endif - /* Sets expirary of primary key in setup. */ - ret = TicketEncCbCtx_Setup(keyCtx, ssl->ctx->heap, ssl->ctx->devId); -#ifndef SINGLE_THREADED - wc_UnLockMutex(&keyCtx->mutex); -#endif - if (ret != 0) - return ret; - } - if (enc) { - /* Return the name of the key - missing key index. */ - XMEMCPY(key_name, keyCtx->name, WOLFSSL_TICKET_NAME_SZ); - /* Generate a new IV into buffer to be returned. * Don't use the RNG in keyCtx as it's for generating private data. */ ret = wc_RNG_GenerateBlock(ssl->rng, iv, WOLFSSL_TICKET_IV_SZ); @@ -47450,106 +47535,48 @@ static int DefTicketEncCb(WOLFSSL* ssl, byte key_name[WOLFSSL_TICKET_NAME_SZ], return WOLFSSL_TICKET_RET_REJECT; } } - else { - /* Mask of last bit that is the key index. */ - byte lastByte = key_name[WOLFSSL_TICKET_NAME_SZ - 1] & 0xfe; - - /* For decryption, see if we know this key - check all but last byte. */ - if (XMEMCMP(key_name, keyCtx->name, WOLFSSL_TICKET_NAME_SZ - 1) != 0) { - return WOLFSSL_TICKET_RET_FATAL; - } - /* Ensure last byte without index bit matches too. */ - if (lastByte != keyCtx->name[WOLFSSL_TICKET_NAME_SZ - 1]) { - return WOLFSSL_TICKET_RET_FATAL; - } - } - - /* Build AAD from: key name, iv, and length of ticket. */ - XMEMCPY(p, keyCtx->name, WOLFSSL_TICKET_NAME_SZ); - p += WOLFSSL_TICKET_NAME_SZ; - XMEMCPY(p, iv, WOLFSSL_TICKET_IV_SZ); - p += WOLFSSL_TICKET_IV_SZ; - XMEMCPY(p, &sLen, sizeof(sLen)); - - /* Encrypt ticket. */ - if (enc) { - word32 now; - - now = LowResTimer(); - /* As long as encryption expirary isn't imminent - no lock. */ - if (keyCtx->expirary[0] > now + ctx->ticketHint) { - keyIdx = 0; - } - else if (keyCtx->expirary[1] > now + ctx->ticketHint) { - keyIdx = 1; - } - else { -#ifndef SINGLE_THREADED - /* Lock around access to expirary and key - stop key being generated - * twice at the same time. */ - if (wc_LockMutex(&keyCtx->mutex) != 0) { - WOLFSSL_MSG("Couldn't lock key context mutex"); - return WOLFSSL_TICKET_RET_REJECT; - } -#endif - ret = TicketEncCbCtx_ChooseKey(keyCtx, ctx->ticketHint, &keyIdx); -#ifndef SINGLE_THREADED - wc_UnLockMutex(&keyCtx->mutex); -#endif - if (ret != 0) { - return WOLFSSL_TICKET_RET_REJECT; - } - } - /* Set the name of the key to the index chosen. */ - key_name[WOLFSSL_TICKET_NAME_SZ - 1] |= keyIdx; - /* Update AAD too. */ - aad[WOLFSSL_TICKET_NAME_SZ - 1] |= keyIdx; - - /* Encrypt ticket data. */ - ret = TicketEncDec(keyCtx->key[keyIdx], WOLFSSL_TICKET_KEY_SZ, iv, aad, - aadSz, ticket, inLen, ticket, outLen, mac, ssl->heap, - 1); - if (ret != 0) return WOLFSSL_TICKET_RET_REJECT; - } - /* Decrypt ticket. */ else { /* Get index of key from name. */ keyIdx = key_name[WOLFSSL_TICKET_NAME_SZ - 1] & 0x1; - /* Update AAD with index. */ - aad[WOLFSSL_TICKET_NAME_SZ - 1] |= keyIdx; + } -#ifndef SINGLE_THREADED - /* Hold the lock over the key/expiry read so a concurrent regen can't swap the key mid-decrypt. */ - if (wc_LockMutex(&keyCtx->mutex) != 0) { - WOLFSSL_MSG("Couldn't lock key context mutex"); - return WOLFSSL_TICKET_RET_REJECT; - } -#endif - /* Check expirary */ - if (keyCtx->expirary[keyIdx] <= LowResTimer()) { -#ifndef SINGLE_THREADED - wc_UnLockMutex(&keyCtx->mutex); +#ifdef WOLFSSL_CHECK_MEM_ZERO + wc_MemZero_Add("DefTicketEncCb key", key, sizeof(key)); #endif - return WOLFSSL_TICKET_RET_REJECT; + ret = TicketEncCbCtx_GetKey(ssl, keyCtx, enc, key_name, &keyIdx, key); + if (ret == 0) { + if (enc) { + /* Set the name of the key to the index chosen. */ + key_name[WOLFSSL_TICKET_NAME_SZ - 1] |= keyIdx; } - /* Decrypt ticket data. */ - ret = TicketEncDec(keyCtx->key[keyIdx], WOLFSSL_TICKET_KEY_SZ, iv, aad, - aadSz, ticket, inLen, ticket, outLen, mac, ssl->heap, - 0); -#ifndef SINGLE_THREADED - wc_UnLockMutex(&keyCtx->mutex); -#endif + /* Build AAD from: key name, iv, and length of ticket. The key name is + * the context's name with the key index. */ + XMEMCPY(p, key_name, WOLFSSL_TICKET_NAME_SZ); + p += WOLFSSL_TICKET_NAME_SZ; + XMEMCPY(p, iv, WOLFSSL_TICKET_IV_SZ); + p += WOLFSSL_TICKET_IV_SZ; + XMEMCPY(p, &sLen, sizeof(sLen)); + + /* Encrypt/decrypt ticket data. */ + ret = TicketEncDec(key, WOLFSSL_TICKET_KEY_SZ, iv, aad, aadSz, ticket, + inLen, ticket, outLen, mac, ssl->heap, enc); if (ret != 0) { - return WOLFSSL_TICKET_RET_REJECT; + ret = WOLFSSL_TICKET_RET_REJECT; } } + ForceZero(key, sizeof(key)); +#ifdef WOLFSSL_CHECK_MEM_ZERO + wc_MemZero_Check(key, sizeof(key)); +#endif #ifndef WOLFSSL_TICKET_DECRYPT_NO_CREATE - if (!IsAtLeastTLSv1_3(ssl->version) && !enc) - return WOLFSSL_TICKET_RET_CREATE; + if ((ret == WOLFSSL_TICKET_RET_OK) && !enc && + !IsAtLeastTLSv1_3(ssl->version)) { + ret = WOLFSSL_TICKET_RET_CREATE; + } #endif - return WOLFSSL_TICKET_RET_OK; + return ret; } #endif /* !WOLFSSL_NO_DEF_TICKET_ENC_CB */ diff --git a/src/ssl_api_ext.c b/src/ssl_api_ext.c index 412babadbea..37458f297e7 100644 --- a/src/ssl_api_ext.c +++ b/src/ssl_api_ext.c @@ -2647,7 +2647,7 @@ int wolfSSL_CTX_set_tlsext_ticket_key_cb(WOLFSSL_CTX *ctx, ticketCompatCb cb) * @param [in] keylen Length of buffer. * @return WOLFSSL_SUCCESS on success. * @return WOLFSSL_FAILURE when ctx is NULL, keys is NULL, keylen is not the - * correct length or the key context mutex cannot be locked. + * correct length or the key context cannot be read locked. */ long wolfSSL_CTX_get_tlsext_ticket_keys(WOLFSSL_CTX *ctx, unsigned char *keys, int keylen) @@ -2659,8 +2659,8 @@ long wolfSSL_CTX_get_tlsext_ticket_keys(WOLFSSL_CTX *ctx, ret = WOLFSSL_FAILURE; } #ifndef SINGLE_THREADED - else if (wc_LockMutex(&ctx->ticketKeyCtx.mutex) != 0) { - WOLFSSL_MSG("Couldn't lock key context mutex"); + else if (wc_LockRwLock_Rd(&ctx->ticketKeyCtx.lock) != 0) { + WOLFSSL_MSG("Couldn't lock key context"); ret = WOLFSSL_FAILURE; } #endif @@ -2675,7 +2675,7 @@ long wolfSSL_CTX_get_tlsext_ticket_keys(WOLFSSL_CTX *ctx, keys += OPAQUE32_LEN; c32toa(ctx->ticketKeyCtx.expirary[1], keys); #ifndef SINGLE_THREADED - wc_UnLockMutex(&ctx->ticketKeyCtx.mutex); + wc_UnLockRwLock(&ctx->ticketKeyCtx.lock); #endif } @@ -2689,7 +2689,7 @@ long wolfSSL_CTX_get_tlsext_ticket_keys(WOLFSSL_CTX *ctx, * @param [in] keylen Length of data. * @return WOLFSSL_SUCCESS on success. * @return WOLFSSL_FAILURE when ctx is NULL, keys is NULL, keylen is not the - * correct length or the key context mutex cannot be locked. + * correct length or the key context cannot be write locked. */ long wolfSSL_CTX_set_tlsext_ticket_keys(WOLFSSL_CTX *ctx, const void *keys_vp, int keylen) @@ -2702,8 +2702,8 @@ long wolfSSL_CTX_set_tlsext_ticket_keys(WOLFSSL_CTX *ctx, ret = WOLFSSL_FAILURE; } #ifndef SINGLE_THREADED - else if (wc_LockMutex(&ctx->ticketKeyCtx.mutex) != 0) { - WOLFSSL_MSG("Couldn't lock key context mutex"); + else if (wc_LockRwLock_Wr(&ctx->ticketKeyCtx.lock) != 0) { + WOLFSSL_MSG("Couldn't lock key context"); ret = WOLFSSL_FAILURE; } #endif @@ -2718,7 +2718,7 @@ long wolfSSL_CTX_set_tlsext_ticket_keys(WOLFSSL_CTX *ctx, keys += OPAQUE32_LEN; ato32(keys, &ctx->ticketKeyCtx.expirary[1]); #ifndef SINGLE_THREADED - wc_UnLockMutex(&ctx->ticketKeyCtx.mutex); + wc_UnLockRwLock(&ctx->ticketKeyCtx.lock); #endif } diff --git a/tests/api/test_session.c b/tests/api/test_session.c index 2c17de5431d..d6c497ace51 100644 --- a/tests/api/test_session.c +++ b/tests/api/test_session.c @@ -1921,6 +1921,336 @@ int test_wolfSSL_ticket_keys_threaded(void) return EXPECT_RESULT(); } +#if defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + !defined(NO_TLS) && !defined(SINGLE_THREADED) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) + +#define TEST_TICKET_ENC_CB_THREADS 3 +#define TEST_TICKET_ENC_CB_ROUNDS 200 +#define TEST_TICKET_ENC_CB_WAIT_MS 10 + +static WOLFSSL_CTX* ticket_enc_cb_ctx = NULL; +/* Index 0 is the thread that sets the keys, the rest are workers. Worker 1 + * goes first and sets up the keys. */ +static WOLFSSL* ticket_enc_cb_ssl[TEST_TICKET_ENC_CB_THREADS + 1]; +static int ticket_enc_cb_fail[TEST_TICKET_ENC_CB_THREADS + 1]; + +/* A ticket encrypted with a context's callback. */ +typedef struct test_ticket_enc_cb_ticket { + byte name[WOLFSSL_TICKET_NAME_SZ]; + byte iv[WOLFSSL_TICKET_IV_SZ]; + byte mac[WOLFSSL_TICKET_MAC_SZ]; + byte data[WOLFSSL_INTERNAL_TICKET_LEN]; +} test_ticket_enc_cb_ticket; + +/* Encrypt a ticket of known bytes with the context's callback. */ +static int test_ticket_enc_cb_enc(WOLFSSL_CTX* ctx, WOLFSSL* ssl, + test_ticket_enc_cb_ticket* t) +{ + int len = (int)sizeof(t->data); + int ret; + + XMEMSET(t->data, 0x3c, sizeof(t->data)); + ret = ctx->ticketEncCb(ssl, t->name, t->iv, t->mac, 1, t->data, len, &len, + ctx->ticketEncCtx); + if ((ret != WOLFSSL_TICKET_RET_OK) || (len != (int)sizeof(t->data))) { + return -1; + } + return 0; +} + +/* Decrypt a ticket with the context's callback and check the bytes. */ +static int test_ticket_enc_cb_dec(WOLFSSL_CTX* ctx, WOLFSSL* ssl, + test_ticket_enc_cb_ticket* t) +{ + byte plain[WOLFSSL_INTERNAL_TICKET_LEN]; + int len = (int)sizeof(t->data); + int ret; + + XMEMSET(plain, 0x3c, sizeof(plain)); + ret = ctx->ticketEncCb(ssl, t->name, t->iv, t->mac, 0, t->data, len, &len, + ctx->ticketEncCtx); + if (((ret != WOLFSSL_TICKET_RET_OK) && + (ret != WOLFSSL_TICKET_RET_CREATE)) || + (len != (int)sizeof(plain)) || + (XMEMCMP(t->data, plain, sizeof(plain)) != 0)) { + return -1; + } + return 0; +} + +/* Encrypt a ticket with the context's callback and decrypt it back. */ +static int test_ticket_enc_cb_round(WOLFSSL_CTX* ctx, WOLFSSL* ssl) +{ + test_ticket_enc_cb_ticket t; + + if (test_ticket_enc_cb_enc(ctx, ssl, &t) != 0) { + return -1; + } + return test_ticket_enc_cb_dec(ctx, ssl, &t); +} + +static THREAD_RETURN WOLFSSL_THREAD test_ticket_enc_cb_worker(void* args) +{ + int tno = ((func_args*)args)->argc; + int i; + + /* Start once worker 1 has set up the keys. Sleep rather than wait on a + * lock, which would order this thread after the set up. */ + if (tno > 1) { + XSLEEP_MS(TEST_TICKET_ENC_CB_WAIT_MS); + } + for (i = 0; i < TEST_TICKET_ENC_CB_ROUNDS; i++) { + if (test_ticket_enc_cb_round(ticket_enc_cb_ctx, + ticket_enc_cb_ssl[tno]) != 0) { + ticket_enc_cb_fail[tno]++; + } + /* Worker 1 pauses after the round that sets up the keys, or its later + * accesses push the set up out of TSAN's history too early. */ + if ((tno == 1) && (i == 0)) { + XSLEEP_MS(2 * TEST_TICKET_ENC_CB_WAIT_MS); + } + } + + WOLFSSL_RETURN_FROM_THREAD(0); +} + +/* Sets the keys the context already has, over and over. */ +static THREAD_RETURN WOLFSSL_THREAD test_ticket_enc_cb_setter(void* args) +{ + int tno = ((func_args*)args)->argc; + byte keys[WOLFSSL_TICKET_KEYS_SZ]; + int i; + + XSLEEP_MS(TEST_TICKET_ENC_CB_WAIT_MS); + /* This round sets up the keys if worker 1 has not yet. */ + if ((test_ticket_enc_cb_round(ticket_enc_cb_ctx, + ticket_enc_cb_ssl[tno]) != 0) || + (wolfSSL_CTX_get_tlsext_ticket_keys(ticket_enc_cb_ctx, keys, + (int)sizeof(keys)) != WOLFSSL_SUCCESS)) { + ticket_enc_cb_fail[tno]++; + } + for (i = 0; (ticket_enc_cb_fail[tno] == 0) && + (i < TEST_TICKET_ENC_CB_ROUNDS); i++) { + if (wolfSSL_CTX_set_tlsext_ticket_keys(ticket_enc_cb_ctx, keys, + (int)sizeof(keys)) != WOLFSSL_SUCCESS) { + ticket_enc_cb_fail[tno]++; + } + } + + WOLFSSL_RETURN_FROM_THREAD(0); +} +#endif + +/* Threads use one context's default ticket callback. The first use sets up + * the keys; then tickets are made while another thread sets the keys. */ +int test_wolfSSL_ticket_enc_cb_threaded(void) +{ + EXPECT_DECLS; +#if defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + !defined(NO_TLS) && !defined(SINGLE_THREADED) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) + struct test_memio_ctx test_ctx; + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL* ssl_c = NULL; + THREAD_TYPE threads[TEST_TICKET_ENC_CB_THREADS + 1]; + func_args args[TEST_TICKET_ENC_CB_THREADS + 1]; + int i; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(args, 0, sizeof(args)); + XMEMSET(ticket_enc_cb_ssl, 0, sizeof(ticket_enc_cb_ssl)); + XMEMSET(ticket_enc_cb_fail, 0, sizeof(ticket_enc_cb_fail)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ticket_enc_cb_ctx, + &ssl_c, &ticket_enc_cb_ssl[0], wolfSSLv23_client_method, + wolfSSLv23_server_method), 0); + for (i = 1; i <= TEST_TICKET_ENC_CB_THREADS; i++) { + ExpectNotNull(ticket_enc_cb_ssl[i] = wolfSSL_new(ticket_enc_cb_ctx)); + } + /* wolfCrypt sets its CPU feature flags on first use without a lock: run the + * cipher once on the client context first */ + if (EXPECT_SUCCESS()) { + ExpectIntEQ(test_ticket_enc_cb_round(ctx_c, ssl_c), 0); + } + + if (EXPECT_SUCCESS()) { + for (i = 0; i <= TEST_TICKET_ENC_CB_THREADS; i++) { + args[i].argc = i; + start_thread((i == 0) ? test_ticket_enc_cb_setter : + test_ticket_enc_cb_worker, &args[i], &threads[i]); + } + for (i = 0; i <= TEST_TICKET_ENC_CB_THREADS; i++) { + join_thread(threads[i]); + } + } + + for (i = 0; i <= TEST_TICKET_ENC_CB_THREADS; i++) { + ExpectIntEQ(ticket_enc_cb_fail[i], 0); + wolfSSL_free(ticket_enc_cb_ssl[i]); + ticket_enc_cb_ssl[i] = NULL; + } + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ticket_enc_cb_ctx); + ticket_enc_cb_ctx = NULL; +#endif + return EXPECT_RESULT(); +} + +#if defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + !defined(NO_TLS) && !defined(SINGLE_THREADED) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) + +#define TEST_TICKET_KEYGEN_THREADS 4 +#define TEST_TICKET_KEYGEN_ROUNDS 50 + +/* Offsets in the keys of wolfSSL_CTX_get_tlsext_ticket_keys(). */ +#define TEST_TICKET_KEY0_OFF WOLFSSL_TICKET_NAME_SZ +#define TEST_TICKET_KEY1_OFF (TEST_TICKET_KEY0_OFF + WOLFSSL_TICKET_KEY_SZ) +#define TEST_TICKET_EXP0_OFF (TEST_TICKET_KEY1_OFF + WOLFSSL_TICKET_KEY_SZ) +#define TEST_TICKET_EXP1_OFF (TEST_TICKET_EXP0_OFF + OPAQUE32_LEN) + +static WOLFSSL_CTX* ticket_keygen_ctx = NULL; +static WOLFSSL* ticket_keygen_ssl[TEST_TICKET_KEYGEN_THREADS]; +static int ticket_keygen_fail[TEST_TICKET_KEYGEN_THREADS]; +/* First ticket of each thread, decrypted again once all are done. */ +static test_ticket_enc_cb_ticket + ticket_keygen_first[TEST_TICKET_KEYGEN_THREADS]; + +static THREAD_RETURN WOLFSSL_THREAD test_ticket_keygen_worker(void* args) +{ + int tno = ((func_args*)args)->argc; + test_ticket_enc_cb_ticket t; + int i; + + for (i = 0; i < TEST_TICKET_KEYGEN_ROUNDS; i++) { + if (test_ticket_enc_cb_enc(ticket_keygen_ctx, ticket_keygen_ssl[tno], + &t) != 0) { + ticket_keygen_fail[tno]++; + continue; + } + /* Every ticket is made with the one new key 1. */ + if ((t.name[WOLFSSL_TICKET_NAME_SZ - 1] & 1) != 1) { + ticket_keygen_fail[tno]++; + } + if (i == 0) { + XMEMCPY(&ticket_keygen_first[tno], &t, sizeof(t)); + } + if (test_ticket_enc_cb_dec(ticket_keygen_ctx, ticket_keygen_ssl[tno], + &t) != 0) { + ticket_keygen_fail[tno]++; + } + } + + WOLFSSL_RETURN_FROM_THREAD(0); +} +#endif + +/* Threads find no key valid for encryption and race for the write lock to + * make one. Only the first makes a key, and every ticket still decrypts. */ +int test_wolfSSL_ticket_enc_cb_keygen_threaded(void) +{ + EXPECT_DECLS; +#if defined(HAVE_SESSION_TICKET) && !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && \ + !defined(NO_TLS) && !defined(SINGLE_THREADED) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) + struct test_memio_ctx test_ctx; + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL* ssl_c = NULL; + THREAD_TYPE threads[TEST_TICKET_KEYGEN_THREADS]; + func_args args[TEST_TICKET_KEYGEN_THREADS]; + byte keys[WOLFSSL_TICKET_KEYS_SZ]; + byte keysAfter[WOLFSSL_TICKET_KEYS_SZ]; + word32 now = 0; + word32 exp = 0; + int i; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(args, 0, sizeof(args)); + XMEMSET(keys, 0, sizeof(keys)); + XMEMSET(keysAfter, 0, sizeof(keysAfter)); + XMEMSET(ticket_keygen_ssl, 0, sizeof(ticket_keygen_ssl)); + XMEMSET(ticket_keygen_fail, 0, sizeof(ticket_keygen_fail)); + XMEMSET(ticket_keygen_first, 0, sizeof(ticket_keygen_first)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ticket_keygen_ctx, + &ssl_c, &ticket_keygen_ssl[0], wolfSSLv23_client_method, + wolfSSLv23_server_method), 0); + for (i = 1; i < TEST_TICKET_KEYGEN_THREADS; i++) { + ExpectNotNull(ticket_keygen_ssl[i] = wolfSSL_new(ticket_keygen_ctx)); + } + /* Set up the keys before the threads start. */ + if (EXPECT_SUCCESS()) { + ExpectIntEQ(test_ticket_enc_cb_round(ticket_keygen_ctx, + ticket_keygen_ssl[0]), 0); + } + + /* Leave key 0 valid for decryption only and expire key 1. Half the ticket + * hint is left for the seconds that pass while the test runs. */ + ExpectIntEQ(wolfSSL_CTX_get_tlsext_ticket_keys(ticket_keygen_ctx, keys, + (int)sizeof(keys)), WOLFSSL_SUCCESS); + if (EXPECT_SUCCESS()) { + ato32(keys + TEST_TICKET_EXP0_OFF, &exp); + now = exp - WOLFSSL_TICKET_KEY_LIFETIME; + c32toa(now + (word32)ticket_keygen_ctx->ticketHint / 2, + keys + TEST_TICKET_EXP0_OFF); + c32toa(0, keys + TEST_TICKET_EXP1_OFF); + } + ExpectIntEQ(wolfSSL_CTX_set_tlsext_ticket_keys(ticket_keygen_ctx, keys, + (int)sizeof(keys)), WOLFSSL_SUCCESS); + + /* Hold the write lock while the threads start, so they find no key for + * encryption under the read lock at the same time. */ + if (EXPECT_SUCCESS()) { + ExpectIntEQ(wc_LockRwLock_Wr(&ticket_keygen_ctx->ticketKeyCtx.lock), + 0); + } + if (EXPECT_SUCCESS()) { + for (i = 0; i < TEST_TICKET_KEYGEN_THREADS; i++) { + args[i].argc = i; + start_thread(test_ticket_keygen_worker, &args[i], &threads[i]); + } + XSLEEP_MS(TEST_TICKET_ENC_CB_WAIT_MS); + ExpectIntEQ(wc_UnLockRwLock(&ticket_keygen_ctx->ticketKeyCtx.lock), + 0); + for (i = 0; i < TEST_TICKET_KEYGEN_THREADS; i++) { + join_thread(threads[i]); + } + } + + for (i = 0; i < TEST_TICKET_KEYGEN_THREADS; i++) { + ExpectIntEQ(ticket_keygen_fail[i], 0); + /* Made with the new key 1. A second key made would replace it. */ + ExpectIntEQ(ticket_keygen_first[i].name[WOLFSSL_TICKET_NAME_SZ - 1] & + 1, 1); + ExpectIntEQ(test_ticket_enc_cb_dec(ticket_keygen_ctx, + ticket_keygen_ssl[i], &ticket_keygen_first[i]), 0); + } + + /* Only key 1 and its expirary changed. */ + ExpectIntEQ(wolfSSL_CTX_get_tlsext_ticket_keys(ticket_keygen_ctx, + keysAfter, (int)sizeof(keysAfter)), WOLFSSL_SUCCESS); + ExpectBufEQ(keysAfter, keys, TEST_TICKET_KEY1_OFF); + ExpectBufNE(keysAfter + TEST_TICKET_KEY1_OFF, keys + TEST_TICKET_KEY1_OFF, + WOLFSSL_TICKET_KEY_SZ); + ExpectBufEQ(keysAfter + TEST_TICKET_EXP0_OFF, keys + TEST_TICKET_EXP0_OFF, + OPAQUE32_LEN); + if (EXPECT_SUCCESS()) { + ato32(keysAfter + TEST_TICKET_EXP1_OFF, &exp); + ExpectTrue(exp >= now + WOLFSSL_TICKET_KEY_LIFETIME); + } + + for (i = 0; i < TEST_TICKET_KEYGEN_THREADS; i++) { + wolfSSL_free(ticket_keygen_ssl[i]); + ticket_keygen_ssl[i] = NULL; + } + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ticket_keygen_ctx); + ticket_keygen_ctx = NULL; +#endif + return EXPECT_RESULT(); +} + /*----------------------------------------------------------------------------*/ /* SESSION ex_data new index */ /*----------------------------------------------------------------------------*/ diff --git a/tests/api/test_session.h b/tests/api/test_session.h index 19301e190f2..5c9f0e6ca4d 100644 --- a/tests/api/test_session.h +++ b/tests/api/test_session.h @@ -38,6 +38,8 @@ int test_wolfSSL_SESSION_expire_downgrade(void); int test_wolfSSL_CTX_sess_set_remove_cb(void); int test_wolfSSL_ticket_keys(void); int test_wolfSSL_ticket_keys_threaded(void); +int test_wolfSSL_ticket_enc_cb_threaded(void); +int test_wolfSSL_ticket_enc_cb_keygen_threaded(void); int test_wolfSSL_SESSION_get_ex_new_index(void); int test_wolfSSL_SESSION_dup_peer_fail(void); int test_wolfSSL_GetSessionAtIndex(void); @@ -66,6 +68,8 @@ int test_wolfSSL_client_cache_max_early_data(void); TEST_DECL_GROUP("session", test_wolfSSL_CTX_sess_set_remove_cb), \ TEST_DECL_GROUP("session", test_wolfSSL_ticket_keys), \ TEST_DECL_GROUP("session", test_wolfSSL_ticket_keys_threaded), \ + TEST_DECL_GROUP("session", test_wolfSSL_ticket_enc_cb_threaded), \ + TEST_DECL_GROUP("session", test_wolfSSL_ticket_enc_cb_keygen_threaded), \ TEST_DECL_GROUP("session", test_wolfSSL_SESSION_get_ex_new_index), \ TEST_DECL_GROUP("session", test_wolfSSL_SESSION_dup_peer_fail), \ TEST_DECL_GROUP("session", test_wolfSSL_GetSessionAtIndex), \ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index c96be637e07..c8d7ff0c28c 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -4184,11 +4184,12 @@ typedef struct TicketEncCbCtx { byte key[2][WOLFSSL_TICKET_KEY_SZ]; /* Expirary date of keys. */ word32 expirary[2]; - /* Random number generator to use for generating name, keys and IV. */ + /* Random number generator to use for generating name and keys. */ WC_RNG rng; #ifndef SINGLE_THREADED - /* Mutex for access to changing keys. */ - wolfSSL_Mutex mutex; + /* Read lock to use the name, keys and expirary. Write lock to change + * them; the RNG is only used while changing them. */ + wolfSSL_RwLock lock; #endif /* Pointer back to SSL_CTX. */ WOLFSSL_CTX* ctx; From b9faead23c70897fb12749872c37f321acbfb255 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Wed, 28 Oct 2026 15:26:09 +0000 Subject: [PATCH 02/10] DTLS: bound the receive list by bytes The receive list of out-of-order and fragmented handshake messages was bounded by DTLS_POOL_SZ entries, 20, whatever their size, and a fragment the list could not take ended the connection, also from a plaintext record anyone can send. - WOLFSSL_DTLS_RX_MAX_BYTES bounds the bytes the list retains, default MAX_HANDSHAKE_SZ plus 16 KiB: message and fragment metadata and payload. The sum is kept in dtls_rx_msg_list_bytes and changed where the list changes, by DtlsMsgRxListInsert(), DtlsMsgRxListDetach(), DtlsMsgListRemove(), DtlsMsgListPurge() and DtlsMsgRxListFree(). An entry's size is computed from its counters, without walking its buckets. - The per-message cap on fragment buckets, WOLFSSL_DTLS_FRAG_POOL_SZ, moves from DtlsMsgSet() to DtlsMsgStore(), which checks the projected size and bucket count before allocating anything. - DtlsMsgStore() returns 0 when the fragment is held, a duplicate included, DTLS_MSG_STORE_DROPPED when it is not and that is no error, SEQUENCE_ERROR for a fragment that contradicts the message held, or an error. DtlsMsgStoreFatal() says which results end the connection: only running out of memory, or a peer whose records authenticate exceeding the budget. A plaintext fragment that does not fit, or reaches past its message, is dropped and not acknowledged, so its sender resends it. - Dtls13ProcessBufferedMessages() drops an entry older than the next expected message instead of stalling on it, and detaches the entry it processes, so processing that empties the list frees nothing in use. Dtls13ProcessBufferedMsg() processes one message. - wolfSSL_clear() frees the DTLS 1.3 receive list, which FreeHandshakeResources() leaves to the DTLS 1.3 code. - Tests: the budget in DTLS 1.2 and 1.3, the counter against the list, which store results are fatal, wolfSSL_clear(), and the fragment flood tests sized to the budget. --- ChangeLog.md | 14 +++ src/dtls.c | 11 ++ src/dtls13.c | 149 +++++++++++++---------- src/internal.c | 254 ++++++++++++++++++++++++---------------- src/ssl.c | 1 + tests/api/test_dtls.c | 213 ++++++++++++++++++++++++++++----- tests/api/test_dtls.h | 2 + tests/api/test_dtls13.c | 91 ++++++++++++++ tests/api/test_dtls13.h | 4 + wolfssl/internal.h | 23 +++- 10 files changed, 566 insertions(+), 196 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 9143853e8eb..f3b60730e8b 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -28,6 +28,20 @@ `wolfcrypt/src/port/nxp/README_SE050.md` and `IDE/CRYPTOCELL/README.md`. by @SparkiDev ## Behavioral Changes +* **Behavioral change (the DTLS receive list is bounded by bytes)**: the list + of out-of-order and fragmented handshake messages was bounded by + `DTLS_POOL_SZ` entries, 20, whatever their size, and a fragment the list + could not take ended the connection, also from a plaintext record anyone can + send. `WOLFSSL_DTLS_RX_MAX_BYTES` now bounds the bytes it retains, metadata + and payload, `MAX_HANDSHAKE_SZ` plus 16 KiB by default. A plaintext + fragment that does not fit, or reaches past its message, is dropped and not + acknowledged, so its sender resends it; only running out of memory, or a + peer whose records authenticate exceeding the budget, ends the connection. + The per-message cap on fragment buckets, `WOLFSSL_DTLS_FRAG_POOL_SZ`, is + checked before anything is allocated. A buffered DTLS 1.3 message older + than the next expected one is dropped instead of stalling the list, and + `wolfSSL_clear()` frees the DTLS 1.3 receive list. + * **Behavioral change (DSA now uses the leftmost |q| bytes of the digest)**: `wc_DsaSign_ex()`/`wc_DsaVerify_ex()` read the whole digest, and the EVP DSA paths always read 20 bytes, so neither followed FIPS 186-4 4.2. Both now use diff --git a/src/dtls.c b/src/dtls.c index e4361001ed1..a036a47c4aa 100644 --- a/src/dtls.c +++ b/src/dtls.c @@ -46,6 +46,17 @@ * DTLS server can process it statelessly. This is only implemented for * DTLS 1.3. The user MUST call wolfSSL_dtls13_allow_ch_frag() on the server * to explicitly enable this during runtime. + * WOLFSSL_DTLS_RX_MAX_BYTES + * Bound on the bytes the receive list of one WOLFSSL retains for + * out-of-order and fragmented handshake messages, DTLS 1.2 and 1.3, + * metadata included. A byte bound rather than a message count because + * MAX_HANDSHAKE_SZ varies a lot between builds, by an order of magnitude + * with post-quantum certificates, while a flight has at most one message + * that large. The default is one such message plus 16 KiB for the rest. + * A message has at most WOLFSSL_DTLS_FRAG_POOL_SZ disjoint fragment + * ranges, since every store walks them. Past either, a plaintext fragment + * is dropped without an ACK; an authenticated record past the byte bound + * ends the connection. */ #ifndef WOLFCRYPT_ONLY diff --git a/src/dtls13.c b/src/dtls13.c index f1a9afd7f4d..f76635461c6 100644 --- a/src/dtls13.c +++ b/src/dtls13.c @@ -358,66 +358,85 @@ static void Dtls13MsgWasProcessed(WOLFSSL* ssl, enum HandShakeType hs) } } -int Dtls13ProcessBufferedMessages(WOLFSSL* ssl) +/* Process one complete buffered handshake message. *processed is set when the + * message was consumed and its entry can be removed from the list. */ +static int Dtls13ProcessBufferedMsg(WOLFSSL* ssl, DtlsMsg* msg, int* processed) { - DtlsMsg* msg; word32 idx = 0; - int ret = 0; - - WOLFSSL_ENTER("Dtls13ProcessBufferedMessages"); - - DtlsMsgListPurge(ssl); + int downgraded = 0; + int ret; - while ((msg = DtlsMsgListNextReady(ssl)) != NULL) { - int downgraded = 0; - idx = 0; + *processed = 0; #ifndef WOLFSSL_DISABLE_EARLY_SANITY_CHECKS - ret = MsgCheckEncryption(ssl, msg->type, msg->encrypted); - if (ret != 0) { - SendAlert(ssl, alert_fatal, unexpected_message); - break; - } + ret = MsgCheckEncryption(ssl, msg->type, msg->encrypted); + if (ret != 0) { + SendAlert(ssl, alert_fatal, unexpected_message); + return ret; + } #endif - /* We may have DTLS <=1.2 msgs stored from before we knew which version - * we were going to use. Interpret correctly. */ - if (IsAtLeastTLSv1_3(ssl->version)) { - ret = DoTls13HandShakeMsgType(ssl, msg->fullMsg, &idx, msg->type, - msg->sz, msg->sz); - if (!IsAtLeastTLSv1_3(ssl->version)) - downgraded = 1; - } - else { + /* We may have DTLS <=1.2 msgs stored from before we knew which version + * we were going to use. Interpret correctly. */ + if (IsAtLeastTLSv1_3(ssl->version)) { + ret = DoTls13HandShakeMsgType(ssl, msg->fullMsg, &idx, msg->type, + msg->sz, msg->sz); + if (!IsAtLeastTLSv1_3(ssl->version)) + downgraded = 1; + } + else { #if !defined(WOLFSSL_NO_TLS12) - ret = DoHandShakeMsgType(ssl, msg->fullMsg, &idx, msg->type, - msg->sz, msg->sz); + ret = DoHandShakeMsgType(ssl, msg->fullMsg, &idx, msg->type, + msg->sz, msg->sz); #else - WOLFSSL_MSG("DTLS1.2 disabled with WOLFSSL_NO_TLS12"); - WOLFSSL_ERROR_VERBOSE(NOT_COMPILED_IN); - ret = NOT_COMPILED_IN; + WOLFSSL_MSG("DTLS1.2 disabled with WOLFSSL_NO_TLS12"); + WOLFSSL_ERROR_VERBOSE(NOT_COMPILED_IN); + ret = NOT_COMPILED_IN; #endif - } + } - /* processing certificate_request triggers a connect. The error came - * from there, the message can be considered processed successfully. - * WANT_WRITE means that we are done with processing the msg and we are - * waiting to flush the output buffer. */ - if ((ret == 0 || ret == WC_NO_ERR_TRACE(WANT_WRITE)) || - (msg->type == certificate_request && - ssl->options.handShakeDone && - ret == WC_NO_ERR_TRACE(WC_PENDING_E))) { - if (IsAtLeastTLSv1_3(ssl->version)) - Dtls13MsgWasProcessed(ssl, (enum HandShakeType)msg->type); - else if (downgraded) - /* DoHandShakeMsgType normally handles the hs number but if - * DoTls13HandShakeMsgType processed 1.2 msgs then this wasn't - * incremented. */ - ssl->keys.dtls_expected_peer_handshake_number++; - - DtlsMsgListRemove(ssl, msg); + /* processing certificate_request triggers a connect. The error came + * from there, the message can be considered processed successfully. + * WANT_WRITE means that we are done with processing the msg and we are + * waiting to flush the output buffer. */ + if ((ret == 0 || ret == WC_NO_ERR_TRACE(WANT_WRITE)) || + (msg->type == certificate_request && + ssl->options.handShakeDone && + ret == WC_NO_ERR_TRACE(WC_PENDING_E))) { + if (IsAtLeastTLSv1_3(ssl->version)) + Dtls13MsgWasProcessed(ssl, (enum HandShakeType)msg->type); + else if (downgraded) + /* DoHandShakeMsgType normally handles the hs number but if + * DoTls13HandShakeMsgType processed 1.2 msgs then this wasn't + * incremented. */ + ssl->keys.dtls_expected_peer_handshake_number++; + *processed = 1; + } + + return ret; +} + +int Dtls13ProcessBufferedMessages(WOLFSSL* ssl) +{ + DtlsMsg* msg; + int processed; + int ret = 0; + + WOLFSSL_ENTER("Dtls13ProcessBufferedMessages"); + + DtlsMsgListPurge(ssl); + + while ((msg = DtlsMsgListNextReady(ssl)) != NULL) { + /* Processing a message may empty the list, so the entry is out of it + * meanwhile */ + DtlsMsgRxListDetach(ssl, msg); + ret = Dtls13ProcessBufferedMsg(ssl, msg, &processed); + if (processed) { + DtlsMsgDelete(msg, ssl->heap); DtlsMsgListPurge(ssl); } + else + DtlsMsgRxListInsert(ssl, msg); if (ret != 0) break; @@ -1940,11 +1959,16 @@ static int _Dtls13HandshakeRecv(WOLFSSL* ssl, byte* input, word32 size, return 0; } - /* Same error code as the DTLS 1.2 check so a stateless server drops it */ + /* A fragment reaching past the end of its message: like the size cap, + * error out only on an authenticated record. */ if (fragOff + fragLength > messageLength) { WOLFSSL_MSG("Fragment outside of the handshake message"); - WOLFSSL_ERROR_VERBOSE(LENGTH_ERROR); - return LENGTH_ERROR; + if (IsEncryptionOn(ssl, 0)) { + WOLFSSL_ERROR_VERBOSE(LENGTH_ERROR); + return LENGTH_ERROR; + } + *processedSize = idx + fragLength; + return 0; } ret = Dtls13RtxMsgRecvd(ssl, (enum HandShakeType)handshakeType, fragOff, @@ -2012,21 +2036,16 @@ static int _Dtls13HandshakeRecv(WOLFSSL* ssl, byte* input, word32 size, ssl->keys.dtls_expected_peer_handshake_number || usingAsyncCrypto) { DtlsMsgListPurge(ssl); - if (ssl->dtls_rx_msg_list_sz < DTLS_POOL_SZ) { - /* Only ack a fragment we really buffered. */ - if (DtlsMsgStore(ssl, (word16)w64GetLow32(ssl->keys.curEpoch64), - ssl->keys.dtls_peer_handshake_number, - input + DTLS_HANDSHAKE_HEADER_SZ, messageLength, - handshakeType, fragOff, fragLength, ssl->heap) == 0) { - Dtls13RtxAddAckForCurRecord(ssl); - } - } - else { - /* DTLS_POOL_SZ outstanding messages is way more than enough for any - * valid peer */ - WOLFSSL_ERROR_VERBOSE(DTLS_TOO_MANY_FRAGMENTS_E); - return DTLS_TOO_MANY_FRAGMENTS_E; - } + ret = DtlsMsgStore(ssl, (word16)w64GetLow32(ssl->keys.curEpoch64), + ssl->keys.dtls_peer_handshake_number, + input + DTLS_HANDSHAKE_HEADER_SZ, messageLength, handshakeType, + fragOff, fragLength, ssl->heap); + if (DtlsMsgStoreFatal(ssl, ret)) + return ret; + /* Only ack a fragment we really buffered: the peer does not resend an + * acknowledged record. */ + if (ret == 0) + Dtls13RtxAddAckForCurRecord(ssl); *processedSize = idx + fragLength; if (Dtls13NextMessageComplete(ssl)) diff --git a/src/internal.c b/src/internal.c index 515a9269c19..be522a9ad50 100644 --- a/src/internal.c +++ b/src/internal.c @@ -10443,11 +10443,7 @@ void FreeSuites(WOLFSSL* ssl) static void FreeSSL_DtlsResources(WOLFSSL* ssl) { DtlsMsgPoolReset(ssl); - if (ssl->dtls_rx_msg_list != NULL) { - DtlsMsgListDelete(ssl->dtls_rx_msg_list, ssl->heap); - ssl->dtls_rx_msg_list = NULL; - ssl->dtls_rx_msg_list_sz = 0; - } + DtlsMsgRxListFree(ssl); XFREE(ssl->buffers.dtlsCtx.peer.sa, ssl->heap, DYNAMIC_TYPE_SOCKADDR); ssl->buffers.dtlsCtx.peer.sa = NULL; #ifdef WOLFSSL_RW_THREADED @@ -10965,9 +10961,7 @@ void FreeHandshakeResources(WOLFSSL* ssl) /* DTLS_POOL (DTLSv1.3 flushes the queue autonomously) */ if(!IsAtLeastTLSv1_3(ssl->version)) { DtlsMsgPoolReset(ssl); - DtlsMsgListDelete(ssl->dtls_rx_msg_list, ssl->heap); - ssl->dtls_rx_msg_list = NULL; - ssl->dtls_rx_msg_list_sz = 0; + DtlsMsgRxListFree(ssl); } #ifdef WOLFSSL_DTLS13 if (ssl->dtls13ClientHello != NULL) { @@ -11473,6 +11467,25 @@ void DtlsMsgListDelete(DtlsMsg* head, void* heap) } } +/* RX raw owns the former single fragment allocation after assembly. */ +static size_t DtlsMsgRetainedBytes(const DtlsMsg* msg) +{ + /* Buckets never overlap, so their payload adds up to bytesReceived; a + * complete message keeps its single bucket as raw. */ + size_t bytes = sizeof(DtlsMsg) + msg->bytesReceived + + ((size_t)msg->fragBucketListCount + (msg->raw != NULL ? 1 : 0)) * + sizeof(DtlsFragBucket); + return bytes; +} + +size_t DtlsMsgListRetainedBytes(const DtlsMsg* head) +{ + size_t bytes = 0; + for (; head != NULL; head = head->next) + bytes += DtlsMsgRetainedBytes(head); + return bytes; +} + static DtlsMsg* DtlsMsgListUnlink(DtlsMsg* head, DtlsMsg* item) { DtlsMsg** prevNext = &head; @@ -11498,12 +11511,19 @@ DtlsMsg* DtlsMsgListNextReady(WOLFSSL* ssl) return cur; } -/* Take item out of the rx list and free it. */ -void DtlsMsgListRemove(WOLFSSL* ssl, DtlsMsg* item) +/* Take item out of the rx list; the list's counters follow */ +DtlsMsg* DtlsMsgRxListDetach(WOLFSSL* ssl, DtlsMsg* item) { ssl->dtls_rx_msg_list = DtlsMsgListUnlink(ssl->dtls_rx_msg_list, item); - DtlsMsgDelete(item, ssl->heap); ssl->dtls_rx_msg_list_sz--; + ssl->dtls_rx_msg_list_bytes -= DtlsMsgRetainedBytes(item); + return item; +} + +/* Take item out of the rx list and free it. */ +void DtlsMsgListRemove(WOLFSSL* ssl, DtlsMsg* item) +{ + DtlsMsgDelete(DtlsMsgRxListDetach(ssl, item), ssl->heap); } /* Drop stored messages the handshake already moved past. A retransmission @@ -11520,8 +11540,9 @@ void DtlsMsgListPurge(WOLFSSL* ssl) DtlsMsg* next = cur->next; if (cur->seq < expected) { *prevNext = next; - DtlsMsgDelete(cur, ssl->heap); ssl->dtls_rx_msg_list_sz--; + ssl->dtls_rx_msg_list_bytes -= DtlsMsgRetainedBytes(cur); + DtlsMsgDelete(cur, ssl->heap); } else { prevNext = &cur->next; @@ -11835,12 +11856,8 @@ int DtlsMsgSet(DtlsMsg* msg, word32 seq, word16 epoch, const byte* data, byte ty if (cur == NULL) { /* We reached the end of the list. data is after and disjointed * from anything we have received so far. */ - if (msg->fragBucketListCount >= DTLS_FRAG_POOL_SZ) { - WOLFSSL_ERROR_VERBOSE(DTLS_TOO_MANY_FRAGMENTS_E); - return DTLS_TOO_MANY_FRAGMENTS_E; - } - prev->m.m.next = DtlsMsgCreateFragBucket(fragOffset, data, - fragSz, heap); + prev->m.m.next = + DtlsMsgCreateFragBucket(fragOffset, data, fragSz, heap); if (prev->m.m.next == NULL) return MEMORY_ERROR; msg->bytesReceived += fragSz; @@ -11849,10 +11866,6 @@ int DtlsMsgSet(DtlsMsg* msg, word32 seq, word16 epoch, const byte* data, byte ty else if (fragOffsetEnd < cur->m.m.offset) { /* Fragment is entirely before cur with a gap */ DtlsFragBucket** prev_next; - if (msg->fragBucketListCount >= DTLS_FRAG_POOL_SZ) { - WOLFSSL_ERROR_VERBOSE(DTLS_TOO_MANY_FRAGMENTS_E); - return DTLS_TOO_MANY_FRAGMENTS_E; - } prev_next = prev != NULL ? &prev->m.m.next : &msg->fragBucketList; *prev_next = DtlsMsgCreateFragBucket(fragOffset, data, @@ -11904,82 +11917,132 @@ DtlsMsg* DtlsMsgFind(DtlsMsg* head, word16 epoch, word32 seq) return head; } +/* The retained bytes and buckets after the fragment is stored, not the peak + * while buckets merge. Sorted buckets neither overlap nor touch. */ +static size_t DtlsMsgProjectedBytes(const DtlsMsg* msg, word32 offset, + word32 length, word32* buckets) +{ + const DtlsFragBucket* frag; + word32 end = offset + length; + size_t bytes = DtlsMsgRetainedBytes(msg); -/* Returns 0 when the fragment was stored, negative when it was dropped. */ + *buckets = msg->fragBucketListCount; + if (msg->ready) + return bytes; + for (frag = msg->fragBucketList; frag != NULL; frag = frag->m.m.next) { + word32 fragEnd = frag->m.m.offset + frag->m.m.sz; + if (fragEnd < offset) + continue; + if (frag->m.m.offset > end) + break; + offset = min(offset, frag->m.m.offset); + end = max(end, fragEnd); + bytes -= sizeof(DtlsFragBucket) + frag->m.m.sz; + (*buckets)--; + } + (*buckets)++; + return bytes + sizeof(DtlsFragBucket) + (end - offset); +} + +/* Whether the receive list can grow to hold current, or a new entry, at + * projected retained bytes. */ +static int DtlsMsgRxAdmit(WOLFSSL* ssl, DtlsMsg* current, size_t projected) +{ + size_t old = current == NULL ? 0 : DtlsMsgRetainedBytes(current); + size_t limit = WOLFSSL_DTLS_RX_MAX_BYTES; + + if (projected > limit || + ssl->dtls_rx_msg_list_bytes - old > limit - projected) + return DTLS_TOO_MANY_FRAGMENTS_E; + return 0; +} + +/* The receive list's counters follow every change to it */ +void DtlsMsgRxListInsert(WOLFSSL* ssl, DtlsMsg* msg) +{ + ssl->dtls_rx_msg_list = DtlsMsgInsert(ssl->dtls_rx_msg_list, msg); + ssl->dtls_rx_msg_list_sz++; + ssl->dtls_rx_msg_list_bytes += DtlsMsgRetainedBytes(msg); +} + +void DtlsMsgRxListFree(WOLFSSL* ssl) +{ + DtlsMsgListDelete(ssl->dtls_rx_msg_list, ssl->heap); + ssl->dtls_rx_msg_list = NULL; + ssl->dtls_rx_msg_list_sz = 0; + ssl->dtls_rx_msg_list_bytes = 0; +} + +/* Store a fragment of message seq in the receive list. Returns 0 when it is + * held, DTLS_MSG_STORE_DROPPED when not and that is no error, or an error. */ int DtlsMsgStore(WOLFSSL* ssl, word16 epoch, word32 seq, const byte* data, word32 dataSz, byte type, word32 fragOffset, word32 fragSz, void* heap) { - /* See if seq exists in the list. If it isn't in the list, make - * a new item of size dataSz, copy fragSz bytes from data to msg->msg - * starting at offset fragOffset, and add fragSz to msg->fragSz. If - * the seq is in the list and it isn't full, copy fragSz bytes from - * data to msg->msg starting at offset fragOffset, and add fragSz to - * msg->fragSz. Insertions take into account data already in the list - * in case there are overlaps in the handshake message due to retransmit - * messages. The new item should be inserted into the list in its - * proper position. - * - * 1. Find seq in list, or where seq should go in list. If seq not in - * list, create new item and insert into list. Either case, keep - * pointer to item. - * 2. Copy the data from the message to the stored message where it - * belongs without overlaps. - */ + DtlsMsg* cur; + size_t projected; + word32 buckets = 1; + int isNew; + int ret; - DtlsMsg* head = ssl->dtls_rx_msg_list; - byte encrypted = ssl->keys.decryptedCur == 1; - int ret = 0; WOLFSSL_ENTER("DtlsMsgStore"); + if (data == NULL || fragOffset > dataSz || fragSz > dataSz - fragOffset) + return BAD_FUNC_ARG; - if (head != NULL) { - DtlsMsg* cur = DtlsMsgFind(head, epoch, seq); + cur = DtlsMsgFind(ssl->dtls_rx_msg_list, epoch, seq); + isNew = cur == NULL; + /* A fragment that contradicts the message held */ + if (!isNew && (cur->sz != dataSz || cur->type != type)) + return SEQUENCE_ERROR; + + if (isNew) + projected = sizeof(DtlsMsg) + sizeof(DtlsFragBucket) + fragSz; + else + projected = DtlsMsgProjectedBytes(cur, fragOffset, fragSz, &buckets); + /* Every store walks the buckets, so their number is capped. A fragment + * touching what is held is always taken, so retransmissions complete. */ + if (buckets > DTLS_FRAG_POOL_SZ) + return DTLS_MSG_STORE_DROPPED; + ret = DtlsMsgRxAdmit(ssl, cur, projected); + if (ret == 0 && isNew) { + cur = DtlsMsgNew(dataSz, 0, heap); if (cur == NULL) { - cur = DtlsMsgNew(dataSz, 0, heap); - if (cur == NULL) { - WOLFSSL_MSG("DtlsMsgNew allocation failed"); - ret = MEMORY_E; - ssl->error = ret; - } - else { - ret = DtlsMsgSet(cur, seq, epoch, data, type, - fragOffset, fragSz, heap, dataSz, encrypted); - if (ret < 0) { - DtlsMsgDelete(cur, heap); - } - else { - ssl->dtls_rx_msg_list_sz++; - head = DtlsMsgInsert(head, cur); - } - } - } - else { - /* If this fails, the data is just dropped. */ - ret = DtlsMsgSet(cur, seq, epoch, data, type, fragOffset, - fragSz, heap, dataSz, encrypted); - } - } - else { - head = DtlsMsgNew(dataSz, 0, heap); - if (head == NULL) { WOLFSSL_MSG("DtlsMsgNew allocation failed"); ret = MEMORY_E; ssl->error = ret; } - else if ((ret = DtlsMsgSet(head, seq, epoch, data, type, fragOffset, - fragSz, heap, dataSz, encrypted)) < 0) { - DtlsMsgDelete(head, heap); - head = NULL; - } - else { - ssl->dtls_rx_msg_list_sz++; - } } + if (ret == 0) { + size_t before = isNew ? 0 : DtlsMsgRetainedBytes(cur); - ssl->dtls_rx_msg_list = head; - + ret = DtlsMsgSet(cur, seq, epoch, data, type, fragOffset, fragSz, + heap, dataSz, ssl->keys.decryptedCur == 1); + if (isNew) { + if (ret == 0) + DtlsMsgRxListInsert(ssl, cur); + else + DtlsMsgDelete(cur, heap); + } + else + ssl->dtls_rx_msg_list_bytes += DtlsMsgRetainedBytes(cur) - before; + } return ret; } +/* Whether ret, from DtlsMsgStore(), ends the connection: only running out of + * memory, or an authenticated peer exceeding the receive budget. A fragment + * not held is dropped and not acknowledged, so its sender resends it. */ +int DtlsMsgStoreFatal(WOLFSSL* ssl, int ret) +{ + switch (ret) { + case WC_NO_ERR_TRACE(MEMORY_E): + case WC_NO_ERR_TRACE(MEMORY_ERROR): + return 1; + case WC_NO_ERR_TRACE(DTLS_TOO_MANY_FRAGMENTS_E): + return IsEncryptionOn(ssl, 0); + default: + return 0; + } +} /* DtlsMsgInsert() is an in-order insert. */ DtlsMsg* DtlsMsgInsert(DtlsMsg* head, DtlsMsg* item) @@ -23635,15 +23698,13 @@ static int DoDtlsHandShakeMsg(WOLFSSL* ssl, byte* input, word32* inOutIdx, * with newer and newer cookies.) */ if (type != client_hello) { WOLFSSL_MSG("Current message is out of order"); - if (ssl->dtls_rx_msg_list_sz >= DTLS_POOL_SZ) { - WOLFSSL_MSG("Reached rx msg limit error"); - return DTLS_TOO_MANY_FRAGMENTS_E; - } - DtlsMsgStore(ssl, ssl->keys.curEpoch, + ret = DtlsMsgStore(ssl, ssl->keys.curEpoch, ssl->keys.dtls_peer_handshake_number, input + *inOutIdx, size, type, fragOffset, fragSz, ssl->heap); + if (DtlsMsgStoreFatal(ssl, ret)) + return ret; *inOutIdx += fragSz; ret = 0; #ifndef WOLFSSL_DTLS_RESEND_ONLY_TIMEOUT @@ -23703,15 +23764,12 @@ static int DoDtlsHandShakeMsg(WOLFSSL* ssl, byte* input, word32* inOutIdx, return 0; } - if (ssl->dtls_rx_msg_list_sz >= DTLS_POOL_SZ) { - WOLFSSL_MSG("Reached rx msg limit error"); - WOLFSSL_ERROR(DTLS_TOO_MANY_FRAGMENTS_E); - return DTLS_TOO_MANY_FRAGMENTS_E; - } - DtlsMsgStore(ssl, ssl->keys.curEpoch, + ret = DtlsMsgStore(ssl, ssl->keys.curEpoch, ssl->keys.dtls_peer_handshake_number, input + *inOutIdx, size, type, fragOffset, fragSz, ssl->heap); + if (DtlsMsgStoreFatal(ssl, ret)) + return ret; *inOutIdx += fragSz; ret = 0; if (ssl->dtls_rx_msg_list != NULL) @@ -23725,24 +23783,18 @@ static int DoDtlsHandShakeMsg(WOLFSSL* ssl, byte* input, word32* inOutIdx, #ifdef WOLFSSL_ASYNC_CRYPT if (ssl->devId != INVALID_DEVID) { word32 idx = *inOutIdx; - if (ssl->dtls_rx_msg_list_sz >= DTLS_POOL_SZ) { - WOLFSSL_ERROR(BUFFER_ERROR); - return BUFFER_ERROR; - } if (idx + fragSz > totalSz) return BUFFER_E; *inOutIdx = idx + fragSz; /* In async mode always store the message and process it with * DtlsMsgDrain because in case of a WC_PENDING_E it will be * easier this way. */ - if (ssl->dtls_rx_msg_list_sz >= DTLS_POOL_SZ) { - WOLFSSL_MSG("Reached rx msg limit error"); - return DTLS_TOO_MANY_FRAGMENTS_E; - } - DtlsMsgStore(ssl, ssl->keys.curEpoch, + ret = DtlsMsgStore(ssl, ssl->keys.curEpoch, ssl->keys.dtls_peer_handshake_number, input + idx, size, type, fragOffset, fragSz, ssl->heap); + if (DtlsMsgStoreFatal(ssl, ret)) + return ret; ret = DtlsMsgDrain(ssl); } else diff --git a/src/ssl.c b/src/ssl.c index e3890aeeba9..87df683fd6c 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -5920,6 +5920,7 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out, FreePeerCertKeys(ssl); #ifdef WOLFSSL_DTLS ssl->options.dtlsStateful = 0; + DtlsMsgRxListFree(ssl); #endif #ifdef WOLFSSL_TLS13 #if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index 24aa2593ba2..bd8c520256b 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -4626,7 +4626,9 @@ static void test_wolfSSL_dtls12_fragments_spammer(WOLFSSL* ssl) int i; int fd = wolfSSL_get_wfd(ssl); int ret = wolfSSL_connect_cert(ssl); /* This gets us past the cookie */ - word32 seq_number = 100; /* start high so server definitely reads this */ + /* Use the client's own next record numbers and move its counter past them, + * so the server's replay window does not pass the client's genuine ones. */ + word32 seq_number = ssl->keys.dtls_sequence_number_lo; word16 msg_number = 50; /* start high so server has to buffer this */ AssertIntEQ(ret, 1); /* Now let's start spamming the peer with fragments it needs to store */ @@ -4646,14 +4648,14 @@ static void test_wolfSSL_dtls12_fragments_spammer(WOLFSSL* ssl) seq_offset = idx + 2; /* increment only the low 32 bits */ idx += 6; /* static length in BE */ - c16toa(42, b + idx); + c16toa(DTLS_HANDSHAKE_HEADER_SZ + 1000, b + idx); idx += 2; /* handshake layer */ /* cert type */ b[idx++] = 11; /* length */ - c32to24(1000, b + idx); + c32to24(2000, b + idx); idx += 3; /* message seq */ c16toa(0, b + idx); @@ -4663,27 +4665,32 @@ static void test_wolfSSL_dtls12_fragments_spammer(WOLFSSL* ssl) c32to24(500, b + idx); idx += 3; /* frag length */ - c32to24(30, b + idx); + c32to24(1000, b + idx); idx += 3; (void)idx; /* inhibit clang-analyzer-deadcode.DeadStores */ - for (i = 0; i < DTLS_POOL_SZ * 2 && ret > 0; + /* More than the receive budget holds */ + for (i = 0; i < (int)(WOLFSSL_DTLS_RX_MAX_BYTES / 1000) + 10 && ret > 0; seq_number++, msg_number++, i++) { struct timespec delay; XMEMSET(&delay, 0, sizeof(delay)); delay.tv_nsec = 10000000; /* wait 0.01 seconds */ c32toa(seq_number, b + seq_offset); c16toa(msg_number, b + msg_offset); - ret = (int)send(fd, (MESSAGE_TYPE_CAST)b, 55, 0); + ret = (int)send(fd, (MESSAGE_TYPE_CAST)b, + DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ + 1000, 0); nanosleep(&delay, NULL); } + ssl->keys.dtls_sequence_number_lo = seq_number; } #ifdef WOLFSSL_DTLS13 static void test_wolfSSL_dtls13_fragments_spammer(WOLFSSL* ssl) { - const word16 sendCountMax = 100; - byte b[150]; /* buffer for the messages to send */ + /* More than the receive budget holds */ + const word16 sendCountMax = + (word16)(10 + WOLFSSL_DTLS_RX_MAX_BYTES / 1000 + 10); + byte b[1100]; /* buffer for the messages to send */ size_t idx = 0; size_t msg_offset = 0; int fd = wolfSSL_get_wfd(ssl); @@ -4705,13 +4712,13 @@ static void test_wolfSSL_dtls13_fragments_spammer(WOLFSSL* ssl) c32to24(5000, b + idx); idx += 3; /* fragment_length */ - c32to24(100, b + idx); + c32to24(1000, b + idx); idx += 3; /* fragment contents */ - idx += 100; + idx += 1000; for (; ret > 0 && msg_number < sendCountMax; msg_number++) { - byte sendBuf[150]; + byte sendBuf[1200]; int sendSz = sizeof(sendBuf); struct timespec delay; XMEMSET(&delay, 0, sizeof(delay)); @@ -4736,14 +4743,15 @@ int test_wolfSSL_dtls_fragments(void) method_provider client_meth; method_provider server_meth; ssl_callback spammer; + int authenticated; /* the spam is in an encrypted epoch */ } params[] = { #if !defined(WOLFSSL_NO_TLS12) {wolfDTLSv1_2_client_method, wolfDTLSv1_2_server_method, - test_wolfSSL_dtls12_fragments_spammer}, + test_wolfSSL_dtls12_fragments_spammer, 0}, #endif #ifdef WOLFSSL_DTLS13 {wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method, - test_wolfSSL_dtls13_fragments_spammer}, + test_wolfSSL_dtls13_fragments_spammer, 1}, #endif }; @@ -4774,16 +4782,22 @@ int test_wolfSSL_dtls_fragments(void) ExpectIntEQ(func_cb_client.last_err, WC_NO_ERR_TRACE(SOCKET_ERROR_E)); } } - /* Check the server returned an error indicating the msg buffer - * was full. + /* The flood exceeds the receive budget. Plaintext DTLS 1.2 + * fragments could come from anyone: the server drops what does not + * fit and completes. The DTLS 1.3 ones are authenticated, so the + * server fails with DTLS_TOO_MANY_FRAGMENTS_E. * * Under slow execution (e.g. valgrind + noasm), the real handshake * from wolfSSL_negotiate() may complete before enough spam fragments * accumulate to trigger DTLS_TOO_MANY_FRAGMENTS_E. Accept both - * outcomes: server hit the fragment limit, or completed normally. + * outcomes there: server hit the budget, or completed normally. */ - if (func_cb_server.return_code == TEST_FAIL) { - ExpectIntEQ(func_cb_server.last_err, WC_NO_ERR_TRACE(DTLS_TOO_MANY_FRAGMENTS_E)); + if (!params[i].authenticated) { + ExpectIntEQ(func_cb_server.return_code, TEST_SUCCESS); + } + else if (func_cb_server.return_code == TEST_FAIL) { + ExpectIntEQ(func_cb_server.last_err, + WC_NO_ERR_TRACE(DTLS_TOO_MANY_FRAGMENTS_E)); } if (EXPECT_FAIL()) @@ -5768,6 +5782,147 @@ int test_wolfSSL_DtlsUpdateWindow(void) } #endif /* WOLFSSL_DTLS */ +/* RX admission counts retained allocations, not messages or fragments. */ +int test_wolfSSL_DTLS_rx_bytes(void) +{ + EXPECT_DECLS; +#ifdef WOLFSSL_DTLS + WOLFSSL* ssl = NULL; + byte* data = NULL; + DtlsMsg* msg = NULL; + byte* raw = NULL; + size_t base = sizeof(DtlsMsg) + sizeof(DtlsFragBucket); + word32 len = (word32)(WOLFSSL_DTLS_RX_MAX_BYTES - base); + word32 i; + + ExpectNotNull(ssl = (WOLFSSL*)XMALLOC(sizeof(WOLFSSL), NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + ExpectNotNull(data = (byte*)XMALLOC(WOLFSSL_DTLS_RX_MAX_BYTES, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + if (EXPECT_FAIL()) { + XFREE(ssl, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(data, NULL, DYNAMIC_TYPE_TMP_BUFFER); + return EXPECT_RESULT(); + } + XMEMSET(ssl, 0, sizeof(*ssl)); + XMEMSET(data, 0x5a, WOLFSSL_DTLS_RX_MAX_BYTES); + ssl->options.dtlsStateful = 1; + ExpectIntEQ(DtlsMsgListRetainedBytes(NULL), 0); + /* Separate one-byte buckets cost a header each; bridging releases one. */ + ExpectIntEQ(DtlsMsgStore(ssl, 0, 0, data, 4, certificate, 0, 1, + NULL), 0); + ExpectIntEQ(DtlsMsgListRetainedBytes(ssl->dtls_rx_msg_list), base + 1); + ExpectIntEQ(DtlsMsgStore(ssl, 0, 0, data, 4, certificate, 3, 1, + NULL), 0); + ExpectIntEQ(DtlsMsgListRetainedBytes(ssl->dtls_rx_msg_list), + base + sizeof(DtlsFragBucket) + 2); + ExpectIntEQ(DtlsMsgStore(ssl, 0, 0, data, 4, certificate, 1, 2, + NULL), 0); + ExpectNotNull(msg = ssl->dtls_rx_msg_list); + ExpectIntEQ(msg->ready, 1); + ExpectNull(msg->fragBucketList); + ExpectNotNull(raw = msg->raw); + ExpectBufEQ(msg->fullMsg, data, 4); + ExpectIntEQ(DtlsMsgListRetainedBytes(msg), base + 4); + ExpectIntEQ(DtlsMsgStore(ssl, 0, 0, data, 4, certificate, 0, 4, + NULL), 0); + ExpectPtrEq(msg->raw, raw); + ExpectIntEQ(DtlsMsgListRetainedBytes(msg), base + 4); + DtlsMsgListDelete(ssl->dtls_rx_msg_list, NULL); + ssl->dtls_rx_msg_list = NULL; + ssl->dtls_rx_msg_list_sz = 0; + ssl->dtls_rx_msg_list_bytes = 0; + ExpectIntEQ(DtlsMsgListRetainedBytes(ssl->dtls_rx_msg_list), 0); + + /* Exactly full is admitted. Neither new entries nor growth may evict. */ + ExpectIntEQ(DtlsMsgStore(ssl, 0, 0, data, len + 1, certificate, + 0, len, NULL), 0); + ExpectNotNull(msg = ssl->dtls_rx_msg_list); + ExpectIntEQ(DtlsMsgListRetainedBytes(msg), WOLFSSL_DTLS_RX_MAX_BYTES); + ExpectIntEQ(DtlsMsgStore(ssl, 0, 1, data, 1, certificate, 0, 1, NULL), + DTLS_TOO_MANY_FRAGMENTS_E); + ExpectIntEQ(DtlsMsgStore(ssl, 0, 0, data, len + 1, certificate, + len, 1, NULL), DTLS_TOO_MANY_FRAGMENTS_E); + ExpectPtrEq(ssl->dtls_rx_msg_list, msg); + ExpectIntEQ(ssl->dtls_rx_msg_list_sz, 1); + ExpectIntEQ(msg->bytesReceived, len); + ExpectIntEQ(DtlsMsgListRetainedBytes(msg), WOLFSSL_DTLS_RX_MAX_BYTES); + DtlsMsgListDelete(ssl->dtls_rx_msg_list, NULL); + ssl->dtls_rx_msg_list = NULL; + ssl->dtls_rx_msg_list_sz = 0; + ssl->dtls_rx_msg_list_bytes = 0; + + /* Fill with tiny messages: metadata, not an old count cap, is the limit. */ + for (i = 0; i < WOLFSSL_DTLS_RX_MAX_BYTES / (base + 1) && + EXPECT_SUCCESS(); i++) { + ExpectIntEQ(DtlsMsgStore(ssl, 0, i, data, 1, certificate, 0, 1, + NULL), 0); + ExpectIntEQ(DtlsMsgListRetainedBytes(ssl->dtls_rx_msg_list), + (i + 1) * (base + 1)); + } + ExpectIntEQ(ssl->dtls_rx_msg_list_sz, i); + ExpectIntEQ(ssl->dtls_rx_msg_list_bytes, + DtlsMsgListRetainedBytes(ssl->dtls_rx_msg_list)); + ExpectIntEQ(DtlsMsgStore(ssl, 0, i, data, 1, certificate, 0, 1, NULL), + DTLS_TOO_MANY_FRAGMENTS_E); + ExpectIntEQ(ssl->dtls_rx_msg_list_sz, i); + DtlsMsgListDelete(ssl->dtls_rx_msg_list, NULL); + ssl->dtls_rx_msg_list = NULL; + ssl->dtls_rx_msg_list_sz = 0; + ssl->dtls_rx_msg_list_bytes = 0; + ExpectIntEQ(DtlsMsgListRetainedBytes(ssl->dtls_rx_msg_list), 0); + + /* A merge that reduces retained bytes must succeed even at capacity. */ + ExpectIntEQ(DtlsMsgStore(ssl, 0, 0, data, 4, certificate, 0, 1, + NULL), 0); + ExpectIntEQ(DtlsMsgStore(ssl, 0, 0, data, 4, certificate, 3, 1, + NULL), 0); + len = (word32)(WOLFSSL_DTLS_RX_MAX_BYTES - 2 * base - + sizeof(DtlsFragBucket) - 2); + ExpectIntEQ(DtlsMsgStore(ssl, 0, 1, data, len, certificate, 0, len, + NULL), 0); + ExpectIntEQ(DtlsMsgListRetainedBytes(ssl->dtls_rx_msg_list), + WOLFSSL_DTLS_RX_MAX_BYTES); + ExpectIntEQ(DtlsMsgStore(ssl, 0, 0, data, 4, certificate, 1, 2, + NULL), 0); + ExpectIntEQ(ssl->dtls_rx_msg_list_sz, 2); + ExpectIntEQ(DtlsMsgListRetainedBytes(ssl->dtls_rx_msg_list), + WOLFSSL_DTLS_RX_MAX_BYTES - sizeof(DtlsFragBucket) + 2); + ExpectIntEQ(ssl->dtls_rx_msg_list_bytes, + DtlsMsgListRetainedBytes(ssl->dtls_rx_msg_list)); + DtlsMsgListDelete(ssl->dtls_rx_msg_list, NULL); + ssl->dtls_rx_msg_list = NULL; + ssl->dtls_rx_msg_list_sz = 0; + ssl->dtls_rx_msg_list_bytes = 0; + + /* At most DTLS_FRAG_POOL_SZ buckets: a fragment opening one more gap is + * dropped, one touching a bucket is still taken. */ + for (i = 0; i < DTLS_FRAG_POOL_SZ && EXPECT_SUCCESS(); i++) { + ExpectIntEQ(DtlsMsgStore(ssl, 0, 0, data, 4 * DTLS_FRAG_POOL_SZ, + certificate, 4 * i, 1, NULL), 0); + } + ExpectNotNull(msg = ssl->dtls_rx_msg_list); + ExpectIntEQ(msg->fragBucketListCount, DTLS_FRAG_POOL_SZ); + ExpectIntEQ(DtlsMsgStore(ssl, 0, 0, data, 4 * DTLS_FRAG_POOL_SZ, + certificate, 2, 1, NULL), DTLS_MSG_STORE_DROPPED); + ExpectIntEQ(msg->fragBucketListCount, DTLS_FRAG_POOL_SZ); + ExpectIntEQ(msg->bytesReceived, DTLS_FRAG_POOL_SZ); + ExpectIntEQ(DtlsMsgStore(ssl, 0, 0, data, 4 * DTLS_FRAG_POOL_SZ, + certificate, 1, 1, NULL), 0); + ExpectIntEQ(msg->fragBucketListCount, DTLS_FRAG_POOL_SZ); + ExpectIntEQ(DtlsMsgStore(ssl, 0, 0, data, 4 * DTLS_FRAG_POOL_SZ, + certificate, 2, 2, NULL), 0); + ExpectIntEQ(msg->fragBucketListCount, DTLS_FRAG_POOL_SZ - 1); + ExpectIntEQ(DtlsMsgListRetainedBytes(msg), sizeof(DtlsMsg) + + (DTLS_FRAG_POOL_SZ - 1) * sizeof(DtlsFragBucket) + + DTLS_FRAG_POOL_SZ + 3); + DtlsMsgListDelete(ssl->dtls_rx_msg_list, NULL); + XFREE(data, NULL, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(ssl, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return EXPECT_RESULT(); +} + #ifdef WOLFSSL_DTLS static int DFB_TEST(WOLFSSL* ssl, word32 seq, word32 len, word32 f_offset, word32 f_len, word32 f_count, byte ready, word32 bytesReceived) @@ -5789,7 +5944,8 @@ static int DFB_TEST(WOLFSSL* ssl, word32 seq, word32 len, word32 f_offset, ExpectIntLE(f_offset + f_len, sizeof(msg)); if (EXPECT_SUCCESS()) - DtlsMsgStore(ssl, 0, seq, msg + f_offset, len, certificate, f_offset, f_len, NULL); + ExpectIntEQ(DtlsMsgStore(ssl, 0, seq, msg + f_offset, len, + certificate, f_offset, f_len, NULL), 0); ExpectNotNull(ssl->dtls_rx_msg_list); @@ -5930,6 +6086,7 @@ int test_wolfSSL_DTLS_fragment_buckets(void) DtlsMsgListDelete(ssl->dtls_rx_msg_list, ssl->heap); ssl->dtls_rx_msg_list = NULL; ssl->dtls_rx_msg_list_sz = 0; + ssl->dtls_rx_msg_list_bytes = 0; return EXPECT_RESULT(); } @@ -5986,6 +6143,7 @@ static void dfbr_free_list(WOLFSSL* ssl) DtlsMsgListDelete(ssl->dtls_rx_msg_list, ssl->heap); ssl->dtls_rx_msg_list = NULL; ssl->dtls_rx_msg_list_sz = 0; + ssl->dtls_rx_msg_list_bytes = 0; } /* Check msg against the coverage bitmap and the source pattern. */ @@ -6127,7 +6285,7 @@ static int dfbr_random_pass(WOLFSSL* ssl, const byte* pattern, int injectFail) if (failed) { /* Either an allocation failed or none was needed */ ExpectTrue(ret == 0 || ret == WC_NO_ERR_TRACE(MEMORY_ERROR) || - ret == WC_NO_ERR_TRACE(DTLS_TOO_MANY_FRAGMENTS_E)); + ret == DTLS_MSG_STORE_DROPPED); ExpectIntEQ(dfbr_fail, ret == WC_NO_ERR_TRACE(MEMORY_ERROR) ? 0 : 1); dfbr_fail = 0; @@ -6135,8 +6293,7 @@ static int dfbr_random_pass(WOLFSSL* ssl, const byte* pattern, int injectFail) else #endif { - ExpectTrue(ret == 0 || - ret == WC_NO_ERR_TRACE(DTLS_TOO_MANY_FRAGMENTS_E)); + ExpectTrue(ret == 0 || ret == DTLS_MSG_STORE_DROPPED); } if (ret == 0) { for (i = off; i < off + fsz; i++) @@ -6355,9 +6512,12 @@ int test_wolfSSL_DTLS_fragment_buckets_random(void) NULL), 0); ExpectNotNull(msg = DtlsMsgFind(ssl->dtls_rx_msg_list, 0, 1)); ExpectIntEQ(msg->bytesReceived, 20); - /* Corrupt the count so the next fragment appears to complete the msg */ - if (msg != NULL) + /* Corrupt the count so the next fragment appears to complete the msg. + * The list's byte count follows it. */ + if (msg != NULL) { msg->bytesReceived = 25; + ssl->dtls_rx_msg_list_bytes += 5; + } ExpectIntEQ(DtlsMsgStore(ssl, 0, 1, pattern + 10, 30, certificate, 10, 5, NULL), WC_NO_ERR_TRACE(BAD_STATE_E)); ExpectNull(msg->fragBucketList); @@ -6386,11 +6546,10 @@ int test_wolfSSL_DTLS_fragment_buckets_random(void) ExpectIntEQ(msg->bytesReceived, DTLS_FRAG_POOL_SZ); /* Disjoint after the last bucket */ ExpectIntEQ(DtlsMsgStore(ssl, 0, 1, pattern + len - 1, len, - certificate, len - 1, 1, NULL), - WC_NO_ERR_TRACE(DTLS_TOO_MANY_FRAGMENTS_E)); + certificate, len - 1, 1, NULL), DTLS_MSG_STORE_DROPPED); /* Disjoint before the first bucket */ ExpectIntEQ(DtlsMsgStore(ssl, 0, 1, pattern, len, certificate, 0, 1, - NULL), WC_NO_ERR_TRACE(DTLS_TOO_MANY_FRAGMENTS_E)); + NULL), DTLS_MSG_STORE_DROPPED); ExpectIntEQ(msg->fragBucketListCount, DTLS_FRAG_POOL_SZ); ExpectIntEQ(DtlsMsgStore(ssl, 0, 1, pattern, len, certificate, 0, len - 2, NULL), 0); diff --git a/tests/api/test_dtls.h b/tests/api/test_dtls.h index 99c342c31bf..53ee30317fc 100644 --- a/tests/api/test_dtls.h +++ b/tests/api/test_dtls.h @@ -202,6 +202,7 @@ int test_wolfSSL_DTLS_msg_list_purge(void); int test_wolfSSL_DTLS_msg_list_order(void); int test_wolfSSL_DTLS_stale_partial_head(void); int test_wolfSSL_DTLS_plaintext_record_window(void); +int test_wolfSSL_DTLS_rx_bytes(void); int test_wolfSSL_dtls_stateless2(void); int test_wolfSSL_dtls_stateless_maxfrag(void); int test_wolfSSL_dtls_stateless_resume(void); @@ -279,6 +280,7 @@ int test_dtls_no_cookie_ch_pause(WOLFSSL* ssl, void* ctx); TEST_DECL_GROUP("dtls", test_wolfSSL_dtls_stateless), \ TEST_DECL_GROUP("dtls", test_wolfSSL_dtls_stateless_hrr_group), \ TEST_DECL_GROUP("dtls", test_wolfSSL_DtlsUpdateWindow), \ + TEST_DECL_GROUP("dtls", test_wolfSSL_DTLS_rx_bytes), \ TEST_DECL_GROUP("dtls", test_wolfSSL_DTLS_fragment_buckets), \ TEST_DECL_GROUP("dtls", test_wolfSSL_DTLS_fragment_buckets_random), \ TEST_DECL_GROUP("dtls", test_wolfSSL_DTLS_msg_list_purge), \ diff --git a/tests/api/test_dtls13.c b/tests/api/test_dtls13.c index c97d9b6d809..f7099d3a187 100644 --- a/tests/api/test_dtls13.c +++ b/tests/api/test_dtls13.c @@ -702,6 +702,97 @@ int test_dtls13_no_cookie_handoff(void) return EXPECT_RESULT(); } +/* Which results of DtlsMsgStore() end the connection: only running out of + * memory, or an authenticated peer exceeding the receive budget */ +int test_dtls13_frag_store_fatal(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte data[32]; + word16 epoch = 0; + int ret = 0; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(data, 0xAB, sizeof(data)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + + /* Plaintext could come from anyone: only memory ends the connection */ + ExpectIntEQ(DtlsMsgStoreFatal(ssl_s, 0), 0); + ExpectIntEQ(DtlsMsgStoreFatal(ssl_s, DTLS_MSG_STORE_DROPPED), 0); + ExpectIntEQ(DtlsMsgStoreFatal(ssl_s, WC_NO_ERR_TRACE(BAD_FUNC_ARG)), 0); + ExpectIntEQ(DtlsMsgStoreFatal(ssl_s, + WC_NO_ERR_TRACE(DTLS_TOO_MANY_FRAGMENTS_E)), 0); + ExpectIntNE(DtlsMsgStoreFatal(ssl_s, WC_NO_ERR_TRACE(MEMORY_E)), 0); + ExpectIntNE(DtlsMsgStoreFatal(ssl_s, WC_NO_ERR_TRACE(MEMORY_ERROR)), 0); + + /* The records of a peer that finished the handshake authenticate */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + if (EXPECT_SUCCESS()) { + ExpectIntNE(ssl_s->keys.encryptionOn, 0); + ExpectIntEQ(w64IsZero(ssl_s->keys.curEpoch64), 0); + epoch = (word16)w64GetLow32(ssl_s->keys.curEpoch64); + } + ExpectIntEQ(DtlsMsgStoreFatal(ssl_s, 0), 0); + ExpectIntEQ(DtlsMsgStoreFatal(ssl_s, DTLS_MSG_STORE_DROPPED), 0); + ExpectIntNE(DtlsMsgStoreFatal(ssl_s, + WC_NO_ERR_TRACE(DTLS_TOO_MANY_FRAGMENTS_E)), 0); + ExpectIntNE(DtlsMsgStoreFatal(ssl_s, WC_NO_ERR_TRACE(MEMORY_E)), 0); + /* A fragment that gives the message held another size is dropped */ + ExpectIntEQ(DtlsMsgStore(ssl_s, epoch, 20, data, 32, certificate, 0, 16, + ssl_s->heap), 0); + ret = DtlsMsgStore(ssl_s, epoch, 20, data, 24, certificate, 16, 8, + ssl_s->heap); + ExpectIntEQ(ret, WC_NO_ERR_TRACE(SEQUENCE_ERROR)); + ExpectIntEQ(DtlsMsgStoreFatal(ssl_s, ret), 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* wolfSSL_clear() frees the receive list: a reused object starts with no + * message of the last connection. */ +int test_dtls13_clear_frees_rx_list(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && (defined(OPENSSL_EXTRA) || defined(DEBUG_WOLFSSL_VERBOSE) || \ + defined(HAVE_CURL)) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte data[16]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(data, 0x5a, sizeof(data)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(DtlsMsgStore(ssl_s, 0, 3, data, (word32)sizeof(data), + certificate, 0, 8, ssl_s->heap), 0); + ExpectNotNull(ssl_s->dtls_rx_msg_list); + ExpectIntEQ(ssl_s->dtls_rx_msg_list_sz, 1); + ExpectIntGT(ssl_s->dtls_rx_msg_list_bytes, 0); + ExpectIntEQ(wolfSSL_clear(ssl_s), WOLFSSL_SUCCESS); + ExpectNull(ssl_s->dtls_rx_msg_list); + ExpectIntEQ(ssl_s->dtls_rx_msg_list_sz, 0); + ExpectIntEQ(ssl_s->dtls_rx_msg_list_bytes, 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + int test_dtls_empty_keyshare_with_cookie(void) { EXPECT_DECLS; diff --git a/tests/api/test_dtls13.h b/tests/api/test_dtls13.h index 32030adcc1c..154e8a74b8a 100644 --- a/tests/api/test_dtls13.h +++ b/tests/api/test_dtls13.h @@ -36,6 +36,8 @@ int test_dtls13_frag_ch_pq_no_cookie(void); int test_dtls_frag_ch(void); int test_dtls13_frag_ch1_no_cookie(void); int test_dtls13_no_cookie_handoff(void); +int test_dtls13_frag_store_fatal(void); +int test_dtls13_clear_frees_rx_list(void); int test_dtls_empty_keyshare_with_cookie(void); int test_dtls13_missing_finished_client(void); int test_dtls13_missing_finished_server(void); @@ -79,6 +81,8 @@ int test_dtls13_update_keys_want_write(void); TEST_DECL_GROUP("dtls13", test_dtls_frag_ch), \ TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch1_no_cookie), \ TEST_DECL_GROUP("dtls13", test_dtls13_no_cookie_handoff), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_store_fatal), \ + TEST_DECL_GROUP("dtls13", test_dtls13_clear_frees_rx_list), \ TEST_DECL_GROUP("dtls13", test_dtls_empty_keyshare_with_cookie), \ TEST_DECL_GROUP("dtls13", test_dtls13_missing_finished_client), \ TEST_DECL_GROUP("dtls13", test_dtls13_missing_finished_server), \ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index c8d7ff0c28c..610542b2f1d 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -1237,6 +1237,8 @@ enum { #define WOLFSSL_DTLS_MTU_ADDITIONAL_READ_BUFFER 500 #endif /* WOLFSSL_DTLS_MTU_ADDITIONAL_READ_BUFFER */ +/* Disjoint fragment buckets a buffered handshake message may have. A fragment + * that would open one more gap is dropped, not acknowledged. */ #ifndef WOLFSSL_DTLS_FRAG_POOL_SZ #define WOLFSSL_DTLS_FRAG_POOL_SZ 10 #endif @@ -1244,6 +1246,11 @@ enum { #error "WOLFSSL_DTLS_FRAG_POOL_SZ must be at most 255" #endif +/* Bytes a WOLFSSL's DTLS receive list retains, metadata included. Room for a + * maximum certificate message and other buffered messages. */ +#ifndef WOLFSSL_DTLS_RX_MAX_BYTES + #define WOLFSSL_DTLS_RX_MAX_BYTES (MAX_HANDSHAKE_SZ + 16UL * 1024UL) +#endif /* set minimum DH key size allowed */ #ifndef WOLFSSL_MIN_DHKEY_BITS #if defined(WOLFSSL_HARDEN_TLS) && !defined(WOLFSSL_HARDEN_TLS_NO_PKEY_CHECK) @@ -1660,8 +1667,7 @@ enum Misc { DTLS_RECORD_EXTRA = 8, /* diff from normal */ DTLS_HANDSHAKE_SEQ_SZ = 2, /* handshake header sequence number */ DTLS_HANDSHAKE_FRAG_SZ = 3, /* fragment offset and length are 24 bit */ - DTLS_POOL_SZ = 20, /* allowed number of list items in TX and - * RX pool */ + DTLS_POOL_SZ = 20, /* allowed number of list items in TX pool */ DTLS_FRAG_POOL_SZ = WOLFSSL_DTLS_FRAG_POOL_SZ, /* allowed number of fragments per msg */ DTLS_EXPORT_PRO = 165,/* wolfSSL protocol for serialized session */ @@ -6592,7 +6598,7 @@ typedef struct DtlsMsg { word32 seq; /* Handshake sequence number */ word32 sz; /* Length of whole message */ byte type; - byte fragBucketListCount; + word32 fragBucketListCount; byte ready:1; byte encrypted:1; } DtlsMsg; @@ -7249,6 +7255,7 @@ struct WOLFSSL { #endif /* !NO_ASN_TIME */ word32 dtls_tx_msg_list_sz; word32 dtls_rx_msg_list_sz; + size_t dtls_rx_msg_list_bytes; /* retained by the list */ DtlsMsg* dtls_tx_msg_list; DtlsMsg* dtls_tx_msg; DtlsMsg* dtls_rx_msg_list; @@ -8134,6 +8141,8 @@ WOLFSSL_LOCAL int Tls13KeyUpdateLimitReached(WOLFSSL* ssl); #define DtlsMsgListNextReady wolfSSL_DtlsMsgListNextReady #define DtlsMsgFind wolfSSL_DtlsMsgFind #define DtlsMsgStore wolfSSL_DtlsMsgStore + #define DtlsMsgListRetainedBytes wolfSSL_DtlsMsgListRetainedBytes + #define DtlsMsgStoreFatal wolfSSL_DtlsMsgStoreFatal #endif /* WOLFSSL_API_PREFIX_MAP */ WOLFSSL_LOCAL DtlsMsg* DtlsMsgNew(word32 sz, byte tx, void* heap); WOLFSSL_LOCAL void DtlsMsgDelete(DtlsMsg* item, void* heap); @@ -8148,10 +8157,18 @@ WOLFSSL_LOCAL int Tls13KeyUpdateLimitReached(WOLFSSL* ssl); word32 totalLen, byte encrypted); WOLFSSL_TEST_VIS DtlsMsg* DtlsMsgFind(DtlsMsg* head, word16 epoch, word32 seq); + WOLFSSL_TEST_VIS size_t DtlsMsgListRetainedBytes(const DtlsMsg* head); + /* DtlsMsgStore() kept nothing and that is not an error: the fragment + * must not be acknowledged */ + #define DTLS_MSG_STORE_DROPPED 1 WOLFSSL_TEST_VIS int DtlsMsgStore(WOLFSSL* ssl, word16 epoch, word32 seq, const byte* data, word32 dataSz, byte type, word32 fragOffset, word32 fragSz, void* heap); + WOLFSSL_LOCAL void DtlsMsgRxListInsert(WOLFSSL* ssl, DtlsMsg* msg); + WOLFSSL_LOCAL DtlsMsg* DtlsMsgRxListDetach(WOLFSSL* ssl, DtlsMsg* item); + WOLFSSL_LOCAL void DtlsMsgRxListFree(WOLFSSL* ssl); + WOLFSSL_TEST_VIS int DtlsMsgStoreFatal(WOLFSSL* ssl, int ret); WOLFSSL_LOCAL DtlsMsg* DtlsMsgInsert(DtlsMsg* head, DtlsMsg* item); WOLFSSL_LOCAL int DtlsMsgPoolSave(WOLFSSL* ssl, const byte* data, From d92b198d7a77736408ff9123353dde575125eda9 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Tue, 3 Nov 2026 16:40:05 +0000 Subject: [PATCH 03/10] DTLS 1.3: reassemble a fragmented ClientHello before the cookie is verified A stateless DTLS 1.3 server dropped every fragment of a first ClientHello, so clients that fragment it (a post-quantum key share, a small MTU) failed in silence. RFC 9147 Section 5.5 says a receiver MUST buffer or process a fragment. - Reassemble the first ClientHello before its cookie is verified and run the cookie check on the complete message. No key, group or transcript state is kept until a cookie verifies. - New wolfSSL_dtls13_reassemble_ch_before_cookie() and wolfSSL_CTX_dtls13_reassemble_ch_before_cookie() turn it off, and wolfSSL_CTX_dtls13_clear_ch_pool() frees what is held. On by default, and ClientHello reassembly is on for every WOLFSSL_DTLS_CH_FRAG server, not only with ML-KEM. Like wolfSSL_dtls13_allow_ch_frag(), they return WOLFSSL_FAILURE on a client. - The fragments are held in a pool owned by WOLFSSL_CTX, so any stateless server made from the context can complete a ClientHello another one started: with one listener per handshake, a second client's partial ClientHello would otherwise be lost when the first client's cookie verified. The pool is split into WOLFSSL_DTLS_CH_POOL_SHARDS shards (default 8, 1 with SINGLE_THREADED) picked by a hash of the peer address, each with its own list and lock, so different peers rarely contend. A thread holds one shard lock at most. A shard costs a context 64 bytes on a 64-bit threaded build, 16 bytes single-threaded. Only a DTLS context initializes the pool, and the pool refuses stores and takes until it is, so an object moved to another kind of context by wolfSSL_set_SSL_CTX() holds nothing. The pool needs atomics in a threaded build and a clock in any (NO_ASN_TIME is not supported), and fails to compile without. - Key the pool only on the remote address the I/O layer reports, as the bytes it reports: wolfSSL_dtls_set_peer() takes any bytes a custom transport chooses, so nothing in them is interpreted, and sockAddrEqual() is only for addresses our own I/O callbacks made. For those the bytes are stable: EmbedReceiveFrom() zeroes the buffer, and Linux and FreeBSD zero sin_zero and sin6_flowinfo and derive sin6_scope_id from the address and interface. An empty address never matches. A server that does not know the peer's address holds nothing: it drops such a fragment without an ACK. A ClientHello whose cookie is in its first fragment still completes without an address, statefully. - When a cookie verifies, only that peer's entry leaves the pool. When the verified message is the first fragment of a ClientHello, the peer's held fragments of the same message (epoch, sequence number, type and size) become a message of the connection and the fragment completes it, so the server flight goes out without the client resending them. DoClientHelloStateless() takes the size of the fragment in hand and of the whole ClientHello for this, and the pool frees the peer's entry with DtlsChPoolDrop() or hands back the rest with DtlsChPoolTakeRest(). What the entry holds in the verified fragment's range is cut away, since the receive list keeps the bytes it already has when a fragment overlaps them: held bytes never replace verified ones, and the rest completes the message. - Hold only a first fragment the cookie check could not judge: one with no cookie in it, or one that ends before the fields the check reads. One whose cookie failed gets the fatal illegal_parameter alert a whole ClientHello gets, and whatever the pool held for its peer goes. - WOLFSSL_DTLS_RX_UNVERIFIED_MAX_BYTES bounds every unverified byte of a context, across all shards: 64 KiB by default, 20 to 30 ClientHellos with post-quantum key shares. A pool entry is bounded on its own by MAX_HANDSHAKE_SZ. The budget is an atomic counter. A store reserves the most its fragment can add, a new entry with one bucket, before it takes any lock, evicts for that bound when it passes the budget, then locks its shard once, stores, and settles the reservation to the bytes really added. The bound over-reserves, and so may over-evict, by the headers when the entry exists and by the fragment when it repeats bytes already held; genuine fragments are disjoint, so they come close to it. The largest message the pool completes is the budget less twice the entry overhead, since its last fragment is reserved as a new entry on top of the entry holding the rest. The counter may pass the budget by one bound per store evicting at once; a static assert leaves room for 64. - When the budget is short, evict the least recently progressed entry of the whole pool, ordered by a context-wide progress value an entry takes under the shard lock whenever it gains bytes. Each shard publishes the progress of its two oldest entries at every unlock, so eviction finds the oldest shard without locks. The storing peer's own entry is never evicted: when the scan lands on it, the entry behind it in that shard goes, and the shard is judged by its second value from then on. The victim is rechecked under its shard's lock, where a stale value is a miss. An entry that cannot fit by itself evicts nothing. Frees happen outside the shard lock. - An entry that gains no bytes for the storing server's retransmission maximum (wolfSSL_dtls_set_timeout_max()) expires, and a fragment whose bytes conflict with those held replaces the entry, so stale or forged bytes do not join a later ClientHello. - Never pool a whole ClientHello. One the pool completes joins the receive list and is drained like any other message, which also covers one left pending by asynchronous crypto. - Entries are allocated from the context's heap, which every pool call is given, and rebuilt on the server's heap only when the two differ. The pool allocates only while servers run, by when static memory loaded into the context is in place. - The first fragment of a DTLS 1.2 ClientHello whose cookie verifies is stored, and the rest the pool holds for it taken, where before it was ignored and the client had to resend the message after its timeout. - A second ClientHello carrying another server's cookie fails unless the servers share the cookie secret. - wolfSSL_dtls13_allow_ch_frag() and wolfSSL_dtls13_no_hrr_on_resume() return BAD_FUNC_ARG for a NULL ssl. - Example server: --reassemble-before-cookie [n]. - Document the fragmentation controls and WOLFSSL_DTLS_CH_FRAG. - Tests: reassembly, interleaved peers, eviction, bounds, forged, planted and stale fragments, a foreign datagram mid-handshake, two listeners, second ClientHello hand-off with and without a shared secret, shared budget, expiry under the storing server's maximum, conflicting fragments, fragments without an address, addresses differing in one byte or in length or only in sin_zero being different peers, a whole ClientHello under asynchronous crypto, a first fragment with a corrupted cookie, a pooled tail reaching into the first fragment that verifies, freeing a listener or the context with entries held, wolfSSL_set_SSL_CTX(), a TLS context having no pool, static memory loaded before and after the context is made, a DTLS 1.2 second ClientHello in two fragments, entries in different shards sharing the budget and eviction across shards, the bound settling to the bytes added, a repeated fragment evicting for its bound, eviction of the entry behind the storing peer's own and of other shards' entries around it, a fragment refused by the bucket cap, clearing the pool, many concurrent peers checked against the budget by a monitor thread, concurrent stores, and two threads running full handshakes through one context's pool. Those threads start after one handshake, because wolfCrypt sets its CPU feature flags on first use without a lock. --- CMakeLists.txt | 4 +- ChangeLog.md | 30 + configure.ac | 5 +- doc/dox_comments/header_files/ssl.h | 199 +- examples/server/server.c | 35 +- src/dtls.c | 67 +- src/dtls13.c | 136 +- src/internal.c | 707 +++- src/tls13.c | 3 +- tests/api/test_dtls.c | 113 + tests/api/test_dtls.h | 2 + tests/api/test_dtls13.c | 5251 ++++++++++++++++++++++++++- tests/api/test_dtls13.h | 92 + wolfcrypt/src/misc.c | 12 +- wolfssl/internal.h | 101 +- wolfssl/ssl.h | 5 + 16 files changed, 6641 insertions(+), 121 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 534714847a1..53d43a97789 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1041,9 +1041,9 @@ if(WOLFSSL_DTLS_CID) list(APPEND WOLFSSL_DEFINITIONS "-DWOLFSSL_DTLS_CID") endif() -# DTLS 1.3 Fragment ClientHello +# DTLS 1.3 ClientHello fragmentation handling add_option("WOLFSSL_DTLS_CH_FRAG" - "Enable wolfSSL DTLS 1.3 Fragment ClientHello (default: disabled)" + "Compile DTLS 1.3 ClientHello fragmentation handling: server-side reassembly and the client key-share policy. Forced on with ML-KEM. (default: disabled)" "no" "yes;no") if(WOLFSSL_DTLS_CH_FRAG) diff --git a/ChangeLog.md b/ChangeLog.md index f3b60730e8b..7b0935a75de 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -28,6 +28,36 @@ `wolfcrypt/src/port/nxp/README_SE050.md` and `IDE/CRYPTOCELL/README.md`. by @SparkiDev ## Behavioral Changes +* **Behavioral change (a DTLS 1.3 server reassembles a fragmented first + ClientHello before the cookie exchange)**: a stateless server dropped every + fragment of a first ClientHello, so a client whose ClientHello does not fit + one datagram (a post-quantum key share, a small MTU) failed in silence. In a + `WOLFSSL_DTLS_CH_FRAG` build (`--enable-dtls-frag-ch`, automatic with ML-KEM) + the fragments are now held in a pool owned by the `WOLFSSL_CTX` until the + ClientHello is complete, and the cookie check runs on the whole message; no + key, group or transcript state is kept until a cookie verifies. Any + stateless server made from the context can complete a ClientHello another + one started. This is on by default: the new + `wolfSSL_CTX_dtls13_reassemble_ch_before_cookie()` and + `wolfSSL_dtls13_reassemble_ch_before_cookie()` turn it off, and + `wolfSSL_CTX_dtls13_clear_ch_pool()` frees what is held. Fragmented + ClientHello support, `wolfSSL_dtls13_allow_ch_frag()`, is now on for every + DTLS 1.3 server of such a build, not only with ML-KEM, which supersedes the + 5.9.4 note below that the fragments were still dropped; without + `WOLFSSL_DTLS_CH_FRAG` they still are. The unverified bytes a context holds + are bounded by `WOLFSSL_DTLS_RX_UNVERIFIED_MAX_BYTES`, 64 KiB by default, + metadata included: when the budget is short the least recently progressed + entry is evicted, and an entry that gains no bytes for the server's + retransmission maximum expires. The pool is split into + `WOLFSSL_DTLS_CH_POOL_SHARDS` lists (8; 1 with `SINGLE_THREADED` or + `NO_HASH_WRAPPER`) picked by a hash of the peer address, each with its own + lock. It needs atomics in a + threaded build and a clock in any (`NO_ASN_TIME` is not supported). The + first fragment of a DTLS 1.2 ClientHello whose cookie verifies is now kept + and the rest of the message taken from the pool, where before it was + ignored and the client had to resend the message. The example server takes + `--reassemble-before-cookie [n]`. + * **Behavioral change (the DTLS receive list is bounded by bytes)**: the list of out-of-order and fragmented handshake messages was bounded by `DTLS_POOL_SZ` entries, 20, whatever their size, and a fragment the list diff --git a/configure.ac b/configure.ac index e867cdfe236..8ea9954e626 100644 --- a/configure.ac +++ b/configure.ac @@ -7459,9 +7459,9 @@ then AM_CFLAGS="$AM_CFLAGS -DWOLFSSL_DTLS_CID" fi -# DTLS 1.3 Fragment Second ClientHello +# DTLS 1.3 ClientHello fragmentation handling AC_ARG_ENABLE([dtls-frag-ch], - [AS_HELP_STRING([--enable-dtls-frag-ch],[Enable wolfSSL DTLS 1.3 ClientHello fragmenting (default: disabled)])], + [AS_HELP_STRING([--enable-dtls-frag-ch],[Compile DTLS 1.3 ClientHello fragmentation handling: server-side reassembly and the client key-share policy. Forced on with ML-KEM. (default: disabled)])], [ ENABLED_DTLS_CH_FRAG=$enableval ], [ ENABLED_DTLS_CH_FRAG=no ] ) @@ -15401,6 +15401,7 @@ echo " * Mosquitto $ENABLED_MOSQUITTO" echo " * ERROR_STRINGS: $ENABLED_ERROR_STRINGS" echo " * DTLS: $ENABLED_DTLS" echo " * DTLS v1.3: $ENABLED_DTLS13" +echo " * DTLS v1.3 ClientHello frag: $ENABLED_DTLS_CH_FRAG" echo " * SCTP: $ENABLED_SCTP" echo " * SRTP: $ENABLED_SRTP" echo " * Indefinite Length: $ENABLED_BER_INDEF" diff --git a/doc/dox_comments/header_files/ssl.h b/doc/dox_comments/header_files/ssl.h index 4a1d0e0f51a..42268ece9d5 100644 --- a/doc/dox_comments/header_files/ssl.h +++ b/doc/dox_comments/header_files/ssl.h @@ -3432,10 +3432,10 @@ int wolfDTLS_accept_stateless(WOLFSSL* ssl); Without cookies a DTLS 1.3 server can process a first ClientHello that arrives fragmented, but only when ClientHello fragment reassembly is available: wolfSSL must be built with WOLFSSL_DTLS_CH_FRAG - (--enable-dtls-frag-ch, turned on automatically with ML-KEM and DTLS 1.3) - and it must be enabled on the object with wolfSSL_dtls13_allow_ch_frag(), - which is done by default only in builds with ML-KEM. Otherwise the - fragments are dropped and the handshake does not progress. + (--enable-dtls-frag-ch, turned on automatically with ML-KEM and DTLS 1.3), + where it is on by default, and not turned off with + wolfSSL_dtls13_allow_ch_frag(). Otherwise the fragments are dropped and + the handshake does not progress. The DTLS cookie mode is fixed once the handshake commits to stateful processing, which the accept functions do before the first read and @@ -16204,6 +16204,9 @@ int wolfSSL_set_hrr_cookie_secret_secondary(WOLFSSL* ssl, This delegates to wolfSSL_disable_cookie(): on a DTLS object it also disables the DTLS 1.2 HelloVerifyRequest exchange, and it fails with BAD_STATE_E once the handshake has committed to stateful processing. + Without the cookie exchange a DTLS v1.3 server has nothing to verify before + it commits, so it processes the first ClientHello statefully and, when + built with WOLFSSL_DTLS_CH_FRAG, reassembles a fragmented one. \param [in,out] ssl a pointer to a WOLFSSL structure, created using wolfSSL_new(). @@ -16215,9 +16218,197 @@ int wolfSSL_set_hrr_cookie_secret_secondary(WOLFSSL* ssl, \sa wolfSSL_disable_cookie \sa wolfSSL_send_hrr_cookie + \sa wolfSSL_dtls13_allow_ch_frag */ int wolfSSL_disable_hrr_cookie(WOLFSSL* ssl); +/*! + \ingroup Setup + + \brief This function is called on a DTLS v1.3 server to let it reassemble + a fragmented ClientHello once the cookie in it has been verified, that is + the second ClientHello a client sends after a HelloRetryRequest. A + post-quantum key share does not fit one datagram, so this is on by + default and the function is kept for compatibility. For reassembling a + ClientHello before its cookie is verified see + wolfSSL_dtls13_reassemble_ch_before_cookie(). With the cookie + exchange disabled (see wolfSSL_disable_hrr_cookie()) the server is + stateful from the first datagram and any ClientHello is reassembled. + Requires WOLFSSL_DTLS_CH_FRAG (--enable-dtls-frag-ch, forced on with + ML-KEM). + + \param [in,out] ssl a pointer to a WOLFSSL structure, created using + wolfSSL_new(). + \param [in] enabled 1 to allow reassembly, 0 to drop fragments. + + \return WOLFSSL_SUCCESS if successful + \return BAD_FUNC_ARG if ssl is NULL + \return WOLFSSL_FAILURE if invoked on a client + + _Example_ + \code + WOLFSSL* ssl; + ... + wolfSSL_dtls13_allow_ch_frag(ssl, 1); + \endcode + + \sa wolfSSL_dtls13_reassemble_ch_before_cookie + \sa wolfSSL_send_hrr_cookie + \sa wolfSSL_disable_hrr_cookie +*/ +int wolfSSL_dtls13_allow_ch_frag(WOLFSSL* ssl, int enabled); + +/*! + \ingroup Setup + + \brief Lets a DTLS 1.3 server reassemble a fragmented ClientHello from a + peer whose cookie is not verified yet and run the cookie check on the + complete message. On by default with WOLFSSL_DTLS_CH_FRAG, and in effect + only while wolfSSL_dtls13_allow_ch_frag() is on. RFC 9147 Section 5.5 + requires a receiver to buffer or process a fragment: with this off the + server drops every fragmented first ClientHello, such as one carrying a + post-quantum key share. + + The fragments are held before the cookie verifies, so any sender can make + the server hold memory, within WOLFSSL_DTLS_RX_UNVERIFIED_MAX_BYTES + (default 64 KiB) for all servers of the context. The pool belongs to the + WOLFSSL_CTX, so any of its stateless servers can complete a ClientHello + another one started. Peers are told apart by the address the I/O layer + reports or the bytes set with wolfSSL_dtls_set_peer(), an IPv4 or IPv6 + address by family, port and address. Each peer holds one ClientHello, the + newest. The least recently progressed entry is evicted first, and one that + gained no bytes for the server's retransmission maximum + (wolfSSL_dtls_set_timeout_max()) expires. A server that does not know the + peer's address, as on a connected socket or a custom transport without + wolfSSL_dtls_set_peer(), holds nothing: it drops such fragments, and still + answers a whole ClientHello or one whose cookie is in its first fragment. + A server that completes another server's second ClientHello verifies the + cookie only when both were given the same secret with + wolfSSL_send_hrr_cookie(); otherwise the client gets an illegal_parameter + alert. Use one context per local endpoint if clients may reuse a source + port across endpoints. + Allocator, logging and clock callbacks may run while the pool is locked + and must not call into wolfSSL. Requires WOLFSSL_DTLS_CH_FRAG. + + \param [in,out] ssl a pointer to a WOLFSSL structure, created using + wolfSSL_new(). + \param [in] enabled 1 to reassemble before the cookie check, 0 to add + nothing to the context's pool before a cookie has verified. + + \return WOLFSSL_SUCCESS if successful + \return BAD_FUNC_ARG if ssl is NULL + \return WOLFSSL_FAILURE if invoked on a client + + _Example_ + \code + WOLFSSL* ssl; + ... + // add nothing to the pool before the peer's address is proven + wolfSSL_dtls13_reassemble_ch_before_cookie(ssl, 0); + \endcode + + \sa wolfSSL_CTX_dtls13_reassemble_ch_before_cookie + \sa wolfSSL_dtls13_allow_ch_frag + \sa wolfSSL_send_hrr_cookie + \sa wolfSSL_disable_hrr_cookie + \sa wolfSSL_dtls_set_peer +*/ +int wolfSSL_dtls13_reassemble_ch_before_cookie(WOLFSSL* ssl, int enabled); + +/*! + \ingroup Setup + + \brief This function sets the default of + wolfSSL_dtls13_reassemble_ch_before_cookie() for every WOLFSSL created + from the context, which owns the pool of ClientHellos held before their + cookie verifies. 0 also drops what the pool holds. Objects created + earlier keep their own setting. See + wolfSSL_dtls13_reassemble_ch_before_cookie() for what the setting trades. + Requires WOLFSSL_DTLS_CH_FRAG. + + \param [in,out] ctx a pointer to a WOLFSSL_CTX structure, created using + wolfSSL_CTX_new(). + \param [in] enabled 1 to reassemble before the cookie check, 0 to hold + nothing until a cookie has verified. + + \return WOLFSSL_SUCCESS if successful + \return BAD_FUNC_ARG if ctx is NULL + \return WOLFSSL_FAILURE if the context is for clients + + _Example_ + \code + WOLFSSL_CTX* ctx; + ... + wolfSSL_CTX_dtls13_reassemble_ch_before_cookie(ctx, 0); + \endcode + + \sa wolfSSL_dtls13_reassemble_ch_before_cookie + \sa wolfSSL_CTX_dtls13_clear_ch_pool +*/ +int wolfSSL_CTX_dtls13_reassemble_ch_before_cookie(WOLFSSL_CTX* ctx, + int enabled); + +/*! + \ingroup Setup + + \brief This function frees every ClientHello fragment the context holds + for peers whose cookie has not verified yet, from all its servers. Those + peers retransmit. The setting of + wolfSSL_CTX_dtls13_reassemble_ch_before_cookie() is unchanged, so the + pool fills again. Safe to call while servers of the context are running. + Requires WOLFSSL_DTLS_CH_FRAG. + + \param [in,out] ctx a pointer to a WOLFSSL_CTX structure, created using + wolfSSL_CTX_new(). + + \return WOLFSSL_SUCCESS if successful + \return BAD_FUNC_ARG if ctx is NULL + \return WOLFSSL_FAILURE if the context is for clients + + _Example_ + \code + WOLFSSL_CTX* ctx; + ... + wolfSSL_CTX_dtls13_clear_ch_pool(ctx); + \endcode + + \sa wolfSSL_CTX_dtls13_reassemble_ch_before_cookie +*/ +int wolfSSL_CTX_dtls13_clear_ch_pool(WOLFSSL_CTX* ctx); + +/*! + \ingroup Setup + + \brief This function is called on a DTLS v1.3 server to skip the cookie + exchange when a client resumes with a valid session ticket or PSK. The + resumption then costs one round trip less and the server can process + early data (0-RTT), which is otherwise dropped because the server does not + keep state until it has verified a cookie. The price is that a forged + ClientHello carrying a captured ticket or PSK identity consumes server + resources without a return-routability check. Requires + WOLFSSL_DTLS13_NO_HRR_ON_RESUME. + + \param [in,out] ssl a pointer to a WOLFSSL structure, created using + wolfSSL_new(). + \param [in] enabled 1 to skip the cookie exchange on resumption, 0 to + always do it. + + \return WOLFSSL_SUCCESS if successful + \return BAD_FUNC_ARG if ssl is NULL + \return WOLFSSL_FAILURE if invoked on a client + + _Example_ + \code + WOLFSSL* ssl; + ... + wolfSSL_dtls13_no_hrr_on_resume(ssl, 1); + \endcode + + \sa wolfSSL_send_hrr_cookie + \sa wolfSSL_disable_hrr_cookie +*/ +int wolfSSL_dtls13_no_hrr_on_resume(WOLFSSL* ssl, int enabled); + /*! \ingroup Setup diff --git a/examples/server/server.c b/examples/server/server.c index 51a565d92e4..2f4be03dfce 100644 --- a/examples/server/server.c +++ b/examples/server/server.c @@ -1051,9 +1051,10 @@ static const char* server_usage_msg[][75] = { "-Q Request certificate from client post-handshake\n", /* 51 */ #endif #ifdef WOLFSSL_SEND_HRR_COOKIE - "-J [n] Server sends Cookie Extension containing state\n" - " ('n' disables all server cookies, including the\n" - " DTLS 1.2 HelloVerifyRequest exchange)\n", /* 52 */ + "-J [n] Server sends Cookie Extension containing state (on by\n" + " default for DTLS 1.3; 'n' disables all server cookies,\n" + " including the DTLS 1.2 HelloVerifyRequest exchange)\n", + /* 52 */ #endif #endif /* WOLFSSL_TLS13 */ #ifdef WOLFSSL_EARLY_DATA @@ -1433,6 +1434,11 @@ static void Usage(void) while (msg[++msgId] != NULL) { printf("%s", msg[msgId]); } +#ifdef WOLFSSL_DTLS_CH_FRAG + printf("--reassemble-before-cookie [n] DTLS 1.3: reassemble a fragmented\n" + " ClientHello before its cookie is verified\n" + " (default on, n to disable)\n"); +#endif } #ifdef WOLFSSL_SRTP @@ -1561,6 +1567,9 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args) #if defined(WOLFSSL_TLS13) && defined(WOLFSSL_CERT_WITH_EXTERN_PSK) && \ !defined(NO_PSK) { "psk-with-certs", 0, 271 }, +#endif +#ifdef WOLFSSL_DTLS_CH_FRAG + { "reassemble-before-cookie", 2, 272 }, /* optional argument */ #endif { 0, 0, 0 } }; @@ -1678,6 +1687,9 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args) #endif /* HAVE_SECURE_RENEGOTIATION */ #ifdef WOLFSSL_SEND_HRR_COOKIE int hrrCookie = 0; +#endif +#ifdef WOLFSSL_DTLS_CH_FRAG + int reassembleBeforeCookie = -1; /* -1 keeps the library default */ #endif byte mcastID = 0; #if !defined(NO_DH) && !defined(HAVE_FIPS) && \ @@ -2452,7 +2464,14 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args) } } break; -#endif /* WOLFSSL_CID */ +#endif /* WOLFSSL_DTLS_CID */ +#ifdef WOLFSSL_DTLS_CH_FRAG + case 272: + reassembleBeforeCookie = 1; + if (myoptarg != NULL && XSTRCMP(myoptarg, "n") == 0) + reassembleBeforeCookie = 0; + break; +#endif /* WOLFSSL_DTLS_CH_FRAG */ case 264: #ifdef HAVE_SUPPORTED_CURVES #ifdef WOLFSSL_TLS13 @@ -3618,8 +3637,14 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args) #endif /* WOLFSSL_DTLS_CID */ #ifdef WOLFSSL_DTLS_CH_FRAG - if (doDTLS) + if (doDTLS) { wolfSSL_dtls13_allow_ch_frag(ssl, 1); + if (reassembleBeforeCookie != -1 && + wolfSSL_dtls13_reassemble_ch_before_cookie(ssl, + reassembleBeforeCookie) != WOLFSSL_SUCCESS) { + err_sys("unable to set ClientHello reassembly before the cookie"); + } + } #endif #ifndef WOLFSSL_CALLBACKS diff --git a/src/dtls.c b/src/dtls.c index a036a47c4aa..0a2f872075a 100644 --- a/src/dtls.c +++ b/src/dtls.c @@ -40,12 +40,21 @@ * disable the cookie exchange on resumption * - Continue like with a normal connection * WOLFSSL_DTLS_CH_FRAG - * Allow a server to process a fragmented second/verified (one containing a - * valid cookie response) ClientHello message. The first/unverified (one - * without a cookie extension) ClientHello MUST be unfragmented so that the - * DTLS server can process it statelessly. This is only implemented for - * DTLS 1.3. The user MUST call wolfSSL_dtls13_allow_ch_frag() on the server - * to explicitly enable this during runtime. + * DTLS 1.3 ClientHello fragmentation. Server: reassemble a fragmented + * ClientHello, before its cookie is verified too, and run the cookie check + * on the complete message (RFC 9147 Section 5.5: a receiver must buffer or + * process a fragment). Unverified fragments are held in a pool per + * WOLFSSL_CTX, keyed on the remote address and bounded by + * WOLFSSL_DTLS_RX_UNVERIFIED_MAX_BYTES, so any stateless server of the + * context can complete them. A server without the peer's address holds + * nothing. On by default: wolfSSL_CTX_dtls13_reassemble_ch_before_cookie() + * turns the holding off, wolfSSL_dtls13_allow_ch_frag() the reassembly, + * and wolfSSL_CTX_dtls13_clear_ch_pool() empties the pool. The pool + * needs a clock, so not NO_ASN_TIME, and atomics in a threaded build. + * Client: send an empty key share when the first ClientHello would not fit + * one datagram, at the cost of a HelloRetryRequest round trip. + * With the cookie exchange disabled the server is stateful from the first + * datagram and reassembles any ClientHello. * WOLFSSL_DTLS_RX_MAX_BYTES * Bound on the bytes the receive list of one WOLFSSL retains for * out-of-order and fragmented handshake messages, DTLS 1.2 and 1.3, @@ -1035,8 +1044,8 @@ static int ClientHelloSanityCheck(WolfSSL_CH* ch, byte isTls13) return 0; } -int DoClientHelloStateless(WOLFSSL* ssl, const byte* input, word32 helloSz, - byte isFirstCHFrag, byte* tls13) +static int _DoClientHelloStateless(WOLFSSL* ssl, const byte* input, + word32 helloSz, byte isFirstCHFrag) { int ret; WolfSSL_CH ch; @@ -1052,9 +1061,6 @@ int DoClientHelloStateless(WOLFSSL* ssl, const byte* input, word32 helloSz, return BAD_STATE_E; #endif } - if (tls13 != NULL) - *tls13 = 0; - XMEMSET(&ch, 0, sizeof(ch)); ssl->options.dtlsStateful = 0; @@ -1067,8 +1073,6 @@ int DoClientHelloStateless(WOLFSSL* ssl, const byte* input, word32 helloSz, ret = TlsCheckSupportedVersion(ssl, &ch, &isTls13); if (ret != 0) return ret; - if (tls13 != NULL) - *tls13 = isTls13; if (isTls13) { int tlsxFound; ret = FindExtByType(&ch.cookieExt, TLSX_COOKIE, ch.extension, @@ -1159,6 +1163,43 @@ int DoClientHelloStateless(WOLFSSL* ssl, const byte* input, word32 helloSz, return ret; } + +/* input holds the first fragSz bytes of a msgSz byte ClientHello. They are + * equal when input is the whole message. */ +int DoClientHelloStateless(WOLFSSL* ssl, const byte* input, word32 fragSz, + word32 msgSz) +{ + byte isFrag = fragSz < msgSz; + int ret; + + if (fragSz > msgSz) + return BAD_FUNC_ARG; + ret = _DoClientHelloStateless(ssl, input, fragSz, isFrag); +#ifdef WOLFSSL_DTLS_CH_FRAG + /* The verified peer's entry leaves the pool: kept only as the rest of the + * ClientHello whose first fragment verified. Other peers' entries stay. */ + if (ssl->options.dtlsStateful && ssl->buffers.dtlsCtx.peer.sz > 0) { + DtlsMsg* rest = NULL; + + if (ret == 0 && isFrag) { + rest = DtlsChPoolTakeRest(&ssl->ctx->dtlsChPool, ssl->ctx->heap, + &ssl->buffers.dtlsCtx.peer, + (word16)w64GetLow32(ssl->keys.curEpoch64), + ssl->keys.dtls_peer_handshake_number, fragSz, msgSz, + (word32)ssl->dtls_timeout_max, ssl->heap); + } + else { + /* What is held is a copy of the verified message or of an older + * attempt, and this object no longer reads the pool */ + DtlsChPoolDrop(&ssl->ctx->dtlsChPool, ssl->ctx->heap, + &ssl->buffers.dtlsCtx.peer, (word32)ssl->dtls_timeout_max); + } + if (rest != NULL) + DtlsMsgRxListInsert(ssl, rest); + } +#endif + return ret; +} #endif /* !defined(NO_WOLFSSL_SERVER) */ #if defined(WOLFSSL_DTLS_CID) diff --git a/src/dtls13.c b/src/dtls13.c index f76635461c6..4dd8b365da2 100644 --- a/src/dtls13.c +++ b/src/dtls13.c @@ -427,8 +427,8 @@ int Dtls13ProcessBufferedMessages(WOLFSSL* ssl) DtlsMsgListPurge(ssl); while ((msg = DtlsMsgListNextReady(ssl)) != NULL) { - /* Processing a message may empty the list, so the entry is out of it - * meanwhile */ + /* Processing a ClientHello that turns the server stateful empties + * the list, so the entry is out of it meanwhile */ DtlsMsgRxListDetach(ssl, msg); ret = Dtls13ProcessBufferedMsg(ssl, msg, &processed); if (processed) { @@ -453,6 +453,18 @@ static int Dtls13NextMessageComplete(WOLFSSL* ssl) return DtlsMsgListNextReady(ssl) != NULL; } +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) +/* A plaintext ClientHello a server that reassembles them receives before a + * cookie verifies. Its fragments are held in the context's pool, keyed on the + * sender's address. */ +static int Dtls13ChBeforeCookie(WOLFSSL* ssl, enum HandShakeType type) +{ + return ssl->options.side == WOLFSSL_SERVER_END && + ssl->options.dtls13ChFrag && !ssl->options.dtlsStateful && + type == client_hello && !IsEncryptionOn(ssl, 0); +} +#endif /* WOLFSSL_DTLS_CH_FRAG && !NO_WOLFSSL_SERVER */ + static WC_INLINE int FragIsInOutputBuffer(WOLFSSL* ssl, const byte* frag) { const byte* OutputBuffer = ssl->buffers.outputBuffer.buffer; @@ -1995,13 +2007,20 @@ static int _Dtls13HandshakeRecv(WOLFSSL* ssl, byte* input, word32 size, if (!isComplete && !Dtls13AcceptFragmented(ssl, (enum HandShakeType)handshakeType)) { #if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) - byte tls13 = 0; + int unverifiedCh = Dtls13ChBeforeCookie(ssl, + (enum HandShakeType)handshakeType); + /* The cookie check's verdict on a first fragment: COOKIE_ERROR with no + * cookie, BUFFER_ERROR when it ends before the fields checked. */ + int chRet = WC_NO_ERR_TRACE(COOKIE_ERROR); + + if (unverifiedCh && isFirst) { + chRet = DoClientHelloStateless(ssl, input + idx, fragLength, + messageLength); + } /* check if the first CH fragment contains a valid cookie */ - if (ssl->options.dtls13ChFrag && !ssl->options.dtlsStateful && - isFirst && handshakeType == client_hello && - DoClientHelloStateless(ssl, input + idx, fragLength, 1, &tls13) - == 0 && tls13) { - /* We can save this message and continue as stateful. */ + if (chRet == 0 && ssl->options.dtlsStateful) { + /* We can save this message and continue as stateful, a DTLS 1.2 + * ClientHello included. */ if (ssl->chGoodCb != NULL) { int cbret = ssl->chGoodCb(ssl, ssl->chGoodCtx); if (cbret < 0) { @@ -2012,6 +2031,36 @@ static int _Dtls13HandshakeRecv(WOLFSSL* ssl, byte* input, word32 size, } WOLFSSL_MSG("ClientHello fragment verified"); } + /* A cookie that failed is answered as a whole ClientHello's is, by + * DoTls13HandShakeMsgType(), and nothing of this peer is held */ + else if (unverifiedCh && !ssl->options.dtlsStateful && + chRet == WC_NO_ERR_TRACE(INVALID_PARAMETER)) { + if (ssl->buffers.dtlsCtx.peer.sz > 0) { + DtlsChPoolDrop(&ssl->ctx->dtlsChPool, ssl->ctx->heap, + &ssl->buffers.dtlsCtx.peer, (word32)ssl->dtls_timeout_max); + } + if (ssl->alert_history.last_tx.level != alert_fatal) { + DtlsSetSeqNumForReply(ssl); + if (SendAlert(ssl, alert_fatal, illegal_parameter) == + WC_NO_ERR_TRACE(SOCKET_ERROR_E)) { + return SOCKET_ERROR_E; + } + } + DtlsResetState(ssl); + *processedSize = idx + fragLength; + return 0; + } + /* Only a fragment the check could not judge is held, by a server + * still stateless, for a peer with an address */ + else if (unverifiedCh && !ssl->options.dtlsStateful && + (chRet == WC_NO_ERR_TRACE(COOKIE_ERROR) || + chRet == WC_NO_ERR_TRACE(BUFFER_ERROR)) && + ssl->options.dtls13ReassembleChBeforeCookie && + ssl->buffers.dtlsCtx.peer.sz > 0) { + /* No cookie to verify yet. Hold the fragments, within a bound, and + * run the cookie check on the complete message. */ + WOLFSSL_MSG("Buffering fragment of an unverified ClientHello"); + } else #endif { @@ -2024,6 +2073,32 @@ static int _Dtls13HandshakeRecv(WOLFSSL* ssl, byte* input, word32 size, } } +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + /* A fragment of a ClientHello whose cookie has not verified goes to the + * context's pool. The message it completes is drained like any other. */ + if (!isComplete && + Dtls13ChBeforeCookie(ssl, (enum HandShakeType)handshakeType)) { + DtlsMsg* done = NULL; + + if (ssl->buffers.dtlsCtx.peer.sz > 0) { + (void)DtlsChPoolStore(&ssl->ctx->dtlsChPool, ssl->ctx->heap, + &ssl->buffers.dtlsCtx.peer, + (word16)w64GetLow32(ssl->keys.curEpoch64), + ssl->keys.dtls_peer_handshake_number, + input + DTLS_HANDSHAKE_HEADER_SZ, messageLength, handshakeType, + fragOff, fragLength, (word32)ssl->dtls_timeout_max, ssl->heap, + &done); + } + *processedSize = idx + fragLength; + if (done == NULL) + return 0; + DtlsMsgRxListInsert(ssl, done); + if (Dtls13NextMessageComplete(ssl)) + return Dtls13ProcessBufferedMessages(ssl); + return 0; + } +#endif + usingAsyncCrypto = ssl->devId != INVALID_DEVID; /* store the message if any of the following: (a) incomplete message, (b) @@ -3926,18 +4001,59 @@ int Dtls13CheckAEADFailLimit(WOLFSSL* ssl) #ifdef WOLFSSL_DTLS_CH_FRAG int wolfSSL_dtls13_allow_ch_frag(WOLFSSL *ssl, int enabled) { - if ((ssl == NULL) || (ssl->options.side == WOLFSSL_CLIENT_END)) { + if (ssl == NULL) + return BAD_FUNC_ARG; + if (ssl->options.side == WOLFSSL_CLIENT_END) { return WOLFSSL_FAILURE; } ssl->options.dtls13ChFrag = !!enabled; return WOLFSSL_SUCCESS; } + +int wolfSSL_dtls13_reassemble_ch_before_cookie(WOLFSSL* ssl, int enabled) +{ + if (ssl == NULL) + return BAD_FUNC_ARG; + if (ssl->options.side == WOLFSSL_CLIENT_END) + return WOLFSSL_FAILURE; + ssl->options.dtls13ReassembleChBeforeCookie = !!enabled; + return WOLFSSL_SUCCESS; +} + +int wolfSSL_CTX_dtls13_reassemble_ch_before_cookie(WOLFSSL_CTX* ctx, + int enabled) +{ + if (ctx == NULL) + return BAD_FUNC_ARG; + if (ctx->method->side == WOLFSSL_CLIENT_END) + return WOLFSSL_FAILURE; + ctx->dtls13ReassembleChBeforeCookie = !!enabled; +#ifndef NO_WOLFSSL_SERVER + if (!enabled) + DtlsChPoolClear(&ctx->dtlsChPool, ctx->heap); +#endif + return WOLFSSL_SUCCESS; +} + +int wolfSSL_CTX_dtls13_clear_ch_pool(WOLFSSL_CTX* ctx) +{ + if (ctx == NULL) + return BAD_FUNC_ARG; + if (ctx->method->side == WOLFSSL_CLIENT_END) + return WOLFSSL_FAILURE; +#ifndef NO_WOLFSSL_SERVER + DtlsChPoolClear(&ctx->dtlsChPool, ctx->heap); +#endif + return WOLFSSL_SUCCESS; +} #endif #ifdef WOLFSSL_DTLS13_NO_HRR_ON_RESUME int wolfSSL_dtls13_no_hrr_on_resume(WOLFSSL *ssl, int enabled) { - if ((ssl == NULL) || (ssl->options.side == WOLFSSL_CLIENT_END)) { + if (ssl == NULL) + return BAD_FUNC_ARG; + if (ssl->options.side == WOLFSSL_CLIENT_END) { return WOLFSSL_FAILURE; } ssl->options.dtls13NoHrrOnResume = !!enabled; diff --git a/src/internal.c b/src/internal.c index be522a9ad50..1e5257bfc42 100644 --- a/src/internal.c +++ b/src/internal.c @@ -3189,6 +3189,17 @@ int InitSSL_Ctx(WOLFSSL_CTX* ctx, WOLFSSL_METHOD* method, void* heap) (void)ret; #endif +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + /* Only a DTLS context's servers use the pool, so only it pays its locks */ + if (method->version.major == DTLS_MAJOR) { + if (DtlsChPoolInit(&ctx->dtlsChPool) != 0) { + WOLFSSL_MSG("Bad mutex init"); + WOLFSSL_ERROR_VERBOSE(BAD_MUTEX_E); + return BAD_MUTEX_E; + } + } +#endif + #ifndef NO_CERTS ctx->privateKeyDevId = INVALID_DEVID; #ifdef WOLFSSL_DUAL_ALG_CERTS @@ -3336,6 +3347,11 @@ int InitSSL_Ctx(WOLFSSL_CTX* ctx, WOLFSSL_METHOD* method, void* heap) #elif defined(WOLFSSL_DTLS_MTU) ctx->dtlsMtuSz = MAX_MTU; #endif + #if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_DTLS_CH_FRAG) + /* Serve clients whose ClientHello does not fit one datagram. Bounded; + * see wolfSSL_CTX_dtls13_reassemble_ch_before_cookie(). */ + ctx->dtls13ReassembleChBeforeCookie = 1; + #endif #endif #ifndef NO_CERTS @@ -3713,6 +3729,10 @@ void SSL_CtxResourceFree(WOLFSSL_CTX* ctx) #if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) wolfSSL_local_FreeEchConfigs(ctx->echConfigs, ctx->heap); ctx->echConfigs = NULL; +#endif +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + /* No object holds a reference any more, so none reaches the pool */ + DtlsChPoolFree(&ctx->dtlsChPool, ctx->heap); #endif (void)heapAtCTXInit; } @@ -9229,18 +9249,17 @@ static int InitSSL_DtlsServer(WOLFSSL* ssl) return ret; } } - #if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_DTLS_CH_FRAG) && \ - defined(WOLFSSL_HAVE_MLKEM) + #if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_DTLS_CH_FRAG) if (IsAtLeastTLSv1_3(ssl->version)) { - /* Allow large PQC ClientHellos to fragment independently of the - * cookie policy, including the first CH when cookies are off. */ + /* Reassemble a fragmented ClientHello whatever the cookie policy: a + * post-quantum key share or a small MTU fragments the first too. */ ret = wolfSSL_dtls13_allow_ch_frag(ssl, 1); if (ret != WOLFSSL_SUCCESS) { WOLFSSL_MSG("DTLS1.3 CH frag error"); return ret; } } - #endif /* WOLFSSL_DTLS13 && WOLFSSL_DTLS_CH_FRAG && WOLFSSL_HAVE_MLKEM */ + #endif /* WOLFSSL_DTLS13 && WOLFSSL_DTLS_CH_FRAG */ } return 0; @@ -9406,6 +9425,10 @@ int InitSSL(WOLFSSL* ssl, WOLFSSL_CTX* ctx, int writeDup) #if defined(WOLFSSL_SCTP) || defined(WOLFSSL_DTLS_MTU) ssl->dtlsMtuSz = ctx->dtlsMtuSz; #endif + #if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_DTLS_CH_FRAG) + ssl->options.dtls13ReassembleChBeforeCookie = + ctx->dtls13ReassembleChBeforeCookie; + #endif ssl->dtls_timeout_init = DTLS_TIMEOUT_INIT; ssl->dtls_timeout_max = DTLS_TIMEOUT_MAX; ssl->dtls_timeout = ssl->dtls_timeout_init; @@ -11451,6 +11474,9 @@ void DtlsMsgDelete(DtlsMsg* item, void* heap) item->fragBucketList = next; } XFREE(item->raw, heap, DYNAMIC_TYPE_DTLS_FRAG); +#ifdef WOLFSSL_DTLS_CH_FRAG + XFREE(item->peer.sa, heap, DYNAMIC_TYPE_SOCKADDR); +#endif XFREE(item, heap, DYNAMIC_TYPE_DTLS_MSG); } } @@ -11475,6 +11501,9 @@ static size_t DtlsMsgRetainedBytes(const DtlsMsg* msg) size_t bytes = sizeof(DtlsMsg) + msg->bytesReceived + ((size_t)msg->fragBucketListCount + (msg->raw != NULL ? 1 : 0)) * sizeof(DtlsFragBucket); +#ifdef WOLFSSL_DTLS_CH_FRAG + bytes += msg->peer.bufSz; +#endif return bytes; } @@ -11944,12 +11973,20 @@ static size_t DtlsMsgProjectedBytes(const DtlsMsg* msg, word32 offset, return bytes + sizeof(DtlsFragBucket) + (end - offset); } +/* The retained bytes a receive list, or the context's ClientHello pool when + * unverified, may reach when admitting an entry. */ +static size_t DtlsMsgRxLimit(int unverified) +{ + return unverified ? WOLFSSL_DTLS_RX_UNVERIFIED_MAX_BYTES : + WOLFSSL_DTLS_RX_MAX_BYTES; +} + /* Whether the receive list can grow to hold current, or a new entry, at * projected retained bytes. */ static int DtlsMsgRxAdmit(WOLFSSL* ssl, DtlsMsg* current, size_t projected) { size_t old = current == NULL ? 0 : DtlsMsgRetainedBytes(current); - size_t limit = WOLFSSL_DTLS_RX_MAX_BYTES; + size_t limit = DtlsMsgRxLimit(0); if (projected > limit || ssl->dtls_rx_msg_list_bytes - old > limit - projected) @@ -12028,6 +12065,660 @@ int DtlsMsgStore(WOLFSSL* ssl, word16 epoch, word32 seq, const byte* data, return ret; } +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) +/* Whether msg is keyed on peer's bytes */ +static int DtlsMsgPeerEqual(const DtlsMsg* msg, const WOLFSSL_SOCKADDR* peer) +{ + return peer->sz > 0 && msg->peer.sz == peer->sz && + XMEMCMP(msg->peer.sa, peer->sa, peer->sz) == 0; +} + +/* Key msg on a copy of peer's bytes */ +static int DtlsMsgSetPeer(DtlsMsg* msg, const WOLFSSL_SOCKADDR* peer, + void* heap) +{ + if (wolfssl_local_SockAddrSet(&msg->peer, peer->sa, peer->sz, heap) != + WOLFSSL_SUCCESS) + return MEMORY_E; + return 0; +} + +/* Whether the fragment of fragSz bytes at offset agrees with every byte msg + * holds in its range. */ +static int DtlsMsgFragAgrees(const DtlsMsg* msg, word32 offset, + const byte* data, word32 fragSz) +{ + const DtlsFragBucket* b; + word32 end = offset + fragSz; + + for (b = msg->fragBucketList; b != NULL && b->m.m.offset < end; + b = b->m.m.next) { + word32 lo = max(offset, b->m.m.offset); + word32 hi = min(end, b->m.m.offset + b->m.m.sz); + if (lo < hi && XMEMCMP(b->buf + (lo - b->m.m.offset), + data + (lo - offset), hi - lo) != 0) + return 0; + } + return 1; +} + +/* The shards share one budget, pool->reserved: bytes retained plus the growth + * of stores in progress, reserved before they allocate. One lock at a time. */ +static int DtlsChShardLock(DtlsChPoolShard* sh) +{ +#ifndef SINGLE_THREADED + return wc_LockMutex(&sh->lock); +#else + (void)sh; + return 0; +#endif +} + +static void DtlsChShardUnlock(DtlsChPoolShard* sh) +{ + DtlsMsg* head = sh->head; + DtlsMsg* next = head != NULL ? head->next : NULL; + + /* Every change to the list is made under the lock, so publishing here + * keeps the progress of the two oldest entries current for eviction */ + WOLFSSL_ATOMIC_STORE(sh->headProgress, + (WC_ATOMIC_UINT_ARG)(head != NULL ? head->progress : 0U)); + WOLFSSL_ATOMIC_STORE(sh->nextProgress, + (WC_ATOMIC_UINT_ARG)(next != NULL ? next->progress : 0U)); +#ifndef SINGLE_THREADED + wc_UnLockMutex(&sh->lock); +#endif +} + +/* A store counts the bound of its fragment before it evicts for it, so + * reserved may pass the budget by one bound per store evicting at once. A + * bound is below the budget, by the check before it: room for 64. Progress + * wraps as word32. */ +wc_static_assert(WC_MAX_UINT_OF(WC_ATOMIC_UINT_ARG) >= WOLFSSL_MAX_32BIT); +wc_static_assert(WOLFSSL_DTLS_RX_UNVERIFIED_MAX_BYTES <= + WC_MAX_UINT_OF(WC_ATOMIC_UINT_ARG) / 65); + +/* Add n bytes, at most the budget, to reserved. Returns 1, or 0 with the add + * taken back when the sum passes the budget. */ +static int DtlsChPoolAdd(DtlsChPool* pool, size_t n) +{ + if ((size_t)wolfSSL_Atomic_Uint_AddFetch(&pool->reserved, + (WC_ATOMIC_UINT_ARG)n) <= DtlsMsgRxLimit(1)) + return 1; + (void)wolfSSL_Atomic_Uint_SubFetch(&pool->reserved, (WC_ATOMIC_UINT_ARG)n); + return 0; +} + +/* The next progress value, a logical clock of stores. 0 marks an empty + * shard. */ +static word32 DtlsChPoolNextProgress(DtlsChPool* pool) +{ + word32 progress = (word32)(wolfSSL_Atomic_Uint_FetchAdd( + &pool->lastProgress, 1U) + 1U); + + if (progress == 0) { + progress = (word32)(wolfSSL_Atomic_Uint_FetchAdd(&pool->lastProgress, + 1U) + 1U); + } + return progress; +} + +/* Trade the counted bytes reserved for msg, what it retained plus the bound + * of the fragment, for those it retains now. The bound covers any growth, so + * this gives back and never adds. Should it have to add, it fails past the + * budget, with counted given back: the caller drops msg. */ +static int DtlsChPoolSettle(DtlsChPool* pool, DtlsMsg* msg, size_t counted) +{ + size_t actual = DtlsMsgRetainedBytes(msg); + + if (actual <= counted) { + (void)wolfSSL_Atomic_Uint_SubFetch(&pool->reserved, + (WC_ATOMIC_UINT_ARG)(counted - actual)); + return 0; + } + if (actual <= DtlsMsgRxLimit(1) && DtlsChPoolAdd(pool, actual - counted)) + return 0; + (void)wolfSSL_Atomic_Uint_SubFetch(&pool->reserved, + (WC_ATOMIC_UINT_ARG)counted); + return DTLS_MSG_STORE_DROPPED; +} + +/* Whether progress a was given out before b. Progress wraps. */ +static int DtlsChPoolProgressBefore(word32 a, word32 b) +{ + word32 d = (word32)(b - a); + + return d != 0 && d < 0x80000000UL; +} + +/* Take msg out of the list. The counters are the caller's. */ +static void DtlsChPoolDetach(DtlsChPoolShard* sh, DtlsMsg* msg) +{ + DtlsMsg** link = &sh->head; + + while (*link != NULL && *link != msg) + link = &(*link)->next; + if (*link == msg) + *link = msg->next; + msg->next = NULL; +} + +/* Put msg at the tail, the most recently progressed end. */ +static void DtlsChPoolAppend(DtlsChPoolShard* sh, DtlsMsg* msg) +{ + DtlsMsg** link = &sh->head; + + while (*link != NULL) + link = &(*link)->next; + msg->next = NULL; + *link = msg; +} + +static void DtlsChPoolUnlink(DtlsChPool* pool, DtlsChPoolShard* sh, + DtlsMsg* msg) +{ + size_t bytes = DtlsMsgRetainedBytes(msg); + + DtlsChPoolDetach(sh, msg); + sh->bytes -= bytes; + (void)wolfSSL_Atomic_Uint_SubFetch(&pool->reserved, + (WC_ATOMIC_UINT_ARG)bytes); +} + +/* Peer's entry in shard sh. An entry idle for over maxAge seconds, or with + * a hold time ahead of now, is moved to *gone for the caller to free. */ +static DtlsMsg* DtlsChPoolFind(DtlsChPool* pool, DtlsChPoolShard* sh, + const WOLFSSL_SOCKADDR* peer, word32 now, word32 maxAge, + DtlsMsg** gone) +{ + DtlsMsg** link = &sh->head; + DtlsMsg* found = NULL; + size_t freed = 0; + + while (*link != NULL) { + DtlsMsg* msg = *link; + word32 age = now - msg->heldAt; + + if (age > maxAge) { + size_t bytes = DtlsMsgRetainedBytes(msg); + + *link = msg->next; + sh->bytes -= bytes; + freed += bytes; + msg->next = *gone; + *gone = msg; + continue; + } + if (found == NULL && DtlsMsgPeerEqual(msg, peer)) + found = msg; + link = &msg->next; + } + (void)wolfSSL_Atomic_Uint_SubFetch(&pool->reserved, + (WC_ATOMIC_UINT_ARG)freed); + (void)now; + (void)maxAge; + (void)gone; + return found; +} + +/* How often eviction looks again when the oldest entry changed under it */ +#define DTLS_CH_POOL_EVICT_RETRIES 4 + +/* The shard holding the oldest entry, and its progress in *oldest, from the + * published values alone: no lock is taken. Shard skip, when not NULL, is + * judged by the entry after its head, on equal terms with the other heads. + * The values are hints checked under the victim's lock. A shard's values + * only rise, as entries leave or move to the young end, so a racing read can + * only make a shard look older than it is, which the check turns into a + * miss. */ +static DtlsChPoolShard* DtlsChPoolFindOldest(DtlsChPool* pool, + const DtlsChPoolShard* skip, word32* oldest) +{ + DtlsChPoolShard* best = NULL; + word32 s; + int i; + + for (i = 0; i < WOLFSSL_DTLS_CH_POOL_SHARDS; i++) { + DtlsChPoolShard* sh = &pool->shard[i]; + + if (sh == skip) + s = (word32)WOLFSSL_ATOMIC_LOAD(sh->nextProgress); + else + s = (word32)WOLFSSL_ATOMIC_LOAD(sh->headProgress); + if (s == 0) + continue; + if (best == NULL || DtlsChPoolProgressBefore(s, *oldest)) { + best = sh; + *oldest = s; + } + } + return best; +} + +/* Free need bytes, or until reserved is within the budget, by evicting the + * oldest entries of any shard, never keep's. The shard holding keep's entry + * is learned when the scan lands on it: the entry behind it goes then, as + * the oldest of that shard but keep's, and the shard is judged by the entry + * behind its head from then on. The caller holds no shard lock: victims' + * shards are, and two at once deadlock. */ +static int DtlsChPoolEvict(DtlsChPool* pool, void* heap, + const WOLFSSL_SOCKADDR* keep, size_t need) +{ + DtlsChPoolShard* skip = NULL; + size_t freed = 0; + int misses = 0; + + while (freed < need && + (size_t)WOLFSSL_ATOMIC_LOAD(pool->reserved) > DtlsMsgRxLimit(1)) { + DtlsChPoolShard* best; + DtlsMsg* victim; + word32 oldest = 0; + + best = DtlsChPoolFindOldest(pool, skip, &oldest); + if (best == NULL || DtlsChShardLock(best) != 0) + return DTLS_MSG_STORE_DROPPED; + victim = best->head; + if (victim != NULL && DtlsMsgPeerEqual(victim, keep)) { + skip = best; + victim = victim->next; + } + else if (victim != NULL && victim->progress != oldest) + victim = NULL; + if (victim != NULL) { + freed += DtlsMsgRetainedBytes(victim); + DtlsChPoolUnlink(pool, best, victim); + } + DtlsChShardUnlock(best); + if (victim == NULL && ++misses > DTLS_CH_POOL_EVICT_RETRIES) + return DTLS_MSG_STORE_DROPPED; + DtlsMsgDelete(victim, heap); + } + return 0; +} + +/* The shard of a peer: a hash of its address bytes. A sender choosing + * addresses can crowd one shard, but the budget still holds. */ +word32 DtlsChPoolShardIdx(const WOLFSSL_SOCKADDR* peer) +{ +#if WOLFSSL_DTLS_CH_POOL_SHARDS > 1 + int err = 0; + /* 0 when hashing fails, so such peers share shard 0 */ + word32 h = HashObject((const byte*)peer->sa, (word32)peer->sz, &err); + + (void)err; + return h % WOLFSSL_DTLS_CH_POOL_SHARDS; +#else + (void)peer; + return 0; +#endif +} + +int DtlsChPoolInit(DtlsChPool* pool) +{ + int i; + + if (pool == NULL) + return BAD_FUNC_ARG; + XMEMSET(pool, 0, sizeof(*pool)); + wolfSSL_Atomic_Uint_Init(&pool->reserved, 0U); + wolfSSL_Atomic_Uint_Init(&pool->lastProgress, 0U); + for (i = 0; i < WOLFSSL_DTLS_CH_POOL_SHARDS; i++) { + wolfSSL_Atomic_Uint_Init(&pool->shard[i].headProgress, 0U); + wolfSSL_Atomic_Uint_Init(&pool->shard[i].nextProgress, 0U); + } +#ifndef SINGLE_THREADED + for (i = 0; i < WOLFSSL_DTLS_CH_POOL_SHARDS; i++) { + if (wc_InitMutex(&pool->shard[i].lock) != 0) { + while (i-- > 0) + wc_FreeMutex(&pool->shard[i].lock); + return BAD_MUTEX_E; + } + } +#endif + pool->ready = 1; + return 0; +} + +/* Free every entry and the locks, without locking: nothing else may use the + * pool. */ +void DtlsChPoolFree(DtlsChPool* pool, void* heap) +{ + int i; + + if (pool == NULL || !pool->ready) + return; + pool->ready = 0; + for (i = 0; i < WOLFSSL_DTLS_CH_POOL_SHARDS; i++) { + DtlsChPoolShard* sh = &pool->shard[i]; + + DtlsMsgListDelete(sh->head, heap); + sh->head = NULL; + sh->bytes = 0; + WOLFSSL_ATOMIC_STORE(sh->headProgress, 0U); + WOLFSSL_ATOMIC_STORE(sh->nextProgress, 0U); +#ifndef SINGLE_THREADED + wc_FreeMutex(&sh->lock); +#endif + } + WOLFSSL_ATOMIC_STORE(pool->reserved, 0U); +} + +void DtlsChPoolClear(DtlsChPool* pool, void* heap) +{ + int i; + + if (pool == NULL || !pool->ready) + return; + for (i = 0; i < WOLFSSL_DTLS_CH_POOL_SHARDS; i++) { + DtlsChPoolShard* sh = &pool->shard[i]; + DtlsMsg* head; + + if (DtlsChShardLock(sh) != 0) + continue; + head = sh->head; + (void)wolfSSL_Atomic_Uint_SubFetch(&pool->reserved, + (WC_ATOMIC_UINT_ARG)sh->bytes); + sh->head = NULL; + sh->bytes = 0; + DtlsChShardUnlock(sh); + DtlsMsgListDelete(head, heap); + } +} + +/* Hand msg, allocated with from, to an owner that frees it with to: rebuilt + * only when the heaps differ, without its peer key. On failure *msg is NULL. */ +int DtlsMsgAdopt(DtlsMsg** msg, void* from, void* to) +{ + DtlsMsg* m; + DtlsMsg* n; + int ret = 0; + + if (msg == NULL || *msg == NULL) + return BAD_FUNC_ARG; + m = *msg; + XFREE(m->peer.sa, from, DYNAMIC_TYPE_SOCKADDR); + XMEMSET(&m->peer, 0, sizeof(m->peer)); + if (from == to) + return 0; + + n = DtlsMsgNew(m->sz, 0, to); + if (n == NULL) + ret = MEMORY_E; + else if (m->ready) + ret = DtlsMsgSet(n, m->seq, m->epoch, m->fullMsg, m->type, 0, m->sz, + to, m->sz, m->encrypted); + else { + const DtlsFragBucket* b; + for (b = m->fragBucketList; b != NULL && ret == 0; b = b->m.m.next) + ret = DtlsMsgSet(n, m->seq, m->epoch, b->buf, m->type, + b->m.m.offset, b->m.m.sz, to, m->sz, m->encrypted); + } + DtlsMsgDelete(m, from); + if (ret != 0) { + DtlsMsgDelete(n, to); + n = NULL; + ret = MEMORY_E; + } + *msg = n; + return ret; +} + +/* Peer's entry in sh, unless it is of another message or contradicts the + * fragment: then it moves to *gone. Call with sh locked. */ +static DtlsMsg* DtlsChPoolFindMsg(DtlsChPool* pool, DtlsChPoolShard* sh, + const WOLFSSL_SOCKADDR* peer, word16 epoch, word32 seq, + const byte* data, word32 dataSz, byte type, word32 fragOffset, + word32 fragSz, word32 now, word32 maxAge, DtlsMsg** gone) +{ + DtlsMsg* cur = DtlsChPoolFind(pool, sh, peer, now, maxAge, gone); + + if (cur != NULL && (cur->epoch != epoch || cur->seq != seq || + cur->sz != dataSz || cur->type != type || + !DtlsMsgFragAgrees(cur, fragOffset, data, fragSz))) { + DtlsChPoolUnlink(pool, sh, cur); + cur->next = *gone; + *gone = cur; + cur = NULL; + } + return cur; +} + +/* Store a fragment of a ClientHello sent before its cookie verified. One entry + * per peer, the newest. The entry the fragment completes leaves through *done. + * Returns 0 when held, DTLS_MSG_STORE_DROPPED when not, or BAD_FUNC_ARG. + * The most the fragment can add, a new entry with one bucket, is reserved + * with no lock held and evicted for when it passes the budget. Only then is + * the shard locked, once, and the reservation settled to the bytes really + * added. A fragment of what is held evicts for its bound too. */ +int DtlsChPoolStore(DtlsChPool* pool, void* heap, + const WOLFSSL_SOCKADDR* peer, word16 epoch, word32 seq, + const byte* data, word32 dataSz, byte type, word32 fragOffset, + word32 fragSz, word32 maxAge, void* outHeap, DtlsMsg** done) +{ + DtlsChPoolShard* sh; + DtlsMsg* cur = NULL; + DtlsMsg* gone = NULL; + DtlsMsg* detached = NULL; + size_t limit = DtlsMsgRxLimit(1); + size_t overhead; + size_t before; + size_t reserve; /* the bound of the fragment, held in pool->reserved */ + size_t sum; + word32 received; + word32 now; + word32 buckets; + int isNew; + int progressed; + int ret = 0; + + WOLFSSL_ENTER("DtlsChPoolStore"); + if (done != NULL) + *done = NULL; + if (pool == NULL || !pool->ready || peer == NULL || done == NULL || + data == NULL || fragOffset > dataSz || + fragSz > dataSz - fragOffset) + return BAD_FUNC_ARG; + /* No address is no key, and a whole message is processed at once */ + if (peer->sz == 0 || (fragOffset == 0 && fragSz == dataSz)) + return BAD_FUNC_ARG; + /* A ClientHello that can never complete is dropped before anything is + * evicted: its last fragment is reserved as a new entry on top of the + * entry holding the rest, so the metadata is counted twice. */ + if (peer->sz > limit) + return DTLS_MSG_STORE_DROPPED; + overhead = sizeof(DtlsMsg) + sizeof(DtlsFragBucket) + peer->sz; + if (overhead > limit / 2 || (size_t)dataSz > limit - 2 * overhead) + return DTLS_MSG_STORE_DROPPED; + sh = &pool->shard[DtlsChPoolShardIdx(peer)]; + + /* Reserve the bound before evicting for it, so the room evicting makes + * is this store's. Evicting locks any shard, so none is held. */ + reserve = overhead + fragSz; + sum = (size_t)wolfSSL_Atomic_Uint_AddFetch(&pool->reserved, + (WC_ATOMIC_UINT_ARG)reserve); + if (sum > limit) { + size_t need = sum - limit; + + /* Over by more than this reservation: other threads reserved too. + * Each evicts for its own, so this one evicts no more than its own. */ + if (need > reserve) + need = reserve; + if (DtlsChPoolEvict(pool, heap, peer, need) != 0) { + (void)wolfSSL_Atomic_Uint_SubFetch(&pool->reserved, + (WC_ATOMIC_UINT_ARG)reserve); + return DTLS_MSG_STORE_DROPPED; + } + } + + /* The budget now has room for this fragment */ + if (DtlsChShardLock(sh) != 0) { + (void)wolfSSL_Atomic_Uint_SubFetch(&pool->reserved, + (WC_ATOMIC_UINT_ARG)reserve); + return DTLS_MSG_STORE_DROPPED; + } + now = LowResTimer(); + /* Peer's entry, unless expired or contradicted: then it is replaced */ + cur = DtlsChPoolFindMsg(pool, sh, peer, epoch, seq, data, dataSz, type, + fragOffset, fragSz, now, maxAge, &gone); + isNew = cur == NULL; + before = isNew ? 0 : DtlsMsgRetainedBytes(cur); + /* As for the receive list, one more gap than DTLS_FRAG_POOL_SZ buckets + * is refused, since every store walks them */ + buckets = 1; + if (!isNew) + (void)DtlsMsgProjectedBytes(cur, fragOffset, fragSz, &buckets); + if (buckets > DTLS_FRAG_POOL_SZ) { + (void)wolfSSL_Atomic_Uint_SubFetch(&pool->reserved, + (WC_ATOMIC_UINT_ARG)reserve); + ret = DTLS_MSG_STORE_DROPPED; + } + /* Store the fragment, in a new entry when none is held */ + else if (isNew) { + cur = DtlsMsgNew(dataSz, 0, heap); + if (cur == NULL || DtlsMsgSetPeer(cur, peer, heap) != 0) { + if (cur != NULL) { + cur->next = gone; + gone = cur; + cur = NULL; + } + (void)wolfSSL_Atomic_Uint_SubFetch(&pool->reserved, + (WC_ATOMIC_UINT_ARG)reserve); + ret = DTLS_MSG_STORE_DROPPED; + } + } + if (ret == 0) { + received = cur->bytesReceived; + /* A pooled ClientHello is always plaintext */ + ret = DtlsMsgSet(cur, seq, epoch, data, type, fragOffset, fragSz, heap, + dataSz, 0); + /* New bytes refresh recency, duplicates do not */ + progressed = ret == 0 && (isNew || cur->bytesReceived > received); + if (ret != 0 && isNew) { + cur->next = gone; + gone = cur; + cur = NULL; + (void)wolfSSL_Atomic_Uint_SubFetch(&pool->reserved, + (WC_ATOMIC_UINT_ARG)reserve); + } + /* Settle the bound against the bytes really held: what the fragment + * did not add goes back */ + else if (DtlsChPoolSettle(pool, cur, before + reserve) != 0) { + if (!isNew) { + DtlsChPoolDetach(sh, cur); + sh->bytes -= before; + } + cur->next = gone; + gone = cur; + cur = NULL; + ret = DTLS_MSG_STORE_DROPPED; + } + /* Count the entry in its shard. New bytes move it to the recent end + * and give it the newest progress value. */ + else { + sh->bytes = sh->bytes - before + DtlsMsgRetainedBytes(cur); + if (progressed) { + if (!isNew) + DtlsChPoolDetach(sh, cur); + DtlsChPoolAppend(sh, cur); + cur->heldAt = now; + cur->progress = DtlsChPoolNextProgress(pool); + } + } + } + /* A completed ClientHello leaves the pool, for the caller's heap */ + if (ret == 0 && cur->ready) { + DtlsChPoolUnlink(pool, sh, cur); + detached = cur; + } + DtlsChShardUnlock(sh); + DtlsMsgListDelete(gone, heap); + + if (ret != 0) + return DTLS_MSG_STORE_DROPPED; + if (detached != NULL) { + if (DtlsMsgAdopt(&detached, heap, outHeap) != 0) + return DTLS_MSG_STORE_DROPPED; + *done = detached; + } + return 0; +} + +/* Take peer's entry out of the pool, unless it has aged out. Only peer's + * shard is locked. */ +static DtlsMsg* DtlsChPoolTake(DtlsChPool* pool, void* heap, + const WOLFSSL_SOCKADDR* peer, word32 maxAge) +{ + DtlsChPoolShard* sh; + DtlsMsg* held; + DtlsMsg* gone = NULL; + + if (pool == NULL || !pool->ready || peer == NULL || peer->sz == 0) + return NULL; + sh = &pool->shard[DtlsChPoolShardIdx(peer)]; + if (DtlsChShardLock(sh) != 0) + return NULL; + held = DtlsChPoolFind(pool, sh, peer, LowResTimer(), maxAge, &gone); + if (held != NULL) + DtlsChPoolUnlink(pool, sh, held); + DtlsChShardUnlock(sh); + DtlsMsgListDelete(gone, heap); + return held; +} + +/* Free peer's entry. */ +void DtlsChPoolDrop(DtlsChPool* pool, void* heap, const WOLFSSL_SOCKADDR* peer, + word32 maxAge) +{ + DtlsMsg* held = DtlsChPoolTake(pool, heap, peer, maxAge); + + if (held != NULL) + DtlsMsgDelete(held, heap); +} + +/* Take peer's entry out of the pool. It is returned, allocated with outHeap, + * only as the rest of the ClientHello of epoch and seq whose first fragSz of + * msgSz bytes verified: what it held of those bytes is cut away, since only + * the verified copy may be used. Nothing past them, or another message: + * freed. */ +DtlsMsg* DtlsChPoolTakeRest(DtlsChPool* pool, void* heap, + const WOLFSSL_SOCKADDR* peer, word16 epoch, word32 seq, word32 fragSz, + word32 msgSz, word32 maxAge, void* outHeap) +{ + DtlsMsg* held = DtlsChPoolTake(pool, heap, peer, maxAge); + DtlsFragBucket* b; + + if (held == NULL) + return NULL; + if (held->epoch != epoch || held->seq != seq || + held->type != client_hello || held->sz != msgSz) { + DtlsMsgDelete(held, heap); + return NULL; + } + /* Buckets are sorted and disjoint: drop those the fragment covers, then + * cut the head of one it reaches into */ + while ((b = held->fragBucketList) != NULL && + b->m.m.offset + b->m.m.sz <= fragSz) { + held->fragBucketList = b->m.m.next; + held->bytesReceived -= b->m.m.sz; + held->fragBucketListCount--; + DtlsMsgDestroyFragBucket(b, heap); + } + if (b != NULL && b->m.m.offset < fragSz) { + word32 cut = fragSz - b->m.m.offset; + + XMEMMOVE(b->buf, b->buf + cut, b->m.m.sz - cut); + b->m.m.offset = fragSz; + b->m.m.sz -= cut; + held->bytesReceived -= cut; + } + if (b == NULL || DtlsMsgAdopt(&held, heap, outHeap) != 0) { + DtlsMsgDelete(held, heap); + return NULL; + } + return held; +} +#endif /* WOLFSSL_DTLS_CH_FRAG && !NO_WOLFSSL_SERVER */ + /* Whether ret, from DtlsMsgStore(), ends the connection: only running out of * memory, or an authenticated peer exceeding the receive budget. A fragment * not held is dropped and not acknowledged, so its sender resends it. */ @@ -44353,8 +45044,8 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl) if (IsDtlsNotSctpMode(ssl) && IsDtlsNotSrtpMode(ssl) && !IsSCR(ssl) && !ssl->options.dtlsStateful) { DtlsSetSeqNumForReply(ssl); - ret = DoClientHelloStateless(ssl, input + *inOutIdx, helloSz, 0, - NULL); + ret = DoClientHelloStateless(ssl, input + *inOutIdx, helloSz, + helloSz); if (ret != 0 || !ssl->options.dtlsStateful) { int alertType = TranslateErrorToAlert(ret); if (alertType != invalid_alert) { diff --git a/src/tls13.c b/src/tls13.c index 869d0665b8c..cd222168f39 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -8261,7 +8261,8 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, if (IsDtlsNotSctpMode(ssl) && ssl->options.sendCookie && !ssl->options.dtlsStateful) { DtlsSetSeqNumForReply(ssl); - ret = DoClientHelloStateless(ssl, input + *inOutIdx, helloSz, 0, NULL); + ret = DoClientHelloStateless(ssl, input + *inOutIdx, helloSz, + helloSz); if (ret != 0 || !ssl->options.dtlsStateful) { *inOutIdx += helloSz; goto exit_dch; diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index bd8c520256b..3dd2fa694af 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -3665,6 +3665,116 @@ int test_dtls_memio_wolfio_invalid_peer(void) return EXPECT_RESULT(); } +/* Once stateful, the default receive callback pins the peer address. A + * datagram from another address is dropped, not a fatal error or processed. */ +int test_dtls_memio_wolfio_foreign_peer(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS) && \ + !defined(WOLFSSL_PEER_ADDRESS_CHANGES) + size_t i; + struct { + method_provider client_meth; + method_provider server_meth; + } params[] = { +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_DTLS13) + { wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method }, +#endif +#if !defined(WOLFSSL_NO_TLS12) && defined(WOLFSSL_DTLS) + { wolfDTLSv1_2_client_method, wolfDTLSv1_2_server_method }, +#endif + }; + XMEMSET(&test_memio_wolfio_ctx, 0, sizeof(test_memio_wolfio_ctx)); + for (i = 0; i < XELEM_CNT(params) && !EXPECT_FAIL(); i++) { + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + SOCKADDR_IN* peer = (SOCKADDR_IN*)&test_memio_wolfio_ctx.peer_addr; + const char foreign[] = "not from the pinned peer"; + byte* flight = NULL; + int flightSz = 0; + int flightMsgs = 0; + int flightSizes[TEST_MEMIO_MAX_MSGS]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + params[i].client_meth, params[i].server_meth), 0); + + test_memio_wolfio_ctx.test_ctx = &test_ctx; + test_memio_wolfio_ctx.ssl_s = ssl_s; + /* Large number to error out if any syscalls are called with it */ + test_memio_wolfio_ctx.fd = 6000; + XMEMSET(&test_memio_wolfio_ctx.peer_addr, 0, + sizeof(test_memio_wolfio_ctx.peer_addr)); + test_memio_wolfio_ctx.peer_addr.ss_family = AF_INET; + peer->sin_port = 1; + + wolfSSL_dtls_set_using_nonblock(ssl_s, 1); + wolfSSL_SetRecvFrom(ssl_s, test_memio_wolfio_recvfrom); + wolfSSL_SetSendTo(ssl_s, test_memio_wolfio_sendto); + /* Restore default functions */ + wolfSSL_SSLSetIORecv(ssl_s, EmbedReceiveFrom); + wolfSSL_SSLSetIOSend(ssl_s, EmbedSendTo); + ExpectIntEQ(wolfSSL_set_fd(ssl_s, test_memio_wolfio_ctx.fd), + WOLFSSL_SUCCESS); + + /* First ClientHello, cookie request, second ClientHello: the server + * verifies the cookie, turns stateful and pins the peer address. */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + + /* A datagram from another address is dropped */ + peer->sin_port = 2; + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, foreign, + sizeof(foreign)), 0); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_ctx.s_len, 0); + + /* So is the client's genuine final flight from another address */ + (void)wolfSSL_connect(ssl_c); + ExpectIntGT(flightSz = test_ctx.s_len, 0); + ExpectNotNull(flight = (byte*)XMALLOC((size_t)flightSz, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + if (EXPECT_SUCCESS()) { + XMEMCPY(flight, test_ctx.s_buff, (size_t)flightSz); + flightMsgs = test_ctx.s_msg_count; + XMEMCPY(flightSizes, test_ctx.s_msg_sizes, sizeof(flightSizes)); + } + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectFalse(wolfSSL_is_init_finished(ssl_s)); + ExpectIntEQ(test_ctx.s_len, 0); + + /* The pinned peer's flight completes the handshake */ + peer->sin_port = 1; + if (EXPECT_SUCCESS()) { + XMEMCPY(test_ctx.s_buff, flight, (size_t)flightSz); + test_ctx.s_len = flightSz; + test_ctx.s_msg_count = flightMsgs; + test_ctx.s_msg_pos = 0; + XMEMCPY(test_ctx.s_msg_sizes, flightSizes, sizeof(flightSizes)); + } + XFREE(flight, NULL, DYNAMIC_TYPE_TMP_BUFFER); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_s); + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_s); + wolfSSL_CTX_free(ctx_c); + } +#endif + return EXPECT_RESULT(); +} + int test_dtls_mtu_split_messages(void) { #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ @@ -9515,6 +9625,9 @@ int test_dtls12_frag_ch_no_cookie(void) ExpectIntEQ(test_ctx.c_len, 0); ExpectIntEQ(calls, 0); ExpectNull(ssl_s->dtls_rx_msg_list); +#ifdef WOLFSSL_DTLS_CH_FRAG + ExpectIntEQ(WOLFSSL_ATOMIC_LOAD(ctx_s->dtlsChPool.reserved), 0); +#endif rec += recLen; len -= recLen; records++; diff --git a/tests/api/test_dtls.h b/tests/api/test_dtls.h index 53ee30317fc..8e538c7f1a4 100644 --- a/tests/api/test_dtls.h +++ b/tests/api/test_dtls.h @@ -63,6 +63,7 @@ int test_dtls_certreq_order(void); int test_dtls_memio_wolfio(void); int test_dtls_memio_wolfio_stateless(void); int test_dtls_memio_wolfio_invalid_peer(void); +int test_dtls_memio_wolfio_foreign_peer(void); int test_dtls_mtu_fragment_headroom(void); int test_dtls_mtu_split_messages(void); int test_dtls_set_session_min_downgrade(void); @@ -263,6 +264,7 @@ int test_dtls_no_cookie_ch_pause(WOLFSSL* ssl, void* ctx); TEST_DECL_GROUP("dtls", test_dtls_mtu_split_messages), \ TEST_DECL_GROUP("dtls", test_dtls_memio_wolfio_stateless), \ TEST_DECL_GROUP("dtls", test_dtls_memio_wolfio_invalid_peer), \ + TEST_DECL_GROUP("dtls", test_dtls_memio_wolfio_foreign_peer), \ TEST_DECL_GROUP("dtls", test_dtls_set_session_min_downgrade), \ TEST_DECL_GROUP("dtls", test_wolfSSL_dtls_export), \ TEST_DECL_GROUP("dtls", test_wolfSSL_dtls_import_dh_key_sz), \ diff --git a/tests/api/test_dtls13.c b/tests/api/test_dtls13.c index f7099d3a187..bc89fe4728c 100644 --- a/tests/api/test_dtls13.c +++ b/tests/api/test_dtls13.c @@ -345,11 +345,33 @@ static int test_dtls13_reply_is_server_hello( #endif #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ - && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_DTLS_MTU) \ - && defined(WOLFSSL_AES_256) + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) \ + && ((defined(WOLFSSL_DTLS_MTU) && defined(WOLFSSL_AES_256)) || \ + (defined(HAVE_ECC) && !defined(NO_ECC256))) +/* Run the server until it has consumed every datagram queued for it. Whether + * a reply was produced is for the caller to check in test_ctx->c_len. */ +static int test_dtls13_server_read_all(struct test_memio_ctx* test_ctx, + WOLFSSL* ssl_s) +{ + EXPECT_DECLS; + /* wolfSSL_accept(), not wolfSSL_negotiate(): the generic accept path + * decides whether the server starts out stateful */ + while (test_ctx->s_len > 0 && EXPECT_SUCCESS()) { + int s_len = test_ctx->s_len; + ExpectIntEQ(wolfSSL_accept(ssl_s), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_s, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + /* Fail if we didn't advance the buffer to avoid infinite loops */ + ExpectIntLT(test_ctx->s_len, s_len); + } + /* Expect all fragments read */ + ExpectIntEQ(test_ctx->s_len, 0); + return EXPECT_RESULT(); +} + +#ifdef WOLFSSL_SEND_HRR_COOKIE /* Deliver test_dtls13_four_frag_ch to the server one record per datagram and - * run the server until it has consumed every datagram. Whether a reply was - * produced is for the caller to check in test_ctx->c_len. */ + * run the server until it has consumed every datagram. */ static int test_dtls13_inject_four_frag_ch(struct test_memio_ctx* test_ctx, WOLFSSL* ssl_s) { @@ -374,19 +396,319 @@ static int test_dtls13_inject_four_frag_ch(struct test_memio_ctx* test_ctx, } ExpectIntEQ(records, 4); ExpectIntEQ(test_ctx->s_len, sizeof(test_dtls13_four_frag_ch)); - /* wolfSSL_accept(), not wolfSSL_negotiate(): applications enter through - * the generic accept path and it decides whether the server starts out - * stateful. */ - while (test_ctx->s_len > 0 && EXPECT_SUCCESS()) { - int s_len = test_ctx->s_len; - ExpectIntEQ(wolfSSL_accept(ssl_s), WOLFSSL_FATAL_ERROR); - ExpectIntEQ(wolfSSL_get_error(ssl_s, WOLFSSL_FATAL_ERROR), - WOLFSSL_ERROR_WANT_READ); - /* Fail if we didn't advance the buffer to avoid infinite loops */ - ExpectIntLT(test_ctx->s_len, s_len); + ExpectIntEQ(test_dtls13_server_read_all(test_ctx, ssl_s), TEST_SUCCESS); + return EXPECT_RESULT(); +} +#endif +#endif + +/* The retained bytes a pool holds for unverified ClientHellos */ +#define TEST_DTLS_RX_UNVERIFIED_LIMIT WOLFSSL_DTLS_RX_UNVERIFIED_MAX_BYTES + +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) +/* Entries in all shards */ +static int test_dtls13_pool_count(const DtlsChPool* p) +{ + const DtlsMsg* m; + int n = 0; + int i; + + if (p == NULL) + return -1; + for (i = 0; i < WOLFSSL_DTLS_CH_POOL_SHARDS; i++) { + for (m = p->shard[i].head; m != NULL; m = m->next) + n++; } - /* Expect all fragments read */ - ExpectIntEQ(test_ctx->s_len, 0); + return n; +} + +/* Bytes retained by all shards */ +static size_t test_dtls13_pool_bytes(const DtlsChPool* p) +{ + size_t n = 0; + int i; + + for (i = 0; p != NULL && i < WOLFSSL_DTLS_CH_POOL_SHARDS; i++) + n += p->shard[i].bytes; + return n; +} + +/* Bytes the pool has reserved */ +static size_t test_dtls13_pool_reserved(const DtlsChPool* p) +{ + return (size_t)WOLFSSL_ATOMIC_LOAD(p->reserved); +} + +/* The entry that progressed next after m in any shard, or the least recently + * progressed one of the pool when m is NULL */ +static DtlsMsg* test_dtls13_pool_next(const DtlsChPool* p, const DtlsMsg* m) +{ + DtlsMsg* best = NULL; + DtlsMsg* e; + int i; + + for (i = 0; p != NULL && i < WOLFSSL_DTLS_CH_POOL_SHARDS; i++) { + for (e = p->shard[i].head; e != NULL; e = e->next) { + if ((m == NULL || e->progress > m->progress) && + (best == NULL || e->progress < best->progress)) + best = e; + } + } + return best; +} + +/* The least recently progressed entry of the pool */ +static DtlsMsg* test_dtls13_pool_head(const DtlsChPool* p) +{ + return test_dtls13_pool_next(p, NULL); +} + +/* The entry held for the peer test_dtls13_server_peer() set as id */ +static DtlsMsg* test_dtls13_held_for(const DtlsChPool* p, int id) +{ + DtlsMsg* m; + + for (m = test_dtls13_pool_head(p); m != NULL; + m = test_dtls13_pool_next(p, m)) { + if (m->peer.sz == sizeof(int) && + XMEMCMP(m->peer.sa, &id, sizeof(int)) == 0) + return m; + } + return NULL; +} + +/* With no store in progress: the shards' counters match their entries, each + * entry is in its hash's shard and in progress order, the reservation is what + * the shards retain, the budget holds, and no entry is complete. */ +static word32 test_dtls13_head_progress(const DtlsChPoolShard* sh) +{ + return (word32)WOLFSSL_ATOMIC_LOAD(sh->headProgress); +} + +static word32 test_dtls13_next_progress(const DtlsChPoolShard* sh) +{ + return (word32)WOLFSSL_ATOMIC_LOAD(sh->nextProgress); +} + +/* Whether sh publishes the progress of its head and of the entry after it, + * 0 when there is none */ +static int test_dtls13_head_progress_ok(const DtlsChPoolShard* sh) +{ + const DtlsMsg* next = sh->head != NULL ? sh->head->next : NULL; + + return test_dtls13_head_progress(sh) == + (sh->head != NULL ? sh->head->progress : 0U) && + test_dtls13_next_progress(sh) == + (next != NULL ? next->progress : 0U); +} + +static int test_dtls13_pool_ok(const DtlsChPool* p) +{ + const DtlsMsg* m; + int i; + + if (p == NULL) + return 0; + for (i = 0; i < WOLFSSL_DTLS_CH_POOL_SHARDS; i++) { + const DtlsChPoolShard* sh = &p->shard[i]; + + if (!test_dtls13_head_progress_ok(sh)) + return 0; + + for (m = sh->head; m != NULL; m = m->next) { + if (m->ready || DtlsChPoolShardIdx(&m->peer) != (word32)i) + return 0; + if (m->next != NULL && m->next->progress <= m->progress) + return 0; + } + if (sh->bytes != DtlsMsgListRetainedBytes(sh->head)) + return 0; + } + return test_dtls13_pool_reserved(p) == test_dtls13_pool_bytes(p) && + test_dtls13_pool_bytes(p) <= TEST_DTLS_RX_UNVERIFIED_LIMIT; +} + +/* Make m look as if it gained no bytes for longer than the pool keeps it */ +static void test_dtls13_age(DtlsMsg* m) +{ + if (m != NULL) + m->heldAt -= (word32)DTLS_TIMEOUT_MAX + 1; +} + +/* Store bytes [off, off + len) of an sz byte ClientHello from peer id */ +static int test_dtls13_pool_put(DtlsChPool* pool, int id, const byte* data, + word32 sz, word32 off, word32 len) +{ + WOLFSSL_SOCKADDR peer; + DtlsMsg* done = NULL; + int ret; + + peer.sz = sizeof(id); + peer.bufSz = sizeof(id); + peer.sa = &id; + ret = DtlsChPoolStore(pool, NULL, &peer, 0, 1, data, sz, client_hello, off, + len, DTLS_TIMEOUT_MAX, NULL, &done); + DtlsMsgListDelete(done, NULL); + return ret; +} + +static word32 test_dtls13_shard_of(int id) +{ + WOLFSSL_SOCKADDR peer; + + peer.sz = sizeof(id); + peer.bufSz = sizeof(id); + peer.sa = &id; + return DtlsChPoolShardIdx(&peer); +} + +/* The first id from id on whose shard is, or is not, shard */ +static int test_dtls13_id_in(int id, word32 shard, int in) +{ + if (WOLFSSL_DTLS_CH_POOL_SHARDS == 1) + return id; + while ((test_dtls13_shard_of(id) == shard) != in) + id++; + return id; +} +#endif + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) +/* The context's pool */ +static DtlsChPool* test_dtls13_ch_pool(WOLFSSL* s) +{ + if (s == NULL || s->ctx == NULL) + return NULL; + return &s->ctx->dtlsChPool; +} + +/* Nothing of an unverified ClientHello is held: not in the context's pool + * and not in the object's receive list */ +static int test_dtls13_nothing_held(WOLFSSL* s) +{ + const DtlsChPool* p = test_dtls13_ch_pool(s); + + return p != NULL && test_dtls13_pool_count(p) == 0 && + test_dtls13_pool_bytes(p) == 0 && test_dtls13_pool_reserved(p) == 0 && + s->dtls_rx_msg_list == NULL; +} + +#endif + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) +/* Point the server at the buffers and the peer identity of the next datagrams. + * Memio has no address, so wolfSSL_dtls_set_peer() supplies one. */ +static int test_dtls13_server_peer(WOLFSSL* ssl_s, + struct test_memio_ctx* test_ctx, int peerId) +{ + wolfSSL_SetIOReadCtx(ssl_s, test_ctx); + wolfSSL_SetIOWriteCtx(ssl_s, test_ctx); + return wolfSSL_dtls_set_peer(ssl_s, &peerId, sizeof(peerId)); +} +#endif + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) +/* Write body bytes [fragOff, fragOff + fragLen) of the message in record rec + * as a record of its own, seq + recSeqAdd. Returns its size, or -1. */ +static int test_dtls13_frag_range(const byte* rec, int recSz, word32 fragOff, + word32 fragLen, word32 recSeqAdd, byte* out, int outSz) +{ + const int hdrSz = DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ; + const byte* hs = rec + DTLS_RECORD_HEADER_SZ; + word32 msgLen, recSeq; + + if (recSz < hdrSz) + return -1; + ato24(hs + 1, &msgLen); + if ((word32)recSz != (word32)hdrSz + msgLen || fragOff > msgLen || + fragLen > msgLen - fragOff || + (word32)outSz < (word32)hdrSz + fragLen) + return -1; + + XMEMCPY(out, rec, hdrSz); + /* low 32 bits of the 48-bit record sequence number */ + ato32(rec + DTLS_RECORD_HEADER_SZ - 6, &recSeq); + c32toa(recSeq + recSeqAdd, out + DTLS_RECORD_HEADER_SZ - 6); + c16toa((word16)(DTLS_HANDSHAKE_HEADER_SZ + fragLen), + out + DTLS_RECORD_HEADER_SZ - 2); + /* fragment_offset and fragment_length follow type(1) length(3) seq(2) */ + c32to24(fragOff, out + DTLS_RECORD_HEADER_SZ + 6); + c32to24(fragLen, out + DTLS_RECORD_HEADER_SZ + 9); + XMEMCPY(out + hdrSz, hs + DTLS_HANDSHAKE_HEADER_SZ + fragOff, fragLen); + return hdrSz + (int)fragLen; +} + +/* Write fragment fragIdx of the message in rec, fragSz body bytes at most, as + * test_dtls13_frag_range(). Returns the size, 0 past the end, or -1. */ +static int test_dtls13_frag_record(const byte* rec, int recSz, int fragSz, + int fragIdx, byte* out, int outSz) +{ + const int hdrSz = DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ; + word32 msgLen, fragOff, fragLen; + + if (recSz < hdrSz) + return -1; + ato24(rec + DTLS_RECORD_HEADER_SZ + 1, &msgLen); + if ((word32)recSz != (word32)hdrSz + msgLen) + return -1; + fragOff = (word32)fragIdx * (word32)fragSz; + if (fragOff >= msgLen) + return 0; + fragLen = msgLen - fragOff; + if (fragLen > (word32)fragSz) + fragLen = (word32)fragSz; + return test_dtls13_frag_range(rec, recSz, fragOff, fragLen, + (word32)fragIdx, out, outSz); +} + +/* Queue fragment fragIdx of the message in rec for the server. Returns 1 when + * queued, 0 when there is no such fragment, -1 on error. */ +static int test_dtls13_inject_frag(struct test_memio_ctx* test_ctx, + const byte* rec, int recSz, int fragSz, int fragIdx) +{ + byte frag[TEST_MEMIO_BUF_SZ]; + int n = test_dtls13_frag_record(rec, recSz, fragSz, fragIdx, frag, + (int)sizeof(frag)); + if (n <= 0) + return n; + if (test_memio_inject_message(test_ctx, 0, (const char*)frag, n) != 0) + return -1; + return 1; +} + +/* Queue every fragment of the message in rec for the server, one datagram + * each. Returns the number of fragments, or -1 on error. */ +static int test_dtls13_inject_fragmented(struct test_memio_ctx* test_ctx, + const byte* rec, int recSz, int fragSz) +{ + int i; + for (i = 0; ; i++) { + int ret = test_dtls13_inject_frag(test_ctx, rec, recSz, fragSz, i); + if (ret < 0) + return -1; + if (ret == 0) + return i; + } +} + +/* Client sends its first ClientHello; the record is copied to ch and removed + * from the server's queue so the test can deliver it in pieces. */ +static int test_dtls13_take_ch(struct test_memio_ctx* test_ctx, + WOLFSSL* ssl_c, byte* ch, int* chSz) +{ + EXPECT_DECLS; + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + /* one record, whole ClientHello */ + ExpectIntEQ(test_ctx->s_msg_count, 1); + ExpectIntEQ(test_memio_copy_message(test_ctx, 0, (char*)ch, chSz, 0), 0); + test_memio_clear_buffer(test_ctx, 0); return EXPECT_RESULT(); } #endif @@ -454,12 +776,18 @@ int test_dtls_frag_ch(void) #endif ExpectIntEQ(wolfSSL_dtls13_allow_ch_frag(ssl_s, 1), WOLFSSL_SUCCESS); + /* Hold nothing before the cookie verifies: a fragmented first ClientHello + * is dropped, a second one accepted from the fragment with the cookie. */ + ExpectIntEQ(wolfSSL_dtls13_reassemble_ch_before_cookie(ssl_s, 0), + WOLFSSL_SUCCESS); /* Reject fragmented first CH */ ExpectIntEQ(test_dtls13_inject_four_frag_ch(&test_ctx, ssl_s), TEST_SUCCESS); /* Expect quietly dropping fragmented first CH */ ExpectIntEQ(test_ctx.c_len, 0); + ExpectNull(ssl_s->dtls_rx_msg_list); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 0); #if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) /* Disable ECH as it pushes it over our MTU */ @@ -602,6 +930,7 @@ int test_dtls13_frag_ch1_no_cookie(void) ExpectIntEQ(test_ctx.s_len, 0); if (records == 0) { ExpectNotNull(ssl_s->dtls_rx_msg_list); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 0); ExpectIntEQ(test_ctx.c_len, 0); ExpectIntEQ(ssl_s->keys.dtls_expected_peer_handshake_number, 0); /* A retransmitted first fragment must not notify. */ @@ -702,53 +1031,60 @@ int test_dtls13_no_cookie_handoff(void) return EXPECT_RESULT(); } -/* Which results of DtlsMsgStore() end the connection: only running out of - * memory, or an authenticated peer exceeding the receive budget */ -int test_dtls13_frag_store_fatal(void) + +/* Clearing the pool frees every entry and leaves the setting alone */ +int test_dtls13_clear_ch_pool_api(void) { EXPECT_DECLS; #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ - && defined(WOLFSSL_DTLS_CH_FRAG) - WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; - WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + WOLFSSL_CTX *tls = NULL; struct test_memio_ctx test_ctx; - byte data[32]; - word16 epoch = 0; - int ret = 0; + byte peerBuf[4]; + WOLFSSL_SOCKADDR peer; + byte data[8]; + DtlsMsg* done = NULL; XMEMSET(&test_ctx, 0, sizeof(test_ctx)); - XMEMSET(data, 0xAB, sizeof(data)); ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); - /* Plaintext could come from anyone: only memory ends the connection */ - ExpectIntEQ(DtlsMsgStoreFatal(ssl_s, 0), 0); - ExpectIntEQ(DtlsMsgStoreFatal(ssl_s, DTLS_MSG_STORE_DROPPED), 0); - ExpectIntEQ(DtlsMsgStoreFatal(ssl_s, WC_NO_ERR_TRACE(BAD_FUNC_ARG)), 0); - ExpectIntEQ(DtlsMsgStoreFatal(ssl_s, - WC_NO_ERR_TRACE(DTLS_TOO_MANY_FRAGMENTS_E)), 0); - ExpectIntNE(DtlsMsgStoreFatal(ssl_s, WC_NO_ERR_TRACE(MEMORY_E)), 0); - ExpectIntNE(DtlsMsgStoreFatal(ssl_s, WC_NO_ERR_TRACE(MEMORY_ERROR)), 0); + ExpectIntEQ(wolfSSL_CTX_dtls13_clear_ch_pool(NULL), BAD_FUNC_ARG); + ExpectIntEQ(wolfSSL_CTX_dtls13_clear_ch_pool(ctx_c), WOLFSSL_FAILURE); + /* A context without a pool has nothing to clear */ + ExpectNotNull(tls = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + ExpectIntEQ(wolfSSL_CTX_dtls13_clear_ch_pool(tls), WOLFSSL_SUCCESS); + wolfSSL_CTX_free(tls); - /* The records of a peer that finished the handshake authenticate */ - ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); - if (EXPECT_SUCCESS()) { - ExpectIntNE(ssl_s->keys.encryptionOn, 0); - ExpectIntEQ(w64IsZero(ssl_s->keys.curEpoch64), 0); - epoch = (word16)w64GetLow32(ssl_s->keys.curEpoch64); - } - ExpectIntEQ(DtlsMsgStoreFatal(ssl_s, 0), 0); - ExpectIntEQ(DtlsMsgStoreFatal(ssl_s, DTLS_MSG_STORE_DROPPED), 0); - ExpectIntNE(DtlsMsgStoreFatal(ssl_s, - WC_NO_ERR_TRACE(DTLS_TOO_MANY_FRAGMENTS_E)), 0); - ExpectIntNE(DtlsMsgStoreFatal(ssl_s, WC_NO_ERR_TRACE(MEMORY_E)), 0); - /* A fragment that gives the message held another size is dropped */ - ExpectIntEQ(DtlsMsgStore(ssl_s, epoch, 20, data, 32, certificate, 0, 16, - ssl_s->heap), 0); - ret = DtlsMsgStore(ssl_s, epoch, 20, data, 24, certificate, 16, 8, - ssl_s->heap); - ExpectIntEQ(ret, WC_NO_ERR_TRACE(SEQUENCE_ERROR)); - ExpectIntEQ(DtlsMsgStoreFatal(ssl_s, ret), 0); + XMEMSET(peerBuf, 1, sizeof(peerBuf)); + XMEMSET(data, 0x5a, sizeof(data)); + peer.sa = peerBuf; + peer.sz = peer.bufSz = (unsigned int)sizeof(peerBuf); + ExpectIntEQ(DtlsChPoolStore(test_dtls13_ch_pool(ssl_s), ctx_s->heap, &peer, + 0, 0, data, 8, client_hello, 0, 4, DTLS_TIMEOUT_MAX, ssl_s->heap, + &done), 0); + peerBuf[0] = 2; + ExpectIntEQ(DtlsChPoolStore(test_dtls13_ch_pool(ssl_s), ctx_s->heap, &peer, + 0, 0, data, 8, client_hello, 0, 4, DTLS_TIMEOUT_MAX, ssl_s->heap, + &done), 0); + ExpectNull(done); + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), 2); + ExpectIntEQ(wolfSSL_CTX_dtls13_clear_ch_pool(ctx_s), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), 0); + ExpectIntEQ(test_dtls13_pool_bytes(test_dtls13_ch_pool(ssl_s)), 0); + ExpectTrue(test_dtls13_pool_ok(test_dtls13_ch_pool(ssl_s))); + ExpectIntEQ(ctx_s->dtls13ReassembleChBeforeCookie, 1); + /* The pool takes entries again */ + ExpectIntEQ(DtlsChPoolStore(test_dtls13_ch_pool(ssl_s), ctx_s->heap, &peer, + 0, 0, data, 8, client_hello, 0, 4, DTLS_TIMEOUT_MAX, ssl_s->heap, + &done), 0); + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), 1); + ExpectIntEQ(wolfSSL_CTX_dtls13_clear_ch_pool(ctx_s), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), 0); wolfSSL_free(ssl_c); wolfSSL_free(ssl_s); @@ -758,32 +1094,99 @@ int test_dtls13_frag_store_fatal(void) return EXPECT_RESULT(); } -/* wolfSSL_clear() frees the receive list: a reused object starts with no - * message of the last connection. */ -int test_dtls13_clear_frees_rx_list(void) +/* The knob, its argument checks and its context default. */ +int test_dtls13_reassemble_ch_before_cookie_api(void) { EXPECT_DECLS; #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ - && (defined(OPENSSL_EXTRA) || defined(DEBUG_WOLFSSL_VERBOSE) || \ - defined(HAVE_CURL)) - WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; - WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) + 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 data[16]; XMEMSET(&test_ctx, 0, sizeof(test_ctx)); - XMEMSET(data, 0x5a, sizeof(data)); ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); - ExpectIntEQ(DtlsMsgStore(ssl_s, 0, 3, data, (word32)sizeof(data), - certificate, 0, 8, ssl_s->heap), 0); - ExpectNotNull(ssl_s->dtls_rx_msg_list); - ExpectIntEQ(ssl_s->dtls_rx_msg_list_sz, 1); - ExpectIntGT(ssl_s->dtls_rx_msg_list_bytes, 0); - ExpectIntEQ(wolfSSL_clear(ssl_s), WOLFSSL_SUCCESS); - ExpectNull(ssl_s->dtls_rx_msg_list); - ExpectIntEQ(ssl_s->dtls_rx_msg_list_sz, 0); - ExpectIntEQ(ssl_s->dtls_rx_msg_list_bytes, 0); + + ExpectIntEQ(wolfSSL_dtls13_reassemble_ch_before_cookie(NULL, 1), + BAD_FUNC_ARG); + ExpectIntEQ(wolfSSL_CTX_dtls13_reassemble_ch_before_cookie(NULL, 1), + BAD_FUNC_ARG); + ExpectIntEQ(wolfSSL_dtls13_reassemble_ch_before_cookie(ssl_c, 1), + WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_CTX_dtls13_reassemble_ch_before_cookie(ctx_c, 1), + WOLFSSL_FAILURE); + + /* On by default, inherited from the context */ + ExpectIntEQ(ssl_s->options.dtls13ReassembleChBeforeCookie, 1); + ExpectIntEQ(wolfSSL_CTX_dtls13_reassemble_ch_before_cookie(ctx_s, 0), + WOLFSSL_SUCCESS); + wolfSSL_free(ssl_s); + ssl_s = NULL; + ExpectNotNull(ssl_s = wolfSSL_new(ctx_s)); + ExpectIntEQ(ssl_s->options.dtls13ReassembleChBeforeCookie, 0); + ExpectIntEQ(wolfSSL_dtls13_reassemble_ch_before_cookie(ssl_s, 1), + WOLFSSL_SUCCESS); + ExpectIntEQ(ssl_s->options.dtls13ReassembleChBeforeCookie, 1); + ExpectIntEQ(wolfSSL_dtls13_reassemble_ch_before_cookie(ssl_s, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(ssl_s->options.dtls13ReassembleChBeforeCookie, 0); + +#if defined(HAVE_ECC) && !defined(NO_ECC256) + { + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + int piece; + + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + piece = (chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + 1) / + 2; + ExpectIntEQ(wolfSSL_dtls13_reassemble_ch_before_cookie(ssl_s, 1), + WOLFSSL_SUCCESS); + + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, 2), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx, ch, chSz, piece, 0), 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + + /* Turning an object off only stops it adding: the entries belong to + * the context */ + ExpectIntEQ(wolfSSL_dtls13_reassemble_ch_before_cookie(ssl_s, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, 3), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx, ch, chSz, piece, 0), 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + + /* Without a peer address nothing is held */ + ExpectIntEQ(wolfSSL_dtls13_reassemble_ch_before_cookie(ssl_s, 1), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_dtls_set_peer(ssl_s, NULL, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx, ch, chSz, piece, 0), 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + ExpectNull(ssl_s->dtls_rx_msg_list); + + /* Turning the context off drops what its pool holds */ + ExpectIntEQ(wolfSSL_CTX_dtls13_reassemble_ch_before_cookie(ctx_s, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 0); + ExpectIntEQ(test_dtls13_pool_bytes(&ctx_s->dtlsChPool), 0); + ExpectNull(test_dtls13_pool_head(&ctx_s->dtlsChPool)); + ExpectIntEQ(test_ctx.c_len, 0); + } +#endif wolfSSL_free(ssl_c); wolfSSL_free(ssl_s); @@ -793,6 +1196,4712 @@ int test_dtls13_clear_frees_rx_list(void) return EXPECT_RESULT(); } +/* A fragmented first ClientHello is reassembled and answered with a HRR, the + * server still stateless after. Without a peer address it is dropped. */ +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) +static int test_dtls13_frag_ch1_reassemble_ex(int peerId) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + int isHrr = 0; + + 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); + if (peerId != 0) { + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, peerId), + WOLFSSL_SUCCESS); + } + ExpectIntEQ(wolfSSL_dtls13_allow_ch_frag(ssl_s, 1), WOLFSSL_SUCCESS); + + /* Canned four-fragment first ClientHello */ + ExpectIntEQ(test_dtls13_inject_four_frag_ch(&test_ctx, ssl_s), + TEST_SUCCESS); + if (peerId != 0) { + ExpectIntGT(test_ctx.c_len, 0); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + } + else { + ExpectIntEQ(test_ctx.c_len, 0); + } + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectTrue(test_dtls13_nothing_held(ssl_s)); + ExpectTrue(test_dtls13_pool_ok(test_dtls13_ch_pool(ssl_s))); + test_memio_clear_buffer(&test_ctx, 1); + + /* Real client. One classical group keeps its first ClientHello small and + * in one record, which the test then splits into three datagrams. */ + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_inject_fragmented(&test_ctx, ch, chSz, + (chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + 2) / 3), 3); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + if (peerId == 0) { + /* No address: dropped. The whole ClientHello is answered. */ + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectTrue(test_dtls13_nothing_held(ssl_s)); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)ch, + chSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), + TEST_SUCCESS); + } + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectTrue(test_dtls13_nothing_held(ssl_s)); + ExpectTrue(test_dtls13_pool_ok(test_dtls13_ch_pool(ssl_s))); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +int test_dtls13_frag_ch1_reassemble(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + ExpectIntEQ(test_dtls13_frag_ch1_reassemble_ex(0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch1_reassemble_ex(7), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +/* The second ClientHello fragmented with the cookie past the first fragment, + * as every peer sends it. Completed before the cookie check, or dropped. */ +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) +static int test_dtls13_frag_ch2_cookie_not_first_ex(int peerId) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + /* Shorter than legacy_version, random and the length bytes before the + * extensions, so the first fragment holds no extension at all. */ + const int fragSz = 40; + int isHrr = 0; + + 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); + if (peerId != 0) { + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, peerId), + WOLFSSL_SUCCESS); + } + ExpectIntEQ(wolfSSL_dtls13_allow_ch_frag(ssl_s, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + + /* CH1, HRR with cookie, CH2 */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + + /* Nothing held before the cookie verifies: every fragment is dropped */ + ExpectIntEQ(wolfSSL_dtls13_reassemble_ch_before_cookie(ssl_s, 0), + WOLFSSL_SUCCESS); + ExpectIntGE(test_dtls13_inject_fragmented(&test_ctx, ch, chSz, fragSz), 3); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), 0); + ExpectTrue(test_dtls13_pool_ok(test_dtls13_ch_pool(ssl_s))); + ExpectNull(ssl_s->dtls_rx_msg_list); + + /* Reassembled, cookie verified on the complete message, server flight */ + ExpectIntEQ(wolfSSL_dtls13_reassemble_ch_before_cookie(ssl_s, 1), + WOLFSSL_SUCCESS); + ExpectIntGE(test_dtls13_inject_fragmented(&test_ctx, ch, chSz, fragSz), 3); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + if (peerId == 0) { + /* No address: dropped. The whole ClientHello is answered. */ + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectTrue(test_dtls13_nothing_held(ssl_s)); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)ch, + chSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), + TEST_SUCCESS); + } + ExpectIntGT(test_ctx.c_len, 0); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 0); + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + ExpectTrue(test_dtls13_nothing_held(ssl_s)); + ExpectTrue(test_dtls13_pool_ok(test_dtls13_ch_pool(ssl_s))); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +int test_dtls13_frag_ch2_cookie_not_first(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + ExpectIntEQ(test_dtls13_frag_ch2_cookie_not_first_ex(0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch2_cookie_not_first_ex(7), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) +/* The offset in the body of the ClientHello in the plaintext DTLS record ch + * just past its cookie extension, or 0 when it has none. */ +static int test_dtls13_ch_cookie_end(const byte* ch, int chSz) +{ + const byte* body = ch + DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ; + int sz = chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ; + /* past legacy_version and random */ + int idx = OPAQUE16_LEN + RAN_LEN; + int end; + word16 len; + + /* legacy_session_id and the DTLS cookie field */ + if (idx >= sz) + return 0; + idx += 1 + body[idx]; + if (idx >= sz) + return 0; + idx += 1 + body[idx]; + /* cipher_suites */ + if (idx + OPAQUE16_LEN > sz) + return 0; + ato16(body + idx, &len); + idx += OPAQUE16_LEN + len; + /* legacy_compression_methods */ + if (idx >= sz) + return 0; + idx += 1 + body[idx]; + /* extensions */ + if (idx + OPAQUE16_LEN > sz) + return 0; + ato16(body + idx, &len); + idx += OPAQUE16_LEN; + end = idx + len; + if (end > sz) + return 0; + while (idx + 2 * OPAQUE16_LEN <= end) { + word16 type; + + ato16(body + idx, &type); + ato16(body + idx + OPAQUE16_LEN, &len); + idx += 2 * OPAQUE16_LEN + len; + if (idx > end) + return 0; + if (type == TLSX_COOKIE) + return idx; + } + return 0; +} +#endif + +/* The second ClientHello fragmented with the cookie in the first fragment, + * which arrives last: the held rest completes it at once. Aged, or without a + * peer address, the rest is not held and the client must resend it. */ +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) +static int test_dtls13_frag_ch2_cookie_frag_last_ex(int peerId, int aged) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + byte frag[TEST_MEMIO_BUF_SZ]; + int fragSz = 0; + /* Three fragments: the first ends just past the cookie extension and the + * rest of the body is split in two. */ + int off[3] = { 0, 0, 0 }; + int len[3] = { 0, 0, 0 }; + int bodySz = 0; + int i; + int isHrr = 1; + /* The later fragments are not there when the first verifies */ + int late = aged || peerId == 0; + DtlsChPool* pool = NULL; + DtlsMsg* held = NULL; + + 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); + if (peerId != 0) { + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, peerId), + WOLFSSL_SUCCESS); + } + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + + /* CH1, HRR with cookie, CH2 */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + + bodySz = chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ; + ExpectIntGT(len[0] = test_dtls13_ch_cookie_end(ch, chSz), 0); + ExpectIntGE(bodySz - len[0], 2); + off[1] = len[0]; + len[1] = (bodySz - len[0]) / 2; + off[2] = off[1] + len[1]; + len[2] = bodySz - off[2]; + + /* The later fragments are held: no reply, still stateless. Without an + * address they are dropped. */ + for (i = 1; i < 3 && EXPECT_SUCCESS(); i++) { + ExpectIntGT(fragSz = test_dtls13_frag_range(ch, chSz, (word32)off[i], + (word32)len[i], (word32)i, frag, (int)sizeof(frag)), 0); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, + (const char*)frag, fragSz), 0); + } + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectNotNull(pool = test_dtls13_ch_pool(ssl_s)); + if (peerId != 0) { + ExpectIntEQ(test_dtls13_pool_count(pool), 1); + ExpectTrue(test_dtls13_pool_ok(pool)); + ExpectNotNull(held = test_dtls13_pool_head(pool)); + ExpectIntEQ(held != NULL ? (int)held->bytesReceived : -1, + len[1] + len[2]); + ExpectNull(ssl_s->dtls_rx_msg_list); + if (aged) + test_dtls13_age(held); + } + else { + ExpectTrue(test_dtls13_nothing_held(ssl_s)); + } + + /* The first fragment verifies the cookie and, with the rest held, completes + * the message at once. Otherwise the server waits for a resend. */ + ExpectIntGT(fragSz = test_dtls13_frag_range(ch, chSz, (word32)off[0], + (word32)len[0], 0, frag, (int)sizeof(frag)), 0); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)frag, + fragSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + if (late) { + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 0); + ExpectIntEQ(test_dtls13_pool_count(pool), 0); + ExpectTrue(test_dtls13_pool_ok(pool)); + ExpectNotNull(ssl_s->dtls_rx_msg_list); + test_memio_clear_buffer(&test_ctx, 1); + for (i = 1; i < 3 && EXPECT_SUCCESS(); i++) { + ExpectIntGT(fragSz = test_dtls13_frag_range(ch, chSz, + (word32)off[i], (word32)len[i], (word32)(i + 2), frag, + (int)sizeof(frag)), 0); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, + (const char*)frag, fragSz), 0); + } + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), + TEST_SUCCESS); + } + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 0); + ExpectIntEQ(test_dtls13_pool_count(pool), 0); + ExpectTrue(test_dtls13_pool_ok(pool)); + ExpectNull(ssl_s->dtls_rx_msg_list); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +int test_dtls13_frag_ch2_cookie_frag_last(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + ExpectIntEQ(test_dtls13_frag_ch2_cookie_frag_last_ex(0, 0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch2_cookie_frag_last_ex(7, 0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch2_cookie_frag_last_ex(7, 1), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +/* A forged first fragment, right length but no cookie, is held. When the + * genuine one verifies the forged bytes must not replace the verified ones. */ +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) +static int test_dtls13_frag_ch2_forged_first_frag_ex(int peerId) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + byte bad[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + int piece = 0; + int isHrr = 1; + + 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); + if (peerId != 0) { + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, peerId), + WOLFSSL_SUCCESS); + } + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + /* CH1 and HRR */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + /* The cookie fits the first fragment: the last byte goes on its own. */ + piece = chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ - 1; + + /* Same framing, body past the version and random replaced */ + XMEMCPY(bad, ch, (size_t)chSz); + XMEMSET(bad + DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ + + OPAQUE16_LEN + RAN_LEN, 0xff, (size_t)(chSz - DTLS_RECORD_HEADER_SZ - + DTLS_HANDSHAKE_HEADER_SZ - OPAQUE16_LEN - RAN_LEN)); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx, bad, chSz, piece, 0), 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + /* Without an address it is dropped */ + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), + peerId != 0 ? 1 : 0); + ExpectTrue(test_dtls13_pool_ok(test_dtls13_ch_pool(ssl_s))); + ExpectNull(ssl_s->dtls_rx_msg_list); + + ExpectIntEQ(test_dtls13_inject_fragmented(&test_ctx, ch, chSz, piece), 2); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 0); + /* Nothing of the forged entry lies past the verified fragment: freed */ + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), 0); + ExpectTrue(test_dtls13_pool_ok(test_dtls13_ch_pool(ssl_s))); + ExpectNull(ssl_s->dtls_rx_msg_list); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +int test_dtls13_frag_ch2_forged_first_frag(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + ExpectIntEQ(test_dtls13_frag_ch2_forged_first_frag_ex(0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch2_forged_first_frag_ex(7), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +/* A pooled fragment reaching into the first fragment that then verifies: the + * bytes past it complete the ClientHello without a resend, the overlap is + * taken from the verified copy. */ +int test_dtls13_frag_ch2_overlap_handoff(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + byte frag[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + int cut = 0; + int bodySz = 0; + int fragSz = 0; + int isHrr = 0; + + 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(test_dtls13_server_peer(ssl_s, &test_ctx, 2), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + + /* CH1, HRR with cookie, CH2 */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + ExpectIntGT(cut = test_dtls13_ch_cookie_end(ch, chSz), 8); + bodySz = chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ; + + /* The tail first, starting 8 bytes before the cookie ends: pooled */ + ExpectIntGT(fragSz = test_dtls13_frag_range(ch, chSz, (word32)(cut - 8), + (word32)(bodySz - (cut - 8)), 1, frag, (int)sizeof(frag)), 0); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)frag, + fragSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + ExpectIntEQ(test_ctx.c_len, 0); + + /* The first fragment, through the cookie, verifies and completes it */ + ExpectIntGT(fragSz = test_dtls13_frag_range(ch, chSz, 0, (word32)cut, 0, + frag, (int)sizeof(frag)), 0); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)frag, + fragSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 0); + ExpectNull(ssl_s->dtls_rx_msg_list); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* Two peers fragment their first ClientHello at once on one server. Each is + * reassembled and answered on its own, leaving the other's fragments. */ +int test_dtls13_frag_ch1_interleaved_peers(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + WOLFSSL_CTX *ctx_c1 = NULL, *ctx_c2 = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c1 = NULL, *ssl_c2 = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx1, test_ctx2; + int group = WOLFSSL_ECC_SECP256R1; + byte ch1[TEST_MEMIO_BUF_SZ], ch2[TEST_MEMIO_BUF_SZ]; + int ch1Sz = (int)sizeof(ch1), ch2Sz = (int)sizeof(ch2); + const int hdrSz = DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ; + int fragSz1 = 0, fragSz2 = 0; + int i; + int isHrr = 0; + + XMEMSET(&test_ctx1, 0, sizeof(test_ctx1)); + XMEMSET(&test_ctx2, 0, sizeof(test_ctx2)); + ExpectIntEQ(test_memio_setup(&test_ctx1, &ctx_c1, &ctx_s, &ssl_c1, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(test_memio_setup(&test_ctx2, &ctx_c2, NULL, &ssl_c2, NULL, + wolfDTLSv1_3_client_method, NULL), 0); + ExpectIntEQ(wolfSSL_dtls13_allow_ch_frag(ssl_s, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_c1, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_c2, &group, 1), WOLFSSL_SUCCESS); + + ExpectIntEQ(test_dtls13_take_ch(&test_ctx1, ssl_c1, ch1, &ch1Sz), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx2, ssl_c2, ch2, &ch2Sz), + TEST_SUCCESS); + /* three fragments each */ + fragSz1 = (ch1Sz - hdrSz + 2) / 3; + fragSz2 = (ch2Sz - hdrSz + 2) / 3; + + /* Deliver the fragments turn by turn. Neither ClientHello is complete + * before its last fragment, so no reply until then. */ + for (i = 0; i < 2 && EXPECT_SUCCESS(); i++) { + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx1, 1), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx1, ch1, ch1Sz, fragSz1, i), + 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx1, ssl_s), + TEST_SUCCESS); + ExpectIntEQ(test_ctx1.c_len, 0); + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx2, 2), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx2, ch2, ch2Sz, fragSz2, i), + 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx2, ssl_s), + TEST_SUCCESS); + ExpectIntEQ(test_ctx2.c_len, 0); + } + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 2); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + ExpectNull(ssl_s->dtls_rx_msg_list); + + /* Peer 1 completes: its HelloRetryRequest goes to peer 1 and peer 2's + * partial ClientHello stays. */ + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx1, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx1, ch1, ch1Sz, fragSz1, 2), 1); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx1, ch1, ch1Sz, fragSz1, 3), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx1, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx1, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(test_ctx2.c_len, 0); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + ExpectNotNull(test_dtls13_held_for(&ctx_s->dtlsChPool, 2)); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + ExpectNull(ssl_s->dtls_rx_msg_list); + + /* Peer 2 completes */ + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx2, 2), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx2, ch2, ch2Sz, fragSz2, 2), 1); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx2, ch2, ch2Sz, fragSz2, 3), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx2, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx2, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 0); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + ExpectNull(ssl_s->dtls_rx_msg_list); + + /* One object serves one handshake: peer 1 goes on to complete it */ + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx1, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c1, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c1); + wolfSSL_free(ssl_c2); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c1); + wolfSSL_CTX_free(ctx_c2); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* Exhaust the unverified byte budget with equal-sized partial ClientHellos. A + * further peer evicts the oldest, while the newest peer is still served. */ +int test_dtls13_frag_ch1_evict_oldest(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + int fragSz; + int peer; + int capacity; + int isHrr = 0; + + 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_dtls13_allow_ch_frag(ssl_s, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + /* two fragments */ + fragSz = (chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + 1) / 2; + + capacity = (int)(TEST_DTLS_RX_UNVERIFIED_LIMIT / + (sizeof(DtlsMsg) + sizeof(int) + sizeof(DtlsFragBucket) + fragSz)); + ExpectIntGT(capacity, 1); + /* The same ClientHello from one peer more than the byte budget holds. */ + for (peer = 1; peer <= capacity + 1 && + EXPECT_SUCCESS(); peer++) { + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, peer), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx, ch, chSz, fragSz, 0), + 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), + TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + } + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), capacity); + ExpectNull(test_dtls13_held_for(&ctx_s->dtlsChPool, 1)); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + ExpectNull(ssl_s->dtls_rx_msg_list); + + /* Peer 1 was evicted: its second fragment starts a new entry that does not + * complete, so no reply */ + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx, ch, chSz, fragSz, 1), 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), capacity); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + + /* The newest peer is still held and completes */ + peer = capacity + 1; + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, peer), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx, ch, chSz, fragSz, 1), 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + /* Completion can require another eviction before the HRR frees it. */ + ExpectIntLE(test_dtls13_pool_count(&ctx_s->dtlsChPool), capacity - 1); + ExpectNull(test_dtls13_held_for(&ctx_s->dtlsChPool, capacity + 1)); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + ExpectNull(ssl_s->dtls_rx_msg_list); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* The announced completed allocation includes message and bucket metadata. */ +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) +static int test_dtls13_frag_ch1_over_bound_ex(int peerId) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + byte frag[TEST_MEMIO_BUF_SZ]; + int fragSz = 0; + word32 maxPayload; + + 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); + if (peerId != 0) { + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, peerId), + WOLFSSL_SUCCESS); + } + ExpectIntEQ(wolfSSL_dtls13_allow_ch_frag(ssl_s, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + + /* First fragment of a message claiming one byte over the bound */ + ExpectIntGT(fragSz = test_dtls13_frag_record(ch, chSz, 100, 0, frag, + (int)sizeof(frag)), 0); + /* The last fragment is reserved as a new entry, so twice the metadata */ + maxPayload = (word32)(TEST_DTLS_RX_UNVERIFIED_LIMIT - 2 * (sizeof(DtlsMsg) + + sizeof(DtlsFragBucket) + ssl_s->buffers.dtlsCtx.peer.sz)); + /* A build whose protocol message limit is below the unverified budget + * rejects at that limit instead. */ + maxPayload = min(maxPayload, MAX_HANDSHAKE_SZ); + c32to24(maxPayload + 1, + frag + DTLS_RECORD_HEADER_SZ + 1); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)frag, + fragSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), 0); + ExpectNull(ssl_s->dtls_rx_msg_list); + + /* At the bound it is held. No address: nothing held even within the + * budget. */ + c32to24(maxPayload, + frag + DTLS_RECORD_HEADER_SZ + 1); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)frag, + fragSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + /* Freed with the context */ + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), + peerId != 0 ? 1 : 0); + ExpectTrue(test_dtls13_pool_ok(test_dtls13_ch_pool(ssl_s))); + ExpectNull(ssl_s->dtls_rx_msg_list); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +int test_dtls13_frag_ch1_over_bound(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + ExpectIntEQ(test_dtls13_frag_ch1_over_bound_ex(0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch1_over_bound_ex(7), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +/* A plaintext fragment reaching past its message is dropped, cookie exchange + * on or off, and the genuine ClientHello is then answered. */ +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) +static int test_dtls13_frag_past_end_ex(int peerId) +{ + EXPECT_DECLS; + int cookie; + + for (cookie = 0; cookie < 2 && EXPECT_SUCCESS(); cookie++) { + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + byte frag[TEST_MEMIO_BUF_SZ]; + int fragSz = 0; + int piece = 0; + int isHrr = 0; + + 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); + if (peerId != 0) { + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, peerId), + WOLFSSL_SUCCESS); + } + if (!cookie) { + ExpectIntEQ(wolfSSL_disable_hrr_cookie(ssl_s), WOLFSSL_SUCCESS); + } + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + piece = (chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + 2) / + 3; + + /* Last fragment, announcing a message that ends before it */ + ExpectIntGT(fragSz = test_dtls13_frag_record(ch, chSz, piece, 2, frag, + (int)sizeof(frag)), 0); + c32to24(10, frag + DTLS_RECORD_HEADER_SZ + 1); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)frag, + fragSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), + TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), 0); + ExpectTrue(test_dtls13_pool_ok(test_dtls13_ch_pool(ssl_s))); + ExpectNull(ssl_s->dtls_rx_msg_list); + + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)ch, + chSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, cookie); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + } + return EXPECT_RESULT(); +} +#endif + +int test_dtls13_frag_past_end(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + ExpectIntEQ(test_dtls13_frag_past_end_ex(0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_past_end_ex(7), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +/* A fragment planted before the cookie verifies, announcing another length, + * is dropped when the cookie verifies: the stateful server must not fail. */ +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) +static int test_dtls13_frag_ch2_planted_fragment_ex(int peerId) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + byte frag[TEST_MEMIO_BUF_SZ]; + int fragSz = 0; + int piece = 0; + word32 msgLen = 0; + + 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); + if (peerId != 0) { + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, peerId), + WOLFSSL_SUCCESS); + } + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + /* CH1 and HRR */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + /* The cookie fits the first fragment: the last byte goes on its own. */ + piece = chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ - 1; + + ExpectIntGT(fragSz = test_dtls13_frag_record(ch, chSz, piece, 1, frag, + (int)sizeof(frag)), 0); + if (EXPECT_SUCCESS()) { + ato24(frag + DTLS_RECORD_HEADER_SZ + 1, &msgLen); + c32to24(msgLen + 1, frag + DTLS_RECORD_HEADER_SZ + 1); + } + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)frag, + fragSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + /* Without an address it is dropped */ + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), + peerId != 0 ? 1 : 0); + ExpectTrue(test_dtls13_pool_ok(test_dtls13_ch_pool(ssl_s))); + ExpectNull(ssl_s->dtls_rx_msg_list); + + ExpectIntEQ(test_dtls13_inject_fragmented(&test_ctx, ch, chSz, piece), 2); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), 0); + ExpectTrue(test_dtls13_pool_ok(test_dtls13_ch_pool(ssl_s))); + ExpectIntGT(test_ctx.c_len, 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectNull(ssl_s->dtls_rx_msg_list); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +int test_dtls13_frag_ch2_planted_fragment(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + ExpectIntEQ(test_dtls13_frag_ch2_planted_fragment_ex(0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch2_planted_fragment_ex(7), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +/* A stale fragment of the second ClientHello, then the whole message: the + * entry must not stay at the head of the list and stall later fragments. */ +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) && defined(WOLFSSL_DTLS_MTU) && \ + !defined(NO_CERTS) && !defined(NO_FILESYSTEM) +static int test_dtls13_frag_ch2_stale_fragment_ex(int peerId) +{ + EXPECT_DECLS; + int mode; + + for (mode = 0; mode < 4 && EXPECT_SUCCESS(); mode++) { + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + int piece = 0; + + 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); + if (peerId != 0) { + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, peerId), + WOLFSSL_SUCCESS); + } + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + if (mode & 1) { + ExpectIntEQ(wolfSSL_use_certificate_file(ssl_c, cliCertFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_use_PrivateKey_file(ssl_c, cliKeyFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, cliCertFile, + NULL), WOLFSSL_SUCCESS); + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_PEER | + WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + } + /* CH1 and HRR */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + if (mode & 2) { + ExpectIntEQ(wolfSSL_dtls_set_mtu(ssl_c, 400), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_dtls_set_mtu(ssl_s, 400), WOLFSSL_SUCCESS); + } + piece = (chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + 2) / + 3; + + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx, ch, chSz, piece, 2), 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), + TEST_SUCCESS); + /* Without an address it is dropped */ + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), + peerId != 0 ? 1 : 0); + ExpectTrue(test_dtls13_pool_ok(test_dtls13_ch_pool(ssl_s))); + ExpectNull(ssl_s->dtls_rx_msg_list); + + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)ch, + chSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), + TEST_SUCCESS); + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), 0); + ExpectTrue(test_dtls13_pool_ok(test_dtls13_ch_pool(ssl_s))); + ExpectNull(ssl_s->dtls_rx_msg_list); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + ExpectNull(ssl_s->dtls_rx_msg_list); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + } + return EXPECT_RESULT(); +} +#endif + +int test_dtls13_frag_ch2_stale_fragment(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) && defined(WOLFSSL_DTLS_MTU) && \ + !defined(NO_CERTS) && !defined(NO_FILESYSTEM) + ExpectIntEQ(test_dtls13_frag_ch2_stale_fragment_ex(0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch2_stale_fragment_ex(7), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +/* A fragment whose length contradicts the held part of an unverified + * ClientHello replaces it: a forger cannot pin the entry or fail the server. */ +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) +static int test_dtls13_frag_ch1_length_mismatch_ex(int peerId) +{ + EXPECT_DECLS; + int forgedFirst; + + for (forgedFirst = 0; forgedFirst < 2 && EXPECT_SUCCESS(); forgedFirst++) { + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + byte frag[TEST_MEMIO_BUF_SZ]; + int fragSz = 0; + int piece = 0; + int isHrr = 0; + word32 msgLen = 0; + DtlsChPool* pool = NULL; + + 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); + if (peerId != 0) { + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, peerId), + WOLFSSL_SUCCESS); + } + ExpectNotNull(pool = test_dtls13_ch_pool(ssl_s)); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + piece = (chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + 2) / + 3; + + if (!forgedFirst) { + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx, ch, chSz, piece, 0), + 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_pool_count(pool), peerId != 0 ? 1 : 0); + ExpectTrue(test_dtls13_pool_ok(pool)); + } + + /* Second fragment announcing a longer message */ + ExpectIntGT(fragSz = test_dtls13_frag_record(ch, chSz, piece, 1, frag, + (int)sizeof(frag)), 0); + if (EXPECT_SUCCESS()) { + ato24(frag + DTLS_RECORD_HEADER_SZ + 1, &msgLen); + c32to24(msgLen + 1, frag + DTLS_RECORD_HEADER_SZ + 1); + } + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)frag, + fragSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), + TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + if (peerId != 0) { + /* Only the forged message is held */ + ExpectIntEQ(test_dtls13_pool_count(pool), 1); + ExpectTrue(test_dtls13_pool_ok(pool)); + ExpectIntEQ(test_dtls13_pool_head(pool) != NULL ? + test_dtls13_pool_head(pool)->sz : 0, msgLen + 1); + ExpectNull(ssl_s->dtls_rx_msg_list); + } + else { + ExpectTrue(test_dtls13_nothing_held(ssl_s)); + } + + /* The retransmitted ClientHello replaces it and completes */ + ExpectIntEQ(test_dtls13_inject_fragmented(&test_ctx, ch, chSz, piece), + 3); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), + TEST_SUCCESS); + if (peerId == 0) { + /* No address: dropped. The whole ClientHello is answered. */ + ExpectIntEQ(test_ctx.c_len, 0); + ExpectTrue(test_dtls13_nothing_held(ssl_s)); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, + (const char*)ch, chSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), + TEST_SUCCESS); + } + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(test_dtls13_pool_count(pool), 0); + ExpectTrue(test_dtls13_pool_ok(pool)); + ExpectNull(ssl_s->dtls_rx_msg_list); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + } + return EXPECT_RESULT(); +} +#endif + +int test_dtls13_frag_ch1_length_mismatch(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + ExpectIntEQ(test_dtls13_frag_ch1_length_mismatch_ex(0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch1_length_mismatch_ex(7), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +/* A reassembled first ClientHello that fails to parse is released and the + * stateless server goes on: the genuine ClientHello gets its HRR. */ +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) +static int test_dtls13_frag_ch1_bad_reassembled_ex(int peerId) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + byte bad[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + int piece = 0; + int isHrr = 0; + + 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); + if (peerId != 0) { + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, peerId), + WOLFSSL_SUCCESS); + } + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + piece = (chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + 2) / 3; + + /* Same framing, body past the version and random replaced */ + XMEMCPY(bad, ch, (size_t)chSz); + XMEMSET(bad + DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ + + OPAQUE16_LEN + RAN_LEN, 0xff, (size_t)(chSz - DTLS_RECORD_HEADER_SZ - + DTLS_HANDSHAKE_HEADER_SZ - OPAQUE16_LEN - RAN_LEN)); + ExpectIntEQ(test_dtls13_inject_fragmented(&test_ctx, bad, chSz, piece), 3); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + /* At most a stateless alert goes back. Without an address nothing is + * reassembled, so nothing goes back. */ + if (peerId == 0) { + ExpectIntEQ(test_ctx.c_len, 0); + } + else { + ExpectTrue(test_ctx.c_len == 0 || test_ctx.c_buff[0] == alert); + } + test_memio_clear_buffer(&test_ctx, 1); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), 0); + ExpectTrue(test_dtls13_pool_ok(test_dtls13_ch_pool(ssl_s))); + ExpectNull(ssl_s->dtls_rx_msg_list); + + /* Fresh record numbers for the genuine message */ + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)ch, + chSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +int test_dtls13_frag_ch1_bad_reassembled(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + ExpectIntEQ(test_dtls13_frag_ch1_bad_reassembled_ex(0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch1_bad_reassembled_ex(7), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +/* Peer 2's partial first ClientHello stays in the pool when peer 1's cookie + * verifies and the server turns stateful; another server completes it. */ +int test_dtls13_frag_ch2_verify_keeps_other_peers(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + WOLFSSL_CTX *ctx_c1 = NULL, *ctx_c2 = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c1 = NULL, *ssl_c2 = NULL, *ssl_s = NULL, *c2 = NULL; + struct test_memio_ctx test_ctx1, test_ctx2; + int group = WOLFSSL_ECC_SECP256R1; + byte ch1[TEST_MEMIO_BUF_SZ], ch2[TEST_MEMIO_BUF_SZ]; + int ch1Sz = (int)sizeof(ch1), ch2Sz = (int)sizeof(ch2); + int piece1 = 0, piece2 = 0; + int isHrr = 0; + + XMEMSET(&test_ctx1, 0, sizeof(test_ctx1)); + XMEMSET(&test_ctx2, 0, sizeof(test_ctx2)); + ExpectIntEQ(test_memio_setup(&test_ctx1, &ctx_c1, &ctx_s, &ssl_c1, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(test_memio_setup(&test_ctx2, &ctx_c2, NULL, &ssl_c2, NULL, + wolfDTLSv1_3_client_method, NULL), 0); + ExpectIntEQ(wolfSSL_set_groups(ssl_c1, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_c2, &group, 1), WOLFSSL_SUCCESS); + + /* Peer 1: CH1 and HRR */ + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx1, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_connect(ssl_c1), -1); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx1, ssl_c1, ch1, &ch1Sz), + TEST_SUCCESS); + /* The cookie fits the first fragment */ + piece1 = ch1Sz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ - 1; + + /* Peer 2: part of its first ClientHello */ + ExpectIntEQ(test_dtls13_take_ch(&test_ctx2, ssl_c2, ch2, &ch2Sz), + TEST_SUCCESS); + piece2 = (ch2Sz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + 2) / + 3; + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx2, 2), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx2, ch2, ch2Sz, piece2, 0), 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx2, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + ExpectNull(ssl_s->dtls_rx_msg_list); + + /* Peer 1's second ClientHello verifies from its first fragment */ + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx1, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx1, ch1, ch1Sz, piece1, 0), 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx1, ssl_s), TEST_SUCCESS); + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + ExpectIntEQ(ssl_s->dtls_rx_msg_list_sz, 1); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + ExpectNotNull(test_dtls13_held_for(&ctx_s->dtlsChPool, 2)); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx1, ch1, ch1Sz, piece1, 1), 1); + ExpectIntEQ(test_memio_do_handshake(ssl_c1, ssl_s, 10, NULL), 0); + ExpectNull(ssl_s->dtls_rx_msg_list); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + ExpectIntEQ(test_ctx2.c_len, 0); + + /* Another server of the context completes peer 2's ClientHello */ + ExpectNotNull(c2 = wolfSSL_new(ctx_s)); + ExpectIntEQ(test_dtls13_server_peer(c2, &test_ctx2, 2), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx2, ch2, ch2Sz, piece2, 1), 1); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx2, ch2, ch2Sz, piece2, 2), 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx2, c2), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx2, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 0); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + ExpectIntEQ(test_memio_do_handshake(ssl_c2, c2, 10, NULL), 0); + + wolfSSL_free(ssl_c1); + wolfSSL_free(ssl_c2); + wolfSSL_free(c2); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c1); + wolfSSL_CTX_free(ctx_c2); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* Listener c1 holds partial ClientHellos from A and B and turns stateful with + * A. B's stays with the context, and a second listener completes it. */ +int test_dtls13_frag_ch_ctx_two_listeners(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + WOLFSSL_CTX *ctx_cA = NULL, *ctx_cB = NULL, *ctx_s = NULL; + WOLFSSL *ssl_cA = NULL, *ssl_cB = NULL, *c1 = NULL, *c2 = NULL; + struct test_memio_ctx tA, tB; + int group = WOLFSSL_ECC_SECP256R1; + byte chA[TEST_MEMIO_BUF_SZ], chB[TEST_MEMIO_BUF_SZ]; + int chASz = (int)sizeof(chA), chBSz = (int)sizeof(chB); + const int hdrSz = DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ; + int pieceA = 0, pieceB = 0; + int isHrr = 0; + DtlsMsg* held = NULL; + + XMEMSET(&tA, 0, sizeof(tA)); + XMEMSET(&tB, 0, sizeof(tB)); + ExpectIntEQ(test_memio_setup(&tA, &ctx_cA, &ctx_s, &ssl_cA, &c1, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(test_memio_setup(&tB, &ctx_cB, NULL, &ssl_cB, NULL, + wolfDTLSv1_3_client_method, NULL), 0); + ExpectIntEQ(wolfSSL_set_groups(ssl_cA, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_cB, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&tA, ssl_cA, chA, &chASz), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&tB, ssl_cB, chB, &chBSz), TEST_SUCCESS); + pieceA = (chASz - hdrSz + 2) / 3; + pieceB = (chBSz - hdrSz + 2) / 3; + /* The pool allocates as the context's objects do, so a completed + * ClientHello is handed over without a copy */ + ExpectNotNull(ctx_s); + ExpectNotNull(c1); + if (EXPECT_SUCCESS()) + ExpectPtrEq(ctx_s->heap, c1->heap); + + /* A's first fragment and two of B's at c1 */ + ExpectIntEQ(test_dtls13_server_peer(c1, &tA, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&tA, chA, chASz, pieceA, 0), 1); + ExpectIntEQ(test_dtls13_server_read_all(&tA, c1), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_server_peer(c1, &tB, 2), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&tB, chB, chBSz, pieceB, 0), 1); + ExpectIntEQ(test_dtls13_inject_frag(&tB, chB, chBSz, pieceB, 1), 1); + ExpectIntEQ(test_dtls13_server_read_all(&tB, c1), TEST_SUCCESS); + ExpectIntEQ(tA.c_len, 0); + ExpectIntEQ(tB.c_len, 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 2); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + ExpectNull(c1->dtls_rx_msg_list); + + /* A completes and gets its HelloRetryRequest */ + ExpectIntEQ(test_dtls13_server_peer(c1, &tA, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&tA, chA, chASz, pieceA, 1), 1); + ExpectIntEQ(test_dtls13_inject_frag(&tA, chA, chASz, pieceA, 2), 1); + ExpectIntEQ(test_dtls13_server_read_all(&tA, c1), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&tA, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + + /* c1 serves A. B's partial stays with the context. */ + ExpectIntEQ(test_memio_do_handshake(ssl_cA, c1, 10, NULL), 0); + ExpectIntEQ(c1->options.dtlsStateful, 1); + ExpectIntEQ(tB.c_len, 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + ExpectNotNull(held = test_dtls13_held_for(&ctx_s->dtlsChPool, 2)); + ExpectIntEQ(held != NULL ? (int)held->bytesReceived : -1, 2 * pieceB); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + + /* A second listener completes B's ClientHello */ + ExpectNotNull(c2 = wolfSSL_new(ctx_s)); + ExpectIntEQ(test_dtls13_server_peer(c2, &tB, 2), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&tB, chB, chBSz, pieceB, 2), 1); + ExpectIntEQ(test_dtls13_server_read_all(&tB, c2), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&tB, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(c2->options.dtlsStateful, 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 0); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + ExpectIntEQ(test_memio_do_handshake(ssl_cB, c2, 10, NULL), 0); + + wolfSSL_free(c2); + wolfSSL_free(c1); + wolfSSL_free(ssl_cA); + wolfSSL_free(ssl_cB); + wolfSSL_CTX_free(ctx_s); + wolfSSL_CTX_free(ctx_cA); + wolfSSL_CTX_free(ctx_cB); +#endif + return EXPECT_RESULT(); +} + +/* A partial ClientHello held by the context outlives the server that stored + * it, and a later server of the context completes it. */ +int test_dtls13_frag_ch_ctx_survives_listener_free(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + WOLFSSL_CTX *ctx_cB = NULL, *ctx_s = NULL; + WOLFSSL *ssl_cB = NULL, *c1 = NULL, *c2 = NULL; + struct test_memio_ctx tB; + int group = WOLFSSL_ECC_SECP256R1; + byte chB[TEST_MEMIO_BUF_SZ]; + int chBSz = (int)sizeof(chB); + int pieceB = 0; + int isHrr = 0; + + XMEMSET(&tB, 0, sizeof(tB)); + ExpectIntEQ(test_memio_setup(&tB, &ctx_cB, &ctx_s, &ssl_cB, &c1, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_set_groups(ssl_cB, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&tB, ssl_cB, chB, &chBSz), TEST_SUCCESS); + pieceB = (chBSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + 2) / + 3; + + ExpectIntEQ(test_dtls13_server_peer(c1, &tB, 2), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&tB, chB, chBSz, pieceB, 0), 1); + ExpectIntEQ(test_dtls13_inject_frag(&tB, chB, chBSz, pieceB, 1), 1); + ExpectIntEQ(test_dtls13_server_read_all(&tB, c1), TEST_SUCCESS); + ExpectIntEQ(tB.c_len, 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + + wolfSSL_free(c1); + c1 = NULL; + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + ExpectNotNull(test_dtls13_held_for(&ctx_s->dtlsChPool, 2)); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + + ExpectNotNull(c2 = wolfSSL_new(ctx_s)); + ExpectIntEQ(test_dtls13_server_peer(c2, &tB, 2), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&tB, chB, chBSz, pieceB, 2), 1); + ExpectIntEQ(test_dtls13_server_read_all(&tB, c2), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&tB, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(c2->options.dtlsStateful, 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 0); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + ExpectIntEQ(test_memio_do_handshake(ssl_cB, c2, 10, NULL), 0); + + wolfSSL_free(c2); + wolfSSL_free(ssl_cB); + wolfSSL_CTX_free(ctx_s); + wolfSSL_CTX_free(ctx_cB); +#endif + return EXPECT_RESULT(); +} + +/* The application drops its context reference while a server holds it: the + * server still stores in the pool, freed with the context when it goes. */ +int test_dtls13_frag_ch_ctx_free_pending(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + int piece = 0; + int peer; + + 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_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + piece = (chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + 1) / 2; + + for (peer = 1; peer <= 3 && EXPECT_SUCCESS(); peer++) { + if (peer == 3) { + /* Only the server's reference is left */ + wolfSSL_CTX_free(ctx_s); + ctx_s = NULL; + } + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, peer), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx, ch, chSz, piece, 0), 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), + TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), peer); + ExpectTrue(test_dtls13_pool_ok(test_dtls13_ch_pool(ssl_s))); + } + + /* The last reference: the context and its entries go */ + wolfSSL_free(ssl_s); + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_s); + wolfSSL_CTX_free(ctx_c); +#endif + return EXPECT_RESULT(); +} + +/* The budget is shared by every server of the context: a store through one + * evicts an entry another stored, and either completes any entry. */ +int test_dtls13_frag_ch1_ctx_shared_budget(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *c1 = NULL, *c2 = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + int fragSz; + int peer; + int capacity; + int isHrr = 0; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &c1, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectNotNull(c2 = wolfSSL_new(ctx_s)); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + /* two fragments */ + fragSz = (chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + 1) / 2; + capacity = (int)(TEST_DTLS_RX_UNVERIFIED_LIMIT / + (sizeof(DtlsMsg) + sizeof(int) + sizeof(DtlsFragBucket) + fragSz)); + ExpectIntGT(capacity, 1); + + /* Odd peers through c1, even ones and the last through c2 */ + for (peer = 1; peer <= capacity + 1 && EXPECT_SUCCESS(); peer++) { + WOLFSSL* s = (peer % 2 == 1 && peer <= capacity) ? c1 : c2; + ExpectIntEQ(test_dtls13_server_peer(s, &test_ctx, peer), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx, ch, chSz, fragSz, 0), + 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, s), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + } + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), capacity); + /* Stored through c1, evicted by c2's store */ + ExpectNull(test_dtls13_held_for(&ctx_s->dtlsChPool, 1)); + ExpectNotNull(test_dtls13_held_for(&ctx_s->dtlsChPool, capacity + 1)); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + ExpectNull(c1->dtls_rx_msg_list); + ExpectNull(c2->dtls_rx_msg_list); + + /* c1 completes what c2 started */ + ExpectIntEQ(test_dtls13_server_peer(c1, &test_ctx, capacity + 1), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx, ch, chSz, fragSz, 1), 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, c1), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectNull(test_dtls13_held_for(&ctx_s->dtlsChPool, capacity + 1)); + ExpectIntLE(test_dtls13_pool_count(&ctx_s->dtlsChPool), capacity - 1); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + + wolfSSL_free(c1); + wolfSSL_free(c2); + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_s); + wolfSSL_CTX_free(ctx_c); +#endif + return EXPECT_RESULT(); +} + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) +/* Deliver body bytes [off, off + len) of the message in rec to server s as a + * record of its own and run s until it has read it */ +static int test_dtls13_deliver_range(struct test_memio_ctx* test_ctx, + WOLFSSL* s, const byte* rec, int recSz, word32 off, word32 len, + word32 recSeqAdd) +{ + EXPECT_DECLS; + byte frag[TEST_MEMIO_BUF_SZ]; + int fragSz = 0; + + ExpectIntGT(fragSz = test_dtls13_frag_range(rec, recSz, off, len, + recSeqAdd, frag, (int)sizeof(frag)), 0); + ExpectIntEQ(test_memio_inject_message(test_ctx, 0, (const char*)frag, + fragSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(test_ctx, s), TEST_SUCCESS); + return EXPECT_RESULT(); +} +#endif + +/* A server without the peer's address holds nothing before the cookie + * verifies: it drops fragments silently and answers whole ClientHellos. */ +int test_dtls13_frag_ch_no_addr_dropped(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL, *c2 = NULL, *c3 = NULL; + struct test_memio_ctx test_ctx, t2; + int group = WOLFSSL_ECC_SECP256R1; + byte ch1[TEST_MEMIO_BUF_SZ], ch2[TEST_MEMIO_BUF_SZ]; + int ch1Sz = (int)sizeof(ch1), ch2Sz = (int)sizeof(ch2); + int bodySz = 0; + int cookieEnd = 0; + int piece = 0; + int i; + int isHrr = 0; + DtlsChPool* pool = NULL; + DtlsMsg* head = NULL; + size_t bytes = 0; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(&t2, 0, sizeof(t2)); + 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); + ExpectNotNull(pool = test_dtls13_ch_pool(ssl_s)); + /* Memio reports no address */ + ExpectIntEQ(ssl_s->buffers.dtlsCtx.peer.sz, 0); + + /* The first ClientHello in three pieces, then the canned one in four: + * every fragment is dropped */ + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch1, &ch1Sz), + TEST_SUCCESS); + piece = (ch1Sz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + 2) / 3; + ExpectIntEQ(test_dtls13_inject_fragmented(&test_ctx, ch1, ch1Sz, piece), + 3); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(test_ctx.s_len, 0); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectTrue(test_dtls13_nothing_held(ssl_s)); + ExpectIntEQ(test_dtls13_inject_four_frag_ch(&test_ctx, ssl_s), + TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(test_ctx.s_len, 0); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectTrue(test_dtls13_nothing_held(ssl_s)); + + /* The whole first ClientHello gets a HelloRetryRequest */ + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)ch1, + ch1Sz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectTrue(test_dtls13_nothing_held(ssl_s)); + + /* The second ClientHello split just past its cookie */ + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch2, &ch2Sz), + TEST_SUCCESS); + bodySz = ch2Sz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ; + ExpectIntGT(cookieEnd = test_dtls13_ch_cookie_end(ch2, ch2Sz), 0); + ExpectIntGT(bodySz - cookieEnd, 0); + + /* The tail first: dropped */ + ExpectIntEQ(test_dtls13_deliver_range(&test_ctx, ssl_s, ch2, ch2Sz, + (word32)cookieEnd, (word32)(bodySz - cookieEnd), 1), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectTrue(test_dtls13_nothing_held(ssl_s)); + + /* The first fragment verifies at once. The server holds it as a message + * of the connection, not in the pool. */ + ExpectIntEQ(test_dtls13_deliver_range(&test_ctx, ssl_s, ch2, ch2Sz, 0, + (word32)cookieEnd, 0), TEST_SUCCESS); + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + ExpectNotNull(ssl_s->dtls_rx_msg_list); + ExpectIntEQ(test_dtls13_pool_count(pool), 0); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 0); + test_memio_clear_buffer(&test_ctx, 1); + + /* The tail again, in a new record, completes it */ + ExpectIntEQ(test_dtls13_deliver_range(&test_ctx, ssl_s, ch2, ch2Sz, + (word32)cookieEnd, (word32)(bodySz - cookieEnd), 2), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 0); + ExpectTrue(test_dtls13_nothing_held(ssl_s)); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* Another server of the context holds a fragment for peer 7. One without + * an address adds nothing next to it. */ + ExpectNotNull(c2 = wolfSSL_new(ctx_s)); + ExpectIntEQ(test_dtls13_server_peer(c2, &t2, 7), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&t2, ch1, ch1Sz, piece, 0), 1); + ExpectIntEQ(test_dtls13_server_read_all(&t2, c2), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_pool_count(pool), 1); + ExpectNotNull(head = test_dtls13_pool_head(pool)); + bytes = test_dtls13_pool_bytes(pool); + + ExpectNotNull(c3 = wolfSSL_new(ctx_s)); + wolfSSL_SetIOReadCtx(c3, &t2); + wolfSSL_SetIOWriteCtx(c3, &t2); + for (i = 0; i < 3 && EXPECT_SUCCESS(); i++) { + ExpectIntEQ(test_dtls13_inject_frag(&t2, ch1, ch1Sz, piece, i), 1); + ExpectIntEQ(test_dtls13_server_read_all(&t2, c3), TEST_SUCCESS); + ExpectIntEQ(t2.c_len, 0); + ExpectIntEQ(c3->options.dtlsStateful, 0); + ExpectNull(c3->dtls_rx_msg_list); + ExpectIntEQ(test_dtls13_pool_count(pool), 1); + ExpectPtrEq(test_dtls13_pool_head(pool), head); + ExpectIntEQ(test_dtls13_pool_bytes(pool), bytes); + ExpectTrue(test_dtls13_pool_ok(pool)); + } + + wolfSSL_free(c3); + wolfSSL_free(c2); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* The budget bounds all that a context's servers hold. c1 stores odd peers, + * c2 even, c3 has no address: eviction crosses servers, never the growing + * entry, a replaced entry counts once, and c3 adds nothing. */ +int test_dtls13_frag_ch1_ctx_budget_two_objects(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *c1 = NULL, *c2 = NULL, *c3 = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + byte frag[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + int fragSz = 0; + int bodySz = 0; + /* Small first fragments: many entries, each smaller than the growth */ + const int first = 16; + int grow = 0; + size_t entry = sizeof(DtlsMsg) + sizeof(int) + sizeof(DtlsFragBucket) + + (size_t)first; + size_t bytes = 0; + int capacity = 0; + int evicted = 0; + int behind = 0; + int count = 0; + int peer; + word32 msgLen = 0; + DtlsChPool* pool = NULL; + DtlsMsg* head = NULL; + DtlsMsg* m = NULL; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &c1, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectNotNull(c2 = wolfSSL_new(ctx_s)); + ExpectNotNull(c3 = wolfSSL_new(ctx_s)); + wolfSSL_SetIOReadCtx(c3, &test_ctx); + wolfSSL_SetIOWriteCtx(c3, &test_ctx); + ExpectNotNull(pool = test_dtls13_ch_pool(c1)); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + bodySz = chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ; + /* All but the last byte: the entry grows but does not complete */ + grow = bodySz - first - 1; + capacity = (int)(TEST_DTLS_RX_UNVERIFIED_LIMIT / entry); + ExpectIntGT(capacity, 4); + /* What is left of the budget once full is less than one entry, so the + * growth must evict */ + ExpectIntGT(grow, (int)entry); + + /* Fill the budget: odd peers through c1, even ones through c2 */ + for (peer = 1; peer <= capacity && EXPECT_SUCCESS(); peer++) { + WOLFSSL* s = (peer % 2 == 1) ? c1 : c2; + + ExpectIntEQ(test_dtls13_server_peer(s, &test_ctx, peer), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_deliver_range(&test_ctx, s, ch, chSz, 0, + (word32)first, 0), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(test_dtls13_pool_count(pool), peer); + ExpectTrue(test_dtls13_pool_bytes(pool) == (size_t)peer * entry); + ExpectTrue(test_dtls13_pool_ok(pool)); + } + + /* c2's store evicts the oldest entry, which c1 stored */ + ExpectIntEQ(test_dtls13_server_peer(c2, &test_ctx, capacity + 1), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_deliver_range(&test_ctx, c2, ch, chSz, 0, + (word32)first, 0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_pool_count(pool), capacity); + ExpectTrue(test_dtls13_pool_bytes(pool) == (size_t)capacity * entry); + ExpectNull(test_dtls13_held_for(pool, 1)); + ExpectNotNull(test_dtls13_held_for(pool, capacity + 1)); + ExpectTrue(test_dtls13_pool_ok(pool)); + + /* c1 grows the oldest entry, peer 2's from c2, without completing it. It + * evicts the oldest other entries, never itself, and goes to the tail. + * It evicts for the bound of its fragment: a new entry holding it. */ + evicted = (int)(((size_t)capacity * entry + entry - (size_t)first + + (size_t)grow - TEST_DTLS_RX_UNVERIFIED_LIMIT + entry - 1) / entry); + ExpectIntGE(evicted, 1); + ExpectIntLE(evicted, capacity - 2); + ExpectPtrEq(test_dtls13_pool_head(pool), + test_dtls13_held_for(pool, 2)); + ExpectIntEQ(test_dtls13_server_peer(c1, &test_ctx, 2), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_deliver_range(&test_ctx, c1, ch, chSz, + (word32)first, (word32)grow, 0), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + count = capacity - evicted; + ExpectIntEQ(test_dtls13_pool_count(pool), count); + ExpectNotNull(m = test_dtls13_held_for(pool, 2)); + ExpectIntEQ(m != NULL ? (int)m->bytesReceived : -1, first + grow); + ExpectIntEQ(m != NULL ? (int)m->fragBucketListCount : -1, 1); + ExpectNull(test_dtls13_pool_next(pool, m)); + /* The entry behind peer 2 in its shard goes first, as the scan lands on + * peer 2's own, then the oldest of the rest */ + evicted--; + for (peer = 3, head = NULL; peer <= capacity + 1 && EXPECT_SUCCESS(); + peer++) { + if (!behind && (WOLFSSL_DTLS_CH_POOL_SHARDS == 1 || + test_dtls13_shard_of(peer) == test_dtls13_shard_of(2))) { + ExpectNull(test_dtls13_held_for(pool, peer)); + behind = 1; + } + else if (evicted > 0) { + ExpectNull(test_dtls13_held_for(pool, peer)); + evicted--; + } + else { + ExpectNotNull(test_dtls13_held_for(pool, peer)); + if (head == NULL) + head = test_dtls13_held_for(pool, peer); + } + } + ExpectIntEQ(behind, 1); + ExpectPtrEq(test_dtls13_pool_head(pool), head); + ExpectTrue(pool != NULL && + test_dtls13_pool_bytes(pool) == (size_t)count * entry + + (size_t)grow); + ExpectTrue(test_dtls13_pool_ok(pool)); + + /* A fragment contradicting peer capacity + 1's entry, through c1, + * replaces it. The bytes are counted once, but the bound of the new + * entry is reserved before the old one is freed under the lock: at + * capacity the oldest entry goes for it. */ + bytes = test_dtls13_pool_bytes(pool); + if (TEST_DTLS_RX_UNVERIFIED_LIMIT - bytes < entry) { + count--; + bytes -= entry; + } + ExpectIntGT(fragSz = test_dtls13_frag_range(ch, chSz, 0, (word32)first, 0, + frag, (int)sizeof(frag)), 0); + if (EXPECT_SUCCESS()) { + ato24(frag + DTLS_RECORD_HEADER_SZ + 1, &msgLen); + c32to24(msgLen + 1, frag + DTLS_RECORD_HEADER_SZ + 1); + } + ExpectIntEQ(test_dtls13_server_peer(c1, &test_ctx, capacity + 1), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)frag, + fragSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, c1), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(test_dtls13_pool_count(pool), count); + ExpectTrue(test_dtls13_pool_bytes(pool) == bytes); + ExpectNotNull(m = test_dtls13_held_for(pool, capacity + 1)); + ExpectIntEQ(m != NULL ? (int)m->sz : -1, (int)msgLen + 1); + ExpectNull(test_dtls13_pool_next(pool, m)); + ExpectTrue(test_dtls13_pool_ok(pool)); + + /* c3 has no address: even all of a ClientHello adds nothing */ + head = test_dtls13_pool_head(pool); + ExpectIntEQ(test_dtls13_deliver_range(&test_ctx, c3, ch, chSz, 0, + (word32)first, 0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_deliver_range(&test_ctx, c3, ch, chSz, + (word32)first, (word32)(bodySz - first), 1), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(c3->options.dtlsStateful, 0); + ExpectIntEQ(test_dtls13_pool_count(pool), count); + ExpectPtrEq(test_dtls13_pool_head(pool), head); + ExpectTrue(test_dtls13_pool_bytes(pool) == bytes); + ExpectTrue(test_dtls13_pool_ok(pool)); + + /* Nothing unverified is held anywhere else */ + ExpectNull(c1->dtls_rx_msg_list); + ExpectNull(c2->dtls_rx_msg_list); + ExpectNull(c3->dtls_rx_msg_list); + + wolfSSL_free(c1); + wolfSSL_free(c2); + wolfSSL_free(c3); + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_s); + wolfSSL_CTX_free(ctx_c); +#endif + return EXPECT_RESULT(); +} + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) && defined(WOLF_CRYPTO_CB) && \ + !defined(WOLF_CRYPTO_CB_ONLY_RSA) && !defined(WOLF_CRYPTO_CB_ONLY_ECC) && \ + !defined(WOLF_CRYPTO_CB_ONLY_SHA256) && \ + !defined(WOLF_CRYPTO_CB_ONLY_AES) && \ + !defined(WOLF_CRYPTO_CB_ONLY_SHA512) && \ + !defined(WOLF_CRYPTO_CB_ONLY_ED25519) && \ + !defined(WOLF_CRYPTO_CB_ONLY_CURVE25519) && \ + !defined(WOLF_CRYPTO_CB_ONLY_MLKEM) +#define TEST_DTLS13_CH_CB_DEVID 0x4443 +/* A device that does nothing: wolfCrypt falls back to software */ +static int test_dtls13_ch_unavailable_cb(int devId, wc_CryptoInfo* info, + void* ctx) +{ + (void)devId; + (void)info; + (void)ctx; + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); +} +#endif + +/* With a crypto device a whole ClientHello is stored before processing, but + * never through the pool: a full pool neither drops it nor loses an entry. */ +int test_dtls13_ch_whole_async_not_pooled(void) +{ + EXPECT_DECLS; +/* The callback leaves every operation to software, which a callback-only + * build does not have. */ +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) && defined(WOLF_CRYPTO_CB) && \ + !defined(WOLF_CRYPTO_CB_ONLY_RSA) && !defined(WOLF_CRYPTO_CB_ONLY_ECC) && \ + !defined(WOLF_CRYPTO_CB_ONLY_SHA256) && \ + !defined(WOLF_CRYPTO_CB_ONLY_AES) && \ + !defined(WOLF_CRYPTO_CB_ONLY_SHA512) && \ + !defined(WOLF_CRYPTO_CB_ONLY_ED25519) && \ + !defined(WOLF_CRYPTO_CB_ONLY_CURVE25519) && \ + !defined(WOLF_CRYPTO_CB_ONLY_MLKEM) + int addr; + + ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_DTLS13_CH_CB_DEVID, + test_dtls13_ch_unavailable_cb, NULL), 0); + for (addr = 0; addr < 2 && EXPECT_SUCCESS(); addr++) { + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL, *filler = NULL; + struct test_memio_ctx test_ctx, tf; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + int fragSz = 0; + int capacity = 0; + int peer; + int isHrr = 0; + DtlsChPool* pool = NULL; + DtlsMsg* head = NULL; + size_t bytes = 0; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(&tf, 0, sizeof(tf)); + 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_SetDevId(ssl_s, TEST_DTLS13_CH_CB_DEVID), + WOLFSSL_SUCCESS); + if (addr) { + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, 1000), + WOLFSSL_SUCCESS); + } + ExpectNotNull(pool = test_dtls13_ch_pool(ssl_s)); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + + /* Another server fills the pool */ + ExpectNotNull(filler = wolfSSL_new(ctx_s)); + fragSz = (chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + + 1) / 2; + capacity = (int)(TEST_DTLS_RX_UNVERIFIED_LIMIT / (sizeof(DtlsMsg) + + sizeof(int) + sizeof(DtlsFragBucket) + (size_t)fragSz)); + for (peer = 1; peer <= capacity && EXPECT_SUCCESS(); peer++) { + ExpectIntEQ(test_dtls13_server_peer(filler, &tf, peer), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&tf, ch, chSz, fragSz, 0), 1); + ExpectIntEQ(test_dtls13_server_read_all(&tf, filler), + TEST_SUCCESS); + ExpectIntEQ(tf.c_len, 0); + } + ExpectIntEQ(test_dtls13_pool_count(pool), capacity); + head = test_dtls13_pool_head(pool); + bytes = test_dtls13_pool_bytes(pool); + + /* The whole first ClientHello is answered */ + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)ch, + chSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectNull(ssl_s->dtls_rx_msg_list); + ExpectIntEQ(test_dtls13_pool_count(pool), capacity); + ExpectPtrEq(test_dtls13_pool_head(pool), head); + ExpectTrue(test_dtls13_pool_bytes(pool) == bytes); + + /* and so is the second */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(test_dtls13_pool_count(pool), capacity); + ExpectPtrEq(test_dtls13_pool_head(pool), head); + ExpectTrue(test_dtls13_pool_bytes(pool) == bytes); + ExpectTrue(test_dtls13_pool_ok(pool)); + + wolfSSL_free(filler); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + } + wc_CryptoCb_UnRegisterDevice(TEST_DTLS13_CH_CB_DEVID); +#endif + return EXPECT_RESULT(); +} + +/* No peer address, asynchronous device: a whole ClientHello left pending + * after its cookie verifies resumes as a message of the connection. */ +int test_dtls13_ch_async_no_addr(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) && \ + defined(WOLFSSL_ASYNC_CRYPT) && defined(WOLFSSL_ASYNC_CRYPT_SW) && \ + !defined(WC_ECC_NONBLOCK) && !defined(WOLFSSL_SP_NONBLOCK) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + int devId = INVALID_DEVID; + + ExpectIntGE(wolfAsync_DevOpen(&devId), 0); + 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_SetDevId(ssl_s, devId), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(ssl_s->buffers.dtlsCtx.peer.sz, 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), 0); + ExpectNull(ssl_s->dtls_rx_msg_list); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + wolfAsync_DevClose(&devId); +#endif + return EXPECT_RESULT(); +} + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) +/* B's second ClientHello split after its cookie: the later fragments reach + * c1 while it serves A, the first fragment a new listener c2. sharedSecret + * gives c2 c1's cookie secret; mode 1 turns holding before the cookie off. */ +static int test_dtls13_frag_ch2_ctx_second(int sharedSecret, int mode) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_cA = NULL, *ctx_cB = NULL, *ctx_cD = NULL, *ctx_s = NULL; + WOLFSSL *ssl_cA = NULL, *ssl_cB = NULL, *ssl_cD = NULL; + WOLFSSL *c1 = NULL, *c2 = NULL; + struct test_memio_ctx tA, tB, tD; + int group = WOLFSSL_ECC_SECP256R1; + static const byte secret[32] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, + 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20 + }; + byte chB[TEST_MEMIO_BUF_SZ], chD[TEST_MEMIO_BUF_SZ]; + int chBSz = (int)sizeof(chB), chDSz = (int)sizeof(chD); + byte frag[TEST_MEMIO_BUF_SZ]; + int fragSz = 0; + int off[3] = { 0, 0, 0 }; + int len[3] = { 0, 0, 0 }; + int bodySz = 0; + int pieceD = 0; + int i; + int isHrr = 1; + + XMEMSET(&tA, 0, sizeof(tA)); + XMEMSET(&tB, 0, sizeof(tB)); + XMEMSET(&tD, 0, sizeof(tD)); + ExpectIntEQ(test_memio_setup(&tA, &ctx_cA, &ctx_s, &ssl_cA, &c1, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(test_memio_setup(&tB, &ctx_cB, NULL, &ssl_cB, NULL, + wolfDTLSv1_3_client_method, NULL), 0); + ExpectIntEQ(wolfSSL_set_groups(ssl_cA, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_cB, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_send_hrr_cookie(c1, secret, sizeof(secret)), + WOLFSSL_SUCCESS); + + /* B's first ClientHello at c1 gets a HelloRetryRequest */ + ExpectIntEQ(test_dtls13_server_peer(c1, &tB, 2), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_connect(ssl_cB), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_cB, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(c1), -1); + ExpectIntEQ(wolfSSL_get_error(c1, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&tB, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(test_dtls13_take_ch(&tB, ssl_cB, chB, &chBSz), TEST_SUCCESS); + + /* Three fragments: the first ends just past the cookie extension */ + bodySz = chBSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ; + ExpectIntGT(len[0] = test_dtls13_ch_cookie_end(chB, chBSz), 0); + ExpectIntGE(bodySz - len[0], 2); + off[1] = len[0]; + len[1] = (bodySz - len[0]) / 2; + off[2] = off[1] + len[1]; + len[2] = bodySz - off[2]; + + /* The later fragments are held by the context */ + for (i = 1; i < 3 && EXPECT_SUCCESS(); i++) { + ExpectIntGT(fragSz = test_dtls13_frag_range(chB, chBSz, + (word32)off[i], (word32)len[i], (word32)i, frag, + (int)sizeof(frag)), 0); + ExpectIntEQ(test_memio_inject_message(&tB, 0, (const char*)frag, + fragSz), 0); + } + ExpectIntEQ(test_dtls13_server_read_all(&tB, c1), TEST_SUCCESS); + ExpectIntEQ(tB.c_len, 0); + ExpectIntEQ(c1->options.dtlsStateful, 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + + /* c1 serves A */ + ExpectIntEQ(test_dtls13_server_peer(c1, &tA, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_cA, c1, 10, NULL), 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + + ExpectNotNull(c2 = wolfSSL_new(ctx_s)); + if (sharedSecret) { + ExpectIntEQ(wolfSSL_send_hrr_cookie(c2, secret, sizeof(secret)), + WOLFSSL_SUCCESS); + } + if (mode == 1) { + /* An object with reassembly off adds nothing to the pool */ + ExpectIntEQ(wolfSSL_dtls13_reassemble_ch_before_cookie(c2, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_server_peer(c2, &tB, 9), WOLFSSL_SUCCESS); + ExpectIntGT(fragSz = test_dtls13_frag_range(chB, chBSz, + (word32)off[1], (word32)len[1], 1, frag, (int)sizeof(frag)), 0); + ExpectIntEQ(test_memio_inject_message(&tB, 0, (const char*)frag, + fragSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&tB, c2), TEST_SUCCESS); + ExpectIntEQ(tB.c_len, 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + ExpectNull(test_dtls13_held_for(&ctx_s->dtlsChPool, 9)); + } + + /* B's first fragment reaches c2 */ + ExpectIntEQ(test_dtls13_server_peer(c2, &tB, 2), WOLFSSL_SUCCESS); + ExpectIntGT(fragSz = test_dtls13_frag_range(chB, chBSz, (word32)off[0], + (word32)len[0], 0, frag, (int)sizeof(frag)), 0); + ExpectIntEQ(test_memio_inject_message(&tB, 0, (const char*)frag, fragSz), + 0); + if (sharedSecret) { + /* The cookie verifies and the held rest completes the message */ + ExpectIntEQ(test_dtls13_server_read_all(&tB, c2), TEST_SUCCESS); + ExpectIntEQ(c2->options.dtlsStateful, 1); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&tB, &isHrr), 1); + ExpectIntEQ(isHrr, 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 0); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + ExpectNull(c2->dtls_rx_msg_list); + ExpectIntEQ(test_memio_do_handshake(ssl_cB, c2, 10, NULL), 0); + } + else { + /* c2 cannot verify c1's cookie: a fatal alert, and c2 stays a + * stateless server */ + ExpectIntEQ(test_dtls13_server_read_all(&tB, c2), TEST_SUCCESS); + ExpectIntGT(tB.c_len, DTLS_RECORD_HEADER_SZ + 1); + ExpectIntEQ(tB.c_buff[0], alert); + ExpectIntEQ(tB.c_buff[DTLS_RECORD_HEADER_SZ], alert_fatal); + ExpectIntEQ(tB.c_buff[DTLS_RECORD_HEADER_SZ + 1], illegal_parameter); + ExpectIntEQ(c2->options.dtlsStateful, 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 0); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + ExpectNull(c2->dtls_rx_msg_list); + test_memio_clear_buffer(&tB, 1); + + /* It still serves: client D's fragmented first ClientHello */ + ExpectIntEQ(test_memio_setup(&tD, &ctx_cD, NULL, &ssl_cD, NULL, + wolfDTLSv1_3_client_method, NULL), 0); + ExpectIntEQ(wolfSSL_set_groups(ssl_cD, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&tD, ssl_cD, chD, &chDSz), + TEST_SUCCESS); + pieceD = (chDSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + + 2) / 3; + ExpectIntEQ(test_dtls13_server_peer(c2, &tD, 4), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_fragmented(&tD, chD, chDSz, pieceD), + 3); + ExpectIntEQ(test_dtls13_server_read_all(&tD, c2), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&tD, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_cD, c2, 10, NULL), 0); + } + + wolfSSL_free(c1); + wolfSSL_free(c2); + wolfSSL_free(ssl_cA); + wolfSSL_free(ssl_cB); + wolfSSL_free(ssl_cD); + wolfSSL_CTX_free(ctx_s); + wolfSSL_CTX_free(ctx_cA); + wolfSSL_CTX_free(ctx_cB); + wolfSSL_CTX_free(ctx_cD); + return EXPECT_RESULT(); +} +#endif + +/* The rest of a second ClientHello held by the context is taken by the server + * that verifies its first fragment, when the servers share the secret. */ +int test_dtls13_frag_ch2_ctx_handoff(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + ExpectIntEQ(test_dtls13_frag_ch2_ctx_second(1, 0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch2_ctx_second(1, 1), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +/* A second ClientHello completed by a server with a different cookie secret + * gets an illegal_parameter alert; the pool stays clean, the server goes on. */ +int test_dtls13_frag_ch2_ctx_cookie_mismatch(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + ExpectIntEQ(test_dtls13_frag_ch2_ctx_second(0, 0), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +/* A server moved to another context stores in the new context's pool. The + * old context's entries stay with it and go when it is freed. */ +int test_dtls13_frag_ch_ctx_set_ssl_ctx(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) && \ + (defined(OPENSSL_ALL) || defined(OPENSSL_EXTRA)) + WOLFSSL_CTX *ctx_cB = NULL, *ctx_s = NULL, *ctx_s2 = NULL; + WOLFSSL *ssl_cB = NULL, *c1 = NULL; + struct test_memio_ctx tB; + int group = WOLFSSL_ECC_SECP256R1; + byte chB[TEST_MEMIO_BUF_SZ]; + int chBSz = (int)sizeof(chB); + int pieceB = 0; + + XMEMSET(&tB, 0, sizeof(tB)); + ExpectIntEQ(test_memio_setup(&tB, &ctx_cB, &ctx_s, &ssl_cB, &c1, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(test_memio_setup(&tB, NULL, &ctx_s2, NULL, NULL, + NULL, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_set_groups(ssl_cB, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&tB, ssl_cB, chB, &chBSz), TEST_SUCCESS); + pieceB = (chBSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + 2) / + 3; + + ExpectIntEQ(test_dtls13_server_peer(c1, &tB, 2), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&tB, chB, chBSz, pieceB, 0), 1); + ExpectIntEQ(test_dtls13_server_read_all(&tB, c1), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + + ExpectPtrEq(wolfSSL_set_SSL_CTX(c1, ctx_s2), ctx_s2); + ExpectIntEQ(test_dtls13_inject_frag(&tB, chB, chBSz, pieceB, 1), 1); + ExpectIntEQ(test_dtls13_server_read_all(&tB, c1), TEST_SUCCESS); + ExpectIntEQ(tB.c_len, 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s2->dtlsChPool), 1); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + ExpectTrue(test_dtls13_pool_ok(&ctx_s2->dtlsChPool)); + + /* The old context has no other user: its pool goes with it */ + wolfSSL_CTX_free(ctx_s); + ctx_s = NULL; + ExpectIntEQ(test_dtls13_server_peer(c1, &tB, 3), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&tB, chB, chBSz, pieceB, 0), 1); + ExpectIntEQ(test_dtls13_server_read_all(&tB, c1), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s2->dtlsChPool), 2); + ExpectTrue(test_dtls13_pool_ok(&ctx_s2->dtlsChPool)); + + wolfSSL_free(c1); + wolfSSL_free(ssl_cB); + wolfSSL_CTX_free(ctx_s); + wolfSSL_CTX_free(ctx_s2); + wolfSSL_CTX_free(ctx_cB); +#endif + return EXPECT_RESULT(); +} + +/* An abandoned partial ClientHello outlives its server. B starts over from the + * same address with one of the same size. mode 0: the new first fragment + * replaces the old one. mode 1, 2: the old entry aged out and never merges. */ +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) +static int test_dtls13_frag_ch1_ctx_stale_partial_ex(int mode) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_cA = NULL, *ctx_cB = NULL, *ctx_s = NULL; + WOLFSSL *ssl_cA = NULL, *ssl_cB = NULL, *c1 = NULL, *c2 = NULL; + struct test_memio_ctx tA, tB; + int group = WOLFSSL_ECC_SECP256R1; + byte chOld[TEST_MEMIO_BUF_SZ], chNew[TEST_MEMIO_BUF_SZ]; + int chOldSz = (int)sizeof(chOld), chNewSz = (int)sizeof(chNew); + int piece = 0; + int isHrr = 0; + int i; + /* The fragments of the new ClientHello in the order they arrive. Its + * handshake must not be answered before the last one. */ + static const int order[3][3] = { { 0, 1, 2 }, { 1, 2, 0 }, { 0, 1, 2 } }; + + XMEMSET(&tA, 0, sizeof(tA)); + XMEMSET(&tB, 0, sizeof(tB)); + ExpectIntEQ(test_memio_setup(&tA, &ctx_cA, &ctx_s, &ssl_cA, &c1, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(test_memio_setup(&tB, &ctx_cB, NULL, &ssl_cB, NULL, + wolfDTLSv1_3_client_method, NULL), 0); + ExpectIntEQ(wolfSSL_set_groups(ssl_cB, &group, 1), WOLFSSL_SUCCESS); +#ifdef HAVE_ECH + /* GREASE ECH has a random length */ + wolfSSL_SetEchEnable(ssl_cB, 0); +#endif + ExpectIntEQ(test_dtls13_take_ch(&tB, ssl_cB, chOld, &chOldSz), + TEST_SUCCESS); + piece = (chOldSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + 2) / + 3; + + /* Some of B's fragments arrive, then B gives up */ + ExpectIntEQ(test_dtls13_server_peer(c1, &tB, 2), WOLFSSL_SUCCESS); + if (mode == 2) { + ExpectIntEQ(test_dtls13_inject_frag(&tB, chOld, chOldSz, piece, 1), 1); + ExpectIntEQ(test_dtls13_inject_frag(&tB, chOld, chOldSz, piece, 2), 1); + } + else { + ExpectIntEQ(test_dtls13_inject_frag(&tB, chOld, chOldSz, piece, 0), 1); + } + ExpectIntEQ(test_dtls13_server_read_all(&tB, c1), TEST_SUCCESS); + ExpectIntEQ(tB.c_len, 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + wolfSSL_free(ssl_cB); + ssl_cB = NULL; + + /* A's cookie verifies at c1, which then goes */ + ExpectIntEQ(test_dtls13_server_peer(c1, &tA, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_cA, c1, 10, NULL), 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + wolfSSL_free(c1); + c1 = NULL; + if (mode != 0) + test_dtls13_age(test_dtls13_held_for(&ctx_s->dtlsChPool, 2)); + + /* B starts over from the same address */ + test_memio_clear_buffer(&tB, 0); + test_memio_clear_buffer(&tB, 1); + ExpectIntEQ(test_memio_setup(&tB, &ctx_cB, NULL, &ssl_cB, NULL, + wolfDTLSv1_3_client_method, NULL), 0); + ExpectIntEQ(wolfSSL_set_groups(ssl_cB, &group, 1), WOLFSSL_SUCCESS); +#ifdef HAVE_ECH + wolfSSL_SetEchEnable(ssl_cB, 0); +#endif + ExpectIntEQ(test_dtls13_take_ch(&tB, ssl_cB, chNew, &chNewSz), + TEST_SUCCESS); + ExpectIntEQ(chNewSz, chOldSz); + ExpectBufNE(chNew, chOld, chOldSz); + + ExpectNotNull(c2 = wolfSSL_new(ctx_s)); + ExpectIntEQ(test_dtls13_server_peer(c2, &tB, 2), WOLFSSL_SUCCESS); + for (i = 0; i < 2; i++) { + ExpectIntEQ(test_dtls13_inject_frag(&tB, chNew, chNewSz, piece, + order[mode][i]), 1); + ExpectIntEQ(test_dtls13_server_read_all(&tB, c2), TEST_SUCCESS); + ExpectIntEQ(tB.c_len, 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + } + ExpectIntEQ(test_dtls13_inject_frag(&tB, chNew, chNewSz, piece, + order[mode][2]), 1); + ExpectIntEQ(test_dtls13_server_read_all(&tB, c2), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&tB, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 0); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + ExpectIntEQ(test_memio_do_handshake(ssl_cB, c2, 10, NULL), 0); + + wolfSSL_free(c2); + wolfSSL_free(ssl_cA); + wolfSSL_free(ssl_cB); + wolfSSL_CTX_free(ctx_s); + wolfSSL_CTX_free(ctx_cA); + wolfSSL_CTX_free(ctx_cB); + return EXPECT_RESULT(); +} +#endif + +int test_dtls13_frag_ch1_ctx_stale_partial(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + ExpectIntEQ(test_dtls13_frag_ch1_ctx_stale_partial_ex(0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch1_ctx_stale_partial_ex(1), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch1_ctx_stale_partial_ex(2), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) && \ + defined(WOLFSSL_STATIC_MEMORY) && !defined(WOLFSSL_STATIC_MEMORY_LEAN) && \ + !defined(WOLFSSL_NO_MALLOC) +#define TEST_DTLS13_STATIC_MEMSZ 400000 + +/* Hold B's partial ClientHello through one server of a static memory context + * and complete it through another. lateLoad: memory given after creation. */ +static int test_dtls13_frag_ch_ctx_static_memory_ex(int lateLoad, byte* mem, + word32 memSz) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_cB = NULL, *ctx_s = NULL; + WOLFSSL *ssl_cB = NULL, *c1 = NULL, *c2 = NULL; + struct test_memio_ctx tB; + WOLFSSL_MEM_STATS base, now; + int group = WOLFSSL_ECC_SECP256R1; + byte chB[TEST_MEMIO_BUF_SZ]; + int chBSz = (int)sizeof(chB); + int piece = 0; + int isHrr = 0; + + XMEMSET(&tB, 0, sizeof(tB)); + XMEMSET(&base, 0, sizeof(base)); + XMEMSET(&now, 0, sizeof(now)); + if (lateLoad) { + ExpectNotNull(ctx_s = wolfSSL_CTX_new(wolfDTLSv1_3_server_method())); + ExpectIntEQ(wolfSSL_CTX_load_static_memory(&ctx_s, NULL, mem, memSz, + 0, 0), WOLFSSL_SUCCESS); + } + else { + ExpectIntEQ(wolfSSL_CTX_load_static_memory(&ctx_s, + wolfDTLSv1_3_server_method_ex, mem, memSz, 0, 0), + WOLFSSL_SUCCESS); + } + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx_s, svrKeyFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx_s, svrCertFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + if (ctx_s != NULL) { + wolfSSL_SetIORecv(ctx_s, test_memio_read_cb); + wolfSSL_SetIOSend(ctx_s, test_memio_write_cb); + } + /* What the context allocates on first use stays out of the count */ + ExpectNotNull(c1 = wolfSSL_new(ctx_s)); + wolfSSL_free(c1); + c1 = NULL; + ExpectIntEQ(wolfSSL_CTX_is_static_memory(ctx_s, &base), 1); + + ExpectIntEQ(test_memio_setup(&tB, &ctx_cB, &ctx_s, &ssl_cB, &c1, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_set_groups(ssl_cB, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&tB, ssl_cB, chB, &chBSz), TEST_SUCCESS); + piece = (chBSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + 2) / + 3; + + /* Each object allocates with a hint of its own */ + ExpectNotNull(c1); + if (EXPECT_SUCCESS()) + ExpectPtrNE(ctx_s->heap, c1->heap); + ExpectIntEQ(test_dtls13_server_peer(c1, &tB, 2), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&tB, chB, chBSz, piece, 0), 1); + ExpectIntEQ(test_dtls13_inject_frag(&tB, chB, chBSz, piece, 1), 1); + ExpectIntEQ(test_dtls13_server_read_all(&tB, c1), TEST_SUCCESS); + ExpectIntEQ(tB.c_len, 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + wolfSSL_free(c1); + c1 = NULL; + ExpectIntEQ(wolfSSL_CTX_is_static_memory(ctx_s, &now), 1); + ExpectIntGT(now.curAlloc, base.curAlloc); + + /* Another server completes it, on its own hint */ + ExpectNotNull(c2 = wolfSSL_new(ctx_s)); + if (EXPECT_SUCCESS()) + ExpectPtrNE(ctx_s->heap, c2->heap); + ExpectIntEQ(test_dtls13_server_peer(c2, &tB, 2), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_inject_frag(&tB, chB, chBSz, piece, 2), 1); + ExpectIntEQ(test_dtls13_server_read_all(&tB, c2), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&tB, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 0); + ExpectTrue(test_dtls13_pool_ok(&ctx_s->dtlsChPool)); + ExpectIntEQ(test_memio_do_handshake(ssl_cB, c2, 10, NULL), 0); + + /* Every allocation went back where it came from */ + wolfSSL_free(c2); + c2 = NULL; + ExpectIntEQ(wolfSSL_CTX_is_static_memory(ctx_s, &now), 1); + ExpectIntEQ(now.curAlloc, base.curAlloc); + + wolfSSL_free(ssl_cB); + wolfSSL_CTX_free(ctx_s); + wolfSSL_CTX_free(ctx_cB); + return EXPECT_RESULT(); +} +#endif + +/* The pool allocates with the heap of the context and rebuilds a ClientHello + * on the heap of the object that completes it. */ +int test_dtls13_frag_ch_ctx_static_memory(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) && \ + defined(WOLFSSL_STATIC_MEMORY) && !defined(WOLFSSL_STATIC_MEMORY_LEAN) && \ + !defined(WOLFSSL_NO_MALLOC) + static byte mem[TEST_DTLS13_STATIC_MEMSZ]; + int lateLoad; + + for (lateLoad = 0; lateLoad < 2 && EXPECT_SUCCESS(); lateLoad++) { + ExpectIntEQ(test_dtls13_frag_ch_ctx_static_memory_ex(lateLoad, mem, + (word32)sizeof(mem)), TEST_SUCCESS); + } +#endif + return EXPECT_RESULT(); +} + +/* Which results of DtlsMsgStore() end the connection: only running out of + * memory, or an authenticated peer exceeding the receive budget */ +int test_dtls13_frag_store_fatal(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte data[32]; + word16 epoch = 0; + int ret = 0; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(data, 0xAB, sizeof(data)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + + /* Plaintext could come from anyone: only memory ends the connection */ + ExpectIntEQ(DtlsMsgStoreFatal(ssl_s, 0), 0); + ExpectIntEQ(DtlsMsgStoreFatal(ssl_s, DTLS_MSG_STORE_DROPPED), 0); + ExpectIntEQ(DtlsMsgStoreFatal(ssl_s, WC_NO_ERR_TRACE(BAD_FUNC_ARG)), 0); + ExpectIntEQ(DtlsMsgStoreFatal(ssl_s, + WC_NO_ERR_TRACE(DTLS_TOO_MANY_FRAGMENTS_E)), 0); + ExpectIntNE(DtlsMsgStoreFatal(ssl_s, WC_NO_ERR_TRACE(MEMORY_E)), 0); + ExpectIntNE(DtlsMsgStoreFatal(ssl_s, WC_NO_ERR_TRACE(MEMORY_ERROR)), 0); + + /* The records of a peer that finished the handshake authenticate */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + if (EXPECT_SUCCESS()) { + ExpectIntNE(ssl_s->keys.encryptionOn, 0); + ExpectIntEQ(w64IsZero(ssl_s->keys.curEpoch64), 0); + epoch = (word16)w64GetLow32(ssl_s->keys.curEpoch64); + } + ExpectIntEQ(DtlsMsgStoreFatal(ssl_s, 0), 0); + ExpectIntEQ(DtlsMsgStoreFatal(ssl_s, DTLS_MSG_STORE_DROPPED), 0); + ExpectIntNE(DtlsMsgStoreFatal(ssl_s, + WC_NO_ERR_TRACE(DTLS_TOO_MANY_FRAGMENTS_E)), 0); + ExpectIntNE(DtlsMsgStoreFatal(ssl_s, WC_NO_ERR_TRACE(MEMORY_E)), 0); + /* A fragment that gives the message held another size is dropped */ + ExpectIntEQ(DtlsMsgStore(ssl_s, epoch, 20, data, 32, certificate, 0, 16, + ssl_s->heap), 0); + ret = DtlsMsgStore(ssl_s, epoch, 20, data, 24, certificate, 16, 8, + ssl_s->heap); + ExpectIntEQ(ret, WC_NO_ERR_TRACE(SEQUENCE_ERROR)); + ExpectIntEQ(DtlsMsgStoreFatal(ssl_s, ret), 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) && !defined(WOLFSSL_NO_TLS12) +/* Offset of the extensions in the body of a DTLS 1.2 ClientHello, or 0 when + * the body ends before them */ +static word32 test_dtls12_ch_ext_offset(const byte* body, word32 bodySz) +{ + word32 off = OPAQUE16_LEN + RAN_LEN; + word16 suitesSz; + + if (off >= bodySz) + return 0; + off += 1 + body[off]; + if (off >= bodySz) + return 0; + off += 1 + body[off]; + if (off + OPAQUE16_LEN > bodySz) + return 0; + ato16(body + off, &suitesSz); + off += OPAQUE16_LEN + suitesSz; + if (off >= bodySz) + return 0; + off += 1 + body[off]; + return off < bodySz ? off : 0; +} +#endif + +/* wolfSSL_clear() frees the receive list: a reused object starts with no + * message of the last connection. */ +int test_dtls13_clear_frees_rx_list(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && (defined(OPENSSL_EXTRA) || defined(DEBUG_WOLFSSL_VERBOSE) || \ + defined(HAVE_CURL)) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte data[16]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(data, 0x5a, sizeof(data)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(DtlsMsgStore(ssl_s, 0, 3, data, (word32)sizeof(data), + certificate, 0, 8, ssl_s->heap), 0); + ExpectNotNull(ssl_s->dtls_rx_msg_list); + ExpectIntEQ(ssl_s->dtls_rx_msg_list_sz, 1); + ExpectIntGT(ssl_s->dtls_rx_msg_list_bytes, 0); + ExpectIntEQ(wolfSSL_clear(ssl_s), WOLFSSL_SUCCESS); + ExpectNull(ssl_s->dtls_rx_msg_list); + ExpectIntEQ(ssl_s->dtls_rx_msg_list_sz, 0); + ExpectIntEQ(ssl_s->dtls_rx_msg_list_bytes, 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* A first fragment whose cookie fails is answered with an alert and not held: + * holding it would cost other peers' entries. The genuine ones complete. */ +int test_dtls13_frag_ch2_bad_cookie_dropped(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + byte bad[TEST_MEMIO_BUF_SZ]; + byte frag[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + int cut = 0; + int bodySz = 0; + int fragSz = 0; + int isHrr = 0; + + 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(test_dtls13_server_peer(ssl_s, &test_ctx, 2), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + + /* CH1, HRR with cookie, CH2 */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + ExpectIntGT(cut = test_dtls13_ch_cookie_end(ch, chSz), 0); + bodySz = chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ; + + /* The first fragment with the last byte of the cookie changed */ + XMEMCPY(bad, ch, (size_t)chSz); + bad[DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ + cut - 1] ^= 0x01; + ExpectIntGT(fragSz = test_dtls13_frag_range(bad, chSz, 0, (word32)cut, 0, + frag, (int)sizeof(frag)), 0); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)frag, + fragSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntGT(test_ctx.c_len, DTLS_RECORD_HEADER_SZ + 1); + ExpectIntEQ(test_ctx.c_buff[0], alert); + ExpectIntEQ(test_ctx.c_buff[DTLS_RECORD_HEADER_SZ], alert_fatal); + ExpectIntEQ(test_ctx.c_buff[DTLS_RECORD_HEADER_SZ + 1], illegal_parameter); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectTrue(test_dtls13_nothing_held(ssl_s)); + test_memio_clear_buffer(&test_ctx, 1); + + /* The genuine first fragment verifies and the rest completes it */ + ExpectIntGT(fragSz = test_dtls13_frag_range(ch, chSz, 0, (word32)cut, 1, + frag, (int)sizeof(frag)), 0); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)frag, + fragSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + ExpectIntGT(fragSz = test_dtls13_frag_range(ch, chSz, (word32)cut, + (word32)(bodySz - cut), 2, frag, (int)sizeof(frag)), 0); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)frag, + fragSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* A DTLS 1.2 second ClientHello in two fragments at a DTLS 1.3 server: the + * first carries the cookie and is held, the second completes it. */ +int test_dtls13_frag_ch2_dtls12_cookie(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) && !defined(WOLFSSL_NO_TLS12) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte ch[TEST_MEMIO_BUF_SZ]; + byte frag[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + word32 bodySz = 0; + word32 cut = 0; + int fragSz = 0; + int isHrr = 0; + + 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, wolfDTLS_server_method), 0); + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &test_ctx, 2), + WOLFSSL_SUCCESS); + + /* The first ClientHello gets a HelloVerifyRequest */ + ExpectIntEQ(wolfSSL_connect(ssl_c), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntGT(test_ctx.c_len, 0); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + + /* The second carries the cookie. Cut it after the compression methods so + * the first fragment holds everything the cookie check parses. */ + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + if (EXPECT_SUCCESS()) { + bodySz = (word32)chSz - DTLS_RECORD_HEADER_SZ - + DTLS_HANDSHAKE_HEADER_SZ; + cut = test_dtls12_ch_ext_offset(ch + DTLS_RECORD_HEADER_SZ + + DTLS_HANDSHAKE_HEADER_SZ, bodySz); + } + ExpectIntGT(cut, 0); + ExpectIntGT(fragSz = test_dtls13_frag_range(ch, chSz, 0, cut, 0, frag, + (int)sizeof(frag)), 0); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)frag, + fragSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + /* Verified: the server is stateful and holds the fragment */ + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + ExpectNotNull(ssl_s->dtls_rx_msg_list); + ExpectIntEQ(test_ctx.c_len, 0); + + ExpectIntGT(fragSz = test_dtls13_frag_range(ch, chSz, cut, bodySz - cut, 1, + frag, (int)sizeof(frag)), 0); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)frag, + fragSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(ssl_s->version.minor, DTLSv1_2_MINOR); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(SINGLE_THREADED) +#define TEST_CH_POOL_THREADS 2 +#define TEST_CH_POOL_OPS 5000 +/* A quarter held for every peer exceeds the budget, so stores evict */ +#define TEST_CH_POOL_PEERS 64 +#define TEST_CH_POOL_MSG_SZ ((TEST_DTLS_RX_UNVERIFIED_LIMIT / 16) & ~3) + +static DtlsChPool test_ch_pool_shared; +static int test_ch_pool_bad[TEST_CH_POOL_THREADS]; +static int test_ch_pool_out[TEST_CH_POOL_THREADS]; +static byte test_ch_pool_data[TEST_CH_POOL_THREADS][TEST_CH_POOL_MSG_SZ]; + +/* Whether every byte msg holds is peer's: a message never mixes peers */ +static int test_ch_pool_msg_ok(const DtlsMsg* msg, byte peer, word32 sz) +{ + const DtlsFragBucket* b; + word32 i; + + if (msg->peer.sz != 0 || msg->sz != sz) + return 0; + if (msg->ready) { + for (i = 0; i < msg->sz; i++) { + if (msg->fullMsg[i] != (byte)(peer + 1)) + return 0; + } + return 1; + } + for (b = msg->fragBucketList; b != NULL; b = b->m.m.next) { + for (i = 0; i < b->m.m.sz; i++) { + if (b->buf[i] != (byte)(peer + 1)) + return 0; + } + } + return 1; +} + +static THREAD_RETURN WOLFSSL_THREAD test_dtls13_ch_pool_worker(void* args) +{ + int tno = ((func_args*)args)->argc; + word32 rnd = 0x9e3779b9U * (word32)(tno + 1); + byte peerId = 0; + byte* data = test_ch_pool_data[tno]; + WOLFSSL_SOCKADDR peer; + int i; + + peer.sz = 1; + peer.bufSz = 1; + peer.sa = &peerId; + for (i = 0; i < TEST_CH_POOL_OPS; i++) { + DtlsMsg* msg = NULL; + word32 op; + word32 quarter; + + rnd = rnd * 1103515245U + 12345U; + peerId = (byte)((rnd >> 16) % TEST_CH_POOL_PEERS); + op = (rnd >> 8) % 256; + quarter = (rnd >> 24) % 4; + XMEMSET(data, peerId + 1, TEST_CH_POOL_MSG_SZ); + if (op == 0) { + DtlsChPoolClear(&test_ch_pool_shared, NULL); + } + else if (op < 16) { + msg = DtlsChPoolTakeRest(&test_ch_pool_shared, NULL, &peer, 0, 1, 0, + TEST_CH_POOL_MSG_SZ, DTLS_TIMEOUT_MAX, NULL); + } + else { + (void)DtlsChPoolStore(&test_ch_pool_shared, NULL, &peer, 0, 1, data, + TEST_CH_POOL_MSG_SZ, client_hello, + quarter * (TEST_CH_POOL_MSG_SZ / 4), TEST_CH_POOL_MSG_SZ / 4, + DTLS_TIMEOUT_MAX, NULL, &msg); + } + if (msg != NULL) { + if (!test_ch_pool_msg_ok(msg, peerId, TEST_CH_POOL_MSG_SZ)) + test_ch_pool_bad[tno]++; + test_ch_pool_out[tno]++; + DtlsMsgListDelete(msg, NULL); + } + } + WOLFSSL_RETURN_FROM_THREAD(0); +} +#endif + +/* Two threads store into, release from and clear one pool past its budget. + * Each message that leaves holds one peer's bytes, and the counters agree. */ +int test_dtls13_ch_pool_threads(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(SINGLE_THREADED) + THREAD_TYPE threads[TEST_CH_POOL_THREADS]; + func_args args[TEST_CH_POOL_THREADS]; + int i; + + XMEMSET(args, 0, sizeof(args)); + XMEMSET(test_ch_pool_bad, 0, sizeof(test_ch_pool_bad)); + XMEMSET(test_ch_pool_out, 0, sizeof(test_ch_pool_out)); + ExpectTrue(TEST_CH_POOL_PEERS * (sizeof(DtlsMsg) + 1 + + sizeof(DtlsFragBucket) + TEST_CH_POOL_MSG_SZ / 4) > + TEST_DTLS_RX_UNVERIFIED_LIMIT); + ExpectIntEQ(DtlsChPoolInit(&test_ch_pool_shared), 0); + if (EXPECT_FAIL()) + return EXPECT_RESULT(); + for (i = 0; i < TEST_CH_POOL_THREADS; i++) { + args[i].argc = i; + start_thread(test_dtls13_ch_pool_worker, &args[i], &threads[i]); + } + for (i = 0; i < TEST_CH_POOL_THREADS; i++) + join_thread(threads[i]); + for (i = 0; i < TEST_CH_POOL_THREADS; i++) { + ExpectIntEQ(test_ch_pool_bad[i], 0); + ExpectIntGT(test_ch_pool_out[i], 0); + } + ExpectTrue(test_dtls13_pool_ok(&test_ch_pool_shared)); + ExpectIntLE(test_dtls13_pool_count(&test_ch_pool_shared), + TEST_CH_POOL_PEERS); + DtlsChPoolFree(&test_ch_pool_shared, NULL); +#endif + return EXPECT_RESULT(); +} + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) && !defined(SINGLE_THREADED) +#define TEST_CH_HS_THREADS 2 +#define TEST_CH_HS_ROUNDS 12 + +/* The buffers of one handshake, too big for a thread's stack */ +typedef struct test_dtls13_ch_hs_bufs { + struct test_memio_ctx t; + byte ch[TEST_MEMIO_BUF_SZ]; + byte frag[TEST_MEMIO_BUF_SZ]; +} test_dtls13_ch_hs_bufs; + +static WOLFSSL_CTX* test_ch_hs_ctx_s; +static WOLFSSL_CTX* test_ch_hs_ctx_c[TEST_CH_HS_THREADS]; +static int test_ch_hs_fail[TEST_CH_HS_THREADS]; + +/* One handshake whose ClientHello the pool holds. mode 0: the first one in + * three pieces. 1: the second, cookie past the first. 2: the second, first + * piece last. */ +static int test_dtls13_ch_hs_one(WOLFSSL_CTX* ctx_c, WOLFSSL_CTX* ctx_s, + test_dtls13_ch_hs_bufs* b, int peerId, int mode) +{ + EXPECT_DECLS; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + const int hdrSz = DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ; + const char hello[] = "hello pool"; + char reply[sizeof(hello)]; + int group = WOLFSSL_ECC_SECP256R1; + int chSz = (int)sizeof(b->ch); + int cookieEnd = 0; + int fragSz = 0; + int isHrr = 0; + + XMEMSET(&b->t, 0, sizeof(b->t)); + /* The contexts are shared: only objects are made and freed here */ + ExpectNotNull(ssl_c = wolfSSL_new(ctx_c)); + ExpectNotNull(ssl_s = wolfSSL_new(ctx_s)); + if (EXPECT_SUCCESS()) { + wolfSSL_SetIOReadCtx(ssl_c, &b->t); + wolfSSL_SetIOWriteCtx(ssl_c, &b->t); + } + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &b->t, peerId), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + + if (mode == 0) { + ExpectIntEQ(test_dtls13_take_ch(&b->t, ssl_c, b->ch, &chSz), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_inject_fragmented(&b->t, b->ch, chSz, + (chSz - hdrSz + 2) / 3), 3); + ExpectIntEQ(test_dtls13_server_read_all(&b->t, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&b->t, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + } + else { + /* CH1, HRR with cookie, CH2 */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&b->t, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(test_dtls13_take_ch(&b->t, ssl_c, b->ch, &chSz), + TEST_SUCCESS); + if (mode == 1) { + /* Pieces shorter than what precedes the extensions */ + ExpectIntGE(test_dtls13_inject_fragmented(&b->t, b->ch, chSz, 40), + 3); + } + else { + ExpectIntGT(cookieEnd = test_dtls13_ch_cookie_end(b->ch, chSz), + 0); + ExpectIntLT(cookieEnd, chSz - hdrSz); + ExpectIntGT(fragSz = test_dtls13_frag_range(b->ch, chSz, + (word32)cookieEnd, (word32)(chSz - hdrSz - cookieEnd), 1, + b->frag, (int)sizeof(b->frag)), 0); + ExpectIntEQ(test_memio_inject_message(&b->t, 0, + (const char*)b->frag, fragSz), 0); + ExpectIntGT(fragSz = test_dtls13_frag_range(b->ch, chSz, 0, + (word32)cookieEnd, 0, b->frag, (int)sizeof(b->frag)), 0); + ExpectIntEQ(test_memio_inject_message(&b->t, 0, + (const char*)b->frag, fragSz), 0); + } + ExpectIntEQ(test_dtls13_server_read_all(&b->t, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&b->t, &isHrr), 1); + ExpectIntEQ(isHrr, 0); + } + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_write(ssl_c, hello, (int)sizeof(hello)), + (int)sizeof(hello)); + ExpectIntEQ(wolfSSL_read(ssl_s, reply, (int)sizeof(reply)), + (int)sizeof(hello)); + ExpectIntEQ(wolfSSL_write(ssl_s, reply, (int)sizeof(reply)), + (int)sizeof(reply)); + ExpectIntEQ(wolfSSL_read(ssl_c, reply, (int)sizeof(reply)), + (int)sizeof(hello)); + ExpectBufEQ(reply, hello, sizeof(hello)); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + return EXPECT_RESULT(); +} + +static THREAD_RETURN WOLFSSL_THREAD test_dtls13_ch_hs_worker(void* args) +{ + int tno = ((func_args*)args)->argc; + test_dtls13_ch_hs_bufs* b; + int r; + + b = (test_dtls13_ch_hs_bufs*)XMALLOC(sizeof(*b), NULL, + DYNAMIC_TYPE_TMP_BUFFER); + if (b == NULL) + test_ch_hs_fail[tno]++; + for (r = 0; b != NULL && r < TEST_CH_HS_ROUNDS; r++) { + /* A new peer each round, so no round meets another's entry */ + if (test_dtls13_ch_hs_one(test_ch_hs_ctx_c[tno], test_ch_hs_ctx_s, b, + (tno + 1) * 1000 + r, r % 3) != TEST_SUCCESS) + test_ch_hs_fail[tno]++; + } + XFREE(b, NULL, DYNAMIC_TYPE_TMP_BUFFER); + WOLFSSL_RETURN_FROM_THREAD(0); +} +#endif + +/* Two threads run full handshakes through servers of one context, whose pool + * holds every ClientHello on the way, in each way it can */ +int test_dtls13_ch_pool_handshake_threads(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) && !defined(SINGLE_THREADED) + THREAD_TYPE threads[TEST_CH_HS_THREADS]; + func_args args[TEST_CH_HS_THREADS]; + struct test_memio_ctx t; + test_dtls13_ch_hs_bufs* b = NULL; + int i; + + XMEMSET(&t, 0, sizeof(t)); + XMEMSET(args, 0, sizeof(args)); + XMEMSET(test_ch_hs_fail, 0, sizeof(test_ch_hs_fail)); + test_ch_hs_ctx_s = NULL; + for (i = 0; i < TEST_CH_HS_THREADS; i++) { + test_ch_hs_ctx_c[i] = NULL; + ExpectIntEQ(test_memio_setup(&t, &test_ch_hs_ctx_c[i], + &test_ch_hs_ctx_s, NULL, NULL, wolfDTLSv1_3_client_method, + wolfDTLSv1_3_server_method), 0); + } + /* One handshake first: wolfCrypt sets its CPU feature flags on first use + * without a lock */ + ExpectNotNull(b = (test_dtls13_ch_hs_bufs*)XMALLOC(sizeof(*b), NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + if (EXPECT_SUCCESS()) { + ExpectIntEQ(test_dtls13_ch_hs_one(test_ch_hs_ctx_c[0], + test_ch_hs_ctx_s, b, 1, 0), TEST_SUCCESS); + } + XFREE(b, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (EXPECT_SUCCESS()) { + for (i = 0; i < TEST_CH_HS_THREADS; i++) { + args[i].argc = i; + start_thread(test_dtls13_ch_hs_worker, &args[i], &threads[i]); + } + for (i = 0; i < TEST_CH_HS_THREADS; i++) + join_thread(threads[i]); + } + for (i = 0; i < TEST_CH_HS_THREADS; i++) + ExpectIntEQ(test_ch_hs_fail[i], 0); + ExpectNotNull(test_ch_hs_ctx_s); + if (test_ch_hs_ctx_s != NULL) { + ExpectIntEQ(test_dtls13_pool_count(&test_ch_hs_ctx_s->dtlsChPool), 0); + ExpectTrue(test_dtls13_pool_ok(&test_ch_hs_ctx_s->dtlsChPool)); + } + + for (i = 0; i < TEST_CH_HS_THREADS; i++) { + wolfSSL_CTX_free(test_ch_hs_ctx_c[i]); + test_ch_hs_ctx_c[i] = NULL; + } + wolfSSL_CTX_free(test_ch_hs_ctx_s); + test_ch_hs_ctx_s = NULL; +#endif + return EXPECT_RESULT(); +} + +/* Cookie exchange off: the server is stateful from the start, but a plaintext + * fragment contradicting the held ClientHello is dropped, not fatal. */ +int test_dtls13_frag_ch1_no_cookie_conflict(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + byte frag[TEST_MEMIO_BUF_SZ]; + int fragSz = 0; + int piece = 0; + int isHrr = 1; + word32 msgLen = 0; + word32 recSeq = 0; + + 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_disable_hrr_cookie(ssl_s), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + piece = (chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ + 2) / 3; + + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx, ch, chSz, piece, 0), 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + ExpectNotNull(ssl_s->dtls_rx_msg_list); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 0); + + ExpectIntGT(fragSz = test_dtls13_frag_record(ch, chSz, piece, 1, frag, + (int)sizeof(frag)), 0); + if (EXPECT_SUCCESS()) { + ato24(frag + DTLS_RECORD_HEADER_SZ + 1, &msgLen); + c32to24(msgLen + 1, frag + DTLS_RECORD_HEADER_SZ + 1); + /* A record number of its own, or the replay window drops the + * genuine fragment as a duplicate of it */ + ato32(frag + DTLS_RECORD_HEADER_SZ - 6, &recSeq); + c32toa(recSeq + 10, frag + DTLS_RECORD_HEADER_SZ - 6); + } + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)frag, + fragSz), 0); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx, ch, chSz, piece, 1), 1); + ExpectIntEQ(test_dtls13_inject_frag(&test_ctx, ch, chSz, piece, 2), 1); + ExpectIntEQ(test_dtls13_server_read_all(&test_ctx, ssl_s), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* Direct pool coverage avoids record parsing and cookie generation masking + * admission, LRU, completion and ownership regressions. */ +int test_dtls13_ch_pool_unit(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + DtlsChPool pool; + byte* data = NULL; + byte peerBuf[16]; + byte alt[8]; + WOLFSSL_SOCKADDR peer; + WOLFSSL_SOCKADDR none; + DtlsMsg* current = NULL; + DtlsMsg* other = NULL; + DtlsMsg* done = NULL; + DtlsMsg* saved = NULL; + DtlsMsg* h = NULL; + size_t base = sizeof(DtlsMsg) + sizeof(peerBuf) + sizeof(DtlsFragBucket); + /* The largest ClientHello the pool completes: its last fragment is + * reserved as a new entry on top of the one holding the rest */ + word32 len = (word32)(TEST_DTLS_RX_UNVERIFIED_LIMIT - 2 * base); + size_t bytes; + word32 held = 0; + word32 shard = 0; + int i; + + ExpectIntEQ(DtlsChPoolInit(&pool), 0); + ExpectNotNull(data = (byte*)XMALLOC(TEST_DTLS_RX_UNVERIFIED_LIMIT, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + if (EXPECT_FAIL()) { + DtlsChPoolFree(&pool, NULL); + return EXPECT_RESULT(); + } + XMEMSET(data, 0x5a, TEST_DTLS_RX_UNVERIFIED_LIMIT); + XMEMSET(peerBuf, 0x01, sizeof(peerBuf)); + peer.sz = sizeof(peerBuf); + peer.bufSz = sizeof(peerBuf); + peer.sa = peerBuf; + XMEMSET(&none, 0, sizeof(none)); + + ExpectIntEQ(DtlsChPoolStore(NULL, NULL, &peer, 0, 0, data, len, + client_hello, 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, NULL, 0, 0, data, len, + client_hello, 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, 4, client_hello, + 3, 2, DTLS_TIMEOUT_MAX, NULL, &done), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectNull(done); + + /* The peer copy is counted */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, len, + client_hello, 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectNull(done); + ExpectNotNull(current = test_dtls13_pool_head(&pool)); + ExpectIntEQ(current != NULL ? (int)current->peer.bufSz : 0, + sizeof(peerBuf)); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectIntEQ(test_dtls13_pool_bytes(&pool), base + 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + /* Capacity remains allocated even if the logical address gets shorter */ + if (EXPECT_SUCCESS()) + current->peer.sz = 1; + ExpectIntEQ(DtlsMsgListRetainedBytes(current), base + 1); + if (EXPECT_SUCCESS()) + current->peer.sz = sizeof(peerBuf); + /* A second peer goes after the first */ + peerBuf[0] = 2; + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, len, + client_hello, 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectNotNull(other = test_dtls13_pool_next(&pool, current)); + bytes = 2 * (base + 1); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); + ExpectIntEQ(test_dtls13_pool_bytes(&pool), bytes); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + /* An empty peer is no key, and a whole message is never held */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &none, 0, 0, data, len, + client_hello, 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectNull(DtlsChPoolTakeRest(&pool, NULL, &none, 0, 0, 1, len, + DTLS_TIMEOUT_MAX, NULL)); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, 4, client_hello, + 0, 4, DTLS_TIMEOUT_MAX, NULL, &done), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectNull(done); + ExpectPtrEq(test_dtls13_pool_head(&pool), current); + ExpectPtrEq(test_dtls13_pool_next(&pool, current), other); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); + ExpectIntEQ(test_dtls13_pool_bytes(&pool), bytes); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + /* An impossible completed CH must not evict the current entry or another + * one. */ + peerBuf[0] = 3; + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, len + 1, + client_hello, 0, 1, + DTLS_TIMEOUT_MAX, NULL, &done), DTLS_MSG_STORE_DROPPED); + peerBuf[0] = 1; + ExpectNull(done); + ExpectPtrEq(test_dtls13_pool_head(&pool), current); + ExpectPtrEq(test_dtls13_pool_next(&pool, current), other); + ExpectIntEQ(current != NULL ? (int)current->bytesReceived : 0, 1); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); + ExpectIntEQ(test_dtls13_pool_bytes(&pool), bytes); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + /* A duplicate does not refresh; new bytes do, partial overlaps too */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, len, + client_hello, 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectPtrEq(test_dtls13_pool_head(&pool), current); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, len, + client_hello, 0, 2, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectPtrEq(test_dtls13_pool_head(&pool), other); + ExpectPtrEq(test_dtls13_pool_next(&pool, other), current); + ExpectNull(test_dtls13_pool_next(&pool, current)); + ExpectIntEQ(test_dtls13_pool_bytes(&pool), bytes + 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + /* Refresh other, making current the oldest again */ + peerBuf[0] = 2; + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, len, + client_hello, 1, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectPtrEq(test_dtls13_pool_head(&pool), current); + peerBuf[0] = 3; + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, len, + client_hello, 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), 3); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + /* Completing the oldest, to the largest the pool holds, evicts both other + * entries, never the one extended. The completed message leaves without + * its peer. */ + peerBuf[0] = 1; + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, len, + client_hello, 2, len - 2, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectPtrEq(done, current); + ExpectIntEQ(test_dtls13_pool_count(&pool), 0); + ExpectIntEQ(test_dtls13_pool_bytes(&pool), 0); + ExpectNull(test_dtls13_pool_head(&pool)); + ExpectNotNull(done); + if (done != NULL) { + ExpectIntEQ(done->ready, 1); + ExpectIntEQ(done->peer.sz, 0); + ExpectNull(done->peer.sa); + ExpectNull(done->next); + ExpectBufEQ(done->fullMsg, data, len); + ExpectIntEQ(DtlsMsgListRetainedBytes(done), + base - sizeof(peerBuf) + len); + } + DtlsMsgListDelete(done, NULL); + done = NULL; + + /* One entry per peer, the newest: another message, or a fragment + * contradicting the one held, replaces it */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, 4, client_hello, + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + h = test_dtls13_pool_head(&pool); + ExpectIntEQ(h != NULL ? (int)h->seq : -1, 1); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 5, client_hello, + 1, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + h = test_dtls13_pool_head(&pool); + ExpectIntEQ(h != NULL ? (int)h->sz : -1, 5); + h = test_dtls13_pool_head(&pool); + ExpectIntEQ(h != NULL ? (int)h->bytesReceived : -1, 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + ExpectNull(done); + + /* A fragment whose bytes differ from those it overlaps replaces the + * entry too. Agreeing and disjoint fragments merge. */ + XMEMSET(alt, 0xa5, sizeof(alt)); + peerBuf[0] = 2; + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 8, client_hello, + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + peerBuf[0] = 1; + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 8, client_hello, + 0, 2, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 8, client_hello, + 1, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, alt, 8, client_hello, + 2, 2, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); + ExpectNotNull(current = test_dtls13_pool_next(&pool, + test_dtls13_pool_head(&pool))); + ExpectIntEQ(current != NULL ? (int)current->bytesReceived : -1, 4); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 8, client_hello, + 3, 2, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); + ExpectNotNull(current = test_dtls13_pool_next(&pool, + test_dtls13_pool_head(&pool))); + ExpectIntEQ(current != NULL ? (int)current->bytesReceived : -1, 2); + ExpectIntEQ(current != NULL && current->fragBucketList != NULL ? + (int)current->fragBucketList->m.m.offset : -1, 3); + ExpectTrue(test_dtls13_pool_ok(&pool)); + /* Every bucket the fragment spans is compared: [4,7) agrees with [3,5) + * but not with [6,7), so it replaces the entry */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 8, client_hello, + 6, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectIntEQ(current != NULL ? (int)current->fragBucketListCount : -1, 2); + XMEMSET(alt, 0x5a, 6); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, alt + 4, 8, + client_hello, 4, 3, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectNull(done); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); + ExpectNotNull(current = test_dtls13_pool_next(&pool, + test_dtls13_pool_head(&pool))); + ExpectIntEQ(current != NULL ? (int)current->bytesReceived : -1, 3); + ExpectIntEQ(current != NULL && current->fragBucketList != NULL ? + (int)current->fragBucketList->m.m.offset : -1, 4); + ExpectTrue(test_dtls13_pool_ok(&pool)); + /* The message it completes is all its own */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, alt, 8, client_hello, + 0, 4, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectNull(done); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, alt + 7, 8, + client_hello, 7, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectNotNull(done); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + if (done != NULL) { + ExpectIntEQ(done->ready, 1); + ExpectBufEQ(done->fullMsg, alt, 8); + } + DtlsMsgListDelete(done, NULL); + done = NULL; + peerBuf[0] = 2; + ExpectNotNull(test_dtls13_pool_head(&pool)); + h = test_dtls13_pool_head(&pool); + ExpectIntEQ(h != NULL ? (int)h->bytesReceived : -1, 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + /* A fragment opening one more gap than DTLS_FRAG_POOL_SZ is dropped, as + * in the receive list; one touching what is held is taken */ + DtlsChPoolClear(&pool, NULL); + peerBuf[0] = 1; + for (i = 0; i < DTLS_FRAG_POOL_SZ && EXPECT_SUCCESS(); i++) { + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, + 2 * DTLS_FRAG_POOL_SZ + 2, client_hello, 2 * i, 1, + DTLS_TIMEOUT_MAX, NULL, &done), + 0); + } + ExpectNotNull(current = test_dtls13_pool_head(&pool)); + ExpectIntEQ(current != NULL ? (int)current->fragBucketListCount : -1, + DTLS_FRAG_POOL_SZ); + bytes = test_dtls13_pool_bytes(&pool); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, + 2 * DTLS_FRAG_POOL_SZ + 2, client_hello, 2 * DTLS_FRAG_POOL_SZ, 1, + DTLS_TIMEOUT_MAX, NULL, &done), DTLS_MSG_STORE_DROPPED); + ExpectPtrEq(test_dtls13_pool_head(&pool), current); + ExpectIntEQ(current != NULL ? (int)current->fragBucketListCount : -1, + DTLS_FRAG_POOL_SZ); + ExpectIntEQ(test_dtls13_pool_bytes(&pool), bytes); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, + 2 * DTLS_FRAG_POOL_SZ + 2, client_hello, 2 * DTLS_FRAG_POOL_SZ - 1, 1, + DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectIntEQ(current != NULL ? (int)current->fragBucketListCount : -1, + DTLS_FRAG_POOL_SZ); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, + 2 * DTLS_FRAG_POOL_SZ + 2, client_hello, 1, 1, + DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectIntEQ(current != NULL ? (int)current->fragBucketListCount : -1, + DTLS_FRAG_POOL_SZ - 1); + ExpectNull(done); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + /* Clear */ + DtlsChPoolClear(&pool, NULL); + ExpectIntEQ(test_dtls13_pool_count(&pool), 0); + ExpectIntEQ(test_dtls13_pool_bytes(&pool), 0); + ExpectNull(test_dtls13_pool_head(&pool)); + + /* Release: only the rest of the ClientHello whose first fragment + * verified is returned, without its peer copy */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 1, 3, DTLS_TIMEOUT_MAX, NULL, &done), 0); + peerBuf[0] = 2; + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + peerBuf[0] = 3; + ExpectNull(DtlsChPoolTakeRest(&pool, NULL, &peer, 0, 1, 1, 4, + DTLS_TIMEOUT_MAX, NULL)); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); + peerBuf[0] = 1; + ExpectNotNull(done = DtlsChPoolTakeRest(&pool, NULL, &peer, 0, 1, 1, 4, + DTLS_TIMEOUT_MAX, NULL)); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + if (done != NULL) { + ExpectIntEQ(done->peer.sz, 0); + ExpectNull(done->peer.sa); + ExpectIntEQ(DtlsMsgListRetainedBytes(done), base - sizeof(peerBuf) + 3); + ExpectNull(done->next); + ExpectPtrEq(DtlsMsgFind(done, 0, 1), done); + } + DtlsMsgListDelete(done, NULL); + done = NULL; + /* An overlap is cut away: only the bytes past the verified fragment */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 1, 3, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectNotNull(done = DtlsChPoolTakeRest(&pool, NULL, &peer, 0, 1, 2, 4, + DTLS_TIMEOUT_MAX, NULL)); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + if (done != NULL) { + ExpectIntEQ(done->bytesReceived, 2); + ExpectIntEQ(done->fragBucketListCount, 1); + ExpectNotNull(done->fragBucketList); + if (done->fragBucketList != NULL) { + ExpectIntEQ(done->fragBucketList->m.m.offset, 2); + ExpectIntEQ(done->fragBucketList->m.m.sz, 2); + ExpectBufEQ(done->fragBucketList->buf, data + 2, 2); + ExpectNull(done->fragBucketList->m.m.next); + } + } + DtlsMsgListDelete(done, NULL); + done = NULL; + /* Nothing past the verified fragment, another seq or another size: freed */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 1, 3, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectNull(DtlsChPoolTakeRest(&pool, NULL, &peer, 0, 1, 4, 4, + DTLS_TIMEOUT_MAX, NULL)); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 1, 3, DTLS_TIMEOUT_MAX, NULL, &done), 0); + DtlsChPoolDrop(&pool, NULL, &peer, DTLS_TIMEOUT_MAX); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 1, 3, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectNull(DtlsChPoolTakeRest(&pool, NULL, &peer, 0, 2, 1, 4, + DTLS_TIMEOUT_MAX, NULL)); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 1, 3, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectNull(DtlsChPoolTakeRest(&pool, NULL, &peer, 0, 1, 1, 5, + DTLS_TIMEOUT_MAX, NULL)); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + peerBuf[0] = 2; + ExpectNotNull(test_dtls13_pool_head(&pool)); + h = test_dtls13_pool_head(&pool); + ExpectIntEQ(h != NULL ? (int)h->bytesReceived : -1, 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + ExpectNull(done); + DtlsChPoolFree(&pool, NULL); + ExpectIntEQ(test_dtls13_pool_count(&pool), 0); + ExpectNull(test_dtls13_pool_head(&pool)); + ExpectIntEQ(DtlsChPoolInit(&pool), 0); + + /* Adopting on the same heap keeps the message. A partial one comes from + * the pool: no part of it overlaps an empty first fragment. */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 0, 2, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 3, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectNotNull(done = DtlsChPoolTakeRest(&pool, NULL, &peer, 0, 1, 0, 4, + DTLS_TIMEOUT_MAX, NULL)); + saved = done; + ExpectIntEQ(DtlsMsgAdopt(&done, NULL, NULL), 0); + ExpectPtrEq(done, saved); + ExpectIntEQ(DtlsMsgAdopt(NULL, NULL, NULL), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); +#if !defined(WOLFSSL_STATIC_MEMORY) && !defined(XMALLOC_USER) && \ + !defined(WOLFSSL_HEAP_TEST) + /* On another heap it is rebuilt, bucket by bucket */ + ExpectIntEQ(DtlsMsgAdopt(&done, NULL, (void*)&pool), 0); + ExpectNotNull(done); + ExpectPtrNE(done, saved); + if (done != NULL) { + ExpectIntEQ(done->ready, 0); + ExpectIntEQ(done->bytesReceived, 3); + ExpectIntEQ(done->fragBucketListCount, 2); + ExpectIntEQ(done->type, client_hello); + ExpectIntEQ(done->seq, 1); + ExpectNotNull(done->fragBucketList); + if (done->fragBucketList != NULL) { + ExpectIntEQ(done->fragBucketList->m.m.offset, 0); + ExpectIntEQ(done->fragBucketList->m.m.sz, 2); + ExpectNotNull(done->fragBucketList->m.m.next); + } + if (done->fragBucketList != NULL && + done->fragBucketList->m.m.next != NULL) { + ExpectIntEQ(done->fragBucketList->m.m.next->m.m.offset, 3); + ExpectIntEQ(done->fragBucketList->m.m.next->m.m.sz, 1); + } + } + DtlsMsgListDelete(done, (void*)&pool); + /* and a complete one as a whole */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 0, 2, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectNull(done); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 2, 2, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectNotNull(done); + saved = done; + ExpectIntEQ(DtlsMsgAdopt(&done, NULL, (void*)&pool), 0); + ExpectNotNull(done); + ExpectPtrNE(done, saved); + if (done != NULL) { + ExpectIntEQ(done->ready, 1); + ExpectIntEQ(done->bytesReceived, 4); + ExpectBufEQ(done->fullMsg, data, 4); + } + DtlsMsgListDelete(done, (void*)&pool); +#else + DtlsMsgListDelete(done, NULL); +#endif + done = NULL; + ExpectIntEQ(test_dtls13_pool_count(&pool), 0); + + /* Only new bytes restart an entry's clock */ + peerBuf[0] = 1; + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectNotNull(current = test_dtls13_pool_head(&pool)); + if (current != NULL) + current->heldAt -= (word32)DTLS_TIMEOUT_MAX / 2; + saved = current; + held = current != NULL ? current->heldAt : 0; + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectPtrEq(test_dtls13_pool_head(&pool), saved); + ExpectTrue(current != NULL && current->heldAt == held); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 2, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectPtrEq(test_dtls13_pool_head(&pool), saved); + ExpectIntEQ(current != NULL ? (int)current->bytesReceived : -1, 2); + ExpectTrue(current != NULL && current->heldAt != held); + /* An entry that aged out is abandoned: a later disjoint fragment of the + * same size starts a new entry instead of merging */ + test_dtls13_age(current); + XMEMSET(alt, 0xa5, sizeof(alt)); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, alt, 4, client_hello, + 1, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectNull(done); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectNotNull(current = test_dtls13_pool_head(&pool)); + ExpectIntEQ(current != NULL ? (int)current->bytesReceived : -1, 1); + ExpectIntEQ(current != NULL && current->fragBucketList != NULL ? + (int)current->fragBucketList->m.m.offset : -1, 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + /* A rest whose clock ran out is freed, not returned */ + DtlsChPoolClear(&pool, NULL); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 1, 3, DTLS_TIMEOUT_MAX, NULL, &done), 0); + test_dtls13_age(test_dtls13_pool_head(&pool)); + ExpectNull(DtlsChPoolTakeRest(&pool, NULL, &peer, 0, 1, 1, 4, + DTLS_TIMEOUT_MAX, NULL)); + ExpectIntEQ(test_dtls13_pool_count(&pool), 0); + ExpectIntEQ(test_dtls13_pool_bytes(&pool), 0); + /* Any store in its shard frees it */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + test_dtls13_age(test_dtls13_pool_head(&pool)); + shard = DtlsChPoolShardIdx(&peer); + for (i = 2; i < 256; i++) { + peerBuf[0] = (byte)i; + if (DtlsChPoolShardIdx(&peer) == shard) + break; + } + ExpectIntLT(i, 256); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectNotNull(test_dtls13_pool_head(&pool)); + h = test_dtls13_pool_head(&pool); + ExpectIntEQ(h != NULL ? ((byte*)h->peer.sa)[0] : 0, i); + ExpectTrue(test_dtls13_pool_ok(&pool)); + ExpectNull(done); + DtlsChPoolClear(&pool, NULL); + /* A hold time ahead of the clock, after a timer wrap or clock step, gives no + * age. The store frees the entry, so the fragment starts a new one. */ + peerBuf[0] = 1; + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 1, 3, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectNotNull(current = test_dtls13_pool_head(&pool)); + if (current != NULL) + current->heldAt += 3600; + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectNull(done); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectNotNull(current = test_dtls13_pool_head(&pool)); + ExpectIntEQ(current != NULL ? (int)current->bytesReceived : -1, 1); + ExpectIntEQ(current != NULL && current->fragBucketList != NULL ? + (int)current->fragBucketList->m.m.offset : -1, 0); + ExpectTrue(test_dtls13_pool_ok(&pool)); + DtlsChPoolClear(&pool, NULL); + /* A release frees it too: held 10 s before a 1 kHz 32-bit tick count + * wrapped */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 1, 3, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectNotNull(saved = test_dtls13_pool_head(&pool)); + peerBuf[0] = 2; + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); + ExpectPtrEq(test_dtls13_pool_head(&pool), saved); + if (saved != NULL) + saved->heldAt += (word32)(4294967UL - 10); + peerBuf[0] = 1; + ExpectNull(DtlsChPoolTakeRest(&pool, NULL, &peer, 0, 1, 1, 4, + DTLS_TIMEOUT_MAX, NULL)); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectNotNull(test_dtls13_pool_head(&pool)); + h = test_dtls13_pool_head(&pool); + ExpectIntEQ(h != NULL ? ((byte*)h->peer.sa)[0] : 0, 2); + ExpectTrue(test_dtls13_pool_ok(&pool)); + DtlsChPoolClear(&pool, NULL); + + /* Free leaves nothing, and the pool can be set up again */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + DtlsChPoolFree(&pool, NULL); + ExpectIntEQ(test_dtls13_pool_count(&pool), 0); + ExpectIntEQ(test_dtls13_pool_reserved(&pool), 0); + ExpectIntEQ(DtlsChPoolInit(&pool), 0); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectTrue(test_dtls13_pool_ok(&pool)); + DtlsChPoolFree(&pool, NULL); + XFREE(data, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return EXPECT_RESULT(); +} + +/* A peer is its address bytes, whatever they look like: the same bytes are + * one peer, any differing byte is another, padding of a sockaddr included */ +int test_dtls13_ch_pool_peer_key(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + DtlsChPool pool; + byte o1[16]; + byte o2[16]; + WOLFSSL_SOCKADDR pa; + WOLFSSL_SOCKADDR pb; + byte data[8]; + DtlsMsg* done = NULL; + + XMEMSET(data, 0x5a, sizeof(data)); + ExpectIntEQ(DtlsChPoolInit(&pool), 0); + + /* The same bytes from another buffer complete the ClientHello */ + XMEMSET(o1, 0x01, sizeof(o1)); + XMEMCPY(o2, o1, sizeof(o2)); + pa.sa = o1; + pa.sz = pa.bufSz = (unsigned int)sizeof(o1); + pb.sa = o2; + pb.sz = pb.bufSz = (unsigned int)sizeof(o2); + ExpectIntEQ(DtlsChPoolShardIdx(&pa), DtlsChPoolShardIdx(&pb)); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &pa, 0, 0, data, 8, client_hello, + 0, 4, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectNull(done); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &pb, 0, 0, data, 8, client_hello, + 4, 4, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectNotNull(done); + DtlsMsgListDelete(done, NULL); + done = NULL; + ExpectIntEQ(test_dtls13_pool_count(&pool), 0); + + /* One byte off is another peer */ + o2[sizeof(o2) - 1] = 0x02; + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &pa, 0, 0, data, 8, client_hello, + 0, 4, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &pb, 0, 0, data, 8, client_hello, + 4, 4, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectNull(done); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); + ExpectTrue(test_dtls13_pool_ok(&pool)); + DtlsChPoolClear(&pool, NULL); + + /* A shorter address is another peer, even as a prefix */ + pb.sa = o1; + pb.sz = pb.bufSz = (unsigned int)sizeof(o1) - 1; + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &pa, 0, 0, data, 8, client_hello, + 0, 4, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &pb, 0, 0, data, 8, client_hello, + 4, 4, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectNull(done); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); + ExpectTrue(test_dtls13_pool_ok(&pool)); + +#ifdef HAVE_SOCKADDR + /* A sockaddr_in differing only in sin_zero is another peer: the bytes + * are not interpreted, so a custom transport's 16-byte id that happens + * to read as AF_INET is keyed whole too */ + { + SOCKADDR_IN a4; + SOCKADDR_IN b4; + + DtlsChPoolClear(&pool, NULL); + XMEMSET(&a4, 0, sizeof(a4)); + a4.sin_family = WOLFSSL_IP4; + a4.sin_port = 4433; + a4.sin_addr.s_addr = 0x0100007fU; + b4 = a4; + XMEMSET(b4.sin_zero, 0xee, sizeof(b4.sin_zero)); + pa.sa = &a4; + pa.sz = pa.bufSz = (unsigned int)sizeof(a4); + pb.sa = &b4; + pb.sz = pb.bufSz = (unsigned int)sizeof(b4); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &pa, 0, 0, data, 8, + client_hello, 0, 4, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &pb, 0, 0, data, 8, + client_hello, 4, 4, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectNull(done); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); + ExpectTrue(test_dtls13_pool_ok(&pool)); + } +#endif + + DtlsChPoolFree(&pool, NULL); +#endif + return EXPECT_RESULT(); +} + +/* Only a DTLS context initializes the ClientHello pool. A TLS context's pool + * takes nothing. */ +int test_dtls13_ch_pool_ctx_init(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX* tls = NULL; + WOLFSSL_CTX* dtls = NULL; + byte peerBuf[4]; + WOLFSSL_SOCKADDR peer; + byte data[8]; + DtlsMsg* done = NULL; + + XMEMSET(data, 0x5a, sizeof(data)); + XMEMSET(peerBuf, 0x07, sizeof(peerBuf)); + peer.sa = peerBuf; + peer.sz = peer.bufSz = (unsigned int)sizeof(peerBuf); + + ExpectNotNull(tls = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + ExpectNotNull(dtls = wolfSSL_CTX_new(wolfDTLSv1_3_server_method())); + if (EXPECT_SUCCESS()) { + ExpectIntEQ(tls->dtlsChPool.ready, 0); + ExpectIntEQ(dtls->dtlsChPool.ready, 1); + ExpectIntEQ(DtlsChPoolStore(&tls->dtlsChPool, tls->heap, &peer, 0, 0, + data, 8, client_hello, 0, 4, DTLS_TIMEOUT_MAX, NULL, &done), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectNull(done); + ExpectNull(DtlsChPoolTakeRest(&tls->dtlsChPool, tls->heap, &peer, 0, 0, + 4, 8, DTLS_TIMEOUT_MAX, NULL)); + DtlsChPoolDrop(&tls->dtlsChPool, tls->heap, &peer, DTLS_TIMEOUT_MAX); + DtlsChPoolClear(&tls->dtlsChPool, tls->heap); + ExpectIntEQ(DtlsChPoolStore(&dtls->dtlsChPool, dtls->heap, &peer, 0, 0, + data, 8, client_hello, 0, 4, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectIntEQ(test_dtls13_pool_count(&dtls->dtlsChPool), 1); + } + wolfSSL_CTX_free(tls); + wolfSSL_CTX_free(dtls); +#endif + return EXPECT_RESULT(); +} + +/* An entry expires after the storing server's retransmission maximum, not a + * fixed time. The rest taken for a verified first fragment ages the same. */ +int test_dtls13_ch_pool_max_age(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + DtlsChPool pool; + byte peerBuf[4]; + WOLFSSL_SOCKADDR peer; + byte data[8]; + DtlsMsg* done = NULL; + DtlsMsg* rest = NULL; + word32 longer = 2 * (word32)DTLS_TIMEOUT_MAX; + + XMEMSET(data, 0x5a, sizeof(data)); + XMEMSET(peerBuf, 0x07, sizeof(peerBuf)); + peer.sa = peerBuf; + peer.sz = peer.bufSz = (unsigned int)sizeof(peerBuf); + ExpectIntEQ(DtlsChPoolInit(&pool), 0); + + /* Older than the default but within a longer maximum: kept */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, 8, client_hello, + 0, 4, longer, NULL, &done), 0); + test_dtls13_age(test_dtls13_pool_head(&pool)); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, 8, client_hello, + 4, 4, longer, NULL, &done), 0); + ExpectNotNull(done); + DtlsMsgListDelete(done, NULL); + done = NULL; + ExpectIntEQ(test_dtls13_pool_count(&pool), 0); + + /* Older than the maximum: gone, and the fragment starts over */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, 8, client_hello, + 0, 4, DTLS_TIMEOUT_MAX, NULL, &done), 0); + test_dtls13_age(test_dtls13_pool_head(&pool)); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, 8, client_hello, + 4, 4, DTLS_TIMEOUT_MAX, NULL, &done), 0); + ExpectNull(done); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + DtlsChPoolClear(&pool, NULL); + + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, 8, client_hello, + 4, 4, longer, NULL, &done), 0); + test_dtls13_age(test_dtls13_pool_head(&pool)); + rest = DtlsChPoolTakeRest(&pool, NULL, &peer, 0, 0, 4, 8, longer, NULL); + ExpectNotNull(rest); + DtlsMsgListDelete(rest, NULL); + rest = NULL; + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, 8, client_hello, + 4, 4, longer, NULL, &done), 0); + test_dtls13_age(test_dtls13_pool_head(&pool)); + ExpectNull(DtlsChPoolTakeRest(&pool, NULL, &peer, 0, 0, 4, 8, + DTLS_TIMEOUT_MAX, NULL)); + ExpectIntEQ(test_dtls13_pool_count(&pool), 0); + + DtlsChPoolFree(&pool, NULL); +#endif + return EXPECT_RESULT(); +} + +/* All shards share one budget. Eviction takes the oldest entry of the whole + * pool, whatever its shard. A store evicts for the bound of its fragment, + * a new entry holding it, before it learns what the fragment adds. */ +int test_dtls13_ch_pool_shards(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + DtlsChPool pool; + byte* data = NULL; + const word32 msgSz = 512; + const word32 first = 16; + size_t entry = sizeof(DtlsMsg) + sizeof(int) + sizeof(DtlsFragBucket) + + first; + size_t left; + size_t bytes = 0; + /* The largest ClientHello the pool completes */ + word32 bigSz = (word32)(TEST_DTLS_RX_UNVERIFIED_LIMIT - + 2 * (sizeof(int) + sizeof(DtlsMsg) + sizeof(DtlsFragBucket))); + int capacity = (int)(TEST_DTLS_RX_UNVERIFIED_LIMIT / entry); + int order[TEST_DTLS_RX_UNVERIFIED_LIMIT / (sizeof(DtlsMsg) + 16)]; + int last = WOLFSSL_DTLS_CH_POOL_SHARDS - 1; + int a, b, c, d, e, w, x, y, z; + int id; + int i; + DtlsMsg* m = NULL; + + left = TEST_DTLS_RX_UNVERIFIED_LIMIT - (size_t)capacity * entry; + ExpectIntGT(capacity, 4); + ExpectIntLE(capacity, (int)(sizeof(order) / sizeof(order[0]))); + ExpectIntEQ(DtlsChPoolInit(&pool), 0); + ExpectNotNull(data = (byte*)XMALLOC(TEST_DTLS_RX_UNVERIFIED_LIMIT, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + if (EXPECT_FAIL()) { + DtlsChPoolFree(&pool, NULL); + return EXPECT_RESULT(); + } + XMEMSET(data, 0x5a, TEST_DTLS_RX_UNVERIFIED_LIMIT); + + /* The oldest is in the last shard, so a scan from shard 0 meets other + * entries first, and e is the next behind it there. The rest spread over + * all shards. */ + a = test_dtls13_id_in(1, (word32)last, 1); + e = test_dtls13_id_in(a + 1, (word32)last, 1); + order[0] = a; + order[1] = e; + for (i = 2, id = 1; i < capacity; i++, id++) { + while (id == a || id == e) + id++; + order[i] = id; + } + for (i = 0; i < capacity && EXPECT_SUCCESS(); i++) { + ExpectIntEQ(test_dtls13_pool_put(&pool, order[i], data, msgSz, 0, + first), 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), i + 1); + ExpectTrue(test_dtls13_pool_bytes(&pool) == (size_t)(i + 1) * entry); + ExpectTrue(test_dtls13_pool_ok(&pool)); + } +#if WOLFSSL_DTLS_CH_POOL_SHARDS > 1 + for (i = 0, id = 0; i < WOLFSSL_DTLS_CH_POOL_SHARDS; i++) + id += pool.shard[i].head != NULL; + ExpectIntGT(id, 1); +#endif + b = order[2]; + c = order[3]; + d = order[capacity - 1]; + + /* A duplicate of a at capacity adds nothing but evicts for its bound: + * e, the entry behind a's own, the oldest. a stays the oldest. */ + ExpectIntEQ(test_dtls13_pool_put(&pool, a, data, msgSz, 0, first), 0); + ExpectPtrEq(test_dtls13_pool_head(&pool), test_dtls13_held_for(&pool, a)); + ExpectNull(test_dtls13_held_for(&pool, e)); + ExpectIntEQ(test_dtls13_pool_count(&pool), capacity - 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + /* A new peer takes the room that made. The next one, outside a's shard, + * evicts a and nothing else. */ + w = test_dtls13_id_in(capacity + 1, test_dtls13_shard_of(a), 0); + ExpectIntEQ(test_dtls13_pool_put(&pool, w, data, msgSz, 0, first), 0); + ExpectNotNull(test_dtls13_held_for(&pool, a)); + ExpectIntEQ(test_dtls13_pool_count(&pool), capacity); + x = test_dtls13_id_in(w + 1, test_dtls13_shard_of(a), 0); + ExpectIntEQ(test_dtls13_pool_put(&pool, x, data, msgSz, 0, first), 0); + ExpectNull(test_dtls13_held_for(&pool, a)); + ExpectNotNull(test_dtls13_held_for(&pool, x)); + ExpectIntEQ(test_dtls13_pool_count(&pool), capacity); + ExpectTrue(test_dtls13_pool_bytes(&pool) == (size_t)capacity * entry); + ExpectPtrEq(test_dtls13_pool_head(&pool), test_dtls13_held_for(&pool, b)); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + /* Released bytes go back to the budget. New bytes make b the newest. */ + if (EXPECT_SUCCESS()) { + WOLFSSL_SOCKADDR peer; + + peer.sz = sizeof(d); + peer.bufSz = sizeof(d); + peer.sa = &d; + DtlsChPoolDrop(&pool, NULL, &peer, DTLS_TIMEOUT_MAX); + } + ExpectIntEQ(test_dtls13_pool_count(&pool), capacity - 1); + ExpectIntEQ(test_dtls13_pool_put(&pool, b, data, msgSz, first, 1), 0); + ExpectNotNull(m = test_dtls13_held_for(&pool, b)); + ExpectNull(test_dtls13_pool_next(&pool, m)); + ExpectPtrEq(test_dtls13_pool_head(&pool), test_dtls13_held_for(&pool, c)); + ExpectTrue(test_dtls13_pool_bytes(&pool) == + (size_t)(capacity - 1) * entry + 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + /* y needs just more than is left: it evicts c, the oldest, from another + * shard when there is one, and not b */ + y = test_dtls13_id_in(x + 1, test_dtls13_shard_of(c), 0); + ExpectIntEQ(test_dtls13_pool_put(&pool, y, data, msgSz, 0, + first + (word32)left), 0); + ExpectNull(test_dtls13_held_for(&pool, c)); + ExpectNotNull(test_dtls13_held_for(&pool, b)); + ExpectNotNull(test_dtls13_held_for(&pool, y)); + ExpectIntEQ(test_dtls13_pool_count(&pool), capacity - 1); + ExpectTrue(test_dtls13_pool_bytes(&pool) == + (size_t)(capacity - 1) * entry + 1 + left); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + /* One more byte than the largest ClientHello the pool completes is + * dropped before anything is evicted */ + z = y + 1; + bytes = test_dtls13_pool_bytes(&pool); + i = test_dtls13_pool_count(&pool); + ExpectIntEQ(test_dtls13_pool_put(&pool, z, data, bigSz + 1, 0, 1), + DTLS_MSG_STORE_DROPPED); + ExpectTrue(test_dtls13_pool_bytes(&pool) == bytes); + ExpectIntEQ(test_dtls13_pool_count(&pool), i); + /* z holds one byte of the largest. The rest but its last byte evicts + * every other entry for its bound, and the last byte's bound is just + * what is left: it completes. */ + ExpectIntEQ(test_dtls13_pool_put(&pool, z, data, bigSz, 0, 1), 0); + ExpectNotNull(test_dtls13_held_for(&pool, z)); + ExpectIntEQ(test_dtls13_pool_count(&pool), i + 1); + ExpectIntEQ(test_dtls13_pool_put(&pool, z, data, bigSz, 1, bigSz - 2), + 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectTrue(test_dtls13_pool_bytes(&pool) == + TEST_DTLS_RX_UNVERIFIED_LIMIT - entry + first - 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + ExpectIntEQ(test_dtls13_pool_put(&pool, z, data, bigSz, bigSz - 1, 1), + 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), 0); + ExpectIntEQ(test_dtls13_pool_reserved(&pool), 0); + + /* With another store's add in flight, a duplicate adds nothing but + * evicts one entry, its bound. A new peer evicts one entry and gets in. */ + DtlsChPoolClear(&pool, NULL); + for (i = 1; i <= capacity; i++) + ExpectIntEQ(test_dtls13_pool_put(&pool, i, data, msgSz, 0, first), 0); + (void)wolfSSL_Atomic_Uint_AddFetch(&pool.reserved, + (WC_ATOMIC_UINT_ARG)TEST_DTLS_RX_UNVERIFIED_LIMIT); + ExpectIntEQ(test_dtls13_pool_put(&pool, 1, data, msgSz, 0, first), 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), capacity - 1); + ExpectNotNull(test_dtls13_held_for(&pool, 1)); + ExpectTrue(test_dtls13_pool_bytes(&pool) == + (size_t)(capacity - 1) * entry); + ExpectIntEQ(test_dtls13_pool_put(&pool, capacity + 1, data, msgSz, 0, + first), 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), capacity - 1); + ExpectNull(test_dtls13_held_for(&pool, 1)); + ExpectNotNull(test_dtls13_held_for(&pool, capacity + 1)); + (void)wolfSSL_Atomic_Uint_SubFetch(&pool.reserved, + (WC_ATOMIC_UINT_ARG)TEST_DTLS_RX_UNVERIFIED_LIMIT); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + DtlsChPoolClear(&pool, NULL); + ExpectIntEQ(test_dtls13_pool_count(&pool), 0); + ExpectIntEQ(test_dtls13_pool_reserved(&pool), 0); + DtlsChPoolFree(&pool, NULL); + XFREE(data, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return EXPECT_RESULT(); +} + +/* Eviction learns the storing peer's own entry when the lock-free scan lands + * on it as the oldest: the entry behind it in its shard goes then, before + * an older head elsewhere, and the shard is judged by its second published + * value from then on. With nothing elsewhere, the entry behind the peer's + * goes too. */ +int test_dtls13_ch_pool_evict_own_shard(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + DtlsChPool pool; + byte* data = NULL; + size_t overhead = sizeof(DtlsMsg) + sizeof(int) + sizeof(DtlsFragBucket); + word32 half = (word32)(TEST_DTLS_RX_UNVERIFIED_LIMIT / 2 - overhead); + DtlsMsg* m = NULL; + int p = 1; + int r; +#if WOLFSSL_DTLS_CH_POOL_SHARDS > 1 + const word32 msgSz = 1024; + const word32 first = 16; + size_t entry = overhead + first; + int capacity = (int)(TEST_DTLS_RX_UNVERIFIED_LIMIT / entry); + word32 left = (word32)(TEST_DTLS_RX_UNVERIFIED_LIMIT - + (size_t)capacity * entry); + int q; + int id; + int i; +#endif + + ExpectIntEQ(DtlsChPoolInit(&pool), 0); + ExpectNotNull(data = (byte*)XMALLOC(TEST_DTLS_RX_UNVERIFIED_LIMIT, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + if (EXPECT_FAIL()) { + DtlsChPoolFree(&pool, NULL); + return EXPECT_RESULT(); + } + XMEMSET(data, 0x5a, TEST_DTLS_RX_UNVERIFIED_LIMIT); + r = test_dtls13_id_in(p + 1, test_dtls13_shard_of(p), 1); + +#if WOLFSSL_DTLS_CH_POOL_SHARDS > 1 + /* p oldest, q the oldest elsewhere, r next behind p, then full */ + ExpectIntGT(capacity, 3); + q = test_dtls13_id_in(r + 1, test_dtls13_shard_of(p), 0); + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, msgSz, 0, first), 0); + ExpectIntEQ(test_dtls13_pool_put(&pool, q, data, msgSz, 0, first), 0); + ExpectIntEQ(test_dtls13_pool_put(&pool, r, data, msgSz, 0, first), 0); + for (i = 3, id = q + 1; i < capacity && EXPECT_SUCCESS(); i++, id++) + ExpectIntEQ(test_dtls13_pool_put(&pool, id, data, msgSz, 0, first), 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), capacity); + ExpectTrue(test_dtls13_pool_ok(&pool)); + /* p grows past what is left: the scan lands on p, so r goes, not the + * older q */ + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, msgSz, first, left + 1), + 0); + ExpectNull(test_dtls13_held_for(&pool, r)); + ExpectNotNull(test_dtls13_held_for(&pool, q)); + ExpectNotNull(m = test_dtls13_held_for(&pool, p)); + ExpectIntEQ(m != NULL ? (int)m->bytesReceived : -1, + (int)(first + left + 1)); + ExpectIntEQ(test_dtls13_pool_count(&pool), capacity - 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + DtlsChPoolClear(&pool, NULL); +#endif + + /* Two entries of half the budget, p ahead of r in one shard: p's growth + * evicts r, the entry behind its own */ + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, half + 2, 0, half), 0); + ExpectIntEQ(test_dtls13_pool_put(&pool, r, data, half + 2, 0, half), 0); + ExpectTrue(test_dtls13_pool_bytes(&pool) == TEST_DTLS_RX_UNVERIFIED_LIMIT); + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, half + 2, half, 1), 0); + ExpectNull(test_dtls13_held_for(&pool, r)); + ExpectNotNull(m = test_dtls13_held_for(&pool, p)); + ExpectIntEQ(m != NULL ? (int)m->bytesReceived : -1, (int)half + 1); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + DtlsChPoolFree(&pool, NULL); + XFREE(data, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return EXPECT_RESULT(); +} + +/* The entry behind the storing peer's own goes when the scan lands on the + * peer's entry as the oldest of the pool, and the shard competes with its + * second published value after that. The published values follow the head + * as it leaves. */ +int test_dtls13_ch_pool_evict_behind_own(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + DtlsChPool pool; + byte* data = NULL; + const word32 msgSz = 1024; + const word32 first = 16; + size_t entry = sizeof(DtlsMsg) + sizeof(int) + sizeof(DtlsFragBucket) + + first; + int capacity = (int)(TEST_DTLS_RX_UNVERIFIED_LIMIT / entry); + word32 left = (word32)(TEST_DTLS_RX_UNVERIFIED_LIMIT - + (size_t)capacity * entry); + const DtlsChPoolShard* sh; + DtlsMsg* m = NULL; + word32 progress = 0; + int p = 1; + int q, r; + int id; + int i; + + ExpectIntGT(capacity, 3); + ExpectIntEQ(DtlsChPoolInit(&pool), 0); + ExpectNotNull(data = (byte*)XMALLOC(TEST_DTLS_RX_UNVERIFIED_LIMIT, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + if (EXPECT_FAIL()) { + DtlsChPoolFree(&pool, NULL); + return EXPECT_RESULT(); + } + XMEMSET(data, 0x5a, TEST_DTLS_RX_UNVERIFIED_LIMIT); + sh = &pool.shard[test_dtls13_shard_of(p)]; + r = test_dtls13_id_in(p + 1, test_dtls13_shard_of(p), 1); + q = test_dtls13_id_in(r + 1, test_dtls13_shard_of(p), 0); + + /* p heads its shard with r behind it: both are published */ + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, msgSz, 0, first), 0); + ExpectNotNull(m = test_dtls13_held_for(&pool, p)); + ExpectTrue(m != NULL && test_dtls13_head_progress(sh) == m->progress); + ExpectTrue(test_dtls13_next_progress(sh) == 0); + ExpectIntEQ(test_dtls13_pool_put(&pool, r, data, msgSz, 0, first), 0); + ExpectNotNull(m = test_dtls13_held_for(&pool, r)); + ExpectTrue(m != NULL && test_dtls13_next_progress(sh) == m->progress); + /* q and the rest are younger, in any shard, until full */ + ExpectIntEQ(test_dtls13_pool_put(&pool, q, data, msgSz, 0, first), 0); + for (i = 3, id = q + 1; i < capacity && EXPECT_SUCCESS(); i++, id++) + ExpectIntEQ(test_dtls13_pool_put(&pool, id, data, msgSz, 0, first), 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), capacity); + ExpectPtrEq(test_dtls13_pool_next(&pool, test_dtls13_held_for(&pool, p)), + test_dtls13_held_for(&pool, r)); + ExpectTrue(test_dtls13_pool_ok(&pool)); + /* p grows past what is left: r goes, the oldest but p, not q */ + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, msgSz, first, left + 1), + 0); + ExpectNull(test_dtls13_held_for(&pool, r)); + ExpectNotNull(test_dtls13_held_for(&pool, q)); + ExpectNotNull(m = test_dtls13_held_for(&pool, p)); + ExpectIntEQ(m != NULL ? (int)m->bytesReceived : -1, + (int)(first + left + 1)); + ExpectIntEQ(test_dtls13_pool_count(&pool), capacity - 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + DtlsChPoolClear(&pool, NULL); + + /* The head leaves, to the young end on new bytes and then dropped: the + * published values follow */ + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, msgSz, 0, first), 0); + ExpectIntEQ(test_dtls13_pool_put(&pool, r, data, msgSz, 0, first), 0); + ExpectNotNull(m = test_dtls13_held_for(&pool, r)); + progress = m != NULL ? m->progress : 0; + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, msgSz, first, 1), 0); + ExpectTrue(test_dtls13_head_progress(sh) == progress); + ExpectNotNull(m = test_dtls13_held_for(&pool, p)); + ExpectTrue(m != NULL && test_dtls13_next_progress(sh) == m->progress); + if (EXPECT_SUCCESS()) { + WOLFSSL_SOCKADDR peer; + + peer.sz = sizeof(r); + peer.bufSz = sizeof(r); + peer.sa = &r; + DtlsChPoolDrop(&pool, NULL, &peer, DTLS_TIMEOUT_MAX); + } + ExpectTrue(m != NULL && test_dtls13_head_progress(sh) == m->progress); + ExpectTrue(test_dtls13_next_progress(sh) == 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + DtlsChPoolFree(&pool, NULL); + XFREE(data, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return EXPECT_RESULT(); +} + +/* A store of an entry already held reserves the bound of its fragment, a + * new entry holding it, and settles to what the fragment added: nothing for + * a duplicate, its new bytes for a merge, a bucket more for a gap. */ +int test_dtls13_ch_pool_reserve_bound(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + DtlsChPool pool; + byte data[64]; + size_t overhead = sizeof(DtlsMsg) + sizeof(int) + sizeof(DtlsFragBucket); + int p = 1; + + XMEMSET(data, 0x5a, sizeof(data)); + ExpectIntEQ(DtlsChPoolInit(&pool), 0); + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, 64, 0, 8), 0); + ExpectIntEQ(test_dtls13_pool_reserved(&pool), overhead + 8); + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, 64, 0, 8), 0); + ExpectIntEQ(test_dtls13_pool_reserved(&pool), overhead + 8); + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, 64, 4, 8), 0); + ExpectIntEQ(test_dtls13_pool_reserved(&pool), overhead + 12); + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, 64, 20, 4), 0); + ExpectIntEQ(test_dtls13_pool_reserved(&pool), + overhead + sizeof(DtlsFragBucket) + 16); + /* Closing the gap gives the bucket back */ + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, 64, 12, 8), 0); + ExpectIntEQ(test_dtls13_pool_reserved(&pool), overhead + 24); + ExpectTrue(test_dtls13_pool_reserved(&pool) == + test_dtls13_pool_bytes(&pool)); + ExpectTrue(test_dtls13_pool_ok(&pool)); + DtlsChPoolFree(&pool, NULL); +#endif + return EXPECT_RESULT(); +} + +/* At capacity a retransmitted fragment evicts another peer's oldest entry + * for its bound, though it adds nothing: the bound is reserved before the + * entry is looked at. With the room there, it evicts nothing. */ +int test_dtls13_ch_pool_evict_for_bound(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + DtlsChPool pool; + byte* data = NULL; + const word32 msgSz = 512; + const word32 first = 16; + size_t entry = sizeof(DtlsMsg) + sizeof(int) + sizeof(DtlsFragBucket) + + first; + int capacity = (int)(TEST_DTLS_RX_UNVERIFIED_LIMIT / entry); + DtlsMsg* m = NULL; + int i; + + ExpectIntGT(capacity, 2); + ExpectIntEQ(DtlsChPoolInit(&pool), 0); + ExpectNotNull(data = (byte*)XMALLOC(msgSz, NULL, DYNAMIC_TYPE_TMP_BUFFER)); + if (EXPECT_FAIL()) { + DtlsChPoolFree(&pool, NULL); + return EXPECT_RESULT(); + } + XMEMSET(data, 0x5a, msgSz); + for (i = 1; i <= capacity && EXPECT_SUCCESS(); i++) + ExpectIntEQ(test_dtls13_pool_put(&pool, i, data, msgSz, 0, first), 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), capacity); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + /* The newest peer's first fragment again: peer 1 goes for its bound */ + ExpectIntEQ(test_dtls13_pool_put(&pool, capacity, data, msgSz, 0, first), + 0); + ExpectNull(test_dtls13_held_for(&pool, 1)); + ExpectIntEQ(test_dtls13_pool_count(&pool), capacity - 1); + ExpectTrue(test_dtls13_pool_bytes(&pool) == + (size_t)(capacity - 1) * entry); + ExpectNotNull(m = test_dtls13_held_for(&pool, capacity)); + ExpectIntEQ(m != NULL ? (int)m->bytesReceived : -1, (int)first); + ExpectPtrEq(test_dtls13_pool_head(&pool), test_dtls13_held_for(&pool, 2)); + ExpectTrue(test_dtls13_pool_ok(&pool)); + /* Again, in the room that made: nothing goes */ + ExpectIntEQ(test_dtls13_pool_put(&pool, capacity, data, msgSz, 0, first), + 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), capacity - 1); + ExpectPtrEq(test_dtls13_pool_head(&pool), test_dtls13_held_for(&pool, 2)); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + DtlsChPoolFree(&pool, NULL); + XFREE(data, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return EXPECT_RESULT(); +} + +/* When the storing peer's own entry is the oldest of the pool, eviction + * learns of it under the lock and goes on: the entry behind it in its shard + * goes, with none the oldest head of another shard, and with nothing but + * the peer's entry anywhere the store is dropped and the entry stays. */ +int test_dtls13_ch_pool_evict_own_oldest(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + DtlsChPool pool; + byte* data = NULL; + size_t overhead = sizeof(DtlsMsg) + sizeof(int) + sizeof(DtlsFragBucket); + word32 third = (word32)(TEST_DTLS_RX_UNVERIFIED_LIMIT / 3 - overhead); + word32 bigSz = (word32)(TEST_DTLS_RX_UNVERIFIED_LIMIT - 2 * overhead); + DtlsMsg* m = NULL; + int p = 1; + int q, r; +#if WOLFSSL_DTLS_CH_POOL_SHARDS > 1 + word32 half = (word32)(TEST_DTLS_RX_UNVERIFIED_LIMIT / 2 - overhead); +#endif + + ExpectIntEQ(DtlsChPoolInit(&pool), 0); + ExpectNotNull(data = (byte*)XMALLOC(TEST_DTLS_RX_UNVERIFIED_LIMIT, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + if (EXPECT_FAIL()) { + DtlsChPoolFree(&pool, NULL); + return EXPECT_RESULT(); + } + XMEMSET(data, 0x5a, TEST_DTLS_RX_UNVERIFIED_LIMIT); + r = test_dtls13_id_in(p + 1, test_dtls13_shard_of(p), 1); + q = test_dtls13_id_in(r + 1, test_dtls13_shard_of(p), 0); + + /* p, q, then r behind p in its shard, a third of the budget each: one + * more byte for p evicts r, though q is older */ + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, third + 2, 0, third), 0); + ExpectIntEQ(test_dtls13_pool_put(&pool, q, data, third + 2, 0, third), 0); + ExpectIntEQ(test_dtls13_pool_put(&pool, r, data, third + 2, 0, third), 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), 3); + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, third + 2, third, 1), 0); + ExpectNull(test_dtls13_held_for(&pool, r)); + ExpectNotNull(test_dtls13_held_for(&pool, q)); + ExpectNotNull(m = test_dtls13_held_for(&pool, p)); + ExpectIntEQ(m != NULL ? (int)m->bytesReceived : -1, (int)third + 1); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); + ExpectTrue(test_dtls13_pool_ok(&pool)); + DtlsChPoolClear(&pool, NULL); + +#if WOLFSSL_DTLS_CH_POOL_SHARDS > 1 + /* p alone in its shard, q in another, half each: q goes */ + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, half + 2, 0, half), 0); + ExpectIntEQ(test_dtls13_pool_put(&pool, q, data, half + 2, 0, half), 0); + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, half + 2, half, 1), 0); + ExpectNull(test_dtls13_held_for(&pool, q)); + ExpectNotNull(m = test_dtls13_held_for(&pool, p)); + ExpectIntEQ(m != NULL ? (int)m->bytesReceived : -1, (int)half + 1); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + DtlsChPoolClear(&pool, NULL); +#endif + + /* Nothing but p, holding two buckets of the largest ClientHello: the + * bound of its rest is one bucket more than the merge would leave, so + * it is dropped and p stays */ + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, bigSz, 0, 1), 0); + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, bigSz, 2, 1), 0); + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, bigSz, 3, bigSz - 3), + DTLS_MSG_STORE_DROPPED); + ExpectNotNull(m = test_dtls13_held_for(&pool, p)); + ExpectIntEQ(m != NULL ? (int)m->bytesReceived : -1, 2); + ExpectIntEQ(m != NULL ? (int)m->fragBucketListCount : -1, 2); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + DtlsChPoolFree(&pool, NULL); + XFREE(data, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return EXPECT_RESULT(); +} + +/* A fragment opening one gap too many is refused under the lock, after its + * bound was reserved and, at capacity, evicted for: the reservation goes + * back and the counter is what the shards retain. */ +int test_dtls13_ch_pool_bucket_cap(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + DtlsChPool pool; + byte* data = NULL; + const word32 msgSz = 512; + const word32 first = 16; + const word32 gapSz = 2 * DTLS_FRAG_POOL_SZ + first; + size_t entry = sizeof(DtlsMsg) + sizeof(int) + sizeof(DtlsFragBucket) + + first; + size_t held = 0; + int capacity = 0; + DtlsMsg* m = NULL; + int p = 1; + int r; + int id; + int i; + + ExpectIntEQ(DtlsChPoolInit(&pool), 0); + ExpectNotNull(data = (byte*)XMALLOC(msgSz, NULL, DYNAMIC_TYPE_TMP_BUFFER)); + if (EXPECT_FAIL()) { + DtlsChPoolFree(&pool, NULL); + return EXPECT_RESULT(); + } + XMEMSET(data, 0x5a, msgSz); + for (i = 0; i < DTLS_FRAG_POOL_SZ && EXPECT_SUCCESS(); i++) + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, gapSz, 2 * i, 1), 0); + ExpectNotNull(m = test_dtls13_held_for(&pool, p)); + ExpectIntEQ(m != NULL ? (int)m->fragBucketListCount : -1, + DTLS_FRAG_POOL_SZ); + held = test_dtls13_pool_bytes(&pool); + + /* With room: refused, nothing changes */ + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, gapSz, + 2 * DTLS_FRAG_POOL_SZ, first), DTLS_MSG_STORE_DROPPED); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectIntEQ(test_dtls13_pool_reserved(&pool), held); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + /* At capacity, r behind p in its shard and the rest anywhere: r goes + * for the bound, then the fragment is refused */ + r = test_dtls13_id_in(p + 1, test_dtls13_shard_of(p), 1); + capacity = (int)((TEST_DTLS_RX_UNVERIFIED_LIMIT - held) / entry); + ExpectIntGT(capacity, 1); + for (i = 0, id = r; i < capacity && EXPECT_SUCCESS(); i++, id++) + ExpectIntEQ(test_dtls13_pool_put(&pool, id, data, msgSz, 0, first), 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), capacity + 1); + ExpectIntEQ(test_dtls13_pool_put(&pool, p, data, gapSz, + 2 * DTLS_FRAG_POOL_SZ, first), DTLS_MSG_STORE_DROPPED); + ExpectNull(test_dtls13_held_for(&pool, r)); + ExpectIntEQ(test_dtls13_pool_count(&pool), capacity); + ExpectIntEQ(m != NULL ? (int)m->fragBucketListCount : -1, + DTLS_FRAG_POOL_SZ); + ExpectIntEQ(test_dtls13_pool_reserved(&pool), + held + (size_t)(capacity - 1) * entry); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + DtlsChPoolFree(&pool, NULL); + XFREE(data, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return EXPECT_RESULT(); +} + +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(SINGLE_THREADED) +#define TEST_CH_SHARD_THREADS 4 +#define TEST_CH_SHARD_OPS 4000 +#define TEST_CH_SHARD_PEERS 200 +#define TEST_CH_SHARD_MSG_SZ ((TEST_DTLS_RX_UNVERIFIED_LIMIT / 8) & ~3) + +static DtlsChPool test_ch_shard_pool; +static int test_ch_shard_bad[TEST_CH_SHARD_THREADS + 1]; +static int test_ch_shard_out[TEST_CH_SHARD_THREADS]; +static int test_ch_shard_snaps; +static int test_ch_shard_running; +static wolfSSL_Mutex test_ch_shard_mutex; +static byte test_ch_shard_data[TEST_CH_SHARD_THREADS][TEST_CH_SHARD_MSG_SZ]; + +/* Whether an entry of a shard after i has m's progress */ +static int test_ch_shard_progress_taken(const DtlsChPool* p, int i, + const DtlsMsg* m) +{ + const DtlsMsg* e; + + for (i++; i < WOLFSSL_DTLS_CH_POOL_SHARDS; i++) { + for (e = p->shard[i].head; e != NULL; e = e->next) { + if (e->progress == m->progress) + return 1; + } + } + return 0; +} + +/* With every shard lock held: counters match the lists in progress order, no + * progress repeats, and the shards retain what is reserved, within the budget. + * The library holds one shard lock at most, so taking them all cannot + * deadlock. */ +static int test_ch_shard_snapshot(DtlsChPool* p) +{ + size_t sum = 0; + int ok = 1; + int i; + + for (i = 0; i < WOLFSSL_DTLS_CH_POOL_SHARDS; i++) { + if (wc_LockMutex(&p->shard[i].lock) != 0) + ok = 0; + } + for (i = 0; i < WOLFSSL_DTLS_CH_POOL_SHARDS; i++) { + const DtlsChPoolShard* sh = &p->shard[i]; + const DtlsMsg* m; + + if (!test_dtls13_head_progress_ok(sh)) + ok = 0; + for (m = sh->head; m != NULL; m = m->next) { + if (m->ready || DtlsChPoolShardIdx(&m->peer) != (word32)i) + ok = 0; + if (m->next != NULL && m->next->progress <= m->progress) + ok = 0; + if (test_ch_shard_progress_taken(p, i, m)) + ok = 0; + } + if (sh->bytes != DtlsMsgListRetainedBytes(sh->head)) + ok = 0; + sum += sh->bytes; + } + /* A store evicting holds its reservation with no shard locked */ + if (sum > test_dtls13_pool_reserved(p) || + sum > TEST_DTLS_RX_UNVERIFIED_LIMIT) + ok = 0; + for (i = WOLFSSL_DTLS_CH_POOL_SHARDS - 1; i >= 0; i--) + wc_UnLockMutex(&p->shard[i].lock); + return ok; +} + +static THREAD_RETURN WOLFSSL_THREAD test_dtls13_ch_shard_worker(void* args) +{ + int tno = ((func_args*)args)->argc; + word32 rnd = 0x85ebca6bU * (word32)(tno + 1); + byte addr[2]; + byte* data = test_ch_shard_data[tno]; + WOLFSSL_SOCKADDR peer; + int i; + + peer.sz = sizeof(addr); + peer.bufSz = sizeof(addr); + peer.sa = addr; + addr[1] = 0x42; + for (i = 0; i < TEST_CH_SHARD_OPS; i++) { + DtlsMsg* msg = NULL; + word32 op; + word32 quarter; + + rnd = rnd * 1103515245U + 12345U; + addr[0] = (byte)((rnd >> 16) % TEST_CH_SHARD_PEERS); + op = (rnd >> 8) % 256; + quarter = (rnd >> 24) % 4; + XMEMSET(data, addr[0] + 1, TEST_CH_SHARD_MSG_SZ); + if (op < 24) { + msg = DtlsChPoolTakeRest(&test_ch_shard_pool, NULL, &peer, 0, 1, 0, + TEST_CH_SHARD_MSG_SZ, DTLS_TIMEOUT_MAX, NULL); + } + else { + (void)DtlsChPoolStore(&test_ch_shard_pool, NULL, &peer, 0, 1, data, + TEST_CH_SHARD_MSG_SZ, client_hello, + quarter * (TEST_CH_SHARD_MSG_SZ / 4), TEST_CH_SHARD_MSG_SZ / 4, + DTLS_TIMEOUT_MAX, NULL, &msg); + } + if (msg != NULL) { + if (!test_ch_pool_msg_ok(msg, addr[0], TEST_CH_SHARD_MSG_SZ)) + test_ch_shard_bad[tno]++; + test_ch_shard_out[tno]++; + DtlsMsgListDelete(msg, NULL); + } + } + if (wc_LockMutex(&test_ch_shard_mutex) == 0) { + test_ch_shard_running--; + wc_UnLockMutex(&test_ch_shard_mutex); + } + WOLFSSL_RETURN_FROM_THREAD(0); +} + +/* Checks the pool while the workers run, until they are done. It sleeps + * between snapshots: a snapshot holds every shard lock, and mutexes are not + * fair, so without the sleep the workers starve on some schedulers. */ +static THREAD_RETURN WOLFSSL_THREAD test_dtls13_ch_shard_monitor(void* args) +{ + int running = 1; + + (void)args; + while (running > 0) { + if (!test_ch_shard_snapshot(&test_ch_shard_pool)) + test_ch_shard_bad[TEST_CH_SHARD_THREADS]++; + test_ch_shard_snaps++; + XSLEEP_MS(1); + if (wc_LockMutex(&test_ch_shard_mutex) != 0) + break; + running = test_ch_shard_running; + wc_UnLockMutex(&test_ch_shard_mutex); + } + WOLFSSL_RETURN_FROM_THREAD(0); +} +#endif + +/* Threads store, complete and release ClientHellos of many peers across the + * shards of one pool, past its budget, while one checks the budget holds. */ +int test_dtls13_ch_pool_shards_threads(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(SINGLE_THREADED) + THREAD_TYPE threads[TEST_CH_SHARD_THREADS + 1]; + func_args args[TEST_CH_SHARD_THREADS + 1]; + int out = 0; + int i; + + XMEMSET(args, 0, sizeof(args)); + XMEMSET(test_ch_shard_bad, 0, sizeof(test_ch_shard_bad)); + XMEMSET(test_ch_shard_out, 0, sizeof(test_ch_shard_out)); + test_ch_shard_snaps = 0; + test_ch_shard_running = TEST_CH_SHARD_THREADS; + ExpectTrue(TEST_CH_SHARD_PEERS * (sizeof(DtlsMsg) + 2 + + sizeof(DtlsFragBucket) + TEST_CH_SHARD_MSG_SZ / 4) > + 4 * TEST_DTLS_RX_UNVERIFIED_LIMIT); + ExpectIntEQ(wc_InitMutex(&test_ch_shard_mutex), 0); + ExpectIntEQ(DtlsChPoolInit(&test_ch_shard_pool), 0); + if (EXPECT_FAIL()) + return EXPECT_RESULT(); + for (i = 0; i <= TEST_CH_SHARD_THREADS; i++) { + args[i].argc = i; + start_thread(i < TEST_CH_SHARD_THREADS ? test_dtls13_ch_shard_worker : + test_dtls13_ch_shard_monitor, &args[i], &threads[i]); + } + for (i = 0; i <= TEST_CH_SHARD_THREADS; i++) + join_thread(threads[i]); + for (i = 0; i < TEST_CH_SHARD_THREADS; i++) { + ExpectIntEQ(test_ch_shard_bad[i], 0); + out += test_ch_shard_out[i]; + } + ExpectIntEQ(test_ch_shard_bad[TEST_CH_SHARD_THREADS], 0); + ExpectIntGT(test_ch_shard_snaps, 0); + ExpectIntGT(out, 0); + ExpectTrue(test_dtls13_pool_ok(&test_ch_shard_pool)); + ExpectIntLE(test_dtls13_pool_count(&test_ch_shard_pool), + TEST_CH_SHARD_PEERS); + DtlsChPoolFree(&test_ch_shard_pool, NULL); + ExpectIntEQ(wc_FreeMutex(&test_ch_shard_mutex), 0); +#endif + return EXPECT_RESULT(); +} + int test_dtls_empty_keyshare_with_cookie(void) { EXPECT_DECLS; diff --git a/tests/api/test_dtls13.h b/tests/api/test_dtls13.h index 154e8a74b8a..8c62464655d 100644 --- a/tests/api/test_dtls13.h +++ b/tests/api/test_dtls13.h @@ -36,8 +36,54 @@ int test_dtls13_frag_ch_pq_no_cookie(void); int test_dtls_frag_ch(void); int test_dtls13_frag_ch1_no_cookie(void); int test_dtls13_no_cookie_handoff(void); +int test_dtls13_clear_ch_pool_api(void); +int test_dtls13_reassemble_ch_before_cookie_api(void); +int test_dtls13_frag_ch1_reassemble(void); +int test_dtls13_frag_ch2_cookie_not_first(void); +int test_dtls13_frag_ch2_cookie_frag_last(void); +int test_dtls13_frag_ch2_forged_first_frag(void); +int test_dtls13_frag_ch2_overlap_handoff(void); +int test_dtls13_frag_ch1_interleaved_peers(void); +int test_dtls13_frag_ch1_evict_oldest(void); +int test_dtls13_frag_ch1_over_bound(void); +int test_dtls13_frag_ch1_length_mismatch(void); +int test_dtls13_frag_past_end(void); +int test_dtls13_frag_ch2_planted_fragment(void); +int test_dtls13_frag_ch2_stale_fragment(void); +int test_dtls13_frag_ch1_no_cookie_conflict(void); +int test_dtls13_frag_ch1_bad_reassembled(void); +int test_dtls13_frag_ch2_verify_keeps_other_peers(void); +int test_dtls13_frag_ch_ctx_two_listeners(void); +int test_dtls13_frag_ch_ctx_survives_listener_free(void); +int test_dtls13_frag_ch_ctx_free_pending(void); +int test_dtls13_frag_ch1_ctx_shared_budget(void); +int test_dtls13_frag_ch_no_addr_dropped(void); +int test_dtls13_frag_ch1_ctx_budget_two_objects(void); +int test_dtls13_ch_whole_async_not_pooled(void); +int test_dtls13_ch_async_no_addr(void); +int test_dtls13_frag_ch2_ctx_handoff(void); +int test_dtls13_frag_ch2_ctx_cookie_mismatch(void); +int test_dtls13_frag_ch_ctx_set_ssl_ctx(void); +int test_dtls13_frag_ch1_ctx_stale_partial(void); +int test_dtls13_frag_ch_ctx_static_memory(void); int test_dtls13_frag_store_fatal(void); int test_dtls13_clear_frees_rx_list(void); +int test_dtls13_frag_ch2_bad_cookie_dropped(void); +int test_dtls13_frag_ch2_dtls12_cookie(void); +int test_dtls13_ch_pool_threads(void); +int test_dtls13_ch_pool_shards_threads(void); +int test_dtls13_ch_pool_handshake_threads(void); +int test_dtls13_ch_pool_unit(void); +int test_dtls13_ch_pool_shards(void); +int test_dtls13_ch_pool_evict_own_shard(void); +int test_dtls13_ch_pool_evict_behind_own(void); +int test_dtls13_ch_pool_reserve_bound(void); +int test_dtls13_ch_pool_evict_for_bound(void); +int test_dtls13_ch_pool_evict_own_oldest(void); +int test_dtls13_ch_pool_bucket_cap(void); +int test_dtls13_ch_pool_peer_key(void); +int test_dtls13_ch_pool_max_age(void); +int test_dtls13_ch_pool_ctx_init(void); int test_dtls_empty_keyshare_with_cookie(void); int test_dtls13_missing_finished_client(void); int test_dtls13_missing_finished_server(void); @@ -81,8 +127,54 @@ int test_dtls13_update_keys_want_write(void); TEST_DECL_GROUP("dtls13", test_dtls_frag_ch), \ TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch1_no_cookie), \ TEST_DECL_GROUP("dtls13", test_dtls13_no_cookie_handoff), \ + TEST_DECL_GROUP("dtls13", test_dtls13_clear_ch_pool_api), \ + TEST_DECL_GROUP("dtls13", test_dtls13_reassemble_ch_before_cookie_api), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch1_reassemble), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch2_cookie_not_first), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch2_cookie_frag_last), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch2_forged_first_frag), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch2_overlap_handoff), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch1_interleaved_peers), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch1_evict_oldest), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_unit), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_shards), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_evict_own_shard), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_evict_behind_own), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_reserve_bound), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_evict_for_bound), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_evict_own_oldest), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_bucket_cap), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_peer_key), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_max_age), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_ctx_init), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch1_over_bound), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch1_length_mismatch), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_past_end), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch2_planted_fragment), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch2_stale_fragment), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch1_no_cookie_conflict), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch1_bad_reassembled), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch2_verify_keeps_other_peers), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch_ctx_two_listeners), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch_ctx_survives_listener_free), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch_ctx_free_pending), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch1_ctx_shared_budget), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch_no_addr_dropped), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch1_ctx_budget_two_objects), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_whole_async_not_pooled), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_async_no_addr), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch2_ctx_handoff), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch2_ctx_cookie_mismatch), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch_ctx_set_ssl_ctx), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch1_ctx_stale_partial), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch_ctx_static_memory), \ TEST_DECL_GROUP("dtls13", test_dtls13_frag_store_fatal), \ TEST_DECL_GROUP("dtls13", test_dtls13_clear_frees_rx_list), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch2_bad_cookie_dropped), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch2_dtls12_cookie), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_threads), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_shards_threads), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_handshake_threads), \ TEST_DECL_GROUP("dtls13", test_dtls_empty_keyshare_with_cookie), \ TEST_DECL_GROUP("dtls13", test_dtls13_missing_finished_client), \ TEST_DECL_GROUP("dtls13", test_dtls13_missing_finished_server), \ diff --git a/wolfcrypt/src/misc.c b/wolfcrypt/src/misc.c index f4b81fb8373..f2ffdf16066 100644 --- a/wolfcrypt/src/misc.c +++ b/wolfcrypt/src/misc.c @@ -1509,18 +1509,21 @@ WC_MISC_STATIC WC_INLINE w64wrapper w64Mul(word32 a, word32 b) #endif /* WOLFSSL_W64_WRAPPER */ #if defined(HAVE_SESSION_TICKET) || !defined(NO_CERTS) || \ - !defined(NO_SESSION_CACHE) + !defined(NO_SESSION_CACHE) || \ + (defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER)) /* Make a word from the front of random hash */ WC_MISC_STATIC WC_INLINE word32 MakeWordFromHash(const byte* hashID) { return ((word32)hashID[0] << 24) | ((word32)hashID[1] << 16) | ((word32)hashID[2] << 8) | (word32)hashID[3]; } -#endif /* HAVE_SESSION_TICKET || !NO_CERTS || !NO_SESSION_CACHE */ +#endif /* HAVE_SESSION_TICKET || !NO_CERTS || !NO_SESSION_CACHE || + * (WOLFSSL_DTLS_CH_FRAG && !NO_WOLFSSL_SERVER) */ #if !defined(WOLFCRYPT_ONLY) && !defined(NO_HASH_WRAPPER) && \ - (!defined(NO_SESSION_CACHE) || defined(HAVE_SESSION_TICKET)) + (!defined(NO_SESSION_CACHE) || defined(HAVE_SESSION_TICKET) || \ + (defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER))) #include @@ -1543,7 +1546,8 @@ WC_MISC_STATIC WC_INLINE word32 HashObject(const byte* o, word32 len, return *error == 0 ? MakeWordFromHash(digest) : 0; /* 0 on failure */ } #endif /* WOLFCRYPT_ONLY && !NO_HASH_WRAPPER && - * (!NO_SESSION_CACHE || HAVE_SESSION_TICKET) */ + * (!NO_SESSION_CACHE || HAVE_SESSION_TICKET || + * (WOLFSSL_DTLS_CH_FRAG && !NO_WOLFSSL_SERVER)) */ WC_MISC_STATIC WC_INLINE char* CopyString(const char* src, int srcLen, void* heap, int type) { diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 610542b2f1d..b7995bb32e7 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -1251,6 +1251,28 @@ enum { #ifndef WOLFSSL_DTLS_RX_MAX_BYTES #define WOLFSSL_DTLS_RX_MAX_BYTES (MAX_HANDSHAKE_SZ + 16UL * 1024UL) #endif +/* The most a WOLFSSL_CTX holds for ClientHellos whose cookie has not verified, + * counted like the above: one budget for all its servers, one entry per peer. */ +#ifndef WOLFSSL_DTLS_RX_UNVERIFIED_MAX_BYTES + #define WOLFSSL_DTLS_RX_UNVERIFIED_MAX_BYTES (64UL * 1024UL) +#endif +/* Shards of the context pool, each with its own lock, peers spread over them + * by a hash of their address. The budget above is for all. More than one + * needs HashObject(), which NO_HASH_WRAPPER leaves out. */ +#ifndef WOLFSSL_DTLS_CH_POOL_SHARDS + #if defined(SINGLE_THREADED) || defined(NO_HASH_WRAPPER) + #define WOLFSSL_DTLS_CH_POOL_SHARDS 1 + #else + #define WOLFSSL_DTLS_CH_POOL_SHARDS 8 + #endif +#endif +#if WOLFSSL_DTLS_CH_POOL_SHARDS < 1 + #error WOLFSSL_DTLS_CH_POOL_SHARDS must be at least 1 +#endif +#if WOLFSSL_DTLS_CH_POOL_SHARDS > 1 && defined(NO_HASH_WRAPPER) + #error WOLFSSL_DTLS_CH_POOL_SHARDS above 1 needs the hash wrappers +#endif + /* set minimum DH key size allowed */ #ifndef WOLFSSL_MIN_DHKEY_BITS #if defined(WOLFSSL_HARDEN_TLS) && !defined(WOLFSSL_HARDEN_TLS_NO_PKEY_CHECK) @@ -3070,6 +3092,43 @@ struct WOLFSSL_SOCKADDR { void* sa; /* pointer to the sockaddr_in or sockaddr_in6 */ }; +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) +/* The pool's budget and eviction order are kept with atomics, so threads + * storing for different peers never wait on each other */ +#if !defined(SINGLE_THREADED) && defined(WOLFSSL_NO_ATOMICS) + #error "WOLFSSL_DTLS_CH_FRAG needs atomics unless SINGLE_THREADED" +#endif +/* Entries expire by LowResTimer() */ +#ifdef NO_ASN_TIME + #error "WOLFSSL_DTLS_CH_FRAG needs a clock: NO_ASN_TIME is not supported" +#endif + +/* The entries of the peers whose address hashes to one shard, least + * recently progressed first */ +typedef struct DtlsChPoolShard { + struct DtlsMsg* head; + size_t bytes; /* retained by its entries */ + /* Progress of the shard's two oldest entries, 0 when none, published at + * each unlock. Eviction scans them lock-free and checks the real entry + * under the lock: a stale value is only ever too old, so a miss. */ + wolfSSL_Atomic_Uint headProgress; + wolfSSL_Atomic_Uint nextProgress; +#ifndef SINGLE_THREADED + wolfSSL_Mutex lock; +#endif +} DtlsChPoolShard; + +/* ClientHellos a DTLS 1.3 server holds before their cookie verifies, owned by + * a WOLFSSL_CTX whose heap each call takes. One entry per peer, one budget. */ +typedef struct DtlsChPool { + DtlsChPoolShard shard[WOLFSSL_DTLS_CH_POOL_SHARDS]; + wolfSSL_Atomic_Uint reserved; /* shard bytes plus a bound per store in + * progress, evicted for before it locks */ + wolfSSL_Atomic_Uint lastProgress; /* last progress value given out */ + byte ready; /* initialized: only a DTLS context's is */ +} DtlsChPool; +#endif + #ifdef WOLFSSL_DTLS typedef struct WOLFSSL_DTLS_CTX { #ifdef WOLFSSL_RW_THREADED @@ -4670,6 +4729,10 @@ struct WOLFSSL_CTX { #if defined(WOLFSSL_DTLS) && defined(WOLFSSL_SCTP) byte dtlsSctp:1; /* DTLS-over-SCTP mode */ #endif +#if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_DTLS_CH_FRAG) + byte dtls13ReassembleChBeforeCookie:1; /* Server reassembles a CH + * before the cookie check */ +#endif #if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) byte disableECH:1; byte enableEchTrialDecrypt:1; /* Trial decryption of the @@ -4693,6 +4756,10 @@ struct WOLFSSL_CTX { (defined(WOLFSSL_SCTP) || defined(WOLFSSL_DTLS_MTU)) word16 dtlsMtuSz; /* DTLS MTU size */ #endif +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + DtlsChPool dtlsChPool; /* unverified ClientHellos of all + * servers */ +#endif #ifndef NO_DH word16 minDhKeySz; /* minimum DH key size */ word16 maxDhKeySz; /* maximum DH key size */ @@ -6070,6 +6137,7 @@ struct Options { #endif #ifdef WOLFSSL_DTLS_CH_FRAG word16 dtls13ChFrag:1; + word16 dtls13ReassembleChBeforeCookie:1; #endif #endif #ifdef WOLFSSL_TLS13 @@ -6601,6 +6669,13 @@ typedef struct DtlsMsg { word32 fragBucketListCount; byte ready:1; byte encrypted:1; +#ifdef WOLFSSL_DTLS_CH_FRAG + WOLFSSL_SOCKADDR peer; /* remote address a pooled ClientHello is + * keyed on */ + word32 heldAt; /* LowResTimer() when a pooled ClientHello + * last gained bytes */ + word32 progress; /* pool order of that gain */ +#endif } DtlsMsg; @@ -8143,6 +8218,14 @@ WOLFSSL_LOCAL int Tls13KeyUpdateLimitReached(WOLFSSL* ssl); #define DtlsMsgStore wolfSSL_DtlsMsgStore #define DtlsMsgListRetainedBytes wolfSSL_DtlsMsgListRetainedBytes #define DtlsMsgStoreFatal wolfSSL_DtlsMsgStoreFatal + #define DtlsChPoolInit wolfSSL_DtlsChPoolInit + #define DtlsChPoolFree wolfSSL_DtlsChPoolFree + #define DtlsChPoolClear wolfSSL_DtlsChPoolClear + #define DtlsChPoolStore wolfSSL_DtlsChPoolStore + #define DtlsChPoolDrop wolfSSL_DtlsChPoolDrop + #define DtlsChPoolTakeRest wolfSSL_DtlsChPoolTakeRest + #define DtlsChPoolShardIdx wolfSSL_DtlsChPoolShardIdx + #define DtlsMsgAdopt wolfSSL_DtlsMsgAdopt #endif /* WOLFSSL_API_PREFIX_MAP */ WOLFSSL_LOCAL DtlsMsg* DtlsMsgNew(word32 sz, byte tx, void* heap); WOLFSSL_LOCAL void DtlsMsgDelete(DtlsMsg* item, void* heap); @@ -8168,6 +8251,22 @@ WOLFSSL_LOCAL int Tls13KeyUpdateLimitReached(WOLFSSL* ssl); WOLFSSL_LOCAL void DtlsMsgRxListInsert(WOLFSSL* ssl, DtlsMsg* msg); WOLFSSL_LOCAL DtlsMsg* DtlsMsgRxListDetach(WOLFSSL* ssl, DtlsMsg* item); WOLFSSL_LOCAL void DtlsMsgRxListFree(WOLFSSL* ssl); +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_TEST_VIS int DtlsChPoolInit(DtlsChPool* pool); + WOLFSSL_TEST_VIS void DtlsChPoolFree(DtlsChPool* pool, void* heap); + WOLFSSL_TEST_VIS void DtlsChPoolClear(DtlsChPool* pool, void* heap); + WOLFSSL_TEST_VIS int DtlsChPoolStore(DtlsChPool* pool, void* heap, + const WOLFSSL_SOCKADDR* peer, word16 epoch, word32 seq, + const byte* data, word32 dataSz, byte type, word32 fragOffset, + word32 fragSz, word32 maxAge, void* outHeap, DtlsMsg** done); + WOLFSSL_TEST_VIS void DtlsChPoolDrop(DtlsChPool* pool, void* heap, + const WOLFSSL_SOCKADDR* peer, word32 maxAge); + WOLFSSL_TEST_VIS DtlsMsg* DtlsChPoolTakeRest(DtlsChPool* pool, void* heap, + const WOLFSSL_SOCKADDR* peer, word16 epoch, word32 seq, + word32 fragSz, word32 msgSz, word32 maxAge, void* outHeap); + WOLFSSL_TEST_VIS word32 DtlsChPoolShardIdx(const WOLFSSL_SOCKADDR* peer); + WOLFSSL_TEST_VIS int DtlsMsgAdopt(DtlsMsg** msg, void* from, void* to); +#endif WOLFSSL_TEST_VIS int DtlsMsgStoreFatal(WOLFSSL* ssl, int ret); WOLFSSL_LOCAL DtlsMsg* DtlsMsgInsert(DtlsMsg* head, DtlsMsg* item); @@ -8193,7 +8292,7 @@ WOLFSSL_LOCAL int Tls13KeyUpdateLimitReached(WOLFSSL* ssl); #if !defined(NO_WOLFSSL_SERVER) WOLFSSL_LOCAL int DoClientHelloStateless(WOLFSSL* ssl, - const byte* input, word32 helloSz, byte isFirstCHFrag, byte* tls13); + const byte* input, word32 fragSz, word32 msgSz); #endif /* !defined(NO_WOLFSSL_SERVER) */ #if !defined(WOLFCRYPT_ONLY) && !defined(WOLFSSL_NO_SOCK) && \ (defined(USE_WOLFSSL_IO) || defined(WOLFSSL_USER_IO)) diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index ad14fcf5ab4..e6282af9f44 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -6718,6 +6718,11 @@ WOLFSSL_API const unsigned char* wolfSSL_dtls_cid_parse(const unsigned char* msg #ifdef WOLFSSL_DTLS_CH_FRAG WOLFSSL_API int wolfSSL_dtls13_allow_ch_frag(WOLFSSL *ssl, int enabled); + WOLFSSL_API int wolfSSL_dtls13_reassemble_ch_before_cookie(WOLFSSL* ssl, + int enabled); + WOLFSSL_API int wolfSSL_CTX_dtls13_reassemble_ch_before_cookie( + WOLFSSL_CTX* ctx, int enabled); + WOLFSSL_API int wolfSSL_CTX_dtls13_clear_ch_pool(WOLFSSL_CTX* ctx); #endif #ifdef WOLFSSL_DTLS13_NO_HRR_ON_RESUME WOLFSSL_API int wolfSSL_dtls13_no_hrr_on_resume(WOLFSSL *ssl, int enabled); From db69cb0c9eb39cd89068828187ed8a260c4f43e8 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Wed, 4 Nov 2026 12:05:34 +0000 Subject: [PATCH 04/10] CI: DTLS 1.3 interop with OpenSSL s_client and s_server With and without the cookie exchange, post-quantum groups, small MTUs and KeyUpdate. wolfSSL is built with --enable-curve25519, which is off by default, so X25519MLKEM768 is available. OpenSSL 4.1.0 is the first release with DTLS 1.3. --- .github/scripts/openssl-dtls13.sh | 330 +++++++++++++++++++++++++++ .github/workflows/openssl-dtls13.yml | 124 ++++++++++ 2 files changed, 454 insertions(+) create mode 100755 .github/scripts/openssl-dtls13.sh create mode 100644 .github/workflows/openssl-dtls13.yml diff --git a/.github/scripts/openssl-dtls13.sh b/.github/scripts/openssl-dtls13.sh new file mode 100755 index 00000000000..9ac1beb53ac --- /dev/null +++ b/.github/scripts/openssl-dtls13.sh @@ -0,0 +1,330 @@ +#!/usr/bin/env bash +# +# DTLS 1.3 interoperability between wolfSSL and OpenSSL (4.1 and later). +# +# Runs the wolfSSL example client and server against `openssl s_server` and +# `openssl s_client` over loopback UDP, in both directions, and checks each +# handshake negotiates DTLS 1.3, the expected group, and exchanges data. +# +# Run from the root of a built wolfSSL tree (the examples use certs/). +# OPENSSL openssl binary with DTLS 1.3 (default: openssl) +# WOLFSSL_CLIENT wolfSSL example client (default: examples/client/client) +# WOLFSSL_SERVER wolfSSL example server (default: examples/server/server) +# +# wolfSSL must be built with at least: +# --enable-dtls --enable-dtls13 --enable-dtls-mtu --enable-dtls-frag-ch +# --enable-hrrcookie --enable-mlkem --enable-curve25519 + +set -u + +OPENSSL=${OPENSSL:-openssl} +WOLFSSL_CLIENT=${WOLFSSL_CLIENT:-./examples/client/client} +WOLFSSL_SERVER=${WOLFSSL_SERVER:-./examples/server/server} +LOG_DIR=$(mktemp -d) +# Upper bound for one side of one case, in seconds. +CASE_TIMEOUT=20 + +# How the wolfSSL server is run for a case whose first ClientHello is +# fragmented (a post-quantum key share, or a small MTU on the client). +# cookie the cookie exchange is on: the stateless server reassembles the +# ClientHello within a bounded budget before the cookie is +# verified (wolfSSL_dtls13_reassemble_ch_before_cookie(), on by +# default), then runs the cookie check on the complete message. +# no-cookie the cookie exchange is disabled (-J n): the server is stateful +# from the first datagram and reassembles the ClientHello. +FRAG_CH_SERVER_MODES="cookie no-cookie" + +PASS=0 +FAIL=0 +XFAIL=0 +FAILED_CASES="" +# Set by xfail() for the case it runs: a pattern one of the case's logs must +# show for the failure to be the known one, and why the case fails. +XFAIL_PATTERN="" +XFAIL_WHY="" + +trap 'rm -rf "$LOG_DIR"' EXIT + +fail() { + local name=$1 why=$2 + echo "FAIL: $name: $why" + FAIL=$((FAIL + 1)) + FAILED_CASES="$FAILED_CASES $name" + for f in "$LOG_DIR/$name".*; do + echo "----- $f" + cat "$f" + done +} + +# Record the outcome of a case: $2 is empty on success, else why it failed. +report() { + local name=$1 why=$2 + if [ -n "$XFAIL_PATTERN" ]; then + if [ -z "$why" ]; then + fail "$name" "passed, but expected to fail ($XFAIL_WHY):\ + drop the xfail if that is fixed" + elif cat "$LOG_DIR/$name".* | grep -q -- "$XFAIL_PATTERN"; then + echo "XFAIL: $name: $XFAIL_WHY" + XFAIL=$((XFAIL + 1)) + else + fail "$name" "$why (expected to fail only with '$XFAIL_PATTERN')" + fi + elif [ -n "$why" ]; then + fail "$name" "$why" + else + echo "PASS: $name" + PASS=$((PASS + 1)) + fi +} + +# Run a case that fails for a known reason outside wolfSSL. +# $1 pattern a log of the case must show when it fails as expected +# $2 why the case is expected to fail +# $@ the case: ossl_server ... or wolf_server ... +xfail() { + XFAIL_PATTERN=$1 + XFAIL_WHY=$2 + shift 2 + "$@" + XFAIL_PATTERN="" + XFAIL_WHY="" +} + +# Wait for an OpenSSL log to show the handshake completed, then type a +# KeyUpdate command on its stdin: K requests the peer to update too, k does +# not. +# $1 log $2 line s_client/s_server prints once the handshake is done +# $3 K or k +type_key_update() { + local log=$1 done=$2 cmd=$3 i + for i in $(seq $((CASE_TIMEOUT * 10))); do + grep -q "$done" "$log" 2>/dev/null && break + sleep 0.1 + done + echo "$cmd" + # s_client/s_server read stdin in chunks and drop the rest of a chunk + # that holds a command: keep the next line out of it. + sleep 1 +} + +# wolfSSL client <-> OpenSSL server +# $1 case name +# $2 extra s_server arguments +# $3 extra wolfSSL client arguments +# $4 group the wolfSSL client must report as negotiated +# $5 KeyUpdate, optional: +# K, k s_server sends a KeyUpdate, requesting one back (K) or not (k) +# wolf the wolfSSL client sends a KeyUpdate requesting one back (-I) +# before the data each side sends, and that KeyUpdate must be seen. +ossl_server() { + local name=$1 oargs=$2 wargs=$3 group=$4 ku=${5:-} + local slog="$LOG_DIR/$name.server" clog="$LOG_DIR/$name.client" + local port="" spid crc src i + + case "$ku" in + K|k) oargs="$oargs -msg" ;; + wolf) oargs="$oargs -msg"; wargs="$wargs -I" ;; + esac + + : > "$slog" + # s_server exits on EOF on stdin, and writing before the handshake + # completes drives it early: hold stdin open, then send one line. + (if [ "$ku" = K ] || [ "$ku" = k ]; then + type_key_update "$slog" "^CIPHER is" "$ku" + else + sleep 2 + fi + echo "hello-from-openssl"; sleep 6) | \ + timeout "$CASE_TIMEOUT" stdbuf -oL "$OPENSSL" s_server -dtls1_3 \ + -accept 127.0.0.1:0 -naccept 1 \ + -cert certs/server-cert.pem -key certs/server-key.pem \ + $oargs >> "$slog" 2>&1 & + spid=$! + for i in $(seq 50); do + port=$(grep -m1 -oE '^ACCEPT .*:[0-9]+' "$slog" | sed 's/.*://') + [ -n "$port" ] && break + sleep 0.1 + done + if [ -z "$port" ]; then + kill "$spid" 2>/dev/null + wait "$spid" 2>/dev/null + report "$name" "s_server did not start" + return + fi + + timeout "$CASE_TIMEOUT" "$WOLFSSL_CLIENT" -v 4 -h 127.0.0.1 -p "$port" \ + $wargs > "$clog" 2>&1 + crc=$? + wait "$spid" + src=$? + + if [ $crc -ne 0 ] || [ $src -ne 0 ]; then + report "$name" "exit codes: wolfSSL client $crc, s_server $src" + elif ! grep -q "SSL version is DTLSv1.3" "$clog"; then + report "$name" "DTLS 1.3 not negotiated" + elif ! grep -q "SSL curve name is $group\$" "$clog"; then + report "$name" "group $group not negotiated" + elif [ "$ku" = wolf ] && ! grep -q "^<<< .*, KeyUpdate$" "$slog"; then + report "$name" "s_server did not receive a KeyUpdate" + elif [ -n "$ku" ] && [ "$ku" != wolf ] && + ! grep -q "^>>> .*, KeyUpdate$" "$slog"; then + report "$name" "s_server did not send a KeyUpdate" + elif ! grep -q "hello-from-openssl" "$clog" || + ! grep -q "hello wolfssl!" "$slog"; then + report "$name" "application data not exchanged" + else + report "$name" "" + fi +} + +# OpenSSL client <-> wolfSSL server +# $1 case name +# $2 extra wolfSSL server arguments +# $3 extra s_client arguments +# $4 group the wolfSSL server must report as negotiated +# $5 KeyUpdate, optional: +# K, k s_client sends a KeyUpdate, requesting one back (K) or not (k) +# wolf the wolfSSL server sends a KeyUpdate requesting one back (-U) +# before the data each side sends, and that KeyUpdate must be seen. +wolf_server() { + local name=$1 wargs=$2 oargs=$3 group=$4 ku=${5:-} + local slog="$LOG_DIR/$name.server" clog="$LOG_DIR/$name.client" + local ready="$LOG_DIR/$name.ready" port spid crc src i + + case "$ku" in + K|k) oargs="$oargs -msg" ;; + wolf) oargs="$oargs -msg"; wargs="$wargs -U" ;; + esac + + timeout "$CASE_TIMEOUT" "$WOLFSSL_SERVER" -v 4 -u -p 0 -R "$ready" \ + $wargs > "$slog" 2>&1 & + spid=$! + for i in $(seq 50); do + [ -s "$ready" ] && break + sleep 0.1 + done + if [ ! -s "$ready" ]; then + kill "$spid" 2>/dev/null + wait "$spid" 2>/dev/null + report "$name" "wolfSSL server did not start" + return + fi + port=$(cat "$ready") + + # s_client exits on EOF on stdin: keep it open for the server's reply. + # After its KeyUpdate (-U) the wolfSSL server reads a second message. + : > "$clog" + (if [ "$ku" = K ] || [ "$ku" = k ]; then + type_key_update "$clog" "^Protocol: DTLSv1.3" "$ku" + fi + echo "hello-from-openssl" + if [ "$ku" = wolf ]; then + sleep 1 + echo "hello-again-from-openssl" + fi + sleep 2) | \ + timeout "$CASE_TIMEOUT" "$OPENSSL" s_client -dtls1_3 \ + -connect "127.0.0.1:$port" \ + -CAfile certs/ca-cert.pem -verify_return_error \ + -cert certs/client-cert.pem -key certs/client-key.pem \ + $oargs >> "$clog" 2>&1 + crc=$? + wait "$spid" + src=$? + + if [ $crc -ne 0 ] || [ $src -ne 0 ]; then + report "$name" "exit codes: s_client $crc, wolfSSL server $src" + elif ! grep -q "Protocol: DTLSv1.3" "$clog"; then + report "$name" "DTLS 1.3 not negotiated" + elif ! grep -q "SSL curve name is $group\$" "$slog"; then + report "$name" "group $group not negotiated" + elif [ "$ku" = wolf ] && ! grep -q "^<<< .*, KeyUpdate$" "$clog"; then + report "$name" "s_client did not receive a KeyUpdate" + elif [ -n "$ku" ] && [ "$ku" != wolf ] && + ! grep -q "^>>> .*, KeyUpdate$" "$clog"; then + report "$name" "s_client did not send a KeyUpdate" + elif ! grep -q "Client message: hello-from-openssl" "$slog" || + ! grep -q "I hear you fa shizzle!" "$clog"; then + report "$name" "application data not exchanged" + elif [ "$ku" = wolf ] && + ! grep -q "Client message: hello-again-from-openssl" "$slog"; then + report "$name" "application data after the KeyUpdate not received" + else + report "$name" "" + fi +} + +# OpenSSL client <-> wolfSSL server, first ClientHello fragmented: run once +# per mode in FRAG_CH_SERVER_MODES. Arguments as for wolf_server(). +wolf_server_frag_ch() { + local name=$1 wargs=$2 oargs=$3 group=$4 mode + for mode in $FRAG_CH_SERVER_MODES; do + case "$mode" in + cookie) wolf_server "$name-cookie" "$wargs" "$oargs" "$group" ;; + no-cookie) wolf_server "$name-no-cookie" "-J n $wargs" "$oargs" \ + "$group" ;; + *) echo "unknown mode $mode"; exit 1 ;; + esac + done +} + +"$OPENSSL" version + +# OpenSSL server. s_server always runs the DTLS 1.3 cookie exchange, so +# every case also exercises the wolfSSL client's HelloRetryRequest path. +ossl_server ossl-server-default "" "-u" "X25519MLKEM768" +# The wolfSSL client key shares a group s_server does not accept. +ossl_server ossl-server-hrr-group "-groups secp384r1" "-u" "SECP384R1" +ossl_server ossl-server-x25519mlkem768 "-groups X25519MLKEM768" \ + "-u --pqc X25519MLKEM768" "X25519MLKEM768" +ossl_server ossl-server-p384mlkem1024 "-groups SecP384r1MLKEM1024" \ + "-u --pqc SecP384r1MLKEM1024" "SecP384r1MLKEM1024" +ossl_server ossl-server-mtu512 "" "-u 512" "X25519MLKEM768" +ossl_server ossl-server-p384mlkem1024-mtu512 "-groups SecP384r1MLKEM1024" \ + "-u 512 --pqc SecP384r1MLKEM1024" "SecP384r1MLKEM1024" + +# wolfSSL server, first ClientHello in one datagram, cookie exchange on. +wolf_server wolf-server-default "" "" "X25519MLKEM768" +# s_client key shares X25519, which the wolfSSL server does not accept. +wolf_server wolf-server-hrr-group "--force-curve SECP384R1" \ + "-groups X25519:P-384" "SECP384R1" +wolf_server wolf-server-x25519mlkem768 "--pqc X25519MLKEM768" \ + "-groups X25519MLKEM768" "X25519MLKEM768" +wolf_server wolf-server-default-no-cookie "-J n" "" "X25519MLKEM768" + +# wolfSSL server, first ClientHello fragmented. +wolf_server_frag_ch wolf-server-mtu512 "" "-mtu 512" "X25519MLKEM768" +wolf_server_frag_ch wolf-server-mtu256 "" "-mtu 256" "X25519MLKEM768" +# Loopback has a 64 KiB MTU, so without -mtu s_client sends a ClientHello +# larger than an Ethernet datagram unfragmented, beyond what the server reads +# in one datagram. -mtu 1500 makes it fragment as on a real link. +wolf_server_frag_ch wolf-server-p384mlkem1024 "--pqc SecP384r1MLKEM1024" \ + "-groups SecP384r1MLKEM1024 -mtu 1500" "SecP384r1MLKEM1024" +wolf_server_frag_ch wolf-server-x25519mlkem768-mtu512 \ + "--pqc X25519MLKEM768" "-groups X25519MLKEM768 -mtu 512" "X25519MLKEM768" + +# KeyUpdate (RFC 9147 section 8), from each side, with and without asking +# the peer to update too. The sender keeps its old keys until the KeyUpdate +# is ACKed, so data sent right after it is at the old epoch, which the +# receiver has already moved past. +ossl_server ossl-server-keyupdate "" "-u" "X25519MLKEM768" k +# s_server, after sending its KeyUpdate, waits in TLS_ST_SW_KEY_UPDATE, where +# only an ACK is a valid message (ssl/statem/statem_srvr.c:163-170 at 4.1.0 +# beta2). wolfSSL answers update_requested with its own KeyUpdate at once, +# before the ACK, as the crossing allowed by RFC 9147 section 8 and RFC 8446 +# section 4.6.3, and s_server fails with unexpected_message +# (statem_srvr.c:342). OpenSSL's own client only answers with its next data, +# by which time the ACK has arrived. +xfail "ossl_statem_server_read_transition:unexpected message" \ + "OpenSSL server rejects a crossing KeyUpdate" \ + ossl_server ossl-server-keyupdate-requested "" "-u" "X25519MLKEM768" K +ossl_server ossl-server-wolf-keyupdate "" "-u" "X25519MLKEM768" wolf +wolf_server wolf-server-keyupdate "" "" "X25519MLKEM768" k +wolf_server wolf-server-keyupdate-requested "" "" "X25519MLKEM768" K +wolf_server wolf-server-wolf-keyupdate "" "" "X25519MLKEM768" wolf + +echo "passed: $PASS, expected failures: $XFAIL, failed: $FAIL" +if [ $FAIL -ne 0 ]; then + echo "failed cases:$FAILED_CASES" + exit 1 +fi diff --git a/.github/workflows/openssl-dtls13.yml b/.github/workflows/openssl-dtls13.yml new file mode 100644 index 00000000000..faedd6933ec --- /dev/null +++ b/.github/workflows/openssl-dtls13.yml @@ -0,0 +1,124 @@ +name: OpenSSL DTLS 1.3 interop Tests + +# START OF COMMON SECTION +on: + push: + branches: [ 'release/**' ] + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + branches: [ '*' ] + # Daily run on master reseeds the shared cache (see save steps below). + schedule: + - cron: '50 4 * * *' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read +# END OF COMMON SECTION + +env: + # First OpenSSL line with DTLS 1.3 (RFC 9147) is 4.1. + OPENSSL_REF: openssl-4.1.0 + +jobs: + build_openssl: + name: Build OpenSSL + if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} + runs-on: ubuntu-24.04 + timeout-minutes: 15 + steps: + - name: Checking if we have OpenSSL in cache + uses: actions/cache/restore@v5 + id: cache + with: + path: openssl-install + key: openssl-dtls13-${{ env.OPENSSL_REF }} + lookup-only: true + + - name: Checkout OpenSSL + if: steps.cache.outputs.cache-hit != 'true' + uses: actions/checkout@v5 + with: + repository: openssl/openssl + ref: ${{ env.OPENSSL_REF }} + path: openssl + fetch-depth: 1 + + - name: Compile OpenSSL + if: steps.cache.outputs.cache-hit != 'true' + working-directory: openssl + run: | + # Static, so the openssl binary needs no LD_LIBRARY_PATH. + ./Configure --prefix=$GITHUB_WORKSPACE/openssl-install \ + --libdir=lib no-docs no-tests no-shared + make -j$(nproc) + make install_sw + + # Only master (the daily schedule) saves, so all PRs share one entry. + - name: Save OpenSSL cache + if: github.ref == 'refs/heads/master' && steps.cache.outputs.cache-hit != 'true' + uses: actions/cache/save@v5 + with: + path: openssl-install + key: openssl-dtls13-${{ env.OPENSSL_REF }} + + # On a cache miss, hand the freshly built OpenSSL to openssl_test via + # an artifact so it is not compiled a second time in the same run. + - name: tar OpenSSL + if: steps.cache.outputs.cache-hit != 'true' + run: tar -zcf openssl-install.tgz openssl-install + + - name: Upload OpenSSL build + if: steps.cache.outputs.cache-hit != 'true' + uses: actions/upload-artifact@v6 + with: + name: openssl-dtls13-build + path: openssl-install.tgz + retention-days: 1 + + openssl_test: + name: Test DTLS 1.3 interop with OpenSSL + if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} + runs-on: ubuntu-24.04 + needs: build_openssl + timeout-minutes: 15 + steps: + # Restore only: the build_openssl job (master/schedule) owns the save. + - name: Checking if we have OpenSSL in cache + uses: actions/cache/restore@v5 + id: cache + with: + path: openssl-install + key: openssl-dtls13-${{ env.OPENSSL_REF }} + + # On a cache miss, reuse the build_openssl artifact instead of + # recompiling. master restores from the cache above instead. + - name: Download OpenSSL build (fallback on cache miss) + if: steps.cache.outputs.cache-hit != 'true' + uses: actions/download-artifact@v7 + with: + name: openssl-dtls13-build + + - name: untar OpenSSL (fallback on cache miss) + if: steps.cache.outputs.cache-hit != 'true' + run: tar -xf openssl-install.tgz + + - name: Build wolfSSL + uses: wolfSSL/actions-build-autotools-project@v1 + with: + path: wolfssl + configure: >- + --enable-dtls --enable-dtls13 --enable-dtls-mtu + --enable-dtls-frag-ch --enable-hrrcookie --enable-mlkem + --enable-curve25519 + install: false + check: false + + - name: Test interop + working-directory: wolfssl + run: | + OPENSSL=$GITHUB_WORKSPACE/openssl-install/bin/openssl \ + bash .github/scripts/openssl-dtls13.sh From b3aeb6800bc6de7023a27b521d8526426507fb91 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Tue, 3 Nov 2026 11:34:04 +0000 Subject: [PATCH 05/10] Cookie secret: the servers of a context share one, an object may override Cookie secrets were per WOLFSSL, so a server could not verify a cookie another server of the same context issued. A second ClientHello handed to a new listener failed: a fatal alert in DTLS 1.3, a new HelloVerifyRequest in DTLS 1.2. - Every server context holds a cookie secret, as a key per cookie type, an HMAC of a label under the secret, so a HelloVerifyRequest cookie never verifies as a HelloRetryRequest cookie. It keeps only the keys, hvrKey and hrrKey, under a leaf rwlock: every cookie a server without a secret of its own makes or checks reads it, a few API calls write it. TLS 1.3 HelloRetryRequest cookies use it as the DTLS ones do. - New wolfSSL_CTX_set_cookie_secret(), wolfSSL_CTX_set_cookie_secret_secondary() and wolfSSL_CTX_rotate_cookie_secret(). Rotate moves the primary to the secondary, so cookies issued before it keep verifying. Set discards the previous key: a set between a cookie's issue and its check fails that handshake and the client retries. A secret of any non-zero size, as the object setters take; a FIPS HMAC still needs 14 bytes or more. NULL generates a random secret as long as the keys. - A context without a secret gets a random one the first time one of its objects enables cookies: accept, wolfSSL_enable_cookie(), or NULL to wolfSSL_send_hrr_cookie() or wolfSSL_DTLS_SetCookieSecret(). Installed under the lock only if still empty, so of racing defaults the first stays. The servers of one context thus verify each other's cookies with no API call. NULL to those two functions drops the object's own secret and uses the context's. - CookieSecretGet() resolves the secrets at each use: the object's own secret for the cookie type when the application set one, else the context's keys copied under its lock, primary and secondary. The object's buffers hold only application-supplied secrets, so nothing is copied ahead or kept in step. No secret anywhere fails creating and checking a cookie with COOKIE_ERROR. - DoTls13ClientHello() checks the cookie before the SNI callback, where wolfSSL_set_SSL_CTX() usually runs: checked after the move it would be checked with the new context's secret. wolfSSL_set_SSL_CTX() gives a new context without a secret a default when the object is a server with cookies on. - Tests: hand-off of a second ClientHello between listeners in DTLS 1.2 and 1.3 and of a HelloRetryRequest cookie between two TLS 1.3 servers, rotation keeping cookies valid and set not, per-object override, objects created before the context's secret was set, wolfSSL_set_SSL_CTX(), no splicing between cookie types, device and RNG failures, no secret anywhere, rotation racing verification on threads, and a TLS context having the lock. --- ChangeLog.md | 21 + doc/dox_comments/header_files/ssl.h | 252 ++++- src/dtls.c | 56 +- src/internal.c | 12 + src/ssl.c | 6 + src/ssl_api_dtls.c | 495 ++++++++- src/tls13.c | 97 +- tests/api/test_dtls.c | 1532 +++++++++++++++++++++++++-- tests/api/test_dtls.h | 39 +- tests/api/test_dtls13.c | 914 +++++++++++++++- tests/api/test_dtls13.h | 10 + tests/api/test_ssl_cert.c | 7 + tests/api/test_tls13.c | 114 +- tests/api/test_tls13.h | 2 + wolfssl/internal.h | 81 +- wolfssl/ssl.h | 8 + 16 files changed, 3349 insertions(+), 297 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 7b0935a75de..f44cf8972c8 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -72,6 +72,27 @@ than the next expected one is dropped instead of stalling the list, and `wolfSSL_clear()` frees the DTLS 1.3 receive list. +* **Behavioral change (the servers of a context share a cookie secret)**: + cookie secrets were per `WOLFSSL`, so a server could not verify a cookie + another server of the same context issued, and a second ClientHello handed + to a new listener failed: a fatal alert in DTLS 1.3, a new + HelloVerifyRequest in DTLS 1.2. Every server context now holds a cookie + secret, kept as a key per cookie type so a HelloVerifyRequest cookie never + verifies as a HelloRetryRequest cookie, and a context without one gets a + random secret the first time one of its objects enables cookies, so the + servers of one context verify each other's cookies with no API call. TLS + 1.3 HelloRetryRequest cookies use it as the DTLS ones do. The new + `wolfSSL_CTX_set_cookie_secret()`, + `wolfSSL_CTX_set_cookie_secret_secondary()` and + `wolfSSL_CTX_rotate_cookie_secret()` set it: rotate moves the primary to the + secondary, so cookies issued before it keep verifying, while set discards + the previous key. A secret may be of any non-zero size, and NULL generates + a random one. A secret given to `wolfSSL_send_hrr_cookie()` or + `wolfSSL_DTLS_SetCookieSecret()` still overrides the context's for that + object, and NULL to either now drops the object's own secret in favour of + the context's. A DTLS 1.3 cookie is checked before the SNI callback, so a + `wolfSSL_set_SSL_CTX()` made there cannot change the secret the check uses. + * **Behavioral change (DSA now uses the leftmost |q| bytes of the digest)**: `wc_DsaSign_ex()`/`wc_DsaVerify_ex()` read the whole digest, and the EVP DSA paths always read 20 bytes, so neither followed FIPS 186-4 4.2. Both now use diff --git a/doc/dox_comments/header_files/ssl.h b/doc/dox_comments/header_files/ssl.h index 42268ece9d5..af0efa526fa 100644 --- a/doc/dox_comments/header_files/ssl.h +++ b/doc/dox_comments/header_files/ssl.h @@ -3427,7 +3427,8 @@ int wolfDTLS_accept_stateless(WOLFSSL* ssl); DoS/amplification attacks. wolfDTLS_accept_stateless() cannot be used. Primary and secondary cookie secrets for the applicable protocols are - securely erased and freed. + securely erased and freed. Only this object's secrets are erased. The + context's secret is kept, and wolfSSL_enable_cookie() uses it again. Without cookies a DTLS 1.3 server can process a first ClientHello that arrives fragmented, but only when ClientHello fragment reassembly is @@ -3461,8 +3462,10 @@ int wolfSSL_disable_cookie(WOLFSSL* ssl); \brief Enable server cookies for DTLS 1.2, DTLS 1.3 and TLS 1.3. Like wolfSSL_disable_cookie(), this does not change stateful processing. It can undo a disable before accept begins, including - wolfSSL_disable_hrr_cookie(). Missing primary cookie secrets are randomly - generated immediately; existing primary and secondary secrets are preserved. + wolfSSL_disable_hrr_cookie(). Secrets the application gave the object are + kept. A cookie type the object has no secret of its own for uses the + context's secret (wolfSSL_CTX_set_cookie_secret()), generated now if the + context has none. \param ssl DTLS or TLS 1.3 server session created with wolfSSL_new(). A general-purpose session that has not chosen a side yet is accepted: the @@ -3471,14 +3474,14 @@ int wolfSSL_disable_cookie(WOLFSSL* ssl); \return BAD_FUNC_ARG if ssl is NULL or uses an unsupported protocol (TLS 1.2). \return SIDE_ERROR if ssl is a client. \return BAD_STATE_E if the DTLS handshake already committed to stateful - processing, as for wolfSSL_disable_cookie(), or if a missing secret must - be generated and the session's RNG has been released (as happens when - its handshake resources are freed). - \return MEMORY_ERROR if secret allocation fails, or another negative error - if random secret generation fails. A failure is all or nothing: no secret - is installed and the cookie policy is left as it was found, even when the - session needs both a DTLS 1.2 and a DTLS 1.3 secret. + processing, as for wolfSSL_disable_cookie(). + \return BAD_MUTEX_E if the context's cookie secret cannot be locked. + \return MEMORY_E, an RNG or an HMAC error if the context's secret cannot + be generated, as for wolfSSL_CTX_set_cookie_secret(). A failure is all or + nothing: no secret is installed and the cookie policy is left as it was + found. \sa wolfSSL_disable_cookie + \sa wolfSSL_CTX_set_cookie_secret */ int wolfSSL_enable_cookie(WOLFSSL* ssl); @@ -10954,23 +10957,26 @@ void wolfSSL_SetFuzzerCb(WOLFSSL* ssl, CallbackFuzzer cbf, void* fCtx); /*! \brief This function sets a new dtls cookie secret. + A secret given here is the object's own and wins over the context's + secret for the DTLS 1.2 HelloVerifyRequest cookie of this object. A NULL + secret drops the object's own: the object uses the context's secret + (wolfSSL_CTX_set_cookie_secret()), generated now if the context has none. + \return 0 returned if the function executed without an error. - \return BAD_FUNC_ARG returned if there was an argument passed - to the function with an unacceptable value. + \return BAD_FUNC_ARG returned if ssl is NULL, or secret is not NULL and + secretSz is 0. \return MEMORY_ERROR returned if there was a problem allocating memory for a new cookie secret. - \return BAD_STATE_E returned if secret is NULL and the session's RNG has - been released, so no secret can be generated. - \return Another -ve value when a new secret could not be generated. The - rotation does not happen and the secret already in use is kept, so the - server carries on issuing and verifying cookies under it. + \return BAD_MUTEX_E returned if the context's secret cannot be locked. + \return Another -ve value when the context's secret could not be + generated. The secret already in use is kept, so the server carries on + issuing and verifying cookies under it. \param ssl a pointer to a WOLFSSL structure, created using wolfSSL_new(). - \param secret a constant byte pointer representing the secret buffer. - Passing NULL indicates to generate a new random secret. - \param secretSz the size of the buffer. Passing 0 with a NULL secret - generates a secret of the default size (COOKIE_SECRET_SZ); passing 0 with - a non-NULL secret returns BAD_FUNC_ARG. + \param secret a constant byte pointer representing the secret buffer, or + NULL to use the context's secret. + \param secretSz the size of the buffer. Ignored with a NULL secret. + Passing 0 with a non-NULL secret returns BAD_FUNC_ARG. _Example_ \code @@ -10986,8 +10992,8 @@ void wolfSSL_SetFuzzerCb(WOLFSSL* ssl, CallbackFuzzer cbf, void* fCtx); } \endcode - \sa ForceZero - \sa wc_RNG_GenerateBlock + \sa wolfSSL_DTLS_SetCookieSecretSecondary + \sa wolfSSL_CTX_set_cookie_secret */ int wolfSSL_DTLS_SetCookieSecret(WOLFSSL* ssl, const byte* secret, @@ -11001,7 +11007,9 @@ int wolfSSL_DTLS_SetCookieSecret(WOLFSSL* ssl, 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(). + DTLS 1.2 counterpart of wolfSSL_set_hrr_cookie_secret_secondary(). It + wins over the context's secondary + (wolfSSL_CTX_set_cookie_secret_secondary()) for this object. \return 0 returned if the function executed without an error. \return BAD_FUNC_ARG returned if ssl is NULL. @@ -11029,11 +11037,156 @@ int wolfSSL_DTLS_SetCookieSecret(WOLFSSL* ssl, \endcode \sa wolfSSL_DTLS_SetCookieSecret + \sa wolfSSL_CTX_set_cookie_secret_secondary */ int wolfSSL_DTLS_SetCookieSecretSecondary(WOLFSSL* ssl, const byte* secret, word32 secretSz); +/*! + \ingroup Setup + + \brief Sets the cookie secret of a server context. Its server objects use + it to make and check the TLS 1.3 and DTLS 1.3 HelloRetryRequest cookie and + the DTLS 1.2 HelloVerifyRequest cookie, for each cookie type an object has + no secret of its own (wolfSSL_send_hrr_cookie(), + wolfSSL_DTLS_SetCookieSecret()). A ClientHello whose cookie one object + issued then verifies at any other object of the context that sees the + same peer address (wolfSSL_dtls_set_peer()) and supports the same cipher + suite and group. Each cookie type uses its own key made from the secret, + so an object given the same bytes with wolfSSL_send_hrr_cookie() or + wolfSSL_DTLS_SetCookieSecret() does not accept these cookies. + + Objects read the secret each time they make or check a cookie, so a + change applies at once to every object of the context, and + wolfSSL_set_SSL_CTX() moves an object to the new context's secret. The + server name callback runs after the cookie check, so a context it moves + the object to need not share the secret. A + context without a secret gets a random one when cookies are enabled on + one of its objects: by wolfSSL_accept() or wolfDTLS_accept_stateless() + on a DTLS server, wolfSSL_enable_cookie(), or wolfSSL_send_hrr_cookie() + and wolfSSL_DTLS_SetCookieSecret() with a NULL secret. Call this to + share the secret between processes or to choose it. + + Cookies made with the previous secret stop verifying unless it is the + secondary: a client that got its HelloRetryRequest or HelloVerifyRequest + before the change fails that handshake and retries. Use + wolfSSL_CTX_rotate_cookie_secret() to keep them verifying. Does not + change the secondary secret and does not turn cookies on or off. Safe to + call while other threads use objects of the context. + + \param [in,out] ctx a pointer to a WOLFSSL_CTX structure, created using + wolfSSL_CTX_new(). + \param [in] secret the secret, or NULL to generate a random one as long + as the keys made from it (32 bytes with SHA-256). + \param [in] secretSz size of secret in bytes. Not used with a NULL + secret. + + \return WOLFSSL_SUCCESS on success. + \return BAD_FUNC_ARG if ctx is NULL, or secret is not NULL and secretSz + is 0. + \return SIDE_ERROR if the context is for clients or the library has no + server support. + \return BAD_MUTEX_E, MEMORY_E, an RNG or an HMAC error. The RNG and the + HMAC use the context's device (wolfSSL_CTX_SetDevId()), so this can be a + device error such as NO_VALID_DEVID, or WC_PENDING_E when a crypto + callback pends: call again. A FIPS build gives HMAC_MIN_KEYLEN_E for a + secret shorter than 14 bytes. The secret in use is unchanged. + + _Example_ + \code + WOLFSSL_CTX* ctx = wolfSSL_CTX_new(wolfDTLSv1_3_server_method()); + // secret shared by every process that answers this endpoint + if (wolfSSL_CTX_set_cookie_secret(ctx, secret, secretSz) != + WOLFSSL_SUCCESS) { + // error + } + WOLFSSL* ssl = wolfSSL_new(ctx); + \endcode + + \sa wolfSSL_CTX_rotate_cookie_secret + \sa wolfSSL_CTX_set_cookie_secret_secondary + \sa wolfSSL_send_hrr_cookie + \sa wolfSSL_DTLS_SetCookieSecret + \sa wolfSSL_disable_cookie + \sa wolfSSL_dtls_set_peer +*/ +int wolfSSL_CTX_set_cookie_secret(WOLFSSL_CTX* ctx, + const unsigned char* secret, unsigned int secretSz); + +/*! + \ingroup Setup + + \brief Sets the secondary cookie secret of a server context. An object + without a secondary of its own for a cookie type checks a cookie against + it only after the primary fails; it never makes cookies. It is used only + while the context has a primary secret. NULL or a size of 0 clears it. + Safe to call while other threads use objects of the context. + + To rotate in two calls, set the secondary to the current secret first, + then set the new primary. wolfSSL_CTX_rotate_cookie_secret() does both + in one step. + + \param [in,out] ctx a pointer to a WOLFSSL_CTX structure, created using + wolfSSL_CTX_new(). + \param [in] secret the secondary secret, or NULL to clear it. + \param [in] secretSz size of secret in bytes, or 0 to clear it. + + \return WOLFSSL_SUCCESS on success. + \return BAD_FUNC_ARG if ctx is NULL. + \return SIDE_ERROR if the context is for clients or the library has no + server support. + \return BAD_MUTEX_E, MEMORY_E or an HMAC error, which can be a device + error such as NO_VALID_DEVID, or WC_PENDING_E when a crypto callback + pends: call again. The secondary in use is unchanged. + + _Example_ + \code + wolfSSL_CTX_set_cookie_secret_secondary(ctx, cur, curSz); + wolfSSL_CTX_set_cookie_secret(ctx, next, nextSz); + \endcode + + \sa wolfSSL_CTX_set_cookie_secret + \sa wolfSSL_CTX_rotate_cookie_secret + \sa wolfSSL_set_hrr_cookie_secret_secondary + \sa wolfSSL_DTLS_SetCookieSecretSecondary +*/ +int wolfSSL_CTX_set_cookie_secret_secondary(WOLFSSL_CTX* ctx, + const unsigned char* secret, unsigned int secretSz); + +/*! + \ingroup Setup + + \brief Rotates the cookie secret of a server context in one step: the + primary secret becomes the secondary and secret becomes the primary. + Cookies made with the previous primary keep verifying until the next + rotation, so this is safe at any moment, with handshakes in flight. With + no primary yet, it sets the primary and clears the secondary. Rotate less + often than the longest time a client may take between the + HelloRetryRequest or HelloVerifyRequest and its second ClientHello. Safe + to call while other threads use objects of the context. + + \param [in,out] ctx a pointer to a WOLFSSL_CTX structure, created using + wolfSSL_CTX_new(). + \param [in] secret the new secret, or NULL to generate a random one. + \param [in] secretSz as for wolfSSL_CTX_set_cookie_secret(). + + \return as for wolfSSL_CTX_set_cookie_secret(). + + _Example_ + \code + // from a timer + if (wolfSSL_CTX_rotate_cookie_secret(ctx, NULL, 0) != WOLFSSL_SUCCESS) { + // secret unchanged + } + \endcode + + \sa wolfSSL_CTX_set_cookie_secret + \sa wolfSSL_CTX_set_cookie_secret_secondary +*/ +int wolfSSL_CTX_rotate_cookie_secret(WOLFSSL_CTX* ctx, + const unsigned char* secret, unsigned int secretSz); + /*! \brief This function retrieves the random number. @@ -16104,27 +16257,34 @@ int wolfSSL_connect(WOLFSSL* ssl); secret while cookies are already enabled remains supported. DTLS 1.2-only callers should use wolfSSL_enable_cookie() and wolfSSL_DTLS_SetCookieSecret(). + A non-NULL secret is the object's own and wins over the context's secret + (wolfSSL_CTX_set_cookie_secret()) for the HelloRetryRequest cookie of + this object. NULL drops the object's own: the object uses the context's + secret, generated now if the context has none. A DTLS 1.3 server without + a DTLS 1.2 secret of its own (wolfSSL_DTLS_SetCookieSecret()) also uses + the context's for the HelloVerifyRequest cookie. + \param [in,out] ssl a pointer to a WOLFSSL structure, created using wolfSSL_new(). - \param [in] secret a pointer to a buffer holding the secret. - Passing NULL indicates to generate a new random secret. - \param [in] secretSz Size of the secret in bytes. - Passing 0 indicates to use the default size: the digest size of the hash - the cookie MAC uses, WC_SHA256_DIGEST_SIZE, or when SHA-256 is not - available WC_SHA384_DIGEST_SIZE, WC_SHA512_DIGEST_SIZE (with + \param [in] secret a pointer to a buffer holding the secret, or NULL to + use the context's secret. + \param [in] secretSz Size of the secret in bytes. Ignored with a NULL + secret. Otherwise passing 0 indicates to use the default + size: the digest size of the hash the cookie MAC uses, + WC_SHA256_DIGEST_SIZE, or when SHA-256 is not available + WC_SHA384_DIGEST_SIZE, WC_SHA512_DIGEST_SIZE (with WOLFSSL_TLS13_SHA512) or WC_SM3_DIGEST_SIZE, in that order. \return BAD_FUNC_ARG if ssl is NULL or not using TLS v1.3. \return SIDE_ERROR if called with a client. \return BAD_STATE_E if the DTLS handshake already committed to stateful - processing, as for wolfSSL_enable_cookie(), or if a secret must be - generated (secret is NULL, or a DTLS 1.3 server is missing its DTLS 1.2 - secret) and the session's RNG has been released. The installed secret is - left unchanged. + processing, as for wolfSSL_enable_cookie(). \return WOLFSSL_SUCCESS if successful. \return MEMORY_ERROR if allocating dynamic memory for storing secret failed. - \return Another -ve value on internal error. Every failure is all or - nothing: the secret in use and the cookie policy are left exactly as they - were found, so the object stays usable. + \return BAD_MUTEX_E if the context's cookie secret cannot be locked. + \return Another -ve value on internal error, including when the + context's secret cannot be generated. Every failure is all or nothing: + the secret in use and the cookie policy are left exactly as they were + found, so the object stays usable. _Example_ \code @@ -16140,6 +16300,7 @@ int wolfSSL_connect(WOLFSSL* ssl); \sa wolfSSL_new \sa wolfSSL_disable_hrr_cookie + \sa wolfSSL_CTX_set_cookie_secret */ int wolfSSL_send_hrr_cookie(WOLFSSL* ssl, const unsigned char* secret, unsigned int secretSz); @@ -16156,6 +16317,8 @@ int wolfSSL_send_hrr_cookie(WOLFSSL* ssl, 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. + It wins over the context's secondary + (wolfSSL_CTX_set_cookie_secret_secondary()) for this object. \param [in,out] ssl a pointer to a WOLFSSL structure, created using wolfSSL_new(). @@ -16188,6 +16351,7 @@ int wolfSSL_send_hrr_cookie(WOLFSSL* ssl, \sa wolfSSL_send_hrr_cookie \sa wolfSSL_disable_hrr_cookie + \sa wolfSSL_CTX_set_cookie_secret_secondary */ int wolfSSL_set_hrr_cookie_secret_secondary(WOLFSSL* ssl, const unsigned char* secret, unsigned int secretSz); @@ -16206,7 +16370,8 @@ int wolfSSL_set_hrr_cookie_secret_secondary(WOLFSSL* ssl, BAD_STATE_E once the handshake has committed to stateful processing. Without the cookie exchange a DTLS v1.3 server has nothing to verify before it commits, so it processes the first ClientHello statefully and, when - built with WOLFSSL_DTLS_CH_FRAG, reassembles a fragmented one. + built with WOLFSSL_DTLS_CH_FRAG, reassembles a fragmented one. The + context's secret is kept. \param [in,out] ssl a pointer to a WOLFSSL structure, created using wolfSSL_new(). @@ -16283,10 +16448,10 @@ int wolfSSL_dtls13_allow_ch_frag(WOLFSSL* ssl, int enabled); wolfSSL_dtls_set_peer(), holds nothing: it drops such fragments, and still answers a whole ClientHello or one whose cookie is in its first fragment. A server that completes another server's second ClientHello verifies the - cookie only when both were given the same secret with - wolfSSL_send_hrr_cookie(); otherwise the client gets an illegal_parameter - alert. Use one context per local endpoint if clients may reuse a source - port across endpoints. + cookie only when they share the cookie secret: both use the context's + (wolfSSL_CTX_set_cookie_secret(), the default) or each was given the + same secret; otherwise the client gets an illegal_parameter alert. Use one context per + local endpoint if clients may reuse a source port across endpoints. Allocator, logging and clock callbacks may run while the pool is locked and must not call into wolfSSL. Requires WOLFSSL_DTLS_CH_FRAG. @@ -16312,6 +16477,7 @@ int wolfSSL_dtls13_allow_ch_frag(WOLFSSL* ssl, int enabled); \sa wolfSSL_send_hrr_cookie \sa wolfSSL_disable_hrr_cookie \sa wolfSSL_dtls_set_peer + \sa wolfSSL_CTX_set_cookie_secret */ int wolfSSL_dtls13_reassemble_ch_before_cookie(WOLFSSL* ssl, int enabled); diff --git a/src/dtls.c b/src/dtls.c index 0a2f872075a..8e58c7be8b1 100644 --- a/src/dtls.c +++ b/src/dtls.c @@ -332,33 +332,36 @@ static int CheckDtlsCookie(const WOLFSSL* ssl, WolfSSL_CH* ch, else #endif { + CookieSecrets cs; + if (ch->cookie.size != DTLS_COOKIE_SZ) return 0; + ret = CookieSecretGet(ssl, CTX_COOKIE_DTLS12, &cs); + if (ret != 0) + return ret; if (!ch->dtls12cookieSet) { - ret = CreateDtls12Cookie(ssl, ch, - ssl->buffers.dtlsCookieSecret.buffer, - ssl->buffers.dtlsCookieSecret.length, ch->dtls12cookie); - if (ret != 0) - return ret; - ch->dtls12cookieSet = 1; + ret = CreateDtls12Cookie(ssl, ch, cs.pri, cs.priSz, + ch->dtls12cookie); + if (ret == 0) + 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, + if (ret == 0) { + *cookieGood = ConstantCompare(ch->cookie.elements, + ch->dtls12cookie, DTLS_COOKIE_SZ) == 0; } + /* The secondary, verify only, keeps cookies issued before a rotation + * of the secret valid */ + if (ret == 0 && !*cookieGood && cs.sec != NULL) { + byte altCookie[DTLS_COOKIE_SZ]; + + ret = CreateDtls12Cookie(ssl, ch, cs.sec, cs.secSz, altCookie); + if (ret == 0) { + *cookieGood = ConstantCompare(ch->cookie.elements, altCookie, + DTLS_COOKIE_SZ) == 0; + } + } + CookieSecretsClear(&cs); } return ret; } @@ -1005,9 +1008,14 @@ static int SendStatelessReply(const WOLFSSL* ssl, WolfSSL_CH* ch, byte isTls13) { #if !defined(WOLFSSL_NO_TLS12) if (!ch->dtls12cookieSet) { - ret = CreateDtls12Cookie(ssl, ch, - ssl->buffers.dtlsCookieSecret.buffer, - ssl->buffers.dtlsCookieSecret.length, ch->dtls12cookie); + CookieSecrets cs; + + ret = CookieSecretGet(ssl, CTX_COOKIE_DTLS12, &cs); + if (ret == 0) { + ret = CreateDtls12Cookie(ssl, ch, cs.pri, cs.priSz, + ch->dtls12cookie); + CookieSecretsClear(&cs); + } if (ret != 0) return ret; ch->dtls12cookieSet = 1; diff --git a/src/internal.c b/src/internal.c index 1e5257bfc42..f4f6e365c28 100644 --- a/src/internal.c +++ b/src/internal.c @@ -3199,6 +3199,14 @@ int InitSSL_Ctx(WOLFSSL_CTX* ctx, WOLFSSL_METHOD* method, void* heap) } } #endif +#if (defined(WOLFSSL_DTLS) || defined(WOLFSSL_SEND_HRR_COOKIE)) && \ + !defined(NO_WOLFSSL_SERVER) + if (method->side != WOLFSSL_CLIENT_END) { + ret = CookieSecretCtxInit(ctx); + if (ret != 0) + return ret; + } +#endif #ifndef NO_CERTS ctx->privateKeyDevId = INVALID_DEVID; @@ -3733,6 +3741,10 @@ void SSL_CtxResourceFree(WOLFSSL_CTX* ctx) #if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) /* No object holds a reference any more, so none reaches the pool */ DtlsChPoolFree(&ctx->dtlsChPool, ctx->heap); +#endif +#if (defined(WOLFSSL_DTLS) || defined(WOLFSSL_SEND_HRR_COOKIE)) && \ + !defined(NO_WOLFSSL_SERVER) + CookieSecretCtxFree(ctx); #endif (void)heapAtCTXInit; } diff --git a/src/ssl.c b/src/ssl.c index 87df683fd6c..266d98b3cc0 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -9049,6 +9049,12 @@ WOLFSSL_CTX* wolfSSL_set_SSL_CTX(WOLFSSL* ssl, WOLFSSL_CTX* ctx) if (ssl->ctx != NULL) wolfSSL_CTX_free(ssl->ctx); ssl->ctx = ctx; +#if (defined(WOLFSSL_DTLS) || defined(WOLFSSL_SEND_HRR_COOKIE)) && \ + !defined(NO_WOLFSSL_SERVER) + /* A server's cookies now come from this context's secret */ + if (ssl->options.sendCookie && ssl->options.side != WOLFSSL_CLIENT_END) + (void)CookieSecretCtxDefault(ctx); +#endif #ifndef NO_CERTS /* Release the certificate, chain and keys this SSL owns so that a ctx diff --git a/src/ssl_api_dtls.c b/src/ssl_api_dtls.c index 174aa97ff7b..c6361519171 100644 --- a/src/ssl_api_dtls.c +++ b/src/ssl_api_dtls.c @@ -1726,21 +1726,400 @@ int SetCookieSecret(WOLFSSL* ssl, buffer* dst, const byte* secret, #endif /* (WOLFSSL_DTLS || WOLFSSL_SEND_HRR_COOKIE) && !NO_WOLFSSL_SERVER */ +#if (defined(WOLFSSL_DTLS) || defined(WOLFSSL_SEND_HRR_COOKIE)) && \ + !defined(NO_WOLFSSL_SERVER) + +/* A context's cookie secret is a leaf lock: hold it only for memcpy and field + * updates, never with another lock held. Readers share it. */ +static int CookieSecretLockRd(CookieSecretCtx* cs) +{ +#ifndef SINGLE_THREADED + if (cs->lock != NULL && wc_LockRwLock_Rd(cs->lock) != 0) + return BAD_MUTEX_E; +#else + (void)cs; +#endif + return 0; +} + +static int CookieSecretLockWr(CookieSecretCtx* cs) +{ +#ifndef SINGLE_THREADED + if (cs->lock != NULL && wc_LockRwLock_Wr(cs->lock) != 0) + return BAD_MUTEX_E; +#else + (void)cs; +#endif + return 0; +} + +static void CookieSecretUnlock(CookieSecretCtx* cs) +{ +#ifndef SINGLE_THREADED + if (cs->lock != NULL) + wc_UnLockRwLock(cs->lock); +#else + (void)cs; +#endif +} + +/* Prepare the cookie secret of a new server context. It starts empty. */ +int CookieSecretCtxInit(WOLFSSL_CTX* ctx) +{ +#ifndef SINGLE_THREADED + if (wc_InitRwLock(&ctx->cookieSecretLock) != 0) { + WOLFSSL_MSG("Bad rwlock init"); + WOLFSSL_ERROR_VERBOSE(BAD_MUTEX_E); + return BAD_MUTEX_E; + } + ctx->cookieSecret.lock = &ctx->cookieSecretLock; +#endif +#ifdef WOLFSSL_CHECK_MEM_ZERO + wc_MemZero_Add("ctx cookie hvr keys", ctx->cookieSecret.hvrKey, + sizeof(ctx->cookieSecret.hvrKey)); + wc_MemZero_Add("ctx cookie hrr keys", ctx->cookieSecret.hrrKey, + sizeof(ctx->cookieSecret.hrrKey)); +#endif + (void)ctx; + return 0; +} + +/* Erase the cookie secret of a context no object uses any more */ +void CookieSecretCtxFree(WOLFSSL_CTX* ctx) +{ + ForceZero(ctx->cookieSecret.hvrKey, sizeof(ctx->cookieSecret.hvrKey)); + ForceZero(ctx->cookieSecret.hrrKey, sizeof(ctx->cookieSecret.hrrKey)); + ctx->cookieSecret.keySz[0] = 0; + ctx->cookieSecret.keySz[1] = 0; +#ifdef WOLFSSL_CHECK_MEM_ZERO + wc_MemZero_Check(ctx->cookieSecret.hvrKey, + sizeof(ctx->cookieSecret.hvrKey)); + wc_MemZero_Check(ctx->cookieSecret.hrrKey, + sizeof(ctx->cookieSecret.hrrKey)); +#endif +#ifndef SINGLE_THREADED + if (ctx->cookieSecret.lock != NULL) { + wc_FreeRwLock(ctx->cookieSecret.lock); + ctx->cookieSecret.lock = NULL; + } +#endif +} + +/* The secrets a cookie of type uses. An own primary rules the context out for + * the type; an own secondary wins over the context's. Both of the context's + * are read under one lock. COOKIE_ERROR: no primary anywhere. */ +int CookieSecretGet(const WOLFSSL* ssl, int type, CookieSecrets* cs) +{ + const buffer* own = NULL; + const buffer* ownSec = NULL; + CookieSecretCtx* ctxCs; + + XMEMSET(cs, 0, sizeof(*cs)); +#ifdef WOLFSSL_SEND_HRR_COOKIE + if (type == CTX_COOKIE_HRR) { + own = &ssl->buffers.tls13CookieSecret; + ownSec = &ssl->buffers.tls13CookieSecretSecondary; + } +#endif +#ifdef WOLFSSL_DTLS + if (type == CTX_COOKIE_DTLS12) { + own = &ssl->buffers.dtlsCookieSecret; + ownSec = &ssl->buffers.dtlsCookieSecretSecondary; + } +#endif + if (own == NULL) + return BAD_FUNC_ARG; + if (ownSec->buffer != NULL) { + cs->sec = ownSec->buffer; + cs->secSz = ownSec->length; + } + if (own->buffer != NULL) { + cs->pri = own->buffer; + cs->priSz = own->length; + return 0; + } + if (ssl->ctx == NULL) + return COOKIE_ERROR; +#ifdef WOLFSSL_CHECK_MEM_ZERO + wc_MemZero_Add("cookie secret keys", cs->key, sizeof(cs->key)); +#endif + ctxCs = &ssl->ctx->cookieSecret; + if (CookieSecretLockRd(ctxCs) != 0) { + CookieSecretsClear(cs); + return BAD_MUTEX_E; + } + if (ctxCs->keySz[0] != 0) { + const byte* pri = (type == CTX_COOKIE_HRR) ? ctxCs->hrrKey[0] : + ctxCs->hvrKey[0]; + const byte* sec = (type == CTX_COOKIE_HRR) ? ctxCs->hrrKey[1] : + ctxCs->hvrKey[1]; + + XMEMCPY(cs->key[0], pri, CTX_COOKIE_KEY_SZ); + cs->pri = cs->key[0]; + cs->priSz = ctxCs->keySz[0]; + if (cs->sec == NULL && ctxCs->keySz[1] != 0) { + XMEMCPY(cs->key[1], sec, CTX_COOKIE_KEY_SZ); + cs->sec = cs->key[1]; + cs->secSz = ctxCs->keySz[1]; + } + } + CookieSecretUnlock(ctxCs); + if (cs->pri == NULL) { + WOLFSSL_MSG("No cookie secret on the object or its context"); + CookieSecretsClear(cs); + return COOKIE_ERROR; + } + return 0; +} + +/* Erase the copies of the context's keys */ +void CookieSecretsClear(CookieSecrets* cs) +{ + ForceZero(cs->key, sizeof(cs->key)); +#ifdef WOLFSSL_CHECK_MEM_ZERO + wc_MemZero_Check(cs->key, sizeof(cs->key)); +#endif +} + +#define CTX_COOKIE_SET 0 +#define CTX_COOKIE_SECONDARY 1 +#define CTX_COOKIE_ROTATE 2 +#define CTX_COOKIE_DEFAULT 3 /* a random primary, only if there is none */ + +/* Generate a random cookie secret of sz bytes with a temporary RNG */ +static int CtxCookieSecretGenerate(WOLFSSL_CTX* ctx, byte* out, word32 sz) +{ + int ret; + WC_DECLARE_VAR(rng, WC_RNG, 1, ctx->heap); + + WC_ALLOC_VAR_EX(rng, WC_RNG, 1, ctx->heap, DYNAMIC_TYPE_RNG, + return MEMORY_E); + /* Unused when the RNG is on the stack and FIPS takes no heap */ + (void)ctx; +#ifdef HAVE_FIPS + ret = wc_InitRng(rng); +#else + ret = wc_InitRng_ex(rng, ctx->heap, ctx->devId); +#endif + if (ret == 0) { + ret = wc_RNG_GenerateBlock(rng, out, sz); + wc_FreeRng(rng); + } + WC_FREE_VAR_EX(rng, ctx->heap, DYNAMIC_TYPE_RNG); + return ret; +} + +static const char ctxCookieLabelDtls12[] = + "wolfSSL DTLS HelloVerifyRequest cookie"; +static const char ctxCookieLabelHrr[] = + "wolfSSL HelloRetryRequest cookie"; + +/* Wait out a hardware HMAC, as HKDF does. A crypto callback pending is + * returned: the context is unchanged and the call can be made again. */ +#if defined(WOLFSSL_ASYNC_CRYPT) && defined(WC_ASYNC_ENABLE_HMAC) && \ + (defined(HAVE_INTEL_QA) || defined(HAVE_CAVIUM)) + #define CTX_COOKIE_HMAC_WAIT(ret, hmac) \ + (ret) = wc_AsyncWait((ret), &(hmac)->asyncDev, WC_ASYNC_FLAG_NONE) +#else + #define CTX_COOKIE_HMAC_WAIT(ret, hmac) WC_DO_NOTHING +#endif + +/* Make the key of each cookie type: an HMAC of a label per type under the + * secret, so a cookie of one type cannot pass as the other. */ +static int CtxCookieKeysMake(WOLFSSL_CTX* ctx, const byte* secret, + word32 secretSz, byte* hvrKey, byte* hrrKey) +{ + int ret = 0; + int i; + WC_DECLARE_VAR(hmac, Hmac, 1, ctx->heap); + + WC_ALLOC_VAR_EX(hmac, Hmac, 1, ctx->heap, DYNAMIC_TYPE_HMAC, + return MEMORY_E); + for (i = 0; i < 2 && ret == 0; i++) { + const char* label = (i == CTX_COOKIE_HRR) ? ctxCookieLabelHrr : + ctxCookieLabelDtls12; + byte* key = (i == CTX_COOKIE_HRR) ? hrrKey : hvrKey; + + ret = wc_HmacInit(hmac, ctx->heap, ctx->devId); + if (ret == 0) { + ret = wc_HmacSetKey(hmac, CTX_COOKIE_KEY_TYPE, secret, secretSz); + CTX_COOKIE_HMAC_WAIT(ret, hmac); + if (ret == 0) { + ret = wc_HmacUpdate(hmac, (const byte*)label, + (word32)XSTRLEN(label)); + CTX_COOKIE_HMAC_WAIT(ret, hmac); + } + if (ret == 0) { + ret = wc_HmacFinal(hmac, key); + CTX_COOKIE_HMAC_WAIT(ret, hmac); + } + wc_HmacFree(hmac); + } + } + WC_FREE_VAR_EX(hmac, ctx->heap, DYNAMIC_TYPE_HMAC); + return ret; +} + +/* Change the cookie secret of a server context: CTX_COOKIE_SET, _SECONDARY, + * _ROTATE or _DEFAULT. NULL generates a secret, or clears the secondary. */ +static int CtxCookieSecretUpdate(WOLFSSL_CTX* ctx, int op, + const byte* secret, word32 secretSz) +{ + int ret = 0; + int slot = (op == CTX_COOKIE_SECONDARY) ? 1 : 0; + CookieSecretCtx* cs; + const byte* src = secret; + byte buf[CTX_COOKIE_KEY_SZ]; + byte hvr[CTX_COOKIE_KEY_SZ]; + byte hrr[CTX_COOKIE_KEY_SZ]; + + if (ctx == NULL) + return BAD_FUNC_ARG; + if (ctx->method->side == WOLFSSL_CLIENT_END) + return SIDE_ERROR; + if (op == CTX_COOKIE_SECONDARY && (secret == NULL || secretSz == 0)) { + secretSz = 0; + } + else if (secret == NULL) { + /* A random secret as long as the keys made from it */ + secretSz = (word32)sizeof(buf); + } + else if (secretSz == 0) { + WOLFSSL_MSG("Cookie secret without a size"); + return BAD_FUNC_ARG; + } + + XMEMSET(hvr, 0, sizeof(hvr)); + XMEMSET(hrr, 0, sizeof(hrr)); +#ifdef WOLFSSL_CHECK_MEM_ZERO + XMEMSET(buf, 0, sizeof(buf)); + wc_MemZero_Add("ctx cookie secret new", buf, sizeof(buf)); + wc_MemZero_Add("ctx cookie hvr key new", hvr, sizeof(hvr)); + wc_MemZero_Add("ctx cookie hrr key new", hrr, sizeof(hrr)); +#endif + if (secret == NULL && secretSz != 0) { + ret = CtxCookieSecretGenerate(ctx, buf, secretSz); + src = buf; + } + if (ret == 0 && secretSz != 0) + ret = CtxCookieKeysMake(ctx, src, secretSz, hvr, hrr); + + cs = &ctx->cookieSecret; + if (ret == 0 && CookieSecretLockWr(cs) != 0) + ret = BAD_MUTEX_E; + if (ret == 0) { + /* Of racing defaults, the first one installed stays */ + if (op != CTX_COOKIE_DEFAULT || cs->keySz[0] == 0) { + if (op == CTX_COOKIE_ROTATE) { + XMEMCPY(cs->hvrKey[1], cs->hvrKey[0], CTX_COOKIE_KEY_SZ); + XMEMCPY(cs->hrrKey[1], cs->hrrKey[0], CTX_COOKIE_KEY_SZ); + cs->keySz[1] = cs->keySz[0]; + } + XMEMCPY(cs->hvrKey[slot], hvr, CTX_COOKIE_KEY_SZ); + XMEMCPY(cs->hrrKey[slot], hrr, CTX_COOKIE_KEY_SZ); + cs->keySz[slot] = (secretSz != 0) ? CTX_COOKIE_KEY_SZ : 0; + } + CookieSecretUnlock(cs); + ret = WOLFSSL_SUCCESS; + } + + ForceZero(buf, sizeof(buf)); + ForceZero(hvr, sizeof(hvr)); + ForceZero(hrr, sizeof(hrr)); +#ifdef WOLFSSL_CHECK_MEM_ZERO + wc_MemZero_Check(buf, sizeof(buf)); + wc_MemZero_Check(hvr, sizeof(hvr)); + wc_MemZero_Check(hrr, sizeof(hrr)); +#endif + return ret; +} + +/* Give a context without a cookie secret a random one, so its servers' cookies + * verify at each other. Returns 0 when it has one. */ +int CookieSecretCtxDefault(WOLFSSL_CTX* ctx) +{ + CookieSecretCtx* cs; + word32 sz; + int ret; + + if (ctx == NULL) + return BAD_FUNC_ARG; + cs = &ctx->cookieSecret; + if (CookieSecretLockRd(cs) != 0) + return BAD_MUTEX_E; + sz = cs->keySz[0]; + CookieSecretUnlock(cs); + if (sz != 0) + return 0; + ret = CtxCookieSecretUpdate(ctx, CTX_COOKIE_DEFAULT, NULL, 0); + return (ret == WOLFSSL_SUCCESS) ? 0 : ret; +} + +int wolfSSL_CTX_set_cookie_secret(WOLFSSL_CTX* ctx, + const unsigned char* secret, unsigned int secretSz) +{ + WOLFSSL_ENTER("wolfSSL_CTX_set_cookie_secret"); + return CtxCookieSecretUpdate(ctx, CTX_COOKIE_SET, secret, secretSz); +} + +int wolfSSL_CTX_set_cookie_secret_secondary(WOLFSSL_CTX* ctx, + const unsigned char* secret, unsigned int secretSz) +{ + WOLFSSL_ENTER("wolfSSL_CTX_set_cookie_secret_secondary"); + return CtxCookieSecretUpdate(ctx, CTX_COOKIE_SECONDARY, secret, secretSz); +} + +int wolfSSL_CTX_rotate_cookie_secret(WOLFSSL_CTX* ctx, + const unsigned char* secret, unsigned int secretSz) +{ + WOLFSSL_ENTER("wolfSSL_CTX_rotate_cookie_secret"); + return CtxCookieSecretUpdate(ctx, CTX_COOKIE_ROTATE, secret, secretSz); +} + +#elif defined(WOLFSSL_DTLS) || defined(WOLFSSL_SEND_HRR_COOKIE) + +int wolfSSL_CTX_set_cookie_secret(WOLFSSL_CTX* ctx, + const unsigned char* secret, unsigned int secretSz) +{ + (void)secret; + (void)secretSz; + return (ctx == NULL) ? BAD_FUNC_ARG : SIDE_ERROR; +} + +int wolfSSL_CTX_set_cookie_secret_secondary(WOLFSSL_CTX* ctx, + const unsigned char* secret, unsigned int secretSz) +{ + (void)secret; + (void)secretSz; + return (ctx == NULL) ? BAD_FUNC_ARG : SIDE_ERROR; +} + +int wolfSSL_CTX_rotate_cookie_secret(WOLFSSL_CTX* ctx, + const unsigned char* secret, unsigned int secretSz) +{ + (void)secret; + (void)secretSz; + return (ctx == NULL) ? BAD_FUNC_ARG : SIDE_ERROR; +} + +#endif /* (WOLFSSL_DTLS || WOLFSSL_SEND_HRR_COOKIE) && !NO_WOLFSSL_SERVER */ + #if defined(WOLFSSL_DTLS) && !defined(NO_WOLFSSL_SERVER) /* Set the DTLS cookie secret used to generate HelloVerifyRequest cookies. * - * When secret is NULL a new secret is randomly generated. The object's RNG - * must be initialized. This is not an SSL function. + * A secret given here wins over the context's. NULL drops the object's own + * and uses the context's secret, made now if the context has none. This is + * not an SSL function. * * @param [in] ssl SSL/TLS object. - * @param [in] secret Cookie secret data, or NULL to generate one. - * @param [in] secretSz Length of secret in bytes, 0 to use the default. + * @param [in] secret Cookie secret data, or NULL to use the context's. + * @param [in] secretSz Length of secret in bytes. Not used with NULL. * @return 0 on success. * @return BAD_FUNC_ARG when ssl is NULL or secret is set with size 0. - * @return BAD_STATE_E when a secret must be generated and the RNG has been - * released. The secret in use is unchanged. + * @return BAD_MUTEX_E when the context cannot be locked. * @return MEMORY_ERROR on allocation failure. The secret in use is unchanged. + * @return A negative error code when the context's secret cannot be made. */ int wolfSSL_DTLS_SetCookieSecret(WOLFSSL* ssl, const byte* secret, word32 secretSz) @@ -1754,15 +2133,19 @@ int wolfSSL_DTLS_SetCookieSecret(WOLFSSL* ssl, return BAD_FUNC_ARG; } - if (secret != NULL && secretSz == 0) { + if (secret == NULL) { + ret = CookieSecretCtxDefault(ssl->ctx); + if (ret == 0) + FreeCookieSecret(ssl, &ssl->buffers.dtlsCookieSecret); + WOLFSSL_LEAVE("wolfSSL_DTLS_SetCookieSecret", ret); + return ret; + } + + if (secretSz == 0) { WOLFSSL_MSG("can't have a new secret without a size"); return BAD_FUNC_ARG; } - /* If secretSz is 0, use the default size. */ - if (secretSz == 0) - secretSz = COOKIE_SECRET_SZ; - ret = SetCookieSecret(ssl, &ssl->buffers.dtlsCookieSecret, secret, secretSz, "wolfSSL_DTLS_SetCookieSecret secret"); @@ -1927,7 +2310,8 @@ int DtlsNoCookieChGood(WOLFSSL* ssl) /* 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. + * wolfSSL_DTLS_SetCookieSecret()) fails to verify it. On an object that uses + * its context's secret, the secret it holds becomes its own. * * This supports an application-driven cookie-secret rotation on a stateless * server: after rotating the primary secret, install the previous secret here @@ -2069,66 +2453,74 @@ int wolfSSL_disable_cookie(WOLFSSL* ssl) /* Turn the cookie policy on, optionally replacing the HelloRetryRequest * cookie secret. * - * Every secret the policy needs, including the DTLS 1.2 fallback secret of a - * DTLS 1.3 server, is built before any is installed, so a failure leaves the - * object as it was found. The handshake state is not checked here: the public - * entry points check it, and InitSSL_DtlsServer() runs before it matters. + * A secret given is the object's own. A cookie type the object has no secret + * of its own for uses the context's, made now if the context has none. The + * handshake state is not checked here: the public entry points check it, and + * InitSSL_DtlsServer() runs before it matters. * * @param [in, out] ssl SSL/TLS object. - * @param [in] hrrSecret HRR cookie secret to install, or NULL to - * generate one. + * @param [in] hrrSecret HRR cookie secret to install, or NULL to use + * the context's. * @param [in] hrrSecretSz Length of hrrSecret in bytes, 0 for the * default size. * @param [in] replaceHrr Replace an HRR secret that is already set. - * When 0, only missing secrets are generated and - * application-supplied secrets are kept. + * When 0, an application-supplied secret is kept. * @return WOLFSSL_SUCCESS on success. * @return BAD_FUNC_ARG when ssl is NULL or the protocol has no cookie. * @return SIDE_ERROR when called on a client. - * @return A negative error code when a secret cannot be generated. The - * object is left as it was found. + * @return A negative error code when a secret cannot be stored or the + * context's made. The object is left as it was found. */ int CookiePolicySet(WOLFSSL* ssl, const byte* hrrSecret, word32 hrrSecretSz, int replaceHrr) { int ret; + int needCtx = 0; #ifdef WOLFSSL_SEND_HRR_COOKIE buffer newTls13Secret; -#endif -#ifdef WOLFSSL_DTLS - buffer newDtlsSecret; -#endif + int dropHrr = 0; -#ifdef WOLFSSL_SEND_HRR_COOKIE XMEMSET(&newTls13Secret, 0, sizeof(newTls13Secret)); #endif -#ifdef WOLFSSL_DTLS - XMEMSET(&newDtlsSecret, 0, sizeof(newDtlsSecret)); -#endif ret = CheckCookieSide(ssl); if (ret != WOLFSSL_SUCCESS) return ret; - /* Prepare. Every step that can fail runs here. */ #ifdef WOLFSSL_SEND_HRR_COOKIE - if (IsAtLeastTLSv1_3(ssl->version) && - (replaceHrr || ssl->buffers.tls13CookieSecret.buffer == NULL)) { - ret = NewCookieSecret(ssl, &newTls13Secret, hrrSecret, - (hrrSecretSz != 0) ? hrrSecretSz - : (word32)TLS13_COOKIE_SECRET_SZ); - if (ret != 0) - goto cleanup; + if (IsAtLeastTLSv1_3(ssl->version)) { + if (hrrSecret != NULL) { + if (replaceHrr || ssl->buffers.tls13CookieSecret.buffer == NULL) { + ret = NewCookieSecret(ssl, &newTls13Secret, hrrSecret, + (hrrSecretSz != 0) ? hrrSecretSz + : (word32)TLS13_COOKIE_SECRET_SZ); + if (ret != 0) + return ret; + } + } + else if (replaceHrr) { + dropHrr = 1; + needCtx = 1; + } + else if (ssl->buffers.tls13CookieSecret.buffer == NULL) { + needCtx = 1; + } } #endif #ifdef WOLFSSL_DTLS /* DTLS 1.3 also needs this secret for DTLS 1.2 fallback. */ - if (ssl->options.dtls && ssl->buffers.dtlsCookieSecret.buffer == NULL) { - ret = NewCookieSecret(ssl, &newDtlsSecret, NULL, COOKIE_SECRET_SZ); - if (ret != 0) - goto cleanup; - } + if (ssl->options.dtls && ssl->buffers.dtlsCookieSecret.buffer == NULL) + needCtx = 1; #endif + if (needCtx) { + ret = CookieSecretCtxDefault(ssl->ctx); + if (ret != 0) { +#ifdef WOLFSSL_SEND_HRR_COOKIE + FreeCookieSecret(ssl, &newTls13Secret); +#endif + return ret; + } + } /* Install. Nothing below this point can fail. */ #ifdef WOLFSSL_SEND_HRR_COOKIE @@ -2136,11 +2528,8 @@ int CookiePolicySet(WOLFSSL* ssl, const byte* hrrSecret, word32 hrrSecretSz, InstallCookieSecret(ssl, &ssl->buffers.tls13CookieSecret, &newTls13Secret, "tls13CookieSecret"); } -#endif -#ifdef WOLFSSL_DTLS - if (newDtlsSecret.buffer != NULL) { - InstallCookieSecret(ssl, &ssl->buffers.dtlsCookieSecret, - &newDtlsSecret, "dtlsCookieSecret"); + else if (dropHrr) { + FreeCookieSecret(ssl, &ssl->buffers.tls13CookieSecret); } #endif ssl->options.sendCookie = 1; @@ -2149,16 +2538,6 @@ int CookiePolicySet(WOLFSSL* ssl, const byte* hrrSecret, word32 hrrSecretSz, (void)hrrSecretSz; (void)replaceHrr; return WOLFSSL_SUCCESS; - -cleanup: - /* Only the secrets this call built are dropped. */ -#ifdef WOLFSSL_SEND_HRR_COOKIE - FreeCookieSecret(ssl, &newTls13Secret); -#endif -#ifdef WOLFSSL_DTLS - FreeCookieSecret(ssl, &newDtlsSecret); -#endif - return ret; } /* Prepare missing secrets without replacing application-supplied secrets. diff --git a/src/tls13.c b/src/tls13.c index cd222168f39..291a22d9458 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -4006,17 +4006,12 @@ int CreateCookieExt(const WOLFSSL* ssl, byte* hash, word16 hashSz, byte cookie[OPAQUE8_LEN + WC_MAX_DIGEST_SIZE + OPAQUE16_LEN * 2]; TLSX* ext; word16 cookieSz = 0; + CookieSecrets cs; if (hash == NULL || hashSz == 0) { return BAD_FUNC_ARG; } - if (ssl->buffers.tls13CookieSecret.buffer == NULL || - ssl->buffers.tls13CookieSecret.length == 0) { - WOLFSSL_MSG("Missing DTLS 1.3 cookie secret"); - return COOKIE_ERROR; - } - /* Cookie Data = Hash Len | Hash | CS | KeyShare Group */ cookie[cookieSz++] = (byte)hashSz; XMEMCPY(cookie + cookieSz, hash, hashSz); @@ -4050,15 +4045,16 @@ int CreateCookieExt(const WOLFSSL* ssl, byte* hash, word16 hashSz, #error "No digest to available to use with HMAC for cookies." #endif /* NO_SHA */ + ret = CookieSecretGet(ssl, CTX_COOKIE_HRR, &cs); + if (ret != 0) + return ret; WC_ALLOC_VAR_EX(cookieHmac, Hmac, 1, ssl->heap, DYNAMIC_TYPE_HMAC, - return MEMORY_E); + { CookieSecretsClear(&cs); 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); - } + if (ret == 0) + ret = wc_HmacSetKey(cookieHmac, cookieType, cs.pri, cs.priSz); + CookieSecretsClear(&cs); if (ret == 0) ret = wc_HmacUpdate(cookieHmac, cookie, cookieSz); #ifdef WOLFSSL_DTLS13 @@ -7777,6 +7773,7 @@ int TlsCheckCookie(const WOLFSSL* ssl, const byte* cookie, word16 cookieSz) int ret; byte cookieType = 0; byte macSz = 0; + CookieSecrets cs; #ifndef NO_SHA256 cookieType = WC_SHA256; @@ -7794,50 +7791,29 @@ 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) -#ifdef WOLFSSL_DTLS13 - && (ssl->buffers.tls13CookieSecretSecondary.buffer == NULL || - ssl->buffers.tls13CookieSecretSecondary.length == 0) -#endif - ) { - WOLFSSL_MSG("Missing DTLS 1.3 cookie secret"); - return COOKIE_ERROR; - } + ret = CookieSecretGet(ssl, CTX_COOKIE_HRR, &cs); + if (ret != 0) + return ret; - if (cookieSz < ssl->specs.hash_size + macSz) + if (cookieSz < ssl->specs.hash_size + macSz) { + CookieSecretsClear(&cs); return HRR_COOKIE_ERROR; + } cookieSz -= macSz; - /* 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. The secondary secret is - * DTLS 1.3 only, so its verify path is compiled in only for WOLFSSL_DTLS13. */ - ret = WC_NO_ERR_TRACE(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 != WC_NO_ERR_TRACE(HRR_COOKIE_ERROR)) - return ret; + /* The secondary, verify only, keeps cookies issued before a rotation of + * the secret valid */ + ret = TlsCheckCookieMac(ssl, cookie, cookieSz, cs.pri, cs.priSz, + cookieType, macSz); + if (ret == WC_NO_ERR_TRACE(HRR_COOKIE_ERROR) && cs.sec != NULL) { + ret = TlsCheckCookieMac(ssl, cookie, cookieSz, cs.sec, cs.secSz, + cookieType, macSz); } -#ifdef WOLFSSL_DTLS13 - if (ret == WC_NO_ERR_TRACE(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 != WC_NO_ERR_TRACE(HRR_COOKIE_ERROR)) - return ret; - } -#endif - + CookieSecretsClear(&cs); if (ret != 0) { - WOLFSSL_ERROR_VERBOSE(HRR_COOKIE_ERROR); - return HRR_COOKIE_ERROR; + if (ret == WC_NO_ERR_TRACE(HRR_COOKIE_ERROR)) + WOLFSSL_ERROR_VERBOSE(HRR_COOKIE_ERROR); + return ret; } return cookieSz; @@ -8568,17 +8544,13 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, } #endif -#ifdef HAVE_SNI - if ((ret = SNI_Callback(ssl)) != 0) - goto exit_dch; - ssl->options.side = WOLFSSL_SERVER_END; -#endif - args->idx += totalExtSz; ssl->options.haveSessionId = 1; ssl->options.sendVerify = SEND_CERT; #if defined(WOLFSSL_SEND_HRR_COOKIE) + /* Before the SNI callback: it may move the object to a context with + * another cookie secret (wolfSSL_set_SSL_CTX()). */ ssl->options.cookieGood = 0; if (ssl->options.sendCookie && (ssl->options.serverState == SERVER_HELLO_RETRY_REQUEST_COMPLETE @@ -8616,6 +8588,12 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, } #endif +#ifdef HAVE_SNI + if ((ret = SNI_Callback(ssl)) != 0) + goto exit_dch; + ssl->options.side = WOLFSSL_SERVER_END; +#endif + #ifdef HAVE_SUPPORTED_CURVES if (ssl->hrr_keyshare_group != 0) { /* @@ -17221,9 +17199,12 @@ int wolfSSL_connect_TLSv13(WOLFSSL* ssl) * * ssl SSL/TLS object. * secret Secret to use when generating integrity check for cookie. - * A value of NULL indicates to generate a new random secret. + * A value of NULL indicates to use the context's secret on a DTLS + * object whose context has one, and otherwise to generate a new + * random secret. * secretSz Size of secret data in bytes. - * Use a value of 0 to indicate use of default size. + * Use a value of 0 to indicate use of default size. Ignored when + * the context's secret is used. * returns BAD_FUNC_ARG when ssl is NULL or not using TLS v1.3, SIDE_ERROR when * called on a client, BAD_STATE_E when the DTLS handshake has already decided * how to process this ClientHello; WOLFSSL_SUCCESS on success and otherwise diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index 3dd2fa694af..24bf1174b39 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -8663,6 +8663,47 @@ int test_dtls13_hrr_cookie_secret_secondary(void) return EXPECT_RESULT(); } +/* Setting a primary keeps a secondary the application set, even after a + * NULL primary in between. */ +int test_dtls13_hrr_cookie_secret_secondary_null_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]; + 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); + + ExpectIntEQ(wolfSSL_set_hrr_cookie_secret_secondary(ssl_s, s1, sizeof(s1)), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_send_hrr_cookie(ssl_s, NULL, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_send_hrr_cookie(ssl_s, s2, sizeof(s2)), + WOLFSSL_SUCCESS); + ExpectNotNull(ssl_s); + ExpectNotNull(ssl_s->buffers.tls13CookieSecretSecondary.buffer); + + 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) @@ -9212,6 +9253,49 @@ int test_dtls12_cookie_secret_secondary(void) return EXPECT_RESULT(); } +/* Setting a primary keeps a secondary the application set, even after a + * NULL primary in between. */ +int test_dtls12_cookie_secret_secondary_null_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]; + 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); + + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecretSecondary(ssl_s, s1, sizeof(s1)), + 0); + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecret(ssl_s, NULL, 0), 0); + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecret(ssl_s, s2, sizeof(s2)), 0); + ExpectNotNull(ssl_s); + ExpectNotNull(ssl_s->buffers.dtlsCookieSecretSecondary.buffer); + + ExpectIntEQ(wolfSSL_negotiate(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + 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. */ @@ -9765,14 +9849,20 @@ int test_dtls_cookie_policy(void) "custom12-secret!", 16); } } - /* Secrets must be ready before accept, not generated by it. */ + /* Secrets must be ready before accept, not generated by it: the + * object's own or the context's */ if (enabled) { - ExpectNotNull(ssl_s->buffers.dtlsCookieSecret.buffer); - ExpectIntGT(ssl_s->buffers.dtlsCookieSecret.length, 0); + CookieSecrets cs; + + ExpectIntEQ(CookieSecretGet(ssl_s, CTX_COOKIE_DTLS12, &cs), 0); + ExpectIntGT(cs.priSz, 0); + CookieSecretsClear(&cs); #if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) if (version != 0) { - ExpectNotNull(ssl_s->buffers.tls13CookieSecret.buffer); - ExpectIntGT(ssl_s->buffers.tls13CookieSecret.length, 0); + ExpectIntEQ(CookieSecretGet(ssl_s, CTX_COOKIE_HRR, &cs), + 0); + ExpectIntGT(cs.priSz, 0); + CookieSecretsClear(&cs); } #endif } @@ -16558,129 +16648,135 @@ int test_dtls_sctp_app_data_size(void) return EXPECT_RESULT(); } -/* A failure to generate a new secret leaves the object exactly as it was - * found: the secret the caller asked to replace is still installed and still - * usable, and the cookie policy is unchanged. ssl->rng is cleared to model - * a reused object whose handshake resources have not been recreated yet. */ +#if defined(WOLFSSL_DTLS) && !defined(NO_WOLFSSL_SERVER) +/* Erase the context's secret under its lock: no object of it has one then */ +static void test_dtls_cs_ctx_erase(WOLFSSL_CTX* ctx) +{ + if (ctx == NULL) + return; +#ifndef SINGLE_THREADED + if (wc_LockRwLock_Wr(&ctx->cookieSecretLock) != 0) + return; +#endif + ctx->cookieSecret.keySz[0] = 0; + ctx->cookieSecret.keySz[1] = 0; +#ifndef SINGLE_THREADED + wc_UnLockRwLock(&ctx->cookieSecretLock); +#endif +} +#endif + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS) && \ + !defined(NO_WOLFSSL_SERVER) && defined(WOLF_CRYPTO_CB) && \ + !defined(WOLF_CRYPTO_CB_ONLY_SHA256) && !defined(WOLF_CRYPTO_CB_ONLY_SHA) && \ + !defined(HAVE_FIPS) + #define TEST_DTLS_CS_RNG_FAIL_DEVID 0x5C6 + +/* A device whose random numbers fail; the rest is left to software */ +static int test_dtls_cs_rng_fail_cb(int devId, wc_CryptoInfo* info, void* ctx) +{ + (void)devId; + (void)ctx; + if (info->algo_type == WC_ALGO_TYPE_RNG) + return WC_NO_ERR_TRACE(RNG_FAILURE_E); + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); +} +#endif + +/* The context's secret is made on its device. When that fails the object and + * its policy are left as found: cookies are not turned on without a secret, + * and a caller-supplied secret needs no random numbers. */ int test_dtls12_cookie_secret_generate_fail(void) { EXPECT_DECLS; -#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS) \ - && !defined(WOLFSSL_NO_TLS12) && !defined(NO_WOLFSSL_SERVER) +#if defined(TEST_DTLS_CS_RNG_FAIL_DEVID) && !defined(WOLFSSL_NO_TLS12) WOLFSSL_CTX *ctx_s = NULL; WOLFSSL *ssl_s = NULL; struct test_memio_ctx test_ctx; - WC_RNG* savedRng = NULL; - byte* oldSecret = NULL; - word32 oldSecretSz = 0; XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_DTLS_CS_RNG_FAIL_DEVID, + test_dtls_cs_rng_fail_cb, NULL), 0); ExpectIntEQ(test_memio_setup(&test_ctx, NULL, &ctx_s, NULL, &ssl_s, NULL, wolfDTLSv1_2_server_method), 0); - /* The object is created with cookies enabled and a secret generated. */ + /* The object is created with cookies enabled, on the context's secret */ ExpectIntEQ(ssl_s->options.sendCookie, 1); - ExpectNotNull(ssl_s->buffers.dtlsCookieSecret.buffer); + ExpectNull(ssl_s->buffers.dtlsCookieSecret.buffer); + ExpectIntEQ(ctx_s->cookieSecret.keySz[0], CTX_COOKIE_KEY_SZ); + ExpectIntEQ(wolfSSL_CTX_SetDevId(ctx_s, TEST_DTLS_CS_RNG_FAIL_DEVID), + WOLFSSL_SUCCESS); + test_dtls_cs_ctx_erase(ctx_s); - if (EXPECT_SUCCESS()) { - oldSecret = ssl_s->buffers.dtlsCookieSecret.buffer; - oldSecretSz = ssl_s->buffers.dtlsCookieSecret.length; - savedRng = ssl_s->rng; - ssl_s->rng = NULL; - } + /* NULL asks for the context's secret, which cannot be made */ ExpectIntEQ(wolfSSL_DTLS_SetCookieSecret(ssl_s, NULL, 0), - WC_NO_ERR_TRACE(BAD_STATE_E)); - /* The rotation did not happen, so the server keeps issuing cookies under - * the secret it already had rather than losing it. */ - ExpectPtrEq(ssl_s->buffers.dtlsCookieSecret.buffer, oldSecret); - ExpectIntEQ(ssl_s->buffers.dtlsCookieSecret.length, oldSecretSz); - /* The policy is not a secret and is left alone. */ + WC_NO_ERR_TRACE(RNG_FAILURE_E)); + ExpectIntEQ(ctx_s->cookieSecret.keySz[0], 0); ExpectIntEQ(ssl_s->options.sendCookie, 1); /* wolfSSL_enable_cookie() reports the same failure and leaves the policy * off, rather than enabling cookies it cannot honour. */ ExpectIntEQ(wolfSSL_disable_cookie(ssl_s), WOLFSSL_SUCCESS); ExpectIntEQ(ssl_s->options.sendCookie, 0); - ExpectIntEQ(wolfSSL_enable_cookie(ssl_s), WC_NO_ERR_TRACE(BAD_STATE_E)); + ExpectIntEQ(wolfSSL_enable_cookie(ssl_s), WC_NO_ERR_TRACE(RNG_FAILURE_E)); ExpectIntEQ(ssl_s->options.sendCookie, 0); - ExpectNull(ssl_s->buffers.dtlsCookieSecret.buffer); - ExpectNull(ssl_s->buffers.dtlsCookieSecretSecondary.buffer); - if (ssl_s != NULL) - ssl_s->rng = savedRng; + /* A caller-supplied secret needs no RNG */ + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecret(ssl_s, + (const byte*)"0123456789abcdef", 16), 0); + ExpectIntEQ(wolfSSL_enable_cookie(ssl_s), WOLFSSL_SUCCESS); + ExpectIntEQ(ssl_s->options.sendCookie, 1); + wolfSSL_free(ssl_s); wolfSSL_CTX_free(ctx_s); + wc_CryptoCb_UnRegisterDevice(TEST_DTLS_CS_RNG_FAIL_DEVID); #endif return EXPECT_RESULT(); } -/* As test_dtls12_cookie_secret_generate_fail(), for the HelloRetryRequest - * secret of a DTLS 1.3 server. */ +/* As test_dtls12_cookie_secret_generate_fail(), for a DTLS 1.3 server, which + * needs the DTLS 1.2 fallback secret too: all or nothing. */ int test_dtls13_hrr_cookie_secret_generate_fail(void) { EXPECT_DECLS; -#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ - && defined(WOLFSSL_SEND_HRR_COOKIE) && !defined(NO_WOLFSSL_SERVER) +#if defined(TEST_DTLS_CS_RNG_FAIL_DEVID) && defined(WOLFSSL_DTLS13) && \ + defined(WOLFSSL_SEND_HRR_COOKIE) WOLFSSL_CTX *ctx_s = NULL; WOLFSSL *ssl_s = NULL; struct test_memio_ctx test_ctx; - WC_RNG* savedRng = NULL; - byte* oldSecret = NULL; - word32 oldSecretSz = 0; XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_DTLS_CS_RNG_FAIL_DEVID, + test_dtls_cs_rng_fail_cb, NULL), 0); ExpectIntEQ(test_memio_setup(&test_ctx, NULL, &ctx_s, NULL, &ssl_s, NULL, wolfDTLSv1_3_server_method), 0); ExpectIntEQ(ssl_s->options.sendCookie, 1); - ExpectNotNull(ssl_s->buffers.tls13CookieSecret.buffer); + ExpectNull(ssl_s->buffers.tls13CookieSecret.buffer); + ExpectIntEQ(wolfSSL_CTX_SetDevId(ctx_s, TEST_DTLS_CS_RNG_FAIL_DEVID), + WOLFSSL_SUCCESS); + test_dtls_cs_ctx_erase(ctx_s); - if (EXPECT_SUCCESS()) { - oldSecret = ssl_s->buffers.tls13CookieSecret.buffer; - oldSecretSz = ssl_s->buffers.tls13CookieSecret.length; - savedRng = ssl_s->rng; - ssl_s->rng = NULL; - } ExpectIntEQ(wolfSSL_send_hrr_cookie(ssl_s, NULL, 0), - WC_NO_ERR_TRACE(BAD_STATE_E)); - ExpectPtrEq(ssl_s->buffers.tls13CookieSecret.buffer, oldSecret); - ExpectIntEQ(ssl_s->buffers.tls13CookieSecret.length, oldSecretSz); + WC_NO_ERR_TRACE(RNG_FAILURE_E)); ExpectIntEQ(ssl_s->options.sendCookie, 1); - /* A caller-supplied secret needs no RNG and still works. */ + /* An own HRR secret still leaves the DTLS 1.2 fallback secret to the + * context, so nothing is installed */ + ExpectIntEQ(wolfSSL_send_hrr_cookie(ssl_s, (const byte*)"0123456789abcdef", + 16), WC_NO_ERR_TRACE(RNG_FAILURE_E)); + ExpectNull(ssl_s->buffers.tls13CookieSecret.buffer); + + /* Both of its own: no RNG needed */ + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecret(ssl_s, + (const byte*)"fedcba9876543210", 16), 0); ExpectIntEQ(wolfSSL_send_hrr_cookie(ssl_s, (const byte*)"0123456789abcdef", 16), WOLFSSL_SUCCESS); ExpectNotNull(ssl_s->buffers.tls13CookieSecret.buffer); ExpectIntEQ(ssl_s->buffers.tls13CookieSecret.length, 16); ExpectIntEQ(ssl_s->options.sendCookie, 1); - /* A DTLS 1.3 server also needs the DTLS 1.2 secret for the fallback - * HelloVerifyRequest. Drop just that one and break the RNG again: the - * generation of the missing secret is the step that fails, and it must - * not leave the caller-supplied HelloRetryRequest secret half installed. - */ - if (EXPECT_SUCCESS()) { - ForceZero(ssl_s->buffers.dtlsCookieSecret.buffer, - ssl_s->buffers.dtlsCookieSecret.length); - XFREE(ssl_s->buffers.dtlsCookieSecret.buffer, ssl_s->heap, - DYNAMIC_TYPE_COOKIE_PWD); - ssl_s->buffers.dtlsCookieSecret.buffer = NULL; - ssl_s->buffers.dtlsCookieSecret.length = 0; - oldSecret = ssl_s->buffers.tls13CookieSecret.buffer; - oldSecretSz = ssl_s->buffers.tls13CookieSecret.length; - ssl_s->rng = NULL; - } - ExpectIntEQ(wolfSSL_send_hrr_cookie(ssl_s, (const byte*)"fedcba9876543210", - 16), WC_NO_ERR_TRACE(BAD_STATE_E)); - ExpectPtrEq(ssl_s->buffers.tls13CookieSecret.buffer, oldSecret); - ExpectIntEQ(ssl_s->buffers.tls13CookieSecret.length, oldSecretSz); - ExpectIntEQ(XMEMCMP(ssl_s->buffers.tls13CookieSecret.buffer, - "0123456789abcdef", 16), 0); - ExpectNull(ssl_s->buffers.dtlsCookieSecret.buffer); - ExpectIntEQ(ssl_s->options.sendCookie, 1); - - if (ssl_s != NULL) - ssl_s->rng = savedRng; wolfSSL_free(ssl_s); wolfSSL_CTX_free(ctx_s); + wc_CryptoCb_UnRegisterDevice(TEST_DTLS_CS_RNG_FAIL_DEVID); #endif return EXPECT_RESULT(); } @@ -16804,3 +16900,1283 @@ int test_dtls_set_rfd_wfd(void) #endif return EXPECT_RESULT(); } + +/* Context cookie secret tests. Servers here leave the peer unset: memio has + * no address, so every server sees the same empty one. */ +#if defined(WOLFSSL_DTLS) && !defined(NO_WOLFSSL_SERVER) && \ + (defined(NO_CERTS) || (!defined(NO_RSA) && !defined(NO_FILESYSTEM))) + #define TEST_DTLS_CS_API +#endif +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS) + #define TEST_DTLS_CS +#endif +#if defined(WOLFSSL_DTLS) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_WOLFSSL_CLIENT) && \ + (defined(OPENSSL_EXTRA) || defined(WOLFSSL_EITHER_SIDE)) && \ + (defined(OPENSSL_EXTRA) || defined(WOLFSSL_EXTRA) || \ + defined(WOLFSSL_WPAS_SMALL)) + #define TEST_DTLS_CS_SIDE +#endif + +#if (defined(WOLFSSL_DTLS) || defined(WOLFSSL_SEND_HRR_COOKIE)) && \ + !defined(NO_WOLFSSL_SERVER) +/* The key a context cookie secret gives cookie type type (CTX_COOKIE_DTLS12 + * or CTX_COOKIE_HRR): an HMAC of its label. */ +int test_dtls_ctx_cookie_key(const unsigned char* secret, + unsigned int secretSz, int type, unsigned char* key) +{ + static const char* label[2] = { + "wolfSSL DTLS HelloVerifyRequest cookie", + "wolfSSL HelloRetryRequest cookie" + }; + Hmac hmac; + int ret; + + ret = wc_HmacInit(&hmac, NULL, INVALID_DEVID); + if (ret == 0) { + ret = wc_HmacSetKey(&hmac, CTX_COOKIE_KEY_TYPE, secret, secretSz); + if (ret == 0) { + ret = wc_HmacUpdate(&hmac, (const byte*)label[type], + (word32)XSTRLEN(label[type])); + } + if (ret == 0) + ret = wc_HmacFinal(&hmac, key); + wc_HmacFree(&hmac); + } + return ret; +} + +/* Whether s resolves cookie type type to the primary pri, priSz bytes, and + * the secondary sec, secSz bytes. NULL: none. */ +int test_dtls_cs_resolves(const WOLFSSL* s, int type, const byte* pri, + word32 priSz, const byte* sec, word32 secSz) +{ + CookieSecrets cs; + int ok; + + if (s == NULL) + return 0; + if (CookieSecretGet(s, type, &cs) != 0) + return pri == NULL; + ok = pri != NULL && cs.pri != NULL && cs.priSz == priSz && + XMEMCMP(cs.pri, pri, priSz) == 0; + if (sec == NULL) + ok = ok && cs.sec == NULL; + else + ok = ok && cs.sec != NULL && cs.secSz == secSz && + XMEMCMP(cs.sec, sec, secSz) == 0; + CookieSecretsClear(&cs); + return ok; +} + +/* Whether s resolves cookie type type to the keys context secrets p (primary, + * pSz bytes) and q (secondary, qSz bytes, NULL for none) give */ +int test_dtls_cs_uses_ctx(const WOLFSSL* s, int type, const byte* p, + word32 pSz, const byte* q, word32 qSz) +{ + byte kp[CTX_COOKIE_KEY_SZ]; + byte kq[CTX_COOKIE_KEY_SZ]; + + if (p == NULL) + return test_dtls_cs_resolves(s, type, NULL, 0, NULL, 0); + if (test_dtls_ctx_cookie_key(p, pSz, type, kp) != 0) + return 0; + if (q != NULL && test_dtls_ctx_cookie_key(q, qSz, type, kq) != 0) + return 0; + return test_dtls_cs_resolves(s, type, kp, sizeof(kp), + q != NULL ? kq : NULL, sizeof(kq)); +} +#endif + +#ifdef TEST_DTLS_CS +/* Whether b holds exactly the sz bytes at secret, or nothing when secret is + * NULL */ +static int test_dtls_cs_holds(const buffer* b, const byte* secret, word32 sz) +{ + if (secret == NULL) + return b->buffer == NULL && b->length == 0; + return b->buffer != NULL && b->length == sz && + XMEMCMP(b->buffer, secret, sz) == 0; +} +#endif + +#if defined(TEST_DTLS_CS_API) || defined(TEST_DTLS_CS) || \ + defined(TEST_DTLS_CS_SIDE) +/* Whether s uses the keys of context secrets p (primary) and q (secondary, + * NULL for none), sz bytes each, for the DTLS 1.2 cookie, and the HRR cookie + * when hrr is set */ +static int test_dtls_cs_pair(const WOLFSSL* s, int hrr, const byte* p, + const byte* q, word32 sz) +{ + if (!test_dtls_cs_uses_ctx(s, CTX_COOKIE_DTLS12, p, sz, q, sz)) + return 0; +#ifdef WOLFSSL_SEND_HRR_COOKIE + if (hrr && !test_dtls_cs_uses_ctx(s, CTX_COOKIE_HRR, p, sz, q, sz)) + return 0; +#else + (void)hrr; +#endif + return 1; +} + +#if defined(TEST_DTLS_CS_API) || defined(TEST_DTLS_CS) +/* Whether the context holds the keys of secrets p, pSz bytes, primary, and q, + * qSz bytes, secondary (NULL: none) */ +static int test_dtls_cs_ctx_holds(const WOLFSSL_CTX* ctx, const byte* p, + word32 pSz, const byte* q, word32 qSz) +{ + const byte* secret[2]; + word32 sz[2]; + byte key[CTX_COOKIE_KEY_SZ]; + int slot; + int type; + + if (ctx == NULL) + return 0; + secret[0] = p; + secret[1] = q; + sz[0] = pSz; + sz[1] = qSz; + for (slot = 0; slot < 2; slot++) { + if (secret[slot] == NULL) { + if (ctx->cookieSecret.keySz[slot] != 0) + return 0; + continue; + } + if (ctx->cookieSecret.keySz[slot] != CTX_COOKIE_KEY_SZ) + return 0; + for (type = 0; type < 2; type++) { + if (test_dtls_ctx_cookie_key(secret[slot], sz[slot], type, + key) != 0 || + XMEMCMP(type == CTX_COOKIE_HRR ? + ctx->cookieSecret.hrrKey[slot] : + ctx->cookieSecret.hvrKey[slot], key, + sizeof(key)) != 0) + return 0; + } + } + return 1; +} +#endif +#endif + +#ifdef TEST_DTLS_CS +/* A new client of ctx_c on memio t sends server s its first ClientHello, gets + * a HelloRetryRequest (dtls13) or HelloVerifyRequest, and sends its second. */ +static int test_dtls_cs_to_ch2(WOLFSSL_CTX* ctx_c, WOLFSSL* s, + struct test_memio_ctx* t, WOLFSSL** ssl_c, int dtls13) +{ + EXPECT_DECLS; +#if defined(HAVE_ECC) && !defined(NO_ECC256) + int group = WOLFSSL_ECC_SECP256R1; +#endif + + XMEMSET(t, 0, sizeof(*t)); + ExpectNotNull(*ssl_c = wolfSSL_new(ctx_c)); +#if defined(HAVE_ECC) && !defined(NO_ECC256) + /* One small key share: the ClientHello is never fragmented */ + if (dtls13) + ExpectIntEQ(wolfSSL_set_groups(*ssl_c, &group, 1), WOLFSSL_SUCCESS); +#endif + wolfSSL_SetIOReadCtx(*ssl_c, t); + wolfSSL_SetIOWriteCtx(*ssl_c, t); + wolfSSL_SetIOReadCtx(s, t); + wolfSSL_SetIOWriteCtx(s, t); + ExpectIntEQ(wolfSSL_negotiate(*ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(*ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_negotiate(s), -1); + ExpectIntEQ(wolfSSL_get_error(s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectNotNull(s); + ExpectIntEQ(s->options.dtlsStateful, 0); + ExpectIntGT(t->c_len, DTLS_RECORD_HEADER_SZ); + ExpectIntEQ(t->c_buff[0], handshake); + ExpectIntEQ(t->c_buff[DTLS_RECORD_HEADER_SZ], + dtls13 ? server_hello : hello_verify_request); + ExpectIntEQ(wolfSSL_negotiate(*ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(*ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + return EXPECT_RESULT(); +} + +/* Server s handles the second ClientHello of ssl_c on memio t. ok: the + * handshake completes. Else DTLS 1.3 sends illegal_parameter, 1.2 a new HVR. */ +static int test_dtls_cs_ch2_at(WOLFSSL* ssl_c, WOLFSSL* s, + struct test_memio_ctx* t, int dtls13, int ok) +{ + EXPECT_DECLS; + WOLFSSL_ALERT_HISTORY h; + + XMEMSET(&h, 0, sizeof(h)); + ExpectNotNull(s); + wolfSSL_SetIOReadCtx(s, t); + wolfSSL_SetIOWriteCtx(s, t); + if (!dtls13) { + ExpectIntEQ(wolfSSL_negotiate(s), -1); + ExpectIntEQ(wolfSSL_get_error(s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(s->options.dtlsStateful, ok); + ExpectIntGT(t->c_len, DTLS_RECORD_HEADER_SZ); + ExpectIntEQ(t->c_buff[0], handshake); + ExpectIntEQ(t->c_buff[DTLS_RECORD_HEADER_SZ], + ok ? server_hello : hello_verify_request); + } + if (ok) { + ExpectIntEQ(test_memio_do_handshake(ssl_c, s, 10, NULL), 0); + } + else if (dtls13) { + ExpectIntNE(test_memio_do_handshake(ssl_c, s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_get_alert_history(ssl_c, &h), WOLFSSL_SUCCESS); + ExpectIntEQ(h.last_rx.code, illegal_parameter); + } + return EXPECT_RESULT(); +} + +/* The protocol versions the context cookie secret tests run */ +typedef struct test_dtls_cs_ver { + method_provider client; + method_provider server; + int dtls13; +} test_dtls_cs_ver; + +static const test_dtls_cs_ver test_dtls_cs_vers[] = { +#ifndef WOLFSSL_NO_TLS12 + { wolfDTLSv1_2_client_method, wolfDTLSv1_2_server_method, 0 }, +#endif +#if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) + { wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method, 1 }, +#endif + { NULL, NULL, 0 } +}; +#endif + +/* Argument checks of the context cookie secret API, and what an object of the + * context resolves to. */ +int test_dtls_ctx_cookie_secret_api(void) +{ + EXPECT_DECLS; +#ifdef TEST_DTLS_CS_API + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte buf[100]; + byte p[64]; + byte p2[32]; + byte q[16]; + byte zero[CTX_COOKIE_KEY_SZ]; + byte zeroKey[CTX_COOKIE_KEY_SZ]; + byte key0[CTX_COOKIE_KEY_SZ]; + byte key1[CTX_COOKIE_KEY_SZ]; + int hrr = 0; + + XMEMSET(buf, 0x5A, sizeof(buf)); + XMEMSET(p, 0x11, sizeof(p)); + XMEMSET(p2, 0x22, sizeof(p2)); + XMEMSET(q, 0x33, sizeof(q)); + XMEMSET(zero, 0, sizeof(zero)); + XMEMSET(zeroKey, 0, sizeof(zeroKey)); + XMEMSET(key0, 0, sizeof(key0)); + XMEMSET(key1, 0, sizeof(key1)); + + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(NULL, p2, sizeof(p2)), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(NULL, NULL, 0), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret_secondary(NULL, p2, + sizeof(p2)), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(NULL, p2, sizeof(p2)), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + +#ifndef NO_WOLFSSL_CLIENT + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLS_client_method())); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx, p2, sizeof(p2)), + WC_NO_ERR_TRACE(SIDE_ERROR)); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret_secondary(ctx, p2, + sizeof(p2)), WC_NO_ERR_TRACE(SIDE_ERROR)); + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx, p2, sizeof(p2)), + WC_NO_ERR_TRACE(SIDE_ERROR)); + wolfSSL_CTX_free(ctx); + ctx = NULL; +#endif +#if !defined(NO_TLS) && !defined(NO_WOLFSSL_SERVER) + /* A TLS server context takes a secret too */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_server_method())); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx, p2, sizeof(p2)), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret_secondary(ctx, p2, + sizeof(p2)), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx, q, sizeof(q)), + WOLFSSL_SUCCESS); + ExpectTrue(test_dtls_cs_ctx_holds(ctx, q, sizeof(q), p2, sizeof(p2))); + wolfSSL_CTX_free(ctx); + ctx = NULL; +#endif +#if defined(OPENSSL_EXTRA) || defined(WOLFSSL_EITHER_SIDE) + /* A context for either side is accepted */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLS_method())); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx, p2, sizeof(p2)), + WOLFSSL_SUCCESS); + wolfSSL_CTX_free(ctx); + ctx = NULL; +#endif + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLS_server_method())); +#ifndef NO_CERTS + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx, svrKeyFile, CERT_FILETYPE), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx, svrCertFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); +#endif +#if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) + hrr = 1; +#endif + + /* A secret without a size changes nothing */ + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx, buf, 0), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx, buf, 0), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectNotNull(ctx); + ExpectIntEQ(ctx->cookieSecret.keySz[0], 0); + ExpectIntEQ(ctx->cookieSecret.keySz[1], 0); + + /* A secondary alone is no secret: the first object made gives the context + * a random primary, which it uses with the secondary */ + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret_secondary(ctx, q, + sizeof(q)), WOLFSSL_SUCCESS); + ExpectTrue(test_dtls_cs_ctx_holds(ctx, NULL, 0, q, sizeof(q))); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(ctx->cookieSecret.keySz[0], CTX_COOKIE_KEY_SZ); + ExpectIntEQ(ctx->cookieSecret.keySz[1], CTX_COOKIE_KEY_SZ); + ExpectNull(ssl->buffers.dtlsCookieSecret.buffer); + ExpectNull(ssl->buffers.dtlsCookieSecretSecondary.buffer); + if (EXPECT_SUCCESS()) { + ExpectTrue(test_dtls_cs_resolves(ssl, CTX_COOKIE_DTLS12, + ctx->cookieSecret.hvrKey[0], CTX_COOKIE_KEY_SZ, + ctx->cookieSecret.hvrKey[1], CTX_COOKIE_KEY_SZ)); + } + wolfSSL_free(ssl); + ssl = NULL; + + /* NULL makes a random secret as long as the keys. A size given with it is + * not used. */ + ExpectIntEQ(test_dtls_ctx_cookie_key(zero, sizeof(zero), + CTX_COOKIE_DTLS12, zeroKey), 0); + if (ctx != NULL) + XMEMCPY(key0, ctx->cookieSecret.hvrKey[0], sizeof(key0)); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx, NULL, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(ctx->cookieSecret.keySz[0], CTX_COOKIE_KEY_SZ); + ExpectIntEQ(ctx->cookieSecret.keySz[1], CTX_COOKIE_KEY_SZ); + ExpectBufNE(ctx->cookieSecret.hvrKey[0], key0, sizeof(key0)); + ExpectBufNE(ctx->cookieSecret.hvrKey[0], zeroKey, + sizeof(zeroKey)); + if (ctx != NULL) + XMEMCPY(key1, ctx->cookieSecret.hvrKey[0], sizeof(key1)); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx, NULL, 1), + WOLFSSL_SUCCESS); + ExpectIntEQ(ctx->cookieSecret.keySz[0], CTX_COOKIE_KEY_SZ); + ExpectBufNE(ctx->cookieSecret.hvrKey[0], key1, sizeof(key1)); + ExpectBufNE(ctx->cookieSecret.hvrKey[0], zeroKey, + sizeof(zeroKey)); + + /* Rotate moves the primary to the secondary */ + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx, p, sizeof(p)), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx, p2, sizeof(p2)), + WOLFSSL_SUCCESS); + ExpectTrue(test_dtls_cs_ctx_holds(ctx, p2, sizeof(p2), p, sizeof(p))); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectTrue(test_dtls_cs_uses_ctx(ssl, CTX_COOKIE_DTLS12, p2, sizeof(p2), + p, sizeof(p))); +#if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) + ExpectTrue(test_dtls_cs_uses_ctx(ssl, CTX_COOKIE_HRR, p2, sizeof(p2), + p, sizeof(p))); + /* The two cookie types never share a key */ + if (EXPECT_SUCCESS()) { + CookieSecrets dtls12; + CookieSecrets hrrCs; + + ExpectIntEQ(CookieSecretGet(ssl, CTX_COOKIE_DTLS12, &dtls12), 0); + ExpectIntEQ(CookieSecretGet(ssl, CTX_COOKIE_HRR, &hrrCs), 0); + ExpectIntEQ(dtls12.priSz, hrrCs.priSz); + ExpectBufNE(dtls12.pri, hrrCs.pri, dtls12.priSz); + CookieSecretsClear(&dtls12); + CookieSecretsClear(&hrrCs); + } +#endif + /* The secret itself is not a key */ + ExpectFalse(test_dtls_cs_resolves(ssl, CTX_COOKIE_DTLS12, p2, sizeof(p2), + p, sizeof(p))); + wolfSSL_free(ssl); + ssl = NULL; + + /* Clearing the secondary leaves the primary */ + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret_secondary(ctx, NULL, 0), + WOLFSSL_SUCCESS); + ExpectTrue(test_dtls_cs_ctx_holds(ctx, p2, sizeof(p2), NULL, 0)); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret_secondary(ctx, p, 0), + WOLFSSL_SUCCESS); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectTrue(test_dtls_cs_pair(ssl, hrr, p2, NULL, sizeof(p2))); + wolfSSL_free(ssl); + ssl = NULL; + wolfSSL_CTX_free(ctx); + ctx = NULL; + + /* Rotate with no primary clears the secondary */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLS_server_method())); +#ifndef NO_CERTS + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx, svrKeyFile, CERT_FILETYPE), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx, svrCertFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); +#endif + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret_secondary(ctx, q, + sizeof(q)), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx, p2, sizeof(p2)), + WOLFSSL_SUCCESS); + ExpectTrue(test_dtls_cs_ctx_holds(ctx, p2, sizeof(p2), NULL, 0)); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectTrue(test_dtls_cs_pair(ssl, hrr, p2, NULL, sizeof(p2))); + + /* Without a secret anywhere there is no cookie */ + test_dtls_cs_ctx_erase(ctx); + ExpectTrue(test_dtls_cs_pair(ssl, hrr, NULL, NULL, 0)); + if (EXPECT_SUCCESS()) { + CookieSecrets cs; + + ExpectIntEQ(CookieSecretGet(ssl, CTX_COOKIE_DTLS12, &cs), + WC_NO_ERR_TRACE(COOKIE_ERROR)); + CookieSecretsClear(&cs); + } + wolfSSL_free(ssl); + ssl = NULL; + wolfSSL_CTX_free(ctx); + ctx = NULL; + + /* Any size is taken, as by the object setters. FIPS HMAC needs a key of + * 14 bytes or more. */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLS_server_method())); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx, buf, sizeof(buf)), + WOLFSSL_SUCCESS); + ExpectTrue(test_dtls_cs_ctx_holds(ctx, buf, sizeof(buf), NULL, 0)); +#ifdef HAVE_FIPS + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx, buf, 1), + WC_NO_ERR_TRACE(HMAC_MIN_KEYLEN_E)); + ExpectTrue(test_dtls_cs_ctx_holds(ctx, buf, sizeof(buf), NULL, 0)); +#else + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx, buf, 1), + WOLFSSL_SUCCESS); + ExpectTrue(test_dtls_cs_ctx_holds(ctx, buf, 1, buf, sizeof(buf))); +#endif + wolfSSL_CTX_free(ctx); + ctx = NULL; + (void)hrr; +#endif +#if defined(WOLFSSL_DTLS) && defined(NO_WOLFSSL_SERVER) + { + WOLFSSL_CTX* cctx = NULL; + byte s[32]; + + XMEMSET(s, 0x44, sizeof(s)); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(NULL, s, sizeof(s)), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret_secondary(NULL, s, + sizeof(s)), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(NULL, s, sizeof(s)), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + #ifndef NO_WOLFSSL_CLIENT + ExpectNotNull(cctx = wolfSSL_CTX_new(wolfDTLS_client_method())); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(cctx, s, sizeof(s)), + WC_NO_ERR_TRACE(SIDE_ERROR)); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret_secondary(cctx, s, + sizeof(s)), WC_NO_ERR_TRACE(SIDE_ERROR)); + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(cctx, s, sizeof(s)), + WC_NO_ERR_TRACE(SIDE_ERROR)); + wolfSSL_CTX_free(cctx); + #else + (void)cctx; + #endif + } +#endif + return EXPECT_RESULT(); +} + +#if defined(TEST_DTLS_CS_API) && defined(WOLF_CRYPTO_CB) && \ + !defined(WOLF_CRYPTO_CB_ONLY_SHA256) && !defined(WOLF_CRYPTO_CB_ONLY_SHA) + #define TEST_DTLS_CS_DEVID 0x5C5 + +/* Counts the requests of a device and leaves them to software */ +static int test_dtls_cs_devid_cb(int devId, wc_CryptoInfo* info, void* ctx) +{ + (void)devId; + (void)info; + (*(int*)ctx)++; + return WC_NO_ERR_TRACE(CRYPTOCB_UNAVAILABLE); +} +#endif + +/* The keys of a context cookie secret are made on the context's device. A + * build that has only the device's hash cannot make them otherwise. */ +int test_dtls_ctx_cookie_secret_devid(void) +{ + EXPECT_DECLS; +#ifdef TEST_DTLS_CS_DEVID + WOLFSSL_CTX* ctx = NULL; + byte p[32]; + byte q[32]; + int calls = 0; + + XMEMSET(p, 0x11, sizeof(p)); + XMEMSET(q, 0x22, sizeof(q)); + ExpectIntEQ(wc_CryptoCb_RegisterDevice(TEST_DTLS_CS_DEVID, + test_dtls_cs_devid_cb, &calls), 0); + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLS_server_method())); + ExpectIntEQ(wolfSSL_CTX_SetDevId(ctx, TEST_DTLS_CS_DEVID), + WOLFSSL_SUCCESS); + + calls = 0; + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx, p, sizeof(p)), + WOLFSSL_SUCCESS); + ExpectIntGT(calls, 0); + ExpectTrue(test_dtls_cs_ctx_holds(ctx, p, sizeof(p), NULL, 0)); + calls = 0; + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret_secondary(ctx, q, + sizeof(q)), WOLFSSL_SUCCESS); + ExpectIntGT(calls, 0); + ExpectTrue(test_dtls_cs_ctx_holds(ctx, p, sizeof(p), q, sizeof(q))); + calls = 0; + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx, q, sizeof(q)), + WOLFSSL_SUCCESS); + ExpectIntGT(calls, 0); + ExpectTrue(test_dtls_cs_ctx_holds(ctx, q, sizeof(q), p, sizeof(p))); + + wolfSSL_CTX_free(ctx); + wc_CryptoCb_UnRegisterDevice(TEST_DTLS_CS_DEVID); +#endif + return EXPECT_RESULT(); +} + +#if defined(TEST_DTLS_CS) && !defined(WOLFSSL_NO_TLS12) +/* A DTLS 1.2 client gets a HelloVerifyRequest from listener c1 and sends its + * second ClientHello to listener c2. mode 0: the context's default secret. + * 1: a secret set on the context. 2: c2 has a secret of its own: it fails. */ +static int test_dtls12_cs_handoff(method_provider server, int mode) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *c1 = NULL, *c2 = NULL; + struct test_memio_ctx t; + byte own[16]; + + XMEMSET(&t, 0, sizeof(t)); + XMEMSET(own, 0x77, sizeof(own)); + ExpectIntEQ(test_memio_setup(&t, &ctx_c, &ctx_s, NULL, NULL, + wolfDTLSv1_2_client_method, server), 0); + if (mode == 1) { + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s, NULL, 0), + WOLFSSL_SUCCESS); + } + ExpectNotNull(c1 = wolfSSL_new(ctx_s)); + ExpectIntEQ(test_dtls_cs_to_ch2(ctx_c, c1, &t, &ssl_c, 0), TEST_SUCCESS); + ExpectNotNull(c2 = wolfSSL_new(ctx_s)); + if (mode == 2) + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecret(c2, own, sizeof(own)), 0); + ExpectIntEQ(test_dtls_cs_ch2_at(ssl_c, c2, &t, 0, mode != 2), + TEST_SUCCESS); + ExpectIntEQ(c1->options.dtlsStateful, 0); + + wolfSSL_free(c1); + wolfSSL_free(c2); + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +/* The DTLS 1.2 HelloVerifyRequest cookie uses the context secret: a second + * ClientHello completes at another listener of the context. */ +int test_dtls12_ctx_cookie_secret_handoff(void) +{ + EXPECT_DECLS; +#if defined(TEST_DTLS_CS) && !defined(WOLFSSL_NO_TLS12) + int mode; + + for (mode = 0; mode < 3 && EXPECT_SUCCESS(); mode++) { + ExpectIntEQ(test_dtls12_cs_handoff(wolfDTLSv1_2_server_method, mode), + TEST_SUCCESS); + } +#endif + return EXPECT_RESULT(); +} + +/* As test_dtls12_ctx_cookie_secret_handoff(), for a DTLS 1.3 server that + * falls back to DTLS 1.2. */ +int test_dtls13_ctx_cookie_secret_dtls12_fallback(void) +{ + EXPECT_DECLS; +#if defined(TEST_DTLS_CS) && !defined(WOLFSSL_NO_TLS12) && \ + defined(WOLFSSL_DTLS13) + int mode; + + for (mode = 0; mode < 3 && EXPECT_SUCCESS(); mode++) { + ExpectIntEQ(test_dtls12_cs_handoff(wolfDTLS_server_method, mode), + TEST_SUCCESS); + } +#endif + return EXPECT_RESULT(); +} + +#ifdef TEST_DTLS_CS +static int test_dtls_cs_rotate_ex(const test_dtls_cs_ver* v) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *c1 = NULL, *s = NULL; + struct test_memio_ctx t; + byte p[6][32]; + word32 sz = (word32)sizeof(p[0]); + int i; + + for (i = 0; i < 6; i++) + XMEMSET(p[i], 0x11 * (i + 1), sizeof(p[i])); + XMEMSET(&t, 0, sizeof(t)); + ExpectIntEQ(test_memio_setup(&t, &ctx_c, &ctx_s, NULL, NULL, v->client, + v->server), 0); + + /* (1) X gets a cookie under P1 */ + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s, p[0], sz), + WOLFSSL_SUCCESS); + ExpectNotNull(c1 = wolfSSL_new(ctx_s)); + ExpectIntEQ(test_dtls_cs_to_ch2(ctx_c, c1, &t, &ssl_c, v->dtls13), + TEST_SUCCESS); + + /* (2) After a rotation P1 is the secondary: X completes */ + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx_s, p[1], sz), + WOLFSSL_SUCCESS); + ExpectNotNull(s = wolfSSL_new(ctx_s)); + ExpectTrue(test_dtls_cs_pair(s, v->dtls13, p[1], p[0], sz)); + ExpectIntEQ(test_dtls_cs_ch2_at(ssl_c, s, &t, v->dtls13, 1), + TEST_SUCCESS); + wolfSSL_free(s); + s = NULL; + wolfSSL_free(ssl_c); + ssl_c = NULL; + + /* (3) The long-lived listener uses the rotated secret at once: Y's cookie + * is under P2 and survives the next rotation */ + ExpectTrue(test_dtls_cs_pair(c1, v->dtls13, p[1], p[0], sz)); + ExpectIntEQ(test_dtls_cs_to_ch2(ctx_c, c1, &t, &ssl_c, v->dtls13), + TEST_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx_s, p[2], sz), + WOLFSSL_SUCCESS); + ExpectNotNull(s = wolfSSL_new(ctx_s)); + ExpectIntEQ(test_dtls_cs_ch2_at(ssl_c, s, &t, v->dtls13, 1), + TEST_SUCCESS); + wolfSSL_free(s); + s = NULL; + wolfSSL_free(ssl_c); + ssl_c = NULL; + + /* (4) Two rotations drop Z's secret */ + ExpectIntEQ(test_dtls_cs_to_ch2(ctx_c, c1, &t, &ssl_c, v->dtls13), + TEST_SUCCESS); + ExpectTrue(test_dtls_cs_pair(c1, v->dtls13, p[2], p[1], sz)); + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx_s, p[3], sz), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx_s, p[4], sz), + WOLFSSL_SUCCESS); + ExpectNotNull(s = wolfSSL_new(ctx_s)); + ExpectIntEQ(test_dtls_cs_ch2_at(ssl_c, s, &t, v->dtls13, 0), + TEST_SUCCESS); + wolfSSL_free(s); + s = NULL; + wolfSSL_free(ssl_c); + ssl_c = NULL; + + /* (5) Rotation in two calls: secondary first, then the primary */ + ExpectIntEQ(test_dtls_cs_to_ch2(ctx_c, c1, &t, &ssl_c, v->dtls13), + TEST_SUCCESS); + ExpectTrue(test_dtls_cs_pair(c1, v->dtls13, p[4], p[3], sz)); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret_secondary(ctx_s, p[4], sz), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s, p[5], sz), + WOLFSSL_SUCCESS); + ExpectNotNull(s = wolfSSL_new(ctx_s)); + ExpectTrue(test_dtls_cs_pair(s, v->dtls13, p[5], p[4], sz)); + ExpectIntEQ(test_dtls_cs_ch2_at(ssl_c, s, &t, v->dtls13, 1), + TEST_SUCCESS); + ExpectIntEQ(c1->options.dtlsStateful, 0); + + /* (6) A set, not a rotation, drops the cookie W got before it */ + wolfSSL_free(s); + s = NULL; + wolfSSL_free(ssl_c); + ssl_c = NULL; + ExpectIntEQ(test_dtls_cs_to_ch2(ctx_c, c1, &t, &ssl_c, v->dtls13), + TEST_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s, p[0], sz), + WOLFSSL_SUCCESS); + ExpectNotNull(s = wolfSSL_new(ctx_s)); + ExpectIntEQ(test_dtls_cs_ch2_at(ssl_c, s, &t, v->dtls13, 0), + TEST_SUCCESS); + + wolfSSL_free(s); + wolfSSL_free(ssl_c); + wolfSSL_free(c1); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +/* Rotating the context secret keeps the cookies made with the previous + * primary valid at every object of the context, until the next rotation. A + * set does not. */ +int test_dtls_ctx_cookie_secret_rotate(void) +{ + EXPECT_DECLS; +#ifdef TEST_DTLS_CS + int i; + + for (i = 0; test_dtls_cs_vers[i].client != NULL && EXPECT_SUCCESS(); i++) + ExpectIntEQ(test_dtls_cs_rotate_ex(&test_dtls_cs_vers[i]), + TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +/* A secret set on an object wins over the context's for that cookie type, and + * rules the context's secondary out with it. NULL goes back to the context's. + * An own secondary alone goes with the context's primary. */ +int test_dtls_ctx_cookie_secret_override(void) +{ + EXPECT_DECLS; +#if defined(TEST_DTLS_CS) && defined(WOLFSSL_DTLS13) && \ + defined(WOLFSSL_SEND_HRR_COOKIE) + { + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *c1 = NULL, *c2 = NULL, *c4 = NULL; + struct test_memio_ctx t; + byte p[32]; + byte p2[32]; + byte p3[32]; + byte r[32]; + byte sx[32]; + byte q[32]; + byte kp[CTX_COOKIE_KEY_SZ]; + byte kp2[CTX_COOKIE_KEY_SZ]; + + XMEMSET(&t, 0, sizeof(t)); + XMEMSET(p, 0x11, sizeof(p)); + XMEMSET(p2, 0x22, sizeof(p2)); + XMEMSET(p3, 0x33, sizeof(p3)); + XMEMSET(r, 0x44, sizeof(r)); + XMEMSET(sx, 0x55, sizeof(sx)); + XMEMSET(q, 0x66, sizeof(q)); + ExpectIntEQ(test_dtls_ctx_cookie_key(p, sizeof(p), CTX_COOKIE_HRR, + kp), 0); + ExpectIntEQ(test_dtls_ctx_cookie_key(p2, sizeof(p2), CTX_COOKIE_HRR, + kp2), 0); + ExpectIntEQ(test_memio_setup(&t, &ctx_c, &ctx_s, NULL, NULL, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret_secondary(ctx_s, r, + sizeof(r)), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s, p, sizeof(p)), + WOLFSSL_SUCCESS); + ExpectNotNull(c1 = wolfSSL_new(ctx_s)); + ExpectNotNull(c2 = wolfSSL_new(ctx_s)); + ExpectTrue(test_dtls_cs_pair(c2, 1, p, r, sizeof(p))); + + /* Own HRR secret: the context is out for that type, secondary too */ + ExpectIntEQ(wolfSSL_send_hrr_cookie(c2, sx, sizeof(sx)), + WOLFSSL_SUCCESS); + ExpectTrue(test_dtls_cs_holds(&c2->buffers.tls13CookieSecret, sx, + sizeof(sx))); + ExpectNull(c2->buffers.tls13CookieSecretSecondary.buffer); + ExpectTrue(test_dtls_cs_resolves(c2, CTX_COOKIE_HRR, sx, sizeof(sx), + NULL, 0)); + ExpectTrue(test_dtls_cs_pair(c2, 0, p, r, sizeof(p))); + ExpectIntEQ(test_dtls_cs_to_ch2(ctx_c, c1, &t, &ssl_c, 1), + TEST_SUCCESS); + ExpectIntEQ(test_dtls_cs_ch2_at(ssl_c, c2, &t, 1, 0), TEST_SUCCESS); + wolfSSL_free(ssl_c); + ssl_c = NULL; + + /* The DTLS 1.2 secret still follows the context */ + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx_s, p2, sizeof(p2)), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls_cs_to_ch2(ctx_c, c2, &t, &ssl_c, 1), + TEST_SUCCESS); + ExpectTrue(test_dtls_cs_resolves(c2, CTX_COOKIE_HRR, sx, sizeof(sx), + NULL, 0)); + ExpectTrue(test_dtls_cs_pair(c2, 0, p2, p, sizeof(p))); + wolfSSL_free(ssl_c); + ssl_c = NULL; + + /* NULL follows the context again */ + ExpectIntEQ(wolfSSL_send_hrr_cookie(c2, NULL, 0), WOLFSSL_SUCCESS); + ExpectNull(c2->buffers.tls13CookieSecret.buffer); + ExpectTrue(test_dtls_cs_pair(c2, 1, p2, p, sizeof(p))); + ExpectIntEQ(test_dtls_cs_to_ch2(ctx_c, c1, &t, &ssl_c, 1), + TEST_SUCCESS); + ExpectIntEQ(test_dtls_cs_ch2_at(ssl_c, c2, &t, 1, 1), TEST_SUCCESS); + wolfSSL_free(ssl_c); + ssl_c = NULL; + + /* An own HRR secondary goes with the context's primary */ + ExpectNotNull(c4 = wolfSSL_new(ctx_s)); + ExpectIntEQ(wolfSSL_set_hrr_cookie_secret_secondary(c4, q, sizeof(q)), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx_s, p3, + sizeof(p3)), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls_cs_to_ch2(ctx_c, c4, &t, &ssl_c, 1), + TEST_SUCCESS); + ExpectTrue(test_dtls_cs_holds(&c4->buffers.tls13CookieSecretSecondary, + q, sizeof(q))); + if (EXPECT_SUCCESS()) { + byte kp3[CTX_COOKIE_KEY_SZ]; + + ExpectIntEQ(test_dtls_ctx_cookie_key(p3, sizeof(p3), + CTX_COOKIE_HRR, kp3), 0); + ExpectTrue(test_dtls_cs_resolves(c4, CTX_COOKIE_HRR, kp3, + sizeof(kp3), q, sizeof(q))); + } + ExpectTrue(test_dtls_cs_pair(c4, 0, p3, p2, sizeof(p3))); + + wolfSSL_free(ssl_c); + wolfSSL_free(c1); + wolfSSL_free(c2); + wolfSSL_free(c4); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + } +#endif +#if defined(TEST_DTLS_CS) && !defined(WOLFSSL_NO_TLS12) + { + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *c1 = NULL, *c3 = NULL; + struct test_memio_ctx t; + byte p0[32]; + byte p[32]; + byte s12[16]; + + XMEMSET(&t, 0, sizeof(t)); + XMEMSET(p0, 0x10, sizeof(p0)); + XMEMSET(p, 0x11, sizeof(p)); + XMEMSET(s12, 0x77, sizeof(s12)); + ExpectIntEQ(test_memio_setup(&t, &ctx_c, &ctx_s, NULL, NULL, + wolfDTLSv1_2_client_method, wolfDTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s, p0, sizeof(p0)), + WOLFSSL_SUCCESS); + ExpectNotNull(c1 = wolfSSL_new(ctx_s)); + /* X gets a cookie under P0, then P0 becomes the secondary */ + ExpectIntEQ(test_dtls_cs_to_ch2(ctx_c, c1, &t, &ssl_c, 0), + TEST_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx_s, p, sizeof(p)), + WOLFSSL_SUCCESS); + ExpectNotNull(c3 = wolfSSL_new(ctx_s)); + ExpectTrue(test_dtls_cs_pair(c3, 0, p, p0, sizeof(p))); + + /* An own secret rules the context's secondary out too, so X's cookie + * gets a new HelloVerifyRequest */ + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecret(c3, s12, sizeof(s12)), 0); + ExpectTrue(test_dtls_cs_holds(&c3->buffers.dtlsCookieSecret, s12, + sizeof(s12))); + ExpectNull(c3->buffers.dtlsCookieSecretSecondary.buffer); + ExpectTrue(test_dtls_cs_resolves(c3, CTX_COOKIE_DTLS12, s12, + sizeof(s12), NULL, 0)); + ExpectIntEQ(test_dtls_cs_ch2_at(ssl_c, c3, &t, 0, 0), TEST_SUCCESS); + wolfSSL_free(ssl_c); + ssl_c = NULL; + + /* Nor does a cookie under the context's primary verify */ + ExpectIntEQ(test_dtls_cs_to_ch2(ctx_c, c1, &t, &ssl_c, 0), + TEST_SUCCESS); + ExpectTrue(test_dtls_cs_pair(c1, 0, p, p0, sizeof(p))); + ExpectIntEQ(test_dtls_cs_ch2_at(ssl_c, c3, &t, 0, 0), TEST_SUCCESS); + wolfSSL_free(ssl_c); + ssl_c = NULL; + + /* NULL follows the context again, whatever the size */ + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecret(c3, NULL, 7), 0); + ExpectNull(c3->buffers.dtlsCookieSecret.buffer); + ExpectTrue(test_dtls_cs_pair(c3, 0, p, p0, sizeof(p))); + ExpectIntEQ(test_dtls_cs_to_ch2(ctx_c, c1, &t, &ssl_c, 0), + TEST_SUCCESS); + ExpectIntEQ(test_dtls_cs_ch2_at(ssl_c, c3, &t, 0, 1), TEST_SUCCESS); + + wolfSSL_free(ssl_c); + wolfSSL_free(c1); + wolfSSL_free(c3); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + } +#endif + return EXPECT_RESULT(); +} + +#ifdef TEST_DTLS_CS +static int test_dtls_cs_disable_enable_ex(const test_dtls_cs_ver* v, + int useHrrApi) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *c1 = NULL, *c2 = NULL; + struct test_memio_ctx t; + byte p[32]; + byte q[32]; + byte kp[CTX_COOKIE_KEY_SZ]; +#if defined(HAVE_ECC) && !defined(NO_ECC256) + int group = WOLFSSL_ECC_SECP256R1; +#endif + + XMEMSET(&t, 0, sizeof(t)); + XMEMSET(p, 0x11, sizeof(p)); + XMEMSET(q, 0x22, sizeof(q)); + ExpectIntEQ(test_memio_setup(&t, &ctx_c, &ctx_s, NULL, NULL, v->client, + v->server), 0); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s, p, sizeof(p)), + WOLFSSL_SUCCESS); + ExpectNotNull(c1 = wolfSSL_new(ctx_s)); + ExpectNotNull(c2 = wolfSSL_new(ctx_s)); + + /* Give c2 secondaries of its own */ + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecretSecondary(c2, q, sizeof(q)), 0); +#if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) + if (v->dtls13) { + ExpectIntEQ(wolfSSL_set_hrr_cookie_secret_secondary(c2, q, sizeof(q)), + WOLFSSL_SUCCESS); + } +#endif + +#if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) + if (useHrrApi) + ExpectIntEQ(wolfSSL_disable_hrr_cookie(c2), WOLFSSL_SUCCESS); + else +#endif + ExpectIntEQ(wolfSSL_disable_cookie(c2), WOLFSSL_SUCCESS); + (void)useHrrApi; + /* Only the object's own secrets go; the context keeps its secret */ + ExpectNull(c2->buffers.dtlsCookieSecret.buffer); + ExpectNull(c2->buffers.dtlsCookieSecretSecondary.buffer); + ExpectIntEQ(c2->options.sendCookie, 0); + ExpectTrue(test_dtls_cs_ctx_holds(ctx_s, p, sizeof(p), NULL, 0)); + ExpectTrue(test_dtls_cs_pair(c2, v->dtls13, p, NULL, sizeof(p))); + + /* c1 still verifies cookies with it */ + XMEMSET(&t, 0, sizeof(t)); + ExpectNotNull(ssl_c = wolfSSL_new(ctx_c)); +#if defined(HAVE_ECC) && !defined(NO_ECC256) + if (v->dtls13) + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); +#endif + wolfSSL_SetIOReadCtx(ssl_c, &t); + wolfSSL_SetIOWriteCtx(ssl_c, &t); + wolfSSL_SetIOReadCtx(c1, &t); + wolfSSL_SetIOWriteCtx(c1, &t); + ExpectIntEQ(test_memio_do_handshake(ssl_c, c1, 10, NULL), 0); + + /* Enabling again uses the context's secret, with no RNG of the object's */ + ExpectIntEQ(wolfSSL_enable_cookie(c2), WOLFSSL_SUCCESS); + ExpectIntEQ(c2->options.sendCookie, 1); + ExpectNull(c2->buffers.dtlsCookieSecret.buffer); + ExpectTrue(test_dtls_cs_pair(c2, v->dtls13, p, NULL, sizeof(p))); + + /* Secondaries of its own set while cookies are off go with the context's + * primary once they are on */ +#if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) + if (useHrrApi) + ExpectIntEQ(wolfSSL_disable_hrr_cookie(c2), WOLFSSL_SUCCESS); + else +#endif + ExpectIntEQ(wolfSSL_disable_cookie(c2), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecretSecondary(c2, q, sizeof(q)), 0); +#if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) + if (v->dtls13) { + ExpectIntEQ(wolfSSL_set_hrr_cookie_secret_secondary(c2, q, sizeof(q)), + WOLFSSL_SUCCESS); + } +#endif + ExpectIntEQ(wolfSSL_enable_cookie(c2), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls_ctx_cookie_key(p, sizeof(p), CTX_COOKIE_DTLS12, kp), + 0); + ExpectTrue(test_dtls_cs_resolves(c2, CTX_COOKIE_DTLS12, kp, sizeof(kp), + q, sizeof(q))); +#if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) + if (v->dtls13) { + ExpectIntEQ(test_dtls_ctx_cookie_key(p, sizeof(p), CTX_COOKIE_HRR, + kp), 0); + ExpectTrue(test_dtls_cs_resolves(c2, CTX_COOKIE_HRR, kp, sizeof(kp), + q, sizeof(q))); + } +#endif + + wolfSSL_free(ssl_c); + wolfSSL_free(c1); + wolfSSL_free(c2); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +/* Turning an object's cookies off erases only its own secrets. The context + * keeps its secret and turning cookies on again uses it. */ +int test_dtls_ctx_cookie_secret_disable_enable(void) +{ + EXPECT_DECLS; +#ifdef TEST_DTLS_CS + int i; + + for (i = 0; test_dtls_cs_vers[i].client != NULL && EXPECT_SUCCESS(); i++) { + ExpectIntEQ(test_dtls_cs_disable_enable_ex(&test_dtls_cs_vers[i], 0), + TEST_SUCCESS); + if (test_dtls_cs_vers[i].dtls13) { + ExpectIntEQ(test_dtls_cs_disable_enable_ex(&test_dtls_cs_vers[i], + 1), TEST_SUCCESS); + } + } +#endif + return EXPECT_RESULT(); +} + +#ifdef TEST_DTLS_CS +/* An object created before the application sets the context secret issued + * cookies under the context's generated default. rotate: it is the secondary + * and they verify. set: it is gone and they do not. */ +static int test_dtls_cs_created_before_ex(const test_dtls_cs_ver* v, int rotate) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *cOld = NULL, *cNew = NULL; + struct test_memio_ctx t; + byte p[32]; + + XMEMSET(&t, 0, sizeof(t)); + XMEMSET(p, 0x11, sizeof(p)); + ExpectIntEQ(test_memio_setup(&t, &ctx_c, &ctx_s, NULL, NULL, v->client, + v->server), 0); + ExpectNotNull(cOld = wolfSSL_new(ctx_s)); + ExpectIntEQ(ctx_s->cookieSecret.keySz[0], CTX_COOKIE_KEY_SZ); + ExpectIntEQ(ctx_s->cookieSecret.keySz[1], 0); + ExpectIntEQ(test_dtls_cs_to_ch2(ctx_c, cOld, &t, &ssl_c, v->dtls13), + TEST_SUCCESS); + + if (rotate) { + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx_s, p, sizeof(p)), + WOLFSSL_SUCCESS); + ExpectIntEQ(ctx_s->cookieSecret.keySz[1], CTX_COOKIE_KEY_SZ); + } + else { + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s, p, sizeof(p)), + WOLFSSL_SUCCESS); + ExpectIntEQ(ctx_s->cookieSecret.keySz[1], 0); + } + ExpectNotNull(cNew = wolfSSL_new(ctx_s)); + ExpectIntEQ(test_dtls_cs_ch2_at(ssl_c, cNew, &t, v->dtls13, rotate), + TEST_SUCCESS); + + wolfSSL_free(ssl_c); + wolfSSL_free(cOld); + wolfSSL_free(cNew); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +int test_dtls_ctx_cookie_secret_created_before(void) +{ + EXPECT_DECLS; +#ifdef TEST_DTLS_CS + int i; + + for (i = 0; test_dtls_cs_vers[i].client != NULL && EXPECT_SUCCESS(); i++) { + ExpectIntEQ(test_dtls_cs_created_before_ex(&test_dtls_cs_vers[i], 1), + TEST_SUCCESS); + ExpectIntEQ(test_dtls_cs_created_before_ex(&test_dtls_cs_vers[i], 0), + TEST_SUCCESS); + } +#endif + return EXPECT_RESULT(); +} + +/* An object that has not chosen a side holds no cookie secret. As a server it + * uses the context's; a client gets none of its own. */ +int test_dtls_ctx_cookie_secret_neither_end(void) +{ + EXPECT_DECLS; +#ifdef TEST_DTLS_CS_SIDE + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + WOLFSSL* ssl2 = NULL; + WOLFSSL* ssl3 = NULL; + byte p[32]; + byte q[32]; + byte kp[CTX_COOKIE_KEY_SZ]; + int hrr = 0; + +#if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) + hrr = 1; +#endif + XMEMSET(p, 0x11, sizeof(p)); + XMEMSET(q, 0x22, sizeof(q)); + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLS_method())); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx, p, sizeof(p)), + WOLFSSL_SUCCESS); + + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(ssl->options.side, WOLFSSL_NEITHER_END); + ExpectNull(ssl->buffers.dtlsCookieSecret.buffer); + wolfSSL_set_accept_state(ssl); + ExpectIntEQ(ssl->options.side, WOLFSSL_SERVER_END); + ExpectNull(ssl->buffers.dtlsCookieSecret.buffer); + ExpectTrue(test_dtls_cs_pair(ssl, hrr, p, NULL, sizeof(p))); + +#if defined(OPENSSL_EXTRA) || defined(WOLFSSL_WPAS_SMALL) + ExpectNotNull(ssl2 = wolfSSL_new(ctx)); + wolfSSL_set_connect_state(ssl2); + ExpectIntEQ(ssl2->options.side, WOLFSSL_CLIENT_END); + ExpectNull(ssl2->buffers.dtlsCookieSecret.buffer); +#if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) + ExpectNull(ssl2->buffers.tls13CookieSecret.buffer); +#endif +#endif + + /* Only a secondary of its own before it becomes a server: the primary is + * the context's */ + ExpectNotNull(ssl3 = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecretSecondary(ssl3, q, sizeof(q)), 0); +#if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) + ExpectIntEQ(wolfSSL_set_hrr_cookie_secret_secondary(ssl3, q, sizeof(q)), + WOLFSSL_SUCCESS); +#endif + wolfSSL_set_accept_state(ssl3); + ExpectIntEQ(ssl3->options.side, WOLFSSL_SERVER_END); + ExpectIntEQ(test_dtls_ctx_cookie_key(p, sizeof(p), CTX_COOKIE_DTLS12, kp), + 0); + ExpectTrue(test_dtls_cs_resolves(ssl3, CTX_COOKIE_DTLS12, kp, sizeof(kp), + q, sizeof(q))); +#if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) + ExpectIntEQ(test_dtls_ctx_cookie_key(p, sizeof(p), CTX_COOKIE_HRR, kp), + 0); + ExpectTrue(test_dtls_cs_resolves(ssl3, CTX_COOKIE_HRR, kp, sizeof(kp), + q, sizeof(q))); +#endif + + wolfSSL_free(ssl); + wolfSSL_free(ssl2); + wolfSSL_free(ssl3); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* Using the context's secret needs no RNG of the object's */ +int test_dtls_ctx_cookie_secret_no_rng(void) +{ + EXPECT_DECLS; +#ifdef TEST_DTLS_CS + int i; + + for (i = 0; test_dtls_cs_vers[i].client != NULL && EXPECT_SUCCESS(); i++) { + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx t; + WC_RNG* savedRng = NULL; + byte p[32]; + byte q[32]; + byte kp[CTX_COOKIE_KEY_SZ]; + + XMEMSET(&t, 0, sizeof(t)); + XMEMSET(p, 0x11, sizeof(p)); + XMEMSET(q, 0x22, sizeof(q)); + ExpectIntEQ(test_memio_setup(&t, NULL, &ctx_s, NULL, &ssl_s, NULL, + test_dtls_cs_vers[i].server), 0); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s, p, sizeof(p)), + WOLFSSL_SUCCESS); + if (EXPECT_SUCCESS()) { + savedRng = ssl_s->rng; + ssl_s->rng = NULL; + } + ExpectIntEQ(wolfSSL_disable_cookie(ssl_s), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_enable_cookie(ssl_s), WOLFSSL_SUCCESS); + ExpectTrue(test_dtls_cs_pair(ssl_s, test_dtls_cs_vers[i].dtls13, p, + NULL, sizeof(p))); + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecret(ssl_s, NULL, 0), 0); + ExpectTrue(test_dtls_cs_pair(ssl_s, test_dtls_cs_vers[i].dtls13, p, + NULL, sizeof(p))); + /* Nor when the object has only secondaries of its own */ + ExpectIntEQ(wolfSSL_disable_cookie(ssl_s), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_DTLS_SetCookieSecretSecondary(ssl_s, q, + sizeof(q)), 0); + #if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) + if (test_dtls_cs_vers[i].dtls13) { + ExpectIntEQ(wolfSSL_set_hrr_cookie_secret_secondary(ssl_s, q, + sizeof(q)), WOLFSSL_SUCCESS); + } + #endif + ExpectIntEQ(wolfSSL_enable_cookie(ssl_s), WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls_ctx_cookie_key(p, sizeof(p), CTX_COOKIE_DTLS12, + kp), 0); + ExpectTrue(test_dtls_cs_resolves(ssl_s, CTX_COOKIE_DTLS12, kp, + sizeof(kp), q, sizeof(q))); + #if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) + if (test_dtls_cs_vers[i].dtls13) { + ExpectIntEQ(test_dtls_ctx_cookie_key(p, sizeof(p), CTX_COOKIE_HRR, + kp), 0); + ExpectTrue(test_dtls_cs_resolves(ssl_s, CTX_COOKIE_HRR, kp, + sizeof(kp), q, sizeof(q))); + } + #endif + if (ssl_s != NULL && savedRng != NULL) + ssl_s->rng = savedRng; + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); + } +#endif + return EXPECT_RESULT(); +} + +/* A server whose context lost its secret has no cookie: the ClientHello that + * needs one fails with COOKIE_ERROR, in DTLS 1.2 and 1.3 */ +int test_dtls_ctx_cookie_secret_none(void) +{ + EXPECT_DECLS; +#ifdef TEST_DTLS_CS + int i; + + for (i = 0; test_dtls_cs_vers[i].client != NULL && EXPECT_SUCCESS(); i++) { + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx t; +#if defined(HAVE_ECC) && !defined(NO_ECC256) + int group = WOLFSSL_ECC_SECP256R1; +#endif + + XMEMSET(&t, 0, sizeof(t)); + ExpectIntEQ(test_memio_setup(&t, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + test_dtls_cs_vers[i].client, test_dtls_cs_vers[i].server), 0); +#if defined(HAVE_ECC) && !defined(NO_ECC256) + if (test_dtls_cs_vers[i].dtls13) + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); +#endif + ExpectIntEQ(ssl_s->options.sendCookie, 1); + ExpectIntEQ(ctx_s->cookieSecret.keySz[0], CTX_COOKIE_KEY_SZ); + test_dtls_cs_ctx_erase(ctx_s); + ExpectIntEQ(wolfSSL_negotiate(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_negotiate(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), + WC_NO_ERR_TRACE(COOKIE_ERROR)); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + /* No HelloVerifyRequest or HelloRetryRequest, at most an alert */ + ExpectTrue(t.c_len == 0 || t.c_buff[0] == alert); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + } +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_dtls.h b/tests/api/test_dtls.h index 8e538c7f1a4..b2d89fb479a 100644 --- a/tests/api/test_dtls.h +++ b/tests/api/test_dtls.h @@ -155,6 +155,7 @@ int test_dtls12_cid_not_negotiated(void); int test_dtls_sctp_app_data_size(void); int test_dtls12_missing_finished(void); int test_dtls12_cookie_secret_secondary(void); +int test_dtls12_cookie_secret_secondary_null_primary(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); @@ -162,6 +163,7 @@ 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_null_primary(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); @@ -182,6 +184,17 @@ int test_dtls12_cookie_secret_generate_fail(void); int test_dtls13_hrr_cookie_secret_generate_fail(void); int test_dtls_max_fragment_write(void); int test_dtls_set_rfd_wfd(void); +int test_dtls_ctx_cookie_secret_api(void); +int test_dtls_ctx_cookie_secret_devid(void); +int test_dtls12_ctx_cookie_secret_handoff(void); +int test_dtls13_ctx_cookie_secret_dtls12_fallback(void); +int test_dtls_ctx_cookie_secret_rotate(void); +int test_dtls_ctx_cookie_secret_override(void); +int test_dtls_ctx_cookie_secret_disable_enable(void); +int test_dtls_ctx_cookie_secret_created_before(void); +int test_dtls_ctx_cookie_secret_neither_end(void); +int test_dtls_ctx_cookie_secret_no_rng(void); +int test_dtls_ctx_cookie_secret_none(void); int test_wolfSSL_dtls_export(void); int test_wolfSSL_dtls_import_dh_key_sz(void); int test_wolfSSL_dtls_export_peers(void); @@ -216,6 +229,15 @@ extern const unsigned char test_dtls13_four_frag_ch[TEST_DTLS13_FOUR_FRAG_CH_SZ]; int test_dtls_no_cookie_ch_good(WOLFSSL* ssl, void* ctx); int test_dtls_no_cookie_ch_pause(WOLFSSL* ssl, void* ctx); +#if (defined(WOLFSSL_DTLS) || defined(WOLFSSL_SEND_HRR_COOKIE)) && \ + !defined(NO_WOLFSSL_SERVER) +int test_dtls_ctx_cookie_key(const unsigned char* secret, + unsigned int secretSz, int type, unsigned char* key); +int test_dtls_cs_resolves(const WOLFSSL* s, int type, const byte* pri, + word32 priSz, const byte* sec, word32 secSz); +int test_dtls_cs_uses_ctx(const WOLFSSL* s, int type, const byte* p, + word32 pSz, const byte* q, word32 qSz); +#endif #define TEST_DTLS_DECLS \ TEST_DECL_GROUP("dtls", test_dtls12_basic_connection_id), \ @@ -382,6 +404,8 @@ int test_dtls_no_cookie_ch_pause(WOLFSSL* ssl, void* ctx); TEST_DECL_GROUP("dtls", test_wolfSSL_set_secret), \ TEST_DECL_GROUP("dtls", test_wolfSSL_mcast_ccs_peer_entry), \ TEST_DECL_GROUP("dtls", test_dtls12_cookie_secret_secondary), \ + TEST_DECL_GROUP("dtls", \ + test_dtls12_cookie_secret_secondary_null_primary), \ 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), \ @@ -397,6 +421,8 @@ int test_dtls_no_cookie_ch_pause(WOLFSSL* ssl, void* ctx); TEST_DECL_GROUP("dtls", test_dtls_cookie_policy_neither_end), \ TEST_DECL_GROUP("dtls", test_dtls13_hrr_cookie_state_guard), \ TEST_DECL_GROUP("dtls", test_dtls13_hrr_cookie_secret_secondary), \ + TEST_DECL_GROUP("dtls", \ + test_dtls13_hrr_cookie_secret_secondary_null_primary), \ 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), \ @@ -408,5 +434,16 @@ int test_dtls_no_cookie_ch_pause(WOLFSSL* ssl, void* ctx); TEST_DECL_GROUP("dtls", test_dtls12_cookie_secret_generate_fail), \ TEST_DECL_GROUP("dtls", test_dtls13_hrr_cookie_secret_generate_fail), \ TEST_DECL_GROUP("dtls", test_dtls_max_fragment_write), \ - TEST_DECL_GROUP("dtls", test_dtls_set_rfd_wfd) + TEST_DECL_GROUP("dtls", test_dtls_set_rfd_wfd), \ + TEST_DECL_GROUP("dtls", test_dtls_ctx_cookie_secret_api), \ + TEST_DECL_GROUP("dtls", test_dtls_ctx_cookie_secret_devid), \ + TEST_DECL_GROUP("dtls", test_dtls12_ctx_cookie_secret_handoff), \ + TEST_DECL_GROUP("dtls", test_dtls13_ctx_cookie_secret_dtls12_fallback), \ + TEST_DECL_GROUP("dtls", test_dtls_ctx_cookie_secret_rotate), \ + TEST_DECL_GROUP("dtls", test_dtls_ctx_cookie_secret_override), \ + TEST_DECL_GROUP("dtls", test_dtls_ctx_cookie_secret_disable_enable), \ + TEST_DECL_GROUP("dtls", test_dtls_ctx_cookie_secret_created_before), \ + TEST_DECL_GROUP("dtls", test_dtls_ctx_cookie_secret_neither_end), \ + TEST_DECL_GROUP("dtls", test_dtls_ctx_cookie_secret_no_rng), \ + TEST_DECL_GROUP("dtls", test_dtls_ctx_cookie_secret_none) #endif /* TESTS_API_DTLS_H */ diff --git a/tests/api/test_dtls13.c b/tests/api/test_dtls13.c index bc89fe4728c..a5b01145ea0 100644 --- a/tests/api/test_dtls13.c +++ b/tests/api/test_dtls13.c @@ -304,10 +304,18 @@ int test_dtls13_frag_ch_pq_no_cookie(void) return EXPECT_RESULT(); } +/* The context cookie secret tests */ #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_SEND_HRR_COOKIE) && defined(HAVE_ECC) && \ + !defined(NO_ECC256) + #define TEST_DTLS13_CTX_COOKIE +#endif + +#if (defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ && ((defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE)) \ || (defined(HAVE_SESSION_TICKET) && \ - defined(WOLFSSL_DTLS13_ECHO_LEGACY_SESSION_ID) && defined(HAVE_ECC))) + defined(WOLFSSL_DTLS13_ECHO_LEGACY_SESSION_ID) && defined(HAVE_ECC)))) \ + || defined(TEST_DTLS13_CTX_COOKIE) /* RFC 8446 Section 4.1.3: an HRR is a ServerHello carrying this magic random. * Used to assert a real ServerHello was captured, not an HRR. */ static const byte hrrRandom[RAN_LEN] = { @@ -318,8 +326,9 @@ static const byte hrrRandom[RAN_LEN] = { }; #endif -#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ - && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) +#if (defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE)) || \ + defined(TEST_DTLS13_CTX_COOKIE) /* Whether the first record the server has queued for the client is a plaintext * handshake record holding a ServerHello. When it is, *isHrr reports whether * its random is the HelloRetryRequest value. */ @@ -597,9 +606,9 @@ static int test_dtls13_nothing_held(WOLFSSL* s) #endif -#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ +#if (defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ - defined(HAVE_ECC) && !defined(NO_ECC256) + defined(HAVE_ECC) && !defined(NO_ECC256)) || defined(TEST_DTLS13_CTX_COOKIE) /* Point the server at the buffers and the peer identity of the next datagrams. * Memio has no address, so wolfSSL_dtls_set_peer() supplies one. */ static int test_dtls13_server_peer(WOLFSSL* ssl_s, @@ -3285,13 +3294,107 @@ int test_dtls13_ch_async_no_addr(void) return EXPECT_RESULT(); } +#ifdef TEST_DTLS13_CTX_COOKIE +/* Whether a and b make the HelloRetryRequest cookie with the same secret */ +static int test_dtls13_cs_same(const WOLFSSL* a, const WOLFSSL* b) +{ + CookieSecrets ca; + CookieSecrets cb; + int same = 0; + + if (a == NULL || b == NULL) + return 0; + if (CookieSecretGet(a, CTX_COOKIE_HRR, &ca) != 0) + return 0; + if (CookieSecretGet(b, CTX_COOKIE_HRR, &cb) == 0) { + same = ca.priSz == cb.priSz && XMEMCMP(ca.pri, cb.pri, ca.priSz) == 0; + CookieSecretsClear(&cb); + } + CookieSecretsClear(&ca); + return same; +} + +/* A client gets a HRR from listener c1 of ctx_s and sends its second + * ClientHello to new listener c2. mode 0: the context's generated secret. 1: a + * secret set on it once c1 exists. 2: c2 has a secret of its own and sends + * illegal_parameter. */ +static int test_dtls13_cs_handoff(WOLFSSL_CTX* ctx_c, WOLFSSL_CTX* ctx_s, + int mode) +{ + EXPECT_DECLS; + WOLFSSL *ssl_c = NULL, *c1 = NULL, *c2 = NULL; + struct test_memio_ctx t; + WOLFSSL_ALERT_HISTORY h; + int group = WOLFSSL_ECC_SECP256R1; + int isHrr = 0; + int ok = mode != 2; + byte own[32]; + + XMEMSET(&t, 0, sizeof(t)); + XMEMSET(&h, 0, sizeof(h)); + XMEMSET(own, 0x77, sizeof(own)); + ExpectNotNull(ssl_c = wolfSSL_new(ctx_c)); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + wolfSSL_SetIOReadCtx(ssl_c, &t); + wolfSSL_SetIOWriteCtx(ssl_c, &t); + ExpectNotNull(c1 = wolfSSL_new(ctx_s)); + if (mode == 1) { + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s, NULL, 0), + WOLFSSL_SUCCESS); + } + ExpectIntEQ(test_dtls13_server_peer(c1, &t, 2), WOLFSSL_SUCCESS); + + /* The first ClientHello gets a HelloRetryRequest from c1 */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(c1), -1); + ExpectIntEQ(wolfSSL_get_error(c1, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&t, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + + /* The second goes to a new listener */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectNotNull(c2 = wolfSSL_new(ctx_s)); + ExpectIntEQ(test_dtls13_server_peer(c2, &t, 2), WOLFSSL_SUCCESS); + if (mode == 2) { + ExpectIntEQ(wolfSSL_send_hrr_cookie(c2, own, sizeof(own)), + WOLFSSL_SUCCESS); + } + if (ok) { + ExpectTrue(test_dtls13_cs_same(c1, c2)); + ExpectIntEQ(test_memio_do_handshake(ssl_c, c2, 10, NULL), 0); + ExpectIntEQ(c1->options.dtlsStateful, 0); + } + else { + ExpectFalse(test_dtls13_cs_same(c1, c2)); + ExpectIntNE(test_memio_do_handshake(ssl_c, c2, 10, NULL), 0); + ExpectIntEQ(wolfSSL_get_alert_history(ssl_c, &h), WOLFSSL_SUCCESS); + ExpectIntEQ(h.last_rx.code, illegal_parameter); + ExpectIntEQ(h.last_rx.level, alert_fatal); + } + + wolfSSL_free(c1); + wolfSSL_free(c2); + wolfSSL_free(ssl_c); + return EXPECT_RESULT(); +} +#endif + #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ defined(HAVE_ECC) && !defined(NO_ECC256) +#define TEST_CS_DEFAULT 0 /* the secret the context generates */ +#define TEST_CS_OBJ 1 /* the same secret set on each listener */ +#define TEST_CS_CTX 2 /* a secret set on the context */ +#define TEST_CS_CTX_RANDOM 3 /* a random secret made by the context */ +#define TEST_CS_CTX_OVERRIDE 4 /* as CTX, but c2 has a secret of its own */ +#define TEST_CS_OBJ_ONE 5 /* only c1 has a secret of its own */ + /* B's second ClientHello split after its cookie: the later fragments reach - * c1 while it serves A, the first fragment a new listener c2. sharedSecret - * gives c2 c1's cookie secret; mode 1 turns holding before the cookie off. */ -static int test_dtls13_frag_ch2_ctx_second(int sharedSecret, int mode) + * c1 while it serves A, the first fragment a new listener c2. secretMode says + * how they get their secret; mode 1 turns holding before the cookie off. */ +static int test_dtls13_frag_ch2_ctx_second(int secretMode, int mode) { EXPECT_DECLS; WOLFSSL_CTX *ctx_cA = NULL, *ctx_cB = NULL, *ctx_cD = NULL, *ctx_s = NULL; @@ -3299,12 +3402,15 @@ static int test_dtls13_frag_ch2_ctx_second(int sharedSecret, int mode) WOLFSSL *c1 = NULL, *c2 = NULL; struct test_memio_ctx tA, tB, tD; int group = WOLFSSL_ECC_SECP256R1; - static const byte secret[32] = { - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, - 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, - 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, - 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20 - }; + byte secret[32]; + byte other[32]; + byte hrrKey[CTX_COOKIE_KEY_SZ]; + byte dtlsKey[CTX_COOKIE_KEY_SZ]; + int ctxSecret = secretMode == TEST_CS_CTX || + secretMode == TEST_CS_CTX_OVERRIDE; + int sharedSecret = secretMode == TEST_CS_DEFAULT || + secretMode == TEST_CS_OBJ || secretMode == TEST_CS_CTX || + secretMode == TEST_CS_CTX_RANDOM; byte chB[TEST_MEMIO_BUF_SZ], chD[TEST_MEMIO_BUF_SZ]; int chBSz = (int)sizeof(chB), chDSz = (int)sizeof(chD); byte frag[TEST_MEMIO_BUF_SZ]; @@ -3319,14 +3425,38 @@ static int test_dtls13_frag_ch2_ctx_second(int sharedSecret, int mode) XMEMSET(&tA, 0, sizeof(tA)); XMEMSET(&tB, 0, sizeof(tB)); XMEMSET(&tD, 0, sizeof(tD)); + for (i = 0; i < (int)sizeof(secret); i++) + secret[i] = (byte)(i + 1); + XMEMSET(other, 0x77, sizeof(other)); + ExpectIntEQ(test_dtls_ctx_cookie_key(secret, sizeof(secret), + CTX_COOKIE_HRR, hrrKey), 0); + ExpectIntEQ(test_dtls_ctx_cookie_key(secret, sizeof(secret), + CTX_COOKIE_DTLS12, dtlsKey), 0); ExpectIntEQ(test_memio_setup(&tA, &ctx_cA, &ctx_s, &ssl_cA, &c1, wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); ExpectIntEQ(test_memio_setup(&tB, &ctx_cB, NULL, &ssl_cB, NULL, wolfDTLSv1_3_client_method, NULL), 0); ExpectIntEQ(wolfSSL_set_groups(ssl_cA, &group, 1), WOLFSSL_SUCCESS); ExpectIntEQ(wolfSSL_set_groups(ssl_cB, &group, 1), WOLFSSL_SUCCESS); - ExpectIntEQ(wolfSSL_send_hrr_cookie(c1, secret, sizeof(secret)), - WOLFSSL_SUCCESS); + if (secretMode == TEST_CS_OBJ) { + ExpectIntEQ(wolfSSL_send_hrr_cookie(c1, secret, sizeof(secret)), + WOLFSSL_SUCCESS); + } + else if (secretMode == TEST_CS_OBJ_ONE) { + ExpectIntEQ(wolfSSL_send_hrr_cookie(c1, other, sizeof(other)), + WOLFSSL_SUCCESS); + } + else if (ctxSecret) { + /* c1 already exists: it uses the secret from now on */ + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s, secret, + sizeof(secret)), WOLFSSL_SUCCESS); + ExpectTrue(test_dtls_cs_resolves(c1, CTX_COOKIE_HRR, hrrKey, + sizeof(hrrKey), NULL, 0)); + } + else if (secretMode == TEST_CS_CTX_RANDOM) { + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s, NULL, 0), + WOLFSSL_SUCCESS); + } /* B's first ClientHello at c1 gets a HelloRetryRequest */ ExpectIntEQ(test_dtls13_server_peer(c1, &tB, 2), WOLFSSL_SUCCESS); @@ -3336,6 +3466,10 @@ static int test_dtls13_frag_ch2_ctx_second(int sharedSecret, int mode) ExpectIntEQ(wolfSSL_get_error(c1, -1), WOLFSSL_ERROR_WANT_READ); ExpectIntEQ(test_dtls13_reply_is_server_hello(&tB, &isHrr), 1); ExpectIntEQ(isHrr, 1); + if (ctxSecret) { + ExpectTrue(test_dtls_cs_resolves(c1, CTX_COOKIE_HRR, hrrKey, + sizeof(hrrKey), NULL, 0)); + } ExpectIntEQ(test_dtls13_take_ch(&tB, ssl_cB, chB, &chBSz), TEST_SUCCESS); /* Three fragments: the first ends just past the cookie extension */ @@ -3367,10 +3501,25 @@ static int test_dtls13_frag_ch2_ctx_second(int sharedSecret, int mode) ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); ExpectNotNull(c2 = wolfSSL_new(ctx_s)); - if (sharedSecret) { + if (secretMode == TEST_CS_OBJ) { ExpectIntEQ(wolfSSL_send_hrr_cookie(c2, secret, sizeof(secret)), WOLFSSL_SUCCESS); } + else if (ctxSecret) { + /* A new listener uses the context's secret at once */ + ExpectTrue(test_dtls_cs_resolves(c2, CTX_COOKIE_HRR, hrrKey, + sizeof(hrrKey), NULL, 0)); + ExpectTrue(test_dtls_cs_resolves(c2, CTX_COOKIE_DTLS12, dtlsKey, + sizeof(dtlsKey), NULL, 0)); + } + else if (secretMode == TEST_CS_CTX_RANDOM || + secretMode == TEST_CS_DEFAULT) { + ExpectTrue(test_dtls13_cs_same(c1, c2)); + } + if (secretMode == TEST_CS_CTX_OVERRIDE) { + ExpectIntEQ(wolfSSL_send_hrr_cookie(c2, other, sizeof(other)), + WOLFSSL_SUCCESS); + } if (mode == 1) { /* An object with reassembly off adds nothing to the pool */ ExpectIntEQ(wolfSSL_dtls13_reassemble_ch_before_cookie(c2, 0), @@ -3449,15 +3598,26 @@ static int test_dtls13_frag_ch2_ctx_second(int sharedSecret, int mode) #endif /* The rest of a second ClientHello held by the context is taken by the server - * that verifies its first fragment, when the servers share the secret. */ + * that verifies its first fragment: the servers share the context's secret + * unless given their own. */ int test_dtls13_frag_ch2_ctx_handoff(void) { EXPECT_DECLS; #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ defined(HAVE_ECC) && !defined(NO_ECC256) - ExpectIntEQ(test_dtls13_frag_ch2_ctx_second(1, 0), TEST_SUCCESS); - ExpectIntEQ(test_dtls13_frag_ch2_ctx_second(1, 1), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch2_ctx_second(TEST_CS_DEFAULT, 0), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch2_ctx_second(TEST_CS_OBJ, 0), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch2_ctx_second(TEST_CS_OBJ, 1), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch2_ctx_second(TEST_CS_CTX, 0), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch2_ctx_second(TEST_CS_CTX, 1), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch2_ctx_second(TEST_CS_CTX_RANDOM, 0), + TEST_SUCCESS); #endif return EXPECT_RESULT(); } @@ -3470,7 +3630,10 @@ int test_dtls13_frag_ch2_ctx_cookie_mismatch(void) #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ defined(HAVE_ECC) && !defined(NO_ECC256) - ExpectIntEQ(test_dtls13_frag_ch2_ctx_second(0, 0), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch2_ctx_second(TEST_CS_OBJ_ONE, 0), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_frag_ch2_ctx_second(TEST_CS_CTX_OVERRIDE, 0), + TEST_SUCCESS); #endif return EXPECT_RESULT(); } @@ -3491,8 +3654,10 @@ int test_dtls13_frag_ch_ctx_set_ssl_ctx(void) byte chB[TEST_MEMIO_BUF_SZ]; int chBSz = (int)sizeof(chB); int pieceB = 0; + byte key[CTX_COOKIE_KEY_SZ]; XMEMSET(&tB, 0, sizeof(tB)); + XMEMSET(key, 0, sizeof(key)); ExpectIntEQ(test_memio_setup(&tB, &ctx_cB, &ctx_s, &ssl_cB, &c1, wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); ExpectIntEQ(test_memio_setup(&tB, NULL, &ctx_s2, NULL, NULL, @@ -3507,7 +3672,20 @@ int test_dtls13_frag_ch_ctx_set_ssl_ctx(void) ExpectIntEQ(test_dtls13_server_read_all(&tB, c1), TEST_SUCCESS); ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); + /* The object uses its context's secret: the new context's, generated by + * the move, after it */ + ExpectNotNull(c1); + ExpectNull(c1->buffers.tls13CookieSecret.buffer); + ExpectIntEQ(ctx_s->cookieSecret.keySz[0], CTX_COOKIE_KEY_SZ); + ExpectIntEQ(ctx_s2->cookieSecret.keySz[0], 0); + if (EXPECT_SUCCESS()) + XMEMCPY(key, ctx_s->cookieSecret.hrrKey[0], sizeof(key)); + ExpectTrue(test_dtls_cs_resolves(c1, CTX_COOKIE_HRR, key, sizeof(key), + NULL, 0)); ExpectPtrEq(wolfSSL_set_SSL_CTX(c1, ctx_s2), ctx_s2); + ExpectIntEQ(ctx_s2->cookieSecret.keySz[0], CTX_COOKIE_KEY_SZ); + ExpectFalse(test_dtls_cs_resolves(c1, CTX_COOKIE_HRR, key, sizeof(key), + NULL, 0)); ExpectIntEQ(test_dtls13_inject_frag(&tB, chB, chBSz, pieceB, 1), 1); ExpectIntEQ(test_dtls13_server_read_all(&tB, c1), TEST_SUCCESS); ExpectIntEQ(tB.c_len, 0); @@ -3701,6 +3879,16 @@ static int test_dtls13_frag_ch_ctx_static_memory_ex(int lateLoad, byte* mem, wolfSSL_free(c1); c1 = NULL; ExpectIntEQ(wolfSSL_CTX_is_static_memory(ctx_s, &base), 1); + /* The context secret is stored inline: setting it allocates nothing + * that outlives the call */ + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s, NULL, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx_s, NULL, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret_secondary(ctx_s, NULL, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_is_static_memory(ctx_s, &now), 1); + ExpectIntEQ(now.curAlloc, base.curAlloc); ExpectIntEQ(test_memio_setup(&tB, &ctx_cB, &ctx_s, &ssl_cB, &c1, wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); @@ -3743,6 +3931,11 @@ static int test_dtls13_frag_ch_ctx_static_memory_ex(int lateLoad, byte* mem, ExpectIntEQ(wolfSSL_CTX_is_static_memory(ctx_s, &now), 1); ExpectIntEQ(now.curAlloc, base.curAlloc); + /* Objects that copy the context secret free their copies */ + ExpectIntEQ(test_dtls13_cs_handoff(ctx_cB, ctx_s, 0), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_is_static_memory(ctx_s, &now), 1); + ExpectIntEQ(now.curAlloc, base.curAlloc); + wolfSSL_free(ssl_cB); wolfSSL_CTX_free(ctx_s); wolfSSL_CTX_free(ctx_cB); @@ -4346,6 +4539,677 @@ int test_dtls13_ch_pool_handshake_threads(void) return EXPECT_RESULT(); } +/* Listener c1 sends a HelloRetryRequest and the whole second ClientHello + * reaches a later listener c2, which completes it with the context's secret, + * generated or set, and not with one of its own. */ +int test_dtls13_ctx_cookie_secret_handoff(void) +{ + EXPECT_DECLS; +#ifdef TEST_DTLS13_CTX_COOKIE + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + struct test_memio_ctx t; + int mode; + + for (mode = 0; mode < 3 && EXPECT_SUCCESS(); mode++) { + XMEMSET(&t, 0, sizeof(t)); + ExpectIntEQ(test_memio_setup(&t, &ctx_c, &ctx_s, NULL, NULL, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(test_dtls13_cs_handoff(ctx_c, ctx_s, mode), TEST_SUCCESS); + wolfSSL_CTX_free(ctx_c); + ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); + ctx_s = NULL; + } +#endif + return EXPECT_RESULT(); +} + +#if defined(TEST_DTLS13_CTX_COOKIE) && \ + (defined(OPENSSL_ALL) || defined(OPENSSL_EXTRA)) +/* Whether s makes cookies of type type with the key context secret secret, + * sz bytes, gives */ +static int test_dtls13_cs_pri_is(const WOLFSSL* s, int type, + const byte* secret, word32 sz) +{ + byte key[CTX_COOKIE_KEY_SZ]; + CookieSecrets cs; + int ok; + + if (s == NULL || test_dtls_ctx_cookie_key(secret, sz, type, key) != 0) + return 0; + if (CookieSecretGet(s, type, &cs) != 0) + return 0; + ok = cs.priSz == sizeof(key) && XMEMCMP(cs.pri, key, sizeof(key)) == 0; + CookieSecretsClear(&cs); + return ok; +} + +/* A new client of ctx_c on memio t sends its first ClientHello to server s, + * which answers with a HelloRetryRequest, and then its second ClientHello. */ +static int test_dtls13_cs_to_ch2(WOLFSSL_CTX* ctx_c, WOLFSSL* s, + struct test_memio_ctx* t, WOLFSSL** ssl_c) +{ + EXPECT_DECLS; + int group = WOLFSSL_ECC_SECP256R1; + int isHrr = 0; + + XMEMSET(t, 0, sizeof(*t)); + ExpectNotNull(*ssl_c = wolfSSL_new(ctx_c)); + ExpectIntEQ(wolfSSL_set_groups(*ssl_c, &group, 1), WOLFSSL_SUCCESS); + wolfSSL_SetIOReadCtx(*ssl_c, t); + wolfSSL_SetIOWriteCtx(*ssl_c, t); + ExpectIntEQ(test_dtls13_server_peer(s, t, 2), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_connect(*ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(*ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(s), -1); + ExpectIntEQ(wolfSSL_get_error(s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_dtls13_reply_is_server_hello(t, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(wolfSSL_connect(*ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(*ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + return EXPECT_RESULT(); +} + +/* Server s handles the second ClientHello of ssl_c on memio t. ok: the + * handshake completes, else a fatal illegal_parameter alert */ +static int test_dtls13_cs_ch2_at(WOLFSSL* ssl_c, WOLFSSL* s, + struct test_memio_ctx* t, int ok) +{ + EXPECT_DECLS; + WOLFSSL_ALERT_HISTORY h; + + XMEMSET(&h, 0, sizeof(h)); + ExpectIntEQ(test_dtls13_server_peer(s, t, 2), WOLFSSL_SUCCESS); + if (ok) { + ExpectIntEQ(test_memio_do_handshake(ssl_c, s, 10, NULL), 0); + } + else { + ExpectIntNE(test_memio_do_handshake(ssl_c, s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_get_alert_history(ssl_c, &h), WOLFSSL_SUCCESS); + ExpectIntEQ(h.last_rx.code, illegal_parameter); + } + return EXPECT_RESULT(); +} +#endif + +#if defined(TEST_DTLS13_CTX_COOKIE) && \ + (defined(OPENSSL_ALL) || defined(OPENSSL_EXTRA)) && defined(HAVE_SNI) +typedef struct test_dtls13_cs_sni_arg { + WOLFSSL_CTX* next; /* the context the object moves to */ + WOLFSSL_CTX* rotate; /* the context whose secret is changed */ + const byte* secret; /* mode 2: the new secret of rotate */ + word32 secretSz; + int mode; + int calls; + int bad; +} test_dtls13_cs_sni_arg; + +/* Runs after the cookie verified. mode 0: move to another context and rotate + * the old one's secret twice. 1: move and ask for the context's DTLS 1.2 + * secret. 2: set a new secret on the context and ask for its 1.2 secret. */ +static int test_dtls13_cs_sni_cb(WOLFSSL* ssl, int* ad, void* arg) +{ + test_dtls13_cs_sni_arg* a = (test_dtls13_cs_sni_arg*)arg; + + (void)ad; + if (a->calls++ != 0) + return 0; + if (a->mode != 2 && wolfSSL_set_SSL_CTX(ssl, a->next) != a->next) + a->bad++; + if (a->mode == 0) { + if (wolfSSL_CTX_rotate_cookie_secret(a->rotate, NULL, 0) != + WOLFSSL_SUCCESS) + a->bad++; + if (wolfSSL_CTX_rotate_cookie_secret(a->rotate, NULL, 0) != + WOLFSSL_SUCCESS) + a->bad++; + } + if (a->mode == 2 && wolfSSL_CTX_set_cookie_secret(a->rotate, + a->secret, a->secretSz) != WOLFSSL_SUCCESS) + a->bad++; + if (a->mode != 0 && wolfSSL_DTLS_SetCookieSecret(ssl, NULL, 0) != 0) + a->bad++; + return 0; +} +#endif + +/* wolfSSL_set_SSL_CTX(): an object uses the new context's secret from then + * on, and the move gives the context one when it has none. The SNI callback + * runs after the cookie check, so it may move the object and change secrets + * freely. */ +int test_dtls13_ctx_cookie_secret_set_ssl_ctx(void) +{ + EXPECT_DECLS; +#if defined(TEST_DTLS13_CTX_COOKIE) && \ + (defined(OPENSSL_ALL) || defined(OPENSSL_EXTRA)) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s1 = NULL, *ctx_s2 = NULL, *ctx_s3 = NULL; + WOLFSSL *ssl_c = NULL, *c1 = NULL, *x = NULL; + struct test_memio_ctx t; + byte p1[32]; + byte p2[32]; +#ifdef HAVE_SNI + test_dtls13_cs_sni_arg arg; + int group = WOLFSSL_ECC_SECP256R1; + int mode; + const char* name = "www.wolfssl.com"; + byte p3[32]; +#endif + + XMEMSET(&t, 0, sizeof(t)); + XMEMSET(p1, 0x11, sizeof(p1)); + XMEMSET(p2, 0x22, sizeof(p2)); +#ifdef HAVE_SNI + XMEMSET(p3, 0x33, sizeof(p3)); +#endif + ExpectIntEQ(test_memio_setup(&t, &ctx_c, &ctx_s1, NULL, NULL, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(test_memio_setup(&t, NULL, &ctx_s2, NULL, NULL, NULL, + wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(test_memio_setup(&t, NULL, &ctx_s3, NULL, NULL, NULL, + wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s1, p1, sizeof(p1)), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s2, p2, sizeof(p2)), + WOLFSSL_SUCCESS); + + /* (a) The move changes the secret the object uses at once */ + ExpectNotNull(c1 = wolfSSL_new(ctx_s1)); + ExpectTrue(test_dtls13_cs_pri_is(c1, CTX_COOKIE_HRR, p1, sizeof(p1))); + ExpectPtrEq(wolfSSL_set_SSL_CTX(c1, ctx_s2), ctx_s2); + ExpectTrue(test_dtls13_cs_pri_is(c1, CTX_COOKIE_HRR, p2, sizeof(p2))); + ExpectIntEQ(test_dtls13_cs_to_ch2(ctx_c, c1, &t, &ssl_c), TEST_SUCCESS); + ExpectNotNull(x = wolfSSL_new(ctx_s2)); + ExpectIntEQ(test_dtls13_cs_ch2_at(ssl_c, x, &t, 1), TEST_SUCCESS); + wolfSSL_free(x); + x = NULL; + wolfSSL_free(ssl_c); + ssl_c = NULL; + /* A server of the old context rejects its cookies */ + ExpectIntEQ(test_dtls13_cs_to_ch2(ctx_c, c1, &t, &ssl_c), TEST_SUCCESS); + ExpectNotNull(x = wolfSSL_new(ctx_s1)); + ExpectIntEQ(test_dtls13_cs_ch2_at(ssl_c, x, &t, 0), TEST_SUCCESS); + wolfSSL_free(x); + x = NULL; + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(c1); + c1 = NULL; + + /* (b) A context without a secret gets one when a server moves to it */ + ExpectIntEQ(ctx_s3->cookieSecret.keySz[0], 0); + ExpectNotNull(c1 = wolfSSL_new(ctx_s1)); + ExpectPtrEq(wolfSSL_set_SSL_CTX(c1, ctx_s3), ctx_s3); + ExpectIntEQ(ctx_s3->cookieSecret.keySz[0], CTX_COOKIE_KEY_SZ); + ExpectFalse(test_dtls13_cs_pri_is(c1, CTX_COOKIE_HRR, p1, sizeof(p1))); + ExpectIntEQ(test_dtls13_cs_to_ch2(ctx_c, c1, &t, &ssl_c), TEST_SUCCESS); + ExpectNotNull(x = wolfSSL_new(ctx_s3)); + ExpectIntEQ(test_dtls13_cs_ch2_at(ssl_c, x, &t, 1), TEST_SUCCESS); + wolfSSL_free(x); + x = NULL; + wolfSSL_free(ssl_c); + ssl_c = NULL; + ExpectIntEQ(test_dtls13_cs_to_ch2(ctx_c, c1, &t, &ssl_c), TEST_SUCCESS); + ExpectNotNull(x = wolfSSL_new(ctx_s1)); + ExpectIntEQ(test_dtls13_cs_ch2_at(ssl_c, x, &t, 0), TEST_SUCCESS); + wolfSSL_free(x); + x = NULL; + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(c1); + c1 = NULL; + +#ifdef HAVE_SNI + /* (c) */ + wolfSSL_CTX_set_servername_callback(ctx_s1, test_dtls13_cs_sni_cb); + ExpectIntEQ(wolfSSL_CTX_set_servername_arg(ctx_s1, &arg), + WOLFSSL_SUCCESS); + for (mode = 0; mode < 3 && EXPECT_SUCCESS(); mode++) { + XMEMSET(&arg, 0, sizeof(arg)); + arg.next = ctx_s2; + arg.rotate = ctx_s1; + arg.secret = p3; + arg.secretSz = sizeof(p3); + arg.mode = mode; + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s1, p1, sizeof(p1)), + WOLFSSL_SUCCESS); + XMEMSET(&t, 0, sizeof(t)); + ExpectNotNull(c1 = wolfSSL_new(ctx_s1)); + ExpectNotNull(ssl_c = wolfSSL_new(ctx_c)); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSNI(ssl_c, WOLFSSL_SNI_HOST_NAME, name, + (word16)XSTRLEN(name)), WOLFSSL_SUCCESS); + wolfSSL_SetIOReadCtx(ssl_c, &t); + wolfSSL_SetIOWriteCtx(ssl_c, &t); + ExpectIntEQ(test_dtls13_server_peer(c1, &t, 2), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, c1, 10, NULL), 0); + ExpectIntGE(arg.calls, 1); + ExpectIntEQ(arg.bad, 0); + ExpectPtrEq(c1->ctx, (mode == 2) ? ctx_s1 : ctx_s2); + /* The secrets are the current context's */ + ExpectTrue(test_dtls13_cs_pri_is(c1, CTX_COOKIE_HRR, + (mode == 2) ? p3 : p2, sizeof(p2))); + ExpectTrue(test_dtls13_cs_pri_is(c1, CTX_COOKIE_DTLS12, + (mode == 2) ? p3 : p2, sizeof(p2))); + ExpectNull(c1->buffers.dtlsCookieSecret.buffer); + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(c1); + c1 = NULL; + } +#endif + + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s1); + wolfSSL_CTX_free(ctx_s2); + wolfSSL_CTX_free(ctx_s3); +#endif + return EXPECT_RESULT(); +} + +#if defined(TEST_DTLS13_CTX_COOKIE) && !defined(SINGLE_THREADED) +#define TEST_CS_THREADS 2 +#define TEST_CS_ROUNDS 20 + +static WOLFSSL_CTX* test_cs_ctx_c; +static WOLFSSL_CTX* test_cs_ctx_s; +static struct test_memio_ctx test_cs_memio[TEST_CS_THREADS]; +static int test_cs_ok[TEST_CS_THREADS]; +static int test_cs_bad[TEST_CS_THREADS]; +static int test_cs_rot_bad; +static int test_cs_rotations; +static wolfSSL_Mutex test_cs_mutex; +static int test_cs_started; +static int test_cs_done; +static byte test_cs_a[32]; +static byte test_cs_b[32]; +/* The keys A and B give: [A, B][cookie type] */ +static byte test_cs_key[2][2][CTX_COOKIE_KEY_SZ]; + +static int test_cs_get(const int* flag) +{ + int v = 0; + + if (wc_LockMutex(&test_cs_mutex) == 0) { + v = *flag; + wc_UnLockMutex(&test_cs_mutex); + } + return v; +} + +static void test_cs_set(int* flag) +{ + if (wc_LockMutex(&test_cs_mutex) == 0) { + *flag = 1; + wc_UnLockMutex(&test_cs_mutex); + } +} + +static THREAD_RETURN WOLFSSL_THREAD test_dtls13_cs_rotator(void* args) +{ + (void)args; + test_cs_set(&test_cs_started); + do { + if (wolfSSL_CTX_rotate_cookie_secret(test_cs_ctx_s, test_cs_a, + sizeof(test_cs_a)) != WOLFSSL_SUCCESS) + test_cs_rot_bad++; + if (wolfSSL_CTX_rotate_cookie_secret(test_cs_ctx_s, test_cs_b, + sizeof(test_cs_b)) != WOLFSSL_SUCCESS) + test_cs_rot_bad++; + test_cs_rotations += 2; + } while (!test_cs_get(&test_cs_done)); + WOLFSSL_RETURN_FROM_THREAD(0); +} + +/* A long-lived listener c1 sends each client a HelloRetryRequest. The second + * ClientHello goes to a new listener c2. */ +static THREAD_RETURN WOLFSSL_THREAD test_dtls13_cs_worker(void* args) +{ + int tno = ((func_args*)args)->argc; + struct test_memio_ctx* t = &test_cs_memio[tno]; + int group = WOLFSSL_ECC_SECP256R1; + WOLFSSL* c1 = wolfSSL_new(test_cs_ctx_s); + int round; + + for (round = 0; round < TEST_CS_ROUNDS; round++) { + WOLFSSL* ssl_c = NULL; + WOLFSSL* c2 = NULL; + int peer = tno * 100 + round; + int ok = 0; + + XMEMSET(t, 0, sizeof(*t)); + ssl_c = wolfSSL_new(test_cs_ctx_c); + if (c1 != NULL && ssl_c != NULL && + wolfSSL_set_groups(ssl_c, &group, 1) == WOLFSSL_SUCCESS) { + wolfSSL_SetIOReadCtx(ssl_c, t); + wolfSSL_SetIOWriteCtx(ssl_c, t); + if (test_dtls13_server_peer(c1, t, peer) == WOLFSSL_SUCCESS && + wolfSSL_connect(ssl_c) == -1 && + wolfSSL_get_error(ssl_c, -1) == WOLFSSL_ERROR_WANT_READ && + wolfSSL_accept(c1) == -1 && + wolfSSL_get_error(c1, -1) == WOLFSSL_ERROR_WANT_READ && + !c1->options.dtlsStateful && + wolfSSL_connect(ssl_c) == -1 && + wolfSSL_get_error(ssl_c, -1) == WOLFSSL_ERROR_WANT_READ) { + c2 = wolfSSL_new(test_cs_ctx_s); + } + } + if (c2 != NULL && + test_dtls13_server_peer(c2, t, peer) == WOLFSSL_SUCCESS) { + ok = test_memio_do_handshake(ssl_c, c2, 10, NULL) == 0; + } + if (ok) + test_cs_ok[tno]++; + else + test_cs_bad[tno]++; + wolfSSL_free(c2); + wolfSSL_free(ssl_c); + } + wolfSSL_free(c1); + WOLFSSL_RETURN_FROM_THREAD(0); +} +#endif + +/* One thread rotates the secret between A and B while two hand clients from + * long-lived listeners to new ones. Every check sees A and B: all complete. */ +int test_dtls13_ctx_cookie_secret_threads(void) +{ + EXPECT_DECLS; +#if defined(TEST_DTLS13_CTX_COOKIE) && !defined(SINGLE_THREADED) + THREAD_TYPE rotator; + THREAD_TYPE workers[TEST_CS_THREADS]; + func_args rotArgs; + func_args args[TEST_CS_THREADS]; + int i; + + XMEMSET(&rotArgs, 0, sizeof(rotArgs)); + XMEMSET(args, 0, sizeof(args)); + XMEMSET(test_cs_ok, 0, sizeof(test_cs_ok)); + XMEMSET(test_cs_bad, 0, sizeof(test_cs_bad)); + XMEMSET(test_cs_a, 0xA1, sizeof(test_cs_a)); + XMEMSET(test_cs_b, 0xB2, sizeof(test_cs_b)); + for (i = 0; i < 2; i++) { + ExpectIntEQ(test_dtls_ctx_cookie_key(test_cs_a, sizeof(test_cs_a), i, + test_cs_key[0][i]), 0); + ExpectIntEQ(test_dtls_ctx_cookie_key(test_cs_b, sizeof(test_cs_b), i, + test_cs_key[1][i]), 0); + } + test_cs_rot_bad = 0; + test_cs_rotations = 0; + test_cs_started = 0; + test_cs_done = 0; + test_cs_ctx_c = NULL; + test_cs_ctx_s = NULL; + ExpectIntEQ(test_memio_setup(&test_cs_memio[0], &test_cs_ctx_c, + &test_cs_ctx_s, NULL, NULL, wolfDTLSv1_3_client_method, + wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(test_cs_ctx_s, + test_cs_a, sizeof(test_cs_a)), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(test_cs_ctx_s, + test_cs_b, sizeof(test_cs_b)), WOLFSSL_SUCCESS); + /* One hand-off on this thread first: it sets up what the library makes + * lazily on first use, such as the default session ticket key */ + ExpectIntEQ(test_dtls13_cs_handoff(test_cs_ctx_c, test_cs_ctx_s, 0), + TEST_SUCCESS); + ExpectIntEQ(wc_InitMutex(&test_cs_mutex), 0); + if (EXPECT_SUCCESS()) { + start_thread(test_dtls13_cs_rotator, &rotArgs, &rotator); + while (!test_cs_get(&test_cs_started)) + XSLEEP_MS(1); + for (i = 0; i < TEST_CS_THREADS; i++) { + args[i].argc = i; + start_thread(test_dtls13_cs_worker, &args[i], &workers[i]); + } + for (i = 0; i < TEST_CS_THREADS; i++) + join_thread(workers[i]); + test_cs_set(&test_cs_done); + join_thread(rotator); + ExpectIntEQ(wc_FreeMutex(&test_cs_mutex), 0); + + for (i = 0; i < TEST_CS_THREADS; i++) { + ExpectIntEQ(test_cs_bad[i], 0); + ExpectIntEQ(test_cs_ok[i], TEST_CS_ROUNDS); + } + ExpectIntEQ(test_cs_rot_bad, 0); + ExpectIntGE(test_cs_rotations, 2); + /* Each loop of the rotator ends with B as the primary */ + ExpectIntEQ(test_cs_ctx_s->cookieSecret.keySz[0], CTX_COOKIE_KEY_SZ); + ExpectIntEQ(test_cs_ctx_s->cookieSecret.keySz[1], CTX_COOKIE_KEY_SZ); + ExpectBufEQ(test_cs_ctx_s->cookieSecret.hvrKey[0], + test_cs_key[1][CTX_COOKIE_DTLS12], CTX_COOKIE_KEY_SZ); + ExpectBufEQ(test_cs_ctx_s->cookieSecret.hrrKey[0], + test_cs_key[1][CTX_COOKIE_HRR], CTX_COOKIE_KEY_SZ); + ExpectBufEQ(test_cs_ctx_s->cookieSecret.hvrKey[1], + test_cs_key[0][CTX_COOKIE_DTLS12], CTX_COOKIE_KEY_SZ); + ExpectBufEQ(test_cs_ctx_s->cookieSecret.hrrKey[1], + test_cs_key[0][CTX_COOKIE_HRR], CTX_COOKIE_KEY_SZ); + } + wolfSSL_CTX_free(test_cs_ctx_c); + wolfSSL_CTX_free(test_cs_ctx_s); + test_cs_ctx_c = NULL; + test_cs_ctx_s = NULL; +#endif + return EXPECT_RESULT(); +} + +/* A server given no secret issued its HelloRetryRequest under the one the + * context generated. An application secret set by rotation keeps that cookie + * valid; one set outright does not. */ +int test_dtls13_ctx_cookie_secret_after_random(void) +{ + EXPECT_DECLS; +#ifdef TEST_DTLS13_CTX_COOKIE + int rotate; + + for (rotate = 1; rotate >= 0 && EXPECT_SUCCESS(); rotate--) { + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx t; + WOLFSSL_ALERT_HISTORY h; + int group = WOLFSSL_ECC_SECP256R1; + int isHrr = 0; + byte p[32]; + + XMEMSET(&t, 0, sizeof(t)); + XMEMSET(&h, 0, sizeof(h)); + XMEMSET(p, 0x11, sizeof(p)); + ExpectIntEQ(test_memio_setup(&t, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_send_hrr_cookie(ssl_s, NULL, 0), WOLFSSL_SUCCESS); + ExpectNull(ssl_s->buffers.tls13CookieSecret.buffer); + ExpectIntEQ(ctx_s->cookieSecret.keySz[0], CTX_COOKIE_KEY_SZ); + ExpectIntEQ(test_dtls13_server_peer(ssl_s, &t, 2), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + + /* The HelloRetryRequest cookie is made under the generated secret */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&t, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + + if (rotate) { + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(ctx_s, p, sizeof(p)), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + } + else { + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s, p, sizeof(p)), + WOLFSSL_SUCCESS); + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_get_alert_history(ssl_c, &h), + WOLFSSL_SUCCESS); + ExpectIntEQ(h.last_rx.code, illegal_parameter); + } + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + } +#endif + return EXPECT_RESULT(); +} + +/* A HelloVerifyRequest cookie must not pass as a HelloRetryRequest cookie. + * Their MAC inputs can be lined up: an attacker's HVR cookie for a ClientHello + * ending in the victim's address replays as the victim's HRR cookie. */ +int test_dtls13_ctx_cookie_secret_no_splice(void) +{ + EXPECT_DECLS; +#if defined(TEST_DTLS13_CTX_COOKIE) && !defined(NO_SHA256) && \ + !defined(WOLFSSL_NO_TLS12) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx t; + int group = WOLFSSL_ECC_SECP256R1; + /* As cookie data: hash length 2, hash, TLS_AES_128_GCM_SHA256, + * secp256r1 */ + byte a[28] = { 2, 0x5a, 0x5a, 0x13, 0x01, 0x00, 0x17 }; + byte v[28]; + byte rnd[RAN_LEN]; + byte hvrCh[DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ + + OPAQUE16_LEN + RAN_LEN + 1 + 1 + OPAQUE16_LEN + 1 + sizeof(v)]; + byte mac[WC_SHA256_DIGEST_SIZE]; + byte ch[TEST_MEMIO_BUF_SZ]; + byte* p; + word32 chSz = 0; + word32 idx = 0; + word32 len = 0; + word32 extSz; + word32 cookieSz; + word16 len16 = 0; + + XMEMSET(&t, 0, sizeof(t)); + XMEMSET(v, 0x56, sizeof(v)); + XMEMSET(rnd, 0x52, sizeof(rnd)); + XMEMSET(mac, 0, sizeof(mac)); + ExpectIntEQ(test_memio_setup(&t, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s, NULL, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_send_hrr_cookie(ssl_s, NULL, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + + /* A ClientHello without supported_versions from a: empty session id and + * cipher suites, v as the compression list */ + p = hvrCh; + *p++ = handshake; + *p++ = DTLS_MAJOR; + *p++ = DTLSv1_2_MINOR; + XMEMSET(p, 0, 8); + p += 8; + c16toa((word16)(sizeof(hvrCh) - DTLS_RECORD_HEADER_SZ), p); + p += OPAQUE16_LEN; + *p++ = client_hello; + c32to24((word32)(sizeof(hvrCh) - DTLS_RECORD_HEADER_SZ - + DTLS_HANDSHAKE_HEADER_SZ), p); + p += OPAQUE24_LEN; + XMEMSET(p, 0, OPAQUE16_LEN + OPAQUE24_LEN); + p += OPAQUE16_LEN + OPAQUE24_LEN; + c32to24((word32)(sizeof(hvrCh) - DTLS_RECORD_HEADER_SZ - + DTLS_HANDSHAKE_HEADER_SZ), p); + p += OPAQUE24_LEN; + *p++ = DTLS_MAJOR; + *p++ = DTLSv1_2_MINOR; + XMEMCPY(p, rnd, RAN_LEN); + p += RAN_LEN; + *p++ = 0; + *p++ = 0; + c16toa(0, p); + p += OPAQUE16_LEN; + *p++ = (byte)sizeof(v); + XMEMCPY(p, v, sizeof(v)); + + wolfSSL_SetIOReadCtx(ssl_s, &t); + wolfSSL_SetIOWriteCtx(ssl_s, &t); + ExpectIntEQ(wolfSSL_dtls_set_peer(ssl_s, a, sizeof(a)), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_inject_message(&t, 0, (const char*)hvrCh, + sizeof(hvrCh)), 0); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + idx = DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ + OPAQUE16_LEN; + ExpectIntGE(t.c_len, idx + 1 + sizeof(mac)); + ExpectIntEQ(t.c_buff[0], handshake); + ExpectIntEQ(t.c_buff[DTLS_RECORD_HEADER_SZ], hello_verify_request); + ExpectIntEQ(t.c_buff[idx], sizeof(mac)); + if (EXPECT_SUCCESS()) + XMEMCPY(mac, t.c_buff + idx + 1, sizeof(mac)); + test_memio_clear_buffer(&t, 1); + + /* A real first ClientHello, given the spliced cookie */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + cookieSz = sizeof(a) + OPAQUE16_LEN + RAN_LEN + sizeof(mac); + extSz = HELLO_EXT_TYPE_SZ + OPAQUE16_LEN + OPAQUE16_LEN + cookieSz; + ExpectIntLE(t.s_len + extSz, sizeof(ch)); + if (EXPECT_SUCCESS()) { + chSz = (word32)t.s_len; + XMEMCPY(ch, t.s_buff, chSz); + ato16(ch + DTLS_RECORD_HEADER_SZ - OPAQUE16_LEN, &len16); + ato24(ch + DTLS_RECORD_HEADER_SZ + 1, &len); + } + test_memio_clear_buffer(&t, 0); + /* One unfragmented message in one record */ + ExpectIntEQ(len16, chSz - DTLS_RECORD_HEADER_SZ); + ExpectIntEQ(len, chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ); + if (EXPECT_SUCCESS()) { + idx = DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ + + OPAQUE16_LEN + RAN_LEN; + idx += 1 + ch[idx]; /* session id */ + idx += 1 + ch[idx]; /* cookie */ + idx += OPAQUE16_LEN + ((word32)ch[idx] << 8 | ch[idx + 1]); /* suites */ + idx += 1 + ch[idx]; /* compression */ + } + ExpectIntLE(idx + OPAQUE16_LEN, chSz); + if (EXPECT_SUCCESS()) { + /* Insert the cookie extension first */ + ato16(ch + idx, &len16); + c16toa((word16)(len16 + extSz), ch + idx); + idx += OPAQUE16_LEN; + XMEMMOVE(ch + idx + extSz, ch + idx, chSz - idx); + p = ch + idx; + c16toa(TLSX_COOKIE, p); + p += HELLO_EXT_TYPE_SZ; + c16toa((word16)(OPAQUE16_LEN + cookieSz), p); + p += OPAQUE16_LEN; + c16toa((word16)cookieSz, p); + p += OPAQUE16_LEN; + XMEMCPY(p, a, sizeof(a)); + p += sizeof(a); + *p++ = DTLS_MAJOR; + *p++ = DTLSv1_2_MINOR; + XMEMCPY(p, rnd, RAN_LEN); + p += RAN_LEN; + XMEMCPY(p, mac, sizeof(mac)); + chSz += extSz; + c16toa((word16)(chSz - DTLS_RECORD_HEADER_SZ), + ch + DTLS_RECORD_HEADER_SZ - OPAQUE16_LEN); + c32to24(chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ, + ch + DTLS_RECORD_HEADER_SZ + 1); + c32to24(chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ, + ch + DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ - + OPAQUE24_LEN); + } + + /* Sent as v: the server must not take it */ + ExpectIntEQ(wolfSSL_dtls_set_peer(ssl_s, v, sizeof(v)), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_inject_message(&t, 0, (const char*)ch, (int)chSz), + 0); + ExpectIntNE(wolfSSL_accept(ssl_s), WOLFSSL_SUCCESS); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectFalse(t.c_len > DTLS_RECORD_HEADER_SZ && t.c_buff[0] == handshake && + t.c_buff[DTLS_RECORD_HEADER_SZ] == server_hello); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + /* Cookie exchange off: the server is stateful from the start, but a plaintext * fragment contradicting the held ClientHello is dropped, not fatal. */ int test_dtls13_frag_ch1_no_cookie_conflict(void) @@ -5030,8 +5894,8 @@ int test_dtls13_ch_pool_peer_key(void) return EXPECT_RESULT(); } -/* Only a DTLS context initializes the ClientHello pool. A TLS context's pool - * takes nothing. */ +/* Only a DTLS context initializes the ClientHello pool; every server context + * has a cookie secret lock. A TLS context's pool takes nothing. */ int test_dtls13_ch_pool_ctx_init(void) { EXPECT_DECLS; @@ -5053,6 +5917,10 @@ int test_dtls13_ch_pool_ctx_init(void) if (EXPECT_SUCCESS()) { ExpectIntEQ(tls->dtlsChPool.ready, 0); ExpectIntEQ(dtls->dtlsChPool.ready, 1); +#ifndef SINGLE_THREADED + ExpectNotNull(tls->cookieSecret.lock); + ExpectNotNull(dtls->cookieSecret.lock); +#endif ExpectIntEQ(DtlsChPoolStore(&tls->dtlsChPool, tls->heap, &peer, 0, 0, data, 8, client_hello, 0, 4, DTLS_TIMEOUT_MAX, NULL, &done), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); diff --git a/tests/api/test_dtls13.h b/tests/api/test_dtls13.h index 8c62464655d..ead5dd6d098 100644 --- a/tests/api/test_dtls13.h +++ b/tests/api/test_dtls13.h @@ -73,6 +73,11 @@ int test_dtls13_frag_ch2_dtls12_cookie(void); int test_dtls13_ch_pool_threads(void); int test_dtls13_ch_pool_shards_threads(void); int test_dtls13_ch_pool_handshake_threads(void); +int test_dtls13_ctx_cookie_secret_handoff(void); +int test_dtls13_ctx_cookie_secret_set_ssl_ctx(void); +int test_dtls13_ctx_cookie_secret_threads(void); +int test_dtls13_ctx_cookie_secret_no_splice(void); +int test_dtls13_ctx_cookie_secret_after_random(void); int test_dtls13_ch_pool_unit(void); int test_dtls13_ch_pool_shards(void); int test_dtls13_ch_pool_evict_own_shard(void); @@ -175,6 +180,11 @@ int test_dtls13_update_keys_want_write(void); TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_threads), \ TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_shards_threads), \ TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_handshake_threads), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ctx_cookie_secret_handoff), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ctx_cookie_secret_set_ssl_ctx), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ctx_cookie_secret_threads), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ctx_cookie_secret_no_splice), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ctx_cookie_secret_after_random), \ TEST_DECL_GROUP("dtls13", test_dtls_empty_keyshare_with_cookie), \ TEST_DECL_GROUP("dtls13", test_dtls13_missing_finished_client), \ TEST_DECL_GROUP("dtls13", test_dtls13_missing_finished_server), \ diff --git a/tests/api/test_ssl_cert.c b/tests/api/test_ssl_cert.c index 5871f320be4..5208ab12fb3 100644 --- a/tests/api/test_ssl_cert.c +++ b/tests/api/test_ssl_cert.c @@ -6749,6 +6749,13 @@ int test_wolfSSL_dtls_api_more_guards(void) (void)wolfSSL_dtls13_has_pending_msg(dssl); #endif + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(NULL, NULL, 0), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret_secondary(NULL, NULL, 0), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_CTX_rotate_cookie_secret(NULL, NULL, 0), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + #if !defined(NO_WOLFSSL_SERVER) { byte secret[16]; diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index 5a78ed63d7e..cb6aa17d639 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -313,10 +313,14 @@ int test_tls13_apis(void) WC_NO_ERR_TRACE(BAD_FUNC_ARG)); #endif + /* Without a secret of its own the object uses the context's, generated + * when first needed */ ExpectIntEQ(wolfSSL_enable_cookie(serverSsl), WOLFSSL_SUCCESS); ExpectIntEQ(serverSsl->options.sendCookie, 1); - ExpectNotNull(serverSsl->buffers.tls13CookieSecret.buffer); + ExpectNull(serverSsl->buffers.tls13CookieSecret.buffer); + ExpectIntEQ(serverSsl->ctx->cookieSecret.keySz[0], CTX_COOKIE_KEY_SZ); ExpectIntEQ(wolfSSL_send_hrr_cookie(serverSsl, NULL, 0), WOLFSSL_SUCCESS); + ExpectNull(serverSsl->buffers.tls13CookieSecret.buffer); ExpectIntEQ(wolfSSL_send_hrr_cookie(serverSsl, fixedKey, sizeof(fixedKey)), WOLFSSL_SUCCESS); ExpectIntEQ(wolfSSL_enable_cookie(serverSsl), WOLFSSL_SUCCESS); @@ -330,7 +334,8 @@ int test_tls13_apis(void) ExpectIntEQ(wolfSSL_disable_cookie(serverSsl), WOLFSSL_SUCCESS); ExpectIntEQ(wolfSSL_enable_cookie(serverSsl), WOLFSSL_SUCCESS); ExpectIntEQ(serverSsl->options.sendCookie, 1); - ExpectNotNull(serverSsl->buffers.tls13CookieSecret.buffer); + ExpectNull(serverSsl->buffers.tls13CookieSecret.buffer); + ExpectIntEQ(serverSsl->ctx->cookieSecret.keySz[0], CTX_COOKIE_KEY_SZ); #endif #endif @@ -11994,13 +11999,12 @@ int test_tls13_hrr_bad_cookie(void) ExpectIntEQ(wolfSSL_get_error(ssl_c, WOLFSSL_FATAL_ERROR), WOLFSSL_ERROR_WANT_READ); - /* Corrupt the server-side cookie secret after HRR so CH2's cookie no longer + /* Corrupt the context's cookie key after HRR so CH2's cookie no longer * verifies in TlsCheckCookie(). */ if (EXPECT_SUCCESS()) { - ExpectNotNull(ssl_s->buffers.tls13CookieSecret.buffer); - ExpectIntGT(ssl_s->buffers.tls13CookieSecret.length, 0); - ssl_s->buffers.tls13CookieSecret.buffer[ - ssl_s->buffers.tls13CookieSecret.length - 1] ^= 0xFF; + ExpectNull(ssl_s->buffers.tls13CookieSecret.buffer); + ExpectIntEQ(ctx_s->cookieSecret.keySz[0], CTX_COOKIE_KEY_SZ); + ctx_s->cookieSecret.hrrKey[0][0] ^= 0xFF; } /* Step 4: Server processes corrupted CH2 - should fail */ @@ -12016,6 +12020,102 @@ int test_tls13_hrr_bad_cookie(void) return EXPECT_RESULT(); } +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) +/* Erase the context's cookie secret under its lock */ +static void test_tls13_cs_ctx_erase(WOLFSSL_CTX* ctx) +{ + if (ctx == NULL) + return; +#ifndef SINGLE_THREADED + if (wc_LockRwLock_Wr(&ctx->cookieSecretLock) != 0) + return; +#endif + ctx->cookieSecret.keySz[0] = 0; + ctx->cookieSecret.keySz[1] = 0; +#ifndef SINGLE_THREADED + wc_UnLockRwLock(&ctx->cookieSecretLock); +#endif +} +#endif + +/* A TLS 1.3 server context has a cookie secret too, generated when a server + * first needs one or set, and every server of the context uses it. Without a + * secret anywhere there is no cookie. */ +int test_tls13_ctx_cookie_secret(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *s1 = NULL, *s2 = NULL; + struct test_memio_ctx t; + byte p[32]; + int mode; + + XMEMSET(p, 0x11, sizeof(p)); + for (mode = 0; mode < 3 && EXPECT_SUCCESS(); mode++) { + XMEMSET(&t, 0, sizeof(t)); + ExpectIntEQ(test_memio_setup(&t, &ctx_c, &ctx_s, &ssl_c, &s1, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + if (mode == 1) { + ExpectIntEQ(wolfSSL_CTX_set_cookie_secret(ctx_s, p, sizeof(p)), + WOLFSSL_SUCCESS); + } + ExpectIntEQ(wolfSSL_send_hrr_cookie(s1, NULL, 0), WOLFSSL_SUCCESS); + ExpectNull(s1->buffers.tls13CookieSecret.buffer); + ExpectIntEQ(ctx_s->cookieSecret.keySz[0], CTX_COOKIE_KEY_SZ); + ExpectIntEQ(wolfSSL_NoKeyShares(ssl_c), WOLFSSL_SUCCESS); + if (mode == 2) { + /* No secret: no HelloRetryRequest cookie can be made */ + test_tls13_cs_ctx_erase(ctx_s); + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), + WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(s1), -1); + ExpectIntEQ(wolfSSL_get_error(s1, -1), + WC_NO_ERR_TRACE(COOKIE_ERROR)); + } + else { + byte hash[WC_SHA256_DIGEST_SIZE]; + TLSX* ext = NULL; + + /* A cookie one server of the context issues verifies at another. + * TLS keeps the HelloRetryRequest state on the object, so the + * handshake stays with s1 and s2 only issues. */ + ExpectNotNull(s2 = wolfSSL_new(ctx_s)); + ExpectIntEQ(wolfSSL_send_hrr_cookie(s2, NULL, 0), WOLFSSL_SUCCESS); + XMEMSET(hash, 0x5A, sizeof(hash)); + ExpectIntEQ(CreateCookieExt(s2, hash, (word16)sizeof(hash), + &s2->extensions, 0x13, 0x01), 0); + ExpectNotNull(ext = TLSX_Find(s2->extensions, TLSX_COOKIE)); + if (ext != NULL) { + Cookie* cookie = (Cookie*)ext->data; + + ExpectIntGT(TlsCheckCookie(s1, cookie->data, cookie->len), 0); + cookie->data[0] ^= 0xFF; + ExpectIntEQ(TlsCheckCookie(s1, cookie->data, cookie->len), + WC_NO_ERR_TRACE(HRR_COOKIE_ERROR)); + } + ExpectIntEQ(test_memio_do_handshake(ssl_c, s1, 10, NULL), 0); + } + wolfSSL_free(s2); + s2 = NULL; + wolfSSL_free(ssl_c); + ssl_c = NULL; + wolfSSL_free(s1); + s1 = NULL; + wolfSSL_CTX_free(ctx_c); + ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); + ctx_s = NULL; + } +#endif + return EXPECT_RESULT(); +} + /* Run a handshake where the server sends a cookie in its HelloRetryRequest: * the client must echo it back for the server to accept the second * ClientHello. With noKeyShares the retry also asks for a different group, diff --git a/tests/api/test_tls13.h b/tests/api/test_tls13.h index 9bb51900267..32699878899 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -104,6 +104,7 @@ int test_tls13_corrupted_finished(void); int test_tls13_certificate_verify_bad_sigalgo(void); int test_tls13_peerauth_failsafe(void); int test_tls13_hrr_bad_cookie(void); +int test_tls13_ctx_cookie_secret(void); int test_tls13_hrr_cookie_handshake(void); int test_tls13_hrr_cookie_only_handshake(void); int test_tls13_client_cookie_echo(void); @@ -297,6 +298,7 @@ int test_tls13_exporter_suite_decision_coverage(void); TEST_DECL_GROUP("tls13", test_tls13_certificate_verify_bad_sigalgo), \ TEST_DECL_GROUP("tls13", test_tls13_peerauth_failsafe), \ TEST_DECL_GROUP("tls13", test_tls13_hrr_bad_cookie), \ + TEST_DECL_GROUP("tls13", test_tls13_ctx_cookie_secret), \ TEST_DECL_GROUP("tls13", test_tls13_hrr_cookie_handshake), \ TEST_DECL_GROUP("tls13", test_tls13_hrr_cookie_only_handshake), \ TEST_DECL_GROUP("tls13", test_tls13_client_cookie_echo), \ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index b7995bb32e7..8fc1bba58d4 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -3129,6 +3129,64 @@ typedef struct DtlsChPool { } DtlsChPool; #endif +#if (defined(WOLFSSL_DTLS) || defined(WOLFSSL_SEND_HRR_COOKIE)) && \ + !defined(NO_WOLFSSL_SERVER) +/* Each cookie type gets its own key, an HMAC of a label under the context's + * secret, so a cookie of one type never verifies as the other. */ +#ifndef NO_SHA256 + #define CTX_COOKIE_KEY_TYPE WC_SHA256 + #define CTX_COOKIE_KEY_SZ WC_SHA256_DIGEST_SIZE +#elif defined(WOLFSSL_SHA384) + #define CTX_COOKIE_KEY_TYPE WC_SHA384 + #define CTX_COOKIE_KEY_SZ WC_SHA384_DIGEST_SIZE +#elif defined(WOLFSSL_TLS13_SHA512) + #define CTX_COOKIE_KEY_TYPE WC_SHA512 + #define CTX_COOKIE_KEY_SZ WC_SHA512_DIGEST_SIZE +#elif defined(WOLFSSL_SM3) + #define CTX_COOKIE_KEY_TYPE WC_SM3 + #define CTX_COOKIE_KEY_SZ WC_SM3_DIGEST_SIZE +#elif !defined(NO_SHA) + #define CTX_COOKIE_KEY_TYPE WC_SHA + #define CTX_COOKIE_KEY_SZ WC_SHA_DIGEST_SIZE +#else + #error "No digest available to make the cookie keys with." +#endif +#define CTX_COOKIE_DTLS12 0 /* HelloVerifyRequest cookie */ +#define CTX_COOKIE_HRR 1 /* HelloRetryRequest cookie */ + +/* Cookie secret the servers of a context share, as the key of each type */ +typedef struct CookieSecretCtx { + /* The key of each cookie type, [primary, secondary] */ + byte hvrKey[2][CTX_COOKIE_KEY_SZ]; + byte hrrKey[2][CTX_COOKIE_KEY_SZ]; + word32 keySz[2]; /* 0: not set */ +#ifndef SINGLE_THREADED + wolfSSL_RwLock* lock; /* NULL until the lock is ready */ +#endif +} CookieSecretCtx; + +/* The secrets a cookie of one type is made and checked with, resolved at use: + * the object's own, else copies of the context's keys in key. */ +typedef struct CookieSecrets { + const byte* pri; /* NULL: none */ + const byte* sec; /* NULL: none */ + word32 priSz; + word32 secSz; + byte key[2][CTX_COOKIE_KEY_SZ]; +} CookieSecrets; + +WOLFSSL_LOCAL int CookieSecretCtxInit(WOLFSSL_CTX* ctx); +WOLFSSL_LOCAL void CookieSecretCtxFree(WOLFSSL_CTX* ctx); +WOLFSSL_LOCAL int CookieSecretCtxDefault(WOLFSSL_CTX* ctx); +#ifdef WOLFSSL_API_PREFIX_MAP + #define CookieSecretGet wolfSSL_CookieSecretGet + #define CookieSecretsClear wolfSSL_CookieSecretsClear +#endif +WOLFSSL_TEST_VIS int CookieSecretGet(const WOLFSSL* ssl, int type, + CookieSecrets* cs); +WOLFSSL_TEST_VIS void CookieSecretsClear(CookieSecrets* cs); +#endif + #ifdef WOLFSSL_DTLS typedef struct WOLFSSL_DTLS_CTX { #ifdef WOLFSSL_RW_THREADED @@ -4336,8 +4394,11 @@ typedef struct Cookie { WOLFSSL_LOCAL int TLSX_Cookie_Use(const WOLFSSL* ssl, const byte* data, word16 len, byte* mac, byte macSz, int resp, TLSX** exts); -WOLFSSL_LOCAL int TlsCheckCookie(const WOLFSSL* ssl, const byte* cookie, - word16 cookieSz); +#ifdef WOLFSSL_API_PREFIX_MAP + #define TlsCheckCookie wolfSSL_TlsCheckCookie +#endif +WOLFSSL_TEST_VIS int TlsCheckCookie(const WOLFSSL* ssl, const byte* cookie, + word16 cookieSz); /* Key Share - TLS v1.3 Specification */ @@ -4760,6 +4821,13 @@ struct WOLFSSL_CTX { DtlsChPool dtlsChPool; /* unverified ClientHellos of all * servers */ #endif +#if (defined(WOLFSSL_DTLS) || defined(WOLFSSL_SEND_HRR_COOKIE)) && \ + !defined(NO_WOLFSSL_SERVER) + CookieSecretCtx cookieSecret; +#ifndef SINGLE_THREADED + wolfSSL_RwLock cookieSecretLock; /* see cookieSecret.lock */ +#endif +#endif #ifndef NO_DH word16 minDhKeySz; /* minimum DH key size */ word16 maxDhKeySz; /* maximum DH key size */ @@ -8789,9 +8857,12 @@ WOLFSSL_LOCAL int FindPskSuite(const WOLFSSL* ssl, PreSharedKey* psk, WOLFSSL_LOCAL int wolfSSL_GetHmacType_ex(CipherSpecs* specs); #if defined(WOLFSSL_SEND_HRR_COOKIE) && !defined(NO_WOLFSSL_SERVER) -WOLFSSL_LOCAL int CreateCookieExt(const WOLFSSL* ssl, byte* hash, - word16 hashSz, TLSX** exts, - byte cipherSuite0, byte cipherSuite); +#ifdef WOLFSSL_API_PREFIX_MAP + #define CreateCookieExt wolfSSL_CreateCookieExt +#endif +WOLFSSL_TEST_VIS int CreateCookieExt(const WOLFSSL* ssl, byte* hash, + word16 hashSz, TLSX** exts, + byte cipherSuite0, byte cipherSuite); #endif WOLFSSL_LOCAL int TranslateErrorToAlert(int err); diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index e6282af9f44..b52e31f33fe 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -4181,6 +4181,14 @@ 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); +#if defined(WOLFSSL_DTLS) || defined(WOLFSSL_SEND_HRR_COOKIE) +WOLFSSL_API int wolfSSL_CTX_set_cookie_secret(WOLFSSL_CTX* ctx, + const unsigned char* secret, unsigned int secretSz); +WOLFSSL_API int wolfSSL_CTX_set_cookie_secret_secondary(WOLFSSL_CTX* ctx, + const unsigned char* secret, unsigned int secretSz); +WOLFSSL_API int wolfSSL_CTX_rotate_cookie_secret(WOLFSSL_CTX* ctx, + const unsigned char* secret, unsigned int secretSz); +#endif /* CA cache callbacks */ From b9b9ff01ff6d9f11e534aa24ac5c78125b7ae8a4 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Wed, 4 Nov 2026 12:05:35 +0000 Subject: [PATCH 06/10] CI: unit test groups under ThreadSanitizer The ClientHello pool, the context cookie secret and the ticket key context are shared between threads, and their threaded tests only prove anything with a race detector. Build with -fsanitize=thread and --enable-rwlock, so readers run in parallel, and run the dtls, dtls13, tls13 and session groups. One report fails the job. Two minutes locally. Checked that a report fails the run: with DtlsChShardLock() and DtlsChShardUnlock() made no-ops, the same command reports a race in DtlsChPoolFind() from test_dtls13_ch_pool_threads and exits 66. --- .github/workflows/tsan.yml | 56 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 .github/workflows/tsan.yml diff --git a/.github/workflows/tsan.yml b/.github/workflows/tsan.yml new file mode 100644 index 00000000000..1a09d7766d4 --- /dev/null +++ b/.github/workflows/tsan.yml @@ -0,0 +1,56 @@ +name: ThreadSanitizer Tests + +# START OF COMMON SECTION +on: + push: + branches: [ 'master', 'release/**' ] + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + branches: [ '*' ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read +# END OF COMMON SECTION + +jobs: + tsan: + name: unit tests under ThreadSanitizer + if: ${{ (github.repository_owner == 'wolfssl') && (github.event_name != 'pull_request' || github.event.pull_request.draft == false) }} + runs-on: ubuntu-24.04 + timeout-minutes: 30 + steps: + - uses: actions/checkout@v5 + name: Checkout wolfSSL + + - name: Install dependencies + uses: ./.github/actions/install-apt-deps + with: + packages: autoconf automake libtool build-essential + ghcr-debs-tag: ubuntu-24.04-minimal + + # libtsan rejects the kernel's default address space randomization. + - name: Lower ASLR entropy for ThreadSanitizer + run: sudo sysctl -w vm.mmap_rnd_bits=28 + + # --enable-rwlock: without it wolfSSL_RwLock is a mutex and readers + # never run in parallel. + - name: Build wolfSSL + run: | + ./autogen.sh + ./configure --enable-dtls --enable-dtls13 --enable-dtls-mtu \ + --enable-dtls-frag-ch --enable-hrrcookie --enable-session-ticket \ + --enable-rwlock --enable-curve25519 --enable-opensslextra \ + --enable-debug \ + CFLAGS="-fsanitize=thread -g -O1 -fno-omit-frame-pointer" \ + LDFLAGS="-fsanitize=thread" + make -j$(nproc) + + # The groups holding every threaded test. A report fails the run. + - name: Run threaded test groups + run: | + TSAN_OPTIONS=halt_on_error=1 ./tests/unit.test \ + --group dtls --group dtls13 --group tls13 --group session From c2cda12c32dcb51b50c963273a04d1316b6052b2 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Thu, 12 Nov 2026 14:36:53 +0000 Subject: [PATCH 07/10] DTLS 1.3: drop held plaintext fragments once the peer's records authenticate A plaintext handshake fragment stored in the receive list, from the peer or anyone else, stayed there after the peer's records started to authenticate and could get in the way of the handshake later. Now a DTLS 1.3 endpoint drops the epoch 0 messages of its receive list when it processes an authenticated record while the list holds any. Protected messages stay. test_dtls_stale_partial_head() now expects the plaintext entry it plants far ahead in a client's receive list to be gone once the server's first authenticated record arrives. --- ChangeLog.md | 6 ++-- src/internal.c | 23 +++++++++++++++ tests/api/test_dtls.c | 5 ++-- tests/api/test_dtls13.c | 65 +++++++++++++++++++++++++++++++++++++++++ tests/api/test_dtls13.h | 2 ++ wolfssl/internal.h | 3 ++ 6 files changed, 100 insertions(+), 4 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index f44cf8972c8..dd1893dda76 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -69,8 +69,10 @@ peer whose records authenticate exceeding the budget, ends the connection. The per-message cap on fragment buckets, `WOLFSSL_DTLS_FRAG_POOL_SZ`, is checked before anything is allocated. A buffered DTLS 1.3 message older - than the next expected one is dropped instead of stalling the list, and - `wolfSSL_clear()` frees the DTLS 1.3 receive list. + than the next expected one is dropped instead of stalling the list, a + DTLS 1.3 endpoint drops the plaintext handshake fragments it holds once a + record from the peer authenticates, and `wolfSSL_clear()` frees the + DTLS 1.3 receive list. * **Behavioral change (the servers of a context share a cookie secret)**: cookie secrets were per `WOLFSSL`, so a server could not verify a cookie diff --git a/src/internal.c b/src/internal.c index f4f6e365c28..82483abe476 100644 --- a/src/internal.c +++ b/src/internal.c @@ -12022,6 +12022,23 @@ void DtlsMsgRxListFree(WOLFSSL* ssl) ssl->dtls_rx_msg_list_bytes = 0; } +#ifdef WOLFSSL_DTLS13 +/* Drop the plaintext messages of the receive list. Once a record of the peer + * authenticates, what it sent in the clear is stale. */ +void DtlsMsgRxListDropPlaintext(WOLFSSL* ssl) +{ + DtlsMsg* cur = ssl->dtls_rx_msg_list; + + while (cur != NULL) { + DtlsMsg* next = cur->next; + + if (cur->epoch == 0) + DtlsMsgListRemove(ssl, cur); + cur = next; + } +} +#endif + /* Store a fragment of message seq in the receive list. Returns 0 when it is * held, DTLS_MSG_STORE_DROPPED when not and that is no error, or an error. */ int DtlsMsgStore(WOLFSSL* ssl, word16 epoch, word32 seq, const byte* data, @@ -28805,6 +28822,12 @@ static int DoProcessReplyEx(WOLFSSL* ssl, int allowSocketErr) continue; } + /* This record authenticated: fragments the peer sent in + * the clear are stale */ + if (!w64IsZero(ssl->keys.curEpoch64) && + ssl->dtls_rx_msg_list != NULL) { + DtlsMsgRxListDropPlaintext(ssl); + } /* Only update the window once we enter stateful parsing */ if (ssl->options.dtlsStateful) { diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index 24bf1174b39..2ad340eb693 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -6849,8 +6849,9 @@ static int test_dtls_stale_partial_head(method_provider method_c, ExpectIntLE(test_ctx.s_len + 128, mtu); ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); - /* Only the entry far ahead may remain */ - ExpectIntEQ(ssl_c->dtls_rx_msg_list_sz, highSeq ? 1 : 0); + /* The partial copy is purged, and the plaintext entry far ahead goes with + * the first record of the server that authenticates */ + ExpectIntEQ(ssl_c->dtls_rx_msg_list_sz, 0); wolfSSL_free(ssl_c); wolfSSL_CTX_free(ctx_c); diff --git a/tests/api/test_dtls13.c b/tests/api/test_dtls13.c index a5b01145ea0..2a86a4bcf55 100644 --- a/tests/api/test_dtls13.c +++ b/tests/api/test_dtls13.c @@ -3964,6 +3964,71 @@ int test_dtls13_frag_ch_ctx_static_memory(void) return EXPECT_RESULT(); } +/* Once a record from the peer authenticates, the plaintext handshake fragments + * the connection still holds are stale and dropped. Protected ones stay. */ +int test_dtls13_auth_drops_plaintext_frags(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte data[16]; + byte buf[8]; + word32 seq = 0; + int i; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(data, 0x5a, sizeof(data)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + + /* Up to the server flight, with or without a cookie exchange. The server + * then waits for the client's Finished, the first record it can + * authenticate. */ + for (i = 0; i < 4 && EXPECT_SUCCESS(); i++) { + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + if (ssl_s->options.dtlsStateful && test_ctx.c_len > 0) + break; + } + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + ExpectNull(ssl_s->dtls_rx_msg_list); + + /* Two fragments ahead of the next message expected, which the list keeps: + * a plaintext one and a protected one */ + seq = ssl_s->keys.dtls_expected_peer_handshake_number + 1; + ExpectIntEQ(DtlsMsgStore(ssl_s, 0, seq, data, (word32)sizeof(data), + client_hello, 0, 8, ssl_s->heap), 0); + ExpectIntEQ(DtlsMsgStore(ssl_s, DTLS13_EPOCH_TRAFFIC0, seq + 1, data, + (word32)sizeof(data), key_update, 0, 8, ssl_s->heap), 0); + ExpectIntEQ(ssl_s->dtls_rx_msg_list_sz, 2); + + /* The client's Finished authenticates: the plaintext fragment goes */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(ssl_s->dtls_rx_msg_list_sz, 1); + ExpectNotNull(ssl_s->dtls_rx_msg_list); + if (ssl_s->dtls_rx_msg_list != NULL) { + ExpectIntEQ(ssl_s->dtls_rx_msg_list->epoch, DTLS13_EPOCH_TRAFFIC0); + ExpectIntEQ(ssl_s->dtls_rx_msg_list->seq, seq + 1); + ExpectNull(ssl_s->dtls_rx_msg_list->next); + } + + /* The connection works on */ + ExpectIntEQ(wolfSSL_write(ssl_c, data, 4), 4); + ExpectIntEQ(wolfSSL_read(ssl_s, buf, (int)sizeof(buf)), 4); + ExpectIntEQ(ssl_s->dtls_rx_msg_list_sz, 1); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + /* Which results of DtlsMsgStore() end the connection: only running out of * memory, or an authenticated peer exceeding the receive budget */ int test_dtls13_frag_store_fatal(void) diff --git a/tests/api/test_dtls13.h b/tests/api/test_dtls13.h index ead5dd6d098..279559a6451 100644 --- a/tests/api/test_dtls13.h +++ b/tests/api/test_dtls13.h @@ -66,6 +66,7 @@ int test_dtls13_frag_ch2_ctx_cookie_mismatch(void); int test_dtls13_frag_ch_ctx_set_ssl_ctx(void); int test_dtls13_frag_ch1_ctx_stale_partial(void); int test_dtls13_frag_ch_ctx_static_memory(void); +int test_dtls13_auth_drops_plaintext_frags(void); int test_dtls13_frag_store_fatal(void); int test_dtls13_clear_frees_rx_list(void); int test_dtls13_frag_ch2_bad_cookie_dropped(void); @@ -173,6 +174,7 @@ int test_dtls13_update_keys_want_write(void); TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch_ctx_set_ssl_ctx), \ TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch1_ctx_stale_partial), \ TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch_ctx_static_memory), \ + TEST_DECL_GROUP("dtls13", test_dtls13_auth_drops_plaintext_frags), \ TEST_DECL_GROUP("dtls13", test_dtls13_frag_store_fatal), \ TEST_DECL_GROUP("dtls13", test_dtls13_clear_frees_rx_list), \ TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch2_bad_cookie_dropped), \ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 8fc1bba58d4..f3de4429040 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -8319,6 +8319,9 @@ WOLFSSL_LOCAL int Tls13KeyUpdateLimitReached(WOLFSSL* ssl); WOLFSSL_LOCAL void DtlsMsgRxListInsert(WOLFSSL* ssl, DtlsMsg* msg); WOLFSSL_LOCAL DtlsMsg* DtlsMsgRxListDetach(WOLFSSL* ssl, DtlsMsg* item); WOLFSSL_LOCAL void DtlsMsgRxListFree(WOLFSSL* ssl); +#ifdef WOLFSSL_DTLS13 + WOLFSSL_LOCAL void DtlsMsgRxListDropPlaintext(WOLFSSL* ssl); +#endif #if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) WOLFSSL_TEST_VIS int DtlsChPoolInit(DtlsChPool* pool); WOLFSSL_TEST_VIS void DtlsChPoolFree(DtlsChPool* pool, void* heap); From 618822277110bd486b0de9b2d1a8192584beda79 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Thu, 12 Nov 2026 16:19:55 +0000 Subject: [PATCH 08/10] DTLS 1.3: size the ClientHello pool's budget at runtime wolfSSL_CTX_dtls13_set_ch_pool_max_bytes() sets the bytes a context holds for ClientHellos before their cookie verifies. The budget was the compile time WOLFSSL_DTLS_RX_UNVERIFIED_MAX_BYTES alone, and a server facing the internet sits at it, so sizing it meant rebuilding. The macro is now the initial value, kept in the pool as an atomic that stores read lock-free. A store reads the budget again after reserving, so one lowered meanwhile is seen. Lowering it evicts down to the new budget at once: a store evicts no more than its own bound, so stores alone would never get the pool back under a lowered budget. The example server takes --ch-pool-max-bytes. Built with NO_WOLFSSL_SERVER there is no pool to size or clear, so the setter and wolfSSL_CTX_dtls13_clear_ch_pool() return WOLFSSL_FAILURE. The budget is read with the atomic load directly and the receive list reads WOLFSSL_DTLS_RX_MAX_BYTES directly: DtlsChPoolLimit and DtlsMsgRxLimit are gone. --- ChangeLog.md | 9 +- doc/dox_comments/header_files/ssl.h | 59 ++++++++++-- examples/server/server.c | 17 ++++ src/dtls.c | 14 +-- src/dtls13.c | 24 +++++ src/internal.c | 65 ++++++++----- tests/api/test_dtls13.c | 142 +++++++++++++++++++++++++++- tests/api/test_dtls13.h | 4 + wolfssl/internal.h | 3 + wolfssl/ssl.h | 2 + 10 files changed, 297 insertions(+), 42 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index dd1893dda76..0240b944e3a 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -46,9 +46,10 @@ 5.9.4 note below that the fragments were still dropped; without `WOLFSSL_DTLS_CH_FRAG` they still are. The unverified bytes a context holds are bounded by `WOLFSSL_DTLS_RX_UNVERIFIED_MAX_BYTES`, 64 KiB by default, - metadata included: when the budget is short the least recently progressed - entry is evicted, and an entry that gains no bytes for the server's - retransmission maximum expires. The pool is split into + or the budget `wolfSSL_CTX_dtls13_set_ch_pool_max_bytes()` sets at + runtime, metadata included: when the budget is short the least recently + progressed entry is evicted, and an entry that gains no bytes for the + server's retransmission maximum expires. The pool is split into `WOLFSSL_DTLS_CH_POOL_SHARDS` lists (8; 1 with `SINGLE_THREADED` or `NO_HASH_WRAPPER`) picked by a hash of the peer address, each with its own lock. It needs atomics in a @@ -56,7 +57,7 @@ first fragment of a DTLS 1.2 ClientHello whose cookie verifies is now kept and the rest of the message taken from the pool, where before it was ignored and the client had to resend the message. The example server takes - `--reassemble-before-cookie [n]`. + `--reassemble-before-cookie [n]` and `--ch-pool-max-bytes `. * **Behavioral change (the DTLS receive list is bounded by bytes)**: the list of out-of-order and fragmented handshake messages was bounded by diff --git a/doc/dox_comments/header_files/ssl.h b/doc/dox_comments/header_files/ssl.h index af0efa526fa..b86d03629fb 100644 --- a/doc/dox_comments/header_files/ssl.h +++ b/doc/dox_comments/header_files/ssl.h @@ -16436,13 +16436,15 @@ int wolfSSL_dtls13_allow_ch_frag(WOLFSSL* ssl, int enabled); The fragments are held before the cookie verifies, so any sender can make the server hold memory, within WOLFSSL_DTLS_RX_UNVERIFIED_MAX_BYTES - (default 64 KiB) for all servers of the context. The pool belongs to the - WOLFSSL_CTX, so any of its stateless servers can complete a ClientHello - another one started. Peers are told apart by the address the I/O layer - reports or the bytes set with wolfSSL_dtls_set_peer(), an IPv4 or IPv6 - address by family, port and address. Each peer holds one ClientHello, the - newest. The least recently progressed entry is evicted first, and one that - gained no bytes for the server's retransmission maximum + (default 64 KiB, set at runtime with + wolfSSL_CTX_dtls13_set_ch_pool_max_bytes()) for all servers of the + context. The pool belongs to the WOLFSSL_CTX, so any of its stateless + servers can complete a ClientHello another one started. Peers are told + apart by the address the I/O layer reports or the bytes set with + wolfSSL_dtls_set_peer(), an IPv4 or IPv6 address by family, port and + address. Each peer holds one ClientHello, the newest. The least recently + progressed entry is evicted first, and one that gained no bytes for the + server's retransmission maximum (wolfSSL_dtls_set_timeout_max()) expires. A server that does not know the peer's address, as on a connected socket or a custom transport without wolfSSL_dtls_set_peer(), holds nothing: it drops such fragments, and still @@ -16529,7 +16531,8 @@ int wolfSSL_CTX_dtls13_reassemble_ch_before_cookie(WOLFSSL_CTX* ctx, \return WOLFSSL_SUCCESS if successful \return BAD_FUNC_ARG if ctx is NULL - \return WOLFSSL_FAILURE if the context is for clients + \return WOLFSSL_FAILURE if the context is for clients, or the build has + no server (NO_WOLFSSL_SERVER) and so no pool _Example_ \code @@ -16539,9 +16542,49 @@ int wolfSSL_CTX_dtls13_reassemble_ch_before_cookie(WOLFSSL_CTX* ctx, \endcode \sa wolfSSL_CTX_dtls13_reassemble_ch_before_cookie + \sa wolfSSL_CTX_dtls13_set_ch_pool_max_bytes */ int wolfSSL_CTX_dtls13_clear_ch_pool(WOLFSSL_CTX* ctx); +/*! + \ingroup Setup + + \brief This function sets the most bytes the context's pool holds for + ClientHellos whose cookie has not verified yet, metadata included, for + all its servers: the budget wolfSSL_dtls13_reassemble_ch_before_cookie() + describes, WOLFSSL_DTLS_RX_UNVERIFIED_MAX_BYTES (64 KiB) until set. A + server facing the internet sits at the budget, so size it for the memory + the pool may take instead of rebuilding. Lowering it evicts the least + recently progressed entries down to the new budget before returning; a + store in progress on another thread may still leave the pool over it by + that one fragment's bound. Raising it takes effect at once. The budget is + at most WC_MAX_UINT_OF(WC_ATOMIC_UINT_ARG) / 65, about 66 MB with 32-bit + atomics. Safe to call while servers of the context are running. Requires + WOLFSSL_DTLS_CH_FRAG. + + \param [in,out] ctx a pointer to a WOLFSSL_CTX structure, created using + wolfSSL_CTX_new(). + \param [in] maxBytes the budget in bytes, at least 1. + + \return WOLFSSL_SUCCESS if successful + \return BAD_FUNC_ARG if ctx is NULL, or maxBytes is 0 or above the bound + \return WOLFSSL_FAILURE if the context is for clients or not for DTLS, + or the build has no server (NO_WOLFSSL_SERVER) and so no pool + + _Example_ + \code + WOLFSSL_CTX* ctx; + ... + // hold at most 1 MiB of unverified ClientHellos + wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(ctx, 1024 * 1024); + \endcode + + \sa wolfSSL_dtls13_reassemble_ch_before_cookie + \sa wolfSSL_CTX_dtls13_clear_ch_pool +*/ +int wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(WOLFSSL_CTX* ctx, + size_t maxBytes); + /*! \ingroup Setup diff --git a/examples/server/server.c b/examples/server/server.c index 2f4be03dfce..e4d6a962727 100644 --- a/examples/server/server.c +++ b/examples/server/server.c @@ -1438,6 +1438,8 @@ static void Usage(void) printf("--reassemble-before-cookie [n] DTLS 1.3: reassemble a fragmented\n" " ClientHello before its cookie is verified\n" " (default on, n to disable)\n"); + printf("--ch-pool-max-bytes DTLS 1.3: bytes the context holds for\n" + " ClientHellos before their cookie is verified\n"); #endif } @@ -1570,6 +1572,7 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args) #endif #ifdef WOLFSSL_DTLS_CH_FRAG { "reassemble-before-cookie", 2, 272 }, /* optional argument */ + { "ch-pool-max-bytes", 1, 273 }, #endif { 0, 0, 0 } }; @@ -1690,6 +1693,7 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args) #endif #ifdef WOLFSSL_DTLS_CH_FRAG int reassembleBeforeCookie = -1; /* -1 keeps the library default */ + int chPoolMaxBytes = 0; /* 0 keeps the library default */ #endif byte mcastID = 0; #if !defined(NO_DH) && !defined(HAVE_FIPS) && \ @@ -2471,6 +2475,11 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args) if (myoptarg != NULL && XSTRCMP(myoptarg, "n") == 0) reassembleBeforeCookie = 0; break; + case 273: + chPoolMaxBytes = atoi(myoptarg); + if (chPoolMaxBytes <= 0) + err_sys("--ch-pool-max-bytes takes a positive number"); + break; #endif /* WOLFSSL_DTLS_CH_FRAG */ case 264: #ifdef HAVE_SUPPORTED_CURVES @@ -2884,6 +2893,14 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args) wolfSSL_CTX_dtls_set_sctp(ctx); #endif +#ifdef WOLFSSL_DTLS_CH_FRAG + if (doDTLS && chPoolMaxBytes != 0 && + wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(ctx, + (size_t)chPoolMaxBytes) != WOLFSSL_SUCCESS) { + err_sys("unable to set the ClientHello pool budget"); + } +#endif + #ifdef WOLFSSL_ENCRYPTED_KEYS wolfSSL_CTX_set_default_passwd_cb(ctx, PasswordCallBack); #endif diff --git a/src/dtls.c b/src/dtls.c index 8e58c7be8b1..90b258123e6 100644 --- a/src/dtls.c +++ b/src/dtls.c @@ -45,12 +45,14 @@ * on the complete message (RFC 9147 Section 5.5: a receiver must buffer or * process a fragment). Unverified fragments are held in a pool per * WOLFSSL_CTX, keyed on the remote address and bounded by - * WOLFSSL_DTLS_RX_UNVERIFIED_MAX_BYTES, so any stateless server of the - * context can complete them. A server without the peer's address holds - * nothing. On by default: wolfSSL_CTX_dtls13_reassemble_ch_before_cookie() - * turns the holding off, wolfSSL_dtls13_allow_ch_frag() the reassembly, - * and wolfSSL_CTX_dtls13_clear_ch_pool() empties the pool. The pool - * needs a clock, so not NO_ASN_TIME, and atomics in a threaded build. + * WOLFSSL_DTLS_RX_UNVERIFIED_MAX_BYTES, or the budget + * wolfSSL_CTX_dtls13_set_ch_pool_max_bytes() sets at runtime, so any + * stateless server of the context can complete them. A server without + * the peer's address holds nothing. On by default: + * wolfSSL_CTX_dtls13_reassemble_ch_before_cookie() turns the holding off, + * wolfSSL_dtls13_allow_ch_frag() the reassembly, and + * wolfSSL_CTX_dtls13_clear_ch_pool() empties the pool. The pool needs a + * clock, so not NO_ASN_TIME, and atomics in a threaded build. * Client: send an empty key share when the first ClientHello would not fit * one datagram, at the cost of a HelloRetryRequest round trip. * With the cookie exchange disabled the server is stateful from the first diff --git a/src/dtls13.c b/src/dtls13.c index 4dd8b365da2..5ade515eb07 100644 --- a/src/dtls13.c +++ b/src/dtls13.c @@ -4043,8 +4043,32 @@ int wolfSSL_CTX_dtls13_clear_ch_pool(WOLFSSL_CTX* ctx) return WOLFSSL_FAILURE; #ifndef NO_WOLFSSL_SERVER DtlsChPoolClear(&ctx->dtlsChPool, ctx->heap); + return WOLFSSL_SUCCESS; +#else + /* No server, no pool to clear */ + return WOLFSSL_FAILURE; #endif +} + +int wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(WOLFSSL_CTX* ctx, + size_t maxBytes) +{ + if (ctx == NULL) + return BAD_FUNC_ARG; + if (ctx->method->side == WOLFSSL_CLIENT_END) + return WOLFSSL_FAILURE; +#ifndef NO_WOLFSSL_SERVER + /* Only a DTLS context has a pool */ + if (!ctx->dtlsChPool.ready) + return WOLFSSL_FAILURE; + if (DtlsChPoolSetMaxBytes(&ctx->dtlsChPool, ctx->heap, maxBytes) != 0) + return BAD_FUNC_ARG; return WOLFSSL_SUCCESS; +#else + /* No server, no pool to size */ + (void)maxBytes; + return WOLFSSL_FAILURE; +#endif } #endif diff --git a/src/internal.c b/src/internal.c index 82483abe476..ac2df6a76ce 100644 --- a/src/internal.c +++ b/src/internal.c @@ -11985,20 +11985,12 @@ static size_t DtlsMsgProjectedBytes(const DtlsMsg* msg, word32 offset, return bytes + sizeof(DtlsFragBucket) + (end - offset); } -/* The retained bytes a receive list, or the context's ClientHello pool when - * unverified, may reach when admitting an entry. */ -static size_t DtlsMsgRxLimit(int unverified) -{ - return unverified ? WOLFSSL_DTLS_RX_UNVERIFIED_MAX_BYTES : - WOLFSSL_DTLS_RX_MAX_BYTES; -} - /* Whether the receive list can grow to hold current, or a new entry, at * projected retained bytes. */ static int DtlsMsgRxAdmit(WOLFSSL* ssl, DtlsMsg* current, size_t projected) { size_t old = current == NULL ? 0 : DtlsMsgRetainedBytes(current); - size_t limit = DtlsMsgRxLimit(0); + size_t limit = WOLFSSL_DTLS_RX_MAX_BYTES; if (projected > limit || ssl->dtls_rx_msg_list_bytes - old > limit - projected) @@ -12161,8 +12153,9 @@ static void DtlsChShardUnlock(DtlsChPoolShard* sh) /* A store counts the bound of its fragment before it evicts for it, so * reserved may pass the budget by one bound per store evicting at once. A - * bound is below the budget, by the check before it: room for 64. Progress - * wraps as word32. */ + * bound is below the budget, by the check before it: room for 64. The + * default budget and any set at runtime keep to this. Progress wraps as + * word32. */ wc_static_assert(WC_MAX_UINT_OF(WC_ATOMIC_UINT_ARG) >= WOLFSSL_MAX_32BIT); wc_static_assert(WOLFSSL_DTLS_RX_UNVERIFIED_MAX_BYTES <= WC_MAX_UINT_OF(WC_ATOMIC_UINT_ARG) / 65); @@ -12171,8 +12164,10 @@ wc_static_assert(WOLFSSL_DTLS_RX_UNVERIFIED_MAX_BYTES <= * taken back when the sum passes the budget. */ static int DtlsChPoolAdd(DtlsChPool* pool, size_t n) { - if ((size_t)wolfSSL_Atomic_Uint_AddFetch(&pool->reserved, - (WC_ATOMIC_UINT_ARG)n) <= DtlsMsgRxLimit(1)) + size_t sum = (size_t)wolfSSL_Atomic_Uint_AddFetch(&pool->reserved, + (WC_ATOMIC_UINT_ARG)n); + + if (sum <= (size_t)WOLFSSL_ATOMIC_LOAD(pool->maxBytes)) return 1; (void)wolfSSL_Atomic_Uint_SubFetch(&pool->reserved, (WC_ATOMIC_UINT_ARG)n); return 0; @@ -12205,7 +12200,8 @@ static int DtlsChPoolSettle(DtlsChPool* pool, DtlsMsg* msg, size_t counted) (WC_ATOMIC_UINT_ARG)(counted - actual)); return 0; } - if (actual <= DtlsMsgRxLimit(1) && DtlsChPoolAdd(pool, actual - counted)) + if (actual <= (size_t)WOLFSSL_ATOMIC_LOAD(pool->maxBytes) && + DtlsChPoolAdd(pool, actual - counted)) return 0; (void)wolfSSL_Atomic_Uint_SubFetch(&pool->reserved, (WC_ATOMIC_UINT_ARG)counted); @@ -12325,11 +12321,11 @@ static DtlsChPoolShard* DtlsChPoolFindOldest(DtlsChPool* pool, } /* Free need bytes, or until reserved is within the budget, by evicting the - * oldest entries of any shard, never keep's. The shard holding keep's entry - * is learned when the scan lands on it: the entry behind it goes then, as - * the oldest of that shard but keep's, and the shard is judged by the entry - * behind its head from then on. The caller holds no shard lock: victims' - * shards are, and two at once deadlock. */ + * oldest entries of any shard, never keep's; any, when keep is NULL. The + * shard holding keep's entry is learned when the scan lands on it: the entry + * behind it goes then, as the oldest of that shard but keep's, and the shard + * is judged by the entry behind its head from then on. The caller holds no + * shard lock: victims' shards are, and two at once deadlock. */ static int DtlsChPoolEvict(DtlsChPool* pool, void* heap, const WOLFSSL_SOCKADDR* keep, size_t need) { @@ -12337,8 +12333,8 @@ static int DtlsChPoolEvict(DtlsChPool* pool, void* heap, size_t freed = 0; int misses = 0; - while (freed < need && - (size_t)WOLFSSL_ATOMIC_LOAD(pool->reserved) > DtlsMsgRxLimit(1)) { + while (freed < need && (size_t)WOLFSSL_ATOMIC_LOAD(pool->reserved) > + (size_t)WOLFSSL_ATOMIC_LOAD(pool->maxBytes)) { DtlsChPoolShard* best; DtlsMsg* victim; word32 oldest = 0; @@ -12347,7 +12343,8 @@ static int DtlsChPoolEvict(DtlsChPool* pool, void* heap, if (best == NULL || DtlsChShardLock(best) != 0) return DTLS_MSG_STORE_DROPPED; victim = best->head; - if (victim != NULL && DtlsMsgPeerEqual(victim, keep)) { + if (victim != NULL && keep != NULL && + DtlsMsgPeerEqual(victim, keep)) { skip = best; victim = victim->next; } @@ -12391,6 +12388,8 @@ int DtlsChPoolInit(DtlsChPool* pool) XMEMSET(pool, 0, sizeof(*pool)); wolfSSL_Atomic_Uint_Init(&pool->reserved, 0U); wolfSSL_Atomic_Uint_Init(&pool->lastProgress, 0U); + wolfSSL_Atomic_Uint_Init(&pool->maxBytes, + (WC_ATOMIC_UINT_ARG)WOLFSSL_DTLS_RX_UNVERIFIED_MAX_BYTES); for (i = 0; i < WOLFSSL_DTLS_CH_POOL_SHARDS; i++) { wolfSSL_Atomic_Uint_Init(&pool->shard[i].headProgress, 0U); wolfSSL_Atomic_Uint_Init(&pool->shard[i].nextProgress, 0U); @@ -12454,6 +12453,23 @@ void DtlsChPoolClear(DtlsChPool* pool, void* heap) } } +/* Set the budget, within the bound the static assert above puts on the + * default, and evict down to it. Stores in flight may still leave reserved + * above it by their bounds: they evict for their own at once. */ +int DtlsChPoolSetMaxBytes(DtlsChPool* pool, void* heap, size_t maxBytes) +{ + size_t over; + + if (pool == NULL || !pool->ready || maxBytes == 0 || + maxBytes > WC_MAX_UINT_OF(WC_ATOMIC_UINT_ARG) / 65) + return BAD_FUNC_ARG; + WOLFSSL_ATOMIC_STORE(pool->maxBytes, (WC_ATOMIC_UINT_ARG)maxBytes); + over = (size_t)WOLFSSL_ATOMIC_LOAD(pool->reserved); + if (over > maxBytes) + (void)DtlsChPoolEvict(pool, heap, NULL, over - maxBytes); + return 0; +} + /* Hand msg, allocated with from, to an owner that frees it with to: rebuilt * only when the heaps differ, without its peer key. On failure *msg is NULL. */ int DtlsMsgAdopt(DtlsMsg** msg, void* from, void* to) @@ -12528,7 +12544,7 @@ int DtlsChPoolStore(DtlsChPool* pool, void* heap, DtlsMsg* cur = NULL; DtlsMsg* gone = NULL; DtlsMsg* detached = NULL; - size_t limit = DtlsMsgRxLimit(1); + size_t limit; size_t overhead; size_t before; size_t reserve; /* the bound of the fragment, held in pool->reserved */ @@ -12553,6 +12569,7 @@ int DtlsChPoolStore(DtlsChPool* pool, void* heap, /* A ClientHello that can never complete is dropped before anything is * evicted: its last fragment is reserved as a new entry on top of the * entry holding the rest, so the metadata is counted twice. */ + limit = (size_t)WOLFSSL_ATOMIC_LOAD(pool->maxBytes); if (peer->sz > limit) return DTLS_MSG_STORE_DROPPED; overhead = sizeof(DtlsMsg) + sizeof(DtlsFragBucket) + peer->sz; @@ -12565,6 +12582,8 @@ int DtlsChPoolStore(DtlsChPool* pool, void* heap, reserve = overhead + fragSz; sum = (size_t)wolfSSL_Atomic_Uint_AddFetch(&pool->reserved, (WC_ATOMIC_UINT_ARG)reserve); + /* Read again after reserving, so a budget lowered meanwhile is seen */ + limit = (size_t)WOLFSSL_ATOMIC_LOAD(pool->maxBytes); if (sum > limit) { size_t need = sum - limit; diff --git a/tests/api/test_dtls13.c b/tests/api/test_dtls13.c index 2a86a4bcf55..33865d771ba 100644 --- a/tests/api/test_dtls13.c +++ b/tests/api/test_dtls13.c @@ -534,7 +534,7 @@ static int test_dtls13_pool_ok(const DtlsChPool* p) return 0; } return test_dtls13_pool_reserved(p) == test_dtls13_pool_bytes(p) && - test_dtls13_pool_bytes(p) <= TEST_DTLS_RX_UNVERIFIED_LIMIT; + test_dtls13_pool_bytes(p) <= (size_t)WOLFSSL_ATOMIC_LOAD(p->maxBytes); } /* Make m look as if it gained no bytes for longer than the pool keeps it */ @@ -6004,6 +6004,146 @@ int test_dtls13_ch_pool_ctx_init(void) return EXPECT_RESULT(); } +/* The budget setter checks its arguments and takes only a DTLS server + * context, whose pool starts at the default */ +int test_dtls13_ch_pool_max_bytes_api(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX* dtls = NULL; + WOLFSSL_CTX* tls = NULL; +#ifndef NO_WOLFSSL_CLIENT + WOLFSSL_CTX* client = NULL; +#endif + size_t bound = WC_MAX_UINT_OF(WC_ATOMIC_UINT_ARG) / 65; + + ExpectNotNull(dtls = wolfSSL_CTX_new(wolfDTLSv1_3_server_method())); + ExpectNotNull(tls = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + ExpectIntEQ(wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(NULL, 1024), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(dtls, 0), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(dtls, bound + 1), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* A context without a pool has nothing to size */ + ExpectIntEQ(wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(tls, 1024), + WOLFSSL_FAILURE); +#ifndef NO_WOLFSSL_CLIENT + ExpectNotNull(client = wolfSSL_CTX_new(wolfDTLSv1_3_client_method())); + ExpectIntEQ(wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(client, 1024), + WOLFSSL_FAILURE); + wolfSSL_CTX_free(client); +#endif + if (EXPECT_SUCCESS()) { + ExpectTrue((size_t)WOLFSSL_ATOMIC_LOAD(dtls->dtlsChPool.maxBytes) == + TEST_DTLS_RX_UNVERIFIED_LIMIT); + ExpectIntEQ(wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(dtls, 1024), + WOLFSSL_SUCCESS); + ExpectTrue((size_t)WOLFSSL_ATOMIC_LOAD(dtls->dtlsChPool.maxBytes) == + 1024); + ExpectIntEQ(wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(dtls, bound), + WOLFSSL_SUCCESS); + ExpectTrue((size_t)WOLFSSL_ATOMIC_LOAD(dtls->dtlsChPool.maxBytes) == + bound); + ExpectTrue(test_dtls13_pool_ok(&dtls->dtlsChPool)); + } + wolfSSL_CTX_free(tls); + wolfSSL_CTX_free(dtls); +#elif defined(WOLFSSL_DTLS_CH_FRAG) && defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_WOLFSSL_CLIENT) + /* Without a server there is no pool to size or clear */ + WOLFSSL_CTX* client = NULL; + + ExpectNotNull(client = wolfSSL_CTX_new(wolfDTLSv1_3_client_method())); + ExpectIntEQ(wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(NULL, 1024), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(client, 1024), + WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_CTX_dtls13_clear_ch_pool(NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_CTX_dtls13_clear_ch_pool(client), WOLFSSL_FAILURE); + wolfSSL_CTX_free(client); +#endif + return EXPECT_RESULT(); +} + +/* The budget set at runtime: with room for two entries a third store evicts + * the oldest; raised, more get in; lowered below what is held, the oldest go + * at once, down to it, and the next store evicts for its own again */ +int test_dtls13_ch_pool_max_bytes(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) + WOLFSSL_CTX* ctx = NULL; + DtlsChPool* pool = NULL; + byte data[32]; + const word32 msgSz = 32; + const word32 first = 16; + size_t entry = sizeof(DtlsMsg) + sizeof(int) + sizeof(DtlsFragBucket) + + first; + int i; + + XMEMSET(data, 0x5a, sizeof(data)); + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLSv1_3_server_method())); + if (EXPECT_FAIL()) + return EXPECT_RESULT(); + pool = &ctx->dtlsChPool; + + ExpectIntEQ(wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(ctx, 2 * entry), + WOLFSSL_SUCCESS); + for (i = 1; i <= 3; i++) + ExpectIntEQ(test_dtls13_pool_put(pool, i, data, msgSz, 0, first), 0); + ExpectIntEQ(test_dtls13_pool_count(pool), 2); + ExpectNull(test_dtls13_held_for(pool, 1)); + ExpectNotNull(test_dtls13_held_for(pool, 2)); + ExpectNotNull(test_dtls13_held_for(pool, 3)); + ExpectTrue(test_dtls13_pool_bytes(pool) == 2 * entry); + ExpectTrue(test_dtls13_pool_ok(pool)); + + /* Raised: two more fit, then the oldest goes again */ + ExpectIntEQ(wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(ctx, 4 * entry), + WOLFSSL_SUCCESS); + for (i = 4; i <= 5; i++) + ExpectIntEQ(test_dtls13_pool_put(pool, i, data, msgSz, 0, first), 0); + ExpectIntEQ(test_dtls13_pool_count(pool), 4); + ExpectNotNull(test_dtls13_held_for(pool, 2)); + ExpectIntEQ(test_dtls13_pool_put(pool, 6, data, msgSz, 0, first), 0); + ExpectIntEQ(test_dtls13_pool_count(pool), 4); + ExpectNull(test_dtls13_held_for(pool, 2)); + ExpectNotNull(test_dtls13_held_for(pool, 6)); + ExpectTrue(test_dtls13_pool_bytes(pool) == 4 * entry); + ExpectTrue(test_dtls13_pool_ok(pool)); + + /* Lowered: the oldest go at once, as many as the budget asks */ + ExpectIntEQ(wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(ctx, 3 * entry), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_pool_count(pool), 3); + ExpectNull(test_dtls13_held_for(pool, 3)); + ExpectTrue(test_dtls13_pool_ok(pool)); + ExpectIntEQ(wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(ctx, 2 * entry), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_pool_count(pool), 2); + ExpectNull(test_dtls13_held_for(pool, 4)); + ExpectNotNull(test_dtls13_held_for(pool, 5)); + ExpectNotNull(test_dtls13_held_for(pool, 6)); + ExpectTrue(test_dtls13_pool_bytes(pool) == 2 * entry); + ExpectTrue(test_dtls13_pool_ok(pool)); + /* The same budget again evicts nothing */ + ExpectIntEQ(wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(ctx, 2 * entry), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_dtls13_pool_count(pool), 2); + /* The next store evicts the oldest for its own */ + ExpectIntEQ(test_dtls13_pool_put(pool, 7, data, msgSz, 0, first), 0); + ExpectIntEQ(test_dtls13_pool_count(pool), 2); + ExpectNull(test_dtls13_held_for(pool, 5)); + ExpectNotNull(test_dtls13_held_for(pool, 7)); + ExpectTrue(test_dtls13_pool_ok(pool)); + + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + /* An entry expires after the storing server's retransmission maximum, not a * fixed time. The rest taken for a verified first fragment ages the same. */ int test_dtls13_ch_pool_max_age(void) diff --git a/tests/api/test_dtls13.h b/tests/api/test_dtls13.h index 279559a6451..4900ece5a4f 100644 --- a/tests/api/test_dtls13.h +++ b/tests/api/test_dtls13.h @@ -90,6 +90,8 @@ int test_dtls13_ch_pool_bucket_cap(void); int test_dtls13_ch_pool_peer_key(void); int test_dtls13_ch_pool_max_age(void); int test_dtls13_ch_pool_ctx_init(void); +int test_dtls13_ch_pool_max_bytes_api(void); +int test_dtls13_ch_pool_max_bytes(void); int test_dtls_empty_keyshare_with_cookie(void); int test_dtls13_missing_finished_client(void); int test_dtls13_missing_finished_server(void); @@ -153,6 +155,8 @@ int test_dtls13_update_keys_want_write(void); TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_peer_key), \ TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_max_age), \ TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_ctx_init), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_max_bytes_api), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_max_bytes), \ TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch1_over_bound), \ TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch1_length_mismatch), \ TEST_DECL_GROUP("dtls13", test_dtls13_frag_past_end), \ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index f3de4429040..78bb6d179c8 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -3125,6 +3125,7 @@ typedef struct DtlsChPool { wolfSSL_Atomic_Uint reserved; /* shard bytes plus a bound per store in * progress, evicted for before it locks */ wolfSSL_Atomic_Uint lastProgress; /* last progress value given out */ + wolfSSL_Atomic_Uint maxBytes; /* the budget, read lock-free by stores */ byte ready; /* initialized: only a DTLS context's is */ } DtlsChPool; #endif @@ -8337,6 +8338,8 @@ WOLFSSL_LOCAL int Tls13KeyUpdateLimitReached(WOLFSSL* ssl); word32 fragSz, word32 msgSz, word32 maxAge, void* outHeap); WOLFSSL_TEST_VIS word32 DtlsChPoolShardIdx(const WOLFSSL_SOCKADDR* peer); WOLFSSL_TEST_VIS int DtlsMsgAdopt(DtlsMsg** msg, void* from, void* to); + WOLFSSL_LOCAL int DtlsChPoolSetMaxBytes(DtlsChPool* pool, void* heap, + size_t maxBytes); #endif WOLFSSL_TEST_VIS int DtlsMsgStoreFatal(WOLFSSL* ssl, int ret); WOLFSSL_LOCAL DtlsMsg* DtlsMsgInsert(DtlsMsg* head, DtlsMsg* item); diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index b52e31f33fe..74fcf69a79b 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -6731,6 +6731,8 @@ WOLFSSL_API const unsigned char* wolfSSL_dtls_cid_parse(const unsigned char* msg WOLFSSL_API int wolfSSL_CTX_dtls13_reassemble_ch_before_cookie( WOLFSSL_CTX* ctx, int enabled); WOLFSSL_API int wolfSSL_CTX_dtls13_clear_ch_pool(WOLFSSL_CTX* ctx); + WOLFSSL_API int wolfSSL_CTX_dtls13_set_ch_pool_max_bytes(WOLFSSL_CTX* ctx, + size_t maxBytes); #endif #ifdef WOLFSSL_DTLS13_NO_HRR_ON_RESUME WOLFSSL_API int wolfSSL_dtls13_no_hrr_on_resume(WOLFSSL *ssl, int enabled); From e6c15ed0ae1d33ff4d74051a84717ecd2704d092 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Thu, 12 Nov 2026 16:20:40 +0000 Subject: [PATCH 09/10] DTLS 1.3: check the cookie on the pooled ClientHello's head as it grows A server that pools ClientHellos, reassembly before the cookie on and a peer address known, now sends every fragment of an unverified ClientHello to the context's pool, the first included. DtlsChPoolStore() runs the cookie check in place on the entry's contiguous head, with the shard locked, once the head grew to a size that at least doubles each time, so a message is parsed O(log n) times whatever its fragments. A verified entry leaves the pool in the same step and moves whole, later fragments included, to the connection's receive list; a bad cookie frees the entry and the server answers illegal_parameter; no cookie, or a head that ends before it, keeps pooling. A message that completes in the pool goes through the whole-message path as before. A server that pools nothing, reassembly off or no address, still checks the cookie on a first fragment as it arrives and keeps it. The stateless check is split: ClientHelloStatelessVerdict() reads the object only and returns the verdict, ClientHelloStatelessCommit() turns the object stateful and marks the replay window. DoClientHelloStateless() does both for a whole message, with the stateless reply when there is no cookie. DoClientHelloStatelessHead() is the verdict on a prefix, for the pool: const object, no I/O, no pool access, and only the cookie secret's leaf lock, so the lock order is shard lock, then cookie secret lock. The caller commits once the pool has handed the entry out. DtlsChPoolTakeRest and the trimming of the verified range go. --- ChangeLog.md | 25 +- doc/dox_comments/header_files/ssl.h | 49 +- src/dtls.c | 185 +++--- src/dtls13.c | 146 +++-- src/internal.c | 106 ++-- tests/api/test_dtls13.c | 938 +++++++++++++++++++++++----- tests/api/test_dtls13.h | 12 + wolfssl/internal.h | 20 +- 8 files changed, 1115 insertions(+), 366 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 0240b944e3a..2e3b78b8a4d 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -33,11 +33,15 @@ fragment of a first ClientHello, so a client whose ClientHello does not fit one datagram (a post-quantum key share, a small MTU) failed in silence. In a `WOLFSSL_DTLS_CH_FRAG` build (`--enable-dtls-frag-ch`, automatic with ML-KEM) - the fragments are now held in a pool owned by the `WOLFSSL_CTX` until the - ClientHello is complete, and the cookie check runs on the whole message; no - key, group or transcript state is kept until a cookie verifies. Any - stateless server made from the context can complete a ClientHello another - one started. This is on by default: the new + the fragments are now held in a pool owned by the `WOLFSSL_CTX`, keyed on + the peer address, and the cookie check runs in place on the contiguous + head of the held message, under the pool's lock, as the head grows to + sizes that at least double each time, and on the complete message; no key, + group or transcript state is kept until a cookie verifies, and once it does + the whole entry, later fragments included, leaves the pool for the server + that verified it in the same step. Any stateless server made + from the context can complete a ClientHello another one started. This is + on by default: the new `wolfSSL_CTX_dtls13_reassemble_ch_before_cookie()` and `wolfSSL_dtls13_reassemble_ch_before_cookie()` turn it off, and `wolfSSL_CTX_dtls13_clear_ch_pool()` frees what is held. Fragmented @@ -53,11 +57,12 @@ `WOLFSSL_DTLS_CH_POOL_SHARDS` lists (8; 1 with `SINGLE_THREADED` or `NO_HASH_WRAPPER`) picked by a hash of the peer address, each with its own lock. It needs atomics in a - threaded build and a clock in any (`NO_ASN_TIME` is not supported). The - first fragment of a DTLS 1.2 ClientHello whose cookie verifies is now kept - and the rest of the message taken from the pool, where before it was - ignored and the client had to resend the message. The example server takes - `--reassemble-before-cookie [n]` and `--ch-pool-max-bytes `. + threaded build and a clock in any (`NO_ASN_TIME` is not supported). A + server that holds nothing, with the reassembly off or without the peer's + address, still checks the cookie on a first fragment as it arrives and + keeps that fragment, a DTLS 1.2 ClientHello's included, where before it + was ignored and the client had to resend the message. The example server + takes `--reassemble-before-cookie [n]` and `--ch-pool-max-bytes `. * **Behavioral change (the DTLS receive list is bounded by bytes)**: the list of out-of-order and fragmented handshake messages was bounded by diff --git a/doc/dox_comments/header_files/ssl.h b/doc/dox_comments/header_files/ssl.h index b86d03629fb..60e961b67ff 100644 --- a/doc/dox_comments/header_files/ssl.h +++ b/doc/dox_comments/header_files/ssl.h @@ -11073,7 +11073,11 @@ int wolfSSL_DTLS_SetCookieSecretSecondary(WOLFSSL* ssl, before the change fails that handshake and retries. Use wolfSSL_CTX_rotate_cookie_secret() to keep them verifying. Does not change the secondary secret and does not turn cookies on or off. Safe to - call while other threads use objects of the context. + call while other threads use objects of the context. Every cookie + carries its issue time under the MAC and stops verifying + WOLFSSL_COOKIE_MAX_AGE seconds later (60 by default), so a captured + cookie cannot be replayed later and the secret need not be rotated for + that. \param [in,out] ctx a pointer to a WOLFSSL_CTX structure, created using wolfSSL_CTX_new(). @@ -11164,7 +11168,10 @@ int wolfSSL_CTX_set_cookie_secret_secondary(WOLFSSL_CTX* ctx, no primary yet, it sets the primary and clears the secondary. Rotate less often than the longest time a client may take between the HelloRetryRequest or HelloVerifyRequest and its second ClientHello. Safe - to call while other threads use objects of the context. + to call while other threads use objects of the context. A cookie expires + WOLFSSL_COOKIE_MAX_AGE seconds (60 by default) after it was issued + whichever secret made it, so rotating is not needed against replay of a + captured cookie. \param [in,out] ctx a pointer to a WOLFSSL_CTX structure, created using wolfSSL_CTX_new(). @@ -16251,7 +16258,11 @@ int wolfSSL_connect(WOLFSSL* ssl); exchange is enabled by default. The Cookie holds a hash of the current transcript so that another server process can handle the ClientHello in reply. The secret is used when generating the integrity check on the Cookie - data. This replaces or regenerates the HRR secret, then delegates to + data. The cookie also carries its issue time and is rejected, as a bad + cookie is, WOLFSSL_COOKIE_MAX_AGE seconds (60 by default) after it was + issued, so a captured cookie cannot be replayed later. Without a clock + (NO_ASN_TIME) no time is sent or checked. + This replaces or regenerates the HRR secret, then delegates to wolfSSL_enable_cookie(). Changing cookie mode after handshake processing starts is unsupported; rotating a secret while cookies are already enabled remains supported. DTLS 1.2-only callers @@ -16427,12 +16438,17 @@ int wolfSSL_dtls13_allow_ch_frag(WOLFSSL* ssl, int enabled); \ingroup Setup \brief Lets a DTLS 1.3 server reassemble a fragmented ClientHello from a - peer whose cookie is not verified yet and run the cookie check on the - complete message. On by default with WOLFSSL_DTLS_CH_FRAG, and in effect - only while wolfSSL_dtls13_allow_ch_frag() is on. RFC 9147 Section 5.5 - requires a receiver to buffer or process a fragment: with this off the - server drops every fragmented first ClientHello, such as one carrying a - post-quantum key share. + peer whose cookie is not verified yet. The cookie check runs in place on + the contiguous head of the held message, under the pool's lock, as the + head grows to sizes that at least double each time, and on the complete + message: once it verifies the whole entry, later fragments included, + leaves the pool for the server in the same step, and the server is + stateful from then on. On by default with WOLFSSL_DTLS_CH_FRAG, and in + effect only while wolfSSL_dtls13_allow_ch_frag() is on. RFC 9147 Section + 5.5 requires a receiver to buffer or process a fragment: with this off + the server holds nothing before a cookie verifies, so it checks the + cookie on a first fragment as it arrives and drops every other fragment + of a first ClientHello, such as one carrying a post-quantum key share. The fragments are held before the cookie verifies, so any sender can make the server hold memory, within WOLFSSL_DTLS_RX_UNVERIFIED_MAX_BYTES @@ -16447,15 +16463,18 @@ int wolfSSL_dtls13_allow_ch_frag(WOLFSSL* ssl, int enabled); server's retransmission maximum (wolfSSL_dtls_set_timeout_max()) expires. A server that does not know the peer's address, as on a connected socket or a custom transport without - wolfSSL_dtls_set_peer(), holds nothing: it drops such fragments, and still - answers a whole ClientHello or one whose cookie is in its first fragment. + wolfSSL_dtls_set_peer(), holds nothing either: it drops such fragments, + and still answers a whole ClientHello or one whose cookie is in its + first fragment. A server that completes another server's second ClientHello verifies the cookie only when they share the cookie secret: both use the context's (wolfSSL_CTX_set_cookie_secret(), the default) or each was given the - same secret; otherwise the client gets an illegal_parameter alert. Use one context per - local endpoint if clients may reuse a source port across endpoints. - Allocator, logging and clock callbacks may run while the pool is locked - and must not call into wolfSSL. Requires WOLFSSL_DTLS_CH_FRAG. + same secret; otherwise the client gets an illegal_parameter alert. Use + one context per local endpoint if clients may reuse a source port across + endpoints. + Allocator, logging, clock and crypto callbacks may run while the pool is + locked, the cookie check included, and must not call into wolfSSL. + Requires WOLFSSL_DTLS_CH_FRAG. \param [in,out] ssl a pointer to a WOLFSSL structure, created using wolfSSL_new(). diff --git a/src/dtls.c b/src/dtls.c index 90b258123e6..12a3801dd9b 100644 --- a/src/dtls.c +++ b/src/dtls.c @@ -41,14 +41,19 @@ * - Continue like with a normal connection * WOLFSSL_DTLS_CH_FRAG * DTLS 1.3 ClientHello fragmentation. Server: reassemble a fragmented - * ClientHello, before its cookie is verified too, and run the cookie check - * on the complete message (RFC 9147 Section 5.5: a receiver must buffer or - * process a fragment). Unverified fragments are held in a pool per - * WOLFSSL_CTX, keyed on the remote address and bounded by - * WOLFSSL_DTLS_RX_UNVERIFIED_MAX_BYTES, or the budget + * ClientHello, before its cookie is verified too (RFC 9147 Section 5.5: a + * receiver must buffer or process a fragment). Unverified fragments are + * held in a pool per WOLFSSL_CTX, keyed on the remote address and bounded + * by WOLFSSL_DTLS_RX_UNVERIFIED_MAX_BYTES, or the budget * wolfSSL_CTX_dtls13_set_ch_pool_max_bytes() sets at runtime, so any - * stateless server of the context can complete them. A server without - * the peer's address holds nothing. On by default: + * stateless server of the context can complete them. The cookie check + * runs in place on the contiguous head of the held message, under the + * pool's lock, as the head grows to sizes that at least double each + * time, so the server turns stateful as soon as the head reaches the + * cookie and the whole entry, later fragments included, leaves the pool + * for its receive list in the same step. A server without the peer's + * address holds nothing: it checks the cookie on a first fragment as it + * arrives and keeps only that one. On by default: * wolfSSL_CTX_dtls13_reassemble_ch_before_cookie() turns the holding off, * wolfSSL_dtls13_allow_ch_frag() the reassembly, and * wolfSSL_CTX_dtls13_clear_ch_pool() empties the pool. The pool needs a @@ -1054,14 +1059,18 @@ static int ClientHelloSanityCheck(WolfSSL_CH* ch, byte isTls13) return 0; } -static int _DoClientHelloStateless(WOLFSSL* ssl, const byte* input, - word32 helloSz, byte isFirstCHFrag) +/* The stateless verdict on the first helloSz bytes of a ClientHello, a + * prefix when isFirstCHFrag. Reads ssl only; ch holds what was parsed. + * 0: cookie verified. COOKIE_ERROR: no cookie, or a bad DTLS 1.2 one. + * INVALID_PARAMETER: a bad DTLS 1.3 cookie. BUFFER_ERROR: the prefix ends + * before a field checked. */ +static int ClientHelloStatelessVerdict(const WOLFSSL* ssl, const byte* input, + word32 helloSz, byte isFirstCHFrag, WolfSSL_CH* ch, byte* isTls13) { int ret; - WolfSSL_CH ch; - byte isTls13 = 0; - WOLFSSL_ENTER("DoClientHelloStateless"); + WOLFSSL_ENTER("ClientHelloStatelessVerdict"); + *isTls13 = 0; if (isFirstCHFrag) { #ifdef WOLFSSL_DTLS_CH_FRAG WOLFSSL_MSG("\tProcessing fragmented ClientHello"); @@ -1071,21 +1080,20 @@ static int _DoClientHelloStateless(WOLFSSL* ssl, const byte* input, return BAD_STATE_E; #endif } - XMEMSET(&ch, 0, sizeof(ch)); + XMEMSET(ch, 0, sizeof(*ch)); - ssl->options.dtlsStateful = 0; - ret = ParseClientHello(input, helloSz, &ch, isFirstCHFrag); + ret = ParseClientHello(input, helloSz, ch, isFirstCHFrag); if (ret != 0) return ret; #ifdef WOLFSSL_DTLS13 if (IsAtLeastTLSv1_3(ssl->version)) { - ret = TlsCheckSupportedVersion(ssl, &ch, &isTls13); + ret = TlsCheckSupportedVersion(ssl, ch, isTls13); if (ret != 0) return ret; - if (isTls13) { + if (*isTls13) { int tlsxFound; - ret = FindExtByType(&ch.cookieExt, TLSX_COOKIE, ch.extension, + ret = FindExtByType(&ch->cookieExt, TLSX_COOKIE, ch->extension, &tlsxFound); if (ret != 0) { if (isFirstCHFrag) { @@ -1098,118 +1106,115 @@ static int _DoClientHelloStateless(WOLFSSL* ssl, const byte* input, } #endif - ret = ClientHelloSanityCheck(&ch, isTls13); + ret = ClientHelloSanityCheck(ch, *isTls13); if (ret != 0) return ret; #ifdef WOLFSSL_DTLS_NO_HVR_ON_RESUME - if (!isTls13 && !isFirstCHFrag) { + if (!*isTls13 && !isFirstCHFrag) { int resume = FALSE; - ret = TlsResumptionIsValid(ssl, &ch, &resume); + ret = TlsResumptionIsValid(ssl, ch, &resume); if (ret != 0) return ret; - if (resume) { - ssl->options.dtlsStateful = 1; - /* Update the window now that we enter the stateful parsing */ - DtlsUpdateWindow(ssl); - /* Set record numbers before current record number as read */ - XMEMSET(ssl->keys.peerSeq->window, 0xFF, - sizeof(ssl->keys.peerSeq->window)); + if (resume) return 0; - } } #endif - if (ch.cookie.size == 0 && ch.cookieExt.size == 0) { -#ifdef WOLFSSL_DTLS_CH_FRAG - /* Don't send anything here when processing fragment */ - if (isFirstCHFrag) - ret = COOKIE_ERROR; - else -#endif - ret = SendStatelessReply(ssl, &ch, isTls13); - } + if (ch->cookie.size == 0 && ch->cookieExt.size == 0) + return COOKIE_ERROR; else { byte cookieGood; - ret = CheckDtlsCookie(ssl, &ch, isTls13, &cookieGood); + ret = CheckDtlsCookie(ssl, ch, *isTls13, &cookieGood); if (ret != 0) return ret; if (!cookieGood) { #ifdef WOLFSSL_DTLS13 /* Invalid cookie for DTLS 1.3 results in an alert. Alert to be sent * in DoTls13ClientHello. */ - if (isTls13) - ret = INVALID_PARAMETER; - else -#endif -#ifdef WOLFSSL_DTLS_CH_FRAG - /* Don't send anything here when processing fragment */ - if (isFirstCHFrag) - ret = COOKIE_ERROR; - else + if (*isTls13) + return INVALID_PARAMETER; #endif - ret = SendStatelessReply(ssl, &ch, isTls13); - } - else { - ssl->options.dtlsStateful = 1; - /* Update the window now that we enter the stateful parsing */ -#ifdef WOLFSSL_DTLS13 - if (isTls13) { - /* Set record numbers before current record number as read */ - Dtls13Epoch* e; - ret = Dtls13UpdateWindowRecordRecvd(ssl); - e = Dtls13GetEpoch(ssl, ssl->keys.curEpoch64); - if (e != NULL) - XMEMSET(e->window, 0xFF, sizeof(e->window)); - } - else -#endif - DtlsUpdateWindow(ssl); - /* Set record numbers before current record number as read */ - XMEMSET(ssl->keys.peerSeq->window, 0xFF, - sizeof(ssl->keys.peerSeq->window)); + return COOKIE_ERROR; } } + return 0; +} + +/* Turn ssl stateful on a verified ClientHello. The record it came in, and + * every one before it, counts as read. */ +int ClientHelloStatelessCommit(WOLFSSL* ssl, byte isTls13) +{ + int ret = 0; + + (void)isTls13; + ssl->options.dtlsStateful = 1; +#ifdef WOLFSSL_DTLS13 + if (isTls13) { + Dtls13Epoch* e; + ret = Dtls13UpdateWindowRecordRecvd(ssl); + e = Dtls13GetEpoch(ssl, ssl->keys.curEpoch64); + if (e != NULL) + XMEMSET(e->window, 0xFF, sizeof(e->window)); + } + else +#endif + DtlsUpdateWindow(ssl); + XMEMSET(ssl->keys.peerSeq->window, 0xFF, + sizeof(ssl->keys.peerSeq->window)); return ret; } /* input holds the first fragSz bytes of a msgSz byte ClientHello. They are - * equal when input is the whole message. */ + * equal when input is the whole message, which is answered when it has no + * cookie or a bad DTLS 1.2 one. A prefix is checked as far as it goes: + * BUFFER_ERROR or COOKIE_ERROR when it ends before the cookie, and the + * caller decides what becomes of the message it belongs to. */ int DoClientHelloStateless(WOLFSSL* ssl, const byte* input, word32 fragSz, word32 msgSz) { byte isFrag = fragSz < msgSz; + byte isTls13 = 0; + WolfSSL_CH ch; int ret; if (fragSz > msgSz) return BAD_FUNC_ARG; - ret = _DoClientHelloStateless(ssl, input, fragSz, isFrag); + ssl->options.dtlsStateful = 0; + ret = ClientHelloStatelessVerdict(ssl, input, fragSz, isFrag, &ch, + &isTls13); + if (ret == 0) + ret = ClientHelloStatelessCommit(ssl, isTls13); + else if (ret == WC_NO_ERR_TRACE(COOKIE_ERROR) && !isFrag) + ret = SendStatelessReply(ssl, &ch, isTls13); #ifdef WOLFSSL_DTLS_CH_FRAG - /* The verified peer's entry leaves the pool: kept only as the rest of the - * ClientHello whose first fragment verified. Other peers' entries stay. */ - if (ssl->options.dtlsStateful && ssl->buffers.dtlsCtx.peer.sz > 0) { - DtlsMsg* rest = NULL; - - if (ret == 0 && isFrag) { - rest = DtlsChPoolTakeRest(&ssl->ctx->dtlsChPool, ssl->ctx->heap, - &ssl->buffers.dtlsCtx.peer, - (word16)w64GetLow32(ssl->keys.curEpoch64), - ssl->keys.dtls_peer_handshake_number, fragSz, msgSz, - (word32)ssl->dtls_timeout_max, ssl->heap); - } - else { - /* What is held is a copy of the verified message or of an older - * attempt, and this object no longer reads the pool */ - DtlsChPoolDrop(&ssl->ctx->dtlsChPool, ssl->ctx->heap, - &ssl->buffers.dtlsCtx.peer, (word32)ssl->dtls_timeout_max); - } - if (rest != NULL) - DtlsMsgRxListInsert(ssl, rest); + /* A whole message verified: what the pool holds of this peer is an older + * attempt, and this object no longer reads the pool */ + if (!isFrag && ssl->options.dtlsStateful && + ssl->buffers.dtlsCtx.peer.sz > 0) { + DtlsChPoolDrop(&ssl->ctx->dtlsChPool, ssl->ctx->heap, + &ssl->buffers.dtlsCtx.peer, (word32)ssl->dtls_timeout_max); } #endif return ret; } + +#ifdef WOLFSSL_DTLS_CH_FRAG +/* The verdict on the first headSz bytes of a msgSz byte ClientHello, headSz + * below msgSz. Nothing is sent, written to ssl or read from the pool, so it + * may run with a pool shard locked: it takes only the cookie secret's lock. + * On 0, isTls13 is what ClientHelloStatelessCommit() takes. */ +int DoClientHelloStatelessHead(const WOLFSSL* ssl, const byte* head, + word32 headSz, word32 msgSz, byte* isTls13) +{ + WolfSSL_CH ch; + + if (headSz >= msgSz) + return BAD_FUNC_ARG; + return ClientHelloStatelessVerdict(ssl, head, headSz, 1, &ch, isTls13); +} +#endif #endif /* !defined(NO_WOLFSSL_SERVER) */ #if defined(WOLFSSL_DTLS_CID) diff --git a/src/dtls13.c b/src/dtls13.c index 5ade515eb07..eb403b5f307 100644 --- a/src/dtls13.c +++ b/src/dtls13.c @@ -455,14 +455,53 @@ static int Dtls13NextMessageComplete(WOLFSSL* ssl) #if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) /* A plaintext ClientHello a server that reassembles them receives before a - * cookie verifies. Its fragments are held in the context's pool, keyed on the - * sender's address. */ + * cookie verifies. Its fragments are held in the context's pool, keyed on + * the sender's address, and the cookie check runs in place on the contiguous + * head of the held message, under the pool's lock, as the head grows. A + * verified entry leaves the pool in that same step. */ static int Dtls13ChBeforeCookie(WOLFSSL* ssl, enum HandShakeType type) { return ssl->options.side == WOLFSSL_SERVER_END && ssl->options.dtls13ChFrag && !ssl->options.dtlsStateful && type == client_hello && !IsEncryptionOn(ssl, 0); } + +/* Whether this server pools such fragments: reassembly before the cookie is + * on and the peer has an address to key them on */ +static int Dtls13ChPooled(const WOLFSSL* ssl) +{ + return ssl->options.dtls13ReassembleChBeforeCookie && + ssl->buffers.dtlsCtx.peer.sz > 0; +} + +/* Tell the application a ClientHello verified on a stateless object */ +static int Dtls13ChGoodCb(WOLFSSL* ssl) +{ + if (ssl->chGoodCb != NULL) { + int cbret = ssl->chGoodCb(ssl, ssl->chGoodCtx); + if (cbret < 0) { + ssl->error = cbret; + WOLFSSL_MSG("ClientHello Good Cb don't continue error"); + return WOLFSSL_FATAL_ERROR; + } + } + return 0; +} + +/* A fragment's cookie failed. Answered as a whole ClientHello's is, by + * DoTls13HandShakeMsgType(). The caller holds nothing of this peer. */ +static int Dtls13ChBadCookie(WOLFSSL* ssl) +{ + if (ssl->alert_history.last_tx.level != alert_fatal) { + DtlsSetSeqNumForReply(ssl); + if (SendAlert(ssl, alert_fatal, illegal_parameter) == + WC_NO_ERR_TRACE(SOCKET_ERROR_E)) { + return SOCKET_ERROR_E; + } + } + DtlsResetState(ssl); + return 0; +} #endif /* WOLFSSL_DTLS_CH_FRAG && !NO_WOLFSSL_SERVER */ static WC_INLINE int FragIsInOutputBuffer(WOLFSSL* ssl, const byte* frag) @@ -2012,54 +2051,39 @@ static int _Dtls13HandshakeRecv(WOLFSSL* ssl, byte* input, word32 size, /* The cookie check's verdict on a first fragment: COOKIE_ERROR with no * cookie, BUFFER_ERROR when it ends before the fields checked. */ int chRet = WC_NO_ERR_TRACE(COOKIE_ERROR); + int pooled = unverifiedCh && Dtls13ChPooled(ssl); - if (unverifiedCh && isFirst) { + /* A server that pools nothing judges a first fragment as it arrives + * and keeps only that one */ + if (unverifiedCh && !pooled && isFirst) { chRet = DoClientHelloStateless(ssl, input + idx, fragLength, messageLength); } - /* check if the first CH fragment contains a valid cookie */ - if (chRet == 0 && ssl->options.dtlsStateful) { - /* We can save this message and continue as stateful, a DTLS 1.2 - * ClientHello included. */ - if (ssl->chGoodCb != NULL) { - int cbret = ssl->chGoodCb(ssl, ssl->chGoodCtx); - if (cbret < 0) { - ssl->error = cbret; - WOLFSSL_MSG("ClientHello Good Cb don't continue error"); - return WOLFSSL_FATAL_ERROR; - } + if (pooled) { + /* Every fragment is pooled below, where the check runs on the + * head of the held message */ + WOLFSSL_MSG("Buffering fragment of an unverified ClientHello"); + } + else if (chRet == 0 && ssl->options.dtlsStateful) { + /* Stateful from here on, a DTLS 1.2 ClientHello included. What + * the pool holds of this peer is an older attempt. */ + if (ssl->buffers.dtlsCtx.peer.sz > 0) { + DtlsChPoolDrop(&ssl->ctx->dtlsChPool, ssl->ctx->heap, + &ssl->buffers.dtlsCtx.peer, (word32)ssl->dtls_timeout_max); } + ret = Dtls13ChGoodCb(ssl); + if (ret != 0) + return ret; WOLFSSL_MSG("ClientHello fragment verified"); } - /* A cookie that failed is answered as a whole ClientHello's is, by - * DoTls13HandShakeMsgType(), and nothing of this peer is held */ - else if (unverifiedCh && !ssl->options.dtlsStateful && - chRet == WC_NO_ERR_TRACE(INVALID_PARAMETER)) { + else if (unverifiedCh && chRet == WC_NO_ERR_TRACE(INVALID_PARAMETER)) { + /* Nothing of this peer is held */ if (ssl->buffers.dtlsCtx.peer.sz > 0) { DtlsChPoolDrop(&ssl->ctx->dtlsChPool, ssl->ctx->heap, &ssl->buffers.dtlsCtx.peer, (word32)ssl->dtls_timeout_max); } - if (ssl->alert_history.last_tx.level != alert_fatal) { - DtlsSetSeqNumForReply(ssl); - if (SendAlert(ssl, alert_fatal, illegal_parameter) == - WC_NO_ERR_TRACE(SOCKET_ERROR_E)) { - return SOCKET_ERROR_E; - } - } - DtlsResetState(ssl); *processedSize = idx + fragLength; - return 0; - } - /* Only a fragment the check could not judge is held, by a server - * still stateless, for a peer with an address */ - else if (unverifiedCh && !ssl->options.dtlsStateful && - (chRet == WC_NO_ERR_TRACE(COOKIE_ERROR) || - chRet == WC_NO_ERR_TRACE(BUFFER_ERROR)) && - ssl->options.dtls13ReassembleChBeforeCookie && - ssl->buffers.dtlsCtx.peer.sz > 0) { - /* No cookie to verify yet. Hold the fragments, within a bound, and - * run the cookie check on the complete message. */ - WOLFSSL_MSG("Buffering fragment of an unverified ClientHello"); + return Dtls13ChBadCookie(ssl); } else #endif @@ -2075,24 +2099,44 @@ static int _Dtls13HandshakeRecv(WOLFSSL* ssl, byte* input, word32 size, #if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) /* A fragment of a ClientHello whose cookie has not verified goes to the - * context's pool. The message it completes is drained like any other. */ + * context's pool. The head it grows is checked for the cookie there; the + * message it completes is drained like any other. */ if (!isComplete && Dtls13ChBeforeCookie(ssl, (enum HandShakeType)handshakeType)) { DtlsMsg* done = NULL; + DtlsMsg* verified = NULL; + byte isTls13 = 0; - if (ssl->buffers.dtlsCtx.peer.sz > 0) { - (void)DtlsChPoolStore(&ssl->ctx->dtlsChPool, ssl->ctx->heap, - &ssl->buffers.dtlsCtx.peer, - (word16)w64GetLow32(ssl->keys.curEpoch64), - ssl->keys.dtls_peer_handshake_number, - input + DTLS_HANDSHAKE_HEADER_SZ, messageLength, handshakeType, - fragOff, fragLength, (word32)ssl->dtls_timeout_max, ssl->heap, - &done); - } + /* The cookie check runs on the head of the held message in there */ + ret = DtlsChPoolStore(&ssl->ctx->dtlsChPool, ssl->ctx->heap, + &ssl->buffers.dtlsCtx.peer, + (word16)w64GetLow32(ssl->keys.curEpoch64), + ssl->keys.dtls_peer_handshake_number, + input + DTLS_HANDSHAKE_HEADER_SZ, messageLength, handshakeType, + fragOff, fragLength, (word32)ssl->dtls_timeout_max, ssl->heap, + &done, ssl, &verified, &isTls13); *processedSize = idx + fragLength; - if (done == NULL) - return 0; - DtlsMsgRxListInsert(ssl, done); + /* The entry is gone: nothing of this peer is held */ + if (ret == WC_NO_ERR_TRACE(INVALID_PARAMETER)) + return Dtls13ChBadCookie(ssl); + if (verified != NULL) { + /* Stateful before the entry moves, as on a whole message */ + ret = ClientHelloStatelessCommit(ssl, isTls13); + if (ret != 0) { + DtlsMsgListDelete(verified, ssl->ctx->heap); + return ret; + } + /* Stateful either way: the client resends what the receive list + * does not hold */ + if (DtlsMsgAdopt(&verified, ssl->ctx->heap, ssl->heap) == 0) + DtlsMsgRxListInsert(ssl, verified); + WOLFSSL_MSG("ClientHello verified from its pooled head"); + ret = Dtls13ChGoodCb(ssl); + if (ret != 0) + return ret; + } + if (done != NULL) + DtlsMsgRxListInsert(ssl, done); if (Dtls13NextMessageComplete(ssl)) return Dtls13ProcessBufferedMessages(ssl); return 0; diff --git a/src/internal.c b/src/internal.c index ac2df6a76ce..4d13369491c 100644 --- a/src/internal.c +++ b/src/internal.c @@ -12528,17 +12528,35 @@ static DtlsMsg* DtlsChPoolFindMsg(DtlsChPool* pool, DtlsChPoolShard* sh, return cur; } +/* The bytes msg holds from its start without a gap: its first bucket, since + * buckets that touch are merged */ +static word32 DtlsMsgHeadSz(const DtlsMsg* msg) +{ + const DtlsFragBucket* b = msg->fragBucketList; + + return b != NULL && b->m.m.offset == 0 ? b->m.m.sz : 0; +} + /* Store a fragment of a ClientHello sent before its cookie verified. One entry * per peer, the newest. The entry the fragment completes leaves through *done. * Returns 0 when held, DTLS_MSG_STORE_DROPPED when not, or BAD_FUNC_ARG. * The most the fragment can add, a new entry with one bucket, is reserved * with no lock held and evicted for when it passes the budget. Only then is * the shard locked, once, and the reservation settled to the bytes really - * added. A fragment of what is held evicts for its bound too. */ + * added. A fragment of what is held evicts for its bound too. + * With ssl given, its cookie check runs on the entry's contiguous head, in + * place and with the shard locked, once the head grew to headReportAt: from + * the first byte, then from twice the size last checked, O(log n) times + * whatever the fragments. Verified, 0, the entry leaves the pool through + * *verified, still keyed on the peer and allocated with heap, and *isTls13 + * is what ClientHelloStatelessCommit() takes. INVALID_PARAMETER frees it and + * is returned. Any other verdict keeps it pooled. A completed entry is not + * checked. */ int DtlsChPoolStore(DtlsChPool* pool, void* heap, const WOLFSSL_SOCKADDR* peer, word16 epoch, word32 seq, const byte* data, word32 dataSz, byte type, word32 fragOffset, - word32 fragSz, word32 maxAge, void* outHeap, DtlsMsg** done) + word32 fragSz, word32 maxAge, void* outHeap, DtlsMsg** done, + const WOLFSSL* ssl, DtlsMsg** verified, byte* isTls13) { DtlsChPoolShard* sh; DtlsMsg* cur = NULL; @@ -12550,18 +12568,24 @@ int DtlsChPoolStore(DtlsChPool* pool, void* heap, size_t reserve; /* the bound of the fragment, held in pool->reserved */ size_t sum; word32 received; + word32 headBefore = 0; + word32 headAfter = 0; word32 now; word32 buckets; int isNew; int progressed; + int badCookie = 0; int ret = 0; WOLFSSL_ENTER("DtlsChPoolStore"); if (done != NULL) *done = NULL; + if (verified != NULL) + *verified = NULL; if (pool == NULL || !pool->ready || peer == NULL || done == NULL || data == NULL || fragOffset > dataSz || - fragSz > dataSz - fragOffset) + fragSz > dataSz - fragOffset || + (ssl != NULL && (verified == NULL || isTls13 == NULL))) return BAD_FUNC_ARG; /* No address is no key, and a whole message is processed at once */ if (peer->sz == 0 || (fragOffset == 0 && fragSz == dataSz)) @@ -12636,9 +12660,11 @@ int DtlsChPoolStore(DtlsChPool* pool, void* heap, } if (ret == 0) { received = cur->bytesReceived; + headBefore = DtlsMsgHeadSz(cur); /* A pooled ClientHello is always plaintext */ ret = DtlsMsgSet(cur, seq, epoch, data, type, fragOffset, fragSz, heap, dataSz, 0); + headAfter = DtlsMsgHeadSz(cur); /* New bytes refresh recency, duplicates do not */ progressed = ret == 0 && (isNew || cur->bytesReceived > received); if (ret != 0 && isNew) { @@ -12678,11 +12704,36 @@ int DtlsChPoolStore(DtlsChPool* pool, void* heap, DtlsChPoolUnlink(pool, sh, cur); detached = cur; } + /* A head that grew to the next check size is checked in place, with the + * lock held since another listener may change the entry once released. + * Its verdict settles the entry before that. */ + else if (ret == 0 && ssl != NULL && headAfter > headBefore && + headAfter >= cur->headReportAt) { + int verdict; + + cur->headReportAt = 2 * headAfter; + /* Lock order: the check runs under the shard lock and takes the + * context's cookie secret lock inside it, a leaf lock */ + verdict = DoClientHelloStatelessHead(ssl, cur->fragBucketList->buf, + headAfter, cur->sz, isTls13); + if (verdict == 0) { + DtlsChPoolUnlink(pool, sh, cur); + *verified = cur; + } + else if (verdict == WC_NO_ERR_TRACE(INVALID_PARAMETER)) { + DtlsChPoolUnlink(pool, sh, cur); + cur->next = gone; + gone = cur; + badCookie = 1; + } + } DtlsChShardUnlock(sh); DtlsMsgListDelete(gone, heap); if (ret != 0) return DTLS_MSG_STORE_DROPPED; + if (badCookie) + return INVALID_PARAMETER; if (detached != NULL) { if (DtlsMsgAdopt(&detached, heap, outHeap) != 0) return DTLS_MSG_STORE_DROPPED; @@ -12692,9 +12743,10 @@ int DtlsChPoolStore(DtlsChPool* pool, void* heap, } /* Take peer's entry out of the pool, unless it has aged out. Only peer's - * shard is locked. */ -static DtlsMsg* DtlsChPoolTake(DtlsChPool* pool, void* heap, - const WOLFSSL_SOCKADDR* peer, word32 maxAge) + * shard is locked. The entry stays keyed on the peer and allocated with heap: + * DtlsMsgAdopt() hands it to a connection. */ +DtlsMsg* DtlsChPoolTake(DtlsChPool* pool, void* heap, + const WOLFSSL_SOCKADDR* peer, word32 maxAge) { DtlsChPoolShard* sh; DtlsMsg* held; @@ -12723,48 +12775,6 @@ void DtlsChPoolDrop(DtlsChPool* pool, void* heap, const WOLFSSL_SOCKADDR* peer, DtlsMsgDelete(held, heap); } -/* Take peer's entry out of the pool. It is returned, allocated with outHeap, - * only as the rest of the ClientHello of epoch and seq whose first fragSz of - * msgSz bytes verified: what it held of those bytes is cut away, since only - * the verified copy may be used. Nothing past them, or another message: - * freed. */ -DtlsMsg* DtlsChPoolTakeRest(DtlsChPool* pool, void* heap, - const WOLFSSL_SOCKADDR* peer, word16 epoch, word32 seq, word32 fragSz, - word32 msgSz, word32 maxAge, void* outHeap) -{ - DtlsMsg* held = DtlsChPoolTake(pool, heap, peer, maxAge); - DtlsFragBucket* b; - - if (held == NULL) - return NULL; - if (held->epoch != epoch || held->seq != seq || - held->type != client_hello || held->sz != msgSz) { - DtlsMsgDelete(held, heap); - return NULL; - } - /* Buckets are sorted and disjoint: drop those the fragment covers, then - * cut the head of one it reaches into */ - while ((b = held->fragBucketList) != NULL && - b->m.m.offset + b->m.m.sz <= fragSz) { - held->fragBucketList = b->m.m.next; - held->bytesReceived -= b->m.m.sz; - held->fragBucketListCount--; - DtlsMsgDestroyFragBucket(b, heap); - } - if (b != NULL && b->m.m.offset < fragSz) { - word32 cut = fragSz - b->m.m.offset; - - XMEMMOVE(b->buf, b->buf + cut, b->m.m.sz - cut); - b->m.m.offset = fragSz; - b->m.m.sz -= cut; - held->bytesReceived -= cut; - } - if (b == NULL || DtlsMsgAdopt(&held, heap, outHeap) != 0) { - DtlsMsgDelete(held, heap); - return NULL; - } - return held; -} #endif /* WOLFSSL_DTLS_CH_FRAG && !NO_WOLFSSL_SERVER */ /* Whether ret, from DtlsMsgStore(), ends the connection: only running out of diff --git a/tests/api/test_dtls13.c b/tests/api/test_dtls13.c index 33865d771ba..dd6e882cb8b 100644 --- a/tests/api/test_dtls13.c +++ b/tests/api/test_dtls13.c @@ -556,7 +556,7 @@ static int test_dtls13_pool_put(DtlsChPool* pool, int id, const byte* data, peer.bufSz = sizeof(id); peer.sa = &id; ret = DtlsChPoolStore(pool, NULL, &peer, 0, 1, data, sz, client_hello, off, - len, DTLS_TIMEOUT_MAX, NULL, &done); + len, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL); DtlsMsgListDelete(done, NULL); return ret; } @@ -1075,11 +1075,11 @@ int test_dtls13_clear_ch_pool_api(void) peer.sz = peer.bufSz = (unsigned int)sizeof(peerBuf); ExpectIntEQ(DtlsChPoolStore(test_dtls13_ch_pool(ssl_s), ctx_s->heap, &peer, 0, 0, data, 8, client_hello, 0, 4, DTLS_TIMEOUT_MAX, ssl_s->heap, - &done), 0); + &done, NULL, NULL, NULL), 0); peerBuf[0] = 2; ExpectIntEQ(DtlsChPoolStore(test_dtls13_ch_pool(ssl_s), ctx_s->heap, &peer, 0, 0, data, 8, client_hello, 0, 4, DTLS_TIMEOUT_MAX, ssl_s->heap, - &done), 0); + &done, NULL, NULL, NULL), 0); ExpectNull(done); ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), 2); ExpectIntEQ(wolfSSL_CTX_dtls13_clear_ch_pool(ctx_s), WOLFSSL_SUCCESS); @@ -1090,7 +1090,7 @@ int test_dtls13_clear_ch_pool_api(void) /* The pool takes entries again */ ExpectIntEQ(DtlsChPoolStore(test_dtls13_ch_pool(ssl_s), ctx_s->heap, &peer, 0, 0, data, 8, client_hello, 0, 4, DTLS_TIMEOUT_MAX, ssl_s->heap, - &done), 0); + &done, NULL, NULL, NULL), 0); ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), 1); ExpectIntEQ(wolfSSL_CTX_dtls13_clear_ch_pool(ctx_s), WOLFSSL_SUCCESS); ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), 0); @@ -1580,8 +1580,9 @@ int test_dtls13_frag_ch2_cookie_frag_last(void) return EXPECT_RESULT(); } -/* A forged first fragment, right length but no cookie, is held. When the - * genuine one verifies the forged bytes must not replace the verified ones. */ +/* A forged first fragment, right length but no cookie, is held and its head + * fails the check. The genuine one contradicts it, replaces it and verifies + * from the pool: nothing of the forged bytes reaches the connection. */ #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ defined(HAVE_ECC) && !defined(NO_ECC256) @@ -1636,7 +1637,7 @@ static int test_dtls13_frag_ch2_forged_first_frag_ex(int peerId) ExpectIntEQ(ssl_s->options.dtlsStateful, 1); ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); ExpectIntEQ(isHrr, 0); - /* Nothing of the forged entry lies past the verified fragment: freed */ + /* The genuine entry moved to the connection, the forged one is gone */ ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), 0); ExpectTrue(test_dtls13_pool_ok(test_dtls13_ch_pool(ssl_s))); ExpectNull(ssl_s->dtls_rx_msg_list); @@ -1662,9 +1663,8 @@ int test_dtls13_frag_ch2_forged_first_frag(void) return EXPECT_RESULT(); } -/* A pooled fragment reaching into the first fragment that then verifies: the - * bytes past it complete the ClientHello without a resend, the overlap is - * taken from the verified copy. */ +/* A pooled fragment reaching into the first fragment, which then arrives: + * the two complete the ClientHello in the pool and it is checked whole. */ int test_dtls13_frag_ch2_overlap_handoff(void) { EXPECT_DECLS; @@ -1712,7 +1712,7 @@ int test_dtls13_frag_ch2_overlap_handoff(void) ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 1); ExpectIntEQ(test_ctx.c_len, 0); - /* The first fragment, through the cookie, verifies and completes it */ + /* The first fragment, through the cookie, completes it */ ExpectIntGT(fragSz = test_dtls13_frag_range(ch, chSz, 0, (word32)cut, 0, frag, (int)sizeof(frag)), 0); ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, (const char*)frag, @@ -3542,9 +3542,26 @@ static int test_dtls13_frag_ch2_ctx_second(int secretMode, int mode) ExpectIntEQ(test_memio_inject_message(&tB, 0, (const char*)frag, fragSz), 0); if (sharedSecret) { - /* The cookie verifies and the held rest completes the message */ + /* The cookie verifies. Pooled, the fragment completes the held rest + * at once. */ ExpectIntEQ(test_dtls13_server_read_all(&tB, c2), TEST_SUCCESS); ExpectIntEQ(c2->options.dtlsStateful, 1); + if (mode == 1) { + /* With reassembly off c2 checked the datagram and keeps only + * that fragment: the held rest is freed, not read, and the + * client resends it */ + ExpectIntEQ(test_dtls13_reply_is_server_hello(&tB, &isHrr), 0); + ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 0); + ExpectNotNull(c2->dtls_rx_msg_list); + for (i = 1; i < 3 && EXPECT_SUCCESS(); i++) { + ExpectIntGT(fragSz = test_dtls13_frag_range(chB, chBSz, + (word32)off[i], (word32)len[i], (word32)(i + 2), frag, + (int)sizeof(frag)), 0); + ExpectIntEQ(test_memio_inject_message(&tB, 0, + (const char*)frag, fragSz), 0); + } + ExpectIntEQ(test_dtls13_server_read_all(&tB, c2), TEST_SUCCESS); + } ExpectIntEQ(test_dtls13_reply_is_server_hello(&tB, &isHrr), 1); ExpectIntEQ(isHrr, 0); ExpectIntEQ(test_dtls13_pool_count(&ctx_s->dtlsChPool), 0); @@ -3597,9 +3614,9 @@ static int test_dtls13_frag_ch2_ctx_second(int secretMode, int mode) } #endif -/* The rest of a second ClientHello held by the context is taken by the server - * that verifies its first fragment: the servers share the context's secret - * unless given their own. */ +/* The rest of a second ClientHello held by the context completes it at the + * server its first fragment reaches: the servers share the context's secret + * unless given their own. One with reassembly off reads nothing held. */ int test_dtls13_frag_ch2_ctx_handoff(void) { EXPECT_DECLS; @@ -4147,8 +4164,10 @@ int test_dtls13_clear_frees_rx_list(void) return EXPECT_RESULT(); } -/* A first fragment whose cookie fails is answered with an alert and not held: - * holding it would cost other peers' entries. The genuine ones complete. */ +/* A first fragment with a bad cookie, pooled and checked from its head: an + * illegal_parameter alert, its entry dropped since holding it would cost + * other peers' entries, the server still stateless. The genuine fragment + * then verifies from the pool and the rest completes it. */ int test_dtls13_frag_ch2_bad_cookie_dropped(void) { EXPECT_DECLS; @@ -4228,7 +4247,8 @@ int test_dtls13_frag_ch2_bad_cookie_dropped(void) } /* A DTLS 1.2 second ClientHello in two fragments at a DTLS 1.3 server: the - * first carries the cookie and is held, the second completes it. */ + * first carries the cookie and verifies from the pool, the second completes + * it in the connection. */ int test_dtls13_frag_ch2_dtls12_cookie(void) { EXPECT_DECLS; @@ -4279,6 +4299,7 @@ int test_dtls13_frag_ch2_dtls12_cookie(void) /* Verified: the server is stateful and holds the fragment */ ExpectIntEQ(ssl_s->options.dtlsStateful, 1); ExpectNotNull(ssl_s->dtls_rx_msg_list); + ExpectIntEQ(test_dtls13_pool_count(test_dtls13_ch_pool(ssl_s)), 0); ExpectIntEQ(test_ctx.c_len, 0); ExpectIntGT(fragSz = test_dtls13_frag_range(ch, chSz, cut, bodySz - cut, 1, @@ -4299,6 +4320,184 @@ int test_dtls13_frag_ch2_dtls12_cookie(void) return EXPECT_RESULT(); } +/* The cookie in the second of three fragments, delivered in order. The head + * is checked when the first arrives, too short, and again when the second + * doubles it: the server turns stateful and the entry moves to its receive + * list. The third completes the message there. */ +int test_dtls13_frag_ch2_cookie_in_second(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + int cut = 0; + int half = 0; + int bodySz = 0; + int isHrr = 0; + DtlsChPool* pool = NULL; + DtlsMsg* held = NULL; + + 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(test_dtls13_server_peer(ssl_s, &test_ctx, 3), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectNotNull(pool = test_dtls13_ch_pool(ssl_s)); + + /* CH1, HRR with cookie, CH2 */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + bodySz = chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ; + ExpectIntGT(cut = test_dtls13_ch_cookie_end(ch, chSz), 0); + ExpectIntGT(bodySz - cut, 0); + /* The second fragment ends at the cookie and at least doubles the head */ + half = cut / 2; + + /* The first: held, its head checked and found short */ + ExpectIntEQ(test_dtls13_deliver_range(&test_ctx, ssl_s, ch, chSz, 0, + (word32)half, 0), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectIntEQ(test_dtls13_pool_count(pool), 1); + ExpectNotNull(held = test_dtls13_held_for(pool, 3)); + ExpectIntEQ(held != NULL ? (int)held->headReportAt : -1, 2 * half); + ExpectNull(ssl_s->dtls_rx_msg_list); + + /* The second: the head reaches the cookie and verifies. The entry is the + * connection's now, nothing is answered before the message completes. */ + ExpectIntEQ(test_dtls13_deliver_range(&test_ctx, ssl_s, ch, chSz, + (word32)half, (word32)(cut - half), 1), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + ExpectIntEQ(test_dtls13_pool_count(pool), 0); + ExpectTrue(test_dtls13_pool_ok(pool)); + ExpectIntEQ(ssl_s->dtls_rx_msg_list_sz, 1); + ExpectNotNull(ssl_s->dtls_rx_msg_list); + if (ssl_s->dtls_rx_msg_list != NULL) { + ExpectIntEQ(ssl_s->dtls_rx_msg_list->bytesReceived, cut); + ExpectIntEQ(ssl_s->dtls_rx_msg_list->peer.sz, 0); + } + + /* The third completes it in the receive list */ + ExpectIntEQ(test_dtls13_deliver_range(&test_ctx, ssl_s, ch, chSz, + (word32)cut, (word32)(bodySz - cut), 2), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 0); + ExpectTrue(test_dtls13_nothing_held(ssl_s)); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* Four fragments, the cookie in the first: the second and third arrive + * first and are held without a head, then the first. One head report covers + * all three, the cookie verifies and the whole entry moves to the + * connection. The fourth completes it there. */ +int test_dtls13_frag_ch2_cookie_frag_out_of_order(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + int cut = 0; + int bodySz = 0; + int third = 0; + int isHrr = 0; + DtlsChPool* pool = NULL; + DtlsMsg* held = NULL; + + 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(test_dtls13_server_peer(ssl_s, &test_ctx, 4), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectNotNull(pool = test_dtls13_ch_pool(ssl_s)); + + /* CH1, HRR with cookie, CH2 */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + ExpectIntEQ(test_dtls13_take_ch(&test_ctx, ssl_c, ch, &chSz), + TEST_SUCCESS); + bodySz = chSz - DTLS_RECORD_HEADER_SZ - DTLS_HANDSHAKE_HEADER_SZ; + ExpectIntGT(cut = test_dtls13_ch_cookie_end(ch, chSz), 0); + /* The bytes past the cookie in three: two go before the first fragment, + * the last after it */ + ExpectIntGE(bodySz - cut, 3); + third = (bodySz - cut) / 3; + + /* The second and third fragments: held, no head to check */ + ExpectIntEQ(test_dtls13_deliver_range(&test_ctx, ssl_s, ch, chSz, + (word32)(cut + third), (word32)third, 2), TEST_SUCCESS); + ExpectIntEQ(test_dtls13_deliver_range(&test_ctx, ssl_s, ch, chSz, + (word32)cut, (word32)third, 1), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectIntEQ(test_dtls13_pool_count(pool), 1); + ExpectNotNull(held = test_dtls13_held_for(pool, 4)); + ExpectIntEQ(held != NULL ? (int)held->bytesReceived : -1, 2 * third); + ExpectIntEQ(held != NULL ? (int)held->headReportAt : -1, 0); + ExpectNull(ssl_s->dtls_rx_msg_list); + + /* The first: the head spans all three and verifies */ + ExpectIntEQ(test_dtls13_deliver_range(&test_ctx, ssl_s, ch, chSz, 0, + (word32)cut, 0), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_len, 0); + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + ExpectIntEQ(test_dtls13_pool_count(pool), 0); + ExpectTrue(test_dtls13_pool_ok(pool)); + ExpectIntEQ(ssl_s->dtls_rx_msg_list_sz, 1); + ExpectNotNull(ssl_s->dtls_rx_msg_list); + if (ssl_s->dtls_rx_msg_list != NULL) { + ExpectIntEQ(ssl_s->dtls_rx_msg_list->bytesReceived, cut + 2 * third); + ExpectIntEQ(ssl_s->dtls_rx_msg_list->fragBucketListCount, 1); + } + + /* The fourth completes it in the receive list */ + ExpectIntEQ(test_dtls13_deliver_range(&test_ctx, ssl_s, ch, chSz, + (word32)(cut + 2 * third), (word32)(bodySz - cut - 2 * third), 3), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 0); + ExpectTrue(test_dtls13_nothing_held(ssl_s)); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + #if defined(WOLFSSL_DTLS_CH_FRAG) && !defined(NO_WOLFSSL_SERVER) && \ !defined(SINGLE_THREADED) #define TEST_CH_POOL_THREADS 2 @@ -4362,14 +4561,17 @@ static THREAD_RETURN WOLFSSL_THREAD test_dtls13_ch_pool_worker(void* args) DtlsChPoolClear(&test_ch_pool_shared, NULL); } else if (op < 16) { - msg = DtlsChPoolTakeRest(&test_ch_pool_shared, NULL, &peer, 0, 1, 0, - TEST_CH_POOL_MSG_SZ, DTLS_TIMEOUT_MAX, NULL); + msg = DtlsChPoolTake(&test_ch_pool_shared, NULL, &peer, + DTLS_TIMEOUT_MAX); + if (msg != NULL && DtlsMsgAdopt(&msg, NULL, NULL) != 0) + test_ch_pool_bad[tno]++; } else { + /* No check: the bytes are no ClientHello */ (void)DtlsChPoolStore(&test_ch_pool_shared, NULL, &peer, 0, 1, data, TEST_CH_POOL_MSG_SZ, client_hello, quarter * (TEST_CH_POOL_MSG_SZ / 4), TEST_CH_POOL_MSG_SZ / 4, - DTLS_TIMEOUT_MAX, NULL, &msg); + DTLS_TIMEOUT_MAX, NULL, &msg, NULL, NULL, NULL); } if (msg != NULL) { if (!test_ch_pool_msg_ok(msg, peerId, TEST_CH_POOL_MSG_SZ)) @@ -5384,18 +5586,20 @@ int test_dtls13_ch_pool_unit(void) XMEMSET(&none, 0, sizeof(none)); ExpectIntEQ(DtlsChPoolStore(NULL, NULL, &peer, 0, 0, data, len, - client_hello, 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), + client_hello, 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); ExpectIntEQ(DtlsChPoolStore(&pool, NULL, NULL, 0, 0, data, len, - client_hello, 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), + client_hello, 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, 4, client_hello, - 3, 2, DTLS_TIMEOUT_MAX, NULL, &done), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + 3, 2, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); ExpectNull(done); /* The peer copy is counted */ ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, len, - client_hello, 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + client_hello, 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, + NULL), 0); ExpectNull(done); ExpectNotNull(current = test_dtls13_pool_head(&pool)); ExpectIntEQ(current != NULL ? (int)current->peer.bufSz : 0, @@ -5412,7 +5616,8 @@ int test_dtls13_ch_pool_unit(void) /* A second peer goes after the first */ peerBuf[0] = 2; ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, len, - client_hello, 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + client_hello, 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, + NULL), 0); ExpectNotNull(other = test_dtls13_pool_next(&pool, current)); bytes = 2 * (base + 1); ExpectIntEQ(test_dtls13_pool_count(&pool), 2); @@ -5421,12 +5626,12 @@ int test_dtls13_ch_pool_unit(void) /* An empty peer is no key, and a whole message is never held */ ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &none, 0, 0, data, len, - client_hello, 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), + client_hello, 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); - ExpectNull(DtlsChPoolTakeRest(&pool, NULL, &none, 0, 0, 1, len, - DTLS_TIMEOUT_MAX, NULL)); + ExpectNull(DtlsChPoolTake(&pool, NULL, &none, DTLS_TIMEOUT_MAX)); ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, 4, client_hello, - 0, 4, DTLS_TIMEOUT_MAX, NULL, &done), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + 0, 4, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); ExpectNull(done); ExpectPtrEq(test_dtls13_pool_head(&pool), current); ExpectPtrEq(test_dtls13_pool_next(&pool, current), other); @@ -5439,7 +5644,8 @@ int test_dtls13_ch_pool_unit(void) peerBuf[0] = 3; ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, len + 1, client_hello, 0, 1, - DTLS_TIMEOUT_MAX, NULL, &done), DTLS_MSG_STORE_DROPPED); + DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), + DTLS_MSG_STORE_DROPPED); peerBuf[0] = 1; ExpectNull(done); ExpectPtrEq(test_dtls13_pool_head(&pool), current); @@ -5451,10 +5657,12 @@ int test_dtls13_ch_pool_unit(void) /* A duplicate does not refresh; new bytes do, partial overlaps too */ ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, len, - client_hello, 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + client_hello, 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, + NULL), 0); ExpectPtrEq(test_dtls13_pool_head(&pool), current); ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, len, - client_hello, 0, 2, DTLS_TIMEOUT_MAX, NULL, &done), 0); + client_hello, 0, 2, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, + NULL), 0); ExpectPtrEq(test_dtls13_pool_head(&pool), other); ExpectPtrEq(test_dtls13_pool_next(&pool, other), current); ExpectNull(test_dtls13_pool_next(&pool, current)); @@ -5463,11 +5671,13 @@ int test_dtls13_ch_pool_unit(void) /* Refresh other, making current the oldest again */ peerBuf[0] = 2; ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, len, - client_hello, 1, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + client_hello, 1, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, + NULL), 0); ExpectPtrEq(test_dtls13_pool_head(&pool), current); peerBuf[0] = 3; ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, len, - client_hello, 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + client_hello, 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, + NULL), 0); ExpectIntEQ(test_dtls13_pool_count(&pool), 3); ExpectTrue(test_dtls13_pool_ok(&pool)); @@ -5476,7 +5686,8 @@ int test_dtls13_ch_pool_unit(void) * its peer. */ peerBuf[0] = 1; ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, len, - client_hello, 2, len - 2, DTLS_TIMEOUT_MAX, NULL, &done), 0); + client_hello, 2, len - 2, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, + NULL), 0); ExpectPtrEq(done, current); ExpectIntEQ(test_dtls13_pool_count(&pool), 0); ExpectIntEQ(test_dtls13_pool_bytes(&pool), 0); @@ -5497,14 +5708,14 @@ int test_dtls13_ch_pool_unit(void) /* One entry per peer, the newest: another message, or a fragment * contradicting the one held, replaces it */ ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, 4, client_hello, - 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, - 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); ExpectIntEQ(test_dtls13_pool_count(&pool), 1); h = test_dtls13_pool_head(&pool); ExpectIntEQ(h != NULL ? (int)h->seq : -1, 1); ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 5, client_hello, - 1, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 1, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); ExpectIntEQ(test_dtls13_pool_count(&pool), 1); h = test_dtls13_pool_head(&pool); ExpectIntEQ(h != NULL ? (int)h->sz : -1, 5); @@ -5518,20 +5729,20 @@ int test_dtls13_ch_pool_unit(void) XMEMSET(alt, 0xa5, sizeof(alt)); peerBuf[0] = 2; ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 8, client_hello, - 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); peerBuf[0] = 1; ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 8, client_hello, - 0, 2, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 0, 2, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 8, client_hello, - 1, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 1, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, alt, 8, client_hello, - 2, 2, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 2, 2, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); ExpectIntEQ(test_dtls13_pool_count(&pool), 2); ExpectNotNull(current = test_dtls13_pool_next(&pool, test_dtls13_pool_head(&pool))); ExpectIntEQ(current != NULL ? (int)current->bytesReceived : -1, 4); ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 8, client_hello, - 3, 2, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 3, 2, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); ExpectIntEQ(test_dtls13_pool_count(&pool), 2); ExpectNotNull(current = test_dtls13_pool_next(&pool, test_dtls13_pool_head(&pool))); @@ -5542,11 +5753,12 @@ int test_dtls13_ch_pool_unit(void) /* Every bucket the fragment spans is compared: [4,7) agrees with [3,5) * but not with [6,7), so it replaces the entry */ ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 8, client_hello, - 6, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 6, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); ExpectIntEQ(current != NULL ? (int)current->fragBucketListCount : -1, 2); XMEMSET(alt, 0x5a, 6); ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, alt + 4, 8, - client_hello, 4, 3, DTLS_TIMEOUT_MAX, NULL, &done), 0); + client_hello, 4, 3, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, + NULL), 0); ExpectNull(done); ExpectIntEQ(test_dtls13_pool_count(&pool), 2); ExpectNotNull(current = test_dtls13_pool_next(&pool, @@ -5557,10 +5769,11 @@ int test_dtls13_ch_pool_unit(void) ExpectTrue(test_dtls13_pool_ok(&pool)); /* The message it completes is all its own */ ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, alt, 8, client_hello, - 0, 4, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 0, 4, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); ExpectNull(done); ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, alt + 7, 8, - client_hello, 7, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + client_hello, 7, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, + NULL), 0); ExpectNotNull(done); ExpectIntEQ(test_dtls13_pool_count(&pool), 1); if (done != NULL) { @@ -5582,7 +5795,7 @@ int test_dtls13_ch_pool_unit(void) for (i = 0; i < DTLS_FRAG_POOL_SZ && EXPECT_SUCCESS(); i++) { ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 2 * DTLS_FRAG_POOL_SZ + 2, client_hello, 2 * i, 1, - DTLS_TIMEOUT_MAX, NULL, &done), + DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); } ExpectNotNull(current = test_dtls13_pool_head(&pool)); @@ -5591,19 +5804,20 @@ int test_dtls13_ch_pool_unit(void) bytes = test_dtls13_pool_bytes(&pool); ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 2 * DTLS_FRAG_POOL_SZ + 2, client_hello, 2 * DTLS_FRAG_POOL_SZ, 1, - DTLS_TIMEOUT_MAX, NULL, &done), DTLS_MSG_STORE_DROPPED); + DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), + DTLS_MSG_STORE_DROPPED); ExpectPtrEq(test_dtls13_pool_head(&pool), current); ExpectIntEQ(current != NULL ? (int)current->fragBucketListCount : -1, DTLS_FRAG_POOL_SZ); ExpectIntEQ(test_dtls13_pool_bytes(&pool), bytes); ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 2 * DTLS_FRAG_POOL_SZ + 2, client_hello, 2 * DTLS_FRAG_POOL_SZ - 1, 1, - DTLS_TIMEOUT_MAX, NULL, &done), 0); + DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); ExpectIntEQ(current != NULL ? (int)current->fragBucketListCount : -1, DTLS_FRAG_POOL_SZ); ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 2 * DTLS_FRAG_POOL_SZ + 2, client_hello, 1, 1, - DTLS_TIMEOUT_MAX, NULL, &done), 0); + DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); ExpectIntEQ(current != NULL ? (int)current->fragBucketListCount : -1, DTLS_FRAG_POOL_SZ - 1); ExpectNull(done); @@ -5616,69 +5830,47 @@ int test_dtls13_ch_pool_unit(void) ExpectIntEQ(test_dtls13_pool_bytes(&pool), 0); ExpectNull(test_dtls13_pool_head(&pool)); - /* Release: only the rest of the ClientHello whose first fragment - * verified is returned, without its peer copy */ + /* Take: the peer's whole entry leaves, still keyed on the peer until + * adopted. Other peers' entries stay. */ ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, - 1, 3, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 1, 3, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); peerBuf[0] = 2; ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, - 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); peerBuf[0] = 3; - ExpectNull(DtlsChPoolTakeRest(&pool, NULL, &peer, 0, 1, 1, 4, - DTLS_TIMEOUT_MAX, NULL)); + ExpectNull(DtlsChPoolTake(&pool, NULL, &peer, DTLS_TIMEOUT_MAX)); ExpectIntEQ(test_dtls13_pool_count(&pool), 2); peerBuf[0] = 1; - ExpectNotNull(done = DtlsChPoolTakeRest(&pool, NULL, &peer, 0, 1, 1, 4, - DTLS_TIMEOUT_MAX, NULL)); + ExpectNotNull(done = DtlsChPoolTake(&pool, NULL, &peer, DTLS_TIMEOUT_MAX)); ExpectIntEQ(test_dtls13_pool_count(&pool), 1); ExpectTrue(test_dtls13_pool_ok(&pool)); if (done != NULL) { - ExpectIntEQ(done->peer.sz, 0); - ExpectNull(done->peer.sa); - ExpectIntEQ(DtlsMsgListRetainedBytes(done), base - sizeof(peerBuf) + 3); + ExpectIntEQ(done->peer.sz, sizeof(peerBuf)); + ExpectBufEQ(done->peer.sa, peerBuf, sizeof(peerBuf)); + ExpectIntEQ(DtlsMsgListRetainedBytes(done), base + 3); ExpectNull(done->next); ExpectPtrEq(DtlsMsgFind(done, 0, 1), done); - } - DtlsMsgListDelete(done, NULL); - done = NULL; - /* An overlap is cut away: only the bytes past the verified fragment */ - ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, - 1, 3, DTLS_TIMEOUT_MAX, NULL, &done), 0); - ExpectNotNull(done = DtlsChPoolTakeRest(&pool, NULL, &peer, 0, 1, 2, 4, - DTLS_TIMEOUT_MAX, NULL)); - ExpectIntEQ(test_dtls13_pool_count(&pool), 1); - if (done != NULL) { - ExpectIntEQ(done->bytesReceived, 2); - ExpectIntEQ(done->fragBucketListCount, 1); + ExpectIntEQ(done->bytesReceived, 3); ExpectNotNull(done->fragBucketList); if (done->fragBucketList != NULL) { - ExpectIntEQ(done->fragBucketList->m.m.offset, 2); - ExpectIntEQ(done->fragBucketList->m.m.sz, 2); - ExpectBufEQ(done->fragBucketList->buf, data + 2, 2); - ExpectNull(done->fragBucketList->m.m.next); + ExpectIntEQ(done->fragBucketList->m.m.offset, 1); + ExpectIntEQ(done->fragBucketList->m.m.sz, 3); + ExpectBufEQ(done->fragBucketList->buf, data + 1, 3); } + ExpectIntEQ(DtlsMsgAdopt(&done, NULL, NULL), 0); + ExpectIntEQ(done->peer.sz, 0); + ExpectNull(done->peer.sa); + ExpectIntEQ(DtlsMsgListRetainedBytes(done), base - sizeof(peerBuf) + 3); } DtlsMsgListDelete(done, NULL); done = NULL; - /* Nothing past the verified fragment, another seq or another size: freed */ + /* Drop frees the peer's entry and no other */ ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, - 1, 3, DTLS_TIMEOUT_MAX, NULL, &done), 0); - ExpectNull(DtlsChPoolTakeRest(&pool, NULL, &peer, 0, 1, 4, 4, - DTLS_TIMEOUT_MAX, NULL)); - ExpectIntEQ(test_dtls13_pool_count(&pool), 1); - ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, - 1, 3, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 1, 3, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); DtlsChPoolDrop(&pool, NULL, &peer, DTLS_TIMEOUT_MAX); ExpectIntEQ(test_dtls13_pool_count(&pool), 1); - ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, - 1, 3, DTLS_TIMEOUT_MAX, NULL, &done), 0); - ExpectNull(DtlsChPoolTakeRest(&pool, NULL, &peer, 0, 2, 1, 4, - DTLS_TIMEOUT_MAX, NULL)); - ExpectIntEQ(test_dtls13_pool_count(&pool), 1); - ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, - 1, 3, DTLS_TIMEOUT_MAX, NULL, &done), 0); - ExpectNull(DtlsChPoolTakeRest(&pool, NULL, &peer, 0, 1, 1, 5, - DTLS_TIMEOUT_MAX, NULL)); + ExpectNull(DtlsChPoolTake(&pool, NULL, &peer, DTLS_TIMEOUT_MAX)); ExpectIntEQ(test_dtls13_pool_count(&pool), 1); peerBuf[0] = 2; ExpectNotNull(test_dtls13_pool_head(&pool)); @@ -5692,13 +5884,12 @@ int test_dtls13_ch_pool_unit(void) ExpectIntEQ(DtlsChPoolInit(&pool), 0); /* Adopting on the same heap keeps the message. A partial one comes from - * the pool: no part of it overlaps an empty first fragment. */ + * the pool with every bucket it held. */ ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, - 0, 2, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 0, 2, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, - 3, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); - ExpectNotNull(done = DtlsChPoolTakeRest(&pool, NULL, &peer, 0, 1, 0, 4, - DTLS_TIMEOUT_MAX, NULL)); + 3, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); + ExpectNotNull(done = DtlsChPoolTake(&pool, NULL, &peer, DTLS_TIMEOUT_MAX)); saved = done; ExpectIntEQ(DtlsMsgAdopt(&done, NULL, NULL), 0); ExpectPtrEq(done, saved); @@ -5730,10 +5921,10 @@ int test_dtls13_ch_pool_unit(void) DtlsMsgListDelete(done, (void*)&pool); /* and a complete one as a whole */ ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, - 0, 2, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 0, 2, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); ExpectNull(done); ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, - 2, 2, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 2, 2, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); ExpectNotNull(done); saved = done; ExpectIntEQ(DtlsMsgAdopt(&done, NULL, (void*)&pool), 0); @@ -5754,18 +5945,18 @@ int test_dtls13_ch_pool_unit(void) /* Only new bytes restart an entry's clock */ peerBuf[0] = 1; ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, - 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); ExpectNotNull(current = test_dtls13_pool_head(&pool)); if (current != NULL) current->heldAt -= (word32)DTLS_TIMEOUT_MAX / 2; saved = current; held = current != NULL ? current->heldAt : 0; ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, - 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); ExpectPtrEq(test_dtls13_pool_head(&pool), saved); ExpectTrue(current != NULL && current->heldAt == held); ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, - 2, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 2, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); ExpectPtrEq(test_dtls13_pool_head(&pool), saved); ExpectIntEQ(current != NULL ? (int)current->bytesReceived : -1, 2); ExpectTrue(current != NULL && current->heldAt != held); @@ -5774,7 +5965,7 @@ int test_dtls13_ch_pool_unit(void) test_dtls13_age(current); XMEMSET(alt, 0xa5, sizeof(alt)); ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, alt, 4, client_hello, - 1, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 1, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); ExpectNull(done); ExpectIntEQ(test_dtls13_pool_count(&pool), 1); ExpectNotNull(current = test_dtls13_pool_head(&pool)); @@ -5782,18 +5973,17 @@ int test_dtls13_ch_pool_unit(void) ExpectIntEQ(current != NULL && current->fragBucketList != NULL ? (int)current->fragBucketList->m.m.offset : -1, 1); ExpectTrue(test_dtls13_pool_ok(&pool)); - /* A rest whose clock ran out is freed, not returned */ + /* An entry whose clock ran out is freed, not taken */ DtlsChPoolClear(&pool, NULL); ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, - 1, 3, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 1, 3, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); test_dtls13_age(test_dtls13_pool_head(&pool)); - ExpectNull(DtlsChPoolTakeRest(&pool, NULL, &peer, 0, 1, 1, 4, - DTLS_TIMEOUT_MAX, NULL)); + ExpectNull(DtlsChPoolTake(&pool, NULL, &peer, DTLS_TIMEOUT_MAX)); ExpectIntEQ(test_dtls13_pool_count(&pool), 0); ExpectIntEQ(test_dtls13_pool_bytes(&pool), 0); /* Any store in its shard frees it */ ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, - 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); test_dtls13_age(test_dtls13_pool_head(&pool)); shard = DtlsChPoolShardIdx(&peer); for (i = 2; i < 256; i++) { @@ -5803,7 +5993,7 @@ int test_dtls13_ch_pool_unit(void) } ExpectIntLT(i, 256); ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, - 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); ExpectIntEQ(test_dtls13_pool_count(&pool), 1); ExpectNotNull(test_dtls13_pool_head(&pool)); h = test_dtls13_pool_head(&pool); @@ -5815,12 +6005,12 @@ int test_dtls13_ch_pool_unit(void) * age. The store frees the entry, so the fragment starts a new one. */ peerBuf[0] = 1; ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, - 1, 3, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 1, 3, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); ExpectNotNull(current = test_dtls13_pool_head(&pool)); if (current != NULL) current->heldAt += 3600; ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, - 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); ExpectNull(done); ExpectIntEQ(test_dtls13_pool_count(&pool), 1); ExpectNotNull(current = test_dtls13_pool_head(&pool)); @@ -5829,21 +6019,20 @@ int test_dtls13_ch_pool_unit(void) (int)current->fragBucketList->m.m.offset : -1, 0); ExpectTrue(test_dtls13_pool_ok(&pool)); DtlsChPoolClear(&pool, NULL); - /* A release frees it too: held 10 s before a 1 kHz 32-bit tick count + /* A take frees it too: held 10 s before a 1 kHz 32-bit tick count * wrapped */ ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, - 1, 3, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 1, 3, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); ExpectNotNull(saved = test_dtls13_pool_head(&pool)); peerBuf[0] = 2; ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, - 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); ExpectIntEQ(test_dtls13_pool_count(&pool), 2); ExpectPtrEq(test_dtls13_pool_head(&pool), saved); if (saved != NULL) saved->heldAt += (word32)(4294967UL - 10); peerBuf[0] = 1; - ExpectNull(DtlsChPoolTakeRest(&pool, NULL, &peer, 0, 1, 1, 4, - DTLS_TIMEOUT_MAX, NULL)); + ExpectNull(DtlsChPoolTake(&pool, NULL, &peer, DTLS_TIMEOUT_MAX)); ExpectIntEQ(test_dtls13_pool_count(&pool), 1); ExpectNotNull(test_dtls13_pool_head(&pool)); h = test_dtls13_pool_head(&pool); @@ -5853,13 +6042,13 @@ int test_dtls13_ch_pool_unit(void) /* Free leaves nothing, and the pool can be set up again */ ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, - 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); DtlsChPoolFree(&pool, NULL); ExpectIntEQ(test_dtls13_pool_count(&pool), 0); ExpectIntEQ(test_dtls13_pool_reserved(&pool), 0); ExpectIntEQ(DtlsChPoolInit(&pool), 0); ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, 4, client_hello, - 0, 1, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); ExpectTrue(test_dtls13_pool_ok(&pool)); DtlsChPoolFree(&pool, NULL); XFREE(data, NULL, DYNAMIC_TYPE_TMP_BUFFER); @@ -5867,6 +6056,458 @@ int test_dtls13_ch_pool_unit(void) return EXPECT_RESULT(); } +/* The head check: the contiguous bytes from offset 0, checked in place once + * they first exist and then once they at least double, so a message of n + * bytes is checked O(log n) times whatever its fragments. Bytes that are no + * ClientHello keep the entry, and the firings show in headReportAt. A + * completed message goes through *done alone. */ +int test_dtls13_ch_pool_head_report(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl = NULL; + struct test_memio_ctx test_ctx; + DtlsChPool pool; + byte peerBuf[4]; + WOLFSSL_SOCKADDR peer; + byte data[256]; + DtlsMsg* done = NULL; + DtlsMsg* cur = NULL; + DtlsMsg* verified = NULL; + byte isTls13 = 0; + word32 sz = (word32)sizeof(data); + word32 last = 0; + word32 reportAt = 0; + word32 i; + + for (i = 0; i < sz; i++) + data[i] = (byte)i; + XMEMSET(peerBuf, 0x09, sizeof(peerBuf)); + peer.sa = peerBuf; + peer.sz = peer.bufSz = (unsigned int)sizeof(peerBuf); + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx, &ssl_c, &ssl, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(DtlsChPoolInit(&pool), 0); + + /* A check needs somewhere to hand a verified entry and its version */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, sz, + client_hello, 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, ssl, NULL, + &isTls13), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, sz, + client_hello, 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, ssl, &verified, + NULL), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(test_dtls13_pool_count(&pool), 0); + + /* A fragment past the start leaves no head */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data + 8, sz, + client_hello, 8, 4, DTLS_TIMEOUT_MAX, NULL, &done, ssl, &verified, + &isTls13), 0); + ExpectNull(verified); + ExpectNotNull(cur = test_dtls13_pool_head(&pool)); + ExpectIntEQ(cur != NULL ? (int)cur->headReportAt : -1, 0); + + /* The first byte of the head is checked, then only from twice the size + * last checked */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, sz, + client_hello, 0, 1, DTLS_TIMEOUT_MAX, NULL, &done, ssl, &verified, + &isTls13), 0); + ExpectIntEQ(cur != NULL ? (int)cur->headReportAt : -1, 2); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data + 1, sz, + client_hello, 1, 1, DTLS_TIMEOUT_MAX, NULL, &done, ssl, &verified, + &isTls13), 0); + ExpectIntEQ(cur != NULL ? (int)cur->headReportAt : -1, 4); + /* Below the threshold: held, not checked */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data + 2, sz, + client_hello, 2, 1, DTLS_TIMEOUT_MAX, NULL, &done, ssl, &verified, + &isTls13), 0); + ExpectIntEQ(cur != NULL ? (int)cur->headReportAt : -1, 4); + ExpectIntEQ(cur != NULL ? (int)cur->bytesReceived : -1, 7); + /* A duplicate grows nothing */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, sz, + client_hello, 0, 3, DTLS_TIMEOUT_MAX, NULL, &done, ssl, &verified, + &isTls13), 0); + ExpectIntEQ(cur != NULL ? (int)cur->headReportAt : -1, 4); + /* At the threshold: checked */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data + 3, sz, + client_hello, 3, 1, DTLS_TIMEOUT_MAX, NULL, &done, ssl, &verified, + &isTls13), 0); + ExpectIntEQ(cur != NULL ? (int)cur->headReportAt : -1, 8); + /* The gap closed, the head takes in the bytes held past it */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data + 4, sz, + client_hello, 4, 4, DTLS_TIMEOUT_MAX, NULL, &done, ssl, &verified, + &isTls13), 0); + ExpectIntEQ(cur != NULL ? (int)cur->headReportAt : -1, 24); + ExpectNull(done); + ExpectNull(verified); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + /* The rest byte by byte: a check at 24, 48, 96 and 192, then the whole + * message through *done, unchecked */ + last = 12; + reportAt = 24; + for (i = 12; i < sz && EXPECT_SUCCESS(); i++) { + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data + i, sz, + client_hello, i, 1, DTLS_TIMEOUT_MAX, NULL, &done, ssl, &verified, + &isTls13), 0); + ExpectNull(verified); + if (cur->headReportAt != reportAt) { + ExpectIntEQ(cur->headReportAt, 2 * (i + 1)); + ExpectIntGE(i + 1, 2 * last); + ExpectNull(done); + last = i + 1; + reportAt = cur->headReportAt; + } + else { + ExpectIntLT(i + 1 < sz ? i + 1 : 0, 2 * last); + } + } + ExpectIntEQ(last, 192); + ExpectNotNull(done); + ExpectIntEQ(done != NULL ? done->ready : 0, 1); + ExpectIntEQ(done != NULL ? (int)done->headReportAt : -1, 384); + ExpectIntEQ(test_dtls13_pool_count(&pool), 0); + DtlsMsgListDelete(done, NULL); + done = NULL; + + /* A store with no check leaves the threshold alone */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data, sz, + client_hello, 0, 3, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), + 0); + ExpectNotNull(cur = test_dtls13_pool_head(&pool)); + ExpectIntEQ(cur != NULL ? (int)cur->headReportAt : -1, 0); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, data + 3, sz, + client_hello, 3, 1, DTLS_TIMEOUT_MAX, NULL, &done, ssl, &verified, + &isTls13), 0); + ExpectIntEQ(cur != NULL ? (int)cur->headReportAt : -1, 8); + ExpectNull(done); + ExpectNull(verified); + + DtlsChPoolFree(&pool, NULL); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) +/* A server past its HRR and the bodies of the two ClientHellos it saw: ch1 + * without a cookie, ch2 with the one its HRR gave. cut is the offset in ch2 + * just past the cookie: the head [0, cut) verifies on its own. */ +typedef struct test_dtls13_ch2 { + WOLFSSL_CTX* ctx_c; + WOLFSSL_CTX* ctx_s; + WOLFSSL* ssl_c; + WOLFSSL* ssl_s; + struct test_memio_ctx test_ctx; + byte ch1[2048]; + byte ch2[2048]; + word32 ch1Sz; + word32 ch2Sz; + word32 cut; +} test_dtls13_ch2; + +static int test_dtls13_ch2_setup(test_dtls13_ch2* t) +{ + EXPECT_DECLS; + const int hdrSz = DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ; + int group = WOLFSSL_ECC_SECP256R1; + byte ch[TEST_MEMIO_BUF_SZ]; + int chSz = (int)sizeof(ch); + int isHrr = 0; + int cut = 0; + + XMEMSET(t, 0, sizeof(*t)); + ExpectIntEQ(test_memio_setup(&t->test_ctx, &t->ctx_c, &t->ctx_s, + &t->ssl_c, &t->ssl_s, wolfDTLSv1_3_client_method, + wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(test_dtls13_server_peer(t->ssl_s, &t->test_ctx, 2), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(t->ssl_c, &group, 1), WOLFSSL_SUCCESS); + + /* CH1, kept and delivered, then the HRR with the cookie and CH2 */ + ExpectIntEQ(test_dtls13_take_ch(&t->test_ctx, t->ssl_c, ch, &chSz), + TEST_SUCCESS); + ExpectIntGT(chSz, hdrSz); + ExpectIntLE(chSz - hdrSz, (int)sizeof(t->ch1)); + if (EXPECT_SUCCESS()) { + t->ch1Sz = (word32)(chSz - hdrSz); + XMEMCPY(t->ch1, ch + hdrSz, t->ch1Sz); + } + ExpectIntEQ(test_memio_inject_message(&t->test_ctx, 0, (const char*)ch, + chSz), 0); + ExpectIntEQ(wolfSSL_accept(t->ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(t->ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_dtls13_reply_is_server_hello(&t->test_ctx, &isHrr), 1); + ExpectIntEQ(isHrr, 1); + chSz = (int)sizeof(ch); + ExpectIntEQ(test_dtls13_take_ch(&t->test_ctx, t->ssl_c, ch, &chSz), + TEST_SUCCESS); + ExpectIntGT(cut = test_dtls13_ch_cookie_end(ch, chSz), 0); + /* Bytes past the cookie: a head of cut bytes is a prefix */ + ExpectIntGT(chSz - hdrSz, cut + 1); + ExpectIntLE(chSz - hdrSz, (int)sizeof(t->ch2)); + if (EXPECT_SUCCESS()) { + t->ch2Sz = (word32)(chSz - hdrSz); + t->cut = (word32)cut; + XMEMCPY(t->ch2, ch + hdrSz, t->ch2Sz); + } + return EXPECT_RESULT(); +} + +static void test_dtls13_ch2_free(test_dtls13_ch2* t) +{ + wolfSSL_free(t->ssl_c); + wolfSSL_free(t->ssl_s); + wolfSSL_CTX_free(t->ctx_c); + wolfSSL_CTX_free(t->ctx_s); +} + +/* A pool with another peer's entry, whose head check will not run */ +static int test_dtls13_head_pool_setup(DtlsChPool* pool, const byte* data, + word32 sz) +{ + EXPECT_DECLS; + + ExpectIntEQ(DtlsChPoolInit(pool), 0); + ExpectIntEQ(test_dtls13_pool_put(pool, 0x77, data, sz, 0, 8), 0); + ExpectIntEQ(test_dtls13_pool_count(pool), 1); + return EXPECT_RESULT(); +} +#endif + +/* A head the server's check verifies: the entry leaves the pool through + * *verified in that same store, unlinked and with the counters settled, + * still keyed on the peer and holding every fragment, the ones past the + * head included */ +int test_dtls13_ch_pool_head_verified(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + test_dtls13_ch2 t; + DtlsChPool pool; + byte peerBuf[4]; + WOLFSSL_SOCKADDR peer; + DtlsMsg* done = NULL; + DtlsMsg* verified = NULL; + byte isTls13 = 0; + word32 sz = 0; + word32 cut = 0; + + ExpectIntEQ(test_dtls13_ch2_setup(&t), TEST_SUCCESS); + sz = t.ch2Sz; + cut = t.cut; + XMEMSET(peerBuf, 0x0a, sizeof(peerBuf)); + peer.sa = peerBuf; + peer.sz = peer.bufSz = (unsigned int)sizeof(peerBuf); + ExpectIntEQ(test_dtls13_head_pool_setup(&pool, t.ch2, sz), TEST_SUCCESS); + + /* The bytes past the cookie, short of it by one: held, no head */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, t.ch2 + cut + 1, + sz, client_hello, cut + 1, sz - cut - 1, DTLS_TIMEOUT_MAX, NULL, + &done, t.ssl_s, &verified, &isTls13), 0); + ExpectNull(verified); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); + + /* The head up to the cookie verifies, the gap after it still open */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, t.ch2, sz, + client_hello, 0, cut, DTLS_TIMEOUT_MAX, NULL, &done, t.ssl_s, + &verified, &isTls13), 0); + ExpectNull(done); + ExpectNotNull(verified); + ExpectIntEQ(isTls13, 1); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + ExpectIntEQ(test_dtls13_pool_bytes(&pool), + DtlsMsgListRetainedBytes(test_dtls13_pool_head(&pool))); + if (verified != NULL) { + ExpectIntEQ(verified->ready, 0); + ExpectIntEQ(verified->sz, sz); + ExpectIntEQ(verified->bytesReceived, sz - 1); + ExpectIntEQ(verified->fragBucketListCount, 2); + ExpectIntEQ(verified->headReportAt, 2 * cut); + ExpectNull(verified->next); + ExpectIntEQ(verified->peer.sz, sizeof(peerBuf)); + ExpectNotNull(verified->fragBucketList); + if (verified->fragBucketList != NULL) + ExpectBufEQ(verified->fragBucketList->buf, t.ch2, cut); + /* Handed to a connection like a completed entry */ + ExpectIntEQ(DtlsMsgAdopt(&verified, NULL, NULL), 0); + ExpectIntEQ(verified != NULL ? (int)verified->peer.sz : -1, 0); + DtlsMsgListDelete(verified, NULL); + verified = NULL; + } + + /* The peer starts over in the pool and the same head verifies again */ + isTls13 = 0; + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, t.ch2, sz, + client_hello, 0, cut, DTLS_TIMEOUT_MAX, NULL, &done, t.ssl_s, + &verified, &isTls13), 0); + ExpectNotNull(verified); + ExpectIntEQ(isTls13, 1); + ExpectIntEQ(verified != NULL ? (int)verified->bytesReceived : -1, + (int)cut); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + DtlsMsgListDelete(verified, NULL); + verified = NULL; + + DtlsChPoolFree(&pool, NULL); + test_dtls13_ch2_free(&t); +#endif + return EXPECT_RESULT(); +} + +/* A head whose cookie fails the server's check: the entry is freed in that + * same store, which returns INVALID_PARAMETER, and the peer may start over */ +int test_dtls13_ch_pool_head_bad_cookie(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + test_dtls13_ch2 t; + DtlsChPool pool; + byte peerBuf[4]; + WOLFSSL_SOCKADDR peer; + DtlsMsg* done = NULL; + DtlsMsg* verified = NULL; + byte isTls13 = 0; + word32 sz = 0; + word32 cut = 0; + + ExpectIntEQ(test_dtls13_ch2_setup(&t), TEST_SUCCESS); + sz = t.ch2Sz; + cut = t.cut; + /* The last byte of the cookie changed */ + if (cut > 0) + t.ch2[cut - 1] ^= 0x01; + XMEMSET(peerBuf, 0x0b, sizeof(peerBuf)); + peer.sa = peerBuf; + peer.sz = peer.bufSz = (unsigned int)sizeof(peerBuf); + ExpectIntEQ(test_dtls13_head_pool_setup(&pool, t.ch2, sz), TEST_SUCCESS); + + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, t.ch2 + cut + 1, + sz, client_hello, cut + 1, sz - cut - 1, DTLS_TIMEOUT_MAX, NULL, + &done, t.ssl_s, &verified, &isTls13), 0); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, t.ch2, sz, + client_hello, 0, cut, DTLS_TIMEOUT_MAX, NULL, &done, t.ssl_s, + &verified, &isTls13), WC_NO_ERR_TRACE(INVALID_PARAMETER)); + ExpectNull(done); + ExpectNull(verified); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectNull(DtlsChPoolTake(&pool, NULL, &peer, DTLS_TIMEOUT_MAX)); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + /* The peer starts over */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, t.ch2, sz, + client_hello, 0, cut, DTLS_TIMEOUT_MAX, NULL, &done, t.ssl_s, + &verified, &isTls13), WC_NO_ERR_TRACE(INVALID_PARAMETER)); + ExpectNull(verified); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + DtlsChPoolFree(&pool, NULL); + test_dtls13_ch2_free(&t); +#endif + return EXPECT_RESULT(); +} + +/* A head the server's check cannot judge, short of the cookie or with none: + * the entry stays and the next check waits for twice the size. The completed + * message goes through *done, unchecked. */ +int test_dtls13_ch_pool_head_kept(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ + && defined(WOLFSSL_DTLS_CH_FRAG) && defined(WOLFSSL_SEND_HRR_COOKIE) && \ + defined(HAVE_ECC) && !defined(NO_ECC256) + test_dtls13_ch2 t; + DtlsChPool pool; + byte peerBuf[4]; + WOLFSSL_SOCKADDR peer; + DtlsMsg* done = NULL; + DtlsMsg* cur = NULL; + DtlsMsg* verified = NULL; + byte isTls13 = 0; + word32 sz = 0; + word32 cut = 0; + + ExpectIntEQ(test_dtls13_ch2_setup(&t), TEST_SUCCESS); + sz = t.ch2Sz; + cut = t.cut; + XMEMSET(peerBuf, 0x0c, sizeof(peerBuf)); + peer.sa = peerBuf; + peer.sz = peer.bufSz = (unsigned int)sizeof(peerBuf); + ExpectIntEQ(test_dtls13_head_pool_setup(&pool, t.ch2, sz), TEST_SUCCESS); + + /* One byte short of the cookie: checked and kept */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, t.ch2, sz, + client_hello, 0, cut - 1, DTLS_TIMEOUT_MAX, NULL, &done, t.ssl_s, + &verified, &isTls13), 0); + ExpectNull(verified); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); + ExpectNotNull(cur = test_dtls13_held_for(&pool, 0x77)); + ExpectNotNull(cur = test_dtls13_pool_next(&pool, cur)); + ExpectIntEQ(cur != NULL ? (int)cur->headReportAt : -1, 2 * (cut - 1)); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + /* The cookie completes the head, short of the next size: not checked */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, t.ch2 + cut - 1, + sz, client_hello, cut - 1, 1, DTLS_TIMEOUT_MAX, NULL, &done, t.ssl_s, + &verified, &isTls13), 0); + ExpectNull(verified); + ExpectNull(done); + ExpectIntEQ(cur != NULL ? (int)cur->headReportAt : -1, 2 * (cut - 1)); + ExpectIntEQ(cur != NULL ? (int)cur->bytesReceived : -1, (int)cut); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); + + /* Complete: through *done, with no check */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, t.ch2 + cut, sz, + client_hello, cut, sz - cut, DTLS_TIMEOUT_MAX, NULL, &done, t.ssl_s, + &verified, &isTls13), 0); + ExpectNull(verified); + ExpectNotNull(done); + ExpectIntEQ(done != NULL ? done->ready : 0, 1); + ExpectIntEQ(done != NULL ? (int)done->headReportAt : -1, 2 * (cut - 1)); + ExpectIntEQ(test_dtls13_pool_count(&pool), 1); + ExpectTrue(test_dtls13_pool_ok(&pool)); + DtlsMsgListDelete(done, NULL); + done = NULL; + + /* The first ClientHello, with no cookie: all but its last byte is + * checked and kept */ + ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 1, t.ch1, t.ch1Sz, + client_hello, 0, t.ch1Sz - 1, DTLS_TIMEOUT_MAX, NULL, &done, t.ssl_s, + &verified, &isTls13), 0); + ExpectNull(verified); + ExpectNull(done); + ExpectIntEQ(test_dtls13_pool_count(&pool), 2); + ExpectNotNull(cur = test_dtls13_held_for(&pool, 0x77)); + ExpectNotNull(cur = test_dtls13_pool_next(&pool, cur)); + ExpectIntEQ(cur != NULL ? (int)cur->headReportAt : -1, + 2 * ((int)t.ch1Sz - 1)); + ExpectTrue(test_dtls13_pool_ok(&pool)); + + DtlsChPoolFree(&pool, NULL); + test_dtls13_ch2_free(&t); +#endif + return EXPECT_RESULT(); +} + /* A peer is its address bytes, whatever they look like: the same bytes are * one peer, any differing byte is another, padding of a sockaddr included */ int test_dtls13_ch_pool_peer_key(void) @@ -5893,11 +6534,11 @@ int test_dtls13_ch_pool_peer_key(void) pb.sz = pb.bufSz = (unsigned int)sizeof(o2); ExpectIntEQ(DtlsChPoolShardIdx(&pa), DtlsChPoolShardIdx(&pb)); ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &pa, 0, 0, data, 8, client_hello, - 0, 4, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 0, 4, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); ExpectNull(done); ExpectIntEQ(test_dtls13_pool_count(&pool), 1); ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &pb, 0, 0, data, 8, client_hello, - 4, 4, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 4, 4, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); ExpectNotNull(done); DtlsMsgListDelete(done, NULL); done = NULL; @@ -5906,9 +6547,9 @@ int test_dtls13_ch_pool_peer_key(void) /* One byte off is another peer */ o2[sizeof(o2) - 1] = 0x02; ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &pa, 0, 0, data, 8, client_hello, - 0, 4, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 0, 4, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &pb, 0, 0, data, 8, client_hello, - 4, 4, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 4, 4, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); ExpectNull(done); ExpectIntEQ(test_dtls13_pool_count(&pool), 2); ExpectTrue(test_dtls13_pool_ok(&pool)); @@ -5918,9 +6559,9 @@ int test_dtls13_ch_pool_peer_key(void) pb.sa = o1; pb.sz = pb.bufSz = (unsigned int)sizeof(o1) - 1; ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &pa, 0, 0, data, 8, client_hello, - 0, 4, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 0, 4, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &pb, 0, 0, data, 8, client_hello, - 4, 4, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 4, 4, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); ExpectNull(done); ExpectIntEQ(test_dtls13_pool_count(&pool), 2); ExpectTrue(test_dtls13_pool_ok(&pool)); @@ -5945,9 +6586,11 @@ int test_dtls13_ch_pool_peer_key(void) pb.sa = &b4; pb.sz = pb.bufSz = (unsigned int)sizeof(b4); ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &pa, 0, 0, data, 8, - client_hello, 0, 4, DTLS_TIMEOUT_MAX, NULL, &done), 0); + client_hello, 0, 4, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, + NULL), 0); ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &pb, 0, 0, data, 8, - client_hello, 4, 4, DTLS_TIMEOUT_MAX, NULL, &done), 0); + client_hello, 4, 4, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, + NULL), 0); ExpectNull(done); ExpectIntEQ(test_dtls13_pool_count(&pool), 2); ExpectTrue(test_dtls13_pool_ok(&pool)); @@ -5987,15 +6630,16 @@ int test_dtls13_ch_pool_ctx_init(void) ExpectNotNull(dtls->cookieSecret.lock); #endif ExpectIntEQ(DtlsChPoolStore(&tls->dtlsChPool, tls->heap, &peer, 0, 0, - data, 8, client_hello, 0, 4, DTLS_TIMEOUT_MAX, NULL, &done), - WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + data, 8, client_hello, 0, 4, DTLS_TIMEOUT_MAX, NULL, &done, NULL, + NULL, NULL), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); ExpectNull(done); - ExpectNull(DtlsChPoolTakeRest(&tls->dtlsChPool, tls->heap, &peer, 0, 0, - 4, 8, DTLS_TIMEOUT_MAX, NULL)); + ExpectNull(DtlsChPoolTake(&tls->dtlsChPool, tls->heap, &peer, + DTLS_TIMEOUT_MAX)); DtlsChPoolDrop(&tls->dtlsChPool, tls->heap, &peer, DTLS_TIMEOUT_MAX); DtlsChPoolClear(&tls->dtlsChPool, tls->heap); ExpectIntEQ(DtlsChPoolStore(&dtls->dtlsChPool, dtls->heap, &peer, 0, 0, - data, 8, client_hello, 0, 4, DTLS_TIMEOUT_MAX, NULL, &done), 0); + data, 8, client_hello, 0, 4, DTLS_TIMEOUT_MAX, NULL, &done, NULL, + NULL, NULL), 0); ExpectIntEQ(test_dtls13_pool_count(&dtls->dtlsChPool), 1); } wolfSSL_CTX_free(tls); @@ -6166,10 +6810,10 @@ int test_dtls13_ch_pool_max_age(void) /* Older than the default but within a longer maximum: kept */ ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, 8, client_hello, - 0, 4, longer, NULL, &done), 0); + 0, 4, longer, NULL, &done, NULL, NULL, NULL), 0); test_dtls13_age(test_dtls13_pool_head(&pool)); ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, 8, client_hello, - 4, 4, longer, NULL, &done), 0); + 4, 4, longer, NULL, &done, NULL, NULL, NULL), 0); ExpectNotNull(done); DtlsMsgListDelete(done, NULL); done = NULL; @@ -6177,26 +6821,25 @@ int test_dtls13_ch_pool_max_age(void) /* Older than the maximum: gone, and the fragment starts over */ ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, 8, client_hello, - 0, 4, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 0, 4, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); test_dtls13_age(test_dtls13_pool_head(&pool)); ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, 8, client_hello, - 4, 4, DTLS_TIMEOUT_MAX, NULL, &done), 0); + 4, 4, DTLS_TIMEOUT_MAX, NULL, &done, NULL, NULL, NULL), 0); ExpectNull(done); ExpectIntEQ(test_dtls13_pool_count(&pool), 1); DtlsChPoolClear(&pool, NULL); ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, 8, client_hello, - 4, 4, longer, NULL, &done), 0); + 4, 4, longer, NULL, &done, NULL, NULL, NULL), 0); test_dtls13_age(test_dtls13_pool_head(&pool)); - rest = DtlsChPoolTakeRest(&pool, NULL, &peer, 0, 0, 4, 8, longer, NULL); + rest = DtlsChPoolTake(&pool, NULL, &peer, longer); ExpectNotNull(rest); DtlsMsgListDelete(rest, NULL); rest = NULL; ExpectIntEQ(DtlsChPoolStore(&pool, NULL, &peer, 0, 0, data, 8, client_hello, - 4, 4, longer, NULL, &done), 0); + 4, 4, longer, NULL, &done, NULL, NULL, NULL), 0); test_dtls13_age(test_dtls13_pool_head(&pool)); - ExpectNull(DtlsChPoolTakeRest(&pool, NULL, &peer, 0, 0, 4, 8, - DTLS_TIMEOUT_MAX, NULL)); + ExpectNull(DtlsChPoolTake(&pool, NULL, &peer, DTLS_TIMEOUT_MAX)); ExpectIntEQ(test_dtls13_pool_count(&pool), 0); DtlsChPoolFree(&pool, NULL); @@ -6883,14 +7526,17 @@ static THREAD_RETURN WOLFSSL_THREAD test_dtls13_ch_shard_worker(void* args) quarter = (rnd >> 24) % 4; XMEMSET(data, addr[0] + 1, TEST_CH_SHARD_MSG_SZ); if (op < 24) { - msg = DtlsChPoolTakeRest(&test_ch_shard_pool, NULL, &peer, 0, 1, 0, - TEST_CH_SHARD_MSG_SZ, DTLS_TIMEOUT_MAX, NULL); + msg = DtlsChPoolTake(&test_ch_shard_pool, NULL, &peer, + DTLS_TIMEOUT_MAX); + if (msg != NULL && DtlsMsgAdopt(&msg, NULL, NULL) != 0) + test_ch_shard_bad[tno]++; } else { + /* No check: the bytes are no ClientHello */ (void)DtlsChPoolStore(&test_ch_shard_pool, NULL, &peer, 0, 1, data, TEST_CH_SHARD_MSG_SZ, client_hello, quarter * (TEST_CH_SHARD_MSG_SZ / 4), TEST_CH_SHARD_MSG_SZ / 4, - DTLS_TIMEOUT_MAX, NULL, &msg); + DTLS_TIMEOUT_MAX, NULL, &msg, NULL, NULL, NULL); } if (msg != NULL) { if (!test_ch_pool_msg_ok(msg, addr[0], TEST_CH_SHARD_MSG_SZ)) diff --git a/tests/api/test_dtls13.h b/tests/api/test_dtls13.h index 4900ece5a4f..529e592bbce 100644 --- a/tests/api/test_dtls13.h +++ b/tests/api/test_dtls13.h @@ -71,6 +71,8 @@ int test_dtls13_frag_store_fatal(void); int test_dtls13_clear_frees_rx_list(void); int test_dtls13_frag_ch2_bad_cookie_dropped(void); int test_dtls13_frag_ch2_dtls12_cookie(void); +int test_dtls13_frag_ch2_cookie_in_second(void); +int test_dtls13_frag_ch2_cookie_frag_out_of_order(void); int test_dtls13_ch_pool_threads(void); int test_dtls13_ch_pool_shards_threads(void); int test_dtls13_ch_pool_handshake_threads(void); @@ -80,6 +82,10 @@ int test_dtls13_ctx_cookie_secret_threads(void); int test_dtls13_ctx_cookie_secret_no_splice(void); int test_dtls13_ctx_cookie_secret_after_random(void); int test_dtls13_ch_pool_unit(void); +int test_dtls13_ch_pool_head_report(void); +int test_dtls13_ch_pool_head_verified(void); +int test_dtls13_ch_pool_head_bad_cookie(void); +int test_dtls13_ch_pool_head_kept(void); int test_dtls13_ch_pool_shards(void); int test_dtls13_ch_pool_evict_own_shard(void); int test_dtls13_ch_pool_evict_behind_own(void); @@ -145,6 +151,10 @@ int test_dtls13_update_keys_want_write(void); TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch1_interleaved_peers), \ TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch1_evict_oldest), \ TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_unit), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_head_report), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_head_verified), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_head_bad_cookie), \ + TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_head_kept), \ TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_shards), \ TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_evict_own_shard), \ TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_evict_behind_own), \ @@ -183,6 +193,8 @@ int test_dtls13_update_keys_want_write(void); TEST_DECL_GROUP("dtls13", test_dtls13_clear_frees_rx_list), \ TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch2_bad_cookie_dropped), \ TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch2_dtls12_cookie), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch2_cookie_in_second), \ + TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch2_cookie_frag_out_of_order), \ TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_threads), \ TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_shards_threads), \ TEST_DECL_GROUP("dtls13", test_dtls13_ch_pool_handshake_threads), \ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 78bb6d179c8..da566fff3e6 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -1252,7 +1252,8 @@ enum { #define WOLFSSL_DTLS_RX_MAX_BYTES (MAX_HANDSHAKE_SZ + 16UL * 1024UL) #endif /* The most a WOLFSSL_CTX holds for ClientHellos whose cookie has not verified, - * counted like the above: one budget for all its servers, one entry per peer. */ + * counted like the above: one budget for all its servers, one entry per + * peer. */ #ifndef WOLFSSL_DTLS_RX_UNVERIFIED_MAX_BYTES #define WOLFSSL_DTLS_RX_UNVERIFIED_MAX_BYTES (64UL * 1024UL) #endif @@ -6744,6 +6745,8 @@ typedef struct DtlsMsg { word32 heldAt; /* LowResTimer() when a pooled ClientHello * last gained bytes */ word32 progress; /* pool order of that gain */ + word32 headReportAt; /* contiguous head size at which the pool + * next runs the cookie check on it */ #endif } DtlsMsg; @@ -8292,7 +8295,7 @@ WOLFSSL_LOCAL int Tls13KeyUpdateLimitReached(WOLFSSL* ssl); #define DtlsChPoolClear wolfSSL_DtlsChPoolClear #define DtlsChPoolStore wolfSSL_DtlsChPoolStore #define DtlsChPoolDrop wolfSSL_DtlsChPoolDrop - #define DtlsChPoolTakeRest wolfSSL_DtlsChPoolTakeRest + #define DtlsChPoolTake wolfSSL_DtlsChPoolTake #define DtlsChPoolShardIdx wolfSSL_DtlsChPoolShardIdx #define DtlsMsgAdopt wolfSSL_DtlsMsgAdopt #endif /* WOLFSSL_API_PREFIX_MAP */ @@ -8330,12 +8333,12 @@ WOLFSSL_LOCAL int Tls13KeyUpdateLimitReached(WOLFSSL* ssl); WOLFSSL_TEST_VIS int DtlsChPoolStore(DtlsChPool* pool, void* heap, const WOLFSSL_SOCKADDR* peer, word16 epoch, word32 seq, const byte* data, word32 dataSz, byte type, word32 fragOffset, - word32 fragSz, word32 maxAge, void* outHeap, DtlsMsg** done); + word32 fragSz, word32 maxAge, void* outHeap, DtlsMsg** done, + const WOLFSSL* ssl, DtlsMsg** verified, byte* isTls13); WOLFSSL_TEST_VIS void DtlsChPoolDrop(DtlsChPool* pool, void* heap, const WOLFSSL_SOCKADDR* peer, word32 maxAge); - WOLFSSL_TEST_VIS DtlsMsg* DtlsChPoolTakeRest(DtlsChPool* pool, void* heap, - const WOLFSSL_SOCKADDR* peer, word16 epoch, word32 seq, - word32 fragSz, word32 msgSz, word32 maxAge, void* outHeap); + WOLFSSL_TEST_VIS DtlsMsg* DtlsChPoolTake(DtlsChPool* pool, void* heap, + const WOLFSSL_SOCKADDR* peer, word32 maxAge); WOLFSSL_TEST_VIS word32 DtlsChPoolShardIdx(const WOLFSSL_SOCKADDR* peer); WOLFSSL_TEST_VIS int DtlsMsgAdopt(DtlsMsg** msg, void* from, void* to); WOLFSSL_LOCAL int DtlsChPoolSetMaxBytes(DtlsChPool* pool, void* heap, @@ -8367,6 +8370,11 @@ WOLFSSL_LOCAL int Tls13KeyUpdateLimitReached(WOLFSSL* ssl); #if !defined(NO_WOLFSSL_SERVER) WOLFSSL_LOCAL int DoClientHelloStateless(WOLFSSL* ssl, const byte* input, word32 fragSz, word32 msgSz); + WOLFSSL_LOCAL int ClientHelloStatelessCommit(WOLFSSL* ssl, byte isTls13); +#ifdef WOLFSSL_DTLS_CH_FRAG + WOLFSSL_LOCAL int DoClientHelloStatelessHead(const WOLFSSL* ssl, + const byte* head, word32 headSz, word32 msgSz, byte* isTls13); +#endif #endif /* !defined(NO_WOLFSSL_SERVER) */ #if !defined(WOLFCRYPT_ONLY) && !defined(WOLFSSL_NO_SOCK) && \ (defined(USE_WOLFSSL_IO) || defined(WOLFSSL_USER_IO)) From 9a99878ee47b3edb984826d87d448ce5ddeec420 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Thu, 12 Nov 2026 16:21:14 +0000 Subject: [PATCH 10/10] Stamp handshake cookies with their issue time Put the issue time, 4 byte big endian seconds from LowResTimer(), under the MAC of both stateless cookies: at the end of the HelloRetryRequest cookie data and in front of the DTLS 1.2 HelloVerifyRequest MAC. After the MAC verifies, a cookie older than WOLFSSL_COOKIE_MAX_AGE seconds (60 by default, 0 disables the check) or from the future takes the invalid-cookie path: illegal_parameter for TLS 1.3 and DTLS 1.3, a new HelloVerifyRequest for DTLS 1.2. A captured cookie thus cannot be replayed later (RFC 9147 Section 5.1). The DTLS 1.2 cookie stays 32 bytes on the wire, the time and the leftmost 28 bytes of the HMAC-SHA256 (SP 800-107 truncation), so deployed clients capped at WOLFSSL_COOKIE_LEN keep working; the SHA-1 fallback keeps its full 20. Without a clock (NO_ASN_TIME) no time is sent or checked. CreateDtls12Cookie() takes the issue time and writes the whole cookie. Tests move the clock through wc_SetTimeCb() and check that a DTLS 1.3, DTLS 1.2 and TLS 1.3 server each reject a stale cookie and stay stateless, and verify it with the check disabled. --- ChangeLog.md | 7 ++ src/dtls.c | 112 +++++++++++++------ src/internal.c | 20 ++++ src/tls13.c | 38 ++++++- tests/api/test_dtls.c | 242 ++++++++++++++++++++++++++++++++++++++++ tests/api/test_dtls.h | 6 + tests/api/test_dtls13.c | 27 ++++- wolfssl/internal.h | 9 ++ 8 files changed, 418 insertions(+), 43 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 2e3b78b8a4d..f1d97acefbe 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -100,6 +100,13 @@ object, and NULL to either now drops the object's own secret in favour of the context's. A DTLS 1.3 cookie is checked before the SNI callback, so a `wolfSSL_set_SSL_CTX()` made there cannot change the secret the check uses. + Both cookies now carry their issue time under the MAC and are rejected + `WOLFSSL_COOKIE_MAX_AGE` seconds later (60 by default, 0 disables the age + check), so a captured cookie cannot be replayed once that has passed and + the secret need not be rotated for that. The DTLS 1.2 cookie stays 32 + bytes on the wire: the time and the leftmost 28 bytes of the HMAC, so + deployed clients, which accept no more than `WOLFSSL_COOKIE_LEN`, keep + working. Without a clock (`NO_ASN_TIME`) no time is sent or checked. * **Behavioral change (DSA now uses the leftmost |q| bytes of the digest)**: `wc_DsaSign_ex()`/`wc_DsaVerify_ex()` read the whole digest, and the EVP DSA diff --git a/src/dtls.c b/src/dtls.c index 12a3801dd9b..6e0a3591242 100644 --- a/src/dtls.c +++ b/src/dtls.c @@ -62,6 +62,16 @@ * one datagram, at the cost of a HelloRetryRequest round trip. * With the cookie exchange disabled the server is stateful from the first * datagram and reassembles any ClientHello. + * WOLFSSL_COOKIE_MAX_AGE + * Both cookies, the DTLS 1.2 HelloVerifyRequest one and the DTLS 1.3 + * HelloRetryRequest one, carry their issue time under the MAC and stop + * verifying this many seconds later (RFC 9147 Section 5.1), so a captured + * cookie cannot be replayed once the interval has passed. A stale cookie + * is treated as a bad one: a new HelloVerifyRequest in DTLS 1.2, an + * illegal_parameter alert in DTLS 1.3. Default 60; 0 disables the age + * check. Without a clock (NO_ASN_TIME) no time is sent or checked. The + * DTLS 1.2 cookie stays 32 bytes, the time and a truncated HMAC, since + * clients accept no more than WOLFSSL_COOKIE_LEN. * WOLFSSL_DTLS_RX_MAX_BYTES * Bound on the bytes the receive list of one WOLFSSL retains for * out-of-order and fragmented handshake messages, DTLS 1.2 and 1.3, @@ -211,14 +221,32 @@ void DtlsSetSeqNumForReply(WOLFSSL* ssl) #if !defined(NO_SHA) && defined(NO_SHA256) #define DTLS_COOKIE_TYPE WC_SHA -#define DTLS_COOKIE_SZ WC_SHA_DIGEST_SIZE +#define DTLS_COOKIE_MAC_SZ WC_SHA_DIGEST_SIZE #endif /* !NO_SHA && NO_SHA256 */ #ifndef NO_SHA256 #define DTLS_COOKIE_TYPE WC_SHA256 -#define DTLS_COOKIE_SZ WC_SHA256_DIGEST_SIZE +#define DTLS_COOKIE_MAC_SZ WC_SHA256_DIGEST_SIZE #endif /* !NO_SHA256 */ +/* DTLS 1.2 cookie = issue time (4 bytes, big endian seconds) | HMAC over the + * time, peer address, version, random, session ID, cipher suites and + * compression. CreateDtls12Cookie writes both parts. Without a clock + * (NO_ASN_TIME) the time is left out and the whole HMAC is sent. */ +#ifndef NO_ASN_TIME +#ifndef NO_SHA256 +/* Only the leftmost 28 bytes of HMAC-SHA256 go in the cookie (SP 800-107): + * clients cap the cookie at WOLFSSL_COOKIE_LEN, 32, and the time takes 4 */ +#define DTLS_COOKIE_MAC_USED_SZ 28 +#else +#define DTLS_COOKIE_MAC_USED_SZ DTLS_COOKIE_MAC_SZ +#endif +#define DTLS_COOKIE_SZ (OPAQUE32_LEN + DTLS_COOKIE_MAC_USED_SZ) +#else +#define DTLS_COOKIE_MAC_USED_SZ DTLS_COOKIE_MAC_SZ +#define DTLS_COOKIE_SZ DTLS_COOKIE_MAC_SZ +#endif + #if defined(WOLFSSL_DTLS13) && (defined(HAVE_SESSION_TICKET) || \ !defined(NO_PSK)) typedef struct PskInfo { @@ -244,9 +272,6 @@ typedef struct WolfSSL_CH { WolfSSL_ConstVector cookieExt; const byte* raw; word32 length; - /* Store the DTLS 1.2 cookie since we can just compute it once in dtls.c */ - byte dtls12cookie[DTLS_COOKIE_SZ]; - byte dtls12cookieSet:1; } WolfSSL_CH; static word32 ReadVector8(const byte* input, WolfSSL_ConstVector* v) @@ -265,10 +290,14 @@ static word32 ReadVector16(const byte* input, WolfSSL_ConstVector* v) return v->size + OPAQUE16_LEN; } +/* Write a DTLS_COOKIE_SZ byte cookie: the issue time (with a clock) then the + * MAC over it and the ClientHello */ static int CreateDtls12Cookie(const WOLFSSL* ssl, const WolfSSL_CH* ch, - const byte* secret, word32 secretSz, byte* cookie) + const byte* secret, word32 secretSz, + word32 issued, byte* cookie) { int ret; + byte mac[DTLS_COOKIE_MAC_SZ]; WC_DECLARE_VAR(cookieHmac, Hmac, 1, ssl->heap); if (secret == NULL || secretSz == 0) { @@ -282,6 +311,14 @@ 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, secret, secretSz); +#ifndef NO_ASN_TIME + if (ret == 0) { + c32toa(issued, cookie); + ret = wc_HmacUpdate(cookieHmac, cookie, OPAQUE32_LEN); + } +#else + (void)issued; +#endif if (ret == 0) { /* peerLock not necessary. Still in handshake phase. */ ret = wc_HmacUpdate(cookieHmac, @@ -305,9 +342,13 @@ static int CreateDtls12Cookie(const WOLFSSL* ssl, const WolfSSL_CH* ch, ch->compression.size); } if (ret == 0) - ret = wc_HmacFinal(cookieHmac, cookie); + ret = wc_HmacFinal(cookieHmac, mac); wc_HmacFree(cookieHmac); } + if (ret == 0) { + XMEMCPY(cookie + DTLS_COOKIE_SZ - DTLS_COOKIE_MAC_USED_SZ, mac, + DTLS_COOKIE_MAC_USED_SZ); + } WC_FREE_VAR_EX(cookieHmac, ssl->heap, DYNAMIC_TYPE_HMAC); return ret; @@ -340,35 +381,41 @@ static int CheckDtlsCookie(const WOLFSSL* ssl, WolfSSL_CH* ch, #endif { CookieSecrets cs; + byte cookie[DTLS_COOKIE_SZ]; + word32 issued = 0; if (ch->cookie.size != DTLS_COOKIE_SZ) return 0; ret = CookieSecretGet(ssl, CTX_COOKIE_DTLS12, &cs); if (ret != 0) return ret; - if (!ch->dtls12cookieSet) { - ret = CreateDtls12Cookie(ssl, ch, cs.pri, cs.priSz, - ch->dtls12cookie); - if (ret == 0) - ch->dtls12cookieSet = 1; - } +#ifndef NO_ASN_TIME + /* Rebuild with the issue time the peer echoes, its age is checked + * after */ + ato32(ch->cookie.elements, &issued); +#endif + ret = CreateDtls12Cookie(ssl, ch, cs.pri, cs.priSz, issued, cookie); if (ret == 0) { - *cookieGood = ConstantCompare(ch->cookie.elements, - ch->dtls12cookie, + *cookieGood = ConstantCompare(ch->cookie.elements, cookie, DTLS_COOKIE_SZ) == 0; } /* The secondary, verify only, keeps cookies issued before a rotation * of the secret valid */ if (ret == 0 && !*cookieGood && cs.sec != NULL) { - byte altCookie[DTLS_COOKIE_SZ]; - - ret = CreateDtls12Cookie(ssl, ch, cs.sec, cs.secSz, altCookie); + ret = CreateDtls12Cookie(ssl, ch, cs.sec, cs.secSz, issued, + cookie); if (ret == 0) { - *cookieGood = ConstantCompare(ch->cookie.elements, altCookie, + *cookieGood = ConstantCompare(ch->cookie.elements, cookie, DTLS_COOKIE_SZ) == 0; } } CookieSecretsClear(&cs); +#ifndef NO_ASN_TIME + if (ret == 0 && *cookieGood && CookieExpired(ch->cookie.elements)) { + WOLFSSL_MSG("DTLS 1.2 cookie too old"); + *cookieGood = 0; + } +#endif } return ret; } @@ -1014,21 +1061,22 @@ static int SendStatelessReply(const WOLFSSL* ssl, WolfSSL_CH* ch, byte isTls13) #endif { #if !defined(WOLFSSL_NO_TLS12) - if (!ch->dtls12cookieSet) { - CookieSecrets cs; + CookieSecrets cs; + byte cookie[DTLS_COOKIE_SZ]; + word32 issued = 0; - ret = CookieSecretGet(ssl, CTX_COOKIE_DTLS12, &cs); - if (ret == 0) { - ret = CreateDtls12Cookie(ssl, ch, cs.pri, cs.priSz, - ch->dtls12cookie); - CookieSecretsClear(&cs); - } - if (ret != 0) - return ret; - ch->dtls12cookieSet = 1; + ret = CookieSecretGet(ssl, CTX_COOKIE_DTLS12, &cs); + if (ret == 0) { +#ifndef NO_ASN_TIME + issued = LowResTimer(); +#endif + ret = CreateDtls12Cookie(ssl, ch, cs.pri, cs.priSz, issued, + cookie); + CookieSecretsClear(&cs); } - ret = SendHelloVerifyRequest((WOLFSSL*)ssl, ch->dtls12cookie, - DTLS_COOKIE_SZ); + if (ret != 0) + return ret; + ret = SendHelloVerifyRequest((WOLFSSL*)ssl, cookie, DTLS_COOKIE_SZ); #else WOLFSSL_MSG("DTLS1.2 disabled with WOLFSSL_NO_TLS12"); WOLFSSL_ERROR_VERBOSE(NOT_COMPILED_IN); diff --git a/src/internal.c b/src/internal.c index 4d13369491c..260c5486a29 100644 --- a/src/internal.c +++ b/src/internal.c @@ -13409,6 +13409,26 @@ ProtocolVersion MakeDTLSv1_3(void) */ #endif /* !NO_ASN_TIME */ +#ifndef NO_ASN_TIME +/* Is a cookie issued at the given time, 4 byte big endian seconds, older + * than WOLFSSL_COOKIE_MAX_AGE? A time from the future is rejected too. */ +int CookieExpired(const byte* issued) +{ +#if WOLFSSL_COOKIE_MAX_AGE > 0 + word32 at; + word32 now = LowResTimer(); + + ato32(issued, &at); + if (now < at) + return 1; + return now - at > (word32)WOLFSSL_COOKIE_MAX_AGE; +#else + (void)issued; + return 0; +#endif +} +#endif /* !NO_ASN_TIME */ + #ifndef NO_TLS #if !defined(WOLFSSL_NO_CLIENT_AUTH) && \ diff --git a/src/tls13.c b/src/tls13.c index 291a22d9458..e7336f7098b 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -3989,6 +3989,10 @@ byte SuiteMac(const byte* suite) #if defined(WOLFSSL_SEND_HRR_COOKIE) && !defined(NO_WOLFSSL_SERVER) /* Create Cookie extension using the hash of the first ClientHello. + * + * Cookie = Hash Len | Hash | CS | KeyShare Group | Issue Time | MAC + * The issue time, 4 byte big endian seconds, is left out without a clock + * (NO_ASN_TIME). The MAC also covers the peer address for DTLS. * * ssl SSL/TLS object. * hash The hash data. @@ -4003,7 +4007,8 @@ int CreateCookieExt(const WOLFSSL* ssl, byte* hash, word16 hashSz, WC_DECLARE_VAR(cookieHmac, Hmac, 1, ssl->heap); byte cookieType = 0; byte macSz = 0; - byte cookie[OPAQUE8_LEN + WC_MAX_DIGEST_SIZE + OPAQUE16_LEN * 2]; + byte cookie[OPAQUE8_LEN + WC_MAX_DIGEST_SIZE + OPAQUE16_LEN * 2 + + OPAQUE32_LEN]; TLSX* ext; word16 cookieSz = 0; CookieSecrets cs; @@ -4012,7 +4017,7 @@ int CreateCookieExt(const WOLFSSL* ssl, byte* hash, word16 hashSz, return BAD_FUNC_ARG; } - /* Cookie Data = Hash Len | Hash | CS | KeyShare Group */ + /* Cookie Data = Hash Len | Hash | CS | KeyShare Group | Issue Time */ cookie[cookieSz++] = (byte)hashSz; XMEMCPY(cookie + cookieSz, hash, hashSz); cookieSz += hashSz; @@ -4028,6 +4033,10 @@ int CreateCookieExt(const WOLFSSL* ssl, byte* hash, word16 hashSz, c16toa(kse->group, cookie + cookieSz); cookieSz += OPAQUE16_LEN; } +#ifndef NO_ASN_TIME + c32toa(LowResTimer(), cookie + cookieSz); + cookieSz += OPAQUE32_LEN; +#endif #ifndef NO_SHA256 cookieType = WC_SHA256; @@ -7761,18 +7770,21 @@ static int TlsCheckCookieMac(const WOLFSSL* ssl, const byte* cookie, return 0; } -/* Check that the Cookie data's integrity. +/* Check that the Cookie data's integrity, then that the cookie is not older + * than WOLFSSL_COOKIE_MAX_AGE (layout in CreateCookieExt()). * * ssl SSL/TLS object. * cookie The cookie data - hash and MAC. * cookieSz The length of the cookie data in bytes. - * returns Length of the hash on success, otherwise failure. + * returns Length of the cookie data before the MAC on success, otherwise + * failure: HRR_COOKIE_ERROR for a bad or stale cookie. */ int TlsCheckCookie(const WOLFSSL* ssl, const byte* cookie, word16 cookieSz) { int ret; byte cookieType = 0; byte macSz = 0; + word16 minSz; CookieSecrets cs; #ifndef NO_SHA256 @@ -7795,7 +7807,11 @@ int TlsCheckCookie(const WOLFSSL* ssl, const byte* cookie, word16 cookieSz) if (ret != 0) return ret; - if (cookieSz < ssl->specs.hash_size + macSz) { + minSz = (word16)(ssl->specs.hash_size + macSz); +#ifndef NO_ASN_TIME + minSz += OPAQUE32_LEN; +#endif + if (cookieSz < minSz) { CookieSecretsClear(&cs); return HRR_COOKIE_ERROR; } @@ -7815,6 +7831,14 @@ int TlsCheckCookie(const WOLFSSL* ssl, const byte* cookie, word16 cookieSz) WOLFSSL_ERROR_VERBOSE(HRR_COOKIE_ERROR); return ret; } +#ifndef NO_ASN_TIME + /* The issue time ends the cookie data */ + if (CookieExpired(cookie + cookieSz - OPAQUE32_LEN)) { + WOLFSSL_MSG("HRR cookie too old"); + WOLFSSL_ERROR_VERBOSE(HRR_COOKIE_ERROR); + return HRR_COOKIE_ERROR; + } +#endif return cookieSz; } @@ -7860,6 +7884,10 @@ static int RestartHandshakeHashWithCookie(WOLFSSL* ssl, Cookie* cookie) if (ret < 0) return ret; cookieDataSz = (word16)ret; +#ifndef NO_ASN_TIME + /* The issue time is not part of the HelloRetryRequest */ + cookieDataSz -= OPAQUE32_LEN; +#endif hashSz = cookie->data[0]; cookieData = cookie->data; idx = OPAQUE8_LEN; diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index 2ad340eb693..92bb98240a4 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -9575,6 +9575,248 @@ int test_dtls12_cookie_secret_issue_uses_primary(void) return EXPECT_RESULT(); } +/*-- cookie age --------------------------------------------------------------*/ +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_ASN_TIME) && !defined(NO_ASN) && !defined(USER_TICKS) && \ + !defined(USE_WINDOWS_API) +/* The cookie age tests move the clock with wc_SetTimeCb(), which reaches + * LowResTimer() on the default, POSIX time, build only */ +#define TEST_COOKIE_AGE +#endif + +#ifdef TEST_COOKIE_AGE +static time_t test_cookie_clock_skew = 0; + +static time_t test_cookie_clock_cb(time_t* t) +{ + time_t now = XTIME(NULL) + test_cookie_clock_skew; + if (t != NULL) + *t = now; + return now; +} + +/* Seconds past the limit: with the age check disabled, an hour */ +static time_t test_cookie_skew(void) +{ +#if WOLFSSL_COOKIE_MAX_AGE > 0 + return (time_t)WOLFSSL_COOKIE_MAX_AGE + 1; +#else + return 3601; +#endif +} + +/* Run the server over the second ClientHello with the clock moved skew + * seconds, so its cookie is that much older, or newer when negative */ +static int test_cookie_age_accept(WOLFSSL* ssl_s, time_t skew) +{ + EXPECT_DECLS; + int ret; + + test_cookie_clock_skew = skew; + ExpectIntEQ(wc_SetTimeCb(test_cookie_clock_cb), 0); + ret = wolfSSL_accept(ssl_s); + ExpectIntEQ(wc_SetTimeCb(NULL), 0); + test_cookie_clock_skew = 0; + ExpectIntEQ(ret, -1); + return EXPECT_RESULT(); +} +#endif + +#if defined(TEST_COOKIE_AGE) && defined(WOLFSSL_DTLS13) && \ + defined(WOLFSSL_SEND_HRR_COOKIE) +static int test_dtls13_hrr_cookie_age_ex(time_t skew) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + 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(test_cookie_age_accept(ssl_s, skew), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); +#if WOLFSSL_COOKIE_MAX_AGE > 0 + /* A stale cookie is a bad one: a fatal illegal_parameter alert and the + * server stays stateless, ready for the next ClientHello */ + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectIntGT(test_ctx.c_len, DTLS_RECORD_HEADER_SZ + 1); + ExpectIntEQ(test_ctx.c_buff[0], alert); + ExpectIntEQ(test_ctx.c_buff[DTLS_RECORD_HEADER_SZ], alert_fatal); + ExpectIntEQ(test_ctx.c_buff[DTLS_RECORD_HEADER_SZ + 1], illegal_parameter); + test_memio_clear_buffer(&test_ctx, 1); + wolfSSL_free(ssl_c); + ssl_c = NULL; + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, NULL, + wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); +#else + /* The age check is disabled: the cookie verifies */ + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + ExpectIntEQ(test_ctx.c_len > DTLS_RECORD_HEADER_SZ && + test_ctx.c_buff[0] == alert, 0); +#endif + (void)group; + 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); + return EXPECT_RESULT(); +} +#endif + +/* A DTLS 1.3 HelloRetryRequest cookie older than WOLFSSL_COOKIE_MAX_AGE, or + * from the future, is rejected like a forged one. */ +int test_dtls13_hrr_cookie_age(void) +{ + EXPECT_DECLS; +#if defined(TEST_COOKIE_AGE) && defined(WOLFSSL_DTLS13) && \ + defined(WOLFSSL_SEND_HRR_COOKIE) + ExpectIntEQ(test_dtls13_hrr_cookie_age_ex(test_cookie_skew()), + TEST_SUCCESS); + ExpectIntEQ(test_dtls13_hrr_cookie_age_ex(-test_cookie_skew()), + TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +#if defined(TEST_COOKIE_AGE) && !defined(WOLFSSL_NO_TLS12) +static int test_dtls12_cookie_age_ex(time_t skew) +{ + EXPECT_DECLS; + 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(test_cookie_age_accept(ssl_s, skew), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntGT(test_ctx.c_len, DTLS_RECORD_HEADER_SZ); + ExpectIntEQ(test_ctx.c_buff[0], handshake); +#if WOLFSSL_COOKIE_MAX_AGE > 0 + /* A stale cookie is a bad one: a new HelloVerifyRequest and the server + * stays stateless. A fresh client gets a fresh cookie. */ + ExpectIntEQ(ssl_s->options.dtlsStateful, 0); + ExpectIntEQ(test_ctx.c_buff[DTLS_RECORD_HEADER_SZ], hello_verify_request); + test_memio_clear_buffer(&test_ctx, 1); + wolfSSL_free(ssl_c); + ssl_c = NULL; + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, NULL, + wolfDTLSv1_2_client_method, wolfDTLSv1_2_server_method), 0); +#else + /* The age check is disabled: the cookie verifies */ + ExpectIntEQ(ssl_s->options.dtlsStateful, 1); + ExpectIntEQ(test_ctx.c_buff[DTLS_RECORD_HEADER_SZ], server_hello); +#endif + 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); + return EXPECT_RESULT(); +} +#endif + +/* As test_dtls13_hrr_cookie_age() for the DTLS 1.2 HelloVerifyRequest + * cookie. */ +int test_dtls12_cookie_age(void) +{ + EXPECT_DECLS; +#if defined(TEST_COOKIE_AGE) && !defined(WOLFSSL_NO_TLS12) + ExpectIntEQ(test_dtls12_cookie_age_ex(test_cookie_skew()), TEST_SUCCESS); + ExpectIntEQ(test_dtls12_cookie_age_ex(-test_cookie_skew()), TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + +#if defined(TEST_COOKIE_AGE) && defined(WOLFSSL_TLS13) && \ + defined(WOLFSSL_SEND_HRR_COOKIE) && defined(HAVE_ECC) && \ + !defined(NO_ECC256) +static int test_tls13_hrr_cookie_age_ex(time_t skew) +{ + EXPECT_DECLS; + WOLFSSL_CTX *ctx_c = NULL; + WOLFSSL_CTX *ctx_s = NULL; + WOLFSSL *ssl_c = NULL; + WOLFSSL *ssl_s = NULL; + struct test_memio_ctx test_ctx; + int group = WOLFSSL_ECC_SECP256R1; + byte s1[32]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + XMEMSET(s1, 0x5A, sizeof(s1)); + 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_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_s, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_send_hrr_cookie(ssl_s, s1, sizeof(s1)), + WOLFSSL_SUCCESS); + + /* CH1, HRR with cookie, CH2 */ + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(ssl_s), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_memio_msg_is_hello_retry_request(&test_ctx), 1); + ExpectIntEQ(wolfSSL_connect(ssl_c), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + + ExpectIntEQ(test_cookie_age_accept(ssl_s, skew), TEST_SUCCESS); +#if WOLFSSL_COOKIE_MAX_AGE > 0 + /* A stale cookie is a bad one: a fatal illegal_parameter alert */ + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), + WC_NO_ERR_TRACE(HRR_COOKIE_ERROR)); + ExpectIntGT(test_ctx.c_len, RECORD_HEADER_SZ + 1); + ExpectIntEQ(test_ctx.c_buff[0], alert); + ExpectIntEQ(test_ctx.c_buff[RECORD_HEADER_SZ], alert_fatal); + ExpectIntEQ(test_ctx.c_buff[RECORD_HEADER_SZ + 1], illegal_parameter); +#else + /* The age check is disabled: the cookie verifies */ + ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); +#endif + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); + return EXPECT_RESULT(); +} +#endif + +/* As test_dtls13_hrr_cookie_age() for a TLS 1.3 server + * (wolfSSL_send_hrr_cookie()). */ +int test_tls13_hrr_cookie_age(void) +{ + EXPECT_DECLS; +#if defined(TEST_COOKIE_AGE) && defined(WOLFSSL_TLS13) && \ + defined(WOLFSSL_SEND_HRR_COOKIE) && defined(HAVE_ECC) && \ + !defined(NO_ECC256) + ExpectIntEQ(test_tls13_hrr_cookie_age_ex(test_cookie_skew()), + TEST_SUCCESS); + ExpectIntEQ(test_tls13_hrr_cookie_age_ex(-test_cookie_skew()), + TEST_SUCCESS); +#endif + return EXPECT_RESULT(); +} + /* Shared with tests/api/test_dtls13.c. */ #if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS) /* A DTLS 1.3 ClientHello without a cookie, split into four records. */ diff --git a/tests/api/test_dtls.h b/tests/api/test_dtls.h index b2d89fb479a..949c482bf00 100644 --- a/tests/api/test_dtls.h +++ b/tests/api/test_dtls.h @@ -182,6 +182,9 @@ int test_dtls_cookie_policy_neither_end(void); int test_dtls13_hrr_cookie_state_guard(void); int test_dtls12_cookie_secret_generate_fail(void); int test_dtls13_hrr_cookie_secret_generate_fail(void); +int test_dtls13_hrr_cookie_age(void); +int test_dtls12_cookie_age(void); +int test_tls13_hrr_cookie_age(void); int test_dtls_max_fragment_write(void); int test_dtls_set_rfd_wfd(void); int test_dtls_ctx_cookie_secret_api(void); @@ -433,6 +436,9 @@ int test_dtls_cs_uses_ctx(const WOLFSSL* s, int type, const byte* p, TEST_DECL_GROUP("dtls", test_dtls13_hrr_cookie_secret_secondary_disabled), \ TEST_DECL_GROUP("dtls", test_dtls12_cookie_secret_generate_fail), \ TEST_DECL_GROUP("dtls", test_dtls13_hrr_cookie_secret_generate_fail), \ + TEST_DECL_GROUP("dtls", test_dtls13_hrr_cookie_age), \ + TEST_DECL_GROUP("dtls", test_dtls12_cookie_age), \ + TEST_DECL_GROUP("tls13", test_tls13_hrr_cookie_age), \ TEST_DECL_GROUP("dtls", test_dtls_max_fragment_write), \ TEST_DECL_GROUP("dtls", test_dtls_set_rfd_wfd), \ TEST_DECL_GROUP("dtls", test_dtls_ctx_cookie_secret_api), \ diff --git a/tests/api/test_dtls13.c b/tests/api/test_dtls13.c index dd6e882cb8b..fec9f553679 100644 --- a/tests/api/test_dtls13.c +++ b/tests/api/test_dtls13.c @@ -5337,7 +5337,12 @@ int test_dtls13_ctx_cookie_secret_no_splice(void) byte rnd[RAN_LEN]; byte hvrCh[DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ + OPAQUE16_LEN + RAN_LEN + 1 + 1 + OPAQUE16_LEN + 1 + sizeof(v)]; + /* The issue time leads the HelloVerifyRequest cookie, with a clock, and + * the MAC is cut to make room for it: the rest is left zero here */ + byte ts[OPAQUE32_LEN]; + word32 tsSz = 0; byte mac[WC_SHA256_DIGEST_SIZE]; + word32 macSz = sizeof(mac); byte ch[TEST_MEMIO_BUF_SZ]; byte* p; word32 chSz = 0; @@ -5347,9 +5352,14 @@ int test_dtls13_ctx_cookie_secret_no_splice(void) word32 cookieSz; word16 len16 = 0; +#ifndef NO_ASN_TIME + tsSz = OPAQUE32_LEN; + macSz -= tsSz; +#endif XMEMSET(&t, 0, sizeof(t)); XMEMSET(v, 0x56, sizeof(v)); XMEMSET(rnd, 0x52, sizeof(rnd)); + XMEMSET(ts, 0, sizeof(ts)); XMEMSET(mac, 0, sizeof(mac)); ExpectIntEQ(test_memio_setup(&t, &ctx_c, &ctx_s, &ssl_c, &ssl_s, wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method), 0); @@ -5397,18 +5407,21 @@ int test_dtls13_ctx_cookie_secret_no_splice(void) ExpectIntEQ(wolfSSL_get_error(ssl_s, -1), WOLFSSL_ERROR_WANT_READ); ExpectIntEQ(ssl_s->options.dtlsStateful, 0); idx = DTLS_RECORD_HEADER_SZ + DTLS_HANDSHAKE_HEADER_SZ + OPAQUE16_LEN; - ExpectIntGE(t.c_len, idx + 1 + sizeof(mac)); + ExpectIntGE(t.c_len, idx + 1 + tsSz + macSz); ExpectIntEQ(t.c_buff[0], handshake); ExpectIntEQ(t.c_buff[DTLS_RECORD_HEADER_SZ], hello_verify_request); - ExpectIntEQ(t.c_buff[idx], sizeof(mac)); - if (EXPECT_SUCCESS()) - XMEMCPY(mac, t.c_buff + idx + 1, sizeof(mac)); + ExpectIntEQ(t.c_buff[idx], tsSz + macSz); + if (EXPECT_SUCCESS()) { + XMEMCPY(ts, t.c_buff + idx + 1, tsSz); + XMEMCPY(mac, t.c_buff + idx + 1 + tsSz, macSz); + } test_memio_clear_buffer(&t, 1); - /* A real first ClientHello, given the spliced cookie */ + /* A real first ClientHello, given the spliced cookie: the time first, + * as the HelloVerifyRequest MAC took it */ ExpectIntEQ(wolfSSL_connect(ssl_c), -1); ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); - cookieSz = sizeof(a) + OPAQUE16_LEN + RAN_LEN + sizeof(mac); + cookieSz = tsSz + sizeof(a) + OPAQUE16_LEN + RAN_LEN + sizeof(mac); extSz = HELLO_EXT_TYPE_SZ + OPAQUE16_LEN + OPAQUE16_LEN + cookieSz; ExpectIntLE(t.s_len + extSz, sizeof(ch)); if (EXPECT_SUCCESS()) { @@ -5443,6 +5456,8 @@ int test_dtls13_ctx_cookie_secret_no_splice(void) p += OPAQUE16_LEN; c16toa((word16)cookieSz, p); p += OPAQUE16_LEN; + XMEMCPY(p, ts, tsSz); + p += tsSz; XMEMCPY(p, a, sizeof(a)); p += sizeof(a); *p++ = DTLS_MAJOR; diff --git a/wolfssl/internal.h b/wolfssl/internal.h index da566fff3e6..bae16ff1900 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -1522,6 +1522,12 @@ enum { #error "WOLFSSL_COOKIE_LEN must be <= 255 per RFC 6347 (opaque<0..2^8-1>)" #endif +/* Seconds a HelloVerifyRequest or HelloRetryRequest cookie verifies after it + * was issued. 0 disables the age check; the issue time is still sent. */ +#ifndef WOLFSSL_COOKIE_MAX_AGE +#define WOLFSSL_COOKIE_MAX_AGE 60 +#endif + #if defined(WOLFSSL_TLS13) || !defined(NO_PSK) #define TLS13_TICKET_NONCE_MAX_SZ 255 @@ -8410,6 +8416,9 @@ WOLFSSL_LOCAL int Tls13KeyUpdateLimitReached(WOLFSSL* ssl); #endif WOLFSSL_LOCAL word32 LowResTimer(void); +#ifndef NO_ASN_TIME +WOLFSSL_LOCAL int CookieExpired(const byte* issued); +#endif WOLFSSL_LOCAL int FindSuiteSSL(const WOLFSSL* ssl, byte* suite); WOLFSSL_LOCAL int FindSuite(const Suites* suites, byte first, byte second);