review-001 decomposition: 6 remediation tasks from verified findings

Verify every review-001 finding before decomposition:
- S-1 re-confirmed by fresh executable probe (X.509 + raw-key
  impersonation both complete the handshake with the victim's
  fingerprint extracted server-side)
- U-2/U-3 uncovered-line inventory re-derived from cargo llvm-cov
  (--all-features); matches Part C exactly
- rcgen 1975/4096 defaults, rustls cert-type negotiation arm,
  packaging list, doc texts: all verified against sources

Tasks (all verified, none speculative):
- fix-accept-any-cert-verifier-posture (S-1 + N-1 + OQ-TLS-09 + probe)
- handshake-tests (U-3 suites 1-3; suite 4 lives in the S-1 task)
- coverage-cheap-closes (U-2's seven groups)
- acme-event-loop-test (U-1; depends on coverage-cheap-closes for the
  tracing-capture pattern)
- config-validation-and-trivia (C-2 ALPN dedup, C-3 empty-domains,
  N-6 excludes, N-7 https doc line)
- docs-pin-c1-c4-n3-n4 (C-1, C-4, N-3 decision note, N-4 negotiation
  note)

Graph: acme-event-loop-test is generation 2; the rest run in
generation 1. taskgraph validate: 14 tasks, no cycles.

Verification: cargo test (default + --all-features) green before and
after; probe file deleted after its run.
This commit is contained in:
2026-09-10 21:16:13 +00:00
parent bb0d060135
commit d7db6b17a6
6 changed files with 580 additions and 0 deletions
+101
View File
@@ -0,0 +1,101 @@
---
id: docs-pin-c1-c4-n3-n4
name: Docs + pin batch — self-signed validity, fingerprint pin format-exactness, Clone-under-no-acme, cert-type negotiation note (C-1, C-4, N-3, N-4)
status: pending
depends_on: []
scope: narrow
risk: low
impact: component
level: implementation
tags: [docs, review-001, c1, c4, n3, n4]
---
## Description
Four doc/doc+pin findings that close as rustdoc (plus one decided
question):
1. **C-1**`generate_self_signed_cert()` produces a cert valid
1975→4096 (rcgen's `CertificateParams::default()` — verified
against rcgen 0.13.2 source, certificate.rs:94-96) and has no SANs.
"Dev cert" silently meaning "never expires" is a documented-behavior
gap. One doc line on `generate_self_signed_cert` (and
`SelfSignedCert`): the generated cert never expires in practice,
carries no SANs, and pins against CA verification by design; if a
tighter validity is ever wanted, `not_before`/`not_after` are
additive params.
2. **C-4**`RemoteIdentity::fingerprint` accepts any string and the
pin comparison is exact-string (src/client.rs:272): uppercase-hex
pins, `sha256:` lowercase prefix, and cross-prefix pins
(`SHA256:`-of-an-ed25519-remote) construct fine and reject at
handshake — fail-closed, never a downgrade, but a config-author
trap. Doc line on `RemoteIdentity::fingerprint` (and/or
`FingerprintPinVerifier`): pins must be produced by
`fingerprint_from_cert_der` — case- and format-exact
(`ed25519:<lowercase hex>` / `SHA256:<lowercase hex>`); malformed
pins fail closed at handshake.
3. **N-3**`TlsServerConfig` is not `Clone` even under
`default = []` where the struct has no `JoinHandle` field. The
all-features posture is what the API freeze pins (conditional
`Clone` is its own trap: an API difference between feature
configurations). Decision: keep non-Clone for v1; record the
reasoning in the type's doc (one sentence) and close the question
— revisit only with a concrete consumer demanding it.
4. **N-4** — the cert-type negotiation interaction needs an explicit
doc note on `AcceptAnyCertVerifier`: `requires_raw_public_keys()`
stays `false` (both cert types accepted), and — the subtle part,
verified against rustls 0.23.44 `validate_client_cert_type_extension`
in the decomposition session — **a client offering ONLY
`RawPublicKey` fails the handshake** with
`IncorrectCertificateTypeExtension`; it must offer both
`[X509, RawPublicKey]` (or the verifier would have to set
`requires_raw_public_keys() == true`). The extracted alknet client
resolver offers both types, which is why production works. Without
the note, the next reader may "fix" the verifier to `true` and
break X.509 clients, or mis-diagnose the raw-only-client failure as
a bug.
## Work
1. C-1 doc lines (`generate_self_signed_cert` + `SelfSignedCert`).
2. C-4 doc lines (`RemoteIdentity::fingerprint`,
`FingerprintPinVerifier` — format-exactness + fail-closed).
3. N-3: the one-sentence decision note on `TlsServerConfig`'s doc;
close N-3 in the review's finding list (no code change).
4. N-4: the negotiation note on `AcceptAnyCertVerifier`'s doc. If
desired, add a pinned unit test asserting
`requires_raw_public_keys() == false` stays default (it's a trait
default — the test just documents the choice).
5. Cross-check the review's finding numbering so each doc change
cites its finding.
## Verification
- [ ] All four doc notes exist and render (`cargo doc --no-deps`
warning-free)
- [ ] The N-3 decision is recorded (doc + this task's Summary)
- [ ] No code-behavior change beyond an optional unit pin
- [ ] `cargo test`, clippy, fmt, doc green
## Acceptance Criteria
- [ ] A config author reading the rustdoc cannot mis-case a pin, pin
the wrong prefix, or expect a never-expiring dev cert to expire
- [ ] The raw-only-client negotiation trap is documented before a
consumer hits it
## References
- docs/reviews/001-implementation-review.md §C-1, §C-4, §N-3, §N-4
- src/server.rs:226-256, src/client.rs:225-236, src/credentials.rs:35-39,
src/identity.rs (SelfSigned doc)
- rustls 0.23.44 `server/hs.rs` `validate_client_cert_type_extension`
(the negotiation table N-4 documents)
## Notes
> Agent fills this during implementation.
## Summary
> Agent fills this on completion.