Skip to content

Commit 0be9581

Browse files
committed
feat(tls)!: wire ALPN, protocol/cipher selection, PKCS#12, and CRL checking
Wire up TLS features that were declared but not applied, with OpenSSL and WolfSSL parity (fail-closed on WolfSSL builds lacking a capability). - ALPN: negotiate protocols on OpenSSL and add an alpn_protocol() accessor; fail closed on WolfSSL builds without HAVE_ALPN. - Protocol version: apply the configured min/max window; an inverted window fails closed. - Ciphersuites: select TLS 1.3 suites on both backends and drop the forced security level so a weak cipher string fails loudly. - PKCS#12: load bundles (leaf, key, and intermediate chain) on both backends; a bundle takes precedence over discrete cert/key fields. - CRL revocation: check on OpenSSL (PEM or DER, soft/hard fail) and fail closed on WolfSSL builds without HAVE_CRL. - Remove the OCSP stapling API, which shipped non-functional and is not implementable on the current transport. - Document the wired features and add cross-backend tests.
1 parent 19d76f3 commit 0be9581

16 files changed

Lines changed: 1966 additions & 472 deletions

File tree

‎doc/modules/ROOT/pages/3.tutorials/3d.tls-context.adoc‎

Lines changed: 42 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -36,23 +36,11 @@ handles:
3636
* Configuring trust anchors for peer verification
3737
* Setting protocol versions and cipher suites
3838
* Configuring certificate verification behavior
39-
* Managing revocation checking (CRL, OCSP)
39+
* Managing revocation checking (CRLs)
4040

4141
Use `tls_context` to configure TLS settings once, then pass it to TLS streams
4242
for establishing secure connections.
4343

44-
[WARNING]
45-
.Not yet implemented
46-
====
47-
Some settings shown in this tutorial are accepted by the API but *not yet
48-
wired up* by the backends; they are stored and silently ignored:
49-
`set_alpn()`, `set_min_protocol_version()` / `set_max_protocol_version()`,
50-
all revocation features (`add_crl*`, `set_ocsp_staple` /
51-
`set_require_ocsp_staple`, `set_revocation_policy`), and `use_pkcs12*`
52-
(returns `std::errc::function_not_supported`). `set_ciphersuites()` is
53-
applied by OpenSSL only; WolfSSL ignores it.
54-
====
55-
5644
[NOTE]
5745
====
5846
Two implemented features depend on how WolfSSL was built:
@@ -146,9 +134,10 @@ into a single password-protected file:
146134
ctx.use_pkcs12_file( "credentials.pfx", "bundle-password" );
147135
----
148136

149-
NOTE: PKCS#12 loading is not yet implemented; `use_pkcs12()` and
150-
`use_pkcs12_file()` return `std::errc::function_not_supported`. Load the
151-
certificate and key separately for now.
137+
NOTE: The bundle is decoded when the first stream is created; a wrong
138+
passphrase or malformed bundle surfaces as a handshake failure.
139+
Intermediate certificates in the bundle are loaded and sent during the
140+
handshake on both backends.
152141

153142
=== Loading from Memory
154143

@@ -281,24 +270,29 @@ ctx.set_min_protocol_version( tls_version::tls_1_3 );
281270
ctx.set_max_protocol_version( tls_version::tls_1_3 );
282271
----
283272

284-
NOTE: Protocol version bounds are not yet applied by the backends. The
285-
negotiated range is whatever the native default method provides.
273+
NOTE: On WolfSSL the ceiling is enforced by selecting a version-specific
274+
method (no native set-max call exists); a window whose minimum exceeds its
275+
maximum yields a context that fails the handshake.
286276

287277
=== Cipher Suites
288278

289-
Configure allowed cipher suites using OpenSSL-style syntax:
279+
TLS 1.2-and-below suites use an OpenSSL-style cipher list; TLS 1.3 suites
280+
are configured separately:
290281

291282
[source,cpp]
292283
----
293-
// Strong cipher suites only
284+
// TLS 1.2 and below
294285
ctx.set_ciphersuites( "ECDHE+AESGCM:ECDHE+CHACHA20" );
295286
296-
// Disable weak ciphers
297-
ctx.set_ciphersuites( "HIGH:!aNULL:!MD5:!RC4" );
287+
// TLS 1.3 (distinct API and suite names)
288+
ctx.set_ciphersuites_tls13( "TLS_AES_256_GCM_SHA384" );
298289
----
299290

