diff --git a/src/dtls13.c b/src/dtls13.c index 526026f83..2b9d1edaa 100644 --- a/src/dtls13.c +++ b/src/dtls13.c @@ -1846,6 +1846,20 @@ struct Dtls13Epoch* Dtls13GetEpoch(WOLFSSL* ssl, w64wrapper epochNumber) return NULL; } +void Dtls13SetOlderEpochSide(WOLFSSL* ssl, w64wrapper epochNumber, + int side) +{ + Dtls13Epoch* e; + int i; + + for (i = 0; i < DTLS13_EPOCH_SIZE; ++i) { + e = &ssl->dtls13Epochs[i]; + if (e->isValid && w64LT(e->epochNumber, epochNumber)) { + e->side = (byte)side; + } + } +} + static void Dtls13EpochCopyKeys(WOLFSSL* ssl, Dtls13Epoch* e, Keys* k, int side) { byte clientWrite, serverWrite; @@ -2011,6 +2025,14 @@ int Dtls13NewEpoch(WOLFSSL* ssl, w64wrapper epochNumber, int side) e->side = ENCRYPT_AND_DECRYPT_SIDE; } + /* Once handshake is done. Mark epochs older than the last one as encrypt + * only so that they can't be used for decryption. */ + if (ssl->options.handShakeDone && (e->side == ENCRYPT_AND_DECRYPT_SIDE || + e->side == DECRYPT_SIDE_ONLY)) { + w64Decrement(&epochNumber); + Dtls13SetOlderEpochSide(ssl, epochNumber, ENCRYPT_SIDE_ONLY); + } + return 0; } @@ -2373,6 +2395,13 @@ int Dtls13DoScheduledWork(WOLFSSL* ssl) ssl->dtls13SendingAckOrRtx = 0; + if (ssl->dtls13DoKeyUpdate) { + ssl->dtls13DoKeyUpdate = 0; + ret = Tls13UpdateKeys(ssl); + if (ret != 0) + return ret; + } + return 0; } @@ -2610,4 +2639,73 @@ int wolfSSL_dtls13_has_pending_msg(WOLFSSL* ssl) return ssl->dtls13Rtx.rtxRecords != NULL; } +#ifndef WOLFSSL_TLS13_IGNORE_AEAD_LIMITS +/* Limits specified by + * https://www.rfc-editor.org/rfc/rfc9147.html#name-aead-limits + * We specify the limit by which we need to do a key update as the halfway point + * to the hard decryption fail limit. */ +int Dtls13CheckAEADFailLimit(WOLFSSL* ssl) +{ + w64wrapper keyUpdateLimit; + w64wrapper hardLimit; + switch (ssl->specs.bulk_cipher_algorithm) { +#if defined(BUILD_AESGCM) || (defined(HAVE_CHACHA) && defined(HAVE_POLY1305)) + case wolfssl_aes_gcm: + case wolfssl_chacha: + hardLimit = DTLS_AEAD_AES_GCM_CHACHA_FAIL_LIMIT; + keyUpdateLimit = DTLS_AEAD_AES_GCM_CHACHA_FAIL_KU_LIMIT; + break; +#endif +#ifdef HAVE_AESCCM + case wolfssl_aes_ccm: + if (ssl->specs.aead_mac_size == AES_CCM_8_AUTH_SZ) { + /* Limit is 2^7. The RFC recommends that + * "TLS_AES_128_CCM_8_SHA256 is not suitable for general use". + * We still should enforce the limit. */ + hardLimit = DTLS_AEAD_AES_CCM_8_FAIL_LIMIT; + keyUpdateLimit = DTLS_AEAD_AES_CCM_8_FAIL_KU_LIMIT; + } + else { + /* Limit is 2^23.5. + * Without the fraction is 11863283 (0x00B504F3) + * Half of this value is 5931641 (0x005A8279) */ + hardLimit = DTLS_AEAD_AES_CCM_FAIL_LIMIT; + keyUpdateLimit = DTLS_AEAD_AES_CCM_FAIL_KU_LIMIT; + } + break; +#endif + case wolfssl_cipher_null: + /* No encryption being done. The MAC check must have failed. */ + return 0; + default: + WOLFSSL_MSG("Unrecognized ciphersuite for AEAD limit check"); + WOLFSSL_ERROR_VERBOSE(DECRYPT_ERROR); + return DECRYPT_ERROR; + } + if (ssl->dtls13DecryptEpoch == NULL) { + WOLFSSL_MSG("Dtls13CheckAEADFailLimit: ssl->dtls13DecryptEpoch should " + "not be NULL"); + WOLFSSL_ERROR_VERBOSE(BAD_STATE_E); + return BAD_STATE_E; + } + w64Increment(&ssl->dtls13DecryptEpoch->dropCount); + if (w64GT(ssl->dtls13DecryptEpoch->dropCount, hardLimit)) { + /* We have reached the hard limit for failed decryptions. */ + WOLFSSL_MSG("Connection exceeded hard AEAD limit"); + WOLFSSL_ERROR_VERBOSE(DECRYPT_ERROR); + return DECRYPT_ERROR; + } + else if (w64GT(ssl->dtls13DecryptEpoch->dropCount, keyUpdateLimit)) { + WOLFSSL_MSG("Connection exceeded key update limit. Issuing key update"); + /* If not waiting for a response then request a key update. */ + if (!ssl->keys.updateResponseReq) { + ssl->dtls13DoKeyUpdate = 1; + ssl->dtls13InvalidateBefore = ssl->dtls13PeerEpoch; + w64Increment(&ssl->dtls13InvalidateBefore); + } + } + return 0; +} +#endif + #endif /* WOLFSSL_DTLS13 */ diff --git a/src/internal.c b/src/internal.c index 1384c7680..2f48a3fb8 100644 --- a/src/internal.c +++ b/src/internal.c @@ -80,6 +80,12 @@ * Verifies the ECC signature after signing in case of faults in the * calculation of the signature. Useful when signature fault injection is a * possible attack. + * WOLFSSL_TLS13_IGNORE_AEAD_LIMITS + * Ignore the AEAD limits for messages specified in the RFC. After + * reaching the limit, we initiate a key update. We enforce the AEAD limits + * by default. + * https://www.rfc-editor.org/rfc/rfc8446#section-5.5 + * https://www.rfc-editor.org/rfc/rfc9147.html#name-aead-limits */ @@ -18247,6 +18253,23 @@ int DoApplicationData(WOLFSSL* ssl, byte* input, word32* inOutIdx, int sniff) return OUT_OF_ORDER_E; } + +#if defined(WOLFSSL_DTLS13) && !defined(WOLFSSL_TLS13_IGNORE_AEAD_LIMITS) + /* Check if we want to invalidate old epochs. If + * ssl->dtls13InvalidateBefore is set then we want to mark all old + * epochs as encrypt only. This is done when we detect too many failed + * decryptions. We do this here to confirm that the peer has updated its + * keys and we can stop using the old keys. */ + if (ssl->options.dtls && IsAtLeastTLSv1_3(ssl->version)) { + if (!w64IsZero(ssl->dtls13InvalidateBefore) && + w64Equal(ssl->keys.curEpoch64, ssl->dtls13InvalidateBefore)) { + Dtls13SetOlderEpochSide(ssl, ssl->dtls13InvalidateBefore, + ENCRYPT_SIDE_ONLY); + w64Zero(&ssl->dtls13InvalidateBefore); + } + } +#endif + #ifndef WOLFSSL_AEAD_ONLY if (ssl->specs.cipher_type == block) { if (ssl->options.tls1_1) @@ -18831,6 +18854,26 @@ static WC_INLINE int VerifyMac(WOLFSSL* ssl, const byte* input, word32 msgSz, return 0; } +#ifdef WOLFSSL_DTLS +static int HandleDTLSDecryptFailed(WOLFSSL* ssl) +{ + int ret = 0; +#ifdef WOLFSSL_DTLS_DROP_STATS + ssl->macDropCount++; +#endif + +#if defined(WOLFSSL_DTLS13) && !defined(WOLFSSL_TLS13_IGNORE_AEAD_LIMITS) + /* Handle AEAD limits specified by the RFC for failed decryption */ + if (IsAtLeastTLSv1_3(ssl->version)) + ret = Dtls13CheckAEADFailLimit(ssl); +#endif + + (void)ssl; + WOLFSSL_MSG("DTLS: Ignoring failed decryption"); + return ret; +} +#endif + int ProcessReply(WOLFSSL* ssl) { return ProcessReplyEx(ssl, 0); @@ -19137,17 +19180,14 @@ int ProcessReplyEx(WOLFSSL* ssl, int allowSocketErr) #endif if (ret < 0) { WOLFSSL_MSG("VerifyMacEnc failed"); - WOLFSSL_ERROR(ret); #ifdef WOLFSSL_DTLS /* If in DTLS mode, if the decrypt fails for any * reason, pretend the datagram never happened. */ if (ssl->options.dtls) { ssl->options.processReply = doProcessInit; ssl->buffers.inputBuffer.idx = - ssl->buffers.inputBuffer.length; - #ifdef WOLFSSL_DTLS_DROP_STATS - ssl->macDropCount++; - #endif /* WOLFSSL_DTLS_DROP_STATS */ + ssl->buffers.inputBuffer.length; + return HandleDTLSDecryptFailed(ssl); } #endif /* WOLFSSL_DTLS */ #ifdef WOLFSSL_EXTRA_ALERTS @@ -19304,14 +19344,10 @@ int ProcessReplyEx(WOLFSSL* ssl, int allowSocketErr) /* If in DTLS mode, if the decrypt fails for any * reason, pretend the datagram never happened. */ if (ssl->options.dtls) { - WOLFSSL_MSG("DTLS: Ignoring failed decryption"); ssl->options.processReply = doProcessInit; ssl->buffers.inputBuffer.idx = - ssl->buffers.inputBuffer.length; - #ifdef WOLFSSL_DTLS_DROP_STATS - ssl->macDropCount++; - #endif /* WOLFSSL_DTLS_DROP_STATS */ - return 0; + ssl->buffers.inputBuffer.length; + return HandleDTLSDecryptFailed(ssl); } #endif /* WOLFSSL_DTLS */ #ifdef WOLFSSL_EARLY_DATA @@ -19374,24 +19410,21 @@ int ProcessReplyEx(WOLFSSL* ssl, int allowSocketErr) return ret; #endif if (ret < 0) { - WOLFSSL_MSG("VerifyMac failed"); - WOLFSSL_ERROR(ret); #ifdef WOLFSSL_DTLS /* If in DTLS mode, if the decrypt fails for any * reason, pretend the datagram never happened. */ if (ssl->options.dtls) { ssl->options.processReply = doProcessInit; ssl->buffers.inputBuffer.idx = - ssl->buffers.inputBuffer.length; - #ifdef WOLFSSL_DTLS_DROP_STATS - ssl->macDropCount++; - #endif /* WOLFSSL_DTLS_DROP_STATS */ + ssl->buffers.inputBuffer.length; + return HandleDTLSDecryptFailed(ssl); } #endif /* WOLFSSL_DTLS */ #ifdef WOLFSSL_EXTRA_ALERTS if (!ssl->options.dtls) SendAlert(ssl, alert_fatal, bad_record_mac); #endif + WOLFSSL_MSG("VerifyMac failed"); WOLFSSL_ERROR_VERBOSE(DECRYPT_ERROR); return DECRYPT_ERROR; } @@ -22021,6 +22054,66 @@ static int ModifyForMTU(WOLFSSL* ssl, int buffSz, int outputSz, int mtuSz) } #endif /* WOLFSSL_DTLS */ +#if defined(WOLFSSL_TLS13) && !defined(WOLFSSL_TLS13_IGNORE_AEAD_LIMITS) +/* + * Enforce limits specified in + * https://www.rfc-editor.org/rfc/rfc8446#section-5.5 + */ +static int CheckTLS13AEADSendLimit(WOLFSSL* ssl) +{ + w64wrapper seq; + w64wrapper limit; + + switch (ssl->specs.bulk_cipher_algorithm) { +#ifdef BUILD_AESGCM + case wolfssl_aes_gcm: + /* Limit is 2^24.5 */ + limit = AEAD_AES_LIMIT; + break; +#endif +#if defined(HAVE_CHACHA) && defined(HAVE_POLY1305) + case wolfssl_chacha: + /* For ChaCha20/Poly1305, the record sequence number would wrap + * before the safety limit is reached. */ + return 0; +#endif +#ifdef HAVE_AESCCM + case wolfssl_aes_ccm: + /* Use the limits calculated in the DTLS 1.3 spec + * https://www.rfc-editor.org/rfc/rfc9147.html#name-analysis-of-limits-on-ccm-u */ +#ifdef WOLFSSL_DTLS13 + if (ssl->options.dtls) + limit = DTLS_AEAD_AES_CCM_LIMIT; /* Limit is 2^23 */ + else +#endif + limit = AEAD_AES_LIMIT; /* Limit is 2^24.5 */ + break; +#endif + case wolfssl_cipher_null: + /* No encryption being done */ + return 0; + default: + WOLFSSL_MSG("Unrecognized ciphersuite for AEAD limit check"); + return BAD_STATE_E; + + } +#ifdef WOLFSSL_DTLS13 + if (ssl->options.dtls) { + seq = ssl->dtls13EncryptEpoch->nextSeqNumber; + } + else +#endif + { + seq = w64From32(ssl->keys.sequence_number_hi, + ssl->keys.sequence_number_lo); + } + + if (w64GTE(seq, limit)) + return Tls13UpdateKeys(ssl); /* Need to generate new keys */ + + return 0; +} +#endif /* WOLFSSL_TLS13 && !WOLFSSL_TLS13_IGNORE_AEAD_LIMITS */ int SendData(WOLFSSL* ssl, const void* data, int sz) { @@ -22123,6 +22216,16 @@ int SendData(WOLFSSL* ssl, const void* data, int sz) byte comp[MAX_RECORD_SIZE + MAX_COMP_EXTRA]; #endif +#if defined(WOLFSSL_TLS13) && !defined(WOLFSSL_TLS13_IGNORE_AEAD_LIMITS) + if (IsAtLeastTLSv1_3(ssl->version)) { + ret = CheckTLS13AEADSendLimit(ssl); + if (ret != 0) { + ssl->error = ret; + return WOLFSSL_FATAL_ERROR; + } + } +#endif + #ifdef WOLFSSL_DTLS13 if (ssl->options.dtls && ssl->options.tls1_3) { byte isEarlyData = 0; diff --git a/src/ssl.c b/src/ssl.c index ae906b848..33a8544da 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -3172,6 +3172,11 @@ static int _Rehandshake(WOLFSSL* ssl) if (ssl == NULL) return BAD_FUNC_ARG; + if (IsAtLeastTLSv1_3(ssl->version)) { + WOLFSSL_MSG("Secure Renegotiation not supported in TLS 1.3"); + return SECURE_RENEGOTIATION_E; + } + if (ssl->secure_renegotiation == NULL) { WOLFSSL_MSG("Secure Renegotiation not forced on by user"); return SECURE_RENEGOTIATION_E; @@ -3182,6 +3187,13 @@ static int _Rehandshake(WOLFSSL* ssl) return SECURE_RENEGOTIATION_E; } +#ifdef WOLFSSL_DTLS + if (ssl->options.dtls && ssl->keys.dtls_epoch == 0xFFFF) { + WOLFSSL_MSG("Secure Renegotiation not allowed. Epoch would wrap"); + return SECURE_RENEGOTIATION_E; + } +#endif + /* If the client started the renegotiation, the server will already * have processed the client's hello. */ if (ssl->options.side != WOLFSSL_SERVER_END || diff --git a/src/tls13.c b/src/tls13.c index 1f2625776..87191db85 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -2097,7 +2097,7 @@ static WC_INLINE void WriteSEQTls13(WOLFSSL* ssl, int verifyOrder, byte* out) Dtls13GetSeq(ssl, verifyOrder, seq, 1); #endif /* WOLFSSL_DTLS13 */ } - else if (verifyOrder) { + else if (verifyOrder == PEER_ORDER) { seq[0] = ssl->keys.peer_sequence_number_hi; seq[1] = ssl->keys.peer_sequence_number_lo++; /* handle rollover */ @@ -11130,6 +11130,26 @@ int wolfSSL_no_dhe_psk(WOLFSSL* ssl) return 0; } + +int Tls13UpdateKeys(WOLFSSL* ssl) +{ + if (ssl == NULL || !IsAtLeastTLSv1_3(ssl->version)) + return BAD_FUNC_ARG; + +#ifdef WOLFSSL_DTLS13 + /* we are already waiting for the ack of a sent key update message. We can't + send another one before receiving its ack. Either wolfSSL_update_keys() + was invoked multiple times over a short period of time or we replied to a + KeyUpdate with update request. We'll just ignore sending this + KeyUpdate. */ + /* TODO: add WOLFSSL_ERROR_ALREADY_IN_PROGRESS type of error here */ + if (ssl->options.dtls && ssl->dtls13WaitKeyUpdateAck) + return 0; +#endif /* WOLFSSL_DTLS13 */ + + return SendTls13KeyUpdate(ssl); +} + /* Update the keys for encryption and decryption. * If using non-blocking I/O and WOLFSSL_ERROR_WANT_WRITE is returned then * calling wolfSSL_write() will have the message sent when ready. @@ -11142,22 +11162,7 @@ int wolfSSL_no_dhe_psk(WOLFSSL* ssl) int wolfSSL_update_keys(WOLFSSL* ssl) { int ret; - - if (ssl == NULL || !IsAtLeastTLSv1_3(ssl->version)) - return BAD_FUNC_ARG; - -#ifdef WOLFSSL_DTLS13 - /* we are already waiting for the ack of a sent key update message. We can't - send another one before receiving its ack. Either wolfSSL_update_keys() - was invoked multiple times over a short period of time or we replied to a - KeyUpdate with update request. We'll just ignore sending this - KeyUpdate. */ - /* TODO: add WOLFSSL_ERROR_ALREADY_IN_PROGRESS type of error here */ - if (ssl->options.dtls && ssl->dtls13WaitKeyUpdateAck) - return WOLFSSL_SUCCESS; -#endif /* WOLFSSL_DTLS13 */ - - ret = SendTls13KeyUpdate(ssl); + ret = Tls13UpdateKeys(ssl); if (ret == WANT_WRITE) ret = WOLFSSL_ERROR_WANT_WRITE; else if (ret == 0) diff --git a/tests/api.c b/tests/api.c index 3864b776c..b3e3eee4a 100644 --- a/tests/api.c +++ b/tests/api.c @@ -5786,6 +5786,49 @@ done: return 0; } +void test_wolfSSL_client_server_nofail(callback_functions* client_cb, + callback_functions* server_cb) +{ + func_args client_args; + func_args server_args; + tcp_ready ready; + THREAD_TYPE serverThread; + + XMEMSET(&client_args, 0, sizeof(func_args)); + XMEMSET(&server_args, 0, sizeof(func_args)); + +#ifdef WOLFSSL_TIRTOS + fdOpenSession(Task_self()); +#endif + + StartTCP(); + InitTcpReady(&ready); + +#if defined(USE_WINDOWS_API) + /* use RNG to get random port if using windows */ + ready.port = GetRandomPort(); +#endif + + server_args.signal = &ready; + server_args.callbacks = server_cb; + client_args.signal = &ready; + client_args.callbacks = client_cb; + + start_thread(test_server_nofail, &server_args, &serverThread); + wait_tcp_ready(&server_args); + test_client_nofail(&client_args, NULL); + join_thread(serverThread); + + client_cb->return_code = client_args.return_code; + server_cb->return_code = server_args.return_code; + + FreeTcpReady(&ready); + +#ifdef WOLFSSL_TIRTOS + fdOpenSession(Task_self()); +#endif +} + #if defined(OPENSSL_EXTRA) && !defined(NO_SESSION_CACHE) && \ !defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_CLIENT) static void test_client_reuse_WOLFSSLobj(void* args, void *cb, void* server_args) @@ -55575,12 +55618,8 @@ static void test_wolfSSL_dtls_plaintext_client(WOLFSSL* ssl) static int test_wolfSSL_dtls_plaintext(void) { - tcp_ready ready; - func_args client_args; - func_args server_args; callback_functions func_cb_client; callback_functions func_cb_server; - THREAD_TYPE serverThread; size_t i; struct test_params { method_provider client_meth; @@ -55596,47 +55635,21 @@ static int test_wolfSSL_dtls_plaintext(void) printf(testingFmt, "test_wolfSSL_dtls_plaintext"); for (i = 0; i < sizeof(params)/sizeof(*params); i++) { - XMEMSET(&client_args, 0, sizeof(func_args)); - XMEMSET(&server_args, 0, sizeof(func_args)); XMEMSET(&func_cb_client, 0, sizeof(callback_functions)); XMEMSET(&func_cb_server, 0, sizeof(callback_functions)); - #ifdef WOLFSSL_TIRTOS - fdOpenSession(Task_self()); - #endif - - StartTCP(); - InitTcpReady(&ready); - -#if defined(USE_WINDOWS_API) - /* use RNG to get random port if using windows */ - ready.port = GetRandomPort(); -#endif - - server_args.signal = &ready; - server_args.callbacks = &func_cb_server; - client_args.signal = &ready; - client_args.callbacks = &func_cb_client; - func_cb_client.doUdp = func_cb_server.doUdp = 1; func_cb_server.method = params[i].server_meth; func_cb_client.method = params[i].client_meth; func_cb_client.on_result = params[i].on_result_client; func_cb_server.on_result = params[i].on_result_server; - start_thread(test_server_nofail, &server_args, &serverThread); - wait_tcp_ready(&server_args); - test_client_nofail(&client_args, NULL); - join_thread(serverThread); + test_wolfSSL_client_server_nofail(&func_cb_client, &func_cb_server); - AssertTrue(client_args.return_code); - AssertTrue(server_args.return_code); - - FreeTcpReady(&ready); - -#ifdef WOLFSSL_TIRTOS - fdOpenSession(Task_self()); -#endif + if (!func_cb_client.return_code) + return -1; + if (!func_cb_server.return_code) + return -2; } printf(resultFmt, passed); @@ -55761,12 +55774,8 @@ static void test_wolfSSL_dtls13_fragments_spammer(WOLFSSL* ssl) static int test_wolfSSL_dtls_fragments(void) { - tcp_ready ready; - func_args client_args; - func_args server_args; callback_functions func_cb_client; callback_functions func_cb_server; - THREAD_TYPE serverThread; size_t i; struct test_params { method_provider client_meth; @@ -55784,52 +55793,26 @@ static int test_wolfSSL_dtls_fragments(void) printf(testingFmt, "test_wolfSSL_dtls_fragments"); for (i = 0; i < sizeof(params)/sizeof(*params); i++) { - XMEMSET(&client_args, 0, sizeof(func_args)); - XMEMSET(&server_args, 0, sizeof(func_args)); XMEMSET(&func_cb_client, 0, sizeof(callback_functions)); XMEMSET(&func_cb_server, 0, sizeof(callback_functions)); - #ifdef WOLFSSL_TIRTOS - fdOpenSession(Task_self()); - #endif - - StartTCP(); - InitTcpReady(&ready); - -#if defined(USE_WINDOWS_API) - /* use RNG to get random port if using windows */ - ready.port = GetRandomPort(); -#endif - - server_args.signal = &ready; - server_args.callbacks = &func_cb_server; - client_args.signal = &ready; - client_args.callbacks = &func_cb_client; func_cb_client.doUdp = func_cb_server.doUdp = 1; func_cb_server.method = params[i].server_meth; func_cb_client.method = params[i].client_meth; func_cb_client.ssl_ready = params[i].spammer; - start_thread(test_server_nofail, &server_args, &serverThread); - wait_tcp_ready(&server_args); - test_client_nofail(&client_args, NULL); - join_thread(serverThread); + test_wolfSSL_client_server_nofail(&func_cb_client, &func_cb_server); + + AssertFalse(func_cb_client.return_code); + AssertFalse(func_cb_server.return_code); - AssertFalse(client_args.return_code); - AssertFalse(server_args.return_code); /* The socket should be closed by the server resulting in a * socket error */ AssertIntEQ(func_cb_client.last_err, SOCKET_ERROR_E); /* Check the server returned an error indicating the msg buffer * was full */ AssertIntEQ(func_cb_server.last_err, DTLS_TOO_MANY_FRAGMENTS_E); - - FreeTcpReady(&ready); - -#ifdef WOLFSSL_TIRTOS - fdOpenSession(Task_self()); -#endif } printf(resultFmt, passed); @@ -55842,6 +55825,206 @@ static int test_wolfSSL_dtls_fragments(void) { } #endif +#if defined(WOLFSSL_DTLS13) && !defined(WOLFSSL_TLS13_IGNORE_AEAD_LIMITS) +static byte test_AEAD_fail_decryption = 0; +static byte test_AEAD_seq_num = 0; +static byte test_AEAD_done = 0; + +static int test_AEAD_cbiorecv(WOLFSSL *ssl, char *buf, int sz, void *ctx) +{ + int ret = recv(wolfSSL_get_fd(ssl), buf, sz, 0); + if (ret > 0) { + if (test_AEAD_fail_decryption) { + /* Modify the packet to trigger a decryption failure */ + buf[ret/2] ^= 0xFF; + if (test_AEAD_fail_decryption == 1) + test_AEAD_fail_decryption = 0; + } + } + (void)ctx; + return ret; +} + +static void test_AEAD_get_limits(WOLFSSL* ssl, w64wrapper* hardLimit, + w64wrapper* keyUpdateLimit, w64wrapper* sendLimit) +{ + if (sendLimit) + w64Zero(sendLimit); + switch (ssl->specs.bulk_cipher_algorithm) { + case wolfssl_aes_gcm: + if (sendLimit) + *sendLimit = AEAD_AES_LIMIT; + FALL_THROUGH; + case wolfssl_chacha: + if (hardLimit) + *hardLimit = DTLS_AEAD_AES_GCM_CHACHA_FAIL_LIMIT; + if (keyUpdateLimit) + *keyUpdateLimit = DTLS_AEAD_AES_GCM_CHACHA_FAIL_KU_LIMIT; + break; + case wolfssl_aes_ccm: + if (sendLimit) + *sendLimit = DTLS_AEAD_AES_CCM_LIMIT; + if (ssl->specs.aead_mac_size == AES_CCM_8_AUTH_SZ) { + if (hardLimit) + *hardLimit = DTLS_AEAD_AES_CCM_8_FAIL_LIMIT; + if (keyUpdateLimit) + *keyUpdateLimit = DTLS_AEAD_AES_CCM_8_FAIL_KU_LIMIT; + } + else { + if (hardLimit) + *hardLimit = DTLS_AEAD_AES_CCM_FAIL_LIMIT; + if (keyUpdateLimit) + *keyUpdateLimit = DTLS_AEAD_AES_CCM_FAIL_KU_LIMIT; + } + break; + default: + fprintf(stderr, "Unrecognized bulk cipher"); + AssertFalse(1); + break; + } +} + +static void test_AEAD_limit_client(WOLFSSL* ssl) +{ + int ret; + int i; + int didReKey = 0; + char msgBuf[20]; + w64wrapper hardLimit; + w64wrapper keyUpdateLimit; + w64wrapper counter; + w64wrapper sendLimit; + + test_AEAD_get_limits(ssl, &hardLimit, &keyUpdateLimit, &sendLimit); + + w64Zero(&counter); + AssertTrue(w64Equal(Dtls13GetEpoch(ssl, ssl->dtls13Epoch)->dropCount, counter)); + + wolfSSL_SSLSetIORecv(ssl, test_AEAD_cbiorecv); + + for (i = 0; i < 10; i++) { + /* Test some failed decryptions */ + test_AEAD_fail_decryption = 1; + w64Increment(&counter); + ret = wolfSSL_read(ssl, msgBuf, sizeof(msgBuf)); + /* Should succeed since decryption failures are dropped */ + AssertIntGT(ret, 0); + AssertTrue(w64Equal(Dtls13GetEpoch(ssl, ssl->dtls13PeerEpoch)->dropCount, counter)); + } + + test_AEAD_fail_decryption = 1; + Dtls13GetEpoch(ssl, ssl->dtls13PeerEpoch)->dropCount = keyUpdateLimit; + w64Increment(&Dtls13GetEpoch(ssl, ssl->dtls13PeerEpoch)->dropCount); + /* 100 read calls should be enough to complete the key update */ + w64Zero(&counter); + for (i = 0; i < 100; i++) { + /* Key update should be sent and negotiated */ + ret = wolfSSL_read(ssl, msgBuf, sizeof(msgBuf)); + AssertIntGT(ret, 0); + /* Epoch after one key update is 4 */ + if (w64Equal(ssl->dtls13PeerEpoch, w64From32(0, 4)) && + w64Equal(Dtls13GetEpoch(ssl, ssl->dtls13PeerEpoch)->dropCount, counter)) { + didReKey = 1; + break; + } + } + AssertTrue(didReKey); + + if (!w64IsZero(sendLimit)) { + /* Test the sending limit for AEAD ciphers */ + Dtls13GetEpoch(ssl, ssl->dtls13Epoch)->nextSeqNumber = sendLimit; + test_AEAD_seq_num = 1; + ret = wolfSSL_write(ssl, msgBuf, sizeof(msgBuf)); + AssertIntGT(ret, 0); + didReKey = 0; + w64Zero(&counter); + /* 100 read calls should be enough to complete the key update */ + for (i = 0; i < 100; i++) { + /* Key update should be sent and negotiated */ + ret = wolfSSL_read(ssl, msgBuf, sizeof(msgBuf)); + AssertIntGT(ret, 0); + /* Epoch after another key update is 5 */ + if (w64Equal(ssl->dtls13Epoch, w64From32(0, 5)) && + w64Equal(Dtls13GetEpoch(ssl, ssl->dtls13Epoch)->dropCount, counter)) { + didReKey = 1; + break; + } + } + AssertTrue(didReKey); + } + + test_AEAD_fail_decryption = 2; + Dtls13GetEpoch(ssl, ssl->dtls13PeerEpoch)->dropCount = hardLimit; + w64Decrement(&Dtls13GetEpoch(ssl, ssl->dtls13PeerEpoch)->dropCount); + /* Connection should fail with a DECRYPT_ERROR */ + ret = wolfSSL_read(ssl, msgBuf, sizeof(msgBuf)); + AssertIntEQ(ret, WOLFSSL_FATAL_ERROR); + AssertIntEQ(wolfSSL_get_error(ssl, ret), DECRYPT_ERROR); + + test_AEAD_done = 1; +} + +int counter = 0; +static void test_AEAD_limit_server(WOLFSSL* ssl) +{ + char msgBuf[] = "Sending data"; + int ret = WOLFSSL_SUCCESS; + w64wrapper sendLimit; + SOCKET_T fd = wolfSSL_get_fd(ssl); + struct timespec delay; + XMEMSET(&delay, 0, sizeof(delay)); + delay.tv_nsec = 100000000; /* wait 0.1 seconds */ + tcp_set_nonblocking(&fd); /* So that read doesn't block */ + test_AEAD_get_limits(ssl, NULL, NULL, &sendLimit); + while (!test_AEAD_done && ret > 0) { + counter++; + if (test_AEAD_seq_num) { + /* We need to update the seq number so that we can understand the + * peer. Otherwise we will incorrectly interpret the seq number. */ + Dtls13Epoch* e = Dtls13GetEpoch(ssl, ssl->dtls13PeerEpoch); + AssertNotNull(e); + e->nextPeerSeqNumber = sendLimit; + test_AEAD_seq_num = 0; + } + (void)wolfSSL_read(ssl, msgBuf, sizeof(msgBuf)); + ret = wolfSSL_write(ssl, msgBuf, sizeof(msgBuf)); + nanosleep(&delay, NULL); + } +} + +static int test_wolfSSL_dtls_AEAD_limit(void) +{ + callback_functions func_cb_client; + callback_functions func_cb_server; + XMEMSET(&func_cb_client, 0, sizeof(callback_functions)); + XMEMSET(&func_cb_server, 0, sizeof(callback_functions)); + + printf(testingFmt, "test_wolfSSL_dtls_AEAD_limit"); + + func_cb_client.doUdp = func_cb_server.doUdp = 1; + func_cb_server.method = wolfDTLSv1_3_server_method; + func_cb_client.method = wolfDTLSv1_3_client_method; + func_cb_server.on_result = test_AEAD_limit_server; + func_cb_client.on_result = test_AEAD_limit_client; + + test_wolfSSL_client_server_nofail(&func_cb_client, &func_cb_server); + + if (!func_cb_client.return_code) + return -1; + if (!func_cb_server.return_code) + return -2; + + printf(resultFmt, passed); + + return 0; +} +#else +static int test_wolfSSL_dtls_AEAD_limit(void) +{ + return 0; +} +#endif + #if !defined(NO_RSA) && !defined(NO_SHA) && !defined(NO_FILESYSTEM) && \ !defined(NO_CERTS) && (!defined(NO_WOLFSSL_CLIENT) || \ !defined(WOLFSSL_NO_CLIENT_AUTH)) @@ -58553,6 +58736,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_wolfSSL_either_side), TEST_DECL(test_wolfSSL_DTLS_either_side), TEST_DECL(test_wolfSSL_dtls_fragments), + TEST_DECL(test_wolfSSL_dtls_AEAD_limit), TEST_DECL(test_generate_cookie), TEST_DECL(test_wolfSSL_X509_STORE_set_flags), TEST_DECL(test_wolfSSL_X509_LOOKUP_load_file), diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 4e7d2e75c..d5451b752 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -1274,6 +1274,30 @@ enum { #define TLS13_MAX_TICKET_AGE (7*24*60*60) #endif + +/* Limit is 2^24.5 + * https://www.rfc-editor.org/rfc/rfc8446#section-5.5 + * Without the fraction is 23726566 (0x016A09E6) */ +#define AEAD_AES_LIMIT w64From32(0x016A, 0x09E6) +/* Limit is 2^23 + * https://www.rfc-editor.org/rfc/rfc9147.html#name-integrity-limits */ +#define DTLS_AEAD_AES_CCM_LIMIT w64From32(0, 1 << 22) + +/* Limit is 2^36 + * https://www.rfc-editor.org/rfc/rfc9147.html#name-aead-limits */ +#define DTLS_AEAD_AES_GCM_CHACHA_FAIL_LIMIT w64From32(1 << 3, 0) +#define DTLS_AEAD_AES_GCM_CHACHA_FAIL_KU_LIMIT w64From32(1 << 2, 0) +/* Limit is 2^7 + * https://www.rfc-editor.org/rfc/rfc9147.html#name-limits-for-aead_aes_128_ccm */ +#define DTLS_AEAD_AES_CCM_8_FAIL_LIMIT w64From32(0, 1 << 6) +#define DTLS_AEAD_AES_CCM_8_FAIL_KU_LIMIT w64From32(0, 1 << 5) +/* Limit is 2^23.5. + * https://www.rfc-editor.org/rfc/rfc9147.html#name-integrity-limits + * Without the fraction is 11863283 (0x00B504F3) + * Half of this value is 5931641 (0x005A8279) */ +#define DTLS_AEAD_AES_CCM_FAIL_LIMIT w64From32(0x00B5, 0x04F3) +#define DTLS_AEAD_AES_CCM_FAIL_KU_LIMIT w64From32(0x005A, 0x8279) + enum Misc { CIPHER_BYTE = 0x00, /* Default ciphers */ ECC_BYTE = 0xC0, /* ECC first cipher suite byte */ @@ -4035,6 +4059,10 @@ typedef struct Options { #endif #endif #ifdef WOLFSSL_DTLS +#ifdef HAVE_SECURE_RENEGOTIATION + word16 dtlsDoSCR:1; /* Enough packets were dropped. We + * need to re-key. */ +#endif word16 dtlsUseNonblock:1; /* are we using nonblocking socket */ word16 dtlsHsRetain:1; /* DTLS retaining HS data */ word16 haveMcast:1; /* using multicast ? */ @@ -4632,6 +4660,10 @@ typedef struct Dtls13Epoch { w64wrapper nextSeqNumber; w64wrapper nextPeerSeqNumber; +#ifndef WOLFSSL_TLS13_IGNORE_AEAD_LIMITS + w64wrapper dropCount; /* Amount of records that failed decryption */ +#endif + word32 window[WOLFSSL_DTLS_WINDOW_WORDS]; /* key material for the epoch */ @@ -4687,10 +4719,10 @@ typedef struct Dtls13Rtx { Dtls13RtxRecord *rtxRecords; Dtls13RtxRecord **rtxRecordTailPtr; Dtls13RecordNumber *seenRecords; + word32 lastRtx; byte triggeredRtxs; byte sendAcks:1; byte retransmit:1; - word32 lastRtx; } Dtls13Rtx; #endif /* WOLFSSL_DTLS13 */ @@ -4910,6 +4942,7 @@ struct WOLFSSL { Dtls13Epoch *dtls13DecryptEpoch; w64wrapper dtls13Epoch; w64wrapper dtls13PeerEpoch; + w64wrapper dtls13InvalidateBefore; byte dtls13CurRL[DTLS_RECVD_RL_HEADER_MAX_SZ]; word16 dtls13CurRlLength; @@ -4919,6 +4952,7 @@ struct WOLFSSL { byte dtls13SendingAckOrRtx:1; byte dtls13FastTimeout:1; byte dtls13WaitKeyUpdateAck:1; + byte dtls13DoKeyUpdate:1; word32 dtls13MessageLength; word32 dtls13FragOffset; byte dtls13FragHandshakeType; @@ -5666,6 +5700,7 @@ WOLFSSL_LOCAL int BuildMessage(WOLFSSL* ssl, byte* output, int outSz, /* Use WOLFSSL_API to use this function in tests/api.c */ WOLFSSL_API int BuildTls13Message(WOLFSSL* ssl, byte* output, int outSz, const byte* input, int inSz, int type, int hashOutput, int sizeOnly, int asyncOkay); +WOLFSSL_LOCAL int Tls13UpdateKeys(WOLFSSL* ssl); #endif WOLFSSL_LOCAL int AllocKey(WOLFSSL* ssl, int type, void** pKey); @@ -5726,8 +5761,11 @@ WOLFSSL_API int wolfSSL_DtlsUpdateWindow(word16 cur_hi, word32 cur_lo, #ifdef WOLFSSL_DTLS13 -WOLFSSL_LOCAL struct Dtls13Epoch* Dtls13GetEpoch(WOLFSSL* ssl, +/* Use WOLFSSL_API to use this function in tests/api.c */ +WOLFSSL_API struct Dtls13Epoch* Dtls13GetEpoch(WOLFSSL* ssl, w64wrapper epochNumber); +WOLFSSL_LOCAL void Dtls13SetOlderEpochSide(WOLFSSL* ssl, w64wrapper epochNumber, + int side); WOLFSSL_LOCAL int Dtls13NewEpoch(WOLFSSL* ssl, w64wrapper epochNumber, int side); WOLFSSL_LOCAL int Dtls13SetEpochKeys(WOLFSSL* ssl, w64wrapper epochNumber, @@ -5779,6 +5817,7 @@ WOLFSSL_LOCAL int Dtls13HashHandshake(WOLFSSL* ssl, const byte* output, WOLFSSL_LOCAL void Dtls13FreeFsmResources(WOLFSSL* ssl); WOLFSSL_LOCAL int Dtls13RtxTimeout(WOLFSSL* ssl); WOLFSSL_LOCAL int Dtls13ProcessBufferedMessages(WOLFSSL* ssl); +WOLFSSL_LOCAL int Dtls13CheckAEADFailLimit(WOLFSSL* ssl); #endif /* WOLFSSL_DTLS13 */ #ifdef WOLFSSL_STATIC_EPHEMERAL diff --git a/wolfssl/test.h b/wolfssl/test.h index ec4569973..537e0b54e 100644 --- a/wolfssl/test.h +++ b/wolfssl/test.h @@ -657,6 +657,9 @@ typedef THREAD_RETURN WOLFSSL_THREAD THREAD_FUNC(void*); void start_thread(THREAD_FUNC fun, func_args* args, THREAD_TYPE* thread); void join_thread(THREAD_TYPE thread); +void test_wolfSSL_client_server_nofail(callback_functions* client_cb, + callback_functions* server_cb); + /* wolfSSL */ #ifndef TEST_IPV6 static const char* const wolfSSLIP = "127.0.0.1"; diff --git a/wolfssl/wolfcrypt/misc.h b/wolfssl/wolfcrypt/misc.h index 8f8fc1764..74d249c8d 100644 --- a/wolfssl/wolfcrypt/misc.h +++ b/wolfssl/wolfcrypt/misc.h @@ -130,6 +130,24 @@ WOLFSSL_LOCAL void ctMaskCopy(byte mask, byte* dst, byte* src, word16 size); WOLFSSL_LOCAL word32 MakeWordFromHash(const byte* hashID); WOLFSSL_LOCAL word32 HashObject(const byte* o, word32 len, int* error); +WOLFSSL_LOCAL void w64Increment(w64wrapper *n); +WOLFSSL_LOCAL void w64Decrement(w64wrapper *n); +WOLFSSL_LOCAL byte w64Equal(w64wrapper a, w64wrapper b); +WOLFSSL_LOCAL word32 w64GetLow32(w64wrapper n); +WOLFSSL_LOCAL word32 w64GetHigh32(w64wrapper n); +WOLFSSL_LOCAL void w64SetLow32(w64wrapper *n, word32 low); +WOLFSSL_LOCAL w64wrapper w64Add32(w64wrapper a, word32 b, byte *wrap); +WOLFSSL_LOCAL w64wrapper w64Sub32(w64wrapper a, word32 b, byte *wrap); +WOLFSSL_LOCAL byte w64GT(w64wrapper a, w64wrapper b); +WOLFSSL_LOCAL byte w64IsZero(w64wrapper a); +WOLFSSL_LOCAL void c64toa(const w64wrapper *a, byte *out); +WOLFSSL_LOCAL void ato64(const byte *in, w64wrapper *w64); +WOLFSSL_LOCAL w64wrapper w64From32(word32 hi, word32 lo); +WOLFSSL_LOCAL byte w64GTE(w64wrapper a, w64wrapper b); +WOLFSSL_LOCAL byte w64LT(w64wrapper a, w64wrapper b); +WOLFSSL_LOCAL w64wrapper w64Sub(w64wrapper a, w64wrapper b); +WOLFSSL_LOCAL void w64Zero(w64wrapper *a); + #endif /* NO_INLINE */ diff --git a/wolfssl/wolfcrypt/settings.h b/wolfssl/wolfcrypt/settings.h index 934213852..d9a854c6c 100644 --- a/wolfssl/wolfcrypt/settings.h +++ b/wolfssl/wolfcrypt/settings.h @@ -2771,8 +2771,9 @@ extern void uITRON4_free(void *p) ; #define NO_SESSION_CACHE_REF #endif -/* DTLS v1.3 requires 64-bit number wrappers */ -#if defined(WOLFSSL_DTLS13) && !defined(WOLFSSL_W64_WRAPPER) +/* (D)TLS v1.3 requires 64-bit number wrappers */ +#if defined(WOLFSSL_TLS13) || defined(WOLFSSL_DTLS_DROP_STATS) + #undef WOLFSSL_W64_WRAPPER #define WOLFSSL_W64_WRAPPER #endif