use crate::aead;
use crate::kex::{self, AgreementKey, AgreementPublicKey};
use crate::SessionError;
const WINDOW: u64 = 64;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Role {
Initiator,
Responder,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Sealed {
pub counter: u64,
pub tag: [u8; 16],
}
pub struct Session {
key: [u8; 32],
nonce_prefix: [u8; 3],
send_dir: u8,
recv_dir: u8,
send_counter: u64,
recv_highest: u64,
recv_window: u64,
}
impl Session {
pub fn establish(
local: &AgreementKey,
peer: &AgreementPublicKey,
salt: &[u8],
role: Role,
) -> Self {
let shared = local.shared_secret(peer);
let local_public = local.public().to_bytes();
let peer_public = peer.to_bytes();
let (initiator, responder) = match role {
Role::Initiator => (local_public, peer_public),
Role::Responder => (peer_public, local_public),
};
let okm = kex::derive(&shared, salt, &initiator, &responder);
let mut key = [0u8; 32];
key.copy_from_slice(&okm[..32]);
let mut nonce_prefix = [0u8; 3];
nonce_prefix.copy_from_slice(&okm[32..]);
let (send_dir, recv_dir) = match role {
Role::Initiator => (0, 1),
Role::Responder => (1, 0),
};
Self {
key,
nonce_prefix,
send_dir,
recv_dir,
send_counter: 0,
recv_highest: 0,
recv_window: 0,
}
}
pub fn seal(&mut self, buf: &mut [u8], aad: &[u8]) -> Sealed {
let counter = self.send_counter;
let nonce = nonce(&self.nonce_prefix, self.send_dir, counter);
let tag = aead::seal(&self.key, &nonce, aad, buf);
self.send_counter += 1;
Sealed { counter, tag }
}
pub fn open(
&mut self,
sealed: &Sealed,
buf: &mut [u8],
aad: &[u8],
) -> Result<(), SessionError> {
if !self.replay_ok(sealed.counter) {
return Err(SessionError::Replayed);
}
let nonce = nonce(&self.nonce_prefix, self.recv_dir, sealed.counter);
aead::open(&self.key, &nonce, aad, buf, &sealed.tag)?;
self.commit(sealed.counter);
Ok(())
}
fn replay_ok(&self, counter: u64) -> bool {
if counter > self.recv_highest {
return true;
}
let behind = self.recv_highest - counter;
if behind >= WINDOW {
return false;
}
(self.recv_window >> behind) & 1 == 0
}
fn commit(&mut self, counter: u64) {
if counter > self.recv_highest {
let shift = counter - self.recv_highest;
self.recv_window = if shift >= WINDOW {
1
} else {
(self.recv_window << shift) | 1
};
self.recv_highest = counter;
} else {
let behind = self.recv_highest - counter;
self.recv_window |= 1 << behind;
}
}
}
fn nonce(prefix: &[u8; 3], direction: u8, counter: u64) -> [u8; 12] {
let mut nonce = [0u8; 12];
nonce[0] = direction;
nonce[1..4].copy_from_slice(prefix);
nonce[4..].copy_from_slice(&counter.to_be_bytes());
nonce
}
#[cfg(test)]
mod tests {
use super::*;
fn pair() -> (Session, Session) {
let initiator = AgreementKey::from_seed(&[1u8; 32]);
let responder = AgreementKey::from_seed(&[2u8; 32]);
let salt = [3u8; 16];
let a = Session::establish(&initiator, &responder.public(), &salt, Role::Initiator);
let b = Session::establish(&responder, &initiator.public(), &salt, Role::Responder);
(a, b)
}
#[test]
fn a_sealed_message_opens_on_the_peer() {
let (mut a, mut b) = pair();
let mut buf = *b"hello";
let sealed = a.seal(&mut buf, b"meta");
b.open(&sealed, &mut buf, b"meta").expect("authentic");
assert_eq!(&buf, b"hello");
}
#[test]
fn the_two_sides_derive_the_same_key() {
let (mut a, mut b) = pair();
let mut up = *b"up";
let sealed_up = a.seal(&mut up, b"");
b.open(&sealed_up, &mut up, b"").expect("a to b");
let mut down = *b"down";
let sealed_down = b.seal(&mut down, b"");
a.open(&sealed_down, &mut down, b"").expect("b to a");
}
#[test]
fn a_replayed_message_is_rejected() {
let (mut a, mut b) = pair();
let mut buf = *b"once";
let sealed = a.seal(&mut buf, b"");
let mut first = buf;
b.open(&sealed, &mut first, b"").expect("first delivery");
let mut again = buf;
assert_eq!(
b.open(&sealed, &mut again, b""),
Err(SessionError::Replayed)
);
}
#[test]
fn out_of_order_within_the_window_is_accepted_once_each() {
let (mut a, mut b) = pair();
let mut payloads = [*b"00", *b"01", *b"02", *b"03"];
let sealed: [Sealed; 4] = core::array::from_fn(|i| a.seal(&mut payloads[i], b""));
for i in [3, 1, 2, 0] {
let mut buf = payloads[i];
b.open(&sealed[i], &mut buf, b"")
.expect("fresh within window");
}
let mut buf = payloads[2];
assert_eq!(
b.open(&sealed[2], &mut buf, b""),
Err(SessionError::Replayed)
);
}
#[test]
fn a_counter_older_than_the_window_is_rejected() {
let (mut a, mut b) = pair();
a.send_counter = 100;
let mut new = *b"new";
let sealed_new = a.seal(&mut new, b"");
b.open(&sealed_new, &mut new, b"")
.expect("new high counter");
a.send_counter = 0;
let mut old = *b"old";
let sealed_old = a.seal(&mut old, b"");
assert_eq!(
b.open(&sealed_old, &mut old, b""),
Err(SessionError::Replayed)
);
}
#[test]
fn a_forged_tag_does_not_advance_the_window() {
let (mut a, mut b) = pair();
let forged = Sealed {
counter: 50,
tag: [0u8; 16],
};
let mut junk = *b"junk";
assert_eq!(
b.open(&forged, &mut junk, b""),
Err(SessionError::Inauthentic)
);
let mut buf = *b"first";
let sealed = a.seal(&mut buf, b"");
b.open(&sealed, &mut buf, b"")
.expect("window was not advanced by the forgery");
}
#[test]
fn a_different_salt_yields_an_incompatible_session() {
let initiator = AgreementKey::from_seed(&[1u8; 32]);
let responder = AgreementKey::from_seed(&[2u8; 32]);
let mut a =
Session::establish(&initiator, &responder.public(), &[3u8; 16], Role::Initiator);
let mut b =
Session::establish(&responder, &initiator.public(), &[4u8; 16], Role::Responder);
let mut buf = *b"hello";
let sealed = a.seal(&mut buf, b"");
assert_eq!(
b.open(&sealed, &mut buf, b""),
Err(SessionError::Inauthentic)
);
}
}