300-
NOTE: `set_ciphersuites()` is applied by the OpenSSL backend only; the
301-
WolfSSL backend accepts the string but silently ignores it.
291+
NOTE: Both backends apply these (WolfSSL merges the two into a single
292+
list). Suite *names* differ between backends: OpenSSL uses
293+
`TLS_AES_128_GCM_SHA256`, WolfSSL uses `TLS13-AES128-GCM-SHA256`. The
294+
OpenSSL security level is left at the library default; include a
295+
`@SECLEVEL=` token in the cipher string if you need a lower level.
302296

303297
=== ALPN (Application-Layer Protocol Negotiation)
304298

@@ -311,13 +305,18 @@ ctx.set_alpn( { "h2", "http/1.1" } );
311305
312306
// gRPC
313307
ctx.set_alpn( { "h2" } );
308+
----
309+
310+
Read the negotiated protocol from the stream after the handshake:
314311

315-
// Custom protocol
316-
ctx.set_alpn( { "my-protocol/1.0" } );
312+
[source,cpp]
313+
----
314+
std::string_view proto = stream.alpn_protocol(); // "h2", or empty if none
317315
----
318316

319-
NOTE: ALPN is not yet wired up; the protocol list is accepted but never
320-
negotiated by either backend.
317+
NOTE: On WolfSSL, ALPN requires a `HAVE_ALPN` build; without it, offering
318+
protocols fails the handshake with `std::errc::function_not_supported`
319+
rather than negotiate nothing silently.
321320

322321
The server selects from the client's list based on its own preferences.
323322

@@ -414,31 +413,23 @@ Which certificates the callback sees depends on the backend:
414413
== Revocation Checking
415414

416415
Certificate revocation checking verifies that certificates haven't been
417-
invalidated by the issuing CA. Two mechanisms are supported: CRL and OCSP.
418-
419-
[WARNING]
420-
====
421-
None of the revocation features in this section are wired up yet.
422-
`set_revocation_policy()`, CRLs (`add_crl()` / `add_crl_file()`), and OCSP
423-
stapling (`set_ocsp_staple()` / `set_require_ocsp_staple()`) are all
424-
accepted but inert. In particular, `set_require_ocsp_staple(true)` does
425-
*not* fail the handshake when no staple is present, and `soft_fail` /
426-
`hard_fail` do not change verification behavior.
427-
====
416+
invalidated by the issuing CA. Revocation is checked against Certificate
417+
Revocation Lists (CRLs).
428418

429419
=== Revocation Policy
430420

431-
Set the overall revocation checking behavior:
421+
Set the overall revocation checking behavior. CRLs are consulted only when
422+
the policy is not `disabled`:
432423

433424
[source,cpp]
434425
----
435426
// Don't check revocation (default)
436427
ctx.set_revocation_policy( tls_revocation_policy::disabled );
437428
438-
// Check but allow if status is unknown (lenient)
429+
// Accept unknown status, reject a listed (revoked) certificate
439430
ctx.set_revocation_policy( tls_revocation_policy::soft_fail );
440431
441-
// Require successful revocation check (strict)
432+
// Also reject when status can't be determined (strict)
442433
ctx.set_revocation_policy( tls_revocation_policy::hard_fail );
443434
----
444435

@@ -454,33 +445,15 @@ ctx.add_crl_file( "/path/to/issuer.crl" );
454445
// From memory (e.g., fetched via HTTP)
455446
std::string crl_data = fetch_crl_from_url( crl_url );
456447
ctx.add_crl( crl_data );
457-
----
458-
459-
CRLs must be refreshed periodically as they expire.
460-
461-
=== OCSP Stapling
462-
463-
OCSP provides per-certificate revocation status without downloading
464-
large CRLs.
465-
466-
**Server-side** (provide pre-fetched OCSP response):
467-
468-
[source,cpp]
469-
----
470-
// Fetch OCSP response for your certificate
471-
std::string ocsp_response = fetch_ocsp_response();
472448
473-
// Provide to clients during handshake
474-
ctx.set_ocsp_staple( ocsp_response );
449+
ctx.set_revocation_policy( tls_revocation_policy::hard_fail );
475450
----
476451

477-
**Client-side** (require server to staple):
452+
CRLs must be refreshed periodically as they expire.
478453

479-
[source,cpp]
480-
----
481-
// Fail if server doesn't provide OCSP staple
482-
ctx.set_require_ocsp_staple( true );
483-
----
454+
NOTE: On WolfSSL, CRL checking requires a `HAVE_CRL` build; without it,
455+
supplying a CRL or a non-disabled policy fails the handshake with
456+
`std::errc::function_not_supported`.
484457

485458
=== Bootstrap vs. Hardened Connections
486459

