diff --git a/src/context.rs b/src/context.rs new file mode 100644 index 0000000..c026be5 --- /dev/null +++ b/src/context.rs @@ -0,0 +1,107 @@ +use crate::{InternalError, SignatureError}; + +/// Ed25519 contexts as used by Ed25519ph. +/// +/// Contexts are domain separator strings that can be used to isolate uses of +/// the algorithm between different protocols (which is very hard to reliably do +/// otherwise) and between different uses within the same protocol. +/// +/// To create a context, call either of the following: +/// +/// - [`SigningKey::with_context`](crate::SigningKey::with_context) +/// - [`VerifyingKey::with_context`](crate::VerifyingKey::with_context) +/// +/// For more information, see [RFC8032 ยง 8.3](https://www.rfc-editor.org/rfc/rfc8032#section-8.3). +/// +/// # Example +/// +#[cfg_attr(feature = "digest", doc = "```")] +#[cfg_attr(not(feature = "digest"), doc = "```ignore")] +/// # fn main() { +/// use ed25519_dalek::{Signature, SigningKey, VerifyingKey, Sha512}; +/// # use curve25519_dalek::digest::Digest; +/// # use rand::rngs::OsRng; +/// use ed25519_dalek::{DigestSigner, DigestVerifier}; +/// +/// # let mut csprng = OsRng; +/// # let signing_key = SigningKey::generate(&mut csprng); +/// # let verifying_key = signing_key.verifying_key(); +/// let context_str = b"Local Channel 3"; +/// let prehashed_message = Sha512::default().chain_update(b"Stay tuned for more news at 7"); +/// +/// // Signer +/// let signing_context = signing_key.with_context(context_str).unwrap(); +/// let signature = signing_context.sign_digest(prehashed_message.clone()); +/// +/// // Verifier +/// let verifying_context = verifying_key.with_context(context_str).unwrap(); +/// let verified: bool = verifying_context +/// .verify_digest(prehashed_message, &signature) +/// .is_ok(); +/// +/// # assert!(verified); +/// # } +/// ``` +#[derive(Clone, Debug)] +pub struct Context<'k, 'v, K> { + /// Key this context is being used with. + key: &'k K, + + /// Context value: a bytestring no longer than 255 octets. + value: &'v [u8], +} + +impl<'k, 'v, K> Context<'k, 'v, K> { + /// Maximum length of the context value in octets. + pub const MAX_LENGTH: usize = 255; + + /// Create a new Ed25519ph context. + pub(crate) fn new(key: &'k K, value: &'v [u8]) -> Result { + if value.len() <= Self::MAX_LENGTH { + Ok(Self { key, value }) + } else { + Err(SignatureError::from(InternalError::PrehashedContextLength)) + } + } + + /// Borrow the key. + pub fn key(&self) -> &'k K { + self.key + } + + /// Borrow the context string value. + pub fn value(&self) -> &'v [u8] { + self.value + } +} + +#[cfg(all(test, feature = "digest"))] +mod test { + use crate::{Signature, SigningKey, VerifyingKey}; + use curve25519_dalek::digest::Digest; + use ed25519::signature::{DigestSigner, DigestVerifier}; + use rand::rngs::OsRng; + use sha2::Sha512; + + #[test] + fn context_correctness() { + let mut csprng = OsRng; + let signing_key: SigningKey = SigningKey::generate(&mut csprng); + let verifying_key: VerifyingKey = signing_key.verifying_key(); + + let context_str = b"Local Channel 3"; + let prehashed_message = Sha512::default().chain_update(b"Stay tuned for more news at 7"); + + // Signer + let signing_context = signing_key.with_context(context_str).unwrap(); + let signature: Signature = signing_context.sign_digest(prehashed_message.clone()); + + // Verifier + let verifying_context = verifying_key.with_context(context_str).unwrap(); + let verified: bool = verifying_context + .verify_digest(prehashed_message, &signature) + .is_ok(); + + assert!(verified); + } +} diff --git a/src/errors.rs b/src/errors.rs index 7cba06d..aa4e5aa 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -48,7 +48,6 @@ pub(crate) enum InternalError { length_c: usize, }, /// An ed25519ph signature can only take up to 255 octets of context. - #[cfg(feature = "digest")] PrehashedContextLength, /// A mismatched (public, secret) key pair. MismatchedKeypair, @@ -77,7 +76,6 @@ impl Display for InternalError { {} has length {}, {} has length {}.", na, la, nb, lb, nc, lc ), - #[cfg(feature = "digest")] InternalError::PrehashedContextLength => write!( f, "An ed25519ph signature can only take up to 255 octets of context" diff --git a/src/lib.rs b/src/lib.rs index ead3d29..906ad05 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -73,7 +73,7 @@ //! # use ed25519_dalek::Signature; //! # use ed25519_dalek::Signer; //! use ed25519_dalek::{VerifyingKey, Verifier}; -//! # let mut csprng = OsRng{}; +//! # let mut csprng = OsRng; //! # let signing_key: SigningKey = SigningKey::generate(&mut csprng); //! # let message: &[u8] = b"This is a test of the tsunami alert system."; //! # let signature: Signature = signing_key.sign(message); @@ -97,7 +97,7 @@ //! # use rand::rngs::OsRng; //! # use ed25519_dalek::{SigningKey, Signature, Signer, VerifyingKey}; //! use ed25519_dalek::{PUBLIC_KEY_LENGTH, SECRET_KEY_LENGTH, KEYPAIR_LENGTH, SIGNATURE_LENGTH}; -//! # let mut csprng = OsRng{}; +//! # let mut csprng = OsRng; //! # let signing_key: SigningKey = SigningKey::generate(&mut csprng); //! # let message: &[u8] = b"This is a test of the tsunami alert system."; //! # let signature: Signature = signing_key.sign(message); @@ -258,6 +258,7 @@ pub use ed25519; #[cfg(feature = "batch")] mod batch; mod constants; +mod context; mod errors; mod signature; mod signing; @@ -265,15 +266,20 @@ mod verifying; #[cfg(feature = "digest")] pub use curve25519_dalek::digest::Digest; +#[cfg(feature = "digest")] +pub use sha2::Sha512; #[cfg(feature = "batch")] pub use crate::batch::*; pub use crate::constants::*; +pub use crate::context::Context; pub use crate::errors::*; pub use crate::signing::*; pub use crate::verifying::*; // Re-export the `Signer` and `Verifier` traits from the `signature` crate +#[cfg(feature = "digest")] +pub use ed25519::signature::{DigestSigner, DigestVerifier}; pub use ed25519::signature::{Signer, Verifier}; pub use ed25519::Signature; diff --git a/src/signing.rs b/src/signing.rs index ad299c1..b6326a7 100644 --- a/src/signing.rs +++ b/src/signing.rs @@ -40,6 +40,7 @@ use signature::DigestSigner; use zeroize::{Zeroize, ZeroizeOnDrop}; use crate::constants::*; +use crate::context::Context; use crate::errors::*; use crate::signature::*; use crate::verifying::*; @@ -158,6 +159,15 @@ impl SigningKey { self.verifying_key } + /// Create a signing context that can be used for Ed25519ph with + /// [`DigestSigner`]. + pub fn with_context<'k, 'v>( + &'k self, + context_value: &'v [u8], + ) -> Result, SignatureError> { + Context::new(self, context_value) + } + /// Generate an ed25519 signing key. /// /// # Example @@ -200,9 +210,7 @@ impl SigningKey { /// /// # Inputs /// - /// * `prehashed_message` is an instantiated hash digest with 512-bits of - /// output which has had the message to be signed previously fed into its - /// state. + /// * `prehashed_message` is an instantiated SHA-512 digest of the message /// * `context` is an optional context string, up to 255 bytes inclusive, /// which may be used to provide additional domain separation. If not /// set, this will default to an empty string. @@ -211,6 +219,13 @@ impl SigningKey { /// /// An Ed25519ph [`Signature`] on the `prehashed_message`. /// + /// # Note + /// + /// The RFC only permits SHA-512 to be used for prehashing. This function technically works, + /// and is probably safe to use, with any secure hash function with 512-bit digests, but + /// anything outside of SHA-512 is NOT specification-compliant. We expose [`crate::Sha512`] for + /// user convenience. + /// /// # Examples /// #[cfg_attr(all(feature = "rand_core", feature = "digest"), doc = "```")] @@ -226,7 +241,7 @@ impl SigningKey { /// /// # #[cfg(feature = "std")] /// # fn main() { - /// let mut csprng = OsRng{}; + /// let mut csprng = OsRng; /// let signing_key: SigningKey = SigningKey::generate(&mut csprng); /// let message: &[u8] = b"All I want is to pet all of the dogs."; /// @@ -274,7 +289,7 @@ impl SigningKey { /// # use rand::rngs::OsRng; /// # /// # fn do_test() -> Result { - /// # let mut csprng = OsRng{}; + /// # let mut csprng = OsRng; /// # let signing_key: SigningKey = SigningKey::generate(&mut csprng); /// # let message: &[u8] = b"All I want is to pet all of the dogs."; /// # let mut prehashed: Sha512 = Sha512::new(); @@ -348,7 +363,7 @@ impl SigningKey { /// use rand::rngs::OsRng; /// /// # fn do_test() -> Result<(), SignatureError> { - /// let mut csprng = OsRng{}; + /// let mut csprng = OsRng; /// let signing_key: SigningKey = SigningKey::generate(&mut csprng); /// let message: &[u8] = b"All I want is to pet all of the dogs."; /// @@ -485,6 +500,12 @@ impl Signer for SigningKey { } /// Equivalent to [`SigningKey::sign_prehashed`] with `context` set to [`None`]. +/// +/// # Note +/// +/// The RFC only permits SHA-512 to be used for prehashing. This function technically works, and is +/// probably safe to use, with any secure hash function with 512-bit digests, but anything outside +/// of SHA-512 is NOT specification-compliant. We expose [`crate::Sha512`] for user convenience. #[cfg(feature = "digest")] impl DigestSigner for SigningKey where @@ -495,6 +516,24 @@ where } } +/// Equivalent to [`SigningKey::sign_prehashed`] with `context` set to [`Some`] +/// containing `self.value()`. +/// +/// # Note +/// +/// The RFC only permits SHA-512 to be used for prehashing. This function technically works, and is +/// probably safe to use, with any secure hash function with 512-bit digests, but anything outside +/// of SHA-512 is NOT specification-compliant. We expose [`crate::Sha512`] for user convenience. +#[cfg(feature = "digest")] +impl DigestSigner for Context<'_, '_, SigningKey> +where + D: Digest, +{ + fn try_sign_digest(&self, msg_digest: D) -> Result { + self.key().sign_prehashed(msg_digest, Some(self.value())) + } +} + impl Verifier for SigningKey { /// Verify a signature on a message with this signing key's public key. fn verify(&self, message: &[u8], signature: &Signature) -> Result<(), SignatureError> { diff --git a/src/verifying.rs b/src/verifying.rs index 48f8769..e57f2e9 100644 --- a/src/verifying.rs +++ b/src/verifying.rs @@ -38,6 +38,7 @@ use serde_bytes::{ByteBuf as SerdeByteBuf, Bytes as SerdeBytes}; use signature::DigestVerifier; use crate::constants::*; +use crate::context::Context; use crate::errors::*; use crate::signature::*; use crate::signing::*; @@ -153,6 +154,15 @@ impl VerifyingKey { Ok(VerifyingKey(compressed, point)) } + /// Create a verifying context that can be used for Ed25519ph with + /// [`DigestVerifier`]. + pub fn with_context<'k, 'v>( + &'k self, + context_value: &'v [u8], + ) -> Result, SignatureError> { + Context::new(self, context_value) + } + /// Internal utility function for clamping a scalar representation and multiplying by the /// basepont to produce a public key. fn clamp_and_mul_base(bits: [u8; 32]) -> VerifyingKey { @@ -431,6 +441,23 @@ where } } +/// Equivalent to [`VerifyingKey::verify_prehashed`] with `context` set to [`Some`] +/// containing `self.value()`. +#[cfg(feature = "digest")] +impl DigestVerifier for Context<'_, '_, VerifyingKey> +where + D: Digest, +{ + fn verify_digest( + &self, + msg_digest: D, + signature: &ed25519::Signature, + ) -> Result<(), SignatureError> { + self.key() + .verify_prehashed(msg_digest, Some(self.value()), signature) + } +} + impl TryFrom<&[u8]> for VerifyingKey { type Error = SignatureError;