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
6 changes: 4 additions & 2 deletions ecdsa/src/der.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ where
<FieldBytesSize<C> as Add>::Output: Add<MaxOverhead> + ArraySize,
{
/// Parse signature from DER-encoded bytes.
///
/// # Errors
/// Returns [`Error`] if `input` failed to parse as an ASN.1 DER-encoded ECDSA signature.
pub fn from_bytes(input: &[u8]) -> Result<Self> {
let SignatureRef { r, s } = SignatureRef::from_der(input).map_err(|_| Error::new())?;

Expand All @@ -110,8 +113,7 @@ where
})
}

/// Create an ASN.1 DER encoded signature from big endian `r` and `s` scalar
/// components.
/// Create an ASN.1 DER encoded signature from big endian `r` and `s` scalar components.
pub(crate) fn from_components(r: &[u8], s: &[u8]) -> der::Result<Self> {
let sig = SignatureRef {
r: UintRef::new(r)?,
Expand Down
19 changes: 11 additions & 8 deletions ecdsa/src/hazmat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ use digest::{Digest, block_api::BlockSizeUser};
///
/// - `d`: signing key. MUST BE UNIFORMLY RANDOM!!!
/// - `k`: ephemeral scalar value. MUST BE UNIFORMLY RANDOM!!!
/// - `z`: message digest to be signed. MUST BE OUTPUT OF A CRYPTOGRAPHICALLY
/// SECURE DIGEST ALGORITHM!!!
/// - `z`: message digest to sign. MUST BE OUTPUT OF A CRYPTOGRAPHICALLY SECURE DIGEST ALGORITHM!
///
/// # Low-S Normalization
///
Expand All @@ -58,6 +57,7 @@ pub fn sign_prehashed<C>(
where
C: EcdsaCurve + CurveArithmetic,
{
// Reduce message hash into an element of the scalar field for `C`.
let z = bytes2scalar::<C>(z);

// Compute scalar inversion of 𝑘.
Expand Down Expand Up @@ -128,19 +128,22 @@ where
/// Accepts the following arguments:
///
/// - `q`: public key with which to verify the signature.
/// - `z`: message digest to be verified. MUST BE OUTPUT OF A CRYPTOGRAPHICALLY SECURE DIGEST
/// ALGORITHM!!!
/// - `sig`: signature to be verified against the key and message.
pub fn verify_prehashed<C>(q: &ProjectivePoint<C>, z: &[u8], sig: &Signature<C>) -> Result<()>
/// - `z`: message digest to verify. MUST BE OUTPUT OF A CRYPTOGRAPHICALLY SECURE DIGEST ALGORITHM!
/// - `signature`: purported signature to verify against the key and message.
///
/// # Errors
/// Returns [`Error`] if the signature failed to verify.
pub fn verify_prehashed<C>(q: &ProjectivePoint<C>, z: &[u8], signature: &Signature<C>) -> Result<()>
where
C: EcdsaCurve + CurveArithmetic,
{
if C::NORMALIZE_S && sig.s().is_high().into() {
let (r, s) = signature.split_scalars();

if C::NORMALIZE_S && s.is_high().into() {
return Err(Error::new());
}

let z = bytes2scalar::<C>(z);
let (r, s) = sig.split_scalars();
let s_inv = *s.invert_vartime();
let u1 = z * s_inv;
let u2 = *r * s_inv;
Expand Down
51 changes: 37 additions & 14 deletions ecdsa/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg",
html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg"
)]
#![allow(clippy::missing_errors_doc, reason = "TODO")]

//! ## `serde` support
//!
Expand Down Expand Up @@ -192,8 +191,8 @@ impl<C> Signature<C>
where
C: EcdsaCurve,
{
/// Parse a signature from fixed-width bytes, i.e. 2 * the size of
/// [`FieldBytes`] for a particular curve.
/// Parse a signature from fixed-width bytes, i.e. 2 * the size of [`FieldBytes`] for a
/// particular curve.
///
/// # Errors
/// If the `r` and/or `s` component of the signature is out-of-range when interpreted as a big
Expand All @@ -206,13 +205,20 @@ where
}

/// Parse a signature from a byte slice.
///
/// # Errors
/// Returns [`Error`] in the event the signature is not the expected size, i.e. 2 * the size of
/// [`FieldBytes`] for a particular curve.
pub fn from_slice(slice: &[u8]) -> Result<Self> {
<&SignatureBytes<C>>::try_from(slice)
.map_err(|_| Error::new())
.and_then(Self::from_bytes)
}

/// Parse a signature from ASN.1 DER.
///
/// # Errors
/// Returns [`Error`] if `input` failed to parse as an ASN.1 DER-encoded ECDSA signature.
#[cfg(feature = "der")]
pub fn from_der(bytes: &[u8]) -> Result<Self>
where
Expand All @@ -226,7 +232,8 @@ where
/// which comprise the signature.
///
/// # Errors
/// If the `r` and/or `s` component of the signature is out-of-range when interpreted as a big endian integer.
/// If the `r` and/or `s` component of the signature is out-of-range when interpreted as a big
/// endian integer.
pub fn from_scalars(r: impl Into<FieldBytes<C>>, s: impl Into<FieldBytes<C>>) -> Result<Self> {
let r = ScalarValue::from_slice(&r.into()).map_err(|_| Error::new())?;
let s = ScalarValue::from_slice(&s.into()).map_err(|_| Error::new())?;
Expand Down Expand Up @@ -515,26 +522,26 @@ where
/// OID must begin with `1.2.840.10045.4`, the [RFC5758] OID prefix for ECDSA variants.
///
/// [RFC5758]: https://www.rfc-editor.org/rfc/rfc5758#section-3.2
///
/// # Errors
/// Returns [`Error`] if `oid` does not start with `1.2.840.10045.4`.
pub fn new(signature: Signature<C>, oid: ObjectIdentifier) -> Result<Self> {
// TODO(tarcieri): use `ObjectIdentifier::starts_with`
for (arc1, arc2) in ObjectIdentifier::new_unwrap("1.2.840.10045.4.3")
.arcs()
.zip(oid.arcs())
{
if arc1 != arc2 {
return Err(Error::new());
}
if !oid.starts_with(ObjectIdentifier::new_unwrap("1.2.840.10045.4")) {
return Err(Error::new());
}

Ok(Self { signature, oid })
}

/// Create a new signature, determining the OID from the given digest.
///
/// Supports SHA-2 family digests as enumerated in [RFC5758 § 3.2], i.e.
/// SHA-224, SHA-256, SHA-384, or SHA-512.
/// Supports SHA-2 family digests as enumerated in [RFC5758 § 3.2], i.e. SHA-224, SHA-256,
/// SHA-384, or SHA-512.
///
/// [RFC5758 § 3.2]: https://www.rfc-editor.org/rfc/rfc5758#section-3.2
///
/// # Errors
/// Returns [`Error`] if the [`AssociatedOid`] for `D` is not one from the SHA2 family.
pub fn new_with_digest<D>(signature: Signature<C>) -> Result<Self>
where
D: AssociatedOid + Digest,
Expand All @@ -544,6 +551,10 @@ where
}

/// Parse a signature from fixed-with bytes.
///
/// # Errors
/// Returns [`Error`] if [`Signature`] fails to parse, or if `D` is not a valid digest.
/// See [`SignatureWithOid::new_with_digest`] documentation.
pub fn from_bytes_with_digest<D>(bytes: &SignatureBytes<C>) -> Result<Self>
where
D: AssociatedOid + Digest,
Expand All @@ -552,6 +563,10 @@ where
}

/// Parse a signature from a byte slice.
///
/// # Errors
/// Returns [`Error`] if [`Signature`] fails to parse, or if `D` is not a valid digest.
/// See [`SignatureWithOid::new_with_digest`] documentation.
pub fn from_slice_with_digest<D>(slice: &[u8]) -> Result<Self>
where
D: AssociatedOid + Digest,
Expand All @@ -560,6 +575,10 @@ where
}

/// Parse a signature from ASN.1 DER and associate the given digest's OID with it.
///
/// # Errors
/// Returns [`Error`] if `input` failed to parse as an ASN.1 DER-encoded ECDSA signature,
/// or if `D` is not a valid digest.
#[cfg(feature = "der")]
pub fn from_der_with_digest<D>(der_bytes: &[u8]) -> Result<Self>
where
Expand All @@ -571,6 +590,10 @@ where
}

/// Parse a signature from ASN.1 DER and associate the given OID with it.
///
/// # Errors
/// Returns [`Error`] if `input` failed to parse as an ASN.1 DER-encoded ECDSA signature,
/// or if `D` is not a valid digest.
#[cfg(feature = "der")]
pub fn from_der_with_oid(der_bytes: &[u8], oid: ObjectIdentifier) -> Result<Self>
where
Expand Down
38 changes: 22 additions & 16 deletions ecdsa/src/recovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,13 +183,16 @@ where
{
/// Sign the given message prehash, using the given rng for the RFC6979 Section 3.6 "additional
/// data", returning a signature and recovery ID.
///
/// # Errors
///
pub fn sign_prehash_recoverable_with_rng<R: TryCryptoRng + ?Sized>(
&self,
rng: &mut R,
prehash: &[u8],
) -> Result<(Signature<C>, RecoveryId)> {
) -> core::result::Result<(Signature<C>, RecoveryId), R::Error> {
let mut ad = FieldBytes::<C>::default();
rng.try_fill_bytes(&mut ad).map_err(|_| Error::new())?;
rng.try_fill_bytes(&mut ad)?;
Ok(sign_prehashed_rfc6979::<C, C::Digest>(
self.as_nonzero_scalar(),
prehash,
Expand All @@ -198,25 +201,18 @@ where
}

/// Sign the given message prehash, returning a signature and recovery ID.
pub fn sign_prehash_recoverable(&self, prehash: &[u8]) -> Result<(Signature<C>, RecoveryId)> {
Ok(sign_prehashed_rfc6979::<C, C::Digest>(
self.as_nonzero_scalar(),
prehash,
b"",
))
pub fn sign_prehash_recoverable(&self, prehash: &[u8]) -> (Signature<C>, RecoveryId) {
sign_prehashed_rfc6979::<C, C::Digest>(self.as_nonzero_scalar(), prehash, b"")
}

/// Sign the given message digest, returning a signature and recovery ID.
pub fn sign_digest_recoverable<D>(&self, msg_digest: D) -> Result<(Signature<C>, RecoveryId)>
where
D: Digest,
{
pub fn sign_digest_recoverable<D: Digest>(&self, msg_digest: D) -> (Signature<C>, RecoveryId) {
self.sign_prehash_recoverable(&msg_digest.finalize())
}

/// Sign the given message, hashing it with the curve's default digest
/// function, and returning a signature and recovery ID.
pub fn sign_recoverable(&self, msg: &[u8]) -> Result<(Signature<C>, RecoveryId)> {
pub fn sign_recoverable(&self, msg: &[u8]) -> (Signature<C>, RecoveryId) {
self.sign_digest_recoverable(C::Digest::new_with_prefix(msg))
}
}
Expand All @@ -234,7 +230,7 @@ where
) -> Result<(Signature<C>, RecoveryId)> {
let mut digest = D::new();
f(&mut digest)?;
self.sign_digest_recoverable(digest)
Ok(self.sign_digest_recoverable(digest))
}
}

Expand All @@ -250,6 +246,7 @@ where
prehash: &[u8],
) -> Result<(Signature<C>, RecoveryId)> {
self.sign_prehash_recoverable_with_rng(rng, prehash)
.map_err(|_| Error::new())
}
}

Expand Down Expand Up @@ -278,7 +275,7 @@ where
Scalar<C>: Invert<Output = CtOption<Scalar<C>>>,
{
fn sign_prehash(&self, prehash: &[u8]) -> Result<(Signature<C>, RecoveryId)> {
self.sign_prehash_recoverable(prehash)
Ok(self.sign_prehash_recoverable(prehash))
}
}

Expand All @@ -303,7 +300,7 @@ where
let mut digest = C::Digest::new();
msg.iter()
.for_each(|slice| Update::update(&mut digest, slice));
self.sign_digest_recoverable(digest)
Ok(self.sign_digest_recoverable(digest))
}
}

Expand All @@ -317,6 +314,9 @@ where
/// Recover a [`VerifyingKey`] from the given message, signature, and [`RecoveryId`].
///
/// The message is first hashed using this curve's [`DigestAlgorithm`].
///
/// # Errors
/// Returns [`Error`] if the recovered elliptic curve point is the additive identity.
pub fn recover_from_msg(
msg: &[u8],
signature: &Signature<C>,
Expand All @@ -329,6 +329,9 @@ where
}

/// Recover a [`VerifyingKey`] from the given message [`Digest`], signature, and [`RecoveryId`].
///
/// # Errors
/// Returns [`Error`] if the recovered elliptic curve point is the additive identity.
pub fn recover_from_digest<D>(
msg_digest: D,
signature: &Signature<C>,
Expand All @@ -353,6 +356,9 @@ where
/// it in a system of linear equations that can cause the recovery function to output any public
/// key the attacker wants.
/// </div>
///
/// # Errors
/// Returns [`Error`] if the recovered elliptic curve point is the additive identity.
#[allow(non_snake_case)]
pub fn recover_from_prehash(
prehash: &[u8],
Expand Down
15 changes: 11 additions & 4 deletions ecdsa/src/signing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,20 @@ where
C: EcdsaCurve + CurveArithmetic,
{
/// Initialize signing key from a raw scalar serialized as a byte array.
///
/// # Errors
/// Returns an error if `bytes` is not a valid element of the scalar field for `C`.
pub fn from_bytes(bytes: &FieldBytes<C>) -> Result<Self> {
SecretKey::<C>::from_bytes(bytes)
.map(Into::into)
.map_err(|_| Error::new())
}

/// Initialize signing key from a raw scalar serialized as a byte slice.
///
/// # Errors
/// Returns an error if `bytes` is not the length of [`FieldBytes`] for `C`, or if it is not
/// a valid element of the scalar field for `C`.
pub fn from_slice(bytes: &[u8]) -> Result<Self> {
SecretKey::<C>::from_slice(bytes)
.map(Into::into)
Expand All @@ -98,11 +105,11 @@ where

/// Borrow the secret [`NonZeroScalar`] value for this key.
///
/// # ⚠️ Warning
///
/// This value is key material.
/// <div class="warning">
/// <b>Security Warning</b>
///
/// Please treat it with the care it deserves!
/// This value is key material. Please treat it with the care it deserves!
/// </div>
pub fn as_nonzero_scalar(&self) -> &NonZeroScalar<C> {
&self.secret_scalar
}
Expand Down
12 changes: 10 additions & 2 deletions ecdsa/src/verifying.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ where
FieldBytesSize<C>: sec1::ModulusSize,
{
/// Initialize [`VerifyingKey`] from a SEC1-encoded public key.
///
/// # Errors
/// Returns [`Error`] if `bytes` is not a valid SEC1 encoding of a compressed or uncompressed
/// curve point (i.e. it should begin with `0x02`, `0x03`, or `0x04`), or if
/// [`VerifyingKey::from_sec1_point`] returns an error.
pub fn from_sec1_bytes(bytes: &[u8]) -> Result<Self> {
PublicKey::from_sec1_bytes(bytes)
.map(|pk| Self { inner: pk })
Expand All @@ -84,15 +89,18 @@ where

/// Initialize [`VerifyingKey`] from an affine point.
///
/// Returns an [`Error`] if the given affine point is the additive identity
/// (a.k.a. point at infinity).
/// # Errors
/// Returns [`Error`] if the `affine` point is the additive identity (a.k.a. point at infinity).
pub fn from_affine(affine: AffinePoint<C>) -> Result<Self> {
Ok(Self {
inner: PublicKey::from_affine(affine).map_err(|_| Error::new())?,
})
}

/// Initialize [`VerifyingKey`] from an [`Sec1Point`].
///
/// # Errors
/// Returns [`Error`] if `public_key` does not represent a valid elliptic curve point for `C`.
pub fn from_sec1_point(public_key: &Sec1Point<C>) -> Result<Self> {
PublicKey::<C>::from_sec1_point(public_key)
.into_option()
Expand Down