General cleanups and reorganizing code (#236)

This commit is contained in:
Kevin Lewi
2021-09-25 16:36:00 -07:00
committed by GitHub
parent 11a93fe63e
commit 65a0c2f98d
12 changed files with 976 additions and 947 deletions
+2 -1
View File
@@ -47,7 +47,8 @@ To learn more about contributing to this project, [see this document](./CONTRIBU
#### Acknowledgments #### Acknowledgments
Special thanks go to Hugo Krawczyk and Chris Wood for helping to clarify discrepancies and making suggestions for improving Special thanks go to Hugo Krawczyk and Chris Wood for helping to clarify discrepancies and making suggestions for improving
this implementation. this implementation. Additional credit goes to @daxpedda for adding no_std support, p256 support, and making other general
improvements to the library.
License License
+88 -89
View File
@@ -26,45 +26,8 @@ const STR_AUTH_KEY: &[u8] = b"AuthKey";
const STR_EXPORT_KEY: &[u8] = b"ExportKey"; const STR_EXPORT_KEY: &[u8] = b"ExportKey";
const STR_PRIVATE_KEY: &[u8] = b"PrivateKey"; const STR_PRIVATE_KEY: &[u8] = b"PrivateKey";
const STR_OPAQUE_DERIVE_AUTH_KEY_PAIR: &[u8] = b"OPAQUE-DeriveAuthKeyPair"; const STR_OPAQUE_DERIVE_AUTH_KEY_PAIR: &[u8] = b"OPAQUE-DeriveAuthKeyPair";
const NONCE_LEN: usize = 32; const NONCE_LEN: usize = 32;
fn build_inner_envelope_internal<CS: CipherSuite>(
random_pwd: &[u8],
nonce: &[u8],
) -> Result<PublicKey<CS::KeGroup>, ProtocolError> {
let h = Hkdf::<CS::Hash>::new(None, random_pwd);
let mut keypair_seed = vec![0u8; <CS::KeGroup as Group>::ScalarLen::USIZE];
h.expand(&[nonce, STR_PRIVATE_KEY].concat(), &mut keypair_seed)
.map_err(|_| InternalError::HkdfError)?;
let client_static_keypair = KeyPair::<CS::KeGroup>::from_private_key_slice(
&CS::OprfGroup::scalar_as_bytes(CS::OprfGroup::hash_to_scalar::<CS::Hash>(
&keypair_seed[..],
STR_OPAQUE_DERIVE_AUTH_KEY_PAIR,
)?),
)?;
Ok(client_static_keypair.public().clone())
}
fn recover_keys_internal<CS: CipherSuite>(
random_pwd: &[u8],
nonce: &[u8],
) -> Result<KeyPair<CS::KeGroup>, ProtocolError> {
let h = Hkdf::<CS::Hash>::new(None, random_pwd);
let mut keypair_seed = vec![0u8; <CS::KeGroup as Group>::ScalarLen::USIZE];
h.expand(&[nonce, STR_PRIVATE_KEY].concat(), &mut keypair_seed)
.map_err(|_| InternalError::HkdfError)?;
let client_static_keypair = KeyPair::<CS::KeGroup>::from_private_key_slice(
&CS::OprfGroup::scalar_as_bytes(CS::OprfGroup::hash_to_scalar::<CS::Hash>(
&keypair_seed[..],
STR_OPAQUE_DERIVE_AUTH_KEY_PAIR,
)?),
)?;
Ok(client_static_keypair)
}
#[derive(Clone, Debug, Eq, Hash, PartialEq, Zeroize)] #[derive(Clone, Debug, Eq, Hash, PartialEq, Zeroize)]
#[zeroize(drop)] #[zeroize(drop)]
pub(crate) enum InnerEnvelopeMode { pub(crate) enum InnerEnvelopeMode {
@@ -151,58 +114,6 @@ type SealResult<CS> = (
); );
impl<CS: CipherSuite> Envelope<CS> { impl<CS: CipherSuite> Envelope<CS> {
fn hmac_key_size() -> usize {
<CS::Hash as Digest>::OutputSize::USIZE
}
fn export_key_size() -> usize {
<CS::Hash as Digest>::OutputSize::USIZE
}
pub(crate) fn len() -> usize {
<CS::Hash as Digest>::OutputSize::USIZE + NONCE_LEN
}
pub(crate) fn serialize(&self) -> Vec<u8> {
[&self.nonce[..], &self.hmac[..]].concat()
}
pub(crate) fn deserialize(bytes: &[u8]) -> Result<Self, ProtocolError> {
let mode = InnerEnvelopeMode::Internal; // Better way to hard-code this?
if bytes.len() < NONCE_LEN {
return Err(ProtocolError::SerializationError);
}
let nonce = bytes[..NONCE_LEN].to_vec();
let remainder = match mode {
InnerEnvelopeMode::Zero => {
return Err(InternalError::IncompatibleEnvelopeModeError.into())
}
InnerEnvelopeMode::Internal => bytes[NONCE_LEN..].to_vec(),
};
let hmac_key_size = Self::hmac_key_size();
let hmac = check_slice_size(&remainder, hmac_key_size, "hmac_key_size")?;
Ok(Self {
mode,
nonce,
hmac: GenericArray::clone_from_slice(hmac),
})
}
// Creates a dummy envelope object that serializes to the all-zeros byte string
pub(crate) fn dummy() -> Self {
Self {
mode: InnerEnvelopeMode::Zero,
nonce: vec![0u8; NONCE_LEN],
hmac: GenericArray::clone_from_slice(&vec![
0u8;
<CS::Hash as Digest>::OutputSize::USIZE
]),
}
}
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
pub(crate) fn seal<R: RngCore + CryptoRng>( pub(crate) fn seal<R: RngCore + CryptoRng>(
rng: &mut R, rng: &mut R,
@@ -330,6 +241,58 @@ impl<CS: CipherSuite> Envelope<CS> {
}) })
} }
// Creates a dummy envelope object that serializes to the all-zeros byte string
pub(crate) fn dummy() -> Self {
Self {
mode: InnerEnvelopeMode::Zero,
nonce: vec![0u8; NONCE_LEN],
hmac: GenericArray::clone_from_slice(&vec![
0u8;
<CS::Hash as Digest>::OutputSize::USIZE
]),
}
}
fn hmac_key_size() -> usize {
<CS::Hash as Digest>::OutputSize::USIZE
}
fn export_key_size() -> usize {
<CS::Hash as Digest>::OutputSize::USIZE
}
pub(crate) fn len() -> usize {
<CS::Hash as Digest>::OutputSize::USIZE + NONCE_LEN
}
pub(crate) fn serialize(&self) -> Vec<u8> {
[&self.nonce[..], &self.hmac[..]].concat()
}
pub(crate) fn deserialize(bytes: &[u8]) -> Result<Self, ProtocolError> {
let mode = InnerEnvelopeMode::Internal; // Better way to hard-code this?
if bytes.len() < NONCE_LEN {
return Err(ProtocolError::SerializationError);
}
let nonce = bytes[..NONCE_LEN].to_vec();
let remainder = match mode {
InnerEnvelopeMode::Zero => {
return Err(InternalError::IncompatibleEnvelopeModeError.into())
}
InnerEnvelopeMode::Internal => bytes[NONCE_LEN..].to_vec(),
};
let hmac_key_size = Self::hmac_key_size();
let hmac = check_slice_size(&remainder, hmac_key_size, "hmac_key_size")?;
Ok(Self {
mode,
nonce,
hmac: GenericArray::clone_from_slice(hmac),
})
}
#[cfg(test)] #[cfg(test)]
pub fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> { pub fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> {
vec![(self.hmac.as_ptr(), self.hmac.len())] vec![(self.hmac.as_ptr(), self.hmac.len())]
@@ -353,6 +316,42 @@ impl<CS: CipherSuite> Drop for Envelope<CS> {
// Helper functions // Helper functions
fn build_inner_envelope_internal<CS: CipherSuite>(
random_pwd: &[u8],
nonce: &[u8],
) -> Result<PublicKey<CS::KeGroup>, ProtocolError> {
let h = Hkdf::<CS::Hash>::new(None, random_pwd);
let mut keypair_seed = vec![0u8; <CS::KeGroup as Group>::ScalarLen::USIZE];
h.expand(&[nonce, STR_PRIVATE_KEY].concat(), &mut keypair_seed)
.map_err(|_| InternalError::HkdfError)?;
let client_static_keypair = KeyPair::<CS::KeGroup>::from_private_key_slice(
&CS::OprfGroup::scalar_as_bytes(CS::OprfGroup::hash_to_scalar::<CS::Hash>(
&keypair_seed[..],
STR_OPAQUE_DERIVE_AUTH_KEY_PAIR,
)?),
)?;
Ok(client_static_keypair.public().clone())
}
fn recover_keys_internal<CS: CipherSuite>(
random_pwd: &[u8],
nonce: &[u8],
) -> Result<KeyPair<CS::KeGroup>, ProtocolError> {
let h = Hkdf::<CS::Hash>::new(None, random_pwd);
let mut keypair_seed = vec![0u8; <CS::KeGroup as Group>::ScalarLen::USIZE];
h.expand(&[nonce, STR_PRIVATE_KEY].concat(), &mut keypair_seed)
.map_err(|_| InternalError::HkdfError)?;
let client_static_keypair = KeyPair::<CS::KeGroup>::from_private_key_slice(
&CS::OprfGroup::scalar_as_bytes(CS::OprfGroup::hash_to_scalar::<CS::Hash>(
&keypair_seed[..],
STR_OPAQUE_DERIVE_AUTH_KEY_PAIR,
)?),
)?;
Ok(client_static_keypair)
}
fn construct_aad(id_u: &[u8], id_s: &[u8], server_s_pk: &[u8]) -> Vec<u8> { fn construct_aad(id_u: &[u8], id_s: &[u8], server_s_pk: &[u8]) -> Vec<u8> {
[server_s_pk, id_s, id_u].concat() [server_s_pk, id_s, id_u].concat()
} }
+4
View File
@@ -54,6 +54,7 @@ impl Group for RistrettoPoint {
) -> Result<Self::Scalar, InternalError> { ) -> Result<Self::Scalar, InternalError> {
Ok(Scalar::from_bytes_mod_order(*scalar_bits.as_ref())) Ok(Scalar::from_bytes_mod_order(*scalar_bits.as_ref()))
} }
fn random_nonzero_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar { fn random_nonzero_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
loop { loop {
let scalar = { let scalar = {
@@ -78,9 +79,11 @@ impl Group for RistrettoPoint {
} }
} }
} }
fn scalar_as_bytes(scalar: Self::Scalar) -> GenericArray<u8, Self::ScalarLen> { fn scalar_as_bytes(scalar: Self::Scalar) -> GenericArray<u8, Self::ScalarLen> {
scalar.to_bytes().into() scalar.to_bytes().into()
} }
fn scalar_invert(scalar: &Self::Scalar) -> Self::Scalar { fn scalar_invert(scalar: &Self::Scalar) -> Self::Scalar {
scalar.invert() scalar.invert()
} }
@@ -94,6 +97,7 @@ impl Group for RistrettoPoint {
.decompress() .decompress()
.ok_or(InternalError::PointError) .ok_or(InternalError::PointError)
} }
// serialization of a group element // serialization of a group element
fn to_arr(&self) -> GenericArray<u8, Self::ElemLen> { fn to_arr(&self) -> GenericArray<u8, Self::ElemLen> {
self.compress().to_bytes().into() self.compress().to_bytes().into()
+4
View File
@@ -29,6 +29,7 @@ impl Group for MontgomeryPoint {
) -> Result<Self::Scalar, InternalError> { ) -> Result<Self::Scalar, InternalError> {
Ok(Scalar::from_bytes_mod_order(*scalar_bits.as_ref())) Ok(Scalar::from_bytes_mod_order(*scalar_bits.as_ref()))
} }
fn random_nonzero_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar { fn random_nonzero_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
loop { loop {
let scalar = { let scalar = {
@@ -53,9 +54,11 @@ impl Group for MontgomeryPoint {
} }
} }
} }
fn scalar_as_bytes(scalar: Self::Scalar) -> GenericArray<u8, Self::ScalarLen> { fn scalar_as_bytes(scalar: Self::Scalar) -> GenericArray<u8, Self::ScalarLen> {
scalar.to_bytes().into() scalar.to_bytes().into()
} }
fn scalar_invert(_scalar: &Self::Scalar) -> Self::Scalar { fn scalar_invert(_scalar: &Self::Scalar) -> Self::Scalar {
unreachable!("this algorithm should only be used as the `KeGroup`") unreachable!("this algorithm should only be used as the `KeGroup`")
} }
@@ -67,6 +70,7 @@ impl Group for MontgomeryPoint {
) -> Result<Self, InternalError> { ) -> Result<Self, InternalError> {
Ok(Self(*element_bits.as_ref())) Ok(Self(*element_bits.as_ref()))
} }
// serialization of a group element // serialization of a group element
fn to_arr(&self) -> GenericArray<u8, Self::ElemLen> { fn to_arr(&self) -> GenericArray<u8, Self::ElemLen> {
self.to_bytes().into() self.to_bytes().into()
+67
View File
@@ -100,3 +100,70 @@ macro_rules! impl_clone_for {
} }
}; };
} }
/// Inner macro used for deriving `serde`'s `Serialize` and `Deserialize` traits.
macro_rules! impl_serialize_and_deserialize_for {
($t:ident) => {
#[cfg(feature = "serialize")]
impl<CS: CipherSuite> serde::Serialize for $t<CS> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
if serializer.is_human_readable() {
serializer.serialize_str(&base64::encode(&self.serialize()))
} else {
serializer.serialize_bytes(&self.serialize())
}
}
}
#[cfg(feature = "serialize")]
impl<'de, CS: CipherSuite> serde::Deserialize<'de> for $t<CS> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
if deserializer.is_human_readable() {
let s = <&str>::deserialize(deserializer)?;
$t::<CS>::deserialize(&base64::decode(s).map_err(serde::de::Error::custom)?)
.map_err(serde::de::Error::custom)
} else {
struct ByteVisitor<CS: CipherSuite> {
marker: core::marker::PhantomData<CS>,
}
impl<'de, CS: CipherSuite> serde::de::Visitor<'de> for ByteVisitor<CS> {
type Value = $t<CS>;
fn expecting(
&self,
formatter: &mut core::fmt::Formatter,
) -> core::fmt::Result {
formatter.write_str(core::concat!(
"the byte representation of a ",
core::stringify!($t)
))
}
fn visit_bytes<E>(self, value: &[u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
$t::<CS>::deserialize(value).map_err(|_| {
serde::de::Error::invalid_value(
serde::de::Unexpected::Bytes(value),
&core::concat!(
"invalid byte sequence for ",
core::stringify!($t)
),
)
})
}
}
deserializer.deserialize_bytes(ByteVisitor::<CS> {
marker: core::marker::PhantomData,
})
}
}
}
};
}
+254 -225
View File
@@ -31,8 +31,12 @@ use hmac::{Hmac, Mac, NewMac};
use rand::{CryptoRng, RngCore}; use rand::{CryptoRng, RngCore};
use zeroize::Zeroize; use zeroize::Zeroize;
pub(crate) type NonceLen = U32; ///////////////
// Constants //
// ========= //
///////////////
pub(crate) type NonceLen = U32;
static STR_RFC: &[u8] = b"RFCXXXX"; static STR_RFC: &[u8] = b"RFCXXXX";
static STR_CLIENT_MAC: &[u8] = b"ClientMAC"; static STR_CLIENT_MAC: &[u8] = b"ClientMAC";
static STR_HANDSHAKE_SECRET: &[u8] = b"HandshakeSecret"; static STR_HANDSHAKE_SECRET: &[u8] = b"HandshakeSecret";
@@ -40,10 +44,72 @@ static STR_SERVER_MAC: &[u8] = b"ServerMAC";
static STR_SESSION_KEY: &[u8] = b"SessionKey"; static STR_SESSION_KEY: &[u8] = b"SessionKey";
static STR_OPAQUE: &[u8] = b"OPAQUE-"; static STR_OPAQUE: &[u8] = b"OPAQUE-";
////////////////////////////
// High-level API Structs //
// ====================== //
////////////////////////////
#[allow(clippy::upper_case_acronyms)] #[allow(clippy::upper_case_acronyms)]
/// The Triple Diffie-Hellman key exchange implementation /// The Triple Diffie-Hellman key exchange implementation
pub struct TripleDH; pub struct TripleDH;
/// The client state produced after the first key exchange message
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
pub struct Ke1State<G: Group> {
client_e_sk: PrivateKey<G>,
client_nonce: GenericArray<u8, NonceLen>,
}
impl_clone_for!(
struct Ke1State<G: Group>,
[client_e_sk, client_nonce],
);
impl_debug_eq_hash_for!(
struct Ke1State<G: Group>,
[client_e_sk, client_nonce],
);
/// The first key exchange message
#[derive(PartialEq, Eq, Debug, Hash, Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
pub struct Ke1Message<G: Group> {
pub(crate) client_nonce: GenericArray<u8, NonceLen>,
pub(crate) client_e_pk: PublicKey<G>,
}
/// The server state produced after the second key exchange message
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serialize", serde(bound = ""))]
pub struct Ke2State<HashLen: ArrayLength<u8>> {
km3: GenericArray<u8, HashLen>,
hashed_transcript: GenericArray<u8, HashLen>,
session_key: GenericArray<u8, HashLen>,
}
/// The second key exchange message
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serialize", serde(bound = ""))]
pub struct Ke2Message<G: Group, HashLen: ArrayLength<u8>> {
server_nonce: GenericArray<u8, NonceLen>,
server_e_pk: PublicKey<G>,
mac: GenericArray<u8, HashLen>,
}
/// The third key exchange message
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serialize", serde(bound = ""))]
pub struct Ke3Message<HashLen: ArrayLength<u8>> {
mac: GenericArray<u8, HashLen>,
}
////////////////////////////////
// High-level Implementations //
// ========================== //
////////////////////////////////
impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH { impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
type KE1State = Ke1State<G>; type KE1State = Ke1State<G>;
type KE2State = Ke2State<<D as FixedOutput>::OutputSize>; type KE2State = Ke2State<<D as FixedOutput>::OutputSize>;
@@ -213,207 +279,10 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
} }
} }
/// The client state produced after the first key exchange message /////////////////////////
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))] // Convenience Structs //
pub struct Ke1State<G: Group> { //==================== //
client_e_sk: PrivateKey<G>, /////////////////////////
client_nonce: GenericArray<u8, NonceLen>,
}
impl_clone_for!(
struct Ke1State<G: Group>,
[client_e_sk, client_nonce],
);
impl_debug_eq_hash_for!(
struct Ke1State<G: Group>,
[client_e_sk, client_nonce],
);
// This can't be derived because of the use of a generic parameter
impl<G: Group> Zeroize for Ke1State<G> {
fn zeroize(&mut self) {
self.client_e_sk.zeroize();
self.client_nonce.zeroize();
}
}
impl<G: Group> Drop for Ke1State<G> {
fn drop(&mut self) {
self.zeroize();
}
}
/// The first key exchange message
#[derive(PartialEq, Eq, Debug, Hash, Clone)]
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
pub struct Ke1Message<G: Group> {
pub(crate) client_nonce: GenericArray<u8, NonceLen>,
pub(crate) client_e_pk: PublicKey<G>,
}
impl<G: Group> FromBytes for Ke1State<G> {
fn from_bytes<CS: CipherSuite>(bytes: &[u8]) -> Result<Self, ProtocolError> {
let key_len = <G as Group>::ElemLen::USIZE;
let nonce_len = NonceLen::USIZE;
let checked_bytes = check_slice_size_atleast(bytes, key_len + nonce_len, "ke1_state")?;
Ok(Self {
client_e_sk: PrivateKey::from_bytes(&checked_bytes[..key_len])?,
client_nonce: GenericArray::clone_from_slice(
&checked_bytes[key_len..key_len + nonce_len],
),
})
}
}
impl<G: Group> ToBytesWithPointers for Ke1State<G> {
fn to_bytes(&self) -> Vec<u8> {
let output: Vec<u8> = [&self.client_e_sk.to_arr(), &self.client_nonce[..]].concat();
output
}
#[cfg(test)]
fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> {
vec![
(self.client_e_sk.as_ptr(), G::ScalarLen::USIZE),
(self.client_nonce.as_ptr(), NonceLen::USIZE),
]
}
}
impl<G: Group> ToBytes for Ke1Message<G> {
fn to_bytes(&self) -> Vec<u8> {
[&self.client_nonce[..], &self.client_e_pk.to_arr()].concat()
}
}
impl<G: Group> FromBytes for Ke1Message<G> {
fn from_bytes<CS: CipherSuite>(ke1_message_bytes: &[u8]) -> Result<Self, ProtocolError> {
let nonce_len = NonceLen::USIZE;
let checked_nonce = check_slice_size(
ke1_message_bytes,
nonce_len + <G as Group>::ElemLen::USIZE,
"ke1_message nonce",
)?;
Ok(Self {
client_nonce: GenericArray::clone_from_slice(&checked_nonce[..nonce_len]),
client_e_pk: PublicKey::from_bytes(&checked_nonce[nonce_len..])?,
})
}
}
/// The server state produced after the second key exchange message
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serialize", serde(bound = ""))]
pub struct Ke2State<HashLen: ArrayLength<u8>> {
km3: GenericArray<u8, HashLen>,
hashed_transcript: GenericArray<u8, HashLen>,
session_key: GenericArray<u8, HashLen>,
}
// This can't be derived because of the use of a phantom parameter
impl<HashLen: ArrayLength<u8>> Zeroize for Ke2State<HashLen> {
fn zeroize(&mut self) {
self.km3.zeroize();
self.hashed_transcript.zeroize();
self.session_key.zeroize();
}
}
impl<HashLen: ArrayLength<u8>> Drop for Ke2State<HashLen> {
fn drop(&mut self) {
self.zeroize();
}
}
impl<HashLen: ArrayLength<u8>> ToBytesWithPointers for Ke2State<HashLen> {
fn to_bytes(&self) -> Vec<u8> {
[
&self.km3[..],
&self.hashed_transcript[..],
&self.session_key[..],
]
.concat()
}
#[cfg(test)]
fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> {
vec![
(self.km3.as_ptr(), HashLen::USIZE),
(self.hashed_transcript.as_ptr(), HashLen::USIZE),
(self.session_key.as_ptr(), HashLen::USIZE),
]
}
}
/// The second key exchange message
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serialize", serde(bound = ""))]
pub struct Ke2Message<G: Group, HashLen: ArrayLength<u8>> {
server_nonce: GenericArray<u8, NonceLen>,
server_e_pk: PublicKey<G>,
mac: GenericArray<u8, HashLen>,
}
impl<HashLen: ArrayLength<u8>> FromBytes for Ke2State<HashLen> {
fn from_bytes<CS: CipherSuite>(input: &[u8]) -> Result<Self, ProtocolError> {
let hash_len = HashLen::USIZE;
let checked_bytes = check_slice_size(input, 3 * hash_len, "ke2_state")?;
Ok(Self {
km3: GenericArray::clone_from_slice(&checked_bytes[..hash_len]),
hashed_transcript: GenericArray::clone_from_slice(
&checked_bytes[hash_len..2 * hash_len],
),
session_key: GenericArray::clone_from_slice(&checked_bytes[2 * hash_len..3 * hash_len]),
})
}
}
impl<G: Group, HashLen: ArrayLength<u8>> ToBytes for Ke2Message<G, HashLen> {
fn to_bytes(&self) -> Vec<u8> {
[&self.to_bytes_without_info_or_mac(), &self.mac[..]].concat()
}
}
impl<G: Group, HashLen: ArrayLength<u8>> Ke2Message<G, HashLen> {
fn to_bytes_without_info_or_mac(&self) -> Vec<u8> {
[&self.server_nonce[..], &self.server_e_pk.to_arr()].concat()
}
}
impl<G: Group, HashLen: ArrayLength<u8>> FromBytes for Ke2Message<G, HashLen> {
fn from_bytes<CS: CipherSuite>(input: &[u8]) -> Result<Self, ProtocolError> {
let key_len = <G as Group>::ElemLen::USIZE;
let nonce_len = NonceLen::USIZE;
let checked_nonce = check_slice_size_atleast(input, nonce_len, "ke2_message nonce")?;
let unchecked_server_e_pk = check_slice_size_atleast(
&checked_nonce[nonce_len..],
key_len,
"ke2_message server_e_pk",
)?;
let checked_mac = check_slice_size(
&unchecked_server_e_pk[key_len..],
HashLen::USIZE,
"ke1_message mac",
)?;
// Check the public key bytes
let server_e_pk = KeyPair::<CS::OprfGroup>::check_public_key(PublicKey::from_bytes(
&unchecked_server_e_pk[..key_len],
)?)?;
Ok(Self {
server_nonce: GenericArray::clone_from_slice(&checked_nonce[..nonce_len]),
server_e_pk: PublicKey::from_bytes(&server_e_pk)?,
mac: GenericArray::clone_from_slice(checked_mac),
})
}
}
#[allow(clippy::upper_case_acronyms)] #[allow(clippy::upper_case_acronyms)]
// The triple of public and private components used in the 3DH computation // The triple of public and private components used in the 3DH computation
@@ -442,29 +311,10 @@ type TripleDHDerivationResult<D> = (
Vec<u8>, Vec<u8>,
); );
/// The third key exchange message ////////////////////////////////////////////////
#[derive(Clone, Debug, Eq, Hash, PartialEq)] // Helper functions and Trait Implementations //
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))] // ========================================== //
#[cfg_attr(feature = "serialize", serde(bound = ""))] ////////////////////////////////////////////////
pub struct Ke3Message<HashLen: ArrayLength<u8>> {
mac: GenericArray<u8, HashLen>,
}
impl<HashLen: ArrayLength<u8>> ToBytes for Ke3Message<HashLen> {
fn to_bytes(&self) -> Vec<u8> {
self.mac.to_vec()
}
}
impl<HashLen: ArrayLength<u8>> FromBytes for Ke3Message<HashLen> {
fn from_bytes<CS: CipherSuite>(bytes: &[u8]) -> Result<Self, ProtocolError> {
let checked_bytes = check_slice_size(bytes, HashLen::USIZE, "ke3_message")?;
Ok(Self {
mac: GenericArray::clone_from_slice(checked_bytes),
})
}
}
// Helper functions // Helper functions
@@ -577,3 +427,182 @@ fn generate_nonce<R: RngCore + CryptoRng>(rng: &mut R) -> GenericArray<u8, Nonce
rng.fill_bytes(&mut nonce_bytes); rng.fill_bytes(&mut nonce_bytes);
GenericArray::clone_from_slice(&nonce_bytes) GenericArray::clone_from_slice(&nonce_bytes)
} }
// Serialization and deserialization implementations
impl<G: Group> FromBytes for Ke1State<G> {
fn from_bytes<CS: CipherSuite>(bytes: &[u8]) -> Result<Self, ProtocolError> {
let key_len = <G as Group>::ElemLen::USIZE;
let nonce_len = NonceLen::USIZE;
let checked_bytes = check_slice_size_atleast(bytes, key_len + nonce_len, "ke1_state")?;
Ok(Self {
client_e_sk: PrivateKey::from_bytes(&checked_bytes[..key_len])?,
client_nonce: GenericArray::clone_from_slice(
&checked_bytes[key_len..key_len + nonce_len],
),
})
}
}
impl<G: Group> ToBytesWithPointers for Ke1State<G> {
fn to_bytes(&self) -> Vec<u8> {
let output: Vec<u8> = [&self.client_e_sk.to_arr(), &self.client_nonce[..]].concat();
output
}
#[cfg(test)]
fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> {
vec![
(self.client_e_sk.as_ptr(), G::ScalarLen::USIZE),
(self.client_nonce.as_ptr(), NonceLen::USIZE),
]
}
}
impl<G: Group> FromBytes for Ke1Message<G> {
fn from_bytes<CS: CipherSuite>(ke1_message_bytes: &[u8]) -> Result<Self, ProtocolError> {
let nonce_len = NonceLen::USIZE;
let checked_nonce = check_slice_size(
ke1_message_bytes,
nonce_len + <G as Group>::ElemLen::USIZE,
"ke1_message nonce",
)?;
Ok(Self {
client_nonce: GenericArray::clone_from_slice(&checked_nonce[..nonce_len]),
client_e_pk: PublicKey::from_bytes(&checked_nonce[nonce_len..])?,
})
}
}
impl<G: Group> ToBytes for Ke1Message<G> {
fn to_bytes(&self) -> Vec<u8> {
[&self.client_nonce[..], &self.client_e_pk.to_arr()].concat()
}
}
impl<HashLen: ArrayLength<u8>> FromBytes for Ke2State<HashLen> {
fn from_bytes<CS: CipherSuite>(input: &[u8]) -> Result<Self, ProtocolError> {
let hash_len = HashLen::USIZE;
let checked_bytes = check_slice_size(input, 3 * hash_len, "ke2_state")?;
Ok(Self {
km3: GenericArray::clone_from_slice(&checked_bytes[..hash_len]),
hashed_transcript: GenericArray::clone_from_slice(
&checked_bytes[hash_len..2 * hash_len],
),
session_key: GenericArray::clone_from_slice(&checked_bytes[2 * hash_len..3 * hash_len]),
})
}
}
impl<HashLen: ArrayLength<u8>> ToBytesWithPointers for Ke2State<HashLen> {
fn to_bytes(&self) -> Vec<u8> {
[
&self.km3[..],
&self.hashed_transcript[..],
&self.session_key[..],
]
.concat()
}
#[cfg(test)]
fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> {
vec![
(self.km3.as_ptr(), HashLen::USIZE),
(self.hashed_transcript.as_ptr(), HashLen::USIZE),
(self.session_key.as_ptr(), HashLen::USIZE),
]
}
}
impl<G: Group, HashLen: ArrayLength<u8>> FromBytes for Ke2Message<G, HashLen> {
fn from_bytes<CS: CipherSuite>(input: &[u8]) -> Result<Self, ProtocolError> {
let key_len = <G as Group>::ElemLen::USIZE;
let nonce_len = NonceLen::USIZE;
let checked_nonce = check_slice_size_atleast(input, nonce_len, "ke2_message nonce")?;
let unchecked_server_e_pk = check_slice_size_atleast(
&checked_nonce[nonce_len..],
key_len,
"ke2_message server_e_pk",
)?;
let checked_mac = check_slice_size(
&unchecked_server_e_pk[key_len..],
HashLen::USIZE,
"ke1_message mac",
)?;
// Check the public key bytes
let server_e_pk = KeyPair::<CS::OprfGroup>::check_public_key(PublicKey::from_bytes(
&unchecked_server_e_pk[..key_len],
)?)?;
Ok(Self {
server_nonce: GenericArray::clone_from_slice(&checked_nonce[..nonce_len]),
server_e_pk: PublicKey::from_bytes(&server_e_pk)?,
mac: GenericArray::clone_from_slice(checked_mac),
})
}
}
impl<G: Group, HashLen: ArrayLength<u8>> ToBytes for Ke2Message<G, HashLen> {
fn to_bytes(&self) -> Vec<u8> {
[&self.to_bytes_without_info_or_mac(), &self.mac[..]].concat()
}
}
impl<G: Group, HashLen: ArrayLength<u8>> Ke2Message<G, HashLen> {
fn to_bytes_without_info_or_mac(&self) -> Vec<u8> {
[&self.server_nonce[..], &self.server_e_pk.to_arr()].concat()
}
}
impl<HashLen: ArrayLength<u8>> FromBytes for Ke3Message<HashLen> {
fn from_bytes<CS: CipherSuite>(bytes: &[u8]) -> Result<Self, ProtocolError> {
let checked_bytes = check_slice_size(bytes, HashLen::USIZE, "ke3_message")?;
Ok(Self {
mac: GenericArray::clone_from_slice(checked_bytes),
})
}
}
impl<HashLen: ArrayLength<u8>> ToBytes for Ke3Message<HashLen> {
fn to_bytes(&self) -> Vec<u8> {
self.mac.to_vec()
}
}
// Zeroize on drop implementations
// This can't be derived because of the use of a generic parameter
impl<G: Group> Zeroize for Ke1State<G> {
fn zeroize(&mut self) {
self.client_e_sk.zeroize();
self.client_nonce.zeroize();
}
}
impl<G: Group> Drop for Ke1State<G> {
fn drop(&mut self) {
self.zeroize();
}
}
// This can't be derived because of the use of a phantom parameter
impl<HashLen: ArrayLength<u8>> Zeroize for Ke2State<HashLen> {
fn zeroize(&mut self) {
self.km3.zeroize();
self.hashed_transcript.zeroize();
self.session_key.zeroize();
}
}
impl<HashLen: ArrayLength<u8>> Drop for Ke2State<HashLen> {
fn drop(&mut self) {
self.zeroize();
}
}
+4 -11
View File
@@ -843,28 +843,21 @@ pub mod errors;
mod impls; mod impls;
#[macro_use] #[macro_use]
mod serialization; mod serialization;
// High-level API
mod opaque;
mod messages;
pub mod ciphersuite; pub mod ciphersuite;
mod envelope; mod envelope;
pub mod hash;
pub mod group; pub mod group;
pub mod hash;
pub mod key_exchange; pub mod key_exchange;
pub mod keypair; pub mod keypair;
mod messages;
mod opaque;
pub mod slow_hash;
#[cfg(feature = "bench")] #[cfg(feature = "bench")]
pub mod oprf; pub mod oprf;
#[cfg(not(feature = "bench"))] #[cfg(not(feature = "bench"))]
mod oprf; mod oprf;
pub mod slow_hash;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
+120 -139
View File
@@ -22,8 +22,10 @@ use digest::Digest;
use generic_array::{typenum::Unsigned, GenericArray}; use generic_array::{typenum::Unsigned, GenericArray};
use rand::{CryptoRng, RngCore}; use rand::{CryptoRng, RngCore};
// Messages ////////////////////////////
// ========= // High-level API Structs //
// ====================== //
////////////////////////////
/// The message sent by the client to the server, to initiate registration /// The message sent by the client to the server, to initiate registration
pub struct RegistrationRequest<CS: CipherSuite> { pub struct RegistrationRequest<CS: CipherSuite> {
@@ -31,24 +33,62 @@ pub struct RegistrationRequest<CS: CipherSuite> {
pub(crate) alpha: CS::OprfGroup, pub(crate) alpha: CS::OprfGroup,
} }
/// The answer sent by the server to the user, upon reception of the
/// registration attempt
pub struct RegistrationResponse<CS: CipherSuite> {
/// The server's oprf output
pub(crate) beta: CS::OprfGroup,
/// Server's static public key
pub(crate) server_s_pk: PublicKey<CS::KeGroup>,
}
/// The final message from the client, containing sealed cryptographic
/// identifiers
pub struct RegistrationUpload<CS: CipherSuite> {
/// The "envelope" generated by the user, containing sealed
/// cryptographic identifiers
pub(crate) envelope: Envelope<CS>,
/// The masking key used to mask the envelope
pub(crate) masking_key: GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
/// The user's public key
pub(crate) client_s_pk: PublicKey<CS::KeGroup>,
}
/// The message sent by the user to the server, to initiate registration
pub struct CredentialRequest<CS: CipherSuite> {
/// blinded password information
pub(crate) alpha: CS::OprfGroup,
pub(crate) ke1_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE1Message,
}
/// The answer sent by the server to the user, upon reception of the
/// login attempt
pub struct CredentialResponse<CS: CipherSuite> {
/// the server's oprf output
pub(crate) beta: CS::OprfGroup,
pub(crate) masking_nonce: Vec<u8>,
pub(crate) masked_response: Vec<u8>,
pub(crate) ke2_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE2Message,
}
/// The answer sent by the client to the server, upon reception of the
/// sealed envelope
pub struct CredentialFinalization<CS: CipherSuite> {
pub(crate) ke3_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE3Message,
}
////////////////////////////////
// High-level Implementations //
// ========================== //
////////////////////////////////
impl<CS: CipherSuite> RegistrationRequest<CS> { impl<CS: CipherSuite> RegistrationRequest<CS> {
/// Only used for testing purposes /// Only used for testing purposes
#[cfg(test)] #[cfg(test)]
pub fn get_alpha_for_testing(&self) -> CS::OprfGroup { pub fn get_alpha_for_testing(&self) -> CS::OprfGroup {
self.alpha self.alpha
} }
}
// Cannot be derived because it would require for CS to be Clone.
impl<CS: CipherSuite> Clone for RegistrationRequest<CS> {
fn clone(&self) -> Self {
Self { alpha: self.alpha }
}
}
impl_debug_eq_hash_for!(struct RegistrationRequest<CS: CipherSuite>, [alpha], [CS::OprfGroup]);
impl<CS: CipherSuite> RegistrationRequest<CS> {
/// Serialization into bytes /// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> { pub fn serialize(&self) -> Vec<u8> {
self.alpha.to_arr().to_vec() self.alpha.to_arr().to_vec()
@@ -71,33 +111,6 @@ impl<CS: CipherSuite> RegistrationRequest<CS> {
} }
} }
impl_serialize_and_deserialize_for!(RegistrationRequest);
/// The answer sent by the server to the user, upon reception of the
/// registration attempt
pub struct RegistrationResponse<CS: CipherSuite> {
/// The server's oprf output
pub(crate) beta: CS::OprfGroup,
/// Server's static public key
pub(crate) server_s_pk: PublicKey<CS::KeGroup>,
}
// Cannot be derived because it would require for CS to be Clone.
impl<CS: CipherSuite> Clone for RegistrationResponse<CS> {
fn clone(&self) -> Self {
Self {
beta: self.beta,
server_s_pk: self.server_s_pk.clone(),
}
}
}
impl_debug_eq_hash_for!(
struct RegistrationResponse<CS: CipherSuite>,
[beta, server_s_pk],
[CS::OprfGroup],
);
impl<CS: CipherSuite> RegistrationResponse<CS> { impl<CS: CipherSuite> RegistrationResponse<CS> {
/// Serialization into bytes /// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> { pub fn serialize(&self) -> Vec<u8> {
@@ -140,29 +153,6 @@ impl<CS: CipherSuite> RegistrationResponse<CS> {
} }
} }
impl_serialize_and_deserialize_for!(RegistrationResponse);
/// The final message from the client, containing sealed cryptographic
/// identifiers
pub struct RegistrationUpload<CS: CipherSuite> {
/// The "envelope" generated by the user, containing sealed
/// cryptographic identifiers
pub(crate) envelope: Envelope<CS>,
/// The masking key used to mask the envelope
pub(crate) masking_key: GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
/// The user's public key
pub(crate) client_s_pk: PublicKey<CS::KeGroup>,
}
impl_clone_for!(
struct RegistrationUpload<CS: CipherSuite>,
[envelope, masking_key, client_s_pk],
);
impl_debug_eq_hash_for!(
struct RegistrationUpload<CS: CipherSuite>,
[envelope, masking_key, client_s_pk],
);
impl<CS: CipherSuite> RegistrationUpload<CS> { impl<CS: CipherSuite> RegistrationUpload<CS> {
/// Serialization into bytes /// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> { pub fn serialize(&self) -> Vec<u8> {
@@ -208,34 +198,6 @@ impl<CS: CipherSuite> RegistrationUpload<CS> {
} }
} }
impl_serialize_and_deserialize_for!(RegistrationUpload);
/// The message sent by the user to the server, to initiate registration
pub struct CredentialRequest<CS: CipherSuite> {
/// blinded password information
pub(crate) alpha: CS::OprfGroup,
pub(crate) ke1_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE1Message,
}
// Cannot be derived because it would require for CS to be Clone.
impl<CS: CipherSuite> Clone for CredentialRequest<CS> {
fn clone(&self) -> Self {
Self {
alpha: self.alpha,
ke1_message: self.ke1_message.clone(),
}
}
}
impl_debug_eq_hash_for!(
struct CredentialRequest<CS: CipherSuite>,
[alpha, ke1_message],
[
CS::OprfGroup,
<CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE1Message
],
);
impl<CS: CipherSuite> CredentialRequest<CS> { impl<CS: CipherSuite> CredentialRequest<CS> {
/// Serialization into bytes /// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> { pub fn serialize(&self) -> Vec<u8> {
@@ -273,39 +235,6 @@ impl<CS: CipherSuite> CredentialRequest<CS> {
} }
} }
impl_serialize_and_deserialize_for!(CredentialRequest);
/// The answer sent by the server to the user, upon reception of the
/// login attempt
pub struct CredentialResponse<CS: CipherSuite> {
/// the server's oprf output
pub(crate) beta: CS::OprfGroup,
pub(crate) masking_nonce: Vec<u8>,
pub(crate) masked_response: Vec<u8>,
pub(crate) ke2_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE2Message,
}
// Cannot be derived because it would require for CS to be Clone.
impl<CS: CipherSuite> Clone for CredentialResponse<CS> {
fn clone(&self) -> Self {
Self {
beta: self.beta,
masking_nonce: self.masking_nonce.clone(),
masked_response: self.masked_response.clone(),
ke2_message: self.ke2_message.clone(),
}
}
}
impl_debug_eq_hash_for!(
struct CredentialResponse<CS: CipherSuite>,
[beta, masking_nonce, masked_response, ke2_message],
[
CS::OprfGroup,
<CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE2Message,
],
);
impl<CS: CipherSuite> CredentialResponse<CS> { impl<CS: CipherSuite> CredentialResponse<CS> {
/// Serialization into bytes /// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> { pub fn serialize(&self) -> Vec<u8> {
@@ -380,21 +309,6 @@ impl<CS: CipherSuite> CredentialResponse<CS> {
} }
} }
impl_serialize_and_deserialize_for!(CredentialResponse);
/// The answer sent by the client to the server, upon reception of the
/// sealed envelope
pub struct CredentialFinalization<CS: CipherSuite> {
pub(crate) ke3_message: <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE3Message,
}
impl_clone_for!(struct CredentialFinalization<CS: CipherSuite>, [ke3_message]);
impl_debug_eq_hash_for!(
struct CredentialFinalization<CS: CipherSuite>,
[ke3_message],
[<CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE3Message],
);
impl<CS: CipherSuite> CredentialFinalization<CS> { impl<CS: CipherSuite> CredentialFinalization<CS> {
/// Serialization into bytes /// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> { pub fn serialize(&self) -> Vec<u8> {
@@ -411,4 +325,71 @@ impl<CS: CipherSuite> CredentialFinalization<CS> {
} }
} }
///////////////////////////
// Trait Implementations //
// ===================== //
///////////////////////////
impl_clone_for!(
struct RegistrationRequest<CS: CipherSuite>,
[alpha],
);
impl_debug_eq_hash_for!(struct RegistrationRequest<CS: CipherSuite>, [alpha], [CS::OprfGroup]);
impl_serialize_and_deserialize_for!(RegistrationRequest);
impl_clone_for!(
struct RegistrationResponse<CS: CipherSuite>,
[beta, server_s_pk],
);
impl_debug_eq_hash_for!(
struct RegistrationResponse<CS: CipherSuite>,
[beta, server_s_pk],
[CS::OprfGroup],
);
impl_serialize_and_deserialize_for!(RegistrationResponse);
impl_clone_for!(
struct RegistrationUpload<CS: CipherSuite>,
[envelope, masking_key, client_s_pk],
);
impl_debug_eq_hash_for!(
struct RegistrationUpload<CS: CipherSuite>,
[envelope, masking_key, client_s_pk],
);
impl_serialize_and_deserialize_for!(RegistrationUpload);
impl_clone_for!(
struct CredentialRequest<CS: CipherSuite>,
[alpha, ke1_message],
);
impl_debug_eq_hash_for!(
struct CredentialRequest<CS: CipherSuite>,
[alpha, ke1_message],
[
CS::OprfGroup,
<CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE1Message
],
);
impl_serialize_and_deserialize_for!(CredentialRequest);
impl_clone_for!(
struct CredentialResponse<CS: CipherSuite>,
[beta, masking_nonce, masked_response, ke2_message],
);
impl_debug_eq_hash_for!(
struct CredentialResponse<CS: CipherSuite>,
[beta, masking_nonce, masked_response, ke2_message],
[
CS::OprfGroup,
<CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE2Message,
],
);
impl_serialize_and_deserialize_for!(CredentialResponse);
impl_clone_for!(struct CredentialFinalization<CS: CipherSuite>, [ke3_message]);
impl_debug_eq_hash_for!(
struct CredentialFinalization<CS: CipherSuite>,
[ke3_message],
[<CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE3Message],
);
impl_serialize_and_deserialize_for!(CredentialFinalization); impl_serialize_and_deserialize_for!(CredentialFinalization);
+431 -413
View File
@@ -28,13 +28,20 @@ use hkdf::Hkdf;
use rand::{CryptoRng, RngCore}; use rand::{CryptoRng, RngCore};
use zeroize::Zeroize; use zeroize::Zeroize;
///////////////
// Constants //
// ========= //
///////////////
const STR_CREDENTIAL_RESPONSE_PAD: &[u8] = b"CredentialResponsePad"; const STR_CREDENTIAL_RESPONSE_PAD: &[u8] = b"CredentialResponsePad";
const STR_MASKING_KEY: &[u8] = b"MaskingKey"; const STR_MASKING_KEY: &[u8] = b"MaskingKey";
const STR_OPRF_KEY: &[u8] = b"OprfKey"; const STR_OPRF_KEY: &[u8] = b"OprfKey";
const STR_OPAQUE_DERIVE_KEY_PAIR: &[u8] = b"OPAQUE-DeriveKeyPair"; const STR_OPAQUE_DERIVE_KEY_PAIR: &[u8] = b"OPAQUE-DeriveKeyPair";
// Server Setup ////////////////////////////
// ============ // High-level API Structs //
// ====================== //
////////////////////////////
/// The state elements the server holds upon setup /// The state elements the server holds upon setup
#[cfg_attr( #[cfg_attr(
@@ -54,6 +61,86 @@ pub struct ServerSetup<
pub(crate) fake_keypair: KeyPair<CS::KeGroup>, pub(crate) fake_keypair: KeyPair<CS::KeGroup>,
} }
// Cannot be derived because it would require for CS to be bound.
impl_clone_for!(
struct ServerSetup<CS: CipherSuite>,
[oprf_seed, keypair, fake_keypair],
);
impl_debug_eq_hash_for!(
struct ServerSetup<CS: CipherSuite>,
[oprf_seed, oprf_seed, fake_keypair],
);
/// The state elements the client holds to register itself
pub struct ClientRegistration<CS: CipherSuite> {
alpha: CS::OprfGroup,
/// token containing the client's password and the blinding factor
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::OprfGroup>],
);
impl_serialize_and_deserialize_for!(ClientRegistration);
/// The state elements the server holds to record a registration
pub struct ServerRegistration<CS: CipherSuite>(RegistrationUpload<CS>);
impl_clone_for!(tuple ServerRegistration<CS: CipherSuite>, [0]);
impl_debug_eq_hash_for!(
tuple ServerRegistration<CS: CipherSuite>,
[0],
);
impl_serialize_and_deserialize_for!(ServerRegistration);
/// The state elements the client holds to perform a login
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(
feature = "serialize",
serde(bound(
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::OprfGroup>,
ke1_state: <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE1State,
serialized_credential_request: Vec<u8>,
}
impl_clone_for!(struct ClientLogin<CS: CipherSuite>, [token, ke1_state, serialized_credential_request]);
impl_debug_eq_hash_for!(
struct ClientLogin<CS: CipherSuite>,
[token, ke1_state, serialized_credential_request],
[oprf::Token<CS::OprfGroup>, <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE1State],
);
/// The state elements the server holds to record a login
pub struct ServerLogin<CS: CipherSuite> {
ke2_state: <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE2State,
_cs: PhantomData<CS>,
}
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::KeGroup>>::KE2State],
);
impl_serialize_and_deserialize_for!(ServerLogin);
////////////////////////////////
// High-level Implementations //
// ========================== //
////////////////////////////////
// Server Setup
// ============
impl<CS: CipherSuite> ServerSetup<CS, PrivateKey<CS::KeGroup>> { impl<CS: CipherSuite> ServerSetup<CS, PrivateKey<CS::KeGroup>> {
/// Generate a new instance of server setup /// Generate a new instance of server setup
pub fn new<R: CryptoRng + RngCore>(rng: &mut R) -> Self { pub fn new<R: CryptoRng + RngCore>(rng: &mut R) -> Self {
@@ -108,33 +195,9 @@ impl<CS: CipherSuite, S: SecretKey<CS::KeGroup>> ServerSetup<CS, S> {
} }
} }
// Cannot be derived because it would require for CS to be bound.
impl_clone_for!(
struct ServerSetup<CS: CipherSuite>,
[oprf_seed, keypair, fake_keypair],
);
impl_debug_eq_hash_for!(
struct ServerSetup<CS: CipherSuite>,
[oprf_seed, oprf_seed, fake_keypair],
);
// Registration // Registration
// ============ // ============
/// The state elements the client holds to register itself
pub struct ClientRegistration<CS: CipherSuite> {
alpha: CS::OprfGroup,
/// token containing the client's password and the blinding factor
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::OprfGroup>],
);
impl<CS: CipherSuite> ClientRegistration<CS> { impl<CS: CipherSuite> ClientRegistration<CS> {
/// Serialization into bytes /// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> { pub fn serialize(&self) -> Vec<u8> {
@@ -188,85 +251,7 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
/* cannot provide raw pointer to self.token.blind until this is exposed in curve25519_dalek::scalar::Scalar */ /* cannot provide raw pointer to self.token.blind until this is exposed in curve25519_dalek::scalar::Scalar */
] ]
} }
}
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],
) -> Result<(Vec<u8>, Vec<u8>), ProtocolError> {
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()),
};
Ok((
serialize(&client_identity, 2)?,
serialize(&server_identity, 2)?,
))
}
/// Optional parameters for client registration finish
#[derive(Clone)]
pub struct ClientRegistrationFinishParameters<'h, CS: CipherSuite> {
/// Specifying the identifiers idU and idS
pub identifiers: Option<Identifiers>,
/// Specifying a configuration for the slow hash
pub slow_hash: Option<&'h CS::SlowHash>,
}
impl<'h, CS: CipherSuite> Default for ClientRegistrationFinishParameters<'h, CS> {
fn default() -> Self {
Self {
identifiers: None,
slow_hash: None,
}
}
}
impl<'h, CS: CipherSuite> ClientRegistrationFinishParameters<'h, CS> {
/// Create a new [`ClientRegistrationFinishParameters`]
pub fn new(identifiers: Option<Identifiers>, slow_hash: Option<&'h CS::SlowHash>) -> Self {
Self {
identifiers,
slow_hash,
}
}
}
/// Contains the fields that are returned by a client registration start
pub struct ClientRegistrationStartResult<CS: CipherSuite> {
/// The registration request message to be sent to the server
pub message: RegistrationRequest<CS>,
/// The client state that must be persisted in order to complete registration
pub state: ClientRegistration<CS>,
}
// Cannot be derived because it would require for CS to be Clone.
impl<CS: CipherSuite> Clone for ClientRegistrationStartResult<CS> {
fn clone(&self) -> Self {
Self {
message: self.message.clone(),
state: self.state.clone(),
}
}
}
impl<CS: CipherSuite> ClientRegistration<CS> {
/// Returns an initial "blinded" request to send to the server, as well as a ClientRegistration /// Returns an initial "blinded" request to send to the server, as well as a ClientRegistration
pub fn start<R: RngCore + CryptoRng>( pub fn start<R: RngCore + CryptoRng>(
blinding_factor_rng: &mut R, blinding_factor_rng: &mut R,
@@ -280,45 +265,7 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
state: Self { alpha, token }, state: Self { alpha, token },
}) })
} }
}
/// Contains the fields that are returned by a client registration finish
pub struct ClientRegistrationFinishResult<CS: CipherSuite> {
/// The registration upload message to be sent to the server
pub message: RegistrationUpload<CS>,
/// 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::KeGroup>,
/// Instance of the ClientRegistration, only used in tests for checking zeroize
#[cfg(test)]
pub state: ClientRegistration<CS>,
/// AuthKey, only used in tests
#[cfg(test)]
pub auth_key: Vec<u8>,
/// Password derived key, only used in tests
#[cfg(test)]
pub randomized_pwd: GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
}
// Cannot be derived because it would require for CS to be Clone.
impl<CS: CipherSuite> Clone for ClientRegistrationFinishResult<CS> {
fn clone(&self) -> Self {
Self {
message: self.message.clone(),
export_key: self.export_key.clone(),
server_s_pk: self.server_s_pk.clone(),
#[cfg(test)]
state: self.state.clone(),
#[cfg(test)]
auth_key: self.auth_key.clone(),
#[cfg(test)]
randomized_pwd: self.randomized_pwd.clone(),
}
}
}
impl<CS: CipherSuite> ClientRegistration<CS> {
/// "Unblinds" the server's answer and returns a final message containing /// "Unblinds" the server's answer and returns a final message containing
/// cryptographic identifiers, to be sent to the server on setup finalization /// cryptographic identifiers, to be sent to the server on setup finalization
pub fn finish<R: CryptoRng + RngCore>( pub fn finish<R: CryptoRng + RngCore>(
@@ -366,36 +313,6 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
} }
} }
/// 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>,
/// OPRF key, only used in tests
#[cfg(test)]
pub oprf_key: GenericArray<u8, <CS::OprfGroup as Group>::ScalarLen>,
}
// Cannot be derived because it would require for CS to be Clone.
impl<CS: CipherSuite> Clone for ServerRegistrationStartResult<CS> {
fn clone(&self) -> Self {
Self {
message: self.message.clone(),
#[cfg(test)]
oprf_key: self.oprf_key.clone(),
}
}
}
/// The state elements the server holds to record a registration
pub struct ServerRegistration<CS: CipherSuite>(RegistrationUpload<CS>);
impl_clone_for!(tuple ServerRegistration<CS: CipherSuite>, [0]);
impl_debug_eq_hash_for!(
tuple ServerRegistration<CS: CipherSuite>,
[0],
);
impl<CS: CipherSuite> ServerRegistration<CS> { impl<CS: CipherSuite> ServerRegistration<CS> {
/// Serialization into bytes /// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> { pub fn serialize(&self) -> Vec<u8> {
@@ -456,34 +373,9 @@ impl<CS: CipherSuite> ServerRegistration<CS> {
} }
} }
impl_serialize_and_deserialize_for!(ServerRegistration);
// Login // Login
// ===== // =====
/// The state elements the client holds to perform a login
#[cfg_attr(feature = "serialize", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(
feature = "serialize",
serde(bound(
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::OprfGroup>,
ke1_state: <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE1State,
serialized_credential_request: Vec<u8>,
}
impl_clone_for!(struct ClientLogin<CS: CipherSuite>, [token, ke1_state, serialized_credential_request]);
impl_debug_eq_hash_for!(
struct ClientLogin<CS: CipherSuite>,
[token, ke1_state, serialized_credential_request],
[oprf::Token<CS::OprfGroup>, <CS::KeyExchange as KeyExchange<CS::Hash, CS::KeGroup>>::KE1State],
);
impl<CS: CipherSuite> ClientLogin<CS> { impl<CS: CipherSuite> ClientLogin<CS> {
/// Serialization into bytes /// Serialization into bytes
pub fn serialize(&self) -> Result<Vec<u8>, ProtocolError> { pub fn serialize(&self) -> Result<Vec<u8>, ProtocolError> {
@@ -543,99 +435,6 @@ impl<CS: CipherSuite> ClientLogin<CS> {
} }
} }
/// 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
pub message: CredentialRequest<CS>,
/// The state that the client must keep in order to complete the protocol
pub state: ClientLogin<CS>,
}
// Cannot be derived because it would require for CS to be Clone.
impl<CS: CipherSuite> Clone for ClientLoginStartResult<CS> {
fn clone(&self) -> Self {
Self {
message: self.message.clone(),
state: self.state.clone(),
}
}
}
/// Optional parameters for client login finish
#[derive(Clone)]
pub struct ClientLoginFinishParameters<'h, CS: CipherSuite> {
/// Specifying a context field that the server must agree on
pub context: Option<Vec<u8>>,
/// Specifying a user identifier and server identifier that will be matched against the server
pub identifiers: Option<Identifiers>,
/// Specifying a configuration for the slow hash
pub slow_hash: Option<&'h CS::SlowHash>,
}
impl<'h, CS: CipherSuite> Default for ClientLoginFinishParameters<'h, CS> {
fn default() -> Self {
Self {
context: None,
identifiers: None,
slow_hash: None,
}
}
}
impl<'h, CS: CipherSuite> ClientLoginFinishParameters<'h, CS> {
/// Create a new [`ClientLoginFinishParameters`]
pub fn new(
context: Option<Vec<u8>>,
identifiers: Option<Identifiers>,
slow_hash: Option<&'h CS::SlowHash>,
) -> Self {
Self {
context,
identifiers,
slow_hash,
}
}
}
/// Contains the fields that are returned by a client login finish
pub struct ClientLoginFinishResult<CS: CipherSuite> {
/// The message to send to the server to complete the protocol
pub message: CredentialFinalization<CS>,
/// The session key
pub session_key: Vec<u8>,
/// 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::KeGroup>,
/// Instance of the ClientLogin, only used in tests for checking zeroize
#[cfg(test)]
pub state: ClientLogin<CS>,
/// Handshake secret, only used in tests
#[cfg(test)]
pub handshake_secret: Vec<u8>,
/// Client MAC key, only used in tests
#[cfg(test)]
pub client_mac_key: GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
}
// Cannot be derived because it would require for CS to be Clone.
impl<CS: CipherSuite> Clone for ClientLoginFinishResult<CS> {
fn clone(&self) -> Self {
Self {
message: self.message.clone(),
session_key: self.session_key.clone(),
export_key: self.export_key.clone(),
server_s_pk: self.server_s_pk.clone(),
#[cfg(test)]
state: self.state.clone(),
#[cfg(test)]
handshake_secret: self.handshake_secret.clone(),
#[cfg(test)]
client_mac_key: self.client_mac_key.clone(),
}
}
}
impl<CS: CipherSuite> ClientLogin<CS> { impl<CS: CipherSuite> ClientLogin<CS> {
/// Returns an initial "blinded" password request to send to the server, as well as a ClientLogin /// Returns an initial "blinded" password request to send to the server, as well as a ClientLogin
pub fn start<R: RngCore + CryptoRng>( pub fn start<R: RngCore + CryptoRng>(
@@ -743,93 +542,6 @@ 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::KeGroup>>::KE2State,
_cs: PhantomData<CS>,
}
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::KeGroup>>::KE2State],
);
/// Optional parameters for server login start
#[derive(Clone)]
pub enum ServerLoginStartParameters {
/// 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(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)
WithContextAndIdentifiers(Vec<u8>, Identifiers),
}
impl Default for ServerLoginStartParameters {
fn default() -> Self {
Self::WithContext(Vec::new())
}
}
/// Contains the fields that are returned by a server login start
pub struct ServerLoginStartResult<CS: CipherSuite> {
/// The message to send back to the client
pub message: CredentialResponse<CS>,
/// The state that the server must keep in order to finish the protocl
pub state: ServerLogin<CS>,
/// Handshake secret, only used in tests
#[cfg(test)]
pub handshake_secret: Vec<u8>,
/// Server MAC key, only used in tests
#[cfg(test)]
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::OprfGroup as Group>::ScalarLen>,
}
// Cannot be derived because it would require for CS to be Clone.
impl<CS: CipherSuite> Clone for ServerLoginStartResult<CS> {
fn clone(&self) -> Self {
Self {
message: self.message.clone(),
state: self.state.clone(),
#[cfg(test)]
handshake_secret: self.handshake_secret.clone(),
#[cfg(test)]
server_mac_key: self.server_mac_key.clone(),
#[cfg(test)]
oprf_key: self.oprf_key.clone(),
}
}
}
/// Contains the fields that are returned by a server login finish
pub struct ServerLoginFinishResult<CS: CipherSuite> {
/// The session key between client and server
pub session_key: Vec<u8>,
_cs: PhantomData<CS>,
/// Instance of the ClientRegistration, only used in tests for checking zeroize
#[cfg(test)]
pub state: ServerLogin<CS>,
}
// Cannot be derived because it would require for CS to be Clone.
impl<CS: CipherSuite> Clone for ServerLoginFinishResult<CS> {
fn clone(&self) -> Self {
Self {
session_key: self.session_key.clone(),
_cs: PhantomData,
#[cfg(test)]
state: self.state.clone(),
}
}
}
impl<CS: CipherSuite> ServerLogin<CS> { impl<CS: CipherSuite> ServerLogin<CS> {
/// Serialization into bytes /// Serialization into bytes
pub fn serialize(&self) -> Vec<u8> { pub fn serialize(&self) -> Vec<u8> {
@@ -964,68 +676,297 @@ impl<CS: CipherSuite> ServerLogin<CS> {
} }
} }
impl_serialize_and_deserialize_for!(ServerLogin); /////////////////////////
// Convenience Structs //
//==================== //
/////////////////////////
// Zeroize on drop implementations /// 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>),
}
// This can't be derived because of the use of a phantom parameter /// Optional parameters for client registration finish
impl<CS: CipherSuite> Zeroize for ClientRegistration<CS> { #[derive(Clone)]
fn zeroize(&mut self) { pub struct ClientRegistrationFinishParameters<'h, CS: CipherSuite> {
self.token.data.zeroize(); /// Specifying the identifiers idU and idS
self.token.blind.zeroize(); pub identifiers: Option<Identifiers>,
/// Specifying a configuration for the slow hash
pub slow_hash: Option<&'h CS::SlowHash>,
}
impl<'h, CS: CipherSuite> Default for ClientRegistrationFinishParameters<'h, CS> {
fn default() -> Self {
Self {
identifiers: None,
slow_hash: None,
}
} }
} }
impl<CS: CipherSuite> Drop for ClientRegistration<CS> { impl<'h, CS: CipherSuite> ClientRegistrationFinishParameters<'h, CS> {
fn drop(&mut self) { /// Create a new [`ClientRegistrationFinishParameters`]
self.zeroize(); pub fn new(identifiers: Option<Identifiers>, slow_hash: Option<&'h CS::SlowHash>) -> Self {
Self {
identifiers,
slow_hash,
}
} }
} }
// This can't be derived because of the use of a phantom parameter /// Contains the fields that are returned by a client registration start
impl<CS: CipherSuite> Zeroize for ServerRegistration<CS> { pub struct ClientRegistrationStartResult<CS: CipherSuite> {
fn zeroize(&mut self) { /// The registration request message to be sent to the server
self.0.envelope.zeroize(); pub message: RegistrationRequest<CS>,
self.0.masking_key.zeroize(); /// The client state that must be persisted in order to complete registration
self.0.client_s_pk.zeroize(); pub state: ClientRegistration<CS>,
}
// Cannot be derived because it would require for CS to be Clone.
impl<CS: CipherSuite> Clone for ClientRegistrationStartResult<CS> {
fn clone(&self) -> Self {
Self {
message: self.message.clone(),
state: self.state.clone(),
}
} }
} }
impl<CS: CipherSuite> Drop for ServerRegistration<CS> { /// Contains the fields that are returned by a client registration finish
fn drop(&mut self) { pub struct ClientRegistrationFinishResult<CS: CipherSuite> {
self.zeroize(); /// The registration upload message to be sent to the server
pub message: RegistrationUpload<CS>,
/// 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::KeGroup>,
/// Instance of the ClientRegistration, only used in tests for checking zeroize
#[cfg(test)]
pub state: ClientRegistration<CS>,
/// AuthKey, only used in tests
#[cfg(test)]
pub auth_key: Vec<u8>,
/// Password derived key, only used in tests
#[cfg(test)]
pub randomized_pwd: GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
}
// Cannot be derived because it would require for CS to be Clone.
impl<CS: CipherSuite> Clone for ClientRegistrationFinishResult<CS> {
fn clone(&self) -> Self {
Self {
message: self.message.clone(),
export_key: self.export_key.clone(),
server_s_pk: self.server_s_pk.clone(),
#[cfg(test)]
state: self.state.clone(),
#[cfg(test)]
auth_key: self.auth_key.clone(),
#[cfg(test)]
randomized_pwd: self.randomized_pwd.clone(),
}
} }
} }
// This can't be derived because of the use of a phantom parameter /// Contains the fields that are returned by a server registration start.
impl<CS: CipherSuite> Zeroize for ClientLogin<CS> { /// Note that there is no state output in this step
fn zeroize(&mut self) { pub struct ServerRegistrationStartResult<CS: CipherSuite> {
self.token.data.zeroize(); /// The registration resposne message to send to the client
self.token.blind.zeroize(); pub message: RegistrationResponse<CS>,
self.ke1_state.zeroize(); /// OPRF key, only used in tests
self.serialized_credential_request.zeroize(); #[cfg(test)]
pub oprf_key: GenericArray<u8, <CS::OprfGroup as Group>::ScalarLen>,
}
// Cannot be derived because it would require for CS to be Clone.
impl<CS: CipherSuite> Clone for ServerRegistrationStartResult<CS> {
fn clone(&self) -> Self {
Self {
message: self.message.clone(),
#[cfg(test)]
oprf_key: self.oprf_key.clone(),
}
} }
} }
impl<CS: CipherSuite> Drop for ClientLogin<CS> { /// Contains the fields that are returned by a client login start
fn drop(&mut self) { pub struct ClientLoginStartResult<CS: CipherSuite> {
self.zeroize(); /// The message to send to the server to begin the login protocol
pub message: CredentialRequest<CS>,
/// The state that the client must keep in order to complete the protocol
pub state: ClientLogin<CS>,
}
// Cannot be derived because it would require for CS to be Clone.
impl<CS: CipherSuite> Clone for ClientLoginStartResult<CS> {
fn clone(&self) -> Self {
Self {
message: self.message.clone(),
state: self.state.clone(),
}
} }
} }
// This can't be derived because of the use of a phantom parameter /// Optional parameters for client login finish
impl<CS: CipherSuite> Zeroize for ServerLogin<CS> { #[derive(Clone)]
fn zeroize(&mut self) { pub struct ClientLoginFinishParameters<'h, CS: CipherSuite> {
self.ke2_state.zeroize(); /// Specifying a context field that the server must agree on
pub context: Option<Vec<u8>>,
/// Specifying a user identifier and server identifier that will be matched against the server
pub identifiers: Option<Identifiers>,
/// Specifying a configuration for the slow hash
pub slow_hash: Option<&'h CS::SlowHash>,
}
impl<'h, CS: CipherSuite> Default for ClientLoginFinishParameters<'h, CS> {
fn default() -> Self {
Self {
context: None,
identifiers: None,
slow_hash: None,
}
} }
} }
impl<CS: CipherSuite> Drop for ServerLogin<CS> { impl<'h, CS: CipherSuite> ClientLoginFinishParameters<'h, CS> {
fn drop(&mut self) { /// Create a new [`ClientLoginFinishParameters`]
self.zeroize(); pub fn new(
context: Option<Vec<u8>>,
identifiers: Option<Identifiers>,
slow_hash: Option<&'h CS::SlowHash>,
) -> Self {
Self {
context,
identifiers,
slow_hash,
}
} }
} }
/// Contains the fields that are returned by a client login finish
pub struct ClientLoginFinishResult<CS: CipherSuite> {
/// The message to send to the server to complete the protocol
pub message: CredentialFinalization<CS>,
/// The session key
pub session_key: Vec<u8>,
/// 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::KeGroup>,
/// Instance of the ClientLogin, only used in tests for checking zeroize
#[cfg(test)]
pub state: ClientLogin<CS>,
/// Handshake secret, only used in tests
#[cfg(test)]
pub handshake_secret: Vec<u8>,
/// Client MAC key, only used in tests
#[cfg(test)]
pub client_mac_key: GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
}
// Cannot be derived because it would require for CS to be Clone.
impl<CS: CipherSuite> Clone for ClientLoginFinishResult<CS> {
fn clone(&self) -> Self {
Self {
message: self.message.clone(),
session_key: self.session_key.clone(),
export_key: self.export_key.clone(),
server_s_pk: self.server_s_pk.clone(),
#[cfg(test)]
state: self.state.clone(),
#[cfg(test)]
handshake_secret: self.handshake_secret.clone(),
#[cfg(test)]
client_mac_key: self.client_mac_key.clone(),
}
}
}
/// Contains the fields that are returned by a server login finish
pub struct ServerLoginFinishResult<CS: CipherSuite> {
/// The session key between client and server
pub session_key: Vec<u8>,
_cs: PhantomData<CS>,
/// Instance of the ClientRegistration, only used in tests for checking zeroize
#[cfg(test)]
pub state: ServerLogin<CS>,
}
// Cannot be derived because it would require for CS to be Clone.
impl<CS: CipherSuite> Clone for ServerLoginFinishResult<CS> {
fn clone(&self) -> Self {
Self {
session_key: self.session_key.clone(),
_cs: PhantomData,
#[cfg(test)]
state: self.state.clone(),
}
}
}
/// Optional parameters for server login start
#[derive(Clone)]
pub enum ServerLoginStartParameters {
/// 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(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)
WithContextAndIdentifiers(Vec<u8>, Identifiers),
}
impl Default for ServerLoginStartParameters {
fn default() -> Self {
Self::WithContext(Vec::new())
}
}
/// Contains the fields that are returned by a server login start
pub struct ServerLoginStartResult<CS: CipherSuite> {
/// The message to send back to the client
pub message: CredentialResponse<CS>,
/// The state that the server must keep in order to finish the protocl
pub state: ServerLogin<CS>,
/// Handshake secret, only used in tests
#[cfg(test)]
pub handshake_secret: Vec<u8>,
/// Server MAC key, only used in tests
#[cfg(test)]
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::OprfGroup as Group>::ScalarLen>,
}
// Cannot be derived because it would require for CS to be Clone.
impl<CS: CipherSuite> Clone for ServerLoginStartResult<CS> {
fn clone(&self) -> Self {
Self {
message: self.message.clone(),
state: self.state.clone(),
#[cfg(test)]
handshake_secret: self.handshake_secret.clone(),
#[cfg(test)]
server_mac_key: self.server_mac_key.clone(),
#[cfg(test)]
oprf_key: self.oprf_key.clone(),
}
}
}
////////////////////////////////////////////////
// Helper functions and Trait Implementations //
// ========================================== //
////////////////////////////////////////////////
// Helper functions // Helper functions
fn get_password_derived_key<CS: CipherSuite>( fn get_password_derived_key<CS: CipherSuite>(
@@ -1107,3 +1048,80 @@ fn unmask_response<CS: CipherSuite>(
Ok((server_s_pk, envelope)) Ok((server_s_pk, envelope))
} }
pub(crate) fn bytestrings_from_identifiers(
ids: &Option<Identifiers>,
client_s_pk: &[u8],
server_s_pk: &[u8],
) -> Result<(Vec<u8>, Vec<u8>), ProtocolError> {
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()),
};
Ok((
serialize(&client_identity, 2)?,
serialize(&server_identity, 2)?,
))
}
// Zeroize on drop implementations
// This can't be derived because of the use of a phantom parameter
impl<CS: CipherSuite> Zeroize for ClientRegistration<CS> {
fn zeroize(&mut self) {
self.token.data.zeroize();
self.token.blind.zeroize();
}
}
impl<CS: CipherSuite> Drop for ClientRegistration<CS> {
fn drop(&mut self) {
self.zeroize();
}
}
// 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.0.envelope.zeroize();
self.0.masking_key.zeroize();
self.0.client_s_pk.zeroize();
}
}
impl<CS: CipherSuite> Drop for ServerRegistration<CS> {
fn drop(&mut self) {
self.zeroize();
}
}
// This can't be derived because of the use of a phantom parameter
impl<CS: CipherSuite> Zeroize for ClientLogin<CS> {
fn zeroize(&mut self) {
self.token.data.zeroize();
self.token.blind.zeroize();
self.ke1_state.zeroize();
self.serialized_credential_request.zeroize();
}
}
impl<CS: CipherSuite> Drop for ClientLogin<CS> {
fn drop(&mut self) {
self.zeroize();
}
}
// This can't be derived because of the use of a phantom parameter
impl<CS: CipherSuite> Zeroize for ServerLogin<CS> {
fn zeroize(&mut self) {
self.ke2_state.zeroize();
}
}
impl<CS: CipherSuite> Drop for ServerLogin<CS> {
fn drop(&mut self) {
self.zeroize();
}
}
+1 -1
View File
@@ -76,7 +76,7 @@ fn finalize_after_unblind<G: Group, H: Hash>(
} }
//////////////////////// ////////////////////////
// Benchmarking shims // // Benchmarking Shims //
//////////////////////// ////////////////////////
#[cfg(feature = "bench")] #[cfg(feature = "bench")]
-67
View File
@@ -64,73 +64,6 @@ pub(crate) fn tokenize(
)) ))
} }
/// Inner macro used for deriving `serde`'s `Serialize` and `Deserialize` traits.
macro_rules! impl_serialize_and_deserialize_for {
($t:ident) => {
#[cfg(feature = "serialize")]
impl<CS: CipherSuite> serde::Serialize for $t<CS> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
if serializer.is_human_readable() {
serializer.serialize_str(&base64::encode(&self.serialize()))
} else {
serializer.serialize_bytes(&self.serialize())
}
}
}
#[cfg(feature = "serialize")]
impl<'de, CS: CipherSuite> serde::Deserialize<'de> for $t<CS> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
if deserializer.is_human_readable() {
let s = <&str>::deserialize(deserializer)?;
$t::<CS>::deserialize(&base64::decode(s).map_err(serde::de::Error::custom)?)
.map_err(serde::de::Error::custom)
} else {
struct ByteVisitor<CS: CipherSuite> {
marker: core::marker::PhantomData<CS>,
}
impl<'de, CS: CipherSuite> serde::de::Visitor<'de> for ByteVisitor<CS> {
type Value = $t<CS>;
fn expecting(
&self,
formatter: &mut core::fmt::Formatter,
) -> core::fmt::Result {
formatter.write_str(core::concat!(
"the byte representation of a ",
core::stringify!($t)
))
}
fn visit_bytes<E>(self, value: &[u8]) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
$t::<CS>::deserialize(value).map_err(|_| {
serde::de::Error::invalid_value(
serde::de::Unexpected::Bytes(value),
&core::concat!(
"invalid byte sequence for ",
core::stringify!($t)
),
)
})
}
}
deserializer.deserialize_bytes(ByteVisitor::<CS> {
marker: core::marker::PhantomData,
})
}
}
}
};
}
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
+1 -1
View File
@@ -257,7 +257,7 @@ fn credential_response_roundtrip() {
} }
#[test] #[test]
fn login_third_message_roundtrip() { fn credential_finalization_roundtrip() {
let mut rng = OsRng; let mut rng = OsRng;
let mut mac = [0u8; MAC_SIZE]; let mut mac = [0u8; MAC_SIZE];
rng.fill_bytes(&mut mac); rng.fill_bytes(&mut mac);