diff --git a/src/dtls13.c b/src/dtls13.c index b9df18b92..bc25179c8 100644 --- a/src/dtls13.c +++ b/src/dtls13.c @@ -1998,6 +1998,13 @@ int Dtls13NewEpoch(WOLFSSL* ssl, w64wrapper epochNumber, int side) return BAD_STATE_E; } +#ifndef WOLFSSL_TLS13_IGNORE_AEAD_LIMITS + /* We are updating the receiving keys for this connection. We can restart + * the failed decryption counter. */ + if (side == ENCRYPT_AND_DECRYPT_SIDE || side == DECRYPT_SIDE_ONLY) + w64Zero(&ssl->macDropCount); +#endif + Dtls13EpochCopyKeys(ssl, e, &ssl->keys, side); if (!e->isValid) { @@ -2373,6 +2380,13 @@ int Dtls13DoScheduledWork(WOLFSSL* ssl) ssl->dtls13SendingAckOrRtx = 0; + if (ssl->dtls13DoKeyUpdate) { + ssl->dtls13DoKeyUpdate = 0; + ret = Tls13UpdateKeys(ssl); + if (ret != 0) + return ret; + } + return 0; } diff --git a/src/internal.c b/src/internal.c index 6aa2cf751..203629a96 100644 --- a/src/internal.c +++ b/src/internal.c @@ -80,6 +80,11 @@ * 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. + * https://www.rfc-editor.org/rfc/rfc8446#section-5.5 + * https://www.rfc-editor.org/rfc/rfc9147.html#name-aead-limits */ @@ -18771,6 +18776,98 @@ static WC_INLINE int VerifyMac(WOLFSSL* ssl, const byte* input, word32 msgSz, return 0; } +enum { + AEAD_LIMIT_OK, + AEAD_LIMIT_KEY_UPDATE, + AEAD_LIMIT_FAIL, +}; + +#if defined(WOLFSSL_DTLS13) && !defined(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. */ +static int checkDTLS13AEADFailLimit(byte bulk_cipher_algorithm, + word16 aead_mac_size, w64wrapper dropped) +{ + w64wrapper keyUpdateLimit; + w64wrapper hardLimit; + switch (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 (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 + default: + WOLFSSL_MSG("Unrecognized ciphersuite for AEAD limit check"); + return AEAD_LIMIT_FAIL; + } + if (w64GT(dropped, hardLimit)) { + WOLFSSL_MSG("Connection exceeded hard AEAD limit"); + return AEAD_LIMIT_FAIL; + } + else if (w64GT(dropped, keyUpdateLimit)) { + WOLFSSL_MSG("Connection exceeded key update limit. Issuing key update"); + return AEAD_LIMIT_KEY_UPDATE; + } + return AEAD_LIMIT_OK; +} +#endif + +#ifdef WOLFSSL_DTLS +static int HandleDTLSDecryptFailed(WOLFSSL* ssl) +{ + ssl->options.processReply = doProcessInit; + ssl->buffers.inputBuffer.idx = ssl->buffers.inputBuffer.length; + +#if defined(WOLFSSL_DTLS_DROP_STATS) || \ + (defined(WOLFSSL_DTLS13) && !defined(WOLFSSL_TLS13_IGNORE_AEAD_LIMITS)) + w64Increment(&ssl->macDropCount); + +#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)) { + int ret = checkDTLS13AEADFailLimit(ssl->specs.bulk_cipher_algorithm, + ssl->specs.aead_mac_size, ssl->macDropCount); + if (ret == AEAD_LIMIT_KEY_UPDATE) { + /* If not waiting for a response then request a key update. */ + if (!ssl->keys.updateResponseReq) + ssl->dtls13DoKeyUpdate = 1; + } + else if (ret == AEAD_LIMIT_FAIL) { + /* We have reached the hard limit for failed decryptions. */ + WOLFSSL_ERROR_VERBOSE(DECRYPT_ERROR); + return DECRYPT_ERROR; + } + } +#endif +#endif + + WOLFSSL_MSG("DTLS: Ignoring failed decryption"); + return 0; +} +#endif + int ProcessReply(WOLFSSL* ssl) { return ProcessReplyEx(ssl, 0); @@ -19077,18 +19174,11 @@ 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 */ - } + if (ssl->options.dtls) + return HandleDTLSDecryptFailed(ssl); #endif /* WOLFSSL_DTLS */ #ifdef WOLFSSL_EXTRA_ALERTS if (!ssl->options.dtls) @@ -19243,16 +19333,8 @@ int ProcessReplyEx(WOLFSSL* ssl, int allowSocketErr) #ifdef WOLFSSL_DTLS /* 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; - } + if (ssl->options.dtls) + return HandleDTLSDecryptFailed(ssl); #endif /* WOLFSSL_DTLS */ #ifdef WOLFSSL_EARLY_DATA if (ssl->options.tls1_3) { @@ -19314,24 +19396,17 @@ 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 */ - } + if (ssl->options.dtls) + 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; } diff --git a/src/ssl.c b/src/ssl.c index 7810c02d5..17b732258 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -3183,10 +3183,12 @@ static int _Rehandshake(WOLFSSL* ssl) return SECURE_RENEGOTIATION_E; } - if (ssl->keys.dtls_epoch == 0xFFFF) { +#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. */ @@ -3282,6 +3284,11 @@ int wolfSSL_Rehandshake(WOLFSSL* ssl) /* CLIENT/SERVER: Reset peer authentication for full secure handshake. */ ssl->options.peerAuthGood = 0; +#ifdef WOLFSSL_DTLS_DROP_STATS + if (ssl->options.dtls) + w64Zero(&ssl->macDropCount); +#endif + #ifdef HAVE_SESSION_TICKET if (ret == WOLFSSL_SUCCESS) #endif diff --git a/src/tls13.c b/src/tls13.c index 50cb2de58..00708b934 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -11120,6 +11120,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. @@ -11132,22 +11152,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 f9727467f..625a96ec9 100644 --- a/tests/api.c +++ b/tests/api.c @@ -55306,6 +55306,148 @@ static int test_wolfSSL_dtls_fragments(void) { } #endif +#ifdef WOLFSSL_DTLS13 +static byte test_AEAD_fail_decryption = 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_limit_client(WOLFSSL* ssl) +{ + int ret; + int i; + int didReKey = 0; + char msgBuf[20]; + w64wrapper hardLimit; + w64wrapper keyUpdateLimit; + w64wrapper counter; + + switch (ssl->specs.bulk_cipher_algorithm) { + 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; + case wolfssl_aes_ccm: + if (ssl->specs.aead_mac_size == AES_CCM_8_AUTH_SZ) { + hardLimit = DTLS_AEAD_AES_CCM_8_FAIL_LIMIT; + keyUpdateLimit = DTLS_AEAD_AES_CCM_8_FAIL_KU_LIMIT; + } + else { + hardLimit = DTLS_AEAD_AES_CCM_FAIL_LIMIT; + keyUpdateLimit = DTLS_AEAD_AES_CCM_FAIL_KU_LIMIT; + } + break; + default: + fprintf(stderr, "Unrecognized bulk cipher"); + AssertFalse(1); + break; + } + + w64Zero(&counter); + AssertTrue(w64Equal(ssl->macDropCount, 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(ssl->macDropCount, counter)); + } + + test_AEAD_fail_decryption = 1; + ssl->macDropCount = keyUpdateLimit; + w64Increment(&ssl->macDropCount); + /* 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 one key update is 4 */ + if (w64Equal(ssl->dtls13Epoch, w64From32(0, 4))) { + didReKey = 1; + break; + } + } + AssertTrue(didReKey); + w64Zero(&counter); + AssertTrue(w64Equal(ssl->macDropCount, counter)); + + test_AEAD_fail_decryption = 2; + ssl->macDropCount = hardLimit; + w64Decrement(&ssl->macDropCount); + /* 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; +} + +static void test_AEAD_limit_server(WOLFSSL* ssl) +{ + char msgBuf[] = "Sending data"; + int ret = WOLFSSL_SUCCESS; + SOCKET_T fd = wolfSSL_get_fd(ssl); + struct timespec delay; + XMEMSET(&delay, 0, sizeof(delay)); + delay.tv_nsec = 50000000; /* wait 0.05 seconds */ + tcp_set_nonblocking(&fd); /* So that read doesn't block */ + while (!test_AEAD_done && ret > 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); + + AssertTrue(func_cb_client.return_code); + AssertTrue(func_cb_server.return_code); + + 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)) @@ -58015,6 +58157,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 c62e2a6b5..e2585c663 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -1267,6 +1267,22 @@ enum { #define TLS13_MAX_TICKET_AGE (7*24*60*60) #endif + +/* 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 */ @@ -4026,6 +4042,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 ? */ @@ -4678,10 +4698,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 */ @@ -4884,8 +4904,10 @@ struct WOLFSSL { #ifdef WOLFSSL_MULTICAST void* mcastHwCbCtx; /* Multicast highwater callback ctx */ #endif /* WOLFSSL_MULTICAST */ +#if defined(WOLFSSL_DTLS_DROP_STATS) || defined(WOLFSSL_DTLS13) + w64wrapper macDropCount; +#endif #ifdef WOLFSSL_DTLS_DROP_STATS - word32 macDropCount; word32 replayDropCount; #endif /* WOLFSSL_DTLS_DROP_STATS */ #ifdef WOLFSSL_SRTP @@ -4910,6 +4932,7 @@ struct WOLFSSL { byte dtls13SendingAckOrRtx:1; byte dtls13FastTimeout:1; byte dtls13WaitKeyUpdateAck:1; + byte dtls13DoKeyUpdate:1; word32 dtls13MessageLength; word32 dtls13FragOffset; byte dtls13FragHandshakeType; @@ -5657,6 +5680,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);