Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
f74dcb7
Offer the RFC 7027 brainpool groups only when TLS 1.2 or older is rea…
Frauschi Oct 21, 2026
30f6d7f
Downgrade a DTLS 1.3 SSL when TLS 1.3 is masked off
Frauschi Oct 21, 2026
33a8843
Keep the RFC 7027 brainpool groups out of the ECH inner ClientHello
Frauschi Oct 21, 2026
deec546
Address review on DTLS 1.3 downgrade and ECH outer hello
Frauschi Oct 22, 2026
ee8700e
Check the DTLS version mask when the handshake starts
Frauschi Oct 22, 2026
2111f43
Rebuild suites and cover all entry points in the DTLS mask check
Frauschi Oct 22, 2026
772f776
Apply the DTLS version mask in set_options and on every ClientHello
Frauschi Oct 22, 2026
801e5da
Start the DTLS mask walk from the method version when resuming
Frauschi Oct 22, 2026
353b23d
Pick only listed DTLS versions and share the server mask check
Frauschi Oct 22, 2026
873e2d3
Apply the DTLS mask when a session is set, not mid-ClientHello
Frauschi Oct 22, 2026
d328c1b
Add SSL_OP_NO_DTLSv1 and apply DTLS min and max proto versions
Frauschi Oct 23, 2026
85064c2
Hand pre-1.3 DTLS connections to the legacy handshake
Frauschi Oct 23, 2026
39fcfac
Let DTLS max proto and options lift an earlier cap
Frauschi Oct 27, 2026
e113d17
Filter suites and sigalgs for the version from an unfiltered copy
Frauschi Nov 3, 2026
8a5482d
Keep a fixed DTLS method's version under a CTX maximum
Frauschi Nov 10, 2026
0a94fee
Keep the HRR version when a mask changes during ClientHello
Frauschi Nov 11, 2026
564bd95
Keep a fixed TLS method's version under a maximum
Frauschi Nov 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/dox_comments/header_files/ssl.h
Original file line number Diff line number Diff line change
Expand Up @@ -7660,6 +7660,9 @@ long wolfSSL_CTX_set_tlsext_opaque_prf_input_callback_arg(
| SSL_OP_NO_TLSv1_1 | Disable TLS 1.1 |
| SSL_OP_NO_TLSv1_2 | Disable TLS 1.2 |
| SSL_OP_NO_TLSv1_3 | Disable TLS 1.3 (requires WOLFSSL_TLS13) |
| SSL_OP_NO_DTLSv1 | Disable DTLS 1.0; same bit as SSL_OP_NO_TLSv1_1, so it disables TLS 1.1 on a TLS object |
| SSL_OP_NO_DTLSv1_2 | Disable DTLS 1.2; same bit as SSL_OP_NO_TLSv1_2 |
| SSL_OP_NO_DTLSv1_3 | Disable DTLS 1.3 (requires WOLFSSL_DTLS13); same bit as SSL_OP_NO_TLSv1_3 |
| SSL_OP_NO_COMPRESSION | Disable record-layer compression (no-op unless HAVE_LIBZ) |
| SSL_OP_NO_TICKET | Disable RFC 5077 session tickets (TLS 1.2 only; TLS 1.3 ignores this flag); requires HAVE_SESSION_TICKET and (OPENSSL_EXTRA or HAVE_WEBSERVER or WOLFSSL_WPAS_SMALL) |
| SSL_OP_NO_RENEGOTIATION | Reject peer-initiated renegotiation |
Expand Down
155 changes: 143 additions & 12 deletions src/internal.c
Original file line number Diff line number Diff line change
Expand Up @@ -2818,10 +2818,6 @@ void InitSSL_Method(WOLFSSL_METHOD* method, ProtocolVersion pv)
method->downgrade = 0;
}

#if defined(WOLFSSL_DTLS) && !defined(NO_WOLFSSL_SERVER)
static int InitSSL_DtlsServer(WOLFSSL* ssl);
#endif /* WOLFSSL_DTLS && !NO_WOLFSSL_SERVER */

#if defined(OPENSSL_EXTRA) || defined(WOLFSSL_EITHER_SIDE) || \
defined(WOLFSSL_EXTRA) || defined(WOLFSSL_WPAS_SMALL)
int InitSSL_Side(WOLFSSL* ssl, word16 side)
Expand Down Expand Up @@ -8050,7 +8046,7 @@ int wolfSSL_CTX_IsPrivatePkSet(WOLFSSL_CTX* ctx)
}
#endif /* HAVE_PK_CALLBACKS */

