use ed25519_dalek::{Signature, VerifyingKey};
const MIN_KNOWN_TIME: u64 = 1_782_864_000;
#[derive(Clone, Debug)]
pub struct DnscryptCert {
pub resolver_pk: [u8; 32],
pub client_magic: [u8; 8],
pub serial: u32,
pub ts_start: u32,
pub ts_end: u32,
}
#[must_use]
pub fn verify_cert(cert: &[u8], provider_pk: &[u8; 32]) -> Option<DnscryptCert> {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
verify_cert_at(cert, provider_pk, now)
}
fn verify_cert_at(cert: &[u8], provider_pk: &[u8; 32], now: u64) -> Option<DnscryptCert> {
if cert.len() < 124 {
return None;
}
if &cert[0..4] != b"DNSC" {
return None;
}
if cert[4..6] != [0x00, 0x02] {
return None;
}
let pk = VerifyingKey::from_bytes(provider_pk).ok()?;
let signature_bytes: [u8; 64] = cert[8..72].try_into().ok()?;
let signature = Signature::from_bytes(&signature_bytes);
pk.verify_strict(&cert[72..], &signature).ok()?;
let mut resolver_pk = [0u8; 32];
resolver_pk.copy_from_slice(&cert[72..104]);
let mut client_magic = [0u8; 8];
client_magic.copy_from_slice(&cert[104..112]);
let serial = u32::from_be_bytes(cert[112..116].try_into().ok()?);
let ts_start = u32::from_be_bytes(cert[116..120].try_into().ok()?);
let ts_end = u32::from_be_bytes(cert[120..124].try_into().ok()?);
if now < MIN_KNOWN_TIME {
return None; }
if now < u64::from(ts_start) || now > u64::from(ts_end) {
return None;
}
Some(DnscryptCert {
resolver_pk,
client_magic,
serial,
ts_start,
ts_end,
})
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used, clippy::cast_possible_truncation)]
mod tests {
use super::*;
use ed25519_dalek::{Signer, SigningKey};
fn generate_test_cert(
modify_signature: bool,
ts_start: u32,
ts_end: u32,
magic: [u8; 4],
es_version: [u8; 2],
) -> (Vec<u8>, [u8; 32]) {
let mut secret = [0u8; 32];
getrandom::fill(&mut secret).expect("getrandom failed");
let signing_key = SigningKey::from_bytes(&secret);
let provider_pk = signing_key.verifying_key().to_bytes();
let mut cert = vec![0u8; 124];
cert[0..4].copy_from_slice(&magic);
cert[4..6].copy_from_slice(&es_version);
cert[72..104].copy_from_slice(&[1u8; 32]);
cert[104..112].copy_from_slice(&[2u8; 8]);
cert[112..116].copy_from_slice(&1u32.to_be_bytes());
cert[116..120].copy_from_slice(&ts_start.to_be_bytes());
cert[120..124].copy_from_slice(&ts_end.to_be_bytes());
let mut signature = signing_key.sign(&cert[72..]).to_bytes();
if modify_signature {
signature[0] ^= 0xFF;
}
cert[8..72].copy_from_slice(&signature);
(cert, provider_pk)
}
#[test]
fn test_verify_cert_valid() {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as u32;
let (cert, pk) = generate_test_cert(false, now - 1000, now + 1000, *b"DNSC", [0x00, 0x02]);
let parsed = verify_cert(&cert, &pk);
assert!(parsed.is_some());
let parsed = parsed.unwrap();
assert_eq!(parsed.client_magic, [2u8; 8]);
assert_eq!(parsed.serial, 1);
}
#[test]
fn test_verify_cert_invalid_signature() {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as u32;
let (cert, pk) = generate_test_cert(true, now - 1000, now + 1000, *b"DNSC", [0x00, 0x02]);
let parsed = verify_cert(&cert, &pk);
assert!(parsed.is_none());
}
#[test]
fn test_verify_cert_expired() {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as u32;
let (cert1, pk1) =
generate_test_cert(false, now - 2000, now - 1000, *b"DNSC", [0x00, 0x02]);
assert!(verify_cert(&cert1, &pk1).is_none());
let (cert2, pk2) =
generate_test_cert(false, now + 1000, now + 2000, *b"DNSC", [0x00, 0x02]);
assert!(verify_cert(&cert2, &pk2).is_none());
}
#[test]
fn test_verify_cert_at_rejects_untrustworthy_clock() {
let (cert, pk) = generate_test_cert(false, 0, u32::MAX, *b"DNSC", [0x00, 0x02]);
assert!(verify_cert_at(&cert, &pk, MIN_KNOWN_TIME - 1).is_none());
assert!(verify_cert_at(&cert, &pk, MIN_KNOWN_TIME).is_some());
}
#[test]
fn test_verify_cert_invalid_magic_or_version() {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as u32;
let (cert1, pk1) =
generate_test_cert(false, now - 1000, now + 1000, *b"DNXX", [0x00, 0x02]);
assert!(verify_cert(&cert1, &pk1).is_none());
let (cert2, pk2) =
generate_test_cert(false, now - 1000, now + 1000, *b"DNSC", [0x00, 0x03]);
assert!(verify_cert(&cert2, &pk2).is_none());
}
#[test]
fn test_verify_cert_truncated() {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as u32;
let (mut cert, pk) =
generate_test_cert(false, now - 1000, now + 1000, *b"DNSC", [0x00, 0x02]);
cert.truncate(123);
assert!(verify_cert(&cert, &pk).is_none());
}
}