diff --git a/ecdsa/src/asn1_signature.rs b/ecdsa/src/asn1_signature.rs new file mode 100644 index 0000000..28a9e14 --- /dev/null +++ b/ecdsa/src/asn1_signature.rs @@ -0,0 +1,99 @@ +//! ASN.1 DER-encoded ECDSA signatures + +use crate::curve::Curve; +use core::{ + convert::{TryFrom, TryInto}, + fmt::{self, Debug}, + ops::Add, +}; +use generic_array::{typenum::Unsigned, ArrayLength, GenericArray}; +use signature::Error; + +/// 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) +type MaxOverhead = generic_array::typenum::U9; + +/// Maximum size of an ASN.1 DER encoded signature for the given elliptic curve. +// TODO(tarcieri): const generics +pub type MaxSize = <::Output as Add>::Output; + +/// ASN.1 DER-encoded ECDSA signature generic over elliptic curves. +pub struct Asn1Signature +where + ::Output: Add, + MaxSize: ArrayLength, +{ + /// ASN.1 DER-encoded signature data + bytes: GenericArray>, + + /// Length of the signature in bytes (DER is variable-width) + length: usize, +} + +impl signature::Signature for Asn1Signature +where + ::Output: Add, + MaxSize: ArrayLength, +{ + fn from_bytes(bytes: impl AsRef<[u8]>) -> Result { + bytes.as_ref().try_into() + } +} + +impl AsRef<[u8]> for Asn1Signature +where + ::Output: Add, + MaxSize: ArrayLength, +{ + fn as_ref(&self) -> &[u8] { + &self.bytes.as_slice()[..self.length] + } +} + +impl Debug for Asn1Signature +where + ::Output: Add, + MaxSize: ArrayLength, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Asn1Signature<{:?}> {{ bytes: {:?}) }}", + C::default(), + self.as_ref() + ) + } +} + +impl<'a, C: Curve> TryFrom<&'a [u8]> for Asn1Signature +where + ::Output: Add, + MaxSize: ArrayLength, +{ + 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 }) + } +} diff --git a/ecdsa/src/curve.rs b/ecdsa/src/curve.rs index 9c6eb80..96a1df0 100644 --- a/ecdsa/src/curve.rs +++ b/ecdsa/src/curve.rs @@ -6,12 +6,12 @@ pub mod secp256k1; pub use self::{nistp256::NistP256, nistp384::NistP384, secp256k1::Secp256k1}; -use core::fmt::Debug; +use core::{fmt::Debug, ops::Add}; /// 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; + type ScalarSize: Add; // `Add` impl from typenum used for doubling } diff --git a/ecdsa/src/curve/nistp256.rs b/ecdsa/src/curve/nistp256.rs index 0a04b30..fab451b 100644 --- a/ecdsa/src/curve/nistp256.rs +++ b/ecdsa/src/curve/nistp256.rs @@ -26,5 +26,8 @@ impl Curve for NistP256 { type ScalarSize = U32; } +/// ASN.1 DER encoded NIST P-256 ECDSA signature +pub type Asn1Signature = crate::Asn1Signature; + /// Fixed-sized (a.k.a. "raw") NIST P-256 ECDSA signature -pub type FixedSignature = crate::fixed_signature::FixedSignature; +pub type FixedSignature = crate::FixedSignature; diff --git a/ecdsa/src/curve/nistp384.rs b/ecdsa/src/curve/nistp384.rs index 764effe..4129649 100644 --- a/ecdsa/src/curve/nistp384.rs +++ b/ecdsa/src/curve/nistp384.rs @@ -27,5 +27,8 @@ impl Curve for NistP384 { type ScalarSize = U48; } +/// ASN.1 DER encoded NIST P-384 ECDSA signature +pub type Asn1Signature = crate::Asn1Signature; + /// Fixed-sized (a.k.a. "raw") NIST P-384 ECDSA signature -pub type FixedSignature = crate::fixed_signature::FixedSignature; +pub type FixedSignature = crate::FixedSignature; diff --git a/ecdsa/src/curve/secp256k1.rs b/ecdsa/src/curve/secp256k1.rs index a501745..8311e79 100644 --- a/ecdsa/src/curve/secp256k1.rs +++ b/ecdsa/src/curve/secp256k1.rs @@ -17,5 +17,8 @@ impl Curve for Secp256k1 { type ScalarSize = U32; } +/// ASN.1 DER encoded secp256k1 ECDSA signature +pub type Asn1Signature = crate::Asn1Signature; + /// Fixed-sized (a.k.a. "raw") secp256k1 ECDSA signature -pub type FixedSignature = crate::fixed_signature::FixedSignature; +pub type FixedSignature = crate::FixedSignature; diff --git a/ecdsa/src/fixed_signature.rs b/ecdsa/src/fixed_signature.rs index f46133f..89f719e 100644 --- a/ecdsa/src/fixed_signature.rs +++ b/ecdsa/src/fixed_signature.rs @@ -1,47 +1,41 @@ //! Fixed-sized (a.k.a. "raw") ECDSA signatures use crate::curve::Curve; -use core::{fmt, ops::Add}; +use core::{ + convert::{TryFrom, TryInto}, + fmt::{self, Debug}, + ops::Add, +}; use generic_array::{typenum::Unsigned, ArrayLength, GenericArray}; use signature::Error; -/// Size of a fixed sized signature: double that of the scalar size -// TODO(tarcieri): use typenum's `Double` op or switch to const generics -pub type Size = >::Output; +/// Size of a fixed sized signature for the given elliptic curve. +// TODO(tarcieri): const generics +pub type Size = ::Output; -/// Fixed-sized (a.k.a. "raw") ECDSA signatures: serialized as fixed-sized -/// big endian scalar values with no additional framing. +/// 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. #[derive(Clone, Eq, PartialEq)] -pub struct FixedSignature +pub struct FixedSignature where - C: Curve, - C::ScalarSize: Add, Size: ArrayLength, { bytes: GenericArray>, } -impl signature::Signature for FixedSignature +impl signature::Signature for FixedSignature where - C: Curve, - C::ScalarSize: Add, Size: ArrayLength, { fn from_bytes(bytes: impl AsRef<[u8]>) -> Result { - if bytes.as_ref().len() == >::to_usize() { - Ok(Self { - bytes: GenericArray::clone_from_slice(bytes.as_ref()), - }) - } else { - Err(Error::new()) - } + bytes.as_ref().try_into() } } -impl AsRef<[u8]> for FixedSignature +impl AsRef<[u8]> for FixedSignature where - C: Curve, - C::ScalarSize: Add, Size: ArrayLength, { fn as_ref(&self) -> &[u8] { @@ -49,10 +43,8 @@ where } } -impl fmt::Debug for FixedSignature +impl Debug for FixedSignature where - C: Curve, - C::ScalarSize: Add, Size: ArrayLength, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -64,3 +56,20 @@ where ) } } + +impl<'a, C: Curve> TryFrom<&'a [u8]> for FixedSignature +where + Size: ArrayLength, +{ + type Error = Error; + + fn try_from(bytes: &'a [u8]) -> Result { + if bytes.len() == >::to_usize() { + Ok(Self { + bytes: GenericArray::clone_from_slice(bytes), + }) + } else { + Err(Error::new()) + } + } +} diff --git a/ecdsa/src/lib.rs b/ecdsa/src/lib.rs index 6116ade..9414d14 100644 --- a/ecdsa/src/lib.rs +++ b/ecdsa/src/lib.rs @@ -11,7 +11,8 @@ pub use signature; +pub mod asn1_signature; pub mod curve; -mod fixed_signature; +pub mod fixed_signature; -pub use self::{curve::Curve, fixed_signature::FixedSignature}; +pub use self::{asn1_signature::Asn1Signature, curve::Curve, fixed_signature::FixedSignature}; diff --git a/ed25519/src/lib.rs b/ed25519/src/lib.rs index 2b36a17..a39f643 100644 --- a/ed25519/src/lib.rs +++ b/ed25519/src/lib.rs @@ -63,6 +63,23 @@ impl AsRef<[u8]> for Signature { } } +// can't derive `Debug`, `PartialEq`, or `Eq` below because core array types +// only have trait implementations for lengths 0..=32 +// TODO(tarcieri): derive `PartialEq` and `Eq` after const generics are available +impl Debug for Signature { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "ed25519::Signature({:?})", &self.0[..]) + } +} + +impl Eq for Signature {} + +impl PartialEq for Signature { + fn eq(&self, other: &Self) -> bool { + self.as_ref().eq(other.as_ref()) + } +} + impl From<[u8; SIGNATURE_LENGTH]> for Signature { fn from(bytes: [u8; SIGNATURE_LENGTH]) -> Signature { Signature(bytes) @@ -86,23 +103,6 @@ impl<'a> TryFrom<&'a [u8]> for Signature { } } -// can't derive `Debug`, `PartialEq`, or `Eq` below because core array types -// only have trait implementations for lengths 0..=32 -// TODO(tarcieri): derive `PartialEq` and `Eq` after const generics are available -impl Debug for Signature { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "ed25519::Signature({:?})", &self.0[..]) - } -} - -impl PartialEq for Signature { - fn eq(&self, other: &Self) -> bool { - self.as_ref().eq(other.as_ref()) - } -} - -impl Eq for Signature {} - #[cfg(feature = "serde")] impl Serialize for Signature { fn serialize(&self, serializer: S) -> Result {