From 4b7a92e526876a7812655239f8ec8f21305f447a Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Thu, 6 Aug 2026 11:19:50 +0000 Subject: [PATCH 1/2] Add native API for TLS 1.3 certificate_authorities extension RFC 8446 4.2.4 certificate_authorities (extension type 47) was only wired up behind OPENSSL_EXTRA via the client_ca_names / ca_names / peer_ca_names WOLF_STACK_OF(WOLFSSL_X509_NAME) stacks. Users without the OpenSSL compat layer had no way to send or inspect the extension. This commit introduces a native API gated on WOLFSSL_TLS13: - CertificateAuthority node: singly-linked list with a flexible-array DN buffer holding the inner DER-encoded Name content (no SEQUENCE header). TLSX_CertificateAuthorities_Add/FreeAll manage the list. Everything is gated on WOLFSSL_TLS13 && !NO_CERTS && !WOLFSSL_NO_CA_NAMES. - Storage on WOLFSSL_CTX (ws_ca_names) and WOLFSSL (ws_ca_names + ws_peer_ca_names); SSL shadows CTX via the WS_CA_NAMES helper. - Public API mirroring UseSNI style: wolfSSL_UseCertificateAuthority / CTX variant wolfSSL_ClearCertificateAuthorities / CTX variant wolfSSL_GetPeerCertificateAuthorityCount wolfSSL_GetPeerCertificateAuthority (index-based, copy-out) The library prepends the DER SEQUENCE header on the wire and strips it on parse, so callers pass the raw subject content straight from wc_GetDecodedCertSubjectRaw (new accessor on DecodedCert). - TLSX_CA_Names_Write is a single emitter matching the PHA_GET_SIZE / PHA_WRITE style (int return, word16* pSz accumulator); macros pass NULL when sizing and the output buffer when serializing. It walks the compat stack first (when OPENSSL_EXTRA is on) then the native list, enforcing the RFC 8446 per-DN cap and capping the whole extension payload (outer length included) at WOLFSSL_MAX_16BIT so the word16 size accumulator stays exact, returning BUFFER_ERROR on overflow. - TLSX_CA_Names_Parse always populates ws_peer_ca_names and, when OPENSSL_EXTRA is compiled in, additionally populates peer_ca_names through the existing InitDecodedCert/GetName/CopyDecodedName path. Length validation enforces the RFC 8446 DistinguishedName<1..2^16-1> and authorities<3..2^16-1> bounds. - TLSX_GetSize now propagates non-zero ret by breaking out of the walk, matching TLSX_Write. - Teardown paths in SSL_CtxResourceFree and wolfSSL_ResourceFree free the native lists on the owning heap. Tests in tests/api/test_tls_ext.c cover argument validation, size limits, send/receive counts, handshake round-trips on SSL and CTX, a cert_cb scenario that feeds DecodedCert subjects to the API (with params loop for TLS 1.3 and DTLS 1.3), the existing OPENSSL_EXTRA cases, and a bad-extension regression. Documentation for all new API functions is in doc/dox_comments/header_files/. --- doc/dox_comments/header_files/asn_public.h | 54 +++ doc/dox_comments/header_files/ssl.h | 220 +++++++++ src/internal.c | 22 +- src/ssl_api_cert.c | 2 +- src/ssl_api_ext.c | 132 ++++++ src/tls.c | 300 ++++++++----- tests/api.c | 7 + tests/api/test_tls_ext.c | 490 +++++++++++++++++++++ tests/api/test_tls_ext.h | 7 + wolfcrypt/src/asn.c | 22 + wolfssl/internal.h | 83 +++- wolfssl/ssl.h | 39 ++ wolfssl/wolfcrypt/asn_public.h | 3 + 13 files changed, 1250 insertions(+), 131 deletions(-) diff --git a/doc/dox_comments/header_files/asn_public.h b/doc/dox_comments/header_files/asn_public.h index 2592e5cd005..bc56e185908 100644 --- a/doc/dox_comments/header_files/asn_public.h +++ b/doc/dox_comments/header_files/asn_public.h @@ -4405,3 +4405,57 @@ int wc_Asn1_PrintAll(Asn1* asn1, Asn1PrintOptions* opts, unsigned char* data, */ int wc_Asn1_SetOidToNameCb(Asn1* asn1, Asn1OidToNameCb nameCb); +/*! + \ingroup ASN + + \brief Retrieves the raw DER-encoded subject Name content from a parsed + DecodedCert. + + The returned pointer and size reference the inner content of the subject + Name SEQUENCE (i.e. the bytes after the SEQUENCE tag and length). The + pointer aliases memory inside the DecodedCert and must not be freed by + the caller. The data remains valid until the DecodedCert is freed. + + This function is intended for use with wolfSSL_UseCertificateAuthority(), + which expects the subject content without the outer SEQUENCE header. + + Requires IGNORE_NAME_CONSTRAINTS to be undefined or WOLFSSL_CERT_EXT to + be defined. + + \param cert Pointer to the DecodedCert (must have been parsed). + \param subjectRaw Output pointer that receives the address of the raw + DER subject content. + \param subjectRawSz Output pointer that receives the size in bytes of the + raw subject content. + + \return 0 on success. + \return BAD_FUNC_ARG if any argument is NULL. + \return ASN_PARSE_E if the subject was not populated during parsing. + \return NOT_COMPILED_IN if the required build options are not enabled. + + _Example_ + \code + DecodedCert decoded; + const byte* subject = NULL; + int subjectSz = 0; + + wc_InitDecodedCert(&decoded, certDer, certDerSz, NULL); + if (wc_ParseCert(&decoded, CERT_TYPE, NO_VERIFY, NULL) == 0) { + if (wc_GetDecodedCertSubjectRaw(&decoded, &subject, + &subjectSz) == 0) { + // subject and subjectSz now reference the raw DER content + } + } + wc_FreeDecodedCert(&decoded); + \endcode + + \sa wc_InitDecodedCert + \sa wc_ParseCert + \sa wc_FreeDecodedCert + \sa wc_GetDecodedCertSubject + \sa wolfSSL_UseCertificateAuthority +*/ +int wc_GetDecodedCertSubjectRaw(const struct DecodedCert* cert, + const byte** subjectRaw, + int* subjectRawSz); + diff --git a/doc/dox_comments/header_files/ssl.h b/doc/dox_comments/header_files/ssl.h index 1297fbfe325..db957d11db9 100644 --- a/doc/dox_comments/header_files/ssl.h +++ b/doc/dox_comments/header_files/ssl.h @@ -17128,3 +17128,223 @@ int wolfSSL_get_scr_check_enabled(const WOLFSSL* ssl); \sa wolfSSL_get_scr_check_enabled */ int wolfSSL_set_scr_check_enabled(WOLFSSL* ssl, byte enabled); + +/*! + \ingroup TLS + + \brief Adds a CA distinguished name to the list of certificate authorities + announced via the TLS 1.3 certificate_authorities extension (RFC 8446 + section 4.2.4) on the given SSL session. The DN must be the inner content + of a DER-encoded X.509 Name (the bytes after the SEQUENCE tag and length), + as returned by wc_GetDecodedCertSubjectRaw(). The library copies the + bytes and prepends the SEQUENCE header on the wire automatically. + + Multiple DNs may be added; each call appends to the list. Use + wolfSSL_ClearCertificateAuthorities() to reset the list. + + Requires WOLFSSL_TLS13 and !NO_CERTS and !WOLFSSL_NO_CA_NAMES. + + \return 0 on success. + \return BAD_FUNC_ARG if ssl or dn is NULL, dnSz is 0, or dnSz exceeds + the maximum content size. + \return MEMORY_E if memory allocation fails. + + \param ssl pointer to a WOLFSSL object, created with wolfSSL_new(). + \param dn pointer to the DER-encoded subject Name content. + \param dnSz size in bytes of the DN content. + + _Example_ + \code + DecodedCert decoded; + const byte* subject = NULL; + int subjectSz = 0; + + wc_InitDecodedCert(&decoded, certDer, certDerSz, NULL); + wc_ParseCert(&decoded, CERT_TYPE, NO_VERIFY, NULL); + wc_GetDecodedCertSubjectRaw(&decoded, &subject, &subjectSz); + + ret = wolfSSL_UseCertificateAuthority(ssl, subject, + (unsigned int)subjectSz); + if (ret != 0) { + // error adding CA DN + } + wc_FreeDecodedCert(&decoded); + \endcode + + \sa wolfSSL_CTX_UseCertificateAuthority + \sa wolfSSL_ClearCertificateAuthorities + \sa wolfSSL_GetPeerCertificateAuthorityCount + \sa wolfSSL_GetPeerCertificateAuthority + \sa wc_GetDecodedCertSubjectRaw +*/ +int wolfSSL_UseCertificateAuthority(WOLFSSL* ssl, + const unsigned char* dn, unsigned int dnSz); + +/*! + \ingroup TLS + + \brief Adds a CA distinguished name to the list of certificate authorities + announced via the TLS 1.3 certificate_authorities extension (RFC 8446 + section 4.2.4) on all SSL sessions created from this context. The DN + format and requirements are identical to wolfSSL_UseCertificateAuthority(). + + Per-session lists set via wolfSSL_UseCertificateAuthority() take + precedence; if the SSL object has its own list, the CTX list is not sent. + + Requires WOLFSSL_TLS13 and !NO_CERTS and !WOLFSSL_NO_CA_NAMES. + + \return 0 on success. + \return BAD_FUNC_ARG if ctx or dn is NULL, dnSz is 0, or dnSz exceeds + the maximum content size. + \return MEMORY_E if memory allocation fails. + + \param ctx pointer to a WOLFSSL_CTX object, created with + wolfSSL_CTX_new(). + \param dn pointer to the DER-encoded subject Name content. + \param dnSz size in bytes of the DN content. + + _Example_ + \code + ret = wolfSSL_CTX_UseCertificateAuthority(ctx, subject, + (unsigned int)subjectSz); + if (ret != 0) { + // error adding CA DN + } + \endcode + + \sa wolfSSL_UseCertificateAuthority + \sa wolfSSL_CTX_ClearCertificateAuthorities + \sa wc_GetDecodedCertSubjectRaw +*/ +int wolfSSL_CTX_UseCertificateAuthority(WOLFSSL_CTX* ctx, + const unsigned char* dn, unsigned int dnSz); + +/*! + \ingroup TLS + + \brief Frees and removes all CA distinguished names previously added to + the SSL session via wolfSSL_UseCertificateAuthority(). After this call + the session-level native CA list is empty; the CTX-level list (if any) is + not affected. + + Requires WOLFSSL_TLS13 and !NO_CERTS and !WOLFSSL_NO_CA_NAMES. + + \return none No return value. + + \param ssl pointer to a WOLFSSL object, created with wolfSSL_new(). + + _Example_ + \code + wolfSSL_UseCertificateAuthority(ssl, dn1, dn1Sz); + wolfSSL_UseCertificateAuthority(ssl, dn2, dn2Sz); + // Clear all session-level CA DNs: + wolfSSL_ClearCertificateAuthorities(ssl); + \endcode + + \sa wolfSSL_UseCertificateAuthority + \sa wolfSSL_CTX_ClearCertificateAuthorities +*/ +void wolfSSL_ClearCertificateAuthorities(WOLFSSL* ssl); + +/*! + \ingroup TLS + + \brief Frees and removes all CA distinguished names previously added to + the context via wolfSSL_CTX_UseCertificateAuthority(). After this call + the CTX-level native CA list is empty. + + Requires WOLFSSL_TLS13 and !NO_CERTS and !WOLFSSL_NO_CA_NAMES. + + \return none No return value. + + \param ctx pointer to a WOLFSSL_CTX object, created with + wolfSSL_CTX_new(). + + _Example_ + \code + wolfSSL_CTX_UseCertificateAuthority(ctx, dn, dnSz); + // Clear all CTX-level CA DNs: + wolfSSL_CTX_ClearCertificateAuthorities(ctx); + \endcode + + \sa wolfSSL_CTX_UseCertificateAuthority + \sa wolfSSL_ClearCertificateAuthorities +*/ +void wolfSSL_CTX_ClearCertificateAuthorities(WOLFSSL_CTX* ctx); + +/*! + \ingroup TLS + + \brief Returns the number of CA distinguished names received from the peer + in the TLS 1.3 certificate_authorities extension. This is typically called + inside a cert_cb (WOLFSSL_CERT_SETUP_CB) on the server side to inspect + which CAs the client trusts. + + Requires WOLFSSL_TLS13 and !NO_CERTS and !WOLFSSL_NO_CA_NAMES. + + \return >= 0 The number of peer CA DNs. Returns 0 if ssl is NULL or no + certificate_authorities extension was received. + + \param ssl pointer to a WOLFSSL object, created with wolfSSL_new(). + + _Example_ + \code + int count = wolfSSL_GetPeerCertificateAuthorityCount(ssl); + for (int i = 0; i < count; i++) { + int sz = wolfSSL_GetPeerCertificateAuthority(ssl, i, NULL, 0); + // sz is the DN size in bytes + } + \endcode + + \sa wolfSSL_GetPeerCertificateAuthority + \sa wolfSSL_UseCertificateAuthority +*/ +int wolfSSL_GetPeerCertificateAuthorityCount(const WOLFSSL* ssl); + +/*! + \ingroup TLS + + \brief Copies the idx-th CA distinguished name received from the peer in + the TLS 1.3 certificate_authorities extension into the caller's buffer. + The DN is the inner content of the DER-encoded Name (without the SEQUENCE + header), matching the format accepted by wolfSSL_UseCertificateAuthority(). + + If outDn is NULL, returns the size of the DN in bytes (allowing the caller + to allocate the right amount of memory). If outDn is non-NULL and outDnSz + is large enough, copies the DN bytes and returns the number of bytes + written. If outDnSz is too small, returns BUFFER_E. + + Requires WOLFSSL_TLS13 and !NO_CERTS and !WOLFSSL_NO_CA_NAMES. + + \return > 0 The number of bytes written to outDn, or the DN size if + outDn is NULL. + \return BAD_FUNC_ARG if ssl is NULL or idx is out of range. + \return BUFFER_E if outDnSz is smaller than the DN. + + \param ssl pointer to a WOLFSSL object, created with wolfSSL_new(). + \param idx zero-based index of the peer CA DN to retrieve. Must be less + than the count returned by wolfSSL_GetPeerCertificateAuthorityCount(). + \param outDn output buffer to receive the DN bytes, or NULL to query size. + \param outDnSz size of the output buffer in bytes. + + _Example_ + \code + int count = wolfSSL_GetPeerCertificateAuthorityCount(ssl); + for (int i = 0; i < count; i++) { + int sz = wolfSSL_GetPeerCertificateAuthority(ssl, i, NULL, 0); + if (sz > 0) { + unsigned char* dn = malloc(sz); + wolfSSL_GetPeerCertificateAuthority(ssl, i, dn, + (unsigned int)sz); + // use dn[0..sz-1] + free(dn); + } + } + \endcode + + \sa wolfSSL_GetPeerCertificateAuthorityCount + \sa wolfSSL_UseCertificateAuthority + \sa wc_GetDecodedCertSubjectRaw +*/ +int wolfSSL_GetPeerCertificateAuthority(const WOLFSSL* ssl, int idx, + unsigned char* outDn, unsigned int outDnSz); diff --git a/src/internal.c b/src/internal.c index 3de4b1d060c..3a24b558f9a 100644 --- a/src/internal.c +++ b/src/internal.c @@ -3100,12 +3100,17 @@ void SSL_CtxResourceFree(WOLFSSL_CTX* ctx) defined(WOLFSSL_WPAS_SMALL) wolfSSL_X509_STORE_free(ctx->x509_store_pt); #endif - #ifndef WOLFSSL_NO_CA_NAMES + #if !defined(WOLFSSL_NO_CA_NAMES) && defined(OPENSSL_EXTRA) wolfSSL_sk_X509_NAME_pop_free(ctx->client_ca_names, NULL); ctx->client_ca_names = NULL; wolfSSL_sk_X509_NAME_pop_free(ctx->ca_names, NULL); ctx->ca_names = NULL; #endif + #if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CA_NAMES) && \ + defined(WOLFSSL_TLS13) + TLSX_CertificateAuthorities_FreeAll(ctx->ws_ca_names, ctx->heap); + ctx->ws_ca_names = NULL; + #endif #ifdef OPENSSL_EXTRA if (ctx->x509Chain) { wolfSSL_sk_X509_pop_free(ctx->x509Chain, NULL); @@ -9822,7 +9827,7 @@ void wolfSSL_ResourceFree(WOLFSSL* ssl) wolfSSL_sk_X509_pop_free(ssl->ourCertChain, NULL); #endif #endif -#ifndef WOLFSSL_NO_CA_NAMES +#if !defined(WOLFSSL_NO_CA_NAMES) && defined(OPENSSL_EXTRA) wolfSSL_sk_X509_NAME_pop_free(ssl->client_ca_names, NULL); ssl->client_ca_names = NULL; wolfSSL_sk_X509_NAME_pop_free(ssl->ca_names, NULL); @@ -9830,6 +9835,13 @@ void wolfSSL_ResourceFree(WOLFSSL* ssl) wolfSSL_sk_X509_NAME_pop_free(ssl->peer_ca_names, NULL); ssl->peer_ca_names = NULL; #endif +#if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CA_NAMES) && \ + defined(WOLFSSL_TLS13) + TLSX_CertificateAuthorities_FreeAll(ssl->ws_ca_names, ssl->heap); + ssl->ws_ca_names = NULL; + TLSX_CertificateAuthorities_FreeAll(ssl->ws_peer_ca_names, ssl->heap); + ssl->ws_peer_ca_names = NULL; +#endif #ifdef WOLFSSL_DTLS13 Dtls13FreeFsmResources(ssl); @@ -27712,7 +27724,7 @@ int SendCertificateRequest(WOLFSSL* ssl) int sendSz; word32 i = RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ; word32 dnLen = 0; -#ifndef WOLFSSL_NO_CA_NAMES +#if !defined(WOLFSSL_NO_CA_NAMES) && defined(OPENSSL_EXTRA) WOLF_STACK_OF(WOLFSSL_X509_NAME)* names; #endif byte certTypes[MAX_CERT_REQ_CERT_TYPE_CNT]; @@ -27734,7 +27746,7 @@ int SendCertificateRequest(WOLFSSL* ssl) if (IsAtLeastTLSv1_2(ssl)) reqSz += LENGTH_SZ + localHashSigAlgoSz; -#ifndef WOLFSSL_NO_CA_NAMES +#if !defined(WOLFSSL_NO_CA_NAMES) && defined(OPENSSL_EXTRA) /* Certificate Authorities */ names = SSL_PRIORITY_CA_NAMES(ssl); while (names != NULL) { @@ -27800,7 +27812,7 @@ int SendCertificateRequest(WOLFSSL* ssl) /* Certificate Authorities */ c16toa((word16)dnLen, &output[i]); /* auth's */ i += REQ_HEADER_SZ; -#ifndef WOLFSSL_NO_CA_NAMES +#if !defined(WOLFSSL_NO_CA_NAMES) && defined(OPENSSL_EXTRA) names = SSL_PRIORITY_CA_NAMES(ssl); while (names != NULL) { byte seq[MAX_SEQ_SZ]; diff --git a/src/ssl_api_cert.c b/src/ssl_api_cert.c index 8bc0f2dfaae..40b3a4923ff 100644 --- a/src/ssl_api_cert.c +++ b/src/ssl_api_cert.c @@ -1135,7 +1135,7 @@ int wolfSSL_Unload_trust_peers(WOLFSSL* ssl) #endif /* WOLFSSL_LOCAL_X509_STORE */ #endif /* WOLFSSL_TRUST_PEER_CERT */ -#ifndef WOLFSSL_NO_CA_NAMES +#if !defined(WOLFSSL_NO_CA_NAMES) && defined(OPENSSL_EXTRA) /* Add a CA certificate to the list of CA names. * * @param [in, out] ca_names List of CA certificate subject names. diff --git a/src/ssl_api_ext.c b/src/ssl_api_ext.c index d76521968bb..0d947b63042 100644 --- a/src/ssl_api_ext.c +++ b/src/ssl_api_ext.c @@ -157,6 +157,138 @@ int wolfSSL_SNI_GetFromBuffer(const byte* clientHello, word32 helloSz, #endif /* HAVE_SNI */ +#if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CA_NAMES) && \ + defined(WOLFSSL_TLS13) + +/* Maximum content size accepted by the native API. The library wraps the + * content with a DER SEQUENCE header (up to MAX_SEQ_SZ bytes) and the wire + * entry length is itself a 16-bit field, so content is capped at + * WOLFSSL_MAX_16BIT - MAX_SEQ_SZ bytes. */ +#define WOLFSSL_CA_NAME_MAX_CONTENT_SZ (WOLFSSL_MAX_16BIT - MAX_SEQ_SZ) + +/* Add a CA distinguished name to advertise in the TLS 1.3 + * certificate_authorities extension on the object. + * + * @param [in] ssl SSL/TLS object. + * @param [in] dn DER-encoded Name content (no SEQUENCE header). + * @param [in] dnSz Length of dn in bytes. + * @return 0 on success. + * @return BAD_FUNC_ARG when ssl or dn is NULL, or dnSz is out of range. + * @return Negative value on error. + */ +int wolfSSL_UseCertificateAuthority(WOLFSSL* ssl, + const unsigned char* dn, unsigned int dnSz) +{ + if (ssl == NULL || dn == NULL || dnSz == 0 || + dnSz > WOLFSSL_CA_NAME_MAX_CONTENT_SZ) + return BAD_FUNC_ARG; + + return TLSX_CertificateAuthorities_Add(&ssl->ws_ca_names, + dn, (word16)dnSz, ssl->heap); +} + +/* Add a CA distinguished name to advertise in the TLS 1.3 + * certificate_authorities extension on the context. + * + * @param [in] ctx SSL/TLS context object. + * @param [in] dn DER-encoded Name content (no SEQUENCE header). + * @param [in] dnSz Length of dn in bytes. + * @return 0 on success. + * @return BAD_FUNC_ARG when ctx or dn is NULL, or dnSz is out of range. + * @return Negative value on error. + */ +int wolfSSL_CTX_UseCertificateAuthority(WOLFSSL_CTX* ctx, + const unsigned char* dn, unsigned int dnSz) +{ + if (ctx == NULL || dn == NULL || dnSz == 0 || + dnSz > WOLFSSL_CA_NAME_MAX_CONTENT_SZ) + return BAD_FUNC_ARG; + + return TLSX_CertificateAuthorities_Add(&ctx->ws_ca_names, + dn, (word16)dnSz, ctx->heap); +} + +/* Free all CA distinguished names set on the object. + * + * @param [in] ssl SSL/TLS object. + */ +void wolfSSL_ClearCertificateAuthorities(WOLFSSL* ssl) +{ + if (ssl == NULL) + return; + TLSX_CertificateAuthorities_FreeAll(ssl->ws_ca_names, ssl->heap); + ssl->ws_ca_names = NULL; +} + +/* Free all CA distinguished names set on the context. + * + * @param [in] ctx SSL/TLS context object. + */ +void wolfSSL_CTX_ClearCertificateAuthorities(WOLFSSL_CTX* ctx) +{ + if (ctx == NULL) + return; + TLSX_CertificateAuthorities_FreeAll(ctx->ws_ca_names, ctx->heap); + ctx->ws_ca_names = NULL; +} + +/* Get the number of CA distinguished names received from the peer's + * certificate_authorities extension. + * + * @param [in] ssl SSL/TLS object. + * @return Count of peer CA names, or 0 when ssl is NULL. + */ +int wolfSSL_GetPeerCertificateAuthorityCount(const WOLFSSL* ssl) +{ + int count = 0; + CertificateAuthority* cur; + + if (ssl == NULL) + return 0; + for (cur = ssl->ws_peer_ca_names; cur != NULL; cur = cur->next) + count++; + return count; +} + +/* Copy a peer CA distinguished name by index. + * + * @param [in] ssl SSL/TLS object. + * @param [in] idx Zero-based index of the peer CA name. + * @param [out] outDn Buffer to receive the DN content, or NULL to query + * the length. + * @param [in] outDnSz Size of outDn in bytes. + * @return Length of the DN content on success. + * @return BAD_FUNC_ARG when ssl is NULL or idx is out of range. + * @return BUFFER_E when outDn is too small. + */ +int wolfSSL_GetPeerCertificateAuthority(const WOLFSSL* ssl, int idx, + unsigned char* outDn, unsigned int outDnSz) +{ + CertificateAuthority* cur; + int i; + + if (ssl == NULL || idx < 0) + return BAD_FUNC_ARG; + + cur = ssl->ws_peer_ca_names; + for (i = 0; i < idx && cur != NULL; i++) + cur = cur->next; + if (cur == NULL) + return BAD_FUNC_ARG; + + if (outDn == NULL) + return (int)cur->dnSz; + + if (outDnSz < cur->dnSz) + return BUFFER_E; + + XMEMCPY(outDn, cur->dn, cur->dnSz); + return (int)cur->dnSz; +} + +#endif /* !NO_CERTS && !WOLFSSL_NO_CA_NAMES && WOLFSSL_TLS13 */ + + #ifdef HAVE_TRUSTED_CA /* Set the Trusted CA Indication extension on the object. diff --git a/src/tls.c b/src/tls.c index 1fe31d43af0..5f77a0d2885 100644 --- a/src/tls.c +++ b/src/tls.c @@ -7700,61 +7700,132 @@ int TLSX_Cookie_Use(const WOLFSSL* ssl, const byte* data, word16 len, byte* mac, #endif #if defined(WOLFSSL_TLS13) && !defined(NO_CERTS) && \ - !defined(WOLFSSL_NO_CA_NAMES) && defined(OPENSSL_EXTRA) -/* Currently only settable through compatibility API */ + !defined(WOLFSSL_NO_CA_NAMES) /******************************************************************************/ -/* Certificate Authorities */ +/* Certificate Authorities */ /******************************************************************************/ -static word16 TLSX_CA_Names_GetSize(void* data) +/* Push a copy of dn/dnSz onto the list head. */ +int TLSX_CertificateAuthorities_Add(CertificateAuthority** head, + const byte* dn, word16 dnSz, void* heap) { - WOLFSSL* ssl = (WOLFSSL*)data; - WOLF_STACK_OF(WOLFSSL_X509_NAME)* names; - word32 size = 0; + CertificateAuthority* node; + size_t sz; - /* Length of names */ - size += OPAQUE16_LEN; - for (names = SSL_PRIORITY_CA_NAMES(ssl); names != NULL; names = names->next) { - byte seq[MAX_SEQ_SZ]; - WOLFSSL_X509_NAME* name = names->data.name; + if (head == NULL || dn == NULL || dnSz == 0) + return BAD_FUNC_ARG; - if (name != NULL) { - /* 16-bit length | SEQ | Len | DER of name */ - size += (word32)(OPAQUE16_LEN + SetSequence(name->rawLen, seq) + - name->rawLen); - if (size > WOLFSSL_MAX_16BIT) { - return 0; - } - } + sz = sizeof(*node) + (size_t)dnSz; + node = (CertificateAuthority*)XMALLOC(sz, heap, DYNAMIC_TYPE_TLSX); + if (node == NULL) + return MEMORY_ERROR; + XMEMCPY(node->dn, dn, dnSz); + node->dnSz = dnSz; + node->next = *head; + *head = node; + return 0; +} + +void TLSX_CertificateAuthorities_FreeAll(CertificateAuthority* head, void* heap) +{ + while (head != NULL) { + CertificateAuthority* next = head->next; + XFREE(head, heap, DYNAMIC_TYPE_TLSX); + head = next; } - return (word16)size; +} +/* True if any CA name (compat or wolfSSL native) is configured. */ +static int HasAnyCANames(const WOLFSSL* ssl) +{ +#ifdef OPENSSL_EXTRA + if (SSL_PRIORITY_CA_NAMES(ssl) != NULL) + return 1; +#endif + if (WS_CA_NAMES(ssl) != NULL) + return 1; + return 0; } -static word16 TLSX_CA_Names_Write(void* data, byte* output) +/* Certificate_authorities extension emitter. Walks the optional OPENSSL_EXTRA + * compat stack and the native list and either accumulates the payload size + * into *pSz (output == NULL) or serializes the payload into output and + * accumulates the written length. + * + * RFC 8446 4.2.4: + * opaque DistinguishedName<1..2^16-1>; + * struct { + * DistinguishedName authorities<3..2^16-1>; + * } CertificateAuthoritiesExtension; + * + * Each DN entry is at most 2^16-1 bytes and the whole authorities vector is + * also at most 2^16-1 bytes. Returns 0 on success, BUFFER_ERROR if any entry + * or the combined total would exceed either cap. */ +static int TLSX_CA_Names_Write(WOLFSSL* ssl, byte* output, word16* pSz) { - WOLFSSL* ssl = (WOLFSSL*)data; - WOLF_STACK_OF(WOLFSSL_X509_NAME)* names; - byte* len; + CertificateAuthority* cur; + word32 total = OPAQUE16_LEN; /* outer 16-bit length */ + byte* outerLen = output; - /* Reserve space for the length value */ - len = output; - output += OPAQUE16_LEN; - for (names = SSL_PRIORITY_CA_NAMES(ssl); names != NULL; names = names->next) { - byte seq[MAX_SEQ_SZ]; - WOLFSSL_X509_NAME* name = names->data.name; + if (output != NULL) + output += OPAQUE16_LEN; - if (name != NULL) { - c16toa((word16)name->rawLen + - (word16)SetSequence(name->rawLen, seq), output); +#ifdef OPENSSL_EXTRA + { + WOLF_STACK_OF(WOLFSSL_X509_NAME)* names; + for (names = SSL_PRIORITY_CA_NAMES(ssl); names != NULL; + names = names->next) { + byte seq[MAX_SEQ_SZ]; + word32 seqSz; + word32 entrySz; + WOLFSSL_X509_NAME* name = names->data.name; + + if (name == NULL || name->rawLen <= 0) + continue; + seqSz = SetSequence((word32)name->rawLen, seq); + entrySz = seqSz + (word32)name->rawLen; + /* Per-DN cap, and cumulative cap on the whole extension payload + * (outer length included) so *pSz below stays exact. */ + if (entrySz > WOLFSSL_MAX_16BIT || + total + OPAQUE16_LEN + entrySz > WOLFSSL_MAX_16BIT) + return BUFFER_ERROR; + /* 16-bit entry length | SEQ hdr | DER of name */ + total += OPAQUE16_LEN + entrySz; + if (output != NULL) { + c16toa((word16)entrySz, output); + output += OPAQUE16_LEN; + XMEMCPY(output, seq, seqSz); + output += seqSz; + XMEMCPY(output, name->raw, name->rawLen); + output += name->rawLen; + } + } + } +#endif + + /* Native entries store the inner subject content; wrap with a SEQUENCE + * header to form the full DER Name expected on the wire. */ + for (cur = WS_CA_NAMES(ssl); cur != NULL; cur = cur->next) { + byte seq[MAX_SEQ_SZ]; + word32 seqSz = SetSequence(cur->dnSz, seq); + word32 entrySz = seqSz + (word32)cur->dnSz; + if (entrySz > WOLFSSL_MAX_16BIT || + total + OPAQUE16_LEN + entrySz > WOLFSSL_MAX_16BIT) + return BUFFER_ERROR; + total += OPAQUE16_LEN + entrySz; + if (output != NULL) { + c16toa((word16)entrySz, output); output += OPAQUE16_LEN; - output += SetSequence(name->rawLen, output); - XMEMCPY(output, name->raw, name->rawLen); - output += name->rawLen; + XMEMCPY(output, seq, seqSz); + output += seqSz; + XMEMCPY(output, cur->dn, cur->dnSz); + output += cur->dnSz; } } - /* Write the total length */ - c16toa((word16)(output - len - OPAQUE16_LEN), len); - return (word16)(output - len); + + if (outerLen != NULL) + c16toa((word16)(total - OPAQUE16_LEN), outerLen); + *pSz += (word16)total; + return 0; } static int TLSX_CA_Names_Parse(WOLFSSL *ssl, const byte* input, @@ -7764,10 +7835,16 @@ static int TLSX_CA_Names_Parse(WOLFSSL *ssl, const byte* input, (void)isRequest; + /* Reset the wolfSSL native peer list. */ + TLSX_CertificateAuthorities_FreeAll(ssl->ws_peer_ca_names, ssl->heap); + ssl->ws_peer_ca_names = NULL; + +#ifdef OPENSSL_EXTRA wolfSSL_sk_X509_NAME_pop_free(ssl->peer_ca_names, NULL); ssl->peer_ca_names = wolfSSL_sk_X509_NAME_new(NULL); if (ssl->peer_ca_names == NULL) return MEMORY_ERROR; +#endif if (length < OPAQUE16_LEN) return BUFFER_ERROR; @@ -7777,75 +7854,90 @@ static int TLSX_CA_Names_Parse(WOLFSSL *ssl, const byte* input, length -= OPAQUE16_LEN; if (extLen != length) return BUFFER_ERROR; + /* authorities<3..2^16-1>: minimum 3 bytes when present. */ + if (length > 0 && length < 3) + return BUFFER_ERROR; while (length) { - word16 idx = 0; - WOLFSSL_X509_NAME* name = NULL; - int ret = 0; - int didInit = FALSE; - /* Use a DecodedCert struct to get access to GetName to - * parse DN name */ -#ifdef WOLFSSL_SMALL_STACK - DecodedCert *cert = (DecodedCert *)XMALLOC( - sizeof(*cert), ssl->heap, DYNAMIC_TYPE_DCERT); - if (cert == NULL) - return MEMORY_ERROR; -#else - DecodedCert cert[1]; -#endif + word16 entrySz; + word32 seqIdx = 0; + int innerLen = 0; + int ret; - if (length < OPAQUE16_LEN) { - ret = BUFFER_ERROR; - } + if (length < OPAQUE16_LEN) + return BUFFER_ERROR; + ato16(input, &entrySz); + /* DistinguishedName<1..2^16-1>: each entry must be at least 1 byte. */ + if (entrySz == 0) + return BUFFER_ERROR; + if ((word16)(length - OPAQUE16_LEN) < entrySz) + return BUFFER_ERROR; - if (ret == 0) { - ato16(input, &extLen); - idx += OPAQUE16_LEN; + /* Strip the outer SEQUENCE so that the native list stores subject + * content only, mirroring the send path. */ + if (GetSequence(input + OPAQUE16_LEN, &seqIdx, &innerLen, + entrySz) < 0) + return BUFFER_ERROR; + if ((word32)innerLen + seqIdx != entrySz) + return BUFFER_ERROR; - if (extLen > length - idx) - ret = BUFFER_ERROR; - } + ret = TLSX_CertificateAuthorities_Add(&ssl->ws_peer_ca_names, + input + OPAQUE16_LEN + seqIdx, (word16)innerLen, + ssl->heap); + if (ret != 0) + return ret; - if (ret == 0) { - InitDecodedCert(cert, input + idx, extLen, ssl->heap); +#ifdef OPENSSL_EXTRA + { + WOLFSSL_X509_NAME* name = NULL; + int didInit = FALSE; +#ifdef WOLFSSL_SMALL_STACK + DecodedCert *cert = (DecodedCert *)XMALLOC( + sizeof(*cert), ssl->heap, DYNAMIC_TYPE_DCERT); + if (cert == NULL) + return MEMORY_ERROR; +#else + DecodedCert cert[1]; +#endif + InitDecodedCert(cert, input + OPAQUE16_LEN, entrySz, ssl->heap); didInit = TRUE; - idx += extLen; - ret = GetName(cert, ASN_SUBJECT, extLen); - } + ret = GetName(cert, ASN_SUBJECT, entrySz); - if (ret == 0 && (name = wolfSSL_X509_NAME_new()) == NULL) - ret = MEMORY_ERROR; - - if (ret == 0) { - CopyDecodedName(name, cert, ASN_SUBJECT); - if (wolfSSL_sk_X509_NAME_push(ssl->peer_ca_names, name) <= 0) { - wolfSSL_X509_NAME_free(name); + if (ret == 0 && (name = wolfSSL_X509_NAME_new()) == NULL) ret = MEMORY_ERROR; + + if (ret == 0) { + CopyDecodedName(name, cert, ASN_SUBJECT); + if (wolfSSL_sk_X509_NAME_push(ssl->peer_ca_names, name) <= 0) { + wolfSSL_X509_NAME_free(name); + ret = MEMORY_ERROR; + } } - } - if (didInit) - FreeDecodedCert(cert); + if (didInit) + FreeDecodedCert(cert); - WC_FREE_VAR_EX(cert, ssl->heap, DYNAMIC_TYPE_DCERT); - if (ret != 0) - return ret; + WC_FREE_VAR_EX(cert, ssl->heap, DYNAMIC_TYPE_DCERT); + if (ret != 0) + return ret; + } +#endif /* OPENSSL_EXTRA */ - input += idx; - length -= idx; + input += OPAQUE16_LEN + entrySz; + length -= OPAQUE16_LEN + entrySz; } return 0; } -#define CAN_GET_SIZE(data) TLSX_CA_Names_GetSize(data) -#define CAN_WRITE(data, output) TLSX_CA_Names_Write(data, output) +#define CAN_GET_SIZE(ssl, pSz) TLSX_CA_Names_Write(ssl, NULL, pSz) +#define CAN_WRITE(ssl, output, pSz) TLSX_CA_Names_Write(ssl, output, pSz) #define CAN_PARSE(ssl, input, length, isRequest) \ TLSX_CA_Names_Parse(ssl, input, length, isRequest) #else -#define CAN_GET_SIZE(data) 0 -#define CAN_WRITE(data, output) 0 +#define CAN_GET_SIZE(ssl, pSz) 0 +#define CAN_WRITE(ssl, output, pSz) 0 #define CAN_PARSE(ssl, input, length, isRequest) 0 #endif @@ -15558,16 +15650,11 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType, #endif #if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CA_NAMES) - case TLSX_CERTIFICATE_AUTHORITIES: { - word16 canSz = CAN_GET_SIZE(extension->data); - /* 0 on non-empty list means 16-bit overflow. */ - if (canSz == 0) { - ret = LENGTH_ERROR; - break; - } - length += canSz; + case TLSX_CERTIFICATE_AUTHORITIES: + cbShim = 0; + ret = CAN_GET_SIZE((WOLFSSL*)extension->data, &cbShim); + length += cbShim; break; - } #endif #endif #ifdef WOLFSSL_SRTP @@ -15622,6 +15709,10 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType, /* marks the extension as processed so ctx level */ /* extensions don't overlap with ssl level ones. */ TURN_ON(semaphore, TLSX_ToSemaphore((word16)extension->type)); + + /* if we encountered an error propagate it */ + if (ret != 0) + break; } if ((word32)*pLength + length > WOLFSSL_MAX_16BIT) { @@ -15863,7 +15954,10 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore, #if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CA_NAMES) case TLSX_CERTIFICATE_AUTHORITIES: WOLFSSL_MSG("Certificate Authorities extension to write"); - offset += CAN_WRITE(extension->data, output + offset); + cbShim = 0; + ret = CAN_WRITE((WOLFSSL*)extension->data, + output + offset, &cbShim); + offset += cbShim; break; #endif #endif @@ -16457,8 +16551,7 @@ int TLSX_PopulateExtensions(WOLFSSL* ssl, byte isServer) #endif #ifdef WOLFSSL_TLS13 #if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CA_NAMES) - if (IsAtLeastTLSv1_3(ssl->version) && - SSL_PRIORITY_CA_NAMES(ssl) != NULL) { + if (IsAtLeastTLSv1_3(ssl->version) && HasAnyCANames(ssl)) { WOLFSSL_MSG("Adding certificate authorities extension"); if ((ret = TLSX_Push(&ssl->extensions, TLSX_CERTIFICATE_AUTHORITIES, ssl, ssl->heap)) != 0) { @@ -17500,8 +17593,7 @@ int TLSX_GetRequestSize(WOLFSSL* ssl, byte msgType, word32* pLength) } #endif #if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CA_NAMES) - if (!IsAtLeastTLSv1_3(ssl->version) || - SSL_CA_NAMES(ssl) == NULL) { + if (!IsAtLeastTLSv1_3(ssl->version) || !HasAnyCANames(ssl)) { TURN_ON(semaphore, TLSX_ToSemaphore(TLSX_CERTIFICATE_AUTHORITIES)); } @@ -17526,7 +17618,7 @@ int TLSX_GetRequestSize(WOLFSSL* ssl, byte msgType, word32* pLength) TURN_OFF(semaphore, TLSX_ToSemaphore(TLSX_SIGNATURE_ALGORITHMS)); #endif #if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CA_NAMES) - if (SSL_PRIORITY_CA_NAMES(ssl) != NULL) { + if (HasAnyCANames(ssl)) { TURN_OFF(semaphore, TLSX_ToSemaphore(TLSX_CERTIFICATE_AUTHORITIES)); } @@ -17740,7 +17832,7 @@ int TLSX_WriteRequest(WOLFSSL* ssl, byte* output, byte msgType, word32* pOffset) } #endif #if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CA_NAMES) - if (!IsAtLeastTLSv1_3(ssl->version) || SSL_CA_NAMES(ssl) == NULL) { + if (!IsAtLeastTLSv1_3(ssl->version) || !HasAnyCANames(ssl)) { TURN_ON(semaphore, TLSX_ToSemaphore(TLSX_CERTIFICATE_AUTHORITIES)); } @@ -17771,7 +17863,7 @@ int TLSX_WriteRequest(WOLFSSL* ssl, byte* output, byte msgType, word32* pOffset) TURN_OFF(semaphore, TLSX_ToSemaphore(TLSX_SIGNATURE_ALGORITHMS)); #endif #if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CA_NAMES) - if (SSL_PRIORITY_CA_NAMES(ssl) != NULL) { + if (HasAnyCANames(ssl)) { TURN_OFF(semaphore, TLSX_ToSemaphore(TLSX_CERTIFICATE_AUTHORITIES)); } diff --git a/tests/api.c b/tests/api.c index d182d0000cb..ecb4cca3db0 100644 --- a/tests/api.c +++ b/tests/api.c @@ -39129,6 +39129,13 @@ TEST_CASE testCases[] = { TEST_DECL(test_wolfSSL_DisableExtendedMasterSecret), TEST_DECL(test_certificate_authorities_certificate_request), TEST_DECL(test_certificate_authorities_client_hello), + TEST_DECL(test_wolfSSL_UseCertificateAuthority_args), + TEST_DECL(test_wolfSSL_UseCertificateAuthority_size_limits), + TEST_DECL(test_wolfSSL_UseCertificateAuthority_counts), + TEST_DECL(test_wolfSSL_GetPeerCertificateAuthority_empty), + TEST_DECL(test_wolfSSL_CertificateAuthority_handshake), + TEST_DECL(test_wolfSSL_CertificateAuthority_ctx_handshake), + TEST_DECL(test_wolfSSL_CertificateAuthority_cert_cb), TEST_DECL(test_TLSX_TCA_Find), TEST_DECL(test_TLSX_SNI_GetSize_overflow), TEST_DECL(test_TLSX_ECH_msg_type_validation), diff --git a/tests/api/test_tls_ext.c b/tests/api/test_tls_ext.c index 6091ffc89eb..be44508c9ba 100644 --- a/tests/api/test_tls_ext.c +++ b/tests/api/test_tls_ext.c @@ -29,6 +29,11 @@ #endif #include +#include +#include +/* Must precede certs_test.h: defines USE_CERT_BUFFERS_2048. */ +#include +#include #include #include @@ -2158,3 +2163,488 @@ int test_TLSX_PointFormat_uncompressed_required(void) #endif return EXPECT_RESULT(); } + +/* ------------------------------------------------------------------------- */ +/* Tests for the native certificate_authorities API */ +/* ------------------------------------------------------------------------- */ + +#if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CA_NAMES) && !defined(NO_TLS) && \ + !defined(NO_WOLFSSL_CLIENT) && defined(WOLFSSL_TLS13) + +/* Minimal valid DER X.509 Name contents: RDN set containing CN=. 12 bytes. + * The outer SEQUENCE header is added by the library on the wire. Varying byte + * 11 yields a unique, parseable DN without touching the outer length. */ +static const byte kMinDnTemplate[] = { + 0x31, 0x0A, 0x30, 0x08, 0x06, 0x03, + 0x55, 0x04, 0x03, 0x0C, 0x01, 0x41 +}; + +static void make_min_dn(byte* out, byte tag) +{ + XMEMCPY(out, kMinDnTemplate, sizeof(kMinDnTemplate)); + out[11] = tag; +} + +#endif + +int test_wolfSSL_UseCertificateAuthority_args(void) +{ + EXPECT_DECLS; +#if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CA_NAMES) && !defined(NO_TLS) && \ + !defined(NO_WOLFSSL_CLIENT) && defined(WOLFSSL_TLS13) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte dn[sizeof(kMinDnTemplate)]; + + make_min_dn(dn, 'X'); + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + /* NULL ssl / ctx */ + ExpectIntEQ(wolfSSL_UseCertificateAuthority(NULL, dn, sizeof(dn)), + BAD_FUNC_ARG); + ExpectIntEQ(wolfSSL_CTX_UseCertificateAuthority(NULL, dn, sizeof(dn)), + BAD_FUNC_ARG); + + /* NULL dn */ + ExpectIntEQ(wolfSSL_UseCertificateAuthority(ssl, NULL, 5), BAD_FUNC_ARG); + ExpectIntEQ(wolfSSL_CTX_UseCertificateAuthority(ctx, NULL, 5), + BAD_FUNC_ARG); + + /* dnSz == 0 */ + ExpectIntEQ(wolfSSL_UseCertificateAuthority(ssl, dn, 0), BAD_FUNC_ARG); + ExpectIntEQ(wolfSSL_CTX_UseCertificateAuthority(ctx, dn, 0), BAD_FUNC_ARG); + + /* dnSz above the content limit */ + ExpectIntEQ(wolfSSL_UseCertificateAuthority(ssl, dn, 0xFFFCU), + BAD_FUNC_ARG); + ExpectIntEQ(wolfSSL_UseCertificateAuthority(ssl, dn, 0x10000U), + BAD_FUNC_ARG); + ExpectIntEQ(wolfSSL_UseCertificateAuthority(ssl, dn, 0xFFFFFFFFU), + BAD_FUNC_ARG); + ExpectIntEQ(wolfSSL_CTX_UseCertificateAuthority(ctx, dn, 0x10000U), + BAD_FUNC_ARG); + + /* Valid */ + ExpectIntEQ(wolfSSL_UseCertificateAuthority(ssl, dn, sizeof(dn)), 0); + ExpectIntEQ(wolfSSL_CTX_UseCertificateAuthority(ctx, dn, sizeof(dn)), 0); + + /* Clear accepts NULL and may be called repeatedly */ + wolfSSL_ClearCertificateAuthorities(NULL); + wolfSSL_CTX_ClearCertificateAuthorities(NULL); + wolfSSL_ClearCertificateAuthorities(ssl); + wolfSSL_ClearCertificateAuthorities(ssl); + wolfSSL_CTX_ClearCertificateAuthorities(ctx); + wolfSSL_CTX_ClearCertificateAuthorities(ctx); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_wolfSSL_UseCertificateAuthority_size_limits(void) +{ + EXPECT_DECLS; +#if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CA_NAMES) && !defined(NO_TLS) && \ + !defined(NO_WOLFSSL_CLIENT) && defined(WOLFSSL_TLS13) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte* bigDn = NULL; + byte oneByte = 0x30; + unsigned int maxSz = WOLFSSL_MAX_16BIT - MAX_SEQ_SZ; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + /* Minimum: 1 byte */ + ExpectIntEQ(wolfSSL_UseCertificateAuthority(ssl, &oneByte, 1), 0); + wolfSSL_ClearCertificateAuthorities(ssl); + + /* The API caps content at WOLFSSL_MAX_16BIT - MAX_SEQ_SZ (wire entry + * length is word16 and must include the DER SEQUENCE header added by + * the library). */ + ExpectNotNull(bigDn = + (byte*)XMALLOC(maxSz, NULL, DYNAMIC_TYPE_TMP_BUFFER)); + if (bigDn != NULL) + XMEMSET(bigDn, 0x42, maxSz); + + /* Just under the limit */ + ExpectIntEQ(wolfSSL_UseCertificateAuthority(ssl, bigDn, maxSz - 1), 0); + wolfSSL_ClearCertificateAuthorities(ssl); + + /* Exactly at the limit */ + ExpectIntEQ(wolfSSL_UseCertificateAuthority(ssl, bigDn, maxSz), 0); + wolfSSL_ClearCertificateAuthorities(ssl); + + /* Just over the limit */ + ExpectIntEQ(wolfSSL_UseCertificateAuthority(ssl, bigDn, maxSz + 1), + BAD_FUNC_ARG); + ExpectIntEQ(wolfSSL_UseCertificateAuthority(ssl, bigDn, 0x10000U), + BAD_FUNC_ARG); + + /* Same checks on the CTX */ + ExpectIntEQ(wolfSSL_CTX_UseCertificateAuthority(ctx, bigDn, maxSz), 0); + wolfSSL_CTX_ClearCertificateAuthorities(ctx); + ExpectIntEQ(wolfSSL_CTX_UseCertificateAuthority(ctx, bigDn, maxSz + 1), + BAD_FUNC_ARG); + ExpectIntEQ(wolfSSL_CTX_UseCertificateAuthority(ctx, bigDn, 0x10000U), + BAD_FUNC_ARG); + + XFREE(bigDn, NULL, DYNAMIC_TYPE_TMP_BUFFER); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_wolfSSL_UseCertificateAuthority_counts(void) +{ + EXPECT_DECLS; +#if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CA_NAMES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + defined(WOLFSSL_TLS13) && defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) + /* Add many names through the public API, run a handshake, and verify + * the server's peer count matches what the client added. */ + struct test_memio_ctx test_ctx; + WOLFSSL_CTX* ctx_cli = NULL; + WOLFSSL_CTX* ctx_srv = NULL; + WOLFSSL* ssl_cli = NULL; + WOLFSSL* ssl_srv = NULL; + byte dn[sizeof(kMinDnTemplate)]; + int i; + const int count = 200; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(0, test_memio_setup(&test_ctx, &ctx_cli, &ctx_srv, + &ssl_cli, &ssl_srv, wolfTLSv1_3_client_method, + wolfTLSv1_3_server_method)); + + for (i = 0; i < count; i++) { + make_min_dn(dn, (byte)(i & 0x7F)); + ExpectIntEQ(wolfSSL_UseCertificateAuthority(ssl_cli, dn, sizeof(dn)), + 0); + } + + /* Clear wipes the list: a handshake started fresh afterward sends zero + * names. We verify the full cycle below. */ + wolfSSL_ClearCertificateAuthorities(ssl_cli); + wolfSSL_ClearCertificateAuthorities(ssl_cli); /* idempotent */ + + /* Re-populate with the final count. */ + for (i = 0; i < count; i++) { + make_min_dn(dn, (byte)(i & 0x7F)); + ExpectIntEQ(wolfSSL_UseCertificateAuthority(ssl_cli, dn, sizeof(dn)), + 0); + } + + ExpectIntEQ(0, test_memio_do_handshake(ssl_cli, ssl_srv, 10, NULL)); + ExpectIntEQ(wolfSSL_GetPeerCertificateAuthorityCount(ssl_srv), count); + + wolfSSL_free(ssl_cli); + wolfSSL_CTX_free(ctx_cli); + wolfSSL_free(ssl_srv); + wolfSSL_CTX_free(ctx_srv); +#endif + return EXPECT_RESULT(); +} + +int test_wolfSSL_GetPeerCertificateAuthority_empty(void) +{ + EXPECT_DECLS; +#if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CA_NAMES) && !defined(NO_TLS) && \ + !defined(NO_WOLFSSL_CLIENT) && defined(WOLFSSL_TLS13) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + unsigned char buf[16]; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + /* NULL ssl */ + ExpectIntEQ(wolfSSL_GetPeerCertificateAuthorityCount(NULL), 0); + ExpectIntLT( + wolfSSL_GetPeerCertificateAuthority(NULL, 0, buf, sizeof(buf)), 0); + ExpectIntLT(wolfSSL_GetPeerCertificateAuthority(NULL, 0, NULL, 0), 0); + + /* Empty peer list */ + ExpectIntEQ(wolfSSL_GetPeerCertificateAuthorityCount(ssl), 0); + ExpectIntLT( + wolfSSL_GetPeerCertificateAuthority(ssl, 0, buf, sizeof(buf)), 0); + ExpectIntLT(wolfSSL_GetPeerCertificateAuthority(ssl, 0, NULL, 0), 0); + + /* Negative indices always fail */ + ExpectIntLT( + wolfSSL_GetPeerCertificateAuthority(ssl, -1, buf, sizeof(buf)), 0); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_wolfSSL_CertificateAuthority_handshake(void) +{ + EXPECT_DECLS; +#if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CA_NAMES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + defined(WOLFSSL_TLS13) && defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) + /* Exercise the full send/parse pipeline at several name counts, + * including empty, one, a handful, and enough to force many list nodes. */ + const int counts[] = { 0, 1, 3, 17 }; + size_t ci; + + for (ci = 0; ci < sizeof(counts) / sizeof(*counts); ci++) { + struct test_memio_ctx test_ctx; + WOLFSSL_CTX* ctx_cli = NULL; + WOLFSSL_CTX* ctx_srv = NULL; + WOLFSSL* ssl_cli = NULL; + WOLFSSL* ssl_srv = NULL; + byte dn[sizeof(kMinDnTemplate)]; + int i; + const int count = counts[ci]; + int peerCount; + + if (EXPECT_FAIL()) + break; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(0, test_memio_setup(&test_ctx, &ctx_cli, &ctx_srv, + &ssl_cli, &ssl_srv, wolfTLSv1_3_client_method, + wolfTLSv1_3_server_method)); + + for (i = 0; i < count; i++) { + make_min_dn(dn, (byte)('A' + i)); + ExpectIntEQ(wolfSSL_UseCertificateAuthority(ssl_cli, dn, + sizeof(dn)), 0); + } + + ExpectIntEQ(0, test_memio_do_handshake(ssl_cli, ssl_srv, 10, NULL)); + + /* Empty set: the extension should not have been emitted, so the + * server's peer list stays empty. */ + peerCount = wolfSSL_GetPeerCertificateAuthorityCount(ssl_srv); + ExpectIntEQ(peerCount, count); + + if (count > 0 && peerCount == count) { + /* Verify every sent DN shows up exactly once on the server. + * Iteration order is unspecified, so match by tag byte. */ + int seen[32]; + XMEMSET(seen, 0, sizeof(seen)); + for (i = 0; i < count; i++) { + byte expected[sizeof(kMinDnTemplate)]; + int idx; + int matched = 0; + + make_min_dn(expected, (byte)('A' + i)); + for (idx = 0; idx < peerCount; idx++) { + byte buf[sizeof(kMinDnTemplate)]; + int sz = wolfSSL_GetPeerCertificateAuthority(ssl_srv, idx, + buf, sizeof(buf)); + if (sz == (int)sizeof(expected) && !seen[idx] && + XMEMCMP(buf, expected, sz) == 0) { + seen[idx] = 1; + matched = 1; + break; + } + } + ExpectIntEQ(matched, 1); + } + + /* Sizing query on index 0: NULL buf returns the DN length. */ + ExpectIntEQ(wolfSSL_GetPeerCertificateAuthority(ssl_srv, 0, + NULL, 0), (int)sizeof(kMinDnTemplate)); + + /* Too-small buffer returns BUFFER_E and leaves the out buffer + * untouched. */ + { + byte buf[sizeof(kMinDnTemplate)]; + byte guard[sizeof(kMinDnTemplate)]; + XMEMSET(buf, 0xCD, sizeof(buf)); + XMEMSET(guard, 0xCD, sizeof(guard)); + ExpectIntEQ(wolfSSL_GetPeerCertificateAuthority(ssl_srv, 0, + buf, (unsigned int)sizeof(buf) - 1), BUFFER_E); + ExpectIntEQ(XMEMCMP(buf, guard, sizeof(buf)), 0); + } + + /* Exact-size buffer succeeds. */ + { + byte buf[sizeof(kMinDnTemplate)]; + ExpectIntEQ(wolfSSL_GetPeerCertificateAuthority(ssl_srv, 0, + buf, (unsigned int)sizeof(buf)), + (int)sizeof(kMinDnTemplate)); + } + + /* Out-of-range idx returns BAD_FUNC_ARG. */ + { + byte buf[sizeof(kMinDnTemplate)]; + ExpectIntLT(wolfSSL_GetPeerCertificateAuthority(ssl_srv, + peerCount, buf, sizeof(buf)), 0); + ExpectIntLT(wolfSSL_GetPeerCertificateAuthority(ssl_srv, + peerCount + 100, buf, sizeof(buf)), 0); + } + } + + /* Clearing the sent list on an already-handshook SSL is still + * valid. */ + wolfSSL_ClearCertificateAuthorities(ssl_cli); + + wolfSSL_free(ssl_cli); + wolfSSL_CTX_free(ctx_cli); + wolfSSL_free(ssl_srv); + wolfSSL_CTX_free(ctx_srv); + } +#endif + return EXPECT_RESULT(); +} + +int test_wolfSSL_CertificateAuthority_ctx_handshake(void) +{ + EXPECT_DECLS; +#if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CA_NAMES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + defined(WOLFSSL_TLS13) && defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) + /* CA names set on the CTX (not the SSL) must still be emitted on the + * wire via the WS_CA_NAMES(ssl) fallback. */ + struct test_memio_ctx test_ctx; + WOLFSSL_CTX* ctx_cli = NULL; + WOLFSSL_CTX* ctx_srv = NULL; + WOLFSSL* ssl_cli = NULL; + WOLFSSL* ssl_srv = NULL; + byte dn[sizeof(kMinDnTemplate)]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(0, test_memio_setup(&test_ctx, &ctx_cli, &ctx_srv, + &ssl_cli, &ssl_srv, wolfTLSv1_3_client_method, + wolfTLSv1_3_server_method)); + + /* Two names on the CTX; SSL has none of its own. */ + make_min_dn(dn, 'P'); + ExpectIntEQ(wolfSSL_CTX_UseCertificateAuthority(ctx_cli, dn, sizeof(dn)), 0); + make_min_dn(dn, 'Q'); + ExpectIntEQ(wolfSSL_CTX_UseCertificateAuthority(ctx_cli, dn, sizeof(dn)), 0); + + ExpectIntEQ(0, test_memio_do_handshake(ssl_cli, ssl_srv, 10, NULL)); + + /* Server's peer list has both CTX entries. */ + ExpectIntEQ(wolfSSL_GetPeerCertificateAuthorityCount(ssl_srv), 2); + + wolfSSL_free(ssl_cli); + wolfSSL_CTX_free(ctx_cli); + wolfSSL_free(ssl_srv); + wolfSSL_CTX_free(ctx_srv); +#endif + return EXPECT_RESULT(); +} + +#if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CA_NAMES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + defined(WOLFSSL_TLS13) && defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_CERT_SETUP_CB) && !defined(NO_FILESYSTEM) && \ + defined(USE_CERT_BUFFERS_2048) && \ + (!defined(IGNORE_NAME_CONSTRAINTS) || defined(WOLFSSL_CERT_EXT)) + +#define SVR_DN_MAX 1024 + +struct cert_cb_arg { + int peerCount; + byte firstDn[SVR_DN_MAX]; + int firstDnSz; + int loadedCert; +}; + +/* Server cert_cb: record the peer CA list seen via the native getters, + * then load the server cert. */ +static int native_ca_cert_cb(WOLFSSL* ssl, void* arg) +{ + struct cert_cb_arg* out = (struct cert_cb_arg*)arg; + int sz; + + out->peerCount = wolfSSL_GetPeerCertificateAuthorityCount(ssl); + if (out->peerCount > 0) { + sz = wolfSSL_GetPeerCertificateAuthority(ssl, 0, out->firstDn, + (unsigned int)sizeof(out->firstDn)); + if (sz > 0) + out->firstDnSz = sz; + } + + if (wolfSSL_use_certificate_file(ssl, svrCertFile, SSL_FILETYPE_PEM) + != WOLFSSL_SUCCESS) + return 0; + if (wolfSSL_use_PrivateKey_file(ssl, svrKeyFile, SSL_FILETYPE_PEM) + != WOLFSSL_SUCCESS) + return 0; + out->loadedCert = 1; + return 1; +} +#endif + +int test_wolfSSL_CertificateAuthority_cert_cb(void) +{ + EXPECT_DECLS; +#if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CA_NAMES) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + defined(WOLFSSL_TLS13) && defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_CERT_SETUP_CB) && !defined(NO_FILESYSTEM) && \ + defined(USE_CERT_BUFFERS_2048) && \ + (!defined(IGNORE_NAME_CONSTRAINTS) || defined(WOLFSSL_CERT_EXT)) + /* Announce the actual subject DN of the server cert as an acceptable CA, + * then verify the server's cert_cb sees it via the native getters. */ + struct test_params { + method_provider client_meth; + method_provider server_meth; + } params[] = { + {wolfTLSv1_3_client_method, wolfTLSv1_3_server_method}, +#ifdef WOLFSSL_DTLS13 + {wolfDTLSv1_3_client_method, wolfDTLSv1_3_server_method}, +#endif + }; + size_t i; + DecodedCert decoded; + const byte* subject = NULL; + int subjectSz = 0; + + wc_InitDecodedCert(&decoded, server_cert_der_2048, + (word32)sizeof_server_cert_der_2048, NULL); + ExpectIntEQ(wc_ParseCert(&decoded, CERT_TYPE, NO_VERIFY, NULL), 0); + ExpectIntEQ(wc_GetDecodedCertSubjectRaw(&decoded, &subject, &subjectSz), 0); + ExpectIntGT(subjectSz, 0); + + for (i = 0; i < sizeof(params) / sizeof(*params) && !EXPECT_FAIL(); i++) { + struct test_memio_ctx test_ctx; + WOLFSSL_CTX* ctx_cli = NULL; + WOLFSSL_CTX* ctx_srv = NULL; + WOLFSSL* ssl_cli = NULL; + WOLFSSL* ssl_srv = NULL; + struct cert_cb_arg cb_arg; + + XMEMSET(&cb_arg, 0, sizeof(cb_arg)); + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + + ExpectIntEQ(0, test_memio_setup(&test_ctx, &ctx_cli, &ctx_srv, + &ssl_cli, &ssl_srv, params[i].client_meth, + params[i].server_meth)); + + wolfSSL_CTX_set_cert_cb(ctx_srv, native_ca_cert_cb, &cb_arg); + + ExpectIntEQ(wolfSSL_UseCertificateAuthority(ssl_cli, subject, + (unsigned int)subjectSz), 0); + + ExpectIntEQ(0, test_memio_do_handshake(ssl_cli, ssl_srv, 10, NULL)); + + ExpectIntEQ(cb_arg.loadedCert, 1); + ExpectIntEQ(cb_arg.peerCount, 1); + ExpectIntEQ(cb_arg.firstDnSz, subjectSz); + ExpectBufEQ(cb_arg.firstDn, subject, subjectSz); + + wolfSSL_free(ssl_cli); + wolfSSL_CTX_free(ctx_cli); + wolfSSL_free(ssl_srv); + wolfSSL_CTX_free(ctx_srv); + } + + wc_FreeDecodedCert(&decoded); +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_tls_ext.h b/tests/api/test_tls_ext.h index 40041c05333..cf6727b66a2 100644 --- a/tests/api/test_tls_ext.h +++ b/tests/api/test_tls_ext.h @@ -36,6 +36,13 @@ int test_tls13_ticket_age_out_of_window(void); int test_wolfSSL_DisableExtendedMasterSecret(void); int test_certificate_authorities_certificate_request(void); int test_certificate_authorities_client_hello(void); +int test_wolfSSL_UseCertificateAuthority_args(void); +int test_wolfSSL_UseCertificateAuthority_size_limits(void); +int test_wolfSSL_UseCertificateAuthority_counts(void); +int test_wolfSSL_GetPeerCertificateAuthority_empty(void); +int test_wolfSSL_CertificateAuthority_handshake(void); +int test_wolfSSL_CertificateAuthority_ctx_handshake(void); +int test_wolfSSL_CertificateAuthority_cert_cb(void); int test_TLSX_TCA_Find(void); int test_TLSX_SNI_GetSize_overflow(void); int test_TLSX_ECH_msg_type_validation(void); diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index 8826a18432a..1a305c6484b 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -23306,6 +23306,28 @@ int wc_GetDecodedCertSubject(const struct DecodedCert* cert, char* buf, return 0; } +/* Return a pointer to the decoded certificate's subject Name contents + * (the inside of the SEQUENCE, not the SEQUENCE header) and its length. + * The pointer aliases storage owned by cert and is only valid while + * cert is alive. */ +int wc_GetDecodedCertSubjectRaw(const struct DecodedCert* cert, + const byte** subjectRaw, int* subjectRawSz) +{ + if (cert == NULL || subjectRaw == NULL || subjectRawSz == NULL) + return BAD_FUNC_ARG; + +#if !defined(IGNORE_NAME_CONSTRAINTS) || defined(WOLFSSL_CERT_EXT) + if (cert->subjectRaw == NULL || cert->subjectRawLen <= 0) + return ASN_PARSE_E; + *subjectRaw = cert->subjectRaw; + *subjectRawSz = cert->subjectRawLen; + return 0; +#else + (void)cert; (void)subjectRaw; (void)subjectRawSz; + return NOT_COMPILED_IN; +#endif +} + int wc_GetDecodedCertIssuer(const struct DecodedCert* cert, char* buf, word32* bufSz) { diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 573bab58c90..ecee1909e66 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -1003,25 +1003,32 @@ #undef WSSL_HARDEN_TLS -/* CA Names feature */ -#if !defined(WOLFSSL_NO_CA_NAMES) && defined(OPENSSL_EXTRA) - #define SSL_CLIENT_CA_NAMES(ssl) ((ssl)->client_ca_names != NULL ? \ - (ssl)->client_ca_names : \ - (ssl)->ctx->client_ca_names) - #define SSL_CA_NAMES(ssl) ((ssl)->ca_names != NULL ? \ - (ssl)->ca_names : \ - (ssl)->ctx->ca_names) - /* On the server, client_ca_names has priority over ca_names if both are - * set. This mimics OpenSSL's API: - * https://docs.openssl.org/3.6/man3/SSL_CTX_set0_CA_list/ */ - #define SSL_PRIORITY_CA_NAMES(ssl) \ - (((ssl)->options.side == WOLFSSL_SERVER_END && \ - SSL_CLIENT_CA_NAMES(ssl) != NULL) ? \ - SSL_CLIENT_CA_NAMES(ssl) : \ - SSL_CA_NAMES(ssl)) -#else - #undef WOLFSSL_NO_CA_NAMES - #define WOLFSSL_NO_CA_NAMES +/* CA Names feature (TLS 1.3 certificate_authorities extension, RFC 8446). + * Enabled by default; opt out with WOLFSSL_NO_CA_NAMES. The OpenSSL + * stack-of-X509_NAME API is wired in when OPENSSL_EXTRA is also defined. */ +#ifndef WOLFSSL_NO_CA_NAMES + #ifdef OPENSSL_EXTRA + #define SSL_CLIENT_CA_NAMES(ssl) ((ssl)->client_ca_names != NULL ? \ + (ssl)->client_ca_names : \ + (ssl)->ctx->client_ca_names) + #define SSL_CA_NAMES(ssl) ((ssl)->ca_names != NULL ? \ + (ssl)->ca_names : \ + (ssl)->ctx->ca_names) + /* On the server, client_ca_names has priority over ca_names if both + * are set. This mimics OpenSSL's API: + * https://docs.openssl.org/3.6/man3/SSL_CTX_set0_CA_list/ */ + #define SSL_PRIORITY_CA_NAMES(ssl) \ + (((ssl)->options.side == WOLFSSL_SERVER_END && \ + SSL_CLIENT_CA_NAMES(ssl) != NULL) ? \ + SSL_CLIENT_CA_NAMES(ssl) : \ + SSL_CA_NAMES(ssl)) + #endif + #ifdef WOLFSSL_TLS13 + /* wolfSSL native CA list: SSL override, else CTX. */ + #define WS_CA_NAMES(ssl) ((ssl)->ws_ca_names != NULL ? \ + (ssl)->ws_ca_names : \ + ((ssl)->ctx != NULL ? (ssl)->ctx->ws_ca_names : NULL)) + #endif #endif @@ -3624,6 +3631,28 @@ WOLFSSL_LOCAL void TLSX_SignatureAlgorithms_FreeAll(SignatureAlgorithms* sa, void* heap); #endif +/** Certificate Authorities - RFC 8446 section 4.2.4. + * wolfSSL native list node holding the inner content of one DER-encoded Name + * (no SEQUENCE header; that is added on the wire). */ +#if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CA_NAMES) && \ + defined(WOLFSSL_TLS13) +typedef struct CertificateAuthority { + struct CertificateAuthority* next; + word16 dnSz; + /* Ignore "nonstandard extension used : zero-sized array in struct/union" + * MSVC warning */ + #ifdef _MSC_VER + #pragma warning(disable: 4200) + #endif + byte dn[]; +} CertificateAuthority; + +WOLFSSL_LOCAL int TLSX_CertificateAuthorities_Add(CertificateAuthority** head, + const byte* dn, word16 dnSz, void* heap); +WOLFSSL_LOCAL void TLSX_CertificateAuthorities_FreeAll( + CertificateAuthority* head, void* heap); +#endif + /** Supported Elliptic Curves - RFC 4492 (session 4) */ #ifdef HAVE_SUPPORTED_CURVES @@ -4120,10 +4149,15 @@ struct WOLFSSL_CTX { DerBuffer* certChain; int certChainCnt; /* chain after self, in DER, with leading size for each cert */ - #ifndef WOLFSSL_NO_CA_NAMES + #if !defined(WOLFSSL_NO_CA_NAMES) && defined(OPENSSL_EXTRA) WOLF_STACK_OF(WOLFSSL_X509_NAME)* client_ca_names; WOLF_STACK_OF(WOLFSSL_X509_NAME)* ca_names; #endif + #if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CA_NAMES) && \ + defined(WOLFSSL_TLS13) + /* wolfSSL native CA DN list sent in certificate_authorities. */ + CertificateAuthority* ws_ca_names; + #endif #ifdef OPENSSL_EXTRA WOLF_STACK_OF(WOLFSSL_X509)* x509Chain; #endif @@ -6863,7 +6897,7 @@ struct WOLFSSL { byte clientFinished_len; byte serverFinished_len; #endif -#ifndef WOLFSSL_NO_CA_NAMES +#if !defined(WOLFSSL_NO_CA_NAMES) && defined(OPENSSL_EXTRA) WOLF_STACK_OF(WOLFSSL_X509_NAME)* client_ca_names; /* Used in *_set/get_client_CA_list (server only) */ WOLF_STACK_OF(WOLFSSL_X509_NAME)* ca_names; /* Used in *_set0/get0_CA_list */ @@ -6871,6 +6905,13 @@ struct WOLFSSL { and (client only) wolfSSL_get_client_CA_list */ #endif +#if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CA_NAMES) && \ + defined(WOLFSSL_TLS13) + /* wolfSSL native CA DN list sent in certificate_authorities. */ + CertificateAuthority* ws_ca_names; + /* wolfSSL native CA DN list received from the peer. */ + CertificateAuthority* ws_peer_ca_names; +#endif #if defined(WOLFSSL_IOTSAFE) && defined(HAVE_PK_CALLBACKS) IOTSAFE iotsafe; #endif diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index c168ec24d9c..a93e9d7d692 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -4734,6 +4734,45 @@ WOLFSSL_API unsigned short wolfSSL_SNI_GetRequest(WOLFSSL *ssl, #endif /* HAVE_SNI */ +/* Certificate Authorities - RFC 8446 (TLS 1.3 extension type 47). + * + * Native API, independent of the OpenSSL compatibility layer. CA DNs passed + * to the Use functions are the inner content of a DER-encoded Name, i.e. the + * bytes after the SEQUENCE tag and length; the library prepends the SEQUENCE + * header on the wire and strips it on parse. The bytes are copied; the caller + * retains ownership of the input buffer. When both the native API and the + * compat layer (set0_CA_list et al.) provide CAs, the two lists are + * concatenated on the wire. */ +#if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CA_NAMES) && \ + defined(WOLFSSL_TLS13) + +WOLFSSL_API int wolfSSL_UseCertificateAuthority(WOLFSSL* ssl, + const unsigned char* dn, unsigned int dnSz); +WOLFSSL_API int wolfSSL_CTX_UseCertificateAuthority(WOLFSSL_CTX* ctx, + const unsigned char* dn, unsigned int dnSz); + +WOLFSSL_API void wolfSSL_ClearCertificateAuthorities(WOLFSSL* ssl); +WOLFSSL_API void wolfSSL_CTX_ClearCertificateAuthorities(WOLFSSL_CTX* ctx); + +/* Number of CA DNs received from the peer. */ +WOLFSSL_API int wolfSSL_GetPeerCertificateAuthorityCount(const WOLFSSL* ssl); + +/* Copy out the idx-th CA DN received from the peer (0-based). Iteration + * order is unspecified; visit every entry by pairing this with + * wolfSSL_GetPeerCertificateAuthorityCount. + * + * If outDn is NULL, returns the size in bytes of the DN (allowing the caller + * to size an allocation) or a negative error code. + * + * If outDn is non-NULL, copies up to outDnSz bytes into outDn. On success + * returns the number of bytes written. If outDnSz is smaller than the DN, + * returns BUFFER_E and does not modify outDn. Returns BAD_FUNC_ARG for + * invalid inputs or when idx is out of range. */ +WOLFSSL_API int wolfSSL_GetPeerCertificateAuthority(const WOLFSSL* ssl, int idx, + unsigned char* outDn, unsigned int outDnSz); + +#endif /* !NO_CERTS && !WOLFSSL_NO_CA_NAMES && WOLFSSL_TLS13 */ + /* Trusted CA Key Indication - RFC 6066 (Section 6) */ #ifdef HAVE_TRUSTED_CA diff --git a/wolfssl/wolfcrypt/asn_public.h b/wolfssl/wolfcrypt/asn_public.h index c975613f690..4e1fa932bd6 100644 --- a/wolfssl/wolfcrypt/asn_public.h +++ b/wolfssl/wolfcrypt/asn_public.h @@ -1008,6 +1008,9 @@ WOLFSSL_API int wc_GetSubjectPubKeyInfoDerFromCert(const byte* certDer, word32* pubKeyDerSz); WOLFSSL_API int wc_GetDecodedCertSubject(const struct DecodedCert* cert, char* buf, word32* bufSz); +WOLFSSL_API int wc_GetDecodedCertSubjectRaw(const struct DecodedCert* cert, + const byte** subjectRaw, + int* subjectRawSz); WOLFSSL_API int wc_GetDecodedCertIssuer(const struct DecodedCert* cert, char* buf, word32* bufSz); WOLFSSL_API int wc_GetDecodedCertSerial(const struct DecodedCert* cert, From 516fd372a4c940a189aca282fe54cffbaf401267 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Thu, 6 Aug 2026 13:49:33 +0000 Subject: [PATCH 2/2] Address review feedback on certificate_authorities API - Only advertise the extension when the emitter would produce at least one DN. HasAnyCANames() asks TLSX_CA_Names_Write() in size-only mode rather than restating its filtering, so the two cannot drift; a compat stack holding only NULL/empty names no longer emits an empty authorities vector, which RFC 8446 4.2.4 forbids. It reports true on error so an oversized list still fails at write time instead of silently dropping the extension. TLSX_CA_Names_Write() takes a const WOLFSSL* now that it is called from a predicate. - TLSX_CertificateAuthorities_Add() appends instead of prepending, so wire order matches call order as the docs state. The handshake test asserts that order rather than ignoring it. - Scope the MSVC C4200 suppression with warning(push)/(pop) so it does not leak into the rest of the translation unit. - Document MEMORY_ERROR, not MEMORY_E, as the allocation failure return; that is what the implementation returns. --- doc/dox_comments/header_files/ssl.h | 4 ++-- src/tls.c | 32 +++++++++++++++++------------ tests/api/test_tls_ext.c | 25 +++++++--------------- wolfssl/internal.h | 4 ++++ 4 files changed, 32 insertions(+), 33 deletions(-) diff --git a/doc/dox_comments/header_files/ssl.h b/doc/dox_comments/header_files/ssl.h index db957d11db9..2a31c5a33b8 100644 --- a/doc/dox_comments/header_files/ssl.h +++ b/doc/dox_comments/header_files/ssl.h @@ -17147,7 +17147,7 @@ int wolfSSL_set_scr_check_enabled(WOLFSSL* ssl, byte enabled); \return 0 on success. \return BAD_FUNC_ARG if ssl or dn is NULL, dnSz is 0, or dnSz exceeds the maximum content size. - \return MEMORY_E if memory allocation fails. + \return MEMORY_ERROR if memory allocation fails. \param ssl pointer to a WOLFSSL object, created with wolfSSL_new(). \param dn pointer to the DER-encoded subject Name content. @@ -17196,7 +17196,7 @@ int wolfSSL_UseCertificateAuthority(WOLFSSL* ssl, \return 0 on success. \return BAD_FUNC_ARG if ctx or dn is NULL, dnSz is 0, or dnSz exceeds the maximum content size. - \return MEMORY_E if memory allocation fails. + \return MEMORY_ERROR if memory allocation fails. \param ctx pointer to a WOLFSSL_CTX object, created with wolfSSL_CTX_new(). diff --git a/src/tls.c b/src/tls.c index 5f77a0d2885..d3c613a801a 100644 --- a/src/tls.c +++ b/src/tls.c @@ -7721,7 +7721,10 @@ int TLSX_CertificateAuthorities_Add(CertificateAuthority** head, return MEMORY_ERROR; XMEMCPY(node->dn, dn, dnSz); node->dnSz = dnSz; - node->next = *head; + node->next = NULL; + /* Append so wire order matches the order of the Add calls. */ + while (*head != NULL) + head = &(*head)->next; *head = node; return 0; } @@ -7734,17 +7737,6 @@ void TLSX_CertificateAuthorities_FreeAll(CertificateAuthority* head, void* heap) head = next; } } -/* True if any CA name (compat or wolfSSL native) is configured. */ -static int HasAnyCANames(const WOLFSSL* ssl) -{ -#ifdef OPENSSL_EXTRA - if (SSL_PRIORITY_CA_NAMES(ssl) != NULL) - return 1; -#endif - if (WS_CA_NAMES(ssl) != NULL) - return 1; - return 0; -} /* Certificate_authorities extension emitter. Walks the optional OPENSSL_EXTRA * compat stack and the native list and either accumulates the payload size @@ -7760,7 +7752,7 @@ static int HasAnyCANames(const WOLFSSL* ssl) * Each DN entry is at most 2^16-1 bytes and the whole authorities vector is * also at most 2^16-1 bytes. Returns 0 on success, BUFFER_ERROR if any entry * or the combined total would exceed either cap. */ -static int TLSX_CA_Names_Write(WOLFSSL* ssl, byte* output, word16* pSz) +static int TLSX_CA_Names_Write(const WOLFSSL* ssl, byte* output, word16* pSz) { CertificateAuthority* cur; word32 total = OPAQUE16_LEN; /* outer 16-bit length */ @@ -7828,6 +7820,20 @@ static int TLSX_CA_Names_Write(WOLFSSL* ssl, byte* output, word16* pSz) return 0; } +/* True if the emitter would produce at least one DN. Asks the emitter itself + * rather than restating its filtering, so the two cannot drift and an + * all-empty compat stack never yields an empty authorities vector. On error + * report true, so an oversized list fails at write time instead of silently + * dropping the extension. */ +static int HasAnyCANames(const WOLFSSL* ssl) +{ + word16 sz = 0; + + if (TLSX_CA_Names_Write(ssl, NULL, &sz) != 0) + return 1; + return sz > OPAQUE16_LEN; +} + static int TLSX_CA_Names_Parse(WOLFSSL *ssl, const byte* input, word16 length, byte isRequest) { diff --git a/tests/api/test_tls_ext.c b/tests/api/test_tls_ext.c index be44508c9ba..f8a71c89a11 100644 --- a/tests/api/test_tls_ext.c +++ b/tests/api/test_tls_ext.c @@ -2428,28 +2428,17 @@ int test_wolfSSL_CertificateAuthority_handshake(void) ExpectIntEQ(peerCount, count); if (count > 0 && peerCount == count) { - /* Verify every sent DN shows up exactly once on the server. - * Iteration order is unspecified, so match by tag byte. */ - int seen[32]; - XMEMSET(seen, 0, sizeof(seen)); + /* Each Add appends, and the parser preserves wire order, so the + * server sees the DNs in the order the client added them. */ for (i = 0; i < count; i++) { byte expected[sizeof(kMinDnTemplate)]; - int idx; - int matched = 0; + byte buf[sizeof(kMinDnTemplate)]; make_min_dn(expected, (byte)('A' + i)); - for (idx = 0; idx < peerCount; idx++) { - byte buf[sizeof(kMinDnTemplate)]; - int sz = wolfSSL_GetPeerCertificateAuthority(ssl_srv, idx, - buf, sizeof(buf)); - if (sz == (int)sizeof(expected) && !seen[idx] && - XMEMCMP(buf, expected, sz) == 0) { - seen[idx] = 1; - matched = 1; - break; - } - } - ExpectIntEQ(matched, 1); + ExpectIntEQ(wolfSSL_GetPeerCertificateAuthority(ssl_srv, i, + buf, (unsigned int)sizeof(buf)), + (int)sizeof(expected)); + ExpectBufEQ(buf, expected, (int)sizeof(expected)); } /* Sizing query on index 0: NULL buf returns the DN length. */ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index ecee1909e66..b0bc943a89e 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -3642,9 +3642,13 @@ typedef struct CertificateAuthority { /* Ignore "nonstandard extension used : zero-sized array in struct/union" * MSVC warning */ #ifdef _MSC_VER + #pragma warning(push) #pragma warning(disable: 4200) #endif byte dn[]; + #ifdef _MSC_VER + #pragma warning(pop) + #endif } CertificateAuthority; WOLFSSL_LOCAL int TLSX_CertificateAuthorities_Add(CertificateAuthority** head,