use crate::crypto_types::UnsignedKeys;
use crate::test_helpers::{DummyTask, TestRandom};
use super::cookie::{Cookie, CookiePair};
use super::csn::{CombinedSequence, CombinedSequenceSnapshot};
use super::messages::*;
use super::*;
struct TestContext<S: Signaling> {
pub our_ks: KeyPair,
pub server_ks: KeyPair,
pub our_cookie: Cookie,
pub server_cookie: Cookie,
pub signaling: S,
}
impl TestContext<InitiatorSignaling> {
fn initiator(
identity: ClientIdentity,
peer_trusted_pubkey: Option<PublicKey>,
signaling_state: SignalingState,
server_handshake_state: ServerHandshakeState,
) -> TestContext<InitiatorSignaling> {
let our_ks = KeyPair::new();
let server_ks = KeyPair::new();
let our_cookie = Cookie::random();
let server_cookie = Cookie::random();
let ks = KeyPair::from_private_key(our_ks.private_key().clone());
let tasks = Tasks::new(Box::new(DummyTask::new(42)));
let mut signaling = InitiatorSignaling::new(ks, tasks, peer_trusted_pubkey, None, None);
signaling.common_mut().identity = identity;
signaling
.server_mut()
.set_handshake_state(server_handshake_state);
signaling.server_mut().cookie_pair = CookiePair {
ours: our_cookie.clone(),
theirs: Some(server_cookie.clone()),
};
signaling.server_mut().session_key = Some(server_ks.public_key().clone());
signaling
.common_mut()
.set_signaling_state_forced(signaling_state)
.expect("Could not set test signaling state");
TestContext {
our_ks,
server_ks,
our_cookie,
server_cookie,
signaling,
}
}
}
impl TestContext<ResponderSignaling> {
fn responder(
identity: ClientIdentity,
signaling_state: SignalingState,
server_handshake_state: ServerHandshakeState,
initiator_pubkey: Option<PublicKey>,
auth_token: Option<AuthToken>,
) -> TestContext<ResponderSignaling> {
let our_ks = KeyPair::new();
let server_ks = KeyPair::new();
let our_cookie = Cookie::random();
let server_cookie = Cookie::random();
let mut signaling = {
let pk = match initiator_pubkey {
Some(pk) => pk,
None => PublicKey::from([0u8; 32]),
};
let ks = KeyPair::from_private_key(our_ks.private_key().clone());
let mut tasks = Tasks::new(Box::new(DummyTask::new(23)));
tasks.add_task(Box::new(DummyTask::new(42))).unwrap();
ResponderSignaling::new(ks, pk, auth_token, None, tasks, None)
};
signaling.common_mut().identity = identity;
signaling
.server_mut()
.set_handshake_state(server_handshake_state);
signaling.server_mut().cookie_pair = CookiePair {
ours: our_cookie.clone(),
theirs: Some(server_cookie.clone()),
};
signaling.server_mut().session_key = Some(server_ks.public_key().clone());
signaling
.common_mut()
.set_signaling_state_forced(signaling_state)
.expect("Could not set test signaling state");
TestContext {
our_ks,
server_ks,
our_cookie,
server_cookie,
signaling,
}
}
}
#[derive(Debug)]
struct TestMsgBuilder {
msg: Message,
src: Option<Address>,
dest: Option<Address>,
}
impl TestMsgBuilder {
pub fn new(msg: Message) -> Self {
TestMsgBuilder {
msg,
src: None,
dest: None,
}
}
pub fn from(mut self, addr: u8) -> Self {
self.src = Some(Address(addr));
self
}
pub fn to(mut self, addr: u8) -> Self {
self.dest = Some(Address(addr));
self
}
pub fn build(self, cookie: Cookie, kp: &KeyPair, pubkey: &PublicKey) -> ByteBox {
self.build_with_csn(cookie, kp, pubkey, CombinedSequenceSnapshot::random())
}
pub fn build_with_csn(
self,
cookie: Cookie,
kp: &KeyPair,
pubkey: &PublicKey,
csn: CombinedSequenceSnapshot,
) -> ByteBox {
let nonce = Nonce::new(
cookie,
self.src.expect("Source not set"),
self.dest.expect("Destination not set"),
csn,
);
let obox = OpenBox::<Message>::new(self.msg, nonce);
obox.encrypt(kp, pubkey).expect("Failed to encrypt")
}
pub fn build_from_server<S: Signaling>(self, ctx: &TestContext<S>) -> ByteBox {
self.build(
ctx.server_cookie.clone(),
&ctx.server_ks,
ctx.our_ks.public_key(),
)
}
}
mod server_auth {
use super::*;
fn assert_client_info_sent_fail<S: Signaling>(
ctx: &mut TestContext<S>,
bbox: ByteBox,
error: SignalingError,
) {
assert_eq!(
ctx.signaling.server().handshake_state(),
ServerHandshakeState::ClientInfoSent
);
assert_eq!(ctx.signaling.handle_message(bbox), Err(error))
}
#[test]
fn no_identity() {
let ctx = TestContext::responder(
ClientIdentity::Unknown,
SignalingState::ServerHandshake,
ServerHandshakeState::ClientInfoSent,
None,
None,
);
let msg = ServerAuth::for_responder(ctx.our_cookie.clone(), None, false).into_message();
let bbox = TestMsgBuilder::new(msg)
.from(0)
.to(13)
.build_from_server(&ctx);
let mut s = ctx.signaling;
assert_eq!(
s.server().handshake_state(),
ServerHandshakeState::ClientInfoSent
);
let _actions = s.handle_message(bbox).unwrap();
assert_eq!(s.identity(), ClientIdentity::Responder(13));
}
#[test]
fn your_cookie() {
let mut ctx = TestContext::initiator(
ClientIdentity::Initiator,
None,
SignalingState::ServerHandshake,
ServerHandshakeState::ClientInfoSent,
);
let msg = ServerAuth::for_initiator(Cookie::random(), None, vec![]).into_message();
let bbox = TestMsgBuilder::new(msg)
.from(0)
.to(1)
.build_from_server(&ctx);
assert_client_info_sent_fail(
&mut ctx,
bbox,
SignalingError::Protocol(
"Repeated cookie in auth message from server does not match our cookie".into(),
),
);
}
#[test]
fn initiator_wrong_fields() {
let mut ctx = TestContext::initiator(
ClientIdentity::Initiator,
None,
SignalingState::ServerHandshake,
ServerHandshakeState::ClientInfoSent,
);
let msg = ServerAuth::for_responder(ctx.our_cookie.clone(), None, true).into_message();
let bbox = TestMsgBuilder::new(msg)
.from(0)
.to(1)
.build_from_server(&ctx);
assert_client_info_sent_fail(&mut ctx, bbox,
SignalingError::InvalidMessage(
"We're the initiator, but the `initiator_connected` field in the server-auth message is set".into()));
}
#[test]
fn initiator_missing_fields() {
let mut ctx = TestContext::initiator(
ClientIdentity::Initiator,
None,
SignalingState::ServerHandshake,
ServerHandshakeState::ClientInfoSent,
);
let msg = ServerAuth {
your_cookie: ctx.our_cookie.clone(),
signed_keys: None,
responders: None,
initiator_connected: None,
}
.into_message();
let bbox = TestMsgBuilder::new(msg)
.from(0)
.to(1)
.build_from_server(&ctx);
assert_client_info_sent_fail(
&mut ctx,
bbox,
SignalingError::InvalidMessage(
"`responders` field in server-auth message not set".into(),
),
);
}
#[test]
fn initiator_duplicate_fields() {
let mut ctx = TestContext::initiator(
ClientIdentity::Initiator,
None,
SignalingState::ServerHandshake,
ServerHandshakeState::ClientInfoSent,
);
let msg = ServerAuth::for_initiator(
ctx.our_cookie.clone(),
None,
vec![Address(2), Address(3), Address(3)],
)
.into_message();
let bbox = TestMsgBuilder::new(msg)
.from(0)
.to(1)
.build_from_server(&ctx);
assert_client_info_sent_fail(
&mut ctx,
bbox,
SignalingError::InvalidMessage(
"`responders` field in server-auth message may not contain duplicates".into(),
),
);
}
#[test]
fn initiator_invalid_fields() {
let mut ctx = TestContext::initiator(
ClientIdentity::Initiator,
None,
SignalingState::ServerHandshake,
ServerHandshakeState::ClientInfoSent,
);
let msg = ServerAuth::for_initiator(
ctx.our_cookie.clone(),
None,
vec![Address(1), Address(2), Address(3)],
)
.into_message();
let bbox = TestMsgBuilder::new(msg)
.from(0)
.to(1)
.build_from_server(&ctx);
assert_client_info_sent_fail(
&mut ctx,
bbox,
SignalingError::InvalidMessage(
"`responders` field in server-auth message may not contain addresses <0x02".into(),
),
);
}
#[test]
fn initiator_stored_responder() {
let ctx = TestContext::initiator(
ClientIdentity::Initiator,
None,
SignalingState::ServerHandshake,
ServerHandshakeState::ClientInfoSent,
);
let msg =
ServerAuth::for_initiator(ctx.our_cookie.clone(), None, vec![Address(2), Address(3)])
.into_message();
let bbox = TestMsgBuilder::new(msg)
.from(0)
.to(1)
.build_from_server(&ctx);
let mut s = ctx.signaling;
assert_eq!(
s.server().handshake_state(),
ServerHandshakeState::ClientInfoSent
);
assert_eq!(s.responders.len(), 0);
let _actions = s.handle_message(bbox).unwrap();
assert_eq!(s.server().handshake_state(), ServerHandshakeState::Done);
assert_eq!(s.responders.len(), 2);
}
#[test]
fn responder_validate_initiator_connected() {
let mut ctx = TestContext::responder(
ClientIdentity::Responder(4),
SignalingState::ServerHandshake,
ServerHandshakeState::ClientInfoSent,
None,
None,
);
let msg = ServerAuth {
your_cookie: ctx.our_cookie.clone(),
signed_keys: None,
responders: None,
initiator_connected: None,
}
.into_message();
let bbox = TestMsgBuilder::new(msg)
.from(0)
.to(4)
.build_from_server(&ctx);
assert_client_info_sent_fail(&mut ctx, bbox,
SignalingError::InvalidMessage(
"We're a responder, but the `initiator_connected` field in the server-auth message is not set".into()));
}
fn _server_auth_respond(ctx: TestContext<ResponderSignaling>) -> Vec<HandleAction> {
let msg = ServerAuth {
your_cookie: ctx.our_cookie.clone(),
signed_keys: None,
responders: None,
initiator_connected: Some(true),
}
.into_message();
let bbox = TestMsgBuilder::new(msg)
.from(0)
.to(7)
.build_from_server(&ctx);
let mut s = ctx.signaling;
assert_eq!(
s.server().handshake_state(),
ServerHandshakeState::ClientInfoSent
);
assert_eq!(s.initiator.handshake_state(), InitiatorHandshakeState::New);
let actions = s.handle_message(bbox).unwrap();
assert_eq!(s.server().handshake_state(), ServerHandshakeState::Done);
assert_eq!(
s.initiator.handshake_state(),
InitiatorHandshakeState::KeySent
);
actions
}
#[test]
fn respond_initiator_with_token() {
let ctx = TestContext::responder(
ClientIdentity::Responder(7),
SignalingState::ServerHandshake,
ServerHandshakeState::ClientInfoSent,
None,
Some(AuthToken::new()),
);
let actions = _server_auth_respond(ctx);
assert_eq!(actions.len(), 3);
assert_eq!(
actions[2],
HandleAction::Event(Event::ServerHandshakeDone(true))
);
}
#[test]
fn respond_initiator_without_token() {
let ctx = TestContext::responder(
ClientIdentity::Responder(7),
SignalingState::ServerHandshake,
ServerHandshakeState::ClientInfoSent,
None,
None,
);
let actions = _server_auth_respond(ctx);
assert_eq!(actions.len(), 2);
assert_eq!(
actions[1],
HandleAction::Event(Event::ServerHandshakeDone(true))
);
}
#[test]
fn signaling_state_transition() {
let ctx = TestContext::responder(
ClientIdentity::Responder(7),
SignalingState::ServerHandshake,
ServerHandshakeState::ClientInfoSent,
None,
None,
);
let msg = ServerAuth {
your_cookie: ctx.our_cookie.clone(),
signed_keys: None,
responders: None,
initiator_connected: Some(false),
}
.into_message();
let bbox = TestMsgBuilder::new(msg)
.from(0)
.to(7)
.build_from_server(&ctx);
let mut s = ctx.signaling;
assert_eq!(
s.server().handshake_state(),
ServerHandshakeState::ClientInfoSent
);
assert_eq!(
s.common().signaling_state(),
SignalingState::ServerHandshake
);
let actions = s.handle_message(bbox).unwrap();
assert_eq!(s.server().handshake_state(), ServerHandshakeState::Done);
assert_eq!(s.common().signaling_state(), SignalingState::PeerHandshake);
assert_eq!(
actions,
vec![HandleAction::Event(Event::ServerHandshakeDone(false)),]
);
}
fn _server_public_permanent_key_validate(
correct_content: bool,
) -> (TestContext<InitiatorSignaling>, ByteBox) {
let server_permanent_ks1 = KeyPair::new();
let mut ctx = TestContext::initiator(
ClientIdentity::Initiator,
None,
SignalingState::ServerHandshake,
ServerHandshakeState::ClientInfoSent,
);
ctx.signaling.server_mut().permanent_key = Some(server_permanent_ks1.public_key().clone());
let nonce = Nonce::new(
ctx.server_cookie.clone(),
Address(0),
Address(1),
CombinedSequenceSnapshot::random(),
);
let unsigned_keys = UnsignedKeys::new(
ctx.signaling.server().session_key().unwrap().clone(),
if correct_content {
ctx.our_ks.public_key().clone()
} else {
PublicKey::from([1; 32])
},
);
let signed_keys =
unsigned_keys.sign(&server_permanent_ks1, ctx.our_ks.public_key(), unsafe {
nonce.clone()
});
let msg = ServerAuth::for_initiator(ctx.our_cookie.clone(), Some(signed_keys), vec![])
.into_message();
let msg_bytes = msg.to_msgpack();
let encrypted = ctx
.our_ks
.encrypt(
&msg_bytes,
unsafe { nonce.clone() },
ctx.server_ks.public_key(),
)
.unwrap();
let bbox = ByteBox::new(encrypted, nonce);
(ctx, bbox)
}
#[test]
fn server_public_permanent_key_decrypt_failed() {
let (mut ctx, bbox) = _server_public_permanent_key_validate(true);
let server_permanent_ks2 = KeyPair::new();
ctx.signaling.server_mut().permanent_key = Some(server_permanent_ks2.public_key().clone());
let mut s = ctx.signaling;
assert_eq!(
s.server().handshake_state(),
ServerHandshakeState::ClientInfoSent
);
assert_eq!(
s.handle_message(bbox),
Err(SignalingError::Crypto(
"Could not decrypt signed keys".into()
))
);
assert_eq!(
s.server().handshake_state(),
ServerHandshakeState::ClientInfoSent
);
}
#[test]
fn server_public_permanent_key_compare_failed() {
let (ctx, bbox) = _server_public_permanent_key_validate(false);
let mut s = ctx.signaling;
assert_eq!(
s.server().handshake_state(),
ServerHandshakeState::ClientInfoSent
);
assert_eq!(
s.handle_message(bbox),
Err(SignalingError::Protocol(
"Our public permanent key sent in `signed_keys` is not valid".into()
))
);
assert_eq!(
s.server().handshake_state(),
ServerHandshakeState::ClientInfoSent
);
}
#[test]
fn server_public_permanent_key_compare_success() {
let (ctx, bbox) = _server_public_permanent_key_validate(true);
let mut s = ctx.signaling;
assert_eq!(
s.server().handshake_state(),
ServerHandshakeState::ClientInfoSent
);
assert!(s.handle_message(bbox).is_ok());
assert_eq!(s.server().handshake_state(), ServerHandshakeState::Done);
}
}
mod client_auth {
use super::*;
fn _test_ping_interval(interval: Option<Duration>) -> ClientAuth {
let kp = KeyPair::new();
let mut s = InitiatorSignaling::new(
kp,
Tasks::new(Box::new(DummyTask::new(123))),
None,
None,
interval,
);
let server_pubkey = PublicKey::random();
let server_hello = ServerHello::new(server_pubkey.clone()).into_message();
let cs = CombinedSequenceSnapshot::random();
let nonce = Nonce::new(Cookie::random(), Address(0), Address(0), cs);
let obox = OpenBox::<Message>::new(server_hello, nonce);
let bbox = obox.encode();
assert_eq!(s.server().handshake_state(), ServerHandshakeState::New);
let mut actions = s.handle_message(bbox).unwrap();
assert_eq!(
s.server().handshake_state(),
ServerHandshakeState::ClientInfoSent
);
assert_eq!(actions.len(), 1);
let action = actions.remove(0);
let bytes: ByteBox = match action {
HandleAction::Reply(bbox) => bbox,
HandleAction::HandshakeDone => panic!("Unexpected HandshakeDone"),
HandleAction::HandshakeError(_) => panic!("Unexpected HandshakeError"),
HandleAction::TaskMessage(_) => panic!("Unexpected TaskMessage"),
HandleAction::Event(_) => panic!("Unexpected Event"),
};
let decrypted =
OpenBox::<Message>::decrypt(bytes, &s.common().permanent_keypair, &server_pubkey)
.unwrap();
match decrypted.message {
Message::ClientAuth(client_auth) => client_auth,
other => panic!("Expected ClientAuth, got {:?}", other),
}
}
#[test]
fn ping_interval_none() {
let client_auth = _test_ping_interval(None);
assert_eq!(client_auth.ping_interval, 0);
}
#[test]
fn ping_interval_zero() {
let client_auth = _test_ping_interval(Some(Duration::from_secs(0)));
assert_eq!(client_auth.ping_interval, 0);
}
#[test]
fn ping_interval_12345() {
let client_auth = _test_ping_interval(Some(Duration::from_secs(12345)));
assert_eq!(client_auth.ping_interval, 12345);
}
#[test]
fn ping_interval_nanos() {
let client_auth = _test_ping_interval(Some(Duration::new(123, 45)));
assert_eq!(client_auth.ping_interval, 123);
}
}
mod token {
use super::*;
#[test]
fn token_initiator_validate_public_key() {
let mut ctx = TestContext::initiator(
ClientIdentity::Initiator,
None,
SignalingState::PeerHandshake,
ServerHandshakeState::Done,
);
let addr = Address(3);
let responder = ResponderContext::new(addr, 0);
ctx.signaling.responders.insert(addr, responder);
let msg_bytes = [
0x82, 0xa4, 0x74, 0x79, 0x70, 0x65, 0xa5, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0xa3, 0x6b, 0x65, 0x79, 0xc4, 0x03, 0x00, 0x01, 0x02,
];
let cookie = Cookie::random();
let nonce = Nonce::new(
cookie,
Address(3),
Address(1),
CombinedSequenceSnapshot::random(),
);
let encrypted = ctx
.signaling
.auth_token()
.expect("Could not get auth token")
.encrypt(&msg_bytes, unsafe { nonce.clone() })
.unwrap();
let bbox = ByteBox::new(encrypted, nonce);
let err = ctx.signaling.handle_message(bbox).unwrap_err();
assert_eq!(err, SignalingError::Decode(
"Cannot decode message payload: Decoding error: Could not decode msgpack data: error while decoding value".into()
));
}
#[test]
fn token_initiator_cannot_decrypt() {
}
#[test]
fn token_initiator_set_public_key() {
let mut ctx = TestContext::initiator(
ClientIdentity::Initiator,
None,
SignalingState::PeerHandshake,
ServerHandshakeState::Done,
);
let addr = Address(3);
let responder = ResponderContext::new(addr, 0);
ctx.signaling.responders.insert(addr, responder);
let pk = PublicKey::random();
let msg: Message = Token { key: pk.clone() }.into_message();
let msg_bytes = msg.to_msgpack();
let cookie = Cookie::random();
let nonce = Nonce::new(
cookie,
Address(3),
Address(1),
CombinedSequenceSnapshot::random(),
);
let encrypted = ctx
.signaling
.auth_token()
.expect("Could not get auth token")
.encrypt(&msg_bytes, unsafe { nonce.clone() })
.unwrap();
let bbox = ByteBox::new(encrypted, nonce);
{
let responder = ctx.signaling.responders.get(&addr).unwrap();
assert_eq!(responder.handshake_state(), ResponderHandshakeState::New);
assert!(responder.permanent_key.is_none());
}
let actions = ctx.signaling.handle_message(bbox).unwrap();
{
let responder = ctx.signaling.responders.get(&addr).unwrap();
assert_eq!(
responder.handshake_state(),
ResponderHandshakeState::TokenReceived
);
assert_eq!(responder.permanent_key, Some(pk));
assert_eq!(actions, vec![]);
}
}
}
mod key {
use super::*;
#[test]
fn key_initiator_success() {
let mut ctx = TestContext::initiator(
ClientIdentity::Initiator,
None,
SignalingState::PeerHandshake,
ServerHandshakeState::Done,
);
let peer_permanent_pk = PublicKey::random();
let peer_session_pk = PublicKey::random();
let cookie = Cookie::random();
let addr = Address(3);
let mut responder = ResponderContext::new(addr, 0);
responder.set_handshake_state(ResponderHandshakeState::TokenReceived);
responder.permanent_key = Some(peer_permanent_pk.clone());
let msg: Message = Key {
key: peer_session_pk.clone(),
}
.into_message();
let bbox =
TestMsgBuilder::new(msg)
.from(3)
.to(1)
.build(cookie, &ctx.our_ks, &peer_permanent_pk);
ctx.signaling.responders.insert(addr, responder);
{
let responder = ctx.signaling.responders.get(&addr).unwrap();
assert_eq!(
responder.handshake_state(),
ResponderHandshakeState::TokenReceived
);
assert!(responder.session_key.is_none());
}
let actions = ctx.signaling.handle_message(bbox).unwrap();
{
let responder = ctx.signaling.responders.get(&addr).unwrap();
assert_eq!(
responder.handshake_state(),
ResponderHandshakeState::KeySent
);
assert_eq!(responder.session_key, Some(peer_session_pk));
assert_eq!(actions.len(), 1); }
}
#[test]
fn key_responder_success() {
let peer_permanent_pk = PublicKey::random();
let peer_session_pk = PublicKey::random();
let cookie = Cookie::random();
let mut ctx = TestContext::responder(
ClientIdentity::Responder(6),
SignalingState::PeerHandshake,
ServerHandshakeState::Done,
Some(peer_permanent_pk.clone()),
None,
);
assert_eq!(
ctx.signaling.initiator.permanent_key,
peer_permanent_pk.clone()
);
ctx.signaling
.initiator
.set_handshake_state(InitiatorHandshakeState::KeySent);
let msg: Message = Key {
key: peer_session_pk.clone(),
}
.into_message();
let bbox =
TestMsgBuilder::new(msg)
.from(1)
.to(6)
.build(cookie, &ctx.our_ks, &peer_permanent_pk);
assert_eq!(
ctx.signaling.initiator.handshake_state(),
InitiatorHandshakeState::KeySent
);
assert_eq!(ctx.signaling.initiator.session_key, None);
let actions = ctx.signaling.handle_message(bbox).unwrap();
assert_eq!(
ctx.signaling.initiator.handshake_state(),
InitiatorHandshakeState::AuthSent
);
assert_eq!(ctx.signaling.initiator.session_key, Some(peer_session_pk));
assert_eq!(actions.len(), 1); }
}
mod auth {
use super::*;
fn _auth_msg_prepare_initiator() -> (TestContext<InitiatorSignaling>, ResponderContext) {
let mut ctx = TestContext::initiator(
ClientIdentity::Initiator,
None,
SignalingState::PeerHandshake,
ServerHandshakeState::Done,
);
let peer_session_pk = PublicKey::random();
let mut responder = ResponderContext::new(Address(3), 0);
responder.set_handshake_state(ResponderHandshakeState::KeySent);
responder.session_key = Some(peer_session_pk.clone());
fn make_responder(addr: u8, state: ResponderHandshakeState) -> ResponderContext {
let mut r = ResponderContext::new(Address(addr), 0);
r.set_handshake_state(state);
r.session_key = Some(PublicKey::random());
r
}
ctx.signaling
.responders
.insert(Address(4), make_responder(4, ResponderHandshakeState::New));
ctx.signaling.responders.insert(
Address(7),
make_responder(7, ResponderHandshakeState::KeySent),
);
(ctx, responder)
}
fn _auth_msg_prepare_responder() -> TestContext<ResponderSignaling> {
let mut ctx = TestContext::responder(
ClientIdentity::Responder(3),
SignalingState::PeerHandshake,
ServerHandshakeState::Done,
Some(PublicKey::random()),
Some(AuthToken::new()),
);
ctx.signaling
.initiator
.set_handshake_state(InitiatorHandshakeState::AuthSent);
ctx.signaling.initiator.session_key = Some(PublicKey::random());
ctx
}
fn _auth_msg_handle_initiator(
msg: Message,
ctx: &mut TestContext<InitiatorSignaling>,
responder: ResponderContext,
) -> SignalingResult<Vec<HandleAction>> {
let bbox = TestMsgBuilder::new(msg).from(3).to(1).build(
Cookie::random(),
&responder.keypair,
responder.session_key.as_ref().unwrap(),
);
ctx.signaling
.responders
.insert(responder.address, responder);
ctx.signaling.handle_message(bbox)
}
fn _auth_msg_handle_responder(
msg: Message,
ctx: &mut TestContext<ResponderSignaling>,
) -> SignalingResult<Vec<HandleAction>> {
let bbox = TestMsgBuilder::new(msg).from(1).to(3).build(
Cookie::random(),
&ctx.signaling.initiator.keypair,
ctx.signaling.initiator.session_key.as_ref().unwrap(),
);
ctx.signaling.handle_message(bbox)
}
#[test]
fn initiator_validate_repeated_cookie() {
let (mut ctx, responder) = _auth_msg_prepare_initiator();
let msg: Message = ResponderAuthBuilder::new(Cookie::random()) .add_task("dummy", None)
.build()
.unwrap()
.into_message();
let err = _auth_msg_handle_initiator(msg, &mut ctx, responder).unwrap_err();
assert_eq!(
err,
SignalingError::Protocol(
"Repeated cookie in auth message from responder 0x03 does not match our cookie"
.into()
)
);
}
#[test]
fn responder_validate_repeated_cookie() {
let mut ctx = _auth_msg_prepare_responder();
let msg: Message = ResponderAuthBuilder::new(Cookie::random()) .add_task("dummy", None)
.build()
.unwrap()
.into_message();
let err = _auth_msg_handle_responder(msg, &mut ctx).unwrap_err();
assert_eq!(
err,
SignalingError::Protocol(
"Repeated cookie in auth message from initiator does not match our cookie".into()
)
);
}
#[test]
fn initiator_task_field() {
let (mut ctx, responder) = _auth_msg_prepare_initiator();
let msg: Message = Auth {
your_cookie: responder.cookie_pair.ours.clone(),
task: Some("foo".into()),
tasks: None,
data: HashMap::new(),
}
.into_message();
let err = _auth_msg_handle_initiator(msg, &mut ctx, responder).unwrap_err();
assert_eq!(
err,
SignalingError::InvalidMessage(
"We're an initiator, but the `task` field in the auth message is set".into()
)
);
}
#[test]
fn initiator_tasks_field_missing() {
let (mut ctx, responder) = _auth_msg_prepare_initiator();
let msg: Message = Auth {
your_cookie: responder.cookie_pair.ours.clone(),
task: None,
tasks: None,
data: HashMap::new(),
}
.into_message();
let err = _auth_msg_handle_initiator(msg, &mut ctx, responder).unwrap_err();
assert_eq!(
err,
SignalingError::InvalidMessage(
"The `tasks` field in the auth message is not set".into()
)
);
}
#[test]
fn initiator_tasks_field_empty() {
let (mut ctx, responder) = _auth_msg_prepare_initiator();
let msg: Message = Auth {
your_cookie: responder.cookie_pair.ours.clone(),
task: None,
tasks: Some(vec![]),
data: HashMap::new(),
}
.into_message();
let err = _auth_msg_handle_initiator(msg, &mut ctx, responder).unwrap_err();
assert_eq!(
err,
SignalingError::InvalidMessage("The `tasks` field in the auth message is empty".into())
);
}
#[test]
fn responder_tasks_field() {
let mut ctx = _auth_msg_prepare_responder();
let msg: Message = Auth {
your_cookie: ctx.signaling.initiator.cookie_pair.ours.clone(),
task: None,
tasks: Some(vec!["asjk".into()]),
data: HashMap::new(),
}
.into_message();
let err = _auth_msg_handle_responder(msg, &mut ctx).unwrap_err();
assert_eq!(
err,
SignalingError::InvalidMessage(
"We're a responder, but the `tasks` field in the auth message is set".into()
)
);
}
#[test]
fn responder_task_field_missing() {
let mut ctx = _auth_msg_prepare_responder();
let msg: Message = Auth {
your_cookie: ctx.signaling.initiator.cookie_pair.ours.clone(),
task: None,
tasks: None,
data: HashMap::new(),
}
.into_message();
let err = _auth_msg_handle_responder(msg, &mut ctx).unwrap_err();
assert_eq!(
err,
SignalingError::InvalidMessage(
"The `task` field in the auth message is not set".into()
)
);
}
#[test]
fn responder_task_field_unknown() {
let mut ctx = _auth_msg_prepare_responder();
let msg: Message = Auth {
your_cookie: ctx.signaling.initiator.cookie_pair.ours.clone(),
task: Some("unknown".into()),
tasks: None,
data: HashMap::new(),
}
.into_message();
let err = _auth_msg_handle_responder(msg, &mut ctx).unwrap_err();
assert_eq!(
err,
SignalingError::Protocol(
"The `task` field in the auth message contains an unknown task".into()
)
);
}
#[test]
fn initiator_data_field_length_mismatch() {
let (mut ctx, responder) = _auth_msg_prepare_initiator();
let msg: Message = Auth {
your_cookie: responder.cookie_pair.ours.clone(),
task: None,
tasks: Some(vec!["a".into(), "b".into()]),
data: {
let mut m = HashMap::new();
m.insert("a".into(), None);
m
},
}
.into_message();
let err = _auth_msg_handle_initiator(msg, &mut ctx, responder).unwrap_err();
assert_eq!(err, SignalingError::InvalidMessage(
"The `tasks` and `data` fields in the auth message have a different number of entries".into()));
}
#[test]
fn responder_data_field_empty() {
let mut ctx = _auth_msg_prepare_responder();
let msg: Message = Auth {
your_cookie: ctx.signaling.initiator.cookie_pair.ours.clone(),
task: Some("dummy.42".into()),
tasks: None,
data: HashMap::new(),
}
.into_message();
let err = _auth_msg_handle_responder(msg, &mut ctx).unwrap_err();
assert_eq!(
err,
SignalingError::Protocol("The `data` field in the auth message is empty".into())
);
}
#[test]
fn responder_data_field_multiple() {
let mut ctx = _auth_msg_prepare_responder();
let msg: Message = Auth {
your_cookie: ctx.signaling.initiator.cookie_pair.ours.clone(),
task: Some("dummy.42".into()),
tasks: None,
data: {
let mut m = HashMap::new();
m.insert("a".into(), None);
m.insert("b".into(), None);
m
},
}
.into_message();
let err = _auth_msg_handle_responder(msg, &mut ctx).unwrap_err();
assert_eq!(
err,
SignalingError::Protocol(
"The `data` field in the auth message contains more than one entry".into()
)
);
}
#[test]
fn initiator_data_field_key_mismatch() {
let (mut ctx, responder) = _auth_msg_prepare_initiator();
let msg: Message = Auth {
your_cookie: responder.cookie_pair.ours.clone(),
task: None,
tasks: Some(vec!["a".into(), "b".into()]),
data: {
let mut m = HashMap::new();
m.insert("a".into(), None);
m.insert("c".into(), None);
m
},
}
.into_message();
let err = _auth_msg_handle_initiator(msg, &mut ctx, responder).unwrap_err();
assert_eq!(
err,
SignalingError::InvalidMessage(
"The task \"b\" in the auth message does not have a corresponding data entry"
.into()
)
);
}
#[test]
fn responder_data_key_not_found() {
let mut ctx = _auth_msg_prepare_responder();
let msg: Message = Auth {
your_cookie: ctx.signaling.initiator.cookie_pair.ours.clone(),
task: Some(DummyTask::name_for(42)),
tasks: None,
data: {
let mut m = HashMap::new();
m.insert("a".into(), None);
m
},
}
.into_message();
let err = _auth_msg_handle_responder(msg, &mut ctx).unwrap_err();
assert_eq!(
err,
SignalingError::Protocol(
"The task in the auth message does not have a corresponding data entry".into()
)
);
}
#[test]
fn initiator_choose_task() {
let (mut ctx, responder) = _auth_msg_prepare_initiator();
let msg: Message = Auth {
your_cookie: responder.cookie_pair.ours.clone(),
task: None,
tasks: Some(vec!["a".into(), DummyTask::name_for(42)]),
data: {
let mut m = HashMap::new();
m.insert("a".into(), None);
m.insert(DummyTask::name_for(42), None);
m
},
}
.into_message();
assert!(ctx.signaling.common().task.is_none());
assert_eq!(ctx.signaling.common().tasks.as_ref().unwrap().len(), 1);
assert_eq!(
ctx.signaling.common().signaling_state(),
SignalingState::PeerHandshake
);
let actions = _auth_msg_handle_initiator(msg, &mut ctx, responder).unwrap();
assert!(ctx.signaling.common().task.is_some());
assert_eq!(
ctx.signaling
.common()
.task
.as_ref()
.unwrap()
.lock()
.unwrap()
.name(),
DummyTask::name_for(42)
);
assert!(ctx.signaling.common().tasks.is_none());
assert_eq!(ctx.signaling.responders.len(), 0);
assert!(ctx.signaling.get_peer().is_some());
assert_eq!(
ctx.signaling.get_peer().as_ref().unwrap().identity(),
ctx.signaling.responder.as_ref().unwrap().identity()
);
assert_eq!(actions.len(), 4);
assert_eq!(
ctx.signaling.common().signaling_state(),
SignalingState::Task
);
assert_eq!(
ctx.signaling.responder.unwrap().handshake_state(),
ResponderHandshakeState::AuthSent
);
}
#[test]
fn responder_choose_task() {
let mut ctx = _auth_msg_prepare_responder();
let msg: Message = Auth {
your_cookie: ctx.signaling.initiator.cookie_pair.ours.clone(),
task: Some(DummyTask::name_for(42)),
tasks: None,
data: {
let mut m = HashMap::new();
m.insert(DummyTask::name_for(42), None);
m
},
}
.into_message();
assert!(ctx.signaling.common().task.is_none());
assert_eq!(ctx.signaling.common().tasks.as_ref().unwrap().len(), 2);
assert_eq!(
ctx.signaling.common().signaling_state(),
SignalingState::PeerHandshake
);
let actions = _auth_msg_handle_responder(msg, &mut ctx).unwrap();
assert!(ctx.signaling.common().task.is_some());
assert_eq!(
ctx.signaling
.common()
.task
.as_ref()
.unwrap()
.lock()
.unwrap()
.name(),
DummyTask::name_for(42)
);
assert!(ctx.signaling.common().tasks.is_none());
assert!(ctx.signaling.get_peer().is_some());
assert_eq!(
ctx.signaling.get_peer().as_ref().unwrap().identity(),
ctx.signaling.initiator.identity()
);
assert_eq!(actions, vec![HandleAction::HandshakeDone]);
assert_eq!(
ctx.signaling.common().signaling_state(),
SignalingState::Task
);
assert_eq!(
ctx.signaling.initiator.handshake_state(),
InitiatorHandshakeState::AuthReceived
);
}
#[test]
fn responder_auth_tasks_no_duplicates_simple() {
let simple = ResponderAuthBuilder::new(Cookie::random())
.add_task("dummy1", None)
.add_task("dummy1", None)
.build();
assert_eq!(
simple,
Err(SignalingError::InvalidMessage(
"An `Auth` message may not contain duplicate tasks".into()
)),
);
let nonconsecutive = ResponderAuthBuilder::new(Cookie::random())
.add_task("dummy1", None)
.add_task("dummy2", None)
.add_task("dummy1", None)
.build();
assert_eq!(
nonconsecutive,
Err(SignalingError::InvalidMessage(
"An `Auth` message may not contain duplicate tasks".into()
)),
);
let different_data = ResponderAuthBuilder::new(Cookie::random())
.add_task("dummy1", None)
.add_task(
"dummy1",
Some({
let mut data = HashMap::new();
data.insert("a".into(), 1.into());
data
}),
)
.build();
assert_eq!(
different_data,
Err(SignalingError::InvalidMessage(
"An `Auth` message may not contain duplicate tasks".into()
)),
);
}
}
mod new_initiator {
use super::*;
#[test]
fn handle_as_initiator() {
let mut ctx = TestContext::initiator(
ClientIdentity::Initiator,
None,
SignalingState::PeerHandshake,
ServerHandshakeState::Done,
);
let msg = Message::NewInitiator(NewInitiator);
let bbox = TestMsgBuilder::new(msg).from(0).to(1).build(
ctx.server_cookie.clone(),
&ctx.server_ks,
ctx.our_ks.public_key(),
);
let err = ctx.signaling.handle_message(bbox).unwrap_err();
let msg = "Received \'new-responder\' message as initiator".into();
assert_eq!(err, SignalingError::Protocol(msg))
}
#[test]
fn handle_as_responder() {
let mut ctx = TestContext::responder(
ClientIdentity::Responder(7),
SignalingState::PeerHandshake,
ServerHandshakeState::Done,
None,
None,
);
let msg = Message::NewInitiator(NewInitiator);
let bbox = TestMsgBuilder::new(msg).from(0).to(7).build(
ctx.server_cookie.clone(),
&ctx.server_ks,
ctx.our_ks.public_key(),
);
let old_cookie_pair = ctx.signaling.initiator.cookie_pair().clone();
assert!(ctx
.signaling
.initiator
.csn_pair
.read()
.unwrap()
.theirs
.is_none());
ctx.signaling.initiator.csn_pair.write().unwrap().theirs =
Some(CombinedSequenceSnapshot::new(0, 0));
ctx.signaling
.initiator
.set_handshake_state(InitiatorHandshakeState::AuthSent);
let actions = ctx.signaling.handle_message(bbox).unwrap();
let new_cookie_pair = ctx.signaling.initiator.cookie_pair().clone();
assert_ne!(old_cookie_pair, new_cookie_pair);
assert!(ctx
.signaling
.initiator
.csn_pair
.read()
.unwrap()
.theirs
.is_none());
assert_ne!(
ctx.signaling.initiator.handshake_state(),
InitiatorHandshakeState::AuthSent
);
assert_eq!(actions.len(), 1);
assert_eq!(
ctx.signaling.initiator.handshake_state(),
InitiatorHandshakeState::KeySent
);
}
}
mod new_responder {
use super::*;
#[test]
fn expect_no_token() {
let peer_trusted_pk = PublicKey::random();
let mut ctx = TestContext::initiator(
ClientIdentity::Initiator,
Some(peer_trusted_pk.clone()),
SignalingState::PeerHandshake,
ServerHandshakeState::Done,
);
let address = Address::from(7);
let msg = Message::NewResponder(NewResponder {
id: address.clone(),
});
let bbox = TestMsgBuilder::new(msg).from(0).to(1).build(
ctx.server_cookie.clone(),
&ctx.server_ks,
ctx.our_ks.public_key(),
);
let _actions = ctx.signaling.handle_message(bbox).unwrap();
assert!(ctx.signaling.responder.is_none());
let bbox = {
let responder_cookie = Cookie::random();
let responder: &mut ResponderContext =
ctx.signaling.responders.get_mut(&address).unwrap();
responder.cookie_pair_mut().theirs = Some(responder_cookie.clone());
let msg = Message::Token(Token {
key: peer_trusted_pk,
});
TestMsgBuilder::new(msg).from(7).to(1).build(
responder_cookie,
&responder.keypair().expect("No responder keypair"),
ctx.our_ks.public_key(),
)
};
let actions = ctx.signaling.handle_message(bbox).unwrap();
assert_eq!(actions.len(), 1); }
#[test]
fn path_cleaning() {
let mut ctx = TestContext::initiator(
ClientIdentity::Initiator,
None,
SignalingState::PeerHandshake,
ServerHandshakeState::Done,
);
let mut csn = CombinedSequence::random();
let mut handle_message = |css: CombinedSequenceSnapshot, i: u8| {
let msg = Message::NewResponder(NewResponder { id: i.into() });
let bbox = TestMsgBuilder::new(msg).from(0).to(1).build_with_csn(
ctx.server_cookie.clone(),
&ctx.server_ks,
ctx.our_ks.public_key(),
css,
);
ctx.signaling.handle_message(bbox).unwrap()
};
for i in 0..252 {
let actions = handle_message(csn.increment().unwrap(), i + 2);
assert!(actions.is_empty());
}
let actions = handle_message(csn.increment().unwrap(), 255);
assert_eq!(actions.len(), 1);
}
}
mod disconnected {
use super::*;
#[test]
fn disconnected_during_server_auth() {
let mut ctx = TestContext::initiator(
ClientIdentity::Initiator,
None,
SignalingState::ServerHandshake,
ServerHandshakeState::ClientInfoSent,
);
let msg = Message::Disconnected(Disconnected::new(ClientIdentity::Responder(3).into()));
let bbox = TestMsgBuilder::new(msg).from(0).to(1).build(
ctx.server_cookie.clone(),
&ctx.server_ks,
ctx.our_ks.public_key(),
);
let err = ctx.signaling.handle_message(bbox).unwrap_err();
let msg = "Got \'disconnected\' message from server in ClientInfoSent state".into();
assert_eq!(err, SignalingError::InvalidStateTransition(msg))
}
#[test]
fn disconnected_initiator_invalid_id() {
let mut ctx = TestContext::initiator(
ClientIdentity::Initiator,
None,
SignalingState::PeerHandshake,
ServerHandshakeState::Done,
);
let msg = Message::Disconnected(Disconnected::new(ClientIdentity::Initiator.into()));
let bbox = TestMsgBuilder::new(msg).from(0).to(1).build(
ctx.server_cookie.clone(),
&ctx.server_ks,
ctx.our_ks.public_key(),
);
let err = ctx.signaling.handle_message(bbox).unwrap_err();
let msg = "Received \'disconnected\' message with non-responder id".into();
assert_eq!(err, SignalingError::Protocol(msg))
}
#[test]
fn disconnected_responder_invalid_id() {
let mut ctx = TestContext::responder(
ClientIdentity::Responder(3),
SignalingState::PeerHandshake,
ServerHandshakeState::Done,
None,
None,
);
let msg = Message::Disconnected(Disconnected::new(ClientIdentity::Responder(7).into()));
let bbox = TestMsgBuilder::new(msg).from(0).to(3).build(
ctx.server_cookie.clone(),
&ctx.server_ks,
ctx.our_ks.public_key(),
);
let err = ctx.signaling.handle_message(bbox).unwrap_err();
let msg = "Received \'disconnected\' message with non-initiator id".into();
assert_eq!(err, SignalingError::Protocol(msg))
}
#[test]
fn disconnected_notify_user() {
let mut ctx = TestContext::initiator(
ClientIdentity::Initiator,
None,
SignalingState::PeerHandshake,
ServerHandshakeState::Done,
);
let msg = Message::Disconnected(Disconnected::new(ClientIdentity::Responder(7).into()));
let bbox = TestMsgBuilder::new(msg).from(0).to(1).build(
ctx.server_cookie.clone(),
&ctx.server_ks,
ctx.our_ks.public_key(),
);
let actions = ctx.signaling.handle_message(bbox).unwrap();
assert_eq!(actions.len(), 1);
assert_eq!(actions[0], HandleAction::Event(Event::Disconnected(7)));
}
#[test]
fn disconnected_in_task_signaling_state() {
let mut ctx = TestContext::initiator(
ClientIdentity::Initiator,
None,
SignalingState::Task,
ServerHandshakeState::Done,
);
let msg = Message::Disconnected(Disconnected::new(ClientIdentity::Responder(7).into()));
let bbox = TestMsgBuilder::new(msg).from(0).to(1).build(
ctx.server_cookie.clone(),
&ctx.server_ks,
ctx.our_ks.public_key(),
);
let actions = ctx.signaling.handle_message(bbox).unwrap();
assert_eq!(actions.len(), 1);
assert_eq!(actions[0], HandleAction::Event(Event::Disconnected(7)));
}
}
mod regressions {
use super::*;
#[test]
fn ignore_in_flight_responder_message() {
let mut ctx = TestContext::initiator(
ClientIdentity::Initiator,
None,
SignalingState::PeerHandshake,
ServerHandshakeState::Done,
);
let responder_ks = KeyPair::new();
let msg = Message::Close(Close::from_close_code(CloseCode::NoSharedTask));
let bbox = TestMsgBuilder::new(msg).from(3).to(1).build(
Cookie::random(),
&responder_ks,
ctx.our_ks.public_key(),
);
let actions = ctx.signaling.handle_message(bbox).unwrap();
assert_eq!(actions.len(), 0);
}
}