From a2c7fa24193aae509dfda9fa7729ea2788f36a94 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Sun, 21 Nov 2021 11:33:16 -0700 Subject: [PATCH] rfc6979: initial crate (#409) Splits out the RFC6979 deterministic nonce generation so it can (eventually) be used for DSA as well as ECDSA. The implementation is generic over a `Digest` as well as a `UInt` type as defined by `crypto-bigint`. It's also `no_std` friendly and avoids making heap allocations. --- Cargo.lock | 17 ++++-- Cargo.toml | 6 +- ecdsa/Cargo.toml | 14 ++--- ecdsa/README.md | 10 +++- ecdsa/src/lib.rs | 12 ++-- ecdsa/src/rfc6979.rs | 128 ------------------------------------------- ecdsa/src/sign.rs | 30 ++++++++-- ed25519/README.md | 8 ++- rfc6979/CHANGELOG.md | 5 ++ rfc6979/Cargo.toml | 24 ++++++++ rfc6979/README.md | 55 +++++++++++++++++++ rfc6979/src/lib.rs | 113 ++++++++++++++++++++++++++++++++++++++ rfc6979/tests/lib.rs | 14 +++++ 13 files changed, 280 insertions(+), 156 deletions(-) delete mode 100644 ecdsa/src/rfc6979.rs create mode 100644 rfc6979/CHANGELOG.md create mode 100644 rfc6979/Cargo.toml create mode 100644 rfc6979/README.md create mode 100644 rfc6979/src/lib.rs create mode 100644 rfc6979/tests/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 333c242..b747c4c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -161,9 +161,9 @@ name = "ecdsa" version = "0.13.0-pre" dependencies = [ "der 0.5.1", - "elliptic-curve 0.11.0", + "elliptic-curve 0.11.1", "hex-literal", - "hmac", + "rfc6979", "sha2", "signature", ] @@ -221,9 +221,9 @@ dependencies = [ [[package]] name = "elliptic-curve" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be5ff748dcb04505ce82b6447d4bad4ef9cf653a1d811bed2b04083127a44c3f" +checksum = "f73d21281779c2c22cade2a4ab34eee34271869942546a364f7d552d30c62434" dependencies = [ "crypto-bigint 0.3.2", "der 0.5.1", @@ -473,6 +473,15 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rfc6979" +version = "0.0.0" +dependencies = [ + "crypto-bigint 0.3.2", + "hmac", + "zeroize", +] + [[package]] name = "ring" version = "0.16.20" diff --git a/Cargo.toml b/Cargo.toml index 8337261..e5e84f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,7 @@ [workspace] resolver = "2" -members = ["ecdsa", "ed25519"] +members = [ + "ecdsa", + "ed25519", + "rfc6979" +] diff --git a/ecdsa/Cargo.toml b/ecdsa/Cargo.toml index 1b8238f..c277aa4 100644 --- a/ecdsa/Cargo.toml +++ b/ecdsa/Cargo.toml @@ -2,12 +2,12 @@ name = "ecdsa" version = "0.13.0-pre" # Also update html_root_url in lib.rs when bumping this description = """ -Signature and elliptic curve types providing interoperable support for the -Elliptic Curve Digital Signature Algorithm (ECDSA) +Pure Rust implementation of the Elliptic Curve Digital Signature Algorithm +(ECDSA) as specified in FIPS 186-4 (Digital Signature Standard) """ authors = ["RustCrypto Developers"] license = "Apache-2.0 OR MIT" -repository = "https://github.com/RustCrypto/signatures" +repository = "https://github.com/RustCrypto/signatures/tree/master/ecdsa" readme = "README.md" categories = ["cryptography", "no-std"] keywords = ["crypto", "ecc", "nist", "secp256k1", "signature"] @@ -15,12 +15,12 @@ edition = "2021" rust-version = "1.56" [dependencies] -elliptic-curve = { version = "0.11", default-features = false, features = ["sec1"] } -hmac = { version = "0.11", optional = true, default-features = false } -signature = { version = ">= 1.3.1", default-features = false, features = ["rand-preview"] } +elliptic-curve = { version = "0.11.1", default-features = false, features = ["sec1"] } +signature = { version = ">= 1.3.1, <1.5", default-features = false, features = ["rand-preview"] } # optional dependencies der = { version = "0.5", optional = true } +rfc6979 = { version = "0", optional = true, path = "../rfc6979" } [dev-dependencies] elliptic-curve = { version = "0.11", default-features = false, features = ["dev"] } @@ -37,7 +37,7 @@ hazmat = [] pkcs8 = ["elliptic-curve/pkcs8", "der"] pem = ["elliptic-curve/pem", "pkcs8"] serde = ["elliptic-curve/serde"] -sign = ["arithmetic", "digest", "hazmat", "hmac"] +sign = ["arithmetic", "digest", "hazmat", "rfc6979"] std = ["alloc", "elliptic-curve/std", "signature/std"] verify = ["arithmetic", "digest", "hazmat"] diff --git a/ecdsa/README.md b/ecdsa/README.md index f31cc81..1b98e68 100644 --- a/ecdsa/README.md +++ b/ecdsa/README.md @@ -1,4 +1,4 @@ -# RustCrypto: ECDSA +# [RustCrypto]: ECDSA [![crate][crate-image]][crate-link] [![Docs][docs-image]][docs-link] @@ -57,8 +57,12 @@ dual licensed as above, without any additional terms or conditions. [rustc-image]: https://img.shields.io/badge/rustc-1.56+-blue.svg [chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg [chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260048-signatures -[build-image]: https://github.com/RustCrypto/signatures/workflows/ecdsa/badge.svg?branch=master&event=push -[build-link]: https://github.com/RustCrypto/signatures/actions?query=workflow%3Aecdsa +[build-image]: https://github.com/RustCrypto/signatures/actions/workflows/ecdsa.yml/badge.svg +[build-link]: https://github.com/RustCrypto/signatures/actions/workflows/ecdsa.yml + +[//]: # (links) + +[RustCrypto]: https://github.com/RustCrypto [//]: # (footnotes) diff --git a/ecdsa/src/lib.rs b/ecdsa/src/lib.rs index 73005e8..4b20db3 100644 --- a/ecdsa/src/lib.rs +++ b/ecdsa/src/lib.rs @@ -69,10 +69,6 @@ pub mod dev; #[cfg_attr(docsrs, doc(cfg(feature = "hazmat")))] pub mod hazmat; -#[cfg(feature = "sign")] -#[cfg_attr(docsrs, doc(cfg(feature = "sign")))] -pub mod rfc6979; - #[cfg(feature = "sign")] mod sign; @@ -87,13 +83,17 @@ pub use elliptic_curve::{self, sec1::EncodedPoint, PrimeCurve}; // Re-export the `signature` crate (and select types) pub use signature::{self, Error, Result}; +#[cfg(feature = "rfc6979")] +#[cfg_attr(docsrs, doc(cfg(feature = "rfc6979")))] +pub use rfc6979; + #[cfg(feature = "sign")] #[cfg_attr(docsrs, doc(cfg(feature = "sign")))] -pub use sign::SigningKey; +pub use crate::sign::SigningKey; #[cfg(feature = "verify")] #[cfg_attr(docsrs, doc(cfg(feature = "verify")))] -pub use verify::VerifyingKey; +pub use crate::verify::VerifyingKey; use core::{ fmt::{self, Debug}, diff --git a/ecdsa/src/rfc6979.rs b/ecdsa/src/rfc6979.rs deleted file mode 100644 index 341a3a9..0000000 --- a/ecdsa/src/rfc6979.rs +++ /dev/null @@ -1,128 +0,0 @@ -//! Support for computing deterministic ECDSA ephemeral scalar (`k`). -//! -//! Implementation of the algorithm described in RFC 6979 (Section 3.2): -//! - -use elliptic_curve::{ - generic_array::GenericArray, - group::ff::PrimeField, - ops::{Invert, Reduce}, - zeroize::{Zeroize, Zeroizing}, - FieldBytes, FieldSize, NonZeroScalar, PrimeCurve, ProjectiveArithmetic, Scalar, -}; -use hmac::{Hmac, Mac, NewMac}; -use signature::digest::{BlockInput, FixedOutput, Reset, Update}; - -/// Generate ephemeral scalar `k` from the secret scalar and a digest of the -/// input message. -pub fn generate_k( - secret_scalar: &NonZeroScalar, - msg_digest: D, - additional_data: &[u8], -) -> Zeroizing> -where - C: PrimeCurve + ProjectiveArithmetic, - D: FixedOutput> + BlockInput + Clone + Default + Reset + Update, - Scalar: Invert> + Reduce, -{ - let mut x = secret_scalar.to_repr(); - let h1 = Scalar::::from_be_bytes_reduced(msg_digest.finalize_fixed()).to_repr(); - let mut hmac_drbg = HmacDrbg::::new(&x, &h1, additional_data); - x.zeroize(); - - loop { - let mut tmp = FieldBytes::::default(); - hmac_drbg.generate_into(&mut tmp); - if let Some(k) = NonZeroScalar::from_repr(tmp).into() { - return Zeroizing::new(k); - } - } -} - -/// Internal implementation of `HMAC_DRBG` as described in NIST SP800-90A: -/// -/// -/// This is a HMAC-based deterministic random bit generator used internally -/// to compute a deterministic ECDSA ephemeral scalar `k`. -// TODO(tarcieri): use `hmac-drbg` crate when sorpaas/rust-hmac-drbg#3 is merged -struct HmacDrbg -where - D: BlockInput + FixedOutput + Clone + Default + Reset + Update, -{ - /// HMAC key `K` (see RFC 6979 Section 3.2.c) - k: Hmac, - - /// Chaining value `V` (see RFC 6979 Section 3.2.c) - v: GenericArray, -} - -impl HmacDrbg -where - D: BlockInput + FixedOutput + Clone + Default + Reset + Update, -{ - /// Initialize `HMAC_DRBG` - pub fn new(entropy_input: &[u8], nonce: &[u8], additional_data: &[u8]) -> Self { - let mut k = Hmac::new(&Default::default()); - let mut v = GenericArray::default(); - - for b in &mut v { - *b = 0x01; - } - - for i in 0..=1 { - k.update(&v); - k.update(&[i]); - k.update(entropy_input); - k.update(nonce); - k.update(additional_data); - k = Hmac::new_from_slice(&k.finalize().into_bytes()).expect("HMAC error"); - - // Steps 3.2.e,g: v = HMAC_k(v) - k.update(&v); - v = k.finalize_reset().into_bytes(); - } - - Self { k, v } - } - - /// Get the next `HMAC_DRBG` output - pub fn generate_into(&mut self, out: &mut [u8]) { - for out_chunk in out.chunks_mut(self.v.len()) { - self.k.update(&self.v); - self.v = self.k.finalize_reset().into_bytes(); - out_chunk.copy_from_slice(&self.v[..out_chunk.len()]); - } - - self.k.update(&self.v); - self.k.update(&[0x00]); - self.k = Hmac::new_from_slice(&self.k.finalize_reset().into_bytes()).expect("HMAC error"); - self.k.update(&self.v); - self.v = self.k.finalize_reset().into_bytes(); - } -} - -#[cfg(test)] -mod tests { - use super::generate_k; - use elliptic_curve::{dev::NonZeroScalar, group::ff::PrimeField}; - use hex_literal::hex; - use sha2::{Digest, Sha256}; - - /// Test vector from RFC 6979 Appendix 2.5 (NIST P-256 + SHA-256) - /// - #[test] - fn appendix_2_5_test_vector() { - let x = NonZeroScalar::from_repr( - hex!("c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721").into(), - ) - .unwrap(); - - let digest = Sha256::new().chain("sample"); - let k = generate_k(&x, digest, &[]); - - assert_eq!( - k.to_repr().as_slice(), - &hex!("a6e3c57dd01abe90086538398355dd4c3b17aa873382b0f24d6129493d8aad60")[..] - ); - } -} diff --git a/ecdsa/src/sign.rs b/ecdsa/src/sign.rs index c15a636..0054b48 100644 --- a/ecdsa/src/sign.rs +++ b/ecdsa/src/sign.rs @@ -13,7 +13,9 @@ use elliptic_curve::{ ops::{Invert, Reduce}, subtle::{Choice, ConstantTimeEq}, zeroize::Zeroize, - FieldBytes, FieldSize, NonZeroScalar, PrimeCurve, ProjectiveArithmetic, Scalar, SecretKey, + zeroize::Zeroizing, + FieldBytes, FieldSize, NonZeroScalar, PrimeCurve, ProjectiveArithmetic, Scalar, ScalarCore, + SecretKey, }; use signature::{ digest::{BlockInput, Digest, FixedOutput, Reset, Update}, @@ -176,8 +178,17 @@ where /// computed using the algorithm described in RFC 6979 (Section 3.2): /// fn try_sign_digest(&self, msg_digest: D) -> Result> { - let k = rfc6979::generate_k(&self.inner, msg_digest.clone(), &[]); + let x = Zeroizing::new(ScalarCore::::from(self.inner)); let msg_scalar = Scalar::::from_be_bytes_reduced(msg_digest.finalize_fixed()); + let k = Zeroizing::new( + NonZeroScalar::::from_uint(*rfc6979::generate_k::( + x.as_uint(), + &C::ORDER, + &msg_scalar.to_repr(), + &[], + )) + .unwrap(), + ); Ok(self.inner.try_sign_prehashed(**k, msg_scalar)?.0) } } @@ -209,11 +220,20 @@ where mut rng: impl CryptoRng + RngCore, msg_digest: D, ) -> Result> { - let mut added_entropy = FieldBytes::::default(); - rng.fill_bytes(&mut added_entropy); + let mut entropy = FieldBytes::::default(); + rng.fill_bytes(&mut entropy); - let k = rfc6979::generate_k(&self.inner, msg_digest.clone(), &added_entropy); + let x = Zeroizing::new(ScalarCore::::from(self.inner)); let msg_scalar = Scalar::::from_be_bytes_reduced(msg_digest.finalize_fixed()); + let k = Zeroizing::new( + NonZeroScalar::::from_uint(*rfc6979::generate_k::( + x.as_uint(), + &C::ORDER, + &msg_scalar.to_repr(), + &entropy, + )) + .unwrap(), + ); Ok(self.inner.try_sign_prehashed(**k, msg_scalar)?.0) } } diff --git a/ed25519/README.md b/ed25519/README.md index adbfa73..d4ce8da 100644 --- a/ed25519/README.md +++ b/ed25519/README.md @@ -1,4 +1,4 @@ -# RustCrypto: Ed25519 +# [RustCrypto]: Ed25519 [![crate][crate-image]][crate-link] [![Docs][docs-image]][docs-link] @@ -57,7 +57,11 @@ dual licensed as above, without any additional terms or conditions. [build-image]: https://github.com/RustCrypto/signatures/workflows/ed25519/badge.svg?branch=master&event=push [build-link]: https://github.com/RustCrypto/signatures/actions?query=workflow%3Aed25519 -[//]: # (general links) +[//]: # (links) + +[RustCrypto]: https://github.com/RustCrypto + +[//]: # (footnotes) [1]: https://en.wikipedia.org/wiki/EdDSA [2]: https://tools.ietf.org/html/rfc8032 diff --git a/rfc6979/CHANGELOG.md b/rfc6979/CHANGELOG.md new file mode 100644 index 0000000..d6637e0 --- /dev/null +++ b/rfc6979/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). diff --git a/rfc6979/Cargo.toml b/rfc6979/Cargo.toml new file mode 100644 index 0000000..d7ace92 --- /dev/null +++ b/rfc6979/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "rfc6979" +version = "0.0.0" # Also update html_root_url in lib.rs when bumping this +description = """ +Pure Rust implementation of RFC6979: Deterministic Usage of the +Digital Signature Algorithm (DSA) and Elliptic Curve Digital Signature Algorithm (ECDSA) +""" +authors = ["RustCrypto Developers"] +license = "Apache-2.0 OR MIT" +repository = "https://github.com/RustCrypto/signatures/tree/master/rfc6979" +readme = "README.md" +categories = ["cryptography", "no-std"] +keywords = ["dsa", "ecdsa", "signature"] +edition = "2021" +rust-version = "1.56" + +[dependencies] +crypto-bigint = { version = "0.3", default-features = false, features = ["generic-array", "zeroize"] } +hmac = { version = "0.11", default-features = false } +zeroize = { version = "1", default-features = false } + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] diff --git a/rfc6979/README.md b/rfc6979/README.md new file mode 100644 index 0000000..d8405f3 --- /dev/null +++ b/rfc6979/README.md @@ -0,0 +1,55 @@ +# [RustCrypto]: RFC6979 Deterministic Signatures + +[![crate][crate-image]][crate-link] +[![Docs][docs-image]][docs-link] +![Apache2/MIT licensed][license-image] +![MSRV][rustc-image] +[![Project Chat][chat-image]][chat-link] +[![Build Status][build-image]][build-link] + +Pure Rust implementation of RFC6979: Deterministic Usage of the +Digital Signature Algorithm (DSA) and Elliptic Curve Digital Signature Algorithm (ECDSA). + +Algorithm described in RFC 6979 ยง 3.2: + + +[Documentation][docs-link] + +## Minimum Supported Rust Version + +This crate requires **Rust 1.56** at a minimum. + +We may change the MSRV in the future, but it will be accompanied by a minor +version bump. + +## License + +All crates licensed under either of + + * [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) + * [MIT license](http://opensource.org/licenses/MIT) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall be +dual licensed as above, without any additional terms or conditions. + +[//]: # (badges) + +[crate-image]: https://img.shields.io/crates/v/rfc6979.svg +[crate-link]: https://crates.io/crates/rfc6979 +[docs-image]: https://docs.rs/rfc6979/badge.svg +[docs-link]: https://docs.rs/rfc6979/ +[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg +[rustc-image]: https://img.shields.io/badge/rustc-1.56+-blue.svg +[chat-image]: https://img.shields.io/badge/zulip-join_chat-blue.svg +[chat-link]: https://rustcrypto.zulipchat.com/#narrow/stream/260048-signatures +[build-image]: https://github.com/RustCrypto/signatures/actions/workflows/rfc6979.yml/badge.svg +[build-link]: https://github.com/RustCrypto/signatures/actions/workflows/rfc6979.yml + +[//]: # (links) + +[RustCrypto]: https://github.com/RustCrypto diff --git a/rfc6979/src/lib.rs b/rfc6979/src/lib.rs new file mode 100644 index 0000000..af47607 --- /dev/null +++ b/rfc6979/src/lib.rs @@ -0,0 +1,113 @@ +#![doc = include_str!("../README.md")] + +//! ## Usage +//! +//! See the documentation for the [`generate_k`] function. + +#![no_std] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![forbid(unsafe_code, clippy::unwrap_used)] +#![warn(missing_docs, rust_2018_idioms)] +#![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", + html_root_url = "https://docs.rs/rfc6979/0.0.0" +)] + +use crypto_bigint::{ArrayEncoding, ByteArray, Integer}; +use hmac::{ + digest::{generic_array::GenericArray, BlockInput, FixedOutput, Reset, Update}, + Hmac, Mac, NewMac, +}; +use zeroize::{Zeroize, Zeroizing}; + +/// Deterministically generate ephemeral scalar `k`. +/// +/// Accepts the following parameters and inputs: +/// +/// - `x`: secret key +/// - `n`: field modulus +/// - `h`: hash/digest of input message: must be reduced modulo `n` in advance +/// - `data`: additional associated data, e.g. CSRNG output used as added entropy +#[inline] +pub fn generate_k(x: &I, n: &I, h: &ByteArray, data: &[u8]) -> Zeroizing +where + D: FixedOutput + BlockInput + Clone + Default + Reset + Update, + I: ArrayEncoding + Integer + Zeroize, +{ + let mut x = x.to_be_byte_array(); + let mut hmac_drbg = HmacDrbg::::new(&x, h, data); + x.zeroize(); + + loop { + let mut bytes = ByteArray::::default(); + hmac_drbg.fill_bytes(&mut bytes); + let k = I::from_be_byte_array(bytes); + + if (!k.is_zero() & k.ct_lt(n)).into() { + return Zeroizing::new(k); + } + } +} + +/// Internal implementation of `HMAC_DRBG` as described in NIST SP800-90A. +/// +/// +/// +/// This is a HMAC-based deterministic random bit generator used compute a +/// deterministic ephemeral scalar `k`. +pub struct HmacDrbg +where + D: BlockInput + FixedOutput + Clone + Default + Reset + Update, +{ + /// HMAC key `K` (see RFC 6979 Section 3.2.c) + k: Hmac, + + /// Chaining value `V` (see RFC 6979 Section 3.2.c) + v: GenericArray, +} + +impl HmacDrbg +where + D: BlockInput + FixedOutput + Clone + Default + Reset + Update, +{ + /// Initialize `HMAC_DRBG` + pub fn new(entropy_input: &[u8], nonce: &[u8], additional_data: &[u8]) -> Self { + let mut k = Hmac::new(&Default::default()); + let mut v = GenericArray::default(); + + for b in &mut v { + *b = 0x01; + } + + for i in 0..=1 { + k.update(&v); + k.update(&[i]); + k.update(entropy_input); + k.update(nonce); + k.update(additional_data); + k = Hmac::new_from_slice(&k.finalize().into_bytes()).expect("HMAC error"); + + // Steps 3.2.e,g: v = HMAC_k(v) + k.update(&v); + v = k.finalize_reset().into_bytes(); + } + + Self { k, v } + } + + /// Write the next `HMAC_DRBG` output to the given byte slice. + pub fn fill_bytes(&mut self, out: &mut [u8]) { + for out_chunk in out.chunks_mut(self.v.len()) { + self.k.update(&self.v); + self.v = self.k.finalize_reset().into_bytes(); + out_chunk.copy_from_slice(&self.v[..out_chunk.len()]); + } + + self.k.update(&self.v); + self.k.update(&[0x00]); + self.k = Hmac::new_from_slice(&self.k.finalize_reset().into_bytes()).expect("HMAC error"); + self.k.update(&self.v); + self.v = self.k.finalize_reset().into_bytes(); + } +} diff --git a/rfc6979/tests/lib.rs b/rfc6979/tests/lib.rs new file mode 100644 index 0000000..1b622f8 --- /dev/null +++ b/rfc6979/tests/lib.rs @@ -0,0 +1,14 @@ +//! Smoke tests which use `MockCurve` + +#![cfg(feature = "dev")] + +use elliptic_curve::dev::MockCurve; + +type Signature = ecdsa::Signature; +type SignatureBytes = ecdsa::SignatureBytes; + +#[test] +fn rejects_all_zero_signature() { + let all_zero_bytes = SignatureBytes::default(); + assert!(Signature::try_from(all_zero_bytes.as_ref()).is_err()); +}