working key handshake, client subcommands, just need to start encrypting and sending content

This commit is contained in:
Andy Pack 2024-02-11 22:30:07 +00:00
parent 6e954a9795
commit 6cef74968c
Signed by: sarsoo
GPG Key ID: A55BA3536A5E0ED7
22 changed files with 669 additions and 189 deletions

View File

@ -0,0 +1,6 @@
use crate::NetSettings;
pub fn download(net_settings: NetSettings)
{
}

View File

@ -1,32 +1,58 @@
//! # Client Side //! # Client Side
//! //!
use std::fs::OpenOptions; mod test;
use std::net::SocketAddr; mod upload;
use std::thread; mod download;
use std::time::Duration;
use clap::Parser;
use log::{info, LevelFilter};
use rand::RngCore;
use simplelog::*;
use dnstplib::DomainConfig;
use dnstplib::message::DNSMessage; use std::fs::OpenOptions;
use dnstplib::net::{DNSSocket, NetworkMessage}; use clap::{Parser, Subcommand};
use dnstplib::processor::ResponseProcesor; use log::{LevelFilter};
use simplelog::*;
use crate::download::download;
use crate::test::send_test_requests;
use crate::upload::upload;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)] #[command(author, version, about, long_about = None)]
struct Args { struct Args {
/// Addresses to send requests #[clap(subcommand)]
command: Command,
}
#[derive(Debug, Subcommand)]
enum Command {
/// Send test requests on loop to the server
Test {
#[clap(flatten)]
net_options: NetSettings
},
/// Upload data to the remote server
Upload {
#[clap(flatten)]
net_options: NetSettings,
#[arg(short, long)]
value: String
},
/// Download a payload from the remote server
Download {
#[clap(flatten)]
net_options: NetSettings
}
}
#[derive(Parser, Debug)]
struct NetSettings {
/// Server address to send requests to
#[arg(short, long)] #[arg(short, long)]
address: String, address: String,
/// Base domain to operate on /// Base domain server is operating on
#[arg(long)] #[arg(long)]
base_domain: String, base_domain: String,
/// Sub-domain to handle key handling when requested /// Sub-domain to handle key handling when requested
#[arg(long, default_value = "static")] #[arg(short, long, default_value = "static")]
key_endpoint: String key_endpoint: String,
} }
fn main() { fn main() {
@ -44,40 +70,15 @@ fn main() {
let args = Args::parse(); let args = Args::parse();
let address = SocketAddr::from(([127, 0, 0, 1], 0)); match args.command {
Command::Test { net_options } => {
let mut socket = DNSSocket::new(vec!(address)); send_test_requests(net_options);
socket.bind(); }
socket.run_tx(); Command::Upload { net_options, value } => {
upload(net_options, value);
let tx_channel = socket.get_tx_message_channel().unwrap(); }
Command::Download { net_options } => {
let mut processor = ResponseProcesor::new(); download(net_options);
processor.run(); }
socket.run_rx(processor.get_message_channel().expect("couldn't get message processing channel"));
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::req_from_hostname(address, rng.next_u32() as u16, domain.clone());
let bytes = message.to_bytes();
tx_channel.send(Box::new(NetworkMessage {
buffer: Box::new(bytes),
peer: args.address.parse().unwrap()
}));
thread::sleep(Duration::from_secs(1));
} }
} }

52
dnstp-client/src/test.rs Normal file
View File

@ -0,0 +1,52 @@
use std::net::SocketAddr;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
use log::info;
use rand::RngCore;
use dnstplib::client_crypto_context::ClientCryptoContext;
use dnstplib::DomainConfig;
use dnstplib::message::DNSMessage;
use dnstplib::net::{DNSSocket, NetworkMessage};
use dnstplib::processor::ResponseProcesor;
use crate::NetSettings;
pub fn send_test_requests(args: NetSettings)
{
let address = SocketAddr::from(([127, 0, 0, 1], 0));
let mut socket = DNSSocket::new(vec!(address));
socket.bind();
socket.run_tx();
let tx_channel = socket.get_tx_message_channel().unwrap();
let crypto_context = Arc::new(Mutex::new(ClientCryptoContext::new()));
let mut processor = ResponseProcesor::new(crypto_context.clone());
processor.run();
socket.run_rx(processor.get_message_channel().expect("couldn't get message processing channel"));
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::req_from_hostname(address, rng.next_u32() as u16, domain.clone());
let bytes = message.to_bytes();
tx_channel.send(Box::new(NetworkMessage {
buffer: Box::new(bytes),
peer: args.address.parse().unwrap()
}));
thread::sleep(Duration::from_secs(1));
}
}

View File

