diff --git a/doc/modules/ROOT/pages/3.tutorials/3d.tls-context.adoc b/doc/modules/ROOT/pages/3.tutorials/3d.tls-context.adoc index a1b404f61..270ada7fc 100644 --- a/doc/modules/ROOT/pages/3.tutorials/3d.tls-context.adoc +++ b/doc/modules/ROOT/pages/3.tutorials/3d.tls-context.adoc @@ -36,23 +36,11 @@ handles: * Configuring trust anchors for peer verification * Setting protocol versions and cipher suites * Configuring certificate verification behavior -* Managing revocation checking (CRL, OCSP) +* Managing revocation checking (CRLs) Use `tls_context` to configure TLS settings once, then pass it to TLS streams for establishing secure connections. -[WARNING] -.Not yet implemented -==== -Some settings shown in this tutorial are accepted by the API but *not yet -wired up* by the backends; they are stored and silently ignored: -`set_alpn()`, `set_min_protocol_version()` / `set_max_protocol_version()`, -all revocation features (`add_crl*`, `set_ocsp_staple` / -`set_require_ocsp_staple`, `set_revocation_policy`), and `use_pkcs12*` -(returns `std::errc::function_not_supported`). `set_ciphersuites()` is -applied by OpenSSL only; WolfSSL ignores it. -==== - [NOTE] ==== Two implemented features depend on how WolfSSL was built: @@ -146,9 +134,10 @@ into a single password-protected file: ctx.use_pkcs12_file( "credentials.pfx", "bundle-password" ); ---- -NOTE: PKCS#12 loading is not yet implemented; `use_pkcs12()` and -`use_pkcs12_file()` return `std::errc::function_not_supported`. Load the -certificate and key separately for now. +NOTE: The bundle is decoded when the first stream is created; a wrong +passphrase or malformed bundle surfaces as a handshake failure. +Intermediate certificates in the bundle are loaded and sent during the +handshake on both backends. === Loading from Memory @@ -281,24 +270,29 @@ ctx.set_min_protocol_version( tls_version::tls_1_3 ); ctx.set_max_protocol_version( tls_version::tls_1_3 ); ---- -NOTE: Protocol version bounds are not yet applied by the backends. The -negotiated range is whatever the native default method provides. +NOTE: On WolfSSL the ceiling is enforced by selecting a version-specific +method (no native set-max call exists); a window whose minimum exceeds its +maximum yields a context that fails the handshake. === Cipher Suites -Configure allowed cipher suites using OpenSSL-style syntax: +TLS 1.2-and-below suites use an OpenSSL-style cipher list; TLS 1.3 suites +are configured separately: [source,cpp] ---- -// Strong cipher suites only +// TLS 1.2 and below ctx.set_ciphersuites( "ECDHE+AESGCM:ECDHE+CHACHA20" ); -// Disable weak ciphers -ctx.set_ciphersuites( "HIGH:!aNULL:!MD5:!RC4" ); +// TLS 1.3 (distinct API and suite names) +ctx.set_ciphersuites_tls13( "TLS_AES_256_GCM_SHA384" ); ---- -NOTE: `set_ciphersuites()` is applied by the OpenSSL backend only; the -WolfSSL backend accepts the string but silently ignores it. +NOTE: Both backends apply these (WolfSSL merges the two into a single +list). Suite *names* differ between backends: OpenSSL uses +`TLS_AES_128_GCM_SHA256`, WolfSSL uses `TLS13-AES128-GCM-SHA256`. The +OpenSSL security level is left at the library default; include a +`@SECLEVEL=` token in the cipher string if you need a lower level. === ALPN (Application-Layer Protocol Negotiation) @@ -311,13 +305,18 @@ ctx.set_alpn( { "h2", "http/1.1" } ); // gRPC ctx.set_alpn( { "h2" } ); +---- + +Read the negotiated protocol from the stream after the handshake: -// Custom protocol -ctx.set_alpn( { "my-protocol/1.0" } ); +[source,cpp] +---- +std::string_view proto = stream.alpn_protocol(); // "h2", or empty if none ---- -NOTE: ALPN is not yet wired up; the protocol list is accepted but never -negotiated by either backend. +NOTE: On WolfSSL, ALPN requires a `HAVE_ALPN` build; without it, offering +protocols fails the handshake with `std::errc::function_not_supported` +rather than negotiate nothing silently. The server selects from the client's list based on its own preferences. @@ -414,31 +413,23 @@ Which certificates the callback sees depends on the backend: == Revocation Checking Certificate revocation checking verifies that certificates haven't been -invalidated by the issuing CA. Two mechanisms are supported: CRL and OCSP. - -[WARNING] -==== -None of the revocation features in this section are wired up yet. -`set_revocation_policy()`, CRLs (`add_crl()` / `add_crl_file()`), and OCSP -stapling (`set_ocsp_staple()` / `set_require_ocsp_staple()`) are all -accepted but inert. In particular, `set_require_ocsp_staple(true)` does -*not* fail the handshake when no staple is present, and `soft_fail` / -`hard_fail` do not change verification behavior. -==== +invalidated by the issuing CA. Revocation is checked against Certificate +Revocation Lists (CRLs). === Revocation Policy -Set the overall revocation checking behavior: +Set the overall revocation checking behavior. CRLs are consulted only when +the policy is not `disabled`: [source,cpp] ---- // Don't check revocation (default) ctx.set_revocation_policy( tls_revocation_policy::disabled ); -// Check but allow if status is unknown (lenient) +// Accept unknown status, reject a listed (revoked) certificate ctx.set_revocation_policy( tls_revocation_policy::soft_fail ); -// Require successful revocation check (strict) +// Also reject when status can't be determined (strict) ctx.set_revocation_policy( tls_revocation_policy::hard_fail ); ---- @@ -454,33 +445,15 @@ ctx.add_crl_file( "/path/to/issuer.crl" ); // From memory (e.g., fetched via HTTP) std::string crl_data = fetch_crl_from_url( crl_url ); ctx.add_crl( crl_data ); ----- - -CRLs must be refreshed periodically as they expire. - -=== OCSP Stapling - -OCSP provides per-certificate revocation status without downloading -large CRLs. - -**Server-side** (provide pre-fetched OCSP response): - -[source,cpp] ----- -// Fetch OCSP response for your certificate -std::string ocsp_response = fetch_ocsp_response(); -// Provide to clients during handshake -ctx.set_ocsp_staple( ocsp_response ); +ctx.set_revocation_policy( tls_revocation_policy::hard_fail ); ---- -**Client-side** (require server to staple): +CRLs must be refreshed periodically as they expire. -[source,cpp] ----- -// Fail if server doesn't provide OCSP staple -ctx.set_require_ocsp_staple( true ); ----- +NOTE: On WolfSSL, CRL checking requires a `HAVE_CRL` build; without it, +supplying a CRL or a non-disabled policy fails the handshake with +`std::errc::function_not_supported`. === Bootstrap vs. Hardened Connections @@ -489,10 +462,9 @@ A common pattern uses two context configurations: [NOTE] ==== Both contexts below verify the peer via `set_default_verify_paths()` and -`set_verify_mode( tls_verify_mode::peer )`. The revocation hardening on the -"hardened" path (`add_crl_file()` / `set_revocation_policy()`) is *not yet -applied* by the backends, so that context currently offers the same -verification as the bootstrap one. +`set_verify_mode( tls_verify_mode::peer )`. The "hardened" path adds CRL +checking (`add_crl_file()` / `set_revocation_policy( hard_fail )`), which +is applied on OpenSSL and on WolfSSL builds with `HAVE_CRL`. ==== [source,cpp] @@ -576,8 +548,7 @@ scenarios. NOTE: This example trusts public CAs via `set_default_verify_paths()`. On WolfSSL builds without `WOLFSSL_SYS_CA_CERTS`, load an explicit CA bundle instead, for example -`ctx.load_verify_file( "/etc/ssl/certs/ca-certificates.crt" );`. The -`set_min_protocol_version()` call is inert in this release. +`ctx.load_verify_file( "/etc/ssl/certs/ca-certificates.crt" );`. [source,cpp] ---- diff --git a/doc/modules/ROOT/pages/4.guide/4l.tls.adoc b/doc/modules/ROOT/pages/4.guide/4l.tls.adoc index 510988f72..75dde6984 100644 --- a/doc/modules/ROOT/pages/4.guide/4l.tls.adoc +++ b/doc/modules/ROOT/pages/4.guide/4l.tls.adoc @@ -56,23 +56,12 @@ Trust anchors can also be supplied explicitly with `add_certificate_authority()`, `load_verify_file()`, or `add_verify_path()`, and `set_verify_callback()` installs a custom verification hook. -[WARNING] -.Not yet implemented -==== -Several other `tls_context` settings are accepted by the API but are *not -yet wired up* by the OpenSSL or WolfSSL backends in this release. They are -stored and silently ignored: - -* `set_alpn()` — ALPN is never negotiated. -* `set_min_protocol_version()` / `set_max_protocol_version()` — version - bounds are never applied; the range is the native default. -* `add_crl()` / `add_crl_file()`, `set_ocsp_staple()` / - `set_require_ocsp_staple()`, `set_revocation_policy()` — all - revocation checking is inert. -* `use_pkcs12()` / `use_pkcs12_file()` — return - `std::errc::function_not_supported`. -* `set_ciphersuites()` — applied by OpenSSL only; WolfSSL ignores it. -==== +Protocol version bounds (`set_min_protocol_version()` / +`set_max_protocol_version()`), cipher suites (`set_ciphersuites()` and +`set_ciphersuites_tls13()`), ALPN (`set_alpn()`, read back with +`alpn_protocol()`), PKCS#12 credentials (`use_pkcs12()` / +`use_pkcs12_file()`), and CRL-based revocation (`add_crl()` / +`add_crl_file()` with `set_revocation_policy()`) are all supported. [NOTE] ==== @@ -183,9 +172,10 @@ PKCS#12 (`.pfx` or `.p12`) files bundle certificate, key, and chain together: ctx.use_pkcs12_file("credentials.pfx", "bundle-password"); ---- -NOTE: PKCS#12 loading is not yet implemented; `use_pkcs12()` and -`use_pkcs12_file()` return `std::errc::function_not_supported`. Load the -certificate and key separately for now. +NOTE: The bundle is decoded when the first stream is created; a malformed +bundle or wrong passphrase surfaces as a handshake failure. Intermediate +certificates in the bundle are loaded and sent during the handshake on +both backends. === Trust Anchors @@ -238,8 +228,9 @@ ctx.set_min_protocol_version(tls_version::tls_1_3); ctx.set_max_protocol_version(tls_version::tls_1_2); ---- -NOTE: Protocol version bounds are not yet applied by the backends. The -negotiated range is whatever the native default method provides. +NOTE: On WolfSSL the ceiling is applied by selecting a version-specific +method (there is no native set-max call); a window whose minimum exceeds +its maximum yields a context that fails the handshake. Available versions: @@ -258,25 +249,36 @@ Available versions: [source,cpp] ---- -// OpenSSL-style cipher string +// TLS 1.2-and-below cipher list (OpenSSL syntax) ctx.set_ciphersuites("ECDHE+AESGCM:ECDHE+CHACHA20"); +// TLS 1.3 cipher suites (configured separately) +ctx.set_ciphersuites_tls13("TLS_AES_256_GCM_SHA384"); ---- -NOTE: `set_ciphersuites()` is applied by the OpenSSL backend only; the -WolfSSL backend accepts the string but silently ignores it. +NOTE: `set_ciphersuites()` covers TLS 1.2 and below; TLS 1.3 suites use +`set_ciphersuites_tls13()`. Both backends apply them (WolfSSL merges the +two into one list). Suite *names* differ between backends (OpenSSL +`TLS_AES_128_GCM_SHA256` vs WolfSSL `TLS13-AES128-GCM-SHA256`). The OpenSSL +security level is left at the library default; express a lower level +explicitly with a `@SECLEVEL=` token if required. ==== ALPN -Application-Layer Protocol Negotiation selects the application protocol: +Application-Layer Protocol Negotiation selects the application protocol. +Read the negotiated protocol after the handshake with +`stream.alpn_protocol()`: [source,cpp] ---- // Prefer HTTP/2, fall back to HTTP/1.1 ctx.set_alpn({"h2", "http/1.1"}); +// ... after handshake: +std::string_view proto = stream.alpn_protocol(); // e.g. "h2", or empty ---- -NOTE: ALPN is not yet wired up; the protocol list is accepted but never -negotiated by either backend. +NOTE: On WolfSSL, ALPN requires a `HAVE_ALPN` build; without it, offering +protocols fails the handshake with `std::errc::function_not_supported` +rather than negotiate nothing silently. === Certificate Verification @@ -361,53 +363,27 @@ Which certificates the callback sees depends on the backend: === Revocation Checking -WARNING: None of the revocation features below are wired up yet. CRLs -(`add_crl()` / `add_crl_file()`), OCSP stapling (`set_ocsp_staple()` / -`set_require_ocsp_staple()`), and `set_revocation_policy()` are all accepted -but inert. In particular, `set_require_ocsp_staple(true)` does *not* fail the -handshake when no staple is present, and `soft_fail` / `hard_fail` do not -change verification behavior. - -==== Certificate Revocation Lists +Revocation is checked against Certificate Revocation Lists (CRLs). CRLs +are consulted only when a revocation policy other than `disabled` is set. [source,cpp] ---- -// Load CRL from file +// Load a CRL (PEM or DER), from file or memory ctx.add_crl_file("issuer.crl"); - -// Load CRL from memory ctx.add_crl(crl_data); ----- - -==== OCSP Stapling - -For servers, provide a stapled OCSP response: - -[source,cpp] ----- -ctx.set_ocsp_staple(ocsp_response_data); ----- - -For clients, require the server to staple: - -[source,cpp] ----- -ctx.set_require_ocsp_staple(true); ----- -==== Revocation Policy - -[source,cpp] +// Choose how strict to be +ctx.set_revocation_policy(tls_revocation_policy::hard_fail); ---- -// Don't check revocation (default) -ctx.set_revocation_policy(tls_revocation_policy::disabled); -// Check but allow if status unknown -ctx.set_revocation_policy(tls_revocation_policy::soft_fail); +Under `soft_fail`, a certificate whose status cannot be determined +(missing or expired CRL) is accepted, but one that is actually listed as +revoked is rejected. Under `hard_fail`, an undeterminable status is also +rejected. `disabled` (the default) skips revocation entirely. -// Fail if revocation status can't be determined -ctx.set_revocation_policy(tls_revocation_policy::hard_fail); ----- +NOTE: On WolfSSL, CRL checking requires a `HAVE_CRL` build; without it, +supplying a CRL or a non-disabled policy fails the handshake with +`std::errc::function_not_supported` rather than skip the check silently. == TLS Streams diff --git a/example/tls_context_examples.cpp b/example/tls_context_examples.cpp index f6c919956..dda62faa5 100644 --- a/example/tls_context_examples.cpp +++ b/example/tls_context_examples.cpp @@ -250,34 +250,6 @@ tls_context make_client_with_crl( std::string_view crl_path ) return ctx; } -// Client requiring OCSP stapling -tls_context make_client_require_ocsp() -{ - tls_context ctx; - - must(ctx.set_default_verify_paths()); - must(ctx.set_verify_mode( tls_verify_mode::peer )); - - // Require server to provide OCSP staple - ctx.set_require_ocsp_staple( true ); - - return ctx; -} - -// Server with OCSP stapling -tls_context make_server_with_ocsp( std::string_view ocsp_response ) -{ - tls_context ctx; - - must(ctx.use_certificate_chain_file( "server.crt" )); - must(ctx.use_private_key_file( "server.key", tls_file_format::pem )); - - // Provide pre-fetched OCSP response to clients - must(ctx.set_ocsp_staple( ocsp_response )); - - return ctx; -} - // Strict revocation checking tls_context make_hardened_client() { @@ -289,9 +261,6 @@ tls_context make_hardened_client() // Fail if revocation status cannot be determined ctx.set_revocation_policy( tls_revocation_policy::hard_fail ); - // Require OCSP staple from server - ctx.set_require_ocsp_staple( true ); - return ctx; } diff --git a/include/boost/corosio/openssl_stream.hpp b/include/boost/corosio/openssl_stream.hpp index 471f70a7a..6c474b631 100644 --- a/include/boost/corosio/openssl_stream.hpp +++ b/include/boost/corosio/openssl_stream.hpp @@ -191,6 +191,9 @@ class BOOST_COROSIO_DECL openssl_stream final : public tls_stream /// Return the TLS backend name ("openssl"). std::string_view name() const noexcept override; + /// Return the ALPN protocol negotiated during the handshake, or empty. + std::string_view alpn_protocol() const noexcept override; + protected: capy::io_task do_read_some( capy::detail::mutable_buffer_array buffers) override; diff --git a/include/boost/corosio/tls_context.hpp b/include/boost/corosio/tls_context.hpp index e13b03d7e..037b00829 100644 --- a/include/boost/corosio/tls_context.hpp +++ b/include/boost/corosio/tls_context.hpp @@ -474,13 +474,13 @@ class BOOST_COROSIO_DECL tls_context @param passphrase The password protecting the bundle. - @return Success, or an error if the bundle could not be parsed - or the passphrase is incorrect. + @return Success. The bundle is recorded and decoded into the + certificate, private key, and chain when the native context is + first built; a malformed bundle or wrong passphrase surfaces as + a handshake failure. - @note Not yet implemented in this release; returns - `std::errc::function_not_supported`. Load the certificate - and key separately via `use_certificate_chain()` and - `use_private_key()` instead. + @note Intermediate certificates inside the bundle are loaded and + sent during the handshake on both backends. @see use_pkcs12_file */ @@ -498,13 +498,13 @@ class BOOST_COROSIO_DECL tls_context @param passphrase The password protecting the file. - @return Success, or an error if the file could not be read, - parsed, or the passphrase is incorrect. + @return Success, or an error if the file could not be read. The + bundle is decoded when the native context is first built; a + malformed bundle or wrong passphrase surfaces as a handshake + failure. - @note Not yet implemented in this release; returns - `std::errc::function_not_supported`. Load the certificate - and key separately via `use_certificate_chain_file()` and - `use_private_key_file()` instead. + @note Intermediate certificates inside the bundle are loaded and + sent during the handshake on both backends. @par Example @code @@ -634,10 +634,6 @@ class BOOST_COROSIO_DECL tls_context @return Success, or an error if the version is not supported by the backend. - @note Not yet applied by the backends in this release; the bound - is accepted but has no effect. The negotiated range is - whatever the native default method provides. - @par Example @code // Require TLS 1.3 minimum @@ -658,9 +654,10 @@ class BOOST_COROSIO_DECL tls_context @return Success, or an error if the version is not supported by the backend. - @note Not yet applied by the backends in this release; the bound - is accepted but has no effect. The negotiated range is - whatever the native default method provides. + @note On WolfSSL the ceiling is applied by selecting a + version-specific method (no native set-max API exists); an + invalid window where the minimum exceeds the maximum yields a + context that fails the handshake. @see set_min_protocol_version */ @@ -682,14 +679,35 @@ class BOOST_COROSIO_DECL tls_context ctx.set_ciphersuites( "ECDHE+AESGCM:ECDHE+CHACHA20" ); @endcode - @note For TLS 1.3, use `set_ciphersuites_tls13()` on backends - that distinguish between TLS 1.2 and 1.3 cipher configuration. - - @note Applied by the OpenSSL backend only in this release; the - WolfSSL backend accepts the string but silently ignores it. + @note This configures cipher suites for TLS 1.2 and below. For + TLS 1.3, use @ref set_ciphersuites_tls13. */ std::error_code set_ciphersuites(std::string_view ciphers); + /** Set the allowed TLS 1.3 cipher suites. + + TLS 1.3 uses a distinct, fixed set of cipher suites configured + separately from earlier versions. The format is a colon-separated + list of TLS 1.3 suite names. + + @param ciphers The TLS 1.3 cipher suite list. + + @return Success, or an error if the cipher string is invalid. + + @par Example + @code + ctx.set_ciphersuites_tls13( + "TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256" ); + @endcode + + @note On the WolfSSL backend, TLS 1.2 and TLS 1.3 suites share a + single cipher list; this call and @ref set_ciphersuites are + merged into one list. + + @see set_ciphersuites + */ + std::error_code set_ciphersuites_tls13(std::string_view ciphers); + /** Set the ALPN protocol list. Configures Application-Layer Protocol Negotiation (ALPN) for @@ -703,8 +721,11 @@ class BOOST_COROSIO_DECL tls_context @return Success, or an error if ALPN configuration fails. - @note Not yet applied by the backends in this release; the - protocol list is accepted but ALPN is never negotiated. + @note Read the negotiated protocol after the handshake via + @ref tls_stream::alpn_protocol. On WolfSSL, ALPN requires a + build with `HAVE_ALPN`; without it, offering protocols fails + the handshake with `std::errc::function_not_supported` rather + than negotiate nothing silently. @par Example @code @@ -896,8 +917,11 @@ class BOOST_COROSIO_DECL tls_context @return Success, or an error if the CRL could not be parsed. - @note Not yet applied by the backends in this release; the CRL is - accepted but never used during verification. + @note CRLs are consulted only when a revocation policy is set via + @ref set_revocation_policy. On WolfSSL, CRL checking requires a + build with `HAVE_CRL`; without it, supplying a CRL or a + revocation policy fails the handshake with + `std::errc::function_not_supported`. @see add_crl_file @see set_revocation_policy @@ -914,8 +938,9 @@ class BOOST_COROSIO_DECL tls_context @return Success, or an error if the file could not be read or the CRL is invalid. - @note Not yet applied by the backends in this release; the CRL is - accepted but never used during verification. + @note CRLs are consulted only when a revocation policy is set via + @ref set_revocation_policy (WolfSSL requires a `HAVE_CRL` + build). @par Example @code @@ -927,50 +952,10 @@ class BOOST_COROSIO_DECL tls_context */ std::error_code add_crl_file(std::string_view filename); - /** Set the OCSP staple response for server-side stapling. - - For servers, provides a pre-fetched OCSP response to send - to clients during the handshake. This proves the server's - certificate hasn't been revoked without requiring the client - to contact the OCSP responder. - - The OCSP response must be periodically refreshed (typically - every few hours to days) before it expires. - - @param response The DER-encoded OCSP response. - - @return Success, or an error if the response is invalid. - - @note Not yet applied by the backends in this release; the - response is accepted but never stapled into the handshake. - - @note This is a server-side operation. Clients use - `set_require_ocsp_staple()` to require stapled responses. - */ - std::error_code set_ocsp_staple(std::string_view response); - - /** Require OCSP stapling from the server. - - For clients, requires the server to provide a stapled OCSP - response proving its certificate hasn't been revoked. If - the server doesn't provide a stapled response, the handshake - fails. - - @param require Whether to require OCSP stapling. - - @note Not yet applied by the backends in this release; the flag - is accepted but has no effect. Setting it to `true` does not - make the handshake fail when no staple is present. - - @note Not all servers support OCSP stapling. Enable this only - when connecting to servers known to support it. - */ - void set_require_ocsp_staple(bool require); - /** Set the certificate revocation checking policy. Controls how certificate revocation status is checked during - verification. This affects both CRL and OCSP checking. + verification via CRLs. @param policy The revocation checking policy. @@ -983,9 +968,14 @@ class BOOST_COROSIO_DECL tls_context ctx.set_revocation_policy( tls_revocation_policy::soft_fail ); @endcode - @note Not yet applied by the backends in this release; the policy - is accepted but has no effect. `soft_fail` and `hard_fail` do - not change verification behavior. + @note Revocation is checked via CRLs supplied with @ref add_crl / + @ref add_crl_file. `soft_fail` accepts a certificate whose + status cannot be determined (missing/expired CRL) but rejects + one that is actually revoked; `hard_fail` also rejects unknown + status. OCSP-based revocation is not available (see the TLS + guide). On WolfSSL a non-disabled policy requires a `HAVE_CRL` + build, else the handshake fails with + `std::errc::function_not_supported`. @see tls_revocation_policy @see add_crl diff --git a/include/boost/corosio/tls_stream.hpp b/include/boost/corosio/tls_stream.hpp index 3e949ebc9..d03e610e7 100644 --- a/include/boost/corosio/tls_stream.hpp +++ b/include/boost/corosio/tls_stream.hpp @@ -1,5 +1,6 @@ // // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -171,6 +172,24 @@ class BOOST_COROSIO_DECL tls_stream */ virtual std::string_view name() const noexcept = 0; + /** Returns the ALPN protocol negotiated during the handshake. + + Application-Layer Protocol Negotiation selects a single + application protocol (for example `"h2"` or `"http/1.1"`) + during the TLS handshake, from the list supplied via + @ref tls_context::set_alpn. + + @return The negotiated protocol, or an empty view if no + protocol was negotiated, ALPN was not offered, the + handshake has not completed, or the backend/build does + not support ALPN. + + @par Thread Safety + Safe to call after the handshake completes; not safe to call + concurrently with a handshake or reset. + */ + virtual std::string_view alpn_protocol() const noexcept { return {}; } + protected: tls_stream() = default; diff --git a/include/boost/corosio/wolfssl_stream.hpp b/include/boost/corosio/wolfssl_stream.hpp index 4f45bd9c2..aefa35a44 100644 --- a/include/boost/corosio/wolfssl_stream.hpp +++ b/include/boost/corosio/wolfssl_stream.hpp @@ -191,6 +191,9 @@ class BOOST_COROSIO_DECL wolfssl_stream final : public tls_stream /// Return the TLS backend name ("wolfssl"). std::string_view name() const noexcept override; + /// Return the ALPN protocol negotiated during the handshake, or empty. + std::string_view alpn_protocol() const noexcept override; + protected: capy::io_task do_read_some( capy::detail::mutable_buffer_array buffers) override; @@ -237,6 +240,35 @@ wolfssl_category() noexcept; BOOST_COROSIO_DECL bool wolfssl_supports_verify_callback() noexcept; +/** Report whether this WolfSSL build can negotiate ALPN. + + ALPN requires a WolfSSL built with `HAVE_ALPN`. On a build without + it, offering protocols via @ref tls_context::set_alpn fails the + handshake with `std::errc::function_not_supported` rather than + silently negotiating nothing. + + @return `true` if ALPN is supported by this build, `false` if + offering protocols will cause the handshake to fail. + + @see tls_context::set_alpn, tls_stream::alpn_protocol +*/ +BOOST_COROSIO_DECL bool +wolfssl_supports_alpn() noexcept; + +/** Report whether this WolfSSL build can check certificate revocation. + + CRL checking requires a WolfSSL built with `HAVE_CRL`. On a build + without it, supplying a CRL or a revocation policy fails the handshake + with `std::errc::function_not_supported` rather than silently skipping + revocation. + + @return `true` if revocation checking is supported by this build. + + @see tls_context::add_crl, tls_context::set_revocation_policy +*/ +BOOST_COROSIO_DECL bool +wolfssl_supports_crl() noexcept; + } // namespace boost::corosio #endif diff --git a/src/corosio/src/tls/context.cpp b/src/corosio/src/tls/context.cpp index 2b7891113..ca5a7ff45 100644 --- a/src/corosio/src/tls/context.cpp +++ b/src/corosio/src/tls/context.cpp @@ -18,6 +18,25 @@ namespace boost::corosio { +namespace { + +// Read an entire file in binary mode into `out`, returning ENOENT if it +// cannot be opened. Shared by the file-based credential/trust loaders. +std::error_code +read_file_contents(std::string_view filename, std::string& out) +{ + std::ifstream file(std::string(filename), std::ios::binary); + if (!file) + return std::error_code(ENOENT, std::generic_category()); + + std::ostringstream ss; + ss << file.rdbuf(); + out = ss.str(); + return {}; +} + +} // namespace + tls_context::tls_context() : impl_(std::make_shared()) {} // @@ -37,13 +56,8 @@ std::error_code tls_context::use_certificate_file( std::string_view filename, tls_file_format format) { - std::ifstream file(std::string(filename), std::ios::binary); - if (!file) - return std::error_code(ENOENT, std::generic_category()); - - std::ostringstream ss; - ss << file.rdbuf(); - impl_->entity_certificate = ss.str(); + if (auto ec = read_file_contents(filename, impl_->entity_certificate); ec) + return ec; impl_->entity_cert_format = format; return {}; } @@ -58,14 +72,7 @@ tls_context::use_certificate_chain(std::string_view chain) std::error_code tls_context::use_certificate_chain_file(std::string_view filename) { - std::ifstream file(std::string(filename), std::ios::binary); - if (!file) - return std::error_code(ENOENT, std::generic_category()); - - std::ostringstream ss; - ss << file.rdbuf(); - impl_->certificate_chain = ss.str(); - return {}; + return read_file_contents(filename, impl_->certificate_chain); } std::error_code @@ -81,31 +88,34 @@ std::error_code tls_context::use_private_key_file( std::string_view filename, tls_file_format format) { - std::ifstream file(std::string(filename), std::ios::binary); - if (!file) - return std::error_code(ENOENT, std::generic_category()); - - std::ostringstream ss; - ss << file.rdbuf(); - impl_->private_key = ss.str(); + if (auto ec = read_file_contents(filename, impl_->private_key); ec) + return ec; impl_->private_key_format = format; return {}; } std::error_code -tls_context::use_pkcs12( - std::string_view /*data*/, std::string_view /*passphrase*/) +tls_context::use_pkcs12(std::string_view data, std::string_view passphrase) { - // TODO: Implement PKCS#12 parsing - return std::make_error_code(std::errc::function_not_supported); + // assign(ptr, len) rather than std::string(string_view): libstdc++'s + // basic_string(const char*, size_t) computes std::distance(s, s + n), + // whose one-past-the-end pointer ASan's detect_invalid_pointer_pairs + // rejects for a global buffer. assign copies without that subtraction. + impl_->pkcs12_data.assign(data.data(), data.size()); + impl_->pkcs12_password.assign(passphrase.data(), passphrase.size()); + return {}; } std::error_code tls_context::use_pkcs12_file( - std::string_view /*filename*/, std::string_view /*passphrase*/) + std::string_view filename, std::string_view passphrase) { - // TODO: Implement PKCS#12 file loading - return std::make_error_code(std::errc::function_not_supported); + if (auto ec = read_file_contents(filename, impl_->pkcs12_data); ec) + return ec; + // assign(ptr, len), not std::string(passphrase): see the note in + // use_pkcs12. + impl_->pkcs12_password.assign(passphrase.data(), passphrase.size()); + return {}; } // @@ -122,13 +132,10 @@ tls_context::add_certificate_authority(std::string_view ca) std::error_code tls_context::load_verify_file(std::string_view filename) { - std::ifstream file(std::string(filename), std::ios::binary); - if (!file) - return std::error_code(ENOENT, std::generic_category()); - - std::ostringstream ss; - ss << file.rdbuf(); - impl_->ca_certificates.push_back(ss.str()); + std::string contents; + if (auto ec = read_file_contents(filename, contents); ec) + return ec; + impl_->ca_certificates.push_back(std::move(contents)); return {}; } @@ -171,9 +178,25 @@ tls_context::set_ciphersuites(std::string_view ciphers) return {}; } +std::error_code +tls_context::set_ciphersuites_tls13(std::string_view ciphers) +{ + impl_->ciphersuites_tls13 = std::string(ciphers); + return {}; +} + std::error_code tls_context::set_alpn(std::initializer_list protocols) { + // Validate before mutating so a bad entry doesn't silently drop part of + // the list (or wipe a prior valid configuration). A name must be a + // non-empty token no longer than 255 bytes (the ALPN wire length field) + // and must not contain a comma (WolfSSL's list separator). + for (auto const& p : protocols) + if (p.empty() || p.size() > 255 || + p.find(',') != std::string_view::npos) + return std::make_error_code(std::errc::invalid_argument); + impl_->alpn_protocols.clear(); for (auto const& p : protocols) impl_->alpn_protocols.emplace_back(p); @@ -239,29 +262,13 @@ tls_context::add_crl(std::string_view crl) std::error_code tls_context::add_crl_file(std::string_view filename) { - std::ifstream file(std::string(filename), std::ios::binary); - if (!file) - return std::error_code(ENOENT, std::generic_category()); - - std::ostringstream ss; - ss << file.rdbuf(); - impl_->crls.push_back(ss.str()); + std::string contents; + if (auto ec = read_file_contents(filename, contents); ec) + return ec; + impl_->crls.push_back(std::move(contents)); return {}; } -std::error_code -tls_context::set_ocsp_staple(std::string_view response) -{ - impl_->ocsp_staple = std::string(response); - return {}; -} - -void -tls_context::set_require_ocsp_staple(bool require) -{ - impl_->require_ocsp_staple = require; -} - void tls_context::set_revocation_policy(tls_revocation_policy policy) { diff --git a/src/corosio/src/tls/detail/context_impl.hpp b/src/corosio/src/tls/detail/context_impl.hpp index 5c08e5f5d..324f8a52d 100644 --- a/src/corosio/src/tls/detail/context_impl.hpp +++ b/src/corosio/src/tls/detail/context_impl.hpp @@ -47,6 +47,10 @@ struct tls_context_data std::string private_key; tls_file_format private_key_format = tls_file_format::pem; + // PKCS#12 bundle (decoded by the backend into cert/key/chain). + std::string pkcs12_data; + std::string pkcs12_password; + // Trust anchors std::vector ca_certificates; @@ -57,7 +61,8 @@ struct tls_context_data tls_version min_version = tls_version::tls_1_2; tls_version max_version = tls_version::tls_1_3; - std::string ciphersuites; + std::string ciphersuites; // TLS 1.2 and below (cipher list) + std::string ciphersuites_tls13; // TLS 1.3 ciphersuites std::vector alpn_protocols; // Verification @@ -74,8 +79,6 @@ struct tls_context_data // Revocation std::vector crls; - std::string ocsp_staple; - bool require_ocsp_staple = false; tls_revocation_policy revocation = tls_revocation_policy::disabled; // Password diff --git a/src/openssl/src/openssl_stream.cpp b/src/openssl/src/openssl_stream.cpp index 988f6693f..c69d0ea99 100644 --- a/src/openssl/src/openssl_stream.cpp +++ b/src/openssl/src/openssl_stream.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -88,6 +89,30 @@ apply_hostname_verification(SSL* ssl, std::string const& hostname) #endif } +// Map a portable protocol version to the OpenSSL version constant. +inline int +openssl_proto_version(tls_version v) noexcept +{ + return v == tls_version::tls_1_3 ? TLS1_3_VERSION : TLS1_2_VERSION; +} + +// Encode a protocol list into ALPN wire format: each entry is a +// one-byte length followed by that many bytes. Entries longer than 255 +// bytes are skipped (invalid per RFC 7301). +inline std::string +build_alpn_wire(std::vector const& protocols) +{ + std::string wire; + for (auto const& p : protocols) + { + if (p.empty() || p.size() > 255) + continue; + wire.push_back(static_cast(p.size())); + wire.append(p); + } + return wire; +} + inline std::error_code normalize_openssl_shutdown_read_error(std::error_code ec) noexcept { @@ -173,8 +198,10 @@ password_callback(char* buf, int size, int rwflag, void* userdata) } // Trampoline installed via SSL_CTX_set_verify. Recovers the portable -// context data from the SSL_CTX ex_data (populated for every context), -// wraps the store context, and delegates to the user's callback. +// context data from the SSL_CTX ex_data (populated for every context) +// and applies, in order: the revocation policy's soft-fail downgrade, +// then the user's verify callback. Installed whenever a verify callback +// or a non-disabled revocation policy is configured. static int verify_callback_trampoline(int preverified, X509_STORE_CTX* store_ctx) { @@ -185,25 +212,89 @@ verify_callback_trampoline(int preverified, X509_STORE_CTX* store_ctx) auto* cd = static_cast( SSL_CTX_get_ex_data(SSL_get_SSL_CTX(ssl), sni_ctx_data_index)); - if (!cd || !cd->verify_callback) + if (!cd) return preverified; - // Expose the current certificate's DER so the callback can inspect it - // portably. i2d_X509 allocates; free it after the callback returns. - X509* cert = X509_STORE_CTX_get_current_cert(store_ctx); - unsigned char* der = nullptr; - int der_len = cert ? i2d_X509(cert, &der) : 0; + bool ok = preverified != 0; + + // Soft-fail revocation: accept certificates whose revocation status + // could not be determined (missing/expired CRL), but never downgrade + // an actual revocation. hard_fail leaves every CRL error fatal. + if (!ok && cd->revocation == tls_revocation_policy::soft_fail) + { + int const err = X509_STORE_CTX_get_error(store_ctx); + if (err == X509_V_ERR_UNABLE_TO_GET_CRL || + err == X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER || + err == X509_V_ERR_CRL_HAS_EXPIRED || + err == X509_V_ERR_CRL_NOT_YET_VALID) + ok = true; + } - verify_context vc( - store_ctx, der, - der_len > 0 ? static_cast(der_len) : 0); - bool const ok = cd->verify_callback(preverified != 0, vc); + if (cd->verify_callback) + { + // Expose the current certificate's DER so the callback can inspect + // it portably. i2d_X509 allocates; free it after the callback. + X509* cert = X509_STORE_CTX_get_current_cert(store_ctx); + unsigned char* der = nullptr; + int der_len = cert ? i2d_X509(cert, &der) : 0; + + verify_context vc( + store_ctx, der, + der_len > 0 ? static_cast(der_len) : 0); + ok = cd->verify_callback(ok, vc); + + if (der) + OPENSSL_free(der); + } - if (der) - OPENSSL_free(der); return ok ? 1 : 0; } +// Server-side ALPN selection. Chooses the server's most-preferred +// protocol that the client also offered. On no overlap it sends a fatal +// no_application_protocol alert (RFC 7301 §3.2). +// +// `arg` points at the native context's build-time snapshot of the server +// preference list (a std::vector), so client offer and server +// selection are both taken from the same immutable snapshot. +// +// The selected protocol pointer must stay valid until the callback runs +// again, so we point *out into the client list `in` (OpenSSL keeps it +// valid for the connection) rather than into a local buffer. +static int +alpn_select_cb( + SSL* /* ssl */, unsigned char const** out, unsigned char* outlen, + unsigned char const* in, unsigned int inlen, void* arg) +{ + auto const* prefs = static_cast const*>(arg); + if (!prefs || prefs->empty()) + return SSL_TLSEXT_ERR_NOACK; // nothing configured (defensive) + + // Server preference order wins: for each server protocol, look for a + // matching entry in the client's offered list. + for (auto const& pref : *prefs) + { + for (unsigned int i = 0; i + 1 <= inlen;) + { + unsigned int len = in[i]; + if (i + 1 + len > inlen) + break; // malformed + if (len == pref.size() && + std::memcmp(in + i + 1, pref.data(), len) == 0) + { + *out = in + i + 1; + *outlen = static_cast(len); + return SSL_TLSEXT_ERR_OK; + } + i += 1 + len; + } + } + + // The server supports ALPN but shares no protocol with the client. + // RFC 7301 §3.2: fail the handshake with a fatal alert. + return SSL_TLSEXT_ERR_ALERT_FATAL; +} + static int sni_callback(SSL* ssl, int* /* alert */, void* /* arg */) { @@ -229,6 +320,22 @@ class openssl_native_context : public native_context_base public: SSL_CTX* ctx_; tls_context_data const* cd_; + // Set when a requested configuration could not be applied: an inverted + // protocol window (min > max), a cipher list / suite the library + // rejected, a protocol-version bound that would not set, or a CRL that + // parsed as neither PEM nor DER. Silently proceeding would negotiate an + // unexpected version, ignore the requested ciphers, or weaken revocation + // (fail-open under soft_fail), so do_handshake refuses the handshake. + bool setup_failed_ = false; + // ALPN offer in wire format (length-prefixed), encoded once from the + // immutable protocol list. The client sets it per-SSL each handshake; + // caching it here avoids re-encoding and re-allocating per connection. + std::string alpn_wire_; + // Server preference snapshot, captured at build time so the select + // callback matches against the same immutable list the client offers + // from (see alpn_select_cb). Its address is handed to OpenSSL as the + // callback arg, so it must outlive the SSL_CTX (it does — same object). + std::vector alpn_snapshot_; explicit openssl_native_context(tls_context_data const& cd) : ctx_(nullptr) @@ -248,23 +355,113 @@ class openssl_native_context : public native_context_base if (cd.servername_callback) SSL_CTX_set_tlsext_servername_callback(ctx_, sni_callback); + // ALPN server-side selection. The callback only fires when this + // context is used as a server; the client offer (encoded once here) + // is set per-SSL from alpn_wire_. Snapshot the preference list so the + // callback and the client offer share one immutable source. + if (!cd.alpn_protocols.empty()) + { + alpn_snapshot_ = cd.alpn_protocols; + SSL_CTX_set_alpn_select_cb(ctx_, alpn_select_cb, &alpn_snapshot_); + alpn_wire_ = build_alpn_wire(cd.alpn_protocols); + } + SSL_CTX_set_mode(ctx_, SSL_MODE_ENABLE_PARTIAL_WRITE); SSL_CTX_set_mode(ctx_, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); #if defined(SSL_MODE_RELEASE_BUFFERS) SSL_CTX_set_mode(ctx_, SSL_MODE_RELEASE_BUFFERS); #endif + // Enforce the configured protocol version window (role-agnostic). + // An inverted window (min > max) admits no protocol; fail closed + // rather than silently negotiate an unexpected version. + if (cd.min_version > cd.max_version) + setup_failed_ = true; + if (!SSL_CTX_set_min_proto_version( + ctx_, openssl_proto_version(cd.min_version))) + setup_failed_ = true; + if (!SSL_CTX_set_max_proto_version( + ctx_, openssl_proto_version(cd.max_version))) + setup_failed_ = true; + int verify_mode_flag = SSL_VERIFY_NONE; if (cd.verification_mode == tls_verify_mode::peer) verify_mode_flag = SSL_VERIFY_PEER; else if (cd.verification_mode == tls_verify_mode::require_peer) verify_mode_flag = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + // The trampoline runs the revocation soft-fail downgrade and the + // user callback, so install it if either is configured. + bool const need_trampoline = + cd.verify_callback || + cd.revocation != tls_revocation_policy::disabled; SSL_CTX_set_verify( ctx_, verify_mode_flag, - cd.verify_callback ? &verify_callback_trampoline : nullptr); + need_trampoline ? &verify_callback_trampoline : nullptr); + + // PKCS#12 bundle: decode cert + key + chain directly into the + // context. This is an alternative credential source; the PEM/DER + // fields below are only consulted when no bundle is supplied. + if (!cd.pkcs12_data.empty()) + { + // A bundle that fails to decode or parse (wrong passphrase, + // malformed) must not leave the context silently credential-less: + // a client using PKCS#12 for mTLS would then fail open against a + // verify_mode::peer server. Fail closed like every other setup + // error. + BIO* bio = BIO_new_mem_buf( + cd.pkcs12_data.data(), + static_cast(cd.pkcs12_data.size())); + if (!bio) + setup_failed_ = true; + else + { + PKCS12* p12 = d2i_PKCS12_bio(bio, nullptr); + if (!p12) + setup_failed_ = true; + else + { + EVP_PKEY* pkey = nullptr; + X509* cert = nullptr; + STACK_OF(X509)* chain = nullptr; + if (PKCS12_parse( + p12, cd.pkcs12_password.c_str(), &pkey, &cert, + &chain)) + { + if (cert) + SSL_CTX_use_certificate(ctx_, cert); + if (pkey) + SSL_CTX_use_PrivateKey(ctx_, pkey); + if (chain) + for (int i = 0; i < sk_X509_num(chain); ++i) + { + // add_extra_chain_cert takes ownership of the + // dup only on success; free it (and fail + // closed) otherwise so a partial chain isn't + // sent silently. + X509* dup = X509_dup(sk_X509_value(chain, i)); + if (!dup || + !SSL_CTX_add_extra_chain_cert(ctx_, dup)) + { + X509_free(dup); + setup_failed_ = true; + } + } + } + else + setup_failed_ = true; + EVP_PKEY_free(pkey); + X509_free(cert); + if (chain) + sk_X509_pop_free(chain, X509_free); + PKCS12_free(p12); + } + ERR_clear_error(); + BIO_free(bio); + } + } - if (!cd.entity_certificate.empty()) + if (cd.pkcs12_data.empty() && !cd.entity_certificate.empty()) { BIO* bio = BIO_new_mem_buf( cd.entity_certificate.data(), @@ -285,7 +482,7 @@ class openssl_native_context : public native_context_base } } - if (!cd.certificate_chain.empty()) + if (cd.pkcs12_data.empty() && !cd.certificate_chain.empty()) { BIO* bio = BIO_new_mem_buf( cd.certificate_chain.data(), @@ -311,7 +508,7 @@ class openssl_native_context : public native_context_base } } - if (!cd.private_key.empty()) + if (cd.pkcs12_data.empty() && !cd.private_key.empty()) { BIO* bio = BIO_new_mem_buf( cd.private_key.data(), static_cast(cd.private_key.size())); @@ -365,13 +562,65 @@ class openssl_native_context : public native_context_base SSL_CTX_load_verify_locations(ctx_, nullptr, path.c_str()); ERR_clear_error(); - SSL_CTX_set_verify_depth(ctx_, cd.verify_depth); - - if (!cd.ciphersuites.empty()) + // Certificate revocation via CRLs. Load any supplied CRLs and, when + // a revocation policy is active, enable leaf CRL checking. soft_fail + // vs hard_fail is applied in the verify trampoline. CRL_CHECK (leaf + // only) is used so a missing CRL for a trusted root is not itself an + // error. + if (cd.revocation != tls_revocation_policy::disabled) { - SSL_CTX_set_security_level(ctx_, 0); - SSL_CTX_set_cipher_list(ctx_, cd.ciphersuites.c_str()); + for (auto const& crl_data : cd.crls) + { + BIO* bio = BIO_new_mem_buf( + crl_data.data(), static_cast(crl_data.size())); + if (!bio) + { + setup_failed_ = true; + continue; + } + // Accept PEM or DER (the documented contract). Try PEM first, + // then rewind and try DER. + X509_CRL* crl = + PEM_read_bio_X509_CRL(bio, nullptr, nullptr, nullptr); + if (!crl) + { + BIO_reset(bio); + crl = d2i_X509_CRL_bio(bio, nullptr); + } + if (crl) + { + X509_STORE_add_crl(store, crl); + X509_CRL_free(crl); + } + else + { + // A supplied CRL that parses as neither PEM nor DER must + // not be silently dropped; record it so the handshake + // fails closed rather than weakening revocation. + setup_failed_ = true; + } + BIO_free(bio); + } + X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK); + ERR_clear_error(); } + + SSL_CTX_set_verify_depth(ctx_, cd.verify_depth); + + // Cipher configuration. TLS 1.2-and-below use the cipher list; + // TLS 1.3 uses the separate ciphersuites API. The security level + // is deliberately left at the library default: a weak cipher + // string should fail loudly rather than be silently permitted via + // a forced @SECLEVEL=0. Callers that genuinely need a lower level + // can express it in the cipher string (e.g. "...:@SECLEVEL=0"). + // A cipher string the library rejects must not silently fall back to + // the default suites; fail closed instead. + if (!cd.ciphersuites.empty() && + !SSL_CTX_set_cipher_list(ctx_, cd.ciphersuites.c_str())) + setup_failed_ = true; + if (!cd.ciphersuites_tls13.empty() && + !SSL_CTX_set_ciphersuites(ctx_, cd.ciphersuites_tls13.c_str())) + setup_failed_ = true; } ~openssl_native_context() override @@ -381,12 +630,18 @@ class openssl_native_context : public native_context_base } }; -inline SSL_CTX* -get_openssl_context(tls_context_data const& cd) +inline openssl_native_context* +get_openssl_native_context(tls_context_data const& cd) { static char key; auto* p = cd.find(&key, [&] { return new openssl_native_context(cd); }); - return static_cast(p)->ctx_; + return static_cast(p); +} + +SSL_CTX* +get_openssl_context(tls_context_data const& cd) +{ + return get_openssl_native_context(cd)->ctx_; } } // namespace detail @@ -399,6 +654,9 @@ struct openssl_stream::impl BIO* ext_bio_ = nullptr; bool used_ = false; + // ALPN protocol negotiated during the handshake (empty if none). + std::string alpn_selected_; + std::vector in_buf_; std::vector out_buf_; @@ -435,9 +693,20 @@ struct openssl_stream::impl auto& cd = detail::get_tls_context_data(ctx_); apply_hostname_verification(ssl_, cd.hostname); + alpn_selected_.clear(); used_ = false; } + // Record the ALPN protocol selected during the handshake, if any. + void capture_alpn() + { + unsigned char const* data = nullptr; + unsigned int len = 0; + SSL_get0_alpn_selected(ssl_, &data, &len); + if (data && len) + alpn_selected_.assign(reinterpret_cast(data), len); + } + capy::task flush_output() { while (BIO_ctrl_pending(ext_bio_) > 0) @@ -635,11 +904,36 @@ struct openssl_stream::impl capy::io_task<> do_handshake(int type) { + // A requested configuration could not be applied when the native + // context was built (inverted protocol window, rejected cipher/ + // version, or an unparseable CRL). Refuse the handshake rather than + // proceed with weakened or unexpected settings. + auto* nc = detail::get_openssl_native_context( + detail::get_tls_context_data(ctx_)); + if (nc->setup_failed_) + co_return std::make_error_code(std::errc::invalid_argument); + if (used_) reset(); std::error_code ec; + // Client offers its ALPN protocol list; the server selects via the + // context callback. Role is only known here, so set the pre-encoded + // wire offer per-SSL. + if (type == openssl_stream::client && !nc->alpn_wire_.empty()) + { + // SSL_set_alpn_protos uses the inverted convention: 0 = success. + // A non-zero return (allocation failure) means the offer was not + // installed; fail closed rather than negotiate nothing silently. + if (SSL_set_alpn_protos( + ssl_, + reinterpret_cast( + nc->alpn_wire_.data()), + static_cast(nc->alpn_wire_.size())) != 0) + co_return std::make_error_code(std::errc::invalid_argument); + } + while (true) { ERR_clear_error(); @@ -652,7 +946,8 @@ struct openssl_stream::impl if (ret == 1) { used_ = true; - ec = co_await flush_output(); + capture_alpn(); + ec = co_await flush_output(); co_return {ec}; } else @@ -868,4 +1163,10 @@ openssl_stream::name() const noexcept return "openssl"; } +std::string_view +openssl_stream::alpn_protocol() const noexcept +{ + return impl_->alpn_selected_; +} + } // namespace boost::corosio diff --git a/src/wolfssl/src/wolfssl_stream.cpp b/src/wolfssl/src/wolfssl_stream.cpp index 5c5f6eaca..3aaa55092 100644 --- a/src/wolfssl/src/wolfssl_stream.cpp +++ b/src/wolfssl/src/wolfssl_stream.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -134,6 +135,26 @@ wolfssl_supports_verify_callback() noexcept #endif } +bool +wolfssl_supports_alpn() noexcept +{ +#if defined(HAVE_ALPN) + return true; +#else + return false; +#endif +} + +bool +wolfssl_supports_crl() noexcept +{ +#if defined(HAVE_CRL) + return true; +#else + return false; +#endif +} + // // Native context caching // @@ -163,6 +184,25 @@ wolfssl_sni_callback(WOLFSSL* ssl, int* /* alert */, void* arg) return 0; // Accept } +#if defined(HAVE_CRL) +// CRL error callback for the soft-fail revocation policy. +// +// WolfSSL invokes this ONLY when it cannot determine a certificate's +// revocation status because no CRL entry was found for it (ret == +// CRL_MISSING, or an expired/undecodable CRL). Returning non-zero overrides +// that error to success. A certificate that is actually listed in a loaded +// CRL is rejected before this path (CRL_CERT_REVOKED), so it still fails +// closed. This mirrors OpenSSL's soft_fail: tolerate "status unknown", +// still reject "revoked". +static int +wolfssl_crl_soft_fail_cb( + int /*ret*/, WOLFSSL_CRL* /*crl*/, WOLFSSL_CERT_MANAGER* /*cm*/, + void* /*ctx*/) +{ + return 1; // override missing/unknown-status CRL error -> accept +} +#endif + #if defined(WOLFSSL_ALWAYS_VERIFY_CB) // WOLFSSL_CTX ex_data slot holding the portable tls_context_data pointer, // used to reach the user's verify callback from the trampoline. Allocated @@ -221,6 +261,28 @@ wolfssl_verify_callback(int preverified, WOLFSSL_X509_STORE_CTX* store) } #endif // WOLFSSL_ALWAYS_VERIFY_CB +// Select the WolfSSL method for a [min,max] version window. WolfSSL has +// no native set_max_proto_version (that API needs OPENSSL_EXTRA), so the +// ceiling is expressed by choosing a version-specific method; the floor +// is additionally enforced via wolfSSL_CTX_SetMinVersion. +inline WOLFSSL_METHOD* +wolfssl_method_for(bool server, tls_version min_v, tls_version max_v) +{ + if (min_v == tls_version::tls_1_3) + return server ? wolfTLSv1_3_server_method() + : wolfTLSv1_3_client_method(); + if (max_v == tls_version::tls_1_2) + return server ? wolfTLSv1_2_server_method() + : wolfTLSv1_2_client_method(); + return server ? wolfTLS_server_method() : wolfTLS_client_method(); +} + +inline int +wolfssl_min_version_const(tls_version v) noexcept +{ + return v == tls_version::tls_1_3 ? WOLFSSL_TLSV1_3 : WOLFSSL_TLSV1_2; +} + /** Cached WolfSSL contexts owning WOLFSSL_CTX for client and server. Created on first stream construction for a given tls_context, @@ -233,8 +295,19 @@ class wolfssl_native_context : public native_context_base public: WOLFSSL_CTX* client_ctx_; WOLFSSL_CTX* server_ctx_; - - static void + // Set when a requested configuration could not be applied: an inverted + // protocol window (min > max), a cipher list the library rejected, a + // version floor that would not set, or a CRL that parsed as neither PEM + // nor DER. init_ssl_for_role then fails closed rather than negotiate an + // unexpected version, ignore the requested ciphers, or weaken revocation + // (fail-open under soft_fail). + bool setup_failed_ = false; + // ALPN protocol list in WolfSSL's comma-separated format, built once + // from the immutable protocol list. Installed per-session (client offer + // / server candidates); caching it avoids rebuilding per connection. + std::string alpn_list_; + + void apply_common_settings(WOLFSSL_CTX* ctx, tls_context_data const& cd) { if (!ctx) @@ -281,9 +354,79 @@ class wolfssl_native_context : public native_context_base wolfSSL_CTX_set_verify(ctx, verify_mode_flag, nullptr); #endif - // Apply certificate chain if provided (entity cert + intermediates) - // wolfSSL_CTX_use_certificate_chain_buffer loads entity as cert, rest as chain - if (!cd.certificate_chain.empty()) + // PKCS#12 bundle: decode cert + key with the native wolfcrypt API + // (the wolfSSL_d2i_PKCS12_bio wrapper needs OPENSSL_EXTRA). CA/chain + // entries inside the bundle are loaded and sent during the handshake + // (see below), matching the OpenSSL backend. + if (!cd.pkcs12_data.empty()) + { + // A bundle that fails to decode or parse (wrong passphrase, + // malformed) must not leave the context silently credential-less: + // a client using PKCS#12 for mTLS would then fail open against a + // verify_mode::peer server. Fail closed like every other setup + // error. + WC_PKCS12* p12 = wc_PKCS12_new(); + if (!p12) + setup_failed_ = true; + else + { + byte* pkey = nullptr; + word32 pkeySz = 0; + byte* cert = nullptr; + word32 certSz = 0; + WC_DerCertList* ca = nullptr; + if (wc_d2i_PKCS12( + reinterpret_cast(cd.pkcs12_data.data()), + static_cast(cd.pkcs12_data.size()), p12) != 0 || + wc_PKCS12_parse( + p12, cd.pkcs12_password.c_str(), &pkey, &pkeySz, &cert, + &certSz, &ca) != 0) + { + setup_failed_ = true; + } + else + { + if (cert && ca) + { + // Concatenate leaf + chain into one DER blob and load + // it as a chain so the intermediate(s) are sent during + // the handshake (parity with the OpenSSL backend). + std::vector chain(cert, cert + certSz); + for (WC_DerCertList* n = ca; n; n = n->next) + chain.insert( + chain.end(), n->buffer, n->buffer + n->bufferSz); + if (wolfSSL_CTX_use_certificate_chain_buffer_format( + ctx, chain.data(), + static_cast(chain.size()), + WOLFSSL_FILETYPE_ASN1) != WOLFSSL_SUCCESS) + setup_failed_ = true; + } + else if (cert) + { + if (wolfSSL_CTX_use_certificate_buffer( + ctx, cert, static_cast(certSz), + WOLFSSL_FILETYPE_ASN1) != WOLFSSL_SUCCESS) + setup_failed_ = true; + } + if (pkey && + wolfSSL_CTX_use_PrivateKey_buffer( + ctx, pkey, static_cast(pkeySz), + WOLFSSL_FILETYPE_ASN1) != WOLFSSL_SUCCESS) + setup_failed_ = true; + if (ca) + wc_FreeCertList(ca, nullptr); + XFREE(pkey, nullptr, DYNAMIC_TYPE_PKCS); + XFREE(cert, nullptr, DYNAMIC_TYPE_PKCS); + } + wc_PKCS12_free(p12); + } + } + + // Apply certificate chain if provided (entity cert + intermediates). + // These discrete PEM/DER fields are an alternative credential source + // to a PKCS#12 bundle; when a bundle was supplied it already loaded + // the credential above, so skip them (parity with the OpenSSL path). + if (cd.pkcs12_data.empty() && !cd.certificate_chain.empty()) { wolfSSL_CTX_use_certificate_chain_buffer( ctx, @@ -291,7 +434,7 @@ class wolfssl_native_context : public native_context_base cd.certificate_chain.data()), static_cast(cd.certificate_chain.size())); } - else if (!cd.entity_certificate.empty()) + else if (cd.pkcs12_data.empty() && !cd.entity_certificate.empty()) { // Only use single certificate if no chain provided int format = (cd.entity_cert_format == tls_file_format::pem) @@ -304,8 +447,9 @@ class wolfssl_native_context : public native_context_base static_cast(cd.entity_certificate.size()), format); } - // Apply private key if provided - if (!cd.private_key.empty()) + // Apply private key if provided (skipped when a PKCS#12 bundle + // already supplied the credential, as above). + if (cd.pkcs12_data.empty() && !cd.private_key.empty()) { if (cd.password_callback) { @@ -381,8 +525,72 @@ class wolfssl_native_context : public native_context_base for (auto const& path : cd.verify_paths) wolfSSL_CTX_load_verify_locations(ctx, nullptr, path.c_str()); + // Enforce the version floor. The method chosen in the ctor sets the + // ceiling; SetMinVersion pins the floor (the range method would + // otherwise permit older versions the build still supports). An + // inverted window (min > max) is silently reduced to a min-only + // context by wolfssl_method_for, so catch it explicitly and fail + // closed rather than negotiate an unexpected version. + if (cd.min_version > cd.max_version) + setup_failed_ = true; + if (wolfSSL_CTX_SetMinVersion( + ctx, wolfssl_min_version_const(cd.min_version)) != + WOLFSSL_SUCCESS) + setup_failed_ = true; + // Apply verify depth wolfSSL_CTX_set_verify_depth(ctx, cd.verify_depth); + + // Cipher configuration. WolfSSL accepts TLS 1.2 and TLS 1.3 suite + // names in a single colon-separated list, so merge both fields. + if (!cd.ciphersuites.empty() || !cd.ciphersuites_tls13.empty()) + { + std::string list = cd.ciphersuites; + if (!cd.ciphersuites_tls13.empty()) + { + if (!list.empty()) + list.push_back(':'); + list.append(cd.ciphersuites_tls13); + } + // A cipher list the library rejects must not silently fall back + // to the default suites; fail closed instead. + if (wolfSSL_CTX_set_cipher_list(ctx, list.c_str()) != + WOLFSSL_SUCCESS) + setup_failed_ = true; + } + +#if defined(HAVE_CRL) + // Certificate revocation via CRLs (fuller builds only; when HAVE_CRL + // is absent, init_ssl_for_role fails closed instead). + if (cd.revocation != tls_revocation_policy::disabled) + { + if (wolfSSL_CTX_EnableCRL(ctx, WOLFSSL_CRL_CHECK) != + WOLFSSL_SUCCESS) + setup_failed_ = true; + for (auto const& crl : cd.crls) + { + auto const* buf = + reinterpret_cast(crl.data()); + auto const sz = static_cast(crl.size()); + // Accept PEM or DER (the documented contract): try PEM, then + // DER. A supplied CRL that parses as neither must not be + // silently dropped, so record it for a fail-closed handshake. + if (wolfSSL_CTX_LoadCRLBuffer( + ctx, buf, sz, WOLFSSL_FILETYPE_PEM) != WOLFSSL_SUCCESS && + wolfSSL_CTX_LoadCRLBuffer( + ctx, buf, sz, WOLFSSL_FILETYPE_ASN1) != WOLFSSL_SUCCESS) + setup_failed_ = true; + } + // soft_fail tolerates an undeterminable revocation status (no CRL + // loaded for a cert) the way OpenSSL does. Without this, WolfSSL's + // WOLFSSL_CRL_CHECK hard-fails with CRL_MISSING; the callback + // downgrades that to success while still rejecting a cert that a + // loaded CRL actually revokes. + if (cd.revocation == tls_revocation_policy::soft_fail) + wolfSSL_CTX_SetCRL_ErrorCb( + ctx, &detail::wolfssl_crl_soft_fail_cb, nullptr); + } +#endif } tls_context_data const* cd_; // For SNI callback access @@ -392,13 +600,25 @@ class wolfssl_native_context : public native_context_base , server_ctx_(nullptr) , cd_(&cd) { - // Create separate contexts for client and server - client_ctx_ = wolfSSL_CTX_new(wolfTLS_client_method()); - server_ctx_ = wolfSSL_CTX_new(wolfTLS_server_method()); + // Create separate contexts for client and server, choosing the + // method that honors the configured protocol version window. + client_ctx_ = wolfSSL_CTX_new( + wolfssl_method_for(false, cd.min_version, cd.max_version)); + server_ctx_ = wolfSSL_CTX_new( + wolfssl_method_for(true, cd.min_version, cd.max_version)); apply_common_settings(client_ctx_, cd); apply_common_settings(server_ctx_, cd); + // Encode the ALPN protocol list once (comma-separated); each session + // installs it via wolfSSL_UseALPN in init_ssl_for_role. + for (auto const& p : cd.alpn_protocols) + { + if (!alpn_list_.empty()) + alpn_list_.push_back(','); + alpn_list_.append(p); + } + // Set SNI callback on server context if provided if (server_ctx_ && cd.servername_callback) { @@ -441,6 +661,9 @@ struct wolfssl_stream::impl WOLFSSL* ssl_ = nullptr; bool used_ = false; + // ALPN protocol negotiated during the handshake (empty if none). + std::string alpn_selected_; + // Buffers for read operations std::vector read_in_buf_; std::size_t read_in_pos_ = 0; @@ -503,9 +726,22 @@ struct wolfssl_stream::impl write_in_len_ = 0; write_out_len_ = 0; current_op_ = nullptr; + alpn_selected_.clear(); used_ = false; } + // Record the ALPN protocol selected during the handshake, if any. + void capture_alpn() + { +#if defined(HAVE_ALPN) + char* name = nullptr; + unsigned short sz = 0; + if (wolfSSL_ALPN_GetProtocol(ssl_, &name, &sz) == WOLFSSL_SUCCESS && + name && sz) + alpn_selected_.assign(name, sz); +#endif + } + // WolfSSL I/O Callbacks /** Callback invoked by WolfSSL when it needs to receive data. @@ -867,6 +1103,7 @@ struct wolfssl_stream::impl { // Handshake completed successfully used_ = true; + capture_alpn(); // Flush any remaining output if (read_out_len_ > 0) { @@ -1105,6 +1342,13 @@ struct wolfssl_stream::impl wolfSSL_get_error(nullptr, 0), wolfssl_category()); } + // A requested configuration could not be applied when the native + // context was built (inverted protocol window, rejected cipher/ + // version, or an unparseable CRL). Fail closed rather than proceed + // with weakened or unexpected settings. + if (native->setup_failed_) + return std::make_error_code(std::errc::invalid_argument); + // Select appropriate context based on role WOLFSSL_CTX* native_ctx = (type == wolfssl_stream::client) ? native->client_ctx_ @@ -1156,6 +1400,46 @@ struct wolfssl_stream::impl #endif } + // ALPN. Both client (offer) and server (candidate list) install + // the same protocol list; WolfSSL negotiates from it. + if (!cd.alpn_protocols.empty()) + { +#if defined(HAVE_ALPN) + // FAILED_ON_MISMATCH: on no shared protocol the server aborts the + // handshake with a fatal no_application_protocol alert (RFC 7301 + // §3.2), matching the OpenSSL backend. A non-success return means + // the offer was not installed; fail closed rather than proceed. + if (wolfSSL_UseALPN( + ssl_, native->alpn_list_.data(), + static_cast(native->alpn_list_.size()), + WOLFSSL_ALPN_FAILED_ON_MISMATCH) != WOLFSSL_SUCCESS) + return std::make_error_code(std::errc::invalid_argument); +#else + // This WolfSSL build cannot negotiate ALPN. An application that + // offered protocols (and may read alpn_protocol() expecting a + // result) must not silently proceed with none; fail closed. + // Rebuild WolfSSL with HAVE_ALPN to enable ALPN. + wolfSSL_free(ssl_); + ssl_ = nullptr; + return std::make_error_code(std::errc::function_not_supported); +#endif + } + +#if !defined(HAVE_CRL) + // Revocation via CRL requires a WolfSSL build with HAVE_CRL. When a + // policy actually requests checking, fail closed rather than skip it + // silently. Gate on the policy alone: a CRL supplied while the policy + // stays disabled is inert by contract (consulted only when the policy + // is not disabled), so that config must work here as it does + // everywhere else. + if (cd.revocation != tls_revocation_policy::disabled) + { + wolfSSL_free(ssl_); + ssl_ = nullptr; + return std::make_error_code(std::errc::function_not_supported); + } +#endif + // Apply per-session config (SNI + hostname verification) from context if (type == wolfssl_stream::client && !cd.hostname.empty()) { @@ -1246,4 +1530,10 @@ wolfssl_stream::name() const noexcept return "wolfssl"; } +std::string_view +wolfssl_stream::alpn_protocol() const noexcept +{ + return impl_->alpn_selected_; +} + } // namespace boost::corosio diff --git a/test/unit/openssl_stream.cpp b/test/unit/openssl_stream.cpp index 674c1924f..38ab26b36 100644 --- a/test/unit/openssl_stream.cpp +++ b/test/unit/openssl_stream.cpp @@ -80,33 +80,6 @@ struct openssl_stream_test BOOST_TEST(&mutable_next == &const_next); } - /** Test certificate chain validation (OpenSSL-specific). - - OpenSSL supports sending full certificate chains via - use_certificate_chain() and add_extra_chain_cert(). - */ - void testCertificateChain() - { - using namespace test; - - // Server sends full chain - { - io_context ioc; - auto client_ctx = make_rootonly_client_context(); - auto server_ctx = make_fullchain_server_context(); - run_tls_test(ioc, client_ctx, server_ctx, make_stream, make_stream); - } - - // Server sends only entity cert (fails) - { - io_context ioc; - auto client_ctx = make_rootonly_client_context(); - auto server_ctx = make_chain_server_context(); - run_tls_test_fail( - ioc, client_ctx, server_ctx, make_stream, make_stream); - } - } - /** Test that OpenSSL errors carry the OpenSSL category (issue #223). Errors from the OpenSSL error queue must render readable messages, @@ -143,30 +116,6 @@ struct openssl_stream_test } } - /** Test that set_default_verify_paths() is applied without breaking - context creation. - - Behaviorally exercising the system trust store would require - redirecting it (SSL_CERT_FILE), which is not portable across the CI - matrix, so this only asserts the call path runs cleanly: a client - that trusts the CA explicitly *and* calls set_default_verify_paths() - still completes the handshake. The load-from-path mechanism is - covered behaviorally by testAddVerifyPath(). - */ - void testDefaultVerifyPaths() - { - using namespace test; - - io_context ioc; - auto client_ctx = make_client_context(); - // Adding the system store on top of the explicit CA must not break - // context creation or verification. - client_ctx.set_default_verify_paths(); // NOLINT(bugprone-unused-return-value) - - auto server_ctx = make_server_context(); - run_tls_test(ioc, client_ctx, server_ctx, make_stream, make_stream); - } - /** Test that add_verify_path() loads CAs from a hashed directory. OpenSSL CApath lookups require files named by subject-name hash (as @@ -216,6 +165,17 @@ struct openssl_stream_test test::testCertificateValidation(make_stream); test::testSni(make_stream); test::testSniCallback(make_stream); + test::testAlpnAccessorEmpty(make_stream); + test::testAlpn(make_stream, /*alpn_supported=*/true); + test::testAlpnNoOverlap(make_stream, /*alpn_supported=*/true); + test::testProtocolVersion(make_stream); + test::testCiphersuitesTls13( + make_stream, "TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384"); + test::testPkcs12(make_stream); + test::testPkcs12Chain(make_stream); + test::testCertificateChain(make_stream); + test::testDefaultVerifyPaths(make_stream); + test::testCrlRevocation(make_stream, /*crl_supported=*/true); test::testVerifyCallback(make_stream); test::testVerifyCallbackOnSuccess(make_stream); test::testMtls(make_stream); @@ -228,9 +188,7 @@ struct openssl_stream_test test::testResetViaHandshake(make_stream, cert_modes); test::testResetFuse(make_stream); - testCertificateChain(); testErrorCategory(); - testDefaultVerifyPaths(); testAddVerifyPath(); testName(); testNextLayer(); diff --git a/test/unit/test_utils.hpp b/test/unit/test_utils.hpp index dbc06ed80..67cbea624 100644 --- a/test/unit/test_utils.hpp +++ b/test/unit/test_utils.hpp @@ -255,32 +255,36 @@ inline constexpr char const* root_ca_key_pem = // Intermediate CA certificate (signed by root CA) // Subject: CN=Test Intermediate CA // Issuer: CN=Test Root CA -// Valid: 2026-01-22 to 2031-01-21 (CA:TRUE) +// Valid: 2026-07-09 to 2036-07-06 (CA:TRUE) +// basicConstraints is marked CRITICAL: WolfSSL enforces RFC 5280's rule that +// a CA certificate must carry a critical basicConstraints extension (OpenSSL +// is lenient). Regenerated with the same key + subject as before, so every +// certificate signed by this intermediate remains valid. // Commands: -// openssl req -new -newkey rsa:2048 -keyout intermediate_key.pem -out intermediate.csr -// -nodes -subj "/CN=Test Intermediate CA" +// openssl req -new -key intermediate_key.pem -out intermediate.csr +// -subj "/CN=Test Intermediate CA" // openssl x509 -req -in intermediate.csr -CA root_ca_cert.pem -CAkey root_ca_key.pem -// -CAcreateserial -out intermediate_cert.pem -days 1825 -// -extfile <(echo "basicConstraints=CA:TRUE") +// -CAcreateserial -out intermediate_cert.pem -days 3650 +// -extfile <(printf "basicConstraints=critical,CA:TRUE\nkeyUsage=critical,keyCertSign,cRLSign\n") inline constexpr char const* intermediate_cert_pem = "-----BEGIN CERTIFICATE-----\n" - "MIIDFDCCAfygAwIBAgIUTxjxnkuFSB8P+4VeoVw5wrVEv9swDQYJKoZIhvcNAQEL\n" - "BQAwFzEVMBMGA1UEAwwMVGVzdCBSb290IENBMB4XDTI2MDEyMjE3MzIxM1oXDTMx\n" - "MDEyMTE3MzIxM1owHzEdMBsGA1UEAwwUVGVzdCBJbnRlcm1lZGlhdGUgQ0EwggEi\n" + "MIIDJzCCAg+gAwIBAgIUcUBzbbnpdTvewEIRh0FLBDvDDBMwDQYJKoZIhvcNAQEL\n" + "BQAwFzEVMBMGA1UEAwwMVGVzdCBSb290IENBMB4XDTI2MDcwOTE3MzkyN1oXDTM2\n" + "MDcwNjE3MzkyN1owHzEdMBsGA1UEAwwUVGVzdCBJbnRlcm1lZGlhdGUgQ0EwggEi\n" "MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDCqobUGWRLfletWGsTWGdySYCb\n" "l2DJ06wVSW/TXvozFmIMKve4T5LKFDTAQtVrp/hK97HqAlTXWjhMTqq1SYHlN4dv\n" "utguzY7Vf96nJWVoJzsq7jAVhukK3bpRo6ytMcj6TRK7DIELKsbCOtvsLTxl0iGk\n" "26uE1zn2xk78GXJLRL5QHgeMrkgwWEdY8AeHm9VJ+dxBtnhzPR0z/AFaMmPODMSN\n" "+HGkDwVyBxOiPrt9GouEci+rx7AUv3Iv8wLZ+AOiCC0Fbfe9zMqVxVppRB8mUt4c\n" "+Np45GnIUk6/Fi+pdNJLTEE5WnoiA87GK+CbAezZt36vYIxSUIfoGz0jKrbpAgMB\n" - "AAGjUDBOMAwGA1UdEwQFMAMBAf8wHQYDVR0OBBYEFGxcuu5CLhAiH3moziBaSMvW\n" - "BzVkMB8GA1UdIwQYMBaAFIMzUb5b+JvY7MnzQFVN+CG75ojHMA0GCSqGSIb3DQEB\n" - "CwUAA4IBAQAEr9QYAOU47frtpTt/TYazaPRt0gzJMQeG+YlFf+Zgsk02L81kxx+U\n" - "4cxggby/TGJlJs8x5X7p6AIW1xHXh976uk1wQjR8A4xojdxauQ7pZXrawCesNfz+\n" - "BJD4rtWD1GL+mGAwL8RT9w5MW+i+6M2IHsxfNp/gVuzEIUeKSaN3hEw10nQ/GZla\n" - "xXlsA7IDcCDBLR35yV/i2kgUlELJMGJfuMJyLt3nbf4y1exZHoq4q4tP4TYU3338\n" - "UXsP85AFORr1q+hDwpXoThPn9aAMlQpzgx6UvGekQK3IheMoqVtsir4N9EL2yMyo\n" - "fDrPhvAUJTaYU/pWeMqNGpOBmvGyiXh9\n" + "AAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQW\n" + "BBRsXLruQi4QIh95qM4gWkjL1gc1ZDAfBgNVHSMEGDAWgBSDM1G+W/ib2OzJ80BV\n" + "Tfghu+aIxzANBgkqhkiG9w0BAQsFAAOCAQEAc1MilNqUSic4RhznHdFj0fXTPQ0K\n" + "73WRf/6TmFcoQymlJhMqk2e3NJVxsfW7G/DfR/0Lm2mOn14mDczIBAHFGMdEb4+s\n" + "iUtu992nroGkxf4euwlwD+LJckWVbnJ1kUhx5WBpFICgW5dvF5KFmYNf9fhLXQs4\n" + "Ltt/hnNtF4b+vzjBH3xq8LGyQZyt3BkyjLQcHFnPsKepPGW1JFn/2POI2as7WMob\n" + "vy3qJ9hh52y3K0VO/zxtVef9cpOTHLH1Eo42uHLc5xQMhDN9Kvv1oywthZYyZ+Mc\n" + "r25U90/0AfnuJjXdCUhj1EMW6QiVk6MQrWhiUXUUKjFUmnChEs++TcL5hw==\n" "-----END CERTIFICATE-----\n"; // Intermediate CA private key (RSA 2048-bit) @@ -550,23 +554,23 @@ inline constexpr char const* server_fullchain_pem = "7T5qXswPM7MpHozuTg==\n" "-----END CERTIFICATE-----\n" "-----BEGIN CERTIFICATE-----\n" - "MIIDFDCCAfygAwIBAgIUTxjxnkuFSB8P+4VeoVw5wrVEv9swDQYJKoZIhvcNAQEL\n" - "BQAwFzEVMBMGA1UEAwwMVGVzdCBSb290IENBMB4XDTI2MDEyMjE3MzIxM1oXDTMx\n" - "MDEyMTE3MzIxM1owHzEdMBsGA1UEAwwUVGVzdCBJbnRlcm1lZGlhdGUgQ0EwggEi\n" + "MIIDJzCCAg+gAwIBAgIUcUBzbbnpdTvewEIRh0FLBDvDDBMwDQYJKoZIhvcNAQEL\n" + "BQAwFzEVMBMGA1UEAwwMVGVzdCBSb290IENBMB4XDTI2MDcwOTE3MzkyN1oXDTM2\n" + "MDcwNjE3MzkyN1owHzEdMBsGA1UEAwwUVGVzdCBJbnRlcm1lZGlhdGUgQ0EwggEi\n" "MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDCqobUGWRLfletWGsTWGdySYCb\n" "l2DJ06wVSW/TXvozFmIMKve4T5LKFDTAQtVrp/hK97HqAlTXWjhMTqq1SYHlN4dv\n" "utguzY7Vf96nJWVoJzsq7jAVhukK3bpRo6ytMcj6TRK7DIELKsbCOtvsLTxl0iGk\n" "26uE1zn2xk78GXJLRL5QHgeMrkgwWEdY8AeHm9VJ+dxBtnhzPR0z/AFaMmPODMSN\n" "+HGkDwVyBxOiPrt9GouEci+rx7AUv3Iv8wLZ+AOiCC0Fbfe9zMqVxVppRB8mUt4c\n" "+Np45GnIUk6/Fi+pdNJLTEE5WnoiA87GK+CbAezZt36vYIxSUIfoGz0jKrbpAgMB\n" - "AAGjUDBOMAwGA1UdEwQFMAMBAf8wHQYDVR0OBBYEFGxcuu5CLhAiH3moziBaSMvW\n" - "BzVkMB8GA1UdIwQYMBaAFIMzUb5b+JvY7MnzQFVN+CG75ojHMA0GCSqGSIb3DQEB\n" - "CwUAA4IBAQAEr9QYAOU47frtpTt/TYazaPRt0gzJMQeG+YlFf+Zgsk02L81kxx+U\n" - "4cxggby/TGJlJs8x5X7p6AIW1xHXh976uk1wQjR8A4xojdxauQ7pZXrawCesNfz+\n" - "BJD4rtWD1GL+mGAwL8RT9w5MW+i+6M2IHsxfNp/gVuzEIUeKSaN3hEw10nQ/GZla\n" - "xXlsA7IDcCDBLR35yV/i2kgUlELJMGJfuMJyLt3nbf4y1exZHoq4q4tP4TYU3338\n" - "UXsP85AFORr1q+hDwpXoThPn9aAMlQpzgx6UvGekQK3IheMoqVtsir4N9EL2yMyo\n" - "fDrPhvAUJTaYU/pWeMqNGpOBmvGyiXh9\n" + "AAGjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQW\n" + "BBRsXLruQi4QIh95qM4gWkjL1gc1ZDAfBgNVHSMEGDAWgBSDM1G+W/ib2OzJ80BV\n" + "Tfghu+aIxzANBgkqhkiG9w0BAQsFAAOCAQEAc1MilNqUSic4RhznHdFj0fXTPQ0K\n" + "73WRf/6TmFcoQymlJhMqk2e3NJVxsfW7G/DfR/0Lm2mOn14mDczIBAHFGMdEb4+s\n" + "iUtu992nroGkxf4euwlwD+LJckWVbnJ1kUhx5WBpFICgW5dvF5KFmYNf9fhLXQs4\n" + "Ltt/hnNtF4b+vzjBH3xq8LGyQZyt3BkyjLQcHFnPsKepPGW1JFn/2POI2as7WMob\n" + "vy3qJ9hh52y3K0VO/zxtVef9cpOTHLH1Eo42uHLc5xQMhDN9Kvv1oywthZYyZ+Mc\n" + "r25U90/0AfnuJjXdCUhj1EMW6QiVk6MQrWhiUXUUKjFUmnChEs++TcL5hw==\n" "-----END CERTIFICATE-----\n"; // @@ -665,6 +669,612 @@ inline constexpr char const* encrypted_server_key_pem = /// Passphrase matching `encrypted_server_key_pem`. inline constexpr char const* encrypted_key_password = "test-password"; +/** server_cert_pem + server_key_pem exported as a PKCS#12 bundle. + Passphrase: "test-password". Generated with: + openssl pkcs12 -export -inkey server.key -in server.crt \\ + -keypbe AES-256-CBC -certpbe AES-256-CBC -macalg sha256 + (PBES2/AES-256-CBC; 3DES is disabled in the test wolfSSL build) +*/ +inline constexpr unsigned char server_p12[] = { + 0x30, 0x82, 0x0a, 0x87, 0x02, 0x01, 0x03, 0x30, 0x82, 0x0a, 0x35, 0x06, + 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x01, 0xa0, 0x82, + 0x0a, 0x26, 0x04, 0x82, 0x0a, 0x22, 0x30, 0x82, 0x0a, 0x1e, 0x30, 0x82, + 0x04, 0x8a, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, + 0x06, 0xa0, 0x82, 0x04, 0x7b, 0x30, 0x82, 0x04, 0x77, 0x02, 0x01, 0x00, + 0x30, 0x82, 0x04, 0x70, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x01, 0x07, 0x01, 0x30, 0x5f, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x05, 0x0d, 0x30, 0x52, 0x30, 0x31, 0x06, 0x09, 0x2a, 0x86, + 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x05, 0x0c, 0x30, 0x24, 0x04, 0x10, 0xd7, + 0x31, 0x9c, 0xcd, 0x95, 0x22, 0xd6, 0x91, 0x14, 0xbd, 0x9e, 0xbe, 0x88, + 0x5f, 0x0d, 0xad, 0x02, 0x02, 0x08, 0x00, 0x30, 0x0c, 0x06, 0x08, 0x2a, + 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x09, 0x05, 0x00, 0x30, 0x1d, 0x06, + 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x2a, 0x04, 0x10, + 0xe5, 0x84, 0x61, 0xf8, 0x7b, 0xde, 0xa8, 0x89, 0x02, 0xde, 0x22, 0x49, + 0xc8, 0x5b, 0xe4, 0x72, 0x80, 0x82, 0x04, 0x00, 0xaf, 0x0c, 0x6e, 0xfd, + 0x77, 0xb4, 0xbb, 0x12, 0x7f, 0x8b, 0x79, 0xd6, 0x17, 0x2c, 0xd6, 0xd0, + 0x5e, 0x60, 0x29, 0xbc, 0xcf, 0xcf, 0x43, 0x63, 0x4a, 0x3b, 0x6e, 0xa8, + 0x0e, 0x85, 0x66, 0xca, 0xc6, 0x02, 0xe2, 0x20, 0x1a, 0xd7, 0x21, 0xd0, + 0xb1, 0x17, 0x74, 0xec, 0xe1, 0x53, 0xd9, 0x1f, 0xb2, 0xae, 0xeb, 0x2e, + 0x94, 0x01, 0xd6, 0x46, 0x7f, 0x5f, 0x21, 0x8e, 0x1f, 0x9f, 0x21, 0x6d, + 0x6c, 0xca, 0x4f, 0x77, 0x2a, 0xe0, 0x4d, 0x8b, 0xf0, 0x1a, 0x65, 0x00, + 0xe0, 0x23, 0x65, 0x7b, 0x82, 0x53, 0xd8, 0x00, 0x6d, 0x20, 0xe9, 0x78, + 0xe4, 0x54, 0x06, 0xcd, 0xde, 0x2f, 0x39, 0xe8, 0x2d, 0x97, 0x81, 0xee, + 0x8e, 0x5b, 0x81, 0x40, 0x77, 0x4d, 0x8a, 0x88, 0x8a, 0xcc, 0xf0, 0x20, + 0x1c, 0xae, 0x92, 0x9a, 0xac, 0xf7, 0x28, 0x77, 0xcf, 0x89, 0x10, 0x2f, + 0x1b, 0x2c, 0x46, 0x96, 0x2b, 0xf2, 0x13, 0x65, 0xdf, 0x7c, 0x73, 0xbd, + 0x01, 0x87, 0xc3, 0x22, 0xca, 0x41, 0xa4, 0xc0, 0x9a, 0xe5, 0xab, 0x27, + 0x28, 0xc5, 0xcd, 0x21, 0x2e, 0xa6, 0xcd, 0x69, 0x76, 0x5e, 0x35, 0xee, + 0x8b, 0xc4, 0xfb, 0x31, 0xaa, 0x76, 0x9a, 0x1d, 0x5b, 0x30, 0x25, 0xfb, + 0x46, 0x47, 0xd8, 0x6d, 0x99, 0x2c, 0x4e, 0xc3, 0x6c, 0x42, 0x97, 0xcf, + 0x16, 0x06, 0x07, 0xc5, 0x81, 0xc5, 0x17, 0xab, 0x7e, 0xd2, 0x39, 0x9b, + 0xc2, 0xe7, 0x25, 0x30, 0x22, 0x6a, 0xe3, 0xa6, 0x1e, 0x93, 0x62, 0x60, + 0xc2, 0x22, 0x7a, 0xcd, 0x23, 0xe1, 0x05, 0x71, 0xd3, 0x5b, 0xcb, 0xc1, + 0xd5, 0x71, 0xdf, 0x9b, 0x70, 0x62, 0x61, 0x18, 0xef, 0x6b, 0x17, 0x68, + 0x96, 0x9f, 0x29, 0x2a, 0xf1, 0x13, 0xc1, 0xa0, 0x75, 0xb3, 0x7a, 0x39, + 0x04, 0x32, 0xa9, 0xc3, 0xe1, 0xfd, 0x9c, 0x2d, 0x76, 0x95, 0x49, 0x86, + 0x92, 0x6e, 0x1a, 0xf9, 0x26, 0x9d, 0x09, 0xb6, 0x51, 0xd3, 0x96, 0xf7, + 0x70, 0x9e, 0x23, 0x48, 0x10, 0x30, 0x2a, 0xd9, 0xb6, 0x3d, 0xe0, 0x3f, + 0x93, 0x21, 0xfd, 0x20, 0xff, 0xf6, 0xeb, 0x45, 0xb3, 0x9a, 0xe3, 0x2d, + 0x3d, 0xe9, 0x57, 0xb9, 0xab, 0xc5, 0xb7, 0x53, 0x49, 0x48, 0x31, 0x09, + 0x4b, 0x0c, 0x20, 0x0b, 0x20, 0x7f, 0x43, 0x3b, 0xc7, 0x04, 0x35, 0xcf, + 0x08, 0xa4, 0x4d, 0x6b, 0xff, 0x0a, 0xa9, 0xaa, 0xa2, 0x72, 0x1c, 0x5d, + 0x53, 0xde, 0xe6, 0x4b, 0x9b, 0xc9, 0x56, 0x56, 0x8b, 0x3a, 0xec, 0x4d, + 0xae, 0x4f, 0xef, 0x31, 0x3c, 0x2f, 0x42, 0xf2, 0xb7, 0x23, 0xb3, 0x50, + 0x3d, 0xb6, 0xcb, 0xb3, 0xe9, 0x56, 0x7a, 0xca, 0xbb, 0x68, 0xbe, 0xec, + 0xb7, 0xd8, 0x82, 0x62, 0x3a, 0x30, 0xc2, 0xfa, 0xee, 0x1f, 0xbb, 0x86, + 0x91, 0x93, 0x71, 0x05, 0x72, 0x96, 0xf5, 0x11, 0xd7, 0x3f, 0xad, 0x7c, + 0x5f, 0xb7, 0x69, 0xe0, 0x4a, 0xbf, 0x1f, 0x7b, 0x44, 0x2b, 0x51, 0x21, + 0xda, 0x72, 0x23, 0xbc, 0x46, 0x7f, 0x02, 0x40, 0x5b, 0xef, 0xcc, 0x7c, + 0x8a, 0x33, 0xd5, 0xe8, 0x52, 0x1a, 0x9a, 0xe1, 0x13, 0x3c, 0x56, 0xa5, + 0xe7, 0x77, 0x22, 0x72, 0x36, 0x7f, 0x57, 0x57, 0xe5, 0x32, 0xf6, 0x2a, + 0xf7, 0xab, 0xf9, 0xfa, 0x87, 0xe9, 0x20, 0xe6, 0xe3, 0xed, 0xcb, 0x3e, + 0x9b, 0xf0, 0xa7, 0x99, 0x06, 0xd7, 0xaa, 0x3d, 0x3a, 0x80, 0xf2, 0xd5, + 0x7b, 0x37, 0xc4, 0xbc, 0x26, 0x97, 0x72, 0x78, 0x56, 0x37, 0xa2, 0xd6, + 0x90, 0x37, 0xcd, 0xf9, 0x78, 0xd8, 0x8d, 0xf8, 0x10, 0x30, 0xd7, 0x61, + 0xe9, 0x5c, 0x00, 0xc1, 0x8a, 0x11, 0x88, 0xf2, 0x4e, 0xab, 0xce, 0x3f, + 0x55, 0x36, 0xfe, 0xd0, 0xa9, 0xf9, 0x6a, 0xcd, 0xed, 0xf3, 0xdc, 0x94, + 0x33, 0x0d, 0x10, 0x45, 0x3f, 0x54, 0x5c, 0x75, 0xfb, 0x05, 0x48, 0x27, + 0x4e, 0x2e, 0x75, 0xe3, 0x56, 0xd4, 0x92, 0xc9, 0x79, 0xb3, 0xb3, 0x93, + 0x6d, 0x89, 0x68, 0x85, 0xff, 0xee, 0x59, 0x76, 0xa5, 0x4e, 0xb6, 0x0b, + 0xa8, 0xfb, 0x23, 0x84, 0xdb, 0x3b, 0x53, 0x4a, 0xd5, 0xb7, 0x30, 0x4d, + 0x2a, 0x75, 0xb2, 0x71, 0xf4, 0xda, 0x73, 0x27, 0x56, 0x98, 0xcb, 0xe5, + 0x03, 0x6f, 0x43, 0xb4, 0x65, 0xbb, 0xb3, 0x0f, 0xc0, 0xfc, 0xdd, 0x45, + 0x7e, 0x99, 0x75, 0x16, 0xb6, 0x44, 0xfb, 0x0b, 0x1d, 0xe5, 0xb8, 0x44, + 0xe8, 0x87, 0xed, 0xed, 0x6d, 0x21, 0x8c, 0xa5, 0x55, 0xd2, 0xb5, 0xb2, + 0x36, 0xbc, 0xa3, 0x93, 0x5d, 0x6f, 0x73, 0x11, 0xba, 0xda, 0x75, 0xb9, + 0x98, 0x20, 0xef, 0x94, 0x3d, 0xf4, 0x08, 0x01, 0xab, 0xd7, 0xae, 0xe2, + 0x86, 0xd1, 0xe5, 0x64, 0xa8, 0x5b, 0x14, 0x99, 0x01, 0x8b, 0xa3, 0x09, + 0xa9, 0x1e, 0x44, 0x1c, 0xdc, 0x46, 0x33, 0xe7, 0xe5, 0x8a, 0x70, 0xb5, + 0xea, 0x1f, 0x4c, 0x21, 0x18, 0xfe, 0x75, 0xc9, 0x0b, 0x7a, 0xd1, 0x4c, + 0x94, 0x2d, 0x36, 0xf9, 0xe7, 0xe5, 0x67, 0x24, 0xa3, 0x43, 0xcc, 0x51, + 0xa0, 0xcb, 0x33, 0x9b, 0x36, 0x03, 0x1b, 0xdb, 0x91, 0xd7, 0xd8, 0xea, + 0x4d, 0x0a, 0x7b, 0x27, 0x6f, 0xee, 0x11, 0xc3, 0xc3, 0x52, 0x9c, 0x91, + 0x7b, 0x56, 0x10, 0x04, 0x70, 0xd7, 0x1f, 0x68, 0xfb, 0xa0, 0x53, 0x85, + 0x87, 0x21, 0x7a, 0xe9, 0xaf, 0x50, 0xe4, 0x80, 0x37, 0xe9, 0x12, 0x5c, + 0x69, 0x96, 0xbd, 0xe6, 0x69, 0xb8, 0x79, 0x64, 0x6f, 0x97, 0xd4, 0x0c, + 0x54, 0x45, 0xce, 0xee, 0x45, 0x07, 0xdf, 0x76, 0x44, 0xa0, 0xb7, 0x34, + 0x8d, 0xfc, 0x2b, 0x3d, 0xe1, 0x40, 0xa2, 0xf3, 0xeb, 0xb6, 0x22, 0x7f, + 0x76, 0x9f, 0x72, 0x01, 0x59, 0xee, 0x99, 0x17, 0x2b, 0xbf, 0x7f, 0x29, + 0x62, 0x87, 0x25, 0x50, 0x12, 0x7a, 0x86, 0x55, 0x2a, 0x74, 0xf6, 0x1f, + 0x39, 0xc6, 0xb8, 0x4b, 0xac, 0x5a, 0x63, 0x7a, 0xa9, 0xe3, 0x8c, 0xdd, + 0x70, 0xc0, 0xb4, 0x5d, 0x88, 0xa8, 0x8e, 0xc1, 0xc0, 0xa8, 0xf3, 0xf6, + 0x91, 0x9f, 0xe4, 0x64, 0x3f, 0x79, 0x29, 0xc1, 0xfc, 0x4c, 0x31, 0xb1, + 0x5b, 0x30, 0x61, 0x7e, 0xc6, 0x68, 0xc5, 0x27, 0x5e, 0xb5, 0x83, 0xd2, + 0x58, 0x75, 0x43, 0xb3, 0x75, 0x4a, 0x0e, 0xa8, 0xf2, 0xd8, 0x80, 0x6d, + 0x31, 0x9e, 0x48, 0x3f, 0x09, 0x39, 0xf8, 0xc0, 0x97, 0x4b, 0x4e, 0xa6, + 0xad, 0xcc, 0x18, 0xee, 0x44, 0x0d, 0xe8, 0x5e, 0xbd, 0xb7, 0x34, 0x65, + 0x60, 0x64, 0x36, 0xb3, 0xb9, 0xa2, 0x50, 0xd8, 0x34, 0x47, 0x39, 0xbe, + 0x0d, 0x80, 0x24, 0x2d, 0x75, 0x73, 0x32, 0x4f, 0x10, 0x6c, 0x59, 0xe0, + 0xb0, 0x95, 0x3a, 0x96, 0x86, 0x6e, 0xf5, 0x74, 0xb5, 0x78, 0x97, 0x53, + 0x87, 0xf8, 0x9e, 0x3f, 0xa5, 0x1e, 0x22, 0xd5, 0xa5, 0xf7, 0x78, 0xb2, + 0xbb, 0x22, 0x1d, 0xc6, 0x70, 0x79, 0x26, 0x97, 0x18, 0x58, 0x80, 0x1c, + 0x43, 0xc8, 0xae, 0x44, 0xbf, 0x09, 0x83, 0x30, 0xec, 0x0f, 0xb1, 0x86, + 0x3b, 0x3e, 0x1a, 0x4f, 0x4e, 0x8e, 0xa6, 0x23, 0xf7, 0x9a, 0xfa, 0x4e, + 0x42, 0xcb, 0x02, 0x1f, 0xc8, 0x0a, 0xfa, 0xe5, 0x7c, 0x1c, 0xda, 0x68, + 0xb2, 0x9c, 0x99, 0x68, 0x5d, 0x70, 0x71, 0x24, 0xb1, 0x1f, 0x29, 0xba, + 0xc4, 0x81, 0x01, 0x78, 0x77, 0x06, 0x46, 0xdd, 0x54, 0x53, 0xae, 0x13, + 0xd6, 0x50, 0xaf, 0x48, 0xfd, 0x57, 0x98, 0xae, 0x1f, 0xff, 0x34, 0x91, + 0xe8, 0xfd, 0x3f, 0xbf, 0x7c, 0x99, 0x78, 0x48, 0x9a, 0xe4, 0x9a, 0xea, + 0x78, 0xf7, 0x94, 0x42, 0x0b, 0x7a, 0x63, 0x06, 0xa2, 0x25, 0xc4, 0x63, + 0x30, 0x82, 0x05, 0x8c, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x01, 0x07, 0x01, 0xa0, 0x82, 0x05, 0x7d, 0x04, 0x82, 0x05, 0x79, 0x30, + 0x82, 0x05, 0x75, 0x30, 0x82, 0x05, 0x71, 0x06, 0x0b, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x0a, 0x01, 0x02, 0xa0, 0x82, 0x05, 0x39, + 0x30, 0x82, 0x05, 0x35, 0x30, 0x5f, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x05, 0x0d, 0x30, 0x52, 0x30, 0x31, 0x06, 0x09, 0x2a, + 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x05, 0x0c, 0x30, 0x24, 0x04, 0x10, + 0x5e, 0x1a, 0x19, 0x51, 0xeb, 0x2e, 0x9b, 0xf2, 0xdd, 0xfe, 0x32, 0x0b, + 0x8e, 0x13, 0xa4, 0xac, 0x02, 0x02, 0x08, 0x00, 0x30, 0x0c, 0x06, 0x08, + 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x09, 0x05, 0x00, 0x30, 0x1d, + 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x2a, 0x04, + 0x10, 0x2e, 0x39, 0xe8, 0x93, 0xe5, 0x92, 0x85, 0x7f, 0x21, 0xa3, 0xf4, + 0x89, 0xda, 0x51, 0xf9, 0xcf, 0x04, 0x82, 0x04, 0xd0, 0x37, 0x51, 0x2f, + 0x8d, 0xd3, 0x48, 0x0f, 0x17, 0xe1, 0x8a, 0x4b, 0xb9, 0xc2, 0x49, 0x0c, + 0x60, 0x81, 0x0d, 0x0a, 0x2e, 0x4a, 0x44, 0x93, 0x62, 0x9b, 0x24, 0xc8, + 0x81, 0x2f, 0x8d, 0x91, 0xce, 0x98, 0x4a, 0xcd, 0x07, 0xd6, 0xd5, 0xb9, + 0x89, 0xad, 0xdf, 0xc2, 0x7d, 0x29, 0x43, 0x5f, 0xb8, 0x65, 0x2f, 0x18, + 0x51, 0x2c, 0x5f, 0xab, 0xe3, 0xa9, 0x0d, 0x02, 0xf5, 0xd5, 0x9a, 0x0e, + 0x4c, 0x3d, 0x84, 0x4a, 0x92, 0x61, 0xb8, 0x34, 0xfd, 0xe9, 0x1a, 0xd6, + 0xeb, 0xf1, 0x15, 0x90, 0x43, 0x71, 0xd0, 0xdf, 0x61, 0x09, 0xa3, 0x24, + 0x1f, 0x0c, 0x04, 0xb1, 0x8e, 0x4a, 0x9b, 0xe9, 0x35, 0xf9, 0x9a, 0x84, + 0xd6, 0xaf, 0xc6, 0xb3, 0x1c, 0x7c, 0xa9, 0xb9, 0x44, 0x09, 0xc9, 0x45, + 0xe0, 0xb7, 0x4a, 0x6d, 0x27, 0x68, 0x0a, 0xd8, 0x95, 0x1f, 0x4b, 0x4d, + 0x7f, 0x4a, 0x18, 0xd0, 0x1d, 0x5a, 0x46, 0xd5, 0xcb, 0x7e, 0xfd, 0x6d, + 0xf2, 0x9f, 0x3b, 0xe0, 0xe0, 0x71, 0xa3, 0xbf, 0xd1, 0x5b, 0xff, 0xd5, + 0x3d, 0xc2, 0xda, 0xcd, 0x27, 0xa2, 0x90, 0x5b, 0x13, 0xa9, 0x99, 0x4e, + 0xd1, 0x60, 0x85, 0x14, 0x09, 0x26, 0x5d, 0x9e, 0x52, 0x4a, 0x7e, 0x2b, + 0x98, 0x2b, 0xf4, 0x8b, 0xd4, 0x35, 0xdb, 0xd8, 0xe4, 0xa8, 0x51, 0xc4, + 0xd7, 0xf7, 0x3a, 0xad, 0xe7, 0x80, 0xa6, 0xe1, 0x44, 0xe9, 0x95, 0x6e, + 0x3e, 0xc5, 0x69, 0x95, 0x79, 0xb7, 0x02, 0x02, 0x59, 0x29, 0xa4, 0x90, + 0x82, 0x7e, 0xb3, 0x14, 0x28, 0x47, 0xab, 0x2e, 0xdd, 0x5e, 0x43, 0xa6, + 0xd8, 0xec, 0x80, 0xe4, 0x0e, 0x0c, 0x0d, 0x2e, 0xc7, 0xc9, 0xdb, 0xbb, + 0x23, 0xe2, 0xa9, 0x3a, 0x0d, 0x7e, 0xb3, 0x77, 0x12, 0xf0, 0x18, 0xbd, + 0x41, 0x1c, 0x96, 0x43, 0xf2, 0xb6, 0x3a, 0xc0, 0xd2, 0x90, 0x5d, 0x7f, + 0x4d, 0xa4, 0xda, 0x50, 0x28, 0x60, 0x62, 0x36, 0x05, 0xc6, 0xa1, 0x2f, + 0xd6, 0x91, 0xcf, 0x4a, 0xc4, 0x0b, 0x33, 0xd4, 0x33, 0xd2, 0x85, 0xae, + 0xbd, 0xb3, 0xc2, 0x21, 0x2e, 0x74, 0x67, 0x62, 0x01, 0x91, 0xbf, 0xaf, + 0x1a, 0xa6, 0x52, 0x8f, 0xe1, 0x5c, 0x2c, 0xf9, 0x34, 0x98, 0x7f, 0xcc, + 0x9f, 0x6c, 0xf1, 0x1b, 0x0f, 0x8f, 0x99, 0xf1, 0x91, 0x34, 0xa4, 0x11, + 0xf3, 0x86, 0x23, 0xe8, 0x95, 0x6a, 0x07, 0xad, 0x52, 0x2e, 0xec, 0xdd, + 0x81, 0xd9, 0xb5, 0x49, 0x9c, 0x1d, 0x3e, 0x89, 0x88, 0x90, 0x56, 0x7f, + 0xc1, 0xff, 0x46, 0xf3, 0x8f, 0xf6, 0xbd, 0x0d, 0x57, 0x9c, 0x2a, 0xad, + 0x86, 0xb3, 0x81, 0x45, 0x13, 0x1f, 0x11, 0x61, 0xe0, 0x45, 0xa4, 0x55, + 0x68, 0xb7, 0x6c, 0x48, 0x94, 0x05, 0xeb, 0x2e, 0x26, 0x4f, 0x41, 0x6a, + 0x9a, 0x28, 0x7d, 0x43, 0xb5, 0xcb, 0x78, 0x32, 0xf2, 0xf8, 0xb8, 0x05, + 0x85, 0x39, 0x24, 0xc7, 0x89, 0x04, 0x77, 0x23, 0xe3, 0x5d, 0x1f, 0x78, + 0x98, 0x09, 0xab, 0x8a, 0xbc, 0x2b, 0x8a, 0xf2, 0xc6, 0xd2, 0x85, 0x6e, + 0xb8, 0xe6, 0xb7, 0xb3, 0x5c, 0x0c, 0x2f, 0xe4, 0xdd, 0x2c, 0xab, 0xf2, + 0xf5, 0x48, 0x8e, 0xd4, 0xc1, 0x5b, 0xe9, 0x2b, 0xdb, 0x71, 0x7f, 0xc2, + 0xc0, 0x10, 0x96, 0x8a, 0x10, 0xd3, 0x5c, 0x1d, 0xf6, 0x26, 0xca, 0xcc, + 0x11, 0xa4, 0x23, 0xdc, 0x30, 0x48, 0x48, 0x73, 0x5d, 0x8a, 0x14, 0x09, + 0x3a, 0x8f, 0x89, 0x1d, 0x4f, 0xd5, 0x96, 0x87, 0x5e, 0xc7, 0x38, 0x63, + 0x40, 0x9b, 0x8d, 0x14, 0xc0, 0x3e, 0xd8, 0xeb, 0xf7, 0xc3, 0x2e, 0x88, + 0x90, 0x62, 0x68, 0x77, 0x72, 0xed, 0x35, 0xda, 0xf4, 0xed, 0xb0, 0x19, + 0xe1, 0xcb, 0x4c, 0x6a, 0x4a, 0x99, 0x88, 0x81, 0xcd, 0xd2, 0x0c, 0x24, + 0x32, 0x02, 0x72, 0x95, 0x86, 0xa8, 0x0d, 0xaa, 0x19, 0x33, 0x91, 0xeb, + 0xd0, 0xf2, 0x0f, 0x33, 0xd7, 0x85, 0xcf, 0x56, 0x58, 0x0e, 0x0b, 0xb4, + 0x6a, 0xb0, 0xdd, 0x0a, 0xed, 0x51, 0xb1, 0xa1, 0xbb, 0x9c, 0x3b, 0xf4, + 0x50, 0xea, 0x77, 0x34, 0x66, 0x28, 0x1f, 0xfc, 0xa7, 0xad, 0xb0, 0x50, + 0x29, 0x8d, 0x81, 0xf6, 0xd7, 0xbb, 0x21, 0xa8, 0x88, 0x98, 0xae, 0x5e, + 0x8b, 0xea, 0xc9, 0xc9, 0x50, 0xfb, 0xce, 0x81, 0x5d, 0xbc, 0x08, 0x87, + 0x8b, 0x04, 0x2a, 0x8a, 0xd1, 0x64, 0x08, 0x47, 0x5b, 0xeb, 0x72, 0xc5, + 0x1f, 0x90, 0x17, 0x3a, 0xb2, 0xb0, 0x01, 0x5a, 0x02, 0x57, 0xf4, 0x4f, + 0xdd, 0x40, 0xd0, 0xae, 0x2f, 0xd0, 0x58, 0x98, 0xa8, 0xa0, 0xb8, 0x37, + 0x0a, 0x74, 0x2f, 0x1a, 0x1a, 0x71, 0x93, 0xaf, 0xb2, 0xfa, 0xe1, 0x03, + 0x61, 0xb4, 0x59, 0x4a, 0xaa, 0x05, 0xf7, 0x30, 0xc9, 0xa0, 0x32, 0xdf, + 0x34, 0xb0, 0x16, 0xa6, 0xfc, 0x6a, 0x2b, 0x73, 0x50, 0x00, 0x1f, 0xf8, + 0x20, 0x09, 0x94, 0x55, 0xd9, 0x01, 0xa7, 0x55, 0x15, 0x32, 0xdb, 0x92, + 0xf4, 0x65, 0x02, 0xb5, 0x1e, 0x87, 0xde, 0x5c, 0xc1, 0x9e, 0x4b, 0xe3, + 0xa4, 0xc6, 0x61, 0xe2, 0x09, 0x6e, 0x9e, 0x60, 0x09, 0xfc, 0x1f, 0x88, + 0xb6, 0x8c, 0xc3, 0x44, 0x3a, 0xd0, 0x57, 0x82, 0xdc, 0x44, 0x62, 0x71, + 0xc2, 0x19, 0xf1, 0xf9, 0x3b, 0x06, 0xfd, 0xcd, 0xde, 0x87, 0xb7, 0xf8, + 0xfe, 0x36, 0xad, 0x56, 0x81, 0x7c, 0xfc, 0x72, 0x95, 0xda, 0xbe, 0xa4, + 0xfe, 0x5c, 0x20, 0xca, 0xb4, 0x11, 0xa4, 0xb1, 0x4a, 0x53, 0x60, 0x6d, + 0xd8, 0xc7, 0x08, 0xf0, 0xf2, 0x3a, 0xc4, 0x1d, 0xfa, 0xed, 0x58, 0x6e, + 0x5e, 0xd7, 0x61, 0xfb, 0xd7, 0xa6, 0x41, 0x51, 0x88, 0x69, 0xb5, 0x33, + 0xcd, 0x6a, 0x7a, 0x09, 0x9e, 0x2d, 0xce, 0x39, 0xe9, 0xd7, 0xf3, 0x40, + 0x6f, 0x18, 0x9c, 0x00, 0xbb, 0xb8, 0xaa, 0x96, 0x80, 0x41, 0x32, 0x86, + 0x85, 0xf0, 0xe2, 0xfc, 0x5d, 0x94, 0xdf, 0xf6, 0x83, 0xcc, 0x4b, 0xf9, + 0x98, 0xb2, 0x41, 0x06, 0xff, 0x2d, 0x40, 0xd8, 0x1a, 0x10, 0x55, 0xe6, + 0x24, 0x77, 0x82, 0x6f, 0xe9, 0x42, 0x5a, 0x5a, 0x94, 0x0b, 0xf1, 0xd9, + 0xf7, 0x47, 0x65, 0xae, 0x08, 0xda, 0x3b, 0x02, 0x8b, 0xdb, 0xbd, 0x03, + 0x21, 0x90, 0x56, 0x74, 0x11, 0xaa, 0x7e, 0x4f, 0x3a, 0xe0, 0x83, 0xbe, + 0x21, 0x40, 0x40, 0x5f, 0xad, 0x39, 0x94, 0xa7, 0x13, 0xfc, 0xbd, 0xc5, + 0x3b, 0x7a, 0x08, 0xbe, 0xa9, 0x68, 0xd9, 0x1d, 0x0e, 0x9c, 0xa7, 0x5b, + 0x9d, 0x3c, 0xb0, 0x41, 0xde, 0x9c, 0xf6, 0x6e, 0x8c, 0x04, 0x34, 0xed, + 0xfc, 0x8e, 0x55, 0xd3, 0xc5, 0x86, 0x78, 0xd2, 0x25, 0x7b, 0xaf, 0x1d, + 0xde, 0x5c, 0x80, 0x27, 0x27, 0x63, 0x16, 0x29, 0xa5, 0xc4, 0x3a, 0xd7, + 0xec, 0x85, 0x39, 0x37, 0xc0, 0x16, 0xae, 0x2b, 0x2d, 0x42, 0x7f, 0x72, + 0x73, 0x54, 0x23, 0xca, 0x1d, 0xa3, 0x92, 0x52, 0xec, 0x62, 0x1c, 0x43, + 0x34, 0x79, 0x19, 0x5a, 0x67, 0x07, 0x9f, 0x9a, 0xac, 0x5a, 0xf2, 0x92, + 0x16, 0x0a, 0x6e, 0xd9, 0x2a, 0x98, 0x66, 0x7c, 0x59, 0x66, 0xac, 0x5f, + 0x57, 0xb9, 0x5d, 0x2e, 0x33, 0xb3, 0x74, 0xcc, 0x74, 0x7e, 0x81, 0xfb, + 0x06, 0x6d, 0x7b, 0x47, 0xb2, 0xe0, 0x6f, 0x7d, 0xd4, 0x8a, 0x92, 0x13, + 0xfe, 0x9d, 0x5d, 0x56, 0x5a, 0x77, 0x29, 0x6e, 0x12, 0x4e, 0xa6, 0x47, + 0x8c, 0x75, 0xbe, 0x56, 0x27, 0x51, 0x41, 0xf2, 0x03, 0x4e, 0xb0, 0xae, + 0x9a, 0x9e, 0x5e, 0x2f, 0x2e, 0x4f, 0x8d, 0xa7, 0x45, 0x4b, 0x8c, 0xc5, + 0xaa, 0x4c, 0x14, 0x3f, 0x57, 0x74, 0x3f, 0xb8, 0xe0, 0xdc, 0x8e, 0xfd, + 0x70, 0x78, 0xf4, 0x50, 0xf3, 0x23, 0x3e, 0x6c, 0xcd, 0x98, 0x2c, 0x24, + 0x39, 0x4a, 0x8b, 0xea, 0x91, 0x67, 0xed, 0x84, 0xbd, 0x0e, 0x54, 0xdb, + 0xbd, 0x67, 0x52, 0x88, 0x63, 0x31, 0xef, 0x7c, 0x3d, 0xab, 0x61, 0xc2, + 0xfd, 0xcd, 0xb3, 0xf5, 0x33, 0xd8, 0xbd, 0xcd, 0x25, 0x1d, 0x93, 0x7f, + 0x4c, 0x41, 0xf2, 0x08, 0xc4, 0x97, 0xb6, 0xd3, 0x02, 0xc2, 0x1d, 0xc9, + 0x18, 0x4e, 0x3f, 0x84, 0xf7, 0x9a, 0xe2, 0x93, 0x08, 0x62, 0x0e, 0x19, + 0xc7, 0x6c, 0xb0, 0x53, 0x07, 0x2a, 0x22, 0xc3, 0x05, 0xcc, 0x11, 0xb0, + 0x97, 0x08, 0x98, 0x69, 0xe4, 0xfb, 0xb1, 0xe5, 0x45, 0xe2, 0xda, 0x14, + 0x6f, 0x60, 0xaa, 0xc1, 0x18, 0xb3, 0x1a, 0x78, 0x85, 0xbd, 0x70, 0x43, + 0x4a, 0xb3, 0x39, 0x0e, 0x38, 0x7b, 0x4c, 0xb9, 0x5b, 0x02, 0xbf, 0xda, + 0x65, 0xb7, 0xfb, 0x3d, 0xab, 0xde, 0x95, 0xb8, 0xea, 0xfe, 0xff, 0x8a, + 0xfa, 0x93, 0x11, 0x00, 0xf1, 0x2e, 0xa7, 0xb2, 0x34, 0x06, 0xdd, 0xdc, + 0xfc, 0x6c, 0xba, 0x38, 0x48, 0xde, 0xc8, 0xd0, 0x65, 0x22, 0x50, 0xf3, + 0x26, 0x87, 0x66, 0x2c, 0x3f, 0x14, 0x29, 0x50, 0x03, 0xb1, 0x05, 0x6a, + 0x3d, 0xa6, 0x9e, 0x9e, 0xbd, 0x5b, 0xf7, 0x20, 0x80, 0x5e, 0x52, 0x32, + 0xc9, 0x41, 0xb5, 0x6c, 0xf4, 0x4e, 0x16, 0xb5, 0x23, 0xd6, 0xa2, 0xbb, + 0xfc, 0x3f, 0xd5, 0x5a, 0x3f, 0x23, 0xa7, 0xce, 0x6a, 0xb9, 0x94, 0x05, + 0x38, 0x76, 0x86, 0x66, 0x97, 0x31, 0x25, 0x30, 0x23, 0x06, 0x09, 0x2a, + 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x15, 0x31, 0x16, 0x04, 0x14, + 0x3a, 0x9d, 0x84, 0xd4, 0xfd, 0x45, 0xd5, 0xc2, 0x80, 0x6d, 0xaf, 0x27, + 0x85, 0x1f, 0x23, 0xa3, 0xa0, 0xc7, 0xa9, 0x49, 0x30, 0x49, 0x30, 0x31, + 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, + 0x01, 0x05, 0x00, 0x04, 0x20, 0x25, 0xaf, 0xce, 0x7f, 0x92, 0xfb, 0x42, + 0x36, 0xe3, 0x97, 0xe4, 0x0c, 0x5e, 0x6f, 0x6e, 0x37, 0xe4, 0x0c, 0x4c, + 0x10, 0x18, 0x96, 0xba, 0xba, 0x9c, 0x0b, 0xa1, 0xb1, 0x04, 0x14, 0x1b, + 0x01, 0x04, 0x10, 0x67, 0xa7, 0x59, 0x31, 0xf2, 0x26, 0x1f, 0x63, 0xfe, + 0x87, 0x20, 0xeb, 0x11, 0xa8, 0x05, 0x00, 0x02, 0x02, 0x08, 0x00, +}; + +/// Passphrase matching `server_p12`. +inline constexpr char const* p12_password = "test-password"; + +/** A PKCS#12 bundle carrying leaf + key + one intermediate. + + The leaf is signed by an intermediate (with *critical* basicConstraints, + which WolfSSL requires of a CA) that is itself signed by + root_ca_cert_pem. Passphrase: "test-password" (AES-256-CBC PBES2). + + Used to verify the backend loads *and sends* the bundle's chain: a + client trusting only the root CA can verify only if the server presents + the intermediate. +*/ +inline constexpr unsigned char server_chain_p12[] = { + 0x30, 0x82, 0x0d, 0x77, 0x02, 0x01, 0x03, 0x30, 0x82, 0x0d, 0x25, 0x06, + 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x01, 0xa0, 0x82, + 0x0d, 0x16, 0x04, 0x82, 0x0d, 0x12, 0x30, 0x82, 0x0d, 0x0e, 0x30, 0x82, + 0x07, 0x7a, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, + 0x06, 0xa0, 0x82, 0x07, 0x6b, 0x30, 0x82, 0x07, 0x67, 0x02, 0x01, 0x00, + 0x30, 0x82, 0x07, 0x60, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, + 0x01, 0x07, 0x01, 0x30, 0x5f, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x05, 0x0d, 0x30, 0x52, 0x30, 0x31, 0x06, 0x09, 0x2a, 0x86, + 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x05, 0x0c, 0x30, 0x24, 0x04, 0x10, 0xee, + 0x4c, 0xf0, 0xfe, 0xd0, 0x8f, 0xd3, 0xc4, 0xa0, 0x63, 0x6d, 0x1c, 0xee, + 0x99, 0xf0, 0x0d, 0x02, 0x02, 0x08, 0x00, 0x30, 0x0c, 0x06, 0x08, 0x2a, + 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x09, 0x05, 0x00, 0x30, 0x1d, 0x06, + 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x2a, 0x04, 0x10, + 0x85, 0xef, 0x8a, 0xc1, 0x5d, 0x12, 0xd0, 0x75, 0xaa, 0x83, 0x08, 0xf6, + 0xd9, 0x59, 0x67, 0x61, 0x80, 0x82, 0x06, 0xf0, 0x25, 0xde, 0x75, 0xcb, + 0xbd, 0x86, 0x76, 0x5d, 0x55, 0x86, 0xb2, 0xd8, 0x25, 0x13, 0x4e, 0x63, + 0xb4, 0x7e, 0x94, 0xbf, 0xf9, 0xf8, 0xc0, 0x2a, 0x89, 0x9a, 0xd4, 0x79, + 0x6c, 0xf0, 0x47, 0xc1, 0x51, 0x1c, 0x36, 0x6d, 0xd7, 0xee, 0x14, 0x40, + 0xc9, 0xb2, 0x7c, 0x08, 0x92, 0xd8, 0xf2, 0xd9, 0x71, 0x7f, 0xd9, 0x05, + 0x4d, 0x86, 0xa1, 0x23, 0x11, 0x25, 0xc6, 0x79, 0x35, 0xb0, 0x45, 0xcc, + 0x44, 0xbc, 0x34, 0xe6, 0x05, 0x91, 0xf1, 0xa3, 0xe1, 0x1d, 0x6f, 0x4a, + 0xd0, 0xfa, 0x51, 0x90, 0x74, 0x0d, 0x15, 0x28, 0xd6, 0xda, 0xad, 0xf0, + 0x17, 0x94, 0x8f, 0x81, 0x37, 0x4d, 0x91, 0x75, 0x93, 0xeb, 0xbe, 0xc5, + 0x59, 0xbc, 0x96, 0x0e, 0xce, 0xae, 0x1b, 0x7d, 0xfb, 0x49, 0x81, 0x2b, + 0xf6, 0x33, 0x90, 0xa6, 0xa7, 0xfe, 0xd0, 0x40, 0x2e, 0x60, 0x02, 0xc4, + 0x54, 0x34, 0x63, 0x21, 0xf5, 0x9c, 0x97, 0x26, 0x42, 0xcd, 0xf3, 0x97, + 0x07, 0x5a, 0x6e, 0x41, 0x59, 0x6d, 0xae, 0xa9, 0xc6, 0xa3, 0x1f, 0xbc, + 0x18, 0xfe, 0x8b, 0xf4, 0x14, 0xcd, 0x9c, 0x8a, 0x90, 0x83, 0xd0, 0x1b, + 0x1b, 0x84, 0x7f, 0x5c, 0x38, 0xfa, 0x2a, 0x2b, 0x1e, 0x88, 0xa8, 0xe7, + 0x0e, 0x17, 0xa2, 0xec, 0x63, 0x17, 0x8b, 0xe0, 0xdf, 0xb2, 0xdf, 0x9b, + 0x41, 0x84, 0xf5, 0x43, 0x97, 0x7d, 0x19, 0xf8, 0xe7, 0x41, 0xdb, 0x1f, + 0xfd, 0x9a, 0xe2, 0x5c, 0x53, 0x35, 0x71, 0x23, 0x36, 0x3a, 0x0c, 0x2f, + 0x1e, 0x6a, 0x43, 0x24, 0x16, 0xea, 0x43, 0x6a, 0xe2, 0xf1, 0x1b, 0xc5, + 0x0d, 0x04, 0xa9, 0xe4, 0xc5, 0xe3, 0x9b, 0x8d, 0xb0, 0x05, 0xf6, 0x13, + 0x5f, 0xfa, 0x87, 0x4c, 0x5d, 0xa1, 0xcb, 0x41, 0x0f, 0xd5, 0x2b, 0x4c, + 0xcb, 0xcf, 0x76, 0xaf, 0x94, 0x0d, 0x31, 0x45, 0xe0, 0x98, 0x42, 0xdb, + 0x59, 0xa0, 0x57, 0x77, 0x29, 0x2e, 0x92, 0x2f, 0x99, 0x6f, 0x1f, 0xc6, + 0x09, 0x3c, 0xb3, 0xfd, 0x03, 0x7f, 0xfe, 0x82, 0x44, 0x71, 0xe8, 0x9d, + 0x2e, 0x45, 0x73, 0xfd, 0x51, 0xa1, 0x09, 0x36, 0xb6, 0x08, 0xf4, 0xb4, + 0x2d, 0x95, 0x7d, 0x28, 0x99, 0x18, 0xd6, 0xe9, 0xbe, 0xa0, 0x8f, 0x89, + 0x92, 0xa2, 0x18, 0x55, 0x3d, 0x64, 0x78, 0xb1, 0xa6, 0x4d, 0xa7, 0x9a, + 0x99, 0x68, 0x62, 0x64, 0x01, 0x45, 0x21, 0x71, 0x6f, 0xa1, 0x19, 0x43, + 0xdc, 0xb7, 0x87, 0xf1, 0x49, 0x2d, 0xc7, 0xc4, 0x8f, 0x18, 0xb1, 0xfa, + 0x26, 0x2b, 0xe8, 0x7a, 0xc0, 0x9a, 0x2e, 0xb5, 0xf0, 0xb4, 0x6f, 0x3f, + 0xff, 0x9f, 0x72, 0x28, 0x2e, 0x45, 0x52, 0x61, 0x47, 0xa7, 0x19, 0xb3, + 0x46, 0x7a, 0x24, 0x2f, 0xc9, 0x9f, 0xb4, 0xe9, 0xb4, 0x93, 0x49, 0x40, + 0x30, 0x00, 0x88, 0x65, 0xb3, 0x8d, 0xec, 0xd2, 0x80, 0x19, 0xed, 0xcb, + 0xfe, 0x63, 0x8e, 0xed, 0x82, 0x1a, 0x0f, 0x53, 0xa2, 0x1f, 0xbe, 0x6f, + 0xf3, 0xed, 0x9d, 0x12, 0x37, 0x3b, 0xba, 0x6f, 0xdf, 0x40, 0x7f, 0x15, + 0x58, 0x06, 0xa5, 0xa5, 0x27, 0xc7, 0x7d, 0xcc, 0x80, 0xe5, 0x6c, 0x6d, + 0x0c, 0x5a, 0xdb, 0xc3, 0x49, 0xb9, 0x94, 0x2d, 0xa8, 0x12, 0x5e, 0xa4, + 0x93, 0x0c, 0xf4, 0x8f, 0x36, 0xd4, 0xe8, 0x1d, 0x7d, 0xbc, 0x35, 0x57, + 0x61, 0x29, 0xca, 0xb7, 0xe4, 0xb3, 0x9c, 0xbc, 0x11, 0x57, 0x15, 0x89, + 0xef, 0xda, 0x43, 0xa6, 0xe5, 0x9c, 0xb9, 0x1c, 0xa1, 0x61, 0x8b, 0xe4, + 0x35, 0x13, 0x11, 0xd4, 0x56, 0xba, 0xd2, 0x3c, 0xfd, 0xfc, 0x5e, 0x12, + 0xf9, 0x55, 0x2d, 0x29, 0x66, 0x6d, 0x58, 0xaa, 0x71, 0x2b, 0xae, 0xba, + 0x4b, 0xe0, 0x87, 0x90, 0x0b, 0x1d, 0x73, 0xe2, 0xe7, 0xc2, 0x0d, 0x98, + 0x3a, 0x39, 0x23, 0xdb, 0x1e, 0xb9, 0x14, 0x68, 0xb1, 0xbc, 0x27, 0xe7, + 0x7b, 0xc2, 0x3a, 0x4c, 0x43, 0x03, 0xf7, 0x48, 0x84, 0x68, 0x0a, 0xca, + 0xd6, 0xbd, 0xa6, 0xb3, 0xf6, 0x1c, 0x5e, 0x18, 0xf9, 0x6b, 0x97, 0x98, + 0x36, 0x94, 0xe7, 0x69, 0xab, 0x46, 0x71, 0xac, 0xb1, 0x77, 0x16, 0xe6, + 0x79, 0x9a, 0xab, 0x7c, 0x8b, 0x06, 0x98, 0x43, 0x87, 0xa0, 0xd7, 0x57, + 0x7c, 0x79, 0x46, 0x7a, 0x59, 0x08, 0xde, 0x92, 0x90, 0x7b, 0xe7, 0x9b, + 0xe9, 0xb6, 0xc5, 0xf6, 0x9e, 0x62, 0x61, 0x75, 0x02, 0x99, 0x13, 0x66, + 0x9d, 0x8f, 0x3a, 0xc3, 0x38, 0xd0, 0x66, 0xdc, 0x48, 0xbe, 0xb5, 0xe7, + 0x89, 0xa8, 0x97, 0x54, 0xf8, 0x70, 0x7d, 0xab, 0x51, 0xb3, 0xe7, 0x70, + 0x91, 0x22, 0xe5, 0xa5, 0x8b, 0x44, 0x94, 0x0e, 0x26, 0x1d, 0xe6, 0x77, + 0x8a, 0x19, 0x00, 0x8b, 0xb6, 0x1b, 0x5d, 0x28, 0xca, 0xaa, 0x94, 0xe7, + 0x73, 0x5f, 0x7c, 0xaf, 0xf7, 0x72, 0x96, 0xda, 0x8d, 0x70, 0x74, 0x57, + 0x81, 0x82, 0x3c, 0xb6, 0xe9, 0x9d, 0xad, 0x76, 0x13, 0x7f, 0xb1, 0x49, + 0x76, 0x9e, 0x7c, 0x50, 0x33, 0x46, 0x77, 0x07, 0x3c, 0x30, 0xa6, 0x78, + 0xec, 0xba, 0x4a, 0xe0, 0x90, 0xf3, 0x28, 0x14, 0xd8, 0x42, 0xac, 0x63, + 0xcb, 0xf8, 0x64, 0xe3, 0x0a, 0x95, 0x8c, 0xfb, 0x85, 0x19, 0xd1, 0xe1, + 0xc4, 0xfa, 0x59, 0xdb, 0xbc, 0x95, 0xc1, 0x8a, 0x3e, 0x76, 0xce, 0x34, + 0x95, 0x70, 0x0c, 0xb4, 0x67, 0x23, 0x86, 0x4a, 0xeb, 0x35, 0xcf, 0x61, + 0xd0, 0xe9, 0x47, 0x04, 0x8b, 0xc3, 0x34, 0xc4, 0xf1, 0x28, 0x7a, 0x14, + 0x77, 0x18, 0x2c, 0xe8, 0xac, 0xac, 0xfc, 0x7e, 0x6f, 0x94, 0x2c, 0xb6, + 0xaa, 0xe5, 0xbf, 0x56, 0x78, 0x3f, 0x9a, 0x9a, 0x5b, 0x94, 0x39, 0xce, + 0x8b, 0x8b, 0xde, 0xb9, 0xf3, 0xb1, 0x15, 0x0f, 0x60, 0x79, 0x50, 0xa8, + 0x4d, 0xf6, 0x9b, 0x89, 0xf6, 0xfc, 0x8c, 0x00, 0xfd, 0x29, 0xa1, 0x1c, + 0x7a, 0x79, 0x57, 0xb6, 0xcb, 0x28, 0x0e, 0x71, 0x26, 0x59, 0x48, 0x63, + 0x83, 0xfa, 0xb8, 0xc6, 0x52, 0x99, 0x52, 0xac, 0xba, 0x26, 0x98, 0xec, + 0xbb, 0x0e, 0x2d, 0x23, 0xc0, 0x79, 0xcc, 0x93, 0x10, 0x38, 0xff, 0x67, + 0xe7, 0x4a, 0x3b, 0x3e, 0xdc, 0x01, 0x18, 0xa2, 0x43, 0x82, 0x45, 0x3d, + 0x18, 0x6e, 0x35, 0xfe, 0x2f, 0x28, 0xe0, 0x65, 0x69, 0x59, 0x84, 0x0e, + 0xe5, 0x1f, 0x5e, 0x00, 0x74, 0x1c, 0xf3, 0xa0, 0x51, 0x27, 0xae, 0x2a, + 0xc4, 0x2f, 0x41, 0x67, 0x38, 0xef, 0x32, 0x4f, 0xa5, 0x2a, 0x4b, 0x36, + 0xc3, 0x79, 0xfc, 0x21, 0x4e, 0x4f, 0x43, 0x3b, 0x26, 0xbc, 0xdd, 0xd1, + 0xa0, 0xa8, 0xee, 0x68, 0x7f, 0xd3, 0xbf, 0x8c, 0xde, 0x49, 0x46, 0xc1, + 0xec, 0x18, 0x7c, 0x9f, 0x39, 0x81, 0xde, 0x6f, 0x53, 0x9c, 0xe0, 0x47, + 0x9b, 0xf6, 0xa1, 0x11, 0x59, 0x60, 0xe5, 0xfb, 0x96, 0xb0, 0xe1, 0xcf, + 0x19, 0xdc, 0x68, 0x64, 0xe1, 0x26, 0x92, 0x96, 0x54, 0xc3, 0x7f, 0xac, + 0x5b, 0xa6, 0x34, 0xc3, 0x6e, 0x47, 0x64, 0xa4, 0x31, 0xa4, 0x97, 0x66, + 0x92, 0x7d, 0x78, 0x24, 0xe8, 0x86, 0xea, 0x29, 0x9b, 0x76, 0x8e, 0xd0, + 0x5c, 0x4c, 0x74, 0x8a, 0xf9, 0x13, 0x4f, 0xb7, 0x77, 0x26, 0x04, 0xcd, + 0xfd, 0x9d, 0x65, 0x79, 0x5b, 0xd8, 0xd5, 0xe7, 0x24, 0x76, 0x29, 0x6e, + 0xf8, 0x17, 0x07, 0x97, 0x4c, 0x38, 0x62, 0x31, 0x0e, 0x98, 0xb1, 0x5a, + 0x42, 0xd4, 0xa5, 0xe2, 0xcd, 0xef, 0xe0, 0x78, 0xbd, 0x0d, 0x9f, 0x35, + 0x1b, 0x1c, 0x96, 0x2b, 0x5a, 0xb0, 0xc9, 0x5f, 0xfe, 0xac, 0xfe, 0x3a, + 0x8f, 0x51, 0x11, 0x22, 0x15, 0xdb, 0x28, 0x10, 0xc5, 0x44, 0xad, 0x98, + 0x13, 0x3b, 0x0d, 0x77, 0xe4, 0x3f, 0xc2, 0x39, 0xf6, 0x9f, 0x51, 0x25, + 0xd0, 0x19, 0x1e, 0x60, 0x4e, 0x00, 0xb1, 0x93, 0x4a, 0x8b, 0x24, 0xce, + 0x7c, 0x0d, 0xbe, 0xb7, 0x85, 0x33, 0x8c, 0x01, 0x66, 0x5d, 0xf8, 0x32, + 0x00, 0xb8, 0xb5, 0xf5, 0x7e, 0xd8, 0xa7, 0xc2, 0x80, 0xdb, 0x56, 0xf3, + 0x63, 0x99, 0xcd, 0x66, 0xef, 0xd3, 0xc5, 0x5c, 0x00, 0xb4, 0x73, 0x27, + 0x8e, 0x43, 0xa2, 0x24, 0x6b, 0x6f, 0x2a, 0xc1, 0xd1, 0x29, 0x23, 0x5b, + 0x82, 0xf5, 0x26, 0x53, 0x8f, 0x70, 0x03, 0x7b, 0x2d, 0x67, 0xcd, 0x27, + 0x09, 0x50, 0x3e, 0xfc, 0xe2, 0xb5, 0x7e, 0x5f, 0x16, 0x58, 0xb2, 0x9d, + 0xd1, 0xb5, 0xd8, 0x96, 0xa3, 0x3b, 0x70, 0xe9, 0x8c, 0x33, 0xcc, 0x29, + 0xb2, 0xc3, 0xc2, 0x21, 0x48, 0xe8, 0x2d, 0x53, 0xeb, 0xa5, 0x24, 0xbb, + 0x24, 0x99, 0x8d, 0xb6, 0x4c, 0x4e, 0x31, 0xad, 0x55, 0xff, 0xb1, 0x3e, + 0x95, 0x33, 0xa8, 0xcc, 0xef, 0xd8, 0x3a, 0xf7, 0x3d, 0x7d, 0x55, 0xfd, + 0x3f, 0xa1, 0x29, 0xba, 0xe6, 0x7d, 0x1d, 0xf2, 0x27, 0xb8, 0x7b, 0xcf, + 0xd0, 0x42, 0x79, 0x36, 0x72, 0xb0, 0x47, 0xd5, 0xff, 0x8c, 0x02, 0x64, + 0x65, 0x1e, 0x6c, 0x94, 0x45, 0x25, 0x97, 0xc0, 0xa5, 0x84, 0x6b, 0xd6, + 0x11, 0x95, 0x03, 0x6e, 0x3f, 0xbc, 0x32, 0x97, 0xa5, 0xf2, 0x36, 0xb5, + 0xb8, 0x41, 0x39, 0xcc, 0x8d, 0xe5, 0xac, 0x4f, 0xa0, 0xdc, 0xa2, 0x8e, + 0xd2, 0xe5, 0xfa, 0x73, 0x30, 0xb5, 0x30, 0x7e, 0x79, 0x43, 0x74, 0x45, + 0x5d, 0xb6, 0xa1, 0x3d, 0x31, 0xf6, 0x0e, 0x6a, 0x18, 0x9c, 0x72, 0x49, + 0x44, 0xb1, 0xf6, 0x0d, 0x27, 0xd8, 0x63, 0x1b, 0xbc, 0xbe, 0x0f, 0x04, + 0x10, 0x43, 0xd2, 0x50, 0xd5, 0x36, 0xa2, 0x82, 0x78, 0x74, 0x51, 0xe2, + 0x68, 0xd0, 0x84, 0x13, 0x5d, 0xf8, 0xa5, 0x7a, 0x73, 0x69, 0x64, 0xc1, + 0x29, 0xd0, 0x6e, 0xd1, 0x03, 0xaa, 0xfc, 0x03, 0xf2, 0xbf, 0xb1, 0xe3, + 0x33, 0xba, 0x3e, 0x5f, 0x27, 0xcc, 0x78, 0x33, 0xca, 0x73, 0xe1, 0x1a, + 0x05, 0xb0, 0x66, 0xac, 0xc3, 0x5b, 0xe3, 0x34, 0x06, 0x36, 0x44, 0x92, + 0x8d, 0x5d, 0x25, 0x6f, 0x19, 0xa7, 0x6f, 0xf1, 0xa8, 0xb3, 0x9f, 0x37, + 0xf6, 0x22, 0x70, 0xf8, 0x61, 0x85, 0x58, 0xcb, 0x83, 0x5d, 0x86, 0x0a, + 0xbe, 0x7c, 0x85, 0x09, 0x3f, 0xa5, 0x4f, 0xb0, 0x89, 0xf6, 0x09, 0xaa, + 0x1e, 0x90, 0x1e, 0x55, 0xe6, 0x23, 0xb2, 0x4b, 0xed, 0x09, 0xa8, 0x6a, + 0x0e, 0x21, 0xa6, 0x92, 0xac, 0x17, 0x92, 0xe1, 0xa7, 0x36, 0x83, 0xbd, + 0xe5, 0xae, 0xf4, 0x00, 0x4e, 0x48, 0x7f, 0xda, 0x2f, 0x6f, 0xe3, 0x83, + 0xf5, 0x04, 0x28, 0xbe, 0xb6, 0xbb, 0x68, 0x85, 0x9a, 0x04, 0x1b, 0x44, + 0xfb, 0x6f, 0x62, 0xcd, 0x71, 0x88, 0x3d, 0xf8, 0x75, 0x2a, 0x41, 0x0a, + 0xeb, 0xfa, 0x4a, 0xe6, 0x52, 0x0f, 0xaf, 0xe3, 0xfe, 0x50, 0xcc, 0xf3, + 0x50, 0x57, 0x57, 0xff, 0x42, 0x62, 0x57, 0x78, 0x44, 0xa5, 0x50, 0xc6, + 0x66, 0xd1, 0x0d, 0x28, 0xf1, 0xa2, 0xdc, 0x07, 0xc8, 0x35, 0x85, 0xd4, + 0xea, 0x7a, 0xec, 0x41, 0x48, 0x2f, 0x90, 0x2d, 0x97, 0x77, 0x97, 0x3e, + 0xa9, 0x1a, 0x48, 0x7e, 0x6e, 0x83, 0x91, 0xcc, 0xba, 0x2a, 0xcd, 0x87, + 0x00, 0x85, 0xe1, 0x15, 0x8f, 0xbc, 0xf5, 0xf7, 0x28, 0x5f, 0x55, 0x94, + 0x52, 0x06, 0xe8, 0x40, 0x0d, 0x75, 0x1a, 0xe3, 0xe6, 0xcf, 0xc5, 0xc2, + 0x36, 0x29, 0x46, 0x8e, 0xa8, 0x4e, 0x64, 0x5e, 0xf0, 0x58, 0xe7, 0x34, + 0x5d, 0xd7, 0x71, 0x11, 0xcd, 0x78, 0x9f, 0xcf, 0x61, 0x7d, 0x71, 0x31, + 0x99, 0x53, 0xd2, 0xe6, 0x8c, 0xf7, 0xb5, 0x66, 0xcb, 0xcd, 0xb8, 0x90, + 0x04, 0x77, 0x8a, 0xdf, 0xb8, 0xf7, 0x59, 0xc9, 0x5b, 0xee, 0xcb, 0x9e, + 0xe8, 0xca, 0x6a, 0x10, 0xc5, 0xc2, 0xa6, 0x7e, 0xe1, 0x3e, 0xc4, 0x37, + 0x74, 0x98, 0xa2, 0x58, 0x3e, 0xfb, 0x34, 0x75, 0xbc, 0xb6, 0xd1, 0x30, + 0x8f, 0xc8, 0x3f, 0xeb, 0x1e, 0x6c, 0xdf, 0x01, 0x9d, 0xdf, 0xf0, 0x15, + 0x99, 0xfd, 0x9b, 0xf8, 0xda, 0xe2, 0xb2, 0xb9, 0x39, 0xa9, 0x81, 0xaa, + 0x86, 0x00, 0x45, 0xd0, 0x09, 0x5e, 0x65, 0xc9, 0x11, 0x83, 0x48, 0x65, + 0x92, 0x0e, 0x2a, 0x77, 0x15, 0x16, 0xaa, 0x50, 0x07, 0x31, 0xe5, 0xc7, + 0xd4, 0x92, 0x60, 0x39, 0xc9, 0xa6, 0x92, 0x4b, 0x2e, 0x86, 0x34, 0x73, + 0x2f, 0x6e, 0xfc, 0x8a, 0xa1, 0xc4, 0x06, 0xab, 0xf5, 0xb2, 0x01, 0x41, + 0x0d, 0xa3, 0x0e, 0x8b, 0x9d, 0x36, 0x07, 0xbd, 0xbc, 0x60, 0xf1, 0x69, + 0xcf, 0x7c, 0xff, 0x3d, 0xf2, 0x8e, 0x6c, 0xf0, 0xa9, 0xba, 0x1e, 0xed, + 0x08, 0x0a, 0x58, 0x1a, 0x34, 0xbf, 0x6f, 0x33, 0x5f, 0xe7, 0x76, 0xf4, + 0x35, 0x92, 0xa0, 0xa8, 0x09, 0xca, 0xaa, 0xee, 0xe4, 0xc4, 0x4c, 0xa7, + 0x40, 0x20, 0x48, 0x31, 0x0f, 0xa2, 0xbb, 0x42, 0x17, 0x47, 0xb9, 0xf9, + 0x76, 0x9c, 0xf9, 0x6b, 0x79, 0x34, 0xa1, 0xeb, 0x94, 0x64, 0x91, 0x38, + 0x3d, 0xc0, 0x3f, 0x08, 0xe0, 0x56, 0xa9, 0x0b, 0x0d, 0x4b, 0xcc, 0x9d, + 0xc7, 0xdc, 0x5c, 0x4d, 0x18, 0x17, 0xb9, 0xee, 0xbf, 0x85, 0x21, 0xc2, + 0xeb, 0x99, 0x2b, 0x86, 0x21, 0x20, 0xdf, 0xf4, 0xc1, 0xa5, 0x59, 0x89, + 0x4f, 0xc0, 0xa2, 0x9f, 0x20, 0xdc, 0xc6, 0xf9, 0x9f, 0xfa, 0xfd, 0x53, + 0x33, 0x4f, 0xa6, 0xb8, 0xe7, 0xb2, 0xf0, 0xc9, 0x30, 0x82, 0x05, 0x8c, + 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x01, 0xa0, + 0x82, 0x05, 0x7d, 0x04, 0x82, 0x05, 0x79, 0x30, 0x82, 0x05, 0x75, 0x30, + 0x82, 0x05, 0x71, 0x06, 0x0b, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, + 0x0c, 0x0a, 0x01, 0x02, 0xa0, 0x82, 0x05, 0x39, 0x30, 0x82, 0x05, 0x35, + 0x30, 0x5f, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x05, + 0x0d, 0x30, 0x52, 0x30, 0x31, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x05, 0x0c, 0x30, 0x24, 0x04, 0x10, 0x0f, 0x2a, 0x09, 0xd1, + 0x70, 0xd5, 0x1c, 0xe4, 0x2e, 0xc2, 0xac, 0x82, 0x0c, 0xdd, 0x11, 0x32, + 0x02, 0x02, 0x08, 0x00, 0x30, 0x0c, 0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x02, 0x09, 0x05, 0x00, 0x30, 0x1d, 0x06, 0x09, 0x60, 0x86, + 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x2a, 0x04, 0x10, 0x81, 0x10, 0xe4, + 0xff, 0xdd, 0x0c, 0x0e, 0xed, 0x45, 0x46, 0x0a, 0x1b, 0xcf, 0x78, 0x3c, + 0xd3, 0x04, 0x82, 0x04, 0xd0, 0xee, 0x4b, 0x1d, 0x9d, 0xa9, 0xa2, 0x3b, + 0xf8, 0x48, 0x8d, 0xe5, 0x28, 0x18, 0x41, 0xe6, 0x40, 0x4b, 0xa7, 0x61, + 0xef, 0x77, 0x7a, 0xfe, 0x28, 0x8d, 0x39, 0x51, 0x0e, 0xdc, 0x24, 0x4f, + 0xe0, 0x1e, 0x09, 0x28, 0x01, 0x88, 0x19, 0x4c, 0x37, 0xc2, 0x99, 0x2c, + 0x6c, 0x2f, 0x3b, 0xbd, 0x24, 0x80, 0x98, 0x18, 0x33, 0x5e, 0x2b, 0x0b, + 0xe4, 0xcd, 0x00, 0xec, 0x9b, 0x0c, 0xdd, 0xf5, 0x68, 0x3b, 0x57, 0xeb, + 0x72, 0x9a, 0xab, 0x73, 0xa4, 0x9f, 0x9a, 0x40, 0xec, 0xe8, 0x74, 0x95, + 0xe4, 0x55, 0xb0, 0x0c, 0x6d, 0xcb, 0xdf, 0x7f, 0x7c, 0x58, 0xf6, 0xc5, + 0x99, 0xd7, 0x57, 0x56, 0x90, 0xf9, 0x78, 0x0f, 0x15, 0xd3, 0x7e, 0xc9, + 0xff, 0x6a, 0xcc, 0xfa, 0x98, 0xdd, 0x0d, 0x12, 0xfb, 0x7a, 0xb8, 0x2e, + 0x71, 0xf6, 0x4e, 0xa1, 0x1d, 0x84, 0x3a, 0x58, 0x02, 0x34, 0x2b, 0x03, + 0xab, 0x4b, 0x14, 0xd9, 0xa1, 0xa1, 0xa6, 0x15, 0x64, 0x06, 0x37, 0xf6, + 0xa6, 0x88, 0x2c, 0x22, 0xab, 0x5d, 0x3e, 0x46, 0x3b, 0xe8, 0x3c, 0x4a, + 0x96, 0x33, 0x69, 0xa2, 0x7c, 0x0f, 0xcf, 0xc1, 0x3b, 0x89, 0x27, 0x55, + 0x0d, 0xac, 0x60, 0xf1, 0x81, 0x22, 0x40, 0x24, 0x46, 0xd9, 0x47, 0x22, + 0xd1, 0xc9, 0xb4, 0xf7, 0x17, 0x86, 0x46, 0x3f, 0x73, 0x6c, 0xb6, 0x7d, + 0x20, 0x29, 0xda, 0x9c, 0x44, 0x08, 0x5d, 0xc1, 0x6f, 0x23, 0x90, 0xaf, + 0x57, 0xc0, 0x47, 0xfa, 0x42, 0x02, 0x1e, 0x22, 0x88, 0x1e, 0x5e, 0x38, + 0x2a, 0x20, 0x97, 0xd9, 0x0f, 0xb7, 0x62, 0xc4, 0xa7, 0x85, 0x3b, 0xd4, + 0x97, 0xe0, 0x33, 0x83, 0x91, 0x86, 0xc1, 0x34, 0xfe, 0x2c, 0xf4, 0x49, + 0x98, 0x1c, 0x52, 0x23, 0x77, 0x39, 0x33, 0x4a, 0x2b, 0x8d, 0x10, 0x67, + 0x57, 0x06, 0x01, 0x68, 0x60, 0x6c, 0x97, 0x71, 0x18, 0xd2, 0x30, 0x91, + 0xba, 0x43, 0xf7, 0x31, 0x2b, 0xe1, 0x64, 0xc9, 0xc7, 0xf4, 0x63, 0x2d, + 0x0a, 0xf4, 0xe1, 0x57, 0xab, 0x52, 0x37, 0x56, 0x66, 0x53, 0xbd, 0xdd, + 0xfa, 0xbf, 0x25, 0x65, 0xf7, 0x6e, 0xdf, 0x9d, 0x79, 0x26, 0x93, 0x41, + 0xf2, 0x82, 0x0c, 0x57, 0xaa, 0x40, 0x49, 0x1e, 0x21, 0x47, 0xe1, 0x41, + 0x7f, 0xc5, 0x47, 0x08, 0xa9, 0x3f, 0xb4, 0xa2, 0xb5, 0xb4, 0xf6, 0x52, + 0x2e, 0x5a, 0x1e, 0xa7, 0x54, 0x23, 0xee, 0x32, 0x11, 0x18, 0x1f, 0x1a, + 0x8a, 0x18, 0x48, 0x9e, 0x06, 0x2a, 0x28, 0x53, 0x9c, 0x9d, 0x0d, 0xce, + 0xef, 0x2e, 0xba, 0xce, 0xff, 0x2a, 0x8b, 0x55, 0x5d, 0x71, 0x36, 0x65, + 0x2c, 0x6d, 0xd2, 0xbb, 0x51, 0x5a, 0x87, 0xa3, 0x99, 0x01, 0x4f, 0x45, + 0x5b, 0xce, 0xff, 0xcc, 0xf6, 0x16, 0x34, 0xd9, 0xc4, 0xec, 0xa1, 0xe6, + 0x25, 0x8b, 0x0e, 0x64, 0x00, 0xc4, 0x9e, 0x8c, 0xb0, 0xa6, 0xa9, 0x4a, + 0x52, 0x81, 0x0a, 0xab, 0x6f, 0x98, 0x87, 0xf2, 0xe1, 0xad, 0x60, 0x98, + 0xf6, 0xfa, 0xe6, 0xc6, 0xcb, 0xdd, 0xd8, 0x59, 0x88, 0xe7, 0xd1, 0x67, + 0xb2, 0x16, 0x16, 0x97, 0x19, 0x18, 0x4b, 0xec, 0x6d, 0x96, 0xc0, 0xc5, + 0xaa, 0x2f, 0xc9, 0xa8, 0xa7, 0x3e, 0x29, 0x23, 0x85, 0xfd, 0x64, 0xf9, + 0xd2, 0x27, 0x1c, 0x1c, 0x11, 0x46, 0xf0, 0x9e, 0xfe, 0x87, 0xde, 0x14, + 0xa9, 0xc5, 0x6a, 0x2a, 0xe7, 0x1b, 0xd1, 0x9e, 0xd3, 0x2c, 0xbd, 0x75, + 0x07, 0x8a, 0xb9, 0x01, 0xfe, 0xa5, 0xfb, 0x4e, 0x6d, 0x69, 0x01, 0x97, + 0x68, 0x02, 0xc3, 0xec, 0xd5, 0xbb, 0x20, 0xcf, 0x44, 0x33, 0xfe, 0x46, + 0x3d, 0xd1, 0xbb, 0x5c, 0x33, 0x76, 0x49, 0x58, 0x71, 0x0f, 0xa3, 0x63, + 0x54, 0x98, 0xf3, 0xee, 0xc9, 0x7a, 0x7c, 0xc0, 0xe4, 0x35, 0x4f, 0xe4, + 0x26, 0x42, 0x59, 0xe0, 0x57, 0xfa, 0xa4, 0xa0, 0xb8, 0x37, 0x6a, 0xe3, + 0x97, 0xbe, 0xcc, 0x00, 0x73, 0x10, 0xb2, 0xf7, 0x4a, 0x9d, 0x3a, 0xdc, + 0x8e, 0x7c, 0x8c, 0x2d, 0x6d, 0x1f, 0x9a, 0x52, 0x9a, 0x96, 0x0f, 0xb4, + 0x76, 0x57, 0x6c, 0x1c, 0x17, 0xe8, 0xef, 0xcb, 0x5a, 0x07, 0x74, 0x17, + 0x08, 0xf9, 0x1a, 0x7d, 0xc6, 0x3d, 0x87, 0x40, 0xef, 0xd3, 0xff, 0x3b, + 0xb5, 0x4f, 0x45, 0xec, 0x73, 0xd1, 0x2f, 0x46, 0x3d, 0x6d, 0x48, 0x97, + 0x4c, 0xb2, 0x2c, 0x7b, 0xe0, 0xee, 0x6b, 0xbd, 0xd5, 0x5b, 0x51, 0xe4, + 0xd2, 0xa0, 0x96, 0xe4, 0x6f, 0xd0, 0xeb, 0x39, 0xbc, 0xac, 0x30, 0x2f, + 0x89, 0xbf, 0xf6, 0xab, 0xc5, 0x50, 0x9d, 0xe1, 0x8b, 0x06, 0xcb, 0xa0, + 0xc7, 0x9c, 0x6e, 0x5f, 0x08, 0x62, 0xea, 0x9b, 0x4d, 0x93, 0xc1, 0xee, + 0x90, 0x1b, 0x8d, 0xd4, 0xb6, 0x29, 0xf4, 0x7d, 0xde, 0x49, 0xb6, 0x30, + 0xc2, 0x64, 0x0b, 0xde, 0x07, 0x6f, 0x2c, 0x6a, 0x32, 0x81, 0xc6, 0x0e, + 0x9e, 0xae, 0x3f, 0x93, 0x58, 0xef, 0x2c, 0xd6, 0xb5, 0xc2, 0x4a, 0x13, + 0xe4, 0x41, 0x6d, 0xf3, 0xf0, 0xc5, 0x96, 0x06, 0xf3, 0x91, 0xe7, 0xe9, + 0x94, 0x34, 0x8d, 0x76, 0xfa, 0xe7, 0xa1, 0x10, 0xc3, 0xc7, 0x73, 0x42, + 0x63, 0x21, 0xdf, 0x33, 0x53, 0xdb, 0x81, 0x0f, 0xea, 0xee, 0xba, 0x69, + 0x54, 0xfd, 0x95, 0xe1, 0xf0, 0xdd, 0x25, 0x55, 0x27, 0xf4, 0xa1, 0x51, + 0x6d, 0x19, 0x01, 0xab, 0x73, 0xdc, 0x16, 0xa5, 0xde, 0x45, 0x1f, 0x13, + 0x4e, 0x8f, 0x2a, 0x3f, 0x25, 0x4f, 0xb0, 0x70, 0xca, 0x00, 0x4c, 0x5f, + 0xf8, 0x31, 0xde, 0x53, 0xd9, 0x84, 0x70, 0xfe, 0xfe, 0xe6, 0x75, 0x8b, + 0xac, 0xb0, 0x2c, 0x1c, 0x7e, 0xb2, 0x90, 0x17, 0x37, 0xa6, 0x9c, 0x22, + 0xaa, 0xbe, 0x40, 0x6c, 0xdc, 0xa7, 0xcc, 0x6f, 0xa8, 0xb7, 0xa5, 0x57, + 0xd5, 0x16, 0xd9, 0xac, 0x9b, 0xd5, 0x1e, 0xc8, 0x5d, 0xc8, 0x01, 0xaa, + 0x3b, 0x70, 0xee, 0x80, 0xc3, 0xec, 0x89, 0xb1, 0xeb, 0x27, 0xb0, 0xca, + 0x4b, 0x73, 0x2c, 0x7c, 0x13, 0xda, 0x47, 0x51, 0x01, 0x67, 0xd8, 0xda, + 0x47, 0xb1, 0x45, 0xd5, 0x69, 0xb0, 0x5f, 0x42, 0x54, 0x51, 0x55, 0x35, + 0xe1, 0x04, 0x7b, 0x4b, 0x61, 0x3d, 0xb7, 0xe8, 0x72, 0x8b, 0x30, 0x3a, + 0x53, 0xfb, 0x7b, 0x8a, 0x77, 0x17, 0x9c, 0x18, 0x1c, 0x57, 0x71, 0x60, + 0x45, 0xad, 0xc4, 0xd7, 0x93, 0x46, 0x04, 0x08, 0x45, 0x22, 0x58, 0x21, + 0x3a, 0xbf, 0x7b, 0x45, 0x2c, 0x3b, 0x2d, 0x6a, 0xa2, 0x8e, 0xcc, 0xbb, + 0x1f, 0xf2, 0xf3, 0xaf, 0x4a, 0x78, 0xb3, 0x5c, 0x8e, 0x74, 0xcb, 0x06, + 0x52, 0xc8, 0xe9, 0x35, 0xc5, 0x35, 0xdb, 0x57, 0x34, 0xe5, 0x96, 0x41, + 0xd3, 0xdf, 0xfc, 0xe7, 0xef, 0xbd, 0x58, 0xf1, 0x43, 0x04, 0xe5, 0x0b, + 0x1e, 0x38, 0xd9, 0x2d, 0xaa, 0xb8, 0xce, 0xf5, 0x4e, 0x36, 0xb5, 0xc8, + 0x3d, 0xce, 0x9d, 0xc4, 0x1a, 0xcf, 0xac, 0x68, 0x18, 0x6f, 0xce, 0x9a, + 0xa8, 0x55, 0x65, 0x94, 0xc8, 0x70, 0x32, 0xa2, 0x74, 0x00, 0x40, 0xe5, + 0x53, 0xc1, 0xd5, 0xe6, 0x10, 0xfd, 0x18, 0xe4, 0x0c, 0xca, 0xb0, 0x47, + 0xd2, 0xae, 0x9f, 0x73, 0xf6, 0x00, 0xe5, 0xb8, 0x8b, 0xd0, 0xe9, 0xbc, + 0xfb, 0x54, 0xc9, 0x1f, 0x1c, 0xa1, 0x7e, 0x43, 0x49, 0x05, 0x9e, 0x6d, + 0xcf, 0x48, 0x73, 0x25, 0xf7, 0xfc, 0xfc, 0xae, 0xa1, 0x89, 0xa0, 0x19, + 0x19, 0xf6, 0x3e, 0x53, 0x07, 0x64, 0xaf, 0x92, 0xe1, 0x30, 0x5f, 0x58, + 0x6e, 0x3c, 0x94, 0x25, 0x1e, 0xe1, 0xdc, 0x0b, 0xf3, 0xb6, 0x3f, 0xbb, + 0xf6, 0x25, 0x9c, 0xd1, 0xe9, 0xe1, 0x87, 0x93, 0x4b, 0x88, 0x72, 0xd1, + 0xdf, 0xb1, 0x3d, 0xc4, 0x96, 0xff, 0x54, 0x17, 0x76, 0xce, 0xd1, 0xce, + 0x9e, 0x6e, 0x0a, 0x38, 0xc1, 0x35, 0xcc, 0xee, 0xdb, 0x29, 0x91, 0x15, + 0xd9, 0xb7, 0x3f, 0x82, 0x1d, 0x69, 0x89, 0x53, 0x32, 0x95, 0x7b, 0x43, + 0xa6, 0xde, 0x5a, 0x23, 0xb0, 0xa0, 0x7c, 0x35, 0xea, 0x79, 0x43, 0x27, + 0xf5, 0xf1, 0xa7, 0xeb, 0x3f, 0x8d, 0x96, 0xc1, 0x05, 0x40, 0xde, 0xfa, + 0xd1, 0x30, 0x9a, 0x26, 0xfb, 0xa0, 0x10, 0x60, 0x5b, 0x85, 0xee, 0x9a, + 0xf9, 0x14, 0x15, 0x46, 0xf3, 0x1c, 0x61, 0xe7, 0xa2, 0x9b, 0x6f, 0x8a, + 0x31, 0x1d, 0x1d, 0x71, 0xea, 0x21, 0x2c, 0x36, 0xd4, 0x52, 0x25, 0x6f, + 0xb3, 0x89, 0x6f, 0x34, 0x44, 0x6f, 0x4c, 0xd9, 0xb1, 0xe7, 0xc8, 0x21, + 0x1d, 0x7b, 0xe5, 0x18, 0x6d, 0x67, 0xf4, 0xfa, 0xfb, 0x55, 0x96, 0x6b, + 0x9b, 0xeb, 0xc6, 0x80, 0x57, 0x53, 0x6a, 0x3b, 0xda, 0xb5, 0x55, 0x93, + 0x82, 0xa0, 0xc1, 0x09, 0xfe, 0x52, 0xfe, 0xb4, 0xea, 0x6e, 0xd5, 0x37, + 0xd2, 0x55, 0x63, 0xff, 0x63, 0x65, 0x94, 0x37, 0x2c, 0x83, 0x99, 0x1f, + 0x09, 0x8f, 0xee, 0x5c, 0x3a, 0x09, 0x85, 0xfb, 0xbb, 0x8b, 0xeb, 0x19, + 0xe6, 0xdf, 0x11, 0x48, 0x5d, 0xb7, 0x7d, 0x71, 0xee, 0x09, 0xe0, 0x2d, + 0x5d, 0x5b, 0x9b, 0xf1, 0xa4, 0x83, 0xd2, 0x1a, 0x5d, 0xac, 0xef, 0xe7, + 0xa3, 0x6b, 0x52, 0x9f, 0x5d, 0x4a, 0xb6, 0x09, 0x46, 0x24, 0x94, 0x39, + 0x31, 0x31, 0x25, 0x30, 0x23, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, + 0x0d, 0x01, 0x09, 0x15, 0x31, 0x16, 0x04, 0x14, 0xe0, 0x54, 0x19, 0x5a, + 0x15, 0x9f, 0x5f, 0x13, 0xbf, 0xc1, 0x88, 0x01, 0x7e, 0x95, 0x88, 0x64, + 0xf8, 0x9f, 0x50, 0xe2, 0x30, 0x49, 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, + 0x20, 0x2b, 0xfc, 0x32, 0x0d, 0xfd, 0xa2, 0x97, 0x36, 0x58, 0x3b, 0x03, + 0x13, 0x4d, 0x66, 0x3c, 0x9d, 0x1c, 0xa7, 0x83, 0x15, 0xbb, 0xe0, 0xd3, + 0x4f, 0x9a, 0x72, 0x35, 0x81, 0x4d, 0x67, 0xb9, 0xdd, 0x04, 0x10, 0xb6, + 0xf2, 0x05, 0x54, 0xd5, 0x3c, 0x0f, 0x1b, 0x5d, 0x2d, 0x07, 0x58, 0x63, + 0x52, 0x43, 0x78, 0x02, 0x02, 0x08, 0x00, +}; + +/** Server leaf signed by root_ca_cert_pem; revoked in revoked_crl_pem. + SAN: www.example.com. Generated via the openssl ca workflow. */ +inline constexpr char const* revoked_leaf_cert_pem = + "-----BEGIN CERTIFICATE-----\n" + "MIIDGjCCAgKgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwFzEVMBMGA1UEAwwMVGVz\n" + "dCBSb290IENBMB4XDTI2MDcwODIwMzQ1NVoXDTM2MDcwNTIwMzQ1NVowHjEcMBoG\n" + "A1UEAwwTcmV2b2tlZC5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEP\n" + "ADCCAQoCggEBALOWQpheNKte2RFaJyQiKbJ/tCUYSGAQPwbPwbBpi1rgCz6FcPBK\n" + "XY3TmhtgU4ACIgt/lZ0W+vGvyoP2VH68+4Tic6/D043DEYwj6VfnCoukJszVUqwB\n" + "/bWfHg5eSRqL390LPdJCMhdSDBjlIw2Kocpt7XwL7o9azzSOlcswgP7OJhLGg3Zj\n" + "FtLuJzLCFZnZwn4Eyxv1HxhOq7v3+GJwYUo9YwHiK++6trzLOLaV4TCosWUVEueL\n" + "qMYTcB3A6kXAHlA/PfQyD55kR4Kde/kRSVvrYrODZokgmdcKjAeV4Y3Kg2Fo8qAH\n" + "RnzBoRW4+Sh1LJxvSoWJ6gG5FX8vqDAxIvkCAwEAAaNpMGcwCQYDVR0TBAIwADAa\n" + "BgNVHREEEzARgg93d3cuZXhhbXBsZS5jb20wHQYDVR0OBBYEFB3qd1NF/SNrZrr8\n" + "JoRvIEoBaYX8MB8GA1UdIwQYMBaAFIMzUb5b+JvY7MnzQFVN+CG75ojHMA0GCSqG\n" + "SIb3DQEBCwUAA4IBAQCtBcup+tPyJzAYYc8k9w6mr5itVLtJ/pY/KgAGsXdVjdPu\n" + "jEVt5/O3sxkxguSlC34MlTvs9bznt+j9pHkg7iZ1iE4nLWumBkEUdADs+HKq3h6s\n" + "l4fdDfkbI7ANPd7zf7gidwB1cfcrswBJuNUkRPg9O+59qdo690bw5ucTz2meDagt\n" + "+RZ3qoZQzGlFT+cbXt1u3pUmVnt7wMfdN9Xig5PNSx5pEEz9SDDnD5UXKqP/h7Qm\n" + "mtbntoIyAdn8mNn+2HEWaVz8zmF1ONP/dchhu54ntd731/xi0ElbFZUUWIsI/l9N\n" + "OG746U3+6RD0HP2ktKanQ3tfJOyrsmdzAFFCWTWJ\n" + "-----END CERTIFICATE-----\n"; + +/** Private key for revoked_leaf_cert_pem. */ +inline constexpr char const* revoked_leaf_key_pem = + "-----BEGIN PRIVATE KEY-----\n" + "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCzlkKYXjSrXtkR\n" + "WickIimyf7QlGEhgED8Gz8GwaYta4As+hXDwSl2N05obYFOAAiILf5WdFvrxr8qD\n" + "9lR+vPuE4nOvw9ONwxGMI+lX5wqLpCbM1VKsAf21nx4OXkkai9/dCz3SQjIXUgwY\n" + "5SMNiqHKbe18C+6PWs80jpXLMID+ziYSxoN2YxbS7icywhWZ2cJ+BMsb9R8YTqu7\n" + "9/hicGFKPWMB4ivvura8yzi2leEwqLFlFRLni6jGE3AdwOpFwB5QPz30Mg+eZEeC\n" + "nXv5EUlb62Kzg2aJIJnXCowHleGNyoNhaPKgB0Z8waEVuPkodSycb0qFieoBuRV/\n" + "L6gwMSL5AgMBAAECggEAFvzGCSSPNQZpiP0+DbPVHDTuKrdj1ZlkGpZFB99NXV8D\n" + "TB1JmmsdHuE16d4tIga5M+PraJW/SROEO9sFRXR29N4FtTsUR8zvNeiHibRtwaJ2\n" + "mE6QDADJ6Hx1x5y4WnkKuTZ4qILHpaY0x9+rWnmN1yW2Dzmkb4jOBWoLm4bZuYO+\n" + "cKZXsPAhFk897kRtK4v0zZZ3kraVjSuL4wuBi0xb5nZ59DH/mZWZH82SQuiu1r5K\n" + "adfPXZenAd9iDp+NlwuEMJ+cnFmwjrrLKYnn/H3zdwX/6ZdDk7FxGbbYP/iHC21J\n" + "rPf7mYyUJv8foJ3F8dbInBgPjqXzPmM2Gck5Z199hwKBgQDihlixSH2Kn5TweCp6\n" + "qhn5jsw8RZteSM0WSm/VcVftHtdzMS/tlrRTLDI0ZIhu1f+p4NrwStW2+0MLkanG\n" + "/RZOtErp23pmP2qnMVR1WQu3MeqbAkwM9Mm7w02nVOwBeOaWtlCP3Na4bUKbpMiG\n" + "jcyfRZORZ1eigeEopKK+1OHG9wKBgQDK9GQV/9JkfW7iLpIHFBxxhd+5TlkNqhML\n" + "lMwTAgE4KewBKKRViwVHZAWjqhRuvC1+JCiaH7QPQWKZx0eEAPNmaeXQ0yUzAQin\n" + "aaaa0UxOFkkD4kBSYkaownKuymReKwZyK9xoeSPwtXAZkJKpqHSHFkt9wFtt6i4z\n" + "l3WoNeo5jwKBgDKNzQlzpiDj0HeCOei6QaXCSq5A0pXOJYcOAbte2kKfGXIpzgp2\n" + "EbRmLqYmsZQayj39Yp8x9FQr6yCP15YDMZFLB1T9mGltSb4ackDmKIkv6K3Da3mQ\n" + "v9zZj2ECwNDrTHriIUSaAomSSMU3l1EAIGSDQJW4vIQV/Ev3wiJYnDKtAoGAD1kD\n" + "6JX79xV1OS2EZXyj2gHhtUWzflEKN6n89MMGDJU+/6dvJfjpYUizFHlcKjOYzR02\n" + "5NDY8P5k0nQ7eEQKJAiGFJCjE4RUfzSCINsLBiyxQNXvP0unREPQIF+1z1k5l5Cx\n" + "jkT67s0JuSUxshrHFSAefVf6kglPjR87ColpOQkCgYEAzvtmjisxacqneV8nn8mm\n" + "hutYvKk2a90zBD79iwMTT0+aFGT/5e4A2wzrPTCtsV0z2o1aKHIVc6y01q2iev7t\n" + "iVHRH2aBXfjfQOjNaM1wx/wtvYX1RLOXQ29l/wJ60AGhCs4qXvwxpnXlk8zMpWPS\n" + "SYpfgX5cEhMVn4H0TjRHP0E=\n" + "-----END PRIVATE KEY-----\n"; + +/** CRL signed by root_ca_cert_pem revoking revoked_leaf_cert_pem. */ +inline constexpr char const* revoked_crl_pem = + "-----BEGIN X509 CRL-----\n" + "MIIBhzBxAgEBMA0GCSqGSIb3DQEBCwUAMBcxFTATBgNVBAMMDFRlc3QgUm9vdCBD\n" + "QRcNMjYwNzA4MjAzNDU1WhcNMzYwNzA1MjAzNDU1WjAVMBMCAhAAFw0yNjA3MDgy\n" + "MDM0NTVaoA8wDTALBgNVHRQEBAICEAAwDQYJKoZIhvcNAQELBQADggEBAGv/v6zT\n" + "PHvnPndh9RffnHBKcOAGglPSP23SaMSvz4MfHYEliKNq13fuOfoMubs6nd0XLTBI\n" + "1+cKgeVGFhwbfeFjprY/5F3VtYrt/aq0VMA396A+FfRgFsutS4FLRDFApseUm/+S\n" + "AAuLKW+0hanr9rvDstDLudm+Hw3x8fVRRUuOIpVwZjsDJWP9Jz+aoLS2qDP74LaT\n" + "SVuLkjawrA56SN5DdWsSVwsvS1re8JTMm5cYEypS8UWc+LMD+8hkAkWNGS65GoFf\n" + "iAFKZ+1B7ulCCkvaiiH8pqkHfq7oZkMV2JFMrrEONkd79Opi1+N10TucEg5wC/KJ\n" + "8122Qwh+h0NOgHk=\n" + "-----END X509 CRL-----\n"; + /** Create a server context whose key needs the password callback. */ inline tls_context make_encrypted_key_server_context(bool& callback_invoked) diff --git a/test/unit/tls_context.cpp b/test/unit/tls_context.cpp index 4d44a6110..b75bc42a7 100644 --- a/test/unit/tls_context.cpp +++ b/test/unit/tls_context.cpp @@ -1,5 +1,6 @@ // // Copyright (c) 2026 Steve Gerbino +// Copyright (c) 2026 Michael Vandeberg // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -85,7 +86,6 @@ struct tls_context_test BOOST_TEST(data.verification_mode == tls_verify_mode::none); BOOST_TEST_EQ(data.verify_depth, 100); BOOST_TEST(!data.use_default_verify_paths); - BOOST_TEST(!data.require_ocsp_staple); BOOST_TEST(data.revocation == tls_revocation_policy::disabled); } @@ -242,19 +242,21 @@ struct tls_context_test } // - // PKCS#12 (currently unsupported) + // PKCS#12 // - void testPkcs12Unsupported() + void testPkcs12() { tls_context ctx; + + // In-memory data is stored and decoded lazily at handshake time, + // so the setter itself succeeds regardless of contents. auto ec = ctx.use_pkcs12("not-pkcs12-data", "password"); - BOOST_TEST(ec); - BOOST_TEST(ec == std::make_error_code(std::errc::function_not_supported)); + BOOST_TEST(!ec); - ec = ctx.use_pkcs12_file("/some/path", "password"); + // Loading from a missing file fails at the file layer. + ec = ctx.use_pkcs12_file("/nonexistent/path.p12", "password"); BOOST_TEST(ec); - BOOST_TEST(ec == std::make_error_code(std::errc::function_not_supported)); } // @@ -513,26 +515,6 @@ struct tls_context_test } } - void testOcspStaple() - { - tls_context ctx; - auto ec = ctx.set_ocsp_staple("\x30\x82\x01\x00 binary ocsp blob"); - BOOST_TEST(!ec); - BOOST_TEST(!detail::get_tls_context_data(ctx).ocsp_staple.empty()); - } - - void testRequireOcspStaple() - { - tls_context ctx; - BOOST_TEST(!detail::get_tls_context_data(ctx).require_ocsp_staple); - - ctx.set_require_ocsp_staple(true); - BOOST_TEST(detail::get_tls_context_data(ctx).require_ocsp_staple); - - ctx.set_require_ocsp_staple(false); - BOOST_TEST(!detail::get_tls_context_data(ctx).require_ocsp_staple); - } - void testRevocationPolicy() { tls_context ctx; @@ -560,7 +542,7 @@ struct tls_context_test testUseCertificateFile(); testUseCertificateChainFile(); testUsePrivateKeyFile(); - testPkcs12Unsupported(); + testPkcs12(); testAddCertificateAuthority(); testLoadVerifyFile(); @@ -578,8 +560,6 @@ struct tls_context_test testAddCrl(); testAddCrlFile(); - testOcspStaple(); - testRequireOcspStaple(); testRevocationPolicy(); } }; diff --git a/test/unit/tls_stream_tests.hpp b/test/unit/tls_stream_tests.hpp index 6b213cedb..3c3c49350 100644 --- a/test/unit/tls_stream_tests.hpp +++ b/test/unit/tls_stream_tests.hpp @@ -468,6 +468,498 @@ testSniCallback(StreamFactory make_stream) } } +/** A freshly constructed stream reports no negotiated ALPN protocol. + + The accessor must return an empty view before any handshake, on + every backend (including builds without ALPN support). +*/ +template +void +testAlpnAccessorEmpty(StreamFactory make_stream) +{ + io_context ioc; + auto [m1, m2] = corosio::test::make_mocket_pair(ioc); + auto ctx = make_client_context(); + auto stream = make_stream(m1, ctx); + BOOST_TEST(stream.alpn_protocol().empty()); + m1.close(); // NOLINT(bugprone-unused-return-value) + m2.close(); // NOLINT(bugprone-unused-return-value) +} + +/** Test CRL-based revocation. + + The server presents a leaf that a CRL revokes. + + When @p crl_supported (OpenSSL, or WolfSSL built with HAVE_CRL): + 1. hard_fail with the CRL loaded rejects the revoked leaf. + 2. soft_fail with no CRL loaded accepts (status unknown is allowed). + Otherwise (WolfSSL without HAVE_CRL): any revocation request fails the + handshake with function_not_supported rather than skip the check. + + On every build, a CRL that parses as neither PEM nor DER fails the + handshake closed rather than being silently dropped (which would let + soft_fail accept a peer the missing CRL might have revoked). +*/ +template +void +testCrlRevocation(StreamFactory make_stream, bool crl_supported) +{ + auto revoked_server = []() { + tls_context ctx; + // NOLINTNEXTLINE(bugprone-unused-return-value) + ctx.use_certificate(revoked_leaf_cert_pem, tls_file_format::pem); + // NOLINTNEXTLINE(bugprone-unused-return-value) + ctx.use_private_key(revoked_leaf_key_pem, tls_file_format::pem); + // NOLINTNEXTLINE(bugprone-unused-return-value) + ctx.set_verify_mode(tls_verify_mode::none); + return ctx; + }; + auto revoking_client = [](tls_revocation_policy policy, bool load_crl) { + tls_context ctx; + // NOLINTNEXTLINE(bugprone-unused-return-value) + ctx.add_certificate_authority(root_ca_cert_pem); + // NOLINTNEXTLINE(bugprone-unused-return-value) + ctx.set_verify_mode(tls_verify_mode::peer); + if (load_crl) + ctx.add_crl(revoked_crl_pem); // NOLINT(bugprone-unused-return-value) + ctx.set_revocation_policy(policy); + return ctx; + }; + + if (crl_supported) + { + // 1. hard_fail + CRL -> revoked leaf rejected. + { + io_context ioc; + auto client_ctx = + revoking_client(tls_revocation_policy::hard_fail, true); + auto server_ctx = revoked_server(); + run_tls_test_fail( + ioc, client_ctx, server_ctx, make_stream, make_stream); + } + // 2. soft_fail + no CRL -> unknown status accepted. + { + io_context ioc; + auto client_ctx = + revoking_client(tls_revocation_policy::soft_fail, false); + auto server_ctx = revoked_server(); + run_tls_test( + ioc, client_ctx, server_ctx, make_stream, make_stream); + } + } + else + { + io_context ioc; + auto client_ctx = + revoking_client(tls_revocation_policy::hard_fail, true); + auto server_ctx = revoked_server(); + run_tls_test_fail( + ioc, client_ctx, server_ctx, make_stream, make_stream); + } + + // A supplied CRL that parses as neither PEM nor DER must fail the + // handshake, never silently downgrade to accepting the peer. This holds + // on every build: a HAVE_CRL backend rejects the unparseable CRL, and a + // backend without CRL support rejects any revocation request outright. + // Uses soft_fail specifically: without the fail-closed guard, soft_fail + // would treat the missing (dropped) CRL as "status unknown" and accept. + { + io_context ioc; + tls_context client_ctx; + // NOLINTNEXTLINE(bugprone-unused-return-value) + client_ctx.add_certificate_authority(root_ca_cert_pem); + // NOLINTNEXTLINE(bugprone-unused-return-value) + client_ctx.set_verify_mode(tls_verify_mode::peer); + // NOLINTNEXTLINE(bugprone-unused-return-value) + client_ctx.add_crl("this is not a valid PEM or DER CRL"); + client_ctx.set_revocation_policy(tls_revocation_policy::soft_fail); + auto server_ctx = revoked_server(); + run_tls_test_fail( + ioc, client_ctx, server_ctx, make_stream, make_stream); + } + + // A CRL supplied while the policy stays disabled is inert by contract + // (consulted only when the policy is not disabled). This config must + // work on every build — including a WolfSSL build without HAVE_CRL, + // which must not reject a revocation feature that was never requested. + { + io_context ioc; + auto client_ctx = make_client_context(); + // NOLINTNEXTLINE(bugprone-unused-return-value) + client_ctx.add_crl(revoked_crl_pem); // policy left disabled + auto server_ctx = make_server_context(); + run_tls_test(ioc, client_ctx, server_ctx, make_stream, make_stream); + } +} + +/** Test loading server credentials from a PKCS#12 bundle. + + 1. A server context whose cert and key come from a PKCS#12 blob + completes a handshake with a client that trusts the CA. + 2. A wrong passphrase loads no credentials, so the handshake fails. + 3. A bundle takes precedence over discrete cert/key fields (which are + ignored when a bundle is present). + 4. A client whose bundle fails to decode fails the handshake closed + rather than silently proceeding with no credential (fail-open mTLS). + + Both backends decode PKCS#12 natively (OpenSSL PKCS12_parse, WolfSSL + wc_PKCS12_parse), so no capability branching is needed. +*/ +template +void +testPkcs12(StreamFactory make_stream) +{ + std::string_view const p12( + reinterpret_cast(server_p12), sizeof(server_p12)); + + // 1. Correct passphrase: credentials load, handshake succeeds. + { + io_context ioc; + auto client_ctx = make_client_context(); + tls_context server_ctx; + // NOLINTNEXTLINE(bugprone-unused-return-value) + server_ctx.use_pkcs12(p12, p12_password); + // NOLINTNEXTLINE(bugprone-unused-return-value) + server_ctx.set_verify_mode(tls_verify_mode::none); + run_tls_test(ioc, client_ctx, server_ctx, make_stream, make_stream); + } + + // 2. Wrong passphrase: nothing loads, handshake fails. + { + io_context ioc; + auto client_ctx = make_client_context(); + tls_context server_ctx; + // NOLINTNEXTLINE(bugprone-unused-return-value) + server_ctx.use_pkcs12(p12, "wrong-password"); + // NOLINTNEXTLINE(bugprone-unused-return-value) + server_ctx.set_verify_mode(tls_verify_mode::none); + run_tls_test_fail( + ioc, client_ctx, server_ctx, make_stream, make_stream); + } + + // 3. A PKCS#12 bundle takes precedence over discrete cert/key fields. + // The bundle holds a valid, trusted credential; the discrete cert is + // an expired self-signed one the client would reject. The handshake + // succeeds only if the bundle's credential is used — i.e. the discrete + // fields are ignored when a bundle is present. + { + io_context ioc; + auto client_ctx = make_client_context(); + tls_context server_ctx; + // NOLINTNEXTLINE(bugprone-unused-return-value) + server_ctx.use_pkcs12(p12, p12_password); + // NOLINTNEXTLINE(bugprone-unused-return-value) + server_ctx.use_certificate(expired_cert_pem, tls_file_format::pem); + // NOLINTNEXTLINE(bugprone-unused-return-value) + server_ctx.use_private_key(expired_key_pem, tls_file_format::pem); + // NOLINTNEXTLINE(bugprone-unused-return-value) + server_ctx.set_verify_mode(tls_verify_mode::none); + run_tls_test(ioc, client_ctx, server_ctx, make_stream, make_stream); + } + + // 4. A client whose PKCS#12 credential fails to decode must fail closed, + // not silently proceed with no certificate. The server verifies the + // peer with `peer` (not `require_peer`), so a client that lost its + // identity would otherwise complete the handshake — the fail-open the + // review flagged. A wrong passphrase makes the bundle fail to parse. + { + io_context ioc; + auto client_ctx = make_client_context(); + // NOLINTNEXTLINE(bugprone-unused-return-value) + client_ctx.use_pkcs12(p12, "wrong-password"); + auto server_ctx = make_server_context(); + // NOLINTNEXTLINE(bugprone-unused-return-value) + server_ctx.set_verify_mode(tls_verify_mode::peer); + run_tls_test_fail( + ioc, client_ctx, server_ctx, make_stream, make_stream); + } +} + +/** Test that the intermediate chain inside a PKCS#12 bundle is loaded + and sent during the handshake. + + The server loads a bundle containing leaf + key + intermediate. The + client trusts only the root CA, so verification succeeds only if the + server presents the intermediate — i.e. the bundle's chain was loaded, + not just the leaf. +*/ +template +void +testPkcs12Chain(StreamFactory make_stream) +{ + std::string_view const p12( + reinterpret_cast(server_chain_p12), + sizeof(server_chain_p12)); + + io_context ioc; + auto client_ctx = make_rootonly_client_context(); + tls_context server_ctx; + // NOLINTNEXTLINE(bugprone-unused-return-value) + server_ctx.use_pkcs12(p12, p12_password); + // NOLINTNEXTLINE(bugprone-unused-return-value) + server_ctx.set_verify_mode(tls_verify_mode::none); + run_tls_test(ioc, client_ctx, server_ctx, make_stream, make_stream); +} + +/** Certificate-chain delivery: the server must send its intermediate. + + Both backends load a full chain (leaf + intermediate) and send the + intermediate during the handshake, so a client that trusts only the + root CA can complete the path. A server that presents only its leaf + cannot, since the client lacks the intermediate to bridge to the root. + + 1. Server sends the full chain, client trusts only the root -> succeeds. + 2. Server sends the leaf only, client trusts only the root -> fails. + + The intermediate carries a critical basicConstraints extension so that + WolfSSL (which enforces RFC 5280 strictly) accepts it as a CA, matching + OpenSSL. +*/ +template +void +testCertificateChain(StreamFactory make_stream) +{ + // Server sends the full chain; client trusting only the root succeeds. + { + io_context ioc; + auto client_ctx = make_rootonly_client_context(); + auto server_ctx = make_fullchain_server_context(); + run_tls_test(ioc, client_ctx, server_ctx, make_stream, make_stream); + } + + // Server sends only the leaf; the client cannot build the path -> fails. + { + io_context ioc; + auto client_ctx = make_rootonly_client_context(); + auto server_ctx = make_chain_server_context(); + run_tls_test_fail( + ioc, client_ctx, server_ctx, make_stream, make_stream); + } +} + +/** set_default_verify_paths() applies cleanly on top of an explicit CA. + + Both backends can add the system trust store (OpenSSL: + SSL_CTX_set_default_verify_paths; WolfSSL: wolfSSL_CTX_load_system_CA_certs + where the build enables it, otherwise a no-op). Behaviorally exercising + the system store would require redirecting it per-platform, which is not + portable across the CI matrix, so this asserts only that the call path + runs cleanly: a client trusting the test CA explicitly *and* adding the + system store still completes the handshake. The load-from-path mechanism + is covered behaviorally by each backend's add_verify_path test. +*/ +template +void +testDefaultVerifyPaths(StreamFactory make_stream) +{ + io_context ioc; + auto client_ctx = make_client_context(); + // Adding the system store on top of the explicit CA must not break + // context creation or verification. + // NOLINTNEXTLINE(bugprone-unused-return-value) + client_ctx.set_default_verify_paths(); + + auto server_ctx = make_server_context(); + run_tls_test(ioc, client_ctx, server_ctx, make_stream, make_stream); +} + +/** Test that TLS 1.3 cipher suite selection is applied. + + 1. Both peers restricted to @p suite_a -> handshake succeeds. + 2. Client restricted to @p suite_a, server to @p suite_b (disjoint) + -> no common suite, handshake fails. + 3. An unparseable cipher string fails the handshake closed (setup_failed_) + rather than silently reverting to the default suites. + + Suite names differ between backends (OpenSSL `TLS_AES_128_GCM_SHA256` + vs WolfSSL `TLS13-AES128-GCM-SHA256`), so the caller supplies them. +*/ +template +void +testCiphersuitesTls13( + StreamFactory make_stream, char const* suite_a, char const* suite_b) +{ + auto make_ctx = [&](auto base, char const* suite) { + auto ctx = base(); + // NOLINTNEXTLINE(bugprone-unused-return-value) + ctx.set_min_protocol_version(tls_version::tls_1_3); + // NOLINTNEXTLINE(bugprone-unused-return-value) + ctx.set_ciphersuites_tls13(suite); + return ctx; + }; + + // 1. Matching suite succeeds. + { + io_context ioc; + auto client_ctx = make_ctx(make_client_context, suite_a); + auto server_ctx = make_ctx(make_server_context, suite_a); + run_tls_test(ioc, client_ctx, server_ctx, make_stream, make_stream); + } + + // 2. Disjoint suites fail. + { + io_context ioc; + auto client_ctx = make_ctx(make_client_context, suite_a); + auto server_ctx = make_ctx(make_server_context, suite_b); + run_tls_test_fail( + ioc, client_ctx, server_ctx, make_stream, make_stream); + } + + // 3. A cipher string the library rejects fails closed, rather than + // silently falling back to the default suites. + { + io_context ioc; + auto client_ctx = make_ctx(make_client_context, "not-a-real-suite"); + auto server_ctx = make_server_context(); + run_tls_test_fail( + ioc, client_ctx, server_ctx, make_stream, make_stream); + } +} + +/** Test that protocol version bounds are enforced. + + 1. Both peers pinned to TLS 1.3 -> handshake succeeds. + 2. Client capped at TLS 1.2 while the server requires TLS 1.3 -> + no common version, handshake fails. + + Both backends support version bounds natively, so no capability + branching is needed. +*/ +template +void +testProtocolVersion(StreamFactory make_stream) +{ + // 1. TLS 1.3 on both sides succeeds. + { + io_context ioc; + auto client_ctx = make_client_context(); + // NOLINTNEXTLINE(bugprone-unused-return-value) + client_ctx.set_min_protocol_version(tls_version::tls_1_3); + auto server_ctx = make_server_context(); + // NOLINTNEXTLINE(bugprone-unused-return-value) + server_ctx.set_min_protocol_version(tls_version::tls_1_3); + run_tls_test(ioc, client_ctx, server_ctx, make_stream, make_stream); + } + + // 2. Version window mismatch fails: client max 1.2, server min 1.3. + { + io_context ioc; + auto client_ctx = make_client_context(); + // NOLINTNEXTLINE(bugprone-unused-return-value) + client_ctx.set_max_protocol_version(tls_version::tls_1_2); + auto server_ctx = make_server_context(); + // NOLINTNEXTLINE(bugprone-unused-return-value) + server_ctx.set_min_protocol_version(tls_version::tls_1_3); + run_tls_test_fail( + ioc, client_ctx, server_ctx, make_stream, make_stream); + } + + // 3. An inverted window on one context (min 1.3 > max 1.2) admits no + // protocol; the handshake must fail closed rather than silently + // negotiate an unexpected version. + { + io_context ioc; + auto client_ctx = make_client_context(); + // NOLINTNEXTLINE(bugprone-unused-return-value) + client_ctx.set_min_protocol_version(tls_version::tls_1_3); + // NOLINTNEXTLINE(bugprone-unused-return-value) + client_ctx.set_max_protocol_version(tls_version::tls_1_2); + auto server_ctx = make_server_context(); + run_tls_test_fail( + ioc, client_ctx, server_ctx, make_stream, make_stream); + } +} + +/** Test ALPN negotiation end-to-end. + + Client and server both offer `{ "h2", "http/1.1" }`. + + @param alpn_supported Whether the backend build can negotiate ALPN. + When true, the handshake succeeds and both peers report `"h2"`. + When false (e.g. WolfSSL without `HAVE_ALPN`), offering ALPN must + fail the handshake with `function_not_supported` rather than + silently negotiate nothing. +*/ +template +void +testAlpn(StreamFactory make_stream, bool alpn_supported) +{ + io_context ioc; + auto [m1, m2] = corosio::test::make_mocket_pair(ioc); + + auto client_ctx = make_client_context(); + // NOLINTNEXTLINE(bugprone-unused-return-value) + client_ctx.set_alpn({"h2", "http/1.1"}); + auto server_ctx = make_server_context(); + // NOLINTNEXTLINE(bugprone-unused-return-value) + server_ctx.set_alpn({"h2", "http/1.1"}); + + auto client = make_stream(m1, client_ctx); + auto server = make_stream(m2, server_ctx); + using stream_type = std::remove_reference_t; + + std::error_code cec, sec; + auto hc = [&]() -> capy::task<> { + auto [ec] = co_await client.handshake(stream_type::client); + cec = ec; + }; + auto hs = [&]() -> capy::task<> { + auto [ec] = co_await server.handshake(stream_type::server); + sec = ec; + }; + capy::run_async(ioc.get_executor())(hc()); + capy::run_async(ioc.get_executor())(hs()); + ioc.run(); + + if (alpn_supported) + { + BOOST_TEST(!cec); + BOOST_TEST(!sec); + BOOST_TEST(client.alpn_protocol() == "h2"); + BOOST_TEST(server.alpn_protocol() == "h2"); + } + else + { + // Fail-closed: offering ALPN a build cannot honor must not + // silently proceed. + BOOST_TEST( + cec == std::errc::function_not_supported || + sec == std::errc::function_not_supported); + } + + m1.close(); // NOLINT(bugprone-unused-return-value) + m2.close(); // NOLINT(bugprone-unused-return-value) +} + +/** ALPN with no common protocol fails the handshake (RFC 7301 §3.2). + + Client offers `{"h2"}`, server offers `{"http/1.1"}`. A server that + supports ALPN but shares no protocol with the client must abort with a + fatal `no_application_protocol` alert, so both peers see the handshake + fail. + + @param alpn_supported Whether the backend build can negotiate ALPN. When + false (e.g. WolfSSL without `HAVE_ALPN`) offering ALPN already fails + closed regardless of overlap — that path is covered by @ref testAlpn — + so this test only runs when ALPN is available. +*/ +template +void +testAlpnNoOverlap(StreamFactory make_stream, bool alpn_supported) +{ + if (!alpn_supported) + return; + + io_context ioc; + auto client_ctx = make_client_context(); + // NOLINTNEXTLINE(bugprone-unused-return-value) + client_ctx.set_alpn({"h2"}); + auto server_ctx = make_server_context(); + // NOLINTNEXTLINE(bugprone-unused-return-value) + server_ctx.set_alpn({"http/1.1"}); + run_tls_test_fail(ioc, client_ctx, server_ctx, make_stream, make_stream); +} + /** Test the certificate verification callback (portable contract). Exercises the guarantees that hold on every backend that supports the diff --git a/test/unit/wolfssl_stream.cpp b/test/unit/wolfssl_stream.cpp index 239813e13..0a31cf8c3 100644 --- a/test/unit/wolfssl_stream.cpp +++ b/test/unit/wolfssl_stream.cpp @@ -80,38 +80,6 @@ struct wolfssl_stream_test BOOST_TEST(&mutable_next == &const_next); } - /** Test certificate chain validation (WolfSSL-specific). - - WolfSSL has limited certificate chain support compared to OpenSSL. - wolfSSL_CTX_add_extra_chain_cert doesn't properly send intermediates - during handshake, so fullchain tests are disabled. - */ - void testCertificateChain() - { - using namespace test; - - // Basic chain test: client trusts both CAs - { - io_context ioc; - auto client_ctx = make_chain_client_context(); - auto server_ctx = make_chain_server_context(); - run_tls_test(ioc, client_ctx, server_ctx, make_stream, make_stream); - } - - // Server sends only entity cert - client trusts only root (fails) - { - io_context ioc; - auto client_ctx = make_rootonly_client_context(); - auto server_ctx = make_chain_server_context(); - run_tls_test_fail( - ioc, client_ctx, server_ctx, make_stream, make_stream); - } - - // Note: Fullchain test disabled for WolfSSL due to - // wolfSSL_CTX_add_extra_chain_cert not properly sending - // intermediates during handshake. - } - /** Test that WolfSSL errors carry the WolfSSL category (issue #223). Errors from wolfSSL_get_error must render readable messages, not @@ -189,6 +157,7 @@ struct wolfssl_stream_test test::testCertificateValidation(make_stream); test::testSni(make_stream); test::testSniCallback(make_stream); + test::testAlpnAccessorEmpty(make_stream); // Whether the linked WolfSSL can honor a verify callback on success // (WOLFSSL_ALWAYS_VERIFY_CB) is a build-time property, queried here // at runtime so the test needs no WolfSSL headers. @@ -205,6 +174,19 @@ struct wolfssl_stream_test // fails closed rather than let a tightening callback fail open. test::testVerifyCallback(make_stream, /*callback_supported=*/false); } + // ALPN is likewise build-gated (HAVE_ALPN); when absent, offering + // protocols fails closed instead of negotiating nothing silently. + test::testAlpn(make_stream, wolfssl_supports_alpn()); + test::testAlpnNoOverlap(make_stream, wolfssl_supports_alpn()); + test::testProtocolVersion(make_stream); + test::testCiphersuitesTls13( + make_stream, "TLS13-AES128-GCM-SHA256", + "TLS13-AES256-GCM-SHA384"); + test::testPkcs12(make_stream); + test::testPkcs12Chain(make_stream); + test::testCertificateChain(make_stream); + test::testDefaultVerifyPaths(make_stream); + test::testCrlRevocation(make_stream, wolfssl_supports_crl()); test::testMtls(make_stream); test::testMoveSemantics(make_stream); test::testAbruptClose(make_stream); @@ -217,7 +199,6 @@ struct wolfssl_stream_test test::testResetViaHandshake(make_stream, cert_modes); test::testResetFuse(make_stream); - testCertificateChain(); testErrorCategory(); testAddVerifyPath(); testName();