signature_derive: Custom derive support for Signer/Verifier

We've explored several different possible trait bounds for a blanket
impl of `Signer` for `DigestSigner` (and likewise for `Verifier`).

Unfortunately, the approach we netted out at in #12 does not permit
anything but the blanket impl, and was removed in #16.

It's definitely still worth exploring if there's a way to define the
bounds of the default impl that works, however there is one approach we
haven't explored yet: a procedural macro.

This approach feels like a bit of a hack, but gets us to where we
originally wanted to be with the blanket impl API-wise. The impl it
derives uses the same approach and bounds as the blanket impl did:
the `Digest` to use is sourced from an associated type of the
`DigestSignature` trait, which means the digest to use is both automatic
and ensures the type deriving the trait supports the expected `Digest`
for the given signature.

The implementation uses `synstructure` which simplifies both handling of
generics and testing the output of the proc macro.

Additionally, it includes a complete integration test of the derived
code which ensures it works as expected.

It's gated under a cargo feature and disabled-by-default. This should
make it unobtrusive for downstream crates which don't need its
functionality.
This commit is contained in:
Tony Arcieri
2019-06-06 15:50:01 -07:00
parent a94234e649
commit 338dbb1136
12 changed files with 300 additions and 22 deletions
+1
View File
@@ -15,6 +15,7 @@ install:
script:
- cargo test --verbose --release
- cargo test --verbose --all-features --release
# Can't use `--no-default-features` with a workspace. See rust-lang/cargo#4753
- cd signature-crate && cargo build --no-default-features --release
+1
View File
@@ -3,4 +3,5 @@ members = [
"ecdsa",
"ed25519",
"signature-crate",
"signature-crate/signature_derive"
]
+5
View File
@@ -13,6 +13,11 @@ categories = ["cryptography", "no-std"]
[dependencies]
digest = { version = "0.8", optional = true, default-features = false }
signature_derive = { version = "0", optional = true, path = "signature_derive" }
[dev-dependencies]
hex-literal = "0.2"
sha2 = { version = "0.8", default-features = false }
[features]
default = ["digest", "std"]
+28 -3
View File
@@ -1,9 +1,19 @@
# `signature` crate
[![Build Status](https://travis-ci.org/RustCrypto/signatures.svg?branch=master)](https://travis-ci.org/RustCrypto/signatures)
[![crate][crate-image]][crate-link]
[![Docs][docs-image]][docs-link]
![Apache2/MIT licensed][license-image]
![Rust Version][rustc-image]
[![Build Status][build-image]][build-link]
This crate contains traits which provide generic, object-safe APIs for
generating and verifying [digital signatures][1].
generating and verifying [digital signatures].
The long-term goal is to use this crate in conjunction with the
[`ecdsa`][ecdsa-crate] and [`ed25519`][ed25519-crate], however those crates
are a work-in-progress.
[Documentation][docs-link]
## License
@@ -20,4 +30,19 @@ 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.
[1]: https://en.wikipedia.org/wiki/Digital_signature
[//]: # (badges)
[crate-image]: https://img.shields.io/crates/v/signature.svg
[crate-link]: https://crates.io/crates/signature
[docs-image]: https://docs.rs/signature/badge.svg
[docs-link]: https://docs.rs/signature/
[license-image]: https://img.shields.io/badge/license-Apache2.0/MIT-blue.svg
[rustc-image]: https://img.shields.io/badge/rustc-1.31+-blue.svg
[build-image]: https://travis-ci.org/RustCrypto/signatures.svg?branch=master
[build-link]: https://travis-ci.org/RustCrypto/signatures
[//]: # (general links)
[digital signatures]: https://en.wikipedia.org/wiki/Digital_signature
[ecdsa-crate]: https://github.com/RustCrypto/signatures/tree/master/ecdsa
[ed25519-crate]: https://github.com/RustCrypto/signatures/tree/master/ed25519
@@ -0,0 +1,21 @@
[package]
name = "signature_derive"
version = "0.0.0"
authors = ["RustCrypto Developers"]
license = "Apache-2.0 OR MIT"
description = "Custom derive support for the 'signature' crate"
documentation = "https://docs.rs/signature"
repository = "https://github.com/RustCrypto/signatures/tree/master/signature-crate/"
readme = "README.md"
edition = "2018"
keywords = ["crypto", "ecdsa", "ed25519", "signature", "signing"]
categories = ["cryptography", "no-std"]
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "0.4"
quote = "0.6"
syn = "0.15"
synstructure = "0.10"
@@ -0,0 +1,27 @@
# `signature` crate custom derive support
[![Build Status](https://travis-ci.org/RustCrypto/signatures.svg?branch=master)](https://travis-ci.org/RustCrypto/signatures)
This crate provides proc macros used by the `signature` crate.
It's not intended to be used directly. See the signature crate's documentation
for additional details:
[Documentation][docs]
## 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.
[Documentation]: https://docs.rs/signature/
+115
View File
@@ -0,0 +1,115 @@
//! Custom derive support for the `signature` crate.
//!
//! This crate can be used to derive `Signer` and `Verifier` impls for
//! types that impl `DigestSigner` or `DigestVerifier` respectively.
#![crate_type = "proc-macro"]
#![recursion_limit = "128"]
#![deny(warnings, unused_import_braces, unused_qualifications)]
#![forbid(unsafe_code)]
extern crate proc_macro;
use proc_macro2::TokenStream;
use quote::quote;
use synstructure::{decl_derive, AddBounds};
/// Derive the `Signer` trait for `DigestSigner` types
fn derive_signer(mut s: synstructure::Structure) -> TokenStream {
s.add_bounds(AddBounds::None);
s.gen_impl(quote! {
use signature::{DigestSignature, DigestSigner, Error};
gen impl<S> Signer<S> for @Self
where
S: DigestSignature,
Self: DigestSigner<S::Digest, S>,
{
fn try_sign(&self, msg: &[u8]) -> Result<S, Error> {
self.try_sign_digest(S::Digest::new().chain(msg))
}
}
})
}
decl_derive!([Signer] => derive_signer);
/// Derive the `Verifier` trait for `DigestVerifier` types
fn derive_verifier(mut s: synstructure::Structure) -> TokenStream {
s.add_bounds(AddBounds::None);
s.gen_impl(quote! {
use signature::{DigestSignature, DigestVerifier, Error};
gen impl<S> Verifier<S> for @Self
where
S: DigestSignature,
Self: DigestVerifier<S::Digest, S>,
{
fn verify(&self, msg: &[u8], signature: &S) -> Result<(), Error> {
self.verify_digest(S::Digest::new().chain(msg), signature)
}
}
})
}
decl_derive!([Verifier] => derive_verifier);
#[cfg(test)]
mod tests {
use super::*;
use synstructure::test_derive;
#[test]
fn signer() {
test_derive! {
derive_signer {
struct MySigner<C: EllipticCurve> {
scalar: Scalar<C::ScalarSize>
}
}
expands to {
#[allow(non_upper_case_globals)]
const _DERIVE_Signer_S_FOR_MySigner: () = {
use signature::{DigestSignature, DigestSigner, Error};
impl<S, C: EllipticCurve> Signer<S> for MySigner<C>
where
S: DigestSignature,
Self: DigestSigner<S::Digest, S>,
{
fn try_sign(&self, msg: &[u8]) -> Result <S, Error> {
self.try_sign_digest(S::Digest::new().chain(msg))
}
}
};
}
no_build // tests in `signature-crate/tests`
}
}
#[test]
fn verifier() {
test_derive! {
derive_verifier {
struct MyVerifier<C: EllipticCurve> {
point: UncompressedPoint<C>
}
}
expands to {
#[allow(non_upper_case_globals)]
const _DERIVE_Verifier_S_FOR_MyVerifier: () = {
use signature::{DigestSignature, DigestVerifier, Error};
impl<S, C: EllipticCurve> Verifier<S> for MyVerifier<C>
where
S: DigestSignature,
Self: DigestVerifier<S::Digest, S>,
{
fn verify(&self, msg: &[u8], signature: &S) -> Result<(), Error> {
self.verify_digest(S::Digest::new().chain(msg), signature)
}
}
};
}
no_build // tests in `signature-crate/tests`
}
}
}
+10 -1
View File
@@ -18,8 +18,17 @@
#[macro_use]
extern crate std;
#[cfg(feature = "signature_derive")]
#[allow(unused_imports)]
#[macro_use]
extern crate signature_derive;
#[cfg(feature = "signature_derive")]
#[doc(hidden)]
pub use signature_derive::{Signer, Verifier};
#[cfg(feature = "digest")]
pub extern crate digest;
pub use digest;
mod error;
mod prelude;
+16
View File
@@ -22,3 +22,19 @@ pub trait Signature: AsRef<[u8]> + Debug + Sized {
self.as_slice().into()
}
}
/// Marker trait for `Signature` types computable as `S(H(m))`
///
/// - `S`: signature algorithm
/// - `H`: hash (a.k.a. digest) function
/// - `m`: message
///
/// For signature types that implement this trait, a blanket impl of
/// `Signer` will be provided for all types that `impl DigestSigner`
/// along with a corresponding impl of `Verifier` for all types that
/// `impl DigestVerifier`.
#[cfg(feature = "digest")]
pub trait DigestSignature: Signature {
/// Preferred `Digest` algorithm to use when computing this signature type.
type Digest: digest::Digest;
}
-13
View File
@@ -27,19 +27,6 @@ where
D: Digest,
S: Signature,
{
/// Sign the computed digest of the given message.
///
/// Panics in the event of a signing error.
fn sign_msg_digest(&self, msg: &[u8]) -> S {
self.try_sign_msg_digest(msg)
.expect("signature operation failed")
}
/// Attempt to sign the computed digest of the given message.
fn try_sign_msg_digest(&self, msg: &[u8]) -> Result<S, Error> {
self.try_sign_digest(D::new().chain(msg))
}
/// Sign the given prehashed message `Digest`, returning a signature.
///
/// Panics in the event of a signing error.
-5
View File
@@ -21,11 +21,6 @@ where
D: Digest,
S: Signature,
{
/// Verify the signature against the computed `Digest` output.
fn verify_msg_digest(&self, msg: &[u8], signature: &S) -> Result<(), Error> {
self.verify_digest(D::new().chain(msg), signature)
}
/// Verify the signature against the given `Digest` output.
fn verify_digest(&self, digest: D, signature: &S) -> Result<(), Error>;
}
+76
View File
@@ -0,0 +1,76 @@
/// "Tests" for code generated by `signature_derive`
#[cfg(all(test, feature = "signature_derive"))]
mod tests {
use digest::{generic_array::GenericArray, Digest};
use hex_literal::hex;
use sha2::Sha256;
use signature::{
DigestSignature, DigestSigner, DigestVerifier, Error, Signature, Signer, Verifier,
};
/// Test vector to compute SHA-256 digest of
const INPUT_STRING: &[u8] = b"abc";
/// Expected SHA-256 digest for the input string
const INPUT_STRING_DIGEST: [u8; 32] =
hex!("ba7816bf 8f01cfea 414140de 5dae2223 b00361a3 96177a9c b410ff61 f20015ad");
/// Dummy signature which just contains a digest output
#[derive(Debug)]
struct DummySignature(GenericArray<u8, <Sha256 as Digest>::OutputSize>);
impl Signature for DummySignature {
fn from_bytes<B: AsRef<[u8]>>(bytes: B) -> Result<Self, Error> {
Ok(DummySignature(GenericArray::clone_from_slice(
bytes.as_ref(),
)))
}
}
impl AsRef<[u8]> for DummySignature {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl DigestSignature for DummySignature {
type Digest = Sha256;
}
/// Dummy signer which just returns the message digest as a `DummySignature`
#[derive(Signer, Default)]
struct DummySigner {}
impl DigestSigner<Sha256, DummySignature> for DummySigner {
fn try_sign_digest(&self, digest: Sha256) -> Result<DummySignature, Error> {
DummySignature::from_bytes(digest.result())
}
}
/// Dummy verifier which ensures the `DummySignature` digest matches the
/// expected value.
///
/// Panics (via `assert_eq!`) if the value is not what is expected.
#[derive(Verifier, Default)]
struct DummyVerifier {}
impl DigestVerifier<Sha256, DummySignature> for DummyVerifier {
fn verify_digest(&self, digest: Sha256, signature: &DummySignature) -> Result<(), Error> {
let actual_digest = digest.result();
assert_eq!(signature.as_ref(), actual_digest.as_ref());
Ok(())
}
}
#[test]
fn derived_signer_impl() {
let sig: DummySignature = DummySigner::default().sign(INPUT_STRING);
assert_eq!(sig.as_ref(), INPUT_STRING_DIGEST.as_ref())
}
#[test]
fn derived_verifier_impl() {
let sig: DummySignature = DummySigner::default().sign(INPUT_STRING);
assert!(DummyVerifier::default().verify(INPUT_STRING, &sig).is_ok());
}
}