diff --git a/Cargo.lock b/Cargo.lock index b7d61d6..9a0872c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -202,6 +202,7 @@ dependencies = [ "aead 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "base64 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", "curve25519-dalek 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "generic-array 0.14.2 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "hkdf 0.9.0-alpha.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 0881562..6966080 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ slow-hash = ["scrypt"] [dependencies] aead = "0.3.1" curve25519-dalek = "2.1.0" +digest = "0.9" generic-array = "0.14.2" hkdf = "0.9.0-alpha.0" hmac = "0.8.0" diff --git a/src/ciphersuite.rs b/src/ciphersuite.rs index 9f96968..10e2938 100644 --- a/src/ciphersuite.rs +++ b/src/ciphersuite.rs @@ -9,17 +9,22 @@ use crate::{ errors::InternalPakeError, group::Group, keypair::{Key, KeyPair}, + oprf::HkdfDigest, slow_hash::SlowHash, }; -use generic_array::typenum::{U32, U64}; +use digest::FixedOutput; +use generic_array::typenum::{U32}; use rand_core::{CryptoRng, RngCore}; /// Configures the underlying primitives used in OPAQUE -/// * Group: a finite cyclic group along with a point representation -/// * KeyFormat: a keypair type composed of public and private components -/// * SlowHash: a slow hashing function, typically used for password hashing +/// * `Digest`: a digest suitable for use in an Hkdf, with an output length equal +/// to the input of the hash-to-curve function of the `Group` parameter. +/// * `Group`: a finite cyclic group along with a point representation +/// * `KeyFormat`: a keypair type composed of public and private components +/// * `SlowHash`: a slow hashing function, typically used for password hashing pub trait CipherSuite { - type Group: Group; + type Digest: HkdfDigest; + type Group: Group::OutputSize>; type KeyFormat: KeyPair + PartialEq; type SlowHash: SlowHash; diff --git a/src/envelope.rs b/src/envelope.rs index bb6bbb5..7f481e2 100644 --- a/src/envelope.rs +++ b/src/envelope.rs @@ -11,14 +11,16 @@ use generic_array::{ use hkdf::Hkdf; use hmac::{Hmac, Mac, NewMac}; use rand_core::{CryptoRng, RngCore}; -use sha2::{Digest, Sha256}; +use sha2::Sha256; // Constant string used as salt for HKDF computation const STR_ENVU: &[u8] = b"EnvU"; /// The length of the "export key" output by the client registration /// and login finish steps -pub(crate) type ExportKeySize = ::OutputSize; +pub(crate) type ExportKeySize = U32; + +const NONCE_LEN: usize = 32; /// This struct is an instantiation of the envelope as described in /// https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-06#section-4 @@ -33,42 +35,32 @@ pub(crate) type ExportKeySize = ::OutputSize; pub(crate) struct Envelope { nonce: Vec, ciphertext: Vec, - hmac: Vec, + hmac: GenericArray, } -type NonceLen = U32; - impl Envelope { /// The additional number of bytes added to the plaintext pub(crate) fn additional_size() -> usize { - Self::nonce_size() + ::OutputSize::to_usize() + NONCE_LEN + U32::to_usize() } fn hmac_key_size() -> usize { - ::OutputSize::to_usize() + U32::to_usize() } fn hmac_size() -> usize { - ::OutputSize::to_usize() - } - - fn nonce_size() -> usize { - NonceLen::to_usize() + U32::to_usize() } fn export_key_size() -> usize { ExportKeySize::to_usize() } - pub(crate) fn new( - nonce: Vec, - ciphertext: Vec, - hmac: &GenericArray::OutputSize>, - ) -> Self { + pub(crate) fn new(nonce: Vec, ciphertext: Vec, hmac: GenericArray) -> Self { Self { nonce, ciphertext, - hmac: hmac.to_vec(), + hmac, } } @@ -76,13 +68,13 @@ impl Envelope { /// nonce | ciphertext | hmac /// nonce_size bytes | variable length | hmac_size bytes pub(crate) fn from_bytes(bytes: &[u8]) -> Result { - let ciphertext_start = Self::nonce_size(); + let ciphertext_start = NONCE_LEN; let ciphertext_end = bytes.len() - Self::hmac_size(); Ok(Self::new( bytes[..ciphertext_start].to_vec(), bytes[ciphertext_start..ciphertext_end].to_vec(), - GenericArray::from_slice(&bytes[ciphertext_end..]), + GenericArray::clone_from_slice(&bytes[ciphertext_end..]), )) } @@ -98,7 +90,7 @@ impl Envelope { aad: &[u8], rng: &mut R, ) -> Result<(Self, GenericArray), InternalPakeError> { - let mut nonce = vec![0u8; Self::nonce_size()]; + let mut nonce = vec![0u8; NONCE_LEN]; rng.fill_bytes(&mut nonce); let h = Hkdf::::new(Some(&nonce), &key); @@ -121,7 +113,7 @@ impl Envelope { hmac.update(&aad); Ok(( - Self::new(nonce, ciphertext.to_vec(), &hmac.finalize().into_bytes()), + Self::new(nonce, ciphertext.to_vec(), hmac.finalize().into_bytes()), *GenericArray::from_slice(&export_key), )) } diff --git a/src/group.rs b/src/group.rs index 44547d2..27d0b39 100644 --- a/src/group.rs +++ b/src/group.rs @@ -140,7 +140,7 @@ impl Group for EdwardsPoint { *GenericArray::from_slice(c.as_bytes()) } - type UniformBytesLen = U64; + type UniformBytesLen = U32; fn hash_to_curve(uniform_bytes: &GenericArray) -> Self { let mut result = [0u8; 32]; let mut counter = 0; diff --git a/src/key_exchange.rs b/src/key_exchange.rs index 39ca876..494f5a9 100644 --- a/src/key_exchange.rs +++ b/src/key_exchange.rs @@ -9,7 +9,7 @@ use crate::{ sized_bytes_using_constant_and_try_from, }; use generic_array::{ - typenum::{U64, U96}, + typenum::{U32, U64, U96}, GenericArray, }; use hkdf::Hkdf; @@ -199,9 +199,9 @@ struct TripleDHComponents { // Consists of a shared secret, followed by two mac keys type TripleDHDerivationResult = ( - GenericArray::OutputSize>, - GenericArray::OutputSize>, - GenericArray::OutputSize>, + GenericArray, + GenericArray, + GenericArray, ); // Internal function which takes the public and private components of the client and server keypairs, along diff --git a/src/lib.rs b/src/lib.rs index cea3e78..897889d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,7 @@ //! use opaque_ke::ciphersuite::CipherSuite; //! struct Default; //! impl CipherSuite for Default { +//! type Digest = sha2::Sha512; //! type Group = curve25519_dalek::ristretto::RistrettoPoint; //! type KeyFormat = opaque_ke::keypair::X25519KeyPair; //! type SlowHash = opaque_ke::slow_hash::NoOpHash; @@ -41,6 +42,7 @@ //! # use opaque_ke::ciphersuite::CipherSuite; //! # struct Default; //! # impl CipherSuite for Default { +//! # type Digest = sha2::Sha512; //! # type Group = curve25519_dalek::ristretto::RistrettoPoint; //! # type KeyFormat = opaque_ke::keypair::X25519KeyPair; //! # type SlowHash = opaque_ke::slow_hash::NoOpHash; @@ -72,6 +74,7 @@ //! # use opaque_ke::ciphersuite::CipherSuite; //! # struct Default; //! # impl CipherSuite for Default { +//! # type Digest = sha2::Sha512; //! # type Group = curve25519_dalek::ristretto::RistrettoPoint; //! # type KeyFormat = opaque_ke::keypair::X25519KeyPair; //! # type SlowHash = opaque_ke::slow_hash::NoOpHash; @@ -100,6 +103,7 @@ //! # use opaque_ke::ciphersuite::CipherSuite; //! # struct Default; //! # impl CipherSuite for Default { +//! # type Digest = sha2::Sha512; //! # type Group = curve25519_dalek::ristretto::RistrettoPoint; //! # type KeyFormat = opaque_ke::keypair::X25519KeyPair; //! # type SlowHash = opaque_ke::slow_hash::NoOpHash; @@ -131,6 +135,7 @@ //! # use opaque_ke::ciphersuite::CipherSuite; //! # struct Default; //! # impl CipherSuite for Default { +//! # type Digest = sha2::Sha512; //! # type Group = curve25519_dalek::ristretto::RistrettoPoint; //! # type KeyFormat = opaque_ke::keypair::X25519KeyPair; //! # type SlowHash = opaque_ke::slow_hash::NoOpHash; @@ -164,6 +169,7 @@ //! # use opaque_ke::ciphersuite::CipherSuite; //! # struct Default; //! # impl CipherSuite for Default { +//! # type Digest = sha2::Sha512; //! # type Group = curve25519_dalek::ristretto::RistrettoPoint; //! # type KeyFormat = opaque_ke::keypair::X25519KeyPair; //! # type SlowHash = opaque_ke::slow_hash::NoOpHash; @@ -205,6 +211,7 @@ //! # use opaque_ke::ciphersuite::CipherSuite; //! # struct Default; //! # impl CipherSuite for Default { +//! # type Digest = sha2::Sha512; //! # type Group = curve25519_dalek::ristretto::RistrettoPoint; //! # type KeyFormat = opaque_ke::keypair::X25519KeyPair; //! # type SlowHash = opaque_ke::slow_hash::NoOpHash; @@ -233,6 +240,7 @@ //! # use opaque_ke::ciphersuite::CipherSuite; //! # struct Default; //! # impl CipherSuite for Default { +//! # type Digest = sha2::Sha512; //! # type Group = curve25519_dalek::ristretto::RistrettoPoint; //! # type KeyFormat = opaque_ke::keypair::X25519KeyPair; //! # type SlowHash = opaque_ke::slow_hash::NoOpHash; @@ -276,6 +284,7 @@ //! # use opaque_ke::ciphersuite::CipherSuite; //! # struct Default; //! # impl CipherSuite for Default { +//! # type Digest = sha2::Sha512; //! # type Group = curve25519_dalek::ristretto::RistrettoPoint; //! # type KeyFormat = opaque_ke::keypair::X25519KeyPair; //! # type SlowHash = opaque_ke::slow_hash::NoOpHash; @@ -331,6 +340,7 @@ //! # use opaque_ke::ciphersuite::CipherSuite; //! # struct Default; //! # impl CipherSuite for Default { +//! # type Digest = sha2::Sha512; //! # type Group = curve25519_dalek::ristretto::RistrettoPoint; //! # type KeyFormat = opaque_ke::keypair::X25519KeyPair; //! # type SlowHash = opaque_ke::slow_hash::NoOpHash; diff --git a/src/opaque.rs b/src/opaque.rs index 5b306cc..82493e9 100644 --- a/src/opaque.rs +++ b/src/opaque.rs @@ -290,6 +290,7 @@ impl ClientRegistration { /// use opaque_ke::ciphersuite::CipherSuite; /// struct Default; /// impl CipherSuite for Default { + /// type Digest = sha2::Sha512; /// type Group = curve25519_dalek::ristretto::RistrettoPoint; /// type KeyFormat = opaque_ke::keypair::X25519KeyPair; /// type SlowHash = opaque_ke::slow_hash::NoOpHash; @@ -306,7 +307,11 @@ impl ClientRegistration { let OprfClientBytes { alpha, blinding_factor, - } = oprf::generate_oprf1::(&password, pepper, blinding_factor_rng)?; + } = oprf::generate_oprf1::( + &password, + pepper, + blinding_factor_rng, + )?; Ok(( RegisterFirstMessage:: { alpha }, @@ -340,6 +345,7 @@ impl ClientRegistration { /// use opaque_ke::ciphersuite::CipherSuite; /// struct Default; /// impl CipherSuite for Default { + /// type Digest = sha2::Sha512; /// type Group = curve25519_dalek::ristretto::RistrettoPoint; /// type KeyFormat = opaque_ke::keypair::X25519KeyPair; /// type SlowHash = opaque_ke::slow_hash::NoOpHash; @@ -503,6 +509,7 @@ where /// use opaque_ke::ciphersuite::CipherSuite; /// struct Default; /// impl CipherSuite for Default { + /// type Digest = sha2::Sha512; /// type Group = curve25519_dalek::ristretto::RistrettoPoint; /// type KeyFormat = opaque_ke::keypair::X25519KeyPair; /// type SlowHash = opaque_ke::slow_hash::NoOpHash; @@ -549,6 +556,7 @@ where /// use opaque_ke::ciphersuite::CipherSuite; /// struct Default; /// impl CipherSuite for Default { + /// type Digest = sha2::Sha512; /// type Group = curve25519_dalek::ristretto::RistrettoPoint; /// type KeyFormat = opaque_ke::keypair::X25519KeyPair; /// type SlowHash = opaque_ke::slow_hash::NoOpHash; @@ -637,6 +645,7 @@ impl ClientLogin { /// use opaque_ke::ciphersuite::CipherSuite; /// struct Default; /// impl CipherSuite for Default { + /// type Digest = sha2::Sha512; /// type Group = curve25519_dalek::ristretto::RistrettoPoint; /// type KeyFormat = opaque_ke::keypair::X25519KeyPair; /// type SlowHash = opaque_ke::slow_hash::NoOpHash; @@ -653,7 +662,7 @@ impl ClientLogin { let OprfClientBytes { alpha, blinding_factor, - } = oprf::generate_oprf1::(&password, pepper, rng)?; + } = oprf::generate_oprf1::(&password, pepper, rng)?; let (ke1_state, ke1_message) = generate_ke1::<_, CS::KeyFormat>(alpha.to_arr().to_vec(), rng)?; @@ -688,6 +697,7 @@ impl ClientLogin { /// use opaque_ke::ciphersuite::CipherSuite; /// struct Default; /// impl CipherSuite for Default { + /// type Digest = sha2::Sha512; /// type Group = curve25519_dalek::ristretto::RistrettoPoint; /// type KeyFormat = opaque_ke::keypair::X25519KeyPair; /// type SlowHash = opaque_ke::slow_hash::NoOpHash; @@ -783,6 +793,7 @@ impl ServerLogin { /// use opaque_ke::ciphersuite::CipherSuite; /// struct Default; /// impl CipherSuite for Default { + /// type Digest = sha2::Sha512; /// type Group = curve25519_dalek::ristretto::RistrettoPoint; /// type KeyFormat = opaque_ke::keypair::X25519KeyPair; /// type SlowHash = opaque_ke::slow_hash::NoOpHash; @@ -852,6 +863,7 @@ impl ServerLogin { /// use opaque_ke::ciphersuite::CipherSuite; /// struct Default; /// impl CipherSuite for Default { + /// type Digest = sha2::Sha512; /// type Group = curve25519_dalek::ristretto::RistrettoPoint; /// type KeyFormat = opaque_ke::keypair::X25519KeyPair; /// type SlowHash = opaque_ke::slow_hash::NoOpHash; diff --git a/src/oprf.rs b/src/oprf.rs index 98c066d..c9542c8 100644 --- a/src/oprf.rs +++ b/src/oprf.rs @@ -4,29 +4,51 @@ // LICENSE file in the root directory of this source tree. use crate::{errors::InternalPakeError, group::Group}; -use generic_array::{typenum::U64, GenericArray}; +use digest::{BlockInput, FixedOutput, Reset, Update}; +use generic_array::{typenum::U32, ArrayLength, GenericArray}; use hkdf::Hkdf; use rand_core::{CryptoRng, RngCore}; -use sha2::{Digest, Sha256}; pub(crate) struct OprfClientBytes { pub(crate) alpha: Grp, pub(crate) blinding_factor: Grp::Scalar, } +/// The `HkDFDigest` trait specifies the interface required for a parameter of `hkdf::Hkdf`. +/// +/// It's a convenience wrapper around [`Blockinput`], [`Update`], [`FixedOutput`], [`Reset`], +/// [`Clone`], and [`Default`] traits. +pub trait HkdfDigest: Update + BlockInput + FixedOutput + Reset + Default + Clone +where + Self::BlockSize: ArrayLength, + Self::OutputSize: ArrayLength, +{ +} + +impl HkdfDigest for T +where + T: Update + BlockInput + FixedOutput + Reset + Default + Clone, + T::BlockSize: ArrayLength, + T::OutputSize: ArrayLength, +{ +} + /// Computes the first step for the multiplicative blinding version of DH-OPRF. This /// message is sent from the client (who holds the input) to the server (who holds the OPRF key). /// The client can also pass in an optional "pepper" string to be mixed in with the input through /// an HKDF computation. -pub(crate) fn generate_oprf1>( +pub(crate) fn generate_oprf1< + R: RngCore + CryptoRng, + D: HkdfDigest, + G: Group, +>( input: &[u8], pepper: Option<&[u8]>, blinding_factor_rng: &mut R, ) -> Result, InternalPakeError> { - let (hashed_input, _) = Hkdf::::extract(pepper, &input); - let curve_input: Vec = [hashed_input.as_slice(), &[0u8; 32]].concat(); + let (hashed_input, _) = Hkdf::::extract(pepper, &input); let blinding_factor = G::random_scalar(blinding_factor_rng); - let alpha = G::hash_to_curve(GenericArray::from_slice(&curve_input)) * &blinding_factor; + let alpha = G::hash_to_curve(GenericArray::from_slice(&hashed_input)) * &blinding_factor; Ok(OprfClientBytes { alpha, blinding_factor, @@ -48,10 +70,10 @@ pub(crate) fn generate_oprf3( input: &[u8], point: G, blinding_factor: &G::Scalar, -) -> Result::OutputSize>, InternalPakeError> { +) -> Result, InternalPakeError> { let unblinded = point * &G::scalar_invert(&blinding_factor); let ikm: Vec = [&unblinded.to_arr()[..], input].concat(); - let (prk, _) = Hkdf::::extract(None, &ikm); + let (prk, _) = Hkdf::::extract(None, &ikm); Ok(prk) } @@ -66,14 +88,14 @@ mod tests { use generic_array::{arr, GenericArray}; use hkdf::Hkdf; use rand_core::OsRng; + use sha2::{Digest, Sha256, Sha512}; fn prf( input: &[u8], oprf_key: &[u8; 32], ) -> GenericArray::ElemLen> { - let (hashed_input, _) = Hkdf::::extract(None, &input); - let curve_input: Vec = [hashed_input.as_slice(), &[0u8; 32]].concat(); - let point = RistrettoPoint::hash_to_curve(GenericArray::from_slice(&curve_input)); + let (hashed_input, _) = Hkdf::::extract(None, &input); + let point = RistrettoPoint::hash_to_curve(GenericArray::from_slice(&hashed_input)); let scalar = RistrettoPoint::from_scalar_slice(GenericArray::from_slice(&oprf_key[..])).unwrap(); let res = point * scalar; @@ -90,7 +112,7 @@ mod tests { let OprfClientBytes { alpha, blinding_factor, - } = generate_oprf1::<_, RistrettoPoint>(&input[..], None, &mut rng)?; + } = generate_oprf1::<_, Sha512, RistrettoPoint>(&input[..], None, &mut rng)?; let salt_bytes = arr![ u8; 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, @@ -111,17 +133,15 @@ mod tests { let OprfClientBytes { alpha, blinding_factor, - } = generate_oprf1::<_, RistrettoPoint>(&input, None, &mut rng).unwrap(); + } = generate_oprf1::<_, Sha512, RistrettoPoint>(&input, None, &mut rng).unwrap(); let res = generate_oprf3::(&input, alpha, &blinding_factor).unwrap(); - let (hashed_input, _) = Hkdf::::extract(None, &input); - let mut curve_input: Vec = Vec::new(); - curve_input.extend_from_slice(&hashed_input); - curve_input.extend_from_slice(&[0u8; 32]); + let (hashed_input, _) = Hkdf::::extract(None, &input); + // This is because RistrettoPoint is on an obsolete sha2 version let mut bits = [0u8; 64]; let mut hasher = sha2::Sha512::new(); - hasher.update(&curve_input[..]); + Digest::update(&mut hasher, &hashed_input[..]); bits.copy_from_slice(&hasher.finalize()); let point = RistrettoPoint::from_uniform_bytes(&bits); diff --git a/src/slow_hash.rs b/src/slow_hash.rs index 7a502a5..537d521 100644 --- a/src/slow_hash.rs +++ b/src/slow_hash.rs @@ -7,33 +7,26 @@ use crate::errors::InternalPakeError; -use generic_array::GenericArray; -use sha2::{Digest, Sha256}; +use generic_array::{typenum::U32, GenericArray}; /// Used for the slow hashing function in OPAQUE pub trait SlowHash { /// Computes the slow hashing function - fn hash( - input: GenericArray::OutputSize>, - ) -> Result, InternalPakeError>; + fn hash(input: GenericArray) -> Result, InternalPakeError>; } /// A no-op hash which simply returns its input pub struct NoOpHash; impl SlowHash for NoOpHash { - fn hash( - input: GenericArray::OutputSize>, - ) -> Result, InternalPakeError> { + fn hash(input: GenericArray) -> Result, InternalPakeError> { Ok(input.to_vec()) } } #[cfg(feature = "slow-hash")] impl SlowHash for scrypt::ScryptParams { - fn hash( - input: GenericArray::OutputSize>, - ) -> Result, InternalPakeError> { + fn hash(input: GenericArray) -> Result, InternalPakeError> { let params = scrypt::ScryptParams::new(15, 8, 1).unwrap(); let mut output = [0u8; 32]; scrypt::scrypt(&input, &[], ¶ms, &mut output) diff --git a/src/tests/opaque_ke_test.rs b/src/tests/opaque_ke_test.rs index 60c81e9..2d5e9ec 100644 --- a/src/tests/opaque_ke_test.rs +++ b/src/tests/opaque_ke_test.rs @@ -23,6 +23,7 @@ use std::convert::TryFrom; struct X255193dhNoSlowHash; impl CipherSuite for X255193dhNoSlowHash { + type Digest = sha2::Sha256; type Group = EdwardsPoint; type KeyFormat = X25519KeyPair; type SlowHash = NoOpHash; diff --git a/src/tests/serialization.rs b/src/tests/serialization.rs index 0a755fe..3690dfd 100644 --- a/src/tests/serialization.rs +++ b/src/tests/serialization.rs @@ -21,6 +21,7 @@ use std::convert::TryFrom; struct Default; impl CipherSuite for Default { + type Digest = sha2::Sha512; type Group = RistrettoPoint; type KeyFormat = crate::keypair::X25519KeyPair; type SlowHash = crate::slow_hash::NoOpHash;