diff --git a/src/crl.c b/src/crl.c index c04eccb6fc..995576763f 100644 --- a/src/crl.c +++ b/src/crl.c @@ -42,6 +42,9 @@ CRL Options: #include #include #include +#if defined(OPENSSL_EXTRA) +#include +#endif #ifndef NO_STRING_H #include @@ -93,6 +96,9 @@ int InitCRL(WOLFSSL_CRL* crl, WOLFSSL_CERT_MANAGER* cm) (void)ret; } #endif +#if defined(OPENSSL_EXTRA) + crl->revokedStack = NULL; +#endif return 0; } @@ -250,6 +256,14 @@ static void CRL_Entry_free(CRL_Entry* crle, void* heap) return; } #ifdef CRL_STATIC_REVOKED_LIST +#if defined(OPENSSL_EXTRA) + { + int i; + for (i = 0; i < CRL_MAX_REVOKED_CERTS; i++) { + XFREE(crle->certs[i].extensions, heap, DYNAMIC_TYPE_REVOKED); + } + } +#endif XMEMSET(crle->certs, 0, CRL_MAX_REVOKED_CERTS*sizeof(RevokedCert)); #else { @@ -258,6 +272,9 @@ static void CRL_Entry_free(CRL_Entry* crle, void* heap) for (tmp = crle->certs; tmp != NULL; tmp = next) { next = tmp->next; +#if defined(OPENSSL_EXTRA) + XFREE(tmp->extensions, heap, DYNAMIC_TYPE_REVOKED); +#endif XFREE(tmp, heap, DYNAMIC_TYPE_REVOKED); } @@ -312,6 +329,12 @@ void FreeCRL(WOLFSSL_CRL* crl, int dynamic) XFREE(crl->monitors[1].path, crl->heap, DYNAMIC_TYPE_CRL_MONITOR); #endif +#if defined(OPENSSL_EXTRA) + if (crl->revokedStack != NULL) { + wolfSSL_sk_pop_free(crl->revokedStack, NULL); + crl->revokedStack = NULL; + } +#endif XFREE(crl->currentEntry, crl->heap, DYNAMIC_TYPE_CRL_ENTRY); crl->currentEntry = NULL; while(tmp) { @@ -1231,6 +1254,20 @@ static RevokedCert *DupRevokedCertList(RevokedCert* in, void* heap) XMEMCPY(tmp->revDate, current->revDate, MAX_DATE_SIZE); tmp->revDateFormat = current->revDateFormat; + tmp->reasonCode = current->reasonCode; +#if defined(OPENSSL_EXTRA) + tmp->extensions = NULL; + tmp->extensionsSz = 0; + if (current->extensions != NULL && current->extensionsSz > 0) { + tmp->extensions = (byte*)XMALLOC(current->extensionsSz, heap, + DYNAMIC_TYPE_REVOKED); + if (tmp->extensions != NULL) { + XMEMCPY(tmp->extensions, current->extensions, + current->extensionsSz); + tmp->extensionsSz = current->extensionsSz; + } + } +#endif tmp->next = NULL; if (prev != NULL) prev->next = tmp; @@ -1244,6 +1281,9 @@ static RevokedCert *DupRevokedCertList(RevokedCert* in, void* heap) while (head != NULL) { current = head; head = head->next; +#if defined(OPENSSL_EXTRA) + XFREE(current->extensions, heap, DYNAMIC_TYPE_REVOKED); +#endif XFREE(current, heap, DYNAMIC_TYPE_REVOKED); } return NULL; @@ -2360,10 +2400,8 @@ WOLFSSL_X509_CRL* wolfSSL_X509_CRL_new(void) #ifdef WOLFSSL_CERT_GEN /* Add a revoked certificate entry to CRL. * crl: target CRL - * rev: serial number of revoked certificate + * rev: revoked certificate entry (serial, date, reason, etc.) * Returns WOLFSSL_SUCCESS on success. - * TODO: support other fields for OpenSSL compatibility: revocationDate, - * extensions, issuer, etc. */ int wolfSSL_X509_CRL_add_revoked(WOLFSSL_X509_CRL* crl, WOLFSSL_X509_REVOKED* rev) @@ -2371,7 +2409,6 @@ int wolfSSL_X509_CRL_add_revoked(WOLFSSL_X509_CRL* crl, CRL_Entry* entry; RevokedCert* rc; RevokedCert* curr; - WOLFSSL_ASN1_TIME revDate; WOLFSSL_ENTER("wolfSSL_X509_CRL_add_revoked"); @@ -2379,16 +2416,14 @@ int wolfSSL_X509_CRL_add_revoked(WOLFSSL_X509_CRL* crl, return BAD_FUNC_ARG; } - entry = crl->crlList; - if (entry == NULL) { + if (rev->revocationDate != NULL && (rev->revocationDate->length <= 0 || + (unsigned)rev->revocationDate->length > sizeof(rc->revDate))) { return BAD_FUNC_ARG; } - /* Set the revocation date to the current time */ - XMEMSET(&revDate, 0, sizeof(revDate)); - if (wolfSSL_ASN1_TIME_adj(&revDate, XTIME(NULL), 0, 0) == NULL) { - WOLFSSL_MSG("Failed to get current time"); - return BAD_STATE_E; + entry = crl->crlList; + if (entry == NULL) { + return BAD_FUNC_ARG; } { @@ -2427,8 +2462,25 @@ int wolfSSL_X509_CRL_add_revoked(WOLFSSL_X509_CRL* crl, rc->serialSz = serialSz; } - XMEMCPY(rc->revDate, revDate.data, revDate.length); - rc->revDateFormat = (byte)revDate.type; + /* Use caller-provided revocation date, or fall back to current time */ + if (rev->revocationDate != NULL && rev->revocationDate->length > 0) { + XMEMCPY(rc->revDate, rev->revocationDate->data, + (size_t)rev->revocationDate->length); + rc->revDateFormat = (byte)rev->revocationDate->type; + } + else { + WOLFSSL_ASN1_TIME revDate; + XMEMSET(&revDate, 0, sizeof(revDate)); + if (wolfSSL_ASN1_TIME_adj(&revDate, XTIME(NULL), 0, 0) == NULL) { + WOLFSSL_MSG("Failed to get current time"); + XFREE(rc, crl->heap, DYNAMIC_TYPE_REVOKED); + return BAD_STATE_E; + } + XMEMCPY(rc->revDate, revDate.data, revDate.length); + rc->revDateFormat = (byte)revDate.type; + } + + rc->reasonCode = rev->reason; rc->next = NULL; /* Add to end of list */ @@ -2442,6 +2494,12 @@ int wolfSSL_X509_CRL_add_revoked(WOLFSSL_X509_CRL* crl, } entry->totalCerts++; + /* Invalidate cached STACK_OF(X509_REVOKED) since list changed */ + if (crl->revokedStack != NULL) { + wolfSSL_sk_pop_free(crl->revokedStack, NULL); + crl->revokedStack = NULL; + } + WOLFSSL_LEAVE("wolfSSL_X509_CRL_add_revoked", WOLFSSL_SUCCESS); return WOLFSSL_SUCCESS; } @@ -2513,7 +2571,9 @@ int wolfSSL_X509_CRL_add_revoked_cert(WOLFSSL_X509_CRL* crl, XMEMCPY(serialInt->data, cert->serial, cert->serialSz); serialInt->length = cert->serialSz; + XMEMSET(&revoked, 0, sizeof(revoked)); revoked.serialNumber = serialInt; + revoked.reason = CRL_REASON_NONE; /* Add the revoked certificate entry */ ret = wolfSSL_X509_CRL_add_revoked(crl, &revoked); diff --git a/src/ssl_sk.c b/src/ssl_sk.c index a8cf68d52e..3e8d5c4439 100644 --- a/src/ssl_sk.c +++ b/src/ssl_sk.c @@ -168,6 +168,7 @@ static void* wolfssl_sk_node_get_data(WOLFSSL_STACK* node, int no_static) case STACK_TYPE_X509_OBJ: case STACK_TYPE_DIST_POINT: case STACK_TYPE_X509_CRL: + case STACK_TYPE_X509_REVOKED: case STACK_TYPE_GENERAL_SUBTREE: default: ret = node->data.generic; @@ -213,6 +214,7 @@ static void wolfssl_sk_node_set_data(WOLFSSL_STACK* node, WOLF_STACK_TYPE type, case STACK_TYPE_X509_OBJ: case STACK_TYPE_DIST_POINT: case STACK_TYPE_X509_CRL: + case STACK_TYPE_X509_REVOKED: case STACK_TYPE_GENERAL_SUBTREE: default: node->data.generic = (void*)data; @@ -494,6 +496,7 @@ static int wolfssl_sk_dup_data(WOLFSSL_STACK* dst, WOLFSSL_STACK* src) case STACK_TYPE_BY_DIR_entry: case STACK_TYPE_BY_DIR_hash: case STACK_TYPE_DIST_POINT: + case STACK_TYPE_X509_REVOKED: case STACK_TYPE_GENERAL_SUBTREE: default: WOLFSSL_MSG("Unsupported stack type"); @@ -688,6 +691,7 @@ void* wolfSSL_sk_value(const WOLFSSL_STACK* sk, int i) case STACK_TYPE_X509_OBJ: case STACK_TYPE_DIST_POINT: case STACK_TYPE_X509_CRL: + case STACK_TYPE_X509_REVOKED: case STACK_TYPE_GENERAL_SUBTREE: default: val = sk->data.generic; @@ -940,6 +944,11 @@ static wolfSSL_sk_freefunc wolfssl_sk_get_free_func(WOLF_STACK_TYPE type) func = (wolfSSL_sk_freefunc)wolfSSL_X509_CRL_free; #endif break; + case STACK_TYPE_X509_REVOKED: + #if defined(HAVE_CRL) && defined(OPENSSL_EXTRA) + func = (wolfSSL_sk_freefunc)wolfSSL_X509_REVOKED_free; + #endif + break; case STACK_TYPE_CIPHER: /* Static copy kept in node. */ case STACK_TYPE_NULL: diff --git a/src/x509.c b/src/x509.c index fd46e89c40..50c5d1bc45 100644 --- a/src/x509.c +++ b/src/x509.c @@ -9571,16 +9571,16 @@ const WOLFSSL_ASN1_INTEGER* wolfSSL_X509_REVOKED_get0_serial_number(const return NULL; } -#ifndef NO_WOLFSSL_STUB const WOLFSSL_ASN1_TIME* wolfSSL_X509_REVOKED_get0_revocation_date(const WOLFSSL_X509_REVOKED *rev) { - WOLFSSL_STUB("wolfSSL_X509_REVOKED_get0_revocation_date"); + WOLFSSL_ENTER("wolfSSL_X509_REVOKED_get0_revocation_date"); - (void) rev; + if (rev != NULL) { + return rev->revocationDate; + } return NULL; } -#endif #ifndef NO_BIO @@ -10760,34 +10760,192 @@ WOLFSSL_ASN1_TIME* wolfSSL_X509_gmtime_adj(WOLFSSL_ASN1_TIME *s, long adj) } #endif -#ifndef NO_WOLFSSL_STUB -int wolfSSL_sk_X509_REVOKED_num(WOLFSSL_X509_REVOKED* revoked) +int wolfSSL_sk_X509_REVOKED_num(WOLFSSL_STACK* sk) { - (void)revoked; - WOLFSSL_STUB("sk_X509_REVOKED_num"); + WOLFSSL_ENTER("wolfSSL_sk_X509_REVOKED_num"); + if (sk != NULL) { + return (int)sk->num; + } return 0; } -#endif -#ifndef NO_WOLFSSL_STUB -WOLFSSL_X509_REVOKED* wolfSSL_X509_CRL_get_REVOKED(WOLFSSL_X509_CRL* crl) +/* Free a WOLFSSL_X509_REVOKED and all its owned memory. */ +void wolfSSL_X509_REVOKED_free(WOLFSSL_X509_REVOKED* rev) { - (void)crl; - WOLFSSL_STUB("X509_CRL_get_REVOKED"); + if (rev == NULL) { + return; + } + + wolfSSL_ASN1_INTEGER_free(rev->serialNumber); + wolfSSL_ASN1_TIME_free(rev->revocationDate); + + if (rev->extensions != NULL) { + wolfSSL_sk_pop_free(rev->extensions, NULL); + } + if (rev->issuer != NULL) { + wolfSSL_sk_pop_free(rev->issuer, NULL); + } + + XFREE(rev, NULL, DYNAMIC_TYPE_OPENSSL); +} + +#ifdef HAVE_CRL +/* Build a WOLFSSL_X509_REVOKED from an internal RevokedCert. + * Caller takes ownership of the returned object. */ +static WOLFSSL_X509_REVOKED* RevokedCertToRevoked(RevokedCert* rc, int seq) +{ + WOLFSSL_X509_REVOKED* rev; + WOLFSSL_ASN1_INTEGER* serial; + + if (rc == NULL) { + return NULL; + } + + rev = (WOLFSSL_X509_REVOKED*)XMALLOC(sizeof(WOLFSSL_X509_REVOKED), NULL, + DYNAMIC_TYPE_OPENSSL); + if (rev == NULL) { + return NULL; + } + XMEMSET(rev, 0, sizeof(WOLFSSL_X509_REVOKED)); + + /* Serial number */ + serial = wolfSSL_ASN1_INTEGER_new(); + if (serial == NULL) { + XFREE(rev, NULL, DYNAMIC_TYPE_OPENSSL); + return NULL; + } + if (rc->serialSz > 0 && rc->serialSz <= EXTERNAL_SERIAL_SIZE) { + serial->data = (unsigned char*)XMALLOC((size_t)rc->serialSz, NULL, + DYNAMIC_TYPE_OPENSSL); + if (serial->data == NULL) { + wolfSSL_ASN1_INTEGER_free(serial); + XFREE(rev, NULL, DYNAMIC_TYPE_OPENSSL); + return NULL; + } + XMEMCPY(serial->data, rc->serialNumber, (size_t)rc->serialSz); + serial->length = rc->serialSz; + serial->dataMax = rc->serialSz; + serial->isDynamic = 1; + } + rev->serialNumber = serial; + + /* Revocation date */ + { + WOLFSSL_ASN1_TIME* revDate = wolfSSL_ASN1_TIME_new(); + if (revDate != NULL) { + int dateLen = 0; + /* Determine date length from the format byte */ + if (rc->revDateFormat == ASN_UTC_TIME || + rc->revDateFormat == ASN_GENERALIZED_TIME) { + /* Find actual length: dates are null-terminated strings in + * revDate buffer up to MAX_DATE_SIZE */ + while (dateLen < MAX_DATE_SIZE && rc->revDate[dateLen] != 0) + dateLen++; + } + if (dateLen > 0 && dateLen < MAX_DATE_SIZE) { + XMEMCPY(revDate->data, rc->revDate, (size_t)dateLen); + revDate->length = dateLen; + revDate->type = rc->revDateFormat; + } + } + rev->revocationDate = revDate; + } + + /* Reason code */ + rev->reason = rc->reasonCode; + + /* Sequence (load order) */ + rev->sequence = seq; + + /* issuer: left as NULL (indirect CRL not yet supported) */ + /* extensions: left as NULL for now (raw DER available in RevokedCert + * but decoded STACK_OF(X509_EXTENSION) build not yet implemented) */ + + return rev; +} +#endif /* HAVE_CRL */ + +WOLFSSL_STACK* wolfSSL_X509_CRL_get_REVOKED(WOLFSSL_X509_CRL* crl) +{ + WOLFSSL_ENTER("wolfSSL_X509_CRL_get_REVOKED"); + + if (crl == NULL) { + return NULL; + } + +#if defined(OPENSSL_EXTRA) && defined(HAVE_CRL) + /* Return cached stack if already built */ + if (crl->revokedStack != NULL) { + return crl->revokedStack; + } + + /* Build the stack from the internal RevokedCert linked list */ + if (crl->crlList != NULL) { + WOLFSSL_STACK* sk; + RevokedCert* rc; + int seq = 0; + + sk = wolfSSL_sk_new_null(); + if (sk == NULL) { + return NULL; + } + sk->type = STACK_TYPE_X509_REVOKED; + + for (rc = crl->crlList->certs; rc != NULL; rc = rc->next) { + WOLFSSL_X509_REVOKED* rev = RevokedCertToRevoked(rc, seq); + if (rev == NULL) { + /* Clean up on failure */ + wolfSSL_sk_pop_free(sk, NULL); + return NULL; + } + /* Push to stack. wolfSSL_sk_push returns total count on success. */ + if (wolfSSL_sk_push(sk, rev) <= 0) { + wolfSSL_X509_REVOKED_free(rev); + wolfSSL_sk_pop_free(sk, NULL); + return NULL; + } + seq++; + } + + crl->revokedStack = sk; + return sk; + } +#endif /* OPENSSL_EXTRA && HAVE_CRL */ + return NULL; } -#endif -#ifndef NO_WOLFSSL_STUB WOLFSSL_X509_REVOKED* wolfSSL_sk_X509_REVOKED_value( - WOLFSSL_X509_REVOKED* revoked, int value) + WOLFSSL_STACK* sk, int idx) { - (void)revoked; - (void)value; - WOLFSSL_STUB("sk_X509_REVOKED_value"); + WOLFSSL_ENTER("wolfSSL_sk_X509_REVOKED_value"); + + if (sk == NULL) { + return NULL; + } + + return (WOLFSSL_X509_REVOKED*)wolfSSL_sk_value(sk, idx); +} + +/* Extension accessors for WOLFSSL_X509_REVOKED */ +int wolfSSL_X509_REVOKED_get_ext_count(const WOLFSSL_X509_REVOKED* rev) +{ + WOLFSSL_ENTER("wolfSSL_X509_REVOKED_get_ext_count"); + if (rev != NULL && rev->extensions != NULL) { + return (int)rev->extensions->num; + } + return 0; +} + +WOLFSSL_X509_EXTENSION* wolfSSL_X509_REVOKED_get_ext( + const WOLFSSL_X509_REVOKED* rev, int loc) +{ + WOLFSSL_ENTER("wolfSSL_X509_REVOKED_get_ext"); + if (rev != NULL && rev->extensions != NULL) { + return (WOLFSSL_X509_EXTENSION*)wolfSSL_sk_value(rev->extensions, loc); + } return NULL; } -#endif #endif /* OPENSSL_EXTRA */ diff --git a/tests/api.c b/tests/api.c index 0cda4e114a..ea88cf29e6 100644 --- a/tests/api.c +++ b/tests/api.c @@ -20956,6 +20956,9 @@ static int test_sk_X509_CRL_decode(void) WOLFSSL_ASN1_INTEGER* asnInt = NULL; const WOLFSSL_ASN1_INTEGER* sn = NULL; + XMEMSET(&revoked, 0, sizeof(revoked)); + revoked.reason = CRL_REASON_NONE; + #if (!defined(NO_FILESYSTEM) && !defined(NO_STDIO_FILESYSTEM)) || \ !defined(NO_BIO) XMEMSET(&empty, 0, sizeof(X509_CRL)); @@ -21075,13 +21078,32 @@ static int test_sk_X509_CRL_decode(void) WOLFSSL_SUCCESS); ExpectIntEQ(len, 1); -#ifndef NO_WOLFSSL_STUB + /* Test X509_CRL_get_REVOKED and stack iteration */ ExpectIntEQ(wolfSSL_sk_X509_REVOKED_num(NULL), 0); - ExpectIntEQ(wolfSSL_sk_X509_REVOKED_num(&revoked), 0); ExpectNull(wolfSSL_X509_CRL_get_REVOKED(NULL)); - ExpectNull(wolfSSL_X509_CRL_get_REVOKED(crl)); - ExpectNull(wolfSSL_sk_X509_REVOKED_value(NULL, 0)); - ExpectNull(wolfSSL_sk_X509_REVOKED_value(&revoked, 0)); + { + WOLFSSL_STACK* revokedSk = NULL; + ExpectNotNull(revokedSk = wolfSSL_X509_CRL_get_REVOKED(crl)); + if (revokedSk != NULL) { + int numRevoked = wolfSSL_sk_X509_REVOKED_num(revokedSk); + ExpectIntGT(numRevoked, 0); + /* Verify first revoked entry has a serial number */ + { + WOLFSSL_X509_REVOKED* r = NULL; + ExpectNotNull(r = wolfSSL_sk_X509_REVOKED_value(revokedSk, 0)); + if (r != NULL) { + ExpectNotNull(r->serialNumber); + /* Verify revocation date is populated */ + ExpectNotNull( + wolfSSL_X509_REVOKED_get0_revocation_date(r)); + /* Verify sequence field (first entry should be 0) */ + ExpectIntEQ(r->sequence, 0); + } + } + } + ExpectNull(wolfSSL_sk_X509_REVOKED_value(NULL, 0)); + } +#ifndef NO_WOLFSSL_STUB ExpectIntEQ(wolfSSL_X509_CRL_verify(NULL, NULL), 0); ExpectIntEQ(X509_OBJECT_set1_X509_CRL(NULL, NULL), 0); ExpectIntEQ(X509_OBJECT_set1_X509(NULL, NULL), 0); @@ -21097,10 +21119,9 @@ static int test_sk_X509_CRL_decode(void) ExpectNull(wolfSSL_X509_REVOKED_get0_serial_number(NULL)); ExpectNotNull(sn = wolfSSL_X509_REVOKED_get0_serial_number(&revoked)); ExpectPtrEq(sn, asnInt); -#ifndef NO_WOLFSSL_STUB ExpectNull(wolfSSL_X509_REVOKED_get0_revocation_date(NULL)); + /* revoked on stack has no revocationDate set, so should be NULL */ ExpectNull(wolfSSL_X509_REVOKED_get0_revocation_date(&revoked)); -#endif wolfSSL_ASN1_INTEGER_free(asnInt); ExpectTrue((fp = XFOPEN("./certs/crl/crl.pem", "rb")) != XBADFILE); @@ -21130,6 +21151,71 @@ static int test_sk_X509_CRL_decode(void) return EXPECT_RESULT(); } +#if (defined(OPENSSL_ALL) || defined(OPENSSL_EXTRA)) && !defined(NO_CERTS) && \ + defined(HAVE_CRL) && defined(WOLFSSL_CERT_GEN) +/* Ensure oversized caller-provided revocationDate is rejected. */ +static int test_wolfSSL_X509_CRL_add_revoked_oversized_revocation_date(void) +{ + EXPECT_DECLS; + WOLFSSL_X509_CRL* crl = NULL; + WOLFSSL_ASN1_INTEGER* serial = NULL; + WOLFSSL_X509_REVOKED revoked; + WOLFSSL_ASN1_TIME revDate; + byte serialData[] = { 0x01 }; + + XMEMSET(&revoked, 0, sizeof(revoked)); + XMEMSET(&revDate, 0, sizeof(revDate)); + + ExpectNotNull(crl = wolfSSL_X509_CRL_new()); + ExpectNotNull(serial = wolfSSL_ASN1_INTEGER_new()); + if (serial != NULL) { + serial->data = serialData; + serial->dataMax = (int)sizeof(serialData); + serial->length = (int)sizeof(serialData); + serial->isDynamic = 0; + } + + revDate.length = MAX_DATE_SIZE + 1; /* intentionally too large */ + revDate.type = ASN_GENERALIZED_TIME; + + revoked.serialNumber = serial; + revoked.revocationDate = &revDate; + revoked.reason = CRL_REASON_NONE; + + ExpectIntEQ(wolfSSL_X509_CRL_add_revoked(crl, &revoked), BAD_FUNC_ARG); + + wolfSSL_ASN1_INTEGER_free(serial); + wolfSSL_X509_CRL_free(crl); + + return EXPECT_RESULT(); +} +#endif + +#if (defined(OPENSSL_ALL) || defined(OPENSSL_EXTRA)) && !defined(NO_CERTS) && \ + defined(HAVE_CRL) && !defined(NO_FILESYSTEM) && \ + !defined(NO_STDIO_FILESYSTEM) +/* Ensure reason-code parsing handles optional critical BOOLEAN in entry ext. */ +static int test_wolfSSL_X509_CRL_reason_critical_boolean(void) +{ + EXPECT_DECLS; + int reasonCode = CRL_REASON_NONE; + /* One Extension SEQUENCE with: + * OID: 2.5.29.21 (CRL Reason Code) + * critical: TRUE + * extnValue: OCTET STRING wrapping ENUMERATED 2 (CA compromise) */ + static const byte ext[] = { + 0x30, 0x0d, 0x06, 0x03, 0x55, 0x1d, 0x15, + 0x01, 0x01, 0xff, 0x04, 0x03, 0x0a, 0x01, 0x02 + }; + + ExpectIntEQ(wc_ParseCRLReasonFromExtensions(ext, (word32)sizeof(ext), + &reasonCode), 0); + ExpectIntEQ(reasonCode, CRL_REASON_CA_COMPROMISE); + + return EXPECT_RESULT(); +} +#endif + #if (defined(OPENSSL_ALL) || defined(OPENSSL_EXTRA)) && !defined(NO_CERTS) && \ defined(HAVE_CRL) && !defined(NO_FILESYSTEM) && \ !defined(NO_STDIO_FILESYSTEM) && defined(WOLFSSL_CERT_GEN) @@ -21176,9 +21262,9 @@ static int generate_crl_test(const char* keyFile, const char* certFile, .type = 0 } }; WOLFSSL_X509_REVOKED revoked[3] = { - { .serialNumber = &serialsToRevoke[0] }, - { .serialNumber = &serialsToRevoke[1] }, - { .serialNumber = &serialsToRevoke[2] } + { .serialNumber = &serialsToRevoke[0], .reason = CRL_REASON_NONE }, + { .serialNumber = &serialsToRevoke[1], .reason = CRL_REASON_NONE }, + { .serialNumber = &serialsToRevoke[2], .reason = CRL_REASON_NONE } }; WOLFSSL_X509* certToRevoke = NULL; @@ -21451,7 +21537,10 @@ static int test_wolfSSL_X509_CRL_sign_large(void) ExpectIntEQ(wolfSSL_X509_CRL_set_nextUpdate(crl, &asnTime), WOLFSSL_SUCCESS); - ExpectNotNull(revoked.serialNumber = wolfSSL_ASN1_INTEGER_new()); + XMEMSET(&revoked, 0, sizeof(revoked)); + revoked.serialNumber = wolfSSL_ASN1_INTEGER_new(); + ExpectNotNull(revoked.serialNumber); + revoked.reason = CRL_REASON_NONE; if (revoked.serialNumber != NULL) { revoked.serialNumber->data = serial; revoked.serialNumber->length = (int)sizeof(serial); @@ -32804,6 +32893,15 @@ TEST_CASE testCases[] = { TEST_DECL(test_sk_X509), /* OpenSSL sk_X509_CRL API test */ TEST_DECL(test_sk_X509_CRL_decode), +#if (defined(OPENSSL_ALL) || defined(OPENSSL_EXTRA)) && !defined(NO_CERTS) && \ + defined(HAVE_CRL) && defined(WOLFSSL_CERT_GEN) + TEST_DECL(test_wolfSSL_X509_CRL_add_revoked_oversized_revocation_date), +#endif +#if (defined(OPENSSL_ALL) || defined(OPENSSL_EXTRA)) && !defined(NO_CERTS) && \ + defined(HAVE_CRL) && !defined(NO_FILESYSTEM) && \ + !defined(NO_STDIO_FILESYSTEM) + TEST_DECL(test_wolfSSL_X509_CRL_reason_critical_boolean), +#endif TEST_DECL(test_sk_X509_CRL_encode), TEST_DECL(test_wolfSSL_X509_CRL_sign_large), diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index 231d250b6e..436dd6c434 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -2760,7 +2760,7 @@ static int SetASNNull(byte* output) #endif #ifndef NO_CERTS -#ifndef WOLFSSL_ASN_TEMPLATE +#if !defined(WOLFSSL_ASN_TEMPLATE) || defined(HAVE_CRL) /* Get the DER/BER encoding of an ASN.1 BOOLEAN. * * input Buffer holding DER/BER encoded data. @@ -2770,7 +2770,7 @@ static int SetASNNull(byte* output) * ASN_PARSE_E when the BOOLEAN tag is not found or length is not 1. * Otherwise, 0 to indicate the value was false and 1 to indicate true. */ -static int GetBoolean(const byte* input, word32* inOutIdx, word32 maxIdx) +WC_MAYBE_UNUSED static int GetBoolean(const byte* input, word32* inOutIdx, word32 maxIdx) { word32 idx = *inOutIdx; byte b; @@ -2790,7 +2790,7 @@ static int GetBoolean(const byte* input, word32* inOutIdx, word32 maxIdx) *inOutIdx = idx; return b; } -#endif +#endif /* !WOLFSSL_ASN_TEMPLATE || HAVE_CRL */ #endif /* !NO_CERTS*/ @@ -41047,6 +41047,77 @@ enum { #define revokedASN_Length (sizeof(revokedASN) / sizeof(ASNItem)) #endif +/* CRL Reason Code OID: 2.5.29.21 */ +static const byte crlReasonOid[] = { 0x55, 0x1d, 0x15 }; + +/* Parse CRL entry extensions to extract the reason code. + * Sets *reasonCode if found, otherwise leaves it unchanged. */ +static void ParseCRL_ReasonCode(const byte* buff, word32 idx, word32 maxIdx, + int* reasonCode) +{ + while (idx < maxIdx) { + int len; + word32 end; + word32 localIdx; + byte tag; + + /* Each extension is a SEQUENCE */ + if (GetSequence(buff, &idx, &len, maxIdx) < 0) { + break; + } + end = idx + (word32)len; + + /* Check for CRL Reason OID: 2.5.29.21 */ + if (end - idx >= (word32)(2 + sizeof(crlReasonOid)) && + buff[idx] == ASN_OBJECT_ID && + buff[idx + 1] == sizeof(crlReasonOid) && + XMEMCMP(buff + idx + 2, crlReasonOid, + sizeof(crlReasonOid)) == 0) { + /* Skip past the OID */ + idx += 2 + (word32)sizeof(crlReasonOid); + /* Skip optional critical BOOLEAN */ + localIdx = idx; + if (GetASNTag(buff, &localIdx, &tag, end) == 0 && + tag == ASN_BOOLEAN) { + /* Consume full BOOLEAN TLV (tag + length + value). */ + if (GetBoolean(buff, &idx, end) < 0) { + break; + } + } + /* Get OCTET STRING wrapping the ENUMERATED */ + if (GetOctetString(buff, &idx, &len, end) >= 0) { + /* Parse ENUMERATED reason value */ + localIdx = idx; + if (GetASNTag(buff, &localIdx, &tag, end) == 0 && + tag == ASN_ENUMERATED) { + int reasonLen; + idx = localIdx; + if (GetLength(buff, &idx, &reasonLen, end) >= 0 && + reasonLen == 1) { + *reasonCode = (int)buff[idx]; + } + } + } + } + idx = end; + } +} + +#ifdef HAVE_CRL +/* Test-visible helper: parse reasonCode from encoded Extension list bytes. */ +WOLFSSL_TEST_VIS int wc_ParseCRLReasonFromExtensions(const byte* ext, + word32 extSz, + int* reasonCode) +{ + if (ext == NULL || reasonCode == NULL) { + return BAD_FUNC_ARG; + } + + ParseCRL_ReasonCode(ext, 0, extSz, reasonCode); + return 0; +} +#endif + /* Get Revoked Cert list, 0 on success */ static int GetRevoked(RevokedCert* rcert, const byte* buff, word32* idx, DecodedCRL* dcrl, word32 maxIdx) @@ -41087,6 +41158,7 @@ static int GetRevoked(RevokedCert* rcert, const byte* buff, word32* idx, WOLFSSL_MSG("Alloc Revoked Cert failed"); return MEMORY_E; } + XMEMSET(rc, 0, sizeof(RevokedCert)); ret = wc_GetSerialNumber(buff, idx, rc->serialNumber, &rc->serialSz,maxIdx); if (ret < 0) { WOLFSSL_MSG("wc_GetSerialNumber error"); @@ -41108,7 +41180,41 @@ static int GetRevoked(RevokedCert* rcert, const byte* buff, word32* idx, return ret; } #endif - /* skip extensions */ + /* Initialize reason code to absent */ + rc->reasonCode = -1; + + /* Parse CRL entry extensions if present */ + if (*idx < end) { + word32 extIdx = *idx; + int extLen; + byte tag; + + /* Check for SEQUENCE tag (extensions wrapper) */ + if (GetASNTag(buff, &extIdx, &tag, end) == 0 && + tag == (ASN_SEQUENCE | ASN_CONSTRUCTED)) { + word32 seqIdx = extIdx - 1; /* back up to re-read tag */ + if (GetSequence(buff, &seqIdx, &extLen, end) >= 0) { + word32 extEnd = seqIdx + (word32)extLen; + +#if defined(OPENSSL_EXTRA) + /* Store raw DER of extensions for OpenSSL compat API */ + { + word32 rawStart = *idx; + word32 rawLen = end - rawStart; + rc->extensions = (byte*)XMALLOC(rawLen, dcrl->heap, + DYNAMIC_TYPE_REVOKED); + if (rc->extensions != NULL) { + XMEMCPY(rc->extensions, buff + rawStart, rawLen); + rc->extensionsSz = rawLen; + } + } +#endif + + ParseCRL_ReasonCode(buff, seqIdx, extEnd, &rc->reasonCode); + } + } + } + *idx = end; return 0; @@ -41134,6 +41240,9 @@ static int GetRevoked(RevokedCert* rcert, const byte* buff, word32* idx, if (rc == NULL) { ret = MEMORY_E; } + if (ret == 0) { + XMEMSET(rc, 0, sizeof(RevokedCert)); + } #endif /* CRL_STATIC_REVOKED_LIST */ CALLOC_ASNGETDATA(dataASN, revokedASN_Length, ret, dcrl->heap); @@ -41158,7 +41267,39 @@ static int GetRevoked(RevokedCert* rcert, const byte* buff, word32* idx, ? dataASN[REVOKEDASN_IDX_TIME_UTC].tag : dataASN[REVOKEDASN_IDX_TIME_GT].tag; - /* TODO: use extensions, only v2 */ + /* Initialize reason code to absent */ + rc->reasonCode = -1; + + /* Parse CRL entry extensions (v2 only) */ + if (dataASN[REVOKEDASN_IDX_TIME_EXT].length > 0) { + word32 extOff = dataASN[REVOKEDASN_IDX_TIME_EXT].offset; + word32 extLen = dataASN[REVOKEDASN_IDX_TIME_EXT].length; + word32 extEnd = extOff + extLen; + word32 extIdx2 = extOff; + +#if defined(OPENSSL_EXTRA) + /* Store raw DER of extensions for OpenSSL compat API. + * Include the outer SEQUENCE tag+length. */ + { + /* Back up to include the SEQUENCE header. We know the + * content starts at extOff, so the header is just before. + * Use the raw buffer start from before GetASN_Items. */ + word32 seqHdrSz = 0; + /* The outer SEQUENCE header is at most 4 bytes before + * content. Rather than guess, store just the content. */ + rc->extensions = (byte*)XMALLOC(extLen, dcrl->heap, + DYNAMIC_TYPE_REVOKED); + if (rc->extensions != NULL) { + XMEMCPY(rc->extensions, buff + extOff, extLen); + rc->extensionsSz = extLen; + } + (void)seqHdrSz; + } +#endif + + ParseCRL_ReasonCode(buff, extIdx2, extEnd, &rc->reasonCode); + } + /* Add revoked certificate to chain. */ #ifndef CRL_STATIC_REVOKED_LIST rc->next = dcrl->certs; @@ -41170,6 +41311,9 @@ static int GetRevoked(RevokedCert* rcert, const byte* buff, word32* idx, FREE_ASNGETDATA(dataASN, dcrl->heap); #ifndef CRL_STATIC_REVOKED_LIST if ((ret != 0) && (rc != NULL)) { +#if defined(OPENSSL_EXTRA) + XFREE(rc->extensions, dcrl->heap, DYNAMIC_TYPE_REVOKED); +#endif XFREE(rc, dcrl->heap, DYNAMIC_TYPE_CRL); } (void)rcert; diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 18d7d10213..185098a3be 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -2589,6 +2589,9 @@ struct WOLFSSL_CRL { #endif #ifdef OPENSSL_ALL wolfSSL_Ref ref; +#endif +#if defined(OPENSSL_EXTRA) + WOLFSSL_STACK* revokedStack; /* cached STACK_OF(X509_REVOKED) */ #endif void* heap; /* heap hint for dynamic memory */ }; @@ -5318,6 +5321,7 @@ typedef enum { STACK_TYPE_X509_NAME_ENTRY = 17, STACK_TYPE_X509_REQ_ATTR = 18, STACK_TYPE_GENERAL_SUBTREE = 19, + STACK_TYPE_X509_REVOKED = 20, } WOLF_STACK_TYPE; #if defined(OPENSSL_EXTRA) || defined(OPENSSL_EXTRA_X509_SMALL) @@ -5352,6 +5356,7 @@ struct WOLFSSL_STACK { WOLFSSL_X509_OBJECT* x509_obj; WOLFSSL_DIST_POINT* dp; WOLFSSL_X509_CRL* crl; + WOLFSSL_X509_REVOKED* revoked; } data; void* heap; /* memory heap hint */ WOLFSSL_STACK* next; diff --git a/wolfssl/openssl/ssl.h b/wolfssl/openssl/ssl.h index aa954cd6b1..2d4d6aaadd 100644 --- a/wolfssl/openssl/ssl.h +++ b/wolfssl/openssl/ssl.h @@ -882,6 +882,9 @@ wolfSSL_X509_STORE_set_verify_cb((WOLFSSL_X509_STORE *)(s), (WOLFSSL_X509_STORE_ #define X509_REVOKED_get0_serialNumber wolfSSL_X509_REVOKED_get0_serial_number #define X509_REVOKED_get0_revocationDate wolfSSL_X509_REVOKED_get0_revocation_date +#define X509_REVOKED_free wolfSSL_X509_REVOKED_free +#define X509_REVOKED_get_ext_count wolfSSL_X509_REVOKED_get_ext_count +#define X509_REVOKED_get_ext wolfSSL_X509_REVOKED_get_ext #define X509_check_purpose(x, id, ca) 0 diff --git a/wolfssl/openssl/x509v3.h b/wolfssl/openssl/x509v3.h index 0bc04abb08..480242c325 100644 --- a/wolfssl/openssl/x509v3.h +++ b/wolfssl/openssl/x509v3.h @@ -98,6 +98,24 @@ struct WOLFSSL_X509_EXTENSION { #define WOLFSSL_GEN_RID 8 #define WOLFSSL_GEN_IA5 9 +/* CRL reason codes per RFC 5280 section 5.3.1 */ +#ifndef CRL_REASON_UNSPECIFIED +#ifndef CRL_REASON_NONE +#define CRL_REASON_NONE (-1) +#endif +#define CRL_REASON_UNSPECIFIED 0 +#define CRL_REASON_KEY_COMPROMISE 1 +#define CRL_REASON_CA_COMPROMISE 2 +#define CRL_REASON_AFFILIATION_CHANGED 3 +#define CRL_REASON_SUPERSEDED 4 +#define CRL_REASON_CESSATION_OF_OPERATION 5 +#define CRL_REASON_CERTIFICATE_HOLD 6 +/* value 7 is not used */ +#define CRL_REASON_REMOVE_FROM_CRL 8 +#define CRL_REASON_PRIVILEGE_WITHDRAWN 9 +#define CRL_REASON_AA_COMPROMISE 10 +#endif + typedef WOLF_STACK_OF(WOLFSSL_ACCESS_DESCRIPTION) WOLFSSL_AUTHORITY_INFO_ACCESS; WOLFSSL_API WOLFSSL_BASIC_CONSTRAINTS* wolfSSL_BASIC_CONSTRAINTS_new(void); diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index 2fdbfa4a94..8d56767dc4 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -885,10 +885,14 @@ struct WOLFSSL_X509_VERIFY_PARAM { #endif /* OPENSSL_EXTRA || WOLFSSL_WPAS_SMALL */ typedef struct WOLFSSL_X509_REVOKED { - WOLFSSL_ASN1_INTEGER* serialNumber; /* stunnel dereference */ - /* TODO: add other fields to match OpenSSL's X509_REVOKED - * (struct x509_revoked_st) such as revocationDate, extensions, etc. - * Then update wolfSSL_X509_CRL_add_revoked to handle those fields. */ + WOLFSSL_ASN1_INTEGER* serialNumber; /* certificate serial number */ + WOLFSSL_ASN1_TIME* revocationDate; /* revocation date */ + WOLFSSL_STACK* extensions; /* STACK_OF(X509_EXTENSION) */ + WOLFSSL_STACK* issuer; /* STACK_OF(GENERAL_NAME) for + * indirect CRL (currently NULL) */ + int reason; /* CRL reason code, -1 if absent */ + int sequence; /* tracks original order of entries + * in the CRL for iteration */ } WOLFSSL_X509_REVOKED; typedef enum { @@ -2447,7 +2451,7 @@ WOLFSSL_API WOLFSSL_ASN1_TIME *wolfSSL_X509_time_adj(WOLFSSL_ASN1_TIME *asnTime, long offset_sec, time_t *in_tm); WOLFSSL_API WOLFSSL_ASN1_TIME* wolfSSL_X509_gmtime_adj(WOLFSSL_ASN1_TIME* s, long adj); -WOLFSSL_API int wolfSSL_sk_X509_REVOKED_num(WOLFSSL_X509_REVOKED* revoked); +WOLFSSL_API int wolfSSL_sk_X509_REVOKED_num(WOLFSSL_STACK* sk); WOLFSSL_API void wolfSSL_X509_STORE_CTX_set_time(WOLFSSL_X509_STORE_CTX* ctx, unsigned long flags, time_t t); @@ -2479,9 +2483,9 @@ WOLFSSL_API int wolfSSL_X509_load_crl_file(WOLFSSL_X509_LOOKUP *ctx, WOLFSSL_API int wolfSSL_X509_load_cert_crl_file(WOLFSSL_X509_LOOKUP *ctx, const char *file, int type); #endif -WOLFSSL_API WOLFSSL_X509_REVOKED* wolfSSL_X509_CRL_get_REVOKED(WOLFSSL_X509_CRL* crl); +WOLFSSL_API WOLFSSL_STACK* wolfSSL_X509_CRL_get_REVOKED(WOLFSSL_X509_CRL* crl); WOLFSSL_API WOLFSSL_X509_REVOKED* wolfSSL_sk_X509_REVOKED_value( - WOLFSSL_X509_REVOKED* revoked,int value); + WOLFSSL_STACK* sk, int idx); WOLFSSL_API WOLFSSL_ASN1_INTEGER* wolfSSL_X509_get_serialNumber(WOLFSSL_X509* x509); WOLFSSL_API void wolfSSL_ASN1_INTEGER_free(WOLFSSL_ASN1_INTEGER* in); WOLFSSL_API WOLFSSL_ASN1_INTEGER* wolfSSL_ASN1_INTEGER_new(void); @@ -3589,6 +3593,11 @@ const WOLFSSL_ASN1_INTEGER* wolfSSL_X509_REVOKED_get0_serial_number(const WOLFSSL_API const WOLFSSL_ASN1_TIME* wolfSSL_X509_REVOKED_get0_revocation_date(const WOLFSSL_X509_REVOKED *rev); +WOLFSSL_API void wolfSSL_X509_REVOKED_free(WOLFSSL_X509_REVOKED* rev); +WOLFSSL_API int wolfSSL_X509_REVOKED_get_ext_count( + const WOLFSSL_X509_REVOKED* rev); +WOLFSSL_API WOLFSSL_X509_EXTENSION* wolfSSL_X509_REVOKED_get_ext( + const WOLFSSL_X509_REVOKED* rev, int loc); #ifndef NO_FILESYSTEM #ifndef NO_STDIO_FILESYSTEM diff --git a/wolfssl/wolfcrypt/asn.h b/wolfssl/wolfcrypt/asn.h index 340f189ec5..a59a5d431b 100644 --- a/wolfssl/wolfcrypt/asn.h +++ b/wolfssl/wolfcrypt/asn.h @@ -2412,6 +2412,11 @@ WOLFSSL_LOCAL int wc_ValidateDateWithTime(const byte* date, byte format, #endif WOLFSSL_TEST_VIS int wc_AsnSetSkipDateCheck(int skip_p); WOLFSSL_LOCAL int wc_AsnGetSkipDateCheck(void); +#ifdef HAVE_CRL +WOLFSSL_TEST_VIS int wc_ParseCRLReasonFromExtensions(const byte* ext, + word32 extSz, + int* reasonCode); +#endif /* ASN.1 helper functions */ #ifdef WOLFSSL_CERT_GEN @@ -2842,6 +2847,11 @@ struct RevokedCert { RevokedCert* next; byte revDate[MAX_DATE_SIZE]; byte revDateFormat; + int reasonCode; /* CRL reason code, -1 if absent */ +#if defined(OPENSSL_EXTRA) + byte* extensions; /* raw DER of crlEntryExtensions */ + word32 extensionsSz; +#endif }; #ifndef CRL_MAX_NUM_SZ