Initial commit
Rust CI / test + Clippy + rustfmt (push) Has been cancelled

This commit is contained in:
Kevin Lewi
2020-06-05 09:35:14 -07:00
commit 88696763cc
22 changed files with 4253 additions and 0 deletions
+123
View File
@@ -0,0 +1,123 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
//! A list of error types which are produced during an execution of the protocol
use thiserror::Error;
/// Represents an error in the manipulation of internal cryptographic data
#[derive(Debug, Error)]
pub enum InternalPakeError {
#[error("Invalid length for {name}: expected {len}, but is actually {actual_len}.")]
SizeError {
name: &'static str,
len: usize,
actual_len: usize,
},
#[error("Could not decompress point.")]
PointError,
#[error("Key belongs to a small subgroup!")]
SubGroupError,
#[error("hashing to a key failed")]
HashingFailure,
#[error("Computing HKDF failed while deriving subkeys")]
HkdfError,
#[error("Computing HMAC failed while supplying a secret key")]
HmacError,
}
/// Represents an error in password checking
#[derive(Debug, Error)]
pub enum PakeError {
/// This error results from an internal error during PRF construction
///
#[error("Internal error during PRF verification: {0}")]
CryptoError(InternalPakeError),
/// This error occurs when the symmetric encryption fails
#[error("Symmetric encryption failed.")]
EncryptionError,
/// This error occurs when the symmetric decryption fails
#[error("Symmetric decryption failed.")]
DecryptionError,
/// This error occurs when the symmetric decryption's hmac check fails
#[error("HMAC check in symmetric decryption failed.")]
DecryptionHmacError,
/// This error occurs when the server object that is being called finish() on is malformed
#[error("Incomplete set of keys passed into finish() function")]
IncompleteKeysError,
#[error("The provided server public key doesn't match the encrypted one")]
IncompatibleServerStaticPublicKeyError,
#[error("Error in key exchange protocol when attempting to validate MACs")]
KeyExchangeMacValidationError,
#[error("Error in validating credentials")]
InvalidLoginError,
}
// This is meant to express future(ly) non-trivial ways of converting the
// internal error into a PakeError
impl From<InternalPakeError> for PakeError {
fn from(e: InternalPakeError) -> PakeError {
PakeError::CryptoError(e)
}
}
/// Represents an error in protocol handling
#[derive(Debug, Error)]
pub enum ProtocolError {
/// This error results from an error during password verification
///
#[error("Internal error during password verification: {0}")]
VerificationError(PakeError),
/// This error occurs when the server answer cannot be handled
#[error("Server response cannot be handled.")]
ServerError,
/// This error occurs when the client request cannot be handled
#[error("Client request cannot be handled.")]
ClientError,
}
// This is meant to express future(ly) non-trivial ways of converting the
// Pake error into a ProtocolError
impl From<PakeError> for ProtocolError {
fn from(e: PakeError) -> ProtocolError {
ProtocolError::VerificationError(e)
}
}
// This is meant to express future(ly) non-trivial ways of converting the
// internal error into a ProtocolError
impl From<InternalPakeError> for ProtocolError {
fn from(e: InternalPakeError) -> ProtocolError {
ProtocolError::VerificationError(e.into())
}
}
// See https://github.com/rust-lang/rust/issues/64715 and remove this when
// merged, and https://github.com/dtolnay/thiserror/issues/62 for why this
// comes up in our doc tests.
impl From<::std::convert::Infallible> for ProtocolError {
fn from(_: ::std::convert::Infallible) -> Self {
unreachable!()
}
}
pub(crate) mod utils {
use super::*;
pub fn check_slice_size<'a>(
slice: &'a [u8],
expected_len: usize,
arg_name: &'static str,
) -> Result<&'a [u8], InternalPakeError> {
if slice.len() != expected_len {
return Err(InternalPakeError::SizeError {
name: arg_name,
len: expected_len,
actual_len: slice.len(),
});
}
Ok(slice)
}
}
+160
View File
@@ -0,0 +1,160 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
use crate::errors::InternalPakeError;
use curve25519_dalek::{
edwards::{CompressedEdwardsY, EdwardsPoint},
ristretto::{CompressedRistretto, RistrettoPoint},
scalar::Scalar,
};
use generic_array::{
typenum::{U32, U64},
ArrayLength, GenericArray,
};
use rand_core::{CryptoRng, RngCore};
use sha2::{Digest, Sha256};
use std::ops::Mul;
use zeroize::Zeroize;
/// A prime-order subgroup of a base field (EC, prime-order field ...). This
/// subgroup is noted additively — as in the draft RFC — in this trait.
pub trait Group: Sized + for<'a> Mul<&'a <Self as Group>::Scalar, Output = Self> {
/// The type of base field scalars
type Scalar: Zeroize;
/// The byte length necessary to represent scalars
type ScalarLen: ArrayLength<u8>;
/// Return a scalat from its fixed-length bytes representation
fn from_scalar_slice(
scalar_bits: &GenericArray<u8, Self::ScalarLen>,
) -> Result<Self::Scalar, InternalPakeError>;
/// picks a scalar at random
fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar;
/// Serializes a scalar to bytes
fn scalar_as_bytes(scalar: &Self::Scalar) -> &GenericArray<u8, Self::ScalarLen>;
/// The multiplicative inverse of this scalar
fn scalar_invert(scalar: &Self::Scalar) -> Self::Scalar;
/// The byte length necessary to represent group elements
type ElemLen: ArrayLength<u8>;
/// Return an element from its fixed-length bytes representation
fn from_element_slice(
element_bits: &GenericArray<u8, Self::ElemLen>,
) -> Result<Self, InternalPakeError>;
/// Serializes the `self` group element
fn to_bytes(&self) -> GenericArray<u8, Self::ElemLen>;
/// Hashes points presumed to be uniformly random to the curve. The
/// impl is allowed to perform additional hashes if it needs to, but this
/// may not be necessary as this function is going to be called with the
/// output of a kdf.
type UniformBytesLen: ArrayLength<u8>;
fn hash_to_curve(uniform_bytes: &GenericArray<u8, Self::UniformBytesLen>) -> Self;
}
/// The implementation of such a subgroup for Ristretto
impl Group for RistrettoPoint {
type Scalar = Scalar;
type ScalarLen = U32;
fn from_scalar_slice(
scalar_bits: &GenericArray<u8, Self::ScalarLen>,
) -> Result<Self::Scalar, InternalPakeError> {
let mut bits = [0u8; 32];
bits.copy_from_slice(scalar_bits);
Ok(Scalar::from_bytes_mod_order(bits))
}
fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
Scalar::random(rng)
}
fn scalar_as_bytes(scalar: &Self::Scalar) -> &GenericArray<u8, Self::ScalarLen> {
GenericArray::from_slice(scalar.as_bytes())
}
fn scalar_invert(scalar: &Self::Scalar) -> Self::Scalar {
scalar.invert()
}
// The byte length necessary to represent group elements
type ElemLen = U32;
fn from_element_slice(
element_bits: &GenericArray<u8, Self::ElemLen>,
) -> Result<Self, InternalPakeError> {
CompressedRistretto::from_slice(element_bits)
.decompress()
.ok_or_else(|| InternalPakeError::PointError)
}
// serialization of a group element
fn to_bytes(&self) -> GenericArray<u8, Self::ElemLen> {
let c = self.compress();
*GenericArray::from_slice(c.as_bytes())
}
type UniformBytesLen = U64;
fn hash_to_curve(uniform_bytes: &GenericArray<u8, Self::UniformBytesLen>) -> Self {
let mut bits = [0u8; 64];
bits.copy_from_slice(uniform_bytes);
// This could really be a from_uniform_bytes!
RistrettoPoint::hash_from_bytes::<sha2::Sha512>(&bits)
}
}
/// The implementation of such a subgroup for points on the large Curve25519-subgroup
impl Group for EdwardsPoint {
type Scalar = Scalar;
type ScalarLen = U32;
fn from_scalar_slice(
scalar_bits: &GenericArray<u8, Self::ScalarLen>,
) -> Result<Self::Scalar, InternalPakeError> {
let mut bits = [0u8; 32];
bits.copy_from_slice(scalar_bits);
Ok(Scalar::from_bytes_mod_order(bits))
}
fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
Scalar::random(rng)
}
fn scalar_as_bytes(scalar: &Self::Scalar) -> &GenericArray<u8, Self::ScalarLen> {
GenericArray::from_slice(scalar.as_bytes())
}
fn scalar_invert(scalar: &Self::Scalar) -> Self::Scalar {
scalar.invert()
}
// The byte length necessary to represent group elements
type ElemLen = U32;
fn from_element_slice(
element_bits: &GenericArray<u8, Self::ElemLen>,
) -> Result<Self, InternalPakeError> {
CompressedEdwardsY::from_slice(element_bits)
.decompress()
.ok_or_else(|| InternalPakeError::PointError)
}
// serialization of a group element
fn to_bytes(&self) -> GenericArray<u8, Self::ElemLen> {
let c = self.compress();
*GenericArray::from_slice(c.as_bytes())
}
type UniformBytesLen = U64;
fn hash_to_curve(uniform_bytes: &GenericArray<u8, Self::UniformBytesLen>) -> Self {
let mut result = [0u8; 32];
let mut counter = 0;
let mut wrapped_point: Option<EdwardsPoint> = None;
while wrapped_point.is_none() {
result.copy_from_slice(
&Sha256::new()
.chain(&uniform_bytes[..32])
.chain(&[counter])
.result()[..32],
);
wrapped_point = CompressedEdwardsY::from_slice(&result).decompress();
counter += 1;
}
wrapped_point
.expect("guarded by loop exit condition")
.mul_by_cofactor()
}
}
+406
View File
@@ -0,0 +1,406 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
use crate::{
errors::{utils::check_slice_size, InternalPakeError, PakeError, ProtocolError},
keypair::{Key, KeyPair, SizedBytes},
};
use generic_array::GenericArray;
use hkdf::Hkdf;
use hmac::{Hmac, Mac};
use rand_core::{CryptoRng, RngCore};
use sha2::{Digest, Sha256};
use std::convert::TryFrom;
/// This module is a somewhat minimalistic implementation of a key Exchange
/// protocol based on 3DH. It assumes a pre-exchange has allowed client and
/// server to learn each other's static public key.
///
/// This private module may undergo significant changes in the near term.
const KEY_LEN: usize = 32;
pub(crate) const NONCE_LEN: usize = 32;
pub(crate) const KE1_STATE_LEN: usize = KEY_LEN + KEY_LEN + NONCE_LEN;
pub(crate) const KE2_MESSAGE_LEN: usize = NONCE_LEN + 2 * KEY_LEN;
static STR_3DH: &[u8] = b"3DH keys";
pub(crate) struct KE1State {
client_e_sk: Key,
client_nonce: Vec<u8>,
hashed_l1: Vec<u8>,
}
pub(crate) struct KE1Message {
pub(crate) client_nonce: Vec<u8>,
pub(crate) client_e_pk: Key,
}
impl TryFrom<&[u8]> for KE1State {
type Error = ProtocolError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let checked_bytes = check_slice_size(bytes, KE1_STATE_LEN, "ke1_state")?;
Ok(Self {
client_e_sk: Key::from_bytes(&checked_bytes[..KEY_LEN])?,
client_nonce: checked_bytes[KEY_LEN..KEY_LEN + NONCE_LEN].to_vec(),
hashed_l1: checked_bytes[KEY_LEN + NONCE_LEN..].to_vec(),
})
}
}
impl KE1State {
pub fn to_bytes(&self) -> Vec<u8> {
let output: Vec<u8> = [
&self.client_e_sk.to_arr(),
&self.client_nonce[..],
&self.hashed_l1[..],
]
.concat();
output
}
}
impl KE1Message {
pub fn to_bytes(&self) -> Vec<u8> {
[&self.client_nonce[..], &self.client_e_pk.to_arr()].concat()
}
}
impl TryFrom<&[u8]> for KE1Message {
type Error = ProtocolError;
fn try_from(ke1_message_bytes: &[u8]) -> Result<Self, Self::Error> {
let checked_bytes =
check_slice_size(ke1_message_bytes, NONCE_LEN + KEY_LEN, "ke1_message")?;
Ok(Self {
client_nonce: checked_bytes[..NONCE_LEN].to_vec(),
client_e_pk: Key::from_bytes(&checked_bytes[NONCE_LEN..])?,
})
}
}
pub(crate) fn generate_ke1<R: RngCore + CryptoRng, KeyFormat: KeyPair<Repr = Key>>(
l1_component: Vec<u8>,
rng: &mut R,
) -> Result<(KE1State, KE1Message), ProtocolError> {
let client_e_kp = KeyFormat::generate_random(rng)?;
let mut client_nonce = [0u8; NONCE_LEN];
rng.fill_bytes(&mut client_nonce);
let ke1_message = KE1Message {
client_nonce: client_nonce.to_vec(),
client_e_pk: client_e_kp.public().clone(),
};
let l1_data: Vec<u8> = [&l1_component[..], &ke1_message.to_bytes()].concat();
let mut hasher = Sha256::new();
hasher.input(&l1_data);
let hashed_l1 = hasher.result();
Ok((
KE1State {
client_e_sk: client_e_kp.private().clone(),
client_nonce: client_nonce.to_vec(),
hashed_l1: hashed_l1.to_vec(),
},
ke1_message,
))
}
pub(crate) struct KE2State {
km3: Vec<u8>,
hashed_transcript: Vec<u8>,
shared_secret: Vec<u8>,
}
pub(crate) struct KE2Message {
server_nonce: Vec<u8>,
server_e_pk: Key,
mac: Vec<u8>,
}
impl KE2State {
pub fn to_bytes(&self) -> Vec<u8> {
let output: Vec<u8> = [
&self.km3[..],
&self.hashed_transcript[..],
&self.shared_secret[..],
]
.concat();
output
}
}
impl TryFrom<&[u8]> for KE2State {
type Error = ProtocolError;
fn try_from(ke1_message_bytes: &[u8]) -> Result<Self, Self::Error> {
let checked_bytes = check_slice_size(ke1_message_bytes, 3 * KEY_LEN, "ke2_state")?;
Ok(Self {
km3: checked_bytes[..KEY_LEN].to_vec(),
hashed_transcript: checked_bytes[KEY_LEN..2 * KEY_LEN].to_vec(),
shared_secret: checked_bytes[2 * KEY_LEN..].to_vec(),
})
}
}
impl KE2Message {
pub fn to_bytes(&self) -> Vec<u8> {
let output: Vec<u8> = [
&self.server_nonce[..],
&self.server_e_pk.to_arr(),
&self.mac[..],
]
.concat();
output
}
}
impl TryFrom<&[u8]> for KE2Message {
type Error = ProtocolError;
fn try_from(ke1_message_bytes: &[u8]) -> Result<Self, Self::Error> {
let checked_bytes = check_slice_size(ke1_message_bytes, KE2_MESSAGE_LEN, "ke2_message")?;
Ok(Self {
server_nonce: checked_bytes[..NONCE_LEN].to_vec(),
server_e_pk: Key::from_bytes(&checked_bytes[NONCE_LEN..NONCE_LEN + KEY_LEN])?,
mac: checked_bytes[NONCE_LEN + KEY_LEN..].to_vec(),
})
}
}
// The triple of public and private components used in the 3DH computation
struct TripleDHComponents {
pk1: Key,
sk1: Key,
pk2: Key,
sk2: Key,
pk3: Key,
sk3: Key,
}
// Consists of a shared secret, followed by two mac keys
type TripleDHDerivationResult = (
GenericArray<u8, <Sha256 as Digest>::OutputSize>,
GenericArray<u8, <Sha256 as Digest>::OutputSize>,
GenericArray<u8, <Sha256 as Digest>::OutputSize>,
);
// Internal function which takes the public and private components of the client and server keypairs, along
// with some auxiliary metadata, to produce the shared secret and two MAC keys
fn derive_3dh_keys<KeyFormat: KeyPair<Repr = Key>>(
dh: TripleDHComponents,
client_nonce: &[u8],
server_nonce: &[u8],
client_s_pk: KeyFormat::Repr,
server_s_pk: KeyFormat::Repr,
) -> Result<TripleDHDerivationResult, ProtocolError> {
let ikm: Vec<u8> = [
&KeyFormat::diffie_hellman(dh.pk1, dh.sk1)[..],
&KeyFormat::diffie_hellman(dh.pk2, dh.sk2)[..],
&KeyFormat::diffie_hellman(dh.pk3, dh.sk3)[..],
]
.concat();
let info: Vec<u8> = [
STR_3DH,
&client_nonce,
&server_nonce,
&client_s_pk.to_arr(),
&server_s_pk.to_arr(),
]
.concat();
const OUTPUT_SIZE: usize = 32;
let mut okm = [0u8; 3 * OUTPUT_SIZE];
let h = Hkdf::<Sha256>::new(None, &ikm);
h.expand(&info, &mut okm)
.map_err(|_| InternalPakeError::HkdfError)?;
Ok((
*GenericArray::from_slice(&okm[..OUTPUT_SIZE]),
*GenericArray::from_slice(&okm[OUTPUT_SIZE..2 * OUTPUT_SIZE]),
*GenericArray::from_slice(&okm[2 * OUTPUT_SIZE..]),
))
}
pub(crate) fn generate_ke2<R: RngCore + CryptoRng, KeyFormat: KeyPair<Repr = Key>>(
rng: &mut R,
l1_bytes: Vec<u8>,
l2_bytes: Vec<u8>,
client_e_pk: KeyFormat::Repr,
client_s_pk: KeyFormat::Repr,
server_s_sk: KeyFormat::Repr,
client_nonce: Vec<u8>,
) -> Result<(KE2State, KE2Message), ProtocolError> {
let server_e_kp = KeyFormat::generate_random(rng)?;
let mut server_nonce = [0u8; NONCE_LEN];
rng.fill_bytes(&mut server_nonce);
let (shared_secret, km2, km3) = derive_3dh_keys::<KeyFormat>(
TripleDHComponents {
pk1: client_e_pk.clone(),
sk1: server_e_kp.private().clone(),
pk2: client_e_pk,
sk2: server_s_sk.clone(),
pk3: client_s_pk.clone(),
sk3: server_e_kp.private().clone(),
},
&client_nonce,
&server_nonce,
client_s_pk,
KeyFormat::public_from_private(&server_s_sk),
)?;
let mut hasher = Sha256::new();
hasher.input(&l1_bytes);
let hashed_l1 = hasher.result();
let transcript2: Vec<u8> = [
&hashed_l1[..],
&l2_bytes[..],
&server_nonce[..],
&server_e_kp.public().to_arr(),
]
.concat();
let mut hasher2 = Sha256::new();
hasher2.input(&transcript2);
let hashed_transcript = hasher2.result();
let mut mac = Hmac::<Sha256>::new_varkey(&km2).map_err(|_| InternalPakeError::HmacError)?;
mac.input(&hashed_transcript);
Ok((
KE2State {
km3: km3.to_vec(),
hashed_transcript: hashed_transcript.to_vec(),
shared_secret: shared_secret.to_vec(),
},
KE2Message {
server_nonce: server_nonce.to_vec(),
server_e_pk: server_e_kp.public().clone(),
mac: mac.result().code().to_vec(),
},
))
}
pub(crate) struct KE3State {
pub(crate) shared_secret: Vec<u8>,
}
pub(crate) struct KE3Message {
mac: Vec<u8>,
}
impl TryFrom<&[u8]> for KE3State {
type Error = ProtocolError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let checked_bytes = check_slice_size(bytes, KEY_LEN, "ke3_state")?;
Ok(Self {
shared_secret: checked_bytes.to_vec(),
})
}
}
impl KE3Message {
pub fn to_bytes(&self) -> Vec<u8> {
self.mac.clone()
}
}
impl TryFrom<&[u8]> for KE3Message {
type Error = ProtocolError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let checked_bytes = check_slice_size(bytes, KEY_LEN, "ke3_message")?;
Ok(Self {
mac: checked_bytes.to_vec(),
})
}
}
pub(crate) fn generate_ke3<KeyFormat: KeyPair<Repr = Key>>(
l2_component: Vec<u8>,
ke2_message: KE2Message,
ke1_state: &KE1State,
server_s_pk: KeyFormat::Repr,
client_s_sk: KeyFormat::Repr,
) -> Result<(KE3State, KE3Message), ProtocolError> {
let (shared_secret, km2, km3) = derive_3dh_keys::<KeyFormat>(
TripleDHComponents {
pk1: ke2_message.server_e_pk.clone(),
sk1: ke1_state.client_e_sk.clone(),
pk2: server_s_pk.clone(),
sk2: ke1_state.client_e_sk.clone(),
pk3: ke2_message.server_e_pk.clone(),
sk3: client_s_sk.clone(),
},
&ke1_state.client_nonce,
&ke2_message.server_nonce,
KeyFormat::public_from_private(&client_s_sk),
server_s_pk,
)?;
let transcript: Vec<u8> = [
&ke1_state.hashed_l1[..],
&l2_component[..],
&ke2_message.server_nonce[..],
&ke2_message.server_e_pk[..],
]
.concat();
let mut hasher = Sha256::new();
hasher.input(&transcript);
let hashed_transcript = hasher.result();
let mut server_mac =
Hmac::<Sha256>::new_varkey(&km2).map_err(|_| InternalPakeError::HmacError)?;
server_mac.input(&hashed_transcript);
if ke2_message.mac != server_mac.result().code().to_vec() {
return Err(ProtocolError::VerificationError(
PakeError::KeyExchangeMacValidationError,
));
}
let mut client_mac =
Hmac::<Sha256>::new_varkey(&km3).map_err(|_| InternalPakeError::HmacError)?;
client_mac.input(&hashed_transcript);
Ok((
KE3State {
shared_secret: shared_secret.to_vec(),
},
KE3Message {
mac: client_mac.result().code().to_vec(),
},
))
}
// Outputs a shared secret
pub(crate) fn finish_ke(
ke3_message: KE3Message,
ke2_state: &KE2State,
) -> Result<Vec<u8>, ProtocolError> {
let mut client_mac =
Hmac::<Sha256>::new_varkey(&ke2_state.km3).map_err(|_| InternalPakeError::HmacError)?;
client_mac.input(&ke2_state.hashed_transcript);
if ke3_message.mac != client_mac.result().code().to_vec() {
return Err(ProtocolError::VerificationError(
PakeError::KeyExchangeMacValidationError,
));
}
Ok(ke2_state.shared_secret.to_vec())
}
+283
View File
@@ -0,0 +1,283 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
//! Contains the keypair types that must be supplied for the OPAQUE API
use crate::errors::{utils::check_slice_size, InternalPakeError};
use generic_array::{
sequence::Concat,
typenum::{Sum, Unsigned, U32},
ArrayLength, GenericArray,
};
use rand_core::{CryptoRng, RngCore};
use x25519_dalek::{PublicKey, StaticSecret};
use std::convert::TryFrom;
use std::ops::{Add, Deref};
/// A trait for sized key material that can be represented within a fixed byte
/// array size, used to represent our DH key types
pub trait SizedBytes: Sized + PartialEq {
/// The typed representation of the byte length
type Len: ArrayLength<u8>;
/// Converts this sized key material to a `GenericArray` of the same
/// size. One can convert this to a `&[u8]` with `GenericArray::as_slice()`
/// but the size information is then lost from the type.
fn to_arr(&self) -> GenericArray<u8, Self::Len>;
/// How to parse such sized material from a byte slice.
fn from_bytes(key_bytes: &[u8]) -> Result<Self, InternalPakeError>;
}
/// A Keypair trait with public-private verification
pub trait KeyPair: Sized {
/// The single key representation must have a specific byte size itself
type Repr: SizedBytes + Clone;
/// The public key component
fn public(&self) -> &Self::Repr;
/// The private key component
fn private(&self) -> &Self::Repr;
/// A constructor that receives public and private key independently as
/// bytes
fn new(public: Self::Repr, private: Self::Repr) -> Result<Self, InternalPakeError>;
/// Generating a random key pair given a cryptographic rng
fn generate_random<R: RngCore + CryptoRng>(rng: &mut R) -> Result<Self, InternalPakeError>;
/// Obtaining a public key from secret bytes. At all times, we should have
/// &public_from_private(self.private()) == self.public()
fn public_from_private(secret: &Self::Repr) -> Self::Repr;
/// Check whether a public key is valid. This is meant to be applied on
/// material provided through the network which fits the key
/// representation (i.e. can be mapped to a curve point), but presents
/// some risk - e.g. small subgroup check
fn check_public_key(key: Self::Repr) -> Result<Self::Repr, InternalPakeError>;
/// Computes the diffie hellman function on a public key and private key
fn diffie_hellman(pk: Self::Repr, sk: Self::Repr) -> Vec<u8>;
}
/// This is a blanket implementation of SizedBytes for any instance of KeyPair
/// with any length of keys. This encodes that we serialize the public key
/// first, followed by the private key in binary formats (and expect it in this
/// order upon decoding).
impl<T, KP> SizedBytes for KP
where
T: SizedBytes + Clone,
KP: KeyPair<Repr = T> + PartialEq,
T::Len: Add<T::Len>,
Sum<T::Len, T::Len>: ArrayLength<u8>,
{
type Len = Sum<T::Len, T::Len>;
fn to_arr(&self) -> GenericArray<u8, Self::Len> {
let private = self.private().to_arr();
let public = self.public().to_arr();
public.concat(private)
}
fn from_bytes(key_bytes: &[u8]) -> Result<Self, InternalPakeError> {
let checked_bytes =
check_slice_size(key_bytes, <Self::Len as Unsigned>::to_usize(), "key_bytes")?;
let single_key_len = <<KP::Repr as SizedBytes>::Len as Unsigned>::to_usize();
let public = <T as SizedBytes>::from_bytes(&checked_bytes[..single_key_len])?;
let private = <T as SizedBytes>::from_bytes(&checked_bytes[single_key_len..])?;
KP::new(public, private)
}
}
/// A minimalist key type built around [u8;32]
#[derive(PartialEq, Eq, Clone)]
#[repr(transparent)]
pub struct Key(Vec<u8>);
impl Deref for Key {
type Target = Vec<u8>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl TryFrom<Vec<u8>> for Key {
type Error = InternalPakeError;
fn try_from(key_bytes: Vec<u8>) -> Result<Self, Self::Error> {
Key::from_bytes(&key_bytes[..])
}
}
impl SizedBytes for Key {
type Len = U32;
fn to_arr(&self) -> GenericArray<u8, Self::Len> {
GenericArray::clone_from_slice(&self.0[..])
}
fn from_bytes(key_bytes: &[u8]) -> Result<Self, InternalPakeError> {
let checked_bytes =
check_slice_size(key_bytes, <Self::Len as Unsigned>::to_usize(), "key_bytes")?;
Ok(Key(checked_bytes.to_vec()))
}
}
/// A representation of an X25519 keypair according to RFC7748
#[derive(PartialEq)]
pub struct X25519KeyPair {
pk: Key,
sk: Key,
}
impl X25519KeyPair {
fn gen<R: RngCore + CryptoRng>(rng: &mut R) -> (Vec<u8>, Vec<u8>) {
let sk = StaticSecret::new(rng);
let pk = PublicKey::from(&sk);
(pk.as_bytes().to_vec(), sk.to_bytes().to_vec())
}
}
impl KeyPair for X25519KeyPair {
type Repr = Key;
fn public(&self) -> &Self::Repr {
&self.pk
}
fn private(&self) -> &Self::Repr {
&self.sk
}
fn new(public: Self::Repr, private: Self::Repr) -> Result<Self, InternalPakeError> {
Ok(X25519KeyPair {
pk: public,
sk: private,
})
}
fn generate_random<R: RngCore + CryptoRng>(rng: &mut R) -> Result<Self, InternalPakeError> {
let (public, private) = X25519KeyPair::gen(rng);
Ok(X25519KeyPair {
pk: Key(public),
sk: Key(private),
})
}
fn public_from_private(secret: &Self::Repr) -> Self::Repr {
let mut secret_data = [0u8; 32];
secret_data.copy_from_slice(&secret.0[..]);
let base_data = ::x25519_dalek::X25519_BASEPOINT_BYTES;
Key(::x25519_dalek::x25519(secret_data, base_data).to_vec())
}
fn check_public_key(key: Self::Repr) -> Result<Self::Repr, InternalPakeError> {
let mut key_bytes = [0u8; 32];
key_bytes.copy_from_slice(&key);
let point = ::curve25519_dalek::montgomery::MontgomeryPoint(key_bytes)
.to_edwards(1)
.ok_or(InternalPakeError::PointError)?;
if !point.is_torsion_free() {
Err(InternalPakeError::SubGroupError)
} else {
Ok(key)
}
}
fn diffie_hellman(pk: Self::Repr, sk: Self::Repr) -> Vec<u8> {
let mut pk_data = [0; 32];
pk_data.copy_from_slice(&pk.0[..]);
let mut sk_data = [0; 32];
sk_data.copy_from_slice(&sk.0[..]);
::x25519_dalek::x25519(sk_data, pk_data).to_vec()
}
}
/// A custom, minimalistic Key pair struct built on Key, aimed at reproducing the behavior of libsignal's keypairs
#[derive(PartialEq)]
pub struct SignalKeyPair {
pk: Key,
sk: Key,
}
impl SignalKeyPair {
fn clamp_scalar(mut scalar: [u8; 32]) -> ::curve25519_dalek::scalar::Scalar {
scalar[0] &= 248;
scalar[31] &= 127;
scalar[31] |= 64;
::curve25519_dalek::scalar::Scalar::from_bits(scalar)
}
fn gen<R: RngCore + CryptoRng>(rng: &mut R) -> (Vec<u8>, Vec<u8>) {
let mut bits = [0u8; 32];
rng.fill_bytes(&mut bits);
// It's proper to sanitize the scalar here, and reproduces x25519::StaticSecret::new
let sk = SignalKeyPair::clamp_scalar(bits);
let pk = ::curve25519_dalek::constants::X25519_BASEPOINT * sk;
(pk.as_bytes().to_vec(), sk.as_bytes().to_vec())
}
}
impl KeyPair for SignalKeyPair {
type Repr = Key;
fn public(&self) -> &Self::Repr {
&self.pk
}
fn private(&self) -> &Self::Repr {
&self.sk
}
fn new(public: Self::Repr, private: Self::Repr) -> Result<Self, InternalPakeError> {
Ok(SignalKeyPair {
pk: public,
sk: private,
})
}
fn generate_random<R: RngCore + CryptoRng>(rng: &mut R) -> Result<Self, InternalPakeError> {
let (public, private) = SignalKeyPair::gen(rng);
Ok(SignalKeyPair {
pk: Key(public),
sk: Key(private),
})
}
fn public_from_private(secret: &Self::Repr) -> Self::Repr {
let mut secret_data = [0u8; 32];
secret_data.copy_from_slice(&secret.0[..]);
let base_data = ::x25519_dalek::X25519_BASEPOINT_BYTES;
Key(::x25519_dalek::x25519(secret_data, base_data).to_vec())
}
fn check_public_key(key: Self::Repr) -> Result<Self::Repr, InternalPakeError> {
let mut key_bytes = [0u8; 32];
key_bytes.copy_from_slice(&key);
let point = ::curve25519_dalek::montgomery::MontgomeryPoint(key_bytes)
.to_edwards(1)
.ok_or(InternalPakeError::PointError)?;
if !point.is_torsion_free() {
Err(InternalPakeError::SubGroupError)
} else {
Ok(key)
}
}
fn diffie_hellman(pk: Self::Repr, sk: Self::Repr) -> Vec<u8> {
let mut pk_data = [0; 32];
pk_data.copy_from_slice(&pk.0[..]);
let mut sk_data = [0; 32];
sk_data.copy_from_slice(&sk.0[..]);
::x25519_dalek::x25519(sk_data, pk_data).to_vec()
}
}
+336
View File
@@ -0,0 +1,336 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
//! An implementation of the OPAQUE asymmetric password authentication key exchange protocol
//!
//! # Overview
//!
//! OPAQUE is a protocol between a client and a server. They must first agree on a collection of primitives
//! to be kept consistent throughout protocol execution. These include:
//! * an authenticated encryption scheme,
//! * a finite cyclic group along with a point representation, and
//! * a keypair type.
//!
//! We will use the following choices in this example:
//! ```
//! use chacha20poly1305::ChaCha20Poly1305;
//! use curve25519_dalek::ristretto::RistrettoPoint;
//! use opaque_ke::keypair::X25519KeyPair;
//! ```
//!
//! This implementation is in sync with [draft-krawczyk-cfrg-opaque-05](https://tools.ietf.org/html/draft-krawczyk-cfrg-opaque-05),
//! with a concrete instantiation of the authenticated key exchange protocol using 3DH. In the future, we plan to
//! add support for other KE protocols as well.
//!
//!
//! ## Setup
//! To setup the protocol, the server begins by generating a static keypair:
//! ```
//! # use opaque_ke::keypair::{KeyPair, X25519KeyPair, SizedBytes};
//! # use opaque_ke::errors::ProtocolError;
//! use rand_core::{OsRng, RngCore};
//! let mut rng = OsRng;
//! let server_kp = X25519KeyPair::generate_random(&mut rng)?;
//! # Ok::<(), ProtocolError>(())
//! ```
//! The server must persist this keypair for the registration and login steps, where the public component will be
//! used by the client during both registration and login, and the private component will be used by the server during login.
//!
//! ## Registration
//! The registration protocol between the client and server consists of four steps along with three messages, denoted
//! as `r1`, `r2`, and `r3`. Before registration begins, it is expected that the server's static public key, `server_kp.public()`,
//! has been transmitted to the client in an offline step. A successful execution of the registration protocol results in the
//! server producing a password file corresponding to the tuple combination of (password, pepper, server public key) provided by
//! the client. This password file is typically stored server-side, and retrieved upon future login attempts made by the client.
//!
//! In the first step (client registration start), the client chooses a registration password and an optional "pepper", and
//! runs `ClientRegistration::start` to produce a message `r1`:
//! ```
//! # use opaque_ke::{opaque::{ClientRegistration, ServerRegistration}, keypair::{KeyPair, X25519KeyPair, SizedBytes}};
//! # use opaque_ke::errors::ProtocolError;
//! # use curve25519_dalek::ristretto::RistrettoPoint;
//! # use chacha20poly1305::ChaCha20Poly1305;
//! use rand_core::{OsRng, RngCore};
//! let mut client_rng = OsRng;
//! let (r1, client_state) = ClientRegistration::<ChaCha20Poly1305, RistrettoPoint>::start(
//! b"password",
//! Some(b"pepper"),
//! &mut client_rng,
//! )?;
//! # Ok::<(), ProtocolError>(())
//! ```
//! `r1` is sent to the server, and `client_state` must be persisted on the client for the final step of client
//! registration.
//!
//! In the second step (server registration start), the server takes as input the `r1` message from the client and runs
//! `ServerRegistration::start` to produce `r2`:
//! ```
//! # use opaque_ke::{opaque::{ClientRegistration, ServerRegistration}, keypair::{KeyPair, X25519KeyPair, SizedBytes}};
//! # use opaque_ke::errors::ProtocolError;
//! # use curve25519_dalek::ristretto::RistrettoPoint;
//! # use chacha20poly1305::ChaCha20Poly1305;
//! # use rand_core::{OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let (r1, client_state) = ClientRegistration::<ChaCha20Poly1305, RistrettoPoint>::start(
//! # b"password",
//! # Some(b"pepper"),
//! # &mut client_rng,
//! # )?;
//! let mut server_rng = OsRng;
//! let (r2, server_state) =
//! ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start(
//! r1,
//! &mut server_rng,
//! )?;
//! # Ok::<(), ProtocolError>(())
//! ```
//! `r2` is returned to the client, and `server_state` must be persisted on the server for the final step of server
//! registration.
//!
//! In the third step (client registration finish), the client takes as input the `r2` message from the server, along
//! with the server's static public key `server_kp.public()`, and uses `client_state` from the first step to run
//! `finish` and produce a message `r3` along with the key derivation key `kd_key_registration`:
//! ```
//! # use opaque_ke::{opaque::{ClientRegistration, ServerRegistration}, keypair::{KeyPair, X25519KeyPair, SizedBytes}};
//! # use opaque_ke::errors::ProtocolError;
//! # use curve25519_dalek::ristretto::RistrettoPoint;
//! # use chacha20poly1305::ChaCha20Poly1305;
//! # use rand_core::{OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let (r1, client_state) = ClientRegistration::<ChaCha20Poly1305, RistrettoPoint>::start(
//! # b"password",
//! # Some(b"pepper"),
//! # &mut client_rng,
//! # )?;
//! # let mut server_rng = OsRng;
//! # let (r2, server_state) =
//! # ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start(
//! # r1,
//! # &mut server_rng,
//! # )?;
//! # let server_kp = X25519KeyPair::generate_random(&mut server_rng)?;
//! let (r3, kd_key_registration) =
//! client_state.finish::<_, X25519KeyPair>(r2, server_kp.public(), &mut client_rng)?;
//! # Ok::<(), ProtocolError>(())
//! ```
//! `r3` is sent to the server, and the client can optionally use `kd_key_registration` for applications that choose to
//! process user information beyond the OPAQUE functionality (e.g., additional secrets or credentials).
//!
//! In the fourth step of registration, the server takes as input the `r3` message from the client and uses
//! `server_state` from the second step to run `finish` and produce `password_file`:
//! ```
//! # use opaque_ke::{opaque::{ClientRegistration, ServerRegistration}, keypair::{KeyPair, X25519KeyPair, SizedBytes}};
//! # use opaque_ke::errors::ProtocolError;
//! # use curve25519_dalek::ristretto::RistrettoPoint;
//! # use chacha20poly1305::ChaCha20Poly1305;
//! # use rand_core::{OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let (r1, client_state) = ClientRegistration::<ChaCha20Poly1305, RistrettoPoint>::start(
//! # b"password",
//! # Some(b"pepper"),
//! # &mut client_rng,
//! # )?;
//! # let mut server_rng = OsRng;
//! # let (r2, server_state) =
//! # ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start(
//! # r1,
//! # &mut server_rng,
//! # )?;
//! # let server_kp = X25519KeyPair::generate_random(&mut server_rng)?;
//! # let (r3, kd_key_registration) =
//! # client_state.finish::<_, X25519KeyPair>(r2, server_kp.public(), &mut client_rng)?;
//! let password_file = server_state.finish(r3)?;
//! # Ok::<(), ProtocolError>(())
//! ```
//! At this point, the client can be considered as successfully registered, and the server can store
//! `password_file.to_bytes()` for use during the login protocol.
//!
//!
//! ## Login
//! The login protocol between a client and server also consists of four steps along with three messages, denoted as
//! `l1`, `l2`, and `l3`. The server is expected to have access to the a password file corresponding to an output
//! of the registration phase. The login protocol will execute successfully only if the same tuple combination of
//! (password, pepper, server public key) is presented as was used in the registration phase that produced the
//! password file that the server is testing against.
//!
//! In the first step (client login start), the client chooses a registration password and an optional "pepper", and runs
//! `ClientLogin::start` to produce a message `l1`:
//! ```
//! # use opaque_ke::{opaque::{ClientRegistration, ServerRegistration, ClientLogin, ServerLogin, LoginThirdMessage}, keypair::{KeyPair, X25519KeyPair, SizedBytes}};
//! # use opaque_ke::errors::ProtocolError;
//! # use curve25519_dalek::ristretto::RistrettoPoint;
//! # use chacha20poly1305::ChaCha20Poly1305;
//! # use rand_core::{OsRng, RngCore};
//! let mut client_rng = OsRng;
//! let (l1, client_state) = ClientLogin::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start(
//! b"password",
//! Some(b"pepper"),
//! &mut client_rng,
//! )?;
//! # Ok::<(), ProtocolError>(())
//! ```
//! `l1` is sent to the server, and `client_state` must be persisted on the client for the final step of client login.
//!
//! In the second step (server login start), the server takes as input the `l1` message from the client, the server's
//! private key `server_kp.private()`, along with a serialized version of the password file, `password_file_bytes`, and
//! runs `ServerLogin::start` to produce `l2`:
//! ```
//! # use opaque_ke::{opaque::{ClientRegistration, ServerRegistration, ClientLogin, ServerLogin, LoginThirdMessage}, keypair::{KeyPair, X25519KeyPair, SizedBytes}};
//! # use opaque_ke::errors::ProtocolError;
//! # use curve25519_dalek::ristretto::RistrettoPoint;
//! # use chacha20poly1305::ChaCha20Poly1305;
//! # use rand_core::{OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let (r1, client_state) = ClientRegistration::<ChaCha20Poly1305, RistrettoPoint>::start(
//! # b"password",
//! # Some(b"pepper"),
//! # &mut client_rng,
//! # )?;
//! # let mut server_rng = OsRng;
//! # let (r2, server_state) =
//! # ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start(
//! # r1,
//! # &mut server_rng,
//! # )?;
//! # let server_kp = X25519KeyPair::generate_random(&mut server_rng)?;
//! # let (r3, kd_key_registration) =
//! # client_state.finish::<_, X25519KeyPair>(r2, server_kp.public(), &mut client_rng)?;
//! # let password_file_bytes = server_state.finish(r3)?.to_bytes();
//! # let (l1, client_state) = ClientLogin::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start(
//! # b"password",
//! # Some(b"pepper"),
//! # &mut client_rng,
//! # )?;
//! use std::convert::TryFrom;
//! let password_file =
//! ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::try_from(
//! &password_file_bytes[..],
//! )?;
//! let mut server_rng = OsRng;
//! let (l2, server_state) =
//! ServerLogin::start(password_file, &server_kp.private(), l1, &mut server_rng)?;
//! # Ok::<(), ProtocolError>(())
//! ```
//! `l2` is returned to the client, and `server_state` must be persisted on the server for the final step of server login.
//!
//! In the third step (client login finish), the client takes as input the `l2` message from the server, along with the
//! server's static public key `server_kp.public()`, and uses `client_state` from the first step to run `finish` and produce
//! a message `l3`, the shared secret `client_shared_secret`, and the key derivation key `kd_key_login`:
//! ```
//! # use opaque_ke::{opaque::{ClientRegistration, ServerRegistration, ClientLogin, ServerLogin, LoginThirdMessage}, keypair::{KeyPair, X25519KeyPair, SizedBytes}};
//! # use opaque_ke::errors::ProtocolError;
//! # use curve25519_dalek::ristretto::RistrettoPoint;
//! # use chacha20poly1305::ChaCha20Poly1305;
//! # use rand_core::{OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let (r1, client_state) = ClientRegistration::<ChaCha20Poly1305, RistrettoPoint>::start(
//! # b"password",
//! # Some(b"pepper"),
//! # &mut client_rng,
//! # )?;
//! # let mut server_rng = OsRng;
//! # let (r2, server_state) =
//! # ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start(
//! # r1,
//! # &mut server_rng,
//! # )?;
//! # let server_kp = X25519KeyPair::generate_random(&mut server_rng)?;
//! # let (r3, kd_key_registration) =
//! # client_state.finish::<_, X25519KeyPair>(r2, server_kp.public(), &mut client_rng)?;
//! # let password_file_bytes = server_state.finish(r3)?.to_bytes();
//! # let (l1, client_state) = ClientLogin::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start(
//! # b"password",
//! # Some(b"pepper"),
//! # &mut client_rng,
//! # )?;
//! # use std::convert::TryFrom;
//! # let password_file =
//! # ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::try_from(
//! # &password_file_bytes[..],
//! # )?;
//! # let (l2, server_state) =
//! # ServerLogin::start(password_file, &server_kp.private(), l1, &mut server_rng)?;
//! let (l3, client_shared_secret, kd_key_login) = client_state.finish(
//! l2,
//! &server_kp.public(),
//! &mut client_rng,
//! )?;
//! assert_eq!(kd_key_registration, kd_key_login);
//! # Ok::<(), ProtocolError>(())
//! ```
//! Note that if the client supplies a tuple (password, pepper, server public key) that does not match the tuple
//! used to create the password file, then at this point the `finish` algorithm outputs the error `InvalidLoginError`.
//!
//! If `finish` completes successfully, then `l3` is sent to the server, and (similarly to registration) the client
//! can use `kd_key_login` for applications that can take advantage of the fact that this key is identical to
//! `kd_key_registration`.
//!
//! In the fourth step of login, the server takes as input the `l3` message from the client and uses `server_state` from
//! the second step to run `finish`:
//! ```
//! # use opaque_ke::{opaque::{ClientRegistration, ServerRegistration, ClientLogin, ServerLogin, LoginThirdMessage}, keypair::{KeyPair, X25519KeyPair, SizedBytes}};
//! # use opaque_ke::errors::ProtocolError;
//! # use curve25519_dalek::ristretto::RistrettoPoint;
//! # use chacha20poly1305::ChaCha20Poly1305;
//! # use rand_core::{OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let (r1, client_state) = ClientRegistration::<ChaCha20Poly1305, RistrettoPoint>::start(
//! # b"password",
//! # Some(b"pepper"),
//! # &mut client_rng,
//! # )?;
//! # let mut server_rng = OsRng;
//! # let (r2, server_state) =
//! # ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start(
//! # r1,
//! # &mut server_rng,
//! # )?;
//! # let server_kp = X25519KeyPair::generate_random(&mut server_rng)?;
//! # let (r3, kd_key) =
//! # client_state.finish::<_, X25519KeyPair>(r2, server_kp.public(), &mut client_rng)?;
//! # let password_file_bytes = server_state.finish(r3)?.to_bytes();
//! # let (l1, client_state) = ClientLogin::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start(
//! # b"password",
//! # Some(b"pepper"),
//! # &mut client_rng,
//! # )?;
//! # use std::convert::TryFrom;
//! # let password_file =
//! # ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::try_from(
//! # &password_file_bytes[..],
//! # )?;
//! # let (l2, server_state) =
//! # ServerLogin::start(password_file, &server_kp.private(), l1, &mut server_rng)?;
//! # let (l3, client_shared_secret, kd_key) = client_state.finish(
//! # l2,
//! # &server_kp.public(),
//! # &mut client_rng,
//! # )?;
//! let server_shared_secret = server_state.finish(l3)?;
//! assert_eq!(client_shared_secret, server_shared_secret);
//! # Ok::<(), ProtocolError>(())
//! ```
//! If the protocol completes successfully, then the server obtains a `server_shared_secret` which is guaranteed to
//! match `client_shared_secret`. Otherwise, on failure, the `finish` algorithm outputs the error `InvalidLoginError`.
//!
// Error types
pub mod errors;
// High-level API
pub mod opaque;
// Your choice of RKR encryption
mod rkr_encryption;
// Your choice of KE
mod key_exchange;
pub mod keypair;
// Low-level API contains OPRF stuff
mod oprf;
// Technical module for your choice of cyclic subgroup to
// do the oprf on
mod group;
#[cfg(test)]
mod tests;
+918
View File
@@ -0,0 +1,918 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
//! Provides the main OPAQUE API
use crate::{
errors::{utils::check_slice_size, InternalPakeError, PakeError, ProtocolError},
group::Group,
key_exchange::{
finish_ke, generate_ke1, generate_ke2, generate_ke3, KE1Message, KE1State, KE2Message,
KE2State, KE3Message, KE1_STATE_LEN, KE2_MESSAGE_LEN,
},
keypair::{Key, KeyPair, SizedBytes},
oprf,
oprf::OprfClientBytes,
rkr_encryption::{RKRCipher, RKRCiphertext},
};
use generic_array::{
typenum::{Unsigned, U32, U64},
GenericArray,
};
use hkdf::Hkdf;
use rand_core::{CryptoRng, RngCore};
use sha2::{Digest, Sha256};
use std::{convert::TryFrom, marker::PhantomData};
use zeroize::Zeroize;
// Constant string used as salt for HKDF computation
const STR_ENVU: &[u8] = b"EnvU";
/// The length of the "key-derivation key" output by the client registration
/// and login finish steps
pub const DERIVED_KEY_LEN: usize = 32;
// Messages
// =========
/// The message sent by the client to the server, to initiate registration
pub struct RegisterFirstMessage<Grp> {
/// blinded password information
alpha: Grp,
}
impl<Grp: Group> TryFrom<&[u8]> for RegisterFirstMessage<Grp> {
type Error = ProtocolError;
fn try_from(first_message_bytes: &[u8]) -> Result<Self, Self::Error> {
// Check that the message is actually containing an element of the
// correct subgroup
let arr = GenericArray::from_slice(first_message_bytes);
let alpha = Grp::from_element_slice(arr)?;
Ok(Self { alpha })
}
}
impl<Grp: Group> RegisterFirstMessage<Grp> {
pub fn to_bytes(&self) -> GenericArray<u8, Grp::ElemLen> {
self.alpha.to_bytes()
}
}
/// The answer sent by the server to the user, upon reception of the
/// registration attempt
pub struct RegisterSecondMessage<Grp> {
/// The server's oprf output
beta: Grp,
}
impl<Grp> TryFrom<&[u8]> for RegisterSecondMessage<Grp>
where
Grp: Group,
{
type Error = ProtocolError;
fn try_from(second_message_bytes: &[u8]) -> Result<Self, Self::Error> {
let checked_slice = check_slice_size(
second_message_bytes,
Grp::ElemLen::to_usize(),
"second_message_bytes",
)?;
// Check that the message is actually containing an element of the
// correct subgroup
let arr = GenericArray::from_slice(&checked_slice);
let beta = Grp::from_element_slice(arr)?;
Ok(Self { beta })
}
}
impl<Grp> RegisterSecondMessage<Grp>
where
Grp: Group,
{
pub fn to_bytes(&self) -> Vec<u8> {
self.beta.to_bytes().to_vec()
}
}
/// The final message from the client, containing encrypted cryptographic
/// identifiers
pub struct RegisterThirdMessage<Aead, KeyFormat: KeyPair> {
/// The "envelope" generated by the user, containing encrypted
/// cryptographic identifiers
envelope: RKRCiphertext<Aead>,
/// The user's public key
client_s_pk: KeyFormat::Repr,
}
impl<Aead, KeyFormat> RegisterThirdMessage<Aead, KeyFormat>
where
Aead: aead::Aead + aead::NewAead<KeySize = U32>,
KeyFormat: KeyPair,
{
pub fn to_bytes(&self) -> Vec<u8> {
let mut res = Vec::new();
res.extend(self.envelope.to_bytes());
res.extend(self.client_s_pk.to_arr());
res
}
}
impl<Aead, KeyFormat> TryFrom<&[u8]> for RegisterThirdMessage<Aead, KeyFormat>
where
Aead: aead::Aead + aead::NewAead<KeySize = U32>,
KeyFormat: KeyPair,
{
type Error = ProtocolError;
fn try_from(third_message_bytes: &[u8]) -> Result<Self, Self::Error> {
let rkr_size = RKRCiphertext::<Aead>::rkr_with_nonce_size();
let key_len = <KeyFormat::Repr as SizedBytes>::Len::to_usize();
let checked_bytes =
check_slice_size(third_message_bytes, rkr_size + key_len, "third_message")?;
let unchecked_client_s_pk = KeyFormat::Repr::from_bytes(&checked_bytes[rkr_size..])?;
let client_s_pk = KeyFormat::check_public_key(unchecked_client_s_pk)?;
Ok(Self {
envelope: RKRCiphertext::from_bytes(&checked_bytes[..rkr_size])?,
client_s_pk,
})
}
}
/// The message sent by the user to the server, to initiate registration
pub struct LoginFirstMessage<Grp> {
/// blinded password information
alpha: Grp,
ke1_message: KE1Message,
}
impl<Grp: Group> TryFrom<&[u8]> for LoginFirstMessage<Grp> {
type Error = ProtocolError;
fn try_from(first_message_bytes: &[u8]) -> Result<Self, Self::Error> {
// Check that the message is actually containing an element of the
// correct subgroup
let elem_len = Grp::ElemLen::to_usize();
let arr = GenericArray::from_slice(&first_message_bytes[..elem_len]);
let alpha = Grp::from_element_slice(arr)?;
let ke1_message = KE1Message::try_from(&first_message_bytes[elem_len..])?;
Ok(Self { alpha, ke1_message })
}
}
impl<Grp: Group> LoginFirstMessage<Grp> {
pub fn to_bytes(&self) -> Vec<u8> {
let output: Vec<u8> = [
self.alpha.to_bytes().as_slice(),
&self.ke1_message.to_bytes(),
]
.concat();
output
}
}
/// The answer sent by the server to the user, upon reception of the
/// login attempt.
pub struct LoginSecondMessage<Aead, Grp> {
/// the server's oprf output
beta: Grp,
/// the user's encrypted information,
envelope: RKRCiphertext<Aead>,
ke2_message: KE2Message,
}
impl<Aead, Grp> LoginSecondMessage<Aead, Grp>
where
Aead: aead::NewAead<KeySize = U32> + aead::Aead,
Grp: Group,
{
pub fn to_bytes(&self) -> Vec<u8> {
[
&self.beta.to_bytes()[..],
&self.envelope.to_bytes()[..],
&self.ke2_message.to_bytes()[..],
]
.concat()
}
}
impl<Aead, Grp> TryFrom<&[u8]> for LoginSecondMessage<Aead, Grp>
where
Aead: aead::NewAead<KeySize = U32> + aead::Aead,
Grp: Group,
{
type Error = ProtocolError;
fn try_from(second_message_bytes: &[u8]) -> Result<Self, Self::Error> {
let cipher_len = RKRCiphertext::<Aead>::rkr_with_nonce_size();
let elem_len = Grp::ElemLen::to_usize();
let checked_slice = check_slice_size(
second_message_bytes,
elem_len + cipher_len + KE2_MESSAGE_LEN,
"login_second_message_bytes",
)?;
// Check that the message is actually containing an element of the
// correct subgroup
let beta_bytes = &checked_slice[..elem_len];
let arr = GenericArray::from_slice(beta_bytes);
let beta = Grp::from_element_slice(arr)?;
let envelope =
RKRCiphertext::<Aead>::from_bytes(&checked_slice[elem_len..elem_len + cipher_len])?;
let ke2_message = KE2Message::try_from(&checked_slice[elem_len + cipher_len..])?;
Ok(Self {
beta,
envelope,
ke2_message,
})
}
}
/// The answer sent by the client to the server, upon reception of the
/// encrypted envelope
pub struct LoginThirdMessage {
ke3_message: KE3Message,
}
impl TryFrom<&[u8]> for LoginThirdMessage {
type Error = ProtocolError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let ke3_message = KE3Message::try_from(&bytes[..])?;
Ok(Self { ke3_message })
}
}
impl LoginThirdMessage {
pub fn to_bytes(&self) -> Vec<u8> {
self.ke3_message.to_bytes()
}
}
// Registration
// ============
/// The state elements the client holds to register itself
pub struct ClientRegistration<Aead, Grp: Group> {
/// A choice of symmetric encryption for the envelope
_aead: PhantomData<Aead>,
/// a blinding factor
pub(crate) blinding_factor: Grp::Scalar,
/// the client's password
password: Vec<u8>,
}
impl<Aead: aead::NewAead<KeySize = U32> + aead::Aead, Grp: Group> TryFrom<&[u8]>
for ClientRegistration<Aead, Grp>
{
type Error = ProtocolError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
// Check that the message is actually containing an element of the
// correct subgroup
let scalar_len = Grp::ScalarLen::to_usize();
let blinding_factor_bytes = GenericArray::from_slice(&bytes[..scalar_len]);
let blinding_factor = Grp::from_scalar_slice(blinding_factor_bytes)?;
let password = bytes[scalar_len..].to_vec();
Ok(Self {
_aead: PhantomData,
blinding_factor,
password,
})
}
}
impl<Aead, Grp> ClientRegistration<Aead, Grp>
where
Aead: aead::NewAead<KeySize = U32> + aead::Aead,
Grp: Group,
{
pub fn to_bytes(&self) -> Vec<u8> {
let output: Vec<u8> = [
Grp::scalar_as_bytes(&self.blinding_factor).as_slice(),
&self.password,
]
.concat();
output
}
}
impl<Aead, Grp> ClientRegistration<Aead, Grp>
where
Grp: Group<ScalarLen = U32, UniformBytesLen = U64>,
{
/// Returns an initial "blinded" request to send to the server, as well as a ClientRegistration
///
/// # Arguments
/// * `password` - A user password
///
/// # Example
///
/// ```
/// use opaque_ke::opaque::ClientRegistration;
/// # use opaque_ke::errors::ProtocolError;
/// use chacha20poly1305::ChaCha20Poly1305;
/// use curve25519_dalek::ristretto::RistrettoPoint;
/// use rand_core::{OsRng, RngCore};
/// let mut rng = OsRng;
/// let (register_m1, registration_state) = ClientRegistration::<ChaCha20Poly1305, RistrettoPoint>::start(b"hunter2", None, &mut rng)?;
/// # Ok::<(), ProtocolError>(())
/// ```
pub fn start<R: RngCore + CryptoRng>(
password: &[u8],
pepper: Option<&[u8]>,
blinding_factor_rng: &mut R,
) -> Result<(RegisterFirstMessage<Grp>, Self), ProtocolError> {
let OprfClientBytes {
alpha,
blinding_factor,
} = oprf::generate_oprf1::<R, Grp>(&password, pepper, blinding_factor_rng)?;
Ok((
RegisterFirstMessage::<Grp> { alpha },
Self {
_aead: PhantomData,
blinding_factor,
password: password.to_vec(),
},
))
}
}
type ClientRegistrationFinishResult<Aead, KeyFormat> = (
RegisterThirdMessage<Aead, KeyFormat>,
GenericArray<u8, <Sha256 as Digest>::OutputSize>,
);
impl<Aead, Grp> ClientRegistration<Aead, Grp>
where
Aead: aead::NewAead<KeySize = U32> + aead::Aead,
Grp: Group,
{
/// "Unblinds" the server's answer and returns a final message containing
/// cryptographic identifiers, to be sent to the server on setup finalization
///
/// # Arguments
/// * `message` - the server's answer to the initial registration attempt
///
/// # Example
///
/// ```
/// use opaque_ke::{opaque::{ClientRegistration, ServerRegistration}, keypair::{X25519KeyPair, SizedBytes}};
/// # use opaque_ke::errors::ProtocolError;
/// # use opaque_ke::keypair::KeyPair;
/// use rand_core::{OsRng, RngCore};
/// use chacha20poly1305::ChaCha20Poly1305;
/// use curve25519_dalek::ristretto::RistrettoPoint;
/// let mut client_rng = OsRng;
/// let mut server_rng = OsRng;
/// let server_kp = X25519KeyPair::generate_random(&mut server_rng)?;
/// let (register_m1, client_state) = ClientRegistration::<ChaCha20Poly1305, RistrettoPoint>::start(b"hunter2", None, &mut client_rng)?;
/// let (register_m2, server_state) =
/// ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start(register_m1, &mut server_rng)?;
/// let mut client_rng = OsRng;
/// let register_m3 = client_state.finish::<_, X25519KeyPair>(register_m2, server_kp.public(), &mut client_rng)?;
/// # Ok::<(), ProtocolError>(())
/// ```
pub fn finish<R: CryptoRng + RngCore, KeyFormat: KeyPair>(
self,
r2: RegisterSecondMessage<Grp>,
server_s_pk: &KeyFormat::Repr,
rng: &mut R,
) -> Result<ClientRegistrationFinishResult<Aead, KeyFormat>, ProtocolError> {
let client_static_keypair = KeyFormat::generate_random(rng)?;
let password_derived_key =
get_password_derived_key::<Grp>(self.password.clone(), r2.beta, &self.blinding_factor)?;
let h = Hkdf::<Sha256>::new(None, &password_derived_key);
let mut okm = [0u8; 3 * DERIVED_KEY_LEN];
h.expand(STR_ENVU, &mut okm)
.map_err(|_| InternalPakeError::HkdfError)?;
let encryption_key = &okm[..DERIVED_KEY_LEN];
let hmac_key = &okm[DERIVED_KEY_LEN..2 * DERIVED_KEY_LEN];
let kd_key = &okm[2 * DERIVED_KEY_LEN..];
let envelope = RKRCiphertext::<Aead>::encrypt(
&encryption_key,
&hmac_key,
&client_static_keypair.private().to_arr(),
&server_s_pk.to_arr(),
rng,
)?;
Ok((
RegisterThirdMessage {
envelope,
client_s_pk: client_static_keypair.public().clone(),
},
*GenericArray::from_slice(&kd_key),
))
}
}
// This can't be derived because of the use of a phantom parameter
impl<Aead, Grp: Group> Zeroize for ClientRegistration<Aead, Grp> {
fn zeroize(&mut self) {
self.password.zeroize();
self.blinding_factor.zeroize();
}
}
impl<Aead, Grp: Group> Drop for ClientRegistration<Aead, Grp> {
fn drop(&mut self) {
self.zeroize();
}
}
// This can't be derived because of the use of a phantom parameter
impl<Aead, Grp: Group, KeyFormat> Zeroize for ClientLogin<Aead, Grp, KeyFormat> {
fn zeroize(&mut self) {
self.password.zeroize();
self.blinding_factor.zeroize();
}
}
impl<Aead, Grp: Group, KeyFormat> Drop for ClientLogin<Aead, Grp, KeyFormat> {
fn drop(&mut self) {
self.zeroize();
}
}
/// The state elements the server holds to record a registration
pub struct ServerRegistration<Aead, Grp: Group, KeyFormat: KeyPair> {
envelope: Option<RKRCiphertext<Aead>>,
client_s_pk: Option<KeyFormat::Repr>,
pub(crate) oprf_key: Grp::Scalar,
}
impl<Aead, Grp, KeyFormat> TryFrom<&[u8]> for ServerRegistration<Aead, Grp, KeyFormat>
where
Aead: aead::NewAead<KeySize = U32> + aead::Aead,
Grp: Group,
KeyFormat: KeyPair + PartialEq,
<KeyFormat::Repr as SizedBytes>::Len: std::ops::Add<<KeyFormat::Repr as SizedBytes>::Len>,
generic_array::typenum::Sum<
<KeyFormat::Repr as SizedBytes>::Len,
<KeyFormat::Repr as SizedBytes>::Len,
>: generic_array::ArrayLength<u8>,
{
type Error = ProtocolError;
fn try_from(server_registration_bytes: &[u8]) -> Result<Self, Self::Error> {
let key_len = <KeyFormat::Repr as SizedBytes>::Len::to_usize();
let scalar_len = Grp::ScalarLen::to_usize();
let rkr_size = RKRCiphertext::<Aead>::rkr_with_nonce_size();
if server_registration_bytes.len() == scalar_len {
return Ok(Self {
oprf_key: Grp::from_scalar_slice(GenericArray::from_slice(
server_registration_bytes,
))?,
client_s_pk: None,
envelope: None,
});
}
let checked_bytes = check_slice_size(
server_registration_bytes,
rkr_size + key_len + scalar_len,
"server_registration_bytes",
)?;
let oprf_key_bytes = GenericArray::from_slice(&checked_bytes[..scalar_len]);
let oprf_key = Grp::from_scalar_slice(oprf_key_bytes)?;
let unchecked_client_s_pk =
KeyFormat::Repr::from_bytes(&checked_bytes[scalar_len..scalar_len + key_len])?;
let client_s_pk = KeyFormat::check_public_key(unchecked_client_s_pk)?;
Ok(Self {
envelope: Some(RKRCiphertext::from_bytes(
&checked_bytes[checked_bytes.len() - rkr_size..],
)?),
client_s_pk: Some(client_s_pk),
oprf_key,
})
}
}
impl<Aead, Grp, KeyFormat> ServerRegistration<Aead, Grp, KeyFormat>
where
Aead: aead::NewAead<KeySize = U32> + aead::Aead,
Grp: Group,
KeyFormat: KeyPair + PartialEq,
<KeyFormat::Repr as SizedBytes>::Len: std::ops::Add<<KeyFormat::Repr as SizedBytes>::Len>,
generic_array::typenum::Sum<
<KeyFormat::Repr as SizedBytes>::Len,
<KeyFormat::Repr as SizedBytes>::Len,
>: generic_array::ArrayLength<u8>,
{
pub fn to_bytes(&self) -> Vec<u8> {
let mut output: Vec<u8> = Grp::scalar_as_bytes(&self.oprf_key).to_vec();
match &self.client_s_pk {
Some(v) => output.extend_from_slice(&v.to_arr()),
None => {}
};
match &self.envelope {
Some(v) => output.extend_from_slice(&v.to_bytes()),
None => {}
};
output
}
/// From the client's "blinded" password, returns a response to be
/// sent back to the client, as well as a ServerRegistration
///
/// # Arguments
/// * `message` - the initial registration message
///
/// # Example
///
/// ```
/// use opaque_ke::{opaque::*, keypair::{X25519KeyPair, SizedBytes}};
/// # use opaque_ke::errors::ProtocolError;
/// # use opaque_ke::keypair::KeyPair;
/// use rand_core::{OsRng, RngCore};
/// use chacha20poly1305::ChaCha20Poly1305;
/// use curve25519_dalek::ristretto::RistrettoPoint;
/// let mut client_rng = OsRng;
/// let mut server_rng = OsRng;
/// let (register_m1, client_state) = ClientRegistration::<ChaCha20Poly1305, RistrettoPoint>::start(b"hunter2", None, &mut client_rng)?;
/// let (register_m2, server_state) =
/// ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start(register_m1, &mut server_rng)?;
/// # Ok::<(), ProtocolError>(())
/// ```
pub fn start<R: RngCore + CryptoRng>(
message: RegisterFirstMessage<Grp>,
rng: &mut R,
) -> Result<(RegisterSecondMessage<Grp>, Self), ProtocolError> {
// RFC: generate oprf_key (salt) and v_u = g^oprf_key
let oprf_key = Grp::random_scalar(rng);
// Compute beta = alpha^oprf_key
let beta = oprf::generate_oprf2::<Grp>(message.alpha, &oprf_key)?;
Ok((
RegisterSecondMessage { beta },
Self {
envelope: None,
client_s_pk: None,
oprf_key,
},
))
}
/// From the client's cryptographic identifiers, fully populates and
/// returns a ServerRegistration
///
/// # Arguments
/// * `message` - the final client message
///
/// # Example
///
/// ```
/// use opaque_ke::{opaque::*, keypair::{X25519KeyPair, SizedBytes}};
/// # use opaque_ke::errors::ProtocolError;
/// # use opaque_ke::keypair::KeyPair;
/// use rand_core::{OsRng, RngCore};
/// use chacha20poly1305::ChaCha20Poly1305;
/// use curve25519_dalek::ristretto::RistrettoPoint;
/// let mut client_rng = OsRng;
/// let mut server_rng = OsRng;
/// let server_kp = X25519KeyPair::generate_random(&mut server_rng)?;
/// let (register_m1, client_state) = ClientRegistration::<ChaCha20Poly1305, RistrettoPoint>::start(b"hunter2", None, &mut client_rng)?;
/// let (register_m2, server_state) =
/// ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start(register_m1, &mut server_rng)?;
/// let mut client_rng = OsRng;
/// let (register_m3, _opaque_key) = client_state.finish(register_m2, server_kp.public(), &mut client_rng)?;
/// let client_record = server_state.finish(register_m3)?;
/// # Ok::<(), ProtocolError>(())
/// ```
pub fn finish(
self,
message: RegisterThirdMessage<Aead, KeyFormat>,
) -> Result<Self, ProtocolError> {
Ok(Self {
envelope: Some(message.envelope),
client_s_pk: Some(message.client_s_pk),
oprf_key: self.oprf_key,
})
}
}
// Login
// =====
/// The state elements the client holds to perform a login
pub struct ClientLogin<Aead, Grp: Group, KeyFormat> {
/// A choice of symmetric encryption for the envelope
_aead: PhantomData<Aead>,
/// A choice of the keypair type
_key_format: PhantomData<KeyFormat>,
/// A blinding factor, which is used to mask (and unmask) secret
/// information before transmission
blinding_factor: Grp::Scalar,
/// The user's password
password: Vec<u8>,
ke1_state: KE1State,
}
impl<Aead: aead::NewAead<KeySize = U32> + aead::Aead, Grp: Group, KeyFormat: KeyPair> TryFrom<&[u8]>
for ClientLogin<Aead, Grp, KeyFormat>
{
type Error = ProtocolError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let scalar_len = Grp::ScalarLen::to_usize();
let blinding_factor_bytes = GenericArray::from_slice(&bytes[..scalar_len]);
let blinding_factor = Grp::from_scalar_slice(blinding_factor_bytes)?;
let ke1_state = KE1State::try_from(&bytes[scalar_len..scalar_len + KE1_STATE_LEN])?;
let password = bytes[scalar_len + KE1_STATE_LEN..].to_vec();
Ok(Self {
_aead: PhantomData,
_key_format: PhantomData,
blinding_factor,
password,
ke1_state,
})
}
}
impl<Aead, Grp, KeyFormat> ClientLogin<Aead, Grp, KeyFormat>
where
Aead: aead::NewAead<KeySize = U32> + aead::Aead,
Grp: Group,
KeyFormat: KeyPair,
{
pub fn to_bytes(&self) -> Vec<u8> {
let output: Vec<u8> = [
Grp::scalar_as_bytes(&self.blinding_factor).as_slice(),
&self.ke1_state.to_bytes(),
&self.password,
]
.concat();
output
}
}
type ClientLoginFinishResult = (
LoginThirdMessage,
Vec<u8>,
GenericArray<u8, <Sha256 as Digest>::OutputSize>,
);
impl<Aead, Grp, KeyFormat> ClientLogin<Aead, Grp, KeyFormat>
where
Aead: aead::NewAead<KeySize = U32> + aead::Aead,
Grp: Group<UniformBytesLen = U64>,
KeyFormat: KeyPair<Repr = Key>,
{
/// 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::opaque::ClientLogin;
/// # use opaque_ke::errors::ProtocolError;
/// use chacha20poly1305::ChaCha20Poly1305;
/// use curve25519_dalek::ristretto::RistrettoPoint;
/// use opaque_ke::keypair::X25519KeyPair;
/// use rand_core::{OsRng, RngCore};
/// let mut client_rng = OsRng;
/// let (login_m1, client_login_state) = ClientLogin::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start(b"hunter2", None, &mut client_rng)?;
/// # Ok::<(), ProtocolError>(())
/// ```
pub fn start<R: RngCore + CryptoRng>(
password: &[u8],
pepper: Option<&[u8]>,
rng: &mut R,
) -> Result<(LoginFirstMessage<Grp>, Self), ProtocolError> {
let OprfClientBytes {
alpha,
blinding_factor,
} = oprf::generate_oprf1::<R, Grp>(&password, pepper, rng)?;
let (ke1_state, ke1_message) =
generate_ke1::<_, KeyFormat>(alpha.to_bytes().to_vec(), rng)?;
let l1 = LoginFirstMessage { alpha, ke1_message };
Ok((
l1,
Self {
_aead: PhantomData,
_key_format: PhantomData,
blinding_factor,
password: password.to_vec(),
ke1_state,
},
))
}
/// "Unblinds" the server's answer and returns the decrypted assets from
/// the server
///
/// # Arguments
/// * `message` - the server's answer to the initial login attempt
///
/// # Example
///
/// ```
/// use opaque_ke::opaque::{ClientLogin, ServerLogin};
/// # use opaque_ke::opaque::{ClientRegistration, ServerRegistration};
/// # use opaque_ke::errors::ProtocolError;
/// # use opaque_ke::keypair::{X25519KeyPair, KeyPair};
/// use rand_core::{OsRng, RngCore};
/// use chacha20poly1305::ChaCha20Poly1305;
/// use curve25519_dalek::ristretto::RistrettoPoint;
/// let mut client_rng = OsRng;
/// # let mut server_rng = OsRng;
/// # let (register_m1, client_state) = ClientRegistration::<ChaCha20Poly1305, RistrettoPoint>::start(b"hunter2", None, &mut client_rng)?;
/// # let server_kp = X25519KeyPair::generate_random(&mut server_rng)?;
/// # let (register_m2, server_state) = ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start(register_m1, &mut server_rng)?;
/// # let (register_m3, _opaque_key) = client_state.finish(register_m2, server_kp.public(), &mut client_rng)?;
/// # let p_file = server_state.finish(register_m3)?;
/// let (login_m1, client_login_state) = ClientLogin::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start(b"hunter2", None, &mut client_rng)?;
/// let (login_m2, server_login_state) = ServerLogin::start(p_file, &server_kp.private(), login_m1, &mut server_rng)?;
/// let (login_m3, client_transport, _opaque_key) = client_login_state.finish(login_m2, &server_kp.public(), &mut client_rng)?;
/// # Ok::<(), ProtocolError>(())
/// ```
pub fn finish<R: RngCore + CryptoRng>(
self,
l2: LoginSecondMessage<Aead, Grp>,
server_s_pk: &KeyFormat::Repr,
_client_e_sk_rng: &mut R,
) -> Result<ClientLoginFinishResult, ProtocolError> {
let l2_bytes: Vec<u8> = [l2.beta.to_bytes().as_slice(), &l2.envelope.to_bytes()].concat();
let password_derived_key =
get_password_derived_key::<Grp>(self.password.clone(), l2.beta, &self.blinding_factor)?;
let h = Hkdf::<Sha256>::new(None, &password_derived_key);
let mut okm = [0u8; 3 * DERIVED_KEY_LEN];
h.expand(STR_ENVU, &mut okm)
.map_err(|_| InternalPakeError::HkdfError)?;
let encryption_key = &okm[..DERIVED_KEY_LEN];
let hmac_key = &okm[DERIVED_KEY_LEN..2 * DERIVED_KEY_LEN];
let kd_key = &okm[2 * DERIVED_KEY_LEN..];
let client_s_sk = Key::from_bytes(
&l2.envelope
.decrypt(&encryption_key, &hmac_key, &server_s_pk.to_arr())
.map_err(|e| match e {
PakeError::DecryptionHmacError => PakeError::InvalidLoginError,
err => err,
})?,
)?;
let (ke3_state, ke3_message) = generate_ke3::<KeyFormat>(
l2_bytes,
l2.ke2_message,
&self.ke1_state,
server_s_pk.clone(),
client_s_sk,
)?;
Ok((
LoginThirdMessage { ke3_message },
ke3_state.shared_secret,
*GenericArray::from_slice(&kd_key),
))
}
}
/// The state elements the server holds to record a login
pub struct ServerLogin {
ke2_state: KE2State,
}
impl TryFrom<&[u8]> for ServerLogin {
type Error = ProtocolError;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
Ok(Self {
ke2_state: KE2State::try_from(&bytes[..])?,
})
}
}
impl ServerLogin {
pub fn to_bytes(&self) -> Vec<u8> {
self.ke2_state.to_bytes()
}
/// From the client's "blinded"" password, returns a challenge to be
/// sent back to the client, as well as a ServerLogin
///
/// # Arguments
/// * `message` - the initial registration message
///
/// # Example
///
/// ```
/// use opaque_ke::opaque::{ClientLogin, ServerLogin};
/// # use opaque_ke::opaque::{ClientRegistration, ServerRegistration};
/// # use opaque_ke::errors::ProtocolError;
/// # use opaque_ke::keypair::{KeyPair, X25519KeyPair};
/// use rand_core::{OsRng, RngCore};
/// use chacha20poly1305::ChaCha20Poly1305;
/// use curve25519_dalek::ristretto::RistrettoPoint;
/// let mut client_rng = OsRng;
/// let mut server_rng = OsRng;
/// let server_kp = X25519KeyPair::generate_random(&mut server_rng)?;
/// # let (register_m1, client_state) = ClientRegistration::<ChaCha20Poly1305, RistrettoPoint>::start(b"hunter2", None, &mut client_rng)?;
/// # let (register_m2, server_state) =
/// ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start(register_m1, &mut server_rng)?;
/// # let (register_m3, _opaque_key) = client_state.finish(register_m2, server_kp.public(), &mut client_rng)?;
/// # let p_file = server_state.finish(register_m3)?;
/// let (login_m1, client_login_state) = ClientLogin::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start(b"hunter2", None, &mut client_rng)?;
/// let (login_m2, server_login_state) = ServerLogin::start(p_file, &server_kp.private(), login_m1, &mut server_rng)?;
/// # Ok::<(), ProtocolError>(())
/// ```
pub fn start<
R: RngCore + CryptoRng,
Aead: aead::NewAead<KeySize = U32> + aead::Aead,
Grp: Group,
KeyFormat: KeyPair<Repr = Key>,
>(
password_file: ServerRegistration<Aead, Grp, KeyFormat>,
server_s_sk: &Key,
l1: LoginFirstMessage<Grp>,
rng: &mut R,
) -> Result<(LoginSecondMessage<Aead, Grp>, Self), ProtocolError> {
let l1_bytes = &l1.to_bytes();
let beta = oprf::generate_oprf2(l1.alpha, &password_file.oprf_key)?;
let client_s_pk = password_file
.client_s_pk
.ok_or(PakeError::EncryptionError)?;
let envelope = password_file.envelope.ok_or(PakeError::EncryptionError)?;
let l2_component: Vec<u8> = [beta.to_bytes().as_slice(), &envelope.to_bytes()].concat();
let (ke2_state, ke2_message) = generate_ke2::<_, KeyFormat>(
rng,
l1_bytes.to_vec(),
l2_component,
l1.ke1_message.client_e_pk,
client_s_pk,
server_s_sk.clone(),
l1.ke1_message.client_nonce.to_vec(),
)?;
let l2 = LoginSecondMessage {
beta,
envelope,
ke2_message,
};
Ok((l2, Self { ke2_state }))
}
/// From the client's second & final message, check the client's
/// authentication & produce a message transport
///
/// # Arguments
/// * `message` - the client's second login message
///
/// # Example
///
/// ```
/// use opaque_ke::opaque::{ClientLogin, ServerLogin};
/// # use opaque_ke::opaque::{ClientRegistration, ServerRegistration};
/// # use opaque_ke::errors::ProtocolError;
/// # use opaque_ke::keypair::{KeyPair, X25519KeyPair};
/// use rand_core::{OsRng, RngCore};
/// use chacha20poly1305::ChaCha20Poly1305;
/// use curve25519_dalek::ristretto::RistrettoPoint;
/// let mut client_rng = OsRng;
/// let mut server_rng = OsRng;
/// let server_kp = X25519KeyPair::generate_random(&mut server_rng)?;
/// # let (register_m1, client_state) = ClientRegistration::<ChaCha20Poly1305, RistrettoPoint>::start(b"hunter2", None, &mut client_rng)?;
/// # let (register_m2, server_state) =
/// ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start(register_m1, &mut server_rng)?;
/// # let (register_m3, _opaque_key) = client_state.finish(register_m2, server_kp.public(), &mut client_rng)?;
/// # let p_file = server_state.finish(register_m3)?;
/// let (login_m1, client_login_state) = ClientLogin::<ChaCha20Poly1305, RistrettoPoint, X25519KeyPair>::start(b"hunter2", None, &mut client_rng)?;
/// let (login_m2, server_login_state) = ServerLogin::start(p_file, &server_kp.private(), login_m1, &mut server_rng)?;
/// let (login_m3, client_transport, _opaque_key) = client_login_state.finish(login_m2, &server_kp.public(), &mut client_rng)?;
/// let mut server_transport = server_login_state.finish(login_m3)?;
/// # Ok::<(), ProtocolError>(())
/// ```
pub fn finish(&self, message: LoginThirdMessage) -> Result<Vec<u8>, ProtocolError> {
finish_ke(message.ke3_message, &self.ke2_state).map_err(|e| match e {
ProtocolError::VerificationError(PakeError::KeyExchangeMacValidationError) => {
ProtocolError::VerificationError(PakeError::InvalidLoginError)
}
err => err,
})
}
}
// Helper functions
fn get_password_derived_key<G: Group>(
password: Vec<u8>,
beta: G,
blinding_factor: &G::Scalar,
) -> Result<GenericArray<u8, <Sha256 as Digest>::OutputSize>, PakeError> {
Ok(oprf::generate_oprf3::<G>(&password, beta, blinding_factor)?)
}
+133
View File
@@ -0,0 +1,133 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
use crate::{errors::InternalPakeError, group::Group};
use generic_array::{typenum::U64, GenericArray};
use hkdf::Hkdf;
use rand_core::{CryptoRng, RngCore};
use sha2::{Digest, Sha256};
// Low-level API
// =============
// This file contains an implementation of an oblivious pseudorandom function (OPRF), as well as password hashing and encryption functions.
pub(crate) struct OprfClientBytes<Grp: Group> {
pub(crate) alpha: Grp,
pub(crate) blinding_factor: Grp::Scalar,
}
/// Computes the first step for the multiplicative blinding version of DH-OPRF. This
/// message is sent from the client (who holds the input) to the server (who holds the OPRF key).
/// The client can also pass in an optional "pepper" string to be mixed in with the input through
/// an HKDF computation.
pub(crate) fn generate_oprf1<R: RngCore + CryptoRng, G: Group<UniformBytesLen = U64>>(
input: &[u8],
pepper: Option<&[u8]>,
blinding_factor_rng: &mut R,
) -> Result<OprfClientBytes<G>, InternalPakeError> {
let (hashed_input, _) = Hkdf::<Sha256>::extract(pepper, &input);
let curve_input: Vec<u8> = [hashed_input.as_slice(), &[0u8; 32]].concat();
let blinding_factor = G::random_scalar(blinding_factor_rng);
let alpha = G::hash_to_curve(GenericArray::from_slice(&curve_input)) * &blinding_factor;
Ok(OprfClientBytes {
alpha,
blinding_factor,
})
}
/// Computes the second step for the multiplicative blinding version of DH-OPRF. This
/// message is sent from the server (who holds the OPRF key) to the client.
pub(crate) fn generate_oprf2<G: Group>(
point: G,
oprf_key: &G::Scalar,
) -> Result<G, InternalPakeError> {
Ok(point * oprf_key)
}
/// Computes the third step for the multiplicative blinding version of DH-OPRF, in which
/// the client unblinds the server's message.
pub(crate) fn generate_oprf3<G: Group>(
input: &[u8],
point: G,
blinding_factor: &G::Scalar,
) -> Result<GenericArray<u8, <Sha256 as Digest>::OutputSize>, InternalPakeError> {
let unblinded = point * &G::scalar_invert(&blinding_factor);
let ikm: Vec<u8> = [&unblinded.to_bytes(), input].concat();
let (prk, _) = Hkdf::<Sha256>::extract(None, &ikm);
Ok(prk)
}
// Tests
// =====
#[cfg(test)]
mod tests {
use super::*;
use crate::group::Group;
use curve25519_dalek::ristretto::RistrettoPoint;
use generic_array::{arr, arr_impl, GenericArray};
use hkdf::Hkdf;
use rand_core::OsRng;
fn prf(
input: &[u8],
oprf_key: &[u8; 32],
) -> GenericArray<u8, <RistrettoPoint as Group>::ElemLen> {
let (hashed_input, _) = Hkdf::<Sha256>::extract(None, &input);
let curve_input: Vec<u8> = [hashed_input.as_slice(), &[0u8; 32]].concat();
let point = RistrettoPoint::hash_to_curve(GenericArray::from_slice(&curve_input));
let scalar =
RistrettoPoint::from_scalar_slice(GenericArray::from_slice(&oprf_key[..])).unwrap();
let res = point * scalar;
let ikm: Vec<u8> = [res.to_bytes().as_slice(), &input].concat();
let (prk, _) = Hkdf::<Sha256>::extract(None, &ikm);
prk
}
#[test]
fn oprf_retrieval() -> Result<(), InternalPakeError> {
let input = b"hunter2";
let mut rng = OsRng;
let OprfClientBytes {
alpha,
blinding_factor,
} = generate_oprf1::<_, RistrettoPoint>(&input[..], None, &mut rng)?;
let salt_bytes = arr![
u8; 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 32,
];
let salt = RistrettoPoint::from_scalar_slice(&salt_bytes)?;
let beta = generate_oprf2::<RistrettoPoint>(alpha, &salt)?;
let res = generate_oprf3::<RistrettoPoint>(input, beta, &blinding_factor)?;
let res2 = prf(&input[..], &salt.as_bytes());
assert_eq!(res, res2);
Ok(())
}
#[test]
fn oprf_inversion_unsalted() {
let mut rng = OsRng;
let mut input = vec![0u8; 64];
rng.fill_bytes(&mut input);
let OprfClientBytes {
alpha,
blinding_factor,
} = generate_oprf1::<_, RistrettoPoint>(&input, None, &mut rng).unwrap();
let res = generate_oprf3::<RistrettoPoint>(&input, alpha, &blinding_factor).unwrap();
let (hashed_input, _) = Hkdf::<Sha256>::extract(None, &input);
let mut curve_input: Vec<u8> = Vec::new();
curve_input.extend_from_slice(&hashed_input);
curve_input.extend_from_slice(&[0u8; 32]);
let point = RistrettoPoint::hash_from_bytes::<sha2::Sha512>(&curve_input);
let mut ikm: Vec<u8> = Vec::new();
ikm.extend_from_slice(&point.to_bytes());
ikm.extend_from_slice(&input);
let (prk, _) = Hkdf::<Sha256>::extract(None, &ikm);
assert_eq!(res, prk);
}
}
+202
View File
@@ -0,0 +1,202 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
use crate::errors::{utils::check_slice_size, InternalPakeError, PakeError};
use aead::{Aead, NewAead};
use generic_array::{typenum::Unsigned, GenericArray};
use hmac::{Hmac, Mac};
use rand_core::{CryptoRng, RngCore};
use sha2::{Digest, Sha256};
/// This trait encapsulates an encryption scheme that satisfies random-key robustness (RKR), which is implemented
/// through encrypt-then-HMAC -- see Section 3.1.1 of
/// https://www.ietf.org/id/draft-krawczyk-cfrg-opaque-03.txt
/// We require an Aead implementation with a 32-bit key size, since we
/// will derive the symmetric key from pw using Sha256
pub trait RKRCipher: Sized {
/// The requirement of KeySize = U32 is so that we can use a 32-bit hash
/// for key derivation form the user's password
type AEAD: NewAead<KeySize = <Sha256 as Digest>::OutputSize> + Aead;
// Required members
fn new(
aead_output: Vec<u8>,
hmac: &GenericArray<u8, <Sha256 as Digest>::OutputSize>,
nonce: &GenericArray<u8, <Self::AEAD as Aead>::NonceSize>,
) -> Self;
fn aead_output(&self) -> &Vec<u8>;
fn hmac(&self) -> &GenericArray<u8, <Sha256 as Digest>::OutputSize>;
fn nonce(&self) -> &GenericArray<u8, <Self::AEAD as Aead>::NonceSize>;
fn to_bytes(&self) -> Vec<u8>;
// Provided members for enc / dec
fn key_len() -> usize {
<Self::AEAD as NewAead>::KeySize::to_usize()
}
fn nonce_size() -> usize {
<Self::AEAD as Aead>::NonceSize::to_usize()
}
fn hmac_size() -> usize {
<Sha256 as Digest>::OutputSize::to_usize()
}
/// This estimates the size of the ciphertext once we encode —very specifically—
/// the payload we have planned for the protocol's env_u
fn ciphertest_size() -> usize {
Self::key_len() + <Self::AEAD as Aead>::TagSize::to_usize() + Self::hmac_size()
}
fn rkr_with_nonce_size() -> usize {
Self::ciphertest_size() + Self::nonce_size()
}
/// The format of the output ciphertext here is:
/// encryption_output | tag | hmac | nonce
/// variable length | AEAD_TAG_SIZE bytes | HMAC_SIZE bytes | NONCE_SIZE bytes
fn from_bytes(bytes: &[u8]) -> Result<Self, InternalPakeError> {
let checked_bytes = check_slice_size(&bytes[..], Self::rkr_with_nonce_size(), "bytes")?;
let nonce_start = bytes.len() - Self::nonce_size();
let hmac_start = nonce_start - Self::hmac_size();
Ok(<Self as RKRCipher>::new(
bytes[..hmac_start].to_vec(),
GenericArray::from_slice(&checked_bytes[hmac_start..nonce_start]),
GenericArray::from_slice(&checked_bytes[nonce_start..]),
))
}
/// Encrypt with AEAD. Note that this encryption scheme needs to satisfy "random-key robustness" (RKR).
fn encrypt<R: RngCore + CryptoRng>(
encryption_key: &[u8],
hmac_key: &[u8],
plaintext: &[u8],
aad: &[u8],
rng: &mut R,
) -> Result<Self, PakeError> {
let mut nonce = vec![0u8; Self::nonce_size()];
rng.fill_bytes(&mut nonce);
let gen_nonce = GenericArray::from_slice(&nonce[..]);
let ciphertext = <Self::AEAD as NewAead>::new(*GenericArray::from_slice(&encryption_key))
.encrypt(
GenericArray::from_slice(&nonce),
aead::Payload {
msg: &plaintext,
aad: &aad,
},
)
.map_err(|_| PakeError::EncryptionError)?;
let mut mac =
Hmac::<Sha256>::new_varkey(&hmac_key).map_err(|_| InternalPakeError::HmacError)?;
mac.input(&ciphertext);
Ok(<Self as RKRCipher>::new(
ciphertext,
&mac.result().code(),
gen_nonce,
))
}
fn decrypt(
&self,
encryption_key: &[u8],
hmac_key: &[u8],
aad: &[u8],
) -> Result<Vec<u8>, PakeError> {
let mut mac =
Hmac::<Sha256>::new_varkey(&hmac_key).map_err(|_| InternalPakeError::HmacError)?;
mac.input(self.aead_output());
if mac.verify(self.hmac()).is_err() {
return Err(PakeError::DecryptionHmacError);
}
Aead::decrypt(
&<Self::AEAD as NewAead>::new(*GenericArray::from_slice(&encryption_key)),
self.nonce(),
aead::Payload {
msg: self.aead_output(),
aad: &aad,
},
)
.map_err(|_| PakeError::DecryptionError)
}
}
/// This struct is a straightforward instantiation of the trait separating the
/// three components in Vecs
pub struct RKRCiphertext<T> {
aead_choice: std::marker::PhantomData<T>,
aead_output: Vec<u8>,
hmac: Vec<u8>,
nonce: Vec<u8>,
}
impl<T: NewAead<KeySize = <Sha256 as Digest>::OutputSize> + Aead> RKRCipher for RKRCiphertext<T> {
type AEAD = T;
fn new(
aead_output: Vec<u8>,
hmac: &GenericArray<u8, <Sha256 as Digest>::OutputSize>,
nonce: &GenericArray<u8, <Self::AEAD as Aead>::NonceSize>,
) -> Self {
Self {
aead_choice: std::marker::PhantomData,
aead_output,
hmac: hmac.to_vec(),
nonce: nonce.to_vec(),
}
}
fn aead_output(&self) -> &Vec<u8> {
&self.aead_output
}
fn to_bytes(&self) -> Vec<u8> {
[&self.aead_output[..], &self.hmac[..], &self.nonce[..]].concat()
}
fn hmac(&self) -> &GenericArray<u8, <Sha256 as Digest>::OutputSize> {
GenericArray::from_slice(&self.hmac[..])
}
fn nonce(&self) -> &GenericArray<u8, <T as Aead>::NonceSize> {
GenericArray::from_slice(&self.nonce[..])
}
}
#[cfg(test)]
mod tests {
use super::*;
use chacha20poly1305::ChaCha20Poly1305;
use rand_core::OsRng;
#[test]
fn encrypt_and_decrypt() {
let mut rng = OsRng;
let mut encryption_key = [0u8; 32];
rng.fill_bytes(&mut encryption_key);
let mut hmac_key = [0u8; 32];
rng.fill_bytes(&mut hmac_key);
let mut msg = [0u8; 100];
rng.fill_bytes(&mut msg);
let ciphertext = RKRCiphertext::<ChaCha20Poly1305>::encrypt(
&encryption_key,
&hmac_key,
&msg,
b"",
&mut rng,
)
.unwrap();
let decrypted = ciphertext.decrypt(&encryption_key, &hmac_key, b"").unwrap();
assert_eq!(&msg.to_vec(), &decrypted);
}
}
+63
View File
@@ -0,0 +1,63 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
use rand_core::{CryptoRng, Error, RngCore};
use std::cmp::min;
/// A simple implementation of `RngCore` for testing purposes.
///
/// This generates a cyclic sequence (i.e. cycles over an initial buffer)
///
///
#[derive(Debug, Clone)]
pub struct CycleRng {
v: Vec<u8>,
}
impl CycleRng {
/// Create a `CycleRng`, yielding a sequence starting with
/// `initial` and looping thereafter
pub fn new(initial: Vec<u8>) -> Self {
CycleRng { v: initial }
}
}
fn rotate_left<T>(data: &mut [T], steps: usize) {
if data.is_empty() {
return;
}
let steps = steps % data.len();
data[..steps].reverse();
data[steps..].reverse();
data.reverse();
}
impl RngCore for CycleRng {
fn next_u32(&mut self) -> u32 {
unimplemented!()
}
#[inline]
fn next_u64(&mut self) -> u64 {
unimplemented!()
}
#[inline]
fn fill_bytes(&mut self, dest: &mut [u8]) {
let len = min(self.v.len(), dest.len());
(&mut dest[..len]).copy_from_slice(&self.v[..len]);
rotate_left(&mut self.v, len);
}
#[inline]
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
self.fill_bytes(dest);
Ok(())
}
}
// This is meant for testing only
impl CryptoRng for CycleRng {}
+8
View File
@@ -0,0 +1,8 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
pub mod mock_rng;
mod opaque_ke_test;
mod serialization;
+577
View File
@@ -0,0 +1,577 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
use crate::{
errors::*,
group::Group,
key_exchange::NONCE_LEN,
keypair::{Key, KeyPair, SignalKeyPair},
opaque::*,
tests::mock_rng::CycleRng,
};
use aes_gcm::Aes256Gcm;
use curve25519_dalek::edwards::EdwardsPoint;
use rand_core::{OsRng, RngCore};
use serde_json::Value;
use std::convert::TryFrom;
// Tests
// =====
pub struct TestVectorParameters {
pub client_s_pk: Vec<u8>,
pub client_s_sk: Vec<u8>,
pub client_e_pk: Vec<u8>,
pub client_e_sk: Vec<u8>,
pub server_s_pk: Vec<u8>,
pub server_s_sk: Vec<u8>,
pub server_e_pk: Vec<u8>,
pub server_e_sk: Vec<u8>,
pub password: Vec<u8>,
pub blinding_factor_raw: Vec<u8>,
pub blinding_factor: Vec<u8>,
pub pepper: Vec<u8>,
pub oprf_key: Vec<u8>,
pub envelope_nonce: Vec<u8>,
pub client_nonce: Vec<u8>,
pub server_nonce: Vec<u8>,
pub r1: Vec<u8>,
pub r2: Vec<u8>,
pub r3: Vec<u8>,
pub l1: Vec<u8>,
pub l2: Vec<u8>,
pub l3: Vec<u8>,
client_registration_state: Vec<u8>,
server_registration_state: Vec<u8>,
client_login_state: Vec<u8>,
server_login_state: Vec<u8>,
pub password_file: Vec<u8>,
pub opaque_key: Vec<u8>,
pub shared_secret: Vec<u8>,
}
static TEST_VECTOR: &str = r#"
{
"client_s_pk": "f7b150789db3322c8c7b8c4a10ce42baa5ee846de83eaf04c17ffbd0d9e5cd60",
"client_s_sk": "601ed276a42ec5795b3471f1a64e312f192e17ff252ce6053c8ecaf210138273",
"client_e_pk": "57260d4e231035f0f3e1fb836fe5d9ddb498c956cacb5fab1d6b287e1422376c",
"client_e_sk": "e89d0fa4e387a9bd7c26466704ec30e62f58892bf3dfd1fd25133be52f34ea68",
"server_s_pk": "a2b4e12d0621ebfb2631e00f5c872ab749e1a33915f16fb11203658b2189cc5e",
"server_s_sk": "90b6ca2ea8a37306060c7cd0998d4cdae59e972af7760312f7cf77099e78f940",
"server_e_pk": "64ce4a453eb8c27b1d81f6acdc01d36d3ae6cea506432e9509917b195ad90073",
"server_e_sk": "883148cc1ba70acb1eb909d99e09493b5d4b3fe6b12c75e2f5aeea6c5d4b267f",
"password": "70617373776f7264",
"blinding_factor_raw": "b85e0df2ad0495771edf09a04b1073045e6472e2f86a41e9bab3143ebfb8eb08a3462503eb3750bf006dc82c93b37e07cdf3768018c22b431cf5146a9caeda1c",
"blinding_factor": "fac0ed1c38bc8945a91dc4d944af22c466cbffc24fc3d97b8a91798d1ec8b60f",
"pepper": "706570706572",
"oprf_key": "d5cedff72509af4702a985bb31af8dbe88d72c4eee13a09e3f52a76766fa6f0b",
"envelope_nonce": "c87e44792a9dfd8858db676e",
"client_nonce": "1f023acc6155a06166ee7e5b7ef0360277ed5da3a46adcd4a0a5bce938a67a23",
"server_nonce": "d448cb1f58c38605fc29069ac688ec9c667c99d0316b38cd1b2609c1bc14aa90",
"r1": "e46efe7d673805b6135a5293ecab13082b322c45f029595efa4b8d1d53ccd897",
"r2": "a2a3df89cf85976c4aa5add752736419f728805722571a9646983587ce4c55fb",
"r3": "374c49768e4399d4cd46e8b3bc2050e2f6737e3a2f8aee6fddc82e117f340f79a7f10c84445657c6bb4940bd02bc08ca0f107618d810ec94639e8ae43af48ab66f1f75e8bbc169eed0035e347310978bc87e44792a9dfd8858db676ef7b150789db3322c8c7b8c4a10ce42baa5ee846de83eaf04c17ffbd0d9e5cd60",
"l1": "e46efe7d673805b6135a5293ecab13082b322c45f029595efa4b8d1d53ccd8971f023acc6155a06166ee7e5b7ef0360277ed5da3a46adcd4a0a5bce938a67a2357260d4e231035f0f3e1fb836fe5d9ddb498c956cacb5fab1d6b287e1422376c",
"l2": "a2a3df89cf85976c4aa5add752736419f728805722571a9646983587ce4c55fb374c49768e4399d4cd46e8b3bc2050e2f6737e3a2f8aee6fddc82e117f340f79a7f10c84445657c6bb4940bd02bc08ca0f107618d810ec94639e8ae43af48ab66f1f75e8bbc169eed0035e347310978bc87e44792a9dfd8858db676e883148cc1ba70acb1eb909d99e09493b5d4b3fe6b12c75e2f5aeea6c5d4b267f64ce4a453eb8c27b1d81f6acdc01d36d3ae6cea506432e9509917b195ad90073d81a1104fbd599ef56228bdbe9bf7be4a38ae907a8717ca0883b9d69b2efc529",
"l3": "a01332643e8aa7113f6f160205a9b3bd0705f3b33d8e4ea8eab9eae6685a6adb",
"client_registration_state": "fac0ed1c38bc8945a91dc4d944af22c466cbffc24fc3d97b8a91798d1ec8b60f70617373776f7264",
"client_login_state": "fac0ed1c38bc8945a91dc4d944af22c466cbffc24fc3d97b8a91798d1ec8b60fe89d0fa4e387a9bd7c26466704ec30e62f58892bf3dfd1fd25133be52f34ea681f023acc6155a06166ee7e5b7ef0360277ed5da3a46adcd4a0a5bce938a67a23dd1a7c2b4e9f9be94bd36f3b6c7f23aa9f1e6b3fda9030412a918d1288b4af1970617373776f7264",
"server_registration_state": "d5cedff72509af4702a985bb31af8dbe88d72c4eee13a09e3f52a76766fa6f0b",
"server_login_state": "809f95143f8f7fc1d0b42f578a83f714f58cfd96d9499aacee730ad296b37b19c18c903396e85da607d02542d4d07456e5357ff2e2eade3aaa42e532d4e9364f66317ab0460307e33d6151e99c7406f2fa1d309f507b46e43f732924d1dc8d0d",
"password_file": "d5cedff72509af4702a985bb31af8dbe88d72c4eee13a09e3f52a76766fa6f0bf7b150789db3322c8c7b8c4a10ce42baa5ee846de83eaf04c17ffbd0d9e5cd60374c49768e4399d4cd46e8b3bc2050e2f6737e3a2f8aee6fddc82e117f340f79a7f10c84445657c6bb4940bd02bc08ca0f107618d810ec94639e8ae43af48ab66f1f75e8bbc169eed0035e347310978bc87e44792a9dfd8858db676e",
"opaque_key": "682f2868a3e1460fed5a16767bd8778c33b4aecac6607270f848aa61c95a1a68",
"shared_secret": "66317ab0460307e33d6151e99c7406f2fa1d309f507b46e43f732924d1dc8d0d"
}
"#;
fn decode(values: &Value, key: &str) -> Option<Vec<u8>> {
values[key]
.as_str()
.and_then(|s| hex::decode(&s.to_string()).ok())
}
fn populate_test_vectors(values: &Value) -> TestVectorParameters {
TestVectorParameters {
client_s_pk: decode(&values, "client_s_pk").unwrap(),
client_s_sk: decode(&values, "client_s_sk").unwrap(),
client_e_pk: decode(&values, "client_e_pk").unwrap(),
client_e_sk: decode(&values, "client_e_sk").unwrap(),
server_s_pk: decode(&values, "server_s_pk").unwrap(),
server_s_sk: decode(&values, "server_s_sk").unwrap(),
server_e_pk: decode(&values, "server_e_pk").unwrap(),
server_e_sk: decode(&values, "server_e_sk").unwrap(),
password: decode(&values, "password").unwrap(),
blinding_factor_raw: decode(&values, "blinding_factor_raw").unwrap(),
blinding_factor: decode(&values, "blinding_factor").unwrap(),
pepper: decode(&values, "pepper").unwrap(),
oprf_key: decode(&values, "oprf_key").unwrap(),
envelope_nonce: decode(&values, "envelope_nonce").unwrap(),
client_nonce: decode(&values, "client_nonce").unwrap(),
server_nonce: decode(&values, "server_nonce").unwrap(),
r1: decode(&values, "r1").unwrap(),
r2: decode(&values, "r2").unwrap(),
r3: decode(&values, "r3").unwrap(),
l1: decode(&values, "l1").unwrap(),
l2: decode(&values, "l2").unwrap(),
l3: decode(&values, "l3").unwrap(),
client_registration_state: decode(&values, "client_registration_state").unwrap(),
client_login_state: decode(&values, "client_login_state").unwrap(),
server_registration_state: decode(&values, "server_registration_state").unwrap(),
server_login_state: decode(&values, "server_login_state").unwrap(),
password_file: decode(&values, "password_file").unwrap(),
opaque_key: decode(&values, "opaque_key").unwrap(),
shared_secret: decode(&values, "shared_secret").unwrap(),
}
}
fn stringify_test_vectors(p: &TestVectorParameters) -> String {
let mut s = String::new();
s.push_str("{\n");
s.push_str(format!("\"client_s_pk\": \"{}\",\n", hex::encode(&p.client_s_pk)).as_str());
s.push_str(format!("\"client_s_sk\": \"{}\",\n", hex::encode(&p.client_s_sk)).as_str());
s.push_str(format!("\"client_e_pk\": \"{}\",\n", hex::encode(&p.client_e_pk)).as_str());
s.push_str(format!("\"client_e_sk\": \"{}\",\n", hex::encode(&p.client_e_sk)).as_str());
s.push_str(format!("\"server_s_pk\": \"{}\",\n", hex::encode(&p.server_s_pk)).as_str());
s.push_str(format!("\"server_s_sk\": \"{}\",\n", hex::encode(&p.server_s_sk)).as_str());
s.push_str(format!("\"server_e_pk\": \"{}\",\n", hex::encode(&p.server_e_pk)).as_str());
s.push_str(format!("\"server_e_sk\": \"{}\",\n", hex::encode(&p.server_e_sk)).as_str());
s.push_str(format!("\"password\": \"{}\",\n", hex::encode(&p.password)).as_str());
s.push_str(
format!(
"\"blinding_factor_raw\": \"{}\",\n",
hex::encode(&p.blinding_factor_raw)
)
.as_str(),
);
s.push_str(
format!(
"\"blinding_factor\": \"{}\",\n",
hex::encode(&p.blinding_factor)
)
.as_str(),
);
s.push_str(format!("\"pepper\": \"{}\",\n", hex::encode(&p.pepper)).as_str());
s.push_str(format!("\"oprf_key\": \"{}\",\n", hex::encode(&p.oprf_key)).as_str());
s.push_str(
format!(
"\"envelope_nonce\": \"{}\",\n",
hex::encode(&p.envelope_nonce)
)
.as_str(),
);
s.push_str(format!("\"client_nonce\": \"{}\",\n", hex::encode(&p.client_nonce)).as_str());
s.push_str(format!("\"server_nonce\": \"{}\",\n", hex::encode(&p.server_nonce)).as_str());
s.push_str(format!("\"r1\": \"{}\",\n", hex::encode(&p.r1)).as_str());
s.push_str(format!("\"r2\": \"{}\",\n", hex::encode(&p.r2)).as_str());
s.push_str(format!("\"r3\": \"{}\",\n", hex::encode(&p.r3)).as_str());
s.push_str(format!("\"l1\": \"{}\",\n", hex::encode(&p.l1)).as_str());
s.push_str(format!("\"l2\": \"{}\",\n", hex::encode(&p.l2)).as_str());
s.push_str(format!("\"l3\": \"{}\",\n", hex::encode(&p.l3)).as_str());
s.push_str(
format!(
"\"client_registration_state\": \"{}\",\n",
hex::encode(&p.client_registration_state)
)
.as_str(),
);
s.push_str(
format!(
"\"client_login_state\": \"{}\",\n",
hex::encode(&p.client_login_state)
)
.as_str(),
);
s.push_str(
format!(
"\"server_registration_state\": \"{}\",\n",
hex::encode(&p.server_registration_state)
)
.as_str(),
);
s.push_str(
format!(
"\"server_login_state\": \"{}\",\n",
hex::encode(&p.server_login_state)
)
.as_str(),
);
s.push_str(
format!(
"\"password_file\": \"{}\",\n",
hex::encode(&p.password_file)
)
.as_str(),
);
s.push_str(format!("\"opaque_key\": \"{}\",\n", hex::encode(&p.opaque_key)).as_str());
s.push_str(format!("\"shared_secret\": \"{}\"\n", hex::encode(&p.shared_secret)).as_str());
s.push_str("}\n");
s
}
fn generate_parameters() -> TestVectorParameters {
let mut rng = OsRng;
// Inputs
let server_s_kp = SignalKeyPair::generate_random(&mut rng).unwrap();
let server_e_kp = SignalKeyPair::generate_random(&mut rng).unwrap();
let client_s_kp = SignalKeyPair::generate_random(&mut rng).unwrap();
let client_e_kp = SignalKeyPair::generate_random(&mut rng).unwrap();
let password = b"password";
let pepper = b"pepper";
let mut blinding_factor_raw = [0u8; 64];
rng.fill_bytes(&mut blinding_factor_raw);
let mut oprf_key_raw = [0u8; 32];
rng.fill_bytes(&mut oprf_key_raw);
let mut envelope_nonce = [0u8; 12];
rng.fill_bytes(&mut envelope_nonce);
let mut client_nonce = [0u8; NONCE_LEN];
rng.fill_bytes(&mut client_nonce);
let mut server_nonce = [0u8; NONCE_LEN];
rng.fill_bytes(&mut server_nonce);
let mut blinding_factor_registration_rng = CycleRng::new(blinding_factor_raw.to_vec());
let (r1, client_registration) = ClientRegistration::<Aes256Gcm, EdwardsPoint>::start(
password,
Some(pepper),
&mut blinding_factor_registration_rng,
)
.unwrap();
let r1_bytes = r1.to_bytes().to_vec();
let blinding_factor_bytes = client_registration.blinding_factor.to_bytes();
let client_registration_state = client_registration.to_bytes().to_vec();
let mut oprf_key_rng = CycleRng::new(oprf_key_raw.to_vec());
let (r2, server_registration) =
ServerRegistration::<Aes256Gcm, EdwardsPoint, SignalKeyPair>::start(r1, &mut oprf_key_rng)
.unwrap();
let r2_bytes = r2.to_bytes().to_vec();
let oprf_key = server_registration.oprf_key;
let oprf_key_bytes = EdwardsPoint::scalar_as_bytes(&oprf_key);
let server_registration_state = server_registration.to_bytes().to_vec();
let mut client_s_sk_and_nonce: Vec<u8> = Vec::new();
client_s_sk_and_nonce.extend_from_slice(&client_s_kp.private());
client_s_sk_and_nonce.extend_from_slice(&envelope_nonce);
let mut finish_registration_rng = CycleRng::new(client_s_sk_and_nonce);
let (r3, opaque_key_registration) = client_registration
.finish::<_, SignalKeyPair>(r2, server_s_kp.public(), &mut finish_registration_rng)
.unwrap();
let r3_bytes = r3.to_bytes().to_vec();
let password_file = server_registration.finish(r3).unwrap();
let password_file_bytes = password_file.to_bytes();
let mut client_login_start: Vec<u8> = Vec::new();
client_login_start.extend_from_slice(&blinding_factor_raw);
client_login_start.extend_from_slice(&client_e_kp.private());
client_login_start.extend_from_slice(&client_nonce);
let mut client_login_start_rng = CycleRng::new(client_login_start);
let (l1, client_login) = ClientLogin::<Aes256Gcm, EdwardsPoint, SignalKeyPair>::start(
password,
Some(pepper),
&mut client_login_start_rng,
)
.unwrap();
let l1_bytes = l1.to_bytes().to_vec();
let client_login_state = client_login.to_bytes().to_vec();
let mut server_e_sk_rng = CycleRng::new(server_e_kp.private().to_vec());
let (l2, server_login) = ServerLogin::start(
password_file,
server_s_kp.private(),
l1,
&mut server_e_sk_rng,
)
.unwrap();
let l2_bytes = l2.to_bytes().to_vec();
let server_login_state = server_login.to_bytes().to_vec();
let mut client_e_sk_rng = CycleRng::new(client_e_kp.private().to_vec());
let (l3, client_shared_secret, _opaque_key_login) = client_login
.finish(l2, server_s_kp.public(), &mut client_e_sk_rng)
.unwrap();
let l3_bytes = l3.to_bytes().to_vec();
TestVectorParameters {
client_s_pk: client_s_kp.public().to_vec(),
client_s_sk: client_s_kp.private().to_vec(),
client_e_pk: client_e_kp.public().to_vec(),
client_e_sk: client_e_kp.private().to_vec(),
server_s_pk: server_s_kp.public().to_vec(),
server_s_sk: server_s_kp.private().to_vec(),
server_e_pk: server_e_kp.public().to_vec(),
server_e_sk: server_e_kp.private().to_vec(),
password: password.to_vec(),
blinding_factor_raw: blinding_factor_raw.to_vec(),
blinding_factor: blinding_factor_bytes.to_vec(),
pepper: pepper.to_vec(),
oprf_key: oprf_key_bytes.to_vec(),
envelope_nonce: envelope_nonce.to_vec(),
client_nonce: client_nonce.to_vec(),
server_nonce: server_nonce.to_vec(),
r1: r1_bytes,
r2: r2_bytes,
r3: r3_bytes,
l1: l1_bytes,
l2: l2_bytes,
l3: l3_bytes,
password_file: password_file_bytes,
client_registration_state,
server_registration_state,
client_login_state,
server_login_state,
shared_secret: client_shared_secret,
opaque_key: opaque_key_registration.to_vec(),
}
}
#[test]
fn generate_test_vectors() {
let parameters = generate_parameters();
println!("{}", stringify_test_vectors(&parameters));
}
#[test]
fn test_r1() -> Result<(), PakeError> {
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
let mut blinding_factor_rng = CycleRng::new(parameters.blinding_factor_raw);
let (r1, client_registration) = ClientRegistration::<Aes256Gcm, EdwardsPoint>::start(
&parameters.password,
Some(&parameters.pepper),
&mut blinding_factor_rng,
)
.unwrap();
assert_eq!(hex::encode(&parameters.r1), hex::encode(r1.to_bytes()));
assert_eq!(
hex::encode(&parameters.client_registration_state),
hex::encode(client_registration.to_bytes())
);
Ok(())
}
#[test]
fn test_r2() -> Result<(), PakeError> {
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
let mut oprf_key_rng = CycleRng::new(parameters.oprf_key);
let (r2, server_registration) =
ServerRegistration::<Aes256Gcm, EdwardsPoint, SignalKeyPair>::start(
RegisterFirstMessage::try_from(&parameters.r1[..]).unwrap(),
&mut oprf_key_rng,
)
.unwrap();
assert_eq!(hex::encode(parameters.r2), hex::encode(r2.to_bytes()));
assert_eq!(
hex::encode(&parameters.server_registration_state),
hex::encode(server_registration.to_bytes())
);
Ok(())
}
#[test]
fn test_r3() -> Result<(), PakeError> {
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
let client_s_sk_and_nonce: Vec<u8> =
[parameters.client_s_sk, parameters.envelope_nonce].concat();
let mut finish_registration_rng = CycleRng::new(client_s_sk_and_nonce);
let (r3, opaque_key_registration) = ClientRegistration::<Aes256Gcm, EdwardsPoint>::try_from(
&parameters.client_registration_state[..],
)
.unwrap()
.finish::<CycleRng, SignalKeyPair>(
RegisterSecondMessage::try_from(&parameters.r2[..]).unwrap(),
&Key::try_from(parameters.server_s_pk).unwrap(),
&mut finish_registration_rng,
)
.unwrap();
assert_eq!(hex::encode(parameters.r3), hex::encode(r3.to_bytes()));
assert_eq!(
hex::encode(parameters.opaque_key),
hex::encode(opaque_key_registration.to_vec())
);
Ok(())
}
#[test]
fn test_password_file() -> Result<(), PakeError> {
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
let server_registration =
ServerRegistration::<Aes256Gcm, EdwardsPoint, SignalKeyPair>::try_from(
&parameters.server_registration_state[..],
)
.unwrap();
let password_file = server_registration
.finish(RegisterThirdMessage::try_from(&parameters.r3[..]).unwrap())
.unwrap();
assert_eq!(
hex::encode(parameters.password_file),
hex::encode(password_file.to_bytes())
);
Ok(())
}
#[test]
fn test_l1() -> Result<(), PakeError> {
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
let client_login_start = [
parameters.blinding_factor_raw,
parameters.client_e_sk,
parameters.client_nonce,
]
.concat();
let mut client_login_start_rng = CycleRng::new(client_login_start);
let (l1, client_login) = ClientLogin::<Aes256Gcm, EdwardsPoint, SignalKeyPair>::start(
&parameters.password,
Some(&parameters.pepper),
&mut client_login_start_rng,
)
.unwrap();
assert_eq!(hex::encode(&parameters.l1), hex::encode(l1.to_bytes()));
assert_eq!(
hex::encode(&parameters.client_login_state),
hex::encode(client_login.to_bytes())
);
Ok(())
}
#[test]
fn test_l2() -> Result<(), PakeError> {
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
let mut server_e_sk_rng = CycleRng::new(parameters.server_e_sk);
let (l2, server_login) = ServerLogin::start::<_, Aes256Gcm, _, SignalKeyPair>(
ServerRegistration::try_from(&parameters.password_file[..]).unwrap(),
&Key::try_from(parameters.server_s_sk).unwrap(),
LoginFirstMessage::<EdwardsPoint>::try_from(&parameters.l1[..]).unwrap(),
&mut server_e_sk_rng,
)
.unwrap();
assert_eq!(hex::encode(&parameters.l2), hex::encode(l2.to_bytes()));
assert_eq!(
hex::encode(&parameters.server_login_state),
hex::encode(server_login.to_bytes())
);
Ok(())
}
#[test]
fn test_l3() -> Result<(), PakeError> {
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
let mut client_e_sk_rng = CycleRng::new(parameters.client_e_sk.to_vec());
let (l3, shared_secret, opaque_key_login) =
ClientLogin::<Aes256Gcm, EdwardsPoint, SignalKeyPair>::try_from(
&parameters.client_login_state[..],
)
.unwrap()
.finish(
LoginSecondMessage::<Aes256Gcm, EdwardsPoint>::try_from(&parameters.l2[..]).unwrap(),
&Key::try_from(parameters.server_s_pk)?,
&mut client_e_sk_rng,
)
.unwrap();
assert_eq!(
hex::encode(&parameters.shared_secret),
hex::encode(&shared_secret)
);
assert_eq!(hex::encode(&parameters.l3), hex::encode(l3.to_bytes()));
assert_eq!(
hex::encode(&parameters.opaque_key),
hex::encode(opaque_key_login)
);
Ok(())
}
#[test]
fn test_server_login_finish() -> Result<(), ProtocolError> {
let parameters = populate_test_vectors(&serde_json::from_str(TEST_VECTOR).unwrap());
let shared_secret = ServerLogin::try_from(&parameters.server_login_state[..])
.unwrap()
.finish(LoginThirdMessage::try_from(&parameters.l3[..])?)
.unwrap();
assert_eq!(
hex::encode(parameters.shared_secret),
hex::encode(shared_secret)
);
Ok(())
}
fn test_complete_flow(
registration_password: &[u8],
login_password: &[u8],
) -> Result<(), ProtocolError> {
let mut client_rng = OsRng;
let mut server_rng = OsRng;
let server_kp = SignalKeyPair::generate_random(&mut server_rng)?;
let (register_m1, client_state) = ClientRegistration::<Aes256Gcm, EdwardsPoint>::start(
registration_password,
None,
&mut client_rng,
)?;
let (register_m2, server_state) =
ServerRegistration::<Aes256Gcm, EdwardsPoint, SignalKeyPair>::start(
register_m1,
&mut server_rng,
)?;
let (register_m3, registration_opaque_key) =
client_state.finish(register_m2, server_kp.public(), &mut client_rng)?;
let p_file = server_state.finish(register_m3)?;
let (login_m1, client_login_state) =
ClientLogin::<Aes256Gcm, EdwardsPoint, SignalKeyPair>::start(
login_password,
None,
&mut client_rng,
)?;
let (login_m2, server_login_state) =
ServerLogin::start(p_file, &server_kp.private(), login_m1, &mut server_rng)?;
let client_login_result =
client_login_state.finish(login_m2, &server_kp.public(), &mut client_rng);
if hex::encode(registration_password) == hex::encode(login_password) {
let (login_m3, client_shared_secret, login_opaque_key) = client_login_result?;
let server_shared_secret = server_login_state.finish(login_m3)?;
assert_eq!(
hex::encode(server_shared_secret),
hex::encode(client_shared_secret)
);
assert_eq!(
hex::encode(registration_opaque_key),
hex::encode(login_opaque_key)
);
} else {
let res = match client_login_result {
Err(ProtocolError::VerificationError(PakeError::InvalidLoginError)) => true,
_ => false,
};
assert!(res);
}
Ok(())
}
#[test]
fn test_complete_flow_success() -> Result<(), ProtocolError> {
test_complete_flow(b"good password", b"good password")
}
#[test]
fn test_complete_flow_fail() -> Result<(), ProtocolError> {
test_complete_flow(b"good password", b"bad password")
}
+124
View File
@@ -0,0 +1,124 @@
// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.
use crate::{
group::Group,
keypair::{KeyPair, SignalKeyPair, SizedBytes},
opaque::*,
rkr_encryption::{RKRCipher as _, RKRCiphertext},
};
use curve25519_dalek::ristretto::RistrettoPoint;
use chacha20poly1305::ChaCha20Poly1305;
use rand_core::{OsRng, RngCore};
use std::convert::TryFrom;
fn random_ristretto_point() -> RistrettoPoint {
let mut rng = OsRng;
let mut bits = [0u8; 64];
rng.fill_bytes(&mut bits);
RistrettoPoint::hash_from_bytes::<sha2::Sha512>(&bits)
}
#[test]
fn client_registration_roundtrip() {
let pw = b"hunter2";
let mut rng = OsRng;
let sc = <RistrettoPoint as Group>::random_scalar(&mut rng);
// serialization order: scalar, password
let mut bytes: Vec<u8> = vec![];
bytes.extend_from_slice(sc.as_bytes());
bytes.extend_from_slice(pw);
let reg = ClientRegistration::<ChaCha20Poly1305, RistrettoPoint>::try_from(&bytes[..]).unwrap();
let reg_bytes = reg.to_bytes();
assert_eq!(reg_bytes, bytes);
}
#[test]
fn server_registration_roundtrip() {
// If we don't have envelope and client_pk, the server registration just
// contains the prf key
let mut rng = OsRng;
let sc = <RistrettoPoint as Group>::random_scalar(&mut rng);
let mut oprf_bytes: Vec<u8> = vec![];
oprf_bytes.extend_from_slice(sc.as_bytes());
let reg = ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, SignalKeyPair>::try_from(
&oprf_bytes[..],
)
.unwrap();
let reg_bytes = reg.to_bytes();
assert_eq!(reg_bytes, oprf_bytes);
// If we do have envelope and client pk, the server registration contains
// the whole kit
let rkr_size = RKRCiphertext::<ChaCha20Poly1305>::rkr_with_nonce_size();
let mut mock_rkr_bytes = vec![0u8; rkr_size];
rng.fill_bytes(&mut mock_rkr_bytes);
println!("{}", mock_rkr_bytes.len());
let mock_client_kp = SignalKeyPair::generate_random(&mut rng).unwrap();
// serialization order: scalar, public key, envelope
let mut bytes = Vec::<u8>::new();
bytes.extend_from_slice(sc.as_bytes());
bytes.extend_from_slice(&mock_client_kp.public().to_arr());
bytes.extend_from_slice(&mock_rkr_bytes);
let reg =
ServerRegistration::<ChaCha20Poly1305, RistrettoPoint, SignalKeyPair>::try_from(&bytes[..])
.unwrap();
let reg_bytes = reg.to_bytes();
assert_eq!(reg_bytes, bytes);
}
#[test]
fn register_first_message_roundtrip() {
let pt = random_ristretto_point();
let pt_bytes = pt.to_bytes();
let r1 = RegisterFirstMessage::<RistrettoPoint>::try_from(pt_bytes.as_slice()).unwrap();
let r1_bytes = r1.to_bytes();
assert_eq!(pt_bytes, r1_bytes);
}
#[test]
fn register_second_message_roundtrip() {
let pt = random_ristretto_point();
let pt_bytes = pt.to_bytes();
let message = pt_bytes.to_vec();
let r2 = RegisterSecondMessage::<RistrettoPoint>::try_from(&message[..]).unwrap();
let r2_bytes = r2.to_bytes();
assert_eq!(message, r2_bytes);
}
#[test]
fn register_third_message_roundtrip() {
let mut rng = OsRng;
let skp = SignalKeyPair::generate_random(&mut rng).unwrap();
let pubkey_bytes = skp.public().to_arr();
let mut encryption_key = [0u8; 32];
rng.fill_bytes(&mut encryption_key);
let mut hmac_key = [0u8; 32];
rng.fill_bytes(&mut hmac_key);
let mut msg = [0u8; 32];
rng.fill_bytes(&mut msg);
let ciphertext = RKRCiphertext::<ChaCha20Poly1305>::encrypt(
&encryption_key,
&hmac_key,
&msg,
&pubkey_bytes,
&mut rng,
)
.unwrap();
let mut message = Vec::new();
message.extend_from_slice(&ciphertext.to_bytes());
message.extend_from_slice(&pubkey_bytes);
let r3 =
RegisterThirdMessage::<ChaCha20Poly1305, SignalKeyPair>::try_from(&message[..]).unwrap();
let r3_bytes = r3.to_bytes();
assert_eq!(message, r3_bytes);
}