710 lines
21 KiB
Rust
710 lines
21 KiB
Rust
#![no_std]
|
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
|
#![doc = include_str!("../README.md")]
|
|
#![doc(
|
|
html_logo_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg",
|
|
html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/media/8f1a9894/logo.svg"
|
|
)]
|
|
#![forbid(unsafe_code)]
|
|
#![warn(
|
|
clippy::cast_lossless,
|
|
clippy::cast_possible_truncation,
|
|
clippy::cast_possible_wrap,
|
|
clippy::cast_precision_loss,
|
|
clippy::cast_sign_loss,
|
|
clippy::checked_conversions,
|
|
clippy::implicit_saturating_sub,
|
|
clippy::panic,
|
|
clippy::panic_in_result_fn,
|
|
clippy::unwrap_used,
|
|
missing_docs,
|
|
rust_2018_idioms,
|
|
unused_lifetimes,
|
|
unused_qualifications
|
|
)]
|
|
|
|
//! ## `serde` support
|
|
//!
|
|
//! When the `serde` feature of this crate is enabled, `Serialize` and
|
|
//! `Deserialize` impls are provided for the [`Signature`] and [`VerifyingKey`]
|
|
//! types.
|
|
//!
|
|
//! Please see type-specific documentation for more information.
|
|
//!
|
|
//! ## Interop
|
|
//!
|
|
//! Any crates which provide an implementation of ECDSA for a particular
|
|
//! elliptic curve can leverage the types from this crate, along with the
|
|
//! [`k256`], [`p256`], and/or [`p384`] crates to expose ECDSA functionality in
|
|
//! a generic, interoperable way by leveraging the [`Signature`] type with in
|
|
//! conjunction with the [`signature::Signer`] and [`signature::Verifier`]
|
|
//! traits.
|
|
//!
|
|
//! For example, the [`ring-compat`] crate implements the [`signature::Signer`]
|
|
//! and [`signature::Verifier`] traits in conjunction with the
|
|
//! [`p256::ecdsa::Signature`] and [`p384::ecdsa::Signature`] types to
|
|
//! wrap the ECDSA implementations from [*ring*] in a generic, interoperable
|
|
//! API.
|
|
//!
|
|
//! [`k256`]: https://docs.rs/k256
|
|
//! [`p256`]: https://docs.rs/p256
|
|
//! [`p256::ecdsa::Signature`]: https://docs.rs/p256/latest/p256/ecdsa/type.Signature.html
|
|
//! [`p384`]: https://docs.rs/p384
|
|
//! [`p384::ecdsa::Signature`]: https://docs.rs/p384/latest/p384/ecdsa/type.Signature.html
|
|
//! [`ring-compat`]: https://docs.rs/ring-compat
|
|
//! [*ring*]: https://docs.rs/ring
|
|
|
|
#[cfg(feature = "alloc")]
|
|
extern crate alloc;
|
|
|
|
mod normalized;
|
|
mod recovery;
|
|
|
|
#[cfg(feature = "der")]
|
|
pub mod der;
|
|
#[cfg(feature = "dev")]
|
|
pub mod dev;
|
|
#[cfg(feature = "hazmat")]
|
|
pub mod hazmat;
|
|
#[cfg(feature = "signing")]
|
|
mod signing;
|
|
#[cfg(feature = "verifying")]
|
|
mod verifying;
|
|
|
|
pub use crate::{normalized::NormalizedSignature, recovery::RecoveryId};
|
|
|
|
// Re-export the `elliptic-curve` crate (and select types)
|
|
pub use elliptic_curve::{self, sec1::EncodedPoint, PrimeCurve};
|
|
|
|
// Re-export the `signature` crate (and select types)
|
|
pub use signature::{self, Error, Result, SignatureEncoding};
|
|
|
|
#[cfg(feature = "signing")]
|
|
pub use crate::signing::SigningKey;
|
|
#[cfg(feature = "verifying")]
|
|
pub use crate::verifying::VerifyingKey;
|
|
|
|
use core::{fmt, ops::Add};
|
|
use elliptic_curve::{
|
|
generic_array::{typenum::Unsigned, ArrayLength, GenericArray},
|
|
FieldBytes, FieldBytesSize, ScalarPrimitive,
|
|
};
|
|
|
|
#[cfg(feature = "alloc")]
|
|
use alloc::vec::Vec;
|
|
|
|
#[cfg(feature = "arithmetic")]
|
|
use {
|
|
core::str,
|
|
elliptic_curve::{scalar::IsHigh, CurveArithmetic, NonZeroScalar},
|
|
};
|
|
|
|
#[cfg(feature = "digest")]
|
|
use digest::{
|
|
const_oid::{AssociatedOid, ObjectIdentifier},
|
|
Digest,
|
|
};
|
|
|
|
#[cfg(feature = "pkcs8")]
|
|
use elliptic_curve::pkcs8::spki::{
|
|
der::AnyRef, AlgorithmIdentifierRef, AssociatedAlgorithmIdentifier,
|
|
};
|
|
|
|
#[cfg(feature = "serde")]
|
|
use serdect::serde::{de, ser, Deserialize, Serialize};
|
|
|
|
#[cfg(all(feature = "alloc", feature = "pkcs8"))]
|
|
use elliptic_curve::pkcs8::spki::{
|
|
self, AlgorithmIdentifierOwned, DynAssociatedAlgorithmIdentifier,
|
|
};
|
|
|
|
/// OID for ECDSA with SHA-224 digests.
|
|
///
|
|
/// ```text
|
|
/// ecdsa-with-SHA224 OBJECT IDENTIFIER ::= { iso(1) member-body(2)
|
|
/// us(840) ansi-X9-62(10045) signatures(4) ecdsa-with-SHA2(3) 1 }
|
|
/// ```
|
|
// TODO(tarcieri): use `ObjectIdentifier::push_arc` when const unwrap is stable
|
|
#[cfg(feature = "digest")]
|
|
pub const ECDSA_SHA224_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.4.3.1");
|
|
|
|
/// OID for ECDSA with SHA-256 digests.
|
|
///
|
|
/// ```text
|
|
/// ecdsa-with-SHA256 OBJECT IDENTIFIER ::= { iso(1) member-body(2)
|
|
/// us(840) ansi-X9-62(10045) signatures(4) ecdsa-with-SHA2(3) 2 }
|
|
/// ```
|
|
#[cfg(feature = "digest")]
|
|
pub const ECDSA_SHA256_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.4.3.2");
|
|
|
|
/// OID for ECDSA with SHA-384 digests.
|
|
///
|
|
/// ```text
|
|
/// ecdsa-with-SHA384 OBJECT IDENTIFIER ::= { iso(1) member-body(2)
|
|
/// us(840) ansi-X9-62(10045) signatures(4) ecdsa-with-SHA2(3) 3 }
|
|
/// ```
|
|
#[cfg(feature = "digest")]
|
|
pub const ECDSA_SHA384_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.4.3.3");
|
|
|
|
/// OID for ECDSA with SHA-512 digests.
|
|
///
|
|
/// ```text
|
|
/// ecdsa-with-SHA512 OBJECT IDENTIFIER ::= { iso(1) member-body(2)
|
|
/// us(840) ansi-X9-62(10045) signatures(4) ecdsa-with-SHA2(3) 4 }
|
|
/// ```
|
|
#[cfg(feature = "digest")]
|
|
pub const ECDSA_SHA512_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.4.3.4");
|
|
|
|
#[cfg(feature = "digest")]
|
|
const SHA224_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.4");
|
|
#[cfg(feature = "digest")]
|
|
const SHA256_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.1");
|
|
#[cfg(feature = "digest")]
|
|
const SHA384_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.2");
|
|
#[cfg(feature = "digest")]
|
|
const SHA512_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("2.16.840.1.101.3.4.2.3");
|
|
|
|
/// Size of a fixed sized signature for the given elliptic curve.
|
|
pub type SignatureSize<C> = <FieldBytesSize<C> as Add>::Output;
|
|
|
|
/// Fixed-size byte array containing an ECDSA signature
|
|
pub type SignatureBytes<C> = GenericArray<u8, SignatureSize<C>>;
|
|
|
|
/// ECDSA signature (fixed-size). Generic over elliptic curve types.
|
|
///
|
|
/// Serialized as fixed-sized big endian scalar values with no added framing:
|
|
///
|
|
/// - `r`: field element size for the given curve, big-endian
|
|
/// - `s`: field element size for the given curve, big-endian
|
|
///
|
|
/// Both `r` and `s` MUST be non-zero.
|
|
///
|
|
/// For example, in a curve with a 256-bit modulus like NIST P-256 or
|
|
/// secp256k1, `r` and `s` will both be 32-bytes and serialized as big endian,
|
|
/// resulting in a signature with a total of 64-bytes.
|
|
///
|
|
/// ASN.1 DER-encoded signatures also supported via the
|
|
/// [`Signature::from_der`] and [`Signature::to_der`] methods.
|
|
///
|
|
/// # `serde` support
|
|
///
|
|
/// When the `serde` feature of this crate is enabled, it provides support for
|
|
/// serializing and deserializing ECDSA signatures using the `Serialize` and
|
|
/// `Deserialize` traits.
|
|
///
|
|
/// The serialization uses a hexadecimal encoding when used with
|
|
/// "human readable" text formats, and a binary encoding otherwise.
|
|
#[derive(Clone, Eq, PartialEq)]
|
|
pub struct Signature<C: PrimeCurve> {
|
|
r: ScalarPrimitive<C>,
|
|
s: ScalarPrimitive<C>,
|
|
}
|
|
|
|
impl<C> Signature<C>
|
|
where
|
|
C: PrimeCurve,
|
|
SignatureSize<C>: ArrayLength<u8>,
|
|
{
|
|
/// Parse a signature from fixed-width bytes, i.e. 2 * the size of
|
|
/// [`FieldBytes`] for a particular curve.
|
|
///
|
|
/// # Returns
|
|
/// - `Ok(signature)` if the `r` and `s` components are both in the valid
|
|
/// range `1..n` when serialized as concatenated big endian integers.
|
|
/// - `Err(err)` if the `r` and/or `s` component of the signature is
|
|
/// out-of-range when interpreted as a big endian integer.
|
|
pub fn from_bytes(bytes: &SignatureBytes<C>) -> Result<Self> {
|
|
let (r_bytes, s_bytes) = bytes.split_at(C::FieldBytesSize::USIZE);
|
|
let r = FieldBytes::<C>::clone_from_slice(r_bytes);
|
|
let s = FieldBytes::<C>::clone_from_slice(s_bytes);
|
|
Self::from_scalars(r, s)
|
|
}
|
|
|
|
/// Parse a signature from a byte slice.
|
|
pub fn from_slice(slice: &[u8]) -> Result<Self> {
|
|
if slice.len() == SignatureSize::<C>::USIZE {
|
|
Self::from_bytes(SignatureBytes::<C>::from_slice(slice))
|
|
} else {
|
|
Err(Error::new())
|
|
}
|
|
}
|
|
|
|
/// Parse a signature from ASN.1 DER.
|
|
#[cfg(feature = "der")]
|
|
pub fn from_der(bytes: &[u8]) -> Result<Self>
|
|
where
|
|
der::MaxSize<C>: ArrayLength<u8>,
|
|
<FieldBytesSize<C> as Add>::Output: Add<der::MaxOverhead> + ArrayLength<u8>,
|
|
{
|
|
der::Signature::<C>::try_from(bytes).and_then(Self::try_from)
|
|
}
|
|
|
|
/// Create a [`Signature`] from the serialized `r` and `s` scalar values
|
|
/// which comprise the signature.
|
|
///
|
|
/// # Returns
|
|
/// - `Ok(signature)` if the `r` and `s` components are both in the valid
|
|
/// range `1..n` when serialized as concatenated big endian integers.
|
|
/// - `Err(err)` if the `r` and/or `s` component of the signature is
|
|
/// out-of-range when interpreted as a big endian integer.
|
|
pub fn from_scalars(r: impl Into<FieldBytes<C>>, s: impl Into<FieldBytes<C>>) -> Result<Self> {
|
|
let r = ScalarPrimitive::from_slice(&r.into()).map_err(|_| Error::new())?;
|
|
let s = ScalarPrimitive::from_slice(&s.into()).map_err(|_| Error::new())?;
|
|
|
|
if r.is_zero().into() || s.is_zero().into() {
|
|
return Err(Error::new());
|
|
}
|
|
|
|
Ok(Self { r, s })
|
|
}
|
|
|
|
/// Split the signature into its `r` and `s` components, represented as bytes.
|
|
pub fn split_bytes(&self) -> (FieldBytes<C>, FieldBytes<C>) {
|
|
(self.r.to_bytes(), self.s.to_bytes())
|
|
}
|
|
|
|
/// Serialize this signature as bytes.
|
|
pub fn to_bytes(&self) -> SignatureBytes<C> {
|
|
let mut bytes = SignatureBytes::<C>::default();
|
|
let (r_bytes, s_bytes) = bytes.split_at_mut(C::FieldBytesSize::USIZE);
|
|
r_bytes.copy_from_slice(&self.r.to_bytes());
|
|
s_bytes.copy_from_slice(&self.s.to_bytes());
|
|
bytes
|
|
}
|
|
|
|
/// Serialize this signature as ASN.1 DER.
|
|
#[cfg(feature = "der")]
|
|
pub fn to_der(&self) -> der::Signature<C>
|
|
where
|
|
der::MaxSize<C>: ArrayLength<u8>,
|
|
<FieldBytesSize<C> as Add>::Output: Add<der::MaxOverhead> + ArrayLength<u8>,
|
|
{
|
|
let (r, s) = self.split_bytes();
|
|
der::Signature::from_components(&r, &s).expect("DER encoding error")
|
|
}
|
|
|
|
/// Convert this signature into a byte vector.
|
|
#[cfg(feature = "alloc")]
|
|
pub fn to_vec(&self) -> Vec<u8> {
|
|
self.to_bytes().to_vec()
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "arithmetic")]
|
|
impl<C> Signature<C>
|
|
where
|
|
C: PrimeCurve + CurveArithmetic,
|
|
SignatureSize<C>: ArrayLength<u8>,
|
|
{
|
|
/// Get the `r` component of this signature
|
|
pub fn r(&self) -> NonZeroScalar<C> {
|
|
NonZeroScalar::new(self.r.into()).unwrap()
|
|
}
|
|
|
|
/// Get the `s` component of this signature
|
|
pub fn s(&self) -> NonZeroScalar<C> {
|
|
NonZeroScalar::new(self.s.into()).unwrap()
|
|
}
|
|
|
|
/// Split the signature into its `r` and `s` scalars.
|
|
pub fn split_scalars(&self) -> (NonZeroScalar<C>, NonZeroScalar<C>) {
|
|
(self.r(), self.s())
|
|
}
|
|
|
|
/// Normalize signature into "low S" form as described in
|
|
/// [BIP 0062: Dealing with Malleability][1].
|
|
///
|
|
/// [1]: https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki
|
|
pub fn normalize_s(&self) -> Option<Self> {
|
|
let s = self.s();
|
|
|
|
if s.is_high().into() {
|
|
let mut result = self.clone();
|
|
result.s = ScalarPrimitive::from(-s);
|
|
Some(result)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<C> Copy for Signature<C>
|
|
where
|
|
C: PrimeCurve,
|
|
SignatureSize<C>: ArrayLength<u8>,
|
|
<SignatureSize<C> as ArrayLength<u8>>::ArrayType: Copy,
|
|
{
|
|
}
|
|
|
|
impl<C> From<Signature<C>> for SignatureBytes<C>
|
|
where
|
|
C: PrimeCurve,
|
|
SignatureSize<C>: ArrayLength<u8>,
|
|
{
|
|
fn from(signature: Signature<C>) -> SignatureBytes<C> {
|
|
signature.to_bytes()
|
|
}
|
|
}
|
|
|
|
impl<C> SignatureEncoding for Signature<C>
|
|
where
|
|
C: PrimeCurve,
|
|
SignatureSize<C>: ArrayLength<u8>,
|
|
{
|
|
type Repr = SignatureBytes<C>;
|
|
}
|
|
|
|
impl<C> TryFrom<&[u8]> for Signature<C>
|
|
where
|
|
C: PrimeCurve,
|
|
SignatureSize<C>: ArrayLength<u8>,
|
|
{
|
|
type Error = Error;
|
|
|
|
fn try_from(slice: &[u8]) -> Result<Self> {
|
|
Self::from_slice(slice)
|
|
}
|
|
}
|
|
|
|
impl<C> fmt::Debug for Signature<C>
|
|
where
|
|
C: PrimeCurve,
|
|
SignatureSize<C>: ArrayLength<u8>,
|
|
{
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "ecdsa::Signature<{:?}>(", C::default())?;
|
|
|
|
for byte in self.to_bytes() {
|
|
write!(f, "{:02X}", byte)?;
|
|
}
|
|
|
|
write!(f, ")")
|
|
}
|
|
}
|
|
|
|
impl<C> fmt::Display for Signature<C>
|
|
where
|
|
C: PrimeCurve,
|
|
SignatureSize<C>: ArrayLength<u8>,
|
|
{
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "{:X}", self)
|
|
}
|
|
}
|
|
|
|
impl<C> fmt::LowerHex for Signature<C>
|
|
where
|
|
C: PrimeCurve,
|
|
SignatureSize<C>: ArrayLength<u8>,
|
|
{
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
for byte in self.to_bytes() {
|
|
write!(f, "{:02x}", byte)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl<C> fmt::UpperHex for Signature<C>
|
|
where
|
|
C: PrimeCurve,
|
|
SignatureSize<C>: ArrayLength<u8>,
|
|
{
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
for byte in self.to_bytes() {
|
|
write!(f, "{:02X}", byte)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "arithmetic")]
|
|
impl<C> str::FromStr for Signature<C>
|
|
where
|
|
C: PrimeCurve + CurveArithmetic,
|
|
SignatureSize<C>: ArrayLength<u8>,
|
|
{
|
|
type Err = Error;
|
|
|
|
fn from_str(hex: &str) -> Result<Self> {
|
|
if hex.as_bytes().len() != C::FieldBytesSize::USIZE * 4 {
|
|
return Err(Error::new());
|
|
}
|
|
|
|
// This check is mainly to ensure `hex.split_at` below won't panic
|
|
if !hex
|
|
.as_bytes()
|
|
.iter()
|
|
.all(|&byte| matches!(byte, b'0'..=b'9' | b'a'..=b'z' | b'A'..=b'Z'))
|
|
{
|
|
return Err(Error::new());
|
|
}
|
|
|
|
let (r_hex, s_hex) = hex.split_at(C::FieldBytesSize::USIZE * 2);
|
|
|
|
let r = r_hex
|
|
.parse::<NonZeroScalar<C>>()
|
|
.map_err(|_| Error::new())?;
|
|
|
|
let s = s_hex
|
|
.parse::<NonZeroScalar<C>>()
|
|
.map_err(|_| Error::new())?;
|
|
|
|
Self::from_scalars(r, s)
|
|
}
|
|
}
|
|
|
|
/// ECDSA [`ObjectIdentifier`] which identifies the digest used by default
|
|
/// with the `Signer` and `Verifier` traits.
|
|
///
|
|
/// To support non-default digest algorithms, use the [`SignatureWithOid`]
|
|
/// type instead.
|
|
#[cfg(all(feature = "digest", feature = "hazmat"))]
|
|
impl<C> AssociatedOid for Signature<C>
|
|
where
|
|
C: hazmat::DigestPrimitive,
|
|
C::Digest: AssociatedOid,
|
|
{
|
|
const OID: ObjectIdentifier = match ecdsa_oid_for_digest(C::Digest::OID) {
|
|
Some(oid) => oid,
|
|
None => panic!("no RFC5758 ECDSA OID defined for DigestPrimitive::Digest"),
|
|
};
|
|
}
|
|
|
|
/// ECDSA `AlgorithmIdentifier` which identifies the digest used by default
|
|
/// with the `Signer` and `Verifier` traits.
|
|
#[cfg(feature = "pkcs8")]
|
|
impl<C> AssociatedAlgorithmIdentifier for Signature<C>
|
|
where
|
|
C: PrimeCurve,
|
|
Self: AssociatedOid,
|
|
{
|
|
type Params = AnyRef<'static>;
|
|
|
|
const ALGORITHM_IDENTIFIER: AlgorithmIdentifierRef<'static> = AlgorithmIdentifierRef {
|
|
oid: Self::OID,
|
|
parameters: None,
|
|
};
|
|
}
|
|
|
|
#[cfg(feature = "serde")]
|
|
impl<C> Serialize for Signature<C>
|
|
where
|
|
C: PrimeCurve,
|
|
SignatureSize<C>: ArrayLength<u8>,
|
|
{
|
|
fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
|
|
where
|
|
S: ser::Serializer,
|
|
{
|
|
serdect::array::serialize_hex_upper_or_bin(&self.to_bytes(), serializer)
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "serde")]
|
|
impl<'de, C> Deserialize<'de> for Signature<C>
|
|
where
|
|
C: PrimeCurve,
|
|
SignatureSize<C>: ArrayLength<u8>,
|
|
{
|
|
fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
|
|
where
|
|
D: de::Deserializer<'de>,
|
|
{
|
|
let mut bytes = SignatureBytes::<C>::default();
|
|
serdect::array::deserialize_hex_or_bin(&mut bytes, deserializer)?;
|
|
Self::try_from(bytes.as_slice()).map_err(de::Error::custom)
|
|
}
|
|
}
|
|
|
|
/// An extended [`Signature`] type which is parameterized by an
|
|
/// `ObjectIdentifier` which identifies the ECDSA variant used by a
|
|
/// particular signature.
|
|
///
|
|
/// Valid `ObjectIdentifiers` are defined in [RFC5758 § 3.2]:
|
|
///
|
|
/// - SHA-224: [`ECDSA_SHA224_OID`] (1.2.840.10045.4.3.1)
|
|
/// - SHA-256: [`ECDSA_SHA256_OID`] (1.2.840.10045.4.3.2)
|
|
/// - SHA-384: [`ECDSA_SHA384_OID`] (1.2.840.10045.4.3.3)
|
|
/// - SHA-512: [`ECDSA_SHA512_OID`] (1.2.840.10045.4.3.4)
|
|
///
|
|
/// [RFC5758 § 3.2]: https://www.rfc-editor.org/rfc/rfc5758#section-3.2
|
|
#[cfg(feature = "digest")]
|
|
#[derive(Clone, Eq, PartialEq)]
|
|
pub struct SignatureWithOid<C: PrimeCurve> {
|
|
/// Inner signature type.
|
|
signature: Signature<C>,
|
|
|
|
/// OID which identifies the ECDSA variant used.
|
|
///
|
|
/// MUST be one of the ECDSA algorithm variants as defined in RFC5758.
|
|
///
|
|
/// These OIDs begin with `1.2.840.10045.4`.
|
|
oid: ObjectIdentifier,
|
|
}
|
|
|
|
#[cfg(feature = "digest")]
|
|
impl<C> SignatureWithOid<C>
|
|
where
|
|
C: PrimeCurve,
|
|
{
|
|
/// Create a new signature with an explicitly provided OID.
|
|
///
|
|
/// OID must begin with `1.2.840.10045.4`, the [RFC5758] OID prefix for
|
|
/// ECDSA variants.
|
|
///
|
|
/// [RFC5758]: https://www.rfc-editor.org/rfc/rfc5758#section-3.2
|
|
pub fn new(signature: Signature<C>, oid: ObjectIdentifier) -> Result<Self> {
|
|
// TODO(tarcieri): use `ObjectIdentifier::starts_with`
|
|
for (arc1, arc2) in ObjectIdentifier::new_unwrap("1.2.840.10045.4.3")
|
|
.arcs()
|
|
.zip(oid.arcs())
|
|
{
|
|
if arc1 != arc2 {
|
|
return Err(Error::new());
|
|
}
|
|
}
|
|
|
|
Ok(Self { signature, oid })
|
|
}
|
|
|
|
/// Create a new signature, determining the OID from the given digest.
|
|
///
|
|
/// Supports SHA-2 family digests as enumerated in [RFC5758 § 3.2], i.e.
|
|
/// SHA-224, SHA-256, SHA-384, or SHA-512.
|
|
///
|
|
/// [RFC5758 § 3.2]: https://www.rfc-editor.org/rfc/rfc5758#section-3.2
|
|
pub fn new_with_digest<D>(signature: Signature<C>) -> Result<Self>
|
|
where
|
|
D: AssociatedOid + Digest,
|
|
{
|
|
let oid = ecdsa_oid_for_digest(D::OID).ok_or_else(Error::new)?;
|
|
Ok(Self { signature, oid })
|
|
}
|
|
|
|
/// Parse a signature from fixed-with bytes.
|
|
pub fn from_bytes_with_digest<D>(bytes: &SignatureBytes<C>) -> Result<Self>
|
|
where
|
|
D: AssociatedOid + Digest,
|
|
SignatureSize<C>: ArrayLength<u8>,
|
|
{
|
|
Self::new_with_digest::<D>(Signature::<C>::from_bytes(bytes)?)
|
|
}
|
|
|
|
/// Parse a signature from a byte slice.
|
|
pub fn from_slice_with_digest<D>(slice: &[u8]) -> Result<Self>
|
|
where
|
|
D: AssociatedOid + Digest,
|
|
SignatureSize<C>: ArrayLength<u8>,
|
|
{
|
|
Self::new_with_digest::<D>(Signature::<C>::from_slice(slice)?)
|
|
}
|
|
|
|
/// Get the fixed-width ECDSA signature.
|
|
pub fn signature(&self) -> &Signature<C> {
|
|
&self.signature
|
|
}
|
|
|
|
/// Get the ECDSA OID for this signature.
|
|
pub fn oid(&self) -> ObjectIdentifier {
|
|
self.oid
|
|
}
|
|
|
|
/// Serialize this signature as bytes.
|
|
pub fn to_bytes(&self) -> SignatureBytes<C>
|
|
where
|
|
SignatureSize<C>: ArrayLength<u8>,
|
|
{
|
|
self.signature.to_bytes()
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "digest")]
|
|
impl<C> Copy for SignatureWithOid<C>
|
|
where
|
|
C: PrimeCurve,
|
|
SignatureSize<C>: ArrayLength<u8>,
|
|
<SignatureSize<C> as ArrayLength<u8>>::ArrayType: Copy,
|
|
{
|
|
}
|
|
|
|
#[cfg(feature = "digest")]
|
|
impl<C> From<SignatureWithOid<C>> for Signature<C>
|
|
where
|
|
C: PrimeCurve,
|
|
{
|
|
fn from(sig: SignatureWithOid<C>) -> Signature<C> {
|
|
sig.signature
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "digest")]
|
|
impl<C> From<SignatureWithOid<C>> for SignatureBytes<C>
|
|
where
|
|
C: PrimeCurve,
|
|
SignatureSize<C>: ArrayLength<u8>,
|
|
{
|
|
fn from(signature: SignatureWithOid<C>) -> SignatureBytes<C> {
|
|
signature.to_bytes()
|
|
}
|
|
}
|
|
|
|
/// NOTE: this implementation assumes the default digest for the given elliptic
|
|
/// curve as defined by [`hazmat::DigestPrimitive`].
|
|
///
|
|
/// When working with alternative digests, you will need to use e.g.
|
|
/// [`SignatureWithOid::new_with_digest`].
|
|
#[cfg(all(feature = "digest", feature = "hazmat"))]
|
|
impl<C> SignatureEncoding for SignatureWithOid<C>
|
|
where
|
|
C: hazmat::DigestPrimitive,
|
|
C::Digest: AssociatedOid,
|
|
SignatureSize<C>: ArrayLength<u8>,
|
|
{
|
|
type Repr = SignatureBytes<C>;
|
|
}
|
|
|
|
/// NOTE: this implementation assumes the default digest for the given elliptic
|
|
/// curve as defined by [`hazmat::DigestPrimitive`].
|
|
///
|
|
/// When working with alternative digests, you will need to use e.g.
|
|
/// [`SignatureWithOid::new_with_digest`].
|
|
#[cfg(all(feature = "digest", feature = "hazmat"))]
|
|
impl<C> TryFrom<&[u8]> for SignatureWithOid<C>
|
|
where
|
|
C: hazmat::DigestPrimitive,
|
|
C::Digest: AssociatedOid,
|
|
SignatureSize<C>: ArrayLength<u8>,
|
|
{
|
|
type Error = Error;
|
|
|
|
fn try_from(slice: &[u8]) -> Result<Self> {
|
|
Self::new(Signature::<C>::from_slice(slice)?, C::Digest::OID)
|
|
}
|
|
}
|
|
|
|
#[cfg(all(feature = "alloc", feature = "pkcs8"))]
|
|
impl<C> DynAssociatedAlgorithmIdentifier for SignatureWithOid<C>
|
|
where
|
|
C: PrimeCurve,
|
|
{
|
|
fn algorithm_identifier(&self) -> spki::Result<AlgorithmIdentifierOwned> {
|
|
Ok(AlgorithmIdentifierOwned {
|
|
oid: self.oid,
|
|
parameters: None,
|
|
})
|
|
}
|
|
}
|
|
|
|
/// Get the ECDSA OID for a given digest OID.
|
|
#[cfg(feature = "digest")]
|
|
const fn ecdsa_oid_for_digest(digest_oid: ObjectIdentifier) -> Option<ObjectIdentifier> {
|
|
match digest_oid {
|
|
SHA224_OID => Some(ECDSA_SHA224_OID),
|
|
SHA256_OID => Some(ECDSA_SHA256_OID),
|
|
SHA384_OID => Some(ECDSA_SHA384_OID),
|
|
SHA512_OID => Some(ECDSA_SHA512_OID),
|
|
_ => None,
|
|
}
|
|
}
|