working key handshake, client subcommands, just need to start encrypting and sending content
This commit is contained in:
parent
6e954a9795
commit
6cef74968c
6
dnstp-client/src/download.rs
Normal file
6
dnstp-client/src/download.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
use crate::NetSettings;
|
||||||
|
|
||||||
|
pub fn download(net_settings: NetSettings)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
@ -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
52
dnstp-client/src/test.rs
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
81
dnstp-client/src/upload.rs
Normal file
81
dnstp-client/src/upload.rs
Normal 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");
|
||||||
|
}
|
39
dnstp/src/client_crypto_context.rs
Normal file
39
dnstp/src/client_crypto_context.rs
Normal 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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -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))
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
|
|
||||||
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
@ -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};
|
||||||
|
|
||||||
|
0
dnstp/src/processor/request/download.rs
Normal file
0
dnstp/src/processor/request/download.rs
Normal 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
|
||||||
];
|
];
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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::*;
|
||||||
|
0
dnstp/src/processor/request/upload.rs
Normal file
0
dnstp/src/processor/request/upload.rs
Normal file
38
dnstp/src/processor/response/encryption.rs
Normal file
38
dnstp/src/processor/response/encryption.rs
Normal 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(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
@ -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
89
dnstp/src/string/mod.rs
Normal 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
35
dnstp/src/string/tests.rs
Normal 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);
|
||||||
|
}
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user