Merge pull request #10768 from Frauschi/altNames_API

Add public alt-name list APIs
This commit is contained in:
Daniel Pouzzner
2026-07-03 01:14:57 -05:00
committed by GitHub
5 changed files with 393 additions and 0 deletions
+155
View File
@@ -1067,6 +1067,161 @@ int wc_SetSubjectBuffer(Cert* cert, const byte* der, int derSz);
*/
int wc_SetAltNamesBuffer(Cert* cert, const byte* der, int derSz);
/*!
\ingroup ASN
\brief This function allocates a single subject alternative name (SAN)
entry, copies the supplied name into it, sets its GeneralName type and
length, and appends it to the linked list pointed to by entries. The name
is duplicated internally, so the caller's buffer need not outlive the call.
The resulting list can be encoded with wc_FlattenAltNames and must be freed
with FreeAltNames.
\return 0 Returned on success.
\return MEMORY_E Returned if dynamic memory allocation fails.
\return BAD_FUNC_ARG Returned if str or entries is NULL, strLen is
negative, or type is not a supported GeneralName type; also returned for an
ASN_IP_TYPE entry whose length is not a valid IPv4/IPv6 address, or an
ASN_RID_TYPE entry with malformed contents.
\return BUFFER_E Returned if the string representation of an ASN_IP_TYPE or
ASN_RID_TYPE entry does not fit its internal buffer.
\return Other negative error codes may propagate from generating the string
form of ASN_IP_TYPE and ASN_RID_TYPE entries.
\param heap pointer to the heap hint used for allocations (may be NULL)
\param str pointer to the name bytes (e.g. a DNS string, or raw IP octets
for ASN_IP_TYPE)
\param strLen length of str in bytes
\param type GeneralName type (e.g. ASN_DNS_TYPE, ASN_IP_TYPE,
ASN_RFC822_TYPE, ASN_URI_TYPE)
\param entries in/out pointer to the head of the alt-name linked list; a new
entry is appended
_Example_
\code
DNS_entry* list = NULL;
if (wc_SetDNSEntry(NULL, "example.com", 11, ASN_DNS_TYPE, &list) != 0) {
// error adding alt name
}
// ... encode with wc_FlattenAltNames, then:
FreeAltNames(list, NULL);
\endcode
\note This helper (along with wc_FlattenAltNames and FreeAltNames) is
exported from the library only when WOLFSSL_PUBLIC_ASN, OPENSSL_EXTRA,
OPENSSL_EXTRA_X509_SMALL, or WOLFSSL_TEST_CERT is defined; its prototype
lives in wolfssl/wolfcrypt/asn.h (not asn_public.h) because it uses the
DNS_entry type.
\note This function additionally requires WOLFSSL_ASN_TEMPLATE (its
internal SetDNSEntry/AddDNSEntryToList helpers are template-only), on top of
the WOLFSSL_CERT_GEN && WOLFSSL_ALT_NAMES gating shared with its companions
wc_FlattenAltNames and wc_SetAltNamesFromList, which do not require
WOLFSSL_ASN_TEMPLATE. Because this builder is the more restrictive of the
set, a DNS_entry list built here can always be encoded by a public API in
the same build; in non-template builds the public list can only be sourced
from a parsed DecodedCert.altNames.
\sa wc_FlattenAltNames
\sa FreeAltNames
*/
int wc_SetDNSEntry(void* heap, const char* str, int strLen, int type,
DNS_entry** entries);
/*!
\ingroup ASN
\brief This function encodes a linked list of subject alternative name
entries into the DER GeneralNames SEQUENCE used as the value of the
subjectAltName certificate extension. The output is suitable for assigning
to Cert.altNames (with the return value stored in Cert.altNamesSz) prior to
signing.
\return >0 the number of bytes written to output (the full SEQUENCE,
including its tag and length).
\return 0 Returned when names is NULL (nothing to encode).
\return BAD_FUNC_ARG Returned if output is NULL.
\return BUFFER_E Returned if output is too small to hold the encoding.
\param output buffer that receives the DER GeneralNames SEQUENCE; size it to
hold the full extension value (e.g. CTC_MAX_ALT_SIZE)
\param outputSz capacity of output in bytes
\param names head of the alt-name linked list to encode (e.g. built with
wc_SetDNSEntry, or taken from a parsed DecodedCert.altNames)
_Example_
\code
Cert cert;
DNS_entry* list = NULL;
// ... populate list with wc_SetDNSEntry ...
int n = wc_FlattenAltNames(cert.altNames, sizeof(cert.altNames), list);
if (n < 0) {
// error encoding alt names
}
cert.altNamesSz = n;
FreeAltNames(list, NULL);
\endcode
\note This helper (along with wc_SetDNSEntry and FreeAltNames) is exported
from the library only when WOLFSSL_PUBLIC_ASN, OPENSSL_EXTRA,
OPENSSL_EXTRA_X509_SMALL, or WOLFSSL_TEST_CERT is defined; its prototype
lives in wolfssl/wolfcrypt/asn.h (not asn_public.h) because it uses the
DNS_entry type.
\sa wc_SetDNSEntry
\sa wc_SetAltNamesFromList
\sa FreeAltNames
\sa wc_SetAltNamesBuffer
*/
int wc_FlattenAltNames(byte* output, word32 outputSz, const DNS_entry* names);
/*!
\ingroup ASN
\brief This function encodes a linked list of subject alternative name
entries directly into a Cert structure, ready for signing. It is a
convenience wrapper around wc_FlattenAltNames: the list is encoded into
cert->altNames and the encoded length is stored in cert->altNamesSz, so the
caller does not have to manage the buffer or size bookkeeping. The supplied
list is not consumed and must still be freed by the caller with
FreeAltNames.
\return 0 Returned on success.
\return BAD_FUNC_ARG Returned if cert is NULL.
\return BUFFER_E Returned if the encoded names do not fit in cert->altNames.
\param cert pointer to the Cert whose altNames/altNamesSz fields are set
\param names head of the alt-name linked list to encode (e.g. built with
wc_SetDNSEntry, or taken from a parsed DecodedCert.altNames); may be NULL,
in which case cert->altNamesSz is set to 0
_Example_
\code
Cert cert;
DNS_entry* list = NULL;
wc_InitCert(&cert);
// ... populate list with wc_SetDNSEntry ...
if (wc_SetAltNamesFromList(&cert, list) != 0) {
// error encoding alt names
}
FreeAltNames(list, NULL);
// ... wc_MakeCert / wc_SignCert ...
\endcode
\note This helper (along with wc_SetDNSEntry, wc_FlattenAltNames, and
FreeAltNames) is exported from the library only when WOLFSSL_PUBLIC_ASN,
OPENSSL_EXTRA, OPENSSL_EXTRA_X509_SMALL, or WOLFSSL_TEST_CERT is defined;
its prototype lives in wolfssl/wolfcrypt/asn.h (not asn_public.h) because it
uses the DNS_entry type. Its DER-input sibling wc_SetAltNamesBuffer is
always exported.
\sa wc_SetDNSEntry
\sa wc_FlattenAltNames
\sa wc_SetAltNamesBuffer
\sa FreeAltNames
*/
int wc_SetAltNamesFromList(Cert* cert, const DNS_entry* names);
/*!
\ingroup ASN
+69
View File
@@ -14520,6 +14520,46 @@ static int SetDNSEntry(void* heap, const char* str, int strLen,
return ret;
}
/* Public wrapper for SetDNSEntry(): allocate an alt-name entry that copies the
* given name, set its type/length, and append it to the linked list. The list
* is freed with FreeAltNames() and can be flattened with wc_FlattenAltNames().
* Additionally requires WOLFSSL_ASN_TEMPLATE (its internal SetDNSEntry/
* AddDNSEntryToList helpers are template-only), on top of the
* WOLFSSL_CERT_GEN && WOLFSSL_ALT_NAMES gating of its companions
* wc_FlattenAltNames()/wc_SetAltNamesFromList(). Because this builder is the
* more restrictive of the set, a DNS_entry list built here can always be
* encoded by a public API in the same build. */
#if defined(WOLFSSL_CERT_GEN) && defined(WOLFSSL_ALT_NAMES)
int wc_SetDNSEntry(void* heap, const char* str, int strLen, int type,
DNS_entry** entries)
{
/* Validate caller-supplied arguments at the public boundary: a negative
* strLen would cast to a huge size_t in the internal allocate/copy, a NULL
* entries list head would be dereferenced, and a NULL str cannot be
* copied. */
if (str == NULL || entries == NULL || strLen < 0) {
return BAD_FUNC_ARG;
}
/* Reject unsupported GeneralName types so a public caller cannot OR an
* out-of-range value into the context tag emitted by FlattenAltNames(). */
switch (type) {
case ASN_OTHER_TYPE:
case ASN_RFC822_TYPE:
case ASN_DNS_TYPE:
case ASN_DIR_TYPE:
case ASN_URI_TYPE:
case ASN_IP_TYPE:
case ASN_RID_TYPE:
break;
default:
return BAD_FUNC_ARG;
}
return SetDNSEntry(heap, str, strLen, type, entries);
}
#endif /* WOLFSSL_CERT_GEN && WOLFSSL_ALT_NAMES */
#endif
/* Set the details of a subject name component into a certificate.
@@ -27228,6 +27268,15 @@ int FlattenAltNames(byte* output, word32 outputSz, const DNS_entry* names)
return (int)idx;
}
/* Public wrapper for FlattenAltNames(): encode a linked list of alt-name
* entries into the DER GeneralNames SEQUENCE used as the subjectAltName
* extension value. Returns the encoded length, 0 for a NULL list, or a
* negative error code. */
int wc_FlattenAltNames(byte* output, word32 outputSz, const DNS_entry* names)
{
return FlattenAltNames(output, outputSz, names);
}
#endif /* WOLFSSL_ALT_NAMES */
#endif /* WOLFSSL_CERT_GEN */
@@ -31775,6 +31824,26 @@ int wc_SetAltNamesBuffer(Cert* cert, const byte* der, int derSz)
return(ret);
}
/* Set cert alt names from a linked list of alt-name entries (e.g. built with
* wc_SetDNSEntry()). Encodes the list into cert->altNames and stores the
* length in cert->altNamesSz. Returns 0 on success or a negative error code. */
int wc_SetAltNamesFromList(Cert* cert, const DNS_entry* names)
{
int ret;
if (cert == NULL) {
return BAD_FUNC_ARG;
}
ret = FlattenAltNames(cert->altNames, sizeof(cert->altNames), names);
if (ret < 0) {
return ret;
}
cert->altNamesSz = ret;
return 0;
}
/* Set cert dates from DER buffer */
WOLFSSL_ABI
int wc_SetDatesBuffer(Cert* cert, const byte* der, int derSz)
+153
View File
@@ -1035,6 +1035,12 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t certext_test(void);
defined(WOLFSSL_CERT_EXT) && defined(WOLFSSL_CERT_GEN)
WOLFSSL_TEST_SUBROUTINE wc_test_ret_t decodedCertCache_test(void);
#endif
#if defined(WOLFSSL_CERT_GEN) && defined(WOLFSSL_ALT_NAMES) && \
defined(WOLFSSL_ASN_TEMPLATE) && \
(defined(WOLFSSL_TEST_CERT) || defined(OPENSSL_EXTRA) || \
defined(OPENSSL_EXTRA_X509_SMALL) || defined(WOLFSSL_PUBLIC_ASN))
WOLFSSL_TEST_SUBROUTINE wc_test_ret_t flattenAltNames_test(void);
#endif
WOLFSSL_TEST_SUBROUTINE wc_test_ret_t memory_test(void);
#if defined(WOLFSSL_PUBLIC_MP) && \
((defined(WOLFSSL_SP_MATH_ALL) && !defined(WOLFSSL_RSA_VERIFY_ONLY)) || \
@@ -3089,6 +3095,16 @@ options: [-s max_relative_stack_bytes] [-m max_relative_heap_memory_bytes]\n\
TEST_PASS("DECODED CERT CACHE test passed!\n");
#endif
#if defined(WOLFSSL_CERT_GEN) && defined(WOLFSSL_ALT_NAMES) && \
defined(WOLFSSL_ASN_TEMPLATE) && \
(defined(WOLFSSL_TEST_CERT) || defined(OPENSSL_EXTRA) || \
defined(OPENSSL_EXTRA_X509_SMALL) || defined(WOLFSSL_PUBLIC_ASN))
if ( (ret = flattenAltNames_test()) != 0)
TEST_FAIL("FLATTEN ALT NAMES test failed!\n", ret);
else
TEST_PASS("FLATTEN ALT NAMES test passed!\n");
#endif
#ifdef HAVE_CURVE25519
if ( (ret = curve25519_test()) != 0)
TEST_FAIL("CURVE25519 test failed!\n", ret);
@@ -26547,6 +26563,143 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t decodedCertCache_test(void)
#endif /* defined(WOLFSSL_CERT_GEN_CACHE) && defined(WOLFSSL_TEST_CERT) &&
defined(WOLFSSL_CERT_EXT) && defined(WOLFSSL_CERT_GEN) */
#if defined(WOLFSSL_CERT_GEN) && defined(WOLFSSL_ALT_NAMES) && \
defined(WOLFSSL_ASN_TEMPLATE) && \
(defined(WOLFSSL_TEST_CERT) || defined(OPENSSL_EXTRA) || \
defined(OPENSSL_EXTRA_X509_SMALL) || defined(WOLFSSL_PUBLIC_ASN))
/* Exercise the public wc_SetDNSEntry() + wc_FlattenAltNames() pair: build an
* alt-name list and encode it into a GeneralNames SEQUENCE. The order entries
* land in depends on build config (OPENSSL_EXTRA appends, otherwise prepends),
* so presence checks are order-independent. Also exercise the
* wc_SetAltNamesFromList() convenience that encodes straight into a Cert. */
WOLFSSL_TEST_SUBROUTINE wc_test_ret_t flattenAltNames_test(void)
{
wc_test_ret_t ret = 0;
DNS_entry* list = NULL;
Cert* cert = NULL;
byte out[256];
int len;
/* dNSName "example.com" -> [2] IMPLICIT IA5String */
static const byte dnsTlv[] = {
0x82, 0x0B, 'e','x','a','m','p','l','e','.','c','o','m'
};
/* iPAddress 10.0.0.7 -> [7] IMPLICIT OCTET STRING */
static const byte ipTlv[] = { 0x87, 0x04, 0x0A, 0x00, 0x00, 0x07 };
static const byte ip[] = { 0x0A, 0x00, 0x00, 0x07 };
const int innerSz = (int)sizeof(dnsTlv) + (int)sizeof(ipTlv); /* 19 */
const int expSz = 2 + innerSz; /* 0x30,len + body */
int i, foundDns = 0, foundIp = 0;
WOLFSSL_ENTER("flattenAltNames_test");
/* A NULL list encodes to nothing. */
len = wc_FlattenAltNames(out, sizeof(out), NULL);
if (len != 0)
ret = WC_TEST_RET_ENC_EC(len);
if (ret == 0) {
ret = wc_SetDNSEntry(HEAP_HINT, "example.com", 11, ASN_DNS_TYPE, &list);
if (ret != 0)
ret = WC_TEST_RET_ENC_EC(ret);
}
if (ret == 0) {
ret = wc_SetDNSEntry(HEAP_HINT, (const char*)ip, (int)sizeof(ip),
ASN_IP_TYPE, &list);
if (ret != 0)
ret = WC_TEST_RET_ENC_EC(ret);
}
if (ret == 0) {
len = wc_FlattenAltNames(out, sizeof(out), list);
if (len != expSz)
ret = WC_TEST_RET_ENC_EC(len);
}
if (ret == 0 && (out[0] != ASN_SEQUENCE + ASN_CONSTRUCTED ||
out[1] != (byte)innerSz))
ret = WC_TEST_RET_ENC_NC;
/* Both GeneralName TLVs must be present, regardless of order. */
for (i = 0; ret == 0 && i + (int)sizeof(dnsTlv) <= len; i++) {
if (XMEMCMP(out + i, dnsTlv, sizeof(dnsTlv)) == 0)
foundDns = 1;
}
for (i = 0; ret == 0 && i + (int)sizeof(ipTlv) <= len; i++) {
if (XMEMCMP(out + i, ipTlv, sizeof(ipTlv)) == 0)
foundIp = 1;
}
if (ret == 0 && (!foundDns || !foundIp))
ret = WC_TEST_RET_ENC_NC;
/* NULL output is rejected. */
if (ret == 0) {
len = wc_FlattenAltNames(NULL, sizeof(out), list);
if (len != WC_NO_ERR_TRACE(BAD_FUNC_ARG))
ret = WC_TEST_RET_ENC_EC(len);
}
/* Output one byte too small is rejected with BUFFER_E. */
if (ret == 0) {
len = wc_FlattenAltNames(out, (word32)expSz - 1, list);
if (len != WC_NO_ERR_TRACE(BUFFER_E))
ret = WC_TEST_RET_ENC_EC(len);
}
/* wc_SetAltNamesFromList() encodes the same list straight into a Cert and
* records the length; the result must match the standalone encoding. */
if (ret == 0) {
cert = (Cert*)XMALLOC(sizeof(Cert), HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER);
if (cert == NULL)
ret = WC_TEST_RET_ENC_EC(MEMORY_E);
}
if (ret == 0) {
ret = wc_InitCert_ex(cert, HEAP_HINT, devId);
if (ret != 0)
ret = WC_TEST_RET_ENC_EC(ret);
}
if (ret == 0) {
ret = wc_SetAltNamesFromList(cert, list);
if (ret != 0)
ret = WC_TEST_RET_ENC_EC(ret);
}
if (ret == 0 && (cert->altNamesSz != expSz ||
XMEMCMP(cert->altNames, out, (size_t)expSz) != 0))
ret = WC_TEST_RET_ENC_NC;
/* NULL cert is rejected. */
if (ret == 0) {
int r = wc_SetAltNamesFromList(NULL, list);
if (r != WC_NO_ERR_TRACE(BAD_FUNC_ARG))
ret = WC_TEST_RET_ENC_EC(r);
}
/* A NULL names list encodes to nothing: returns 0 and zeroes altNamesSz. */
if (ret == 0) {
cert->altNamesSz = 1; /* poison so we can see it get cleared */
if (wc_SetAltNamesFromList(cert, NULL) != 0 || cert->altNamesSz != 0)
ret = WC_TEST_RET_ENC_NC;
}
/* wc_SetDNSEntry() rejects invalid arguments at the public boundary. */
if (ret == 0) {
DNS_entry* badList = NULL;
/* NULL str */
if (wc_SetDNSEntry(HEAP_HINT, NULL, 1, ASN_DNS_TYPE, &badList)
!= WC_NO_ERR_TRACE(BAD_FUNC_ARG))
ret = WC_TEST_RET_ENC_NC;
/* NULL entries */
else if (wc_SetDNSEntry(HEAP_HINT, "x", 1, ASN_DNS_TYPE, NULL)
!= WC_NO_ERR_TRACE(BAD_FUNC_ARG))
ret = WC_TEST_RET_ENC_NC;
/* negative strLen */
else if (wc_SetDNSEntry(HEAP_HINT, "x", -1, ASN_DNS_TYPE, &badList)
!= WC_NO_ERR_TRACE(BAD_FUNC_ARG))
ret = WC_TEST_RET_ENC_NC;
if (badList != NULL)
FreeAltNames(badList, HEAP_HINT);
}
XFREE(cert, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER);
FreeAltNames(list, HEAP_HINT);
return ret;
}
#endif /* WOLFSSL_CERT_GEN && WOLFSSL_ALT_NAMES && WOLFSSL_ASN_TEMPLATE &&
* (WOLFSSL_TEST_CERT || OPENSSL_EXTRA || OPENSSL_EXTRA_X509_SMALL ||
* WOLFSSL_PUBLIC_ASN) */
#define RSA_TEST_BYTES (RSA_MAX_SIZE / 8)
#if !defined(NO_ASN) && !defined(WOLFSSL_RSA_PUBLIC_ONLY) && \
+6
View File
@@ -377,6 +377,12 @@ extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t certext_test(void);
defined(WOLFSSL_CERT_EXT) && defined(WOLFSSL_CERT_GEN)
extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t decodedCertCache_test(void);
#endif
#if defined(WOLFSSL_CERT_GEN) && defined(WOLFSSL_ALT_NAMES) && \
defined(WOLFSSL_ASN_TEMPLATE) && \
(defined(WOLFSSL_TEST_CERT) || defined(OPENSSL_EXTRA) || \
defined(OPENSSL_EXTRA_X509_SMALL) || defined(WOLFSSL_PUBLIC_ASN))
extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t flattenAltNames_test(void);
#endif
extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t memory_test(void);
#if defined(WOLFSSL_PUBLIC_MP) && \
((defined(WOLFSSL_SP_MATH_ALL) && !defined(WOLFSSL_RSA_VERIFY_ONLY)) || \
+10
View File
@@ -2404,6 +2404,11 @@ WOLFSSL_LOCAL int StreamOctetString(const byte* inBuf, word32 inBufSz,
WOLFSSL_ASN_API void FreeAltNames(DNS_entry* altNames, void* heap);
WOLFSSL_ASN_API DNS_entry* AltNameNew(void* heap);
WOLFSSL_ASN_API DNS_entry* AltNameDup(DNS_entry* from, void* heap);
#if defined(WOLFSSL_ASN_TEMPLATE) && defined(WOLFSSL_CERT_GEN) && \
defined(WOLFSSL_ALT_NAMES)
WOLFSSL_ASN_API int wc_SetDNSEntry(void* heap, const char* str, int strLen,
int type, DNS_entry** entries);
#endif
#ifndef IGNORE_NAME_CONSTRAINTS
WOLFSSL_ASN_API void FreeNameSubtrees(Base_entry* names, void* heap);
#endif /* IGNORE_NAME_CONSTRAINTS */
@@ -2691,6 +2696,11 @@ WOLFSSL_API int wc_DhPublicKeyDecode(const byte* input, word32* inOutIdx,
#endif
WOLFSSL_LOCAL int FlattenAltNames(byte* output, word32 outputSz,
const DNS_entry* names);
#if defined(WOLFSSL_CERT_GEN) && defined(WOLFSSL_ALT_NAMES)
WOLFSSL_ASN_API int wc_FlattenAltNames(byte* output, word32 outputSz,
const DNS_entry* names);
WOLFSSL_ASN_API int wc_SetAltNamesFromList(Cert* cert, const DNS_entry* names);
#endif
WOLFSSL_LOCAL int wc_EncodeName(EncodedName* name, const char* nameStr,
char nameType, byte type);