diff --git a/src/ciphersuite.rs b/src/ciphersuite.rs index 9f96968..ce159e2 100644 --- a/src/ciphersuite.rs +++ b/src/ciphersuite.rs @@ -7,19 +7,22 @@ use crate::{ errors::InternalPakeError, - group::Group, keypair::{Key, KeyPair}, + map_to_curve::GroupWithMapToCurve, slow_hash::SlowHash, }; -use generic_array::typenum::{U32, U64}; + 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 +/// * `Group`: a finite cyclic group along with a point representation, along +/// with an extension trait PasswordToCurve that allows some customization on +/// how to hash a password to a curve point. See `group::Group` and +/// `map_to_curve::GroupWithMapToCurve`. +/// * `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 Group: GroupWithMapToCurve; 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..2a824eb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -380,9 +380,13 @@ pub mod opaque; pub mod ciphersuite; mod envelope; + mod group; +mod map_to_curve; + mod key_exchange; pub mod keypair; + mod oprf; pub mod slow_hash; diff --git a/src/map_to_curve.rs b/src/map_to_curve.rs new file mode 100644 index 0000000..8992d9a --- /dev/null +++ b/src/map_to_curve.rs @@ -0,0 +1,23 @@ +use crate::group::Group; +use curve25519_dalek::{edwards::EdwardsPoint, ristretto::RistrettoPoint}; + +use generic_array::GenericArray; +use hkdf::Hkdf; + +pub trait GroupWithMapToCurve: Group { + fn map_to_curve(password: &[u8], pepper: Option<&[u8]>) -> Self; +} + +impl GroupWithMapToCurve for RistrettoPoint { + fn map_to_curve(password: &[u8], pepper: Option<&[u8]>) -> Self { + let (hashed_input, _) = Hkdf::::extract(pepper, password); + ::hash_to_curve(GenericArray::from_slice(&hashed_input)) + } +} + +impl GroupWithMapToCurve for EdwardsPoint { + fn map_to_curve(password: &[u8], pepper: Option<&[u8]>) -> Self { + let (hashed_input, _) = Hkdf::::extract(pepper, password); + ::hash_to_curve(GenericArray::from_slice(&hashed_input)) + } +} diff --git a/src/oprf.rs b/src/oprf.rs index 98c066d..e4f145d 100644 --- a/src/oprf.rs +++ b/src/oprf.rs @@ -3,11 +3,10 @@ // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. -use crate::{errors::InternalPakeError, group::Group}; -use generic_array::{typenum::U64, GenericArray}; +use crate::{errors::InternalPakeError, group::Group, map_to_curve::GroupWithMapToCurve}; +use generic_array::{typenum::U32, GenericArray}; use hkdf::Hkdf; use rand_core::{CryptoRng, RngCore}; -use sha2::{Digest, Sha256}; pub(crate) struct OprfClientBytes { pub(crate) alpha: Grp, @@ -18,15 +17,14 @@ pub(crate) struct OprfClientBytes { /// 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( 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 mapped_point = G::map_to_curve(input, pepper); let blinding_factor = G::random_scalar(blinding_factor_rng); - let alpha = G::hash_to_curve(GenericArray::from_slice(&curve_input)) * &blinding_factor; + let alpha = mapped_point * &blinding_factor; Ok(OprfClientBytes { alpha, blinding_factor, @@ -48,10 +46,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 +64,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; @@ -114,14 +112,12 @@ mod tests { } = generate_oprf1::<_, 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..fc13a3f 100644 --- a/src/tests/opaque_ke_test.rs +++ b/src/tests/opaque_ke_test.rs @@ -247,13 +247,14 @@ fn generate_parameters() -> TestVectorParameters { ) .unwrap(); let r1_bytes = r1.to_bytes().to_vec(); - let blinding_factor_bytes = *CS::Group::scalar_as_bytes(&client_registration.blinding_factor); + let blinding_factor_bytes = + CS::Group::scalar_as_bytes(&client_registration.blinding_factor).clone(); let client_registration_state = client_registration.to_bytes().to_vec(); let mut oprf_key_rng = CycleRng::new(oprf_key_raw.to_vec()); let (r2, server_registration) = ServerRegistration::::start(r1, &mut oprf_key_rng).unwrap(); let r2_bytes = r2.to_bytes().to_vec(); - let oprf_key_bytes = *CS::Group::scalar_as_bytes(&server_registration.oprf_key); + let oprf_key_bytes = CS::Group::scalar_as_bytes(&server_registration.oprf_key).clone(); let server_registration_state = server_registration.to_bytes().to_vec(); let mut client_s_sk_and_nonce: Vec = Vec::new();