refactor(core): consolidate the fingerprint-pinning verifier into core::tls
Per plan §2.5: the security-critical rustls fingerprint-pinning ServerCertVerifier was hand-rolled three times — quic/endpoint.rs (PinVerify), pf-client-core library.rs, punktfunk-tray status.rs — drifting copies on a trust boundary. Add one canonical punktfunk_core::tls::PinVerify (+ cert_fingerprint) behind a light `tls` feature (rustls + sha2 only, no QUIC runtime); `quic` now depends on it, and quic::endpoint re-exports cert_fingerprint so that path stays byte-stable (gamestream + pf-client-core reach it there). - core::tls::PinVerify: new(pin) for the HTTP clients, with_observed(pin, slot) for the QUIC TOFU connect. Behavior-identical to all three originals (pin-check + real CertificateVerify signature verification; only hashes the leaf when a pin or observed slot needs it). Two focused unit tests anchor the boundary. - quic/endpoint.rs: drop the private PinVerify, wire client_pinned through tls::PinVerify::with_observed. - pf-client-core library.rs + tray status.rs: use the shared verifier; tray also routes load_pin through core cert_fingerprint and drops its direct sha2 dep, gaining only the light core `tls` feature (still no host dep, no QUIC runtime). Verified on Linux (home-worker-5): clippy 0/0 for core(quic), core(tls), pf-client-core, tray, host(nvenc,vulkan-encode,pyrowave); core 153 lib tests + loopback 7/7 (pinned handshake) + c_abi round-trip green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -257,10 +257,9 @@ fn fetch_summary(agent: &ureq::Agent, url: &str) -> Option<Summary> {
|
||||
/// a port-squatter gains nothing but a fake "streaming" tooltip on an already-compromised box.
|
||||
fn load_pin() -> Option<[u8; 32]> {
|
||||
use rustls::pki_types::pem::PemObject;
|
||||
use sha2::Digest;
|
||||
let pem = std::fs::read(punktfunk_config_dir()?.join("cert.pem")).ok()?;
|
||||
let der = rustls::pki_types::CertificateDer::from_pem_slice(&pem).ok()?;
|
||||
Some(sha2::Sha256::digest(der.as_ref()).into())
|
||||
Some(punktfunk_core::tls::cert_fingerprint(der.as_ref()))
|
||||
}
|
||||
|
||||
/// The host's config dir, mirroring `gamestream::config_dir()` without linking the host crate:
|
||||
@@ -297,7 +296,7 @@ fn agent(pin: Option<[u8; 32]>) -> ureq::Agent {
|
||||
.with_safe_default_protocol_versions()
|
||||
.expect("rustls default protocol versions")
|
||||
.dangerous()
|
||||
.with_custom_certificate_verifier(Arc::new(PinVerify { pin }))
|
||||
.with_custom_certificate_verifier(Arc::new(punktfunk_core::tls::PinVerify::new(pin)))
|
||||
.with_no_client_auth();
|
||||
ureq::AgentBuilder::new()
|
||||
.tls_config(Arc::new(cfg))
|
||||
@@ -306,69 +305,6 @@ fn agent(pin: Option<[u8; 32]>) -> ureq::Agent {
|
||||
.build()
|
||||
}
|
||||
|
||||
/// Trust = the SHA-256 of the host's self-signed leaf (or any cert when un-pinned). Handshake
|
||||
/// signatures are still verified for real — CertificateVerify proves the peer holds the key.
|
||||
#[derive(Debug)]
|
||||
struct PinVerify {
|
||||
pin: Option<[u8; 32]>,
|
||||
}
|
||||
|
||||
impl rustls::client::danger::ServerCertVerifier for PinVerify {
|
||||
fn verify_server_cert(
|
||||
&self,
|
||||
end_entity: &rustls::pki_types::CertificateDer<'_>,
|
||||
_intermediates: &[rustls::pki_types::CertificateDer<'_>],
|
||||
_server_name: &rustls::pki_types::ServerName<'_>,
|
||||
_ocsp: &[u8],
|
||||
_now: rustls::pki_types::UnixTime,
|
||||
) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
|
||||
use sha2::Digest;
|
||||
if let Some(expected) = self.pin {
|
||||
let fp: [u8; 32] = sha2::Sha256::digest(end_entity.as_ref()).into();
|
||||
if fp != expected {
|
||||
return Err(rustls::Error::InvalidCertificate(
|
||||
rustls::CertificateError::ApplicationVerificationFailure,
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(rustls::client::danger::ServerCertVerified::assertion())
|
||||
}
|
||||
|
||||
fn verify_tls12_signature(
|
||||
&self,
|
||||
message: &[u8],
|
||||
cert: &rustls::pki_types::CertificateDer<'_>,
|
||||
dss: &rustls::DigitallySignedStruct,
|
||||
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
|
||||
rustls::crypto::verify_tls12_signature(
|
||||
message,
|
||||
cert,
|
||||
dss,
|
||||
&rustls::crypto::ring::default_provider().signature_verification_algorithms,
|
||||
)
|
||||
}
|
||||
|
||||
fn verify_tls13_signature(
|
||||
&self,
|
||||
message: &[u8],
|
||||
cert: &rustls::pki_types::CertificateDer<'_>,
|
||||
dss: &rustls::DigitallySignedStruct,
|
||||
) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
|
||||
rustls::crypto::verify_tls13_signature(
|
||||
message,
|
||||
cert,
|
||||
dss,
|
||||
&rustls::crypto::ring::default_provider().signature_verification_algorithms,
|
||||
)
|
||||
}
|
||||
|
||||
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
|
||||
rustls::crypto::ring::default_provider()
|
||||
.signature_verification_algorithms
|
||||
.supported_schemes()
|
||||
}
|
||||
}
|
||||
|
||||
// ── Service-manager probe ───────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// The SCM name registered by `punktfunk-host service install` (windows/service.rs SERVICE_NAME).
|
||||
|
||||
Reference in New Issue
Block a user