From b95afa61699dd63d1089fa4e26eecf6f723985ef Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Thu, 30 Jul 2020 10:37:47 -0700 Subject: [PATCH] ecdsa: Signer facade (gated under `signer` feature) (#109) Adds a `ecdsa::signer::Signer` facade which handles parsing of `SecretKey` data and zeroization of the private scalar. The implementation is generic over the elliptic curve and digest function, providing a `RandomizedDigestSigner` impl, and also when the curve type impls `DigestPrimitive`, a `RandomizedSigner` impl. --- .github/workflows/ecdsa.yml | 82 +++++------------------- Cargo.lock | 21 ++++++- ecdsa/Cargo.toml | 5 +- ecdsa/src/hazmat.rs | 8 ++- ecdsa/src/lib.rs | 7 +++ ecdsa/src/signer.rs | 122 ++++++++++++++++++++++++++++++++++++ 6 files changed, 173 insertions(+), 72 deletions(-) create mode 100644 ecdsa/src/signer.rs diff --git a/.github/workflows/ecdsa.yml b/.github/workflows/ecdsa.yml index 5b7aeda..ffbabaa 100644 --- a/.github/workflows/ecdsa.yml +++ b/.github/workflows/ecdsa.yml @@ -23,82 +23,34 @@ jobs: target: - thumbv7em-none-eabi - wasm32-unknown-unknown - toolchain: + rust: - 1.41.0 # MSRV - stable steps: - - name: Checkout sources - uses: actions/checkout@v1 - - - name: Cache cargo registry - uses: actions/cache@v1 - with: - path: ~/.cargo/registry - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }} - - - name: Cache cargo index - uses: actions/cache@v1 - with: - path: ~/.cargo/git - key: ${{ runner.os }}-cargo-index-${{ hashFiles('Cargo.lock') }} - - - name: Cache cargo build - uses: actions/cache@v1 - with: - path: target - key: ${{ runner.os }}-rust-${{ matrix.toolchain }}-${{ matrix.target }}-cargo-build-target-${{ hashFiles('Cargo.lock') }} - - - name: Install toolchain - uses: actions-rs/toolchain@v1 + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 with: + profile: minimal + toolchain: ${{ matrix.rust }} target: ${{ matrix.target }} - toolchain: ${{ matrix.toolchain }} override: true - - - name: Run cargo build --no-default-features - run: cargo build --no-default-features --release --target ${{ matrix.target }} + - run: cargo build --no-default-features --release --target ${{ matrix.target }} + - run: cargo build --no-default-features --features signer --release --target ${{ matrix.target }} test: + runs-on: ubuntu-latest strategy: matrix: - platform: - - ubuntu-latest - - macos-latest - - windows-latest - toolchain: + rust: - 1.41.0 # MSRV - stable - runs-on: ${{ matrix.platform }} steps: - - name: Checkout sources - uses: actions/checkout@v1 - - - name: Cache cargo registry - uses: actions/cache@v1 + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 with: - path: ~/.cargo/registry - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }} - - - name: Cache cargo index - uses: actions/cache@v1 - with: - path: ~/.cargo/git - key: ${{ runner.os }}-cargo-index-${{ hashFiles('Cargo.lock') }} - - - name: Cache cargo build - uses: actions/cache@v1 - with: - path: target - key: ${{ runner.os }}-rust-${{ matrix.toolchain }}-cargo-build-target-${{ hashFiles('Cargo.lock') }} - - - name: Install toolchain - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.toolchain }} - override: true - - - name: Run cargo test --lib - run: cargo test --lib --release - - - name: Run cargo test --all-features - run: cargo test --all-features --release + profile: minimal + toolchain: ${{ matrix.rust }} + - run: cargo check --all-features + - run: cargo test --no-default-features + - run: cargo test + - run: cargo test --all-features diff --git a/Cargo.lock b/Cargo.lock index 9a2e1b6..91b9c32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,10 +45,12 @@ dependencies = [ [[package]] name = "elliptic-curve" version = "0.5.0-pre" -source = "git+https://github.com/RustCrypto/traits#f8a916bbcff07aece243db24f17f663d4077baba" +source = "git+https://github.com/RustCrypto/traits#cb1fdb7ca135d02f9cadf819e036e95d024248f2" dependencies = [ "generic-array", + "rand_core", "subtle", + "zeroize", ] [[package]] @@ -61,6 +63,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" + [[package]] name = "serde" version = "1.0.114" @@ -69,11 +77,12 @@ checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3" [[package]] name = "signature" -version = "1.1.0" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65211b7b6fc3f14ff9fc7a2011a434e3e6880585bd2e9e9396315ae24cbf7852" +checksum = "29f060a7d147e33490ec10da418795238fd7545bba241504d6b31a409f2e6210" dependencies = [ "digest", + "rand_core", ] [[package]] @@ -93,3 +102,9 @@ name = "version_check" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" + +[[package]] +name = "zeroize" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbac2ed2ba24cc90f5e06485ac8c7c1e5449fe8911aef4d8877218af021a5b8" diff --git a/ecdsa/Cargo.toml b/ecdsa/Cargo.toml index 26f3007..57989bd 100644 --- a/ecdsa/Cargo.toml +++ b/ecdsa/Cargo.toml @@ -20,7 +20,7 @@ default-features = false features = ["weierstrass"] [dependencies.signature] -version = ">= 1.1.0, < 1.2.0" +version = ">= 1.2.2, < 1.3.0" default-features = false [features] @@ -28,7 +28,10 @@ default = ["digest", "std"] dev = [] digest = ["signature/digest-preview"] hazmat = [] +rand = ["elliptic-curve/rand_core", "signature/rand-preview"] +signer = ["digest", "hazmat", "rand", "zeroize"] # TODO(tarcieri): deterministic signing std = ["elliptic-curve/std", "signature/std"] +zeroize = ["elliptic-curve/zeroize"] [package.metadata.docs.rs] features = ["digest", "std"] diff --git a/ecdsa/src/hazmat.rs b/ecdsa/src/hazmat.rs index 668ec20..5edb181 100644 --- a/ecdsa/src/hazmat.rs +++ b/ecdsa/src/hazmat.rs @@ -12,6 +12,7 @@ //! FULL PRIVATE KEY RECOVERY! use crate::{Signature, SignatureSize}; +use core::borrow::Borrow; use elliptic_curve::{ generic_array::ArrayLength, ops::Invert, weierstrass::Curve, Arithmetic, ScalarBytes, }; @@ -34,9 +35,10 @@ where /// /// Accepts the following arguments: /// - /// - `ephemeral_scalar`: ECDSA `k` value (MUST BE UNIFORMLY RANDOM!!!) - /// - `hashed_msg`: prehashed message to be signed - fn try_sign_prehashed + Invert>( + /// - `ephemeral_scalar`: ECDSA `k` value. MUST BE UNIFORMLY RANDOM!!! + /// - `hashed_msg`: hashed message digest to be signed. + /// MUST BE OUTPUT OF A CRYPTOGRAPHICALLY SECURE DIGEST ALGORITHM!!! + fn try_sign_prehashed + Invert>( &self, ephemeral_scalar: &K, hashed_msg: &ScalarBytes, diff --git a/ecdsa/src/lib.rs b/ecdsa/src/lib.rs index 7fc6e58..05a5c4d 100644 --- a/ecdsa/src/lib.rs +++ b/ecdsa/src/lib.rs @@ -33,6 +33,10 @@ pub mod dev; #[cfg_attr(docsrs, doc(cfg(feature = "hazmat")))] pub mod hazmat; +#[cfg(feature = "signer")] +#[cfg_attr(docsrs, doc(cfg(feature = "signer")))] +pub mod signer; + // Re-export the `elliptic-curve` crate (and select types) pub use elliptic_curve::{ self, generic_array, @@ -43,6 +47,9 @@ pub use elliptic_curve::{ // Re-export the `signature` crate (and select types) pub use signature::{self, Error}; +#[cfg(feature = "signer")] +pub use signer::Signer; + use core::{ convert::TryFrom, fmt::{self, Debug}, diff --git a/ecdsa/src/signer.rs b/ecdsa/src/signer.rs new file mode 100644 index 0000000..35425ee --- /dev/null +++ b/ecdsa/src/signer.rs @@ -0,0 +1,122 @@ +//! ECDSA signer. Generic over elliptic curves. +//! +//! Requires an [`elliptic_curve::Arithmetic`] impl on the curve, and a +//! [`SignPrimitive`] impl on its associated `Scalar` type. + +// TODO(tarcieri): RFC 6979; support for hardware crypto accelerators + +use crate::{ + hazmat::{DigestPrimitive, SignPrimitive}, + Error, Signature, SignatureSize, +}; +use elliptic_curve::{ + generic_array::ArrayLength, + ops::Invert, + secret_key::{FromSecretKey, SecretKey}, + weierstrass::Curve, + zeroize::{Zeroize, Zeroizing}, + Arithmetic, +}; + +#[cfg(feature = "rand")] +use { + elliptic_curve::Generate, + signature::{ + digest::Digest, + rand_core::{CryptoRng, RngCore}, + RandomizedDigestSigner, RandomizedSigner, + }, +}; + +/// ECDSA signer +pub struct Signer +where + C: Curve + Arithmetic, + C::Scalar: Invert + SignPrimitive + Zeroize, + SignatureSize: ArrayLength, +{ + secret_scalar: C::Scalar, +} + +impl Signer +where + C: Curve + Arithmetic, + C::Scalar: Invert + SignPrimitive + Zeroize, + SignatureSize: ArrayLength, +{ + /// Create a new signer + pub fn new(secret_key: &SecretKey) -> Result { + let scalar = C::Scalar::from_secret_key(secret_key); + + if scalar.is_some().into() { + Ok(Self { + secret_scalar: scalar.unwrap(), + }) + } else { + Err(Error::new()) + } + } +} + +#[cfg(feature = "rand")] +#[cfg_attr(docsrs, doc(cfg(feature = "rand")))] +impl RandomizedDigestSigner> for Signer +where + C: Curve + Arithmetic, + D: Digest, + C::Scalar: Invert + Generate + SignPrimitive + Zeroize, + SignatureSize: ArrayLength, +{ + fn try_sign_digest_with_rng( + &self, + rng: impl CryptoRng + RngCore, + digest: D, + ) -> Result, Error> { + let ephemeral_scalar = Zeroizing::new(C::Scalar::generate(rng)); + + self.secret_scalar + .try_sign_prehashed(&*ephemeral_scalar, &digest.finalize()) + } +} + +#[cfg(feature = "rand")] +#[cfg_attr(docsrs, doc(cfg(feature = "rand")))] +impl RandomizedSigner> for Signer +where + C: Curve + Arithmetic + DigestPrimitive, + C::Digest: Digest, + C::Scalar: Invert + Generate + SignPrimitive + Zeroize, + SignatureSize: ArrayLength, +{ + fn try_sign_with_rng( + &self, + rng: impl CryptoRng + RngCore, + msg: &[u8], + ) -> Result, Error> { + self.try_sign_digest_with_rng(rng, C::Digest::new().chain(msg)) + } +} + +impl Zeroize for Signer +where + C: Curve + Arithmetic, + C::Scalar: Invert + SignPrimitive + Zeroize, + + SignatureSize: ArrayLength, +{ + fn zeroize(&mut self) { + self.secret_scalar.zeroize(); + } +} + +impl Drop for Signer +where + C: Curve + Arithmetic, + C::Scalar: Invert + SignPrimitive + Zeroize, + + SignatureSize: ArrayLength, +{ + fn drop(&mut self) { + self.zeroize(); + } +}