diff --git a/ssh-key/src/algorithm.rs b/ssh-key/src/algorithm.rs index 83f00f89..7a74b15e 100644 --- a/ssh-key/src/algorithm.rs +++ b/ssh-key/src/algorithm.rs @@ -6,12 +6,10 @@ mod name; use crate::{Error, Result}; use core::{fmt, str}; use encoding::{Label, LabelError}; +use sha2::{Digest, Sha256, Sha512}; #[cfg(feature = "alloc")] -use { - alloc::{borrow::ToOwned, string::String, vec::Vec}, - sha2::{Digest, Sha256, Sha512}, -}; +use alloc::{borrow::ToOwned, string::String, vec::Vec}; #[cfg(feature = "alloc")] pub use name::AlgorithmName; @@ -88,10 +86,7 @@ const SK_ECDSA_SHA2_P256: &str = "sk-ecdsa-sha2-nistp256@openssh.com"; /// U2F/FIDO security key with Ed25519 const SK_SSH_ED25519: &str = "sk-ssh-ed25519@openssh.com"; -/// SSH key algorithms. -/// -/// This type provides a registry of supported digital signature algorithms -/// used for SSH keys. +/// SSH key algorithms, i.e. digital signature algorithms used with SSH private/public keys. #[derive(Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] #[non_exhaustive] pub enum Algorithm { @@ -471,6 +466,20 @@ impl str::FromStr for HashAlg { } } +/// Associate an SSH [`HashAlg`] with the given type. +pub trait AssociatedHashAlg: Digest { + /// Algorithm identifier for this hash. + const HASH_ALG: HashAlg; +} + +impl AssociatedHashAlg for Sha256 { + const HASH_ALG: HashAlg = HashAlg::Sha256; +} + +impl AssociatedHashAlg for Sha512 { + const HASH_ALG: HashAlg = HashAlg::Sha512; +} + /// Key Derivation Function (KDF) algorithms. #[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)] #[non_exhaustive] diff --git a/ssh-key/src/lib.rs b/ssh-key/src/lib.rs index 1b8835a8..0f210848 100644 --- a/ssh-key/src/lib.rs +++ b/ssh-key/src/lib.rs @@ -168,7 +168,7 @@ mod signature; mod sshsig; pub use crate::{ - algorithm::{Algorithm, EcdsaCurve, HashAlg, KdfAlg}, + algorithm::{Algorithm, AssociatedHashAlg, EcdsaCurve, HashAlg, KdfAlg}, authorized_keys::AuthorizedKeys, error::{Error, Result}, fingerprint::Fingerprint, diff --git a/ssh-key/src/private.rs b/ssh-key/src/private.rs index 1fdc212f..674d0317 100644 --- a/ssh-key/src/private.rs +++ b/ssh-key/src/private.rs @@ -131,6 +131,7 @@ pub use crate::{ rsa::{RsaKeypair, RsaPrivateKey}, sk::SkEd25519, }, + sha2::Digest, }; #[cfg(feature = "ecdsa")] @@ -150,6 +151,7 @@ use subtle::{Choice, ConstantTimeEq}; #[cfg(feature = "alloc")] use { + crate::AssociatedHashAlg, alloc::{string::String, vec::Vec}, zeroize::Zeroizing, }; @@ -344,7 +346,21 @@ impl PrivateKey { SshSig::sign(self, namespace, hash_alg, msg) } - /// Sign the given message prehash using this private key, returning an [`SshSig`]. + /// Sign the given message [`Digest`] using this private key, returning an [`SshSig`]. + /// + /// These signatures can be produced using `ssh-keygen -Y sign`. + /// + /// For more information, see [`PrivateKey::sign`]. + #[cfg(feature = "alloc")] + pub fn sign_digest( + &self, + namespace: &str, + digest: D, + ) -> Result { + SshSig::sign_digest(self, namespace, digest) + } + + /// Sign the given raw message prehash using this private key, returning an [`SshSig`]. /// /// These signatures can be produced using `ssh-keygen -Y sign`. /// diff --git a/ssh-key/src/public.rs b/ssh-key/src/public.rs index 14c6dcec..0991f427 100644 --- a/ssh-key/src/public.rs +++ b/ssh-key/src/public.rs @@ -35,13 +35,14 @@ use encoding::{Base64Reader, Decode, Reader}; #[cfg(feature = "alloc")] use { - crate::{Comment, SshSig}, + crate::{AssociatedHashAlg, Comment, SshSig}, alloc::{ borrow::ToOwned, string::{String, ToString}, vec::Vec, }, encoding::Encode, + sha2::Digest, }; #[cfg(all(feature = "alloc", feature = "serde"))] @@ -177,8 +178,7 @@ impl PublicKey { Ok(self.key_data.encode_vec()?) } - /// Verify the [`SshSig`] signature over the given message using this - /// public key. + /// Verify the [`SshSig`] signature is valid the given message using this public key. /// /// These signatures can be produced using `ssh-keygen -Y sign`. They're /// encoded as PEM and begin with the following: @@ -241,7 +241,24 @@ impl PublicKey { ) } - /// Verify the [`SshSig`] signature over the given prehashed message digest using this + /// Verify the [`SshSig`] signature is valid the given message [`Digest`] using this public key. + /// + /// See [`PublicKey::verify`] for more information. + #[cfg(feature = "alloc")] + pub fn verify_digest( + &self, + namespace: &str, + digest: D, + signature: &SshSig, + ) -> Result<()> { + if D::HASH_ALG != signature.hash_alg() { + return Err(Error::Crypto); + } + + self.verify_prehash(namespace, digest.finalize().as_slice(), signature) + } + + /// Verify the [`SshSig`] signature matches the given prehashed message digest using this /// public key. /// /// See [`PublicKey::verify`] for more information. diff --git a/ssh-key/src/sshsig.rs b/ssh-key/src/sshsig.rs index 57f893cc..a3437586 100644 --- a/ssh-key/src/sshsig.rs +++ b/ssh-key/src/sshsig.rs @@ -1,12 +1,13 @@ //! `sshsig` implementation. -use crate::{Algorithm, Error, HashAlg, Result, Signature, SigningKey, public}; +use crate::{Algorithm, AssociatedHashAlg, Error, HashAlg, Result, Signature, SigningKey, public}; use alloc::{string::String, string::ToString, vec::Vec}; use core::str::FromStr; use encoding::{ CheckedSum, Decode, DecodePem, Encode, EncodePem, Reader, Writer, pem::{LineEnding, PemLabel}, }; +use sha2::Digest; use signature::Verifier; #[cfg(doc)] @@ -115,6 +116,20 @@ impl SshSig { ) } + /// Sign the given message digest with the provided signing key. + pub fn sign_digest( + signing_key: &S, + namespace: &str, + digest: D, + ) -> Result { + Self::sign_prehash( + signing_key, + namespace, + D::HASH_ALG, + digest.finalize().as_slice(), + ) + } + /// Sign the given prehashed message digest with the provided signing key. pub fn sign_prehash( signing_key: &S,