summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStuart Stock <stuart@int08h.com>2017-06-19 22:01:46 -0500
committerStuart Stock <stuart@int08h.com>2017-06-19 22:01:46 -0500
commitdc17df4885cef546ed44d14a291ba782b82d93ed (patch)
tree8a593164f7d678fcae4501945891eb8bfcb3cfa6
parent13d53fec92fddae9c312d323db81bbcb97adbe21 (diff)
downloadroughenough-dc17df4885cef546ed44d14a291ba782b82d93ed.zip
begin work on server
-rw-r--r--.gitignore1
-rw-r--r--Cargo.toml2
-rw-r--r--src/bin/server.rs81
-rw-r--r--src/error.rs5
-rw-r--r--src/hex.rs232
-rw-r--r--src/lib.rs52
-rw-r--r--src/message.rs56
-rw-r--r--src/tag.rs35
8 files changed, 431 insertions, 33 deletions
diff --git a/.gitignore b/.gitignore
index 1e7caa9..ff6d05d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
Cargo.lock
target/
+*.rs.bk
diff --git a/Cargo.toml b/Cargo.toml
index d960854..01e9df3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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;
+ }
+}
+
diff --git a/src/lib.rs b/src/lib.rs
index 7f339c1..4a6d2fa 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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();
diff --git a/src/tag.rs b/src/tag.rs
index 168d81a..b50f5f5 100644
--- a/src/tag.rs
+++ b/src/tag.rs
@@ -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(),