use std::sync::{Arc, Mutex};
use crate::error::Result;
use crate::hostkey::HostKey;
use super::client::Agent;
use super::protocol::{SSH_AGENT_RSA_SHA2_256, SSH_AGENT_RSA_SHA2_512};
pub struct AgentHostKey {
agent: Arc<Mutex<Agent>>,
key_blob: Vec<u8>,
algorithm: &'static str,
flags: u32,
}
impl AgentHostKey {
pub fn from_identity(agent: Arc<Mutex<Agent>>, key_blob: Vec<u8>) -> Result<Self> {
let algo = first_string(&key_blob).ok_or(crate::error::Error::Format(
"agent identity blob: missing algorithm prefix",
))?;
let (algorithm, flags): (&'static str, u32) = match algo.as_str() {
"ssh-ed25519" => ("ssh-ed25519", 0),
"ecdsa-sha2-nistp256" => ("ecdsa-sha2-nistp256", 0),
"ecdsa-sha2-nistp384" => ("ecdsa-sha2-nistp384", 0),
"ecdsa-sha2-nistp521" => ("ecdsa-sha2-nistp521", 0),
"ssh-mldsa44-ed25519@openssh.com" => ("ssh-mldsa44-ed25519@openssh.com", 0),
"ssh-rsa" => ("rsa-sha2-256", SSH_AGENT_RSA_SHA2_256),
"rsa-sha2-256" => ("rsa-sha2-256", SSH_AGENT_RSA_SHA2_256),
"rsa-sha2-512" => ("rsa-sha2-512", SSH_AGENT_RSA_SHA2_512),
"ssh-rsa-cert-v01@openssh.com" => {
("rsa-sha2-256-cert-v01@openssh.com", SSH_AGENT_RSA_SHA2_256)
}
other if crate::cert::is_cert_name(other) => {
match crate::cert::cert_name_to_plain(other)
.and_then(crate::cert::cert_name_for_key_type)
{
Some(name) => (name, 0),
None => {
return Err(crate::error::Error::Format(
"agent identity blob: unsupported certificate",
));
}
}
}
other => {
return Err(crate::error::Error::Format({
let _ = other;
"agent identity blob: unsupported algorithm"
}));
}
};
Ok(Self {
agent,
key_blob,
algorithm,
flags,
})
}
pub fn with_rsa_hash(mut self, hash: RsaHash) -> Self {
match self.algorithm {
"rsa-sha2-256" | "rsa-sha2-512" => {
let (algo, flags) = match hash {
RsaHash::Sha256 => ("rsa-sha2-256", SSH_AGENT_RSA_SHA2_256),
RsaHash::Sha512 => ("rsa-sha2-512", SSH_AGENT_RSA_SHA2_512),
};
self.algorithm = algo;
self.flags = flags;
}
_ => {}
}
self
}
}
#[derive(Debug, Clone, Copy)]
pub enum RsaHash {
Sha256,
Sha512,
}
impl HostKey for AgentHostKey {
fn algorithm(&self) -> &'static str {
self.algorithm
}
fn public_blob(&self) -> Vec<u8> {
self.key_blob.clone()
}
fn sign(&self, msg: &[u8]) -> Result<Vec<u8>> {
let sig_blob = {
let mut agent = self
.agent
.lock()
.map_err(|_| crate::error::Error::Protocol("agent mutex poisoned"))?;
agent.sign(&self.key_blob, msg, self.flags)?
};
let expected = crate::cert::cert_name_to_plain(self.algorithm).unwrap_or(self.algorithm);
if let Some(algo) = first_string(&sig_blob)
&& algo != expected
{
return Err(crate::error::Error::Protocol(
"agent: signature algorithm mismatch",
));
}
Ok(sig_blob)
}
fn upgraded_for(&self, server_sig_algs: &str) -> Option<Box<dyn HostKey>> {
let upgraded = match self.algorithm {
"rsa-sha2-256" => "rsa-sha2-512",
"rsa-sha2-256-cert-v01@openssh.com" => "rsa-sha2-512-cert-v01@openssh.com",
_ => return None,
};
let advertises_512 = server_sig_algs
.split(',')
.any(|a| a.trim() == "rsa-sha2-512");
if !advertises_512 {
return None;
}
Some(Box::new(AgentHostKey {
agent: Arc::clone(&self.agent),
key_blob: self.key_blob.clone(),
algorithm: upgraded,
flags: SSH_AGENT_RSA_SHA2_512,
}))
}
}
fn first_string(buf: &[u8]) -> Option<String> {
crate::format::Reader::new(buf)
.read_string()
.ok()
.map(|s| String::from_utf8_lossy(s).into_owned())
}
#[cfg(test)]
mod tests {
use super::*;
fn make_blob(algo: &str) -> Vec<u8> {
let mut v = Vec::new();
v.extend_from_slice(&(algo.len() as u32).to_be_bytes());
v.extend_from_slice(algo.as_bytes());
v.extend_from_slice(b"key-body-placeholder");
v
}
#[test]
fn first_string_handles_short_blob() {
assert_eq!(first_string(b""), None);
assert_eq!(first_string(b"\x00\x00\x00\x05ab"), None); assert_eq!(
first_string(b"\x00\x00\x00\x03foo+padding").as_deref(),
Some("foo"),
);
}
#[test]
fn from_identity_picks_correct_algorithm() {
let blob = make_blob("ssh-ed25519");
assert_eq!(first_string(&blob).as_deref(), Some("ssh-ed25519"));
let blob = make_blob("rsa-sha2-512");
assert_eq!(first_string(&blob).as_deref(), Some("rsa-sha2-512"));
}
}