Separate AKE from OPRF take 2 (#222)

* Separate AKE from OPRF

Introduce X25519 implementation

* Rename `AkeGroup` to `KeGroup` and `Group` to `OprfGroup`

* Add documentation to "Overview"
This commit is contained in:
daxpedda
2021-08-04 12:24:46 -07:00
committed by GitHub
parent 10a38bc58b
commit 88673d8e05
14 changed files with 377 additions and 157 deletions
+64 -59
View File
@@ -40,32 +40,32 @@ const STR_OPAQUE_DERIVE_KEY_PAIR: &[u8] = b"OPAQUE-DeriveKeyPair";
feature = "serialize",
derive(serde::Deserialize, serde::Serialize),
serde(bound(
deserialize = "KeyPair<CS::Group, S>: serde::Deserialize<'de>",
serialize = "KeyPair<CS::Group, S>: serde::Serialize"
deserialize = "KeyPair<CS::KeGroup, S>: serde::Deserialize<'de>",
serialize = "KeyPair<CS::KeGroup, S>: serde::Serialize"
))
)]
pub struct ServerSetup<
CS: CipherSuite,
S: SecretKey<CS::Group> = PrivateKey<<CS as CipherSuite>::Group>,
S: SecretKey<CS::KeGroup> = PrivateKey<<CS as CipherSuite>::KeGroup>,
> {
oprf_seed: GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
keypair: KeyPair<CS::Group, S>,
pub(crate) fake_keypair: KeyPair<CS::Group>,
keypair: KeyPair<CS::KeGroup, S>,
pub(crate) fake_keypair: KeyPair<CS::KeGroup>,
}
impl<CS: CipherSuite> ServerSetup<CS, PrivateKey<CS::Group>> {
impl<CS: CipherSuite> ServerSetup<CS, PrivateKey<CS::KeGroup>> {
/// Generate a new instance of server setup
pub fn new<R: CryptoRng + RngCore>(rng: &mut R) -> Self {
let keypair = KeyPair::<CS::Group>::generate_random(rng);
let keypair = KeyPair::<CS::KeGroup>::generate_random(rng);
Self::new_with_key(rng, keypair)
}
}
impl<CS: CipherSuite, S: SecretKey<CS::Group>> ServerSetup<CS, S> {
impl<CS: CipherSuite, S: SecretKey<CS::KeGroup>> ServerSetup<CS, S> {
/// Create [`ServerSetup`] with the given keypair
pub fn new_with_key<R: CryptoRng + RngCore>(
rng: &mut R,
keypair: KeyPair<CS::Group, S>,
keypair: KeyPair<CS::KeGroup, S>,
) -> Self {
let mut seed = vec![0u8; <CS::Hash as Digest>::OutputSize::to_usize()];
rng.fill_bytes(&mut seed);
@@ -73,7 +73,7 @@ impl<CS: CipherSuite, S: SecretKey<CS::Group>> ServerSetup<CS, S> {
Self {
oprf_seed: GenericArray::clone_from_slice(&seed[..]),
keypair,
fake_keypair: KeyPair::<CS::Group>::generate_random(rng),
fake_keypair: KeyPair::<CS::KeGroup>::generate_random(rng),
}
}
@@ -90,7 +90,7 @@ impl<CS: CipherSuite, S: SecretKey<CS::Group>> ServerSetup<CS, S> {
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError<S::Error>> {
let seed_len = <CS::Hash as Digest>::OutputSize::to_usize();
let key_len = <PrivateKey<CS::Group> as SizedBytes>::Len::to_usize();
let key_len = <PrivateKey<CS::KeGroup> as SizedBytes>::Len::to_usize();
let checked_slice = check_slice_size(input, seed_len + key_len + key_len, "server_setup")?;
Ok(Self {
@@ -102,7 +102,7 @@ impl<CS: CipherSuite, S: SecretKey<CS::Group>> ServerSetup<CS, S> {
}
/// Returns the keypair
pub fn keypair(&self) -> &KeyPair<CS::Group, S> {
pub fn keypair(&self) -> &KeyPair<CS::KeGroup, S> {
&self.keypair
}
}
@@ -122,16 +122,16 @@ impl_debug_eq_hash_for!(
/// The state elements the client holds to register itself
pub struct ClientRegistration<CS: CipherSuite> {
alpha: CS::Group,
alpha: CS::OprfGroup,
/// token containing the client's password and the blinding factor
pub(crate) token: oprf::Token<CS::Group>,
pub(crate) token: oprf::Token<CS::OprfGroup>,
}
impl_clone_for!(struct ClientRegistration<CS: CipherSuite>, [token, alpha]);
impl_debug_eq_hash_for!(
struct ClientRegistration<CS: CipherSuite>,
[token],
[oprf::Token<CS::Group>],
[oprf::Token<CS::OprfGroup>],
);
impl<CS: CipherSuite> ClientRegistration<CS> {
@@ -139,7 +139,7 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
pub fn serialize(&self) -> Vec<u8> {
[
&self.alpha.to_arr().to_vec(),
&CS::Group::scalar_as_bytes(self.token.blind)[..],
&CS::OprfGroup::scalar_as_bytes(self.token.blind)[..],
&self.token.data,
]
.concat()
@@ -147,8 +147,8 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let elem_len = <CS::Group as Group>::ElemLen::to_usize();
let scalar_len = <CS::Group as Group>::ScalarLen::to_usize();
let elem_len = <CS::OprfGroup as Group>::ElemLen::to_usize();
let scalar_len = <CS::OprfGroup as Group>::ScalarLen::to_usize();
let min_expected_len = elem_len + scalar_len;
let checked_slice = (if input.len() <= min_expected_len {
Err(InternalPakeError::SizeError {
@@ -160,14 +160,15 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
Ok(input)
})?;
let alpha =
CS::Group::from_element_slice(GenericArray::from_slice(&checked_slice[..elem_len]))?;
let alpha = CS::OprfGroup::from_element_slice(GenericArray::from_slice(
&checked_slice[..elem_len],
))?;
// Check that the message is actually containing an element of the
// correct subgroup
let blinding_factor_bytes =
GenericArray::from_slice(&checked_slice[elem_len..elem_len + scalar_len]);
let blinding_factor = CS::Group::from_scalar_slice(blinding_factor_bytes)?;
let blinding_factor = CS::OprfGroup::from_scalar_slice(blinding_factor_bytes)?;
let password = checked_slice[elem_len + scalar_len..].to_vec();
Ok(Self {
@@ -257,7 +258,8 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
blinding_factor_rng: &mut R,
password: &[u8],
) -> Result<ClientRegistrationStartResult<CS>, ProtocolError> {
let (token, alpha) = oprf::blind::<R, CS::Group, CS::Hash>(password, blinding_factor_rng)?;
let (token, alpha) =
oprf::blind::<R, CS::OprfGroup, CS::Hash>(password, blinding_factor_rng)?;
Ok(ClientRegistrationStartResult {
message: RegistrationRequest::<CS> { alpha },
@@ -273,7 +275,7 @@ pub struct ClientRegistrationFinishResult<CS: CipherSuite> {
/// The export key output by client registration
pub export_key: GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
/// The server's static public key
pub server_s_pk: PublicKey<CS::Group>,
pub server_s_pk: PublicKey<CS::KeGroup>,
/// Instance of the ClientRegistration, only used in tests for checking zeroize
#[cfg(test)]
pub state: ClientRegistration<CS>,
@@ -321,8 +323,10 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
return Err(ProtocolError::ReflectedValueError);
}
let password_derived_key =
get_password_derived_key::<CS::Group, CS::SlowHash, CS::Hash>(&self.token, r2.beta)?;
let password_derived_key = get_password_derived_key::<CS::OprfGroup, CS::SlowHash, CS::Hash>(
&self.token,
r2.beta,
)?;
#[cfg_attr(not(test), allow(unused_variables))]
let (randomized_pwd, h) = Hkdf::<CS::Hash>::extract(None, &password_derived_key);
@@ -358,7 +362,7 @@ pub struct ServerRegistrationStartResult<CS: CipherSuite> {
pub message: RegistrationResponse<CS>,
/// OPRF key, only used in tests
#[cfg(test)]
pub oprf_key: GenericArray<u8, <CS::Group as Group>::ScalarLen>,
pub oprf_key: GenericArray<u8, <CS::OprfGroup as Group>::ScalarLen>,
}
// Cannot be derived because it would require for CS to be Clone.
@@ -403,18 +407,18 @@ impl<CS: CipherSuite> ServerRegistration<CS> {
/// From the client's "blinded" password, returns a response to be
/// sent back to the client, as well as a ServerRegistration
pub fn start<S: SecretKey<CS::Group>>(
pub fn start<S: SecretKey<CS::KeGroup>>(
server_setup: &ServerSetup<CS, S>,
message: RegistrationRequest<CS>,
credential_identifier: &[u8],
) -> Result<ServerRegistrationStartResult<CS>, ProtocolError> {
let oprf_key = oprf_key_from_seed::<CS::Group, CS::Hash>(
let oprf_key = oprf_key_from_seed::<CS::OprfGroup, CS::Hash>(
&server_setup.oprf_seed,
credential_identifier,
)?;
// Compute beta = alpha^oprf_key
let beta = oprf::evaluate::<CS::Group>(message.alpha, &oprf_key);
let beta = oprf::evaluate::<CS::OprfGroup>(message.alpha, &oprf_key);
Ok(ServerRegistrationStartResult {
message: RegistrationResponse {
@@ -422,7 +426,7 @@ impl<CS: CipherSuite> ServerRegistration<CS> {
server_s_pk: server_setup.keypair.public().clone(),
},
#[cfg(test)]
oprf_key: CS::Group::scalar_as_bytes(oprf_key),
oprf_key: CS::OprfGroup::scalar_as_bytes(oprf_key),
})
}
@@ -433,7 +437,7 @@ impl<CS: CipherSuite> ServerRegistration<CS> {
}
// Creates a dummy instance used for faking a [CredentialResponse]
pub(crate) fn dummy<R: RngCore + CryptoRng, S: SecretKey<CS::Group>>(
pub(crate) fn dummy<R: RngCore + CryptoRng, S: SecretKey<CS::KeGroup>>(
rng: &mut R,
server_setup: &ServerSetup<CS, S>,
) -> Self {
@@ -451,14 +455,14 @@ impl_serialize_and_deserialize_for!(ServerRegistration);
#[cfg_attr(
feature = "serialize",
serde(bound(
deserialize = "oprf::Token<CS::Group>: serde::Deserialize<'de>, <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE1State: serde::Deserialize<'de>",
serialize = "oprf::Token<CS::Group>: serde::Serialize, <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE1State: serde::Serialize"
deserialize = "oprf::Token<CS::OprfGroup>: serde::Deserialize<'de>, <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE1State: serde::Deserialize<'de>",
serialize = "oprf::Token<CS::OprfGroup>: serde::Serialize, <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE1State: serde::Serialize"
))
)]
pub struct ClientLogin<CS: CipherSuite> {
/// token containing the client's password and the blinding factor
token: oprf::Token<CS::Group>,
ke1_state: <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE1State,
token: oprf::Token<CS::OprfGroup>,
ke1_state: <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE1State,
serialized_credential_request: Vec<u8>,
}
@@ -466,14 +470,14 @@ impl_clone_for!(struct ClientLogin<CS: CipherSuite>, [token, ke1_state, serializ
impl_debug_eq_hash_for!(
struct ClientLogin<CS: CipherSuite>,
[token, ke1_state, serialized_credential_request],
[oprf::Token<CS::Group>, <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE1State],
[oprf::Token<CS::OprfGroup>, <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE1State],
);
impl<CS: CipherSuite> ClientLogin<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> Result<Vec<u8>, ProtocolError> {
let output: Vec<u8> = [
&CS::Group::scalar_as_bytes(self.token.blind)[..],
&CS::OprfGroup::scalar_as_bytes(self.token.blind)[..],
&serialize(&self.serialized_credential_request, 2)?,
&serialize(&self.ke1_state.to_bytes(), 2)?,
&self.token.data,
@@ -484,7 +488,7 @@ impl<CS: CipherSuite> ClientLogin<CS> {
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let scalar_len = <CS::Group as Group>::ScalarLen::to_usize();
let scalar_len = <CS::OprfGroup as Group>::ScalarLen::to_usize();
let checked_slice = (if input.len() <= scalar_len {
Err(InternalPakeError::SizeError {
name: "client_login_bytes",
@@ -496,13 +500,13 @@ impl<CS: CipherSuite> ClientLogin<CS> {
})?;
let blinding_factor_bytes = GenericArray::from_slice(&checked_slice[..scalar_len]);
let blinding_factor = CS::Group::from_scalar_slice(blinding_factor_bytes)?;
let blinding_factor = CS::OprfGroup::from_scalar_slice(blinding_factor_bytes)?;
let (serialized_credential_request, remainder) = tokenize(&checked_slice[scalar_len..], 2)?;
let (ke1_state_bytes, password) = tokenize(&remainder, 2)?;
let ke1_state =
<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE1State::from_bytes::<CS>(
<CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE1State::from_bytes::<CS>(
&ke1_state_bytes[..],
)?;
Ok(Self {
@@ -575,7 +579,7 @@ pub struct ClientLoginFinishResult<CS: CipherSuite> {
/// The client-side export key
pub export_key: GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
/// The server's static public key
pub server_s_pk: PublicKey<CS::Group>,
pub server_s_pk: PublicKey<CS::KeGroup>,
/// Instance of the ClientLogin, only used in tests for checking zeroize
#[cfg(test)]
pub state: ClientLogin<CS>,
@@ -611,7 +615,7 @@ impl<CS: CipherSuite> ClientLogin<CS> {
rng: &mut R,
password: &[u8],
) -> Result<ClientLoginStartResult<CS>, ProtocolError> {
let (token, alpha) = oprf::blind::<R, CS::Group, CS::Hash>(password, rng)?;
let (token, alpha) = oprf::blind::<R, CS::OprfGroup, CS::Hash>(password, rng)?;
let (ke1_state, ke1_message) = CS::KeyExchange::generate_ke1(rng)?;
@@ -652,7 +656,7 @@ impl<CS: CipherSuite> ClientLogin<CS> {
return Err(ProtocolError::ReflectedValueError);
}
let password_derived_key = get_password_derived_key::<CS::Group, CS::SlowHash, CS::Hash>(
let password_derived_key = get_password_derived_key::<CS::OprfGroup, CS::SlowHash, CS::Hash>(
&self.token,
credential_response.beta,
)?;
@@ -722,7 +726,7 @@ impl<CS: CipherSuite> ClientLogin<CS> {
/// The state elements the server holds to record a login
pub struct ServerLogin<CS: CipherSuite> {
ke2_state: <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE2State,
ke2_state: <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE2State,
_cs: PhantomData<CS>,
}
@@ -730,7 +734,7 @@ impl_clone_for!(struct ServerLogin<CS: CipherSuite>, [ke2_state, _cs]);
impl_debug_eq_hash_for!(
struct ServerLogin<CS: CipherSuite>,
[ke2_state, _cs],
[<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE2State],
[<CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE2State],
);
/// Optional parameters for server login start
@@ -766,7 +770,7 @@ pub struct ServerLoginStartResult<CS: CipherSuite> {
pub server_mac_key: GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
/// OPRF key, only used in tests
#[cfg(test)]
pub oprf_key: GenericArray<u8, <CS::Group as Group>::ScalarLen>,
pub oprf_key: GenericArray<u8, <CS::OprfGroup as Group>::ScalarLen>,
}
// Cannot be derived because it would require for CS to be Clone.
@@ -817,15 +821,16 @@ impl<CS: CipherSuite> ServerLogin<CS> {
pub fn deserialize(bytes: &[u8]) -> Result<Self, ProtocolError> {
Ok(Self {
_cs: PhantomData,
ke2_state: <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE2State::from_bytes::<
CS,
>(bytes)?,
ke2_state:
<CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE2State::from_bytes::<CS>(
bytes,
)?,
})
}
/// From the client's "blinded" password, returns a challenge to be
/// sent back to the client, as well as a ServerLogin
pub fn start<R: RngCore + CryptoRng, S: SecretKey<CS::Group>>(
pub fn start<R: RngCore + CryptoRng, S: SecretKey<CS::KeGroup>>(
rng: &mut R,
server_setup: &ServerSetup<CS, S>,
password_file: Option<ServerRegistration<CS>>,
@@ -871,7 +876,7 @@ impl<CS: CipherSuite> ServerLogin<CS> {
let l1_bytes = &l1.serialize();
let oprf_key = oprf_key_from_seed::<CS::Group, CS::Hash>(
let oprf_key = oprf_key_from_seed::<CS::OprfGroup, CS::Hash>(
&server_setup.oprf_seed,
credential_identifier,
)
@@ -911,7 +916,7 @@ impl<CS: CipherSuite> ServerLogin<CS> {
#[cfg(test)]
server_mac_key: result.3,
#[cfg(test)]
oprf_key: CS::Group::scalar_as_bytes(oprf_key),
oprf_key: CS::OprfGroup::scalar_as_bytes(oprf_key),
})
}
@@ -921,7 +926,7 @@ impl<CS: CipherSuite> ServerLogin<CS> {
self,
message: CredentialFinalization<CS>,
) -> Result<ServerLoginFinishResult<CS>, ProtocolError> {
let session_key = <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::finish_ke(
let session_key = <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::finish_ke(
message.ke3_message,
&self.ke2_state,
)
@@ -1033,11 +1038,11 @@ fn oprf_key_from_seed<G: Group, D: Hash>(
fn mask_response<CS: CipherSuite>(
masking_key: &[u8],
masking_nonce: &[u8],
server_s_pk: &PublicKey<CS::Group>,
server_s_pk: &PublicKey<CS::KeGroup>,
envelope: &Envelope<CS>,
) -> Result<Vec<u8>, ProtocolError> {
let mut xor_pad =
vec![0u8; <PublicKey<CS::Group> as SizedBytes>::Len::to_usize() + Envelope::<CS>::len()];
vec![0u8; <PublicKey<CS::KeGroup> as SizedBytes>::Len::to_usize() + Envelope::<CS>::len()];
Hkdf::<CS::Hash>::from_prk(masking_key)
.map_err(|_| InternalPakeError::HkdfError)?
.expand(
@@ -1059,9 +1064,9 @@ fn unmask_response<CS: CipherSuite>(
masking_key: &[u8],
masking_nonce: &[u8],
masked_response: &[u8],
) -> Result<(PublicKey<CS::Group>, Envelope<CS>), ProtocolError> {
) -> Result<(PublicKey<CS::KeGroup>, Envelope<CS>), ProtocolError> {
let mut xor_pad =
vec![0u8; <PublicKey<CS::Group> as SizedBytes>::Len::to_usize() + Envelope::<CS>::len()];
vec![0u8; <PublicKey<CS::KeGroup> as SizedBytes>::Len::to_usize() + Envelope::<CS>::len()];
Hkdf::<CS::Hash>::from_prk(masking_key)
.map_err(|_| InternalPakeError::HkdfError)?
.expand(
@@ -1074,13 +1079,13 @@ fn unmask_response<CS: CipherSuite>(
.zip(masked_response.iter())
.map(|(&x1, &x2)| x1 ^ x2)
.collect();
let key_len = <PublicKey<CS::Group> as SizedBytes>::Len::to_usize();
let key_len = <PublicKey<CS::KeGroup> 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..])?;
// Ensure that public key is valid
let server_s_pk = KeyPair::<CS::Group>::check_public_key(unchecked_server_s_pk)
let server_s_pk = KeyPair::<CS::KeGroup>::check_public_key(unchecked_server_s_pk)
.map_err(|_| ProtocolError::VerificationError(PakeError::SerializationError))?;
Ok((server_s_pk, envelope))