16
16
//! - `VerifyHostKeyDNS` — Uses SSHFP DNS records to fetch a host key.
17
17
//!
18
18
//! There's also a number of things that aren't supported but could be easily
19
- //! added (it just adds a little complexity). For example, hashed hostnames,
20
- //! hostname patterns, and revoked markers. See "FIXME" comments littered in
21
- //! this file.
19
+ //! added (it just adds a little complexity). For example, hostname patterns,
20
+ //! and revoked markers. See "FIXME" comments littered in this file.
22
21
23
22
use crate :: util:: config:: { Definition , Value } ;
24
23
use git2:: cert:: { Cert , SshHostKeyType } ;
25
24
use git2:: CertificateCheckStatus ;
25
+ use hmac:: Mac ;
26
26
use std:: collections:: HashSet ;
27
27
use std:: fmt:: Write ;
28
28
use std:: path:: { Path , PathBuf } ;
@@ -419,6 +419,8 @@ fn user_known_host_location_to_add(diagnostic_home_config: &str) -> String {
419
419
)
420
420
}
421
421
422
+ const HASH_HOSTNAME_PREFIX : & str = "|1|" ;
423
+
422
424
/// A single known host entry.
423
425
#[ derive( Clone ) ]
424
426
struct KnownHost {
@@ -434,7 +436,9 @@ impl KnownHost {
434
436
fn host_matches ( & self , host : & str ) -> bool {
435
437
let mut match_found = false ;
436
438
let host = host. to_lowercase ( ) ;
437
- // FIXME: support hashed hostnames
439
+ if let Some ( hashed) = self . patterns . strip_prefix ( HASH_HOSTNAME_PREFIX ) {
440
+ return hashed_hostname_matches ( & host, hashed) ;
441
+ }
438
442
for pattern in self . patterns . split ( ',' ) {
439
443
let pattern = pattern. to_lowercase ( ) ;
440
444
// FIXME: support * and ? wildcards
@@ -450,6 +454,16 @@ impl KnownHost {
450
454
}
451
455
}
452
456
457
+ fn hashed_hostname_matches ( host : & str , hashed : & str ) -> bool {
458
+ let Some ( ( b64_salt, b64_host) ) = hashed. split_once ( '|' ) else { return false ; } ;
459
+ let Ok ( salt) = base64:: decode ( b64_salt) else { return false ; } ;
460
+ let Ok ( hashed_host) = base64:: decode ( b64_host) else { return false ; } ;
461
+ let Ok ( mut mac) = hmac:: Hmac :: < sha1:: Sha1 > :: new_from_slice ( & salt) else { return false ; } ;
462
+ mac. update ( host. as_bytes ( ) ) ;
463
+ let result = mac. finalize ( ) . into_bytes ( ) ;
464
+ hashed_host == & result[ ..]
465
+ }
466
+
453
467
/// Loads an OpenSSH known_hosts file.
454
468
fn load_hostfile ( path : & Path ) -> Result < Vec < KnownHost > , anyhow:: Error > {
455
469
let contents = cargo_util:: paths:: read ( path) ?;
@@ -474,7 +488,7 @@ fn load_hostfile_contents(path: &Path, contents: &str) -> Vec<KnownHost> {
474
488
fn parse_known_hosts_line ( line : & str , location : KnownHostLocation ) -> Option < KnownHost > {
475
489
let line = line. trim ( ) ;
476
490
// FIXME: @revoked and @cert-authority is currently not supported.
477
- if line. is_empty ( ) || line. starts_with ( [ '#' , '@' , '|' ] ) {
491
+ if line. is_empty ( ) || line. starts_with ( [ '#' , '@' ] ) {
478
492
return None ;
479
493
}
480
494
let mut parts = line. split ( [ ' ' , '\t' ] ) . filter ( |s| !s. is_empty ( ) ) ;
@@ -506,8 +520,7 @@ mod tests {
506
520
@revoked * ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKtQsi+KPYispwm2rkMidQf30fG1Niy8XNkvASfePoca eric@host
507
521
example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAWkjI6XT2SZh3xNk5NhisA3o3sGzWR+VAKMSqHtI0aY eric@host
508
522
192.168.42.12 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKVYJpa0yUGaNk0NXQTPWa0tHjqRpx+7hl2diReH6DtR eric@host
509
- # Hash not yet supported.
510
- |1|7CMSYgzdwruFLRhwowMtKx0maIE=|Tlff1GFqc3Ao+fUWxMEVG8mJiyk= ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIHgN3O21U4LWtP5OzjTzPnUnSDmCNDvyvlaj6Hi65JC eric@host
523
+ |1|QxzZoTXIWLhUsuHAXjuDMIV3FjQ=|M6NCOIkjiWdCWqkh5+Q+/uFLGjs= ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIHgN3O21U4LWtP5OzjTzPnUnSDmCNDvyvlaj6Hi65JC eric@host
511
524
# Negation isn't terribly useful without globs.
512
525
neg.example.com,!neg.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOXfUnaAHTlo1Qi//rNk26OcmHikmkns1Z6WW/UuuS3K eric@host
513
526
"# ;
@@ -516,7 +529,7 @@ mod tests {
516
529
fn known_hosts_parse ( ) {
517
530
let kh_path = Path :: new ( "/home/abc/.known_hosts" ) ;
518
531
let khs = load_hostfile_contents ( kh_path, COMMON_CONTENTS ) ;
519
- assert_eq ! ( khs. len( ) , 9 ) ;
532
+ assert_eq ! ( khs. len( ) , 10 ) ;
520
533
match & khs[ 0 ] . location {
521
534
KnownHostLocation :: File { path, lineno } => {
522
535
assert_eq ! ( path, kh_path) ;
@@ -551,7 +564,9 @@ mod tests {
551
564
assert ! ( !khs[ 0 ] . host_matches( "example.net" ) ) ;
552
565
assert ! ( khs[ 2 ] . host_matches( "[example.net]:2222" ) ) ;
553
566
assert ! ( !khs[ 2 ] . host_matches( "example.net" ) ) ;
554
- assert ! ( !khs[ 8 ] . host_matches( "neg.example.com" ) ) ;
567
+ assert ! ( khs[ 8 ] . host_matches( "hashed.example.com" ) ) ;
568
+ assert ! ( !khs[ 8 ] . host_matches( "example.com" ) ) ;
569
+ assert ! ( !khs[ 9 ] . host_matches( "neg.example.com" ) ) ;
555
570
}
556
571
557
572
#[ test]
0 commit comments