Reject duplicate certificatePolicies extension in WOLFSSL_CERT_EXT builds

DecodeExtensionType() guarded the certificatePolicies duplicate check
(VERIFY_AND_SET_OID) under WOLFSSL_SEP only, because the extCertPolicySet
tracking bit was SEP-only. In a WOLFSSL_CERT_EXT-without-WOLFSSL_SEP build a
cert with two certificatePolicies extensions was accepted and the second
silently overwrote the first (RFC 5280 4.2 forbids repeats). Make the bit and
the guard available under WOLFSSL_CERT_EXT too, matching every other
non-repeatable extension.

Add test_DecodeCertExtensions_dup_certpol (DecodeExtensionType now
WOLFSSL_TEST_VIS).
This commit is contained in:
Tobias Frauenschläger
2026-06-17 14:30:18 +02:00
parent 8e5be42a9d
commit dedba75ad4
4 changed files with 58 additions and 8 deletions
+40
View 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;
+2
View File
@@ -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), \
+11 -4
View File
@@ -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;
}
+5 -4
View File
@@ -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)