static void InitSuites_EitherSide(Suites* suites, ProtocolVersion pv, int keySz,
void InitSuites_EitherSide(Suites* suites, ProtocolVersion pv, int keySz,
word16 haveRSA, word16 havePSK, word16 haveDH, word16 haveECDSAsig,
word16 haveECC, word16 haveStaticECC,
word16 haveAnon,
Expand Down Expand Up @@ -8094,6 +8090,20 @@ void InitSSL_CTX_Suites(const WOLFSSL_CTX* ctx, Suites* suites)
haveAnon, ctx->method->side);
}

/* Returns the newest version a downgrade SSL can be moved back up to. DTLS
* minors count down. */
ProtocolVersion SslNewestVersion(const WOLFSSL* ssl)
{
ProtocolVersion pv = ssl->version;
byte methodMinor = ssl->ctx->method->version.minor;

if (ssl->options.downgrade && (ssl->options.dtls ?
methodMinor < pv.minor : methodMinor > pv.minor)) {
pv = ssl->ctx->method->version;
}
return pv;
}

int InitSSL_Suites(WOLFSSL* ssl)
{
int keySz = 0;
Expand Down Expand Up @@ -8152,6 +8162,8 @@ int InitSSL_Suites(WOLFSSL* ssl)
havePSK, ssl->options.haveDH, ssl->options.haveECDSAsig,
ssl->options.haveECC, ssl->options.haveStaticECC,
ssl->options.useAnon, ssl->options.side);
if (FilterSuitesForVersion(ssl) != 0)
return MEMORY_E;
}

#if !defined(NO_CERTS) && !defined(WOLFSSL_SESSION_EXPORT)
Expand Down Expand Up @@ -8200,6 +8212,61 @@ int InitSSL_Suites(WOLFSSL* ssl)
return WOLFSSL_SUCCESS;
}

#ifdef WOLFSSL_DTLS
/* Maps a TLS minimum to the DTLS version that shares its mask bit. */
byte DtlsMinDowngrade(byte minDowngrade)
{
if (minDowngrade == TLSv1_3_MINOR)
return DTLSv1_3_MINOR;
if (minDowngrade == TLSv1_2_MINOR)
return DTLSv1_2_MINOR;
if (minDowngrade < DTLSv1_3_MINOR)
return DTLS_MINOR;
return minDowngrade;
}

/* Returns the newest built DTLS minor, no newer than maxMinor, that the version
* mask and minDowngrade allow, or 0. DTLS minors count down. */
byte DtlsMaskAllowedMinor(unsigned long mask, byte maxMinor, byte minDowngrade)
{
#ifdef WOLFSSL_DTLS13
if (maxMinor <= DTLSv1_3_MINOR &&
(mask & WOLFSSL_OP_NO_TLSv1_3) == 0) {
return DTLSv1_3_MINOR;
}
#endif
#ifndef WOLFSSL_NO_TLS12
if (maxMinor <= DTLSv1_2_MINOR &&
(mask & WOLFSSL_OP_NO_TLSv1_2) == 0 &&
minDowngrade >= DTLSv1_2_MINOR) {
return DTLSv1_2_MINOR;
}
#endif
#ifndef NO_OLD_TLS
if ((mask & WOLFSSL_OP_NO_TLSv1_1) == 0 &&
minDowngrade >= DTLS_MINOR) {
return DTLS_MINOR;
}
#endif
(void)mask;
(void)maxMinor;
(void)minDowngrade;
return 0;
}

/* DtlsMaskAllowedMinor() with the SSL's own version mask. */
byte DtlsAllowedMinor(const WOLFSSL* ssl, byte maxMinor, byte minDowngrade)
{
#if defined(OPENSSL_EXTRA) || defined(HAVE_WEBSERVER) || \
defined(WOLFSSL_WPAS_SMALL)
return DtlsMaskAllowedMinor(ssl->options.mask, maxMinor, minDowngrade);
#else
(void)ssl;
return DtlsMaskAllowedMinor(0, maxMinor, minDowngrade);
#endif
}
#endif

