Files
sausage/userland/ssh-client/src/ssh_protection_tests.inc
T

90 lines
5.6 KiB
C++

#[cfg(test)]
mod protection_tests {
use super::*;
use std::io::Cursor;
struct Memory { input: Cursor<Vec<u8>>, output: Vec<u8> }
impl Read for Memory { fn read(&mut self, b: &mut [u8]) -> io::Result<usize> { self.input.read(b) } }
impl Write for Memory {
fn write(&mut self, b: &[u8]) -> io::Result<usize> { self.output.extend_from_slice(b); Ok(b.len()) }
fn flush(&mut self) -> io::Result<()> { Ok(()) }
}
fn protection(algorithm: MacAlgorithm) -> Protection {
Protection::with_mac(&[3;16], &[4;16], vec![5;algorithm.key_len()], algorithm).unwrap()
}
fn reader(bytes: Vec<u8>, algorithm: MacAlgorithm) -> Packets<Memory> {
let mut packets = Packets::new(Memory { input: Cursor::new(bytes), output: vec![] });
packets.recv = Some(protection(algorithm)); packets
}
fn hex(s: &str) -> Vec<u8> {
s.as_bytes().chunks_exact(2).map(|p| u8::from_str_radix(std::str::from_utf8(p).unwrap(), 16).unwrap()).collect()
}
#[test]
fn etm_openssl_fixtures_and_authenticate_before_decrypt() {
// Generated independently using Node/OpenSSL; generator and output recorded in
// /docs/symphony/artifacts/SAU-415/vectors.mjs (no shared packet/KDF code).
for (algorithm, vector) in [
(MacAlgorithm::Sha256Etm, "00000010e0682f200ab506a20a4da74db3b6ff408f978288292d6b1f9a194fcf108ee3d8dd20edd265854e0683c7545d89a98533000000208c23fc658200527682c8cef58c2867088a31921a7419b34e9d807ee551118a9d96ec28c65f3d7a23da2197da11b30ea7f2625d4179ece3c724ccbe8f78da4463"),
(MacAlgorithm::Sha512Etm, "00000010e0682f200ab506a20a4da74db3b6ff406d1fd80c98bae4318b01500b79fe77b71ec0ab3ca69841dd51116b6d3277d7e51405c3c76ac95b4964988851f12e55d9430c8e227931fffdd5b95a98859586e5000000208c23fc658200527682c8cef58c2867088a31921a7419b34e9d807ee551118a9da235ac50894cf397f912da5296a848b5d240de3e78fadf4ac278430a377ee6448ddaa491aa9007041e83a6fcd9d77aec0855f0cfff765924d1e060796ca2b09c"),
] {
let bytes = hex(vector);
let mut packets = reader(bytes.clone(), algorithm);
assert_eq!(packets.read().unwrap(), [5]);
assert_eq!(packets.read().unwrap(), b"independent fixture");
for offset in [4, 19, 20] { // encrypted padding length, ciphertext, tag
let mut bad = bytes.clone(); bad[offset] ^= 1;
let mut packets = reader(bad, algorithm);
assert!(packets.read().unwrap_err().to_string().contains("MAC mismatch"));
// Failed authentication did not consume cipher state or sequence number.
packets.stream.input = Cursor::new(bytes.clone());
assert_eq!(packets.read().unwrap(), [5]);
}
let first_len = 4 + 16 + algorithm.key_len();
assert!(reader(bytes[first_len..].to_vec(), algorithm).read().unwrap_err().to_string().contains("MAC mismatch"));
for end in 0..first_len { assert!(reader(bytes[..end].to_vec(), algorithm).read().is_err()); }
for len in [0u32, 5, 17, 262145, u32::MAX] {
let mut bad = bytes.clone(); bad[..4].copy_from_slice(&len.to_be_bytes());
assert!(reader(bad, algorithm).read().unwrap_err().to_string().contains("invalid packet length"));
}
let mut bad = bytes.clone(); bad[..4].copy_from_slice(&32u32.to_be_bytes());
assert!(reader(bad, algorithm).read().unwrap_err().to_string().contains("MAC mismatch"));
}
}
#[test]
fn all_mac_packet_boundaries_and_independent_outbound_check() {
for algorithm in [MacAlgorithm::Sha256, MacAlgorithm::Sha256Etm, MacAlgorithm::Sha512Etm] {
let mut packets = Packets::new(Memory { input: Cursor::new(vec![]), output: vec![] });
packets.send = Some(protection(algorithm));
let mut cipher = Aes::new_from_slices(&[3;16], &[4;16]).unwrap();
let mut incoming = reader(vec![], algorithm);
for (seq, size) in [1, 7, 8, 11, 12, 15, 16, 31, 32768, 65536].into_iter().enumerate() {
let payload = vec![42;size]; packets.write(&payload).unwrap();
let encoded = std::mem::take(&mut packets.stream.output);
incoming.stream.input = Cursor::new(encoded.clone());
assert_eq!(incoming.read().unwrap(), payload);
// Check sender without using Protection or Packets for decryption/MAC.
let split = encoded.len() - algorithm.key_len();
let mut plain = encoded[..split].to_vec();
if algorithm.etm() {
assert_eq!(Decoder(&plain).uint().unwrap() as usize, split - 4);
assert_eq!((split - 4) % 16, 0);
}
let start = if algorithm.etm() { 4 } else { 0 };
cipher.apply_keystream(&mut plain[start..]);
let signed = if algorithm.etm() { &encoded[..split] } else { &plain };
let key = vec![5;algorithm.key_len()];
let seq = (seq as u32).to_be_bytes();
if algorithm == MacAlgorithm::Sha512Etm {
let mut h = <Hmac<Sha512> as Mac>::new_from_slice(&key).unwrap();
h.update(&seq); h.update(signed); h.verify_slice(&encoded[split..]).unwrap();
} else {
let mut h = <Hmac<Sha256> as Mac>::new_from_slice(&key).unwrap();
h.update(&seq); h.update(signed); h.verify_slice(&encoded[split..]).unwrap();
}
assert_eq!(&plain[5..5+size], payload);
assert!(plain[4] >= 4);
}
}
}
}