use blake2::{
digest::{consts::U12, Mac},
Blake2bMac, Blake2bMac512,
};
use clap::ValueEnum;
use crypto_secretstream as secret_stream;
use kem::{Decapsulate, Encapsulate};
use rand::rngs::OsRng;
use rust_base58::{FromBase58, ToBase58};
use serde::{Deserialize, Serialize};
use strum_macros::IntoStaticStr;
use subtle::ConstantTimeEq;
use crate::transport::{Transport, TransportCaller, TransportListener};
#[derive(Clone)]
pub enum Party {
Controller,
Breakpoint,
}
#[derive(ValueEnum, Clone, Debug)]
pub enum AuthVersion {
DualPub,
}
#[derive(Clone)]
pub struct XWingKeypair {
pub public_key: x_wing::EncapsulationKey,
pub private_key: x_wing::DecapsulationKey,
}
impl XWingKeypair {
#[allow(clippy::new_without_default)]
pub fn new() -> XWingKeypair {
let (decaps, encaps) = x_wing::generate_key_pair_from_os_rng();
XWingKeypair {
public_key: encaps,
private_key: decaps,
}
}
pub fn from_decaps(decaps: &x_wing::DecapsulationKey) -> XWingKeypair {
XWingKeypair {
public_key: decaps.encapsulation_key(),
private_key: decaps.clone(),
}
}
}
type Blake2KeyDigests = Blake2bMac<U12>;
#[cfg(test)]
const DIGEST_BYTE_LEN: usize = 12;
const DIGEST_MIN_CHARS: usize = 12;
const DIGEST_MAX_CHARS: usize = 17;
pub trait IdString: Sized {
fn id_string(&self) -> String;
}
#[derive(Clone)]
pub struct ControllerId(String);
impl ControllerId {
pub fn from_key(public_key: &x_wing::EncapsulationKey) -> ControllerId {
ControllerId(pubkey_digest(public_key, "controller"))
}
pub fn from_received_string(id_string: &str) -> Result<ControllerId, String> {
validate_id_format(id_string)?;
Ok(ControllerId(id_string.to_owned()))
}
}
impl IdString for ControllerId {
fn id_string(&self) -> String {
self.0.to_owned()
}
}
#[derive(Clone)]
pub struct BreakpointId(String);
impl BreakpointId {
pub fn from_key(public_key: &x_wing::EncapsulationKey) -> BreakpointId {
BreakpointId(pubkey_digest(public_key, "breakpoint"))
}
pub fn from_received_string(id_string: &str) -> Result<BreakpointId, String> {
validate_id_format(id_string)?;
Ok(BreakpointId(id_string.to_owned()))
}
}
impl IdString for BreakpointId {
fn id_string(&self) -> String {
self.0.to_owned()
}
}
fn validate_id_format(id: &str) -> Result<(), String> {
id.from_base58()
.map_err(|err| format!("Not valid Base58: {err}"))?;
if id.len() < DIGEST_MIN_CHARS {
Err(format!(
"Too short (less than {DIGEST_MIN_CHARS} characters long)"
))
} else if id.len() > DIGEST_MAX_CHARS {
Err(format!(
"Too long (more than {DIGEST_MAX_CHARS} characters long)"
))
} else {
Ok(())
}
}
fn pubkey_digest(public_key: &x_wing::EncapsulationKey, party: &str) -> String {
let pub_raw = public_key.as_bytes();
let domain = format!("breakmancer.dual-pub.pubkey-digest.{party}");
let mut hasher =
Blake2KeyDigests::new_with_salt_and_personal(domain.as_bytes(), &[], &[]).unwrap();
hasher.update(&pub_raw);
let digest = hasher.finalize().into_bytes();
digest.to_base58()
}
fn ids_match<D: IdString>(trusted_id: &D, received_key_id: &D) -> bool {
let trusted_digest = trusted_id.id_string();
let received_key_digest = received_key_id.id_string();
let trusted_digest = trusted_digest.replace(['l', 'I'], "1").trim().to_string();
trusted_digest
.as_bytes()
.ct_eq(received_key_digest.as_bytes())
.into()
}
const PROTO_DUAL_PUB: &str = "dual-pub";
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct HandshakeRejected {
reason: String,
permanent: bool,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct BreakpointHello {
protocol: String,
#[serde(with = "serde_bytes")]
break_pub: [u8; x_wing::ENCAPSULATION_KEY_SIZE],
expecting_controller_id: String,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct ControllerHello {
#[serde(with = "serde_bytes")]
ctrl_pub: [u8; x_wing::ENCAPSULATION_KEY_SIZE],
#[serde(with = "serde_bytes")]
setup_secret_part1: [u8; x_wing::CIPHERTEXT_SIZE],
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct BreakpointSetupFinalize {
#[serde(with = "serde_bytes")]
setup_secret_part2: [u8; x_wing::CIPHERTEXT_SIZE],
#[serde(with = "serde_bytes")]
cipher_header: [u8; secret_stream::Header::BYTES],
encrypted_intro: Vec<u8>,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct ControllerSetupFinalize {
#[serde(with = "serde_bytes")]
cipher_header: [u8; secret_stream::Header::BYTES],
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct EncryptedMsg {
#[serde(with = "serde_bytes")]
encrypted: Vec<u8>,
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, PartialEq, Serialize, Deserialize, IntoStaticStr)]
enum InsecureMsg {
HandshakeRejected(HandshakeRejected),
BreakpointHello(BreakpointHello),
ControllerHello(ControllerHello),
BreakpointSetupFinalize(BreakpointSetupFinalize),
ControllerSetupFinalize(ControllerSetupFinalize),
EncryptedMsg(EncryptedMsg),
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct BreakpointIntro {
pub which: Option<String>,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct Command {
pub command: String,
pub seq: u32,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct OutputLine {
#[serde(with = "serde_bytes")]
pub bytes: Vec<u8>,
pub gender: OutputType,
pub seq: u32,
}
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
pub enum OutputType {
Stdout,
Stderr,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct Finished {
pub exit_code: Option<i32>,
pub seq: u32,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct TerminateCmd {
pub seq: u32,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct ExitBreak {}
#[derive(Debug, PartialEq, Serialize, Deserialize, IntoStaticStr)]
pub enum SecureMsg {
BreakpointIntro(BreakpointIntro),
Command(Command),
OutputLine(OutputLine),
Finished(Finished),
TerminateCmd(TerminateCmd),
ExitBreak(ExitBreak),
}
fn derive_stream_keys_dual_pub(
pubkey_controller: &x_wing::EncapsulationKey,
pubkey_breakpoint: &x_wing::EncapsulationKey,
setup_secret_controller: &x_wing::SharedSecret,
setup_secret_breakpoint: &x_wing::SharedSecret,
) -> (secret_stream::Key, secret_stream::Key) {
let mut in_raw = Vec::new();
in_raw.extend(&pubkey_controller.as_bytes());
in_raw.extend(&pubkey_breakpoint.as_bytes());
in_raw.extend(*setup_secret_controller);
in_raw.extend(*setup_secret_breakpoint);
let key_len = secret_stream::Key::BYTES;
let domain = "breakmancer.dual-pub.stream-keys";
let mut hasher =
Blake2bMac512::new_with_salt_and_personal(domain.as_bytes(), &[], &[]).unwrap();
hasher.update(&in_raw);
let key_bytes = hasher.finalize().into_bytes();
assert_eq!(key_bytes.len(), 2 * key_len);
let controller_stream_key = secret_stream::Key::try_from(&key_bytes[0..key_len]).unwrap();
let breakpoint_stream_key = secret_stream::Key::try_from(&key_bytes[key_len..]).unwrap();
(controller_stream_key, breakpoint_stream_key)
}
fn serialize<D>(data: &D) -> Vec<u8>
where
D: Serialize,
{
serde_cbor::to_vec(&data).unwrap()
}
fn deserialize<'a, T>(bytes: &'a [u8]) -> Result<T, String>
where
T: Deserialize<'a>,
{
serde_cbor::from_slice(bytes).map_err(|err| format!("{err}"))
}
fn encrypt_message(msg: &SecureMsg, cipher_tx: &mut secret_stream::PushStream) -> EncryptedMsg {
let mut in_place_buffer = serialize(msg);
cipher_tx
.push(&mut in_place_buffer, &[], secret_stream::Tag::Message)
.unwrap();
EncryptedMsg {
encrypted: in_place_buffer,
}
}
fn decrypt_message(
msg: &EncryptedMsg,
cipher_rx: &mut secret_stream::PullStream,
) -> Result<SecureMsg, String> {
let mut in_place_buffer = msg.encrypted.clone();
let tag = cipher_rx
.pull(&mut in_place_buffer, &[])
.map_err(|err| format!("Unable to decrypt message: {err}"))?;
if tag != secret_stream::Tag::Message {
Err(format!("Unexpected tag in decryption stream: {tag:?}"))?;
}
deserialize(&in_place_buffer).map_err(|err| format!("Could not parse message data: {err}"))
}
async fn send_insecure(transport: &mut Transport, msg: &InsecureMsg) -> Result<(), String> {
transport.send_raw(&serialize(msg)).await
}
async fn receive_insecure(transport: &mut Transport) -> Result<Option<InsecureMsg>, String> {
let raw = match transport.receive_raw().await? {
None => return Ok(None),
Some(raw) => raw,
};
deserialize(&raw).map_err(|err| format!("Could not parse message body: {err}"))
}
pub struct Connection {
transport: Transport,
cipher_tx: secret_stream::PushStream,
cipher_rx: secret_stream::PullStream,
}
impl Connection {
pub async fn send(&mut self, msg: SecureMsg) -> Result<(), String> {
let encrypted = InsecureMsg::EncryptedMsg(encrypt_message(&msg, &mut self.cipher_tx));
send_insecure(&mut self.transport, &encrypted).await
}
pub async fn recv(&mut self) -> Result<Option<SecureMsg>, String> {
match receive_insecure(&mut self.transport).await? {
None => Ok(None),
Some(InsecureMsg::EncryptedMsg(enc)) => {
Ok(Some(decrypt_message(&enc, &mut self.cipher_rx)?))
}
Some(clear_msg) => Err(format!("Unexpected insecure message type: {clear_msg:?}")),
}
}
}
pub async fn controller_open_connection(
controller_keypair: XWingKeypair,
transport_listener: &mut TransportListener,
) -> Result<ControllerReceivedHello, String> {
let mut transport = transport_listener.accept_connection().await?;
let breakpoint_hello = match receive_insecure(&mut transport).await {
Ok(None) => Err(String::from("Breakpoint hung up before BreakpointHello"))?,
Ok(Some(InsecureMsg::BreakpointHello(hello))) => hello,
Ok(Some(wrong)) => Err(format!(
"Unexpected message type when waiting for BreakpointHello: {}",
Into::<&str>::into(wrong)
))?,
Err(err) => Err(format!(
"Couldn't receive data when waiting for a BreakpointHello: {err}"
))?,
};
if breakpoint_hello.protocol != PROTO_DUAL_PUB {
Err(format!(
"Unexpected protocol field from breakpoint: {}",
breakpoint_hello.protocol
))?;
}
let breakpoint_pub_key: x_wing::EncapsulationKey = (&breakpoint_hello.break_pub).into();
let breakpoint_expecting_id =
ControllerId::from_received_string(&breakpoint_hello.expecting_controller_id)?;
let ctrl_id = ControllerId::from_key(&controller_keypair.public_key);
if !ids_match(&ctrl_id, &breakpoint_expecting_id) {
let err_msg = "Breakpoint sent wrong expected controller verification string, \
exiting early. (Breakpoint had outdated connection string?)";
send_insecure(
&mut transport,
&InsecureMsg::HandshakeRejected(HandshakeRejected {
reason: String::from(
"Controller doesn't match expected verification string sent by breakpoint.",
),
permanent: true,
}),
)
.await
.map_err(|err| format!("Unable to send rejection message. Rejection was: {err}"))?;
Err(err_msg)?;
}
Ok(ControllerReceivedHello {
controller_keypair,
transport,
breakpoint_pub_unverified: breakpoint_pub_key,
})
}
pub struct ControllerReceivedHello {
controller_keypair: XWingKeypair,
transport: Transport,
breakpoint_pub_unverified: x_wing::EncapsulationKey,
}
impl ControllerReceivedHello {
pub async fn verify_breakpoint_and_complete_setup(
mut self,
expected_breakpoint_id: &BreakpointId,
) -> Result<(Connection, BreakpointIntro), String> {
let received_id = BreakpointId::from_key(&self.breakpoint_pub_unverified);
if !ids_match(expected_breakpoint_id, &received_id) {
let err_msg = "Breakpoint verification string did not match. \
Is it possible that you have multiple breakpoints attempting \
to connect here at the same time and entered the verification \
string from the wrong one?";
send_insecure(
&mut self.transport,
&InsecureMsg::HandshakeRejected(HandshakeRejected {
reason: String::from(
"Breakpoint sent a public key that doesn't match \
the verification string the user entered.",
),
permanent: false, }),
)
.await
.map_err(|err| format!("Unable to send rejection message. Rejection was: {err}"))?;
return Err(err_msg.to_string());
}
let breakpoint_pubkey = self.breakpoint_pub_unverified;
let (controller_secret_encap, controller_secret) = breakpoint_pubkey
.encapsulate(&mut rand::rngs::OsRng)
.map_err(|err| format!("Failed to create controller secret: {err}"))?;
send_insecure(
&mut self.transport,
&InsecureMsg::ControllerHello(ControllerHello {
ctrl_pub: self.controller_keypair.public_key.as_bytes(),
setup_secret_part1: controller_secret_encap.as_bytes(),
}),
)
.await
.map_err(|err| format!("Couldn't respond to breakpoint's hello: {err}"))?;
let breakpoint_finalize = match receive_insecure(&mut self.transport).await {
Ok(None) => Err(String::from(
"Breakpoint hung up before BreakpointSetupFinalize",
))?,
Ok(Some(InsecureMsg::BreakpointSetupFinalize(finalize))) => finalize,
Ok(Some(wrong)) => Err(format!(
"Unexpected message type when waiting for BreakpointSetupFinalize: {}",
Into::<&str>::into(wrong)
))?,
Err(err) => Err(format!(
"Couldn't receive data when waiting for a BreakpointSetupFinalize: {err}"
))?,
};
let breakpoint_secret_encap: x_wing::Ciphertext =
(&breakpoint_finalize.setup_secret_part2).into();
let breakpoint_secret = self
.controller_keypair
.private_key
.decapsulate(&breakpoint_secret_encap)
.map_err(|err| {
format!("Unable to decrypt breakpoint's half of the setup secret: {err}")
})?;
let (controller_stream_key, breakpoint_stream_key) = derive_stream_keys_dual_pub(
&self.controller_keypair.public_key,
&breakpoint_pubkey,
&controller_secret,
&breakpoint_secret,
);
let mut rx_stream = secret_stream::PullStream::init(
secret_stream::Header::from(breakpoint_finalize.cipher_header),
&breakpoint_stream_key,
);
let maybe_intro = decrypt_message(
&EncryptedMsg {
encrypted: breakpoint_finalize.encrypted_intro,
},
&mut rx_stream,
)
.map_err(|err| format!("Could not decrypt breakpoint introduction: {err}"))?;
let breakpoint_intro = match maybe_intro {
SecureMsg::BreakpointIntro(intro) => intro,
_ => Err("Unexpected message type for breakpoint intro.")?,
};
let (tx_header, tx_stream) = secret_stream::PushStream::init(OsRng, &controller_stream_key);
send_insecure(
&mut self.transport,
&InsecureMsg::ControllerSetupFinalize(ControllerSetupFinalize {
cipher_header: *tx_header.as_ref(),
}),
)
.await
.map_err(|err| format!("Unable to send controller setup finalization: {err}"))?;
let secured_conn = Connection {
transport: self.transport,
cipher_tx: tx_stream,
cipher_rx: rx_stream,
};
Ok((secured_conn, breakpoint_intro))
}
}
#[derive(Debug)]
pub struct BreakpointConnectError {
pub message: String,
pub permanent: bool,
}
impl From<String> for BreakpointConnectError {
fn from(message: String) -> Self {
Self {
message,
permanent: false,
}
}
}
#[allow(clippy::too_many_lines)]
pub async fn breakpoint_open_connection(
breakpoint_keypair: &XWingKeypair,
transport_caller: &TransportCaller,
expected_controller_id: &ControllerId,
which: Option<String>,
) -> Result<BreakpointProducedId, BreakpointConnectError> {
let mut transport = transport_caller.new_connection().await?;
send_insecure(
&mut transport,
&InsecureMsg::BreakpointHello(BreakpointHello {
protocol: PROTO_DUAL_PUB.to_owned(),
break_pub: breakpoint_keypair.public_key.as_bytes(),
expecting_controller_id: expected_controller_id.id_string(),
}),
)
.await
.map_err(|err| format!("Unable to send hello: {err}"))?;
let breakpoint_id = BreakpointId::from_key(&breakpoint_keypair.public_key);
Ok(BreakpointProducedId {
breakpoint_keypair: breakpoint_keypair.clone(),
expected_controller_id: expected_controller_id.clone(),
which,
transport,
breakpoint_id,
})
}
pub struct BreakpointProducedId {
breakpoint_keypair: XWingKeypair,
expected_controller_id: ControllerId,
which: Option<String>,
transport: Transport,
pub breakpoint_id: BreakpointId,
}
impl BreakpointProducedId {
pub async fn continue_after_id_printed(mut self) -> Result<Connection, BreakpointConnectError> {
let controller_hello = match receive_insecure(&mut self.transport).await {
Ok(None) => Err(String::from("Controller hung up before ControllerHello"))?,
Ok(Some(InsecureMsg::ControllerHello(hello))) => hello,
Ok(Some(InsecureMsg::HandshakeRejected(HandshakeRejected { reason, permanent }))) => {
Err(BreakpointConnectError {
message: format!("Controller rejected connection: {reason}"),
permanent,
})?
}
Ok(Some(wrong)) => Err(format!(
"Unexpected message type when waiting for ControllerHello: {}",
Into::<&str>::into(wrong)
))?,
Err(err) => Err(format!(
"Couldn't receive data when waiting for a ControllerHello: {err}"
))?,
};
let unverified_controller_pubkey: x_wing::EncapsulationKey =
(&controller_hello.ctrl_pub).into();
let received_id = ControllerId::from_key(&unverified_controller_pubkey);
if !ids_match(&self.expected_controller_id, &received_id) {
Err("Controller verification string did not match. \
Is it possible that the controller has since been \
restarted? (This would mean it is using a new set of keys.)"
.to_string())?;
}
let controller_pubkey = unverified_controller_pubkey;
let controller_secret_encap: x_wing::Ciphertext =
(&controller_hello.setup_secret_part1).into();
let controller_secret = self
.breakpoint_keypair
.private_key
.decapsulate(&controller_secret_encap)
.map_err(|err| {
format!("Unable to decrypt controller's half of the setup secret: {err}")
})?;
let (breakpoint_secret_encap, breakpoint_secret) = controller_pubkey
.encapsulate(&mut rand::rngs::OsRng)
.map_err(|err| format!("Failed to create breakpoint secret: {err}"))?;
let (controller_stream_key, breakpoint_stream_key) = derive_stream_keys_dual_pub(
&controller_pubkey,
&self.breakpoint_keypair.public_key,
&controller_secret,
&breakpoint_secret,
);
let (tx_header, mut tx_stream) =
secret_stream::PushStream::init(OsRng, &breakpoint_stream_key);
let intro = &SecureMsg::BreakpointIntro(BreakpointIntro {
which: self.which.clone(),
});
send_insecure(
&mut self.transport,
&InsecureMsg::BreakpointSetupFinalize(BreakpointSetupFinalize {
setup_secret_part2: breakpoint_secret_encap.as_bytes(),
cipher_header: *tx_header.as_ref(),
encrypted_intro: encrypt_message(intro, &mut tx_stream).encrypted,
}),
)
.await
.map_err(|err| format!("Couldn't send breakpoint setup finalize: {err}"))?;
let controller_finalize = match receive_insecure(&mut self.transport).await {
Ok(None) => Err(String::from(
"Controller hung up before ControllerSetupFinalize",
))?,
Ok(Some(InsecureMsg::ControllerSetupFinalize(setup))) => setup,
Ok(Some(InsecureMsg::HandshakeRejected(HandshakeRejected { reason, permanent }))) => {
Err(BreakpointConnectError {
message: format!("Controller rejected connection: {reason}"),
permanent,
})?
}
Ok(Some(wrong)) => Err(format!(
"Unexpected message type when waiting for ControllerSetupFinalize: {}",
Into::<&str>::into(wrong)
))?,
Err(err) => Err(format!(
"Couldn't receive data when waiting for a ControllerSetupFinalize: {err}"
))?,
};
let rx_stream = secret_stream::PullStream::init(
secret_stream::Header::from(controller_finalize.cipher_header),
&controller_stream_key,
);
Ok(Connection {
transport: self.transport,
cipher_tx: tx_stream,
cipher_rx: rx_stream,
})
}
}
#[cfg(test)]
mod tests {
use crate::test_utils::all_variant_mapping;
use super::*;
#[test]
fn pubkey_digest() {
let privkey = x_wing::DecapsulationKey::from([7u8; 32]);
let pubkey = privkey.encapsulation_key();
let digest_c = ControllerId::from_key(&pubkey);
let digest_b = BreakpointId::from_key(&pubkey);
assert_eq!(&digest_c.id_string(), "5TQGToHGhhPzq1dng");
assert_eq!(&digest_b.id_string(), "59ARWFefSuMVrE9QG");
}
#[test]
fn id_size_bounds() {
assert_eq!([0u8; DIGEST_BYTE_LEN].to_base58().len(), DIGEST_MIN_CHARS);
assert_eq!([100u8; DIGEST_BYTE_LEN].to_base58().len(), DIGEST_MAX_CHARS);
let hasher = Blake2KeyDigests::new_with_salt_and_personal(&[], &[], &[]).unwrap();
let hash_out = hasher.finalize();
assert_eq!(hash_out.into_bytes().len(), DIGEST_BYTE_LEN);
}
#[test]
fn id_format() {
validate_id_format("3ZgBvRApjQw64rb8r").unwrap();
}
#[test]
fn stream_key_pinning() {
let ctrl_pair = x_wing::DecapsulationKey::from([20u8; 32]);
let break_pair = x_wing::DecapsulationKey::from([21u8; 32]);
let ctrl_secret = x_wing::SharedSecret::from([22u8; 32]);
let break_secret = x_wing::SharedSecret::from([23u8; 32]);
let (ctrl_stream_key, break_stream_key) = derive_stream_keys_dual_pub(
&ctrl_pair.encapsulation_key(),
&break_pair.encapsulation_key(),
&ctrl_secret,
&break_secret,
);
assert_eq!(
hex::encode(ctrl_stream_key.as_ref()),
"cab71a3c070baa772ece7eb5f108032527bc3e9d6d51839ec4b394cb3afd175f"
);
assert_eq!(
hex::encode(break_stream_key.as_ref()),
"bc1aeb4d8668dbc18f7fadb2d9f9fa8d3487996016dbbb8795acebaffcfa5b0c"
);
}
#[tokio::test]
async fn insecure_message_format_pinning() {
let examples = all_variant_mapping!(
InsecureMsg => Vec<u8>:
InsecureMsg::HandshakeRejected =
(
HandshakeRejected {
reason: String::from("not feeling it"),
permanent: true,
}
)
=> [
b"\xA1qHandshakeRejected\xA2" as &[u8],
b"freasonnnot feeling it",
b"ipermanent\xF5",
].concat(),
InsecureMsg::BreakpointHello =
(
BreakpointHello {
protocol: String::from("SOMEPROTO"),
break_pub: [6u8; 1216],
expecting_controller_id: String::from("cdigest"),
}
)
=> [
b"\xA1oBreakpointHello\xA3" as &[u8],
b"hprotocoliSOMEPROTO",
b"ibreak_pubY\x04\xC0", &[b'\x06'; 1216],
b"wexpecting_controller_idgcdigest",
].concat(),
InsecureMsg::ControllerHello =
(
ControllerHello {
ctrl_pub: [6u8; 1216],
setup_secret_part1: [4u8; 1120],
}
)
=> [
b"\xA1oControllerHello\xA2" as &[u8],
b"hctrl_pubY\x04\xC0", &[b'\x06'; 1216],
b"rsetup_secret_part1Y\x04\x60", &[b'\x04'; 1120],
].concat(),
InsecureMsg::BreakpointSetupFinalize =
(
BreakpointSetupFinalize {
setup_secret_part2: [5u8; 1120],
cipher_header: [8u8; 24],
encrypted_intro: [7u8; 10].to_vec(),
}
)
=> [
b"\xA1wBreakpointSetupFinalize\xA3" as &[u8],
b"rsetup_secret_part2Y\x04\x60", &[b'\x05'; 1120],
b"mcipher_headerX\x18", &[b'\x08'; 24],
b"oencrypted_intro\x8A", &[b'\x07'; 10],
].concat(),
InsecureMsg::ControllerSetupFinalize =
(
ControllerSetupFinalize {
cipher_header: [9u8; 24],
}
)
=> [
b"\xA1wControllerSetupFinalize\xA1" as &[u8],
b"mcipher_headerX\x18", &[b'\x09'; 24],
].concat(),
InsecureMsg::EncryptedMsg =
(
EncryptedMsg {
encrypted: [9u8; 3].to_vec(),
}
)
=> b"\xA1lEncryptedMsg\xA1iencrypted\x43\x09\x09\x09".to_vec(),
);
for (variant_value, serialized) in examples {
assert_eq!(
serialized,
serialize(&variant_value),
"Serialized bytes did not match expected value for input {variant_value:?}"
);
let deserialized = deserialize(&serialized)
.map_err(|err| format!("Could not deserialize: {err}; expected {variant_value:?}"))
.unwrap();
assert_eq!(
variant_value, deserialized,
"Deserialized value did not match expected value {variant_value:?}"
);
}
}
#[tokio::test]
async fn secure_message_format_pinning() {
let examples = all_variant_mapping!(
SecureMsg => &[u8]:
SecureMsg::BreakpointIntro =
(
BreakpointIntro {
which: Some("loop".to_owned()),
}
)
=> b"\xA1oBreakpointIntro\xA1ewhichdloop",
SecureMsg::Command =
(
Command {
command: "pwd".to_owned(),
seq: 5,
}
)
=> b"\xA1gCommand\xA2gcommandcpwdcseq\x05",
SecureMsg::OutputLine =
(
OutputLine {
bytes: b"hello world!".to_vec(),
gender: OutputType::Stdout,
seq: 7,
}
)
=> b"\xA1jOutputLine\xA3ebytes\x4Chello world!fgenderfStdoutcseq\x07",
SecureMsg::Finished =
(
Finished {
exit_code: Some(1),
seq: 9,
}
)
=> b"\xA1hFinished\xA2iexit_code\x01cseq\x09",
SecureMsg::TerminateCmd =
(
TerminateCmd { seq: 11 }
)
=> b"\xA1lTerminateCmd\xA1cseq\x0b",
SecureMsg::ExitBreak =
(ExitBreak {})
=> b"\xA1iExitBreak\xA0",
);
for (variant_value, serialized) in examples {
assert_eq!(
serialized,
serialize(&variant_value),
"Serialized bytes did not match expected value for input {variant_value:?}"
);
let deserialized = deserialize(serialized)
.map_err(|err| format!("Could not deserialize: {err}; expected {variant_value:?}"))
.unwrap();
assert_eq!(
variant_value, deserialized,
"Deserialized value did not match expected value {variant_value:?}"
);
}
}
}