@@ -489,10 +462,9 @@ A common pattern uses two context configurations:
489462
[NOTE]
490463
====
491464
Both contexts below verify the peer via `set_default_verify_paths()` and
492-
`set_verify_mode( tls_verify_mode::peer )`. The revocation hardening on the
493-
"hardened" path (`add_crl_file()` / `set_revocation_policy()`) is *not yet
494-
applied* by the backends, so that context currently offers the same
495-
verification as the bootstrap one.
465+
`set_verify_mode( tls_verify_mode::peer )`. The "hardened" path adds CRL
466+
checking (`add_crl_file()` / `set_revocation_policy( hard_fail )`), which
467+
is applied on OpenSSL and on WolfSSL builds with `HAVE_CRL`.
496468
====
497469

498470
[source,cpp]
@@ -576,8 +548,7 @@ scenarios.
576548
NOTE: This example trusts public CAs via `set_default_verify_paths()`. On
577549
WolfSSL builds without `WOLFSSL_SYS_CA_CERTS`, load an explicit CA bundle
578550
instead, for example
579-
`ctx.load_verify_file( "/etc/ssl/certs/ca-certificates.crt" );`. The
580-
`set_min_protocol_version()` call is inert in this release.
551+
`ctx.load_verify_file( "/etc/ssl/certs/ca-certificates.crt" );`.
581552

582553
[source,cpp]
583554
----

‎doc/modules/ROOT/pages/4.guide/4l.tls.adoc‎

Lines changed: 42 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -56,23 +56,12 @@ Trust anchors can also be supplied explicitly with
5656
`add_certificate_authority()`, `load_verify_file()`, or `add_verify_path()`,
5757
and `set_verify_callback()` installs a custom verification hook.
5858

59-
[WARNING]
60-
.Not yet implemented
61-
====
62-
Several other `tls_context` settings are accepted by the API but are *not
63-
yet wired up* by the OpenSSL or WolfSSL backends in this release. They are
64-
stored and silently ignored:
65-
66-
* `set_alpn()` — ALPN is never negotiated.
67-
* `set_min_protocol_version()` / `set_max_protocol_version()` — version
68-
bounds are never applied; the range is the native default.
69-
* `add_crl()` / `add_crl_file()`, `set_ocsp_staple()` /
70-
`set_require_ocsp_staple()`, `set_revocation_policy()` — all
71-
revocation checking is inert.
72-
* `use_pkcs12()` / `use_pkcs12_file()` — return
73-
`std::errc::function_not_supported`.
74-
* `set_ciphersuites()` — applied by OpenSSL only; WolfSSL ignores it.
75-
====
59+
Protocol version bounds (`set_min_protocol_version()` /
60+
`set_max_protocol_version()`), cipher suites (`set_ciphersuites()` and
61+
`set_ciphersuites_tls13()`), ALPN (`set_alpn()`, read back with
62+
`alpn_protocol()`), PKCS#12 credentials (`use_pkcs12()` /
63+
`use_pkcs12_file()`), and CRL-based revocation (`add_crl()` /
64+
`add_crl_file()` with `set_revocation_policy()`) are all supported.
7665

7766
[NOTE]
7867
====
@@ -183,9 +172,10 @@ PKCS#12 (`.pfx` or `.p12`) files bundle certificate, key, and chain together:
183172
ctx.use_pkcs12_file("credentials.pfx", "bundle-password");
184173
----
185174

186-
NOTE: PKCS#12 loading is not yet implemented; `use_pkcs12()` and
187-
`use_pkcs12_file()` return `std::errc::function_not_supported`. Load the
188-
certificate and key separately for now.
175+
NOTE: The bundle is decoded when the first stream is created; a malformed
176+
bundle or wrong passphrase surfaces as a handshake failure. Intermediate
177+
certificates in the bundle are loaded and sent during the handshake on
178+
both backends.
189179

190180
=== Trust Anchors
191181

@@ -238,8 +228,9 @@ ctx.set_min_protocol_version(tls_version::tls_1_3);
238228
ctx.set_max_protocol_version(tls_version::tls_1_2);
239229
----
240230

241-
NOTE: Protocol version bounds are not yet applied by the backends. The
242-
negotiated range is whatever the native default method provides.
231+
NOTE: On WolfSSL the ceiling is applied by selecting a version-specific
232+
method (there is no native set-max call); a window whose minimum exceeds
233+
its maximum yields a context that fails the handshake.
243234

244235
Available versions:
245236

@@ -258,25 +249,36 @@ Available versions:
258249

