mirror of
https://github.com/wolfSSL/wolfssl.git
synced 2026-07-08 08:10:48 +02:00
+142
-2
@@ -29404,6 +29404,93 @@ int GetCipherSuiteFromName(const char* name, byte* cipherSuite0,
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if defined(OPENSSL_EXTRA) || defined(OPENSSL_ALL)
|
||||
/* Classify a cipher suite for OpenSSL-style "!aNULL"/"!eNULL" exclusion.
|
||||
* anon != 0: match suites that perform NO peer authentication.
|
||||
* enull != 0: match suites that perform NO encryption.
|
||||
* Classification is by suite name: the RFC/IANA name when compiled in
|
||||
* (anonymous suites contain "_anon_", named NULL-cipher suites contain "_NULL"
|
||||
* - e.g. TLS_DH_anon_*, TLS_ECDH_anon_*, TLS_*_WITH_NULL_*) and the OpenSSL
|
||||
* short name (ADH/AECDH/...NULL...), which is the only name available under
|
||||
* NO_ERROR_STRINGS. The TLS 1.3 integrity-only suites (RFC 9150) are
|
||||
* NULL-encryption too but carry no "NULL" token in their names, so they are
|
||||
* matched by suite id below. */
|
||||
static int CipherSuiteExcluded(byte first, byte second, int anon, int enull)
|
||||
{
|
||||
int i;
|
||||
const int sz = GetCipherNamesSize();
|
||||
|
||||
#ifdef HAVE_NULL_CIPHER
|
||||
/* TLS 1.3 integrity-only suites perform no encryption; wolfSSL keys them
|
||||
* off haveNull in InitSuites(), so honor "!eNULL" for them here too. */
|
||||
if (enull && (first == ECC_BYTE) &&
|
||||
((second == TLS_SHA256_SHA256) || (second == TLS_SHA384_SHA384))) {
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
for (i = 0; i < sz; i++) {
|
||||
if ((cipher_names[i].cipherSuite0 != first) ||
|
||||
(cipher_names[i].cipherSuite != second)) {
|
||||
continue;
|
||||
}
|
||||
#ifndef NO_CIPHER_SUITE_ALIASES
|
||||
if (cipher_names[i].flags & WOLFSSL_CIPHER_SUITE_FLAG_NAMEALIAS) {
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
#ifndef NO_ERROR_STRINGS
|
||||
if (cipher_names[i].name_iana != NULL) {
|
||||
if (anon &&
|
||||
(XSTRSTR(cipher_names[i].name_iana, "_anon_") != NULL)) {
|
||||
return 1;
|
||||
}
|
||||
if (enull &&
|
||||
(XSTRSTR(cipher_names[i].name_iana, "_NULL") != NULL)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (cipher_names[i].name != NULL) {
|
||||
if (anon && ((XSTRSTR(cipher_names[i].name, "ADH") != NULL) ||
|
||||
(XSTRSTR(cipher_names[i].name, "AECDH") != NULL))) {
|
||||
return 1;
|
||||
}
|
||||
if (enull && (XSTRSTR(cipher_names[i].name, "NULL") != NULL)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
break; /* found the suite; not excluded */
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Drop suites matching an exclusion from the explicit user-suite array
|
||||
* suites[0..*idx). Used to honor an OpenSSL-style exclusion ("!aNULL",
|
||||
* "!eNULL"/"!NULL", and the "!aNULL:!eNULL" embedded by DEFAULT): the keyword
|
||||
* only adjusts the InitSuites mask, so without this an explicitly listed
|
||||
* anonymous or NULL-cipher suite would survive (anonymous = no peer
|
||||
* authentication; NULL = no confidentiality). Compacts in place; *idx is a
|
||||
* byte count of 2-byte suite pairs. */
|
||||
static void RemoveExcludedSuites(byte* suites, int* idx, int anon, int enull)
|
||||
{
|
||||
int i;
|
||||
int out = 0;
|
||||
|
||||
for (i = 0; (i + 1) < *idx; i += 2) {
|
||||
if (CipherSuiteExcluded(suites[i], suites[i + 1], anon, enull)) {
|
||||
continue;
|
||||
}
|
||||
if (out != i) {
|
||||
suites[out + 0] = suites[i + 0];
|
||||
suites[out + 1] = suites[i + 1];
|
||||
}
|
||||
out += 2;
|
||||
}
|
||||
*idx = out;
|
||||
}
|
||||
#endif /* OPENSSL_EXTRA || OPENSSL_ALL */
|
||||
|
||||
/**
|
||||
Set the enabled cipher suites.
|
||||
|
||||
@@ -29436,6 +29523,8 @@ static int ParseCipherList(Suites* suites,
|
||||
word16 haveAES128 = 1; /* allowed by default if compiled in */
|
||||
word16 haveSHA1 = 1; /* allowed by default if compiled in */
|
||||
word16 haveRC4 = 1; /* allowed by default if compiled in */
|
||||
int excludeAnon = 0; /* "!aNULL"/DEFAULT seen (applied at end) */
|
||||
int excludeNull = 0; /* "!eNULL"/"!NULL"/DEFAULT seen */
|
||||
#endif
|
||||
int tls1_3 = 0;
|
||||
const int suiteSz = GetCipherNamesSize();
|
||||
@@ -29540,10 +29629,19 @@ static int ParseCipherList(Suites* suites,
|
||||
}
|
||||
|
||||
if (XSTRCMP(name, "DEFAULT") == 0 || XSTRCMP(name, "ALL") == 0) {
|
||||
if (XSTRCMP(name, "ALL") == 0)
|
||||
if (XSTRCMP(name, "ALL") == 0) {
|
||||
/* OpenSSL "ALL" includes anonymous suites and is "all but
|
||||
* eNULL" (haveNull=0 below stops eNULL generation), but it is
|
||||
* not a delete directive: unlike DEFAULT it must not set
|
||||
* excludeAnon/excludeNull, nor clear a prior "!aNULL"/"!eNULL". */
|
||||
haveSig |= SIG_ANON;
|
||||
else
|
||||
}
|
||||
else {
|
||||
/* OpenSSL "DEFAULT" embeds "!aNULL:!eNULL" (delete both). */
|
||||
haveSig &= ~SIG_ANON;
|
||||
excludeAnon = 1;
|
||||
excludeNull = 1;
|
||||
}
|
||||
haveRSA = 1;
|
||||
haveDH = 1;
|
||||
haveECC = 1;
|
||||
@@ -29585,6 +29683,11 @@ static int ParseCipherList(Suites* suites,
|
||||
haveSig |= SIG_ANON;
|
||||
else
|
||||
haveSig &= ~SIG_ANON;
|
||||
/* Track exclusion (sticky) so an explicit ADH suite is dropped at
|
||||
* the end regardless of where "!aNULL" sits in the list; a later
|
||||
* allowing "aNULL" does not undo it (OpenSSL "!" is permanent). */
|
||||
if (!allowing)
|
||||
excludeAnon = 1;
|
||||
if (allowing) {
|
||||
/* Allow RSA by default. */
|
||||
if (!haveECC)
|
||||
@@ -29599,6 +29702,11 @@ static int ParseCipherList(Suites* suites,
|
||||
|
||||
if (XSTRCMP(name, "eNULL") == 0 || XSTRCMP(name, "NULL") == 0) {
|
||||
haveNull = allowing;
|
||||
/* Track exclusion (sticky) so an explicit NULL-cipher suite is
|
||||
* dropped at the end regardless of "!eNULL"/"!NULL" position; a
|
||||
* later allowing "eNULL" does not undo it. */
|
||||
if (!allowing)
|
||||
excludeNull = 1;
|
||||
if (allowing) {
|
||||
/* Allow RSA by default. */
|
||||
if (!haveECC)
|
||||
@@ -29841,6 +29949,38 @@ static int ParseCipherList(Suites* suites,
|
||||
}
|
||||
} while (next);
|
||||
|
||||
#if defined(OPENSSL_EXTRA) || defined(OPENSSL_ALL)
|
||||
/* Apply OpenSSL-style category exclusions to the explicitly-listed suites.
|
||||
* Done here, after the whole list is parsed, so list order does not matter:
|
||||
* the "!aNULL"/"!eNULL" keywords above only adjust the InitSuites mask
|
||||
* (which governs generated defaults), never the suites the user named
|
||||
* explicitly. The excludeAnon/excludeNull flags are set-only / sticky: like
|
||||
* OpenSSL's permanent "!", once "!aNULL"/"!eNULL" has excluded a category a
|
||||
* later allowing keyword does not re-enable it - fail closed. */
|
||||
if (excludeAnon) {
|
||||
haveSig &= ~SIG_ANON;
|
||||
RemoveExcludedSuites(suites->suites, &idx, 1, 0);
|
||||
}
|
||||
if (excludeNull) {
|
||||
haveNull = 0;
|
||||
RemoveExcludedSuites(suites->suites, &idx, 0, 1);
|
||||
}
|
||||
/* If the user named suites but every one was excluded, fail like OpenSSL
|
||||
* (empty list) rather than silently returning an unusable suite set.
|
||||
* callInitSuites means defaults were also requested, so an empty explicit
|
||||
* part is fine there. */
|
||||
if (ret && !callInitSuites && idx == 0) {
|
||||
/* Commit a fully-consistent empty state: suiteSz 0 with setSuites set
|
||||
* makes InitSuites() leave it empty (it early-outs on setSuites), so
|
||||
* this fails closed - no suites offered, no default regeneration. */
|
||||
suites->suiteSz = 0;
|
||||
suites->hashSigAlgoSz = 0;
|
||||
suites->setSuites = 1;
|
||||
WOLFSSL_MSG("Cipher list empty after !aNULL/!eNULL exclusions");
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (ret) {
|
||||
int keySz = 0;
|
||||
#ifndef NO_CERTS
|
||||
|
||||
+229
@@ -2314,6 +2314,208 @@ static int test_wolfSSL_set_cipher_list_tls13_with_version(void)
|
||||
return EXPECT_RESULT();
|
||||
}
|
||||
|
||||
/* Build gating for the cipher-list exclusion test. Each sub-test is gated only
|
||||
* on the BUILD_* macro of the suite it needs, so it cleanly skips (rather than
|
||||
* failing at runtime) in any build missing it - and the two are independent
|
||||
* (BUILD_TLS_DH_anon_* already implies HAVE_ANON;
|
||||
* BUILD_TLS_ECDHE_ECDSA_WITH_NULL_SHA already implies HAVE_NULL_CIPHER). The
|
||||
* "survivor" suite used only by the mixed (scrub-is-surgical) sub-cases is
|
||||
* gated separately, so the core exclusion cases still run without it. */
|
||||
#if defined(OPENSSL_EXTRA) && !defined(NO_WOLFSSL_CLIENT) && \
|
||||
!defined(WOLFSSL_NO_TLS12)
|
||||
#if defined(BUILD_TLS_DH_anon_WITH_AES_128_CBC_SHA)
|
||||
#define TEST_CIPHER_EXCLUDE_ANON
|
||||
#endif
|
||||
#if defined(BUILD_TLS_ECDHE_ECDSA_WITH_NULL_SHA)
|
||||
#define TEST_CIPHER_EXCLUDE_NULL
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(TEST_CIPHER_EXCLUDE_ANON) || defined(TEST_CIPHER_EXCLUDE_NULL)
|
||||
/* Does the parsed suite list on ssl contain the given suite bytes? */
|
||||
static int test_suites_contains(WOLFSSL* ssl, byte s0, byte s1)
|
||||
{
|
||||
int i;
|
||||
if (ssl == NULL || ssl->suites == NULL)
|
||||
return 0;
|
||||
for (i = 0; (i + 1) < ssl->suites->suiteSz; i += 2) {
|
||||
if ((ssl->suites->suites[i] == s0) &&
|
||||
(ssl->suites->suites[i + 1] == s1))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* OpenSSL-style "!aNULL"/"!eNULL" must remove an explicitly-listed anonymous
|
||||
* (ADH) or NULL-cipher suite from the parsed list, not merely clear the
|
||||
* InitSuites mask - otherwise a list like "ADH-AES128-SHA:!aNULL" still
|
||||
* negotiates an unauthenticated (anonymous) suite, defeating the operator's
|
||||
* intent. Each sub-case uses a fresh WOLFSSL so state cannot bleed across
|
||||
* calls. */
|
||||
static int test_wolfSSL_set_cipher_list_exclusions(void)
|
||||
{
|
||||
EXPECT_DECLS;
|
||||
#if defined(TEST_CIPHER_EXCLUDE_ANON) || defined(TEST_CIPHER_EXCLUDE_NULL)
|
||||
WOLFSSL_CTX* ctx = NULL;
|
||||
WOLFSSL* ssl = NULL;
|
||||
|
||||
ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method()));
|
||||
|
||||
#ifdef TEST_CIPHER_EXCLUDE_ANON
|
||||
/* Control: the explicit ADH suite IS recognized and added on its own (so
|
||||
* the later "absent" checks cannot pass trivially). */
|
||||
ExpectNotNull(ssl = wolfSSL_new(ctx));
|
||||
ExpectIntEQ(wolfSSL_set_cipher_list(ssl, "ADH-AES128-SHA"),
|
||||
WOLFSSL_SUCCESS);
|
||||
ExpectIntEQ(test_suites_contains(ssl, CIPHER_BYTE,
|
||||
TLS_DH_anon_WITH_AES_128_CBC_SHA), 1);
|
||||
wolfSSL_free(ssl);
|
||||
ssl = NULL;
|
||||
|
||||
/* Reported case: "!aNULL" after the only (anon) suite empties the list,
|
||||
* which fails like OpenSSL - and leaves no anonymous suite behind. */
|
||||
ExpectNotNull(ssl = wolfSSL_new(ctx));
|
||||
ExpectIntEQ(wolfSSL_set_cipher_list(ssl, "ADH-AES128-SHA:!aNULL"),
|
||||
WOLFSSL_FAILURE);
|
||||
ExpectIntEQ(test_suites_contains(ssl, CIPHER_BYTE,
|
||||
TLS_DH_anon_WITH_AES_128_CBC_SHA), 0);
|
||||
wolfSSL_free(ssl);
|
||||
ssl = NULL;
|
||||
|
||||
/* Reverse order: "!aNULL" before the suite must also block it. */
|
||||
ExpectNotNull(ssl = wolfSSL_new(ctx));
|
||||
ExpectIntEQ(wolfSSL_set_cipher_list(ssl, "!aNULL:ADH-AES128-SHA"),
|
||||
WOLFSSL_FAILURE);
|
||||
ExpectIntEQ(test_suites_contains(ssl, CIPHER_BYTE,
|
||||
TLS_DH_anon_WITH_AES_128_CBC_SHA), 0);
|
||||
wolfSSL_free(ssl);
|
||||
ssl = NULL;
|
||||
|
||||
/* Report PoC #3: the suite named by its IANA name must also be dropped
|
||||
* (classification is by suite bytes, independent of the input name form). */
|
||||
ExpectNotNull(ssl = wolfSSL_new(ctx));
|
||||
ExpectIntEQ(wolfSSL_set_cipher_list(ssl,
|
||||
"TLS_DH_anon_WITH_AES_128_CBC_SHA:!aNULL"), WOLFSSL_FAILURE);
|
||||
ExpectIntEQ(test_suites_contains(ssl, CIPHER_BYTE,
|
||||
TLS_DH_anon_WITH_AES_128_CBC_SHA), 0);
|
||||
wolfSSL_free(ssl);
|
||||
ssl = NULL;
|
||||
|
||||
#ifdef BUILD_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
|
||||
/* Mixed list: the authenticated suite survives, only ADH is removed. */
|
||||
ExpectNotNull(ssl = wolfSSL_new(ctx));
|
||||
ExpectIntEQ(wolfSSL_set_cipher_list(ssl,
|
||||
"ECDHE-RSA-AES128-GCM-SHA256:ADH-AES128-SHA:!aNULL"),
|
||||
WOLFSSL_SUCCESS);
|
||||
ExpectIntEQ(test_suites_contains(ssl, CIPHER_BYTE,
|
||||
TLS_DH_anon_WITH_AES_128_CBC_SHA), 0);
|
||||
ExpectIntEQ(test_suites_contains(ssl, ECC_BYTE,
|
||||
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256), 1);
|
||||
wolfSSL_free(ssl);
|
||||
ssl = NULL;
|
||||
#endif /* BUILD_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 */
|
||||
|
||||
/* "DEFAULT" embeds "!aNULL", so an explicit ADH before it is removed. */
|
||||
ExpectNotNull(ssl = wolfSSL_new(ctx));
|
||||
ExpectIntEQ(wolfSSL_set_cipher_list(ssl, "ADH-AES128-SHA:DEFAULT"),
|
||||
WOLFSSL_SUCCESS);
|
||||
ExpectIntEQ(test_suites_contains(ssl, CIPHER_BYTE,
|
||||
TLS_DH_anon_WITH_AES_128_CBC_SHA), 0);
|
||||
wolfSSL_free(ssl);
|
||||
ssl = NULL;
|
||||
|
||||
/* Sticky/fail-closed: once "!aNULL" excludes anon, a later allowing form
|
||||
* ("ALL" includes anon defaults) must NOT bring it back, matching
|
||||
* OpenSSL's permanent "!". The generated default list must contain no ADH. */
|
||||
ExpectNotNull(ssl = wolfSSL_new(ctx));
|
||||
ExpectIntEQ(wolfSSL_set_cipher_list(ssl, "!aNULL:ALL"), WOLFSSL_SUCCESS);
|
||||
ExpectIntEQ(test_suites_contains(ssl, CIPHER_BYTE,
|
||||
TLS_DH_anon_WITH_AES_128_CBC_SHA), 0);
|
||||
wolfSSL_free(ssl);
|
||||
ssl = NULL;
|
||||
#endif /* TEST_CIPHER_EXCLUDE_ANON */
|
||||
|
||||
#ifdef TEST_CIPHER_EXCLUDE_NULL
|
||||
/* eNULL: a NULL-cipher suite must likewise be removed by "!eNULL". This
|
||||
* runs independently of HAVE_ANON, in any NULL-cipher-enabled build. */
|
||||
ExpectNotNull(ssl = wolfSSL_new(ctx));
|
||||
ExpectIntEQ(wolfSSL_set_cipher_list(ssl, "ECDHE-ECDSA-NULL-SHA"),
|
||||
WOLFSSL_SUCCESS);
|
||||
ExpectIntEQ(test_suites_contains(ssl, ECC_BYTE,
|
||||
TLS_ECDHE_ECDSA_WITH_NULL_SHA), 1); /* control: recognized */
|
||||
wolfSSL_free(ssl);
|
||||
ssl = NULL;
|
||||
|
||||
/* "!eNULL" after the only (NULL-cipher) suite empties the list and fails,
|
||||
* leaving no NULL-cipher suite behind (no survivor suite needed). */
|
||||
ExpectNotNull(ssl = wolfSSL_new(ctx));
|
||||
ExpectIntEQ(wolfSSL_set_cipher_list(ssl, "ECDHE-ECDSA-NULL-SHA:!eNULL"),
|
||||
WOLFSSL_FAILURE);
|
||||
ExpectIntEQ(test_suites_contains(ssl, ECC_BYTE,
|
||||
TLS_ECDHE_ECDSA_WITH_NULL_SHA), 0);
|
||||
wolfSSL_free(ssl);
|
||||
ssl = NULL;
|
||||
|
||||
#ifdef BUILD_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
|
||||
/* Mixed list: the survivor stays, only the NULL-cipher suite is removed. */
|
||||
ExpectNotNull(ssl = wolfSSL_new(ctx));
|
||||
ExpectIntEQ(wolfSSL_set_cipher_list(ssl,
|
||||
"ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-NULL-SHA:!eNULL"),
|
||||
WOLFSSL_SUCCESS);
|
||||
ExpectIntEQ(test_suites_contains(ssl, ECC_BYTE,
|
||||
TLS_ECDHE_ECDSA_WITH_NULL_SHA), 0); /* NULL cipher dropped */
|
||||
ExpectIntEQ(test_suites_contains(ssl, ECC_BYTE,
|
||||
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256), 1); /* survivor kept */
|
||||
wolfSSL_free(ssl);
|
||||
ssl = NULL;
|
||||
|
||||
#ifdef BUILD_TLS_SHA256_SHA256
|
||||
/* TLS 1.3 integrity-only suite (RFC 9150) has no "NULL" in its name but is
|
||||
* NULL-encryption; "!eNULL" must still drop it (matched by suite id). */
|
||||
ExpectNotNull(ssl = wolfSSL_new(ctx));
|
||||
ExpectIntEQ(wolfSSL_set_cipher_list(ssl,
|
||||
"ECDHE-RSA-AES128-GCM-SHA256:TLS13-SHA256-SHA256:!eNULL"),
|
||||
WOLFSSL_SUCCESS);
|
||||
ExpectIntEQ(test_suites_contains(ssl, ECC_BYTE,
|
||||
TLS_SHA256_SHA256), 0); /* integrity-only suite dropped */
|
||||
ExpectIntEQ(test_suites_contains(ssl, ECC_BYTE,
|
||||
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256), 1); /* survivor kept */
|
||||
wolfSSL_free(ssl);
|
||||
ssl = NULL;
|
||||
|
||||
/* OpenSSL compat: "ALL" is "all but eNULL" - it does not generate NULL
|
||||
* suites, but it is not a delete directive, so a following "eNULL"
|
||||
* re-enables them. (The earlier sticky excludeNull-for-ALL wrongly kept
|
||||
* them disabled; only "!eNULL"/"DEFAULT" should.) */
|
||||
ExpectNotNull(ssl = wolfSSL_new(ctx));
|
||||
ExpectIntEQ(wolfSSL_set_cipher_list(ssl, "ALL"), WOLFSSL_SUCCESS);
|
||||
ExpectIntEQ(test_suites_contains(ssl, ECC_BYTE,
|
||||
TLS_SHA256_SHA256), 0); /* ALL alone: no NULL generated */
|
||||
wolfSSL_free(ssl);
|
||||
ssl = NULL;
|
||||
|
||||
ExpectNotNull(ssl = wolfSSL_new(ctx));
|
||||
ExpectIntEQ(wolfSSL_set_cipher_list(ssl, "ALL:eNULL"), WOLFSSL_SUCCESS);
|
||||
ExpectIntEQ(test_suites_contains(ssl, ECC_BYTE,
|
||||
TLS_SHA256_SHA256), 1); /* "eNULL" after "ALL" re-enables NULL */
|
||||
wolfSSL_free(ssl);
|
||||
ssl = NULL;
|
||||
#endif /* BUILD_TLS_SHA256_SHA256 */
|
||||
#endif /* BUILD_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 */
|
||||
#endif /* TEST_CIPHER_EXCLUDE_NULL */
|
||||
|
||||
wolfSSL_CTX_free(ctx);
|
||||
#endif /* TEST_CIPHER_EXCLUDE_ANON || TEST_CIPHER_EXCLUDE_NULL */
|
||||
return EXPECT_RESULT();
|
||||
}
|
||||
#ifdef TEST_CIPHER_EXCLUDE_ANON
|
||||
#undef TEST_CIPHER_EXCLUDE_ANON
|
||||
#endif
|
||||
#ifdef TEST_CIPHER_EXCLUDE_NULL
|
||||
#undef TEST_CIPHER_EXCLUDE_NULL
|
||||
#endif
|
||||
|
||||
static int test_wolfSSL_set_alpn_protos_default_fails(void)
|
||||
{
|
||||
EXPECT_DECLS;
|
||||
@@ -16966,6 +17168,32 @@ static int test_wolfSSL_PKCS8_d2i(void)
|
||||
EVP_PKEY_free(pkey);
|
||||
pkey = NULL;
|
||||
#endif
|
||||
/* A negative length must be rejected before the signed->word32 cast
|
||||
* (CWE-190 -> CWE-125): length = -1 becomes (word32)0xFFFFFFFF, so the
|
||||
* decoder believes ~4 GiB are available and reads past the input. The
|
||||
* same key buffers are decoded with their correct positive length above,
|
||||
* so these calls fail only because of the negative length. All must
|
||||
* return NULL; with the guard removed the over-read is reported by ASan
|
||||
* (and, with a full key, faults outright). */
|
||||
{
|
||||
const unsigned char malformed[2] = { 0x30, 0x10 };
|
||||
const unsigned char* mp;
|
||||
#ifndef NO_RSA
|
||||
#ifdef USE_CERT_BUFFERS_1024
|
||||
const unsigned char* rp = (const unsigned char*)server_key_der_1024;
|
||||
#else
|
||||
const unsigned char* rp = (const unsigned char*)server_key_der_2048;
|
||||
#endif
|
||||
const unsigned char* rp0 = rp;
|
||||
ExpectNull(d2i_AutoPrivateKey(NULL, &rp, -1));
|
||||
rp = rp0;
|
||||
ExpectNull(d2i_PrivateKey(EVP_PKEY_RSA, NULL, &rp, -1));
|
||||
#endif
|
||||
/* The reported PoC: a 2-byte header claiming 16 bytes of absent
|
||||
* content drives the over-read in the key-type detection loop. */
|
||||
mp = malformed;
|
||||
ExpectNull(d2i_AutoPrivateKey(NULL, &mp, -1));
|
||||
}
|
||||
#endif /* OPENSSL_ALL */
|
||||
|
||||
#ifndef NO_FILESYSTEM
|
||||
@@ -35084,6 +35312,7 @@ TEST_CASE testCases[] = {
|
||||
TEST_DECL(test_wolfSSL_set_cipher_list_tls13_keeps_tls12),
|
||||
TEST_DECL(test_wolfSSL_set_cipher_list_tls12_with_version),
|
||||
TEST_DECL(test_wolfSSL_set_cipher_list_tls13_with_version),
|
||||
TEST_DECL(test_wolfSSL_set_cipher_list_exclusions),
|
||||
TEST_DECL(test_wolfSSL_set_alpn_protos_default_fails),
|
||||
TEST_DECL(test_wolfSSL_CTX_use_certificate),
|
||||
TEST_DECL(test_wolfSSL_CTX_use_certificate_file),
|
||||
|
||||
@@ -1495,6 +1495,46 @@ int test_DecodeAltNames_length_underflow(void)
|
||||
return EXPECT_RESULT();
|
||||
}
|
||||
|
||||
/* A certificate must not carry two certificatePolicies extensions
|
||||
* (non-repeatable per RFC 5280 4.2). DecodeCertExtensions calls
|
||||
* DecodeExtensionType once per extension; with strict ASN.1 (the default) a
|
||||
* second certificatePolicies extension must be rejected (ASN_OBJECT_ID_E)
|
||||
* rather than silently overwriting the first - which happened in
|
||||
* WOLFSSL_CERT_EXT builds without WOLFSSL_SEP before the duplicate guard was
|
||||
* extended to cover them. */
|
||||
int test_DecodeCertExtensions_dup_certpol(void)
|
||||
{
|
||||
EXPECT_DECLS;
|
||||
#if (defined(WOLFSSL_SEP) || defined(WOLFSSL_CERT_EXT)) && \
|
||||
!defined(WOLFSSL_NO_ASN_STRICT) && !defined(NO_CERTS) && !defined(NO_ASN)
|
||||
/* Minimal certificatePolicies extnValue: SEQUENCE OF PolicyInformation
|
||||
* with one policyIdentifier OID 1.2.3.4 (encoded 2A 03 04). */
|
||||
static const byte policy[] = {
|
||||
0x30, 0x07, /* certificatePolicies SEQUENCE */
|
||||
0x30, 0x05, /* PolicyInformation SEQUENCE */
|
||||
0x06, 0x03, 0x2A, 0x03, 0x04 /* policyIdentifier OID 1.2.3.4 */
|
||||
};
|
||||
DecodedCert cert;
|
||||
int isUnknown = 0;
|
||||
|
||||
/* DecodeExtensionType only needs an initialized DecodedCert for its
|
||||
* bit-fields and policy storage; the source buffer is never parsed here,
|
||||
* so any non-NULL pointer/size suffices. */
|
||||
wc_InitDecodedCert(&cert, policy, (word32)sizeof(policy), NULL);
|
||||
|
||||
/* First certificatePolicies extension: accepted. */
|
||||
ExpectIntEQ(DecodeExtensionType(policy, (word32)sizeof(policy),
|
||||
CERT_POLICY_OID, 0, &cert, &isUnknown), 0);
|
||||
/* Duplicate certificatePolicies extension: rejected as non-repeatable. */
|
||||
ExpectIntEQ(DecodeExtensionType(policy, (word32)sizeof(policy),
|
||||
CERT_POLICY_OID, 0, &cert, &isUnknown),
|
||||
WC_NO_ERR_TRACE(ASN_OBJECT_ID_E));
|
||||
|
||||
wc_FreeDecodedCert(&cert);
|
||||
#endif
|
||||
return EXPECT_RESULT();
|
||||
}
|
||||
|
||||
int test_ParseCert_SM3wSM2_short_pubkey(void)
|
||||
{
|
||||
EXPECT_DECLS;
|
||||
|
||||
@@ -35,6 +35,7 @@ int test_wolfssl_local_MatchUriNameConstraint(void);
|
||||
int test_wc_DecodeRsaPssParams(void);
|
||||
int test_SerialNumber0_RootCA(void);
|
||||
int test_DecodeAltNames_length_underflow(void);
|
||||
int test_DecodeCertExtensions_dup_certpol(void);
|
||||
int test_ParseCert_SM3wSM2_short_pubkey(void);
|
||||
int test_wc_DecodeObjectId(void);
|
||||
int test_ToTraditional_ex_handcrafted(void);
|
||||
@@ -54,6 +55,7 @@ int test_ToTraditional_ex_mldsa_bad_params(void);
|
||||
TEST_DECL_GROUP("asn", test_wc_DecodeRsaPssParams), \
|
||||
TEST_DECL_GROUP("asn", test_SerialNumber0_RootCA), \
|
||||
TEST_DECL_GROUP("asn", test_DecodeAltNames_length_underflow), \
|
||||
TEST_DECL_GROUP("asn", test_DecodeCertExtensions_dup_certpol), \
|
||||
TEST_DECL_GROUP("asn", test_ParseCert_SM3wSM2_short_pubkey), \
|
||||
TEST_DECL_GROUP("asn", test_wc_DecodeObjectId), \
|
||||
TEST_DECL_GROUP("asn", test_ToTraditional_ex_handcrafted), \
|
||||
|
||||
@@ -2816,6 +2816,28 @@ int test_ASN1_strings(void)
|
||||
der = NULL;
|
||||
}
|
||||
|
||||
/* Negative length must be rejected before the signed->word32 cast
|
||||
* (CWE-190 -> CWE-125). The 2-byte header claims 16 bytes of content
|
||||
* that are not present in the buffer; passing len = -1 would, without
|
||||
* the guard, cast to ~4 GiB so GetLength accepts the claimed length and
|
||||
* d2i_ASN1_STRING over-reads 16 bytes past the buffer. Every wrapper
|
||||
* must instead return NULL without reading the content. */
|
||||
{
|
||||
unsigned char hdr[2];
|
||||
const unsigned char* p;
|
||||
|
||||
hdr[1] = 0x10; /* claim 16 bytes of (absent) content */
|
||||
|
||||
p = hdr; hdr[0] = ASN_OCTET_STRING;
|
||||
ExpectNull(d2i_ASN1_OCTET_STRING(NULL, &p, -1));
|
||||
p = hdr; hdr[0] = ASN_GENERALSTRING;
|
||||
ExpectNull(d2i_ASN1_GENERALSTRING(NULL, &p, -1));
|
||||
p = hdr; hdr[0] = ASN_UTF8STRING;
|
||||
ExpectNull(d2i_ASN1_UTF8STRING(NULL, &p, -1));
|
||||
p = hdr; hdr[0] = ASN_BIT_STRING;
|
||||
ExpectNull(d2i_ASN1_BIT_STRING(NULL, &p, -1));
|
||||
}
|
||||
|
||||
#endif
|
||||
return EXPECT_RESULT();
|
||||
}
|
||||
|
||||
+11
-4
@@ -21036,8 +21036,9 @@ static int DecodeAltSigVal(const byte* input, int sz, DecodedCert* cert)
|
||||
* @return MEMORY_E on dynamic memory allocation failure.
|
||||
* @return Other negative values on error.
|
||||
*/
|
||||
int DecodeExtensionType(const byte* input, word32 length, word32 oid,
|
||||
byte critical, DecodedCert* cert, int *isUnknownExt)
|
||||
WOLFSSL_TEST_VIS int DecodeExtensionType(const byte* input, word32 length,
|
||||
word32 oid, byte critical,
|
||||
DecodedCert* cert, int *isUnknownExt)
|
||||
{
|
||||
int ret = 0;
|
||||
word32 idx = 0;
|
||||
@@ -21137,11 +21138,17 @@ int DecodeExtensionType(const byte* input, word32 length, word32 oid,
|
||||
|
||||
/* Certificate policies. */
|
||||
case CERT_POLICY_OID:
|
||||
#ifdef WOLFSSL_SEP
|
||||
#if defined(WOLFSSL_SEP) || defined(WOLFSSL_CERT_EXT)
|
||||
/* certificatePolicies is non-repeatable (RFC 5280 4.2). In strict
|
||||
* mode (the default; VERIFY_AND_SET_OID is a no-op under
|
||||
* WOLFSSL_NO_ASN_STRICT, like every other extension) reject a
|
||||
* duplicate regardless of WOLFSSL_SEP - otherwise the second one
|
||||
* silently overwrites the first (DecodeCertPolicy resets
|
||||
* extCertPoliciesNb), a policy-authorization confusion. */
|
||||
VERIFY_AND_SET_OID(cert->extCertPolicySet);
|
||||
#ifdef WOLFSSL_SEP
|
||||
cert->extCertPolicyCrit = critical ? 1 : 0;
|
||||
#endif
|
||||
#if defined(WOLFSSL_SEP) || defined(WOLFSSL_CERT_EXT)
|
||||
if (DecodeCertPolicy(input, length, cert) < 0) {
|
||||
ret = ASN_PARSE_E;
|
||||
}
|
||||
|
||||
@@ -2089,7 +2089,7 @@ struct DecodedCert {
|
||||
WC_BITFIELD extSubjAltNameSet:1;
|
||||
WC_BITFIELD inhibitAnyOidSet:1;
|
||||
WC_BITFIELD selfSigned:1; /* Indicates subject and issuer are same */
|
||||
#ifdef WOLFSSL_SEP
|
||||
#if defined(WOLFSSL_SEP) || defined(WOLFSSL_CERT_EXT)
|
||||
WC_BITFIELD extCertPolicySet:1;
|
||||
#endif
|
||||
WC_BITFIELD extCRLdistCrit:1;
|
||||
@@ -2369,6 +2369,7 @@ typedef enum MimeStatus
|
||||
#define FreeSigner wc_FreeSigner
|
||||
#define AllocDer wc_AllocDer
|
||||
#define FreeDer wc_FreeDer
|
||||
#define DecodeExtensionType wc_DecodeExtensionType
|
||||
#endif /* WOLFSSL_API_PREFIX_MAP */
|
||||
|
||||
WOLFSSL_LOCAL int HashIdAlg(word32 oidSum);
|
||||
@@ -2412,9 +2413,9 @@ WOLFSSL_LOCAL int DecodePolicyOID(char *out, word32 outSz, const byte *in,
|
||||
word32 inSz);
|
||||
WOLFSSL_LOCAL int EncodePolicyOID(byte *out, word32 *outSz,
|
||||
const char *in, void* heap);
|
||||
WOLFSSL_LOCAL int DecodeExtensionType(const byte* input, word32 length,
|
||||
word32 oid, byte critical,
|
||||
DecodedCert* cert, int *isUnknownExt);
|
||||
WOLFSSL_TEST_VIS int DecodeExtensionType(const byte* input, word32 length,
|
||||
word32 oid, byte critical,
|
||||
DecodedCert* cert, int *isUnknownExt);
|
||||
WOLFSSL_LOCAL int CheckCertSignaturePubKey(const byte* cert, word32 certSz,
|
||||
void* heap, const byte* pubKey, word32 pubKeySz, int pubKeyOID);
|
||||
#if defined(OPENSSL_EXTRA) || defined(WOLFSSL_SMALL_CERT_VERIFY)
|
||||
|
||||
Reference in New Issue
Block a user