diff --git a/.travis.yml b/.travis.yml index a5b5c1f..eceb10e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ rust: env: - TEST_COMMAND=test EXTRA_FLAGS='' FEATURES='default' + - TEST_COMMAND=test EXTRA_FLAGS='' FEATURES='nightly' - TEST_COMMAND=bench EXTRA_FLAGS='' FEATURES='default' - TEST_COMMAND=build EXTRA_FLAGS='--no-default-features' FEATURES='u32_backend nightly' - TEST_COMMAND=build EXTRA_FLAGS='--no-default-features' FEATURES='u64_backend nightly' diff --git a/README.md b/README.md index d1cbb4c..f435094 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ with curve operations provided by This crate provides two levels of API: a bare byte-oriented `x25519` function which matches the function specified in [RFC7748][rfc7748], as -well as a higher-level Rust API for ephemeral Diffie-Hellman. +well as a higher-level Rust API for static and ephemeral Diffie-Hellman. ## Examples @@ -29,50 +29,111 @@ kittens will be able to secretly organise to find their mittens, and then spend the rest of the afternoon nomming some yummy pie! First, Alice uses `EphemeralSecret::new()` and then -`EphemeralPublic::from()` to produce her secret and public keys: +`PublicKey::from()` to produce her secret and public keys: ```rust -extern crate x25519_dalek; extern crate rand_os; - -use x25519_dalek::EphemeralPublic; -use x25519_dalek::EphemeralSecret; use rand_os::OsRng; +extern crate x25519_dalek; +use x25519_dalek::EphemeralSecret; +use x25519_dalek::PublicKey; + +# fn main() { let mut alice_csprng = OsRng::new().unwrap(); let alice_secret = EphemeralSecret::new(&mut alice_csprng); -let alice_public = EphemeralPublic::from(&alice_secret); +let alice_public = PublicKey::from(&alice_secret); +# } ``` Bob does the same: -```rust,ignore +```rust +# extern crate rand_os; +# use rand_os::OsRng; +# +# extern crate x25519_dalek; +# use x25519_dalek::EphemeralSecret; +# use x25519_dalek::PublicKey; +# fn main() { let mut bob_csprng = OsRng::new().unwrap(); let bob_secret = EphemeralSecret::new(&mut bob_csprng); -let bob_public = EphemeralPublic::from(&bob_secret); +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 -use x25519_dalek::EphemeralPublic; -use x25519_dalek::EphemeralSecret; - -let shared_secret = EphemeralSecret::diffie_hellman(alice_secret, &bob_public); +```rust +# extern crate rand_os; +# use rand_os::OsRng; +# +# extern crate x25519_dalek; +# use x25519_dalek::EphemeralSecret; +# use x25519_dalek::PublicKey; +# +# fn main() { +# let mut csprng = OsRng::new().unwrap(); +# let alice_secret = EphemeralSecret::new(&mut csprng); +# let alice_public = PublicKey::from(&alice_secret); +# let bob_secret = EphemeralSecret::new(&mut csprng); +# let bob_public = PublicKey::from(&bob_secret); +let alice_shared_secret = alice_secret.diffie_hellman(&bob_public); +# } ``` -Similarly, Bob computes the same shared secret by doing: +Similarly, Bob computes a shared secret by doing: -```rust,ignore -let shared_secret = EphemeralSecret::diffie_hellman(bob_secret, &alice_public); +```rust +# extern crate rand_os; +# use rand_os::OsRng; +# +# extern crate x25519_dalek; +# use x25519_dalek::EphemeralSecret; +# use x25519_dalek::PublicKey; +# +# fn main() { +# let mut csprng = OsRng::new().unwrap(); +# let alice_secret = EphemeralSecret::new(&mut csprng); +# let alice_public = PublicKey::from(&alice_secret); +# let bob_secret = EphemeralSecret::new(&mut csprng); +# let bob_public = PublicKey::from(&bob_secret); +let bob_shared_secret = bob_secret.diffie_hellman(&alice_public); +# } +``` + +These secrets are the same: + +```rust +# extern crate rand_os; +# use rand_os::OsRng; +# +# extern crate x25519_dalek; +# use x25519_dalek::EphemeralSecret; +# use x25519_dalek::PublicKey; +# +# fn main() { +# let mut csprng = OsRng::new().unwrap(); +# let alice_secret = EphemeralSecret::new(&mut csprng); +# let alice_public = PublicKey::from(&alice_secret); +# let bob_secret = EphemeralSecret::new(&mut csprng); +# 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()); +# } ``` Voilá! Alice and Bob can now use their shared secret to encrypt their meows, for example, by using it to generate a key and nonce for an authenticated-encryption cipher. +This example used the ephemeral DH API, which ensures that secret keys +cannot be reused; Alice and Bob could instead use the static DH API +and load a long-term secret key. + # Installation To install, add the following to your project's `Cargo.toml`: diff --git a/benches/x25519.rs b/benches/x25519.rs index 4660a69..cfded70 100644 --- a/benches/x25519.rs +++ b/benches/x25519.rs @@ -23,13 +23,13 @@ use curve25519_dalek::montgomery::MontgomeryPoint; use rand_os::OsRng; -use x25519_dalek::EphemeralPublic; +use x25519_dalek::PublicKey; use x25519_dalek::EphemeralSecret; fn bench_diffie_hellman(c: &mut Criterion) { let mut csprng: OsRng = OsRng::new().unwrap(); - let bob_secret: EphemeralSecret = EphemeralSecret::new(&mut csprng); - let bob_public: EphemeralPublic = EphemeralPublic::from(&bob_secret); + let bob_secret = EphemeralSecret::new(&mut csprng); + let bob_public = PublicKey::from(&bob_secret); c.bench_function("diffie_hellman", move |b| { b.iter_with_setup( diff --git a/src/x25519.rs b/src/x25519.rs index f3c6870..511a9b5 100644 --- a/src/x25519.rs +++ b/src/x25519.rs @@ -23,25 +23,27 @@ use curve25519_dalek::scalar::Scalar; use rand_core::RngCore; use rand_core::CryptoRng; -/// A DH ephemeral public key. -pub struct EphemeralPublic(pub (crate) MontgomeryPoint); +/// A `PublicKey` is the corresponding public key converted from +/// an `EphemeralSecret` or a `StaticSecret` key. +pub struct PublicKey(pub (crate) MontgomeryPoint); -impl From<[u8; 32]> for EphemeralPublic { - /// Given a byte array, construct an x25519 `EphemeralPublic` key - fn from(bytes: [u8; 32]) -> EphemeralPublic { - EphemeralPublic(MontgomeryPoint(bytes)) +impl From<[u8; 32]> for PublicKey { + /// Given a byte array, construct a x25519 `PublicKey`. + fn from(bytes: [u8; 32]) -> PublicKey { + PublicKey(MontgomeryPoint(bytes)) } } -impl EphemeralPublic { - /// View this ephemeral public key as a byte array. +impl PublicKey { + /// View this public key as a byte array. #[inline] pub fn as_bytes(&self) -> &[u8; 32] { self.0.as_bytes() } } -/// A DH ephemeral secret key. +/// A `EphemeralSecret` is a short lived Diffie-Hellman secret key +/// used to create a `SharedSecret` when given their `PublicKey`. pub struct EphemeralSecret(pub (crate) Scalar); /// Overwrite ephemeral secret key material with null bytes when it goes out of scope. @@ -52,10 +54,9 @@ impl Drop for EphemeralSecret { } impl EphemeralSecret { - /// Utility function to make it easier to call `x25519()` with - /// an ephemeral secret key and montegomery point as input and - /// a shared secret as the output. - pub fn diffie_hellman(self, their_public: &EphemeralPublic) -> SharedSecret { + /// Perform a Diffie-Hellman key agreement between `self` and + /// `their_public` key to produce a `SharedSecret`. + pub fn diffie_hellman(self, their_public: &PublicKey) -> SharedSecret { SharedSecret(self.0 * their_public.0) } @@ -72,16 +73,70 @@ impl EphemeralSecret { } -impl<'a> From<&'a EphemeralSecret> for EphemeralPublic { +impl<'a> From<&'a EphemeralSecret> for PublicKey { /// Given an x25519 `EphemeralSecret` key, compute its corresponding - /// `EphemeralPublic` key. - fn from(secret: &'a EphemeralSecret) -> EphemeralPublic { - EphemeralPublic((&ED25519_BASEPOINT_TABLE * &secret.0).to_montgomery()) + /// `PublicKey` key. + fn from(secret: &'a EphemeralSecret) -> PublicKey { + PublicKey((&ED25519_BASEPOINT_TABLE * &secret.0).to_montgomery()) } } -/// A DH SharedSecret +/// A `StaticSecret` is a static Diffie-Hellman secret key that +/// can be saved and loaded to create a `SharedSecret` when given +/// their `PublicKey`. +pub struct StaticSecret(pub (crate) Scalar); + +/// Overwrite static secret key material with null bytes when it goes out of scope. +impl Drop for StaticSecret { + fn drop(&mut self) { + self.0.clear(); + } +} + +impl StaticSecret { + /// Perform a Diffie-Hellman key agreement between `self` and + /// `their_public` key to produce a `SharedSecret`. + pub fn diffie_hellman(&self, their_public: &PublicKey) -> SharedSecret { + SharedSecret(&self.0 * their_public.0) + } + + /// Generate a x25519 `StaticSecret` key. + pub fn new(csprng: &mut T) -> Self + where T: RngCore + CryptoRng + { + let mut bytes = [0u8; 32]; + + csprng.fill_bytes(&mut bytes); + + StaticSecret(clamp_scalar(bytes)) + } + + /// Save a x25519 `StaticSecret` key's bytes. + pub fn to_bytes(&self) -> [u8; 32] { + self.0.to_bytes() + } + +} + +impl From<[u8; 32]> for StaticSecret { + /// Load a `StaticSecret` 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. + 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`. pub struct SharedSecret(pub (crate) MontgomeryPoint); /// Overwrite shared secret material with null bytes when it goes out of scope. @@ -120,14 +175,14 @@ fn clamp_scalar(scalar: [u8; 32]) -> Scalar { /// The bare, byte-oriented x25519 function, exactly as specified in RFC7748. /// /// This can be used with [`X25519_BASEPOINT_BYTES`] for people who -/// cannot use the better, safer, and faster ephemeral DH API. +/// cannot use the better, safer, and faster DH API. pub fn x25519(k: [u8; 32], u: [u8; 32]) -> [u8; 32] { (clamp_scalar(k) * MontgomeryPoint(u)).to_bytes() } /// The X25519 basepoint, for use with the bare, byte-oriented x25519 /// function. This is provided for people who cannot use the typed -/// ephemeral DH API for some reason. +/// DH API for some reason. pub const X25519_BASEPOINT_BYTES: [u8; 32] = [ 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ];