use std::net::SocketAddr;
use std::time::{Duration, Instant};
use hiss::noise::{Blake2b, ChaChaPoly, P256};
use hiss::psk::Psk;
use super::{Endpoint, EndpointOutput, IntroId};
use crate::config::Config;
use crate::constants::{INIT_PACKET_LEN, RESP_PACKET_LEN};
use crate::error::AuthError;
use crate::identity::{Identity, PublicKeyOf};
use crate::testutil::{CountingIdentity, DhCounter};
crate::channel_psk! {
pub PairingSuite<P256, ChaChaPoly, Blake2b>;
}
type Id = CountingIdentity<PairingSuite>;
type Pk = PublicKeyOf<Id>;
fn shared_psk() -> Psk {
Psk::from_bytes([0x5A; 32])
}
fn stranger_psk() -> Psk {
Psk::from_bytes([0xA5; 32])
}
#[derive(Default)]
struct Drained {
outs: Vec<EndpointOutput<PairingSuite>>,
}
impl Drained {
fn transmits(&self) -> Vec<(SocketAddr, Vec<u8>)> {
self.outs
.iter()
.filter_map(|o| match o {
EndpointOutput::Transmit(t) => Some((t.to, t.data.clone())),
_ => None,
})
.collect()
}
fn one_transmit(&self) -> (SocketAddr, Vec<u8>) {
let mut ts = self.transmits();
assert_eq!(ts.len(), 1, "expected exactly one transmit");
ts.pop().expect("checked")
}
fn intros(&self) -> Vec<IntroId> {
self.outs
.iter()
.filter_map(|o| match o {
EndpointOutput::IntroReady(id, _) => Some(*id),
_ => None,
})
.collect()
}
fn one_intro(&self) -> IntroId {
let ids = self.intros();
assert_eq!(ids.len(), 1, "expected exactly one IntroReady");
ids[0]
}
fn installs(&self) -> usize {
self.outs
.iter()
.filter(|o| matches!(o, EndpointOutput::ToConnection(..)))
.count()
}
}
struct Ep {
ep: Endpoint<Id>,
dhs: DhCounter,
public_static: Pk,
addr: SocketAddr,
}
impl Ep {
fn new(now: Instant, key_seed: u8, rng_seed: u8, addr: SocketAddr) -> Self {
let identity: Id = CountingIdentity::seeded([key_seed; 32]);
let dhs = identity.counter();
let public_static = *identity.public_static();
Ep {
ep: Endpoint::new(now, Config::default(), identity, [rng_seed; 32]),
dhs,
public_static,
addr,
}
}
fn canonical(&self) -> &[u8] {
self.public_static.as_ref()
}
fn drain(&mut self) -> Drained {
let mut d = Drained::default();
for _ in 0..100_000 {
match self.ep.poll_output() {
EndpointOutput::Timeout(_) => return d,
other => d.outs.push(other),
}
}
panic!("poll_output() did not terminate (§16.4)");
}
fn feed(&mut self, now: Instant, src: SocketAddr, dgram: &[u8]) -> Drained {
self.ep.handle_datagram(now, src, dgram);
self.drain()
}
fn connect(&mut self, now: Instant, remote: SocketAddr, peer: &Pk, psk: Psk) -> Drained {
let (conn, _core) = self
.ep
.mint_pending(now, remote, *peer, psk)
.expect("a fresh static dials");
let _ = self.drain();
self.ep.start_attempt(now, conn);
self.drain()
}
}
fn v4(last: u8, port: u16) -> SocketAddr {
format!("10.0.0.{last}:{port}").parse().expect("literal")
}
fn t0() -> Instant {
Instant::now() + Duration::from_secs(3600)
}
fn pair(now: Instant) -> (Ep, Ep) {
(
Ep::new(now, 7, 0x11, v4(1, 1)),
Ep::new(now, 9, 0x22, v4(2, 2)),
)
}
fn msg1_under(a: &mut Ep, now: Instant, b: &Ep, psk: Psk) -> Vec<u8> {
let peer = b.public_static;
let drained = a.connect(now, b.addr, &peer, psk);
let (to, data) = drained.one_transmit();
assert_eq!(to, b.addr);
assert_eq!(
data.len(),
INIT_PACKET_LEN,
"§2.3: the `psk` token puts no bytes on the wire, so an IKpsk1 \
init packet is the same length as an IK one"
);
data
}
#[test]
fn intro_reveals_the_claimed_static_at_one_dh_even_under_an_unknown_psk() {
let t = t0();
let (mut a, mut b) = pair(t);
let msg1 = msg1_under(&mut a, t, &b, stranger_psk());
let id = b.feed(t, a.addr, &msg1).one_intro();
assert_eq!(b.dhs.get(), 0, "the arrival itself is free");
let claimed = b.ep.read_identity(t, id).expect("a real msg1 is readable");
let _ = b.drain();
assert_eq!(b.dhs.get(), 1, "the intro read is exactly one `es`");
assert_eq!(
claimed.as_ref(),
a.canonical(),
"the claimed static is the dialler's, PSK notwithstanding"
);
}
#[test]
fn a_dial_without_the_psk_yields_no_session_and_costs_one_dh() {
let t = t0();
let (mut a, mut b) = pair(t);
let msg1 = msg1_under(&mut a, t, &b, stranger_psk());
let id = b.feed(t, a.addr, &msg1).one_intro();
let claimed = b.ep.read_identity(t, id).expect("readable");
let _ = b.drain();
assert_eq!(b.dhs.get(), 1);
assert_ne!(
claimed.as_ref(),
b.canonical(),
"sanity: the claim is the peer's static, not our own"
);
b.ep.reject(t, id);
let d = b.drain();
assert_eq!(
b.dhs.get(),
1,
"declining an unenrolled stranger ran the proving `ss`"
);
assert!(
d.transmits().is_empty(),
"a declined pairing answered on the wire"
);
assert_eq!(d.installs(), 0, "a declined pairing installed a session");
}
#[test]
fn wrong_psk_fails_at_authenticate() {
let t = t0();
let (mut a, mut b) = pair(t);
let msg1 = msg1_under(&mut a, t, &b, stranger_psk());
let id = b.feed(t, a.addr, &msg1).one_intro();
b.ep.read_identity(t, id).expect("readable");
let _ = b.drain();
let outcome = b.ep.authenticate(t, id, &shared_psk());
let d = b.drain();
assert!(
matches!(outcome, Err(AuthError::HandshakeFailed)),
"a wrong PSK must fail the tail tag, got {outcome:?}"
);
assert_eq!(
b.dhs.get(),
2,
"the failure costs §6.1's ordinary 2 DH — no more, and no less"
);
assert!(d.transmits().is_empty(), "a failed pairing sent msg2");
assert_eq!(d.installs(), 0);
}
#[test]
fn the_psk_is_mixed_at_complete_not_at_intro() {
let t = t0();
let (mut a, mut b) = pair(t);
let msg1 = msg1_under(&mut a, t, &b, stranger_psk());
let id = b.feed(t, a.addr, &msg1).one_intro();
let claimed =
b.ep.read_identity(t, id)
.expect("the intro read is PSK-blind");
let _ = b.drain();
assert_eq!(claimed.as_ref(), a.canonical());
assert_eq!(b.dhs.get(), 1);
assert!(
matches!(
b.ep.authenticate(t, id, &shared_psk()),
Err(AuthError::HandshakeFailed)
),
"the PSK was not mixed at `complete()` at all"
);
let _ = b.drain();
assert_eq!(b.dhs.get(), 2);
}
#[test]
fn a_matching_psk_establishes_at_the_ordinary_four_dh() {
let t = t0();
let (mut a, mut b) = pair(t);
let msg1 = msg1_under(&mut a, t, &b, shared_psk());
let id = b.feed(t, a.addr, &msg1).one_intro();
b.ep.read_identity(t, id).expect("readable");
let _ = b.drain();
assert_eq!(b.dhs.get(), 1, "§6.1 row 2");
let (peer, _ts) =
b.ep.authenticate(t, id, &shared_psk())
.expect("a matching PSK proves possession");
let _ = b.drain();
assert_eq!(b.dhs.get(), 2, "§6.1 row 3");
assert_eq!(peer.as_ref(), a.canonical());
let (_conn, _core) = b.ep.accept(t, id).expect("a fresh static accepts");
let d = b.drain();
assert_eq!(b.dhs.get(), 4, "§6.1 row 4 — the accept fast path");
let (to, data) = d.one_transmit();
assert_eq!(to, a.addr, "§5.6 anchors at the msg1 source");
assert_eq!(
data.len(),
RESP_PACKET_LEN,
"§2.3: IKpsk1's msg2 is the same length as IK's"
);
}
#[test]
fn the_psk_suite_renames_the_protocol_and_moves_no_wire_byte() {
use crate::packet::{Channel, ReferenceSuite};
assert_eq!(
<PairingSuite as Channel>::PROTOCOL_NAME,
"Noise_IKpsk1_P256_ChaChaPoly_BLAKE2b"
);
assert_ne!(
<PairingSuite as Channel>::PROTOCOL_NAME,
<ReferenceSuite as Channel>::PROTOCOL_NAME,
"the two patterns must not share an initial handshake hash"
);
assert_eq!(
<PairingSuite as Channel>::MSG1_LEN,
<ReferenceSuite as Channel>::MSG1_LEN
);
assert_eq!(
<PairingSuite as Channel>::MSG2_LEN,
<ReferenceSuite as Channel>::MSG2_LEN
);
assert_eq!(
<PairingSuite as Channel>::INIT_PACKET_LEN,
<ReferenceSuite as Channel>::INIT_PACKET_LEN
);
assert_eq!(
<PairingSuite as Channel>::RESP_PACKET_LEN,
<ReferenceSuite as Channel>::RESP_PACKET_LEN
);
assert_eq!(
<PairingSuite as Channel>::MSG1_LEN,
crate::constants::IK_MSG1_LEN
);
assert_eq!(
<PairingSuite as Channel>::MSG2_LEN,
crate::constants::IK_MSG2_LEN
);
}