diff --git a/.github/configs/os-check-linux.json b/.github/configs/os-check-linux.json index 23dfc3a4e2..3adf430faf 100644 --- a/.github/configs/os-check-linux.json +++ b/.github/configs/os-check-linux.json @@ -81,6 +81,9 @@ "configure": ["--enable-opensslextra", "CPPFLAGS=-DWOLFSSL_NO_CA_NAMES"]}, {"name": "sessionexport-dtls13", "minutes": 2.6, "configure": ["--enable-sessionexport", "--enable-dtls", "--enable-dtls13"]}, +{"name": "sessionexport-nopeer-dtls13", "minutes": 2.6, + "configure": ["--enable-sessionexport=nopeer", "--enable-dtls", + "--enable-dtls13"]}, {"name": "lms-xmss-verify-only", "minutes": 2.5, "configure": ["--enable-lms=small,verify-only", "--enable-xmss=small,verify-only"]}, {"name": "opensslall-rng-seed-cb", "minutes": 2.2, diff --git a/doc/dox_comments/header_files/ssl.h b/doc/dox_comments/header_files/ssl.h index 1297fbfe32..5e8fff6d1c 100644 --- a/doc/dox_comments/header_files/ssl.h +++ b/doc/dox_comments/header_files/ssl.h @@ -692,7 +692,9 @@ int wolfSSL_use_old_poly(WOLFSSL* ssl, int value); returned. \return Failure All unsuccessful return values will be less than 0. \return VERSION_ERROR If a version mismatch is found ie DTLS v1 and ctx - was set up for DTLS v1.2 then VERSION_ERROR is returned. + was set up for DTLS v1.2 then VERSION_ERROR is returned. Also returned + when importing a DTLS v1.3 session from a buffer serialized before export + version 7, which lacks the DTLS v1.3 record layer state. \param ssl a pointer to a WOLFSSL structure, created using wolfSSL_new(). \param buf serialized session to import. @@ -830,11 +832,26 @@ int wolfSSL_dtls_set_export(WOLFSSL* ssl, wc_dtls_export func); \return Success If successful, the amount of the buffer used will be returned. \return Failure All unsuccessful return values will be less than 0. + \return NOT_READY_ERROR For a DTLS v1.3 session whose handshake has not + completed, or which has a KeyUpdate in flight. Both are transient: retry + the export after the handshake or KeyUpdate exchange completes. + \return DTLS_CID_ERROR For a session that negotiated a Connection ID, + which can not be serialized. \param ssl a pointer to a WOLFSSL structure, created using wolfSSL_new(). \param buf buffer to hold serialized session. \param sz size of buffer. + \note The exported session and the connection it came from share the same + record protection keys and sequence numbers. Once the session has been + imported elsewhere, the original WOLFSSL must no longer be used to send + or receive, otherwise the two ends reuse AEAD nonces. + + \note Records the peer has not yet acknowledged (for example a + NewSessionTicket) and acknowledgments not yet sent are not carried in the + serialized session; the peer retransmits anything it does not see + acknowledged. + _Example_ \code WOLFSSL* ssl; @@ -868,11 +885,20 @@ int wolfSSL_dtls_export(WOLFSSL* ssl, unsigned char* buf, be encrypted before storing if stored. \return the number of bytes written into buffer 'buf' + \return NOT_READY_ERROR For a TLS v1.3 session whose handshake has not + completed. \param ssl WOLFSSL structure to export the session from \param buf output of serialized session \param sz size in bytes set in 'buf' + \note For TLS v1.3 the application traffic secrets are only carried from + export version 7 onward. A session imported from an older serialization + can carry traffic but can not perform a KeyUpdate. + + \note As with wolfSSL_dtls_export, the original WOLFSSL must no longer be + used once the exported session has been imported elsewhere. + \sa wolfSSL_dtls_import \sa wolfSSL_tls_import */ diff --git a/src/internal.c b/src/internal.c index f5400c2088..62d20b7f8a 100644 --- a/src/internal.c +++ b/src/internal.c @@ -1515,6 +1515,7 @@ static int ImportOptions(WOLFSSL* ssl, const byte* exp, word32 len, byte ver, switch (ver) { case WOLFSSL_EXPORT_VERSION: + case WOLFSSL_EXPORT_VERSION_6: if (len < DTLS_EXPORT_OPT_SZ) { WOLFSSL_MSG("Sanity check on buffer size failed"); return BAD_FUNC_ARG; @@ -1689,9 +1690,8 @@ static int ImportOptions(WOLFSSL* ssl, const byte* exp, word32 len, byte ver, return VERSION_ERROR; } - /* set TLS 1.3 flag in options if this was a TLS 1.3 connection */ - if (ssl->version.major == SSLv3_MAJOR && - ssl->version.minor == TLSv1_3_MINOR) { + /* set TLS 1.3 flag in options if this was a (D)TLS 1.3 connection */ + if (IsAtLeastTLSv1_3(ssl->version)) { options->tls1_3 = 1; } @@ -1751,8 +1751,9 @@ static int ImportPeerInfo(WOLFSSL* ssl, const byte* buf, word32 len, byte ver) word16 port; char ip[MAX_EXPORT_IP]; - if (ver != WOLFSSL_EXPORT_VERSION && ver != WOLFSSL_EXPORT_VERSION_5 && - ver != WOLFSSL_EXPORT_VERSION_4 && ver != WOLFSSL_EXPORT_VERSION_3) { + if (ver != WOLFSSL_EXPORT_VERSION && ver != WOLFSSL_EXPORT_VERSION_6 && + ver != WOLFSSL_EXPORT_VERSION_5 && ver != WOLFSSL_EXPORT_VERSION_4 && + ver != WOLFSSL_EXPORT_VERSION_3) { WOLFSSL_MSG("Export version not supported"); return BAD_FUNC_ARG; } @@ -1793,6 +1794,367 @@ static int ImportPeerInfo(WOLFSSL* ssl, const byte* buf, word32 len, byte ver) } +#ifdef WOLFSSL_TLS13 +/* Serialize the (D)TLS 1.3 application traffic secrets and KeyUpdate state. + * On success returns the number of bytes written, negative value on error. */ +static int ExportTls13State(WOLFSSL* ssl, byte* exp, word32 len, byte ver) +{ + word32 idx = 0; + byte secretSz; + + WOLFSSL_ENTER("ExportTls13State"); + + if (exp == NULL || ssl == NULL) { + return BAD_FUNC_ARG; + } + + if (ver != WOLFSSL_EXPORT_VERSION) { + WOLFSSL_MSG("Export version not supported"); + return BAD_FUNC_ARG; + } + + secretSz = ssl->specs.hash_size; + if (secretSz > SECRET_LEN) { + return BAD_STATE_E; + } + if (len < OPAQUE8_LEN + (2 * (word32)secretSz) + (2 * OPAQUE8_LEN) + + (2 * OPAQUE32_LEN)) { + WOLFSSL_MSG("Not enough space to export (D)TLS 1.3 state"); + return BUFFER_E; + } + + exp[idx++] = secretSz; + XMEMCPY(exp + idx, ssl->clientSecret, secretSz); idx += secretSz; + XMEMCPY(exp + idx, ssl->serverSecret, secretSz); idx += secretSz; + exp[idx++] = ssl->keys.updateResponseReq; + exp[idx++] = ssl->keys.keyUpdateRespond; + c32toa(w64GetHigh32(ssl->keys.keyUpdateCount), exp + idx); + idx += OPAQUE32_LEN; + c32toa(w64GetLow32(ssl->keys.keyUpdateCount), exp + idx); + idx += OPAQUE32_LEN; + + WOLFSSL_LEAVE("ExportTls13State", (int)idx); + return (int)idx; +} + + +/* Import the (D)TLS 1.3 application traffic secrets and KeyUpdate state. + * Expects the CipherSpecs to have been imported already so the secret length + * can be validated against the negotiated suite. + * On success returns the number of bytes read, negative value on error. */ +static int ImportTls13State(WOLFSSL* ssl, const byte* exp, word32 len, byte ver) +{ + word32 idx = 0; + word32 hi; + word32 lo; + byte secretSz; + + WOLFSSL_ENTER("ImportTls13State"); + + if (exp == NULL || ssl == NULL) { + return BAD_FUNC_ARG; + } + + if (ver != WOLFSSL_EXPORT_VERSION) { + WOLFSSL_MSG("Export version not supported"); + return BAD_FUNC_ARG; + } + + if (len < OPAQUE8_LEN) { + return BUFFER_E; + } + secretSz = exp[idx++]; + if (secretSz > SECRET_LEN || + len < OPAQUE8_LEN + (2 * (word32)secretSz) + (2 * OPAQUE8_LEN) + + (2 * OPAQUE32_LEN)) { + WOLFSSL_MSG("Buffer not large enough for secrets import"); + return BUFFER_E; + } + if (secretSz != ssl->specs.hash_size) { + WOLFSSL_MSG("Imported secret length does not match cipher suite"); + return BAD_STATE_E; + } + + XMEMCPY(ssl->clientSecret, exp + idx, secretSz); idx += secretSz; + XMEMCPY(ssl->serverSecret, exp + idx, secretSz); idx += secretSz; + ssl->keys.updateResponseReq = exp[idx++]; + ssl->keys.keyUpdateRespond = exp[idx++]; + ato32(exp + idx, &hi); idx += OPAQUE32_LEN; + ato32(exp + idx, &lo); idx += OPAQUE32_LEN; + ssl->keys.keyUpdateCount = w64From32(hi, lo); + + WOLFSSL_LEAVE("ImportTls13State", (int)idx); + return (int)idx; +} +#endif /* WOLFSSL_TLS13 */ + + +#ifdef WOLFSSL_DTLS13 +/* Serialize the DTLS 1.3 record layer state: current epoch numbers, the send + * and expected peer record sequence numbers, the AEAD decrypt failure count + * and the replay window of the current decrypt epoch. + * On success returns the number of bytes written, negative value on error. */ +static int ExportDtls13State(WOLFSSL* ssl, byte* exp, word32 len, byte ver) +{ + word32 idx = 0; + word32 i; + Dtls13Epoch* sendEpoch; + Dtls13Epoch* recvEpoch; + w64wrapper dropCount = w64From32(0, 0); + + WOLFSSL_ENTER("ExportDtls13State"); + + if (exp == NULL || ssl == NULL) { + return BAD_FUNC_ARG; + } + + if (ver != WOLFSSL_EXPORT_VERSION) { + WOLFSSL_MSG("Export version not supported"); + return BAD_FUNC_ARG; + } + + if (len < WOLFSSL_EXPORT_DTLS13_SZ) { + WOLFSSL_MSG("Not enough space to export DTLS 1.3 state"); + return BUFFER_E; + } + + sendEpoch = Dtls13GetEpoch(ssl, ssl->dtls13Epoch); + recvEpoch = Dtls13GetEpoch(ssl, ssl->dtls13PeerEpoch); + if (sendEpoch == NULL || recvEpoch == NULL) { + WOLFSSL_MSG("DTLS 1.3 export is missing current epoch state"); + return BAD_STATE_E; + } + + c32toa(w64GetHigh32(ssl->dtls13Epoch), exp + idx); idx += OPAQUE32_LEN; + c32toa(w64GetLow32(ssl->dtls13Epoch), exp + idx); idx += OPAQUE32_LEN; + c32toa(w64GetHigh32(ssl->dtls13PeerEpoch), exp + idx); idx += OPAQUE32_LEN; + c32toa(w64GetLow32(ssl->dtls13PeerEpoch), exp + idx); idx += OPAQUE32_LEN; + c32toa(w64GetHigh32(sendEpoch->nextSeqNumber), exp + idx); + idx += OPAQUE32_LEN; + c32toa(w64GetLow32(sendEpoch->nextSeqNumber), exp + idx); + idx += OPAQUE32_LEN; + c32toa(w64GetHigh32(recvEpoch->nextPeerSeqNumber), exp + idx); + idx += OPAQUE32_LEN; + c32toa(w64GetLow32(recvEpoch->nextPeerSeqNumber), exp + idx); + idx += OPAQUE32_LEN; +#ifndef WOLFSSL_TLS13_IGNORE_AEAD_LIMITS + dropCount = recvEpoch->dropCount; +#endif + c32toa(w64GetHigh32(dropCount), exp + idx); idx += OPAQUE32_LEN; + c32toa(w64GetLow32(dropCount), exp + idx); idx += OPAQUE32_LEN; + + c16toa(WOLFSSL_DTLS_WINDOW_WORDS, exp + idx); idx += OPAQUE16_LEN; + for (i = 0; i < WOLFSSL_DTLS_WINDOW_WORDS; i++) { + c32toa(recvEpoch->window[i], exp + idx); idx += OPAQUE32_LEN; + } + + WOLFSSL_LEAVE("ExportDtls13State", (int)idx); + return (int)idx; +} + + +/* Import the DTLS 1.3 record layer state and rebuild the epoch table and + * cipher state from it. Expects Options, Keys, CipherSpecs and the (D)TLS 1.3 + * secrets to have been imported already. When stateOnly is set only the + * sequence numbers and replay window of the already existing epochs are + * updated (no secrets are available in a state-only blob). + * On success returns the number of bytes read, negative value on error. */ +static int ImportDtls13State(WOLFSSL* ssl, const byte* exp, word32 len, + byte ver, int stateOnly) +{ + word32 idx = 0; + word32 hi; + word32 lo; + word32 wordAdj = 0; + word16 wordCount; + word16 i; + word32 window[WOLFSSL_DTLS_WINDOW_WORDS]; + w64wrapper sendEpochNum; + w64wrapper recvEpochNum; + w64wrapper nextSeq; + w64wrapper nextPeerSeq; + w64wrapper dropCount; + Dtls13Epoch* e; + int ret; + + WOLFSSL_ENTER("ImportDtls13State"); + + if (exp == NULL || ssl == NULL) { + return BAD_FUNC_ARG; + } + + if (ver != WOLFSSL_EXPORT_VERSION) { + WOLFSSL_MSG("Export version not supported"); + return BAD_FUNC_ARG; + } + + if (len < (5 * 2 * OPAQUE32_LEN) + OPAQUE16_LEN) { + WOLFSSL_MSG("Buffer not large enough for DTLS 1.3 state import"); + return BUFFER_E; + } + + ato32(exp + idx, &hi); idx += OPAQUE32_LEN; + ato32(exp + idx, &lo); idx += OPAQUE32_LEN; + sendEpochNum = w64From32(hi, lo); + ato32(exp + idx, &hi); idx += OPAQUE32_LEN; + ato32(exp + idx, &lo); idx += OPAQUE32_LEN; + recvEpochNum = w64From32(hi, lo); + ato32(exp + idx, &hi); idx += OPAQUE32_LEN; + ato32(exp + idx, &lo); idx += OPAQUE32_LEN; + nextSeq = w64From32(hi, lo); + ato32(exp + idx, &hi); idx += OPAQUE32_LEN; + ato32(exp + idx, &lo); idx += OPAQUE32_LEN; + nextPeerSeq = w64From32(hi, lo); + ato32(exp + idx, &hi); idx += OPAQUE32_LEN; + ato32(exp + idx, &lo); idx += OPAQUE32_LEN; + dropCount = w64From32(hi, lo); + + /* application traffic epochs start at DTLS13_EPOCH_TRAFFIC0 */ + if (w64GetHigh32(sendEpochNum) == 0 && + w64GetLow32(sendEpochNum) < DTLS13_EPOCH_TRAFFIC0) { + WOLFSSL_MSG("Imported DTLS 1.3 send epoch not a traffic epoch"); + return BAD_STATE_E; + } + if (w64GetHigh32(recvEpochNum) == 0 && + w64GetLow32(recvEpochNum) < DTLS13_EPOCH_TRAFFIC0) { + WOLFSSL_MSG("Imported DTLS 1.3 peer epoch not a traffic epoch"); + return BAD_STATE_E; + } + + ato16(exp + idx, &wordCount); idx += OPAQUE16_LEN; + if (wordCount == 0) { + WOLFSSL_MSG("Imported DTLS 1.3 replay window is empty"); + return BAD_STATE_E; + } + if (wordCount > WOLFSSL_DTLS_WINDOW_WORDS) { + wordAdj = (wordCount - WOLFSSL_DTLS_WINDOW_WORDS) * sizeof(word32); + wordCount = WOLFSSL_DTLS_WINDOW_WORDS; + } + if (idx + (wordCount * OPAQUE32_LEN) + wordAdj > len) { + WOLFSSL_MSG("Buffer not large enough for replay window import"); + return BUFFER_E; + } + XMEMSET(window, 0xFF, sizeof(window)); + for (i = 0; i < wordCount; i++) { + ato32(exp + idx, &window[i]); idx += OPAQUE32_LEN; + } + idx += wordAdj; + + if (!stateOnly) { + /* drop pending retransmission and ACK state; the peer retransmits + * anything it does not see acknowledged */ + Dtls13FreeFsmResources(ssl); + + ssl->dtls13Epoch = sendEpochNum; + ssl->dtls13PeerEpoch = recvEpochNum; + + ret = Dtls13DeriveSnKeys(ssl, PROVISION_CLIENT_SERVER); + if (ret != 0) { + return ret; + } + + if (w64Equal(sendEpochNum, recvEpochNum)) { + ret = Dtls13NewEpoch(ssl, sendEpochNum, ENCRYPT_AND_DECRYPT_SIDE); + } + else { + ret = Dtls13NewEpoch(ssl, sendEpochNum, ENCRYPT_SIDE_ONLY); + if (ret == 0) { + ret = Dtls13NewEpoch(ssl, recvEpochNum, DECRYPT_SIDE_ONLY); + } + } + if (ret != 0) { + return ret; + } + } + else { + ssl->dtls13Epoch = sendEpochNum; + ssl->dtls13PeerEpoch = recvEpochNum; + } + + e = Dtls13GetEpoch(ssl, sendEpochNum); + if (e == NULL) { + WOLFSSL_MSG("Imported DTLS 1.3 send epoch not found"); + return BAD_STATE_E; + } + e->nextSeqNumber = nextSeq; + + e = Dtls13GetEpoch(ssl, recvEpochNum); + if (e == NULL) { + WOLFSSL_MSG("Imported DTLS 1.3 peer epoch not found"); + return BAD_STATE_E; + } + e->nextPeerSeqNumber = nextPeerSeq; + XMEMCPY(e->window, window, sizeof(window)); +#ifndef WOLFSSL_TLS13_IGNORE_AEAD_LIMITS + e->dropCount = dropCount; +#else + (void)dropCount; +#endif + + if (w64Equal(sendEpochNum, recvEpochNum)) { + ret = Dtls13SetEpochKeys(ssl, sendEpochNum, ENCRYPT_AND_DECRYPT_SIDE); + } + else { + ret = Dtls13SetEpochKeys(ssl, sendEpochNum, ENCRYPT_SIDE_ONLY); + if (ret == 0) { + ret = Dtls13SetEpochKeys(ssl, recvEpochNum, DECRYPT_SIDE_ONLY); + } + } + if (ret != 0) { + return ret; + } + + WOLFSSL_LEAVE("ImportDtls13State", (int)idx); + return (int)idx; +} +#endif /* WOLFSSL_DTLS13 */ + + +/* Check if a (D)TLS 1.3 connection is in a state that can be serialized. + * Returns 0 when export can proceed, negative error value otherwise. */ +static int ExportStateReady13(WOLFSSL* ssl, int stateOnly) +{ + int ret = 0; + + if (!IsAtLeastTLSv1_3(ssl->version)) + return 0; + + if (ssl->options.handShakeDone == 0) { + WOLFSSL_MSG("(D)TLS 1.3 session export needs a completed handshake"); + ret = NOT_READY_ERROR; + } + +#ifdef WOLFSSL_DTLS13 + if (ret == 0 && ssl->options.dtls) { + int keyUpdateInFlight = ssl->dtls13WaitKeyUpdateAck || + ssl->dtls13DoKeyUpdate || ssl->keys.keyUpdateRespond || + ssl->options.sendKeyUpdate; + + if (keyUpdateInFlight) { + WOLFSSL_MSG("Not exporting DTLS 1.3 session with a KeyUpdate in " + "flight"); + ret = NOT_READY_ERROR; + } +#ifdef WOLFSSL_DTLS_CID + if (ret == 0 && ssl->dtlsCidInfo != NULL) { + WOLFSSL_MSG("Exporting a session using a Connection ID is not " + "supported"); + ret = DTLS_CID_ERROR; + } +#endif + if (ret == 0 && (Dtls13GetEpoch(ssl, ssl->dtls13Epoch) == NULL || + Dtls13GetEpoch(ssl, ssl->dtls13PeerEpoch) == NULL)) { + WOLFSSL_MSG("DTLS 1.3 export is missing current epoch state"); + ret = BAD_STATE_E; + } + } +#endif /* WOLFSSL_DTLS13 */ + + (void)stateOnly; + return ret; +} + + #ifdef WOLFSSL_DTLS /* WOLFSSL_LOCAL function that serializes the current WOLFSSL session state only * buf is used to hold the serialized WOLFSSL struct and sz is the size of buf @@ -1811,9 +2173,20 @@ int wolfSSL_dtls_export_state_internal(WOLFSSL* ssl, byte* buf, word32 sz) return BAD_FUNC_ARG; } + ret = ExportStateReady13(ssl, 1); + if (ret != 0) { + WOLFSSL_LEAVE("wolfSSL_dtls_export_state_internal", ret); + return ret; + } + totalLen += WOLFSSL_EXPORT_LEN * 2; /* 2 protocol bytes and 2 length bytes */ /* each of the following have a 2 byte length before data */ totalLen += WOLFSSL_EXPORT_LEN + DTLS_EXPORT_MIN_KEY_SZ; +#ifdef WOLFSSL_DTLS13 + if (IsAtLeastTLSv1_3(ssl->version)) { + totalLen += WOLFSSL_EXPORT_LEN + WOLFSSL_EXPORT_DTLS13_SZ; + } +#endif if (totalLen > sz) { WOLFSSL_LEAVE("wolfSSL_dtls_export_state_internal", BUFFER_E); return BUFFER_E; @@ -1833,6 +2206,19 @@ int wolfSSL_dtls_export_state_internal(WOLFSSL* ssl, byte* buf, word32 sz) } c16toa((word16)ret, buf + idx - WOLFSSL_EXPORT_LEN); idx += ret; +#ifdef WOLFSSL_DTLS13 + /* export of DTLS 1.3 record layer state */ + if (IsAtLeastTLSv1_3(ssl->version)) { + idx += WOLFSSL_EXPORT_LEN; /* leave room for length */ + if ((ret = ExportDtls13State(ssl, buf + idx, sz - idx, + WOLFSSL_EXPORT_VERSION)) < 0) { + WOLFSSL_LEAVE("wolfSSL_dtls_export_state_internal", ret); + return ret; + } + c16toa((word16)ret, buf + idx - WOLFSSL_EXPORT_LEN); idx += ret; + } +#endif /* WOLFSSL_DTLS13 */ + /* place total length of exported buffer minus 2 bytes protocol/version */ c16toa((word16)(idx - WOLFSSL_EXPORT_LEN), buf + WOLFSSL_EXPORT_LEN); @@ -1894,6 +2280,7 @@ int wolfSSL_dtls_import_state_internal(WOLFSSL* ssl, const byte* buf, word32 sz) /* perform sanity checks and extract Options information used */ switch (version) { case WOLFSSL_EXPORT_VERSION: + case WOLFSSL_EXPORT_VERSION_6: break; default: @@ -1902,6 +2289,13 @@ int wolfSSL_dtls_import_state_internal(WOLFSSL* ssl, const byte* buf, word32 sz) } + /* pre-v7 state blobs have no DTLS 1.3 record layer state (per-epoch + * sequence numbers, replay window) */ + if (version <= WOLFSSL_EXPORT_VERSION_6 && IsAtLeastTLSv1_3(ssl->version)) { + WOLFSSL_MSG("Pre-v7 state blob can not hold DTLS 1.3 state"); + return VERSION_ERROR; + } + /* perform sanity checks and extract Keys struct */ if (WOLFSSL_EXPORT_LEN + idx > sz) { WOLFSSL_MSG("Import Key struct error"); @@ -1920,6 +2314,27 @@ int wolfSSL_dtls_import_state_internal(WOLFSSL* ssl, const byte* buf, word32 sz) } idx += ret; +#ifdef WOLFSSL_DTLS13 + /* perform sanity checks and extract the DTLS 1.3 record layer state */ + if (IsAtLeastTLSv1_3(ssl->version)) { + if (WOLFSSL_EXPORT_LEN + idx > sz) { + WOLFSSL_MSG("Import DTLS 1.3 state error"); + return BUFFER_E; + } + ato16(buf + idx, &length); idx += WOLFSSL_EXPORT_LEN; + if (idx + length > sz) { + WOLFSSL_MSG("Import DTLS 1.3 state error"); + return BUFFER_E; + } + if ((ret = ImportDtls13State(ssl, buf + idx, length, version, 1)) < 0) { + WOLFSSL_MSG("Import DTLS 1.3 state error"); + WOLFSSL_LEAVE("wolfSSL_dtls_import_state_internal", ret); + return ret; + } + idx += ret; + } +#endif /* WOLFSSL_DTLS13 */ + WOLFSSL_LEAVE("wolfSSL_dtls_import_state_internal", ret); return idx; } @@ -2002,6 +2417,7 @@ int wolfSSL_session_import_internal(WOLFSSL* ssl, const unsigned char* buf, if (ret == 0) { switch (version) { case WOLFSSL_EXPORT_VERSION: + case WOLFSSL_EXPORT_VERSION_6: if (type == WOLFSSL_EXPORT_DTLS) { optSz = DTLS_EXPORT_OPT_SZ; } @@ -2063,6 +2479,15 @@ int wolfSSL_session_import_internal(WOLFSSL* ssl, const unsigned char* buf, } } + /* a DTLS 1.3 session can not be restored from a pre-v7 blob: the format + * predates the DTLS 1.3 record layer state */ + if (ret == 0 && type == WOLFSSL_EXPORT_DTLS && + version <= WOLFSSL_EXPORT_VERSION_6 && + IsAtLeastTLSv1_3(ssl->version)) { + WOLFSSL_MSG("Pre-v7 export blob can not hold a DTLS 1.3 session"); + ret = VERSION_ERROR; + } + /* perform sanity checks and extract Keys struct */ if (ret == 0 && (WOLFSSL_EXPORT_LEN + idx > sz)) { WOLFSSL_MSG("Import Key struct error"); @@ -2142,12 +2567,77 @@ int wolfSSL_session_import_internal(WOLFSSL* ssl, const unsigned char* buf, } } +#ifdef WOLFSSL_TLS13 + /* perform sanity checks and extract the (D)TLS 1.3 secrets and KeyUpdate + * state; the chunk is required for a (D)TLS 1.3 session from version 7 */ + if (ret == 0 && version >= WOLFSSL_EXPORT_VERSION && + IsAtLeastTLSv1_3(ssl->version)) { + if (WOLFSSL_EXPORT_LEN + idx > sz) { + WOLFSSL_MSG("Import TLS 1.3 state error"); + ret = BUFFER_E; + } + if (ret == 0) { + ato16(buf + idx, &length); idx += WOLFSSL_EXPORT_LEN; + if (idx + length > sz) { + WOLFSSL_MSG("Import TLS 1.3 state error"); + ret = BUFFER_E; + } + } + if (ret == 0) { + rc = ImportTls13State(ssl, buf + idx, length, version); + if (rc < 0) { + WOLFSSL_MSG("Import TLS 1.3 state error"); + ret = rc; + } + else { + idx += rc; + } + } + } + +#ifdef WOLFSSL_DTLS13 + /* perform sanity checks and extract the DTLS 1.3 record layer state */ + if (ret == 0 && version >= WOLFSSL_EXPORT_VERSION && + IsAtLeastTLSv1_3(ssl->version) && type == WOLFSSL_EXPORT_DTLS) { + if (WOLFSSL_EXPORT_LEN + idx > sz) { + WOLFSSL_MSG("Import DTLS 1.3 state error"); + ret = BUFFER_E; + } + if (ret == 0) { + ato16(buf + idx, &length); idx += WOLFSSL_EXPORT_LEN; + if (idx + length > sz) { + WOLFSSL_MSG("Import DTLS 1.3 state error"); + ret = BUFFER_E; + } + } + if (ret == 0) { + rc = ImportDtls13State(ssl, buf + idx, length, version, 0); + if (rc < 0) { + WOLFSSL_MSG("Import DTLS 1.3 state error"); + ret = rc; + } + else { + idx += rc; + } + } + } +#endif /* WOLFSSL_DTLS13 */ +#endif /* WOLFSSL_TLS13 */ + /* make sure is a valid suite used */ if (ret == 0 && wolfSSL_get_cipher(ssl) == NULL) { WOLFSSL_MSG("Can not match cipher suite imported"); ret = MATCH_SUITE_ERROR; } +#ifdef WOLFSSL_TLS13 + /* mark Finished as received so post-handshake messages (KeyUpdate, + * NewSessionTicket) are accepted on the imported session */ + if (ret == 0 && ssl->options.tls1_3 && ssl->options.handShakeDone) { + ssl->msgsReceived.got_finished = 1; + } +#endif + #ifndef WOLFSSL_AEAD_ONLY /* set hmac function to use when verifying */ if (ret == 0 && (ssl->options.tls == 1 || ssl->options.tls1_1 == 1 || @@ -2201,6 +2691,31 @@ int wolfSSL_session_export_internal(WOLFSSL* ssl, byte* buf, word32* sz, ret = BAD_FUNC_ARG; } + if (ret == 0) { + ret = ExportStateReady13(ssl, 0); + } + +#ifdef WOLFSSL_DTLS13 + /* ssl->keys holds the keys of the epochs last used on the wire, which can + * lag the current epoch pair: processing a record of an older epoch loads + * that epoch's keys. Sync ssl->keys back to the current epoch pair so the + * serialized key state matches the epoch numbers in the DTLS 1.3 state + * chunk. */ + if (ret == 0 && ssl->options.dtls && IsAtLeastTLSv1_3(ssl->version)) { + if (ssl->dtls13EncryptEpoch == NULL || + !w64Equal(ssl->dtls13EncryptEpoch->epochNumber, + ssl->dtls13Epoch)) { + ret = Dtls13SetEpochKeys(ssl, ssl->dtls13Epoch, ENCRYPT_SIDE_ONLY); + } + if (ret == 0 && (ssl->dtls13DecryptEpoch == NULL || + !w64Equal(ssl->dtls13DecryptEpoch->epochNumber, + ssl->dtls13PeerEpoch))) { + ret = Dtls13SetEpochKeys(ssl, ssl->dtls13PeerEpoch, + DECRYPT_SIDE_ONLY); + } + } +#endif + if (ret == 0) { totalLen += WOLFSSL_EXPORT_LEN * 2; /* 2 protocol bytes and 2 length bytes */ /* each of the following have a 2 byte length before data */ @@ -2212,6 +2727,16 @@ int wolfSSL_session_export_internal(WOLFSSL* ssl, byte* buf, word32* sz, totalLen += WOLFSSL_EXPORT_LEN + ssl->buffers.dtlsCtx.peer.sz; } #endif + #ifdef WOLFSSL_TLS13 + if (IsAtLeastTLSv1_3(ssl->version)) { + totalLen += WOLFSSL_EXPORT_LEN + WOLFSSL_EXPORT_TLS13_SZ; + #ifdef WOLFSSL_DTLS13 + if (type == WOLFSSL_EXPORT_DTLS) { + totalLen += WOLFSSL_EXPORT_LEN + WOLFSSL_EXPORT_DTLS13_SZ; + } + #endif + } + #endif } /* check if sz is sufficient for the worst-case scenario computed above, @@ -2284,6 +2809,35 @@ int wolfSSL_session_export_internal(WOLFSSL* ssl, byte* buf, word32* sz, } } +#ifdef WOLFSSL_TLS13 + /* export of (D)TLS 1.3 secrets and KeyUpdate state */ + if (ret == 0 && IsAtLeastTLSv1_3(ssl->version)) { + idx += WOLFSSL_EXPORT_LEN; + ret = ExportTls13State(ssl, buf + idx, *sz - idx, + WOLFSSL_EXPORT_VERSION); + if (ret >= 0) { + c16toa((word16)ret, buf + idx - WOLFSSL_EXPORT_LEN); + idx += ret; + ret = 0; + } + } + +#ifdef WOLFSSL_DTLS13 + /* export of DTLS 1.3 record layer state */ + if (ret == 0 && IsAtLeastTLSv1_3(ssl->version) && + type == WOLFSSL_EXPORT_DTLS) { + idx += WOLFSSL_EXPORT_LEN; + ret = ExportDtls13State(ssl, buf + idx, *sz - idx, + WOLFSSL_EXPORT_VERSION); + if (ret >= 0) { + c16toa((word16)ret, buf + idx - WOLFSSL_EXPORT_LEN); + idx += ret; + ret = 0; + } + } +#endif /* WOLFSSL_DTLS13 */ +#endif /* WOLFSSL_TLS13 */ + if (ret != 0 && ret != WC_NO_ERR_TRACE(LENGTH_ONLY_E) && buf != NULL) { /*in a fail case clear the buffer which could contain partial key info*/ XMEMSET(buf, 0, *sz); diff --git a/src/tls13.c b/src/tls13.c index 83ac9bd39a..5d042e0c97 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -16881,6 +16881,15 @@ int wolfSSL_accept_TLSv13(WOLFSSL* ssl) FreeAsyncCtx(ssl, 1); #endif +#if defined(WOLFSSL_SESSION_EXPORT) && defined(WOLFSSL_DTLS) + if (ssl->dtls_export) { + if ((ssl->error = wolfSSL_send_session(ssl)) != 0) { + WOLFSSL_MSG("Export DTLS session error"); + WOLFSSL_ERROR(ssl->error); + return WOLFSSL_FATAL_ERROR; + } + } +#endif ssl->error = 0; /* clear the error */ WOLFSSL_LEAVE("wolfSSL_accept", WOLFSSL_SUCCESS); diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index fa70f7126d..3af55f8b4d 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -3235,11 +3235,9 @@ int test_wolfSSL_dtls_export_peers(void) {wolfDTLSv1_client_method, wolfDTLSv1_server_method, "1.0"}, #endif {wolfDTLSv1_2_client_method, wolfDTLSv1_2_server_method, "1.2"}, - /* TODO DTLS 1.3 exporting not supported #ifdef WOLFSSL_DTLS13 {wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method, "1.3"}, #endif - */ }; for (i = 0; i < sizeof(params)/sizeof(*params); i++) { @@ -3386,6 +3384,149 @@ int test_wolfSSL_dtls_import_state_extra_window_words(void) return EXPECT_RESULT(); } + +#if defined(WOLFSSL_DTLS) && defined(WOLFSSL_SESSION_EXPORT) && \ + !defined(WOLFSSL_NO_TLS12) +/* DTLS 1.2 session serialized with export version 6, captured before the + * (D)TLS 1.3 state chunks were added to the format. Generated with a memio + * DTLS 1.2 handshake followed by wolfSSL_dtls_export() of the server end + * (dummy peer callbacks: ip[0]=-1, ipSz=1, port=1, fam=2). */ +static byte canned_server_dtls12_session_v6[] = { + 0xA5, 0xA6, 0x01, 0x78, 0x00, 0x42, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x02, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x1C, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x30, 0x08, 0x0F, 0x10, + 0x01, 0xFF, 0x00, 0x10, 0x05, 0x00, 0x00, 0x00, 0x00, 0x01, + 0xFE, 0xFD, 0x01, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x10, + 0x01, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x20, 0xDC, 0x84, 0xDA, 0x67, 0x4C, 0x44, 0xF3, 0x60, 0xD1, + 0xCB, 0x80, 0x4D, 0xBD, 0xA2, 0x0F, 0xDC, 0x6B, 0x91, 0x57, + 0xFF, 0x15, 0xB3, 0x1A, 0xA3, 0xC0, 0x8E, 0xB5, 0x9D, 0x1E, + 0xB7, 0xB3, 0x59, 0x52, 0x65, 0xFC, 0xB4, 0x92, 0x54, 0x3B, + 0x2C, 0x1E, 0x8D, 0x7C, 0xAD, 0x48, 0x3F, 0x95, 0x5D, 0x99, + 0xBE, 0xF4, 0x5B, 0xAA, 0x78, 0xA6, 0xAD, 0xA0, 0x70, 0x90, + 0xB5, 0x6C, 0x6F, 0x1D, 0x65, 0x04, 0x3D, 0x5F, 0xDC, 0xBA, + 0xCE, 0xEF, 0x3C, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x04, 0xCE, 0xEF, 0x3C, 0xDC, 0x3D, 0x5F, 0xDC, + 0xBA, 0x00, 0x10, 0x00, 0x20, 0x00, 0x04, 0x00, 0x10, 0x00, + 0x10, 0x07, 0x02, 0x05, 0x07, 0x01, 0x30, 0x28, 0x00, 0x00, + 0x07, 0x00, 0x02, 0x00, 0x01, 0xFF, 0x00, 0x01, +}; + +static int test_dtls_import_v6_set_peer(WOLFSSL* ssl, char* ip, int ipSz, + unsigned short port, int fam) +{ + (void)ssl; + (void)ip; + (void)ipSz; + (void)port; + (void)fam; + return 1; +} +#endif + +/* Session blobs serialized with export version 6 must still import. */ +int test_wolfSSL_dtls_import_v6_canned(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS) && defined(WOLFSSL_SESSION_EXPORT) && \ + !defined(WOLFSSL_NO_TLS12) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfDTLSv1_2_server_method())); + wolfSSL_CTX_SetIOSetPeer(ctx, test_dtls_import_v6_set_peer); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_dtls_import(ssl, canned_server_dtls12_session_v6, + sizeof(canned_server_dtls12_session_v6)), + (int)sizeof(canned_server_dtls12_session_v6)); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* Importing a pre-v7 blob into a DTLS 1.3 object must fail with + * VERSION_ERROR: those formats predate the DTLS 1.3 record state. */ +int test_wolfSSL_dtls13_import_reject_v6(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_DTLS) && defined(WOLFSSL_SESSION_EXPORT) && \ + !defined(WOLFSSL_NO_TLS12) && defined(WOLFSSL_DTLS13) + WOLFSSL_CTX* ctx12 = NULL; + WOLFSSL_CTX* ctx13 = NULL; + WOLFSSL* ssl12 = NULL; + WOLFSSL* ssl13 = NULL; + byte blob[sizeof(canned_server_dtls12_session_v6)]; + byte state[MAX_EXPORT_STATE_BUFFER]; + unsigned int stateSz; + word16 optLen = 0; + word32 verOff; + + ExpectNotNull(ctx13 = wolfSSL_CTX_new(wolfDTLSv1_3_server_method())); + wolfSSL_CTX_SetIOSetPeer(ctx13, test_dtls_import_v6_set_peer); + ExpectNotNull(ssl13 = wolfSSL_new(ctx13)); + + /* patch the protocol version bytes at the end of the Options chunk from + * DTLS 1.2 to DTLS 1.3 */ + XMEMCPY(blob, canned_server_dtls12_session_v6, sizeof(blob)); + optLen = (word16)((blob[2 * WOLFSSL_EXPORT_LEN] << 8) | + blob[2 * WOLFSSL_EXPORT_LEN + 1]); + ExpectIntEQ(optLen, DTLS_EXPORT_OPT_SZ); + verOff = 3 * WOLFSSL_EXPORT_LEN + optLen - 2; + ExpectIntEQ(blob[verOff], DTLS_MAJOR); + ExpectIntEQ(blob[verOff + 1], DTLSv1_2_MINOR); + blob[verOff + 1] = DTLSv1_3_MINOR; + + ExpectIntEQ(wolfSSL_dtls_import(ssl13, blob, sizeof(blob)), + WC_NO_ERR_TRACE(VERSION_ERROR)); + + /* the same patched blob into a DTLS 1.2 object is a version mismatch */ + ExpectNotNull(ctx12 = wolfSSL_CTX_new(wolfDTLSv1_2_server_method())); + wolfSSL_CTX_SetIOSetPeer(ctx12, test_dtls_import_v6_set_peer); + ExpectNotNull(ssl12 = wolfSSL_new(ctx12)); + ExpectIntEQ(wolfSSL_dtls_import(ssl12, blob, sizeof(blob)), + WC_NO_ERR_TRACE(VERSION_ERROR)); + + /* a pre-v7 state-only blob is also refused on a DTLS 1.3 object */ + wolfSSL_free(ssl12); + ssl12 = NULL; + ExpectNotNull(ssl12 = wolfSSL_new(ctx12)); + stateSz = sizeof(state); + ExpectIntGT(wolfSSL_dtls_export_state_only(ssl12, state, &stateSz), 0); + /* force the export version nibble to 6 so this stays a pre-v7 blob */ + state[1] = (byte)(((byte)DTLS_EXPORT_STATE_PRO & 0xF0) | + ((byte)WOLFSSL_EXPORT_VERSION_6 & 0x0F)); + ExpectIntEQ(wolfSSL_dtls_import(ssl13, state, stateSz), + WC_NO_ERR_TRACE(VERSION_ERROR)); + + wolfSSL_free(ssl12); + wolfSSL_free(ssl13); + wolfSSL_CTX_free(ctx12); + wolfSSL_CTX_free(ctx13); +#endif + return EXPECT_RESULT(); +} + /*----------------------------------------------------------------------------*/ /* DTLS either-side method and cookie generation */ /*----------------------------------------------------------------------------*/ diff --git a/tests/api/test_dtls.h b/tests/api/test_dtls.h index 37d8a8a2a4..24ddb1369a 100644 --- a/tests/api/test_dtls.h +++ b/tests/api/test_dtls.h @@ -91,6 +91,8 @@ int test_dtls12_missing_finished(void); int test_wolfSSL_dtls_export(void); int test_wolfSSL_dtls_export_peers(void); int test_wolfSSL_dtls_import_state_extra_window_words(void); +int test_wolfSSL_dtls_import_v6_canned(void); +int test_wolfSSL_dtls13_import_reject_v6(void); int test_wolfSSL_DTLS_either_side(void); int test_generate_cookie(void); int test_wolfSSL_dtls_set_mtu(void); @@ -144,6 +146,8 @@ int test_WOLFSSL_dtls_version_alert(void); TEST_DECL_GROUP("dtls", test_wolfSSL_dtls_export_peers), \ TEST_DECL_GROUP("dtls", \ test_wolfSSL_dtls_import_state_extra_window_words), \ + TEST_DECL_GROUP("dtls", test_wolfSSL_dtls_import_v6_canned), \ + TEST_DECL_GROUP("dtls", test_wolfSSL_dtls13_import_reject_v6), \ TEST_DECL_GROUP("dtls", test_wolfSSL_DTLS_either_side), \ TEST_DECL_GROUP("dtls", test_generate_cookie), \ TEST_DECL_GROUP("dtls", test_wolfSSL_dtls_set_mtu), \ diff --git a/tests/api/test_dtls13.c b/tests/api/test_dtls13.c index c8fdcd65df..33da7dc7a3 100644 --- a/tests/api/test_dtls13.c +++ b/tests/api/test_dtls13.c @@ -2110,3 +2110,1515 @@ int test_dtls13_reuse_after_clear(void) #endif return EXPECT_RESULT(); } + +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SESSION_EXPORT) +/* Dummy peer info callbacks for the session exporter/importer (required + * unless built with WOLFSSL_SESSION_EXPORT_NOPEER). */ +static int test_dtls13_export_get_peer(WOLFSSL* ssl, char* ip, int* ipSz, + unsigned short* port, int* fam) +{ + (void)ssl; + ip[0] = -1; + *ipSz = 1; + *port = 1; + *fam = 2; + return 1; +} + +static int test_dtls13_export_set_peer(WOLFSSL* ssl, char* ip, int ipSz, + unsigned short port, int fam) +{ + (void)ssl; + (void)ip; + (void)ipSz; + (void)port; + (void)fam; + return 1; +} + +static void test_dtls13_export_set_peer_cb(WOLFSSL_CTX* ctx) +{ + wolfSSL_CTX_SetIOGetPeer(ctx, test_dtls13_export_get_peer); + wolfSSL_CTX_SetIOSetPeer(ctx, test_dtls13_export_set_peer); +} +#endif + +/* Export of a DTLS 1.3 session must be refused mid-handshake, with a + * KeyUpdate in flight and with a negotiated Connection ID. */ +int test_dtls13_export_guards(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SESSION_EXPORT) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + unsigned char buf[MAX_EXPORT_BUFFER]; + unsigned char readBuf[64]; + unsigned int sz; + + 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); + test_dtls13_export_set_peer_cb(ctx_c); + test_dtls13_export_set_peer_cb(ctx_s); + + /* client: first flight sent, handshake not done */ + ExpectIntNE(wolfSSL_connect(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), + WOLFSSL_ERROR_WANT_READ); + sz = sizeof(buf); + ExpectIntEQ(wolfSSL_dtls_export(ssl_c, buf, &sz), + WC_NO_ERR_TRACE(NOT_READY_ERROR)); + sz = sizeof(buf); + ExpectIntEQ(wolfSSL_dtls_export_state_only(ssl_c, buf, &sz), + WC_NO_ERR_TRACE(NOT_READY_ERROR)); + + /* server: ClientHello consumed, handshake not done */ + ExpectIntNE(wolfSSL_accept(ssl_s), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_get_error(ssl_s, WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), + WOLFSSL_ERROR_WANT_READ); + sz = sizeof(buf); + ExpectIntEQ(wolfSSL_dtls_export(ssl_s, buf, &sz), + WC_NO_ERR_TRACE(NOT_READY_ERROR)); + sz = sizeof(buf); + ExpectIntEQ(wolfSSL_dtls_export_state_only(ssl_s, buf, &sz), + WC_NO_ERR_TRACE(NOT_READY_ERROR)); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* deliver the server's post-handshake NewSessionTickets and the client's + * ACKs for them */ + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), + WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), + WOLFSSL_ERROR_WANT_READ); + + /* KeyUpdate sent but not yet ACKed: export must refuse */ + ExpectIntEQ(wolfSSL_update_keys(ssl_c), WOLFSSL_SUCCESS); + if (ssl_c != NULL) + ExpectIntEQ(ssl_c->dtls13WaitKeyUpdateAck, 1); + sz = sizeof(buf); + ExpectIntEQ(wolfSSL_dtls_export(ssl_c, buf, &sz), + WC_NO_ERR_TRACE(NOT_READY_ERROR)); + sz = sizeof(buf); + ExpectIntEQ(wolfSSL_dtls_export_state_only(ssl_c, buf, &sz), + WC_NO_ERR_TRACE(NOT_READY_ERROR)); + + /* deliver the KeyUpdate and its ACK */ + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), + WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), + WOLFSSL_ERROR_WANT_READ); + if (ssl_c != NULL) + ExpectIntEQ(ssl_c->dtls13WaitKeyUpdateAck, 0); + + /* export must succeed again after the ACK */ + sz = sizeof(buf); + ExpectIntGT(wolfSSL_dtls_export(ssl_c, buf, &sz), 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + ssl_c = NULL; + ssl_s = NULL; + ctx_c = NULL; + ctx_s = NULL; + +#ifdef WOLFSSL_DTLS_CID + /* connection with a negotiated CID: export must refuse */ + { + unsigned char client_cid[] = { 1, 2, 3, 4 }; + unsigned char server_cid[] = { 5, 6, 7, 8 }; + + 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); + test_dtls13_export_set_peer_cb(ctx_c); + test_dtls13_export_set_peer_cb(ctx_s); + + ExpectIntEQ(wolfSSL_dtls_cid_use(ssl_c), 1); + ExpectIntEQ(wolfSSL_dtls_cid_set(ssl_c, server_cid, + sizeof(server_cid)), 1); + ExpectIntEQ(wolfSSL_dtls_cid_use(ssl_s), 1); + ExpectIntEQ(wolfSSL_dtls_cid_set(ssl_s, client_cid, + sizeof(client_cid)), 1); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + sz = sizeof(buf); + ExpectIntEQ(wolfSSL_dtls_export(ssl_s, buf, &sz), + WC_NO_ERR_TRACE(DTLS_CID_ERROR)); + sz = sizeof(buf); + ExpectIntEQ(wolfSSL_dtls_export_state_only(ssl_s, buf, &sz), + WC_NO_ERR_TRACE(DTLS_CID_ERROR)); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + } +#endif /* WOLFSSL_DTLS_CID */ +#endif + return EXPECT_RESULT(); +} + +/* Serialize an established DTLS 1.3 connection, restore it into a fresh + * WOLFSSL object and continue the connection with the original peer. */ +int test_dtls13_export_import_roundtrip(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SESSION_EXPORT) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL *ssl_imp = NULL; + struct test_memio_ctx test_ctx; + unsigned char blob[MAX_EXPORT_BUFFER]; + unsigned char blob2[MAX_EXPORT_BUFFER]; + unsigned char readBuf[64]; + unsigned int sz, sz2; + const char msgC[] = "client to server"; + const char msgS[] = "server to client"; + + 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); + test_dtls13_export_set_peer_cb(ctx_c); + test_dtls13_export_set_peer_cb(ctx_s); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* deliver NewSessionTickets and their ACKs */ + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), + WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), + WOLFSSL_ERROR_WANT_READ); + if (ssl_s != NULL) + ExpectNull(ssl_s->dtls13Rtx.rtxRecords); + + /* some traffic so sequence numbers and windows are non-trivial */ + ExpectIntEQ(wolfSSL_write(ssl_c, msgC, (int)sizeof(msgC)), + (int)sizeof(msgC)); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), + (int)sizeof(msgC)); + ExpectIntEQ(wolfSSL_write(ssl_s, msgS, (int)sizeof(msgS)), + (int)sizeof(msgS)); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), + (int)sizeof(msgS)); + + /* a NULL output buffer returns the required size */ + sz = 0; + ExpectIntEQ(wolfSSL_dtls_export(ssl_s, NULL, &sz), 0); + ExpectIntEQ(sz, MAX_EXPORT_BUFFER); + + /* export the server, import into a fresh object from the same CTX */ + sz = sizeof(blob); + ExpectIntGT(wolfSSL_dtls_export(ssl_s, blob, &sz), 0); + ExpectNotNull(ssl_imp = wolfSSL_new(ctx_s)); + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, blob, sz), (int)sz); + + /* re-export of the imported object must be byte-identical */ + sz2 = sizeof(blob2); + ExpectIntGT(wolfSSL_dtls_export(ssl_imp, blob2, &sz2), 0); + ExpectIntEQ(sz2, sz); + ExpectBufEQ(blob2, blob, sz); + + if (ssl_s != NULL && ssl_imp != NULL) { + ExpectIntEQ(w64Equal(ssl_imp->dtls13Epoch, ssl_s->dtls13Epoch), 1); + ExpectIntEQ(w64Equal(ssl_imp->dtls13PeerEpoch, ssl_s->dtls13PeerEpoch), + 1); + ExpectNotNull(ssl_imp->dtls13EncryptEpoch); + ExpectNotNull(ssl_imp->dtls13DecryptEpoch); + } + + /* move the transport to the imported object */ + wolfSSL_SetIOReadCtx(ssl_imp, &test_ctx); + wolfSSL_SetIOWriteCtx(ssl_imp, &test_ctx); + wolfSSL_free(ssl_s); + ssl_s = NULL; + + /* traffic between the live client and the imported server */ + ExpectIntEQ(wolfSSL_write(ssl_c, msgC, (int)sizeof(msgC)), + (int)sizeof(msgC)); + XMEMSET(readBuf, 0, sizeof(readBuf)); + ExpectIntEQ(wolfSSL_read(ssl_imp, readBuf, sizeof(readBuf)), + (int)sizeof(msgC)); + ExpectStrEQ(readBuf, msgC); + ExpectIntEQ(wolfSSL_write(ssl_imp, msgS, (int)sizeof(msgS)), + (int)sizeof(msgS)); + XMEMSET(readBuf, 0, sizeof(readBuf)); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), + (int)sizeof(msgS)); + ExpectStrEQ(readBuf, msgS); + + /* same round trip for the client side */ + ssl_s = ssl_imp; + ssl_imp = NULL; + sz = sizeof(blob); + ExpectIntGT(wolfSSL_dtls_export(ssl_c, blob, &sz), 0); + ExpectNotNull(ssl_imp = wolfSSL_new(ctx_c)); + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, blob, sz), (int)sz); + wolfSSL_SetIOReadCtx(ssl_imp, &test_ctx); + wolfSSL_SetIOWriteCtx(ssl_imp, &test_ctx); + wolfSSL_free(ssl_c); + ssl_c = ssl_imp; + ssl_imp = NULL; + + ExpectIntEQ(wolfSSL_write(ssl_c, msgC, (int)sizeof(msgC)), + (int)sizeof(msgC)); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), + (int)sizeof(msgC)); + ExpectIntEQ(wolfSSL_write(ssl_s, msgS, (int)sizeof(msgS)), + (int)sizeof(msgS)); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), + (int)sizeof(msgS)); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + ssl_c = NULL; + ssl_s = NULL; + ctx_c = NULL; + ctx_s = NULL; + + /* run a mutual KeyUpdate so both directions move from epoch 3 to epoch 4 + * (wolfSSL responds to a peer KeyUpdate with its own) */ + 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); + test_dtls13_export_set_peer_cb(ctx_c); + test_dtls13_export_set_peer_cb(ctx_s); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + + ExpectIntEQ(wolfSSL_update_keys(ssl_c), WOLFSSL_SUCCESS); + /* server: process KeyUpdate, ACK it, send responding KeyUpdate */ + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + /* client: process ACK, process responding KeyUpdate and ACK it */ + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + /* server: process the ACK of its responding KeyUpdate */ + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + if (ssl_c != NULL) { + ExpectIntEQ(ssl_c->dtls13WaitKeyUpdateAck, 0); + ExpectIntEQ(w64GetLow32(ssl_c->dtls13Epoch), DTLS13_EPOCH_TRAFFIC0 + 1); + ExpectIntEQ(w64GetLow32(ssl_c->dtls13PeerEpoch), + DTLS13_EPOCH_TRAFFIC0 + 1); + } + if (ssl_s != NULL) { + ExpectIntEQ(ssl_s->dtls13WaitKeyUpdateAck, 0); + ExpectIntEQ(w64GetLow32(ssl_s->dtls13Epoch), DTLS13_EPOCH_TRAFFIC0 + 1); + } + + /* export/import the server at the post-KeyUpdate epoch */ + sz = sizeof(blob); + ExpectIntGT(wolfSSL_dtls_export(ssl_s, blob, &sz), 0); + ExpectNotNull(ssl_imp = wolfSSL_new(ctx_s)); + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, blob, sz), (int)sz); + wolfSSL_SetIOReadCtx(ssl_imp, &test_ctx); + wolfSSL_SetIOWriteCtx(ssl_imp, &test_ctx); + wolfSSL_free(ssl_s); + ssl_s = ssl_imp; + ssl_imp = NULL; + + ExpectIntEQ(wolfSSL_write(ssl_c, msgC, (int)sizeof(msgC)), + (int)sizeof(msgC)); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), + (int)sizeof(msgC)); + ExpectIntEQ(wolfSSL_write(ssl_s, msgS, (int)sizeof(msgS)), + (int)sizeof(msgS)); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), + (int)sizeof(msgS)); + + /* export/import the client at the post-KeyUpdate epoch */ + sz = sizeof(blob); + ExpectIntGT(wolfSSL_dtls_export(ssl_c, blob, &sz), 0); + ExpectNotNull(ssl_imp = wolfSSL_new(ctx_c)); + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, blob, sz), (int)sz); + wolfSSL_SetIOReadCtx(ssl_imp, &test_ctx); + wolfSSL_SetIOWriteCtx(ssl_imp, &test_ctx); + wolfSSL_free(ssl_c); + ssl_c = ssl_imp; + ssl_imp = NULL; + + ExpectIntEQ(wolfSSL_write(ssl_c, msgC, (int)sizeof(msgC)), + (int)sizeof(msgC)); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), + (int)sizeof(msgC)); + ExpectIntEQ(wolfSSL_write(ssl_s, msgS, (int)sizeof(msgS)), + (int)sizeof(msgS)); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), + (int)sizeof(msgS)); + + 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_SESSION_EXPORT) +/* Walk the length-prefixed chunks of a serialized session and return the + * payload offset and length of chunk chunkIdx (0 Options, 1 Keys, + * 2 CipherSpecs, 3 PeerInfo, 4 Tls13State, 5 Dtls13State). Returns 0 on + * success. Valid for DTLS blobs (no un-prefixed AES-CBC state involved). */ +static int test_dtls13_export_find_chunk(const byte* blob, word32 sz, + int chunkIdx, word32* off, word16* len) +{ + word32 idx = 2 * WOLFSSL_EXPORT_LEN; /* header and total length */ + word16 l; + int i; + + if (sz > MAX_EXPORT_BUFFER) + return -1; + for (i = 0; i <= chunkIdx; i++) { + if (idx + WOLFSSL_EXPORT_LEN > sz) + return -1; + l = (word16)((blob[idx] << 8) | blob[idx + 1]); + idx += WOLFSSL_EXPORT_LEN; + if (i == chunkIdx) { + *off = idx; + *len = l; + return 0; + } + idx += l; + } + return -1; +} +#endif + +/* Corrupt a valid exported blob and check each mutation is rejected with the + * expected error. */ +int test_dtls13_import_negative(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SESSION_EXPORT) + /* field offsets inside the Dtls13State chunk */ + const word32 DTLS13_CHUNK_SEND_EPOCH_LO = 4; + const word32 DTLS13_CHUNK_PEER_EPOCH_LO = 12; + const word32 DTLS13_CHUNK_WINDOW_COUNT = 40; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL *ssl_imp = NULL; + struct test_memio_ctx test_ctx; + unsigned char blob[MAX_EXPORT_BUFFER]; + unsigned char bad[MAX_EXPORT_BUFFER + 64]; + unsigned char readBuf[64]; + unsigned int sz; + word32 cut; + word32 off = 0; + word16 len = 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); + test_dtls13_export_set_peer_cb(ctx_c); + test_dtls13_export_set_peer_cb(ctx_s); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + + /* export argument checks */ + sz = sizeof(blob); + ExpectIntEQ(wolfSSL_dtls_export(NULL, blob, &sz), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectIntEQ(wolfSSL_dtls_export(ssl_s, blob, NULL), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + /* too small a buffer reports the needed size */ + sz = 16; + ExpectIntEQ(wolfSSL_dtls_export(ssl_s, blob, &sz), + WC_NO_ERR_TRACE(LENGTH_ONLY_E)); + ExpectIntGT(sz, 16); + + sz = sizeof(blob); + ExpectIntGT(wolfSSL_dtls_export(ssl_s, blob, &sz), 0); + + /* import argument checks */ + ExpectIntEQ(wolfSSL_dtls_import(NULL, blob, sz), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + ExpectNotNull(ssl_imp = wolfSSL_new(ctx_s)); + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, blob, WOLFSSL_EXPORT_LEN), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* truncation at every position: with the total length field patched to + * match, some chunk parser must reject the short buffer */ + for (cut = 2 * WOLFSSL_EXPORT_LEN; cut < sz && EXPECT_SUCCESS(); cut++) { + XMEMCPY(bad, blob, cut); + bad[WOLFSSL_EXPORT_LEN] = (byte)((cut - WOLFSSL_EXPORT_LEN) >> 8); + bad[WOLFSSL_EXPORT_LEN + 1] = (byte)(cut - WOLFSSL_EXPORT_LEN); + ExpectIntLT(wolfSSL_dtls_import(ssl_imp, bad, cut), 0); + } + + /* total length larger than the buffer */ + XMEMCPY(bad, blob, sz); + bad[WOLFSSL_EXPORT_LEN] = 0xFF; + bad[WOLFSSL_EXPORT_LEN + 1] = 0xFF; + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, bad, sz), + WC_NO_ERR_TRACE(BUFFER_E)); + + /* bad protocol byte */ + XMEMCPY(bad, blob, sz); + bad[0] = 0xA8; + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, bad, sz), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* bad version nibble */ + XMEMCPY(bad, blob, sz); + bad[1] = 0xA0; + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, bad, sz), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* unknown (future) export version 8 */ + XMEMCPY(bad, blob, sz); + bad[1] = (byte)(((byte)DTLS_EXPORT_PRO & 0xF0) | 8); + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, bad, sz), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* Keys chunk length field overflow */ + off = 0; + len = 0; + ExpectIntEQ(test_dtls13_export_find_chunk(blob, sz, 1, &off, &len), 0); + if (EXPECT_SUCCESS() && off >= WOLFSSL_EXPORT_LEN && off + len <= sz) { + XMEMCPY(bad, blob, sz); + bad[off - WOLFSSL_EXPORT_LEN] = 0xFF; + bad[off - WOLFSSL_EXPORT_LEN + 1] = 0xFF; + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, bad, sz), + WC_NO_ERR_TRACE(BUFFER_E)); + } + + /* Tls13State chunk */ + off = 0; + len = 0; + ExpectIntEQ(test_dtls13_export_find_chunk(blob, sz, 4, &off, &len), 0); + if (EXPECT_SUCCESS() && off >= WOLFSSL_EXPORT_LEN && off + len <= sz) { + /* length field overflow */ + XMEMCPY(bad, blob, sz); + bad[off - WOLFSSL_EXPORT_LEN] = 0xFF; + bad[off - WOLFSSL_EXPORT_LEN + 1] = 0xFF; + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, bad, sz), + WC_NO_ERR_TRACE(BUFFER_E)); + + /* secret length not matching the negotiated cipher suite */ + XMEMCPY(bad, blob, sz); + if (ssl_s != NULL) { + ExpectIntGT(ssl_s->specs.hash_size, 0); + bad[off] = (byte)(ssl_s->specs.hash_size == + WC_SHA256_DIGEST_SIZE ? + WC_SHA384_DIGEST_SIZE : WC_SHA256_DIGEST_SIZE); + } + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, bad, sz), + WC_NO_ERR_TRACE(BAD_STATE_E)); + + /* secret length above the largest supported secret */ + XMEMCPY(bad, blob, sz); + bad[off] = 0xFF; + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, bad, sz), + WC_NO_ERR_TRACE(BUFFER_E)); + } + + /* Dtls13State chunk */ + off = 0; + len = 0; + ExpectIntEQ(test_dtls13_export_find_chunk(blob, sz, 5, &off, &len), 0); + ExpectIntEQ(len, WOLFSSL_EXPORT_DTLS13_SZ); + if (EXPECT_SUCCESS() && off >= WOLFSSL_EXPORT_LEN && off + len <= sz) { + /* send epoch below the first application traffic epoch */ + XMEMCPY(bad, blob, sz); + bad[off + DTLS13_CHUNK_SEND_EPOCH_LO + 3] = DTLS13_EPOCH_HANDSHAKE; + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, bad, sz), + WC_NO_ERR_TRACE(BAD_STATE_E)); + + /* peer epoch below the first application traffic epoch */ + XMEMCPY(bad, blob, sz); + bad[off + DTLS13_CHUNK_PEER_EPOCH_LO + 3] = DTLS13_EPOCH_HANDSHAKE; + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, bad, sz), + WC_NO_ERR_TRACE(BAD_STATE_E)); + + /* empty replay window */ + XMEMCPY(bad, blob, sz); + bad[off + DTLS13_CHUNK_WINDOW_COUNT] = 0; + bad[off + DTLS13_CHUNK_WINDOW_COUNT + 1] = 0; + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, bad, sz), + WC_NO_ERR_TRACE(BAD_STATE_E)); + + /* huge replay window word count */ + XMEMCPY(bad, blob, sz); + bad[off + DTLS13_CHUNK_WINDOW_COUNT] = 0x7F; + bad[off + DTLS13_CHUNK_WINDOW_COUNT + 1] = 0xFF; + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, bad, sz), + WC_NO_ERR_TRACE(BUFFER_E)); + + /* a send epoch one above the peer epoch is a valid state and must + * import */ + XMEMCPY(bad, blob, sz); + bad[off + DTLS13_CHUNK_SEND_EPOCH_LO + 3] = + DTLS13_EPOCH_TRAFFIC0 + 1; + wolfSSL_free(ssl_imp); + ssl_imp = NULL; + ExpectNotNull(ssl_imp = wolfSSL_new(ctx_s)); + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, bad, sz), (int)sz); + } + + /* trailing garbage after the serialized session is ignored */ + XMEMCPY(bad, blob, sz); + XMEMSET(bad + sz, 0xAA, 4); + wolfSSL_free(ssl_imp); + ssl_imp = NULL; + ExpectNotNull(ssl_imp = wolfSSL_new(ctx_s)); + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, bad, sz + 4), (int)sz); + + /* window words above ours are skipped (peer built with a larger + * WOLFSSL_DTLS_WINDOW_WORDS) */ + off = 0; + len = 0; + ExpectIntEQ(test_dtls13_export_find_chunk(blob, sz, 5, &off, &len), 0); + if (EXPECT_SUCCESS() && off >= WOLFSSL_EXPORT_LEN && off + len == sz) { + XMEMCPY(bad, blob, off + len); + XMEMSET(bad + off + len, 0xEE, 4); /* one extra window word */ + bad[off + DTLS13_CHUNK_WINDOW_COUNT + 1] = + (byte)(WOLFSSL_DTLS_WINDOW_WORDS + 1); + /* patch the chunk length and total length for the added word */ + bad[off - WOLFSSL_EXPORT_LEN] = (byte)((len + 4) >> 8); + bad[off - WOLFSSL_EXPORT_LEN + 1] = (byte)(len + 4); + bad[WOLFSSL_EXPORT_LEN] = + (byte)((sz + 4 - WOLFSSL_EXPORT_LEN) >> 8); + bad[WOLFSSL_EXPORT_LEN + 1] = (byte)(sz + 4 - WOLFSSL_EXPORT_LEN); + wolfSSL_free(ssl_imp); + ssl_imp = NULL; + ExpectNotNull(ssl_imp = wolfSSL_new(ctx_s)); + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, bad, sz + 4), (int)(sz + 4)); + } + + /* a DTLS 1.3 blob is not importable on a stream TLS 1.3 object */ + { + WOLFSSL_CTX* ctx_tls = NULL; + WOLFSSL* ssl_tls = NULL; + + ExpectNotNull(ctx_tls = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + ExpectNotNull(ssl_tls = wolfSSL_new(ctx_tls)); + ExpectIntEQ(wolfSSL_tls_import(ssl_tls, blob, sz), + WC_NO_ERR_TRACE(VERSION_ERROR)); + wolfSSL_free(ssl_tls); + wolfSSL_CTX_free(ctx_tls); + } + + /* a DTLS 1.3 blob is not importable on a DTLS 1.2 object */ + { + WOLFSSL_CTX* ctx_12 = NULL; + WOLFSSL* ssl_12 = NULL; + + ExpectNotNull(ctx_12 = wolfSSL_CTX_new(wolfDTLSv1_2_server_method())); + ExpectNotNull(ssl_12 = wolfSSL_new(ctx_12)); + ExpectIntEQ(wolfSSL_dtls_import(ssl_12, blob, sz), + WC_NO_ERR_TRACE(VERSION_ERROR)); + wolfSSL_free(ssl_12); + wolfSSL_CTX_free(ctx_12); + } + + /* Unknown cipher suite bytes in the Options chunk are tolerated: the + * suite lookup falls back to a placeholder name and the cipher + * parameters used come from the CipherSpecs chunk. The suite bytes sit + * 17 bytes before the end of the Options chunk (followed by the two + * state machine bytes, minDowngrade, connect/accept/async state, the + * four Encrypt-Then-MAC bytes, dtlsStateful and the two version + * bytes). */ + off = 0; + len = 0; + ExpectIntEQ(test_dtls13_export_find_chunk(blob, sz, 0, &off, &len), 0); + if (EXPECT_SUCCESS() && off + len <= sz && len >= 17) { + XMEMCPY(bad, blob, sz); + bad[off + len - 17] = 0xFF; + bad[off + len - 16] = 0xFF; + wolfSSL_free(ssl_imp); + ssl_imp = NULL; + ExpectNotNull(ssl_imp = wolfSSL_new(ctx_s)); + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, bad, sz), (int)sz); + } + + /* a DTLS session claiming a stream cipher type is rejected; the cipher + * type byte follows the four 16-bit size fields and the bulk cipher + * algorithm byte in the CipherSpecs chunk */ + off = 0; + len = 0; + ExpectIntEQ(test_dtls13_export_find_chunk(blob, sz, 2, &off, &len), 0); + ExpectIntEQ(len, WOLFSSL_EXPORT_SPC_SZ); + if (EXPECT_SUCCESS() && off + len <= sz) { + XMEMCPY(bad, blob, sz); + bad[off + 9] = stream; + wolfSSL_free(ssl_imp); + ssl_imp = NULL; + ExpectNotNull(ssl_imp = wolfSSL_new(ctx_s)); + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, bad, sz), + WC_NO_ERR_TRACE(SANITY_CIPHER_E)); + } + + /* double import of the same blob is idempotent */ + wolfSSL_free(ssl_imp); + ssl_imp = NULL; + ExpectNotNull(ssl_imp = wolfSSL_new(ctx_s)); + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, blob, sz), (int)sz); + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, blob, sz), (int)sz); + + /* the side of the connection comes from the blob, not from the CTX the + * fresh object was created on */ + wolfSSL_free(ssl_imp); + ssl_imp = NULL; + ExpectNotNull(ssl_imp = wolfSSL_new(ctx_c)); + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, blob, sz), (int)sz); + ExpectIntEQ(wolfSSL_GetSide(ssl_imp), WOLFSSL_SERVER_END); + + wolfSSL_free(ssl_imp); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* KeyUpdates keep working on an imported DTLS 1.3 connection, initiated from + * either end, repeatedly. */ +int test_dtls13_export_import_keyupdate(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SESSION_EXPORT) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL *ssl_imp = NULL; + struct test_memio_ctx test_ctx; + unsigned char blob[MAX_EXPORT_BUFFER]; + unsigned char readBuf[64]; + unsigned int sz; + const char msgC[] = "client data"; + const char msgS[] = "server data"; + int i; + + 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); + test_dtls13_export_set_peer_cb(ctx_c); + test_dtls13_export_set_peer_cb(ctx_s); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + + sz = sizeof(blob); + ExpectIntGT(wolfSSL_dtls_export(ssl_s, blob, &sz), 0); + ExpectNotNull(ssl_imp = wolfSSL_new(ctx_s)); + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, blob, sz), (int)sz); + wolfSSL_SetIOReadCtx(ssl_imp, &test_ctx); + wolfSSL_SetIOWriteCtx(ssl_imp, &test_ctx); + wolfSSL_free(ssl_s); + ssl_s = ssl_imp; + ssl_imp = NULL; + + /* three KeyUpdates initiated by the imported server */ + for (i = 0; i < 3 && EXPECT_SUCCESS(); i++) { + ExpectIntEQ(wolfSSL_update_keys(ssl_s), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_write(ssl_c, msgC, (int)sizeof(msgC)), + (int)sizeof(msgC)); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), + (int)sizeof(msgC)); + ExpectIntEQ(wolfSSL_write(ssl_s, msgS, (int)sizeof(msgS)), + (int)sizeof(msgS)); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), + (int)sizeof(msgS)); + } + + /* two KeyUpdates initiated by the live client */ + for (i = 0; i < 2 && EXPECT_SUCCESS(); i++) { + ExpectIntEQ(wolfSSL_update_keys(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_write(ssl_c, msgC, (int)sizeof(msgC)), + (int)sizeof(msgC)); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), + (int)sizeof(msgC)); + ExpectIntEQ(wolfSSL_write(ssl_s, msgS, (int)sizeof(msgS)), + (int)sizeof(msgS)); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), + (int)sizeof(msgS)); + } + + /* check both directions advanced five epochs */ + if (ssl_c != NULL) { + ExpectIntEQ(w64GetLow32(ssl_c->dtls13Epoch), DTLS13_EPOCH_TRAFFIC0 + 5); + ExpectIntEQ(w64GetLow32(ssl_c->dtls13PeerEpoch), + DTLS13_EPOCH_TRAFFIC0 + 5); + } + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* The anti-replay window survives export/import: a record the original + * connection already received, and a record received after import, are both + * dropped when replayed into the imported connection. */ +int test_dtls13_export_import_replay_window(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SESSION_EXPORT) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL *ssl_imp = NULL; + struct test_memio_ctx test_ctx; + unsigned char blob[MAX_EXPORT_BUFFER]; + unsigned char readBuf[64]; + char oldRecord[256]; + char newRecord[256]; + int oldRecordSz = (int)sizeof(oldRecord); + int newRecordSz = (int)sizeof(newRecord); + unsigned int sz; + const char msg1[] = "before export"; + const char msg2[] = "after import"; + const char msg3[] = "still alive"; + + 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); + test_dtls13_export_set_peer_cb(ctx_c); + test_dtls13_export_set_peer_cb(ctx_s); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + + /* a record received by the original server before the export */ + ExpectIntEQ(wolfSSL_write(ssl_c, msg1, (int)sizeof(msg1)), + (int)sizeof(msg1)); + ExpectIntEQ(test_memio_copy_message(&test_ctx, 0, oldRecord, &oldRecordSz, + 0), 0); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), + (int)sizeof(msg1)); + + sz = sizeof(blob); + ExpectIntGT(wolfSSL_dtls_export(ssl_s, blob, &sz), 0); + ExpectNotNull(ssl_imp = wolfSSL_new(ctx_s)); + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, blob, sz), (int)sz); + wolfSSL_SetIOReadCtx(ssl_imp, &test_ctx); + wolfSSL_SetIOWriteCtx(ssl_imp, &test_ctx); + wolfSSL_free(ssl_s); + ssl_s = ssl_imp; + ssl_imp = NULL; + + /* replay of the pre-export record must be dropped */ + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, oldRecord, + oldRecordSz), 0); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), + WOLFSSL_ERROR_WANT_READ); + + /* a record received once after import is dropped when replayed */ + ExpectIntEQ(wolfSSL_write(ssl_c, msg2, (int)sizeof(msg2)), + (int)sizeof(msg2)); + ExpectIntEQ(test_memio_copy_message(&test_ctx, 0, newRecord, &newRecordSz, + 0), 0); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), + (int)sizeof(msg2)); + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, newRecord, + newRecordSz), 0); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), + WOLFSSL_ERROR_WANT_READ); + + /* normal traffic must still work after the replays */ + ExpectIntEQ(wolfSSL_write(ssl_c, msg3, (int)sizeof(msg3)), + (int)sizeof(msg3)); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), + (int)sizeof(msg3)); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* After a post-import KeyUpdate a replayed record of the retired epoch is + * rejected by the imported connection. */ +int test_dtls13_export_import_old_epoch_record(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SESSION_EXPORT) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL *ssl_imp = NULL; + struct test_memio_ctx test_ctx; + unsigned char blob[MAX_EXPORT_BUFFER]; + unsigned char readBuf[64]; + char oldRecord[256]; + int oldRecordSz = (int)sizeof(oldRecord); + unsigned int sz; + const char msgA[] = "epoch three data"; + const char msgB[] = "epoch four data"; + + 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); + test_dtls13_export_set_peer_cb(ctx_c); + test_dtls13_export_set_peer_cb(ctx_s); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + + /* an epoch 3 record, delivered and remembered for replay */ + ExpectIntEQ(wolfSSL_write(ssl_c, msgA, (int)sizeof(msgA)), + (int)sizeof(msgA)); + ExpectIntEQ(test_memio_copy_message(&test_ctx, 0, oldRecord, &oldRecordSz, + 0), 0); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), + (int)sizeof(msgA)); + + sz = sizeof(blob); + ExpectIntGT(wolfSSL_dtls_export(ssl_s, blob, &sz), 0); + ExpectNotNull(ssl_imp = wolfSSL_new(ctx_s)); + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, blob, sz), (int)sz); + wolfSSL_SetIOReadCtx(ssl_imp, &test_ctx); + wolfSSL_SetIOWriteCtx(ssl_imp, &test_ctx); + wolfSSL_free(ssl_s); + ssl_s = ssl_imp; + ssl_imp = NULL; + + /* move both directions to epoch 4 */ + ExpectIntEQ(wolfSSL_update_keys(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + + /* the retired epoch 3 record must be rejected */ + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, oldRecord, + oldRecordSz), 0); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), + WOLFSSL_ERROR_WANT_READ); + + /* epoch 4 traffic must still work */ + ExpectIntEQ(wolfSSL_write(ssl_c, msgB, (int)sizeof(msgB)), + (int)sizeof(msgB)); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), + (int)sizeof(msgB)); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* A KeyUpdate in flight toward the exported connection must complete against + * the imported object, whether the record is delivered after the import or + * lost and retransmitted. */ +int test_dtls13_export_import_peer_keyupdate_inflight(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SESSION_EXPORT) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL *ssl_imp = NULL; + struct test_memio_ctx test_ctx; + unsigned char blob[MAX_EXPORT_BUFFER]; + unsigned char readBuf[64]; + unsigned int sz; + const char msgC[] = "client data"; + const char msgS[] = "server data"; + int i; + + /* i == 0: the in-flight KeyUpdate is delivered to the imported object. + * i == 1: the in-flight KeyUpdate is lost with the original object and + * the client retransmits it to the imported one. */ + for (i = 0; i < 2 && EXPECT_SUCCESS(); i++) { + 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); + test_dtls13_export_set_peer_cb(ctx_c); + test_dtls13_export_set_peer_cb(ctx_s); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + + /* export the server before the client sends a KeyUpdate toward it */ + sz = sizeof(blob); + ExpectIntGT(wolfSSL_dtls_export(ssl_s, blob, &sz), 0); + ExpectIntEQ(wolfSSL_update_keys(ssl_c), WOLFSSL_SUCCESS); + + ExpectNotNull(ssl_imp = wolfSSL_new(ctx_s)); + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, blob, sz), (int)sz); + wolfSSL_SetIOReadCtx(ssl_imp, &test_ctx); + wolfSSL_SetIOWriteCtx(ssl_imp, &test_ctx); + wolfSSL_free(ssl_s); + ssl_s = ssl_imp; + ssl_imp = NULL; + + if (i == 1) { + /* the KeyUpdate datagram is lost with the original process; the + * client retransmits it on timeout. A quick timeout only flushes + * ACKs, so a second timeout is needed to retransmit. */ + test_memio_clear_buffer(&test_ctx, 0); + if (wolfSSL_dtls13_use_quick_timeout(ssl_c)) + ExpectIntEQ(wolfSSL_dtls_got_timeout(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_dtls_got_timeout(ssl_c), WOLFSSL_SUCCESS); + ExpectIntGT(test_ctx.s_len, 0); + } + + /* drive the KeyUpdate exchange against the imported server */ + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + if (ssl_c != NULL) { + ExpectIntEQ(ssl_c->dtls13WaitKeyUpdateAck, 0); + ExpectIntEQ(w64GetLow32(ssl_c->dtls13Epoch), + DTLS13_EPOCH_TRAFFIC0 + 1); + ExpectIntEQ(w64GetLow32(ssl_c->dtls13PeerEpoch), + DTLS13_EPOCH_TRAFFIC0 + 1); + } + + ExpectIntEQ(wolfSSL_write(ssl_c, msgC, (int)sizeof(msgC)), + (int)sizeof(msgC)); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), + (int)sizeof(msgC)); + ExpectIntEQ(wolfSSL_write(ssl_s, msgS, (int)sizeof(msgS)), + (int)sizeof(msgS)); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), + (int)sizeof(msgS)); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + ssl_c = NULL; + ssl_s = NULL; + ctx_c = NULL; + ctx_s = NULL; + } +#endif + return EXPECT_RESULT(); +} + +/* A server exported right after the handshake still has the NewSessionTicket + * records waiting to be acknowledged. Those are not carried in the export, so + * the export must succeed and the imported connection must tolerate an ACK + * for a record it no longer tracks. */ +int test_dtls13_export_import_unacked_ticket(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SESSION_EXPORT) && \ + defined(HAVE_SESSION_TICKET) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL *ssl_imp = NULL; + struct test_memio_ctx test_ctx; + unsigned char blob[MAX_EXPORT_BUFFER]; + unsigned char readBuf[64]; + unsigned int sz; + const char msgC[] = "client data"; + const char msgS[] = "server data"; + int i; + + /* i == 0: the client receives the tickets and ACKs them to the imported + * server, which no longer tracks those records. + * i == 1: the tickets never reach the client at all. */ + for (i = 0; i < 2 && EXPECT_SUCCESS(); i++) { + 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); + test_dtls13_export_set_peer_cb(ctx_c); + test_dtls13_export_set_peer_cb(ctx_s); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* the handshake left the session tickets unacknowledged */ + if (ssl_s != NULL) + ExpectNotNull(ssl_s->dtls13Rtx.rtxRecords); + + sz = sizeof(blob); + ExpectIntGT(wolfSSL_dtls_export(ssl_s, blob, &sz), 0); + ExpectNotNull(ssl_imp = wolfSSL_new(ctx_s)); + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, blob, sz), (int)sz); + /* the un-acknowledged records must not survive the import */ + if (ssl_imp != NULL) + ExpectNull(ssl_imp->dtls13Rtx.rtxRecords); + wolfSSL_SetIOReadCtx(ssl_imp, &test_ctx); + wolfSSL_SetIOWriteCtx(ssl_imp, &test_ctx); + wolfSSL_free(ssl_s); + ssl_s = ssl_imp; + ssl_imp = NULL; + + if (i == 0) { + /* the client reads the tickets and ACKs them */ + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, + WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), WOLFSSL_ERROR_WANT_READ); + /* the imported server must ignore the ACK for the dropped records */ + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, + WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), WOLFSSL_ERROR_WANT_READ); + } + else { + /* the tickets are lost in transit */ + test_memio_clear_buffer(&test_ctx, 1); + } + + /* traffic must work in both variants */ + ExpectIntEQ(wolfSSL_write(ssl_c, msgC, (int)sizeof(msgC)), + (int)sizeof(msgC)); + XMEMSET(readBuf, 0, sizeof(readBuf)); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), + (int)sizeof(msgC)); + ExpectStrEQ(readBuf, msgC); + ExpectIntEQ(wolfSSL_write(ssl_s, msgS, (int)sizeof(msgS)), + (int)sizeof(msgS)); + XMEMSET(readBuf, 0, sizeof(readBuf)); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), + (int)sizeof(msgS)); + ExpectStrEQ(readBuf, msgS); + + /* KeyUpdates must still work after the dropped records */ + ExpectIntEQ(wolfSSL_update_keys(ssl_s), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_write(ssl_c, msgC, (int)sizeof(msgC)), + (int)sizeof(msgC)); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), + (int)sizeof(msgC)); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + ssl_c = NULL; + ssl_s = NULL; + ctx_c = NULL; + ctx_s = NULL; + } +#endif + return EXPECT_RESULT(); +} + +/* State-only export refreshes the volatile record layer state of an already + * imported DTLS 1.3 session: the sequence numbers and the replay window of + * the live connection are applied on top of a previously imported session + * without carrying any key material. */ +int test_dtls13_export_state_only_refresh(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SESSION_EXPORT) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL *ssl_imp = NULL; + struct test_memio_ctx test_ctx; + unsigned char blob[MAX_EXPORT_BUFFER]; + unsigned char state[MAX_EXPORT_STATE_BUFFER]; + unsigned char readBuf[64]; + char staleRecord[256]; + int staleRecordSz = (int)sizeof(staleRecord); + unsigned int sz; + unsigned int stateSz; + word32 i; + const char msg[] = "traffic"; + Dtls13Epoch* eLive = NULL; + Dtls13Epoch* eImp = 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); + test_dtls13_export_set_peer_cb(ctx_c); + test_dtls13_export_set_peer_cb(ctx_s); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + + /* the standby copy of the session */ + sz = sizeof(blob); + ExpectIntGT(wolfSSL_dtls_export(ssl_s, blob, &sz), 0); + ExpectNotNull(ssl_imp = wolfSSL_new(ctx_s)); + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, blob, sz), (int)sz); + + /* a NULL output buffer returns the required state blob size */ + stateSz = 0; + ExpectIntEQ(wolfSSL_dtls_export_state_only(ssl_s, NULL, &stateSz), 0); + ExpectIntEQ(stateSz, MAX_EXPORT_STATE_BUFFER); + + /* run traffic to advance sequence numbers and the replay window past + * what the standby copy holds */ + for (i = 0; i < 4 && EXPECT_SUCCESS(); i++) { + ExpectIntEQ(wolfSSL_write(ssl_c, msg, (int)sizeof(msg)), + (int)sizeof(msg)); + if (i == 0) { + ExpectIntEQ(test_memio_copy_message(&test_ctx, 0, staleRecord, + &staleRecordSz, 0), 0); + } + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), + (int)sizeof(msg)); + ExpectIntEQ(wolfSSL_write(ssl_s, msg, (int)sizeof(msg)), + (int)sizeof(msg)); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), + (int)sizeof(msg)); + } + + /* the standby copy is behind on both directions */ + if (ssl_s != NULL && ssl_imp != NULL) { + eLive = Dtls13GetEpoch(ssl_s, ssl_s->dtls13Epoch); + eImp = Dtls13GetEpoch(ssl_imp, ssl_imp->dtls13Epoch); + ExpectNotNull(eLive); + ExpectNotNull(eImp); + if (eLive != NULL && eImp != NULL) { + ExpectIntEQ(w64Equal(eImp->nextSeqNumber, eLive->nextSeqNumber), 0); + ExpectIntEQ(w64Equal(eImp->nextPeerSeqNumber, + eLive->nextPeerSeqNumber), 0); + } + } + + /* refresh the standby copy from a state-only blob */ + stateSz = sizeof(state); + ExpectIntGT(wolfSSL_dtls_export_state_only(ssl_s, state, &stateSz), 0); + ExpectIntEQ(state[0], DTLS_EXPORT_STATE_PRO); + ExpectIntEQ(state[1] & 0x0F, WOLFSSL_EXPORT_VERSION); + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, state, stateSz), (int)stateSz); + + /* the standby copy must match the live connection */ + if (ssl_s != NULL && ssl_imp != NULL) { + eLive = Dtls13GetEpoch(ssl_s, ssl_s->dtls13Epoch); + eImp = Dtls13GetEpoch(ssl_imp, ssl_imp->dtls13Epoch); + ExpectNotNull(eLive); + ExpectNotNull(eImp); + if (eLive != NULL && eImp != NULL) { + ExpectIntEQ(w64Equal(eImp->nextSeqNumber, eLive->nextSeqNumber), 1); + ExpectIntEQ(w64Equal(eImp->nextPeerSeqNumber, + eLive->nextPeerSeqNumber), 1); + ExpectIntEQ(XMEMCMP(eImp->window, eLive->window, + sizeof(eImp->window)), 0); + } + } + + /* the refreshed copy must reject an already seen record */ + wolfSSL_SetIOReadCtx(ssl_imp, &test_ctx); + wolfSSL_SetIOWriteCtx(ssl_imp, &test_ctx); + wolfSSL_free(ssl_s); + ssl_s = ssl_imp; + ssl_imp = NULL; + + ExpectIntEQ(test_memio_inject_message(&test_ctx, 0, staleRecord, + staleRecordSz), 0); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_s, WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), + WOLFSSL_ERROR_WANT_READ); + + /* normal traffic must still work */ + ExpectIntEQ(wolfSSL_write(ssl_c, msg, (int)sizeof(msg)), + (int)sizeof(msg)); + XMEMSET(readBuf, 0, sizeof(readBuf)); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), + (int)sizeof(msg)); + ExpectStrEQ(readBuf, msg); + ExpectIntEQ(wolfSSL_write(ssl_s, msg, (int)sizeof(msg)), + (int)sizeof(msg)); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), + (int)sizeof(msg)); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* Negative cases for the DTLS 1.3 state-only blob. */ +int test_dtls13_import_state_negative(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SESSION_EXPORT) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL *ssl_imp = NULL; + struct test_memio_ctx test_ctx; + unsigned char blob[MAX_EXPORT_BUFFER]; + unsigned char state[MAX_EXPORT_STATE_BUFFER]; + unsigned char bad[MAX_EXPORT_STATE_BUFFER]; + unsigned char readBuf[64]; + unsigned int sz; + unsigned int stateSz; + word32 cut; + + 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); + test_dtls13_export_set_peer_cb(ctx_c); + test_dtls13_export_set_peer_cb(ctx_s); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + + sz = sizeof(blob); + ExpectIntGT(wolfSSL_dtls_export(ssl_s, blob, &sz), 0); + ExpectNotNull(ssl_imp = wolfSSL_new(ctx_s)); + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, blob, sz), (int)sz); + + stateSz = sizeof(state); + ExpectIntGT(wolfSSL_dtls_export_state_only(ssl_s, state, &stateSz), 0); + + /* truncation at every position */ + for (cut = 2 * WOLFSSL_EXPORT_LEN; cut < stateSz && EXPECT_SUCCESS(); + cut++) { + XMEMCPY(bad, state, cut); + bad[WOLFSSL_EXPORT_LEN] = (byte)((cut - WOLFSSL_EXPORT_LEN) >> 8); + bad[WOLFSSL_EXPORT_LEN + 1] = (byte)(cut - WOLFSSL_EXPORT_LEN); + ExpectIntLT(wolfSSL_dtls_import(ssl_imp, bad, cut), 0); + } + + /* wrong protocol byte for a state blob */ + XMEMCPY(bad, state, stateSz); + bad[0] = 0xA8; + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, bad, stateSz), + WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + /* a pre-v7 state blob must be refused on a DTLS 1.3 object */ + XMEMCPY(bad, state, stateSz); + bad[1] = (byte)(((byte)DTLS_EXPORT_STATE_PRO & 0xF0) | + ((byte)WOLFSSL_EXPORT_VERSION_6 & 0x0F)); + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, bad, stateSz), + WC_NO_ERR_TRACE(VERSION_ERROR)); + + /* a state blob whose epochs the target does not hold is refused: a state + * refresh can not rebuild key material */ + ExpectIntEQ(wolfSSL_update_keys(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + stateSz = sizeof(state); + ExpectIntGT(wolfSSL_dtls_export_state_only(ssl_s, state, &stateSz), 0); + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, state, stateSz), + WC_NO_ERR_TRACE(BAD_STATE_E)); + + wolfSSL_free(ssl_imp); + 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_SESSION_EXPORT) +/* Collects the session the library hands to the export callback. */ +struct test_dtls13_export_cb_ctx { + unsigned char blob[MAX_EXPORT_BUFFER]; + unsigned int sz; + int calls; +}; + +static struct test_dtls13_export_cb_ctx test_dtls13_export_cb_state; + +static int test_dtls13_export_cb(WOLFSSL* ssl, unsigned char* buf, + unsigned int sz, void* userCtx) +{ + (void)ssl; + (void)userCtx; + if (sz > sizeof(test_dtls13_export_cb_state.blob)) + return -1; + XMEMCPY(test_dtls13_export_cb_state.blob, buf, sz); + test_dtls13_export_cb_state.sz = sz; + test_dtls13_export_cb_state.calls++; + return WOLFSSL_SUCCESS; +} +#endif + +/* A DTLS 1.3 server with an export callback registered hands the serialized + * session to it when the handshake completes, and that session is usable. */ +int test_dtls13_export_callback(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SESSION_EXPORT) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL *ssl_imp = NULL; + struct test_memio_ctx test_ctx; + unsigned char readBuf[64]; + const char msgC[] = "client data"; + const char msgS[] = "server data"; + + XMEMSET(&test_dtls13_export_cb_state, 0, + sizeof(test_dtls13_export_cb_state)); + 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); + test_dtls13_export_set_peer_cb(ctx_c); + test_dtls13_export_set_peer_cb(ctx_s); + ExpectIntEQ(wolfSSL_CTX_dtls_set_export(ctx_s, test_dtls13_export_cb), + WOLFSSL_SUCCESS); + /* the callback is inherited by objects created after it is set */ + wolfSSL_free(ssl_s); + ssl_s = NULL; + ExpectNotNull(ssl_s = wolfSSL_new(ctx_s)); + wolfSSL_SetIOReadCtx(ssl_s, &test_ctx); + wolfSSL_SetIOWriteCtx(ssl_s, &test_ctx); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* the callback must have been invoked once with the serialized session */ + ExpectIntEQ(test_dtls13_export_cb_state.calls, 1); + ExpectIntGT(test_dtls13_export_cb_state.sz, 0); + ExpectIntEQ(test_dtls13_export_cb_state.blob[0], DTLS_EXPORT_PRO); + ExpectIntEQ(test_dtls13_export_cb_state.blob[1] & 0x0F, + WOLFSSL_EXPORT_VERSION); + + /* the callback's session must restore a working connection */ + ExpectNotNull(ssl_imp = wolfSSL_new(ctx_s)); + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, test_dtls13_export_cb_state.blob, + test_dtls13_export_cb_state.sz), + (int)test_dtls13_export_cb_state.sz); + wolfSSL_SetIOReadCtx(ssl_imp, &test_ctx); + wolfSSL_SetIOWriteCtx(ssl_imp, &test_ctx); + wolfSSL_free(ssl_s); + ssl_s = ssl_imp; + ssl_imp = NULL; + + /* the client still has the session tickets queued; drop them since the + * exported session predates their acknowledgment */ + test_memio_clear_buffer(&test_ctx, 1); + + ExpectIntEQ(wolfSSL_write(ssl_c, msgC, (int)sizeof(msgC)), + (int)sizeof(msgC)); + XMEMSET(readBuf, 0, sizeof(readBuf)); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), + (int)sizeof(msgC)); + ExpectStrEQ(readBuf, msgC); + ExpectIntEQ(wolfSSL_write(ssl_s, msgS, (int)sizeof(msgS)), + (int)sizeof(msgS)); + XMEMSET(readBuf, 0, sizeof(readBuf)); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), + (int)sizeof(msgS)); + ExpectStrEQ(readBuf, msgS); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* Export/import round trip across the DTLS 1.3 cipher suites: the secret and + * key sizes differ (SHA-256 vs SHA-384) and ChaCha20-Poly1305 uses a + * different record number protection cipher than the AES suites. */ +int test_dtls13_export_import_ciphersuites(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_DTLS13) && defined(WOLFSSL_SESSION_EXPORT) + const char* suites[] = { +#ifndef NO_SHA256 +#ifdef WOLFSSL_AES_128 +#ifdef HAVE_AESGCM + "TLS13-AES128-GCM-SHA256", +#endif +#ifdef HAVE_AESCCM + "TLS13-AES128-CCM-SHA256", +#endif +#endif +#if defined(HAVE_CHACHA) && defined(HAVE_POLY1305) + "TLS13-CHACHA20-POLY1305-SHA256", +#endif +#endif +#if defined(WOLFSSL_SHA384) && defined(WOLFSSL_AES_256) && defined(HAVE_AESGCM) + "TLS13-AES256-GCM-SHA384", +#endif + }; + size_t i; + + for (i = 0; i < XELEM_CNT(suites) && EXPECT_SUCCESS(); i++) { + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL *ssl_imp = NULL; + struct test_memio_ctx test_ctx; + unsigned char blob[MAX_EXPORT_BUFFER]; + unsigned char readBuf[64]; + unsigned int sz; + const char msgC[] = "client data"; + const char msgS[] = "server data"; + + 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); + test_dtls13_export_set_peer_cb(ctx_c); + test_dtls13_export_set_peer_cb(ctx_s); + ExpectIntEQ(wolfSSL_set_cipher_list(ssl_c, suites[i]), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_cipher_list(ssl_s, suites[i]), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + + sz = sizeof(blob); + ExpectIntGT(wolfSSL_dtls_export(ssl_s, blob, &sz), 0); + ExpectNotNull(ssl_imp = wolfSSL_new(ctx_s)); + ExpectIntEQ(wolfSSL_dtls_import(ssl_imp, blob, sz), (int)sz); + wolfSSL_SetIOReadCtx(ssl_imp, &test_ctx); + wolfSSL_SetIOWriteCtx(ssl_imp, &test_ctx); + wolfSSL_free(ssl_s); + ssl_s = ssl_imp; + ssl_imp = NULL; + + /* traffic and a KeyUpdate on the restored connection */ + ExpectIntEQ(wolfSSL_write(ssl_c, msgC, (int)sizeof(msgC)), + (int)sizeof(msgC)); + XMEMSET(readBuf, 0, sizeof(readBuf)); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), + (int)sizeof(msgC)); + ExpectStrEQ(readBuf, msgC); + ExpectIntEQ(wolfSSL_write(ssl_s, msgS, (int)sizeof(msgS)), + (int)sizeof(msgS)); + XMEMSET(readBuf, 0, sizeof(readBuf)); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), + (int)sizeof(msgS)); + ExpectStrEQ(readBuf, msgS); + + ExpectIntEQ(wolfSSL_update_keys(ssl_s), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_write(ssl_c, msgC, (int)sizeof(msgC)), + (int)sizeof(msgC)); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), + (int)sizeof(msgC)); + + 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_dtls13.h b/tests/api/test_dtls13.h index 2cf31bec27..7523e4ab18 100644 --- a/tests/api/test_dtls13.h +++ b/tests/api/test_dtls13.h @@ -57,7 +57,21 @@ int test_dtls13_5_9_0_compat_bad_echo(void); int test_dtls13_5_9_0_compat_empty_echo(void); int test_dtls13_reuse_after_clear(void); -#define TEST_DTLS13_DECLS \ +/* Session export/import (WOLFSSL_SESSION_EXPORT) for DTLS 1.3. */ +int test_dtls13_export_guards(void); +int test_dtls13_export_import_roundtrip(void); +int test_dtls13_import_negative(void); +int test_dtls13_export_import_keyupdate(void); +int test_dtls13_export_import_replay_window(void); +int test_dtls13_export_import_old_epoch_record(void); +int test_dtls13_export_import_peer_keyupdate_inflight(void); +int test_dtls13_export_import_unacked_ticket(void); +int test_dtls13_export_state_only_refresh(void); +int test_dtls13_import_state_negative(void); +int test_dtls13_export_callback(void); +int test_dtls13_export_import_ciphersuites(void); + +#define TEST_DTLS13_DECLS \ TEST_DECL_GROUP("dtls13", test_dtls13_bad_epoch_ch), \ TEST_DECL_GROUP("dtls13", test_wolfSSL_dtls13_null_cipher), \ TEST_DECL_GROUP("dtls13", test_dtls13_frag_ch_pq), \ @@ -82,6 +96,19 @@ int test_dtls13_reuse_after_clear(void); TEST_DECL_GROUP("dtls13", test_dtls13_5_9_0_compat), \ TEST_DECL_GROUP("dtls13", test_dtls13_5_9_0_compat_bad_echo), \ TEST_DECL_GROUP("dtls13", test_dtls13_5_9_0_compat_empty_echo), \ - TEST_DECL_GROUP("dtls13", test_dtls13_reuse_after_clear) + TEST_DECL_GROUP("dtls13", test_dtls13_reuse_after_clear), \ + TEST_DECL_GROUP("dtls13", test_dtls13_export_guards), \ + TEST_DECL_GROUP("dtls13", test_dtls13_export_import_roundtrip), \ + TEST_DECL_GROUP("dtls13", test_dtls13_import_negative), \ + TEST_DECL_GROUP("dtls13", test_dtls13_export_import_keyupdate), \ + TEST_DECL_GROUP("dtls13", test_dtls13_export_import_replay_window), \ + TEST_DECL_GROUP("dtls13", test_dtls13_export_import_old_epoch_record), \ + TEST_DECL_GROUP("dtls13", \ + test_dtls13_export_import_peer_keyupdate_inflight), \ + TEST_DECL_GROUP("dtls13", test_dtls13_export_import_unacked_ticket), \ + TEST_DECL_GROUP("dtls13", test_dtls13_export_state_only_refresh), \ + TEST_DECL_GROUP("dtls13", test_dtls13_import_state_negative), \ + TEST_DECL_GROUP("dtls13", test_dtls13_export_callback), \ + TEST_DECL_GROUP("dtls13", test_dtls13_export_import_ciphersuites) #endif /* TESTS_API_DTLS13_H */ diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index c5fe3189c3..c138d2954f 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -8865,3 +8865,195 @@ int test_tls13_pha_status_request(void) #endif return EXPECT_RESULT(); } + +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_SESSION_EXPORT) +/* Stream TLS 1.3 session serialized with export version 6, captured before + * the (D)TLS 1.3 state chunks were added to the format. Generated with a + * memio TLS 1.3 handshake followed by wolfSSL_tls_export() of the server end + * (dummy peer callbacks: ip[0]=-1, ipSz=1, port=1, fam=2). */ +static byte canned_server_tls13_session_v6[] = { + 0xA7, 0xA6, 0x01, 0x5A, 0x00, 0x42, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x80, 0x02, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x1C, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x02, 0x0A, 0x0F, 0x10, + 0x01, 0x03, 0x00, 0x0F, 0x05, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x03, 0x04, 0x00, 0xF7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x11, 0x01, 0x01, + 0x00, 0x30, 0x70, 0x35, 0x1E, 0xCD, 0x42, 0x72, 0xD5, 0xBC, + 0x76, 0x96, 0xF0, 0xCF, 0x9F, 0xD4, 0x75, 0x16, 0x59, 0xFB, + 0xDF, 0xD5, 0xC1, 0xFE, 0x8A, 0xA5, 0x3D, 0x0F, 0x61, 0x24, + 0x7C, 0x3C, 0xC7, 0xCF, 0xA8, 0x46, 0xFC, 0x70, 0xF2, 0x6C, + 0x41, 0xDC, 0x2D, 0x0B, 0xAD, 0x2C, 0xBC, 0x23, 0xC3, 0x92, + 0x32, 0x9D, 0x87, 0x1D, 0x96, 0x4E, 0x49, 0x63, 0x33, 0x14, + 0xC8, 0xB8, 0x70, 0x43, 0xEC, 0xAE, 0x74, 0x17, 0x86, 0xBB, + 0xC9, 0xC6, 0x7E, 0x99, 0x4A, 0x54, 0xF5, 0xF2, 0xCB, 0xF0, + 0xBF, 0x9B, 0xE2, 0x0B, 0x59, 0xAE, 0xB8, 0x87, 0xFD, 0xE8, + 0x85, 0xF2, 0x7D, 0xBA, 0xE1, 0x2F, 0xEC, 0x8A, 0x20, 0xA4, + 0x6C, 0x8E, 0x6F, 0x97, 0x55, 0xB5, 0x46, 0x6C, 0x79, 0x56, + 0x04, 0x39, 0x8E, 0x2C, 0xD3, 0x01, 0x2B, 0x08, 0x06, 0xE0, + 0xC5, 0x7B, 0x7F, 0x93, 0xDC, 0x7F, 0x70, 0x2A, 0x4C, 0x12, + 0xF4, 0x39, 0x43, 0x8C, 0x24, 0x11, 0x4E, 0x6B, 0xDB, 0xD4, + 0xB6, 0xFD, 0x6C, 0x69, 0x62, 0x40, 0xE2, 0x16, 0x45, 0x27, + 0x9B, 0xCA, 0xC2, 0xA0, 0xA3, 0xE1, 0xFB, 0x0A, 0x14, 0xB0, + 0xCC, 0xD7, 0xFF, 0x0C, 0x4C, 0x37, 0x2A, 0x1D, 0x86, 0xDA, + 0x32, 0xF1, 0x24, 0x2D, 0x82, 0xEC, 0x6D, 0x70, 0x39, 0x36, + 0xDE, 0x41, 0x18, 0x47, 0x7D, 0xEF, 0x0B, 0x32, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x6D, 0x70, 0x39, + 0x36, 0xDE, 0x41, 0x18, 0x47, 0x7D, 0xEF, 0x0B, 0x32, 0x4C, + 0x37, 0x2A, 0x1D, 0x86, 0xDA, 0x32, 0xF1, 0x24, 0x2D, 0x82, + 0xEC, 0x00, 0x10, 0x00, 0x20, 0x00, 0x0C, 0x00, 0x10, 0x00, + 0x10, 0x07, 0x02, 0x05, 0x09, 0x12, 0x30, 0x28, 0x00, 0x00, + 0x07, 0x00, 0x02, 0x00, 0x01, 0xFF, 0x00, 0x01, +}; + +static int test_tls13_export_get_peer(WOLFSSL* ssl, char* ip, int* ipSz, + unsigned short* port, int* fam) +{ + (void)ssl; + ip[0] = -1; + *ipSz = 1; + *port = 1; + *fam = 2; + return 1; +} + +static int test_tls13_import_set_peer(WOLFSSL* ssl, char* ip, int ipSz, + unsigned short port, int fam) +{ + (void)ssl; + (void)ip; + (void)ipSz; + (void)port; + (void)fam; + return 1; +} +#endif + +/* Stream TLS 1.3 blobs serialized with export version 6 must still import: + * unlike DTLS 1.3, the stream TLS 1.3 record state fits in the pre-v7 + * format (KeyUpdate after import is only guaranteed for v7+ blobs). */ +int test_tls13_import_v6_canned(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_SESSION_EXPORT) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + wolfSSL_CTX_SetIOSetPeer(ctx, test_tls13_import_set_peer); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + ExpectIntEQ(wolfSSL_tls_import(ssl, canned_server_tls13_session_v6, + sizeof(canned_server_tls13_session_v6)), + (int)sizeof(canned_server_tls13_session_v6)); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* A stream TLS 1.3 connection restored from an exported session must still be + * able to run KeyUpdates. That needs the application traffic secrets, which + * only travel in export version 7 and later. */ +int test_tls13_export_import_keyupdate(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TLS13) && \ + defined(WOLFSSL_SESSION_EXPORT) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL *ssl_imp = NULL; + struct test_memio_ctx test_ctx; + unsigned char blob[MAX_EXPORT_BUFFER]; + unsigned char readBuf[64]; + unsigned int sz; + const char msgC[] = "client to server"; + const char msgS[] = "server to client"; + int i; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + wolfSSL_CTX_SetIOGetPeer(ctx_s, test_tls13_export_get_peer); + wolfSSL_CTX_SetIOSetPeer(ctx_s, test_tls13_import_set_peer); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* let the client consume the post-handshake session tickets */ + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, WC_NO_ERR_TRACE(WOLFSSL_FATAL_ERROR)), + WOLFSSL_ERROR_WANT_READ); + + sz = sizeof(blob); + ExpectIntGT(wolfSSL_tls_export(ssl_s, blob, &sz), 0); + /* the blob carries the current export version */ + ExpectIntEQ(blob[1] & 0x0F, WOLFSSL_EXPORT_VERSION); + + ExpectNotNull(ssl_imp = wolfSSL_new(ctx_s)); + ExpectIntEQ(wolfSSL_tls_import(ssl_imp, blob, sz), (int)sz); + /* the imported secrets must match the exported connection */ + if (ssl_s != NULL && ssl_imp != NULL) { + ExpectBufEQ(ssl_imp->clientSecret, ssl_s->clientSecret, + ssl_s->specs.hash_size); + ExpectBufEQ(ssl_imp->serverSecret, ssl_s->serverSecret, + ssl_s->specs.hash_size); + } + + /* the imported object takes over the transport */ + wolfSSL_SetIOReadCtx(ssl_imp, &test_ctx); + wolfSSL_SetIOWriteCtx(ssl_imp, &test_ctx); + wolfSSL_free(ssl_s); + ssl_s = ssl_imp; + ssl_imp = NULL; + + /* traffic works before any key update */ + ExpectIntEQ(wolfSSL_write(ssl_c, msgC, (int)sizeof(msgC)), + (int)sizeof(msgC)); + XMEMSET(readBuf, 0, sizeof(readBuf)); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), + (int)sizeof(msgC)); + ExpectStrEQ(readBuf, msgC); + + /* KeyUpdates initiated by the imported server */ + for (i = 0; i < 3 && EXPECT_SUCCESS(); i++) { + ExpectIntEQ(wolfSSL_update_keys(ssl_s), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_write(ssl_s, msgS, (int)sizeof(msgS)), + (int)sizeof(msgS)); + XMEMSET(readBuf, 0, sizeof(readBuf)); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), + (int)sizeof(msgS)); + ExpectStrEQ(readBuf, msgS); + /* the client sends its responding KeyUpdate with its next write */ + ExpectIntEQ(wolfSSL_write(ssl_c, msgC, (int)sizeof(msgC)), + (int)sizeof(msgC)); + XMEMSET(readBuf, 0, sizeof(readBuf)); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), + (int)sizeof(msgC)); + ExpectStrEQ(readBuf, msgC); + } + + /* KeyUpdates initiated by the live peer toward the imported object */ + for (i = 0; i < 2 && EXPECT_SUCCESS(); i++) { + ExpectIntEQ(wolfSSL_update_keys(ssl_c), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_write(ssl_c, msgC, (int)sizeof(msgC)), + (int)sizeof(msgC)); + XMEMSET(readBuf, 0, sizeof(readBuf)); + ExpectIntEQ(wolfSSL_read(ssl_s, readBuf, sizeof(readBuf)), + (int)sizeof(msgC)); + ExpectStrEQ(readBuf, msgC); + ExpectIntEQ(wolfSSL_write(ssl_s, msgS, (int)sizeof(msgS)), + (int)sizeof(msgS)); + XMEMSET(readBuf, 0, sizeof(readBuf)); + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, sizeof(readBuf)), + (int)sizeof(msgS)); + ExpectStrEQ(readBuf, msgS); + } + + 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_tls13.h b/tests/api/test_tls13.h index 30ac62952f..196c4e4502 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -106,6 +106,9 @@ int test_tls13_KeyUpdate_sender_limit(void); int test_tls13_pqc_hybrid_async_server(void); int test_tls13_pha_status_request(void); +int test_tls13_import_v6_canned(void); +int test_tls13_export_import_keyupdate(void); + #define TEST_TLS13_DECLS \ TEST_DECL_GROUP("tls13", test_tls13_apis), \ TEST_DECL_GROUP("tls13", test_tls13_cipher_suites), \ @@ -187,6 +190,8 @@ int test_tls13_pha_status_request(void); TEST_DECL_GROUP("tls13", test_tls13_AEAD_limit_KU_aes128_ccm_8_sha256), \ TEST_DECL_GROUP("tls13", test_tls13_KeyUpdate_sender_limit), \ TEST_DECL_GROUP("tls13", test_tls13_pqc_hybrid_async_server), \ - TEST_DECL_GROUP("tls13", test_tls13_pha_status_request) + TEST_DECL_GROUP("tls13", test_tls13_pha_status_request), \ + TEST_DECL_GROUP("tls13", test_tls13_import_v6_canned), \ + TEST_DECL_GROUP("tls13", test_tls13_export_import_keyupdate) #endif /* WOLFCRYPT_TEST_TLS13_H */ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 49e7cafe81..0aae321ea8 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -1656,9 +1656,19 @@ enum Misc { WOLFSSL_EXPORT_SPC_SZ = 16, /* number of bytes used from CipherSpecs */ #endif WOLFSSL_EXPORT_LEN = 2, /* 2 bytes for length and protocol */ - WOLFSSL_EXPORT_VERSION = 6, /* wolfSSL version for serialized session */ + WOLFSSL_EXPORT_TLS13_SZ = 11 + (2 * SECRET_LEN), + /* max size of (D)TLS 1.3 secrets chunk: + * length byte, two traffic secrets, two + * KeyUpdate flags and a 64-bit counter */ + WOLFSSL_EXPORT_DTLS13_SZ = 42 + DTLS_SEQ_SZ, + /* size of DTLS 1.3 state chunk: five + * 64-bit values (send/peer epoch, next + * send/peer sequence number, drop count), + * window word count and window */ + WOLFSSL_EXPORT_VERSION = 7, /* wolfSSL version for serialized session */ /* older export versions supported */ + WOLFSSL_EXPORT_VERSION_6 = 6, /* version before (D)TLS 1.3 state chunks */ WOLFSSL_EXPORT_VERSION_5 = 5, /* version before DTLS Encrypt-Then-MAC */ WOLFSSL_EXPORT_VERSION_4 = 4, /* 5.6.4 release and before */ WOLFSSL_EXPORT_VERSION_3 = 3, /* wolfSSL version before TLS 1.3 addition */ @@ -1668,8 +1678,11 @@ enum Misc { /* Additional bytes to read so that * we can work with a peer that has * a slightly different MTU than us. */ - MAX_EXPORT_BUFFER = 514, /* max size of buffer for exporting */ - MAX_EXPORT_STATE_BUFFER = (DTLS_EXPORT_MIN_KEY_SZ) + (3 * WOLFSSL_EXPORT_LEN), + MAX_EXPORT_BUFFER = 514 + (2 * WOLFSSL_EXPORT_LEN) + + WOLFSSL_EXPORT_TLS13_SZ + WOLFSSL_EXPORT_DTLS13_SZ, + /* max size of buffer for exporting */ + MAX_EXPORT_STATE_BUFFER = (DTLS_EXPORT_MIN_KEY_SZ) + + (4 * WOLFSSL_EXPORT_LEN) + WOLFSSL_EXPORT_DTLS13_SZ, /* max size of buffer for exporting state */ FINISHED_LABEL_SZ = 15, /* TLS finished label size */ TLS_FINISHED_SZ = 12, /* TLS has a shorter size */