Files
reverse-proxy/tasks/tls/manual-tls.md

78 lines
3.2 KiB
Markdown

---
id: tls/manual-tls
name: Implement manual TLS certificate loading and ServerConfig construction
status: completed
depends_on: [setup/project-init]
scope: narrow
risk: low
impact: component
level: implementation
---
## Description
Implement the manual TLS mode where certificates are loaded from PEM files on disk at startup. This covers building a `rustls::ServerConfig` with manually loaded certificate chains and private keys.
### Manual Mode
For each listener in manual mode:
1. Load `cert_path` PEM file using `rustls_pemfile``Vec<CertificateDer>`
2. Load `key_path` PEM file using `rustls_pemfile``PrivateKeyDer`
3. Build `ServerConfig` with `with_no_client_auth()` and the loaded cert/key
4. Configure cipher suites (restricted set per ADR-012)
5. Configure protocol versions (TLS 1.2 and 1.3 only)
### Cipher Suite Configuration
Per ADR-012, restrict to nginx-equivalent cipher suites:
**TLS 1.2 (explicitly selected):**
- `TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256`
- `TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256`
- `TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384`
- `TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384`
**TLS 1.3 (all default suites):**
- `TLS_AES_128_GCM_SHA256`
- `TLS_AES_256_GCM_SHA384`
- `TLS_CHACHA20_POLY1305_SHA256`
This is configured via a custom `CryptoProvider` with a `cipher_suite` list passed to `ServerConfig::builder_with_provider()`.
### Single-Domain Manual Mode
For a listener with one domain, build a simple `ServerConfig` with the single certificate chain and private key. No SNI resolver needed.
### Multi-Domain Manual Mode (on shared-IP listener)
For a listener with multiple sites on a shared IP, implement a custom `ResolvesServerCert` that maps SNI hostnames to `CertifiedKey` entries loaded from disk. If no certificate matches the SNI hostname, the handshake fails — we don't serve a default certificate for unknown domains.
Note: multi-domain manual mode with different certs per domain is a rare edge case. The initial implementation should handle the common case (single cert per manual listener). The SNI resolver can be a follow-up if needed.
## Acceptance Criteria
- [ ] `rustls::ServerConfig` construction for manual TLS mode
- [ ] PEM file loading via `rustls_pemfile` for certificates and private keys
- [ ] Cipher suite restriction per ADR-012 (4 TLS 1.2 suites + all TLS 1.3)
- [ ] Protocol version restriction to TLS 1.2 and 1.3
- [ `aws_lc_rs` crypto provider used
- [ ] `with_no_client_auth()` for no client certificate requirement
- [ ] Custom `ResolvesServerCert` for SNI-based cert selection in multi-domain manual mode
- [ ] Unknown SNI hostname → handshake fails (no default cert)
- [ ] Unit tests for ServerConfig construction with test certs (using `rcgen`)
- [ ] Unit tests for cipher suite and protocol version configuration
## References
- docs/architecture/tls.md — manual mode, cipher suites, SNI
- docs/architecture/decisions/004-rustls-acme.md — manual mode is fallback
- docs/architecture/decisions/005-tokio-rustls-direct.md — direct tokio-rustls usage
- docs/architecture/decisions/012-cipher-suite-restriction.md — cipher suite selection
## Notes
> This task focuses on ServerConfig construction. The actual TCP listener + TLS acceptor wiring is in tls/tls-listener-setup.
## Summary
> To be filled on completion