use crate::keys::{NodePrivate, NodePublic};
use crypto_box::aead::AeadInPlace;
use crypto_box::{PublicKey, SalsaBox, SecretKey};
use crate::derp::frame::{self, Frame, FrameType};
use crate::derp::{Error, MAGIC};
pub const MAX_PACKET: usize = frame::MAX_FRAME - 32;
const NONCE_LEN: usize = 24;
const TAG_LEN: usize = 16;
const KEY_LEN: usize = 32;
const CLIENT_INFO: &[u8] = br#"{"version":2}"#;
pub trait Rng {
fn fill_bytes(&mut self, dest: &mut [u8]);
}
fn shared(ours: &NodePrivate, theirs: &NodePublic) -> SalsaBox {
SalsaBox::new(
&PublicKey::from_bytes(*theirs.as_bytes()),
&SecretKey::from_bytes(ours.to_bytes()),
)
}
pub struct Client {
server: NodePublic,
}
impl Client {
pub fn read_server_key(frame: Frame, payload: &[u8]) -> Result<NodePublic, Error> {
if frame.kind != FrameType::ServerKey {
return Err(Error::Malformed);
}
if payload.len() < MAGIC.len() + KEY_LEN || payload[..MAGIC.len()] != MAGIC {
return Err(Error::Malformed);
}
let mut key = [0u8; KEY_LEN];
key.copy_from_slice(&payload[MAGIC.len()..MAGIC.len() + KEY_LEN]);
Ok(NodePublic::from_bytes(key))
}
pub fn write_client_info(
ours: &NodePrivate,
our_public: &NodePublic,
server: &NodePublic,
rng: &mut impl Rng,
out: &mut [u8],
) -> Result<usize, Error> {
let mut payload = [0u8; KEY_LEN + NONCE_LEN + 128];
let sealed_len = CLIENT_INFO.len() + TAG_LEN;
let total = KEY_LEN + NONCE_LEN + sealed_len;
if total > payload.len() {
return Err(Error::BufferTooSmall);
}
payload[..KEY_LEN].copy_from_slice(our_public.as_bytes());
let mut nonce = [0u8; NONCE_LEN];
rng.fill_bytes(&mut nonce);
payload[KEY_LEN..KEY_LEN + NONCE_LEN].copy_from_slice(&nonce);
let body_start = KEY_LEN + NONCE_LEN + TAG_LEN;
payload[body_start..body_start + CLIENT_INFO.len()].copy_from_slice(CLIENT_INFO);
let tag = shared(ours, server)
.encrypt_in_place_detached(
&nonce.into(),
&[],
&mut payload[body_start..body_start + CLIENT_INFO.len()],
)
.map_err(|_| Error::Decryption)?;
payload[KEY_LEN + NONCE_LEN..body_start].copy_from_slice(&tag);
frame::write(FrameType::ClientInfo, &payload[..total], out)
}
pub fn read_server_info(
ours: &NodePrivate,
server: &NodePublic,
frame: Frame,
payload: &[u8],
out: &mut [u8],
) -> Result<Self, Error> {
if frame.kind != FrameType::ServerInfo {
return Err(Error::Malformed);
}
let sealed = payload.get(NONCE_LEN..).ok_or(Error::Malformed)?;
let len = sealed.len().checked_sub(TAG_LEN).ok_or(Error::Malformed)?;
let out = out.get_mut(..len).ok_or(Error::BufferTooSmall)?;
let mut nonce = [0u8; NONCE_LEN];
nonce.copy_from_slice(&payload[..NONCE_LEN]);
let (tag, ciphertext) = sealed.split_at(TAG_LEN);
out.copy_from_slice(ciphertext);
shared(ours, server)
.decrypt_in_place_detached(&nonce.into(), &[], out, tag.into())
.map_err(|_| Error::Decryption)?;
Ok(Self { server: *server })
}
pub fn server(&self) -> &NodePublic {
&self.server
}
pub fn send_packet(
&self,
destination: &NodePublic,
packet: &[u8],
out: &mut [u8],
) -> Result<usize, Error> {
if packet.len() > MAX_PACKET {
return Err(Error::TooLarge);
}
let mut payload = [0u8; frame::MAX_FRAME];
payload[..KEY_LEN].copy_from_slice(destination.as_bytes());
payload[KEY_LEN..KEY_LEN + packet.len()].copy_from_slice(packet);
frame::write(
FrameType::SendPacket,
&payload[..KEY_LEN + packet.len()],
out,
)
}
pub fn read_packet(frame: Frame, payload: &[u8]) -> Result<(NodePublic, &[u8]), Error> {
if frame.kind != FrameType::RecvPacket {
return Err(Error::Malformed);
}
if payload.len() < KEY_LEN {
return Err(Error::Malformed);
}
let mut key = [0u8; KEY_LEN];
key.copy_from_slice(&payload[..KEY_LEN]);
Ok((NodePublic::from_bytes(key), &payload[KEY_LEN..]))
}
pub fn write_note_preferred(preferred: bool, out: &mut [u8]) -> Result<usize, Error> {
frame::write(FrameType::NotePreferred, &[preferred as u8], out)
}
}
pub fn write_upgrade_request<'o>(host: &str, out: &'o mut [u8]) -> Result<&'o [u8], Error> {
let parts: [&[u8]; 6] = [
b"GET ",
crate::derp::PATH.as_bytes(),
b" HTTP/1.1\r\nHost: ",
host.as_bytes(),
b"\r\nConnection: Upgrade\r\nUpgrade: ",
crate::derp::UPGRADE_TOKEN.as_bytes(),
];
let mut len = 0;
for part in parts {
let end = len + part.len();
out.get_mut(len..end)
.ok_or(Error::BufferTooSmall)?
.copy_from_slice(part);
len = end;
}
let tail = b"\r\n\r\n";
let end = len + tail.len();
out.get_mut(len..end)
.ok_or(Error::BufferTooSmall)?
.copy_from_slice(tail);
Ok(&out[..end])
}
pub fn parse_upgrade(bytes: &[u8]) -> Result<usize, Error> {
let end = bytes
.windows(4)
.position(|w| w == b"\r\n\r\n")
.map(|i| i + 4)
.ok_or(Error::Incomplete)?;
let head = &bytes[..end];
if !head.starts_with(b"HTTP/1.1 101") {
return Err(Error::Malformed);
}
if !contains(head, b"Derp-Version: 2") && !contains(head, b"derp-version: 2") {
return Err(Error::Unsupported);
}
Ok(end)
}
fn contains(haystack: &[u8], needle: &[u8]) -> bool {
haystack.windows(needle.len()).any(|w| w == needle)
}
#[cfg(test)]
mod tests {
use super::*;
struct Counter(u8);
impl Rng for Counter {
fn fill_bytes(&mut self, dest: &mut [u8]) {
for byte in dest {
*byte = self.0;
self.0 = self.0.wrapping_add(1);
}
}
}
fn server_key_frame(key: &NodePublic) -> heapless::Vec<u8, 64> {
let mut payload = heapless::Vec::<u8, 64>::new();
payload.extend_from_slice(&MAGIC).unwrap();
payload.extend_from_slice(key.as_bytes()).unwrap();
let mut out = heapless::Vec::<u8, 64>::new();
out.resize_default(64).unwrap();
let len = frame::write(FrameType::ServerKey, &payload, &mut out).unwrap();
out.truncate(len);
out
}
#[test]
fn reads_the_server_key_out_of_its_opening_frame() {
let server = NodePrivate::from_bytes([0x22; 32]).public();
let bytes = server_key_frame(&server);
let frame = Frame::parse(&bytes).unwrap();
assert_eq!(
Client::read_server_key(frame, &bytes[frame::HEADER_LEN..]).unwrap(),
server
);
}
#[test]
fn a_frame_without_the_magic_is_not_a_server_key() {
let mut payload = [0u8; 40];
payload[..8].copy_from_slice(b"NOTDERP!");
let mut bytes = [0u8; 64];
let len = frame::write(FrameType::ServerKey, &payload, &mut bytes).unwrap();
let frame = Frame::parse(&bytes).unwrap();
assert_eq!(
Client::read_server_key(frame, &bytes[frame::HEADER_LEN..len]),
Err(Error::Malformed)
);
}
#[test]
fn the_handshake_completes_against_a_server_that_holds_the_matching_key() {
let ours = NodePrivate::from_bytes([0x11; 32]);
let server_private = NodePrivate::from_bytes([0x22; 32]);
let server = server_private.public();
let mut out = [0u8; 256];
let len =
Client::write_client_info(&ours, &ours.public(), &server, &mut Counter(1), &mut out)
.unwrap();
let frame = Frame::parse(&out).unwrap();
assert_eq!(frame.kind, FrameType::ClientInfo);
let payload = &out[frame::HEADER_LEN..len];
assert_eq!(&payload[..32], ours.public().as_bytes());
let sealed = &payload[32 + NONCE_LEN..];
let mut nonce = [0u8; NONCE_LEN];
nonce.copy_from_slice(&payload[32..32 + NONCE_LEN]);
let (tag, ciphertext) = sealed.split_at(TAG_LEN);
let mut plaintext = [0u8; 64];
plaintext[..ciphertext.len()].copy_from_slice(ciphertext);
shared(&server_private, &ours.public())
.decrypt_in_place_detached(
&nonce.into(),
&[],
&mut plaintext[..ciphertext.len()],
tag.into(),
)
.expect("the server must be able to open our hello");
assert_eq!(&plaintext[..ciphertext.len()], CLIENT_INFO);
}
#[test]
fn a_packet_carries_its_destination_and_comes_back_with_its_source() {
let ours = NodePrivate::from_bytes([0x11; 32]);
let destination = NodePrivate::from_bytes([0x33; 32]).public();
let client = Client {
server: NodePrivate::from_bytes([0x22; 32]).public(),
};
let mut out = [0u8; 256];
client
.send_packet(&destination, b"wireguard bytes", &mut out)
.unwrap();
let frame = Frame::parse(&out).unwrap();
assert_eq!(frame.kind, FrameType::SendPacket);
assert_eq!(
&out[frame::HEADER_LEN..frame::HEADER_LEN + 32],
destination.as_bytes()
);
let mut relayed = [0u8; 256];
relayed[..32].copy_from_slice(ours.public().as_bytes());
relayed[32..32 + 15].copy_from_slice(b"wireguard bytes");
let mut framed = [0u8; 256];
let len = frame::write(FrameType::RecvPacket, &relayed[..47], &mut framed).unwrap();
let frame = Frame::parse(&framed).unwrap();
let (source, packet) = Client::read_packet(frame, &framed[frame::HEADER_LEN..len]).unwrap();
assert_eq!(source, ours.public());
assert_eq!(packet, b"wireguard bytes");
}
#[test]
fn the_upgrade_request_and_response_match_what_a_real_server_expects() {
let mut out = [0u8; 256];
let request = write_upgrade_request("127.0.0.1:8080", &mut out).unwrap();
let text = core::str::from_utf8(request).unwrap();
assert!(text.starts_with("GET /derp HTTP/1.1\r\n"));
assert!(text.contains("Upgrade: DERP\r\n"));
assert!(text.contains("Connection: Upgrade\r\n"));
let response = b"HTTP/1.1 101 Switching Protocols\r\nUpgrade: DERP\r\nConnection: Upgrade\r\nDerp-Version: 2\r\nDerp-Public-Key: nodekey:ee02\r\n\r\n\x01\x00";
let end = parse_upgrade(response).unwrap();
assert_eq!(&response[end..], b"\x01\x00", "the first frame follows");
let old = b"HTTP/1.1 101 Switching Protocols\r\nUpgrade: DERP\r\nDerp-Version: 1\r\n\r\n";
assert_eq!(parse_upgrade(old), Err(Error::Unsupported));
assert_eq!(
parse_upgrade(b"HTTP/1.1 400 Bad\r\n\r\n"),
Err(Error::Malformed)
);
assert_eq!(parse_upgrade(b"HTTP/1.1 101"), Err(Error::Incomplete));
}
}