Skip to content

Commit 490c106

Browse files
authored
Merge pull request #10274 from gasbytes/crl-idp-extension-fix-follow-up
Reject CRLs with unrecognized critical entry extensions per RFC 5280 section 5.3
2 parents 545376c + 6111c60 commit 490c106

7 files changed

Lines changed: 171 additions & 51 deletions

File tree

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDGzCCAgOgAwIBAgIUHiXBXW8CIaDwFBWcO00dcxYA5FEwDQYJKoZIhvcNAQEL
3+
BQAwFTETMBEGA1UEAwwKY2xhaW0tcm9vdDAeFw0yNjA0MTYxMTM4NTVaFw0zNjA0
4+
MTMxMTM4NTVaMBUxEzARBgNVBAMMCmNsYWltLXJvb3QwggEiMA0GCSqGSIb3DQEB
5+
AQUAA4IBDwAwggEKAoIBAQC6SYy1F8EBJG9WGqk7A+KfJLElmPs4gnhUpx9ph+SW
6+
G4EYELDAW0u/uB307nUPtUVycM5lhEQ+MHjE8+y6lnikZfxijfUp+Xw9eGwdSkzJ
7+
FS0iEOqTJrimF9MOvAyrg2P2HMyDcyl+f4N/vWOqjfp4hdI+YJVajfqPzZQ/EyjZ
8+
0IcoF3jiYY15lwGpfITAHL5fXcooa17dg6VVNBG6+ouSo96287qrfxpn/W8ghUx3
9+
p46+uPiPcONa03fJnhBgtNsMxQXhH73mee6CP1F24n9cEW3TIWnsBRGLyDmzLKaD
10+
tG+sGdZqZQ0IlFjePckMVANzDI0kCfOxXdLj61bWKGZbAgMBAAGjYzBhMA8GA1Ud
11+
EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRSl1hHmMr4maB+
12+
jhw4Luq76pt0MDAfBgNVHSMEGDAWgBRSl1hHmMr4maB+jhw4Luq76pt0MDANBgkq
13+
hkiG9w0BAQsFAAOCAQEAfTDUagGJO2LtFkZZD/I7td6JCBdoy0bcOabLVrCR6wOy
14+
FcQ7TWNVIgom5mRG6A+o897hQ1Tm14r0T6tWkxJxSyVxCjEYee5FpPVyZ/pB2YeX
15+
Ce9VrW9HHVqy6fciBS1agajoU7CU9mP/P1F6CKwnmlcRIqQAhHCGdjkPT1fPjpTS
16+
jkPA1TR99aFFHrfIfnz+XU1TQyUVnggBVqT/eVklySOYrwWvwQsp8eLENjGR+vK7
17+
Euhn+cehXoztkhKjK+HC4aCwDhKn0KKu1vowIQ9z/iQhXwOGac3sdhjh/bZkkKYG
18+
LhlAk1A35JDjHweu+4nD9sSQq0BnTEMsorA+YRZpjw==
19+
-----END CERTIFICATE-----
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
-----BEGIN X509 CRL-----
2+
MIIBjDB2AgEBMA0GCSqGSIb3DQEBCwUAMBUxEzARBgNVBAMMCmNsYWltLXJvb3QX
3+
DTI2MDQyMTEyMzM0OFoXDTM2MDQxODEyMzM0OFowLTArAgIQABcNMjYwNDIxMTIz
4+
MzQ4WjAWMBQGA1UdAQEB/wQKDAhvYnNvbGV0ZTANBgkqhkiG9w0BAQsFAAOCAQEA
5+
PPG5bodrM2jK+6KcqRh9vEkWhLyxCkJij1om7R8BMO5bpvxZFOSqdN9GvIqYANYT
6+
ZNQzZZOer9DSXc1I9Cha179dyGnBsX33geSdhF99JxA/kZUVynfOn56El7iywkPQ
7+
yEpuY7uQQRfcp7D7tGz8S7mNhOMAeaN+jR2Psmn8q1Yc3W01vwYZos3qjHG1xDZ6
8+
97QVFX6tbnbeu/hBsF87oiAyCbXnhRQOvrVsKqjrZXGkfRKfJ1deHc1vWgio7fhr
9+
A1/Hb4sdfTWZUwtduTRI9Vyaw1IL41d+0ExdzTshIHbddB/dyByrRqlIzJdeiigt
10+
d1Gcf1EYSoWiV8Xaj9SFng==
11+
-----END X509 CRL-----

