Skip to content

Commit 7ae1a54

Browse files
committed
Fix ParseCertRelative to check pathlen on self-issued certs
1 parent a05413a commit 7ae1a54

2 files changed

Lines changed: 261 additions & 10 deletions

File tree

tests/api.c

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21402,6 +21402,226 @@ static int test_MakeCertWithPathLen(void)
2140221402
return EXPECT_RESULT();
2140321403
}
2140421404

21405+
static int test_PathLenSelfIssued(void)
21406+
{
21407+
EXPECT_DECLS;
21408+
#if defined(WOLFSSL_CERT_REQ) && !defined(NO_ASN_TIME) && \
21409+
defined(WOLFSSL_CERT_GEN) && defined(HAVE_ECC) && \
21410+
defined(WOLFSSL_CERT_EXT) && !defined(NO_CERTS) && \
21411+
(!defined(NO_WOLFSSL_CLIENT) || !defined(WOLFSSL_NO_CLIENT_AUTH))
21412+
Cert cert;
21413+
DecodedCert decodedCert;
21414+
byte rootDer[FOURK_BUF];
21415+
byte icaDer[FOURK_BUF];
21416+
byte entityDer[FOURK_BUF];
21417+
int rootDerSz = 0;
21418+
int icaDerSz = 0;
21419+
int entityDerSz = 0;
21420+
WC_RNG rng;
21421+
ecc_key rootKey;
21422+
ecc_key icaKey;
21423+
ecc_key entityKey;
21424+
WOLFSSL_CERT_MANAGER* cm = NULL;
21425+
21426+
XMEMSET(&rng, 0, sizeof(WC_RNG));
21427+
XMEMSET(&rootKey, 0, sizeof(ecc_key));
21428+
XMEMSET(&icaKey, 0, sizeof(ecc_key));
21429+
XMEMSET(&entityKey, 0, sizeof(ecc_key));
21430+
21431+
ExpectIntEQ(wc_InitRng(&rng), 0);
21432+
ExpectIntEQ(wc_ecc_init(&rootKey), 0);
21433+
ExpectIntEQ(wc_ecc_init(&icaKey), 0);
21434+
ExpectIntEQ(wc_ecc_init(&entityKey), 0);
21435+
ExpectIntEQ(wc_ecc_make_key(&rng, 32, &rootKey), 0);
21436+
ExpectIntEQ(wc_ecc_make_key(&rng, 32, &icaKey), 0);
21437+
ExpectIntEQ(wc_ecc_make_key(&rng, 32, &entityKey), 0);
21438+
21439+
/* Step 1: Create root CA with pathLen=0 */
21440+
ExpectIntEQ(wc_InitCert(&cert), 0);
21441+
(void)XSTRNCPY(cert.subject.country, "US", CTC_NAME_SIZE);
21442+
(void)XSTRNCPY(cert.subject.state, "MT", CTC_NAME_SIZE);
21443+
(void)XSTRNCPY(cert.subject.locality, "Bozeman", CTC_NAME_SIZE);
21444+
(void)XSTRNCPY(cert.subject.org, "TestCA", CTC_NAME_SIZE);
21445+
(void)XSTRNCPY(cert.subject.unit, "Test", CTC_NAME_SIZE);
21446+
(void)XSTRNCPY(cert.subject.commonName, "TestRootCA", CTC_NAME_SIZE);
21447+
(void)XSTRNCPY(cert.subject.email, "root@test.com", CTC_NAME_SIZE);
21448+
cert.selfSigned = 1;
21449+
cert.isCA = 1;
21450+
cert.pathLen = 0;
21451+
cert.pathLenSet = 1;
21452+
cert.sigType = CTC_SHA256wECDSA;
21453+
cert.keyUsage = KEYUSE_KEY_CERT_SIGN | KEYUSE_CRL_SIGN;
21454+
ExpectIntEQ(wc_SetSubjectKeyIdFromPublicKey_ex(&cert, ECC_TYPE, &rootKey),
21455+
0);
21456+
21457+
ExpectIntGE(wc_MakeCert(&cert, rootDer, FOURK_BUF, NULL, &rootKey, &rng),
21458+
0);
21459+
ExpectIntGE(rootDerSz = wc_SignCert(cert.bodySz, cert.sigType, rootDer,
21460+
FOURK_BUF, NULL, &rootKey, &rng), 0);
21461+
21462+
/* Step 2: Create self-issued intermediate (same subject DN as root,
21463+
* different key, signed by root) - this should be blocked by pathLen=0 */
21464+
ExpectIntEQ(wc_InitCert(&cert), 0);
21465+
cert.selfSigned = 0;
21466+
cert.isCA = 1;
21467+
cert.sigType = CTC_SHA256wECDSA;
21468+
cert.keyUsage = KEYUSE_KEY_CERT_SIGN | KEYUSE_CRL_SIGN;
21469+
/* Set both subject and issuer from the root cert so they match */
21470+
ExpectIntEQ(wc_SetSubjectBuffer(&cert, rootDer, rootDerSz), 0);
21471+
ExpectIntEQ(wc_SetIssuerBuffer(&cert, rootDer, rootDerSz), 0);
21472+
ExpectIntEQ(wc_SetAuthKeyIdFromPublicKey_ex(&cert, ECC_TYPE, &rootKey), 0);
21473+
ExpectIntEQ(wc_SetSubjectKeyIdFromPublicKey_ex(&cert, ECC_TYPE, &icaKey),
21474+
0);
21475+
21476+
ExpectIntGE(wc_MakeCert(&cert, icaDer, FOURK_BUF, NULL, &icaKey, &rng), 0);
21477+
ExpectIntGE(icaDerSz = wc_SignCert(cert.bodySz, cert.sigType, icaDer,
21478+
FOURK_BUF, NULL, &rootKey, &rng), 0);
21479+
21480+
/* Step 3: Create entity cert signed by the intermediate */
21481+
ExpectIntEQ(wc_InitCert(&cert), 0);
21482+
cert.selfSigned = 0;
21483+
cert.isCA = 0;
21484+
cert.sigType = CTC_SHA256wECDSA;
21485+
(void)XSTRNCPY(cert.subject.country, "US", CTC_NAME_SIZE);
21486+
(void)XSTRNCPY(cert.subject.state, "MT", CTC_NAME_SIZE);
21487+
(void)XSTRNCPY(cert.subject.locality, "Bozeman", CTC_NAME_SIZE);
21488+
(void)XSTRNCPY(cert.subject.org, "TestEntity", CTC_NAME_SIZE);
21489+
(void)XSTRNCPY(cert.subject.commonName, "entity.test", CTC_NAME_SIZE);
21490+
ExpectIntEQ(wc_SetIssuerBuffer(&cert, icaDer, icaDerSz), 0);
21491+
ExpectIntEQ(wc_SetAuthKeyIdFromPublicKey_ex(&cert, ECC_TYPE, &icaKey), 0);
21492+
21493+
ExpectIntGE(wc_MakeCert(&cert, entityDer, FOURK_BUF, NULL, &entityKey,
21494+
&rng), 0);
21495+
ExpectIntGE(entityDerSz = wc_SignCert(cert.bodySz, cert.sigType, entityDer,
21496+
FOURK_BUF, NULL, &icaKey, &rng), 0);
21497+
21498+
/* Step 4: Load root CA into cert manager */
21499+
ExpectNotNull(cm = wolfSSL_CertManagerNew());
21500+
ExpectIntEQ(wolfSSL_CertManagerLoadCABuffer(cm, rootDer, rootDerSz,
21501+
WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS);
21502+
21503+
/* Step 5: Parse the self-issued intermediate as a chain cert.
21504+
* This simulates TLS chain verification where the intermediate is
21505+
* received as part of the certificate chain.
21506+
* Root CA has pathLen=0, so it should NOT be allowed to sign any
21507+
* intermediate CA (including self-issued ones).
21508+
* BUG: wolfSSL sets selfSigned=1 for this cert (issuer==subject DN),
21509+
* which causes the pathLen enforcement to be entirely skipped. */
21510+
wc_InitDecodedCert(&decodedCert, icaDer, (word32)icaDerSz, NULL);
21511+
ExpectIntEQ(wc_ParseCert(&decodedCert, CHAIN_CERT_TYPE, VERIFY,
21512+
cm), WC_NO_ERR_TRACE(ASN_PATHLEN_INV_E));
21513+
wc_FreeDecodedCert(&decodedCert);
21514+
21515+
wolfSSL_CertManagerFree(cm);
21516+
wc_ecc_free(&entityKey);
21517+
wc_ecc_free(&icaKey);
21518+
wc_ecc_free(&rootKey);
21519+
wc_FreeRng(&rng);
21520+
#endif
21521+
return EXPECT_RESULT();
21522+
}
21523+
21524+
static int test_PathLenNoKeyUsage(void)
21525+
{
21526+
EXPECT_DECLS;
21527+
#if defined(WOLFSSL_CERT_REQ) && !defined(NO_ASN_TIME) && \
21528+
defined(WOLFSSL_CERT_GEN) && defined(HAVE_ECC) && \
21529+
defined(WOLFSSL_CERT_EXT) && !defined(NO_CERTS) && \
21530+
(!defined(NO_WOLFSSL_CLIENT) || !defined(WOLFSSL_NO_CLIENT_AUTH))
21531+
Cert cert;
21532+
DecodedCert decodedCert;
21533+
byte rootDer[FOURK_BUF];
21534+
byte icaDer[FOURK_BUF];
21535+
int rootDerSz = 0;
21536+
int icaDerSz = 0;
21537+
WC_RNG rng;
21538+
ecc_key rootKey;
21539+
ecc_key icaKey;
21540+
WOLFSSL_CERT_MANAGER* cm = NULL;
21541+
21542+
XMEMSET(&rng, 0, sizeof(WC_RNG));
21543+
XMEMSET(&rootKey, 0, sizeof(ecc_key));
21544+
XMEMSET(&icaKey, 0, sizeof(ecc_key));
21545+
21546+
ExpectIntEQ(wc_InitRng(&rng), 0);
21547+
ExpectIntEQ(wc_ecc_init(&rootKey), 0);
21548+
ExpectIntEQ(wc_ecc_init(&icaKey), 0);
21549+
ExpectIntEQ(wc_ecc_make_key(&rng, 32, &rootKey), 0);
21550+
ExpectIntEQ(wc_ecc_make_key(&rng, 32, &icaKey), 0);
21551+
21552+
/* Step 1: Create root CA with pathLen=0 and KeyUsage */
21553+
ExpectIntEQ(wc_InitCert(&cert), 0);
21554+
(void)XSTRNCPY(cert.subject.country, "US", CTC_NAME_SIZE);
21555+
(void)XSTRNCPY(cert.subject.state, "MT", CTC_NAME_SIZE);
21556+
(void)XSTRNCPY(cert.subject.locality, "Bozeman", CTC_NAME_SIZE);
21557+
(void)XSTRNCPY(cert.subject.org, "TestCA2", CTC_NAME_SIZE);
21558+
(void)XSTRNCPY(cert.subject.unit, "Test", CTC_NAME_SIZE);
21559+
(void)XSTRNCPY(cert.subject.commonName, "TestRootCA2", CTC_NAME_SIZE);
21560+
(void)XSTRNCPY(cert.subject.email, "root@test2.com", CTC_NAME_SIZE);
21561+
cert.selfSigned = 1;
21562+
cert.isCA = 1;
21563+
cert.pathLen = 0;
21564+
cert.pathLenSet = 1;
21565+
cert.sigType = CTC_SHA256wECDSA;
21566+
cert.keyUsage = KEYUSE_KEY_CERT_SIGN | KEYUSE_CRL_SIGN;
21567+
ExpectIntEQ(wc_SetSubjectKeyIdFromPublicKey_ex(&cert, ECC_TYPE, &rootKey),
21568+
0);
21569+
21570+
ExpectIntGE(wc_MakeCert(&cert, rootDer, FOURK_BUF, NULL, &rootKey, &rng),
21571+
0);
21572+
ExpectIntGE(rootDerSz = wc_SignCert(cert.bodySz, cert.sigType, rootDer,
21573+
FOURK_BUF, NULL, &rootKey, &rng), 0);
21574+
21575+
/* Step 2: Create intermediate CA WITHOUT KeyUsage extension.
21576+
* Per RFC 5280, when KeyUsage is absent all uses are valid.
21577+
* The root's pathLen=0 should still block this intermediate CA.
21578+
* BUG: pathLen check requires extKeyUsageSet which is false when
21579+
* KeyUsage is absent, so the check is skipped entirely. */
21580+
ExpectIntEQ(wc_InitCert(&cert), 0);
21581+
cert.selfSigned = 0;
21582+
cert.isCA = 1;
21583+
cert.sigType = CTC_SHA256wECDSA;
21584+
/* Intentionally do NOT set keyUsage - test that pathLen is still enforced */
21585+
cert.keyUsage = 0;
21586+
(void)XSTRNCPY(cert.subject.country, "US", CTC_NAME_SIZE);
21587+
(void)XSTRNCPY(cert.subject.state, "MT", CTC_NAME_SIZE);
21588+
(void)XSTRNCPY(cert.subject.locality, "Bozeman", CTC_NAME_SIZE);
21589+
(void)XSTRNCPY(cert.subject.org, "TestICA", CTC_NAME_SIZE);
21590+
(void)XSTRNCPY(cert.subject.unit, "Test", CTC_NAME_SIZE);
21591+
(void)XSTRNCPY(cert.subject.commonName, "TestICA-NoKU", CTC_NAME_SIZE);
21592+
(void)XSTRNCPY(cert.subject.email, "ica@test2.com", CTC_NAME_SIZE);
21593+
ExpectIntEQ(wc_SetIssuerBuffer(&cert, rootDer, rootDerSz), 0);
21594+
ExpectIntEQ(wc_SetAuthKeyIdFromPublicKey_ex(&cert, ECC_TYPE, &rootKey), 0);
21595+
ExpectIntEQ(wc_SetSubjectKeyIdFromPublicKey_ex(&cert, ECC_TYPE, &icaKey),
21596+
0);
21597+
21598+
ExpectIntGE(wc_MakeCert(&cert, icaDer, FOURK_BUF, NULL, &icaKey, &rng), 0);
21599+
ExpectIntGE(icaDerSz = wc_SignCert(cert.bodySz, cert.sigType, icaDer,
21600+
FOURK_BUF, NULL, &rootKey, &rng), 0);
21601+
21602+
/* Step 3: Load root CA into cert manager */
21603+
ExpectNotNull(cm = wolfSSL_CertManagerNew());
21604+
ExpectIntEQ(wolfSSL_CertManagerLoadCABuffer(cm, rootDer, rootDerSz,
21605+
WOLFSSL_FILETYPE_ASN1), WOLFSSL_SUCCESS);
21606+
21607+
/* Step 4: Parse the intermediate (no KeyUsage) as a chain cert.
21608+
* Root CA has pathLen=0, this intermediate CA should be rejected.
21609+
* The intermediate does NOT have the KeyUsage extension, but per
21610+
* RFC 5280 4.2.1.3 all key uses are valid when the extension is
21611+
* absent, so pathLen must still be enforced. */
21612+
wc_InitDecodedCert(&decodedCert, icaDer, (word32)icaDerSz, NULL);
21613+
ExpectIntEQ(wc_ParseCert(&decodedCert, CHAIN_CERT_TYPE, VERIFY,
21614+
cm), WC_NO_ERR_TRACE(ASN_PATHLEN_INV_E));
21615+
wc_FreeDecodedCert(&decodedCert);
21616+
21617+
wolfSSL_CertManagerFree(cm);
21618+
wc_ecc_free(&icaKey);
21619+
wc_ecc_free(&rootKey);
21620+
wc_FreeRng(&rng);
21621+
#endif
21622+
return EXPECT_RESULT();
21623+
}
21624+
2140521625
static int test_MakeCertWith0Ser(void)
2140621626
{
2140721627
EXPECT_DECLS;
@@ -35277,6 +35497,8 @@ TEST_CASE testCases[] = {
3527735497
TEST_DECL(test_wc_ParseCert),
3527835498
TEST_DECL(test_wc_ParseCert_Error),
3527935499
TEST_DECL(test_MakeCertWithPathLen),
35500+
TEST_DECL(test_PathLenSelfIssued),
35501+
TEST_DECL(test_PathLenNoKeyUsage),
3528035502
TEST_DECL(test_MakeCertWith0Ser),
3528135503
TEST_DECL(test_MakeCertWithCaFalse),
3528235504
#ifdef WOLFSSL_CERT_SIGN_CB

wolfcrypt/src/asn.c

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22425,16 +22425,24 @@ int ParseCertRelative(DecodedCert* cert, int type, int verify, void* cm,
2242522425
if (cert->pathLengthSet)
2242622426
cert->maxPathLen = cert->pathLength;
2242722427

22428-
if (!cert->selfSigned) {
22429-
/* Need to perform a pathlen check on anything that will be used
22430-
* to sign certificates later on. Otherwise, pathLen doesn't
22431-
* mean anything.
22432-
* Nothing to check if we don't have the issuer of this cert. */
22433-
if (type != CERT_TYPE && cert->isCA && cert->extKeyUsageSet &&
22434-
(cert->extKeyUsage & KEYUSE_KEY_CERT_SIGN) != 0 && cert->ca) {
22428+
/* RFC 5280 6.1.4: Check issuer's pathLen constraint.
22429+
* Need to perform a pathlen check on anything that will be used
22430+
* to sign certificates later on. Otherwise, pathLen doesn't
22431+
* mean anything.
22432+
* Nothing to check if we don't have the issuer of this cert.
22433+
*
22434+
* Per RFC 5280, when the KeyUsage extension is absent, all key
22435+
* uses are implicitly valid (including keyCertSign), so pathLen
22436+
* enforcement must not be gated on KeyUsage presence. */
22437+
if (type != CERT_TYPE && cert->isCA && cert->ca &&
22438+
(!cert->extKeyUsageSet ||
22439+
(cert->extKeyUsage & KEYUSE_KEY_CERT_SIGN) != 0)) {
22440+
if (!cert->selfSigned) {
22441+
/* RFC 5280 6.1.4(l): Non-self-issued cert decrements and
22442+
* checks the issuer's max_path_length. */
2243522443
if (cert->ca->maxPathLen == 0) {
22436-
/* This cert CAN NOT be used as an intermediate cert. The
22437-
* issuer does not allow it. */
22444+
/* This cert CAN NOT be used as an intermediate cert.
22445+
* The issuer does not allow it. */
2243822446
cert->maxPathLen = 0;
2243922447
if (verify != NO_VERIFY) {
2244022448
WOLFSSL_MSG("\tNon-entity cert, maxPathLen is 0");
@@ -22444,7 +22452,28 @@ int ParseCertRelative(DecodedCert* cert, int type, int verify, void* cm,
2244422452
}
2244522453
}
2244622454
else {
22447-
cert->maxPathLen = (byte)min(cert->ca->maxPathLen - 1U,
22455+
cert->maxPathLen = (word16)min(cert->ca->maxPathLen - 1U,
22456+
cert->maxPathLen);
22457+
}
22458+
}
22459+
else {
22460+
/* RFC 5280 6.1.4(l): Self-issued certs do NOT decrement
22461+
* max_path_length, but the issuer's constraint still
22462+
* applies. A self-issued cert from a CA with maxPathLen=0
22463+
* cannot act as an intermediate CA. */
22464+
if (cert->ca->maxPathLen == 0) {
22465+
cert->maxPathLen = 0;
22466+
if (verify != NO_VERIFY) {
22467+
WOLFSSL_MSG("\tSelf-issued cert, maxPathLen is 0");
22468+
WOLFSSL_MSG("\tmaxPathLen status: ERROR");
22469+
WOLFSSL_ERROR_VERBOSE(ASN_PATHLEN_INV_E);
22470+
return ASN_PATHLEN_INV_E;
22471+
}
22472+
}
22473+
else {
22474+
/* Self-issued: honor issuer's constraint without
22475+
* decrementing. */
22476+
cert->maxPathLen = (word16)min(cert->ca->maxPathLen,
2244822477
cert->maxPathLen);
2244922478
}
2245022479
}

0 commit comments

Comments
 (0)