Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Nts4ptp server and client #426

Draft
wants to merge 23 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
24ea922
Implemented the authentication tlvs and associated infra.
davidv1992 Feb 2, 2024
01d7789
Added support for the authentication tlv to the library.
davidv1992 Feb 2, 2024
071d04b
Added support for sequence id registration and checking.
davidv1992 Feb 15, 2024
73ce51c
Implementation of NTS4PTP KE server
rnijveld Feb 27, 2024
ab4b8d6
Added nts security provider framework.
davidv1992 Feb 22, 2024
5550ad9
Deny NTS4PTP TLS connection to clients not on the allowed client list
rnijveld Mar 7, 2024
b5adcb4
Cleanup of ke code structure
rnijveld Mar 7, 2024
5a10978
Added client implementation.
davidv1992 Mar 7, 2024
7475ae7
Use nts security provider in statime-linux.
davidv1992 Mar 7, 2024
c968e8e
Split port from dnsname.
davidv1992 Mar 8, 2024
c6ddf9d
Fix incorrect handling of spp in port.
davidv1992 Mar 8, 2024
13162fd
Fix minor bug in sequence id registration.
davidv1992 Mar 8, 2024
f6e88b7
Added additional logging to verification.
davidv1992 Mar 8, 2024
102ad22
Fixed bugs in announce message sending.
davidv1992 Mar 8, 2024
7a67343
Fix incorrect shutdown of tls streams.
davidv1992 Mar 8, 2024
4563b1a
Fixed bug in record parsing causing an infinite loop.
davidv1992 Mar 8, 2024
abf40f4
Fixed parameters lifetime bug in security provider.
davidv1992 Mar 14, 2024
fff98b4
Re-add signing operations
rnijveld Oct 7, 2024
f2a0304
Force update
rnijveld Oct 7, 2024
1035ac7
Setup a preshared key provider
rnijveld Oct 8, 2024
beceda6
Allow configuring security parameters via config
rnijveld Oct 8, 2024
28abb0a
Fixed code formatting
rnijveld Oct 9, 2024
0c61c90
Fixed accidentally missed merge markers
rnijveld Oct 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
243 changes: 229 additions & 14 deletions Cargo.lock

Large diffs are not rendered by default.

7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,20 @@ 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" }
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"
base64 = "0.22.1"


# our own crates used as dependencies, same version as the workspace version
Expand Down
11 changes: 10 additions & 1 deletion statime-linux/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -32,10 +36,15 @@ 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
base64.workspace = true

clock-steering.workspace = true
timestamped-socket.workspace = true
Expand Down
4 changes: 4 additions & 0 deletions statime-linux/bin/statime-ke.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
statime_linux::ke_main().await
}
67 changes: 67 additions & 0 deletions statime-linux/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,71 @@ pub struct Config {
pub observability: ObservabilityConfig,
#[serde(default)]
pub virtual_system_clock: bool,
#[serde(default)]
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<u8>),
Bytes(String),
Base64(String),
Hex(String),
}

impl KeyDataConfig {
pub fn as_vec(&self) -> Vec<u8> {
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)]
Expand Down Expand Up @@ -116,6 +181,7 @@ impl From<PortConfig> for statime::config::PortConfig<Option<Vec<ClockIdentity>>
interval: Interval::from_log_2(pc.delay_interval),
},
},
spp: None,
}
}
}
Expand Down Expand Up @@ -288,6 +354,7 @@ interface = "enp0s31f6"
ports: vec![expected_port],
observability: ObservabilityConfig::default(),
virtual_system_clock: false,
security: crate::config::SecurityConfig::None,
};

let actual = toml::from_str(MINIMAL_CONFIG).unwrap();
Expand Down
63 changes: 63 additions & 0 deletions statime-linux/src/ke/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use std::{error::Error, io, sync::Arc};

use rustls::{pki_types::ServerName, ClientConfig};
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
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<ClientConfig>,
) -> Result<PtpKeyResponseMessage<'static>, Box<dyn Error>> {
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.split_once(':').unwrap().0.to_owned())?;

let stream = TcpStream::connect(server_address).await?;
let mut stream = connector.connect(dnsname, stream).await?;

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;

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());
}
};

log::trace!("Received response");

let _ = stream.shutdown().await;

let records: Vec<_> = records.into_iter().map(|r| r.into_owned()).collect();

Ok(records.try_into()?)
}
50 changes: 50 additions & 0 deletions statime-linux/src/ke/common.rs
Original file line number Diff line number Diff line change
@@ -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<Path>) -> io::Result<Vec<CertificateDer<'static>>> {
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<Path>) -> io::Result<PrivateKeyDer<'static>> {
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<Item = impl AsRef<Path>>,
) -> io::Result<Vec<CertificateDer<'static>>> {
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)
}
7 changes: 7 additions & 0 deletions statime-linux/src/ke/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pub mod client;
pub mod common;
mod record;
mod server;
mod tls_utils;

pub use server::main;
Loading
Loading