Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 71 additions & 10 deletions russh/src/client/encrypted.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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("[email protected]") {
// 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, .. } => {
Expand All @@ -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 } => {
Expand Down Expand Up @@ -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)
Expand Down
68 changes: 66 additions & 2 deletions russh/src/keys/agent/client.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use core::str;
use std::sync::{Mutex, OnceLock};
use std::collections::HashMap;

use byteorder::{BigEndian, ByteOrder};
use bytes::Bytes;
Expand All @@ -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<Mutex<HashMap<String, Vec<u8>>>> = OnceLock::new();

fn get_cert_blobs_map() -> &'static Mutex<HashMap<String, Vec<u8>>> {
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<u8>) {
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<Vec<u8>> {
if let Ok(map) = get_cert_blobs_map().lock() {
map.get(fingerprint).cloned()
} else {
None
}
}

pub trait AgentStream: AsyncRead + AsyncWrite {}

impl<S: AsyncRead + AsyncWrite> AgentStream for S {}
Expand Down Expand Up @@ -273,8 +299,30 @@ impl<S: AgentStream + Unpin> AgentClient<S> {
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("[email protected]")
} 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);
}
}
Expand Down Expand Up @@ -316,9 +364,25 @@ impl<S: AgentStream + Unpin> AgentClient<S> {
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 {
Expand Down
Loading