use crate::{Error, Result, BETA_VERSION, CURRENT_VERSION};
use bramble_crypto::{
dh, hash, mac, KeyPair, Mac, PublicKey, Role, SymmetricKey, KEY_LEN, MAC_LEN,
};
use futures::io::{AsyncRead, AsyncWrite};
use rand::thread_rng;
use std::{pin::Pin, slice};
pub async fn perform_handshake<T>(
transport: T,
our_static_kp: KeyPair,
their_static_pk: PublicKey,
) -> Result<(SymmetricKey, Role)>
where
T: AsyncRead + AsyncWrite,
{
let our_bytes = our_static_kp.public().as_ref();
let their_bytes = their_static_pk.as_ref();
let role = if our_bytes < their_bytes {
Role::Alice
} else {
Role::Bob
};
let protocol = Protocol {
transport: Box::pin(transport),
role,
our_static_kp,
their_static_pk,
};
Ok((protocol.perform().await?, role))
}
struct Protocol<T>
where
T: AsyncRead + AsyncWrite,
{
transport: Pin<Box<T>>,
role: Role,
our_static_kp: KeyPair,
their_static_pk: PublicKey,
}
impl<T> Protocol<T>
where
T: AsyncRead + AsyncWrite,
{
pub async fn perform(mut self) -> Result<SymmetricKey> {
let our_ephemeral_kp = KeyPair::generate(&mut thread_rng());
let their_ephemeral_pk = if self.role == Role::Alice {
self.send_ephemeral_public_key(our_ephemeral_kp.public())
.await?;
self.receive_ephemeral_public_key().await?
} else {
let their_ephemeral_pk = self.receive_ephemeral_public_key().await?;
self.send_ephemeral_public_key(our_ephemeral_kp.public())
.await?;
their_ephemeral_pk
};
let master_key = self.derive_master_key(our_ephemeral_kp, their_ephemeral_pk)?;
if self.role == Role::Alice {
self.send_proof_of_ownership(&master_key, Role::Alice)
.await?;
self.receive_proof_of_ownership(&master_key, Role::Bob)
.await?;
} else {
self.receive_proof_of_ownership(&master_key, Role::Alice)
.await?;
self.send_proof_of_ownership(&master_key, Role::Bob).await?;
}
Ok(master_key)
}
async fn send_ephemeral_public_key(&mut self, pk: &PublicKey) -> Result<()> {
use futures::io::AsyncWriteExt;
let mut buf = [0u8; EPHEMERAL_PUBLIC_KEY_RECORD_LEN];
let (version, slice) = buf.split_at_mut(1);
let (typ, slice) = slice.split_at_mut(1);
let (len, key) = slice.split_at_mut(2);
version.copy_from_slice(&CURRENT_VERSION.to_be_bytes());
typ.copy_from_slice(&EPHEMERAL_PUBLIC_KEY_RECORD_TYPE.to_be_bytes());
len.copy_from_slice(&(KEY_LEN as u16).to_be_bytes());
key.copy_from_slice(pk.as_ref());
self.transport.write_all(&buf).await?;
Ok(())
}
async fn receive_ephemeral_public_key(&mut self) -> Result<PublicKey> {
use futures::io::AsyncReadExt;
let _version = self.read_version().await?;
let mut record_type = 0u8;
self.transport
.read_exact(slice::from_mut(&mut record_type))
.await?;
if record_type != EPHEMERAL_PUBLIC_KEY_RECORD_TYPE {
return Err(Error::InvalidRecord);
}
let mut len_bytes = [0u8; 2];
self.transport.read_exact(&mut len_bytes).await?;
let len = u16::from_be_bytes(len_bytes);
if len as usize != KEY_LEN {
return Err(Error::InvalidKey);
}
let mut pk_bytes = [0u8; KEY_LEN];
self.transport.read_exact(&mut pk_bytes).await?;
Ok(PublicKey::from(pk_bytes))
}
async fn send_proof_of_ownership(&mut self, key: &SymmetricKey, role: Role) -> Result<()> {
use futures::io::AsyncWriteExt;
let proof = self.derive_proof_of_ownership(key, role);
let mut buf = [0u8; PROOF_OF_OWNERSHIP_RECORD_LEN];
let (version, slice) = buf.split_at_mut(1);
let (typ, slice) = slice.split_at_mut(1);
let (len, key) = slice.split_at_mut(2);
version.copy_from_slice(&CURRENT_VERSION.to_be_bytes());
typ.copy_from_slice(&PROOF_OF_OWNERSHIP_RECORD_TYPE.to_be_bytes());
len.copy_from_slice(&(MAC_LEN as u16).to_be_bytes());
key.copy_from_slice(proof.as_ref());
self.transport.write_all(&buf).await?;
Ok(())
}
async fn receive_proof_of_ownership(&mut self, key: &SymmetricKey, role: Role) -> Result<()> {
use futures::io::AsyncReadExt;
let _version = self.read_version().await?;
let mut record_type = 0u8;
self.transport
.read_exact(slice::from_mut(&mut record_type))
.await?;
if record_type != PROOF_OF_OWNERSHIP_RECORD_TYPE {
return Err(Error::InvalidRecord);
}
let mut len_bytes = [0u8; 2];
self.transport.read_exact(&mut len_bytes).await?;
let len = u16::from_be_bytes(len_bytes);
if len as usize != MAC_LEN {
return Err(Error::InvalidKey);
}
let mut proof_bytes = [0u8; MAC_LEN];
self.transport.read_exact(&mut proof_bytes).await?;
let proof = Mac::from(proof_bytes);
let expected = self.derive_proof_of_ownership(key, role);
proof.verify(&expected).map_err(|_| Error::InvalidProof)
}
async fn read_version(&mut self) -> Result<u8> {
use futures::io::AsyncReadExt;
let mut version = 0u8;
self.transport
.read_exact(slice::from_mut(&mut version))
.await?;
if version == BETA_VERSION {
return Err(Error::BetaVersion);
}
#[allow(clippy::absurd_extreme_comparisons)]
if version < CURRENT_VERSION {
return Err(Error::OlderVersion);
}
if version > CURRENT_VERSION {
return Err(Error::NewerVersion);
}
Ok(version)
}
fn derive_master_key(
&self,
our_ephemeral_kp: KeyPair,
their_ephemeral_pk: PublicKey,
) -> Result<SymmetricKey> {
let raw_static = dh(self.our_static_kp.secret(), &self.their_static_pk);
let raw1 = dh(self.our_static_kp.secret(), &their_ephemeral_pk);
let raw2 = dh(our_ephemeral_kp.secret(), &self.their_static_pk);
let (
raw_static_ephemeral,
raw_ephemeral_static,
alice_static_pk,
bob_static_pk,
alice_ephemeral_pk,
bob_ephemeral_pk,
) = if self.role == Role::Alice {
(
raw1,
raw2,
self.our_static_kp.public(),
&self.their_static_pk,
our_ephemeral_kp.public(),
&their_ephemeral_pk,
)
} else {
(
raw2,
raw1,
&self.their_static_pk,
self.our_static_kp.public(),
&their_ephemeral_pk,
our_ephemeral_kp.public(),
)
};
let key = SymmetricKey::from(hash(
MASTER_KEY_LABEL,
&[
raw_static.as_ref(),
raw_static_ephemeral.as_ref(),
raw_ephemeral_static.as_ref(),
alice_static_pk.as_ref(),
bob_static_pk.as_ref(),
alice_ephemeral_pk.as_ref(),
bob_ephemeral_pk.as_ref(),
],
));
Ok(key)
}
fn derive_proof_of_ownership(&mut self, key: &SymmetricKey, role: Role) -> Mac {
let label = if role == Role::Alice {
ALICE_PROOF_LABEL
} else {
BOB_PROOF_LABEL
};
mac(label, key, &[])
}
}
const MASTER_KEY_LABEL: &[u8] = b"org.briarproject.bramble.handshake/MASTER_KEY";
const ALICE_PROOF_LABEL: &[u8] = b"org.briarproject.bramble.handshake/ALICE_PROOF";
const BOB_PROOF_LABEL: &[u8] = b"org.briarproject.bramble.handshake/BOB_PROOF";
const EPHEMERAL_PUBLIC_KEY_RECORD_LEN: usize = 1 + 1 + 2 + KEY_LEN;
const PROOF_OF_OWNERSHIP_RECORD_LEN: usize = 1 + 1 + 2 + MAC_LEN;
const EPHEMERAL_PUBLIC_KEY_RECORD_TYPE: u8 = 0;
const PROOF_OF_OWNERSHIP_RECORD_TYPE: u8 = 1;
#[cfg(test)]
mod test {
use super::*;
use bramble_common::make_duplex;
use futures::{executor::block_on, try_join};
use rand::thread_rng;
#[test]
fn both_sides_compute_the_same_master_secret() {
let (transport1, transport2) = make_duplex();
let mut rng = thread_rng();
let kp1 = KeyPair::generate(&mut rng);
let pk1 = kp1.public();
let mut rng = thread_rng();
let kp2 = KeyPair::generate(&mut rng);
let pk2 = kp2.public();
let fut1 = perform_handshake(transport1, kp1, *pk2);
let fut2 = perform_handshake(transport2, kp2, *pk1);
let ((master_key1, role1), (master_key2, role2)) =
block_on(async { try_join!(fut1, fut2) }).unwrap();
assert_eq!(master_key1, master_key2);
assert_ne!(role1, role2);
}
}