// Copyright © 2022 Kim Altintop // SPDX-License-Identifier: GPL-2.0-only WITH openvpn-openssl-exception use core::fmt; use std::{ borrow::Cow, io, ops::{ Deref, DerefMut, }, str::FromStr, }; use anyhow::anyhow; use signature::SignerMut; use crate::{ cfg, metadata, ssh::{ self, agent, }, }; pub type Signature = ssh::Signature; pub trait Signer { fn ident(&self) -> VerificationKey; fn sign(&mut self, msg: &[u8]) -> Result; } impl Signer for Box where T: Signer + ?Sized, { fn ident(&self) -> VerificationKey { self.deref().ident() } fn sign(&mut self, msg: &[u8]) -> Result { self.deref_mut().sign(msg) } } impl Signer for ssh::PrivateKey { fn ident(&self) -> VerificationKey { self.public_key().into() } fn sign(&mut self, msg: &[u8]) -> Result { self.try_sign(msg) } } pub struct Agent { client: agent::Client, ident: ssh::PublicKey, } impl Agent { pub fn from_gitconfig(cfg: &git2::Config) -> crate::Result { let client = agent::Client::from_env()?; let ident = VerificationKey::from_gitconfig(cfg)?.0.into_owned(); Ok(Self { client, ident }) } pub fn boxed(self) -> Box { Box::new(self) } pub fn as_dyn(&mut self) -> &mut dyn Signer { self } } impl Agent { pub fn new(client: agent::Client, key: VerificationKey<'_>) -> Self { let ident = key.0.into_owned(); Self { client, ident } } pub fn verification_key(&self) -> VerificationKey { VerificationKey::from(&self.ident) } } impl Signer for Agent where T: io::Read + io::Write, { fn ident(&self) -> VerificationKey { self.verification_key() } fn sign(&mut self, msg: &[u8]) -> Result { self.client .sign(&self.ident, msg) .map_err(signature::Error::from_source) } } impl Signer for &mut Agent where T: io::Read + io::Write, { fn ident(&self) -> VerificationKey { self.verification_key() } fn sign(&mut self, msg: &[u8]) -> Result { self.client .sign(&self.ident, msg) .map_err(signature::Error::from_source) } } #[derive(Clone)] pub struct VerificationKey<'a>(Cow<'a, ssh::PublicKey>); impl<'a> VerificationKey<'a> { pub fn from_openssh(key: &str) -> Result { ssh::PublicKey::from_openssh(key).map(Cow::Owned).map(Self) } pub fn to_openssh(&self) -> Result { self.0.to_openssh() } pub fn from_gitconfig(cfg: &git2::Config) -> crate::Result { let key = cfg::git::signing_key(cfg)? .ok_or_else(|| anyhow!("unable to determine signing key from git config"))? .public() .to_owned(); Ok(Self(Cow::Owned(key))) } pub fn algorithm(&self) -> ssh::Algorithm { self.0.algorithm() } pub fn strip_comment(&mut self) { self.0.to_mut().set_comment("") } pub fn without_comment(mut self) -> Self { self.strip_comment(); self } pub fn sha256(&self) -> [u8; 32] { self.0.fingerprint(ssh::HashAlg::Sha256).sha256().unwrap() } pub fn to_owned<'b>(&self) -> VerificationKey<'b> { VerificationKey(Cow::Owned(self.0.clone().into_owned())) } pub fn keyid(&self) -> metadata::KeyId { metadata::KeyId::from(self) } pub(crate) fn key_data(&self) -> ssh::public::KeyData { self.as_ref().into() } } impl AsRef for VerificationKey<'_> { fn as_ref(&self) -> &ssh::PublicKey { &self.0 } } impl fmt::Display for VerificationKey<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(&self.0.to_string()) } } impl From for VerificationKey<'_> { fn from(key: ssh::PublicKey) -> Self { Self(Cow::Owned(key)) } } impl<'a> From<&'a ssh::PublicKey> for VerificationKey<'a> { fn from(key: &'a ssh::PublicKey) -> Self { Self(Cow::Borrowed(key)) } } impl FromStr for VerificationKey<'_> { type Err = ssh::Error; fn from_str(s: &str) -> Result { Self::from_openssh(s) } } impl signature::Verifier for VerificationKey<'_> { fn verify(&self, msg: &[u8], signature: &ssh::Signature) -> Result<(), signature::Error> { signature::Verifier::verify(&*self.0, msg, signature) } }