use ed25519_dalek::{Signature, VerifyingKey};
const MIN_KNOWN_TIME: u64 = 1_780_272_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,
}
pub fn verify_cert(cert: &[u8], provider_pk: &[u8; 32]) -> 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()?);
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
{
if now < MIN_KNOWN_TIME {
return None; }
if now < ts_start as u64 || now > ts_end as u64 {
return None;
}
}
Some(DnscryptCert {
resolver_pk,
client_magic,
serial,
ts_start,
ts_end,
})
}
#[cfg(test)]
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_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());
}
}