Adding client enumeration mitigations (#153)

This commit is contained in:
Kevin Lewi
2021-06-21 01:29:39 -07:00
committed by Kevin Lewi
parent 98f1821897
commit f0c13945d1
14 changed files with 895 additions and 901 deletions
+224 -316
View File
@@ -8,7 +8,7 @@
use crate::{
ciphersuite::CipherSuite,
envelope::{mode_from_ids, Envelope},
errors::{utils::check_slice_size_atleast, InternalPakeError, PakeError, ProtocolError},
errors::{utils::check_slice_size, InternalPakeError, PakeError, ProtocolError},
group::Group,
hash::Hash,
key_exchange::traits::{FromBytes, KeyExchange, ToBytesWithPointers},
@@ -23,10 +23,66 @@ use crate::{
use digest::Digest;
use generic_array::{typenum::Unsigned, GenericArray};
use generic_bytes::SizedBytes;
use hkdf::Hkdf;
use rand::{CryptoRng, RngCore};
use std::marker::PhantomData;
use zeroize::Zeroize;
const STR_CREDENTIAL_RESPONSE_PAD: &[u8] = b"CredentialResponsePad";
const STR_MASKING_KEY: &[u8] = b"MaskingKey";
const STR_OPRF_KEY: &[u8] = b"OprfKey";
// Server Setup
// ============
/// The state elements the server holds upon setup
pub struct ServerSetup<CS: CipherSuite> {
oprf_seed: GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
keypair: KeyPair<CS::Group>,
}
impl<CS: CipherSuite> ServerSetup<CS> {
/// Generate a new instance of server setup
pub fn new<R: CryptoRng + RngCore>(rng: &mut R) -> Self {
let mut seed = vec![0u8; <CS::Hash as Digest>::OutputSize::to_usize()];
rng.fill_bytes(&mut seed);
Self {
oprf_seed: GenericArray::clone_from_slice(&seed[..]),
keypair: KeyPair::<CS::Group>::generate_random(rng),
}
}
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
[
self.oprf_seed.to_vec(),
self.keypair.private().to_arr().to_vec(),
]
.concat()
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let seed_len = <CS::Hash as Digest>::OutputSize::to_usize();
let checked_slice = check_slice_size(
input,
seed_len + <PrivateKey as SizedBytes>::Len::to_usize(),
"server_setup",
)?;
Ok(Self {
oprf_seed: GenericArray::clone_from_slice(&checked_slice[..seed_len]),
keypair: KeyPair::from_private_key_slice(&checked_slice[seed_len..])?,
})
}
/// Returns the keypair
pub fn keypair(&self) -> &KeyPair<CS::Group> {
&self.keypair
}
}
// Registration
// ============
@@ -48,12 +104,11 @@ impl<CS: CipherSuite> Clone for ClientRegistration<CS> {
impl<CS: CipherSuite> ClientRegistration<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
let output: Vec<u8> = [
[
&CS::Group::scalar_as_bytes(&self.token.blind)[..],
&self.token.data,
]
.concat();
output
.concat()
}
/// Deserialization from bytes
@@ -129,28 +184,6 @@ impl<CS: CipherSuite> Clone for ClientRegistrationStartResult<CS> {
impl<CS: CipherSuite> ClientRegistration<CS> {
/// Returns an initial "blinded" request to send to the server, as well as a ClientRegistration
///
/// # Arguments
/// * `password` - A user password
///
/// # Example
///
/// ```
/// use opaque_ke::ClientRegistration;
/// # use opaque_ke::errors::ProtocolError;
/// use rand::{rngs::OsRng, RngCore};
/// use opaque_ke::ciphersuite::CipherSuite;
/// struct Default;
/// impl CipherSuite for Default {
/// type Group = curve25519_dalek::ristretto::RistrettoPoint;
/// type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
/// type Hash = sha2::Sha512;
/// type SlowHash = opaque_ke::slow_hash::NoOpHash;
/// }
/// let mut client_rng = OsRng;
/// let client_registration_start_result = ClientRegistration::<Default>::start(&mut client_rng, b"hunter2")?;
/// # Ok::<(), ProtocolError>(())
/// ```
pub fn start<R: RngCore + CryptoRng>(
blinding_factor_rng: &mut R,
password: &[u8],
@@ -190,35 +223,6 @@ impl<CS: CipherSuite> Clone for ClientRegistrationFinishResult<CS> {
impl<CS: CipherSuite> ClientRegistration<CS> {
/// "Unblinds" the server's answer and returns a final message containing
/// cryptographic identifiers, to be sent to the server on setup finalization
///
/// # Arguments
/// * `message` - the server's answer to the initial registration attempt
///
/// # Example
///
/// ```
/// use opaque_ke::{ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration};
/// # use opaque_ke::errors::ProtocolError;
/// # use opaque_ke::keypair::KeyPair;
/// use rand::{rngs::OsRng, RngCore};
/// use opaque_ke::ciphersuite::CipherSuite;
/// struct Default;
/// impl CipherSuite for Default {
/// type Group = curve25519_dalek::ristretto::RistrettoPoint;
/// type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
/// type Hash = sha2::Sha512;
/// type SlowHash = opaque_ke::slow_hash::NoOpHash;
/// }
/// let mut client_rng = OsRng;
/// let mut server_rng = OsRng;
/// let server_kp = Default::generate_random_keypair(&mut server_rng);
/// let client_registration_start_result = ClientRegistration::<Default>::start(&mut client_rng, b"hunter2")?;
/// let server_registration_start_result =
/// ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
/// let mut client_rng = OsRng;
/// let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::default())?;
/// # Ok::<(), ProtocolError>(())
/// ```
pub fn finish<R: CryptoRng + RngCore>(
self,
rng: &mut R,
@@ -229,11 +233,16 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
ClientRegistrationFinishParameters::WithIdentifiers(id_u, id_s) => Some((id_u, id_s)),
ClientRegistrationFinishParameters::Default => None,
};
let client_static_keypair = CS::generate_random_keypair(rng);
let client_static_keypair = KeyPair::<CS::Group>::generate_random(rng);
let password_derived_key =
get_password_derived_key::<CS::Group, CS::SlowHash, CS::Hash>(&self.token, r2.beta)?;
let h = Hkdf::<CS::Hash>::new(None, &password_derived_key);
let mut masking_key = vec![0u8; <CS::Hash as Digest>::OutputSize::to_usize()];
h.expand(STR_MASKING_KEY, &mut masking_key)
.map_err(|_| InternalPakeError::HkdfError)?;
let (envelope, export_key) = Envelope::<CS::Hash>::seal(
rng,
&password_derived_key,
@@ -245,6 +254,7 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
Ok(ClientRegistrationFinishResult {
message: RegistrationUpload {
envelope,
masking_key: GenericArray::clone_from_slice(&masking_key[..]),
client_s_pk: client_static_keypair.public().clone(),
},
export_key,
@@ -254,12 +264,11 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
}
}
/// Contains the fields that are returned by a server registration start
/// Contains the fields that are returned by a server registration start.
/// Note that there is no state output in this step
pub struct ServerRegistrationStartResult<CS: CipherSuite> {
/// The registration resposne message to send to the client
pub message: RegistrationResponse<CS>,
/// The state that the server must keep in order to complete registration
pub state: ServerRegistration<CS>,
}
// Cannot be derived because it would require for CS to be Clone.
@@ -267,123 +276,51 @@ impl<CS: CipherSuite> Clone for ServerRegistrationStartResult<CS> {
fn clone(&self) -> Self {
Self {
message: self.message.clone(),
state: self.state.clone(),
}
}
}
/// The state elements the server holds to record a registration
pub struct ServerRegistration<CS: CipherSuite> {
envelope: Option<Envelope<CS::Hash>>,
client_s_pk: Option<PublicKey>,
pub(crate) oprf_key: <CS::Group as Group>::Scalar,
}
pub struct ServerRegistration<CS: CipherSuite>(RegistrationUpload<CS>);
// Cannot be derived because it would require for CS to be Clone.
impl<CS: CipherSuite> Clone for ServerRegistration<CS> {
fn clone(&self) -> Self {
Self {
envelope: self.envelope.clone(),
client_s_pk: self.client_s_pk.clone(),
oprf_key: self.oprf_key.clone(),
}
Self(self.0.clone())
}
}
impl<CS: CipherSuite> ServerRegistration<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> {
let mut output: Vec<u8> = CS::Group::scalar_as_bytes(&self.oprf_key).to_vec();
self.client_s_pk
.iter()
.for_each(|v| output.extend_from_slice(&v.to_arr()));
self.envelope
.iter()
.for_each(|v| output.extend_from_slice(&v.to_bytes()));
output
self.0.serialize()
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let scalar_len = <CS::Group as Group>::ScalarLen::to_usize();
if input.len() == scalar_len {
return Ok(Self {
oprf_key: CS::Group::from_scalar_slice(GenericArray::from_slice(input))?,
client_s_pk: None,
envelope: None,
});
}
// Need to do this check manually because envelope is variable-size
let key_len = <PublicKey as SizedBytes>::Len::to_usize();
let checked_bytes =
check_slice_size_atleast(input, scalar_len + key_len, "server_registration_bytes")?;
let oprf_key_bytes = GenericArray::from_slice(&checked_bytes[..scalar_len]);
let oprf_key = CS::Group::from_scalar_slice(oprf_key_bytes)?;
let unchecked_client_s_pk =
PublicKey::from_bytes(&checked_bytes[scalar_len..scalar_len + key_len])?;
let client_s_pk = KeyPair::<CS::Group>::check_public_key(unchecked_client_s_pk)?;
let envelope = Envelope::<CS::Hash>::from_bytes(&checked_bytes[scalar_len + key_len..])?;
Ok(Self {
envelope: Some(envelope),
client_s_pk: Some(client_s_pk),
oprf_key,
})
Ok(Self(RegistrationUpload::deserialize(input)?))
}
#[cfg(test)]
pub fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> {
[
match &self.envelope {
Some(env) => env.as_byte_ptrs(),
None => vec![],
},
match &self.client_s_pk {
Some(pk) => vec![(pk.as_ptr(), pk.len())],
None => vec![],
},
self.0.envelope.as_byte_ptrs(),
vec![(self.0.client_s_pk.as_ptr(), self.0.client_s_pk.len())],
/* cannot provide raw pointer to self.oprf_key until this is exposed in curve25519_dalek::scalar::Scalar */
].concat()
}
/// From the client's "blinded" password, returns a response to be
/// sent back to the client, as well as a ServerRegistration
///
/// # Arguments
/// * `message` - the initial registration message
///
/// # Example
///
/// ```
/// use opaque_ke::*;
/// # use opaque_ke::errors::ProtocolError;
/// use rand::{rngs::OsRng, RngCore};
/// use opaque_ke::ciphersuite::CipherSuite;
/// struct Default;
/// impl CipherSuite for Default {
/// type Group = curve25519_dalek::ristretto::RistrettoPoint;
/// type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
/// type Hash = sha2::Sha512;
/// type SlowHash = opaque_ke::slow_hash::NoOpHash;
/// }
/// let mut client_rng = OsRng;
/// let mut server_rng = OsRng;
/// let server_kp = Default::generate_random_keypair(&mut server_rng);
/// let client_registration_start_result = ClientRegistration::<Default>::start(&mut client_rng, b"hunter2")?;
/// let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
/// # Ok::<(), ProtocolError>(())
/// ```
pub fn start<R: RngCore + CryptoRng>(
rng: &mut R,
pub fn start(
server_setup: &ServerSetup<CS>,
message: RegistrationRequest<CS>,
server_s_pk: &PublicKey,
credential_identifier: &[u8],
) -> Result<ServerRegistrationStartResult<CS>, ProtocolError> {
// RFC: generate oprf_key (salt) and v_u = g^oprf_key
let oprf_key = CS::Group::random_nonzero_scalar(rng);
let oprf_key = oprf_key_from_seed::<CS::Group, CS::Hash>(
&server_setup.oprf_seed,
credential_identifier,
)?;
// Compute beta = alpha^oprf_key
let beta = oprf::evaluate::<CS::Group>(message.alpha, &oprf_key);
@@ -391,52 +328,20 @@ impl<CS: CipherSuite> ServerRegistration<CS> {
Ok(ServerRegistrationStartResult {
message: RegistrationResponse {
beta,
server_s_pk: server_s_pk.clone(),
},
state: Self {
envelope: None,
client_s_pk: None,
oprf_key,
server_s_pk: server_setup.keypair.public().clone(),
},
})
}
/// From the client's cryptographic identifiers, fully populates and
/// returns a ServerRegistration
///
/// # Arguments
/// * `message` - the final client message
///
/// # Example
///
/// ```
/// use opaque_ke::{*, keypair::KeyPair};
/// # use opaque_ke::errors::ProtocolError;
/// use rand::{rngs::OsRng, RngCore};
/// use opaque_ke::ciphersuite::CipherSuite;
/// struct Default;
/// impl CipherSuite for Default {
/// type Group = curve25519_dalek::ristretto::RistrettoPoint;
/// type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
/// type Hash = sha2::Sha512;
/// type SlowHash = opaque_ke::slow_hash::NoOpHash;
/// }
/// let mut client_rng = OsRng;
/// let mut server_rng = OsRng;
/// let server_kp = Default::generate_random_keypair(&mut server_rng);
/// let client_registration_start_result = ClientRegistration::<Default>::start(&mut client_rng, b"hunter2")?;
/// let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
/// let mut client_rng = OsRng;
/// let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::default())?;
/// let client_record = server_registration_start_result.state.finish(client_registration_finish_result.message)?;
/// # Ok::<(), ProtocolError>(())
/// ```
pub fn finish(self, message: RegistrationUpload<CS>) -> Result<Self, ProtocolError> {
Ok(Self {
envelope: Some(message.envelope),
client_s_pk: Some(message.client_s_pk),
oprf_key: self.oprf_key.clone(),
})
pub fn finish(message: RegistrationUpload<CS>) -> Self {
Self(message)
}
// Creates a dummy instance used for faking a [CredentialResponse]
pub(crate) fn dummy<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
Self(RegistrationUpload::dummy(rng))
}
}
@@ -653,41 +558,9 @@ impl<CS: CipherSuite> ClientLogin<CS> {
/// "Unblinds" the server's answer and returns the opened assets from
/// the server
///
/// # Arguments
/// * `message` - the server's answer to the initial login attempt
///
/// # Example
///
/// ```
/// use opaque_ke::{ClientLogin, ClientLoginStartParameters, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters};
/// # use opaque_ke::{ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration};
/// # use opaque_ke::errors::ProtocolError;
/// # use opaque_ke::keypair::KeyPair;
/// use rand::{rngs::OsRng, RngCore};
/// use opaque_ke::ciphersuite::CipherSuite;
/// struct Default;
/// impl CipherSuite for Default {
/// type Group = curve25519_dalek::ristretto::RistrettoPoint;
/// type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
/// type Hash = sha2::Sha512;
/// type SlowHash = opaque_ke::slow_hash::NoOpHash;
/// }
/// let mut client_rng = OsRng;
/// # let mut server_rng = OsRng;
/// # let client_registration_start_result = ClientRegistration::<Default>::start(&mut client_rng, b"hunter2")?;
/// # let server_kp = Default::generate_random_keypair(&mut server_rng);
/// # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
/// # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::default())?;
/// # let p_file = server_registration_start_result.state.finish(client_registration_finish_result.message)?;
/// let client_login_start_result = ClientLogin::<Default>::start(&mut client_rng, b"hunter2", ClientLoginStartParameters::default())?;
/// let server_login_start_result = ServerLogin::start(&mut server_rng, p_file, &server_kp.private(), client_login_start_result.message, ServerLoginStartParameters::default())?;
/// let client_login_finish_result = client_login_start_result.state.finish(server_login_start_result.message, ClientLoginFinishParameters::default())?;
/// # Ok::<(), ProtocolError>(())
/// ```
pub fn finish(
self,
l2: CredentialResponse<CS>,
credential_response: CredentialResponse<CS>,
params: ClientLoginFinishParameters,
) -> Result<ClientLoginFinishResult<CS>, ProtocolError> {
let optional_ids = match params {
@@ -695,12 +568,28 @@ impl<CS: CipherSuite> ClientLogin<CS> {
ClientLoginFinishParameters::WithIdentifiers(id_u, id_s) => Some((id_u, id_s)),
};
let server_s_pk_bytes = l2.server_s_pk.to_arr().to_vec();
let password_derived_key = get_password_derived_key::<CS::Group, CS::SlowHash, CS::Hash>(
&self.token,
credential_response.beta,
)?;
let password_derived_key =
get_password_derived_key::<CS::Group, CS::SlowHash, CS::Hash>(&self.token, l2.beta)?;
let opened_envelope = &l2
.envelope
let h = Hkdf::<CS::Hash>::new(None, &password_derived_key);
let mut masking_key = vec![0u8; <CS::Hash as Digest>::OutputSize::to_usize()];
h.expand(STR_MASKING_KEY, &mut masking_key)
.map_err(|_| InternalPakeError::HkdfError)?;
let (server_s_pk, envelope) = unmask_response::<CS::Hash>(
&masking_key,
&credential_response.masking_nonce,
&credential_response.masked_response,
)
.map_err(|e| match e {
ProtocolError::InvalidInnerEnvelopeError => PakeError::InvalidLoginError.into(),
err => err,
})?;
let server_s_pk_bytes = server_s_pk.to_arr().to_vec();
let opened_envelope = &envelope
.open(&password_derived_key, &server_s_pk_bytes, &optional_ids)
.map_err(|e| match e {
InternalPakeError::SealOpenHmacError => PakeError::InvalidLoginError,
@@ -719,15 +608,18 @@ impl<CS: CipherSuite> ClientLogin<CS> {
Some((id_u, id_s)) => (id_u, id_s),
};
let credential_response_component =
CredentialResponse::<CS>::serialize_without_ke(&l2.beta, &l2.server_s_pk, &l2.envelope);
let credential_response_component = CredentialResponse::<CS>::serialize_without_ke(
&credential_response.beta,
&credential_response.masking_nonce,
&credential_response.masked_response,
);
let (confidential_info, session_key, ke3_message) = CS::KeyExchange::generate_ke3(
credential_response_component,
l2.ke2_message,
credential_response.ke2_message,
&self.ke1_state,
&self.serialized_credential_request,
l2.server_s_pk.clone(),
server_s_pk.clone(),
client_s_sk,
id_u,
id_s,
@@ -738,7 +630,7 @@ impl<CS: CipherSuite> ClientLogin<CS> {
message: CredentialFinalization { ke3_message },
session_key,
export_key: opened_envelope.export_key.clone(),
server_s_pk: l2.server_s_pk,
server_s_pk,
#[cfg(test)]
state: self,
})
@@ -839,50 +731,24 @@ impl<CS: CipherSuite> ServerLogin<CS> {
})
}
/// From the client's "blinded"" password, returns a challenge to be
/// From the client's "blinded" password, returns a challenge to be
/// sent back to the client, as well as a ServerLogin
///
/// # Arguments
/// * `message` - the initial registration message
///
/// # Example
///
/// ```
/// use opaque_ke::{ClientLogin, ClientLoginStartParameters, ServerLogin, ServerLoginStartParameters};
/// # use opaque_ke::{ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration};
/// # use opaque_ke::errors::ProtocolError;
/// # use opaque_ke::keypair::KeyPair;
/// use rand::{rngs::OsRng, RngCore};
/// use opaque_ke::ciphersuite::CipherSuite;
/// struct Default;
/// impl CipherSuite for Default {
/// type Group = curve25519_dalek::ristretto::RistrettoPoint;
/// type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
/// type Hash = sha2::Sha512;
/// type SlowHash = opaque_ke::slow_hash::NoOpHash;
/// }
/// let mut client_rng = OsRng;
/// let mut server_rng = OsRng;
/// let server_kp = Default::generate_random_keypair(&mut server_rng);
/// # let client_registration_start_result = ClientRegistration::<Default>::start(&mut client_rng, b"hunter2")?;
/// # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
/// # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::default())?;
/// # let p_file = server_registration_start_result.state.finish(client_registration_finish_result.message)?;
/// let client_login_start_result = ClientLogin::<Default>::start(&mut client_rng, b"hunter2", ClientLoginStartParameters::default())?;
/// let server_login_start_result = ServerLogin::start(&mut server_rng, p_file, &server_kp.private(), client_login_start_result.message, ServerLoginStartParameters::default())?;
/// # Ok::<(), ProtocolError>(())
/// ```
pub fn start<R: RngCore + CryptoRng>(
rng: &mut R,
password_file: ServerRegistration<CS>,
server_s_sk: &PrivateKey,
server_setup: &ServerSetup<CS>,
password_file: Option<ServerRegistration<CS>>,
l1: CredentialRequest<CS>,
credential_identifier: &[u8],
params: ServerLoginStartParameters,
) -> Result<ServerLoginStartResult<CS>, ProtocolError> {
let client_s_pk = password_file
.client_s_pk
.clone()
.ok_or(InternalPakeError::SealError)?;
// FIXME: handle optional password_file case, ensure that there is no timing attack by generating a random pubkey anyway
let record = match password_file {
Some(x) => x,
None => ServerRegistration::dummy(rng),
};
let client_s_pk = record.0.client_s_pk.clone();
let (e_info, optional_ids) = match params {
ServerLoginStartParameters::WithInfo(e_info) => (e_info, None),
@@ -894,30 +760,39 @@ impl<CS: CipherSuite> ServerLogin<CS> {
}
};
let envelope = password_file
.envelope
.clone()
.ok_or(InternalPakeError::SealError)?;
let envelope = record.0.envelope.clone();
if envelope.get_mode() != mode_from_ids(&optional_ids) {
return Err(InternalPakeError::IncompatibleEnvelopeModeError.into());
}
let server_s_sk = server_setup.keypair.private();
let server_s_pk = KeyPair::<CS::Group>::public_from_private(&server_s_sk);
let mut masking_nonce = vec![0u8; 32];
rng.fill_bytes(&mut masking_nonce);
let masked_response = mask_response(
&record.0.masking_key,
&masking_nonce,
&server_s_pk,
&envelope,
)?;
let (id_u, id_s) = match optional_ids {
None => (
client_s_pk.to_arr().to_vec(),
KeyPair::<CS::Group>::public_from_private(server_s_sk)
.to_arr()
.to_vec(),
),
None => (client_s_pk.to_arr().to_vec(), server_s_pk.to_arr().to_vec()),
Some((id_u, id_s)) => (id_u, id_s),
};
let l1_bytes = &l1.serialize();
let beta = oprf::evaluate(l1.alpha, &password_file.oprf_key);
let server_s_pk = KeyPair::<CS::Group>::public_from_private(server_s_sk);
let oprf_key = oprf_key_from_seed::<CS::Group, CS::Hash>(
&server_setup.oprf_seed,
credential_identifier,
)?;
let beta = oprf::evaluate(l1.alpha, &oprf_key);
let credential_response_component =
CredentialResponse::<CS>::serialize_without_ke(&beta, &server_s_pk, &envelope);
CredentialResponse::<CS>::serialize_without_ke(&beta, &masking_nonce, &masked_response);
let (plain_info, ke2_state, ke2_message) = CS::KeyExchange::generate_ke2(
rng,
@@ -933,8 +808,8 @@ impl<CS: CipherSuite> ServerLogin<CS> {
let credential_response = CredentialResponse {
beta,
server_s_pk,
envelope,
masking_nonce,
masked_response,
ke2_message,
};
@@ -950,39 +825,6 @@ impl<CS: CipherSuite> ServerLogin<CS> {
/// From the client's second and final message, check the client's
/// authentication and produce a message transport
///
/// # Arguments
/// * `message` - the client's second login message
///
/// # Example
///
/// ```
/// use opaque_ke::{ClientLogin, ClientLoginFinishParameters, ClientLoginStartParameters, ServerLogin, ServerLoginStartParameters};
/// # use opaque_ke::{ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration};
/// # use opaque_ke::errors::ProtocolError;
/// # use opaque_ke::keypair::KeyPair;
/// use rand::{rngs::OsRng, RngCore};
/// use opaque_ke::ciphersuite::CipherSuite;
/// struct Default;
/// impl CipherSuite for Default {
/// type Group = curve25519_dalek::ristretto::RistrettoPoint;
/// type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDH;
/// type Hash = sha2::Sha512;
/// type SlowHash = opaque_ke::slow_hash::NoOpHash;
/// }
/// let mut client_rng = OsRng;
/// let mut server_rng = OsRng;
/// let server_kp = Default::generate_random_keypair(&mut server_rng);
/// # let client_registration_start_result = ClientRegistration::<Default>::start(&mut client_rng, b"hunter2")?;
/// # let server_registration_start_result = ServerRegistration::<Default>::start(&mut server_rng, client_registration_start_result.message, server_kp.public())?;
/// # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, server_registration_start_result.message, ClientRegistrationFinishParameters::default())?;
/// # let p_file = server_registration_start_result.state.finish(client_registration_finish_result.message)?;
/// let client_login_start_result = ClientLogin::<Default>::start(&mut client_rng, b"hunter2", ClientLoginStartParameters::default())?;
/// let server_login_start_result = ServerLogin::start(&mut server_rng, p_file, &server_kp.private(), client_login_start_result.message, ServerLoginStartParameters::default())?;
/// let client_login_finish_result = client_login_start_result.state.finish(server_login_start_result.message, ClientLoginFinishParameters::default())?;
/// let mut server_transport = server_login_start_result.state.finish(client_login_finish_result.message)?;
/// # Ok::<(), ProtocolError>(())
/// ```
pub fn finish(
self,
message: CredentialFinalization<CS>,
@@ -1033,9 +875,9 @@ impl<CS: CipherSuite> Drop for ClientRegistration<CS> {
// This can't be derived because of the use of a phantom parameter
impl<CS: CipherSuite> Zeroize for ServerRegistration<CS> {
fn zeroize(&mut self) {
self.envelope.zeroize();
self.client_s_pk.zeroize();
self.oprf_key.zeroize();
self.0.envelope.zeroize();
self.0.masking_key.zeroize();
self.0.client_s_pk.zeroize();
}
}
@@ -1083,3 +925,69 @@ fn get_password_derived_key<G: GroupWithMapToCurve, SH: SlowHash<D>, D: Hash>(
let oprf_output = oprf::finalize::<G, D>(&token.data, &token.blind, beta);
SH::hash(oprf_output)
}
fn oprf_key_from_seed<G: GroupWithMapToCurve, D: Hash>(
oprf_seed: &GenericArray<u8, D::OutputSize>,
credential_identifier: &[u8],
) -> Result<G::Scalar, InternalPakeError> {
let mut oprf_key_bytes = vec![0u8; <PrivateKey as SizedBytes>::Len::to_usize()];
Hkdf::<D>::from_prk(oprf_seed)
.map_err(|_| InternalPakeError::HkdfError)?
.expand(
&[credential_identifier, &STR_OPRF_KEY].concat(),
&mut oprf_key_bytes,
)
.map_err(|_| InternalPakeError::HkdfError)?;
G::hash_to_scalar::<D>(&oprf_key_bytes[..])
}
fn mask_response<D: Hash>(
masking_key: &[u8],
masking_nonce: &[u8],
server_s_pk: &PublicKey,
envelope: &Envelope<D>,
) -> Result<Vec<u8>, ProtocolError> {
let mut xor_pad = vec![0u8; <PublicKey as SizedBytes>::Len::to_usize() + Envelope::<D>::len()];
Hkdf::<D>::from_prk(&masking_key)
.map_err(|_| InternalPakeError::HkdfError)?
.expand(
&[masking_nonce, &STR_CREDENTIAL_RESPONSE_PAD].concat(),
&mut xor_pad,
)
.map_err(|_| InternalPakeError::HkdfError)?;
let plaintext = [&server_s_pk.to_arr()[..], &envelope.serialize()].concat();
Ok(xor_pad
.iter()
.zip(plaintext.iter())
.map(|(&x1, &x2)| x1 ^ x2)
.collect())
}
fn unmask_response<D: Hash>(
masking_key: &[u8],
masking_nonce: &[u8],
masked_response: &[u8],
) -> Result<(PublicKey, Envelope<D>), ProtocolError> {
let mut xor_pad = vec![0u8; <PublicKey as SizedBytes>::Len::to_usize() + Envelope::<D>::len()];
Hkdf::<D>::from_prk(&masking_key)
.map_err(|_| InternalPakeError::HkdfError)?
.expand(
&[masking_nonce, &STR_CREDENTIAL_RESPONSE_PAD].concat(),
&mut xor_pad,
)
.map_err(|_| InternalPakeError::HkdfError)?;
let plaintext: Vec<u8> = xor_pad
.iter()
.zip(masked_response.iter())
.map(|(&x1, &x2)| x1 ^ x2)
.collect();
let key_len = <PublicKey as SizedBytes>::Len::to_usize();
let unchecked_server_s_pk =
PublicKey::from_arr(&GenericArray::clone_from_slice(&plaintext[..key_len]))?;
let envelope = Envelope::deserialize(&plaintext[key_len..])?;
// FIXME check server_s_pk
Ok((unchecked_server_s_pk, envelope))
}