From 5f0bb6daf5f58731f9529e20f074ffdb776456a0 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Tue, 17 Sep 2019 22:33:15 +0200 Subject: [PATCH] Start decrypting DNSCrypt queries --- src/crypto.rs | 82 +++++++++++++++++++++++++++++++++++++++++++ src/dns.rs | 15 ++++---- src/dnscrypt.rs | 71 +++++++++++++++++++++++++++++++++++++ src/dnscrypt_certs.rs | 37 +++++++++++++++++-- src/globals.rs | 3 +- src/main.rs | 51 +++++++++++++++++---------- 6 files changed, 229 insertions(+), 30 deletions(-) create mode 100644 src/dnscrypt.rs diff --git a/src/crypto.rs b/src/crypto.rs index b21dbe7..e24d736 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -4,6 +4,10 @@ use libsodium_sys::*; use std::ffi::CStr; use std::ptr; +#[allow(non_upper_case_globals)] +pub const crypto_box_curve25519xchacha20poly1305_HALFNONCEBYTES: usize = + crypto_box_curve25519xchacha20poly1305_NONCEBYTES as usize / 2; + #[derive(Derivative)] #[derivative(Default)] pub struct Signature( @@ -86,6 +90,84 @@ impl SignKeyPair { } } +#[derive(Debug, Default)] +pub struct CryptSK([u8; crypto_box_curve25519xchacha20poly1305_SECRETKEYBYTES as usize]); + +impl CryptSK { + pub fn as_bytes( + &self, + ) -> &[u8; crypto_box_curve25519xchacha20poly1305_SECRETKEYBYTES as usize] { + &self.0 + } + + pub fn from_bytes( + bytes: [u8; crypto_box_curve25519xchacha20poly1305_SECRETKEYBYTES as usize], + ) -> Self { + CryptSK(bytes) + } +} + +#[derive(Debug, Default)] +pub struct CryptPK([u8; crypto_box_curve25519xchacha20poly1305_PUBLICKEYBYTES as usize]); + +impl CryptPK { + pub fn as_bytes( + &self, + ) -> &[u8; crypto_box_curve25519xchacha20poly1305_PUBLICKEYBYTES as usize] { + &self.0 + } + + pub fn from_bytes( + bytes: [u8; crypto_box_curve25519xchacha20poly1305_PUBLICKEYBYTES as usize], + ) -> Self { + CryptPK(bytes) + } +} + +#[derive(Debug, Default)] +pub struct CryptKeyPair { + pub sk: CryptSK, + pub pk: CryptPK, +} + +impl CryptKeyPair { + pub fn new() -> Self { + let mut kp = CryptKeyPair::default(); + unsafe { + crypto_box_curve25519xchacha20poly1305_keypair( + kp.pk.0.as_mut_ptr(), + kp.sk.0.as_mut_ptr(), + ) + }; + kp + } + + pub fn decrypt( + &self, + client_pk: &[u8], + nonce: &[u8], + encrypted: &[u8], + ) -> Result, Error> { + let encrypted_len = encrypted.len(); + let mut decrypted = + vec![0u8; encrypted_len - crypto_box_curve25519xchacha20poly1305_MACBYTES as usize]; + let res = unsafe { + libsodium_sys::crypto_box_curve25519xchacha20poly1305_open_easy( + decrypted.as_mut_ptr(), + encrypted.as_ptr(), + encrypted_len as _, + nonce.as_ptr(), + client_pk.as_ptr(), + self.sk.as_bytes().as_ptr(), + ) + }; + match res { + 0 => Ok(decrypted), + _ => bail!("Unable to decrypt"), + } + } +} + pub fn bin2hex(bin: &[u8]) -> String { let bin_len = bin.len(); let hex_len = bin_len * 2 + 1; diff --git a/src/dns.rs b/src/dns.rs index c41ce3b..223f56d 100644 --- a/src/dns.rs +++ b/src/dns.rs @@ -3,7 +3,7 @@ use crate::errors::*; use byteorder::{BigEndian, ByteOrder, WriteBytesExt}; -pub const DNS_MAX_HOSTNAME_LEN: usize = 256; +pub const DNS_MAX_HOSTNAME_SIZE: usize = 256; pub const DNS_HEADER_SIZE: usize = 12; pub const DNS_OFFSET_FLAGS: usize = 2; pub const DNS_MAX_PACKET_SIZE: usize = 8192; @@ -96,7 +96,7 @@ pub fn qname(packet: &[u8]) -> Result, Error> { ensure!(qdcount(packet) == 1, "Unexpected query count"); let packet_len = packet.len(); let mut offset = DNS_HEADER_SIZE; - let mut qname = Vec::with_capacity(DNS_MAX_HOSTNAME_LEN); + let mut qname = Vec::with_capacity(DNS_MAX_HOSTNAME_SIZE); let mut indirections = 0; loop { ensure!(offset < packet_len, "Short packet"); @@ -127,7 +127,7 @@ pub fn qname(packet: &[u8]) -> Result, Error> { qname.push(b'.') } ensure!( - qname.len() < DNS_MAX_HOSTNAME_LEN - label_len, + qname.len() < DNS_MAX_HOSTNAME_SIZE - label_len, "Name too long" ); qname.extend_from_slice(&packet[offset..offset + label_len]); @@ -157,7 +157,7 @@ fn skip_name(packet: &[u8], offset: usize) -> Result { "Malformed packet with an out-of-bounds name" ); qname_len += label_len + 1; - ensure!(qname_len <= DNS_MAX_HOSTNAME_LEN, "Name too long"); + ensure!(qname_len <= DNS_MAX_HOSTNAME_SIZE, "Name too long"); offset += label_len + 1; if label_len == 0 { break; @@ -293,7 +293,7 @@ pub fn set_edns_max_payload_size(packet: &mut Vec, max_payload_size: u16) -> pub fn serve_certificates<'t>( client_packet: &[u8], expected_qname: &str, - dnscrypt_certs: impl IntoIterator, + dnscrypt_encryption_params_set: impl IntoIterator, ) -> Result>, Error> { let offset = skip_name(client_packet, DNS_HEADER_SIZE)?; ensure!(client_packet.len() - offset >= 4, "Short packet"); @@ -309,9 +309,8 @@ pub fn serve_certificates<'t>( } let mut packet = (&client_packet[..offset + 4]).to_vec(); authoritative_response(&mut packet); - - for dnscrypt_cert in dnscrypt_certs { - let cert_bin = dnscrypt_cert.as_bytes(); + for dnscrypt_encryption_params in dnscrypt_encryption_params_set { + let cert_bin = dnscrypt_encryption_params.dnscrypt_cert().as_bytes(); ensure!(cert_bin.len() <= 0xff, "Certificate too long"); ancount_inc(&mut packet)?; packet.write_u16::(0xc000 + DNS_HEADER_SIZE as u16)?; diff --git a/src/dnscrypt.rs b/src/dnscrypt.rs new file mode 100644 index 0000000..009db97 --- /dev/null +++ b/src/dnscrypt.rs @@ -0,0 +1,71 @@ +use crate::crypto::*; +use crate::dns::*; +use crate::dnscrypt_certs::*; +use crate::errors::*; + +use libsodium_sys::*; +use std::ffi::CStr; +use std::ptr; + +pub const DNSCRYPT_CLIENT_MAGIC_SIZE: usize = 8; +pub const DNSCRYPT_CLIENT_PK_SIZE: usize = 32; +pub const DNSCRYPT_CLIENT_NONCE_SIZE: usize = 12; + +pub struct DNSCryptQuery<'t> { + wrapped_packet: &'t [u8], +} + +impl<'t> DNSCryptQuery<'t> { + pub fn new( + wrapped_packet: &'t [u8], + dnscrypt_encryption_params_set: &[DNSCryptEncryptionParams], + ) -> Result { + ensure!( + wrapped_packet.len() + >= DNSCRYPT_CLIENT_MAGIC_SIZE + + DNSCRYPT_CLIENT_PK_SIZE + + DNSCRYPT_CLIENT_NONCE_SIZE + + DNS_HEADER_SIZE, + "Short packet" + ); + let dnscrypt_query = DNSCryptQuery { wrapped_packet }; + let client_magic = dnscrypt_query.client_magic(); + let dnscrypt_encryption_params = dnscrypt_encryption_params_set + .iter() + .find(|p| p.client_magic() == client_magic) + .ok_or_else(|| format_err!("Client magic not found"))?; + + let encrypted_packet = dnscrypt_query.encrypted_packet(); + let encrypted_packet_len = encrypted_packet.len(); + let mut nonce = vec![0u8; crypto_box_curve25519xchacha20poly1305_NONCEBYTES as usize]; + &mut nonce[..crypto_box_curve25519xchacha20poly1305_HALFNONCEBYTES] + .copy_from_slice(dnscrypt_query.client_nonce()); + let resolver_kp = dnscrypt_encryption_params.resolver_kp(); + resolver_kp.decrypt(dnscrypt_query.client_pk(), &nonce, encrypted_packet)?; + dbg!("ok"); + Ok(dnscrypt_query) + } + + pub fn client_magic(&self) -> &[u8] { + &self.wrapped_packet[..DNSCRYPT_CLIENT_MAGIC_SIZE] + } + + pub fn client_pk(&self) -> &[u8] { + &self.wrapped_packet + [DNSCRYPT_CLIENT_MAGIC_SIZE..DNSCRYPT_CLIENT_MAGIC_SIZE + DNSCRYPT_CLIENT_PK_SIZE] + } + + pub fn client_nonce(&self) -> &[u8] { + &self.wrapped_packet[DNSCRYPT_CLIENT_MAGIC_SIZE + DNSCRYPT_CLIENT_PK_SIZE + ..DNSCRYPT_CLIENT_MAGIC_SIZE + DNSCRYPT_CLIENT_PK_SIZE + DNSCRYPT_CLIENT_NONCE_SIZE] + } + + pub fn encrypted_packet(&self) -> &[u8] { + &self.wrapped_packet + [DNSCRYPT_CLIENT_MAGIC_SIZE + DNSCRYPT_CLIENT_PK_SIZE + DNSCRYPT_CLIENT_NONCE_SIZE..] + } + + pub fn into_packet(self) -> Vec { + self.encrypted_packet().to_vec() + } +} diff --git a/src/dnscrypt_certs.rs b/src/dnscrypt_certs.rs index 5ed6ec5..92f029e 100644 --- a/src/dnscrypt_certs.rs +++ b/src/dnscrypt_certs.rs @@ -41,7 +41,7 @@ pub struct DNSCryptCert { } impl DNSCryptCert { - pub fn new(resolver_kp: &SignKeyPair) -> Self { + pub fn new(provider_kp: &SignKeyPair, resolver_kp: &CryptKeyPair) -> Self { let ts_start = now(); let ts_end = ts_start + 86400; @@ -63,7 +63,7 @@ impl DNSCryptCert { BigEndian::write_u16(&mut dnscrypt_cert.minor_version, 0); dnscrypt_cert.signature.copy_from_slice( - resolver_kp + provider_kp .sk .sign(dnscrypt_cert_inner.as_bytes()) .as_bytes(), @@ -74,4 +74,37 @@ impl DNSCryptCert { pub fn as_bytes(&self) -> &[u8] { unsafe { slice::from_raw_parts(self as *const _ as *const u8, mem::size_of_val(self)) } } + + pub fn client_magic(&self) -> &[u8] { + &self.inner.client_magic + } +} + +#[derive(Debug)] +pub struct DNSCryptEncryptionParams { + dnscrypt_cert: DNSCryptCert, + resolver_kp: CryptKeyPair, +} + +impl DNSCryptEncryptionParams { + pub fn new(provider_kp: &SignKeyPair) -> Self { + let resolver_kp = CryptKeyPair::new(); + let dnscrypt_cert = DNSCryptCert::new(&provider_kp, &resolver_kp); + DNSCryptEncryptionParams { + dnscrypt_cert, + resolver_kp, + } + } + + pub fn client_magic(&self) -> &[u8] { + self.dnscrypt_cert.client_magic() + } + + pub fn dnscrypt_cert(&self) -> &DNSCryptCert { + &self.dnscrypt_cert + } + + pub fn resolver_kp(&self) -> &CryptKeyPair { + &self.resolver_kp + } } diff --git a/src/globals.rs b/src/globals.rs index c29d9bf..fdc0252 100644 --- a/src/globals.rs +++ b/src/globals.rs @@ -13,8 +13,7 @@ use tokio::sync::oneshot; #[derive(Debug)] pub struct Globals { pub runtime: Arc, - pub resolver_kp: SignKeyPair, - pub dnscrypt_certs: Vec, + pub dnscrypt_encryption_params_set: Vec, pub provider_name: String, pub listen_addr: SocketAddr, pub external_addr: SocketAddr, diff --git a/src/main.rs b/src/main.rs index cb4882d..68deb15 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,12 +18,14 @@ extern crate log; mod config; mod crypto; mod dns; +mod dnscrypt; mod dnscrypt_certs; mod errors; mod globals; use crypto::*; use dns::*; +use dnscrypt::*; use dnscrypt_certs::*; use errors::*; use globals::*; @@ -94,19 +96,30 @@ async fn respond_to_query(client_ctx: ClientCtx, packet: Vec) -> Result<(), async fn handle_client_query( globals: Arc, client_ctx: ClientCtx, - mut packet: Vec, + encrypted_packet: Vec, ) -> Result<(), Error> { - ensure!(packet.len() >= DNSCRYPT_QUERY_MIN_SIZE, "Short packet"); - ensure!(dns::qdcount(&packet) == 1, "No question"); - ensure!( - !dns::is_response(&packet), - "Question expected, but got a response instead" - ); - if let Some(synth_packet) = - serve_certificates(&packet, &globals.provider_name, &globals.dnscrypt_certs)? - { - return respond_to_query(client_ctx, synth_packet).await; - } + let packet = DNSCryptQuery::new(&encrypted_packet, &globals.dnscrypt_encryption_params_set); + let mut packet = match packet { + Err(_) => { + let packet = encrypted_packet; + ensure!(packet.len() >= DNS_HEADER_SIZE, "Short packet"); + ensure!(dns::qdcount(&packet) == 1, "No question"); + ensure!( + !dns::is_response(&packet), + "Question expected, but got a response instead" + ); + if let Some(synth_packet) = serve_certificates( + &packet, + &globals.provider_name, + &globals.dnscrypt_encryption_params_set, + )? { + return respond_to_query(client_ctx, synth_packet).await; + } + bail!("Unencrypted query"); + } + Ok(packet) => packet.into_packet(), + }; + let original_tid = dns::tid(&packet); let tid = random(); dns::set_tid(&mut packet, tid); @@ -317,15 +330,15 @@ fn main() -> Result<(), Error> { let udp_timeout = Duration::from_secs(10); let tcp_timeout = Duration::from_secs(10); - let resolver_kp = SignKeyPair::new(); + let provider_kp = SignKeyPair::new(); info!("Server address: {}", listen_addr); - info!("Provider public key: {}", resolver_kp.pk.as_string()); + info!("Provider public key: {}", provider_kp.pk.as_string()); info!("Provider name: {}", provider_name); let stamp = dnsstamps::DNSCryptBuilder::new(dnsstamps::DNSCryptProvider::new( provider_name.clone(), - resolver_kp.pk.as_bytes().to_vec(), + provider_kp.pk.as_bytes().to_vec(), )) .with_addr(listen_addr_s.to_string()) .with_informal_property(InformalProperty::DNSSEC) @@ -335,15 +348,17 @@ fn main() -> Result<(), Error> { .unwrap(); println!("DNS Stamp: {}", stamp); - let dnscrypt_cert = DNSCryptCert::new(&resolver_kp); + let resolver_kp = CryptKeyPair::new(); + let dnscrypt_cert = DNSCryptCert::new(&provider_kp, &resolver_kp); + + let dnscrypt_encryption_params = DNSCryptEncryptionParams::new(&provider_kp); let runtime = Arc::new(Runtime::new()?); let udp_max_active_connections = 1000; let tcp_max_active_connections = 100; let globals = Arc::new(Globals { runtime: runtime.clone(), - resolver_kp, - dnscrypt_certs: vec![dnscrypt_cert], + dnscrypt_encryption_params_set: vec![dnscrypt_encryption_params], provider_name, listen_addr, upstream_addr,