From 41b33e28443b557d365db72ede7c6e2a26d92a91 Mon Sep 17 00:00:00 2001 From: Andy Pack Date: Mon, 29 Jul 2024 21:51:05 +0100 Subject: [PATCH] adding key value upload capability --- dnstp-client/src/main.rs | 10 ++- dnstp-client/src/upload.rs | 78 +++++++++++++++--- dnstp-server/src/main.rs | 2 +- dnstp/src/message/message.rs | 2 + dnstp/src/message/mod.rs | 2 +- dnstp/src/net/socket.rs | 18 +++-- dnstp/src/processor/request/mod.rs | 2 +- dnstp/src/processor/request/upload.rs | 107 ++++++++++++++++++------- dnstp/src/session/message_generator.rs | 77 +++++++++++++++++- dnstp/src/session/mod.rs | 2 +- 10 files changed, 243 insertions(+), 57 deletions(-) diff --git a/dnstp-client/src/main.rs b/dnstp-client/src/main.rs index 84aee3c..ada9640 100644 --- a/dnstp-client/src/main.rs +++ b/dnstp-client/src/main.rs @@ -34,6 +34,8 @@ enum Command { #[clap(flatten)] net_options: NetSettings, #[arg(short, long)] + key: Option>, + #[arg(short, long)] value: Vec }, /// Download a payload from the remote server @@ -52,7 +54,7 @@ struct NetSettings { #[arg(long)] base_domain: String, /// Sub-domain to handle key handling when requested - #[arg(short, long, default_value = "static")] + #[arg(long, default_value = "static")] key_endpoint: String, } @@ -60,7 +62,7 @@ fn main() { CombinedLogger::init( vec![ TermLogger::new(LevelFilter::Info, Config::default(), TerminalMode::Mixed, ColorChoice::Auto), - WriteLogger::new(LevelFilter::Info, Config::default(), OpenOptions::new() + WriteLogger::new(LevelFilter::Trace, Config::default(), OpenOptions::new() .read(true) .write(true) .append(true) @@ -75,8 +77,8 @@ fn main() { Command::Test { net_options } => { send_test_requests(net_options); } - Command::Upload { net_options, value } => { - upload(net_options, value); + Command::Upload { net_options, key, value } => { + upload(net_options, key, value); } Command::Download { net_options } => { download(net_options); diff --git a/dnstp-client/src/upload.rs b/dnstp-client/src/upload.rs index 03b7053..3a59647 100644 --- a/dnstp-client/src/upload.rs +++ b/dnstp-client/src/upload.rs @@ -4,14 +4,21 @@ use std::thread; use std::time::Duration; use log::info; use rand::rngs::OsRng; -use dnstplib::session::{ClientCryptoContext, generate_client_handshake_message, generate_string_encryption_message}; +use dnstplib::session::{ClientCryptoContext, generate_client_handshake_message, generate_key_string_encryption_message, generate_string_encryption_message}; use dnstplib::{DomainConfig, send_message}; use dnstplib::net::DNSSocket; use dnstplib::processor::ResponseProcesor; use crate::NetSettings; -pub fn upload(net_settings: NetSettings, values: Vec) +pub fn upload(net_settings: NetSettings, keys: Option>, values: Vec) { + if let Some(keys) = &keys { + if keys.len() > values.len() { + println!("Cannot provide more keys than values [{} keys] and [{} values]", keys.len(), values.len()); + return; + } + } + let address = SocketAddr::from(([127, 0, 0, 1], 0)); let mut socket = DNSSocket::new(vec!(address)); @@ -45,18 +52,63 @@ pub fn upload(net_settings: NetSettings, values: Vec) info!("crypto complete, sending data"); - for v in values { + match keys { + // no keys, just upload values + None => { + for v in values { - info!("sending [{}]", v); - - if let Ok(encryption_message) = generate_string_encryption_message( - v, - &mut OsRng, - &domain_config, - crypto_context.clone(), - &net_settings.address - ) { - send_message(encryption_message, &tx_channel); + info!("sending [{}]", v); + + if let Ok(encryption_message) = generate_string_encryption_message( + v, + &mut OsRng, + &domain_config, + crypto_context.clone(), + &net_settings.address + ) { + send_message(encryption_message, &tx_channel); + } + } + } + // keys, present loop through keys to send associated values, then send any un-keyed values + Some(keys) => { + + let mut k_index = 0; + for k in keys { + + let v = values.get(k_index).unwrap().clone(); + + info!("sending [{}]:[{}]", k, v); + + if let Ok(encryption_message) = generate_key_string_encryption_message( + k, + v, + &mut OsRng, + &domain_config, + crypto_context.clone(), + &net_settings.address + ) { + send_message(encryption_message, &tx_channel); + } + + k_index = k_index + 1; + } + + if k_index < values.len() { + for v in &values[k_index..] { + info!("sending [{}]", v); + + if let Ok(encryption_message) = generate_string_encryption_message( + v.clone(), + &mut OsRng, + &domain_config, + crypto_context.clone(), + &net_settings.address + ) { + send_message(encryption_message, &tx_channel); + } + } + } } } } \ No newline at end of file diff --git a/dnstp-server/src/main.rs b/dnstp-server/src/main.rs index 1d2b881..a954ce2 100644 --- a/dnstp-server/src/main.rs +++ b/dnstp-server/src/main.rs @@ -35,7 +35,7 @@ fn main() { CombinedLogger::init( vec![ TermLogger::new(LevelFilter::Info, Config::default(), TerminalMode::Mixed, ColorChoice::Auto), - WriteLogger::new(LevelFilter::Info, Config::default(), OpenOptions::new() + WriteLogger::new(LevelFilter::Trace, Config::default(), OpenOptions::new() .read(true) .write(true) .append(true) diff --git a/dnstp/src/message/message.rs b/dnstp/src/message/message.rs index 8e3edc3..9195309 100644 --- a/dnstp/src/message/message.rs +++ b/dnstp/src/message/message.rs @@ -2,6 +2,8 @@ use std::net::{Ipv4Addr, SocketAddr}; use crate::message::{DNSQuestion, DNSHeader, questions_to_bytes, Direction, ResponseCode, QType, QClass, ResourceRecord, records_to_bytes, ARdata, TXTRdata, RData}; use crate::RequestError; +pub const MESSAGE_SIZE: usize = 512; + /// A DNS message which can be used as either a request or response based on its direction and composition #[derive(Debug)] pub struct DNSMessage { diff --git a/dnstp/src/message/mod.rs b/dnstp/src/message/mod.rs index f90b47d..54ccac4 100644 --- a/dnstp/src/message/mod.rs +++ b/dnstp/src/message/mod.rs @@ -8,5 +8,5 @@ pub mod message_parser; pub use question::{DNSQuestion, QClass, QType, QuestionParseError, questions_from_bytes, questions_to_bytes}; pub use record::{AAAARdata, ARdata, RawRData, RData, RecordParseError, records_from_bytes, records_to_bytes, ResourceRecord, TXTRdata}; pub use header::{Direction, DNSHeader, HEADER_SIZE, Opcode, ResponseCode}; -pub use message::DNSMessage; +pub use message::{DNSMessage, MESSAGE_SIZE}; pub use message_parser::*; \ No newline at end of file diff --git a/dnstp/src/net/socket.rs b/dnstp/src/net/socket.rs index d4c41ff..9a357c3 100644 --- a/dnstp/src/net/socket.rs +++ b/dnstp/src/net/socket.rs @@ -1,11 +1,11 @@ use std::net::{SocketAddr, UdpSocket}; use std::thread; use std::thread::{JoinHandle}; -use log::{debug, error, info}; +use log::{debug, error, info, warn}; use std::sync::mpsc; use std::sync::mpsc::{Receiver, Sender, TryRecvError}; -use crate::message::HEADER_SIZE; +use crate::message::{HEADER_SIZE, MESSAGE_SIZE}; use crate::net::{NetworkMessage, NetworkMessagePtr}; pub struct DNSSocket { @@ -76,8 +76,8 @@ impl DNSSocket { Some(s) => { let mut cancelled = false; while !cancelled { - let mut buf = Box::new(Vec::with_capacity(512)); - buf.resize(512, 0); + let mut buf = Box::new(Vec::with_capacity(MESSAGE_SIZE)); + buf.resize(MESSAGE_SIZE, 0); let res = s.recv_from(&mut (*buf)); match res { @@ -92,7 +92,7 @@ impl DNSSocket { } } else { - debug!("skipping processing message from [{}], message isn't longer than standard header", peer); + debug!("[{}] skipping processing message, message isn't longer than standard header", peer); } } Err(_) => {} @@ -131,8 +131,14 @@ impl DNSSocket { while !cancelled { for m in &msg_rx { + + let message_length = m.buffer.len(); + if message_length > MESSAGE_SIZE { + warn!("[{}] message is longer than standard maximum [{} bytes]", m.peer, message_length); + } + if let Err(e) = s.send_to(&(*m.buffer), m.peer){ - error!("error sending response to [{}], {}", m.peer, e); + error!("[{}] error sending response {}", m.peer, e); } } diff --git a/dnstp/src/processor/request/mod.rs b/dnstp/src/processor/request/mod.rs index fa64b73..ab081dc 100644 --- a/dnstp/src/processor/request/mod.rs +++ b/dnstp/src/processor/request/mod.rs @@ -95,7 +95,7 @@ impl RequestProcesor { 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 + if r.questions.len() == 3 || r.questions.len() == 4 { match r.questions[1].qtype { QType::A => { diff --git a/dnstp/src/processor/request/upload.rs b/dnstp/src/processor/request/upload.rs index c6b228e..a7eab8c 100644 --- a/dnstp/src/processor/request/upload.rs +++ b/dnstp/src/processor/request/upload.rs @@ -1,7 +1,10 @@ use std::net::SocketAddr; use std::sync::mpsc::Sender; use std::sync::{Arc, Mutex}; +use aes_gcm_siv::aead::consts::U12; +use aes_gcm_siv::aead::generic_array::GenericArray; use aes_gcm_siv::Nonce; +use base64::DecodeError; use log::{info, error}; use base64::prelude::*; @@ -14,6 +17,54 @@ use crate::message::DNSMessage; use crate::net::NetworkMessagePtr; use crate::processor::RequestProcesor; +struct UploadValue { + pub key: Option, + pub value: String, + pub nonce: String +} + +impl UploadValue { + pub fn from(r: &DNSMessage) -> Option { + if r.questions.len() == 3 { + return Some(UploadValue { + key: None, + value: r.questions[1].qname.clone(), + nonce: r.questions[2].qname.clone() + }); + } + else if r.questions.len() == 4 { + return Some(UploadValue { + key: Some(r.questions[1].qname.clone()), + value: r.questions[2].qname.clone(), + nonce: r.questions[3].qname.clone() + }); + } + + None + } + + pub fn get_decoded_nonce(&self) -> Result, DecodeError> + { + BASE64_STANDARD.decode(&self.nonce) + } + + pub fn get_decoded_encrypted_key(&self) -> Option> + { + if let Some(key) = &self.key { + if let Ok(decode) = BASE64_STANDARD.decode(key) { + return Some(decode); + } + } + + None + } + + pub fn get_decoded_encrypted_value(&self) -> Result, DecodeError> + { + BASE64_STANDARD.decode(&self.value) + } +} + impl RequestProcesor { pub fn handle_upload_request(r: DNSMessage, _sending_channel: &Sender, clients: &Arc>, peer: SocketAddr) { @@ -24,37 +75,37 @@ impl RequestProcesor { error!("[{}] failed to bump last seen time", peer); } - let encrypted_value = BASE64_STANDARD.decode(r.questions[1].qname.clone()); - let nonce_value = BASE64_STANDARD.decode(r.questions[2].qname.clone()); + if let Some(value_context) = UploadValue::from(&r) { - match (encrypted_value, nonce_value) { - (Ok(encrypted_value), Ok(nonce_value)) => { - let nonce = Nonce::from_slice(nonce_value.as_slice()); - let decrypted = decrypt(clients.lock().unwrap().get_shared_key(client_id).unwrap(), nonce, &encrypted_value).unwrap(); - let decrypted_string = String::from_utf8(decrypted).unwrap(); + match (value_context.get_decoded_encrypted_value(), value_context.get_decoded_nonce()) { + (Ok(encrypted_value), Ok(nonce_value)) => { + let nonce = Nonce::from_slice(nonce_value.as_slice()); - info!("[{}] decrypted [{}] from peer", peer, decrypted_string.as_str()); + let mut clients = clients.lock().unwrap(); + let shared_key = clients.get_shared_key(client_id).unwrap(); + let decrypted = decrypt(shared_key, nonce, &encrypted_value).unwrap(); + let decrypted_string = String::from_utf8(decrypted).unwrap(); - // let mut file = OpenOptions::new() - // .read(true) - // .write(true) - // .append(true) - // .create(true) - // .open(client_id) - // .unwrap(); - // - // if let Err(e) = file.write(decrypted_string.as_bytes()) { - // error!("[{}] couldn't write to file: {}", peer, e); - // } - // if let Err(e) = file.write("\n".as_bytes()) { - // error!("[{}] couldn't write to file: {}", peer, e); - // } - } - (Err(e), _) => { - error!("[{}] failed to decode encrypted value from peer: {}", peer, e); - } - (_, Err(e)) => { - error!("[{}] failed to decode nonce from peer: {}", peer, e); + match value_context.get_decoded_encrypted_key() { + Some(encrypted_key) => { + + let decrypted_key = decrypt(shared_key, nonce, &encrypted_key).unwrap(); + let decrypted_key_string = String::from_utf8(decrypted_key).unwrap(); + + info!("[{}] decrypted [{}]:[{}] from peer", peer, decrypted_key_string.as_str(), decrypted_string.as_str()); + } + None => { + info!("[{}] decrypted [{}] from peer", peer, decrypted_string.as_str()); + } + } + + } + (Err(e), _) => { + error!("[{}] failed to decode encrypted value from peer: {}", peer, e); + } + (_, Err(e)) => { + error!("[{}] failed to decode nonce from peer: {}", peer, e); + } } } } diff --git a/dnstp/src/session/message_generator.rs b/dnstp/src/session/message_generator.rs index 4d90154..5a35d81 100644 --- a/dnstp/src/session/message_generator.rs +++ b/dnstp/src/session/message_generator.rs @@ -60,7 +60,7 @@ pub fn generate_string_encryption_message(value: String, rand: &mut OsRng, domai if let Ok(e) = encrypted { let encrypted_string = BASE64_STANDARD.encode(e); let nonce_string = BASE64_STANDARD.encode(nonce); - + return Ok(get_string_encryption_message( rand.next_u32() as u16, crypto_context.lock().unwrap().get_public_key_domain(&domain_config.base_domain), @@ -69,7 +69,7 @@ pub fn generate_string_encryption_message(value: String, rand: &mut OsRng, domai peer )) } - + Err(()) } @@ -112,4 +112,77 @@ pub fn get_string_encryption_message(msg_id: u16, public_key_domain: String, enc additional_records: vec![], peer: peer.parse().unwrap(), } +} + +pub fn generate_key_string_encryption_message(key: String, value: String, rand: &mut OsRng, domain_config: &DomainConfig, crypto_context: Arc>, peer: &String) -> Result { + + let nonce = generate_aes_nonce(); + let encrypted_key = encrypt(&crypto_context.lock().unwrap().shared_key.clone().unwrap(), &nonce, &key.clone().into_bytes()); + let encrypted_value = encrypt(&crypto_context.lock().unwrap().shared_key.clone().unwrap(), &nonce, &value.clone().into_bytes()); + + match (encrypted_key, encrypted_value) { + (Ok(encrypted_key), Ok(encrypted_value)) => { + let encrypted_key = BASE64_STANDARD.encode(encrypted_key); + let encrypted_value = BASE64_STANDARD.encode(encrypted_value); + let nonce_string = BASE64_STANDARD.encode(nonce); + + return Ok(get_key_string_encryption_message( + rand.next_u32() as u16, + crypto_context.lock().unwrap().get_public_key_domain(&domain_config.base_domain), + encrypted_key, + encrypted_value, + nonce_string, + peer + )) + } + (_, _) => {} + } + + Err(()) +} + +pub fn get_key_string_encryption_message(msg_id: u16, public_key_domain: String, encrypted_key: String, encrypted_string: String, nonce_string: String, peer: &String) -> DNSMessage { + DNSMessage { + header: DNSHeader { + id: msg_id, + direction: Direction::Request, + opcode: Opcode::Query, + authoritative: false, + truncation: false, + recursion_desired: false, + recursion_available: false, + valid_zeroes: true, + response: ResponseCode::NoError, + question_count: 4, + answer_record_count: 0, + authority_record_count: 0, + additional_record_count: 0, + }, + questions: vec![ + DNSQuestion { + qname: public_key_domain, + qtype: QType::A, + qclass: QClass::Internet, + }, + DNSQuestion { + qname: encrypted_key, + qtype: QType::A, + qclass: QClass::Internet, + }, + DNSQuestion { + qname: encrypted_string, + qtype: QType::A, + qclass: QClass::Internet, + }, + DNSQuestion { + qname: nonce_string, + qtype: QType::A, + qclass: QClass::Internet, + } + ], + answer_records: vec![], + authority_records: vec![], + additional_records: vec![], + peer: peer.parse().unwrap(), + } } \ No newline at end of file diff --git a/dnstp/src/session/mod.rs b/dnstp/src/session/mod.rs index c75f741..f852e76 100644 --- a/dnstp/src/session/mod.rs +++ b/dnstp/src/session/mod.rs @@ -4,4 +4,4 @@ mod message_generator; pub use clients::Clients; pub use client_crypto_context::ClientCryptoContext; -pub use message_generator::{generate_client_handshake_message, generate_string_encryption_message}; +pub use message_generator::{generate_client_handshake_message, generate_string_encryption_message, generate_key_string_encryption_message};