certs/crl/include.am

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ EXTRA_DIST += \
2727
certs/crl/extra-crls/large_crlnum.pem \
2828
certs/crl/extra-crls/large_crlnum2.pem \
2929
certs/crl/extra-crls/crlnum_57oct.pem \
30-
certs/crl/extra-crls/crlnum_64oct.pem
30+
certs/crl/extra-crls/crlnum_64oct.pem \
31+
certs/crl/extra-crls/claim-root.pem \
32+
certs/crl/extra-crls/crl_critical_entry.pem
3133

3234
# Intermediate cert CRL's
3335
EXTRA_DIST += \

tests/api/test_certman.c

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2427,6 +2427,31 @@ int test_wolfSSL_CRL_unknown_critical_ext(void)
24272427
return EXPECT_RESULT();
24282428
}
24292429

2430+
int test_wolfSSL_CRL_unknown_critical_entry_ext(void)
2431+
{
2432+
EXPECT_DECLS;
2433+
#if !defined(NO_CERTS) && defined(HAVE_CRL) && !defined(NO_RSA) && \
2434+
!defined(NO_FILESYSTEM)
2435+
WOLFSSL_CERT_MANAGER* cm = NULL;
2436+
2437+
ExpectNotNull(cm = wolfSSL_CertManagerNew());
2438+
ExpectIntEQ(wolfSSL_CertManagerLoadCA(cm,
2439+
"./certs/crl/extra-crls/claim-root.pem", NULL), WOLFSSL_SUCCESS);
2440+
ExpectIntEQ(wolfSSL_CertManagerEnableCRL(cm, WOLFSSL_CRL_CHECKALL),
2441+
WOLFSSL_SUCCESS);
2442+
2443+
/* CRL with a revoked entry that carries a critical unknown extension
2444+
* (OID 2.5.29.1, old X.509v2 AKI, permanently superseded).
2445+
* Per RFC 5280 Section 5.3, the CRL must not be used. */
2446+
ExpectIntNE(wolfSSL_CertManagerLoadCRLFile(cm,
2447+
"./certs/crl/extra-crls/crl_critical_entry.pem", WOLFSSL_FILETYPE_PEM),
2448+
WOLFSSL_SUCCESS);
2449+
2450+
wolfSSL_CertManagerFree(cm);
2451+
#endif
2452+
return EXPECT_RESULT();
2453+
}
2454+
24302455
int test_wolfSSL_CertManagerCheckOCSPResponse(void)
24312456
{
24322457
EXPECT_DECLS;

tests/api/test_certman.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ int test_wolfSSL_CRL_static_revoked_list(void);
4242
int test_wolfSSL_CRL_duplicate_extensions(void);
4343
int test_wolfSSL_CRL_critical_idp(void);
4444
int test_wolfSSL_CRL_unknown_critical_ext(void);
45+
int test_wolfSSL_CRL_unknown_critical_entry_ext(void);
4546
int test_wolfSSL_CertManagerCheckOCSPResponse(void);
4647
int test_various_pathlen_chains(void);
4748
int test_wolfSSL_CertManagerRejectMD5Cert(void);
@@ -65,6 +66,7 @@ int test_wolfSSL_CertManagerRejectMD5Cert(void);
6566
TEST_DECL_GROUP("certman", test_wolfSSL_CRL_duplicate_extensions), \
6667
TEST_DECL_GROUP("certman", test_wolfSSL_CRL_critical_idp), \
6768
TEST_DECL_GROUP("certman", test_wolfSSL_CRL_unknown_critical_ext), \
69+
TEST_DECL_GROUP("certman", test_wolfSSL_CRL_unknown_critical_entry_ext), \
6870
TEST_DECL_GROUP("certman", test_wolfSSL_CertManagerCheckOCSPResponse), \
6971
TEST_DECL_GROUP("certman", test_various_pathlen_chains), \
7072
TEST_DECL_GROUP("certman", test_wolfSSL_CertManagerRejectMD5Cert)

wolfcrypt/src/asn.c

Lines changed: 88 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -34653,40 +34653,62 @@ enum {
3465334653
/* CRL Reason Code OID: 2.5.29.21 */
3465434654
static const byte crlReasonOid[] = { 0x55, 0x1d, 0x15 };
3465534655

34656-
/* Parse CRL entry extensions to extract the reason code.
34657-
* Sets *reasonCode if found, otherwise leaves it unchanged. */
34658-
static void ParseCRL_ReasonCode(const byte* buff, word32 idx, word32 maxIdx,
34659-
int* reasonCode)
34656+
/* Parse CRL entry extensions.
34657+
* Extracts the reason code into *reasonCode if the CRL Reason extension
34658+
* is present. Per RFC 5280 Section 5.3, returns ASN_CRIT_EXT_E if any
34659+
* unknown extension is marked critical. Returns 0 on success. */
34660+
static int ParseCRL_EntryExtensions(const byte* buff, word32 idx, word32 maxIdx,
34661+
int* reasonCode)
3466034662
{
3466134663
while (idx < maxIdx) {
3466234664
int len;
34665+
int oidLen;
3466334666
word32 end;
3466434667
word32 localIdx;
34668+
word32 oidContent;
3466534669
byte tag;
34670+
int critical = 0;
34671+
int isReasonOid = 0;
3466634672

3466734673
/* Each extension is a SEQUENCE */
3466834674
if (GetSequence(buff, &idx, &len, maxIdx) < 0) {
3466934675
break;
3467034676
}
3467134677
end = idx + (word32)len;
3467234678

34673-
/* Check for CRL Reason OID: 2.5.29.21 */
34674-
if (end - idx >= (word32)(2 + sizeof(crlReasonOid)) &&
34675-
buff[idx] == ASN_OBJECT_ID &&
34676-
buff[idx + 1] == sizeof(crlReasonOid) &&
34677-
XMEMCMP(buff + idx + 2, crlReasonOid,
34679+
/* Parse OID: tag, length (short or long form), content */
34680+
if (GetASNTag(buff, &idx, &tag, end) < 0 ||
34681+
tag != ASN_OBJECT_ID) {
34682+
break;
34683+
}
34684+
if (GetLength(buff, &idx, &oidLen, end) < 0) {
34685+
break;
34686+
}
34687+
oidContent = idx;
34688+
if (idx + (word32)oidLen > end) {
34689+
break;
34690+
}
34691+
34692+
/* Check if it's the CRL Reason OID: 2.5.29.21 */
34693+
if ((word32)oidLen == sizeof(crlReasonOid) &&
34694+
XMEMCMP(buff + oidContent, crlReasonOid,
3467834695
sizeof(crlReasonOid)) == 0) {
34679-
/* Skip past the OID */
34680-
idx += 2 + (word32)sizeof(crlReasonOid);
34681-
/* Skip optional critical BOOLEAN */
34682-
localIdx = idx;
34683-
if (GetASNTag(buff, &localIdx, &tag, end) == 0 &&
34684-
tag == ASN_BOOLEAN) {
34685-
/* Consume full BOOLEAN TLV (tag + length + value). */
34686-
if (GetBoolean(buff, &idx, end) < 0) {
34687-
break;
34688-
}
34696+
isReasonOid = 1;
34697+
}
34698+
idx = oidContent + (word32)oidLen;
34699+
34700+
/* Parse optional critical BOOLEAN */
34701+
localIdx = idx;
34702+
if (GetASNTag(buff, &localIdx, &tag, end) == 0 &&
34703+
tag == ASN_BOOLEAN) {
34704+
int ret = GetBoolean(buff, &idx, end);
34705+
if (ret < 0) {
34706+
break;
3468934707
}
34708+
critical = ret;
34709+
}
34710+
34711+
if (isReasonOid) {
3469034712
/* Get OCTET STRING wrapping the ENUMERATED */
3469134713
if (GetOctetString(buff, &idx, &len, end) >= 0) {
3469234714
/* Parse ENUMERATED reason value */
@@ -34702,8 +34724,15 @@ static void ParseCRL_ReasonCode(const byte* buff, word32 idx, word32 maxIdx,
3470234724
}
3470334725
}
3470434726
}
34727+
else if (critical) {
34728+
/* RFC 5280 Section 5.3: reject CRL with unknown critical
34729+
* entry extension. */
34730+
WOLFSSL_MSG("Unknown critical CRL entry extension");
34731+
return ASN_CRIT_EXT_E;
34732+
}
3470534733
idx = end;
3470634734
}
34735+
return 0;
3470734736
}
3470834737

3470934738
#ifdef HAVE_CRL
@@ -34716,8 +34745,7 @@ WOLFSSL_TEST_VIS int wc_ParseCRLReasonFromExtensions(const byte* ext,
3471634745
return BAD_FUNC_ARG;
3471734746
}
3471834747

34719-
ParseCRL_ReasonCode(ext, 0, extSz, reasonCode);
34720-
return 0;
34748+
return ParseCRL_EntryExtensions(ext, 0, extSz, reasonCode);
3472134749
}
3472234750
#endif
3472334751

@@ -34780,49 +34808,58 @@ static int GetRevoked(RevokedCert* rcert, const byte* buff, word32* idx,
3478034808
/* Parse CRL entry extensions (v2 only) */
3478134809
if (dataASN[REVOKEDASN_IDX_TIME_EXT].length > 0) {
3478234810
word32 extOff = dataASN[REVOKEDASN_IDX_TIME_EXT].offset;
34783-
word32 extLen = dataASN[REVOKEDASN_IDX_TIME_EXT].length;
34784-
word32 extEnd = extOff + extLen;
34785-
word32 extIdx2 = extOff;
34811+
word32 extTagEnd = extOff +
34812+
dataASN[REVOKEDASN_IDX_TIME_EXT].length + 6;
34813+
int extLen;
34814+
34815+
/* .offset points at the outer SEQUENCE tag. Re-parse the
34816+
* SEQUENCE header to locate the content start (list of
34817+
* Extension SEQUENCEs), which handles long-form length.
34818+
* extTagEnd adds 6 to cover the worst-case tag+long-form-length
34819+
* header for the outer SEQUENCE. */
34820+
if (GetSequence(buff, &extOff, &extLen, extTagEnd) < 0) {
34821+
ret = ASN_PARSE_E;
34822+
}
34823+
else {
34824+
word32 extEnd = extOff + (word32)extLen;
3478634825

3478734826
#if defined(OPENSSL_EXTRA)
34788-
/* Store raw DER of extensions for OpenSSL compat API.
34789-
* Include the outer SEQUENCE tag+length. */
34790-
{
34791-
/* Back up to include the SEQUENCE header. We know the
34792-
* content starts at extOff, so the header is just before.
34793-
* Use the raw buffer start from before GetASN_Items. */
34794-
word32 seqHdrSz = 0;
34795-
/* The outer SEQUENCE header is at most 4 bytes before
34796-
* content. Rather than guess, store just the content. */
34797-
rc->extensions = (byte*)XMALLOC(extLen, dcrl->heap,
34827+
/* Store raw DER of extension contents for OpenSSL compat. */
34828+
rc->extensions = (byte*)XMALLOC((size_t)extLen, dcrl->heap,
3479834829
DYNAMIC_TYPE_REVOKED);
3479934830
if (rc->extensions != NULL) {
34800-
XMEMCPY(rc->extensions, buff + extOff, extLen);
34801-
rc->extensionsSz = extLen;
34831+
XMEMCPY(rc->extensions, buff + extOff, (size_t)extLen);
34832+
rc->extensionsSz = (word32)extLen;
3480234833
}
34803-
(void)seqHdrSz;
34804-
}
3480534834
#endif
3480634835

34807-
ParseCRL_ReasonCode(buff, extIdx2, extEnd, &rc->reasonCode);
34836+
ret = ParseCRL_EntryExtensions(buff, extOff, extEnd,
34837+
&rc->reasonCode);
34838+
}
3480834839
}
3480934840

34810-
/* Add revoked certificate to chain. */
34841+
if (ret == 0) {
34842+
/* Add revoked certificate to chain. */
3481134843
#ifndef CRL_STATIC_REVOKED_LIST
34812-
rc->next = dcrl->certs;
34813-
dcrl->certs = rc;
34844+
rc->next = dcrl->certs;
34845+
dcrl->certs = rc;
3481434846
#endif
34815-
dcrl->totalCerts++;
34847+
dcrl->totalCerts++;
34848+
}
3481634849
}
3481734850

3481834851
FREE_ASNGETDATA(dataASN, dcrl->heap);
34819-
#ifndef CRL_STATIC_REVOKED_LIST
3482034852
if ((ret != 0) && (rc != NULL)) {
3482134853
#if defined(OPENSSL_EXTRA)
3482234854
XFREE(rc->extensions, dcrl->heap, DYNAMIC_TYPE_REVOKED);
34855+
rc->extensions = NULL;
34856+
rc->extensionsSz = 0;
3482334857
#endif
34858+
#ifndef CRL_STATIC_REVOKED_LIST
3482434859
XFREE(rc, dcrl->heap, DYNAMIC_TYPE_CRL);
34860+
#endif
3482534861
}
34862+
#ifndef CRL_STATIC_REVOKED_LIST
3482634863
(void)rcert;
3482734864
#endif
3482834865
return ret;
@@ -34846,7 +34883,13 @@ static int ParseCRL_RevokedCerts(RevokedCert* rcert, DecodedCRL* dcrl,
3484634883
/* Parse each revoked certificate. */
3484734884
while ((ret == 0) && (idx < maxIdx)) {
3484834885
/* Parse a revoked certificate. */
34849-
if (GetRevoked(rcert, buff, &idx, dcrl, maxIdx) < 0) {
34886+
int r = GetRevoked(rcert, buff, &idx, dcrl, maxIdx);
34887+
if (r == WC_NO_ERR_TRACE(ASN_CRIT_EXT_E)) {
34888+
/* Preserve the specific error so callers can distinguish a
34889+
* rejected critical extension from a generic parse failure. */
34890+
ret = r;
34891+
}
34892+
else if (r < 0) {
3485034893
ret = ASN_PARSE_E;
3485134894
}
3485234895
}

wolfcrypt/src/asn_orig.c

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9182,18 +9182,17 @@ static int GetRevoked(RevokedCert* rcert, const byte* buff, word32* idx,
91829182
XFREE(rc, dcrl->heap, DYNAMIC_TYPE_REVOKED);
91839183
return ret;
91849184
}
9185-
/* add to list */
9186-
rc->next = dcrl->certs;
9187-
dcrl->certs = rc;
91889185

91899186
(void)rcert;
91909187
#endif /* CRL_STATIC_REVOKED_LIST */
9191-
dcrl->totalCerts++;
91929188
/* get date */
91939189
#ifndef NO_ASN_TIME
91949190
ret = GetBasicDate(buff, idx, rc->revDate, &rc->revDateFormat, maxIdx);
91959191
if (ret < 0) {
91969192
WOLFSSL_MSG("Expecting Date");
9193+
#ifndef CRL_STATIC_REVOKED_LIST
9194+
XFREE(rc, dcrl->heap, DYNAMIC_TYPE_REVOKED);
9195+
#endif
91979196
return ret;
91989197
}
91999198
#endif
@@ -9227,11 +9226,30 @@ static int GetRevoked(RevokedCert* rcert, const byte* buff, word32* idx,
92279226
}
92289227
#endif
92299228

9230-
ParseCRL_ReasonCode(buff, seqIdx, extEnd, &rc->reasonCode);
9229+
ret = ParseCRL_EntryExtensions(buff, seqIdx, extEnd,
9230+
&rc->reasonCode);
9231+
if (ret != 0) {
9232+
#if defined(OPENSSL_EXTRA)
9233+
XFREE(rc->extensions, dcrl->heap, DYNAMIC_TYPE_REVOKED);
9234+
rc->extensions = NULL;
9235+
rc->extensionsSz = 0;
9236+
#endif
9237+
#ifndef CRL_STATIC_REVOKED_LIST
9238+
XFREE(rc, dcrl->heap, DYNAMIC_TYPE_REVOKED);
9239+
#endif
9240+
return ret;
9241+
}
92319242
}
92329243
}
92339244
}
92349245

9246+
#ifndef CRL_STATIC_REVOKED_LIST
9247+
/* add to list only after all parsing succeeded */
9248+
rc->next = dcrl->certs;
9249+
dcrl->certs = rc;
9250+
#endif
9251+
dcrl->totalCerts++;
9252+
92359253
*idx = end;
92369254

92379255
return 0;

0 commit comments

Comments
 (0)