/* dtls.c * * Copyright (C) 2006-2023 wolfSSL Inc. * * This file is part of wolfSSL. * * wolfSSL is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * wolfSSL is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA */ /* * WOLFSSL_DTLS_NO_HVR_ON_RESUME * WOLFSSL_DTLS13_NO_HRR_ON_RESUME * If defined, a DTLS server will not do a cookie exchange on successful * client resumption: the resumption will be faster (one RTT less) and * will consume less bandwidth (one ClientHello and one * HelloVerifyRequest/HelloRetryRequest less). On the other hand, if a valid * SessionID/ticket/psk is collected, forged clientHello messages will * consume resources on the server. For DTLS 1.3, using this option also * allows for the server to process Early Data/0-RTT Data. Without this, the * Early Data would be dropped since the server doesn't enter stateful * processing until receiving a verified ClientHello with the cookie. * * To allow DTLS 1.3 resumption without the cookie exchange: * - Compile wolfSSL with WOLFSSL_DTLS13_NO_HRR_ON_RESUME defined * - Call wolfSSL_dtls13_no_hrr_on_resume(ssl, 1) on the WOLFSSL object to * disable the cookie exchange on resumption * - Continue like with a normal connection * WOLFSSL_DTLS_CH_FRAG * Allow a server to process a fragmented second/verified (one containing a * valid cookie response) ClientHello message. The first/unverified (one * without a cookie extension) ClientHello MUST be unfragmented so that the * DTLS server can process it statelessly. This is only implemented for * DTLS 1.3. The user MUST call wolfSSL_dtls13_allow_ch_frag() on the server * to explicitly enable this during runtime. */ #ifdef HAVE_CONFIG_H #include #endif #include #ifndef WOLFCRYPT_ONLY #include #include #include #ifdef NO_INLINE #include #else #define WOLFSSL_MISC_INCLUDED #include #endif #define ERROR_OUT(err, eLabel) { ret = (err); goto eLabel; } #ifdef WOLFSSL_DTLS void DtlsResetState(WOLFSSL* ssl) { /* Reset the state so that we can statelessly await the * ClientHello that contains the cookie. Don't gate on IsAtLeastTLSv1_3 * to handle the edge case when the peer wants a lower version. */ /* Reset DTLS window */ #ifdef WOLFSSL_DTLS13 w64Zero(&ssl->dtls13Epochs[0].nextSeqNumber); w64Zero(&ssl->dtls13Epochs[0].nextPeerSeqNumber); XMEMSET(ssl->dtls13Epochs[0].window, 0, sizeof(ssl->dtls13Epochs[0].window)); Dtls13FreeFsmResources(ssl); #endif ssl->keys.dtls_expected_peer_handshake_number = 0; ssl->keys.dtls_handshake_number = 0; ssl->keys.dtls_sequence_number_hi = 0; ssl->keys.dtls_sequence_number_lo = 0; /* Reset states */ ssl->options.serverState = NULL_STATE; ssl->options.clientState = NULL_STATE; ssl->options.connectState = CONNECT_BEGIN; ssl->options.acceptState = ACCEPT_BEGIN; ssl->options.handShakeState = NULL_STATE; ssl->options.seenUnifiedHdr = 0; ssl->msgsReceived.got_client_hello = 0; ssl->keys.dtls_handshake_number = 0; ssl->keys.dtls_expected_peer_handshake_number = 0; XMEMSET(ssl->keys.peerSeq, 0, sizeof(ssl->keys.peerSeq)); ssl->options.tls = 0; ssl->options.tls1_1 = 0; ssl->options.tls1_3 = 0; } int DtlsIgnoreError(int err) { /* Whitelist of errors not to ignore */ switch (err) { case MEMORY_E: case MEMORY_ERROR: case ASYNC_INIT_E: case ASYNC_OP_E: case SOCKET_ERROR_E: case WANT_READ: case WANT_WRITE: return 0; default: return 1; } } void DtlsSetSeqNumForReply(WOLFSSL* ssl) { /* We cover both DTLS 1.2 and 1.3 cases because we may be negotiating * protocols. */ /* We should continue with the same sequence number as the * Client Hello. */ ssl->keys.dtls_sequence_number_hi = ssl->keys.curSeq_hi; ssl->keys.dtls_sequence_number_lo = ssl->keys.curSeq_lo; #ifdef WOLFSSL_DTLS13 if (ssl->dtls13EncryptEpoch != NULL) { ssl->dtls13EncryptEpoch->nextSeqNumber = w64From32(ssl->keys.curSeq_hi, ssl->keys.curSeq_lo); } #endif /* We should continue with the same handshake number as the * Client Hello. */ ssl->keys.dtls_handshake_number = ssl->keys.dtls_peer_handshake_number; } #if !defined(NO_WOLFSSL_SERVER) #if defined(NO_SHA) && defined(NO_SHA256) #error "DTLS needs either SHA or SHA-256" #endif /* NO_SHA && NO_SHA256 */ #if !defined(NO_SHA) && defined(NO_SHA256) #define DTLS_COOKIE_TYPE WC_SHA #define DTLS_COOKIE_SZ WC_SHA_DIGEST_SIZE #endif /* !NO_SHA && NO_SHA256 */ #ifndef NO_SHA256 #define DTLS_COOKIE_TYPE WC_SHA256 #define DTLS_COOKIE_SZ WC_SHA256_DIGEST_SIZE #endif /* !NO_SHA256 */ #if defined(WOLFSSL_DTLS13) && (defined(HAVE_SESSION_TICKET) || \ !defined(NO_PSK)) typedef struct PskInfo { byte cipherSuite0; byte cipherSuite; byte isValid:1; } PskInfo; #endif typedef struct WolfSSL_ConstVector { word32 size; const byte* elements; } WolfSSL_ConstVector; typedef struct WolfSSL_CH { ProtocolVersion* pv; const byte* random; WolfSSL_ConstVector sessionId; WolfSSL_ConstVector cookie; WolfSSL_ConstVector cipherSuite; WolfSSL_ConstVector compression; WolfSSL_ConstVector extension; WolfSSL_ConstVector cookieExt; const byte* raw; word32 length; /* Store the DTLS 1.2 cookie since we can just compute it once in dtls.c */ byte dtls12cookie[DTLS_COOKIE_SZ]; byte dtls12cookieSet:1; } WolfSSL_CH; static int ReadVector8(const byte* input, WolfSSL_ConstVector* v) { v->size = *input; v->elements = input + OPAQUE8_LEN; return v->size + OPAQUE8_LEN; } static int ReadVector16(const byte* input, WolfSSL_ConstVector* v) { word16 size16; ato16(input, &size16); v->size = (word32)size16; v->elements = input + OPAQUE16_LEN; return v->size + OPAQUE16_LEN; } static int CreateDtls12Cookie(const WOLFSSL* ssl, const WolfSSL_CH* ch, byte* cookie) { int ret; Hmac cookieHmac; ret = wc_HmacInit(&cookieHmac, ssl->heap, ssl->devId); if (ret == 0) { ret = wc_HmacSetKey(&cookieHmac, DTLS_COOKIE_TYPE, ssl->buffers.dtlsCookieSecret.buffer, ssl->buffers.dtlsCookieSecret.length); if (ret == 0) { ret = wc_HmacUpdate(&cookieHmac, (const byte*)ssl->buffers.dtlsCtx.peer.sa, ssl->buffers.dtlsCtx.peer.sz); } if (ret == 0) ret = wc_HmacUpdate(&cookieHmac, (byte*)ch->pv, OPAQUE16_LEN); if (ret == 0) ret = wc_HmacUpdate(&cookieHmac, (byte*)ch->random, RAN_LEN); if (ret == 0) { ret = wc_HmacUpdate(&cookieHmac, (byte*)ch->sessionId.elements, ch->sessionId.size); } if (ret == 0) { ret = wc_HmacUpdate(&cookieHmac, (byte*)ch->cipherSuite.elements, ch->cipherSuite.size); } if (ret == 0) { ret = wc_HmacUpdate(&cookieHmac, (byte*)ch->compression.elements, ch->compression.size); } if (ret == 0) ret = wc_HmacFinal(&cookieHmac, cookie); wc_HmacFree(&cookieHmac); } return ret; } static int CheckDtlsCookie(const WOLFSSL* ssl, WolfSSL_CH* ch, byte isTls13, byte* cookieGood) { int ret = 0; (void)isTls13; *cookieGood = 0; #ifdef WOLFSSL_DTLS13 if (isTls13) { word16 len; if (ch->cookieExt.size < OPAQUE16_LEN + 1) return BUFFER_E; ato16(ch->cookieExt.elements, &len); if (ch->cookieExt.size - OPAQUE16_LEN != len) return BUFFER_E; ret = TlsCheckCookie(ssl, ch->cookieExt.elements + OPAQUE16_LEN, (word16)(ch->cookieExt.size - OPAQUE16_LEN)); if (ret < 0 && ret != HRR_COOKIE_ERROR) return ret; *cookieGood = ret > 0; ret = 0; } else #endif { if (ch->cookie.size != DTLS_COOKIE_SZ) return 0; if (!ch->dtls12cookieSet) { ret = CreateDtls12Cookie(ssl, ch, ch->dtls12cookie); if (ret != 0) return ret; ch->dtls12cookieSet = 1; } *cookieGood = ConstantCompare(ch->cookie.elements, ch->dtls12cookie, DTLS_COOKIE_SZ) == 0; } return ret; } static int ParseClientHello(const byte* input, word32 helloSz, WolfSSL_CH* ch, byte isFirstCHFrag) { word32 idx = 0; (void)isFirstCHFrag; /* protocol version, random and session id length check */ if (OPAQUE16_LEN + RAN_LEN + OPAQUE8_LEN > helloSz) return BUFFER_ERROR; ch->raw = input; ch->pv = (ProtocolVersion*)(input + idx); idx += OPAQUE16_LEN; ch->random = (byte*)(input + idx); idx += RAN_LEN; idx += ReadVector8(input + idx, &ch->sessionId); if (idx > helloSz - OPAQUE8_LEN) return BUFFER_ERROR; idx += ReadVector8(input + idx, &ch->cookie); if (idx > helloSz - OPAQUE16_LEN) return BUFFER_ERROR; idx += ReadVector16(input + idx, &ch->cipherSuite); if (idx > helloSz - OPAQUE8_LEN) return BUFFER_ERROR; idx += ReadVector8(input + idx, &ch->compression); if (idx < helloSz - OPAQUE16_LEN) { /* Extensions are optional */ #ifdef WOLFSSL_DTLS_CH_FRAG word32 extStart = idx + OPAQUE16_LEN; #endif idx += ReadVector16(input + idx, &ch->extension); if (idx > helloSz) { #ifdef WOLFSSL_DTLS_CH_FRAG idx = helloSz; /* Allow incomplete extensions if we are parsing a fragment */ if (isFirstCHFrag && extStart < helloSz) ch->extension.size = helloSz - extStart; else #endif return BUFFER_ERROR; } } if (idx != helloSz) return BUFFER_ERROR; ch->length = idx; return 0; } #if (defined(WOLFSSL_DTLS_NO_HVR_ON_RESUME) && defined(HAVE_SESSION_TICKET)) \ || defined(WOLFSSL_DTLS13) static int FindExtByType(WolfSSL_ConstVector* ret, word16 extType, WolfSSL_ConstVector exts, int* tlsxFound) { word32 len, idx = 0; word16 type; WolfSSL_ConstVector ext; XMEMSET(ret, 0, sizeof(*ret)); len = exts.size; *tlsxFound = FALSE; /* type + len */ while (len >= OPAQUE16_LEN + OPAQUE16_LEN) { ato16(exts.elements + idx, &type); idx += OPAQUE16_LEN; idx += ReadVector16(exts.elements + idx, &ext); if (idx > exts.size) return BUFFER_ERROR; if (type == extType) { XMEMCPY(ret, &ext, sizeof(ext)); *tlsxFound = TRUE; return 0; } len = exts.size - idx; } return 0; } #endif #if defined(WOLFSSL_DTLS_NO_HVR_ON_RESUME) #ifdef HAVE_SESSION_TICKET static int TlsTicketIsValid(const WOLFSSL* ssl, WolfSSL_ConstVector exts, int* resume) { WolfSSL_ConstVector tlsxSessionTicket; byte tempTicket[SESSION_TICKET_LEN]; InternalTicket* it = NULL; int ret = 0; int tlsxFound; *resume = FALSE; ret = FindExtByType(&tlsxSessionTicket, TLSX_SESSION_TICKET, exts, &tlsxFound); if (ret != 0) return ret; if (tlsxSessionTicket.size == 0) return 0; if (tlsxSessionTicket.size > SESSION_TICKET_LEN) return 0; XMEMCPY(tempTicket, tlsxSessionTicket.elements, tlsxSessionTicket.size); 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, int* resume) { const WOLFSSL_SESSION* sess; word32 sessRow; int ret; #ifdef HAVE_EXT_CACHE int copy; #endif *resume = FALSE; if (ssl->options.sessionCacheOff) return 0; if (sessionID.size != ID_LEN) return 0; #ifdef HAVE_EXT_CACHE if (ssl->ctx->get_sess_cb != NULL) { WOLFSSL_SESSION* extSess = ssl->ctx->get_sess_cb((WOLFSSL*)ssl, sessionID.elements, ID_LEN, ©); if (extSess != NULL) { #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(extSess->version)) #endif *resume = TRUE; if (!copy) wolfSSL_FreeSession(ssl->ctx, extSess); if (*resume) return 0; } } if (ssl->ctx->internalCacheLookupOff) return 0; #endif ret = TlsSessionCacheGetAndRdLock(sessionID.elements, &sess, &sessRow, ssl->options.side); if (ret == 0 && sess != NULL) { #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 *resume = TRUE; TlsSessionCacheUnlockRow(sessRow); } return 0; } static int TlsResumptionIsValid(const WOLFSSL* ssl, WolfSSL_CH* ch, int* resume) { int ret; #ifdef HAVE_SESSION_TICKET ret = TlsTicketIsValid(ssl, ch->extension, resume); if (ret != 0) return ret; if (*resume) return 0; #endif /* HAVE_SESSION_TICKET */ ret = TlsSessionIdIsValid(ssl, ch->sessionId, resume); return ret; } #endif /* WOLFSSL_DTLS13 || WOLFSSL_DTLS_NO_HVR_ON_RESUME */ #ifdef WOLFSSL_DTLS13 static int TlsCheckSupportedVersion(const WOLFSSL* ssl, WolfSSL_CH* ch, byte *isTls13) { WolfSSL_ConstVector tlsxSupportedVersions; int ret; ProtocolVersion pv = ssl->version; int tlsxFound; ret = FindExtByType(&tlsxSupportedVersions, TLSX_SUPPORTED_VERSIONS, ch->extension, &tlsxFound); if (ret != 0) return ret; if (!tlsxFound) { *isTls13 = 0; return 0; } ret = TLSX_SupportedVersions_Parse(ssl, tlsxSupportedVersions.elements, (word16)tlsxSupportedVersions.size, client_hello, &pv, NULL, NULL); if (ret != 0) return ret; if (IsAtLeastTLSv1_3(pv)) *isTls13 = 1; else *isTls13 = 0; return 0; } #endif #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); PreSharedKey* current; int i; int ret; if (pskExt == NULL) return; for (i = 0; i < suites->suiteSz; i += 2) { for (current = (PreSharedKey*)pskExt->data; current != NULL; current = current->next) { #ifdef HAVE_SESSION_TICKET { /* Decode the identity. */ switch (current->decryptRet) { case PSK_DECRYPT_NONE: ret = DoClientTicket_ex(ssl, current, 0); 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: default: ret = WOLFSSL_TICKET_RET_REJECT; break; } if (ret == WOLFSSL_TICKET_RET_OK) { if (DoClientTicketCheck(ssl, 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; goto cleanup; } } #endif } } /* Empty return necessary so we can have both the label and macro guard */ cleanup: #ifdef HAVE_SESSION_TICKET CleanupClientTickets((PreSharedKey*)pskExt->data); #endif return; } #endif #ifdef WOLFSSL_DTLS13 #ifndef WOLFSSL_SEND_HRR_COOKIE #error "WOLFSSL_SEND_HRR_COOKIE has to be defined to use DTLS 1.3 server" #endif #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; WolfSSL_ConstVector tlsx; int tlsxFound; Suites suites; byte haveSA = 0; byte haveKS = 0; byte haveSG = 0; #if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) byte usePSK = 0; byte doKE = 0; #endif CipherSuite cs; CipherSpecs specs; byte cookieHash[WC_MAX_DIGEST_SIZE]; int cookieHashSz; #if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) PskInfo pskInfo; XMEMSET(&pskInfo, 0, sizeof(pskInfo)); #endif #ifndef HAVE_SUPPORTED_CURVES (void)doKE; #endif /* !HAVE_SUPPORTED_CURVES */ XMEMSET(&cs, 0, sizeof(cs)); /* We need to echo the session ID sent by the client */ if (ch->sessionId.size > ID_LEN) { /* Too large. We can't echo this. */ ERROR_OUT(INVALID_PARAMETER, dtls13_cleanup); } /* Populate the suites struct to find a common ciphersuite */ XMEMSET(&suites, 0, sizeof(suites)); suites.suiteSz = (word16)ch->cipherSuite.size; if ((suites.suiteSz % 2) != 0) ERROR_OUT(INVALID_PARAMETER, dtls13_cleanup); if (suites.suiteSz > WOLFSSL_MAX_SUITE_SZ) ERROR_OUT(BUFFER_ERROR, dtls13_cleanup); XMEMCPY(suites.suites, ch->cipherSuite.elements, suites.suiteSz); /* Populate extensions */ /* Supported versions always need to be present. Has to appear after * key share as that is the order we reconstruct it in * RestartHandshakeHashWithCookie. */ ret = TLSX_Push(&parsedExts, TLSX_SUPPORTED_VERSIONS, ssl, ssl->heap); if (ret != 0) goto dtls13_cleanup; /* Set that this is a response extension */ parsedExts->resp = 1; #if defined(HAVE_SUPPORTED_CURVES) ret = TLSX_SupportedCurve_Copy(ssl->extensions, &parsedExts, ssl->heap); if (ret != 0) goto dtls13_cleanup; #endif #if !defined(NO_CERTS) /* Signature algs */ ret = FindExtByType(&tlsx, TLSX_SIGNATURE_ALGORITHMS, ch->extension, &tlsxFound); if (ret != 0) goto dtls13_cleanup; if (tlsxFound) { WolfSSL_ConstVector sigAlgs; if (tlsx.size < OPAQUE16_LEN) ERROR_OUT(BUFFER_ERROR, dtls13_cleanup); ReadVector16(tlsx.elements, &sigAlgs); if (sigAlgs.size != tlsx.size - OPAQUE16_LEN) ERROR_OUT(BUFFER_ERROR, dtls13_cleanup); if ((sigAlgs.size % 2) != 0) ERROR_OUT(BUFFER_ERROR, dtls13_cleanup); suites.hashSigAlgoSz = (word16)sigAlgs.size; XMEMCPY(suites.hashSigAlgo, sigAlgs.elements, sigAlgs.size); haveSA = 1; } #endif /* !defined(NO_CERTS) */ #ifdef HAVE_SUPPORTED_CURVES /* Supported groups */ ret = FindExtByType(&tlsx, TLSX_SUPPORTED_GROUPS, ch->extension, &tlsxFound); if (ret != 0) goto dtls13_cleanup; if (tlsxFound) { ret = TLSX_SupportedCurve_Parse(ssl, tlsx.elements, (word16)tlsx.size, 1, &parsedExts); if (ret != 0) goto dtls13_cleanup; haveSG = 1; } /* Key share */ ret = FindExtByType(&tlsx, TLSX_KEY_SHARE, ch->extension, &tlsxFound); if (ret != 0) goto dtls13_cleanup; if (tlsxFound) { ret = TLSX_KeyShare_Parse_ClientHello(ssl, tlsx.elements, (word16)tlsx.size, &parsedExts); if (ret != 0) goto dtls13_cleanup; haveKS = 1; } #endif /* HAVE_SUPPORTED_CURVES */ #if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) /* Pre-shared key */ ret = FindExtByType(&tlsx, TLSX_PRE_SHARED_KEY, ch->extension, &tlsxFound); if (ret != 0) goto dtls13_cleanup; if (tlsxFound) { /* Let's just assume that the binders are correct here. We will * actually verify this in the stateful part of the processing * and if they don't match we will error out there anyway. */ byte modes; /* 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) { ret = FindExtByType(&tlsx, TLSX_PSK_KEY_EXCHANGE_MODES, ch->extension, &tlsxFound); if (ret != 0) goto dtls13_cleanup; if (!tlsxFound) ERROR_OUT(PSK_KEY_ERROR, dtls13_cleanup); ret = TLSX_PskKeyModes_Parse_Modes(tlsx.elements, tlsx.size, client_hello, &modes); if (ret != 0) goto dtls13_cleanup; if ((modes & (1 << PSK_DHE_KE)) && !ssl->options.noPskDheKe) { if (!haveKS) ERROR_OUT(PSK_KEY_ERROR, dtls13_cleanup); doKE = 1; } else if ((modes & (1 << PSK_KE)) == 0) { ERROR_OUT(PSK_KEY_ERROR, dtls13_cleanup); } usePSK = 1; } } #endif #if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) if (usePSK && pskInfo.isValid) { cs.cipherSuite0 = pskInfo.cipherSuite0; cs.cipherSuite = pskInfo.cipherSuite; /* https://datatracker.ietf.org/doc/html/rfc8446#section-9.2 */ if (haveSG ^ haveKS) { WOLFSSL_MSG("Client needs to send both or none of KeyShare and " "SupportedGroups"); ERROR_OUT(INCOMPLETE_DATA, dtls13_cleanup); } #ifdef HAVE_SUPPORTED_CURVES if (doKE) { byte searched = 0; ret = TLSX_KeyShare_Choose(ssl, parsedExts, cs.cipherSuite0, cs.cipherSuite, &cs.clientKSE, &searched); if (ret != 0) goto dtls13_cleanup; if (cs.clientKSE == NULL && searched) cs.doHelloRetry = 1; } #endif /* HAVE_SUPPORTED_CURVES */ } else #endif /* defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) */ { /* https://datatracker.ietf.org/doc/html/rfc8446#section-9.2 */ if (!haveKS || !haveSA || !haveSG) { WOLFSSL_MSG("Client didn't send KeyShare or SigAlgs or " "SupportedGroups."); ERROR_OUT(INCOMPLETE_DATA, dtls13_cleanup); } /* TLSX_KeyShare_Choose is done deep inside MatchSuite_ex */ ret = MatchSuite_ex(ssl, &suites, &cs, parsedExts); if (ret < 0) { WOLFSSL_MSG("Unsupported cipher suite, ClientHello DTLS 1.3"); ERROR_OUT(INCOMPLETE_DATA, dtls13_cleanup); } } #ifdef WOLFSSL_DTLS13_NO_HRR_ON_RESUME if (ssl->options.dtls13NoHrrOnResume && usePSK && pskInfo.isValid && !cs.doHelloRetry) { /* Skip HRR on resumption */ ((WOLFSSL*)ssl)->options.dtlsStateful = 1; goto dtls13_cleanup; } #endif #ifdef HAVE_SUPPORTED_CURVES if (cs.doHelloRetry) { ret = TLSX_KeyShare_SetSupported(ssl, &parsedExts); if (ret != 0) goto dtls13_cleanup; } else { /* Need to remove the keyshare ext if we found a common group * and are not doing curve negotiation. */ TLSX_Remove(&parsedExts, TLSX_KEY_SHARE, ssl->heap); } #endif /* HAVE_SUPPORTED_CURVES */ /* This is required to correctly generate the hash */ ret = GetCipherSpec(WOLFSSL_SERVER_END, cs.cipherSuite0, cs.cipherSuite, &specs, NULL); if (ret != 0) goto dtls13_cleanup; /* Calculate the cookie hash */ ret = Dtls13HashClientHello(ssl, cookieHash, &cookieHashSz, ch->raw, ch->length, &specs); if (ret != 0) goto dtls13_cleanup; /* Push the cookie to extensions */ ret = CreateCookieExt(ssl, cookieHash, (word16)cookieHashSz, &parsedExts, cs.cipherSuite0, cs.cipherSuite); if (ret != 0) goto dtls13_cleanup; { WOLFSSL* nonConstSSL = (WOLFSSL*)ssl; TLSX* sslExts = nonConstSSL->extensions; if (ret != 0) goto dtls13_cleanup; nonConstSSL->options.tls = 1; nonConstSSL->options.tls1_1 = 1; nonConstSSL->options.tls1_3 = 1; XMEMCPY(nonConstSSL->session->sessionID, ch->sessionId.elements, ch->sessionId.size); nonConstSSL->session->sessionIDSz = (byte)ch->sessionId.size; nonConstSSL->options.cipherSuite0 = cs.cipherSuite0; nonConstSSL->options.cipherSuite = cs.cipherSuite; nonConstSSL->extensions = parsedExts; ret = SendTls13ServerHello(nonConstSSL, hello_retry_request); /* Can be modified inside SendTls13ServerHello */ parsedExts = nonConstSSL->extensions; nonConstSSL->session->sessionIDSz = 0; nonConstSSL->options.cipherSuite0 = 0; nonConstSSL->options.cipherSuite = 0; nonConstSSL->extensions = sslExts; nonConstSSL->options.tls = 0; nonConstSSL->options.tls1_1 = 0; nonConstSSL->options.tls1_3 = 0; } dtls13_cleanup: TLSX_FreeAll(parsedExts, ssl->heap); return ret; } #endif static int SendStatelessReply(const WOLFSSL* ssl, WolfSSL_CH* ch, byte isTls13) { int ret; (void)isTls13; #ifdef WOLFSSL_DTLS13 if (isTls13) { ret = SendStatelessReplyDtls13(ssl, ch); } else #endif { #if !defined(WOLFSSL_NO_TLS12) if (!ch->dtls12cookieSet) { ret = CreateDtls12Cookie(ssl, ch, ch->dtls12cookie); if (ret != 0) return ret; ch->dtls12cookieSet = 1; } ret = SendHelloVerifyRequest((WOLFSSL*)ssl, ch->dtls12cookie, DTLS_COOKIE_SZ); #else WOLFSSL_MSG("DTLS1.2 disabled with WOLFSSL_NO_TLS12"); WOLFSSL_ERROR_VERBOSE(NOT_COMPILED_IN); ret = NOT_COMPILED_IN; #endif } return ret; } static int ClientHelloSanityCheck(WolfSSL_CH* ch, byte isTls13) { /* Do basic checks on the basic fields */ /* Check the protocol version */ if (ch->pv->major != DTLS_MAJOR) return VERSION_ERROR; if (ch->pv->minor != DTLSv1_2_MINOR && ch->pv->minor != DTLS_MINOR) return VERSION_ERROR; if (isTls13) { if (ch->cookie.size != 0) return INVALID_PARAMETER; if (ch->compression.size != COMP_LEN) return INVALID_PARAMETER; if (ch->compression.elements[0] != NO_COMPRESSION) return INVALID_PARAMETER; } return 0; } int DoClientHelloStateless(WOLFSSL* ssl, const byte* input, word32 helloSz, byte isFirstCHFrag, byte* tls13) { int ret; WolfSSL_CH ch; byte isTls13 = 0; WOLFSSL_ENTER("DoClientHelloStateless"); if (isFirstCHFrag) { #ifdef WOLFSSL_DTLS_CH_FRAG WOLFSSL_MSG("\tProcessing fragmented ClientHello"); #else WOLFSSL_MSG("\tProcessing fragmented ClientHello but " "WOLFSSL_DTLS_CH_FRAG is not defined. This should not happen."); return BAD_STATE_E; #endif } if (tls13 != NULL) *tls13 = 0; XMEMSET(&ch, 0, sizeof(ch)); ssl->options.dtlsStateful = 0; ret = ParseClientHello(input, helloSz, &ch, isFirstCHFrag); if (ret != 0) return ret; #ifdef WOLFSSL_DTLS13 if (IsAtLeastTLSv1_3(ssl->version)) { ret = TlsCheckSupportedVersion(ssl, &ch, &isTls13); if (ret != 0) return ret; if (tls13 != NULL) *tls13 = isTls13; if (isTls13) { int tlsxFound; ret = FindExtByType(&ch.cookieExt, TLSX_COOKIE, ch.extension, &tlsxFound); if (ret != 0) return ret; } } #endif ret = ClientHelloSanityCheck(&ch, isTls13); if (ret != 0) return ret; #ifdef WOLFSSL_DTLS_NO_HVR_ON_RESUME if (!isTls13 && !isFirstCHFrag) { 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) { #ifdef WOLFSSL_DTLS_CH_FRAG /* Don't send anything here when processing fragment */ if (isFirstCHFrag) ret = COOKIE_ERROR; else #endif ret = SendStatelessReply(ssl, &ch, isTls13); } else { byte cookieGood; ret = CheckDtlsCookie(ssl, &ch, isTls13, &cookieGood); if (ret != 0) return ret; if (!cookieGood) { #ifdef WOLFSSL_DTLS13 /* Invalid cookie for DTLS 1.3 results in an alert. Alert to be sent * in DoTls13ClientHello. */ if (isTls13) ret = INVALID_PARAMETER; else #endif #ifdef WOLFSSL_DTLS_CH_FRAG /* Don't send anything here when processing fragment */ if (isFirstCHFrag) ret = COOKIE_ERROR; else #endif ret = SendStatelessReply(ssl, &ch, isTls13); } else { ssl->options.dtlsStateful = 1; /* Update the window now that we enter the stateful parsing */ #ifdef WOLFSSL_DTLS13 if (isTls13) ret = Dtls13UpdateWindowRecordRecvd(ssl); else #endif DtlsUpdateWindow(ssl); } } return ret; } #endif /* !defined(NO_WOLFSSL_SERVER) */ #if defined(WOLFSSL_DTLS_CID) typedef struct ConnectionID { byte length; /* Ignore "nonstandard extension used : zero-sized array in struct/union" * MSVC warning */ #ifdef _MSC_VER #pragma warning(disable: 4200) #endif byte id[]; } ConnectionID; typedef struct CIDInfo { ConnectionID* tx; ConnectionID* rx; byte negotiated : 1; } CIDInfo; static ConnectionID* DtlsCidNew(const byte* cid, byte size, void* heap) { ConnectionID* ret; ret = (ConnectionID*)XMALLOC(sizeof(ConnectionID) + size, heap, DYNAMIC_TYPE_TLSX); if (ret == NULL) return NULL; ret->length = size; XMEMCPY(ret->id, cid, size); return ret; } static WC_INLINE CIDInfo* DtlsCidGetInfo(WOLFSSL* ssl) { return ssl->dtlsCidInfo; } static int DtlsCidGetSize(WOLFSSL* ssl, unsigned int* size, int rx) { ConnectionID* id; CIDInfo* info; if (ssl == NULL || size == NULL) return BAD_FUNC_ARG; info = DtlsCidGetInfo(ssl); if (info == NULL) return WOLFSSL_FAILURE; id = rx ? info->rx : info->tx; if (id == NULL) { *size = 0; return WOLFSSL_SUCCESS; } *size = id->length; return WOLFSSL_SUCCESS; } static int DtlsCidGet(WOLFSSL* ssl, unsigned char* buf, int bufferSz, int rx) { ConnectionID* id; CIDInfo* info; if (ssl == NULL || buf == NULL) return BAD_FUNC_ARG; info = DtlsCidGetInfo(ssl); if (info == NULL) return WOLFSSL_FAILURE; id = rx ? info->rx : info->tx; if (id == NULL || id->length == 0) return WOLFSSL_SUCCESS; if (id->length > bufferSz) return LENGTH_ERROR; XMEMCPY(buf, id->id, id->length); return WOLFSSL_SUCCESS; } static CIDInfo* DtlsCidGetInfoFromExt(byte* ext) { WOLFSSL** sslPtr; WOLFSSL* ssl; if (ext == NULL) return NULL; sslPtr = (WOLFSSL**)ext; ssl = *sslPtr; if (ssl == NULL) return NULL; return ssl->dtlsCidInfo; } static void DtlsCidUnsetInfoFromExt(byte* ext) { WOLFSSL** sslPtr; WOLFSSL* ssl; if (ext == NULL) return; sslPtr = (WOLFSSL**)ext; ssl = *sslPtr; if (ssl == NULL) return; ssl->dtlsCidInfo = NULL; } void TLSX_ConnectionID_Free(byte* ext, void* heap) { CIDInfo* info; (void)heap; info = DtlsCidGetInfoFromExt(ext); if (info == NULL) return; if (info->rx != NULL) XFREE(info->rx, heap, DYNAMIC_TYPE_TLSX); if (info->tx != NULL) XFREE(info->tx, heap, DYNAMIC_TYPE_TLSX); XFREE(info, heap, DYNAMIC_TYPE_TLSX); DtlsCidUnsetInfoFromExt(ext); XFREE(ext, heap, DYNAMIC_TYPE_TLSX); } word16 TLSX_ConnectionID_Write(byte* ext, byte* output) { CIDInfo* info; info = DtlsCidGetInfoFromExt(ext); if (info == NULL) return 0; /* empty CID */ if (info->rx == NULL) { *output = 0; return OPAQUE8_LEN; } *output = info->rx->length; XMEMCPY(output + OPAQUE8_LEN, info->rx->id, info->rx->length); return OPAQUE8_LEN + info->rx->length; } word16 TLSX_ConnectionID_GetSize(byte* ext) { CIDInfo* info = DtlsCidGetInfoFromExt(ext); if (info == NULL) return 0; return info->rx == NULL ? OPAQUE8_LEN : OPAQUE8_LEN + info->rx->length; } int TLSX_ConnectionID_Use(WOLFSSL* ssl) { CIDInfo* info; WOLFSSL** ext; int ret; ext = (WOLFSSL**)TLSX_Find(ssl->extensions, TLSX_CONNECTION_ID); if (ext != NULL) return 0; info = (CIDInfo*)XMALLOC(sizeof(CIDInfo), ssl->heap, DYNAMIC_TYPE_TLSX); if (info == NULL) return MEMORY_ERROR; ext = (WOLFSSL**)XMALLOC(sizeof(WOLFSSL**), ssl->heap, DYNAMIC_TYPE_TLSX); if (ext == NULL) { XFREE(info, ssl->heap, DYNAMIC_TYPE_TLSX); return MEMORY_ERROR; } XMEMSET(info, 0, sizeof(CIDInfo)); /* CIDInfo needs to be accessed every time we send or receive a record. To * avoid the cost of the extension lookup save a pointer to the structure * inside the SSL object itself, and save a pointer to the SSL object in the * extension. The extension freeing routine uses the pointer to the SSL * object to find the structure and to set ssl->dtlsCidInfo pointer to NULL * after freeing the structure. */ ssl->dtlsCidInfo = info; *ext = ssl; ret = TLSX_Push(&ssl->extensions, TLSX_CONNECTION_ID, (void*)ext, ssl->heap); if (ret != 0) { XFREE(info, ssl->heap, DYNAMIC_TYPE_TLSX); XFREE(ext, ssl->heap, DYNAMIC_TYPE_TLSX); ssl->dtlsCidInfo = NULL; return ret; } return 0; } int TLSX_ConnectionID_Parse(WOLFSSL* ssl, const byte* input, word16 length, byte isRequest) { ConnectionID* id; CIDInfo* info; byte cidSize; TLSX* ext; ext = TLSX_Find(ssl->extensions, TLSX_CONNECTION_ID); if (ext == NULL) { /* CID not enabled */ if (isRequest) { WOLFSSL_MSG("Received CID ext but it's not enabled, ignoring"); return 0; } else { WOLFSSL_MSG("CID ext not requested by the Client, aborting"); return UNSUPPORTED_EXTENSION; } } info = DtlsCidGetInfo(ssl); if (info == NULL) return BAD_STATE_E; /* it may happen if we process two ClientHello because the server sent an * HRR request */ if (info->tx != NULL) { if (ssl->options.side != WOLFSSL_SERVER_END && ssl->options.serverState != SERVER_HELLO_RETRY_REQUEST_COMPLETE) return BAD_STATE_E; XFREE(info->tx, ssl->heap, DYNAMIC_TYPE_TLSX); info->tx = NULL; } if (length < OPAQUE8_LEN) return BUFFER_ERROR; cidSize = *input; if (cidSize + OPAQUE8_LEN > length) return BUFFER_ERROR; if (cidSize > 0) { id = (ConnectionID*)XMALLOC(sizeof(*id) + cidSize, ssl->heap, DYNAMIC_TYPE_TLSX); if (id == NULL) return MEMORY_ERROR; XMEMCPY(id->id, input + OPAQUE8_LEN, cidSize); id->length = cidSize; info->tx = id; } info->negotiated = 1; if (isRequest) ext->resp = 1; return 0; } void DtlsCIDOnExtensionsParsed(WOLFSSL* ssl) { CIDInfo* info; info = DtlsCidGetInfo(ssl); if (info == NULL) return; if (!info->negotiated) { TLSX_Remove(&ssl->extensions, TLSX_CONNECTION_ID, ssl->heap); return; } } byte DtlsCIDCheck(WOLFSSL* ssl, const byte* input, word16 inputSize) { CIDInfo* info; info = DtlsCidGetInfo(ssl); if (info == NULL || info->rx == NULL || info->rx->length == 0) return 0; if (inputSize < info->rx->length) return 0; return XMEMCMP(input, info->rx->id, info->rx->length) == 0; } int wolfSSL_dtls_cid_use(WOLFSSL* ssl) { int ret; /* CID is supported on DTLSv1.3 only */ if (!IsAtLeastTLSv1_3(ssl->version)) return WOLFSSL_FAILURE; ssl->options.useDtlsCID = 1; ret = TLSX_ConnectionID_Use(ssl); if (ret != 0) return ret; return WOLFSSL_SUCCESS; } int wolfSSL_dtls_cid_is_enabled(WOLFSSL* ssl) { return DtlsCidGetInfo(ssl) != NULL; } int wolfSSL_dtls_cid_set(WOLFSSL* ssl, unsigned char* cid, unsigned int size) { ConnectionID* newCid; CIDInfo* cidInfo; if (!ssl->options.useDtlsCID) return WOLFSSL_FAILURE; cidInfo = DtlsCidGetInfo(ssl); if (cidInfo == NULL) return WOLFSSL_FAILURE; if (cidInfo->rx != NULL) { XFREE(cidInfo->rx, ssl->heap, DYNAMIC_TYPE_TLSX); cidInfo->rx = NULL; } /* empty CID */ if (size == 0) return WOLFSSL_SUCCESS; if (size > DTLS_CID_MAX_SIZE) return LENGTH_ERROR; newCid = DtlsCidNew(cid, (byte)size, ssl->heap); if (newCid == NULL) return MEMORY_ERROR; cidInfo->rx = newCid; return WOLFSSL_SUCCESS; } int wolfSSL_dtls_cid_get_rx_size(WOLFSSL* ssl, unsigned int* size) { return DtlsCidGetSize(ssl, size, 1); } int wolfSSL_dtls_cid_get_rx(WOLFSSL* ssl, unsigned char* buf, unsigned int bufferSz) { return DtlsCidGet(ssl, buf, bufferSz, 1); } int wolfSSL_dtls_cid_get_tx_size(WOLFSSL* ssl, unsigned int* size) { return DtlsCidGetSize(ssl, size, 0); } int wolfSSL_dtls_cid_get_tx(WOLFSSL* ssl, unsigned char* buf, unsigned int bufferSz) { return DtlsCidGet(ssl, buf, bufferSz, 0); } #endif /* WOLFSSL_DTLS_CID */ #endif /* WOLFSSL_DTLS */ #endif /* WOLFCRYPT_ONLY */