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:
daxpedda
2025-05-19 13:56:25 -07:00
committed by GitHub
parent 58b4d746c0
commit bebd2c605b
37 changed files with 9402 additions and 3375 deletions
+53 -44
View File
@@ -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());
}
+459
View File
@@ -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();
}
}
+108 -52
View File
@@ -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);
}
}
+13 -75
View File
@@ -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>;
}
+53 -55
View File
@@ -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());
}
}
+4
View File
@@ -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;
+391
View File
@@ -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);
}
}
}
+192
View File
@@ -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();
}
+88
View File
@@ -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>;
}
}
+296
View File
@@ -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())
}
}
+645
View File
@@ -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);
}
}
}
+89
View File
@@ -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>;
}
}
+54
View File
@@ -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
View File
@@ -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
View File
@@ -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);
}
}
}