#![forbid(unsafe_code, elided_lifetimes_in_paths)]
#![allow(clippy::multiple_crate_versions)]
#[cfg(doctest)]
#[doc = include_str!("../README.md")]
struct ReadmeDoctests;
mod cache;
pub mod cert;
pub mod crypto;
pub mod error;
pub mod net;
pub mod packet;
#[cfg(feature = "reqwest")]
pub mod reqwest_resolver;
pub mod resolver;
pub use error::Error;
#[cfg(feature = "reqwest")]
pub use reqwest_resolver::DnscryptResolver;
pub use resolver::{
DnscryptClientState, HARDCODED_RESOLVERS, HardcodedResolver, establish_dnscrypt_session,
resolve, resolve_domain_via_dnscrypt_session,
};
#[allow(clippy::expect_used)]
pub(crate) fn random_bytes<const N: usize>() -> [u8; N] {
let mut buf = [0u8; N];
aws_lc_rs::rand::fill(&mut buf).expect("aws-lc-rs RNG failed: no OS CSPRNG available");
buf
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::indexing_slicing,
clippy::arithmetic_side_effects,
clippy::as_conversions
)]
mod tests {
use crate::crypto::{CipherSuite, djb_poly1305_decrypt, djb_poly1305_encrypt};
use crate::packet::{build_a_record_query, pad_query, parse_dns_response, unpad_response};
use std::net::{IpAddr, Ipv4Addr};
#[test]
fn test_build_a_record_query() {
let query = build_a_record_query("github.com", [0xab, 0xcd]);
assert_eq!(
&query[0..12],
&[
0xab, 0xcd, 0x01, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
]
);
assert_eq!(query[12], 6);
assert_eq!(&query[13..19], b"github");
assert_eq!(query[19], 3);
assert_eq!(&query[20..23], b"com");
assert_eq!(query[23], 0);
assert_eq!(&query[24..28], &[0x00, 0x01, 0x00, 0x01]);
}
#[test]
fn test_parse_dns_response_valid() {
let mut response = Vec::new();
response.extend_from_slice(&[
0xab, 0xcd, 0x81, 0x80, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
]);
response.extend_from_slice(&[6]);
response.extend_from_slice(b"github");
response.extend_from_slice(&[3]);
response.extend_from_slice(b"com");
response.extend_from_slice(&[0]);
response.extend_from_slice(&[0x00, 0x01, 0x00, 0x01]);
response.extend_from_slice(&[0xc0, 0x0c]);
response.extend_from_slice(&[0x00, 0x01, 0x00, 0x01]);
response.extend_from_slice(&[0x00, 0x00, 0x00, 0x3c]);
response.extend_from_slice(&[0x00, 0x04]);
response.extend_from_slice(&[140, 82, 121, 4]);
let parsed = parse_dns_response(&response);
assert_eq!(parsed[0], IpAddr::V4(Ipv4Addr::new(140, 82, 121, 4)));
}
#[test]
fn test_parse_dns_response_truncated() {
assert!(parse_dns_response(&[]).is_empty());
assert!(parse_dns_response(&[0; 10]).is_empty());
let mut response = Vec::new();
response.extend_from_slice(&[
0xab, 0xcd, 0x81, 0x80, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
]);
response.extend_from_slice(&[6]);
response.extend_from_slice(b"github");
response.extend_from_slice(&[3]);
response.extend_from_slice(b"com");
response.extend_from_slice(&[0]);
response.extend_from_slice(&[0x00, 0x01, 0x00, 0x01]);
response.extend_from_slice(&[0xc0, 0x0c]);
assert!(parse_dns_response(&response).is_empty());
}
#[test]
fn test_pad_query() {
let query = vec![1, 2, 3];
let padded = pad_query(&query, 64);
assert!(padded.len() >= 64 && padded.len().is_multiple_of(64));
assert_eq!(padded[0..3], [1, 2, 3]);
assert_eq!(padded[3], 0x80);
assert!(padded[4..].iter().all(|&b| b == 0));
let query2 = vec![5; 64];
let padded2 = pad_query(&query2, 64);
assert!(padded2.len() >= 128 && padded2.len().is_multiple_of(64));
assert_eq!(padded2[64], 0x80);
}
#[test]
fn test_unpad_response() {
let valid = vec![1, 2, 3, 0x80, 0, 0, 0];
assert_eq!(unpad_response(&valid).unwrap(), vec![1, 2, 3]);
assert!(unpad_response(&[1, 2, 3, 0, 0, 0]).is_err());
assert!(unpad_response(&[]).is_err());
}
#[test]
fn test_djb_poly1305_roundtrip() {
for suite in [
CipherSuite::XChaCha20Poly1305,
CipherSuite::XSalsa20Poly1305,
] {
let key = [9u8; 32];
let nonce = [12u8; 24];
let msg = b"Hello, DNSCrypt!";
let encrypted = djb_poly1305_encrypt(suite, &key, &nonce, msg);
assert_eq!(encrypted.len(), msg.len() + 16);
let decrypted = djb_poly1305_decrypt(suite, &key, &nonce, &encrypted).unwrap();
assert_eq!(decrypted, msg);
}
}
#[test]
fn test_djb_poly1305_tamper_detection() {
for suite in [
CipherSuite::XChaCha20Poly1305,
CipherSuite::XSalsa20Poly1305,
] {
let key = [9u8; 32];
let nonce = [12u8; 24];
let msg = b"Hello, DNSCrypt!";
let mut encrypted = djb_poly1305_encrypt(suite, &key, &nonce, msg);
encrypted[16] ^= 0xff;
assert!(djb_poly1305_decrypt(suite, &key, &nonce, &encrypted).is_err());
}
}
}