From 338dbb11367b743ae3786cf17339f0855ffad694 Mon Sep 17 00:00:00 2001 From: Tony Arcieri Date: Thu, 6 Jun 2019 15:50:01 -0700 Subject: [PATCH] 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. --- .travis.yml | 1 + Cargo.toml | 1 + signature-crate/Cargo.toml | 5 + signature-crate/README.md | 31 +++++- signature-crate/signature_derive/Cargo.toml | 21 ++++ signature-crate/signature_derive/README.md | 27 +++++ signature-crate/signature_derive/src/lib.rs | 115 ++++++++++++++++++++ signature-crate/src/lib.rs | 11 +- signature-crate/src/signature.rs | 16 +++ signature-crate/src/signer.rs | 13 --- signature-crate/src/verifier.rs | 5 - signature-crate/tests/signature_derive.rs | 76 +++++++++++++ 12 files changed, 300 insertions(+), 22 deletions(-) create mode 100644 signature-crate/signature_derive/Cargo.toml create mode 100644 signature-crate/signature_derive/README.md create mode 100644 signature-crate/signature_derive/src/lib.rs create mode 100644 signature-crate/tests/signature_derive.rs diff --git a/.travis.yml b/.travis.yml index aedb939c..db686246 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/Cargo.toml b/Cargo.toml index 2d6b4aec..3c673051 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,4 +3,5 @@ members = [ "ecdsa", "ed25519", "signature-crate", + "signature-crate/signature_derive" ] diff --git a/signature-crate/Cargo.toml b/signature-crate/Cargo.toml index 9263e31b..654bc538 100644 --- a/signature-crate/Cargo.toml +++ b/signature-crate/Cargo.toml @@ -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"] diff --git a/signature-crate/README.md b/signature-crate/README.md index 2f3f1368..69dc6199 100644 --- a/signature-crate/README.md +++ b/signature-crate/README.md @@ -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 diff --git a/signature-crate/signature_derive/Cargo.toml b/signature-crate/signature_derive/Cargo.toml new file mode 100644 index 00000000..43d5ea50 --- /dev/null +++ b/signature-crate/signature_derive/Cargo.toml @@ -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" diff --git a/signature-crate/signature_derive/README.md b/signature-crate/signature_derive/README.md new file mode 100644 index 00000000..6f035014 --- /dev/null +++ b/signature-crate/signature_derive/README.md @@ -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/ diff --git a/signature-crate/signature_derive/src/lib.rs b/signature-crate/signature_derive/src/lib.rs new file mode 100644 index 00000000..bd0686d1 --- /dev/null +++ b/signature-crate/signature_derive/src/lib.rs @@ -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 Signer for @Self + where + S: DigestSignature, + Self: DigestSigner, + { + fn try_sign(&self, msg: &[u8]) -> Result { + 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 Verifier for @Self + where + S: DigestSignature, + Self: DigestVerifier, + { + 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 { + scalar: Scalar + } + } + expands to { + #[allow(non_upper_case_globals)] + const _DERIVE_Signer_S_FOR_MySigner: () = { + use signature::{DigestSignature, DigestSigner, Error}; + + impl Signer for MySigner + where + S: DigestSignature, + Self: DigestSigner, + { + fn try_sign(&self, msg: &[u8]) -> Result { + 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 { + point: UncompressedPoint + } + } + expands to { + #[allow(non_upper_case_globals)] + const _DERIVE_Verifier_S_FOR_MyVerifier: () = { + use signature::{DigestSignature, DigestVerifier, Error}; + + impl Verifier for MyVerifier + where + S: DigestSignature, + Self: DigestVerifier, + { + 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` + } + } +} diff --git a/signature-crate/src/lib.rs b/signature-crate/src/lib.rs index e37f4cf5..003813f3 100644 --- a/signature-crate/src/lib.rs +++ b/signature-crate/src/lib.rs @@ -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; diff --git a/signature-crate/src/signature.rs b/signature-crate/src/signature.rs index 8376a5a4..8d021467 100644 --- a/signature-crate/src/signature.rs +++ b/signature-crate/src/signature.rs @@ -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; +} diff --git a/signature-crate/src/signer.rs b/signature-crate/src/signer.rs index 46ec9d0d..7ccf5689 100644 --- a/signature-crate/src/signer.rs +++ b/signature-crate/src/signer.rs @@ -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 { - 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. diff --git a/signature-crate/src/verifier.rs b/signature-crate/src/verifier.rs index 96aa7c58..23f2edbc 100644 --- a/signature-crate/src/verifier.rs +++ b/signature-crate/src/verifier.rs @@ -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>; } diff --git a/signature-crate/tests/signature_derive.rs b/signature-crate/tests/signature_derive.rs new file mode 100644 index 00000000..1adffaa6 --- /dev/null +++ b/signature-crate/tests/signature_derive.rs @@ -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::OutputSize>); + + impl Signature for DummySignature { + fn from_bytes>(bytes: B) -> Result { + 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 for DummySigner { + fn try_sign_digest(&self, digest: Sha256) -> Result { + 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 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()); + } +}