SIGMA-I Key Exchange (#378)
* Move `KeGroup` to `KeyExchange::Group` - Introduce `KeyExchange::Hash`, which separates the OPRF hash from the one used in `KeyExchange`. - Remove `De/Serialize` requirement on key exchange messages and states, which forced a lot of where bounds on downstream users. - Rename `KeGroup` to `Group`. - Replace `D` generic for hash with `H`. * Use `voprf::derive_key()` directly * Implement SIGMA-I key exchange * Improve `KeyExchange` for SIGMA-I and Ed25519 * Implement EdDSA * Un-qualify some method calls * SIGMA-I: only include client identity in client mac * SIGMA-I: include server mac in client signature * Expose key exchange types in `crate` & move modules * Implement Ed25519ph * Document `ed25519` crate feature * Remove `ristretto255-voprf` crate feature * Adjust CI crate feature testing * Fix Rustdoc * Remove unnecessary generic parameters from SIGMA-I * Properly mark to-do's with TODO * Assorted fixes * SIGMA-I: include context in signature * SIGMA-I: include identifiers in signature * Merge `ServerLoginStart/FinishParameters` * Re-export more necessary types * More carefully expose types * Add ECDSA test * SIGMA-I: share context hashing * De-duplicate client static public key storage * Hide `KeyExchange` better * Use the correct hash in the root documentation * Bump `derive-where` * Format documentation examples a bit further * Add remote OPRF seed documentation * Rename `deserialize_key_pair` to `deserialize_take_key_pair` * Add more key tests * Remove `SharedSecret` trait * SIGMA-I refactor message API * Share more implementation between 3DH and SIGMA-I * Remove unnecessary zero scalar check for Curve25519 * Use correct hash in test * Add some more TODOs * Exclude `tests` folder from Cargo publishing * Enable missing dependencies * Use right crate for testing Ed25519 * Remove unnecessary `Sized` constraints * Remove unnecessary `ecdsa` crate features * Move signature de/serialization to trait methods * Nit: move import to appropriate location * Add warning to SIGMA-I
This commit is contained in:
@@ -8,26 +8,25 @@
|
||||
|
||||
//! Key Exchange group implementation for Curve25519
|
||||
|
||||
pub use curve25519_dalek;
|
||||
use curve25519_dalek::montgomery::MontgomeryPoint;
|
||||
use curve25519_dalek::scalar;
|
||||
use curve25519_dalek::traits::Identity;
|
||||
use digest::core_api::BlockSizeUser;
|
||||
use digest::{FixedOutput, HashMarker, OutputSizeUser};
|
||||
use generic_array::typenum::{IsLess, IsLessOrEqual, U256, U32};
|
||||
use generic_array::typenum::U32;
|
||||
use generic_array::GenericArray;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use subtle::ConstantTimeEq;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use super::KeGroup;
|
||||
use super::Group;
|
||||
use crate::errors::{InternalError, ProtocolError};
|
||||
use crate::key_exchange::tripledh::DiffieHellman;
|
||||
use crate::key_exchange::shared::DiffieHellman;
|
||||
use crate::serialization::SliceExt;
|
||||
|
||||
/// Implementation for Curve25519.
|
||||
pub struct Curve25519;
|
||||
|
||||
/// The implementation of such a subgroup for Curve25519
|
||||
impl KeGroup for Curve25519 {
|
||||
impl Group for Curve25519 {
|
||||
type Pk = MontgomeryPoint;
|
||||
type PkLen = U32;
|
||||
type Sk = Scalar;
|
||||
@@ -37,50 +36,28 @@ impl KeGroup for Curve25519 {
|
||||
pk.to_bytes().into()
|
||||
}
|
||||
|
||||
fn deserialize_pk(bytes: &[u8]) -> Result<Self::Pk, ProtocolError> {
|
||||
fn deserialize_take_pk(bytes: &mut &[u8]) -> Result<Self::Pk, ProtocolError> {
|
||||
bytes
|
||||
.try_into()
|
||||
.take_array::<U32>("public key")
|
||||
.ok()
|
||||
.map(MontgomeryPoint)
|
||||
.map(|array| MontgomeryPoint(array.into()))
|
||||
.filter(|pk| pk != &MontgomeryPoint::identity())
|
||||
.ok_or(ProtocolError::SerializationError)
|
||||
}
|
||||
|
||||
fn random_sk<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Sk {
|
||||
loop {
|
||||
// Sample 32 random bytes and then clamp, as described in https://cr.yp.to/ecdh.html
|
||||
let mut scalar_bytes = [0u8; 32];
|
||||
rng.fill_bytes(&mut scalar_bytes);
|
||||
let scalar = scalar::clamp_integer(scalar_bytes);
|
||||
// Sample 32 random bytes and then clamp, as described in https://cr.yp.to/ecdh.html
|
||||
let mut scalar_bytes = [0u8; 32];
|
||||
rng.fill_bytes(&mut scalar_bytes);
|
||||
let scalar = scalar::clamp_integer(scalar_bytes);
|
||||
|
||||
if scalar != curve25519_dalek::Scalar::ZERO.to_bytes() {
|
||||
break Scalar(scalar);
|
||||
}
|
||||
}
|
||||
Scalar(scalar)
|
||||
}
|
||||
|
||||
fn hash_to_scalar<'a, H>(_input: &[&[u8]], _dst: &[&[u8]]) -> Result<Self::Sk, InternalError>
|
||||
where
|
||||
H: BlockSizeUser + Default + FixedOutput + HashMarker,
|
||||
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>,
|
||||
{
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn derive_auth_keypair<CS: voprf::CipherSuite>(
|
||||
seed: GenericArray<u8, Self::SkLen>,
|
||||
) -> Result<Self::Sk, InternalError>
|
||||
where
|
||||
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||
{
|
||||
fn derive_scalar(seed: GenericArray<u8, Self::SkLen>) -> Result<Self::Sk, InternalError> {
|
||||
Ok(Scalar(scalar::clamp_integer(seed.into())))
|
||||
}
|
||||
|
||||
fn is_zero_scalar(scalar: Self::Sk) -> subtle::Choice {
|
||||
scalar.0.ct_eq(&curve25519_dalek::Scalar::ZERO.to_bytes())
|
||||
}
|
||||
|
||||
fn public_key(sk: Self::Sk) -> Self::Pk {
|
||||
MontgomeryPoint::mul_base_clamped(sk.0)
|
||||
}
|
||||
@@ -89,22 +66,21 @@ impl KeGroup for Curve25519 {
|
||||
sk.0.into()
|
||||
}
|
||||
|
||||
fn deserialize_sk(bytes: &[u8]) -> Result<Self::Sk, ProtocolError> {
|
||||
fn deserialize_take_sk(bytes: &mut &[u8]) -> Result<Self::Sk, ProtocolError> {
|
||||
bytes
|
||||
.try_into()
|
||||
.take_array::<U32>("secret key")
|
||||
.ok()
|
||||
.and_then(|bytes| {
|
||||
let scalar = scalar::clamp_integer(bytes);
|
||||
(scalar == bytes).then_some(scalar)
|
||||
let scalar = scalar::clamp_integer(bytes.into());
|
||||
(scalar == *bytes).then_some(scalar)
|
||||
})
|
||||
.filter(|scalar| scalar != &curve25519_dalek::Scalar::ZERO.to_bytes())
|
||||
.map(Scalar)
|
||||
.ok_or(ProtocolError::SerializationError)
|
||||
}
|
||||
}
|
||||
|
||||
/// Curve25519 scalar.
|
||||
#[derive(Clone, Copy, Zeroize)]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Zeroize)]
|
||||
pub struct Scalar([u8; 32]);
|
||||
|
||||
impl DiffieHellman<Curve25519> for Scalar {
|
||||
@@ -112,3 +88,36 @@ impl DiffieHellman<Curve25519> for Scalar {
|
||||
Curve25519::serialize_pk(pk.mul_clamped(self.0))
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////
|
||||
// Test Implementations //
|
||||
//===================== //
|
||||
//////////////////////////
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::serialization::AssertZeroized;
|
||||
|
||||
#[cfg(test)]
|
||||
impl AssertZeroized for MontgomeryPoint {
|
||||
fn assert_zeroized(&self) {
|
||||
assert_eq!(*self, MontgomeryPoint::default());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl AssertZeroized for Scalar {
|
||||
fn assert_zeroized(&self) {
|
||||
assert_eq!(*self, Scalar(<_>::default()));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_zero_scalar() {
|
||||
use std::vec;
|
||||
|
||||
use crate::tests::mock_rng::CycleRng;
|
||||
|
||||
let mut rng = CycleRng::new(vec![0]);
|
||||
let sk = Curve25519::random_sk(&mut rng);
|
||||
assert_ne!(sk.0, curve25519_dalek::Scalar::ZERO.to_bytes());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,459 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
//
|
||||
// This source code is dual-licensed under either the MIT license found in the
|
||||
// LICENSE-MIT file in the root directory of this source tree or the Apache
|
||||
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
|
||||
// of this source tree. You may select, at your option, one of the above-listed
|
||||
// licenses.
|
||||
|
||||
//! Key Exchange group implementation for Ed25519
|
||||
|
||||
use core::iter;
|
||||
|
||||
use curve25519_dalek::edwards::CompressedEdwardsY;
|
||||
use curve25519_dalek::traits::IsIdentity;
|
||||
use curve25519_dalek::{EdwardsPoint, Scalar};
|
||||
use digest::Digest;
|
||||
pub use ed25519_dalek;
|
||||
use ed25519_dalek::hazmat::ExpandedSecretKey;
|
||||
use ed25519_dalek::{SecretKey, Sha512};
|
||||
use generic_array::sequence::Concat;
|
||||
use generic_array::typenum::{U32, U64};
|
||||
use generic_array::GenericArray;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use super::Group;
|
||||
use crate::ciphersuite::CipherSuite;
|
||||
use crate::errors::{InternalError, ProtocolError};
|
||||
use crate::key_exchange::sigma_i::hash_eddsa::implementation::HashEddsaImpl;
|
||||
use crate::key_exchange::sigma_i::pure_eddsa::implementation::PureEddsaImpl;
|
||||
pub use crate::key_exchange::sigma_i::shared::PreHash;
|
||||
use crate::key_exchange::sigma_i::{CachedMessage, Message, MessageBuilder};
|
||||
use crate::serialization::{SliceExt, UpdateExt};
|
||||
|
||||
/// Implementation for Ed25519.
|
||||
pub struct Ed25519;
|
||||
|
||||
impl Group for Ed25519 {
|
||||
type Pk = VerifyingKey;
|
||||
type PkLen = U32;
|
||||
type Sk = SigningKey;
|
||||
type SkLen = U32;
|
||||
|
||||
fn serialize_pk(pk: Self::Pk) -> GenericArray<u8, Self::PkLen> {
|
||||
pk.compressed.0.into()
|
||||
}
|
||||
|
||||
fn deserialize_take_pk(bytes: &mut &[u8]) -> Result<Self::Pk, ProtocolError> {
|
||||
let compressed = bytes
|
||||
.take_array("public key")
|
||||
.map(|bytes| CompressedEdwardsY(bytes.into()))?;
|
||||
|
||||
if let Some(point) = compressed.decompress().filter(|point| !point.is_identity()) {
|
||||
Ok(VerifyingKey { point, compressed })
|
||||
} else {
|
||||
Err(ProtocolError::SerializationError)
|
||||
}
|
||||
}
|
||||
|
||||
fn random_sk<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Sk {
|
||||
let mut sk = <[u8; 32]>::default();
|
||||
rng.fill_bytes(&mut sk);
|
||||
|
||||
SigningKey::from_bytes(sk)
|
||||
}
|
||||
|
||||
fn derive_scalar(seed: GenericArray<u8, Self::SkLen>) -> Result<Self::Sk, InternalError> {
|
||||
Ok(SigningKey::from_bytes(seed.into()))
|
||||
}
|
||||
|
||||
fn public_key(sk: Self::Sk) -> Self::Pk {
|
||||
sk.verifying_key
|
||||
}
|
||||
|
||||
fn serialize_sk(sk: Self::Sk) -> GenericArray<u8, Self::SkLen> {
|
||||
sk.sk.into()
|
||||
}
|
||||
|
||||
fn deserialize_take_sk(bytes: &mut &[u8]) -> Result<Self::Sk, ProtocolError> {
|
||||
Ok(SigningKey::from_bytes(
|
||||
bytes.take_array("secret key")?.into(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
/// Ed25519 verifying key.
|
||||
// `ed25519_dalek::VerifyingKey` doesn't implement `Zeroize`.
|
||||
// TODO: remove after https://github.com/dalek-cryptography/curve25519-dalek/pull/747.
|
||||
// Required for manual implementation of EdDSA.
|
||||
// TODO: remove after https://github.com/dalek-cryptography/curve25519-dalek/pull/556.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Zeroize)]
|
||||
pub struct VerifyingKey {
|
||||
point: EdwardsPoint,
|
||||
compressed: CompressedEdwardsY,
|
||||
}
|
||||
|
||||
/// Ed25519 siging key.
|
||||
// We store the `ExpandedSecret` in memory to avoid computing it on demand and then discarding it
|
||||
// again.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Zeroize)]
|
||||
pub struct SigningKey {
|
||||
// `ed25519_dalek::SigningKey` doesn't implement `Zeroize`. See
|
||||
// https://github.com/dalek-cryptography/curve25519-dalek/pull/747
|
||||
// Required for manual implementation of EdDSA.
|
||||
// TODO: remove after https://github.com/dalek-cryptography/curve25519-dalek/pull/556.
|
||||
sk: SecretKey,
|
||||
verifying_key: VerifyingKey,
|
||||
// `ed25519_dalek::ExpandedSecret` doesn't implement traits we need. See
|
||||
// TODO: remove after https://github.com/dalek-cryptography/curve25519-dalek/pull/748 and
|
||||
// https://github.com/dalek-cryptography/curve25519-dalek/pull/747.
|
||||
scalar: Scalar,
|
||||
hash_prefix: [u8; 32],
|
||||
}
|
||||
|
||||
impl SigningKey {
|
||||
fn from_bytes(sk: [u8; 32]) -> Self {
|
||||
let ExpandedSecretKey {
|
||||
scalar,
|
||||
hash_prefix,
|
||||
} = ExpandedSecretKey::from(&sk);
|
||||
let point = EdwardsPoint::mul_base(&scalar);
|
||||
let verifying_key = VerifyingKey {
|
||||
point,
|
||||
compressed: point.compress(),
|
||||
};
|
||||
|
||||
SigningKey {
|
||||
sk,
|
||||
verifying_key,
|
||||
scalar,
|
||||
hash_prefix,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PureEddsaImpl for Ed25519 {
|
||||
type Signature = Signature;
|
||||
type SignatureLen = U64;
|
||||
|
||||
fn sign<CS: CipherSuite, KE: Group>(
|
||||
sk: &Self::Sk,
|
||||
message: &Message<CS, KE>,
|
||||
) -> (Self::Signature, CachedMessage<CS, KE>) {
|
||||
(sign(sk, false, message.sign_message()), message.to_cached())
|
||||
}
|
||||
|
||||
/// Validates that the signature was created by signing the given message
|
||||
/// with the corresponding private key.
|
||||
fn verify<CS: CipherSuite, KE: Group>(
|
||||
pk: &Self::Pk,
|
||||
message_builder: MessageBuilder<'_, CS>,
|
||||
state: CachedMessage<CS, KE>,
|
||||
signature: &Self::Signature,
|
||||
) -> Result<(), ProtocolError> {
|
||||
verify(
|
||||
pk,
|
||||
false,
|
||||
message_builder.build::<KE>(state).verify_message(),
|
||||
signature,
|
||||
)
|
||||
}
|
||||
|
||||
fn deserialize_take_signature(bytes: &mut &[u8]) -> Result<Self::Signature, ProtocolError> {
|
||||
Signature::deserialize_take(bytes)
|
||||
}
|
||||
|
||||
fn serialize_signature(signature: &Self::Signature) -> GenericArray<u8, Self::SignatureLen> {
|
||||
signature.serialize()
|
||||
}
|
||||
}
|
||||
|
||||
impl HashEddsaImpl for Ed25519 {
|
||||
type Signature = Signature;
|
||||
type SignatureLen = U64;
|
||||
type VerifyState<CS: CipherSuite, KE: Group> = PreHash<Sha512>;
|
||||
|
||||
fn sign<CS: CipherSuite, KE: Group>(
|
||||
sk: &Self::Sk,
|
||||
message: &Message<CS, KE>,
|
||||
) -> (Self::Signature, Self::VerifyState<CS, KE>) {
|
||||
let hash = message.hash::<Sha512>();
|
||||
|
||||
(
|
||||
sign(sk, true, iter::once(hash.sign.finalize().as_slice())),
|
||||
PreHash(hash.verify.finalize()),
|
||||
)
|
||||
}
|
||||
|
||||
/// Validates that the signature was created by signing the given message
|
||||
/// with the corresponding private key.
|
||||
fn verify<CS: CipherSuite, KE: Group>(
|
||||
pk: &Self::Pk,
|
||||
state: Self::VerifyState<CS, KE>,
|
||||
signature: &Self::Signature,
|
||||
) -> Result<(), ProtocolError> {
|
||||
verify(pk, true, iter::once(state.0.as_slice()), signature)
|
||||
}
|
||||
|
||||
fn deserialize_take_signature(bytes: &mut &[u8]) -> Result<Self::Signature, ProtocolError> {
|
||||
Signature::deserialize_take(bytes)
|
||||
}
|
||||
|
||||
fn serialize_signature(signature: &Self::Signature) -> GenericArray<u8, Self::SignatureLen> {
|
||||
signature.serialize()
|
||||
}
|
||||
}
|
||||
|
||||
// This contains a manual implementation of EdDSA because `ed25519-dalek`
|
||||
// doesn't support message streaming. See
|
||||
// TODO: remove after https://github.com/dalek-cryptography/curve25519-dalek/pull/556.
|
||||
fn sign<'a>(
|
||||
sk: &SigningKey,
|
||||
pre_hash: bool,
|
||||
message: impl Clone + Iterator<Item = &'a [u8]>,
|
||||
) -> Signature {
|
||||
let mut h = Sha512::new();
|
||||
|
||||
if pre_hash {
|
||||
h.update(b"SigEd25519 no Ed25519 collisions");
|
||||
h.update([1]); // Ed25519ph
|
||||
h.update([0]);
|
||||
}
|
||||
|
||||
h.update(sk.hash_prefix);
|
||||
h.update_iter(message.clone());
|
||||
|
||||
let r = Scalar::from_hash(h);
|
||||
#[allow(non_snake_case)]
|
||||
let R = EdwardsPoint::mul_base(&r).compress();
|
||||
|
||||
h = Sha512::new();
|
||||
|
||||
if pre_hash {
|
||||
h.update(b"SigEd25519 no Ed25519 collisions");
|
||||
h.update([1]); // Ed25519ph
|
||||
h.update([0]);
|
||||
}
|
||||
|
||||
h.update(R.as_bytes());
|
||||
h.update(sk.verifying_key.compressed.0);
|
||||
h.update_iter(message);
|
||||
|
||||
let k = Scalar::from_hash(h);
|
||||
let s: Scalar = (k * sk.scalar) + r;
|
||||
|
||||
Signature { R, s }
|
||||
}
|
||||
|
||||
fn verify<'a>(
|
||||
pk: &VerifyingKey,
|
||||
pre_hash: bool,
|
||||
message: impl Iterator<Item = &'a [u8]>,
|
||||
signature: &Signature,
|
||||
) -> Result<(), ProtocolError> {
|
||||
let mut h = Sha512::new();
|
||||
|
||||
if pre_hash {
|
||||
h.update(b"SigEd25519 no Ed25519 collisions");
|
||||
h.update([1]); // Ed25519ph
|
||||
h.update([0]);
|
||||
}
|
||||
|
||||
h.update(signature.R.as_bytes());
|
||||
h.update(pk.compressed.as_bytes());
|
||||
h.update_iter(message);
|
||||
let k = Scalar::from_hash(h);
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
let minus_A: EdwardsPoint = -pk.point;
|
||||
#[allow(non_snake_case)]
|
||||
let expected_R =
|
||||
EdwardsPoint::vartime_double_scalar_mul_basepoint(&k, &(minus_A), &signature.s).compress();
|
||||
|
||||
if expected_R == signature.R {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(ProtocolError::InvalidLoginError)
|
||||
}
|
||||
}
|
||||
|
||||
/// Ed25519 Signature.
|
||||
// `ed25519_dalek::Signature` doesn't implement validation with Serde de/serialization.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
#[allow(non_snake_case)]
|
||||
pub struct Signature {
|
||||
R: CompressedEdwardsY,
|
||||
s: Scalar,
|
||||
}
|
||||
|
||||
impl Signature {
|
||||
/// Expects the `R` and `s` components of a Ed25519 signature with no added
|
||||
/// framing.
|
||||
pub fn from_slice(mut bytes: &[u8]) -> Result<Self, ProtocolError> {
|
||||
Self::deserialize_take(&mut bytes)
|
||||
}
|
||||
|
||||
fn deserialize_take(bytes: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
#[allow(non_snake_case)]
|
||||
let R = CompressedEdwardsY(bytes.take_array("signature R")?.into());
|
||||
|
||||
let s = Scalar::from_canonical_bytes(bytes.take_array("signature s")?.into())
|
||||
.into_option()
|
||||
.ok_or(ProtocolError::SerializationError)?;
|
||||
|
||||
Ok(Self { R, s })
|
||||
}
|
||||
|
||||
fn serialize(&self) -> GenericArray<u8, U64> {
|
||||
GenericArray::from(self.R.0).concat(GenericArray::from(self.s.to_bytes()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl<'de> serde::Deserialize<'de> for Signature {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
use serde::de::Error;
|
||||
|
||||
Signature::deserialize_take(
|
||||
&mut (GenericArray::<_, U64>::deserialize(deserializer)?.as_slice()),
|
||||
)
|
||||
.map_err(D::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
impl serde::Serialize for Signature {
|
||||
fn serialize<SK>(&self, serializer: SK) -> Result<SK::Ok, SK::Error>
|
||||
where
|
||||
SK: serde::Serializer,
|
||||
{
|
||||
self.serialize().serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl Zeroize for Signature {
|
||||
fn zeroize(&mut self) {
|
||||
self.R.0 = [0; 32];
|
||||
self.s = Scalar::default();
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////
|
||||
// Test Implementations //
|
||||
//===================== //
|
||||
//////////////////////////
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::serialization::AssertZeroized;
|
||||
|
||||
#[cfg(test)]
|
||||
impl AssertZeroized for VerifyingKey {
|
||||
fn assert_zeroized(&self) {
|
||||
use curve25519_dalek::traits::Identity;
|
||||
|
||||
let Self { point, compressed } = self;
|
||||
|
||||
assert_eq!(point, &EdwardsPoint::identity());
|
||||
assert_eq!(compressed, &EdwardsPoint::identity().compress());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl AssertZeroized for SigningKey {
|
||||
fn assert_zeroized(&self) {
|
||||
let Self {
|
||||
sk,
|
||||
verifying_key,
|
||||
scalar,
|
||||
hash_prefix,
|
||||
} = self;
|
||||
|
||||
verifying_key.assert_zeroized();
|
||||
|
||||
for byte in sk.iter().chain(scalar.to_bytes().iter()).chain(hash_prefix) {
|
||||
assert_eq!(byte, &0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::iter;
|
||||
|
||||
use ed25519_dalek::{Signer, SigningKey, Verifier, VerifyingKey};
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn pure_eddsa() {
|
||||
let mut message = [0; 1024];
|
||||
OsRng.fill_bytes(&mut message);
|
||||
|
||||
let mut sk = SecretKey::default();
|
||||
OsRng.fill_bytes(&mut sk);
|
||||
let signing_key = SigningKey::from_bytes(&sk);
|
||||
|
||||
let signature = signing_key.sign(&message);
|
||||
|
||||
let custom_sk = Ed25519::deserialize_take_sk(&mut sk.as_slice()).unwrap();
|
||||
let custom_signature = sign(&custom_sk, false, iter::once(message.as_slice()));
|
||||
|
||||
assert_eq!(
|
||||
signature.to_bytes(),
|
||||
custom_signature.serialize().as_slice()
|
||||
);
|
||||
|
||||
let verifying_key = VerifyingKey::from(&signing_key);
|
||||
verifying_key.verify(&message, &signature).unwrap();
|
||||
|
||||
let custom_pk = Ed25519::public_key(custom_sk);
|
||||
verify(
|
||||
&custom_pk,
|
||||
false,
|
||||
iter::once(message.as_slice()),
|
||||
&custom_signature,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_eddsa() {
|
||||
let mut message = [0; 1024];
|
||||
OsRng.fill_bytes(&mut message);
|
||||
let message = Sha512::new_with_prefix(message);
|
||||
let pre_hash = message.clone().finalize();
|
||||
|
||||
let mut sk = SecretKey::default();
|
||||
OsRng.fill_bytes(&mut sk);
|
||||
let signing_key = SigningKey::from_bytes(&sk);
|
||||
|
||||
let signature = signing_key.sign_prehashed(message.clone(), None).unwrap();
|
||||
|
||||
let custom_sk = Ed25519::deserialize_take_sk(&mut sk.as_slice()).unwrap();
|
||||
let custom_signature = sign(&custom_sk, true, iter::once(pre_hash.as_slice()));
|
||||
|
||||
assert_eq!(
|
||||
signature.to_bytes(),
|
||||
custom_signature.serialize().as_slice()
|
||||
);
|
||||
|
||||
let verifying_key = VerifyingKey::from(&signing_key);
|
||||
verifying_key
|
||||
.verify_prehashed(message, None, &signature)
|
||||
.unwrap();
|
||||
|
||||
let custom_pk = Ed25519::public_key(custom_sk);
|
||||
verify(
|
||||
&custom_pk,
|
||||
true,
|
||||
iter::once(pre_hash.as_slice()),
|
||||
&custom_signature,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
@@ -6,101 +6,157 @@
|
||||
// of this source tree. You may select, at your option, one of the above-listed
|
||||
// licenses.
|
||||
|
||||
use digest::core_api::BlockSizeUser;
|
||||
use digest::{FixedOutput, HashMarker};
|
||||
use elliptic_curve::group::cofactor::CofactorGroup;
|
||||
use elliptic_curve::hash2curve::{ExpandMsgXmd, FromOkm, GroupDigest};
|
||||
use elliptic_curve::sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint};
|
||||
//! Implementation for EC curves via [`elliptic_curve`] traits.
|
||||
|
||||
use core::fmt::{self, Debug, Formatter};
|
||||
|
||||
use derive_where::derive_where;
|
||||
use elliptic_curve::group::GroupEncoding;
|
||||
use elliptic_curve::ops::MulByGenerator;
|
||||
use elliptic_curve::sec1::{ModulusSize, ToEncodedPoint};
|
||||
use elliptic_curve::{
|
||||
AffinePoint, Field, FieldBytesSize, Group, ProjectivePoint, PublicKey, Scalar, SecretKey,
|
||||
point, CurveArithmetic, FieldBytesSize, Group as _, NonZeroScalar, ProjectivePoint, Scalar,
|
||||
SecretKey,
|
||||
};
|
||||
use generic_array::typenum::{IsLess, IsLessOrEqual, U256};
|
||||
use generic_array::GenericArray;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use voprf::Mode;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use super::KeGroup;
|
||||
use super::{Group, STR_OPAQUE_DERIVE_AUTH_KEY_PAIR};
|
||||
use crate::errors::{InternalError, ProtocolError};
|
||||
use crate::key_exchange::tripledh::DiffieHellman;
|
||||
use crate::key_exchange::shared::DiffieHellman;
|
||||
use crate::serialization::SliceExt;
|
||||
|
||||
impl<G> KeGroup for G
|
||||
impl<G> Group for G
|
||||
where
|
||||
G: GroupDigest,
|
||||
Self: CurveArithmetic + voprf::CipherSuite<Group = Self> + voprf::Group<Scalar = Scalar<Self>>,
|
||||
FieldBytesSize<Self>: ModulusSize,
|
||||
AffinePoint<Self>: FromEncodedPoint<Self> + ToEncodedPoint<Self>,
|
||||
ProjectivePoint<Self>: CofactorGroup + ToEncodedPoint<Self>,
|
||||
Scalar<Self>: FromOkm,
|
||||
ProjectivePoint<Self>: GroupEncoding<
|
||||
Repr = GenericArray<u8, <FieldBytesSize<Self> as ModulusSize>::CompressedPointSize>,
|
||||
> + ToEncodedPoint<Self>,
|
||||
{
|
||||
type Pk = ProjectivePoint<Self>;
|
||||
type Pk = NonIdentity<Self>;
|
||||
|
||||
type PkLen = <FieldBytesSize<Self> as ModulusSize>::CompressedPointSize;
|
||||
|
||||
type Sk = Scalar<Self>;
|
||||
type Sk = NonZeroScalar<Self>;
|
||||
|
||||
type SkLen = FieldBytesSize<Self>;
|
||||
|
||||
fn serialize_pk(pk: Self::Pk) -> GenericArray<u8, Self::PkLen> {
|
||||
GenericArray::clone_from_slice(pk.to_encoded_point(true).as_bytes())
|
||||
GenericArray::clone_from_slice(pk.0.to_encoded_point(true).as_bytes())
|
||||
}
|
||||
|
||||
fn deserialize_pk(bytes: &[u8]) -> Result<Self::Pk, ProtocolError> {
|
||||
PublicKey::<Self>::from_sec1_bytes(bytes)
|
||||
.map(|public_key| public_key.to_projective())
|
||||
.map_err(|_| ProtocolError::SerializationError)
|
||||
fn deserialize_take_pk(bytes: &mut &[u8]) -> Result<Self::Pk, ProtocolError> {
|
||||
point::NonIdentity::<ProjectivePoint<Self>>::from_bytes(&bytes.take_array("public key")?)
|
||||
.into_option()
|
||||
.map(NonIdentity)
|
||||
.ok_or(ProtocolError::SerializationError)
|
||||
}
|
||||
|
||||
fn random_sk<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Sk {
|
||||
*SecretKey::<Self>::random(rng).to_nonzero_scalar()
|
||||
SecretKey::<Self>::random(rng).to_nonzero_scalar()
|
||||
}
|
||||
|
||||
// Implements the `HashToScalar()` function from
|
||||
// <https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-19.html#section-4>
|
||||
fn hash_to_scalar<H>(input: &[&[u8]], dst: &[&[u8]]) -> Result<Self::Sk, InternalError>
|
||||
where
|
||||
H: BlockSizeUser + Default + FixedOutput + HashMarker,
|
||||
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>,
|
||||
{
|
||||
Self::hash_to_scalar::<ExpandMsgXmd<H>>(input, dst)
|
||||
.map_err(|_| InternalError::HashToScalar)
|
||||
.and_then(|scalar| {
|
||||
if bool::from(scalar.is_zero()) {
|
||||
Err(InternalError::HashToScalar)
|
||||
} else {
|
||||
Ok(scalar)
|
||||
}
|
||||
fn derive_scalar(seed: GenericArray<u8, Self::SkLen>) -> Result<Self::Sk, InternalError> {
|
||||
voprf::derive_key::<Self>(&seed, &STR_OPAQUE_DERIVE_AUTH_KEY_PAIR, Mode::Oprf)
|
||||
.map(|scalar| {
|
||||
NonZeroScalar::new(scalar).expect("`voprf::derive_key()` returned a zero scalar")
|
||||
})
|
||||
.map_err(InternalError::from)
|
||||
}
|
||||
|
||||
fn public_key(sk: Self::Sk) -> Self::Pk {
|
||||
ProjectivePoint::<Self>::generator() * sk
|
||||
}
|
||||
|
||||
fn is_zero_scalar(scalar: Self::Sk) -> subtle::Choice {
|
||||
scalar.is_zero()
|
||||
// Non-panicking version in https://github.com/RustCrypto/traits/pull/1833.
|
||||
NonIdentity(
|
||||
point::NonIdentity::new(ProjectivePoint::<Self>::mul_by_generator(&*sk))
|
||||
.expect("multiplying with a non-zero scalar can never yield the identity element"),
|
||||
)
|
||||
}
|
||||
|
||||
fn serialize_sk(sk: Self::Sk) -> GenericArray<u8, Self::SkLen> {
|
||||
sk.into()
|
||||
}
|
||||
|
||||
fn deserialize_sk(bytes: &[u8]) -> Result<Self::Sk, ProtocolError> {
|
||||
SecretKey::<Self>::from_slice(bytes)
|
||||
.map(|secret_key| *secret_key.to_nonzero_scalar())
|
||||
fn deserialize_take_sk(bytes: &mut &[u8]) -> Result<Self::Sk, ProtocolError> {
|
||||
SecretKey::<Self>::from_bytes(&bytes.take_array("secret key")?)
|
||||
.map(|secret_key| secret_key.to_nonzero_scalar())
|
||||
.map_err(|_| ProtocolError::SerializationError)
|
||||
}
|
||||
}
|
||||
|
||||
impl<G> DiffieHellman<G> for Scalar<G>
|
||||
/// Wrapper around [`NonIdentity`](point::NonIdentity) to implement [`Zeroize`].
|
||||
// TODO: remove after https://github.com/RustCrypto/traits/pull/1832.
|
||||
#[derive_where(Clone, Copy)]
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(
|
||||
deserialize = "point::NonIdentity<ProjectivePoint<G>>: serde::Deserialize<'de>",
|
||||
serialize = "point::NonIdentity<ProjectivePoint<G>>: serde::Serialize"
|
||||
))
|
||||
)]
|
||||
pub struct NonIdentity<G: CurveArithmetic>(pub point::NonIdentity<ProjectivePoint<G>>);
|
||||
|
||||
impl<G: CurveArithmetic> Debug for NonIdentity<G> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
f.debug_tuple("NonIdentity")
|
||||
.field(&self.0.to_point())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<G: CurveArithmetic> PartialEq for NonIdentity<G> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0.to_point().eq(&other.0.to_point())
|
||||
}
|
||||
}
|
||||
|
||||
impl<G: CurveArithmetic> Eq for NonIdentity<G> {}
|
||||
|
||||
impl<G: CurveArithmetic> Zeroize for NonIdentity<G> {
|
||||
fn zeroize(&mut self) {
|
||||
self.0 = point::NonIdentity::new(ProjectivePoint::<G>::generator()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl<G> DiffieHellman<G> for NonZeroScalar<G>
|
||||
where
|
||||
G: GroupDigest,
|
||||
G: CurveArithmetic + voprf::CipherSuite<Group = G> + voprf::Group<Scalar = Scalar<G>>,
|
||||
FieldBytesSize<G>: ModulusSize,
|
||||
AffinePoint<G>: FromEncodedPoint<G> + ToEncodedPoint<G>,
|
||||
ProjectivePoint<G>: CofactorGroup + ToEncodedPoint<G>,
|
||||
Scalar<G>: FromOkm,
|
||||
ProjectivePoint<G>: GroupEncoding<
|
||||
Repr = GenericArray<u8, <FieldBytesSize<G> as ModulusSize>::CompressedPointSize>,
|
||||
> + ToEncodedPoint<G>,
|
||||
{
|
||||
fn diffie_hellman(
|
||||
self,
|
||||
pk: ProjectivePoint<G>,
|
||||
pk: NonIdentity<G>,
|
||||
) -> GenericArray<u8, <FieldBytesSize<G> as ModulusSize>::CompressedPointSize> {
|
||||
GenericArray::clone_from_slice((pk * self).to_encoded_point(true).as_bytes())
|
||||
GenericArray::clone_from_slice((pk.0 * self).to_encoded_point(true).as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////
|
||||
// Test Implementations //
|
||||
//===================== //
|
||||
//////////////////////////
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::serialization::AssertZeroized;
|
||||
|
||||
#[cfg(test)]
|
||||
impl<G: CurveArithmetic> AssertZeroized for NonIdentity<G> {
|
||||
fn assert_zeroized(&self) {
|
||||
assert_eq!(self.0.to_point(), ProjectivePoint::<G>::generator());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl<G: CurveArithmetic> AssertZeroized for NonZeroScalar<G> {
|
||||
fn assert_zeroized(&self) {
|
||||
use elliptic_curve::Field;
|
||||
|
||||
assert_eq!(**self, Scalar::<G>::ONE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,18 +6,16 @@
|
||||
// of this source tree. You may select, at your option, one of the above-listed
|
||||
// licenses.
|
||||
|
||||
//! Includes the [`KeGroup`] trait and definitions for the key exchange groups
|
||||
//! Includes the [`Group`] trait and definitions for the key exchange groups
|
||||
|
||||
#[cfg(feature = "curve25519")]
|
||||
pub mod curve25519;
|
||||
mod elliptic_curve;
|
||||
#[cfg(feature = "ed25519")]
|
||||
pub mod ed25519;
|
||||
pub mod elliptic_curve;
|
||||
#[cfg(feature = "ristretto255")]
|
||||
pub mod ristretto255;
|
||||
|
||||
use digest::core_api::BlockSizeUser;
|
||||
use digest::{FixedOutput, HashMarker, OutputSizeUser};
|
||||
use generic_array::sequence::Concat;
|
||||
use generic_array::typenum::{IsLess, IsLessOrEqual, U256};
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use zeroize::Zeroize;
|
||||
@@ -27,7 +25,7 @@ use crate::errors::{InternalError, ProtocolError};
|
||||
const STR_OPAQUE_DERIVE_AUTH_KEY_PAIR: [u8; 33] = *b"OPAQUE-DeriveDiffieHellmanKeyPair";
|
||||
|
||||
/// A group representation for use in the key exchange
|
||||
pub trait KeGroup {
|
||||
pub trait Group {
|
||||
/// Public key
|
||||
type Pk: Copy + Zeroize;
|
||||
/// Length of the public key
|
||||
@@ -41,65 +39,15 @@ pub trait KeGroup {
|
||||
fn serialize_pk(pk: Self::Pk) -> GenericArray<u8, Self::PkLen>;
|
||||
|
||||
/// Return a public key from its fixed-length bytes representation
|
||||
fn deserialize_pk(bytes: &[u8]) -> Result<Self::Pk, ProtocolError>;
|
||||
///
|
||||
/// The deserialized bytes must be taken from `bytes`.
|
||||
fn deserialize_take_pk(bytes: &mut &[u8]) -> Result<Self::Pk, ProtocolError>;
|
||||
|
||||
/// Generate a random secret key
|
||||
fn random_sk<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Sk;
|
||||
|
||||
/// Hashes a slice of pseudo-random bytes to a scalar
|
||||
///
|
||||
/// # Errors
|
||||
/// [`InternalError::HashToScalar`] if the `input` is empty or longer then
|
||||
/// [`u16::MAX`].
|
||||
fn hash_to_scalar<H>(input: &[&[u8]], dst: &[&[u8]]) -> Result<Self::Sk, InternalError>
|
||||
where
|
||||
H: BlockSizeUser + Default + FixedOutput + HashMarker,
|
||||
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>;
|
||||
|
||||
/// Corresponds to the `DeriveAuthKeyPair()` function defined in
|
||||
/// <https://www.ietf.org/archive/id/draft-irtf-cfrg-opaque-08.html#section-6.4.2>
|
||||
///
|
||||
/// Note that we cannot call the voprf crate directly since we need to
|
||||
/// ensure that the [`KeGroup`] is used for the
|
||||
/// [`hash_to_scalar`](Self::hash_to_scalar) operation (as opposed to
|
||||
/// the [`OprfGroup`](voprf::Group)).
|
||||
fn derive_auth_keypair<CS: voprf::CipherSuite>(
|
||||
seed: GenericArray<u8, Self::SkLen>,
|
||||
) -> Result<Self::Sk, InternalError>
|
||||
where
|
||||
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||
{
|
||||
let info = &STR_OPAQUE_DERIVE_AUTH_KEY_PAIR;
|
||||
let dst_1 = GenericArray::from(STR_DERIVE_KEYPAIR)
|
||||
.concat(STR_OPRF.into())
|
||||
.concat([voprf::Mode::Oprf.to_u8()].into())
|
||||
.concat([b'-'].into());
|
||||
let dst_2 = CS::ID.as_bytes();
|
||||
|
||||
let info_len = i2osp_2(info.len())
|
||||
.map_err(|_| InternalError::OprfError(voprf::Error::DeriveKeyPair))?;
|
||||
|
||||
for counter in 0_u8..=u8::MAX {
|
||||
// deriveInput = seed || I2OSP(len(info), 2) || info
|
||||
// skS = G.HashToScalar(deriveInput || I2OSP(counter, 1), DST = "DeriveKeyPair"
|
||||
// || contextString)
|
||||
let sk_s = Self::hash_to_scalar::<CS::Hash>(
|
||||
&[&seed, &info_len, info, &counter.to_be_bytes()],
|
||||
&[&dst_1, dst_2],
|
||||
)
|
||||
.map_err(|_| InternalError::OprfError(voprf::Error::DeriveKeyPair))?;
|
||||
|
||||
if !bool::from(Self::is_zero_scalar(sk_s)) {
|
||||
return Ok(sk_s);
|
||||
}
|
||||
}
|
||||
|
||||
Err(InternalError::OprfError(voprf::Error::DeriveKeyPair))
|
||||
}
|
||||
|
||||
/// Returns `true` if the scalar is zero.
|
||||
fn is_zero_scalar(scalar: Self::Sk) -> subtle::Choice;
|
||||
/// Deterministically derive a [`Self::Sk`] from `seed`.
|
||||
fn derive_scalar(seed: GenericArray<u8, Self::SkLen>) -> Result<Self::Sk, InternalError>;
|
||||
|
||||
/// Return a public key from its secret key
|
||||
fn public_key(sk: Self::Sk) -> Self::Pk;
|
||||
@@ -108,17 +56,7 @@ pub trait KeGroup {
|
||||
fn serialize_sk(sk: Self::Sk) -> GenericArray<u8, Self::SkLen>;
|
||||
|
||||
/// Return a public key from its fixed-length bytes representation
|
||||
fn deserialize_sk(bytes: &[u8]) -> Result<Self::Sk, ProtocolError>;
|
||||
}
|
||||
|
||||
// Helper functions used to compute DeriveAuthKeyPair() (taken from the voprf
|
||||
// crate)
|
||||
|
||||
const STR_OPRF: [u8; 7] = *b"OPRFV1-";
|
||||
const STR_DERIVE_KEYPAIR: [u8; 13] = *b"DeriveKeyPair";
|
||||
|
||||
fn i2osp_2(input: usize) -> Result<[u8; 2], InternalError> {
|
||||
u16::try_from(input)
|
||||
.map(|input| input.to_be_bytes())
|
||||
.map_err(|_| InternalError::OprfInternalError(voprf::InternalError::I2osp))
|
||||
///
|
||||
/// The deserialized bytes must be taken from `bytes`.
|
||||
fn deserialize_take_sk(bytes: &mut &[u8]) -> Result<Self::Sk, ProtocolError>;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
//! Key Exchange group implementation for ristretto255
|
||||
|
||||
pub use curve25519_dalek;
|
||||
use curve25519_dalek::constants::RISTRETTO_BASEPOINT_POINT;
|
||||
use curve25519_dalek::ristretto::{CompressedRistretto, RistrettoPoint};
|
||||
use curve25519_dalek::scalar::Scalar;
|
||||
@@ -17,19 +18,19 @@ use digest::{FixedOutput, HashMarker};
|
||||
use generic_array::typenum::{IsLess, IsLessOrEqual, U256, U32};
|
||||
use generic_array::GenericArray;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use subtle::ConstantTimeEq;
|
||||
use voprf::Group;
|
||||
use voprf::Mode;
|
||||
|
||||
use super::KeGroup;
|
||||
use super::{Group, STR_OPAQUE_DERIVE_AUTH_KEY_PAIR};
|
||||
use crate::errors::{InternalError, ProtocolError};
|
||||
use crate::key_exchange::tripledh::DiffieHellman;
|
||||
use crate::key_exchange::shared::DiffieHellman;
|
||||
use crate::serialization::SliceExt;
|
||||
|
||||
/// Implementation for Ristretto255.
|
||||
// This is necessary because Rust lacks specialization, otherwise we could
|
||||
// implement `KeGroup` for `voprf::Ristretto255`.
|
||||
pub struct Ristretto255;
|
||||
|
||||
impl KeGroup for Ristretto255 {
|
||||
impl Group for Ristretto255 {
|
||||
type Pk = RistrettoPoint;
|
||||
type PkLen = U32;
|
||||
type Sk = Scalar;
|
||||
@@ -39,8 +40,8 @@ impl KeGroup for Ristretto255 {
|
||||
pk.compress().to_bytes().into()
|
||||
}
|
||||
|
||||
fn deserialize_pk(bytes: &[u8]) -> Result<Self::Pk, ProtocolError> {
|
||||
CompressedRistretto::from_slice(bytes)
|
||||
fn deserialize_take_pk(bytes: &mut &[u8]) -> Result<Self::Pk, ProtocolError> {
|
||||
CompressedRistretto::from_slice(&bytes.take_array::<U32>("public key")?)
|
||||
.map_err(|_| ProtocolError::SerializationError)?
|
||||
.decompress()
|
||||
.filter(|point| point != &RistrettoPoint::identity())
|
||||
@@ -49,21 +50,7 @@ impl KeGroup for Ristretto255 {
|
||||
|
||||
fn random_sk<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Sk {
|
||||
loop {
|
||||
let scalar = {
|
||||
#[cfg(not(test))]
|
||||
{
|
||||
Scalar::random(rng)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
};
|
||||
let scalar = Scalar::random(rng);
|
||||
|
||||
if scalar != Scalar::ZERO {
|
||||
break scalar;
|
||||
@@ -71,19 +58,9 @@ impl KeGroup for Ristretto255 {
|
||||
}
|
||||
}
|
||||
|
||||
// Implements the `HashToScalar()` function from
|
||||
// <https://www.ietf.org/archive/id/draft-irtf-cfrg-voprf-19.html#section-4>
|
||||
fn hash_to_scalar<'a, H>(input: &[&[u8]], dst: &[&[u8]]) -> Result<Self::Sk, InternalError>
|
||||
where
|
||||
H: BlockSizeUser + Default + FixedOutput + HashMarker,
|
||||
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>,
|
||||
{
|
||||
<voprf::Ristretto255 as Group>::hash_to_scalar::<H>(input, dst)
|
||||
.map_err(InternalError::OprfInternalError)
|
||||
}
|
||||
|
||||
fn is_zero_scalar(scalar: Self::Sk) -> subtle::Choice {
|
||||
scalar.ct_eq(&Scalar::ZERO)
|
||||
fn derive_scalar(seed: GenericArray<u8, Self::SkLen>) -> Result<Self::Sk, InternalError> {
|
||||
voprf::derive_key::<Self>(&seed, &STR_OPAQUE_DERIVE_AUTH_KEY_PAIR, Mode::Oprf)
|
||||
.map_err(InternalError::from)
|
||||
}
|
||||
|
||||
fn public_key(sk: Self::Sk) -> Self::Pk {
|
||||
@@ -94,17 +71,16 @@ impl KeGroup for Ristretto255 {
|
||||
sk.to_bytes().into()
|
||||
}
|
||||
|
||||
fn deserialize_sk(bytes: &[u8]) -> Result<Self::Sk, ProtocolError> {
|
||||
fn deserialize_take_sk(bytes: &mut &[u8]) -> Result<Self::Sk, ProtocolError> {
|
||||
bytes
|
||||
.try_into()
|
||||
.take_array::<U32>("secret key")
|
||||
.ok()
|
||||
.and_then(|bytes| Scalar::from_canonical_bytes(bytes).into())
|
||||
.and_then(|bytes| Scalar::from_canonical_bytes(bytes.into()).into())
|
||||
.filter(|scalar| scalar != &Scalar::ZERO)
|
||||
.ok_or(ProtocolError::SerializationError)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ristretto255-voprf")]
|
||||
impl voprf::CipherSuite for Ristretto255 {
|
||||
const ID: &'static str = voprf::Ristretto255::ID;
|
||||
|
||||
@@ -113,14 +89,14 @@ impl voprf::CipherSuite for Ristretto255 {
|
||||
type Hash = <voprf::Ristretto255 as voprf::CipherSuite>::Hash;
|
||||
}
|
||||
|
||||
impl Group for Ristretto255 {
|
||||
type Elem = <voprf::Ristretto255 as Group>::Elem;
|
||||
impl voprf::Group for Ristretto255 {
|
||||
type Elem = <voprf::Ristretto255 as voprf::Group>::Elem;
|
||||
|
||||
type ElemLen = <voprf::Ristretto255 as Group>::ElemLen;
|
||||
type ElemLen = <voprf::Ristretto255 as voprf::Group>::ElemLen;
|
||||
|
||||
type Scalar = <voprf::Ristretto255 as Group>::Scalar;
|
||||
type Scalar = <voprf::Ristretto255 as voprf::Group>::Scalar;
|
||||
|
||||
type ScalarLen = <voprf::Ristretto255 as Group>::ScalarLen;
|
||||
type ScalarLen = <voprf::Ristretto255 as voprf::Group>::ScalarLen;
|
||||
|
||||
fn hash_to_curve<H>(
|
||||
input: &[&[u8]],
|
||||
@@ -130,7 +106,7 @@ impl Group for Ristretto255 {
|
||||
H: BlockSizeUser + Default + FixedOutput + HashMarker,
|
||||
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>,
|
||||
{
|
||||
<voprf::Ristretto255 as Group>::hash_to_curve::<H>(input, dst)
|
||||
<voprf::Ristretto255 as voprf::Group>::hash_to_curve::<H>(input, dst)
|
||||
}
|
||||
|
||||
fn hash_to_scalar<H>(
|
||||
@@ -141,43 +117,43 @@ impl Group for Ristretto255 {
|
||||
H: BlockSizeUser + Default + FixedOutput + HashMarker,
|
||||
H::OutputSize: IsLess<U256> + IsLessOrEqual<H::BlockSize>,
|
||||
{
|
||||
<voprf::Ristretto255 as Group>::hash_to_scalar::<H>(input, dst)
|
||||
<voprf::Ristretto255 as voprf::Group>::hash_to_scalar::<H>(input, dst)
|
||||
}
|
||||
|
||||
fn base_elem() -> Self::Elem {
|
||||
<voprf::Ristretto255 as Group>::base_elem()
|
||||
<voprf::Ristretto255 as voprf::Group>::base_elem()
|
||||
}
|
||||
|
||||
fn identity_elem() -> Self::Elem {
|
||||
<voprf::Ristretto255 as Group>::identity_elem()
|
||||
<voprf::Ristretto255 as voprf::Group>::identity_elem()
|
||||
}
|
||||
|
||||
fn serialize_elem(elem: Self::Elem) -> GenericArray<u8, Self::ElemLen> {
|
||||
<voprf::Ristretto255 as Group>::serialize_elem(elem)
|
||||
<voprf::Ristretto255 as voprf::Group>::serialize_elem(elem)
|
||||
}
|
||||
|
||||
fn deserialize_elem(element_bits: &[u8]) -> voprf::Result<Self::Elem> {
|
||||
<voprf::Ristretto255 as Group>::deserialize_elem(element_bits)
|
||||
<voprf::Ristretto255 as voprf::Group>::deserialize_elem(element_bits)
|
||||
}
|
||||
|
||||
fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
|
||||
<voprf::Ristretto255 as Group>::random_scalar(rng)
|
||||
<voprf::Ristretto255 as voprf::Group>::random_scalar(rng)
|
||||
}
|
||||
|
||||
fn invert_scalar(scalar: Self::Scalar) -> Self::Scalar {
|
||||
<voprf::Ristretto255 as Group>::invert_scalar(scalar)
|
||||
<voprf::Ristretto255 as voprf::Group>::invert_scalar(scalar)
|
||||
}
|
||||
|
||||
fn is_zero_scalar(scalar: Self::Scalar) -> subtle::Choice {
|
||||
<voprf::Ristretto255 as Group>::is_zero_scalar(scalar)
|
||||
<voprf::Ristretto255 as voprf::Group>::is_zero_scalar(scalar)
|
||||
}
|
||||
|
||||
fn serialize_scalar(scalar: Self::Scalar) -> GenericArray<u8, Self::ScalarLen> {
|
||||
<voprf::Ristretto255 as Group>::serialize_scalar(scalar)
|
||||
<voprf::Ristretto255 as voprf::Group>::serialize_scalar(scalar)
|
||||
}
|
||||
|
||||
fn deserialize_scalar(scalar_bits: &[u8]) -> voprf::Result<Self::Scalar> {
|
||||
<voprf::Ristretto255 as Group>::deserialize_scalar(scalar_bits)
|
||||
<voprf::Ristretto255 as voprf::Group>::deserialize_scalar(scalar_bits)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,3 +162,25 @@ impl DiffieHellman<Ristretto255> for Scalar {
|
||||
Ristretto255::serialize_pk(pk * self)
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////
|
||||
// Test Implementations //
|
||||
//===================== //
|
||||
//////////////////////////
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::serialization::AssertZeroized;
|
||||
|
||||
#[cfg(test)]
|
||||
impl AssertZeroized for RistrettoPoint {
|
||||
fn assert_zeroized(&self) {
|
||||
assert_eq!(*self, RistrettoPoint::default());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl AssertZeroized for Scalar {
|
||||
fn assert_zeroized(&self) {
|
||||
assert_eq!(*self, Scalar::default());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,5 +10,9 @@
|
||||
//! OPAQUE
|
||||
|
||||
pub mod group;
|
||||
pub(crate) mod shared;
|
||||
pub mod sigma_i;
|
||||
pub(crate) mod traits;
|
||||
pub mod tripledh;
|
||||
|
||||
pub use crate::key_exchange::traits::KeyExchange;
|
||||
|
||||
@@ -0,0 +1,391 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
//
|
||||
// This source code is dual-licensed under either the MIT license found in the
|
||||
// LICENSE-MIT file in the root directory of this source tree or the Apache
|
||||
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
|
||||
// of this source tree. You may select, at your option, one of the above-listed
|
||||
// licenses.
|
||||
|
||||
use core::ops::Add;
|
||||
|
||||
use derive_where::derive_where;
|
||||
use digest::core_api::BlockSizeUser;
|
||||
use digest::{Digest, Output, OutputSizeUser, Update};
|
||||
use generic_array::sequence::Concat;
|
||||
use generic_array::typenum::{IsLess, Le, NonZero, Sum, Unsigned, U1, U2, U256, U32};
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
use hkdf::{Hkdf, HkdfExtract};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
|
||||
use crate::ciphersuite::{CipherSuite, KeGroup, KeHash};
|
||||
use crate::errors::{InternalError, ProtocolError};
|
||||
use crate::hash::{Hash, OutputSize, ProxyHash};
|
||||
use crate::key_exchange::group::Group;
|
||||
use crate::key_exchange::traits::{
|
||||
CredentialRequestParts, CredentialResponseParts, Deserialize, Serialize, SerializedContext,
|
||||
SerializedIdentifiers,
|
||||
};
|
||||
use crate::keypair::{KeyPair, PrivateKey, PublicKey};
|
||||
use crate::serialization::{i2osp, SliceExt, UpdateExt};
|
||||
|
||||
///////////////
|
||||
// Constants //
|
||||
// ========= //
|
||||
///////////////
|
||||
|
||||
pub(crate) type NonceLen = U32;
|
||||
pub(super) static STR_CONTEXT: &[u8] = b"OPAQUEv1-";
|
||||
static STR_CLIENT_MAC: &[u8] = b"ClientMAC";
|
||||
static STR_HANDSHAKE_SECRET: &[u8] = b"HandshakeSecret";
|
||||
static STR_SERVER_MAC: &[u8] = b"ServerMAC";
|
||||
static STR_SESSION_KEY: &[u8] = b"SessionKey";
|
||||
static STR_OPAQUE: &[u8] = b"OPAQUE-";
|
||||
|
||||
////////////////////////////
|
||||
// High-level API Structs //
|
||||
// ====================== //
|
||||
////////////////////////////
|
||||
|
||||
/// Trait required by [`Group::Sk`] to be compatible with
|
||||
/// [`TripleDh`](crate::TripleDh) and [`SigmaI`](crate::SigmaI).
|
||||
pub trait DiffieHellman<G: Group> {
|
||||
/// Diffie-Hellman key exchange.
|
||||
fn diffie_hellman(self, pk: G::Pk) -> GenericArray<u8, G::PkLen>;
|
||||
}
|
||||
|
||||
/// The client state produced after the first key exchange message
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound = "")
|
||||
)]
|
||||
#[derive_where(Clone, ZeroizeOnDrop)]
|
||||
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; G::Sk)]
|
||||
pub struct Ke1State<G: Group> {
|
||||
pub(super) client_e_sk: PrivateKey<G>,
|
||||
pub(super) client_nonce: GenericArray<u8, NonceLen>,
|
||||
}
|
||||
|
||||
/// The first key exchange message
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound = "")
|
||||
)]
|
||||
#[derive_where(Clone, ZeroizeOnDrop)]
|
||||
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; G::Pk)]
|
||||
pub struct Ke1Message<G: Group> {
|
||||
pub(super) client_nonce: GenericArray<u8, NonceLen>,
|
||||
pub(super) client_e_pk: PublicKey<G>,
|
||||
}
|
||||
|
||||
/////////////////////////
|
||||
// Convenience Structs //
|
||||
//==================== //
|
||||
/////////////////////////
|
||||
|
||||
// Consists of a session key, followed by two mac keys: (session_key, km2, km3)
|
||||
pub(super) struct DerivedKeys<H: OutputSizeUser> {
|
||||
pub(super) session_key: Output<H>,
|
||||
pub(super) km2: Output<H>,
|
||||
pub(super) km3: Output<H>,
|
||||
#[cfg(test)]
|
||||
pub(super) handshake_secret: Output<H>,
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// Helper functions and Trait Implementations //
|
||||
// ========================================== //
|
||||
////////////////////////////////////////////////
|
||||
|
||||
// Helper functions
|
||||
|
||||
pub(super) fn generate_ke1<R: RngCore + CryptoRng, G: Group>(
|
||||
rng: &mut R,
|
||||
) -> Result<(Ke1State<G>, Ke1Message<G>), ProtocolError> {
|
||||
let client_e_kp = KeyPair::<G>::derive_random(rng);
|
||||
let client_nonce = generate_nonce::<R>(rng);
|
||||
|
||||
let ke1_message = Ke1Message {
|
||||
client_nonce,
|
||||
client_e_pk: client_e_kp.public().clone(),
|
||||
};
|
||||
|
||||
Ok((
|
||||
Ke1State {
|
||||
client_e_sk: client_e_kp.private().clone(),
|
||||
client_nonce,
|
||||
},
|
||||
ke1_message,
|
||||
))
|
||||
}
|
||||
|
||||
// Generate a random nonce up to NonceLen::USIZE bytes.
|
||||
pub(super) fn generate_nonce<R: RngCore + CryptoRng>(rng: &mut R) -> GenericArray<u8, NonceLen> {
|
||||
let mut nonce_bytes = GenericArray::default();
|
||||
rng.fill_bytes(&mut nonce_bytes);
|
||||
nonce_bytes
|
||||
}
|
||||
|
||||
pub(super) fn transcript<CS: CipherSuite, KE: Group>(
|
||||
context: &SerializedContext<'_>,
|
||||
identifiers: &SerializedIdentifiers<'_, KeGroup<CS>>,
|
||||
credential_request: &CredentialRequestParts<CS>,
|
||||
ke1_message: &Ke1MessageIter<KE>,
|
||||
credential_response: &CredentialResponseParts<CS>,
|
||||
server_nonce: GenericArray<u8, NonceLen>,
|
||||
server_e_pk: &GenericArray<u8, KE::PkLen>,
|
||||
) -> KeHash<CS> {
|
||||
KeHash::<CS>::new()
|
||||
.chain_iter(context.iter())
|
||||
.chain_iter(identifiers.client.iter())
|
||||
.chain_iter(credential_request.iter())
|
||||
.chain_iter(ke1_message.iter())
|
||||
.chain_iter(identifiers.server.iter())
|
||||
.chain_iter(credential_response.iter())
|
||||
.chain(server_nonce)
|
||||
.chain(server_e_pk)
|
||||
}
|
||||
|
||||
// Internal function which takes computed shared secrets, along with some
|
||||
// auxiliary metadata, to produce the session key and two MAC keys
|
||||
pub(super) fn derive_keys<'a, H: Hash>(
|
||||
ikms: impl Iterator<Item = &'a [u8]>,
|
||||
hashed_derivation_transcript: &[u8],
|
||||
) -> Result<DerivedKeys<H>, ProtocolError>
|
||||
where
|
||||
H::Core: ProxyHash,
|
||||
<H::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<H::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
{
|
||||
let mut hkdf = HkdfExtract::<H>::new(None);
|
||||
|
||||
for ikm in ikms {
|
||||
hkdf.input_ikm(ikm);
|
||||
}
|
||||
|
||||
let (_, extracted_ikm) = hkdf.finalize();
|
||||
let handshake_secret = derive_secrets::<H>(
|
||||
&extracted_ikm,
|
||||
STR_HANDSHAKE_SECRET,
|
||||
hashed_derivation_transcript,
|
||||
)?;
|
||||
let session_key = derive_secrets::<H>(
|
||||
&extracted_ikm,
|
||||
STR_SESSION_KEY,
|
||||
hashed_derivation_transcript,
|
||||
)?;
|
||||
|
||||
let km2 = hkdf_expand_label::<H>(&handshake_secret, STR_SERVER_MAC, b"")?;
|
||||
let km3 = hkdf_expand_label::<H>(&handshake_secret, STR_CLIENT_MAC, b"")?;
|
||||
|
||||
Ok(DerivedKeys {
|
||||
session_key,
|
||||
km2,
|
||||
km3,
|
||||
#[cfg(test)]
|
||||
handshake_secret,
|
||||
})
|
||||
}
|
||||
|
||||
fn hkdf_expand_label<H: Hash>(
|
||||
secret: &[u8],
|
||||
label: &[u8],
|
||||
context: &[u8],
|
||||
) -> Result<Output<H>, ProtocolError>
|
||||
where
|
||||
H::Core: ProxyHash,
|
||||
<H::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<H::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
{
|
||||
let h = Hkdf::<H>::from_prk(secret).map_err(|_| InternalError::HkdfError)?;
|
||||
hkdf_expand_label_extracted(&h, label, context)
|
||||
}
|
||||
|
||||
fn hkdf_expand_label_extracted<H: Hash>(
|
||||
hkdf: &Hkdf<H>,
|
||||
label: &[u8],
|
||||
context: &[u8],
|
||||
) -> Result<Output<H>, ProtocolError>
|
||||
where
|
||||
H::Core: ProxyHash,
|
||||
<H::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<H::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
{
|
||||
let mut okm = GenericArray::default();
|
||||
|
||||
let length = i2osp::<U2>(OutputSize::<H>::USIZE)?;
|
||||
let label_length = i2osp::<U1>(STR_OPAQUE.len() + label.len())?;
|
||||
let context_len = i2osp::<U1>(context.len())?;
|
||||
|
||||
let hkdf_label = [
|
||||
length.as_slice(),
|
||||
&label_length,
|
||||
STR_OPAQUE,
|
||||
label,
|
||||
&context_len,
|
||||
context,
|
||||
];
|
||||
|
||||
hkdf.expand_multi_info(&hkdf_label, &mut okm)
|
||||
.map_err(|_| InternalError::HkdfError)?;
|
||||
Ok(okm)
|
||||
}
|
||||
|
||||
fn derive_secrets<H: Hash>(
|
||||
hkdf: &Hkdf<H>,
|
||||
label: &[u8],
|
||||
hashed_derivation_transcript: &[u8],
|
||||
) -> Result<Output<H>, ProtocolError>
|
||||
where
|
||||
H::Core: ProxyHash,
|
||||
<H::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<H::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
{
|
||||
hkdf_expand_label_extracted::<H>(hkdf, label, hashed_derivation_transcript)
|
||||
}
|
||||
|
||||
// Serialization and deserialization implementations
|
||||
|
||||
impl<G: Group> Deserialize for Ke1State<G> {
|
||||
fn deserialize_take(bytes: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
Ok(Self {
|
||||
client_e_sk: PrivateKey::deserialize_take(bytes)?,
|
||||
client_nonce: bytes.take_array("client nonce")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<G: Group> Serialize for Ke1State<G>
|
||||
where
|
||||
// Ke1State: KeSk + Nonce
|
||||
G::SkLen: Add<NonceLen>,
|
||||
Sum<G::SkLen, NonceLen>: ArrayLength<u8>,
|
||||
{
|
||||
type Len = Sum<G::SkLen, NonceLen>;
|
||||
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len> {
|
||||
self.client_e_sk.serialize().concat(self.client_nonce)
|
||||
}
|
||||
}
|
||||
|
||||
impl<G: Group> Deserialize for Ke1Message<G> {
|
||||
fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
Ok(Self {
|
||||
client_nonce: input.take_array("client nonce")?,
|
||||
client_e_pk: PublicKey::deserialize_take(input)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<G: Group> Serialize for Ke1Message<G>
|
||||
where
|
||||
// Ke1Message: Nonce + KePk
|
||||
NonceLen: Add<G::PkLen>,
|
||||
Sum<NonceLen, G::PkLen>: ArrayLength<u8>,
|
||||
{
|
||||
type Len = Sum<NonceLen, G::PkLen>;
|
||||
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len> {
|
||||
self.client_nonce.concat(self.client_e_pk.serialize())
|
||||
}
|
||||
}
|
||||
|
||||
impl<G: Group> Ke1Message<G> {
|
||||
pub(crate) fn to_iter(&self) -> Ke1MessageIter<G> {
|
||||
Ke1MessageIter {
|
||||
client_nonce: self.client_nonce,
|
||||
client_e_pk: self.client_e_pk.serialize(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound = "")
|
||||
)]
|
||||
#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize)]
|
||||
pub(crate) struct Ke1MessageIter<G: Group> {
|
||||
client_nonce: GenericArray<u8, NonceLen>,
|
||||
client_e_pk: GenericArray<u8, G::PkLen>,
|
||||
}
|
||||
|
||||
pub(crate) type Ke1MessageIterLen<G: Group> = Sum<NonceLen, G::PkLen>;
|
||||
|
||||
impl<G: Group> Ke1MessageIter<G> {
|
||||
pub(crate) fn iter(&self) -> impl Clone + Iterator<Item = &[u8]> {
|
||||
[self.client_nonce.as_slice(), self.client_e_pk.as_slice()].into_iter()
|
||||
}
|
||||
|
||||
pub(crate) fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
Ok(Ke1MessageIter {
|
||||
client_nonce: input.take_array("client nonce")?,
|
||||
client_e_pk: input.take_array("client ephemeral public key")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<G: Group> Ke1MessageIter<G>
|
||||
where
|
||||
NonceLen: Add<G::PkLen>,
|
||||
Ke1MessageIterLen<G>: ArrayLength<u8>,
|
||||
{
|
||||
pub(crate) fn serialize(&self) -> GenericArray<u8, Ke1MessageIterLen<G>> {
|
||||
self.client_nonce.concat(self.client_e_pk.clone())
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////
|
||||
// Test Implementations //
|
||||
//===================== //
|
||||
//////////////////////////
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::serialization::AssertZeroized;
|
||||
|
||||
#[cfg(test)]
|
||||
impl<G: Group> AssertZeroized for Ke1State<G>
|
||||
where
|
||||
G::Sk: AssertZeroized,
|
||||
{
|
||||
fn assert_zeroized(&self) {
|
||||
let Self {
|
||||
client_e_sk,
|
||||
client_nonce,
|
||||
} = self;
|
||||
|
||||
client_e_sk.assert_zeroized();
|
||||
assert_eq!(client_nonce, &GenericArray::default());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl<G: Group> AssertZeroized for Ke1Message<G>
|
||||
where
|
||||
G::Pk: AssertZeroized,
|
||||
{
|
||||
fn assert_zeroized(&self) {
|
||||
let Self {
|
||||
client_nonce,
|
||||
client_e_pk,
|
||||
} = self;
|
||||
|
||||
assert_eq!(client_nonce, &GenericArray::default());
|
||||
client_e_pk.assert_zeroized();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl<G: Group> AssertZeroized for Ke1MessageIter<G> {
|
||||
fn assert_zeroized(&self) {
|
||||
let Self {
|
||||
client_nonce,
|
||||
client_e_pk,
|
||||
} = self;
|
||||
|
||||
for byte in client_nonce.iter().chain(client_e_pk) {
|
||||
assert_eq!(byte, &0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
//
|
||||
// This source code is dual-licensed under either the MIT license found in the
|
||||
// LICENSE-MIT file in the root directory of this source tree or the Apache
|
||||
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
|
||||
// of this source tree. You may select, at your option, one of the above-listed
|
||||
// licenses.
|
||||
|
||||
//! ECDSA implementation for [`elliptic_curve`] [`Group`] implementations to
|
||||
//! support [`SigmaI`](crate::SigmaI).
|
||||
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use derive_where::derive_where;
|
||||
use digest::core_api::BlockSizeUser;
|
||||
use digest::{FixedOutputReset, HashMarker};
|
||||
use ecdsa::{hazmat, PrimeCurve, SignatureSize};
|
||||
use elliptic_curve::{
|
||||
CurveArithmetic, Field, FieldBytes, FieldBytesEncoding, FieldBytesSize, NonZeroScalar,
|
||||
PrimeField, Scalar,
|
||||
};
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use super::{Message, MessageBuilder, SignatureProtocol};
|
||||
use crate::ciphersuite::CipherSuite;
|
||||
use crate::errors::ProtocolError;
|
||||
use crate::key_exchange::group::elliptic_curve::NonIdentity;
|
||||
use crate::key_exchange::group::Group;
|
||||
pub use crate::key_exchange::sigma_i::shared::PreHash;
|
||||
use crate::serialization::SliceExt;
|
||||
|
||||
/// ECDSA for [`SigmaI`](crate::SigmaI).
|
||||
///
|
||||
/// The ["verification state"](Self::VerifyState) is the pre-hash for the
|
||||
/// message to be verified.
|
||||
pub struct Ecdsa<G, H>(PhantomData<(G, H)>);
|
||||
|
||||
impl<G, H> SignatureProtocol for Ecdsa<G, H>
|
||||
where
|
||||
G: CurveArithmetic + Group<Sk = NonZeroScalar<G>, Pk = NonIdentity<G>> + PrimeCurve,
|
||||
SignatureSize<G>: ArrayLength<u8>,
|
||||
H: Clone
|
||||
+ Default
|
||||
+ BlockSizeUser
|
||||
+ FixedOutputReset<OutputSize = FieldBytesSize<G>>
|
||||
+ HashMarker,
|
||||
{
|
||||
type Group = G;
|
||||
type Signature = Signature<G>;
|
||||
type SignatureLen = SignatureSize<G>;
|
||||
type VerifyState<CS: CipherSuite, KE: Group> = PreHash<H>;
|
||||
|
||||
// We use a manual implementation of `RandomizedPrehashSigner` to use the same
|
||||
// hash for the message as for generating `k`. See
|
||||
// https://github.com/RustCrypto/signatures/issues/949.
|
||||
fn sign<'a, R: CryptoRng + RngCore, CS: CipherSuite, KE: Group>(
|
||||
sk: &<Self::Group as Group>::Sk,
|
||||
rng: &mut R,
|
||||
message: &Message<CS, KE>,
|
||||
) -> (Self::Signature, Self::VerifyState<CS, KE>) {
|
||||
let hash = message.hash::<H>();
|
||||
|
||||
(
|
||||
Signature(sign::<_, G, H>(sk, rng, &hash.sign.finalize_fixed())),
|
||||
PreHash(hash.verify.finalize_fixed()),
|
||||
)
|
||||
}
|
||||
|
||||
fn verify<CS: CipherSuite, KE: Group>(
|
||||
pk: &<Self::Group as Group>::Pk,
|
||||
_: MessageBuilder<'_, CS>,
|
||||
state: Self::VerifyState<CS, KE>,
|
||||
signature: &Self::Signature,
|
||||
) -> Result<(), ProtocolError> {
|
||||
verify(pk, &state.0, &signature.0)
|
||||
}
|
||||
|
||||
fn serialize_signature(signature: &Self::Signature) -> GenericArray<u8, Self::SignatureLen> {
|
||||
signature.0.to_bytes()
|
||||
}
|
||||
|
||||
fn deserialize_take_signature(bytes: &mut &[u8]) -> Result<Self::Signature, ProtocolError> {
|
||||
ecdsa::Signature::from_bytes(&bytes.take_array("signature")?)
|
||||
.map(Signature)
|
||||
.map_err(|_| ProtocolError::SerializationError)
|
||||
}
|
||||
}
|
||||
|
||||
fn sign<R, C, H>(sk: &NonZeroScalar<C>, rng: &mut R, pre_hash: &[u8]) -> ecdsa::Signature<C>
|
||||
where
|
||||
R: CryptoRng + RngCore,
|
||||
C: CurveArithmetic + PrimeCurve,
|
||||
SignatureSize<C>: ArrayLength<u8>,
|
||||
H: Default + BlockSizeUser + FixedOutputReset<OutputSize = FieldBytesSize<C>> + HashMarker,
|
||||
{
|
||||
let repr = sk.to_repr();
|
||||
let order = C::ORDER.encode_field_bytes();
|
||||
let z =
|
||||
hazmat::bits2field::<C>(pre_hash).expect("hash output can not be shorter than a scalar");
|
||||
|
||||
// This can only fail if the computed `r` or `s` are zero, in which case we just
|
||||
// retry with a new `k`. See https://github.com/RustCrypto/signatures/pull/951.
|
||||
loop {
|
||||
let mut ad = FieldBytes::<C>::default();
|
||||
rng.fill_bytes(&mut ad);
|
||||
|
||||
let k =
|
||||
Scalar::<C>::from_repr(rfc6979::generate_k::<H, _>(&repr, &order, &z, &ad)).unwrap();
|
||||
|
||||
if let Ok((signature, _)) = hazmat::sign_prehashed::<C, _>(sk, k, &z) {
|
||||
break signature;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn verify<C>(
|
||||
pk: &NonIdentity<C>,
|
||||
pre_hash: &[u8],
|
||||
signature: &ecdsa::Signature<C>,
|
||||
) -> Result<(), ProtocolError>
|
||||
where
|
||||
C: CurveArithmetic + PrimeCurve,
|
||||
SignatureSize<C>: ArrayLength<u8>,
|
||||
{
|
||||
let z =
|
||||
hazmat::bits2field::<C>(pre_hash).expect("hash output can not be shorter than a scalar");
|
||||
hazmat::verify_prehashed(&pk.0.to_point(), &z, signature)
|
||||
.map_err(|_| ProtocolError::InvalidLoginError)
|
||||
}
|
||||
|
||||
/// Wrapper around [`ecdsa::Signature`] to implement [`Zeroize`].
|
||||
// TODO: remove after https://github.com/RustCrypto/signatures/pull/948.
|
||||
#[derive_where(Clone, Debug, Eq, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound = "")
|
||||
)]
|
||||
pub struct Signature<G: CurveArithmetic + PrimeCurve>(pub ecdsa::Signature<G>)
|
||||
where
|
||||
SignatureSize<G>: ArrayLength<u8>;
|
||||
|
||||
impl<G: CurveArithmetic + PrimeCurve> Zeroize for Signature<G>
|
||||
where
|
||||
SignatureSize<G>: ArrayLength<u8>,
|
||||
{
|
||||
fn zeroize(&mut self) {
|
||||
self.0 = ecdsa::Signature::from_scalars(
|
||||
Into::<FieldBytes<G>>::into(Scalar::<G>::ONE),
|
||||
Into::<FieldBytes<G>>::into(Scalar::<G>::ONE),
|
||||
)
|
||||
.expect("failed to create `Signature` with non-zero `Scalar`s");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ecdsa() {
|
||||
use std::vec;
|
||||
|
||||
use digest::Digest;
|
||||
use p256::ecdsa::signature::{DigestVerifier, RandomizedDigestSigner};
|
||||
use p256::ecdsa::{Signature, SigningKey, VerifyingKey};
|
||||
use p256::{NistP256, PublicKey};
|
||||
use rand::rngs::OsRng;
|
||||
use sha2::Sha256;
|
||||
|
||||
use crate::tests::mock_rng::CycleRng;
|
||||
|
||||
let mut rng = CycleRng::new(vec![1; 32]);
|
||||
|
||||
let mut message = [0; 1024];
|
||||
OsRng.fill_bytes(&mut message);
|
||||
let hash = Sha256::new_with_prefix(message);
|
||||
|
||||
let sk = NistP256::random_sk(&mut OsRng);
|
||||
let signing_key = SigningKey::from(sk);
|
||||
|
||||
let signature: Signature = signing_key.sign_digest_with_rng(&mut rng, hash.clone());
|
||||
let custom_signature = sign::<_, _, Sha256>(&sk, &mut rng, &hash.clone().finalize());
|
||||
|
||||
assert_eq!(signature, custom_signature);
|
||||
|
||||
let pk = NistP256::public_key(sk);
|
||||
let verifying_key = VerifyingKey::from(PublicKey::from(pk.0));
|
||||
|
||||
verifying_key
|
||||
.verify_digest(hash.clone(), &signature)
|
||||
.unwrap();
|
||||
verify(&pk, &hash.finalize(), &custom_signature).unwrap();
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
//
|
||||
// This source code is dual-licensed under either the MIT license found in the
|
||||
// LICENSE-MIT file in the root directory of this source tree or the Apache
|
||||
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
|
||||
// of this source tree. You may select, at your option, one of the above-listed
|
||||
// licenses.
|
||||
|
||||
//! HashEdDSA implementation for [`SigmaI`](crate::SigmaI). Currently only
|
||||
//! supports [`Ed25519`](crate::Ed25519).
|
||||
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use generic_array::GenericArray;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use self::implementation::HashEddsaImpl;
|
||||
use super::{Message, MessageBuilder, SignatureProtocol};
|
||||
use crate::ciphersuite::CipherSuite;
|
||||
use crate::errors::ProtocolError;
|
||||
use crate::key_exchange::group::Group;
|
||||
|
||||
/// HashEdDSA for [`SigmaI`](crate::SigmaI).
|
||||
///
|
||||
/// The ["verification state"](Self::VerifyState) is the pre-hash for the
|
||||
/// message to be verified.
|
||||
pub struct HashEddsa<G>(PhantomData<G>);
|
||||
|
||||
impl<G: HashEddsaImpl> SignatureProtocol for HashEddsa<G> {
|
||||
type Group = G;
|
||||
type Signature = G::Signature;
|
||||
type SignatureLen = G::SignatureLen;
|
||||
type VerifyState<CS: CipherSuite, KE: Group> = G::VerifyState<CS, KE>;
|
||||
|
||||
fn sign<'a, R: CryptoRng + RngCore, CS: CipherSuite, KE: Group>(
|
||||
sk: &<Self::Group as Group>::Sk,
|
||||
_: &mut R,
|
||||
message: &Message<CS, KE>,
|
||||
) -> (Self::Signature, Self::VerifyState<CS, KE>) {
|
||||
G::sign(sk, message)
|
||||
}
|
||||
|
||||
fn verify<CS: CipherSuite, KE: Group>(
|
||||
pk: &<Self::Group as Group>::Pk,
|
||||
_: MessageBuilder<'_, CS>,
|
||||
state: Self::VerifyState<CS, KE>,
|
||||
signature: &Self::Signature,
|
||||
) -> Result<(), ProtocolError> {
|
||||
G::verify(pk, state, signature)
|
||||
}
|
||||
|
||||
fn serialize_signature(signature: &Self::Signature) -> GenericArray<u8, Self::SignatureLen> {
|
||||
G::serialize_signature(signature)
|
||||
}
|
||||
|
||||
fn deserialize_take_signature(bytes: &mut &[u8]) -> Result<Self::Signature, ProtocolError> {
|
||||
G::deserialize_take_signature(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
pub(in super::super) mod implementation {
|
||||
use generic_array::ArrayLength;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub trait HashEddsaImpl: Group {
|
||||
type Signature: Clone + Zeroize;
|
||||
type SignatureLen: ArrayLength<u8>;
|
||||
type VerifyState<CS: CipherSuite, KE: Group>: Clone + Zeroize;
|
||||
|
||||
fn sign<CS: CipherSuite, KE: Group>(
|
||||
sk: &Self::Sk,
|
||||
message: &Message<CS, KE>,
|
||||
) -> (Self::Signature, Self::VerifyState<CS, KE>);
|
||||
|
||||
fn verify<CS: CipherSuite, KE: Group>(
|
||||
pk: &Self::Pk,
|
||||
state: Self::VerifyState<CS, KE>,
|
||||
signature: &Self::Signature,
|
||||
) -> Result<(), ProtocolError>;
|
||||
|
||||
fn deserialize_take_signature(bytes: &mut &[u8]) -> Result<Self::Signature, ProtocolError>;
|
||||
|
||||
fn serialize_signature(signature: &Self::Signature)
|
||||
-> GenericArray<u8, Self::SignatureLen>;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
use core::ops::Add;
|
||||
|
||||
use derive_where::derive_where;
|
||||
use digest::{FixedOutput, Output, Update};
|
||||
use generic_array::sequence::Concat;
|
||||
use generic_array::typenum::Sum;
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
use crate::ciphersuite::{CipherSuite, KeGroup, KeHash, OprfGroup};
|
||||
use crate::errors::ProtocolError;
|
||||
use crate::hash::OutputSize;
|
||||
use crate::key_exchange::group::Group;
|
||||
use crate::key_exchange::shared::{Ke1MessageIter, Ke1MessageIterLen, NonceLen};
|
||||
use crate::key_exchange::traits::{
|
||||
CredentialRequestParts, CredentialRequestPartsLen, CredentialResponseParts,
|
||||
CredentialResponsePartsLen, Deserialize, Serialize, SerializedContext, SerializedIdentifier,
|
||||
SerializedIdentifiers,
|
||||
};
|
||||
use crate::opaque::MaskedResponseLen;
|
||||
use crate::serialization::{SliceExt, UpdateExt};
|
||||
|
||||
/// This holds the message to be signed and the message to be verified.
|
||||
///
|
||||
/// If your signature protocol requires pre-hashes, you can call [`hash()`].
|
||||
///
|
||||
/// If you require the actual message, call [`sign_message()`]. To get the
|
||||
/// message to verify, call [`to_cached()`] to create a [`CachedMessage`] and
|
||||
/// save it in [`SignatureProtocol::VerifyState`], which you can then use in
|
||||
/// [`SignatureProtocol::verify()`] with [`MessageBuilder`] to create
|
||||
/// [`VerifyMessage`].
|
||||
///
|
||||
/// [`hash()`]: super::Message::hash
|
||||
/// [`sign_message()`]: super::Message::sign_message
|
||||
/// [`to_cached()`]: super::Message::to_cached
|
||||
/// [`SignatureProtocol::sign()`]: super::SignatureProtocol::sign
|
||||
/// [`SignatureProtocol::verify()`]: super::SignatureProtocol::verify
|
||||
/// [`SignatureProtocol::VerifyState`]: super::SignatureProtocol::VerifyState
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(deserialize = "'de: 'a", serialize = ""))
|
||||
)]
|
||||
#[derive_where(Clone, Debug, Eq, Hash, PartialEq, ZeroizeOnDrop)]
|
||||
pub struct Message<'a, CS: CipherSuite, KE: Group> {
|
||||
pub(super) role: Role,
|
||||
pub(super) context: SerializedContext<'a>,
|
||||
pub(super) identifiers: SerializedIdentifiers<'a, KeGroup<CS>>,
|
||||
pub(super) cache: CachedMessage<CS, KE>,
|
||||
}
|
||||
|
||||
/// This holds the message to be verified.
|
||||
///
|
||||
/// Create it by using [`MessageBuilder::build()`] with [`CachedMessage`].
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(deserialize = "'de: 'a", serialize = ""))
|
||||
)]
|
||||
#[derive_where(Clone, Debug, Eq, Hash, PartialEq, ZeroizeOnDrop)]
|
||||
pub struct VerifyMessage<'a, CS: CipherSuite, KE: Group> {
|
||||
role: Role,
|
||||
context: SerializedContext<'a>,
|
||||
identifier: SerializedIdentifier<'a, KeGroup<CS>>,
|
||||
pub(super) cache: CachedMessage<CS, KE>,
|
||||
}
|
||||
|
||||
/// Used to build [`VerifyMessage`]. It is only available in
|
||||
/// [`SignatureProtocol::verify()`].
|
||||
///
|
||||
/// [`SignatureProtocol::verify()`]: super::SignatureProtocol::verify
|
||||
#[derive(Debug, Eq, Hash, PartialEq, ZeroizeOnDrop)]
|
||||
pub struct MessageBuilder<'a, CS: CipherSuite> {
|
||||
pub(super) role: Role,
|
||||
pub(super) context: SerializedContext<'a>,
|
||||
pub(super) identifier: SerializedIdentifier<'a, KeGroup<CS>>,
|
||||
}
|
||||
|
||||
/// Created by [`Message::to_cached()`]. This is used to save the message to be
|
||||
/// verified in [`SignatureProtocol::VerifyState`].
|
||||
///
|
||||
/// Use [`MessageBuilder::build()`] to create [`VerifyMessage`] in
|
||||
/// [`SignatureProtocol::verify()`].
|
||||
///
|
||||
/// [`SignatureProtocol::verify()`]: super::SignatureProtocol::verify
|
||||
/// [`SignatureProtocol::VerifyState`]: super::SignatureProtocol::VerifyState
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound = "")
|
||||
)]
|
||||
#[derive_where(Clone, Debug, Eq, Hash, PartialEq, Zeroize, ZeroizeOnDrop)]
|
||||
pub struct CachedMessage<CS: CipherSuite, KE: Group> {
|
||||
pub(super) credential_request: CredentialRequestParts<CS>,
|
||||
pub(super) ke1_message: Ke1MessageIter<KE>,
|
||||
pub(super) credential_response: CredentialResponseParts<CS>,
|
||||
pub(super) server_nonce: GenericArray<u8, NonceLen>,
|
||||
pub(super) server_e_pk: GenericArray<u8, KE::PkLen>,
|
||||
pub(super) server_mac: Output<KeHash<CS>>,
|
||||
}
|
||||
|
||||
impl<CS: CipherSuite, KE: Group> Message<'_, CS, KE> {
|
||||
/// Returns the message to be signed.
|
||||
pub fn sign_message(&self) -> impl Clone + Iterator<Item = &[u8]> {
|
||||
self.context.iter().chain(self.post_message(Stage::Sign))
|
||||
}
|
||||
|
||||
/// Returns the hash of both messages.
|
||||
pub fn hash<KEH: Default + Clone + FixedOutput + Update>(&self) -> HashOutput<KEH> {
|
||||
let mut context = KEH::default();
|
||||
context.update_iter(self.context.iter());
|
||||
|
||||
let sign = context.clone().chain_iter(self.post_message(Stage::Sign));
|
||||
let verify = context.chain_iter(self.post_message(Stage::Verify));
|
||||
|
||||
HashOutput { sign, verify }
|
||||
}
|
||||
|
||||
fn post_message(&self, stage: Stage) -> impl Clone + Iterator<Item = &[u8]> {
|
||||
let transcript = match (self.role, stage) {
|
||||
(Role::Server, Stage::Sign) => Role::Server,
|
||||
(Role::Server, Stage::Verify) => Role::Client,
|
||||
(Role::Client, Stage::Sign) => Role::Client,
|
||||
(Role::Client, Stage::Verify) => Role::Server,
|
||||
};
|
||||
let identifier = match transcript {
|
||||
Role::Server => &self.identifiers.server,
|
||||
Role::Client => &self.identifiers.client,
|
||||
};
|
||||
|
||||
self.cache.post_message(transcript, identifier)
|
||||
}
|
||||
|
||||
/// Create a [`CachedMessage`], which can be saved in
|
||||
/// [`SignatureProtocol::VerifyState`] and create a [`VerifyMessage`] with
|
||||
/// [`MessageBuilder::build()`].
|
||||
///
|
||||
/// [`SignatureProtocol::VerifyState`]: super::SignatureProtocol::VerifyState
|
||||
pub fn to_cached(&self) -> CachedMessage<CS, KE> {
|
||||
self.cache.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<CS: CipherSuite, KE: Group> VerifyMessage<'_, CS, KE> {
|
||||
/// Returns the message to be verified.
|
||||
pub fn verify_message(&self) -> impl Clone + Iterator<Item = &[u8]> {
|
||||
let transcript = match self.role {
|
||||
Role::Server => Role::Client,
|
||||
Role::Client => Role::Server,
|
||||
};
|
||||
|
||||
self.context
|
||||
.iter()
|
||||
.chain(self.cache.post_message(transcript, &self.identifier))
|
||||
}
|
||||
}
|
||||
|
||||
impl<CS: CipherSuite, KE: Group> CachedMessage<CS, KE> {
|
||||
fn post_message<'a>(
|
||||
&'a self,
|
||||
transcript: Role,
|
||||
identifier: &'a SerializedIdentifier<'_, KeGroup<CS>>,
|
||||
) -> impl Clone + Iterator<Item = &'a [u8]> {
|
||||
Some(identifier.iter())
|
||||
.filter(|_| matches!(transcript, Role::Client))
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.chain(self.credential_request.iter())
|
||||
.chain(self.ke1_message.iter())
|
||||
.chain(
|
||||
Some(identifier.iter())
|
||||
.filter(|_| matches!(transcript, Role::Server))
|
||||
.into_iter()
|
||||
.flatten(),
|
||||
)
|
||||
.chain(self.credential_response.iter())
|
||||
.chain([self.server_nonce.as_slice(), &self.server_e_pk])
|
||||
.chain(Some(self.server_mac.as_slice()).filter(|_| matches!(transcript, Role::Client)))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound = "")
|
||||
)]
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Zeroize)]
|
||||
pub(super) enum Role {
|
||||
Server,
|
||||
Client,
|
||||
}
|
||||
|
||||
enum Stage {
|
||||
Sign,
|
||||
Verify,
|
||||
}
|
||||
|
||||
/// Returned by [`Message::hash()`] containing the hash of the message to be
|
||||
/// signed and the message to be verified.
|
||||
pub struct HashOutput<H> {
|
||||
/// The hash of the message to be signed.
|
||||
pub sign: H,
|
||||
/// The hash of the message to be verified.
|
||||
pub verify: H,
|
||||
}
|
||||
|
||||
impl<'a, CS: CipherSuite> MessageBuilder<'a, CS> {
|
||||
/// Creates a [`VerifyMessage`]. [`CachedMessage`] can be created by
|
||||
/// [`Message::to_cached()`] and stored in
|
||||
/// [`SignatureProtocol::VerifyState`].
|
||||
///
|
||||
/// [`SignatureProtocol::VerifyState`]: super::SignatureProtocol::VerifyState
|
||||
pub fn build<KE: Group>(self, cache: CachedMessage<CS, KE>) -> VerifyMessage<'a, CS, KE> {
|
||||
VerifyMessage {
|
||||
role: self.role,
|
||||
context: self.context.clone(),
|
||||
identifier: self.identifier.clone(),
|
||||
cache,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<CS: CipherSuite, KE: Group> Deserialize for CachedMessage<CS, KE> {
|
||||
fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
Ok(Self {
|
||||
credential_request: CredentialRequestParts::deserialize_take(input)?,
|
||||
ke1_message: Ke1MessageIter::deserialize_take(input)?,
|
||||
credential_response: CredentialResponseParts::deserialize_take(input)?,
|
||||
server_nonce: input.take_array("server nonce")?,
|
||||
server_e_pk: input.take_array("serialized server ephemeral key")?,
|
||||
server_mac: input.take_array("server mac")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Length of [`CachedMessage`].
|
||||
type CachedMessageLen<CS: CipherSuite, KE: Group> = Sum<
|
||||
Sum<
|
||||
Sum<
|
||||
Sum<
|
||||
Sum<CredentialRequestPartsLen<CS>, Ke1MessageIterLen<KE>>,
|
||||
CredentialResponsePartsLen<CS>,
|
||||
>,
|
||||
NonceLen,
|
||||
>,
|
||||
KE::PkLen,
|
||||
>,
|
||||
OutputSize<KeHash<CS>>,
|
||||
>;
|
||||
|
||||
impl<CS: CipherSuite, KE: Group> Serialize for CachedMessage<CS, KE>
|
||||
where
|
||||
CredentialRequestPartsLen<CS>: ArrayLength<u8> + Add<Ke1MessageIterLen<KE>>,
|
||||
Sum<CredentialRequestPartsLen<CS>, Ke1MessageIterLen<KE>>:
|
||||
ArrayLength<u8> + Add<CredentialResponsePartsLen<CS>>,
|
||||
Sum<Sum<CredentialRequestPartsLen<CS>, Ke1MessageIterLen<KE>>, CredentialResponsePartsLen<CS>>:
|
||||
ArrayLength<u8> + Add<NonceLen>,
|
||||
Sum<
|
||||
Sum<
|
||||
Sum<CredentialRequestPartsLen<CS>, Ke1MessageIterLen<KE>>,
|
||||
CredentialResponsePartsLen<CS>,
|
||||
>,
|
||||
NonceLen,
|
||||
>: ArrayLength<u8> + Add<KE::PkLen>,
|
||||
Sum<
|
||||
Sum<
|
||||
Sum<
|
||||
Sum<CredentialRequestPartsLen<CS>, Ke1MessageIterLen<KE>>,
|
||||
CredentialResponsePartsLen<CS>,
|
||||
>,
|
||||
NonceLen,
|
||||
>,
|
||||
KE::PkLen,
|
||||
>: ArrayLength<u8> + Add<OutputSize<KeHash<CS>>>,
|
||||
CachedMessageLen<CS, KE>: ArrayLength<u8>,
|
||||
// Ke1MessageIter
|
||||
NonceLen: Add<KE::PkLen>,
|
||||
Ke1MessageIterLen<KE>: ArrayLength<u8>,
|
||||
// CredentialResponseParts
|
||||
<OprfGroup<CS> as voprf::Group>::ElemLen: Add<NonceLen>,
|
||||
Sum<<OprfGroup<CS> as voprf::Group>::ElemLen, NonceLen>:
|
||||
ArrayLength<u8> + Add<MaskedResponseLen<CS>>,
|
||||
CredentialResponsePartsLen<CS>: ArrayLength<u8>,
|
||||
{
|
||||
type Len = CachedMessageLen<CS, KE>;
|
||||
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len> {
|
||||
self.credential_request
|
||||
.serialize()
|
||||
.concat(self.ke1_message.serialize())
|
||||
.concat(self.credential_response.serialize())
|
||||
.concat(self.server_nonce)
|
||||
.concat(self.server_e_pk.clone())
|
||||
.concat(self.server_mac.clone())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,645 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
//
|
||||
// This source code is dual-licensed under either the MIT license found in the
|
||||
// LICENSE-MIT file in the root directory of this source tree or the Apache
|
||||
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
|
||||
// of this source tree. You may select, at your option, one of the above-listed
|
||||
// licenses.
|
||||
|
||||
//! An implementation of the SIGMA-I key exchange protocol
|
||||
//!
|
||||
//! ⚠️ **Warning**: This implementation has not been audited. Use at your own
|
||||
//! risk!
|
||||
|
||||
#[cfg(feature = "ecdsa")]
|
||||
pub mod ecdsa;
|
||||
pub mod hash_eddsa;
|
||||
mod message;
|
||||
pub mod pure_eddsa;
|
||||
pub(super) mod shared;
|
||||
|
||||
use core::iter;
|
||||
use core::marker::PhantomData;
|
||||
use core::ops::Add;
|
||||
|
||||
use derive_where::derive_where;
|
||||
use digest::core_api::BlockSizeUser;
|
||||
use digest::{Digest, Mac, Output, OutputSizeUser};
|
||||
use generic_array::sequence::Concat;
|
||||
use generic_array::typenum::{IsLess, Le, NonZero, Sum, U256};
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
use hmac::Hmac;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use subtle::{ConstantTimeEq, CtOption};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use self::message::Role;
|
||||
pub use self::message::{CachedMessage, HashOutput, Message, MessageBuilder, VerifyMessage};
|
||||
use crate::ciphersuite::{CipherSuite, KeGroup, KeHash};
|
||||
use crate::envelope::NonceLen;
|
||||
use crate::errors::{InternalError, ProtocolError};
|
||||
use crate::hash::{Hash, OutputSize, ProxyHash};
|
||||
use crate::key_exchange::group::Group;
|
||||
use crate::key_exchange::shared::{derive_keys, generate_ke1, generate_nonce, transcript};
|
||||
pub use crate::key_exchange::shared::{DiffieHellman, Ke1Message, Ke1State};
|
||||
use crate::key_exchange::traits::{
|
||||
CredentialRequestParts, CredentialResponseParts, Deserialize, GenerateKe2Result,
|
||||
GenerateKe3Result, KeyExchange, Sealed, Serialize, SerializedContext, SerializedIdentifier,
|
||||
SerializedIdentifiers,
|
||||
};
|
||||
use crate::keypair::{KeyPair, PrivateKey, PublicKey};
|
||||
use crate::opaque::Identifiers;
|
||||
use crate::serialization::{SliceExt, UpdateExt};
|
||||
|
||||
/// The SIGMA-I key exchange implementation
|
||||
///
|
||||
/// `SIG` determines the algorithm used for the signature. `KE` determines the
|
||||
/// algorithm used for establishing the shared secret. `KEH` determines the hash
|
||||
/// used for the key exchange.
|
||||
///
|
||||
/// # Remote Key
|
||||
///
|
||||
/// [`ServerLoginBuilder::data()`](crate::ServerLoginBuilder::data()) will
|
||||
/// return [`Message`].
|
||||
///
|
||||
/// [`ServerLoginBuilder::build()`](crate::ServerLoginBuilder::build()) expects
|
||||
/// a signature from signing the [message](Message::sign_message) with the
|
||||
/// servers private key, and a ["verification
|
||||
/// state"](SignatureProtocol::VerifyState).
|
||||
///
|
||||
/// To understand what kind of "verification state" is expected here exactly,
|
||||
/// refer to the documentation of your chosen [`SignatureProtocol`] `SIG`. E.g.
|
||||
/// [`Ecdsa`](ecdsa::Ecdsa), [`PureEddsa`](pure_eddsa::PureEddsa) or
|
||||
/// [`HashEddsa`](hash_eddsa::HashEddsa).
|
||||
pub struct SigmaI<SIG, KE, KEH>(PhantomData<(SIG, KE, KEH)>);
|
||||
|
||||
/// Trait to implement for `SIG` used in [`SigmaI`].
|
||||
///
|
||||
/// The [`sign()`] and [`verify()`] methods do not function independent of each
|
||||
/// other. [`sign()`] is always called first and receives a [Message] containing
|
||||
/// the message for both signing and verifying. A ["verification
|
||||
/// state"](Self::VerifyState) is created by [`sign()`] and then passed onto
|
||||
/// [`verify()`].
|
||||
///
|
||||
/// The most straightforward implementation would simply store the message for
|
||||
/// verifying in [`VerifyState`](Self::VerifyState). However, protocols that
|
||||
/// allow for pre-hashing don't need to store the whole message and can
|
||||
/// preemptively hash the verification message and only store that instead,
|
||||
/// getting rid of the much larger message.
|
||||
///
|
||||
/// [`sign()`]: Self::sign
|
||||
/// [`verify()`]: Self::verify
|
||||
pub trait SignatureProtocol {
|
||||
/// The [`Group`] used to generate and derive keys.
|
||||
type Group: Group;
|
||||
/// The signature.
|
||||
type Signature: Clone + Zeroize;
|
||||
/// Length of a serialized [`Signature`](Self::Signature).
|
||||
type SignatureLen: ArrayLength<u8>;
|
||||
/// The state required to run the verification. This is used to cache the
|
||||
/// pre-hash for curves that support that, otherwise the [`Message`] to
|
||||
/// verify is stored via [`CachedMessage`].
|
||||
type VerifyState<CS: CipherSuite, KE: Group>: Clone + Zeroize;
|
||||
|
||||
/// Returns a signature from the given message signed by the given private
|
||||
/// key.
|
||||
///
|
||||
/// [`Message`] contains both signature messages for signing and
|
||||
/// verification. If you need it again during verification, consider
|
||||
/// using [`CachedMessage`].
|
||||
///
|
||||
/// The returned [`VerifyState`](Self::VerifyState) will be passed to
|
||||
/// [`verify()`](Self::verify) and must contain the necessary
|
||||
/// information to verify the incoming signature.
|
||||
fn sign<R: CryptoRng + RngCore, CS: CipherSuite, KE: Group>(
|
||||
sk: &<Self::Group as Group>::Sk,
|
||||
rng: &mut R,
|
||||
message: &Message<CS, KE>,
|
||||
) -> (Self::Signature, Self::VerifyState<CS, KE>);
|
||||
|
||||
/// Validates that the signature was created by signing the message with the
|
||||
/// corresponding private key.
|
||||
///
|
||||
/// The [`MessageBuilder`] can be used with [`CachedMessage`] to create
|
||||
/// [`VerifyMessage`] which contains the message of the given `signature`.
|
||||
///
|
||||
/// The `state` is created by [`sign()`](Self::sign()).
|
||||
fn verify<CS: CipherSuite, KE: Group>(
|
||||
pk: &<Self::Group as Group>::Pk,
|
||||
message_builder: MessageBuilder<'_, CS>,
|
||||
state: Self::VerifyState<CS, KE>,
|
||||
signature: &Self::Signature,
|
||||
) -> Result<(), ProtocolError>;
|
||||
|
||||
/// Serialize [`Signature`](Self::Signature) into a fixed-sized byte array.
|
||||
fn serialize_signature(signature: &Self::Signature) -> GenericArray<u8, Self::SignatureLen>;
|
||||
|
||||
/// Deserialize [`Signature`](Self::Signature) from the given `bytes`.
|
||||
///
|
||||
/// The deserialized bytes must be taken from `bytes`.
|
||||
fn deserialize_take_signature(bytes: &mut &[u8]) -> Result<Self::Signature, ProtocolError>;
|
||||
}
|
||||
|
||||
/// Builder for the second key exchange message
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(deserialize = "'de: 'a", serialize = ""))
|
||||
)]
|
||||
#[derive_where(Clone, ZeroizeOnDrop)]
|
||||
#[derive_where(Debug, Eq, Hash, PartialEq; PublicKey<KeGroup<CS>>, PublicKey<KE>)]
|
||||
pub struct Ke2Builder<'a, CS: CipherSuite, KE: Group> {
|
||||
transcript: Message<'a, CS, KE>,
|
||||
server_nonce: GenericArray<u8, NonceLen>,
|
||||
client_s_pk: PublicKey<KeGroup<CS>>,
|
||||
server_e_pk: PublicKey<KE>,
|
||||
expected_mac: Output<KeHash<CS>>,
|
||||
session_key: Output<KeHash<CS>>,
|
||||
#[cfg(test)]
|
||||
km3: Output<KeHash<CS>>,
|
||||
#[cfg(test)]
|
||||
handshake_secret: Output<KeHash<CS>>,
|
||||
}
|
||||
|
||||
/// The server state produced after the second key exchange message
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(
|
||||
deserialize = "SIG::VerifyState<CS, KE>: serde::Deserialize<'de>",
|
||||
serialize = "SIG::VerifyState<CS, KE>: serde::Serialize"
|
||||
))
|
||||
)]
|
||||
#[derive_where(Clone, ZeroizeOnDrop)]
|
||||
#[derive_where(Debug, Eq, Hash, PartialEq; <SIG::Group as Group>::Pk, SIG::VerifyState<CS, KE>)]
|
||||
pub struct Ke2State<CS: CipherSuite, SIG: SignatureProtocol, KE: Group> {
|
||||
client_s_pk: PublicKey<SIG::Group>,
|
||||
session_key: Output<KeHash<CS>>,
|
||||
verify_state: SIG::VerifyState<CS, KE>,
|
||||
expected_mac: Output<KeHash<CS>>,
|
||||
}
|
||||
|
||||
/// The second key exchange message
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(
|
||||
deserialize = "SIG::Signature: serde::Deserialize<'de>",
|
||||
serialize = "SIG::Signature: serde::Serialize"
|
||||
))
|
||||
)]
|
||||
#[derive_where(Clone, ZeroizeOnDrop)]
|
||||
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; KE::Pk, SIG::Signature)]
|
||||
pub struct Ke2Message<SIG: SignatureProtocol, KE: Group, KEH: Hash>
|
||||
where
|
||||
KEH::Core: ProxyHash,
|
||||
<KEH::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<KEH::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
{
|
||||
server_nonce: GenericArray<u8, NonceLen>,
|
||||
server_e_pk: PublicKey<KE>,
|
||||
signature: SIG::Signature,
|
||||
mac: Output<KEH>,
|
||||
}
|
||||
|
||||
/// The third key exchange message
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(
|
||||
deserialize = "SIG::Signature: serde::Deserialize<'de>",
|
||||
serialize = "SIG::Signature: serde::Serialize"
|
||||
))
|
||||
)]
|
||||
#[derive_where(Clone, ZeroizeOnDrop)]
|
||||
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; SIG::Signature)]
|
||||
pub struct Ke3Message<SIG: SignatureProtocol, KEH: OutputSizeUser> {
|
||||
signature: SIG::Signature,
|
||||
mac: Output<KEH>,
|
||||
}
|
||||
|
||||
impl<SIG: SignatureProtocol, KE: 'static + Group, KEH: Hash> KeyExchange for SigmaI<SIG, KE, KEH>
|
||||
where
|
||||
KE::Sk: DiffieHellman<KE>,
|
||||
KEH::Core: ProxyHash,
|
||||
<KEH::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<KEH::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
{
|
||||
type Group = SIG::Group;
|
||||
type Hash = KEH;
|
||||
|
||||
type KE1State = Ke1State<KE>;
|
||||
type KE1Message = Ke1Message<KE>;
|
||||
type KE2Builder<'a, CS: CipherSuite<KeyExchange = Self>> = Ke2Builder<'a, CS, KE>;
|
||||
type KE2BuilderData<'a, CS: 'static + CipherSuite> = &'a Message<'a, CS, KE>;
|
||||
type KE2BuilderInput<CS: CipherSuite> = (SIG::Signature, SIG::VerifyState<CS, KE>);
|
||||
type KE2State<CS: CipherSuite> = Ke2State<CS, SIG, KE>;
|
||||
type KE2Message = Ke2Message<SIG, KE, KEH>;
|
||||
type KE3Message = Ke3Message<SIG, KEH>;
|
||||
|
||||
fn generate_ke1<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError> {
|
||||
generate_ke1(rng)
|
||||
}
|
||||
|
||||
fn ke2_builder<'a, CS: CipherSuite<KeyExchange = Self>, R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
credential_request: CredentialRequestParts<CS>,
|
||||
ke1_message: Self::KE1Message,
|
||||
credential_response: CredentialResponseParts<CS>,
|
||||
client_s_pk: PublicKey<Self::Group>,
|
||||
identifiers: SerializedIdentifiers<'a, KeGroup<CS>>,
|
||||
context: SerializedContext<'a>,
|
||||
) -> Result<Self::KE2Builder<'a, CS>, ProtocolError> {
|
||||
let server_e = KeyPair::<KE>::derive_random(rng);
|
||||
let server_nonce = generate_nonce::<R>(rng);
|
||||
|
||||
let ke1_message_iter = ke1_message.to_iter();
|
||||
let server_e_pk = server_e.public().serialize();
|
||||
|
||||
let transcript_hasher = transcript(
|
||||
&context,
|
||||
&identifiers,
|
||||
&credential_request,
|
||||
&ke1_message_iter,
|
||||
&credential_response,
|
||||
server_nonce,
|
||||
&server_e_pk,
|
||||
);
|
||||
|
||||
let shared_secret = server_e
|
||||
.private()
|
||||
.ke_diffie_hellman(&ke1_message.client_e_pk);
|
||||
|
||||
let derived_keys = derive_keys::<KEH>(
|
||||
iter::once(shared_secret.as_slice()),
|
||||
&transcript_hasher.finalize(),
|
||||
)?;
|
||||
|
||||
let mut server_mac =
|
||||
Hmac::<KEH>::new_from_slice(&derived_keys.km2).map_err(|_| InternalError::HmacError)?;
|
||||
server_mac.update_iter(identifiers.server.iter());
|
||||
let server_mac = server_mac.finalize().into_bytes();
|
||||
|
||||
let mut client_mac =
|
||||
Hmac::<KEH>::new_from_slice(&derived_keys.km3).map_err(|_| InternalError::HmacError)?;
|
||||
client_mac.update_iter(identifiers.client.iter());
|
||||
let client_mac = client_mac.finalize().into_bytes();
|
||||
|
||||
let message = Message {
|
||||
role: Role::Server,
|
||||
context,
|
||||
identifiers,
|
||||
cache: CachedMessage {
|
||||
credential_request,
|
||||
ke1_message: ke1_message_iter,
|
||||
credential_response,
|
||||
server_nonce,
|
||||
server_e_pk,
|
||||
server_mac,
|
||||
},
|
||||
};
|
||||
|
||||
Ok(Ke2Builder {
|
||||
transcript: message,
|
||||
server_nonce,
|
||||
client_s_pk,
|
||||
server_e_pk: server_e.public().clone(),
|
||||
expected_mac: client_mac,
|
||||
session_key: derived_keys.session_key,
|
||||
#[cfg(test)]
|
||||
km3: derived_keys.km3,
|
||||
#[cfg(test)]
|
||||
handshake_secret: derived_keys.handshake_secret,
|
||||
})
|
||||
}
|
||||
|
||||
fn ke2_builder_data<'a, CS: 'static + CipherSuite<KeyExchange = Self>>(
|
||||
builder: &'a Self::KE2Builder<'_, CS>,
|
||||
) -> Self::KE2BuilderData<'a, CS> {
|
||||
&builder.transcript
|
||||
}
|
||||
|
||||
fn generate_ke2_input<CS: CipherSuite<KeyExchange = Self>, R: CryptoRng + RngCore>(
|
||||
builder: &Self::KE2Builder<'_, CS>,
|
||||
rng: &mut R,
|
||||
server_s_sk: &PrivateKey<Self::Group>,
|
||||
) -> Self::KE2BuilderInput<CS> {
|
||||
server_s_sk.sign::<_, CS, SIG, KE>(rng, &builder.transcript)
|
||||
}
|
||||
|
||||
fn build_ke2<CS: CipherSuite<KeyExchange = Self>>(
|
||||
builder: Self::KE2Builder<'_, CS>,
|
||||
input: Self::KE2BuilderInput<CS>,
|
||||
) -> Result<GenerateKe2Result<CS>, ProtocolError> {
|
||||
Ok((
|
||||
Ke2State {
|
||||
client_s_pk: builder.client_s_pk.clone(),
|
||||
session_key: builder.session_key.clone(),
|
||||
verify_state: input.1,
|
||||
expected_mac: builder.expected_mac.clone(),
|
||||
},
|
||||
Ke2Message {
|
||||
server_nonce: builder.server_nonce,
|
||||
server_e_pk: builder.server_e_pk.clone(),
|
||||
signature: input.0,
|
||||
mac: builder.transcript.cache.server_mac.clone(),
|
||||
},
|
||||
#[cfg(test)]
|
||||
builder.handshake_secret.clone(),
|
||||
#[cfg(test)]
|
||||
builder.km3.clone(),
|
||||
))
|
||||
}
|
||||
|
||||
fn generate_ke3<CS: CipherSuite<KeyExchange = Self>, R: CryptoRng + RngCore>(
|
||||
rng: &mut R,
|
||||
credential_request: CredentialRequestParts<CS>,
|
||||
ke1_message: Self::KE1Message,
|
||||
credential_response: CredentialResponseParts<CS>,
|
||||
ke2_message: Self::KE2Message,
|
||||
ke1_state: &Self::KE1State,
|
||||
server_s_pk: PublicKey<Self::Group>,
|
||||
client_s_sk: PrivateKey<Self::Group>,
|
||||
identifiers: SerializedIdentifiers<'_, KeGroup<CS>>,
|
||||
context: SerializedContext<'_>,
|
||||
) -> Result<GenerateKe3Result<Self>, ProtocolError> {
|
||||
let ke1_message_iter = ke1_message.to_iter();
|
||||
let server_e_pk = ke2_message.server_e_pk.serialize();
|
||||
|
||||
let transcript_hasher = transcript(
|
||||
&context,
|
||||
&identifiers,
|
||||
&credential_request,
|
||||
&ke1_message_iter,
|
||||
&credential_response,
|
||||
ke2_message.server_nonce,
|
||||
&server_e_pk,
|
||||
);
|
||||
|
||||
let shared_secret = ke1_state
|
||||
.client_e_sk
|
||||
.ke_diffie_hellman(&ke2_message.server_e_pk);
|
||||
|
||||
let derived_keys = derive_keys::<KEH>(
|
||||
iter::once(shared_secret.as_slice()),
|
||||
&transcript_hasher.finalize(),
|
||||
)?;
|
||||
|
||||
let mut server_mac =
|
||||
Hmac::<KEH>::new_from_slice(&derived_keys.km2).map_err(|_| InternalError::HmacError)?;
|
||||
server_mac.update_iter(identifiers.server.iter());
|
||||
let server_mac = server_mac.finalize().into_bytes();
|
||||
|
||||
bool::from(server_mac.ct_eq(&ke2_message.mac))
|
||||
.then_some(())
|
||||
.ok_or(ProtocolError::InvalidLoginError)?;
|
||||
|
||||
let mut client_mac =
|
||||
Hmac::<KEH>::new_from_slice(&derived_keys.km3).map_err(|_| InternalError::HmacError)?;
|
||||
client_mac.update_iter(identifiers.client.iter());
|
||||
let client_mac = client_mac.finalize().into_bytes();
|
||||
|
||||
let message = Message {
|
||||
role: Role::Client,
|
||||
context: context.clone(),
|
||||
identifiers: identifiers.clone(),
|
||||
cache: CachedMessage {
|
||||
credential_request,
|
||||
ke1_message: ke1_message_iter,
|
||||
credential_response,
|
||||
server_nonce: ke2_message.server_nonce,
|
||||
server_e_pk,
|
||||
server_mac,
|
||||
},
|
||||
};
|
||||
|
||||
let (signature, state) = client_s_sk.sign::<_, CS, SIG, KE>(rng, &message);
|
||||
|
||||
server_s_pk.verify::<CS, SIG, KE>(
|
||||
MessageBuilder {
|
||||
role: Role::Client,
|
||||
context,
|
||||
identifier: identifiers.server,
|
||||
},
|
||||
state,
|
||||
&ke2_message.signature,
|
||||
)?;
|
||||
|
||||
Ok((
|
||||
derived_keys.session_key,
|
||||
Ke3Message {
|
||||
signature,
|
||||
mac: client_mac,
|
||||
},
|
||||
#[cfg(test)]
|
||||
derived_keys.handshake_secret,
|
||||
#[cfg(test)]
|
||||
derived_keys.km3,
|
||||
))
|
||||
}
|
||||
|
||||
fn finish_ke<CS: CipherSuite<KeyExchange = Self>>(
|
||||
ke3_message: Self::KE3Message,
|
||||
ke2_state: &Self::KE2State<CS>,
|
||||
identifiers: Identifiers<'_>,
|
||||
context: SerializedContext<'_>,
|
||||
) -> Result<Output<KEH>, ProtocolError> {
|
||||
ke2_state.client_s_pk.verify::<CS, SIG, KE>(
|
||||
MessageBuilder {
|
||||
role: Role::Server,
|
||||
context,
|
||||
identifier: SerializedIdentifier::from_identifier(
|
||||
identifiers.client,
|
||||
ke2_state.client_s_pk.serialize(),
|
||||
)?,
|
||||
},
|
||||
ke2_state.verify_state.clone(),
|
||||
&ke3_message.signature,
|
||||
)?;
|
||||
|
||||
CtOption::new(
|
||||
ke2_state.session_key.clone(),
|
||||
ke2_state.expected_mac.ct_eq(&ke3_message.mac),
|
||||
)
|
||||
.into_option()
|
||||
.ok_or(ProtocolError::InvalidLoginError)
|
||||
}
|
||||
}
|
||||
|
||||
impl<SIG: SignatureProtocol, KE: 'static + Group, KEH: Hash> Sealed for SigmaI<SIG, KE, KEH>
|
||||
where
|
||||
KEH::Core: ProxyHash,
|
||||
<KEH::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<KEH::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
{
|
||||
}
|
||||
|
||||
impl<CS: CipherSuite, SIG: SignatureProtocol, KE: Group> Deserialize for Ke2State<CS, SIG, KE>
|
||||
where
|
||||
SIG::VerifyState<CS, KE>: Deserialize,
|
||||
{
|
||||
fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
Ok(Self {
|
||||
client_s_pk: PublicKey::deserialize_take(input)?,
|
||||
session_key: input.take_array("session key")?,
|
||||
verify_state: SIG::VerifyState::deserialize_take(input)?,
|
||||
expected_mac: input.take_array("expected mac")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type Ke2StateLen<CS, SIG: SignatureProtocol, KE> = Sum<
|
||||
Sum<Sum<<SIG::Group as Group>::PkLen, OutputSize<KeHash<CS>>>, VerifyStateLen<CS, SIG, KE>>,
|
||||
OutputSize<KeHash<CS>>,
|
||||
>;
|
||||
|
||||
type VerifyStateLen<CS, SIG: SignatureProtocol, KE> = <SIG::VerifyState<CS, KE> as Serialize>::Len;
|
||||
|
||||
impl<CS: CipherSuite, SIG: SignatureProtocol, KE: Group> Serialize for Ke2State<CS, SIG, KE>
|
||||
where
|
||||
SIG::VerifyState<CS, KE>: Serialize,
|
||||
// Ke2State: ((SigPk + Hash) + VerifyState) + Hash
|
||||
<SIG::Group as Group>::PkLen: Add<OutputSize<KeHash<CS>>>,
|
||||
Sum<<SIG::Group as Group>::PkLen, OutputSize<KeHash<CS>>>:
|
||||
ArrayLength<u8> + Add<VerifyStateLen<CS, SIG, KE>>,
|
||||
Sum<Sum<<SIG::Group as Group>::PkLen, OutputSize<KeHash<CS>>>, VerifyStateLen<CS, SIG, KE>>:
|
||||
ArrayLength<u8> + Add<OutputSize<KeHash<CS>>>,
|
||||
Ke2StateLen<CS, SIG, KE>: ArrayLength<u8>,
|
||||
{
|
||||
type Len = Ke2StateLen<CS, SIG, KE>;
|
||||
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len> {
|
||||
self.client_s_pk
|
||||
.serialize()
|
||||
.concat(self.session_key.clone())
|
||||
.concat(self.verify_state.serialize())
|
||||
.concat(self.expected_mac.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<SIG: SignatureProtocol, KE: Group, KEH: Hash> Deserialize for Ke2Message<SIG, KE, KEH>
|
||||
where
|
||||
KEH::Core: ProxyHash,
|
||||
<KEH::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<KEH::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
{
|
||||
fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
Ok(Self {
|
||||
server_nonce: input.take_array("server nonce")?,
|
||||
server_e_pk: PublicKey::deserialize_take(input)?,
|
||||
signature: SIG::deserialize_take_signature(input)?,
|
||||
mac: input.take_array("mac")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<SIG: SignatureProtocol, KE: Group, KEH: Hash> Serialize for Ke2Message<SIG, KE, KEH>
|
||||
where
|
||||
KEH::Core: ProxyHash,
|
||||
<KEH::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<KEH::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
// Ke2Message: ((Nonce + KePk) + Signature) + Hash
|
||||
NonceLen: Add<KE::PkLen>,
|
||||
Sum<NonceLen, KE::PkLen>: ArrayLength<u8> + Add<SIG::SignatureLen>,
|
||||
Sum<Sum<NonceLen, KE::PkLen>, SIG::SignatureLen>: ArrayLength<u8> + Add<OutputSize<KEH>>,
|
||||
Sum<Sum<Sum<NonceLen, KE::PkLen>, SIG::SignatureLen>, OutputSize<KEH>>: ArrayLength<u8>,
|
||||
{
|
||||
type Len = Sum<Sum<Sum<NonceLen, KE::PkLen>, SIG::SignatureLen>, OutputSize<KEH>>;
|
||||
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len> {
|
||||
self.server_nonce
|
||||
.concat(self.server_e_pk.serialize())
|
||||
.concat(SIG::serialize_signature(&self.signature))
|
||||
.concat(self.mac.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<SIG: SignatureProtocol, KEH: Hash> Deserialize for Ke3Message<SIG, KEH>
|
||||
where
|
||||
KEH::Core: ProxyHash,
|
||||
<KEH::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<KEH::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
{
|
||||
fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
Ok(Self {
|
||||
signature: SIG::deserialize_take_signature(input)?,
|
||||
mac: input.take_array("mac")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<SIG: SignatureProtocol, KEH: Hash> Serialize for Ke3Message<SIG, KEH>
|
||||
where
|
||||
KEH::Core: ProxyHash,
|
||||
<KEH::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<KEH::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
// Ke2Message: Signature + Hash
|
||||
SIG::SignatureLen: Add<OutputSize<KEH>>,
|
||||
Sum<SIG::SignatureLen, OutputSize<KEH>>: ArrayLength<u8>,
|
||||
{
|
||||
type Len = Sum<SIG::SignatureLen, OutputSize<KEH>>;
|
||||
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len> {
|
||||
SIG::serialize_signature(&self.signature).concat(self.mac.clone())
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////
|
||||
// Test Implementations //
|
||||
//===================== //
|
||||
//////////////////////////
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::key_exchange::shared::Ke1MessageIter;
|
||||
#[cfg(test)]
|
||||
use crate::serialization::AssertZeroized;
|
||||
|
||||
#[cfg(test)]
|
||||
impl<CS: CipherSuite, KE: Group> AssertZeroized for CachedMessage<CS, KE>
|
||||
where
|
||||
Ke1MessageIter<KE>: AssertZeroized,
|
||||
{
|
||||
fn assert_zeroized(&self) {
|
||||
let Self {
|
||||
credential_request,
|
||||
ke1_message,
|
||||
credential_response,
|
||||
server_nonce,
|
||||
server_e_pk,
|
||||
server_mac,
|
||||
} = self;
|
||||
|
||||
credential_request.assert_zeroized();
|
||||
ke1_message.assert_zeroized();
|
||||
credential_response.assert_zeroized();
|
||||
|
||||
for byte in server_nonce.iter().chain(server_e_pk).chain(server_mac) {
|
||||
assert_eq!(byte, &0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl<CS: CipherSuite, SIG: SignatureProtocol, KE: Group> AssertZeroized for Ke2State<CS, SIG, KE>
|
||||
where
|
||||
<SIG::Group as Group>::Pk: AssertZeroized,
|
||||
SIG::VerifyState<CS, KE>: AssertZeroized,
|
||||
{
|
||||
fn assert_zeroized(&self) {
|
||||
let Self {
|
||||
client_s_pk,
|
||||
session_key,
|
||||
verify_state,
|
||||
expected_mac,
|
||||
} = self;
|
||||
|
||||
client_s_pk.assert_zeroized();
|
||||
verify_state.assert_zeroized();
|
||||
|
||||
for byte in session_key.iter().chain(expected_mac) {
|
||||
assert_eq!(byte, &0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
//
|
||||
// This source code is dual-licensed under either the MIT license found in the
|
||||
// LICENSE-MIT file in the root directory of this source tree or the Apache
|
||||
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
|
||||
// of this source tree. You may select, at your option, one of the above-listed
|
||||
// licenses.
|
||||
|
||||
//! PureEdDSA implementation for [`SigmaI`](crate::SigmaI). Currently only
|
||||
//! supports [`Ed25519`](crate::Ed25519).
|
||||
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use generic_array::GenericArray;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use self::implementation::PureEddsaImpl;
|
||||
use super::{Message, MessageBuilder, SignatureProtocol};
|
||||
use crate::ciphersuite::CipherSuite;
|
||||
use crate::errors::ProtocolError;
|
||||
use crate::key_exchange::group::Group;
|
||||
use crate::key_exchange::sigma_i::CachedMessage;
|
||||
|
||||
/// PureEdDSA for [`SigmaI`](crate::SigmaI).
|
||||
///
|
||||
/// The ["verification state"](Self::VerifyState) is a [`CachedMessage`],
|
||||
/// created by calling [`Message::to_cached()`].
|
||||
pub struct PureEddsa<G>(PhantomData<G>);
|
||||
|
||||
impl<G: PureEddsaImpl> SignatureProtocol for PureEddsa<G> {
|
||||
type Group = G;
|
||||
type Signature = G::Signature;
|
||||
type SignatureLen = G::SignatureLen;
|
||||
type VerifyState<CS: CipherSuite, KE: Group> = CachedMessage<CS, KE>;
|
||||
|
||||
fn sign<'a, R: CryptoRng + RngCore, CS: CipherSuite, KE: Group>(
|
||||
sk: &G::Sk,
|
||||
_: &mut R,
|
||||
message: &Message<CS, KE>,
|
||||
) -> (Self::Signature, Self::VerifyState<CS, KE>) {
|
||||
G::sign(sk, message)
|
||||
}
|
||||
|
||||
fn verify<CS: CipherSuite, KE: Group>(
|
||||
pk: &G::Pk,
|
||||
message_builder: MessageBuilder<'_, CS>,
|
||||
state: Self::VerifyState<CS, KE>,
|
||||
signature: &Self::Signature,
|
||||
) -> Result<(), ProtocolError> {
|
||||
G::verify(pk, message_builder, state, signature)
|
||||
}
|
||||
|
||||
fn deserialize_take_signature(bytes: &mut &[u8]) -> Result<Self::Signature, ProtocolError> {
|
||||
G::deserialize_take_signature(bytes)
|
||||
}
|
||||
|
||||
fn serialize_signature(signature: &Self::Signature) -> GenericArray<u8, Self::SignatureLen> {
|
||||
G::serialize_signature(signature)
|
||||
}
|
||||
}
|
||||
|
||||
pub(in super::super) mod implementation {
|
||||
use generic_array::ArrayLength;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub trait PureEddsaImpl: Group {
|
||||
type Signature: Clone + Zeroize;
|
||||
type SignatureLen: ArrayLength<u8>;
|
||||
|
||||
fn sign<CS: CipherSuite, KE: Group>(
|
||||
sk: &Self::Sk,
|
||||
message: &Message<CS, KE>,
|
||||
) -> (Self::Signature, CachedMessage<CS, KE>);
|
||||
|
||||
fn verify<CS: CipherSuite, KE: Group>(
|
||||
pk: &Self::Pk,
|
||||
message_builder: MessageBuilder<'_, CS>,
|
||||
state: CachedMessage<CS, KE>,
|
||||
signature: &Self::Signature,
|
||||
) -> Result<(), ProtocolError>;
|
||||
|
||||
fn deserialize_take_signature(bytes: &mut &[u8]) -> Result<Self::Signature, ProtocolError>;
|
||||
|
||||
fn serialize_signature(signature: &Self::Signature)
|
||||
-> GenericArray<u8, Self::SignatureLen>;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
//
|
||||
// This source code is dual-licensed under either the MIT license found in the
|
||||
// LICENSE-MIT file in the root directory of this source tree or the Apache
|
||||
// License, Version 2.0 found in the LICENSE-APACHE file in the root directory
|
||||
// of this source tree. You may select, at your option, one of the above-listed
|
||||
// licenses.
|
||||
|
||||
use derive_where::derive_where;
|
||||
use digest::{Output, OutputSizeUser};
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
|
||||
use crate::errors::ProtocolError;
|
||||
use crate::key_exchange::traits::{Deserialize, Serialize};
|
||||
use crate::serialization::SliceExt;
|
||||
|
||||
/// Pre-hash of the message to be verified.
|
||||
#[derive_where(Clone, Debug, Eq, Hash, PartialEq, Zeroize)]
|
||||
#[derive_where(Copy; <H::OutputSize as ArrayLength<u8>>::ArrayType)]
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound = "")
|
||||
)]
|
||||
pub struct PreHash<H: OutputSizeUser>(pub Output<H>);
|
||||
|
||||
impl<H: OutputSizeUser> Deserialize for PreHash<H> {
|
||||
fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
Ok(Self(input.take_array("pre-hash")?))
|
||||
}
|
||||
}
|
||||
|
||||
impl<H: OutputSizeUser> Serialize for PreHash<H> {
|
||||
type Len = H::OutputSize;
|
||||
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len> {
|
||||
self.0.clone()
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////
|
||||
// Test Implementations //
|
||||
//===================== //
|
||||
//////////////////////////
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::serialization::AssertZeroized;
|
||||
|
||||
#[cfg(test)]
|
||||
impl<H: OutputSizeUser> AssertZeroized for PreHash<H> {
|
||||
fn assert_zeroized(&self) {
|
||||
assert_eq!(self.0, GenericArray::default());
|
||||
}
|
||||
}
|
||||
+345
-68
@@ -6,83 +6,322 @@
|
||||
// of this source tree. You may select, at your option, one of the above-listed
|
||||
// licenses.
|
||||
|
||||
use digest::core_api::BlockSizeUser;
|
||||
use core::iter;
|
||||
use core::ops::Add;
|
||||
|
||||
use derive_where::derive_where;
|
||||
use digest::core_api::{BlockSizeUser, CoreProxy};
|
||||
use digest::Output;
|
||||
use generic_array::typenum::{IsLess, Le, NonZero, U256};
|
||||
use generic_array::sequence::Concat;
|
||||
use generic_array::typenum::{IsLess, Le, NonZero, Sum, U2, U256};
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use zeroize::ZeroizeOnDrop;
|
||||
use voprf::{BlindedElement, EvaluationElement};
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
use crate::ciphersuite::{CipherSuite, OprfHash};
|
||||
#[cfg(test)]
|
||||
use crate::ciphersuite::KeHash;
|
||||
use crate::ciphersuite::{CipherSuite, OprfGroup};
|
||||
use crate::errors::ProtocolError;
|
||||
use crate::hash::{Hash, ProxyHash};
|
||||
use crate::key_exchange::group::KeGroup;
|
||||
use crate::key_exchange::group::Group;
|
||||
use crate::key_exchange::shared::{NonceLen, STR_CONTEXT};
|
||||
use crate::keypair::{PrivateKey, PublicKey};
|
||||
use crate::opaque::{Identifiers, MaskedResponse, MaskedResponseLen};
|
||||
use crate::serialization::{i2osp, SliceExt};
|
||||
|
||||
pub trait KeyExchange<D: Hash, G: KeGroup>
|
||||
/// The key exchange trait. This is only exposed so users can use it in generics
|
||||
/// and qualified bounds.
|
||||
#[allow(private_bounds)]
|
||||
pub trait KeyExchange: Sealed
|
||||
where
|
||||
D::Core: ProxyHash,
|
||||
<D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
<Self::Hash as CoreProxy>::Core: ProxyHash,
|
||||
<<Self::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<<Self::Hash as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
{
|
||||
type KE1State: Deserialize + Serialize + ZeroizeOnDrop + Clone;
|
||||
type KE2State: Deserialize + Serialize + ZeroizeOnDrop + Clone;
|
||||
type KE1Message: Deserialize + Serialize + ZeroizeOnDrop + Clone;
|
||||
type KE2Builder: ZeroizeOnDrop + Clone;
|
||||
type KE2BuilderData<'a>;
|
||||
type KE2BuilderInput;
|
||||
type KE2Message: Deserialize + Serialize + ZeroizeOnDrop + Clone;
|
||||
type KE3Message: Deserialize + Serialize + ZeroizeOnDrop + Clone;
|
||||
/// The group used for the key exchange.
|
||||
type Group: Group;
|
||||
/// The has used for the key exchange.
|
||||
type Hash: Hash;
|
||||
|
||||
fn generate_ke1<OprfCs: voprf::CipherSuite, R: RngCore + CryptoRng>(
|
||||
#[doc(hidden)]
|
||||
type KE1State: ZeroizeOnDrop + Clone;
|
||||
#[doc(hidden)]
|
||||
type KE2State<CS: CipherSuite>: ZeroizeOnDrop + Clone;
|
||||
#[doc(hidden)]
|
||||
type KE1Message: ZeroizeOnDrop + Clone;
|
||||
#[doc(hidden)]
|
||||
type KE2Builder<'a, CS: CipherSuite<KeyExchange = Self>>: ZeroizeOnDrop + Clone;
|
||||
#[doc(hidden)]
|
||||
type KE2BuilderData<'a, CS: 'static + CipherSuite>;
|
||||
#[doc(hidden)]
|
||||
type KE2BuilderInput<CS: CipherSuite>;
|
||||
#[doc(hidden)]
|
||||
type KE2Message: ZeroizeOnDrop + Clone;
|
||||
#[doc(hidden)]
|
||||
type KE3Message: ZeroizeOnDrop + Clone;
|
||||
|
||||
#[doc(hidden)]
|
||||
fn generate_ke1<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError>;
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn ke2_builder<'a, 'b, 'c, 'd, OprfCs: voprf::CipherSuite, R: RngCore + CryptoRng>(
|
||||
#[doc(hidden)]
|
||||
fn ke2_builder<'a, CS: CipherSuite<KeyExchange = Self>, R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
serialized_credential_request: impl Iterator<Item = &'a [u8]>,
|
||||
serialized_credential_response: impl Iterator<Item = &'b [u8]>,
|
||||
credential_request: CredentialRequestParts<CS>,
|
||||
ke1_message: Self::KE1Message,
|
||||
client_s_pk: PublicKey<G>,
|
||||
id_u: impl Iterator<Item = &'c [u8]>,
|
||||
id_s: impl Iterator<Item = &'d [u8]>,
|
||||
context: &[u8],
|
||||
) -> Result<Self::KE2Builder, ProtocolError>;
|
||||
credential_response: CredentialResponseParts<CS>,
|
||||
client_s_pk: PublicKey<Self::Group>,
|
||||
identifiers: SerializedIdentifiers<'a, Self::Group>,
|
||||
context: SerializedContext<'a>,
|
||||
) -> Result<Self::KE2Builder<'a, CS>, ProtocolError>;
|
||||
|
||||
fn ke2_builder_data(builder: &Self::KE2Builder) -> Self::KE2BuilderData<'_>;
|
||||
#[doc(hidden)]
|
||||
fn ke2_builder_data<'a, CS: CipherSuite<KeyExchange = Self>>(
|
||||
builder: &'a Self::KE2Builder<'_, CS>,
|
||||
) -> Self::KE2BuilderData<'a, CS>;
|
||||
|
||||
fn generate_ke2_input(
|
||||
builder: &Self::KE2Builder,
|
||||
server_s_sk: &PrivateKey<G>,
|
||||
) -> Self::KE2BuilderInput;
|
||||
#[doc(hidden)]
|
||||
fn generate_ke2_input<CS: CipherSuite<KeyExchange = Self>, R: CryptoRng + RngCore>(
|
||||
builder: &Self::KE2Builder<'_, CS>,
|
||||
rng: &mut R,
|
||||
server_s_sk: &PrivateKey<Self::Group>,
|
||||
) -> Self::KE2BuilderInput<CS>;
|
||||
|
||||
fn build_ke2(
|
||||
builder: Self::KE2Builder,
|
||||
input: Self::KE2BuilderInput,
|
||||
) -> Result<GenerateKe2Result<Self, D, G>, ProtocolError>;
|
||||
#[doc(hidden)]
|
||||
fn build_ke2<CS: CipherSuite<KeyExchange = Self>>(
|
||||
builder: Self::KE2Builder<'_, CS>,
|
||||
input: Self::KE2BuilderInput<CS>,
|
||||
) -> Result<GenerateKe2Result<CS>, ProtocolError>;
|
||||
|
||||
#[doc(hidden)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn generate_ke3<'a, 'b, 'c, 'd>(
|
||||
l2_component: impl Iterator<Item = &'a [u8]>,
|
||||
fn generate_ke3<CS: CipherSuite<KeyExchange = Self>, R: CryptoRng + RngCore>(
|
||||
rng: &mut R,
|
||||
credential_request: CredentialRequestParts<CS>,
|
||||
ke1_message: Self::KE1Message,
|
||||
credential_response: CredentialResponseParts<CS>,
|
||||
ke2_message: Self::KE2Message,
|
||||
ke1_state: &Self::KE1State,
|
||||
serialized_credential_request: impl Iterator<Item = &'b [u8]>,
|
||||
server_s_pk: PublicKey<G>,
|
||||
client_s_sk: PrivateKey<G>,
|
||||
id_u: impl Iterator<Item = &'c [u8]>,
|
||||
id_s: impl Iterator<Item = &'d [u8]>,
|
||||
context: &[u8],
|
||||
) -> Result<GenerateKe3Result<Self, D, G>, ProtocolError>;
|
||||
server_s_pk: PublicKey<Self::Group>,
|
||||
client_s_sk: PrivateKey<Self::Group>,
|
||||
identifiers: SerializedIdentifiers<'_, Self::Group>,
|
||||
context: SerializedContext<'_>,
|
||||
) -> Result<GenerateKe3Result<Self>, ProtocolError>;
|
||||
|
||||
fn finish_ke(
|
||||
#[doc(hidden)]
|
||||
fn finish_ke<CS: CipherSuite<KeyExchange = Self>>(
|
||||
ke3_message: Self::KE3Message,
|
||||
ke2_state: &Self::KE2State,
|
||||
) -> Result<Output<D>, ProtocolError>;
|
||||
ke2_state: &Self::KE2State<CS>,
|
||||
identifiers: Identifiers<'_>,
|
||||
context: SerializedContext<'_>,
|
||||
) -> Result<Output<Self::Hash>, ProtocolError>;
|
||||
}
|
||||
|
||||
pub(super) trait Sealed {}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound = "")
|
||||
)]
|
||||
#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize)]
|
||||
pub struct CredentialRequestParts<CS: CipherSuite>(
|
||||
GenericArray<u8, <OprfGroup<CS> as voprf::Group>::ElemLen>,
|
||||
);
|
||||
|
||||
impl<CS: CipherSuite> CredentialRequestParts<CS> {
|
||||
pub(crate) fn new(blinded_element: &BlindedElement<CS::OprfCs>) -> Self {
|
||||
Self(blinded_element.serialize())
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Clone + Iterator<Item = &[u8]> {
|
||||
iter::once(self.0.as_slice())
|
||||
}
|
||||
|
||||
pub fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
Ok(Self(input.take_array("blinded element")?))
|
||||
}
|
||||
}
|
||||
|
||||
pub type CredentialRequestPartsLen<CS: CipherSuite> = <OprfGroup<CS> as voprf::Group>::ElemLen;
|
||||
|
||||
impl<CS: CipherSuite> Serialize for CredentialRequestParts<CS> {
|
||||
type Len = CredentialRequestPartsLen<CS>;
|
||||
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len> {
|
||||
self.0.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound = "")
|
||||
)]
|
||||
#[derive_where(Clone, Debug, Eq, Hash, PartialEq, Zeroize)]
|
||||
pub struct CredentialResponseParts<CS: CipherSuite> {
|
||||
evaluation_element: GenericArray<u8, <OprfGroup<CS> as voprf::Group>::ElemLen>,
|
||||
masking_nonce: GenericArray<u8, NonceLen>,
|
||||
masked_response: MaskedResponse<CS>,
|
||||
}
|
||||
|
||||
impl<CS: CipherSuite> CredentialResponseParts<CS> {
|
||||
pub(crate) fn new(
|
||||
evaluation_element: &EvaluationElement<CS::OprfCs>,
|
||||
masking_nonce: GenericArray<u8, NonceLen>,
|
||||
masked_response: MaskedResponse<CS>,
|
||||
) -> Self {
|
||||
Self {
|
||||
evaluation_element: evaluation_element.serialize(),
|
||||
masking_nonce,
|
||||
masked_response,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Clone + Iterator<Item = &[u8]> {
|
||||
[self.evaluation_element.as_slice(), &self.masking_nonce]
|
||||
.into_iter()
|
||||
.chain(self.masked_response.iter())
|
||||
}
|
||||
|
||||
pub fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
Ok(Self {
|
||||
evaluation_element: input.take_array("evaluation element")?,
|
||||
masking_nonce: input.take_array("masking nonce")?,
|
||||
masked_response: MaskedResponse::deserialize_take(input)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub type CredentialResponsePartsLen<CS: CipherSuite> =
|
||||
Sum<Sum<<OprfGroup<CS> as voprf::Group>::ElemLen, NonceLen>, MaskedResponseLen<CS>>;
|
||||
|
||||
impl<CS: CipherSuite> Serialize for CredentialResponseParts<CS>
|
||||
where
|
||||
<OprfGroup<CS> as voprf::Group>::ElemLen: Add<NonceLen>,
|
||||
Sum<<OprfGroup<CS> as voprf::Group>::ElemLen, NonceLen>:
|
||||
ArrayLength<u8> + Add<MaskedResponseLen<CS>>,
|
||||
CredentialResponsePartsLen<CS>: ArrayLength<u8>,
|
||||
{
|
||||
type Len = CredentialResponsePartsLen<CS>;
|
||||
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len> {
|
||||
self.evaluation_element
|
||||
.clone()
|
||||
.concat(self.masking_nonce)
|
||||
.concat(self.masked_response.serialize())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound = "")
|
||||
)]
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq, Zeroize)]
|
||||
pub struct SerializedContext<'a> {
|
||||
length: GenericArray<u8, U2>,
|
||||
#[zeroize(skip)]
|
||||
context: &'a [u8],
|
||||
}
|
||||
|
||||
impl<'a> SerializedContext<'a> {
|
||||
pub(crate) fn from(context: Option<&'a [u8]>) -> Result<Self, ProtocolError> {
|
||||
let context = context.unwrap_or(&[]);
|
||||
|
||||
Ok(Self {
|
||||
length: i2osp::<U2>(context.len())?,
|
||||
context,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Clone + Iterator<Item = &[u8]> {
|
||||
iter::once(STR_CONTEXT).chain([self.length.as_slice(), self.context])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(deserialize = "'de: 'a", serialize = ""))
|
||||
)]
|
||||
#[derive_where(Clone, Debug, Eq, Hash, PartialEq, Zeroize)]
|
||||
pub struct SerializedIdentifiers<'a, G: Group> {
|
||||
pub client: SerializedIdentifier<'a, G>,
|
||||
pub server: SerializedIdentifier<'a, G>,
|
||||
}
|
||||
|
||||
/// Computes `I2OSP(len(input), max_bytes) || input` and helps hold output
|
||||
/// without allocation.
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(deserialize = "'de: 'a", serialize = ""))
|
||||
)]
|
||||
#[derive_where(Clone, Debug, Eq, Hash, PartialEq, Zeroize)]
|
||||
pub struct SerializedIdentifier<'a, G: Group> {
|
||||
length: GenericArray<u8, U2>,
|
||||
identifier: Identifier<'a, G>,
|
||||
}
|
||||
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound = "")
|
||||
)]
|
||||
#[derive_where(Clone, Debug, Eq, Hash, PartialEq, Zeroize)]
|
||||
enum Identifier<'a, G: Group> {
|
||||
Owned(GenericArray<u8, G::PkLen>),
|
||||
#[derive_where(skip_inner(Zeroize))]
|
||||
Borrowed(&'a [u8]),
|
||||
}
|
||||
|
||||
impl<'a, G: Group> SerializedIdentifiers<'a, G> {
|
||||
pub(crate) fn from_identifiers(
|
||||
ids: Identifiers<'a>,
|
||||
client_s_pk: GenericArray<u8, G::PkLen>,
|
||||
server_s_pk: GenericArray<u8, G::PkLen>,
|
||||
) -> Result<Self, ProtocolError> {
|
||||
let client = SerializedIdentifier::from_identifier(ids.client, client_s_pk)?;
|
||||
let server = SerializedIdentifier::from_identifier(ids.server, server_s_pk)?;
|
||||
|
||||
Ok(Self { client, server })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, G: Group> SerializedIdentifier<'a, G> {
|
||||
pub fn from_identifier(
|
||||
id: Option<&'a [u8]>,
|
||||
s_pk: GenericArray<u8, G::PkLen>,
|
||||
) -> Result<Self, ProtocolError> {
|
||||
if let Some(id) = id {
|
||||
Ok(SerializedIdentifier {
|
||||
length: i2osp::<U2>(id.len())?,
|
||||
identifier: Identifier::Borrowed(id),
|
||||
})
|
||||
} else {
|
||||
Ok(SerializedIdentifier {
|
||||
length: i2osp::<U2>(s_pk.len())?,
|
||||
identifier: Identifier::Owned(s_pk),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn iter(&self) -> impl Clone + Iterator<Item = &[u8]> {
|
||||
// Some magic to make it output the same type in all branches.
|
||||
[self.length.as_slice()]
|
||||
.into_iter()
|
||||
.chain(match &self.identifier {
|
||||
Identifier::Owned(bytes) => [bytes.as_slice()],
|
||||
Identifier::Borrowed(bytes) => [*bytes],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Deserialize: Sized {
|
||||
fn deserialize(input: &[u8]) -> Result<Self, ProtocolError>;
|
||||
fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError>;
|
||||
}
|
||||
|
||||
pub trait Serialize {
|
||||
@@ -92,34 +331,72 @@ pub trait Serialize {
|
||||
}
|
||||
|
||||
#[cfg(not(test))]
|
||||
pub type GenerateKe2Result<K, D, G> = (
|
||||
<K as KeyExchange<D, G>>::KE2State,
|
||||
<K as KeyExchange<D, G>>::KE2Message,
|
||||
pub type GenerateKe2Result<CS: CipherSuite> = (
|
||||
<CS::KeyExchange as KeyExchange>::KE2State<CS>,
|
||||
<CS::KeyExchange as KeyExchange>::KE2Message,
|
||||
);
|
||||
#[cfg(test)]
|
||||
pub type GenerateKe2Result<K, D, G> = (
|
||||
<K as KeyExchange<D, G>>::KE2State,
|
||||
<K as KeyExchange<D, G>>::KE2Message,
|
||||
Output<D>,
|
||||
Output<D>,
|
||||
pub type GenerateKe2Result<CS: CipherSuite> = (
|
||||
<CS::KeyExchange as KeyExchange>::KE2State<CS>,
|
||||
<CS::KeyExchange as KeyExchange>::KE2Message,
|
||||
Output<KeHash<CS>>,
|
||||
Output<KeHash<CS>>,
|
||||
);
|
||||
#[cfg(not(test))]
|
||||
pub type GenerateKe3Result<K, D, G> = (Output<D>, <K as KeyExchange<D, G>>::KE3Message);
|
||||
pub type GenerateKe3Result<K: KeyExchange> = (Output<K::Hash>, K::KE3Message);
|
||||
#[cfg(test)]
|
||||
pub type GenerateKe3Result<K, D, G> = (
|
||||
Output<D>,
|
||||
<K as KeyExchange<D, G>>::KE3Message,
|
||||
Output<D>,
|
||||
Output<D>,
|
||||
pub type GenerateKe3Result<K: KeyExchange> = (
|
||||
Output<K::Hash>,
|
||||
K::KE3Message,
|
||||
Output<K::Hash>,
|
||||
Output<K::Hash>,
|
||||
);
|
||||
|
||||
pub type Ke1StateLen<CS: CipherSuite> =
|
||||
<<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE1State as Serialize>::Len;
|
||||
<<CS::KeyExchange as KeyExchange>::KE1State as Serialize>::Len;
|
||||
pub type Ke1MessageLen<CS: CipherSuite> =
|
||||
<<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE1Message as Serialize>::Len;
|
||||
<<CS::KeyExchange as KeyExchange>::KE1Message as Serialize>::Len;
|
||||
pub type Ke2StateLen<CS: CipherSuite> =
|
||||
<<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE2State as Serialize>::Len;
|
||||
<<CS::KeyExchange as KeyExchange>::KE2State<CS> as Serialize>::Len;
|
||||
pub type Ke2MessageLen<CS: CipherSuite> =
|
||||
<<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE2Message as Serialize>::Len;
|
||||
<<CS::KeyExchange as KeyExchange>::KE2Message as Serialize>::Len;
|
||||
pub type Ke3MessageLen<CS: CipherSuite> =
|
||||
<<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE3Message as Serialize>::Len;
|
||||
<<CS::KeyExchange as KeyExchange>::KE3Message as Serialize>::Len;
|
||||
|
||||
//////////////////////////
|
||||
// Test Implementations //
|
||||
//===================== //
|
||||
//////////////////////////
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::serialization::AssertZeroized;
|
||||
|
||||
#[cfg(test)]
|
||||
impl<CS: CipherSuite> AssertZeroized for CredentialRequestParts<CS> {
|
||||
fn assert_zeroized(&self) {
|
||||
let Self(blinded_element) = self;
|
||||
|
||||
for byte in blinded_element.iter() {
|
||||
assert_eq!(byte, &0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl<CS: CipherSuite> AssertZeroized for CredentialResponseParts<CS> {
|
||||
fn assert_zeroized(&self) {
|
||||
let Self {
|
||||
evaluation_element,
|
||||
masking_nonce,
|
||||
masked_response,
|
||||
} = self;
|
||||
|
||||
for byte in evaluation_element
|
||||
.iter()
|
||||
.chain(masking_nonce)
|
||||
.chain(masked_response.iter().flatten())
|
||||
{
|
||||
assert_eq!(byte, &0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+267
-501
@@ -7,42 +7,34 @@
|
||||
// licenses.
|
||||
|
||||
//! An implementation of the Triple Diffie-Hellman key exchange protocol
|
||||
use core::convert::TryFrom;
|
||||
|
||||
use core::marker::PhantomData;
|
||||
use core::ops::Add;
|
||||
|
||||
use derive_where::derive_where;
|
||||
use digest::core_api::BlockSizeUser;
|
||||
use digest::{Digest, Output};
|
||||
use digest::{Digest, Mac, Output, OutputSizeUser};
|
||||
use generic_array::sequence::Concat;
|
||||
use generic_array::typenum::{IsLess, Le, NonZero, Sum, Unsigned, U1, U2, U256, U32};
|
||||
use generic_array::typenum::{IsLess, Le, NonZero, Sum, U256};
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
use hkdf::{Hkdf, HkdfExtract};
|
||||
use hmac::{Hmac, Mac};
|
||||
use hmac::Hmac;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use subtle::{ConstantTimeEq, CtOption};
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
use crate::errors::utils::{check_slice_size, check_slice_size_atleast};
|
||||
use crate::ciphersuite::{CipherSuite, KeGroup};
|
||||
use crate::errors::{InternalError, ProtocolError};
|
||||
use crate::hash::{Hash, OutputSize, ProxyHash};
|
||||
use crate::key_exchange::group::KeGroup;
|
||||
use crate::key_exchange::group::Group;
|
||||
use crate::key_exchange::shared::{self, NonceLen};
|
||||
pub use crate::key_exchange::shared::{DiffieHellman, Ke1Message, Ke1State};
|
||||
use crate::key_exchange::traits::{
|
||||
Deserialize, GenerateKe2Result, GenerateKe3Result, KeyExchange, Serialize,
|
||||
CredentialRequestParts, CredentialResponseParts, Deserialize, GenerateKe2Result,
|
||||
GenerateKe3Result, KeyExchange, Sealed, Serialize, SerializedContext, SerializedIdentifiers,
|
||||
};
|
||||
use crate::keypair::{KeyPair, PrivateKey, PublicKey};
|
||||
use crate::serialization::{Input, UpdateExt};
|
||||
|
||||
///////////////
|
||||
// Constants //
|
||||
// ========= //
|
||||
///////////////
|
||||
|
||||
pub(crate) type NonceLen = U32;
|
||||
static STR_CONTEXT: &[u8] = b"OPAQUEv1-";
|
||||
static STR_CLIENT_MAC: &[u8] = b"ClientMAC";
|
||||
static STR_HANDSHAKE_SECRET: &[u8] = b"HandshakeSecret";
|
||||
static STR_SERVER_MAC: &[u8] = b"ServerMAC";
|
||||
static STR_SESSION_KEY: &[u8] = b"SessionKey";
|
||||
static STR_OPAQUE: &[u8] = b"OPAQUE-";
|
||||
use crate::opaque::Identifiers;
|
||||
use crate::serialization::SliceExt;
|
||||
|
||||
////////////////////////////
|
||||
// High-level API Structs //
|
||||
@@ -55,36 +47,11 @@ static STR_OPAQUE: &[u8] = b"OPAQUE-";
|
||||
///
|
||||
/// [`ServerLoginBuilder::data()`](crate::ServerLoginBuilder::data()) will
|
||||
/// return the client's ephemeral public key.
|
||||
///
|
||||
/// [`ServerLoginBuilder::build()`](crate::ServerLoginBuilder::build()) expects
|
||||
/// a shared secret computed through Diffie-Hellman from the server's private
|
||||
/// key and the given public key.
|
||||
pub struct TripleDh;
|
||||
|
||||
/// The client state produced after the first key exchange message
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound = "")
|
||||
)]
|
||||
#[derive_where(Clone, ZeroizeOnDrop)]
|
||||
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; KG::Sk)]
|
||||
pub struct Ke1State<KG: KeGroup> {
|
||||
client_e_sk: PrivateKey<KG>,
|
||||
client_nonce: GenericArray<u8, NonceLen>,
|
||||
}
|
||||
|
||||
/// The first key exchange message
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound = "")
|
||||
)]
|
||||
#[derive_where(Clone, ZeroizeOnDrop)]
|
||||
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; KG::Pk)]
|
||||
pub struct Ke1Message<KG: KeGroup> {
|
||||
pub(crate) client_nonce: GenericArray<u8, NonceLen>,
|
||||
pub(crate) client_e_pk: PublicKey<KG>,
|
||||
}
|
||||
/// a shared secret computed through Diffie-Hellman from the servers private key
|
||||
/// and the given public key.
|
||||
pub struct TripleDh<G, H>(PhantomData<(G, H)>);
|
||||
|
||||
/// The server state produced after the second key exchange message
|
||||
#[cfg_attr(
|
||||
@@ -93,15 +60,9 @@ pub struct Ke1Message<KG: KeGroup> {
|
||||
serde(bound = "")
|
||||
)]
|
||||
#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, ZeroizeOnDrop)]
|
||||
pub struct Ke2State<D: Hash>
|
||||
where
|
||||
D::Core: ProxyHash,
|
||||
<D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
{
|
||||
km3: Output<D>,
|
||||
hashed_transcript: Output<D>,
|
||||
session_key: Output<D>,
|
||||
pub struct Ke2State<H: OutputSizeUser> {
|
||||
session_key: Output<H>,
|
||||
expected_mac: Output<H>,
|
||||
}
|
||||
|
||||
/// Builder for the second key exchange message
|
||||
@@ -109,24 +70,24 @@ where
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(
|
||||
deserialize = "D: serde::Deserialize<'de>, PublicKey<KG>: serde::Deserialize<'de>",
|
||||
serialize = "D: serde::Serialize, PublicKey<KG>: serde::Serialize",
|
||||
deserialize = "H: serde::Deserialize<'de>, PublicKey<G>: serde::Deserialize<'de>",
|
||||
serialize = "H: serde::Serialize, PublicKey<G>: serde::Serialize",
|
||||
))
|
||||
)]
|
||||
#[derive_where(Clone)]
|
||||
#[derive_where(Debug, Eq, Hash, PartialEq; D, PublicKey<KG>)]
|
||||
pub struct Ke2Builder<D: Hash, KG: KeGroup>
|
||||
#[derive_where(Debug, Eq, Hash, PartialEq; H, PublicKey<G>)]
|
||||
pub struct Ke2Builder<G: Group, H: Hash>
|
||||
where
|
||||
D::Core: ProxyHash,
|
||||
<D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
H::Core: ProxyHash,
|
||||
<H::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<H::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
{
|
||||
server_nonce: GenericArray<u8, NonceLen>,
|
||||
transcript_hasher: D,
|
||||
client_e_pk: PublicKey<KG>,
|
||||
server_e_pk: PublicKey<KG>,
|
||||
shared_secret_1: GenericArray<u8, KG::PkLen>,
|
||||
shared_secret_3: GenericArray<u8, KG::PkLen>,
|
||||
transcript_hasher: H,
|
||||
client_e_pk: PublicKey<G>,
|
||||
server_e_pk: PublicKey<G>,
|
||||
shared_secret_1: GenericArray<u8, G::PkLen>,
|
||||
shared_secret_3: GenericArray<u8, G::PkLen>,
|
||||
}
|
||||
|
||||
/// The second key exchange message
|
||||
@@ -136,16 +97,16 @@ where
|
||||
serde(bound = "")
|
||||
)]
|
||||
#[derive_where(Clone, ZeroizeOnDrop)]
|
||||
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; KG::Pk)]
|
||||
pub struct Ke2Message<D: Hash, KG: KeGroup>
|
||||
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; G::Pk)]
|
||||
pub struct Ke2Message<G: Group, H: Hash>
|
||||
where
|
||||
D::Core: ProxyHash,
|
||||
<D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
H::Core: ProxyHash,
|
||||
<H::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<H::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
{
|
||||
server_nonce: GenericArray<u8, NonceLen>,
|
||||
server_e_pk: PublicKey<KG>,
|
||||
mac: Output<D>,
|
||||
server_e_pk: PublicKey<G>,
|
||||
mac: Output<H>,
|
||||
}
|
||||
|
||||
/// The third key exchange message
|
||||
@@ -155,19 +116,13 @@ where
|
||||
serde(bound = "")
|
||||
)]
|
||||
#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, ZeroizeOnDrop)]
|
||||
pub struct Ke3Message<D: Hash>
|
||||
pub struct Ke3Message<H: Hash>
|
||||
where
|
||||
D::Core: ProxyHash,
|
||||
<D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
H::Core: ProxyHash,
|
||||
<H::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<H::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
{
|
||||
mac: Output<D>,
|
||||
}
|
||||
|
||||
/// Trait required by [`KeGroup::Sk`] to be compatible with [`TripleDh`].
|
||||
pub trait DiffieHellman<KG: KeGroup> {
|
||||
/// Diffie-Hellman key exchange.
|
||||
fn diffie_hellman(self, pk: KG::Pk) -> GenericArray<u8, KG::PkLen>;
|
||||
mac: Output<H>,
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
@@ -175,78 +130,55 @@ pub trait DiffieHellman<KG: KeGroup> {
|
||||
// ========================== //
|
||||
////////////////////////////////
|
||||
|
||||
impl<D: Hash, KG: KeGroup + 'static> KeyExchange<D, KG> for TripleDh
|
||||
impl<G: Group + 'static, H: Hash> KeyExchange for TripleDh<G, H>
|
||||
where
|
||||
KG::Sk: DiffieHellman<KG>,
|
||||
D::Core: ProxyHash,
|
||||
<D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
// Ke1State: KeSk + Nonce
|
||||
KG::SkLen: Add<NonceLen>,
|
||||
Sum<KG::SkLen, NonceLen>: ArrayLength<u8>,
|
||||
// Ke1Message: Nonce + KePk
|
||||
NonceLen: Add<KG::PkLen>,
|
||||
Sum<NonceLen, KG::PkLen>: ArrayLength<u8>,
|
||||
// Ke2State: (Hash + Hash) + Hash
|
||||
OutputSize<D>: Add<OutputSize<D>>,
|
||||
Sum<OutputSize<D>, OutputSize<D>>: ArrayLength<u8> + Add<OutputSize<D>>,
|
||||
Sum<Sum<OutputSize<D>, OutputSize<D>>, OutputSize<D>>: ArrayLength<u8>,
|
||||
// Ke2Message: (Nonce + KePk) + Hash
|
||||
NonceLen: Add<KG::PkLen>,
|
||||
Sum<NonceLen, KG::PkLen>: ArrayLength<u8> + Add<OutputSize<D>>,
|
||||
Sum<Sum<NonceLen, KG::PkLen>, OutputSize<D>>: ArrayLength<u8>,
|
||||
G::Sk: DiffieHellman<G>,
|
||||
H::Core: ProxyHash,
|
||||
<H::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<H::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
{
|
||||
type KE1State = Ke1State<KG>;
|
||||
type KE2State = Ke2State<D>;
|
||||
type KE1Message = Ke1Message<KG>;
|
||||
type KE2Builder = Ke2Builder<D, KG>;
|
||||
type KE2BuilderData<'a> = &'a PublicKey<KG>;
|
||||
type KE2BuilderInput = GenericArray<u8, KG::PkLen>;
|
||||
type KE2Message = Ke2Message<D, KG>;
|
||||
type KE3Message = Ke3Message<D>;
|
||||
type Group = G;
|
||||
type Hash = H;
|
||||
|
||||
fn generate_ke1<OprfCs: voprf::CipherSuite, R: RngCore + CryptoRng>(
|
||||
type KE1State = Ke1State<G>;
|
||||
type KE2State<CS: CipherSuite> = Ke2State<H>;
|
||||
type KE1Message = Ke1Message<G>;
|
||||
type KE2Builder<'a, CS: CipherSuite<KeyExchange = Self>> = Ke2Builder<G, H>;
|
||||
type KE2BuilderData<'a, CS: 'static + CipherSuite> = &'a PublicKey<G>;
|
||||
type KE2BuilderInput<CS: CipherSuite> = GenericArray<u8, G::PkLen>;
|
||||
type KE2Message = Ke2Message<G, H>;
|
||||
type KE3Message = Ke3Message<H>;
|
||||
|
||||
fn generate_ke1<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError> {
|
||||
let client_e_kp = KeyPair::<KG>::generate_random::<OprfCs, _>(rng);
|
||||
let client_nonce = generate_nonce::<R>(rng);
|
||||
|
||||
let ke1_message = Ke1Message {
|
||||
client_nonce,
|
||||
client_e_pk: client_e_kp.public().clone(),
|
||||
};
|
||||
|
||||
Ok((
|
||||
Ke1State {
|
||||
client_e_sk: client_e_kp.private().clone(),
|
||||
client_nonce,
|
||||
},
|
||||
ke1_message,
|
||||
))
|
||||
shared::generate_ke1(rng)
|
||||
}
|
||||
|
||||
fn ke2_builder<'a, 'b, 'c, 'd, OprfCs: voprf::CipherSuite, R: RngCore + CryptoRng>(
|
||||
fn ke2_builder<'a, CS: CipherSuite<KeyExchange = Self>, R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
serialized_credential_request: impl Iterator<Item = &'a [u8]>,
|
||||
serialized_credential_response: impl Iterator<Item = &'b [u8]>,
|
||||
credential_request: CredentialRequestParts<CS>,
|
||||
ke1_message: Self::KE1Message,
|
||||
client_s_pk: PublicKey<KG>,
|
||||
id_u: impl Iterator<Item = &'c [u8]>,
|
||||
id_s: impl Iterator<Item = &'d [u8]>,
|
||||
context: &[u8],
|
||||
) -> Result<Self::KE2Builder, ProtocolError> {
|
||||
let server_e = KeyPair::<KG>::generate_random::<OprfCs, _>(rng);
|
||||
let server_nonce = generate_nonce::<R>(rng);
|
||||
credential_response: CredentialResponseParts<CS>,
|
||||
client_s_pk: PublicKey<G>,
|
||||
identifiers: SerializedIdentifiers<'_, KeGroup<CS>>,
|
||||
context: SerializedContext<'a>,
|
||||
) -> Result<Self::KE2Builder<'a, CS>, ProtocolError> {
|
||||
let server_e = KeyPair::<G>::derive_random(rng);
|
||||
let server_nonce = shared::generate_nonce::<R>(rng);
|
||||
|
||||
let transcript_hasher = D::new()
|
||||
.chain(STR_CONTEXT)
|
||||
.chain_iter(Input::<U2>::from(context)?.iter())
|
||||
.chain_iter(id_u.into_iter())
|
||||
.chain_iter(serialized_credential_request)
|
||||
.chain_iter(id_s.into_iter())
|
||||
.chain_iter(serialized_credential_response)
|
||||
.chain(server_nonce)
|
||||
.chain(server_e.public().serialize());
|
||||
let ke1_message_iter = ke1_message.to_iter();
|
||||
let server_e_pk = server_e.public().serialize();
|
||||
|
||||
let transcript_hasher = shared::transcript(
|
||||
&context,
|
||||
&identifiers,
|
||||
&credential_request,
|
||||
&ke1_message_iter,
|
||||
&credential_response,
|
||||
server_nonce,
|
||||
&server_e_pk,
|
||||
);
|
||||
|
||||
let shared_secret_1 = server_e
|
||||
.private()
|
||||
@@ -263,40 +195,55 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
fn ke2_builder_data(builder: &Self::KE2Builder) -> Self::KE2BuilderData<'_> {
|
||||
fn ke2_builder_data<'a, CS: 'static + CipherSuite<KeyExchange = Self>>(
|
||||
builder: &'a Self::KE2Builder<'_, CS>,
|
||||
) -> Self::KE2BuilderData<'a, CS> {
|
||||
&builder.client_e_pk
|
||||
}
|
||||
|
||||
fn generate_ke2_input(
|
||||
builder: &Self::KE2Builder,
|
||||
server_s_sk: &PrivateKey<KG>,
|
||||
) -> Self::KE2BuilderInput {
|
||||
fn generate_ke2_input<CS: CipherSuite<KeyExchange = Self>, R: CryptoRng + RngCore>(
|
||||
builder: &Self::KE2Builder<'_, CS>,
|
||||
_: &mut R,
|
||||
server_s_sk: &PrivateKey<G>,
|
||||
) -> Self::KE2BuilderInput<CS> {
|
||||
server_s_sk.ke_diffie_hellman(&builder.client_e_pk)
|
||||
}
|
||||
|
||||
fn build_ke2(
|
||||
mut builder: Self::KE2Builder,
|
||||
shared_secret_2: Self::KE2BuilderInput,
|
||||
) -> Result<GenerateKe2Result<Self, D, KG>, ProtocolError> {
|
||||
let result = derive_3dh_keys::<D, KG>(
|
||||
builder.shared_secret_1.clone(),
|
||||
shared_secret_2,
|
||||
builder.shared_secret_3.clone(),
|
||||
fn build_ke2<CS: CipherSuite<KeyExchange = Self>>(
|
||||
mut builder: Self::KE2Builder<'_, CS>,
|
||||
shared_secret_2: Self::KE2BuilderInput<CS>,
|
||||
) -> Result<GenerateKe2Result<CS>, ProtocolError> {
|
||||
let derived_keys = shared::derive_keys::<H>(
|
||||
[
|
||||
builder.shared_secret_1.as_slice(),
|
||||
&shared_secret_2,
|
||||
&builder.shared_secret_3,
|
||||
]
|
||||
.into_iter(),
|
||||
&builder.transcript_hasher.clone().finalize(),
|
||||
)?;
|
||||
|
||||
let mut mac_hasher =
|
||||
Hmac::<D>::new_from_slice(&result.1).map_err(|_| InternalError::HmacError)?;
|
||||
mac_hasher.update(&builder.transcript_hasher.clone().finalize());
|
||||
Hmac::<H>::new_from_slice(&derived_keys.km2).map_err(|_| InternalError::HmacError)?;
|
||||
Mac::update(
|
||||
&mut mac_hasher,
|
||||
&builder.transcript_hasher.clone().finalize(),
|
||||
);
|
||||
let mac = mac_hasher.finalize().into_bytes();
|
||||
|
||||
Digest::update(&mut builder.transcript_hasher, &mac);
|
||||
builder.transcript_hasher.update(&mac);
|
||||
let mut mac_hasher =
|
||||
Hmac::<H>::new_from_slice(&derived_keys.km3).map_err(|_| InternalError::HmacError)?;
|
||||
Mac::update(
|
||||
&mut mac_hasher,
|
||||
&builder.transcript_hasher.clone().finalize(),
|
||||
);
|
||||
let expected_mac = mac_hasher.finalize().into_bytes();
|
||||
|
||||
Ok((
|
||||
Ke2State {
|
||||
km3: result.2,
|
||||
hashed_transcript: builder.transcript_hasher.clone().finalize(),
|
||||
session_key: result.0,
|
||||
session_key: derived_keys.session_key,
|
||||
expected_mac,
|
||||
},
|
||||
Ke2Message {
|
||||
server_nonce: builder.server_nonce,
|
||||
@@ -304,383 +251,195 @@ where
|
||||
mac,
|
||||
},
|
||||
#[cfg(test)]
|
||||
result.3,
|
||||
derived_keys.handshake_secret,
|
||||
#[cfg(test)]
|
||||
result.1,
|
||||
derived_keys.km2,
|
||||
))
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn generate_ke3<'a, 'b, 'c, 'd>(
|
||||
l2_component: impl Iterator<Item = &'a [u8]>,
|
||||
fn generate_ke3<CS: CipherSuite<KeyExchange = Self>, R: CryptoRng + RngCore>(
|
||||
_: &mut R,
|
||||
credential_request: CredentialRequestParts<CS>,
|
||||
ke1_message: Self::KE1Message,
|
||||
credential_response: CredentialResponseParts<CS>,
|
||||
ke2_message: Self::KE2Message,
|
||||
ke1_state: &Self::KE1State,
|
||||
serialized_credential_request: impl Iterator<Item = &'b [u8]>,
|
||||
server_s_pk: PublicKey<KG>,
|
||||
client_s_sk: PrivateKey<KG>,
|
||||
id_u: impl Iterator<Item = &'c [u8]>,
|
||||
id_s: impl Iterator<Item = &'d [u8]>,
|
||||
context: &[u8],
|
||||
) -> Result<GenerateKe3Result<Self, D, KG>, ProtocolError> {
|
||||
let mut transcript_hasher = D::new()
|
||||
.chain(STR_CONTEXT)
|
||||
.chain_iter(Input::<U2>::from(context)?.iter())
|
||||
.chain_iter(id_u)
|
||||
.chain_iter(serialized_credential_request)
|
||||
.chain_iter(id_s)
|
||||
.chain_iter(l2_component)
|
||||
.chain(ke2_message.to_bytes_without_mac());
|
||||
server_s_pk: PublicKey<G>,
|
||||
client_s_sk: PrivateKey<G>,
|
||||
identifiers: SerializedIdentifiers<'_, KeGroup<CS>>,
|
||||
context: SerializedContext<'_>,
|
||||
) -> Result<GenerateKe3Result<Self>, ProtocolError> {
|
||||
let mut transcript_hasher = shared::transcript(
|
||||
&context,
|
||||
&identifiers,
|
||||
&credential_request,
|
||||
&ke1_message.to_iter(),
|
||||
&credential_response,
|
||||
ke2_message.server_nonce,
|
||||
&ke2_message.server_e_pk.serialize(),
|
||||
);
|
||||
|
||||
let result = derive_3dh_keys::<D, KG>(
|
||||
ke1_state
|
||||
.client_e_sk
|
||||
.ke_diffie_hellman(&ke2_message.server_e_pk),
|
||||
ke1_state.client_e_sk.ke_diffie_hellman(&server_s_pk),
|
||||
client_s_sk.ke_diffie_hellman(&ke2_message.server_e_pk),
|
||||
let shared_secret_1 = ke1_state
|
||||
.client_e_sk
|
||||
.ke_diffie_hellman(&ke2_message.server_e_pk);
|
||||
let shared_secret_2 = ke1_state.client_e_sk.ke_diffie_hellman(&server_s_pk);
|
||||
let shared_secret_3 = client_s_sk.ke_diffie_hellman(&ke2_message.server_e_pk);
|
||||
|
||||
let derived_keys = shared::derive_keys::<H>(
|
||||
[
|
||||
shared_secret_1.as_slice(),
|
||||
&shared_secret_2,
|
||||
&shared_secret_3,
|
||||
]
|
||||
.into_iter(),
|
||||
&transcript_hasher.clone().finalize(),
|
||||
)?;
|
||||
|
||||
let mut server_mac =
|
||||
Hmac::<D>::new_from_slice(&result.1).map_err(|_| InternalError::HmacError)?;
|
||||
server_mac.update(&transcript_hasher.clone().finalize());
|
||||
Hmac::<H>::new_from_slice(&derived_keys.km2).map_err(|_| InternalError::HmacError)?;
|
||||
Mac::update(&mut server_mac, &transcript_hasher.clone().finalize());
|
||||
|
||||
server_mac
|
||||
.verify(&ke2_message.mac)
|
||||
.map_err(|_| ProtocolError::InvalidLoginError)?;
|
||||
|
||||
Digest::update(&mut transcript_hasher, &ke2_message.mac);
|
||||
transcript_hasher.update(&ke2_message.mac);
|
||||
|
||||
let mut client_mac =
|
||||
Hmac::<D>::new_from_slice(&result.2).map_err(|_| InternalError::HmacError)?;
|
||||
client_mac.update(&transcript_hasher.finalize());
|
||||
Hmac::<H>::new_from_slice(&derived_keys.km3).map_err(|_| InternalError::HmacError)?;
|
||||
Mac::update(&mut client_mac, &transcript_hasher.finalize());
|
||||
|
||||
Ok((
|
||||
result.0,
|
||||
derived_keys.session_key,
|
||||
Ke3Message {
|
||||
mac: client_mac.finalize().into_bytes(),
|
||||
},
|
||||
#[cfg(test)]
|
||||
result.3,
|
||||
derived_keys.handshake_secret,
|
||||
#[cfg(test)]
|
||||
result.2,
|
||||
derived_keys.km3,
|
||||
))
|
||||
}
|
||||
|
||||
fn finish_ke(
|
||||
fn finish_ke<CS: CipherSuite>(
|
||||
ke3_message: Self::KE3Message,
|
||||
ke2_state: &Self::KE2State,
|
||||
) -> Result<Output<D>, ProtocolError> {
|
||||
let mut client_mac =
|
||||
Hmac::<D>::new_from_slice(&ke2_state.km3).map_err(|_| InternalError::HmacError)?;
|
||||
client_mac.update(&ke2_state.hashed_transcript);
|
||||
|
||||
client_mac
|
||||
.verify(&ke3_message.mac)
|
||||
.map_err(|_| ProtocolError::InvalidLoginError)?;
|
||||
|
||||
Ok(ke2_state.session_key.clone())
|
||||
ke2_state: &Self::KE2State<CS>,
|
||||
_: Identifiers<'_>,
|
||||
_: SerializedContext<'_>,
|
||||
) -> Result<Output<H>, ProtocolError> {
|
||||
CtOption::new(
|
||||
ke2_state.session_key.clone(),
|
||||
ke2_state.expected_mac.ct_eq(&ke3_message.mac),
|
||||
)
|
||||
.into_option()
|
||||
.ok_or(ProtocolError::InvalidLoginError)
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////
|
||||
// Convenience Structs //
|
||||
//==================== //
|
||||
/////////////////////////
|
||||
|
||||
// Consists of a session key, followed by two mac keys: (session_key, km2, km3)
|
||||
#[cfg(not(test))]
|
||||
type TripleDhDerivationResult<D> = (Output<D>, Output<D>, Output<D>);
|
||||
#[cfg(test)]
|
||||
type TripleDhDerivationResult<D> = (Output<D>, Output<D>, Output<D>, Output<D>);
|
||||
impl<G: Group + 'static, H: Hash> Sealed for TripleDh<G, H>
|
||||
where
|
||||
H::Core: ProxyHash,
|
||||
<H::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<H::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
{
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// Helper functions and Trait Implementations //
|
||||
// Trait Implementations //
|
||||
// ========================================== //
|
||||
////////////////////////////////////////////////
|
||||
|
||||
// Helper functions
|
||||
|
||||
// Internal function which takes the public and private components of the client
|
||||
// and server keypairs, along with some auxiliary metadata, to produce the
|
||||
// session key and two MAC keys
|
||||
fn derive_3dh_keys<D: Hash, KG: KeGroup>(
|
||||
shared_secret_1: GenericArray<u8, KG::PkLen>,
|
||||
shared_secret_2: GenericArray<u8, KG::PkLen>,
|
||||
shared_secret_3: GenericArray<u8, KG::PkLen>,
|
||||
hashed_derivation_transcript: &[u8],
|
||||
) -> Result<TripleDhDerivationResult<D>, ProtocolError>
|
||||
impl<H: Hash> Deserialize for Ke2State<H>
|
||||
where
|
||||
D::Core: ProxyHash,
|
||||
<D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
H::Core: ProxyHash,
|
||||
<H::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<H::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
{
|
||||
let mut hkdf = HkdfExtract::<D>::new(None);
|
||||
|
||||
hkdf.input_ikm(&shared_secret_1);
|
||||
hkdf.input_ikm(&shared_secret_2);
|
||||
hkdf.input_ikm(&shared_secret_3);
|
||||
|
||||
let (_, extracted_ikm) = hkdf.finalize();
|
||||
let handshake_secret = derive_secrets::<D>(
|
||||
&extracted_ikm,
|
||||
STR_HANDSHAKE_SECRET,
|
||||
hashed_derivation_transcript,
|
||||
)?;
|
||||
let session_key = derive_secrets::<D>(
|
||||
&extracted_ikm,
|
||||
STR_SESSION_KEY,
|
||||
hashed_derivation_transcript,
|
||||
)?;
|
||||
|
||||
let km2 = hkdf_expand_label::<D>(&handshake_secret, STR_SERVER_MAC, b"")?;
|
||||
let km3 = hkdf_expand_label::<D>(&handshake_secret, STR_CLIENT_MAC, b"")?;
|
||||
|
||||
Ok((
|
||||
session_key,
|
||||
km2,
|
||||
km3,
|
||||
#[cfg(test)]
|
||||
handshake_secret,
|
||||
))
|
||||
}
|
||||
|
||||
fn hkdf_expand_label<D: Hash>(
|
||||
secret: &[u8],
|
||||
label: &[u8],
|
||||
context: &[u8],
|
||||
) -> Result<Output<D>, ProtocolError>
|
||||
where
|
||||
D::Core: ProxyHash,
|
||||
<D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
{
|
||||
let h = Hkdf::<D>::from_prk(secret).map_err(|_| InternalError::HkdfError)?;
|
||||
hkdf_expand_label_extracted(&h, label, context)
|
||||
}
|
||||
|
||||
fn hkdf_expand_label_extracted<D: Hash>(
|
||||
hkdf: &Hkdf<D>,
|
||||
label: &[u8],
|
||||
context: &[u8],
|
||||
) -> Result<Output<D>, ProtocolError>
|
||||
where
|
||||
D::Core: ProxyHash,
|
||||
<D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
{
|
||||
let mut okm = GenericArray::default();
|
||||
|
||||
let length_u16: u16 =
|
||||
u16::try_from(OutputSize::<D>::USIZE).map_err(|_| ProtocolError::SerializationError)?;
|
||||
let label = Input::<U1>::from_label(STR_OPAQUE, label)?;
|
||||
let label = label.to_array_3();
|
||||
let context = Input::<U1>::from(context)?;
|
||||
let context = context.to_array_2();
|
||||
|
||||
let hkdf_label = [
|
||||
&length_u16.to_be_bytes(),
|
||||
label[0],
|
||||
label[1],
|
||||
label[2],
|
||||
context[0],
|
||||
context[1],
|
||||
];
|
||||
|
||||
hkdf.expand_multi_info(&hkdf_label, &mut okm)
|
||||
.map_err(|_| InternalError::HkdfError)?;
|
||||
Ok(okm)
|
||||
}
|
||||
|
||||
fn derive_secrets<D: Hash>(
|
||||
hkdf: &Hkdf<D>,
|
||||
label: &[u8],
|
||||
hashed_derivation_transcript: &[u8],
|
||||
) -> Result<Output<D>, ProtocolError>
|
||||
where
|
||||
D::Core: ProxyHash,
|
||||
<D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
{
|
||||
hkdf_expand_label_extracted::<D>(hkdf, label, hashed_derivation_transcript)
|
||||
}
|
||||
|
||||
// Generate a random nonce up to NonceLen::USIZE bytes.
|
||||
fn generate_nonce<R: RngCore + CryptoRng>(rng: &mut R) -> GenericArray<u8, NonceLen> {
|
||||
let mut nonce_bytes = GenericArray::default();
|
||||
rng.fill_bytes(&mut nonce_bytes);
|
||||
nonce_bytes
|
||||
}
|
||||
|
||||
// Serialization and deserialization implementations
|
||||
|
||||
impl<KG: KeGroup> Deserialize for Ke1State<KG> {
|
||||
fn deserialize(bytes: &[u8]) -> Result<Self, ProtocolError> {
|
||||
let key_len = KG::SkLen::USIZE;
|
||||
|
||||
let nonce_len = NonceLen::USIZE;
|
||||
let checked_bytes = check_slice_size_atleast(bytes, key_len + nonce_len, "ke1_state")?;
|
||||
|
||||
fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
Ok(Self {
|
||||
client_e_sk: PrivateKey::deserialize(&checked_bytes[..key_len])?,
|
||||
client_nonce: GenericArray::clone_from_slice(
|
||||
&checked_bytes[key_len..key_len + nonce_len],
|
||||
),
|
||||
session_key: input.take_array("session key")?,
|
||||
expected_mac: input.take_array("expected mac")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<KG: KeGroup> Serialize for Ke1State<KG>
|
||||
impl<H: Hash> Serialize for Ke2State<H>
|
||||
where
|
||||
// Ke1State: KeSk + Nonce
|
||||
KG::SkLen: Add<NonceLen>,
|
||||
Sum<KG::SkLen, NonceLen>: ArrayLength<u8>,
|
||||
H::Core: ProxyHash,
|
||||
<H::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<H::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
// Ke2State: Hash + Hash
|
||||
OutputSize<H>: Add<OutputSize<H>>,
|
||||
Sum<OutputSize<H>, OutputSize<H>>: ArrayLength<u8>,
|
||||
{
|
||||
type Len = Sum<KG::SkLen, NonceLen>;
|
||||
type Len = Sum<OutputSize<H>, OutputSize<H>>;
|
||||
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len> {
|
||||
self.client_e_sk.serialize().concat(self.client_nonce)
|
||||
self.session_key.clone().concat(self.expected_mac.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<KG: KeGroup> Deserialize for Ke1Message<KG> {
|
||||
fn deserialize(ke1_message_bytes: &[u8]) -> Result<Self, ProtocolError> {
|
||||
let nonce_len = NonceLen::USIZE;
|
||||
let checked_nonce = check_slice_size(
|
||||
ke1_message_bytes,
|
||||
nonce_len + <KG as KeGroup>::PkLen::USIZE,
|
||||
"ke1_message nonce",
|
||||
)?;
|
||||
|
||||
Ok(Self {
|
||||
client_nonce: GenericArray::clone_from_slice(&checked_nonce[..nonce_len]),
|
||||
client_e_pk: PublicKey::deserialize(&checked_nonce[nonce_len..])?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<KG: KeGroup> Serialize for Ke1Message<KG>
|
||||
impl<G: Group, H: Hash> Drop for Ke2Builder<G, H>
|
||||
where
|
||||
// Ke1Message: Nonce + KePk
|
||||
NonceLen: Add<KG::PkLen>,
|
||||
Sum<NonceLen, KG::PkLen>: ArrayLength<u8>,
|
||||
{
|
||||
type Len = Sum<NonceLen, KG::PkLen>;
|
||||
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len> {
|
||||
self.client_nonce.concat(self.client_e_pk.serialize())
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Hash> Deserialize for Ke2State<D>
|
||||
where
|
||||
D::Core: ProxyHash,
|
||||
<D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
{
|
||||
fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
|
||||
let hash_len = OutputSize::<D>::USIZE;
|
||||
let checked_bytes = check_slice_size(input, 3 * hash_len, "ke2_state")?;
|
||||
|
||||
Ok(Self {
|
||||
km3: GenericArray::clone_from_slice(&checked_bytes[..hash_len]),
|
||||
hashed_transcript: GenericArray::clone_from_slice(
|
||||
&checked_bytes[hash_len..2 * hash_len],
|
||||
),
|
||||
session_key: GenericArray::clone_from_slice(&checked_bytes[2 * hash_len..3 * hash_len]),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Hash> Serialize for Ke2State<D>
|
||||
where
|
||||
D::Core: ProxyHash,
|
||||
<D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
// Ke2State: (Hash + Hash) + Hash
|
||||
OutputSize<D>: Add<OutputSize<D>>,
|
||||
Sum<OutputSize<D>, OutputSize<D>>: ArrayLength<u8> + Add<OutputSize<D>>,
|
||||
Sum<Sum<OutputSize<D>, OutputSize<D>>, OutputSize<D>>: ArrayLength<u8>,
|
||||
{
|
||||
type Len = Sum<Sum<OutputSize<D>, OutputSize<D>>, OutputSize<D>>;
|
||||
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len> {
|
||||
self.km3
|
||||
.clone()
|
||||
.concat(self.hashed_transcript.clone())
|
||||
.concat(self.session_key.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<KG: KeGroup, D: Hash> Drop for Ke2Builder<D, KG>
|
||||
where
|
||||
D::Core: ProxyHash,
|
||||
<D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
H::Core: ProxyHash,
|
||||
<H::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<H::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
struct AssertZeroizeOnDrop<'a, T: ZeroizeOnDrop>(#[allow(unused)] &'a T);
|
||||
|
||||
self.server_nonce.zeroize();
|
||||
self.transcript_hasher.reset();
|
||||
let _ = AssertZeroizeOnDrop(&self.client_e_pk);
|
||||
let _ = AssertZeroizeOnDrop(&self.server_e_pk);
|
||||
self.shared_secret_1.zeroize();
|
||||
self.shared_secret_3.zeroize();
|
||||
let Self {
|
||||
server_nonce,
|
||||
transcript_hasher,
|
||||
client_e_pk,
|
||||
server_e_pk,
|
||||
shared_secret_1,
|
||||
shared_secret_3,
|
||||
} = self;
|
||||
|
||||
server_nonce.zeroize();
|
||||
transcript_hasher.reset();
|
||||
let _ = AssertZeroizeOnDrop(client_e_pk);
|
||||
let _ = AssertZeroizeOnDrop(server_e_pk);
|
||||
shared_secret_1.zeroize();
|
||||
shared_secret_3.zeroize();
|
||||
}
|
||||
}
|
||||
|
||||
impl<KG: KeGroup, D: Hash> ZeroizeOnDrop for Ke2Builder<D, KG>
|
||||
impl<G: Group, H: Hash> ZeroizeOnDrop for Ke2Builder<G, H>
|
||||
where
|
||||
D::Core: ProxyHash,
|
||||
<D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
H::Core: ProxyHash,
|
||||
<H::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<H::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
{
|
||||
}
|
||||
|
||||
impl<KG: KeGroup, D: Hash> Deserialize for Ke2Message<D, KG>
|
||||
impl<G: Group, H: Hash> Deserialize for Ke2Message<G, H>
|
||||
where
|
||||
D::Core: ProxyHash,
|
||||
<D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
H::Core: ProxyHash,
|
||||
<H::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<H::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
{
|
||||
fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
|
||||
let key_len = <KG as KeGroup>::PkLen::USIZE;
|
||||
let nonce_len = NonceLen::USIZE;
|
||||
let checked_nonce = check_slice_size_atleast(input, nonce_len, "ke2_message nonce")?;
|
||||
|
||||
let unchecked_server_e_pk = check_slice_size_atleast(
|
||||
&checked_nonce[nonce_len..],
|
||||
key_len,
|
||||
"ke2_message server_e_pk",
|
||||
)?;
|
||||
let checked_mac = check_slice_size(
|
||||
&unchecked_server_e_pk[key_len..],
|
||||
OutputSize::<D>::USIZE,
|
||||
"ke1_message mac",
|
||||
)?;
|
||||
|
||||
// Check the public key bytes
|
||||
let server_e_pk = PublicKey::deserialize(&unchecked_server_e_pk[..key_len])?;
|
||||
|
||||
fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
Ok(Self {
|
||||
server_nonce: GenericArray::clone_from_slice(&checked_nonce[..nonce_len]),
|
||||
server_e_pk,
|
||||
mac: GenericArray::clone_from_slice(checked_mac),
|
||||
server_nonce: input.take_array("server nonce")?,
|
||||
server_e_pk: PublicKey::deserialize_take(input)?,
|
||||
mac: input.take_array("mac")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Hash, KG: KeGroup> Serialize for Ke2Message<D, KG>
|
||||
impl<H: Hash, G: Group> Serialize for Ke2Message<G, H>
|
||||
where
|
||||
D::Core: ProxyHash,
|
||||
<D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
H::Core: ProxyHash,
|
||||
<H::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<H::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
// Ke2Message: (Nonce + KePk) + Hash
|
||||
NonceLen: Add<KG::PkLen>,
|
||||
Sum<NonceLen, KG::PkLen>: ArrayLength<u8> + Add<OutputSize<D>>,
|
||||
Sum<Sum<NonceLen, KG::PkLen>, OutputSize<D>>: ArrayLength<u8>,
|
||||
NonceLen: Add<G::PkLen>,
|
||||
Sum<NonceLen, G::PkLen>: ArrayLength<u8> + Add<OutputSize<H>>,
|
||||
Sum<Sum<NonceLen, G::PkLen>, OutputSize<H>>: ArrayLength<u8>,
|
||||
{
|
||||
type Len = Sum<Sum<NonceLen, KG::PkLen>, OutputSize<D>>;
|
||||
type Len = Sum<Sum<NonceLen, G::PkLen>, OutputSize<H>>;
|
||||
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len> {
|
||||
self.server_nonce
|
||||
@@ -689,43 +448,50 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Hash, KG: KeGroup> Ke2Message<D, KG>
|
||||
impl<H: Hash> Deserialize for Ke3Message<H>
|
||||
where
|
||||
D::Core: ProxyHash,
|
||||
<D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
NonceLen: Add<KG::PkLen>,
|
||||
Sum<NonceLen, KG::PkLen>: ArrayLength<u8>,
|
||||
H::Core: ProxyHash,
|
||||
<H::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<H::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
{
|
||||
fn to_bytes_without_mac(&self) -> GenericArray<u8, Sum<NonceLen, KG::PkLen>> {
|
||||
self.server_nonce.concat(self.server_e_pk.serialize())
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Hash> Deserialize for Ke3Message<D>
|
||||
where
|
||||
D::Core: ProxyHash,
|
||||
<D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
{
|
||||
fn deserialize(bytes: &[u8]) -> Result<Self, ProtocolError> {
|
||||
let checked_bytes = check_slice_size(bytes, OutputSize::<D>::USIZE, "ke3_message")?;
|
||||
|
||||
fn deserialize_take(bytes: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
Ok(Self {
|
||||
mac: GenericArray::clone_from_slice(checked_bytes),
|
||||
mac: bytes.take_array("mac")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<D: Hash> Serialize for Ke3Message<D>
|
||||
impl<H: Hash> Serialize for Ke3Message<H>
|
||||
where
|
||||
D::Core: ProxyHash,
|
||||
<D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
H::Core: ProxyHash,
|
||||
<H::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<H::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
{
|
||||
type Len = OutputSize<D>;
|
||||
type Len = OutputSize<H>;
|
||||
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len> {
|
||||
self.mac.clone()
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////
|
||||
// Test Implementations //
|
||||
//===================== //
|
||||
//////////////////////////
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::serialization::AssertZeroized;
|
||||
|
||||
#[cfg(test)]
|
||||
impl<H: OutputSizeUser> AssertZeroized for Ke2State<H> {
|
||||
fn assert_zeroized(&self) {
|
||||
let Self {
|
||||
session_key,
|
||||
expected_mac,
|
||||
} = self;
|
||||
|
||||
for byte in session_key.iter().chain(expected_mac) {
|
||||
assert_eq!(byte, &0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user