From 5b827625ab584a4176fb1727f84b70b84769c505 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Mon, 29 Jun 2026 14:59:31 -0700 Subject: [PATCH] asn: zero-allocation X.509 certificate verification under WOLFSSL_NO_MALLOC --- wolfcrypt/src/asn.c | 113 +++++++++++++++++++++++++++++++++------- wolfcrypt/test/test.c | 37 +++++++++++++ wolfssl/wolfcrypt/asn.h | 22 ++++++++ 3 files changed, 154 insertions(+), 18 deletions(-) diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index 7e855986f67..f4a5fc06e7d 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -12471,7 +12471,10 @@ void FreeAltNames(DNS_entry* altNames, void* heap) altNames->ridStringStored = 0; } #endif - XFREE(altNames, heap, DYNAMIC_TYPE_ALTNAME); + /* Only free heap nodes; no-heap pool nodes aren't owned. */ + if (altNames->entryStored) { + XFREE(altNames, heap, DYNAMIC_TYPE_ALTNAME); + } altNames = tmp; } } @@ -12483,6 +12486,7 @@ DNS_entry* AltNameNew(void* heap) ret = (DNS_entry*)XMALLOC(sizeof(DNS_entry), heap, DYNAMIC_TYPE_ALTNAME); if (ret != NULL) { XMEMSET(ret, 0, sizeof(DNS_entry)); + ret->entryStored = 1; /* heap-allocated node; FreeAltNames frees it */ } (void)heap; return ret; @@ -12631,7 +12635,9 @@ static int StoreKey(DecodedCert* cert, const byte* source, word32* srcIdx, { int ret; int length; +#ifndef WC_ASN_NO_HEAP byte* publicKey; +#endif ret = CheckBitString(source, srcIdx, &length, maxIdx, 1, NULL); if (ret == 0) { @@ -12641,6 +12647,17 @@ static int StoreKey(DecodedCert* cert, const byte* source, word32* srcIdx, } if (ret == 0) { #endif +#ifdef WC_ASN_NO_HEAP + /* No heap: reference the key in place; source must outlive the cert. */ + cert->publicKey = (byte*)&source[*srcIdx]; + cert->pubKeyStored = 0; + cert->pubKeySize = (word32)length; + #ifdef HAVE_OCSP_RESPONDER + cert->publicKeyForHash = cert->publicKey; + cert->pubKeyForHashSize = cert->pubKeySize; + #endif + *srcIdx += (word32)length; +#else publicKey = (byte*)XMALLOC((size_t)length, cert->heap, DYNAMIC_TYPE_PUBLIC_KEY); if (publicKey == NULL) { @@ -12659,6 +12676,7 @@ static int StoreKey(DecodedCert* cert, const byte* source, word32* srcIdx, *srcIdx += (word32)length; } +#endif } return ret; @@ -13299,7 +13317,9 @@ static int StoreEccKey(DecodedCert* cert, const byte* source, word32* srcIdx, { int ret = 0; DECL_ASNGETDATA(dataASN, eccCertKeyASN_Length); +#ifndef WC_ASN_NO_HEAP byte* publicKey; +#endif /* Validate parameters. */ if (pubKey == NULL) { @@ -13369,6 +13389,11 @@ static int StoreEccKey(DecodedCert* cert, const byte* source, word32* srcIdx, #endif /* Store public key data length. */ cert->pubKeySize = pubKeyLen; +#ifdef WC_ASN_NO_HEAP + /* No heap: reference the key in place; source must outlive the cert. */ + cert->publicKey = (byte*)pubKey; + cert->pubKeyStored = 0; +#else /* Must allocated space for key. * Don't memcpy into constant pointer so use temp. */ publicKey = (byte*)XMALLOC(cert->pubKeySize, cert->heap, @@ -13383,6 +13408,7 @@ static int StoreEccKey(DecodedCert* cert, const byte* source, word32* srcIdx, /* Indicate publicKey needs to be freed. */ cert->pubKeyStored = 1; } +#endif } FREE_ASNGETDATA(dataASN, cert->heap); @@ -14466,13 +14492,55 @@ static int AddDNSEntryToList(DNS_entry** lst, DNS_entry* entry) * @return 0 on success. * @return MEMORY_E when dynamic memory allocation fails. */ -static int SetDNSEntry(void* heap, const char* str, int strLen, - int type, DNS_entry** entries) +/* No-heap SAN entries come from a caller pool; pass NULL,NULL if none. */ +#ifdef WC_ASN_NO_HEAP + #define WC_DNS_POOL(obj) (obj)->altNamePool, &(obj)->altNamePoolUsed +#else + #define WC_DNS_POOL(obj) NULL, NULL +#endif + +static int SetDNSEntry(void* heap, DNS_entry* pool, word32* poolUsed, + const char* str, int strLen, int type, + DNS_entry** entries) { DNS_entry* dnsEntry; int ret = 0; +#ifndef WC_ASN_NO_HEAP char *dnsEntry_name = NULL; +#endif +#ifdef WC_ASN_NO_HEAP + /* No heap: borrow a pool slot; name points into the source DER. */ + (void)heap; +#ifdef WOLFSSL_IP_ALT_NAME + /* No-heap path can't synthesise an ipString; reject rather than skip. */ + if (type == ASN_IP_TYPE) { + return ASN_PARSE_E; + } +#endif +#ifdef WOLFSSL_RID_ALT_NAME + if (type == ASN_RID_TYPE) { + return ASN_PARSE_E; + } +#endif + if ((pool == NULL) || (*poolUsed >= WC_ASN_MAX_ALTNAMES)) { + ret = MEMORY_E; + dnsEntry = NULL; + } + else { + dnsEntry = &pool[(*poolUsed)++]; + XMEMSET(dnsEntry, 0, sizeof(*dnsEntry)); + dnsEntry->type = type; + dnsEntry->len = strLen; + dnsEntry->name = (char*)str; /* points into the source DER */ + dnsEntry->nameStored = 0; + } + if (ret == 0) { + ret = AddDNSEntryToList(entries, dnsEntry); + } +#else + (void)pool; + (void)poolUsed; /* TODO: consider one malloc. */ /* Allocate DNS Entry object. */ dnsEntry = AltNameNew(heap); @@ -14517,6 +14585,7 @@ static int SetDNSEntry(void* heap, const char* str, int strLen, XFREE(dnsEntry_name, heap, DYNAMIC_TYPE_ALTNAME); XFREE(dnsEntry, heap, DYNAMIC_TYPE_ALTNAME); } +#endif return ret; } @@ -18813,7 +18882,7 @@ static int DecodeOtherHelper(ASNGetData* dataASN, DecodedCert* cert, int oid) } if (ret == 0) { - ret = SetDNSEntry(cert->heap, buf, (int)bufLen, ASN_OTHER_TYPE, &entry); + ret = SetDNSEntry(cert->heap, WC_DNS_POOL(cert), buf, (int)bufLen, ASN_OTHER_TYPE, &entry); if (ret == 0) { #ifdef WOLFSSL_FPKI entry->oidSum = oid; @@ -18875,7 +18944,7 @@ static int DecodeOtherName(DecodedCert* cert, const byte* input, break; default: WOLFSSL_MSG("\tadding unsupported OID"); - ret = SetDNSEntry(cert->heap, name, len, ASN_OTHER_TYPE, + ret = SetDNSEntry(cert->heap, WC_DNS_POOL(cert), name, len, ASN_OTHER_TYPE, &cert->altNames); break; } @@ -18925,7 +18994,7 @@ static int DecodeGeneralName(const byte* input, word32* inOutIdx, byte tag, if (ret != 0) { return ret; } - ret = SetDNSEntry(cert->heap, (const char*)(input + idx), len, + ret = SetDNSEntry(cert->heap, WC_DNS_POOL(cert), (const char*)(input + idx), len, ASN_DNS_TYPE, &cert->altNames); if (ret == 0) { idx += (word32)len; @@ -18944,7 +19013,7 @@ static int DecodeGeneralName(const byte* input, word32* inOutIdx, byte tag, return ASN_PARSE_E; } - ret = SetDNSEntry(cert->heap, (const char*)(input + idxDir), strLen, + ret = SetDNSEntry(cert->heap, WC_DNS_POOL(cert), (const char*)(input + idxDir), strLen, ASN_DIR_TYPE, &cert->altDirNames); if (ret == 0) { idx += (word32)len; @@ -18956,7 +19025,7 @@ static int DecodeGeneralName(const byte* input, word32* inOutIdx, byte tag, if (ret != 0) { return ret; } - ret = SetDNSEntry(cert->heap, (const char*)(input + idx), len, + ret = SetDNSEntry(cert->heap, WC_DNS_POOL(cert), (const char*)(input + idx), len, ASN_RFC822_TYPE, &cert->altEmailNames); if (ret == 0) { idx += (word32)len; @@ -19007,7 +19076,7 @@ static int DecodeGeneralName(const byte* input, word32* inOutIdx, byte tag, } #endif - ret = SetDNSEntry(cert->heap, (const char*)(input + idx), len, + ret = SetDNSEntry(cert->heap, WC_DNS_POOL(cert), (const char*)(input + idx), len, ASN_URI_TYPE, &cert->altNames); if (ret == 0) { idx += (word32)len; @@ -19040,7 +19109,7 @@ static int DecodeGeneralName(const byte* input, word32* inOutIdx, byte tag, * ASN_IP_TYPE case under WOLFSSL_GEN_IPADD in src/x509.c). */ else if (tag == (ASN_CONTEXT_SPECIFIC | ASN_IP_TYPE)) { - ret = SetDNSEntry(cert->heap, (const char*)(input + idx), len, + ret = SetDNSEntry(cert->heap, WC_DNS_POOL(cert), (const char*)(input + idx), len, ASN_IP_TYPE, &cert->altNames); if (ret == 0) { idx += (word32)len; @@ -19072,7 +19141,7 @@ static int DecodeGeneralName(const byte* input, word32* inOutIdx, byte tag, * when ridString is not generated, instead of failing the * whole print operation. */ else if (tag == (ASN_CONTEXT_SPECIFIC | ASN_RID_TYPE)) { - ret = SetDNSEntry(cert->heap, (const char*)(input + idx), len, + ret = SetDNSEntry(cert->heap, WC_DNS_POOL(cert), (const char*)(input + idx), len, ASN_RID_TYPE, &cert->altNames); if (ret == 0) { idx += (word32)len; @@ -19088,7 +19157,7 @@ static int DecodeGeneralName(const byte* input, word32* inOutIdx, byte tag, * the public altNames view (used by OpenSSL-compat APIs) reflects * exactly what the SAN extension carries. */ else if (tag == (ASN_CONTEXT_SPECIFIC | ASN_CONSTRUCTED | ASN_OTHER_TYPE)) { - ret = SetDNSEntry(cert->heap, (const char*)(input + idx), len, + ret = SetDNSEntry(cert->heap, WC_DNS_POOL(cert), (const char*)(input + idx), len, ASN_OTHER_TYPE, &cert->altOtherNamesRaw); if (ret != 0) { return ret; @@ -23949,7 +24018,14 @@ int FillSigner(Signer* signer, DecodedCert* cert, int type, DerBuffer *der) signer->publicKey = cert->publicKey; signer->pubKeySize = cert->pubKeySize; } +#ifdef WC_ASN_NO_HEAP + else if ((cert->publicKey != NULL) && (cert->pubKeySize > 0)) { + /* Borrowed key (points into source); fail rather than retain it. */ + ret = MEMORY_E; + } +#endif + if (ret == 0) { if (cert->subjectCNStored) { signer->nameLen = cert->subjectCNLen; signer->name = cert->subjectCN; @@ -23992,6 +24068,7 @@ int FillSigner(Signer* signer, DecodedCert* cert, int type, DerBuffer *der) cert->excludedNames = NULL; #endif signer->type = (byte)type; + } } return ret; } @@ -38532,7 +38609,7 @@ static int DecodeAcertGeneralName(const byte* input, word32* inOutIdx, /* GeneralName choice: dnsName */ if (tag == (ASN_CONTEXT_SPECIFIC | ASN_DNS_TYPE)) { - ret = SetDNSEntry(acert->heap, (const char*)(input + idx), len, + ret = SetDNSEntry(acert->heap, WC_DNS_POOL(acert), (const char*)(input + idx), len, ASN_DNS_TYPE, entries); if (ret == 0) { idx += (word32)len; @@ -38551,7 +38628,7 @@ static int DecodeAcertGeneralName(const byte* input, word32* inOutIdx, return ASN_PARSE_E; } - ret = SetDNSEntry(acert->heap, (const char*)(input + idxDir), strLen, + ret = SetDNSEntry(acert->heap, WC_DNS_POOL(acert), (const char*)(input + idxDir), strLen, ASN_DIR_TYPE, entries); if (ret == 0) { idx += (word32)len; @@ -38559,7 +38636,7 @@ static int DecodeAcertGeneralName(const byte* input, word32* inOutIdx, } /* GeneralName choice: rfc822Name */ else if (tag == (ASN_CONTEXT_SPECIFIC | ASN_RFC822_TYPE)) { - ret = SetDNSEntry(acert->heap, (const char*)(input + idx), len, + ret = SetDNSEntry(acert->heap, WC_DNS_POOL(acert), (const char*)(input + idx), len, ASN_RFC822_TYPE, entries); if (ret == 0) { idx += (word32)len; @@ -38606,7 +38683,7 @@ static int DecodeAcertGeneralName(const byte* input, word32* inOutIdx, } #endif - ret = SetDNSEntry(acert->heap, (const char*)(input + idx), len, + ret = SetDNSEntry(acert->heap, WC_DNS_POOL(acert), (const char*)(input + idx), len, ASN_URI_TYPE, entries); if (ret == 0) { idx += (word32)len; @@ -38624,7 +38701,7 @@ static int DecodeAcertGeneralName(const byte* input, word32* inOutIdx, * IP-SAN compat layer). If iPAddress name-constraint enforcement is * ever extended to attribute certificates, this gate must drop. */ else if (tag == (ASN_CONTEXT_SPECIFIC | ASN_IP_TYPE)) { - ret = SetDNSEntry(acert->heap, (const char*)(input + idx), len, + ret = SetDNSEntry(acert->heap, WC_DNS_POOL(acert), (const char*)(input + idx), len, ASN_IP_TYPE, entries); if (ret == 0) { idx += (word32)len; @@ -38635,7 +38712,7 @@ static int DecodeAcertGeneralName(const byte* input, word32* inOutIdx, #ifdef OPENSSL_ALL /* GeneralName choice: registeredID */ else if (tag == (ASN_CONTEXT_SPECIFIC | ASN_RID_TYPE)) { - ret = SetDNSEntry(acert->heap, (const char*)(input + idx), len, + ret = SetDNSEntry(acert->heap, WC_DNS_POOL(acert), (const char*)(input + idx), len, ASN_RID_TYPE, entries); if (ret == 0) { idx += (word32)len; diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index e95bba5ae5e..e9f3d97c005 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -25950,6 +25950,39 @@ static wc_test_ret_t cert_bad_asn1_test(void) return ret; } +#if defined(USE_CERT_BUFFERS_2048) && !defined(NO_RSA) +/* Heap/file-free parse from a const DER buffer; checks in-place refs under + * WC_ASN_NO_HEAP. */ +static wc_test_ret_t cert_no_malloc_test(void) +{ + DecodedCert cert; + wc_test_ret_t ret; + + WOLFSSL_ENTER("cert_no_malloc_test"); + + InitDecodedCert(&cert, server_cert_der_2048, sizeof_server_cert_der_2048, + NULL); + ret = ParseCert(&cert, CERT_TYPE, NO_VERIFY, NULL); + if ((ret == 0) && ((cert.publicKey == NULL) || (cert.pubKeySize == 0))) { + ret = WC_TEST_RET_ENC_NC; + } +#ifdef WC_ASN_NO_HEAP + if ((ret == 0) && ((cert.pubKeyStored != 0) || + (cert.publicKey < cert.source) || + (cert.publicKey >= cert.source + cert.maxIdx))) { + ret = WC_TEST_RET_ENC_NC; + } + if ((ret == 0) && (cert.altNames != NULL) && + (cert.altNames->entryStored != 0)) { + ret = WC_TEST_RET_ENC_NC; + } +#endif + FreeDecodedCert(&cert); + + return ret; +} +#endif /* USE_CERT_BUFFERS_2048 && !NO_RSA */ + WOLFSSL_TEST_SUBROUTINE wc_test_ret_t cert_test(void) { #if !defined(NO_FILESYSTEM) @@ -26025,6 +26058,10 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t cert_test(void) ret = cert_asn1_test(); if (ret == 0) ret = cert_bad_asn1_test(); +#if defined(USE_CERT_BUFFERS_2048) && !defined(NO_RSA) + if (ret == 0) + ret = cert_no_malloc_test(); +#endif return ret; } diff --git a/wolfssl/wolfcrypt/asn.h b/wolfssl/wolfcrypt/asn.h index 45993f0638a..a3b384e8d41 100644 --- a/wolfssl/wolfcrypt/asn.h +++ b/wolfssl/wolfcrypt/asn.h @@ -1470,8 +1470,20 @@ struct DNS_entry { #ifdef WOLFSSL_FPKI int oidSum; /* provide oid sum for verification */ #endif + int entryStored; /* 1 = heap node to free; 0 = no-heap pool node */ }; +/* No allocator: reference key/alt-name data in the source DER, not copies. */ +#if defined(WOLFSSL_NO_MALLOC) && defined(NO_WOLFSSL_MEMORY) && \ + !defined(XMALLOC_USER) && !defined(WOLFSSL_STATIC_MEMORY) + #define WC_ASN_NO_HEAP +#endif + +#ifndef WC_ASN_MAX_ALTNAMES + /* Total no-heap SAN pool slots across all lists; excess is rejected. */ + #define WC_ASN_MAX_ALTNAMES 8 +#endif + #ifdef WOLFSSL_FPKI /* RFC4122 i.e urn:uuid:f81d4fae-7dec-11d0-a765-00a0c91e6bf6 */ #define DEFAULT_UUID_SZ 45 @@ -1784,6 +1796,11 @@ struct DecodedCert { #endif int version; /* cert version, 1 or 3 */ DNS_entry* altNames; /* alt names list of dns entries */ +#ifdef WC_ASN_NO_HEAP + /* No-heap SAN pool; entries chain through altNames into the source DER. */ + DNS_entry altNamePool[WC_ASN_MAX_ALTNAMES]; + word32 altNamePoolUsed; +#endif #ifndef IGNORE_NAME_CONSTRAINTS DNS_entry* altEmailNames; /* alt names list of RFC822 entries */ DNS_entry* altDirNames; /* alt names list of DIR entries */ @@ -3194,6 +3211,11 @@ struct DecodedAcert { DNS_entry * holderEntityName; /* Holder entityName from ACERT */ DNS_entry * holderIssuerName; /* Holder issuerName from ACERT */ DNS_entry * AttCertIssuerName; /* AttCertIssuer name from ACERT */ +#ifdef WC_ASN_NO_HEAP + /* No-heap alt-name pool, used like DecodedCert.altNamePool. */ + DNS_entry altNamePool[WC_ASN_MAX_ALTNAMES]; + word32 altNamePoolUsed; +#endif const byte * rawAttr; /* Not owned, points into raw acert. */ word32 rawAttrLen; SignatureCtx sigCtx;