summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md13
-rw-r--r--example.cfg1
-rw-r--r--src/bin/client.rs10
-rw-r--r--src/bin/server.rs251
-rw-r--r--src/config.rs182
-rw-r--r--src/error.rs5
-rw-r--r--src/keys.rs147
-rw-r--r--src/lib.rs38
-rw-r--r--src/merkle.rs5
-rw-r--r--src/message.rs41
-rw-r--r--src/sign.rs43
-rw-r--r--src/tag.rs2
12 files changed, 481 insertions, 257 deletions
diff --git a/README.md b/README.md
index 8bf6c34..fc8f83e 100644
--- a/README.md
+++ b/README.md
@@ -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)
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 8231aa6..5dc3a8c 100644
--- a/src/lib.rs
+++ b/src/lib.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.
@@ -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();
diff --git a/src/tag.rs b/src/tag.rs
index 34223df..14fd39e 100644
--- a/src/tag.rs
+++ b/src/tag.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.