use std::mem::size_of;
use alkali::{
asymmetric::kx::x25519blake2b as X25519,
hash::generic::blake2b,
mem::{hardened_buffer, FullAccess},
symmetric::cipher_stream::{self, DecryptionStream, EncryptionStream},
};
use rand::{thread_rng, Fill};
use serde::{Deserialize, Serialize};
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
net::{
tcp::{OwnedReadHalf, OwnedWriteHalf},
TcpListener, TcpStream,
},
sync::mpsc,
};
pub const OOB_SECRET_LENGTH: usize = 16;
hardened_buffer! {
pub OutOfBandSharedSecret(OOB_SECRET_LENGTH)
}
pub type OobSecret = OutOfBandSharedSecret<FullAccess>;
impl OobSecret {
pub fn new_random() -> OobSecret {
let mut oob_secret = OobSecret::new_empty().unwrap();
oob_secret.try_fill(&mut thread_rng()).unwrap();
oob_secret
}
pub fn from_bytes(bytes: &[u8]) -> OobSecret {
let mut secret = OobSecret::new_empty().unwrap();
secret.copy_from_slice(bytes); secret
}
}
const VERSION: &str = "dev";
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct BreakpointHello {
pub protocol: String,
#[serde(with = "serde_bytes")]
pub breakpoint_pub_key: X25519::PublicKey,
#[serde(with = "serde_bytes")]
pub cipher_header: [u8; cipher_stream::HEADER_LENGTH],
#[serde(with = "serde_bytes")]
pub encrypted_intro: Vec<u8>,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct ListenerHello {
#[serde(with = "serde_bytes")]
pub cipher_header: [u8; cipher_stream::HEADER_LENGTH],
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct EncryptedMsg {
#[serde(with = "serde_bytes")]
pub encrypted: Vec<u8>,
}
#[derive(Debug, PartialEq, Serialize, Deserialize)]
enum InsecureMsg {
BreakpointHello(BreakpointHello),
ListenerHello(ListenerHello),
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)]
pub enum SecureMsg {
BreakpointIntro(BreakpointIntro),
Command(Command),
OutputLine(OutputLine),
Finished(Finished),
TerminateCmd(TerminateCmd),
ExitBreak(ExitBreak),
}
enum Party {
Listener,
Breakpoint,
}
fn make_stream_key(
stream_prekey: &[u8],
oob_secret: &OobSecret,
) -> cipher_stream::xchacha20poly1305::Key<FullAccess> {
let context = "cipher_stream key from KX prekey and OOB secret".as_bytes();
let key_bytes = blake2b::hash_custom_to_vec(
&[context, b"|", stream_prekey].concat(),
Some(oob_secret.as_slice()),
cipher_stream::xchacha20poly1305::KEY_LENGTH,
)
.unwrap();
cipher_stream::xchacha20poly1305::Key::<FullAccess>::try_from(key_bytes.as_slice()).unwrap()
}
fn derive_stream_keys(
my_role: &Party,
my_keypair: &X25519::Keypair,
other_public: &X25519::PublicKey,
oob_secret: &OobSecret,
) -> (
cipher_stream::xchacha20poly1305::Key<FullAccess>,
cipher_stream::xchacha20poly1305::Key<FullAccess>,
) {
let prekeys = match my_role {
Party::Breakpoint => my_keypair.client_keys(other_public),
Party::Listener => my_keypair.server_keys(other_public),
};
let (tx_prekey, rx_prekey) = prekeys.unwrap();
let tx = make_stream_key(tx_prekey.as_slice(), oob_secret);
let rx = make_stream_key(rx_prekey.as_slice(), oob_secret);
(tx, rx)
}
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; 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,
}
impl ConnectionTx {
pub fn from(tcp_write: OwnedWriteHalf, cipher_tx: EncryptionStream) -> ConnectionTx {
ConnectionTx {
tcp_write,
cipher_tx: EncryptionStreamWrapper(cipher_tx),
}
}
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 {
if let Some(msg) = send_consumer.recv().await {
if let Err(err) = self.send_secure(&msg).await {
eprintln!(
"[DEBUG] Failed to send message, shutting down connection: {err}"
);
break;
}
} else {
eprintln!(
"[DEBUG] Failed to get new message to send, shutting down connection."
);
break;
}
}
if let Err(err) = self.tcp_write.shutdown().await {
eprintln!("Couldn't do clean hangup: {err}");
}
});
send_producer
}
}
struct ConnectionRx {
tcp_read: OwnedReadHalf,
cipher_rx: DecryptionStreamWrapper,
}
impl ConnectionRx {
pub fn from(tcp_read: OwnedReadHalf, cipher_rx: DecryptionStream) -> ConnectionRx {
ConnectionRx {
tcp_read,
cipher_rx: DecryptionStreamWrapper(cipher_rx),
}
}
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) => {
eprintln!(
"[DEBUG] Couldn't receive message, shutting down connection: {err}"
);
break;
}
};
if let Err(err) = recv_producer.send(msg).await {
eprintln!("[DEBUG] Couldn't consume new message, shutting down: {err}");
break;
}
}
});
recv_consumer
}
}
pub struct Connection {
pub rx: mpsc::Receiver<SecureMsg>,
pub tx: mpsc::Sender<SecureMsg>,
}
impl Connection {
pub async fn new_listener(
server_socket: &TcpListener,
listener_keypair: &X25519::Keypair,
oob_secret: &OobSecret,
) -> Result<(Connection, BreakpointIntro), 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)) => Ok(hello),
Ok(_) => Err("Expected a breakpoint hello.".to_owned()),
Err(err) => Err(format!(
"Couldn't receive data when waiting for a breakpoint hello: {err}"
)
.to_owned()),
}?;
let (tx, rx) = derive_stream_keys(
&Party::Listener,
listener_keypair,
&breakpoint_hello.breakpoint_pub_key,
oob_secret,
);
let mut rx_stream = DecryptionStream::new(&rx, &breakpoint_hello.cipher_header).unwrap();
let maybe_intro = decrypt_message(
&EncryptedMsg {
encrypted: breakpoint_hello.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(&tx).unwrap();
let tx_header = tx_stream.get_header();
send_clear_frame(
&mut tcp_write,
&InsecureMsg::ListenerHello(ListenerHello {
cipher_header: tx_header,
}),
)
.await
.map_err(|err| format!("Couldn't send encrypted stream header: {err}"))?;
Ok((
Connection {
rx: ConnectionRx::from(tcp_read, rx_stream).spawn_rx(),
tx: ConnectionTx::from(tcp_write, tx_stream).spawn_tx(),
},
breakpoint_intro,
))
}
pub async fn new_breakpoint(
breakpoint_keypair: &X25519::Keypair,
listener_addr: &String,
listener_public: &X25519::PublicKey,
oob_secret: &OobSecret,
which: &Option<String>,
) -> Result<Connection, String> {
let stream = TcpStream::connect(listener_addr)
.await
.map_err(|err| format!("Unable to connect to {listener_addr}: {err}"))?;
match stream.peer_addr() {
Ok(connected) => eprintln!("Connecting to listener at {connected}"),
Err(err) => eprintln!("Unable to get address of listener connection: {err}"),
};
let (mut tcp_read, mut tcp_write) = stream.into_split();
let (tx, rx) = derive_stream_keys(
&Party::Breakpoint,
breakpoint_keypair,
listener_public,
oob_secret,
);
let mut tx_stream = EncryptionStream::new(&tx).unwrap();
let tx_header = tx_stream.get_header();
let intro = SecureMsg::BreakpointIntro(BreakpointIntro {
which: which.clone(),
});
let hello = BreakpointHello {
protocol: VERSION.to_owned(),
breakpoint_pub_key: breakpoint_keypair.public_key,
cipher_header: tx_header,
encrypted_intro: encrypt_message(&intro, &mut tx_stream).encrypted,
};
send_clear_frame(&mut tcp_write, &InsecureMsg::BreakpointHello(hello))
.await
.map_err(|err| format!("Unable to send hello: {err}"))?;
let listener_hello = match receive_clear_frame(&mut tcp_read).await {
Ok(InsecureMsg::ListenerHello(hello)) => Ok(hello),
Ok(_) => Err("Expected a listener cipher header.".to_owned()),
Err(err) => Err(format!(
"Couldn't receive data when waiting for a listener cipher header: {err}"
)
.to_owned()),
}?;
let rx_stream = DecryptionStream::new(&rx, &listener_hello.cipher_header)
.map_err(|err| format!("Unable to start decryption stream: {err}"))?;
Ok(Connection {
rx: ConnectionRx::from(tcp_read, rx_stream).spawn_rx(),
tx: ConnectionTx::from(tcp_write, tx_stream).spawn_tx(),
})
}
pub fn hangup(self) {
std::mem::drop(self);
}
}
#[cfg(test)]
mod tests {
use crate::test_utils::{all_variant_mapping, partial_hexlify};
use super::*;
#[tokio::test]
async fn network_frame() {
let mut written = Vec::<u8>::new();
send_clear_frame(
&mut written,
&InsecureMsg::BreakpointHello(BreakpointHello {
protocol: String::from("test"),
breakpoint_pub_key: [5u8; 32],
cipher_header: [1u8; 24],
encrypted_intro: [3u8; 6].to_vec(),
}),
)
.await
.unwrap();
let expected = [
b"\x94\x00\x00\x00" as &[u8],
b"\xA1", b"o", b"BreakpointHello",
b"\xA4", b"h", b"protocol",
b"d", b"test",
b"r", b"breakpoint_pub_key",
b"X", b"\x20", b"\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05\x05",
b"m", b"cipher_header",
b"X\x18", b"\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01\x01",
b"o", b"encrypted_intro",
b"F", b"\x03\x03\x03\x03\x03\x03",
].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 => &[u8]:
InsecureMsg::BreakpointHello =
(
BreakpointHello {
protocol: String::from("someproto"),
breakpoint_pub_key: [6u8; 32],
cipher_header: [7u8; 24],
encrypted_intro: [3u8; 6].to_vec(),
}
)
=> b"\xA1oBreakpointHello\xA4hprotocolisomeprotorbreakpoint_pub_keyX\x20\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06\x06mcipher_headerX\x18\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07\x07oencrypted_intro\x46\x03\x03\x03\x03\x03\x03",
InsecureMsg::ListenerHello =
(
ListenerHello {
cipher_header: [8u8; 24],
}
)
=> b"\xA1mListenerHello\xA1mcipher_headerX\x18\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08\x08",
InsecureMsg::EncryptedMsg =
(
EncryptedMsg {
encrypted: [9u8; 3].to_vec(),
}
)
=> b"\xA1lEncryptedMsg\xA1iencrypted\x43\x09\x09\x09",
);
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:?}"
);
}
}
}