diff --git a/README.md b/README.md index 4f660f5..1d8612b 100644 --- a/README.md +++ b/README.md @@ -8,4 +8,6 @@ I remember I was listening to, I think, [Security This Week with Carl Franklin]( I also wanted to play with a big rust project for standard targets with threading. Although I had a lot of fun with my browser-based checkers game, [Draught](https://draught.sarsoo.xyz), working against WASM has some restrictions. -[Read the Docs](https://github.com/Sarsoo/dnstp/settings/pages) \ No newline at end of file +[Read the Docs](https://github.com/Sarsoo/dnstp/settings/pages) + +One of my aims was to see whether arbitrary data could be transmitted using more or less compliant DNS messages, i.e.not just sending junk over UDP to port 53. The closer to compliant DNS that the messages are, the more subtle the process is. In my own network, I have NAT rules that will redirect any DNS messages that are destined for external DNS servers to my own internal ones first. If the packets are crap and malformed, they could be rejected before they even reach my subtle server. \ No newline at end of file diff --git a/dnstp-client/src/main.rs b/dnstp-client/src/main.rs index d4bd9a1..12c9e63 100644 --- a/dnstp-client/src/main.rs +++ b/dnstp-client/src/main.rs @@ -1,4 +1,7 @@ -use std::fs::{File, OpenOptions}; +//! # Client Side +//! + +use std::fs::OpenOptions; use std::net::SocketAddr; use std::thread; use std::time::Duration; @@ -6,6 +9,7 @@ use clap::Parser; use log::{info, LevelFilter}; use rand::RngCore; use simplelog::*; +use dnstplib::DomainConfig; use dnstplib::message::DNSMessage; use dnstplib::net::{DNSSocket, NetworkMessage}; @@ -53,19 +57,24 @@ fn main() { socket.run_rx(processor.get_message_channel().expect("couldn't get message processing channel")); - let domain = vec![args.key_endpoint, args.base_domain].join("."); + let domain_config = DomainConfig { + base_domain: args.base_domain, + key_endpoint: args.key_endpoint + }; + + let domain = domain_config.get_fq_key_endpoint(); let mut rng = rand::thread_rng(); loop { info!("sending..."); - let message = DNSMessage::from_hostname(address, rng.next_u32() as u16, domain.clone()); + let message = DNSMessage::req_from_hostname(address, rng.next_u32() as u16, domain.clone()); let bytes = message.to_bytes(); - tx_channel.send(Box::from(NetworkMessage { - buffer: Box::from(bytes), + tx_channel.send(Box::new(NetworkMessage { + buffer: Box::new(bytes), peer: args.address.parse().unwrap() })); diff --git a/dnstp-server/src/main.rs b/dnstp-server/src/main.rs index 674898d..a684105 100644 --- a/dnstp-server/src/main.rs +++ b/dnstp-server/src/main.rs @@ -1,11 +1,14 @@ -//! DNS server component for processing requests and replying with DNS records +//! # Server Side +//! DNS server component for processing requests and replying with DNS records. +//! +//! The aim is to have clients exfil to this server and to allow pulling down data from the server. use clap::Parser; use std::{thread}; use log::info; use simplelog::*; -use std::fs::{File, OpenOptions}; +use std::fs::OpenOptions; use std::net::SocketAddr; use dnstplib::DomainConfig; diff --git a/dnstp/src/config.rs b/dnstp/src/config.rs index 3490288..86c0fa0 100644 --- a/dnstp/src/config.rs +++ b/dnstp/src/config.rs @@ -4,4 +4,11 @@ pub struct DomainConfig { pub base_domain: String, pub key_endpoint: String, +} + +impl DomainConfig { + pub fn get_fq_key_endpoint(&self) -> String + { + vec![self.key_endpoint.clone(), self.base_domain.clone()].join(".") + } } \ No newline at end of file diff --git a/dnstp/src/crypto/mod.rs b/dnstp/src/crypto/mod.rs index 1a37695..6526c5c 100644 --- a/dnstp/src/crypto/mod.rs +++ b/dnstp/src/crypto/mod.rs @@ -1,25 +1,45 @@ //! Method for handling cryptography including ECDH shared secret derivation and symmetric key encryption +//! +//! **Step 1**: [`get_random_asym_pair`] Generate a public/private key pair on each side +//! +//! **Step 2**: Swap the [`p256::EncodedPoint`]s from step 1 between parties +//! +//! **Step 3**: [`get_shared_asym_secret`] Combine one private key with the other public key to end up at the same shared secret +//! +//! **Step 4**: [`asym_to_sym_key`] Take the [`p256::NistP256`] shared asymmetric secret key and use it as a symmetric key ready for encryption decryption +//! +//! **Step 5**: [`generate_aes_nonce`] Get a nonce to use when encrypting +//! +//! **Step 6**: [`encrypt`] Use the key from step 4 with the nonce from step 5 to encrypt arbitrary data +//! +//! **Step 7**: [`decrypt`] Use the same key from step 6 and ***the same nonce from step 6*** to decrypt the outputted ciphertext from step 6. #[cfg(test)] mod tests; +use std::str::FromStr; use p256::{EncodedPoint, PublicKey, ecdh::EphemeralSecret, NistP256}; use p256::elliptic_curve::ecdh::SharedSecret; use aes_gcm_siv::{aead::{Aead, KeyInit}, AeadCore, Aes256GcmSiv, Nonce}; use rand_core::OsRng; -pub fn get_random_asym_pair() -> (EphemeralSecret, EncodedPoint) +pub const PUBLIC_KEY_OPENING: &str = "-----BEGIN PUBLIC KEY-----\n"; +pub const PUBLIC_KEY_CLOSING: &str = "\n-----END PUBLIC KEY-----\n"; + +/// Generate a public/private key pair +pub fn get_random_asym_pair() -> (EphemeralSecret, String) { let secret = EphemeralSecret::random(&mut OsRng); - let public_point = EncodedPoint::from(secret.public_key()); + let public_point = secret.public_key().to_string(); (secret, public_point) } -pub fn get_shared_asym_secret(secret: EphemeralSecret, opposing_public_key: EncodedPoint) -> Result, ()> { +/// Use one private key and an opposing public key to arrive at the same shared secret +pub fn get_shared_asym_secret(secret: EphemeralSecret, opposing_public_key: String) -> Result, ()> { - match PublicKey::from_sec1_bytes(opposing_public_key.as_ref()) { + match PublicKey::from_str(&opposing_public_key) { Ok(other_public) => { Ok(secret.diffie_hellman(&other_public)) } @@ -37,16 +57,19 @@ pub fn get_shared_asym_secret(secret: EphemeralSecret, opposing_public_key: Enco // Nonce::from(nonce_buffer) // } +/// Generate a safe nonce to use in symmetric encryption pub fn generate_aes_nonce() -> Nonce { Aes256GcmSiv::generate_nonce(OsRng) } +/// Turn the asymmetric shared secret into a symmetric encryption key pub fn asym_to_sym_key(secret: &SharedSecret) -> Aes256GcmSiv { Aes256GcmSiv::new(secret.raw_secret_bytes()) } +/// Symmetrically encrypt data using a key derived from ECDH pub fn encrypt(key: &Aes256GcmSiv, nonce: &Nonce, bytes: &Vec) -> Result, ()> { match key.encrypt(nonce, bytes.as_ref()) { @@ -59,6 +82,7 @@ pub fn encrypt(key: &Aes256GcmSiv, nonce: &Nonce, bytes: &Vec) -> Result) -> Result, ()> { match key.decrypt(nonce, bytes.as_ref()) { @@ -69,4 +93,18 @@ pub fn decrypt(key: &Aes256GcmSiv, nonce: &Nonce, bytes: &Vec) -> Result String +{ + public[27.. 125 + 27].to_string().replace('\n', ".") +} + +pub fn fatten_public_key(public: &String) -> String +{ + let mut fattened = public.clone(); + fattened.insert_str(0, PUBLIC_KEY_OPENING); + fattened.push_str(PUBLIC_KEY_CLOSING); + + fattened.replace('.', "\n") } \ No newline at end of file diff --git a/dnstp/src/crypto/tests.rs b/dnstp/src/crypto/tests.rs index f9d072d..0889f8a 100644 --- a/dnstp/src/crypto/tests.rs +++ b/dnstp/src/crypto/tests.rs @@ -32,4 +32,15 @@ fn arbitrary_string_back_and_forth() { let result = String::from_utf8(plain_text).unwrap(); assert_eq!(data, result); +} + +#[test] +fn public_key_trimming() +{ + let (_, public) = get_random_asym_pair(); + + let trimmed = trim_public_key(&public); + let fattened = fatten_public_key(&trimmed.to_string()); + + assert_eq!(public, fattened); } \ No newline at end of file diff --git a/dnstp/src/lib.rs b/dnstp/src/lib.rs index c1e3eb9..3f97e56 100644 --- a/dnstp/src/lib.rs +++ b/dnstp/src/lib.rs @@ -1,3 +1,6 @@ +//! # Common Functionality +//! The vast majority of functionality is in this library crate. The client and server executable crates are really just wiring up bits and pieces from this library. + pub mod message_parser; mod byte; @@ -6,6 +9,6 @@ pub mod message; pub mod net; mod string; pub mod config; -mod crypto; +pub mod crypto; pub use config::DomainConfig; \ No newline at end of file diff --git a/dnstp/src/message/message.rs b/dnstp/src/message/message.rs index 717fa90..c856798 100644 --- a/dnstp/src/message/message.rs +++ b/dnstp/src/message/message.rs @@ -1,18 +1,24 @@ use std::net::{Ipv4Addr, SocketAddr}; use crate::message::{DNSQuestion, DNSHeader, questions_to_bytes, Direction, ResponseCode, QType, QClass, ResourceRecord, records_to_bytes, ARdata}; +/// A DNS message which can be used as either a request or response based on its direction and composition #[derive(Debug)] pub struct DNSMessage { + /// Status/request codes, counts for other collections pub header: DNSHeader, + /// Hostname queries, should be the same in both requests and responses pub questions: Vec, + /// Responses for [`DNSMessage::questions`], has similar structure with varying data field based on query type pub answer_records: Vec, pub authority_records: Vec, pub additional_records: Vec, + /// IP and socket address of the client which sent this message/client to send message to pub peer: SocketAddr } impl DNSMessage { + /// Transform a message into a network transmissable byte sequence pub fn to_bytes(& self) -> Vec { let mut header_bytes = self.header.to_bytes().to_vec(); @@ -29,7 +35,8 @@ impl DNSMessage { return header_bytes } - pub fn from_hostname(peer: SocketAddr, id: u16, hostname: String) -> DNSMessage + /// Helper function for getting a DNS request for the IPv4 of a single hostname + pub fn req_from_hostname(peer: SocketAddr, id: u16, hostname: String) -> DNSMessage { DNSMessage { header: DNSHeader::new_request(id, None), @@ -47,7 +54,8 @@ impl DNSMessage { } } - pub fn from_hostnames(peer: SocketAddr, id: u16, hostnames: Vec) -> DNSMessage + /// Helper function to get a DNS request for the IPv4s of multiple hostnames + pub fn reqs_from_hostnames(peer: SocketAddr, id: u16, hostnames: Vec) -> DNSMessage { DNSMessage { header: DNSHeader::new_request(id, Some(hostnames.len() as u16)), @@ -67,7 +75,7 @@ impl DNSMessage { } } - pub fn a_from_request(request: &DNSMessage, ip: impl Fn(&DNSQuestion) -> Ipv4Addr) -> DNSMessage + pub fn a_resp_from_request(request: &DNSMessage, ip: impl Fn(&DNSQuestion) -> Ipv4Addr) -> DNSMessage { let mut response = DNSMessage{ header: request.header.clone(), @@ -83,7 +91,7 @@ impl DNSMessage { .map(|x| ResourceRecord::from_query(x, 12, - Box::from(ARdata::from(ip(x))), + Box::new(ARdata::from(ip(x))), None)) .collect(); diff --git a/dnstp/src/message/question/tests.rs b/dnstp/src/message/question/tests.rs index 702cff2..a5a9d2c 100644 --- a/dnstp/src/message/question/tests.rs +++ b/dnstp/src/message/question/tests.rs @@ -1,5 +1,13 @@ use super::*; +macro_rules! assert_questions_eq { + ($left:expr, $right:expr) => { + assert_eq!($left.qname, $right.qname); + assert_eq!($left.qclass, $right.qclass); + assert_eq!($left.qtype, $right.qtype); + }; +} + #[test] fn one_question_back_and_forth() { let q = DNSQuestion { @@ -13,9 +21,7 @@ fn one_question_back_and_forth() { let (q_reconstructed, q_remaining) = questions_from_bytes(q_bytes, 1).unwrap(); - assert_eq!(q.qname, q_reconstructed[0].qname); - assert_eq!(q.qclass, q_reconstructed[0].qclass); - assert_eq!(q.qtype, q_reconstructed[0].qtype); + assert_questions_eq!(q, q_reconstructed[0]); } #[test] @@ -39,13 +45,8 @@ fn two_questions_back_and_forth() { let (q_reconstructed, q_remaining) = questions_from_bytes(q_bytes, 2).unwrap(); - assert_eq!(q.qname, q_reconstructed[0].qname); - assert_eq!(q.qclass, q_reconstructed[0].qclass); - assert_eq!(q.qtype, q_reconstructed[0].qtype); - - assert_eq!(q2.qname, q_reconstructed[1].qname); - assert_eq!(q2.qclass, q_reconstructed[1].qclass); - assert_eq!(q2.qtype, q_reconstructed[1].qtype); + assert_questions_eq!(q, q_reconstructed[0]); + assert_questions_eq!(q2, q_reconstructed[1]); } #[test] @@ -77,15 +78,7 @@ fn three_questions_back_and_forth() { let (q_reconstructed, q_remaining) = questions_from_bytes(q_bytes, 3).unwrap(); - assert_eq!(q.qname, q_reconstructed[0].qname); - assert_eq!(q.qclass, q_reconstructed[0].qclass); - assert_eq!(q.qtype, q_reconstructed[0].qtype); - - assert_eq!(q2.qname, q_reconstructed[1].qname); - assert_eq!(q2.qclass, q_reconstructed[1].qclass); - assert_eq!(q2.qtype, q_reconstructed[1].qtype); - - assert_eq!(q3.qname, q_reconstructed[2].qname); - assert_eq!(q3.qclass, q_reconstructed[2].qclass); - assert_eq!(q3.qtype, q_reconstructed[2].qtype); + assert_questions_eq!(q, q_reconstructed[0]); + assert_questions_eq!(q2, q_reconstructed[1]); + assert_questions_eq!(q3, q_reconstructed[2]); } \ No newline at end of file diff --git a/dnstp/src/message/record/a_rdata.rs b/dnstp/src/message/record/a_rdata.rs index a5a9530..cdd36c6 100644 --- a/dnstp/src/message/record/a_rdata.rs +++ b/dnstp/src/message/record/a_rdata.rs @@ -8,8 +8,8 @@ pub struct ARdata { impl Debug for ARdata { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("IP") - .field("data", &self.rdata) + f.debug_struct("A") + .field("IP", &self.rdata) .finish() } } diff --git a/dnstp/src/message/record/aaaa_rdata.rs b/dnstp/src/message/record/aaaa_rdata.rs index 2f9da78..628cb36 100644 --- a/dnstp/src/message/record/aaaa_rdata.rs +++ b/dnstp/src/message/record/aaaa_rdata.rs @@ -8,8 +8,8 @@ pub struct AAAARdata { impl Debug for AAAARdata { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("IP") - .field("data", &self.rdata) + f.debug_struct("AAAA") + .field("IP", &self.rdata) .finish() } } diff --git a/dnstp/src/message/record/mod.rs b/dnstp/src/message/record/mod.rs index a784a85..9b44ece 100644 --- a/dnstp/src/message/record/mod.rs +++ b/dnstp/src/message/record/mod.rs @@ -128,7 +128,7 @@ pub fn records_from_bytes(bytes: Vec, total_answers: u16) -> Result<(Vec { + assert_eq!($left.name_offset, $right.name_offset); + assert_eq!($left.answer_type, $right.answer_type); + assert_eq!($left.class, $right.class); + assert_eq!($left.ttl, $right.ttl); + assert_eq!($left.rd_length, $right.rd_length); + assert_eq!($left.r_data.to_bytes(), $right.r_data.to_bytes()); + }; +} + #[test] fn one_answer_back_and_forth() { let q = ResourceRecord { @@ -11,7 +22,7 @@ fn one_answer_back_and_forth() { class: QClass::Internet, ttl: 0, rd_length: 1, - r_data: Box::from(RawRData::from(vec![1])) + r_data: Box::new(RawRData::from(vec![1])) }; let mut q_bytes = q.to_bytes(); @@ -19,12 +30,7 @@ fn one_answer_back_and_forth() { let (q_reconstructed, q_remaining) = records_from_bytes(q_bytes, 1).unwrap(); - assert_eq!(q.name_offset, q_reconstructed[0].name_offset); - assert_eq!(q.answer_type, q_reconstructed[0].answer_type); - assert_eq!(q.class, q_reconstructed[0].class); - assert_eq!(q.ttl, q_reconstructed[0].ttl); - assert_eq!(q.rd_length, q_reconstructed[0].rd_length); - assert_eq!(q.r_data.to_bytes(), q_reconstructed[0].r_data.to_bytes()); + assert_record_eq!(q, q_reconstructed[0]); } #[test] @@ -36,7 +42,7 @@ fn two_answers_back_and_forth() { class: QClass::Internet, ttl: 0, rd_length: 1, - r_data: Box::from(RawRData::from(vec![1])) + r_data: Box::new(RawRData::from(vec![1])) }; let q_2 = ResourceRecord { @@ -46,7 +52,7 @@ fn two_answers_back_and_forth() { class: QClass::Internet, ttl: 0, rd_length: 3, - r_data: Box::from(RawRData::from(vec![1, 2, 3])) + r_data: Box::new(RawRData::from(vec![1, 2, 3])) }; let mut q_bytes = q.to_bytes(); @@ -55,17 +61,49 @@ fn two_answers_back_and_forth() { let (q_reconstructed, q_remaining) = records_from_bytes(q_bytes, 2).unwrap(); - assert_eq!(q.name_offset, q_reconstructed[0].name_offset); - assert_eq!(q.answer_type, q_reconstructed[0].answer_type); - assert_eq!(q.class, q_reconstructed[0].class); - assert_eq!(q.ttl, q_reconstructed[0].ttl); - assert_eq!(q.rd_length, q_reconstructed[0].rd_length); - assert_eq!(q.r_data.to_bytes(), q_reconstructed[0].r_data.to_bytes()); + assert_record_eq!(q, q_reconstructed[0]); + assert_record_eq!(q_2, q_reconstructed[1]); +} - assert_eq!(q_2.name_offset, q_reconstructed[1].name_offset); - assert_eq!(q_2.answer_type, q_reconstructed[1].answer_type); - assert_eq!(q_2.class, q_reconstructed[1].class); - assert_eq!(q_2.ttl, q_reconstructed[1].ttl); - assert_eq!(q_2.rd_length, q_reconstructed[1].rd_length); - assert_eq!(q_2.r_data.to_bytes(), q_reconstructed[1].r_data.to_bytes()); -} \ No newline at end of file +// #[test] +// fn two_answers_back_and_forth_zero_data() { +// let q = ResourceRecord { +// // name_offset: "google.com".to_string(), +// name_offset: 12, +// answer_type: QType::A, +// class: QClass::Internet, +// ttl: 0, +// rd_length: 0, +// r_data: Box::new(RawRData::from(vec![])) +// }; +// +// let q_2 = ResourceRecord { +// // name_offset: "google.com".to_string(), +// name_offset: 12, +// answer_type: QType::AAAA, +// class: QClass::Internet, +// ttl: 0, +// rd_length: 3, +// r_data: Box::new(RawRData::from(vec![1, 2, 3])) +// }; +// +// let mut q_bytes = q.to_bytes(); +// q_bytes.append(&mut q_2.to_bytes()); +// q_bytes.append(&mut vec![0, 0, 0, 0, 0, 0]); +// +// let (q_reconstructed, q_remaining) = records_from_bytes(q_bytes, 2).unwrap(); +// +// assert_eq!(q.name_offset, q_reconstructed[0].name_offset); +// assert_eq!(q.answer_type, q_reconstructed[0].answer_type); +// assert_eq!(q.class, q_reconstructed[0].class); +// assert_eq!(q.ttl, q_reconstructed[0].ttl); +// assert_eq!(q.rd_length, q_reconstructed[0].rd_length); +// assert_eq!(q.r_data.to_bytes(), q_reconstructed[0].r_data.to_bytes()); +// +// assert_eq!(q_2.name_offset, q_reconstructed[1].name_offset); +// assert_eq!(q_2.answer_type, q_reconstructed[1].answer_type); +// assert_eq!(q_2.class, q_reconstructed[1].class); +// assert_eq!(q_2.ttl, q_reconstructed[1].ttl); +// assert_eq!(q_2.rd_length, q_reconstructed[1].rd_length); +// assert_eq!(q_2.r_data.to_bytes(), q_reconstructed[1].r_data.to_bytes()); +// } \ No newline at end of file diff --git a/dnstp/src/message_parser.rs b/dnstp/src/message_parser.rs index 3d33c7b..ad5d54f 100644 --- a/dnstp/src/message_parser.rs +++ b/dnstp/src/message_parser.rs @@ -3,7 +3,7 @@ use crate::byte; use crate::message::{DNSMessage, Direction, DNSHeader, Opcode, ResponseCode, QuestionParseError, questions_from_bytes, records_from_bytes, RecordParseError}; use crate::net::NetworkMessage; -use crate::message_parser::RequestParseError::{HeaderParse, QuesionsParse}; +use crate::message_parser::MessageParseError::{HeaderParse, QuesionsParse}; pub const ID_START: usize = 0; pub const FLAGS_START: usize = 2; @@ -77,14 +77,14 @@ pub fn parse_header(header: &[u8; 12]) -> Result } #[derive(Ord, PartialOrd, Eq, PartialEq, Debug)] -pub enum RequestParseError { +pub enum MessageParseError { HeaderParse(HeaderParseError), QuesionsParse(QuestionParseError), RecordParse(RecordParseError), RecordCount(u16, usize), } -pub fn parse_message(msg: NetworkMessage) -> Result +pub fn parse_message(msg: NetworkMessage) -> Result { let header = parse_header(msg.buffer[0..12].try_into().unwrap()); @@ -106,7 +106,7 @@ pub fn parse_message(msg: NetworkMessage) -> Result { if answers.len() != total_records as usize { - return Err(RequestParseError::RecordCount(total_records, answers.len())); + return Err(MessageParseError::RecordCount(total_records, answers.len())); } else { let answer_records = answers.drain(0 .. (header.answer_record_count as usize)).collect(); @@ -122,7 +122,7 @@ pub fn parse_message(msg: NetworkMessage) -> Result return Err(RequestParseError::RecordParse(e)) + Err(e) => return Err(MessageParseError::RecordParse(e)) } } else { diff --git a/dnstp/src/net/socket.rs b/dnstp/src/net/socket.rs index 15d058e..8629697 100644 --- a/dnstp/src/net/socket.rs +++ b/dnstp/src/net/socket.rs @@ -47,7 +47,7 @@ impl DNSSocket { { match UdpSocket::bind(&self.addresses[..]) { Ok(s) => { - self.socket = Option::from(Box::from(s)); + self.socket = Option::from(Box::new(s)); }, Err(_) => {} }; @@ -56,7 +56,7 @@ impl DNSSocket { fn get_socket_clone(&mut self) -> Option> { match &self.socket { - Some(s) => Option::from(Box::from(s.try_clone().unwrap())), + Some(s) => Option::from(Box::new(s.try_clone().unwrap())), None => None } } diff --git a/dnstp/src/processor/encryption/mod.rs b/dnstp/src/processor/encryption/mod.rs deleted file mode 100644 index e69de29..0000000 diff --git a/dnstp/src/processor/mod.rs b/dnstp/src/processor/mod.rs index 3bffa48..593a45a 100644 --- a/dnstp/src/processor/mod.rs +++ b/dnstp/src/processor/mod.rs @@ -2,7 +2,55 @@ pub mod request; pub mod response; -pub mod encryption; +use std::net::SocketAddr; +use log::error; pub use request::RequestProcesor; pub use response::ResponseProcesor; +use crate::message::{QuestionParseError, RecordParseError}; +use crate::message_parser::{HeaderParseError, MessageParseError}; + +pub fn print_error(e: MessageParseError, peer: &SocketAddr) +{ + match e { + MessageParseError::HeaderParse(he) => { + match he { + HeaderParseError::OpcodeParse(oe) => { + error!("[{}] failed to parse opcode from received message: [{}]", peer, oe); + } + HeaderParseError::ResponseCodeParse(rce) => { + error!("[{}] failed to parse response code error from received message: [{}]", peer, rce); + } + } + } + MessageParseError::QuesionsParse(qe) => { + match qe { + QuestionParseError::ShortLength(sl) => { + error!("[{}] failed to parse questions of received message, too short: [{} bytes]", peer, sl); + } + QuestionParseError::QTypeParse(te) => { + error!("[{}] failed to parse questions of received message, qtype error: [{}]", peer, te); + } + QuestionParseError::QClassParse(ce) => { + error!("[{}] failed to parse questions of received message, qclass error: [{}]", peer, ce); + } + } + } + MessageParseError::RecordParse(rp) => { + match rp { + RecordParseError::ShortLength(sl) => { + error!("[{}] failed to parse records of received message, too short: [{} bytes]", peer, sl); + } + RecordParseError::QTypeParse(te) => { + error!("[{}] failed to parse records of received message, qtype error: [{}]", peer, te); + } + RecordParseError::QClassParse(ce) => { + error!("[{}] failed to parse records of received message, qclass error: [{}]", peer, ce); + } + } + } + MessageParseError::RecordCount(expected, actual) => { + error!("[{}] failed to parse records of received message, record count mismatch: [Expected:{}] [Actual:{}]", peer, expected, actual); + } + } +} \ No newline at end of file diff --git a/dnstp/src/processor/request.rs b/dnstp/src/processor/request.rs deleted file mode 100644 index 6fc1140..0000000 --- a/dnstp/src/processor/request.rs +++ /dev/null @@ -1,120 +0,0 @@ -use std::net::Ipv4Addr; -use std::sync::mpsc; -use std::sync::mpsc::{Receiver, Sender}; -use std::thread; -use log::{error, info}; -use crate::config::DomainConfig; - -use crate::message::{DNSMessage, QuestionParseError, RecordParseError}; -use crate::net::{NetworkMessage, NetworkMessagePtr}; -use crate::message_parser::{HeaderParseError, parse_message, RequestParseError}; - -pub struct RequestProcesor { - message_channel: Option>, - domain_config: DomainConfig -} - -impl RequestProcesor { - pub fn new(domain_config: DomainConfig) -> RequestProcesor { - RequestProcesor { - message_channel: None, - domain_config - } - } - - pub fn run(&mut self, sending_channel: Sender) - { - let (tx, rx): (Sender, Receiver) = mpsc::channel(); - self.message_channel = Some(tx); - - let mut base_domain_equality = self.domain_config.base_domain.clone(); - base_domain_equality.insert_str(0, "."); - let base_domain_len = base_domain_equality.len() + 1; - - thread::spawn(move || { - - for m in rx - { - let peer = m.peer.clone(); - - match parse_message(*m) { - Ok(r) => { - info!("received dns message: {:?}", r); - - if r.questions.iter().any(|q| q.qname.ends_with(&base_domain_equality)) - { - let mut response = DNSMessage::a_from_request(&r, |q| Ipv4Addr::from([127, 0, 0, 1])); - - sending_channel.send(Box::from( - NetworkMessage { - buffer: Box::from(response.to_bytes()), - peer: response.peer - } - )); - } - else { - let mut response = DNSMessage::a_from_request(&r, |q| Ipv4Addr::from([127, 0, 0, 1])); - - sending_channel.send(Box::from( - NetworkMessage { - buffer: Box::from(response.to_bytes()), - peer: response.peer - } - )); - } - } - Err(e) => { - match e { - RequestParseError::HeaderParse(he) => { - match he { - HeaderParseError::OpcodeParse(oe) => { - error!("[{}] failed to parse opcode from received message: [{}]", peer, oe); - } - HeaderParseError::ResponseCodeParse(rce) => { - error!("[{}] failed to parse response code error from received message: [{}]", peer, rce); - } - } - } - RequestParseError::QuesionsParse(qe) => { - match qe { - QuestionParseError::ShortLength(sl) => { - error!("[{}] failed to parse questions of received message, too short: [{} bytes]", peer, sl); - } - QuestionParseError::QTypeParse(te) => { - error!("[{}] failed to parse questions of received message, qtype error: [{}]", peer, te); - } - QuestionParseError::QClassParse(ce) => { - error!("[{}] failed to parse questions of received message, qclass error: [{}]", peer, ce); - } - } - } - RequestParseError::RecordParse(rp) => { - match rp { - RecordParseError::ShortLength(sl) => { - error!("[{}] failed to parse records of received message, too short: [{} bytes]", peer, sl); - } - RecordParseError::QTypeParse(te) => { - error!("[{}] failed to parse records of received message, qtype error: [{}]", peer, te); - } - RecordParseError::QClassParse(ce) => { - error!("[{}] failed to parse records of received message, qclass error: [{}]", peer, ce); - } - } - } - RequestParseError::RecordCount(expected, actual) => { - error!("[{}] failed to parse records of received message, record count mismatch: [Expected:{}] [Actual:{}]", peer, expected, actual); - } - } - } - } - } - - info!("message processing thread finishing") - }); - } - - pub fn get_message_channel(&mut self) -> Option> - { - self.message_channel.clone() - } -} \ No newline at end of file diff --git a/dnstp/src/processor/request/encryption.rs b/dnstp/src/processor/request/encryption.rs new file mode 100644 index 0000000..ba52da7 --- /dev/null +++ b/dnstp/src/processor/request/encryption.rs @@ -0,0 +1,16 @@ +use std::net::Ipv4Addr; +use p256::ecdh::EphemeralSecret; +use crate::crypto::{get_random_asym_pair, trim_public_key}; +use crate::message::DNSMessage; + +pub fn get_key_response(request: DNSMessage) -> DNSMessage +{ + DNSMessage::a_resp_from_request(&request, |_| Ipv4Addr::from([127, 0, 0, 1])) +} + +pub fn get_key_request_with_base_domain(base_domain: String) -> (EphemeralSecret, String) +{ + let (private, public) = get_random_asym_pair(); + + (private, vec![trim_public_key(&public), base_domain].join(".")) +} diff --git a/dnstp/src/processor/request/mod.rs b/dnstp/src/processor/request/mod.rs new file mode 100644 index 0000000..56e9e86 --- /dev/null +++ b/dnstp/src/processor/request/mod.rs @@ -0,0 +1,103 @@ +use std::net::Ipv4Addr; +use std::sync::mpsc; +use std::sync::mpsc::{Receiver, Sender}; +use std::thread; +use log::info; +use crate::config::DomainConfig; + +use crate::message::DNSMessage; +use crate::net::{NetworkMessage, NetworkMessagePtr}; +use crate::message_parser::parse_message; +use crate::processor::print_error; + +pub mod encryption; + +#[cfg(test)] +mod tests; + +pub struct RequestProcesor { + message_channel: Option>, + domain_config: DomainConfig, + encryption_endpoint: String +} + +impl RequestProcesor { + pub fn new(domain_config: DomainConfig) -> RequestProcesor { + + let fq_key_endpoint = domain_config.get_fq_key_endpoint(); + RequestProcesor { + message_channel: None, + domain_config, + encryption_endpoint: fq_key_endpoint + } + } + + pub fn run(&mut self, sending_channel: Sender) + { + let (tx, rx): (Sender, Receiver) = mpsc::channel(); + self.message_channel = Some(tx); + + let mut base_domain_equality = self.domain_config.base_domain.clone(); + base_domain_equality.insert_str(0, "."); + let base_domain_len = base_domain_equality.len() + 1; + + let fq_key_endpoint = self.encryption_endpoint.clone(); + + thread::spawn(move || { + + // let fq_key_endpoint = fq_key_endpoint; + + for m in rx + { + let peer = m.peer.clone(); + + match parse_message(*m) { + Ok(r) => { + info!("received dns message: {:?}", r); + + if r.questions.iter().any(|q| q.qname.ends_with(&base_domain_equality)) + { + if r.questions[0].qname.eq_ignore_ascii_case(&fq_key_endpoint) + { + info!("[{}] received encryption key request", peer); + + + } + else + { + let response = DNSMessage::a_resp_from_request(&r, |_| Ipv4Addr::from([127, 0, 0, 1])); + + sending_channel.send(Box::new( + NetworkMessage { + buffer: Box::new(response.to_bytes()), + peer: response.peer + } + )); + } + } + else { + let response = DNSMessage::a_resp_from_request(&r, |_| Ipv4Addr::from([127, 0, 0, 1])); + + sending_channel.send(Box::new( + NetworkMessage { + buffer: Box::new(response.to_bytes()), + peer: response.peer + } + )); + } + } + Err(e) => { + print_error(e, &peer); + } + } + } + + info!("message processing thread finishing") + }); + } + + pub fn get_message_channel(&mut self) -> Option> + { + self.message_channel.clone() + } +} diff --git a/dnstp/src/processor/request/tests.rs b/dnstp/src/processor/request/tests.rs new file mode 100644 index 0000000..76649d8 --- /dev/null +++ b/dnstp/src/processor/request/tests.rs @@ -0,0 +1,15 @@ +use crate::crypto::{fatten_public_key, get_random_asym_pair, trim_public_key}; +use crate::string::encode_domain_name; +use super::*; +use super::encryption::*; + +#[test] +fn encryption() +{ + let (private, public) = get_key_request_with_base_domain(String::from("sarsoo.xyz")); + + let encoded = encode_domain_name(&public); + // let decoded = decode_domain_name(); + + assert_eq!(1, 1); +} \ No newline at end of file diff --git a/dnstp/src/processor/response.rs b/dnstp/src/processor/response.rs deleted file mode 100644 index bce8b9b..0000000 --- a/dnstp/src/processor/response.rs +++ /dev/null @@ -1,89 +0,0 @@ -use std::sync::mpsc; -use std::sync::mpsc::{Receiver, Sender}; -use std::thread; -use log::{error, info}; -use crate::message::{QuestionParseError, RecordParseError}; -use crate::net::raw_request::NetworkMessagePtr; -use crate::message_parser::{HeaderParseError, parse_message, RequestParseError}; - -pub struct ResponseProcesor { - message_channel: Option> -} - -impl ResponseProcesor { - pub fn new() -> ResponseProcesor { - ResponseProcesor{ - message_channel: None - } - } - - pub fn run(&mut self) - { - let (tx, rx): (Sender, Receiver) = mpsc::channel(); - self.message_channel = Some(tx); - - thread::spawn(move || { - - for mut m in rx - { - let peer = m.peer.clone(); - - match parse_message(*m) { - Ok(r) => { - info!("received dns message: {:?}", r); - } - Err(e) => { - match e { - RequestParseError::HeaderParse(he) => { - match he { - HeaderParseError::OpcodeParse(oe) => { - error!("[{}] failed to parse opcode from received message: [{}]", peer, oe); - } - HeaderParseError::ResponseCodeParse(rce) => { - error!("[{}] failed to parse response code error from received message: [{}]", peer, rce); - } - } - } - RequestParseError::QuesionsParse(qe) => { - match qe { - QuestionParseError::ShortLength(sl) => { - error!("[{}] failed to parse questions of received message, too short: [{} bytes]", peer, sl); - } - QuestionParseError::QTypeParse(te) => { - error!("[{}] failed to parse questions of received message, qtype error: [{}]", peer, te); - } - QuestionParseError::QClassParse(ce) => { - error!("[{}] failed to parse questions of received message, qclass error: [{}]", peer, ce); - } - } - } - RequestParseError::RecordParse(rp) => { - match rp { - RecordParseError::ShortLength(sl) => { - error!("[{}] failed to parse records of received message, too short: [{} bytes]", peer, sl); - } - RecordParseError::QTypeParse(te) => { - error!("[{}] failed to parse records of received message, qtype error: [{}]", peer, te); - } - RecordParseError::QClassParse(ce) => { - error!("[{}] failed to parse records of received message, qclass error: [{}]", peer, ce); - } - } - } - RequestParseError::RecordCount(expected, actual) => { - error!("[{}] failed to parse records of received message, record count mismatch: [Expected:{}] [Actual:{}]", peer, expected, actual); - } - } - } - } - } - - info!("message processing thread finishing") - }); - } - - pub fn get_message_channel(&mut self) -> Option> - { - self.message_channel.clone() - } -} \ No newline at end of file diff --git a/dnstp/src/processor/response/mod.rs b/dnstp/src/processor/response/mod.rs new file mode 100644 index 0000000..a556cd1 --- /dev/null +++ b/dnstp/src/processor/response/mod.rs @@ -0,0 +1,50 @@ +use std::sync::mpsc; +use std::sync::mpsc::{Receiver, Sender}; +use std::thread; +use log::{error, info}; +use crate::message::{QuestionParseError, RecordParseError}; +use crate::net::raw_request::NetworkMessagePtr; +use crate::message_parser::{HeaderParseError, parse_message, MessageParseError}; +use crate::processor::print_error; + +pub struct ResponseProcesor { + message_channel: Option> +} + +impl ResponseProcesor { + pub fn new() -> ResponseProcesor { + ResponseProcesor{ + message_channel: None + } + } + + pub fn run(&mut self) + { + let (tx, rx): (Sender, Receiver) = mpsc::channel(); + self.message_channel = Some(tx); + + thread::spawn(move || { + + for mut m in rx + { + let peer = m.peer.clone(); + + match parse_message(*m) { + Ok(r) => { + info!("received dns message: {:?}", r); + } + Err(e) => { + print_error(e, &peer); + } + } + } + + info!("message processing thread finishing") + }); + } + + pub fn get_message_channel(&mut self) -> Option> + { + self.message_channel.clone() + } +} \ No newline at end of file