use alloc::boxed::Box;
use alloc::vec::Vec;
use super::HostKey;
use crate::cert::Certificate;
use crate::error::{Error, Result};
pub struct CertHostKey {
inner: Box<dyn HostKey + Send + Sync>,
cert_blob: Vec<u8>,
cert_algo: &'static str,
}
impl CertHostKey {
pub fn new(
inner: Box<dyn HostKey + Send + Sync>,
cert: &Certificate,
cert_algo: &'static str,
) -> Result<Self> {
if inner.public_blob() != cert.embedded_pubkey_blob {
return Err(Error::Config(
"certificate embedded key does not match the host/identity key",
));
}
Ok(CertHostKey {
inner,
cert_blob: cert.raw.clone(),
cert_algo,
})
}
pub fn inner(&self) -> &(dyn HostKey + Send + Sync) {
self.inner.as_ref()
}
}
impl HostKey for CertHostKey {
fn algorithm(&self) -> &'static str {
self.cert_algo
}
fn public_blob(&self) -> Vec<u8> {
self.cert_blob.clone()
}
fn sign(&self, msg: &[u8]) -> Result<Vec<u8>> {
self.inner.sign(msg)
}
fn upgraded_for(&self, allowed: &str) -> Option<Box<dyn HostKey>> {
const RSA: [(&str, &str); 3] = [
("rsa-sha2-512-cert-v01@openssh.com", "rsa-sha2-512"),
("rsa-sha2-256-cert-v01@openssh.com", "rsa-sha2-256"),
("ssh-rsa-cert-v01@openssh.com", "ssh-rsa"),
];
if !RSA.iter().any(|(c, _)| *c == self.cert_algo) {
return None;
}
let listed = |n: &str| allowed.split(',').any(|a| a.trim() == n);
let mut chosen = None;
for &(c, s) in RSA.iter() {
if listed(c) || listed(s) {
chosen = Some((c, s));
break;
}
}
let (cert_algo, sig) = chosen?;
let inner = self.inner.upgraded_for(sig)?;
if inner.algorithm() != sig {
return None;
}
Some(Box::new(UpgradedCert {
inner,
cert_blob: self.cert_blob.clone(),
cert_algo,
}))
}
}
struct UpgradedCert {
inner: Box<dyn HostKey>,
cert_blob: Vec<u8>,
cert_algo: &'static str,
}
impl HostKey for UpgradedCert {
fn algorithm(&self) -> &'static str {
self.cert_algo
}
fn public_blob(&self) -> Vec<u8> {
self.cert_blob.clone()
}
fn sign(&self, msg: &[u8]) -> Result<Vec<u8>> {
self.inner.sign(msg)
}
}
#[cfg(all(test, feature = "alloc"))]
mod tests {
use super::*;
use crate::hostkey::Ed25519HostKey;
fn load(name: &str) -> Vec<u8> {
let path = format!(
"{}/tests/fixtures/cert/{}",
env!("CARGO_MANIFEST_DIR"),
name
);
let text = std::fs::read_to_string(path).unwrap();
let b64 = text.split_whitespace().nth(1).unwrap();
crate::key::base64::decode(b64.as_bytes()).unwrap()
}
#[test]
fn mismatched_key_rejected() {
let cert = Certificate::parse(&load("h_ed25519-cert.pub")).unwrap();
let wrong = Box::new(Ed25519HostKey::from_seed([7u8; 32]));
assert!(CertHostKey::new(wrong, &cert, "ssh-ed25519-cert-v01@openssh.com").is_err());
}
#[test]
fn matched_key_presents_cert_blob() {
let cert = Certificate::parse(&load("h_ed25519-cert.pub")).unwrap();
let direct = Box::new(Ed25519HostKey::from_seed(seed_of("h_ed25519")));
let ch = CertHostKey::new(direct, &cert, "ssh-ed25519-cert-v01@openssh.com").unwrap();
assert_eq!(ch.algorithm(), "ssh-ed25519-cert-v01@openssh.com");
assert_eq!(ch.public_blob(), cert.raw);
let sig = ch.sign(b"H").unwrap();
let verifier = cert.embedded_verifier(&sig).unwrap();
verifier.verify(b"H", &sig).unwrap();
}
fn seed_of(name: &str) -> [u8; 32] {
let pem = std::fs::read_to_string(format!(
"{}/tests/fixtures/cert/{}",
env!("CARGO_MANIFEST_DIR"),
name
))
.unwrap();
let sk = crate::key::PrivateKey::parse_openssh_pem(&pem, None).unwrap();
match sk {
crate::key::PrivateKey::Ed25519 { seed, .. } => seed,
_ => panic!("not ed25519"),
}
}
}