@@ -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+
2255222756static 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),
0 commit comments