From d2f423521ec76406944ad83098ec33afe20c692b Mon Sep 17 00:00:00 2001 From: Kim Altintop Date: Mon, 9 Jan 2023 13:18:33 +0100 Subject: This is it Squashed commit of all the exploration history. Development starts here. Signed-off-by: Kim Altintop --- src/keys.rs | 206 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 src/keys.rs (limited to 'src/keys.rs') diff --git a/src/keys.rs b/src/keys.rs new file mode 100644 index 0000000..c6be894 --- /dev/null +++ b/src/keys.rs @@ -0,0 +1,206 @@ +// 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) + } +} -- cgit v1.2.3