Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement RFC draft 11 #41

Merged
merged 6 commits into from
Sep 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "roughenough"
version = "1.3.0-draft8"
version = "1.3.0-draft11"
repository = "https://github.com/int08h/roughenough"
authors = ["Stuart Stock <[email protected]>", "Aaron Hill <[email protected]>"]
license = "Apache-2.0"
Expand Down
44 changes: 29 additions & 15 deletions src/bin/roughenough-client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@ use clap::{App, Arg};
use data_encoding::{Encoding, BASE64, HEXLOWER_PERMISSIVE};
use ring::rand;
use ring::rand::SecureRandom;

use roughenough::key::LongTermKey;
use roughenough::merkle::MerkleTree;
use roughenough::sign::Verifier;
use roughenough::version::Version;
use roughenough::{
roughenough_version, Error, RtMessage, Tag, CERTIFICATE_CONTEXT, RFC_REQUEST_FRAME_BYTES,
roughenough_version, Error, RtMessage, Tag, CERTIFICATE_CONTEXT, REQUEST_FRAMING_BYTES,
SIGNED_RESPONSE_CONTEXT,
};

Expand All @@ -52,17 +52,22 @@ fn create_nonce(ver: Version) -> Nonce {
rng.fill(&mut nonce).unwrap();
nonce.to_vec()
}
Version::Rfc | Version::RfcDraft8 => {
Version::Rfc | Version::RfcDraft11 => {
let mut nonce = [0u8; 32];
rng.fill(&mut nonce).unwrap();
nonce.to_vec()
}
}
}

