use std::{
mem,
sync::{
atomic::{AtomicBool, Ordering},
Arc,
},
};
use alkali::{
hash::generic::blake2b,
mem::FullAccess,
symmetric::cipher_stream::{self, xchacha20poly1305, DecryptionStream, EncryptionStream},
};
use clap::ValueEnum;
use kem::{Decapsulate, Encapsulate};
use rust_base58::ToBase58;
use serde::{Deserialize, Serialize};
use strum_macros::IntoStaticStr;
use subtle::ConstantTimeEq;
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
net::{
tcp::{OwnedReadHalf, OwnedWriteHalf},
TcpListener, TcpStream,
},
sync::mpsc::{self, error::SendError},
};
#[derive(ValueEnum, Clone, Debug)]
pub enum AuthVersion {
DualPub,
}
const VERIFIER_DIGEST_BYTES: usize = 12;
#[derive(Clone)]
pub struct XWingKeypair {
pub public_key: x_wing::EncapsulationKey,
pub private_key: x_wing::DecapsulationKey,
}
impl XWingKeypair {
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(),
}
}
}
pub fn controller_digest(public_key: &x_wing::EncapsulationKey) -> String {
pubkey_digest(public_key, "controller")
}
pub fn breakpoint_digest(public_key: &x_wing::EncapsulationKey) -> String {
pubkey_digest(public_key, "breakpoint")
}
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 digest = &blake2b::hash_custom_to_vec(&pub_raw, Some(domain.as_bytes()), 16).unwrap()
[..VERIFIER_DIGEST_BYTES];
digest.to_base58()
}
fn verify_digest(trusted_digest: &str, received_key_digest: &str) -> bool {
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],
expected_controller_digest: 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; cipher_stream::HEADER_LENGTH],
encrypted_intro: Vec<u8>,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct ControllerSetupFinalize {
#[serde(with = "serde_bytes")]
cipher_header: [u8; cipher_stream::HEADER_LENGTH],
}
#[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,
) -> (
xchacha20poly1305::Key<FullAccess>,
xchacha20poly1305::Key<FullAccess>,
) {
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 = xchacha20poly1305::KEY_LENGTH;
let key_bytes = blake2b::hash_custom_to_vec(
&in_raw,
Some("breakmancer.dual-pub.stream-keys".as_bytes()),
key_len * 2,
)
.unwrap();
let controller_stream_key =
xchacha20poly1305::Key::<_>::try_from(&key_bytes[0..key_len]).unwrap();
let breakpoint_stream_key =
xchacha20poly1305::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 EncryptionStream) -> EncryptedMsg {
let msg_bytes = serialize(msg);
let msg_slice = msg_bytes.as_slice();
let mut ciphered = vec![0u8; msg_slice.len() + cipher_stream::OVERHEAD_LENGTH];
cipher_tx.encrypt(msg_slice, None, &mut ciphered).unwrap();
EncryptedMsg {
encrypted: ciphered,
}
}
fn decrypt_message(
msg: &EncryptedMsg,
cipher_rx: &mut DecryptionStream,
) -> Result<SecureMsg, String> {
let ciphered = msg.encrypted.as_slice();
let mut data = vec![0u8; ciphered.len() - cipher_stream::OVERHEAD_LENGTH];
cipher_rx
.decrypt(ciphered, None, &mut data)
.map_err(|err| format!("Unable to decrypt message: {err}"))?;
deserialize(&data).map_err(|err| format!("Could not parse message data: {err}"))
}
const FRAME_HEADER_SIZE: u32 = 4;
const FRAME_DATA_MAX_BYTES: usize = 2usize.pow(FRAME_HEADER_SIZE * 8);
async fn send_clear_frame<W>(stream: &mut W, clear_msg: &InsecureMsg) -> Result<(), String>
where
W: AsyncWriteExt + Unpin,
{
let data = serialize(clear_msg);
let data_len = data.len();
if data_len > FRAME_DATA_MAX_BYTES {
return Err(String::from(
"Cannot send frame of more than {data_len} bytes",
));
}
let len_bytes = &data_len.to_le_bytes()[..FRAME_HEADER_SIZE as usize];
let mut frame = Vec::<u8>::from(len_bytes);
frame.extend_from_slice(&data);
stream
.write_all(&frame)
.await
.map_err(|err| format!("Failed to send frame: {err}"))?;
Ok(())
}
async fn receive_clear_frame<R>(stream: &mut R) -> Result<InsecureMsg, String>
where
R: AsyncReadExt + Unpin,
{
let mut len_header = [0u8; mem::size_of::<usize>()];
stream
.read_exact(&mut len_header[..FRAME_HEADER_SIZE as usize])
.await
.map_err(|err| format!("Failed to receive frame length: {err}"))?;
let mut data = vec![0u8; usize::from_le_bytes(len_header)];
stream
.read_exact(&mut data)
.await
.map_err(|err| format!("Failed to receive frame body: {err}"))?;
deserialize(&data).map_err(|err| format!("Could not parse frame body: {err}"))
}
struct EncryptionStreamWrapper(EncryptionStream);
unsafe impl Send for EncryptionStreamWrapper {}
struct DecryptionStreamWrapper(DecryptionStream);
unsafe impl Send for DecryptionStreamWrapper {}
struct ConnectionTx {
tcp_write: OwnedWriteHalf,
cipher_tx: EncryptionStreamWrapper,
shutting_down: Arc<AtomicBool>,
}
impl ConnectionTx {
pub fn from(
tcp_write: OwnedWriteHalf,
cipher_tx: EncryptionStream,
shutting_down: Arc<AtomicBool>,
) -> ConnectionTx {
ConnectionTx {
tcp_write,
cipher_tx: EncryptionStreamWrapper(cipher_tx),
shutting_down,
}
}
fn log_error(&self, msg: &str) {
if !self.shutting_down.load(Ordering::SeqCst) {
eprintln!("[ERROR] {msg}");
}
}
async fn send_insecure(&mut self, msg: &InsecureMsg) -> Result<(), String> {
send_clear_frame(&mut self.tcp_write, msg).await
}
fn encrypt_message(&mut self, msg: &SecureMsg) -> EncryptedMsg {
encrypt_message(msg, &mut self.cipher_tx.0)
}
async fn send_secure(&mut self, msg: &SecureMsg) -> Result<(), String> {
let msg = self.encrypt_message(msg);
self.send_insecure(&InsecureMsg::EncryptedMsg(msg)).await
}
pub fn spawn_tx(mut self) -> mpsc::Sender<SecureMsg> {
let (send_producer, mut send_consumer) = mpsc::channel::<SecureMsg>(200);
tokio::spawn(async move {
loop {
let Some(msg) = send_consumer.recv().await else {
self.log_error(
"Internal error getting new message to send, shutting down connection.",
);
break;
};
if let Err(err) = self.send_secure(&msg).await {
self.log_error(&format!(
"Failed to send message to network, shutting down connection: {err}"
));
break;
}
if let SecureMsg::ExitBreak(_) = msg {
self.shutting_down.store(true, Ordering::SeqCst);
}
}
if let Err(err) = self.tcp_write.shutdown().await {
self.log_error(&format!("Couldn't do clean hangup: {err}"));
}
});
send_producer
}
}
struct ConnectionRx {
tcp_read: OwnedReadHalf,
cipher_rx: DecryptionStreamWrapper,
shutting_down: Arc<AtomicBool>,
}
impl ConnectionRx {
pub fn from(
tcp_read: OwnedReadHalf,
cipher_rx: DecryptionStream,
shutting_down: Arc<AtomicBool>,
) -> ConnectionRx {
ConnectionRx {
tcp_read,
cipher_rx: DecryptionStreamWrapper(cipher_rx),
shutting_down,
}
}
fn log_error(&self, msg: &str) {
if !self.shutting_down.load(Ordering::SeqCst) {
eprintln!("[ERROR] {msg}");
}
}
async fn receive_insecure(&mut self) -> Result<InsecureMsg, String> {
receive_clear_frame(&mut self.tcp_read).await
}
fn decrypt_message(&mut self, msg: &EncryptedMsg) -> Result<SecureMsg, String> {
decrypt_message(msg, &mut self.cipher_rx.0)
}
async fn receive_secure(&mut self) -> Result<SecureMsg, String> {
match self.receive_insecure().await? {
InsecureMsg::EncryptedMsg(enc) => self.decrypt_message(&enc),
clear_msg => Err(format!("Unexpected insecure message type: {clear_msg:?}")),
}
}
pub fn spawn_rx(mut self) -> mpsc::Receiver<SecureMsg> {
let (recv_producer, recv_consumer) = mpsc::channel::<SecureMsg>(200);
tokio::spawn(async move {
loop {
let msg = match self.receive_secure().await {
Ok(msg) => msg,
Err(err) => {
self.log_error(&format!(
"Couldn't receive message from network, shutting down connection: {err}"
));
break;
}
};
if let SecureMsg::ExitBreak(_) = msg {
self.shutting_down.store(true, Ordering::SeqCst);
}
if let Err(err) = recv_producer.send(msg).await {
self.log_error(&format!(
"Internal error passing a received message around; shutting down: {err}"
));
break;
}
}
});
recv_consumer
}
}
pub async fn controller_open_connection(
controller_keypair: XWingKeypair,
server_socket: &TcpListener,
) -> Result<ControllerReceivedHello, String> {
let (stream, client_addr) = server_socket
.accept()
.await
.map_err(|err| format!("Couldn't get new connection: {err}"))?;
println!("Connection from {client_addr:?}");
let (mut tcp_read, mut tcp_write) = stream.into_split();
let breakpoint_hello = match receive_clear_frame(&mut tcp_read).await {
Ok(InsecureMsg::BreakpointHello(hello)) => hello,
Ok(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 ctrl_digest = controller_digest(&controller_keypair.public_key);
if !verify_digest(&ctrl_digest, &breakpoint_hello.expected_controller_digest) {
let err_msg = format!(
"Breakpoint sent wrong expected controller verification string, \
exiting early. (Breakpoint had outdated connection string?) \
Expected = {}, sent = {}.",
ctrl_digest, breakpoint_hello.expected_controller_digest
);
send_clear_frame(
&mut tcp_write,
&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,
tcp_read,
tcp_write,
breakpoint_pub_unverified: breakpoint_pub_key,
})
}
pub struct ControllerReceivedHello {
controller_keypair: XWingKeypair,
tcp_read: OwnedReadHalf,
tcp_write: OwnedWriteHalf,
breakpoint_pub_unverified: x_wing::EncapsulationKey,
}
impl ControllerReceivedHello {
pub async fn verify_breakpoint_and_complete_setup(
mut self,
expected_breakpoint_digest: &str,
) -> Result<(Connection, BreakpointIntro), String> {
let key_digest = breakpoint_digest(&self.breakpoint_pub_unverified);
if !verify_digest(expected_breakpoint_digest, &key_digest) {
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_clear_frame(
&mut self.tcp_write,
&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_clear_frame(
&mut self.tcp_write,
&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_clear_frame(&mut self.tcp_read).await {
Ok(InsecureMsg::BreakpointSetupFinalize(finalize)) => finalize,
Ok(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 =
DecryptionStream::new(&breakpoint_stream_key, &breakpoint_finalize.cipher_header)
.map_err(|err| format!("Unable to start decryption stream: {err}"))?;
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_stream = EncryptionStream::new(&controller_stream_key).unwrap();
let tx_header = tx_stream.get_header();
send_clear_frame(
&mut self.tcp_write,
&InsecureMsg::ControllerSetupFinalize(ControllerSetupFinalize {
cipher_header: tx_header,
}),
)
.await
.map_err(|err| format!("Unable to send controller setup finalization: {err}"))?;
let shutting_down = Arc::new(AtomicBool::from(false));
let secured_conn = Connection {
rx: ConnectionRx::from(self.tcp_read, rx_stream, shutting_down.clone()).spawn_rx(),
tx: ConnectionTx::from(self.tcp_write, tx_stream, shutting_down.clone()).spawn_tx(),
shutting_down,
};
Ok((secured_conn, breakpoint_intro))
}
}
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,
controller_addr: &String,
controller_expected_digest: &str,
which: Option<&String>,
) -> Result<Connection, BreakpointConnectError> {
let stream = TcpStream::connect(controller_addr)
.await
.map_err(|err| format!("Unable to connect to {controller_addr}: {err}"))?;
match stream.peer_addr() {
Ok(connected) => eprintln!("Connecting to controller at {connected}"),
Err(err) => eprintln!("Unable to get address of controller connection: {err}"),
}
let (mut tcp_read, mut tcp_write) = stream.into_split();
send_clear_frame(
&mut tcp_write,
&InsecureMsg::BreakpointHello(BreakpointHello {
protocol: PROTO_DUAL_PUB.to_owned(),
break_pub: breakpoint_keypair.public_key.as_bytes(),
expected_controller_digest: controller_expected_digest.to_owned(),
}),
)
.await
.map_err(|err| format!("Unable to send hello: {err}"))?;
let breakpoint_digest = breakpoint_digest(&breakpoint_keypair.public_key);
println!("This is the verification string for the breakpoint:");
println!();
println!(" {breakpoint_digest}");
println!();
println!("Copy that string into the breakmancer controller when prompted.");
let controller_hello = match receive_clear_frame(&mut tcp_read).await {
Ok(InsecureMsg::ControllerHello(hello)) => hello,
Ok(InsecureMsg::HandshakeRejected(HandshakeRejected { reason, permanent })) => {
Err(BreakpointConnectError {
message: format!("Controller rejected connection: {reason}"),
permanent,
})?
}
Ok(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_digest = controller_digest(&unverified_controller_pubkey);
if !verify_digest(controller_expected_digest, &received_digest) {
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 = 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,
&breakpoint_keypair.public_key,
&controller_secret,
&breakpoint_secret,
);
let mut tx_stream = EncryptionStream::new(&breakpoint_stream_key).unwrap();
let tx_header = tx_stream.get_header();
let intro = &SecureMsg::BreakpointIntro(BreakpointIntro {
which: which.cloned(),
});
send_clear_frame(
&mut tcp_write,
&InsecureMsg::BreakpointSetupFinalize(BreakpointSetupFinalize {
setup_secret_part2: breakpoint_secret_encap.as_bytes(),
cipher_header: tx_header,
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_clear_frame(&mut tcp_read).await {
Ok(InsecureMsg::ControllerSetupFinalize(setup)) => setup,
Ok(InsecureMsg::HandshakeRejected(HandshakeRejected { reason, permanent })) => {
Err(BreakpointConnectError {
message: format!("Controller rejected connection: {reason}"),
permanent,
})?
}
Ok(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 =
DecryptionStream::new(&controller_stream_key, &controller_finalize.cipher_header)
.map_err(|err| format!("Unable to start decryption stream: {err}"))?;
let shutting_down = Arc::new(AtomicBool::from(false));
Ok(Connection {
rx: ConnectionRx::from(tcp_read, rx_stream, shutting_down.clone()).spawn_rx(),
tx: ConnectionTx::from(tcp_write, tx_stream, shutting_down.clone()).spawn_tx(),
shutting_down,
})
}
pub struct Connection {
rx: mpsc::Receiver<SecureMsg>,
tx: mpsc::Sender<SecureMsg>,
shutting_down: Arc<AtomicBool>,
}
impl Connection {
pub async fn recv(&mut self) -> Option<SecureMsg> {
self.rx.recv().await
}
pub async fn send(&self, msg: SecureMsg) -> Result<(), SendError<SecureMsg>> {
self.tx.send(msg).await
}
}
impl Drop for Connection {
fn drop(&mut self) {
self.shutting_down.store(true, Ordering::SeqCst);
}
}
#[cfg(test)]
mod tests {
use crate::test_utils::{all_variant_mapping, partial_hexlify};
use super::*;
#[test]
fn pubkey_digest() {
let privkey = x_wing::DecapsulationKey::from([7u8; 32]);
let pubkey = privkey.encapsulation_key();
let digest_c = controller_digest(&pubkey);
let digest_b = breakpoint_digest(&pubkey);
assert_eq!(&digest_c, "rfFydfomQ4546wuW");
assert_eq!(&digest_b, "3YVBK2JSiKM7Y5i4i");
}
#[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_slice()),
"cab71a3c070baa772ece7eb5f108032527bc3e9d6d51839ec4b394cb3afd175f"
);
assert_eq!(
hex::encode(break_stream_key.as_slice()),
"bc1aeb4d8668dbc18f7fadb2d9f9fa8d3487996016dbbb8795acebaffcfa5b0c"
);
}
#[tokio::test]
async fn network_frame() {
let mut written = Vec::<u8>::new();
send_clear_frame(
&mut written,
&InsecureMsg::BreakpointHello(BreakpointHello {
protocol: String::from("test"),
break_pub: [5u8; 1216],
expected_controller_digest: String::from("abc"),
}),
)
.await
.unwrap();
let expected = [
b"\x0D\x05\x00\x00" as &[u8],
b"\xA1", b"o",
b"BreakpointHello",
b"\xA3", b"h",
b"protocol",
b"d",
b"test",
b"i",
b"break_pub",
b"Y",
b"\x04\xC0",
&[b'\x05'; 1216],
b"x\x1A",
b"expected_controller_digest",
b"c",
b"abc",
]
.concat();
assert_eq!(
expected,
written,
"expected {} but got {}",
partial_hexlify(&expected),
partial_hexlify(&written)
);
}
#[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],
expected_controller_digest: String::from("cdigest"),
}
)
=> [
b"\xA1oBreakpointHello\xA3" as &[u8],
b"hprotocoliSOMEPROTO",
b"ibreak_pubY\x04\xC0", &[b'\x06'; 1216],
b"x\x1Aexpected_controller_digestgcdigest",
].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:?}"
);
}
}
}