use std::io;
use serde::{Deserialize, Serialize};
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
use crate::attestation::CONTROL_PUBKEY_LEN;
pub const MESH_VSOCK_PORT: u32 = 5009;
pub const SYNCHRONIZER_BOOTSTRAP_PORT: u32 = 5008;
pub const SYNCHRONIZER_CLIENT_PORT: u32 = 5010;
pub const SYNCHRONIZER_CUSTOMER_RELAY_PORT: u32 = 5012;
pub const MAX_OPEN_FRAME_SIZE: u32 = 4096;
pub const OPEN_ACK_OK: u8 = 0x00;
pub const OPEN_ACK_FAILED: u8 = 0x01;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum OpenAck {
Ok,
Failed(u8),
Eof,
}
pub async fn read_open_ack<S>(stream: &mut S) -> io::Result<OpenAck>
where
S: AsyncRead + Unpin,
{
let mut byte = [0u8; 1];
match stream.read_exact(&mut byte).await {
Ok(_) => {
if byte[0] == OPEN_ACK_OK {
Ok(OpenAck::Ok)
} else {
Ok(OpenAck::Failed(byte[0]))
}
}
Err(e) if e.kind() == io::ErrorKind::UnexpectedEof => Ok(OpenAck::Eof),
Err(e) => Err(e),
}
}
pub async fn write_open_ack<S>(stream: &mut S, ok: bool) -> io::Result<()>
where
S: AsyncWrite + Unpin,
{
let byte = if ok { OPEN_ACK_OK } else { OPEN_ACK_FAILED };
stream.write_all(&[byte]).await?;
stream.flush().await?;
Ok(())
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Open {
pub target_peer: String,
}
#[derive(Debug, thiserror::Error)]
pub enum ReadOpenError {
#[error("io error: {0}")]
Io(#[from] io::Error),
#[error("opener frame too large: {0} > {MAX_OPEN_FRAME_SIZE}")]
FrameTooLarge(u32),
#[error("failed to decode opener frame: {0}")]
Decode(#[from] ciborium::de::Error<io::Error>),
}
pub async fn read_open_frame<S>(stream: &mut S) -> Result<Open, ReadOpenError>
where
S: AsyncRead + Unpin,
{
let len = stream.read_u32().await?;
if len > MAX_OPEN_FRAME_SIZE {
return Err(ReadOpenError::FrameTooLarge(len));
}
let mut buf = vec![0u8; len as usize];
stream.read_exact(&mut buf).await?;
let open: Open = ciborium::from_reader(&buf[..])?;
Ok(open)
}
pub async fn write_open_frame<S>(stream: &mut S, open: &Open) -> io::Result<()>
where
S: AsyncWrite + Unpin,
{
let mut buf = Vec::new();
ciborium::into_writer(open, &mut buf).expect("ciborium encode Open frame");
let len: u32 = buf
.len()
.try_into()
.expect("Open frame fits in u32 (CBOR encoding is small)");
stream.write_all(&len.to_be_bytes()).await?;
stream.write_all(&buf).await?;
stream.flush().await?;
Ok(())
}
#[derive(Debug, thiserror::Error)]
pub enum MeshIdentityError {
#[error("mesh identity pubkey does not decode as SEC1 P-256")]
BadPubkey,
#[error("mesh identity signature is not 64 bytes raw r||s P-256")]
SignatureShape,
#[error("mesh identity signature does not verify over the handshake hash")]
SignatureInvalid,
}
pub fn sign_mesh_identity(signing_key: &p256::ecdsa::SigningKey, handshake_hash: &[u8]) -> Vec<u8> {
use p256::ecdsa::{Signature, signature::Signer};
let sig: Signature = signing_key.sign(handshake_hash);
sig.to_bytes().to_vec()
}
pub fn verify_mesh_identity(
mesh_pubkey: &[u8; CONTROL_PUBKEY_LEN],
signature: &[u8],
handshake_hash: &[u8],
) -> Result<(), MeshIdentityError> {
use p256::ecdsa::{Signature, VerifyingKey, signature::Verifier};
let verifying =
VerifyingKey::from_sec1_bytes(mesh_pubkey).map_err(|_| MeshIdentityError::BadPubkey)?;
let sig = Signature::from_slice(signature).map_err(|_| MeshIdentityError::SignatureShape)?;
verifying
.verify(handshake_hash, &sig)
.map_err(|_| MeshIdentityError::SignatureInvalid)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn open_roundtrip_cbor() {
let open = Open {
target_peer: "synchronizer-az-b".to_string(),
};
let mut buf = Vec::new();
ciborium::into_writer(&open, &mut buf).unwrap();
let decoded: Open = ciborium::from_reader(&buf[..]).unwrap();
assert_eq!(open, decoded);
}
#[tokio::test]
async fn open_frame_roundtrip_over_stream() {
let open = Open {
target_peer: "synchronizer-az-c".to_string(),
};
let (mut a, mut b) = tokio::io::duplex(1024);
write_open_frame(&mut a, &open).await.unwrap();
let decoded = read_open_frame(&mut b).await.unwrap();
assert_eq!(open, decoded);
}
#[tokio::test]
async fn oversized_open_frame_is_rejected() {
let (mut a, mut b) = tokio::io::duplex(1024);
a.write_u32(MAX_OPEN_FRAME_SIZE + 1).await.unwrap();
a.flush().await.unwrap();
let err = read_open_frame(&mut b).await.unwrap_err();
assert!(matches!(err, ReadOpenError::FrameTooLarge(_)), "{err:?}");
}
#[tokio::test]
async fn open_ack_ok_roundtrips() {
let (mut a, mut b) = tokio::io::duplex(64);
write_open_ack(&mut a, true).await.unwrap();
assert_eq!(read_open_ack(&mut b).await.unwrap(), OpenAck::Ok);
}
#[tokio::test]
async fn open_ack_failed_roundtrips() {
let (mut a, mut b) = tokio::io::duplex(64);
write_open_ack(&mut a, false).await.unwrap();
assert_eq!(
read_open_ack(&mut b).await.unwrap(),
OpenAck::Failed(OPEN_ACK_FAILED)
);
}
#[tokio::test]
async fn open_ack_unexpected_byte_is_failure() {
let (mut a, mut b) = tokio::io::duplex(64);
a.write_all(&[0x7f]).await.unwrap();
a.flush().await.unwrap();
assert_eq!(read_open_ack(&mut b).await.unwrap(), OpenAck::Failed(0x7f));
}
#[tokio::test]
async fn open_ack_eof_is_failure() {
let (a, mut b) = tokio::io::duplex(64);
drop(a);
assert_eq!(read_open_ack(&mut b).await.unwrap(), OpenAck::Eof);
}
#[test]
fn ack_constants_are_distinct() {
assert_eq!(OPEN_ACK_OK, 0x00);
assert_eq!(OPEN_ACK_FAILED, 0x01);
assert_ne!(OPEN_ACK_OK, OPEN_ACK_FAILED);
}
fn mesh_keypair() -> (p256::ecdsa::SigningKey, [u8; CONTROL_PUBKEY_LEN]) {
use p256::ecdsa::SigningKey;
let mut scalar = [0u8; 32];
scalar[0] = 0x01;
scalar[1] = 0x42;
let sk = SigningKey::from_slice(&scalar).unwrap();
let mut pk = [0u8; CONTROL_PUBKEY_LEN];
pk.copy_from_slice(sk.verifying_key().to_encoded_point(false).as_bytes());
(sk, pk)
}
#[test]
fn mesh_identity_signature_roundtrips() {
let (sk, pk) = mesh_keypair();
let hh = [0xab; 32];
let sig = sign_mesh_identity(&sk, &hh);
verify_mesh_identity(&pk, &sig, &hh).expect("verify");
}
#[test]
fn mesh_identity_rejects_wrong_handshake_hash() {
let (sk, pk) = mesh_keypair();
let sig = sign_mesh_identity(&sk, &[0x01; 32]);
let err = verify_mesh_identity(&pk, &sig, &[0x02; 32]).unwrap_err();
assert!(
matches!(err, MeshIdentityError::SignatureInvalid),
"{err:?}"
);
}
#[test]
fn mesh_identity_rejects_wrong_signer() {
let (sk, _pk) = mesh_keypair();
let hh = [0xcd; 32];
let sig = sign_mesh_identity(&sk, &hh);
let mut other = [0u8; 32];
other[0] = 0x01;
other[1] = 0x99;
let other_sk = p256::ecdsa::SigningKey::from_slice(&other).unwrap();
let mut other_pk = [0u8; CONTROL_PUBKEY_LEN];
other_pk.copy_from_slice(other_sk.verifying_key().to_encoded_point(false).as_bytes());
let err = verify_mesh_identity(&other_pk, &sig, &hh).unwrap_err();
assert!(
matches!(err, MeshIdentityError::SignatureInvalid),
"{err:?}"
);
}
#[test]
fn mesh_identity_rejects_malformed_signature() {
let (_sk, pk) = mesh_keypair();
let err = verify_mesh_identity(&pk, &[0xde, 0xad], &[0x00; 32]).unwrap_err();
assert!(matches!(err, MeshIdentityError::SignatureShape), "{err:?}");
}
#[test]
fn mesh_identity_rejects_bad_pubkey() {
let (sk, _pk) = mesh_keypair();
let hh = [0x11; 32];
let sig = sign_mesh_identity(&sk, &hh);
let bogus = [0u8; CONTROL_PUBKEY_LEN];
let err = verify_mesh_identity(&bogus, &sig, &hh).unwrap_err();
assert!(matches!(err, MeshIdentityError::BadPubkey), "{err:?}");
}
#[test]
fn ports_are_distinct_and_match_design() {
assert_eq!(SYNCHRONIZER_BOOTSTRAP_PORT, 5008);
assert_eq!(MESH_VSOCK_PORT, 5009);
assert_eq!(SYNCHRONIZER_CLIENT_PORT, 5010);
assert_eq!(SYNCHRONIZER_CUSTOMER_RELAY_PORT, 5012);
let ports = [
SYNCHRONIZER_BOOTSTRAP_PORT,
MESH_VSOCK_PORT,
SYNCHRONIZER_CLIENT_PORT,
SYNCHRONIZER_CUSTOMER_RELAY_PORT,
];
for (i, p) in ports.iter().enumerate() {
for q in &ports[i + 1..] {
assert_ne!(p, q, "mesh ports must be distinct");
}
}
}
}