259250
[source,cpp]
260251
----
261-
// OpenSSL-style cipher string
252+
// TLS 1.2-and-below cipher list (OpenSSL syntax)
262253
ctx.set_ciphersuites("ECDHE+AESGCM:ECDHE+CHACHA20");
254+
// TLS 1.3 cipher suites (configured separately)
255+
ctx.set_ciphersuites_tls13("TLS_AES_256_GCM_SHA384");
263256
----
264257

265-
NOTE: `set_ciphersuites()` is applied by the OpenSSL backend only; the
266-
WolfSSL backend accepts the string but silently ignores it.
258+
NOTE: `set_ciphersuites()` covers TLS 1.2 and below; TLS 1.3 suites use
259+
`set_ciphersuites_tls13()`. Both backends apply them (WolfSSL merges the
260+
two into one list). Suite *names* differ between backends (OpenSSL
261+
`TLS_AES_128_GCM_SHA256` vs WolfSSL `TLS13-AES128-GCM-SHA256`). The OpenSSL
262+
security level is left at the library default; express a lower level
263+
explicitly with a `@SECLEVEL=` token if required.
267264

268265
==== ALPN
269266

270-
Application-Layer Protocol Negotiation selects the application protocol:
267+
Application-Layer Protocol Negotiation selects the application protocol.
268+
Read the negotiated protocol after the handshake with
269+
`stream.alpn_protocol()`:
271270

272271
[source,cpp]
273272
----
274273
// Prefer HTTP/2, fall back to HTTP/1.1
275274
ctx.set_alpn({"h2", "http/1.1"});
275+
// ... after handshake:
276+
std::string_view proto = stream.alpn_protocol(); // e.g. "h2", or empty
276277
----
277278

278-
NOTE: ALPN is not yet wired up; the protocol list is accepted but never
279-
negotiated by either backend.
279+
NOTE: On WolfSSL, ALPN requires a `HAVE_ALPN` build; without it, offering
280+
protocols fails the handshake with `std::errc::function_not_supported`
281+
rather than negotiate nothing silently.
280282

281283
=== Certificate Verification
282284

@@ -361,53 +363,27 @@ Which certificates the callback sees depends on the backend:
361363

362364
=== Revocation Checking
363365

364-
WARNING: None of the revocation features below are wired up yet. CRLs
365-
(`add_crl()` / `add_crl_file()`), OCSP stapling (`set_ocsp_staple()` /
366-
`set_require_ocsp_staple()`), and `set_revocation_policy()` are all accepted
367-
but inert. In particular, `set_require_ocsp_staple(true)` does *not* fail the
368-
handshake when no staple is present, and `soft_fail` / `hard_fail` do not
369-
change verification behavior.
370-
371-
==== Certificate Revocation Lists
366+
Revocation is checked against Certificate Revocation Lists (CRLs). CRLs
367+
are consulted only when a revocation policy other than `disabled` is set.
372368

373369
[source,cpp]
374370
----
375-
// Load CRL from file
371+
// Load a CRL (PEM or DER), from file or memory
376372
ctx.add_crl_file("issuer.crl");
377-
378-
// Load CRL from memory
379373
ctx.add_crl(crl_data);
380-
----
381-
382-
==== OCSP Stapling
383-
384-
For servers, provide a stapled OCSP response:
385-
386-
[source,cpp]
387-
----
388-
ctx.set_ocsp_staple(ocsp_response_data);
389-
----
390-
391-
For clients, require the server to staple:
392-
393-
[source,cpp]
394-
----
395-
ctx.set_require_ocsp_staple(true);
396-
----
397374
398-
==== Revocation Policy
399-
400-
[source,cpp]
375+
// Choose how strict to be
376+
ctx.set_revocation_policy(tls_revocation_policy::hard_fail);
401377
----
402-
// Don't check revocation (default)
403-
ctx.set_revocation_policy(tls_revocation_policy::disabled);
404378

405-
// Check but allow if status unknown
406-
ctx.set_revocation_policy(tls_revocation_policy::soft_fail);
379+
Under `soft_fail`, a certificate whose status cannot be determined
380+
(missing or expired CRL) is accepted, but one that is actually listed as
381+
revoked is rejected. Under `hard_fail`, an undeterminable status is also
382+
rejected. `disabled` (the default) skips revocation entirely.
407383

408-
// Fail if revocation status can't be determined
409-
ctx.set_revocation_policy(tls_revocation_policy::hard_fail);
410-
----
384+
NOTE: On WolfSSL, CRL checking requires a `HAVE_CRL` build; without it,
385+
supplying a CRL or a non-disabled policy fails the handshake with
386+
`std::errc::function_not_supported` rather than skip the check silently.
411387

412388
== TLS Streams
413389

0 commit comments

Comments
 (0)