From ebd5355be96c82f6e836ff8494c552d6830a421b Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 12 Sep 2022 16:07:41 -0600 Subject: [PATCH] ecdsa: impl `signature::hazmat::{PrehashSigner, PrehashVerifier}` Adds "hazmat" trait impls to `SigningKey` and `VerifyingKey` respectively which allow computing signatures over raw message digests. See RustCrypto/traits#1099. The implementation allows digests which are shorter or longer than the field size of the curve, using zero-padding if the digest is too short, and truncating if it's too long. The minimum digest size is set to half of the curve's field size. --- Cargo.lock | 4 ++-- ecdsa/Cargo.toml | 2 +- ecdsa/src/hazmat.rs | 32 ++++++++++++++++++++++++++++++-- ecdsa/src/sign.rs | 20 ++++++++++++++++++++ ecdsa/src/verify.rs | 20 ++++++++++++++++++-- 5 files changed, 71 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4f6b94ea..142b7c66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -574,9 +574,9 @@ dependencies = [ [[package]] name = "signature" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0ea32af43239f0d353a7dd75a22d94c329c8cdaafdcb4c1c1335aa10c298a4a" +checksum = "e90531723b08e4d6d71b791108faf51f03e1b4a7784f96b2b87f852ebc247228" dependencies = [ "digest 0.10.3", "rand_core 0.6.3", diff --git a/ecdsa/Cargo.toml b/ecdsa/Cargo.toml index cc3d808f..f95042f5 100644 --- a/ecdsa/Cargo.toml +++ b/ecdsa/Cargo.toml @@ -17,7 +17,7 @@ rust-version = "1.57" [dependencies] elliptic-curve = { version = "0.12", default-features = false, features = ["digest", "sec1"] } -signature = { version = ">=1.5, <1.7", default-features = false, features = ["rand-preview"] } +signature = { version = ">=1.6.1, <1.7", default-features = false, features = ["hazmat-preview", "rand-preview"] } # optional dependencies der = { version = "0.6", optional = true } diff --git a/ecdsa/src/hazmat.rs b/ecdsa/src/hazmat.rs index d5de8457..542d2b49 100644 --- a/ecdsa/src/hazmat.rs +++ b/ecdsa/src/hazmat.rs @@ -25,7 +25,8 @@ use { #[cfg(feature = "digest")] use { - elliptic_curve::FieldSize, + core::cmp, + elliptic_curve::{bigint::Encoding, FieldSize}, signature::{digest::Digest, PrehashSignature}, }; @@ -216,8 +217,35 @@ where #[cfg_attr(docsrs, doc(cfg(feature = "digest")))] pub trait DigestPrimitive: PrimeCurve { /// Preferred digest to use when computing ECDSA signatures for this - /// elliptic curve. This should be a member of the SHA-2 family. + /// elliptic curve. This is typically a member of the SHA-2 family. + // TODO(tarcieri): add BlockSizeUser + FixedOutput(Reset) bounds in next breaking release + // These bounds ensure the digest algorithm can be used for HMAC-DRBG for RFC6979 type Digest: Digest; + + /// Compute field bytes for a prehash (message digest), either zero-padding + /// or truncating if the prehash size does not match the field size. + fn prehash_to_field_bytes(prehash: &[u8]) -> Result> { + // Minimum allowed prehash size is half the field size + if prehash.len() < Self::UInt::BYTE_SIZE / 2 { + return Err(Error::new()); + } + + let mut field_bytes = FieldBytes::::default(); + + match prehash.len().cmp(&Self::UInt::BYTE_SIZE) { + cmp::Ordering::Equal => field_bytes.copy_from_slice(prehash), + cmp::Ordering::Less => { + // If prehash is smaller than the field size, pad with zeroes + field_bytes[..prehash.len()].copy_from_slice(prehash); + } + cmp::Ordering::Greater => { + // If prehash is larger than the field size, truncate + field_bytes.copy_from_slice(&prehash[..Self::UInt::BYTE_SIZE]); + } + } + + Ok(field_bytes) + } } #[cfg(feature = "digest")] diff --git a/ecdsa/src/sign.rs b/ecdsa/src/sign.rs index 979fcbd9..ea89b818 100644 --- a/ecdsa/src/sign.rs +++ b/ecdsa/src/sign.rs @@ -17,6 +17,7 @@ use elliptic_curve::{ }; use signature::{ digest::{core_api::BlockSizeUser, Digest, FixedOutput, FixedOutputReset}, + hazmat::PrehashSigner, rand_core::{CryptoRng, RngCore}, DigestSigner, RandomizedDigestSigner, RandomizedSigner, Signer, }; @@ -192,6 +193,7 @@ where C::UInt: for<'a> From<&'a Scalar>, D: Digest + BlockSizeUser + FixedOutput> + FixedOutputReset, Scalar: Invert>> + Reduce + SignPrimitive, + SignatureSize: ArrayLength, { /// Sign message digest using a deterministic ephemeral scalar (`k`) @@ -203,6 +205,24 @@ where } } +impl PrehashSigner> for SigningKey +where + C: PrimeCurve + ProjectiveArithmetic + DigestPrimitive, + C::Digest: BlockSizeUser + FixedOutput> + FixedOutputReset, + C::UInt: for<'a> From<&'a Scalar>, + Scalar: Invert>> + Reduce + SignPrimitive, + SignatureSize: ArrayLength, +{ + fn sign_prehash(&self, prehash: &[u8]) -> Result> { + let prehash = C::prehash_to_field_bytes(prehash)?; + + Ok(self + .inner + .try_sign_prehashed_rfc6979::(prehash, &[])? + .0) + } +} + impl Signer> for SigningKey where Self: DigestSigner>, diff --git a/ecdsa/src/verify.rs b/ecdsa/src/verify.rs index 7c7bf637..5e8d0ab9 100644 --- a/ecdsa/src/verify.rs +++ b/ecdsa/src/verify.rs @@ -11,8 +11,11 @@ use elliptic_curve::{ sec1::{self, EncodedPoint, FromEncodedPoint, ToEncodedPoint}, AffinePoint, FieldSize, PointCompression, PrimeCurve, ProjectiveArithmetic, PublicKey, Scalar, }; -use signature::digest::{Digest, FixedOutput}; -use signature::{DigestVerifier, Verifier}; +use signature::{ + digest::{Digest, FixedOutput}, + hazmat::PrehashVerifier, + DigestVerifier, Verifier, +}; #[cfg(feature = "pkcs8")] use elliptic_curve::pkcs8::{self, AssociatedOid, DecodePublicKey}; @@ -117,6 +120,19 @@ where } } +impl PrehashVerifier> for VerifyingKey +where + C: PrimeCurve + ProjectiveArithmetic + DigestPrimitive, + AffinePoint: VerifyPrimitive, + Scalar: Reduce, + SignatureSize: ArrayLength, +{ + fn verify_prehash(&self, prehash: &[u8], signature: &Signature) -> Result<()> { + let prehash = C::prehash_to_field_bytes(prehash)?; + self.inner.as_affine().verify_prehashed(prehash, signature) + } +} + impl Verifier> for VerifyingKey where C: PrimeCurve + ProjectiveArithmetic + DigestPrimitive,