From f301684c3ac55bda40acdafe2bcd347f247744b7 Mon Sep 17 00:00:00 2001 From: core Date: Wed, 29 Mar 2023 17:42:16 -0400 Subject: [PATCH] finish porting dnapi --- .idea/trifid.iml | 1 + dnapi-rs/src/client_blocking.rs | 68 +++++++++++++++++++++++++++++++-- dnapi-rs/src/message.rs | 5 +++ 3 files changed, 70 insertions(+), 4 deletions(-) diff --git a/.idea/trifid.iml b/.idea/trifid.iml index bef05f9..ecc578a 100644 --- a/.idea/trifid.iml +++ b/.idea/trifid.iml @@ -5,6 +5,7 @@ + diff --git a/dnapi-rs/src/client_blocking.rs b/dnapi-rs/src/client_blocking.rs index f568f8e..8b04920 100644 --- a/dnapi-rs/src/client_blocking.rs +++ b/dnapi-rs/src/client_blocking.rs @@ -6,10 +6,10 @@ use log::{debug, error}; use reqwest::StatusCode; use url::Url; use trifid_pki::cert::serialize_ed25519_public; -use trifid_pki::ed25519_dalek::{Signer, SigningKey}; +use trifid_pki::ed25519_dalek::{Signature, Signer, SigningKey, Verifier}; use crate::credentials::{Credentials, ed25519_public_keys_from_pem}; -use crate::crypto::new_keys; -use crate::message::{ENDPOINT_V1, ENROLL_ENDPOINT, EnrollRequest, EnrollResponse, RequestV1, RequestWrapper}; +use crate::crypto::{new_keys, nonce}; +use crate::message::{CHECK_FOR_UPDATE, CheckForUpdateResponseWrapper, DO_UPDATE, DoUpdateRequest, DoUpdateResponse, ENDPOINT_V1, ENROLL_ENDPOINT, EnrollRequest, EnrollResponse, RequestV1, RequestWrapper, SignedResponseWrapper}; /// A type alias to abstract return types pub type NebulaConfig = Vec; @@ -100,6 +100,66 @@ impl Client { Ok((r.config, dh_privkey_pem, creds, meta)) } + /// Send a signed message to the DNClient API to learn if there is a new configuration available. + pub fn check_for_update(&self, creds: &Credentials) -> Result> { + let body = self.post_dnclient(CHECK_FOR_UPDATE, &[], &creds.host_id, creds.counter, &creds.ed_privkey)?; + + let result: CheckForUpdateResponseWrapper = serde_json::from_slice(&body)?; + + Ok(result.data.update_available) + } + + /// Send a signed message to the DNClient API to fetch the new configuration update. During this call a new + /// DH X25519 keypair is generated for the new Nebula certificate as well as a new Ed25519 keypair for DNClient API + /// communication. On success it returns the new config, a Nebula private key PEM to be inserted into the config + /// and new DNClient API credentials + pub fn do_update(&self, creds: &Credentials) -> Result<(NebulaConfig, DHPrivateKeyPEM, Credentials), Box> { + let (dh_pubkey_pem, dh_privkey_pem, ed_pubkey, ed_privkey) = new_keys(); + + let update_keys = DoUpdateRequest { + ed_pubkey_pem: serialize_ed25519_public(ed_pubkey.as_bytes()), + dh_pubkey_pem, + nonce: nonce().to_vec(), + }; + + let update_keys_blob = serde_json::to_vec(&update_keys)?; + + let resp = self.post_dnclient(DO_UPDATE, &update_keys_blob, &creds.host_id, creds.counter, &creds.ed_privkey)?; + + let result_wrapper: SignedResponseWrapper = serde_json::from_slice(&resp)?; + + let mut valid = false; + + for ca_pubkey in creds.trusted_keys { + if ca_pubkey.verify(&result_wrapper.data.message, &Signature::from_slice(&result_wrapper.data.signature)?).is_ok() { + valid = true; + break; + } + } + + if !valid { + return Err("Failed to verify signed API result".into()) + } + + let result: DoUpdateResponse = serde_json::from_slice(&result_wrapper.data.message)?; + + if result.nonce != update_keys.nonce { + error!("nonce mismatch between request {:x?} and response {:x?}", result.nonce, update_keys.nonce); + return Err(format!("nonce mismatch between request and response").into()) + } + + let trusted_keys = ed25519_public_keys_from_pem(&result.trusted_keys)?; + + let new_creds = Credentials { + host_id: creds.host_id.clone(), + ed_privkey, + counter: result.counter, + trusted_keys, + }; + + Ok((result.config, dh_privkey_pem, new_creds)) + } + /// Wraps and signs the given req_type and value, and then makes the API call. /// On success, returns the response body. /// # Errors @@ -107,7 +167,7 @@ impl Client { /// - serialization in any step fails /// - if the server_url is invalid /// - if the request could not be sent - pub fn post_dnclient(&self, req_type: &str, value: &[u8], host_id: &str, counter: u32, ed_privkey: SigningKey) -> Result, Box> { + pub fn post_dnclient(&self, req_type: &str, value: &[u8], host_id: &str, counter: u32, ed_privkey: &SigningKey) -> Result, Box> { let encoded_msg = serde_json::to_string(&RequestWrapper { message_type: req_type.to_string(), value: value.to_vec(), diff --git a/dnapi-rs/src/message.rs b/dnapi-rs/src/message.rs index 31f4a29..b262105 100644 --- a/dnapi-rs/src/message.rs +++ b/dnapi-rs/src/message.rs @@ -6,6 +6,11 @@ use serde::{Serialize, Deserialize}; /// The version 1 `DNClient` API endpoint pub const ENDPOINT_V1: &str = "/v1/dnclient"; +/// The CheckForUpdate message type +pub const CHECK_FOR_UPDATE: &str = "CheckForUpdate"; +/// The DoUpdate message type +pub const DO_UPDATE: &str = "DoUpdate"; + base64_serde_type!(Base64Standard, base64::engine::general_purpose::STANDARD); #[derive(Serialize, Deserialize)]