diff --git a/doc/dox_comments/header_files/asn_public.h b/doc/dox_comments/header_files/asn_public.h index bd06ae8e0f8..c8dcce4892c 100644 --- a/doc/dox_comments/header_files/asn_public.h +++ b/doc/dox_comments/header_files/asn_public.h @@ -1067,6 +1067,150 @@ 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 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. + + \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 diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index 7e855986f67..cca32249d8c 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -14520,6 +14520,16 @@ 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(). */ +int wc_SetDNSEntry(void* heap, const char* str, int strLen, int type, + DNS_entry** entries) +{ + return SetDNSEntry(heap, str, strLen, type, entries); +} #endif /* Set the details of a subject name component into a certificate. @@ -26960,6 +26970,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 */ @@ -31507,6 +31526,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) diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index e95bba5ae5e..c18957234cd 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -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); @@ -26485,6 +26501,118 @@ 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); + } + + 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) && \ diff --git a/wolfcrypt/test/test.h b/wolfcrypt/test/test.h index e54a7b9fad6..eab78ac9ce3 100644 --- a/wolfcrypt/test/test.h +++ b/wolfcrypt/test/test.h @@ -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)) || \ diff --git a/wolfssl/wolfcrypt/asn.h b/wolfssl/wolfcrypt/asn.h index 45993f0638a..a3a3648c13f 100644 --- a/wolfssl/wolfcrypt/asn.h +++ b/wolfssl/wolfcrypt/asn.h @@ -2390,6 +2390,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(NO_CERTS)) +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 */ @@ -2677,6 +2682,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);