summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml2
-rw-r--r--README.md78
-rw-r--r--src/bin/server.rs105
3 files changed, 140 insertions, 45 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 45a419a..cc717af 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"] }
diff --git a/README.md b/README.md
index 25c2b63..c4fa710 100644
--- a/README.md
+++ b/README.md
@@ -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);
}
}