Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c535c0989 | ||
|
|
9c28c8597a | ||
|
|
e5619d48cd | ||
|
|
a69ad9473a | ||
|
|
f15b37fda4 | ||
|
|
27f6975136 | ||
|
|
3c4b7ce482 | ||
|
|
05427dd97d | ||
|
|
ec8f87944b |
@@ -16,7 +16,7 @@ jobs:
|
||||
- u64_backend
|
||||
- u32_backend
|
||||
toolchain:
|
||||
- nightly
|
||||
- stable
|
||||
- 1.41.0
|
||||
name: test
|
||||
steps:
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
toolchain:
|
||||
- nightly
|
||||
- stable
|
||||
- 1.41.0
|
||||
name: test simple_login command-line example
|
||||
steps:
|
||||
@@ -103,7 +103,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
toolchain:
|
||||
- nightly
|
||||
- stable
|
||||
- 1.41.0
|
||||
name: test digital_locker command-line example
|
||||
steps:
|
||||
@@ -148,11 +148,11 @@ jobs:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install nightly toolchain
|
||||
- name: Install stable toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly
|
||||
toolchain: stable
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
@@ -170,11 +170,11 @@ jobs:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install nightly toolchain
|
||||
- name: Install stable toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly
|
||||
toolchain: stable
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## 0.5.1 (July 16, 2021)
|
||||
|
||||
* Various security improvements: non-zero scalars, zeroizing on drop,
|
||||
constant-time operations, reflected value check, and adding an
|
||||
i2osp error condition
|
||||
|
||||
## 0.5.0 (March 1, 2020)
|
||||
|
||||
* Removed dependency on generic-bytes-derive package
|
||||
|
||||
Generated
+35
-1
@@ -170,6 +170,12 @@ version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6"
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
|
||||
|
||||
[[package]]
|
||||
name = "cpuid-bool"
|
||||
version = "0.1.2"
|
||||
@@ -558,11 +564,12 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-ke"
|
||||
version = "0.5.0"
|
||||
version = "0.5.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
"chacha20poly1305",
|
||||
"constant_time_eq",
|
||||
"criterion",
|
||||
"curve25519-dalek",
|
||||
"digest",
|
||||
@@ -1017,6 +1024,18 @@ dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.2.0"
|
||||
@@ -1248,3 +1267,18 @@ name = "zeroize"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "81a974bcdd357f0dca4d41677db03436324d45a4c9ed2d0b873a5a360ce41c36"
|
||||
dependencies = [
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize_derive"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2c1e130bebaeab2f23886bf9acbaca14b092408c452543c857f66399cd6dab1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
+3
-2
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "opaque-ke"
|
||||
version = "0.5.0"
|
||||
version = "0.5.1"
|
||||
repository = "https://github.com/novifinancial/opaque-ke"
|
||||
keywords = ["cryptography", "crypto", "opaque", "passwords", "authentication"]
|
||||
description = "An implementation of the OPAQUE password-authenticated key exchange protocol"
|
||||
@@ -17,6 +17,7 @@ u64_backend = ["curve25519-dalek/u64_backend"]
|
||||
u32_backend = ["curve25519-dalek/u32_backend"]
|
||||
|
||||
[dependencies]
|
||||
constant_time_eq = "0.1.5"
|
||||
curve25519-dalek = { version = "3.0.0", default-features = false, features = ["std"] }
|
||||
digest = "0.9.0"
|
||||
displaydoc = "0.1.7"
|
||||
@@ -28,7 +29,7 @@ rand = "0.8"
|
||||
scrypt = { version = "0.5.0", optional = true }
|
||||
subtle = { version = "2.3.0", default-features = false }
|
||||
thiserror = "1.0.22"
|
||||
zeroize = "1.1.1"
|
||||
zeroize = { version = "1.1.1", features = ["zeroize_derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0.35"
|
||||
|
||||
@@ -22,7 +22,7 @@ Installation
|
||||
Add the following line to the dependencies of your `Cargo.toml`:
|
||||
|
||||
```
|
||||
opaque-ke = "0.5.0"
|
||||
opaque-ke = "0.5.1"
|
||||
```
|
||||
|
||||
Resources
|
||||
|
||||
@@ -44,7 +44,7 @@ yanked = "warn"
|
||||
# The lint level for crates with security notices. Note that as of
|
||||
# 2019-12-17 there are no security notice advisories in
|
||||
# https://github.com/rustsec/advisory-db
|
||||
notice = "warn"
|
||||
notice = "deny"
|
||||
# A list of advisory IDs to ignore. Note that ignored advisories will still
|
||||
# output a note when they are encountered.
|
||||
ignore = [
|
||||
|
||||
@@ -145,7 +145,7 @@ fn open_locker(
|
||||
ClientLoginStartParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
let credential_request_bytes = client_login_start_result.message.serialize();
|
||||
let credential_request_bytes = client_login_start_result.message.serialize().unwrap();
|
||||
|
||||
// Client sends credential_request_bytes to server
|
||||
|
||||
@@ -160,7 +160,7 @@ fn open_locker(
|
||||
ServerLoginStartParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
let credential_response_bytes = server_login_start_result.message.serialize();
|
||||
let credential_response_bytes = server_login_start_result.message.serialize().unwrap();
|
||||
|
||||
// Server sends credential_response_bytes to client
|
||||
|
||||
@@ -174,7 +174,7 @@ fn open_locker(
|
||||
return Err(String::from("Incorrect password, please try again."));
|
||||
}
|
||||
let client_login_finish_result = result.unwrap();
|
||||
let credential_finalization_bytes = client_login_finish_result.message.serialize();
|
||||
let credential_finalization_bytes = client_login_finish_result.message.serialize().unwrap();
|
||||
|
||||
// Client sends credential_finalization_bytes to server
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ fn account_login(
|
||||
ClientLoginStartParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
let credential_request_bytes = client_login_start_result.message.serialize();
|
||||
let credential_request_bytes = client_login_start_result.message.serialize().unwrap();
|
||||
|
||||
// Client sends credential_request_bytes to server
|
||||
|
||||
@@ -113,7 +113,7 @@ fn account_login(
|
||||
ServerLoginStartParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
let credential_response_bytes = server_login_start_result.message.serialize();
|
||||
let credential_response_bytes = server_login_start_result.message.serialize().unwrap();
|
||||
|
||||
// Server sends credential_response_bytes to client
|
||||
|
||||
@@ -127,7 +127,7 @@ fn account_login(
|
||||
return false;
|
||||
}
|
||||
let client_login_finish_result = result.unwrap();
|
||||
let credential_finalization_bytes = client_login_finish_result.message.serialize();
|
||||
let credential_finalization_bytes = client_login_finish_result.message.serialize().unwrap();
|
||||
|
||||
// Client sends credential_finalization_bytes to server
|
||||
|
||||
|
||||
+54
-14
@@ -16,6 +16,7 @@ use hkdf::Hkdf;
|
||||
use hmac::{Hmac, Mac, NewMac};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use std::convert::TryFrom;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
// Constant string used as salt for HKDF computation
|
||||
const STR_PAD: &[u8] = b"Pad";
|
||||
@@ -24,7 +25,8 @@ const STR_EXPORT_KEY: &[u8] = b"ExportKey";
|
||||
|
||||
const NONCE_LEN: usize = 32;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
#[derive(Clone, Copy, PartialEq, Zeroize)]
|
||||
#[zeroize(drop)]
|
||||
pub(crate) enum InnerEnvelopeMode {
|
||||
Base = 1,
|
||||
CustomIdentifier = 2,
|
||||
@@ -41,6 +43,8 @@ impl TryFrom<u8> for InnerEnvelopeMode {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Zeroize)]
|
||||
#[zeroize(drop)]
|
||||
pub(crate) struct InnerEnvelope {
|
||||
mode: InnerEnvelopeMode,
|
||||
nonce: Vec<u8>,
|
||||
@@ -78,6 +82,15 @@ impl InnerEnvelope {
|
||||
bytes[NONCE_LEN + key_len..].to_vec(),
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> {
|
||||
vec![
|
||||
/* Cannot easily get raw pointer of enum value, otherwise would do self.mode.as_ptr() */
|
||||
(self.nonce.as_ptr(), self.nonce.len()),
|
||||
(self.ciphertext.as_ptr(), self.ciphertext.len()),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/// This struct is an instantiation of the envelope as described in
|
||||
@@ -90,6 +103,7 @@ impl InnerEnvelope {
|
||||
/// The specification update has simplified this assumption by taking
|
||||
/// an XOR-based approach without compromising on security, and to avoid
|
||||
/// the confusion around the implementation of an RKR-secure encryption.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Envelope<D: Hash> {
|
||||
inner_envelope: InnerEnvelope,
|
||||
hmac: GenericArray<u8, <D as Digest>::OutputSize>,
|
||||
@@ -163,8 +177,8 @@ impl<D: Hash> Envelope<D> {
|
||||
client_s_sk: &[u8],
|
||||
server_s_pk: &[u8],
|
||||
optional_ids: Option<(Vec<u8>, Vec<u8>)>,
|
||||
) -> Result<(Self, GenericArray<u8, <D as Digest>::OutputSize>), InternalPakeError> {
|
||||
let aad = construct_aad(server_s_pk, &optional_ids);
|
||||
) -> Result<(Self, GenericArray<u8, <D as Digest>::OutputSize>), ProtocolError> {
|
||||
let aad = construct_aad(server_s_pk, &optional_ids)?;
|
||||
Self::seal_raw(rng, key, &client_s_sk, &aad, mode_from_ids(&optional_ids))
|
||||
}
|
||||
|
||||
@@ -176,7 +190,7 @@ impl<D: Hash> Envelope<D> {
|
||||
plaintext: &[u8],
|
||||
aad: &[u8],
|
||||
mode: InnerEnvelopeMode,
|
||||
) -> Result<(Self, GenericArray<u8, <D as Digest>::OutputSize>), InternalPakeError> {
|
||||
) -> Result<(Self, GenericArray<u8, <D as Digest>::OutputSize>), ProtocolError> {
|
||||
let mut nonce = vec![0u8; NONCE_LEN];
|
||||
rng.fill_bytes(&mut nonce);
|
||||
|
||||
@@ -225,18 +239,18 @@ impl<D: Hash> Envelope<D> {
|
||||
key: &[u8],
|
||||
server_s_pk: &[u8],
|
||||
optional_ids: &Option<(Vec<u8>, Vec<u8>)>,
|
||||
) -> Result<OpenedEnvelope<D>, InternalPakeError> {
|
||||
) -> Result<OpenedEnvelope<D>, ProtocolError> {
|
||||
// First, check that mode matches
|
||||
if self.inner_envelope.mode != mode_from_ids(optional_ids) {
|
||||
return Err(InternalPakeError::IncompatibleEnvelopeModeError);
|
||||
return Err(InternalPakeError::IncompatibleEnvelopeModeError.into());
|
||||
}
|
||||
|
||||
let aad = construct_aad(server_s_pk, optional_ids);
|
||||
let aad = construct_aad(server_s_pk, optional_ids)?;
|
||||
let opened = self.open_raw(key, &aad)?;
|
||||
|
||||
if opened.plaintext.len() != <Key as SizedBytes>::Len::to_usize() {
|
||||
// Plaintext should consist of a single key
|
||||
return Err(InternalPakeError::UnexpectedEnvelopeContentsError);
|
||||
return Err(InternalPakeError::UnexpectedEnvelopeContentsError.into());
|
||||
}
|
||||
|
||||
Ok(OpenedEnvelope {
|
||||
@@ -284,16 +298,42 @@ impl<D: Hash> Envelope<D> {
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> {
|
||||
[
|
||||
self.inner_envelope.as_byte_ptrs(),
|
||||
vec![(self.hmac.as_ptr(), self.hmac.len())],
|
||||
]
|
||||
.concat()
|
||||
}
|
||||
}
|
||||
|
||||
// This can't be derived because of the use of a phantom parameter
|
||||
impl<D: Hash> Zeroize for Envelope<D> {
|
||||
fn zeroize(&mut self) {
|
||||
self.inner_envelope.zeroize();
|
||||
self.hmac.zeroize();
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Hash> Drop for Envelope<D> {
|
||||
fn drop(&mut self) {
|
||||
self.zeroize();
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
fn construct_aad(server_s_pk: &[u8], optional_ids: &Option<(Vec<u8>, Vec<u8>)>) -> Vec<u8> {
|
||||
let ids = optional_ids
|
||||
.iter()
|
||||
.flat_map(|(l, r)| [serialize(l, 2), serialize(r, 2)].concat())
|
||||
.collect();
|
||||
[server_s_pk.to_vec(), ids].concat()
|
||||
fn construct_aad(
|
||||
server_s_pk: &[u8],
|
||||
optional_ids: &Option<(Vec<u8>, Vec<u8>)>,
|
||||
) -> Result<Vec<u8>, ProtocolError> {
|
||||
let ids = match optional_ids {
|
||||
Some((l, r)) => [serialize(l, 2)?, serialize(r, 2)?].concat(),
|
||||
None => vec![],
|
||||
};
|
||||
Ok([server_s_pk.to_vec(), ids].concat())
|
||||
}
|
||||
|
||||
pub(crate) fn mode_from_ids(optional_ids: &Option<(Vec<u8>, Vec<u8>)>) -> InnerEnvelopeMode {
|
||||
|
||||
@@ -73,6 +73,8 @@ pub enum PakeError {
|
||||
InvalidLoginError,
|
||||
/// Error with serializing / deserializing protocol messages
|
||||
SerializationError,
|
||||
/// Identity group element was encountered during deserialization, which is invalid
|
||||
IdentityGroupElementError,
|
||||
}
|
||||
|
||||
// This is meant to express future(ly) non-trivial ways of converting the
|
||||
@@ -99,6 +101,9 @@ pub enum ProtocolError {
|
||||
/// This error occurs when the client request cannot be handled
|
||||
/// Client request cannot be handled.
|
||||
ClientError,
|
||||
/// This error occurs when the client detects that the server has
|
||||
/// reflected the OPRF value (beta == alpha)
|
||||
ReflectedValueError,
|
||||
}
|
||||
|
||||
// This is meant to express future(ly) non-trivial ways of converting the
|
||||
|
||||
+38
-14
@@ -12,6 +12,7 @@ use curve25519_dalek::{
|
||||
constants::RISTRETTO_BASEPOINT_POINT,
|
||||
ristretto::{CompressedRistretto, RistrettoPoint},
|
||||
scalar::Scalar,
|
||||
traits::Identity,
|
||||
};
|
||||
use generic_array::{
|
||||
typenum::{U32, U64},
|
||||
@@ -35,7 +36,7 @@ pub trait Group: Copy + Sized + for<'a> Mul<&'a <Self as Group>::Scalar, Output
|
||||
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;
|
||||
fn random_nonzero_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
|
||||
@@ -64,6 +65,12 @@ pub trait Group: Copy + Sized + for<'a> Mul<&'a <Self as Group>::Scalar, Output
|
||||
|
||||
/// Multiply the point by a scalar, represented as a slice
|
||||
fn mult_by_slice(&self, scalar: &GenericArray<u8, Self::ScalarLen>) -> Self;
|
||||
|
||||
/// Returns if the group element is equal to the identity (1)
|
||||
fn is_identity(&self) -> bool;
|
||||
|
||||
/// Compares in constant time if the group elements are equal
|
||||
fn ct_equal(&self, other: &Self) -> bool;
|
||||
}
|
||||
|
||||
/// The implementation of such a subgroup for Ristretto
|
||||
@@ -77,20 +84,28 @@ impl Group for RistrettoPoint {
|
||||
bits.copy_from_slice(scalar_bits);
|
||||
Ok(Scalar::from_bytes_mod_order(bits))
|
||||
}
|
||||
fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
|
||||
#[cfg(not(test))]
|
||||
{
|
||||
let mut scalar_bytes = [0u8; 64];
|
||||
rng.fill_bytes(&mut scalar_bytes);
|
||||
Scalar::from_bytes_mod_order_wide(&scalar_bytes)
|
||||
}
|
||||
fn random_nonzero_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
|
||||
loop {
|
||||
let scalar = {
|
||||
#[cfg(not(test))]
|
||||
{
|
||||
let mut scalar_bytes = [0u8; 64];
|
||||
rng.fill_bytes(&mut scalar_bytes);
|
||||
Scalar::from_bytes_mod_order_wide(&scalar_bytes)
|
||||
}
|
||||
|
||||
// Tests need an exact conversion from bytes to scalar, sampling only 32 bytes from rng
|
||||
#[cfg(test)]
|
||||
{
|
||||
let mut scalar_bytes = [0u8; 32];
|
||||
rng.fill_bytes(&mut scalar_bytes);
|
||||
Scalar::from_bytes_mod_order(scalar_bytes)
|
||||
// Tests need an exact conversion from bytes to scalar, sampling only 32 bytes from rng
|
||||
#[cfg(test)]
|
||||
{
|
||||
let mut scalar_bytes = [0u8; 32];
|
||||
rng.fill_bytes(&mut scalar_bytes);
|
||||
Scalar::from_bytes_mod_order(scalar_bytes)
|
||||
}
|
||||
};
|
||||
|
||||
if scalar != Scalar::zero() {
|
||||
break scalar;
|
||||
}
|
||||
}
|
||||
}
|
||||
fn scalar_as_bytes(scalar: &Self::Scalar) -> &GenericArray<u8, Self::ScalarLen> {
|
||||
@@ -134,4 +149,13 @@ impl Group for RistrettoPoint {
|
||||
let arr: [u8; 32] = scalar.as_slice().try_into().expect("Wrong length");
|
||||
self * Scalar::from_bits(arr)
|
||||
}
|
||||
|
||||
/// Returns if the group element is equal to the identity (1)
|
||||
fn is_identity(&self) -> bool {
|
||||
self == &Self::identity()
|
||||
}
|
||||
|
||||
fn ct_equal(&self, other: &Self) -> bool {
|
||||
constant_time_eq::constant_time_eq(&self.to_arr(), &other.to_arr())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,12 +12,13 @@ use crate::{
|
||||
use rand::{CryptoRng, RngCore};
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
pub trait KeyExchange<D: Hash, G: Group> {
|
||||
type KE1State: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytes;
|
||||
type KE2State: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytes;
|
||||
type KE1State: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytesWithPointers + Zeroize;
|
||||
type KE2State: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytesWithPointers + Zeroize;
|
||||
type KE1Message: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytes;
|
||||
type KE2Message: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytes;
|
||||
type KE2Message: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytes + Clone;
|
||||
type KE3Message: for<'r> TryFrom<&'r [u8], Error = PakeError> + ToBytes;
|
||||
|
||||
fn generate_ke1<R: RngCore + CryptoRng>(
|
||||
@@ -60,5 +61,13 @@ pub trait KeyExchange<D: Hash, G: Group> {
|
||||
}
|
||||
|
||||
pub trait ToBytes {
|
||||
fn to_bytes(&self) -> Vec<u8>;
|
||||
fn to_bytes(&self) -> Result<Vec<u8>, ProtocolError>;
|
||||
}
|
||||
|
||||
pub trait ToBytesWithPointers {
|
||||
fn to_bytes(&self) -> Result<Vec<u8>, ProtocolError>;
|
||||
|
||||
// Only used for tests to grab raw pointers to data
|
||||
#[cfg(test)]
|
||||
fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)>;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use crate::{
|
||||
},
|
||||
group::Group,
|
||||
hash::Hash,
|
||||
key_exchange::traits::{KeyExchange, ToBytes},
|
||||
key_exchange::traits::{KeyExchange, ToBytes, ToBytesWithPointers},
|
||||
keypair::{Key, KeyPair, SizedBytesExt},
|
||||
serialization::{serialize, tokenize},
|
||||
};
|
||||
@@ -24,8 +24,8 @@ use generic_bytes::SizedBytes;
|
||||
use hkdf::Hkdf;
|
||||
use hmac::{Hmac, Mac, NewMac};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
const KEY_LEN: usize = 32;
|
||||
pub(crate) type NonceLen = U32;
|
||||
@@ -89,9 +89,9 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
|
||||
|
||||
let mut transcript_hasher = D::new()
|
||||
.chain(STR_3DH)
|
||||
.chain(&serialize(&id_u, 2))
|
||||
.chain(&serialize(&id_u, 2)?)
|
||||
.chain(&serialized_credential_request[..])
|
||||
.chain(&serialize(&id_s, 2))
|
||||
.chain(&serialize(&id_s, 2)?)
|
||||
.chain(&l2_bytes[..])
|
||||
.chain(&server_nonce[..])
|
||||
.chain(&server_e_kp.public().to_arr());
|
||||
@@ -119,7 +119,7 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
|
||||
.map(|(&x1, &x2)| x1 ^ x2)
|
||||
.collect();
|
||||
|
||||
transcript_hasher.update(&serialize(&ciphertext, 2));
|
||||
transcript_hasher.update(&serialize(&ciphertext, 2)?);
|
||||
|
||||
let mut mac_hasher =
|
||||
Hmac::<D>::new_varkey(&km2).map_err(|_| InternalPakeError::HmacError)?;
|
||||
@@ -157,9 +157,9 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
|
||||
) -> Result<(Vec<u8>, Vec<u8>, Self::KE3Message), ProtocolError> {
|
||||
let mut transcript_hasher = D::new()
|
||||
.chain(STR_3DH)
|
||||
.chain(&serialize(&id_u, 2))
|
||||
.chain(&serialize(&id_u, 2)?)
|
||||
.chain(&serialized_credential_request)
|
||||
.chain(&serialize(&id_s, 2))
|
||||
.chain(&serialize(&id_s, 2)?)
|
||||
.chain(&l2_component[..])
|
||||
.chain(&ke2_message.to_bytes_without_info_or_mac());
|
||||
|
||||
@@ -175,13 +175,13 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
|
||||
&transcript_hasher.clone().finalize(),
|
||||
)?;
|
||||
|
||||
transcript_hasher.update(&serialize(&ke2_message.e_info[..], 2));
|
||||
transcript_hasher.update(&serialize(&ke2_message.e_info[..], 2)?);
|
||||
|
||||
let mut server_mac =
|
||||
Hmac::<D>::new_varkey(&km2).map_err(|_| InternalPakeError::HmacError)?;
|
||||
server_mac.update(&transcript_hasher.clone().finalize());
|
||||
|
||||
if ke2_message.mac != server_mac.finalize().into_bytes() {
|
||||
if server_mac.verify(&ke2_message.mac).is_err() {
|
||||
return Err(ProtocolError::VerificationError(
|
||||
PakeError::KeyExchangeMacValidationError,
|
||||
));
|
||||
@@ -222,7 +222,7 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
|
||||
Hmac::<D>::new_varkey(&ke2_state.km3).map_err(|_| InternalPakeError::HmacError)?;
|
||||
client_mac.update(&ke2_state.hashed_transcript);
|
||||
|
||||
if ke3_message.mac != client_mac.finalize().into_bytes() {
|
||||
if client_mac.verify(&ke3_message.mac).is_err() {
|
||||
return Err(ProtocolError::VerificationError(
|
||||
PakeError::KeyExchangeMacValidationError,
|
||||
));
|
||||
@@ -237,7 +237,8 @@ impl<D: Hash, G: Group> KeyExchange<D, G> for TripleDH {
|
||||
}
|
||||
|
||||
/// The client state produced after the first key exchange message
|
||||
#[derive(PartialEq, Eq)]
|
||||
#[derive(PartialEq, Eq, Zeroize)]
|
||||
#[zeroize(drop)]
|
||||
pub struct Ke1State {
|
||||
client_e_sk: Key,
|
||||
client_nonce: GenericArray<u8, NonceLen>,
|
||||
@@ -267,21 +268,32 @@ impl TryFrom<&[u8]> for Ke1State {
|
||||
}
|
||||
}
|
||||
|
||||
impl ToBytes for Ke1State {
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
impl ToBytesWithPointers for Ke1State {
|
||||
fn to_bytes(&self) -> Result<Vec<u8>, ProtocolError> {
|
||||
let output: Vec<u8> = [&self.client_e_sk.to_arr(), &self.client_nonce[..]].concat();
|
||||
output
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> {
|
||||
vec![
|
||||
(
|
||||
self.client_e_sk.as_ptr(),
|
||||
<Key as SizedBytes>::Len::to_usize(),
|
||||
),
|
||||
(self.client_nonce.as_ptr(), NonceLen::to_usize()),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl ToBytes for Ke1Message {
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
[
|
||||
fn to_bytes(&self) -> Result<Vec<u8>, ProtocolError> {
|
||||
Ok([
|
||||
&self.client_nonce[..],
|
||||
&serialize(&self.info, 2),
|
||||
&serialize(&self.info, 2)?,
|
||||
&self.client_e_pk.to_arr(),
|
||||
]
|
||||
.concat()
|
||||
.concat())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,7 +323,43 @@ pub struct Ke2State<HashLen: ArrayLength<u8>> {
|
||||
session_key: GenericArray<u8, HashLen>,
|
||||
}
|
||||
|
||||
// This can't be derived because of the use of a phantom parameter
|
||||
impl<HashLen: ArrayLength<u8>> Zeroize for Ke2State<HashLen> {
|
||||
fn zeroize(&mut self) {
|
||||
self.km3.zeroize();
|
||||
self.hashed_transcript.zeroize();
|
||||
self.session_key.zeroize();
|
||||
}
|
||||
}
|
||||
|
||||
impl<HashLen: ArrayLength<u8>> Drop for Ke2State<HashLen> {
|
||||
fn drop(&mut self) {
|
||||
self.zeroize();
|
||||
}
|
||||
}
|
||||
|
||||
impl<HashLen: ArrayLength<u8>> ToBytesWithPointers for Ke2State<HashLen> {
|
||||
fn to_bytes(&self) -> Result<Vec<u8>, ProtocolError> {
|
||||
Ok([
|
||||
&self.km3[..],
|
||||
&self.hashed_transcript[..],
|
||||
&self.session_key[..],
|
||||
]
|
||||
.concat())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> {
|
||||
vec![
|
||||
(self.km3.as_ptr(), HashLen::to_usize()),
|
||||
(self.hashed_transcript.as_ptr(), HashLen::to_usize()),
|
||||
(self.session_key.as_ptr(), HashLen::to_usize()),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/// The second key exchange message
|
||||
#[derive(Clone)]
|
||||
pub struct Ke2Message<HashLen: ArrayLength<u8>> {
|
||||
server_nonce: GenericArray<u8, NonceLen>,
|
||||
server_e_pk: Key,
|
||||
@@ -319,17 +367,6 @@ pub struct Ke2Message<HashLen: ArrayLength<u8>> {
|
||||
mac: GenericArray<u8, HashLen>,
|
||||
}
|
||||
|
||||
impl<HashLen: ArrayLength<u8>> ToBytes for Ke2State<HashLen> {
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
[
|
||||
&self.km3[..],
|
||||
&self.hashed_transcript[..],
|
||||
&self.session_key[..],
|
||||
]
|
||||
.concat()
|
||||
}
|
||||
}
|
||||
|
||||
impl<HashLen: ArrayLength<u8>> TryFrom<&[u8]> for Ke2State<HashLen> {
|
||||
type Error = PakeError;
|
||||
|
||||
@@ -348,13 +385,13 @@ impl<HashLen: ArrayLength<u8>> TryFrom<&[u8]> for Ke2State<HashLen> {
|
||||
}
|
||||
|
||||
impl<HashLen: ArrayLength<u8>> ToBytes for Ke2Message<HashLen> {
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
[
|
||||
fn to_bytes(&self) -> Result<Vec<u8>, ProtocolError> {
|
||||
Ok([
|
||||
&self.to_bytes_without_info_or_mac(),
|
||||
&serialize(&self.e_info, 2),
|
||||
&serialize(&self.e_info, 2)?,
|
||||
&self.mac[..],
|
||||
]
|
||||
.concat()
|
||||
.concat())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -413,8 +450,8 @@ pub struct Ke3Message<HashLen: ArrayLength<u8>> {
|
||||
}
|
||||
|
||||
impl<HashLen: ArrayLength<u8>> ToBytes for Ke3Message<HashLen> {
|
||||
fn to_bytes(&self) -> Vec<u8> {
|
||||
self.mac.to_vec()
|
||||
fn to_bytes(&self) -> Result<Vec<u8>, ProtocolError> {
|
||||
Ok(self.mac.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -503,14 +540,16 @@ fn hkdf_expand_label_extracted<D: Hash>(
|
||||
let mut okm = vec![0u8; length];
|
||||
|
||||
let mut hkdf_label: Vec<u8> = Vec::new();
|
||||
hkdf_label.extend_from_slice(&length.to_be_bytes()[std::mem::size_of::<usize>() - 2..]);
|
||||
|
||||
let length_u16: u16 = u16::try_from(length).map_err(|_| PakeError::SerializationError)?;
|
||||
hkdf_label.extend_from_slice(&length_u16.to_be_bytes());
|
||||
|
||||
let mut opaque_label: Vec<u8> = Vec::new();
|
||||
opaque_label.extend_from_slice(&STR_OPAQUE);
|
||||
opaque_label.extend_from_slice(&label);
|
||||
hkdf_label.extend_from_slice(&serialize(&opaque_label, 1));
|
||||
hkdf_label.extend_from_slice(&serialize(&opaque_label, 1)?);
|
||||
|
||||
hkdf_label.extend_from_slice(&serialize(&context, 1));
|
||||
hkdf_label.extend_from_slice(&serialize(&context, 1)?);
|
||||
|
||||
hkdf.expand(&hkdf_label, &mut okm)
|
||||
.map_err(|_| InternalPakeError::HkdfError)?;
|
||||
|
||||
+70
-13
@@ -5,8 +5,12 @@
|
||||
|
||||
//! Contains the keypair types that must be supplied for the OPAQUE API
|
||||
|
||||
#![allow(unsafe_code)]
|
||||
|
||||
use crate::errors::InternalPakeError;
|
||||
use crate::group::Group;
|
||||
#[cfg(test)]
|
||||
use generic_array::typenum::Unsigned;
|
||||
use generic_array::{typenum::U32, GenericArray};
|
||||
use generic_bytes::{SizedBytes, TryFromSizedBytesError};
|
||||
#[cfg(test)]
|
||||
@@ -17,6 +21,7 @@ use rand::{CryptoRng, RngCore};
|
||||
use std::fmt::Debug;
|
||||
use std::marker::PhantomData;
|
||||
use std::ops::Deref;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
/// Convenience extension trait of SizedBytes
|
||||
pub trait SizedBytesExt: SizedBytes {
|
||||
@@ -37,6 +42,20 @@ pub struct KeyPair<G> {
|
||||
_g: PhantomData<G>,
|
||||
}
|
||||
|
||||
// This can't be derived because of the use of a phantom parameter
|
||||
impl<G> Zeroize for KeyPair<G> {
|
||||
fn zeroize(&mut self) {
|
||||
self.pk.zeroize();
|
||||
self.sk.zeroize();
|
||||
}
|
||||
}
|
||||
|
||||
impl<G> Drop for KeyPair<G> {
|
||||
fn drop(&mut self) {
|
||||
self.zeroize();
|
||||
}
|
||||
}
|
||||
|
||||
impl<G: Group> KeyPair<G> {
|
||||
/// The public key component
|
||||
pub fn public(&self) -> &Key {
|
||||
@@ -48,19 +67,9 @@ impl<G: Group> KeyPair<G> {
|
||||
&self.sk
|
||||
}
|
||||
|
||||
/// A constructor that receives public and private key independently as
|
||||
/// bytes
|
||||
pub fn new(public: Key, private: Key) -> Result<Self, InternalPakeError> {
|
||||
Ok(Self {
|
||||
pk: public,
|
||||
sk: private,
|
||||
_g: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
/// Generating a random key pair given a cryptographic rng
|
||||
pub(crate) fn generate_random<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
|
||||
let sk = G::random_scalar(rng);
|
||||
let sk = G::random_nonzero_scalar(rng);
|
||||
let sk_bytes = G::scalar_as_bytes(&sk);
|
||||
let pk = G::base_point().mult_by_slice(&sk_bytes);
|
||||
Self {
|
||||
@@ -97,7 +106,19 @@ impl<G: Group> KeyPair<G> {
|
||||
pub fn from_private_key_slice(input: &[u8]) -> Result<Self, InternalPakeError> {
|
||||
let sk = Key::from_arr(GenericArray::from_slice(&input))?;
|
||||
let pk = Self::public_from_private(&sk);
|
||||
Self::new(pk, sk)
|
||||
Ok(Self {
|
||||
pk,
|
||||
sk,
|
||||
_g: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> {
|
||||
vec![
|
||||
(self.pk.as_ptr(), <Key as SizedBytes>::Len::to_usize()),
|
||||
(self.sk.as_ptr(), <Key as SizedBytes>::Len::to_usize()),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +140,9 @@ impl<G: Group + Debug> KeyPair<G> {
|
||||
}
|
||||
|
||||
/// A minimalist key type built around a \[u8; 32\]
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Zeroize)]
|
||||
// Ensure Key material is zeroed after use.
|
||||
#[zeroize(drop)]
|
||||
#[repr(transparent)]
|
||||
pub struct Key(Vec<u8>);
|
||||
|
||||
@@ -146,7 +169,41 @@ impl SizedBytes for Key {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::errors::*;
|
||||
use curve25519_dalek::ristretto::RistrettoPoint;
|
||||
use generic_array::typenum::Unsigned;
|
||||
use rand::rngs::OsRng;
|
||||
use std::slice::from_raw_parts;
|
||||
|
||||
#[test]
|
||||
fn test_zeroize_key() -> Result<(), ProtocolError> {
|
||||
let key_len = <Key as SizedBytes>::Len::to_usize();
|
||||
let mut key = Key(vec![1u8; key_len]);
|
||||
let ptr = key.as_ptr();
|
||||
|
||||
key.zeroize();
|
||||
|
||||
let bytes = unsafe { from_raw_parts(ptr, key_len) };
|
||||
assert!(bytes.iter().all(|&x| x == 0));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zeroize_keypair() -> Result<(), ProtocolError> {
|
||||
let mut rng = OsRng;
|
||||
let mut keypair = KeyPair::<RistrettoPoint>::generate_random(&mut rng);
|
||||
let ptrs = keypair.as_byte_ptrs();
|
||||
|
||||
keypair.zeroize();
|
||||
|
||||
for (ptr, len) in ptrs {
|
||||
let bytes = unsafe { from_raw_parts(ptr, len) };
|
||||
assert!(bytes.iter().all(|&x| x == 0));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
|
||||
+13
-13
@@ -6,7 +6,7 @@
|
||||
//! Defines the GroupWithMapToCurve trait to specify how to map a password to a
|
||||
//! curve point
|
||||
|
||||
use crate::errors::InternalPakeError;
|
||||
use crate::errors::{InternalPakeError, ProtocolError};
|
||||
use crate::group::Group;
|
||||
use crate::hash::Hash;
|
||||
use crate::serialization::i2osp;
|
||||
@@ -22,12 +22,12 @@ pub trait GroupWithMapToCurve: Group {
|
||||
const SUITE_ID: usize;
|
||||
|
||||
/// transforms a password and domain separation tag (DST) into a curve point
|
||||
fn map_to_curve<H: Hash>(msg: &[u8], dst: &[u8]) -> Result<Self, InternalPakeError>;
|
||||
fn map_to_curve<H: Hash>(msg: &[u8], dst: &[u8]) -> Result<Self, ProtocolError>;
|
||||
|
||||
/// Generates the contextString parameter as defined in
|
||||
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-05.txt>
|
||||
fn get_context_string(mode: u8) -> Vec<u8> {
|
||||
[i2osp(mode as usize, 1), i2osp(Self::SUITE_ID, 2)].concat()
|
||||
fn get_context_string(mode: u8) -> Result<Vec<u8>, ProtocolError> {
|
||||
Ok([i2osp(mode as usize, 1)?, i2osp(Self::SUITE_ID, 2)?].concat())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ impl GroupWithMapToCurve for RistrettoPoint {
|
||||
|
||||
// Implements the hash_to_ristretto255() function from
|
||||
// https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.txt
|
||||
fn map_to_curve<H: Hash>(msg: &[u8], dst: &[u8]) -> Result<Self, InternalPakeError> {
|
||||
fn map_to_curve<H: Hash>(msg: &[u8], dst: &[u8]) -> Result<Self, ProtocolError> {
|
||||
let uniform_bytes =
|
||||
expand_message_xmd::<H>(msg, dst, <H as Digest>::OutputSize::to_usize())?;
|
||||
Ok(<Self as Group>::hash_to_curve(
|
||||
@@ -65,24 +65,24 @@ pub(crate) fn expand_message_xmd<H: Hash>(
|
||||
msg: &[u8],
|
||||
dst: &[u8],
|
||||
len_in_bytes: usize,
|
||||
) -> Result<Vec<u8>, InternalPakeError> {
|
||||
) -> Result<Vec<u8>, ProtocolError> {
|
||||
let b_in_bytes = <H as Digest>::OutputSize::to_usize();
|
||||
let r_in_bytes = <H as BlockInput>::BlockSize::to_usize();
|
||||
|
||||
let ell = div_ceil(len_in_bytes, b_in_bytes);
|
||||
if ell > 255 {
|
||||
return Err(InternalPakeError::HashToCurveError);
|
||||
return Err(InternalPakeError::HashToCurveError.into());
|
||||
}
|
||||
let dst_prime = [dst, &i2osp(dst.len(), 1)].concat();
|
||||
let z_pad = i2osp(0, r_in_bytes);
|
||||
let l_i_b_str = i2osp(len_in_bytes, 2);
|
||||
let msg_prime = [&z_pad, msg, &l_i_b_str, &i2osp(0, 1), &dst_prime].concat();
|
||||
let dst_prime = [dst, &i2osp(dst.len(), 1)?].concat();
|
||||
let z_pad = i2osp(0, r_in_bytes)?;
|
||||
let l_i_b_str = i2osp(len_in_bytes, 2)?;
|
||||
let msg_prime = [&z_pad, msg, &l_i_b_str, &i2osp(0, 1)?, &dst_prime].concat();
|
||||
|
||||
let mut b: Vec<Vec<u8>> = vec![H::digest(&msg_prime).to_vec()]; // b[0]
|
||||
|
||||
let mut h = H::new();
|
||||
h.update(&b[0]);
|
||||
h.update(&i2osp(1, 1));
|
||||
h.update(&i2osp(1, 1)?);
|
||||
h.update(&dst_prime);
|
||||
b.push(h.finalize_reset().to_vec()); // b[1]
|
||||
|
||||
@@ -91,7 +91,7 @@ pub(crate) fn expand_message_xmd<H: Hash>(
|
||||
|
||||
for i in 2..(ell + 1) {
|
||||
h.update(xor(&b[0], &b[i - 1])?);
|
||||
h.update(&i2osp(i, 1));
|
||||
h.update(&i2osp(i, 1)?);
|
||||
h.update(&dst_prime);
|
||||
b.push(h.finalize_reset().to_vec()); // b[i]
|
||||
uniform_bytes.extend_from_slice(&b[i]);
|
||||
|
||||
+76
-9
@@ -29,6 +29,21 @@ pub struct RegistrationRequest<CS: CipherSuite> {
|
||||
pub(crate) alpha: CS::Group,
|
||||
}
|
||||
|
||||
impl<CS: CipherSuite> RegistrationRequest<CS> {
|
||||
/// Only used for testing purposes
|
||||
#[cfg(test)]
|
||||
pub fn get_alpha_for_testing(&self) -> CS::Group {
|
||||
self.alpha
|
||||
}
|
||||
}
|
||||
|
||||
// Cannot be derived because it would require for CS to be Clone.
|
||||
impl<CS: CipherSuite> Clone for RegistrationRequest<CS> {
|
||||
fn clone(&self) -> Self {
|
||||
Self { alpha: self.alpha }
|
||||
}
|
||||
}
|
||||
|
||||
impl<CS: CipherSuite> RegistrationRequest<CS> {
|
||||
/// Serialization into bytes
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
@@ -43,6 +58,11 @@ impl<CS: CipherSuite> RegistrationRequest<CS> {
|
||||
// correct subgroup
|
||||
let arr = GenericArray::from_slice(checked_slice);
|
||||
let alpha = CS::Group::from_element_slice(arr)?;
|
||||
|
||||
// Throw an error if the identity group element is encountered
|
||||
if alpha.is_identity() {
|
||||
return Err(PakeError::IdentityGroupElementError.into());
|
||||
}
|
||||
Ok(Self { alpha })
|
||||
}
|
||||
}
|
||||
@@ -74,11 +94,30 @@ impl<CS: CipherSuite> RegistrationResponse<CS> {
|
||||
let arr = GenericArray::from_slice(&checked_slice[..elem_len]);
|
||||
let beta = CS::Group::from_element_slice(arr)?;
|
||||
|
||||
// Throw an error if the identity group element is encountered
|
||||
if beta.is_identity() {
|
||||
return Err(PakeError::IdentityGroupElementError.into());
|
||||
}
|
||||
|
||||
// Ensure that public key is valid
|
||||
let server_s_pk =
|
||||
KeyPair::<CS::Group>::check_public_key(Key::from_bytes(&checked_slice[elem_len..])?)?;
|
||||
|
||||
Ok(Self {
|
||||
server_s_pk: checked_slice[elem_len..].to_vec(),
|
||||
beta,
|
||||
server_s_pk: server_s_pk.to_arr().to_vec(),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
/// Only used for tests, where we can set the beta value to test for the reflection
|
||||
/// error case
|
||||
pub fn set_beta_for_testing(&self, new_beta: CS::Group) -> Self {
|
||||
Self {
|
||||
beta: new_beta,
|
||||
server_s_pk: self.server_s_pk.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The final message from the client, containing sealed cryptographic
|
||||
@@ -131,11 +170,11 @@ pub struct CredentialRequest<CS: CipherSuite> {
|
||||
|
||||
impl<CS: CipherSuite> CredentialRequest<CS> {
|
||||
/// Serialization into bytes
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
pub fn serialize(&self) -> Result<Vec<u8>, ProtocolError> {
|
||||
let mut credential_request: Vec<u8> = Vec::new();
|
||||
credential_request.extend_from_slice(&self.alpha.to_arr());
|
||||
credential_request.extend_from_slice(&self.ke1_message.to_bytes());
|
||||
credential_request
|
||||
credential_request.extend_from_slice(&self.ke1_message.to_bytes()?);
|
||||
Ok(credential_request)
|
||||
}
|
||||
|
||||
/// Deserialization from bytes
|
||||
@@ -150,6 +189,11 @@ impl<CS: CipherSuite> CredentialRequest<CS> {
|
||||
let arr = GenericArray::from_slice(&checked_slice[..elem_len]);
|
||||
let alpha = CS::Group::from_element_slice(arr)?;
|
||||
|
||||
// Throw an error if the identity group element is encountered
|
||||
if alpha.is_identity() {
|
||||
return Err(PakeError::IdentityGroupElementError.into());
|
||||
}
|
||||
|
||||
let ke1_message =
|
||||
<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE1Message::try_from(
|
||||
&checked_slice[elem_len..],
|
||||
@@ -157,6 +201,12 @@ impl<CS: CipherSuite> CredentialRequest<CS> {
|
||||
|
||||
Ok(Self { alpha, ke1_message })
|
||||
}
|
||||
|
||||
/// Only used for testing purposes
|
||||
#[cfg(test)]
|
||||
pub fn get_alpha_for_testing(&self) -> CS::Group {
|
||||
self.alpha
|
||||
}
|
||||
}
|
||||
|
||||
/// The answer sent by the server to the user, upon reception of the
|
||||
@@ -172,12 +222,12 @@ pub struct CredentialResponse<CS: CipherSuite> {
|
||||
|
||||
impl<CS: CipherSuite> CredentialResponse<CS> {
|
||||
/// Serialization into bytes
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
[
|
||||
pub fn serialize(&self) -> Result<Vec<u8>, ProtocolError> {
|
||||
Ok([
|
||||
Self::serialize_without_ke(&self.beta, &self.server_s_pk, &self.envelope),
|
||||
self.ke2_message.to_bytes(),
|
||||
self.ke2_message.to_bytes()?,
|
||||
]
|
||||
.concat()
|
||||
.concat())
|
||||
}
|
||||
|
||||
pub(crate) fn serialize_without_ke(
|
||||
@@ -206,6 +256,11 @@ impl<CS: CipherSuite> CredentialResponse<CS> {
|
||||
let arr = GenericArray::from_slice(beta_bytes);
|
||||
let beta = CS::Group::from_element_slice(arr)?;
|
||||
|
||||
// Throw an error if the identity group element is encountered
|
||||
if beta.is_identity() {
|
||||
return Err(PakeError::IdentityGroupElementError.into());
|
||||
}
|
||||
|
||||
let unchecked_server_s_pk = Key::from_bytes(&checked_slice[elem_len..elem_len + key_len])?;
|
||||
let server_s_pk = KeyPair::<CS::Group>::check_public_key(unchecked_server_s_pk)?;
|
||||
|
||||
@@ -227,6 +282,18 @@ impl<CS: CipherSuite> CredentialResponse<CS> {
|
||||
ke2_message,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
/// Only used for tests, where we can set the beta value to test for the reflection
|
||||
/// error case
|
||||
pub fn set_beta_for_testing(&self, new_beta: CS::Group) -> Self {
|
||||
Self {
|
||||
beta: new_beta,
|
||||
server_s_pk: self.server_s_pk.clone(),
|
||||
envelope: self.envelope.clone(),
|
||||
ke2_message: self.ke2_message.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The answer sent by the client to the server, upon reception of the
|
||||
@@ -237,7 +304,7 @@ pub struct CredentialFinalization<CS: CipherSuite> {
|
||||
|
||||
impl<CS: CipherSuite> CredentialFinalization<CS> {
|
||||
/// Serialization into bytes
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
pub fn serialize(&self) -> Result<Vec<u8>, ProtocolError> {
|
||||
self.ke3_message.to_bytes()
|
||||
}
|
||||
|
||||
|
||||
+172
-53
@@ -11,7 +11,7 @@ use crate::{
|
||||
errors::{utils::check_slice_size_atleast, InternalPakeError, PakeError, ProtocolError},
|
||||
group::Group,
|
||||
hash::Hash,
|
||||
key_exchange::traits::{KeyExchange, ToBytes},
|
||||
key_exchange::traits::{KeyExchange, ToBytesWithPointers},
|
||||
keypair::{Key, KeyPair, SizedBytesExt},
|
||||
map_to_curve::GroupWithMapToCurve,
|
||||
oprf,
|
||||
@@ -32,6 +32,7 @@ use zeroize::Zeroize;
|
||||
|
||||
/// The state elements the client holds to register itself
|
||||
pub struct ClientRegistration<CS: CipherSuite> {
|
||||
alpha: CS::Group,
|
||||
/// token containing the client's password and the blinding factor
|
||||
pub(crate) token: oprf::Token<CS::Group>,
|
||||
}
|
||||
@@ -40,6 +41,7 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
|
||||
/// Serialization into bytes
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
let output: Vec<u8> = [
|
||||
&self.alpha.to_arr().to_vec(),
|
||||
&CS::Group::scalar_as_bytes(&self.token.blind)[..],
|
||||
&self.token.data,
|
||||
]
|
||||
@@ -49,7 +51,9 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
|
||||
|
||||
/// Deserialization from bytes
|
||||
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
|
||||
let min_expected_len = <CS::Group as Group>::ScalarLen::to_usize();
|
||||
let elem_len = <CS::Group as Group>::ElemLen::to_usize();
|
||||
let scalar_len = <CS::Group as Group>::ScalarLen::to_usize();
|
||||
let min_expected_len = elem_len + scalar_len;
|
||||
let checked_slice = (if input.len() <= min_expected_len {
|
||||
Err(InternalPakeError::SizeError {
|
||||
name: "client_registration_bytes",
|
||||
@@ -60,19 +64,32 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
|
||||
Ok(input)
|
||||
})?;
|
||||
|
||||
let alpha =
|
||||
CS::Group::from_element_slice(GenericArray::from_slice(&checked_slice[..elem_len]))?;
|
||||
|
||||
// Check that the message is actually containing an element of the
|
||||
// correct subgroup
|
||||
let scalar_len = min_expected_len;
|
||||
let blinding_factor_bytes = GenericArray::from_slice(&checked_slice[..scalar_len]);
|
||||
let blinding_factor_bytes =
|
||||
GenericArray::from_slice(&checked_slice[elem_len..elem_len + scalar_len]);
|
||||
let blinding_factor = CS::Group::from_scalar_slice(blinding_factor_bytes)?;
|
||||
let password = checked_slice[scalar_len..].to_vec();
|
||||
|
||||
let password = checked_slice[elem_len + scalar_len..].to_vec();
|
||||
Ok(Self {
|
||||
alpha,
|
||||
token: oprf::Token {
|
||||
data: password,
|
||||
blind: blinding_factor,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> {
|
||||
vec![
|
||||
(self.token.data.as_ptr(), self.token.data.len()),
|
||||
/* cannot provide raw pointer to self.token.blind until this is exposed in curve25519_dalek::scalar::Scalar */
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/// Optional parameters for client registration finish
|
||||
@@ -129,7 +146,7 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
|
||||
|
||||
Ok(ClientRegistrationStartResult {
|
||||
message: RegistrationRequest::<CS> { alpha },
|
||||
state: Self { token },
|
||||
state: Self { alpha, token },
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -140,6 +157,9 @@ pub struct ClientRegistrationFinishResult<CS: CipherSuite> {
|
||||
pub message: RegistrationUpload<CS>,
|
||||
/// The export key output by client registration
|
||||
pub export_key: GenericArray<u8, <CS::Hash as Digest>::OutputSize>,
|
||||
/// Instance of the ClientRegistration, only used in tests for checking zeroize
|
||||
#[cfg(test)]
|
||||
pub state: ClientRegistration<CS>,
|
||||
}
|
||||
|
||||
impl<CS: CipherSuite> ClientRegistration<CS> {
|
||||
@@ -186,6 +206,11 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
|
||||
};
|
||||
let client_static_keypair = CS::generate_random_keypair(rng);
|
||||
|
||||
// Check for reflected value from server and halt if detected
|
||||
if self.alpha.ct_equal(&r2.beta) {
|
||||
return Err(ProtocolError::ReflectedValueError);
|
||||
}
|
||||
|
||||
let password_derived_key =
|
||||
get_password_derived_key::<CS::Group, CS::SlowHash, CS::Hash>(&self.token, r2.beta)?;
|
||||
|
||||
@@ -203,38 +228,12 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
|
||||
client_s_pk: client_static_keypair.public().clone(),
|
||||
},
|
||||
export_key,
|
||||
#[cfg(test)]
|
||||
state: self,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// This can't be derived because of the use of a phantom parameter
|
||||
impl<CS: CipherSuite> Zeroize for ClientRegistration<CS> {
|
||||
fn zeroize(&mut self) {
|
||||
self.token.data.zeroize();
|
||||
self.token.blind.zeroize();
|
||||
}
|
||||
}
|
||||
|
||||
impl<CS: CipherSuite> Drop for ClientRegistration<CS> {
|
||||
fn drop(&mut self) {
|
||||
self.zeroize();
|
||||
}
|
||||
}
|
||||
|
||||
// This can't be derived because of the use of a phantom parameter
|
||||
impl<CS: CipherSuite> Zeroize for ClientLogin<CS> {
|
||||
fn zeroize(&mut self) {
|
||||
self.token.data.zeroize();
|
||||
self.token.blind.zeroize();
|
||||
}
|
||||
}
|
||||
|
||||
impl<CS: CipherSuite> Drop for ClientLogin<CS> {
|
||||
fn drop(&mut self) {
|
||||
self.zeroize();
|
||||
}
|
||||
}
|
||||
|
||||
/// Contains the fields that are returned by a server registration start
|
||||
pub struct ServerRegistrationStartResult<CS: CipherSuite> {
|
||||
/// The registration resposne message to send to the client
|
||||
@@ -295,6 +294,21 @@ impl<CS: CipherSuite> ServerRegistration<CS> {
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> {
|
||||
[
|
||||
match &self.envelope {
|
||||
Some(env) => env.as_byte_ptrs(),
|
||||
None => vec![],
|
||||
},
|
||||
match &self.client_s_pk {
|
||||
Some(pk) => vec![(pk.as_ptr(), pk.len())],
|
||||
None => vec![],
|
||||
},
|
||||
/* cannot provide raw pointer to self.oprf_key until this is exposed in curve25519_dalek::scalar::Scalar */
|
||||
].concat()
|
||||
}
|
||||
|
||||
/// From the client's "blinded" password, returns a response to be
|
||||
/// sent back to the client, as well as a ServerRegistration
|
||||
///
|
||||
@@ -328,7 +342,7 @@ impl<CS: CipherSuite> ServerRegistration<CS> {
|
||||
server_s_pk: &Key,
|
||||
) -> Result<ServerRegistrationStartResult<CS>, ProtocolError> {
|
||||
// RFC: generate oprf_key (salt) and v_u = g^oprf_key
|
||||
let oprf_key = CS::Group::random_scalar(rng);
|
||||
let oprf_key = CS::Group::random_nonzero_scalar(rng);
|
||||
|
||||
// Compute beta = alpha^oprf_key
|
||||
let beta = oprf::evaluate::<CS::Group>(message.alpha, &oprf_key);
|
||||
@@ -380,7 +394,7 @@ impl<CS: CipherSuite> ServerRegistration<CS> {
|
||||
Ok(Self {
|
||||
envelope: Some(message.envelope),
|
||||
client_s_pk: Some(message.client_s_pk),
|
||||
oprf_key: self.oprf_key,
|
||||
oprf_key: self.oprf_key.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -398,15 +412,15 @@ pub struct ClientLogin<CS: CipherSuite> {
|
||||
|
||||
impl<CS: CipherSuite> ClientLogin<CS> {
|
||||
/// Serialization into bytes
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
pub fn serialize(&self) -> Result<Vec<u8>, ProtocolError> {
|
||||
let output: Vec<u8> = [
|
||||
&CS::Group::scalar_as_bytes(&self.token.blind)[..],
|
||||
&serialize(&self.serialized_credential_request, 2),
|
||||
&serialize(&self.ke1_state.to_bytes(), 2),
|
||||
&serialize(&self.serialized_credential_request, 2)?,
|
||||
&serialize(&self.ke1_state.to_bytes()?, 2)?,
|
||||
&self.token.data,
|
||||
]
|
||||
.concat();
|
||||
output
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
/// Deserialization from bytes
|
||||
@@ -440,6 +454,18 @@ impl<CS: CipherSuite> ClientLogin<CS> {
|
||||
serialized_credential_request,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> {
|
||||
[
|
||||
vec![
|
||||
(self.token.data.as_ptr(), self.token.data.len()),
|
||||
/* cannot provide raw pointer to self.token.blind until this is exposed in curve25519_dalek::scalar::Scalar */
|
||||
],
|
||||
self.ke1_state.as_byte_ptrs(),
|
||||
vec![ (self.serialized_credential_request.as_ptr(), self.serialized_credential_request.len()) ],
|
||||
].concat()
|
||||
}
|
||||
}
|
||||
|
||||
/// Optional parameters for client login start
|
||||
@@ -488,6 +514,9 @@ pub struct ClientLoginFinishResult<CS: CipherSuite> {
|
||||
pub server_s_pk: Key,
|
||||
/// The confidential info sent by the client
|
||||
pub confidential_info: Vec<u8>,
|
||||
/// Instance of the ClientLogin, only used in tests for checking zeroize
|
||||
#[cfg(test)]
|
||||
pub state: ClientLogin<CS>,
|
||||
}
|
||||
|
||||
impl<CS: CipherSuite> ClientLogin<CS> {
|
||||
@@ -526,7 +555,7 @@ impl<CS: CipherSuite> ClientLogin<CS> {
|
||||
let (ke1_state, ke1_message) = CS::KeyExchange::generate_ke1(info, rng)?;
|
||||
|
||||
let credential_request = CredentialRequest { alpha, ke1_message };
|
||||
let serialized_credential_request = credential_request.serialize();
|
||||
let serialized_credential_request = credential_request.serialize()?;
|
||||
|
||||
Ok(ClientLoginStartResult {
|
||||
message: credential_request,
|
||||
@@ -582,6 +611,13 @@ impl<CS: CipherSuite> ClientLogin<CS> {
|
||||
ClientLoginFinishParameters::WithIdentifiers(id_u, id_s) => Some((id_u, id_s)),
|
||||
};
|
||||
|
||||
// Check if beta value from server is equal to alpha value from client
|
||||
let credential_request =
|
||||
CredentialRequest::<CS>::deserialize(&self.serialized_credential_request[..])?;
|
||||
if credential_request.alpha.ct_equal(&l2.beta) {
|
||||
return Err(ProtocolError::ReflectedValueError);
|
||||
}
|
||||
|
||||
let server_s_pk_bytes = l2.server_s_pk.to_arr().to_vec();
|
||||
|
||||
let password_derived_key =
|
||||
@@ -590,8 +626,10 @@ impl<CS: CipherSuite> ClientLogin<CS> {
|
||||
.envelope
|
||||
.open(&password_derived_key, &server_s_pk_bytes, &optional_ids)
|
||||
.map_err(|e| match e {
|
||||
InternalPakeError::SealOpenHmacError => PakeError::InvalidLoginError,
|
||||
err => PakeError::from(err),
|
||||
ProtocolError::VerificationError(PakeError::CryptoError(
|
||||
InternalPakeError::SealOpenHmacError,
|
||||
)) => ProtocolError::VerificationError(PakeError::InvalidLoginError),
|
||||
err => err,
|
||||
})?;
|
||||
|
||||
let client_s_sk = Key::from_bytes(&opened_envelope.client_s_sk)?;
|
||||
@@ -626,6 +664,8 @@ impl<CS: CipherSuite> ClientLogin<CS> {
|
||||
session_key,
|
||||
export_key: opened_envelope.export_key.clone(),
|
||||
server_s_pk: l2.server_s_pk,
|
||||
#[cfg(test)]
|
||||
state: self,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -665,14 +705,18 @@ pub struct ServerLoginStartResult<CS: CipherSuite> {
|
||||
}
|
||||
|
||||
/// Contains the fields that are returned by a server login finish
|
||||
pub struct ServerLoginFinishResult {
|
||||
pub struct ServerLoginFinishResult<CS: CipherSuite> {
|
||||
/// The session key between client and server
|
||||
pub session_key: Vec<u8>,
|
||||
_cs: PhantomData<CS>,
|
||||
/// Instance of the ClientRegistration, only used in tests for checking zeroize
|
||||
#[cfg(test)]
|
||||
pub state: ServerLogin<CS>,
|
||||
}
|
||||
|
||||
impl<CS: CipherSuite> ServerLogin<CS> {
|
||||
/// Serialization into bytes
|
||||
pub fn serialize(&self) -> Vec<u8> {
|
||||
pub fn serialize(&self) -> Result<Vec<u8>, ProtocolError> {
|
||||
self.ke2_state.to_bytes()
|
||||
}
|
||||
|
||||
@@ -728,6 +772,7 @@ impl<CS: CipherSuite> ServerLogin<CS> {
|
||||
) -> Result<ServerLoginStartResult<CS>, ProtocolError> {
|
||||
let client_s_pk = password_file
|
||||
.client_s_pk
|
||||
.clone()
|
||||
.ok_or(InternalPakeError::SealError)?;
|
||||
|
||||
let (e_info, optional_ids) = match params {
|
||||
@@ -740,7 +785,10 @@ impl<CS: CipherSuite> ServerLogin<CS> {
|
||||
}
|
||||
};
|
||||
|
||||
let envelope = password_file.envelope.ok_or(InternalPakeError::SealError)?;
|
||||
let envelope = password_file
|
||||
.envelope
|
||||
.clone()
|
||||
.ok_or(InternalPakeError::SealError)?;
|
||||
if envelope.get_mode() != mode_from_ids(&optional_ids) {
|
||||
return Err(InternalPakeError::IncompatibleEnvelopeModeError.into());
|
||||
}
|
||||
@@ -755,7 +803,7 @@ impl<CS: CipherSuite> ServerLogin<CS> {
|
||||
Some((id_u, id_s)) => (id_u, id_s),
|
||||
};
|
||||
|
||||
let l1_bytes = &l1.serialize();
|
||||
let l1_bytes = &l1.serialize()?;
|
||||
let beta = oprf::evaluate(l1.alpha, &password_file.oprf_key);
|
||||
let server_s_pk = KeyPair::<CS::Group>::public_from_private(&server_s_sk);
|
||||
|
||||
@@ -827,9 +875,9 @@ impl<CS: CipherSuite> ServerLogin<CS> {
|
||||
/// # Ok::<(), ProtocolError>(())
|
||||
/// ```
|
||||
pub fn finish(
|
||||
&self,
|
||||
self,
|
||||
message: CredentialFinalization<CS>,
|
||||
) -> Result<ServerLoginFinishResult, ProtocolError> {
|
||||
) -> Result<ServerLoginFinishResult<CS>, ProtocolError> {
|
||||
let session_key = <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::finish_ke(
|
||||
message.ke3_message,
|
||||
&self.ke2_state,
|
||||
@@ -841,15 +889,86 @@ impl<CS: CipherSuite> ServerLogin<CS> {
|
||||
err => err,
|
||||
})?;
|
||||
|
||||
Ok(ServerLoginFinishResult { session_key })
|
||||
Ok(ServerLoginFinishResult {
|
||||
session_key,
|
||||
_cs: PhantomData,
|
||||
#[cfg(test)]
|
||||
state: self,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn as_byte_ptrs(&self) -> Vec<(*const u8, usize)> {
|
||||
self.ke2_state.as_byte_ptrs()
|
||||
}
|
||||
}
|
||||
|
||||
// Zeroize on drop implementations
|
||||
|
||||
// This can't be derived because of the use of a phantom parameter
|
||||
impl<CS: CipherSuite> Zeroize for ClientRegistration<CS> {
|
||||
fn zeroize(&mut self) {
|
||||
self.token.data.zeroize();
|
||||
self.token.blind.zeroize();
|
||||
}
|
||||
}
|
||||
|
||||
impl<CS: CipherSuite> Drop for ClientRegistration<CS> {
|
||||
fn drop(&mut self) {
|
||||
self.zeroize();
|
||||
}
|
||||
}
|
||||
|
||||
// This can't be derived because of the use of a phantom parameter
|
||||
impl<CS: CipherSuite> Zeroize for ServerRegistration<CS> {
|
||||
fn zeroize(&mut self) {
|
||||
self.envelope.zeroize();
|
||||
self.client_s_pk.zeroize();
|
||||
self.oprf_key.zeroize();
|
||||
}
|
||||
}
|
||||
|
||||
impl<CS: CipherSuite> Drop for ServerRegistration<CS> {
|
||||
fn drop(&mut self) {
|
||||
self.zeroize();
|
||||
}
|
||||
}
|
||||
|
||||
// This can't be derived because of the use of a phantom parameter
|
||||
impl<CS: CipherSuite> Zeroize for ClientLogin<CS> {
|
||||
fn zeroize(&mut self) {
|
||||
self.token.data.zeroize();
|
||||
self.token.blind.zeroize();
|
||||
self.ke1_state.zeroize();
|
||||
self.serialized_credential_request.zeroize();
|
||||
}
|
||||
}
|
||||
|
||||
impl<CS: CipherSuite> Drop for ClientLogin<CS> {
|
||||
fn drop(&mut self) {
|
||||
self.zeroize();
|
||||
}
|
||||
}
|
||||
|
||||
// This can't be derived because of the use of a phantom parameter
|
||||
impl<CS: CipherSuite> Zeroize for ServerLogin<CS> {
|
||||
fn zeroize(&mut self) {
|
||||
self.ke2_state.zeroize();
|
||||
}
|
||||
}
|
||||
|
||||
impl<CS: CipherSuite> Drop for ServerLogin<CS> {
|
||||
fn drop(&mut self) {
|
||||
self.zeroize();
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
fn get_password_derived_key<G: GroupWithMapToCurve, SH: SlowHash<D>, D: Hash>(
|
||||
token: &oprf::Token<G>,
|
||||
beta: G,
|
||||
) -> Result<Vec<u8>, InternalPakeError> {
|
||||
let oprf_output = oprf::finalize::<G, D>(&token.data, &token.blind, beta);
|
||||
SH::hash(oprf_output)
|
||||
) -> Result<Vec<u8>, ProtocolError> {
|
||||
let oprf_output = oprf::finalize::<G, D>(&token.data, &token.blind, beta)?;
|
||||
SH::hash(oprf_output).map_err(ProtocolError::from)
|
||||
}
|
||||
|
||||
+34
-24
@@ -4,7 +4,7 @@
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
use crate::{
|
||||
errors::InternalPakeError, group::Group, hash::Hash, map_to_curve::GroupWithMapToCurve,
|
||||
errors::ProtocolError, group::Group, hash::Hash, map_to_curve::GroupWithMapToCurve,
|
||||
serialization::serialize,
|
||||
};
|
||||
use digest::Digest;
|
||||
@@ -28,9 +28,10 @@ static MODE_BASE: u8 = 0x00;
|
||||
pub(crate) fn blind<R: RngCore + CryptoRng, G: GroupWithMapToCurve, H: Hash>(
|
||||
input: &[u8],
|
||||
blinding_factor_rng: &mut R,
|
||||
) -> Result<(Token<G>, G), InternalPakeError> {
|
||||
let blind = G::random_scalar(blinding_factor_rng);
|
||||
let dst = [STR_VOPRF, &G::get_context_string(MODE_BASE)].concat();
|
||||
) -> Result<(Token<G>, G), ProtocolError> {
|
||||
// Choose a random scalar that must be non-zero
|
||||
let blind = G::random_nonzero_scalar(blinding_factor_rng);
|
||||
let dst = [STR_VOPRF, &G::get_context_string(MODE_BASE)?].concat();
|
||||
let mapped_point = G::map_to_curve::<H>(input, &dst)?;
|
||||
let blind_token = mapped_point * &blind;
|
||||
Ok((
|
||||
@@ -54,7 +55,7 @@ pub(crate) fn finalize<G: GroupWithMapToCurve, H: Hash>(
|
||||
input: &[u8],
|
||||
blind: &G::Scalar,
|
||||
evaluated_element: G,
|
||||
) -> GenericArray<u8, <H as Digest>::OutputSize> {
|
||||
) -> Result<GenericArray<u8, <H as Digest>::OutputSize>, ProtocolError> {
|
||||
let unblinded_element = evaluated_element * &G::scalar_invert(blind);
|
||||
finalize_after_unblind::<G, H>(input, unblinded_element)
|
||||
}
|
||||
@@ -62,15 +63,15 @@ pub(crate) fn finalize<G: GroupWithMapToCurve, H: Hash>(
|
||||
fn finalize_after_unblind<G: GroupWithMapToCurve, H: Hash>(
|
||||
input: &[u8],
|
||||
unblinded_element: G,
|
||||
) -> GenericArray<u8, <H as Digest>::OutputSize> {
|
||||
let finalize_dst = [STR_VOPRF_FINALIZE, &G::get_context_string(MODE_BASE)].concat();
|
||||
) -> Result<GenericArray<u8, <H as Digest>::OutputSize>, ProtocolError> {
|
||||
let finalize_dst = [STR_VOPRF_FINALIZE, &G::get_context_string(MODE_BASE)?].concat();
|
||||
let hash_input = [
|
||||
serialize(input, 2),
|
||||
serialize(&unblinded_element.to_arr().to_vec(), 2),
|
||||
serialize(&finalize_dst, 2),
|
||||
serialize(input, 2)?,
|
||||
serialize(&unblinded_element.to_arr().to_vec(), 2)?,
|
||||
serialize(&finalize_dst, 2)?,
|
||||
]
|
||||
.concat();
|
||||
<H as Digest>::digest(&hash_input)
|
||||
Ok(<H as Digest>::digest(&hash_input))
|
||||
}
|
||||
|
||||
////////////////////////
|
||||
@@ -83,7 +84,7 @@ fn finalize_after_unblind<G: GroupWithMapToCurve, H: Hash>(
|
||||
pub fn blind_shim<R: RngCore + CryptoRng, G: GroupWithMapToCurve, H: Hash>(
|
||||
input: &[u8],
|
||||
blinding_factor_rng: &mut R,
|
||||
) -> Result<(Token<G>, G), InternalPakeError> {
|
||||
) -> Result<(Token<G>, G), ProtocolError> {
|
||||
blind::<R, G, H>(input, blinding_factor_rng)
|
||||
}
|
||||
|
||||
@@ -100,8 +101,8 @@ pub fn evaluate_shim<G: Group>(point: G, oprf_key: &G::Scalar) -> G {
|
||||
pub fn finalize_shim<G: GroupWithMapToCurve, H: Hash>(
|
||||
token: &Token<G>,
|
||||
point: G,
|
||||
) -> Result<GenericArray<u8, <H as Digest>::OutputSize>, InternalPakeError> {
|
||||
Ok(finalize::<G, H>(&token.data, &token.blind, point))
|
||||
) -> Result<GenericArray<u8, <H as Digest>::OutputSize>, ProtocolError> {
|
||||
finalize::<G, H>(&token.data, &token.blind, point)
|
||||
}
|
||||
|
||||
///////////
|
||||
@@ -119,30 +120,34 @@ mod tests {
|
||||
use sha2::Sha512;
|
||||
|
||||
fn prf(input: &[u8], oprf_key: &[u8; 32]) -> GenericArray<u8, <Sha512 as Digest>::OutputSize> {
|
||||
let dst = [STR_VOPRF, &RistrettoPoint::get_context_string(MODE_BASE)].concat();
|
||||
let dst = [
|
||||
STR_VOPRF,
|
||||
&RistrettoPoint::get_context_string(MODE_BASE).unwrap(),
|
||||
]
|
||||
.concat();
|
||||
let point = RistrettoPoint::map_to_curve::<Sha512>(input, &dst).unwrap();
|
||||
let scalar =
|
||||
RistrettoPoint::from_scalar_slice(GenericArray::from_slice(&oprf_key[..])).unwrap();
|
||||
let res = point * scalar;
|
||||
|
||||
finalize_after_unblind::<RistrettoPoint, sha2::Sha512>(&input, res)
|
||||
finalize_after_unblind::<RistrettoPoint, sha2::Sha512>(&input, res).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn oprf_retrieval() -> Result<(), InternalPakeError> {
|
||||
fn oprf_retrieval() {
|
||||
let input = b"hunter2";
|
||||
let mut rng = OsRng;
|
||||
let (token, alpha) = blind::<_, RistrettoPoint, Sha512>(&input[..], &mut rng)?;
|
||||
let (token, alpha) = blind::<_, RistrettoPoint, Sha512>(&input[..], &mut rng).unwrap();
|
||||
let oprf_key_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 oprf_key = RistrettoPoint::from_scalar_slice(&oprf_key_bytes)?;
|
||||
let oprf_key = RistrettoPoint::from_scalar_slice(&oprf_key_bytes).unwrap();
|
||||
let beta = evaluate::<RistrettoPoint>(alpha, &oprf_key);
|
||||
let res = finalize::<RistrettoPoint, sha2::Sha512>(&token.data, &token.blind, beta);
|
||||
let res =
|
||||
finalize::<RistrettoPoint, sha2::Sha512>(&token.data, &token.blind, beta).unwrap();
|
||||
let res2 = prf(&input[..], &oprf_key.as_bytes());
|
||||
assert_eq!(res, res2);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -151,11 +156,16 @@ mod tests {
|
||||
let mut input = vec![0u8; 64];
|
||||
rng.fill_bytes(&mut input);
|
||||
let (token, alpha) = blind::<_, RistrettoPoint, sha2::Sha512>(&input, &mut rng).unwrap();
|
||||
let res = finalize::<RistrettoPoint, sha2::Sha512>(&token.data, &token.blind, alpha);
|
||||
let res =
|
||||
finalize::<RistrettoPoint, sha2::Sha512>(&token.data, &token.blind, alpha).unwrap();
|
||||
|
||||
let dst = [STR_VOPRF, &RistrettoPoint::get_context_string(MODE_BASE)].concat();
|
||||
let dst = [
|
||||
STR_VOPRF,
|
||||
&RistrettoPoint::get_context_string(MODE_BASE).unwrap(),
|
||||
]
|
||||
.concat();
|
||||
let point = RistrettoPoint::map_to_curve::<Sha512>(&input, &dst).unwrap();
|
||||
let res2 = finalize_after_unblind::<RistrettoPoint, sha2::Sha512>(&input, point);
|
||||
let res2 = finalize_after_unblind::<RistrettoPoint, sha2::Sha512>(&input, point).unwrap();
|
||||
|
||||
assert_eq!(res, res2);
|
||||
}
|
||||
|
||||
@@ -6,17 +6,24 @@
|
||||
use crate::errors::PakeError;
|
||||
|
||||
// Corresponds to the I2OSP() function from RFC8017
|
||||
pub(crate) fn i2osp(input: usize, length: usize) -> Vec<u8> {
|
||||
if length <= std::mem::size_of::<usize>() {
|
||||
return (&input.to_be_bytes()[std::mem::size_of::<usize>() - length..]).to_vec();
|
||||
pub(crate) fn i2osp(input: usize, length: usize) -> Result<Vec<u8>, PakeError> {
|
||||
let sizeof_usize = std::mem::size_of::<usize>();
|
||||
|
||||
// Check if input >= 256^length
|
||||
if (sizeof_usize as u32 - input.leading_zeros() / 8) > length as u32 {
|
||||
return Err(PakeError::SerializationError);
|
||||
}
|
||||
|
||||
if length <= sizeof_usize {
|
||||
return Ok((&input.to_be_bytes()[sizeof_usize - length..]).to_vec());
|
||||
}
|
||||
|
||||
let mut output = vec![0u8; length];
|
||||
output.splice(
|
||||
length - std::mem::size_of::<usize>()..length,
|
||||
length - sizeof_usize..length,
|
||||
input.to_be_bytes().iter().cloned(),
|
||||
);
|
||||
output
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
// Corresponds to the OS2IP() function from RFC8017
|
||||
@@ -31,8 +38,8 @@ pub(crate) fn os2ip(input: &[u8]) -> Result<usize, PakeError> {
|
||||
}
|
||||
|
||||
// Computes I2OSP(len(input), max_bytes) || input
|
||||
pub(crate) fn serialize(input: &[u8], max_bytes: usize) -> Vec<u8> {
|
||||
[&i2osp(input.len(), max_bytes), input].concat()
|
||||
pub(crate) fn serialize(input: &[u8], max_bytes: usize) -> Result<Vec<u8>, PakeError> {
|
||||
Ok([&i2osp(input.len(), max_bytes)?, input].concat())
|
||||
}
|
||||
|
||||
// Tokenizes an input of the format I2OSP(len(input), max_bytes) || input, outputting
|
||||
@@ -55,3 +62,22 @@ pub(crate) fn tokenize(input: &[u8], size_bytes: usize) -> Result<(Vec<u8>, Vec<
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
#[cfg(test)]
|
||||
mod unit_tests {
|
||||
use super::*;
|
||||
|
||||
// Test the error condition for I2OSP
|
||||
#[test]
|
||||
fn test_i2osp_err_check() {
|
||||
assert!(i2osp(0, 1).is_ok());
|
||||
|
||||
assert!(i2osp(255, 1).is_ok());
|
||||
assert!(i2osp(256, 1).is_err());
|
||||
assert!(i2osp(257, 1).is_err());
|
||||
|
||||
assert!(i2osp(256 * 256 - 1, 2).is_ok());
|
||||
assert!(i2osp(256 * 256, 2).is_err());
|
||||
assert!(i2osp(256 * 256 + 1, 2).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
+75
-21
@@ -6,6 +6,7 @@
|
||||
use crate::{
|
||||
ciphersuite::CipherSuite,
|
||||
envelope::{Envelope, InnerEnvelopeMode},
|
||||
errors::*,
|
||||
group::Group,
|
||||
key_exchange::{
|
||||
traits::{KeyExchange, ToBytes},
|
||||
@@ -16,7 +17,7 @@ use crate::{
|
||||
*,
|
||||
};
|
||||
|
||||
use curve25519_dalek::ristretto::RistrettoPoint;
|
||||
use curve25519_dalek::{ristretto::RistrettoPoint, traits::Identity};
|
||||
use generic_array::typenum::Unsigned;
|
||||
use generic_bytes::SizedBytes;
|
||||
use proptest::{collection::vec, prelude::*};
|
||||
@@ -54,10 +55,11 @@ fn random_ristretto_point() -> RistrettoPoint {
|
||||
fn client_registration_roundtrip() {
|
||||
let pw = b"hunter2";
|
||||
let mut rng = OsRng;
|
||||
let sc = <RistrettoPoint as Group>::random_scalar(&mut rng);
|
||||
let sc = <RistrettoPoint as Group>::random_nonzero_scalar(&mut rng);
|
||||
let elem = <RistrettoPoint as Group>::base_point() * sc;
|
||||
|
||||
// serialization order: scalar, password
|
||||
let bytes: Vec<u8> = [&sc.as_bytes()[..], &pw[..]].concat();
|
||||
// serialization order: scalar, password, group element
|
||||
let bytes: Vec<u8> = [&elem.to_arr(), &sc.as_bytes()[..], &pw[..]].concat();
|
||||
let reg = ClientRegistration::<Default>::deserialize(&bytes[..]).unwrap();
|
||||
let reg_bytes = reg.serialize();
|
||||
assert_eq!(reg_bytes, bytes);
|
||||
@@ -68,7 +70,7 @@ 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 oprf_key = <RistrettoPoint as Group>::random_scalar(&mut rng);
|
||||
let oprf_key = <RistrettoPoint as Group>::random_nonzero_scalar(&mut rng);
|
||||
let mut oprf_bytes: Vec<u8> = vec![];
|
||||
oprf_bytes.extend_from_slice(oprf_key.as_bytes());
|
||||
let reg = ServerRegistration::<Default>::deserialize(&oprf_bytes[..]).unwrap();
|
||||
@@ -107,6 +109,17 @@ fn registration_request_roundtrip() {
|
||||
let r1 = RegistrationRequest::<Default>::deserialize(input.as_slice()).unwrap();
|
||||
let r1_bytes = r1.serialize();
|
||||
assert_eq!(input, r1_bytes);
|
||||
|
||||
// Assert that identity group element is rejected
|
||||
let identity = RistrettoPoint::identity();
|
||||
let identity_bytes = identity.to_arr().to_vec();
|
||||
|
||||
assert!(
|
||||
match RegistrationRequest::<Default>::deserialize(identity_bytes.as_slice()) {
|
||||
Err(ProtocolError::VerificationError(PakeError::IdentityGroupElementError)) => true,
|
||||
_ => false,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -124,6 +137,17 @@ fn registration_response_roundtrip() {
|
||||
let r2 = RegistrationResponse::<Default>::deserialize(input.as_slice()).unwrap();
|
||||
let r2_bytes = r2.serialize();
|
||||
assert_eq!(input, r2_bytes);
|
||||
|
||||
// Assert that identity group element is rejected
|
||||
let identity = RistrettoPoint::identity();
|
||||
let identity_bytes = identity.to_arr().to_vec();
|
||||
|
||||
assert!(match RegistrationResponse::<Default>::deserialize(
|
||||
&[identity_bytes, pubkey_bytes.to_vec()].concat()
|
||||
) {
|
||||
Err(ProtocolError::VerificationError(PakeError::IdentityGroupElementError)) => true,
|
||||
_ => false,
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -172,7 +196,7 @@ fn credential_request_roundtrip() {
|
||||
|
||||
let ke1m: Vec<u8> = [
|
||||
&client_nonce[..],
|
||||
&serialize(&info.to_vec(), 2),
|
||||
&serialize(&info.to_vec(), 2).unwrap(),
|
||||
&client_e_kp.public(),
|
||||
]
|
||||
.concat();
|
||||
@@ -182,8 +206,19 @@ fn credential_request_roundtrip() {
|
||||
input.extend_from_slice(&ke1m[..]);
|
||||
|
||||
let l1 = CredentialRequest::<Default>::deserialize(input.as_slice()).unwrap();
|
||||
let l1_bytes = l1.serialize();
|
||||
let l1_bytes = l1.serialize().unwrap();
|
||||
assert_eq!(input, l1_bytes);
|
||||
|
||||
// Assert that identity group element is rejected
|
||||
let identity = RistrettoPoint::identity();
|
||||
let identity_bytes = identity.to_arr().to_vec();
|
||||
|
||||
assert!(match CredentialRequest::<Default>::deserialize(
|
||||
&[identity_bytes, ke1m.to_vec()].concat()
|
||||
) {
|
||||
Err(ProtocolError::VerificationError(PakeError::IdentityGroupElementError)) => true,
|
||||
_ => false,
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -222,20 +257,39 @@ fn credential_response_roundtrip() {
|
||||
let ke2m: Vec<u8> = [
|
||||
&server_nonce[..],
|
||||
&server_e_kp.public(),
|
||||
&serialize(&e_info.to_vec(), 2),
|
||||
&serialize(&e_info.to_vec(), 2).unwrap(),
|
||||
&mac[..],
|
||||
]
|
||||
.concat();
|
||||
|
||||
let serialized_envelope = envelope.serialize();
|
||||
|
||||
let mut input = Vec::new();
|
||||
input.extend_from_slice(pt_bytes.as_slice());
|
||||
input.extend_from_slice(&pubkey_bytes.as_slice());
|
||||
input.extend_from_slice(&envelope.serialize());
|
||||
input.extend_from_slice(&serialized_envelope);
|
||||
input.extend_from_slice(&ke2m[..]);
|
||||
|
||||
let l2 = CredentialResponse::<Default>::deserialize(&input).unwrap();
|
||||
let l2_bytes = l2.serialize();
|
||||
let l2_bytes = l2.serialize().unwrap();
|
||||
assert_eq!(input, l2_bytes);
|
||||
|
||||
// Assert that identity group element is rejected
|
||||
let identity = RistrettoPoint::identity();
|
||||
let identity_bytes = identity.to_arr().to_vec();
|
||||
|
||||
assert!(match CredentialResponse::<Default>::deserialize(
|
||||
&[
|
||||
identity_bytes,
|
||||
pubkey_bytes.to_vec(),
|
||||
serialized_envelope,
|
||||
ke2m.to_vec()
|
||||
]
|
||||
.concat()
|
||||
) {
|
||||
Err(ProtocolError::VerificationError(PakeError::IdentityGroupElementError)) => true,
|
||||
_ => false,
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -247,7 +301,7 @@ fn login_third_message_roundtrip() {
|
||||
let input: Vec<u8> = [&mac[..]].concat();
|
||||
|
||||
let l3 = CredentialFinalization::<Default>::deserialize(&input).unwrap();
|
||||
let l3_bytes = l3.serialize();
|
||||
let l3_bytes = l3.serialize().unwrap();
|
||||
assert_eq!(input, l3_bytes);
|
||||
}
|
||||
|
||||
@@ -255,7 +309,7 @@ fn login_third_message_roundtrip() {
|
||||
fn client_login_roundtrip() {
|
||||
let pw = b"hunter2";
|
||||
let mut rng = OsRng;
|
||||
let sc = <RistrettoPoint as Group>::random_scalar(&mut rng);
|
||||
let sc = <RistrettoPoint as Group>::random_nonzero_scalar(&mut rng);
|
||||
|
||||
let client_e_kp = Default::generate_random_keypair(&mut rng);
|
||||
let mut client_nonce = vec![0u8; NonceLen::to_usize()];
|
||||
@@ -267,13 +321,13 @@ fn client_login_roundtrip() {
|
||||
// serialization order: scalar, credential_request, ke1_state, password
|
||||
let bytes: Vec<u8> = [
|
||||
&sc.as_bytes()[..],
|
||||
&serialize(&serialized_credential_request, 2),
|
||||
&serialize(&l1_data, 2),
|
||||
&serialize(&serialized_credential_request, 2).unwrap(),
|
||||
&serialize(&l1_data, 2).unwrap(),
|
||||
&pw[..],
|
||||
]
|
||||
.concat();
|
||||
let reg = ClientLogin::<Default>::deserialize(&bytes[..]).unwrap();
|
||||
let reg_bytes = reg.serialize();
|
||||
let reg_bytes = reg.serialize().unwrap();
|
||||
assert_eq!(reg_bytes, bytes);
|
||||
}
|
||||
|
||||
@@ -290,14 +344,14 @@ fn ke1_message_roundtrip() {
|
||||
|
||||
let ke1m: Vec<u8> = [
|
||||
&client_nonce[..],
|
||||
&serialize(&info.to_vec(), 2),
|
||||
&serialize(&info.to_vec(), 2).unwrap(),
|
||||
&client_e_kp.public(),
|
||||
]
|
||||
.concat();
|
||||
let reg =
|
||||
<TripleDH as KeyExchange<sha2::Sha512, RistrettoPoint>>::KE1Message::try_from(&ke1m[..])
|
||||
.unwrap();
|
||||
let reg_bytes = reg.to_bytes();
|
||||
let reg_bytes = reg.to_bytes().unwrap();
|
||||
assert_eq!(reg_bytes, ke1m);
|
||||
}
|
||||
|
||||
@@ -316,7 +370,7 @@ fn ke2_message_roundtrip() {
|
||||
let ke2m: Vec<u8> = [
|
||||
&server_nonce[..],
|
||||
&server_e_kp.public(),
|
||||
&serialize(&e_info.to_vec(), 2),
|
||||
&serialize(&e_info.to_vec(), 2).unwrap(),
|
||||
&mac[..],
|
||||
]
|
||||
.concat();
|
||||
@@ -324,7 +378,7 @@ fn ke2_message_roundtrip() {
|
||||
let reg =
|
||||
<TripleDH as KeyExchange<sha2::Sha512, RistrettoPoint>>::KE2Message::try_from(&ke2m[..])
|
||||
.unwrap();
|
||||
let reg_bytes = reg.to_bytes();
|
||||
let reg_bytes = reg.to_bytes().unwrap();
|
||||
assert_eq!(reg_bytes, ke2m);
|
||||
}
|
||||
|
||||
@@ -339,7 +393,7 @@ fn ke3_message_roundtrip() {
|
||||
let reg =
|
||||
<TripleDH as KeyExchange<sha2::Sha512, RistrettoPoint>>::KE3Message::try_from(&ke3m[..])
|
||||
.unwrap();
|
||||
let reg_bytes = reg.to_bytes();
|
||||
let reg_bytes = reg.to_bytes().unwrap();
|
||||
assert_eq!(reg_bytes, ke3m);
|
||||
}
|
||||
|
||||
@@ -347,7 +401,7 @@ proptest! {
|
||||
|
||||
#[test]
|
||||
fn test_i2osp_os2ip(bytes in vec(any::<u8>(), 0..std::mem::size_of::<usize>())) {
|
||||
assert_eq!(i2osp(os2ip(&bytes)?, bytes.len()), bytes);
|
||||
assert_eq!(i2osp(os2ip(&bytes)?, bytes.len())?, bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+474
-42
@@ -3,6 +3,8 @@
|
||||
// This source code is licensed under the MIT license found in the
|
||||
// LICENSE file in the root directory of this source tree.
|
||||
|
||||
#![allow(unsafe_code)]
|
||||
|
||||
use crate::{
|
||||
ciphersuite::CipherSuite,
|
||||
errors::*,
|
||||
@@ -14,11 +16,13 @@ use crate::{
|
||||
tests::mock_rng::CycleRng,
|
||||
*,
|
||||
};
|
||||
use curve25519_dalek::ristretto::RistrettoPoint;
|
||||
use curve25519_dalek::{ristretto::RistrettoPoint, traits::Identity};
|
||||
use generic_array::typenum::Unsigned;
|
||||
use generic_bytes::SizedBytes;
|
||||
use rand::{rngs::OsRng, RngCore};
|
||||
use serde_json::Value;
|
||||
use std::slice::from_raw_parts;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
// Tests
|
||||
// =====
|
||||
@@ -65,39 +69,41 @@ pub struct TestVectorParameters {
|
||||
pub session_key: Vec<u8>,
|
||||
}
|
||||
|
||||
static STR_PASSWORD: &str = "password";
|
||||
|
||||
static TEST_VECTOR: &str = r#"
|
||||
{
|
||||
"client_s_pk": "6e0a6082dd29936c44b47ecb8a5fe72e4b321a0ac314b0080ca4c48afdabd215",
|
||||
"client_s_sk": "3000848b34d9073885d427e766b7093cc13bdce992ef31cd00ba2c77ff074504",
|
||||
"client_e_pk": "5260ad6eb47ac1db44babcce9327327d50e1a0133c0425acca3efcf41b119718",
|
||||
"client_e_sk": "1526e0bed9af28830da956589d65768ed2a20d9689e82c90b89e4e33904e4009",
|
||||
"server_s_pk": "c21a38653eb19437669bfa066a446b6eea7c1f253ac7adf4798d6c68e171b273",
|
||||
"server_s_sk": "2a38b385e9fb7e0a89aa0f005c729b0c4e22eeedea8c105cf136d9c5c334880f",
|
||||
"server_e_pk": "125f4a2dd9353c8c37a08527a323908835d3fbc374260d32e829d5c3fa81f325",
|
||||
"server_e_sk": "7ef6ada36f3983c0f24705a29d3a453e3e17c28d347f226b24c0aa5ab12a700d",
|
||||
"client_s_pk": "4275bd003b150aa16dfe06cff83669e2edcf78c18e7608426e5e0d4b706dba18",
|
||||
"client_s_sk": "f0bc6e351b8354e41566c8c9629a9cdceeca640f7e473adaaa1892d97c80640f",
|
||||
"client_e_pk": "165f15328fe643007508b702b14888a14a3bae283cfe5f427aac3e4aae4ca844",
|
||||
"client_e_sk": "c573022221ca5e98bab4556a0d29a8fce930b1b8ac6919f14dc35dececf5c10e",
|
||||
"server_s_pk": "64b44e3d26748057e3d06cc38711e98121ba0baaf8124e95b27ee9d59feabc5b",
|
||||
"server_s_sk": "12be4dd74732e5a4d4eb4069ffa8b5ee19c99f3a61607dad29db239fab868007",
|
||||
"server_e_pk": "d2b56121a97769d578f80d5ffa653b781a7a1db6297a951afd43ef74d132aa53",
|
||||
"server_e_sk": "1cd12dc39777dd5a3bfd4969de2402d9cb074837faa996425e871458e71b7404",
|
||||
"id_u": "696455",
|
||||
"id_s": "696453",
|
||||
"password": "70617373776f7264",
|
||||
"blinding_factor": "a32862d66eb57246321fb6b229e83786745c3afdf8957ebe38b01c17571ba105",
|
||||
"oprf_key": "0851f5ec43e0b702bfcb9d8ec208085b51c0fc5200234901407c176327aa0b02",
|
||||
"envelope_nonce": "78b006042d011bdca5d0058a978d2103a6d546de311a2e8cd025fbc67303a468",
|
||||
"client_nonce": "43497a6f86ba31a6a7f399271fb3b4b4f82c4af086bc431ebaeab7d768ff8a64",
|
||||
"server_nonce": "680cd27da18bc56d4317e7db61de1726a70ebe4e49aee38a3bdb6787cf85466e",
|
||||
"blinding_factor": "f8ab29fa00f1412b78b2f4c993ae3fab4ee644ddf30a784892b3d0d81d838200",
|
||||
"oprf_key": "ca00ce46525fa67aa5101420f2f0643c3e5c12ac8e283d83d7d28213ca777a08",
|
||||
"envelope_nonce": "2695cb3afd4df2029b8d3353f7220876619ad8109e27da917114245071613474",
|
||||
"client_nonce": "8b7a191f297023b5abdf54c4896d57d0c168a2dde7dce00f32886c016298f606",
|
||||
"server_nonce": "2477b1825fc64764e309facda26b5cb046af668d1c2a4e88aab0138317784825",
|
||||
"info1": "696e666f31",
|
||||
"einfo2": "65696e666f32",
|
||||
"registration_request": "14ba86e53018ce5507d2bfb2d98ad3f60e302d826bff3410a5ec669c8e1ef17d",
|
||||
"registration_response": "aee5c937a85acfbdbd71faf1c5519bfe9e44b0b7489dcc663df9f1ca5b520b6ac21a38653eb19437669bfa066a446b6eea7c1f253ac7adf4798d6c68e171b273",
|
||||
"registration_upload": "6e0a6082dd29936c44b47ecb8a5fe72e4b321a0ac314b0080ca4c48afdabd2150278b006042d011bdca5d0058a978d2103a6d546de311a2e8cd025fbc67303a4687618b32fe2ec2a5c2b6efecec1e6e535106de80af68733673daf0b644965966fa3f279e532d7ecef363f8d55ff6df4c473cfb1049a73f632972bfcc6744185d13a671dd6678d49fb1629a6fbfbe266937378fd9e772c2dd72692d1a35c020010",
|
||||
"credential_request": "14ba86e53018ce5507d2bfb2d98ad3f60e302d826bff3410a5ec669c8e1ef17d43497a6f86ba31a6a7f399271fb3b4b4f82c4af086bc431ebaeab7d768ff8a640005696e666f315260ad6eb47ac1db44babcce9327327d50e1a0133c0425acca3efcf41b119718",
|
||||
"credential_response": "aee5c937a85acfbdbd71faf1c5519bfe9e44b0b7489dcc663df9f1ca5b520b6ac21a38653eb19437669bfa066a446b6eea7c1f253ac7adf4798d6c68e171b2730278b006042d011bdca5d0058a978d2103a6d546de311a2e8cd025fbc67303a4687618b32fe2ec2a5c2b6efecec1e6e535106de80af68733673daf0b644965966fa3f279e532d7ecef363f8d55ff6df4c473cfb1049a73f632972bfcc6744185d13a671dd6678d49fb1629a6fbfbe266937378fd9e772c2dd72692d1a35c020010680cd27da18bc56d4317e7db61de1726a70ebe4e49aee38a3bdb6787cf85466e125f4a2dd9353c8c37a08527a323908835d3fbc374260d32e829d5c3fa81f32500068a4c321f3c375613862ba83f7e5abb8f1d26dbd8035d39f192eb1324c2214457098054a2cbdf5d4ab2894eaf152af8c4be61d701c5a1ab1ec3e1cee5810898140b81771db0be",
|
||||
"credential_finalization": "330aa8ac01bbc9d9642fe1c286187379efe12da14aceab86b22449d21f242d89adabc0295751b4e007beabc413ae9cbf9979e324749953705fd85b87c9c1b1a2",
|
||||
"client_registration_state": "a32862d66eb57246321fb6b229e83786745c3afdf8957ebe38b01c17571ba10570617373776f7264",
|
||||
"client_login_state": "a32862d66eb57246321fb6b229e83786745c3afdf8957ebe38b01c17571ba105006714ba86e53018ce5507d2bfb2d98ad3f60e302d826bff3410a5ec669c8e1ef17d43497a6f86ba31a6a7f399271fb3b4b4f82c4af086bc431ebaeab7d768ff8a640005696e666f315260ad6eb47ac1db44babcce9327327d50e1a0133c0425acca3efcf41b11971800401526e0bed9af28830da956589d65768ed2a20d9689e82c90b89e4e33904e400943497a6f86ba31a6a7f399271fb3b4b4f82c4af086bc431ebaeab7d768ff8a6470617373776f7264",
|
||||
"server_registration_state": "0851f5ec43e0b702bfcb9d8ec208085b51c0fc5200234901407c176327aa0b02",
|
||||
"server_login_state": "89cd93dce8f59cf7b187736c50cbc3ca2e0bbbad0a0be1ddee180a2d95db60695b384be2dd673434ef94bcdbb1b457f63f41cd79ed2422c021c34ca1433b70576920ebb950ad1c40c8c015ec6832e12427e755ba21d005f0b6d5d66d2368ab5ec6f32151fced5a3aa25472c425912242de3638ef57f28b0dd02a956064e5bb9b8fcd73add52d233a454d6b20125e9506a95aae8772ebbfac4d70efe1fb10078fd40f93d84aa7db53853ca74436c917c427cd5c2e408860937e6f7ab80816ca47",
|
||||
"password_file": "0851f5ec43e0b702bfcb9d8ec208085b51c0fc5200234901407c176327aa0b026e0a6082dd29936c44b47ecb8a5fe72e4b321a0ac314b0080ca4c48afdabd2150278b006042d011bdca5d0058a978d2103a6d546de311a2e8cd025fbc67303a4687618b32fe2ec2a5c2b6efecec1e6e535106de80af68733673daf0b644965966fa3f279e532d7ecef363f8d55ff6df4c473cfb1049a73f632972bfcc6744185d13a671dd6678d49fb1629a6fbfbe266937378fd9e772c2dd72692d1a35c020010",
|
||||
"export_key": "8197f91f0d4de1ab126d8dfd06abd0d5df420ce40a135ef376e4ffe515930f413632390e7dc3dcfd19afff62b9113e10eb6c359fc327df6e9ad4d0f06c242322",
|
||||
"session_key": "8fcd73add52d233a454d6b20125e9506a95aae8772ebbfac4d70efe1fb10078fd40f93d84aa7db53853ca74436c917c427cd5c2e408860937e6f7ab80816ca47"
|
||||
"registration_request": "28259e84c7eb4b776a87481d4761cafac4b5585bf6f7f4939734c422fc82aa5d",
|
||||
"registration_response": "94a37d9174f7083c44f85017027a6371941ad74eb35b2c666498bd7c5eef3c2a64b44e3d26748057e3d06cc38711e98121ba0baaf8124e95b27ee9d59feabc5b",
|
||||
"registration_upload": "4275bd003b150aa16dfe06cff83669e2edcf78c18e7608426e5e0d4b706dba18022695cb3afd4df2029b8d3353f7220876619ad8109e27da9171142450716134746a4dba2b7bf8e8763cad213fa81c9511135be6e42c44a2806e9fb416dc760884136277c6644682cddbf03a8472dca2925ceb9d7d804c7b616843bdf4e80ae35672aff300a03dfb6e79c9079509645a790f792caebc9b07003ead9d98d1d179c1",
|
||||
"credential_request": "28259e84c7eb4b776a87481d4761cafac4b5585bf6f7f4939734c422fc82aa5d8b7a191f297023b5abdf54c4896d57d0c168a2dde7dce00f32886c016298f6060005696e666f31165f15328fe643007508b702b14888a14a3bae283cfe5f427aac3e4aae4ca844",
|
||||
"credential_response": "94a37d9174f7083c44f85017027a6371941ad74eb35b2c666498bd7c5eef3c2a64b44e3d26748057e3d06cc38711e98121ba0baaf8124e95b27ee9d59feabc5b022695cb3afd4df2029b8d3353f7220876619ad8109e27da9171142450716134746a4dba2b7bf8e8763cad213fa81c9511135be6e42c44a2806e9fb416dc760884136277c6644682cddbf03a8472dca2925ceb9d7d804c7b616843bdf4e80ae35672aff300a03dfb6e79c9079509645a790f792caebc9b07003ead9d98d1d179c12477b1825fc64764e309facda26b5cb046af668d1c2a4e88aab0138317784825d2b56121a97769d578f80d5ffa653b781a7a1db6297a951afd43ef74d132aa530006b71e6bcb7951b448e2b8ad7ab0b358019af909e03090e0f5a65d0f26186e31b10bfc1024e60a5f0c2dbb2648b33d59a3447e9d5618048efe55b7ea9476f90132520d78e3d91f",
|
||||
"credential_finalization": "0df019bc4652ad1539de43bfdd33761ac0b526ff27c46fd1d7c89c6d46c5d179beeab30c060cf7bf0e17114812c1ac9d90369e76c6284e4a04b43ff9a67afb87",
|
||||
"client_registration_state": "28259e84c7eb4b776a87481d4761cafac4b5585bf6f7f4939734c422fc82aa5df8ab29fa00f1412b78b2f4c993ae3fab4ee644ddf30a784892b3d0d81d83820070617373776f7264",
|
||||
"client_login_state": "f8ab29fa00f1412b78b2f4c993ae3fab4ee644ddf30a784892b3d0d81d838200006728259e84c7eb4b776a87481d4761cafac4b5585bf6f7f4939734c422fc82aa5d8b7a191f297023b5abdf54c4896d57d0c168a2dde7dce00f32886c016298f6060005696e666f31165f15328fe643007508b702b14888a14a3bae283cfe5f427aac3e4aae4ca8440040c573022221ca5e98bab4556a0d29a8fce930b1b8ac6919f14dc35dececf5c10e8b7a191f297023b5abdf54c4896d57d0c168a2dde7dce00f32886c016298f60670617373776f7264",
|
||||
"server_registration_state": "ca00ce46525fa67aa5101420f2f0643c3e5c12ac8e283d83d7d28213ca777a08",
|
||||
"server_login_state": "b4c7da6422bd9558887022647974a6b391df989ba0f421eec64bdd8e8329f2aec5fab88b8fd55a2071ac02ffd67acb35a3a7d1a3d9dcbb5406542a2a24b1ed6d7378a115cb801e1cd64c152bfbde36b4c22d77e5d98888ec9af51489315ade5f3f34252c9aafc211e7de9703a039def76adef33074be35a978e729ca125aecfed13415079fc276064cb61eada57c6b59041fbd8cdf4ba55cfb8a5ea9c6e100f85b6cfdd85ffbe99e6e2d80fc9c5f8399aa838e384e8451ce78efde5aa8f03c15",
|
||||
"password_file": "ca00ce46525fa67aa5101420f2f0643c3e5c12ac8e283d83d7d28213ca777a084275bd003b150aa16dfe06cff83669e2edcf78c18e7608426e5e0d4b706dba18022695cb3afd4df2029b8d3353f7220876619ad8109e27da9171142450716134746a4dba2b7bf8e8763cad213fa81c9511135be6e42c44a2806e9fb416dc760884136277c6644682cddbf03a8472dca2925ceb9d7d804c7b616843bdf4e80ae35672aff300a03dfb6e79c9079509645a790f792caebc9b07003ead9d98d1d179c1",
|
||||
"export_key": "88514619c2a0ec5567cedb69c6d0e5596ace9bf5414b326e3cb80f41a62d55d4343f2c5eb41b030f25cbf9d9d0956a20cf70255f356f143f15a175a3adaa37c2",
|
||||
"session_key": "d13415079fc276064cb61eada57c6b59041fbd8cdf4ba55cfb8a5ea9c6e100f85b6cfdd85ffbe99e6e2d80fc9c5f8399aa838e384e8451ce78efde5aa8f03c15"
|
||||
}
|
||||
"#;
|
||||
|
||||
@@ -279,7 +285,7 @@ fn generate_parameters<CS: CipherSuite>() -> TestVectorParameters {
|
||||
let mut server_nonce = vec![0u8; NonceLen::to_usize()];
|
||||
rng.fill_bytes(&mut server_nonce);
|
||||
|
||||
let blinding_factor = CS::Group::random_scalar(&mut rng);
|
||||
let blinding_factor = CS::Group::random_nonzero_scalar(&mut rng);
|
||||
let blinding_factor_bytes = CS::Group::scalar_as_bytes(&blinding_factor).clone();
|
||||
|
||||
let info1 = b"info1";
|
||||
@@ -352,8 +358,16 @@ fn generate_parameters<CS: CipherSuite>() -> TestVectorParameters {
|
||||
ClientLoginStartParameters::WithInfo(info1.to_vec()),
|
||||
)
|
||||
.unwrap();
|
||||
let credential_request_bytes = client_login_start_result.message.serialize().to_vec();
|
||||
let client_login_state = client_login_start_result.state.serialize().to_vec();
|
||||
let credential_request_bytes = client_login_start_result
|
||||
.message
|
||||
.serialize()
|
||||
.unwrap()
|
||||
.to_vec();
|
||||
let client_login_state = client_login_start_result
|
||||
.state
|
||||
.serialize()
|
||||
.unwrap()
|
||||
.to_vec();
|
||||
|
||||
let mut server_e_sk_and_nonce_rng = CycleRng::new(
|
||||
[
|
||||
@@ -374,8 +388,16 @@ fn generate_parameters<CS: CipherSuite>() -> TestVectorParameters {
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
let credential_response_bytes = server_login_start_result.message.serialize().to_vec();
|
||||
let server_login_state = server_login_start_result.state.serialize().to_vec();
|
||||
let credential_response_bytes = server_login_start_result
|
||||
.message
|
||||
.serialize()
|
||||
.unwrap()
|
||||
.to_vec();
|
||||
let server_login_state = server_login_start_result
|
||||
.state
|
||||
.serialize()
|
||||
.unwrap()
|
||||
.to_vec();
|
||||
|
||||
let client_login_finish_result = client_login_start_result
|
||||
.state
|
||||
@@ -384,7 +406,7 @@ fn generate_parameters<CS: CipherSuite>() -> TestVectorParameters {
|
||||
ClientLoginFinishParameters::WithIdentifiers(id_u.to_vec(), id_s.to_vec()),
|
||||
)
|
||||
.unwrap();
|
||||
let credential_finalization_bytes = client_login_finish_result.message.serialize();
|
||||
let credential_finalization_bytes = client_login_finish_result.message.serialize().unwrap();
|
||||
|
||||
TestVectorParameters {
|
||||
client_s_pk: client_s_kp.public().to_arr().to_vec(),
|
||||
@@ -416,7 +438,7 @@ fn generate_parameters<CS: CipherSuite>() -> TestVectorParameters {
|
||||
server_registration_state,
|
||||
client_login_state,
|
||||
server_login_state,
|
||||
session_key: client_login_finish_result.session_key,
|
||||
session_key: client_login_finish_result.session_key.clone(),
|
||||
export_key: client_registration_finish_result.export_key.to_vec(),
|
||||
}
|
||||
}
|
||||
@@ -529,11 +551,11 @@ fn test_credential_request() -> Result<(), ProtocolError> {
|
||||
)?;
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.credential_request),
|
||||
hex::encode(client_login_start_result.message.serialize())
|
||||
hex::encode(client_login_start_result.message.serialize().unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.client_login_state),
|
||||
hex::encode(client_login_start_result.state.serialize())
|
||||
hex::encode(client_login_start_result.state.serialize()?)
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
@@ -563,11 +585,11 @@ fn test_credential_response() -> Result<(), ProtocolError> {
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.credential_response),
|
||||
hex::encode(server_login_start_result.message.serialize())
|
||||
hex::encode(server_login_start_result.message.serialize().unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.server_login_state),
|
||||
hex::encode(server_login_start_result.state.serialize())
|
||||
hex::encode(server_login_start_result.state.serialize().unwrap())
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
@@ -600,7 +622,7 @@ fn test_credential_finalization() -> Result<(), ProtocolError> {
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.credential_finalization),
|
||||
hex::encode(client_login_finish_result.message.serialize())
|
||||
hex::encode(client_login_finish_result.message.serialize().unwrap())
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.export_key),
|
||||
@@ -623,7 +645,7 @@ fn test_server_login_finish() -> Result<(), ProtocolError> {
|
||||
|
||||
assert_eq!(
|
||||
hex::encode(parameters.session_key),
|
||||
hex::encode(server_login_result.session_key)
|
||||
hex::encode(&server_login_result.session_key)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
@@ -680,8 +702,8 @@ fn test_complete_flow(
|
||||
.finish(client_login_finish_result.message)?;
|
||||
|
||||
assert_eq!(
|
||||
hex::encode(server_login_finish_result.session_key),
|
||||
hex::encode(client_login_finish_result.session_key)
|
||||
hex::encode(&server_login_finish_result.session_key),
|
||||
hex::encode(&client_login_finish_result.session_key)
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(client_registration_finish_result.export_key),
|
||||
@@ -706,3 +728,413 @@ fn test_complete_flow_success() -> Result<(), ProtocolError> {
|
||||
fn test_complete_flow_fail() -> Result<(), ProtocolError> {
|
||||
test_complete_flow(b"good password", b"bad password")
|
||||
}
|
||||
|
||||
// Zeroize tests
|
||||
|
||||
#[test]
|
||||
fn test_zeroize_client_registration_start() -> Result<(), ProtocolError> {
|
||||
let mut client_rng = OsRng;
|
||||
let client_registration_start_result =
|
||||
ClientRegistration::<RistrettoSha5123dhNoSlowHash>::start(
|
||||
&mut client_rng,
|
||||
STR_PASSWORD.as_bytes(),
|
||||
)?;
|
||||
|
||||
let mut state = client_registration_start_result.state;
|
||||
let ptrs = state.as_byte_ptrs();
|
||||
state.zeroize();
|
||||
|
||||
for (ptr, len) in ptrs {
|
||||
let bytes = unsafe { from_raw_parts(ptr, len) };
|
||||
assert!(bytes.iter().all(|&x| x == 0));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zeroize_server_registration_start() -> Result<(), ProtocolError> {
|
||||
let mut client_rng = OsRng;
|
||||
let mut server_rng = OsRng;
|
||||
let server_kp = RistrettoSha5123dhNoSlowHash::generate_random_keypair(&mut server_rng);
|
||||
let client_registration_start_result =
|
||||
ClientRegistration::<RistrettoSha5123dhNoSlowHash>::start(
|
||||
&mut client_rng,
|
||||
STR_PASSWORD.as_bytes(),
|
||||
)?;
|
||||
let server_registration_start_result =
|
||||
ServerRegistration::<RistrettoSha5123dhNoSlowHash>::start(
|
||||
&mut server_rng,
|
||||
client_registration_start_result.message,
|
||||
server_kp.public(),
|
||||
)?;
|
||||
|
||||
let mut state = server_registration_start_result.state;
|
||||
let ptrs = state.as_byte_ptrs();
|
||||
state.zeroize();
|
||||
|
||||
for (ptr, len) in ptrs {
|
||||
let bytes = unsafe { from_raw_parts(ptr, len) };
|
||||
assert!(bytes.iter().all(|&x| x == 0));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zeroize_client_registration_finish() -> Result<(), ProtocolError> {
|
||||
let mut client_rng = OsRng;
|
||||
let mut server_rng = OsRng;
|
||||
let server_kp = RistrettoSha5123dhNoSlowHash::generate_random_keypair(&mut server_rng);
|
||||
let client_registration_start_result =
|
||||
ClientRegistration::<RistrettoSha5123dhNoSlowHash>::start(
|
||||
&mut client_rng,
|
||||
STR_PASSWORD.as_bytes(),
|
||||
)?;
|
||||
let server_registration_start_result =
|
||||
ServerRegistration::<RistrettoSha5123dhNoSlowHash>::start(
|
||||
&mut server_rng,
|
||||
client_registration_start_result.message,
|
||||
server_kp.public(),
|
||||
)?;
|
||||
let client_registration_finish_result = client_registration_start_result.state.finish(
|
||||
&mut client_rng,
|
||||
server_registration_start_result.message,
|
||||
ClientRegistrationFinishParameters::default(),
|
||||
)?;
|
||||
|
||||
let mut state = client_registration_finish_result.state;
|
||||
let ptrs = state.as_byte_ptrs();
|
||||
state.zeroize();
|
||||
|
||||
for (ptr, len) in ptrs {
|
||||
let bytes = unsafe { from_raw_parts(ptr, len) };
|
||||
assert!(bytes.iter().all(|&x| x == 0));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zeroize_server_registration_finish() -> Result<(), ProtocolError> {
|
||||
let mut client_rng = OsRng;
|
||||
let mut server_rng = OsRng;
|
||||
let server_kp = RistrettoSha5123dhNoSlowHash::generate_random_keypair(&mut server_rng);
|
||||
let client_registration_start_result =
|
||||
ClientRegistration::<RistrettoSha5123dhNoSlowHash>::start(
|
||||
&mut client_rng,
|
||||
STR_PASSWORD.as_bytes(),
|
||||
)?;
|
||||
let server_registration_start_result =
|
||||
ServerRegistration::<RistrettoSha5123dhNoSlowHash>::start(
|
||||
&mut server_rng,
|
||||
client_registration_start_result.message,
|
||||
server_kp.public(),
|
||||
)?;
|
||||
let client_registration_finish_result = client_registration_start_result.state.finish(
|
||||
&mut client_rng,
|
||||
server_registration_start_result.message,
|
||||
ClientRegistrationFinishParameters::default(),
|
||||
)?;
|
||||
let p_file = server_registration_start_result
|
||||
.state
|
||||
.finish(client_registration_finish_result.message)?;
|
||||
|
||||
let mut state = p_file;
|
||||
let ptrs = state.as_byte_ptrs();
|
||||
state.zeroize();
|
||||
|
||||
for (ptr, len) in ptrs {
|
||||
let bytes = unsafe { from_raw_parts(ptr, len) };
|
||||
assert!(bytes.iter().all(|&x| x == 0));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zeroize_client_login_start() -> Result<(), ProtocolError> {
|
||||
let mut client_rng = OsRng;
|
||||
let client_login_start_result = ClientLogin::<RistrettoSha5123dhNoSlowHash>::start(
|
||||
&mut client_rng,
|
||||
STR_PASSWORD.as_bytes(),
|
||||
ClientLoginStartParameters::default(),
|
||||
)?;
|
||||
|
||||
let mut state = client_login_start_result.state;
|
||||
let ptrs = state.as_byte_ptrs();
|
||||
state.zeroize();
|
||||
|
||||
for (ptr, len) in ptrs {
|
||||
let bytes = unsafe { from_raw_parts(ptr, len) };
|
||||
assert!(bytes.iter().all(|&x| x == 0));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zeroize_server_login_start() -> Result<(), ProtocolError> {
|
||||
let mut client_rng = OsRng;
|
||||
let mut server_rng = OsRng;
|
||||
let server_kp = RistrettoSha5123dhNoSlowHash::generate_random_keypair(&mut server_rng);
|
||||
let client_registration_start_result =
|
||||
ClientRegistration::<RistrettoSha5123dhNoSlowHash>::start(
|
||||
&mut client_rng,
|
||||
STR_PASSWORD.as_bytes(),
|
||||
)?;
|
||||
let server_registration_start_result =
|
||||
ServerRegistration::<RistrettoSha5123dhNoSlowHash>::start(
|
||||
&mut server_rng,
|
||||
client_registration_start_result.message,
|
||||
server_kp.public(),
|
||||
)?;
|
||||
let client_registration_finish_result = client_registration_start_result.state.finish(
|
||||
&mut client_rng,
|
||||
server_registration_start_result.message,
|
||||
ClientRegistrationFinishParameters::default(),
|
||||
)?;
|
||||
let p_file = server_registration_start_result
|
||||
.state
|
||||
.finish(client_registration_finish_result.message)?;
|
||||
let client_login_start_result = ClientLogin::<RistrettoSha5123dhNoSlowHash>::start(
|
||||
&mut client_rng,
|
||||
STR_PASSWORD.as_bytes(),
|
||||
ClientLoginStartParameters::default(),
|
||||
)?;
|
||||
let server_login_start_result = ServerLogin::<RistrettoSha5123dhNoSlowHash>::start(
|
||||
&mut server_rng,
|
||||
p_file,
|
||||
&server_kp.private(),
|
||||
client_login_start_result.message,
|
||||
ServerLoginStartParameters::default(),
|
||||
)?;
|
||||
|
||||
let mut state = server_login_start_result.state;
|
||||
let ptrs = state.as_byte_ptrs();
|
||||
state.zeroize();
|
||||
|
||||
for (ptr, len) in ptrs {
|
||||
let bytes = unsafe { from_raw_parts(ptr, len) };
|
||||
assert!(bytes.iter().all(|&x| x == 0));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zeroize_client_login_finish() -> Result<(), ProtocolError> {
|
||||
let mut client_rng = OsRng;
|
||||
let mut server_rng = OsRng;
|
||||
let server_kp = RistrettoSha5123dhNoSlowHash::generate_random_keypair(&mut server_rng);
|
||||
let client_registration_start_result =
|
||||
ClientRegistration::<RistrettoSha5123dhNoSlowHash>::start(
|
||||
&mut client_rng,
|
||||
STR_PASSWORD.as_bytes(),
|
||||
)?;
|
||||
let server_registration_start_result =
|
||||
ServerRegistration::<RistrettoSha5123dhNoSlowHash>::start(
|
||||
&mut server_rng,
|
||||
client_registration_start_result.message,
|
||||
server_kp.public(),
|
||||
)?;
|
||||
let client_registration_finish_result = client_registration_start_result.state.finish(
|
||||
&mut client_rng,
|
||||
server_registration_start_result.message,
|
||||
ClientRegistrationFinishParameters::default(),
|
||||
)?;
|
||||
let p_file = server_registration_start_result
|
||||
.state
|
||||
.finish(client_registration_finish_result.message)?;
|
||||
let client_login_start_result = ClientLogin::<RistrettoSha5123dhNoSlowHash>::start(
|
||||
&mut client_rng,
|
||||
STR_PASSWORD.as_bytes(),
|
||||
ClientLoginStartParameters::default(),
|
||||
)?;
|
||||
let server_login_start_result = ServerLogin::<RistrettoSha5123dhNoSlowHash>::start(
|
||||
&mut server_rng,
|
||||
p_file,
|
||||
&server_kp.private(),
|
||||
client_login_start_result.message,
|
||||
ServerLoginStartParameters::default(),
|
||||
)?;
|
||||
let client_login_finish_result = client_login_start_result.state.finish(
|
||||
server_login_start_result.message,
|
||||
ClientLoginFinishParameters::default(),
|
||||
)?;
|
||||
|
||||
let mut state = client_login_finish_result.state;
|
||||
let ptrs = state.as_byte_ptrs();
|
||||
state.zeroize();
|
||||
|
||||
for (ptr, len) in ptrs {
|
||||
let bytes = unsafe { from_raw_parts(ptr, len) };
|
||||
assert!(bytes.iter().all(|&x| x == 0));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zeroize_server_login_finish() -> Result<(), ProtocolError> {
|
||||
let mut client_rng = OsRng;
|
||||
let mut server_rng = OsRng;
|
||||
let server_kp = RistrettoSha5123dhNoSlowHash::generate_random_keypair(&mut server_rng);
|
||||
let client_registration_start_result =
|
||||
ClientRegistration::<RistrettoSha5123dhNoSlowHash>::start(
|
||||
&mut client_rng,
|
||||
STR_PASSWORD.as_bytes(),
|
||||
)?;
|
||||
let server_registration_start_result =
|
||||
ServerRegistration::<RistrettoSha5123dhNoSlowHash>::start(
|
||||
&mut server_rng,
|
||||
client_registration_start_result.message,
|
||||
server_kp.public(),
|
||||
)?;
|
||||
let client_registration_finish_result = client_registration_start_result.state.finish(
|
||||
&mut client_rng,
|
||||
server_registration_start_result.message,
|
||||
ClientRegistrationFinishParameters::default(),
|
||||
)?;
|
||||
let p_file = server_registration_start_result
|
||||
.state
|
||||
.finish(client_registration_finish_result.message)?;
|
||||
let client_login_start_result = ClientLogin::<RistrettoSha5123dhNoSlowHash>::start(
|
||||
&mut client_rng,
|
||||
STR_PASSWORD.as_bytes(),
|
||||
ClientLoginStartParameters::default(),
|
||||
)?;
|
||||
let server_login_start_result = ServerLogin::<RistrettoSha5123dhNoSlowHash>::start(
|
||||
&mut server_rng,
|
||||
p_file,
|
||||
&server_kp.private(),
|
||||
client_login_start_result.message,
|
||||
ServerLoginStartParameters::default(),
|
||||
)?;
|
||||
let client_login_finish_result = client_login_start_result.state.finish(
|
||||
server_login_start_result.message,
|
||||
ClientLoginFinishParameters::default(),
|
||||
)?;
|
||||
let server_login_finish_result = server_login_start_result
|
||||
.state
|
||||
.finish(client_login_finish_result.message)?;
|
||||
|
||||
let mut state = server_login_finish_result.state;
|
||||
let ptrs = state.as_byte_ptrs();
|
||||
state.zeroize();
|
||||
|
||||
for (ptr, len) in ptrs {
|
||||
let bytes = unsafe { from_raw_parts(ptr, len) };
|
||||
assert!(bytes.iter().all(|&x| x == 0));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_scalar_always_nonzero() -> Result<(), ProtocolError> {
|
||||
// Start out with a bunch of zeros to force resampling of scalar
|
||||
let mut client_registration_rng = CycleRng::new([vec![0u8; 128], vec![1u8; 128]].concat());
|
||||
let client_registration_start_result =
|
||||
ClientRegistration::<RistrettoSha5123dhNoSlowHash>::start(
|
||||
&mut client_registration_rng,
|
||||
STR_PASSWORD.as_bytes(),
|
||||
)?;
|
||||
|
||||
assert_ne!(
|
||||
RistrettoPoint::identity(),
|
||||
client_registration_start_result
|
||||
.message
|
||||
.get_alpha_for_testing()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reflected_value_error_registration() -> Result<(), ProtocolError> {
|
||||
let password = b"password";
|
||||
let mut client_rng = OsRng;
|
||||
let mut server_rng = OsRng;
|
||||
let server_kp = RistrettoSha5123dhNoSlowHash::generate_random_keypair(&mut server_rng);
|
||||
let client_registration_start_result =
|
||||
ClientRegistration::<RistrettoSha5123dhNoSlowHash>::start(&mut client_rng, password)?;
|
||||
let alpha = client_registration_start_result
|
||||
.message
|
||||
.get_alpha_for_testing();
|
||||
let server_registration_start_result =
|
||||
ServerRegistration::<RistrettoSha5123dhNoSlowHash>::start(
|
||||
&mut server_rng,
|
||||
client_registration_start_result.message,
|
||||
server_kp.public(),
|
||||
)?;
|
||||
|
||||
let reflected_registration_response = server_registration_start_result
|
||||
.message
|
||||
.set_beta_for_testing(alpha);
|
||||
|
||||
let client_registration_finish_result = client_registration_start_result.state.finish(
|
||||
&mut client_rng,
|
||||
reflected_registration_response,
|
||||
ClientRegistrationFinishParameters::default(),
|
||||
);
|
||||
|
||||
assert!(match client_registration_finish_result {
|
||||
Err(ProtocolError::ReflectedValueError) => true,
|
||||
_ => false,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reflected_value_error_login() -> Result<(), ProtocolError> {
|
||||
let password = b"password";
|
||||
let mut client_rng = OsRng;
|
||||
let mut server_rng = OsRng;
|
||||
let server_kp = RistrettoSha5123dhNoSlowHash::generate_random_keypair(&mut server_rng);
|
||||
let client_registration_start_result =
|
||||
ClientRegistration::<RistrettoSha5123dhNoSlowHash>::start(&mut client_rng, password)?;
|
||||
let server_registration_start_result =
|
||||
ServerRegistration::<RistrettoSha5123dhNoSlowHash>::start(
|
||||
&mut server_rng,
|
||||
client_registration_start_result.message,
|
||||
server_kp.public(),
|
||||
)?;
|
||||
let client_registration_finish_result = client_registration_start_result.state.finish(
|
||||
&mut client_rng,
|
||||
server_registration_start_result.message,
|
||||
ClientRegistrationFinishParameters::default(),
|
||||
)?;
|
||||
let p_file = server_registration_start_result
|
||||
.state
|
||||
.finish(client_registration_finish_result.message)?;
|
||||
let client_login_start_result = ClientLogin::<RistrettoSha5123dhNoSlowHash>::start(
|
||||
&mut client_rng,
|
||||
password,
|
||||
ClientLoginStartParameters::default(),
|
||||
)?;
|
||||
let alpha = client_login_start_result.message.get_alpha_for_testing();
|
||||
let server_login_start_result = ServerLogin::<RistrettoSha5123dhNoSlowHash>::start(
|
||||
&mut server_rng,
|
||||
p_file,
|
||||
&server_kp.private(),
|
||||
client_login_start_result.message,
|
||||
ServerLoginStartParameters::default(),
|
||||
)?;
|
||||
|
||||
let reflected_credential_response = server_login_start_result
|
||||
.message
|
||||
.set_beta_for_testing(alpha.clone());
|
||||
|
||||
let client_login_result = client_login_start_result.state.finish(
|
||||
reflected_credential_response,
|
||||
ClientLoginFinishParameters::default(),
|
||||
);
|
||||
|
||||
assert!(match client_login_result {
|
||||
Err(ProtocolError::ReflectedValueError) => true,
|
||||
_ => false,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -457,7 +457,7 @@ fn test_ke1() -> Result<(), ProtocolError> {
|
||||
)?;
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.KE1),
|
||||
hex::encode(client_login_start_result.message.serialize())
|
||||
hex::encode(client_login_start_result.message.serialize()?)
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
@@ -492,7 +492,7 @@ fn test_ke2() -> Result<(), ProtocolError> {
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.KE2),
|
||||
hex::encode(server_login_start_result.message.serialize())
|
||||
hex::encode(server_login_start_result.message.serialize()?)
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
@@ -536,7 +536,7 @@ fn test_ke3() -> Result<(), ProtocolError> {
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.KE3),
|
||||
hex::encode(client_login_finish_result.message.serialize())
|
||||
hex::encode(client_login_finish_result.message.serialize()?)
|
||||
);
|
||||
assert_eq!(
|
||||
hex::encode(¶meters.export_key),
|
||||
@@ -576,7 +576,7 @@ fn test_server_login_finish() -> Result<(), ProtocolError> {
|
||||
|
||||
assert_eq!(
|
||||
hex::encode(parameters.session_key),
|
||||
hex::encode(server_login_result.session_key)
|
||||
hex::encode(&server_login_result.session_key)
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -63,7 +63,7 @@ fn populate_test_vectors(values: &Value) -> VOPRFTestVectorParameters {
|
||||
|
||||
// Tests input -> blind, blinded_element
|
||||
#[test]
|
||||
fn test_blind() -> Result<(), PakeError> {
|
||||
fn test_blind() -> Result<(), ProtocolError> {
|
||||
for tv in OPRF_RISTRETTO255_SHA512 {
|
||||
let parameters = populate_test_vectors(&serde_json::from_str(tv).unwrap());
|
||||
let mut rng = CycleRng::new(parameters.blind.to_vec());
|
||||
@@ -106,7 +106,7 @@ fn test_evaluate() -> Result<(), PakeError> {
|
||||
|
||||
// Tests input, blind, evaluation_element -> output
|
||||
#[test]
|
||||
fn test_finalize() -> Result<(), PakeError> {
|
||||
fn test_finalize() -> Result<(), ProtocolError> {
|
||||
for tv in OPRF_RISTRETTO255_SHA512 {
|
||||
let parameters = populate_test_vectors(&serde_json::from_str(tv).unwrap());
|
||||
|
||||
@@ -116,7 +116,7 @@ fn test_finalize() -> Result<(), PakeError> {
|
||||
RistrettoPoint::from_element_slice(GenericArray::from_slice(
|
||||
¶meters.evaluation_element,
|
||||
))?,
|
||||
);
|
||||
)?;
|
||||
|
||||
assert_eq!(¶meters.output, &output.to_vec());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user