/* This function inherits a WOLFSSL_CTX's fields into an SSL object.
It is used during initialization and to switch an ssl's CTX with
wolfSSL_Set_SSL_CTX. Requires ssl->suites alloc and ssl-arrays with PSK
Expand Down Expand Up @@ -8228,6 +8295,25 @@ static int SetSSL_CTX_CheckVersion(WOLFSSL* ssl, WOLFSSL_CTX* ctx)
ssl->version.minor = TLSv1_2_MINOR;
}
#endif
#ifdef WOLFSSL_DTLS13
if (ssl->version.major == DTLS_MAJOR &&
ssl->version.minor == DTLSv1_3_MINOR &&
(ssl->options.mask & WOLFSSL_OP_NO_TLSv1_3) ==
WOLFSSL_OP_NO_TLSv1_3) {
byte minor = 0;

if (ctx->method->downgrade)
minor = DtlsAllowedMinor(ssl, DTLSv1_2_MINOR, ctx->minDowngrade);
if (minor == 0) {
WOLFSSL_MSG("\tInconsistent protocol options. DTLS 1.3 set but "
"not allowed and no lower DTLS version allowed.");
WOLFSSL_ERROR_VERBOSE(VERSION_ERROR);
return VERSION_ERROR;
}
WOLFSSL_MSG("\tOption set to not allow DTLSv1.3, Downgrading");
ssl->version.minor = minor;
}
#endif
if (ssl->version.minor == TLSv1_2_MINOR &&
(ssl->options.mask & WOLFSSL_OP_NO_TLSv1_2) == WOLFSSL_OP_NO_TLSv1_2) {
Comment on lines +8298 to 8318

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Openssl doesn't use SSL_OP_NO_TLSv1_3 for DTLS. Why are we allowing this?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OpenSSL does now. 4.1, the first release with DTLS 1.3, defines SSL_OP_NO_DTLSv1_3 as SSL_OP_BIT(29), the same bit as SSL_OP_NO_TLSv1_3, and uses it as the mask of the DTLS 1.3 methods (ssl/methods.c). DTLS_method() picks its version in ssl_get_min_max_version() when the handshake starts, skipping masked versions, so SSL_OP_NO_DTLSv1_3 gives DTLS 1.2 there.

wolfSSL master already did half of this: TLSX_SupportedVersions_GetSize() drops DTLS 1.3 from supported_versions under WOLFSSL_OP_NO_TLSv1_3, but the SSL stayed on DTLS 1.3 and still sent a TLS 1.3 key_share and the TLS 1.3-only groups. The PR now follows OpenSSL. wolfSSL_set_options() and wolfSSL_set_session() move the version straight away. wolfSSL_connect()/wolfSSL_accept() and the _TLSv13 entry points check the accumulated mask again when the handshake starts (DtlsCheckVersionMask()), so the order of the calls doesn't matter. On the server, the supported_versions parse only picks versions the mask allows. I also added SSL_OP_NO_DTLSv1_2 and SSL_OP_NO_DTLSv1_3, which use the same bits as in OpenSSL. I left out SSL_OP_NO_DTLSv1: OpenSSL defines it as the SSL_OP_NO_TLSv1 bit, while wolfSSL masks DTLS 1.0 with WOLFSSL_OP_NO_TLSv1_1, so the alias would behave differently.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, the OpenSSL 4.1 behavior checks out.

On SSL_OP_NO_DTLSv1: WOLFSSL_OP_NO_TLSv1_1 is 0x04000000, bit 26. That’s the same value OpenSSL uses for SSL_OP_NO_DTLSv1. So #define SSL_OP_NO_DTLSv1 WOLFSSL_OP_NO_TLSv1_1 would match OpenSSL’s value and mask DTLS 1.0 the way wolfSSL does. The only difference is on a TLS context, where it would mask TLS 1.1 rather than TLS 1.0. Code that sets a DTLS option on a TLS context should be rare.

Without the alias, code ported from OpenSSL that uses SSL_OP_NO_DTLSv1 won’t compile. I’d add the alias and document the TLS-context difference. What do you think?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, I had the bits wrong. wolfSSL's WOLFSSL_OP_NO_TLSv1_1 is OpenSSL's SSL_OP_NO_TLSv1/SSL_OP_NO_DTLSv1 value, so the alias matches OpenSSL's value and wolfSSL's DTLS 1.0 masking. Added SSL_OP_NO_DTLSv1 as WOLFSSL_OP_NO_TLSv1_1, and the wolfSSL_CTX_set_options() doc table now lists the three DTLS options, with the TLS 1.1 effect on a TLS object called out. test_dtls13_no_tls13_option uses the new name.

if (!ctx->method->downgrade) {
Expand Down Expand Up @@ -9215,7 +9301,7 @@ static void InitSSL_Tls13Options(WOLFSSL* ssl, WOLFSSL_CTX* ctx)
* @return 0 on success, and when there is nothing to do.
* @return A negative error code when a secret cannot be generated.
*/
static int InitSSL_DtlsServer(WOLFSSL* ssl)
int InitSSL_DtlsServer(WOLFSSL* ssl)
{
int ret;

Expand Down Expand Up @@ -10435,6 +10521,8 @@ void FreeSuites(WOLFSSL* ssl)
#endif
XFREE(ssl->suites, ssl->heap, DYNAMIC_TYPE_SUITES);
ssl->suites = NULL;
XFREE(ssl->suitesUser, ssl->heap, DYNAMIC_TYPE_SUITES);
ssl->suitesUser = NULL;
}


