ecdsa: Add Asn1Signature type

Support for ASN.1 DER-encoded signatures.

Uses a GenericArray to store the variable-width signature, calculating
maximum DER framing overhead using typenum's type-level arithmetic.
This commit is contained in:
Tony Arcieri
2019-10-28 12:40:00 -07:00
parent a244d36bfe
commit d4b3e706be
8 changed files with 167 additions and 49 deletions
+99
View File
@@ -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<ScalarSize> = <<ScalarSize as Add>::Output as Add<MaxOverhead>>::Output;
/// ASN.1 DER-encoded ECDSA signature generic over elliptic curves.
pub struct Asn1Signature<C: Curve>
where
<C::ScalarSize as Add>::Output: Add<MaxOverhead>,
MaxSize<C::ScalarSize>: ArrayLength<u8>,
{
/// ASN.1 DER-encoded signature data
bytes: GenericArray<u8, MaxSize<C::ScalarSize>>,
/// Length of the signature in bytes (DER is variable-width)
length: usize,
}
impl<C: Curve> signature::Signature for Asn1Signature<C>
where
<C::ScalarSize as Add>::Output: Add<MaxOverhead>,
MaxSize<C::ScalarSize>: ArrayLength<u8>,
{
fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self, Error> {
bytes.as_ref().try_into()
}
}
impl<C: Curve> AsRef<[u8]> for Asn1Signature<C>
where
<C::ScalarSize as Add>::Output: Add<MaxOverhead>,
MaxSize<C::ScalarSize>: ArrayLength<u8>,
{
fn as_ref(&self) -> &[u8] {
&self.bytes.as_slice()[..self.length]
}
}
impl<C: Curve> Debug for Asn1Signature<C>
where
<C::ScalarSize as Add>::Output: Add<MaxOverhead>,
MaxSize<C::ScalarSize>: ArrayLength<u8>,
{
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<C>
where
<C::ScalarSize as Add>::Output: Add<MaxOverhead>,
MaxSize<C::ScalarSize>: ArrayLength<u8>,
{
type Error = Error;
fn try_from(slice: &'a [u8]) -> Result<Self, Error> {
let length = slice.len();
// TODO: better validate signature is well-formed ASN.1 DER
if <MaxSize<C::ScalarSize>>::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 })
}
}
+2 -2
View File
@@ -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<Self>` impl from typenum used for doubling
}
+4 -1
View File
@@ -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<NistP256>;
/// Fixed-sized (a.k.a. "raw") NIST P-256 ECDSA signature
pub type FixedSignature = crate::fixed_signature::FixedSignature<NistP256>;
pub type FixedSignature = crate::FixedSignature<NistP256>;
+4 -1
View File
@@ -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<NistP384>;
/// Fixed-sized (a.k.a. "raw") NIST P-384 ECDSA signature
pub type FixedSignature = crate::fixed_signature::FixedSignature<NistP384>;
pub type FixedSignature = crate::FixedSignature<NistP384>;
+4 -1
View File
@@ -17,5 +17,8 @@ impl Curve for Secp256k1 {
type ScalarSize = U32;
}
/// ASN.1 DER encoded secp256k1 ECDSA signature
pub type Asn1Signature = crate::Asn1Signature<Secp256k1>;
/// Fixed-sized (a.k.a. "raw") secp256k1 ECDSA signature
pub type FixedSignature = crate::fixed_signature::FixedSignature<Secp256k1>;
pub type FixedSignature = crate::FixedSignature<Secp256k1>;
+34 -25
View File
@@ -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<ScalarSize> = <ScalarSize as Add<ScalarSize>>::Output;
/// Size of a fixed sized signature for the given elliptic curve.
// TODO(tarcieri): const generics
pub type Size<ScalarSize> = <ScalarSize as Add>::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<C>
pub struct FixedSignature<C: Curve>
where
C: Curve,
C::ScalarSize: Add<C::ScalarSize>,
Size<C::ScalarSize>: ArrayLength<u8>,
{
bytes: GenericArray<u8, Size<C::ScalarSize>>,
}
impl<C> signature::Signature for FixedSignature<C>
impl<C: Curve> signature::Signature for FixedSignature<C>
where
C: Curve,
C::ScalarSize: Add<C::ScalarSize>,
Size<C::ScalarSize>: ArrayLength<u8>,
{
fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self, Error> {
if bytes.as_ref().len() == <Size<C::ScalarSize>>::to_usize() {
Ok(Self {
bytes: GenericArray::clone_from_slice(bytes.as_ref()),
})
} else {
Err(Error::new())
}
bytes.as_ref().try_into()
}
}
impl<C> AsRef<[u8]> for FixedSignature<C>
impl<C: Curve> AsRef<[u8]> for FixedSignature<C>
where
C: Curve,
C::ScalarSize: Add<C::ScalarSize>,
Size<C::ScalarSize>: ArrayLength<u8>,
{
fn as_ref(&self) -> &[u8] {
@@ -49,10 +43,8 @@ where
}
}
impl<C> fmt::Debug for FixedSignature<C>
impl<C: Curve> Debug for FixedSignature<C>
where
C: Curve,
C::ScalarSize: Add<C::ScalarSize>,
Size<C::ScalarSize>: ArrayLength<u8>,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@@ -64,3 +56,20 @@ where
)
}
}
impl<'a, C: Curve> TryFrom<&'a [u8]> for FixedSignature<C>
where
Size<C::ScalarSize>: ArrayLength<u8>,
{
type Error = Error;
fn try_from(bytes: &'a [u8]) -> Result<Self, Error> {
if bytes.len() == <Size<C::ScalarSize>>::to_usize() {
Ok(Self {
bytes: GenericArray::clone_from_slice(bytes),
})
} else {
Err(Error::new())
}
}
}
+3 -2
View File
@@ -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};
+17 -17
View File
@@ -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<S: ser::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {