Skip to content

Commit c2df7c6

Browse files
committed
Add opt-in X.509 cross-consistency checks (EKU chaining, RSA-PSS key params, critical BasicConstraints)
Follow-up to the algorithm-confusion hardening in wolfSSL#10131 (SigOidMatchesKeyOid). An audit of X.509 parsing/validation found three further "logically related fields that should agree but are not cross-checked" gaps. Each fix is gated behind a new, default-off macro so existing builds are byte-for-byte unchanged (the full testsuite passes with the macros off). WOLFSSL_CHECK_EKU_CHAIN (B1): The issuing CA's extendedKeyUsage did not constrain the certs it issued (only the leaf's EKU was checked, at TLS time). Propagate the CA's effective EKU onto its Signer in FillSigner and, during chain verification, reject a certificate that asserts an EKU not permitted by the issuing CA. An absent CA EKU or anyExtendedKeyUsage imposes no restriction. WOLFSSL_CHECK_RSAPSS_KEY_PARAMS (B2): An id-RSASSA-PSS issuer key's restricting parameters (RFC 4055) were parsed and then discarded (the long-standing "TODO: store parameters so that usage can be checked" in DecodeRsaPublicKey). Capture the SPKI PSS parameters on the DecodedCert/Signer and require a signature made by that key to use the mandated hash/MGF/salt. WOLFSSL_REQUIRE_CRITICAL_BASIC_CONSTRAINTS (B7): RFC 5280 4.2.1.9 requires CA certificates to mark basicConstraints critical; wolfSSL stored the critical bit but never required it. Reject an intermediate CA whose basicConstraints is not critical. Self-signed trust anchors are exempt, as their own extensions are not processed during path validation. All three are validated with dedicated proof-of-concept programs and EKU positive/negative controls; B1+B2 pass the standard testsuite, and B7's rejections are limited to non-conforming (non-critical-BC) intermediates. https://claude.ai/code/session_01NSH5QDCbE9n1hKYQUYbKQA
1 parent beff858 commit c2df7c6

2 files changed

Lines changed: 127 additions & 0 deletions

File tree

wolfcrypt/src/asn.c

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21474,6 +21474,31 @@ static int DecodeCertInternal(DecodedCert* cert, int verify, int* criticalExt,
2147421474
cert->keyOID = dataASN[X509CERTASN_IDX_TBS_SPUBKEYINFO_ALGO_OID].data.oid.sum;
2147521475
cert->certBegin = dataASN[X509CERTASN_IDX_TBS_SEQ].offset;
2147621476

21477+
#if defined(WC_RSA_PSS) && defined(WOLFSSL_CHECK_RSAPSS_KEY_PARAMS)
21478+
/* Capture RSA-PSS parameters carried in this cert's SubjectPublicKey-
21479+
* Info algorithm identifier (RFC 4055). They restrict the hash/MGF/
21480+
* salt usable in signatures made by this key; store them so the
21481+
* restriction can be enforced when this cert acts as an issuer. */
21482+
if ((cert->keyOID == RSAPSSk) &&
21483+
(dataASN[X509CERTASN_IDX_TBS_SPUBKEYINFO_ALGO_P_SEQ].tag != 0)) {
21484+
word32 pssOff =
21485+
dataASN[X509CERTASN_IDX_TBS_SPUBKEYINFO_ALGO_P_SEQ].offset;
21486+
enum wc_HashType pssHash = WC_HASH_TYPE_SHA;
21487+
int pssMgf = WC_MGF1SHA1;
21488+
int pssSaltLen = 20;
21489+
/* DecodeRsaPssParams bounds itself by the SEQUENCE length, so a
21490+
* generous size to end-of-cert is safe. */
21491+
if (DecodeRsaPssParams(cert->source + pssOff,
21492+
cert->maxIdx - pssOff, &pssHash, &pssMgf,
21493+
&pssSaltLen) == 0) {
21494+
cert->keyPssHash = pssHash;
21495+
cert->keyPssMgf = pssMgf;
21496+
cert->keyPssSaltLen = pssSaltLen;
21497+
cert->keyPssParamsSet = 1;
21498+
}
21499+
}
21500+
#endif
21501+
2147721502
/* No bad date error - don't always care. */
2147821503
badDate = 0;
2147921504
/* Find the item with the ASN_BEFORE date and check it. */
@@ -23157,6 +23182,21 @@ int ParseCertRelative(DecodedCert* cert, int type, int verify, void* cm,
2315723182
}
2315823183
#endif
2315923184

23185+
#ifdef WOLFSSL_REQUIRE_CRITICAL_BASIC_CONSTRAINTS
23186+
/* https://datatracker.ietf.org/doc/html/rfc5280#section-4.2.1.9
23187+
* Conforming CAs MUST include [basicConstraints] in all CA
23188+
* certificates ... and MUST mark the extension as critical.
23189+
* Enforced on intermediate CAs only: a trust anchor (self-signed root)
23190+
* is an explicitly-trusted input and its own extensions are not
23191+
* processed during RFC 5280 sec 6.1 path validation. */
23192+
if (cert->isCA && !cert->selfSigned && cert->extBasicConstSet &&
23193+
!cert->extBasicConstCrit) {
23194+
WOLFSSL_MSG("Intermediate CA basicConstraints not marked critical");
23195+
WOLFSSL_ERROR_VERBOSE(ASN_CRIT_EXT_E);
23196+
return ASN_CRIT_EXT_E;
23197+
}
23198+
#endif
23199+
2316023200
#ifndef NO_SKID
2316123201
if (cert->extSubjKeyIdSet == 0 && cert->publicKey != NULL &&
2316223202
cert->pubKeySize > 0) {
@@ -23419,6 +23459,52 @@ int ParseCertRelative(DecodedCert* cert, int type, int verify, void* cm,
2341923459
return ret;
2342023460
}
2342123461

23462+
#ifdef WOLFSSL_CHECK_EKU_CHAIN
23463+
/* RFC 5280 4.2.1.12 / CA-Browser Forum EKU chaining: the
23464+
* extendedKeyUsages this certificate asserts must be a subset
23465+
* of those the issuing CA permits (effective EKU, propagated
23466+
* in FillSigner). A CA EKU of 0 (absent) or anyExtendedKeyUsage
23467+
* imposes no restriction; a child asserting anyExtendedKeyUsage
23468+
* under a constrained CA is likewise rejected. */
23469+
if (cert->extExtKeyUsageSet &&
23470+
(cert->ca->extKeyUsage != 0) &&
23471+
((cert->ca->extKeyUsage & EXTKEYUSE_ANY) == 0) &&
23472+
(((cert->extExtKeyUsage & EXTKEYUSE_ANY) != 0) ||
23473+
((cert->extExtKeyUsage &
23474+
(byte)~cert->ca->extKeyUsage) != 0))) {
23475+
WOLFSSL_MSG("Cert EKU not permitted by issuing CA EKU");
23476+
WOLFSSL_ERROR_VERBOSE(EXTKEYUSAGE_E);
23477+
return EXTKEYUSAGE_E;
23478+
}
23479+
#endif
23480+
23481+
#if defined(WC_RSA_PSS) && defined(WOLFSSL_CHECK_RSAPSS_KEY_PARAMS)
23482+
/* RFC 4055: when the issuing key is RSASSA-PSS with restricting
23483+
* parameters, the signature's PSS parameters must conform to
23484+
* the key's mandated hash/MGF/salt. */
23485+
if (cert->ca->keyPssParamsSet) {
23486+
enum wc_HashType sigPssHash = WC_HASH_TYPE_SHA;
23487+
int sigPssMgf = WC_MGF1SHA1;
23488+
int sigPssSaltLen = 20;
23489+
int pssRet = 0;
23490+
if (cert->sigParamsLength > 0) {
23491+
pssRet = DecodeRsaPssParams(
23492+
cert->source + cert->sigParamsIndex,
23493+
cert->sigParamsLength, &sigPssHash, &sigPssMgf,
23494+
&sigPssSaltLen);
23495+
}
23496+
if ((pssRet != 0) ||
23497+
((int)sigPssHash != cert->ca->keyPssHash) ||
23498+
(sigPssMgf != cert->ca->keyPssMgf) ||
23499+
(sigPssSaltLen != cert->ca->keyPssSaltLen)) {
23500+
WOLFSSL_MSG("Sig PSS params don't match issuer key "
23501+
"PSS params");
23502+
WOLFSSL_ERROR_VERBOSE(ASN_SIG_OID_E);
23503+
return ASN_SIG_OID_E;
23504+
}
23505+
}
23506+
#endif
23507+
2342223508
#ifdef WOLFSSL_DUAL_ALG_CERTS
2342323509
if ((ret == 0) && cert->extAltSigAlgSet &&
2342423510
cert->extAltSigValSet) {
@@ -23642,6 +23728,26 @@ int FillSigner(Signer* signer, DecodedCert* cert, int type, DerBuffer *der)
2364223728
signer->keyUsage = cert->extKeyUsageSet ? cert->extKeyUsage
2364323729
: 0xFFFF;
2364423730
signer->extKeyUsage = cert->extExtKeyUsage;
23731+
#ifdef WOLFSSL_CHECK_EKU_CHAIN
23732+
/* Propagate the issuing CA's extendedKeyUsage restriction onto this
23733+
* signer so it applies transitively down the path (RFC 5280
23734+
* 4.2.1.12 chaining). A CA EKU of 0 (absent) or anyExtendedKeyUsage
23735+
* imposes no restriction. */
23736+
if (cert->ca != NULL && cert->ca->extKeyUsage != 0 &&
23737+
(cert->ca->extKeyUsage & EXTKEYUSE_ANY) == 0) {
23738+
if (signer->extKeyUsage == 0)
23739+
signer->extKeyUsage = cert->ca->extKeyUsage;
23740+
else
23741+
signer->extKeyUsage =
23742+
(byte)(signer->extKeyUsage & cert->ca->extKeyUsage);
23743+
}
23744+
#endif
23745+
#if defined(WC_RSA_PSS) && defined(WOLFSSL_CHECK_RSAPSS_KEY_PARAMS)
23746+
signer->keyPssParamsSet = cert->keyPssParamsSet;
23747+
signer->keyPssHash = (int)cert->keyPssHash;
23748+
signer->keyPssMgf = cert->keyPssMgf;
23749+
signer->keyPssSaltLen = cert->keyPssSaltLen;
23750+
#endif
2364523751
signer->next = NULL; /* If Key Usage not set, all uses valid. */
2364623752
cert->publicKey = 0; /* in case lock fails don't free here. */
2364723753
cert->pubKeyStored = 0;

wolfssl/wolfcrypt/asn.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1779,6 +1779,17 @@ struct DecodedCert {
17791779
#ifdef WC_RSA_PSS
17801780
word32 sigParamsIndex; /* start of signature parameters */
17811781
word32 sigParamsLength; /* length of signature parameters */
1782+
#if defined(WOLFSSL_CHECK_RSAPSS_KEY_PARAMS)
1783+
/* SPKI-level RSA-PSS parameters (RFC 4055) constraining how this cert's
1784+
* key may be used to verify signatures. Captured so the constraint can be
1785+
* enforced against signatures made by this key (resolves the historical
1786+
* "TODO: store parameters so that usage can be checked" in
1787+
* DecodeRsaPublicKey). */
1788+
enum wc_HashType keyPssHash; /* mandated message-digest hash */
1789+
int keyPssMgf; /* mandated MGF1 hash (WC_MGF1...) */
1790+
int keyPssSaltLen; /* mandated salt length */
1791+
byte keyPssParamsSet; /* 1 when key carries PSS params */
1792+
#endif
17821793
#endif
17831794
int version; /* cert version, 1 or 3 */
17841795
DNS_entry* altNames; /* alt names list of dns entries */
@@ -2185,6 +2196,16 @@ struct Signer {
21852196
word32 keyOID; /* key type */
21862197
word16 keyUsage;
21872198
byte extKeyUsage;
2199+
#if defined(WC_RSA_PSS) && defined(WOLFSSL_CHECK_RSAPSS_KEY_PARAMS)
2200+
/* SPKI-level RSA-PSS parameters of this CA's key, propagated from the
2201+
* DecodedCert so a signature made by this key can be checked to conform
2202+
* (RFC 4055). Gated by the macro so the default Signer layout - which is
2203+
* load-bearing for PERSIST_CERT_CACHE - is unchanged. */
2204+
byte keyPssParamsSet;
2205+
int keyPssHash;
2206+
int keyPssMgf;
2207+
int keyPssSaltLen;
2208+
#endif
21882209
word16 maxPathLen;
21892210
WC_BITFIELD selfSigned:1;
21902211
#ifndef IGNORE_NAME_CONSTRAINTS

0 commit comments

Comments
 (0)