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.
This commit is contained in:
Tony Arcieri
2019-10-28 13:59:36 -07:00
parent b60c5f3ae4
commit 409cec8a07
5 changed files with 319 additions and 13 deletions
+15 -11
View File
@@ -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<ScalarSize> = <<ScalarSize as Add>::Output as Add<MaxOverhead>>
/// 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>,
<C::ScalarSize as Add>::Output: ArrayLength<u8> + Add<MaxOverhead>,
{
/// ASN.1 DER-encoded signature data
bytes: GenericArray<u8, MaxSize<C::ScalarSize>>,
pub(crate) bytes: GenericArray<u8, MaxSize<C::ScalarSize>>,
/// Length of the signature in bytes (DER is variable-width)
length: usize,
pub(crate) length: usize,
}
impl<C: Curve> signature::Signature for Asn1Signature<C>
where
<C::ScalarSize as Add>::Output: Add<MaxOverhead>,
MaxSize<C::ScalarSize>: ArrayLength<u8>,
<C::ScalarSize as Add>::Output: ArrayLength<u8> + Add<MaxOverhead>,
{
fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self, Error> {
bytes.as_ref().try_into()
@@ -54,8 +54,8 @@ where
impl<C: Curve> AsRef<[u8]> for Asn1Signature<C>
where
<C::ScalarSize as Add>::Output: Add<MaxOverhead>,
MaxSize<C::ScalarSize>: ArrayLength<u8>,
<C::ScalarSize as Add>::Output: ArrayLength<u8> + Add<MaxOverhead>,
{
fn as_ref(&self) -> &[u8] {
&self.bytes.as_slice()[..self.length]
@@ -64,8 +64,8 @@ where
impl<C: Curve> Debug for Asn1Signature<C>
where
<C::ScalarSize as Add>::Output: Add<MaxOverhead>,
MaxSize<C::ScalarSize>: ArrayLength<u8>,
<C::ScalarSize as Add>::Output: ArrayLength<u8> + Add<MaxOverhead>,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
@@ -79,21 +79,25 @@ where
impl<'a, C: Curve> TryFrom<&'a [u8]> for Asn1Signature<C>
where
<C::ScalarSize as Add>::Output: Add<MaxOverhead>,
MaxSize<C::ScalarSize>: ArrayLength<u8>,
<C::ScalarSize as Add>::Output: ArrayLength<u8> + Add<MaxOverhead>,
{
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 })
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)
}
}
+2 -1
View File
@@ -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<Self>` impl from typenum used for doubling
type ScalarSize: Unsigned + Add; // `Add<Self>` impl from typenum used for doubling
}
+9
View File
@@ -73,3 +73,12 @@ where
}
}
}
impl<C: Curve> From<GenericArray<u8, Size<C::ScalarSize>>> for FixedSignature<C>
where
Size<C::ScalarSize>: ArrayLength<u8>,
{
fn from(bytes: GenericArray<u8, Size<C::ScalarSize>>) -> Self {
Self { bytes }
}
}
+15 -1
View File
@@ -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};
+278
View File
@@ -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 <pornin@bolet.org>.
// Relicensed under Apache 2.0 + MIT (from original MIT) with permission.
//
// <https://www.bearssl.org/gitweb/?p=BearSSL;a=blob;f=src/ec/ecdsa_atr.c>
// <https://www.bearssl.org/gitweb/?p=BearSSL;a=blob;f=src/ec/ecdsa_rta.c>
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<C>,
}
impl<'a, C: Curve + 'a> ScalarPair<'a, C>
where
asn1_signature::MaxSize<C::ScalarSize>: ArrayLength<u8>,
<C::ScalarSize as Add>::Output: ArrayLength<u8> + Add<asn1_signature::MaxOverhead>,
{
/// 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<C>) -> Option<Self> {
// 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<C>) -> 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<C> {
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<C> {
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<C>> for FixedSignature<C>
where
asn1_signature::MaxSize<C::ScalarSize>: ArrayLength<u8>,
<C::ScalarSize as Add>::Output: ArrayLength<u8> + Add<asn1_signature::MaxOverhead>,
{
fn from(asn1_signature: &Asn1Signature<C>) -> FixedSignature<C> {
// 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<C>> for Asn1Signature<C>
where
asn1_signature::MaxSize<C::ScalarSize>: ArrayLength<u8>,
<C::ScalarSize as Add>::Output: ArrayLength<u8> + Add<asn1_signature::MaxOverhead>,
{
/// Parse `r` and `s` values from a fixed-width signature and reserialize
/// them as ASN.1 DER.
fn from(fixed_signature: &FixedSignature<C>) -> Self {
ScalarPair::from_fixed_signature(fixed_signature).to_asn1_signature()
}
}