Skip to content

Commit 588bffa

Browse files
authored
ssh-key: add SshSig support for Digest (#385)
Similar to #384, but adds an `AssociatedHashAlg` trait, impls it for `sha2::{Sha256, Sha512}`, and adds the following methods which operate on a `Digest`, inferring the `HashAlg` to use from the type: - `PrivateKey::sign_digest` - `PublicKey::verify_digest` - `SshSig::sign_digest`
1 parent 0d5202d commit 588bffa

5 files changed

Lines changed: 72 additions & 15 deletions

File tree

ssh-key/src/algorithm.rs

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,10 @@ mod name;
66
use crate::{Error, Result};
77
use core::{fmt, str};
88
use encoding::{Label, LabelError};
9+
use sha2::{Digest, Sha256, Sha512};
910

1011
#[cfg(feature = "alloc")]
11-
use {
12-
alloc::{borrow::ToOwned, string::String, vec::Vec},
13-
sha2::{Digest, Sha256, Sha512},
14-
};
12+
use alloc::{borrow::ToOwned, string::String, vec::Vec};
1513

1614
#[cfg(feature = "alloc")]
1715
pub use name::AlgorithmName;
@@ -88,10 +86,7 @@ const SK_ECDSA_SHA2_P256: &str = "sk-ecdsa-sha2-nistp256@openssh.com";
8886
/// U2F/FIDO security key with Ed25519
8987
const SK_SSH_ED25519: &str = "sk-ssh-ed25519@openssh.com";
9088

91-
/// SSH key algorithms.
92-
///
93-
/// This type provides a registry of supported digital signature algorithms
94-
/// used for SSH keys.
89+
/// SSH key algorithms, i.e. digital signature algorithms used with SSH private/public keys.
9590
#[derive(Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)]
9691
#[non_exhaustive]
9792
pub enum Algorithm {
@@ -471,6 +466,20 @@ impl str::FromStr for HashAlg {
471466
}
472467
}
473468

469+
/// Associate an SSH [`HashAlg`] with the given type.
470+
pub trait AssociatedHashAlg: Digest {
471+
/// Algorithm identifier for this hash.
472+
const HASH_ALG: HashAlg;
473+
}
474+
475+
impl AssociatedHashAlg for Sha256 {
476+
const HASH_ALG: HashAlg = HashAlg::Sha256;
477+
}
478+
479+
impl AssociatedHashAlg for Sha512 {
480+
const HASH_ALG: HashAlg = HashAlg::Sha512;
481+
}
482+
474483
/// Key Derivation Function (KDF) algorithms.
475484
#[derive(Copy, Clone, Debug, Default, Eq, Hash, PartialEq, PartialOrd, Ord)]
476485
#[non_exhaustive]

ssh-key/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ mod signature;
168168
mod sshsig;
169169

170170
pub use crate::{
171-
algorithm::{Algorithm, EcdsaCurve, HashAlg, KdfAlg},
171+
algorithm::{Algorithm, AssociatedHashAlg, EcdsaCurve, HashAlg, KdfAlg},
172172
authorized_keys::AuthorizedKeys,
173173
error::{Error, Result},
174174
fingerprint::Fingerprint,

ssh-key/src/private.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ pub use crate::{
131131
rsa::{RsaKeypair, RsaPrivateKey},
132132
sk::SkEd25519,
133133
},
134+
sha2::Digest,
134135
};
135136

136137
#[cfg(feature = "ecdsa")]
@@ -150,6 +151,7 @@ use subtle::{Choice, ConstantTimeEq};
150151

151152
#[cfg(feature = "alloc")]
152153
use {
154+
crate::AssociatedHashAlg,
153155
alloc::{string::String, vec::Vec},
154156
zeroize::Zeroizing,
155157
};
@@ -344,7 +346,21 @@ impl PrivateKey {
344346
SshSig::sign(self, namespace, hash_alg, msg)
345347
}
346348

347-
/// Sign the given message prehash using this private key, returning an [`SshSig`].
349+
/// Sign the given message [`Digest`] using this private key, returning an [`SshSig`].
350+
///
351+
/// These signatures can be produced using `ssh-keygen -Y sign`.
352+
///
353+
/// For more information, see [`PrivateKey::sign`].
354+
#[cfg(feature = "alloc")]
355+
pub fn sign_digest<D: AssociatedHashAlg + Digest>(
356+
&self,
357+
namespace: &str,
358+
digest: D,
359+
) -> Result<SshSig> {
360+
SshSig::sign_digest(self, namespace, digest)
361+
}
362+
363+
/// Sign the given raw message prehash using this private key, returning an [`SshSig`].
348364
///
349365
/// These signatures can be produced using `ssh-keygen -Y sign`.
350366
///

ssh-key/src/public.rs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,14 @@ use encoding::{Base64Reader, Decode, Reader};
3535

3636
#[cfg(feature = "alloc")]
3737
use {
38-
crate::{Comment, SshSig},
38+
crate::{AssociatedHashAlg, Comment, SshSig},
3939
alloc::{
4040
borrow::ToOwned,
4141
string::{String, ToString},
4242
vec::Vec,
4343
},
4444
encoding::Encode,
45+
sha2::Digest,
4546
};
4647

4748
#[cfg(all(feature = "alloc", feature = "serde"))]
@@ -177,8 +178,7 @@ impl PublicKey {
177178
Ok(self.key_data.encode_vec()?)
178179
}
179180

180-
/// Verify the [`SshSig`] signature over the given message using this
181-
/// public key.
181+
/// Verify the [`SshSig`] signature is valid the given message using this public key.
182182
///
183183
/// These signatures can be produced using `ssh-keygen -Y sign`. They're
184184
/// encoded as PEM and begin with the following:
@@ -241,7 +241,24 @@ impl PublicKey {
241241
)
242242
}
243243

