diff --git a/src/dtls.c b/src/dtls.c index 289ba64b8..523f9b6df 100644 --- a/src/dtls.c +++ b/src/dtls.c @@ -291,24 +291,17 @@ static int TlsxFindByType(WolfSSL_ConstVector* ret, word16 extType, } #endif -#if defined(WOLFSSL_DTLS13) || defined(WOLFSSL_DTLS_NO_HVR_ON_RESUME) +#if defined(WOLFSSL_DTLS_NO_HVR_ON_RESUME) #ifdef HAVE_SESSION_TICKET static int TlsTicketIsValid(const WOLFSSL* ssl, WolfSSL_ConstVector exts, - PskInfo* pskInfo, byte isTls13) + int* resume) { WolfSSL_ConstVector tlsxSessionTicket; byte tempTicket[SESSION_TICKET_LEN]; - InternalTicket* it; - int ret; + InternalTicket* it = NULL; + int ret = 0; - (void)isTls13; - -#if defined(WOLFSSL_TLS13) && !defined(NO_PSK) - if (isTls13) - ret = TlsxFindByType(&tlsxSessionTicket, TLSX_PRE_SHARED_KEY, exts); - else -#endif - ret = TlsxFindByType(&tlsxSessionTicket, TLSX_SESSION_TICKET, exts); + ret = TlsxFindByType(&tlsxSessionTicket, TLSX_SESSION_TICKET, exts); if (ret != 0) return ret; if (tlsxSessionTicket.size == 0) @@ -316,26 +309,20 @@ static int TlsTicketIsValid(const WOLFSSL* ssl, WolfSSL_ConstVector exts, if (tlsxSessionTicket.size > SESSION_TICKET_LEN) return 0; XMEMCPY(tempTicket, tlsxSessionTicket.elements, tlsxSessionTicket.size); - ret = DoDecryptTicket((WOLFSSL*)ssl, tempTicket, - (word32)tlsxSessionTicket.size, &it); - if (ret != WOLFSSL_TICKET_RET_OK && ret != WOLFSSL_TICKET_RET_CREATE) - return 0; - /* Store info for later */ -#if defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) - pskInfo->pv = it->pv; -#endif - pskInfo->cipherSuite0 = it->suite[0]; - pskInfo->cipherSuite = it->suite[1]; - ato16(it->namedGroup, &pskInfo->namedGroup); - - ForceZero(it, sizeof(InternalTicket)); - pskInfo->isValid = 1; + ret = DoDecryptTicket(ssl, tempTicket, (word32)tlsxSessionTicket.size, &it); + if (ret == WOLFSSL_TICKET_RET_OK || ret == WOLFSSL_TICKET_RET_CREATE) { + /* This logic is only for TLS <= 1.2 tickets. Don't accept TLS 1.3. */ + if (!IsAtLeastTLSv1_3(it->pv)) + *resume = TRUE; + } + if (it != NULL) + ForceZero(it, sizeof(InternalTicket)); return 0; } #endif /* HAVE_SESSION_TICKET */ static int TlsSessionIdIsValid(const WOLFSSL* ssl, WolfSSL_ConstVector sessionID, - PskInfo* pskInfo) + int* resume) { WOLFSSL_SESSION* sess; word32 sessRow; @@ -353,17 +340,19 @@ static int TlsSessionIdIsValid(const WOLFSSL* ssl, WolfSSL_ConstVector sessionID ssl->ctx->get_sess_cb((WOLFSSL*)ssl, sessionID.elements, ID_LEN, &unused); if (sess != NULL) { - /* Store info for later */ -#if defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) - pskInfo->pv = sess->version; +#if defined(SESSION_CERTS) || (defined(WOLFSSL_TLS13) && \ + defined(HAVE_SESSION_TICKET)) + /* This logic is only for TLS <= 1.2 tickets. Don't accept + * TLS 1.3. */ + if (IsAtLeastTLSv1_3(sess->version)) + wolfSSL_FreeSession(ssl->ctx, sess); + else #endif - pskInfo->cipherSuite0 = sess->cipherSuite0; - pskInfo->cipherSuite = sess->cipherSuite; - pskInfo->namedGroup = sess->namedGroup; - - pskInfo->isValid = 1; - wolfSSL_FreeSession(ssl->ctx, sess); - return 0; + { + *resume = 1; + wolfSSL_FreeSession(ssl->ctx, sess); + return 0; + } } } if (ssl->ctx->internalCacheLookupOff) @@ -372,15 +361,15 @@ static int TlsSessionIdIsValid(const WOLFSSL* ssl, WolfSSL_ConstVector sessionID #endif ret = TlsSessionCacheGetAndLock(sessionID.elements, &sess, &sessRow, 1); if (ret == 0 && sess != NULL) { - /* Store info for later */ -#if defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) - pskInfo->pv = sess->version; +#if defined(SESSION_CERTS) || (defined(WOLFSSL_TLS13) && \ + defined(HAVE_SESSION_TICKET)) + /* This logic is only for TLS <= 1.2 tickets. Don't accept + * TLS 1.3. */ + if (!IsAtLeastTLSv1_3(sess->version)) #endif - pskInfo->cipherSuite0 = sess->cipherSuite0; - pskInfo->cipherSuite = sess->cipherSuite; - pskInfo->namedGroup = sess->namedGroup; - - pskInfo->isValid = 1; + { + *resume = 1; + } TlsSessionCacheUnlockRow(sessRow); } @@ -388,19 +377,18 @@ static int TlsSessionIdIsValid(const WOLFSSL* ssl, WolfSSL_ConstVector sessionID } static int TlsResumptionIsValid(const WOLFSSL* ssl, WolfSSL_CH* ch, - PskInfo* pskInfo, byte isTls13) + int* resume) { int ret; - (void)isTls13; #ifdef HAVE_SESSION_TICKET - ret = TlsTicketIsValid(ssl, ch->extension, pskInfo, isTls13); + ret = TlsTicketIsValid(ssl, ch->extension, resume); if (ret != 0) return ret; - if (pskInfo->isValid) + if (*resume) return 0; #endif /* HAVE_SESSION_TICKET */ - ret = TlsSessionIdIsValid(ssl, ch->sessionId, pskInfo); + ret = TlsSessionIdIsValid(ssl, ch->sessionId, resume); return ret; } #endif /* WOLFSSL_DTLS13 || WOLFSSL_DTLS_NO_HVR_ON_RESUME */ @@ -454,18 +442,16 @@ static int CopySupportedGroup(TLSX* src, TLSX** dst, void* heap) } #endif -#if defined(WOLFSSL_DTLS13) && !defined(NO_PSK) +#if defined(WOLFSSL_DTLS13) && \ + (!defined(NO_PSK) || defined(HAVE_SESSION_TICKET)) /* Very simplified version of CheckPreSharedKeys to find the current suite */ static void FindPskSuiteFromExt(const WOLFSSL* ssl, TLSX* extensions, PskInfo* pskInfo, Suites* suites) { TLSX* pskExt = TLSX_Find(extensions, TLSX_PRE_SHARED_KEY); - int found = 0; PreSharedKey* current; - byte psk_key[MAX_PSK_KEY_LEN]; - word32 psk_keySz; int i; - byte foundSuite[SUITE_LEN]; + int ret; if (pskExt == NULL) return; @@ -473,17 +459,61 @@ static void FindPskSuiteFromExt(const WOLFSSL* ssl, TLSX* extensions, for (i = 0; i < suites->suiteSz; i += 2) { for (current = (PreSharedKey*)pskExt->data; current != NULL; current = current->next) { - if (FindPskSuite(ssl, current, psk_key, &psk_keySz, - suites->suites + i, &found, foundSuite) == 0) { - if (found) { +#ifdef HAVE_SESSION_TICKET + { + /* Decode the identity. */ + switch (current->decryptRet) { + case PSK_DECRYPT_NONE: + ret = DoClientTicket_ex(ssl, current); + break; + case PSK_DECRYPT_OK: + ret = WOLFSSL_TICKET_RET_OK; + break; + case PSK_DECRYPT_CREATE: + ret = WOLFSSL_TICKET_RET_CREATE; + break; + case PSK_DECRYPT_FAIL: + ret = WOLFSSL_TICKET_RET_REJECT; + break; + } + if (ret == WOLFSSL_TICKET_RET_OK) { + if (DoClientTicketCheck(current, ssl->timeout, + suites->suites + i) != 0) { + continue; + } + + pskInfo->cipherSuite0 = current->it->suite[0]; + pskInfo->cipherSuite = current->it->suite[1]; + pskInfo->isValid = 1; + goto cleanup; + } + } +#endif +#ifndef NO_PSK + { + int found = 0; + byte psk_key[MAX_PSK_KEY_LEN]; + word32 psk_keySz; + byte foundSuite[SUITE_LEN]; + ret = FindPskSuite(ssl, current, psk_key, &psk_keySz, + suites->suites + i, &found, foundSuite); + /* Clear the key just in case */ + ForceZero(psk_key, sizeof(psk_key)); + if (ret == 0 && found) { pskInfo->cipherSuite0 = foundSuite[0]; pskInfo->cipherSuite = foundSuite[1]; pskInfo->isValid = 1; - return; + goto cleanup; } } +#endif } } + +cleanup: +#ifdef HAVE_SESSION_TICKET + CleanupClientTickets((PreSharedKey*)pskExt->data); +#endif } #endif @@ -493,8 +523,12 @@ static void FindPskSuiteFromExt(const WOLFSSL* ssl, TLSX* extensions, #error "WOLFSSL_SEND_HRR_COOKIE has to be defined to use DTLS 1.3 server" #endif -static int SendStatelessReplyDtls13(const WOLFSSL* ssl, WolfSSL_CH* ch, - PskInfo* pskInfo) +#ifdef WOLFSSL_PSK_ONE_ID +#error WOLFSSL_PSK_ONE_ID is not compatible with stateless DTLS 1.3 server. \ + wolfSSL needs to be able to make multiple calls for the same PSK. +#endif + +static int SendStatelessReplyDtls13(const WOLFSSL* ssl, WolfSSL_CH* ch) { int ret = -1; TLSX* parsedExts = NULL; @@ -511,8 +545,10 @@ static int SendStatelessReplyDtls13(const WOLFSSL* ssl, WolfSSL_CH* ch, CipherSpecs specs; byte cookieHash[WC_MAX_DIGEST_SIZE]; int cookieHashSz; - - (void)pskInfo; +#if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) + PskInfo pskInfo; + XMEMSET(&pskInfo, 0, sizeof(pskInfo)); +#endif XMEMSET(&cs, 0, sizeof(cs)); @@ -601,18 +637,13 @@ static int SendStatelessReplyDtls13(const WOLFSSL* ssl, WolfSSL_CH* ch, * and if they don't match we will error out there anyway. */ byte modes; -#ifndef NO_PSK - /* When we didn't find a valid ticket ask the user for the - * ciphersuite matching this identity */ - if (!pskInfo->isValid) { - if (TLSX_PreSharedKey_Parse_ClientHello(&parsedExts, - tlsx.elements, tlsx.size, ssl->heap) == 0) - FindPskSuiteFromExt(ssl, parsedExts, pskInfo, &suites); - /* Revert to full handshake if PSK parsing failed */ - } -#endif + /* Ask the user for the ciphersuite matching this identity */ + if (TLSX_PreSharedKey_Parse_ClientHello(&parsedExts, + tlsx.elements, tlsx.size, ssl->heap) == 0) + FindPskSuiteFromExt(ssl, parsedExts, &pskInfo, &suites); + /* Revert to full handshake if PSK parsing failed */ - if (pskInfo->isValid) { + if (pskInfo.isValid) { ret = TlsxFindByType(&tlsx, TLSX_PSK_KEY_EXCHANGE_MODES, ch->extension); if (ret != 0) @@ -638,9 +669,9 @@ static int SendStatelessReplyDtls13(const WOLFSSL* ssl, WolfSSL_CH* ch, #endif #if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) - if (usePSK && pskInfo->isValid) { - cs.cipherSuite0 = pskInfo->cipherSuite0; - cs.cipherSuite = pskInfo->cipherSuite; + if (usePSK && pskInfo.isValid) { + cs.cipherSuite0 = pskInfo.cipherSuite0; + cs.cipherSuite = pskInfo.cipherSuite; if (haveSG && !haveKS) { WOLFSSL_MSG("Client didn't send KeyShare or Supported Groups."); @@ -737,15 +768,13 @@ dtls13_cleanup: } #endif -static int SendStatelessReply(const WOLFSSL* ssl, WolfSSL_CH* ch, byte isTls13, - PskInfo* pskInfo) +static int SendStatelessReply(const WOLFSSL* ssl, WolfSSL_CH* ch, byte isTls13) { int ret; (void)isTls13; - (void)pskInfo; #ifdef WOLFSSL_DTLS13 if (isTls13) { - ret = SendStatelessReplyDtls13(ssl, ch, pskInfo); + ret = SendStatelessReplyDtls13(ssl, ch); } else #endif @@ -789,9 +818,7 @@ int DoClientHelloStateless(WOLFSSL* ssl, const byte* input, int ret; WolfSSL_CH ch; byte isTls13 = 0; - PskInfo pskInfo; - XMEMSET(&pskInfo, 0, sizeof(pskInfo)); XMEMSET(&ch, 0, sizeof(ch)); ssl->options.dtlsStateful = 0; @@ -816,33 +843,21 @@ int DoClientHelloStateless(WOLFSSL* ssl, const byte* input, if (ret != 0) return ret; -#if defined(WOLFSSL_DTLS13) || defined(WOLFSSL_DTLS_NO_HVR_ON_RESUME) - ret = TlsResumptionIsValid(ssl, &ch, &pskInfo, isTls13); - if (ret != 0) - return ret; -#endif #ifdef WOLFSSL_DTLS_NO_HVR_ON_RESUME - if (pskInfo.isValid) { - ssl->options.dtlsStateful = 1; - return 0; - } -#endif - -#if defined(WOLFSSL_DTLS13) && defined(HAVE_SESSION_TICKET) - if (pskInfo.isValid) { - if (IsAtLeastTLSv1_3(pskInfo.pv)) { - if (!isTls13) - return VERSION_ERROR; - } - else { - if (isTls13) - return VERSION_ERROR; + if (!isTls13) { + int resume = FALSE; + ret = TlsResumptionIsValid(ssl, &ch, &resume); + if (ret != 0) + return ret; + if (resume) { + ssl->options.dtlsStateful = 1; + return 0; } } #endif if (ch.cookie.size == 0 && ch.cookieExt.size == 0) { - ret = SendStatelessReply((WOLFSSL*)ssl, &ch, isTls13, &pskInfo); + ret = SendStatelessReply((WOLFSSL*)ssl, &ch, isTls13); } else { byte cookieGood; @@ -857,7 +872,7 @@ int DoClientHelloStateless(WOLFSSL* ssl, const byte* input, ret = INVALID_PARAMETER; else #endif - ret = SendStatelessReply((WOLFSSL*)ssl, &ch, isTls13, &pskInfo); + ret = SendStatelessReply((WOLFSSL*)ssl, &ch, isTls13); } else ssl->options.dtlsStateful = 1; diff --git a/src/internal.c b/src/internal.c index 9d3d55fe0..67ca31535 100644 --- a/src/internal.c +++ b/src/internal.c @@ -34547,7 +34547,7 @@ static int DoSessionTicket(WOLFSSL* ssl, const byte* input, word32* inOutIdx, } - int DoDecryptTicket(WOLFSSL* ssl, const byte* input, word32 len, + int DoDecryptTicket(const WOLFSSL* ssl, const byte* input, word32 len, InternalTicket **it) { ExternalTicket* et; @@ -34589,7 +34589,9 @@ static int DoSessionTicket(WOLFSSL* ssl, const byte* input, word32* inOutIdx, ret = WOLFSSL_TICKET_RET_REJECT; } else { - ret = ssl->ctx->ticketEncCb(ssl, et->key_name, et->iv, + /* Callback uses ssl without const but for DTLS, it really shouldn't + * modify its state. */ + ret = ssl->ctx->ticketEncCb((WOLFSSL*)ssl, et->key_name, et->iv, et->enc_ticket + inLen, 0, et->enc_ticket, inLen, &outLen, ssl->ctx->ticketEncCtx); @@ -34614,142 +34616,265 @@ static int DoSessionTicket(WOLFSSL* ssl, const byte* input, word32* inOutIdx, return ret; } - /* Parse ticket sent by client, returns callback return value */ - int DoClientTicket(WOLFSSL* ssl, const byte* input, word32 len) + static int DoClientTicketCheckVersion(const WOLFSSL* ssl, + InternalTicket* it) { - InternalTicket* it; - int ret; + if (ssl->version.minor < it->pv.minor) { + WOLFSSL_MSG("Ticket has greater version"); + return VERSION_ERROR; + } + else if (ssl->version.minor > it->pv.minor) { + if (IsAtLeastTLSv1_3(it->pv) != IsAtLeastTLSv1_3(ssl->version)) { + WOLFSSL_MSG("Tickets cannot be shared between " + "TLS 1.3 and TLS 1.2 and lower"); + return VERSION_ERROR; + } + + if (!ssl->options.downgrade) { + WOLFSSL_MSG("Ticket has lesser version"); + return VERSION_ERROR; + } + + WOLFSSL_MSG("Downgrading protocol due to ticket"); + + if (it->pv.minor < ssl->options.minDowngrade) { + WOLFSSL_MSG("Ticket has lesser version than allowed"); + return VERSION_ERROR; + } + } + /* Check resumption master secret. */ + if (IsAtLeastTLSv1_3(it->pv) && + it->ticketNonceLen > MAX_TICKET_NONCE_STATIC_SZ) { + WOLFSSL_MSG("Unsupported ticketNonce len in ticket"); + return BAD_TICKET_ENCRYPT; + } + return 0; + } + + /* Return 0 when check successful. <0 on failure. */ + int DoClientTicketCheck(const PreSharedKey* psk, sword64 timeout, + const byte* suite) + { + word32 ticketAdd; +#ifdef WOLFSSL_32BIT_MILLI_TIME + word32 now; + sword64 diff; + word32 ticketSeen; /* Time ticket seen (ms) */ + + ato32(psk->it->timestamp, &ticketSeen); + + now = TimeNowInMilliseconds(); + if (now == 0) + return GETTIME_ERROR; + /* Difference between now and time ticket constructed + * (from decrypted ticket). */ + diff = now; + diff -= ticketSeen; + if (diff > timeout * 1000 || + diff > (sword64)TLS13_MAX_TICKET_AGE * 1000) + return -1; +#else + sword64 diff; + sword64 ticketSeen; /* Time ticket seen (ms) */ + word32 seenHi, seenLo; + + ato32(psk->it->timestamp , &seenHi); + ato32(psk->it->timestamp + OPAQUE32_LEN, &seenLo); + ticketSeen = ((sword64)seenHi << 32) + seenLo; + + diff = TimeNowInMilliseconds(); + if (diff == 0) + return GETTIME_ERROR; + /* Difference between now and time ticket constructed + * (from decrypted ticket). */ + diff -= ticketSeen; + if (diff > timeout * 1000 || + diff > (sword64)TLS13_MAX_TICKET_AGE * 1000) + return -1; +#endif + ato32(psk->it->ageAdd, &ticketAdd); + /* Subtract client's ticket age and unobfuscate. */ + diff -= psk->ticketAge; + diff += ticketAdd; + /* Check session and ticket age timeout. + * Allow +/- 1000 milliseconds on ticket age. + */ + if (diff < -1000 || diff - MAX_TICKET_AGE_DIFF * 1000 > 1000) + return -1; + +#ifndef WOLFSSL_PSK_ONE_ID + /* Check whether resumption is possible based on suites in SSL and + * ciphersuite in ticket. + */ + if (XMEMCMP(suite, psk->it->suite, SUITE_LEN) != 0) + return -1; +#else + if (!FindSuiteSSL(ssl, psk->it->suite)) + return -1; +#endif + return 0; + } + + void DoClientTicketFinalize(WOLFSSL* ssl, InternalTicket* it) + { +#ifdef WOLFSSL_TICKET_HAVE_ID + ssl->session->haveAltSessionID = 1; + XMEMCPY(ssl->session->altSessionID, it->id, ID_LEN); + if (wolfSSL_GetSession(ssl, NULL, 1) != NULL) { + WOLFSSL_MSG("Found session matching the session id" + " found in the ticket"); + } + else { + WOLFSSL_MSG("Can't find session matching the session id" + " found in the ticket"); + } +#endif + + if (!IsAtLeastTLSv1_3(ssl->version)) { + XMEMCPY(ssl->arrays->masterSecret, it->msecret, SECRET_LEN); + /* Copy the haveExtendedMasterSecret property from the ticket to + * the saved session, so the property may be checked later. */ + ssl->session->haveEMS = it->haveEMS; + ato32((const byte*)&it->timestamp, &ssl->session->bornOn); +#ifndef NO_RESUME_SUITE_CHECK + ssl->session->cipherSuite0 = it->suite[0]; + ssl->session->cipherSuite = it->suite[1]; +#endif + } + else { +#ifdef WOLFSSL_TLS13 + /* This should have been already checked in + * DoClientTicketCheckVersion */ + if (it->ticketNonceLen > MAX_TICKET_NONCE_STATIC_SZ) { + WOLFSSL_MSG("Unsupported ticketNonce len in ticket"); + return; + } + /* Restore information to renegotiate. */ +#ifdef WOLFSSL_32BIT_MILLI_TIME + ato32(it->timestamp, &ssl->session->ticketSeen); +#else + word32 seenHi, seenLo; + + ato32(it->timestamp , &seenHi); + ato32(it->timestamp + OPAQUE32_LEN, &seenLo); + ssl->session->ticketSeen = ((sword64)seenHi << 32) + seenLo; +#endif + ato32(it->ageAdd, &ssl->session->ticketAdd); + ssl->session->cipherSuite0 = it->suite[0]; + ssl->session->cipherSuite = it->suite[1]; +#ifdef WOLFSSL_EARLY_DATA + ato32(it->maxEarlyDataSz, &ssl->session->maxEarlyDataSz); +#endif + /* Resumption master secret. */ + XMEMCPY(ssl->session->masterSecret, it->msecret, SECRET_LEN); +#if defined(WOLFSSL_TICKET_NONCE_MALLOC) && \ + (!defined(HAVE_FIPS) || (defined(FIPS_VERSION_GE) && FIPS_VERSION_GE(5,3))) + if (ssl->session->ticketNonce.data + != ssl->session->ticketNonce.dataStatic) { + XFREE(ssl->session->ticketNonce.data, ssl->heap, + DYNAMIC_TYPE_SESSION_TICK); + ssl->session->ticketNonce.data = + ssl->session->ticketNonce.dataStatic; + } +#endif /* defined(WOLFSSL_TICKET_NONCE_MALLOC) && FIPS_VERSION_GE(5,3) */ + XMEMCPY(ssl->session->ticketNonce.data, it->ticketNonce, + it->ticketNonceLen); + ssl->session->ticketNonce.len = it->ticketNonceLen; + ato16(it->namedGroup, &ssl->session->namedGroup); +#endif + } + ssl->version.minor = it->pv.minor; + } + + /* Parse ticket sent by client, returns callback return value. Doesn't + * modify ssl and stores the InternalTicket inside psk */ + int DoClientTicket_ex(const WOLFSSL* ssl, PreSharedKey* psk) + { + int decryptRet; + int ret; WOLFSSL_START(WC_FUNC_TICKET_DO); - WOLFSSL_ENTER("DoClientTicket"); + WOLFSSL_ENTER("DoClientTicket_ex"); - ret = DoDecryptTicket(ssl, input, len, &it); - if (ret != WOLFSSL_TICKET_RET_OK && ret != WOLFSSL_TICKET_RET_CREATE) { - WOLFSSL_LEAVE("DoClientTicket", ret); - return ret; + decryptRet = DoDecryptTicket(ssl, psk->identity, psk->identityLen, + &psk->it); + switch (decryptRet) { + case WOLFSSL_TICKET_RET_OK: + psk->decryptRet = PSK_DECRYPT_OK; + break; + case WOLFSSL_TICKET_RET_CREATE: + psk->decryptRet = PSK_DECRYPT_CREATE; + break; + default: + psk->decryptRet = PSK_DECRYPT_FAIL; + return decryptRet; } #ifdef WOLFSSL_CHECK_MEM_ZERO /* Internal ticket successfully decrypted. */ wc_MemZero_Add("Do Client Ticket internal", it, sizeof(InternalTicket)); #endif - /* get master secret */ - if (ret == WOLFSSL_TICKET_RET_OK || ret == WOLFSSL_TICKET_RET_CREATE) { - if (ssl->version.minor < it->pv.minor) { - WOLFSSL_MSG("Ticket has greater version"); - ret = VERSION_ERROR; - goto error; - } - else if (ssl->version.minor > it->pv.minor) { - if (IsAtLeastTLSv1_3(it->pv) != IsAtLeastTLSv1_3(ssl->version)) { - WOLFSSL_MSG("Tickets cannot be shared between " - "TLS 1.3 and TLS 1.2 and lower"); - ret = VERSION_ERROR; - goto error; - } + ret = DoClientTicketCheckVersion(ssl, psk->it); + if (ret != 0) { + psk->decryptRet = PSK_DECRYPT_FAIL; + return ret; + } + return decryptRet; + } - if (!ssl->options.downgrade) { - WOLFSSL_MSG("Ticket has lesser version"); - ret = VERSION_ERROR; - goto error; - } + /* Parse ticket sent by client, returns callback return value */ + int DoClientTicket(WOLFSSL* ssl, const byte* input, word32 len) + { + int decryptRet; + int ret; + InternalTicket* it - WOLFSSL_MSG("Downgrading protocol due to ticket"); + WOLFSSL_START(WC_FUNC_TICKET_DO); + WOLFSSL_ENTER("DoClientTicket"); - if (it->pv.minor < ssl->options.minDowngrade) { - WOLFSSL_MSG("Ticket has lesser version than allowed"); - ret = VERSION_ERROR; - goto error; - } - ssl->version.minor = it->pv.minor; - } + decryptRet = DoDecryptTicket(ssl, input, len, &it); + if (decryptRet != WOLFSSL_TICKET_RET_OK && + decryptRet != WOLFSSL_TICKET_RET_CREATE) + return decryptRet; + #ifdef WOLFSSL_CHECK_MEM_ZERO + /* Internal ticket successfully decrypted. */ + wc_MemZero_Add("Do Client Ticket internal", it, sizeof(InternalTicket)); + #endif -#ifdef WOLFSSL_TICKET_HAVE_ID - { - ssl->session->haveAltSessionID = 1; - XMEMCPY(ssl->session->altSessionID, it->id, ID_LEN); - if (wolfSSL_GetSession(ssl, NULL, 1) != NULL) { - WOLFSSL_MSG("Found session matching the session id" - " found in the ticket"); - } - else { - WOLFSSL_MSG("Can't find session matching the session id" - " found in the ticket"); - } - } + ret = DoClientTicketCheckVersion(ssl, it); + if (ret != 0) { + ForceZero(it, sizeof(*it)); +#ifdef WOLFSSL_CHECK_MEM_ZERO + wc_MemZero_Check(it, sizeof(InternalTicket)); +#endif + return ret; + } + + DoClientTicketFinalize(ssl, it); + + ForceZero(it, sizeof(*it)); +#ifdef WOLFSSL_CHECK_MEM_ZERO + wc_MemZero_Check(it, sizeof(InternalTicket)); #endif - if (!IsAtLeastTLSv1_3(ssl->version)) { - XMEMCPY(ssl->arrays->masterSecret, it->msecret, SECRET_LEN); - /* Copy the haveExtendedMasterSecret property from the ticket to - * the saved session, so the property may be checked later. */ - ssl->session->haveEMS = it->haveEMS; - ato32((const byte*)&it->timestamp, &ssl->session->bornOn); - #ifndef NO_RESUME_SUITE_CHECK - ssl->session->cipherSuite0 = it->suite[0]; - ssl->session->cipherSuite = it->suite[1]; - #endif - } - else { -#ifdef WOLFSSL_TLS13 - /* Restore information to renegotiate. */ - #ifdef WOLFSSL_32BIT_MILLI_TIME - ato32(it->timestamp, &ssl->session->ticketSeen); - #else - word32 seenHi, seenLo; + return decryptRet; + } - ato32(it->timestamp , &seenHi); - ato32(it->timestamp + OPAQUE32_LEN, &seenLo); - ssl->session->ticketSeen = ((sword64)seenHi << 32) + seenLo; - #endif - ato32(it->ageAdd, &ssl->session->ticketAdd); - ssl->session->cipherSuite0 = it->suite[0]; - ssl->session->cipherSuite = it->suite[1]; - #ifdef WOLFSSL_EARLY_DATA - ato32(it->maxEarlyDataSz, &ssl->session->maxEarlyDataSz); - #endif - /* Resumption master secret. */ - XMEMCPY(ssl->session->masterSecret, it->msecret, SECRET_LEN); - if (it->ticketNonceLen > MAX_TICKET_NONCE_STATIC_SZ) { - WOLFSSL_MSG("Unsupported ticketNonce len in ticket"); - WOLFSSL_LEAVE("DoClientTicket", BAD_TICKET_ENCRYPT); - return BAD_TICKET_ENCRYPT; - } -#if defined(WOLFSSL_TICKET_NONCE_MALLOC) && \ - (!defined(HAVE_FIPS) || (defined(FIPS_VERSION_GE) && FIPS_VERSION_GE(5,3))) - if (ssl->session->ticketNonce.data - != ssl->session->ticketNonce.dataStatic) { - XFREE(ssl->session->ticketNonce.data, ssl->heap, - DYNAMIC_TYPE_SESSION_TICK); - ssl->session->ticketNonce.data = - ssl->session->ticketNonce.dataStatic; - } -#endif /* defined(WOLFSSL_TICKET_NONCE_MALLOC) && FIPS_VERSION_GE(5,3) */ - XMEMCPY(ssl->session->ticketNonce.data, it->ticketNonce, - it->ticketNonceLen); - ssl->session->ticketNonce.len = it->ticketNonceLen; - ato16(it->namedGroup, &ssl->session->namedGroup); + void CleanupClientTickets(PreSharedKey* psk) + { + for (; psk != NULL; psk = psk->next) { + if (psk->decryptRet == PSK_DECRYPT_OK || + psk->decryptRet == PSK_DECRYPT_CREATE) { + psk->decryptRet = PSK_DECRYPT_NONE; + ForceZero(psk->identity, psk->identityLen); +#ifdef WOLFSSL_CHECK_MEM_ZERO + /* We want to check the InternalTicket area since that is what + * we registered in DoClientTicket_ex */ + wc_MemZero_Check((((ExternalTicket*)psk->identity)->enc_ticket), + sizeof(InternalTicket)); #endif } } - - ForceZero(it, sizeof(*it)); -#ifdef WOLFSSL_CHECK_MEM_ZERO - wc_MemZero_Check(it, sizeof(InternalTicket)); -#endif - - WOLFSSL_LEAVE("DoClientTicket", ret); - WOLFSSL_END(WC_FUNC_TICKET_DO); - - return ret; - -error: - ForceZero(it, sizeof(*it)); -#ifdef WOLFSSL_CHECK_MEM_ZERO - wc_MemZero_Check(it, sizeof(InternalTicket)); -#endif - WOLFSSL_ERROR_VERBOSE(ret); - return WOLFSSL_TICKET_RET_REJECT; } diff --git a/src/tls13.c b/src/tls13.c index f5ecf1dd6..876df789b 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -5658,68 +5658,33 @@ static int DoPreSharedKeys(WOLFSSL* ssl, const byte* input, word32 inputSz, #ifdef HAVE_SESSION_TICKET /* Decode the identity. */ - ret = DoClientTicket(ssl, current->identity, current->identityLen); + switch (current->decryptRet) { + case PSK_DECRYPT_NONE: + ret = DoClientTicket_ex(ssl, current); + break; + case PSK_DECRYPT_OK: + ret = WOLFSSL_TICKET_RET_OK; + break; + case PSK_DECRYPT_CREATE: + ret = WOLFSSL_TICKET_RET_CREATE; + break; + case PSK_DECRYPT_FAIL: + ret = WOLFSSL_TICKET_RET_REJECT; + break; + } + #ifdef WOLFSSL_ASYNC_CRYPT if (ret == WC_PENDING_E) return ret; #endif if (ret == WOLFSSL_TICKET_RET_OK) { - #ifdef WOLFSSL_32BIT_MILLI_TIME - word32 now; - sword64 diff; - - now = TimeNowInMilliseconds(); - if (now == 0) - return GETTIME_ERROR; - /* Difference between now and time ticket constructed - * (from decrypted ticket). */ - diff = now; - diff -= ssl->session->ticketSeen; - if (diff > (sword64)ssl->timeout * 1000 || - diff > (sword64)TLS13_MAX_TICKET_AGE * 1000) { + if (DoClientTicketCheck(current, ssl->timeout, suite) != 0) { + current = current->next; continue; } - #else - sword64 diff; - diff = TimeNowInMilliseconds(); - if (diff == 0) - return GETTIME_ERROR; - /* Difference between now and time ticket constructed - * (from decrypted ticket). */ - diff -= ssl->session->ticketSeen; - if (diff > (sword64)ssl->timeout * 1000 || - diff > (sword64)TLS13_MAX_TICKET_AGE * 1000) { - continue; - } - #endif - /* Subtract client's ticket age and unobfuscate. */ - diff -= current->ticketAge; - diff += ssl->session->ticketAdd; - /* Check session and ticket age timeout. - * Allow +/- 1000 milliseconds on ticket age. - */ - if (diff < -1000 || diff - MAX_TICKET_AGE_DIFF * 1000 > 1000) - continue; - - #if !defined(WOLFSSL_PSK_ONE_ID) && !defined(WOLFSSL_PRIORITIZE_PSK) - /* Check whether resumption is possible based on suites in SSL and - * ciphersuite in ticket. - */ - if ((suite[0] != ssl->session->cipherSuite0) || - (suite[1] != ssl->session->cipherSuite)) - continue; - #else - { - byte s[2] = { - ssl->session->cipherSuite0, - ssl->session->cipherSuite, - }; - if (!FindSuiteSSL(ssl, s)) - continue; - } - #endif + DoClientTicketFinalize(ssl, current->it); /* SERVER: using secret in session ticket for peer auth. */ ssl->options.peerAuthGood = 1; @@ -5893,13 +5858,23 @@ static int CheckPreSharedKeys(WOLFSSL* ssl, const byte* input, word32 helloSz, ret = DoPreSharedKeys(ssl, input, helloSz - bindersLen, suites->suites + i, usingPSK, &first); if (ret != 0) { +#ifdef HAVE_SESSION_TICKET +#ifdef WOLFSSL_ASYNC_CRYPT + if (ret != WC_PENDING_E) +#endif + CleanupClientTickets((PreSharedKey*)ext->data); +#endif WOLFSSL_MSG_EX("DoPreSharedKeys: %d", ret); return ret; } } +#ifdef HAVE_SESSION_TICKET + CleanupClientTickets((PreSharedKey*)ext->data); +#endif #else ret = DoPreSharedKeys(ssl, input, helloSz - bindersLen, suite, usingPSK, &first); + CleanupClientTickets((PreSharedKey*)ext->data); if (ret != 0) { WOLFSSL_MSG_EX("DoPreSharedKeys: %d", ret); return ret; diff --git a/tests/api.c b/tests/api.c index 3c0fc66d1..9fa4fc528 100644 --- a/tests/api.c +++ b/tests/api.c @@ -62844,6 +62844,129 @@ static int test_wolfSSL_CRL_CERT_REVOKED_alert(void) } #endif +#if defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) \ + && defined(HAVE_IO_TESTS_DEPENDENCIES) && defined(HAVE_AESGCM) && \ + !defined(NO_SHA256) && defined(WOLFSSL_AES_128) && \ + defined(WOLFSSL_SHA384) && defined(WOLFSSL_AES_256) + +static WOLFSSL_CTX* test_TLS_13_ticket_different_ciphers_ctx = NULL; +static WOLFSSL_SESSION* test_TLS_13_ticket_different_ciphers_session = NULL; +static int test_TLS_13_ticket_different_ciphers_run = 0; + +static void test_TLS_13_ticket_different_ciphers_ssl_ready(WOLFSSL* ssl) +{ + switch (test_TLS_13_ticket_different_ciphers_run) { + case 0: + /* First run */ + AssertIntEQ(wolfSSL_set_cipher_list(ssl, "TLS13-AES128-GCM-SHA256"), + WOLFSSL_SUCCESS); + if (wolfSSL_is_server(ssl)) { + AssertNotNull(test_TLS_13_ticket_different_ciphers_ctx = + wolfSSL_get_SSL_CTX(ssl)); + AssertIntEQ(WOLFSSL_SUCCESS, + wolfSSL_CTX_up_ref(test_TLS_13_ticket_different_ciphers_ctx)); + } + break; + case 1: + /* Second run */ + AssertIntEQ(wolfSSL_set_cipher_list(ssl, "TLS13-AES256-GCM-SHA384:" + "TLS13-AES128-GCM-SHA256"), + WOLFSSL_SUCCESS); + if (!wolfSSL_is_server(ssl)) { + AssertIntEQ(wolfSSL_set_session(ssl, + test_TLS_13_ticket_different_ciphers_session), + WOLFSSL_SUCCESS); + } + break; + default: + /* Bad state? */ + Fail(("Should not enter here"), ("Should not enter here")); + } +} + +static void test_TLS_13_ticket_different_ciphers_on_result(WOLFSSL* ssl) +{ + switch (test_TLS_13_ticket_different_ciphers_run) { + case 0: + /* First run */ + AssertNotNull(test_TLS_13_ticket_different_ciphers_session = + wolfSSL_get1_session(ssl)); + break; + case 1: + /* Second run */ + AssertTrue(wolfSSL_session_reused(ssl)); + break; + default: + /* Bad state? */ + Fail(("Should not enter here"), ("Should not enter here")); + } +} + +static int test_TLS_13_ticket_different_ciphers(void) +{ + /* Check that we handle the connection when the ticket doesn't match + * the first ciphersuite. */ + callback_functions client_cbs, server_cbs; + struct test_params { + method_provider client_meth; + method_provider server_meth; + int doUdp; + } params[] = { +#ifdef WOLFSSL_DTLS13 + /* Test that the stateless code handles sessions correctly */ + {wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method, 1}, +#endif + {wolfTLSv1_3_client_method, wolfTLSv1_3_server_method, 0}, + }; + size_t i; + + for (i = 0; i < sizeof(params)/sizeof(*params); i++) { + XMEMSET(&client_cbs, 0, sizeof(client_cbs)); + XMEMSET(&server_cbs, 0, sizeof(server_cbs)); + + test_TLS_13_ticket_different_ciphers_run = 0; + + client_cbs.doUdp = server_cbs.doUdp = params[i].doUdp; + + client_cbs.method = params[i].client_meth; + server_cbs.method = params[i].server_meth; + + client_cbs.ssl_ready = test_TLS_13_ticket_different_ciphers_ssl_ready; + server_cbs.ssl_ready = test_TLS_13_ticket_different_ciphers_ssl_ready; + + client_cbs.on_result = test_TLS_13_ticket_different_ciphers_on_result; + + server_cbs.ticNoInit = 1; + + test_wolfSSL_client_server_nofail(&client_cbs, &server_cbs); + + AssertTrue(client_cbs.return_code); + AssertTrue(server_cbs.return_code); + + test_TLS_13_ticket_different_ciphers_run++; + + server_cbs.ctx = test_TLS_13_ticket_different_ciphers_ctx; + + test_wolfSSL_client_server_nofail(&client_cbs, &server_cbs); + + AssertTrue(client_cbs.return_code); + AssertTrue(server_cbs.return_code); + + wolfSSL_SESSION_free(test_TLS_13_ticket_different_ciphers_session); + test_TLS_13_ticket_different_ciphers_session = NULL; + wolfSSL_CTX_free(test_TLS_13_ticket_different_ciphers_ctx); + test_TLS_13_ticket_different_ciphers_ctx = NULL; + } + + return TEST_RES_CHECK(1); +} +#else +static int test_TLS_13_ticket_different_ciphers(void) +{ + return TEST_SKIPPED; +} +#endif + /*----------------------------------------------------------------------------* | Main *----------------------------------------------------------------------------*/ @@ -63844,6 +63967,7 @@ TEST_CASE testCases[] = { * !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) */ TEST_DECL(test_wolfSSL_CTX_set_ciphersuites), TEST_DECL(test_wolfSSL_CRL_CERT_REVOKED_alert), + TEST_DECL(test_TLS_13_ticket_different_ciphers), TEST_DECL(test_WOLFSSL_dtls_version_alert), TEST_DECL(test_ForceZero), diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 1764640a2..5652f7c2c 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -3097,6 +3097,13 @@ WOLFSSL_LOCAL int TLSX_KeyShare_Parse_ClientHello(const WOLFSSL* ssl, #if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) +enum PskDecryptReturn { + PSK_DECRYPT_NONE = 0, + PSK_DECRYPT_OK, + PSK_DECRYPT_CREATE, + PSK_DECRYPT_FAIL, +}; + /* The PreSharedKey extension information - entry in a linked list. */ typedef struct PreSharedKey { word16 identityLen; /* Length of identity */ @@ -3107,8 +3114,12 @@ typedef struct PreSharedKey { word32 binderLen; /* Length of HMAC */ byte binder[WC_MAX_DIGEST_SIZE]; /* HMAC of handshake */ byte hmac; /* HMAC algorithm */ +#ifdef HAVE_SESSION_TICKET + InternalTicket* it; /* ptr to ticket */ +#endif byte resumption:1; /* Resumption PSK */ byte chosen:1; /* Server's choice */ + byte decryptRet:3; /* Ticket decrypt return */ struct PreSharedKey* next; /* List pointer */ } PreSharedKey; @@ -3933,13 +3944,8 @@ typedef struct TicketNonce { #ifdef WOLFSSL_DTLS typedef struct PskInfo { -#if defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) - /* Macro guard matches one for session->version */ - ProtocolVersion pv; -#endif byte cipherSuite0; byte cipherSuite; - word16 namedGroup; byte isValid:1; } PskInfo; #endif @@ -5650,10 +5656,16 @@ extern const WOLF_EC_NIST_NAME kNistCurves[]; WOLFSSL_LOCAL int SendChangeCipher(WOLFSSL* ssl); WOLFSSL_LOCAL int SendTicket(WOLFSSL* ssl); #ifdef HAVE_SESSION_TICKET -WOLFSSL_LOCAL int DoDecryptTicket(WOLFSSL* ssl, const byte* input, word32 len, - InternalTicket **it); -#endif /* HAVE_SESSION_TICKET */ +WOLFSSL_LOCAL int DoDecryptTicket(const WOLFSSL* ssl, const byte* input, + word32 len, InternalTicket **it); +/* Return 0 when check successful. <0 on failure. */ +WOLFSSL_LOCAL int DoClientTicketCheck(const PreSharedKey* psk, sword64 timeout, + const byte* suite); +WOLFSSL_LOCAL void DoClientTicketFinalize(WOLFSSL* ssl, InternalTicket* it); +WOLFSSL_LOCAL void CleanupClientTickets(PreSharedKey* psk); WOLFSSL_LOCAL int DoClientTicket(WOLFSSL* ssl, const byte* input, word32 len); +WOLFSSL_LOCAL int DoClientTicket_ex(const WOLFSSL* ssl, PreSharedKey* psk); +#endif /* HAVE_SESSION_TICKET */ WOLFSSL_LOCAL int SendData(WOLFSSL* ssl, const void* data, int sz); #ifdef WOLFSSL_TLS13 WOLFSSL_LOCAL int SendTls13ServerHello(WOLFSSL* ssl, byte extMsgType);