Codebase list rust-stfu8 / ffff0f5
Cargo - CVE fix and git2 update. Peter Michael Green 1 year, 3 months ago
10 changed file(s) with 1585 addition(s) and 1 deletion(s). Raw diff Collapse all Expand all
00 rust-cargo (0.66.0-1) UNRELEASED-FIXME-AUTOGENERATED-DEBCARGO; urgency=medium
11
2 * Package cargo 0.66.0 from crates.io using debcargo 2.6.0
3 * Backport upstream patches to fix CVE-2022-46176 and update
4 git2 dependencies.
5
6 [ Fabian Gruenbichler ]
27 * Team upload.
38 * Package cargo 0.66.0 from crates.io using debcargo 2.6.0
49
5 -- Fabian Gruenbichler <debian@fabian.gruenbichler.email> Sun, 08 Jan 2023 16:42:17 +0100
10 -- Peter Michael Green <plugwash@debian.org> Wed, 11 Jan 2023 03:06:58 +0000
611
712 rust-cargo (0.63.1-2) unstable; urgency=medium
813
0 This patch is based on the upstream commit described below, adapted for use
1 in the Debian package by Peter Michael Green.
2
3 commit 1387fd4105b242fa2d24ad99d10a5b1af23f293e
4 Author: Eric Huss <eric@huss.org>
5 Date: Wed Dec 7 18:52:00 2022 -0800
6
7 Validate SSH host keys
8
9 Index: cargo/src/cargo/sources/git/known_hosts.rs
10 ===================================================================
11 --- /dev/null
12 +++ cargo/src/cargo/sources/git/known_hosts.rs
13 @@ -0,0 +1,439 @@
14 +//! SSH host key validation support.
15 +//!
16 +//! A primary goal with this implementation is to provide user-friendly error
17 +//! messages, guiding them to understand the issue and how to resolve it.
18 +//!
19 +//! Note that there are a lot of limitations here. This reads OpenSSH
20 +//! known_hosts files from well-known locations, but it does not read OpenSSH
21 +//! config files. The config file can change the behavior of how OpenSSH
22 +//! handles known_hosts files. For example, some things we don't handle:
23 +//!
24 +//! - `GlobalKnownHostsFile` — Changes the location of the global host file.
25 +//! - `UserKnownHostsFile` — Changes the location of the user's host file.
26 +//! - `KnownHostsCommand` — A command to fetch known hosts.
27 +//! - `CheckHostIP` — DNS spoofing checks.
28 +//! - `VisualHostKey` — Shows a visual ascii-art key.
29 +//! - `VerifyHostKeyDNS` — Uses SSHFP DNS records to fetch a host key.
30 +//!
31 +//! There's also a number of things that aren't supported but could be easily
32 +//! added (it just adds a little complexity). For example, hashed hostnames,
33 +//! hostname patterns, and revoked markers. See "FIXME" comments littered in
34 +//! this file.
35 +
36 +use git2::cert::Cert;
37 +use git2::CertificateCheckStatus;
38 +use std::collections::HashSet;
39 +use std::fmt::Write;
40 +use std::path::{Path, PathBuf};
41 +
42 +/// These are host keys that are hard-coded in cargo to provide convenience.
43 +///
44 +/// If GitHub ever publishes new keys, the user can add them to their own
45 +/// configuration file to use those instead.
46 +///
47 +/// The GitHub keys are sourced from <https://api.github.com/meta> or
48 +/// <https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/githubs-ssh-key-fingerprints>.
49 +///
50 +/// These will be ignored if the user adds their own entries for `github.com`,
51 +/// which can be useful if GitHub ever revokes their old keys.
52 +static BUNDLED_KEYS: &[(&str, &str, &str)] = &[
53 + ("github.com", "ssh-ed25519", "AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl"),
54 + ("github.com", "ecdsa-sha2-nistp256", "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg="),
55 + ("github.com", "ssh-rsa", "AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ=="),
56 +];
57 +
58 +enum KnownHostError {
59 + /// Some general error happened while validating the known hosts.
60 + CheckError(anyhow::Error),
61 + /// The host key was not found.
62 + HostKeyNotFound {
63 + hostname: String,
64 + key_type: git2::cert::SshHostKeyType,
65 + remote_host_key: String,
66 + remote_fingerprint: String,
67 + other_hosts: Vec<KnownHost>,
68 + },
69 + /// The host key was found, but does not match the remote's key.
70 + HostKeyHasChanged {
71 + hostname: String,
72 + key_type: git2::cert::SshHostKeyType,
73 + old_known_host: KnownHost,
74 + remote_host_key: String,
75 + remote_fingerprint: String,
76 + },
77 +}
78 +
79 +impl From<anyhow::Error> for KnownHostError {
80 + fn from(err: anyhow::Error) -> KnownHostError {
81 + KnownHostError::CheckError(err.into())
82 + }
83 +}
84 +
85 +/// The location where a host key was located.
86 +#[derive(Clone)]
87 +enum KnownHostLocation {
88 + /// Loaded from a file from disk.
89 + File { path: PathBuf, lineno: u32 },
90 + /// Part of the hard-coded bundled keys in Cargo.
91 + Bundled,
92 +}
93 +
94 +/// The git2 callback used to validate a certificate (only ssh known hosts are validated).
95 +pub fn certificate_check(
96 + cert: &Cert<'_>,
97 + host: &str,
98 + port: Option<u16>,
99 +) -> Result<CertificateCheckStatus, git2::Error> {
100 + let Some(host_key) = cert.as_hostkey() else {
101 + // Return passthrough for TLS X509 certificates to use whatever validation
102 + // was done in git2.
103 + return Ok(CertificateCheckStatus::CertificatePassthrough)
104 + };
105 + // If a nonstandard port is in use, check for that first.
106 + // The fallback to check without a port is handled in the HostKeyNotFound handler.
107 + let host_maybe_port = match port {
108 + Some(port) if port != 22 => format!("[{host}]:{port}"),
109 + _ => host.to_string(),
110 + };
111 + // The error message must be constructed as a string to pass through the libgit2 C API.
112 + let err_msg = match check_ssh_known_hosts(host_key, &host_maybe_port) {
113 + Ok(()) => {
114 + return Ok(CertificateCheckStatus::CertificateOk);
115 + }
116 + Err(KnownHostError::CheckError(e)) => {
117 + format!("error: failed to validate host key:\n{:#}", e)
118 + }
119 + Err(KnownHostError::HostKeyNotFound {
120 + hostname,
121 + key_type,
122 + remote_host_key,
123 + remote_fingerprint,
124 + other_hosts,
125 + }) => {
126 + // Try checking without the port.
127 + if port.is_some()
128 + && !matches!(port, Some(22))
129 + && check_ssh_known_hosts(host_key, host).is_ok()
130 + {
131 + return Ok(CertificateCheckStatus::CertificateOk);
132 + }
133 + let key_type_short_name = key_type.short_name();
134 + let key_type_name = key_type.name();
135 + let known_hosts_location = user_known_host_location_to_add();
136 + let other_hosts_message = if other_hosts.is_empty() {
137 + String::new()
138 + } else {
139 + let mut msg = String::from(
140 + "Note: This host key was found, \
141 + but is associated with a different host:\n",
142 + );
143 + for known_host in other_hosts {
144 + let loc = match known_host.location {
145 + KnownHostLocation::File { path, lineno } => {
146 + format!("{} line {lineno}", path.display())
147 + }
148 + KnownHostLocation::Bundled => format!("bundled with cargo"),
149 + };
150 + write!(msg, " {loc}: {}\n", known_host.patterns).unwrap();
151 + }
152 + msg
153 + };
154 + format!("error: unknown SSH host key\n\
155 + The SSH host key for `{hostname}` is not known and cannot be validated.\n\
156 + \n\
157 + To resolve this issue, add the host key to {known_hosts_location}\n\
158 + \n\
159 + The key to add is:\n\
160 + \n\
161 + {hostname} {key_type_name} {remote_host_key}\n\
162 + \n\
163 + The {key_type_short_name} key fingerprint is: SHA256:{remote_fingerprint}\n\
164 + This fingerprint should be validated with the server administrator that it is correct.\n\
165 + {other_hosts_message}\n\
166 + See https://doc.rust-lang.org/nightly/cargo/appendix/git-authentication.html#ssh-known-hosts \
167 + for more information.\n\
168 + ")
169 + }
170 + Err(KnownHostError::HostKeyHasChanged {
171 + hostname,
172 + key_type,
173 + old_known_host,
174 + remote_host_key,
175 + remote_fingerprint,
176 + }) => {
177 + let key_type_short_name = key_type.short_name();
178 + let key_type_name = key_type.name();
179 + let known_hosts_location = user_known_host_location_to_add();
180 + let old_key_resolution = match old_known_host.location {
181 + KnownHostLocation::File { path, lineno } => {
182 + let old_key_location = path.display();
183 + format!(
184 + "removing the old {key_type_name} key for `{hostname}` \
185 + located at {old_key_location} line {lineno}, \
186 + and adding the new key to {known_hosts_location}",
187 + )
188 + }
189 + KnownHostLocation::Bundled => {
190 + format!(
191 + "adding the new key to {known_hosts_location}\n\
192 + The current host key is bundled as part of Cargo."
193 + )
194 + }
195 + };
196 + format!("error: SSH host key has changed for `{hostname}`\n\
197 + *********************************\n\
198 + * WARNING: HOST KEY HAS CHANGED *\n\
199 + *********************************\n\
200 + This may be caused by a man-in-the-middle attack, or the \
201 + server may have changed its host key.\n\
202 + \n\
203 + The {key_type_short_name} fingerprint for the key from the remote host is:\n\
204 + SHA256:{remote_fingerprint}\n\
205 + \n\
206 + You are strongly encouraged to contact the server \
207 + administrator for `{hostname}` to verify that this new key is \
208 + correct.\n\
209 + \n\
210 + If you can verify that the server has a new key, you can \
211 + resolve this error by {old_key_resolution}\n\
212 + \n\
213 + The key provided by the remote host is:\n\
214 + \n\
215 + {hostname} {key_type_name} {remote_host_key}\n\
216 + \n\
217 + See https://doc.rust-lang.org/nightly/cargo/appendix/git-authentication.html#ssh-known-hosts \
218 + for more information.\n\
219 + ")
220 + }
221 + };
222 + Err(git2::Error::new(
223 + git2::ErrorCode::GenericError,
224 + git2::ErrorClass::Callback,
225 + err_msg,
226 + ))
227 +}
228 +
229 +/// Checks if the given host/host key pair is known.
230 +fn check_ssh_known_hosts(
231 + cert_host_key: &git2::cert::CertHostkey<'_>,
232 + host: &str,
233 +) -> Result<(), KnownHostError> {
234 + let Some(remote_host_key) = cert_host_key.hostkey() else {
235 + return Err(anyhow::format_err!("remote host key is not available").into());
236 + };
237 + let remote_key_type = cert_host_key.hostkey_type().unwrap();
238 + // `changed_key` keeps track of any entries where the key has changed.
239 + let mut changed_key = None;
240 + // `other_hosts` keeps track of any entries that have an identical key,
241 + // but a different hostname.
242 + let mut other_hosts = Vec::new();
243 +
244 + // Collect all the known host entries from disk.
245 + let mut known_hosts = Vec::new();
246 + for path in known_host_files() {
247 + if !path.exists() {
248 + continue;
249 + }
250 + let hosts = load_hostfile(&path)?;
251 + known_hosts.extend(hosts);
252 + }
253 + // Load the bundled keys. Don't add keys for hosts that the user has
254 + // configured, which gives them the option to override them. This could be
255 + // useful if the keys are ever revoked.
256 + let configured_hosts: HashSet<_> = known_hosts
257 + .iter()
258 + .flat_map(|known_host| {
259 + known_host
260 + .patterns
261 + .split(',')
262 + .map(|pattern| pattern.to_lowercase())
263 + })
264 + .collect();
265 + for (patterns, key_type, key) in BUNDLED_KEYS {
266 + if !configured_hosts.contains(*patterns) {
267 + let key = base64::decode(key).unwrap();
268 + known_hosts.push(KnownHost {
269 + location: KnownHostLocation::Bundled,
270 + patterns: patterns.to_string(),
271 + key_type: key_type.to_string(),
272 + key,
273 + });
274 + }
275 + }
276 +
277 + for known_host in known_hosts {
278 + // The key type from libgit2 needs to match the key type from the host file.
279 + if known_host.key_type != remote_key_type.name() {
280 + continue;
281 + }
282 + let key_matches = known_host.key == remote_host_key;
283 + if !known_host.host_matches(host) {
284 + // `name` can be None for hashed hostnames (which libgit2 does not expose).
285 + if key_matches {
286 + other_hosts.push(known_host.clone());
287 + }
288 + continue;
289 + }
290 + if key_matches {
291 + return Ok(());
292 + }
293 + // The host and key type matched, but the key itself did not.
294 + // This indicates the key has changed.
295 + // This is only reported as an error if no subsequent lines have a
296 + // correct key.
297 + changed_key = Some(known_host.clone());
298 + }
299 + // Older versions of OpenSSH (before 6.8, March 2015) showed MD5
300 + // fingerprints (see FingerprintHash ssh config option). Here we only
301 + // support SHA256.
302 + let mut remote_fingerprint = cargo_util::Sha256::new();
303 + remote_fingerprint.update(remote_host_key);
304 + let remote_fingerprint =
305 + base64::encode_config(remote_fingerprint.finish(), base64::STANDARD_NO_PAD);
306 + let remote_host_key = base64::encode(remote_host_key);
307 + // FIXME: Ideally the error message should include the IP address of the
308 + // remote host (to help the user validate that they are connecting to the
309 + // host they were expecting to). However, I don't see a way to obtain that
310 + // information from libgit2.
311 + match changed_key {
312 + Some(old_known_host) => Err(KnownHostError::HostKeyHasChanged {
313 + hostname: host.to_string(),
314 + key_type: remote_key_type,
315 + old_known_host,
316 + remote_host_key,
317 + remote_fingerprint,
318 + }),
319 + None => Err(KnownHostError::HostKeyNotFound {
320 + hostname: host.to_string(),
321 + key_type: remote_key_type,
322 + remote_host_key,
323 + remote_fingerprint,
324 + other_hosts,
325 + }),
326 + }
327 +}
328 +
329 +/// Returns a list of files to try loading OpenSSH-formatted known hosts.
330 +fn known_host_files() -> Vec<PathBuf> {
331 + let mut result = Vec::new();
332 + if cfg!(unix) {
333 + result.push(PathBuf::from("/etc/ssh/ssh_known_hosts"));
334 + } else if cfg!(windows) {
335 + // The msys/cygwin version of OpenSSH uses `/etc` from the posix root
336 + // filesystem there (such as `C:\msys64\etc\ssh\ssh_known_hosts`).
337 + // However, I do not know of a way to obtain that location from
338 + // Windows-land. The ProgramData version here is what the PowerShell
339 + // port of OpenSSH does.
340 + if let Some(progdata) = std::env::var_os("ProgramData") {
341 + let mut progdata = PathBuf::from(progdata);
342 + progdata.push("ssh");
343 + progdata.push("ssh_known_hosts");
344 + result.push(progdata)
345 + }
346 + }
347 + result.extend(user_known_host_location());
348 + result
349 +}
350 +
351 +/// The location of the user's known_hosts file.
352 +fn user_known_host_location() -> Option<PathBuf> {
353 + // NOTE: This is a potentially inaccurate prediction of what the user
354 + // actually wants. The actual location depends on several factors:
355 + //
356 + // - Windows OpenSSH Powershell version: I believe this looks up the home
357 + // directory via ProfileImagePath in the registry, falling back to
358 + // `GetWindowsDirectoryW` if that fails.
359 + // - OpenSSH Portable (under msys): This is very complicated. I got lost
360 + // after following it through some ldap/active directory stuff.
361 + // - OpenSSH (most unix platforms): Uses `pw->pw_dir` from `getpwuid()`.
362 + //
363 + // This doesn't do anything close to that. home_dir's behavior is:
364 + // - Windows: $USERPROFILE, or SHGetFolderPathW()
365 + // - Unix: $HOME, or getpwuid_r()
366 + //
367 + // Since there is a mismatch here, the location returned here might be
368 + // different than what the user's `ssh` CLI command uses. We may want to
369 + // consider trying to align it better.
370 + home::home_dir().map(|mut home| {
371 + home.push(".ssh");
372 + home.push("known_hosts");
373 + home
374 + })
375 +}
376 +
377 +/// The location to display in an error message instructing the user where to
378 +/// add the new key.
379 +fn user_known_host_location_to_add() -> String {
380 + // Note that we don't bother with the legacy known_hosts2 files.
381 + match user_known_host_location() {
382 + Some(path) => path.to_str().expect("utf-8 home").to_string(),
383 + None => "~/.ssh/known_hosts".to_string(),
384 + }
385 +}
386 +
387 +/// A single known host entry.
388 +#[derive(Clone)]
389 +struct KnownHost {
390 + location: KnownHostLocation,
391 + /// The hostname. May be comma separated to match multiple hosts.
392 + patterns: String,
393 + key_type: String,
394 + key: Vec<u8>,
395 +}
396 +
397 +impl KnownHost {
398 + /// Returns whether or not the given host matches this known host entry.
399 + fn host_matches(&self, host: &str) -> bool {
400 + let mut match_found = false;
401 + let host = host.to_lowercase();
402 + // FIXME: support hashed hostnames
403 + for pattern in self.patterns.split(',') {
404 + let pattern = pattern.to_lowercase();
405 + // FIXME: support * and ? wildcards
406 + if let Some(pattern) = pattern.strip_prefix('!') {
407 + if pattern == host {
408 + return false;
409 + }
410 + } else {
411 + match_found = pattern == host;
412 + }
413 + }
414 + match_found
415 + }
416 +}
417 +
418 +/// Loads an OpenSSH known_hosts file.
419 +fn load_hostfile(path: &Path) -> Result<Vec<KnownHost>, anyhow::Error> {
420 + let contents = cargo_util::paths::read(path)?;
421 + let entries = contents
422 + .lines()
423 + .enumerate()
424 + .filter_map(|(lineno, line)| {
425 + let location = KnownHostLocation::File {
426 + path: path.to_path_buf(),
427 + lineno: lineno as u32 + 1,
428 + };
429 + parse_known_hosts_line(line, location)
430 + })
431 + .collect();
432 + Ok(entries)
433 +}
434 +
435 +fn parse_known_hosts_line(line: &str, location: KnownHostLocation) -> Option<KnownHost> {
436 + let line = line.trim();
437 + // FIXME: @revoked and @cert-authority is currently not supported.
438 + if line.is_empty() || line.starts_with('#') || line.starts_with('@') {
439 + return None;
440 + }
441 + let mut parts = line.split([' ', '\t']).filter(|s| !s.is_empty());
442 + let Some(patterns) = parts.next() else { return None };
443 + let Some(key_type) = parts.next() else { return None };
444 + let Some(key) = parts.next() else { return None };
445 + let Ok(key) = base64::decode(key) else { return None };
446 + Some(KnownHost {
447 + location,
448 + patterns: patterns.to_string(),
449 + key_type: key_type.to_string(),
450 + key,
451 + })
452 +}
453 Index: cargo/src/cargo/sources/git/mod.rs
454 ===================================================================
455 --- cargo.orig/src/cargo/sources/git/mod.rs
456 +++ cargo/src/cargo/sources/git/mod.rs
457 @@ -1,4 +1,5 @@
458 pub use self::source::GitSource;
459 pub use self::utils::{fetch, GitCheckout, GitDatabase, GitRemote};
460 +mod known_hosts;
461 mod source;
462 mod utils;
463 Index: cargo/src/cargo/sources/git/utils.rs
464 ===================================================================
465 --- cargo.orig/src/cargo/sources/git/utils.rs
466 +++ cargo/src/cargo/sources/git/utils.rs
467 @@ -647,7 +647,6 @@ where
468 | ErrorClass::Submodule
469 | ErrorClass::FetchHead
470 | ErrorClass::Ssh
471 - | ErrorClass::Callback
472 | ErrorClass::Http => {
473 let mut msg = "network failure seems to have happened\n".to_string();
474 msg.push_str(
475 @@ -658,6 +657,13 @@ where
476 );
477 err = err.context(msg);
478 }
479 + ErrorClass::Callback => {
480 + // This unwraps the git2 error. We're using the callback error
481 + // specifically to convey errors from Rust land through the C
482 + // callback interface. We don't need the `; class=Callback
483 + // (26)` that gets tacked on to the git2 error message.
484 + err = anyhow::format_err!("{}", e.message());
485 + }
486 _ => {}
487 }
488 }
489 @@ -686,12 +692,16 @@ pub fn with_fetch_options(
490 let mut progress = Progress::new("Fetch", config);
491 network::with_retry(config, || {
492 with_authentication(url, git_config, |f| {
493 + let port = Url::parse(url).ok().and_then(|url| url.port());
494 let mut last_update = Instant::now();
495 let mut rcb = git2::RemoteCallbacks::new();
496 // We choose `N=10` here to make a `300ms * 10slots ~= 3000ms`
497 // sliding window for tracking the data transfer rate (in bytes/s).
498 let mut counter = MetricsCounter::<10>::new(0, last_update);
499 rcb.credentials(f);
500 + rcb.certificate_check(|cert, host| {
501 + super::known_hosts::certificate_check(cert, host, port)
502 + });
503 rcb.transfer_progress(|stats| {
504 let indexed_deltas = stats.indexed_deltas();
505 let msg = if indexed_deltas > 0 {
506 Index: cargo/src/doc/src/appendix/git-authentication.md
507 ===================================================================
508 --- cargo.orig/src/doc/src/appendix/git-authentication.md
509 +++ cargo/src/doc/src/appendix/git-authentication.md
510 @@ -58,9 +58,32 @@ on how to start `ssh-agent` and to add k
511 > used by Cargo's built-in SSH library. More advanced requirements should use
512 > [`net.git-fetch-with-cli`].
513
514 +### SSH Known Hosts
515 +
516 +When connecting to an SSH host, Cargo must verify the identity of the host
517 +using "known hosts", which are a list of host keys. Cargo can look for these
518 +known hosts in OpenSSH-style `known_hosts` files located in their standard
519 +locations (`.ssh/known_hosts` in your home directory, or
520 +`/etc/ssh/ssh_known_hosts` on Unix-like platforms or
521 +`%PROGRAMDATA%\ssh\ssh_known_hosts` on Windows). More information about these
522 +files can be found in the [sshd man page].
523 +
524 +When connecting to an SSH host before the known hosts has been configured,
525 +Cargo will display an error message instructing you how to add the host key.
526 +This also includes a "fingerprint", which is a smaller hash of the host key,
527 +which should be easier to visually verify. The server administrator can get
528 +the fingerprint by running `ssh-keygen` against the public key (for example,
529 +`ssh-keygen -l -f /etc/ssh/ssh_host_ecdsa_key.pub`). Well-known sites may
530 +publish their fingerprints on the web; for example GitHub posts theirs at
531 +<https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/githubs-ssh-key-fingerprints>.
532 +
533 +Cargo comes with the host keys for [github.com](https://github.com) built-in.
534 +If those ever change, you can add the new keys to your known_hosts file.
535 +
536 [`credential.helper`]: https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage
537 [`net.git-fetch-with-cli`]: ../reference/config.md#netgit-fetch-with-cli
538 [GCM]: https://github.com/microsoft/Git-Credential-Manager-Core/
539 [PuTTY]: https://www.chiark.greenend.org.uk/~sgtatham/putty/
540 [Microsoft installation documentation]: https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_install_firstuse
541 [key management]: https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_keymanagement
542 +[sshd man page]: https://man.openbsd.org/sshd#SSH_KNOWN_HOSTS_FILE_FORMAT
543 --- rust-cargo-0.66.0.orig/Cargo.toml
544 +++ rust-cargo-0.66.0/Cargo.toml
545 @@ -38,6 +38,9 @@ version = "1.0"
546 [dependencies.atty]
547 version = "0.2"
548
549 +[dependencies.base64]
550 +version = "0.13.1"
551 +
552 [dependencies.bytesize]
553 version = "1.0"
554
555 @@ -72,10 +75,10 @@ features = ["zlib"]
556 default-features = false
557
558 [dependencies.git2]
559 -version = "0.15.0"
560 +version = "0.16.0"
561
562 [dependencies.git2-curl]
563 -version = "0.16.0"
564 +version = "0.17.0"
565
566 [dependencies.glob]
567 version = "0.3.0"
568 @@ -114,7 +120,7 @@ version = "1.2.0"
569 version = "0.2"
570
571 [dependencies.libgit2-sys]
572 -version = "0.14.0"
573 +version = "0.14.1"
574
575 [dependencies.log]
576 version = "0.4.6"
0 commit 9f62f8440e9e542f27d60c75be38ac51186c6c32
1 Author: Eric Huss <eric@huss.org>
2 Date: Fri Dec 9 20:03:27 2022 -0800
3
4 Add support for deserializing Vec<Value<String>> in config.
5
6 This adds the ability to track the definition location of a string
7 in a TOML array.
8
9 diff --git a/src/cargo/util/config/de.rs b/src/cargo/util/config/de.rs
10 index 6fddc7e71f..1408f15b57 100644
11 --- a/src/cargo/util/config/de.rs
12 +++ b/src/cargo/util/config/de.rs
13 @@ -384,7 +384,12 @@ impl<'de> de::SeqAccess<'de> for ConfigSeqAccess {
14 {
15 match self.list_iter.next() {
16 // TODO: add `def` to error?
17 - Some((value, _def)) => seed.deserialize(value.into_deserializer()).map(Some),
18 + Some((value, def)) => {
19 + // This might be a String or a Value<String>.
20 + // ValueDeserializer will handle figuring out which one it is.
21 + let maybe_value_de = ValueDeserializer::new_with_string(value, def);
22 + seed.deserialize(maybe_value_de).map(Some)
23 + }
24 None => Ok(None),
25 }
26 }
27 @@ -400,7 +405,17 @@ impl<'de> de::SeqAccess<'de> for ConfigSeqAccess {
28 struct ValueDeserializer<'config> {
29 hits: u32,
30 definition: Definition,
31 - de: Deserializer<'config>,
32 + /// The deserializer, used to actually deserialize a Value struct.
33 + /// This is `None` if deserializing a string.
34 + de: Option<Deserializer<'config>>,
35 + /// A string value to deserialize.
36 + ///
37 + /// This is used for situations where you can't address a string via a
38 + /// TOML key, such as a string inside an array. The `ConfigSeqAccess`
39 + /// doesn't know if the type it should deserialize to is a `String` or
40 + /// `Value<String>`, so `ValueDeserializer` needs to be able to handle
41 + /// both.
42 + str_value: Option<String>,
43 }
44
45 impl<'config> ValueDeserializer<'config> {
46 @@ -428,9 +443,19 @@ impl<'config> ValueDeserializer<'config> {
47 Ok(ValueDeserializer {
48 hits: 0,
49 definition,
50 - de,
51 + de: Some(de),
52 + str_value: None,
53 })
54 }
55 +
56 + fn new_with_string(s: String, definition: Definition) -> ValueDeserializer<'config> {
57 + ValueDeserializer {
58 + hits: 0,
59 + definition,
60 + de: None,
61 + str_value: Some(s),
62 + }
63 + }
64 }
65
66 impl<'de, 'config> de::MapAccess<'de> for ValueDeserializer<'config> {
67 @@ -459,9 +484,14 @@ impl<'de, 'config> de::MapAccess<'de> for ValueDeserializer<'config> {
68 // If this is the first time around we deserialize the `value` field
69 // which is the actual deserializer
70 if self.hits == 1 {
71 - return seed
72 - .deserialize(self.de.clone())
73 - .map_err(|e| e.with_key_context(&self.de.key, self.definition.clone()));
74 + if let Some(de) = &self.de {
75 + return seed
76 + .deserialize(de.clone())
77 + .map_err(|e| e.with_key_context(&de.key, self.definition.clone()));
78 + } else {
79 + return seed
80 + .deserialize(self.str_value.as_ref().unwrap().clone().into_deserializer());
81 + }
82 }
83
84 // ... otherwise we're deserializing the `definition` field, so we need
85 @@ -484,6 +514,71 @@ impl<'de, 'config> de::MapAccess<'de> for ValueDeserializer<'config> {
86 }
87 }
88
89 +// Deserializer is only implemented to handle deserializing a String inside a
90 +// sequence (like `Vec<String>` or `Vec<Value<String>>`). `Value<String>` is
91 +// handled by deserialize_struct, and the plain `String` is handled by all the
92 +// other functions here.
93 +impl<'de, 'config> de::Deserializer<'de> for ValueDeserializer<'config> {
94 + type Error = ConfigError;
95 +
96 + fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
97 + where
98 + V: de::Visitor<'de>,
99 + {
100 + visitor.visit_str(&self.str_value.expect("string expected"))
101 + }
102 +
103 + fn deserialize_string<V>(self, visitor: V) -> Result<V::Value, Self::Error>
104 + where
105 + V: de::Visitor<'de>,
106 + {
107 + visitor.visit_string(self.str_value.expect("string expected"))
108 + }
109 +
110 + fn deserialize_struct<V>(
111 + self,
112 + name: &'static str,
113 + fields: &'static [&'static str],
114 + visitor: V,
115 + ) -> Result<V::Value, Self::Error>
116 + where
117 + V: de::Visitor<'de>,
118 + {
119 + // Match on the magical struct name/field names that are passed in to
120 + // detect when we're deserializing `Value<T>`.
121 + //
122 + // See more comments in `value.rs` for the protocol used here.
123 + if name == value::NAME && fields == value::FIELDS {
124 + return visitor.visit_map(self);
125 + }
126 + unimplemented!("only strings and Value can be deserialized from a sequence");
127 + }
128 +
129 + fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
130 + where
131 + V: de::Visitor<'de>,
132 + {
133 + visitor.visit_string(self.str_value.expect("string expected"))
134 + }
135 +
136 + fn deserialize_ignored_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
137 + where
138 + V: de::Visitor<'de>,
139 + {
140 + visitor.visit_unit()
141 + }
142 +
143 + serde::forward_to_deserialize_any! {
144 + i8 i16 i32 i64
145 + u8 u16 u32 u64
146 + option
147 + newtype_struct seq tuple tuple_struct map enum bool
148 + f32 f64 char bytes
149 + byte_buf unit unit_struct
150 + identifier
151 + }
152 +}
153 +
154 /// A deserializer which takes two values and deserializes into a tuple of those
155 /// two values. This is similar to types like `StrDeserializer` in upstream
156 /// serde itself.
0 commit 026bda3fb5eddac0df111ee150706f756558a7b3
1 Author: Eric Huss <eric@huss.org>
2 Date: Fri Dec 9 20:38:12 2022 -0800
3
4 Support configuring ssh known-hosts via cargo config.
5
6 diff --git a/src/cargo/sources/git/known_hosts.rs b/src/cargo/sources/git/known_hosts.rs
7 index 875dcf63f3..7efea43c3b 100644
8 --- a/src/cargo/sources/git/known_hosts.rs
9 +++ b/src/cargo/sources/git/known_hosts.rs
10 @@ -20,6 +20,7 @@
11 //! hostname patterns, and revoked markers. See "FIXME" comments littered in
12 //! this file.
13
14 +use crate::util::config::{Definition, Value};
15 use git2::cert::Cert;
16 use git2::CertificateCheckStatus;
17 use std::collections::HashSet;
18 @@ -74,6 +75,8 @@ impl From<anyhow::Error> for KnownHostError {
19 enum KnownHostLocation {
20 /// Loaded from a file from disk.
21 File { path: PathBuf, lineno: u32 },
22 + /// Loaded from cargo's config system.
23 + Config { definition: Definition },
24 /// Part of the hard-coded bundled keys in Cargo.
25 Bundled,
26 }
27 @@ -83,6 +86,8 @@ pub fn certificate_check(
28 cert: &Cert<'_>,
29 host: &str,
30 port: Option<u16>,
31 + config_known_hosts: Option<&Vec<Value<String>>>,
32 + diagnostic_home_config: &str,
33 ) -> Result<CertificateCheckStatus, git2::Error> {
34 let Some(host_key) = cert.as_hostkey() else {
35 // Return passthrough for TLS X509 certificates to use whatever validation
36 @@ -96,7 +101,7 @@ pub fn certificate_check(
37 _ => host.to_string(),
38 };
39 // The error message must be constructed as a string to pass through the libgit2 C API.
40 - let err_msg = match check_ssh_known_hosts(host_key, &host_maybe_port) {
41 + let err_msg = match check_ssh_known_hosts(host_key, &host_maybe_port, config_known_hosts) {
42 Ok(()) => {
43 return Ok(CertificateCheckStatus::CertificateOk);
44 }
45 @@ -113,13 +118,13 @@ pub fn certificate_check(
46 // Try checking without the port.
47 if port.is_some()
48 && !matches!(port, Some(22))
49 - && check_ssh_known_hosts(host_key, host).is_ok()
50 + && check_ssh_known_hosts(host_key, host, config_known_hosts).is_ok()
51 {
52 return Ok(CertificateCheckStatus::CertificateOk);
53 }
54 let key_type_short_name = key_type.short_name();
55 let key_type_name = key_type.name();
56 - let known_hosts_location = user_known_host_location_to_add();
57 + let known_hosts_location = user_known_host_location_to_add(diagnostic_home_config);
58 let other_hosts_message = if other_hosts.is_empty() {
59 String::new()
60 } else {
61 @@ -132,6 +137,9 @@ pub fn certificate_check(
62 KnownHostLocation::File { path, lineno } => {
63 format!("{} line {lineno}", path.display())
64 }
65 + KnownHostLocation::Config { definition } => {
66 + format!("config value from {definition}")
67 + }
68 KnownHostLocation::Bundled => format!("bundled with cargo"),
69 };
70 write!(msg, " {loc}: {}\n", known_host.patterns).unwrap();
71 @@ -163,7 +171,7 @@ pub fn certificate_check(
72 }) => {
73 let key_type_short_name = key_type.short_name();
74 let key_type_name = key_type.name();
75 - let known_hosts_location = user_known_host_location_to_add();
76 + let known_hosts_location = user_known_host_location_to_add(diagnostic_home_config);
77 let old_key_resolution = match old_known_host.location {
78 KnownHostLocation::File { path, lineno } => {
79 let old_key_location = path.display();
80 @@ -173,6 +181,13 @@ pub fn certificate_check(
81 and adding the new key to {known_hosts_location}",
82 )
83 }
84 + KnownHostLocation::Config { definition } => {
85 + format!(
86 + "removing the old {key_type_name} key for `{hostname}` \
87 + loaded from Cargo's config at {definition}, \
88 + and adding the new key to {known_hosts_location}"
89 + )
90 + }
91 KnownHostLocation::Bundled => {
92 format!(
93 "adding the new key to {known_hosts_location}\n\
94 @@ -217,6 +232,7 @@ pub fn certificate_check(
95 fn check_ssh_known_hosts(
96 cert_host_key: &git2::cert::CertHostkey<'_>,
97 host: &str,
98 + config_known_hosts: Option<&Vec<Value<String>>>,
99 ) -> Result<(), KnownHostError> {
100 let Some(remote_host_key) = cert_host_key.hostkey() else {
101 return Err(anyhow::format_err!("remote host key is not available").into());
102 @@ -237,6 +253,23 @@ fn check_ssh_known_hosts(
103 let hosts = load_hostfile(&path)?;
104 known_hosts.extend(hosts);
105 }
106 + if let Some(config_known_hosts) = config_known_hosts {
107 + // Format errors aren't an error in case the format needs to change in
108 + // the future, to retain forwards compatibility.
109 + for line_value in config_known_hosts {
110 + let location = KnownHostLocation::Config {
111 + definition: line_value.definition.clone(),
112 + };
113 + match parse_known_hosts_line(&line_value.val, location) {
114 + Some(known_host) => known_hosts.push(known_host),
115 + None => log::warn!(
116 + "failed to parse known host {} from {}",
117 + line_value.val,
118 + line_value.definition
119 + ),
120 + }
121 + }
122 + }
123 // Load the bundled keys. Don't add keys for hosts that the user has
124 // configured, which gives them the option to override them. This could be
125 // useful if the keys are ever revoked.
126 @@ -363,12 +396,18 @@ fn user_known_host_location() -> Option<PathBuf> {
127
128 /// The location to display in an error message instructing the user where to
129 /// add the new key.
130 -fn user_known_host_location_to_add() -> String {
131 +fn user_known_host_location_to_add(diagnostic_home_config: &str) -> String {
132 // Note that we don't bother with the legacy known_hosts2 files.
133 - match user_known_host_location() {
134 - Some(path) => path.to_str().expect("utf-8 home").to_string(),
135 - None => "~/.ssh/known_hosts".to_string(),
136 - }
137 + let user = user_known_host_location();
138 + let openssh_loc = match &user {
139 + Some(path) => path.to_str().expect("utf-8 home"),
140 + None => "~/.ssh/known_hosts",
141 + };
142 + format!(
143 + "the `net.ssh.known-hosts` array in your Cargo configuration \
144 + (such as {diagnostic_home_config}) \
145 + or in your OpenSSH known_hosts file at {openssh_loc}"
146 + )
147 }
148
149 /// A single known host entry.
150 diff --git a/src/cargo/sources/git/utils.rs b/src/cargo/sources/git/utils.rs
151 index 831c43be6b..457c97c5bb 100644
152 --- a/src/cargo/sources/git/utils.rs
153 +++ b/src/cargo/sources/git/utils.rs
154 @@ -726,6 +726,9 @@ pub fn with_fetch_options(
155 cb: &mut dyn FnMut(git2::FetchOptions<'_>) -> CargoResult<()>,
156 ) -> CargoResult<()> {
157 let mut progress = Progress::new("Fetch", config);
158 + let ssh_config = config.net_config()?.ssh.as_ref();
159 + let config_known_hosts = ssh_config.and_then(|ssh| ssh.known_hosts.as_ref());
160 + let diagnostic_home_config = config.diagnostic_home_config();
161 network::with_retry(config, || {
162 with_authentication(url, git_config, |f| {
163 let port = Url::parse(url).ok().and_then(|url| url.port());
164 @@ -736,7 +739,13 @@ pub fn with_fetch_options(
165 let mut counter = MetricsCounter::<10>::new(0, last_update);
166 rcb.credentials(f);
167 rcb.certificate_check(|cert, host| {
168 - super::known_hosts::certificate_check(cert, host, port)
169 + super::known_hosts::certificate_check(
170 + cert,
171 + host,
172 + port,
173 + config_known_hosts,
174 + &diagnostic_home_config,
175 + )
176 });
177 rcb.transfer_progress(|stats| {
178 let indexed_deltas = stats.indexed_deltas();
179 diff --git a/src/cargo/util/config/mod.rs b/src/cargo/util/config/mod.rs
180 index d30e094413..d9ab142c4e 100644
181 --- a/src/cargo/util/config/mod.rs
182 +++ b/src/cargo/util/config/mod.rs
183 @@ -356,6 +356,18 @@ impl Config {
184 &self.home_path
185 }
186
187 + /// Returns a path to display to the user with the location of their home
188 + /// config file (to only be used for displaying a diagnostics suggestion,
189 + /// such as recommending where to add a config value).
190 + pub fn diagnostic_home_config(&self) -> String {
191 + let home = self.home_path.as_path_unlocked();
192 + let path = match self.get_file_path(home, "config", false) {
193 + Ok(Some(existing_path)) => existing_path,
194 + _ => home.join("config.toml"),
195 + };
196 + path.to_string_lossy().to_string()
197 + }
198 +
199 /// Gets the Cargo Git directory (`<cargo_home>/git`).
200 pub fn git_path(&self) -> Filesystem {
201 self.home_path.join("git")
202 @@ -2356,6 +2368,13 @@ pub struct CargoNetConfig {
203 pub retry: Option<u32>,
204 pub offline: Option<bool>,
205 pub git_fetch_with_cli: Option<bool>,
206 + pub ssh: Option<CargoSshConfig>,
207 +}
208 +
209 +#[derive(Debug, Deserialize)]
210 +#[serde(rename_all = "kebab-case")]
211 +pub struct CargoSshConfig {
212 + pub known_hosts: Option<Vec<Value<String>>>,
213 }
214
215 #[derive(Debug, Deserialize)]
216 diff --git a/src/doc/src/appendix/git-authentication.md b/src/doc/src/appendix/git-authentication.md
217 index a7db1ac7f1..f46a6535c6 100644
218 --- a/src/doc/src/appendix/git-authentication.md
219 +++ b/src/doc/src/appendix/git-authentication.md
220 @@ -66,7 +66,8 @@ known hosts in OpenSSH-style `known_hosts` files located in their standard
221 locations (`.ssh/known_hosts` in your home directory, or
222 `/etc/ssh/ssh_known_hosts` on Unix-like platforms or
223 `%PROGRAMDATA%\ssh\ssh_known_hosts` on Windows). More information about these
224 -files can be found in the [sshd man page].
225 +files can be found in the [sshd man page]. Alternatively, keys may be
226 +configured in a Cargo configuration file with [`net.ssh.known-hosts`].
227
228 When connecting to an SSH host before the known hosts has been configured,
229 Cargo will display an error message instructing you how to add the host key.
230 @@ -78,10 +79,11 @@ publish their fingerprints on the web; for example GitHub posts theirs at
231 <https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/githubs-ssh-key-fingerprints>.
232
233 Cargo comes with the host keys for [github.com](https://github.com) built-in.
234 -If those ever change, you can add the new keys to your known_hosts file.
235 +If those ever change, you can add the new keys to the config or known_hosts file.
236
237 [`credential.helper`]: https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage
238 [`net.git-fetch-with-cli`]: ../reference/config.md#netgit-fetch-with-cli
239 +[`net.ssh.known-hosts`]: ../reference/config.md#netsshknown-hosts
240 [GCM]: https://github.com/microsoft/Git-Credential-Manager-Core/
241 [PuTTY]: https://www.chiark.greenend.org.uk/~sgtatham/putty/
242 [Microsoft installation documentation]: https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_install_firstuse
243 diff --git a/src/doc/src/reference/config.md b/src/doc/src/reference/config.md
244 index 1e50648797..f804ceebea 100644
245 --- a/src/doc/src/reference/config.md
246 +++ b/src/doc/src/reference/config.md
247 @@ -114,6 +114,9 @@ retry = 2 # network retries
248 git-fetch-with-cli = true # use the `git` executable for git operations
249 offline = true # do not access the network
250
251 +[net.ssh]
252 +known-hosts = ["..."] # known SSH host keys
253 +
254 [patch.<registry>]
255 # Same keys as for [patch] in Cargo.toml
256
257 @@ -750,6 +753,41 @@ needed, and generate an error if it encounters a network error.
258
259 Can be overridden with the `--offline` command-line option.
260
261 +##### `net.ssh`
262 +
263 +The `[net.ssh]` table contains settings for SSH connections.
264 +
265 +##### `net.ssh.known-hosts`
266 +* Type: array of strings
267 +* Default: see description
268 +* Environment: not supported
269 +
270 +The `known-hosts` array contains a list of SSH host keys that should be
271 +accepted as valid when connecting to an SSH server (such as for SSH git
272 +dependencies). Each entry should be a string in a format similar to OpenSSH
273 +`known_hosts` files. Each string should start with one or more hostnames
274 +separated by commas, a space, the key type name, a space, and the
275 +base64-encoded key. For example:
276 +
277 +```toml
278 +[net.ssh]
279 +known-hosts = [
280 + "example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFO4Q5T0UV0SQevair9PFwoxY9dl4pQl3u5phoqJH3cF"
281 +]
282 +```
283 +
284 +Cargo will attempt to load known hosts keys from common locations supported in
285 +OpenSSH, and will join those with any listed in a Cargo configuration file.
286 +If any matching entry has the correct key, the connection will be allowed.
287 +
288 +Cargo comes with the host keys for [github.com][github-keys] built-in. If
289 +those ever change, you can add the new keys to the config or known_hosts file.
290 +
291 +See [Git Authentication](../appendix/git-authentication.md#ssh-known-hosts)
292 +for more details.
293 +
294 +[github-keys]: https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/githubs-ssh-key-fingerprints
295 +
296 #### `[patch]`
297
298 Just as you can override dependencies using [`[patch]` in
0 commit 302a543ddf3b7621c2f10623862029d35fae7e3c
1 Author: Eric Huss <eric@huss.org>
2 Date: Mon Dec 12 20:14:23 2022 -0800
3
4 Add some known_hosts tests.
5
6 This also fixes a bug with the host matching when there are comma-separated hosts.
7
8 diff --git a/src/cargo/sources/git/known_hosts.rs b/src/cargo/sources/git/known_hosts.rs
9 index 7efea43c3b..58e64e7913 100644
10 --- a/src/cargo/sources/git/known_hosts.rs
11 +++ b/src/cargo/sources/git/known_hosts.rs
12 @@ -21,7 +21,7 @@
13 //! this file.
14
15 use crate::util::config::{Definition, Value};
16 -use git2::cert::Cert;
17 +use git2::cert::{Cert, SshHostKeyType};
18 use git2::CertificateCheckStatus;
19 use std::collections::HashSet;
20 use std::fmt::Write;
21 @@ -49,7 +49,7 @@ enum KnownHostError {
22 /// The host key was not found.
23 HostKeyNotFound {
24 hostname: String,
25 - key_type: git2::cert::SshHostKeyType,
26 + key_type: SshHostKeyType,
27 remote_host_key: String,
28 remote_fingerprint: String,
29 other_hosts: Vec<KnownHost>,
30 @@ -57,7 +57,7 @@ enum KnownHostError {
31 /// The host key was found, but does not match the remote's key.
32 HostKeyHasChanged {
33 hostname: String,
34 - key_type: git2::cert::SshHostKeyType,
35 + key_type: SshHostKeyType,
36 old_known_host: KnownHost,
37 remote_host_key: String,
38 remote_fingerprint: String,
39 @@ -238,11 +238,6 @@ fn check_ssh_known_hosts(
40 return Err(anyhow::format_err!("remote host key is not available").into());
41 };
42 let remote_key_type = cert_host_key.hostkey_type().unwrap();
43 - // `changed_key` keeps track of any entries where the key has changed.
44 - let mut changed_key = None;
45 - // `other_hosts` keeps track of any entries that have an identical key,
46 - // but a different hostname.
47 - let mut other_hosts = Vec::new();
48
49 // Collect all the known host entries from disk.
50 let mut known_hosts = Vec::new();
51 @@ -293,6 +288,21 @@ fn check_ssh_known_hosts(
52 });
53 }
54 }
55 + check_ssh_known_hosts_loaded(&known_hosts, host, remote_key_type, remote_host_key)
56 +}
57 +
58 +/// Checks a host key against a loaded set of known hosts.
59 +fn check_ssh_known_hosts_loaded(
60 + known_hosts: &[KnownHost],
61 + host: &str,
62 + remote_key_type: SshHostKeyType,
63 + remote_host_key: &[u8],
64 +) -> Result<(), KnownHostError> {
65 + // `changed_key` keeps track of any entries where the key has changed.
66 + let mut changed_key = None;
67 + // `other_hosts` keeps track of any entries that have an identical key,
68 + // but a different hostname.
69 + let mut other_hosts = Vec::new();
70
71 for known_host in known_hosts {
72 // The key type from libgit2 needs to match the key type from the host file.
73 @@ -301,7 +311,6 @@ fn check_ssh_known_hosts(
74 }
75 let key_matches = known_host.key == remote_host_key;
76 if !known_host.host_matches(host) {
77 - // `name` can be None for hashed hostnames (which libgit2 does not expose).
78 if key_matches {
79 other_hosts.push(known_host.clone());
80 }
81 @@ -434,7 +443,7 @@ impl KnownHost {
82 return false;
83 }
84 } else {
85 - match_found = pattern == host;
86 + match_found |= pattern == host;
87 }
88 }
89 match_found
90 @@ -444,6 +453,10 @@ impl KnownHost {
91 /// Loads an OpenSSH known_hosts file.
92 fn load_hostfile(path: &Path) -> Result<Vec<KnownHost>, anyhow::Error> {
93 let contents = cargo_util::paths::read(path)?;
94 + Ok(load_hostfile_contents(path, &contents))
95 +}
96 +
97 +fn load_hostfile_contents(path: &Path, contents: &str) -> Vec<KnownHost> {
98 let entries = contents
99 .lines()
100 .enumerate()
101 @@ -455,13 +468,13 @@ fn load_hostfile(path: &Path) -> Result<Vec<KnownHost>, anyhow::Error> {
102 parse_known_hosts_line(line, location)
103 })
104 .collect();
105 - Ok(entries)
106 + entries
107 }
108
109 fn parse_known_hosts_line(line: &str, location: KnownHostLocation) -> Option<KnownHost> {
110 let line = line.trim();
111 // FIXME: @revoked and @cert-authority is currently not supported.
112 - if line.is_empty() || line.starts_with('#') || line.starts_with('@') {
113 + if line.is_empty() || line.starts_with(['#', '@', '|']) {
114 return None;
115 }
116 let mut parts = line.split([' ', '\t']).filter(|s| !s.is_empty());
117 @@ -476,3 +489,126 @@ fn parse_known_hosts_line(line: &str, location: KnownHostLocation) -> Option<Kno
118 key,
119 })
120 }
121 +
122 +#[cfg(test)]
123 +mod tests {
124 + use super::*;
125 +
126 + static COMMON_CONTENTS: &str = r#"
127 + # Comments allowed at start of line
128 +
129 + example.com,rust-lang.org ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC5MzWIpZwpkpDjyCNiTIEVFhSA9OUUQvjFo7CgZBGCAj/cqeUIgiLsgtfmtBsfWIkAECQpM7ePP7NLZFGJcHvoyg5jXJiIX5s0eKo9IlcuTLLrMkW5MkHXE7bNklVbW1WdCfF2+y7Ao25B4L8FFRokMh0yp/H6+8xZ7PdVwL3FRPEg8ftZ5R0kuups6xiMHPRX+f/07vfJzA47YDPmXfhkn+JK8kL0JYw8iy8BtNBfRQL99d9iXJzWXnNce5NHMuKD5rOonD3aQHLDlwK+KhrFRrdaxQEM8ZWxNti0ux8yT4Dl5jJY0CrIu3Xl6+qroVgTqJGNkTbhs5DGWdFh6BLPTTH15rN4buisg7uMyLyHqx06ckborqD33gWu+Jig7O+PV6KJmL5mp1O1HXvZqkpBdTiT6GiDKG3oECCIXkUk0BSU9VG9VQcrMxxvgiHlyoXUAfYQoXv/lnxkTnm+Sr36kutsVOs7n5B43ZKAeuaxyQ11huJZpxamc0RA1HM641s= eric@host
130 + Example.net ssh-dss AAAAB3NzaC1kc3MAAACBAK2Ek3jVxisXmz5UcZ7W65BAj/nDJCCVvSe0Aytndn4PH6k7sVesut5OoY6PdksZ9tEfuFjjS9HR5SJb8j1GW0GxtaSHHbf+rNc36PeU75bffzyIWwpA8uZFONt5swUAXJXcsHOoapNbUFuhHsRhB2hXxz9QGNiiwIwRJeSHixKRAAAAFQChKfxO1z9H2/757697xP5nJ/Z5dwAAAIEAoc+HIWas+4WowtB/KtAp6XE0B9oHI+55wKtdcGwwb7zHKK9scWNXwxIcMhSvyB3Oe2I7dQQlvyIWxsdZlzOkX0wdsTHjIAnBAP68MyvMv4kq3+I5GAVcFsqoLZfZvh0dlcgUq1/YNYZwKlt89tnzk8Fp4KLWmuw8Bd8IShYVa78AAACAL3qd8kNTY7CthgsQ8iWdjbkGSF/1KCeFyt8UjurInp9wvPDjqagwakbyLOzN7y3/ItTPCaGuX+RjFP0zZTf8i9bsAVyjFJiJ7vzRXcWytuFWANrpzLTn1qzPfh63iK92Aw8AVBYvEA/4bxo+XReAvhNBB/m78G6OedTeu6ZoTsI= eric@host
131 + [example.net]:2222 ssh-dss AAAAB3NzaC1kc3MAAACBAJJN5kLZEpOJpXWyMT4KwYvLAj+b9ErNtglxOi86C6Kw7oZeYdDMCfD3lc3PJyX64udQcWGfO4abSESMiYdY43yFAZH279QGH5Q/B5CklVvTqYpfAUR+1r9TQxy3OVQHk7FB2wOi4xNQ3myO0vaYlBOB9il+P223aERbXx4JTWdvAAAAFQCTHWTcXxLK5Z6ZVPmfdSDyHzkF2wAAAIEAhp41/mTnM0Y0EWSyCXuETMW1QSpKGF8sqoZKp6wdzyhLXu0i32gLdXj4p24em/jObYh93hr+MwgxqWq+FHgD+D80Qg5f6vj4yEl4Uu5hqtTpCBFWUQoyEckbUkPf8uZ4/XzAne+tUSjZm09xATCmK9U2IGqZE+D+90eBkf1Svc8AAACAeKhi4EtfwenFYqKz60ZoEEhIsE1yI2jH73akHnfHpcW84w+fk3YlwjcfDfyYso+D0jZBdJeK5qIdkbUWhAX8wDjJVO0WL6r/YPr4yu/CgEyW1H59tAbujGJ4NR0JDqioulzYqNHnxpiw1RJukZnPBfSFKzRElvPOCq/NkQM/Mwk= eric@host
132 + nistp256.example.org ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJ4iYGCcJrUIfrHfzlsv8e8kaF36qpcUpe3VNAKVCZX/BDptIdlEe8u8vKNRTPgUO9jqS0+tjTcPiQd8/8I9qng= eric@host
133 + nistp384.example.org ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBNuGT3TqMz2rcwOt2ZqkiNqq7dvWPE66W2qPCoZsh0pQhVU3BnhKIc6nEr6+Wts0Z3jdF3QWwxbbTjbVTVhdr8fMCFhDCWiQFm9xLerYPKnu9qHvx9K87/fjc5+0pu4hLA== eric@host
134 + nistp521.example.org ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAD35HH6OsK4DN75BrKipVj/GvZaUzjPNa1F8wMjUdPB1JlVcUfgzJjWSxrhmaNN3u0soiZw8WNRFINsGPCw5E7DywF1689WcIj2Ye2rcy99je15FknScTzBBD04JgIyOI50mCUaPCBoF14vFlN6BmO00cFo+yzy5N8GuQ2sx9kr21xmFQ== eric@host
135 + # Revoked not yet supported.
136 + @revoked * ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKtQsi+KPYispwm2rkMidQf30fG1Niy8XNkvASfePoca eric@host
137 + example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAWkjI6XT2SZh3xNk5NhisA3o3sGzWR+VAKMSqHtI0aY eric@host
138 + 192.168.42.12 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKVYJpa0yUGaNk0NXQTPWa0tHjqRpx+7hl2diReH6DtR eric@host
139 + # Hash not yet supported.
140 + |1|7CMSYgzdwruFLRhwowMtKx0maIE=|Tlff1GFqc3Ao+fUWxMEVG8mJiyk= ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIHgN3O21U4LWtP5OzjTzPnUnSDmCNDvyvlaj6Hi65JC eric@host
141 + # Negation isn't terribly useful without globs.
142 + neg.example.com,!neg.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOXfUnaAHTlo1Qi//rNk26OcmHikmkns1Z6WW/UuuS3K eric@host
143 + "#;
144 +
145 + #[test]
146 + fn known_hosts_parse() {
147 + let kh_path = Path::new("/home/abc/.known_hosts");
148 + let khs = load_hostfile_contents(kh_path, COMMON_CONTENTS);
149 + assert_eq!(khs.len(), 9);
150 + match &khs[0].location {
151 + KnownHostLocation::File { path, lineno } => {
152 + assert_eq!(path, kh_path);
153 + assert_eq!(*lineno, 4);
154 + }
155 + _ => panic!("unexpected"),
156 + }
157 + assert_eq!(khs[0].patterns, "example.com,rust-lang.org");
158 + assert_eq!(khs[0].key_type, "ssh-rsa");
159 + assert_eq!(khs[0].key.len(), 407);
160 + assert_eq!(&khs[0].key[..30], b"\x00\x00\x00\x07ssh-rsa\x00\x00\x00\x03\x01\x00\x01\x00\x00\x01\x81\x00\xb935\x88\xa5\x9c)");
161 + match &khs[1].location {
162 + KnownHostLocation::File { path, lineno } => {
163 + assert_eq!(path, kh_path);
164 + assert_eq!(*lineno, 5);
165 + }
166 + _ => panic!("unexpected"),
167 + }
168 + assert_eq!(khs[2].patterns, "[example.net]:2222");
169 + assert_eq!(khs[3].patterns, "nistp256.example.org");
170 + assert_eq!(khs[7].patterns, "192.168.42.12");
171 + }
172 +
173 + #[test]
174 + fn host_matches() {
175 + let kh_path = Path::new("/home/abc/.known_hosts");
176 + let khs = load_hostfile_contents(kh_path, COMMON_CONTENTS);
177 + assert!(khs[0].host_matches("example.com"));
178 + assert!(khs[0].host_matches("rust-lang.org"));
179 + assert!(khs[0].host_matches("EXAMPLE.COM"));
180 + assert!(khs[1].host_matches("example.net"));
181 + assert!(!khs[0].host_matches("example.net"));
182 + assert!(khs[2].host_matches("[example.net]:2222"));
183 + assert!(!khs[2].host_matches("example.net"));
184 + assert!(!khs[8].host_matches("neg.example.com"));
185 + }
186 +
187 + #[test]
188 + fn check_match() {
189 + let kh_path = Path::new("/home/abc/.known_hosts");
190 + let khs = load_hostfile_contents(kh_path, COMMON_CONTENTS);
191 +
192 + assert!(check_ssh_known_hosts_loaded(
193 + &khs,
194 + "example.com",
195 + SshHostKeyType::Rsa,
196 + &khs[0].key
197 + )
198 + .is_ok());
199 +
200 + match check_ssh_known_hosts_loaded(&khs, "example.com", SshHostKeyType::Dss, &khs[0].key) {
201 + Err(KnownHostError::HostKeyNotFound {
202 + hostname,
203 + remote_fingerprint,
204 + other_hosts,
205 + ..
206 + }) => {
207 + assert_eq!(
208 + remote_fingerprint,
209 + "yn+pONDn0EcgdOCVptgB4RZd/wqmsVKrPnQMLtrvhw8"
210 + );
211 + assert_eq!(hostname, "example.com");
212 + assert_eq!(other_hosts.len(), 0);
213 + }
214 + _ => panic!("unexpected"),
215 + }
216 +
217 + match check_ssh_known_hosts_loaded(
218 + &khs,
219 + "foo.example.com",
220 + SshHostKeyType::Rsa,
221 + &khs[0].key,
222 + ) {
223 + Err(KnownHostError::HostKeyNotFound { other_hosts, .. }) => {
224 + assert_eq!(other_hosts.len(), 1);
225 + assert_eq!(other_hosts[0].patterns, "example.com,rust-lang.org");
226 + }
227 + _ => panic!("unexpected"),
228 + }
229 +
230 + let mut modified_key = khs[0].key.clone();
231 + modified_key[0] = 1;
232 + match check_ssh_known_hosts_loaded(&khs, "example.com", SshHostKeyType::Rsa, &modified_key)
233 + {
234 + Err(KnownHostError::HostKeyHasChanged { old_known_host, .. }) => {
235 + assert!(matches!(
236 + old_known_host.location,
237 + KnownHostLocation::File { lineno: 4, .. }
238 + ));
239 + }
240 + _ => panic!("unexpected"),
241 + }
242 + }
243 +}
0 commit cf716fc3c2b0785013b321f08d6cf9e277f89c84
1 Author: Eric Huss <eric@huss.org>
2 Date: Tue Dec 13 08:14:59 2022 -0800
3
4 Remove let-else, just use ? propagation.
5
6 Co-authored-by: Weihang Lo <weihanglo@users.noreply.github.com>
7
8 diff --git a/src/cargo/sources/git/known_hosts.rs b/src/cargo/sources/git/known_hosts.rs
9 index 58e64e7913..f272195306 100644
10 --- a/src/cargo/sources/git/known_hosts.rs
11 +++ b/src/cargo/sources/git/known_hosts.rs
12 @@ -478,10 +478,9 @@ fn parse_known_hosts_line(line: &str, location: KnownHostLocation) -> Option<Kno
13 return None;
14 }
15 let mut parts = line.split([' ', '\t']).filter(|s| !s.is_empty());
16 - let Some(patterns) = parts.next() else { return None };
17 - let Some(key_type) = parts.next() else { return None };
18 - let Some(key) = parts.next() else { return None };
19 - let Ok(key) = base64::decode(key) else { return None };
20 + let patterns = parts.next()?;
21 + let key_type = parts.next()?;
22 + let key = parts.next().map(base64::decode)?.ok()?;
23 Some(KnownHost {
24 location,
25 patterns: patterns.to_string(),
0 commit 018403ceaf71e205dbec64698bb864f5e094aec8
1 Author: Eric Huss <eric@huss.org>
2 Date: Wed Dec 14 19:01:40 2022 -0800
3
4 Add test for config Value in TOML array.
5
6 diff --git a/tests/testsuite/config.rs b/tests/testsuite/config.rs
7 index b1d07bb405..d1487833f7 100644
8 --- a/tests/testsuite/config.rs
9 +++ b/tests/testsuite/config.rs
10 @@ -1,7 +1,7 @@
11 //! Tests for config settings.
12
13 use cargo::core::{PackageIdSpec, Shell};
14 -use cargo::util::config::{self, Config, SslVersionConfig, StringList};
15 +use cargo::util::config::{self, Config, Definition, SslVersionConfig, StringList};
16 use cargo::util::interning::InternedString;
17 use cargo::util::toml::{self, VecStringOrBool as VSOB};
18 use cargo::CargoResult;
19 @@ -1508,3 +1508,59 @@ fn all_profile_options() {
20 let roundtrip_toml = toml_edit::easy::to_string(&roundtrip).unwrap();
21 compare::assert_match_exact(&profile_toml, &roundtrip_toml);
22 }
23 +
24 +#[cargo_test]
25 +fn value_in_array() {
26 + // Value<String> in an array should work
27 + let root_path = paths::root().join(".cargo/config.toml");
28 + write_config_at(
29 + &root_path,
30 + "\
31 +[net.ssh]
32 +known-hosts = [
33 + \"example.com ...\",
34 + \"example.net ...\",
35 +]
36 +",
37 + );
38 +
39 + let foo_path = paths::root().join("foo/.cargo/config.toml");
40 + write_config_at(
41 + &foo_path,
42 + "\
43 +[net.ssh]
44 +known-hosts = [
45 + \"example.org ...\",
46 +]
47 +",
48 + );
49 +
50 + let config = ConfigBuilder::new()
51 + .cwd("foo")
52 + // environment variables don't actually work for known-hosts due to
53 + // space splitting, but this is included here just to validate that
54 + // they work (particularly if other Vec<Value> config vars are added
55 + // in the future).
56 + .env("CARGO_NET_SSH_KNOWN_HOSTS", "env-example")
57 + .build();
58 + let net_config = config.net_config().unwrap();
59 + let kh = net_config
60 + .ssh
61 + .as_ref()
62 + .unwrap()
63 + .known_hosts
64 + .as_ref()
65 + .unwrap();
66 + assert_eq!(kh.len(), 4);
67 + assert_eq!(kh[0].val, "example.org ...");
68 + assert_eq!(kh[0].definition, Definition::Path(foo_path.clone()));
69 + assert_eq!(kh[1].val, "example.com ...");
70 + assert_eq!(kh[1].definition, Definition::Path(root_path.clone()));
71 + assert_eq!(kh[2].val, "example.net ...");
72 + assert_eq!(kh[2].definition, Definition::Path(root_path.clone()));
73 + assert_eq!(kh[3].val, "env-example");
74 + assert_eq!(
75 + kh[3].definition,
76 + Definition::Environment("CARGO_NET_SSH_KNOWN_HOSTS".to_string())
77 + );
78 +}
0 This patch is based on the upstream commit described below, adapted for use
1 in the Debian package by Peter Michael Green.
2
3 commit 67ae2dcafea5955824b1f390568a5fa109424987
4 Author: Eric Huss <eric@huss.org>
5 Date: Wed Dec 28 15:52:10 2022 -0800
6
7 ssh known_hosts: support hashed hostnames
8
9 Index: cargo/src/cargo/sources/git/known_hosts.rs
10 ===================================================================
11 --- cargo.orig/src/cargo/sources/git/known_hosts.rs
12 +++ cargo/src/cargo/sources/git/known_hosts.rs
13 @@ -16,13 +16,13 @@
14 //! - `VerifyHostKeyDNS` — Uses SSHFP DNS records to fetch a host key.
15 //!
16 //! There's also a number of things that aren't supported but could be easily
17 -//! added (it just adds a little complexity). For example, hashed hostnames,
18 -//! hostname patterns, and revoked markers. See "FIXME" comments littered in
19 -//! this file.
20 +//! added (it just adds a little complexity). For example, hostname patterns,
21 +//! and revoked markers. See "FIXME" comments littered in this file.
22
23 use crate::util::config::{Definition, Value};
24 use git2::cert::{Cert, SshHostKeyType};
25 use git2::CertificateCheckStatus;
26 +use hmac::Mac;
27 use std::collections::HashSet;
28 use std::fmt::Write;
29 use std::path::{Path, PathBuf};
30 @@ -419,6 +419,8 @@ fn user_known_host_location_to_add(diagn
31 )
32 }
33
34 +const HASH_HOSTNAME_PREFIX: &str = "|1|";
35 +
36 /// A single known host entry.
37 #[derive(Clone)]
38 struct KnownHost {
39 @@ -434,7 +436,9 @@ impl KnownHost {
40 fn host_matches(&self, host: &str) -> bool {
41 let mut match_found = false;
42 let host = host.to_lowercase();
43 - // FIXME: support hashed hostnames
44 + if let Some(hashed) = self.patterns.strip_prefix(HASH_HOSTNAME_PREFIX) {
45 + return hashed_hostname_matches(&host, hashed);
46 + }
47 for pattern in self.patterns.split(',') {
48 let pattern = pattern.to_lowercase();
49 // FIXME: support * and ? wildcards
50 @@ -450,6 +454,16 @@ impl KnownHost {
51 }
52 }
53
54 +fn hashed_hostname_matches(host: &str, hashed: &str) -> bool {
55 + let Some((b64_salt, b64_host)) = hashed.split_once('|') else { return false; };
56 + let Ok(salt) = base64::decode(b64_salt) else { return false; };
57 + let Ok(hashed_host) = base64::decode(b64_host) else { return false; };
58 + let Ok(mut mac) = hmac::Hmac::<sha1::Sha1>::new_from_slice(&salt) else { return false; };
59 + mac.update(host.as_bytes());
60 + let result = mac.finalize().into_bytes();
61 + hashed_host == &result[..]
62 +}
63 +
64 /// Loads an OpenSSH known_hosts file.
65 fn load_hostfile(path: &Path) -> Result<Vec<KnownHost>, anyhow::Error> {
66 let contents = cargo_util::paths::read(path)?;
67 @@ -474,7 +488,7 @@ fn load_hostfile_contents(path: &Path, c
68 fn parse_known_hosts_line(line: &str, location: KnownHostLocation) -> Option<KnownHost> {
69 let line = line.trim();
70 // FIXME: @revoked and @cert-authority is currently not supported.
71 - if line.is_empty() || line.starts_with(['#', '@', '|']) {
72 + if line.is_empty() || line.starts_with(['#', '@']) {
73 return None;
74 }
75 let mut parts = line.split([' ', '\t']).filter(|s| !s.is_empty());
76 @@ -506,8 +520,7 @@ mod tests {
77 @revoked * ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKtQsi+KPYispwm2rkMidQf30fG1Niy8XNkvASfePoca eric@host
78 example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAWkjI6XT2SZh3xNk5NhisA3o3sGzWR+VAKMSqHtI0aY eric@host
79 192.168.42.12 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKVYJpa0yUGaNk0NXQTPWa0tHjqRpx+7hl2diReH6DtR eric@host
80 - # Hash not yet supported.
81 - |1|7CMSYgzdwruFLRhwowMtKx0maIE=|Tlff1GFqc3Ao+fUWxMEVG8mJiyk= ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIHgN3O21U4LWtP5OzjTzPnUnSDmCNDvyvlaj6Hi65JC eric@host
82 + |1|QxzZoTXIWLhUsuHAXjuDMIV3FjQ=|M6NCOIkjiWdCWqkh5+Q+/uFLGjs= ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIHgN3O21U4LWtP5OzjTzPnUnSDmCNDvyvlaj6Hi65JC eric@host
83 # Negation isn't terribly useful without globs.
84 neg.example.com,!neg.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOXfUnaAHTlo1Qi//rNk26OcmHikmkns1Z6WW/UuuS3K eric@host
85 "#;
86 @@ -516,7 +529,7 @@ mod tests {
87 fn known_hosts_parse() {
88 let kh_path = Path::new("/home/abc/.known_hosts");
89 let khs = load_hostfile_contents(kh_path, COMMON_CONTENTS);
90 - assert_eq!(khs.len(), 9);
91 + assert_eq!(khs.len(), 10);
92 match &khs[0].location {
93 KnownHostLocation::File { path, lineno } => {
94 assert_eq!(path, kh_path);
95 @@ -551,7 +564,9 @@ mod tests {
96 assert!(!khs[0].host_matches("example.net"));
97 assert!(khs[2].host_matches("[example.net]:2222"));
98 assert!(!khs[2].host_matches("example.net"));
99 - assert!(!khs[8].host_matches("neg.example.com"));
100 + assert!(khs[8].host_matches("hashed.example.com"));
101 + assert!(!khs[8].host_matches("example.com"));
102 + assert!(!khs[9].host_matches("neg.example.com"));
103 }
104
105 #[test]
106 --- rust-cargo-0.66.0.orig/Cargo.toml
107 +++ rust-cargo-0.66.0/Cargo.toml
108 @@ -83,6 +86,9 @@ version = "0.3.0"
109 [dependencies.hex]
110 version = "0.4"
111
112 +[dependencies.hmac]
113 +version = "0.12.1"
114 +
115 [dependencies.home]
116 version = "0.5"
117
118 @@ -163,6 +169,9 @@ version = "0.1.0"
119 version = "1.0.30"
120 features = ["raw_value"]
121
122 +[dependencies.sha1]
123 +version = "0.10.5"
124 +
125 [dependencies.shell-escape]
126 version = "0.1.4"
127
0 This patch eliminates let-else usage in the code introduced
1 to fix CVE-2022-46176 as that construct is not stabalised in
2 the version of rustc currently in Debian.
3
4 It was written specifical for Debian by Peter Michael Green.
5
6 Index: cargo/src/cargo/sources/git/known_hosts.rs
7 ===================================================================
8 --- cargo.orig/src/cargo/sources/git/known_hosts.rs
9 +++ cargo/src/cargo/sources/git/known_hosts.rs
10 @@ -89,11 +89,13 @@ pub fn certificate_check(
11 config_known_hosts: Option<&Vec<Value<String>>>,
12 diagnostic_home_config: &str,
13 ) -> Result<CertificateCheckStatus, git2::Error> {
14 - let Some(host_key) = cert.as_hostkey() else {
15 + let host_key = cert.as_hostkey();
16 + if host_key.is_none() {
17 // Return passthrough for TLS X509 certificates to use whatever validation
18 // was done in git2.
19 return Ok(CertificateCheckStatus::CertificatePassthrough)
20 };
21 + let host_key = host_key.unwrap();
22 // If a nonstandard port is in use, check for that first.
23 // The fallback to check without a port is handled in the HostKeyNotFound handler.
24 let host_maybe_port = match port {
25 @@ -234,9 +236,11 @@ fn check_ssh_known_hosts(
26 host: &str,
27 config_known_hosts: Option<&Vec<Value<String>>>,
28 ) -> Result<(), KnownHostError> {
29 - let Some(remote_host_key) = cert_host_key.hostkey() else {
30 + let remote_host_key = cert_host_key.hostkey();
31 + if remote_host_key.is_none() {
32 return Err(anyhow::format_err!("remote host key is not available").into());
33 };
34 + let remote_host_key = remote_host_key.unwrap();
35 let remote_key_type = cert_host_key.hostkey_type().unwrap();
36
37 // Collect all the known host entries from disk.
38 @@ -455,10 +459,18 @@ impl KnownHost {
39 }
40
41 fn hashed_hostname_matches(host: &str, hashed: &str) -> bool {
42 - let Some((b64_salt, b64_host)) = hashed.split_once('|') else { return false; };
43 - let Ok(salt) = base64::decode(b64_salt) else { return false; };
44 - let Ok(hashed_host) = base64::decode(b64_host) else { return false; };
45 - let Ok(mut mac) = hmac::Hmac::<sha1::Sha1>::new_from_slice(&salt) else { return false; };
46 + let hostandsalt = hashed.split_once('|');
47 + if hostandsalt.is_none() { return false; };
48 + let (b64_salt, b64_host) = hostandsalt.unwrap();
49 + let salt = base64::decode(b64_salt);
50 + if salt.is_err() { return false; };
51 + let salt = salt.unwrap();
52 + let hashed_host = base64::decode(b64_host);
53 + if hashed_host.is_err() { return false; };
54 + let hashed_host = hashed_host.unwrap();
55 + let mac = hmac::Hmac::<sha1::Sha1>::new_from_slice(&salt);
56 + if mac.is_err() { return false; };
57 + let mut mac = mac.unwrap();
58 mac.update(host.as_bytes());
59 let result = mac.finalize().into_bytes();
60 hashed_host == &result[..]
00 disable-vendor.patch
1 CVE-2022-46176-01-validate-ssh-host.keys.patch
2 CVE-2022-46176-02-add-support-for-deserializing-vec-value-string.patch
3 CVE-2022-46176-03-support-configuring-ssh-known-hosts.patch
4 CVE-2022-46176-04-add-some-known-hosts-tests-and-fix-comma-bug.patch
5 CVE-2022-46176-05-remove-let-else.patch
6 CVE-2022-46176-06-add-test-for-config-value-in-toml-array.patch
7 CVE-2022-46176-07-support-hashed-hostnames.patch
8 CVE-2022-46176-08-eliminate-let-else.patch