diff --git a/tests/api/test_asn.c b/tests/api/test_asn.c index 63cbfd365d..bfa9562636 100644 --- a/tests/api/test_asn.c +++ b/tests/api/test_asn.c @@ -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; diff --git a/tests/api/test_asn.h b/tests/api/test_asn.h index a0c72b2517..d48aac7a6f 100644 --- a/tests/api/test_asn.h +++ b/tests/api/test_asn.h @@ -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), \ diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index e84d252931..7e855986f6 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -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; } diff --git a/wolfssl/wolfcrypt/asn.h b/wolfssl/wolfcrypt/asn.h index 8c82294d12..45993f0638 100644 --- a/wolfssl/wolfcrypt/asn.h +++ b/wolfssl/wolfcrypt/asn.h @@ -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)