From 0ee7e8882d1627b19e2a341a7942b36d3dc0f1ec Mon Sep 17 00:00:00 2001 From: Sean Parkinson Date: Wed, 2 Sep 2026 11:09:42 +1000 Subject: [PATCH] TLS Extensions: add more extensions and improve current Added support for record_size_limit in TLS 1.3 and TLS 1.2. Added compress_certificate support for TLS 1.3. Added signed_certificate_timestamp TLS 1.2 send and TLS 1.2 and 1.3 receive. Accepts server_name in CertificateRequest. Added API for setting signature algorithms for signature_algorithms_cert. Tests added and interop performed where possible. --- .github/configs/os-check-linux.json | 19 + .github/workflows/os-check.yml | 2 +- CMakeLists.txt | 47 + cmake/options.h.in | 6 + configure.ac | 95 +- doc/dox_comments/header_files/ssl.h | 312 +++- src/dtls13.c | 2 + src/internal.c | 191 ++- src/ssl.c | 70 +- src/ssl_api_ext.c | 396 +++++ src/ssl_load.c | 22 + src/ssl_sess.c | 4 +- src/tls.c | 870 +++++++++- src/tls13.c | 1214 +++++++++++++- src/x509.c | 4 +- tests/api.c | 6 +- tests/api/test_dtls13.c | 8 +- tests/api/test_tls.c | 143 ++ tests/api/test_tls.h | 4 +- tests/api/test_tls13.c | 2156 ++++++++++++++++++++++++- tests/api/test_tls13.h | 224 ++- tests/api/test_tls_bounds.c | 18 +- tests/api/test_tls_msgtype.c | 2 +- tests/api/test_tls_parse.c | 18 +- tests/unit-mcdc/test_tls13_whitebox.c | 34 +- wolfssl/internal.h | 196 ++- wolfssl/openssl/ssl.h | 17 + wolfssl/ssl.h | 53 +- 28 files changed, 5858 insertions(+), 275 deletions(-) diff --git a/.github/configs/os-check-linux.json b/.github/configs/os-check-linux.json index d3ec20c7331..5021863ddb6 100644 --- a/.github/configs/os-check-linux.json +++ b/.github/configs/os-check-linux.json @@ -1,4 +1,23 @@ [ +{"name": "record-size-limit", + "comment": "RFC 8449 on its own. --enable-all covers it alongside everything else; this is the minimal build, which is where the extension's own guards get exercised.", + "configure": ["--enable-recordsizelimit"]}, +{"name": "record-size-limit-tls12", + "comment": "RFC 8449 defines the extension for TLS 1.2 too, where the server answers in the ServerHello. Guards the parse dispatch staying outside the WOLFSSL_TLS13 block.", + "configure": ["--enable-recordsizelimit", "--disable-tls13"]}, +{"name": "signed-cert-timestamp", + "comment": "RFC 6962 on its own.", + "configure": ["--enable-sct"]}, +{"name": "signed-cert-timestamp-tls12", + "comment": "RFC 6962 is a TLS 1.2 extension that TLS 1.3 relocated, so it must build with TLS 1.3 off. Guards the dispatch case staying outside the WOLFSSL_TLS13 block, and TLSX_SetResponse() staying behind NO_WOLFSSL_SERVER.", + "configure": ["--enable-sct", "--disable-tls13"]}, +{"name": "cert-compression", + "comment": "RFC 8879 on its own, with the zlib it requires.", + "configure": ["--enable-certcomp", "--with-libz"]}, +{"name": "cert-compression-no-client-auth", + "comment": "Server-only build without client auth: DoTls13CompressedCertificate() calls a static function guarded on exactly this combination, so it is the config that catches the guard drifting.", + "configure": ["--enable-certcomp", "--with-libz", + "CPPFLAGS=-DNO_WOLFSSL_CLIENT -DWOLFSSL_NO_CLIENT_AUTH"]}, {"name": "user-settings-all-compat", "minutes": 9.5, "comment": "user_settings_all.h with the compatibility layer enabled by flipping its \"#if 0\" block, as a build-dir copy.", "user_settings": "examples/configs/user_settings_all.h", diff --git a/.github/workflows/os-check.yml b/.github/workflows/os-check.yml index 17255e7ab7d..22d10181a81 100644 --- a/.github/workflows/os-check.yml +++ b/.github/workflows/os-check.yml @@ -101,7 +101,7 @@ jobs: - name: Install dependencies uses: ./.github/actions/install-apt-deps with: - packages: autoconf automake libtool build-essential bubblewrap ccache gcc-multilib + packages: autoconf automake libtool build-essential bubblewrap ccache gcc-multilib zlib1g-dev ghcr-debs-tag: ubuntu-24.04-minimal # Ubuntu 24.04 can restrict unprivileged user namespaces via AppArmor, diff --git a/CMakeLists.txt b/CMakeLists.txt index 8263cd43ab9..5bb3dfc9a51 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4083,6 +4083,53 @@ if(WOLFSSL_LIBZ) list(APPEND WOLFSSL_INCLUDE_DIRS ${ZLIB_INCLUDE_DIRS}) endif() +# Signed certificate timestamp (RFC 6962). Carries a Certificate Transparency +# SCT list between peers; validating it is left to the application. +add_option("WOLFSSL_SCT" + "Enable RFC 6962 signed_certificate_timestamp (default: disabled)" + "no" "yes;no") +if(WOLFSSL_SCT) + list(APPEND WOLFSSL_DEFINITIONS "-DHAVE_TLS_EXTENSIONS") + list(APPEND WOLFSSL_DEFINITIONS "-DHAVE_SIGNED_CERT_TIMESTAMP") +endif() + +# Record size limit (RFC 8449). Applies to TLS 1.2 as well as TLS 1.3, where +# the server answers in the ServerHello rather than EncryptedExtensions, so +# unlike certificate compression below it imposes no TLS 1.3 requirement. +add_option("WOLFSSL_RECORDSIZELIMIT" + "Enable RFC 8449 record_size_limit, for TLS 1.2 and TLS 1.3 (default: disabled)" + "no" "yes;no") +if(WOLFSSL_RECORDSIZELIMIT) + list(APPEND WOLFSSL_DEFINITIONS "-DHAVE_TLS_EXTENSIONS") + list(APPEND WOLFSSL_DEFINITIONS "-DHAVE_RECORD_SIZE_LIMIT") +endif() + +# Certificate compression (RFC 8879); needs zlib and TLS 1.3 +add_option("WOLFSSL_CERTCOMP" + "Enable RFC 8879 TLS 1.3 certificate compression (default: disabled)" + "no" "yes;no") +if(WOLFSSL_CERTCOMP) + if(NOT WOLFSSL_LIBZ) + message(FATAL_ERROR + "WOLFSSL_CERTCOMP requires WOLFSSL_LIBZ.") + endif() + if(NOT WOLFSSL_TLS13) + message(FATAL_ERROR + "WOLFSSL_CERTCOMP requires WOLFSSL_TLS13.") + endif() + # WOLFSSL_CERTS is not an option in this file; what actually produces + # -DNO_CERTS is WOLFSSL_ASN=no or WOLFSSL_LEAN_PSK, both resolved well + # before this point. Testing those is what makes this the configure-time + # equivalent of configure.ac's ENABLED_CERTS check. + if(NOT WOLFSSL_ASN OR WOLFSSL_LEAN_PSK) + message(FATAL_ERROR + "WOLFSSL_CERTCOMP requires certificate support " + "(WOLFSSL_ASN=yes, and not WOLFSSL_LEAN_PSK).") + endif() + list(APPEND WOLFSSL_DEFINITIONS "-DHAVE_TLS_EXTENSIONS") + list(APPEND WOLFSSL_DEFINITIONS "-DHAVE_CERTIFICATE_COMPRESSION") +endif() + #################################################### # Maximum key size options (parity with configure.ac) diff --git a/cmake/options.h.in b/cmake/options.h.in index 0dba372e988..83320880291 100644 --- a/cmake/options.h.in +++ b/cmake/options.h.in @@ -87,6 +87,8 @@ extern "C" { #cmakedefine HAVE_ALPN #undef HAVE_ARIA #cmakedefine HAVE_ARIA +#undef HAVE_CERTIFICATE_COMPRESSION +#cmakedefine HAVE_CERTIFICATE_COMPRESSION #undef HAVE_CERTIFICATE_STATUS_REQUEST #cmakedefine HAVE_CERTIFICATE_STATUS_REQUEST #undef HAVE_CERTIFICATE_STATUS_REQUEST_V2 @@ -159,8 +161,12 @@ extern "C" { #cmakedefine HAVE_PTHREAD 1 #undef HAVE_REPRODUCIBLE_BUILD #cmakedefine HAVE_REPRODUCIBLE_BUILD +#undef HAVE_RECORD_SIZE_LIMIT +#cmakedefine HAVE_RECORD_SIZE_LIMIT #undef HAVE_SESSION_TICKET #cmakedefine HAVE_SESSION_TICKET +#undef HAVE_SIGNED_CERT_TIMESTAMP +#cmakedefine HAVE_SIGNED_CERT_TIMESTAMP #undef HAVE_SNI #cmakedefine HAVE_SNI #undef HAVE_SUPPORTED_CURVES diff --git a/configure.ac b/configure.ac index b2ff85b3c7d..0f84138f4e1 100644 --- a/configure.ac +++ b/configure.ac @@ -1427,6 +1427,21 @@ then test "$enable_earlydata" = "" && enable_earlydata=yes test "$enable_rpk" = "" && enable_rpk=yes + test "$enable_sct" = "" && enable_sct=yes + test "$enable_recordsizelimit" = "" && enable_recordsizelimit=yes + # Certificate compression needs zlib, and --enable-all deliberately does + # NOT pull libz in. Doing so gave every --enable-all build a link-time + # dependency on libz.so and made wolfio.h include zlib.h: that broke the + # OpenWrt image (no zlib on the target) and the Linux kernel module (no + # userspace zlib.h), among others. So it joins only when libz was asked + # for - and even then as an implicit enable, which steps aside with a + # warning if its other requirements are missing rather than stopping + # configure. + if test "$enable_certcomp" = "" && test "$with_libz" = "yes"; then + enable_certcomp=yes + certcomp_implicit=yes + fi + if test "$KERNEL_MODE_DEFAULTS" != "yes" then # Disable QUIC with JNI since incompatible with WOLFSSL_TLS13_MIDDLEBOX_COMPAT @@ -10787,8 +10802,8 @@ AC_ARG_WITH([libz], AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], [[ deflateInit(0, 8); ]])],[ libz_linked=yes ],[ libz_linked=no ]) if test "x$libz_linked" = "xno" ; then - AC_MSG_ERROR([libz isn't found. - If it's already installed, specify its path using --with-libz=/dir/]) + AC_MSG_ERROR([libz isn't found. If it's already + installed, specify its path using --with-libz=/dir/]) fi AC_MSG_RESULT([yes]) else @@ -10798,6 +10813,67 @@ AC_ARG_WITH([libz], ] ) +# Signed Certificate Timestamp (RFC 6962) +AC_ARG_ENABLE([sct], + [AS_HELP_STRING([--enable-sct],[Enable RFC 6962 signed_certificate_timestamp. Carries a Certificate Transparency SCT list between peers; validating it is left to the application (default: disabled)])], + [ ENABLED_SCT=$enableval ], + [ ENABLED_SCT=no ] + ) +if test "$ENABLED_SCT" = "yes" +then + AM_CFLAGS="$AM_CFLAGS -DHAVE_TLS_EXTENSIONS -DHAVE_SIGNED_CERT_TIMESTAMP" +fi + +# Record Size Limit (RFC 8449) +AC_ARG_ENABLE([recordsizelimit], + [AS_HELP_STRING([--enable-recordsizelimit],[Enable RFC 8449 record_size_limit, the byte-exact replacement for max_fragment_length. Applies to TLS 1.2 and TLS 1.3 (default: disabled)])], + [ ENABLED_RECORD_SIZE_LIMIT=$enableval ], + [ ENABLED_RECORD_SIZE_LIMIT=no ] + ) +dnl RFC 8449 defines the extension for TLS 1.2 as well as TLS 1.3, where the +dnl server answers in the ServerHello rather than EncryptedExtensions, so no +dnl TLS 1.3 dependency is imposed here. +if test "$ENABLED_RECORD_SIZE_LIMIT" = "yes" +then + AM_CFLAGS="$AM_CFLAGS -DHAVE_TLS_EXTENSIONS -DHAVE_RECORD_SIZE_LIMIT" +fi + +# Certificate Compression (RFC 8879) +AC_ARG_ENABLE([certcomp], + [AS_HELP_STRING([--enable-certcomp],[Enable RFC 8879 TLS 1.3 certificate compression. Accepts a CompressedCertificate from the peer, and sends one when the certificate has been compressed with wolfSSL_CTX_compress_certs(). Needs --with-libz (default: disabled)])], + [ ENABLED_CERTCOMP=$enableval ], + [ ENABLED_CERTCOMP=no ] + ) +if test "$ENABLED_CERTCOMP" = "yes" +then + certcomp_missing="" + if test "x$ENABLED_TLS13" = "xno" + then + certcomp_missing="TLS 1.3" + fi + if test "x$ENABLED_LIBZ" = "xno" + then + certcomp_missing="libz" + fi + if test "x$certcomp_missing" != "x" + then + if test "x$certcomp_implicit" = "xyes" + then + dnl Switched on by --enable-all rather than asked for, so its + dnl requirements are not the user's to satisfy. + AC_MSG_WARN([certificate compression needs $certcomp_missing; + turning it off]) + ENABLED_CERTCOMP=no + else + AC_MSG_ERROR([Certificate compression requires $certcomp_missing.]) + fi + fi +fi +if test "$ENABLED_CERTCOMP" = "yes" +then + AM_CFLAGS="$AM_CFLAGS -DHAVE_TLS_EXTENSIONS -DHAVE_CERTIFICATE_COMPRESSION" +fi + # PKCS#11 AC_ARG_ENABLE([pkcs11], @@ -12639,6 +12715,18 @@ AS_IF([test "x$ENABLED_MAXSTRENGTH" = "xyes" && \ test "x$ENABLED_LEANPSK" = "xyes"], [AC_MSG_ERROR([Cannot use Max Strength and Lean PSK at the same time.])]) +dnl Certificate compression carries a Certificate message, so it needs +dnl certificates. Checked here, not beside the option: ENABLED_CERTS is still +dnl being assigned well past that point. +AS_IF([test "x$ENABLED_CERTCOMP" = "xyes" && test "x$ENABLED_CERTS" = "xno"], + [AS_IF([test "x$certcomp_implicit" = "xyes"], + [AC_MSG_WARN([certificate compression needs certificates; + turning it off]) + ENABLED_CERTCOMP=no + AM_CFLAGS=`echo "$AM_CFLAGS" | \ + sed 's/ -DHAVE_CERTIFICATE_COMPRESSION//'`], + [AC_MSG_ERROR([Certificate compression requires certificates.])])]) + AS_IF([test "x$ENABLED_CRYPTONLY" = "xno" && \ test "x$ENABLED_PSK" = "xno" && \ test "x$ENABLED_ASN" = "xno"], @@ -14129,6 +14217,9 @@ echo " * Whitewood netRandom: $ENABLED_WNR" echo " * Server Name Indication: $ENABLED_SNI" echo " * ALPN: $ENABLED_ALPN" echo " * Maximum Fragment Length: $ENABLED_MAX_FRAGMENT" +echo " * Record Size Limit: $ENABLED_RECORD_SIZE_LIMIT" +echo " * Certificate Compression: $ENABLED_CERTCOMP" +echo " * Signed Cert Timestamps: $ENABLED_SCT" echo " * Trusted CA Indication: $ENABLED_TRUSTED_CA" echo " * Truncated HMAC: $ENABLED_TRUNCATED_HMAC" echo " * Supported Elliptic Curves: $ENABLED_SUPPORTED_CURVES" diff --git a/doc/dox_comments/header_files/ssl.h b/doc/dox_comments/header_files/ssl.h index 03680e665b8..54538391a0d 100644 --- a/doc/dox_comments/header_files/ssl.h +++ b/doc/dox_comments/header_files/ssl.h @@ -9566,12 +9566,13 @@ int wolfSSL_GetOutputSize(WOLFSSL* ssl, int inSz); /*! \brief Returns the maximum record layer size for plaintext data. This - will correspond to either the maximum SSL/TLS record size as specified - by the protocol standard, the maximum TLS fragment size as set by the - TLS Max Fragment Length extension. This function is helpful when the - application has called wolfSSL_GetOutputSize() and received a INPUT_SIZE_E - error. This function must be called after the SSL/TLS handshake has been - completed. + will correspond to the smallest of the maximum SSL/TLS record size as + specified by the protocol standard, the maximum TLS fragment size as set + by the TLS Max Fragment Length extension, and the limit the peer + advertised with the RFC 8449 record_size_limit extension. This function is + helpful when the application has called wolfSSL_GetOutputSize() and + received a INPUT_SIZE_E error. This function must be called after the + SSL/TLS handshake has been completed. \return size Upon success, the maximum output size will be returned \return BAD_FUNC_ARG will be returned upon invalid function argument, @@ -9585,6 +9586,8 @@ int wolfSSL_GetOutputSize(WOLFSSL* ssl, int inSz); \endcode \sa wolfSSL_GetOutputSize + \sa wolfSSL_UseRecordSizeLimit + \sa wolfSSL_UseMaxFragment */ int wolfSSL_GetMaxOutputSize(WOLFSSL* ssl); @@ -12494,9 +12497,306 @@ int wolfSSL_ALPN_GetPeerProtocol(WOLFSSL* ssl, char **list, \sa wolfSSL_new \sa wolfSSL_CTX_UseMaxFragment + \sa wolfSSL_UseRecordSizeLimit */ int wolfSSL_UseMaxFragment(WOLFSSL* ssl, unsigned char mfl); +/*! + \brief Sets the largest record payload this end will accept, advertised as + the RFC 8449 record_size_limit extension. A client sends it in the + ClientHello; a server that received one answers in EncryptedExtensions + under TLS 1.3 or in the ServerHello under TLS 1.2. The extension only + takes effect when both ends send it, so the peer's records are capped only + once it has answered. It supersedes max_fragment_length, which offers four + fixed sizes rather than an exact byte count; where a peer sends both, RFC + 8449 Sect. 5 says record_size_limit governs. + + The count is payload. RFC 8449's field on the wire covers the whole TLS + 1.3 TLSInnerPlaintext, so a request for n goes out as n+1 under TLS 1.3 + and as n under TLS 1.2; the conversion is internal. + + A limit is advertised by default. Pass WOLFSSL_RECORD_SIZE_LIMIT_OFF to + stop advertising one. Must be called before the handshake starts, since + the value is both advertised to the peer and enforced on arrival. + + The default limit stands aside for an application that asked for + max_fragment_length with wolfSSL_UseMaxFragment(), so a client that set + one keeps offering it alone. Calling this function explicitly overrides + that: the application has then asked for both, and RFC 8449 Sect. 5 says + record_size_limit governs. + + \return WOLFSSL_SUCCESS upon success. + \return BAD_FUNC_ARG when ssl is NULL, when limit is out of range, or when + the handshake has already begun. + + \param ssl pointer to a SSL object, created with wolfSSL_new(). + \param limit largest record payload accepted, from + WOLFSSL_RECORD_SIZE_LIMIT_MIN (64) to WOLFSSL_RECORD_SIZE_LIMIT_MAX + (16384), or WOLFSSL_RECORD_SIZE_LIMIT_OFF (0) to advertise nothing. + Defaults to WOLFSSL_RECORD_SIZE_LIMIT_DEFAULT. + + _Example_ + \code + int ret = 0; + WOLFSSL* ssl = wolfSSL_new(ctx); + if (ssl == NULL) { + // ssl creation failed + } + ret = wolfSSL_UseRecordSizeLimit(ssl, 1024); + if (ret != WOLFSSL_SUCCESS) { + // limit rejected + } + \endcode + + \sa wolfSSL_CTX_UseRecordSizeLimit + \sa wolfSSL_UseMaxFragment + \sa wolfSSL_GetMaxOutputSize +*/ +int wolfSSL_UseRecordSizeLimit(WOLFSSL* ssl, unsigned short limit); + +/*! + \brief Sets the record_size_limit inherited by every WOLFSSL object + created from this context afterwards. See wolfSSL_UseRecordSizeLimit() for + what the value means and how the extension is negotiated. + + \return WOLFSSL_SUCCESS upon success. + \return BAD_FUNC_ARG when ctx is NULL or limit is out of range. + + \param ctx pointer to a SSL context, created with wolfSSL_CTX_new(). + \param limit largest record payload accepted, from + WOLFSSL_RECORD_SIZE_LIMIT_MIN (64) to WOLFSSL_RECORD_SIZE_LIMIT_MAX + (16384), or WOLFSSL_RECORD_SIZE_LIMIT_OFF (0) to advertise nothing. + + _Example_ + \code + int ret = wolfSSL_CTX_UseRecordSizeLimit(ctx, 512); + if (ret != WOLFSSL_SUCCESS) { + // limit rejected + } + \endcode + + \sa wolfSSL_UseRecordSizeLimit +*/ +int wolfSSL_CTX_UseRecordSizeLimit(WOLFSSL_CTX* ctx, unsigned short limit); + +/*! + \brief Compresses the context's certificate chain once, so every handshake + that negotiates the matching RFC 8879 algorithm sends the cached + CompressedCertificate instead of compressing per connection. Call it after + the certificate and any chain certificates are loaded. Loading or + replacing them afterwards discards the cache, and it is rebuilt only by + calling this again. + + The context must not be modified while handshakes using it are in flight. + + Answers the way OpenSSL's SSL_CTX_compress_certs() does, non-zero for + success and zero for failure, so the usual if (!...) test works. The + reason for a failure is logged rather than returned. + + \return WOLFSSL_SUCCESS upon success, including when the compressed form + is no smaller than the original and the cache is therefore left empty - + RFC 8879 leaves compressing to the sender, so such a chain is simply sent + as a plain Certificate. + \return WOLFSSL_FAILURE when ctx is NULL, when alg is not a supported + algorithm, when no certificate is loaded, when the chain does not fit the + message's wire fields, when memory cannot be allocated, or when the + compressor itself fails. + + \param ctx pointer to a SSL context, created with wolfSSL_CTX_new(). + \param alg algorithm to compress with. Only WOLFSSL_CERT_COMP_ZLIB is + implemented; WOLFSSL_CERT_COMP_BROTLI and WOLFSSL_CERT_COMP_ZSTD are + defined but not supported. + + _Example_ + \code + wolfSSL_CTX_use_certificate_file(ctx, cert, WOLFSSL_FILETYPE_PEM); + if (wolfSSL_CTX_compress_certs(ctx, WOLFSSL_CERT_COMP_ZLIB) + != WOLFSSL_SUCCESS) { + // compression failed + } + \endcode + + \sa wolfSSL_get_certificate_compression_used +*/ +int wolfSSL_CTX_compress_certs(WOLFSSL_CTX* ctx, int alg); + +/*! + \brief Reports the RFC 8879 algorithm the peer's Certificate message + arrived compressed with. Meaningful once the peer's certificate has been + received. + + \return one of the WOLFSSL_CERT_COMP_* values when the peer's chain was + received compressed. + \return 0 when it was not compressed, or ssl is NULL. + + \param ssl pointer to a SSL object, created with wolfSSL_new(). + + _Example_ + \code + if (wolfSSL_get_certificate_compression_used(ssl) != 0) { + // peer's chain arrived compressed + } + \endcode + + \sa wolfSSL_CTX_compress_certs +*/ +int wolfSSL_get_certificate_compression_used(WOLFSSL* ssl); + +/*! + \brief Sets the SignedCertificateTimestampList a server presents to + clients that ask for one (RFC 6962). The bytes are copied and sent + verbatim: wolfSSL does not validate them, since checking an SCT needs a + log list and a trust policy the library does not carry. Inherited by + WOLFSSL objects created from this context afterwards. + + Answers the way BoringSSL's SSL_CTX_set_signed_cert_timestamp_list() + does, non-zero for success and zero for failure. + + \return WOLFSSL_SUCCESS upon success. + \return WOLFSSL_FAILURE when ctx or list is NULL, when sz is 0, or when + the copy cannot be allocated. + + \param ctx pointer to a SSL context, created with wolfSSL_CTX_new(). + \param list SignedCertificateTimestampList bytes, including the outer + two-byte list length. + \param sz length of list in bytes. + + _Example_ + \code + if (wolfSSL_CTX_set_signed_cert_timestamp_list(ctx, sctList, sctListSz) + != WOLFSSL_SUCCESS) { + // list rejected + } + \endcode + + \sa wolfSSL_set_signed_cert_timestamp_list + \sa wolfSSL_get0_signed_cert_timestamp_list + \sa wolfSSL_signed_cert_timestamp_requested +*/ +int wolfSSL_CTX_set_signed_cert_timestamp_list(WOLFSSL_CTX* ctx, + const unsigned char* list, unsigned short sz); + +/*! + \brief Sets the SignedCertificateTimestampList for one SSL object, + overriding any list inherited from its context. See + wolfSSL_CTX_set_signed_cert_timestamp_list(). + + \return WOLFSSL_SUCCESS upon success. + \return WOLFSSL_FAILURE when ssl or list is NULL, when sz is 0, or when + the copy cannot be allocated. + + \param ssl pointer to a SSL object, created with wolfSSL_new(). + \param list SignedCertificateTimestampList bytes. + \param sz length of list in bytes. + + _Example_ + \code + wolfSSL_set_signed_cert_timestamp_list(ssl, sctList, sctListSz); + \endcode + + \sa wolfSSL_CTX_set_signed_cert_timestamp_list + \sa wolfSSL_get0_signed_cert_timestamp_list +*/ +int wolfSSL_set_signed_cert_timestamp_list(WOLFSSL* ssl, + const unsigned char* list, unsigned short sz); + +/*! + \brief Returns the SignedCertificateTimestampList the peer sent, without + copying it. The pointer belongs to the SSL object: it is released when + that object is freed, and also by wolfSSL_clear(), which drops the peer's + state so the object can be reused. Copy the bytes before either if they + are needed afterwards. wolfSSL performs no validation of the list. + + \return length of the list in bytes, 0 when the peer sent none. + + \param ssl pointer to a SSL object, created with wolfSSL_new(). + \param list receives a pointer to the list bytes. May be NULL to query + only the length. + + _Example_ + \code + const unsigned char* sct = NULL; + unsigned short sz = wolfSSL_get0_signed_cert_timestamp_list(ssl, &sct); + if (sz > 0) { + // validate sct against a log list + } + \endcode + + \sa wolfSSL_CTX_set_signed_cert_timestamp_list + \sa wolfSSL_signed_cert_timestamp_requested +*/ +unsigned short wolfSSL_get0_signed_cert_timestamp_list(WOLFSSL* ssl, + const unsigned char** list); + +/*! + \brief Reports whether the peer asked this end for signed certificate + timestamps. A server can use it to tell a client that wants SCTs from one + that did not ask. + + \return 1 when the peer sent the signed_certificate_timestamp extension. + \return 0 when it did not, or ssl is NULL. + + \param ssl pointer to a SSL object, created with wolfSSL_new(). + + _Example_ + \code + if (wolfSSL_signed_cert_timestamp_requested(ssl)) { + // the client asked for SCTs + } + \endcode + + \sa wolfSSL_CTX_set_signed_cert_timestamp_list +*/ +int wolfSSL_signed_cert_timestamp_requested(WOLFSSL* ssl); + +/*! + \brief Sets the signature schemes this context will accept in a peer's + certificate chain, sent as the signature_algorithms_cert extension of RFC + 8446 Sect. 4.2.3. A client sends it in the ClientHello and a server in the + CertificateRequest. Without it, the schemes in signature_algorithms apply + to certificates as well; sending it lets the two differ, which is how an + endpoint accepts a legacy digest on a chain without accepting it for + handshake signatures. + + \return WOLFSSL_SUCCESS upon success. + \return WOLFSSL_FAILURE when ctx or list is NULL, or the list cannot be + parsed. + + \param ctx pointer to a SSL context, created with wolfSSL_CTX_new(). + \param list colon separated scheme names, for example + "RSA-PSS+SHA256:ECDSA+SHA256". + + _Example_ + \code + wolfSSL_CTX_set1_sigalgs_cert_list(ctx, "RSA-PSS+SHA256:ECDSA+SHA256"); + \endcode + + \sa wolfSSL_set1_sigalgs_cert_list +*/ +int wolfSSL_CTX_set1_sigalgs_cert_list(WOLFSSL_CTX* ctx, const char* list); + +/*! + \brief Sets the signature schemes accepted in a peer's certificate chain + for one SSL object, overriding the context's list. See + wolfSSL_CTX_set1_sigalgs_cert_list(). + + \return WOLFSSL_SUCCESS upon success. + \return WOLFSSL_FAILURE when ssl or list is NULL, or the list cannot be + parsed. + + \param ssl pointer to a SSL object, created with wolfSSL_new(). + \param list colon separated scheme names. + + _Example_ + \code + wolfSSL_set1_sigalgs_cert_list(ssl, "RSA-PSS+SHA256"); + \endcode + + \sa wolfSSL_CTX_set1_sigalgs_cert_list +*/ +int wolfSSL_set1_sigalgs_cert_list(WOLFSSL* ssl, const char* list); + + /*! \brief This function is called on the client side to enable the use of Maximum Fragment Length for SSL objects created from the SSL context diff --git a/src/dtls13.c b/src/dtls13.c index 32b4e00df0f..5def74d5644 100644 --- a/src/dtls13.c +++ b/src/dtls13.c @@ -221,6 +221,7 @@ static byte Dtls13TypeIsEncrypted(enum HandShakeType hs_type) case encrypted_extensions: case session_ticket: case end_of_early_data: + case compressed_certificate: case certificate: case server_key_exchange: case certificate_request: @@ -1793,6 +1794,7 @@ int Dtls13CheckEpoch(WOLFSSL* ssl, enum HandShakeType type) } break; case certificate_request: + case compressed_certificate: case certificate: case certificate_verify: case finished: diff --git a/src/internal.c b/src/internal.c index 9d16fad25e7..d645b42f1f4 100644 --- a/src/internal.c +++ b/src/internal.c @@ -2691,6 +2691,13 @@ int InitSSL_Ctx(WOLFSSL_CTX* ctx, WOLFSSL_METHOD* method, void* heap) ctx->readAheadSz = WOLFSSL_READ_AHEAD_SZ; #endif +#ifdef HAVE_RECORD_SIZE_LIMIT + /* Advertise by default so the extension actually negotiates; an + * application that does not want to offer one sets 0. Inherited by every + * WOLFSSL made from this context. */ + ctx->recordSizeLimit = WOLFSSL_RECORD_SIZE_LIMIT_DEFAULT; +#endif + #ifdef WOLFSSL_DTLS if (method->version.major == DTLS_MAJOR) { ctx->minDowngrade = WOLFSSL_MIN_DTLS_DOWNGRADE; @@ -3043,6 +3050,33 @@ void FreeEchConfigs(WOLFSSL_EchConfig* configs, void* heap) * wolfSSL_CTX_load_static_memory after CTX creation, which means variables * allocated in InitSSL_Ctx were allocated from heap and should be free'd with * a NULL heap hint. */ +#ifdef HAVE_CERTIFICATE_COMPRESSION +/* Drop the compressed copy of the certificate message. + * + * The cache is built from the context's certificate and chain, so replacing + * either leaves it describing a certificate that is no longer configured. + * Called from every site that frees or replaces them. + * + * ctx SSL/TLS context object, may be NULL. + */ +void CertCompInvalidate(WOLFSSL_CTX* ctx) +{ + if (ctx == NULL) + return; + XFREE(ctx->certComp, ctx->heap, DYNAMIC_TYPE_TMP_BUFFER); + ctx->certComp = NULL; + ctx->certCompSz = 0; + ctx->certCompPlainSz = 0; + ctx->certCompAlgo = 0; + /* The recorded shape describes the cache, so it goes with it: leaving it + * behind would have UseCompressedCertificate()'s backstop compare a new + * certificate against the dimensions of one that is gone. */ + ctx->certCompCertSz = 0; + ctx->certCompChainSz = 0; + ctx->certCompChainCnt = 0; +} +#endif /* HAVE_CERTIFICATE_COMPRESSION */ + void SSL_CtxResourceFree(WOLFSSL_CTX* ctx) { #if defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) && \ @@ -3056,6 +3090,15 @@ void SSL_CtxResourceFree(WOLFSSL_CTX* ctx) } #endif +#ifdef HAVE_CERTIFICATE_COMPRESSION + CertCompInvalidate(ctx); +#endif +#ifdef HAVE_SIGNED_CERT_TIMESTAMP + XFREE(ctx->sctList, ctx->heap, DYNAMIC_TYPE_TLSX); + ctx->sctList = NULL; + ctx->sctListSz = 0; +#endif + #ifdef HAVE_EX_DATA_CLEANUP_HOOKS wolfSSL_CRYPTO_cleanup_ex_data(&ctx->ex_data); #endif @@ -8488,6 +8531,16 @@ static void InitSSL_Tls13Options(WOLFSSL* ssl, WOLFSSL_CTX* ctx) ssl->numGroups = ctx->numGroups; } + /* The SCT list is deliberately not copied from the context here: this + * function cannot report an allocation failure. The copy is taken in + * TLSX_SCTS_Parse() when the server decides to answer, which can. */ + + if (ctx->ourCertSigAlgoSz > 0) { + XMEMCPY(ssl->ourCertSigAlgo, ctx->ourCertSigAlgo, + ctx->ourCertSigAlgoSz); + ssl->ourCertSigAlgoSz = ctx->ourCertSigAlgoSz; + } + #ifdef WOLFSSL_TLS13_MIDDLEBOX_COMPAT ssl->options.tls13MiddleBoxCompat = 1; #endif @@ -8760,6 +8813,15 @@ int InitSSL(WOLFSSL* ssl, WOLFSSL_CTX* ctx, int writeDup) ssl->options.useClientOrder = ctx->useClientOrder; ssl->options.mutualAuth = ctx->mutualAuth; +#ifdef HAVE_RECORD_SIZE_LIMIT + /* Version independent on purpose: RFC 8449 covers TLS 1.2, where the + * server answers in the ServerHello, so this must not live in + * InitSSL_Tls13Options() - a TLS 1.2 only build would never inherit the + * context's limit and would silently never offer the extension. */ + ssl->recordSizeLimit = ctx->recordSizeLimit; + ssl->recordSizeLimitSet = ctx->recordSizeLimitSet; +#endif + #ifdef WOLFSSL_TLS13 InitSSL_Tls13Options(ssl, ctx); #endif /* WOLFSSL_TLS13 */ @@ -9787,6 +9849,14 @@ static void FreeSSL_StaticMemory(WOLFSSL* ssl) /* In case holding SSL object in array and don't want to free actual ssl */ void wolfSSL_ResourceFree(WOLFSSL* ssl) { +#ifdef HAVE_SIGNED_CERT_TIMESTAMP + XFREE(ssl->peerSctList, ssl->heap, DYNAMIC_TYPE_TLSX); + ssl->peerSctList = NULL; + ssl->peerSctListSz = 0; + XFREE(ssl->sctList, ssl->heap, DYNAMIC_TYPE_TLSX); + ssl->sctList = NULL; + ssl->sctListSz = 0; +#endif /* Note: any resources used during the handshake should be released in the * function FreeHandshakeResources(). Be careful with the special cases * like the RNG which may optionally be kept for the whole session. (For @@ -12819,6 +12889,9 @@ int MsgCheckEncryption(WOLFSSL* ssl, byte type, byte encrypted) case session_ticket: case end_of_early_data: case encrypted_extensions: +#ifdef HAVE_CERTIFICATE_COMPRESSION + case compressed_certificate: +#endif case certificate: case server_key_exchange: case certificate_request: @@ -12837,6 +12910,9 @@ int MsgCheckEncryption(WOLFSSL* ssl, byte type, byte encrypted) } break; case message_hash: +#ifndef HAVE_CERTIFICATE_COMPRESSION + case compressed_certificate: +#endif case no_shake: default: WOLFSSL_MSG("Unknown message type"); @@ -12893,6 +12969,7 @@ int MsgCheckEncryption(WOLFSSL* ssl, byte type, byte encrypted) case request_connection_id: case new_connection_id: case message_hash: + case compressed_certificate: case no_shake: default: WOLFSSL_MSG("Unknown message type"); @@ -12932,6 +13009,9 @@ static int MsgCheckBoundary(const WOLFSSL* ssl, byte type, break; case session_ticket: case encrypted_extensions: +#ifdef HAVE_CERTIFICATE_COMPRESSION + case compressed_certificate: +#endif case certificate: case server_key_exchange: case certificate_request: @@ -12945,6 +13025,9 @@ static int MsgCheckBoundary(const WOLFSSL* ssl, byte type, break; case server_hello_done: case message_hash: +#ifndef HAVE_CERTIFICATE_COMPRESSION + case compressed_certificate: +#endif case no_shake: default: WOLFSSL_MSG("Unknown message type"); @@ -12982,6 +13065,7 @@ static int MsgCheckBoundary(const WOLFSSL* ssl, byte type, case request_connection_id: case new_connection_id: case message_hash: + case compressed_certificate: case no_shake: default: WOLFSSL_MSG("Unknown message type"); @@ -13020,6 +13104,7 @@ static int MsgCheckBoundary(const WOLFSSL* ssl, byte type, case request_connection_id: case new_connection_id: case message_hash: + case compressed_certificate: case no_shake: default: WOLFSSL_MSG("Unknown message type"); @@ -13488,6 +13573,65 @@ static int GetRecordHeader(WOLFSSL* ssl, word32* inOutIdx, } #endif +#ifdef HAVE_RECORD_SIZE_LIMIT + /* RFC 8449 Sect. 4: "A TLS endpoint that receives a record larger than + * its advertised limit MUST generate a fatal record_overflow alert." + * LENGTH_ERROR is what the caller turns into that alert. The limit is on + * the plaintext, so the ciphertext is allowed its expansion on top. + * + * The limits only bind once the extension "is negotiated", so this waits + * for the peer's own limit to arrive, which is the point at which it has + * acknowledged ours. Enforcing on advertisement alone would drop every + * peer that ignores the extension, and ignoring it is what an + * implementation without RFC 8449 does. + * + * "Unprotected messages are not subject to this limit", which matters at + * TLS 1.2 where the whole certificate flight travels in the clear and + * routinely exceeds a small limit. */ + if ((ssl->recordSizeLimit != 0) && (ssl->peerRecordSizeLimit != 0) && + IsEncryptionOn(ssl, 0) +#ifdef WOLFSSL_EARLY_DATA + /* Not 0-RTT data. The client composes early data straight after + * its ClientHello, before it can have seen our limit - RFC 8449 + * does not carry the value in the ticket - yet a server has the + * client's limit and its early-data keys from that same + * ClientHello, so every condition above already holds. Holding + * those records to a limit the peer could not know would fail the + * handshake with record_overflow for an early write it had no way + * to bound. */ + && (ssl->earlyData != expecting_early_data) + && (ssl->earlyData != process_early_data) +#endif + ) { + /* Allow the ciphertext only the expansion the negotiated cipher + * actually adds. MAX_MSG_EXTRA is the worst case over every suite + * wolfSSL supports, and at a small limit that slack is wide enough to + * wave through a record several times the size advertised. TLS 1.3 is + * AEAD throughout, so the expansion is exactly the tag - the content + * type byte and any padding are inside the limit already. At TLS 1.2 + * an explicit IV, the MAC and block padding all vary by suite, and the + * worst case is the honest bound to use. */ + /* ssl->recordSizeLimit is payload, so rebuild what that permits on + * the wire: the same content type byte the advertisement carried, + * then the cipher's expansion. */ + word32 maxSz = (word32)ssl->recordSizeLimit; + + #ifdef WOLFSSL_TLS13 + if (IsAtLeastTLSv1_3(ssl->version)) + maxSz += 1 + ssl->specs.aead_mac_size; + else + #endif + maxSz += MAX_MSG_EXTRA; + + if ((word32)*size > maxSz) { + WOLFSSL_MSG_EX("Record length %d exceeds record size limit", + *size); + WOLFSSL_ERROR_VERBOSE(LENGTH_ERROR); + return LENGTH_ERROR; + } + } +#endif + if (*size == 0 && rh->type != application_data) { WOLFSSL_MSG("0 length, non-app data record."); WOLFSSL_ERROR_VERBOSE(LENGTH_ERROR); @@ -32423,14 +32567,16 @@ static byte GetSigAlgFromName(const char* name, int len) return alg; } -/* Set the hash/signature algorithms that are supported for certificate signing. +/* Set a signature algorithms list from a text list. * - * suites [in,out] Cipher suites and signature algorithms. - * list [in] String representing hash/signature algorithms to set. - * returns 0 on failure. - * 1 on success. + * hashSigAlgo Buffer of WOLFSSL_MAX_SIGALGO bytes to encode the list into. + * hashSigAlgoSz Encoded length in bytes. Zeroed first: setting is + * destructive on error. + * list Colon separated + algorithms. + * returns 1 on success, 0 on error. */ -int SetSuitesHashSigAlgo(Suites* suites, const char* list) +int SetHashSigAlgoList(byte* hashSigAlgo, word16* hashSigAlgoSz, + const char* list) { int ret = 1; word16 idx = 0; @@ -32439,7 +32585,7 @@ int SetSuitesHashSigAlgo(Suites* suites, const char* list) byte mac_alg = no_mac; /* Setting is destructive on error. */ - suites->hashSigAlgoSz = 0; + *hashSigAlgoSz = 0; do { if (*list == '+') { @@ -32479,7 +32625,7 @@ int SetSuitesHashSigAlgo(Suites* suites, const char* list) break; } } - AddSuiteHashSigAlgo(suites->hashSigAlgo, mac_alg, sig_alg, 0, &idx); + AddSuiteHashSigAlgo(hashSigAlgo, mac_alg, sig_alg, 0, &idx); sig_alg = 0; mac_alg = no_mac; s = list + 1; @@ -32493,12 +32639,24 @@ int SetSuitesHashSigAlgo(Suites* suites, const char* list) ret = 0; } else { - suites->hashSigAlgoSz = idx; + *hashSigAlgoSz = idx; } return ret; } +/* Set the signature algorithms list on a cipher suite holder. + * + * suites Cipher suites holder. + * list Colon separated + algorithms. + * returns 1 on success, 0 on error. + */ +int SetSuitesHashSigAlgo(Suites* suites, const char* list) +{ + return SetHashSigAlgoList(suites->hashSigAlgo, &suites->hashSigAlgoSz, + list); +} + #endif /* OPENSSL_EXTRA */ #if !defined(NO_TLS) && (!defined(NO_WOLFSSL_SERVER) || !defined(NO_CERTS)) @@ -45936,6 +46094,21 @@ int wolfSSL_GetMaxFragSize(WOLFSSL* ssl) } #endif /* HAVE_MAX_FRAGMENT */ +#ifdef HAVE_RECORD_SIZE_LIMIT + /* RFC 8449 Sect. 4: never generate a protected record whose plaintext is + * larger than the peer's limit. In TLS 1.3 that limit counts the content + * type byte of the TLSInnerPlaintext, so one byte fewer is available to + * the payload itself. */ + if (ssl->peerRecordSizeLimit != 0) { + /* Already payload: TLSX_RecordSizeLimit_Parse() took the TLS 1.3 + * content type byte off when it read the value. */ + int limit = (int)ssl->peerRecordSizeLimit; + + if (maxFragment > limit) + maxFragment = limit; + } +#endif /* HAVE_RECORD_SIZE_LIMIT */ + return maxFragment; } diff --git a/src/ssl.c b/src/ssl.c index 329d5e60c99..08bb08f4405 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -21,6 +21,10 @@ #include +#ifdef HAVE_CERTIFICATE_COMPRESSION + #include +#endif + #if defined(OPENSSL_EXTRA) && !defined(_WIN32) && !defined(_GNU_SOURCE) /* turn on GNU extensions for XISASCII */ #define _GNU_SOURCE 1 @@ -3957,10 +3961,12 @@ int wolfSSL_set_compression(WOLFSSL* ssl) { WOLFSSL_ENTER("wolfSSL_set_compression"); (void)ssl; -#ifdef HAVE_LIBZ +#if defined(HAVE_LIBZ) && !defined(WOLFSSL_NO_TLS_COMPRESSION) ssl->options.usingCompression = 1; return WOLFSSL_SUCCESS; #else + /* WOLFSSL_NO_TLS_COMPRESSION is set when record_size_limit is built: the + * limit bounds plaintext that compression may then expand past it. */ return NOT_COMPILED_IN; #endif } @@ -5733,6 +5739,40 @@ size_t wolfSSL_get_client_random(const WOLFSSL* ssl, unsigned char* out, ssl->options.rpkState.received_ServerCertTypeCnt = 0; #endif + /* Everything below records what the peer did, so it must not survive + * into the next connection on a recycled object. The matching local + * configuration -- ssl->recordSizeLimit, ssl->sctList -- is deliberately + * kept, since that is what the caller set. */ + #ifdef HAVE_CERTIFICATE_COMPRESSION + ssl->peerCertCompAlgo = 0; + ssl->certCompUsed = 0; + ssl->certCompAdvertised = 0; + /* sendingCompCert and fragOffset are one cursor between them: the + * flag says a compressed Certificate is in progress and the offset + * says how far. Clearing only the flag would restart a recycled + * object part way into a message. */ + ssl->sendingCompCert = 0; + ssl->fragOffset = 0; + #endif + #ifdef HAVE_RECORD_SIZE_LIMIT + ssl->peerRecordSizeLimit = 0; + #endif + #ifdef HAVE_SIGNED_CERT_TIMESTAMP + XFREE(ssl->peerSctList, ssl->heap, DYNAMIC_TYPE_TLSX); + ssl->peerSctList = NULL; + ssl->peerSctListSz = 0; + ssl->sctRequested = 0; + /* A snapshot taken from the context last handshake goes with it, so a + * recycled object sees a list the context has rotated since. One the + * application set on this object is configuration and stays. */ + if (ssl->sctListFromCtx) { + XFREE(ssl->sctList, ssl->heap, DYNAMIC_TYPE_TLSX); + ssl->sctList = NULL; + ssl->sctListSz = 0; + ssl->sctListFromCtx = 0; + } + #endif + #if defined(HAVE_TLS_EXTENSIONS) && !defined(NO_TLS) TLSX_FreeAll(ssl->extensions, ssl->heap); ssl->extensions = NULL; @@ -8086,7 +8126,7 @@ CRYPTO_EX_cb_ctx* crypto_ex_cb_ctx_session = NULL; static int crypto_ex_cb_new(CRYPTO_EX_cb_ctx** dst, long ctx_l, void* ctx_ptr, WOLFSSL_CRYPTO_EX_new* new_func, WOLFSSL_CRYPTO_EX_dup* dup_func, - WOLFSSL_CRYPTO_EX_free* free_func) + WOLFSSL_CRYPTO_EX_free* free_cb) { CRYPTO_EX_cb_ctx* new_ctx = (CRYPTO_EX_cb_ctx*)XMALLOC( sizeof(CRYPTO_EX_cb_ctx), NULL, DYNAMIC_TYPE_OPENSSL); @@ -8095,7 +8135,7 @@ static int crypto_ex_cb_new(CRYPTO_EX_cb_ctx** dst, long ctx_l, void* ctx_ptr, new_ctx->ctx_l = ctx_l; new_ctx->ctx_ptr = ctx_ptr; new_ctx->new_func = new_func; - new_ctx->free_func = free_func; + new_ctx->free_func = free_cb; new_ctx->dup_func = dup_func; new_ctx->next = NULL; /* Push to end of list */ @@ -8166,7 +8206,7 @@ void crypto_ex_cb_free_data(void *obj, CRYPTO_EX_cb_ctx* cb_ctx, */ int wolfssl_local_get_ex_new_index(int class_index, long ctx_l, void* ctx_ptr, WOLFSSL_CRYPTO_EX_new* new_func, WOLFSSL_CRYPTO_EX_dup* dup_func, - WOLFSSL_CRYPTO_EX_free* free_func) + WOLFSSL_CRYPTO_EX_free* free_cb) { /* index counter for each class index*/ static int ctx_idx = 0; @@ -8179,22 +8219,22 @@ int wolfssl_local_get_ex_new_index(int class_index, long ctx_l, void* ctx_ptr, switch(class_index) { case WOLF_CRYPTO_EX_INDEX_SSL: WOLFSSL_CRYPTO_EX_DATA_IGNORE_PARAMS(ctx_l, ctx_ptr, new_func, - dup_func, free_func); + dup_func, free_cb); idx = ssl_idx++; break; case WOLF_CRYPTO_EX_INDEX_SSL_CTX: WOLFSSL_CRYPTO_EX_DATA_IGNORE_PARAMS(ctx_l, ctx_ptr, new_func, - dup_func, free_func); + dup_func, free_cb); idx = ctx_idx++; break; case WOLF_CRYPTO_EX_INDEX_X509: WOLFSSL_CRYPTO_EX_DATA_IGNORE_PARAMS(ctx_l, ctx_ptr, new_func, - dup_func, free_func); + dup_func, free_cb); idx = x509_idx++; break; case WOLF_CRYPTO_EX_INDEX_SSL_SESSION: if (crypto_ex_cb_new(&crypto_ex_cb_ctx_session, ctx_l, ctx_ptr, - new_func, dup_func, free_func) != 0) + new_func, dup_func, free_cb) != 0) return WOLFSSL_FATAL_ERROR; idx = ssl_session_idx++; break; @@ -8224,13 +8264,13 @@ int wolfssl_local_get_ex_new_index(int class_index, long ctx_l, void* ctx_ptr, int wolfSSL_CTX_get_ex_new_index(long idx, void* arg, WOLFSSL_CRYPTO_EX_new* new_func, WOLFSSL_CRYPTO_EX_dup* dup_func, - WOLFSSL_CRYPTO_EX_free* free_func) + WOLFSSL_CRYPTO_EX_free* free_cb) { WOLFSSL_ENTER("wolfSSL_CTX_get_ex_new_index"); return wolfssl_local_get_ex_new_index(WOLF_CRYPTO_EX_INDEX_SSL_CTX, idx, - arg, new_func, dup_func, free_func); + arg, new_func, dup_func, free_cb); } /* Return the index that can be used for the WOLFSSL structure to store @@ -8513,6 +8553,10 @@ long wolfSSL_CTX_ctrl(WOLFSSL_CTX* ctx, int cmd, long opt, void* pt) } /* Clear certificate chain */ FreeDer(&ctx->certChain); + #ifdef HAVE_CERTIFICATE_COMPRESSION + /* The compressed copy described the old certificate. */ + CertCompInvalidate(ctx); + #endif if (sk) { for (i = 0; i < wolfSSL_sk_X509_num(sk); i++) { x509 = wolfSSL_sk_X509_value(sk, i); @@ -10226,18 +10270,18 @@ int wolfSSL_CRYPTO_set_ex_data_with_cleanup( * @param argl parameters to be saved * @param new_func a pointer to WOLFSSL_CRYPTO_EX_new * @param dup_func a pointer to WOLFSSL_CRYPTO_EX_dup - * @param free_func a pointer to WOLFSSL_CRYPTO_EX_free + * @param free_cb a pointer to WOLFSSL_CRYPTO_EX_free * @return index value grater or equal to zero on success, -1 on failure. */ int wolfSSL_CRYPTO_get_ex_new_index(int class_index, long argl, void *argp, WOLFSSL_CRYPTO_EX_new* new_func, WOLFSSL_CRYPTO_EX_dup* dup_func, - WOLFSSL_CRYPTO_EX_free* free_func) + WOLFSSL_CRYPTO_EX_free* free_cb) { WOLFSSL_ENTER("wolfSSL_CRYPTO_get_ex_new_index"); return wolfssl_local_get_ex_new_index(class_index, argl, argp, new_func, - dup_func, free_func); + dup_func, free_cb); } #endif /* HAVE_EX_DATA_CRYPTO */ diff --git a/src/ssl_api_ext.c b/src/ssl_api_ext.c index 2be0da03757..1eef8424b1f 100644 --- a/src/ssl_api_ext.c +++ b/src/ssl_api_ext.c @@ -635,8 +635,344 @@ int wolfSSL_set1_groups(WOLFSSL* ssl, int* groups, int count) return ret; } #endif /* OPENSSL_EXTRA */ + #endif /* HAVE_SUPPORTED_CURVES */ +/* These extensions have their own build options and depend on neither the + * OpenSSL compatibility layer nor supported-curves, so they sit at file + * scope: --enable-recordsizelimit, --enable-certcomp and --enable-sct + * would otherwise compile the protocol code while leaving no way to + * configure it. */ + +#ifdef HAVE_SIGNED_CERT_TIMESTAMP +/* Set the SignedCertificateTimestampList a server presents. + * + * The bytes are the extension_data of RFC 6962 Sect. 3.3, that is a + * SignedCertificateTimestampList: a two byte list length then each SCT as a + * two byte length and its body. Certificate Transparency logs and CAs emit + * this blob; wolfSSL carries it and does not interpret it. + * + * @param [in] ctx SSL/TLS context object. + * @param [in] list SignedCertificateTimestampList bytes, copied. + * @param [in] sz Its length. + * Non-zero for success and zero for failure, as the BoringSSL API of this + * name documents, so a ported `if (!...)` test still catches a failure. + * + * @return WOLFSSL_SUCCESS on success. + * @return WOLFSSL_FAILURE on a NULL or empty argument, or when the copy + * cannot be allocated. + */ +int wolfSSL_CTX_set_signed_cert_timestamp_list(WOLFSSL_CTX* ctx, + const unsigned char* list, + unsigned short sz) +{ + byte* copy; + + WOLFSSL_ENTER("wolfSSL_CTX_set_signed_cert_timestamp_list"); + + if ((ctx == NULL) || (list == NULL) || (sz == 0)) { + WOLFSSL_MSG("Bad function arguments"); + return WOLFSSL_FAILURE; + } + + copy = (byte*)XMALLOC(sz, ctx->heap, DYNAMIC_TYPE_TLSX); + if (copy == NULL) + return WOLFSSL_FAILURE; + XMEMCPY(copy, list, sz); + + XFREE(ctx->sctList, ctx->heap, DYNAMIC_TYPE_TLSX); + ctx->sctList = copy; + ctx->sctListSz = sz; + + return WOLFSSL_SUCCESS; +} + +/* Set the SignedCertificateTimestampList on the object. + * + * Overrides any list set on the context, and unlike the context setter takes + * effect on an object that already exists. See + * wolfSSL_CTX_set_signed_cert_timestamp_list(). + * + * @param [in] ssl SSL/TLS object. + * @param [in] list SignedCertificateTimestampList bytes, copied. + * @param [in] sz Its length. + * Non-zero for success and zero for failure, as the BoringSSL API of this + * name documents, so a ported `if (!...)` test still catches a failure. + * + * @return WOLFSSL_SUCCESS on success. + * @return WOLFSSL_FAILURE on a NULL or empty argument, or when the copy + * cannot be allocated. + */ +int wolfSSL_set_signed_cert_timestamp_list(WOLFSSL* ssl, + const unsigned char* list, + unsigned short sz) +{ + byte* copy; + + WOLFSSL_ENTER("wolfSSL_set_signed_cert_timestamp_list"); + + if ((ssl == NULL) || (list == NULL) || (sz == 0)) { + WOLFSSL_MSG("Bad function arguments"); + return WOLFSSL_FAILURE; + } + + copy = (byte*)XMALLOC(sz, ssl->heap, DYNAMIC_TYPE_TLSX); + if (copy == NULL) + return WOLFSSL_FAILURE; + XMEMCPY(copy, list, sz); + + /* Always this object's own copy: a list adopted from the context is + * copied by TLSX_SCTS_Parse() rather than aliased, so there is never a + * pointer here that belongs to someone else. */ + XFREE(ssl->sctList, ssl->heap, DYNAMIC_TYPE_TLSX); + ssl->sctList = copy; + ssl->sctListSz = sz; + /* The application's choice for this object, not a copy of the context's, + * so it outlives a wolfSSL_clear(). */ + ssl->sctListFromCtx = 0; + + return WOLFSSL_SUCCESS; +} + +/* Get the SignedCertificateTimestampList the peer presented. + * + * Points into storage owned by the SSL object. It is released when that + * object is freed and also by wolfSSL_clear(), which drops the peer state + * before the object is reused - so a caller that keeps the pointer across a + * reset is holding freed memory and must copy the bytes it needs. The list is + * structurally checked on arrival; verifying the timestamps needs a log list + * and policy the application supplies. + * + * @param [in] ssl SSL/TLS object. + * @param [out] list Set to the list, or NULL when the peer sent none. + * @return Length of the list in bytes, 0 when there is none. + */ +unsigned short wolfSSL_get0_signed_cert_timestamp_list(WOLFSSL* ssl, + const unsigned char** + list) +{ + WOLFSSL_ENTER("wolfSSL_get0_signed_cert_timestamp_list"); + + if (list != NULL) + *list = NULL; + if ((ssl == NULL) || (ssl->peerSctList == NULL)) + return 0; + if (list != NULL) + *list = ssl->peerSctList; + + return ssl->peerSctListSz; +} +#endif /* HAVE_SIGNED_CERT_TIMESTAMP */ +#ifdef HAVE_RECORD_SIZE_LIMIT +/* Set the largest protected record this end will accept. + * + * Advertised as the RFC 8449 record_size_limit extension: by a client in the + * ClientHello and echoed by a server in EncryptedExtensions. It replaces + * max_fragment_length, which offers only four fixed sizes, and unlike it the + * value is an exact byte count. Advertised by default; pass 0 to stop + * advertising it. + * + * The count is payload. RFC 8449's field on the wire instead covers the whole + * TLS 1.3 TLSInnerPlaintext, so a request for n is advertised as n+1 under + * TLS 1.3 and as n under TLS 1.2; that conversion is internal and a caller + * never sees it. + * + * @param [in] ssl SSL/TLS object. + * @param [in] limit Largest record payload accepted, 64 to 2^14, or 0 to + * not advertise a limit at all. + * @return WOLFSSL_SUCCESS on success. + * @return BAD_FUNC_ARG when ssl is NULL or limit is out of range. + */ +int wolfSSL_UseRecordSizeLimit(WOLFSSL* ssl, word16 limit) +{ + WOLFSSL_ENTER("wolfSSL_UseRecordSizeLimit"); + + /* 0 turns the extension off for this ssl, the only way back to silence + * now that WOLFSSL_RECORD_SIZE_LIMIT_DEFAULT is advertised by default. + * Any other value below the RFC 8449 minimum stays an error. */ + if ((ssl == NULL) || + ((limit != 0) && (limit < WOLFSSL_RECORD_SIZE_LIMIT_MIN)) || + (limit > WOLFSSL_RECORD_SIZE_LIMIT_MAX)) { + return BAD_FUNC_ARG; + } + /* The value is advertised to the peer - in the ClientHello by a client, + * in the ServerHello or EncryptedExtensions by a server - and enforced on + * arrival, so changing it once the handshake is under way would tighten + * what this end accepts without telling the peer: the peer would keep + * sending records sized to the original offer and they would start being + * rejected. */ + if (ssl->options.handShakeState != NULL_STATE) { + WOLFSSL_MSG("Record size limit must be set before the handshake"); + return BAD_FUNC_ARG; + } + ssl->recordSizeLimit = limit; + ssl->recordSizeLimitSet = 1; + + return WOLFSSL_SUCCESS; +} + +/* Set the largest protected record accepted, on the context. + * + * See wolfSSL_UseRecordSizeLimit(). Objects created afterwards inherit it. + * + * @param [in] ctx SSL/TLS context object. + * @param [in] limit Largest record payload accepted, 64 to 2^14, or 0 to + * not advertise a limit at all. + * @return WOLFSSL_SUCCESS on success. + * @return BAD_FUNC_ARG when ctx is NULL or limit is out of range. + */ +int wolfSSL_CTX_UseRecordSizeLimit(WOLFSSL_CTX* ctx, word16 limit) +{ + WOLFSSL_ENTER("wolfSSL_CTX_UseRecordSizeLimit"); + + /* 0 turns the extension off for this ctx, the only way back to silence + * now that WOLFSSL_RECORD_SIZE_LIMIT_DEFAULT is advertised by default. + * Any other value below the RFC 8449 minimum stays an error. */ + if ((ctx == NULL) || + ((limit != 0) && (limit < WOLFSSL_RECORD_SIZE_LIMIT_MIN)) || + (limit > WOLFSSL_RECORD_SIZE_LIMIT_MAX)) { + return BAD_FUNC_ARG; + } + ctx->recordSizeLimit = limit; + ctx->recordSizeLimitSet = 1; + + return WOLFSSL_SUCCESS; +} +#endif /* HAVE_RECORD_SIZE_LIMIT */ + +#ifdef HAVE_CERTIFICATE_COMPRESSION +/* Algorithm the peer's Certificate message arrived compressed with. + * + * @param [in] ssl SSL/TLS object. + * @return One of the WOLFSSL_CERT_COMP_* values when the peer's certificate + * chain was received compressed (RFC 8879). + * @return 0 when it was not compressed, or ssl is NULL. + */ +int wolfSSL_get_certificate_compression_used(WOLFSSL* ssl) +{ + WOLFSSL_ENTER("wolfSSL_get_certificate_compression_used"); + + if (ssl == NULL) + return 0; + + return ssl->certCompUsed; +} +#endif /* HAVE_CERTIFICATE_COMPRESSION */ + +#ifdef HAVE_SIGNED_CERT_TIMESTAMP +/* Whether the peer asked this end for signed certificate timestamps. + * + * A server can use this to tell an absent list from an unasked-for one. + * + * @param [in] ssl SSL/TLS object. + * @return 1 when the peer sent the signed_certificate_timestamp extension. + * @return 0 when it did not, or ssl is NULL. + */ +int wolfSSL_signed_cert_timestamp_requested(WOLFSSL* ssl) +{ + WOLFSSL_ENTER("wolfSSL_signed_cert_timestamp_requested"); + + if (ssl == NULL) + return 0; + + return ssl->sctRequested; +} +#endif /* HAVE_SIGNED_CERT_TIMESTAMP */ +#ifdef HAVE_CERTIFICATE_COMPRESSION +/* Compress the context's certificate chain ahead of the handshake. + * + * RFC 8879 leaves compressing to the sender's discretion, so this is opt in: + * without it wolfSSL still advertises what it can decompress and still accepts + * a CompressedCertificate, but sends its own certificate uncompressed. The + * work is done once here rather than per handshake, since the result is the + * same every time. + * + * The cached body carries an empty certificate_request_context and no + * per-certificate extensions, so a handshake needing either -- post-handshake + * authentication, OCSP stapling, raw public keys -- sends the plain + * Certificate message instead. + * + * @param [in] ctx SSL/TLS context object. + * @param [in] alg Compression algorithm, WOLFSSL_CERT_COMP_ZLIB. + * @return WOLFSSL_SUCCESS on success. + * @return WOLFSSL_FAILURE when ctx is NULL, alg is unsupported, no + * certificate is loaded, or the compressed form is not smaller. + */ +int wolfSSL_CTX_compress_certs(WOLFSSL_CTX* ctx, int alg) +{ + byte* body = NULL; + byte* comp = NULL; + word32 bodySz = 0; + word32 compBufSz; + int compSz; + int ret = WC_NO_ERR_TRACE(WOLFSSL_FAILURE); + + WOLFSSL_ENTER("wolfSSL_CTX_compress_certs"); + + /* This carries OpenSSL's SSL_CTX_compress_certs() name, so it answers the + * way that API does: non-zero for success, zero for failure. A negative + * error code would be truthy and slip straight past the `if (!ret)` a + * caller ported from OpenSSL writes. The specific cause is logged rather + * than returned. */ + if (ctx == NULL || !TLSX_CertificateCompression_Supported((word16)alg)) { + WOLFSSL_MSG("Bad function arguments"); + return WOLFSSL_FAILURE; + } + if (BuildTls13CertificateBody(ctx, &body, &bodySz) != 0) { + WOLFSSL_MSG("Could not build the Certificate body to compress"); + return WOLFSSL_FAILURE; + } + + /* wc_Compress() needs a destination; deflate can expand incompressible + * input, so allow for that rather than assume a saving. Computed once so + * the allocation and the length handed to wc_Compress() cannot drift, and + * checked for wrap since it is derived from a length. */ + compBufSz = bodySz + (bodySz / 2) + 64; + if (compBufSz <= bodySz) { + WOLFSSL_MSG("Certificate body too large to size a compression buffer"); + XFREE(body, ctx->heap, DYNAMIC_TYPE_TMP_BUFFER); + return WOLFSSL_FAILURE; + } + comp = (byte*)XMALLOC(compBufSz, ctx->heap, DYNAMIC_TYPE_TMP_BUFFER); + if (comp == NULL) { + XFREE(body, ctx->heap, DYNAMIC_TYPE_TMP_BUFFER); + return WOLFSSL_FAILURE; + } + + compSz = wc_Compress(comp, compBufSz, body, bodySz, 0); + /* Only keep it when it actually saves bytes on the wire. */ + if (compSz > 0 && (word32)compSz < bodySz) { + XFREE(ctx->certComp, ctx->heap, DYNAMIC_TYPE_TMP_BUFFER); + ctx->certComp = comp; + ctx->certCompSz = (word32)compSz; + ctx->certCompPlainSz = bodySz; + ctx->certCompAlgo = (byte)alg; + ctx->certCompCertSz = ctx->certificate->length; + ctx->certCompChainSz = (ctx->certChain != NULL) ? + ctx->certChain->length : 0; + ctx->certCompChainCnt = ctx->certChainCnt; + comp = NULL; + ret = WOLFSSL_SUCCESS; + } + else if (compSz > 0) { + /* Not a failure: RFC 8879 leaves compressing to the sender, so an + * incompressible chain simply goes out as a plain Certificate. The + * caller asked for a cache and got the right answer, which is that + * there is no point having one. */ + WOLFSSL_MSG("Compressed certificate is no smaller, not cached"); + ret = WOLFSSL_SUCCESS; + } + else { + WOLFSSL_MSG("Certificate compression failed"); + } + + XFREE(comp, ctx->heap, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(body, ctx->heap, DYNAMIC_TYPE_TMP_BUFFER); + + return ret; +} +#endif /* HAVE_CERTIFICATE_COMPRESSION */ + /* Application-Layer Protocol Negotiation */ #ifdef HAVE_ALPN @@ -1808,6 +2144,66 @@ int wolfSSL_set1_sigalgs_list(WOLFSSL* ssl, const char* list) return ret; } +#ifdef WOLFSSL_TLS13 +/* Set the certificate signature algorithms list on the context. + * + * Sent as the TLS 1.3 signature_algorithms_cert extension: by a client in the + * ClientHello and by a server in the CertificateRequest. Leave it unset when + * the same algorithms are acceptable for certificates and for handshake + * signatures -- signature_algorithms then covers both (RFC 8446 Sect 4.2.3). + * + * @param [in] ctx SSL/TLS context object. + * @param [in] list Colon-separated list of + algorithms. + * @return WOLFSSL_SUCCESS on success. + * @return WOLFSSL_FAILURE when ctx or list is NULL or on error. + */ +int wolfSSL_CTX_set1_sigalgs_cert_list(WOLFSSL_CTX* ctx, const char* list) +{ + int ret; + + WOLFSSL_MSG("wolfSSL_CTX_set1_sigalgs_cert_list"); + + if ((ctx == NULL) || (list == NULL)) { + WOLFSSL_MSG("Bad function arguments"); + ret = WOLFSSL_FAILURE; + } + else { + ret = SetHashSigAlgoList(ctx->ourCertSigAlgo, &ctx->ourCertSigAlgoSz, + list); + } + + return ret; +} + +/* Set the certificate signature algorithms list on the object. + * + * Overrides any list set on the context. See + * wolfSSL_CTX_set1_sigalgs_cert_list(). + * + * @param [in] ssl SSL/TLS object. + * @param [in] list Colon-separated list of + algorithms. + * @return WOLFSSL_SUCCESS on success. + * @return WOLFSSL_FAILURE when ssl or list is NULL or on error. + */ +int wolfSSL_set1_sigalgs_cert_list(WOLFSSL* ssl, const char* list) +{ + int ret; + + WOLFSSL_MSG("wolfSSL_set1_sigalgs_cert_list"); + + if ((ssl == NULL) || (list == NULL)) { + WOLFSSL_MSG("Bad function arguments"); + ret = WOLFSSL_FAILURE; + } + else { + ret = SetHashSigAlgoList(ssl->ourCertSigAlgo, &ssl->ourCertSigAlgoSz, + list); + } + + return ret; +} +#endif /* WOLFSSL_TLS13 */ + #ifdef HAVE_ECC #if defined(WOLFSSL_TLS13) && defined(HAVE_SUPPORTED_CURVES) diff --git a/src/ssl_load.c b/src/ssl_load.c index 4d406db0786..82303224712 100644 --- a/src/ssl_load.c +++ b/src/ssl_load.c @@ -257,6 +257,10 @@ static int ProcessUserChainRetain(WOLFSSL_CTX* ctx, WOLFSSL* ssl, else if (ctx != NULL) { /* Dispose of old chain and allocate and copy in new chain. */ FreeDer(&ctx->certChain); + #ifdef HAVE_CERTIFICATE_COMPRESSION + /* The compressed copy described the old certificate. */ + CertCompInvalidate(ctx); + #endif /* Allocate and copy the buffer into SSL context object. */ ret = AllocCopyDer(&ctx->certChain, chainBuffer, len, type, heap); /* Update count of certificates in chain. */ @@ -2487,6 +2491,10 @@ static int ProcessBufferCertHandleDer(WOLFSSL_CTX* ctx, WOLFSSL* ssl, else if (ctx != NULL) { /* Free previous certificate. */ FreeDer(&ctx->certificate); /* Make sure previous is free'd */ + #ifdef HAVE_CERTIFICATE_COMPRESSION + /* The compressed copy described the old certificate. */ + CertCompInvalidate(ctx); + #endif #ifdef KEEP_OUR_CERT /* Dispose of X509 version of certificate if we own it. */ if (ctx->ownOurCert) { @@ -5212,6 +5220,11 @@ static int wolfssl_ctx_add_to_chain(WOLFSSL_CTX* ctx, const byte* der, ctx->heap); if (res == 1) { ctx->certChainCnt++; +#ifdef HAVE_CERTIFICATE_COMPRESSION + /* The compressed Certificate cache was built from the chain that just + * changed, so it no longer describes what would be sent. */ + CertCompInvalidate(ctx); +#endif } return res; @@ -5302,6 +5315,10 @@ int wolfSSL_CTX_use_certificate(WOLFSSL_CTX *ctx, WOLFSSL_X509 *x) if (res == 1) { /* Replace certificate buffer with one holding the new certificate. */ FreeDer(&ctx->certificate); + #ifdef HAVE_CERTIFICATE_COMPRESSION + /* The compressed copy described the old certificate. */ + CertCompInvalidate(ctx); + #endif ret = AllocCopyDer(&ctx->certificate, x->derCert->buffer, x->derCert->length, CERT_TYPE, ctx->heap); if (ret != 0) { @@ -5399,6 +5416,11 @@ int wolfSSL_CTX_add1_chain_cert(WOLFSSL_CTX* ctx, WOLFSSL_X509* x509) x509->derCert->buffer, x509->derCert->length, ctx->heap); if (ret == 1) { ctx->certChainCnt++; +#ifdef HAVE_CERTIFICATE_COMPRESSION + /* Same reason as wolfssl_ctx_add_to_chain(): the cache describes + * a chain that no longer exists. */ + CertCompInvalidate(ctx); +#endif } /* Store cert in stack to free it later. */ if ((ret == 1) && (ctx->x509Chain == NULL)) { diff --git a/src/ssl_sess.c b/src/ssl_sess.c index 9677160825d..e4591ae24c4 100644 --- a/src/ssl_sess.c +++ b/src/ssl_sess.c @@ -4486,11 +4486,11 @@ void* wolfSSL_SESSION_get_ex_data(const WOLFSSL_SESSION* session, int idx) #ifdef HAVE_EX_DATA_CRYPTO int wolfSSL_SESSION_get_ex_new_index(long ctx_l,void* ctx_ptr, WOLFSSL_CRYPTO_EX_new* new_func, WOLFSSL_CRYPTO_EX_dup* dup_func, - WOLFSSL_CRYPTO_EX_free* free_func) + WOLFSSL_CRYPTO_EX_free* free_cb) { WOLFSSL_ENTER("wolfSSL_SESSION_get_ex_new_index"); return wolfssl_local_get_ex_new_index(WOLF_CRYPTO_EX_INDEX_SSL_SESSION, - ctx_l, ctx_ptr, new_func, dup_func, free_func); + ctx_l, ctx_ptr, new_func, dup_func, free_cb); } #endif /* HAVE_EX_DATA_CRYPTO */ #endif /* HAVE_EX_DATA */ diff --git a/src/tls.c b/src/tls.c index f600e08b10e..0f174ab9a5c 100644 --- a/src/tls.c +++ b/src/tls.c @@ -2615,6 +2615,69 @@ static int TLSX_SNI_Parse(WOLFSSL* ssl, const byte* input, word16 length, return 0; } +#ifdef WOLFSSL_TLS13 +/** Parses a ServerNameList carried by a TLS v1.3 CertificateRequest. + * + * RFC 9846 Table 1 lists server_name for CR. That marking arrives from + * RFC 9261 and its IANA note narrows it to the ClientCertificateRequest of a + * client-generated authenticator request, which is not a handshake message, + * so no conformant peer sends the extension in the CertificateRequest parsed + * here. Accept it anyway: RFC 9846 Sect. 4.3 only sanctions an + * illegal_parameter abort for an extension that is *not* listed for the + * message, and server_name is listed for CR. + * + * The name guides certificate selection (RFC 9261 Sect. 5.2.1), which needs + * the exported-authenticator machinery wolfSSL does not implement, so nothing + * consumes it. It is still parsed in full rather than skipped, so that a + * malformed list is rejected here instead of being waved through, and it is + * deliberately not routed to TLSX_SNI_Parse(): that is the server-side + * matching path, and running it here would compare the peer's name against + * the client's own configured SNI. + * + * ssl The SSL/TLS object. + * input The extension data. + * length The length of the extension data in bytes. + * returns 0 on success, BUFFER_ERROR when the list is malformed. + */ +static int TLSX_SNI_ParseCertReq(WOLFSSL* ssl, const byte* input, + word16 length) +{ + word16 size = 0; + word16 offset = 0; + byte type; + + (void)ssl; + + if (OPAQUE16_LEN > length) + return BUFFER_ERROR; + + ato16(input, &size); + offset += OPAQUE16_LEN; + + /* Validating sni list length. */ + if (length != OPAQUE16_LEN + size || size == 0) + return BUFFER_ERROR; + + /* Only one type is recognized and only one value per type (RFC 6066), + * so, no loop. */ + type = input[offset++]; + if (type != WOLFSSL_SNI_HOST_NAME) + return BUFFER_ERROR; + + if (offset + OPAQUE16_LEN > length) + return BUFFER_ERROR; + ato16(input + offset, &size); + offset += OPAQUE16_LEN; + + if (offset + size != length || size == 0) + return BUFFER_ERROR; + + WOLFSSL_MSG("SNI in CertificateRequest parsed and ignored"); + + return 0; +} +#endif /* WOLFSSL_TLS13 */ + static int TLSX_SNI_VerifyParse(WOLFSSL* ssl, byte isRequest) { (void)ssl; @@ -2920,6 +2983,9 @@ int TLSX_SNI_GetFromBuffer(const byte* clientHello, word32 helloSz, #define SNI_WRITE TLSX_SNI_Write #define SNI_PARSE TLSX_SNI_Parse #define SNI_VERIFY_PARSE TLSX_SNI_VerifyParse +#ifdef WOLFSSL_TLS13 +#define SNI_PARSE_CR TLSX_SNI_ParseCertReq +#endif #else @@ -2928,6 +2994,9 @@ int TLSX_SNI_GetFromBuffer(const byte* clientHello, word32 helloSz, #define SNI_WRITE(a, b) 0 #define SNI_PARSE(a, b, c, d) 0 #define SNI_VERIFY_PARSE(a, b) 0 +#ifdef WOLFSSL_TLS13 +#define SNI_PARSE_CR(a, b, c) 0 +#endif #endif /* HAVE_SNI */ @@ -7945,6 +8014,432 @@ static int TLSX_CA_Names_Parse(WOLFSSL *ssl, const byte* input, #endif +/******************************************************************************/ +/* Signed Certificate Timestamp - RFC 6962 */ +/******************************************************************************/ + +#ifdef HAVE_SIGNED_CERT_TIMESTAMP + +/* Returns the size of the signed_certificate_timestamp extension's data. + * + * A client asks with an empty extension; a server answers with the + * SignedCertificateTimestampList the application gave it (RFC 6962 + * Sect. 3.3). + */ +static word16 TLSX_SCTS_GetSize(const WOLFSSL* ssl, byte isRequest) +{ + if (isRequest) + return 0; + + return ssl->sctListSz; +} + +/* Writes the signed_certificate_timestamp extension into the buffer. + * + * ssl The SSL/TLS object. + * output The buffer to write the extension into. + * isRequest Whether this is the asking side. + * returns the length of data that was written. + */ +static word16 TLSX_SCTS_Write(const WOLFSSL* ssl, byte* output, byte isRequest) +{ + if (isRequest || ssl->sctListSz == 0) + return 0; + + XMEMCPY(output, ssl->sctList, ssl->sctListSz); + + return ssl->sctListSz; +} + +/* Parses the signed_certificate_timestamp extension. + * + * struct { SerializedSCT sct_list <1..2^16-1>; } + * SignedCertificateTimestampList; + * opaque SerializedSCT<1..2^16-1>; + * + * The list is checked for a well formed outer framing and then handed to the + * application untouched: verifying an SCT needs the log's public key and a + * trust policy, which belong above this library. + * + * ssl The SSL/TLS object. + * input The extension data. + * length Length of the extension data in bytes. + * msgType Message the extension arrived in. + * returns 0 on success, otherwise failure. + */ +static int TLSX_SCTS_Parse(WOLFSSL* ssl, const byte* input, word16 length, + byte msgType) +{ + word16 listSz; + word16 offset; + + /* The request carries no data; note it so a server knows to answer. */ + if (msgType == client_hello) { + if (length != 0) + return BUFFER_ERROR; + ssl->sctRequested = 1; + /* Adopt the context's list unless this object was given its own. + * Copied rather than aliased: the context setter frees the previous + * list, and rotating it at runtime is normal for a Certificate + * Transparency server, which would otherwise leave this object + * writing freed heap onto the wire. */ + if ((ssl->sctListSz == 0) && (ssl->ctx != NULL) && + (ssl->ctx->sctListSz > 0)) { + byte* copy = (byte*)XMALLOC(ssl->ctx->sctListSz, ssl->heap, + DYNAMIC_TYPE_TLSX); + + if (copy == NULL) + return MEMORY_E; + XMEMCPY(copy, ssl->ctx->sctList, ssl->ctx->sctListSz); + ssl->sctList = copy; + ssl->sctListSz = ssl->ctx->sctListSz; + ssl->sctListFromCtx = 1; + } + #ifndef NO_WOLFSSL_SERVER + /* Only a server answers, and TLSX_SetResponse() is not compiled for a + * client-only build without TLS 1.3. Every pre-existing extension + * wraps its response push the same way. */ + if (ssl->sctListSz > 0) { + int ret = TLSX_Push(&ssl->extensions, TLSX_SIGNED_CERT_TIMESTAMP, + (void*)ssl, ssl->heap); + if (ret != 0) + return ret; + TLSX_SetResponse(ssl, TLSX_SIGNED_CERT_TIMESTAMP); + } + #endif + return 0; + } + + /* A server asking, in its CertificateRequest, for SCTs alongside the + * client's own certificate (RFC 8446 Sect. 4.2 lists CH, CR and CT). + * wolfSSL does not staple SCTs to a client Certificate, so the request is + * accepted and ignored: before this it fell to the default case and was + * ignored as an unknown type, and turning that into a fatal alert would + * break a handshake the peer is entitled to attempt. Handled ahead of the + * response framing below, which this is not. RFC 9846 Table 1 drops the + * extension in favour of transparency_info (RFC 9162), so this is + * strictly RFC 8446 compatibility. */ + if (msgType == certificate_request) { + /* The ask carries no data, the same shape as the ClientHello form. */ + if (length != 0) + return BUFFER_ERROR; + return 0; + } + + /* Only a client receives a response. The role has to be checked before + * the TLSX_Find() below, because on a server that same list entry is the + * server's own pushed response - so the lookup would take the self-push + * as proof of a request and store an unsolicited list handed over in a + * client's Certificate message. A server never asks for SCTs: the + * CertificateRequest semaphore deliberately omits the type. */ + if (ssl->options.side != WOLFSSL_CLIENT_END) { + /* Rejected here rather than through TLSX_HandleUnsupportedExtension(), + * which compiles to a constant 0 under NO_WOLFSSL_CLIENT - a + * server-only build is precisely where this matters, and routing + * through it would accept the list there. */ + WOLFSSL_MSG("Signed certificate timestamps sent to a server"); + SendAlert(ssl, alert_fatal, unsupported_extension); + WOLFSSL_ERROR_VERBOSE(UNSUPPORTED_EXTENSION); + return UNSUPPORTED_EXTENSION; + } + + /* A response is only expected when this end asked for one. */ + if (TLSX_Find(ssl->extensions, TLSX_SIGNED_CERT_TIMESTAMP) == NULL) + return TLSX_HandleUnsupportedExtension(ssl); + + if (length < OPAQUE16_LEN) + return BUFFER_ERROR; + ato16(input, &listSz); + if (listSz == 0 || length != (word16)(OPAQUE16_LEN + listSz)) + return BUFFER_ERROR; + + /* Walk the list so a truncated or overlong SerializedSCT is caught here + * rather than by the application. */ + offset = OPAQUE16_LEN; + while (offset < length) { + word16 sctSz; + + /* Widened deliberately: a word16 sum wraps at the top of the + * extension, letting a list that ends at 65534 pass the check and + * read a byte past the buffer. */ + if ((word32)offset + OPAQUE16_LEN > (word32)length) + return BUFFER_ERROR; + ato16(input + offset, &sctSz); + offset = (word16)(offset + OPAQUE16_LEN); + if (sctSz == 0 || (word32)offset + sctSz > (word32)length) + return BUFFER_ERROR; + offset = (word16)(offset + sctSz); + } + + XFREE(ssl->peerSctList, ssl->heap, DYNAMIC_TYPE_TLSX); + /* Cleared with the buffer it describes: on the failure below the size + * would otherwise still name the previous list, and + * wolfSSL_get0_signed_cert_timestamp_list() would report a length for a + * NULL pointer. */ + ssl->peerSctListSz = 0; + ssl->peerSctList = (byte*)XMALLOC(length, ssl->heap, + DYNAMIC_TYPE_TLSX); + if (ssl->peerSctList == NULL) + return MEMORY_E; + XMEMCPY(ssl->peerSctList, input, length); + ssl->peerSctListSz = length; + + return 0; +} + +/* SCT_ is already taken by ServerCertificateType, so these carry the fuller + * abbreviation. */ +#define SCTS_GET_SIZE TLSX_SCTS_GetSize +#define SCTS_WRITE TLSX_SCTS_Write +#define SCTS_PARSE TLSX_SCTS_Parse + +#endif /* HAVE_SIGNED_CERT_TIMESTAMP */ + +/******************************************************************************/ +/* Record Size Limit - RFC 8449 */ +/******************************************************************************/ + +#ifdef HAVE_RECORD_SIZE_LIMIT + +/* Returns the size of the record_size_limit extension's data: one uint16. */ +static word16 TLSX_RecordSizeLimit_GetSize(void) +{ + return OPAQUE16_LEN; +} + +/* Writes the record_size_limit extension into the buffer. + * + * data The WOLFSSL object, passed as the extension's opaque data. + * output The buffer to write the extension into. + * returns the length of data that was written. + */ +static word16 TLSX_RecordSizeLimit_Write(void* data, byte* output) +{ + const WOLFSSL* ssl = (const WOLFSSL*)data; + word16 limit = ssl->recordSizeLimit; + + /* ssl->recordSizeLimit counts payload only. RFC 8449 Sect. 4's field + * counts the whole TLS 1.3 TLSInnerPlaintext, so the content type byte is + * added here and nowhere else; TLS 1.2 has no such byte. Held to payload + * internally, the value can never exceed the protocol maximum either way: + * 2^14 below TLS 1.3 and 2^14+1 at it. */ + if (IsAtLeastTLSv1_3(ssl->version)) + limit++; + + c16toa(limit, output); + + return OPAQUE16_LEN; +} + +/* Parses the record_size_limit extension. + * + * The value is the largest protected record plaintext the peer will accept, + * counting the content type byte of the TLS 1.3 TLSInnerPlaintext, so it caps + * what this end may send rather than anything it receives. + * + * ssl The SSL/TLS object. + * input The extension data. + * length Length of the extension data in bytes. + * msgType Message the extension arrived in. + * returns 0 on success, otherwise failure. + */ +static int TLSX_RecordSizeLimit_Parse(WOLFSSL* ssl, const byte* input, + word16 length, byte msgType) +{ + word16 limit; + /* The peer's answer arrives in EncryptedExtensions under TLS 1.3 and in + * the ServerHello under TLS 1.2 (RFC 8449 Sect. 4). */ + int isResponse = (msgType == encrypted_extensions) || + (msgType == server_hello); + + if (length != OPAQUE16_LEN) + return BUFFER_ERROR; + + /* RFC 9846 Sect. 4.2: a response to an extension this end never offered + * is an unsupported_extension abort. The client only pushes the extension + * when a limit was set, so its absence means it was never asked for. */ + if (isResponse && + TLSX_Find(ssl->extensions, TLSX_RECORD_SIZE_LIMIT) == NULL) { + return TLSX_HandleUnsupportedExtension(ssl); + } + + ato16(input, &limit); + + /* RFC 8449 Sect. 4: "An endpoint MUST treat receipt of a smaller value as + * a fatal error and generate an illegal_parameter alert." */ + if (limit < WOLFSSL_RECORD_SIZE_LIMIT_MIN) { + WOLFSSL_MSG("Record size limit below the minimum of 64"); + SendAlert(ssl, alert_fatal, illegal_parameter); + WOLFSSL_ERROR_VERBOSE(INVALID_PARAMETER); + return INVALID_PARAMETER; + } + + /* A server "MUST NOT enforce" the upper bound, since a client may be + * advertising a limit enabled by an extension the server does not know. + * A client MAY reject one, and does here. The bound is 2^14+1 for TLS 1.3 + * and 2^14 for TLS 1.2, which counts no content type byte. */ + if (isResponse && limit > (IsAtLeastTLSv1_3(ssl->version) ? + WOLFSSL_RECORD_SIZE_LIMIT_MAX_13 : MAX_RECORD_SIZE)) { + WOLFSSL_MSG("Record size limit above the maximum for this version"); + SendAlert(ssl, alert_fatal, illegal_parameter); + WOLFSSL_ERROR_VERBOSE(INVALID_PARAMETER); + return INVALID_PARAMETER; + } + + /* Never send more than the protocol allows, whatever the peer says. */ + if (limit > WOLFSSL_RECORD_SIZE_LIMIT_MAX_13) + limit = WOLFSSL_RECORD_SIZE_LIMIT_MAX_13; + + /* Back to payload, the unit everything above the wire uses. The peer's + * value covers its TLSInnerPlaintext under TLS 1.3, one byte of which is + * the content type. */ + if (IsAtLeastTLSv1_3(ssl->version)) + limit--; + + /* A server that supports this extension answers in EncryptedExtensions, + * and from then on ignores any max_fragment_length in the ClientHello + * (RFC 8449 Sect. 5). */ +#ifndef NO_WOLFSSL_SERVER + /* Only a server answers, and TLSX_SetResponse() is not compiled for a + * client-only build without TLS 1.3. Every pre-existing extension wraps + * its response push the same way - see TLSX_SCTS_Parse(). */ + if (msgType == client_hello) { + int ret; + + /* RFC 8449 Sect. 4: the extension is negotiated only when both ends + * send it. A server holding no limit of its own stays silent, and so + * must not shrink its own records either - otherwise any client could + * push a server that never opted in down to 64-byte records and pay + * it in per-record overhead. */ + if (ssl->recordSizeLimit == 0) + return 0; + + ret = TLSX_Push(&ssl->extensions, TLSX_RECORD_SIZE_LIMIT, + (void*)ssl, ssl->heap); + if (ret != 0) + return ret; + TLSX_SetResponse(ssl, TLSX_RECORD_SIZE_LIMIT); + } +#endif + + ssl->peerRecordSizeLimit = limit; + + return 0; +} + +#define RSL_GET_SIZE TLSX_RecordSizeLimit_GetSize +#define RSL_WRITE TLSX_RecordSizeLimit_Write +#define RSL_PARSE TLSX_RecordSizeLimit_Parse + +#endif /* HAVE_RECORD_SIZE_LIMIT */ + +/******************************************************************************/ +/* Certificate Compression - RFC 8879 */ +/******************************************************************************/ + +#ifdef HAVE_CERTIFICATE_COMPRESSION + +/* Algorithms this build can decompress, most preferred first. Only zlib is + * supported: brotli and zstd would each add a new dependency. */ +static const word16 certCompAlgs[] = { WOLFSSL_CERT_COMP_ZLIB }; +#define CERT_COMP_ALG_CNT ((word16)(sizeof(certCompAlgs) / sizeof(word16))) + +/* Returns the size of the compress_certificate extension's data: a one byte + * list length then two bytes per algorithm (RFC 8879 Sect. 3). */ +static word16 TLSX_CertificateCompression_GetSize(void) +{ + return (word16)(OPAQUE8_LEN + (CERT_COMP_ALG_CNT * OPAQUE16_LEN)); +} + +/* Writes the compress_certificate extension into the buffer. + * + * output The buffer to write the extension into. + * returns the length of data that was written. + */ +static word16 TLSX_CertificateCompression_Write(byte* output) +{ + word16 i; + word16 offset = 0; + + output[offset++] = (byte)(CERT_COMP_ALG_CNT * OPAQUE16_LEN); + for (i = 0; i < CERT_COMP_ALG_CNT; i++) { + c16toa(certCompAlgs[i], output + offset); + offset += OPAQUE16_LEN; + } + + return offset; +} + +/* Is an algorithm one this build offered to decompress? */ +int TLSX_CertificateCompression_Supported(word16 alg) +{ + word16 i; + + for (i = 0; i < CERT_COMP_ALG_CNT; i++) { + if (certCompAlgs[i] == alg) + return 1; + } + + return 0; +} + +/* Parses the compress_certificate extension. + * + * The extension is a unidirectional indication (RFC 8879 Sect. 3): it states + * what its sender can decompress and draws no response. The peer's list is + * checked for a well formed encoding, and the first algorithm on it that this + * build also supports is kept in ssl->peerCertCompAlgo, which is what the + * send path compresses with. + * + * ssl The SSL/TLS object. + * input The extension data. + * length Length of the extension data in bytes. + * returns 0 on success, BUFFER_ERROR when the list is malformed. + */ +static int TLSX_CertificateCompression_Parse(WOLFSSL* ssl, const byte* input, + word16 length) +{ + byte listSz; + word16 offset; + + if (length < OPAQUE8_LEN) + return BUFFER_ERROR; + + listSz = input[0]; + /* RFC 8879 Sect. 3 declares algorithms<2..2^8-2>: at least one algorithm, + * a whole number of them, and nothing trailing the list. */ + if ((listSz < OPAQUE16_LEN) || ((listSz & 1) != 0) || + (length != (word16)(OPAQUE8_LEN + listSz))) { + return BUFFER_ERROR; + } + + /* Keep the first algorithm the peer named that this build can also + * produce. The peer's order is its preference order (RFC 8879 Sect. 3), + * so the first match wins. */ + for (offset = OPAQUE8_LEN; offset < length; offset += OPAQUE16_LEN) { + word16 alg; + + ato16(input + offset, &alg); + if (TLSX_CertificateCompression_Supported(alg)) { + ssl->peerCertCompAlgo = (byte)alg; + break; + } + } + + return 0; +} + +#define CC_GET_SIZE TLSX_CertificateCompression_GetSize +#define CC_WRITE TLSX_CertificateCompression_Write +#define CC_PARSE TLSX_CertificateCompression_Parse + +#endif /* HAVE_CERTIFICATE_COMPRESSION */ + +/* The three sections above have no dependency on certificates or signature + * algorithms: RFC 8449 and RFC 6962 do not, and RFC 8879 only carries a + * message built elsewhere. Their dispatch cases are guarded by the feature + * macro alone, so guarding the implementations more tightly than the call + * sites broke '--enable-recordsizelimit --disable-certs'. */ #if !defined(NO_CERTS) && !defined(WOLFSSL_NO_SIGALG) /******************************************************************************/ /* Signature Algorithms */ @@ -8144,7 +8639,7 @@ static word16 TLSX_SignatureAlgorithmsCert_GetSize(void* data) { WOLFSSL* ssl = (WOLFSSL*)data; - return OPAQUE16_LEN + ssl->certHashSigAlgoSz; + return OPAQUE16_LEN + ssl->ourCertSigAlgoSz; } /* Writes the SignatureAlgorithmsCert extension into the buffer. @@ -8157,11 +8652,11 @@ static word16 TLSX_SignatureAlgorithmsCert_Write(void* data, byte* output) { WOLFSSL* ssl = (WOLFSSL*)data; - c16toa(ssl->certHashSigAlgoSz, output); - XMEMCPY(output + OPAQUE16_LEN, ssl->certHashSigAlgo, - ssl->certHashSigAlgoSz); + c16toa(ssl->ourCertSigAlgoSz, output); + XMEMCPY(output + OPAQUE16_LEN, ssl->ourCertSigAlgo, + ssl->ourCertSigAlgoSz); - return OPAQUE16_LEN + ssl->certHashSigAlgoSz; + return OPAQUE16_LEN + ssl->ourCertSigAlgoSz; } /* Parse the SignatureAlgorithmsCert extension. @@ -15420,6 +15915,21 @@ void TLSX_FreeAll(TLSX* list, void* heap) SA_FREE_ALL((SignatureAlgorithms*)extension->data, heap); break; #endif +#ifdef HAVE_CERTIFICATE_COMPRESSION + case TLSX_COMPRESS_CERTIFICATE: + /* The algorithm list is a build-time constant. */ + break; +#endif +#ifdef HAVE_RECORD_SIZE_LIMIT + case TLSX_RECORD_SIZE_LIMIT: + /* The limit lives on the SSL object. */ + break; +#endif +#ifdef HAVE_SIGNED_CERT_TIMESTAMP + case TLSX_SIGNED_CERT_TIMESTAMP: + /* The lists live on the SSL object and the context. */ + break; +#endif #if defined(HAVE_ENCRYPT_THEN_MAC) && !defined(WOLFSSL_AEAD_ONLY) case TLSX_ENCRYPT_THEN_MAC: WOLFSSL_MSG("Encrypt-Then-Mac extension free"); @@ -15669,6 +16179,21 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType, length += SA_GET_SIZE(extension->data); break; #endif +#ifdef HAVE_CERTIFICATE_COMPRESSION + case TLSX_COMPRESS_CERTIFICATE: + length += CC_GET_SIZE(); + break; +#endif +#ifdef HAVE_RECORD_SIZE_LIMIT + case TLSX_RECORD_SIZE_LIMIT: + length += RSL_GET_SIZE(); + break; +#endif +#ifdef HAVE_SIGNED_CERT_TIMESTAMP + case TLSX_SIGNED_CERT_TIMESTAMP: + length += SCTS_GET_SIZE((WOLFSSL*)extension->data, isRequest); + break; +#endif #if defined(HAVE_ENCRYPT_THEN_MAC) && !defined(WOLFSSL_AEAD_ONLY) case TLSX_ENCRYPT_THEN_MAC: cbShim = 0; @@ -15957,6 +16482,25 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore, offset += SA_WRITE(extension->data, output + offset); break; #endif +#ifdef HAVE_CERTIFICATE_COMPRESSION + case TLSX_COMPRESS_CERTIFICATE: + WOLFSSL_MSG("Certificate Compression extension to write"); + offset += CC_WRITE(output + offset); + break; +#endif +#ifdef HAVE_RECORD_SIZE_LIMIT + case TLSX_RECORD_SIZE_LIMIT: + WOLFSSL_MSG("Record Size Limit extension to write"); + offset += RSL_WRITE(extension->data, output + offset); + break; +#endif +#ifdef HAVE_SIGNED_CERT_TIMESTAMP + case TLSX_SIGNED_CERT_TIMESTAMP: + WOLFSSL_MSG("Signed Certificate Timestamp extension to write"); + offset += SCTS_WRITE((WOLFSSL*)extension->data, + output + offset, isRequest); + break; +#endif #if defined(HAVE_ENCRYPT_THEN_MAC) && !defined(WOLFSSL_AEAD_ONLY) case TLSX_ENCRYPT_THEN_MAC: WOLFSSL_MSG("Encrypt-Then-Mac extension to write"); @@ -16638,6 +17182,58 @@ int TLSX_PopulateExtensions(WOLFSSL* ssl, byte isServer) #else ret = 0; #endif + #ifdef HAVE_SIGNED_CERT_TIMESTAMP + /* RFC 6962 Sect. 3.3: a client asks with an empty extension. There is + * nothing to configure, so it is offered whenever a client is built + * with the feature; the cost is four bytes on the wire. */ + if (!isServer) { + WOLFSSL_MSG("Adding signed certificate timestamp extension"); + if ((ret = TLSX_Push(&ssl->extensions, TLSX_SIGNED_CERT_TIMESTAMP, + (void*)ssl, ssl->heap)) != 0) { + return ret; + } + } + #endif + + /* Outside the TLS 1.3 block below: RFC 8449 covers TLS 1.2 too, + * where the server answers in the ServerHello, so a client built + * without TLS 1.3 must still offer this. */ + #ifdef HAVE_RECORD_SIZE_LIMIT + /* RFC 8449 Sect. 4: the client states in its ClientHello the largest + * record it will accept; the server answers in EncryptedExtensions + * under TLS 1.3 or in the ServerHello under TLS 1.2, which + * TLSX_RecordSizeLimit_Parse() arranges when it sees the request. */ + /* RFC 8449 Sect. 5 has a server ignore max_fragment_length when both + * extensions appear, and Sect. 5 also says a client that depends on a + * small record size may keep advertising max_fragment_length. So the + * built-in default stands aside for an application that explicitly + * asked for max_fragment_length; an explicit + * wolfSSL_UseRecordSizeLimit() still wins, because then the + * application asked for both and RFC 8449 says which governs. */ + if (!isServer && ssl->recordSizeLimit != 0 + #ifdef HAVE_MAX_FRAGMENT + /* Both lists, because wolfSSL_UseMaxFragment() stores on the + * object and wolfSSL_CTX_UseMaxFragment() on the context, + * nothing copies the context's list onto the object, and the + * write path emits both. Missing the context's copy would + * advertise record_size_limit alongside it, and RFC 8449 + * Sect. 5 then has the server ignore max_fragment_length - + * silently dropping what the application asked for. */ + && (ssl->recordSizeLimitSet || + (TLSX_Find(ssl->extensions, + TLSX_MAX_FRAGMENT_LENGTH) == NULL && + (ssl->ctx == NULL || + TLSX_Find(ssl->ctx->extensions, + TLSX_MAX_FRAGMENT_LENGTH) == NULL))) + #endif + ) { + WOLFSSL_MSG("Adding record size limit extension"); + if ((ret = TLSX_Push(&ssl->extensions, TLSX_RECORD_SIZE_LIMIT, + (void*)ssl, ssl->heap)) != 0) { + return ret; + } + } + #endif #ifdef WOLFSSL_TLS13 #if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CA_NAMES) if (IsAtLeastTLSv1_3(ssl->version) && @@ -16648,6 +17244,49 @@ int TLSX_PopulateExtensions(WOLFSSL* ssl, byte isServer) return ret; } } + #endif + #ifdef HAVE_CERTIFICATE_COMPRESSION + #if !defined(WOLFSSL_ASYNC_CRYPT) && !defined(WOLFSSL_NONBLOCK_OCSP) + /* RFC 8879 Sect. 3: a client advertises in the ClientHello that it can + * accept a compressed server certificate, a server advertises in the + * CertificateRequest that it can accept a compressed client one. The + * extension says only what this end is able to decompress. */ + if (IsAtLeastTLSv1_3(ssl->version)) { + WOLFSSL_MSG("Adding certificate compression extension"); + if ((ret = TLSX_Push(&ssl->extensions, TLSX_COMPRESS_CERTIFICATE, + NULL, ssl->heap)) != 0) { + return ret; + } + /* What this end offered, as opposed to what the build can + * decompress: a CompressedCertificate is only acceptable when + * this handshake asked for one. Client only here - this runs from + * DoTls13ClientHello() on a server, before it has decided whether + * to send the CertificateRequest that is a server's only + * advertisement, so the server sets this where that is written. */ + if (!isServer) + ssl->certCompAdvertised = 1; + } + #else + /* DoTls13CompressedCertificate() cannot hand a decompressed buffer + * across a WC_PENDING_E or OCSP_WANT_READ suspension: the certificate + * parser keeps borrowed pointers into it. Advertising and then failing + * every compressed reply is worse than not offering, so these builds + * stay quiet until the resume path retains the buffer. */ + #endif + #endif + #if !defined(NO_CERTS) && !defined(WOLFSSL_NO_SIGALG) + /* Both roles offer the signature algorithms they accept in a peer's + * certificate: the client in the ClientHello, the server in the + * CertificateRequest. RFC 8446 Sect. 4.2.3 makes signature_algorithms + * cover certificates when this extension is absent, so it is sent + * only when the application asked for a different list. */ + if (IsAtLeastTLSv1_3(ssl->version) && ssl->ourCertSigAlgoSz > 0) { + WOLFSSL_MSG("Adding signature algorithms cert extension"); + if ((ret = TLSX_SetSignatureAlgorithmsCert(&ssl->extensions, ssl, + ssl->heap)) != 0) { + return ret; + } + } #endif if (!isServer && IsAtLeastTLSv1_3(ssl->version)) { /* Add mandatory TLS v1.3 extension: supported version */ @@ -16657,16 +17296,6 @@ int TLSX_PopulateExtensions(WOLFSSL* ssl, byte isServer) return ret; } - #if !defined(NO_CERTS) && !defined(WOLFSSL_NO_SIGALG) - if (ssl->certHashSigAlgoSz > 0) { - WOLFSSL_MSG("Adding signature algorithms cert extension"); - if ((ret = TLSX_SetSignatureAlgorithmsCert(&ssl->extensions, - ssl, ssl->heap)) != 0) { - return ret; - } - } - #endif - #if defined(HAVE_SUPPORTED_CURVES) extension = TLSX_Find(ssl->extensions, TLSX_KEY_SHARE); if (extension == NULL) { @@ -17709,6 +18338,13 @@ int TLSX_GetRequestSize(WOLFSSL* ssl, byte msgType, word32* pLength) XMEMSET(semaphore, 0xff, SEMAPHORE_SIZE); #if !defined(NO_CERTS) && !defined(WOLFSSL_NO_SIGALG) TURN_OFF(semaphore, TLSX_ToSemaphore(TLSX_SIGNATURE_ALGORITHMS)); + if (ssl->ourCertSigAlgoSz > 0) { + TURN_OFF(semaphore, + TLSX_ToSemaphore(TLSX_SIGNATURE_ALGORITHMS_CERT)); + } +#endif +#ifdef HAVE_CERTIFICATE_COMPRESSION + TURN_OFF(semaphore, TLSX_ToSemaphore(TLSX_COMPRESS_CERTIFICATE)); #endif #if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CA_NAMES) if (TLSX_CA_Names_Count(ssl) > 0) { @@ -17716,7 +18352,9 @@ int TLSX_GetRequestSize(WOLFSSL* ssl, byte msgType, word32* pLength) TLSX_ToSemaphore(TLSX_CERTIFICATE_AUTHORITIES)); } #endif - /* TODO: TLSX_SIGNED_CERTIFICATE_TIMESTAMP, OID_FILTERS */ + /* TODO: TLSX_OID_FILTERS, and certificate transparency. RFC 9846 + * Table 1 lists transparency_info (RFC 9162) for CR rather than the + * legacy signed_certificate_timestamp. */ /* TLSX_STATUS_REQUEST is enabled: the server may request the client * to staple an OCSP response with its CertificateRequest. */ TURN_OFF(semaphore, TLSX_ToSemaphore(TLSX_STATUS_REQUEST)); @@ -17954,6 +18592,13 @@ int TLSX_WriteRequest(WOLFSSL* ssl, byte* output, byte msgType, word32* pOffset) XMEMSET(semaphore, 0xff, SEMAPHORE_SIZE); #if !defined(NO_CERTS) && !defined(WOLFSSL_NO_SIGALG) TURN_OFF(semaphore, TLSX_ToSemaphore(TLSX_SIGNATURE_ALGORITHMS)); + if (ssl->ourCertSigAlgoSz > 0) { + TURN_OFF(semaphore, + TLSX_ToSemaphore(TLSX_SIGNATURE_ALGORITHMS_CERT)); + } +#endif +#ifdef HAVE_CERTIFICATE_COMPRESSION + TURN_OFF(semaphore, TLSX_ToSemaphore(TLSX_COMPRESS_CERTIFICATE)); #endif #if !defined(NO_CERTS) && !defined(WOLFSSL_NO_CA_NAMES) if (TLSX_CA_Names_Count(ssl) > 0) { @@ -17961,7 +18606,9 @@ int TLSX_WriteRequest(WOLFSSL* ssl, byte* output, byte msgType, word32* pOffset) TLSX_ToSemaphore(TLSX_CERTIFICATE_AUTHORITIES)); } #endif - /* TODO: TLSX_SIGNED_CERTIFICATE_TIMESTAMP, TLSX_OID_FILTERS */ + /* TODO: TLSX_OID_FILTERS, and certificate transparency. RFC 9846 + * Table 1 lists transparency_info (RFC 9162) for CR rather than the + * legacy signed_certificate_timestamp. */ /* TLSX_STATUS_REQUEST is enabled: the server may request the client * to staple an OCSP response with its CertificateRequest. */ TURN_OFF(semaphore, TLSX_ToSemaphore(TLSX_STATUS_REQUEST)); @@ -18056,6 +18703,20 @@ int TLSX_GetResponseSize(WOLFSSL* ssl, byte msgType, word16* pLength) #ifndef NO_WOLFSSL_SERVER case server_hello: PF_VALIDATE_RESPONSE(ssl, semaphore); + #ifdef HAVE_SIGNED_CERT_TIMESTAMP + /* RFC 6962 Sect. 3.3.1: on a resumed session the server "is not + * expected to process it or include the extension in the + * ServerHello", and RFC 9162 Sect. 6.5 makes that a MUST NOT for + * the successor extension. A resumed handshake sends no + * Certificate, so there is nothing for the timestamps to attest. + * Decided here rather than while parsing, because session ID + * resumption is only settled after the ClientHello extensions + * have been read. */ + if (ssl->options.resuming) { + TURN_ON(semaphore, + TLSX_ToSemaphore(TLSX_SIGNED_CERT_TIMESTAMP)); + } + #endif #ifdef WOLFSSL_TLS13 if (IsAtLeastTLSv1_3(ssl->version)) { XMEMSET(semaphore, 0xff, SEMAPHORE_SIZE); @@ -18138,6 +18799,11 @@ int TLSX_GetResponseSize(WOLFSSL* ssl, byte msgType, word16* pLength) #ifdef HAVE_CERTIFICATE_STATUS_REQUEST TURN_ON(semaphore, TLSX_ToSemaphore(TLSX_STATUS_REQUEST)); #endif + #ifdef HAVE_SIGNED_CERT_TIMESTAMP + /* TLS 1.3 carries this on the Certificate message, not here. */ + TURN_ON(semaphore, + TLSX_ToSemaphore(TLSX_SIGNED_CERT_TIMESTAMP)); + #endif #ifdef HAVE_CERTIFICATE_STATUS_REQUEST_V2 TURN_ON(semaphore, TLSX_ToSemaphore(TLSX_STATUS_REQUEST_V2)); #endif @@ -18166,9 +18832,14 @@ int TLSX_GetResponseSize(WOLFSSL* ssl, byte msgType, word16* pLength) /* Don't send out any extension except those that are turned off. */ XMEMSET(semaphore, 0xff, SEMAPHORE_SIZE); TURN_OFF(semaphore, TLSX_ToSemaphore(TLSX_STATUS_REQUEST)); - /* TODO: TLSX_SIGNED_CERTIFICATE_TIMESTAMP, - * TLSX_SERVER_CERTIFICATE_TYPE - */ +#ifdef HAVE_SIGNED_CERT_TIMESTAMP + TURN_OFF(semaphore, TLSX_ToSemaphore(TLSX_SIGNED_CERT_TIMESTAMP)); +#endif + /* TODO: certificate transparency and delegated_credential. + * RFC 9846 Table 1 allows status_request, delegated_credential + * (RFC 9345) and transparency_info (RFC 9162) in a Certificate + * message; server_certificate_type is listed for CH and EE only, + * so it does not belong here. */ break; #endif #endif @@ -18210,6 +18881,20 @@ int TLSX_WriteResponse(WOLFSSL *ssl, byte* output, byte msgType, word16* pOffset #ifndef NO_WOLFSSL_SERVER case server_hello: PF_VALIDATE_RESPONSE(ssl, semaphore); + #ifdef HAVE_SIGNED_CERT_TIMESTAMP + /* RFC 6962 Sect. 3.3.1: on a resumed session the server "is not + * expected to process it or include the extension in the + * ServerHello", and RFC 9162 Sect. 6.5 makes that a MUST NOT for + * the successor extension. A resumed handshake sends no + * Certificate, so there is nothing for the timestamps to attest. + * Decided here rather than while parsing, because session ID + * resumption is only settled after the ClientHello extensions + * have been read. */ + if (ssl->options.resuming) { + TURN_ON(semaphore, + TLSX_ToSemaphore(TLSX_SIGNED_CERT_TIMESTAMP)); + } + #endif #ifdef WOLFSSL_TLS13 if (IsAtLeastTLSv1_3(ssl->version)) { XMEMSET(semaphore, 0xff, SEMAPHORE_SIZE); @@ -18287,6 +18972,11 @@ int TLSX_WriteResponse(WOLFSSL *ssl, byte* output, byte msgType, word16* pOffset #ifdef HAVE_CERTIFICATE_STATUS_REQUEST TURN_ON(semaphore, TLSX_ToSemaphore(TLSX_STATUS_REQUEST)); #endif + #ifdef HAVE_SIGNED_CERT_TIMESTAMP + /* TLS 1.3 carries this on the Certificate message. */ + TURN_ON(semaphore, + TLSX_ToSemaphore(TLSX_SIGNED_CERT_TIMESTAMP)); + #endif #ifdef HAVE_CERTIFICATE_STATUS_REQUEST_V2 TURN_ON(semaphore, TLSX_ToSemaphore(TLSX_STATUS_REQUEST_V2)); #endif @@ -18316,9 +19006,19 @@ int TLSX_WriteResponse(WOLFSSL *ssl, byte* output, byte msgType, word16* pOffset * off. */ XMEMSET(semaphore, 0xff, SEMAPHORE_SIZE); TURN_OFF(semaphore, TLSX_ToSemaphore(TLSX_STATUS_REQUEST)); - /* TODO: TLSX_SIGNED_CERTIFICATE_TIMESTAMP, - * TLSX_SERVER_CERTIFICATE_TYPE - */ + #ifdef HAVE_SIGNED_CERT_TIMESTAMP + /* Must match TLSX_GetResponseSize()'s arm exactly: whatever + * the size pass counts, this pass has to write, or the + * declared handshake length overruns the bytes emitted. */ + TURN_OFF(semaphore, + TLSX_ToSemaphore(TLSX_SIGNED_CERT_TIMESTAMP)); + #endif + /* TODO: certificate transparency and delegated_credential. + * RFC 9846 Table 1 allows status_request, + * delegated_credential (RFC 9345) and transparency_info + * (RFC 9162) in a Certificate message; + * server_certificate_type is listed for CH and EE only, so it + * does not belong here. */ break; #endif #endif @@ -18722,6 +19422,13 @@ WOLFSSL_TEST_VIS int TLSX_Parse(WOLFSSL* ssl, const byte* input, word16 length, #ifdef WOLFSSL_TLS13 if (IsAtLeastTLSv1_3(ssl->version)) { + /* RFC 9846 Table 1: CH, EE, CR. The CertificateRequest + * name is only validated -- see TLSX_SNI_ParseCertReq() + * -- and must not reach the server-side matching path. */ + if (msgType == certificate_request) { + ret = SNI_PARSE_CR(ssl, input + offset, size); + break; + } if (msgType != client_hello && msgType != encrypted_extensions) return EXT_NOT_ALLOWED; @@ -19031,6 +19738,56 @@ WOLFSSL_TEST_VIS int TLSX_Parse(WOLFSSL* ssl, const byte* input, word16 length, break; #endif /* HAVE_ENCRYPT_THEN_MAC */ +#ifdef HAVE_SIGNED_CERT_TIMESTAMP + case TLSX_SIGNED_CERT_TIMESTAMP: + WOLFSSL_MSG("Signed Certificate Timestamp extension received"); + #ifdef WOLFSSL_DEBUG_TLS + WOLFSSL_BUFFER(input + offset, size); + #endif + /* RFC 6962 Sect. 3.3: the client asks in its ClientHello and + * the server answers in the ServerHello, which TLS 1.3 moved + * to the Certificate message. RFC 8446 Sect. 4.2 also lists + * CertificateRequest, where a server asks the client to + * staple SCTs to its own Certificate. */ + if (IsAtLeastTLSv1_3(ssl->version)) { + if (msgType != client_hello && msgType != certificate && + msgType != certificate_request) { + WOLFSSL_ERROR_VERBOSE(EXT_NOT_ALLOWED); + return EXT_NOT_ALLOWED; + } + } + else if (msgType != client_hello && msgType != server_hello) { + WOLFSSL_ERROR_VERBOSE(EXT_NOT_ALLOWED); + return EXT_NOT_ALLOWED; + } + ret = SCTS_PARSE(ssl, input + offset, size, msgType); + break; +#endif +#ifdef HAVE_RECORD_SIZE_LIMIT + case TLSX_RECORD_SIZE_LIMIT: + WOLFSSL_MSG("Record Size Limit extension received"); + #ifdef WOLFSSL_DEBUG_TLS + WOLFSSL_BUFFER(input + offset, size); + #endif + /* RFC 9846 Table 1 lists CH and EE for TLS 1.3. RFC 8449 + * also covers TLS 1.2, where the server answers in the + * ServerHello instead. */ + if (IsAtLeastTLSv1_3(ssl->version)) { + if (msgType != client_hello && + msgType != encrypted_extensions) { + WOLFSSL_ERROR_VERBOSE(EXT_NOT_ALLOWED); + return EXT_NOT_ALLOWED; + } + } + else if (msgType != client_hello && + msgType != server_hello) { + WOLFSSL_ERROR_VERBOSE(EXT_NOT_ALLOWED); + return EXT_NOT_ALLOWED; + } + ret = RSL_PARSE(ssl, input + offset, size, msgType); + break; +#endif + #ifdef WOLFSSL_TLS13 case TLSX_SUPPORTED_VERSIONS: WOLFSSL_MSG("Skipping Supported Versions - already processed"); @@ -19159,6 +19916,24 @@ WOLFSSL_TEST_VIS int TLSX_Parse(WOLFSSL* ssl, const byte* input, word16 length, break; #endif +#ifdef HAVE_CERTIFICATE_COMPRESSION + case TLSX_COMPRESS_CERTIFICATE: + WOLFSSL_MSG("Certificate Compression extension received"); + #ifdef WOLFSSL_DEBUG_TLS + WOLFSSL_BUFFER(input + offset, size); + #endif + /* RFC 8879 Sect. 3: TLS 1.3 and newer only; peers MUST ignore + * it when an earlier version is negotiated. */ + if (!IsAtLeastTLSv1_3(ssl->version)) + break; + if (msgType != client_hello && + msgType != certificate_request) { + WOLFSSL_ERROR_VERBOSE(EXT_NOT_ALLOWED); + return EXT_NOT_ALLOWED; + } + ret = CC_PARSE(ssl, input + offset, size); + break; +#endif #if !defined(NO_CERTS) && !defined(WOLFSSL_NO_SIGALG) case TLSX_SIGNATURE_ALGORITHMS_CERT: WOLFSSL_MSG("Signature Algorithms extension received"); @@ -19539,8 +20314,55 @@ WOLFSSL_TEST_VIS int TLSX_Parse(WOLFSSL* ssl, const byte* input, word16 length, } #endif +#ifdef HAVE_RECORD_SIZE_LIMIT +#ifdef HAVE_MAX_FRAGMENT + /* RFC 8449 Sect. 5: a server that answers with record_size_limit "MUST + * ignore a max_fragment_length that appears in a ClientHello if both + * extensions appear". Done here rather than while parsing because the two + * extensions may arrive in either order, and sending both would leave the + * client obliged to abort. */ + if (ret == 0 && msgType == client_hello && + TLSX_Find(ssl->extensions, TLSX_RECORD_SIZE_LIMIT) != NULL) { + ssl->max_fragment = MAX_RECORD_SIZE; + /* The session carries its own copy for resumption, so it is cleared + * with the live value; leaving it would have a resumed handshake + * re-apply a length this one just overrode. */ + if (ssl->session != NULL) + ssl->session->mfl = 0; + TLSX_Remove(&ssl->extensions, TLSX_MAX_FRAGMENT_LENGTH, ssl->heap); + } + + /* RFC 8449 Sect. 5: a client "MUST treat receipt of both + * max_fragment_length and record_size_limit as a fatal error". Only a + * client receives these two message types. Checked here for the same + * reason as above: either extension may come first, and a check made + * while parsing one of them cannot see the other. */ + if (ret == 0 && (msgType == encrypted_extensions || + msgType == server_hello) && + !IS_OFF(seenType, TLSX_ToSemaphore(TLSX_RECORD_SIZE_LIMIT)) && + !IS_OFF(seenType, TLSX_ToSemaphore(TLSX_MAX_FRAGMENT_LENGTH))) { + WOLFSSL_MSG("Server sent both max_fragment_length and size limit"); + SendAlert(ssl, alert_fatal, illegal_parameter); + WOLFSSL_ERROR_VERBOSE(INVALID_PARAMETER); + ret = INVALID_PARAMETER; + } +#endif +#endif + + /* SNI enforces "the peer had to send this in its ClientHello", so it keys + * off the ClientHello rather than isRequest, which is also true for a + * CertificateRequest: left as isRequest, a client that received a + * CertificateRequest ran the server side WOLFSSL_SNI_ABORT_ON_ABSENCE + * check against its own configuration. + * + * The trusted_ca_keys check is the other way round - it is the !isRequest + * arm that does the work, asking whether a server answered the list this + * client offered. Narrowing its argument the same way would make every + * message that is not a ClientHello count as that answer, so a + * CertificateRequest would re-run a check the ServerHello or + * EncryptedExtensions has already settled. It keeps isRequest. */ if (ret == 0) - ret = SNI_VERIFY_PARSE(ssl, isRequest); + ret = SNI_VERIFY_PARSE(ssl, msgType == client_hello); if (ret == 0) ret = TCA_VERIFY_PARSE(ssl, isRequest); diff --git a/src/tls13.c b/src/tls13.c index 7cbaf2f416b..5f2ed52fb53 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -21,6 +21,10 @@ #include +#ifdef HAVE_CERTIFICATE_COMPRESSION + #include +#endif + /* * TLS 1.3-Specific Build Options: * (See tls.c for generic TLS options: extensions, curves, callbacks, etc.) @@ -2436,8 +2440,11 @@ static void AddTls13Headers(byte* output, word32 length, byte type, AddTls13HandShakeHeader(output + outputAdj, length, 0, length, type, ssl); } -#if (!defined(NO_WOLFSSL_CLIENT) || !defined(NO_WOLFSSL_SERVER)) \ - && !defined(NO_CERTS) +/* HAVE_RECORD_SIZE_LIMIT is in the condition because SendTls13FragmentedMsg() + * needs this for EncryptedExtensions and NewSessionTicket, which a PSK-only + * TLS 1.3 server sends with NO_CERTS defined and no Certificate in sight. */ +#if ((!defined(NO_WOLFSSL_CLIENT) || !defined(NO_WOLFSSL_SERVER)) \ + && !defined(NO_CERTS)) || defined(HAVE_RECORD_SIZE_LIMIT) /* Add both record layer and fragment handshake header to message. * * output The buffer to write the headers into. @@ -2467,7 +2474,8 @@ static void AddTls13FragHeaders(byte* output, word32 fragSz, word32 fragOffset, AddTls13HandShakeHeader(output + outputAdj, length, fragOffset, fragSz, type, ssl); } -#endif /* (!NO_WOLFSSL_CLIENT || !NO_WOLFSSL_SERVER) && !NO_CERTS */ +#endif /* ((!NO_WOLFSSL_CLIENT || !NO_WOLFSSL_SERVER) && !NO_CERTS) || + * HAVE_RECORD_SIZE_LIMIT */ /* Write the sequence number into the buffer. * No DTLS v1.3 support. @@ -6289,7 +6297,9 @@ static int DoTls13CertificateRequest(WOLFSSL* ssl, const byte* input, *inOutIdx += len; /* TODO: Add support for more extensions: - * signed_certificate_timestamp, certificate_authorities, oid_filters. + * oid_filters, and certificate transparency. RFC 9846 Table 1 lists + * transparency_info (RFC 9162) for this message rather than the legacy + * signed_certificate_timestamp, so that is the one to add. */ /* Certificate extensions */ if ((*inOutIdx - begin) + OPAQUE16_LEN > size) @@ -8625,7 +8635,129 @@ int SendTls13ServerHello(WOLFSSL* ssl, byte extMsgType) return ret; } -/* handle generation of TLS 1.3 encrypted_extensions (8) */ +#ifdef HAVE_RECORD_SIZE_LIMIT +/* Re-emit an already-built handshake message as a series of records. + * + * EncryptedExtensions, CertificateRequest and NewSessionTicket are each laid + * down as exactly one record. That holds until a peer negotiates a + * record_size_limit smaller than the message - RFC 8449 Sect. 4 permits a + * limit as low as 64 - and one oversized record then earns a record_overflow + * alert. Those three messages are small and, by the time this is called, + * fully assembled, so copy the body aside and lay it back down across as many + * records as the limit needs. + * + * Every fragment is written into the output buffer and none is flushed here: + * a WANT_WRITE part way through would otherwise strand the scratch copy and + * force the caller to rebuild - and re-hash - a message already counted in + * the transcript. The caller's own SendBuffered() flushes them together, and + * the existing output-buffer retry resends whatever the socket did not take. + * + * ssl The SSL/TLS object. + * output Start of the built record: record header, handshake header, + * then the body. + * msgSz Bytes written to output, headers included. + * type Handshake message type. + * hashOutput Whether the message belongs in the handshake transcript. + * returns 0 on success, otherwise failure. + */ +static int SendTls13FragmentedMsg(WOLFSSL* ssl, byte* output, word32 msgSz, + byte type, int hashOutput) +{ + byte* body; + word32 bodySz; + word32 offset = 0; + word32 maxFragment; + int maxPlain; + int ret = 0; + + if (msgSz < RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ) + return BUFFER_E; + bodySz = msgSz - RECORD_HEADER_SZ - HANDSHAKE_HEADER_SZ; + + /* Tested as a signed int before the cast: the helper returns a negative + * error code, which as a word32 would sail past the check below. A + * fragment also has to carry at least one body byte or this never ends, + * and the first one carries the handshake header too. */ + maxPlain = wolfssl_local_GetMaxPlaintextSize(ssl); + if (maxPlain <= (int)HANDSHAKE_HEADER_SZ) + return BUFFER_E; + maxFragment = (word32)maxPlain; + + body = (byte*)XMALLOC(bodySz, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + if (body == NULL) + return MEMORY_E; + XMEMCPY(body, output + RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ, bodySz); + + while (offset < bodySz) { + word32 fragSz; + word32 i; + int sendSz; + + if (offset == 0) + fragSz = min(bodySz, maxFragment - HANDSHAKE_HEADER_SZ); + else + fragSz = min(bodySz - offset, maxFragment); + + sendSz = (int)(fragSz + MAX_MSG_EXTRA); + if (offset == 0) + sendSz += HANDSHAKE_HEADER_SZ; + + ret = CheckAvailableSize(ssl, sendSz); + if (ret != 0) + break; + /* CheckAvailableSize() may have moved the buffer. */ + output = GetOutputBuffer(ssl); + + if (offset == 0) { + /* Only the first record carries the handshake header, and it + * states the length of the whole message, not of the fragment. */ + AddTls13FragHeaders(output, fragSz, 0, bodySz, type, ssl); + i = RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ; + } + else { + AddTls13RecordHeader(output, fragSz, handshake, ssl); + i = RECORD_HEADER_SZ; + } + XMEMCPY(output + i, body + offset, fragSz); + i += fragSz; + + /* These messages are always encrypted. */ + sendSz = BuildTls13Message(ssl, output, sendSz, + output + RECORD_HEADER_SZ, + (int)(i - RECORD_HEADER_SZ), handshake, + hashOutput, 0, 0); + if (sendSz < 0) { + ret = sendSz; + break; + } + + ssl->buffers.outputBuffer.length += (word32)sendSz; + offset += fragSz; + } + + XFREE(body, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + + return ret; +} + +/* Would this message overflow the record_size_limit the peer negotiated? + * Deliberately keyed on the peer's limit rather than on the maximum plaintext + * size in general: max_fragment_length has never fragmented these three + * messages either, and changing that is not this change's business. + * + * ssl The SSL/TLS object. + * msgSz Bytes written to the output buffer, headers included. + * returns 1 when the message has to be split, otherwise 0. + */ +static int Tls13MsgNeedsFragmenting(WOLFSSL* ssl, word32 msgSz) +{ + if (ssl->peerRecordSizeLimit == 0) + return 0; + return (int)(msgSz - RECORD_HEADER_SZ) > + wolfssl_local_GetMaxPlaintextSize(ssl); +} +#endif /* HAVE_RECORD_SIZE_LIMIT */ + /* Send the rest of the extensions encrypted under the handshake key. * This message is always encrypted in TLS v1.3. * Only a server will send this message. @@ -8755,6 +8887,25 @@ static int SendTls13EncryptedExtensions(WOLFSSL* ssl) } #endif /* WOLFSSL_DTLS13 */ +#ifdef HAVE_RECORD_SIZE_LIMIT + if (Tls13MsgNeedsFragmenting(ssl, idx)) { + ret = SendTls13FragmentedMsg(ssl, output, idx, encrypted_extensions, + 1); + if (ret != 0) + return ret; + + ssl->options.buildingMsg = 0; + ssl->options.serverState = SERVER_ENCRYPTED_EXTENSIONS_COMPLETE; + if (!ssl->options.groupMessages) + ret = SendBuffered(ssl); + + WOLFSSL_LEAVE("SendTls13EncryptedExtensions", ret); + WOLFSSL_END(WC_FUNC_ENCRYPTED_EXTENSIONS_SEND); + + return ret; + } +#endif + /* This handshake message is always encrypted. */ sendSz = BuildTls13Message(ssl, output, sendSz, output + RECORD_HEADER_SZ, (int)(idx - RECORD_HEADER_SZ), @@ -8855,6 +9006,17 @@ static int SendTls13CertificateRequest(WOLFSSL* ssl, byte* reqCtx, return ret; i += reqSz; +#ifdef HAVE_CERTIFICATE_COMPRESSION + /* RFC 8879 Sect. 4: this message is a server's only advertisement, so the + * offer is made here rather than when the extension list was populated. + * Keyed on the extension actually being in the list just written, which + * is what a build that deliberately offers nothing - async crypt or + * non-blocking OCSP - leaves out. Set before the three send paths below + * so every one of them is covered. */ + if (TLSX_Find(ssl->extensions, TLSX_COMPRESS_CERTIFICATE) != NULL) + ssl->certCompAdvertised = 1; +#endif + #ifdef WOLFSSL_DTLS13 if (ssl->options.dtls) { ssl->options.buildingMsg = 0; @@ -8870,6 +9032,36 @@ static int SendTls13CertificateRequest(WOLFSSL* ssl, byte* reqCtx, } #endif /* WOLFSSL_DTLS13 */ +#ifdef HAVE_RECORD_SIZE_LIMIT + if (Tls13MsgNeedsFragmenting(ssl, i)) { + #if defined(WOLFSSL_CALLBACKS) || defined(OPENSSL_EXTRA) + /* Traced before the split, while output still holds the whole + * message: the fragments are the same bytes in several records, and + * SendTls13FragmentedMsg() may move the buffer. */ + if (ssl->hsInfoOn) + AddPacketName(ssl, "CertificateRequest"); + if (ssl->toInfoOn) { + ret = AddPacketInfo(ssl, "CertificateRequest", handshake, output, + (int)i, WRITE_PROTO, 0, ssl->heap); + if (ret != 0) + return ret; + } + #endif + ret = SendTls13FragmentedMsg(ssl, output, i, certificate_request, 1); + if (ret != 0) + return ret; + + ssl->options.buildingMsg = 0; + if (!ssl->options.groupMessages) + ret = SendBuffered(ssl); + + WOLFSSL_LEAVE("SendTls13CertificateRequest", ret); + WOLFSSL_END(WC_FUNC_CERTIFICATE_REQUEST_SEND); + + return ret; + } +#endif + /* Always encrypted. */ sendSz = BuildTls13Message(ssl, output, sendSz, output + RECORD_HEADER_SZ, (int)(i - RECORD_HEADER_SZ), handshake, 1, 0, 0); @@ -9726,7 +9918,7 @@ static word32 NextCert(byte* data, word32 length, word32* idx) return len; } -#if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(NO_WOLFSSL_SERVER) +#ifdef WOLFSSL_CERT_ENTRY_EXTS /* Write certificate status request into certificate to buffer. * * ssl SSL/TLS object. @@ -9739,80 +9931,133 @@ static word32 NextCert(byte* data, word32 length, word32* idx) * offset index offset * returns Total number of bytes written on success or negative value on error. */ -static int WriteCSRToBuffer(WOLFSSL* ssl, DerBuffer** certExts, - word16* extSz, word16 extSz_num) +static int WriteCertEntryExts(WOLFSSL* ssl, DerBuffer** certExts, + word16* extSz, word16 extSz_num) { int ret = 0; - TLSX* ext; - CertificateStatusRequest* csr; - word32 ex_offset = HELLO_EXT_TYPE_SZ + OPAQUE16_LEN /* extension type */ - + OPAQUE16_LEN /* extension length */; word32 totalSz = 0; word32 tmpSz; word32 extIdx; DerBuffer* der; +#ifdef HAVE_CERTIFICATE_STATUS_REQUEST + TLSX* ext; + CertificateStatusRequest* csr; +#endif +#ifdef HAVE_SIGNED_CERT_TIMESTAMP + word32 sctSz = 0; +#endif if (extSz_num > MAX_CERT_EXTENSIONS) return MAX_CERT_EXTENSIONS_ERR; +#ifdef HAVE_CERTIFICATE_STATUS_REQUEST ext = TLSX_Find(ssl->extensions, TLSX_STATUS_REQUEST); csr = ext ? (CertificateStatusRequest*)ext->data : NULL; +#endif - if (csr) { - for (extIdx = 0; extIdx < (word16)(extSz_num); extIdx++) { - tmpSz = TLSX_CSR_GetSize_ex(csr, 0, (int)extIdx); +#ifdef HAVE_SIGNED_CERT_TIMESTAMP + /* RFC 6962 Sect. 3.3: the server answers in the ServerHello at TLS 1.2, + * which TLS 1.3 moved into the end entity certificate's CertificateEntry + * extensions - the ones this function builds. Server only: a server has + * the extension on its list because TLSX_SCTS_Parse() put it there to + * answer a request, whereas a client's copy is the request it sent and + * carries nothing to send back. The bytes written are the whole + * SerializedSCT list, which is the extension_data the peer's parser + * expects. */ + if ((ssl->options.side == WOLFSSL_SERVER_END) && (ssl->sctListSz > 0) && + (TLSX_Find(ssl->extensions, TLSX_SIGNED_CERT_TIMESTAMP) != NULL)) { + sctSz = HELLO_EXT_TYPE_SZ + OPAQUE16_LEN + ssl->sctListSz; + } +#endif - if (ssl->fragOffset != 0 && certExts[extIdx] != NULL) { - /* A fragmented send is being resumed and this buffer was - * written by the earlier call. extSz starts over on every - * call, so recover this entry's size from the length written - * into the buffer. */ - ato16(certExts[extIdx]->buffer, &extSz[extIdx]); - extSz[extIdx] += OPAQUE16_LEN; - } - else { - /* Not a resume, so anything still allocated here is left over - * from a completed message and must not be reused. */ - FreeDer(&certExts[extIdx]); - - if (tmpSz > (OPAQUE8_LEN + OPAQUE24_LEN)) { - /* csr extension is not zero */ - if (tmpSz > WOLFSSL_MAX_16BIT) - return BUFFER_E; - extSz[extIdx] = (word16)tmpSz; - - ret = AllocDer(&certExts[extIdx], extSz[extIdx] + ex_offset, - CERT_TYPE, ssl->heap); - if (ret < 0) - return ret; - der = certExts[extIdx]; - - /* write extension type */ - c16toa(ext->type, der->buffer - + OPAQUE16_LEN); - /* writes extension data length. */ - c16toa(extSz[extIdx], der->buffer - + HELLO_EXT_TYPE_SZ + OPAQUE16_LEN); - /* write extension data */ - extSz[extIdx] = (word16)TLSX_CSR_Write_ex(csr, - der->buffer + ex_offset, 0, extIdx); - /* add extension offset */ - extSz[extIdx] += (word16)ex_offset; - /* extension length */ - c16toa(extSz[extIdx] - OPAQUE16_LEN, - der->buffer); - } + for (extIdx = 0; extIdx < (word32)extSz_num; extIdx++) { + word32 csrSz = 0; + word32 thisSctSz = 0; + +#ifdef HAVE_SIGNED_CERT_TIMESTAMP + /* Only the end entity certificate is attested. */ + if (extIdx == 0) + thisSctSz = sctSz; +#endif + + if (ssl->fragOffset != 0 && certExts[extIdx] != NULL) { + /* A fragmented send is being resumed and this buffer was written + * by the earlier call. extSz starts over on every call, so recover + * this entry's size from the length written into the buffer. */ + ato16(certExts[extIdx]->buffer, &extSz[extIdx]); + extSz[extIdx] += OPAQUE16_LEN; + totalSz += extSz[extIdx]; + continue; + } + + /* Not a resume, so anything still allocated here is left over from a + * completed message and must not be reused. */ + FreeDer(&certExts[extIdx]); + +#ifdef HAVE_CERTIFICATE_STATUS_REQUEST + if (csr != NULL) { + tmpSz = TLSX_CSR_GetSize_ex(csr, 0, (int)extIdx); + /* csr extension is not zero */ + if (tmpSz > (OPAQUE8_LEN + OPAQUE24_LEN)) { + if (tmpSz > WOLFSSL_MAX_16BIT) + return BUFFER_E; + csrSz = tmpSz; } + } +#endif + + if ((csrSz == 0) && (thisSctSz == 0)) { + /* extSz was primed with OPAQUE16_LEN for an empty extensions + * field, which AddCertExt() writes without reading a buffer. */ totalSz += extSz[extIdx]; + continue; } + + /* The entry's own two length bytes, then each extension with its type + * and length ahead of it. */ + tmpSz = OPAQUE16_LEN + thisSctSz; + if (csrSz != 0) + tmpSz += HELLO_EXT_TYPE_SZ + OPAQUE16_LEN + csrSz; + if (tmpSz > WOLFSSL_MAX_16BIT) + return BUFFER_E; + + ret = AllocDer(&certExts[extIdx], tmpSz, CERT_TYPE, ssl->heap); + if (ret < 0) + return ret; + der = certExts[extIdx]; + + tmpSz = OPAQUE16_LEN; +#ifdef HAVE_CERTIFICATE_STATUS_REQUEST + if (csrSz != 0) { + /* extension type, then its data length */ + c16toa(ext->type, der->buffer + tmpSz); + c16toa((word16)csrSz, der->buffer + tmpSz + HELLO_EXT_TYPE_SZ); + tmpSz += HELLO_EXT_TYPE_SZ + OPAQUE16_LEN; + tmpSz += (word32)TLSX_CSR_Write_ex(csr, der->buffer + tmpSz, 0, + (int)extIdx); + } +#endif +#ifdef HAVE_SIGNED_CERT_TIMESTAMP + if (thisSctSz != 0) { + c16toa(TLSX_SIGNED_CERT_TIMESTAMP, der->buffer + tmpSz); + tmpSz += HELLO_EXT_TYPE_SZ; + c16toa(ssl->sctListSz, der->buffer + tmpSz); + tmpSz += OPAQUE16_LEN; + XMEMCPY(der->buffer + tmpSz, ssl->sctList, ssl->sctListSz); + tmpSz += ssl->sctListSz; + } +#endif + /* The extensions field's length, which does not count its own two + * bytes. */ + c16toa((word16)(tmpSz - OPAQUE16_LEN), der->buffer); + extSz[extIdx] = (word16)tmpSz; + totalSz += extSz[extIdx]; } - else { - /* chain cert empty extension size */ - totalSz += OPAQUE16_LEN * extSz_num; - } + return (int)totalSz; } -#endif /* HAVE_CERTIFICATE_STATUS_REQUEST */ + +#endif /* (status_request or SCT) and server */ /* Add certificate data and empty extension to output up to the fragment size. * * ssl SSL/TLS object. @@ -9972,20 +10217,26 @@ static int SetupOcspResp(WOLFSSL* ssl) #endif #if !defined(NO_CERTS) && !defined(WOLFSSL_NO_SIGALG) -/* Certificate is signed with the deprecated SHA-1 hash. An unrecognized or - * unparsable algorithm is not SHA-1; the peer still verifies the chain. +/* Map a certificate's signatureAlgorithm to the TLS hash and signature pair + * that names it. * - * der Buffer holding the DER encoded certificate. - * derSz Length of the DER encoded certificate. - * returns 1 when SHA-1 signed, 0 otherwise. + * der Buffer holding the DER encoded certificate. + * derSz Length of the DER encoded certificate. + * hashAlgo On success, the digest the certificate was signed with. + * sigAlgo On success, the signature algorithm it was signed with. Both are + * left as no_mac/invalid_sa_algo when the algorithm is not one this + * code recognises, which is not an error: the peer still verifies + * the chain itself. + * returns 1 when the algorithm was recognised, 0 otherwise. */ -static int IsSha1SignedCert(const byte* der, word32 derSz) +static int GetCertSigAlgo(const byte* der, word32 derSz, byte* hashAlgo, + byte* sigAlgo) { word32 idx = 0; word32 oid = 0; word32 algoIdEnd = 0; int len = 0; - int isSha1 = 0; + int known = 0; int ret; #if defined(WC_RSA_PSS) && !defined(NO_RSA) enum wc_HashType hash = WC_HASH_TYPE_NONE; @@ -9993,6 +10244,9 @@ static int IsSha1SignedCert(const byte* der, word32 derSz) int saltLen = 0; #endif + *hashAlgo = no_mac; + *sigAlgo = invalid_sa_algo; + /* Certificate ::= SEQUENCE { tbsCertificate, signatureAlgorithm, ... }. * GetSequence() checks each length against the maximum index passed in, so * idx and idx + len stay inside the buffer. */ @@ -10011,25 +10265,165 @@ static int IsSha1SignedCert(const byte* der, word32 derSz) ret = GetObjectId(der, &idx, &oid, oidSigType, algoIdEnd); } if (ret >= 0) { - if ((oid == CTC_SHAwRSA) || (oid == CTC_SHAwECDSA) || - (oid == CTC_SHAwDSA)) { - isSha1 = 1; + known = 1; + /* The CTC_* OID sums and the wc_MACAlgorithm values are both defined + * unconditionally, so this mapping is not guarded by the algorithm + * build macros: a certificate has to be classified even in a build + * that cannot itself use the algorithm, or it would escape the SHA-1 + * rule below by looking unrecognised. */ + switch (oid) { + case CTC_SHAwRSA: *hashAlgo = sha_mac; break; + case CTC_SHA224wRSA: *hashAlgo = sha224_mac; break; + case CTC_SHA256wRSA: *hashAlgo = sha256_mac; break; + case CTC_SHA384wRSA: *hashAlgo = sha384_mac; break; + case CTC_SHA512wRSA: *hashAlgo = sha512_mac; break; + case CTC_SHAwECDSA: *hashAlgo = sha_mac; break; + case CTC_SHA224wECDSA: *hashAlgo = sha224_mac; break; + case CTC_SHA256wECDSA: *hashAlgo = sha256_mac; break; + case CTC_SHA384wECDSA: *hashAlgo = sha384_mac; break; + case CTC_SHA512wECDSA: *hashAlgo = sha512_mac; break; + case CTC_SHAwDSA: *hashAlgo = sha_mac; break; + case CTC_SHA256wDSA: *hashAlgo = sha256_mac; break; + case CTC_ED25519: *hashAlgo = sha512_mac; break; + case CTC_ED448: *hashAlgo = sha512_mac; break; + case CTC_SM3wSM2: *hashAlgo = sm3_mac; break; + default: + /* Signature algorithms this mapping does not cover, such as + * the post-quantum ones, are reported as unknown so that the + * certificate is left alone rather than rejected. */ + known = 0; + break; } + + switch (oid) { + case CTC_SHAwRSA: case CTC_SHA224wRSA: + case CTC_SHA256wRSA: case CTC_SHA384wRSA: + case CTC_SHA512wRSA: + *sigAlgo = rsa_sa_algo; + break; + case CTC_SHAwECDSA: case CTC_SHA224wECDSA: + case CTC_SHA256wECDSA: case CTC_SHA384wECDSA: + case CTC_SHA512wECDSA: + *sigAlgo = ecc_dsa_sa_algo; + break; + case CTC_SHAwDSA: case CTC_SHA256wDSA: + *sigAlgo = dsa_sa_algo; + break; + case CTC_ED25519: *sigAlgo = ed25519_sa_algo; break; + case CTC_ED448: *sigAlgo = ed448_sa_algo; break; + case CTC_SM3wSM2: *sigAlgo = sm2_sa_algo; break; + default: + break; + } + #if defined(WC_RSA_PSS) && !defined(NO_RSA) /* RSASSA-PSS uses one signature OID for every digest and names the * digest in the algorithm parameters instead. Absent parameters are * passed through as a zero length buffer rather than skipped: RFC 4055 * makes them mean all defaults, which is SHA-1, and * wc_DecodeRsaPssParams() reports that. */ - else if ((oid == RSAPSSk) && (idx <= algoIdEnd) && + if ((oid == RSAPSSk) && (idx <= algoIdEnd) && (wc_DecodeRsaPssParams(der + idx, algoIdEnd - idx, &hash, &mgf, &saltLen) == 0)) { - isSha1 = (hash == WC_HASH_TYPE_SHA); + *sigAlgo = rsa_pss_sa_algo; + known = 1; + if (hash == WC_HASH_TYPE_SHA) + *hashAlgo = sha_mac; + else if (hash == WC_HASH_TYPE_SHA224) + *hashAlgo = sha224_mac; + else if (hash == WC_HASH_TYPE_SHA256) + *hashAlgo = sha256_mac; + else if (hash == WC_HASH_TYPE_SHA384) + *hashAlgo = sha384_mac; + else if (hash == WC_HASH_TYPE_SHA512) + *hashAlgo = sha512_mac; + else + known = 0; } #endif } - return isSha1; + return known; +} + +/* Is a certificate signature algorithm one the peer said it accepts? + * + * Walks the signature_algorithms_cert list the peer sent, decoding each entry + * with DecodeSigAlg() so the encoding is read in exactly one place. + * + * An entry matches only when both the digest and the signature algorithm + * agree. The caller asks about SHA-1 chains alone, and TLS 1.3 assigns no + * rsa_pss_*_sha1 code point, so there is no digest at which a PSS and a + * PKCS#1 entry could stand in for one another here. + * + * ssl The SSL/TLS object. + * hashAlgo Digest the certificate was signed with. + * sigAlgo Signature algorithm the certificate was signed with. + * returns 1 when the peer accepts the algorithm, 0 when it does not. + */ +static int PeerAcceptsCertSigAlgo(const WOLFSSL* ssl, byte hashAlgo, + byte sigAlgo) +{ + word16 i; + + for (i = 0; (word16)(i + OPAQUE16_LEN) <= ssl->certHashSigAlgoSz; + i += OPAQUE16_LEN) { + byte peerHash = no_mac; + byte peerSig = invalid_sa_algo; + + DecodeSigAlg(&ssl->certHashSigAlgo[i], &peerHash, &peerSig); + if (peerHash != hashAlgo) + continue; + if (peerSig == sigAlgo) + return 1; + /* No RSA leniency here on purpose. The caller only asks about SHA-1 + * chains, and TLS 1.3 assigns no rsa_pss_*_sha1 code point, so an + * RSASSA-PSS certificate whose parameters name SHA-1 can never be + * matched by a conformant peer's list and is refused. Accepting one + * against an advertised rsa_pkcs1_sha1 would be honouring a promise + * the peer did not make. */ + } + + return 0; +} + +/* May a certificate about to be sent be rejected on its signature algorithm? + * + * ssl The SSL/TLS object. + * der Buffer holding the DER encoded certificate. + * derSz Length of the DER encoded certificate. + * returns 1 when the peer will not accept the certificate, 0 otherwise. + */ +static int CertSigAlgoRejected(const WOLFSSL* ssl, const byte* der, + word32 derSz) +{ + byte hashAlgo = no_mac; + byte sigAlgo = invalid_sa_algo; + + if (!GetCertSigAlgo(der, derSz, &hashAlgo, &sigAlgo)) { + /* Not a signature algorithm this maps, so nothing to check it + * against. Leave the certificate for the peer to judge. */ + return 0; + } + + /* Only SHA-1 bars a certificate outright. RFC 9846 Sect. 4.5.1.2 lets a + * sender that cannot build a chain signed with the algorithms the peer + * advertised send the chain it has anyway, so a mismatch on any other + * digest is for the peer to judge on receipt -- but that fallback "MUST + * NOT use the deprecated SHA-1 hash, unless the [peer] specifically + * advertises that it is willing to accept SHA-1". */ + if (hashAlgo != sha_mac) + return 0; + + /* That willingness is per signature scheme, not per digest: a peer that + * advertised ecdsa_sha1 alone has not agreed to accept an rsa_pkcs1_sha1 + * certificate. Match the whole scheme when the peer sent a certificate + * list; without one, all that survives parsing is the flag + * SetPeerSha1CertOk() left behind. */ + if (ssl->certHashSigAlgoSz > 0) + return !PeerAcceptsCertSigAlgo(ssl, hashAlgo, sigAlgo); + + return !ssl->options.peerSha1CertOk; } /* Certificate is self signed. RFC 8446 Section 4.4.2.2: "Certificates that are @@ -10089,6 +10483,10 @@ static int IsSelfSignedCert(WOLFSSL* ssl, const byte* der, word32 derSz, * same rule covers both sides. How a failure is resolved differs by side and is * left to the caller. * + * The SHA-1 test is made against the peer's signature_algorithms_cert list + * when it sent one, matching the certificate's whole signature scheme rather + * than only its digest. + * * ssl The SSL/TLS object. * returns 0 when the chain may be sent, MATCH_SUITE_ERROR when it may not and * MEMORY_E when a certificate could not be examined. @@ -10103,7 +10501,7 @@ static int CheckCertChainSigAlgo(WOLFSSL* ssl) int selfSigned = 0; int ret = 0; - if (ssl->options.peerSha1CertOk) + if (ssl->certHashSigAlgoSz == 0 && ssl->options.peerSha1CertOk) return 0; if (ssl->buffers.certificate == NULL || @@ -10111,8 +10509,8 @@ static int CheckCertChainSigAlgo(WOLFSSL* ssl) return 0; } - if (IsSha1SignedCert(ssl->buffers.certificate->buffer, - ssl->buffers.certificate->length)) { + if (CertSigAlgoRejected(ssl, ssl->buffers.certificate->buffer, + ssl->buffers.certificate->length)) { ret = IsSelfSignedCert(ssl, ssl->buffers.certificate->buffer, ssl->buffers.certificate->length, &selfSigned); if (ret != 0) @@ -10140,7 +10538,7 @@ static int CheckCertChainSigAlgo(WOLFSSL* ssl) cur += CERT_HEADER_SZ; len -= CERT_HEADER_SZ; - if (IsSha1SignedCert(cur, len)) { + if (CertSigAlgoRejected(ssl, cur, len)) { ret = IsSelfSignedCert(ssl, cur, len, &selfSigned); if (ret != 0) return ret; @@ -10150,13 +10548,326 @@ static int CheckCertChainSigAlgo(WOLFSSL* ssl) } } - if (ret == WC_NO_ERR_TRACE(MATCH_SUITE_ERROR)) - WOLFSSL_MSG("Chain is SHA-1 signed but peer did not advertise SHA-1"); + if (ret == WC_NO_ERR_TRACE(MATCH_SUITE_ERROR)) { + WOLFSSL_MSG("Chain is SHA-1 signed but peer did not advertise it"); + } return ret; } #endif /* !NO_CERTS && !WOLFSSL_NO_SIGALG */ +#ifdef HAVE_CERTIFICATE_COMPRESSION +/* May this handshake send the context's cached compressed certificate? + * + * The cache was built for an empty certificate_request_context and no + * per-certificate extensions, so anything needing either is excluded: a + * post-handshake authentication request carries a context, OCSP stapling adds + * a status_request extension, and a raw public key is not the message the + * cache holds. The certificate must also still be the context's own, not one + * replaced on this WOLFSSL object. + */ +/* Is this the same DER the cache was built from? Under WOLFSSL_COPY_CERT the + * object holds its own copy of the context's certificate, so the buffers are + * compared rather than the pointers. */ +static int SameDerAsCtx(const DerBuffer* a, const DerBuffer* b) +{ + if (a == b) + return 1; + if (a == NULL || b == NULL) + return 0; + if (a->length != b->length) + return 0; + return XMEMCMP(a->buffer, b->buffer, a->length) == 0; +} + +static int UseCompressedCertificate(WOLFSSL* ssl) +{ + WOLFSSL_CTX* ctx = ssl->ctx; + +#ifdef WOLFSSL_DTLS13 + /* DTLS 1.3 fragments handshake messages inside dtls13_handshake_send(), + * which this path does not go through, so the plain message is sent. */ + if (ssl->options.dtls) + return 0; +#endif + + if (ctx == NULL || ctx->certComp == NULL) + return 0; + if (ssl->peerCertCompAlgo == 0 || + ssl->peerCertCompAlgo != ctx->certCompAlgo) { + return 0; + } + if (ssl->options.sendVerify == SEND_BLANK_CERT) + return 0; + /* The certificate this connection sends has to be the one the cache was + * built from; a per-object certificate replaces it. */ + if (!SameDerAsCtx(ssl->buffers.certificate, ctx->certificate)) + return 0; + if (!SameDerAsCtx(ssl->buffers.certChain, ctx->certChain)) + return 0; + if (ssl->buffers.certChainCnt != ctx->certChainCnt) + return 0; + /* And the cache has to describe the certificate the context holds now. + * The invalidation hooks on the replacement paths should have caught + * this already; checking here means a path added later that forgets one + * degrades to sending the plain message rather than the wrong + * certificate. */ + /* SameDerAsCtx() above reports a match for two NULLs, so reaching here + * does not prove the context still holds a certificate. */ + if (ctx->certificate == NULL) + return 0; + if (ctx->certificate->length != ctx->certCompCertSz) + return 0; + if (((ctx->certChain != NULL) ? ctx->certChain->length : 0) != + ctx->certCompChainSz) { + return 0; + } + if (ctx->certChainCnt != ctx->certCompChainCnt) + return 0; +#ifdef WOLFSSL_POST_HANDSHAKE_AUTH + /* The cached body carries an empty certificate_request_context, so it can + * only answer a request that had one. An in-handshake CertificateRequest + * does (RFC 8446 Sect. 4.3.2), and still lands here in ssl->certReqCtx, + * so the length is what separates it from post-handshake auth - testing + * the pointer alone declined every client-authenticated handshake. */ + if (ssl->certReqCtx != NULL && ssl->certReqCtx->len > 0) + return 0; +#endif + /* A stapled OCSP response rides in the Certificate message's per-entry + * extensions, which the cached body does not have - but only the end that + * staples is affected. These fields record that this end asked its peer + * for a staple, which for a client says nothing about the certificate it + * sends itself; wolfSSL does not staple to a client Certificate. Testing + * them regardless turned every ordinary client, which advertises OCSP by + * default, into one that never compresses. */ + if (ssl->options.side == WOLFSSL_SERVER_END) { +#ifdef HAVE_CERTIFICATE_STATUS_REQUEST + if (ssl->status_request != 0) + return 0; +#endif +#ifdef HAVE_CERTIFICATE_STATUS_REQUEST_V2 + if (ssl->status_request_v2 != 0) + return 0; +#endif +#ifdef HAVE_SIGNED_CERT_TIMESTAMP + /* An SCT list rides in the same CertificateEntry extensions as a + * stapled OCSP response, and the cached body is built with those + * empty. The extension only reaches a server's list when + * TLSX_SCTS_Parse() put it there to answer a request, so its presence + * is exactly the case that would send a CompressedCertificate with + * the list dropped. */ + if (TLSX_Find(ssl->extensions, TLSX_SIGNED_CERT_TIMESTAMP) != NULL) + return 0; +#endif + } +#ifdef HAVE_RPK + /* The counts are non-zero for plain X.509 too, so the negotiated type is + * what matters: a raw public key is not the message the cache holds. */ + if (ssl->options.side == WOLFSSL_SERVER_END) { + if (ssl->options.rpkState.sending_ServerCertTypeCnt > 0 && + ssl->options.rpkState.sending_ServerCertTypes[0] == + WOLFSSL_CERT_TYPE_RPK) { + return 0; + } + } + else if (ssl->options.rpkState.sending_ClientCertTypeCnt > 0 && + ssl->options.rpkState.sending_ClientCertTypes[0] == + WOLFSSL_CERT_TYPE_RPK) { + return 0; + } +#endif + return 1; +} + +/* handle generation TLS v1.3 compressed_certificate (25) */ +/* Send the context's cached compressed certificate in place of the + * Certificate message (RFC 8879 Sect. 4). + * + * struct { + * CertificateCompressionAlgorithm algorithm; + * uint24 uncompressed_length; + * opaque compressed_certificate_message<1..2^24-1>; + * } CompressedCertificate; + * + * Fragmented across records the same way SendTls13Certificate() fragments the + * plain message: the handshake header goes on the first record only and + * ssl->fragOffset carries the position across a WANT_WRITE. Without this the + * message ignored both max_fragment_length and the peer's record_size_limit, + * and a body over 2^14 bytes produced a record a conformant peer must reject. + * + * The message goes into the handshake transcript in this compressed form, + * which BuildTls13Message() does for free, and is what the peer hashes. + * + * ssl The SSL/TLS object. + * returns 0 on success, otherwise failure. + */ +static int SendTls13CompressedCertificate(WOLFSSL* ssl) +{ + WOLFSSL_CTX* ctx = ssl->ctx; + /* algorithm(2) + uncompressed_length(3) + compressed length(3) */ + byte hdr[OPAQUE16_LEN + OPAQUE24_LEN + OPAQUE24_LEN]; + word32 payloadSz; + word32 maxFragment; + int ret = 0; + + /* SendTls13Certificate() already opened the WC_FUNC_CERTIFICATE_SEND + * trace for this message, so only the function marker is added here. */ + WOLFSSL_ENTER("SendTls13CompressedCertificate"); + + /* The cache is read again on every WANT_WRITE resume, so re-check it + * rather than trust the decision made when the message began. + * wolfSSL_CTX_compress_certs() documents that the context must not be + * changed while handshakes are in flight; this turns a violation into a + * hard error instead of a truncated Certificate reported as sent. */ + if ((ctx == NULL) || (ctx->certComp == NULL) || (ctx->certCompSz == 0)) + return BAD_FUNC_ARG; + + /* Sized only after the guard above: initialising this in the declaration + * block dereferenced ctx before the NULL check that exists to prevent + * exactly that. */ + payloadSz = (word32)sizeof(hdr) + ctx->certCompSz; + if (ssl->fragOffset == 0) { + ssl->certCompSendSz = ctx->certCompSz; + ssl->certCompSendAlgo = ctx->certCompAlgo; + } + else if ((ssl->certCompSendSz != ctx->certCompSz) || + (ssl->certCompSendAlgo != ctx->certCompAlgo)) { + WOLFSSL_MSG("Certificate compression cache changed mid-message"); + return BUFFER_E; + } + if (ssl->fragOffset > payloadSz) + return BUFFER_E; + + c16toa((word16)ctx->certCompAlgo, hdr); + c32to24(ctx->certCompPlainSz, hdr + OPAQUE16_LEN); + c32to24(ctx->certCompSz, hdr + OPAQUE16_LEN + OPAQUE24_LEN); + + ret = wolfssl_local_GetMaxPlaintextSize(ssl); + if (ret < 0) + return ret; + maxFragment = (word32)ret; + ret = 0; + /* Room for the handshake header has to come out of the first record. */ + if (maxFragment <= HANDSHAKE_HEADER_SZ) + return BUFFER_E; + + ssl->options.buildingMsg = 1; + + while (ssl->fragOffset < payloadSz && ret == 0) { + byte* output; + word32 i = RECORD_HEADER_SZ; + word32 fragSz; + word32 avail = maxFragment; + int sendSz; + + if (ssl->fragOffset == 0) { + i += HANDSHAKE_HEADER_SZ; + avail -= HANDSHAKE_HEADER_SZ; + } + fragSz = payloadSz - ssl->fragOffset; + if (fragSz > avail) + fragSz = avail; + + sendSz = (int)(i + fragSz + MAX_MSG_EXTRA); + if ((ret = CheckAvailableSize(ssl, sendSz)) != 0) + return ret; + output = GetOutputBuffer(ssl); + + if (ssl->fragOffset == 0) { + AddTls13FragHeaders(output, fragSz, 0, payloadSz, + compressed_certificate, ssl); + } + else { + AddTls13RecordHeader(output, fragSz, handshake, ssl); + } + + /* The message body is the header above followed by the cached + * compressed certificate; copy whichever part of that this fragment + * covers. */ + { + word32 off = ssl->fragOffset; + word32 left = fragSz; + word32 idx = i; + + if (off < sizeof(hdr)) { + word32 n = (word32)sizeof(hdr) - off; + + if (n > left) + n = left; + XMEMCPY(output + idx, hdr + off, n); + idx += n; + left -= n; + off += n; + } + if (left > 0) { + XMEMCPY(output + idx, ctx->certComp + (off - sizeof(hdr)), + left); + } + } + + sendSz = BuildTls13Message(ssl, output, sendSz, + output + RECORD_HEADER_SZ, + (int)(i + fragSz - RECORD_HEADER_SZ), + handshake, 1, 0, 0); + if (sendSz < 0) + return sendSz; + +#if defined(WOLFSSL_CALLBACKS) || defined(OPENSSL_EXTRA) + /* One trace entry per logical message, not per record. */ + if (ssl->fragOffset == 0) { + if (ssl->hsInfoOn) + AddPacketName(ssl, "CompressedCertificate"); + if (ssl->toInfoOn) { + ret = AddPacketInfo(ssl, "CompressedCertificate", handshake, + output, sendSz, WRITE_PROTO, 0, ssl->heap); + if (ret != 0) + return ret; + } + } +#endif + + ssl->buffers.outputBuffer.length += (word32)sendSz; + ssl->fragOffset += fragSz; + + if (!ssl->options.groupMessages) + ret = SendBuffered(ssl); + } + + /* WANT_WRITE keeps the cursor so the message can resume; anything else + * ends it. The state has to advance exactly as the plain path does, or a + * server that compressed its Certificate never reaches + * SERVER_CERT_COMPLETE. */ + if (ret != WC_NO_ERR_TRACE(WANT_WRITE)) { + ssl->fragOffset = 0; + ssl->sendingCompCert = 0; + ssl->options.buildingMsg = 0; + if (ssl->options.side == WOLFSSL_SERVER_END) + ssl->options.serverState = SERVER_CERT_COMPLETE; +#ifdef WOLFSSL_POST_HANDSHAKE_AUTH + /* Answering a CertificateRequest consumes the CertReqCtx it queued. + * SendTls13Certificate() does this in an epilogue that the branch + * into this function returns before reaching, and the case that gets + * here is precisely the one that queued a node: an in-handshake + * request, whose zero length context is what UseCompressedCertificate() + * allows to compress. Inside the completed-send block because on + * WANT_WRITE the message is still in flight and the node is still the + * one being answered. */ + if (ssl->options.side == WOLFSSL_CLIENT_END && + ssl->certReqCtx != NULL) { + CertReqCtx* certReqCtx = ssl->certReqCtx; + ssl->certReqCtx = certReqCtx->next; + XFREE(certReqCtx, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + } +#endif + } + + WOLFSSL_LEAVE("SendTls13CompressedCertificate", ret); + + return ret; +} + +#endif /* HAVE_CERTIFICATE_COMPRESSION */ + /* handle generation TLS v1.3 certificate (11) */ /* Send the certificate for this end and any CAs that help with validation. * This message is always encrypted in TLS v1.3. @@ -10259,6 +10970,24 @@ static int SendTls13Certificate(WOLFSSL* ssl) } #endif +#ifdef HAVE_CERTIFICATE_COMPRESSION + /* A cached compressed copy replaces this message when the peer offered a + * matching algorithm and nothing about this handshake changes the body. + * + * Placed after the signature algorithm check above, not before it: that + * check is what refuses a chain the peer will not accept, and it may + * switch a client to a blank certificate, which is not the message the + * cache holds. Compressing earlier skipped it entirely and sent a SHA-1 + * chain to a peer that had refused SHA-1. */ + /* Decide once, at the start of the message. ssl->fragOffset is the + * cursor for whichever path is chosen, so a resume must stay on it. */ + if (ssl->sendingCompCert || + ((ssl->fragOffset == 0) && UseCompressedCertificate(ssl))) { + ssl->sendingCompCert = 1; + return SendTls13CompressedCertificate(ssl); + } +#endif + if (ssl->options.sendVerify == SEND_BLANK_CERT) { certSz = 0; certChainSz = 0; @@ -10284,7 +11013,7 @@ static int SendTls13Certificate(WOLFSSL* ssl) for (extIdx = 0; extIdx < (word16)XELEM_CNT(extSz); extIdx++) extSz[extIdx] = OPAQUE16_LEN; - #if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(NO_WOLFSSL_SERVER) + #ifdef WOLFSSL_CERT_ENTRY_EXTS /* Staple our own OCSP response with the Certificate. Normally only the * server staples; the client's CSR holds the server's response, so * echoing it back is wrong. The exception is post-handshake auth (PHA), @@ -10298,16 +11027,18 @@ static int SendTls13Certificate(WOLFSSL* ssl) /* Build the responses once. A resumed send reuses them: looking * them up again appends another set of requests to the extension * until it overflows with MAX_CERT_EXTENSIONS_ERR. */ + #ifdef HAVE_CERTIFICATE_STATUS_REQUEST if (ssl->fragOffset == 0) { ret = SetupOcspResp(ssl); if (ret != 0) return ret; } + #endif if ((1 + ssl->buffers.certChainCnt) > MAX_CERT_EXTENSIONS) ret = MAX_CERT_EXTENSIONS_ERR; if (ret == 0) - ret = WriteCSRToBuffer(ssl, &ssl->buffers.certExts[0], &extSz[0], + ret = WriteCertEntryExts(ssl, &ssl->buffers.certExts[0], &extSz[0], 1 /* +1 for leaf */ + (word16)ssl->buffers.certChainCnt); if (ret < 0) return ret; @@ -10355,7 +11086,7 @@ static int SendTls13Certificate(WOLFSSL* ssl) if (certChainSz > 0 && ssl->fragOffset >= certSz + extSz[0]) { word32 chainPos = ssl->fragOffset - (certSz + extSz[0]); - #if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(NO_WOLFSSL_SERVER) + #ifdef WOLFSSL_CERT_ENTRY_EXTS /* The leaf is behind us and its buffer was rebuilt above. */ FreeDer(&ssl->buffers.certExts[0]); #endif @@ -10367,8 +11098,7 @@ static int SendTls13Certificate(WOLFSSL* ssl) ssl->buffers.certChain->length, &idx); if (len == 0) break; - #if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && \ - !defined(NO_WOLFSSL_SERVER) + #ifdef WOLFSSL_CERT_ENTRY_EXTS if (extIdx + 1 < MAX_CERT_EXTENSIONS) extIdx++; #endif @@ -10382,8 +11112,7 @@ static int SendTls13Certificate(WOLFSSL* ssl) } else { /* Entry already sent in full; stay primed for the next one. */ - #if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && \ - !defined(NO_WOLFSSL_SERVER) + #ifdef WOLFSSL_CERT_ENTRY_EXTS /* Its buffer was rebuilt above and nothing writes it again. */ FreeDer(&ssl->buffers.certExts[extIdx]); #endif @@ -10503,8 +11232,7 @@ static int SendTls13Certificate(WOLFSSL* ssl) ssl->buffers.certChain->length, &idx); if (len == 0) break; - #if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && \ - !defined(NO_WOLFSSL_SERVER) + #ifdef WOLFSSL_CERT_ENTRY_EXTS if (extIdx + 1 < MAX_CERT_EXTENSIONS) extIdx++; #endif @@ -11981,8 +12709,272 @@ static int DoTls13Certificate(WOLFSSL* ssl, byte* input, word32* inOutIdx, return ret; } + #endif +/* Certificate compression sits outside the client-auth block above: a server + * compressing the chain it presents has nothing to do with authenticating a + * client, and wolfSSL_CTX_compress_certs() is guarded by the feature macro + * alone, so a server-only build linked against it failed. */ +#ifdef HAVE_CERTIFICATE_COMPRESSION +/* Serialise the Certificate message body for a context's own chain. + * + * Produces the body exactly as SendTls13Certificate() would for the plain + * case with an empty certificate_request_context and no per-certificate + * extensions: + * + * opaque certificate_request_context<0..2^8-1> (empty) + * CertificateEntry certificate_list<0..2^24-1> + * opaque cert_data<1..2^24-1> + * Extension extensions<0..2^16-1> (empty) + * + * Those are the conditions the cached copy is used under, checked by the + * caller; anything else falls back to building the message per handshake. + * + * ctx Context holding the certificate and chain. + * out On success, the allocated body. Caller frees with ctx->heap. + * outSz On success, its length. + * returns 0 on success, otherwise failure. + */ +int BuildTls13CertificateBody(WOLFSSL_CTX* ctx, byte** out, word32* outSz) +{ + byte* body; + word32 listSz = 0; + word32 idx = 0; + word32 chainIdx = 0; + word32 sz; + + if (ctx == NULL || out == NULL || outSz == NULL || + ctx->certificate == NULL || ctx->certificate->buffer == NULL) { + return BAD_FUNC_ARG; + } + + /* Leaf: length, DER, empty extensions. */ + listSz = CERT_HEADER_SZ + ctx->certificate->length + OPAQUE16_LEN; + /* Chain entries arrive already prefixed with their 3 byte length, and + * each one gains an empty extensions field. + * + * The count comes from walking the buffer, not from ctx->certChainCnt: + * wolfSSL_CTX_add1_chain_cert() appends to the chain without maintaining + * that counter, so sizing from it while the loop below walks the buffer + * would write past the allocation once they disagree. The walk also + * rejects a malformed chain before anything is allocated. */ + if (ctx->certChain != NULL && ctx->certChain->buffer != NULL) { + word32 walk = 0; + int entries = 0; + + while (walk + CERT_HEADER_SZ <= ctx->certChain->length) { + word32 certSz = 0; + + c24to32(ctx->certChain->buffer + walk, &certSz); + if ((certSz == 0) || + (walk + CERT_HEADER_SZ + certSz > ctx->certChain->length)) { + return BUFFER_E; + } + walk += CERT_HEADER_SZ + certSz; + if (++entries > MAX_CHAIN_DEPTH) + return MAX_CHAIN_ERROR; + } + if (walk != ctx->certChain->length) + return BUFFER_E; + listSz += ctx->certChain->length + + ((word32)entries * OPAQUE16_LEN); + } + + /* certificate_list is opaque<0..2^24-1> on the wire, and the receive side + * refuses anything past MAX_CERTIFICATE_SZ, so a body this end could never + * see accepted is an error here rather than a truncated length field. */ + if (listSz > MAX_CERTIFICATE_SZ) + return BUFFER_E; + + sz = OPAQUE8_LEN + OPAQUE24_LEN + listSz; + body = (byte*)XMALLOC(sz, ctx->heap, DYNAMIC_TYPE_TMP_BUFFER); + if (body == NULL) + return MEMORY_E; + + body[idx++] = 0; /* empty request context */ + c32to24(listSz, body + idx); + idx += OPAQUE24_LEN; + + c32to24(ctx->certificate->length, body + idx); + idx += CERT_HEADER_SZ; + XMEMCPY(body + idx, ctx->certificate->buffer, ctx->certificate->length); + idx += ctx->certificate->length; + c16toa(0, body + idx); /* empty extensions */ + idx += OPAQUE16_LEN; + + if (ctx->certChain != NULL && ctx->certChain->buffer != NULL) { + while (chainIdx + CERT_HEADER_SZ <= ctx->certChain->length) { + word32 certSz = 0; + + c24to32(ctx->certChain->buffer + chainIdx, &certSz); + if (certSz == 0 || + chainIdx + CERT_HEADER_SZ + certSz > + ctx->certChain->length) { + XFREE(body, ctx->heap, DYNAMIC_TYPE_TMP_BUFFER); + return BUFFER_E; + } + /* Bounded explicitly: the reconciliation after the loop can + * only notice an overrun once it has already happened. */ + if (idx + CERT_HEADER_SZ + certSz + OPAQUE16_LEN > sz) { + XFREE(body, ctx->heap, DYNAMIC_TYPE_TMP_BUFFER); + return BUFFER_E; + } + XMEMCPY(body + idx, ctx->certChain->buffer + chainIdx, + CERT_HEADER_SZ + certSz); + idx += CERT_HEADER_SZ + certSz; + chainIdx += CERT_HEADER_SZ + certSz; + c16toa(0, body + idx); + idx += OPAQUE16_LEN; + } + } + + if (idx != sz) { + XFREE(body, ctx->heap, DYNAMIC_TYPE_TMP_BUFFER); + return BUFFER_E; + } + + *out = body; + *outSz = sz; + + return 0; +} +#endif /* HAVE_CERTIFICATE_COMPRESSION */ + +#ifdef HAVE_CERTIFICATE_COMPRESSION +/* handle processing TLS v1.3 compressed_certificate (25) */ +/* Decompress a CompressedCertificate message and process the Certificate + * message it carries. RFC 8879 Sect. 4. + * + * struct { + * CertificateCompressionAlgorithm algorithm; + * uint24 uncompressed_length; + * opaque compressed_certificate_message<1..2^24-1>; + * } CompressedCertificate; + * + * The payload is the body of the Certificate message that would otherwise + * have been sent, so the decompressed bytes go straight to + * DoTls13Certificate(). The handshake transcript is unaffected: the caller + * hashes the message as it arrived on the wire, in compressed form, which is + * what the peer hashed too. + * + * ssl The SSL/TLS object. + * input The message buffer. + * inOutIdx On entry, offset of the message body. On exit, offset past it. + * totalSz Length of the message body in bytes. + * returns 0 on success, otherwise failure. + */ +/* Same guard as DoTls13Certificate(), which this calls and which is static, + * and as the dispatch site in DoTls13HandShakeMsgType(). Unlike + * BuildTls13CertificateBody() above, receiving a compressed Certificate is + * exactly the client-auth path, so it is unwanted - and unbuildable - in a + * server-only build without client auth. */ +#if !defined(NO_WOLFSSL_CLIENT) || !defined(WOLFSSL_NO_CLIENT_AUTH) +WOLFSSL_TEST_VIS int DoTls13CompressedCertificate(WOLFSSL* ssl, + byte* input, word32* inOutIdx, word32 totalSz) +{ + int ret; + word16 alg = 0; + word32 idx = *inOutIdx; + word32 uncompSz = 0; + word32 compSz = 0; + word32 certIdx = 0; + byte* uncomp = NULL; + + WOLFSSL_START(WC_FUNC_CERTIFICATE_DO); + WOLFSSL_ENTER("DoTls13CompressedCertificate"); + + /* algorithm(2) + uncompressed_length(3) + compressed length(3) */ + if (totalSz < OPAQUE16_LEN + OPAQUE24_LEN + OPAQUE24_LEN) + return BUFFER_ERROR; + + ato16(input + idx, &alg); + idx += OPAQUE16_LEN; + + /* RFC 8879 Sect. 4: "The algorithm MUST be one of the algorithms listed + * in the peer's compress_certificate extension", which is this end's. */ + if (!TLSX_CertificateCompression_Supported(alg)) { + WOLFSSL_MSG("Compressed certificate uses an algorithm not offered"); + SendAlert(ssl, alert_fatal, illegal_parameter); + WOLFSSL_ERROR_VERBOSE(INVALID_PARAMETER); + return INVALID_PARAMETER; + } + + c24to32(input + idx, &uncompSz); + idx += OPAQUE24_LEN; + c24to32(input + idx, &compSz); + idx += OPAQUE24_LEN; + + /* Bound the declared size before committing memory, and require the + * compressed data to be exactly the rest of the message. */ + if ((uncompSz == 0) || (uncompSz > WOLFSSL_MAX_CERT_COMP_SZ)) { + WOLFSSL_MSG("Compressed certificate uncompressed length rejected"); + SendAlert(ssl, alert_fatal, bad_certificate); + WOLFSSL_ERROR_VERBOSE(BUFFER_ERROR); + return BUFFER_ERROR; + } + if ((compSz == 0) || (compSz != totalSz - (idx - *inOutIdx))) { + WOLFSSL_MSG("CompressedCertificate length does not match message"); + SendAlert(ssl, alert_fatal, decode_error); + WOLFSSL_ERROR_VERBOSE(BUFFER_ERROR); + return BUFFER_ERROR; + } + + uncomp = (byte*)XMALLOC(uncompSz, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + if (uncomp == NULL) + return MEMORY_E; + + ret = wc_DeCompress(uncomp, uncompSz, input + idx, compSz); + /* RFC 8879 Sect. 4: a message that will not decompress, or whose real + * length disagrees with uncompressed_length, is a bad_certificate. */ + if (ret < 0 || (word32)ret != uncompSz) { + WOLFSSL_MSG("Compressed certificate did not decompress"); + XFREE(uncomp, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + SendAlert(ssl, alert_fatal, bad_certificate); + WOLFSSL_ERROR_VERBOSE(DECOMPRESS_E); + return DECOMPRESS_E; + } + + ret = DoTls13Certificate(ssl, uncomp, &certIdx, uncompSz); + /* ProcessPeerCerts() borrows pointers into this buffer rather than + * copying, and keeps them in ssl->async across a suspension. The plain + * path is safe because its input is ssl->buffers.inputBuffer, which + * outlives the suspension; this buffer does not. Rather than free memory + * the resumed parse still points at, refuse the suspension: the peer is + * told the message could not be processed and the connection ends, which + * is a great deal better than a use-after-free on peer-controlled data. + * Lifting this needs the buffer retained on the WOLFSSL object across the + * resume. */ + if ((ret == WC_NO_ERR_TRACE(WC_PENDING_E)) || + (ret == WC_NO_ERR_TRACE(OCSP_WANT_READ))) { + WOLFSSL_MSG("Compressed certificate cannot suspend, failing"); + XFREE(uncomp, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + SendAlert(ssl, alert_fatal, internal_error); + WOLFSSL_ERROR_VERBOSE(NOT_COMPILED_IN); + return NOT_COMPILED_IN; + } + XFREE(uncomp, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + if (ret != 0) + return ret; + + if (ret == 0) { + /* Set only now: until the inner Certificate has been accepted there + * is nothing to report, and an accessor that named an algorithm for a + * message that was rejected would be describing a chain this end + * never took. */ + ssl->certCompUsed = (byte)alg; + } + + *inOutIdx = idx + compSz; + + WOLFSSL_LEAVE("DoTls13CompressedCertificate", ret); + WOLFSSL_END(WC_FUNC_CERTIFICATE_DO); + + return ret; +} +#endif /* !NO_WOLFSSL_CLIENT || !WOLFSSL_NO_CLIENT_AUTH */ +#endif /* HAVE_CERTIFICATE_COMPRESSION */ + #if (!defined(NO_RSA) || defined(HAVE_ECC) || defined(HAVE_ED25519) || \ defined(HAVE_ED448) || defined(HAVE_FALCON) || \ defined(WOLFSSL_HAVE_MLDSA) || defined(WOLFSSL_HAVE_SLHDSA)) && \ @@ -14366,6 +15358,22 @@ static int SendTls13NewSessionTicket(WOLFSSL* ssl) (word16)idx, session_ticket, 0); #endif /* WOLFSSL_DTLS13 */ +#ifdef HAVE_RECORD_SIZE_LIMIT + if (Tls13MsgNeedsFragmenting(ssl, idx)) { + /* Not part of the transcript, so hashOutput stays off here too. */ + ret = SendTls13FragmentedMsg(ssl, output, idx, session_ticket, 0); + if (ret != 0) + return ret; + + ret = SendBuffered(ssl); + + WOLFSSL_LEAVE("SendTls13NewSessionTicket", ret); + WOLFSSL_END(WC_FUNC_NEW_SESSION_TICKET_SEND); + + return ret; + } +#endif + /* This message is always encrypted. */ sendSz = BuildTls13Message(ssl, output, sendSz, output + RECORD_HEADER_SZ, @@ -14565,6 +15573,26 @@ static int SanityCheckTls13MsgReceived(WOLFSSL* ssl, byte type) break; #endif +#ifdef HAVE_CERTIFICATE_COMPRESSION + /* RFC 8879 Sect. 4: a CompressedCertificate replaces the Certificate + * message, so it is subject to the same ordering rules. */ + case compressed_certificate: + /* RFC 8879 Sect. 4: "If the peer has not indicated support ... + * the endpoint MUST ... terminate the connection with an + * unexpected_message alert." certCompAdvertised records what this + * handshake actually offered, which is not the same as what the + * build can decompress - an async-crypt or non-blocking-OCSP + * build deliberately offers nothing. Checked here so an + * unsolicited message is refused before + * DoTls13CompressedCertificate() allocates and inflates + * attacker-supplied bytes. */ + if (!ssl->certCompAdvertised) { + WOLFSSL_MSG("CompressedCertificate not advertised by us"); + WOLFSSL_ERROR_VERBOSE(OUT_OF_ORDER_E); + return OUT_OF_ORDER_E; + } + FALL_THROUGH; +#endif case certificate: /* Valid on both sides. */ #ifndef NO_WOLFSSL_CLIENT @@ -15068,6 +16096,9 @@ int DoTls13HandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx, if (ssl->options.handShakeState == HANDSHAKE_DONE && type != session_ticket && type != certificate_request && type != certificate && type != key_update && type != finished +#ifdef HAVE_CERTIFICATE_COMPRESSION + && type != compressed_certificate +#endif #if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_DTLS_CID) && type != request_connection_id && type != new_connection_id #endif @@ -15233,6 +16264,13 @@ int DoTls13HandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx, WOLFSSL_MSG("processing certificate"); ret = DoTls13Certificate(ssl, input, inOutIdx, size); break; + + #ifdef HAVE_CERTIFICATE_COMPRESSION + case compressed_certificate: + WOLFSSL_MSG("processing compressed certificate"); + ret = DoTls13CompressedCertificate(ssl, input, inOutIdx, size); + break; + #endif #endif #if (!defined(NO_RSA) || defined(HAVE_ECC) || defined(HAVE_ED25519) || \ diff --git a/src/x509.c b/src/x509.c index 55967dc5c15..60f529a79c0 100644 --- a/src/x509.c +++ b/src/x509.c @@ -15938,12 +15938,12 @@ int wolfSSL_sk_X509_num(const WOLF_STACK_OF(WOLFSSL_X509) *s) int wolfSSL_X509_get_ex_new_index(int idx, void *arg, WOLFSSL_CRYPTO_EX_new* new_func, WOLFSSL_CRYPTO_EX_dup* dup_func, - WOLFSSL_CRYPTO_EX_free* free_func) + WOLFSSL_CRYPTO_EX_free* free_cb) { WOLFSSL_ENTER("wolfSSL_X509_get_ex_new_index"); return wolfssl_local_get_ex_new_index(WOLF_CRYPTO_EX_INDEX_X509, idx, arg, - new_func, dup_func, free_func); + new_func, dup_func, free_cb); } #endif diff --git a/tests/api.c b/tests/api.c index fc98fe8800d..49bb37f23c4 100644 --- a/tests/api.c +++ b/tests/api.c @@ -4625,9 +4625,13 @@ static int test_wolfSSL_clear_chain_certs(void) return EXPECT_RESULT(); } +/* Guarded exactly as its only caller below is: the caller drives a full + * client/server handshake, so without the client this hook has no user and + * -Werror=unused-function rejects the build. */ #if !defined(NO_FILESYSTEM) && !defined(NO_CERTS) && defined(OPENSSL_EXTRA) && \ defined(KEEP_OUR_CERT) && !defined(NO_RSA) && !defined(NO_TLS) && \ - !defined(NO_WOLFSSL_SERVER) && !defined(OPENSSL_COEXIST) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(OPENSSL_COEXIST) && \ (defined(OPENSSL_ALL) || defined(WOLFSSL_ASIO) || \ defined(WOLFSSL_HAPROXY) || defined(WOLFSSL_NGINX)) /* Server-side ssl_ready hook: add chain certs then clear them, so the diff --git a/tests/api/test_dtls13.c b/tests/api/test_dtls13.c index 01a012c4838..66b3a06f843 100644 --- a/tests/api/test_dtls13.c +++ b/tests/api/test_dtls13.c @@ -349,7 +349,13 @@ int test_dtls_frag_ch(void) WOLFSSL *ssl_c = NULL; WOLFSSL *ssl_s = NULL; struct test_memio_ctx test_ctx; - static unsigned int DUMMY_MTU = 256; + /* Sized so the first ClientHello still fits one datagram. It must not be + * tuned to the byte: the ClientHello grows whenever another extension is + * compiled in - record_size_limit alone adds 6 - and CH1 cannot fragment, + * so too tight a value turns into a BUFFER_ERROR rather than a fragmented + * hello. The headroom here is deliberate; CH2 still fragments, which is + * what this test is checking. */ + static unsigned int DUMMY_MTU = 288; unsigned int len; unsigned char four_frag_CH[] = { 0x16, 0xfe, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, diff --git a/tests/api/test_tls.c b/tests/api/test_tls.c index c9c0c8a44b6..96ddae4fde7 100644 --- a/tests/api/test_tls.c +++ b/tests/api/test_tls.c @@ -3378,3 +3378,146 @@ int test_record_size_cache_invalidated_on_renegotiation(void) #endif return EXPECT_RESULT(); } + + +int test_tls12_record_size_limit(void) +{ + EXPECT_DECLS; +/* Deliberately not gated on WOLFSSL_TLS13: RFC 8449 covers TLS 1.2, where the + * server answers in the ServerHello, so the extension has to work in a build + * with TLS 1.3 compiled out. The TLS 1.3 specific cases live in + * test_tls13_record_size_limit(). */ +#if defined(HAVE_RECORD_SIZE_LIMIT) && !defined(WOLFSSL_NO_TLS12) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_CERTS) && \ + !defined(NO_RSA) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_FILESYSTEM) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + + /* TLS 1.2 has no content type byte and caps at 2^14, so the default is + * trimmed on the way out and the peer learns the smaller value. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->peerRecordSizeLimit, MAX_RECORD_SIZE); + ExpectIntEQ(wolfSSL_GetMaxOutputSize(ssl_c), MAX_RECORD_SIZE); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); ctx_s = NULL; + + /* RFC 8449 covers TLS 1.2 too, where the server answers in the + * ServerHello and the limit counts no content type byte, so the whole + * limit is available to the payload. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_UseRecordSizeLimit(ssl_c, 512), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseRecordSizeLimit(ssl_s, 1024), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + if (ssl_c != NULL) + ExpectIntEQ(ssl_c->peerRecordSizeLimit, 1024); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->peerRecordSizeLimit, 512); + /* No content type byte to allow for at this version. */ + ExpectIntEQ(wolfSSL_GetMaxOutputSize(ssl_s), 512); + ExpectIntEQ(wolfSSL_GetMaxOutputSize(ssl_c), 1024); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); ctx_s = NULL; + +#ifdef HAVE_MAX_FRAGMENT + /* RFC 8449 Sect. 5: a server answering with record_size_limit ignores a + * max_fragment_length sent alongside it, so the smaller record_size_limit + * governs rather than the 2048 byte fragment class. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_UseMaxFragment(ssl_c, WOLFSSL_MFL_2_11), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseRecordSizeLimit(ssl_c, 512), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseRecordSizeLimit(ssl_s, 900), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + if (ssl_s != NULL) { + ExpectIntEQ(ssl_s->peerRecordSizeLimit, 512); + ExpectIntEQ(ssl_s->max_fragment, MAX_RECORD_SIZE); + } + ExpectIntEQ(wolfSSL_GetMaxOutputSize(ssl_s), 512); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); ctx_s = NULL; + + /* The same yielding, but with max_fragment_length asked for on the + * context. wolfSSL_CTX_UseMaxFragment() stores on ctx->extensions and + * nothing copies that list onto the object - the write path emits both - + * so a check of ssl->extensions alone does not see it. Getting this wrong + * makes the client advertise both, which by RFC 8449 Sect. 5 has the + * server ignore max_fragment_length and leaves the application's + * requested fragment size silently unapplied. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + wolfSSL_free(ssl_c); ssl_c = NULL; + ExpectIntEQ(wolfSSL_CTX_UseMaxFragment(ctx_c, WOLFSSL_MFL_2_11), + WOLFSSL_SUCCESS); + ExpectNotNull(ssl_c = wolfSSL_new(ctx_c)); + wolfSSL_SetIOWriteCtx(ssl_c, &test_ctx); + wolfSSL_SetIOReadCtx(ssl_c, &test_ctx); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + /* No record_size_limit was offered, so the peer learned none and the + * fragment length the application asked for is the one in force. */ + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->peerRecordSizeLimit, 0); + if (ssl_c != NULL) + ExpectIntEQ(ssl_c->max_fragment, 2048); + ExpectIntEQ(wolfSSL_GetMaxOutputSize(ssl_c), 2048); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); ctx_s = NULL; +#endif /* HAVE_MAX_FRAGMENT */ + /* Set on the context, inherited by every object made from it. The + * inheritance is version independent code on purpose - keeping it in the + * TLS 1.3 only path left a TLS 1.2 build advertising the default here + * instead of what the application asked for. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + ExpectIntEQ(wolfSSL_CTX_UseRecordSizeLimit(ctx_c, 512), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_UseRecordSizeLimit(ctx_s, 1024), WOLFSSL_SUCCESS); + ExpectNotNull(ssl_c = wolfSSL_new(ctx_c)); + ExpectNotNull(ssl_s = wolfSSL_new(ctx_s)); + /* Rebuilt by hand rather than by test_memio_setup(), so the memio + * contexts the harness would have attached have to be attached here. */ + wolfSSL_SetIOWriteCtx(ssl_c, &test_ctx); + wolfSSL_SetIOReadCtx(ssl_c, &test_ctx); + wolfSSL_SetIOWriteCtx(ssl_s, &test_ctx); + wolfSSL_SetIOReadCtx(ssl_s, &test_ctx); + if (ssl_c != NULL) + ExpectIntEQ(ssl_c->recordSizeLimit, 512); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->recordSizeLimit, 1024); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + if (ssl_c != NULL) + ExpectIntEQ(ssl_c->peerRecordSizeLimit, 1024); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->peerRecordSizeLimit, 512); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); ctx_s = NULL; +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_tls.h b/tests/api/test_tls.h index 6c3fda89962..6c8e3492eda 100644 --- a/tests/api/test_tls.h +++ b/tests/api/test_tls.h @@ -66,6 +66,7 @@ int test_record_size_matches_build_message(void); int test_record_size_preserves_build_msg_state(void); int test_record_size_cache_invalidated_on_renegotiation(void); int test_wolfSSL_get_shared_ciphers(void); +int test_tls12_record_size_limit(void); #define TEST_TLS_DECLS \ TEST_DECL_GROUP("tls", test_utils_memio_move_message), \ @@ -114,6 +115,7 @@ int test_wolfSSL_get_shared_ciphers(void); test_record_size_preserves_build_msg_state), \ TEST_DECL_GROUP("tls", \ test_record_size_cache_invalidated_on_renegotiation), \ - TEST_DECL_GROUP("tls", test_wolfSSL_get_shared_ciphers) + TEST_DECL_GROUP("tls", test_wolfSSL_get_shared_ciphers), \ + TEST_DECL_GROUP("tls", test_tls12_record_size_limit) #endif /* TESTS_API_TEST_TLS_H */ diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index b387e5beb92..7b5b5702f1c 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -5384,6 +5384,148 @@ int test_tls13_early_data_0rtt_replay(void) * has not called wolfSSL_set_max_early_data must not advertise 0-RTT in its * NewSessionTicket. Fails without the ctx->maxEarlyDataSz=0 default fix * because the old default was MAX_EARLY_DATA_SZ (4096). */ +/* A 0-RTT write larger than the server's record_size_limit must still get + * through. The client sends early data before it has seen + * EncryptedExtensions, so it cannot have sized those records to a limit it + * has not been told - RFC 8449 does not carry the value in the ticket - and a + * server that enforced its limit there would fail the handshake with + * record_overflow for something the peer had no way to bound. */ +/* The context level setter, the inheritance it relies on, and what survives a + * wolfSSL_clear(). Configuration set on the context has to reach objects made + * from it, and a recycled object has to forget the peer's limit while keeping + * its own. */ +int test_tls13_record_size_limit_ctx(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_RECORD_SIZE_LIMIT) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_CERTS) && \ + !defined(NO_RSA) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_FILESYSTEM) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + + /* Argument checking on the context setter, including the 0 opt-out. */ + ExpectIntEQ(wolfSSL_CTX_UseRecordSizeLimit(NULL, 512), BAD_FUNC_ARG); + + ExpectNotNull(ctx_c = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectIntEQ(wolfSSL_CTX_UseRecordSizeLimit(ctx_c, 63), BAD_FUNC_ARG); + ExpectIntEQ(wolfSSL_CTX_UseRecordSizeLimit(ctx_c, + WOLFSSL_RECORD_SIZE_LIMIT_MAX + 1), BAD_FUNC_ARG); + ExpectIntEQ(wolfSSL_CTX_UseRecordSizeLimit(ctx_c, + WOLFSSL_RECORD_SIZE_LIMIT_OFF), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_UseRecordSizeLimit(ctx_c, 700), WOLFSSL_SUCCESS); + /* test_memio_setup() only configures a context it creates itself, so this + * pre-made one needs the CA and the memio transport wired up by hand. */ + ExpectTrue(wolfSSL_CTX_load_verify_locations(ctx_c, caCertFile, 0) + == WOLFSSL_SUCCESS); + wolfSSL_SetIORecv(ctx_c, test_memio_read_cb); + wolfSSL_SetIOSend(ctx_c, test_memio_write_cb); + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + + /* Inherited by an object made after the context was configured. */ + if (ssl_c != NULL) + ExpectIntEQ(ssl_c->recordSizeLimit, 700); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->peerRecordSizeLimit, 700); + ExpectIntEQ(wolfSSL_GetMaxOutputSize(ssl_s), 700); + + /* Recycling the object drops what the peer said but keeps this end's own + * configuration, which is not part of the connection. */ + ExpectIntEQ(wolfSSL_clear(ssl_c), WOLFSSL_SUCCESS); + if (ssl_c != NULL) { + ExpectIntEQ(ssl_c->peerRecordSizeLimit, 0); + ExpectIntEQ(ssl_c->recordSizeLimit, 700); + } + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +int test_tls13_record_size_limit_early_data(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_TLS13) && defined(HAVE_RECORD_SIZE_LIMIT) && \ + defined(WOLFSSL_EARLY_DATA) && defined(HAVE_SESSION_TICKET) && \ + !defined(WOLFSSL_NO_DEF_TICKET_ENC_CB) && !defined(NO_CERTS) && \ + !defined(NO_RSA) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_FILESYSTEM) + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL_SESSION *sess = NULL; + char buf[64]; + char early[1024]; + char readBuf[sizeof(early)]; + int written = 0; + int nread = 0; + + XMEMSET(early, 'E', sizeof(early)); + + /* First connection: get a ticket that allows 0-RTT. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_UseRecordSizeLimit(ssl_s, 512), WOLFSSL_SUCCESS); + /* The return convention is build dependent - this returns 0 on success + * unless OPENSSL_EXTRA or WOLFSSL_ERROR_CODE_OPENSSL is defined, when it + * returns WOLFSSL_SUCCESS - so assert the effect instead. */ + (void)wolfSSL_set_max_early_data(ssl_s, (unsigned int)sizeof(early) * 4); + ExpectIntEQ(wolfSSL_get_max_early_data(ssl_s), (int)sizeof(early) * 4); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_read(ssl_c, buf, sizeof(buf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + ExpectNotNull(sess = wolfSSL_get1_session(ssl_c)); + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); ctx_s = NULL; + + /* Resume and write early data twice the server's 512-byte limit. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_UseRecordSizeLimit(ssl_s, 512), WOLFSSL_SUCCESS); + /* The return convention is build dependent - this returns 0 on success + * unless OPENSSL_EXTRA or WOLFSSL_ERROR_CODE_OPENSSL is defined, when it + * returns WOLFSSL_SUCCESS - so assert the effect instead. */ + (void)wolfSSL_set_max_early_data(ssl_s, (unsigned int)sizeof(early) * 4); + ExpectIntEQ(wolfSSL_get_max_early_data(ssl_s), (int)sizeof(early) * 4); + ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_write_early_data(ssl_c, early, (int)sizeof(early), + &written), (int)sizeof(early)); + + /* The point of the test. Before the 0-RTT carve-out this first read + * failed with LENGTH_ERROR - the record-size check rejecting a 1024-byte + * early-data record against the server's 512-byte limit, a limit the + * client could not have known. It now reports WANT_READ instead: the + * record was accepted and the server simply wants more input. Driving + * the rest of the exchange is left to test_tls13_early_data; what is + * asserted here is that the overflow never happens. */ + (void)wolfSSL_read_early_data(ssl_s, readBuf, (int)sizeof(readBuf), + &nread); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->error, WC_NO_ERR_TRACE(WANT_READ)); + + wolfSSL_SESSION_free(sess); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + int test_tls13_0rtt_default_off(void) { EXPECT_DECLS; @@ -7091,7 +7233,7 @@ int test_tls13_warning_alert_is_fatal(void) * rejected with unsupported_extension (RFC 8446 Sec. 4.2). The client MUST * abort the handshake when it receives an extension it did not advertise. */ - int test_tls13_unknown_ext_rejected(void) +int test_tls13_unknown_ext_rejected(void) { EXPECT_DECLS; #if defined(WOLFSSL_TLS13) && defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ @@ -7324,6 +7466,120 @@ int test_tls13_hrr_recognized_ext_downgrade(void) /* Test that wolfSSL_set1_sigalgs_list() is honored in TLS 1.3 */ +/* Test the signature_algorithms_cert extension wolfSSL sends. + * + * RFC 8446 Sect. 4.2.3: when the extension is absent, signature_algorithms + * covers certificates too, so it is offered only when the application sets a + * distinct list. A client offers it in the ClientHello and a server in the + * CertificateRequest; the receiving end records it in ssl->certHashSigAlgo. + * "RSA+SHA256:ECDSA+SHA256" encodes as rsa_pkcs1_sha256 (0x0401) then + * ecdsa_secp256r1_sha256 (0x0403). */ +int test_tls13_sigalgs_cert_offered(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + !defined(NO_CERTS) && !defined(NO_RSA) && defined(HAVE_ECC) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + defined(OPENSSL_EXTRA) && !defined(NO_FILESYSTEM) && \ + !defined(WOLFSSL_NO_SIGALG) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + static const char list[] = "RSA+SHA256:ECDSA+SHA256"; + static const byte expected[] = { 0x04, 0x01, 0x04, 0x03 }; + + /* Argument checking. */ + ExpectIntEQ(wolfSSL_CTX_set1_sigalgs_cert_list(NULL, list), + WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_set1_sigalgs_cert_list(NULL, list), WOLFSSL_FAILURE); + + /* Set on the context, then inherited by the object. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_set1_sigalgs_cert_list(ctx_c, NULL), + WOLFSSL_FAILURE); + /* An unparsable list fails and leaves nothing set. */ + ExpectIntEQ(wolfSSL_CTX_set1_sigalgs_cert_list(ctx_c, "NOSUCHALG+SHA256"), + WOLFSSL_FAILURE); + if (ctx_c != NULL) + ExpectIntEQ(ctx_c->ourCertSigAlgoSz, 0); + ExpectIntEQ(wolfSSL_CTX_set1_sigalgs_cert_list(ctx_c, list), + WOLFSSL_SUCCESS); + if (ctx_c != NULL) { + ExpectIntEQ(ctx_c->ourCertSigAlgoSz, sizeof(expected)); + ExpectIntEQ(XMEMCMP(ctx_c->ourCertSigAlgo, expected, + sizeof(expected)), 0); + } + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + /* An object made after the context was set inherits the list. */ + ExpectNotNull(ssl_c = wolfSSL_new(ctx_c)); + if (ssl_c != NULL) + ExpectIntEQ(ssl_c->ourCertSigAlgoSz, sizeof(expected)); + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); ctx_s = NULL; + + /* Unset: the extension is not offered in either direction. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->certHashSigAlgoSz, 0); + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); ctx_s = NULL; + + /* Client offers it in the ClientHello; the server records it. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_set1_sigalgs_cert_list(ssl_c, list), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + if (ssl_s != NULL) { + ExpectIntEQ(ssl_s->certHashSigAlgoSz, sizeof(expected)); + ExpectIntEQ(XMEMCMP(ssl_s->certHashSigAlgo, expected, + sizeof(expected)), 0); + } + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); ctx_s = NULL; + + /* Server offers it in the CertificateRequest; the client records it. + * This is the direction that was previously parse-only. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + if (EXPECT_SUCCESS()) { + wolfSSL_set_verify(ssl_s, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, cliCertFile, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_use_certificate_file(ssl_c, cliCertFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_use_PrivateKey_file(ssl_c, cliKeyFile, + CERT_FILETYPE), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set1_sigalgs_cert_list(ssl_s, list), + WOLFSSL_SUCCESS); + } + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + if (ssl_c != NULL) { + ExpectIntEQ(ssl_c->certHashSigAlgoSz, sizeof(expected)); + ExpectIntEQ(XMEMCMP(ssl_c->certHashSigAlgo, expected, + sizeof(expected)), 0); + } + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + int test_tls13_cert_req_sigalgs(void) { EXPECT_DECLS; @@ -7585,12 +7841,37 @@ int test_tls13_sha1_cert_chain(void) ExpectIntEQ(wolfSSL_use_certificate_chain_file(ssl_s, sha1CertFile), WOLFSSL_SUCCESS); if (EXPECT_SUCCESS()) { - ssl_c->certHashSigAlgo[0] = sha_mac; - ssl_c->certHashSigAlgo[1] = rsa_sa_algo; - ssl_c->certHashSigAlgoSz = 2; + ssl_c->ourCertSigAlgo[0] = sha_mac; + ssl_c->ourCertSigAlgo[1] = rsa_sa_algo; + ssl_c->ourCertSigAlgoSz = 2; } ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); - ExpectIntEQ(ssl_s->certHashSigAlgoSz, 2); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->certHashSigAlgoSz, 2); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); ctx_s = NULL; + + /* SHA-1 willingness is per signature scheme, not per digest. This client + * offers ecdsa_sha1 in signature_algorithms_cert and nothing else, so it + * has not agreed to accept the RSA SHA-1 leaf the server holds, even + * though the digests match. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + if (EXPECT_SUCCESS()) + wolfSSL_set_verify(ssl_c, WOLFSSL_VERIFY_NONE, NULL); + ExpectIntEQ(wolfSSL_use_certificate_chain_file(ssl_s, sha1CertFile), + WOLFSSL_SUCCESS); + if (EXPECT_SUCCESS()) { + ssl_c->ourCertSigAlgo[0] = sha_mac; + ssl_c->ourCertSigAlgo[1] = ecc_dsa_sa_algo; + ssl_c->ourCertSigAlgoSz = 2; + } + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(ssl_s->error, WC_NO_ERR_TRACE(MATCH_SUITE_ERROR)); wolfSSL_free(ssl_c); ssl_c = NULL; wolfSSL_free(ssl_s); ssl_s = NULL; @@ -7612,9 +7893,9 @@ int test_tls13_sha1_cert_chain(void) ssl_c->suites->hashSigAlgo[ssl_c->suites->hashSigAlgoSz++] = sha_mac; ssl_c->suites->hashSigAlgo[ssl_c->suites->hashSigAlgoSz++] = rsa_sa_algo; - ssl_c->certHashSigAlgo[0] = sha256_mac; - ssl_c->certHashSigAlgo[1] = rsa_sa_algo; - ssl_c->certHashSigAlgoSz = 2; + ssl_c->ourCertSigAlgo[0] = sha256_mac; + ssl_c->ourCertSigAlgo[1] = rsa_sa_algo; + ssl_c->ourCertSigAlgoSz = 2; } ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); ExpectIntEQ(ssl_s->error, WC_NO_ERR_TRACE(MATCH_SUITE_ERROR)); @@ -8981,6 +9262,1865 @@ int test_tls13_client_cookie_too_big(void) return EXPECT_RESULT(); } +/* Test the signed_certificate_timestamp extension (RFC 6962 Sect. 3.3). + * + * A client asks with an empty extension and the server answers with a + * SignedCertificateTimestampList, in the ServerHello at TLS 1.2. wolfSSL + * carries the blob and leaves validation to the application, so the test + * checks delivery and framing rather than any timestamp being genuine. */ +/* An SCT list large enough that its extension cannot fit the 2-byte + * extensions-block length must be refused while sizing the message, not + * wrapped onto the wire. Asserting the specific error matters: a silent wrap + * would also end in a failed handshake, so "it failed" proves nothing. */ +/* A server must not accept a SignedCertificateTimestampList it never asked + * for. The server's own response extension sits on the same list node the + * "did we request this" lookup consults, so without a role check the lookup + * takes that self-push as proof of a request and stores an unsolicited list + * arriving in a client's Certificate message. */ +/* The response framing walk in TLSX_SCTS_Parse() is the attacker controlled + * path: a client that asked for SCTs hands whatever the server sent straight + * into it. Every rejection branch is driven here, including the one where a + * word16 offset sum would wrap at the top of the extension. */ +/* What a recycled object does with its SCT list. A snapshot adopted from the + * context has to go, or a context whose list was rotated never reaches an + * object that has already served one handshake; a list the application set on + * the object is that object's configuration and has to stay. */ +int test_tls13_sct_clear_ctx_snapshot(void) +{ + EXPECT_DECLS; +#if defined(HAVE_SIGNED_CERT_TIMESTAMP) && !defined(WOLFSSL_NO_TLS12) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_CERTS) && \ + !defined(NO_RSA) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_FILESYSTEM) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + /* Two distinguishable lists, one 8 byte SerializedSCT each. */ + static const unsigned char first[] = { + 0x00, 0x0a, 0x00, 0x08, 0xde, 0xad, 0xbe, 0xef, 1, 2, 3, 4 + }; + static const unsigned char second[] = { + 0x00, 0x0a, 0x00, 0x08, 0xfe, 0xed, 0xfa, 0xce, 5, 6, 7, 8 + }; + static const unsigned char ownList[] = { + 0x00, 0x0a, 0x00, 0x08, 0xab, 0xcd, 0xef, 0x01, 9, 8, 7, 6 + }; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_set_signed_cert_timestamp_list(ctx_s, first, + (unsigned short)sizeof(first)), WOLFSSL_SUCCESS); + + /* The server adopts the context's list while answering the request. */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + if (ssl_s != NULL) { + ExpectIntEQ(ssl_s->sctListSz, (word16)sizeof(first)); + ExpectIntEQ(ssl_s->sctListFromCtx, 1); + } + + /* Rotate the context's list and recycle the object: the stale snapshot + * must not survive, or the rotation would never reach this connection. */ + ExpectIntEQ(wolfSSL_CTX_set_signed_cert_timestamp_list(ctx_s, second, + (unsigned short)sizeof(second)), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_clear(ssl_s), WOLFSSL_SUCCESS); + if (ssl_s != NULL) { + ExpectNull(ssl_s->sctList); + ExpectIntEQ(ssl_s->sctListSz, 0); + } + + /* A list set on the object is configuration, and outlives a reset. */ + ExpectIntEQ(wolfSSL_set_signed_cert_timestamp_list(ssl_s, ownList, + (unsigned short)sizeof(ownList)), WOLFSSL_SUCCESS); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->sctListFromCtx, 0); + ExpectIntEQ(wolfSSL_clear(ssl_s), WOLFSSL_SUCCESS); + if (ssl_s != NULL) { + ExpectNotNull(ssl_s->sctList); + ExpectIntEQ(ssl_s->sctListSz, (word16)sizeof(ownList)); + ExpectIntEQ(XMEMCMP(ssl_s->sctList, ownList, sizeof(ownList)), 0); + } + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +int test_tls13_sct_response_framing(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_SIGNED_CERT_TIMESTAMP) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_CERTS) && \ + !defined(NO_RSA) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_FILESYSTEM) + struct { + const char* desc; + const byte* body; + word16 bodySz; + int expect; + } cases[8]; + /* outer list length 10, one 8 byte SerializedSCT: accepted. */ + static const byte ok[] = { 0x00, 0x0a, 0x00, 0x08, + 1, 2, 3, 4, 5, 6, 7, 8 }; + /* Too short to hold even the outer length. */ + static const byte runt[] = { 0x00 }; + /* Outer length of zero is not a list. */ + static const byte zeroList[] = { 0x00, 0x00 }; + /* Outer length disagrees with the extension length. */ + static const byte outerLong[] = { 0x00, 0x20, 0x00, 0x08, + 1, 2, 3, 4, 5, 6, 7, 8 }; + /* An entry whose length runs past the end of the list. */ + static const byte entryLong[] = { 0x00, 0x0a, 0x00, 0x40, + 1, 2, 3, 4, 5, 6, 7, 8 }; + /* A zero length SerializedSCT. */ + static const byte entryZero[] = { 0x00, 0x04, 0x00, 0x00 }; + /* Two entries, the second truncated to a single length byte. */ + static const byte trailing[] = { 0x00, 0x07, 0x00, 0x02, 1, 2, 0x00 }; + /* Entry length 0xFFFF: offset + sctSz overflows a word16, which is what + * the widened comparison in the walk exists to catch. */ + static const byte wrap[] = { 0x00, 0x04, 0xff, 0xff }; + size_t i; + + cases[0].desc = "well formed"; cases[0].body = ok; + cases[0].bodySz = (word16)sizeof(ok); cases[0].expect = 0; + cases[1].desc = "runt"; cases[1].body = runt; + cases[1].bodySz = (word16)sizeof(runt); cases[1].expect = 1; + cases[2].desc = "zero list"; cases[2].body = zeroList; + cases[2].bodySz = (word16)sizeof(zeroList); cases[2].expect = 1; + cases[3].desc = "outer too long"; cases[3].body = outerLong; + cases[3].bodySz = (word16)sizeof(outerLong); cases[3].expect = 1; + cases[4].desc = "entry too long"; cases[4].body = entryLong; + cases[4].bodySz = (word16)sizeof(entryLong); cases[4].expect = 1; + cases[5].desc = "zero entry"; cases[5].body = entryZero; + cases[5].bodySz = (word16)sizeof(entryZero); cases[5].expect = 1; + cases[6].desc = "trailing stub"; cases[6].body = trailing; + cases[6].bodySz = (word16)sizeof(trailing); cases[6].expect = 1; + cases[7].desc = "offset wrap"; cases[7].body = wrap; + cases[7].bodySz = (word16)sizeof(wrap); cases[7].expect = 1; + + for (i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) { + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte ext[64]; + int ret; + + /* A handshake first, so the client has actually asked for SCTs and + * the response is not turned away as unsolicited before the framing + * is ever looked at. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + ext[0] = 0x00; ext[1] = 0x12; + ext[2] = (byte)(cases[i].bodySz >> 8); + ext[3] = (byte)(cases[i].bodySz & 0xff); + XMEMCPY(ext + 4, cases[i].body, cases[i].bodySz); + + ret = TLSX_Parse(ssl_c, ext, (word16)(4 + cases[i].bodySz), + certificate, NULL); + if (cases[i].expect == 0) + ExpectIntEQ(ret, 0); + else + ExpectIntEQ(ret, WC_NO_ERR_TRACE(BUFFER_ERROR)); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + } +#endif + return EXPECT_RESULT(); +} + +int test_tls13_sct_unsolicited_at_server(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_SIGNED_CERT_TIMESTAMP) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_CERTS) && !defined(NO_RSA) && \ + !defined(NO_FILESYSTEM) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + /* A well formed list: outer length 10, one 8 byte SerializedSCT. */ + static const byte sct[] = { + 0x00, 0x0a, 0x00, 0x08, + 0xde, 0xad, 0xbe, 0xef, 0x01, 0x02, 0x03, 0x04 + }; + static const byte list[] = { + 0x00, 0x0a, 0x00, 0x08, + 0xde, 0xad, 0xbe, 0xef, 0x01, 0x02, 0x03, 0x04 + }; + byte ext[4 + sizeof(list)]; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + ExpectTrue(wolfSSL_CTX_use_certificate_file(ctx, svrCertFile, + CERT_FILETYPE)); + ExpectTrue(wolfSSL_CTX_use_PrivateKey_file(ctx, svrKeyFile, + CERT_FILETYPE)); + /* Give the server a list of its own, which is what puts the extension on + * ssl->extensions as a response and made the old lookup pass. */ + ExpectIntEQ(wolfSSL_CTX_set_signed_cert_timestamp_list(ctx, sct, + (unsigned short)sizeof(sct)), WOLFSSL_SUCCESS); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + /* The client asks, so the server pushes its own response. */ + if (ssl != NULL) { + static const byte req[] = { 0x00, 0x12, 0x00, 0x00 }; + + ExpectIntEQ(TLSX_Parse(ssl, req, (word16)sizeof(req), client_hello, + (Suites*)WOLFSSL_SUITES(ssl)), 0); + ExpectIntEQ(wolfSSL_signed_cert_timestamp_requested(ssl), 1); + } + + /* Now the client sends one back in its own Certificate message. */ + ext[0] = 0x00; ext[1] = 0x12; + ext[2] = 0x00; ext[3] = (byte)sizeof(list); + XMEMCPY(ext + 4, list, sizeof(list)); + ExpectIntEQ(TLSX_Parse(ssl, ext, (word16)sizeof(ext), certificate, NULL), + WC_NO_ERR_TRACE(UNSUPPORTED_EXTENSION)); + /* And nothing was stored. */ + if (ssl != NULL) + ExpectIntEQ(ssl->peerSctListSz, 0); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_tls13_sct_oversize_list(void) +{ + EXPECT_DECLS; +#if defined(HAVE_SIGNED_CERT_TIMESTAMP) && !defined(WOLFSSL_NO_TLS12) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_CERTS) && \ + !defined(NO_RSA) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_FILESYSTEM) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + byte* big = NULL; + const word16 bigSz = 0xFFFF; + + /* Well formed framing at the maximum the setter accepts: an outer list + * length followed by one SerializedSCT filling the rest. The extension + * around it needs 4 more bytes than the block length can express. */ + ExpectNotNull(big = (byte*)XMALLOC(bigSz, NULL, DYNAMIC_TYPE_TMP_BUFFER)); + if (big != NULL) { + XMEMSET(big, 0xAB, bigSz); + c16toa((word16)(bigSz - OPAQUE16_LEN), big); + c16toa((word16)(bigSz - (2 * OPAQUE16_LEN)), big + OPAQUE16_LEN); + } + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_set_signed_cert_timestamp_list(ssl_s, big, bigSz), + WOLFSSL_SUCCESS); + + /* The server cannot build a ServerHello carrying it. */ + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->error, WC_NO_ERR_TRACE(BUFFER_E)); + + XFREE(big, NULL, DYNAMIC_TYPE_TMP_BUFFER); + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* RFC 8446 Sect. 4.2 permits signed_certificate_timestamp in a + * CertificateRequest, where a server asks the client to staple SCTs to its + * own Certificate. wolfSSL does not produce those, but it must not turn a + * legal message into a fatal alert - before the extension was implemented the + * type was unknown there and ignored. */ +int test_tls13_sct_in_cert_request(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_SIGNED_CERT_TIMESTAMP) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_CERTS) && !defined(NO_RSA) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + /* signed_certificate_timestamp(18), empty - the ask carries no data. */ + static const byte empty[] = { 0x00, 0x12, 0x00, 0x00 }; + /* The same with a stray payload byte. */ + static const byte bodied[] = { 0x00, 0x12, 0x00, 0x01, 0x00 }; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + /* A CertificateRequest counts as a request, so TLSX_Parse() requires the + * suites argument. */ + ExpectIntEQ(TLSX_Parse(ssl, empty, (word16)sizeof(empty), + certificate_request, (Suites*)WOLFSSL_SUITES(ssl)), 0); + ExpectIntEQ(TLSX_Parse(ssl, bodied, (word16)sizeof(bodied), + certificate_request, (Suites*)WOLFSSL_SUITES(ssl)), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +int test_tls13_signed_cert_timestamp(void) +{ + EXPECT_DECLS; +#if defined(HAVE_SIGNED_CERT_TIMESTAMP) && !defined(WOLFSSL_NO_TLS12) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_CERTS) && \ + !defined(NO_RSA) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_FILESYSTEM) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + /* list length 10, then one 8 byte SerializedSCT. */ + static const unsigned char sctList[] = { + 0x00, 0x0a, 0x00, 0x08, + 0xde, 0xad, 0xbe, 0xef, 0x01, 0x02, 0x03, 0x04 + }; + const unsigned char* got = NULL; + + /* These carry BoringSSL's names, which document one on success and zero + * on error, so a failure has to be falsy: a negative code would slip past + * the `if (!...)` a caller ported from there writes. */ + ExpectIntEQ(wolfSSL_CTX_set_signed_cert_timestamp_list(NULL, sctList, + (unsigned short)sizeof(sctList)), WOLFSSL_FAILURE); + ExpectFalse(wolfSSL_CTX_set_signed_cert_timestamp_list(NULL, sctList, + (unsigned short)sizeof(sctList))); + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_set_signed_cert_timestamp_list(ctx_s, NULL, 4), + WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_CTX_set_signed_cert_timestamp_list(ctx_s, sctList, 0), + WOLFSSL_FAILURE); + ExpectIntEQ(wolfSSL_CTX_set_signed_cert_timestamp_list(ctx_s, sctList, + (unsigned short)sizeof(sctList)), WOLFSSL_SUCCESS); + /* The context setter only reaches objects made after it, and memio built + * these already, so set it on the object too. */ + ExpectIntEQ(wolfSSL_set_signed_cert_timestamp_list(ssl_s, sctList, + (unsigned short)sizeof(sctList)), WOLFSSL_SUCCESS); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* The server saw the request and the client got the list back. */ + if (ssl_s != NULL) + ExpectIntEQ(wolfSSL_signed_cert_timestamp_requested(ssl_s), 1); + ExpectIntEQ(wolfSSL_get0_signed_cert_timestamp_list(ssl_c, &got), + (int)sizeof(sctList)); + ExpectNotNull(got); + if (got != NULL) + ExpectIntEQ(XMEMCMP(got, sctList, sizeof(sctList)), 0); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); ctx_s = NULL; + + /* No list configured: the client still asks, and gets nothing back. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + if (ssl_s != NULL) + ExpectIntEQ(wolfSSL_signed_cert_timestamp_requested(ssl_s), 1); + got = NULL; + ExpectIntEQ(wolfSSL_get0_signed_cert_timestamp_list(ssl_c, &got), 0); + ExpectNull(got); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); ctx_s = NULL; + +#if defined(HAVE_SESSION_TICKET) && !defined(NO_SESSION_CACHE) + /* RFC 6962 Sect. 3.3.1: a resumed session sends no Certificate, so the + * server does not answer with timestamps. RFC 9162 makes this a MUST NOT + * for the successor extension. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_set_signed_cert_timestamp_list(ssl_s, sctList, + (unsigned short)sizeof(sctList)), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + got = NULL; + ExpectIntEQ(wolfSSL_get0_signed_cert_timestamp_list(ssl_c, &got), + (int)sizeof(sctList)); + if (EXPECT_SUCCESS()) { + WOLFSSL_SESSION* sess = wolfSSL_get1_session(ssl_c); + + ExpectNotNull(sess); + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, + &ssl_s, wolfTLSv1_2_client_method, wolfTLSv1_2_server_method), 0); + ExpectIntEQ(wolfSSL_set_signed_cert_timestamp_list(ssl_s, sctList, + (unsigned short)sizeof(sctList)), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_session(ssl_c, sess), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + if (ssl_c != NULL && wolfSSL_session_reused(ssl_c)) { + got = NULL; + ExpectIntEQ(wolfSSL_get0_signed_cert_timestamp_list(ssl_c, &got), + 0); + } + wolfSSL_SESSION_free(sess); + } + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); ctx_s = NULL; +#endif /* HAVE_SESSION_TICKET && !NO_SESSION_CACHE */ + +#endif + return EXPECT_RESULT(); +} + +/* Drive one connection, inject a record of a chosen wire length into the + * buffer the client reads from, and report the error the client ends with. + * + * No co-operating peer will ever send an oversized record, so it is written + * straight into the memio buffer. RFC 8449's check lives in + * GetRecordHeader(), before decryption, so a header claiming the length is + * enough and the body can be filler. + * + * Returns the client's raw error, or a negative setup failure. + */ +#if defined(WOLFSSL_TLS13) && defined(HAVE_RECORD_SIZE_LIMIT) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_CERTS) && \ + !defined(NO_RSA) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_FILESYSTEM) +/* Inject one record of ourLimit + content type + the negotiated AEAD tag + + * overBy bytes, and report the error the client raised. Those are the whole + * of a TLS 1.3 record's expansion over the payload limit, so overBy == 0 is + * exactly the largest record the limit permits and overBy == 1 is the first + * one it does not. */ +static int RecordOfSize(word16 ourLimit, int overBy, int* alertSent) +{ + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + char out[128]; + int err = 0; + int before; + word32 bodySz; + word32 recSz; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + if (test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method) != 0) { + err = -1; + goto done; + } + if (wolfSSL_UseRecordSizeLimit(ssl_c, ourLimit) != WOLFSSL_SUCCESS || + wolfSSL_UseRecordSizeLimit(ssl_s, 4096) != WOLFSSL_SUCCESS) { + err = -2; + goto done; + } + if (test_memio_do_handshake(ssl_c, ssl_s, 10, NULL) != 0) { + err = -3; + goto done; + } + + /* Only known once a cipher suite is negotiated. */ + /* ourLimit is payload, so the largest permitted record is that plus the + * TLS 1.3 content type byte plus the AEAD tag. */ + bodySz = (word32)((int)ourLimit + 1 + ssl_c->specs.aead_mac_size + overBy); + recSz = RECORD_HEADER_SZ + bodySz; + + if ((test_ctx.c_len + (int)recSz) >= TEST_MEMIO_BUF_SZ || + test_ctx.c_msg_count >= TEST_MEMIO_MAX_MSGS) { + err = -4; + goto done; + } + + { + byte* p = test_ctx.c_buff + test_ctx.c_len; + + p[0] = application_data; + p[1] = SSLv3_MAJOR; + p[2] = TLSv1_2_MINOR; /* the wire version TLS 1.3 records carry */ + c16toa((word16)bodySz, p + 3); + XMEMSET(p + RECORD_HEADER_SZ, 0, bodySz); + + /* Register it the way test_memio_write_cb() would. */ + test_ctx.c_msg_sizes[test_ctx.c_msg_count++] = (int)recSz; + test_ctx.c_len += (int)recSz; + } + + before = test_ctx.s_len; + (void)wolfSSL_read(ssl_c, out, (int)sizeof(out)); + /* The raw error: wolfSSL_get_error() remaps this one under OPENSSL_EXTRA, + * as the SHA-1 chain test notes. */ + err = ssl_c->error; + if (alertSent != NULL) + *alertSent = (test_ctx.s_len > before); + +done: + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + return err; +} +#endif + +/* Test that a record larger than the limit this end advertised is refused. + * + * RFC 8449 Sect. 4: "A TLS endpoint that receives a record larger than its + * advertised limit MUST generate a fatal record_overflow alert." Both sides + * of the boundary are pinned, so a change that rejected legitimate records + * would fail here just as loudly as one that let oversized records through. + */ +int test_tls13_record_size_limit_overflow(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_RECORD_SIZE_LIMIT) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_CERTS) && \ + !defined(NO_RSA) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_FILESYSTEM) + const word16 ourLimit = 512; + int alertSent = 0; + int err; + + /* One byte past what the negotiated cipher can expand the limit to: + * refused at the record header, with an alert back to the peer. */ + err = RecordOfSize(ourLimit, 1, &alertSent); + ExpectIntEQ(err, WC_NO_ERR_TRACE(LENGTH_ERROR)); + ExpectIntEQ(alertSent, 1); + + /* Exactly at the allowance: the header check must let this through, so + * it fails later as undecryptable filler instead. Without this the test + * would still pass if the limit were applied far too strictly. */ + err = RecordOfSize(ourLimit, 0, NULL); + ExpectIntNE(err, WC_NO_ERR_TRACE(LENGTH_ERROR)); +#endif + return EXPECT_RESULT(); +} + +/* Test the record_size_limit extension (RFC 8449). + * + * The client states the largest record it will accept, the server echoes its + * own in EncryptedExtensions, and from then on each end caps what it sends to + * the other's limit. The count covers the whole TLS 1.3 TLSInnerPlaintext, so + * a record's payload is one byte short of the limit. */ +/* A small negotiated record_size_limit forces EncryptedExtensions and + * NewSessionTicket across several records. Self-checking: the client enforces + * the limit it advertised on receipt, so an unfragmented message from the + * server fails the handshake or the ticket read right here. */ +/* RFC 8449 Sect. 5: a client that receives both max_fragment_length and + * record_size_limit must abort - whichever order the server put them in. The + * order is the point: a check made while parsing one extension cannot see the + * other, so this fails in one direction only if it is done too early. + * + * A handshake runs first because the client only pushes record_size_limit + * into its extension list while building the ClientHello, and both have to be + * on that list or the unsolicited-response check rejects the message before + * the two ever meet. The synthetic EncryptedExtensions is then fed to the + * client object directly; no wolfSSL server would send one. + */ +int test_tls13_record_size_limit_both_exts(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_RECORD_SIZE_LIMIT) && \ + defined(HAVE_MAX_FRAGMENT) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_CERTS) && \ + !defined(NO_RSA) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_FILESYSTEM) + /* record_size_limit(28) = 512, then max_fragment_length(1) = 2^9. */ + static const byte rslFirst[] = { + 0x00, 0x1c, 0x00, 0x02, 0x02, 0x00, + 0x00, 0x01, 0x00, 0x01, 0x01 + }; + /* The same two the other way round. */ + static const byte mflFirst[] = { + 0x00, 0x01, 0x00, 0x01, 0x01, + 0x00, 0x1c, 0x00, 0x02, 0x02, 0x00 + }; + const byte* order[2]; + word16 orderSz[2]; + int i; + + order[0] = rslFirst; orderSz[0] = (word16)sizeof(rslFirst); + order[1] = mflFirst; orderSz[1] = (word16)sizeof(mflFirst); + + for (i = 0; i < 2; i++) { + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_UseRecordSizeLimit(ssl_c, 512), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseMaxFragment(ssl_c, WOLFSSL_MFL_2_9), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseRecordSizeLimit(ssl_s, 512), WOLFSSL_SUCCESS); + + /* A client may offer both; RFC 8449 Sect. 5 has the server answer + * with record_size_limit and ignore the max_fragment_length, so this + * handshake completes. */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + ExpectIntEQ(TLSX_Parse(ssl_c, order[i], orderSz[i], + encrypted_extensions, NULL), + WC_NO_ERR_TRACE(INVALID_PARAMETER)); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + } +#endif + return EXPECT_RESULT(); +} + +/* Which of the two record-size extensions governs when both are in play. + * The default limit must stand aside for an application that explicitly asked + * for max_fragment_length, because RFC 8449 Sect. 5 has the server ignore + * max_fragment_length whenever both appear - so advertising the default + * alongside would quietly disable the extension the application chose. An + * explicit record_size_limit still wins. */ +int test_tls13_record_size_limit_vs_mfl(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_RECORD_SIZE_LIMIT) && \ + defined(HAVE_MAX_FRAGMENT) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_CERTS) && \ + !defined(NO_RSA) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_FILESYSTEM) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + + /* Only max_fragment_length asked for: the default stays quiet and + * max_fragment_length negotiates as it always did. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_UseMaxFragment(ssl_c, WOLFSSL_MFL_2_9), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + if (ssl_c != NULL) { + ExpectIntEQ(ssl_c->max_fragment, 512); + ExpectIntEQ(ssl_c->peerRecordSizeLimit, 0); + } + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); ctx_s = NULL; + + /* Both asked for explicitly: RFC 8449 Sect. 5 gives it to + * record_size_limit and the server drops the max_fragment_length. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_UseMaxFragment(ssl_c, WOLFSSL_MFL_2_9), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseRecordSizeLimit(ssl_c, 700), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->peerRecordSizeLimit, 700); + if (ssl_c != NULL) + ExpectIntEQ(ssl_c->max_fragment, MAX_RECORD_SIZE); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +int test_tls13_record_size_limit_fragment(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_RECORD_SIZE_LIMIT) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_CERTS) && \ + !defined(NO_RSA) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_FILESYSTEM) + /* 64 is the RFC 8449 Sect. 4 floor - every server flight has to be split + * at that size. 300 lands between the two: large enough for a handshake + * message to fit, small enough for a session ticket not to. */ + static const word16 limits[] = { 64, 300 }; + size_t i; + + for (i = 0; i < sizeof(limits) / sizeof(limits[0]); i++) { + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + char readBuf[64]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + /* Both ends: RFC 8449 Sect. 4 only negotiates the extension when + * both send it, and a server with no limit of its own stays silent. */ + ExpectIntEQ(wolfSSL_UseRecordSizeLimit(ssl_c, limits[i]), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseRecordSizeLimit(ssl_s, limits[i]), + WOLFSSL_SUCCESS); + /* Asking for a client certificate puts a CertificateRequest in the + * server's flight, which is the only message fragmented with + * hashOutput set - EncryptedExtensions is too, but NewSessionTicket + * is not, so without this the transcript-hashing side of + * SendTls13FragmentedMsg() goes unexercised. */ + ExpectTrue(wolfSSL_CTX_load_verify_locations(ctx_s, cliCertFile, 0) + == WOLFSSL_SUCCESS); + if (ssl_s != NULL) + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_PEER, NULL); + ExpectTrue(wolfSSL_use_certificate_file(ssl_c, cliCertFile, + CERT_FILETYPE) == WOLFSSL_SUCCESS); + ExpectTrue(wolfSSL_use_PrivateKey_file(ssl_c, cliKeyFile, + CERT_FILETYPE) == WOLFSSL_SUCCESS); + + /* Generous round count: at 64 bytes a certificate chain alone is + * dozens of records. */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 128, NULL), 0); + /* Proves the limit is actually in force on the server's send side + * rather than silently ignored. The limit is payload, so it is the + * output size directly. */ + ExpectIntEQ(wolfSSL_GetMaxOutputSize(ssl_s), limits[i]); + + /* Drives the post-handshake read so NewSessionTicket is parsed under + * the same enforcement. */ + ExpectIntEQ(wolfSSL_read(ssl_c, readBuf, (int)sizeof(readBuf)), -1); + ExpectIntEQ(wolfSSL_get_error(ssl_c, -1), WOLFSSL_ERROR_WANT_READ); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); + } +#endif + return EXPECT_RESULT(); +} + +int test_tls13_record_size_limit(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_RECORD_SIZE_LIMIT) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_CERTS) && \ + !defined(NO_RSA) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_FILESYSTEM) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + + /* An unsolicited response is an unsupported_extension abort: this client + * never called wolfSSL_UseRecordSizeLimit(), so it never offered one. */ + if (EXPECT_SUCCESS()) { + WOLFSSL_CTX* uctx = NULL; + WOLFSSL* ussl = NULL; + static const byte resp[] = { 0x00, 0x1c, 0x00, 0x02, 0x04, 0x00 }; + + ExpectNotNull(uctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ussl = wolfSSL_new(uctx)); + ExpectIntEQ(TLSX_Parse(ussl, resp, (word16)sizeof(resp), + encrypted_extensions, NULL), + WC_NO_ERR_TRACE(UNSUPPORTED_EXTENSION)); + wolfSSL_free(ussl); + wolfSSL_CTX_free(uctx); + } + + /* Malformed on the wire, on objects of their own: TLSX_Parse() records + * state on the object it is handed, so these must not share one with the + * handshakes below. A value under 64 is a fatal illegal_parameter + * (RFC 8449 Sect. 4) and a wrong length is a decode error. */ + if (EXPECT_SUCCESS()) { + WOLFSSL_CTX* pctx = NULL; + WOLFSSL* pssl = NULL; + static const byte tooSmall[] = { 0x00, 0x1c, 0x00, 0x02, 0x00, 0x3f }; + static const byte badLen[] = { 0x00, 0x1c, 0x00, 0x01, 0x02 }; + static const byte badLen3[] = { 0x00, 0x1c, 0x00, 0x03, 0x02, 0x00, + 0x00 }; + + ExpectNotNull(pctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + ExpectTrue(wolfSSL_CTX_use_certificate_file(pctx, svrCertFile, + CERT_FILETYPE)); + ExpectTrue(wolfSSL_CTX_use_PrivateKey_file(pctx, svrKeyFile, + CERT_FILETYPE)); + ExpectNotNull(pssl = wolfSSL_new(pctx)); + ExpectIntEQ(TLSX_Parse(pssl, tooSmall, (word16)sizeof(tooSmall), + client_hello, (Suites*)WOLFSSL_SUITES(pssl)), + WC_NO_ERR_TRACE(INVALID_PARAMETER)); + ExpectIntEQ(TLSX_Parse(pssl, badLen, (word16)sizeof(badLen), + client_hello, (Suites*)WOLFSSL_SUITES(pssl)), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + ExpectIntEQ(TLSX_Parse(pssl, badLen3, (word16)sizeof(badLen3), + client_hello, (Suites*)WOLFSSL_SUITES(pssl)), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + wolfSSL_free(pssl); + wolfSSL_CTX_free(pctx); + } + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + + /* Out of range values are refused: below the RFC minimum of 64, and + * above the TLS 1.3 maximum of 2^14+1. */ + ExpectIntEQ(wolfSSL_UseRecordSizeLimit(NULL, 512), BAD_FUNC_ARG); + ExpectIntEQ(wolfSSL_UseRecordSizeLimit(ssl_c, 63), BAD_FUNC_ARG); + /* 0 is not "too small", it is the way to stop advertising. */ + ExpectIntEQ(wolfSSL_UseRecordSizeLimit(ssl_c, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseRecordSizeLimit(ssl_c, MAX_RECORD_SIZE + 2), + BAD_FUNC_ARG); + + ExpectIntEQ(wolfSSL_UseRecordSizeLimit(ssl_c, 512), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseRecordSizeLimit(ssl_s, 1024), WOLFSSL_SUCCESS); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* Too late to change: the value was advertised and is being enforced, so + * tightening it now would reject records the peer is entitled to send. */ + ExpectIntEQ(wolfSSL_UseRecordSizeLimit(ssl_c, 256), BAD_FUNC_ARG); + ExpectIntEQ(wolfSSL_UseRecordSizeLimit(ssl_s, 256), BAD_FUNC_ARG); + + /* Each end learned the other's limit. */ + if (ssl_c != NULL) + ExpectIntEQ(ssl_c->peerRecordSizeLimit, 1024); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->peerRecordSizeLimit, 512); + + /* And caps what it sends to it. Both ends name the same number because + * the limit is a payload size at this level; the content type byte lives + * only in the extension's encoding. */ + ExpectIntEQ(wolfSSL_GetMaxOutputSize(ssl_s), 512); + ExpectIntEQ(wolfSSL_GetMaxOutputSize(ssl_c), 1024); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); ctx_s = NULL; + + /* The RFC minimum of 64 works end to end. It fragments the flight hard, + * so the exchange needs many more round trips than a normal handshake, + * which is the point of pinning it. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_UseRecordSizeLimit(ssl_c, + WOLFSSL_RECORD_SIZE_LIMIT_MIN), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseRecordSizeLimit(ssl_s, + WOLFSSL_RECORD_SIZE_LIMIT_MIN), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 200, NULL), 0); + ExpectIntEQ(wolfSSL_GetMaxOutputSize(ssl_c), + WOLFSSL_RECORD_SIZE_LIMIT_MIN); + ExpectIntEQ(wolfSSL_GetMaxOutputSize(ssl_s), + WOLFSSL_RECORD_SIZE_LIMIT_MIN); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); ctx_s = NULL; + + /* Only this end advertises. The peer answers nothing, so the limits are + * not negotiated and must not be enforced: enforcing on advertisement + * alone terminated every handshake with a peer lacking RFC 8449, which is + * how OpenSSL and BoringSSL behave today. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_UseRecordSizeLimit(ssl_c, 512), WOLFSSL_SUCCESS); + /* A limit is advertised by default now, so the server is silenced + * explicitly with 0 rather than by leaving it unset. */ + ExpectIntEQ(wolfSSL_UseRecordSizeLimit(ssl_s, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + if (ssl_c != NULL) { + ExpectIntEQ(ssl_c->recordSizeLimit, 512); + ExpectIntEQ(ssl_c->peerRecordSizeLimit, 0); + } + /* Nothing learned, so nothing capped. */ + ExpectIntEQ(wolfSSL_GetMaxOutputSize(ssl_c), MAX_RECORD_SIZE); + /* And the other direction, which is the half that looks like a bug and + * is not: the server saw the client's 512 but answered nothing, so + * RFC 8449 Sect. 4 leaves it unbound - "When the record_size_limit + * extension is negotiated, an endpoint MUST NOT generate a protected + * record with plaintext that is larger than the RecordSizeLimit value it + * receives from its peer", and "If this extension is not negotiated, + * endpoints can send records of any size permitted by the protocol". The + * duty follows negotiation, not receipt, so a server that declined to + * advertise keeps sending full records and does not retain the value. + * Retaining it would let any client shrink a server that never opted in + * down to 64-byte records. */ + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->peerRecordSizeLimit, 0); + ExpectIntEQ(wolfSSL_GetMaxOutputSize(ssl_s), MAX_RECORD_SIZE); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); ctx_s = NULL; + + /* Turned off on both ends: nothing is advertised and nothing is capped. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_UseRecordSizeLimit(ssl_c, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseRecordSizeLimit(ssl_s, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + if (ssl_c != NULL) + ExpectIntEQ(ssl_c->peerRecordSizeLimit, 0); + ExpectIntEQ(wolfSSL_GetMaxOutputSize(ssl_c), MAX_RECORD_SIZE); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); ctx_s = NULL; + + /* Left alone, both ends advertise the default and negotiate it, which is + * what makes the extension useful without the application asking. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + if (ssl_c != NULL) + ExpectIntEQ(ssl_c->peerRecordSizeLimit, + WOLFSSL_RECORD_SIZE_LIMIT_DEFAULT); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->peerRecordSizeLimit, + WOLFSSL_RECORD_SIZE_LIMIT_DEFAULT); + /* The default is a full record's worth of payload, and payload is the + * unit, so it survives the round trip unchanged. A default that cost a + * byte would fail here. */ + ExpectIntEQ(wolfSSL_GetMaxOutputSize(ssl_c), + WOLFSSL_RECORD_SIZE_LIMIT_DEFAULT); + ExpectIntEQ(wolfSSL_GetMaxOutputSize(ssl_c), MAX_RECORD_SIZE); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); ctx_s = NULL; + +#endif + return EXPECT_RESULT(); +} + +/* Test a TLS 1.3 handshake that carries a CompressedCertificate. + * + * The server pre-compresses its chain with wolfSSL_CTX_compress_certs(), the + * client advertises compress_certificate, and the handshake completes with + * the server's Certificate sent in compressed form (RFC 8879 Sect. 4). + * + * Not built for async-crypt or non-blocking-OCSP configurations: those + * deliberately do not advertise compress_certificate, because + * DoTls13CompressedCertificate() cannot carry a decompressed buffer across a + * suspension, so nothing negotiates compression there. */ +int test_tls13_sct_certificate_msg(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_SIGNED_CERT_TIMESTAMP) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_CERTS) && \ + !defined(NO_RSA) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_FILESYSTEM) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + /* list length 10, then one 8 byte SerializedSCT. */ + static const unsigned char sctList[] = { + 0x00, 0x0a, 0x00, 0x08, + 0xde, 0xad, 0xbe, 0xef, 0x01, 0x02, 0x03, 0x04 + }; + const unsigned char* got = NULL; + + /* RFC 6962 Sect. 3.3: TLS 1.2 answers in the ServerHello, TLS 1.3 in the + * end entity certificate's CertificateEntry extensions. The TLS 1.2 leg + * is test_tls13_signed_cert_timestamp(); this is the 1.3 one, which is a + * different write path entirely. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_set_signed_cert_timestamp_list(ssl_s, sctList, + (unsigned short)sizeof(sctList)), WOLFSSL_SUCCESS); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* The server saw the request and the client got the list back. */ + if (ssl_s != NULL) + ExpectIntEQ(wolfSSL_signed_cert_timestamp_requested(ssl_s), 1); + ExpectIntEQ(wolfSSL_get0_signed_cert_timestamp_list(ssl_c, &got), + (int)sizeof(sctList)); + ExpectNotNull(got); + if (got != NULL) + ExpectIntEQ(XMEMCMP(got, sctList, sizeof(sctList)), 0); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); ctx_s = NULL; + + /* No list configured: the entry's extensions field stays empty and the + * handshake is unaffected. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + got = NULL; + ExpectIntEQ(wolfSSL_get0_signed_cert_timestamp_list(ssl_c, &got), 0); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); ctx_s = NULL; +#endif + return EXPECT_RESULT(); +} + +int test_tls13_sct_cert_chain(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_SIGNED_CERT_TIMESTAMP) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_CERTS) && \ + !defined(NO_RSA) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_FILESYSTEM) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + static const unsigned char sctList[] = { + 0x00, 0x0a, 0x00, 0x08, + 0xde, 0xad, 0xbe, 0xef, 0x01, 0x02, 0x03, 0x04 + }; + const unsigned char* got = NULL; + + /* A chain, not a single certificate: the SCT goes on the end entity + * certificate only, so every certificate after it must advance to its own + * empty extensions entry. When the index stopped advancing, each chain + * certificate reused the leaf's entry - reading a buffer already freed and + * claiming bytes the message length never budgeted for. Built by hand + * because test_memio_setup() loads a single certificate, which leaves + * certChainCnt at zero and cannot reach any of that. */ + ExpectNotNull(ctx_s = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + ExpectIntEQ(wolfSSL_CTX_use_certificate_chain_file(ctx_s, + "certs/intermediate/server-chain.pem"), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx_s, "certs/server-key.pem", + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + wolfSSL_SetIORecv(ctx_s, test_memio_read_cb); + wolfSSL_SetIOSend(ctx_s, test_memio_write_cb); + + ExpectNotNull(ctx_c = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_c, caCertFile, 0), + WOLFSSL_SUCCESS); + wolfSSL_SetIORecv(ctx_c, test_memio_read_cb); + wolfSSL_SetIOSend(ctx_c, test_memio_write_cb); + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + /* The chain really is more than one certificate, or this proves nothing. */ + if (ssl_s != NULL) + ExpectIntGT(ssl_s->buffers.certChainCnt, 0); + ExpectIntEQ(wolfSSL_set_signed_cert_timestamp_list(ssl_s, sctList, + (unsigned short)sizeof(sctList)), WOLFSSL_SUCCESS); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_get0_signed_cert_timestamp_list(ssl_c, &got), + (int)sizeof(sctList)); + ExpectNotNull(got); + if (got != NULL) + ExpectIntEQ(XMEMCMP(got, sctList, sizeof(sctList)), 0); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +int test_tls13_compressed_certificate(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_CERTIFICATE_COMPRESSION) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_CERTS) && \ + !defined(NO_RSA) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_FILESYSTEM) && \ + !defined(WOLFSSL_ASYNC_CRYPT) && !defined(WOLFSSL_NONBLOCK_OCSP) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + + /* Refused, and falsy: this carries OpenSSL's SSL_CTX_compress_certs() + * name and so answers non-zero for success, zero for failure. */ + ExpectIntEQ(wolfSSL_CTX_compress_certs(NULL, WOLFSSL_CERT_COMP_ZLIB), + WOLFSSL_FAILURE); + ExpectFalse(wolfSSL_CTX_compress_certs(NULL, WOLFSSL_CERT_COMP_ZLIB)); + ExpectIntEQ(wolfSSL_CTX_compress_certs(ctx_s, WOLFSSL_CERT_COMP_BROTLI), + WOLFSSL_FAILURE); + if (ctx_s != NULL) + ExpectNull(ctx_s->certComp); + + ExpectIntEQ(wolfSSL_CTX_compress_certs(ctx_s, WOLFSSL_CERT_COMP_ZLIB), + WOLFSSL_SUCCESS); + if (ctx_s != NULL) { + ExpectNotNull(ctx_s->certComp); + ExpectIntEQ(ctx_s->certCompAlgo, WOLFSSL_CERT_COMP_ZLIB); + /* Only cached when it is actually smaller. */ + ExpectIntLT(ctx_s->certCompSz, ctx_s->certCompPlainSz); + } + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + /* The client agreed on the algorithm the server then used. */ + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->peerCertCompAlgo, WOLFSSL_CERT_COMP_ZLIB); + /* The client received the chain compressed, not plain. */ + if (ssl_c != NULL) { + ExpectIntEQ(wolfSSL_get_certificate_compression_used(ssl_c), + WOLFSSL_CERT_COMP_ZLIB); + #ifdef OPENSSL_EXTRA + /* Accessor is OPENSSL_EXTRA only; the rest of the test is not. */ + ExpectIntEQ(wolfSSL_get_verify_result(ssl_c), WOLFSSL_X509_V_OK); + #endif + } + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* Test that each extension added recently is refused in messages it is not + * listed for. + * + * Twice in developing these an extension leaked into EncryptedExtensions and + * only an interop run caught it, because a peer that ignores an unexpected + * extension will still complete the handshake. Checking the receive gate is + * the cheap half of that; a wolfSSL peer rejecting the message is what turns + * a send-side leak into a visible failure. */ +int test_tls13_new_ext_placement(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_FILESYSTEM) && !defined(NO_CERTS) && \ + (!defined(NO_RSA) || defined(HAVE_ECC)) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + Suites* suites = NULL; +#ifdef HAVE_SNI + /* server_name: CH, EE, CR. */ + static const byte sni[] = { + 0x00, 0x00, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x00, 0x0b, + 'w', 'o', 'l', 'f', 's', 's', 'l', '.', 'c', 'o', 'm' + }; +#endif +#ifdef HAVE_CERTIFICATE_COMPRESSION + /* compress_certificate: CH, CR. */ + static const byte cc[] = { 0x00, 0x1b, 0x00, 0x03, 0x02, 0x00, 0x01 }; +#endif +#ifdef HAVE_RECORD_SIZE_LIMIT + /* record_size_limit: CH, EE. */ + static const byte rsl[] = { 0x00, 0x1c, 0x00, 0x02, 0x02, 0x00 }; +#endif +#ifdef HAVE_SIGNED_CERT_TIMESTAMP + /* signed_certificate_timestamp: CH, CR, CT. Empty as a request. */ + static const byte sct[] = { 0x00, 0x12, 0x00, 0x00 }; +#endif + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + /* The certificate exists only so wolfSSL_new() succeeds for a server + * object - nothing below sends one - so use whichever algorithm the + * build has. svrCertFile is RSA, which a build without RSA cannot + * load. */ +#ifndef NO_RSA + ExpectTrue(wolfSSL_CTX_use_certificate_file(ctx, svrCertFile, + CERT_FILETYPE)); + ExpectTrue(wolfSSL_CTX_use_PrivateKey_file(ctx, svrKeyFile, + CERT_FILETYPE)); +#else + ExpectTrue(wolfSSL_CTX_use_certificate_file(ctx, eccCertFile, + CERT_FILETYPE)); + ExpectTrue(wolfSSL_CTX_use_PrivateKey_file(ctx, eccKeyFile, + CERT_FILETYPE)); +#endif + ExpectNotNull(ssl = wolfSSL_new(ctx)); + if (ssl != NULL) + suites = (Suites*)WOLFSSL_SUITES(ssl); + /* Every use below sits behind one of the extension macros, so a build with + * none of them leaves this set and never read. */ + (void)suites; + +#ifdef HAVE_SNI + ExpectIntEQ(TLSX_Parse(ssl, sni, (word16)sizeof(sni), client_hello, + suites), 0); + ExpectIntEQ(TLSX_Parse(ssl, sni, (word16)sizeof(sni), session_ticket, + NULL), WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); +#endif +#ifdef HAVE_CERTIFICATE_COMPRESSION + ExpectIntEQ(TLSX_Parse(ssl, cc, (word16)sizeof(cc), client_hello, + suites), 0); + ExpectIntEQ(TLSX_Parse(ssl, cc, (word16)sizeof(cc), certificate_request, + suites), 0); + /* Not listed for EncryptedExtensions: this is the leak that got through + * to an OpenSSL peer once. */ + ExpectIntEQ(TLSX_Parse(ssl, cc, (word16)sizeof(cc), encrypted_extensions, + NULL), WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + ExpectIntEQ(TLSX_Parse(ssl, cc, (word16)sizeof(cc), session_ticket, + NULL), WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); +#endif +#ifdef HAVE_RECORD_SIZE_LIMIT + ExpectIntEQ(TLSX_Parse(ssl, rsl, (word16)sizeof(rsl), client_hello, + suites), 0); + /* Listed for CH and EE, so a CertificateRequest is refused. */ + ExpectIntEQ(TLSX_Parse(ssl, rsl, (word16)sizeof(rsl), certificate_request, + suites), WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + ExpectIntEQ(TLSX_Parse(ssl, rsl, (word16)sizeof(rsl), session_ticket, + NULL), WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); +#endif +#ifdef HAVE_SIGNED_CERT_TIMESTAMP + ExpectIntEQ(TLSX_Parse(ssl, sct, (word16)sizeof(sct), client_hello, + suites), 0); + /* RFC 8446 Sect. 4.2 also lists CertificateRequest, where a server asks + * the client to staple SCTs to its own Certificate. Accepted and ignored + * rather than fatal - see test_tls13_sct_in_cert_request. */ + ExpectIntEQ(TLSX_Parse(ssl, sct, (word16)sizeof(sct), certificate_request, + suites), 0); + /* TLS 1.3 moved the answer to the Certificate message, so + * EncryptedExtensions is refused. This is the leak OpenSSL rejected. */ + ExpectIntEQ(TLSX_Parse(ssl, sct, (word16)sizeof(sct), encrypted_extensions, + NULL), WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + ExpectIntEQ(TLSX_Parse(ssl, sct, (word16)sizeof(sct), session_ticket, + NULL), WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); +#endif + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* Test a compressed certificate against a small negotiated record limit. + * + * The two features shipped together and broke each other: the compressed + * message was emitted in one record regardless of max_fragment_length or the + * peer's record_size_limit, so a ~1 KiB compressed chain against a 512 byte + * limit tripped the peer's own record check. The compressed body must be + * fragmented like the plain one. */ +int test_tls13_compressed_certificate_fragmented(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_CERTIFICATE_COMPRESSION) && \ + defined(HAVE_RECORD_SIZE_LIMIT) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_CERTS) && \ + !defined(NO_RSA) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_FILESYSTEM) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_compress_certs(ctx_s, WOLFSSL_CERT_COMP_ZLIB), + WOLFSSL_SUCCESS); + /* Small enough that the compressed chain cannot be one record. */ + ExpectIntEQ(wolfSSL_UseRecordSizeLimit(ssl_c, 512), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseRecordSizeLimit(ssl_s, 4096), WOLFSSL_SUCCESS); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 30, NULL), 0); + /* Compression was used, and the client accepted every record. */ + if (ssl_c != NULL) { + ExpectIntEQ(wolfSSL_get_certificate_compression_used(ssl_c), + WOLFSSL_CERT_COMP_ZLIB); + ExpectIntEQ(ssl_c->peerRecordSizeLimit, 4096); + } + if (ssl_s != NULL) { + ExpectIntEQ(ssl_s->peerRecordSizeLimit, 512); + /* fragOffset must be cleared once the message is out. */ + ExpectIntEQ(ssl_s->fragOffset, 0); + } + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* Test the CompressedCertificate message's rejection paths (RFC 8879 Sect. 4). + * + * These are the paths a hostile peer reaches, so each is driven directly: + * an algorithm this end never offered, an uncompressed_length beyond the cap + * that exists to stop a small message demanding a large allocation, a body + * that is not valid zlib, and a body that decompresses to a different length + * than announced. */ +/* RFC 8879 Sect. 4: an endpoint that did not advertise compress_certificate + * must treat a CompressedCertificate as an unexpected_message. The flag is + * cleared after the ClientHello has gone out, which is exactly the state an + * async-crypt or non-blocking-OCSP build is in permanently: it offers + * nothing, so anything compressed arriving back is unsolicited. The message + * has to be refused before it is decompressed, not after. */ +/* The compressed Certificate cache describes the chain it was built from, so + * anything that changes that chain has to discard it. Otherwise a context + * whose certificate was replaced would keep sending the old one, compressed. + */ +int test_tls13_cert_comp_cache_invalidation(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_CERTIFICATE_COMPRESSION) && \ + !defined(NO_CERTS) && !defined(NO_RSA) && !defined(NO_WOLFSSL_SERVER) && \ + !defined(NO_FILESYSTEM) && !defined(WOLFSSL_ASYNC_CRYPT) && \ + !defined(WOLFSSL_NONBLOCK_OCSP) + WOLFSSL_CTX* ctx = NULL; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + /* Nothing to compress yet. */ + ExpectIntEQ(wolfSSL_CTX_compress_certs(ctx, WOLFSSL_CERT_COMP_ZLIB), + WOLFSSL_FAILURE); + ExpectTrue(wolfSSL_CTX_use_certificate_file(ctx, svrCertFile, + CERT_FILETYPE)); + ExpectTrue(wolfSSL_CTX_use_PrivateKey_file(ctx, svrKeyFile, + CERT_FILETYPE)); + ExpectIntEQ(wolfSSL_CTX_compress_certs(ctx, WOLFSSL_CERT_COMP_ZLIB), + WOLFSSL_SUCCESS); + if (ctx != NULL) { + ExpectNotNull(ctx->certComp); + ExpectIntGT(ctx->certCompSz, 0); + /* The recorded shape is what the stale-cache backstop compares. */ + ExpectIntGT(ctx->certCompCertSz, 0); + } + + /* Replacing the certificate discards the cache and its recorded shape. */ + ExpectTrue(wolfSSL_CTX_use_certificate_file(ctx, svrCertFile, + CERT_FILETYPE)); + if (ctx != NULL) { + ExpectNull(ctx->certComp); + ExpectIntEQ(ctx->certCompSz, 0); + ExpectIntEQ(ctx->certCompCertSz, 0); + ExpectIntEQ(ctx->certCompChainSz, 0); + ExpectIntEQ(ctx->certCompChainCnt, 0); + } + + /* Rebuild, then grow the chain: that changes the message just as much. */ + ExpectIntEQ(wolfSSL_CTX_compress_certs(ctx, WOLFSSL_CERT_COMP_ZLIB), + WOLFSSL_SUCCESS); + if (ctx != NULL) + ExpectNotNull(ctx->certComp); + ExpectTrue(wolfSSL_CTX_use_certificate_chain_file(ctx, svrCertFile) + == WOLFSSL_SUCCESS); + if (ctx != NULL) + ExpectNull(ctx->certComp); + + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* Certificate compression in the other direction: the server asks for a + * client certificate and advertises compress_certificate in its + * CertificateRequest, and the client sends its own chain compressed. */ +int test_tls13_compressed_certificate_client(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_CERTIFICATE_COMPRESSION) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_CERTS) && \ + !defined(NO_RSA) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_FILESYSTEM) && \ + !defined(WOLFSSL_ASYNC_CRYPT) && !defined(WOLFSSL_NONBLOCK_OCSP) && \ + !defined(WOLFSSL_NO_CLIENT_AUTH) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + + /* The client context is built first: a certificate loaded onto a context + * only reaches objects created afterwards, and the compressed cache has + * to exist before the WOLFSSL is made. test_memio_setup() leaves a + * pre-made context alone, so the CA and transport are wired by hand. */ + ExpectNotNull(ctx_c = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectTrue(wolfSSL_CTX_load_verify_locations(ctx_c, caCertFile, 0) + == WOLFSSL_SUCCESS); + wolfSSL_SetIORecv(ctx_c, test_memio_read_cb); + wolfSSL_SetIOSend(ctx_c, test_memio_write_cb); + ExpectTrue(wolfSSL_CTX_use_certificate_file(ctx_c, cliCertFile, + CERT_FILETYPE)); + ExpectTrue(wolfSSL_CTX_use_PrivateKey_file(ctx_c, cliKeyFile, + CERT_FILETYPE)); + ExpectIntEQ(wolfSSL_CTX_compress_certs(ctx_c, WOLFSSL_CERT_COMP_ZLIB), + WOLFSSL_SUCCESS); + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + + /* Server asks for a client certificate and trusts its issuer. The cert + * manager is shared through the context, so this reaches ssl_s. */ + ExpectTrue(wolfSSL_CTX_load_verify_locations(ctx_s, cliCertFile, 0) + == WOLFSSL_SUCCESS); + if (ssl_s != NULL) + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_PEER, NULL); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + /* The server received the client's chain compressed. */ + ExpectIntEQ(wolfSSL_get_certificate_compression_used(ssl_s), + WOLFSSL_CERT_COMP_ZLIB); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +int test_tls13_compressed_certificate_unsolicited(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_CERTIFICATE_COMPRESSION) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_CERTS) && \ + !defined(NO_RSA) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_FILESYSTEM) && \ + !defined(WOLFSSL_ASYNC_CRYPT) && !defined(WOLFSSL_NONBLOCK_OCSP) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_compress_certs(ctx_s, WOLFSSL_CERT_COMP_ZLIB), + WOLFSSL_SUCCESS); + + /* One round: the ClientHello goes out advertising compression and the + * server answers with its whole flight, the compressed Certificate + * included. The client has not read any of it yet. */ + (void)test_memio_do_handshake(ssl_c, ssl_s, 1, NULL); + + /* Withdraw the offer before the client reads that flight. */ + if (ssl_c != NULL) + ssl_c->certCompAdvertised = 0; + + /* The client refuses the message instead of decompressing it. */ + ExpectIntNE(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + if (ssl_c != NULL) { + ExpectIntEQ(ssl_c->error, WC_NO_ERR_TRACE(OUT_OF_ORDER_E)); + /* Nothing was inflated: no algorithm was ever recorded. */ + ExpectIntEQ(wolfSSL_get_certificate_compression_used(ssl_c), 0); + } + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +int test_tls13_compressed_certificate_sct(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_CERTIFICATE_COMPRESSION) && \ + defined(HAVE_SIGNED_CERT_TIMESTAMP) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_CERTS) && \ + !defined(NO_RSA) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_FILESYSTEM) && \ + !defined(WOLFSSL_ASYNC_CRYPT) && !defined(WOLFSSL_NONBLOCK_OCSP) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + static const byte sctList[] = { 0x00, 0x05, 0x00, 0x03, 0xAA, 0xBB, 0xCC }; + const byte* got = NULL; + + /* The SCT list rides in the leaf's CertificateEntry extensions, and the + * compressed cache is built with those empty. Compressing here would drop + * a list the server had already committed to sending, so this handshake + * has to fall back to the plain Certificate. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_compress_certs(ctx_s, WOLFSSL_CERT_COMP_ZLIB), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_signed_cert_timestamp_list(ssl_s, sctList, + (unsigned short)sizeof(sctList)), WOLFSSL_SUCCESS); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + /* Sent uncompressed... */ + ExpectIntEQ(wolfSSL_get_certificate_compression_used(ssl_c), 0); + /* ...and the list actually arrived. */ + ExpectIntEQ(wolfSSL_get0_signed_cert_timestamp_list(ssl_c, &got), + (int)sizeof(sctList)); + ExpectNotNull(got); + if (got != NULL) + ExpectIntEQ(XMEMCMP(got, sctList, sizeof(sctList)), 0); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); ctx_s = NULL; + + /* Without a list to send there is nothing to lose, so compression is + * still taken - the opt-out must not cost every other handshake. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(wolfSSL_CTX_compress_certs(ctx_s, WOLFSSL_CERT_COMP_ZLIB), + WOLFSSL_SUCCESS); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectIntEQ(wolfSSL_get_certificate_compression_used(ssl_c), + WOLFSSL_CERT_COMP_ZLIB); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); ctx_s = NULL; +#endif + return EXPECT_RESULT(); +} + +int test_tls13_compressed_certificate_req_ctx(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_CERTIFICATE_COMPRESSION) && \ + defined(WOLFSSL_POST_HANDSHAKE_AUTH) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_CERTS) && \ + !defined(NO_RSA) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_FILESYSTEM) && \ + !defined(WOLFSSL_ASYNC_CRYPT) && !defined(WOLFSSL_NONBLOCK_OCSP) && \ + !defined(WOLFSSL_NO_CLIENT_AUTH) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + + /* Answering a CertificateRequest consumes the CertReqCtx node it queued. + * An in-handshake request queues a zero length one, and that is exactly + * the case UseCompressedCertificate() allows to compress, so the + * compressed send path has to release it just as the plain one does. + * Built like test_tls13_compressed_certificate_client(): the client's own + * certificate has to be on the context before the cache is made, and + * before the WOLFSSL is created from it. */ + ExpectNotNull(ctx_c = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectTrue(wolfSSL_CTX_load_verify_locations(ctx_c, caCertFile, 0) + == WOLFSSL_SUCCESS); + wolfSSL_SetIORecv(ctx_c, test_memio_read_cb); + wolfSSL_SetIOSend(ctx_c, test_memio_write_cb); + ExpectTrue(wolfSSL_CTX_use_certificate_file(ctx_c, cliCertFile, + CERT_FILETYPE)); + ExpectTrue(wolfSSL_CTX_use_PrivateKey_file(ctx_c, cliKeyFile, + CERT_FILETYPE)); + ExpectIntEQ(wolfSSL_CTX_compress_certs(ctx_c, WOLFSSL_CERT_COMP_ZLIB), + WOLFSSL_SUCCESS); + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + + ExpectTrue(wolfSSL_CTX_load_verify_locations(ctx_s, cliCertFile, 0) + == WOLFSSL_SUCCESS); + if (ssl_s != NULL) + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_PEER, NULL); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + /* The client really did take the compressed path, so this is testing what + * it claims to test rather than the plain path by accident. */ + ExpectIntEQ(wolfSSL_get_certificate_compression_used(ssl_s), + WOLFSSL_CERT_COMP_ZLIB); + /* Request answered, node released. */ + if (ssl_c != NULL) + ExpectNull(ssl_c->certReqCtx); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +int test_tls13_compressed_certificate_server_advertise(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_CERTIFICATE_COMPRESSION) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && !defined(NO_CERTS) && \ + !defined(NO_RSA) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_FILESYSTEM) && \ + !defined(WOLFSSL_ASYNC_CRYPT) && !defined(WOLFSSL_NONBLOCK_OCSP) + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + struct test_memio_ctx test_ctx; + + /* certCompAdvertised has to mean "this end put the extension in a message + * it sent", because that is what RFC 8879 Sect. 4 makes the peer's licence + * to send a CompressedCertificate. A server only advertises in the + * CertificateRequest, so a server that never asks for a client + * certificate has advertised nothing - and must not accept one. + * test_tls13_compressed_certificate_unsolicited() checks the guard that + * reads this flag; this checks the flag itself, which the guard is + * worthless without. */ + + /* No client authentication: no CertificateRequest, so nothing offered. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->certCompAdvertised, 0); + /* The client did offer, in its ClientHello. */ + if (ssl_c != NULL) + ExpectIntEQ(ssl_c->certCompAdvertised, 1); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); ctx_s = NULL; + + /* Client authentication requested: the CertificateRequest carries the + * extension, so now the server has advertised and may accept one. */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + ExpectTrue(wolfSSL_CTX_load_verify_locations(ctx_s, cliCertFile, 0) + == WOLFSSL_SUCCESS); + if (ssl_s != NULL) + wolfSSL_set_verify(ssl_s, WOLFSSL_VERIFY_PEER, NULL); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + if (ssl_s != NULL) + ExpectIntEQ(ssl_s->certCompAdvertised, 1); + + wolfSSL_free(ssl_c); ssl_c = NULL; + wolfSSL_free(ssl_s); ssl_s = NULL; + wolfSSL_CTX_free(ctx_c); ctx_c = NULL; + wolfSSL_CTX_free(ctx_s); ctx_s = NULL; +#endif + return EXPECT_RESULT(); +} + +int test_tls13_compressed_certificate_bad(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_CERTIFICATE_COMPRESSION) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_FILESYSTEM) && \ + !defined(NO_CERTS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + word32 idx; + byte msg[64]; + word32 len; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + /* Truncated: shorter than algorithm + uncompressed_length + length. */ + if (EXPECT_SUCCESS()) { + idx = 0; + XMEMSET(msg, 0, sizeof(msg)); + ExpectIntEQ(DoTls13CompressedCertificate(ssl, msg, &idx, 4), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + } + + /* brotli(2): a real algorithm, but not one this build offered. */ + if (EXPECT_SUCCESS()) { + idx = 0; + len = 0; + msg[len++] = 0x00; msg[len++] = 0x02; /* algorithm */ + msg[len++] = 0x00; msg[len++] = 0x00; msg[len++] = 0x20; + msg[len++] = 0x00; msg[len++] = 0x00; msg[len++] = 0x04; + msg[len++] = 0xde; msg[len++] = 0xad; + msg[len++] = 0xbe; msg[len++] = 0xef; + ExpectIntEQ(DoTls13CompressedCertificate(ssl, msg, &idx, len), + WC_NO_ERR_TRACE(INVALID_PARAMETER)); + } + + /* zlib, but announcing more than WOLFSSL_MAX_CERT_COMP_SZ. Rejected + * before any buffer is allocated. */ + if (EXPECT_SUCCESS()) { + idx = 0; + len = 0; + msg[len++] = 0x00; msg[len++] = 0x01; /* zlib */ + msg[len++] = 0xff; msg[len++] = 0xff; msg[len++] = 0xff; + msg[len++] = 0x00; msg[len++] = 0x00; msg[len++] = 0x04; + msg[len++] = 0xde; msg[len++] = 0xad; + msg[len++] = 0xbe; msg[len++] = 0xef; + ExpectIntEQ(DoTls13CompressedCertificate(ssl, msg, &idx, len), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + } + + /* A compressed length that disagrees with the bytes present. */ + if (EXPECT_SUCCESS()) { + idx = 0; + len = 0; + msg[len++] = 0x00; msg[len++] = 0x01; + msg[len++] = 0x00; msg[len++] = 0x00; msg[len++] = 0x20; + /* claims 64 bytes of compressed data */ + msg[len++] = 0x00; msg[len++] = 0x00; msg[len++] = 0x40; + msg[len++] = 0xde; msg[len++] = 0xad; + msg[len++] = 0xbe; msg[len++] = 0xef; + ExpectIntEQ(DoTls13CompressedCertificate(ssl, msg, &idx, len), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + } + + /* Well framed, but the body is not zlib. */ + if (EXPECT_SUCCESS()) { + idx = 0; + len = 0; + msg[len++] = 0x00; msg[len++] = 0x01; + msg[len++] = 0x00; msg[len++] = 0x00; msg[len++] = 0x20; + msg[len++] = 0x00; msg[len++] = 0x00; msg[len++] = 0x04; + msg[len++] = 0xde; msg[len++] = 0xad; + msg[len++] = 0xbe; msg[len++] = 0xef; + ExpectIntEQ(DoTls13CompressedCertificate(ssl, msg, &idx, len), + WC_NO_ERR_TRACE(DECOMPRESS_E)); + } + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* Test the compress_certificate extension encoding (RFC 8879 Sect. 3). + * + * struct { CertificateCompressionAlgorithm algorithms<2..2^8-2>; } + * + * A one byte list length then two bytes per algorithm. The extension is + * listed for ClientHello and CertificateRequest only. + * + * Not built for async-crypt or non-blocking-OCSP configurations: those + * deliberately do not advertise compress_certificate, because + * DoTls13CompressedCertificate() cannot carry a decompressed buffer across a + * suspension, so nothing negotiates compression there. */ +int test_tls13_compress_certificate_ext(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_CERTIFICATE_COMPRESSION) && \ + !defined(NO_WOLFSSL_SERVER) && !defined(NO_FILESYSTEM) && \ + !defined(NO_CERTS) && (!defined(NO_RSA) || defined(HAVE_ECC)) && \ + !defined(WOLFSSL_ASYNC_CRYPT) && !defined(WOLFSSL_NONBLOCK_OCSP) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + /* ext type, ext length, list length, zlib(1). */ + static const byte okExt[] = { + 0x00, 0x1b, 0x00, 0x03, 0x02, 0x00, 0x01 + }; + /* List length that is not a whole number of algorithms. */ + static const byte oddExt[] = { + 0x00, 0x1b, 0x00, 0x04, 0x03, 0x00, 0x01, 0x00 + }; + /* Empty algorithm list. */ + static const byte emptyExt[] = { + 0x00, 0x1b, 0x00, 0x01, 0x00 + }; + /* List length that disagrees with the extension length. */ + static const byte shortExt[] = { + 0x00, 0x1b, 0x00, 0x03, 0x04, 0x00, 0x01 + }; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + /* A server object needs a certificate and key to get past + * wolfSSL_new(): SetSSL_CTX() refuses one that has neither those nor a + * PSK/anon/cert-setup-cb fallback. A richer build supplies a fallback and + * hides this; a build with only certificate compression does not. */ +#ifndef NO_RSA + ExpectTrue(wolfSSL_CTX_use_certificate_file(ctx, svrCertFile, + CERT_FILETYPE)); + ExpectTrue(wolfSSL_CTX_use_PrivateKey_file(ctx, svrKeyFile, + CERT_FILETYPE)); +#else + ExpectTrue(wolfSSL_CTX_use_certificate_file(ctx, eccCertFile, + CERT_FILETYPE)); + ExpectTrue(wolfSSL_CTX_use_PrivateKey_file(ctx, eccKeyFile, + CERT_FILETYPE)); +#endif + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + ExpectIntEQ(TLSX_Parse(ssl, okExt, (word16)sizeof(okExt), client_hello, + (Suites*)WOLFSSL_SUITES(ssl)), 0); + ExpectIntEQ(TLSX_Parse(ssl, oddExt, (word16)sizeof(oddExt), client_hello, + (Suites*)WOLFSSL_SUITES(ssl)), WC_NO_ERR_TRACE(BUFFER_ERROR)); + ExpectIntEQ(TLSX_Parse(ssl, emptyExt, (word16)sizeof(emptyExt), + client_hello, (Suites*)WOLFSSL_SUITES(ssl)), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + ExpectIntEQ(TLSX_Parse(ssl, shortExt, (word16)sizeof(shortExt), + client_hello, (Suites*)WOLFSSL_SUITES(ssl)), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + + /* Listed for CH and CR only. */ + ExpectIntEQ(TLSX_Parse(ssl, okExt, (word16)sizeof(okExt), + certificate_request, (Suites*)WOLFSSL_SUITES(ssl)), 0); + ExpectIntEQ(TLSX_Parse(ssl, okExt, (word16)sizeof(okExt), session_ticket, + NULL), WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + +/* Test that server_name is accepted in a TLS 1.3 CertificateRequest. + * + * RFC 9846 Table 1 lists server_name as CH, EE, CR, so a client must not + * abort on a CertificateRequest that carries it. The name is validated but + * unused: consuming it needs the RFC 9261 exported-authenticator machinery + * that wolfSSL does not implement. Messages outside the table entry must + * still be refused, and a malformed ServerNameList must still be caught + * rather than skipped. */ +int test_tls13_cert_req_server_name(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(HAVE_SNI) && !defined(NO_WOLFSSL_CLIENT) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + /* server_name: ext type, ext length, list length, name type, name + * length, "wolfssl.com". */ + static const byte sniExt[] = { + 0x00, 0x00, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x00, 0x0b, + 'w', 'o', 'l', 'f', 's', 's', 'l', '.', 'c', 'o', 'm' + }; + /* Same, with a name length one past the end of the list. */ + static const byte badLenExt[] = { + 0x00, 0x00, 0x00, 0x10, 0x00, 0x0e, 0x00, 0x00, 0x0c, + 'w', 'o', 'l', 'f', 's', 's', 'l', '.', 'c', 'o', 'm' + }; + /* Same, with a name type that is not host_name(0). */ + static const byte badTypeExt[] = { + 0x00, 0x00, 0x00, 0x10, 0x00, 0x0e, 0x01, 0x00, 0x0b, + 'w', 'o', 'l', 'f', 's', 's', 'l', '.', 'c', 'o', 'm' + }; + /* A list length with no list behind it. */ + static const byte truncExt[] = { + 0x00, 0x00, 0x00, 0x02, 0x00, 0x0e + }; + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + /* Accepted: the extension is listed for CertificateRequest. */ + ExpectIntEQ(TLSX_Parse(ssl, sniExt, (word16)sizeof(sniExt), + certificate_request, (Suites*)WOLFSSL_SUITES(ssl)), 0); + + /* Accepting it must not mean skipping over it. */ + ExpectIntEQ(TLSX_Parse(ssl, badLenExt, (word16)sizeof(badLenExt), + certificate_request, (Suites*)WOLFSSL_SUITES(ssl)), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + ExpectIntEQ(TLSX_Parse(ssl, badTypeExt, (word16)sizeof(badTypeExt), + certificate_request, (Suites*)WOLFSSL_SUITES(ssl)), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + ExpectIntEQ(TLSX_Parse(ssl, truncExt, (word16)sizeof(truncExt), + certificate_request, (Suites*)WOLFSSL_SUITES(ssl)), + WC_NO_ERR_TRACE(BUFFER_ERROR)); + + /* Table 1 lists CH, EE and CR only: NewSessionTicket is still refused. */ + ExpectIntEQ(TLSX_Parse(ssl, sniExt, (word16)sizeof(sniExt), + session_ticket, NULL), WC_NO_ERR_TRACE(EXT_NOT_ALLOWED)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif + return EXPECT_RESULT(); +} + /* Test the cookie extension of a ClientHello on the server. How much of it a * server checks depends on the build: * - without WOLFSSL_TLS13_COOKIE there is no cookie code, so the extension diff --git a/tests/api/test_tls13.h b/tests/api/test_tls13.h index 85d5a56b699..f8dbbab344f 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -25,9 +25,30 @@ #include int test_tls13_apis(void); +int test_tls13_cert_with_extern_psk_apis(void); +int test_tls13_cert_with_extern_psk_handshake(void); +int test_tls13_cert_with_extern_psk_client_requires_cert(void); +int test_tls13_cert_with_extern_psk_requires_key_share(void); +int test_tls13_cert_with_extern_psk_rejects_resumption(void); +int test_tls13_cert_with_extern_psk_sh_missing_key_share(void); +int test_tls13_cert_with_extern_psk_sh_confirms_resumption(void); +int test_tls13_fail_if_no_psk_api(void); +int test_tls13_fail_if_no_psk_handshake(void); +int test_tls13_fail_if_no_psk_rejects_no_psk(void); +int test_tls13_fail_if_no_psk_client_no_psk_configured(void); +int test_tls13_fail_if_no_psk_client_rejects(void); +int test_tls13_fail_if_no_psk_requires_dhe(void); +int test_tls13_fail_if_no_psk_client_requires_dhe(void); +int test_tls13_fail_if_no_psk_resumption_exempt_from_dhe(void); +int test_tls13_fail_if_no_psk_server_rejects_offered_psk(void); +int test_tls13_fail_if_no_psk_no_cert_server(void); +int test_tls13_fail_if_no_psk_dtls13_handshake(void); +int test_tls13_fail_if_no_psk_dtls13_rejects_no_psk(void); int test_tls13_cipher_suites(void); int test_tls13_cipher_list_no_tls13_ctx(void); int test_tls13_bad_psk_binder(void); +int test_tls13_psk_no_cert_bad_binder(void); +int test_tls13_psk_age_no_identity_oracle(void); int test_tls13_rpk_handshake(void); int test_tls13_rpk_handshake_no_negotiation(void); int test_tls13_pha(void); @@ -39,6 +60,16 @@ int test_tls13_rpk_unoffered_cert_type(void); int test_tls13_pq_groups(void); int test_tls13_multi_pqc_key_share(void); int test_tls13_early_data(void); +int test_tls13_early_data_0rtt_replay(void); +int test_tls13_record_size_limit_ctx(void); +int test_tls13_record_size_limit_early_data(void); +int test_tls13_0rtt_default_off(void); +int test_tls13_0rtt_stateless_replay(void); +int test_tls13_remove_session_return(void); +int test_tls13_0rtt_ext_cache_eviction(void); +int test_tls13_early_data_bad_record_mac(void); +int test_tls13_0rtt_fresh_start(void); +int test_tls13_0rtt_fresh_start_check_args(void); int test_tls13_same_ch(void); int test_tls13_hrr_different_cs(void); int test_tls13_ch2_different_cs(void); @@ -53,24 +84,18 @@ int test_tls13_plaintext_alert(void); int test_tls13_warning_alert_is_fatal(void); int test_tls13_unknown_ext_rejected(void); int test_tls13_hrr_recognized_ext_downgrade(void); +int test_tls13_sigalgs_cert_offered(void); int test_tls13_cert_req_sigalgs(void); int test_tls13_sha1_cert_chain(void); int test_tls13_derive_keys_no_key(void); int test_tls13_pqc_hybrid_truncated_keyshare(void); int test_tls13_pqc_hybrid_malformed_ecdh(void); +int test_tls13_pqc_hybrid_async_server(void); int test_tls13_empty_record_limit(void); int test_tls13_short_session_ticket(void); int test_tls13_zero_length_session_ticket(void); int test_tls13_new_session_ticket_max_lifetime(void); int test_tls13_fragmented_session_ticket(void); -int test_tls13_early_data_0rtt_replay(void); -int test_tls13_0rtt_default_off(void); -int test_tls13_0rtt_stateless_replay(void); -int test_tls13_remove_session_return(void); -int test_tls13_0rtt_ext_cache_eviction(void); -int test_tls13_early_data_bad_record_mac(void); -int test_tls13_0rtt_fresh_start(void); -int test_tls13_0rtt_fresh_start_check_args(void); int test_tls13_corrupted_finished(void); int test_tls13_certificate_verify_bad_sigalgo(void); int test_tls13_peerauth_failsafe(void); @@ -79,33 +104,37 @@ int test_tls13_hrr_cookie_handshake(void); int test_tls13_hrr_cookie_only_handshake(void); int test_tls13_client_cookie_echo(void); int test_tls13_client_cookie_too_big(void); +int test_tls13_sct_clear_ctx_snapshot(void); +int test_tls13_sct_response_framing(void); +int test_tls13_sct_unsolicited_at_server(void); +int test_tls13_sct_oversize_list(void); +int test_tls13_sct_in_cert_request(void); +int test_tls13_signed_cert_timestamp(void); +int test_tls13_record_size_limit_overflow(void); +int test_tls13_record_size_limit_both_exts(void); +int test_tls13_record_size_limit_vs_mfl(void); +int test_tls13_record_size_limit_fragment(void); +int test_tls13_record_size_limit(void); +int test_tls13_sct_certificate_msg(void); +int test_tls13_sct_cert_chain(void); +int test_tls13_compressed_certificate(void); +int test_tls13_new_ext_placement(void); +int test_tls13_compressed_certificate_fragmented(void); +int test_tls13_cert_comp_cache_invalidation(void); +int test_tls13_compressed_certificate_client(void); +int test_tls13_compressed_certificate_unsolicited(void); +int test_tls13_compressed_certificate_sct(void); +int test_tls13_compressed_certificate_req_ctx(void); +int test_tls13_compressed_certificate_server_advertise(void); +int test_tls13_compressed_certificate_bad(void); +int test_tls13_compress_certificate_ext(void); +int test_tls13_cert_req_server_name(void); int test_tls13_server_cookie_parse(void); int test_tls13_zero_inner_content_type(void); int test_tls13_post_handshake_auth_no_ext(void); int test_tls13_post_handshake_auth_late_allow(void); int test_tls13_downgrade_sentinel(void); int test_tls13_serverhello_bad_cipher_suites(void); -int test_tls13_psk_no_cert_bad_binder(void); -int test_tls13_psk_age_no_identity_oracle(void); -int test_tls13_cert_with_extern_psk_apis(void); -int test_tls13_cert_with_extern_psk_handshake(void); -int test_tls13_cert_with_extern_psk_client_requires_cert(void); -int test_tls13_cert_with_extern_psk_requires_key_share(void); -int test_tls13_cert_with_extern_psk_rejects_resumption(void); -int test_tls13_cert_with_extern_psk_sh_missing_key_share(void); -int test_tls13_cert_with_extern_psk_sh_confirms_resumption(void); -int test_tls13_fail_if_no_psk_api(void); -int test_tls13_fail_if_no_psk_handshake(void); -int test_tls13_fail_if_no_psk_rejects_no_psk(void); -int test_tls13_fail_if_no_psk_client_no_psk_configured(void); -int test_tls13_fail_if_no_psk_client_rejects(void); -int test_tls13_fail_if_no_psk_requires_dhe(void); -int test_tls13_fail_if_no_psk_client_requires_dhe(void); -int test_tls13_fail_if_no_psk_resumption_exempt_from_dhe(void); -int test_tls13_fail_if_no_psk_server_rejects_offered_psk(void); -int test_tls13_fail_if_no_psk_no_cert_server(void); -int test_tls13_fail_if_no_psk_dtls13_handshake(void); -int test_tls13_fail_if_no_psk_dtls13_rejects_no_psk(void); int test_tls13_ticket_peer_cert_reverify(void); int test_tls13_clear_preserves_psk_dhe(void); int test_tls13_cipher_fuzz_aes128_gcm_sha256(void); @@ -119,24 +148,44 @@ int test_tls13_AEAD_limit_KU_aes256_gcm_sha384(void); int test_tls13_AEAD_limit_KU_aes128_ccm_sha256(void); int test_tls13_AEAD_limit_KU_aes128_ccm_8_sha256(void); int test_tls13_KeyUpdate_sender_limit(void); -int test_tls13_KeyUpdate_limit_ignores_update_requested(void); +int test_tls13_user_canceled_encrypted(void); +int test_tls12_fatal_alert_closes_and_evicts(void); int test_tls13_KeyUpdate_limit_writedup(void); +int test_tls13_early_data_AEAD_limit_exact(void); +int test_tls13_early_data_AEAD_limit_partial(void); int test_tls13_extension_trailing_data_alert(void); +int test_tls13_KeyUpdate_limit_ignores_update_requested(void); int test_tls13_early_data_AEAD_limit(void); -int test_tls13_early_data_AEAD_limit_partial(void); -int test_tls13_early_data_AEAD_limit_exact(void); int test_tls13_user_canceled_fatal_level(void); -int test_tls13_user_canceled_encrypted(void); -int test_tls12_fatal_alert_closes_and_evicts(void); -int test_tls13_pqc_hybrid_async_server(void); int test_tls13_pha_status_request(void); int test_tls13_x25519_keyshare_masks_reserved_bit(void); #define TEST_TLS13_DECLS \ TEST_DECL_GROUP("tls13", test_tls13_apis), \ + TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_apis), \ + TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_handshake), \ + TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_client_requires_cert), \ + TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_requires_key_share), \ + TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_rejects_resumption), \ + TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_sh_missing_key_share), \ + TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_sh_confirms_resumption), \ + TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_api), \ + TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_handshake), \ + TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_rejects_no_psk), \ + TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_client_no_psk_configured), \ + TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_client_rejects), \ + TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_requires_dhe), \ + TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_client_requires_dhe), \ + TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_resumption_exempt_from_dhe), \ + TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_server_rejects_offered_psk), \ + TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_no_cert_server), \ + TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_dtls13_handshake), \ + TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_dtls13_rejects_no_psk), \ TEST_DECL_GROUP("tls13", test_tls13_cipher_suites), \ TEST_DECL_GROUP("tls13", test_tls13_cipher_list_no_tls13_ctx), \ TEST_DECL_GROUP("tls13", test_tls13_bad_psk_binder), \ + TEST_DECL_GROUP("tls13", test_tls13_psk_no_cert_bad_binder), \ + TEST_DECL_GROUP("tls13", test_tls13_psk_age_no_identity_oracle), \ TEST_DECL_GROUP("tls13", test_tls13_rpk_handshake), \ TEST_DECL_GROUP("tls13", test_tls13_rpk_handshake_no_negotiation), \ TEST_DECL_GROUP("tls13", test_tls13_pha), \ @@ -148,6 +197,16 @@ int test_tls13_x25519_keyshare_masks_reserved_bit(void); TEST_DECL_GROUP("tls13", test_tls13_pq_groups), \ TEST_DECL_GROUP("tls13", test_tls13_multi_pqc_key_share), \ TEST_DECL_GROUP("tls13", test_tls13_early_data), \ + TEST_DECL_GROUP("tls13", test_tls13_early_data_0rtt_replay), \ + TEST_DECL_GROUP("tls13", test_tls13_record_size_limit_ctx), \ + TEST_DECL_GROUP("tls13", test_tls13_record_size_limit_early_data), \ + TEST_DECL_GROUP("tls13", test_tls13_0rtt_default_off), \ + TEST_DECL_GROUP("tls13", test_tls13_0rtt_stateless_replay), \ + TEST_DECL_GROUP("tls13", test_tls13_remove_session_return), \ + TEST_DECL_GROUP("tls13", test_tls13_0rtt_ext_cache_eviction), \ + TEST_DECL_GROUP("tls13", test_tls13_early_data_bad_record_mac), \ + TEST_DECL_GROUP("tls13", test_tls13_0rtt_fresh_start), \ + TEST_DECL_GROUP("tls13", test_tls13_0rtt_fresh_start_check_args), \ TEST_DECL_GROUP("tls13", test_tls13_same_ch), \ TEST_DECL_GROUP("tls13", test_tls13_hrr_different_cs), \ TEST_DECL_GROUP("tls13", test_tls13_ch2_different_cs), \ @@ -160,61 +219,59 @@ int test_tls13_x25519_keyshare_masks_reserved_bit(void); TEST_DECL_GROUP("tls13", test_tls13_middlebox_compat_session_id), \ TEST_DECL_GROUP("tls13", test_tls13_plaintext_alert), \ TEST_DECL_GROUP("tls13", test_tls13_warning_alert_is_fatal), \ - TEST_DECL_GROUP("tls13", test_tls13_cert_req_sigalgs), \ - TEST_DECL_GROUP("tls13", test_tls13_sha1_cert_chain), \ + TEST_DECL_GROUP("tls13", test_tls13_unknown_ext_rejected), \ + TEST_DECL_GROUP("tls13", test_tls13_hrr_recognized_ext_downgrade), \ + TEST_DECL_GROUP("tls13", test_tls13_sigalgs_cert_offered), \ + TEST_DECL_GROUP("tls13", test_tls13_cert_req_sigalgs), \ + TEST_DECL_GROUP("tls13", test_tls13_sha1_cert_chain), \ TEST_DECL_GROUP("tls13", test_tls13_derive_keys_no_key), \ TEST_DECL_GROUP("tls13", test_tls13_pqc_hybrid_truncated_keyshare), \ TEST_DECL_GROUP("tls13", test_tls13_pqc_hybrid_malformed_ecdh), \ + TEST_DECL_GROUP("tls13", test_tls13_pqc_hybrid_async_server), \ TEST_DECL_GROUP("tls13", test_tls13_empty_record_limit), \ TEST_DECL_GROUP("tls13", test_tls13_short_session_ticket), \ - TEST_DECL_GROUP("tls13", test_tls13_zero_length_session_ticket), \ + TEST_DECL_GROUP("tls13", test_tls13_zero_length_session_ticket), \ TEST_DECL_GROUP("tls13", test_tls13_new_session_ticket_max_lifetime), \ TEST_DECL_GROUP("tls13", test_tls13_fragmented_session_ticket), \ - TEST_DECL_GROUP("tls13", test_tls13_early_data_0rtt_replay), \ - TEST_DECL_GROUP("tls13", test_tls13_0rtt_default_off), \ - TEST_DECL_GROUP("tls13", test_tls13_0rtt_stateless_replay), \ - TEST_DECL_GROUP("tls13", test_tls13_remove_session_return), \ - TEST_DECL_GROUP("tls13", test_tls13_0rtt_ext_cache_eviction), \ - TEST_DECL_GROUP("tls13", test_tls13_early_data_bad_record_mac), \ - TEST_DECL_GROUP("tls13", test_tls13_0rtt_fresh_start), \ - TEST_DECL_GROUP("tls13", test_tls13_0rtt_fresh_start_check_args), \ - TEST_DECL_GROUP("tls13", test_tls13_unknown_ext_rejected), \ - TEST_DECL_GROUP("tls13", test_tls13_hrr_recognized_ext_downgrade), \ - TEST_DECL_GROUP("tls13", test_tls13_corrupted_finished), \ + TEST_DECL_GROUP("tls13", test_tls13_corrupted_finished), \ TEST_DECL_GROUP("tls13", test_tls13_certificate_verify_bad_sigalgo), \ - TEST_DECL_GROUP("tls13", test_tls13_peerauth_failsafe), \ - TEST_DECL_GROUP("tls13", test_tls13_hrr_bad_cookie), \ - TEST_DECL_GROUP("tls13", test_tls13_hrr_cookie_handshake), \ + TEST_DECL_GROUP("tls13", test_tls13_peerauth_failsafe), \ + TEST_DECL_GROUP("tls13", test_tls13_hrr_bad_cookie), \ + TEST_DECL_GROUP("tls13", test_tls13_hrr_cookie_handshake), \ TEST_DECL_GROUP("tls13", test_tls13_hrr_cookie_only_handshake), \ - TEST_DECL_GROUP("tls13", test_tls13_client_cookie_echo), \ + TEST_DECL_GROUP("tls13", test_tls13_client_cookie_echo), \ TEST_DECL_GROUP("tls13", test_tls13_client_cookie_too_big), \ - TEST_DECL_GROUP("tls13", test_tls13_server_cookie_parse), \ + TEST_DECL_GROUP("tls13", test_tls13_sct_clear_ctx_snapshot), \ + TEST_DECL_GROUP("tls13", test_tls13_sct_response_framing), \ + TEST_DECL_GROUP("tls13", test_tls13_sct_unsolicited_at_server), \ + TEST_DECL_GROUP("tls13", test_tls13_sct_oversize_list), \ + TEST_DECL_GROUP("tls13", test_tls13_sct_in_cert_request), \ + TEST_DECL_GROUP("tls13", test_tls13_signed_cert_timestamp), \ + TEST_DECL_GROUP("tls13", test_tls13_record_size_limit_overflow), \ + TEST_DECL_GROUP("tls13", test_tls13_record_size_limit_both_exts), \ + TEST_DECL_GROUP("tls13", test_tls13_record_size_limit_vs_mfl), \ + TEST_DECL_GROUP("tls13", test_tls13_record_size_limit_fragment), \ + TEST_DECL_GROUP("tls13", test_tls13_record_size_limit), \ + TEST_DECL_GROUP("tls13", test_tls13_sct_certificate_msg), \ + TEST_DECL_GROUP("tls13", test_tls13_sct_cert_chain), \ + TEST_DECL_GROUP("tls13", test_tls13_compressed_certificate), \ + TEST_DECL_GROUP("tls13", test_tls13_new_ext_placement), \ + TEST_DECL_GROUP("tls13", test_tls13_compressed_certificate_fragmented), \ + TEST_DECL_GROUP("tls13", test_tls13_cert_comp_cache_invalidation), \ + TEST_DECL_GROUP("tls13", test_tls13_compressed_certificate_client), \ + TEST_DECL_GROUP("tls13", test_tls13_compressed_certificate_unsolicited), \ + TEST_DECL_GROUP("tls13", test_tls13_compressed_certificate_sct), \ + TEST_DECL_GROUP("tls13", test_tls13_compressed_certificate_req_ctx), \ + TEST_DECL_GROUP("tls13", test_tls13_compressed_certificate_server_advertise), \ + TEST_DECL_GROUP("tls13", test_tls13_compressed_certificate_bad), \ + TEST_DECL_GROUP("tls13", test_tls13_compress_certificate_ext), \ + TEST_DECL_GROUP("tls13", test_tls13_cert_req_server_name), \ + TEST_DECL_GROUP("tls13", test_tls13_server_cookie_parse), \ TEST_DECL_GROUP("tls13", test_tls13_zero_inner_content_type), \ TEST_DECL_GROUP("tls13", test_tls13_post_handshake_auth_no_ext), \ TEST_DECL_GROUP("tls13", test_tls13_post_handshake_auth_late_allow), \ - TEST_DECL_GROUP("tls13", test_tls13_downgrade_sentinel), \ + TEST_DECL_GROUP("tls13", test_tls13_downgrade_sentinel), \ TEST_DECL_GROUP("tls13", test_tls13_serverhello_bad_cipher_suites), \ - TEST_DECL_GROUP("tls13", test_tls13_psk_no_cert_bad_binder), \ - TEST_DECL_GROUP("tls13", test_tls13_psk_age_no_identity_oracle), \ - TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_apis), \ - TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_handshake), \ - TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_client_requires_cert), \ - TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_requires_key_share), \ - TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_rejects_resumption), \ - TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_sh_missing_key_share), \ - TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_sh_confirms_resumption), \ - TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_api), \ - TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_handshake), \ - TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_rejects_no_psk), \ - TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_client_no_psk_configured), \ - TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_client_rejects), \ - TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_requires_dhe), \ - TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_client_requires_dhe), \ - TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_resumption_exempt_from_dhe), \ - TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_server_rejects_offered_psk), \ - TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_no_cert_server), \ - TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_dtls13_handshake), \ - TEST_DECL_GROUP("tls13", test_tls13_fail_if_no_psk_dtls13_rejects_no_psk), \ TEST_DECL_GROUP("tls13", test_tls13_ticket_peer_cert_reverify), \ TEST_DECL_GROUP("tls13", test_tls13_clear_preserves_psk_dhe), \ TEST_DECL_GROUP("tls13", test_tls13_cipher_fuzz_aes128_gcm_sha256), \ @@ -222,23 +279,22 @@ int test_tls13_x25519_keyshare_masks_reserved_bit(void); TEST_DECL_GROUP("tls13", test_tls13_cipher_fuzz_chacha20_poly1305_sha256), \ TEST_DECL_GROUP("tls13", test_tls13_cipher_fuzz_aes128_ccm_sha256), \ TEST_DECL_GROUP("tls13", test_tls13_cipher_fuzz_aes128_ccm_8_sha256), \ - TEST_DECL_GROUP("tls13", test_tls13_AEAD_limit_macros), \ + TEST_DECL_GROUP("tls13", test_tls13_AEAD_limit_macros), \ TEST_DECL_GROUP("tls13", test_tls13_AEAD_limit_KU_aes128_gcm_sha256), \ TEST_DECL_GROUP("tls13", test_tls13_AEAD_limit_KU_aes256_gcm_sha384), \ TEST_DECL_GROUP("tls13", test_tls13_AEAD_limit_KU_aes128_ccm_sha256), \ TEST_DECL_GROUP("tls13", test_tls13_AEAD_limit_KU_aes128_ccm_8_sha256), \ TEST_DECL_GROUP("tls13", test_tls13_KeyUpdate_sender_limit), \ - TEST_DECL_GROUP("tls13", test_tls13_KeyUpdate_limit_ignores_update_requested), \ + TEST_DECL_GROUP("tls13", test_tls13_user_canceled_encrypted), \ + TEST_DECL_GROUP("tls13", test_tls12_fatal_alert_closes_and_evicts), \ TEST_DECL_GROUP("tls13", test_tls13_KeyUpdate_limit_writedup), \ + TEST_DECL_GROUP("tls13", test_tls13_early_data_AEAD_limit_exact), \ + TEST_DECL_GROUP("tls13", test_tls13_early_data_AEAD_limit_partial), \ TEST_DECL_GROUP("tls13", test_tls13_extension_trailing_data_alert), \ + TEST_DECL_GROUP("tls13", test_tls13_KeyUpdate_limit_ignores_update_requested), \ TEST_DECL_GROUP("tls13", test_tls13_early_data_AEAD_limit), \ - TEST_DECL_GROUP("tls13", test_tls13_early_data_AEAD_limit_partial), \ - TEST_DECL_GROUP("tls13", test_tls13_early_data_AEAD_limit_exact), \ TEST_DECL_GROUP("tls13", test_tls13_user_canceled_fatal_level), \ - TEST_DECL_GROUP("tls13", test_tls13_user_canceled_encrypted), \ - TEST_DECL_GROUP("tls13", test_tls12_fatal_alert_closes_and_evicts), \ - TEST_DECL_GROUP("tls13", test_tls13_pqc_hybrid_async_server), \ - TEST_DECL_GROUP("tls13", test_tls13_pha_status_request), \ + TEST_DECL_GROUP("tls13", test_tls13_pha_status_request), \ TEST_DECL_GROUP("tls13", test_tls13_x25519_keyshare_masks_reserved_bit) #endif /* WOLFCRYPT_TEST_TLS13_H */ diff --git a/tests/api/test_tls_bounds.c b/tests/api/test_tls_bounds.c index da3befb3784..be2eb55df0d 100644 --- a/tests/api/test_tls_bounds.c +++ b/tests/api/test_tls_bounds.c @@ -1022,7 +1022,8 @@ static int test_TLSX_CSR_write_getsize_status_cb(WOLFSSL* ssl, void* arg) int test_TLSX_CSR_write_getsize_bounds(void) { #if defined(TEST_TLS_BOUNDS_CSR_STATUS_CB) && \ - defined(HAVE_TLS_EXTENSIONS) + defined(HAVE_TLS_EXTENSIONS) && \ + !defined(NO_CERTS) && !defined(NO_RSA) && !defined(NO_FILESYSTEM) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -1150,7 +1151,8 @@ int test_TLSX_CSR_write_getsize_bounds(void) int test_TLSX_CSR_SetResponseWithStatusCB_bounds(void) { #if defined(TEST_TLS_BOUNDS_CSR_STATUS_CB) && \ - defined(HAVE_TLS_EXTENSIONS) + defined(HAVE_TLS_EXTENSIONS) && \ + !defined(NO_CERTS) && !defined(NO_RSA) && !defined(NO_FILESYSTEM) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -1445,7 +1447,8 @@ static int test_ProcessChainOCSPRequest_setup(WOLFSSL_CTX** pctx, int test_ProcessChainOCSPRequest_bounds(void) { #if defined(TEST_TLS_BOUNDS_OCSP_CHAIN) && \ - defined(HAVE_TLS_EXTENSIONS) + defined(HAVE_TLS_EXTENSIONS) && \ + !defined(NO_CERTS) && !defined(NO_RSA) && !defined(NO_FILESYSTEM) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -1997,7 +2000,8 @@ int test_TLSX_CSR_Parse_bounds(void) { #if defined(TEST_TLS_BOUNDS_CSR_PARSE) && \ defined(HAVE_TLS_EXTENSIONS) && \ - !defined(WOLFSSL_NO_TLS12) + !defined(WOLFSSL_NO_TLS12) && \ + !defined(NO_CERTS) && !defined(NO_RSA) && !defined(NO_FILESYSTEM) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -2101,7 +2105,8 @@ int test_TLSX_CSR2_Parse_bounds(void) { #if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2) && !defined(NO_WOLFSSL_SERVER) && \ defined(HAVE_TLS_EXTENSIONS) && \ - !defined(WOLFSSL_NO_TLS12) + !defined(WOLFSSL_NO_TLS12) && \ + !defined(NO_CERTS) && !defined(NO_RSA) && !defined(NO_FILESYSTEM) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -2342,7 +2347,8 @@ int test_TLSX_WriteRequest_length_prefix_bounds(void) int test_TLSX_WriteResponse_bounds(void) { #if defined(WOLFSSL_TEST_STATIC_BUILD) && defined(HAVE_EXTENDED_MASTER) && !defined(NO_WOLFSSL_SERVER) && !defined(WOLFSSL_NO_TLS12) && \ - defined(HAVE_TLS_EXTENSIONS) + defined(HAVE_TLS_EXTENSIONS) && \ + !defined(NO_CERTS) && !defined(NO_RSA) && !defined(NO_FILESYSTEM) EXPECT_DECLS; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; diff --git a/tests/api/test_tls_msgtype.c b/tests/api/test_tls_msgtype.c index c480696fe47..8cdeb09b72d 100644 --- a/tests/api/test_tls_msgtype.c +++ b/tests/api/test_tls_msgtype.c @@ -1182,7 +1182,7 @@ int test_tls_msgtype_connection_id(void) { EXPECT_DECLS; #if defined(WOLFSSL_DTLS_CID) && !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) && \ - defined(HAVE_TLS_EXTENSIONS) + defined(HAVE_TLS_EXTENSIONS) && defined(WOLFSSL_TLS13) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte buf[8]; diff --git a/tests/api/test_tls_parse.c b/tests/api/test_tls_parse.c index baff3de2ee6..7ae9f117df0 100644 --- a/tests/api/test_tls_parse.c +++ b/tests/api/test_tls_parse.c @@ -406,7 +406,8 @@ int test_TLSX_TCA_parse(void) { EXPECT_DECLS; #if defined(HAVE_TRUSTED_CA) && defined(HAVE_TLS_EXTENSIONS) && !defined(NO_TLS) && !defined(NO_WOLFSSL_SERVER) && !defined(NO_SHA) && \ - !defined(WOLFSSL_NO_TLS12) + !defined(WOLFSSL_NO_TLS12) && \ + !defined(NO_WOLFSSL_CLIENT) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte ext[64]; @@ -861,7 +862,8 @@ int test_TLSX_SecureRenegotiation_parse(void) #if defined(HAVE_SECURE_RENEGOTIATION) && !defined(NO_TLS) && !defined(NO_WOLFSSL_SERVER) && defined(WOLFSSL_TEST_STATIC_BUILD) && \ defined(HAVE_TLS_EXTENSIONS) && \ !defined(WOLFSSL_NO_TLS12) && \ - defined(USE_WOLFSSL_MEMORY) + defined(USE_WOLFSSL_MEMORY) && \ + !defined(NO_WOLFSSL_CLIENT) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte ext[8 + 2 * TLS_FINISHED_SZ]; @@ -1605,7 +1607,8 @@ int test_TLSX_SNI_parse(void) EXPECT_DECLS; #if defined(HAVE_SNI) && !defined(NO_TLS) && !defined(NO_WOLFSSL_SERVER) && (!defined(NO_RSA) || defined(HAVE_ECC)) && \ defined(HAVE_TLS_EXTENSIONS) && \ - !defined(WOLFSSL_NO_TLS12) + !defined(WOLFSSL_NO_TLS12) && \ + !defined(NO_WOLFSSL_CLIENT) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; byte ext[64]; @@ -2500,7 +2503,8 @@ int test_TLSX_KeyShare_gen(void) EXPECT_DECLS; #if defined(WOLFSSL_TLS13) && defined(WOLFSSL_TEST_STATIC_BUILD) && \ defined(HAVE_TLS_EXTENSIONS) && \ - defined(USE_WOLFSSL_MEMORY) + defined(USE_WOLFSSL_MEMORY) && \ + !defined(NO_WOLFSSL_CLIENT) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -2721,7 +2725,8 @@ int test_TLSX_KeyShare_freesizewrite(void) { EXPECT_DECLS; #if defined(WOLFSSL_TLS13) && defined(HAVE_SUPPORTED_CURVES) && !defined(NO_DH) && defined(HAVE_FFDHE_2048) && defined(WOLFSSL_TEST_STATIC_BUILD) && \ - defined(HAVE_TLS_EXTENSIONS) + defined(HAVE_TLS_EXTENSIONS) && \ + !defined(NO_WOLFSSL_CLIENT) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; @@ -2873,7 +2878,8 @@ int test_TLSX_KeyShare_process(void) { EXPECT_DECLS; #if defined(WOLFSSL_TLS13) && defined(HAVE_SUPPORTED_CURVES) && defined(WOLFSSL_TEST_STATIC_BUILD) && \ - defined(HAVE_TLS_EXTENSIONS) + defined(HAVE_TLS_EXTENSIONS) && \ + !defined(NO_WOLFSSL_CLIENT) WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; diff --git a/tests/unit-mcdc/test_tls13_whitebox.c b/tests/unit-mcdc/test_tls13_whitebox.c index fa6077effeb..a59e755e136 100644 --- a/tests/unit-mcdc/test_tls13_whitebox.c +++ b/tests/unit-mcdc/test_tls13_whitebox.c @@ -742,10 +742,10 @@ static void wb_sanity_check_msgs(void) /* ------------------------------------------------------------------------- * * The RFC 8446 Section 4.4.2.2 rule on the chain this end sends: - * IsSha1SignedCert(), CheckCertChainSigAlgo() and the check at the head of + * GetCertSigAlgo(), CheckCertChainSigAlgo() and the check at the head of * SendTls13Certificate(). * - * IsSha1SignedCert() reads two fields of a DER encoded certificate, the + * GetCertSigAlgo() reads two fields of a DER encoded certificate, the * tbsCertificate (for its length only) and the signatureAlgorithm that * follows it, so the vectors below are AlgorithmIdentifiers with a one byte * placeholder in front rather than complete certificates. That is what makes @@ -817,34 +817,48 @@ static void wb_expect_sha1(const char* what, int got, int want) printf(" [wb] %s: expected %d, got %d\n", what, want, got); } +/* GetCertSigAlgo() replaced IsSha1SignedCert(): it reports the certificate's + * whole signature scheme rather than answering one question, so the SHA-1 + * question this decision table is written against is asked here, the same way + * CheckCertChainSigAlgo() asks it. */ +static int wb_sha1_signed(const byte* der, word32 derSz) +{ + byte hashAlgo = no_mac; + byte sigAlgo = invalid_sa_algo; + + if (!GetCertSigAlgo(der, derSz, &hashAlgo, &sigAlgo)) + return 0; + return hashAlgo == sha_mac; +} + static void wb_is_sha1_signed_cert(void) { /* (T,-,-) */ wb_expect_sha1("sha1WithRSAEncryption", - IsSha1SignedCert(wb_sig_sha1_rsa, (word32)sizeof(wb_sig_sha1_rsa)), 1); + wb_sha1_signed(wb_sig_sha1_rsa, (word32)sizeof(wb_sig_sha1_rsa)), 1); /* (F,T,-) */ wb_expect_sha1("ecdsa-with-SHA1", - IsSha1SignedCert(wb_sig_sha1_ecdsa, (word32)sizeof(wb_sig_sha1_ecdsa)), + wb_sha1_signed(wb_sig_sha1_ecdsa, (word32)sizeof(wb_sig_sha1_ecdsa)), 1); /* (F,F,T) */ wb_expect_sha1("id-dsa-with-sha1", - IsSha1SignedCert(wb_sig_sha1_dsa, (word32)sizeof(wb_sig_sha1_dsa)), 1); + wb_sha1_signed(wb_sig_sha1_dsa, (word32)sizeof(wb_sig_sha1_dsa)), 1); /* (F,F,F), and the (F,-,-) partner of the RSASSA-PSS decision. */ wb_expect_sha1("sha256WithRSAEncryption", - IsSha1SignedCert(wb_sig_sha256_rsa, (word32)sizeof(wb_sig_sha256_rsa)), + wb_sha1_signed(wb_sig_sha256_rsa, (word32)sizeof(wb_sig_sha256_rsa)), 0); #if defined(WC_RSA_PSS) && !defined(NO_RSA) /* (T,T,T): the parameters decode, and to SHA-1. */ wb_expect_sha1("id-RSASSA-PSS, absent parameters", - IsSha1SignedCert(wb_sig_pss_absent, (word32)sizeof(wb_sig_pss_absent)), + wb_sha1_signed(wb_sig_pss_absent, (word32)sizeof(wb_sig_pss_absent)), 1); /* (T,T,F): the signature OID matches but the parameters do not decode, so * the digest is unknown and the certificate is not treated as SHA-1. */ wb_expect_sha1("id-RSASSA-PSS, undecodable parameters", - IsSha1SignedCert(wb_sig_pss_bad, (word32)sizeof(wb_sig_pss_bad)), 0); + wb_sha1_signed(wb_sig_pss_bad, (word32)sizeof(wb_sig_pss_bad)), 0); #endif - WB_NOTE("IsSha1SignedCert signature algorithm arms driven with both " + WB_NOTE("GetCertSigAlgo signature algorithm arms driven with both " "halves of each independence pair"); } @@ -990,7 +1004,7 @@ static void wb_cert_chain_sigalgo(void) } #else static void wb_is_sha1_signed_cert(void) -{ WB_NOTE("IsSha1SignedCert not compiled in this variant; skipped"); } +{ WB_NOTE("GetCertSigAlgo not compiled in this variant; skipped"); } static void wb_cert_chain_sigalgo(void) { WB_NOTE("CheckCertChainSigAlgo not compiled in this variant; skipped"); } #endif /* WB_HAVE_SSL_FIXTURE && !NO_CERTS && !WOLFSSL_NO_SIGALG */ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 18ab83ca268..625c59e0478 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -2070,9 +2070,25 @@ WOLFSSL_LOCAL int NamedGroupIsPqcHybrid(int group); #endif #endif +/* Whether the Certificate message carries per-certificate extensions: an OCSP + * response can be stapled to each entry, and a signed_certificate_timestamp + * list attached to the end entity certificate. Every site that builds, indexes + * or frees those buffers has to agree - when only some of them were compiled + * in, the send loop stopped advancing its index and reused the leaf's entry + * for every certificate in the chain. */ +#if (defined(HAVE_CERTIFICATE_STATUS_REQUEST) || \ + defined(HAVE_SIGNED_CERT_TIMESTAMP)) && !defined(NO_WOLFSSL_SERVER) + #define WOLFSSL_CERT_ENTRY_EXTS +#endif + /* Max certificate extensions in TLS1.3 */ -#if defined(HAVE_CERTIFICATE_STATUS_REQUEST) - /* Number of extensions to set each OCSP response */ +#if defined(HAVE_CERTIFICATE_STATUS_REQUEST) || \ + defined(HAVE_SIGNED_CERT_TIMESTAMP) + /* One slot per certificate: an OCSP response can be attached to each, and + * a signed_certificate_timestamp list to the leaf. The leaf's slot cannot + * be the only one - the send loop stops advancing the index at + * MAX_CERT_EXTENSIONS, so a chain would reuse the leaf's entry and repeat + * its extensions on every certificate. */ #define MAX_CERT_EXTENSIONS (1 + MAX_CHAIN_DEPTH) #else /* Only empty extensions */ @@ -2557,6 +2573,27 @@ WOLFSSL_LOCAL int SetCipherList(const WOLFSSL_CTX* ctx, Suites* suites, WOLFSSL_LOCAL int SetCipherListFromBytes(WOLFSSL_CTX* ctx, Suites* suites, const byte* list, const int listSz); WOLFSSL_LOCAL int SetSuitesHashSigAlgo(Suites* suites, const char* list); +WOLFSSL_LOCAL int SetHashSigAlgoList(byte* hashSigAlgo, word16* hashSigAlgoSz, + const char* list); +#ifdef HAVE_CERTIFICATE_COMPRESSION +WOLFSSL_LOCAL void CertCompInvalidate(WOLFSSL_CTX* ctx); +/* Exposed so the decompression bounds and error paths can be driven directly, + * the way TLSX_Parse() is. Guarded to match the definition, and renamed on + * export for the same reason TLSX_Parse is: a test-visible symbol still lands + * in the shared library, and one without a wolf prefix fails the public + * symbol check. */ +#if !defined(NO_WOLFSSL_CLIENT) || !defined(WOLFSSL_NO_CLIENT_AUTH) +#ifdef WOLFSSL_API_PREFIX_MAP + #define DoTls13CompressedCertificate wolfSSL_DoTls13CompressedCertificate +#endif +WOLFSSL_TEST_VIS int DoTls13CompressedCertificate(WOLFSSL* ssl, byte* input, + word32* inOutIdx, + word32 totalSz); +#endif +WOLFSSL_LOCAL int TLSX_CertificateCompression_Supported(word16 alg); +WOLFSSL_LOCAL int BuildTls13CertificateBody(WOLFSSL_CTX* ctx, byte** out, + word32* outSz); +#endif #ifndef PSK_TYPES_DEFINED typedef unsigned int (*wc_psk_client_callback)(WOLFSSL*, const char*, char*, @@ -3119,6 +3156,51 @@ typedef struct Keys { typedef struct Options Options; +#if defined(HAVE_RECORD_SIZE_LIMIT) && defined(HAVE_LIBZ) +/* TLS record layer compression cannot be built alongside record_size_limit. + * The limit bounds the plaintext, but deflate may expand incompressible data, + * so the record carrying it can exceed what either end sized for and the + * receive check has no way to account for the difference. Rather than forbid + * libz outright - certificate compression is a separate RFC 8879 feature that + * needs it and does not touch the record layer - the record compression + * feature itself is compiled out, so the two can never both be active in one + * build. wolfSSL_set_compression() reports NOT_COMPILED_IN accordingly. */ +#define WOLFSSL_NO_TLS_COMPRESSION +#endif + +#ifdef HAVE_RECORD_SIZE_LIMIT +/* The bound on the value as it appears on the wire, the one unit not exposed + * in ssl.h because no caller needs it: RFC 8449 Sect. 4 lets a TLS 1.3 + * endpoint state at most 2^14+1, the full TLSInnerPlaintext including its + * content type byte. Everything above the wire counts payload instead, and + * TLSX_RecordSizeLimit_Write() and _Parse() are the only places the two units + * meet. The payload-side range and the default live beside the setters in + * wolfssl/ssl.h, where an application can reach them. */ +#define WOLFSSL_RECORD_SIZE_LIMIT_MAX_13 (MAX_RECORD_SIZE + 1) +#endif + +#ifdef HAVE_CERTIFICATE_COMPRESSION +/* configure and CMake enforce these; a user_settings.h build has no such + * gate, so say so here rather than emit an implicit declaration of + * wc_DeCompress() or an undefined reference to the TLS 1.3 helpers. */ +#ifndef HAVE_LIBZ +#error "HAVE_CERTIFICATE_COMPRESSION requires HAVE_LIBZ" +#endif +#ifndef WOLFSSL_TLS13 +#error "HAVE_CERTIFICATE_COMPRESSION requires WOLFSSL_TLS13" +#endif +#ifdef NO_CERTS +#error "HAVE_CERTIFICATE_COMPRESSION requires certificate support" +#endif + +/* Ceiling on the uncompressed_length a peer may declare, checked before any + * memory is committed so a small message cannot ask for a large allocation. + * RFC 8879 Sect. 5 calls this out as the decompression bomb defence. */ +#ifndef WOLFSSL_MAX_CERT_COMP_SZ +#define WOLFSSL_MAX_CERT_COMP_SZ MAX_CERTIFICATE_SZ +#endif +#endif /* HAVE_CERTIFICATE_COMPRESSION */ + /** TLS Extensions - RFC 6066 */ #ifdef HAVE_TLS_EXTENSIONS @@ -3133,10 +3215,13 @@ typedef struct Options Options; #define TLSXT_USE_SRTP 0x000e /* 14 */ #define TLSXT_APPLICATION_LAYER_PROTOCOL 0x0010 /* a.k.a. ALPN */ #define TLSXT_STATUS_REQUEST_V2 0x0011 /* a.k.a. OCSP stapling v2 */ +#define TLSXT_SIGNED_CERT_TIMESTAMP 0x0012 /* RFC 6962 */ #define TLSXT_CLIENT_CERTIFICATE 0x0013 /* RFC8446 */ #define TLSXT_SERVER_CERTIFICATE 0x0014 /* RFC8446 */ #define TLSXT_ENCRYPT_THEN_MAC 0x0016 /* RFC 7366 */ #define TLSXT_EXTENDED_MASTER_SECRET 0x0017 /* HELLO_EXT_EXTMS */ +#define TLSXT_COMPRESS_CERTIFICATE 0x001b /* RFC 8879 */ +#define TLSXT_RECORD_SIZE_LIMIT 0x001c /* RFC 8449 */ #define TLSXT_CERT_WITH_EXTERN_PSK 0x0021 /* RFC 9973 */ #define TLSXT_SESSION_TICKET 0x0023 #define TLSXT_PRE_SHARED_KEY 0x0029 @@ -3184,6 +3269,15 @@ typedef enum { TLSX_ENCRYPT_THEN_MAC = TLSXT_ENCRYPT_THEN_MAC, #endif TLSX_EXTENDED_MASTER_SECRET = TLSXT_EXTENDED_MASTER_SECRET, +#ifdef HAVE_CERTIFICATE_COMPRESSION + TLSX_COMPRESS_CERTIFICATE = TLSXT_COMPRESS_CERTIFICATE, +#endif +#ifdef HAVE_RECORD_SIZE_LIMIT + TLSX_RECORD_SIZE_LIMIT = TLSXT_RECORD_SIZE_LIMIT, +#endif +#ifdef HAVE_SIGNED_CERT_TIMESTAMP + TLSX_SIGNED_CERT_TIMESTAMP = TLSXT_SIGNED_CERT_TIMESTAMP, +#endif TLSX_SESSION_TICKET = TLSXT_SESSION_TICKET, #ifdef WOLFSSL_TLS13 #ifdef WOLFSSL_EARLY_DATA @@ -4164,8 +4258,25 @@ struct WOLFSSL_CTX { #ifndef NO_CERTS DerBuffer* certificate; DerBuffer* certChain; - int certChainCnt; /* chain after self, in DER, with leading size for each cert */ + int certChainCnt; +#ifdef HAVE_CERTIFICATE_COMPRESSION + /* Certificate message body compressed once by + * wolfSSL_CTX_compress_certs() and reused by every handshake that + * negotiates the matching algorithm. RFC 8879 leaves compressing to the + * sender's discretion, so an empty cache simply means the plain + * Certificate message is sent. */ + byte* certComp; /* compressed Certificate body */ + word32 certCompSz; /* its length */ + word32 certCompPlainSz; /* length before compression */ + /* Shape of the certificate and chain the cache was built from, so a + * replacement that failed to invalidate cannot go out as a stale + * message. */ + word32 certCompCertSz; + word32 certCompChainSz; + int certCompChainCnt; + byte certCompAlgo; /* algorithm it was compressed with */ +#endif #ifndef WOLFSSL_NO_CA_NAMES WOLF_STACK_OF(WOLFSSL_X509_NAME)* client_ca_names; WOLF_STACK_OF(WOLFSSL_X509_NAME)* ca_names; @@ -4209,6 +4320,25 @@ struct WOLFSSL_CTX { #endif WOLFSSL_CERT_MANAGER* cm; /* our cert manager, ctx owns SSL will use */ #endif +/* Outside the NO_CERTS section above on purpose: neither extension needs a + * certificate, at either version. A PSK-only TLS 1.3 server still sends + * EncryptedExtensions and NewSessionTicket, and record_size_limit governs + * both; a TLS 1.2 build negotiates the extension in the ServerHello with no + * certificate involved either. */ +#ifdef HAVE_RECORD_SIZE_LIMIT + word16 recordSizeLimit; /* largest record we will accept, 0 to + * not advertise a limit */ + byte recordSizeLimitSet; /* application chose it, not the + * default */ +#endif +#ifdef HAVE_SIGNED_CERT_TIMESTAMP + /* SignedCertificateTimestampList a server presents, exactly as the + * application supplied it (RFC 6962 Sect. 3.3). wolfSSL delivers the + * bytes and leaves validation to the application, which needs a log list + * and policy this library does not carry. */ + byte* sctList; + word16 sctListSz; +#endif #ifdef KEEP_OUR_CERT WOLFSSL_X509* ourCert; /* keep alive a X509 struct of cert */ int ownOurCert; /* Dispose of certificate if we own */ @@ -4408,6 +4538,11 @@ struct WOLFSSL_CTX { #ifdef WOLFSSL_TLS13 word16 group[WOLFSSL_MAX_GROUP_COUNT]; byte numGroups; + /* signature_algorithms_cert this end offers, set by + * wolfSSL_CTX_set1_sigalgs_cert_list(). Empty means the extension is not + * sent and signature_algorithms covers certificates too. */ + word16 ourCertSigAlgoSz; + byte ourCertSigAlgo[WOLFSSL_MAX_SIGALGO]; #endif #ifdef WOLFSSL_EARLY_DATA word32 maxEarlyDataSz; @@ -6610,9 +6745,57 @@ struct WOLFSSL { #endif word16 pssAlgo; #ifdef WOLFSSL_TLS13 + /* signature_algorithms_cert received from the peer, in its ClientHello or + * CertificateRequest. Read by SetPeerSha1CertOk(); never sent. */ word16 certHashSigAlgoSz; /* SigAlgoCert ext length in bytes */ - byte certHashSigAlgo[WOLFSSL_MAX_SIGALGO]; /* cert sig/algo to - * offer */ + byte certHashSigAlgo[WOLFSSL_MAX_SIGALGO]; /* peer's cert + * sig/algo */ + /* signature_algorithms_cert this end offers, inherited from the context + * and overridable with wolfSSL_set1_sigalgs_cert_list(). */ + word16 ourCertSigAlgoSz; + byte ourCertSigAlgo[WOLFSSL_MAX_SIGALGO]; +#endif +#ifdef HAVE_CERTIFICATE_COMPRESSION + /* Certificate compression algorithm the peer offered that this build can + * also produce, 0 when there is none. Set while parsing the peer's + * compress_certificate extension. */ + byte peerCertCompAlgo; + /* Algorithm the peer's Certificate message actually arrived compressed + * with, 0 when it was sent plain. */ + byte certCompAdvertised; /* offered in this handshake */ + /* Dimensions of the context cache as it stood when this message began, + * so a context mutated part way through a fragmented send is caught + * rather than silently truncating the message. */ + word32 certCompSendSz; + byte certCompSendAlgo; + byte certCompUsed; + /* Set while this end is emitting a CompressedCertificate. The choice + * between the plain and compressed message is made once, when the first + * fragment goes out, and has to survive a WANT_WRITE: re-deciding on + * re-entry would resume a half sent message on the other path. */ + byte sendingCompCert; +#endif +#ifdef HAVE_SIGNED_CERT_TIMESTAMP + byte* sctList; /* list this end presents */ + word16 sctListSz; + byte* peerSctList; /* list the peer presented, owned here */ + word16 peerSctListSz; + byte sctRequested; /* peer asked us for one */ + /* sctList is a snapshot of the context's list rather than one the + * application set on this object. It is dropped by wolfSSL_clear() so a + * recycled object picks the context's list up again; a list set on the + * object is this object's configuration and survives. */ + byte sctListFromCtx; +#endif +#ifdef HAVE_RECORD_SIZE_LIMIT + /* RFC 8449. recordSizeLimit is what this end advertised and so enforces on + * arrival; peerRecordSizeLimit is what the peer advertised and so caps + * what this end sends. Both 0 when the extension was not exchanged. */ + word16 recordSizeLimit; + word16 peerRecordSizeLimit; + /* Whether recordSizeLimit came from the application or is the built-in + * default, which decides who yields to max_fragment_length. */ + byte recordSizeLimitSet; #endif #if defined(HAVE_ECC) || defined(HAVE_ED25519) || defined(HAVE_ED448) int eccVerifyRes; @@ -7184,6 +7367,7 @@ enum HandShakeType { finished = 20, certificate_status = 22, key_update = 24, + compressed_certificate = 25, /* RFC 8879 */ change_cipher_hs = 55, /* simulate unique handshake type for sanity checks. record layer change_cipher conflicts with handshake finished */ @@ -7907,7 +8091,7 @@ WOLFSSL_LOCAL int crypto_ex_cb_dup_data(const WOLFSSL_CRYPTO_EX_DATA *in, WOLFSSL_CRYPTO_EX_DATA *out, CRYPTO_EX_cb_ctx* cb_ctx); WOLFSSL_LOCAL int wolfssl_local_get_ex_new_index(int class_index, long ctx_l, void* ctx_ptr, WOLFSSL_CRYPTO_EX_new* new_func, - WOLFSSL_CRYPTO_EX_dup* dup_func, WOLFSSL_CRYPTO_EX_free* free_func); + WOLFSSL_CRYPTO_EX_dup* dup_func, WOLFSSL_CRYPTO_EX_free* free_cb); #endif /* HAVE_EX_DATA_CRYPTO */ WOLFSSL_LOCAL WC_RNG* wolfssl_get_global_rng(void); diff --git a/wolfssl/openssl/ssl.h b/wolfssl/openssl/ssl.h index 762eb5833fa..d2927623179 100644 --- a/wolfssl/openssl/ssl.h +++ b/wolfssl/openssl/ssl.h @@ -417,6 +417,23 @@ typedef STACK_OF(ACCESS_DESCRIPTION) AUTHORITY_INFO_ACCESS; #define SSL_CTX_set1_sigalgs_list wolfSSL_CTX_set1_sigalgs_list #define SSL_set1_sigalgs_list wolfSSL_set1_sigalgs_list +#ifdef WOLFSSL_TLS13 +#define SSL_CTX_set1_sigalgs_cert_list wolfSSL_CTX_set1_sigalgs_cert_list +#define SSL_set1_sigalgs_cert_list wolfSSL_set1_sigalgs_cert_list +#endif +#ifdef HAVE_CERTIFICATE_COMPRESSION +/* RFC 8879, spelled as OpenSSL spells it. */ +#define SSL_CTX_compress_certs wolfSSL_CTX_compress_certs +#endif +#ifdef HAVE_SIGNED_CERT_TIMESTAMP +/* RFC 6962, spelled as BoringSSL spells it. */ +#define SSL_CTX_set_signed_cert_timestamp_list \ + wolfSSL_CTX_set_signed_cert_timestamp_list +#define SSL_set_signed_cert_timestamp_list \ + wolfSSL_set_signed_cert_timestamp_list +#define SSL_get0_signed_cert_timestamp_list \ + wolfSSL_get0_signed_cert_timestamp_list +#endif #define SSL_get_signature_nid wolfSSL_get_signature_nid #define SSL_get_signature_type_nid wolfSSL_get_signature_type_nid #define SSL_get_peer_signature_nid wolfSSL_get_peer_signature_nid diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index 84d8963ae71..f6d19ac4711 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -1447,7 +1447,52 @@ WOLFSSL_API int wolfSSL_get_peer_signature_type_nid(const WOLFSSL* ssl, WOLFSSL_API int wolfSSL_CTX_set1_sigalgs_list(WOLFSSL_CTX* ctx, const char* list); WOLFSSL_API int wolfSSL_set1_sigalgs_list(WOLFSSL* ssl, const char* list); +#ifdef WOLFSSL_TLS13 +WOLFSSL_API int wolfSSL_CTX_set1_sigalgs_cert_list(WOLFSSL_CTX* ctx, + const char* list); +WOLFSSL_API int wolfSSL_set1_sigalgs_cert_list(WOLFSSL* ssl, + const char* list); +#endif /* WOLFSSL_TLS13 */ +#endif /* OPENSSL_EXTRA */ +/* Declared outside the OpenSSL compatibility block: these have their own + * build options and no dependency on it. */ +#ifdef HAVE_RECORD_SIZE_LIMIT +/* Range accepted by the two setters below, in payload bytes, and the value + * that stops the extension being advertised at all. */ +#define WOLFSSL_RECORD_SIZE_LIMIT_OFF 0 +#define WOLFSSL_RECORD_SIZE_LIMIT_MIN 64 +#define WOLFSSL_RECORD_SIZE_LIMIT_MAX 16384 +/* Advertised unless a setter says otherwise. */ +#define WOLFSSL_RECORD_SIZE_LIMIT_DEFAULT WOLFSSL_RECORD_SIZE_LIMIT_MAX + +WOLFSSL_API int wolfSSL_UseRecordSizeLimit(WOLFSSL* ssl, unsigned short limit); +WOLFSSL_API int wolfSSL_CTX_UseRecordSizeLimit(WOLFSSL_CTX* ctx, + unsigned short limit); +#endif +#ifdef HAVE_CERTIFICATE_COMPRESSION +/* RFC 8879 Sect. 3 algorithms, for the alg argument below. Only + * WOLFSSL_CERT_COMP_ZLIB is implemented; brotli and zstd would each pull in a + * new dependency. */ +#define WOLFSSL_CERT_COMP_ZLIB 1 +#define WOLFSSL_CERT_COMP_BROTLI 2 +#define WOLFSSL_CERT_COMP_ZSTD 3 + +WOLFSSL_API int wolfSSL_CTX_compress_certs(WOLFSSL_CTX* ctx, int alg); +/* Algorithm the peer's Certificate arrived compressed with, 0 if it was not + * compressed. */ +WOLFSSL_API int wolfSSL_get_certificate_compression_used(WOLFSSL* ssl); +#endif +#ifdef HAVE_SIGNED_CERT_TIMESTAMP +WOLFSSL_API int wolfSSL_CTX_set_signed_cert_timestamp_list(WOLFSSL_CTX* ctx, + const unsigned char* list, unsigned short sz); +/* Whether the peer asked this end for signed certificate timestamps. */ +WOLFSSL_API int wolfSSL_signed_cert_timestamp_requested(WOLFSSL* ssl); +WOLFSSL_API int wolfSSL_set_signed_cert_timestamp_list(WOLFSSL* ssl, + const unsigned char* list, unsigned short sz); +WOLFSSL_API unsigned short wolfSSL_get0_signed_cert_timestamp_list( + WOLFSSL* ssl, const unsigned char** list); #endif + WOLFSSL_ABI WOLFSSL_API WOLFSSL* wolfSSL_new(WOLFSSL_CTX* ctx); WOLFSSL_API WOLFSSL_CTX* wolfSSL_get_SSL_CTX(const WOLFSSL* ssl); WOLFSSL_API WOLFSSL_X509_VERIFY_PARAM* wolfSSL_CTX_get0_param(WOLFSSL_CTX* ctx); @@ -1682,15 +1727,15 @@ WOLFSSL_API int wolfSSL_CTX_get_ex_new_index( long idx, void* arg, WOLFSSL_CRYPTO_EX_new* new_func, WOLFSSL_CRYPTO_EX_dup* dup_func, - WOLFSSL_CRYPTO_EX_free* free_func); + WOLFSSL_CRYPTO_EX_free* free_cb); WOLFSSL_API int wolfSSL_CRYPTO_get_ex_new_index( int class_index, long argl, void *argp, WOLFSSL_CRYPTO_EX_new* new_func, WOLFSSL_CRYPTO_EX_dup* dup_func, - WOLFSSL_CRYPTO_EX_free* free_func); + WOLFSSL_CRYPTO_EX_free* free_cb); WOLFSSL_API int wolfSSL_SESSION_get_ex_new_index(long ctx_l,void* ctx_ptr, WOLFSSL_CRYPTO_EX_new* new_func, WOLFSSL_CRYPTO_EX_dup* dup_func, - WOLFSSL_CRYPTO_EX_free* free_func); + WOLFSSL_CRYPTO_EX_free* free_cb); #endif /* HAVE_EX_DATA_CRYPTO */ #endif /* HAVE_EX_DATA */ @@ -1711,7 +1756,7 @@ WOLFSSL_API int wolfSSL_X509_set_ex_data_with_cleanup( WOLFSSL_API int wolfSSL_X509_get_ex_new_index(int idx, void *arg, WOLFSSL_CRYPTO_EX_new* new_func, WOLFSSL_CRYPTO_EX_dup* dup_func, - WOLFSSL_CRYPTO_EX_free* free_func); + WOLFSSL_CRYPTO_EX_free* free_cb); #endif #endif /* OPENSSL_EXTRA || OPENSSL_EXTRA_X509_SMALL */