use std::sync::Arc;
use rustls::client::danger::{HandshakeSignatureValid, ServerCertVerified, ServerCertVerifier};
use rustls::pki_types::{CertificateDer, ServerName, UnixTime};
use rustls::{DigitallySignedStruct, SignatureScheme};
use sha2::{Digest, Sha256};
use super::types::RuntimeError;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SeedTls {
WebPki,
PinnedSha256([u8; 32]),
PinnedSpki([u8; 32]),
}
impl SeedTls {
pub fn pinned(fingerprint: &str) -> Result<Self, RuntimeError> {
let bad = || {
RuntimeError::InvalidConfig(
"seed certificate pin must be sha256:<64 hex digits>".into(),
)
};
let hex: String = fingerprint
.strip_prefix("sha256:")
.ok_or_else(bad)?
.chars()
.filter(|c| *c != ':')
.collect();
if hex.len() != 64 || !hex.bytes().all(|b| b.is_ascii_hexdigit()) {
return Err(bad());
}
let mut out = [0u8; 32];
for (i, byte) in out.iter_mut().enumerate() {
*byte = u8::from_str_radix(&hex[2 * i..2 * i + 2], 16).map_err(|_| bad())?;
}
Ok(Self::PinnedSha256(out))
}
pub fn pinned_spki(fingerprint: &str) -> Result<Self, RuntimeError> {
let rest = fingerprint.strip_prefix("spki-sha256:").ok_or_else(|| {
RuntimeError::InvalidConfig("seed key pin must be spki-sha256:<64 hex digits>".into())
})?;
match Self::pinned(&format!("sha256:{rest}"))? {
Self::PinnedSha256(h) => Ok(Self::PinnedSpki(h)),
other => Ok(other),
}
}
pub fn spki_fingerprint(der: &[u8]) -> Option<String> {
let d = Sha256::digest(spki_der(der)?);
let hex: String = d.iter().map(|b| format!("{b:02x}")).collect();
Some(format!("spki-sha256:{hex}"))
}
pub fn fingerprint(der: &[u8]) -> String {
let d = Sha256::digest(der);
let hex: String = d.iter().map(|b| format!("{b:02x}")).collect();
format!("sha256:{hex}")
}
pub(crate) fn client_config(&self) -> Option<rustls::ClientConfig> {
let pin = match self {
Self::PinnedSha256(p) => Pin::Cert(*p),
Self::PinnedSpki(p) => Pin::Spki(*p),
Self::WebPki => return None,
};
let provider = Arc::new(rustls::crypto::ring::default_provider());
let verifier = Arc::new(PinnedCert {
pin,
provider: provider.clone(),
});
let mut cfg = rustls::ClientConfig::builder_with_provider(provider)
.with_safe_default_protocol_versions()
.ok()?
.dangerous()
.with_custom_certificate_verifier(verifier)
.with_no_client_auth();
cfg.alpn_protocols = vec![b"http/1.1".to_vec()];
Some(cfg)
}
}
#[derive(Debug, Clone, Copy)]
enum Pin {
Cert([u8; 32]),
Spki([u8; 32]),
}
fn der_take(b: &[u8], tag: u8) -> Option<(&[u8], &[u8])> {
let (&t, b) = b.split_first()?;
if t != tag {
return None;
}
let (&l0, b) = b.split_first()?;
let (len, b) = if l0 < 0x80 {
(l0 as usize, b)
} else {
let n = (l0 & 0x7f) as usize;
if n == 0 || n > 4 || b.len() < n {
return None;
}
let len = b[..n].iter().fold(0usize, |a, x| (a << 8) | *x as usize);
(len, &b[n..])
};
(b.len() >= len).then(|| b.split_at(len))
}
fn spki_der(cert: &[u8]) -> Option<&[u8]> {
let (cert, _) = der_take(cert, 0x30)?;
let (tbs, _) = der_take(cert, 0x30)?;
let mut rest = tbs;
if rest.first() == Some(&0xa0) {
rest = der_take(rest, 0xa0)?.1; }
let (_, r) = der_take(rest, 0x02)?;
rest = r;
for _ in 0..4 {
rest = der_take(rest, 0x30)?.1;
}
let (inner, _) = der_take(rest, 0x30)?;
let head = rest.len() - der_take(rest, 0x30)?.1.len() - inner.len();
Some(&rest[..head + inner.len()])
}
#[derive(Debug)]
struct PinnedCert {
pin: Pin,
provider: Arc<rustls::crypto::CryptoProvider>,
}
impl ServerCertVerifier for PinnedCert {
fn verify_server_cert(
&self,
end_entity: &CertificateDer<'_>,
_intermediates: &[CertificateDer<'_>],
_server_name: &ServerName<'_>,
_ocsp_response: &[u8],
_now: UnixTime,
) -> Result<ServerCertVerified, rustls::Error> {
let ok = match self.pin {
Pin::Cert(p) => <[u8; 32]>::from(Sha256::digest(end_entity.as_ref())) == p,
Pin::Spki(p) => spki_der(end_entity.as_ref())
.is_some_and(|k| <[u8; 32]>::from(Sha256::digest(k)) == p),
};
if ok {
Ok(ServerCertVerified::assertion())
} else {
Err(rustls::Error::General(
"seed certificate does not match the pinned fingerprint".into(),
))
}
}
fn verify_tls12_signature(
&self,
message: &[u8],
cert: &CertificateDer<'_>,
dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
rustls::crypto::verify_tls12_signature(
message,
cert,
dss,
&self.provider.signature_verification_algorithms,
)
}
fn verify_tls13_signature(
&self,
message: &[u8],
cert: &CertificateDer<'_>,
dss: &DigitallySignedStruct,
) -> Result<HandshakeSignatureValid, rustls::Error> {
rustls::crypto::verify_tls13_signature(
message,
cert,
dss,
&self.provider.signature_verification_algorithms,
)
}
fn supported_verify_schemes(&self) -> Vec<SignatureScheme> {
self.provider
.signature_verification_algorithms
.supported_schemes()
}
}