@ -0,0 +1,81 @@
use std::net::SocketAddr;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
use log::info;
use rand::RngCore;
use rand::rngs::OsRng;
use dnstplib::client_crypto_context::ClientCryptoContext;
use dnstplib::{DomainConfig, send_message};
use dnstplib::message::{Direction, DNSHeader, DNSMessage, DNSQuestion, Opcode, QClass, QType, ResponseCode};
use dnstplib::net::DNSSocket;
use dnstplib::processor::ResponseProcesor;
use crate::NetSettings;
pub fn upload(net_settings: NetSettings, value: String)
{
let address = SocketAddr::from(([127, 0, 0, 1], 0));
let mut socket = DNSSocket::new(vec!(address));
socket.bind();
socket.run_tx();
let tx_channel = socket.get_tx_message_channel().unwrap();
let crypto_context = Arc::new(Mutex::new(ClientCryptoContext::new()));
let mut processor = ResponseProcesor::new(crypto_context.clone());
processor.run();
socket.run_rx(processor.get_message_channel().expect("couldn't get message processing channel"));
let domain_config = DomainConfig {
base_domain: net_settings.base_domain,
key_endpoint: net_settings.key_endpoint
};
info!("sending handshake...");
let message = DNSMessage {
header: DNSHeader {
id: OsRng.next_u32() as u16,
direction: Direction::Request,
opcode: Opcode::Query,
authoritative: false,
truncation: false,
recursion_desired: false,
recursion_available: false,
valid_zeroes: true,
response: ResponseCode::NoError,
question_count: 2,
answer_record_count: 0,
authority_record_count: 0,
additional_record_count: 0,
},
questions: vec![
DNSQuestion {
qname: domain_config.get_fq_key_endpoint(),
qtype: QType::A,
qclass: QClass::Internet,
},
DNSQuestion {
qname: crypto_context.lock().unwrap().get_public_key_domain(&domain_config.base_domain),
qtype: QType::A,
qclass: QClass::Internet,
}
],
answer_records: vec![],
authority_records: vec![],
additional_records: vec![],
peer: net_settings.address.parse().unwrap(),
};
send_message(message, &tx_channel);
while !crypto_context.lock().unwrap().is_complete() {
info!("waiting for crypto completion...");
thread::sleep(Duration::from_millis(100));
}
info!("crypto complete, sending data");
}

View File

@ -0,0 +1,39 @@
use aes_gcm_siv::Aes256GcmSiv;
use p256::ecdh::EphemeralSecret;
use crate::crypto::{get_random_asym_pair, trim_public_key};
use crate::string::append_base_domain_to_key;
/// Represents the server from the perspective of a client
pub struct ClientCryptoContext {
pub shared_key: Option<Aes256GcmSiv>,
pub client_private: EphemeralSecret,
pub client_public: String,
pub server_public: Option<String>
}
impl ClientCryptoContext {
pub fn new() -> Self {
let (client_private, client_public) = get_random_asym_pair();
Self {
shared_key: None,
client_private,
client_public,
server_public: None
}
}
pub fn is_complete(&self) -> bool
{
self.server_public.is_some() && self.shared_key.is_some()
}
pub fn get_public_key_domain(&self, base_domain: &String) -> String
{
append_base_domain_to_key(
trim_public_key(&self.client_public),
base_domain
)
}
}

View File

