Skip to content

Commit

Permalink
Support hashed hostnames in known_hosts file
Browse files Browse the repository at this point in the history
  • Loading branch information
george-hopkins committed Nov 1, 2023
1 parent d3c4108 commit 0a95ef9
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 3 deletions.
1 change: 1 addition & 0 deletions russh-keys/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ rand = "0.7"
rand_core = { version = "0.6.4", features = ["std"] }
russh-cryptovec = { version = "0.7.0", path = "../cryptovec" }
serde = { version = "1.0", features = ["derive"] }
sha1 = "0.10"
sha2 = "0.10"
thiserror = "1.0"
tokio = { version = "1.17.0", features = [
Expand Down
41 changes: 38 additions & 3 deletions russh-keys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ use aes::cipher::block_padding::UnpadError;
use aes::cipher::inout::PadError;
use byteorder::{BigEndian, WriteBytesExt};
use data_encoding::BASE64_MIME;
use hmac::{Hmac, Mac};
use log::debug;
use sha1::Sha1;
use thiserror::Error;

pub mod encoding;
Expand Down Expand Up @@ -365,8 +367,7 @@ pub fn check_known_hosts_path<P: AsRef<Path>>(
let key = s.next();
if let (Some(h), Some(k)) = (hosts, key) {
debug!("{:?} {:?}", h, k);
let host_matches = h.split(',').any(|x| x == host_port);
if host_matches {
if match_hostname(&host_port, h) {
if &parse_public_key_base64(k)? == pubkey {
return Ok(true);
} else {
Expand All @@ -381,6 +382,28 @@ pub fn check_known_hosts_path<P: AsRef<Path>>(
Ok(false)
}

fn match_hostname(host: &str, pattern: &str) -> bool {
for entry in pattern.split(',') {
if entry.starts_with("|1|") {
let mut parts = entry.split('|').skip(2);
let Some(Ok(salt)) = parts.next().map(|p| BASE64_MIME.decode(p.as_bytes())) else {
continue;
};
let Some(Ok(hash)) = parts.next().map(|p| BASE64_MIME.decode(p.as_bytes())) else {
continue;
};
if let Ok(hmac) = Hmac::<Sha1>::new_from_slice(&salt) {
if hmac.chain_update(host).verify_slice(&hash).is_ok() {
return true;
}
}
} else if host == entry {
return true;
}
}
false
}

/// Record a host's public key into the user's known_hosts file.
#[cfg(target_os = "windows")]
pub fn learn_known_hosts(host: &str, port: u16, pubkey: &key::PublicKey) -> Result<(), Error> {
Expand Down Expand Up @@ -526,7 +549,10 @@ QR+u0AypRPmzHnOPAAAAEXJvb3RAMTQwOTExNTQ5NDBkAQ==
let path = dir.path().join("known_hosts");
{
let mut f = File::create(&path).unwrap();
f.write(b"[localhost]:13265 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJdD7y3aLq454yWBdwLWbieU1ebz9/cu7/QEXn9OIeZJ\n#pijul.org,37.120.161.53 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA6rWI3G2sz07DnfFlrouTcysQlj2P+jpNSOEWD9OJ3X\npijul.org,37.120.161.53 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA6rWI3G1sz07DnfFlrouTcysQlj2P+jpNSOEWD9OJ3X\n").unwrap();
f.write_all(b"[localhost]:13265 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJdD7y3aLq454yWBdwLWbieU1ebz9/cu7/QEXn9OIeZJ\n").unwrap();
f.write_all(b"#pijul.org,37.120.161.53 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA6rWI3G2sz07DnfFlrouTcysQlj2P+jpNSOEWD9OJ3X\n").unwrap();
f.write_all(b"pijul.org,37.120.161.53 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA6rWI3G1sz07DnfFlrouTcysQlj2P+jpNSOEWD9OJ3X\n").unwrap();
f.write_all(b"|1|O33ESRMWPVkMYIwJ1Uw+n877jTo=|nuuC5vEqXlEZ/8BXQR7m619W6Ak= ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILIG2T/B0l0gaqj3puu510tu9N1OkQ4znY3LYuEm5zCF\n").unwrap();
}

// Valid key, non-standard port.
Expand All @@ -538,6 +564,15 @@ QR+u0AypRPmzHnOPAAAAEXJvb3RAMTQwOTExNTQ5NDBkAQ==
.unwrap();
assert!(check_known_hosts_path(host, port, &hostkey, &path).unwrap());

// Valid key, hashed.
let host = "example.com";
let port = 22;
let hostkey = parse_public_key_base64(
"AAAAC3NzaC1lZDI1NTE5AAAAILIG2T/B0l0gaqj3puu510tu9N1OkQ4znY3LYuEm5zCF",
)
.unwrap();
assert!(check_known_hosts_path(host, port, &hostkey, &path).unwrap());

// Valid key, several hosts, port 22
let host = "pijul.org";
let port = 22;
Expand Down

0 comments on commit 0a95ef9

Please sign in to comment.