diff --git a/Cargo.lock b/Cargo.lock index 04102f1..86ec307 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -481,7 +481,6 @@ dependencies = [ "digest", "fiat-crypto", "rustc_version", - "serde", "subtle", "zeroize", ] @@ -1948,17 +1947,13 @@ dependencies = [ "clap", "dropshot", "ed25519-dalek", - "futures", "hex", - "hkdf", "http", "instrumentation", "rand", "schemars", "serde", - "serde_json", "serde_with", - "sha2", "slog", "slog-async", "sqlx", @@ -1967,7 +1962,6 @@ dependencies = [ "tracing", "tracing-slog", "uuid", - "x25519-dalek", ] [[package]] @@ -2644,9 +2638,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.138" +version = "1.0.135" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" +checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9" dependencies = [ "itoa", "memchr", @@ -4173,18 +4167,6 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" -[[package]] -name = "x25519-dalek" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" -dependencies = [ - "curve25519-dalek", - "rand_core", - "serde", - "zeroize", -] - [[package]] name = "xtask" version = "0.2.0" @@ -4266,20 +4248,6 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] [[package]] name = "zerovec" diff --git a/Cargo.toml b/Cargo.toml index ea7bb55..fdf3949 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,6 @@ dropshot = "0.15.1" ed25519-dalek = { version = "2.1.1", features = ["pem", "rand_core"] } futures = "0.3" hex = "0.4.3" -hkdf = "0.12.4" http = "1.2.0" once_cell = "1.20.2" progenitor = "0.9" @@ -45,10 +44,8 @@ reqwest = { version = "0.12.12", features = ["json", "stream", "rustls-tls"] } schemars = { version = "0.8.21", features = ["bytes", "chrono", "derive"] } semver = "1.0.24" serde = { version = "1.0.217", features = ["derive"] } -serde_json = "1.0.138" serde_with = { version = "3.12.0", features = ["base64", "hex", "macros", "std"] } # serde_with_macros = { version = "3.12.0", features = ["schemars_0_8"] } -sha2 = "0.10.8" slog = "2.7.0" slog-async = "2.8.0" tokio = { version = "1.43.0", features = ["full"] } @@ -57,4 +54,3 @@ tracing-core = "0.1.33" tracing-chrome = "0.7.2" tracing-slog = { git = "https://github.com/oxidecomputer/tracing-slog", default-features = false } uuid = { version = "1", features = [ "serde", "v4" ] } -x25519-dalek = { version = "2.0.1", features = ["serde", "static_secrets"] } diff --git a/api.json b/api.json index d7c3f25..ae26182 100644 --- a/api.json +++ b/api.json @@ -12,16 +12,6 @@ ], "summary": "Get registration info", "operationId": "get_registration_info", - "parameters": [ - { - "in": "query", - "name": "key", - "required": true, - "schema": { - "type": "string" - } - } - ], "responses": { "200": { "description": "successful operation", @@ -181,26 +171,26 @@ "RegistrationInfo": { "type": "object", "properties": { + "challenge": { + "type": "string" + }, + "expiration": { + "type": "string", + "format": "date-time" + }, "node_id": { "type": "string", "format": "uuid" }, - "nonce": { + "public_key": { "type": "string" - }, - "server_dh_pub": { - "type": "string" - }, - "timestamp": { - "type": "string", - "format": "date-time" } }, "required": [ + "challenge", + "expiration", "node_id", - "nonce", - "server_dh_pub", - "timestamp" + "public_key" ] }, "User": { diff --git a/controller/Cargo.toml b/controller/Cargo.toml index f51b59a..6b2f178 100644 --- a/controller/Cargo.toml +++ b/controller/Cargo.toml @@ -11,17 +11,13 @@ chrono.workspace = true clap.workspace = true dropshot.workspace = true ed25519-dalek.workspace = true -futures.workspace = true hex.workspace = true -hkdf.workspace = true http.workspace = true instrumentation = { path = "../instrumentation" } rand.workspace = true schemars.workspace = true serde.workspace = true -serde_json.workspace = true serde_with.workspace = true -sha2.workspace = true slog-async.workspace = true slog.workspace = true sqlx = { version = "0.8.3", default-features = false, features = [ @@ -32,7 +28,6 @@ trace-request = { path = "../trace-request" } tracing-slog.workspace = true tracing.workspace = true uuid.workspace = true -x25519-dalek.workspace = true [package.metadata.cargo-machete] ignored = ["http"] diff --git a/controller/src/context.rs b/controller/src/context.rs index 3d4d4e1..b99d559 100644 --- a/controller/src/context.rs +++ b/controller/src/context.rs @@ -1,20 +1,11 @@ use sqlx::postgres::PgPool; -use crate::node::NodeController; - pub struct ControllerContext { pub pg_pool: PgPool, - // pub node_controller: Mutex<NodeController>, - pub node_controller: NodeController, } impl ControllerContext { pub fn new(pg_pool: PgPool) -> ControllerContext { - // let node_controller = Mutex::new(NodeController::new(pg_pool.to_owned())); - let node_controller = NodeController::new(pg_pool.to_owned()); - ControllerContext { - pg_pool, - node_controller, - } + ControllerContext { pg_pool } } } diff --git a/controller/src/node/api.rs b/controller/src/node/api.rs index 63517a0..cf51771 100644 --- a/controller/src/node/api.rs +++ b/controller/src/node/api.rs @@ -1,28 +1,45 @@ -use dropshot::{endpoint, HttpError, HttpResponseOk, Query, RequestContext}; +use chrono::DateTime; +use dropshot::{endpoint, HttpError, HttpResponseOk, RequestContext}; use dropshot::{ApiDescription, ApiDescriptionRegisterError}; +use rand::RngCore; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use serde_with::base64::Base64; +use serde_with::base64::Crypt; use serde_with::serde_as; use trace_request::trace_request; -use x25519_dalek::PublicKey; +use uuid::Uuid; use std::sync::Arc; use crate::context::ControllerContext; -use crate::node::controller::RegistrationInfo; - -#[serde_as] -#[derive(Clone, Debug, Default, Deserialize, JsonSchema, Serialize)] -pub struct RegistrationParams { - #[serde_as(as = "serde_with::base64::Base64<serde_with::base64::UrlSafe>")] - #[schemars(with = "String")] - key: [u8; 32], -} pub fn register_api( api: &mut ApiDescription<Arc<ControllerContext>>, ) -> Result<(), ApiDescriptionRegisterError> { api.register(get_registration_info) + // api.register(register_node) +} + +#[serde_as] +#[derive(Deserialize, JsonSchema, Serialize)] +struct RegistrationInfo { + node_id: Uuid, + expiration: DateTime<chrono::Utc>, + + // #[serde_as(as = "Base64<serde_with::base64::BinHex, serde_with::formats::Unpadded>")] + // #[serde_as(as = "Base64<serde_with::base64::UrlSafe, serde_with::formats::Unpadded>")] + #[serde_as(as = "serde_with::base64::Base64")] + // #[serde_as(as = "serde_with::hex::Hex<serde_with::formats::Uppercase>")] + #[schemars(with = "String")] + challenge: [u8; 60], + + // #[serde_as(as = "serde_with::base64::Base64")] + #[serde_as(as = "serde_with::hex::Hex")] + // #[serde_as(as = "Base64<serde_with::base64::UrlSafe, serde_with::formats::Unpadded>")] + // #[serde_as(as = "serde_with::hex::Hex<serde_with::formats::Uppercase>")] + #[schemars(with = "String")] + public_key: [u8; 32], } /// Get registration info @@ -34,10 +51,20 @@ pub fn register_api( #[trace_request] async fn get_registration_info( rqctx: RequestContext<Arc<ControllerContext>>, - params: Query<RegistrationParams>, ) -> Result<HttpResponseOk<RegistrationInfo>, HttpError> { - let key = PublicKey::from(params.into_inner().key); - tracing::debug!("Registration info for public key: {:?}", key); - let info = rqctx.context().node_controller.new_registration(&key); + tracing::debug!("Registration info"); + + let rng = &mut rand::rngs::OsRng; + let mut challenge = [0u8; 60]; + rng.fill_bytes(&mut challenge); + let key = ed25519_dalek::SigningKey::generate(rng); + + let info = RegistrationInfo { + node_id: Uuid::new_v4(), + expiration: chrono::Utc::now(), + challenge, + public_key: key.verifying_key().to_bytes(), + }; + Ok(HttpResponseOk(info)) } diff --git a/controller/src/node/controller.rs b/controller/src/node/controller.rs deleted file mode 100644 index 1698dc7..0000000 --- a/controller/src/node/controller.rs +++ /dev/null @@ -1,179 +0,0 @@ -use chrono::DateTime; -use ed25519_dalek::ed25519::signature::SignerMut; -use hkdf::Hkdf; -use rand::Rng; -use schemars::JsonSchema; -use serde::{Deserialize, Serialize}; -use serde_with::serde_as; -use sha2::Sha256; -use sqlx::postgres::PgPool; -use uuid::Uuid; -use x25519_dalek::{PublicKey, StaticSecret}; - -use std::sync::{Arc, RwLock}; - -#[serde_as] -#[derive(Debug, Deserialize, JsonSchema, Serialize)] -pub struct RegistrationInfo { - node_id: Uuid, - challenge: Challenge, - - #[serde_as(as = "serde_with::base64::Base64")] - #[schemars(with = "String")] - challenge_signature: [u8; 64], -} - -#[serde_as] -#[derive(Debug, Deserialize, JsonSchema, Serialize)] -pub struct Challenge { - timestamp: DateTime<chrono::Utc>, - - #[serde_as(as = "serde_with::base64::Base64")] - #[schemars(with = "String")] - nonce: [u8; 32], - - #[serde_as(as = "serde_with::base64::Base64")] - #[schemars(with = "String")] - server_dh_pub: [u8; 32], - - params: ArgonParams, -} - -#[serde_as] -#[derive(Debug, Deserialize, JsonSchema, Serialize)] -struct ArgonParams { - iterations: u32, - memory_kb: u32, - threads: u32, -} - -pub struct NodeController { - _pg_pool: PgPool, - signing_key: RwLock<Option<Arc<SecretKey>>>, -} - -type SecretKey = [u8; 32]; -type Nonce256 = [u8; 32]; - -trait SecretKeyExt { - fn derive_dh_secret(&self, nonce: &Nonce256) -> StaticSecret; -} - -impl SecretKeyExt for SecretKey { - fn derive_dh_secret(&self, nonce: &Nonce256) -> StaticSecret { - let hk = Hkdf::<Sha256>::new(Some(nonce), self); - let mut out = [0u8; 32]; - hk.expand(b"x25519-secret", &mut out).unwrap(); - StaticSecret::from(out) - } -} - -impl RegistrationInfo { - pub fn from(server_secret: &SecretKey, client_dh_public_key: &PublicKey) -> RegistrationInfo { - let mut rng = rand::thread_rng(); - let nonce = rng.gen(); - - let server_dh_secret = server_secret.derive_dh_secret(&nonce); - let _shared_secret = server_dh_secret.diffie_hellman(client_dh_public_key); - let server_dh_public_key = PublicKey::from(&server_dh_secret); - - let timestamp = chrono::Utc::now(); - - let params = ArgonParams { - iterations: 10, - memory_kb: 220 * 1024, // 220 MiB - threads: 4, - }; - - let challenge = Challenge { - timestamp, - nonce, - server_dh_pub: server_dh_public_key.to_bytes(), - params, - }; - - let challenge_json = serde_json::to_vec(&challenge).unwrap(); - - let mut sk = ed25519_dalek::SigningKey::from_bytes(server_secret); - let sig = sk.sign(challenge_json.as_slice()); - - let info = RegistrationInfo { - node_id: Uuid::new_v4(), - challenge, - challenge_signature: sig.to_bytes(), - }; - - tracing::debug!("Info: {:?}", info); - info - } -} - -impl NodeController { - pub fn new(pg_pool: PgPool) -> NodeController { - NodeController { - _pg_pool: pg_pool, - signing_key: RwLock::new(None), - } - } - - pub fn new_registration(&self, client_dh_public_key: &PublicKey) -> RegistrationInfo { - let server_secret = self.master_key(); - RegistrationInfo::from(&server_secret, client_dh_public_key) - } - - fn master_key(&self) -> Arc<SecretKey> { - self.signing_key - .write() - .unwrap() - .get_or_insert_with(|| { - tracing::debug!("Generating key"); - Arc::new(rand::rngs::OsRng.gen()) - }) - .clone() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[tokio::test] - async fn test_registration_info() { - let client_key: SecretKey = rand::rngs::OsRng.gen(); - let server_key = rand::rngs::OsRng.gen(); - let client_public_key = PublicKey::from(client_key); - - let info = RegistrationInfo::from(&server_key, &client_public_key); - let info2 = RegistrationInfo::from(&server_key, &client_public_key); - - // Randomness - assert_ne!(info.node_id, info2.node_id, "Node ID should be unique"); - assert_ne!( - info.challenge.nonce, info2.challenge.nonce, - "Nonce should be unique" - ); - assert_ne!( - info.challenge.server_dh_pub, info2.challenge.server_dh_pub, - "Server public key derived from nonce should be unique" - ); - - // Timestamp - let now = chrono::Utc::now(); - assert!( - info.challenge.timestamp <= now, - "Timestamp is in the future" - ); - assert!( - info.challenge.timestamp >= now - chrono::Duration::milliseconds(5), - "Timestamp is too old" - ); - - // Public key - let server_dh_secret = server_key.derive_dh_secret(&info.challenge.nonce); - let server_dh_public_key = PublicKey::from(&server_dh_secret).to_bytes(); - assert_eq!( - info.challenge.server_dh_pub, server_dh_public_key, - "Server public key should be derived from nonce" - ); - } -} diff --git a/controller/src/node/mod.rs b/controller/src/node/mod.rs index c67ce0c..f35db4c 100644 --- a/controller/src/node/mod.rs +++ b/controller/src/node/mod.rs @@ -1,5 +1,3 @@ mod api; -mod controller; pub use self::api::register_api; -pub(crate) use self::controller::NodeController;