diff options
author | Stuart Stock <stuart@int08h.com> | 2017-06-19 22:01:46 -0500 |
---|---|---|
committer | Stuart Stock <stuart@int08h.com> | 2017-06-19 22:01:46 -0500 |
commit | dc17df4885cef546ed44d14a291ba782b82d93ed (patch) | |
tree | 8a593164f7d678fcae4501945891eb8bfcb3cfa6 | |
parent | 13d53fec92fddae9c312d323db81bbcb97adbe21 (diff) | |
download | roughenough-dc17df4885cef546ed44d14a291ba782b82d93ed.zip |
begin work on server
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | src/bin/server.rs | 81 | ||||
-rw-r--r-- | src/error.rs | 5 | ||||
-rw-r--r-- | src/hex.rs | 232 | ||||
-rw-r--r-- | src/lib.rs | 52 | ||||
-rw-r--r-- | src/message.rs | 56 | ||||
-rw-r--r-- | src/tag.rs | 35 |
8 files changed, 431 insertions, 33 deletions
@@ -1,2 +1,3 @@ Cargo.lock target/ +*.rs.bk @@ -7,3 +7,5 @@ license = "Apache-2.0" [dependencies] byteorder = "1" +ring = "0.11.0" +untrusted = "0.5.0" diff --git a/src/bin/server.rs b/src/bin/server.rs new file mode 100644 index 0000000..f62e4c2 --- /dev/null +++ b/src/bin/server.rs @@ -0,0 +1,81 @@ +//! +//! Roughtime server +//! + +extern crate core; +extern crate ring; +extern crate untrusted; +extern crate roughenough; + +use core::ptr; + +use untrusted::Input; +use roughenough::{RtMessage, Tag, Error}; +use roughenough::hex::*; + +use ring::{digest, rand}; +use ring::rand::SecureRandom; +use ring::signature::Ed25519KeyPair; + +/// Zero all bytes in dst +#[inline] +pub fn zero(dst: &mut [u8]) { + unsafe { + ptr::write_bytes(dst.as_mut_ptr(), 0u8, dst.len()); + } +} + +fn main() { + // Read long-term key + let long_term_key = { + let mut seed = [b'x'; 32]; + + let lt_key = Ed25519KeyPair::from_seed_unchecked(Input::from(&seed)).unwrap(); + println!("Long-term public key: {}", lt_key.public_key_bytes().to_hex()); + + lt_key + }; + + // Create DELE + let ephemeral_key = { + let rng = rand::SystemRandom::new(); + let mut seed = [0u8; 32]; + rng.fill(&mut seed).unwrap(); + + let eph_key = Ed25519KeyPair::from_seed_unchecked(Input::from(&seed)).unwrap(); + println!("Ephemeral public key: {}", eph_key.public_key_bytes().to_hex()); + + eph_key + }; + + let zeros = [0u8; 8]; + let max = [0xff; 8]; + + let mut dele_msg = RtMessage::new(3); + dele_msg.add_field(Tag::PUBK, &ephemeral_key.public_key_bytes()).unwrap(); + dele_msg.add_field(Tag::MINT, &zeros).unwrap(); + dele_msg.add_field(Tag::MAXT, &max).unwrap(); + + let dele_bytes = dele_msg.encode().unwrap(); + + println!("{}", dele_bytes.to_hex()); + + // Sign it with long-term key + // Create CERT + + // Wipe long-term key + + // loop: + // read request + // validate request or goto loop + // create SREP + // sign SREP + // create response: + // - SIG + // - PATH (always 0) + // - SREP + // - CERT (pre-created) + // - INDX (always 0) + // send response + +} diff --git a/src/error.rs b/src/error.rs index 094171d..fed741a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,9 +2,14 @@ use std; use tag::Tag; +/// Error types generated by this implementation #[derive(Debug)] pub enum Error { + /// The associated tag was added to an `RtMessage` in non-increasing order. TagNotStrictlyIncreasing(Tag), + + /// Encoding failed. The associated `std::io::Error` should provide + /// more information. EncodingFailure(std::io::Error), } diff --git a/src/hex.rs b/src/hex.rs new file mode 100644 index 0000000..c2b49e9 --- /dev/null +++ b/src/hex.rs @@ -0,0 +1,232 @@ +// Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Hex binary-to-text encoding + +pub use self::FromHexError::*; + +use std::fmt; +use std::error; + +/// A trait for converting a value to hexadecimal encoding +pub trait ToHex { + /// Converts the value of `self` to a hex value, returning the owned + /// string. + fn to_hex(&self) -> String; +} + +const CHARS: &'static [u8] = b"0123456789abcdef"; + +impl ToHex for [u8] { + /// Turn a vector of `u8` bytes into a hexadecimal string. + /// + /// # Examples + /// + /// ``` + /// #![feature(rustc_private)] + /// + /// extern crate serialize; + /// use serialize::hex::ToHex; + /// + /// fn main () { + /// let str = [52,32].to_hex(); + /// println!("{}", str); + /// } + /// ``` + fn to_hex(&self) -> String { + let mut v = Vec::with_capacity(self.len() * 2); + for &byte in self { + v.push(CHARS[(byte >> 4) as usize]); + v.push(CHARS[(byte & 0xf) as usize]); + } + + unsafe { + String::from_utf8_unchecked(v) + } + } +} + +/// A trait for converting hexadecimal encoded values +pub trait FromHex { + /// Converts the value of `self`, interpreted as hexadecimal encoded data, + /// into an owned vector of bytes, returning the vector. + fn from_hex(&self) -> Result<Vec<u8>, FromHexError>; +} + +/// Errors that can occur when decoding a hex encoded string +#[derive(Copy, Clone, Debug)] +pub enum FromHexError { + /// The input contained a character not part of the hex format + InvalidHexCharacter(char, usize), + /// The input had an invalid length + InvalidHexLength, +} + +impl fmt::Display for FromHexError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + InvalidHexCharacter(ch, idx) => + write!(f, "Invalid character '{}' at position {}", ch, idx), + InvalidHexLength => write!(f, "Invalid input length"), + } + } +} + +impl error::Error for FromHexError { + fn description(&self) -> &str { + match *self { + InvalidHexCharacter(..) => "invalid character", + InvalidHexLength => "invalid length", + } + } +} + + +impl FromHex for str { + /// Convert any hexadecimal encoded string (literal, `@`, `&`, or `~`) + /// to the byte values it encodes. + /// + /// You can use the `String::from_utf8` function to turn a + /// `Vec<u8>` into a string with characters corresponding to those values. + /// + /// # Examples + /// + /// This converts a string literal to hexadecimal and back. + /// + /// ``` + /// #![feature(rustc_private)] + /// + /// extern crate serialize; + /// use serialize::hex::{FromHex, ToHex}; + /// + /// fn main () { + /// let hello_str = "Hello, World".as_bytes().to_hex(); + /// println!("{}", hello_str); + /// let bytes = hello_str.from_hex().unwrap(); + /// println!("{:?}", bytes); + /// let result_str = String::from_utf8(bytes).unwrap(); + /// println!("{}", result_str); + /// } + /// ``` + fn from_hex(&self) -> Result<Vec<u8>, FromHexError> { + // This may be an overestimate if there is any whitespace + let mut b = Vec::with_capacity(self.len() / 2); + let mut modulus = 0; + let mut buf = 0; + + for (idx, byte) in self.bytes().enumerate() { + buf <<= 4; + + match byte { + b'A'...b'F' => buf |= byte - b'A' + 10, + b'a'...b'f' => buf |= byte - b'a' + 10, + b'0'...b'9' => buf |= byte - b'0', + b' '|b'\r'|b'\n'|b'\t' => { + buf >>= 4; + continue + } + _ => { + let ch = self[idx..].chars().next().unwrap(); + return Err(InvalidHexCharacter(ch, idx)) + } + } + + modulus += 1; + if modulus == 2 { + modulus = 0; + b.push(buf); + } + } + + match modulus { + 0 => Ok(b.into_iter().collect()), + _ => Err(InvalidHexLength), + } + } +} + +#[cfg(test)] +mod tests { + extern crate test; + use self::test::Bencher; + use hex::{FromHex, ToHex}; + + #[test] + pub fn test_to_hex() { + assert_eq!("foobar".as_bytes().to_hex(), "666f6f626172"); + } + + #[test] + pub fn test_from_hex_okay() { + assert_eq!("666f6f626172".from_hex().unwrap(), + b"foobar"); + assert_eq!("666F6F626172".from_hex().unwrap(), + b"foobar"); + } + + #[test] + pub fn test_from_hex_odd_len() { + assert!("666".from_hex().is_err()); + assert!("66 6".from_hex().is_err()); + } + + #[test] + pub fn test_from_hex_invalid_char() { + assert!("66y6".from_hex().is_err()); + } + + #[test] + pub fn test_from_hex_ignores_whitespace() { + assert_eq!("666f 6f6\r\n26172 ".from_hex().unwrap(), + b"foobar"); + } + + #[test] + pub fn test_to_hex_all_bytes() { + for i in 0..256 { + assert_eq!([i as u8].to_hex(), format!("{:02x}", i as usize)); + } + } + + #[test] + pub fn test_from_hex_all_bytes() { + for i in 0..256 { + let ii: &[u8] = &[i as u8]; + assert_eq!(format!("{:02x}", i as usize).from_hex() + .unwrap(), + ii); + assert_eq!(format!("{:02X}", i as usize).from_hex() + .unwrap(), + ii); + } + } + + #[bench] + pub fn bench_to_hex(b: &mut Bencher) { + let s = "イロハニホヘト チリヌルヲ ワカヨタレソ ツネナラム \ + ウヰノオクヤマ ケフコエテ アサキユメミシ ヱヒモセスン"; + b.iter(|| { + s.as_bytes().to_hex(); + }); + b.bytes = s.len() as u64; + } + + #[bench] + pub fn bench_from_hex(b: &mut Bencher) { + let s = "イロハニホヘト チリヌルヲ ワカヨタレソ ツネナラム \ + ウヰノオクヤマ ケフコエテ アサキユメミシ ヱヒモセスン"; + let sb = s.as_bytes().to_hex(); + b.iter(|| { + sb.from_hex().unwrap(); + }); + b.bytes = sb.len() as u64; + } +} + @@ -1,5 +1,51 @@ +//! +//! An implementation of the [Roughtime](https://roughtime.googlesource.com/roughtime) +//! secure time synchronization protocol. +//! + extern crate byteorder; -pub mod error; -pub mod tag; -pub mod message; +mod error; +mod tag; +mod message; + +pub mod hex; + +pub use error::Error; +pub use tag::Tag; +pub use message::RtMessage; + +// Constants and magic numbers of the Roughtime protocol +// + /// Minimum size (in bytes) of a client request + pub const MIN_REQUEST_LENGTH: u32 = 1024; + + /// Minimum size (in bytes) of seeds used to derive private keys + pub const MIN_SEED_LENGTH: u32 = 32; + + /// Size (in bytes) of an Ed25519 public key + pub const PUBKEY_LENGTH: u32 = 32; + + /// Size (in bytes) of the client's nonce + pub const NONCE_LENGTH: u32 = 64; + + /// Size (in bytes) of an Ed25519 signature + pub const SIGNATURE_LENGTH: u32 = 64; + + /// Size (in bytes) of server's timestamp value + pub const TIMESTAMP_LENGTH: u32 = 8; + + /// Size (in bytes) of server's time uncertainty value + pub const RADIUS_LENGTH: u32 = 4; + + /// Prefixed to the server's certificate before generating or verifying certificate's signature + pub const CERTIFICATE_CONTEXT: &str = "RoughTime v1 delegation signature--\x00"; + + /// Prefixed to the server's response before generating or verifying the server's signature + pub const SIGNED_RESPONSE_CONTEXT: &str = "RoughTime v1 response signature\x00"; + + /// Value prepended to leaves prior to hashing + pub const TREE_LEAF_TWEAK: u8 = 0x00; + + /// Value prepended to nodes prior to hashing + pub const TREE_NODE_TWEAK: u8 = 0x01; diff --git a/src/message.rs b/src/message.rs index 4e7e84b..73db76a 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1,9 +1,13 @@ + use std::io::Write; use byteorder::{LittleEndian, WriteBytesExt}; use tag::Tag; use error::Error; +/// +/// A Roughtime protocol message; a map of u32 tags to arbitrary byte-strings. +/// #[derive(Debug)] pub struct RtMessage<'a> { tags: Vec<Tag>, @@ -11,13 +15,29 @@ pub struct RtMessage<'a> { } impl<'a> RtMessage<'a> { + + /// Construct a new RtMessage + /// + /// ## Arguments + /// + /// * `num_fields` - Reserve space for this many fields. + /// pub fn new(num_fields: u8) -> Self { RtMessage { tags: Vec::with_capacity(num_fields as usize), - values: Vec::with_capacity(num_fields as usize) + values: Vec::with_capacity(num_fields as usize), } } + /// Add a field to this `RtMessage` + /// + /// ## Arguments + /// + /// * `tag` - The [`Tag`](enum.Tag.html) to add. Tags must be added in **strictly increasing order**, + /// violating this will result in a [`Error::TagNotStrictlyIncreasing`](enum.Error.html). + /// + /// * `value` - Value for the tag. + /// pub fn add_field(&mut self, tag: Tag, value: &'a [u8]) -> Result<(), Error> { if let Some(last_tag) = self.tags.last() { if tag <= *last_tag { @@ -31,10 +51,12 @@ impl<'a> RtMessage<'a> { Ok(()) } + /// Returns the number of tag/value pairs in the message pub fn num_fields(&self) -> u32 { self.tags.len() as u32 } + /// Encode this message into its on-the-wire representation. pub fn encode(&self) -> Result<Vec<u8>, Error> { let num_tags = self.tags.len(); let mut out = Vec::with_capacity(self.encoded_size()); @@ -52,7 +74,7 @@ impl<'a> RtMessage<'a> { } } - // write tags + // write tags for tag in &self.tags { out.write_all(tag.wire_value())?; } @@ -68,6 +90,7 @@ impl<'a> RtMessage<'a> { Ok(out) } + /// Returns the length in bytes of this message's on-the-wire representation. pub fn encoded_size(&self) -> usize { let num_tags = self.tags.len(); let tags_size = 4 * num_tags; @@ -111,11 +134,11 @@ mod test { msg.add_field(Tag::PAD, "abcd".as_bytes()).unwrap(); assert_eq!(msg.num_fields(), 2); - // Two tag message - // 4 num_tags - // 8 (NONC, PAD) tags - // 4 PAD offset - // 8 values + // Two tag message + // 4 num_tags + // 8 (NONC, PAD) tags + // 4 PAD offset + // 8 values assert_eq!(msg.encoded_size(), 24); } @@ -163,24 +186,25 @@ mod test { msg.add_field(Tag::MAXT, &maxt_value).unwrap(); let mut encoded = Cursor::new(msg.encode().unwrap()); - // Wire encoding - // 4 num_tags - // 8 (DELE, MAXT) tags - // 4 MAXT offset - // 24 DELE value - // 32 MAXT value + // Wire encoding + // 4 num_tags + // 8 (DELE, MAXT) tags + // 4 MAXT offset + // 24 DELE value + // 32 MAXT value // num tags assert_eq!(encoded.read_u32::<LittleEndian>().unwrap(), 2); // Offset past DELE value to start of MAXT value - assert_eq!(encoded.read_u32::<LittleEndian>().unwrap(), dele_value.len() as u32); + assert_eq!(encoded.read_u32::<LittleEndian>().unwrap(), + dele_value.len() as u32); // DELE tag let mut dele = [0u8; 4]; encoded.read_exact(&mut dele).unwrap(); assert_eq!(dele, Tag::DELE.wire_value()); - + // MAXT tag let mut maxt = [0u8; 4]; encoded.read_exact(&mut maxt).unwrap(); @@ -190,7 +214,7 @@ mod test { let mut read_dele_val = vec![0u8; 24]; encoded.read_exact(&mut read_dele_val).unwrap(); assert_eq!(dele_value, read_dele_val); - + // MAXT value let mut read_maxt_val = vec![0u8; 32]; encoded.read_exact(&mut read_maxt_val).unwrap(); @@ -1,25 +1,32 @@ + +/// An unsigned 32-bit value (key) that maps to a byte-string (value). #[derive(Debug, Eq, PartialEq, Ord, PartialOrd)] pub enum Tag { - CERT, - DELE, - INDX, - MAXT, - MIDP, - MINT, - NONC, - PAD, - PATH, - PUBK, - RADI, - ROOT, - SIG, + // Enforcement of the "tags in strictly increasing order" rule is done using the + // little-endian encoding of the ASCII tag value; e.g. 'SIG\x00' is 0x00474953 and + // 'NONC' is 0x434e4f4e. The tags are listed here is reverse lexical order which + // matches the little-endian comparison order. SREP, + SIG, + ROOT, + RADI, + PUBK, + PATH, + PAD, + NONC, + MINT, + MIDP, + MAXT, + INDX, + DELE, + CERT, } static PAD_VALUE: [u8; 4] = [b'P', b'A', b'D', 0x00]; static SIG_VALUE: [u8; 4] = [b'S', b'I', b'G', 0xff]; impl Tag { + /// Translates a tag into its on-the-wire representation pub fn wire_value(&self) -> &'static [u8] { match *self { Tag::CERT => "CERT".as_bytes(), @@ -29,7 +36,7 @@ impl Tag { Tag::MIDP => "MIDP".as_bytes(), Tag::MINT => "MINT".as_bytes(), Tag::NONC => "NONC".as_bytes(), - Tag::PAD => PAD_VALUE.as_ref(), + Tag::PAD => PAD_VALUE.as_ref(), Tag::PATH => "PATH".as_bytes(), Tag::PUBK => "PUBK".as_bytes(), Tag::RADI => "RADI".as_bytes(), |