2020//! hostname patterns, and revoked markers. See "FIXME" comments littered in
2121//! this file.
2222
23+ use crate :: util:: config:: { Definition , Value } ;
2324use git2:: cert:: Cert ;
2425use git2:: CertificateCheckStatus ;
2526use std:: collections:: HashSet ;
@@ -74,6 +75,8 @@ impl From<anyhow::Error> for KnownHostError {
7475enum KnownHostLocation {
7576 /// Loaded from a file from disk.
7677 File { path : PathBuf , lineno : u32 } ,
78+ /// Loaded from cargo's config system.
79+ Config { definition : Definition } ,
7780 /// Part of the hard-coded bundled keys in Cargo.
7881 Bundled ,
7982}
@@ -83,6 +86,8 @@ pub fn certificate_check(
8386 cert : & Cert < ' _ > ,
8487 host : & str ,
8588 port : Option < u16 > ,
89+ config_known_hosts : Option < & Vec < Value < String > > > ,
90+ diagnostic_home_config : & str ,
8691) -> Result < CertificateCheckStatus , git2:: Error > {
8792 let Some ( host_key) = cert. as_hostkey ( ) else {
8893 // Return passthrough for TLS X509 certificates to use whatever validation
@@ -96,7 +101,7 @@ pub fn certificate_check(
96101 _ => host. to_string ( ) ,
97102 } ;
98103 // The error message must be constructed as a string to pass through the libgit2 C API.
99- let err_msg = match check_ssh_known_hosts ( host_key, & host_maybe_port) {
104+ let err_msg = match check_ssh_known_hosts ( host_key, & host_maybe_port, config_known_hosts ) {
100105 Ok ( ( ) ) => {
101106 return Ok ( CertificateCheckStatus :: CertificateOk ) ;
102107 }
@@ -113,13 +118,13 @@ pub fn certificate_check(
113118 // Try checking without the port.
114119 if port. is_some ( )
115120 && !matches ! ( port, Some ( 22 ) )
116- && check_ssh_known_hosts ( host_key, host) . is_ok ( )
121+ && check_ssh_known_hosts ( host_key, host, config_known_hosts ) . is_ok ( )
117122 {
118123 return Ok ( CertificateCheckStatus :: CertificateOk ) ;
119124 }
120125 let key_type_short_name = key_type. short_name ( ) ;
121126 let key_type_name = key_type. name ( ) ;
122- let known_hosts_location = user_known_host_location_to_add ( ) ;
127+ let known_hosts_location = user_known_host_location_to_add ( diagnostic_home_config ) ;
123128 let other_hosts_message = if other_hosts. is_empty ( ) {
124129 String :: new ( )
125130 } else {
@@ -132,6 +137,9 @@ pub fn certificate_check(
132137 KnownHostLocation :: File { path, lineno } => {
133138 format ! ( "{} line {lineno}" , path. display( ) )
134139 }
140+ KnownHostLocation :: Config { definition } => {
141+ format ! ( "config value from {definition}" )
142+ }
135143 KnownHostLocation :: Bundled => format ! ( "bundled with cargo" ) ,
136144 } ;
137145 write ! ( msg, " {loc}: {}\n " , known_host. patterns) . unwrap ( ) ;
@@ -163,7 +171,7 @@ pub fn certificate_check(
163171 } ) => {
164172 let key_type_short_name = key_type. short_name ( ) ;
165173 let key_type_name = key_type. name ( ) ;
166- let known_hosts_location = user_known_host_location_to_add ( ) ;
174+ let known_hosts_location = user_known_host_location_to_add ( diagnostic_home_config ) ;
167175 let old_key_resolution = match old_known_host. location {
168176 KnownHostLocation :: File { path, lineno } => {
169177 let old_key_location = path. display ( ) ;
@@ -173,6 +181,13 @@ pub fn certificate_check(
173181 and adding the new key to {known_hosts_location}",
174182 )
175183 }
184+ KnownHostLocation :: Config { definition } => {
185+ format ! (
186+ "removing the old {key_type_name} key for `{hostname}` \
187+ loaded from Cargo's config at {definition}, \
188+ and adding the new key to {known_hosts_location}"
189+ )
190+ }
176191 KnownHostLocation :: Bundled => {
177192 format ! (
178193 "adding the new key to {known_hosts_location}\n \
@@ -217,6 +232,7 @@ pub fn certificate_check(
217232fn check_ssh_known_hosts (
218233 cert_host_key : & git2:: cert:: CertHostkey < ' _ > ,
219234 host : & str ,
235+ config_known_hosts : Option < & Vec < Value < String > > > ,
220236) -> Result < ( ) , KnownHostError > {
221237 let Some ( remote_host_key) = cert_host_key. hostkey ( ) else {
222238 return Err ( anyhow:: format_err!( "remote host key is not available" ) . into ( ) ) ;
@@ -237,6 +253,23 @@ fn check_ssh_known_hosts(
237253 let hosts = load_hostfile ( & path) ?;
238254 known_hosts. extend ( hosts) ;
239255 }
256+ if let Some ( config_known_hosts) = config_known_hosts {
257+ // Format errors aren't an error in case the format needs to change in
258+ // the future, to retain forwards compatibility.
259+ for line_value in config_known_hosts {
260+ let location = KnownHostLocation :: Config {
261+ definition : line_value. definition . clone ( ) ,
262+ } ;
263+ match parse_known_hosts_line ( & line_value. val , location) {
264+ Some ( known_host) => known_hosts. push ( known_host) ,
265+ None => log:: warn!(
266+ "failed to parse known host {} from {}" ,
267+ line_value. val,
268+ line_value. definition
269+ ) ,
270+ }
271+ }
272+ }
240273 // Load the bundled keys. Don't add keys for hosts that the user has
241274 // configured, which gives them the option to override them. This could be
242275 // useful if the keys are ever revoked.
@@ -363,12 +396,18 @@ fn user_known_host_location() -> Option<PathBuf> {
363396
364397/// The location to display in an error message instructing the user where to
365398/// add the new key.
366- fn user_known_host_location_to_add ( ) -> String {
399+ fn user_known_host_location_to_add ( diagnostic_home_config : & str ) -> String {
367400 // Note that we don't bother with the legacy known_hosts2 files.
368- match user_known_host_location ( ) {
369- Some ( path) => path. to_str ( ) . expect ( "utf-8 home" ) . to_string ( ) ,
370- None => "~/.ssh/known_hosts" . to_string ( ) ,
371- }
401+ let user = user_known_host_location ( ) ;
402+ let openssh_loc = match & user {
403+ Some ( path) => path. to_str ( ) . expect ( "utf-8 home" ) ,
404+ None => "~/.ssh/known_hosts" ,
405+ } ;
406+ format ! (
407+ "the `net.ssh.known-hosts` array in your Cargo configuration \
408+ (such as {diagnostic_home_config}) \
409+ or in your OpenSSH known_hosts file at {openssh_loc}"
410+ )
372411}
373412
374413/// A single known host entry.
0 commit comments