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)]