Expand Down Expand Up @@ -34872,7 +34960,8 @@ int SetCipherList_ex(const WOLFSSL_CTX* ctx, const WOLFSSL* ssl,
side = ctx->method->side;
}
else if (ssl != NULL) {
version = ssl->version;
/* A later lift needs the suites of the newer version in the list. */
version = SslNewestVersion(ssl);
#ifndef NO_CERTS
privateKeySz = ssl->buffers.keySz;
#endif
Expand Down Expand Up @@ -35154,9 +35243,7 @@ int SetSuitesHashSigAlgo(Suites* suites, const char* list)
const char* s = list;
byte sig_alg = 0;
byte mac_alg = no_mac;

/* Setting is destructive on error. */
suites->hashSigAlgoSz = 0;
byte hashSigAlgo[WOLFSSL_MAX_SIGALGO];

do {
if (*list == '+') {
Expand Down Expand Up @@ -35196,7 +35283,7 @@ int SetSuitesHashSigAlgo(Suites* suites, const char* list)
break;
}
}
AddSuiteHashSigAlgo(suites->hashSigAlgo, mac_alg, sig_alg, 0, &idx);
AddSuiteHashSigAlgo(hashSigAlgo, mac_alg, sig_alg, 0, &idx);
sig_alg = 0;
mac_alg = no_mac;
s = list + 1;
Expand All @@ -35209,7 +35296,8 @@ int SetSuitesHashSigAlgo(Suites* suites, const char* list)
if (s != list && (sig_alg != 0 || mac_alg != 0)) {
ret = 0;
}
else {
if (ret == 1) {
XMEMCPY(suites->hashSigAlgo, hashSigAlgo, idx);
suites->hashSigAlgoSz = idx;
}

Expand Down Expand Up @@ -37558,6 +37646,17 @@ static int IsTls13CipherSuite(byte first, byte second)
}
}

#ifdef WOLFSSL_DTLS
if (ssl->options.dtls && ssl->options.downgrade &&
!ssl->options.handShakeDone &&
DtlsAllowedMinor(ssl, ssl->version.minor, DTLS_MINOR) !=
ssl->version.minor) {
WOLFSSL_MSG("\tOption set to not allow this DTLS version");
WOLFSSL_ERROR_VERBOSE(VERSION_ERROR);
return VERSION_ERROR;
}
#endif

/* check if option is set to not allow the current version
* set from either wolfSSL_set_options or wolfSSL_CTX_set_options */
if (!ssl->options.dtls && ssl->options.downgrade &&
Expand Down Expand Up @@ -37656,6 +37755,24 @@ static int IsTls13CipherSuite(byte first, byte second)
}
#endif

#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH)
if (ssl->options.echAccepted &&
ssl->msgsReceived.got_hello_retry_request) {
WOLFSSL_MSG("ECH accepted in HelloRetryRequest, ServerHello is "
"not TLS 1.3");
WOLFSSL_ERROR_VERBOSE(INVALID_PARAMETER);
if (SendAlert(ssl, alert_fatal, illegal_parameter) ==
WC_NO_ERR_TRACE(SOCKET_ERROR_E)) {
return SOCKET_ERROR_E;
}
return INVALID_PARAMETER;
}
/* A server negotiating TLS 1.2 or older answers the outer hello. */
ret = TLSX_EchReplaceExtensions(ssl, 0);
if (ret != 0)
return ret;
#endif

/* random */
XMEMCPY(ssl->arrays->serverRandom, input + i, RAN_LEN);
i += RAN_LEN;
Expand Down Expand Up @@ -44464,6 +44581,20 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)
TRUE, TRUE, TRUE, TRUE, ssl->options.side);
}

#ifdef WOLFSSL_DTLS
ret = DtlsCheckVersionMask(ssl, DTLS_MASK_IN_HELLO);
if (ret == WC_NO_ERR_TRACE(VERSION_ERROR)) {
WOLFSSL_ERROR_VERBOSE(VERSION_ERROR);
/* propagate socket errors to avoid re-calling send alert */
if (SendAlert(ssl, alert_fatal, wolfssl_alert_protocol_version)
== WC_NO_ERR_TRACE(SOCKET_ERROR_E)) {
ret = SOCKET_ERROR_E;
}
}
if (ret != 0)
goto out;
#endif

/* Version the record layer is on before the mask walk below steps it
* down. It is one the client offered (or the server's own maximum
* when the client offered more), so the two alert exits inside that
Expand Down
Loading
Loading