From 4b2d7e837e6abc9a4134a6aeaddce93b984bf99e Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Thu, 6 Aug 2026 11:19:50 +0000 Subject: [PATCH 1/5] 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 e3b523af2b8..d3ebbdf59b8 100644 --- a/doc/dox_comments/header_files/ssl.h +++ b/doc/dox_comments/header_files/ssl.h @@ -17370,3 +17370,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 5c03924b7ef..f065faa39ff 100644 --- a/src/internal.c +++ b/src/internal.c @@ -3111,12 +3111,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); @@ -9979,7 +9984,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); @@ -9987,6 +9992,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); @@ -27988,7 +28000,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]; @@ -28010,7 +28022,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) { @@ -28076,7 +28088,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 9c036fac882..b46e3ec7b7a 100644 --- a/src/ssl_api_cert.c +++ b/src/ssl_api_cert.c @@ -1140,7 +1140,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 2be0da03757..1dfb6dba9e1 100644 --- a/src/ssl_api_ext.c +++ b/src/ssl_api_ext.c @@ -180,6 +180,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 7171fcfeb26..1e098bc46c3 100644 --- a/src/tls.c +++ b/src/tls.c @@ -7694,61 +7694,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, @@ -7758,10 +7829,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; @@ -7771,75 +7848,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 @@ -15623,16 +15715,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 @@ -15687,6 +15774,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) { @@ -15928,7 +16019,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 @@ -16522,8 +16616,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) { @@ -17567,8 +17660,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)); } @@ -17593,7 +17685,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)); } @@ -17807,7 +17899,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)); } @@ -17838,7 +17930,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 dcffdf9edde..b1d7f09752e 100644 --- a/tests/api.c +++ b/tests/api.c @@ -40295,6 +40295,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 c639a62484e..5280765f08d 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -23485,6 +23485,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 2f96892e06e..195521aca9c 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -1017,25 +1017,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 @@ -3640,6 +3647,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 @@ -4139,10 +4168,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 @@ -6898,7 +6932,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 */ @@ -6906,6 +6940,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 924c395fd39..5e0a2e1c40a 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -4756,6 +4756,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 3a554f1f3c76bb06fd9208605073103c49da597e Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Thu, 6 Aug 2026 13:49:33 +0000 Subject: [PATCH 2/5] 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 d3ebbdf59b8..32bf089d93f 100644 --- a/doc/dox_comments/header_files/ssl.h +++ b/doc/dox_comments/header_files/ssl.h @@ -17389,7 +17389,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. @@ -17438,7 +17438,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 1e098bc46c3..8c177949ec3 100644 --- a/src/tls.c +++ b/src/tls.c @@ -7715,7 +7715,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; } @@ -7728,17 +7731,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 @@ -7754,7 +7746,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 */ @@ -7822,6 +7814,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 195521aca9c..da30b5b5966 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -3658,9 +3658,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, From 926a1dcb08e495abf64a74dd809f87b6dd2b2557 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Thu, 13 Aug 2026 14:24:26 +0000 Subject: [PATCH 3/5] Fix CI: gate the compat CA-list tests on OPENSSL_EXTRA Dropping the automatic WOLFSSL_NO_CA_NAMES define for builds without OPENSSL_EXTRA left test_wolfSSL_CA_list_add/get compiled in there, where the OpenSSL CA-list API they call is not declared. Smoke, SBOM and bomsh all failed on the resulting implicit declarations. WOLFSSL_NO_CA_NAMES is now only ever tested, never defined in tree, so register it in .wolfssl_known_macro_extras for check-source-text. --- .wolfssl_known_macro_extras | 1 + tests/api/test_ssl_cert.c | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/.wolfssl_known_macro_extras b/.wolfssl_known_macro_extras index cf01cc8f9ce..99a5461ebf2 100644 --- a/.wolfssl_known_macro_extras +++ b/.wolfssl_known_macro_extras @@ -954,6 +954,7 @@ WOLFSSL_NONBLOCK_OCSP WOLFSSL_NOSHA3_384 WOLFSSL_NOT_WINDOWS_API WOLFSSL_NO_BIO_ADDR_IN +WOLFSSL_NO_CA_NAMES WOLFSSL_NO_CHACHA20_POLY1305_FUSED WOLFSSL_NO_CHACHA20_POLY1305_FUSED_IFMA WOLFSSL_NO_CHACHA20_POLY1305_SHORT diff --git a/tests/api/test_ssl_cert.c b/tests/api/test_ssl_cert.c index 22cdc548032..659feac4057 100644 --- a/tests/api/test_ssl_cert.c +++ b/tests/api/test_ssl_cert.c @@ -819,7 +819,9 @@ int test_wolfSSL_get0_verified_chain(void) int test_wolfSSL_CA_list_add(void) { EXPECT_DECLS; +/* The CA-list setters/getters exercised here are the OpenSSL compat layer. */ #if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CA_NAMES) && \ + defined(OPENSSL_EXTRA) && \ !defined(NO_FILESYSTEM) && !defined(NO_RSA) && !defined(NO_TLS) && \ !defined(WOLFSSL_NO_TLS12) && !defined(NO_WOLFSSL_SERVER) && \ defined(WOLFSSL_PEM_TO_DER) @@ -890,7 +892,9 @@ int test_wolfSSL_CA_list_add(void) int test_wolfSSL_CA_list_get(void) { EXPECT_DECLS; +/* The CA-list setters/getters exercised here are the OpenSSL compat layer. */ #if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CA_NAMES) && \ + defined(OPENSSL_EXTRA) && \ !defined(NO_FILESYSTEM) && !defined(NO_RSA) && !defined(NO_TLS) && \ !defined(WOLFSSL_NO_TLS12) && !defined(NO_WOLFSSL_SERVER) && \ !defined(NO_WOLFSSL_CLIENT) && \ From 3a4e22bc82a1628092fd8514def94aaf9cb303e2 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 21 Aug 2026 02:38:19 +0000 Subject: [PATCH 4/5] Address review: correct certificate_authorities lifetime docs --- doc/dox_comments/header_files/asn_public.h | 5 +++-- wolfcrypt/src/asn.c | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/doc/dox_comments/header_files/asn_public.h b/doc/dox_comments/header_files/asn_public.h index bc56e185908..a6d3055b441 100644 --- a/doc/dox_comments/header_files/asn_public.h +++ b/doc/dox_comments/header_files/asn_public.h @@ -4413,8 +4413,9 @@ int wc_Asn1_SetOidToNameCb(Asn1* asn1, Asn1OidToNameCb nameCb); 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. + pointer aliases the DER buffer supplied to wc_InitDecodedCert(), which the + DecodedCert does not own, and must not be freed by the caller. The data + remains valid only while that buffer is alive and unmodified. This function is intended for use with wolfSSL_UseCertificateAuthority(), which expects the subject content without the outer SEQUENCE header. diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index 5280765f08d..2c841643b8b 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -23487,8 +23487,9 @@ int wc_GetDecodedCertSubject(const struct DecodedCert* cert, char* buf, /* 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. */ + * The pointer aliases the DER buffer passed to wc_InitDecodedCert(), which + * cert does not own, so it is only valid while that buffer is alive and + * unmodified. */ int wc_GetDecodedCertSubjectRaw(const struct DecodedCert* cert, const byte** subjectRaw, int* subjectRawSz) { From 9c767558a8ff370a3e8124d283c229510142a685 Mon Sep 17 00:00:00 2001 From: Juliusz Sosinowicz Date: Fri, 21 Aug 2026 02:38:19 +0000 Subject: [PATCH 5/5] Address review: reject an empty authorities vector and reset the peer list RFC 8446 4.2.4 declares authorities as DistinguishedName<3..2^16-1>, so a present extension carrying an empty vector is a framing error. wolfSSL_clear() left ws_peer_ca_names in place, so the getters reported the previous peer's authorities when the next one sent no extension. Parsing appended each entry by walking from the head, which is quadratic in the number of entries a peer can send. --- src/ssl.c | 9 +++++++++ src/tls.c | 42 +++++++++++++++++++++++++++++----------- tests/api.c | 1 + tests/api/test_tls_ext.c | 38 ++++++++++++++++++++++++++++++++++++ tests/api/test_tls_ext.h | 1 + wolfssl/internal.h | 3 +++ 6 files changed, 83 insertions(+), 11 deletions(-) diff --git a/src/ssl.c b/src/ssl.c index 9957a6d2f37..be40688f944 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -5731,6 +5731,15 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out, ssl->options.rpkState.received_ServerCertTypeCnt = 0; #endif + #if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CA_NAMES) && \ + defined(WOLFSSL_TLS13) + /* The peer list belongs to the connection that just ended. Left in + * place, the getters report the previous peer's authorities when the + * next one sends no certificate_authorities extension. */ + TLSX_CertificateAuthorities_FreeAll(ssl->ws_peer_ca_names, ssl->heap); + ssl->ws_peer_ca_names = NULL; + #endif + #if defined(HAVE_TLS_EXTENSIONS) && !defined(NO_TLS) TLSX_FreeAll(ssl->extensions, ssl->heap); ssl->extensions = NULL; diff --git a/src/tls.c b/src/tls.c index 8c177949ec3..70056f142dd 100644 --- a/src/tls.c +++ b/src/tls.c @@ -7699,9 +7699,12 @@ int TLSX_Cookie_Use(const WOLFSSL* ssl, const byte* data, word16 len, byte* mac, /* Certificate Authorities */ /******************************************************************************/ -/* Push a copy of dn/dnSz onto the list head. */ -int TLSX_CertificateAuthorities_Add(CertificateAuthority** head, - const byte* dn, word16 dnSz, void* heap) +/* Append a copy of dn/dnSz to the list so wire order matches call order. + * tail, when not NULL, caches the append point across calls: parsing a + * maximum sized extension adds thousands of entries and walking from the + * head each time would be quadratic. */ +int TLSX_CertificateAuthorities_Add_ex(CertificateAuthority** head, + CertificateAuthority** tail, const byte* dn, word16 dnSz, void* heap) { CertificateAuthority* node; size_t sz; @@ -7716,13 +7719,28 @@ int TLSX_CertificateAuthorities_Add(CertificateAuthority** head, XMEMCPY(node->dn, dn, dnSz); node->dnSz = dnSz; node->next = NULL; - /* Append so wire order matches the order of the Add calls. */ - while (*head != NULL) - head = &(*head)->next; - *head = node; + + if ((tail != NULL) && (*tail != NULL)) { + (*tail)->next = node; + } + else { + CertificateAuthority** end = head; + + while (*end != NULL) + end = &(*end)->next; + *end = node; + } + if (tail != NULL) + *tail = node; return 0; } +int TLSX_CertificateAuthorities_Add(CertificateAuthority** head, + const byte* dn, word16 dnSz, void* heap) +{ + return TLSX_CertificateAuthorities_Add_ex(head, NULL, dn, dnSz, heap); +} + void TLSX_CertificateAuthorities_FreeAll(CertificateAuthority* head, void* heap) { while (head != NULL) { @@ -7832,6 +7850,7 @@ static int TLSX_CA_Names_Parse(WOLFSSL *ssl, const byte* input, word16 length, byte isRequest) { word16 extLen; + CertificateAuthority* tail = NULL; (void)isRequest; @@ -7854,8 +7873,9 @@ 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) + /* authorities<3..2^16-1>: the extension is only parsed when present, so + * an empty vector is a framing error too. */ + if (length < 3) return BUFFER_ERROR; while (length) { @@ -7881,8 +7901,8 @@ static int TLSX_CA_Names_Parse(WOLFSSL *ssl, const byte* input, if ((word32)innerLen + seqIdx != entrySz) return BUFFER_ERROR; - ret = TLSX_CertificateAuthorities_Add(&ssl->ws_peer_ca_names, - input + OPAQUE16_LEN + seqIdx, (word16)innerLen, + ret = TLSX_CertificateAuthorities_Add_ex(&ssl->ws_peer_ca_names, + &tail, input + OPAQUE16_LEN + seqIdx, (word16)innerLen, ssl->heap); if (ret != 0) return ret; diff --git a/tests/api.c b/tests/api.c index b1d7f09752e..bdd106fd006 100644 --- a/tests/api.c +++ b/tests/api.c @@ -40298,6 +40298,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_wolfSSL_UseCertificateAuthority_args), TEST_DECL(test_wolfSSL_UseCertificateAuthority_size_limits), TEST_DECL(test_wolfSSL_UseCertificateAuthority_counts), + TEST_DECL(test_TLSX_certificate_authorities_empty_vector), TEST_DECL(test_wolfSSL_GetPeerCertificateAuthority_empty), TEST_DECL(test_wolfSSL_CertificateAuthority_handshake), TEST_DECL(test_wolfSSL_CertificateAuthority_ctx_handshake), diff --git a/tests/api/test_tls_ext.c b/tests/api/test_tls_ext.c index f8a71c89a11..0d5f69c40d9 100644 --- a/tests/api/test_tls_ext.c +++ b/tests/api/test_tls_ext.c @@ -2350,6 +2350,44 @@ int test_wolfSSL_UseCertificateAuthority_counts(void) return EXPECT_RESULT(); } +/* RFC 8446 4.2.4: authorities is a DistinguishedName<3..2^16-1>, so a + * certificate_authorities extension carrying an empty vector is a framing + * error rather than a request with no authorities. */ +int test_TLSX_certificate_authorities_empty_vector(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; + Suites* suites = NULL; + /* certificate_authorities with a zero-length authorities vector. */ + static const byte emptyVector[] = { + 0x00, 0x2F, /* extension type = 47 */ + 0x00, 0x02, /* extension length = 2 */ + 0x00, 0x00 /* authorities length = 0 */ + }; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + /* The extension is only parsed on a TLS 1.3 connection. */ + if (ssl != NULL) { + ssl->version.major = SSLv3_MAJOR; + ssl->version.minor = TLSv1_3_MINOR; + suites = (Suites*)WOLFSSL_SUITES(ssl); + } + + ExpectIntEQ(TLSX_Parse(ssl, emptyVector, (word16)sizeof(emptyVector), + certificate_request, suites), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + ExpectIntEQ(wolfSSL_GetPeerCertificateAuthorityCount(ssl), 0); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + int test_wolfSSL_GetPeerCertificateAuthority_empty(void) { EXPECT_DECLS; diff --git a/tests/api/test_tls_ext.h b/tests/api/test_tls_ext.h index cf6727b66a2..3f42bae90ca 100644 --- a/tests/api/test_tls_ext.h +++ b/tests/api/test_tls_ext.h @@ -39,6 +39,7 @@ 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_TLSX_certificate_authorities_empty_vector(void); int test_wolfSSL_GetPeerCertificateAuthority_empty(void); int test_wolfSSL_CertificateAuthority_handshake(void); int test_wolfSSL_CertificateAuthority_ctx_handshake(void); diff --git a/wolfssl/internal.h b/wolfssl/internal.h index da30b5b5966..cf40869af2a 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -3669,6 +3669,9 @@ typedef struct CertificateAuthority { WOLFSSL_LOCAL int TLSX_CertificateAuthorities_Add(CertificateAuthority** head, const byte* dn, word16 dnSz, void* heap); +WOLFSSL_LOCAL int TLSX_CertificateAuthorities_Add_ex( + CertificateAuthority** head, CertificateAuthority** tail, + const byte* dn, word16 dnSz, void* heap); WOLFSSL_LOCAL void TLSX_CertificateAuthorities_FreeAll( CertificateAuthority* head, void* heap); #endif