pub mod attestation;
pub mod chain;
pub mod custody;
#[cfg(feature = "async-transport")]
pub mod egress;
#[cfg(feature = "egress-config")]
pub mod egress_config;
pub mod kms_policy;
pub mod kms_recipient;
#[cfg(feature = "async-transport")]
pub mod mesh;
mod noise;
pub mod staging;
pub use noise::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum ClientMessage {
RequestAttestation,
Data { id: u64, payload: Vec<u8> },
Control {
payload: Vec<u8>,
signature: Vec<u8>,
},
GetControlNonce,
OpenStream { id: u64, payload: Vec<u8> },
StreamData { id: u64, payload: Vec<u8> },
StreamClose { id: u64, half: StreamHalf },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum StreamHalf {
Write,
Both,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum ServerMessage {
Attestation {
data: Vec<u8>,
control_nonce: [u8; 32],
},
ControlNonce { nonce: [u8; 32] },
Data { id: u64, payload: Vec<u8> },
Error { id: u64, message: String },
ControlResult { success: bool, message: String },
StreamData { id: u64, payload: Vec<u8> },
StreamClose { id: u64 },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RekeyParams {
pub new_public_key: Vec<u8>,
pub new_key_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "command")]
pub enum ControlCommand {
PrepareUpgrade {
payload: Vec<u8>,
payload_signature: Vec<u8>,
#[serde(default, skip_serializing_if = "Option::is_none")]
rekey: Option<RekeyParams>,
nonce: [u8; 32],
},
RevokeUpgrade {
payload: Vec<u8>,
payload_signature: Vec<u8>,
rollback: bool,
nonce: [u8; 32],
},
}
#[cfg(feature = "async-transport")]
pub async fn submit_chain_link<S>(
stream: &mut S,
link: &chain::ChainLink,
ack_timeout: std::time::Duration,
) -> Result<u8, Box<dyn std::error::Error + Send + Sync>>
where
S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin,
{
use tokio::io::{AsyncReadExt, AsyncWriteExt};
let mut link_bytes = Vec::with_capacity(1024);
ciborium::ser::into_writer(link, &mut link_bytes)?;
let len: u32 = link_bytes
.len()
.try_into()
.map_err(|_| "chain link too large to encode as u32-prefixed frame")?;
stream.write_all(&len.to_be_bytes()).await?;
const VSOCK_CHUNK: usize = 32 * 1024;
for chunk in link_bytes.chunks(VSOCK_CHUNK) {
stream.write_all(chunk).await?;
}
stream.shutdown().await?;
let mut ack = [0u8; 1];
tokio::time::timeout(ack_timeout, stream.read_exact(&mut ack)).await??;
Ok(ack[0])
}
pub const CHAIN_LINK_ACK: u8 = 0x06;
#[cfg(test)]
mod tests {
use super::*;
fn cbor_round_trip<T>(value: &T) -> T
where
T: serde::Serialize + serde::de::DeserializeOwned,
{
let mut buf = Vec::new();
ciborium::into_writer(value, &mut buf).expect("serialize");
ciborium::from_reader(buf.as_slice()).expect("deserialize")
}
#[test]
fn client_open_stream_round_trip() {
let msg = ClientMessage::OpenStream {
id: 11,
payload: b"GET /ws HTTP/1.1\r\n\r\n".to_vec(),
};
let back = cbor_round_trip(&msg);
match back {
ClientMessage::OpenStream { id, payload } => {
assert_eq!(id, 11);
assert_eq!(payload, b"GET /ws HTTP/1.1\r\n\r\n".to_vec());
}
_ => panic!("wrong variant"),
}
}
#[test]
fn client_stream_data_round_trip() {
let msg = ClientMessage::StreamData {
id: 42,
payload: vec![1, 2, 3, 4, 5],
};
let back = cbor_round_trip(&msg);
match back {
ClientMessage::StreamData { id, payload } => {
assert_eq!(id, 42);
assert_eq!(payload, vec![1, 2, 3, 4, 5]);
}
_ => panic!("wrong variant"),
}
}
#[test]
fn client_stream_close_round_trip() {
for half in [StreamHalf::Write, StreamHalf::Both] {
let msg = ClientMessage::StreamClose { id: 7, half };
let back = cbor_round_trip(&msg);
match back {
ClientMessage::StreamClose { id, half: got } => {
assert_eq!(id, 7);
assert_eq!(got, half);
}
_ => panic!("wrong variant"),
}
}
}
#[test]
fn server_stream_data_round_trip() {
let msg = ServerMessage::StreamData {
id: 99,
payload: vec![0xde, 0xad, 0xbe, 0xef],
};
let back = cbor_round_trip(&msg);
match back {
ServerMessage::StreamData { id, payload } => {
assert_eq!(id, 99);
assert_eq!(payload, vec![0xde, 0xad, 0xbe, 0xef]);
}
_ => panic!("wrong variant"),
}
}
#[test]
fn server_stream_close_round_trip() {
let msg = ServerMessage::StreamClose { id: 13 };
let back = cbor_round_trip(&msg);
match back {
ServerMessage::StreamClose { id } => assert_eq!(id, 13),
_ => panic!("wrong variant"),
}
}
#[test]
fn existing_data_variants_still_round_trip() {
let req = ClientMessage::Data {
id: 1,
payload: b"hello".to_vec(),
};
let back = cbor_round_trip(&req);
match back {
ClientMessage::Data { id, payload } => {
assert_eq!(id, 1);
assert_eq!(payload, b"hello".to_vec());
}
_ => panic!("wrong variant"),
}
let resp = ServerMessage::Data {
id: 1,
payload: b"world".to_vec(),
};
let back = cbor_round_trip(&resp);
match back {
ServerMessage::Data { id, payload } => {
assert_eq!(id, 1);
assert_eq!(payload, b"world".to_vec());
}
_ => panic!("wrong variant"),
}
}
#[test]
fn prepare_upgrade_round_trip() {
let cmd = ControlCommand::PrepareUpgrade {
payload: vec![1, 2, 3],
payload_signature: vec![0xde; 64],
rekey: Some(RekeyParams {
new_public_key: vec![0xAB; 32],
new_key_id: "arn:aws:kms:us-east-1:123:key/abc".into(),
}),
nonce: [0x42u8; 32],
};
let back: ControlCommand = cbor_round_trip(&cmd);
match back {
ControlCommand::PrepareUpgrade {
payload,
payload_signature,
rekey,
nonce,
} => {
assert_eq!(payload, vec![1, 2, 3]);
assert_eq!(payload_signature, vec![0xde; 64]);
let rk = rekey.expect("rekey should be Some");
assert_eq!(rk.new_public_key, vec![0xAB; 32]);
assert_eq!(rk.new_key_id, "arn:aws:kms:us-east-1:123:key/abc");
assert_eq!(nonce, [0x42u8; 32]);
}
_ => panic!("wrong variant"),
}
}
#[test]
fn prepare_upgrade_stateless_round_trip() {
let cmd = ControlCommand::PrepareUpgrade {
payload: vec![0xAA],
payload_signature: vec![0xBB; 64],
rekey: None,
nonce: [0x01u8; 32],
};
let back: ControlCommand = cbor_round_trip(&cmd);
match back {
ControlCommand::PrepareUpgrade { rekey, .. } => {
assert!(rekey.is_none(), "stateless: rekey should be None");
}
_ => panic!("wrong variant"),
}
}
#[test]
fn revoke_upgrade_round_trip() {
let cmd = ControlCommand::RevokeUpgrade {
payload: vec![0xCC; 8],
payload_signature: vec![0xDD; 64],
rollback: true,
nonce: [0x99u8; 32],
};
let back: ControlCommand = cbor_round_trip(&cmd);
match back {
ControlCommand::RevokeUpgrade {
payload,
payload_signature,
rollback,
nonce,
} => {
assert_eq!(payload, vec![0xCC; 8]);
assert_eq!(payload_signature, vec![0xDD; 64]);
assert!(rollback);
assert_eq!(nonce, [0x99u8; 32]);
}
_ => panic!("wrong variant"),
}
}
#[test]
fn get_control_nonce_round_trip() {
let msg = ClientMessage::GetControlNonce;
let back = cbor_round_trip(&msg);
assert!(matches!(back, ClientMessage::GetControlNonce));
}
#[test]
fn server_control_nonce_round_trip() {
let nonce = [0x77u8; 32];
let msg = ServerMessage::ControlNonce { nonce };
let back = cbor_round_trip(&msg);
match back {
ServerMessage::ControlNonce { nonce: got } => assert_eq!(got, nonce),
_ => panic!("wrong variant"),
}
}
#[test]
fn prepare_upgrade_json_field_names() {
let cmd = ControlCommand::PrepareUpgrade {
payload: vec![1],
payload_signature: vec![2; 64],
rekey: Some(RekeyParams {
new_public_key: vec![3],
new_key_id: "k1".into(),
}),
nonce: [0u8; 32],
};
let v = serde_json::to_value(&cmd).unwrap();
assert_eq!(v["command"], "PrepareUpgrade");
assert!(v.get("payload").is_some());
assert!(v.get("payload_signature").is_some());
assert!(v.get("rekey").is_some());
assert_eq!(v["rekey"]["new_key_id"], "k1");
assert!(v.get("nonce").is_some());
}
#[test]
fn revoke_upgrade_json_field_names() {
let cmd = ControlCommand::RevokeUpgrade {
payload: vec![1],
payload_signature: vec![2; 64],
rollback: false,
nonce: [0u8; 32],
};
let v = serde_json::to_value(&cmd).unwrap();
assert_eq!(v["command"], "RevokeUpgrade");
assert!(v.get("payload").is_some());
assert!(v.get("payload_signature").is_some());
assert_eq!(v["rollback"], false);
assert!(v.get("nonce").is_some());
}
}