diff options
-rw-r--r-- | README.md | 13 | ||||
-rw-r--r-- | example.cfg | 1 | ||||
-rw-r--r-- | src/bin/client.rs | 10 | ||||
-rw-r--r-- | src/bin/server.rs | 251 | ||||
-rw-r--r-- | src/config.rs | 182 | ||||
-rw-r--r-- | src/error.rs | 5 | ||||
-rw-r--r-- | src/keys.rs | 147 | ||||
-rw-r--r-- | src/lib.rs | 38 | ||||
-rw-r--r-- | src/merkle.rs | 5 | ||||
-rw-r--r-- | src/message.rs | 41 | ||||
-rw-r--r-- | src/sign.rs | 43 | ||||
-rw-r--r-- | src/tag.rs | 2 |
12 files changed, 481 insertions, 257 deletions
@@ -90,19 +90,20 @@ The server is configured via a YAML file: interface: 127.0.0.1 port: 8686 seed: f61075c988feb9cb700a4a6a3291bfbc9cab11b9c9eca8c802468eb38a43d7d3 -batch_size: 64 ``` Where: -* **`interface`** - IP address or interface name for listening to client requests -* **`port`** - UDP port to listen for requests -* **`seed`** - A 32-byte hexadecimal value used to generate the server's long-term +* **`interface`** - [Mandatory] IP address or interface name for listening to client requests +* **`port`** - [Mandatory] UDP port to listen for requests +* **`seed`** - [Mandatory] A 32-byte hexadecimal value used to generate the server's long-term key pair. **This is a secret value and must be un-guessable**, treat it with care. -* **`batch_size`** - The number of requests to process in one batch. All nonces +* **`batch_size`** - [Optional] The maximum number of requests to process in one batch. All nonces in a batch are used to build a Merkle tree, the root of which - is signed. + is signed. Default is 64 requests per batch. +* **`status_interval`** - [Optional] Number of _seconds_ between each logged status update. + Default is 600 seconds (10 minutes). ### Stopping the Server diff --git a/example.cfg b/example.cfg index d27f012..c271481 100644 --- a/example.cfg +++ b/example.cfg @@ -1,4 +1,3 @@ port: 8686 interface: 127.0.0.1 seed: a32049da0ffde0ded92ce10a0230d35fe615ec8461c14986baa63fe3b3bac3db -batch_size: 64 diff --git a/src/bin/client.rs b/src/bin/client.rs index d24315b..75ebe14 100644 --- a/src/bin/client.rs +++ b/src/bin/client.rs @@ -24,17 +24,17 @@ use ring::rand::SecureRandom; use byteorder::{LittleEndian, ReadBytesExt}; -use chrono::TimeZone; use chrono::offset::Utc; +use chrono::TimeZone; -use std::iter::Iterator; use std::collections::HashMap; +use std::iter::Iterator; use std::net::{ToSocketAddrs, UdpSocket}; -use roughenough::{RtMessage, Tag, CERTIFICATE_CONTEXT, SIGNED_RESPONSE_CONTEXT, VERSION}; -use roughenough::sign::Verifier; -use roughenough::merkle::root_from_paths; use clap::{App, Arg}; +use roughenough::merkle::root_from_paths; +use roughenough::sign::Verifier; +use roughenough::{RtMessage, Tag, CERTIFICATE_CONTEXT, SIGNED_RESPONSE_CONTEXT, VERSION}; fn create_nonce() -> [u8; 64] { let rng = rand::SystemRandom::new(); diff --git a/src/bin/server.rs b/src/bin/server.rs index 18ebdaf..0a1bb21 100644 --- a/src/bin/server.rs +++ b/src/bin/server.rs @@ -1,4 +1,4 @@ -// Copyright 2017 int08h LLC +// Copyright 2017-2018 int08h LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,31 +16,15 @@ //! Roughtime server //! //! # Configuration -//! The `roughenough` server is configured via a config file: +//! The `roughenough` server is configured via a YAML config file. See the documentation +//! for [FileConfig](struct.FileConfig.html) for details. //! -//! ```yaml -//! interface: 127.0.0.1 -//! port: 8686 -//! seed: f61075c988feb9cb700a4a6a3291bfbc9cab11b9c9eca8c802468eb38a43d7d3 -//! batch_size: 64 -//! ``` -//! -//! Where: -//! -//! * **interface** - IP address or interface name for listening to client requests -//! * **port** - UDP port to listen to requests -//! * **seed** - A 32-byte hexadecimal value used as the seed to generate the -//! server's long-term key pair. **This is a secret value**, treat it -//! with care. -//! * **batch_size** - The number of requests to process in one batch. All nonces -//! in a batch are used to build a Merkle tree, the root of which -//! is signed. -//! -//! # Running the Server +//! To run the server: //! //! ```bash //! $ cargo run --release --bin server /path/to/config.file //! ``` +//! extern crate byteorder; extern crate ctrlc; @@ -57,146 +41,41 @@ extern crate untrusted; extern crate yaml_rust; use std::env; +use std::io::ErrorKind; use std::process; -use std::fs::File; -use std::io::{ErrorKind, Read}; -use std::time::Duration; -use std::net::SocketAddr; -use std::sync::Arc; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use std::sync::Arc; use std::thread; +use std::time::Duration; -use mio::{Events, Poll, PollOpt, Ready, Token}; use mio::net::UdpSocket; +use mio::{Events, Poll, PollOpt, Ready, Token}; use mio_extras::timer::Timer; use byteorder::{LittleEndian, WriteBytesExt}; +use roughenough::config::{FileConfig, ServerConfig}; +use roughenough::keys::{LongTermKey, OnlineKey}; +use roughenough::merkle::MerkleTree; use roughenough::{Error, RtMessage, Tag}; -use roughenough::{CERTIFICATE_CONTEXT, MIN_REQUEST_LENGTH, SIGNED_RESPONSE_CONTEXT, VERSION}; -use roughenough::sign::Signer; -use roughenough::merkle::*; - -use ring::rand; -use ring::rand::SecureRandom; - -use yaml_rust::YamlLoader; +use roughenough::{MIN_REQUEST_LENGTH, VERSION}; const MESSAGE: Token = Token(0); const STATUS: Token = Token(1); -fn create_ephemeral_key() -> Signer { - let rng = rand::SystemRandom::new(); - let mut seed = [0u8; 32]; - rng.fill(&mut seed).unwrap(); - - Signer::new(&seed) -} - -fn make_dele_bytes(ephemeral_key: &Signer) -> Result<Vec<u8>, Error> { - 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())?; - dele_msg.add_field(Tag::MINT, &zeros)?; - dele_msg.add_field(Tag::MAXT, &max)?; - - dele_msg.encode() -} - -fn make_key_and_cert(seed: &[u8]) -> (Signer, Vec<u8>) { - let mut long_term_key = Signer::new(seed); - let ephemeral_key = create_ephemeral_key(); - - info!( - "Long-term public key: {}", - hex::encode(long_term_key.public_key_bytes()) - ); - info!( - "Ephemeral public key: {}", - hex::encode(ephemeral_key.public_key_bytes()) - ); - - // Make DELE and sign it with long-term key - let dele_bytes = make_dele_bytes(&ephemeral_key).unwrap(); - let dele_signature = { - long_term_key.update(CERTIFICATE_CONTEXT.as_bytes()); - long_term_key.update(&dele_bytes); - long_term_key.sign() - }; - - // Create CERT - let cert_bytes = { - let mut cert_msg = RtMessage::new(2); - cert_msg.add_field(Tag::SIG, &dele_signature).unwrap(); - cert_msg.add_field(Tag::DELE, &dele_bytes).unwrap(); - - cert_msg.encode().unwrap() - }; - - (ephemeral_key, cert_bytes) -} - -struct SRep { - raw_bytes: Vec<u8>, - signature: Vec<u8>, -} - -fn make_srep(ephemeral_key: &mut Signer, root: &[u8]) -> SRep { - let mut radi = [0; 4]; - let mut midp = [0; 8]; - - // one second (in microseconds) - (&mut radi as &mut [u8]) - .write_u32::<LittleEndian>(1_000_000) - .unwrap(); - - // current epoch time in microseconds - let now = { - let tv = time::get_time(); - let secs = (tv.sec as u64) * 1_000_000; - let nsecs = (tv.nsec as u64) / 1_000; - - secs + nsecs - }; - (&mut midp as &mut [u8]) - .write_u64::<LittleEndian>(now) - .unwrap(); - - // Signed response SREP - let srep_bytes = { - let mut srep_msg = RtMessage::new(3); - srep_msg.add_field(Tag::RADI, &radi).unwrap(); - srep_msg.add_field(Tag::MIDP, &midp).unwrap(); - srep_msg.add_field(Tag::ROOT, root).unwrap(); - - srep_msg.encode().unwrap() - }; - - // signature on SREP - let srep_signature = { - ephemeral_key.update(SIGNED_RESPONSE_CONTEXT.as_bytes()); - ephemeral_key.update(&srep_bytes); - ephemeral_key.sign() - }; - - SRep { - raw_bytes: srep_bytes, - signature: srep_signature, - } -} - -fn make_response(srep: &SRep, cert_bytes: &[u8], path: &[u8], idx: u32) -> RtMessage { +fn make_response(srep: &RtMessage, cert_bytes: &[u8], path: &[u8], idx: u32) -> RtMessage { let mut index = [0; 4]; (&mut index as &mut [u8]) .write_u32::<LittleEndian>(idx) .unwrap(); + let sig_bytes = srep.get_field(Tag::SIG).unwrap(); + let srep_bytes = srep.get_field(Tag::SREP).unwrap(); + let mut response = RtMessage::new(5); - response.add_field(Tag::SIG, &srep.signature).unwrap(); - response.add_field(Tag::PATH, &path).unwrap(); - response.add_field(Tag::SREP, &srep.raw_bytes).unwrap(); + response.add_field(Tag::SIG, sig_bytes).unwrap(); + response.add_field(Tag::PATH, path).unwrap(); + response.add_field(Tag::SREP, srep_bytes).unwrap(); response.add_field(Tag::CERT, cert_bytes).unwrap(); response.add_field(Tag::INDX, &index).unwrap(); @@ -224,50 +103,10 @@ fn nonce_from_request(buf: &[u8], num_bytes: usize) -> Result<&[u8], Error> { } } -fn load_config(config_file: &str) -> (SocketAddr, Vec<u8>, u8) { - let mut infile = File::open(config_file).expect("failed to open config file"); - - let mut contents = String::new(); - infile - .read_to_string(&mut contents) - .expect("could not read config file"); - - let cfg = YamlLoader::load_from_str(&contents).expect("could not parse config file"); - - if cfg.len() != 1 { - panic!("empty or malformed config file"); - } - - let mut port: u16 = 0; - let mut iface: String = "unknown".to_string(); - let mut seed: String = "".to_string(); - let mut batch_size: u8 = 1; - - for (key, value) in cfg[0].as_hash().unwrap() { - match key.as_str().unwrap() { - "port" => port = value.as_i64().unwrap() as u16, - "interface" => iface = value.as_str().unwrap().to_string(), - "seed" => seed = value.as_str().unwrap().to_string(), - "batch_size" => batch_size = value.as_i64().unwrap() as u8, - _ => warn!("ignoring unknown config key '{}'", key.as_str().unwrap()), - } - } - - let addr = format!("{}:{}", iface, port); - let sock_addr: SocketAddr = addr.parse() - .expect(&format!("could not create socket address from {}", addr)); - - let binseed = - hex::decode(seed).expect("seed value invalid; 'seed' should be 32 byte hex value"); - - (sock_addr, binseed, batch_size) -} - fn polling_loop( - addr: &SocketAddr, - mut ephemeral_key: &mut Signer, + config: &ServerConfig, + online_key: &mut OnlineKey, cert_bytes: &[u8], - batch_size: u8, response_counter: Arc<AtomicUsize>, ) { let keep_running = Arc::new(AtomicBool::new(true)); @@ -276,12 +115,12 @@ fn polling_loop( ctrlc::set_handler(move || kr.store(false, Ordering::Release)) .expect("failed setting Ctrl-C handler"); - let socket = UdpSocket::bind(addr).expect("failed to bind to socket"); - let status_duration = Duration::from_secs(6); + let sock_addr = config.socket_addr(); + let socket = UdpSocket::bind(&sock_addr).expect("failed to bind to socket"); let poll_duration = Some(Duration::from_millis(100)); let mut timer: Timer<()> = Timer::default(); - timer.set_timeout(status_duration, ()); + timer.set_timeout(config.status_interval(), ()); let mut buf = [0u8; 65_536]; let mut events = Events::with_capacity(32); @@ -294,16 +133,16 @@ fn polling_loop( .unwrap(); let mut merkle = MerkleTree::new(); - let mut requests = Vec::with_capacity(batch_size as usize); + let mut requests = Vec::with_capacity(config.batch_size() as usize); macro_rules! check_ctrlc { - () => { - if !keep_running.load(Ordering::Acquire) { - warn!("Ctrl-C caught, exiting..."); - return; - } + () => { + if !keep_running.load(Ordering::Acquire) { + warn!("Ctrl-C caught, exiting..."); + return; + } + }; } - } loop { check_ctrlc!(); @@ -323,7 +162,7 @@ fn polling_loop( let resp_start = response_counter.load(Ordering::SeqCst); - for i in 0..batch_size { + for i in 0..config.batch_size() { match socket.recv_from(&mut buf) { Ok((num_bytes, src_addr)) => { if let Ok(nonce) = nonce_from_request(&buf, num_bytes) { @@ -358,8 +197,8 @@ fn polling_loop( break 'process_batch; } - let root = merkle.compute_root(); - let srep = make_srep(&mut ephemeral_key, &root); + let merkle_root = merkle.compute_root(); + let srep = online_key.make_srep(time::get_time(), &merkle_root); for (i, &(ref nonce, ref src_addr)) in requests.iter().enumerate() { let paths = merkle.get_paths(i); @@ -394,7 +233,7 @@ fn polling_loop( num_bad_requests ); - timer.set_timeout(status_duration, ()); + timer.set_timeout(config.status_interval(), ()); } _ => unreachable!(), @@ -416,10 +255,17 @@ pub fn main() { process::exit(1); } - let (addr, key_seed, batch_size) = load_config(&args.nth(1).unwrap()); - let (mut ephemeral_key, cert_bytes) = make_key_and_cert(&key_seed); + let config = FileConfig::from_file(&args.nth(1).unwrap()).expect("Unable to load config"); + + let mut online_key = OnlineKey::new(); + let mut long_term_key = LongTermKey::new(config.seed()); + let cert_bytes = long_term_key.make_cert(&online_key).encode().unwrap(); - info!("Server listening on {}", addr); + info!("Long-term public key : {}", long_term_key); + info!("Online public key : {}", online_key); + info!("Max response batch size : {}", config.batch_size()); + info!("Status updates every : {} seconds", config.status_interval().as_secs()); + info!("Server listening on : {}:{}", config.interface(), config.port()); let response_counter = Arc::new(AtomicUsize::new(0)); @@ -444,10 +290,9 @@ pub fn main() { } polling_loop( - &addr, - &mut ephemeral_key, + &config, + &mut online_key, &cert_bytes, - batch_size, response_counter.clone(), ); diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..3804642 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,182 @@ +// Copyright 2017-2018 int08h LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! +//! Ways to configure the Roughenough server. +//! +//! The [ServerConfig](trait.ServerConfig.html) trait specifies the required and optional +//! parameters available for configuring a Roughenoguh server instance. +//! +//! Implementations of `ServerConfig` obtain configurations from different back-end sources. +//! + +extern crate hex; + +use std::fs::File; +use std::io::Read; +use std::net::SocketAddr; +use std::time::Duration; +use yaml_rust::YamlLoader; +use Error; + +const DEFAULT_BATCH_SIZE: u8 = 64; +const DEFAULT_STATUS_INTERVAL: Duration = Duration::from_secs(600); + +/// +/// Specifies parameters needed to configure a Roughenough server. +/// +/// Parameters labeled "**Required**" must are always be provided and have no default value. +/// Parameters labeled "**Optional**" provide default values that can be overridden. +/// +/// * **`interface`** - [Required] IP address or interface name for listening to client requests +/// * **`port`** - [Required] UDP port to listen for requests +/// * **`seed`** - [Required] A 32-byte hexadecimal value used to generate the server's long-term +/// key pair. **This is a secret value and must be un-guessable**, +/// treat it with care. +/// * **`batch_size`** - [Optional] The maximum number of requests to process in one batch. All +/// nonces in a batch are used to build a Merkle tree, the root of which +/// is signed. Default is 64 requests per batch. +/// * **`status_interval`** - [Optional] Amount of time between each logged status update. +/// Default is 600 seconds (10 minutes). +/// +/// Implementations of this trait obtain a valid configuration from different back- +/// end sources. +/// +/// See: +/// * [FileConfig](struct.FileConfig.html) +/// +pub trait ServerConfig { + /// [Required] IP address or interface name to listen for client requests + fn interface(&self) -> &str; + + /// [Required] UDP port to listen for requests + fn port(&self) -> u16; + + /// [Required] A 32-byte hexadecimal value used to generate the server's + /// long-term key pair. **This is a secret value and must be un-guessable**, + /// treat it with care. + fn seed(&self) -> &[u8]; + + /// [Optional] The maximum number of requests to process in one batch. All + /// nonces in a batch are used to build a Merkle tree, the root of which is signed. + /// Default is 64 requests per batch. + fn batch_size(&self) -> u8; + + /// [Optional] Amount of time between each logged status update. + /// Default is 600 seconds (10 minutes). + fn status_interval(&self) -> Duration; + + /// Convenience function to create a `SocketAddr` from the provided `interface` and `port` + fn socket_addr(&self) -> SocketAddr; +} + +/// +/// Read the configuration from a YAML file +/// +/// Example minimal config file with only the required parameters from +/// [ServerConfig](trait.ServerConfig.html): +/// +/// ```yaml +/// interface: 127.0.0.1 +/// port: 8686 +/// seed: f61075c988feb9cb700a4a6a3291bfbc9cab11b9c9eca8c802468eb38a43d7d3 +/// ``` +/// +pub struct FileConfig { + port: u16, + interface: String, + seed: Vec<u8>, + batch_size: u8, + status_interval: Duration, +} + +impl FileConfig { + pub fn from_file(config_file: &str) -> Result<Self, Error> { + let mut infile = File::open(config_file).expect("failed to open config file"); + + let mut contents = String::new(); + infile + .read_to_string(&mut contents) + .expect("could not read config file"); + + let cfg = YamlLoader::load_from_str(&contents).expect("could not parse config file"); + + if cfg.len() != 1 { + return Err(Error::InvalidConfiguration( + "Empty or malformed config file".to_string(), + )); + } + + let mut config = FileConfig { + port: 0, + interface: "unknown".to_string(), + seed: Vec::new(), + batch_size: DEFAULT_BATCH_SIZE, + status_interval: DEFAULT_STATUS_INTERVAL, + }; + + for (key, value) in cfg[0].as_hash().unwrap() { + match key.as_str().unwrap() { + "port" => config.port = value.as_i64().unwrap() as u16, + "interface" => config.interface = value.as_str().unwrap().to_string(), + "batch_size" => config.batch_size = value.as_i64().unwrap() as u8, + "seed" => { + let val = value.as_str().unwrap().to_string(); + config.seed = hex::decode(val) + .expect("seed value invalid; 'seed' should be 32 byte hex value"); + } + "status_interval" => { + let val = value.as_i64().expect("status_interval value invalid"); + config.status_interval = Duration::from_secs(val as u64) + } + _ => { + return Err(Error::InvalidConfiguration(format!( + "unknown config key '{}'", + key.as_str().unwrap() + ))); + } + } + } + + Ok(config) + } +} + +impl ServerConfig for FileConfig { + fn interface(&self) -> &str { + self.interface.as_ref() + } + + fn port(&self) -> u16 { + self.port + } + + fn seed(&self) -> &[u8] { + &self.seed + } + + fn batch_size(&self) -> u8 { + self.batch_size + } + + fn status_interval(&self) -> Duration { + self.status_interval + } + + fn socket_addr(&self) -> SocketAddr { + let addr = format!("{}:{}", self.interface, self.port); + addr.parse() + .expect(&format!("could not create socket address from {}", addr)) + } +} diff --git a/src/error.rs b/src/error.rs index 5833f2d..b681f33 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,4 @@ -// Copyright 2017 int08h LLC +// Copyright 2017-2018 int08h LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -48,6 +48,9 @@ pub enum Error { /// Otherwise invalid request InvalidRequest, + + /// Runtime configuration is invalid for the reason provided + InvalidConfiguration(String), } impl From<std::io::Error> for Error { diff --git a/src/keys.rs b/src/keys.rs new file mode 100644 index 0000000..2fadb00 --- /dev/null +++ b/src/keys.rs @@ -0,0 +1,147 @@ +// Copyright 2017-2018 int08h LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! +//! Representations of Roughtime's online and long-term Ed25519 keys +//! + +use message::RtMessage; +use sign::Signer; +use tag::Tag; +use time::Timespec; + +use byteorder::{LittleEndian, WriteBytesExt}; + +use super::{CERTIFICATE_CONTEXT, SIGNED_RESPONSE_CONTEXT}; +use std::fmt; +use std::fmt::Formatter; + +/// +/// Represents the delegated Roughtime ephemeral online key. +/// +pub struct OnlineKey { + signer: Signer, +} + +impl OnlineKey { + pub fn new() -> Self { + OnlineKey { + signer: Signer::new(), + } + } + + /// Create a DELE message containing the public key of this online key + pub fn make_dele(&self) -> RtMessage { + let zeros = [0u8; 8]; + let max = [0xff; 8]; + let pub_key_bytes = self.signer.public_key_bytes(); + + let mut dele_msg = RtMessage::new(3); + dele_msg.add_field(Tag::PUBK, pub_key_bytes).unwrap(); + dele_msg.add_field(Tag::MINT, &zeros).unwrap(); + dele_msg.add_field(Tag::MAXT, &max).unwrap(); + + dele_msg + } + + /// Create an SREP response containing the provided time and Merkle root, + /// signed by this online key. + pub fn make_srep(&mut self, now: Timespec, merkle_root: &[u8]) -> RtMessage { + let mut radi = [0; 4]; + let mut midp = [0; 8]; + + // one second (in microseconds) + (&mut radi as &mut [u8]) + .write_u32::<LittleEndian>(1_000_000) + .unwrap(); + + // current epoch time in microseconds + let midp_time = { + let secs = (now.sec as u64) * 1_000_000; + let nsecs = (now.nsec as u64) / 1_000; + + secs + nsecs + }; + (&mut midp as &mut [u8]) + .write_u64::<LittleEndian>(midp_time) + .unwrap(); + + // Signed response SREP + let srep_bytes = { + let mut srep_msg = RtMessage::new(3); + srep_msg.add_field(Tag::RADI, &radi).unwrap(); + srep_msg.add_field(Tag::MIDP, &midp).unwrap(); + srep_msg.add_field(Tag::ROOT, merkle_root).unwrap(); + + srep_msg.encode().unwrap() + }; + + // signature on SREP + let srep_signature = { + self.signer.update(SIGNED_RESPONSE_CONTEXT.as_bytes()); + self.signer.update(&srep_bytes); + self.signer.sign() + }; + + let mut result = RtMessage::new(2); + result.add_field(Tag::SIG, &srep_signature).unwrap(); + result.add_field(Tag::SREP, &srep_bytes).unwrap(); + + result + } +} + +impl fmt::Display for OnlineKey { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}", self.signer) + } +} + +/// +/// Represents the server's long-term identity. +/// +pub struct LongTermKey { + signer: Signer, +} + +impl LongTermKey { + pub fn new(seed: &[u8]) -> Self { + LongTermKey { + signer: Signer::from_seed(seed), + } + } + + /// Create a CERT message with a DELE containing the provided online key + /// and a SIG of the DELE value signed by the long-term key + pub fn make_cert(&mut self, online_key: &OnlineKey) -> RtMessage { + let dele_bytes = online_key.make_dele().encode().unwrap(); + + self.signer.update(CERTIFICATE_CONTEXT.as_bytes()); + self.signer.update(&dele_bytes); + + let dele_signature = self.signer.sign(); + + let mut cert_msg = RtMessage::new(2); + cert_msg.add_field(Tag::SIG, &dele_signature).unwrap(); + cert_msg.add_field(Tag::DELE, &dele_bytes).unwrap(); + + cert_msg + } +} + +impl fmt::Display for LongTermKey { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}", self.signer) + } +} @@ -1,4 +1,4 @@ -// Copyright 2017 int08h LLC +// Copyright 2017-2018 int08h LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -37,26 +37,9 @@ //! //! # Server //! -//! A Roughtime server implementation is in `src/bin/server.rs`. The server is -//! configured via a yaml file: -//! -//! ```yaml -//! interface: 127.0.0.1 -//! port: 8686 -//! seed: f61075c988feb9cb700a4a6a3291bfbc9cab11b9c9eca8c802468eb38a43d7d3 -//! batch_size: 64 -//! ``` -//! -//! Where: -//! -//! * **interface** - IP address or interface name for listening to client requests -//! * **port** - UDP port to listen to requests -//! * **seed** - A 32-byte hexadecimal value used as the seed to generate the -//! server's long-term key pair. **This is a secret value**, treat it -//! with care. -//! * **batch_size** - The number of requests to process in one batch. All nonces -//! in a batch are used to build a Merkle tree, the root of which -//! is signed. +//! The Roughtime server implementation is in `src/bin/server.rs`. The server is +//! configured via a YAML config file. See [FileConfig](config/struct.FileConfig.html) +//! for details of the configuration parameters. //! //! To run the server: //! @@ -66,20 +49,25 @@ //! extern crate byteorder; +extern crate core; +extern crate time; +extern crate yaml_rust; mod error; -mod tag; mod message; +mod tag; -pub mod sign; +pub mod config; +pub mod keys; pub mod merkle; +pub mod sign; pub use error::Error; -pub use tag::Tag; pub use message::RtMessage; +pub use tag::Tag; /// Version of Roughenough -pub const VERSION: &str = "1.0.4"; +pub const VERSION: &str = "1.0.5"; // Constants and magic numbers of the Roughtime protocol diff --git a/src/merkle.rs b/src/merkle.rs index ba52f96..e34a5b4 100644 --- a/src/merkle.rs +++ b/src/merkle.rs @@ -1,4 +1,4 @@ -// Copyright 2018 int08h LLC +// Copyright 2017-2018 int08h LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -18,8 +18,8 @@ extern crate ring; -use super::{HASH_LENGTH, TREE_LEAF_TWEAK, TREE_NODE_TWEAK}; use self::ring::digest; +use super::{HASH_LENGTH, TREE_LEAF_TWEAK, TREE_NODE_TWEAK}; type Data = Vec<u8>; type Hash = Data; @@ -32,7 +32,6 @@ pub struct MerkleTree { } impl MerkleTree { - /// /// Create a new empty Merkle Tree /// diff --git a/src/message.rs b/src/message.rs index 2927295..bd11bf7 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1,4 +1,4 @@ -// Copyright 2017 int08h LLC +// Copyright 2017-2018 int08h LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,13 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::io::{Cursor, Read, Write}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; -use std::iter::once; use std::collections::HashMap; +use std::io::{Cursor, Read, Write}; +use std::iter::once; -use tag::Tag; use error::Error; +use tag::Tag; /// /// A Roughtime protocol message; a map of u32 tags to arbitrary byte-strings. @@ -64,7 +64,7 @@ impl RtMessage { match num_tags { 0 => Ok(RtMessage::new(0)), 1 => RtMessage::single_tag_message(bytes, &mut msg), - 2 ... 1024 => RtMessage::multi_tag_message(num_tags, bytes, &mut msg), + 2...1024 => RtMessage::multi_tag_message(num_tags, bytes, &mut msg), _ => Err(Error::InvalidNumTags(num_tags)), } } @@ -180,6 +180,21 @@ impl RtMessage { Ok(()) } + /// Retrieve the value associated with `tag`, if present. + /// + /// ## Arguments + /// + /// * `tag` - The [`Tag`](enum.Tag.html) to try and retrieve. + /// + pub fn get_field(&self, tag: Tag) -> Option<&[u8]> { + for (i, self_tag) in self.tags.iter().enumerate() { + if tag == *self_tag { + return Some(&self.values[i]); + } + } + return None; + } + /// Returns the number of tag/value pairs in the message pub fn num_fields(&self) -> u32 { self.tags.len() as u32 @@ -272,9 +287,9 @@ impl RtMessage { #[cfg(test)] mod test { - use std::io::{Cursor, Read}; use byteorder::{LittleEndian, ReadBytesExt}; use message::*; + use std::io::{Cursor, Read}; use tag::Tag; #[test] @@ -410,6 +425,20 @@ mod test { } #[test] + fn retrieve_message_values() { + let val1 = b"aabbccddeeffgg"; + let val2 = b"0987654321"; + + let mut msg = RtMessage::new(2); + msg.add_field(Tag::NONC, val1).unwrap(); + msg.add_field(Tag::MAXT, val2).unwrap(); + + assert_eq!(msg.get_field(Tag::NONC), Some(val1.as_ref())); + assert_eq!(msg.get_field(Tag::MAXT), Some(val2.as_ref())); + assert_eq!(msg.get_field(Tag::CERT), None); + } + + #[test] #[should_panic(expected = "InvalidAlignment")] fn from_bytes_offset_past_end_of_message() { let mut msg = RtMessage::new(2); diff --git a/src/sign.rs b/src/sign.rs index a990a2f..ec77dc5 100644 --- a/src/sign.rs +++ b/src/sign.rs @@ -1,4 +1,4 @@ -// Copyright 2017 int08h LLC +// Copyright 2017-2018 int08h LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -25,8 +25,13 @@ extern crate untrusted; use self::ring::signature; use self::ring::signature::Ed25519KeyPair; +use self::ring::rand; +use self::ring::rand::SecureRandom; use self::untrusted::Input; +use std::fmt; +use std::fmt::Formatter; + /// A multi-step (init-update-finish) interface for verifying an /// Ed25519 signature #[derive(Debug)] @@ -67,9 +72,18 @@ pub struct Signer { } impl Signer { - pub fn new(seed: &[u8]) -> Self { + pub fn new() -> Self { + let rng = rand::SystemRandom::new(); + let mut seed = [0u8; 32]; + rng.fill(&mut seed).unwrap(); + + Signer::from_seed(&seed) + } + + pub fn from_seed(seed: &[u8]) -> Self { + let seed_input = Input::from(seed); Signer { - key_pair: Ed25519KeyPair::from_seed_unchecked(Input::from(seed)).unwrap(), + key_pair: Ed25519KeyPair::from_seed_unchecked(seed_input).unwrap(), buf: Vec::with_capacity(256), } } @@ -91,6 +105,23 @@ impl Signer { } } +impl fmt::Display for Signer { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}", hex::encode(self.public_key_bytes())) + } +} + +impl fmt::Debug for Signer { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!( + f, + "Signer({}, {:?})", + hex::encode(self.public_key_bytes()), + self.buf + ) + } +} + #[cfg_attr(rustfmt, rustfmt_skip)] // rustfmt errors on the long signature strings #[cfg(test)] mod test { @@ -138,7 +169,7 @@ mod test { "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b" ).unwrap(); - let mut s = Signer::new(&seed); + let mut s = Signer::from_seed(&seed); let sig = s.sign(); assert_eq!(sig, expected_sig); } @@ -154,7 +185,7 @@ mod test { "d9868d52c2bebce5f3fa5a79891970f309cb6591e3e1702a70276fa97c24b3a8e58606c38c9758529da50ee31b8219cba45271c689afa60b0ea26c99db19b00c" ).unwrap(); - let mut s = Signer::new(&seed); + let mut s = Signer::from_seed(&seed); s.update(&message); let sig = s.sign(); assert_eq!(sig, expected_sig); @@ -167,7 +198,7 @@ mod test { let message = "Hello world".as_bytes(); - let mut signer = Signer::new(&seed); + let mut signer = Signer::from_seed(&seed); signer.update(&message); let signature = signer.sign(); @@ -1,4 +1,4 @@ -// Copyright 2017 int08h LLC +// Copyright 2017-2018 int08h LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. |