diff --git a/CHANGELOG.md b/CHANGELOG.md index 66b1ed1..6313216 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ Entries are listed in reverse chronological order. +## 1.0.0 + +* Widen generic bound on `EphemeralSecret::new` and `StaticSecret::new` to + allow owned as well as borrowed RNGs. +* Add `PublicKey::to_bytes` and `SharedSecret::to_bytes`, returning owned byte + arrays, complementing the existing `as_bytes` methods returning references. +* Remove mention of deprecated `rand_os` crate from examples. +* Clarify `EphemeralSecret`/`StaticSecret` distinction in documentation. + ## 0.6.0 * Updates `rand_core` version to `0.5`. diff --git a/Cargo.toml b/Cargo.toml index e86eb01..31e4457 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,11 @@ [package] name = "x25519-dalek" edition = "2018" -# Be sure to update the version in README.md -version = "0.6.0" +# Before changing this: +# - update version in README.md +# - update html_root_url +# - update CHANGELOG +version = "1.0.0" authors = [ "Isis Lovecruft ", "DebugSteven ", @@ -39,7 +42,7 @@ zeroize = { version = "1", default-features = false, features = ["zeroize_derive [dev-dependencies] bincode = "1" -criterion = "0.2" +criterion = "0.3.0" [[bench]] name = "x25519" diff --git a/README.md b/README.md index be90d5a..a05c1f5 100644 --- a/README.md +++ b/README.md @@ -31,45 +31,60 @@ the rest of the afternoon nomming some yummy pie! First, Alice uses `EphemeralSecret::new()` and then `PublicKey::from()` to produce her secret and public keys: -```rust,ignore -extern crate rand_os; -extern crate x25519_dalek; +```rust +use rand_core::OsRng; +use x25519_dalek::{EphemeralSecret, PublicKey}; -use rand_os::OsRng; - -use x25519_dalek::EphemeralSecret; -use x25519_dalek::PublicKey; - -let mut alice_csprng = OsRng::new().unwrap(); -let alice_secret = EphemeralSecret::new(&mut alice_csprng); -let alice_public = PublicKey::from(&alice_secret); +let alice_secret = EphemeralSecret::new(OsRng); +let alice_public = PublicKey::from(&alice_secret); ``` Bob does the same: -```rust,ignore -let mut bob_csprng = OsRng::new().unwrap(); -let bob_secret = EphemeralSecret::new(&mut bob_csprng); -let bob_public = PublicKey::from(&bob_secret); +```rust +# use rand_core::OsRng; +# use x25519_dalek::{EphemeralSecret, PublicKey}; +let bob_secret = EphemeralSecret::new(OsRng); +let bob_public = PublicKey::from(&bob_secret); ``` Alice meows across the room, telling `alice_public` to Bob, and Bob loudly meows `bob_public` back to Alice. Alice now computes her shared secret with Bob by doing: -```rust,ignore +```rust +# use rand_core::OsRng; +# use x25519_dalek::{EphemeralSecret, PublicKey}; +# let alice_secret = EphemeralSecret::new(OsRng); +# let alice_public = PublicKey::from(&alice_secret); +# let bob_secret = EphemeralSecret::new(OsRng); +# let bob_public = PublicKey::from(&bob_secret); let alice_shared_secret = alice_secret.diffie_hellman(&bob_public); ``` Similarly, Bob computes a shared secret by doing: -```rust,ignore +```rust +# use rand_core::OsRng; +# use x25519_dalek::{EphemeralSecret, PublicKey}; +# let alice_secret = EphemeralSecret::new(OsRng); +# let alice_public = PublicKey::from(&alice_secret); +# let bob_secret = EphemeralSecret::new(OsRng); +# let bob_public = PublicKey::from(&bob_secret); let bob_shared_secret = bob_secret.diffie_hellman(&alice_public); ``` These secrets are the same: -```rust,ignore +```rust +# use rand_core::OsRng; +# use x25519_dalek::{EphemeralSecret, PublicKey}; +# let alice_secret = EphemeralSecret::new(OsRng); +# let alice_public = PublicKey::from(&alice_secret); +# let bob_secret = EphemeralSecret::new(OsRng); +# let bob_public = PublicKey::from(&bob_secret); +# let alice_shared_secret = alice_secret.diffie_hellman(&bob_public); +# let bob_shared_secret = bob_secret.diffie_hellman(&alice_public); assert_eq!(alice_shared_secret.as_bytes(), bob_shared_secret.as_bytes()); ``` @@ -86,8 +101,8 @@ and load a long-term secret key. To install, add the following to your project's `Cargo.toml`: ```toml -[dependencies.x25519-dalek] -version = "0.6" +[dependencies] +x25519-dalek = "1" ``` # Documentation @@ -105,3 +120,11 @@ attempt to prevent software side-channels. copyright © Amy Wibowo ([@sailorhg](https://twitter.com/sailorhg)) [rfc7748]: https://tools.ietf.org/html/rfc7748 + +# See also + +- [crypto_box]: pure Rust public-key authenticated encryption compatible with + the NaCl family of encryption libraries (libsodium, TweetNaCl) which uses + `x25519-dalek` for key agreement + +[crypto_box]: https://github.com/RustCrypto/AEADs/tree/master/crypto_box diff --git a/src/lib.rs b/src/lib.rs index 0f80f7e..8a16049 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,6 +20,7 @@ #![cfg_attr(feature = "nightly", deny(missing_docs))] #![cfg_attr(feature = "nightly", doc(include = "../README.md"))] #![doc(html_logo_url = "https://doc.dalek.rs/assets/dalek-logo-clear.png")] +#![doc(html_root_url = "https://docs.rs/x25519-dalek/1.0.0")] //! Note that docs will only build on nightly Rust until //! `feature(external_doc)` is stabilized. diff --git a/src/x25519.rs b/src/x25519.rs index e95d0e3..9fbda07 100644 --- a/src/x25519.rs +++ b/src/x25519.rs @@ -23,8 +23,7 @@ use rand_core::RngCore; use zeroize::Zeroize; -/// A `PublicKey` is the corresponding public key converted from -/// an `EphemeralSecret` or a `StaticSecret` key. +/// A Diffie-Hellman public key, corresponding to an [`EphemeralSecret`] or [`StaticSecret`] key. #[cfg_attr(feature = "serde", serde(crate = "our_serde"))] #[cfg_attr( feature = "serde", @@ -41,6 +40,12 @@ impl From<[u8; 32]> for PublicKey { } impl PublicKey { + /// Convert this public key to a byte array. + #[inline] + pub fn to_bytes(&self) -> [u8; 32] { + self.0.to_bytes() + } + /// View this public key as a byte array. #[inline] pub fn as_bytes(&self) -> &[u8; 32] { @@ -48,24 +53,27 @@ impl PublicKey { } } -/// A `EphemeralSecret` is a short lived Diffie-Hellman secret key -/// used to create a `SharedSecret` when given their `PublicKey`. +/// A short-lived Diffie-Hellman secret key that can only be used to compute a single +/// [`SharedSecret`]. +/// +/// This type is identical to the [`StaticSecret`] type, except that the +/// [`EphemeralSecret::diffie_hellman`] method consumes and then wipes the secret key, and there +/// are no serialization methods defined. This means that [`EphemeralSecret`]s can only be +/// generated from fresh randomness by [`EphemeralSecret::new`] and the compiler statically checks +/// that the resulting secret is used at most once. #[derive(Zeroize)] #[zeroize(drop)] pub struct EphemeralSecret(pub(crate) Scalar); impl EphemeralSecret { /// Perform a Diffie-Hellman key agreement between `self` and - /// `their_public` key to produce a `SharedSecret`. + /// `their_public` key to produce a [`SharedSecret`]. pub fn diffie_hellman(self, their_public: &PublicKey) -> SharedSecret { SharedSecret(self.0 * their_public.0) } - /// Generate an x25519 `EphemeralSecret` key. - pub fn new(csprng: &mut T) -> Self - where - T: RngCore + CryptoRng, - { + /// Generate an x25519 [`EphemeralSecret`] key. + pub fn new(mut csprng: T) -> Self { let mut bytes = [0u8; 32]; csprng.fill_bytes(&mut bytes); @@ -75,16 +83,27 @@ impl EphemeralSecret { } impl<'a> From<&'a EphemeralSecret> for PublicKey { - /// Given an x25519 `EphemeralSecret` key, compute its corresponding - /// `PublicKey` key. + /// Given an x25519 [`EphemeralSecret`] key, compute its corresponding [`PublicKey`]. fn from(secret: &'a EphemeralSecret) -> PublicKey { PublicKey((&ED25519_BASEPOINT_TABLE * &secret.0).to_montgomery()) } } -/// A `StaticSecret` is a static Diffie-Hellman secret key that -/// can be saved and loaded to create a `SharedSecret` when given -/// their `PublicKey`. +/// A Diffie-Hellman secret key that can be used to compute multiple [`SharedSecret`]s. +/// +/// This type is identical to the [`EphemeralSecret`] type, except that the +/// [`StaticSecret::diffie_hellman`] method does not consume the secret key, and the type provides +/// serialization methods to save and load key material. This means that the secret may be used +/// multiple times (but does not *have to be*). +/// +/// Some protocols, such as Noise, already handle the static/ephemeral distinction, so the +/// additional guarantees provided by [`EphemeralSecret`] are not helpful or would cause duplicate +/// code paths. In this case, it may be useful to +/// ```rust,ignore +/// use x25519_dalek::StaticSecret as SecretKey; +/// ``` +/// since the only difference between the two is that [`StaticSecret`] does not enforce at +/// compile-time that the key is only used once. #[cfg_attr(feature = "serde", serde(crate = "our_serde"))] #[cfg_attr( feature = "serde", @@ -103,11 +122,8 @@ impl StaticSecret { SharedSecret(&self.0 * their_public.0) } - /// Generate a x25519 `StaticSecret` key. - pub fn new(csprng: &mut T) -> Self - where - T: RngCore + CryptoRng, - { + /// Generate an x25519 key. + pub fn new(mut csprng: T) -> Self { let mut bytes = [0u8; 32]; csprng.fill_bytes(&mut bytes); @@ -115,34 +131,41 @@ impl StaticSecret { StaticSecret(clamp_scalar(bytes)) } - /// Save a x25519 `StaticSecret` key's bytes. + /// Extract this key's bytes for serialization. pub fn to_bytes(&self) -> [u8; 32] { self.0.to_bytes() } } impl From<[u8; 32]> for StaticSecret { - /// Load a `StaticSecret` from a byte array. + /// Load a secret key from a byte array. fn from(bytes: [u8; 32]) -> StaticSecret { StaticSecret(clamp_scalar(bytes)) } } impl<'a> From<&'a StaticSecret> for PublicKey { - /// Given an x25519 `StaticSecret` key, compute its corresponding - /// `PublicKey` key. + /// Given an x25519 [`StaticSecret`] key, compute its corresponding [`PublicKey`]. fn from(secret: &'a StaticSecret) -> PublicKey { PublicKey((&ED25519_BASEPOINT_TABLE * &secret.0).to_montgomery()) } } -/// A `SharedSecret` is a Diffie-Hellman shared secret that’s generated -/// from your `EphemeralSecret` or `StaticSecret` and their `PublicKey`. +/// The result of a Diffie-Hellman key exchange. +/// +/// Each party computes this using their [`EphemeralSecret`] or [`StaticSecret`] and their +/// counterparty's [`PublicKey`]. #[derive(Zeroize)] #[zeroize(drop)] pub struct SharedSecret(pub(crate) MontgomeryPoint); impl SharedSecret { + /// Convert this shared secret to a byte array. + #[inline] + pub fn to_bytes(&self) -> [u8; 32] { + self.0.to_bytes() + } + /// View this shared secret key as a byte array. #[inline] pub fn as_bytes(&self) -> &[u8; 32] { @@ -205,21 +228,6 @@ mod test { use rand_core::OsRng; - // This was previously a doctest but it got moved to the README to - // avoid duplication where it then wasn't being run, so now it - // lives here. - #[test] - fn alice_and_bob() { - let alice_secret = EphemeralSecret::new(&mut OsRng); - let alice_public = PublicKey::from(&alice_secret); - let bob_secret = EphemeralSecret::new(&mut OsRng); - let bob_public = PublicKey::from(&bob_secret); - let alice_shared_secret = alice_secret.diffie_hellman(&bob_public); - let bob_shared_secret = bob_secret.diffie_hellman(&alice_public); - - assert_eq!(alice_shared_secret.as_bytes(), bob_shared_secret.as_bytes()); - } - #[test] fn byte_basepoint_matches_edwards_scalar_mul() { let mut scalar_bytes = [0x37; 32];