@ -1,29 +1,44 @@
//! Structures for managing the state of connected clients from the perspective of the server
use std::collections::HashMap; use std::collections::HashMap;
use std::time::SystemTime; use std::time::SystemTime;
use aes_gcm_siv::Aes256GcmSiv; use aes_gcm_siv::Aes256GcmSiv;
/// A single client including when they connected and their shared cryptographic key
pub struct Client { pub struct Client {
pub first_seen: SystemTime, pub first_seen: SystemTime,
pub last_seen: SystemTime,
pub shared_key: Aes256GcmSiv pub shared_key: Aes256GcmSiv
} }
impl Client { impl Client {
/// Create a new client as
pub fn new(shared_key: Aes256GcmSiv) -> Client pub fn new(shared_key: Aes256GcmSiv) -> Client
{ {
let time = SystemTime::now();
Client { Client {
first_seen: SystemTime::now(), first_seen: time,
last_seen: time,
shared_key shared_key
} }
} }
pub fn bump_last_seen(&mut self)
{
self.last_seen = SystemTime::now();
}
} }
/// Container for managing connected clients and their keys
pub struct Clients { pub struct Clients {
client_map: HashMap<String, Client> client_map: HashMap<String, Client>
} }
impl Clients { impl Clients {
/// Create a new collection of clients
pub fn new() -> Clients pub fn new() -> Clients
{ {
Clients { Clients {
@ -36,8 +51,26 @@ impl Clients {
// self.client_map.insert(client_id, Client::new(shared_key)); // self.client_map.insert(client_id, Client::new(shared_key));
// } // }
/// Add a newly connected client to the collection of connections. Index the client by public key.
pub fn add(&mut self, client_id: String, client:Client) pub fn add(&mut self, client_id: String, client:Client)
{ {
self.client_map.insert(client_id, client); self.client_map.insert(client_id, client);
} }
pub fn client_is_connected(&self, client_id: &String) -> bool
{
self.client_map.contains_key(client_id)
}
pub fn bump_last_seen(&mut self, client_id: &String) -> Result<(), ()>
{
match self.client_map.get_mut(client_id)
{
None => Err(()),
Some(client) => {
client.bump_last_seen();
Ok(())
}
}
}
} }

View File

@ -37,9 +37,9 @@ pub fn get_random_asym_pair() -> (EphemeralSecret, String)
} }
/// Use one private key and an opposing public key to arrive at the same shared secret /// 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<SharedSecret<NistP256>, ()> { pub fn get_shared_asym_secret(secret: &EphemeralSecret, opposing_public_key: &String) -> Result<SharedSecret<NistP256>, ()> {
match PublicKey::from_str(&opposing_public_key) { match PublicKey::from_str(opposing_public_key) {
Ok(other_public) => { Ok(other_public) => {
Ok(secret.diffie_hellman(&other_public)) Ok(secret.diffie_hellman(&other_public))
} }

View File

@ -5,8 +5,8 @@ fn matching_shared_secrets() {
let (secret_alice, point_alice) = get_random_asym_pair(); let (secret_alice, point_alice) = get_random_asym_pair();
let (secret_bob, point_bob) = get_random_asym_pair(); let (secret_bob, point_bob) = get_random_asym_pair();
let shared_alice = get_shared_asym_secret(secret_alice, point_bob).unwrap(); let shared_alice = get_shared_asym_secret(&secret_alice, &point_bob).unwrap();
let shared_bob = get_shared_asym_secret(secret_bob, point_alice).unwrap(); let shared_bob = get_shared_asym_secret(&secret_bob, &point_alice).unwrap();
assert_eq!(shared_alice.raw_secret_bytes(), shared_bob.raw_secret_bytes()); assert_eq!(shared_alice.raw_secret_bytes(), shared_bob.raw_secret_bytes());
} }
@ -19,8 +19,8 @@ fn arbitrary_string_back_and_forth() {
let (secret_alice, point_alice) = get_random_asym_pair(); let (secret_alice, point_alice) = get_random_asym_pair();
let (secret_bob, point_bob) = get_random_asym_pair(); let (secret_bob, point_bob) = get_random_asym_pair();
let shared_alice = get_shared_asym_secret(secret_alice, point_bob).unwrap(); let shared_alice = get_shared_asym_secret(&secret_alice, &point_bob).unwrap();
let shared_bob = get_shared_asym_secret(secret_bob, point_alice).unwrap(); let shared_bob = get_shared_asym_secret(&secret_bob, &point_alice).unwrap();
assert_eq!(shared_alice.raw_secret_bytes(), shared_bob.raw_secret_bytes()); assert_eq!(shared_alice.raw_secret_bytes(), shared_bob.raw_secret_bytes());

View File

@ -10,6 +10,35 @@ pub mod net;
pub mod string; pub mod string;
pub mod config; pub mod config;
pub mod crypto; pub mod crypto;
mod clients; pub mod clients;
pub mod client_crypto_context;
use std::sync::mpsc::{Sender};
use log::error;
pub use config::DomainConfig; pub use config::DomainConfig;
use crate::message::DNSMessage;
use crate::net::{NetworkMessage, NetworkMessagePtr};
#[repr(u8)]
#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub enum RequestError {
/// Trying to perform an operation without having handshaked first
NoHandshake,
WrongNumberOfQuestions,
CryptoFailure
}
pub fn send_message(response: DNSMessage, sending_channel: &Sender<NetworkMessagePtr>)
{
match sending_channel.send(Box::new(
NetworkMessage {
buffer: Box::new(response.to_bytes()),
peer: response.peer
}
)){
Ok(_) => {}
Err(e) => {
error!("failed to pass a message to the network layer for delivery [{}]", e.to_string());
}
}
}

View File

@ -1,5 +1,6 @@
use std::net::{Ipv4Addr, SocketAddr}; use std::net::{Ipv4Addr, SocketAddr};
use crate::message::{DNSQuestion, DNSHeader, questions_to_bytes, Direction, ResponseCode, QType, QClass, ResourceRecord, records_to_bytes, ARdata}; use crate::message::{DNSQuestion, DNSHeader, questions_to_bytes, Direction, ResponseCode, QType, QClass, ResourceRecord, records_to_bytes, ARdata, TXTRdata, RData};
use crate::RequestError;
/// A DNS message which can be used as either a request or response based on its direction and composition /// A DNS message which can be used as either a request or response based on its direction and composition
#[derive(Debug)] #[derive(Debug)]
@ -108,6 +109,26 @@ impl DNSMessage {
response response
} }
pub fn dumb_resp_from_request(&self) -> DNSMessage
{
let mut response = DNSMessage{
header: self.header.clone(),
questions: self.questions.clone(),
answer_records: vec![],
authority_records: vec![],
additional_records: vec![],
peer: self.peer
};
response.header.direction = Direction::Response;
response.header.response = ResponseCode::NotImplemented;
response.header.answer_record_count = 0;
response.header.authority_record_count = 0;
response.header.additional_record_count = 0;
response
}
pub fn empty_resp_from_request(&self) -> DNSMessage pub fn empty_resp_from_request(&self) -> DNSMessage
{ {
let mut response = DNSMessage{ let mut response = DNSMessage{
@ -131,4 +152,33 @@ impl DNSMessage {
response response
} }
pub fn protocol_error_from_request(&self, error_code: RequestError) -> DNSMessage
{
let txt = Box::new(TXTRdata::from(String::new()));
let mut response = DNSMessage{
header: self.header.clone(),
questions: self.questions.clone(),
answer_records: vec![ResourceRecord {
name_offset: 12,
answer_type: QType::TXT,
class: QClass::Internet,
ttl: 0,
rd_length: txt.to_bytes().len() as u16,
r_data: txt
}],
authority_records: vec![],
additional_records: vec![],
peer: self.peer
};
response.header.direction = Direction::Response;
response.header.response = ResponseCode::ServerFailure;
response.header.answer_record_count = 1;
response.header.authority_record_count = 0;
response.header.additional_record_count = 0;
response
}
} }

View File

@ -17,7 +17,6 @@ pub use cname_rdata::CnameRdata;
mod tests; mod tests;
use std::fmt::Debug; use std::fmt::Debug;
use std::fmt::Display;
use crate::byte::{four_byte_combine, four_byte_split, push_split_bytes, two_byte_combine}; use crate::byte::{four_byte_combine, four_byte_split, push_split_bytes, two_byte_combine};
use crate::message::question::{DNSQuestion, QClass, QType}; use crate::message::question::{DNSQuestion, QClass, QType};

View File

View File

@ -2,17 +2,23 @@ use std::net::Ipv4Addr;
use p256::ecdh::EphemeralSecret; use p256::ecdh::EphemeralSecret;
use crate::clients::Client; use crate::clients::Client;
use crate::crypto::{asym_to_sym_key, fatten_public_key, get_random_asym_pair, get_shared_asym_secret, trim_public_key}; use crate::crypto::{asym_to_sym_key, fatten_public_key, get_random_asym_pair, get_shared_asym_secret, trim_public_key};
use crate::message::{ARdata, DNSMessage, DNSQuestion, QClass, QType, ResourceRecord}; use crate::message::{ARdata, DNSMessage, QClass, QType, ResourceRecord};
use crate::message::record::CnameRdata; use crate::message::record::CnameRdata;
use crate::string::{append_base_domain_to_key, strip_base_domain_from_key}; use crate::string::{append_base_domain_to_key, encode_domain_name, strip_base_domain_from_key};
/// Result of a client's handshake request including server key pair and prepared response
pub struct KeySwapContext { pub struct KeySwapContext {
/// New client structure to track derived shared secret and last seen time
pub new_client: Client, pub new_client: Client,
/// Response message to send to the client with the server's public key
pub response: DNSMessage, pub response: DNSMessage,
/// Public key of the server's key pair
pub server_public: String, pub server_public: String,
/// Public key extracted from the client's request
pub client_public: String pub client_public: String
} }
/// Generate a random asymmetric key pair, append the dnstp base domain to the secret
pub fn get_key_request_with_base_domain(base_domain: String) -> (EphemeralSecret, String) pub fn get_key_request_with_base_domain(base_domain: String) -> (EphemeralSecret, String)
{ {
let (private, public) = get_random_asym_pair(); let (private, public) = get_random_asym_pair();
@ -20,9 +26,10 @@ pub fn get_key_request_with_base_domain(base_domain: String) -> (EphemeralSecret
(private, append_base_domain_to_key(trim_public_key(&public), &base_domain)) (private, append_base_domain_to_key(trim_public_key(&public), &base_domain))
} }
pub fn get_fattened_public_key(key_question: &DNSQuestion) -> (String, String) /// Extract the client's public key from the DNS message, turn the hostname back into the full fat public key with --- BEGIN KEY --- headers and trailers
pub fn get_fattened_public_key(key_question: &String) -> (String, String)
{ {
let public_key = &key_question.qname; let public_key = key_question;
let (trimmed_public_key, base_domain) = strip_base_domain_from_key(public_key); let (trimmed_public_key, base_domain) = strip_base_domain_from_key(public_key);
(fatten_public_key(&trimmed_public_key), base_domain) (fatten_public_key(&trimmed_public_key), base_domain)
@ -36,7 +43,10 @@ pub enum KeyDecodeError {
SharedSecretDerivation, SharedSecretDerivation,
} }
pub fn decode_key_request(message: DNSMessage) -> Result<KeySwapContext, KeyDecodeError> /// Take a client's handshake request, process the crypto and prepare a response
///
/// Includes generating a server key pair, using the public key in the response, deriving the shared secret.
pub fn decode_key_request(message: &DNSMessage) -> Result<KeySwapContext, KeyDecodeError>
{ {
if message.questions.len() == 2 { if message.questions.len() == 2 {
@ -52,18 +62,20 @@ pub fn decode_key_request(message: DNSMessage) -> Result<KeySwapContext, KeyDeco
return Err(KeyDecodeError::SecondQuestionNotA(key_question.qtype)); return Err(KeyDecodeError::SecondQuestionNotA(key_question.qtype));
} }
let (fattened_public_key, base_domain) = get_fattened_public_key(&key_question); // key is transmitted wihout --- BEGIN KEY -- header and trailer bits and with '.' instead of new lines
let (fattened_public_key, base_domain) = get_fattened_public_key(&key_question.qname);
// generate the servers public/private key pair
let (server_private, server_public) = get_random_asym_pair(); let (server_private, server_public) = get_random_asym_pair();
let shared_secret = get_shared_asym_secret(server_private, fattened_public_key); // do the Diffie-Hellman shared secret derivation for the server side symmetric encryption
match get_shared_asym_secret(&server_private, &fattened_public_key) {
match shared_secret {
Ok(secret) => { Ok(secret) => {
let sym_key = asym_to_sym_key(&secret); let sym_key = asym_to_sym_key(&secret);
let new_client = Client::new(sym_key); let new_client = Client::new(sym_key);
let mut response = message.empty_resp_from_request(); let mut response = message.empty_resp_from_request();
// return an empty null response for the key hostname (static.BLANK.TLD) question, not that important
let first_record = ResourceRecord { let first_record = ResourceRecord {
name_offset: 12, name_offset: 12,
answer_type: QType::A, answer_type: QType::A,
@ -73,22 +85,25 @@ pub fn decode_key_request(message: DNSMessage) -> Result<KeySwapContext, KeyDeco
r_data: Box::new(ARdata::from(Ipv4Addr::from([127,0,0,1]))) r_data: Box::new(ARdata::from(Ipv4Addr::from([127,0,0,1])))
}; };
let server_public_domain = append_base_domain_to_key(
trim_public_key(&server_public),
&base_domain
);
// for the public key question that the client sent, respond with the server's public key
let second_record = ResourceRecord { let second_record = ResourceRecord {
name_offset: 12 + (&message.questions[0]).to_bytes().len() as u16, name_offset: 12 + (&message.questions[0]).to_bytes().len() as u16,
answer_type: QType::CNAME, answer_type: QType::CNAME,
class: QClass::Internet, class: QClass::Internet,
ttl: 0, ttl: 0,
rd_length: 4, rd_length: encode_domain_name(&server_public_domain).len() as u16,
r_data: Box::new( r_data: Box::new(
CnameRdata::from( CnameRdata::from(
append_base_domain_to_key( server_public_domain
trim_public_key(&server_public),
&base_domain
)
) )
) )
}; };
response.header.answer_record_count = 2;
response.answer_records = vec![ response.answer_records = vec![
first_record, second_record first_record, second_record
]; ];

View File

@ -1,4 +1,4 @@
use std::net::Ipv4Addr; use std::net::SocketAddr;
use std::sync::{Arc, mpsc, Mutex}; use std::sync::{Arc, mpsc, Mutex};
use std::sync::mpsc::{Receiver, Sender}; use std::sync::mpsc::{Receiver, Sender};
use std::thread; use std::thread;
@ -6,16 +6,19 @@ use log::{error, info};
use crate::clients::Clients; use crate::clients::Clients;
use crate::config::DomainConfig; use crate::config::DomainConfig;
use crate::message::DNSMessage; use crate::message::{DNSMessage, QType};
use crate::net::{NetworkMessage, NetworkMessagePtr}; use crate::net::{NetworkMessagePtr};
use crate::message_parser::parse_message; use crate::message_parser::parse_message;
use crate::processor::print_error; use crate::processor::print_error;
use crate::processor::request::encryption::{decode_key_request, KeyDecodeError}; use crate::processor::request::encryption::{decode_key_request, KeyDecodeError};
use crate::{RequestError, send_message};
pub mod encryption; pub mod encryption;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
mod upload;
mod download;
pub struct RequestProcesor { pub struct RequestProcesor {
message_channel: Option<Sender<NetworkMessagePtr>>, message_channel: Option<Sender<NetworkMessagePtr>>,
@ -37,6 +40,7 @@ impl RequestProcesor {
} }
} }
/// Spin a thread to process parsed DNS requests and respond as appropriate
pub fn run(&mut self, sending_channel: Sender<NetworkMessagePtr>) pub fn run(&mut self, sending_channel: Sender<NetworkMessagePtr>)
{ {
let (tx, rx): (Sender<NetworkMessagePtr>, Receiver<NetworkMessagePtr>) = mpsc::channel(); let (tx, rx): (Sender<NetworkMessagePtr>, Receiver<NetworkMessagePtr>) = mpsc::channel();
@ -50,8 +54,6 @@ impl RequestProcesor {
thread::spawn(move || { thread::spawn(move || {
let clients = clients;
for m in rx for m in rx
{ {
let peer = m.peer.clone(); let peer = m.peer.clone();
@ -60,24 +62,78 @@ impl RequestProcesor {
Ok(r) => { Ok(r) => {
info!("received dns message: {:?}", r); info!("received dns message: {:?}", r);
// If there is a question containing the protocol base domain, treat it as a dnstp request
// (handshake, upload, download) and handle as such
if r.questions.iter().any(|q| q.qname.ends_with(&base_domain_equality)) 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) Self::handle_dnstp_request(r, &sending_channel, &clients, peer, &fq_key_endpoint);
}
else // otherwise it's for something else and reply with some dumb empty answer
{
send_message(DNSMessage::dumb_resp_from_request(&r), &sending_channel);
}
}
Err(e) => {
print_error(e, &peer);
}
}
}
info!("message processing thread finishing")
});
}
/// The message is trying to do some dnstp, work out whether it's hello handshaking, uplaoding or downloading data and handoff the message to that workflow
fn handle_dnstp_request(r: DNSMessage, sending_channel: &Sender<NetworkMessagePtr>, clients: &Arc<Mutex<Clients>>, peer: SocketAddr, fq_key_endpoint: &String)
{
// if the first question is for the key domain (static.BLANK.TLD) it's a handshake for swapping keys
if r.questions[0].qname.eq_ignore_ascii_case(fq_key_endpoint)
{
Self::handle_encryption_handshake(r, sending_channel, clients, peer);
}
// if we're not handshaking then the client should be known to the server
else if clients.lock().unwrap().client_is_connected(&r.questions[0].qname) {
info!("[{}] received request from known client", peer);
// for now lets deal with three questions, first one is the client id, second is the actual request, third is the nonce
if r.questions.len() == 3
{
match r.questions[1].qtype {
QType::A => {
Self::handle_upload_request(r, sending_channel, clients, peer);
}
QType::CNAME => { // makes sense for a cname to return lots of text
Self::handle_download_request(r, sending_channel, clients, peer);
}
_ => {}
}
}
else
{
Self::send_protocol_error(RequestError::WrongNumberOfQuestions, &r, &sending_channel);
}
}
// otherwise return protocol error for trying to do something without handshaking
else
{
Self::send_protocol_error(RequestError::NoHandshake, &r, &sending_channel);
}
}
/// Process a hello message from a new client with a public key and send the response.
///
/// Generate a key pair and repspond with the public key, generate the shared secret and store this in the connected clients.
fn handle_encryption_handshake(r: DNSMessage, sending_channel: &Sender<NetworkMessagePtr>, clients: &Arc<Mutex<Clients>>, peer: SocketAddr)
{ {
info!("[{}] received encryption key request", peer); info!("[{}] received encryption key request", peer);
match decode_key_request(r) // crypto bulk happens in decode, result includes message to be responded with
match decode_key_request(&r)
{ {
Ok(context) => { Ok(context) => {
clients.lock().unwrap().add(context.client_public, context.new_client); clients.lock().unwrap().add(context.client_public, context.new_client);
sending_channel.send(Box::new( send_message(context.response, &sending_channel);
NetworkMessage {
buffer: Box::new(context.response.to_bytes()),
peer: context.response.peer
}
));
} }
Err(e) => { Err(e) => {
match e { match e {
@ -94,44 +150,33 @@ impl RequestProcesor {
error!("[{}] failed to parse public key, failed to derived shared secret", peer); error!("[{}] failed to parse public key, failed to derived shared secret", peer);
} }
} }
Self::send_protocol_error(RequestError::CryptoFailure, &r, &sending_channel);
} }
} }
} }
else
fn handle_download_request(r: DNSMessage, sending_channel: &Sender<NetworkMessagePtr>, clients: &Arc<Mutex<Clients>>, peer: SocketAddr)
{ {
let response = DNSMessage::a_resp_from_request(&r, |_| Ipv4Addr::from([127, 0, 0, 1])); info!("[{}] received download request", peer);
let client_id = &r.questions[0].qname;
sending_channel.send(Box::new( clients.lock().unwrap().bump_last_seen(client_id);
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") fn handle_upload_request(r: DNSMessage, sending_channel: &Sender<NetworkMessagePtr>, clients: &Arc<Mutex<Clients>>, peer: SocketAddr)
}); {
info!("[{}] received upload request", peer);
let client_id = &r.questions[0].qname;
clients.lock().unwrap().bump_last_seen(client_id);
} }
pub fn get_message_channel(&mut self) -> Option<Sender<NetworkMessagePtr>> pub fn get_message_channel(&mut self) -> Option<Sender<NetworkMessagePtr>>
{ {
self.message_channel.clone() self.message_channel.clone()
} }
pub fn send_protocol_error(error_code: RequestError, r: &DNSMessage, sending_channel: &Sender<NetworkMessagePtr>)
{
send_message(DNSMessage::protocol_error_from_request(&r, error_code), sending_channel);
}
} }

View File

@ -1,4 +1,3 @@
use crate::crypto::{fatten_public_key, get_random_asym_pair, trim_public_key};
use crate::string::encode_domain_name; use crate::string::encode_domain_name;
use super::*; use super::*;
use super::encryption::*; use super::encryption::*;

View File

View File

@ -0,0 +1,38 @@
use std::sync::{Arc, Mutex};
use crate::client_crypto_context::ClientCryptoContext;
use crate::crypto::{asym_to_sym_key, get_shared_asym_secret};
use crate::message::DNSMessage;
use crate::processor::request::encryption::get_fattened_public_key;
use crate::string::decode_domain_name;
pub fn decode_key_response(message: &DNSMessage, client_crypto_context: Arc<Mutex<ClientCryptoContext>>)
{
if message.answer_records.len() == 2 {
// if message.questions[0].qtype != QType::A
// {
// return Err(KeyDecodeError::FirstQuestionNotA(message.questions[0].qtype));
// }
let key_answer = &message.answer_records[1];
// if key_answer.answer_type != QType::A
// {
// return Err(KeyDecodeError::SecondQuestionNotA(key_answer.answer_type));
// }
let data_string = decode_domain_name(key_answer.r_data.to_bytes());
// key is transmitted wihout --- BEGIN KEY -- header and trailer bits and with '.' instead of new lines
let (fattened_public_key, _) = get_fattened_public_key(&data_string);
let mut context = client_crypto_context.lock().unwrap();
match get_shared_asym_secret(&context.client_private, &fattened_public_key)
{
Ok(k) => {
context.server_public = Some(fattened_public_key);
context.shared_key = Some(asym_to_sym_key(&k));
}
Err(_) => {}
}
}
}

View File

@ -1,19 +1,25 @@
use std::sync::mpsc; mod encryption;
use std::sync::{Arc, mpsc, Mutex};
use std::sync::mpsc::{Receiver, Sender}; use std::sync::mpsc::{Receiver, Sender};
use std::thread; use std::thread;
use log::info; use log::info;
use crate::client_crypto_context::ClientCryptoContext;
use crate::net::raw_request::NetworkMessagePtr; use crate::net::raw_request::NetworkMessagePtr;
use crate::message_parser::parse_message; use crate::message_parser::parse_message;
use crate::processor::print_error; use crate::processor::print_error;
use crate::processor::response::encryption::decode_key_response;
pub struct ResponseProcesor { pub struct ResponseProcesor {
message_channel: Option<Sender<NetworkMessagePtr>> message_channel: Option<Sender<NetworkMessagePtr>>,
crypto_context: Arc<Mutex<ClientCryptoContext>>
} }
impl ResponseProcesor { impl ResponseProcesor {
pub fn new() -> ResponseProcesor { pub fn new(crypto_context: Arc<Mutex<ClientCryptoContext>>) -> ResponseProcesor {
ResponseProcesor{ ResponseProcesor{
message_channel: None message_channel: None,
crypto_context
} }
} }
@ -22,6 +28,8 @@ impl ResponseProcesor {
let (tx, rx): (Sender<NetworkMessagePtr>, Receiver<NetworkMessagePtr>) = mpsc::channel(); let (tx, rx): (Sender<NetworkMessagePtr>, Receiver<NetworkMessagePtr>) = mpsc::channel();
self.message_channel = Some(tx); self.message_channel = Some(tx);
let crypto_context = self.crypto_context.clone();
thread::spawn(move || { thread::spawn(move || {
for m in rx for m in rx
@ -31,6 +39,8 @@ impl ResponseProcesor {
match parse_message(*m) { match parse_message(*m) {
Ok(r) => { Ok(r) => {
info!("received dns message: {:?}", r); info!("received dns message: {:?}", r);
decode_key_response(&r, crypto_context.clone());
} }
Err(e) => { Err(e) => {
print_error(e, &peer); print_error(e, &peer);

View File

@ -1,45 +0,0 @@
//! Utility functions for manipulating strings
use urlencoding::encode;
pub fn encode_domain_name(name: &String) -> Vec<u8>
{
let mut ret: Vec<u8> = Vec::with_capacity(name.len() + 3);
for part in name.split(".")
{
let encoded_string = encode(part);
let count = encoded_string.len();
ret.push(count as u8);
for x in encoded_string.bytes() {
ret.push(x);
};
}
ret.push(0);
ret
}
pub fn strip_base_domain_from_key(public_key: &String) -> (String, String)
{
let periods: Vec<_> = public_key.rmatch_indices(".").collect();
if periods.len() >= 2 {
(public_key[0 .. periods[1].0].to_string(),
public_key[periods[1].0 .. ].to_string())
}
else if periods.len() == 1 {
(public_key[0 .. periods[0].0].to_string(),
public_key[periods[0].0 .. ].to_string())
}
else {
(public_key.to_string(), String::new())
}
}
pub fn append_base_domain_to_key(trimmed_key: String, base_domain: &String) -> String
{
vec![trimmed_key, base_domain.to_string()].join(".")
}

89
dnstp/src/string/mod.rs Normal file
View File

@ -0,0 +1,89 @@
//! Utility functions for manipulating strings
#[cfg(test)]
mod tests;
use urlencoding::{decode, encode};
pub fn encode_domain_name(name: &String) -> Vec<u8>
{
let mut ret: Vec<u8> = Vec::with_capacity(name.len() + 3);
for part in name.split(".")
{
let encoded_string = encode(part);
let count = encoded_string.len();
ret.push(count as u8);
for x in encoded_string.bytes() {
ret.push(x);
};
}
ret.push(0);
ret
}
pub fn decode_domain_name(name: Vec<u8>) -> String
{
let mut full_domain: String = String::new();
let mut current_query: Vec<u8> = Vec::with_capacity(10);
let mut current_length: Option<u8> = None;
let mut remaining_length: u8 = 0;
for char in name {
match current_length {
None => {
current_length = Some(char);
remaining_length = char;
}
Some(_) => {
if remaining_length == 0 {
let current_part = String::from_utf8(current_query.clone()).unwrap();
let url_decoded = decode(current_part.as_str()).unwrap();
full_domain.push_str(&url_decoded.to_string());
if char != 0 {
full_domain.push('.');
}
current_query.clear();
current_length = Some(char);
remaining_length = char;
}
else {
current_query.push(char);
remaining_length -= 1;
}
}
}
}
full_domain
}
pub fn strip_base_domain_from_key(public_key: &String) -> (String, String)
{
let periods: Vec<_> = public_key.rmatch_indices(".").collect();
if periods.len() >= 2 {
(public_key[0 .. periods[1].0].to_string(),
public_key[periods[1].0 .. ].to_string())
}
else if periods.len() == 1 {
(public_key[0 .. periods[0].0].to_string(),
public_key[periods[0].0 .. ].to_string())
}
else {
(public_key.to_string(), String::new())
}
}
pub fn append_base_domain_to_key(trimmed_key: String, base_domain: &String) -> String
{
vec![trimmed_key, base_domain.to_string()].join(".")
}

35
dnstp/src/string/tests.rs Normal file
View File

@ -0,0 +1,35 @@
use super::*;
#[test]
fn test_encode()
{
let payload = "google.com";
let encoded = encode_domain_name(&payload.to_string());
assert_eq!(encoded.len(), "google".len() + "com".len() + 1 + 1 + 1);
assert_eq!(encoded[0], "google".len() as u8);
// assert_eq!(encoded["google".len()], "com".len() as u8);
}
#[test]
fn test_encode_decode()
{
let payload = "google.com";
let encoded = encode_domain_name(&payload.to_string());
let decoded = decode_domain_name(encoded);
assert_eq!(payload, decoded);
}
#[test]
fn test_encode_decode_two()
{
let payload = "sub.domain.com";
let encoded = encode_domain_name(&payload.to_string());
let decoded = decode_domain_name(encoded);
assert_eq!(payload, decoded);
}

View File

@ -45,15 +45,19 @@ fn test_key_swap()
// SERVER // SERVER
///////////////// /////////////////
let question_count = message.questions.len();
// handle message "received by client" // handle message "received by client"
let resp = decode_key_request(message).unwrap(); let resp = decode_key_request(&message).unwrap();
assert_eq!(question_count, resp.response.questions.len());
assert_eq!(question_count, resp.response.answer_records.len());
//////////// ////////////
// CLIENT // CLIENT
//////////// ////////////
// client has received message from above and constructs shared secret // client has received message from above and constructs shared secret
let shared_secret_client = asym_to_sym_key(&get_shared_asym_secret(client_private, resp.server_public).unwrap()); let shared_secret_client = asym_to_sym_key(&get_shared_asym_secret(&client_private, &resp.server_public).unwrap());
/////////////////////////////// ///////////////////////////////
// TEST ENCRYPTION/DECRYPTION // TEST ENCRYPTION/DECRYPTION