diff --git a/Cargo.lock b/Cargo.lock index 8c3bc5dc0..6eec93364 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -286,6 +286,7 @@ checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ "block-buffer 0.10.2", "crypto-common 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "subtle", ] [[package]] @@ -302,7 +303,7 @@ checksum = "43ee23aa5b4f68c7a092b5c3beb25f50c406adc75e2363634f242f28ab255372" dependencies = [ "der 0.4.5", "elliptic-curve 0.10.4", - "hmac", + "hmac 0.11.0", "signature 1.3.2", ] @@ -335,6 +336,7 @@ dependencies = [ "generic-array", "group 0.12.0", "hex-literal 0.3.4", + "hkdf 0.12.3", "pem-rfc7468 0.5.1", "pkcs8 0.9.0", "rand_core", @@ -475,7 +477,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b" dependencies = [ "digest 0.9.0", - "hmac", + "hmac 0.11.0", +] + +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac 0.12.1", ] [[package]] @@ -488,6 +499,15 @@ dependencies = [ "digest 0.9.0", ] +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "inout" version = "0.1.3" @@ -992,7 +1012,7 @@ dependencies = [ "bincode", "const-oid 0.6.2", "getrandom", - "hkdf", + "hkdf 0.11.0", "p256", "rand_core", "serde", diff --git a/elliptic-curve/Cargo.toml b/elliptic-curve/Cargo.toml index 92e5c7b7a..4f3dd3283 100644 --- a/elliptic-curve/Cargo.toml +++ b/elliptic-curve/Cargo.toml @@ -29,6 +29,7 @@ base64ct = { version = "1", optional = true, default-features = false } digest = { version = "0.10", optional = true } ff = { version = "0.12", optional = true, default-features = false } group = { version = "0.12", optional = true, default-features = false } +hkdf = { version = "0.12", optional = true, default-features = false } hex-literal = { version = "0.3", optional = true } pem-rfc7468 = { version = "0.5", optional = true } pkcs8 = { version = "0.9", optional = true, default-features = false } @@ -48,7 +49,7 @@ arithmetic = ["ff", "group"] bits = ["arithmetic", "ff/bits"] dev = ["arithmetic", "hex-literal", "pem", "pkcs8"] hash2curve = ["arithmetic", "digest"] -ecdh = ["arithmetic"] +ecdh = ["arithmetic", "digest", "hkdf"] hazmat = [] jwk = ["alloc", "base64ct/alloc", "serde", "serde_json", "zeroize/alloc"] pem = ["alloc", "arithmetic", "pem-rfc7468/alloc", "pkcs8", "sec1/pem"] diff --git a/elliptic-curve/src/ecdh.rs b/elliptic-curve/src/ecdh.rs index 7b6540d0e..1e9f7bc31 100644 --- a/elliptic-curve/src/ecdh.rs +++ b/elliptic-curve/src/ecdh.rs @@ -31,7 +31,9 @@ use crate::{ ProjectiveArithmetic, ProjectivePoint, PublicKey, }; use core::borrow::Borrow; +use digest::{crypto_common::BlockSizeUser, Digest}; use group::Curve as _; +use hkdf::{hmac::SimpleHmac, Hkdf}; use rand_core::{CryptoRng, RngCore}; use zeroize::{Zeroize, ZeroizeOnDrop}; @@ -150,20 +152,6 @@ where } /// Shared secret value computed via ECDH key agreement. -/// -/// This value contains the raw serialized x-coordinate of the elliptic curve -/// point computed from a Diffie-Hellman exchange. -/// -/// # ⚠️ WARNING: NOT UNIFORMLY RANDOM! ⚠️ -/// -/// This value is not uniformly random and should not be used directly -/// as a cryptographic key for anything which requires that property -/// (e.g. symmetric ciphers). -/// -/// Instead, the resulting value should be used as input to a Key Derivation -/// Function (KDF) or cryptographic hash function to produce a symmetric key. -// TODO(tarcieri): KDF traits and support for deriving uniform keys -// See: https://github.com/RustCrypto/traits/issues/5 pub struct SharedSecret { /// Computed secret value secret_bytes: FieldBytes, @@ -181,13 +169,48 @@ impl SharedSecret { } } - /// Shared secret value, serialized as bytes. + /// Use [HKDF] (HMAC-based Extract-and-Expand Key Derivation Function) to + /// extract entropy from this shared secret. + /// + /// This method can be used to transform the shared secret into uniformly + /// random values which are suitable as key material. + /// + /// The `D` type parameter is a cryptographic digest function. + /// `sha2::Sha256` is a common choice for use with HKDF. + /// + /// The `salt` parameter can be used to supply additional randomness. + /// Some examples include: + /// + /// - randomly generated (but authenticated) string + /// - fixed application-specific value + /// - previous shared secret used for rekeying (as in TLS 1.3 and Noise) + /// + /// After initializing HKDF, use [`Hkdf::expand`] to obtain output key + /// material. + /// + /// [HKDF]: https://en.wikipedia.org/wiki/HKDF + pub fn extract(&self, salt: Option<&[u8]>) -> Hkdf> + where + D: BlockSizeUser + Clone + Digest, + { + Hkdf::new(salt, &self.secret_bytes) + } + + /// This value contains the raw serialized x-coordinate of the elliptic curve + /// point computed from a Diffie-Hellman exchange, serialized as bytes. + /// + /// When in doubt, use [`SharedSecret::extract`] instead. + /// + /// # ⚠️ WARNING: NOT UNIFORMLY RANDOM! ⚠️ + /// + /// This value is not uniformly random and should not be used directly + /// as a cryptographic key for anything which requires that property + /// (e.g. symmetric ciphers). /// - /// As noted in the comments for this struct, this value is non-uniform and - /// should not be used directly as a symmetric encryption key, but instead - /// as input to a KDF (or failing that, a hash function) used to produce - /// a symmetric key. - pub fn as_bytes(&self) -> &FieldBytes { + /// Instead, the resulting value should be used as input to a Key Derivation + /// Function (KDF) or cryptographic hash function to produce a symmetric key. + /// The [`SharedSecret::extract`] function will do this for you. + pub fn raw_secret_bytes(&self) -> &FieldBytes { &self.secret_bytes } } diff --git a/elliptic-curve/src/secret_key.rs b/elliptic-curve/src/secret_key.rs index 531719783..f6fd70774 100644 --- a/elliptic-curve/src/secret_key.rs +++ b/elliptic-curve/src/secret_key.rs @@ -78,9 +78,7 @@ pub(crate) const SEC1_PEM_TYPE_LABEL: &str = "EC PRIVATE KEY"; /// /// To decode an elliptic curve private key from PKCS#8, enable the `pkcs8` /// feature of this crate (or the `pkcs8` feature of a specific RustCrypto -/// elliptic curve crate) and use the -/// [`DecodePrivateKey`][`elliptic_curve::pkcs8::DecodePrivateKey`] -/// trait to parse it. +/// elliptic curve crate) and use the [`DecodePrivateKey`] trait to parse it. /// /// When the `pem` feature of this crate (or a specific RustCrypto elliptic /// curve crate) is enabled, a [`FromStr`] impl is also available.