From 8c47a16b2878de0525bbe564a30779ece40694f1 Mon Sep 17 00:00:00 2001 From: Jason Bowman Date: Thu, 13 Aug 2026 18:09:31 -0700 Subject: [PATCH] feat: proxy SSH certificates via ssh-agent-lib 0.6 PublicCredential (fix #56) ssh-agent-lib 0.5.1 typed identities as ssh_key::public::KeyData, which cannot represent an OpenSSH certificate: request_identities decoded a ...-cert-v01@openssh.com blob into a lossy opaque key and re-serialized a truncated identity, so any upstream agent holding a certificate made `ssh-add -l` fail with "incomplete message" and (with #94) silently dropped every other agent's keys. Bump to ssh-agent-lib 0.6.0, whose PublicCredential (Key | Cert) round-trips certificates losslessly in both identity listings and sign requests. Key the known-keys map by credential.key_data() so a certificate, its bare public key, and the sign request ssh issues all resolve to the same upstream agent. Note: certificates with valid_before = u64::MAX ("Valid: forever") still fail to decode under the ssh-key 0.6.7 that ssh-agent-lib 0.6.0 builds on (RustCrypto/SSH#504, fixed in ssh-key 0.7). Certificates with a real expiration -- as OpenSSH and correctly configured issuers produce -- work now. --- Cargo.lock | 10 +++++----- Cargo.toml | 2 +- src/lib.rs | 16 +++++++++++++--- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 08f4f82..8ccf8df 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1296,9 +1296,9 @@ dependencies = [ [[package]] name = "secrecy" -version = "0.8.0" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" dependencies = [ "zeroize", ] @@ -1499,9 +1499,9 @@ dependencies = [ [[package]] name = "ssh-agent-lib" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faf67cca8055cc41de60ac7344a98b9c8f9e2a9ee9b1adf3e142b78ec1e7abba" +checksum = "b528981371b3bfdfe927a5ef37221446e0ba44551e673b569fd0fd38e5dcb292" dependencies = [ "async-trait", "byteorder", @@ -1513,7 +1513,7 @@ dependencies = [ "ssh-encoding", "ssh-key", "subtle", - "thiserror 1.0.69", + "thiserror 2.0.12", "tokio", "tokio-util", ] diff --git a/Cargo.toml b/Cargo.toml index d1cde64..6bcc358 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ rust-version = "1.83.0" [dependencies] clap-serde-derive = "0.2.1" flexi_logger = "0.31.7" -ssh-agent-lib = "0.5.1" +ssh-agent-lib = "0.6.0" toml = "0.9.8" [dependencies.shellexpand] diff --git a/src/lib.rs b/src/lib.rs index 0976790..cc40147 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,10 +31,16 @@ impl Session for MuxAgent { } async fn sign(&mut self, request: SignRequest) -> Result { - let fingerprint = request.pubkey.fingerprint(Default::default()); + let fingerprint = request + .credential + .key_data() + .fingerprint(Default::default()); log::trace!("incoming: sign({})", &fingerprint); - if let Some(agent_sock_path) = self.get_agent_sock_for_pubkey(&request.pubkey).await? { + if let Some(agent_sock_path) = self + .get_agent_sock_for_pubkey(request.credential.key_data()) + .await? + { log::info!( "Requesting signature with key {} from upstream agent <{}>", &fingerprint, @@ -215,7 +221,11 @@ impl MuxAgent { }; { for id in &agent_identities { - known_keys.insert(id.pubkey.clone(), sock_path.clone()); + // Key the map by the underlying public key. For an OpenSSH + // certificate this is the cert's public key, which also matches + // the plain-key sign request that ssh issues, so both resolve to + // the same upstream agent. + known_keys.insert(id.credential.key_data().clone(), sock_path.clone()); } } log::trace!(