Skip to content

Commit eb4bf44

Browse files
committed
Merge TASK-194: Complete public TLS profiles, rotation and ACME selection
2 parents 329b110 + 9efe534 commit eb4bf44

33 files changed

Lines changed: 4676 additions & 86 deletions

‎Makefile.am‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -559,3 +559,5 @@ EXTRA_DIST += docs/v2-to-v3-migration.md docs/v3-api.md docs/task-183-migration-
559559
EXTRA_DIST += scripts/audit-v3-native-linkage.sh scripts/test-v3-cutover.py scripts/check-v3-runtime-coexistence.py docs/task-184-cutover-evidence.md
560560

561561
EXTRA_DIST += scripts/check-v3-public-tls.py test/installed/v3_tls_consumer.cpp
562+
563+
EXTRA_DIST += scripts/lib/v3_public_tls_profiles.py test/installed/v3_tls_profiles.cpp test/installed/v3_tls_client.cpp

‎docs/external-psk-lookup-contract.md‎

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,10 @@ Historical seams characterized at TASK-134's base `d1349b59`, before TASK-135:
4242

4343
## Callback shape
4444

45-
The installed provider-neutral types are in `detail/tls_psk.hpp` (internal,
46-
excluded from installed public headers). The callback shape is:
45+
The original private types remain in `detail/tls_psk.hpp`. TASK-194 exposes the
46+
provider-neutral public equivalents in `server/tls_credentials.hpp`: `tls_psk_version`,
47+
`tls_psk_context`, `tls_psk_result`, `tls_psk_lookup` and move-only `tls_psk_key`.
48+
The private callback shape is:
4749

