mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-21 16:02:20 +00:00
test(ssh): match a known_hosts line that ssh-keygen actually produced
The hashed-host test builds its line with the same `hmac_sha1` it then checks, so it holds whether or not that agrees with anyone else. The digest is not the gap -- `sha1_matches_known_vectors` and `hmac_sha1_matches_rfc2202_vector` pin it against outside vectors, and breaking one SHA-1 constant fails all three tests, which is how I checked. The gap is everything wrapped around the digest: base64 in the order this file reads it, `|1|` splitting where OpenSSH puts the separator, and `host_token` spelling a non-default port the way ssh-keygen does -- `[example.com]:2222`, brackets and all. Nothing pinned that, and getting it wrong makes every hashed entry for a non-22 host read as a host never seen before. Not a wrong trust, but a user with `HashKnownHosts` on would meet the prompt at every connection. So both prefixes here came out of a real `ssh-keygen -H` run and are matched end to end. They pair with this file's own key because the digest covers the host token alone. Checked while reading the surrounding logic, which needed nothing: revoked entries are consulted first and outrank everything, `@cert-authority` lines are skipped where they would name the wrong key, a same-algorithm mismatch outranks a different-algorithm one, and `!negation` short-circuits the whole comma-separated list the way OpenSSH does.
This commit is contained in:
@@ -654,6 +654,7 @@ fn base64_decode(s: &str) -> Option<Vec<u8>> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
@@ -832,6 +833,48 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// One line straight out of `ssh-keygen -H`, matched end to end.
|
||||
///
|
||||
/// The digest itself is already pinned against outside vectors by
|
||||
/// `sha1_matches_known_vectors` and `hmac_sha1_matches_rfc2202_vector`.
|
||||
/// What no vector covers is everything wrapped around it: that the salt
|
||||
/// and hash are base64 in the order this file reads them, that `|1|`
|
||||
/// splits where OpenSSH puts the separator, and above all that
|
||||
/// [`host_token`] spells a non-default port the same way `ssh-keygen`
|
||||
/// did — `[example.com]:2222`, brackets and all. Get that spelling wrong
|
||||
/// and every hashed entry for a non-22 host silently reads as a host
|
||||
/// nobody has ever seen, which is a re-prompt rather than a wrong trust,
|
||||
/// but a user with `HashKnownHosts` on would meet it at every connection.
|
||||
///
|
||||
/// The digest covers the host token alone, which is why these pair with
|
||||
/// this file's own key: the hashes say `example.com` and
|
||||
/// `[example.com]:2222` and nothing about what key sat on the line.
|
||||
#[test]
|
||||
fn openssh_hashed_hosts_are_recognised() {
|
||||
const HASHED_PLAIN: &str = "|1|mhBioT0otp5S/Ux0dinqUZ+0xJs=|XL4c/5K7LUzkBHUoDzK10wN1Yzg=";
|
||||
const HASHED_PORT_2222: &str =
|
||||
"|1|NuBKg6vw2CDaDM38U3/fjnHfAUE=|RPBwgouR+2rB4qexN4ESoPqnyP8=";
|
||||
|
||||
let ka = key(KEY_A);
|
||||
let file = format!("{HASHED_PLAIN} {KEY_A}\n{HASHED_PORT_2222} {KEY_A}\n");
|
||||
|
||||
assert_eq!(
|
||||
check_in_str(&file, "example.com", 22, &ka),
|
||||
HostKeyStatus::Known,
|
||||
"a plain host hashed by ssh-keygen"
|
||||
);
|
||||
assert_eq!(
|
||||
check_in_str(&file, "example.com", 2222, &ka),
|
||||
HostKeyStatus::Known,
|
||||
"the [host]:port spelling has to hash the same way OpenSSH spells it"
|
||||
);
|
||||
assert_eq!(
|
||||
check_in_str(&file, "other.com", 22, &ka),
|
||||
HostKeyStatus::Unknown,
|
||||
"a host these hashes do not name"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hashed_host_matches_via_hmac_sha1() {
|
||||
let token = "example.com";
|
||||
|
||||
Reference in New Issue
Block a user