diff options
author | Stuart Stock <stuart@int08h.com> | 2017-07-06 16:10:03 -0500 |
---|---|---|
committer | Stuart Stock <stuart@int08h.com> | 2017-07-06 16:10:03 -0500 |
commit | 9046a2e47472af0e919ac20ad6c48afd0ed2da8d (patch) | |
tree | 2dafb638ae4dc2d6c227e3299d634c89dd4b61d2 | |
parent | 3cb57d473b6b26f81c2ded1dae326c971921b84e (diff) | |
download | roughenough-9046a2e47472af0e919ac20ad6c48afd0ed2da8d.zip |
read from config; updated README
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | README.md | 78 | ||||
-rw-r--r-- | src/bin/server.rs | 105 |
3 files changed, 140 insertions, 45 deletions
@@ -4,6 +4,7 @@ version = "0.1.0" repository = "https://github.com/int08h/roughenough" authors = ["Stuart Stock <stuart@int08h.com>"] license = "Apache-2.0" +description = "A Roughtime secure time sync server written in Rust" [dependencies] byteorder = "1" @@ -12,5 +13,6 @@ untrusted = "0.5.0" time = "0.1" log = "0.3" fern = "0.4" +yaml-rust = "0.3.5" ctrlc = { version = "3.0", features = ["termination"] } @@ -1,21 +1,10 @@ # Roughenough -**Roughenough** is a Rust [Roughtime](https://roughtime.googlesource.com/roughtime) secure time -synchronization server. +**Roughenough** is a [Roughtime](https://roughtime.googlesource.com/roughtime) secure time +synchronization server implemented in Rust. -It is a **work in progress**. Current status: - -* Server is functionally complete: it parses requests and generates valid Roughtime responses. -* Still TODO: - * Run-time configuration (udp port, listening interface, etc) - * Loading the long-term key - * Better operational ergonomics - - -## About the Roughtime Protocol -[Roughtime](https://roughtime.googlesource.com/roughtime) is a protocol that aims to achieve rough -time synchronisation in a secure way that doesn't depend on any particular time server, and in such -a way that, if a time server does misbehave, clients end up with cryptographic proof of it. It was -created by Adam Langley and Robert Obryk. +The server is functionally complete: it parses client requests and generates valid Roughtime responses. +Rough-edges remain, particularly in error-handling. See +[Limitations](#limitations) below. Contributions welcome. ## Links * [Roughenough Github repo](https://github.com/int08h/roughenough) @@ -23,19 +12,64 @@ created by Adam Langley and Robert Obryk. * My blog posts [describing Roughtime features](https://int08h.com/post/to-catch-a-lying-timeserver/) and exploring the [details of Roughtime messages](https://int08h.com/post/roughtime-message-anatomy/). -## Building +## Building and Running -Use `cargo` to compile and run the server binary: +### Starting the Server ```bash -$ cargo run --bin server +$ cargo run --release --bin server /path/to/config.file +... +Thu Jul 6 15:56:12 2017 [INFO] Roughenough server v0.1 starting +Thu Jul 6 15:56:12 2017 [INFO] Long-term public key: d0756ee69ff5fe96cbcf9273208fec53124b1dd3a24d3910e07c7c54e2473012 +Thu Jul 6 15:56:12 2017 [INFO] Ephemeral public key: 7e105566cb7e2e5526b807c4513ef82a417d7dd2556cd6afe6a148e76ac809a6 +Thu Jul 6 15:56:12 2017 [INFO] Server listening on 127.0.0.1:8686 + +``` + +### Configuration File + +The server is configured via a YAML file: + +```yaml +interface: 127.0.0.1 +port: 8686 +seed: f61075c988feb9cb700a4a6a3291bfbc9cab11b9c9eca8c802468eb38a43d7d3 ``` +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 key pair. **This is a secret value**, treat it + with care. + +### Stopping the Server +Use Ctrl-C or `kill` the process. + ## Limitations -Roughenough does not implement the response fault-injection Roughtime ecosystem feature. -On-line delegated key rotation is also not implemented; the server must be restarted to -generate a new delegated key. +Roughtime features not implemented: + +* On-line key rotation. The server must be restarted to generate a new delegated key. +* Ecosystem-style response fault injection. +* Multi-request Merkle tree is not built. Each request gets its own response with + ROOT empty and INDX zero. + +Error-handling is not robust. There are many `unwrap()`'s and `expect()`'s in the request handling path. + +The server is a dead simple single-threaded `recv_from` loop. `mio` and `tokio` are +intentionally avoided to keep the implementation straightforward and maximize +comprehensibility by newbie Rustaceans. Blazing async ninja speed is not a goal. + +Per-request heap allocations could be reduced: a few `Vec`'s could be replaced by +lifetime scoped slices. + +## About the Roughtime Protocol +[Roughtime](https://roughtime.googlesource.com/roughtime) is a protocol that aims to achieve rough +time synchronisation in a secure way that doesn't depend on any particular time server, and in such +a way that, if a time server does misbehave, clients end up with cryptographic proof of it. It was +created by Adam Langley and Robert Obryk. ## Contributors * Stuart Stock, original author and current maintainer (stuart {at} int08h.com) diff --git a/src/bin/server.rs b/src/bin/server.rs index 03e8b54..63a053e 100644 --- a/src/bin/server.rs +++ b/src/bin/server.rs @@ -1,6 +1,28 @@ //! //! Roughtime server //! +//! # Configuration +//! The `roughenough` server is configured via a config file: +//! +//! ```yaml +//! interface: 127.0.0.1 +//! port: 8686 +//! seed: f61075c988feb9cb700a4a6a3291bfbc9cab11b9c9eca8c802468eb38a43d7d3 +//! ``` +//! +//! 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. +//! +//! # Running the Server +//! +//! ```bash +//! $ cargo run --release --bin server /path/to/config.file +//! ``` extern crate byteorder; extern crate core; @@ -10,15 +32,22 @@ extern crate time; extern crate untrusted; extern crate fern; extern crate ctrlc; +extern crate yaml_rust; #[macro_use] extern crate log; +use std::env; use std::io; +use std::process; +use std::fs::File; +use std::io::Read; use std::net::UdpSocket; use std::time::Duration; use std::sync::Arc; use std::sync::atomic::{AtomicBool, Ordering}; +use byteorder::{LittleEndian, WriteBytesExt}; + use roughenough::{RtMessage, Tag, Error}; use roughenough::{CERTIFICATE_CONTEXT, MIN_REQUEST_LENGTH, SIGNED_RESPONSE_CONTEXT, TREE_LEAF_TWEAK}; use roughenough::hex::*; @@ -27,17 +56,11 @@ use roughenough::sign::Signer; use ring::{digest, rand}; use ring::rand::SecureRandom; -use byteorder::{LittleEndian, WriteBytesExt}; +use yaml_rust::YamlLoader; const SERVER_VERSION: &'static str = "0.1"; -fn get_long_term_key() -> Signer { - // TODO: read from config - let seed = [b'x'; 32]; - Signer::new(&seed) -} - -fn make_ephemeral_key() -> Signer { +fn create_ephemeral_key() -> Signer { let rng = rand::SystemRandom::new(); let mut seed = [0u8; 32]; rng.fill(&mut seed).unwrap(); @@ -136,6 +159,7 @@ fn make_response(ephemeral_key: &mut Signer, cert_bytes: &[u8], nonce: &[u8]) -> response } +// extract the client's nonce from its request fn nonce_from_request(buf: &[u8], num_bytes: usize) -> Result<&[u8], Error> { if num_bytes < MIN_REQUEST_LENGTH as usize { return Err(Error::RequestTooShort); @@ -169,33 +193,68 @@ fn init_logging() { .unwrap(); } +fn load_config(config_file: &str) -> (String, u16, Vec<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(); + + 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(), + _ => warn!("ignoring unknown config key '{}'", key.as_str().unwrap()) + } + } + + let binseed = seed.from_hex() + .expect("seed value invalid; 'seed' should be 32 byte hex value"); + + (iface, port, binseed) +} + fn main() { init_logging(); - // TODO: configure - let server = "127.0.01"; - let port = "8686"; - info!("Roughenough server v{} starting", SERVER_VERSION); - let mut lt_key = get_long_term_key(); - let mut ephemeral_key = make_ephemeral_key(); + let mut args = env::args(); + if args.len() != 2 { + error!("Usage: server /path/to/config.file"); + process::exit(1); + } + + let (iface, port, seed) = load_config(&args.nth(1).unwrap()); + + let mut lt_key = Signer::new(&seed); + let mut ephemeral_key = create_ephemeral_key(); + let cert_bytes = make_cert(&mut lt_key, &ephemeral_key).encode().unwrap(); info!("Long-term public key: {}", lt_key.public_key_bytes().to_hex()); info!("Ephemeral public key: {}", ephemeral_key.public_key_bytes().to_hex()); + info!("Server listening on {}:{}", iface, port); - let cert_msg = make_cert(&mut lt_key, &ephemeral_key); - let cert_bytes = cert_msg.encode().unwrap(); - - let socket = UdpSocket::bind(format!("{}:{}", server, port)).expect("failed to bind to socket"); + let socket = UdpSocket::bind(format!("{}:{}", iface, port)).expect("failed to bind to socket"); socket .set_read_timeout(Some(Duration::from_millis(100))) .expect("could not set read timeout"); - info!("operating on port {}", port); - let mut buf = [0u8; 65536]; - let mut loops = 0u64; + let mut loop_count = 0u64; let mut responses = 0u64; let mut bad_requests = 0u64; @@ -229,8 +288,8 @@ fn main() { } } Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => { - loops += 1; - if loops % 600 == 0 { + loop_count += 1; + if loop_count % 600 == 0 { info!("responses {}, invalid requests {}", responses, bad_requests); } } |