Skip to content

Commit 1a3daf0

Browse files
authored
Merge pull request #10087 from padelsbach/crl-num-negative
Reject negative CRL numbers when decoding
2 parents adf70b1 + 18494e1 commit 1a3daf0

3 files changed

Lines changed: 235 additions & 1 deletion

File tree

tests/api.c

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22549,6 +22549,210 @@ static int test_wolfSSL_X509_CRL_sign_large(void)
2254922549
return EXPECT_RESULT();
2255022550
}
2255122551

22552+
/* Test round-trip of a CRL with the maximum size CRL number (CRL_MAX_NUM_SZ
22553+
* = 20 bytes). Build TBS with wc_MakeCRL_ex, sign with wc_SignCRL_ex, then
22554+
* decode with d2i_X509_CRL and verify the CRL number survives.
22555+
* Then patch the DER to make the CRL number negative and verify rejection. */
22556+
static int test_wc_MakeCRL_max_crlnum(void)
22557+
{
22558+
EXPECT_DECLS;
22559+
#if defined(WOLFSSL_CERT_GEN) && defined(HAVE_CRL) && !defined(NO_RSA) && \
22560+
!defined(NO_FILESYSTEM) && !defined(NO_ASN) && defined(OPENSSL_EXTRA)
22561+
const char* caCertDerFile = "./certs/ca-cert.der";
22562+
const char* caKeyDerFile = "./certs/ca-key.der";
22563+
byte certBuf[4096];
22564+
byte keyBuf[4096];
22565+
int certSz = 0;
22566+
int keySz = 0;
22567+
XFILE f = XBADFILE;
22568+
DecodedCert caCert;
22569+
int caCertInit = 0;
22570+
RsaKey rsaKey;
22571+
WC_RNG rng;
22572+
word32 idx = 0;
22573+
int rsaKeyInit = 0;
22574+
int rngInit = 0;
22575+
22576+
/* 20-byte CRL number: 0x01..0x14. First byte < 0x80 so no ASN.1
22577+
* padding byte is needed - the encoded INTEGER content is exactly
22578+
* 20 bytes, matching CRL_MAX_NUM_SZ. */
22579+
byte crlNum[CRL_MAX_NUM_SZ];
22580+
const char* expHex =
22581+
"0102030405060708090A0B0C0D0E0F1011121314";
22582+
22583+
/* thisUpdate/nextUpdate dates in UTC time format */
22584+
const byte thisUpdate[] = "260101000000Z"; /* Jan 1, 2026 */
22585+
const byte nextUpdate[] = "270101000000Z"; /* Jan 1, 2027 */
22586+
22587+
/* issuer DER (SEQUENCE-wrapped Name) */
22588+
byte issuerDer[512];
22589+
word32 issuerDerSz = 0;
22590+
22591+
byte* tbsBuf = NULL;
22592+
int tbsSz = 0;
22593+
byte* crlBuf = NULL;
22594+
int crlSz = 0;
22595+
int bufSz = 0;
22596+
22597+
WOLFSSL_X509_CRL* decodedCrl = NULL;
22598+
int i;
22599+
22600+
/* CRL Number OID 2.5.29.20: used to locate the extension in the DER */
22601+
static const byte crlNumOid[] = { 0x55, 0x1D, 0x14 };
22602+
22603+
/* Fill CRL number with 0x01..0x14 */
22604+
for (i = 0; i < (int)CRL_MAX_NUM_SZ; i++) {
22605+
crlNum[i] = (byte)(i + 1);
22606+
}
22607+
22608+
/* Load CA cert DER */
22609+
ExpectTrue((f = XFOPEN(caCertDerFile, "rb")) != XBADFILE);
22610+
if (f != XBADFILE) {
22611+
ExpectIntGT(certSz = (int)XFREAD(certBuf, 1, sizeof(certBuf), f), 0);
22612+
XFCLOSE(f);
22613+
f = XBADFILE;
22614+
}
22615+
22616+
/* Load CA key DER */
22617+
ExpectTrue((f = XFOPEN(caKeyDerFile, "rb")) != XBADFILE);
22618+
if (f != XBADFILE) {
22619+
ExpectIntGT(keySz = (int)XFREAD(keyBuf, 1, sizeof(keyBuf), f), 0);
22620+
XFCLOSE(f);
22621+
f = XBADFILE;
22622+
}
22623+
22624+
/* Parse CA cert to extract the subject (= CRL issuer) DER.
22625+
* subjectRaw is the Name contents without the outer SEQUENCE tag,
22626+
* so we re-wrap it with SetSequence to produce a valid issuer Name. */
22627+
wc_InitDecodedCert(&caCert, certBuf, (word32)certSz, NULL);
22628+
caCertInit = 1;
22629+
ExpectIntEQ(wc_ParseCert(&caCert, CERT_TYPE, 0, NULL), 0);
22630+
if (EXPECT_SUCCESS()) {
22631+
word32 seqHdrSz = SetSequence((word32)caCert.subjectRawLen, issuerDer);
22632+
XMEMCPY(issuerDer + seqHdrSz, caCert.subjectRaw,
22633+
(size_t)caCert.subjectRawLen);
22634+
issuerDerSz = seqHdrSz + (word32)caCert.subjectRawLen;
22635+
}
22636+
22637+
/* Decode RSA private key */
22638+
ExpectIntEQ(wc_InitRsaKey(&rsaKey, NULL), 0);
22639+
if (EXPECT_SUCCESS()) {
22640+
rsaKeyInit = 1;
22641+
}
22642+
idx = 0;
22643+
ExpectIntEQ(wc_RsaPrivateKeyDecode(keyBuf, &idx, &rsaKey, (word32)keySz),
22644+
0);
22645+
22646+
/* Init RNG for signing */
22647+
ExpectIntEQ(wc_InitRng(&rng), 0);
22648+
if (EXPECT_SUCCESS()) {
22649+
rngInit = 1;
22650+
}
22651+
22652+
/* --- Build TBS --- */
22653+
/* First call with NULL output to get required size */
22654+
tbsSz = wc_MakeCRL_ex(
22655+
issuerDer, issuerDerSz,
22656+
thisUpdate, ASN_UTC_TIME, /* thisUpdate */
22657+
nextUpdate, ASN_UTC_TIME, /* nextUpdate */
22658+
NULL, /* no revoked certs */
22659+
crlNum, (word32)sizeof(crlNum), /* max-size CRL number */
22660+
CTC_SHA256wRSA, 2, /* v2 for extensions */
22661+
NULL, 0);
22662+
ExpectIntGT(tbsSz, 0);
22663+
22664+
/* Allocate buffer large enough for TBS + signature overhead */
22665+
if (EXPECT_SUCCESS()) {
22666+
bufSz = tbsSz + 512; /* generous room for signature wrapper */
22667+
ExpectNotNull(tbsBuf = (byte*)XMALLOC(bufSz, NULL,
22668+
DYNAMIC_TYPE_TMP_BUFFER));
22669+
}
22670+
22671+
/* Second call to actually encode TBS */
22672+
if (EXPECT_SUCCESS()) {
22673+
tbsSz = wc_MakeCRL_ex(
22674+
issuerDer, issuerDerSz,
22675+
thisUpdate, ASN_UTC_TIME,
22676+
nextUpdate, ASN_UTC_TIME,
22677+
NULL,
22678+
crlNum, (word32)sizeof(crlNum),
22679+
CTC_SHA256wRSA, 2,
22680+
tbsBuf, (word32)bufSz);
22681+
ExpectIntGT(tbsSz, 0);
22682+
}
22683+
22684+
/* --- Sign the CRL --- */
22685+
if (EXPECT_SUCCESS()) {
22686+
ExpectNotNull(crlBuf = (byte*)XMALLOC(bufSz, NULL,
22687+
DYNAMIC_TYPE_TMP_BUFFER));
22688+
}
22689+
if (EXPECT_SUCCESS()) {
22690+
crlSz = wc_SignCRL_ex(tbsBuf, tbsSz, CTC_SHA256wRSA,
22691+
crlBuf, (word32)bufSz, &rsaKey, NULL, &rng);
22692+
ExpectIntGT(crlSz, 0);
22693+
}
22694+
22695+
/* --- Decode the CRL and verify CRL number --- */
22696+
if (EXPECT_SUCCESS()) {
22697+
ExpectNotNull(decodedCrl = d2i_X509_CRL(NULL, crlBuf, crlSz));
22698+
}
22699+
if (decodedCrl != NULL && decodedCrl->crlList != NULL) {
22700+
ExpectTrue(decodedCrl->crlList->crlNumberSet);
22701+
if (decodedCrl->crlList->crlNumberSet) {
22702+
ExpectIntEQ(XMEMCMP(decodedCrl->crlList->crlNumber, expHex,
22703+
XSTRLEN(expHex)), 0);
22704+
}
22705+
}
22706+
wolfSSL_X509_CRL_free(decodedCrl);
22707+
decodedCrl = NULL;
22708+
22709+
/* --- Negative test: patch the CRL number to be negative --- */
22710+
/* The encoded CRL number INTEGER is: 02 14 01 02 03 ... 14
22711+
* (tag=0x02, length=0x14=20, content bytes 0x01..0x14).
22712+
* Flip the high bit of the first content byte (0x01 -> 0x81) to make
22713+
* the INTEGER negative. No lengths change, so the DER stays
22714+
* structurally valid. The signature is now wrong, but d2i_X509_CRL
22715+
* uses NO_VERIFY so that doesn't matter. */
22716+
if (EXPECT_SUCCESS()) {
22717+
/* Find CRL Number OID (55 1D 14) in the DER */
22718+
int found = 0;
22719+
for (i = 0; i < crlSz - (int)sizeof(crlNumOid); i++) {
22720+
if (XMEMCMP(crlBuf + i, crlNumOid, sizeof(crlNumOid)) == 0) {
22721+
/* OID found at i. Skip past: OID (3 bytes) -> OCTET STRING
22722+
* tag (1) + length (1) -> INTEGER tag (1) + length (1) ->
22723+
* first content byte. */
22724+
int contentIdx = i + 3 + 2 + 2;
22725+
if (contentIdx < crlSz) {
22726+
crlBuf[contentIdx] |= 0x80;
22727+
found = 1;
22728+
}
22729+
break;
22730+
}
22731+
}
22732+
ExpectTrue(found);
22733+
}
22734+
/* Decoding the patched CRL must fail - the CRL number is negative. */
22735+
if (EXPECT_SUCCESS()) {
22736+
decodedCrl = d2i_X509_CRL(NULL, crlBuf, crlSz);
22737+
ExpectNull(decodedCrl);
22738+
wolfSSL_X509_CRL_free(decodedCrl);
22739+
}
22740+
22741+
XFREE(crlBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER);
22742+
XFREE(tbsBuf, NULL, DYNAMIC_TYPE_TMP_BUFFER);
22743+
if (caCertInit) {
22744+
wc_FreeDecodedCert(&caCert);
22745+
}
22746+
if (rngInit) {
22747+
wc_FreeRng(&rng);
22748+
}
22749+
if (rsaKeyInit) {
22750+
wc_FreeRsaKey(&rsaKey);
22751+
}
22752+
#endif
22753+
return EXPECT_RESULT();
22754+
}
22755+
2255222756
static int test_X509_REQ(void)
2255322757
{
2255422758
EXPECT_DECLS;
@@ -34702,6 +34906,7 @@ TEST_CASE testCases[] = {
3470234906
#endif
3470334907
TEST_DECL(test_sk_X509_CRL_encode),
3470434908
TEST_DECL(test_wolfSSL_X509_CRL_sign_large),
34909+
TEST_DECL(test_wc_MakeCRL_max_crlnum),
3470534910

3470634911
/* OpenSSL X509 REQ API test */
3470734912
TEST_DECL(test_wolfSSL_d2i_X509_REQ),

wolfcrypt/src/asn.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34179,12 +34179,28 @@ static int ParseCRL_Extensions(DecodedCRL* dcrl, const byte* buf, word32 idx,
3417934179

3418034180
if (ret == 0) {
3418134181
int crlNumLen = 0;
34182+
word32 rawIdx = localIdx;
3418234183
word32 tmpIdx = localIdx;
3418334184
ret = GetASNInt(buf, &tmpIdx, &crlNumLen, maxIdx);
3418434185
if (ret == 0 && (crlNumLen > CRL_MAX_NUM_SZ)) {
3418534186
WOLFSSL_MSG("CRL number exceeds limitation");
3418634187
ret = BUFFER_E;
3418734188
}
34189+
/* RFC 5280 s5.2.3: CRL number must be non-negative.
34190+
* Check the raw encoding before GetASNInt strips
34191+
* the leading-zero pad: skip past the INTEGER tag
34192+
* and length, then reject if the first content byte
34193+
* has its high bit set (negative value). A leading
34194+
* 0x00 pad means the value is positive. */
34195+
if (ret == 0) {
34196+
int rawLen = 0;
34197+
(void)GetASNHeader(buf, ASN_INTEGER,
34198+
&rawIdx, &rawLen, maxIdx);
34199+
if (rawLen > 0 && (buf[rawIdx] & 0x80) != 0) {
34200+
WOLFSSL_MSG("CRL number is negative");
34201+
ret = ASN_PARSE_E;
34202+
}
34203+
}
3418834204
if (ret == 0) {
3418934205
ret = GetInt(m, buf, &localIdx, maxIdx);
3419034206
}

wolfcrypt/src/asn_orig.c

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9359,12 +9359,25 @@ static int ParseCRL_Extensions(DecodedCRL* dcrl, const byte* buf,
93599359
localIdx = idx;
93609360
if (GetASNTag(buf, &localIdx, &tag, sz) == 0 &&
93619361
tag == ASN_INTEGER) {
9362+
word32 rawIdx = idx;
9363+
int rawLen = 0;
93629364
ret = GetASNInt(buf, &idx, &length, sz);
93639365
if (ret < 0) {
93649366
WOLFSSL_MSG("\tcouldn't parse CRL number extension");
93659367
return ret;
93669368
}
9367-
else if (length <= CRL_MAX_NUM_SZ) {
9369+
/* RFC 5280 s5.2.3: CRL number must be non-negative.
9370+
* Check the raw encoding before GetASNInt strips
9371+
* the leading-zero pad: skip past the INTEGER tag
9372+
* and length, then reject if the first content byte
9373+
* has its high bit set (negative value). */
9374+
(void)GetASNHeader(buf, ASN_INTEGER,
9375+
&rawIdx, &rawLen, sz);
9376+
if (rawLen > 0 && (buf[rawIdx] & 0x80) != 0) {
9377+
WOLFSSL_MSG("CRL number is negative");
9378+
return ASN_PARSE_E;
9379+
}
9380+
if (length <= CRL_MAX_NUM_SZ) {
93689381
DECL_MP_INT_SIZE_DYN(m, CRL_MAX_NUM_SZ_BITS,
93699382
CRL_MAX_NUM_SZ_BITS);
93709383
NEW_MP_INT_SIZE(m, CRL_MAX_NUM_SZ_BITS, NULL,

0 commit comments

Comments
 (0)