diff --git a/Cargo.lock b/Cargo.lock index a57cc007..5c29b4cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,33 +10,12 @@ dependencies = [ "serde", ] -[[package]] -name = "block-buffer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "generic-array", -] - [[package]] name = "byteorder" version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - -[[package]] -name = "cpuid-bool" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d375c433320f6c5057ae04a04376eef4d04ce2801448cf8863a78da99107be4" - [[package]] name = "digest" version = "0.9.0" @@ -51,7 +30,6 @@ name = "ecdsa" version = "0.7.0-pre" dependencies = [ "elliptic-curve", - "sha2", "signature", ] @@ -83,31 +61,12 @@ dependencies = [ "version_check", ] -[[package]] -name = "opaque-debug" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" - [[package]] name = "serde" version = "1.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3" -[[package]] -name = "sha2" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2933378ddfeda7ea26f48c555bdad8bb446bf8a3d17832dc83e380d444cfb8c1" -dependencies = [ - "block-buffer", - "cfg-if", - "cpuid-bool", - "digest", - "opaque-debug", -] - [[package]] name = "signature" version = "1.1.0" diff --git a/ecdsa/Cargo.toml b/ecdsa/Cargo.toml index 64907577..f9f7f3b1 100644 --- a/ecdsa/Cargo.toml +++ b/ecdsa/Cargo.toml @@ -19,18 +19,13 @@ version = "= 0.5.0-pre" default-features = false features = ["weierstrass"] -[dependencies.sha2] -version = "0.9" -optional = true -default-features = false - [dependencies.signature] version = ">= 1.1.0, < 1.2.0" default-features = false [features] default = ["digest", "std"] -digest = ["signature/digest-preview", "sha2"] +digest = ["signature/digest-preview"] hazmat = [] std = ["elliptic-curve/std", "signature/std"] diff --git a/ecdsa/src/asn1.rs b/ecdsa/src/asn1.rs new file mode 100644 index 00000000..27683e06 --- /dev/null +++ b/ecdsa/src/asn1.rs @@ -0,0 +1,322 @@ +//! Support for ECDSA signatures encoded as ASN.1 DER. + +// Adapted from BearSSL. Copyright (c) 2016 Thomas Pornin . +// Relicensed under Apache 2.0 + MIT (from original MIT) with permission. +// +// +// + +use crate::{ + generic_array::{ArrayLength, GenericArray}, + Error, +}; +use core::{ + convert::TryFrom, + ops::{Add, Range}, +}; +use elliptic_curve::{consts::U9, ScalarBytes}; + +/// Maximum overhead of an ASN.1 DER-encoded ECDSA signature for a given curve: +/// 9-bytes. +/// +/// Includes 3-byte ASN.1 DER header: +/// +/// - 1-byte: ASN.1 `SEQUENCE` tag (0x30) +/// - 2-byte: length +/// +/// ...followed by two ASN.1 `INTEGER` values, which each have a header whose +/// maximum length is the following: +/// +/// - 1-byte: ASN.1 `INTEGER` tag (0x02) +/// - 1-byte: length +/// - 1-byte: zero to indicate value is positive (`INTEGER` is signed) +pub type MaxOverhead = U9; + +/// Maximum size of an ASN.1 DER encoded signature for the given elliptic curve. +pub type MaxSize = <::Output as Add>::Output; + +/// Byte array containing a serialized ASN.1 document +type DocumentBytes = GenericArray>; + +/// ASN.1 `INTEGER` tag +const INTEGER_TAG: u8 = 0x02; + +/// ASN.1 `SEQUENCE` tag +const SEQUENCE_TAG: u8 = 0x30; + +/// Document containing an ASN.1 DER-encoded signature. +/// +/// Generic over the scalar size of the elliptic curve. +pub struct Document +where + ScalarSize: Add + ArrayLength, + MaxSize: ArrayLength, + ::Output: Add + ArrayLength, +{ + /// ASN.1 DER-encoded signature data + bytes: DocumentBytes, + + /// Range of the `r` value within the document + r_range: Range, + + /// Range of the `s` value within the document + s_range: Range, +} + +#[allow(clippy::len_without_is_empty)] +impl Document +where + ScalarSize: Add + ArrayLength, + MaxSize: ArrayLength, + ::Output: Add + ArrayLength, +{ + /// Parse an ASN.1 DER-encoded ECDSA signature from a byte slice + pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result { + let input = bytes.as_ref(); + + // 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. + // + // First byte is SEQUENCE tag. + if input[0] != SEQUENCE_TAG as u8 { + return Err(Error::new()); + } + + // 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 = input[1] as usize; + + let offset = if zlen > 0x80 { + if zlen != 0x81 { + return Err(Error::new()); + } + + zlen = input[2] as usize; + 3 + } else { + 2 + }; + + if zlen != input.len().checked_sub(offset).unwrap() { + return Err(Error::new()); + } + + // First INTEGER (r) + let r_range = parse_int(&input[offset..], ScalarSize::to_usize())?; + let r_start = offset.checked_add(r_range.start).unwrap(); + let r_end = offset.checked_add(r_range.end).unwrap(); + + // Second INTEGER (s) + let s_range = parse_int(&input[r_end..], ScalarSize::to_usize())?; + let s_start = r_end.checked_add(s_range.start).unwrap(); + let s_end = r_end.checked_add(s_range.end).unwrap(); + + if s_end != bytes.as_ref().len() { + return Err(Error::new()); + } + + let mut byte_arr = DocumentBytes::::default(); + byte_arr[..s_end].copy_from_slice(bytes.as_ref()); + + Ok(Document { + bytes: byte_arr, + r_range: Range { + start: r_start, + end: r_end, + }, + s_range: Range { + start: s_start, + end: s_end, + }, + }) + } + + /// Get the length of the document + pub fn len(&self) -> usize { + self.s_range.end + } + + /// Create an ASN.1 DER encoded signature from the `r` and `s` scalars + pub(crate) fn from_scalars(r: &ScalarBytes, s: &ScalarBytes) -> Self { + let r_len = int_length(r); + let s_len = int_length(s); + let scalar_size = ScalarSize::to_usize(); + let mut bytes = DocumentBytes::::default(); + + // SEQUENCE header + bytes[0] = SEQUENCE_TAG as u8; + let zlen = r_len.checked_add(s_len).unwrap().checked_add(4).unwrap(); + + let offset = if zlen >= 0x80 { + bytes[1] = 0x81; + bytes[2] = zlen as u8; + 3 + } else { + bytes[1] = zlen as u8; + 2 + }; + + // First INTEGER (r) + serialize_int(r.as_slice(), &mut bytes[offset..], r_len, scalar_size); + let r_end = offset.checked_add(2).unwrap().checked_add(r_len).unwrap(); + + // Second INTEGER (s) + serialize_int(s.as_slice(), &mut bytes[r_end..], s_len, scalar_size); + let s_end = r_end.checked_add(2).unwrap().checked_add(s_len).unwrap(); + + Self::from_bytes(&bytes[..s_end]).expect("generated invalid ASN.1 DER") + } + + /// Get the `r` component of the signature (leading zeros removed) + pub(crate) fn r(&self) -> &[u8] { + &self.bytes[self.r_range.clone()] + } + + /// Get the `s` component of the signature (leading zeros removed) + pub(crate) fn s(&self) -> &[u8] { + &self.bytes[self.s_range.clone()] + } +} + +impl AsRef<[u8]> for Document +where + ScalarSize: Add + ArrayLength, + MaxSize: ArrayLength, + ::Output: Add + ArrayLength, +{ + fn as_ref(&self) -> &[u8] { + &self.bytes.as_slice()[..self.len()] + } +} + +impl TryFrom<&[u8]> for Document +where + ScalarSize: Add + ArrayLength, + MaxSize: ArrayLength, + ::Output: Add + ArrayLength, +{ + type Error = Error; + + fn try_from(bytes: &[u8]) -> Result { + Self::from_bytes(bytes) + } +} + +/// Parse an integer from its ASN.1 DER serialization +fn parse_int(bytes: &[u8], scalar_size: usize) -> Result, Error> { + if bytes.len() < 3 { + return Err(Error::new()); + } + + if bytes[0] != INTEGER_TAG as u8 { + return Err(Error::new()); + } + + let len = bytes[1] as usize; + + if len >= 0x80 || len.checked_add(2).unwrap() > bytes.len() { + return Err(Error::new()); + } + + let mut start = 2usize; + let end = start.checked_add(len).unwrap(); + + start = start + .checked_add(trim_zeroes(&bytes[start..end], scalar_size)?) + .unwrap(); + + Ok(Range { start, end }) +} + +/// Serialize scalar as ASN.1 DER +fn serialize_int(scalar: &[u8], out: &mut [u8], len: usize, scalar_size: usize) { + out[0] = INTEGER_TAG as u8; + out[1] = len as u8; + + if len > scalar_size { + out[2] = 0x00; + out[3..scalar_size.checked_add(3).unwrap()].copy_from_slice(scalar); + } else { + out[2..len.checked_add(2).unwrap()] + .copy_from_slice(&scalar[scalar_size.checked_sub(len).unwrap()..]); + } +} + +/// 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 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() + } +} + +/// Compute an offset within an ASN.1 INTEGER after skipping leading zeroes +fn trim_zeroes(mut bytes: &[u8], scalar_size: usize) -> Result { + let mut offset = 0; + + if bytes.len() > scalar_size { + if bytes.len() != scalar_size.checked_add(1).unwrap() { + return Err(Error::new()); + } + + if bytes[0] != 0 { + return Err(Error::new()); + } + + bytes = &bytes[1..]; + offset += 1; + } + + while !bytes.is_empty() && bytes[0] == 0 { + bytes = &bytes[1..]; + offset += 1; + } + + Ok(offset) +} + +#[cfg(test)] +mod tests { + use elliptic_curve::{consts::U32, weierstrass::Curve}; + use signature::Signature as _; + + #[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord)] + pub struct ExampleCurve; + + impl Curve for ExampleCurve { + type ScalarSize = U32; + } + + type Signature = crate::Signature; + + const EXAMPLE_SIGNATURE: [u8; 64] = [ + 0xf3, 0xac, 0x80, 0x61, 0xb5, 0x14, 0x79, 0x5b, 0x88, 0x43, 0xe3, 0xd6, 0x62, 0x95, 0x27, + 0xed, 0x2a, 0xfd, 0x6b, 0x1f, 0x6a, 0x55, 0x5a, 0x7a, 0xca, 0xbb, 0x5e, 0x6f, 0x79, 0xc8, + 0xc2, 0xac, 0x8b, 0xf7, 0x78, 0x19, 0xca, 0x5, 0xa6, 0xb2, 0x78, 0x6c, 0x76, 0x26, 0x2b, + 0xf7, 0x37, 0x1c, 0xef, 0x97, 0xb2, 0x18, 0xe9, 0x6f, 0x17, 0x5a, 0x3c, 0xcd, 0xda, 0x2a, + 0xcc, 0x5, 0x89, 0x3, + ]; + + #[test] + fn test_fixed_to_asn1_signature_roundtrip() { + let signature1 = Signature::from_bytes(&EXAMPLE_SIGNATURE).unwrap(); + + // Convert to ASN.1 DER and back + let asn1_signature = signature1.to_asn1(); + let signature2 = Signature::from_asn1(asn1_signature.as_ref()).unwrap(); + + assert_eq!(signature1, signature2); + } +} diff --git a/ecdsa/src/asn1_signature.rs b/ecdsa/src/asn1_signature.rs deleted file mode 100644 index 8711308a..00000000 --- a/ecdsa/src/asn1_signature.rs +++ /dev/null @@ -1,105 +0,0 @@ -//! ASN.1 DER-encoded ECDSA signatures - -use crate::{ - convert::ScalarPair, - generic_array::{typenum::Unsigned, ArrayLength, GenericArray}, - Error, -}; -use core::{ - convert::{TryFrom, TryInto}, - fmt::{self, Debug}, - ops::Add, -}; -use elliptic_curve::weierstrass::Curve; - -/// Maximum overhead of an ASN.1 DER-encoded ECDSA signature for a given curve: -/// 9-bytes. -/// -/// Includes 3-byte ASN.1 DER header: -/// -/// - 1-byte: ASN.1 `SEQUENCE` tag (0x30) -/// - 2-byte: length -/// -/// ...followed by two ASN.1 `INTEGER` values, which each have a header whose -/// maximum length is the following: -/// -/// - 1-byte: ASN.1 `INTEGER` tag (0x02) -/// - 1-byte: length -/// - 1-byte: zero to indicate value is positive (`INTEGER` is signed) -pub type MaxOverhead = crate::generic_array::typenum::U9; - -/// Maximum size of an ASN.1 DER encoded signature for the given elliptic curve. -pub type MaxSize = <::Output as Add>::Output; - -/// ASN.1 DER-encoded ECDSA signature generic over elliptic curves. -pub struct Asn1Signature -where - MaxSize: ArrayLength, - ::Output: ArrayLength + Add, -{ - /// ASN.1 DER-encoded signature data - pub(crate) bytes: GenericArray>, - - /// Length of the signature in bytes (DER is variable-width) - pub(crate) length: usize, -} - -impl signature::Signature for Asn1Signature -where - MaxSize: ArrayLength, - ::Output: ArrayLength + Add, -{ - fn from_bytes(bytes: &[u8]) -> Result { - bytes.try_into() - } -} - -impl AsRef<[u8]> for Asn1Signature -where - MaxSize: ArrayLength, - ::Output: ArrayLength + Add, -{ - fn as_ref(&self) -> &[u8] { - &self.bytes.as_slice()[..self.length] - } -} - -impl Debug for Asn1Signature -where - MaxSize: ArrayLength, - ::Output: ArrayLength + Add, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "Asn1Signature<{:?}> {{ bytes: {:?}) }}", - C::default(), - self.as_ref() - ) - } -} - -impl TryFrom<&[u8]> for Asn1Signature -where - MaxSize: ArrayLength, - ::Output: ArrayLength + Add, -{ - type Error = Error; - - fn try_from(slice: &[u8]) -> Result { - let length = slice.len(); - - if >::to_usize() < length { - return Err(Error::new()); - } - - let mut bytes = GenericArray::default(); - bytes.as_mut_slice()[..length].copy_from_slice(slice); - 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/convert.rs b/ecdsa/src/convert.rs deleted file mode 100644 index 6eeb64f4..00000000 --- a/ecdsa/src/convert.rs +++ /dev/null @@ -1,335 +0,0 @@ -//! Support for converting ECDSA signatures between the ASN.1 DER and "fixed" -//! encodings using a self-contained implementation of the relevant parts of -//! ASN.1 DER (i.e. `SEQUENCE` and `INTEGER`). -//! -//! Adapted from BearSSL. Copyright (c) 2016 Thomas Pornin . -//! Relicensed under Apache 2.0 + MIT (from original MIT) with permission. -//! -//! -//! - -use crate::generic_array::{typenum::Unsigned, ArrayLength, GenericArray}; -use crate::{ - asn1_signature::{self, Asn1Signature}, - Curve, FixedSignature, -}; -use core::{marker::PhantomData, ops::Add}; -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). -/// -/// The `r` and `s` 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 `Curve`). -/// -/// This type provides a convenient representation for converting between -/// formats, i.e. all of the serialization code is in this module. -pub struct ScalarPair<'a, C: Curve + 'a> { - /// `r` scalar value - pub(crate) r: &'a [u8], - - /// `s` scalar value - pub(crate) 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_bytes(); - - // 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(), - } - } - - /// Write the `r` component of this scalar pair to the provided buffer, - /// which must be `C::ScalarSize` bytes (assumed to be initialized to zero) - pub(crate) fn write_r(&self, buffer: &mut [u8]) { - let scalar_size = C::ScalarSize::to_usize(); - debug_assert_eq!(buffer.len(), scalar_size); - - let r_begin = scalar_size.checked_sub(self.r.len()).unwrap(); - buffer[r_begin..].copy_from_slice(self.r); - } - - /// Write the `s` component of this scalar pair to the provided buffer, - /// which must be `C::ScalarSize` bytes (assumed to be initialized to zero) - pub(crate) fn write_s(&self, buffer: &mut [u8]) { - let scalar_size = C::ScalarSize::to_usize(); - debug_assert_eq!(buffer.len(), scalar_size); - - let s_begin = scalar_size.checked_sub(self.s.len()).unwrap(); - buffer[s_begin..].copy_from_slice(self.s); - } - - /// Serialize this ECDSA signature's `r` and `s` scalar pair as a - /// fixed-sized signature - pub(crate) fn to_fixed_signature(&self) -> FixedSignature { - let mut bytes = GenericArray::default(); - let scalar_size = C::ScalarSize::to_usize(); - - self.write_r(&mut bytes[..scalar_size]); - self.write_s(&mut bytes[scalar_size..]); - - 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) - .expect("invalid ASN.1 signature") - .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() - } -} - -#[cfg(all(test, feature = "test-vectors"))] -mod tests { - use elliptic_curve::{consts::U32, weierstrass::Curve}; - use signature::Signature; - - #[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord)] - pub struct ExampleCurve; - - impl Curve for ExampleCurve { - type ScalarSize = U32; - } - - type Asn1Signature = crate::Asn1Signature; - type FixedSignature = crate::FixedSignature; - - const EXAMPLE_SIGNATURE: [u8; 64] = [ - 0xf3, 0xac, 0x80, 0x61, 0xb5, 0x14, 0x79, 0x5b, 0x88, 0x43, 0xe3, 0xd6, 0x62, 0x95, 0x27, - 0xed, 0x2a, 0xfd, 0x6b, 0x1f, 0x6a, 0x55, 0x5a, 0x7a, 0xca, 0xbb, 0x5e, 0x6f, 0x79, 0xc8, - 0xc2, 0xac, 0x8b, 0xf7, 0x78, 0x19, 0xca, 0x5, 0xa6, 0xb2, 0x78, 0x6c, 0x76, 0x26, 0x2b, - 0xf7, 0x37, 0x1c, 0xef, 0x97, 0xb2, 0x18, 0xe9, 0x6f, 0x17, 0x5a, 0x3c, 0xcd, 0xda, 0x2a, - 0xcc, 0x5, 0x89, 0x3, - ]; - - #[test] - fn test_fixed_to_asn1_signature_roundtrip() { - let fixed_signature = FixedSignature::from_bytes(&EXAMPLE_SIGNATURE).unwrap(); - - // Convert to DER and back - let asn1_signature = Asn1Signature::from(&fixed_signature); - let fixed_signature2 = FixedSignature::from(&asn1_signature); - - assert_eq!(fixed_signature, fixed_signature2); - } -} diff --git a/ecdsa/src/fixed_signature.rs b/ecdsa/src/fixed_signature.rs deleted file mode 100644 index f62cf120..00000000 --- a/ecdsa/src/fixed_signature.rs +++ /dev/null @@ -1,92 +0,0 @@ -//! Fixed-sized (a.k.a. "raw") ECDSA signatures - -use crate::{ - generic_array::{typenum::Unsigned, ArrayLength, GenericArray}, - Error, -}; -use core::{ - convert::{TryFrom, TryInto}, - fmt::{self, Debug}, - ops::Add, -}; -use elliptic_curve::weierstrass::Curve; - -/// Size of a fixed sized signature for the given elliptic curve. -pub type Size = ::Output; - -/// Fixed-sized (a.k.a. "raw") ECDSA signatures generic over elliptic curves. -/// -/// These signatures are serialized as fixed-sized big endian scalar values -/// with no additional framing: -/// -/// - `r`: field element size for the given curve, big-endian -/// - `s`: field element size for the given curve, big-endian -/// -/// For example, in a curve with a 256-bit modulus like NIST P-256 or -/// secp256k1, `r` and `s` will both be 32-bytes, resulting in a signature -/// with a total of 64-bytes. -#[derive(Clone, Eq, PartialEq)] -pub struct FixedSignature -where - Size: ArrayLength, -{ - bytes: GenericArray>, -} - -impl signature::Signature for FixedSignature -where - Size: ArrayLength, -{ - fn from_bytes(bytes: &[u8]) -> Result { - bytes.try_into() - } -} - -impl AsRef<[u8]> for FixedSignature -where - Size: ArrayLength, -{ - fn as_ref(&self) -> &[u8] { - self.bytes.as_slice() - } -} - -impl Debug for FixedSignature -where - Size: ArrayLength, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "FixedSignature<{:?}> {{ bytes: {:?}) }}", - C::default(), - self.as_ref() - ) - } -} - -impl TryFrom<&[u8]> for FixedSignature -where - Size: ArrayLength, -{ - type Error = Error; - - fn try_from(bytes: &[u8]) -> Result { - if bytes.len() == >::to_usize() { - Ok(Self { - bytes: GenericArray::clone_from_slice(bytes), - }) - } else { - Err(Error::new()) - } - } -} - -impl From>> for FixedSignature -where - Size: ArrayLength, -{ - fn from(bytes: GenericArray>) -> Self { - Self { bytes } - } -} diff --git a/ecdsa/src/hazmat.rs b/ecdsa/src/hazmat.rs index a46cb0b9..73619c64 100644 --- a/ecdsa/src/hazmat.rs +++ b/ecdsa/src/hazmat.rs @@ -1,4 +1,4 @@ -//! Low-level ECDSA primitives +//! Low-level ECDSA primitives. //! //! # ⚠️ Warning: Hazmat! //! @@ -11,7 +11,7 @@ //! Failure to use them correctly can lead to catastrophic failures including //! FULL PRIVATE KEY RECOVERY! -use crate::fixed_signature::{FixedSignature, Size as FixedSignatureSize}; +use crate::{Signature, SignatureSize}; use elliptic_curve::{generic_array::ArrayLength, weierstrass::Curve, Error, ScalarBytes}; /// Try to sign the given prehashed message using ECDSA. @@ -22,7 +22,7 @@ use elliptic_curve::{generic_array::ArrayLength, weierstrass::Curve, Error, Scal pub trait SignPrimitive where C: Curve, - FixedSignatureSize: ArrayLength, + SignatureSize: ArrayLength, { /// Scalar type // TODO(tarcieri): add bounds that support generation/conversion from bytes @@ -40,7 +40,7 @@ where ephemeral_scalar: Self::Scalar, masking_scalar: Option, hashed_msg: &ScalarBytes, - ) -> Result, Error>; + ) -> Result, Error>; } /// Verify the given prehashed message using ECDSA. @@ -51,7 +51,7 @@ where pub trait VerifyPrimitive where C: Curve, - FixedSignatureSize: ArrayLength, + SignatureSize: ArrayLength, { /// Verify the prehashed message against the provided signature /// @@ -63,6 +63,6 @@ where fn verify_prehashed( &self, hashed_msg: &ScalarBytes, - signature: &FixedSignature, + signature: &Signature, ) -> Result<(), Error>; } diff --git a/ecdsa/src/lib.rs b/ecdsa/src/lib.rs index ebc57157..ce177f7c 100644 --- a/ecdsa/src/lib.rs +++ b/ecdsa/src/lib.rs @@ -2,28 +2,17 @@ //! [FIPS 186-4][1] (Digital Signature Standard) //! //! This crate doesn't contain an implementation of ECDSA itself, but instead -//! contains [`Asn1Signature`] and [`FixedSignature`] types which are generic -//! over elliptic [`Curve`] types (e.g. `NistP256`, `NistP384`, `Secp256k1`) -//! which can be used in conjunction with the [`signature::Signer`] and +//! contains [`Signature`] type which is generic over elliptic [`Curve`] types. +//! It's designed to be used in conjunction with the [`signature::Signer`] and //! [`signature::Verifier`] traits to provide signature types which are //! reusable across multiple signing and verification provider crates. //! -//! Transcoding between [`Asn1Signature`] and [`FixedSignature`] of the same -//! [`Curve`] type is supported in the form of simple `From` impls. -//! -//! Additionally, the [`PublicKey`] and [`SecretKey`] types, also generic -//! over elliptic curve types, provide reusable key types, sourced from the -//! [`elliptic-curve`][2] crate. The [`PublicKey`] type supports both -//! compressed and uncompressed points, and for the P-256 curve in particular -//! supports converting between the compressed and uncompressed forms. -//! //! 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 -//! [2]: http://docs.rs/elliptic-curve #![no_std] #![cfg_attr(docsrs, feature(doc_cfg))] @@ -34,15 +23,11 @@ html_root_url = "https://docs.rs/ecdsa/0.6.1" )] -pub mod asn1_signature; -mod convert; -pub mod fixed_signature; +pub mod asn1; #[cfg(feature = "hazmat")] pub mod hazmat; -pub use self::{asn1_signature::Asn1Signature, fixed_signature::FixedSignature}; - // Re-export the `elliptic-curve` crate (and select types) pub use elliptic_curve::{ self, generic_array, @@ -52,3 +37,153 @@ pub use elliptic_curve::{ // Re-export the `signature` crate (and select types) pub use signature::{self, Error}; + +use core::{ + convert::TryFrom, + fmt::{self, Debug}, + ops::Add, +}; +use elliptic_curve::ScalarBytes; +use generic_array::{typenum::Unsigned, ArrayLength, GenericArray}; + +/// Size of a fixed sized signature for the given elliptic curve. +pub type SignatureSize = <::ScalarSize as Add>::Output; + +/// Fixed-size byte array containing an ECDSA signature +pub type SignatureBytes = GenericArray>; + +/// ECDSA signatures (fixed-size). +/// +/// Generic over elliptic curve types. +/// +/// These signatures are serialized as fixed-sized big endian scalar values +/// with no additional framing: +/// +/// - `r`: field element size for the given curve, big-endian +/// - `s`: field element size for the given curve, big-endian +/// +/// For example, in a curve with a 256-bit modulus like NIST P-256 or +/// secp256k1, `r` and `s` will both be 32-bytes, resulting in a signature +/// with a total of 64-bytes. +/// +/// ASN.1 is also supported via the [`Signature::from_asn1`] and +/// [`Signature::to_asn1`] methods. +#[derive(Clone, Eq, PartialEq)] +pub struct Signature +where + SignatureSize: ArrayLength, +{ + bytes: SignatureBytes, +} + +impl Signature +where + SignatureSize: ArrayLength, +{ + /// Create a [`Signature`] from the serialized `r` and `s` components + pub fn from_scalars(r: &ScalarBytes, s: &ScalarBytes) -> Self { + let mut bytes = SignatureBytes::::default(); + let scalar_size = C::ScalarSize::to_usize(); + bytes[..scalar_size].copy_from_slice(r.as_slice()); + bytes[scalar_size..].copy_from_slice(s.as_slice()); + Signature { bytes } + } + + /// Parse a signature from ASN.1 DER + pub fn from_asn1(bytes: &[u8]) -> Result + where + C::ScalarSize: Add + ArrayLength, + asn1::MaxSize: ArrayLength, + ::Output: Add + ArrayLength, + { + asn1::Document::::from_bytes(bytes).map(Into::into) + } + + /// Serialize this signature as ASN.1 DER + pub fn to_asn1(&self) -> asn1::Document + where + C::ScalarSize: Add + ArrayLength, + asn1::MaxSize: ArrayLength, + ::Output: Add + ArrayLength, + { + asn1::Document::from_scalars(self.r(), self.s()) + } + + /// Get the `r` component of this signature + pub fn r(&self) -> &ScalarBytes { + ScalarBytes::from_slice(&self.bytes[..C::ScalarSize::to_usize()]) + } + + /// Get the `s` component of this signature + pub fn s(&self) -> &ScalarBytes { + ScalarBytes::from_slice(&self.bytes[C::ScalarSize::to_usize()..]) + } +} + +impl signature::Signature for Signature +where + SignatureSize: ArrayLength, +{ + fn from_bytes(bytes: &[u8]) -> Result { + Self::try_from(bytes) + } +} + +impl AsRef<[u8]> for Signature +where + SignatureSize: ArrayLength, +{ + fn as_ref(&self) -> &[u8] { + self.bytes.as_slice() + } +} + +impl Debug for Signature +where + SignatureSize: ArrayLength, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "ecdsa::Signature<{:?}>({:?})", + C::default(), + self.as_ref() + ) + } +} + +impl TryFrom<&[u8]> for Signature +where + SignatureSize: ArrayLength, +{ + type Error = Error; + + fn try_from(bytes: &[u8]) -> Result { + if bytes.len() == >::to_usize() { + Ok(Self { + bytes: GenericArray::clone_from_slice(bytes), + }) + } else { + Err(Error::new()) + } + } +} + +impl From> for Signature +where + C: Curve, + C::ScalarSize: Add + ArrayLength, + asn1::MaxSize: ArrayLength, + ::Output: Add + ArrayLength, +{ + fn from(doc: asn1::Document) -> Signature { + let mut bytes = SignatureBytes::::default(); + let scalar_size = C::ScalarSize::to_usize(); + let r_begin = scalar_size.checked_sub(doc.r().len()).unwrap(); + let s_begin = bytes.len().checked_sub(doc.s().len()).unwrap(); + + bytes[r_begin..scalar_size].copy_from_slice(doc.r()); + bytes[s_begin..].copy_from_slice(doc.s()); + Signature { bytes } + } +}