diff --git a/russh/src/client/encrypted.rs b/russh/src/client/encrypted.rs index 1a643809..e36ab02e 100644 --- a/russh/src/client/encrypted.rs +++ b/russh/src/client/encrypted.rs @@ -909,9 +909,30 @@ impl Encrypted { "publickey".encode(&mut self.write)?; self.write.push(0); // This is a probe - debug!("write_auth_request: key - {:?}", key.algorithm()); - key.algorithm().as_str().encode(&mut self.write)?; - key.public_key().to_bytes()?.encode(&mut self.write)?; + let algo = key.algorithm(); + debug!("write_auth_request: key - {:?}", algo); + + // Check if this is an opaque certificate from the agent + let algo_str = algo.as_str(); + if algo_str.contains("-cert-v01@openssh.com") { + // For certificates, try to retrieve the stored blob + let fingerprint = format!("{}", key.public_key().fingerprint(ssh_key::HashAlg::Sha256)); + if let Some(cert_blob) = crate::keys::agent::client::get_cert_blob(&fingerprint) { + // Send the full certificate blob from storage + algo.to_certificate_type().encode(&mut self.write)?; + cert_blob.as_slice().encode(&mut self.write)?; + debug!("write_auth_request: sending stored certificate blob ({} bytes)", cert_blob.len()); + } else { + // Fallback: try using key_data (will be incomplete but better than nothing) + algo.to_certificate_type().encode(&mut self.write)?; + key.public_key().key_data().encoded()?.encode(&mut self.write)?; + debug!("write_auth_request: certificate blob not found, sending key_data"); + } + } else { + // For regular keys, use standard encoding + algo_str.encode(&mut self.write)?; + key.public_key().to_bytes()?.encode(&mut self.write)?; + } true } auth::Method::OpenSshCertificate { ref cert, .. } => { @@ -933,12 +954,33 @@ impl Encrypted { "publickey".encode(&mut self.write)?; self.write.push(0); // This is a probe - key.algorithm() - .with_hash_alg(hash_alg) - .as_str() - .encode(&mut self.write)?; + // Check if this is a certificate by examining the algorithm name + let algo = key.algorithm(); + let algo_str = algo.as_str(); + + if algo_str.contains("-cert-") { + // For certificates, retrieve the stored blob + let fingerprint = format!("{}", key.fingerprint(ssh_key::HashAlg::Sha256)); + + if let Some(cert_blob) = crate::keys::agent::client::get_cert_blob(&fingerprint) { + // Use algorithm as-is (already a certificate type) + // Do NOT call to_certificate_type() - it would double-append the cert suffix + algo_str.encode(&mut self.write)?; + cert_blob.as_slice().encode(&mut self.write)?; + } else { + // This shouldn't happen - certificate blob should have been stored + debug!("Certificate blob not found for {}, auth may fail", fingerprint); + algo.to_certificate_type().encode(&mut self.write)?; + key.to_bytes()?.as_slice().encode(&mut self.write)?; + } + } else { + // For regular keys, use with_hash_alg + algo.with_hash_alg(hash_alg) + .as_str() + .encode(&mut self.write)?; + key.to_bytes()?.as_slice().encode(&mut self.write)?; + } - key.to_bytes()?.as_slice().encode(&mut self.write)?; true } auth::Method::KeyboardInteractive { ref submethods } => { @@ -976,8 +1018,27 @@ impl Encrypted { cert.to_bytes()?.encode(buffer)?; } PublicKeyOrCertificate::PublicKey { key, hash_alg } => { - key.algorithm().with_hash_alg(*hash_alg).encode(buffer)?; - key.to_bytes()?.encode(buffer)?; + // Check if this is a certificate by examining the algorithm name + let algo = key.algorithm(); + let algo_str = algo.as_str(); + if algo_str.contains("-cert-") { + // For certificates, retrieve the stored blob + let fingerprint = format!("{}", key.fingerprint(ssh_key::HashAlg::Sha256)); + if let Some(cert_blob) = crate::keys::agent::client::get_cert_blob(&fingerprint) { + // Use algorithm as-is (already a certificate type) + algo_str.encode(buffer)?; + cert_blob.as_slice().encode(buffer)?; + } else { + // This shouldn't happen - certificate blob should have been stored + debug!("Certificate blob not found for {}, signature may fail", fingerprint); + algo.to_certificate_type().encode(buffer)?; + key.key_data().encoded()?.encode(buffer)?; + } + } else { + // For regular keys, use with_hash_alg + algo.with_hash_alg(*hash_alg).encode(buffer)?; + key.to_bytes()?.encode(buffer)?; + } } } Ok(i0) diff --git a/russh/src/keys/agent/client.rs b/russh/src/keys/agent/client.rs index 3026075a..ef73d082 100644 --- a/russh/src/keys/agent/client.rs +++ b/russh/src/keys/agent/client.rs @@ -1,4 +1,6 @@ use core::str; +use std::sync::{Mutex, OnceLock}; +use std::collections::HashMap; use byteorder::{BigEndian, ByteOrder}; use bytes::Bytes; @@ -13,6 +15,30 @@ use crate::helpers::EncodedExt; use crate::keys::{key, Error}; use crate::CryptoVec; +// Global storage for certificate blobs, keyed by fingerprint +// This is a workaround for the ssh-key crate not preserving full certificate blobs +static CERT_BLOBS: OnceLock>>> = OnceLock::new(); + +fn get_cert_blobs_map() -> &'static Mutex>> { + CERT_BLOBS.get_or_init(|| Mutex::new(HashMap::new())) +} + +/// Store a certificate blob for later retrieval during authentication +pub fn store_cert_blob(fingerprint: String, blob: Vec) { + if let Ok(mut map) = get_cert_blobs_map().lock() { + map.insert(fingerprint, blob); + } +} + +/// Retrieve a stored certificate blob by fingerprint +pub fn get_cert_blob(fingerprint: &str) -> Option> { + if let Ok(map) = get_cert_blobs_map().lock() { + map.get(fingerprint).cloned() + } else { + None + } +} + pub trait AgentStream: AsyncRead + AsyncWrite {} impl AgentStream for S {} @@ -273,8 +299,30 @@ impl AgentClient { for _ in 0..n { let key_blob = Bytes::decode(&mut r)?; let comment = String::decode(&mut r)?; + + // Check if this is a certificate by peeking at the algorithm name + let mut peek_reader = &key_blob[..]; + let is_cert = if let Ok(algo_name) = String::decode(&mut peek_reader) { + algo_name.contains("-cert-v01@openssh.com") + } else { + false + }; + + // Parse the key let mut key = key::parse_public_key(&key_blob)?; key.set_comment(comment); + + // If it's a certificate, store the original blob for later use + if is_cert { + let fingerprint = format!("{}", key.fingerprint(HashAlg::Sha256)); + debug!( + "Certificate detected: {} ({} bytes), storing for authentication", + key.algorithm(), + key_blob.len() + ); + store_cert_blob(fingerprint, key_blob.to_vec()); + } + keys.push(key); } } @@ -316,9 +364,25 @@ impl AgentClient { self.buf.clear(); self.buf.resize(4); msg::SIGN_REQUEST.encode(&mut self.buf)?; - public.key_data().encoded()?.encode(&mut self.buf)?; + + // For certificates, send the full certificate blob + let algo = public.algorithm(); + let algo_str = algo.as_str(); + if algo_str.contains("-cert-") { + let fingerprint = format!("{}", public.fingerprint(HashAlg::Sha256)); + if let Some(cert_blob) = get_cert_blob(&fingerprint) { + // Send full certificate blob to agent for signature + cert_blob.as_slice().encode(&mut self.buf)?; + } else { + // This shouldn't happen - certificate blob should have been stored + debug!("Certificate blob not found for {}, signature may fail", fingerprint); + public.key_data().encoded()?.encode(&mut self.buf)?; + } + } else { + public.key_data().encoded()?.encode(&mut self.buf)?; + } + data.encode(&mut self.buf)?; - debug!("public = {:?}", public); let hash = match public.algorithm() { Algorithm::Rsa { .. } => match hash_alg {