Adding support for "internal mode" and fake credential response + test vectors (#155)

* Adding support for internal and external mode
This commit is contained in:
Kevin Lewi
2021-06-21 01:29:39 -07:00
committed by Kevin Lewi
parent f0c13945d1
commit 1572ff0104
12 changed files with 907 additions and 927 deletions
+114 -139
View File
@@ -7,12 +7,12 @@
use crate::{
ciphersuite::CipherSuite,
envelope::{mode_from_ids, Envelope},
envelope::Envelope,
errors::{utils::check_slice_size, InternalPakeError, PakeError, ProtocolError},
group::Group,
hash::Hash,
key_exchange::traits::{FromBytes, KeyExchange, ToBytesWithPointers},
keypair::{KeyPair, PrivateKey, PublicKey, SizedBytesExt},
keypair::{KeyPair, PrivateKey, PublicKey},
map_to_curve::GroupWithMapToCurve,
oprf,
serialization::{serialize, tokenize},
@@ -39,6 +39,7 @@ const STR_OPRF_KEY: &[u8] = b"OprfKey";
pub struct ServerSetup<CS: CipherSuite> {
oprf_seed: GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
keypair: KeyPair<CS::Group>,
pub(crate) fake_keypair: KeyPair<CS::Group>,
}
impl<CS: CipherSuite> ServerSetup<CS> {
@@ -50,6 +51,7 @@ impl<CS: CipherSuite> ServerSetup<CS> {
Self {
oprf_seed: GenericArray::clone_from_slice(&seed[..]),
keypair: KeyPair::<CS::Group>::generate_random(rng),
fake_keypair: KeyPair::<CS::Group>::generate_random(rng),
}
}
@@ -58,6 +60,7 @@ impl<CS: CipherSuite> ServerSetup<CS> {
[
self.oprf_seed.to_vec(),
self.keypair.private().to_arr().to_vec(),
self.fake_keypair.private().to_arr().to_vec(),
]
.concat()
}
@@ -65,15 +68,13 @@ impl<CS: CipherSuite> ServerSetup<CS> {
/// 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",
)?;
let key_len = <PrivateKey as SizedBytes>::Len::to_usize();
let checked_slice = check_slice_size(input, seed_len + key_len + key_len, "server_setup")?;
Ok(Self {
oprf_seed: GenericArray::clone_from_slice(&checked_slice[..seed_len]),
keypair: KeyPair::from_private_key_slice(&checked_slice[seed_len..])?,
keypair: KeyPair::from_private_key_slice(&checked_slice[seed_len..seed_len + key_len])?,
fake_keypair: KeyPair::from_private_key_slice(&checked_slice[seed_len + key_len..])?,
})
}
@@ -149,12 +150,40 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
impl_serialize_and_deserialize_for!(ClientRegistration);
/// Options for specifying custom identifiers
#[derive(Clone)]
pub enum Identifiers {
/// Supply only a client identifier
ClientIdentifier(Vec<u8>),
/// Supply only a server identifier
ServerIdentifier(Vec<u8>),
/// Supply a client and server identifier
ClientAndServerIdentifiers(Vec<u8>, Vec<u8>),
}
pub(crate) fn bytestrings_from_identifiers(
ids: &Option<Identifiers>,
client_s_pk: &[u8],
server_s_pk: &[u8],
) -> (Vec<u8>, Vec<u8>) {
let (client_identity, server_identity): (Vec<u8>, Vec<u8>) = match ids {
None => (client_s_pk.to_vec(), server_s_pk.to_vec()),
Some(Identifiers::ClientIdentifier(id_u)) => (id_u.clone(), server_s_pk.to_vec()),
Some(Identifiers::ServerIdentifier(id_s)) => (client_s_pk.to_vec(), id_s.clone()),
Some(Identifiers::ClientAndServerIdentifiers(id_u, id_s)) => (id_u.clone(), id_s.clone()),
};
(
serialize(&client_identity, 2),
serialize(&server_identity, 2),
)
}
/// Optional parameters for client registration finish
#[derive(Clone)]
pub enum ClientRegistrationFinishParameters {
/// Specifying the identifiers idU and idS (corresponding to custom identifier mode)
WithIdentifiers(Vec<u8>, Vec<u8>),
/// No identifiers specified (corresponding to base mode)
/// Specifying the identifiers idU and idS
WithIdentifiers(Identifiers),
/// No identifiers or private key specified
Default,
}
@@ -230,10 +259,9 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
params: ClientRegistrationFinishParameters,
) -> Result<ClientRegistrationFinishResult<CS>, ProtocolError> {
let optional_ids = match params {
ClientRegistrationFinishParameters::WithIdentifiers(id_u, id_s) => Some((id_u, id_s)),
ClientRegistrationFinishParameters::WithIdentifiers(ids) => Some(ids),
ClientRegistrationFinishParameters::Default => None,
};
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)?;
@@ -243,19 +271,14 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
h.expand(STR_MASKING_KEY, &mut masking_key)
.map_err(|_| InternalPakeError::HkdfError)?;
let (envelope, export_key) = Envelope::<CS::Hash>::seal(
rng,
&password_derived_key,
&client_static_keypair.private().to_arr().to_vec(),
&r2.server_s_pk,
optional_ids,
)?;
let (envelope, client_s_pk, export_key) =
Envelope::<CS>::seal(rng, &password_derived_key, &r2.server_s_pk, optional_ids)?;
Ok(ClientRegistrationFinishResult {
message: RegistrationUpload {
envelope,
masking_key: GenericArray::clone_from_slice(&masking_key[..]),
client_s_pk: client_static_keypair.public().clone(),
client_s_pk,
},
export_key,
#[cfg(test)]
@@ -340,8 +363,11 @@ impl<CS: CipherSuite> ServerRegistration<CS> {
}
// Creates a dummy instance used for faking a [CredentialResponse]
pub(crate) fn dummy<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
Self(RegistrationUpload::dummy(rng))
pub(crate) fn dummy<R: RngCore + CryptoRng>(
rng: &mut R,
server_setup: &ServerSetup<CS>,
) -> Self {
Self(RegistrationUpload::dummy(rng, server_setup))
}
}
@@ -428,21 +454,6 @@ impl<CS: CipherSuite> ClientLogin<CS> {
}
}
impl_serialize_and_deserialize_for!(ClientLogin);
/// Optional parameters for client login start
#[derive(Clone)]
pub enum ClientLoginStartParameters {
/// Specifying a plaintext info field that will be sent to the server
WithInfo(Vec<u8>),
}
impl Default for ClientLoginStartParameters {
fn default() -> Self {
Self::WithInfo(Vec::new())
}
}
/// Contains the fields that are returned by a client login start
pub struct ClientLoginStartResult<CS: CipherSuite> {
/// The message to send to the server to begin the login protocol
@@ -464,9 +475,14 @@ impl<CS: CipherSuite> Clone for ClientLoginStartResult<CS> {
/// Optional parameters for client login finish
#[derive(Clone)]
pub enum ClientLoginFinishParameters {
/// Specifying a user identifier and server identifier that will be matched against the client
WithIdentifiers(Vec<u8>, Vec<u8>),
/// No info and no custom identifiers
/// Specifying a context field that the server must agree on
WithContext(Vec<u8>),
/// Specifying a user identifier and server identifier that will be matched against the server
WithIdentifiers(Identifiers),
/// Specifying a context field that the server must agree on,
/// along with a user identifier and server identifier and context that will be matched against the server
WithContextAndIdentifiers(Vec<u8>, Identifiers),
/// No custom identifiers and no context
Default,
}
@@ -486,8 +502,6 @@ pub struct ClientLoginFinishResult<CS: CipherSuite> {
pub export_key: GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
/// The server's static public key
pub server_s_pk: PublicKey,
/// The confidential info sent by the client
pub confidential_info: Vec<u8>,
/// Instance of the ClientLogin, only used in tests for checking zeroize
#[cfg(test)]
pub state: ClientLogin<CS>,
@@ -501,7 +515,6 @@ impl<CS: CipherSuite> Clone for ClientLoginFinishResult<CS> {
session_key: self.session_key.clone(),
export_key: self.export_key.clone(),
server_s_pk: self.server_s_pk.clone(),
confidential_info: self.confidential_info.clone(),
#[cfg(test)]
state: self.state.clone(),
}
@@ -510,38 +523,13 @@ impl<CS: CipherSuite> Clone for ClientLoginFinishResult<CS> {
impl<CS: CipherSuite> ClientLogin<CS> {
/// Returns an initial "blinded" password request to send to the server, as well as a ClientLogin
///
/// # Arguments
/// * `password` - A user password
///
/// # Example
///
/// ```
/// use opaque_ke::{ClientLogin, ClientLoginStartParameters};
/// # 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_login_start_result = ClientLogin::<Default>::start(&mut client_rng, b"hunter2", ClientLoginStartParameters::default())?;
/// # Ok::<(), ProtocolError>(())
/// ```
pub fn start<R: RngCore + CryptoRng>(
rng: &mut R,
password: &[u8],
params: ClientLoginStartParameters,
) -> Result<ClientLoginStartResult<CS>, ProtocolError> {
let ClientLoginStartParameters::WithInfo(info) = params;
let (token, alpha) = oprf::blind::<R, CS::Group, CS::Hash>(password, rng)?;
let (ke1_state, ke1_message) = CS::KeyExchange::generate_ke1(info, rng)?;
let (ke1_state, ke1_message) = CS::KeyExchange::generate_ke1(rng)?;
let credential_request = CredentialRequest { alpha, ke1_message };
let serialized_credential_request = credential_request.serialize();
@@ -563,9 +551,14 @@ impl<CS: CipherSuite> ClientLogin<CS> {
credential_response: CredentialResponse<CS>,
params: ClientLoginFinishParameters,
) -> Result<ClientLoginFinishResult<CS>, ProtocolError> {
let optional_ids = match params {
ClientLoginFinishParameters::Default => None,
ClientLoginFinishParameters::WithIdentifiers(id_u, id_s) => Some((id_u, id_s)),
let (context, optional_ids) = match params {
ClientLoginFinishParameters::Default => (vec![], None),
ClientLoginFinishParameters::WithContext(context) => (context, None),
ClientLoginFinishParameters::WithIdentifiers(ids) => (vec![], Some(ids)),
// add context
ClientLoginFinishParameters::WithContextAndIdentifiers(context, ids) => {
(context, Some(ids))
}
};
let password_derived_key = get_password_derived_key::<CS::Group, CS::SlowHash, CS::Hash>(
@@ -578,13 +571,16 @@ impl<CS: CipherSuite> ClientLogin<CS> {
h.expand(STR_MASKING_KEY, &mut masking_key)
.map_err(|_| InternalPakeError::HkdfError)?;
let (server_s_pk, envelope) = unmask_response::<CS::Hash>(
let (server_s_pk, envelope) = unmask_response::<CS>(
&masking_key,
&credential_response.masking_nonce,
&credential_response.masked_response,
)
.map_err(|e| match e {
ProtocolError::InvalidInnerEnvelopeError => PakeError::InvalidLoginError.into(),
ProtocolError::VerificationError(PakeError::SerializationError) => {
PakeError::InvalidLoginError.into()
}
err => err,
})?;
let server_s_pk_bytes = server_s_pk.to_arr().to_vec();
@@ -596,37 +592,25 @@ impl<CS: CipherSuite> ClientLogin<CS> {
err => PakeError::from(err),
})?;
let client_s_sk = PrivateKey::from_bytes(&opened_envelope.client_s_sk)?;
let (id_u, id_s) = match optional_ids {
None => (
KeyPair::<CS::Group>::public_from_private(&client_s_sk)
.to_arr()
.to_vec(),
server_s_pk_bytes,
),
Some((id_u, id_s)) => (id_u, id_s),
};
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(
let (session_key, ke3_message) = CS::KeyExchange::generate_ke3(
credential_response_component,
credential_response.ke2_message,
&self.ke1_state,
&self.serialized_credential_request,
server_s_pk.clone(),
client_s_sk,
id_u,
id_s,
opened_envelope.client_static_keypair.private().clone(),
opened_envelope.id_u.clone(),
opened_envelope.id_s.clone(),
context,
)?;
Ok(ClientLoginFinishResult {
confidential_info,
message: CredentialFinalization { ke3_message },
session_key,
export_key: opened_envelope.export_key.clone(),
@@ -656,19 +640,19 @@ impl<CS: CipherSuite> Clone for ServerLogin<CS> {
/// Optional parameters for server login start
#[derive(Clone)]
pub enum ServerLoginStartParameters {
/// Specifying a confidential info field that will be sent to the client
WithInfo(Vec<u8>),
/// Specifying a context field that the client must agree on
WithContext(Vec<u8>),
/// Specifying a user identifier and server identifier that will be matched against the client
WithIdentifiers(Vec<u8>, Vec<u8>),
/// Specifying a confidential info field that will be sent to the client,
WithIdentifiers(Identifiers),
/// Specifying a context field that the client must agree on,
/// along with a user identifier and and server identifier that will be matched against the client
/// (in that order)
WithInfoAndIdentifiers(Vec<u8>, Vec<u8>, Vec<u8>),
WithContextAndIdentifiers(Vec<u8>, Identifiers),
}
impl Default for ServerLoginStartParameters {
fn default() -> Self {
Self::WithInfo(Vec::new())
Self::WithContext(Vec::new())
}
}
@@ -678,8 +662,6 @@ pub struct ServerLoginStartResult<CS: CipherSuite> {
pub message: CredentialResponse<CS>,
/// The state that the server must keep in order to finish the protocl
pub state: ServerLogin<CS>,
/// The plaintext info sent by the client
pub plain_info: Vec<u8>,
}
// Cannot be derived because it would require for CS to be Clone.
@@ -688,7 +670,6 @@ impl<CS: CipherSuite> Clone for ServerLoginStartResult<CS> {
Self {
message: self.message.clone(),
state: self.state.clone(),
plain_info: self.plain_info.clone(),
}
}
}
@@ -741,32 +722,23 @@ impl<CS: CipherSuite> ServerLogin<CS> {
credential_identifier: &[u8],
params: ServerLoginStartParameters,
) -> Result<ServerLoginStartResult<CS>, ProtocolError> {
// 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),
None => ServerRegistration::dummy(rng, server_setup),
};
let client_s_pk = record.0.client_s_pk.clone();
let (e_info, optional_ids) = match params {
ServerLoginStartParameters::WithInfo(e_info) => (e_info, None),
ServerLoginStartParameters::WithIdentifiers(id_u, id_s) => {
(Vec::new(), Some((id_u, id_s)))
}
ServerLoginStartParameters::WithInfoAndIdentifiers(e_info, id_u, id_s) => {
(e_info, Some((id_u, id_s)))
let (context, optional_ids) = match params {
ServerLoginStartParameters::WithContext(context) => (context, None),
ServerLoginStartParameters::WithIdentifiers(ids) => (Vec::new(), Some(ids)),
ServerLoginStartParameters::WithContextAndIdentifiers(context, ids) => {
(context, Some(ids))
}
};
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 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);
@@ -775,13 +747,14 @@ impl<CS: CipherSuite> ServerLogin<CS> {
&record.0.masking_key,
&masking_nonce,
&server_s_pk,
&envelope,
&record.0.envelope,
)?;
let (id_u, id_s) = match optional_ids {
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 (id_u, id_s) = bytestrings_from_identifiers(
&optional_ids,
&client_s_pk.to_arr(),
&server_s_pk.to_arr(),
);
let l1_bytes = &l1.serialize();
@@ -794,7 +767,7 @@ impl<CS: CipherSuite> ServerLogin<CS> {
let credential_response_component =
CredentialResponse::<CS>::serialize_without_ke(&beta, &masking_nonce, &masked_response);
let (plain_info, ke2_state, ke2_message) = CS::KeyExchange::generate_ke2(
let (ke2_state, ke2_message) = CS::KeyExchange::generate_ke2(
rng,
l1_bytes.to_vec(),
credential_response_component,
@@ -803,7 +776,7 @@ impl<CS: CipherSuite> ServerLogin<CS> {
server_s_sk.clone(),
id_u,
id_s,
e_info,
context,
)?;
let credential_response = CredentialResponse {
@@ -814,7 +787,6 @@ impl<CS: CipherSuite> ServerLogin<CS> {
};
Ok(ServerLoginStartResult {
plain_info,
message: credential_response,
state: Self {
_cs: PhantomData,
@@ -934,24 +906,24 @@ fn oprf_key_from_seed<G: GroupWithMapToCurve, D: Hash>(
Hkdf::<D>::from_prk(oprf_seed)
.map_err(|_| InternalPakeError::HkdfError)?
.expand(
&[credential_identifier, &STR_OPRF_KEY].concat(),
&[credential_identifier, STR_OPRF_KEY].concat(),
&mut oprf_key_bytes,
)
.map_err(|_| InternalPakeError::HkdfError)?;
G::hash_to_scalar::<D>(&oprf_key_bytes[..])
G::hash_to_scalar::<D>(&oprf_key_bytes[..], b"")
}
fn mask_response<D: Hash>(
fn mask_response<CS: CipherSuite>(
masking_key: &[u8],
masking_nonce: &[u8],
server_s_pk: &PublicKey,
envelope: &Envelope<D>,
envelope: &Envelope<CS>,
) -> 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)
let mut xor_pad = vec![0u8; <PublicKey as SizedBytes>::Len::to_usize() + Envelope::<CS>::len()];
Hkdf::<CS::Hash>::from_prk(masking_key)
.map_err(|_| InternalPakeError::HkdfError)?
.expand(
&[masking_nonce, &STR_CREDENTIAL_RESPONSE_PAD].concat(),
&[masking_nonce, STR_CREDENTIAL_RESPONSE_PAD].concat(),
&mut xor_pad,
)
.map_err(|_| InternalPakeError::HkdfError)?;
@@ -965,16 +937,16 @@ fn mask_response<D: Hash>(
.collect())
}
fn unmask_response<D: Hash>(
fn unmask_response<CS: CipherSuite>(
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)
) -> Result<(PublicKey, Envelope<CS>), ProtocolError> {
let mut xor_pad = vec![0u8; <PublicKey as SizedBytes>::Len::to_usize() + Envelope::<CS>::len()];
Hkdf::<CS::Hash>::from_prk(masking_key)
.map_err(|_| InternalPakeError::HkdfError)?
.expand(
&[masking_nonce, &STR_CREDENTIAL_RESPONSE_PAD].concat(),
&[masking_nonce, STR_CREDENTIAL_RESPONSE_PAD].concat(),
&mut xor_pad,
)
.map_err(|_| InternalPakeError::HkdfError)?;
@@ -985,9 +957,12 @@ fn unmask_response<D: Hash>(
.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]))?;
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))
// Ensure that public key is valid
let server_s_pk = KeyPair::<CS::Group>::check_public_key(unchecked_server_s_pk)
.map_err(|_| ProtocolError::VerificationError(PakeError::SerializationError))?;
Ok((server_s_pk, envelope))
}