244-
/// Verify the [`SshSig`] signature over the given prehashed message digest using this
244+
/// Verify the [`SshSig`] signature is valid the given message [`Digest`] using this public key.
245+
///
246+
/// See [`PublicKey::verify`] for more information.
247+
#[cfg(feature = "alloc")]
248+
pub fn verify_digest<D: AssociatedHashAlg + Digest>(
249+
&self,
250+
namespace: &str,
251+
digest: D,
252+
signature: &SshSig,
253+
) -> Result<()> {
254+
if D::HASH_ALG != signature.hash_alg() {
255+
return Err(Error::Crypto);
256+
}
257+
258+
self.verify_prehash(namespace, digest.finalize().as_slice(), signature)
259+
}
260+
261+
/// Verify the [`SshSig`] signature matches the given prehashed message digest using this
245262
/// public key.
246263
///
247264
/// See [`PublicKey::verify`] for more information.

ssh-key/src/sshsig.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
//! `sshsig` implementation.
22
3-
use crate::{Algorithm, Error, HashAlg, Result, Signature, SigningKey, public};
3+
use crate::{Algorithm, AssociatedHashAlg, Error, HashAlg, Result, Signature, SigningKey, public};
44
use alloc::{string::String, string::ToString, vec::Vec};
55
use core::str::FromStr;
66
use encoding::{
77
CheckedSum, Decode, DecodePem, Encode, EncodePem, Reader, Writer,
88
pem::{LineEnding, PemLabel},
99
};
10+
use sha2::Digest;
1011
use signature::Verifier;
1112

1213
#[cfg(doc)]
@@ -115,6 +116,20 @@ impl SshSig {
115116
)
116117
}
117118

119+
/// Sign the given message digest with the provided signing key.
120+
pub fn sign_digest<S: SigningKey, D: AssociatedHashAlg + Digest>(
121+
signing_key: &S,
122+
namespace: &str,
123+
digest: D,
124+
) -> Result<Self> {
125+
Self::sign_prehash(
126+
signing_key,
127+
namespace,
128+
D::HASH_ALG,
129+
digest.finalize().as_slice(),
130+
)
131+
}
132+
118133
/// Sign the given prehashed message digest with the provided signing key.
119134
pub fn sign_prehash<S: SigningKey>(
120135
signing_key: &S,

0 commit comments

Comments
 (0)