From 24ea92211617666afec11b30dc9e442e8f34c3a9 Mon Sep 17 00:00:00 2001 From: David Venhoek Date: Fri, 2 Feb 2024 15:01:50 +0100 Subject: [PATCH 01/23] Implemented the authentication tlvs and associated infra. --- Cargo.lock | 136 ++++++++++-- Cargo.toml | 1 + statime/Cargo.toml | 4 +- statime/src/crypto.rs | 78 +++++++ statime/src/crypto/ring.rs | 38 ++++ statime/src/datastructures/common/tlv.rs | 12 ++ statime/src/datastructures/messages/mod.rs | 236 ++++++++++++++++++++- statime/src/lib.rs | 1 + 8 files changed, 490 insertions(+), 16 deletions(-) create mode 100644 statime/src/crypto.rs create mode 100644 statime/src/crypto/ring.rs diff --git a/Cargo.lock b/Cargo.lock index 63f1a2f2c..e548fdb2d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -53,7 +53,7 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -63,7 +63,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -93,7 +93,7 @@ dependencies = [ "miniz_oxide", "object", "rustc-demangle", - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -114,6 +114,15 @@ version = "1.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +[[package]] +name = "cc" +version = "1.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" +dependencies = [ + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -320,7 +329,7 @@ dependencies = [ "hermit-abi", "libc", "wasi", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -417,6 +426,20 @@ dependencies = [ "getrandom", ] +[[package]] +name = "ring" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.48.0", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -488,6 +511,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "socket2" version = "0.5.7" @@ -495,9 +524,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "statime" version = "0.2.2" @@ -508,6 +543,7 @@ dependencies = [ "libm", "log", "rand", + "ring", "serde", "serde_test", ] @@ -587,7 +623,7 @@ dependencies = [ "pin-project-lite", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -703,6 +739,12 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "utf8parse" version = "0.2.2" @@ -743,13 +785,37 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] @@ -758,28 +824,46 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -792,24 +876,48 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/Cargo.toml b/Cargo.toml index f8696ed9f..6aaee6fba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ pin-project-lite = "0.2.13" toml = ">=0.5.0, <0.9.0" tokio = "1.33" rand = { version = "0.8.5", default-features = false } +ring = "0.17.7" serde = { version = "1.0.192", features = ["derive"] } serde_json = { version = "1.0.111" } serde_test = { version = "1.0.176" } diff --git a/statime/Cargo.toml b/statime/Cargo.toml index a8eeb2b92..03b663ef7 100644 --- a/statime/Cargo.toml +++ b/statime/Cargo.toml @@ -12,9 +12,10 @@ publish.workspace = true rust-version.workspace = true [features] -default = ["std", "serde"] +default = ["std", "ring", "serde"] std = [] fuzz = ["std"] +ring = ["dep:ring"] serde = ["dep:serde", "arrayvec/serde"] [dependencies] @@ -25,6 +26,7 @@ libm.workspace = true log = { workspace = true, default-features = false} rand = { workspace = true, default-features = false } serde = { workspace = true, optional = true } +ring = { workspace = true, optional = true } [dev-dependencies] serde_test.workspace = true diff --git a/statime/src/crypto.rs b/statime/src/crypto.rs new file mode 100644 index 000000000..6246ca2b5 --- /dev/null +++ b/statime/src/crypto.rs @@ -0,0 +1,78 @@ +//! Provides primitives for cryptographic validation of PTP messages + +/// Error representing a Mac running out of space for its output +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct NoSpaceError; + +/// Message authentication code provider +pub trait Mac { + /// Size of the output + fn output_size(&self) -> usize; + + /// Verify that the given mac represents a signature for the given data + fn verify(&self, data: &[u8], mac: &[u8]) -> bool; + + /// Sign the given data + fn sign(&self, data: &[u8], output_buffer: &mut [u8]) -> Result; +} + +#[cfg(feature = "ring")] +mod ring; +#[cfg(feature = "ring")] +pub use ring::*; + +/// Policy data for a ptp security association (see IEEE1588-2019 section 16.14) +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct SecurityPolicy { + /// Whether or not the correction field is signed + pub ignore_correction: bool, +} + +/// Data for a ptp security association (see IEEE1588-2019 section 16.14) +pub trait SecurityAssociation { + /// Security policy for the assocation + fn policy_data(&self) -> SecurityPolicy; + + /// Lookup a specific key for the association + fn mac(&self, key_id: u32) -> Option<&dyn Mac>; + + /// Get key that should be used for signing + fn signing_mac(&self) -> (u32, &dyn Mac); +} + +/// Interface to the database of security associations +pub trait SecurityAssociationProvider { + /// Type used for the security assocations + type Association: SecurityAssociation; + + /// Lookup a specific security association + fn lookup(&self, spp: u8) -> Option; +} + +/// Association type for the empty security association provider +pub enum NoSecurityAssocation {} + +impl SecurityAssociation for NoSecurityAssocation { + fn policy_data(&self) -> SecurityPolicy { + unreachable!() + } + + fn mac(&self, _key_id: u32) -> Option<&dyn Mac> { + unreachable!() + } + + fn signing_mac(&self) -> (u32, &dyn Mac) { + unreachable!() + } +} + +/// Empty security association provider +pub struct NoSecurityProvider; + +impl SecurityAssociationProvider for NoSecurityProvider { + type Association = NoSecurityAssocation; + + fn lookup(&self, _spp: u8) -> Option { + None + } +} diff --git a/statime/src/crypto/ring.rs b/statime/src/crypto/ring.rs new file mode 100644 index 000000000..6511054e9 --- /dev/null +++ b/statime/src/crypto/ring.rs @@ -0,0 +1,38 @@ +use ring::hmac; + +use super::Mac; + +/// Implementation of the HMAC-SHA256-128 required by the PTP standard +pub struct HmacSha256_128 { + key: ring::hmac::Key, +} + +impl HmacSha256_128 { + /// Create a new keyed instance of the Mac. + pub fn new(key: [u8; 32]) -> Self { + Self { + key: hmac::Key::new(hmac::HMAC_SHA256, &key), + } + } +} + +impl Mac for HmacSha256_128 { + fn output_size(&self) -> usize { + 16 + } + + fn verify(&self, data: &[u8], mac: &[u8]) -> bool { + // because of truncation, regeneration is our only path to verification + let tag = hmac::sign(&self.key, data); + ring::constant_time::verify_slices_are_equal(mac, &tag.as_ref()[..16]).is_ok() + } + + fn sign(&self, data: &[u8], output_buffer: &mut [u8]) -> Result { + if output_buffer.len() < 16 { + return Err(super::NoSpaceError); + } + let tag = hmac::sign(&self.key, data); + output_buffer[..16].copy_from_slice(&tag.as_ref()[..16]); + Ok(16) + } +} diff --git a/statime/src/datastructures/common/tlv.rs b/statime/src/datastructures/common/tlv.rs index 6589fa7d7..fae911739 100644 --- a/statime/src/datastructures/common/tlv.rs +++ b/statime/src/datastructures/common/tlv.rs @@ -95,6 +95,18 @@ impl<'a> TlvSet<'a> { pub(crate) fn tlv(&self) -> TlvSetIterator<'a> { TlvSetIterator { buffer: self.bytes } } + + pub(crate) fn extend_with<'b>( + &self, + tlv: Tlv, + backing_buffer: &'b mut [u8], + ) -> Option> { + let used = self.serialize(backing_buffer).ok()?; + tlv.serialize(&mut backing_buffer[used..]).ok()?; + Some(TlvSet { + bytes: &backing_buffer[..used + tlv.wire_size()], + }) + } } #[derive(Debug)] diff --git a/statime/src/datastructures/messages/mod.rs b/statime/src/datastructures/messages/mod.rs index b44d259da..9277064d5 100644 --- a/statime/src/datastructures/messages/mod.rs +++ b/statime/src/datastructures/messages/mod.rs @@ -12,12 +12,13 @@ pub(crate) use sync::*; use self::{management::ManagementMessage, signalling::SignalingMessage}; use super::{ - common::{PortIdentity, TimeInterval, TlvSet, WireTimestamp}, + common::{PortIdentity, TimeInterval, Tlv, TlvSet, TlvType, WireTimestamp}, datasets::InternalDefaultDS, WireFormatError, }; use crate::{ config::LeapIndicator, + crypto::{NoSpaceError, SecurityAssociation, SecurityAssociationProvider}, ptp_instance::PtpInstanceState, time::{Interval, Time}, }; @@ -244,7 +245,125 @@ pub fn is_compatible(buffer: &[u8]) -> bool { (buffer.len() >= 2) && (buffer[1] & 0xF) == 2 } +impl<'a> Message<'a> { + #[allow(unused)] + pub(crate) fn add_signature( + &mut self, + spp: u8, + provider: &impl SecurityAssociationProvider, + backing_buffer: &'a mut [u8], + ) -> Result<(), NoSpaceError> { + let association = provider.lookup(spp).ok_or(NoSpaceError)?; + let (key_id, key) = association.signing_mac(); + + // partial authentication tlv + let mut temp_tlv_value = [0; MAX_DATA_LEN]; + temp_tlv_value[0] = spp; + temp_tlv_value[1] = 0; + temp_tlv_value[2..6].copy_from_slice(&key_id.to_be_bytes()); + + // Regenerate the tlv set with partial authentication tlv + let mut temp_message = self.clone(); + temp_message.suffix = self + .suffix + .extend_with( + Tlv { + tlv_type: TlvType::Authentication, + value: temp_tlv_value[0..6 + key.output_size()].into(), + }, + backing_buffer, + ) + .ok_or(NoSpaceError)?; + + // generate tag + let mut temp_buffer = [0; MAX_DATA_LEN]; + temp_message + .serialize(&mut temp_buffer) + .map_err(|_| NoSpaceError)?; + if association.policy_data().ignore_correction { + // zero out correction field. + temp_buffer[8..16].fill(0) + } + key.sign( + &temp_buffer[..self.wire_size() + 10], + &mut temp_tlv_value[6..], + )?; + + // Generate final tlv set + self.suffix = self + .suffix + .extend_with( + Tlv { + tlv_type: TlvType::Authentication, + value: temp_tlv_value[0..6 + key.output_size()].into(), + }, + backing_buffer, + ) + .ok_or(NoSpaceError)?; + + Ok(()) + } +} + impl Message<'_> { + #[allow(unused)] + pub(crate) fn verify_signed(&self, provider: &impl SecurityAssociationProvider) -> bool { + let mut tlv_offset = 0; + for tlv in self.suffix.tlv() { + if tlv.tlv_type == TlvType::Authentication { + // Check we have at least the SPP, params and key id + if tlv.value.len() < 6 { + return false; + } + + let spp = tlv.value[0]; + let params = tlv.value[1]; + let key_id = u32::from_be_bytes(tlv.value[2..6].try_into().unwrap()); + + // we dont support presence of any of the optional bits, so not valid if those + // are present + if params != 0 { + return false; + } + + // get the security association and key + let Some(association) = provider.lookup(spp) else { + return false; + }; + let Some(key) = association.mac(key_id) else { + return false; + }; + + // Ensure we have a complete ICV + if tlv.value.len() < 6 + key.output_size() { + return false; + } + + // We need the raw packet data for the signature, so serialize again + // TODO: bad practice to reserialize for checking signatures, should be fixed + // before production ready + let mut buffer = [0; MAX_DATA_LEN]; + if self.serialize(&mut buffer).is_err() { + return false; + } + if association.policy_data().ignore_correction { + // zero out correction field. + buffer[8..16].fill(0) + } + + return key.verify( + &buffer[..self.header.wire_size() + self.body.wire_size() + tlv_offset + 10], + &tlv.value[6..6 + key.output_size()], + ); + } + + tlv_offset += tlv.wire_size(); + } + + // No authentication tlv found, so not signed + false + } + pub(crate) fn sync( default_ds: &InternalDefaultDS, port_identity: PortIdentity, @@ -490,3 +609,118 @@ impl<'a> Message<'a> { }) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + config::InstanceConfig, + crypto::{HmacSha256_128, SecurityPolicy}, + }; + + struct TestSecurityProvider(SecurityPolicy); + + struct TestSecurityAssociation(SecurityPolicy); + + impl SecurityAssociation for TestSecurityAssociation { + fn policy_data(&self) -> crate::crypto::SecurityPolicy { + self.0 + } + + fn mac(&self, _key_id: u32) -> Option<&dyn crate::crypto::Mac> { + Some(std::boxed::Box::leak(std::boxed::Box::new( + HmacSha256_128::new([0; 32]), + ))) + } + + fn signing_mac(&self) -> (u32, &dyn crate::crypto::Mac) { + ( + 0, + std::boxed::Box::leak(std::boxed::Box::new(HmacSha256_128::new([0; 32]))), + ) + } + } + + impl SecurityAssociationProvider for TestSecurityProvider { + type Association = TestSecurityAssociation; + + fn lookup(&self, _spp: u8) -> Option { + Some(TestSecurityAssociation(self.0)) + } + } + + #[test] + fn test_signing_ignoring_correction() { + let port_identity = PortIdentity::default(); + let default_ds = InternalDefaultDS::new(InstanceConfig { + clock_identity: Default::default(), + priority_1: 128, + priority_2: 128, + domain_number: 0, + sdo_id: Default::default(), + slave_only: false, + path_trace: false, + }); + + let provider = TestSecurityProvider(SecurityPolicy { + ignore_correction: true, + }); + + let mut test_message = Message::sync(&default_ds, port_identity, 1); + let mut backing_buffer = [0; MAX_DATA_LEN]; + test_message + .add_signature(0, &provider, &mut backing_buffer) + .unwrap(); + + let mut message_buffer = [0; MAX_DATA_LEN]; + let message_len = test_message.serialize(&mut message_buffer).unwrap(); + + // Modify correction field + message_buffer[9] = message_buffer[9].wrapping_add(9); + + let received_message = Message::deserialize(&message_buffer[..message_len]).unwrap(); + assert!(received_message.verify_signed(&provider)); + + // Modify message + message_buffer[message_len - 1] = message_buffer[message_len - 1].wrapping_add(5); + + let received_message = Message::deserialize(&message_buffer[..message_len]).unwrap(); + assert!(!received_message.verify_signed(&provider)); + } + + #[test] + fn test_signing() { + let port_identity = PortIdentity::default(); + let default_ds = InternalDefaultDS::new(InstanceConfig { + clock_identity: Default::default(), + priority_1: 128, + priority_2: 128, + domain_number: 0, + sdo_id: Default::default(), + slave_only: false, + path_trace: false, + }); + + let provider = TestSecurityProvider(SecurityPolicy { + ignore_correction: false, + }); + + let mut test_message = Message::sync(&default_ds, port_identity, 1); + let mut backing_buffer = [0; MAX_DATA_LEN]; + test_message + .add_signature(0, &provider, &mut backing_buffer) + .unwrap(); + + let mut message_buffer = [0; MAX_DATA_LEN]; + let message_len = test_message.serialize(&mut message_buffer).unwrap(); + + let received_message = Message::deserialize(&message_buffer[..message_len]).unwrap(); + assert!(received_message.verify_signed(&provider)); + + // Modify correction field + message_buffer[9] = message_buffer[9].wrapping_add(9); + + let received_message = Message::deserialize(&message_buffer[..message_len]).unwrap(); + assert!(!received_message.verify_signed(&provider)); + } +} diff --git a/statime/src/lib.rs b/statime/src/lib.rs index d4a3539ce..c2437a0e0 100644 --- a/statime/src/lib.rs +++ b/statime/src/lib.rs @@ -86,6 +86,7 @@ extern crate std; mod bmc; mod clock; pub mod config; +pub mod crypto; pub(crate) mod datastructures; pub mod filters; mod float_polyfill; From 01d7789924402a50855066285f80ab2ff5c5c46b Mon Sep 17 00:00:00 2001 From: David Venhoek Date: Fri, 2 Feb 2024 16:08:10 +0100 Subject: [PATCH 02/23] Added support for the authentication tlv to the library. --- statime-linux/src/config/mod.rs | 1 + statime-linux/src/main.rs | 3 ++ statime-stm32/src/port.rs | 17 ++++++++- statime/src/config/port.rs | 3 ++ statime/src/port/bmca.rs | 10 +++--- statime/src/port/master.rs | 2 +- statime/src/port/mod.rs | 62 +++++++++++++++++++++------------ statime/src/port/slave.rs | 6 ++-- statime/src/ptp_instance.rs | 17 +++++---- 9 files changed, 82 insertions(+), 39 deletions(-) diff --git a/statime-linux/src/config/mod.rs b/statime-linux/src/config/mod.rs index bb8eeda6e..72c44ff1c 100644 --- a/statime-linux/src/config/mod.rs +++ b/statime-linux/src/config/mod.rs @@ -116,6 +116,7 @@ impl From for statime::config::PortConfig> interval: Interval::from_log_2(pc.delay_interval), }, }, + spp: None, } } } diff --git a/statime-linux/src/main.rs b/statime-linux/src/main.rs index dbce7992c..631f732b0 100644 --- a/statime-linux/src/main.rs +++ b/statime-linux/src/main.rs @@ -10,6 +10,7 @@ use clap::Parser; use rand::{rngs::StdRng, SeedableRng}; use statime::{ config::{ClockIdentity, InstanceConfig, SdoId, TimePropertiesDS, TimeSource}, + crypto::NoSecurityProvider, filters::{Filter, KalmanConfiguration, KalmanFilter}, port::{ is_message_buffer_compatible, InBmca, Measurement, Port, PortAction, PortActionIterator, @@ -351,6 +352,7 @@ async fn actual_main() { KalmanConfiguration::default(), port_clock.clone_box(), rng, + NoSecurityProvider, ); let (main_task_sender, port_task_receiver) = tokio::sync::mpsc::channel(1); @@ -518,6 +520,7 @@ type BmcaPort = Port< StdRng, BoxedClock, KalmanFilter, + NoSecurityProvider, RwLock, >; diff --git a/statime-stm32/src/port.rs b/statime-stm32/src/port.rs index 295b2bc3e..0d86a4947 100644 --- a/statime-stm32/src/port.rs +++ b/statime-stm32/src/port.rs @@ -12,6 +12,7 @@ use statime::{ AcceptAnyMaster, ClockIdentity, DelayMechanism, InstanceConfig, PortConfig, SdoId, TimePropertiesDS, TimeSource, }, + crypto::NoSecurityProvider, filters::BasicFilter, port::{InBmca, NoForwardedTLVs, PortAction, PortActionIterator, Running, TimestampContext}, time::{Duration, Interval, Time}, @@ -25,13 +26,20 @@ use crate::{ }; type StmPort = statime::port::Port< +<<<<<<< HEAD 'static, +======= +>>>>>>> f5dee44 (Added support for the authentication tlv to the library.) State, AcceptAnyMaster, Rng, &'static PtpClock, BasicFilter, +<<<<<<< HEAD PtpStateMutex, +======= + NoSecurityProvider, +>>>>>>> f5dee44 (Added support for the authentication tlv to the library.) >; pub struct Port { @@ -303,10 +311,17 @@ pub fn setup_statime( sync_interval: Interval::from_log_2(-6), master_only: false, delay_asymmetry: Duration::ZERO, + spp: None, }; let filter_config = 0.1; - let ptp_port = ptp_instance.add_port(port_config, filter_config, ptp_clock, rng); + let ptp_port = ptp_instance.add_port( + port_config, + filter_config, + ptp_clock, + rng, + NoSecurityProvider, + ); (ptp_instance, ptp_port) } diff --git a/statime/src/config/port.rs b/statime/src/config/port.rs index 32679c025..503809ed7 100644 --- a/statime/src/config/port.rs +++ b/statime/src/config/port.rs @@ -55,6 +55,9 @@ pub struct PortConfig { // Notes: // Fields specific for delay mechanism are kept as part of [DelayMechanism]. // Version is always 2.1, so not stored (versionNumber, minorVersionNumber) + /// Security paramters pointer to use when sending message. If not null, + /// also enables checking on reception + pub spp: Option, } impl PortConfig { diff --git a/statime/src/port/bmca.rs b/statime/src/port/bmca.rs index 92dcecb93..bfe323def 100644 --- a/statime/src/port/bmca.rs +++ b/statime/src/port/bmca.rs @@ -19,8 +19,8 @@ use crate::{ Clock, }; -impl<'a, A: AcceptableMasterList, C: Clock, F: Filter, R: Rng, S: PtpInstanceStateMutex> - Port<'a, Running, A, R, C, F, S> +impl<'a, A: AcceptableMasterList, C: Clock, F: Filter, R: Rng, P, S: PtpInstanceStateMutex> + Port<'a, Running, A, R, C, F, P, S> { pub(super) fn handle_announce<'b>( &'b mut self, @@ -105,15 +105,15 @@ impl<'a, A: AcceptableMasterList, C: Clock, F: Filter, R: Rng, S: PtpInstanceSta } // BMCA related functionality of the port -impl<'a, A: AcceptableMasterList, C: Clock, F: Filter, R: Rng, S: PtpInstanceStateMutex> - Port<'a, InBmca, A, R, C, F, S> +impl<'a, A: AcceptableMasterList, C: Clock, F: Filter, R: Rng, P, S: PtpInstanceStateMutex> + Port<'a, InBmca, A, R, C, F, P, S> { pub(crate) fn calculate_best_local_announce_message(&mut self) { self.lifecycle.local_best = self.bmca.take_best_port_announce_message() } } -impl<'a, A, C: Clock, F: Filter, R: Rng, S: PtpInstanceStateMutex> Port<'a, InBmca, A, R, C, F, S> { +impl<'a, A, C: Clock, F: Filter, R: Rng, P, S: PtpInstanceStateMutex> Port<'a, InBmca, A, R, C, F, P, S> { pub(crate) fn step_announce_age(&mut self, step: Duration) { if let Some(mut age) = self.multiport_disable.take() { age += step; diff --git a/statime/src/port/master.rs b/statime/src/port/master.rs index 18fc74974..e91a84b8f 100644 --- a/statime/src/port/master.rs +++ b/statime/src/port/master.rs @@ -12,7 +12,7 @@ use crate::{ time::Time, }; -impl<'a, A, C, F: Filter, R, S: PtpInstanceStateMutex> Port<'a, Running, A, R, C, F, S> { +impl<'a, A, C, F: Filter, R, P, S: PtpInstanceStateMutex> Port<'a, Running, A, R, C, F, P, S> { pub(super) fn send_sync(&mut self) -> PortActionIterator { if matches!(self.port_state, PortState::Master) { log::trace!("sending sync message"); diff --git a/statime/src/port/mod.rs b/statime/src/port/mod.rs index 402bd89dd..4b77148b5 100644 --- a/statime/src/port/mod.rs +++ b/statime/src/port/mod.rs @@ -24,6 +24,7 @@ use crate::{ }, clock::Clock, config::PortConfig, + crypto::SecurityAssociationProvider, datastructures::{ common::PortIdentity, messages::{Message, MessageBody}, @@ -121,6 +122,7 @@ pub(crate) mod state; /// use statime::filters::BasicFilter; /// use statime::PtpInstance; /// use statime::time::Interval; +/// use statime::crypto::NoSecurityProvider; /// /// let mut instance = PtpInstance::::new(instance_config, time_properties_ds); /// @@ -134,12 +136,13 @@ pub(crate) mod state; /// sync_interval: interval, /// master_only: false, /// delay_asymmetry: Default::default(), +/// spp: None, /// }; /// let filter_config = 1.0; /// let clock = system::Clock {}; /// let rng = thread_rng(); /// -/// let port_in_bmca = instance.add_port(port_config, filter_config, clock, rng); +/// let port_in_bmca = instance.add_port(port_config, filter_config, clock, rng, NoSecurityProvider); /// /// // To handle events for the port it needs to change to running mode /// let (running_port, actions) = port_in_bmca.end_bmca(); @@ -242,8 +245,9 @@ pub(crate) mod state; /// use statime::config::AcceptableMasterList; /// use statime::filters::Filter; /// use statime::port::{NoForwardedTLVs, Port, PortActionIterator, Running}; +/// use statime::crypto::SecurityAssociationProvider; /// -/// fn something_happend(resources: &mut MyPortResources, running_port: &mut Port) { +/// fn something_happend(resources: &mut MyPortResources, running_port: &mut Port) { /// let actions = if resources.announce_timer.has_expired() { /// running_port.handle_announce_timer(&mut NoForwardedTLVs) /// } else if resources.sync_timer.has_expired() { @@ -269,7 +273,7 @@ pub(crate) mod state; /// } /// ``` #[derive(Debug)] -pub struct Port<'a, L, A, R, C, F: Filter, S = RefCell> { +pub struct Port<'a, L, A, R, C, F: Filter, P, S = RefCell> { config: PortConfig<()>, filter_config: F::Config, clock: C, @@ -282,6 +286,7 @@ pub struct Port<'a, L, A, R, C, F: Filter, S = RefCell> { packet_buffer: [u8; MAX_DATA_LEN], lifecycle: L, rng: R, + security_provider: P, // Age of the last announce message that triggered // multiport disable. Once this gets larger than the // port announce interval, we can once again become @@ -328,8 +333,8 @@ pub struct InBmca { local_best: Option, } -impl<'a, A: AcceptableMasterList, C: Clock, F: Filter, R: Rng, S: PtpInstanceStateMutex> - Port<'a, Running, A, R, C, F, S> +impl<'a, A: AcceptableMasterList, C: Clock, F: Filter, R: Rng, P: SecurityAssociationProvider, S: PtpInstanceStateMutex> + Port<'a, Running, A, R, C, F, P, S> { /// Inform the port about a transmit timestamp being available /// @@ -406,7 +411,7 @@ impl<'a, A: AcceptableMasterList, C: Clock, F: Filter, R: Rng, S: PtpInstanceSta /// Set this [`Port`] into [`InBmca`] mode to use it with /// [`PtpInstance::bmca`]. - pub fn start_bmca(self) -> Port<'a, InBmca, A, R, C, F, S> { + pub fn start_bmca(self) -> Port<'a, InBmca, A, R, C, F, P, S> { Port { port_state: self.port_state, instance_state: self.instance_state, @@ -430,6 +435,7 @@ impl<'a, A: AcceptableMasterList, C: Clock, F: Filter, R: Rng, S: PtpInstanceSta filter: self.filter, mean_delay: self.mean_delay, peer_delay_state: self.peer_delay_state, + security_provider: self.security_provider, } } @@ -449,6 +455,11 @@ impl<'a, A: AcceptableMasterList, C: Clock, F: Filter, R: Rng, S: PtpInstanceSta return ControlFlow::Break(actions![]); } }; + + if self.config.spp.is_some() && !message.verify_signed(&self.security_provider) { + return ControlFlow::Break(actions![]); + } + let domain_matches = self.instance_state.with_ref(|state| { message.header().sdo_id == state.default_ds.sdo_id && message.header().domain_number == state.default_ds.domain_number @@ -515,13 +526,13 @@ impl<'a, A: AcceptableMasterList, C: Clock, F: Filter, R: Rng, S: PtpInstanceSta } } -impl<'a, A, C, F: Filter, R, S> Port<'a, InBmca, A, R, C, F, S> { +impl<'a, A, C, F: Filter, R, P, S> Port<'a, InBmca, A, R, C, F, P, S> { /// End a BMCA cycle and make the /// [`handle_*`](`Port::handle_send_timestamp`) methods available again pub fn end_bmca( self, ) -> ( - Port<'a, Running, A, R, C, F, S>, + Port<'a, Running, A, R, C, F, P, S>, PortActionIterator<'static>, ) { ( @@ -544,13 +555,14 @@ impl<'a, A, C, F: Filter, R, S> Port<'a, InBmca, A, R, C, F, S> { filter: self.filter, mean_delay: self.mean_delay, peer_delay_state: self.peer_delay_state, + security_provider: self.security_provider, }, self.lifecycle.pending_action, ) } } -impl Port<'_, L, A, R, C, F, S> { +impl Port<'_, L, A, R, C, F, P, S> { fn set_forced_port_state(&mut self, mut state: PortState) { log::info!( "new state for port {}: {} -> {}", @@ -569,7 +581,7 @@ impl Port<'_, L, A, R, C, F, S> { } } -impl Port<'_, L, A, R, C, F, S> { +impl Port<'_, L, A, R, C, F, P, S> { /// Indicate whether this [`Port`] is steering its clock. pub fn is_steering(&self) -> bool { matches!(self.port_state, PortState::Slave(_)) @@ -589,7 +601,7 @@ impl Port<'_, L, A, R, C, F, S> { } } -impl<'a, A, C, F: Filter, R: Rng, S: PtpInstanceStateMutex> Port<'a, InBmca, A, R, C, F, S> { +impl<'a, A, C, F: Filter, R: Rng, P, S: PtpInstanceStateMutex> Port<'a, InBmca, A, R, C, F, P, S> { /// Create a new port from a port dataset on a given interface. pub(crate) fn new( instance_state: &'a S, @@ -598,6 +610,7 @@ impl<'a, A, C, F: Filter, R: Rng, S: PtpInstanceStateMutex> Port<'a, InBmca, A, clock: C, port_identity: PortIdentity, mut rng: R, + security_provider: P, ) -> Self { let duration = config.announce_duration(&mut rng); let bmca = Bmca::new( @@ -617,6 +630,7 @@ impl<'a, A, C, F: Filter, R: Rng, S: PtpInstanceStateMutex> Port<'a, InBmca, A, sync_interval: config.sync_interval, master_only: config.master_only, delay_asymmetry: config.delay_asymmetry, + spp: None, }, filter_config, clock, @@ -638,6 +652,7 @@ impl<'a, A, C, F: Filter, R: Rng, S: PtpInstanceStateMutex> Port<'a, InBmca, A, filter, mean_delay: None, peer_delay_state: PeerDelayState::Empty, + security_provider, } } } @@ -648,11 +663,7 @@ mod tests { use super::*; use crate::{ - config::{AcceptAnyMaster, DelayMechanism, InstanceConfig, TimePropertiesDS}, - datastructures::datasets::{InternalDefaultDS, InternalParentDS, PathTraceDS}, - filters::BasicFilter, - time::{Duration, Interval, Time}, - Clock, + config::{AcceptAnyMaster, DelayMechanism, InstanceConfig, TimePropertiesDS}, crypto::NoSecurityProvider, datastructures::datasets::{InternalDefaultDS, InternalParentDS, PathTraceDS}, filters::BasicFilter, time::{Duration, Interval, Time}, Clock }; // General test infra @@ -683,8 +694,8 @@ mod tests { pub(super) fn setup_test_port( state: &RefCell, - ) -> Port<'_, Running, AcceptAnyMaster, rand::rngs::mock::StepRng, TestClock, BasicFilter> { - let port = Port::<_, _, _, _, BasicFilter>::new( + ) -> Port<'_, Running, AcceptAnyMaster, rand::rngs::mock::StepRng, TestClock, BasicFilter, NoSecurityProvider> { + let port = Port::<_, _, _, _, BasicFilter, NoSecurityProvider>::new( state, PortConfig { acceptable_master_list: AcceptAnyMaster, @@ -696,22 +707,25 @@ mod tests { sync_interval: Interval::from_log_2(0), master_only: false, delay_asymmetry: Duration::ZERO, + spp: None, }, 0.25, TestClock, Default::default(), rand::rngs::mock::StepRng::new(2, 1), + NoSecurityProvider, ); let (port, _) = port.end_bmca(); port } + pub(super) fn setup_test_port_custom_identity( state: &RefCell, port_identity: PortIdentity, - ) -> Port<'_, Running, AcceptAnyMaster, rand::rngs::mock::StepRng, TestClock, BasicFilter> { - let port = Port::<_, _, _, _, BasicFilter>::new( + ) -> Port<'_, Running, AcceptAnyMaster, rand::rngs::mock::StepRng, TestClock, BasicFilter, NoSecurityProvider> { + let port = Port::<_, _, _, _, BasicFilter, NoSecurityProvider>::new( &state, PortConfig { acceptable_master_list: AcceptAnyMaster, @@ -723,11 +737,13 @@ mod tests { sync_interval: Interval::from_log_2(0), master_only: false, delay_asymmetry: Duration::ZERO, + spp: None, }, 0.25, TestClock, port_identity, rand::rngs::mock::StepRng::new(2, 1), + NoSecurityProvider, ); let (port, _) = port.end_bmca(); @@ -737,8 +753,8 @@ mod tests { pub(super) fn setup_test_port_custom_filter( state: &RefCell, filter_config: F::Config, - ) -> Port<'_, Running, AcceptAnyMaster, rand::rngs::mock::StepRng, TestClock, F> { - let port = Port::<_, _, _, _, F>::new( + ) -> Port<'_, Running, AcceptAnyMaster, rand::rngs::mock::StepRng, TestClock, F, NoSecurityProvider> { + let port = Port::<_, _, _, _, F, _>::new( state, PortConfig { acceptable_master_list: AcceptAnyMaster, @@ -750,11 +766,13 @@ mod tests { sync_interval: Interval::from_log_2(0), master_only: false, delay_asymmetry: Duration::ZERO, + spp: None, }, filter_config, TestClock, Default::default(), rand::rngs::mock::StepRng::new(2, 1), + NoSecurityProvider, ); let (port, _) = port.end_bmca(); diff --git a/statime/src/port/slave.rs b/statime/src/port/slave.rs index d1c794871..03772f79e 100644 --- a/statime/src/port/slave.rs +++ b/statime/src/port/slave.rs @@ -17,7 +17,7 @@ use crate::{ Clock, }; -impl<'a, A, C: Clock, F: Filter, R, S> Port<'a, Running, A, R, C, F, S> { +impl<'a, A, C: Clock, F: Filter, R, P, S> Port<'a, Running, A, R, C, F, P, S> { pub(super) fn handle_time_measurement<'b>(&mut self) -> PortActionIterator<'b> { if let Some(measurement) = self.extract_measurement() { // If the received message allowed the (slave) state to calculate its offset @@ -456,8 +456,8 @@ impl<'a, A, C: Clock, F: Filter, R, S> Port<'a, Running, A, R, C, F, S> { } } -impl<'a, A, C: Clock, F: Filter, R: Rng, S: PtpInstanceStateMutex> - Port<'a, Running, A, R, C, F, S> +impl<'a, A, C: Clock, F: Filter, R: Rng, P, S: PtpInstanceStateMutex> + Port<'a, Running, A, R, C, F, P, S> { pub(super) fn send_delay_request(&mut self) -> PortActionIterator { match self.config.delay_mechanism { diff --git a/statime/src/ptp_instance.rs b/statime/src/ptp_instance.rs index 99c64aa70..4e2ecd821 100644 --- a/statime/src/ptp_instance.rs +++ b/statime/src/ptp_instance.rs @@ -60,6 +60,7 @@ use crate::{ /// use statime::PtpInstance; /// use statime::config::{AcceptAnyMaster, ClockIdentity, InstanceConfig, TimePropertiesDS, TimeSource}; /// use statime::filters::BasicFilter; +/// use statime::crypto::NoSecurityProvider; /// /// let instance_config = InstanceConfig { /// clock_identity: ClockIdentity::from_mac_address(system::get_mac()), @@ -77,7 +78,7 @@ use crate::{ /// time_properties_ds, /// ); /// -/// let mut port = instance.add_port(port_config, filter_config, clock, rng); +/// let mut port = instance.add_port(port_config, filter_config, clock, rng, NoSecurityProvider); /// /// // Send of port to its own thread/task to do its work /// @@ -103,9 +104,9 @@ pub struct PtpInstanceState { } impl PtpInstanceState { - fn bmca( + fn bmca( &mut self, - ports: &mut [&mut Port<'_, InBmca, A, R, C, F, S>], + ports: &mut [&mut Port<'_, InBmca, A, R, C, F, P, S>], bmca_interval: Duration, ) { debug_assert_eq!(self.default_ds.number_ports as usize, ports.len()); @@ -206,13 +207,14 @@ impl PtpInstance { /// the caller is responsible for propagating any property changes to this /// clock, and for synchronizing this clock with the instance clock as /// appropriate based on the ports state. - pub fn add_port( + pub fn add_port( &self, config: PortConfig, filter_config: F::Config, clock: C, rng: R, - ) -> Port<'_, InBmca, A, R, C, F, S> { + security_provider: P, + ) -> Port<'_, InBmca, A, R, C, F, P, S> { self.log_bmca_interval .fetch_min(config.announce_interval.as_log_2(), Ordering::Relaxed); let port_identity = self.state.with_mut(|state| { @@ -230,6 +232,7 @@ impl PtpInstance { clock, port_identity, rng, + security_provider, ) } @@ -237,9 +240,9 @@ impl PtpInstance { /// /// The caller must pass all the ports that were created on this instance in /// the slice! - pub fn bmca( + pub fn bmca( &self, - ports: &mut [&mut Port<'_, InBmca, A, R, C, F, S>], + ports: &mut [&mut Port<'_, InBmca, A, R, C, F, P, S>], ) { self.state.with_mut(|state| { state.bmca( From 071d04b6c9a5c88437c97a7deb07f033ceddaa7b Mon Sep 17 00:00:00 2001 From: David Venhoek Date: Thu, 15 Feb 2024 08:47:38 +0100 Subject: [PATCH 03/23] Added support for sequence id registration and checking. --- statime/src/crypto.rs | 37 +++++++++++++++++++ .../datastructures/common/port_identity.rs | 2 +- statime/src/datastructures/messages/mod.rs | 31 ++++++++++++++-- 3 files changed, 65 insertions(+), 5 deletions(-) diff --git a/statime/src/crypto.rs b/statime/src/crypto.rs index 6246ca2b5..c6de489fd 100644 --- a/statime/src/crypto.rs +++ b/statime/src/crypto.rs @@ -21,6 +21,8 @@ mod ring; #[cfg(feature = "ring")] pub use ring::*; +use crate::datastructures::{common::PortIdentity, messages::MessageType}; + /// Policy data for a ptp security association (see IEEE1588-2019 section 16.14) #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct SecurityPolicy { @@ -28,6 +30,13 @@ pub struct SecurityPolicy { pub ignore_correction: bool, } +/// Identification of a sender for sequence id generation +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct SenderIdentificaton { + pub(crate) message_type: MessageType, + pub(crate) source_port_id: PortIdentity, +} + /// Data for a ptp security association (see IEEE1588-2019 section 16.14) pub trait SecurityAssociation { /// Security policy for the assocation @@ -36,6 +45,25 @@ pub trait SecurityAssociation { /// Lookup a specific key for the association fn mac(&self, key_id: u32) -> Option<&dyn Mac>; + /// Register an observed message sequence id, returning whether it + /// is acceptable for further processing. + /// + /// Per the specification, once a sequence_id has been seen for a + /// combination of sender and key_id, only higher sequence_id's must be + /// accepted, allowing for rollover of the sequence id. Typically, this + /// should check whether the signed difference between the last sequence + /// id and the provided sequence id is larger than 0 and smaller than + /// some configured limit. Note that it should only actually register + /// id's if they are acceptable, as otherwise an attacker can still + /// avoid the checks by first sending an even older sequence id than + /// what he wants the instance to accept. + fn register_sequence_id( + &mut self, + key_id: u32, + sender: SenderIdentificaton, + sequence_id: u16, + ) -> bool; + /// Get key that should be used for signing fn signing_mac(&self) -> (u32, &dyn Mac); } @@ -61,6 +89,15 @@ impl SecurityAssociation for NoSecurityAssocation { unreachable!() } + fn register_sequence_id( + &mut self, + _key_id: u32, + _sender: SenderIdentificaton, + _sequence_id: u16, + ) -> bool { + unreachable!() + } + fn signing_mac(&self) -> (u32, &dyn Mac) { unreachable!() } diff --git a/statime/src/datastructures/common/port_identity.rs b/statime/src/datastructures/common/port_identity.rs index b83b25535..2433d6817 100644 --- a/statime/src/datastructures/common/port_identity.rs +++ b/statime/src/datastructures/common/port_identity.rs @@ -2,7 +2,7 @@ use super::clock_identity::ClockIdentity; use crate::datastructures::{WireFormat, WireFormatError}; /// Identity of a single port of a PTP instance -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct PortIdentity { /// Identity of the clock this port is part of diff --git a/statime/src/datastructures/messages/mod.rs b/statime/src/datastructures/messages/mod.rs index 9277064d5..0f4ca30fe 100644 --- a/statime/src/datastructures/messages/mod.rs +++ b/statime/src/datastructures/messages/mod.rs @@ -18,7 +18,7 @@ use super::{ }; use crate::{ config::LeapIndicator, - crypto::{NoSpaceError, SecurityAssociation, SecurityAssociationProvider}, + crypto::{NoSpaceError, SecurityAssociation, SecurityAssociationProvider, SenderIdentificaton}, ptp_instance::PtpInstanceState, time::{Interval, Time}, }; @@ -42,7 +42,7 @@ mod sync; /// `statime`. pub const MAX_DATA_LEN: usize = 1024; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(u8)] pub enum MessageType { Sync = 0x0, @@ -327,7 +327,7 @@ impl Message<'_> { } // get the security association and key - let Some(association) = provider.lookup(spp) else { + let Some(mut association) = provider.lookup(spp) else { return false; }; let Some(key) = association.mac(key_id) else { @@ -351,10 +351,24 @@ impl Message<'_> { buffer[8..16].fill(0) } - return key.verify( + if !key.verify( &buffer[..self.header.wire_size() + self.body.wire_size() + tlv_offset + 10], &tlv.value[6..6 + key.output_size()], + ) { + return false; + } + + // Check sequence id is acceptable + association.register_sequence_id( + key_id, + SenderIdentificaton { + message_type: self.body.content_type(), + source_port_id: self.header.source_port_identity, + }, + self.header.sequence_id, ); + + return true; } tlv_offset += tlv.wire_size(); @@ -633,6 +647,15 @@ mod tests { ))) } + fn register_sequence_id( + &mut self, + _key_id: u32, + _sender: crate::crypto::SenderIdentificaton, + _sequence_id: u16, + ) -> bool { + true + } + fn signing_mac(&self) -> (u32, &dyn crate::crypto::Mac) { ( 0, From 73ce51ce115b949e239d1964ea967c45278f7e8c Mon Sep 17 00:00:00 2001 From: Ruben Nijveld Date: Tue, 27 Feb 2024 22:30:21 +0100 Subject: [PATCH 04/23] Implementation of NTS4PTP KE server --- Cargo.lock | 100 +++ Cargo.toml | 5 +- statime-linux/Cargo.toml | 10 +- statime-linux/bin/statime-ke.rs | 4 + statime-linux/src/ke/mod.rs | 323 +++++++ statime-linux/src/ke/record.rs | 1160 +++++++++++++++++++++++++ statime-linux/src/lib.rs | 3 + statime-linux/testkeys/gen-cert.sh | 46 + statime-linux/testkeys/test.chain.pem | 44 + statime-linux/testkeys/test.ext | 7 + statime-linux/testkeys/test.key | 28 + statime-linux/testkeys/test.pem | 22 + statime-linux/testkeys/testca.key | 28 + statime-linux/testkeys/testca.pem | 22 + statime-linux/testkeys/testca.srl | 1 + 15 files changed, 1801 insertions(+), 2 deletions(-) create mode 100644 statime-linux/bin/statime-ke.rs create mode 100644 statime-linux/src/ke/mod.rs create mode 100644 statime-linux/src/ke/record.rs create mode 100755 statime-linux/testkeys/gen-cert.sh create mode 100644 statime-linux/testkeys/test.chain.pem create mode 100644 statime-linux/testkeys/test.ext create mode 100644 statime-linux/testkeys/test.key create mode 100644 statime-linux/testkeys/test.pem create mode 100644 statime-linux/testkeys/testca.key create mode 100644 statime-linux/testkeys/testca.pem create mode 100644 statime-linux/testkeys/testca.srl diff --git a/Cargo.lock b/Cargo.lock index e548fdb2d..402009a95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -96,6 +96,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "bytemuck" version = "1.18.0" @@ -208,6 +214,18 @@ dependencies = [ "typenum", ] +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + [[package]] name = "getrandom" version = "0.2.15" @@ -446,6 +464,47 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustls" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35e4980fa29e4c4b212ffb3db068a564cbf560e51d3944b7c88bd8bf5bec64f4" +dependencies = [ + "base64", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048a63e5b3ac996d78d402940b5fa47973d2d080c6c6fffa1d0f19c4445310b7" + +[[package]] +name = "rustls-webpki" +version = "0.102.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "ryu" version = "1.0.18" @@ -560,11 +619,15 @@ dependencies = [ "log", "pin-project-lite", "rand", + "rustls", + "rustls-pemfile", "serde", "serde_json", "statime", "timestamped-socket", "tokio", + "tokio-rustls", + "tokio-util", "toml", "tracing", "tracing-log", @@ -577,6 +640,12 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "syn" version = "2.0.77" @@ -637,6 +706,31 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + [[package]] name = "toml" version = "0.8.19" @@ -953,3 +1047,9 @@ dependencies = [ "quote", "syn", ] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/Cargo.toml b/Cargo.toml index 6aaee6fba..6948ff15f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,10 @@ serde_test = { version = "1.0.176" } az = "1.2.1" fixed = "1.24" libm = "0.2.8" - +rustls = "0.22.2" +rustls-pemfile = "2.0.0" +tokio-rustls = "0.25.0" +tokio-util = { version = "0.7.10", features = ["codec"] } clock-steering = "0.2.0" timestamped-socket = "0.2.4" diff --git a/statime-linux/Cargo.toml b/statime-linux/Cargo.toml index da08fe135..7345581a4 100644 --- a/statime-linux/Cargo.toml +++ b/statime-linux/Cargo.toml @@ -19,6 +19,10 @@ path = "src/main.rs" name = "statime-metrics-exporter" path = "bin/statime-metrics-exporter.rs" +[[bin]] +name = "statime-ke" +path = "bin/statime-ke.rs" + [dependencies] statime.workspace = true @@ -32,10 +36,14 @@ libc.workspace = true log = { workspace = true, default-features = true } pin-project-lite.workspace = true toml.workspace = true -tokio = { workspace = true, features = ["net", "rt-multi-thread", "time", "macros", "sync", "io-util"] } +tokio = { workspace = true, features = ["net", "fs", "rt-multi-thread", "time", "macros", "sync", "io-util"] } rand = { workspace = true, default-features = false, features = ["std", "std_rng"] } serde.workspace = true serde_json.workspace = true +rustls.workspace = true +rustls-pemfile.workspace = true +tokio-rustls.workspace = true +tokio-util.workspace = true clock-steering.workspace = true timestamped-socket.workspace = true diff --git a/statime-linux/bin/statime-ke.rs b/statime-linux/bin/statime-ke.rs new file mode 100644 index 000000000..b7d1ba51a --- /dev/null +++ b/statime-linux/bin/statime-ke.rs @@ -0,0 +1,4 @@ +#[tokio::main] +async fn main() -> Result<(), Box> { + statime_linux::ke_main().await +} diff --git a/statime-linux/src/ke/mod.rs b/statime-linux/src/ke/mod.rs new file mode 100644 index 000000000..9f49f8747 --- /dev/null +++ b/statime-linux/src/ke/mod.rs @@ -0,0 +1,323 @@ +use std::{ + io, + net::{SocketAddr, ToSocketAddrs}, + path::{Path, PathBuf}, + sync::Arc, + time::Duration, +}; + +use clap::Parser; +use log::{debug, info, warn}; +use rustls::{ + pki_types::{CertificateDer, PrivateKeyDer}, + server::NoClientAuth, + ServerConfig, +}; +use tokio::{ + io::AsyncReadExt, + net::{TcpListener, TcpStream}, + sync::RwLock, + time::Instant, +}; +use tokio_rustls::{server::TlsStream, TlsAcceptor}; + +use crate::initialize_logging_parse_config; + +use self::record::{ + NextProtocol, NextProtocols, ParameterSet, PtpKeyRequestMessage, PtpKeyResponseMessage, Record, + SecurityAssocation, ValidityPeriod, +}; + +mod record; + +struct Key { + id: u32, + data: [u8; 32], + pub valid_since: Instant, +} + +impl Key { + fn generate(id: u32, mut rng: impl rand::Rng) -> Key { + let mut data = [0; 32]; + rng.fill_bytes(&mut data); + + Key { + id, + data, + valid_since: Instant::now(), + } + } + + fn as_bytes(&self) -> &[u8] { + &self.data + } +} + +struct KeySetStore { + current: Key, + next: Option, +} + +impl KeySetStore { + /// This function generates the next key if there is no next key, otherwise + /// it moves the next key to the current key. + fn rotate(&mut self) { + let next_key = self.next.take(); + + if let Some(next_key) = next_key { + self.current = next_key; + // when switched to the current key the valid since time resets + self.current.valid_since = Instant::now(); + } else { + self.next + .replace(Key::generate(self.current.id + 1, rand::thread_rng())); + } + } + + /// Generate a new keyset that stores the current key. + fn new() -> KeySetStore { + KeySetStore { + current: Key::generate(0, rand::thread_rng()), + next: None, + } + } + + fn spawn_rotate_process( + store: Arc>, + lifetime: Duration, + update_period: Duration, + ) -> tokio::task::JoinHandle<()> { + tokio::spawn(async move { + loop { + // if update period is as great as lifetime we immediately rotate to have the + // next key available + if update_period < lifetime { + tokio::time::sleep(lifetime - update_period).await; + } + + // first rotate generates the next key + store.write().await.rotate(); + + // wait for the update period, but never more than the lifetime + tokio::time::sleep(update_period.min(lifetime)).await; + + // second rotate moves the next key to current (deleting the previous current + // key) + store.write().await.rotate(); + } + }) + } +} + +struct KeConfig { + validity_period: u32, + update_period: u32, + grace_period: u32, + listen_addr: SocketAddr, + cert_chain_path: PathBuf, + private_key_path: PathBuf, +} + +async fn load_certs(path: impl AsRef) -> io::Result>> { + let cert_chain_data = tokio::fs::read(path).await?; + rustls_pemfile::certs(&mut &cert_chain_data[..]).collect() +} + +async fn load_private_key(path: impl AsRef) -> io::Result> { + let private_key_data = tokio::fs::read(path).await?; + rustls_pemfile::private_key(&mut &private_key_data[..])? + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "No private key data found")) +} + +async fn prep_server_config( + cert_chain_path: impl AsRef, + private_key_path: impl AsRef, +) -> Result> { + let cert_chain = load_certs(cert_chain_path).await?; + let key_der = load_private_key(private_key_path).await?; + + // setup tls server + let mut config = rustls::ServerConfig::builder() + .with_client_cert_verifier(Arc::new( + NoClientAuth, // TODO: replace with proper client cert verification + )) + .with_single_cert(cert_chain, key_der)?; + config.alpn_protocols.clear(); + config.alpn_protocols.push(b"ntske/1".to_vec()); + + Ok(config) +} + +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +pub struct Args { + /// Configuration file to use + #[clap( + long = "config", + short = 'c', + default_value = "/etc/statime/statime.toml" + )] + config_file: Option, +} + + +pub async fn main() -> Result<(), Box> { + let args = Args::parse(); + let _config = initialize_logging_parse_config( + &args + .config_file + .expect("could not determine config file path"), + ); + + let ke_config = { + let listen_addr = "0.0.0.0:4460"; + let cert_chain_path = "statime-linux/testkeys/test.chain.pem"; + let private_key_path = "statime-linux/testkeys/test.key"; + + let listen_addr = listen_addr + .to_socket_addrs()? + .next() + .ok_or_else(|| io::Error::from(io::ErrorKind::AddrNotAvailable))?; + + Arc::new(KeConfig { + validity_period: 3600, + update_period: 300, + grace_period: 5, + listen_addr, + cert_chain_path: cert_chain_path.into(), + private_key_path: private_key_path.into(), + }) + }; + + // setup the tls server + let config = + prep_server_config(&ke_config.cert_chain_path, &ke_config.private_key_path).await?; + let acceptor = TlsAcceptor::from(Arc::new(config)); + let listener = TcpListener::bind(ke_config.listen_addr).await?; + + info!("Statime-KE bound on {:?}", ke_config.listen_addr); + + // create the keyset store and let it automatically update itself + let store = Arc::new(RwLock::new(KeySetStore::new())); + KeySetStore::spawn_rotate_process( + store.clone(), + Duration::from_secs(ke_config.validity_period as u64), + Duration::from_secs(ke_config.update_period as u64), + ); + + // handle new connections on the TCP socket and process them with the TLS + // acceptor + loop { + let (stream, peer_addr) = listener.accept().await?; + let acceptor = acceptor.clone(); + let store = store.clone(); + let ke_config = ke_config.clone(); + + debug!("Received connection from {}", peer_addr); + + // TODO: cancel the future after a timeout occurs + let fut = async move { + let stream = acceptor.accept(stream).await?; + handle_connection(stream, store, ke_config).await?; + + Ok(()) as Result<(), Box> + }; + + tokio::spawn(async move { + if let Err(err) = fut.await { + warn!("Error during connection processing: {:?}", err); + } + }); + } +} + +async fn handle_connection( + mut stream: TlsStream, + store: Arc>, + ke_config: Arc, +) -> Result<(), Box> { + debug!("Attempting to read NTS-KE records from connection"); + + // we expect the to receive messages to be smaller than data_buf + let mut data_buf = vec![0; 4096]; + let mut bytes_received = 0; + + let mut records = None; + while records.is_none() { + bytes_received += stream.read(&mut data_buf[bytes_received..]).await?; + let mut data = &data_buf[0..bytes_received]; + records = Record::read_until_eom(&mut data)?; + if bytes_received == data_buf.len() && records.is_none() { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "NTS message too large to handle", + ) + .into()); + } + } + + // records must always be filled at this point + let Some(records) = records else { + unreachable!() + }; + + let keyset = store.read().await; + let resp = respond(records, &keyset, ke_config).await?; + resp.write(stream).await?; + Ok(()) +} + +async fn respond<'a>( + records: Vec>, + keyset: &'a KeySetStore, + ke_config: Arc, +) -> Result, Box> { + // TODO: probably send back an error message to the client instead of just + // erroring the connection + let request: PtpKeyRequestMessage = records.try_into()?; + + // TODO: we ignore the assocation mode entirely right now + + if !request + .next_protocol + .iter() + .any(|np| *np == NextProtocol::Ptpv2_1) + { + // TODO: send back error instead of just erroring the connection + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "Received NTS request without PTP next protocol", + ) + .into()); + } + + let time_since = keyset.current.valid_since.elapsed().as_secs() as u32; + let lifetime = if time_since > ke_config.validity_period { + 0 + } else { + ke_config.validity_period - time_since + }; + + Ok(PtpKeyResponseMessage { + next_protocol: NextProtocols::ptpv2_1(), + current_parameters: ParameterSet { + security_assocation: SecurityAssocation::from_key_data( + keyset.current.id, + keyset.current.as_bytes(), + ), + validity_period: ValidityPeriod { + lifetime, + update_period: ke_config.update_period, + grace_period: ke_config.grace_period, + }, + }, + next_parameters: keyset.next.as_ref().map(|next| ParameterSet { + security_assocation: SecurityAssocation::from_key_data(next.id, next.as_bytes()), + validity_period: ValidityPeriod { + lifetime: ke_config.validity_period, + update_period: ke_config.update_period, + grace_period: ke_config.grace_period, + }, + }), + }) +} diff --git a/statime-linux/src/ke/record.rs b/statime-linux/src/ke/record.rs new file mode 100644 index 000000000..59075f63c --- /dev/null +++ b/statime-linux/src/ke/record.rs @@ -0,0 +1,1160 @@ +use std::{ + borrow::Cow, + fmt::{self, Display, Formatter}, + io, + net::{Ipv4Addr, Ipv6Addr}, + ops::Deref, +}; + +use statime::config::SdoId; +use tokio::io::{AsyncWrite, AsyncWriteExt}; + +/// Error during parsing of a record +#[derive(Debug, Clone)] +pub enum RecordParseError { + InvalidRecordLength, + MissingCriticalBit, + InvalidSdoId, + UnknownRecordType(u16), + UnknownAssociationType(u16), + UnexpectedExtraBytes, + UnexpectedRecord(Record<'static>), + MissingRecord(RecordType), +} + +impl Display for RecordParseError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + use RecordParseError::*; + + match self { + InvalidRecordLength => write!(f, "Invalid record length"), + MissingCriticalBit => write!(f, "Missing critical bit for record that requires it"), + InvalidSdoId => write!(f, "Found invalid sdoId"), + UnknownRecordType(r) => write!(f, "Found unknown record of type '{}'", r), + UnknownAssociationType(a) => write!(f, "Found unknown assocation type '{}'", a), + UnexpectedExtraBytes => { + write!(f, "Found unexpected extra bytes that cannot parse a record") + } + UnexpectedRecord(_) => write!(f, "A record of an unexpected type was found"), + MissingRecord(t) => write!( + f, + "An expected record of type {:?} was missing in a container", + t + ), + } + } +} + +impl std::error::Error for RecordParseError {} + +/// Parsing helper function for getting the next byte and updating the slice +/// Caller must make sure the input is large enough. +fn next_u8(d: &mut &[u8]) -> u8 { + let res = d[0]; + *d = &d[1..]; + res +} + +/// Parsing helper function for getting the next u16 and updating the slice. +/// Caller must make sure the input is large enough. +fn next_u16(d: &mut &[u8]) -> u16 { + let res = u16::from_be_bytes([d[0], d[1]]); + *d = &d[2..]; + res +} + +/// Parsing helper function for getting the next u32 and updating the slice. +/// Caller must make sure the input is large enough. +fn next_u32(d: &mut &[u8]) -> u32 { + let res = u32::from_be_bytes([d[0], d[1], d[2], d[3]]); + *d = &d[4..]; + res +} + +/// Get an array of size `COUNT` filled with u16s and updates the input slice. +/// Caller must make sure the input is large enough. +fn next_u16s(d: &mut &[u8]) -> [u16; COUNT] { + let mut results = [0; COUNT]; + next_u16s_into(d, &mut results); + results +} + +/// Reads u16 values into the target, filling it up. Updates the input slice. +/// Caller must make sure that the target is small enough and the data input is +/// large enough. +fn next_u16s_into(d: &mut &[u8], target: &mut [u16]) { + for t in target { + *t = next_u16(d); + } +} + +/// Ensure that the record length matches the expected length +fn validate_record_length(record: &[u8], expected_length: usize) -> Result<(), RecordParseError> { + if record.len() != expected_length { + return Err(RecordParseError::InvalidRecordLength); + } + + Ok(()) +} + +/// Variants of the NTS-KE error records (i.e. records in the NTS-KE message) +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ErrorRecord { + UnrecognizedCriticalRecord, + BadRequest, + InternalServerError, + NotAuthorized, + GrantorNotRegistered, + Unassigned(u16), + Reserved(u16), +} + +impl ErrorRecord { + pub fn from_error_code(code: u16) -> ErrorRecord { + use ErrorRecord::*; + + match code { + 0 => UnrecognizedCriticalRecord, + 1 => BadRequest, + 2 => InternalServerError, + 3 => NotAuthorized, + 4 => GrantorNotRegistered, + i @ 5..=32767 => Unassigned(i), + other => Reserved(other), + } + } + + pub fn as_error_code(&self) -> u16 { + use ErrorRecord::*; + + match self { + UnrecognizedCriticalRecord => 0, + BadRequest => 1, + InternalServerError => 2, + NotAuthorized => 3, + GrantorNotRegistered => 4, + Unassigned(i) => *i, + Reserved(i) => *i, + } + } + + async fn write(&self, mut w: impl AsyncWrite + Unpin) -> io::Result { + w.write_u16(RecordType::Error.raw_record_type()).await?; + w.write_u16(2).await?; + let code = self.as_error_code(); + w.write_u16(code).await?; + Ok(6) + } +} + +/// List of next protocols +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct NextProtocols(Vec); + +impl Deref for NextProtocols { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl NextProtocols { + async fn write(&self, mut w: impl AsyncWrite + Unpin) -> io::Result { + // write record type + w.write_u16(RecordType::NextProtocol.raw_record_type()) + .await?; + + // write length of record + w.write_u16((self.0.len() * 2) as u16).await?; + + let mut bytes_written = 4; + + // write next protocols array + for n in self.0.iter() { + w.write_u16(n.as_u16()).await?; + bytes_written += 2; + } + + Ok(bytes_written) + } + + pub fn ptpv2_1() -> NextProtocols { + NextProtocols(vec![NextProtocol::Ptpv2_1]) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum NextProtocol { + Ntpv4, + Ptpv2_1, + Unassigned(u16), + Experimental(u16), +} + +impl NextProtocol { + fn as_u16(&self) -> u16 { + match self { + NextProtocol::Ntpv4 => 0, + NextProtocol::Ptpv2_1 => 1, + NextProtocol::Unassigned(u) => *u, + NextProtocol::Experimental(u) => *u, + } + } + + fn from_u16(np: u16) -> NextProtocol { + match np { + 0 => NextProtocol::Ntpv4, + 1 => NextProtocol::Ptpv2_1, + 2..=32767 => NextProtocol::Unassigned(np), + _ => NextProtocol::Experimental(np), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SupportedMacAlgorithms(Vec); + +impl SupportedMacAlgorithms { + async fn write(&self, mut w: impl AsyncWrite + Unpin) -> io::Result { + // write record type + w.write_u16(RecordType::SupportedMacAlgorithms.raw_record_type()) + .await?; + + // write record length + w.write_u16((self.0.len() * 2) as u16).await?; + + // write the supported mac algorithms + let mut bytes_written = 4; + for a in self.0.iter() { + w.write_u16(*a).await?; + bytes_written += 2; + } + + Ok(bytes_written) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ParameterSet<'a> { + pub security_assocation: SecurityAssocation<'a>, + pub validity_period: ValidityPeriod, +} + +impl<'a> ParameterSet<'a> { + pub fn into_owned(self) -> ParameterSet<'static> { + ParameterSet { + security_assocation: self.security_assocation.into_owned(), + validity_period: self.validity_period, + } + } + + async fn write( + &self, + mut w: impl AsyncWrite + Unpin, + as_next_params: bool, + ) -> io::Result { + // write record type + w.write_u16(if as_next_params { + RecordType::NextParameters.raw_record_type() + } else { + RecordType::CurrentParameters.raw_record_type() + }) + .await?; + + // store records in buf temporarily to determine length + let mut buf = vec![]; + let mut cur = std::io::Cursor::new(&mut buf); + self.security_assocation.write(&mut cur).await?; + self.validity_period.write(&mut cur).await?; + + // write record length + w.write_u16(buf.len() as u16).await?; + + // write record content + w.write_all(&buf).await?; + + Ok(buf.len() + 4) + } +} + +impl<'a> TryFrom>> for ParameterSet<'a> { + type Error = RecordParseError; + + fn try_from(value: Vec>) -> Result { + let mut security_assocation = None; + let mut validity_period = None; + for item in value { + match item { + Record::SecurityAssociation(s) => { + if security_assocation.is_some() { + return Err(RecordParseError::UnexpectedRecord( + Record::SecurityAssociation(s.into_owned()), + )); + } + security_assocation.replace(s); + } + Record::ValidityPeriod(v) => { + if validity_period.is_some() { + return Err(RecordParseError::UnexpectedRecord(Record::ValidityPeriod( + v, + ))); + } + validity_period.replace(v); + } + Record::EndOfMessage => {} + _ => { + return Err(RecordParseError::UnexpectedRecord(item.into_owned())); + } + } + } + + let Some(security_assocation) = security_assocation else { + return Err(RecordParseError::MissingRecord( + RecordType::SecurityAssocation, + )); + }; + let Some(validity_period) = validity_period else { + return Err(RecordParseError::MissingRecord(RecordType::ValidityPeriod)); + }; + Ok(ParameterSet { + security_assocation, + validity_period, + }) + } +} + +impl<'a> From> for Vec> { + fn from(value: ParameterSet<'a>) -> Self { + vec![ + Record::SecurityAssociation(value.security_assocation), + Record::ValidityPeriod(value.validity_period), + ] + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum AssociationMode { + Group { + ptp_domain_number: u8, + sdo_id: SdoId, + subgroup: u16, + }, + Ipv4(Ipv4Addr), + Ipv6(Ipv6Addr), + Mac([u8; 6]), + PortIdentity([u8; 10]), +} + +impl AssociationMode { + fn from_data(mut record: &[u8]) -> Result { + if record.len() < 2 { + return Err(RecordParseError::InvalidRecordLength); + } + + let assoc_type = next_u16(&mut record); + let mode = match assoc_type { + 0 => { + validate_record_length(record, 5)?; + + // top 4 bits in record[1] should be 0 + if record[1] & 0b11110000 != 0 { + return Err(RecordParseError::InvalidSdoId); + } + + // TODO: spec says offset of subgroup should be 4, but we assume it starts right + // after the sdo id at offset 3 + AssociationMode::Group { + ptp_domain_number: next_u8(&mut record), + sdo_id: next_u16(&mut record).try_into().unwrap(), + subgroup: next_u16(&mut record), + } + } + 1 => { + validate_record_length(record, 4)?; + AssociationMode::Ipv4(Ipv4Addr::new(record[3], record[2], record[1], record[0])) + } + 2 => { + validate_record_length(record, 16)?; + let addr = next_u16s::<8>(&mut record); + AssociationMode::Ipv6(Ipv6Addr::new( + addr[7], addr[6], addr[5], addr[4], addr[3], addr[2], addr[1], addr[0], + )) + } + 3 => { + validate_record_length(record, 6)?; + let mut b = [0; 6]; + // insert in reverse order + b.iter_mut() + .rev() + .for_each(|dest| *dest = next_u8(&mut record)); + AssociationMode::Mac(b) + } + 4 => { + validate_record_length(record, 10)?; + let mut b = [0; 10]; + b.iter_mut() + .rev() + .for_each(|dest| *dest = next_u8(&mut record)); + AssociationMode::PortIdentity(b) + } + _ => { + return Err(RecordParseError::UnknownAssociationType(assoc_type)); + } + }; + + Ok(mode) + } + + async fn write(&self, mut w: impl AsyncWrite + Unpin) -> io::Result { + // write record type + w.write_u16(RecordType::AssociationMode.raw_record_type()) + .await?; + + // write record length (variable length based on type + 2 for the mode type u16) + let record_len = match self { + AssociationMode::Group { .. } => 5, + AssociationMode::Ipv4(_) => 4, + AssociationMode::Ipv6(_) => 16, + AssociationMode::Mac(_) => 6, + AssociationMode::PortIdentity(_) => 10, + } + 2; + w.write_u16(record_len).await?; + + // write the content of the assocation mode + match self { + AssociationMode::Group { + ptp_domain_number, + sdo_id, + subgroup, + } => { + w.write_u16(0).await?; + w.write_u8(*ptp_domain_number).await?; + w.write_u16((*sdo_id).into()).await?; + w.write_u16(*subgroup).await?; + } + AssociationMode::Ipv4(addr) => { + w.write_u16(1).await?; + w.write_u32(u32::from(*addr)).await?; + } + AssociationMode::Ipv6(addr) => { + w.write_u16(2).await?; + w.write_u128(u128::from(*addr)).await?; + } + AssociationMode::Mac(mac) => { + w.write_u16(3).await?; + for b in mac.iter().rev() { + w.write_u8(*b).await?; + } + } + AssociationMode::PortIdentity(id) => { + w.write_u16(4).await?; + for b in id.iter().rev() { + w.write_u8(*b).await?; + } + } + } + + Ok(record_len as usize + 4) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SecurityAssocation<'a> { + spp: u8, + iat: u16, + key_id: u32, + key: Cow<'a, [u8]>, +} + +impl<'a> SecurityAssocation<'a> { + pub fn from_key_data(key_id: u32, data: &'a [u8]) -> SecurityAssocation<'a> { + SecurityAssocation { + spp: 0, + iat: 0, + key_id, + key: Cow::Borrowed(data), + } + } + + pub fn into_key_data(self) -> Vec { + self.key.into_owned() + } + + pub fn into_owned(self) -> SecurityAssocation<'static> { + SecurityAssocation { + spp: self.spp, + iat: self.iat, + key_id: self.key_id, + key: Cow::Owned(self.key.into_owned()), + } + } + + fn from_data(mut record: &[u8]) -> Result { + if record.len() < 9 { + return Err(RecordParseError::InvalidRecordLength); + } + + let spp = next_u8(&mut record); + let iat = next_u16(&mut record); + let key_id = next_u32(&mut record); + let key_length = next_u16(&mut record) as usize; + validate_record_length(record, key_length)?; + + // note: we copy the key at this point + let mut key = vec![0; key_length]; + key.copy_from_slice(record); + Ok(SecurityAssocation { + spp, + iat, + key_id, + key: Cow::Owned(key), + }) + } + + async fn write(&self, mut w: impl AsyncWrite + Unpin) -> std::io::Result { + // write record type + w.write_u16(RecordType::SecurityAssocation.raw_record_type()) + .await?; + + // write record length + let record_len = 9 + self.key.len(); + w.write_u16(record_len as u16).await?; + + // write record data + w.write_u8(self.spp).await?; + w.write_u16(self.iat).await?; + w.write_u32(self.key_id).await?; + w.write_u16(self.key.len() as u16).await?; + w.write_all(&self.key).await?; + + Ok(4 + record_len) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ValidityPeriod { + pub lifetime: u32, + pub update_period: u32, + pub grace_period: u32, +} + +impl ValidityPeriod { + fn from_data(mut record: &[u8]) -> Result { + validate_record_length(record, 12)?; + + let lifetime = next_u32(&mut record); + let update_period = next_u32(&mut record); + let grace_period = next_u32(&mut record); + Ok(ValidityPeriod { + lifetime, + update_period, + grace_period, + }) + } + + async fn write(&self, mut w: impl AsyncWrite + Unpin) -> io::Result { + // write record type + w.write_u16(RecordType::ValidityPeriod.raw_record_type()) + .await?; + + // write record length + w.write_u16(12).await?; + + // write record content + w.write_u32(self.lifetime).await?; + w.write_u32(self.update_period).await?; + w.write_u32(self.grace_period).await?; + + Ok(16) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u16)] +pub enum RecordType { + EndOfMessage = 0, + NextProtocol = 1, + Error = 2, + AssociationMode = 1024, + SupportedMacAlgorithms = 1033, + CurrentParameters = 1025, + NextParameters = 1027, + SecurityAssocation = 1030, + ValidityPeriod = 1037, +} + +impl RecordType { + pub fn raw_record_type(&self) -> u16 { + use RecordType::*; + let critical = matches!(self, EndOfMessage | NextProtocol | Error); + *self as u16 + if critical { 0x8000 } else { 0 } + } +} + +impl TryFrom for RecordType { + type Error = (); + + fn try_from(value: u16) -> Result>::Error> { + use RecordType::*; + + match value { + x if x == EndOfMessage as u16 => Ok(EndOfMessage), + x if x == NextProtocol as u16 => Ok(NextProtocol), + x if x == Error as u16 => Ok(Error), + x if x == AssociationMode as u16 => Ok(AssociationMode), + x if x == SupportedMacAlgorithms as u16 => Ok(SupportedMacAlgorithms), + x if x == CurrentParameters as u16 => Ok(CurrentParameters), + x if x == NextParameters as u16 => Ok(NextParameters), + x if x == SecurityAssocation as u16 => Ok(SecurityAssocation), + x if x == ValidityPeriod as u16 => Ok(ValidityPeriod), + _ => Err(()), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Record<'a> { + EndOfMessage, + NextProtocol(NextProtocols), + Error(ErrorRecord), + AssociationMode(AssociationMode), + SupportedMacAlgorithms(SupportedMacAlgorithms), + CurrentParameters(ParameterSet<'a>), + NextParameters(ParameterSet<'a>), + SecurityAssociation(SecurityAssocation<'a>), + ValidityPeriod(ValidityPeriod), +} + +impl<'a> Record<'a> { + /// Returns if this record is an end of message record. + pub fn is_eom(&self) -> bool { + matches!(self, Record::EndOfMessage) + } + + /// Create a copy with a static lifetime. + pub fn into_owned(self) -> Record<'static> { + match self { + Record::SecurityAssociation(a) => Record::SecurityAssociation(a.into_owned()), + Record::CurrentParameters(p) => Record::CurrentParameters(p.into_owned()), + Record::NextParameters(p) => Record::NextParameters(p.into_owned()), + Record::AssociationMode(am) => Record::AssociationMode(am), + Record::EndOfMessage => Record::EndOfMessage, + Record::Error(e) => Record::Error(e), + Record::NextProtocol(p) => Record::NextProtocol(p), + Record::SupportedMacAlgorithms(sma) => Record::SupportedMacAlgorithms(sma), + Record::ValidityPeriod(vp) => Record::ValidityPeriod(vp), + } + } + + /// Helper function that reads the record body after the header and full + /// record data was read If a record is of an unknown type and the + /// critical bit is not set, no error will be given but no record would + /// be returned. + fn from_decoded_header( + critical: bool, + record_type: u16, + mut record: &'a [u8], + ) -> Result, RecordParseError> { + macro_rules! assert_critical_bit { + () => { + if !critical { + return Err(RecordParseError::MissingCriticalBit); + } + }; + } + + match record_type.try_into() { + Ok(RecordType::EndOfMessage) => { + // end of message record + assert_critical_bit!(); + validate_record_length(record, 0)?; + Ok(Some(Record::EndOfMessage)) + } + Ok(RecordType::NextProtocol) => { + assert_critical_bit!(); + if record.len() % 2 != 0 { + return Err(RecordParseError::InvalidRecordLength); + } + let mut next_protocols = vec![0; record.len() / 2]; + next_u16s_into(&mut record, &mut next_protocols); + let next_protocols = next_protocols + .into_iter() + .map(NextProtocol::from_u16) + .collect(); + Ok(Some(Record::NextProtocol(NextProtocols(next_protocols)))) + } + Ok(RecordType::Error) => { + assert_critical_bit!(); + validate_record_length(record, 2)?; + let error_code = next_u16(&mut record); + Ok(Some(Record::Error(ErrorRecord::from_error_code( + error_code, + )))) + } + Ok(RecordType::AssociationMode) => { + let mode = AssociationMode::from_data(record)?; + Ok(Some(Record::AssociationMode(mode))) + } + Ok(RecordType::CurrentParameters) => { + let res = Self::read_all(&mut record)?; + Ok(Some(Record::CurrentParameters(res.try_into()?))) + } + Ok(RecordType::NextParameters) => { + let res = Self::read_all(&mut record)?; + Ok(Some(Record::NextParameters(res.try_into()?))) + } + Ok(RecordType::SecurityAssocation) => { + let assoc = SecurityAssocation::from_data(record)?; + Ok(Some(Record::SecurityAssociation(assoc))) + } + Ok(RecordType::SupportedMacAlgorithms) => { + if record.len() % 2 != 0 { + return Err(RecordParseError::InvalidRecordLength); + } + + let mut supported = vec![0; record.len() / 2]; + next_u16s_into(&mut record, &mut supported); + Ok(Some(Record::SupportedMacAlgorithms( + SupportedMacAlgorithms(supported), + ))) + } + Ok(RecordType::ValidityPeriod) => { + // validity period record + let val_period = ValidityPeriod::from_data(record)?; + Ok(Some(Record::ValidityPeriod(val_period))) + } + Err(_) => { + // unknown record + if critical { + Err(RecordParseError::UnknownRecordType(record_type)) + } else { + Ok(None) + } + } + } + } + + /// Read a single record from a buffer, updating the buffer so it only + /// refers to unparsed data. If not enough data was read for a complete + /// record, nothing will be changed. If a record is unknown and no + /// critical bit is set no error will be given, but no record will be + /// returned either. If the critical bit is set an error will be + /// returned for unknown records. + pub fn from_buffer(buf: &mut &'a [u8]) -> Result>, RecordParseError> { + if buf.len() < 4 { + return Ok(None); + } + + let mut data = *buf; + + let raw_record_type = next_u16(&mut data); + let critical = raw_record_type & 0x8000 != 0; + let record_type = raw_record_type & !0x8000; + let record_length = next_u16(&mut data) as usize; + + if data.len() >= record_length { + let data = &data[0..record_length]; + let res = Self::from_decoded_header(critical, record_type, data)?; + // remove the parsed data from the buffer + *buf = &buf[(4 + record_length)..]; + Ok(res) + } else { + Ok(None) + } + } + + /// Read all records until the end-of-message record, but only return them + /// if the actual end-of-message record was found, otherwise we assume we + /// don't have enough data to fully parse the records yet + pub fn read_until_eom(buf: &mut &'a [u8]) -> Result>>, RecordParseError> { + let mut data = *buf; + let mut records = vec![]; + + while data.len() >= 4 { + if let Some(rec) = Self::from_buffer(&mut data)? { + if rec.is_eom() { + *buf = data; + return Ok(Some(records)); + } + records.push(rec); + } + } + + Ok(None) + } + + /// Read all records in the buffer and return them + fn read_all(buf: &mut &'a [u8]) -> Result>, RecordParseError> { + let mut records = vec![]; + + while buf.len() >= 4 { + if let Some(rec) = Self::from_buffer(buf)? { + records.push(rec); + } + } + + if !buf.is_empty() { + Err(RecordParseError::UnexpectedExtraBytes) + } else { + Ok(records) + } + } + + pub async fn write(&self, mut w: impl AsyncWrite + Unpin) -> std::io::Result { + let mut bytes_written = 0; + + bytes_written += match self { + Record::EndOfMessage => { + w.write_u16(RecordType::EndOfMessage.raw_record_type()) + .await?; + w.write_u16(0u16).await?; + 4 + } + Record::NextProtocol(np) => np.write(w).await?, + Record::Error(e) => e.write(w).await?, + Record::AssociationMode(am) => am.write(w).await?, + Record::SupportedMacAlgorithms(alg) => alg.write(w).await?, + Record::CurrentParameters(params) => params.write(w, false).await?, + Record::NextParameters(params) => params.write(w, true).await?, + Record::SecurityAssociation(sec) => sec.write(w).await?, + Record::ValidityPeriod(vp) => vp.write(w).await?, + }; + + Ok(bytes_written) + } + + pub async fn write_all( + data: &Vec>, + mut w: impl AsyncWrite + Unpin, + append_eom: bool, + ) -> std::io::Result { + let mut bytes_written = 0; + for rec in data { + bytes_written += rec.write(&mut w).await?; + } + + if append_eom { + bytes_written += Record::EndOfMessage.write(&mut w).await?; + } + + Ok(bytes_written) + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct PtpKeyRequestMessage { + pub next_protocol: NextProtocols, + pub association_mode: AssociationMode, +} + +impl PtpKeyRequestMessage { + pub async fn write(&self, mut w: impl AsyncWrite + Unpin) -> io::Result { + let mut bytes_written = 0; + bytes_written += self.next_protocol.write(&mut w).await?; + bytes_written += self.association_mode.write(&mut w).await?; + bytes_written += Record::EndOfMessage.write(&mut w).await?; + + Ok(bytes_written) + } +} + +impl<'a> TryFrom>> for PtpKeyRequestMessage { + type Error = RecordParseError; + + fn try_from(value: Vec>) -> Result { + let mut next_protocol = None; + let mut association_mode = None; + for item in value { + match item { + Record::NextProtocol(p) => { + if next_protocol.is_some() { + return Err(RecordParseError::UnexpectedRecord(Record::NextProtocol(p))); + } + next_protocol.replace(p); + } + Record::AssociationMode(am) => { + if association_mode.is_some() { + return Err(RecordParseError::UnexpectedRecord(Record::AssociationMode( + am, + ))); + } + association_mode.replace(am); + } + Record::EndOfMessage => {} + _ => { + return Err(RecordParseError::UnexpectedRecord(item.into_owned())); + } + } + } + + let Some(next_protocol) = next_protocol else { + return Err(RecordParseError::MissingRecord(RecordType::NextProtocol)); + }; + + let Some(association_mode) = association_mode else { + return Err(RecordParseError::MissingRecord(RecordType::AssociationMode)); + }; + + Ok(PtpKeyRequestMessage { + next_protocol, + association_mode, + }) + } +} + +impl From for Vec> { + fn from(val: PtpKeyRequestMessage) -> Self { + vec![ + Record::NextProtocol(val.next_protocol), + Record::AssociationMode(val.association_mode), + ] + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct PtpKeyResponseMessage<'a> { + pub next_protocol: NextProtocols, + pub current_parameters: ParameterSet<'a>, + pub next_parameters: Option>, +} + +impl<'a> PtpKeyResponseMessage<'a> { + pub async fn write(&self, mut w: impl AsyncWrite + Unpin) -> io::Result { + let mut bytes_written = 0; + bytes_written += self.next_protocol.write(&mut w).await?; + bytes_written += self.current_parameters.write(&mut w, false).await?; + + if let Some(np) = &self.next_parameters { + bytes_written += np.write(&mut w, true).await?; + } + + bytes_written += Record::EndOfMessage.write(&mut w).await?; + + Ok(bytes_written) + } +} + +impl<'a> TryFrom>> for PtpKeyResponseMessage<'a> { + type Error = RecordParseError; + + fn try_from(value: Vec>) -> Result, Self::Error> { + let mut next_protocol = None; + let mut current_parameters: Option> = None; + let mut next_parameters: Option> = None; + for item in value { + match item { + Record::NextProtocol(p) => { + if next_protocol.is_some() { + return Err(RecordParseError::UnexpectedRecord(Record::NextProtocol(p))); + } + next_protocol.replace(p); + } + Record::CurrentParameters(p) => { + if current_parameters.is_some() { + return Err(RecordParseError::UnexpectedRecord( + Record::CurrentParameters(p.into_owned()), + )); + } + current_parameters.replace(p); + } + Record::NextParameters(p) => { + if next_parameters.is_some() { + return Err(RecordParseError::UnexpectedRecord(Record::NextParameters( + p.into_owned(), + ))); + } + next_parameters.replace(p); + } + Record::EndOfMessage => {} + _ => { + return Err(RecordParseError::UnexpectedRecord(item.into_owned())); + } + } + } + + let Some(next_protocol) = next_protocol else { + return Err(RecordParseError::MissingRecord(RecordType::NextProtocol)); + }; + + let Some(current_parameters) = current_parameters else { + return Err(RecordParseError::MissingRecord( + RecordType::CurrentParameters, + )); + }; + + Ok(PtpKeyResponseMessage { + next_protocol, + current_parameters, + next_parameters, + }) + } +} + +impl<'a> From> for Vec> { + fn from(val: PtpKeyResponseMessage<'a>) -> Self { + let mut v = vec![ + Record::NextProtocol(val.next_protocol), + Record::CurrentParameters(val.current_parameters), + ]; + if let Some(next_parameters) = val.next_parameters { + v.push(Record::NextParameters(next_parameters)); + } + + v + } +} + +#[cfg(test)] +mod tests { + use std::io::Cursor; + + use super::*; + + #[tokio::test] + async fn write_read_simple_records() { + let mut buf = vec![]; + + let rec = Record::EndOfMessage; + let written = rec.write(Cursor::new(&mut buf)).await.unwrap(); + assert_eq!(written, buf.len()); + let result = Record::from_buffer(&mut &buf[..]).unwrap(); + assert_eq!(result, Some(rec)); + + buf.clear(); + let rec = Record::AssociationMode(AssociationMode::Group { + ptp_domain_number: 7, + sdo_id: SdoId::try_from(11).unwrap(), + subgroup: 21, + }); + let written = rec.write(Cursor::new(&mut buf)).await.unwrap(); + assert_eq!(written, buf.len()); + let result = Record::from_buffer(&mut &buf[..]).unwrap(); + assert_eq!(result, Some(rec)); + + buf.clear(); + let rec = Record::ValidityPeriod(ValidityPeriod { + lifetime: 42, + update_period: 43, + grace_period: 44, + }); + let written = rec.write(Cursor::new(&mut buf)).await.unwrap(); + assert_eq!(written, buf.len()); + let result = Record::from_buffer(&mut &buf[..]).unwrap(); + assert_eq!(result, Some(rec)); + + buf.clear(); + let rec = Record::SecurityAssociation(SecurityAssocation::from_key_data( + 11, + &[1, 2, 3, 4, 5, 6, 7, 8, 9, 0], + )); + let written = rec.write(Cursor::new(&mut buf)).await.unwrap(); + assert_eq!(written, buf.len()); + let result = Record::from_buffer(&mut &buf[..]).unwrap(); + assert_eq!(result, Some(rec)); + + buf.clear(); + let rec = Record::NextProtocol(NextProtocols::ptpv2_1()); + let written = rec.write(Cursor::new(&mut buf)).await.unwrap(); + assert_eq!(written, buf.len()); + let result = Record::from_buffer(&mut &buf[..]).unwrap(); + assert_eq!(result, Some(rec)); + + buf.clear(); + let rec = Record::Error(ErrorRecord::BadRequest); + let written = rec.write(Cursor::new(&mut buf)).await.unwrap(); + assert_eq!(written, buf.len()); + let result = Record::from_buffer(&mut &buf[..]).unwrap(); + assert_eq!(result, Some(rec)); + + buf.clear(); + let rec = Record::SupportedMacAlgorithms(SupportedMacAlgorithms(vec![42])); + let written = rec.write(Cursor::new(&mut buf)).await.unwrap(); + assert_eq!(written, buf.len()); + let result = Record::from_buffer(&mut &buf[..]).unwrap(); + assert_eq!(result, Some(rec)); + } + + #[tokio::test] + async fn write_read_combined_records() { + let mut buf = vec![]; + + let rec = Record::CurrentParameters(ParameterSet { + security_assocation: SecurityAssocation::from_key_data( + 11, + &[1, 2, 3, 4, 5, 6, 7, 8, 9, 0], + ), + validity_period: ValidityPeriod { + lifetime: 42, + update_period: 43, + grace_period: 44, + }, + }); + let written = rec.write(Cursor::new(&mut buf)).await.unwrap(); + assert_eq!(written, buf.len()); + let result = Record::from_buffer(&mut &buf[..]).unwrap(); + assert_eq!(result, Some(rec)); + + buf.clear(); + let rec = Record::NextParameters(ParameterSet { + security_assocation: SecurityAssocation::from_key_data( + 11, + &[1, 2, 3, 4, 5, 6, 7, 8, 9, 0], + ), + validity_period: ValidityPeriod { + lifetime: 42, + update_period: 43, + grace_period: 44, + }, + }); + let written = rec.write(Cursor::new(&mut buf)).await.unwrap(); + assert_eq!(written, buf.len()); + let result = Record::from_buffer(&mut &buf[..]).unwrap(); + assert_eq!(result, Some(rec)); + } + + #[tokio::test] + async fn write_read_request() { + let mut buf = vec![]; + let request = PtpKeyRequestMessage { + next_protocol: NextProtocols::ptpv2_1(), + association_mode: AssociationMode::Group { + ptp_domain_number: 10, + sdo_id: SdoId::try_from(21).unwrap(), + subgroup: 16, + }, + }; + let written = request.write(Cursor::new(&mut buf)).await.unwrap(); + assert_eq!(written, buf.len()); + + let v = Record::read_until_eom(&mut &buf[..]).unwrap().unwrap(); + let result = PtpKeyRequestMessage::try_from(v).unwrap(); + assert_eq!(result, request); + } + + #[tokio::test] + async fn write_read_response() { + let mut buf = vec![]; + let response = PtpKeyResponseMessage { + next_protocol: NextProtocols::ptpv2_1(), + current_parameters: ParameterSet { + security_assocation: SecurityAssocation::from_key_data( + 11, + &[1, 2, 3, 4, 5, 6, 7, 8, 9, 0], + ), + validity_period: ValidityPeriod { + lifetime: 42, + update_period: 43, + grace_period: 44, + }, + }, + next_parameters: None, + }; + let written = response.write(Cursor::new(&mut buf)).await.unwrap(); + assert_eq!(written, buf.len()); + + let v = Record::read_until_eom(&mut &buf[..]).unwrap().unwrap(); + let result = PtpKeyResponseMessage::try_from(v).unwrap(); + assert_eq!(result, response); + } +} diff --git a/statime-linux/src/lib.rs b/statime-linux/src/lib.rs index 304445d61..7be61bd7e 100644 --- a/statime-linux/src/lib.rs +++ b/statime-linux/src/lib.rs @@ -2,6 +2,7 @@ extern crate core; pub mod clock; pub mod config; +mod ke; pub mod metrics; pub mod observer; pub mod socket; @@ -11,6 +12,8 @@ pub mod tracing; use std::path::Path; use config::Config; + +pub use ke::main as ke_main; pub use metrics::exporter::main as metrics_exporter_main; use tracing::LogLevel; use tracing_log::LogTracer; diff --git a/statime-linux/testkeys/gen-cert.sh b/statime-linux/testkeys/gen-cert.sh new file mode 100755 index 000000000..911d58bbe --- /dev/null +++ b/statime-linux/testkeys/gen-cert.sh @@ -0,0 +1,46 @@ +#! /bin/sh + +# This script generates a private key/certificate for a server, and signs it with the provided CA key +# based on https://docs.ntpd-rs.pendulum-project.org/development/ca/ + +# Because this script generate keys without passwords set, they should only be used in a development setting. + +if [ -z "$1" ]; then + echo "usage: gen-cert.sh name-of-server [ca-name]" + echo + echo "This will generate a name-of-server.key, name-of-server.pem and name-of-server.chain.pem file" + echo "containing the private key, public certificate, and full certificate chain (respectively)" + echo + echo "The second argument denotes the name of the CA be used (found in the files ca-name.key and ca-name.pem)" + echo "If this is omitted, the name 'testca' will be used." + exit +fi + +NAME="${1:-ntpd-rs.test}" +CA="${2:-testca}" + +# generate a key +openssl genrsa -out "$NAME".key 2048 + +# generate a certificate signing request +openssl req -batch -new -key "$NAME".key -out "$NAME".csr + +# generate an ext file +cat >> "$NAME".ext < "$NAME".chain.pem + +# cleanup +rm "$NAME".csr diff --git a/statime-linux/testkeys/test.chain.pem b/statime-linux/testkeys/test.chain.pem new file mode 100644 index 000000000..48929f296 --- /dev/null +++ b/statime-linux/testkeys/test.chain.pem @@ -0,0 +1,44 @@ +-----BEGIN CERTIFICATE----- +MIIDlTCCAn2gAwIBAgIUGAQ9YbdgwO3O0mpX5zBLK5auKRswDQYJKoZIhvcNAQEL +BQAwVzELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEQMA4GA1UEAwwHVGVzdCBDQTAeFw0y +NDAyMTUxMzUxNDlaFw0yOTAyMTMxMzUxNDlaMEUxCzAJBgNVBAYTAkFVMRMwEQYD +VQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBM +dGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDeUvzlIC5FPzVp7Usc +OwXyUp+rxbLP6dUsdDWL6wgk1od4CkzgxkqA/QeJszSZnGR25XUSPpY9N0sIvWZi +qPbUyms8sp/i0S7gOauj2I907prxa4wmzAWUBr5bvcAoq4DE8T5d1V0dWfs4nNx8 +PEo6PRDcwkjjMFRgSyelEKjv7R2xzZgNmwhqww8SculNzmd5fZjQblICZlWdcyni +0TVtYVvkdBV6YWq5no7nMI/Mupmb2GxAYO1mPUPGdZctf4x2fL/bWU3odRmTvqKl +1IKmXpmCux5t75c6f5itvLG3Ix2cMSq6H63aXf5VdjnQUALU00Yy1FCST8Tjylvn +2pmBAgMBAAGjazBpMB8GA1UdIwQYMBaAFHdVrpWwrf3bQ1WPu2uRCK21yG2lMAkG +A1UdEwQCMAAwCwYDVR0PBAQDAgUgMA8GA1UdEQQIMAaCBHRlc3QwHQYDVR0OBBYE +FKhpcMSKIX+ZvsNSsdPGKBmJx33lMA0GCSqGSIb3DQEBCwUAA4IBAQBPL3Roc9HB +yVgQsgn0NNWG0ZoTepdRMzVrbT8G/ubrEIIKSz6qJgFkptuHttdIcGv84cCA2g4+ +lwiB5cgdp0ZW5QmqlfA6vM1Mi4PZvWJknoDxz3ZL8nDf2sMZyT6QNfZ68GhSIeKb +kga1+QUnglmZPLJzL0ULsy/hm4+VZe2F+vaosKlN/2hw7bXuygcObZc72ws3+eCC +Zqd0A9+zQuTP4jXqDrNl8F3FyI5olHbGLItEV3Cm3FiB+iiKDORdYwIdE4C3lRzK +wdNancNXGn9Lio7pKhAmqAu0p+Ezsa8cZ1TVPTAG3ihSeKDqkSugOdTIRt9WNGqF +LWEh979+PsiZ +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDkTCCAnmgAwIBAgIUSJ4RLbU532cpXBrIPM0dgLjFoRowDQYJKoZIhvcNAQEL +BQAwVzELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEQMA4GA1UEAwwHVGVzdCBDQTAgFw0y +MzAxMjAwOTQzMzdaGA80NzYwMTIxNzA5NDMzN1owVzELMAkGA1UEBhMCQVUxEzAR +BgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5 +IEx0ZDEQMA4GA1UEAwwHVGVzdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBALzqkvECUcCFlg4cldjWKD1/X2e+FPrMBesmUCDExAtGYIjJy2YFovFL +20eNFa4K3QK61MfsmnbhC97Q3Nrm2tFiDXdM1XjnnbGk/GKtTH/cS/v5FQt+8kbj +YPKkxfwo02Nhgf8r0Ttsg439tuT+qpw3CymVzEZDllhYFL0EDq5JHAx9Sz5RiXm4 +1+4E0ahWpWbTagiG/Ldgk/sXCTZvxsCw7gbULKSVEbaN+cW+pXqkD3YSvrnYCPtk +/8OK7llBCtDC9puDIntrd5z6tIxCbj3jnfb9Ek/Pb/AmK04NF5OPw+eUgEwteSde +lNInFgNnlEPikNrkDAmBydLuEX7yCO8CAwEAAaNTMFEwHQYDVR0OBBYEFHdVrpWw +rf3bQ1WPu2uRCK21yG2lMB8GA1UdIwQYMBaAFHdVrpWwrf3bQ1WPu2uRCK21yG2l +MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFHWNTDdy9BbCoX5 +RRvP0S4V0g8HcaWohYuI7uNsDwW/xvOsJ7u+1rjv/Hx3lOCtnEHCAS5peJQenf6Y +uQSXbt2BVX7U01TzGKC9y47yxgovpdKJDiJodWSGs6sZP/4x3M5AbGmhmdfSBFAZ +/fchAzZPWd5FdYBEaT5J1nnXDCe3G5Aa43zvZzN8i/YCJ376yB7Vt6qUW8L70o9X +++snpnom2bvIKwkO4Z9jBY6njrpYjE212N1OY+eYRLknOdJlFuy6kGO2ipEoPKt/ ++vur95a6fTo8WiU2kYQc649XiPNW53v1epWNFJCRoOFietIVrKANWuqQB7xVYuIG +Yo0A3Sw= +-----END CERTIFICATE----- diff --git a/statime-linux/testkeys/test.ext b/statime-linux/testkeys/test.ext new file mode 100644 index 000000000..d0b987b96 --- /dev/null +++ b/statime-linux/testkeys/test.ext @@ -0,0 +1,7 @@ +authorityKeyIdentifier=keyid,issuer +basicConstraints=CA:FALSE +keyUsage = keyEncipherment +subjectAltName = @alt_names + +[alt_names] +DNS.1 = test diff --git a/statime-linux/testkeys/test.key b/statime-linux/testkeys/test.key new file mode 100644 index 000000000..57b38f265 --- /dev/null +++ b/statime-linux/testkeys/test.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDeUvzlIC5FPzVp +7UscOwXyUp+rxbLP6dUsdDWL6wgk1od4CkzgxkqA/QeJszSZnGR25XUSPpY9N0sI +vWZiqPbUyms8sp/i0S7gOauj2I907prxa4wmzAWUBr5bvcAoq4DE8T5d1V0dWfs4 +nNx8PEo6PRDcwkjjMFRgSyelEKjv7R2xzZgNmwhqww8SculNzmd5fZjQblICZlWd +cyni0TVtYVvkdBV6YWq5no7nMI/Mupmb2GxAYO1mPUPGdZctf4x2fL/bWU3odRmT +vqKl1IKmXpmCux5t75c6f5itvLG3Ix2cMSq6H63aXf5VdjnQUALU00Yy1FCST8Tj +ylvn2pmBAgMBAAECggEADEUmQfsJsravWv5Gs/Mp+9rFhzRtDv6HzmFHh0Zcd7QD +XAOthIZNHFtarCXg1E2GWe2GDZC4t/4yPPQ/D1jl7o6RZpMxMdJ7tNJZ/7v3BuwY +TxSxcn62HBpXLTcGwPfeLoBmAcXB0tnhfJljrdOiAR7XrGdooGlNbtcXBAwsVrl0 +HULHCeUhNpa4n/vw0wz7z9ikDgJVgyfI+InOQAgpesnJdRE5UiULiJNiUAiTgMxk +5HuTFPQv8DjJ/St+J6K8EuRXyEe/PboNfcJVB3NHr3ger7OJIM3EmsdSiprS27VX +gd6P/DL73ZoPekiR7JT3lJ8wXTPIRZzq23hdoQ3jMQKBgQD3WtpxwK8vTX1LRfPc +kDcMNopqP2xDhSAh8xx83pHq0FNtrbJ1Zzjd1ZhQD64MkyTrf4s65ndAf1WTXPK3 +N5GhypxMHlg0VBo7ihbgF6HDhF9KvAvJRoCvq2RVq0EdKhT6cHV2ffzeCnQPeOaJ +K+uKKqv8kx29B5tOnETfRcEqzQKBgQDmGC2pMKtc2Z1M/DsL9nBMIJdSDOktUD00 +R4zDrTu3Zq3MUnPighxUJ40ndLaQ//rFecPGXrTiKwL+e2I5RaHIBGOf7gPk41I3 +lXPBA4ZA9JKDPARrJEFv1ivxRH3iFbYTX2U5mG5lmvpZAKd00y8F9uRVanqGIxL8 +liOGTMbRhQKBgEROkJhtL3OOoJUS4j78jFcRELQ6g3k54B+/9RP+mqkPkpptJyUI +tkl8XK1vRw8t6zolZJuFXVUwVL93IYe7DrpxKyxSDOWWfBAJGDwWCpPlkDMhLtgM +Yikg2XpW6opVPqnSOH3aWOQwWtCTAIbM3CSxmAEN116GJTtWKCbIKonZAoGBAKNB +3IQDSFMlBHsvLCYcwSkCpttM1dWU7Rg+/B2KuKg0bnRevGKmtrrvJTQtl6viNGgR +MVt6rxUaN5Ggsdy5D0T/+D/tfU3WDOvrWqpEeel0ntAGsYvjuZpEeAFD+52CkiHE +uenXwdzNzXuA/0/g16h/uRrx5eVu1XXqgWI074yRAoGBAOnQby9IvmdJQRZUgJ97 +MGpbrPPRCo4TyrdMKu0wZ7zBpUhm552d98IRE7OXDzdd2cb+APk3hQpPVomHBTHf +kCLe1pzn4j2hskRJ6oAfCer7B3ix+Mz6/hJrwtjRy4f0bETmm3XEb8SlOrhK5TP9 +SS5bFPgS0mwH4VpPe0uxCRlx +-----END PRIVATE KEY----- diff --git a/statime-linux/testkeys/test.pem b/statime-linux/testkeys/test.pem new file mode 100644 index 000000000..aef5566eb --- /dev/null +++ b/statime-linux/testkeys/test.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDlTCCAn2gAwIBAgIUGAQ9YbdgwO3O0mpX5zBLK5auKRswDQYJKoZIhvcNAQEL +BQAwVzELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEQMA4GA1UEAwwHVGVzdCBDQTAeFw0y +NDAyMTUxMzUxNDlaFw0yOTAyMTMxMzUxNDlaMEUxCzAJBgNVBAYTAkFVMRMwEQYD +VQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBM +dGQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDeUvzlIC5FPzVp7Usc +OwXyUp+rxbLP6dUsdDWL6wgk1od4CkzgxkqA/QeJszSZnGR25XUSPpY9N0sIvWZi +qPbUyms8sp/i0S7gOauj2I907prxa4wmzAWUBr5bvcAoq4DE8T5d1V0dWfs4nNx8 +PEo6PRDcwkjjMFRgSyelEKjv7R2xzZgNmwhqww8SculNzmd5fZjQblICZlWdcyni +0TVtYVvkdBV6YWq5no7nMI/Mupmb2GxAYO1mPUPGdZctf4x2fL/bWU3odRmTvqKl +1IKmXpmCux5t75c6f5itvLG3Ix2cMSq6H63aXf5VdjnQUALU00Yy1FCST8Tjylvn +2pmBAgMBAAGjazBpMB8GA1UdIwQYMBaAFHdVrpWwrf3bQ1WPu2uRCK21yG2lMAkG +A1UdEwQCMAAwCwYDVR0PBAQDAgUgMA8GA1UdEQQIMAaCBHRlc3QwHQYDVR0OBBYE +FKhpcMSKIX+ZvsNSsdPGKBmJx33lMA0GCSqGSIb3DQEBCwUAA4IBAQBPL3Roc9HB +yVgQsgn0NNWG0ZoTepdRMzVrbT8G/ubrEIIKSz6qJgFkptuHttdIcGv84cCA2g4+ +lwiB5cgdp0ZW5QmqlfA6vM1Mi4PZvWJknoDxz3ZL8nDf2sMZyT6QNfZ68GhSIeKb +kga1+QUnglmZPLJzL0ULsy/hm4+VZe2F+vaosKlN/2hw7bXuygcObZc72ws3+eCC +Zqd0A9+zQuTP4jXqDrNl8F3FyI5olHbGLItEV3Cm3FiB+iiKDORdYwIdE4C3lRzK +wdNancNXGn9Lio7pKhAmqAu0p+Ezsa8cZ1TVPTAG3ihSeKDqkSugOdTIRt9WNGqF +LWEh979+PsiZ +-----END CERTIFICATE----- diff --git a/statime-linux/testkeys/testca.key b/statime-linux/testkeys/testca.key new file mode 100644 index 000000000..4dd5aea21 --- /dev/null +++ b/statime-linux/testkeys/testca.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC86pLxAlHAhZYO +HJXY1ig9f19nvhT6zAXrJlAgxMQLRmCIyctmBaLxS9tHjRWuCt0CutTH7Jp24Qve +0Nza5trRYg13TNV4552xpPxirUx/3Ev7+RULfvJG42DypMX8KNNjYYH/K9E7bION +/bbk/qqcNwsplcxGQ5ZYWBS9BA6uSRwMfUs+UYl5uNfuBNGoVqVm02oIhvy3YJP7 +Fwk2b8bAsO4G1CyklRG2jfnFvqV6pA92Er652Aj7ZP/Diu5ZQQrQwvabgyJ7a3ec ++rSMQm494532/RJPz2/wJitODReTj8PnlIBMLXknXpTSJxYDZ5RD4pDa5AwJgcnS +7hF+8gjvAgMBAAECggEAAZrFvgbSoSHLqN7lSP7ZLtfkTwpuA7RZeIUQNQmgGW0P +3BFQZA0v8kaImiM8gdb2TC7dKJSGBKImQTW4CXmejxSX7l1H7bsYWHBgHKsYifQw +q95QccSuZHJ0zYIGtcMA8e2Zk4Qa/GVzbT7+0QMb1IKuh+mRrbN9hLWsXJTTuYvf +GppDVqMdDPy5NibudiZPKdpnMyDCJ/Wxl1+1PX18anifzBHw/G8ZPnLU3OKDqL2T +OtEivvk9ZFDiRKKEsHksr+aLcUGhXFswk0zEQJwMj6rFwcDEExTQkMar+xaxshpf +qo6AC88SDT9qEffSHHGJzTi73NIGgLNPO1aON4/pwQKBgQDUPo+ZJymo9IunaXWi +HywqLLVZJSvqo2x9SrlqqYe3Yz0bROGBoHSMaGQzxiDApeOabdyg24wrU1P24jrC +jPt94TWdu8bZKAkZAGOUPvdSGA/5yQkxVSMUK5zZwQxyLWfb77+B+WSvzhxI17Bt +bX6od5pcdFSC5OczJ64DjLeHlQKBgQDj3NjsbLnxFu88A121kPD4AdpoMAtgrA5R +AWwc7mWzKvL1RZlZCn861QMaRoUThQW4+dxTdoOoL68PXK3L8zuU3imKOBOe33lh +j7B+M0gjdWnkcTag5q56qk1VA4YZ0R30LhUw44JxFHXhtuTR00CattI1pOQr6OdK +By3kj4NdcwKBgQChOxko1eg+0e6Y8XMMAjQxoZ7tpmAzMYxDrZUm4rwXYsrTwUKx +jyuaUd70uai90AcTlCuLAtz7OKTLIlZS3nhZytBJD5Fh+5jVpkb/IcoNUfwo20Ah +erRYKT1Q6ebDgZypJfpMCSEksCUqbLc4mXojDiBz5WchvDOp15XIWog89QKBgE3c +Vxtig58IETNWixzRrCVyrKjRUfH0mOfBLqosJAA2+tIouB+e4J6/ztGZqztiRvRQ +HKNAafh8YrtDFfgM4x0ZVORwCPROtHFL4ikdaNcE9ewja2FLse8kZkxYaehEdpHL +dV5BP39YWHeKQWIZZ4f2VJoUAAupB+9ZyKrDB0ZVAoGBALJ0KzHlAizbZyXRtfk+ +ThnegTgjbTd6drMTsRlyHdL1Zet0tdx2nhn2keMQVSDep5KEwTvm+Wy41s9EmzZx +RyehNaq9hMljLGR6mtr4Em5RtxtkPTwoJcOttHXQXnTgplDbePb8zQ8N084fScek +0dIjCbVBt5X7akmgHaaizIDl +-----END PRIVATE KEY----- diff --git a/statime-linux/testkeys/testca.pem b/statime-linux/testkeys/testca.pem new file mode 100644 index 000000000..9d94020be --- /dev/null +++ b/statime-linux/testkeys/testca.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDkTCCAnmgAwIBAgIUSJ4RLbU532cpXBrIPM0dgLjFoRowDQYJKoZIhvcNAQEL +BQAwVzELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEQMA4GA1UEAwwHVGVzdCBDQTAgFw0y +MzAxMjAwOTQzMzdaGA80NzYwMTIxNzA5NDMzN1owVzELMAkGA1UEBhMCQVUxEzAR +BgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5 +IEx0ZDEQMA4GA1UEAwwHVGVzdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC +AQoCggEBALzqkvECUcCFlg4cldjWKD1/X2e+FPrMBesmUCDExAtGYIjJy2YFovFL +20eNFa4K3QK61MfsmnbhC97Q3Nrm2tFiDXdM1XjnnbGk/GKtTH/cS/v5FQt+8kbj +YPKkxfwo02Nhgf8r0Ttsg439tuT+qpw3CymVzEZDllhYFL0EDq5JHAx9Sz5RiXm4 +1+4E0ahWpWbTagiG/Ldgk/sXCTZvxsCw7gbULKSVEbaN+cW+pXqkD3YSvrnYCPtk +/8OK7llBCtDC9puDIntrd5z6tIxCbj3jnfb9Ek/Pb/AmK04NF5OPw+eUgEwteSde +lNInFgNnlEPikNrkDAmBydLuEX7yCO8CAwEAAaNTMFEwHQYDVR0OBBYEFHdVrpWw +rf3bQ1WPu2uRCK21yG2lMB8GA1UdIwQYMBaAFHdVrpWwrf3bQ1WPu2uRCK21yG2l +MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFHWNTDdy9BbCoX5 +RRvP0S4V0g8HcaWohYuI7uNsDwW/xvOsJ7u+1rjv/Hx3lOCtnEHCAS5peJQenf6Y +uQSXbt2BVX7U01TzGKC9y47yxgovpdKJDiJodWSGs6sZP/4x3M5AbGmhmdfSBFAZ +/fchAzZPWd5FdYBEaT5J1nnXDCe3G5Aa43zvZzN8i/YCJ376yB7Vt6qUW8L70o9X +++snpnom2bvIKwkO4Z9jBY6njrpYjE212N1OY+eYRLknOdJlFuy6kGO2ipEoPKt/ ++vur95a6fTo8WiU2kYQc649XiPNW53v1epWNFJCRoOFietIVrKANWuqQB7xVYuIG +Yo0A3Sw= +-----END CERTIFICATE----- diff --git a/statime-linux/testkeys/testca.srl b/statime-linux/testkeys/testca.srl new file mode 100644 index 000000000..48c06f196 --- /dev/null +++ b/statime-linux/testkeys/testca.srl @@ -0,0 +1 @@ +18043D61B760C0EDCED26A57E7304B2B96AE291B From ab4b8d628ec835529bbee540e5d1ebddee56ccb9 Mon Sep 17 00:00:00 2001 From: David Venhoek Date: Thu, 22 Feb 2024 13:49:35 +0100 Subject: [PATCH 05/23] Added nts security provider framework. --- statime-linux/src/lib.rs | 1 + statime-linux/src/securityprovider.rs | 138 +++++++++++++++++++++ statime/src/crypto.rs | 10 +- statime/src/datastructures/messages/mod.rs | 4 +- 4 files changed, 147 insertions(+), 6 deletions(-) create mode 100644 statime-linux/src/securityprovider.rs diff --git a/statime-linux/src/lib.rs b/statime-linux/src/lib.rs index 7be61bd7e..56ab74912 100644 --- a/statime-linux/src/lib.rs +++ b/statime-linux/src/lib.rs @@ -5,6 +5,7 @@ pub mod config; mod ke; pub mod metrics; pub mod observer; +pub mod securityprovider; pub mod socket; pub mod tlvforwarder; pub mod tracing; diff --git a/statime-linux/src/securityprovider.rs b/statime-linux/src/securityprovider.rs new file mode 100644 index 000000000..4b94bacb1 --- /dev/null +++ b/statime-linux/src/securityprovider.rs @@ -0,0 +1,138 @@ +use std::{collections::HashMap, sync::Arc}; + +use rand::Rng; +use statime::crypto::{ + SecurityAssociation, SecurityAssociationProvider, SecurityPolicy, SenderIdentificaton, +}; + +pub struct NTSProvider { + associations: Arc>>>, +} + +struct NTSAssociationInner { + grace_period: std::time::Duration, + transition_period: std::time::Duration, + keys: HashMap, + current_key: u32, + next_key: Option, + sequence_ids: HashMap<(SenderIdentificaton, u32), u16>, +} + +pub struct NTSAssociation<'a>(std::sync::MutexGuard<'a, NTSAssociationInner>); + +struct NTSKey { + key: statime::crypto::HmacSha256_128, + valid_till: std::time::Instant, +} + +impl SecurityAssociationProvider for NTSProvider { + type Association<'a> = NTSAssociation<'a>; + + fn lookup(&self, spp: u8) -> Option> { + self.associations + .get(&spp) + .map(|a| NTSAssociation(a.lock().unwrap())) + } +} + +impl<'a> SecurityAssociation for NTSAssociation<'a> { + fn policy_data(&self) -> statime::crypto::SecurityPolicy { + SecurityPolicy { + ignore_correction: false, + } + } + + fn mac(&self, key_id: u32) -> Option<&dyn statime::crypto::Mac> { + self.0.keys.get(&key_id).and_then(|key| { + if std::time::Instant::now() < key.valid_till + self.0.grace_period { + Some(&key.key as &dyn statime::crypto::Mac) + } else { + None + } + }) + } + + fn register_sequence_id( + &mut self, + key_id: u32, + sender: statime::crypto::SenderIdentificaton, + sequence_id: u16, + ) -> bool { + match self.0.sequence_ids.entry((sender, key_id)) { + std::collections::hash_map::Entry::Occupied(mut entry) => { + if (*entry.get() - sequence_id) as i16 > 0 { + *entry.get_mut() = sequence_id; + true + } else { + false + } + } + std::collections::hash_map::Entry::Vacant(entry) => { + entry.insert(sequence_id); + true + } + } + } + + fn signing_mac(&self) -> (u32, &dyn statime::crypto::Mac) { + ( + self.0.current_key, + self.0 + .keys + .get(&self.0.current_key) + .map(|k| &k.key) + .unwrap(), + ) + } +} + +impl NTSAssociationInner { + fn clean_sequence_id(&mut self) { + self.sequence_ids + .retain(|(_, key_id), _| self.keys.contains_key(key_id)) + } + + fn clean_keys(&mut self) { + let expired = std::time::Instant::now() - self.grace_period; + self.keys + .retain(|_, NTSKey { valid_till, .. }| *valid_till > expired); + } +} + +#[allow(unused)] +// await_holding_lock is somewhat buggy (see https://github.com/rust-lang/rust-clippy/issues/9683) +#[allow(clippy::await_holding_lock)] +async fn assocation_manager(association: Arc>) { + let mut this = association.lock().unwrap(); + + loop { + // wait for new parameters + if this.next_key.is_none() { + let transition_start = this.keys[&this.current_key].valid_till - this.transition_period; + let random_offset = this + .transition_period + .mul_f32(rand::thread_rng().gen_range(0.0..0.75)); + drop(this); + tokio::time::sleep_until((transition_start + random_offset).into()).await; + this = association.lock().unwrap(); + // TODO: Fetch new parameters + } + + // switchover + let switchover_time = this.keys[&this.current_key].valid_till; + + drop(this); + tokio::time::sleep_until(switchover_time.into()).await; + this = association.lock().unwrap(); + let old_key = this.current_key; + this.current_key = this.next_key.take().unwrap(); + + // wait out grace period + let grace_end = this.keys[&old_key].valid_till + this.grace_period; + drop(this); + tokio::time::sleep_until(grace_end.into()).await; + this = association.lock().unwrap(); + this.clean_keys(); + this.clean_sequence_id(); + } +} diff --git a/statime/src/crypto.rs b/statime/src/crypto.rs index c6de489fd..45c17f9a6 100644 --- a/statime/src/crypto.rs +++ b/statime/src/crypto.rs @@ -71,10 +71,12 @@ pub trait SecurityAssociation { /// Interface to the database of security associations pub trait SecurityAssociationProvider { /// Type used for the security assocations - type Association: SecurityAssociation; + type Association<'a>: SecurityAssociation + where + Self: 'a; /// Lookup a specific security association - fn lookup(&self, spp: u8) -> Option; + fn lookup(&self, spp: u8) -> Option>; } /// Association type for the empty security association provider @@ -107,9 +109,9 @@ impl SecurityAssociation for NoSecurityAssocation { pub struct NoSecurityProvider; impl SecurityAssociationProvider for NoSecurityProvider { - type Association = NoSecurityAssocation; + type Association<'a> = NoSecurityAssocation; - fn lookup(&self, _spp: u8) -> Option { + fn lookup(&self, _spp: u8) -> Option> { None } } diff --git a/statime/src/datastructures/messages/mod.rs b/statime/src/datastructures/messages/mod.rs index 0f4ca30fe..c3bd10dac 100644 --- a/statime/src/datastructures/messages/mod.rs +++ b/statime/src/datastructures/messages/mod.rs @@ -665,9 +665,9 @@ mod tests { } impl SecurityAssociationProvider for TestSecurityProvider { - type Association = TestSecurityAssociation; + type Association<'a> = TestSecurityAssociation; - fn lookup(&self, _spp: u8) -> Option { + fn lookup(&self, _spp: u8) -> Option> { Some(TestSecurityAssociation(self.0)) } } From 5550ad9c39454d2246cf427b9cc9c9db3661048f Mon Sep 17 00:00:00 2001 From: Ruben Nijveld Date: Thu, 7 Mar 2024 14:41:22 +0100 Subject: [PATCH 06/23] Deny NTS4PTP TLS connection to clients not on the allowed client list --- statime-linux/src/ke/mod.rs | 32 ++++++++++++++-- statime-linux/src/ke/tls_utils.rs | 61 +++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 4 deletions(-) create mode 100644 statime-linux/src/ke/tls_utils.rs diff --git a/statime-linux/src/ke/mod.rs b/statime-linux/src/ke/mod.rs index 9f49f8747..5f2dc12c0 100644 --- a/statime-linux/src/ke/mod.rs +++ b/statime-linux/src/ke/mod.rs @@ -10,7 +10,6 @@ use clap::Parser; use log::{debug, info, warn}; use rustls::{ pki_types::{CertificateDer, PrivateKeyDer}, - server::NoClientAuth, ServerConfig, }; use tokio::{ @@ -29,6 +28,7 @@ use self::record::{ }; mod record; +mod tls_utils; struct Key { id: u32, @@ -116,6 +116,7 @@ struct KeConfig { listen_addr: SocketAddr, cert_chain_path: PathBuf, private_key_path: PathBuf, + allowed_clients: Vec, } async fn load_certs(path: impl AsRef) -> io::Result>> { @@ -129,17 +130,33 @@ async fn load_private_key(path: impl AsRef) -> io::Result>) -> io::Result>> { + let mut certs = vec![]; + for p in it { + certs.push( + load_certs(p) + .await? + .into_iter() + .next() + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "No certificate found in file"))? + ); + } + Ok(certs) +} + async fn prep_server_config( cert_chain_path: impl AsRef, private_key_path: impl AsRef, + allowed_clients: impl Iterator>, ) -> Result> { let cert_chain = load_certs(cert_chain_path).await?; let key_der = load_private_key(private_key_path).await?; + let allowed_clients = load_certs_from_files(allowed_clients).await?; // setup tls server let mut config = rustls::ServerConfig::builder() .with_client_cert_verifier(Arc::new( - NoClientAuth, // TODO: replace with proper client cert verification + tls_utils::OnlyAllowedClients::new(rustls::crypto::ring::default_provider(), allowed_clients), )) .with_single_cert(cert_chain, key_der)?; config.alpn_protocols.clear(); @@ -173,6 +190,9 @@ pub async fn main() -> Result<(), Box> { let listen_addr = "0.0.0.0:4460"; let cert_chain_path = "statime-linux/testkeys/test.chain.pem"; let private_key_path = "statime-linux/testkeys/test.key"; + let allowed_clients = vec![ + "statime-linux/testkeys/test.chain.pem".into(), + ]; let listen_addr = listen_addr .to_socket_addrs()? @@ -186,12 +206,16 @@ pub async fn main() -> Result<(), Box> { listen_addr, cert_chain_path: cert_chain_path.into(), private_key_path: private_key_path.into(), + allowed_clients, }) }; // setup the tls server - let config = - prep_server_config(&ke_config.cert_chain_path, &ke_config.private_key_path).await?; + let config = prep_server_config( + &ke_config.cert_chain_path, + &ke_config.private_key_path, + ke_config.allowed_clients.iter(), + ).await?; let acceptor = TlsAcceptor::from(Arc::new(config)); let listener = TcpListener::bind(ke_config.listen_addr).await?; diff --git a/statime-linux/src/ke/tls_utils.rs b/statime-linux/src/ke/tls_utils.rs new file mode 100644 index 000000000..82da2c356 --- /dev/null +++ b/statime-linux/src/ke/tls_utils.rs @@ -0,0 +1,61 @@ +use rustls::{crypto::{CryptoProvider, WebPkiSupportedAlgorithms}, server::danger::ClientCertVerified}; + +#[derive(Debug)] +pub struct OnlyAllowedClients { + supported_algs: WebPkiSupportedAlgorithms, + allowed_clients: Vec>, +} + +impl OnlyAllowedClients { + pub fn new(provider: CryptoProvider, allowed_clients: Vec>) -> Self { + OnlyAllowedClients { + supported_algs: provider.signature_verification_algorithms, + allowed_clients, + } + } +} + +impl rustls::server::danger::ClientCertVerifier for OnlyAllowedClients { + fn verify_client_cert( + &self, + end_entity: &rustls::pki_types::CertificateDer<'_>, + _intermediates: &[rustls::pki_types::CertificateDer<'_>], + _now: rustls::pki_types::UnixTime, + ) -> Result { + if self.allowed_clients.iter().any(|c| c == end_entity) { + Ok(ClientCertVerified::assertion()) + } else { + Err(rustls::Error::InvalidCertificate(rustls::CertificateError::ApplicationVerificationFailure)) + } + } + + fn client_auth_mandatory(&self) -> bool { + true + } + + fn root_hint_subjects(&self) -> &[rustls::DistinguishedName] { + &[] + } + + fn verify_tls12_signature( + &self, + message: &[u8], + cert: &rustls::pki_types::CertificateDer<'_>, + dss: &rustls::DigitallySignedStruct, + ) -> Result { + rustls::crypto::verify_tls12_signature(message, cert, dss, &self.supported_algs) + } + + fn verify_tls13_signature( + &self, + message: &[u8], + cert: &rustls::pki_types::CertificateDer<'_>, + dss: &rustls::DigitallySignedStruct, + ) -> Result { + rustls::crypto::verify_tls13_signature(message, cert, dss, &self.supported_algs) + } + + fn supported_verify_schemes(&self) -> Vec { + self.supported_algs.supported_schemes() + } +} From b5adcb449e0da044b8b8701c4038978e1c31811a Mon Sep 17 00:00:00 2001 From: Ruben Nijveld Date: Thu, 7 Mar 2024 15:02:56 +0100 Subject: [PATCH 07/23] Cleanup of ke code structure --- statime-linux/src/ke/client.rs | 1 + statime-linux/src/ke/common.rs | 50 +++++ statime-linux/src/ke/mod.rs | 348 +----------------------------- statime-linux/src/ke/server.rs | 293 +++++++++++++++++++++++++ statime-linux/src/ke/tls_utils.rs | 52 +++-- statime/src/port/bmca.rs | 4 +- statime/src/port/mod.rs | 49 ++++- 7 files changed, 423 insertions(+), 374 deletions(-) create mode 100644 statime-linux/src/ke/client.rs create mode 100644 statime-linux/src/ke/common.rs create mode 100644 statime-linux/src/ke/server.rs diff --git a/statime-linux/src/ke/client.rs b/statime-linux/src/ke/client.rs new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/statime-linux/src/ke/client.rs @@ -0,0 +1 @@ + diff --git a/statime-linux/src/ke/common.rs b/statime-linux/src/ke/common.rs new file mode 100644 index 000000000..025b5675d --- /dev/null +++ b/statime-linux/src/ke/common.rs @@ -0,0 +1,50 @@ +use std::{io, path::Path}; + +use rustls::pki_types::{CertificateDer, PrivateKeyDer}; +use tokio::time::Instant; + +pub struct Key { + pub id: u32, + data: [u8; 32], + pub valid_since: Instant, +} + +impl Key { + pub fn generate(id: u32, mut rng: impl rand::Rng) -> Key { + let mut data = [0; 32]; + rng.fill_bytes(&mut data); + + Key { + id, + data, + valid_since: Instant::now(), + } + } + + pub fn as_bytes(&self) -> &[u8] { + &self.data + } +} + +pub async fn load_certs(path: impl AsRef) -> io::Result>> { + let cert_chain_data = tokio::fs::read(path).await?; + rustls_pemfile::certs(&mut &cert_chain_data[..]).collect() +} + +pub async fn load_private_key(path: impl AsRef) -> io::Result> { + let private_key_data = tokio::fs::read(path).await?; + rustls_pemfile::private_key(&mut &private_key_data[..])? + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "No private key data found")) +} + +pub async fn load_certs_from_files( + it: impl Iterator>, +) -> io::Result>> { + let mut certs = vec![]; + for p in it { + certs.push(load_certs(p).await?.into_iter().next().ok_or_else(|| { + io::Error::new(io::ErrorKind::InvalidData, "No certificate found in file") + })?); + } + Ok(certs) +} diff --git a/statime-linux/src/ke/mod.rs b/statime-linux/src/ke/mod.rs index 5f2dc12c0..0b148b520 100644 --- a/statime-linux/src/ke/mod.rs +++ b/statime-linux/src/ke/mod.rs @@ -1,347 +1,7 @@ -use std::{ - io, - net::{SocketAddr, ToSocketAddrs}, - path::{Path, PathBuf}, - sync::Arc, - time::Duration, -}; - -use clap::Parser; -use log::{debug, info, warn}; -use rustls::{ - pki_types::{CertificateDer, PrivateKeyDer}, - ServerConfig, -}; -use tokio::{ - io::AsyncReadExt, - net::{TcpListener, TcpStream}, - sync::RwLock, - time::Instant, -}; -use tokio_rustls::{server::TlsStream, TlsAcceptor}; - -use crate::initialize_logging_parse_config; - -use self::record::{ - NextProtocol, NextProtocols, ParameterSet, PtpKeyRequestMessage, PtpKeyResponseMessage, Record, - SecurityAssocation, ValidityPeriod, -}; - +mod client; +mod common; mod record; +mod server; mod tls_utils; -struct Key { - id: u32, - data: [u8; 32], - pub valid_since: Instant, -} - -impl Key { - fn generate(id: u32, mut rng: impl rand::Rng) -> Key { - let mut data = [0; 32]; - rng.fill_bytes(&mut data); - - Key { - id, - data, - valid_since: Instant::now(), - } - } - - fn as_bytes(&self) -> &[u8] { - &self.data - } -} - -struct KeySetStore { - current: Key, - next: Option, -} - -impl KeySetStore { - /// This function generates the next key if there is no next key, otherwise - /// it moves the next key to the current key. - fn rotate(&mut self) { - let next_key = self.next.take(); - - if let Some(next_key) = next_key { - self.current = next_key; - // when switched to the current key the valid since time resets - self.current.valid_since = Instant::now(); - } else { - self.next - .replace(Key::generate(self.current.id + 1, rand::thread_rng())); - } - } - - /// Generate a new keyset that stores the current key. - fn new() -> KeySetStore { - KeySetStore { - current: Key::generate(0, rand::thread_rng()), - next: None, - } - } - - fn spawn_rotate_process( - store: Arc>, - lifetime: Duration, - update_period: Duration, - ) -> tokio::task::JoinHandle<()> { - tokio::spawn(async move { - loop { - // if update period is as great as lifetime we immediately rotate to have the - // next key available - if update_period < lifetime { - tokio::time::sleep(lifetime - update_period).await; - } - - // first rotate generates the next key - store.write().await.rotate(); - - // wait for the update period, but never more than the lifetime - tokio::time::sleep(update_period.min(lifetime)).await; - - // second rotate moves the next key to current (deleting the previous current - // key) - store.write().await.rotate(); - } - }) - } -} - -struct KeConfig { - validity_period: u32, - update_period: u32, - grace_period: u32, - listen_addr: SocketAddr, - cert_chain_path: PathBuf, - private_key_path: PathBuf, - allowed_clients: Vec, -} - -async fn load_certs(path: impl AsRef) -> io::Result>> { - let cert_chain_data = tokio::fs::read(path).await?; - rustls_pemfile::certs(&mut &cert_chain_data[..]).collect() -} - -async fn load_private_key(path: impl AsRef) -> io::Result> { - let private_key_data = tokio::fs::read(path).await?; - rustls_pemfile::private_key(&mut &private_key_data[..])? - .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "No private key data found")) -} - -async fn load_certs_from_files(it: impl Iterator>) -> io::Result>> { - let mut certs = vec![]; - for p in it { - certs.push( - load_certs(p) - .await? - .into_iter() - .next() - .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "No certificate found in file"))? - ); - } - Ok(certs) -} - -async fn prep_server_config( - cert_chain_path: impl AsRef, - private_key_path: impl AsRef, - allowed_clients: impl Iterator>, -) -> Result> { - let cert_chain = load_certs(cert_chain_path).await?; - let key_der = load_private_key(private_key_path).await?; - let allowed_clients = load_certs_from_files(allowed_clients).await?; - - // setup tls server - let mut config = rustls::ServerConfig::builder() - .with_client_cert_verifier(Arc::new( - tls_utils::OnlyAllowedClients::new(rustls::crypto::ring::default_provider(), allowed_clients), - )) - .with_single_cert(cert_chain, key_der)?; - config.alpn_protocols.clear(); - config.alpn_protocols.push(b"ntske/1".to_vec()); - - Ok(config) -} - -#[derive(Parser, Debug)] -#[clap(author, version, about, long_about = None)] -pub struct Args { - /// Configuration file to use - #[clap( - long = "config", - short = 'c', - default_value = "/etc/statime/statime.toml" - )] - config_file: Option, -} - - -pub async fn main() -> Result<(), Box> { - let args = Args::parse(); - let _config = initialize_logging_parse_config( - &args - .config_file - .expect("could not determine config file path"), - ); - - let ke_config = { - let listen_addr = "0.0.0.0:4460"; - let cert_chain_path = "statime-linux/testkeys/test.chain.pem"; - let private_key_path = "statime-linux/testkeys/test.key"; - let allowed_clients = vec![ - "statime-linux/testkeys/test.chain.pem".into(), - ]; - - let listen_addr = listen_addr - .to_socket_addrs()? - .next() - .ok_or_else(|| io::Error::from(io::ErrorKind::AddrNotAvailable))?; - - Arc::new(KeConfig { - validity_period: 3600, - update_period: 300, - grace_period: 5, - listen_addr, - cert_chain_path: cert_chain_path.into(), - private_key_path: private_key_path.into(), - allowed_clients, - }) - }; - - // setup the tls server - let config = prep_server_config( - &ke_config.cert_chain_path, - &ke_config.private_key_path, - ke_config.allowed_clients.iter(), - ).await?; - let acceptor = TlsAcceptor::from(Arc::new(config)); - let listener = TcpListener::bind(ke_config.listen_addr).await?; - - info!("Statime-KE bound on {:?}", ke_config.listen_addr); - - // create the keyset store and let it automatically update itself - let store = Arc::new(RwLock::new(KeySetStore::new())); - KeySetStore::spawn_rotate_process( - store.clone(), - Duration::from_secs(ke_config.validity_period as u64), - Duration::from_secs(ke_config.update_period as u64), - ); - - // handle new connections on the TCP socket and process them with the TLS - // acceptor - loop { - let (stream, peer_addr) = listener.accept().await?; - let acceptor = acceptor.clone(); - let store = store.clone(); - let ke_config = ke_config.clone(); - - debug!("Received connection from {}", peer_addr); - - // TODO: cancel the future after a timeout occurs - let fut = async move { - let stream = acceptor.accept(stream).await?; - handle_connection(stream, store, ke_config).await?; - - Ok(()) as Result<(), Box> - }; - - tokio::spawn(async move { - if let Err(err) = fut.await { - warn!("Error during connection processing: {:?}", err); - } - }); - } -} - -async fn handle_connection( - mut stream: TlsStream, - store: Arc>, - ke_config: Arc, -) -> Result<(), Box> { - debug!("Attempting to read NTS-KE records from connection"); - - // we expect the to receive messages to be smaller than data_buf - let mut data_buf = vec![0; 4096]; - let mut bytes_received = 0; - - let mut records = None; - while records.is_none() { - bytes_received += stream.read(&mut data_buf[bytes_received..]).await?; - let mut data = &data_buf[0..bytes_received]; - records = Record::read_until_eom(&mut data)?; - if bytes_received == data_buf.len() && records.is_none() { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - "NTS message too large to handle", - ) - .into()); - } - } - - // records must always be filled at this point - let Some(records) = records else { - unreachable!() - }; - - let keyset = store.read().await; - let resp = respond(records, &keyset, ke_config).await?; - resp.write(stream).await?; - Ok(()) -} - -async fn respond<'a>( - records: Vec>, - keyset: &'a KeySetStore, - ke_config: Arc, -) -> Result, Box> { - // TODO: probably send back an error message to the client instead of just - // erroring the connection - let request: PtpKeyRequestMessage = records.try_into()?; - - // TODO: we ignore the assocation mode entirely right now - - if !request - .next_protocol - .iter() - .any(|np| *np == NextProtocol::Ptpv2_1) - { - // TODO: send back error instead of just erroring the connection - return Err(io::Error::new( - io::ErrorKind::InvalidData, - "Received NTS request without PTP next protocol", - ) - .into()); - } - - let time_since = keyset.current.valid_since.elapsed().as_secs() as u32; - let lifetime = if time_since > ke_config.validity_period { - 0 - } else { - ke_config.validity_period - time_since - }; - - Ok(PtpKeyResponseMessage { - next_protocol: NextProtocols::ptpv2_1(), - current_parameters: ParameterSet { - security_assocation: SecurityAssocation::from_key_data( - keyset.current.id, - keyset.current.as_bytes(), - ), - validity_period: ValidityPeriod { - lifetime, - update_period: ke_config.update_period, - grace_period: ke_config.grace_period, - }, - }, - next_parameters: keyset.next.as_ref().map(|next| ParameterSet { - security_assocation: SecurityAssocation::from_key_data(next.id, next.as_bytes()), - validity_period: ValidityPeriod { - lifetime: ke_config.validity_period, - update_period: ke_config.update_period, - grace_period: ke_config.grace_period, - }, - }), - }) -} +pub use server::main; diff --git a/statime-linux/src/ke/server.rs b/statime-linux/src/ke/server.rs new file mode 100644 index 000000000..319343465 --- /dev/null +++ b/statime-linux/src/ke/server.rs @@ -0,0 +1,293 @@ +use std::{ + io, + net::{SocketAddr, ToSocketAddrs}, + path::{Path, PathBuf}, + sync::Arc, + time::Duration, +}; + +use clap::Parser; +use log::{debug, info, warn}; +use rustls::ServerConfig; +use tokio::{ + io::AsyncReadExt, + net::{TcpListener, TcpStream}, + sync::RwLock, + time::Instant, +}; +use tokio_rustls::{server::TlsStream, TlsAcceptor}; + +use crate::initialize_logging_parse_config; + +use super::{ + common::{load_certs, load_certs_from_files, load_private_key, Key}, + record::*, + tls_utils::OnlyAllowedClients, +}; + +struct KeySetStore { + current: Key, + next: Option, +} + +impl KeySetStore { + /// This function generates the next key if there is no next key, otherwise + /// it moves the next key to the current key. + fn rotate(&mut self) { + let next_key = self.next.take(); + + if let Some(next_key) = next_key { + self.current = next_key; + // when switched to the current key the valid since time resets + self.current.valid_since = Instant::now(); + } else { + self.next + .replace(Key::generate(self.current.id + 1, rand::thread_rng())); + } + } + + /// Generate a new keyset that stores the current key. + fn new() -> KeySetStore { + KeySetStore { + current: Key::generate(0, rand::thread_rng()), + next: None, + } + } + + fn spawn_rotate_process( + store: Arc>, + lifetime: Duration, + update_period: Duration, + ) -> tokio::task::JoinHandle<()> { + tokio::spawn(async move { + loop { + // if update period is as great as lifetime we immediately rotate to have the + // next key available + if update_period < lifetime { + tokio::time::sleep(lifetime - update_period).await; + } + + // first rotate generates the next key + store.write().await.rotate(); + + // wait for the update period, but never more than the lifetime + tokio::time::sleep(update_period.min(lifetime)).await; + + // second rotate moves the next key to current (deleting the previous current + // key) + store.write().await.rotate(); + } + }) + } +} + +struct KeConfig { + validity_period: u32, + update_period: u32, + grace_period: u32, + listen_addr: SocketAddr, + cert_chain_path: PathBuf, + private_key_path: PathBuf, + allowed_clients: Vec, +} + +async fn prep_server_config( + cert_chain_path: impl AsRef, + private_key_path: impl AsRef, + allowed_clients: impl Iterator>, +) -> Result> { + let cert_chain = load_certs(cert_chain_path).await?; + let key_der = load_private_key(private_key_path).await?; + let allowed_clients = load_certs_from_files(allowed_clients).await?; + + // setup tls server + let mut config = rustls::ServerConfig::builder() + .with_client_cert_verifier(Arc::new(OnlyAllowedClients::new( + rustls::crypto::ring::default_provider(), + allowed_clients, + ))) + .with_single_cert(cert_chain, key_der)?; + config.alpn_protocols.clear(); + config.alpn_protocols.push(b"ntske/1".to_vec()); + + Ok(config) +} + +#[derive(Parser, Debug)] +#[clap(author, version, about, long_about = None)] +pub struct Args { + /// Configuration file to use + #[clap( + long = "config", + short = 'c', + default_value = "/etc/statime/statime.toml" + )] + config_file: Option, +} + +pub async fn main() -> Result<(), Box> { + let args = Args::parse(); + let _config = initialize_logging_parse_config( + &args + .config_file + .expect("could not determine config file path"), + ); + + let ke_config = { + let listen_addr = "0.0.0.0:4460"; + let cert_chain_path = "statime-linux/testkeys/test.chain.pem"; + let private_key_path = "statime-linux/testkeys/test.key"; + let allowed_clients = vec!["statime-linux/testkeys/test.chain.pem".into()]; + + let listen_addr = listen_addr + .to_socket_addrs()? + .next() + .ok_or_else(|| io::Error::from(io::ErrorKind::AddrNotAvailable))?; + + Arc::new(KeConfig { + validity_period: 3600, + update_period: 300, + grace_period: 5, + listen_addr, + cert_chain_path: cert_chain_path.into(), + private_key_path: private_key_path.into(), + allowed_clients, + }) + }; + + // setup the tls server + let config = prep_server_config( + &ke_config.cert_chain_path, + &ke_config.private_key_path, + ke_config.allowed_clients.iter(), + ) + .await?; + let acceptor = TlsAcceptor::from(Arc::new(config)); + let listener = TcpListener::bind(ke_config.listen_addr).await?; + + info!("Statime-KE bound on {:?}", ke_config.listen_addr); + + // create the keyset store and let it automatically update itself + let store = Arc::new(RwLock::new(KeySetStore::new())); + KeySetStore::spawn_rotate_process( + store.clone(), + Duration::from_secs(ke_config.validity_period as u64), + Duration::from_secs(ke_config.update_period as u64), + ); + + // handle new connections on the TCP socket and process them with the TLS + // acceptor + loop { + let (stream, peer_addr) = listener.accept().await?; + let acceptor = acceptor.clone(); + let store = store.clone(); + let ke_config = ke_config.clone(); + + debug!("Received connection from {}", peer_addr); + + // TODO: cancel the future after a timeout occurs + let fut = async move { + let stream = acceptor.accept(stream).await?; + handle_connection(stream, store, ke_config).await?; + + Ok(()) as Result<(), Box> + }; + + tokio::spawn(async move { + if let Err(err) = fut.await { + warn!("Error during connection processing: {:?}", err); + } + }); + } +} + +async fn handle_connection( + mut stream: TlsStream, + store: Arc>, + ke_config: Arc, +) -> Result<(), Box> { + debug!("Attempting to read NTS-KE records from connection"); + + // we expect the to receive messages to be smaller than data_buf + let mut data_buf = vec![0; 4096]; + let mut bytes_received = 0; + + let mut records = None; + while records.is_none() { + bytes_received += stream.read(&mut data_buf[bytes_received..]).await?; + let mut data = &data_buf[0..bytes_received]; + records = Record::read_until_eom(&mut data)?; + if bytes_received == data_buf.len() && records.is_none() { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "NTS message too large to handle", + ) + .into()); + } + } + + // records must always be filled at this point + let Some(records) = records else { + unreachable!() + }; + + let keyset = store.read().await; + let resp = respond(records, &keyset, ke_config).await?; + resp.write(stream).await?; + Ok(()) +} + +async fn respond<'a>( + records: Vec>, + keyset: &'a KeySetStore, + ke_config: Arc, +) -> Result, Box> { + // TODO: probably send back an error message to the client instead of just + // erroring the connection + let request: PtpKeyRequestMessage = records.try_into()?; + + // TODO: we ignore the assocation mode entirely right now + + if !request + .next_protocol + .iter() + .any(|np| *np == NextProtocol::Ptpv2_1) + { + // TODO: send back error instead of just erroring the connection + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "Received NTS request without PTP next protocol", + ) + .into()); + } + + let time_since = keyset.current.valid_since.elapsed().as_secs() as u32; + let lifetime = if time_since > ke_config.validity_period { + 0 + } else { + ke_config.validity_period - time_since + }; + + Ok(PtpKeyResponseMessage { + next_protocol: NextProtocols::ptpv2_1(), + current_parameters: ParameterSet { + security_assocation: SecurityAssocation::from_key_data( + keyset.current.id, + keyset.current.as_bytes(), + ), + validity_period: ValidityPeriod { + lifetime, + update_period: ke_config.update_period, + grace_period: ke_config.grace_period, + }, + }, + next_parameters: keyset.next.as_ref().map(|next| ParameterSet { + security_assocation: SecurityAssocation::from_key_data(next.id, next.as_bytes()), + validity_period: ValidityPeriod { + lifetime: ke_config.validity_period, + update_period: ke_config.update_period, + grace_period: ke_config.grace_period, + }, + }), + }) +} diff --git a/statime-linux/src/ke/tls_utils.rs b/statime-linux/src/ke/tls_utils.rs index 82da2c356..10fd5c207 100644 --- a/statime-linux/src/ke/tls_utils.rs +++ b/statime-linux/src/ke/tls_utils.rs @@ -1,4 +1,7 @@ -use rustls::{crypto::{CryptoProvider, WebPkiSupportedAlgorithms}, server::danger::ClientCertVerified}; +use rustls::{ + crypto::{CryptoProvider, WebPkiSupportedAlgorithms}, + server::danger::ClientCertVerified, +}; #[derive(Debug)] pub struct OnlyAllowedClients { @@ -7,7 +10,10 @@ pub struct OnlyAllowedClients { } impl OnlyAllowedClients { - pub fn new(provider: CryptoProvider, allowed_clients: Vec>) -> Self { + pub fn new( + provider: CryptoProvider, + allowed_clients: Vec>, + ) -> Self { OnlyAllowedClients { supported_algs: provider.signature_verification_algorithms, allowed_clients, @@ -17,16 +23,18 @@ impl OnlyAllowedClients { impl rustls::server::danger::ClientCertVerifier for OnlyAllowedClients { fn verify_client_cert( - &self, - end_entity: &rustls::pki_types::CertificateDer<'_>, - _intermediates: &[rustls::pki_types::CertificateDer<'_>], - _now: rustls::pki_types::UnixTime, - ) -> Result { - if self.allowed_clients.iter().any(|c| c == end_entity) { - Ok(ClientCertVerified::assertion()) - } else { - Err(rustls::Error::InvalidCertificate(rustls::CertificateError::ApplicationVerificationFailure)) - } + &self, + end_entity: &rustls::pki_types::CertificateDer<'_>, + _intermediates: &[rustls::pki_types::CertificateDer<'_>], + _now: rustls::pki_types::UnixTime, + ) -> Result { + if self.allowed_clients.iter().any(|c| c == end_entity) { + Ok(ClientCertVerified::assertion()) + } else { + Err(rustls::Error::InvalidCertificate( + rustls::CertificateError::ApplicationVerificationFailure, + )) + } } fn client_auth_mandatory(&self) -> bool { @@ -38,20 +46,20 @@ impl rustls::server::danger::ClientCertVerifier for OnlyAllowedClients { } fn verify_tls12_signature( - &self, - message: &[u8], - cert: &rustls::pki_types::CertificateDer<'_>, - dss: &rustls::DigitallySignedStruct, - ) -> Result { + &self, + message: &[u8], + cert: &rustls::pki_types::CertificateDer<'_>, + dss: &rustls::DigitallySignedStruct, + ) -> Result { rustls::crypto::verify_tls12_signature(message, cert, dss, &self.supported_algs) } fn verify_tls13_signature( - &self, - message: &[u8], - cert: &rustls::pki_types::CertificateDer<'_>, - dss: &rustls::DigitallySignedStruct, - ) -> Result { + &self, + message: &[u8], + cert: &rustls::pki_types::CertificateDer<'_>, + dss: &rustls::DigitallySignedStruct, + ) -> Result { rustls::crypto::verify_tls13_signature(message, cert, dss, &self.supported_algs) } diff --git a/statime/src/port/bmca.rs b/statime/src/port/bmca.rs index bfe323def..01f1029fb 100644 --- a/statime/src/port/bmca.rs +++ b/statime/src/port/bmca.rs @@ -113,7 +113,9 @@ impl<'a, A: AcceptableMasterList, C: Clock, F: Filter, R: Rng, P, S: PtpInstance } } -impl<'a, A, C: Clock, F: Filter, R: Rng, P, S: PtpInstanceStateMutex> Port<'a, InBmca, A, R, C, F, P, S> { +impl<'a, A, C: Clock, F: Filter, R: Rng, P, S: PtpInstanceStateMutex> + Port<'a, InBmca, A, R, C, F, P, S> +{ pub(crate) fn step_announce_age(&mut self, step: Duration) { if let Some(mut age) = self.multiport_disable.take() { age += step; diff --git a/statime/src/port/mod.rs b/statime/src/port/mod.rs index 4b77148b5..7fa5dfabd 100644 --- a/statime/src/port/mod.rs +++ b/statime/src/port/mod.rs @@ -333,8 +333,15 @@ pub struct InBmca { local_best: Option, } -impl<'a, A: AcceptableMasterList, C: Clock, F: Filter, R: Rng, P: SecurityAssociationProvider, S: PtpInstanceStateMutex> - Port<'a, Running, A, R, C, F, P, S> +impl< + 'a, + A: AcceptableMasterList, + C: Clock, + F: Filter, + R: Rng, + P: SecurityAssociationProvider, + S: PtpInstanceStateMutex, + > Port<'a, Running, A, R, C, F, P, S> { /// Inform the port about a transmit timestamp being available /// @@ -663,7 +670,12 @@ mod tests { use super::*; use crate::{ - config::{AcceptAnyMaster, DelayMechanism, InstanceConfig, TimePropertiesDS}, crypto::NoSecurityProvider, datastructures::datasets::{InternalDefaultDS, InternalParentDS, PathTraceDS}, filters::BasicFilter, time::{Duration, Interval, Time}, Clock + config::{AcceptAnyMaster, DelayMechanism, InstanceConfig, TimePropertiesDS}, + crypto::NoSecurityProvider, + datastructures::datasets::{InternalDefaultDS, InternalParentDS, PathTraceDS}, + filters::BasicFilter, + time::{Duration, Interval, Time}, + Clock, }; // General test infra @@ -694,7 +706,15 @@ mod tests { pub(super) fn setup_test_port( state: &RefCell, - ) -> Port<'_, Running, AcceptAnyMaster, rand::rngs::mock::StepRng, TestClock, BasicFilter, NoSecurityProvider> { + ) -> Port< + '_, + Running, + AcceptAnyMaster, + rand::rngs::mock::StepRng, + TestClock, + BasicFilter, + NoSecurityProvider, + > { let port = Port::<_, _, _, _, BasicFilter, NoSecurityProvider>::new( state, PortConfig { @@ -720,11 +740,18 @@ mod tests { port } - pub(super) fn setup_test_port_custom_identity( state: &RefCell, port_identity: PortIdentity, - ) -> Port<'_, Running, AcceptAnyMaster, rand::rngs::mock::StepRng, TestClock, BasicFilter, NoSecurityProvider> { + ) -> Port< + '_, + Running, + AcceptAnyMaster, + rand::rngs::mock::StepRng, + TestClock, + BasicFilter, + NoSecurityProvider, + > { let port = Port::<_, _, _, _, BasicFilter, NoSecurityProvider>::new( &state, PortConfig { @@ -753,7 +780,15 @@ mod tests { pub(super) fn setup_test_port_custom_filter( state: &RefCell, filter_config: F::Config, - ) -> Port<'_, Running, AcceptAnyMaster, rand::rngs::mock::StepRng, TestClock, F, NoSecurityProvider> { + ) -> Port< + '_, + Running, + AcceptAnyMaster, + rand::rngs::mock::StepRng, + TestClock, + F, + NoSecurityProvider, + > { let port = Port::<_, _, _, _, F, _>::new( state, PortConfig { From 5a1097804b3657ed5c695ad60b7929d43359aee5 Mon Sep 17 00:00:00 2001 From: David Venhoek Date: Thu, 7 Mar 2024 16:36:32 +0100 Subject: [PATCH 08/23] Added client implementation. --- statime-linux/src/config/mod.rs | 11 ++ statime-linux/src/ke/client.rs | 52 +++++++ statime-linux/src/ke/mod.rs | 4 +- statime-linux/src/ke/record.rs | 8 +- statime-linux/src/securityprovider.rs | 205 +++++++++++++++++++++----- 5 files changed, 240 insertions(+), 40 deletions(-) diff --git a/statime-linux/src/config/mod.rs b/statime-linux/src/config/mod.rs index 72c44ff1c..519035533 100644 --- a/statime-linux/src/config/mod.rs +++ b/statime-linux/src/config/mod.rs @@ -38,6 +38,13 @@ pub struct Config { pub observability: ObservabilityConfig, #[serde(default)] pub virtual_system_clock: bool, + pub nts4ptp_server: Option, + #[serde(default)] + pub nts4ptp_client_cert: Option, + #[serde(default)] + pub nts4ptp_client_cert_key: Option, + #[serde(default)] + pub nts4ptp_server_root: Option, } #[derive(Deserialize, Debug, Clone, PartialEq, Eq)] @@ -289,6 +296,10 @@ interface = "enp0s31f6" ports: vec![expected_port], observability: ObservabilityConfig::default(), virtual_system_clock: false, + nts4ptp_client_cert: None, + nts4ptp_client_cert_key: None, + nts4ptp_server: None, + nts4ptp_server_root: None, }; let actual = toml::from_str(MINIMAL_CONFIG).unwrap(); diff --git a/statime-linux/src/ke/client.rs b/statime-linux/src/ke/client.rs index 8b1378917..53f34c18d 100644 --- a/statime-linux/src/ke/client.rs +++ b/statime-linux/src/ke/client.rs @@ -1 +1,53 @@ +use std::{error::Error, io, sync::Arc}; +use rustls::{pki_types::ServerName, ClientConfig}; +use tokio::{io::AsyncReadExt, net::TcpStream}; +use tokio_rustls::TlsConnector; + +use super::record::{AssociationMode, NextProtocols, PtpKeyRequestMessage, PtpKeyResponseMessage}; +use crate::ke::record::Record; + +pub async fn fetch_data( + server_address: &str, + config: Arc, +) -> Result, Box> { + let request = PtpKeyRequestMessage { + next_protocol: NextProtocols::ptpv2_1(), + association_mode: AssociationMode::Group { + ptp_domain_number: 0, + sdo_id: 0.try_into().unwrap(), + subgroup: 0, + }, + }; + + let connector = TlsConnector::from(config); + let dnsname = ServerName::try_from(server_address.to_owned())?; + + let stream = TcpStream::connect(server_address).await?; + let mut stream = connector.connect(dnsname, stream).await?; + + request.write(&mut stream).await?; + + // we expect the to receive messages to be smaller than data_buf + let mut data_buf = vec![0; 4096]; + let mut bytes_received = 0; + + let records = loop { + bytes_received += stream.read(&mut data_buf[bytes_received..]).await?; + let mut data = &data_buf[0..bytes_received]; + let records = Record::read_until_eom(&mut data)?; + if let Some(records) = records { + break records; + } else if bytes_received == data_buf.len() { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "NTS message too large to handle", + ) + .into()); + } + }; + + let records: Vec<_> = records.into_iter().map(|r| r.into_owned()).collect(); + + Ok(records.try_into()?) +} diff --git a/statime-linux/src/ke/mod.rs b/statime-linux/src/ke/mod.rs index 0b148b520..e1b9d5f9f 100644 --- a/statime-linux/src/ke/mod.rs +++ b/statime-linux/src/ke/mod.rs @@ -1,5 +1,5 @@ -mod client; -mod common; +pub mod client; +pub mod common; mod record; mod server; mod tls_utils; diff --git a/statime-linux/src/ke/record.rs b/statime-linux/src/ke/record.rs index 59075f63c..032a906d1 100644 --- a/statime-linux/src/ke/record.rs +++ b/statime-linux/src/ke/record.rs @@ -461,10 +461,10 @@ impl AssociationMode { #[derive(Debug, Clone, PartialEq, Eq)] pub struct SecurityAssocation<'a> { - spp: u8, - iat: u16, - key_id: u32, - key: Cow<'a, [u8]>, + pub spp: u8, + pub iat: u16, + pub key_id: u32, + pub key: Cow<'a, [u8]>, } impl<'a> SecurityAssocation<'a> { diff --git a/statime-linux/src/securityprovider.rs b/statime-linux/src/securityprovider.rs index 4b94bacb1..64a53a00a 100644 --- a/statime-linux/src/securityprovider.rs +++ b/statime-linux/src/securityprovider.rs @@ -1,14 +1,116 @@ -use std::{collections::HashMap, sync::Arc}; +use std::{collections::HashMap, error::Error, path::PathBuf, sync::Arc}; use rand::Rng; +use rustls::{ClientConfig, RootCertStore}; use statime::crypto::{ - SecurityAssociation, SecurityAssociationProvider, SecurityPolicy, SenderIdentificaton, + HmacSha256_128, SecurityAssociation, SecurityAssociationProvider, SecurityPolicy, + SenderIdentificaton, +}; + +use crate::ke::{ + client::fetch_data, + common::{load_certs, load_private_key}, }; pub struct NTSProvider { associations: Arc>>>, } +impl NTSProvider { + async fn new( + server_name: String, + client_key: PathBuf, + client_cert: PathBuf, + server_root: PathBuf, + ) -> Result<(Self, u8), Box> { + let client_key = load_private_key(client_key).await?; + let client_chain = load_certs(client_cert).await?; + let mut root_store = RootCertStore::empty(); + for cert in load_certs(server_root).await?.into_iter() { + root_store.add(cert)?; + } + + let config = Arc::new( + ClientConfig::builder() + .with_root_certificates(root_store) + .with_client_auth_cert(client_chain, client_key)?, + ); + + let initial_data = fetch_data(&server_name, config.clone()).await?; + let now = std::time::Instant::now(); + + let mut keys = HashMap::new(); + keys.insert( + initial_data.current_parameters.security_assocation.key_id, + NTSKey { + key: statime::crypto::HmacSha256_128::new( + initial_data + .current_parameters + .security_assocation + .key + .as_ref() + .try_into()?, + ), + valid_till: now + + std::time::Duration::from_secs( + initial_data.current_parameters.validity_period.lifetime as _, + ), + }, + ); + let next_key = if let Some(next_parameters) = initial_data.next_parameters { + keys.insert( + next_parameters.security_assocation.key_id, + NTSKey { + key: statime::crypto::HmacSha256_128::new( + next_parameters + .security_assocation + .key + .as_ref() + .try_into()?, + ), + valid_till: now + + std::time::Duration::from_secs( + next_parameters.validity_period.lifetime as _, + ), + }, + ); + Some(next_parameters.security_assocation.key_id) + } else { + None + }; + + let association = Arc::new(std::sync::Mutex::new(NTSAssociationInner { + grace_period: std::time::Duration::from_secs( + initial_data.current_parameters.validity_period.grace_period as _, + ), + transition_period: std::time::Duration::from_secs( + initial_data + .current_parameters + .validity_period + .update_period as _, + ), + keys, + current_key: initial_data.current_parameters.security_assocation.key_id, + next_key, + sequence_ids: Default::default(), + })); + + let spp = initial_data.current_parameters.security_assocation.spp; + + tokio::spawn(assocation_manager(association.clone(), server_name, config)); + + let mut associations = HashMap::new(); + associations.insert(spp, association); + + Ok(( + NTSProvider { + associations: Arc::new(associations), + }, + spp, + )) + } +} + struct NTSAssociationInner { grace_period: std::time::Duration, transition_period: std::time::Duration, @@ -100,39 +202,74 @@ impl NTSAssociationInner { } #[allow(unused)] -// await_holding_lock is somewhat buggy (see https://github.com/rust-lang/rust-clippy/issues/9683) -#[allow(clippy::await_holding_lock)] -async fn assocation_manager(association: Arc>) { - let mut this = association.lock().unwrap(); - +async fn assocation_manager( + association: Arc>, + server_name: String, + config: Arc, +) { loop { - // wait for new parameters - if this.next_key.is_none() { - let transition_start = this.keys[&this.current_key].valid_till - this.transition_period; - let random_offset = this - .transition_period - .mul_f32(rand::thread_rng().gen_range(0.0..0.75)); - drop(this); - tokio::time::sleep_until((transition_start + random_offset).into()).await; - this = association.lock().unwrap(); - // TODO: Fetch new parameters - } + let deadline = { + let mut this = association.lock().unwrap(); + this.clean_keys(); + this.clean_sequence_id(); + + // wait for new parameters + if this.next_key.is_none() { + let transition_start = + this.keys[&this.current_key].valid_till - this.transition_period; + let random_offset = this + .transition_period + .mul_f32(rand::thread_rng().gen_range(0.0..0.75)); + Some(transition_start + random_offset) + } else { + None + } + }; + let await_data = if let Some(deadline) = deadline { + tokio::time::sleep_until(deadline.into()).await; + let params = fetch_data(&server_name, config.clone()).await.unwrap(); + let now = std::time::Instant::now(); + Some((params, now)) + } else { + None + }; + + let deadline = { + let mut this = association.lock().unwrap(); + if let Some((params, now)) = await_data { + let next_params = params.next_parameters.unwrap(); + this.keys.insert( + next_params.security_assocation.key_id, + NTSKey { + key: HmacSha256_128::new( + next_params + .security_assocation + .key + .as_ref() + .try_into() + .unwrap(), + ), + valid_till: now + + std::time::Duration::from_secs( + next_params.validity_period.lifetime as _, + ), + }, + ); + this.next_key = Some(next_params.security_assocation.key_id); + } + + this.keys[&this.current_key].valid_till + }; + + tokio::time::sleep_until(deadline.into()).await; + let deadline = { + let mut this = association.lock().unwrap(); + let old_key = this.current_key; + this.current_key = this.next_key.take().unwrap(); - // switchover - let switchover_time = this.keys[&this.current_key].valid_till; - - drop(this); - tokio::time::sleep_until(switchover_time.into()).await; - this = association.lock().unwrap(); - let old_key = this.current_key; - this.current_key = this.next_key.take().unwrap(); - - // wait out grace period - let grace_end = this.keys[&old_key].valid_till + this.grace_period; - drop(this); - tokio::time::sleep_until(grace_end.into()).await; - this = association.lock().unwrap(); - this.clean_keys(); - this.clean_sequence_id(); + // wait out grace period + this.keys[&old_key].valid_till + this.grace_period + }; + tokio::time::sleep_until(deadline.into()).await; } } From 7475ae79b87afc44a2cfe391e5dbeb1207df044c Mon Sep 17 00:00:00 2001 From: David Venhoek Date: Thu, 7 Mar 2024 16:50:05 +0100 Subject: [PATCH 09/23] Use nts security provider in statime-linux. --- statime-linux/src/main.rs | 28 +++++++++++++++++++++++---- statime-linux/src/securityprovider.rs | 9 ++++++++- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/statime-linux/src/main.rs b/statime-linux/src/main.rs index 631f732b0..0d753750a 100644 --- a/statime-linux/src/main.rs +++ b/statime-linux/src/main.rs @@ -10,7 +10,6 @@ use clap::Parser; use rand::{rngs::StdRng, SeedableRng}; use statime::{ config::{ClockIdentity, InstanceConfig, SdoId, TimePropertiesDS, TimeSource}, - crypto::NoSecurityProvider, filters::{Filter, KalmanConfiguration, KalmanFilter}, port::{ is_message_buffer_compatible, InBmca, Measurement, Port, PortAction, PortActionIterator, @@ -21,8 +20,10 @@ use statime::{ }; use statime_linux::{ clock::{LinuxClock, PortTimestampToTime}, + config::Config, initialize_logging_parse_config, observer::ObservableInstanceState, + securityprovider::NTSProvider, socket::{ open_ethernet_socket, open_ipv4_event_socket, open_ipv4_general_socket, open_ipv6_event_socket, open_ipv6_general_socket, PtpTargetAddress, @@ -315,6 +316,22 @@ async fn actual_main() { let tlv_forwarder = TlvForwarder::new(); + let (provider, spp) = if let Config { + nts4ptp_server: Some(server_name), + nts4ptp_client_cert_key: Some(client_key), + nts4ptp_client_cert: Some(client_cert), + nts4ptp_server_root: Some(server_root), + .. + } = config + { + let (provider, spp) = NTSProvider::new(server_name, client_key, client_cert, server_root) + .await + .unwrap(); + (provider, Some(spp)) + } else { + (NTSProvider::empty(), None) + }; + for port_config in config.ports { let interface = port_config.interface; let network_mode = port_config.network_mode; @@ -348,11 +365,14 @@ async fn actual_main() { let rng = StdRng::from_entropy(); let bind_phc = port_config.hardware_clock; let port = instance.add_port( - port_config.into(), + statime::config::PortConfig { + spp, + ..port_config.into() + }, KalmanConfiguration::default(), port_clock.clone_box(), rng, - NoSecurityProvider, + provider.clone(), ); let (main_task_sender, port_task_receiver) = tokio::sync::mpsc::channel(1); @@ -520,7 +540,7 @@ type BmcaPort = Port< StdRng, BoxedClock, KalmanFilter, - NoSecurityProvider, + NTSProvider, RwLock, >; diff --git a/statime-linux/src/securityprovider.rs b/statime-linux/src/securityprovider.rs index 64a53a00a..c251d0d32 100644 --- a/statime-linux/src/securityprovider.rs +++ b/statime-linux/src/securityprovider.rs @@ -12,12 +12,19 @@ use crate::ke::{ common::{load_certs, load_private_key}, }; +#[derive(Clone)] pub struct NTSProvider { associations: Arc>>>, } impl NTSProvider { - async fn new( + pub fn empty() -> Self { + NTSProvider { + associations: Arc::new(HashMap::new()), + } + } + + pub async fn new( server_name: String, client_key: PathBuf, client_cert: PathBuf, From c968e8e49210beda4269d0e5b384fe387876fc71 Mon Sep 17 00:00:00 2001 From: David Venhoek Date: Fri, 8 Mar 2024 10:19:32 +0100 Subject: [PATCH 10/23] Split port from dnsname. --- statime-linux/src/ke/client.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/statime-linux/src/ke/client.rs b/statime-linux/src/ke/client.rs index 53f34c18d..b9c4ec349 100644 --- a/statime-linux/src/ke/client.rs +++ b/statime-linux/src/ke/client.rs @@ -21,7 +21,7 @@ pub async fn fetch_data( }; let connector = TlsConnector::from(config); - let dnsname = ServerName::try_from(server_address.to_owned())?; + let dnsname = ServerName::try_from(server_address.split_once(':').unwrap().0.to_owned())?; let stream = TcpStream::connect(server_address).await?; let mut stream = connector.connect(dnsname, stream).await?; From c6ddf9d1772e8f33afafc4f73ddde50bc534559c Mon Sep 17 00:00:00 2001 From: David Venhoek Date: Fri, 8 Mar 2024 10:40:58 +0100 Subject: [PATCH 11/23] Fix incorrect handling of spp in port. --- statime/src/port/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/statime/src/port/mod.rs b/statime/src/port/mod.rs index 7fa5dfabd..0ad9b2e2e 100644 --- a/statime/src/port/mod.rs +++ b/statime/src/port/mod.rs @@ -637,7 +637,7 @@ impl<'a, A, C, F: Filter, R: Rng, P, S: PtpInstanceStateMutex> Port<'a, InBmca, sync_interval: config.sync_interval, master_only: config.master_only, delay_asymmetry: config.delay_asymmetry, - spp: None, + spp: config.spp, }, filter_config, clock, From 13162fdcf9d0c75a860fa7d40c618c0e8fcdd803 Mon Sep 17 00:00:00 2001 From: David Venhoek Date: Fri, 8 Mar 2024 10:44:45 +0100 Subject: [PATCH 12/23] Fix minor bug in sequence id registration. --- statime-linux/src/securityprovider.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/statime-linux/src/securityprovider.rs b/statime-linux/src/securityprovider.rs index c251d0d32..393c13733 100644 --- a/statime-linux/src/securityprovider.rs +++ b/statime-linux/src/securityprovider.rs @@ -169,7 +169,7 @@ impl<'a> SecurityAssociation for NTSAssociation<'a> { ) -> bool { match self.0.sequence_ids.entry((sender, key_id)) { std::collections::hash_map::Entry::Occupied(mut entry) => { - if (*entry.get() - sequence_id) as i16 > 0 { + if (entry.get().wrapping_sub(sequence_id) as i16) < 0 { *entry.get_mut() = sequence_id; true } else { From f6e88b7d0ba29c1b32dc17440b25b900bff2f2f7 Mon Sep 17 00:00:00 2001 From: David Venhoek Date: Fri, 8 Mar 2024 10:53:41 +0100 Subject: [PATCH 13/23] Added additional logging to verification. --- statime/src/datastructures/messages/mod.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/statime/src/datastructures/messages/mod.rs b/statime/src/datastructures/messages/mod.rs index c3bd10dac..9bad9fb4e 100644 --- a/statime/src/datastructures/messages/mod.rs +++ b/statime/src/datastructures/messages/mod.rs @@ -308,11 +308,13 @@ impl<'a> Message<'a> { impl Message<'_> { #[allow(unused)] pub(crate) fn verify_signed(&self, provider: &impl SecurityAssociationProvider) -> bool { + log::trace!("Validation message"); let mut tlv_offset = 0; for tlv in self.suffix.tlv() { if tlv.tlv_type == TlvType::Authentication { // Check we have at least the SPP, params and key id if tlv.value.len() < 6 { + log::trace!("Rejected: Incorrect authentication tlv length"); return false; } @@ -323,19 +325,23 @@ impl Message<'_> { // we dont support presence of any of the optional bits, so not valid if those // are present if params != 0 { + log::trace!("Rejected: Unexpected optional bits"); return false; } // get the security association and key let Some(mut association) = provider.lookup(spp) else { + log::trace!("Rejected: Invalid spp"); return false; }; let Some(key) = association.mac(key_id) else { + log::trace!("Rejected: Invalid key id"); return false; }; // Ensure we have a complete ICV if tlv.value.len() < 6 + key.output_size() { + log::trace!("Rejected: TLV too short"); return false; } @@ -344,6 +350,7 @@ impl Message<'_> { // before production ready let mut buffer = [0; MAX_DATA_LEN]; if self.serialize(&mut buffer).is_err() { + log::trace!("Rejected: cannot reserialize"); return false; } if association.policy_data().ignore_correction { @@ -355,6 +362,7 @@ impl Message<'_> { &buffer[..self.header.wire_size() + self.body.wire_size() + tlv_offset + 10], &tlv.value[6..6 + key.output_size()], ) { + log::trace!("Rejected: signature invalid"); return false; } @@ -368,6 +376,7 @@ impl Message<'_> { self.header.sequence_id, ); + log::trace!("Accepted"); return true; } @@ -375,6 +384,7 @@ impl Message<'_> { } // No authentication tlv found, so not signed + log::trace!("Rejected: no TLV present"); false } From 102ad2241090df206c811ece19bc6294a805b6b4 Mon Sep 17 00:00:00 2001 From: David Venhoek Date: Fri, 8 Mar 2024 12:01:31 +0100 Subject: [PATCH 14/23] Fixed bugs in announce message sending. --- statime/src/datastructures/messages/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/statime/src/datastructures/messages/mod.rs b/statime/src/datastructures/messages/mod.rs index 9bad9fb4e..4e7a45567 100644 --- a/statime/src/datastructures/messages/mod.rs +++ b/statime/src/datastructures/messages/mod.rs @@ -306,7 +306,6 @@ impl<'a> Message<'a> { } impl Message<'_> { - #[allow(unused)] pub(crate) fn verify_signed(&self, provider: &impl SecurityAssociationProvider) -> bool { log::trace!("Validation message"); let mut tlv_offset = 0; From 7a67343affaec8620ca2428a9e0de98168954948 Mon Sep 17 00:00:00 2001 From: David Venhoek Date: Fri, 8 Mar 2024 12:27:59 +0100 Subject: [PATCH 15/23] Fix incorrect shutdown of tls streams. --- statime-linux/src/ke/client.rs | 8 +++++++- statime-linux/src/ke/server.rs | 5 +++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/statime-linux/src/ke/client.rs b/statime-linux/src/ke/client.rs index b9c4ec349..6ef870ea7 100644 --- a/statime-linux/src/ke/client.rs +++ b/statime-linux/src/ke/client.rs @@ -1,7 +1,10 @@ use std::{error::Error, io, sync::Arc}; use rustls::{pki_types::ServerName, ClientConfig}; -use tokio::{io::AsyncReadExt, net::TcpStream}; +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt}, + net::TcpStream, +}; use tokio_rustls::TlsConnector; use super::record::{AssociationMode, NextProtocols, PtpKeyRequestMessage, PtpKeyResponseMessage}; @@ -27,6 +30,7 @@ pub async fn fetch_data( let mut stream = connector.connect(dnsname, stream).await?; request.write(&mut stream).await?; + stream.flush().await?; // we expect the to receive messages to be smaller than data_buf let mut data_buf = vec![0; 4096]; @@ -47,6 +51,8 @@ pub async fn fetch_data( } }; + stream.shutdown().await?; + let records: Vec<_> = records.into_iter().map(|r| r.into_owned()).collect(); Ok(records.try_into()?) diff --git a/statime-linux/src/ke/server.rs b/statime-linux/src/ke/server.rs index 319343465..5b344ec18 100644 --- a/statime-linux/src/ke/server.rs +++ b/statime-linux/src/ke/server.rs @@ -10,7 +10,7 @@ use clap::Parser; use log::{debug, info, warn}; use rustls::ServerConfig; use tokio::{ - io::AsyncReadExt, + io::{AsyncReadExt, AsyncWriteExt}, net::{TcpListener, TcpStream}, sync::RwLock, time::Instant, @@ -233,7 +233,8 @@ async fn handle_connection( let keyset = store.read().await; let resp = respond(records, &keyset, ke_config).await?; - resp.write(stream).await?; + resp.write(&mut stream).await?; + stream.shutdown().await?; Ok(()) } From 4563b1a0047f0e3c9a6e746b451a593e668ce226 Mon Sep 17 00:00:00 2001 From: David Venhoek Date: Fri, 8 Mar 2024 16:34:49 +0100 Subject: [PATCH 16/23] Fixed bug in record parsing causing an infinite loop. --- statime-linux/src/ke/client.rs | 6 +++++- statime-linux/src/ke/record.rs | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/statime-linux/src/ke/client.rs b/statime-linux/src/ke/client.rs index 6ef870ea7..ca58673f5 100644 --- a/statime-linux/src/ke/client.rs +++ b/statime-linux/src/ke/client.rs @@ -32,6 +32,8 @@ pub async fn fetch_data( request.write(&mut stream).await?; stream.flush().await?; + log::trace!("Wrote message"); + // we expect the to receive messages to be smaller than data_buf let mut data_buf = vec![0; 4096]; let mut bytes_received = 0; @@ -51,7 +53,9 @@ pub async fn fetch_data( } }; - stream.shutdown().await?; + log::trace!("Received response"); + + let _ = stream.shutdown().await; let records: Vec<_> = records.into_iter().map(|r| r.into_owned()).collect(); diff --git a/statime-linux/src/ke/record.rs b/statime-linux/src/ke/record.rs index 032a906d1..1a3ac001e 100644 --- a/statime-linux/src/ke/record.rs +++ b/statime-linux/src/ke/record.rs @@ -760,6 +760,8 @@ impl<'a> Record<'a> { *buf = &buf[(4 + record_length)..]; Ok(res) } else { + // Not enough data, last record + *buf = &[]; Ok(None) } } From abf40f49730462acc2ccb5fe879484f91c363c8a Mon Sep 17 00:00:00 2001 From: David Venhoek Date: Thu, 14 Mar 2024 14:04:47 +0100 Subject: [PATCH 17/23] Fixed parameters lifetime bug in security provider. --- statime-linux/src/securityprovider.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/statime-linux/src/securityprovider.rs b/statime-linux/src/securityprovider.rs index 393c13733..5e7027a39 100644 --- a/statime-linux/src/securityprovider.rs +++ b/statime-linux/src/securityprovider.rs @@ -78,6 +78,9 @@ impl NTSProvider { valid_till: now + std::time::Duration::from_secs( next_parameters.validity_period.lifetime as _, + ) + + std::time::Duration::from_secs( + initial_data.current_parameters.validity_period.lifetime as _, ), }, ); @@ -259,6 +262,9 @@ async fn assocation_manager( valid_till: now + std::time::Duration::from_secs( next_params.validity_period.lifetime as _, + ) + + std::time::Duration::from_secs( + params.current_parameters.validity_period.lifetime as _, ), }, ); From fff98b4f6404bc48a13be37db90c7124c632c110 Mon Sep 17 00:00:00 2001 From: Ruben Nijveld Date: Tue, 8 Oct 2024 07:02:03 +0900 Subject: [PATCH 18/23] Re-add signing operations --- statime/src/port/master.rs | 81 ++++++++++++++++++++++++++++++++++---- statime/src/port/mod.rs | 1 + statime/src/port/slave.rs | 39 +++++++++++++++--- 3 files changed, 108 insertions(+), 13 deletions(-) diff --git a/statime/src/port/master.rs b/statime/src/port/master.rs index e91a84b8f..64996c4ef 100644 --- a/statime/src/port/master.rs +++ b/statime/src/port/master.rs @@ -2,6 +2,7 @@ use arrayvec::ArrayVec; use super::{state::PortState, ForwardedTLVProvider, Port, PortActionIterator, Running}; use crate::{ + crypto::SecurityAssociationProvider, datastructures::{ common::{PortIdentity, Tlv, TlvSetBuilder, TlvType}, messages::{DelayReqMessage, Header, Message, MAX_DATA_LEN}, @@ -12,17 +13,28 @@ use crate::{ time::Time, }; -impl<'a, A, C, F: Filter, R, P, S: PtpInstanceStateMutex> Port<'a, Running, A, R, C, F, P, S> { +impl<'a, A, C, F: Filter, R, P: SecurityAssociationProvider, S: PtpInstanceStateMutex> + Port<'a, Running, A, R, C, F, P, S> +{ pub(super) fn send_sync(&mut self) -> PortActionIterator { if matches!(self.port_state, PortState::Master) { log::trace!("sending sync message"); let seq_id = self.sync_seq_ids.generate(); - let packet_length = match self + let mut message = self .instance_state - .with_ref(|state| Message::sync(&state.default_ds, self.port_identity, seq_id)) - .serialize(&mut self.packet_buffer) - { + .with_ref(|state| Message::sync(&state.default_ds, self.port_identity, seq_id)); + let mut temp_buffer = [0; MAX_DATA_LEN]; + if let Some(spp) = self.config.spp { + if message + .add_signature(spp, &self.security_provider, &mut temp_buffer) + .is_err() + { + log::error!("Could not add signature to sync, sending without") + } + } + + let packet_length = match message.serialize(&mut self.packet_buffer) { Ok(message) => message, Err(error) => { log::error!("Statime bug: Could not serialize sync: {:?}", error); @@ -49,6 +61,19 @@ impl<'a, A, C, F: Filter, R, P, S: PtpInstanceStateMutex> Port<'a, Running, A, R pub(super) fn handle_sync_timestamp(&mut self, id: u16, timestamp: Time) -> PortActionIterator { if matches!(self.port_state, PortState::Master) { + let mut message = self.instance_state.with_ref(|state| { + Message::follow_up(&state.default_ds, self.port_identity, id, timestamp) + }); + let mut temp_buffer = [0; MAX_DATA_LEN]; + if let Some(spp) = self.config.spp { + if message + .add_signature(spp, &self.security_provider, &mut temp_buffer) + .is_err() + { + log::error!("Could not sign follow up message, sending unsigned"); + } + } + let packet_length = match self .instance_state .with_ref(|state| { @@ -141,6 +166,16 @@ impl<'a, A, C, F: Filter, R, P, S: PtpInstanceStateMutex> Port<'a, Running, A, R message.suffix = tlv_builder.build(); + let mut temp_buffer = [0; MAX_DATA_LEN]; + if let Some(spp) = self.config.spp { + if message + .add_signature(spp, &self.security_provider, &mut temp_buffer) + .is_err() + { + log::error!("Could not sign announce message, sending unsigned"); + } + } + let packet_length = match message.serialize(&mut self.packet_buffer) { Ok(length) => length, Err(error) => { @@ -174,7 +209,7 @@ impl<'a, A, C, F: Filter, R, P, S: PtpInstanceStateMutex> Port<'a, Running, A, R ) -> PortActionIterator { if matches!(self.port_state, PortState::Master) { log::debug!("Received DelayReq"); - let delay_resp_message = Message::delay_resp( + let mut delay_resp_message = Message::delay_resp( header, message, self.port_identity, @@ -182,6 +217,16 @@ impl<'a, A, C, F: Filter, R, P, S: PtpInstanceStateMutex> Port<'a, Running, A, R timestamp, ); + let mut temp_buffer = [0; MAX_DATA_LEN]; + if let Some(spp) = self.config.spp { + if delay_resp_message + .add_signature(spp, &self.security_provider, &mut temp_buffer) + .is_err() + { + log::error!("Could not sign delay response message, sending unsigned"); + } + } + let packet_length = match delay_resp_message.serialize(&mut self.packet_buffer) { Ok(length) => length, Err(error) => { @@ -205,10 +250,20 @@ impl<'a, A, C, F: Filter, R, P, S: PtpInstanceStateMutex> Port<'a, Running, A, R timestamp: Time, ) -> PortActionIterator { log::debug!("Received PDelayReq"); - let pdelay_resp_message = self.instance_state.with_ref(|state| { + let mut pdelay_resp_message = self.instance_state.with_ref(|state| { Message::pdelay_resp(&state.default_ds, self.port_identity, header, timestamp) }); + let mut temp_buffer = [0; MAX_DATA_LEN]; + if let Some(spp) = self.config.spp { + if pdelay_resp_message + .add_signature(spp, &self.security_provider, &mut temp_buffer) + .is_err() + { + log::error!("Could not sign pdelay response message, sending unsigned"); + } + } + let packet_length = match pdelay_resp_message.serialize(&mut self.packet_buffer) { Ok(length) => length, Err(error) => { @@ -235,7 +290,7 @@ impl<'a, A, C, F: Filter, R, P, S: PtpInstanceStateMutex> Port<'a, Running, A, R requestor_identity: PortIdentity, timestamp: Time, ) -> PortActionIterator { - let pdelay_resp_follow_up_messgae = self.instance_state.with_ref(|state| { + let mut pdelay_resp_follow_up_messgae = self.instance_state.with_ref(|state| { Message::pdelay_resp_follow_up( &state.default_ds, self.port_identity, @@ -245,6 +300,16 @@ impl<'a, A, C, F: Filter, R, P, S: PtpInstanceStateMutex> Port<'a, Running, A, R ) }); + let mut temp_buffer = [0; MAX_DATA_LEN]; + if let Some(spp) = self.config.spp { + if pdelay_resp_follow_up_messgae + .add_signature(spp, &self.security_provider, &mut temp_buffer) + .is_err() + { + log::error!("Could not sign pdelay response follow up message, sending unsigned"); + } + } + let packet_length = match pdelay_resp_follow_up_messgae.serialize(&mut self.packet_buffer) { Ok(length) => length, Err(error) => { diff --git a/statime/src/port/mod.rs b/statime/src/port/mod.rs index 0ad9b2e2e..2f71d5b05 100644 --- a/statime/src/port/mod.rs +++ b/statime/src/port/mod.rs @@ -533,6 +533,7 @@ impl< } } +#[allow(clippy::type_complexity)] impl<'a, A, C, F: Filter, R, P, S> Port<'a, InBmca, A, R, C, F, P, S> { /// End a BMCA cycle and make the /// [`handle_*`](`Port::handle_send_timestamp`) methods available again diff --git a/statime/src/port/slave.rs b/statime/src/port/slave.rs index 03772f79e..7cda5c0bd 100644 --- a/statime/src/port/slave.rs +++ b/statime/src/port/slave.rs @@ -6,12 +6,16 @@ use super::{ }; use crate::{ config::DelayMechanism, + crypto::SecurityAssociationProvider, datastructures::messages::{ DelayRespMessage, FollowUpMessage, Header, Message, PDelayRespFollowUpMessage, PDelayRespMessage, SyncMessage, }, filters::Filter, - port::{actions::TimestampContextInner, state::SyncState, PortAction, TimestampContext}, + port::{ + actions::TimestampContextInner, state::SyncState, PortAction, TimestampContext, + MAX_DATA_LEN, + }, ptp_instance::PtpInstanceStateMutex, time::{Duration, Interval, Time}, Clock, @@ -456,8 +460,15 @@ impl<'a, A, C: Clock, F: Filter, R, P, S> Port<'a, Running, A, R, C, F, P, S> { } } -impl<'a, A, C: Clock, F: Filter, R: Rng, P, S: PtpInstanceStateMutex> - Port<'a, Running, A, R, C, F, P, S> +impl< + 'a, + A, + C: Clock, + F: Filter, + R: Rng, + P: SecurityAssociationProvider, + S: PtpInstanceStateMutex, + > Port<'a, Running, A, R, C, F, P, S> { pub(super) fn send_delay_request(&mut self) -> PortActionIterator { match self.config.delay_mechanism { @@ -472,9 +483,18 @@ impl<'a, A, C: Clock, F: Filter, R: Rng, P, S: PtpInstanceStateMutex> ) -> PortActionIterator { let pdelay_id = self.pdelay_seq_ids.generate(); - let pdelay_req = self.instance_state.with_ref(|state| { + let mut pdelay_req = self.instance_state.with_ref(|state| { Message::pdelay_req(&state.default_ds, self.port_identity, pdelay_id) }); + let mut temp_buffer = [0; MAX_DATA_LEN]; + if let Some(spp) = self.config.spp { + if pdelay_req + .add_signature(spp, &self.security_provider, &mut temp_buffer) + .is_err() + { + log::error!("Could not sign delay request, sending without signature"); + } + } let message_length = match pdelay_req.serialize(&mut self.packet_buffer) { Ok(length) => length, Err(error) => { @@ -519,9 +539,18 @@ impl<'a, A, C: Clock, F: Filter, R: Rng, P, S: PtpInstanceStateMutex> log::debug!("Starting new delay measurement"); let delay_id = self.delay_seq_ids.generate(); - let delay_req = self.instance_state.with_ref(|state| { + let mut delay_req = self.instance_state.with_ref(|state| { Message::delay_req(&state.default_ds, self.port_identity, delay_id) }); + let mut temp_buffer = [0; MAX_DATA_LEN]; + if let Some(spp) = self.config.spp { + if delay_req + .add_signature(spp, &self.security_provider, &mut temp_buffer) + .is_err() + { + log::error!("Could not sign delay request, sending without signature"); + } + } let message_length = match delay_req.serialize(&mut self.packet_buffer) { Ok(length) => length, From f2a030425eef18575d940fe6f5eb66cb888245ca Mon Sep 17 00:00:00 2001 From: Ruben Nijveld Date: Tue, 8 Oct 2024 08:53:56 +0900 Subject: [PATCH 19/23] Force update From 1035ac72fe7556b0e17c31bb4c8fb055fe42219f Mon Sep 17 00:00:00 2001 From: Ruben Nijveld Date: Tue, 8 Oct 2024 10:29:21 +0900 Subject: [PATCH 20/23] Setup a preshared key provider --- statime-linux/src/main.rs | 41 +++++++----- statime-linux/src/securityprovider.rs | 90 ++++++++++++++++++++++++++- 2 files changed, 113 insertions(+), 18 deletions(-) diff --git a/statime-linux/src/main.rs b/statime-linux/src/main.rs index 0d753750a..e1287a442 100644 --- a/statime-linux/src/main.rs +++ b/statime-linux/src/main.rs @@ -23,7 +23,7 @@ use statime_linux::{ config::Config, initialize_logging_parse_config, observer::ObservableInstanceState, - securityprovider::NTSProvider, + securityprovider::{NTSProvider, PresharedSecurityProvider}, socket::{ open_ethernet_socket, open_ipv4_event_socket, open_ipv4_general_socket, open_ipv6_event_socket, open_ipv6_general_socket, PtpTargetAddress, @@ -316,21 +316,28 @@ async fn actual_main() { let tlv_forwarder = TlvForwarder::new(); - let (provider, spp) = if let Config { - nts4ptp_server: Some(server_name), - nts4ptp_client_cert_key: Some(client_key), - nts4ptp_client_cert: Some(client_cert), - nts4ptp_server_root: Some(server_root), - .. - } = config - { - let (provider, spp) = NTSProvider::new(server_name, client_key, client_cert, server_root) - .await - .unwrap(); - (provider, Some(spp)) - } else { - (NTSProvider::empty(), None) - }; + // NTS Key provider + // let (provider, spp) = if let Config { + // nts4ptp_server: Some(server_name), + // nts4ptp_client_cert_key: Some(client_key), + // nts4ptp_client_cert: Some(client_cert), + // nts4ptp_server_root: Some(server_root), + // .. + // } = config + // { + // let (provider, spp) = NTSProvider::new(server_name, client_key, client_cert, server_root) + // .await + // .unwrap(); + // (provider, Some(spp)) + // } else { + // (NTSProvider::empty(), None) + // }; + + // Preshared key provider + let spp = Some(10); + let key_id = 1234; + let key_data = b"abcdefghijklmnopqrstuvwxyz123456"; + let provider = PresharedSecurityProvider::new(spp.unwrap(), key_id, *key_data); for port_config in config.ports { let interface = port_config.interface; @@ -540,7 +547,7 @@ type BmcaPort = Port< StdRng, BoxedClock, KalmanFilter, - NTSProvider, + PresharedSecurityProvider, RwLock, >; diff --git a/statime-linux/src/securityprovider.rs b/statime-linux/src/securityprovider.rs index 5e7027a39..5abdcc3d8 100644 --- a/statime-linux/src/securityprovider.rs +++ b/statime-linux/src/securityprovider.rs @@ -1,7 +1,7 @@ use std::{collections::HashMap, error::Error, path::PathBuf, sync::Arc}; use rand::Rng; -use rustls::{ClientConfig, RootCertStore}; +use rustls::{crypto::hash::Hash, ClientConfig, RootCertStore}; use statime::crypto::{ HmacSha256_128, SecurityAssociation, SecurityAssociationProvider, SecurityPolicy, SenderIdentificaton, @@ -286,3 +286,91 @@ async fn assocation_manager( tokio::time::sleep_until(deadline.into()).await; } } + +pub struct PresharedSecurityAssociationInner { + key: (u32, statime::crypto::HmacSha256_128), + ignore_correction: bool, + sequence_ids: HashMap<(SenderIdentificaton, u32), u16>, +} + +pub struct PresharedSecurityAssociation<'a>(std::sync::MutexGuard<'a, PresharedSecurityAssociationInner>); + +impl<'a> SecurityAssociation for PresharedSecurityAssociation<'a> { + fn policy_data(&self) -> SecurityPolicy { + SecurityPolicy { + ignore_correction: self.0.ignore_correction, + } + } + + fn mac(&self, key_id: u32) -> Option<&dyn statime::crypto::Mac> { + if self.0.key.0 == key_id { + Some(&self.0.key.1 as &dyn statime::crypto::Mac) + } else { + None + } + } + + fn register_sequence_id( + &mut self, + key_id: u32, + sender: SenderIdentificaton, + sequence_id: u16, + ) -> bool { + match self.0.sequence_ids.entry((sender, key_id)) { + std::collections::hash_map::Entry::Occupied(mut entry) => { + if (entry.get().wrapping_sub(sequence_id) as i16) < 0 { + *entry.get_mut() = sequence_id; + true + } else { + false + } + } + std::collections::hash_map::Entry::Vacant(entry) => { + entry.insert(sequence_id); + true + } + } + } + + fn signing_mac(&self) -> (u32, &dyn statime::crypto::Mac) { + ( + self.0.key.0, + &self.0 + .key + .1 as &dyn statime::crypto::Mac + ) + } +} + +#[derive(Clone)] +pub struct PresharedSecurityProvider { + associations: Arc>>>, +} + +impl SecurityAssociationProvider for PresharedSecurityProvider { + type Association<'a> = PresharedSecurityAssociation<'a>; + + fn lookup(&self, spp: u8) -> Option> { + self.associations + .get(&spp) + .map(|a| PresharedSecurityAssociation(a.lock().unwrap())) + } +} + +impl PresharedSecurityProvider { + pub fn new(spp: u8, key_id: u32, key_data: [u8; 32]) -> PresharedSecurityProvider { + let mut data = HashMap::new(); + data.insert(spp, Arc::new(std::sync::Mutex::new(PresharedSecurityAssociationInner { + key: ( + key_id, + statime::crypto::HmacSha256_128::new(key_data), + ), + ignore_correction: false, + sequence_ids: Default::default(), + }))); + + PresharedSecurityProvider { + associations: Arc::new(data), + } + } +} From beceda61dcb5320798e3c6d31cbbe3cefbad9219 Mon Sep 17 00:00:00 2001 From: Ruben Nijveld Date: Tue, 8 Oct 2024 11:44:20 +0900 Subject: [PATCH 21/23] Allow configuring security parameters via config --- Cargo.lock | 9 +++- Cargo.toml | 1 + statime-linux/Cargo.toml | 1 + statime-linux/src/config/mod.rs | 68 ++++++++++++++++++++---- statime-linux/src/main.rs | 42 ++++++--------- statime-linux/src/securityprovider.rs | 74 +++++++++++++++++++++++++-- 6 files changed, 156 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 402009a95..80b985067 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -102,6 +102,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bytemuck" version = "1.18.0" @@ -484,7 +490,7 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35e4980fa29e4c4b212ffb3db068a564cbf560e51d3944b7c88bd8bf5bec64f4" dependencies = [ - "base64", + "base64 0.21.7", "rustls-pki-types", ] @@ -612,6 +618,7 @@ name = "statime-linux" version = "0.2.2" dependencies = [ "arrayvec", + "base64 0.22.1", "clap", "clock-steering", "hex", diff --git a/Cargo.toml b/Cargo.toml index 6948ff15f..8564add10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ tokio-rustls = "0.25.0" tokio-util = { version = "0.7.10", features = ["codec"] } clock-steering = "0.2.0" timestamped-socket = "0.2.4" +base64 = "0.22.1" # our own crates used as dependencies, same version as the workspace version diff --git a/statime-linux/Cargo.toml b/statime-linux/Cargo.toml index 7345581a4..5c1590c58 100644 --- a/statime-linux/Cargo.toml +++ b/statime-linux/Cargo.toml @@ -44,6 +44,7 @@ rustls.workspace = true rustls-pemfile.workspace = true tokio-rustls.workspace = true tokio-util.workspace = true +base64.workspace = true clock-steering.workspace = true timestamped-socket.workspace = true diff --git a/statime-linux/src/config/mod.rs b/statime-linux/src/config/mod.rs index 519035533..bf50a1846 100644 --- a/statime-linux/src/config/mod.rs +++ b/statime-linux/src/config/mod.rs @@ -38,13 +38,64 @@ pub struct Config { pub observability: ObservabilityConfig, #[serde(default)] pub virtual_system_clock: bool, - pub nts4ptp_server: Option, #[serde(default)] - pub nts4ptp_client_cert: Option, - #[serde(default)] - pub nts4ptp_client_cert_key: Option, - #[serde(default)] - pub nts4ptp_server_root: Option, + pub security: SecurityConfig, +} + +#[derive(Deserialize, Debug, Clone, PartialEq, Eq, Default)] +#[serde(rename_all = "kebab-case", deny_unknown_fields, tag = "type")] +pub enum SecurityConfig { + #[default] + None, + #[serde(rename = "nts")] + NTS(NTSConfig), + #[serde(rename = "psk")] + Preshared(PresharedConfig), +} + +#[derive(Deserialize, Debug, Clone, PartialEq, Eq)] +#[serde(rename_all = "kebab-case", deny_unknown_fields)] +pub struct NTSConfig { + pub server: String, + pub client_cert: PathBuf, + pub client_cert_key: PathBuf, + pub server_root: PathBuf, +} + +#[derive(Deserialize, Debug, Clone, PartialEq, Eq)] +#[serde(rename_all = "kebab-case", deny_unknown_fields)] +pub struct PresharedConfig { + pub key: KeyDataConfig, + pub key_id: u32, + pub spp: u8, +} + +#[derive(Deserialize, Debug, Clone, PartialEq, Eq)] +#[serde(rename_all = "kebab-case", deny_unknown_fields, tag = "type", content = "data")] +pub enum KeyDataConfig { + Raw(Vec), + Bytes(String), + Base64(String), + Hex(String), +} + +impl KeyDataConfig { + pub fn as_vec(&self) -> Vec { + use base64::Engine; + + match self { + KeyDataConfig::Raw(data) => data.clone(), + KeyDataConfig::Bytes(data) => data.as_bytes().to_vec(), + KeyDataConfig::Base64(data) => base64::engine::general_purpose::STANDARD.decode(data).unwrap(), + KeyDataConfig::Hex(data) => hex::decode(data).unwrap(), + } + } + + pub fn as_bytes(&self) -> [u8; 32] { + let mut result = [0; 32]; + result.copy_from_slice(&self.as_vec()); + result + } } #[derive(Deserialize, Debug, Clone, PartialEq, Eq)] @@ -296,10 +347,7 @@ interface = "enp0s31f6" ports: vec![expected_port], observability: ObservabilityConfig::default(), virtual_system_clock: false, - nts4ptp_client_cert: None, - nts4ptp_client_cert_key: None, - nts4ptp_server: None, - nts4ptp_server_root: None, + security: crate::config::SecurityConfig::None, }; let actual = toml::from_str(MINIMAL_CONFIG).unwrap(); diff --git a/statime-linux/src/main.rs b/statime-linux/src/main.rs index e1287a442..b8f8e0e98 100644 --- a/statime-linux/src/main.rs +++ b/statime-linux/src/main.rs @@ -20,10 +20,10 @@ use statime::{ }; use statime_linux::{ clock::{LinuxClock, PortTimestampToTime}, - config::Config, + config::SecurityConfig, initialize_logging_parse_config, observer::ObservableInstanceState, - securityprovider::{NTSProvider, PresharedSecurityProvider}, + securityprovider::{DynamicSecurityProvider, NTSProvider, PresharedSecurityProvider}, socket::{ open_ethernet_socket, open_ipv4_event_socket, open_ipv4_general_socket, open_ipv6_event_socket, open_ipv6_general_socket, PtpTargetAddress, @@ -316,28 +316,20 @@ async fn actual_main() { let tlv_forwarder = TlvForwarder::new(); - // NTS Key provider - // let (provider, spp) = if let Config { - // nts4ptp_server: Some(server_name), - // nts4ptp_client_cert_key: Some(client_key), - // nts4ptp_client_cert: Some(client_cert), - // nts4ptp_server_root: Some(server_root), - // .. - // } = config - // { - // let (provider, spp) = NTSProvider::new(server_name, client_key, client_cert, server_root) - // .await - // .unwrap(); - // (provider, Some(spp)) - // } else { - // (NTSProvider::empty(), None) - // }; - - // Preshared key provider - let spp = Some(10); - let key_id = 1234; - let key_data = b"abcdefghijklmnopqrstuvwxyz123456"; - let provider = PresharedSecurityProvider::new(spp.unwrap(), key_id, *key_data); + let (provider, spp) = match config.security { + SecurityConfig::None => (DynamicSecurityProvider::NoSecurity, None), + SecurityConfig::NTS(nts) => { + let (provider, spp) = NTSProvider::new(nts.server, nts.client_cert_key, nts.client_cert, nts.server_root) + .await + .unwrap(); + (DynamicSecurityProvider::NTS(provider), Some(spp)) + }, + SecurityConfig::Preshared(psk) => { + + let provider = PresharedSecurityProvider::new(psk.spp, psk.key_id, psk.key.as_bytes()); + (DynamicSecurityProvider::Preshared(provider), Some(psk.spp)) + } + }; for port_config in config.ports { let interface = port_config.interface; @@ -547,7 +539,7 @@ type BmcaPort = Port< StdRng, BoxedClock, KalmanFilter, - PresharedSecurityProvider, + DynamicSecurityProvider, RwLock, >; diff --git a/statime-linux/src/securityprovider.rs b/statime-linux/src/securityprovider.rs index 5abdcc3d8..d79b1aa24 100644 --- a/statime-linux/src/securityprovider.rs +++ b/statime-linux/src/securityprovider.rs @@ -1,10 +1,9 @@ use std::{collections::HashMap, error::Error, path::PathBuf, sync::Arc}; use rand::Rng; -use rustls::{crypto::hash::Hash, ClientConfig, RootCertStore}; +use rustls::{ClientConfig, RootCertStore}; use statime::crypto::{ - HmacSha256_128, SecurityAssociation, SecurityAssociationProvider, SecurityPolicy, - SenderIdentificaton, + HmacSha256_128, SecurityAssociation, SecurityAssociationProvider, SecurityPolicy, SenderIdentificaton }; use crate::ke::{ @@ -374,3 +373,72 @@ impl PresharedSecurityProvider { } } } + +pub enum DynamicSecurityAssociation<'a> { + NTS(NTSAssociation<'a>), + Preshared(PresharedSecurityAssociation<'a>), +} + +impl<'a> SecurityAssociation for DynamicSecurityAssociation<'a> { + fn policy_data(&self) -> SecurityPolicy { + use DynamicSecurityAssociation::*; + match self { + NTS(association) => association.policy_data(), + Preshared(association) => association.policy_data(), + } + } + + fn mac(&self, key_id: u32) -> Option<&dyn statime::crypto::Mac> { + use DynamicSecurityAssociation::*; + match self { + NTS(association) => association.mac(key_id), + Preshared(association) => association.mac(key_id), + } + } + + fn register_sequence_id( + &mut self, + key_id: u32, + sender: SenderIdentificaton, + sequence_id: u16, + ) -> bool { + use DynamicSecurityAssociation::*; + match self { + NTS(association) => { + association.register_sequence_id(key_id, sender, sequence_id) + } + Preshared(association) => { + association.register_sequence_id(key_id, sender, sequence_id) + } + } + } + + fn signing_mac(&self) -> (u32, &dyn statime::crypto::Mac) { + use DynamicSecurityAssociation::*; + + match self { + NTS(association) => association.signing_mac(), + Preshared(association) => association.signing_mac(), + } + } +} + +#[derive(Clone)] +pub enum DynamicSecurityProvider { + NoSecurity, + NTS(NTSProvider), + Preshared(PresharedSecurityProvider), +} + +impl SecurityAssociationProvider for DynamicSecurityProvider { + type Association<'a> = DynamicSecurityAssociation<'a>; + + fn lookup(&self, spp: u8) -> Option> { + use DynamicSecurityProvider::*; + match self { + NoSecurity => None, + NTS(provider) => provider.lookup(spp).map(DynamicSecurityAssociation::NTS), + Preshared(provider) => provider.lookup(spp).map(DynamicSecurityAssociation::Preshared), + } + } +} From 28abb0a1c465009fb4850796f517370e32f9644f Mon Sep 17 00:00:00 2001 From: Ruben Nijveld Date: Wed, 9 Oct 2024 10:50:08 +0900 Subject: [PATCH 22/23] Fixed code formatting --- statime-linux/src/config/mod.rs | 11 ++++-- statime-linux/src/ke/server.rs | 3 +- statime-linux/src/lib.rs | 1 - statime-linux/src/main.rs | 14 +++++--- statime-linux/src/securityprovider.rs | 42 ++++++++++------------ statime/src/datastructures/messages/mod.rs | 5 +-- statime/src/overlay_clock.rs | 12 ++++--- statime/src/port/mod.rs | 5 +-- statime/src/shared_clock.rs | 14 ++++---- 9 files changed, 60 insertions(+), 47 deletions(-) diff --git a/statime-linux/src/config/mod.rs b/statime-linux/src/config/mod.rs index bf50a1846..341c2a3ab 100644 --- a/statime-linux/src/config/mod.rs +++ b/statime-linux/src/config/mod.rs @@ -71,7 +71,12 @@ pub struct PresharedConfig { } #[derive(Deserialize, Debug, Clone, PartialEq, Eq)] -#[serde(rename_all = "kebab-case", deny_unknown_fields, tag = "type", content = "data")] +#[serde( + rename_all = "kebab-case", + deny_unknown_fields, + tag = "type", + content = "data" +)] pub enum KeyDataConfig { Raw(Vec), Bytes(String), @@ -86,7 +91,9 @@ impl KeyDataConfig { match self { KeyDataConfig::Raw(data) => data.clone(), KeyDataConfig::Bytes(data) => data.as_bytes().to_vec(), - KeyDataConfig::Base64(data) => base64::engine::general_purpose::STANDARD.decode(data).unwrap(), + KeyDataConfig::Base64(data) => base64::engine::general_purpose::STANDARD + .decode(data) + .unwrap(), KeyDataConfig::Hex(data) => hex::decode(data).unwrap(), } } diff --git a/statime-linux/src/ke/server.rs b/statime-linux/src/ke/server.rs index 5b344ec18..684241d3c 100644 --- a/statime-linux/src/ke/server.rs +++ b/statime-linux/src/ke/server.rs @@ -17,13 +17,12 @@ use tokio::{ }; use tokio_rustls::{server::TlsStream, TlsAcceptor}; -use crate::initialize_logging_parse_config; - use super::{ common::{load_certs, load_certs_from_files, load_private_key, Key}, record::*, tls_utils::OnlyAllowedClients, }; +use crate::initialize_logging_parse_config; struct KeySetStore { current: Key, diff --git a/statime-linux/src/lib.rs b/statime-linux/src/lib.rs index 56ab74912..bec013811 100644 --- a/statime-linux/src/lib.rs +++ b/statime-linux/src/lib.rs @@ -13,7 +13,6 @@ pub mod tracing; use std::path::Path; use config::Config; - pub use ke::main as ke_main; pub use metrics::exporter::main as metrics_exporter_main; use tracing::LogLevel; diff --git a/statime-linux/src/main.rs b/statime-linux/src/main.rs index b8f8e0e98..aa7f08c8f 100644 --- a/statime-linux/src/main.rs +++ b/statime-linux/src/main.rs @@ -319,13 +319,17 @@ async fn actual_main() { let (provider, spp) = match config.security { SecurityConfig::None => (DynamicSecurityProvider::NoSecurity, None), SecurityConfig::NTS(nts) => { - let (provider, spp) = NTSProvider::new(nts.server, nts.client_cert_key, nts.client_cert, nts.server_root) - .await - .unwrap(); + let (provider, spp) = NTSProvider::new( + nts.server, + nts.client_cert_key, + nts.client_cert, + nts.server_root, + ) + .await + .unwrap(); (DynamicSecurityProvider::NTS(provider), Some(spp)) - }, + } SecurityConfig::Preshared(psk) => { - let provider = PresharedSecurityProvider::new(psk.spp, psk.key_id, psk.key.as_bytes()); (DynamicSecurityProvider::Preshared(provider), Some(psk.spp)) } diff --git a/statime-linux/src/securityprovider.rs b/statime-linux/src/securityprovider.rs index d79b1aa24..afa26042f 100644 --- a/statime-linux/src/securityprovider.rs +++ b/statime-linux/src/securityprovider.rs @@ -3,7 +3,8 @@ use std::{collections::HashMap, error::Error, path::PathBuf, sync::Arc}; use rand::Rng; use rustls::{ClientConfig, RootCertStore}; use statime::crypto::{ - HmacSha256_128, SecurityAssociation, SecurityAssociationProvider, SecurityPolicy, SenderIdentificaton + HmacSha256_128, SecurityAssociation, SecurityAssociationProvider, SecurityPolicy, + SenderIdentificaton, }; use crate::ke::{ @@ -292,7 +293,9 @@ pub struct PresharedSecurityAssociationInner { sequence_ids: HashMap<(SenderIdentificaton, u32), u16>, } -pub struct PresharedSecurityAssociation<'a>(std::sync::MutexGuard<'a, PresharedSecurityAssociationInner>); +pub struct PresharedSecurityAssociation<'a>( + std::sync::MutexGuard<'a, PresharedSecurityAssociationInner>, +); impl<'a> SecurityAssociation for PresharedSecurityAssociation<'a> { fn policy_data(&self) -> SecurityPolicy { @@ -332,12 +335,7 @@ impl<'a> SecurityAssociation for PresharedSecurityAssociation<'a> { } fn signing_mac(&self) -> (u32, &dyn statime::crypto::Mac) { - ( - self.0.key.0, - &self.0 - .key - .1 as &dyn statime::crypto::Mac - ) + (self.0.key.0, &self.0.key.1 as &dyn statime::crypto::Mac) } } @@ -359,14 +357,14 @@ impl SecurityAssociationProvider for PresharedSecurityProvider { impl PresharedSecurityProvider { pub fn new(spp: u8, key_id: u32, key_data: [u8; 32]) -> PresharedSecurityProvider { let mut data = HashMap::new(); - data.insert(spp, Arc::new(std::sync::Mutex::new(PresharedSecurityAssociationInner { - key: ( - key_id, - statime::crypto::HmacSha256_128::new(key_data), - ), - ignore_correction: false, - sequence_ids: Default::default(), - }))); + data.insert( + spp, + Arc::new(std::sync::Mutex::new(PresharedSecurityAssociationInner { + key: (key_id, statime::crypto::HmacSha256_128::new(key_data)), + ignore_correction: false, + sequence_ids: Default::default(), + })), + ); PresharedSecurityProvider { associations: Arc::new(data), @@ -404,12 +402,8 @@ impl<'a> SecurityAssociation for DynamicSecurityAssociation<'a> { ) -> bool { use DynamicSecurityAssociation::*; match self { - NTS(association) => { - association.register_sequence_id(key_id, sender, sequence_id) - } - Preshared(association) => { - association.register_sequence_id(key_id, sender, sequence_id) - } + NTS(association) => association.register_sequence_id(key_id, sender, sequence_id), + Preshared(association) => association.register_sequence_id(key_id, sender, sequence_id), } } @@ -438,7 +432,9 @@ impl SecurityAssociationProvider for DynamicSecurityProvider { match self { NoSecurity => None, NTS(provider) => provider.lookup(spp).map(DynamicSecurityAssociation::NTS), - Preshared(provider) => provider.lookup(spp).map(DynamicSecurityAssociation::Preshared), + Preshared(provider) => provider + .lookup(spp) + .map(DynamicSecurityAssociation::Preshared), } } } diff --git a/statime/src/datastructures/messages/mod.rs b/statime/src/datastructures/messages/mod.rs index 4e7a45567..08c950018 100644 --- a/statime/src/datastructures/messages/mod.rs +++ b/statime/src/datastructures/messages/mod.rs @@ -241,8 +241,9 @@ fn base_header( /// Checks whether message is of PTP revision compatible with Statime pub fn is_compatible(buffer: &[u8]) -> bool { // this ensures that versionPTP in the header is 2 - // it will never happen in PTPv1 packets because this octet is the LSB of versionPTP there - (buffer.len() >= 2) && (buffer[1] & 0xF) == 2 + // it will never happen in PTPv1 packets because this octet is the LSB of + // versionPTP there + (buffer.len() >= 2) && (buffer[1] & 0xf) == 2 } impl<'a> Message<'a> { diff --git a/statime/src/overlay_clock.rs b/statime/src/overlay_clock.rs index 48d9780ab..912affb98 100644 --- a/statime/src/overlay_clock.rs +++ b/statime/src/overlay_clock.rs @@ -1,8 +1,11 @@ -use crate::{time::Duration, time::Time, Clock}; +use crate::{ + time::{Duration, Time}, + Clock, +}; /// An overlay over other, read-only clock, frequency-locked to it. -/// In other words, a virtual clock which can be tuned in software without affecting -/// the underlying system or hardware clock. +/// In other words, a virtual clock which can be tuned in software without +/// affecting the underlying system or hardware clock. #[derive(Debug)] pub struct OverlayClock { roclock: C, @@ -23,7 +26,8 @@ impl OverlayClock { } } - /// Converts (shifts and scales) `Time` in underlying clock's timescale to overlay clock timescale + /// Converts (shifts and scales) `Time` in underlying clock's timescale to + /// overlay clock timescale pub fn time_from_underlying(&self, roclock_time: Time) -> Time { let elapsed = roclock_time - self.last_sync; let corr = elapsed * self.freq_scale_ppm_diff / 1_000_000; diff --git a/statime/src/port/mod.rs b/statime/src/port/mod.rs index 2f71d5b05..88c53a698 100644 --- a/statime/src/port/mod.rs +++ b/statime/src/port/mod.rs @@ -13,8 +13,9 @@ use rand::Rng; use state::PortState; use self::sequence_id::SequenceIdGenerator; -pub use crate::datastructures::messages::is_compatible as is_message_buffer_compatible; -pub use crate::datastructures::messages::MAX_DATA_LEN; +pub use crate::datastructures::messages::{ + is_compatible as is_message_buffer_compatible, MAX_DATA_LEN, +}; #[cfg(doc)] use crate::PtpInstance; use crate::{ diff --git a/statime/src/shared_clock.rs b/statime/src/shared_clock.rs index 1006a6623..bbbf02632 100644 --- a/statime/src/shared_clock.rs +++ b/statime/src/shared_clock.rs @@ -1,8 +1,9 @@ -use crate::time::Duration; -use crate::time::Time; -use crate::Clock; -use std::sync::Arc; -use std::sync::Mutex; +use std::sync::{Arc, Mutex}; + +use crate::{ + time::{Duration, Time}, + Clock, +}; /// A wrapper for stateful `statime::Clock` implementations to make them behave /// like e.g. `statime_linux::LinuxClock` - clones share state with each other @@ -19,7 +20,8 @@ impl SharedClock { } impl Clone for SharedClock { - /// Clone the shared reference to the clock (behaviour consistent with `statime_linux::LinuxClock`) + /// Clone the shared reference to the clock (behaviour consistent with + /// `statime_linux::LinuxClock`) fn clone(&self) -> Self { Self(self.0.clone()) } From 0c61c90dc0dee13b2a93dc0a0f8dcb0d3cfb5ed3 Mon Sep 17 00:00:00 2001 From: Ruben Nijveld Date: Wed, 9 Oct 2024 10:54:28 +0900 Subject: [PATCH 23/23] Fixed accidentally missed merge markers --- statime-stm32/Cargo.lock | 2 +- statime-stm32/src/port.rs | 8 +------- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/statime-stm32/Cargo.lock b/statime-stm32/Cargo.lock index 3fd788da0..69bccf5ee 100644 --- a/statime-stm32/Cargo.lock +++ b/statime-stm32/Cargo.lock @@ -721,7 +721,7 @@ dependencies = [ [[package]] name = "statime" -version = "0.2.0" +version = "0.2.2" dependencies = [ "arrayvec", "az", diff --git a/statime-stm32/src/port.rs b/statime-stm32/src/port.rs index 0d86a4947..a0b5810ac 100644 --- a/statime-stm32/src/port.rs +++ b/statime-stm32/src/port.rs @@ -26,20 +26,14 @@ use crate::{ }; type StmPort = statime::port::Port< -<<<<<<< HEAD 'static, -======= ->>>>>>> f5dee44 (Added support for the authentication tlv to the library.) State, AcceptAnyMaster, Rng, &'static PtpClock, BasicFilter, -<<<<<<< HEAD - PtpStateMutex, -======= NoSecurityProvider, ->>>>>>> f5dee44 (Added support for the authentication tlv to the library.) + PtpStateMutex, >; pub struct Port {