20
20
//! hostname patterns, and revoked markers. See "FIXME" comments littered in
21
21
//! this file.
22
22
23
+ use crate :: util:: config:: { Definition , Value } ;
23
24
use git2:: cert:: Cert ;
24
25
use git2:: CertificateCheckStatus ;
25
26
use std:: collections:: HashSet ;
@@ -74,6 +75,8 @@ impl From<anyhow::Error> for KnownHostError {
74
75
enum KnownHostLocation {
75
76
/// Loaded from a file from disk.
76
77
File { path : PathBuf , lineno : u32 } ,
78
+ /// Loaded from cargo's config system.
79
+ Config { definition : Definition } ,
77
80
/// Part of the hard-coded bundled keys in Cargo.
78
81
Bundled ,
79
82
}
@@ -83,6 +86,8 @@ pub fn certificate_check(
83
86
cert : & Cert < ' _ > ,
84
87
host : & str ,
85
88
port : Option < u16 > ,
89
+ config_known_hosts : Option < & Vec < Value < String > > > ,
90
+ diagnostic_home_config : & str ,
86
91
) -> Result < CertificateCheckStatus , git2:: Error > {
87
92
let Some ( host_key) = cert. as_hostkey ( ) else {
88
93
// Return passthrough for TLS X509 certificates to use whatever validation
@@ -96,7 +101,7 @@ pub fn certificate_check(
96
101
_ => host. to_string ( ) ,
97
102
} ;
98
103
// 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 ) {
100
105
Ok ( ( ) ) => {
101
106
return Ok ( CertificateCheckStatus :: CertificateOk ) ;
102
107
}
@@ -113,13 +118,13 @@ pub fn certificate_check(
113
118
// Try checking without the port.
114
119
if port. is_some ( )
115
120
&& !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 ( )
117
122
{
118
123
return Ok ( CertificateCheckStatus :: CertificateOk ) ;
119
124
}
120
125
let key_type_short_name = key_type. short_name ( ) ;
121
126
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 ) ;
123
128
let other_hosts_message = if other_hosts. is_empty ( ) {
124
129
String :: new ( )
125
130
} else {
@@ -132,6 +137,9 @@ pub fn certificate_check(
132
137
KnownHostLocation :: File { path, lineno } => {
133
138
format ! ( "{} line {lineno}" , path. display( ) )
134
139
}
140
+ KnownHostLocation :: Config { definition } => {
141
+ format ! ( "config value from {definition}" )
142
+ }
135
143
KnownHostLocation :: Bundled => format ! ( "bundled with cargo" ) ,
136
144
} ;
137
145
write ! ( msg, " {loc}: {}\n " , known_host. patterns) . unwrap ( ) ;
@@ -163,7 +171,7 @@ pub fn certificate_check(
163
171
} ) => {
164
172
let key_type_short_name = key_type. short_name ( ) ;
165
173
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 ) ;
167
175
let old_key_resolution = match old_known_host. location {
168
176
KnownHostLocation :: File { path, lineno } => {
169
177
let old_key_location = path. display ( ) ;
@@ -173,6 +181,13 @@ pub fn certificate_check(
173
181
and adding the new key to {known_hosts_location}",
174
182
)
175
183
}
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
+ }
176
191
KnownHostLocation :: Bundled => {
177
192
format ! (
178
193
"adding the new key to {known_hosts_location}\n \
@@ -217,6 +232,7 @@ pub fn certificate_check(
217
232
fn check_ssh_known_hosts (
218
233
cert_host_key : & git2:: cert:: CertHostkey < ' _ > ,
219
234
host : & str ,
235
+ config_known_hosts : Option < & Vec < Value < String > > > ,
220
236
) -> Result < ( ) , KnownHostError > {
221
237
let Some ( remote_host_key) = cert_host_key. hostkey ( ) else {
222
238
return Err ( anyhow:: format_err!( "remote host key is not available" ) . into ( ) ) ;
@@ -237,6 +253,23 @@ fn check_ssh_known_hosts(
237
253
let hosts = load_hostfile ( & path) ?;
238
254
known_hosts. extend ( hosts) ;
239
255
}
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
+ }
240
273
// Load the bundled keys. Don't add keys for hosts that the user has
241
274
// configured, which gives them the option to override them. This could be
242
275
// useful if the keys are ever revoked.
@@ -363,12 +396,18 @@ fn user_known_host_location() -> Option<PathBuf> {
363
396
364
397
/// The location to display in an error message instructing the user where to
365
398
/// 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 {
367
400
// 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
+ )
372
411
}
373
412
374
413
/// A single known host entry.
0 commit comments