4850
```cpp
4951
enum class psk_tls_version { tls12, tls13 };
@@ -208,7 +210,7 @@ late-result wiping and retirement against this contract. Local acceptance is rec
208210
`tls_psk_runtime_options` defaults to two handshake workers and two lookup
209211
workers, with sixteen additional outstanding slots on each lane and a five
210212
second handshake timeout. Validation permits 1–64 workers per lane, 0–65536
211-
additional slots and a timeout greater than zero and at most ten minutes.
213+
additional slots and a timeout greater than zero and at most 24 hours.
212214
Admission counts running and queued jobs together (`workers + queue`); a reserved
213215
worker slot may still be queued while its worker wakes. Running callbacks never
214216
exceed the worker count. Queued and running jobs share one absolute deadline.
@@ -258,3 +260,28 @@ authentication, binary TLS 1.3 identities, SNI/mixed profiles, replacement,
258260
0-RTT rejection, admission, timeouts, cancellation/destruction, late-key wiping,
259261
local TLS-on/off gates and fully instrumented local sanitizer runs. BSD, Windows
260262
and other nonlocal checks remain assigned to CI and the v3 PR.
263+
264+
## Public runtime ownership
265+
266+
`server::tls_psk_runtime::create(options, output)` reports typed configuration or
267+
availability failures and preserves output on failure. Public options configure
268+
only the callback lane: 1–64 workers and 0–65536 additional queued slots. The
269+
native server separately owns bounded provider steps. Publication retains the
270+
public runtime and copied callable; repeated generations never replenish capacity
271+
occupied by an unreturned callback. Caller handles and PEM buffers may die while
272+
listeners/generations retain their owners. Callback references last one invocation.
273+
274+
`request_stop()` requests cancellation without joining; call it before
275+
`wait_retired(deadline)`. Incomplete retirement returns `timeout`; self-wait returns
276+
`would_deadlock`. Runtime destruction never waits for arbitrary callback code.
277+
Owned captured state lasts through actual callback retirement, including late
278+
results after connection timeout/cancellation. Native server stop never implicitly
279+
stops a shared application lookup runtime. Stop handlers must remain nonblocking.
280+
The private runtime retains its existing scheduling implementation and defaults;
281+
the current private timeout ceiling is 24 hours, matching `server::max_timeout`.
282+
283+
Both public TLS builds expose the same declarations and secure key operations.
284+
TLS-off runtime creation and credential/ACME operations return `not_supported`.
285+
The installed public matrix adds real TCP independent TLS1.2/1.3 PSK, binary
286+
identity, negative authentication, early-data, blocked-callback and retirement
287+
witnesses; private memory-BIO/provider suites remain supporting evidence.

‎docs/native-tls-build.md‎

Lines changed: 74 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ cross builds must run the smoke on the target in CI.
2626

2727
`<httpserver/features.hpp>` exposes the same compiled `query_features()` declaration
2828
in both builds, without backend headers or configuration macros. Availability of
29-
the provider is distinct from availability of TCP TLS, HTTP/2, and HTTP/3. TCP TLS is available in TLS-on builds for certificate-authenticated HTTP/1.0
30-
and HTTP/1.1 in both managed and external modes. HTTP/2 and HTTP/3 remain
29+
the provider is distinct from availability of TCP TLS, HTTP/2, and HTTP/3. TCP TLS is available in TLS-on builds for certificate, initial mTLS and eligible
30+
external-PSK HTTP/1.0 and HTTP/1.1 in both managed and external modes. HTTP/2 and HTTP/3 remain
3131
unavailable until their owning tasks implement public listeners. `listen()`
3232
rejects unsupported protocol/profile requests before binding;
3333
`server_options::validate()` remains semantic.
@@ -58,7 +58,7 @@ checks configure/build provenance, installed metadata, effective public headers,
5858
recursive dependency graphs and all static-archive members, including unused
5959
members. Provider build proof remains distinct from encrypted listener or QUIC
6060
interoperability proof. Nonlocal BSD, macOS and Windows execution belongs to CI
61-
and the v3 PR; public H2/H3 listeners and expanded TLS profiles remain later-task work.
61+
and the v3 PR; public H2/H3 listeners remain later-task work.
6262

6363
Run the clean installed-consumer matrix from a bootstrapped source checkout:
6464

@@ -163,12 +163,10 @@ requests before binding. Every endpoint binds before admission activates; failur
163163
on a later bind synchronously releases earlier endpoints and reports no bound
164164
ports. A bind failure is terminal for that server instance.
165165
166-
TASK-193 supports public certificate/default/SNI configuration and replacement
167-
lifetime safety. Public mTLS, external PSK and ACME operations remain TASK-194
168-
work; native requests for unsupported profiles reject honestly. Private profile
169-
suites continue to test their existing support. The rotation example now uses the
170-
public store and demonstrates typed publication outcomes; expanded installed
171-
profile/rotation examples belong to TASK-194.
166+
TASK-193 supplies public certificate/default/SNI configuration and generation
167+
lifetime safety. TASK-194 extends the same store to initial mTLS, HTTP/1 external
168+
PSK and ACME, and exercises live listener rotation through installed packages.
169+
Private profile suites remain supporting protocol evidence.
172170
173171
Run the installed certificate capability matrix after staging a package:
174172
@@ -186,3 +184,70 @@ Unknown/absent SNI checks verify the chain and compare the selected certificate
186184
with its independently hostname-verified named certificate. TLS-off runs the same
187185
configuration code and requires typed unavailability. Nonlocal platform checks
188186
remain CI/v3-PR owned.
187+
188+
## Public initial client authentication, external PSK and ACME
189+
190+
`server/tls_credentials.hpp` supplies provider-neutral host trust, authentication,
191+
owned keys and a bounded callback runtime. Set `tls_host_credentials::profile`
192+
and the listener's `options.tls().profile` consistently. For mTLS, set
193+
`trust_roots_pem` to the client CA roots. `mutual_tls` defaults to `require`;
194+
explicit `client_auth.mode = tls_client_certificate_mode::request` verifies any
195+
presented chain while allowing anonymous peers. `require` refuses absent or
196+
untrusted client chains. Set the same resolved mode on the listener. Authentication
197+
is initial-handshake only. Exchanges and request hooks own immutable verified
198+
leaf identity, fingerprint and validity metadata.
199+
200+
A bound store rejects publication that contradicts any active listener's profile,
201+
resolved mode or enabled HTTP/1 policy. Bind registration and publication share
202+
one private writer transaction; a concurrent writer cannot downgrade authentication
203+
between validation and admission. Compatible listeners hold independent leases;
204+
stopping admission releases that listener's lease. Already accepted sessions keep
205+
their pinned generation. Invalid material and policy leave the active generation
206+
unchanged. Mixed profiles in one store cannot attach to a listener configured for
207+
one contradictory profile.
208+
209+
For HTTP/1 external PSK, create `tls_psk_runtime` with finite `lookup_workers`
210+
(1–64) and `lookup_queue` (0–65536), supply `tls_psk_config::lookup` and its shared
211+
runtime, and set the host/listener profile to `external_psk`. Certificate/key/trust
212+
material must be empty for PSK. Return `tls_psk_result` with an accepted status and
213+
move-only `tls_psk_key` built from owned bytes. Identity/context references last
214+
one invocation; retain copies when needed. TLS1.3 identities are binary; TLS1.2
215+
exposes textual NUL-terminated identities. The context owns canonical selected
216+
host, generation identity, absolute monotonic deadline and cancellation token.
217+
Callbacks can run concurrently and must synchronize mutable captured state.
218+
219+
The lookup runtime is distinct from native server-owned provider workers. It
220+
counts active and queued callbacks together; ignored cancellation retains a slot
221+
until callback return, including across rotation. Late keys are wiped and never
222+
route requests. `request_stop()` is nonblocking. Call it before
223+
`wait_retired(absolute_deadline)` to observe actual callback retirement: timeout
224+
means application work remains; waiting from that runtime's callback returns
225+
`would_deadlock`. Destruction requests stop without joining arbitrary application
226+
code. Captured resources must be owned through retirement. A native handler's
227+
`request_stop()` or `begin_drain()` does not stop a shared application runtime;
228+
one server stopping cannot disable another server using that runtime.
229+
230+
Normal certificate sessions have a host/context namespace. Same-generation
231+
resumption may reuse authentication; replacements create fresh contexts, so an
232+
old session cannot bypass the replacement or cross host/store identity. External
233+
PSK authentication differs from ticket resumption: PSK profiles and ACME disable
234+
tickets/session caching. Every ACME handshake sends its challenge certificate.
235+
Application 0-RTT remains disabled; early requests never enter routes.
236+
237+
`tls_credentials::publish_acme(tls_acme_challenge)` validates a supplied challenge
238+
certificate/key, canonical DNS host, 32-byte authorization digest and future
239+
absolute system-clock expiry no later than certificate expiry. The certificate
240+
must have exactly one matching DNS SAN and one critical ACME authorization digest
241+
extension. `remove_acme(host)` is canonical and idempotent. Publication validation
242+
is independent of listener port. Selection requires matching exact canonical SNI,
243+
sole `acme-tls/1`, TCP, and the socket's actual local port443. Other ALPN offers and
244+
ordinary HTTP use normal credentials; challenge sessions cannot route HTTP. This
245+
API performs no certificate issuance or challenge-client orchestration.
246+
247+
TLS-off installs identical declarations and secure key operations, with typed
248+
`not_supported` for credential/runtime/ACME availability. Public H2/H3 listeners
249+
remain later-task work; external PSK remains HTTP/1-only. The installed rotation
250+
example takes `certificate-directory managed|external port`, opens a real public
251+
listener and accepts `replace`, `invalid` and `stop` on stdin. Its independent
252+
installed witness holds a usable connection across rotation and validates the
253+
new certificate plus rejected-publication preservation.

‎docs/v2-to-v3-migration.md‎

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -160,11 +160,14 @@ Native TLS selects optional OpenSSL at build time with `--enable-v3-tls`; the
160160
current build gate requires stable OpenSSL **3.5.x patch 9 or later** and its QUIC
161161
TLS callbacks. Public certificate values go through `server::tls_credentials`;
162162
`listener_options::credentials` associates the store. `server_options::tls()`
163-
selects provider/profile policy. Public mTLS/PSK/ACME scenarios remain TASK-194
164-
work; their native listener requests reject honestly. Private initial-handshake
165-
client-auth/PSK suites retain existing coverage; post-handshake auth is unavailable.
166-
167-
The [public publication demonstration](../test/examples/v3_tls_rotation.cpp)
163+
selects provider/profile policy. Public host values accept initial mTLS trust and
164+
request/require mode, or bounded external PSK for HTTP/1. The application owns the
165+
shared lookup runtime through actual callback retirement; server stop requests
166+
cancellation without joining application code. Public ACME publication/removal
167+
uses application-supplied material and exact SNI/sole `acme-tls/1` on TCP443.
168+
Post-handshake auth remains unavailable; see the [public TLS lifetime contract](native-tls-build.md).
169+
170+
The [public listener rotation example](../test/examples/v3_tls_rotation.cpp)
168171
uses `server::tls_credentials::replace()`: successful publication replaces the
169172
active generation, accepted sessions retain their contexts, and failed
170173
replacement leaves the active publication unchanged. It uses only installed
@@ -180,6 +183,8 @@ TASK-193 adds public certificate TCP TLS through
180183
[`server::tls_credentials`](native-tls-build.md#public-certificate-credentials-and-native-tcp-tls).
181184
Associate a shared store with each TLS listener and publish PEM values with
182185
`replace`; accepted sessions retain immutable selections during replacement.
183-
Managed and external loops support the same HTTP/1 routes. Public mTLS/PSK/ACME
184-
profiles and H2/H3 listeners remain subsequent repair tasks; unsupported requests
185-
still return typed pre-bind outcomes.
186+
Managed and external loops support the same HTTP/1 routes. TASK-194 extends
187+
installed witnesses to mTLS/PSK/ACME and live listener rotation. H2/H3 public
188+
listeners remain subsequent repair tasks; unsupported combinations retain typed
189+
pre-bind outcomes. TLS-off exposes the same declarations and typed unavailable
190+
credential/runtime operations.

‎docs/v3-api.md‎

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ There is no v2 source/ABI compatibility shim.
1010
\ref httpserver::server::server_options defines listener endpoints, protocol
1111
selection, budgets, timeouts, worker concurrency and loop ownership.
1212
\ref httpserver::server::native_server validates before binding and owns shutdown.
13-
Plaintext HTTP/1.0 and HTTP/1.1 listeners are available. Public TLS/H2/H3 listeners
14-
return not_supported before binding, even when semantic validation succeeds.
13+
Plaintext HTTP/1.0 and HTTP/1.1 listeners are available. TLS-on builds also serve
14+
public HTTP/1 certificate, initial mTLS and eligible external-PSK listeners.
15+
Public H2/H3 listeners still return not_supported before binding.
1516
Provider availability from \ref httpserver::query_features is a separate fact.
1617

1718
\include v3_protocol_options.cpp
@@ -67,8 +68,18 @@ counted work returns would_deadlock. \ref httpserver::http::outcome and
6768
\ref httpserver::auth::basic_auth_policy, \ref httpserver::auth::digest_auth_policy,
6869
and the bounded forms APIs replace the corresponding v2 conveniences.
6970

70-
Certificate rotation has no public installation API. The separately labeled
71-
repository-internal demonstration at test/examples/v3_tls_rotation.cpp uses an
72-
uninstalled private header. It is not included as a public API example here.
71+
\ref httpserver::server::tls_credentials copies share an immutable-generation
72+
store attached to each TLS listener. replace validates before atomic publication;
73+
selected connections retain their generation, and incompatible bound-policy
74+
changes reject. Public host values carry mTLS trust/request/require policy or
75+
external-PSK lookup configuration. The application owns the bounded shared lookup
76+
runtime through actual callback retirement; native stop never joins application
77+
lookups. Public ACME publish/remove accepts application-supplied material and
78+
selects only exact SNI plus sole acme-tls/1 on authentic TCP443.
79+
80+
The installed public listener example at test/examples/v3_tls_rotation.cpp serves
81+
requests across valid/rejected live replacement. Read the [TLS usage and lifetime
82+
contract](https://github.com/etr/libhttpserver/blob/v3/docs/native-tls-build.md).
83+
TLS-off retains the same declarations and typed unavailable operations.
7384
The [TASK-179 audit](https://github.com/etr/libhttpserver/blob/v3/docs/task-179-behavior-parity.md) distinguishes semantic,
7485
frame, provider and private TCP/TLS/QUIC observations.

0 commit comments

Comments
 (0)