fn make_request(ver: Version, nonce: &Nonce, text_dump: bool) -> Vec<u8> {
fn make_request(ver: Version, nonce: &Nonce, text_dump: bool, pub_key: &Option<Vec<u8>>) -> Vec<u8> {
let mut msg = RtMessage::with_capacity(3);

let srv_value = match pub_key {
None => None,
Some(ref pk) => Some(LongTermKey::calc_srv_value(&pk)),
};

match ver {
Version::Classic => {
msg.add_field(Tag::NONC, nonce).unwrap();
Expand All @@ -81,7 +86,11 @@ fn make_request(ver: Version, nonce: &Nonce, text_dump: bool) -> Vec<u8> {

msg.encode().unwrap()
}
Version::Rfc | Version::RfcDraft8 => {
Version::Rfc | Version::RfcDraft11 => {
if srv_value.is_some() {
let val = srv_value.as_ref().unwrap();
msg.add_field(Tag::SRV, val).unwrap();
}
msg.add_field(Tag::VER, ver.wire_bytes()).unwrap();
msg.add_field(Tag::NONC, nonce).unwrap();
msg.add_field(Tag::ZZZZ, &[]).unwrap();
Expand All @@ -90,6 +99,11 @@ fn make_request(ver: Version, nonce: &Nonce, text_dump: bool) -> Vec<u8> {
let padding: Vec<u8> = (0..padding_needed).map(|_| 0).collect();

msg.clear();

if srv_value.is_some() {
let val = srv_value.as_ref().unwrap();
msg.add_field(Tag::SRV, val).unwrap();
}
msg.add_field(Tag::VER, ver.wire_bytes()).unwrap();
msg.add_field(Tag::NONC, nonce).unwrap();
msg.add_field(Tag::ZZZZ, &padding).unwrap();
Expand All @@ -106,15 +120,15 @@ fn make_request(ver: Version, nonce: &Nonce, text_dump: bool) -> Vec<u8> {
fn receive_response(ver: Version, buf: &[u8], buf_len: usize) -> RtMessage {
match ver {
Version::Classic => RtMessage::from_bytes(&buf[0..buf_len]).unwrap(),
Version::Rfc | Version::RfcDraft8 => {
Version::Rfc | Version::RfcDraft11 => {
verify_framing(&buf).unwrap();
RtMessage::from_bytes(&buf[12..buf_len]).unwrap()
}
}
}

fn verify_framing(buf: &[u8]) -> Result<(), Error> {
if &buf[0..8] != RFC_REQUEST_FRAME_BYTES {
if &buf[0..8] != REQUEST_FRAMING_BYTES {
eprintln!("RFC response is missing framing header bytes");
return Err(Error::InvalidResponse);
}
Expand Down Expand Up @@ -146,8 +160,8 @@ fn stress_test_forever(ver: Version, addr: &SocketAddr) -> ! {
} else {
"0.0.0.0:0"
})
.expect("Couldn't open UDP socket");
let request = make_request(ver, &nonce, false);
.expect("Couldn't open UDP socket");
let request = make_request(ver, &nonce, false, &None);
loop {
socket.send_to(&request, addr).unwrap();
}
Expand Down Expand Up @@ -262,7 +276,7 @@ impl ResponseHandler {

let hash = match self.version {
Version::Classic => MerkleTree::new_sha512_classic(),
Version::Rfc | Version::RfcDraft8 => MerkleTree::new_sha512_ietf(),
Version::Rfc | Version::RfcDraft11 => MerkleTree::new_sha512_ietf(),
}
.root_from_paths(index as usize, &self.nonce, paths);

Expand Down Expand Up @@ -333,7 +347,7 @@ fn main() {
.short("k")
.long("public-key")
.takes_value(true)
.help("The server public key used to validate responses. If unset, no validation will be performed."))
.help("The server public key used to validate responses. When set, will add SRV tag to request to bind request to the expected public key. If unset, no validation will be performed."))
.arg(Arg::with_name("time-format")
.short("f")
.long("time-format")
Expand Down Expand Up @@ -412,7 +426,7 @@ fn main() {
let version = match protocol {
0 => Version::Classic,
1 => Version::Rfc,
8 => Version::RfcDraft8,
11 => Version::RfcDraft11,
_ => panic!(
"Invalid protocol '{}'; valid values are 0, 1, or 8",
protocol
Expand All @@ -438,8 +452,8 @@ fn main() {
} else {
"0.0.0.0:0"
})
.expect("Couldn't open UDP socket");
let request = make_request(version, &nonce, text_dump);
.expect("Couldn't open UDP socket");
let request = make_request(version, &nonce, text_dump, &pub_key);

if let Some(f) = file_for_requests.as_mut() {
f.write_all(&request).expect("Failed to write to file!")
Expand Down Expand Up @@ -499,7 +513,7 @@ fn main() {
let nsecs = (midpoint - (seconds * 10_u64.pow(6))) * 10_u64.pow(3);
(seconds, nsecs as u32)
}
Version::Rfc | Version::RfcDraft8 => (midpoint, 0),
Version::Rfc | Version::RfcDraft11 => (midpoint, 0),
};

let verify_str = if verified { "Yes" } else { "No" };
Expand Down
8 changes: 4 additions & 4 deletions src/bin/roughenough-server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,16 @@
#[macro_use]
extern crate log;

use std::{env, io, thread};
use std::process;
use std::sync::{Arc, Mutex};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::{env, io, thread};

use log::LevelFilter;
use mio::Events;
use mio::net::UdpSocket;
use net2::UdpBuilder;
use mio::Events;
use net2::unix::UnixUdpBuilderExt;
use net2::UdpBuilder;
use once_cell::sync::Lazy;
use simple_logger::SimpleLogger;

Expand Down
6 changes: 6 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ pub enum Error {
/// Request was less than 1024 bytes
RequestTooShort,

/// Request was larger than 1500 bytes
RequestTooLarge,

/// Offset was not 32-bit aligned
InvalidAlignment(u32),

Expand All @@ -64,6 +67,9 @@ pub enum Error {

/// Sending response to a client request has failed
SendingResponseFailed,

/// The request's SRV value and this server's SRV value do not match
SrvMismatch,
}

impl From<std::io::Error> for Error {
Expand Down
26 changes: 22 additions & 4 deletions src/key/longterm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,39 @@
//! Represents the server's long-term identity.
//!

use std::fmt;
use std::fmt::Formatter;

use crate::key::OnlineKey;
use crate::message::RtMessage;
use crate::sign::Signer;
use crate::tag::Tag;
use crate::CERTIFICATE_CONTEXT;
use ring::digest;
use ring::digest::SHA512;
use std::fmt;
use std::fmt::Formatter;

///
/// Represents the server's long-term identity.
///
pub struct LongTermKey {
signer: Signer,
srv_value: Vec<u8>,
}

impl LongTermKey {
pub fn calc_srv_value(pubkey: &[u8]) -> Vec<u8> {
let mut ctx = digest::Context::new(&SHA512);
ctx.update(Tag::HASH_PREFIX_SRV);
ctx.update(pubkey);
ctx.finish().as_ref()[0..32].to_vec()
}

pub fn new(seed: &[u8]) -> Self {
let signer = Signer::from_seed(seed);
let srv_value = LongTermKey::calc_srv_value(signer.public_key_bytes());

LongTermKey {
signer: Signer::from_seed(seed),
signer,
srv_value,
}
}

Expand All @@ -60,6 +73,11 @@ impl LongTermKey {
pub fn public_key(&self) -> &[u8] {
self.signer.public_key_bytes()
}

/// Return the SRV tag value, which is SHA512[0:32] over (0xff || public key)
pub fn srv_value(&self) -> &[u8] {
self.srv_value.as_ref()
}
}

impl fmt::Display for LongTermKey {
Expand Down
7 changes: 4 additions & 3 deletions src/key/online.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,10 @@ impl OnlineKey {
let mut radi = [0; 4];
let mut midp = [0; 8];

// RADI is hard coded at 5 seconds (providing a 10-second measurement window overall)
let radi_time = match ver {
Version::Classic => 2_000_000, // two seconds in microseconds
Version::Rfc | Version::RfcDraft8 => 2, // two seconds
Version::Classic => 5_000_000, // five seconds in microseconds
Version::Rfc | Version::RfcDraft11 => 5, // five seconds
};

(&mut radi as &mut [u8])
Expand All @@ -91,7 +92,7 @@ impl OnlineKey {

let midp_time = match ver {
Version::Classic => self.classic_midp(now),
Version::Rfc | Version::RfcDraft8 => self.rfc_midp(now),
Version::Rfc | Version::RfcDraft11 => self.rfc_midp(now),
};

(&mut midp as &mut [u8])
Expand Down
20 changes: 7 additions & 13 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ pub mod stats;
pub mod version;

/// Version of Roughenough
pub const VERSION: &str = "1.3.0-draft8";
pub const VERSION: &str = "1.3.0-draft11";

/// Roughenough version string enriched with any compile-time optional features
pub fn roughenough_version() -> String {
Expand All @@ -96,24 +96,18 @@ pub fn roughenough_version() -> String {

// 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 a client request. Any request smaller than is will be dropped.
pub const MIN_REQUEST_LENGTH: usize = 1024;

/// Maximum size (in bytes) of a client request. Any request larger than is will be dropped.
pub const MAX_REQUEST_LENGTH: usize = 1500;

/// Size (in bytes) of seeds used to derive private keys
pub const SEED_LENGTH: u32 = 32;

/// Size (in bytes) of an Ed25519 public key
pub const PUBKEY_LENGTH: u32 = 32;

/// 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";

Expand All @@ -127,4 +121,4 @@ pub const TREE_LEAF_TWEAK: &[u8] = &[0x00];
pub const TREE_NODE_TWEAK: &[u8] = &[0x01];

/// RFC first field magic value
pub const RFC_REQUEST_FRAME_BYTES: &[u8] = b"ROUGHTIM";
pub const REQUEST_FRAMING_BYTES: &[u8] = b"ROUGHTIM";
4 changes: 2 additions & 2 deletions src/merkle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
//!

use crate::version::Version;
use crate::version::Version::{Classic, Rfc, RfcDraft8};
use crate::version::Version::{Classic, Rfc, RfcDraft11};
use ring::digest;

use super::{TREE_LEAF_TWEAK, TREE_NODE_TWEAK};
Expand Down Expand Up @@ -172,7 +172,7 @@ impl MerkleTree {
#[inline]
fn finalize_output(&self, data: Hash) -> Hash {
match self.version {
Rfc | RfcDraft8 => data[0..32].into(),
Rfc | RfcDraft11 => data[0..32].into(),
Classic => data,
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use data_encoding::{Encoding, HEXLOWER_PERMISSIVE};

use crate::error::Error;
use crate::tag::Tag;
use crate::RFC_REQUEST_FRAME_BYTES;
use crate::REQUEST_FRAMING_BYTES;

const HEX: Encoding = HEXLOWER_PERMISSIVE;

Expand Down Expand Up @@ -237,8 +237,8 @@ impl RtMessage {
/// Encode this message into an on-the-wire representation prefixed with RFC framing.
pub fn encode_framed(&self) -> Result<Vec<u8>, Error> {
let encoded = self.encode()?;
let mut frame = Vec::with_capacity(RFC_REQUEST_FRAME_BYTES.len() + 4 + encoded.len());
frame.write_all(RFC_REQUEST_FRAME_BYTES)?;
let mut frame = Vec::with_capacity(REQUEST_FRAMING_BYTES.len() + 4 + encoded.len());
frame.write_all(REQUEST_FRAMING_BYTES)?;
frame.write_u32::<LittleEndian>(encoded.len() as u32)?;
frame.write_all(&encoded)?;

Expand Down
Loading
Loading