port-server (src/server.rs, 614 lines): - TlsServerConfig with ADR-004 accessors: for_noq(&self) (borrow + inner-clone + NoqWrap via #[from]), for_tcp_tls(&self) adopted (infallible TlsAcceptor), rustls_config(&self) adopted - acme_handle renamed per ADR-006; the defensive Acme arm returns TlsError::AcmeConfig (unreachable! eliminated) - error sites remapped to typed variants (Rustls/SelfSigned/AcmeConfig) - ACME path verbatim: DirCache + directory + contacts + resolver + acme-tls/1 + spawned event loop (tracing lines ported), returns immediately, detached handle - 14 tests incl. the nine-scheme exact-list pin, ACME spawn/return/ALPN assertion (blackhole URL — no network I/O), no-feature AcmeConfig, for_tcp_tls/rustls_config round-trips port-client (src/credentials.rs + src/client.rs): - ConnectionCredentials/RemoteIdentity wholesale with load-bearing Option-semantics docs (None = public-X.509 state, Some = pin) - verifier selection matrix + client-auth presentation matrix test-pinned at unit level; enable_early_data=true pinned; root-store fallback asserted non-empty - FingerprintPinVerifier: pin match/mismatch + raw-key signature routing (verify_tls13_signature_with_raw_key) asserted - resolvers/verifier made pub for the re-export block Cargo.toml (two required deltas): - noq feature gains aws-lc-rs: lockfile resolves noq-proto 1.3.0 where ServerConfig::with_crypto is #[cfg(any(aws-lc-rs, ring))]; ADR-003's TOML block was written against the 1.2 API. Amendment note recorded in tasks/port-server.md for the review-impl sync - acme = [dep:rustls-acme, dep:futures] — ADR-006 already gates the futures dep on acme; the scaffold omitted it lib.rs: re-export block complete (all eight modules) Verification: cargo test 68, --all-features 75, --features noq 72, clippy -D warnings, fmt --check, doc --no-deps (0 warnings) — green
118 lines
4.6 KiB
Rust
118 lines
4.6 KiB
Rust
//! alktls: shared TLS setup types — server and client `rustls` configs,
|
|
//! cert resolvers, verifiers, ACME state-machine wiring, and the identity /
|
|
//! credential / fingerprint types that drive config construction.
|
|
//!
|
|
//! The crate owns **config construction**: given an identity and an ALPN
|
|
//! list, produce a `rustls::ServerConfig` or `rustls::ClientConfig` and hand
|
|
//! it to whichever transport wrapper the deployment runs (`noq` for QUIC,
|
|
//! `tokio-rustls` for TCP+TLS). It does not dial, accept, dispatch, or
|
|
//! resolve peer identities — those belong to the dial seam, the accept
|
|
//! loop, and the auth layer.
|
|
//!
|
|
//! Core types:
|
|
//!
|
|
//! - [`TlsServerConfig`] — built once per identity + ALPN list, shared
|
|
//! across transports via [`Arc`](std::sync::Arc) (not `Clone`; it holds
|
|
//! the ACME task's `JoinHandle`).
|
|
//! - [`TlsClientConfig`] — built per dial from a [`ConnectionCredentials`] +
|
|
//! ALPN; consumed by its accessors.
|
|
//! - [`TlsError`] — the config-construction error type. Handshake outcomes
|
|
//! and ACME runtime errors are not `TlsError`s.
|
|
//!
|
|
//! Transport-specific accessors are feature-gated: `noq` (`for_noq`),
|
|
//! `tcp` (`for_tcp_tls`), `acme` (the ACME path). `default = []`.
|
|
|
|
pub mod client;
|
|
pub mod credentials;
|
|
pub mod fingerprint;
|
|
pub mod identity;
|
|
pub mod pem;
|
|
pub mod server;
|
|
pub mod signing;
|
|
|
|
pub use fingerprint::{extract_ed25519_raw_key_from_spki, fingerprint_from_cert_der};
|
|
|
|
pub use pem::{load_cert_chain, load_private_key};
|
|
pub use signing::Ed25519SigningKey;
|
|
|
|
pub use identity::{AcmeDirectory, Ed25519SecretKey, TlsIdentity};
|
|
|
|
pub use client::{
|
|
build_client_auth, load_platform_root_cert_store, select_server_verifier,
|
|
FingerprintPinVerifier, NoClientCertResolver, RawKeyClientCertResolver, TlsClientConfig,
|
|
};
|
|
pub use credentials::{ConnectionCredentials, RemoteIdentity};
|
|
|
|
pub use server::{
|
|
build_rustls_server_config, generate_self_signed_cert, AcceptAnyCertVerifier,
|
|
RawKeyCertResolver, SelfSignedCert, TlsServerConfig,
|
|
};
|
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
#[non_exhaustive]
|
|
pub enum TlsError {
|
|
/// Cert or key file read / PEM parse. `rustls_pemfile` funnels its
|
|
/// own error type into `io::Error`, so one `io::Error` source covers
|
|
/// the whole loading path.
|
|
#[error("loading cert/key material: {0}")]
|
|
CertLoad(#[from] std::io::Error),
|
|
|
|
/// Self-signed cert generation (rcgen). Server `SelfSigned` path.
|
|
#[error("generating self-signed cert: {0}")]
|
|
SelfSigned(#[from] rcgen::Error),
|
|
|
|
/// rustls server or client config construction
|
|
/// (`with_safe_default_protocol_versions`, `with_single_cert`,
|
|
/// `CertifiedKey::from_der`, `RootCertStore::add`).
|
|
#[error("building rustls config: {0}")]
|
|
Rustls(#[from] rustls::Error),
|
|
|
|
/// `WebPkiServerVerifier::builder(_with_provider)..build()` — the
|
|
/// unknown-X.509-remote client path. Re-exported by rustls at
|
|
/// `rustls::client` (the `rustls::webpki` module is private at the
|
|
/// pinned 0.23.44; same type, public path).
|
|
#[error("building webpki verifier: {0}")]
|
|
VerifierBuild(#[from] rustls::client::VerifierBuilderError),
|
|
|
|
/// QUIC config wrapping — the one path where `for_noq()` fails
|
|
/// (`NoInitialCipherSuite`, not a `rustls::Error`). noq-gated.
|
|
#[cfg(feature = "noq")]
|
|
#[error("wrapping rustls config for noq: {0}")]
|
|
NoqWrap(#[from] noq_proto::crypto::rustls::NoInitialCipherSuite),
|
|
|
|
/// Config-mismatch errors that are not wrapped third-party
|
|
/// errors: ACME feature not enabled but `Acme` configured
|
|
/// (server), or `Acme` identity used for client auth. A config
|
|
/// error, not a wrapped third-party error.
|
|
#[error("TLS config error: {0}")]
|
|
AcmeConfig(String),
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::TlsError;
|
|
|
|
#[test]
|
|
fn tls_error_matches_the_adr_002_variant_set() {
|
|
let io: TlsError = std::io::Error::other("x").into();
|
|
assert!(matches!(io, TlsError::CertLoad(_)));
|
|
let rcgen: TlsError = rcgen::Error::CouldNotParseCertificate.into();
|
|
assert!(matches!(rcgen, TlsError::SelfSigned(_)));
|
|
let rustls: TlsError = rustls::Error::General("x".into()).into();
|
|
assert!(matches!(rustls, TlsError::Rustls(_)));
|
|
let builder: TlsError = rustls::client::VerifierBuilderError::NoRootAnchors.into();
|
|
assert!(matches!(builder, TlsError::VerifierBuild(_)));
|
|
let acme = TlsError::AcmeConfig("acme feature not enabled".into());
|
|
assert!(acme.to_string().contains("acme feature not enabled"));
|
|
}
|
|
|
|
#[cfg(feature = "noq")]
|
|
#[test]
|
|
fn tls_error_noq_wrap_variant_exists() {
|
|
let _ = matches!(
|
|
Option::<TlsError>::None,
|
|
Some(TlsError::NoqWrap(_)) | Some(_)
|
|
);
|
|
}
|
|
}
|