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.
This commit is contained in:
Tony Arcieri
2020-07-30 10:37:47 -07:00
committed by GitHub
parent e33e8b2c69
commit b95afa6169
6 changed files with 173 additions and 72 deletions
+17 -65
View File
@@ -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
Generated
+18 -3
View File
@@ -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"
+4 -1
View File
@@ -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"]
+5 -3
View File
@@ -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<K: AsRef<C::Scalar> + Invert<Output = C::Scalar>>(
/// - `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<K: Borrow<C::Scalar> + Invert<Output = C::Scalar>>(
&self,
ephemeral_scalar: &K,
hashed_msg: &ScalarBytes<C>,
+7
View File
@@ -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},
+122
View File
@@ -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<C>
where
C: Curve + Arithmetic,
C::Scalar: Invert<Output = C::Scalar> + SignPrimitive<C> + Zeroize,
SignatureSize<C>: ArrayLength<u8>,
{
secret_scalar: C::Scalar,
}
impl<C> Signer<C>
where
C: Curve + Arithmetic,
C::Scalar: Invert<Output = C::Scalar> + SignPrimitive<C> + Zeroize,
SignatureSize<C>: ArrayLength<u8>,
{
/// Create a new signer
pub fn new(secret_key: &SecretKey<C>) -> Result<Self, Error> {
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<C, D> RandomizedDigestSigner<D, Signature<C>> for Signer<C>
where
C: Curve + Arithmetic,
D: Digest<OutputSize = C::ElementSize>,
C::Scalar: Invert<Output = C::Scalar> + Generate + SignPrimitive<C> + Zeroize,
SignatureSize<C>: ArrayLength<u8>,
{
fn try_sign_digest_with_rng(
&self,
rng: impl CryptoRng + RngCore,
digest: D,
) -> Result<Signature<C>, 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<C> RandomizedSigner<Signature<C>> for Signer<C>
where
C: Curve + Arithmetic + DigestPrimitive,
C::Digest: Digest<OutputSize = C::ElementSize>,
C::Scalar: Invert<Output = C::Scalar> + Generate + SignPrimitive<C> + Zeroize,
SignatureSize<C>: ArrayLength<u8>,
{
fn try_sign_with_rng(
&self,
rng: impl CryptoRng + RngCore,
msg: &[u8],
) -> Result<Signature<C>, Error> {
self.try_sign_digest_with_rng(rng, C::Digest::new().chain(msg))
}
}
impl<C> Zeroize for Signer<C>
where
C: Curve + Arithmetic,
C::Scalar: Invert<Output = C::Scalar> + SignPrimitive<C> + Zeroize,
SignatureSize<C>: ArrayLength<u8>,
{
fn zeroize(&mut self) {
self.secret_scalar.zeroize();
}
}
impl<C> Drop for Signer<C>
where
C: Curve + Arithmetic,
C::Scalar: Invert<Output = C::Scalar> + SignPrimitive<C> + Zeroize,
SignatureSize<C>: ArrayLength<u8>,
{
fn drop(&mut self) {
self.zeroize();
}
}