summaryrefslogtreecommitdiff
path: root/src/bin
diff options
context:
space:
mode:
authorAaron Hill <aa1ronham@gmail.com>2018-02-23 14:34:43 -0500
committerAaron Hill <aa1ronham@gmail.com>2018-03-01 19:47:05 -0500
commit5abdc895985ed24544b923495250611292e088a7 (patch)
tree97750afb078fd24abac6bd5d544cc65722dd9639 /src/bin
parent7d450312743556696c58d2779f94e482ab7ccb9a (diff)
downloadroughenough-5abdc895985ed24544b923495250611292e088a7.zip
Add client implementation
Diffstat (limited to 'src/bin')
-rw-r--r--src/bin/client.rs227
1 files changed, 227 insertions, 0 deletions
diff --git a/src/bin/client.rs b/src/bin/client.rs
new file mode 100644
index 0000000..d5bd3d4
--- /dev/null
+++ b/src/bin/client.rs
@@ -0,0 +1,227 @@
+#[macro_use]
+extern crate clap;
+extern crate roughenough;
+extern crate ring;
+extern crate time;
+extern crate chrono;
+extern crate byteorder;
+extern crate hex;
+
+use ring::rand;
+use ring::rand::SecureRandom;
+use ring::digest;
+
+use byteorder::{LittleEndian, ReadBytesExt};
+
+use chrono::TimeZone;
+use chrono::offset::Utc;
+
+use std::iter::Iterator;
+use std::collections::HashMap;
+use std::net::{UdpSocket, ToSocketAddrs};
+
+use roughenough::{RtMessage, Tag, TREE_NODE_TWEAK, TREE_LEAF_TWEAK, CERTIFICATE_CONTEXT, SIGNED_RESPONSE_CONTEXT};
+use roughenough::sign::Verifier;
+use clap::{Arg, App};
+
+fn create_nonce() -> [u8; 64] {
+ let rng = rand::SystemRandom::new();
+ let mut nonce = [0u8; 64];
+ rng.fill(&mut nonce).unwrap();
+
+ nonce
+}
+
+fn make_request(nonce: &[u8]) -> Vec<u8> {
+ let mut msg = RtMessage::new(1);
+ msg.add_field(Tag::NONC, nonce).unwrap();
+ msg.pad_to_kilobyte();
+
+ msg.encode().unwrap()
+}
+
+fn receive_response(sock: &mut UdpSocket) -> RtMessage {
+ let mut buf = [0; 744];
+ let resp_len = sock.recv_from(&mut buf).unwrap().0;
+ RtMessage::from_bytes(&buf[0..resp_len]).unwrap()
+}
+
+
+struct ResponseHandler {
+ pub_key: Option<Vec<u8>>,
+ msg: HashMap<Tag, Vec<u8>>,
+ srep: HashMap<Tag, Vec<u8>>,
+ cert: HashMap<Tag, Vec<u8>>,
+ dele: HashMap<Tag, Vec<u8>>,
+ nonce: [u8; 64]
+}
+
+impl ResponseHandler {
+ pub fn new(pub_key: Option<Vec<u8>>, response: RtMessage, nonce: [u8; 64]) -> ResponseHandler {
+ let msg = response.into_hash_map();
+ let srep = RtMessage::from_bytes(&msg[&Tag::SREP]).unwrap().into_hash_map();
+ let cert = RtMessage::from_bytes(&msg[&Tag::CERT]).unwrap().into_hash_map();
+
+ let dele = RtMessage::from_bytes(&cert[&Tag::DELE]).unwrap().into_hash_map();
+
+ ResponseHandler {
+ pub_key,
+ msg,
+ srep,
+ cert,
+ dele,
+ nonce
+ }
+ }
+
+ pub fn extract_time(&self) -> (u64, u32) {
+ let midpoint = self.srep[&Tag::MIDP].as_slice().read_u64::<LittleEndian>().unwrap();
+ let radius = self.srep[&Tag::RADI].as_slice().read_u32::<LittleEndian>().unwrap();
+
+ if self.pub_key.is_some() {
+ self.validate_dele();
+ self.validate_srep();
+ self.validate_merkle();
+ self.validate_midpoint(midpoint);
+ }
+
+ (midpoint, radius)
+ }
+
+ fn validate_dele(&self) {
+ let mut full_cert = Vec::from(CERTIFICATE_CONTEXT.as_bytes());
+ full_cert.extend(&self.cert[&Tag::DELE]);
+
+ assert!(self.validate_sig(self.pub_key.as_ref().unwrap(), &self.cert[&Tag::SIG], &full_cert),
+ "Invalid signature on DELE tag!");
+ }
+
+ fn validate_srep(&self) {
+ let mut full_srep = Vec::from(SIGNED_RESPONSE_CONTEXT.as_bytes());
+ full_srep.extend(&self.msg[&Tag::SREP]);
+
+ assert!(self.validate_sig(&self.dele[&Tag::PUBK], &self.msg[&Tag::SIG], &full_srep),
+ "Invalid signature on SREP tag!");
+ }
+
+ fn validate_merkle(&self) {
+ let srep = RtMessage::from_bytes(&self.msg[&Tag::SREP]).unwrap().into_hash_map();
+ let mut index = self.msg[&Tag::INDX].as_slice().read_u32::<LittleEndian>().unwrap();
+ let paths = &self.msg[&Tag::PATH];
+
+ let mut hash = sha_512(TREE_LEAF_TWEAK, &self.nonce);
+
+ assert_eq!(paths.len() % 64, 0);
+
+ for path in paths.chunks(64) {
+ let mut ctx = digest::Context::new(&digest::SHA512);
+ ctx.update(TREE_NODE_TWEAK);
+
+ if index & 1 == 0 {
+ // Left
+ ctx.update(&hash);
+ ctx.update(path);
+ } else {
+ // Right
+ ctx.update(path);
+ ctx.update(&hash);
+ }
+ hash = Vec::from(ctx.finish().as_ref());
+
+ index >>= 1;
+ }
+
+ assert_eq!(hash, srep[&Tag::ROOT], "Nonce not in merkle tree!");
+
+ }
+
+ fn validate_midpoint(&self, midpoint: u64) {
+ let mint = self.dele[&Tag::MINT].as_slice().read_u64::<LittleEndian>().unwrap();
+ let maxt = self.dele[&Tag::MAXT].as_slice().read_u64::<LittleEndian>().unwrap();
+
+ assert!(midpoint >= mint, "Response midpoint {} lies before delegation span ({}, {})");
+ assert!(midpoint <= maxt, "Response midpoint {} lies after delegation span ({}, {})");
+ }
+
+ fn validate_sig(&self, public_key: &[u8], sig: &[u8], data: &[u8]) -> bool {
+ let mut verifier = Verifier::new(public_key);
+ verifier.update(data);
+ verifier.verify(sig)
+ }
+}
+
+fn sha_512(prefix: &[u8], data: &[u8]) -> Vec<u8> {
+ let mut ctx = digest::Context::new(&digest::SHA512);
+ ctx.update(prefix);
+ ctx.update(data);
+ Vec::from(ctx.finish().as_ref())
+}
+
+fn main() {
+ let matches = App::new("roughenough client")
+ .version("0.1.1")
+ .arg(Arg::with_name("host")
+ .required(true)
+ .help("The Roughtime server to connect to")
+ .takes_value(true))
+ .arg(Arg::with_name("port")
+ .required(true)
+ .help("The Roughtime server port to connect to")
+ .takes_value(true))
+ .arg(Arg::with_name("public-key")
+ .short("p")
+ .long("public-key")
+ .takes_value(true)
+ .help("The server public key used to validate responses. If unset, no validation will be performed"))
+ .arg(Arg::with_name("time-format")
+ .short("f")
+ .long("time-format")
+ .takes_value(true)
+ .help("The strftime format string used to print the time recieved from the server")
+ .default_value("%b %d %Y %H:%M:%S")
+ )
+ .arg(Arg::with_name("num-requests")
+ .short("n")
+ .long("num-requests")
+ .takes_value(true)
+ .help("The number of requests to make to the server (each from a different source port). This is mainly useful for testing batch response handling")
+ .default_value("1")
+ )
+ .get_matches();
+
+ let host = matches.value_of("host").unwrap();
+ let port = value_t_or_exit!(matches.value_of("port"), u16);
+ let num_requests = value_t_or_exit!(matches.value_of("num-requests"), u16) as usize;
+ let pub_key = matches.value_of("public-key").map(|pkey| hex::decode(pkey).expect("Error parsing public key!"));
+ let time_format = matches.value_of("time-format").unwrap();
+
+ println!("Requesting time from: {:?}:{:?}", host, port);
+
+ let addrs: Vec<_> = (host, port).to_socket_addrs().unwrap().collect();
+
+ let mut requests = Vec::with_capacity(num_requests);
+
+ for _ in 0..num_requests {
+ let nonce = create_nonce();
+ let mut socket = UdpSocket::bind("0.0.0.0:0").expect("Couldn't open UDP socket");
+ let request = make_request(&nonce);
+
+ requests.push((nonce, request, socket));
+ }
+
+ for &mut (_, ref request, ref mut socket) in requests.iter_mut() {
+ socket.send_to(request, addrs.as_slice()).unwrap();
+ }
+
+ for (nonce, _, mut socket) in requests {
+ let resp = receive_response(&mut socket);
+
+ let (midpoint, radius) = ResponseHandler::new(pub_key.clone(), resp, nonce).extract_time();
+
+ let seconds = midpoint / 10_u64.pow(6);
+ let spec = Utc.timestamp(seconds as i64, ((midpoint - (seconds * 10_u64.pow(6))) * 10_u64.pow(3)) as u32);
+ let out = spec.format(time_format).to_string();
+
+ println!("Recieved time from server: midpoint={:?}, radius={:?}", out, radius);
+ }
+}