From 409cec8a072ad4a521ebbde19cf2ee76afdc7e08 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Mon, 28 Oct 2019 13:59:36 -0700 Subject: [PATCH] ecdsa: Asn1Signature <-> FixedSignature translation Adapts the following code from BearSSL to support translation between ASN.1 DER encoded and fixed size (a.k.a. "raw") signatures. This additionally allows eagerly checking that ASN.1 DER-encoded signatures are well-formed. --- ecdsa/src/asn1_signature.rs | 26 ++-- ecdsa/src/curve.rs | 3 +- ecdsa/src/fixed_signature.rs | 9 ++ ecdsa/src/lib.rs | 16 +- ecdsa/src/scalar_pair.rs | 278 +++++++++++++++++++++++++++++++++++ 5 files changed, 319 insertions(+), 13 deletions(-) create mode 100644 ecdsa/src/scalar_pair.rs diff --git a/ecdsa/src/asn1_signature.rs b/ecdsa/src/asn1_signature.rs index 28a9e14..01e2a4f 100644 --- a/ecdsa/src/asn1_signature.rs +++ b/ecdsa/src/asn1_signature.rs @@ -1,6 +1,6 @@ //! ASN.1 DER-encoded ECDSA signatures -use crate::curve::Curve; +use crate::{curve::Curve, scalar_pair::ScalarPair}; use core::{ convert::{TryFrom, TryInto}, fmt::{self, Debug}, @@ -23,7 +23,7 @@ use signature::Error; /// - 1-byte: ASN.1 `INTEGER` tag (0x02) /// - 1-byte: length /// - 1-byte: zero to indicate value is positive (`INTEGER` is signed) -type MaxOverhead = generic_array::typenum::U9; +pub type MaxOverhead = generic_array::typenum::U9; /// Maximum size of an ASN.1 DER encoded signature for the given elliptic curve. // TODO(tarcieri): const generics @@ -32,20 +32,20 @@ pub type MaxSize = <::Output as Add> /// ASN.1 DER-encoded ECDSA signature generic over elliptic curves. pub struct Asn1Signature where - ::Output: Add, MaxSize: ArrayLength, + ::Output: ArrayLength + Add, { /// ASN.1 DER-encoded signature data - bytes: GenericArray>, + pub(crate) bytes: GenericArray>, /// Length of the signature in bytes (DER is variable-width) - length: usize, + pub(crate) length: usize, } impl signature::Signature for Asn1Signature where - ::Output: Add, MaxSize: ArrayLength, + ::Output: ArrayLength + Add, { fn from_bytes(bytes: impl AsRef<[u8]>) -> Result { bytes.as_ref().try_into() @@ -54,8 +54,8 @@ where impl AsRef<[u8]> for Asn1Signature where - ::Output: Add, MaxSize: ArrayLength, + ::Output: ArrayLength + Add, { fn as_ref(&self) -> &[u8] { &self.bytes.as_slice()[..self.length] @@ -64,8 +64,8 @@ where impl Debug for Asn1Signature where - ::Output: Add, MaxSize: ArrayLength, + ::Output: ArrayLength + Add, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( @@ -79,21 +79,25 @@ where impl<'a, C: Curve> TryFrom<&'a [u8]> for Asn1Signature where - ::Output: Add, MaxSize: ArrayLength, + ::Output: ArrayLength + Add, { type Error = Error; fn try_from(slice: &'a [u8]) -> Result { let length = slice.len(); - // TODO: better validate signature is well-formed ASN.1 DER if >::to_usize() < length { return Err(Error::new()); } let mut bytes = GenericArray::default(); bytes.as_mut_slice()[..length].copy_from_slice(slice); - Ok(Self { bytes, length }) + let result = Self { bytes, length }; + + // Ensure signature is well-formed ASN.1 DER + ScalarPair::from_asn1_signature(&result).ok_or_else(Error::new)?; + + Ok(result) } } diff --git a/ecdsa/src/curve.rs b/ecdsa/src/curve.rs index 96a1df0..291f870 100644 --- a/ecdsa/src/curve.rs +++ b/ecdsa/src/curve.rs @@ -7,11 +7,12 @@ pub mod secp256k1; pub use self::{nistp256::NistP256, nistp384::NistP384, secp256k1::Secp256k1}; use core::{fmt::Debug, ops::Add}; +use generic_array::typenum::Unsigned; /// Elliptic curve in short Weierstrass form suitable for use with ECDSA pub trait Curve: Debug + Default + Send + Sync { /// Size of an integer modulo p (i.e. the curve's order) when serialized /// as octets (i.e. bytes). This also describes the size of an ECDSA /// private key, as well as half the size of a fixed-width signature. - type ScalarSize: Add; // `Add` impl from typenum used for doubling + type ScalarSize: Unsigned + Add; // `Add` impl from typenum used for doubling } diff --git a/ecdsa/src/fixed_signature.rs b/ecdsa/src/fixed_signature.rs index 89f719e..cd7d690 100644 --- a/ecdsa/src/fixed_signature.rs +++ b/ecdsa/src/fixed_signature.rs @@ -73,3 +73,12 @@ where } } } + +impl From>> for FixedSignature +where + Size: ArrayLength, +{ + fn from(bytes: GenericArray>) -> Self { + Self { bytes } + } +} diff --git a/ecdsa/src/lib.rs b/ecdsa/src/lib.rs index 9414d14..583de50 100644 --- a/ecdsa/src/lib.rs +++ b/ecdsa/src/lib.rs @@ -1,5 +1,18 @@ //! Elliptic Curve Digital Signature Algorithm (ECDSA) as specified in -//! [FIPS 186-4] (Digital Signature Standard) +//! [FIPS 186-4][1] (Digital Signature Standard) +//! +//! This crate doesn't contain an implementation of ECDSA itself, but instead +//! contains an [`ecdsa::Asn1Signature`] and [`ecdsa::FixedSignature`] type +//! generic over an [`ecdsa::Curve`] type which other crates can use in +//! conjunction with the [`signature::Signer`] and [`signature::Verifier`] +//! traits. +//! +//! These traits allow crates which produce and consume ECDSA signatures +//! to be written abstractly in such a way that different signer/verifier +//! providers can be plugged in, enabling support for using different +//! ECDSA implementations, including HSMs or Cloud KMS services. +//! +//! [1]: https://csrc.nist.gov/publications/detail/fips/186/4/final #![no_std] #![forbid(unsafe_code)] @@ -14,5 +27,6 @@ pub use signature; pub mod asn1_signature; pub mod curve; pub mod fixed_signature; +mod scalar_pair; pub use self::{asn1_signature::Asn1Signature, curve::Curve, fixed_signature::FixedSignature}; diff --git a/ecdsa/src/scalar_pair.rs b/ecdsa/src/scalar_pair.rs new file mode 100644 index 0000000..d238f8f --- /dev/null +++ b/ecdsa/src/scalar_pair.rs @@ -0,0 +1,278 @@ +//! An ECDSA signature comprises 2 scalars: `r` and `s`. The scalars are +//! the same size as the curve's modulus, i.e. for an elliptic curve over +//! a ~256-bit prime field, they will also be 256-bit (i.e. the `ScalarSize` +//! for a particular `WeierstrassCurve`) +//! +//! This type provides a convenient representation for converting between +//! formats, i.e. all of the serialization code is in this module. + +// Adapted from BearSSL. Copyright (c) 2016 Thomas Pornin . +// Relicensed under Apache 2.0 + MIT (from original MIT) with permission. +// +// +// + +use crate::{ + asn1_signature::{self, Asn1Signature}, + Curve, FixedSignature, +}; +use core::{marker::PhantomData, ops::Add}; +use generic_array::{typenum::Unsigned, ArrayLength, GenericArray}; +use signature::Signature; + +/// ASN.1 tags +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u8)] +pub(crate) enum Asn1Tag { + /// ASN.1 `INTEGER` + Integer = 0x02, + + /// ASN.1 `SEQUENCE`: lists of other elements + Sequence = 0x30, +} + +/// ECDSA signature `r` and `s` values, represented as slices which are at +/// most `C::ScalarSize` bytes (but *may* be smaller) +pub struct ScalarPair<'a, C: Curve + 'a> { + /// `r` scalar value + r: &'a [u8], + + /// `s` scalar value + s: &'a [u8], + + /// Placeholder for elliptic curve type + curve: PhantomData, +} + +impl<'a, C: Curve + 'a> ScalarPair<'a, C> +where + asn1_signature::MaxSize: ArrayLength, + ::Output: ArrayLength + Add, +{ + /// Parse the given ASN.1 DER-encoded ECDSA signature, obtaining the + /// `r` and `s` scalar pair + pub(crate) fn from_asn1_signature(signature: &'a Asn1Signature) -> Option { + // Signature format is a SEQUENCE of two INTEGER values. We + // support only integers of less than 127 bytes each (signed + // encoding) so the resulting raw signature will have length + // at most 254 bytes. + let mut bytes = signature.as_slice(); + + // First byte is SEQUENCE tag. + if bytes[0] != Asn1Tag::Sequence as u8 { + return None; + } + + // The SEQUENCE length will be encoded over one or two bytes. We + // limit the total SEQUENCE contents to 255 bytes, because it + // makes things simpler; this is enough for subgroup orders up + // to 999 bits. + let mut zlen = bytes[1] as usize; + + if zlen > 0x80 { + if zlen != 0x81 { + return None; + } + + zlen = bytes[2] as usize; + + if zlen != bytes.len().checked_sub(3).unwrap() { + return None; + } + + bytes = &bytes[3..]; + } else { + if zlen != bytes.len().checked_sub(2).unwrap() { + return None; + } + + bytes = &bytes[2..]; + } + + // First INTEGER (r) + let (mut r, bytes) = Self::asn1_int_parse(bytes)?; + + // Second INTEGER (s) + let (mut s, bytes) = Self::asn1_int_parse(bytes)?; + + if !bytes.is_empty() { + return None; + } + + let scalar_size = C::ScalarSize::to_usize(); + + if r.len() > scalar_size { + if r.len() != scalar_size.checked_add(1).unwrap() { + return None; + } + + if r[0] != 0 { + return None; + } + + r = &r[1..]; + } + + if s.len() > scalar_size { + if s.len() != scalar_size.checked_add(1).unwrap() { + return None; + } + + if s[0] != 0 { + return None; + } + + s = &s[1..]; + } + + // Removing leading zeros from r and s + + while !r.is_empty() && r[0] == 0 { + r = &r[1..]; + } + + while !s.is_empty() && s[0] == 0 { + s = &s[1..]; + } + + Some(Self { + r, + s, + curve: PhantomData, + }) + } + + /// Parse the given fixed-size ECDSA signature, obtaining the `r` and `s` + /// scalar pair + pub(crate) fn from_fixed_signature(signature: &'a FixedSignature) -> Self { + let scalar_size = C::ScalarSize::to_usize(); + + Self { + r: &signature.as_ref()[..scalar_size], + s: &signature.as_ref()[scalar_size..], + curve: PhantomData, + } + } + + /// Serialize this ECDSA signature's `r` and `s` scalar pair as ASN.1 DER + pub(crate) fn to_asn1_signature(&self) -> Asn1Signature { + let rlen = Self::asn1_int_length(self.r); + let slen = Self::asn1_int_length(self.s); + let mut bytes = GenericArray::default(); + + // SEQUENCE header + bytes[0] = Asn1Tag::Sequence as u8; + let zlen = rlen.checked_add(slen).unwrap().checked_add(4).unwrap(); + + let mut offset = if zlen >= 0x80 { + bytes[1] = 0x81; + bytes[2] = zlen as u8; + 3 + } else { + bytes[1] = zlen as u8; + 2 + }; + + // First INTEGER (r) + Self::asn1_int_serialize(self.r, &mut bytes[offset..], rlen); + offset = offset.checked_add(2).unwrap().checked_add(rlen).unwrap(); + + // Second INTEGER (s) + Self::asn1_int_serialize(self.s, &mut bytes[offset..], slen); + + Asn1Signature { + bytes, + length: offset.checked_add(2).unwrap().checked_add(slen).unwrap(), + } + } + + pub(crate) fn to_fixed_signature(&self) -> FixedSignature { + let mut bytes = GenericArray::default(); + + let scalar_size = C::ScalarSize::to_usize(); + let rbegin = scalar_size.checked_sub(self.r.len()).unwrap(); + bytes.as_mut_slice()[rbegin..scalar_size].copy_from_slice(self.r); + + let sbegin = bytes.len().checked_sub(self.s.len()).unwrap(); + bytes.as_mut_slice()[sbegin..].copy_from_slice(self.s); + + FixedSignature::from(bytes) + } + + /// Compute ASN.1 DER encoded length for the provided scalar. The ASN.1 + /// encoding is signed, so its leading bit must have value 0; it must also be + /// of minimal length (so leading bytes of value 0 must be removed, except if + /// that would contradict the rule about the sign bit). + fn asn1_int_length(mut x: &[u8]) -> usize { + while !x.is_empty() && x[0] == 0 { + x = &x[1..]; + } + + if x.is_empty() || x[0] >= 0x80 { + x.len().checked_add(1).unwrap() + } else { + x.len() + } + } + + /// Parse an integer from its ASN.1 DER serialization + fn asn1_int_parse(bytes: &[u8]) -> Option<(&[u8], &[u8])> { + if bytes.len() < 3 { + return None; + } + + if bytes[0] != Asn1Tag::Integer as u8 { + return None; + } + + let len = bytes[1] as usize; + + if len >= 0x80 || len.checked_add(2).unwrap() > bytes.len() { + return None; + } + + let integer = &bytes[2..len.checked_add(2).unwrap()]; + let remaining = &bytes[len.checked_add(2).unwrap()..]; + + Some((integer, remaining)) + } + + /// Serialize scalar as ASN.1 DER + fn asn1_int_serialize(scalar: &[u8], out: &mut [u8], len: usize) { + out[0] = Asn1Tag::Integer as u8; + out[1] = len as u8; + + if len > C::ScalarSize::to_usize() { + out[2] = 0x00; + out[3..C::ScalarSize::to_usize().checked_add(3).unwrap()].copy_from_slice(scalar); + } else { + out[2..len.checked_add(2).unwrap()] + .copy_from_slice(&scalar[C::ScalarSize::to_usize().checked_sub(len).unwrap()..]); + } + } +} + +impl<'a, C: Curve> From<&'a Asn1Signature> for FixedSignature +where + asn1_signature::MaxSize: ArrayLength, + ::Output: ArrayLength + Add, +{ + fn from(asn1_signature: &Asn1Signature) -> FixedSignature { + // We always ensure `Asn1Signature`s parse successfully, so this should always work + ScalarPair::from_asn1_signature(asn1_signature) + .unwrap() + .to_fixed_signature() + } +} + +impl<'a, C: Curve> From<&'a FixedSignature> for Asn1Signature +where + asn1_signature::MaxSize: ArrayLength, + ::Output: ArrayLength + Add, +{ + /// Parse `r` and `s` values from a fixed-width signature and reserialize + /// them as ASN.1 DER. + fn from(fixed_signature: &FixedSignature) -> Self { + ScalarPair::from_fixed_signature(fixed_signature).to_asn1_signature() + } +}