161 lines
5.1 KiB
Rust
161 lines
5.1 KiB
Rust
use irtt_proto::{verify_packet_hmac, FLAG_HMAC};
|
|
|
|
use super::support::{
|
|
client_params, core_with_tokens, expect_normal_open_reply, open_request,
|
|
open_request_with_raw_params, param_int, peer, ScriptedTokens, KEY, OTHER_KEY,
|
|
};
|
|
use crate::ServerConfig;
|
|
|
|
const TOKEN_A: u64 = 0x0102_0304_0506_0708;
|
|
|
|
/// Asserts a datagram is silently discarded, leaving no trace at all: no reply,
|
|
/// no session, and no session token drawn.
|
|
fn assert_discarded(config: ServerConfig, name: &str, packet: &[u8]) {
|
|
let tokens = ScriptedTokens::new([TOKEN_A]);
|
|
let mut core = core_with_tokens(config, tokens.clone());
|
|
|
|
let reply = core
|
|
.handle_datagram(peer(), packet)
|
|
.expect("a rejected datagram is not a server error");
|
|
|
|
assert!(reply.is_none(), "{name} must be answered with silence");
|
|
assert_eq!(core.session_count(), 0, "{name} must create no session");
|
|
assert_eq!(
|
|
tokens.remaining(),
|
|
1,
|
|
"{name} must not consume session capacity or a token"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn an_authenticating_server_discards_everything_that_does_not_authenticate() {
|
|
let config = ServerConfig::default().with_hmac_key(KEY);
|
|
|
|
assert_discarded(
|
|
config.clone(),
|
|
"open with no MAC field at all",
|
|
&open_request(&client_params(), None),
|
|
);
|
|
assert_discarded(
|
|
config.clone(),
|
|
"open signed with a different key",
|
|
&open_request(&client_params(), Some(OTHER_KEY)),
|
|
);
|
|
|
|
let mut corrupted = open_request(&client_params(), Some(KEY));
|
|
corrupted[5] ^= 0x80;
|
|
assert_discarded(
|
|
config.clone(),
|
|
"open with one bit flipped in the MAC",
|
|
&corrupted,
|
|
);
|
|
|
|
let mut zeroed = open_request(&client_params(), Some(KEY));
|
|
zeroed[4..20].fill(0);
|
|
assert_discarded(config.clone(), "open with an all-zero MAC", &zeroed);
|
|
|
|
let mut truncated = open_request(&client_params(), Some(KEY));
|
|
truncated.drain(4..12);
|
|
assert_discarded(
|
|
config.clone(),
|
|
"open with a truncated MAC field",
|
|
&truncated,
|
|
);
|
|
|
|
let mut flag_cleared = open_request(&client_params(), Some(KEY));
|
|
flag_cleared[3] &= !FLAG_HMAC;
|
|
assert_discarded(
|
|
config,
|
|
"open with a valid MAC but the HMAC flag cleared",
|
|
&flag_cleared,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn a_non_authenticating_server_discards_authenticated_requests() {
|
|
assert_discarded(
|
|
ServerConfig::default(),
|
|
"authenticated open to a server with no key",
|
|
&open_request(&client_params(), Some(KEY)),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn an_authenticating_server_answers_a_valid_open_with_an_authenticated_reply() {
|
|
let mut core = core_with_tokens(
|
|
ServerConfig::default().with_hmac_key(KEY),
|
|
ScriptedTokens::new([TOKEN_A]),
|
|
);
|
|
|
|
let packet = core
|
|
.handle_datagram(peer(), &open_request(&client_params(), Some(KEY)))
|
|
.unwrap()
|
|
.expect("a correctly authenticated open must be answered");
|
|
|
|
let reply = expect_normal_open_reply(&packet, Some(KEY));
|
|
assert_eq!(reply.token, TOKEN_A);
|
|
assert_ne!(
|
|
packet.bytes()[3] & FLAG_HMAC,
|
|
0,
|
|
"the reply must set FLAG_HMAC"
|
|
);
|
|
verify_packet_hmac(KEY, packet.bytes()).expect("the reply must carry a valid MAC");
|
|
assert_eq!(core.session_count(), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn a_non_authenticating_server_answers_an_ordinary_open() {
|
|
let mut core = core_with_tokens(ServerConfig::default(), ScriptedTokens::new([TOKEN_A]));
|
|
|
|
let packet = core
|
|
.handle_datagram(peer(), &open_request(&client_params(), None))
|
|
.unwrap()
|
|
.expect("an unauthenticated open must be answered");
|
|
|
|
expect_normal_open_reply(&packet, None);
|
|
assert_eq!(packet.bytes()[3] & FLAG_HMAC, 0);
|
|
assert_eq!(core.session_count(), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn authentication_is_checked_before_parameters_are_decoded() {
|
|
// Both of these are dropped, and neither reveals which stage rejected it.
|
|
// The pairing is the point: bad parameters under a valid MAC still drop,
|
|
// and a bad MAC drops without the parameters ever being parsed.
|
|
let config = ServerConfig::default().with_hmac_key(KEY);
|
|
let bad_params = param_int(2, 0);
|
|
|
|
assert_discarded(
|
|
config.clone(),
|
|
"authenticated open with an explicit zero duration",
|
|
&open_request_with_raw_params(&bad_params, Some(KEY)),
|
|
);
|
|
assert_discarded(
|
|
config,
|
|
"wrongly keyed open with an explicit zero duration",
|
|
&open_request_with_raw_params(&bad_params, Some(OTHER_KEY)),
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn a_failed_authentication_leaves_a_live_session_untouched() {
|
|
let mut core = core_with_tokens(
|
|
ServerConfig::default().with_hmac_key(KEY),
|
|
ScriptedTokens::new([TOKEN_A]),
|
|
);
|
|
core.handle_datagram(peer(), &open_request(&client_params(), Some(KEY)))
|
|
.unwrap()
|
|
.expect("the session-creating open must succeed");
|
|
let session = core.session(TOKEN_A).cloned().expect("session must exist");
|
|
|
|
for bad in [
|
|
open_request(&client_params(), None),
|
|
open_request(&client_params(), Some(OTHER_KEY)),
|
|
] {
|
|
assert_eq!(core.handle_datagram(peer(), &bad).unwrap(), None);
|
|
}
|
|
|
|
assert_eq!(core.session_count(), 1);
|
|
assert_eq!(core.session(TOKEN_A), Some(&session));
|
|
}
|