Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions ssh-key/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ use crate::certificate;
#[cfg(feature = "ppk")]
use crate::ppk::PpkParseError;

#[cfg(doc)]
use crate::HashAlg;

/// Result type with `ssh-key`'s [`Error`] as the error type.
pub type Result<T> = core::result::Result<T, Error>;

Expand Down Expand Up @@ -59,6 +62,9 @@ pub enum Error {
/// Other format encoding errors.
FormatEncoding,

/// Provided hash is the wrong size for a given [`HashAlg`].
HashSize,

/// Input/output errors.
#[cfg(feature = "std")]
Io(std::io::ErrorKind),
Expand Down Expand Up @@ -112,6 +118,7 @@ impl fmt::Display for Error {
Error::Encoding(err) => write!(f, "{err}"),
Error::Encrypted => write!(f, "private key is encrypted"),
Error::FormatEncoding => write!(f, "format encoding error"),
Error::HashSize => write!(f, "hash is the wrong size for the given algorithm"),
#[cfg(feature = "std")]
Error::Io(err) => write!(f, "I/O error: {}", std::io::Error::from(*err)),
Error::Namespace => write!(f, "namespace invalid"),
Expand Down
15 changes: 15 additions & 0 deletions ssh-key/src/private.rs
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,21 @@ impl PrivateKey {
SshSig::sign(self, namespace, hash_alg, msg)
}

/// Sign the given message prehash 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_prehash(
&self,
namespace: &str,
hash_alg: HashAlg,
prehash: &[u8],
) -> Result<SshSig> {
SshSig::sign_prehash(self, namespace, hash_alg, prehash)
}

/// Read private key from an OpenSSH-formatted PEM source.
#[cfg(feature = "std")]
pub fn read_openssh(reader: &mut impl Read) -> Result<Self> {
Expand Down
30 changes: 25 additions & 5 deletions ssh-key/src/public.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,12 @@ impl PublicKey {
///
/// See [PROTOCOL.sshsig] for more information.
///
/// # Notes
///
/// This method loads the entire message has to be loaded into memory for verification.
/// If loading the entire message into memory is a problem consider computing a [Digest]
/// of the data first, and using [`PublicKey::verify_prehash`].
///
/// # Usage
///
/// See also: [`PrivateKey::sign`].
Expand Down Expand Up @@ -224,14 +230,28 @@ impl PublicKey {
/// # }
/// ```
///
/// The entire message has to be loaded into memory for verification. If loading the
/// entire message into memory is a problem consider computing a [Digest] via a
/// streaming API instead, and then signing/verifying a fixed length digest instead.
///
/// [PROTOCOL.sshsig]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.sshsig?annotate=HEAD
/// [Digest]: https://docs.rs/digest/latest/digest/trait.Digest.html
#[cfg(feature = "alloc")]
pub fn verify(&self, namespace: &str, msg: &[u8], signature: &SshSig) -> Result<()> {
self.verify_prehash(
namespace,
signature.hash_alg().digest(msg).as_slice(),
signature,
)
}

/// Verify the [`SshSig`] signature over the given prehashed message digest using this
/// public key.
///
/// See [`PublicKey::verify`] for more information.
#[cfg(feature = "alloc")]
pub fn verify_prehash(
&self,
namespace: &str,
prehash: &[u8],
signature: &SshSig,
) -> Result<()> {
if self.key_data() != signature.public_key() {
return Err(Error::PublicKey);
}
Expand All @@ -240,7 +260,7 @@ impl PublicKey {
return Err(Error::Namespace);
}

signature.verify(msg)
signature.verify_prehash(prehash)
}

/// Read public key from an OpenSSH-formatted source.
Expand Down
47 changes: 40 additions & 7 deletions ssh-key/src/sshsig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,21 @@ impl SshSig {
namespace: &str,
hash_alg: HashAlg,
msg: &[u8],
) -> Result<Self> {
Self::sign_prehash(
signing_key,
namespace,
hash_alg,
hash_alg.digest(msg).as_slice(),
)
}

/// Sign the given prehashed message digest with the provided signing key.
pub fn sign_prehash<S: SigningKey>(
signing_key: &S,
namespace: &str,
hash_alg: HashAlg,
prehash: &[u8],
) -> Result<Self> {
if namespace.is_empty() {
return Err(Error::Namespace);
Expand All @@ -120,13 +135,13 @@ impl SshSig {
return Err(Algorithm::SkEcdsaSha2NistP256.unsupported_error());
}

let signed_data = Self::signed_data(namespace, hash_alg, msg)?;
let signed_data = Self::signed_data_for_prehash(namespace, hash_alg, prehash)?;
let signature = signing_key.try_sign(&signed_data)?;
Self::new(signing_key.public_key(), namespace, hash_alg, signature)
}

/// Get the raw message over which the signature for a given message
/// needs to be computed.
/// Get the raw "enveloped" message over which the signature for a given input message is
/// computed.
///
/// This is a low-level function intended for uses cases which can't be
/// expressed using [`SshSig::sign`], such as if the [`SigningKey`] trait
Expand All @@ -135,6 +150,20 @@ impl SshSig {
/// Once a [`Signature`] has been computed over the returned byte vector,
/// [`SshSig::new`] can be used to construct the final signature.
pub fn signed_data(namespace: &str, hash_alg: HashAlg, msg: &[u8]) -> Result<Vec<u8>> {
Self::signed_data_for_prehash(namespace, hash_alg, hash_alg.digest(msg).as_slice())
}

/// Get the raw message over which the signature for a given message digest (passed as the
/// `prehash` parameter) is computed.
pub fn signed_data_for_prehash(
namespace: &str,
hash_alg: HashAlg,
prehash: &[u8],
) -> Result<Vec<u8>> {
if prehash.len() != hash_alg.digest_size() {
return Err(Error::HashSize);
}

if namespace.is_empty() {
return Err(Error::Namespace);
}
Expand All @@ -143,22 +172,26 @@ impl SshSig {
namespace,
reserved: &[],
hash_alg,
hash: hash_alg.digest(msg).as_slice(),
hash: prehash,
}
.to_bytes()
}

/// Verify the given message against this signature.
/// Verify the given prehashed message digest against this signature.
///
/// Note that this method does not verify the public key or namespace
/// are correct and thus is crate-private so as to ensure these parameters
/// are always authenticated by users of the public API.
pub(crate) fn verify(&self, msg: &[u8]) -> Result<()> {
pub(crate) fn verify_prehash(&self, prehash: &[u8]) -> Result<()> {
if prehash.len() != self.hash_alg.digest_size() {
return Err(Error::HashSize);
}

let signed_data = SignedData {
namespace: self.namespace.as_str(),
reserved: self.reserved.as_slice(),
hash_alg: self.hash_alg,
hash: self.hash_alg.digest(msg).as_slice(),
hash: prehash,
}
.to_bytes()?;

Expand Down