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
+16 -12
View File
@@ -13,10 +13,12 @@ jobs:
fail-fast: false
matrix:
backend_feature:
- --features ristretto255-voprf
-
- --features curve25519,ristretto255-voprf
- --features ristretto255
- --features curve25519
- --features ecdsa
- --features ed25519
- --features ristretto255,curve25519,ecdsa,ed25519
frontend_feature:
-
- --features argon2
@@ -69,10 +71,12 @@ jobs:
# 32-bit x86
- i686-unknown-linux-gnu
backend_feature:
- --features ristretto255-voprf
-
- curve25519,ristretto255-voprf
- curve25519
- --features ristretto255
- --features curve25519
- --features ecdsa
- --features ed25519
- --features ristretto255,curve25519,ecdsa,ed25519
steps:
- uses: actions/checkout@main
- uses: hecrj/setup-rust-action@v2
@@ -140,10 +144,12 @@ jobs:
# for any no_std target
- thumbv6m-none-eabi
backend_feature:
- ristretto255-voprf
-
- curve25519,ristretto255-voprf
- ristretto255
- curve25519
- ecdsa
- ed25519
- ristretto255,curve25519,ecdsa,ed25519
frontend_feature:
- argon2
- serde
@@ -160,10 +166,8 @@ jobs:
fail-fast: false
matrix:
backend_feature:
- --features ristretto255-voprf
- --features ristretto255
-
- --features curve25519,ristretto255-voprf
- --features curve25519
steps:
- name: Checkout sources
uses: actions/checkout@main
@@ -200,7 +204,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: clippy
args: --all-targets --features argon2,std,curve25519 -- -D warnings
args: --all-targets --features argon2,std,curve25519,ecdsa,ed25519 -- -D warnings
- name: Run cargo doc
uses: actions-rs/cargo@v1
@@ -208,7 +212,7 @@ jobs:
RUSTDOCFLAGS: -D warnings
with:
command: doc
args: --no-deps --document-private-items --features argon2,std,curve25519
args: --no-deps --document-private-items --features argon2,std,curve25519,ecdsa,ed25519
format:
name: cargo fmt
+20 -4
View File
@@ -3,6 +3,7 @@ authors = ["Kevin Lewi <[email protected]>", "François Garillot <[email protected]>"]
categories = ["no-std"]
description = "An implementation of the OPAQUE password-authenticated key exchange protocol"
edition = "2021"
exclude = ["/src/tests/"]
keywords = ["cryptography", "crypto", "opaque", "passwords", "authentication"]
license = "Apache-2.0 OR MIT"
name = "opaque-ke"
@@ -14,12 +15,15 @@ version = "3.0.0"
[features]
argon2 = ["dep:argon2"]
curve25519 = ["dep:curve25519-dalek"]
default = ["ristretto255-voprf", "serde"]
ristretto255 = ["dep:curve25519-dalek", "voprf/ristretto255"]
ristretto255-voprf = ["ristretto255", "voprf/ristretto255-ciphersuite"]
default = ["ristretto255", "serde"]
ecdsa = ["dep:ecdsa", "dep:rfc6979"]
ed25519 = ["dep:curve25519-dalek", "dep:ed25519-dalek"]
ristretto255 = ["dep:curve25519-dalek", "voprf/ristretto255-ciphersuite"]
serde = [
"dep:serde",
"curve25519-dalek?/serde",
"ecdsa?/serde",
"ed25519-dalek?/serde",
"generic-array/serde",
"voprf/serde",
"zeroize/serde",
@@ -33,14 +37,23 @@ argon2 = { version = "0.5", default-features = false, features = [
curve25519-dalek = { version = "4", default-features = false, features = [
"zeroize",
], optional = true }
derive-where = { version = "1.3", features = ["zeroize-on-drop"] }
derive-where = { version = "1.4", features = ["zeroize-on-drop"] }
digest = "0.10"
displaydoc = { version = "0.2", default-features = false }
ecdsa = { version = "0.16", default-features = false, features = [
"arithmetic",
"hazmat",
], optional = true }
ed25519-dalek = { version = "2", default-features = false, features = [
"digest",
"hazmat",
], optional = true }
elliptic-curve = { version = "0.13", features = ["hash2curve", "sec1"] }
generic-array = "0.14"
hkdf = "0.12"
hmac = "0.12"
rand = { version = "0.8", default-features = false }
rfc6979 = { version = "0.4", optional = true }
serde = { version = "1", default-features = false, features = [
"derive",
], optional = true }
@@ -60,6 +73,7 @@ cryptoki = "0.9"
elliptic-curve = { version = "0.13", features = ["alloc", "pkcs8"] }
hex = "0.4"
p256 = { version = "0.13", default-features = false, features = [
"ecdsa",
"hash2curve",
"pkcs8",
"voprf",
@@ -74,9 +88,11 @@ p521 = { version = "0.13.3", default-features = false, features = [
"pkcs8",
"voprf",
] }
paste = "1"
proptest = "1"
rand = "0.8"
regex = "1"
sha2 = { version = "0.10", default-features = false }
thiserror = "2"
# MSRV
rustyline = "15"
+12 -9
View File
@@ -23,16 +23,14 @@ struct Default;
#[cfg(feature = "ristretto255")]
impl CipherSuite for Default {
type OprfCs = opaque_ke::Ristretto255;
type KeGroup = opaque_ke::Ristretto255;
type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
type KeyExchange = opaque_ke::TripleDh<opaque_ke::Ristretto255, sha2::Sha512>;
type Ksf = opaque_ke::ksf::Identity;
}
#[cfg(not(feature = "ristretto255"))]
impl CipherSuite for Default {
type OprfCs = p256::NistP256;
type KeGroup = p256::NistP256;
type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
type KeyExchange = opaque_ke::TripleDh<p256::NistP256, sha2::Sha256>;
type Ksf = opaque_ke::ksf::Identity;
}
@@ -187,7 +185,7 @@ fn server_login_start_real(c: &mut Criterion) {
Some(password_file.clone()),
client_login_start_result.clone().message,
username,
ServerLoginStartParameters::default(),
ServerLoginParameters::default(),
)
.unwrap();
})
@@ -209,7 +207,7 @@ fn server_login_start_fake(c: &mut Criterion) {
None,
client_login_start_result.clone().message,
username,
ServerLoginStartParameters::default(),
ServerLoginParameters::default(),
)
.unwrap();
})
@@ -246,7 +244,7 @@ fn client_login_finish(c: &mut Criterion) {
Some(password_file),
client_login_start_result.clone().message,
username,
ServerLoginStartParameters::default(),
ServerLoginParameters::default(),
)
.unwrap();
@@ -256,6 +254,7 @@ fn client_login_finish(c: &mut Criterion) {
.clone()
.state
.finish(
&mut rng,
password,
server_login_start.clone().message,
ClientLoginFinishParameters::default(),
@@ -295,12 +294,13 @@ fn server_login_finish(c: &mut Criterion) {
Some(password_file),
client_login_start_result.clone().message,
username,
ServerLoginStartParameters::default(),
ServerLoginParameters::default(),
)
.unwrap();
let client_login_finish_result = client_login_start_result
.state
.finish(
&mut rng,
password,
server_login_start_result.clone().message,
ClientLoginFinishParameters::default(),
@@ -312,7 +312,10 @@ fn server_login_finish(c: &mut Criterion) {
server_login_start_result
.clone()
.state
.finish(client_login_finish_result.clone().message)
.finish(
client_login_finish_result.clone().message,
ServerLoginParameters::default(),
)
.unwrap();
})
});
+1
View File
@@ -0,0 +1 @@
doc-valid-idents = ["HashEdDSA", "PureEdDSA", ".."]
+9 -7
View File
@@ -38,7 +38,7 @@ use opaque_ke::{
ClientLogin, ClientLoginFinishParameters, ClientRegistration,
ClientRegistrationFinishParameters, CredentialFinalization, CredentialRequest,
CredentialResponse, RegistrationRequest, RegistrationResponse, RegistrationUpload, ServerLogin,
ServerLoginStartParameters, ServerRegistration, ServerRegistrationLen, ServerSetup,
ServerLoginParameters, ServerRegistration, ServerRegistrationLen, ServerSetup,
};
use rustyline::error::ReadlineError;
use rustyline::history::DefaultHistory;
@@ -52,16 +52,14 @@ struct DefaultCipherSuite;
#[cfg(feature = "ristretto255")]
impl CipherSuite for DefaultCipherSuite {
type OprfCs = opaque_ke::Ristretto255;
type KeGroup = opaque_ke::Ristretto255;
type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
type KeyExchange = opaque_ke::TripleDh<opaque_ke::Ristretto255, sha2::Sha512>;
type Ksf = opaque_ke::ksf::Identity;
}
#[cfg(not(feature = "ristretto255"))]
impl CipherSuite for DefaultCipherSuite {
type OprfCs = p256::NistP256;
type KeGroup = p256::NistP256;
type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
type KeyExchange = opaque_ke::TripleDh<p256::NistP256, sha2::Sha256>;
type Ksf = opaque_ke::ksf::Identity;
}
@@ -172,7 +170,7 @@ fn open_locker(
Some(password_file),
CredentialRequest::deserialize(&credential_request_bytes).unwrap(),
&locker_id.to_be_bytes(),
ServerLoginStartParameters::default(),
ServerLoginParameters::default(),
)
.unwrap();
let credential_response_bytes = server_login_start_result.message.serialize();
@@ -180,6 +178,7 @@ fn open_locker(
// Server sends credential_response_bytes to client
let result = client_login_start_result.state.finish(
&mut client_rng,
password.as_bytes(),
CredentialResponse::deserialize(&credential_response_bytes).unwrap(),
ClientLoginFinishParameters::default(),
@@ -196,7 +195,10 @@ fn open_locker(
let server_login_finish_result = server_login_start_result
.state
.finish(CredentialFinalization::deserialize(&credential_finalization_bytes).unwrap())
.finish(
CredentialFinalization::deserialize(&credential_finalization_bytes).unwrap(),
ServerLoginParameters::default(),
)
.unwrap();
// Server sends locker contents, encrypted under the session key, to the client
+9 -7
View File
@@ -33,7 +33,7 @@ use opaque_ke::{
ClientLogin, ClientLoginFinishParameters, ClientRegistration,
ClientRegistrationFinishParameters, CredentialFinalization, CredentialRequest,
CredentialResponse, RegistrationRequest, RegistrationResponse, RegistrationUpload, ServerLogin,
ServerLoginStartParameters, ServerRegistration, ServerRegistrationLen, ServerSetup,
ServerLoginParameters, ServerRegistration, ServerRegistrationLen, ServerSetup,
};
use rustyline::error::ReadlineError;
use rustyline::history::DefaultHistory;
@@ -47,8 +47,7 @@ struct DefaultCipherSuite;
#[cfg(feature = "ristretto255")]
impl CipherSuite for DefaultCipherSuite {
type OprfCs = opaque_ke::Ristretto255;
type KeGroup = opaque_ke::Ristretto255;
type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
type KeyExchange = opaque_ke::TripleDh<opaque_ke::Ristretto255, sha2::Sha512>;
type Ksf = Argon2<'static>;
}
@@ -56,8 +55,7 @@ impl CipherSuite for DefaultCipherSuite {
#[cfg(not(feature = "ristretto255"))]
impl CipherSuite for DefaultCipherSuite {
type OprfCs = p256::NistP256;
type KeGroup = p256::NistP256;
type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
type KeyExchange = opaque_ke::TripleDh<p256::NistP256, sha2::Sha256>;
type Ksf = Argon2<'static>;
}
@@ -128,7 +126,7 @@ fn account_login(
Some(password_file),
CredentialRequest::deserialize(&credential_request_bytes).unwrap(),
username.as_bytes(),
ServerLoginStartParameters::default(),
ServerLoginParameters::default(),
)
.unwrap();
let credential_response_bytes = server_login_start_result.message.serialize();
@@ -136,6 +134,7 @@ fn account_login(
// Server sends credential_response_bytes to client
let result = client_login_start_result.state.finish(
&mut client_rng,
password.as_bytes(),
CredentialResponse::deserialize(&credential_response_bytes).unwrap(),
ClientLoginFinishParameters::default(),
@@ -152,7 +151,10 @@ fn account_login(
let server_login_finish_result = server_login_start_result
.state
.finish(CredentialFinalization::deserialize(&credential_finalization_bytes).unwrap())
.finish(
CredentialFinalization::deserialize(&credential_finalization_bytes).unwrap(),
ServerLoginParameters::default(),
)
.unwrap();
client_login_finish_result.session_key == server_login_finish_result.session_key
+18 -10
View File
@@ -9,14 +9,18 @@
//! Defines the [`CipherSuite`] trait to specify the underlying primitives for
//! OPAQUE
use digest::core_api::{BlockSizeUser, CoreProxy};
use digest::OutputSizeUser;
use generic_array::typenum::{IsLess, IsLessOrEqual, Le, NonZero, U256};
use core::ops::Add;
use crate::hash::{Hash, ProxyHash};
use crate::key_exchange::group::KeGroup;
use digest::core_api::{BlockSizeUser, CoreProxy};
use generic_array::typenum::{IsLess, Le, NonZero, Sum, U256};
use generic_array::ArrayLength;
use crate::envelope::NonceLen;
use crate::hash::{Hash, OutputSize, ProxyHash};
use crate::key_exchange::group::Group;
use crate::key_exchange::traits::KeyExchange;
use crate::ksf::Ksf;
use crate::opaque::MaskedResponseLen;
/// Configures the underlying primitives used in OPAQUE
/// * `OprfCs`: A VOPRF ciphersuite, see [`voprf::CipherSuite`].
@@ -26,22 +30,26 @@ use crate::ksf::Ksf;
/// * `Ksf`: A key stretching function, typically used for password hashing
pub trait CipherSuite
where
<OprfHash<Self> as OutputSizeUser>::OutputSize:
IsLess<U256> + IsLessOrEqual<<OprfHash<Self> as BlockSizeUser>::BlockSize>,
OprfHash<Self>: Hash,
<OprfHash<Self> as CoreProxy>::Core: ProxyHash,
<<OprfHash<Self> as CoreProxy>::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
Le<<<OprfHash<Self> as CoreProxy>::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
// Envelope: Nonce + Hash
// MaskedResponse: (Nonce + Hash) + KePk
OutputSize<OprfHash<Self>>: Add<NonceLen>,
Sum<OutputSize<OprfHash<Self>>, NonceLen>:
ArrayLength<u8> + Add<<KeGroup<Self> as Group>::PkLen>,
MaskedResponseLen<Self>: ArrayLength<u8>,
{
/// A VOPRF ciphersuite, see [`voprf::CipherSuite`].
type OprfCs: voprf::CipherSuite;
/// A `Group` used for the `KeyExchange`.
type KeGroup: 'static + KeGroup;
/// A key exchange protocol
type KeyExchange: KeyExchange<OprfHash<Self>, Self::KeGroup>;
type KeyExchange: KeyExchange;
/// A key stretching function, typically used for password hashing
type Ksf: Ksf;
}
pub(crate) type OprfGroup<CS: CipherSuite> = <CS::OprfCs as voprf::CipherSuite>::Group;
pub(crate) type OprfHash<CS: CipherSuite> = <CS::OprfCs as voprf::CipherSuite>::Hash;
pub(crate) type KeGroup<CS: CipherSuite> = <CS::KeyExchange as KeyExchange>::Group;
pub(crate) type KeHash<CS: CipherSuite> = <CS::KeyExchange as KeyExchange>::Hash;
+67 -69
View File
@@ -7,26 +7,25 @@
// licenses.
use core::convert::TryFrom;
use core::ops::Add;
use derive_where::derive_where;
use digest::Output;
use generic_array::sequence::Concat;
use generic_array::typenum::{Sum, Unsigned, U2, U32};
use generic_array::{ArrayLength, GenericArray};
use generic_array::typenum::{Sum, U32};
use generic_array::GenericArray;
use hkdf::Hkdf;
use hmac::{Hmac, Mac};
use rand::{CryptoRng, RngCore};
use zeroize::{Zeroize, ZeroizeOnDrop};
use crate::ciphersuite::{CipherSuite, OprfHash};
use crate::errors::utils::check_slice_size;
use crate::ciphersuite::{CipherSuite, KeGroup, OprfHash};
use crate::errors::{InternalError, ProtocolError};
use crate::hash::OutputSize;
use crate::key_exchange::group::KeGroup;
use crate::keypair::{KeyPair, PrivateKey, PrivateKeySerialization, PublicKey};
use crate::opaque::{bytestrings_from_identifiers, Identifiers};
use crate::serialization::{Input, MacExt};
use crate::key_exchange::group::Group;
use crate::key_exchange::traits::SerializedIdentifiers;
use crate::keypair::{KeyPair, PrivateKey, PublicKey};
use crate::opaque::Identifiers;
use crate::serialization::{GenericArrayExt, SliceExt, UpdateExt};
// Constant string used as salt for HKDF computation
const STR_AUTH_KEY: [u8; 7] = *b"AuthKey";
@@ -82,10 +81,9 @@ pub(crate) struct Envelope<CS: CipherSuite> {
// which is technically unrelated to the envelope's encrypted and authenticated
// contents.
pub(crate) struct OpenedEnvelope<'a, CS: CipherSuite> {
pub(crate) client_static_keypair: KeyPair<CS::KeGroup>,
pub(crate) client_static_keypair: KeyPair<KeGroup<CS>>,
pub(crate) export_key: Output<OprfHash<CS>>,
pub(crate) id_u: Input<'a, U2, <CS::KeGroup as KeGroup>::PkLen>,
pub(crate) id_s: Input<'a, U2, <CS::KeGroup as KeGroup>::PkLen>,
pub(crate) identifiers: SerializedIdentifiers<'a, KeGroup<CS>>,
}
pub(crate) struct OpenedInnerEnvelope<CS: CipherSuite> {
@@ -97,23 +95,23 @@ type SealRawResult<CS: CipherSuite> = (Envelope<CS>, Output<OprfHash<CS>>);
#[cfg(test)]
type SealRawResult<CS: CipherSuite> = (Envelope<CS>, Output<OprfHash<CS>>, Output<OprfHash<CS>>);
#[cfg(not(test))]
type SealResult<CS: CipherSuite> = (Envelope<CS>, PublicKey<CS::KeGroup>, Output<OprfHash<CS>>);
type SealResult<CS: CipherSuite> = (Envelope<CS>, PublicKey<KeGroup<CS>>, Output<OprfHash<CS>>);
#[cfg(test)]
type SealResult<CS: CipherSuite> = (
Envelope<CS>,
PublicKey<CS::KeGroup>,
PublicKey<KeGroup<CS>>,
Output<OprfHash<CS>>,
Output<OprfHash<CS>>,
);
pub(crate) type EnvelopeLen<CS: CipherSuite> = Sum<NonceLen, OutputSize<OprfHash<CS>>>;
pub(crate) type EnvelopeLen<CS: CipherSuite> = Sum<OutputSize<OprfHash<CS>>, NonceLen>;
impl<CS: CipherSuite> Envelope<CS> {
#[allow(clippy::type_complexity)]
pub(crate) fn seal<R: RngCore + CryptoRng>(
rng: &mut R,
randomized_pwd_hasher: Hkdf<OprfHash<CS>>,
server_s_pk: &PublicKey<CS::KeGroup>,
server_s_pk: &PublicKey<KeGroup<CS>>,
ids: Identifiers,
) -> Result<SealResult<CS>, ProtocolError> {
let mut nonce = GenericArray::default();
@@ -125,12 +123,16 @@ impl<CS: CipherSuite> Envelope<CS> {
);
let server_s_pk_bytes = server_s_pk.serialize();
let (id_u, id_s) = bytestrings_from_identifiers::<CS::KeGroup>(
let identifiers = SerializedIdentifiers::<KeGroup<CS>>::from_identifiers(
ids,
client_s_pk.serialize(),
server_s_pk_bytes.clone(),
)?;
let aad = construct_aad(id_u.iter(), id_s.iter(), &server_s_pk_bytes);
let aad = construct_aad(
identifiers.client.iter(),
identifiers.server.iter(),
&server_s_pk_bytes,
);
let result = Self::seal_raw(randomized_pwd_hasher, nonce, aad, mode)?;
Ok((
@@ -183,7 +185,7 @@ impl<CS: CipherSuite> Envelope<CS> {
pub(crate) fn open<'a>(
&self,
randomized_pwd_hasher: Hkdf<OprfHash<CS>>,
server_s_pk: PublicKey<CS::KeGroup>,
server_s_pk: PublicKey<KeGroup<CS>>,
optional_ids: Identifiers<'a>,
) -> Result<OpenedEnvelope<'a, CS>, ProtocolError> {
let client_static_keypair = match self.mode {
@@ -196,20 +198,23 @@ impl<CS: CipherSuite> Envelope<CS> {
};
let server_s_pk_bytes = server_s_pk.serialize();
let (id_u, id_s) = bytestrings_from_identifiers::<CS::KeGroup>(
let identifiers = SerializedIdentifiers::<KeGroup<CS>>::from_identifiers(
optional_ids,
client_static_keypair.public().serialize(),
server_s_pk_bytes.clone(),
)?;
let aad = construct_aad(id_u.iter(), id_s.iter(), &server_s_pk_bytes);
let aad = construct_aad(
identifiers.client.iter(),
identifiers.server.iter(),
&server_s_pk_bytes,
);
let opened = self.open_raw(randomized_pwd_hasher, aad)?;
Ok(OpenedEnvelope {
client_static_keypair,
export_key: opened.export_key,
id_u,
id_s,
identifiers,
})
}
@@ -249,45 +254,22 @@ impl<CS: CipherSuite> Envelope<CS> {
}
}
fn hmac_key_size() -> usize {
OutputSize::<OprfHash<CS>>::USIZE
}
#[cfg(test)]
pub(crate) fn len() -> usize {
use generic_array::typenum::Unsigned;
OutputSize::<OprfHash<CS>>::USIZE + NonceLen::USIZE
}
pub(crate) fn serialize(&self) -> GenericArray<u8, EnvelopeLen<CS>>
where
// Envelope: Nonce + Hash
NonceLen: Add<OutputSize<OprfHash<CS>>>,
EnvelopeLen<CS>: ArrayLength<u8>,
{
self.nonce.concat(self.hmac.clone())
pub(crate) fn serialize(&self) -> GenericArray<u8, EnvelopeLen<CS>> {
self.nonce.concat_ext(&self.hmac)
}
pub(crate) fn deserialize(bytes: &[u8]) -> Result<Self, ProtocolError> {
let mode = InnerEnvelopeMode::Internal; // Better way to hard-code this?
if bytes.len() < NonceLen::USIZE {
return Err(ProtocolError::SerializationError);
}
let nonce = GenericArray::clone_from_slice(&bytes[..NonceLen::USIZE]);
let remainder = match mode {
InnerEnvelopeMode::Zero => {
return Err(InternalError::IncompatibleEnvelopeModeError.into())
}
InnerEnvelopeMode::Internal => &bytes[NonceLen::USIZE..],
};
let hmac_key_size = Self::hmac_key_size();
let hmac = check_slice_size(remainder, hmac_key_size, "hmac_key_size")?;
pub(crate) fn deserialize_take(bytes: &mut &[u8]) -> Result<Self, ProtocolError> {
Ok(Self {
mode,
nonce,
hmac: GenericArray::clone_from_slice(hmac),
mode: InnerEnvelopeMode::Internal,
nonce: bytes.take_array("nonce")?,
hmac: bytes.take_array("hmac")?,
})
}
}
@@ -297,33 +279,28 @@ impl<CS: CipherSuite> Envelope<CS> {
fn build_inner_envelope_internal<CS: CipherSuite>(
randomized_pwd_hasher: Hkdf<OprfHash<CS>>,
nonce: GenericArray<u8, NonceLen>,
) -> Result<PublicKey<CS::KeGroup>, ProtocolError> {
let mut keypair_seed = GenericArray::<_, <CS::KeGroup as KeGroup>::SkLen>::default();
) -> Result<PublicKey<KeGroup<CS>>, ProtocolError> {
let mut keypair_seed = GenericArray::<_, <KeGroup<CS> as Group>::SkLen>::default();
randomized_pwd_hasher
.expand(&nonce.concat(STR_PRIVATE_KEY.into()), &mut keypair_seed)
.map_err(|_| InternalError::HkdfError)?;
let client_static_keypair =
PrivateKey::<CS::KeGroup>::deserialize_key_pair(&CS::KeGroup::serialize_sk(
CS::KeGroup::derive_auth_keypair::<CS::OprfCs>(keypair_seed)?,
))?;
let client_s_sk = PrivateKey::new(KeGroup::<CS>::derive_scalar(keypair_seed)?);
Ok(client_static_keypair.public().clone())
Ok(client_s_sk.public_key())
}
fn recover_keys_internal<CS: CipherSuite>(
randomized_pwd_hasher: Hkdf<OprfHash<CS>>,
nonce: GenericArray<u8, NonceLen>,
) -> Result<KeyPair<CS::KeGroup>, ProtocolError> {
let mut keypair_seed = GenericArray::<_, <CS::KeGroup as KeGroup>::SkLen>::default();
) -> Result<KeyPair<KeGroup<CS>>, ProtocolError> {
let mut keypair_seed = GenericArray::<_, <KeGroup<CS> as Group>::SkLen>::default();
randomized_pwd_hasher
.expand(&nonce.concat(STR_PRIVATE_KEY.into()), &mut keypair_seed)
.map_err(|_| InternalError::HkdfError)?;
let client_static_keypair =
PrivateKey::<CS::KeGroup>::deserialize_key_pair(&CS::KeGroup::serialize_sk(
CS::KeGroup::derive_auth_keypair::<CS::OprfCs>(keypair_seed)?,
))?;
let client_s_sk = PrivateKey::new(KeGroup::<CS>::derive_scalar(keypair_seed)?);
let client_s_pk = client_s_sk.public_key();
Ok(client_static_keypair)
Ok(KeyPair::new(client_s_sk, client_s_pk))
}
fn construct_aad<'a>(
@@ -333,3 +310,24 @@ fn construct_aad<'a>(
) -> impl Iterator<Item = &'a [u8]> {
[server_s_pk].into_iter().chain(id_s).chain(id_u)
}
//////////////////////////
// Test Implementations //
//===================== //
//////////////////////////
#[cfg(test)]
use crate::serialization::AssertZeroized;
#[cfg(test)]
impl<CS: CipherSuite> AssertZeroized for Envelope<CS> {
fn assert_zeroized(&self) {
let Self { mode, nonce, hmac } = self;
assert_eq!(mode, &InnerEnvelopeMode::Zero);
for byte in nonce.iter().chain(hmac) {
assert_eq!(byte, &0);
}
}
}
-39
View File
@@ -96,9 +96,6 @@ pub enum ProtocolError<T = Infallible> {
/** This error occurs when the client detects that the server has
reflected the OPRF value (beta == alpha) */
ReflectedValueError,
/** Identity group element was encountered during deserialization, which is
invalid */
IdentityGroupElementError,
/// Custom [`SecretKey`](crate::keypair::PrivateKeySerialization) error type
Custom(T),
}
@@ -122,7 +119,6 @@ impl<T: Debug> Debug for ProtocolError<T> {
.field("actual_len", actual_len)
.finish(),
Self::ReflectedValueError => f.debug_tuple("ReflectedValueError").finish(),
Self::IdentityGroupElementError => f.debug_tuple("IdentityGroupElementError").finish(),
Self::Custom(custom) => f.debug_tuple("Custom").field(custom).finish(),
}
}
@@ -164,41 +160,6 @@ impl ProtocolError {
actual_len,
},
Self::ReflectedValueError => ProtocolError::ReflectedValueError,
Self::IdentityGroupElementError => ProtocolError::IdentityGroupElementError,
}
}
}
pub(crate) mod utils {
use super::*;
pub fn check_slice_size<'a>(
slice: &'a [u8],
expected_len: usize,
arg_name: &'static str,
) -> Result<&'a [u8], ProtocolError> {
if slice.len() != expected_len {
return Err(ProtocolError::SizeError {
name: arg_name,
len: expected_len,
actual_len: slice.len(),
});
}
Ok(slice)
}
pub fn check_slice_size_atleast<'a>(
slice: &'a [u8],
expected_len: usize,
arg_name: &'static str,
) -> Result<&'a [u8], ProtocolError> {
if slice.len() < expected_len {
return Err(ProtocolError::SizeError {
name: arg_name,
len: expected_len,
actual_len: slice.len(),
});
}
Ok(slice)
}
}
+1 -1
View File
@@ -13,7 +13,7 @@ use digest::core_api::{BlockSizeUser, BufferKindUser, CoreProxy, FixedOutputCore
use digest::{FixedOutputReset, HashMarker, OutputSizeUser};
use generic_array::typenum::{IsLess, Le, NonZero, U256};
pub(crate) type OutputSize<D> = <<D as CoreProxy>::Core as OutputSizeUser>::OutputSize;
pub(crate) type OutputSize<H> = <<H as CoreProxy>::Core as OutputSizeUser>::OutputSize;
/// Trait to simplify requirements for [`Hash`].
pub trait ProxyHash:
+49 -40
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);
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);
}
}
}
+275 -509
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
let shared_secret_1 = 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),
.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();
}
}
impl<KG: KeGroup, D: Hash> ZeroizeOnDrop for Ke2Builder<D, KG>
where
D::Core: ProxyHash,
<D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
{
}
impl<KG: KeGroup, D: Hash> Deserialize for Ke2Message<D, KG>
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 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])?;
Ok(Self {
server_nonce: GenericArray::clone_from_slice(&checked_nonce[..nonce_len]),
let Self {
server_nonce,
transcript_hasher,
client_e_pk,
server_e_pk,
mac: GenericArray::clone_from_slice(checked_mac),
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<G: Group, H: Hash> ZeroizeOnDrop for Ke2Builder<G, H>
where
H::Core: ProxyHash,
<H::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
Le<<H::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
{
}
impl<G: Group, H: Hash> Deserialize for Ke2Message<G, H>
where
H::Core: ProxyHash,
<H::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
Le<<H::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)?,
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);
}
}
}
+383 -173
View File
@@ -11,12 +11,16 @@
#![allow(unsafe_code)]
use derive_where::derive_where;
use digest::{Output, OutputSizeUser};
use generic_array::{ArrayLength, GenericArray};
use rand::{CryptoRng, RngCore};
use crate::ciphersuite::CipherSuite;
use crate::errors::ProtocolError;
use crate::key_exchange::group::KeGroup;
use crate::key_exchange::tripledh::DiffieHellman;
use crate::key_exchange::group::Group;
use crate::key_exchange::shared::DiffieHellman;
use crate::key_exchange::sigma_i::{Message, MessageBuilder, SignatureProtocol};
use crate::serialization::SliceExt;
/// A Keypair trait with public-private verification
#[cfg_attr(
@@ -28,20 +32,24 @@ use crate::key_exchange::tripledh::DiffieHellman;
))
)]
#[derive_where(Clone)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; KG::Pk, SK)]
pub struct KeyPair<KG: KeGroup, SK: Clone = PrivateKey<KG>> {
pk: PublicKey<KG>,
#[derive_where(Eq, Hash, Ord, PartialEq, PartialOrd; G::Pk, SK)]
// `NonZeroScalar` doesn't implement `Debug`.
// TODO: remove after `elliptic-curve` bump to v0.14.
#[cfg_attr(not(test), derive_where(Debug; G::Pk, SK))]
#[cfg_attr(test, derive_where(Debug), derive_where(skip_inner(Debug)))]
pub struct KeyPair<G: Group, SK: Clone = PrivateKey<G>> {
pk: PublicKey<G>,
sk: SK,
}
impl<KG: KeGroup, SK: Clone> KeyPair<KG, SK> {
impl<G: Group, SK: Clone> KeyPair<G, SK> {
/// Creates a new [`KeyPair`] from the given keys.
pub fn new(sk: SK, pk: PublicKey<KG>) -> Self {
pub fn new(sk: SK, pk: PublicKey<G>) -> Self {
Self { pk, sk }
}
/// The public key component
pub fn public(&self) -> &PublicKey<KG> {
pub fn public(&self) -> &PublicKey<G> {
&self.pk
}
@@ -51,15 +59,22 @@ impl<KG: KeGroup, SK: Clone> KeyPair<KG, SK> {
}
}
impl<KG: KeGroup> KeyPair<KG> {
impl<G: Group> KeyPair<G> {
pub(crate) fn random<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
let sk = G::random_sk(rng);
let pk = G::public_key(sk);
Self {
pk: PublicKey(pk),
sk: PrivateKey(sk),
}
}
/// Generating a random key pair given a cryptographic rng
pub(crate) fn generate_random<CS: voprf::CipherSuite, R: RngCore + CryptoRng>(
rng: &mut R,
) -> Self {
let mut scalar_bytes = GenericArray::<_, <KG as KeGroup>::SkLen>::default();
pub(crate) fn derive_random<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
let mut scalar_bytes = GenericArray::<_, <G as Group>::SkLen>::default();
rng.fill_bytes(&mut scalar_bytes);
let sk = KG::derive_auth_keypair::<CS>(scalar_bytes).unwrap();
let pk = KG::public_key(sk);
let sk = G::derive_scalar(scalar_bytes).unwrap();
let pk = G::public_key(sk);
Self {
pk: PublicKey(pk),
sk: PrivateKey(sk),
@@ -67,16 +82,237 @@ impl<KG: KeGroup> KeyPair<KG> {
}
}
#[cfg(test)]
impl<KG: KeGroup> KeyPair<KG>
/// Wrapper around a Key to enforce that it's a private one.
#[derive_where(Clone, ZeroizeOnDrop)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; G::Sk)]
pub struct PrivateKey<G: Group>(G::Sk);
impl<G: Group> PrivateKey<G> {
pub(crate) fn new(key: G::Sk) -> Self {
Self(key)
}
/// Returns public key from private key
pub fn public_key(&self) -> PublicKey<G> {
PublicKey(G::public_key(self.0))
}
pub(crate) fn serialize(&self) -> GenericArray<u8, G::SkLen> {
G::serialize_sk(self.0)
}
/// Creates a [`PrivateKey`] from the given bytes.
pub fn deserialize(mut input: &[u8]) -> Result<Self, ProtocolError> {
Self::deserialize_take(&mut input)
}
pub(crate) fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError> {
G::deserialize_take_sk(input).map(Self)
}
}
impl<G: Group> PrivateKey<G>
where
KG::Pk: std::fmt::Debug,
KG::Sk: std::fmt::Debug,
G::Sk: DiffieHellman<G>,
{
/// Diffie-Hellman key exchange implementation
pub(crate) fn ke_diffie_hellman(&self, pk: &PublicKey<G>) -> GenericArray<u8, G::PkLen> {
self.0.diffie_hellman(pk.0)
}
}
impl<G: Group> PrivateKey<G> {
/// Private-key signing implementation
pub(crate) fn sign<
R: CryptoRng + RngCore,
CS: CipherSuite,
SIG: SignatureProtocol<Group = G>,
KE: Group,
>(
&self,
rng: &mut R,
message: &Message<CS, KE>,
) -> (SIG::Signature, SIG::VerifyState<CS, KE>) {
SIG::sign(&self.0, rng, message)
}
}
/// A trait to facilitate
/// [`ServerSetup::de/serialize`](crate::ServerSetup::serialize).
pub trait PrivateKeySerialization<G: Group>: Clone {
/// Custom error type that can be passed down to `ProtocolError::Custom`
type Error;
/// Serialization size in bytes.
type Len: ArrayLength<u8>;
/// Serialization into bytes
fn serialize_key_pair(key_pair: &KeyPair<G, Self>) -> GenericArray<u8, Self::Len>;
/// Deserialization from bytes
fn deserialize_take_key_pair(
input: &mut &[u8],
) -> Result<KeyPair<G, Self>, ProtocolError<Self::Error>>;
}
impl<G: Group> PrivateKeySerialization<G> for PrivateKey<G> {
type Error = core::convert::Infallible;
type Len = G::SkLen;
fn serialize_key_pair(key_pair: &KeyPair<G, Self>) -> GenericArray<u8, Self::Len> {
key_pair.private().serialize()
}
fn deserialize_take_key_pair(input: &mut &[u8]) -> Result<KeyPair<G, Self>, ProtocolError> {
let sk = PrivateKey::deserialize_take(input)?;
let pk = sk.public_key();
Ok(KeyPair::new(sk, pk))
}
}
#[cfg(feature = "serde")]
impl<'de, G: Group> serde::Deserialize<'de> for PrivateKey<G> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error;
G::deserialize_take_sk(
&mut (GenericArray::<_, G::SkLen>::deserialize(deserializer)?.as_slice()),
)
.map(Self)
.map_err(D::Error::custom)
}
}
#[cfg(feature = "serde")]
impl<G: Group> serde::Serialize for PrivateKey<G> {
fn serialize<SK>(&self, serializer: SK) -> Result<SK::Ok, SK::Error>
where
SK: serde::Serializer,
{
G::serialize_sk(self.0).serialize(serializer)
}
}
/// Wrapper around a Key to enforce that it's a public one.
#[derive_where(Clone, ZeroizeOnDrop)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; G::Pk)]
pub struct PublicKey<G: Group + ?Sized>(G::Pk);
impl<G: Group> PublicKey<G> {
/// Convert from bytes
pub fn deserialize(mut key_bytes: &[u8]) -> Result<Self, ProtocolError> {
Self::deserialize_take(&mut key_bytes)
}
pub(crate) fn deserialize_take(key_bytes: &mut &[u8]) -> Result<Self, ProtocolError> {
G::deserialize_take_pk(key_bytes).map(Self)
}
/// Convert to bytes
pub fn serialize(&self) -> GenericArray<u8, G::PkLen> {
G::serialize_pk(self.0)
}
/// Returns the inner [`Group::Pk`].
pub fn to_group_type(&self) -> G::Pk {
self.0
}
}
impl<G: Group> PublicKey<G> {
/// Public-key verifying implementation
pub(crate) fn verify<CS: CipherSuite, SIG: SignatureProtocol<Group = G>, KE: Group>(
&self,
message_builder: MessageBuilder<'_, CS>,
state: SIG::VerifyState<CS, KE>,
signature: &SIG::Signature,
) -> Result<(), ProtocolError> {
SIG::verify(&self.0, message_builder, state, signature)
}
}
#[cfg(feature = "serde")]
impl<'de, G: Group> serde::Deserialize<'de> for PublicKey<G> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error;
G::deserialize_take_pk(
&mut (GenericArray::<_, G::PkLen>::deserialize(deserializer)?.as_slice()),
)
.map(Self)
.map_err(D::Error::custom)
}
}
#[cfg(feature = "serde")]
impl<G: Group> serde::Serialize for PublicKey<G> {
fn serialize<SK>(&self, serializer: SK) -> Result<SK::Ok, SK::Error>
where
SK: serde::Serializer,
{
G::serialize_pk(self.0).serialize(serializer)
}
}
/// Default OPRF seed container.
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound = "")
)]
#[derive_where(Clone, Debug, Eq, Hash, PartialEq, ZeroizeOnDrop)]
pub struct OprfSeed<H: OutputSizeUser>(pub(crate) Output<H>);
/// A trait to facilitate
/// [`ServerSetup::de/serialize`](crate::ServerSetup::serialize).
///
/// Will be called with `E` being [`PrivateKeySerialization::Error`].
pub trait OprfSeedSerialization<H, E>: Sized {
/// Serialization size in bytes.
type Len: ArrayLength<u8>;
/// Serialization into bytes
fn serialize(&self) -> GenericArray<u8, Self::Len>;
/// Deserialization from bytes
fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError<E>>;
}
impl<H: OutputSizeUser, E> OprfSeedSerialization<H, E> for OprfSeed<H> {
type Len = H::OutputSize;
fn serialize(&self) -> GenericArray<u8, Self::Len> {
self.0.clone()
}
fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError<E>> {
Ok(Self(
input
.take_array("OPRF seed")
.map_err(ProtocolError::into_custom)?,
))
}
}
//////////////////////////
// Test Implementations //
//===================== //
//////////////////////////
#[cfg(test)]
use crate::serialization::AssertZeroized;
#[cfg(test)]
impl<G: Group> KeyPair<G> {
/// Test-only strategy returning a proptest Strategy based on
/// [`Self::generate_random`]
fn uniform_keypair_strategy<CS: voprf::CipherSuite>() -> proptest::prelude::BoxedStrategy<Self>
{
/// [`Self::derive_random`]
fn uniform_keypair_strategy() -> proptest::prelude::BoxedStrategy<Self> {
use proptest::prelude::*;
use rand::rngs::StdRng;
use rand::SeedableRng;
@@ -86,158 +322,61 @@ where
any::<[u8; 32]>()
.prop_filter_map("valid random keypair", |seed| {
let mut rng = StdRng::from_seed(seed);
Some(Self::generate_random::<CS, _>(&mut rng))
Some(Self::derive_random(&mut rng))
})
.no_shrink()
.boxed()
}
}
/// Wrapper around a Key to enforce that it's a private one.
#[derive_where(Clone, ZeroizeOnDrop)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; KG::Sk)]
pub struct PrivateKey<KG: KeGroup>(KG::Sk);
impl<KG: KeGroup> PrivateKey<KG> {
/// Returns public key from private key
pub fn public_key(&self) -> PublicKey<KG> {
PublicKey(KG::public_key(self.0))
}
pub(crate) fn serialize(&self) -> GenericArray<u8, KG::SkLen> {
KG::serialize_sk(self.0)
}
/// Creates a [`PrivateKey`] from the given bytes.
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
KG::deserialize_sk(input).map(Self)
}
}
impl<KG: KeGroup> PrivateKey<KG>
#[cfg(test)]
impl<G: Group> AssertZeroized for PublicKey<G>
where
KG::Sk: DiffieHellman<KG>,
G::Pk: AssertZeroized,
{
/// Diffie-Hellman key exchange implementation
pub(crate) fn ke_diffie_hellman(&self, pk: &PublicKey<KG>) -> GenericArray<u8, KG::PkLen> {
self.0.diffie_hellman(pk.0)
fn assert_zeroized(&self) {
self.0.assert_zeroized();
}
}
/// A trait to facilitate
/// [`ServerSetup::de/serialize`](crate::ServerSetup::serialize).
pub trait PrivateKeySerialization<KG: KeGroup>: Clone {
/// Custom error type that can be passed down to `ProtocolError::Custom`
type Error;
/// Serialization size in bytes.
type Len: ArrayLength<u8>;
/// Serialization into bytes
fn serialize_key_pair(key_pair: &KeyPair<KG, Self>) -> GenericArray<u8, Self::Len>;
/// Deserialization from bytes
fn deserialize_key_pair(input: &[u8]) -> Result<KeyPair<KG, Self>, ProtocolError<Self::Error>>;
}
impl<KG: KeGroup> PrivateKeySerialization<KG> for PrivateKey<KG> {
type Error = core::convert::Infallible;
type Len = KG::SkLen;
fn serialize_key_pair(key_pair: &KeyPair<KG, Self>) -> GenericArray<u8, Self::Len> {
key_pair.private().serialize()
}
fn deserialize_key_pair(input: &[u8]) -> Result<KeyPair<KG, Self>, ProtocolError> {
let sk = PrivateKey::deserialize(input)?;
let pk = sk.public_key();
Ok(KeyPair::new(sk, pk))
}
}
#[cfg(feature = "serde")]
impl<'de, KG: KeGroup> serde::Deserialize<'de> for PrivateKey<KG> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
#[cfg(test)]
impl<G: Group> AssertZeroized for PrivateKey<G>
where
D: serde::Deserializer<'de>,
G::Sk: AssertZeroized,
{
use serde::de::Error;
KG::deserialize_sk(&GenericArray::<_, KG::SkLen>::deserialize(deserializer)?)
.map(Self)
.map_err(D::Error::custom)
}
}
#[cfg(feature = "serde")]
impl<KG: KeGroup> serde::Serialize for PrivateKey<KG> {
fn serialize<SK>(&self, serializer: SK) -> Result<SK::Ok, SK::Error>
where
SK: serde::Serializer,
{
KG::serialize_sk(self.0).serialize(serializer)
}
}
/// Wrapper around a Key to enforce that it's a public one.
#[derive_where(Clone, ZeroizeOnDrop)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; KG::Pk)]
pub struct PublicKey<KG: KeGroup>(KG::Pk);
impl<KG: KeGroup> PublicKey<KG> {
/// Convert from bytes
pub fn deserialize(key_bytes: &[u8]) -> Result<Self, ProtocolError> {
KG::deserialize_pk(key_bytes).map(Self)
}
/// Convert to bytes
pub fn serialize(&self) -> GenericArray<u8, KG::PkLen> {
KG::serialize_pk(self.0)
}
/// Returns the inner [`KeGroup::Pk`].
pub fn to_group_type(&self) -> KG::Pk {
self.0
}
}
#[cfg(feature = "serde")]
impl<'de, KG: KeGroup> serde::Deserialize<'de> for PublicKey<KG> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error;
KG::deserialize_pk(&GenericArray::<_, KG::PkLen>::deserialize(deserializer)?)
.map(Self)
.map_err(D::Error::custom)
}
}
#[cfg(feature = "serde")]
impl<KG: KeGroup> serde::Serialize for PublicKey<KG> {
fn serialize<SK>(&self, serializer: SK) -> Result<SK::Ok, SK::Error>
where
SK: serde::Serializer,
{
KG::serialize_pk(self.0).serialize(serializer)
fn assert_zeroized(&self) {
self.0.assert_zeroized();
}
}
#[cfg(test)]
mod tests {
use core::ptr;
use hkdf::Hkdf;
use rand::rngs::OsRng;
use super::*;
use crate::util;
use crate::ciphersuite::{KeGroup, OprfHash};
use crate::serialization::AssertZeroized;
use crate::{
CipherSuite, ClientLogin, ClientLoginFinishParameters, ClientLoginFinishResult,
ClientLoginStartResult, ClientRegistration, ClientRegistrationFinishParameters,
ClientRegistrationFinishResult, ClientRegistrationStartResult, ServerLogin,
ServerLoginParameters, ServerLoginStartResult, ServerRegistration,
ServerRegistrationStartResult, ServerSetup,
};
#[test]
fn test_zeroize_key() {
fn inner<G: KeGroup>() {
fn inner<G: Group>()
where
G::Sk: AssertZeroized,
{
let mut rng = OsRng;
let mut key = PrivateKey::<G>(G::random_sk(&mut rng));
util::test_zeroize_on_drop(&mut key);
unsafe { ptr::drop_in_place(&mut key) };
key.0.assert_zeroized();
}
#[cfg(feature = "ristretto255")]
@@ -245,6 +384,10 @@ mod tests {
inner::<::p256::NistP256>();
inner::<::p384::NistP384>();
inner::<::p521::NistP521>();
#[cfg(feature = "curve25519")]
inner::<crate::Curve25519>();
#[cfg(feature = "ed25519")]
inner::<crate::Ed25519>();
}
macro_rules! test {
@@ -258,15 +401,15 @@ mod tests {
proptest! {
#[test]
fn pub_from_priv(kp in KeyPair::<$point>::uniform_keypair_strategy::<$point>()) {
fn pub_from_priv(kp in KeyPair::<$point>::uniform_keypair_strategy()) {
let pk = kp.public();
let sk = kp.private();
prop_assert_eq!(&sk.public_key(), pk);
}
#[test]
fn dh(kp1 in KeyPair::<$point>::uniform_keypair_strategy::<$point>(),
kp2 in KeyPair::<$point>::uniform_keypair_strategy::<$point>()) {
fn dh(kp1 in KeyPair::<$point>::uniform_keypair_strategy(),
kp2 in KeyPair::<$point>::uniform_keypair_strategy()) {
let dh1 = kp2.private().ke_diffie_hellman(&kp1.public());
let dh2 = kp1.private().ke_diffie_hellman(kp2.public());
@@ -275,10 +418,10 @@ mod tests {
}
#[test]
fn private_key_slice(kp in KeyPair::<$point>::uniform_keypair_strategy::<$point>()) {
fn private_key_slice(kp in KeyPair::<$point>::uniform_keypair_strategy()) {
let sk_bytes = kp.private().serialize().to_vec();
let kp2 = PrivateKey::<$point>::deserialize_key_pair(&sk_bytes)?;
let kp2 = PrivateKey::<$point>::deserialize_take_key_pair(&mut (sk_bytes.as_slice()))?;
let kp2_private_bytes = kp2.private().serialize().to_vec();
prop_assert_eq!(sk_bytes, kp2_private_bytes);
@@ -294,18 +437,6 @@ mod tests {
test!(p384, ::p384::NistP384);
test!(p521, ::p521::NistP521);
#[test]
fn remote_key() {
use rand::rngs::OsRng;
use crate::{
CipherSuite, ClientLogin, ClientLoginFinishParameters, ClientLoginFinishResult,
ClientLoginStartResult, ClientRegistration, ClientRegistrationFinishParameters,
ClientRegistrationFinishResult, ClientRegistrationStartResult, ServerLogin,
ServerLoginStartParameters, ServerLoginStartResult, ServerRegistration,
ServerRegistrationStartResult, ServerSetup,
};
struct Default;
impl CipherSuite for Default {
@@ -314,21 +445,23 @@ mod tests {
#[cfg(not(feature = "ristretto255"))]
type OprfCs = ::p256::NistP256;
#[cfg(feature = "ristretto255")]
type KeGroup = crate::Ristretto255;
type KeyExchange = crate::TripleDh<crate::Ristretto255, sha2::Sha512>;
#[cfg(not(feature = "ristretto255"))]
type KeGroup = ::p256::NistP256;
type KeyExchange = crate::key_exchange::tripledh::TripleDh;
type KeyExchange = crate::TripleDh<::p256::NistP256, sha2::Sha256>;
type Ksf = crate::ksf::Identity;
}
type KeCurve = <Default as CipherSuite>::KeGroup;
#[derive(Clone)]
struct RemoteSeed<H: OutputSizeUser>(Output<H>);
#[derive(Clone)]
struct RemoteKey(PrivateKey<KeCurve>);
struct RemoteKey(PrivateKey<KeGroup<Default>>);
const PASSWORD: &str = "password";
let sk = PrivateKey(KeCurve::random_sk(&mut OsRng));
#[test]
fn remote_key() {
let sk = PrivateKey(KeGroup::<Default>::random_sk(&mut OsRng));
let pk = sk.public_key();
let sk = RemoteKey(sk);
let keypair = KeyPair::new(sk, pk);
@@ -362,7 +495,7 @@ mod tests {
Some(file),
message,
&[],
ServerLoginStartParameters::default(),
ServerLoginParameters::default(),
)
.unwrap();
let shared_secret = builder.private_key().0.ke_diffie_hellman(builder.data());
@@ -373,11 +506,88 @@ mod tests {
} = builder.build(shared_secret).unwrap();
let ClientLoginFinishResult { message, .. } = client
.finish(
&mut OsRng,
PASSWORD.as_bytes(),
message,
ClientLoginFinishParameters::default(),
)
.unwrap();
server.finish(message).unwrap();
server
.finish(message, ServerLoginParameters::default())
.unwrap();
}
#[test]
fn remote_seed() {
let mut oprf_seed = RemoteSeed::<OprfHash<Default>>(GenericArray::default());
OsRng.fill_bytes(&mut oprf_seed.0);
let sk = PrivateKey(KeGroup::<Default>::random_sk(&mut OsRng));
let pk = sk.public_key();
let sk = RemoteKey(sk);
let keypair = KeyPair::new(sk, pk);
let server_setup = ServerSetup::<Default, _, _>::new_with_key_pair_and_seed(
&mut OsRng, keypair, oprf_seed,
);
let ClientRegistrationStartResult {
message,
state: client,
} = ClientRegistration::<Default>::start(&mut OsRng, PASSWORD.as_bytes()).unwrap();
let km = server_setup.key_material_info(&[]);
let mut ikm = GenericArray::default();
Hkdf::<OprfHash<Default>>::from_prk(&km.ikm.0)
.unwrap()
.expand_multi_info(&km.info, &mut ikm)
.unwrap();
let ServerRegistrationStartResult { message, .. } =
ServerRegistration::start_with_key_material(&server_setup, ikm, message).unwrap();
let ClientRegistrationFinishResult { message, .. } = client
.finish(
&mut OsRng,
PASSWORD.as_bytes(),
message,
ClientRegistrationFinishParameters::default(),
)
.unwrap();
let file = ServerRegistration::finish(message);
let ClientLoginStartResult {
message,
state: client,
} = ClientLogin::<Default>::start(&mut OsRng, PASSWORD.as_bytes()).unwrap();
let km = server_setup.key_material_info(&[]);
let mut ikm = GenericArray::default();
Hkdf::<OprfHash<Default>>::from_prk(&km.ikm.0)
.unwrap()
.expand_multi_info(&km.info, &mut ikm)
.unwrap();
let builder = ServerLogin::builder_with_key_material(
&mut OsRng,
&server_setup,
ikm,
Some(file),
message,
ServerLoginParameters::default(),
)
.unwrap();
let shared_secret = builder.private_key().0.ke_diffie_hellman(builder.data());
let ServerLoginStartResult {
message,
state: server,
..
} = builder.build(shared_secret).unwrap();
let ClientLoginFinishResult { message, .. } = client
.finish(
&mut OsRng,
PASSWORD.as_bytes(),
message,
ClientLoginFinishParameters::default(),
)
.unwrap();
server
.finish(message, ServerLoginParameters::default())
.unwrap();
}
}
+301 -120
View File
@@ -32,11 +32,12 @@
//! We will use the following choices in this example:
//! ```ignore
//! use opaque_ke::CipherSuite;
//!
//! struct Default;
//!
//! impl CipherSuite for Default {
//! type OprfCs = opaque_ke::Ristretto255;
//! type KeGroup = opaque_ke::Ristretto255;
//! type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
//! type KeyExchange = opaque_ke::TripleDh<opaque_ke::Ristretto255, sha2::Sha512>;
//! type Ksf = opaque_ke::ksf::Identity;
//! }
//! ```
@@ -60,19 +61,18 @@
//! # #[cfg(feature = "ristretto255")]
//! # impl CipherSuite for Default {
//! # type OprfCs = opaque_ke::Ristretto255;
//! # type KeGroup = opaque_ke::Ristretto255;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
//! # type KeyExchange = opaque_ke::TripleDh<opaque_ke::Ristretto255, sha2::Sha512>;
//! # type Ksf = opaque_ke::ksf::Identity;
//! # }
//! # #[cfg(not(feature = "ristretto255"))]
//! # impl CipherSuite for Default {
//! # type OprfCs = p256::NistP256;
//! # type KeGroup = p256::NistP256;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
//! # type KeyExchange = opaque_ke::TripleDh<p256::NistP256, sha2::Sha256>;
//! # type Ksf = opaque_ke::ksf::Identity;
//! # }
//! use rand::rngs::OsRng;
//! use rand::RngCore;
//!
//! let mut rng = OsRng;
//! let server_setup = ServerSetup::<Default>::new(&mut rng);
//! # Ok::<(), ProtocolError>(())
@@ -117,20 +117,19 @@
//! # #[cfg(feature = "ristretto255")]
//! # impl CipherSuite for Default {
//! # type OprfCs = opaque_ke::Ristretto255;
//! # type KeGroup = opaque_ke::Ristretto255;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
//! # type KeyExchange = opaque_ke::TripleDh<opaque_ke::Ristretto255, sha2::Sha512>;
//! # type Ksf = opaque_ke::ksf::Identity;
//! # }
//! # #[cfg(not(feature = "ristretto255"))]
//! # impl CipherSuite for Default {
//! # type OprfCs = p256::NistP256;
//! # type KeGroup = p256::NistP256;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
//! # type KeyExchange = opaque_ke::TripleDh<p256::NistP256, sha2::Sha256>;
//! # type Ksf = opaque_ke::ksf::Identity;
//! # }
//! use opaque_ke::ClientRegistration;
//! use rand::rngs::OsRng;
//! use rand::RngCore;
//!
//! let mut client_rng = OsRng;
//! let client_registration_start_result =
//! ClientRegistration::<Default>::start(&mut client_rng, b"password")?;
@@ -156,15 +155,13 @@
//! # #[cfg(feature = "ristretto255")]
//! # impl CipherSuite for Default {
//! # type OprfCs = opaque_ke::Ristretto255;
//! # type KeGroup = opaque_ke::Ristretto255;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
//! # type KeyExchange = opaque_ke::TripleDh<opaque_ke::Ristretto255, sha2::Sha512>;
//! # type Ksf = opaque_ke::ksf::Identity;
//! # }
//! # #[cfg(not(feature = "ristretto255"))]
//! # impl CipherSuite for Default {
//! # type OprfCs = p256::NistP256;
//! # type KeGroup = p256::NistP256;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
//! # type KeyExchange = opaque_ke::TripleDh<p256::NistP256, sha2::Sha256>;
//! # type Ksf = opaque_ke::ksf::Identity;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
@@ -174,6 +171,7 @@
//! # b"password",
//! # )?;
//! use opaque_ke::ServerRegistration;
//!
//! # let mut server_rng = OsRng;
//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
//! let server_registration_start_result = ServerRegistration::<Default>::start(
@@ -196,7 +194,7 @@
//! ```
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ServerSetup,
//! # ClientRegistration, ServerRegistration, ServerSetup,
//! # ksf::Identity,
//! # };
//! # use opaque_ke::CipherSuite;
@@ -204,15 +202,13 @@
//! # #[cfg(feature = "ristretto255")]
//! # impl CipherSuite for Default {
//! # type OprfCs = opaque_ke::Ristretto255;
//! # type KeGroup = opaque_ke::Ristretto255;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
//! # type KeyExchange = opaque_ke::TripleDh<opaque_ke::Ristretto255, sha2::Sha512>;
//! # type Ksf = opaque_ke::ksf::Identity;
//! # }
//! # #[cfg(not(feature = "ristretto255"))]
//! # impl CipherSuite for Default {
//! # type OprfCs = p256::NistP256;
//! # type KeGroup = p256::NistP256;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
//! # type KeyExchange = opaque_ke::TripleDh<p256::NistP256, sha2::Sha256>;
//! # type Ksf = opaque_ke::ksf::Identity;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
@@ -224,6 +220,8 @@
//! # let mut server_rng = OsRng;
//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&server_setup, client_registration_start_result.message, b"[email protected]")?;
//! use opaque_ke::ClientRegistrationFinishParameters;
//!
//! let client_registration_finish_result = client_registration_start_result.state.finish(
//! &mut client_rng,
//! b"password",
@@ -252,15 +250,13 @@
//! # #[cfg(feature = "ristretto255")]
//! # impl CipherSuite for Default {
//! # type OprfCs = opaque_ke::Ristretto255;
//! # type KeGroup = opaque_ke::Ristretto255;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
//! # type KeyExchange = opaque_ke::TripleDh<opaque_ke::Ristretto255, sha2::Sha512>;
//! # type Ksf = opaque_ke::ksf::Identity;
//! # }
//! # #[cfg(not(feature = "ristretto255"))]
//! # impl CipherSuite for Default {
//! # type OprfCs = p256::NistP256;
//! # type KeGroup = p256::NistP256;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
//! # type KeyExchange = opaque_ke::TripleDh<p256::NistP256, sha2::Sha256>;
//! # type Ksf = opaque_ke::ksf::Identity;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
@@ -305,19 +301,18 @@
//! # #[cfg(feature = "ristretto255")]
//! # impl CipherSuite for Default {
//! # type OprfCs = opaque_ke::Ristretto255;
//! # type KeGroup = opaque_ke::Ristretto255;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
//! # type KeyExchange = opaque_ke::TripleDh<opaque_ke::Ristretto255, sha2::Sha512>;
//! # type Ksf = opaque_ke::ksf::Identity;
//! # }
//! # #[cfg(not(feature = "ristretto255"))]
//! # impl CipherSuite for Default {
//! # type OprfCs = p256::NistP256;
//! # type KeGroup = p256::NistP256;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
//! # type KeyExchange = opaque_ke::TripleDh<p256::NistP256, sha2::Sha256>;
//! # type Ksf = opaque_ke::ksf::Identity;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
//! use opaque_ke::ClientLogin;
//!
//! let mut client_rng = OsRng;
//! let client_login_start_result = ClientLogin::<Default>::start(&mut client_rng, b"password")?;
//! # Ok::<(), ProtocolError>(())
@@ -342,15 +337,13 @@
//! # #[cfg(feature = "ristretto255")]
//! # impl CipherSuite for Default {
//! # type OprfCs = opaque_ke::Ristretto255;
//! # type KeGroup = opaque_ke::Ristretto255;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
//! # type KeyExchange = opaque_ke::TripleDh<opaque_ke::Ristretto255, sha2::Sha512>;
//! # type Ksf = opaque_ke::ksf::Identity;
//! # }
//! # #[cfg(not(feature = "ristretto255"))]
//! # impl CipherSuite for Default {
//! # type OprfCs = p256::NistP256;
//! # type KeGroup = p256::NistP256;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
//! # type KeyExchange = opaque_ke::TripleDh<p256::NistP256, sha2::Sha256>;
//! # type Ksf = opaque_ke::ksf::Identity;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
@@ -368,7 +361,8 @@
//! # &mut client_rng,
//! # b"password",
//! # )?;
//! use opaque_ke::{ServerLogin, ServerLoginStartParameters};
//! use opaque_ke::{ServerLogin, ServerLoginParameters};
//!
//! let password_file = ServerRegistration::<Default>::deserialize(&password_file_bytes)?;
//! let mut server_rng = OsRng;
//! let server_login_start_result = ServerLogin::start(
@@ -377,7 +371,7 @@
//! Some(password_file),
//! client_login_start_result.message,
//! b"[email protected]",
//! ServerLoginStartParameters::default(),
//! ServerLoginParameters::default(),
//! )?;
//! # Ok::<(), ProtocolError>(())
//! ```
@@ -405,7 +399,7 @@
//! ```
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization, ServerSetup,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ServerLogin, ServerLoginParameters, CredentialFinalization, ServerSetup,
//! # ksf::Identity,
//! # };
//! # use opaque_ke::CipherSuite;
@@ -413,15 +407,13 @@
//! # #[cfg(feature = "ristretto255")]
//! # impl CipherSuite for Default {
//! # type OprfCs = opaque_ke::Ristretto255;
//! # type KeGroup = opaque_ke::Ristretto255;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
//! # type KeyExchange = opaque_ke::TripleDh<opaque_ke::Ristretto255, sha2::Sha512>;
//! # type Ksf = opaque_ke::ksf::Identity;
//! # }
//! # #[cfg(not(feature = "ristretto255"))]
//! # impl CipherSuite for Default {
//! # type OprfCs = p256::NistP256;
//! # type KeGroup = p256::NistP256;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
//! # type KeyExchange = opaque_ke::TripleDh<p256::NistP256, sha2::Sha256>;
//! # type Ksf = opaque_ke::ksf::Identity;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
@@ -444,8 +436,11 @@
//! # &password_file_bytes,
//! # )?;
//! # let server_login_start_result =
//! # ServerLogin::start(&mut server_rng, &server_setup, Some(password_file), client_login_start_result.message, b"[email protected]", ServerLoginStartParameters::default())?;
//! # ServerLogin::start(&mut server_rng, &server_setup, Some(password_file), client_login_start_result.message, b"[email protected]", ServerLoginParameters::default())?;
//! use opaque_ke::ClientLoginFinishParameters;
//!
//! let client_login_finish_result = client_login_start_result.state.finish(
//! &mut client_rng,
//! b"password",
//! server_login_start_result.message,
//! ClientLoginFinishParameters::default(),
@@ -461,7 +456,7 @@
//! ```
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization, ServerSetup,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginFinishParameters, ServerLogin, ServerLoginParameters, CredentialFinalization, ServerSetup,
//! # ksf::Identity,
//! # };
//! # use opaque_ke::CipherSuite;
@@ -469,15 +464,13 @@
//! # #[cfg(feature = "ristretto255")]
//! # impl CipherSuite for Default {
//! # type OprfCs = opaque_ke::Ristretto255;
//! # type KeGroup = opaque_ke::Ristretto255;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
//! # type KeyExchange = opaque_ke::TripleDh<opaque_ke::Ristretto255, sha2::Sha512>;
//! # type Ksf = opaque_ke::ksf::Identity;
//! # }
//! # #[cfg(not(feature = "ristretto255"))]
//! # impl CipherSuite for Default {
//! # type OprfCs = p256::NistP256;
//! # type KeGroup = p256::NistP256;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
//! # type KeyExchange = opaque_ke::TripleDh<p256::NistP256, sha2::Sha256>;
//! # type Ksf = opaque_ke::ksf::Identity;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
@@ -500,14 +493,16 @@
//! # &password_file_bytes,
//! # )?;
//! # let server_login_start_result =
//! # ServerLogin::start(&mut server_rng, &server_setup, Some(password_file), client_login_start_result.message, b"[email protected]", ServerLoginStartParameters::default())?;
//! # ServerLogin::start(&mut server_rng, &server_setup, Some(password_file), client_login_start_result.message, b"[email protected]", ServerLoginParameters::default())?;
//! # let client_login_finish_result = client_login_start_result.state.finish(
//! # &mut client_rng,
//! # b"password",
//! # server_login_start_result.message,
//! # ClientLoginFinishParameters::default(),
//! # )?;
//! let server_login_finish_result = server_login_start_result.state.finish(
//! client_login_finish_result.message,
//! ServerLoginParameters::default(),
//! )?;
//!
//! assert_eq!(
@@ -558,7 +553,7 @@
//! ```
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization, ServerSetup,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginFinishParameters, ServerLogin, ServerLoginParameters, CredentialFinalization, ServerSetup,
//! # ksf::Identity,
//! # };
//! # use opaque_ke::CipherSuite;
@@ -566,15 +561,13 @@
//! # #[cfg(feature = "ristretto255")]
//! # impl CipherSuite for Default {
//! # type OprfCs = opaque_ke::Ristretto255;
//! # type KeGroup = opaque_ke::Ristretto255;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
//! # type KeyExchange = opaque_ke::TripleDh<opaque_ke::Ristretto255, sha2::Sha512>;
//! # type Ksf = opaque_ke::ksf::Identity;
//! # }
//! # #[cfg(not(feature = "ristretto255"))]
//! # impl CipherSuite for Default {
//! # type OprfCs = p256::NistP256;
//! # type KeGroup = p256::NistP256;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
//! # type KeyExchange = opaque_ke::TripleDh<p256::NistP256, sha2::Sha256>;
//! # type Ksf = opaque_ke::ksf::Identity;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
@@ -604,10 +597,11 @@
//! # &password_file_bytes,
//! # )?;
//! # let server_login_start_result =
//! # ServerLogin::start(&mut server_rng, &server_setup, Some(password_file), client_login_start_result.message, b"[email protected]", ServerLoginStartParameters::default())?;
//! # ServerLogin::start(&mut server_rng, &server_setup, Some(password_file), client_login_start_result.message, b"[email protected]", ServerLoginParameters::default())?;
//!
//! // And then later, during login...
//! let client_login_finish_result = client_login_start_result.state.finish(
//! &mut client_rng,
//! b"password",
//! server_login_start_result.message,
//! ClientLoginFinishParameters::default(),
@@ -656,7 +650,7 @@
//! ```
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginFinishParameters, ServerLogin, ServerLoginStartParameters, CredentialFinalization, ServerSetup,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginFinishParameters, ServerLogin, ServerLoginParameters, CredentialFinalization, ServerSetup,
//! # ksf::Identity,
//! # };
//! # use opaque_ke::CipherSuite;
@@ -664,15 +658,13 @@
//! # #[cfg(feature = "ristretto255")]
//! # impl CipherSuite for Default {
//! # type OprfCs = opaque_ke::Ristretto255;
//! # type KeGroup = opaque_ke::Ristretto255;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
//! # type KeyExchange = opaque_ke::TripleDh<opaque_ke::Ristretto255, sha2::Sha512>;
//! # type Ksf = opaque_ke::ksf::Identity;
//! # }
//! # #[cfg(not(feature = "ristretto255"))]
//! # impl CipherSuite for Default {
//! # type OprfCs = p256::NistP256;
//! # type KeGroup = p256::NistP256;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
//! # type KeyExchange = opaque_ke::TripleDh<p256::NistP256, sha2::Sha256>;
//! # type Ksf = opaque_ke::ksf::Identity;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
@@ -701,10 +693,11 @@
//! # &password_file_bytes,
//! # )?;
//! # let server_login_start_result =
//! # ServerLogin::start(&mut server_rng, &server_setup, Some(password_file), client_login_start_result.message, b"[email protected]", ServerLoginStartParameters::default())?;
//! # ServerLogin::start(&mut server_rng, &server_setup, Some(password_file), client_login_start_result.message, b"[email protected]", ServerLoginParameters::default())?;
//!
//! // And then later, during login...
//! let client_login_finish_result = client_login_start_result.state.finish(
//! &mut client_rng,
//! b"password",
//! server_login_start_result.message,
//! ClientLoginFinishParameters::default(),
@@ -745,15 +738,13 @@
//! # #[cfg(feature = "ristretto255")]
//! # impl CipherSuite for Default {
//! # type OprfCs = opaque_ke::Ristretto255;
//! # type KeGroup = opaque_ke::Ristretto255;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
//! # type KeyExchange = opaque_ke::TripleDh<opaque_ke::Ristretto255, sha2::Sha512>;
//! # type Ksf = opaque_ke::ksf::Identity;
//! # }
//! # #[cfg(not(feature = "ristretto255"))]
//! # impl CipherSuite for Default {
//! # type OprfCs = p256::NistP256;
//! # type KeGroup = p256::NistP256;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
//! # type KeyExchange = opaque_ke::TripleDh<p256::NistP256, sha2::Sha256>;
//! # type Ksf = opaque_ke::ksf::Identity;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
@@ -780,8 +771,8 @@
//! # Ok::<(), ProtocolError>(())
//! ```
//!
//! The same identifiers must also be supplied using
//! [`ServerLoginStartParameters`] in [Server Login Start](#server-login-start):
//! The same identifiers must also be supplied using [`ServerLoginParameters`]
//! in [Server Login Start](#server-login-start):
//! ```
//! # use opaque_ke::{
//! # errors::ProtocolError,
@@ -793,15 +784,13 @@
//! # #[cfg(feature = "ristretto255")]
//! # impl CipherSuite for Default {
//! # type OprfCs = opaque_ke::Ristretto255;
//! # type KeGroup = opaque_ke::Ristretto255;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
//! # type KeyExchange = opaque_ke::TripleDh<opaque_ke::Ristretto255, sha2::Sha512>;
//! # type Ksf = opaque_ke::ksf::Identity;
//! # }
//! # #[cfg(not(feature = "ristretto255"))]
//! # impl CipherSuite for Default {
//! # type OprfCs = p256::NistP256;
//! # type KeGroup = p256::NistP256;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
//! # type KeyExchange = opaque_ke::TripleDh<p256::NistP256, sha2::Sha256>;
//! # type Ksf = opaque_ke::ksf::Identity;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
@@ -819,7 +808,7 @@
//! # &mut client_rng,
//! # b"password",
//! # )?;
//! # use opaque_ke::{ServerLogin, ServerLoginStartParameters};
//! # use opaque_ke::{ServerLogin, ServerLoginParameters};
//! # let password_file = ServerRegistration::<Default>::deserialize(&password_file_bytes)?;
//! # let mut server_rng = OsRng;
//! let server_login_start_result = ServerLogin::start(
@@ -828,7 +817,7 @@
//! Some(password_file),
//! client_login_start_result.message,
//! b"[email protected]",
//! ServerLoginStartParameters {
//! ServerLoginParameters {
//! context: None,
//! identifiers: Identifiers {
//! client: Some(b"Alice_the_Cryptographer"),
@@ -844,7 +833,7 @@
//! ```
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginFinishParameters, Identifiers, ServerLogin, ServerLoginStartParameters, CredentialFinalization, ServerSetup,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginFinishParameters, Identifiers, ServerLogin, ServerLoginParameters, CredentialFinalization, ServerSetup,
//! # ksf::Identity,
//! # };
//! # use opaque_ke::CipherSuite;
@@ -852,15 +841,13 @@
//! # #[cfg(feature = "ristretto255")]
//! # impl CipherSuite for Default {
//! # type OprfCs = opaque_ke::Ristretto255;
//! # type KeGroup = opaque_ke::Ristretto255;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
//! # type KeyExchange = opaque_ke::TripleDh<opaque_ke::Ristretto255, sha2::Sha512>;
//! # type Ksf = opaque_ke::ksf::Identity;
//! # }
//! # #[cfg(not(feature = "ristretto255"))]
//! # impl CipherSuite for Default {
//! # type OprfCs = p256::NistP256;
//! # type KeGroup = p256::NistP256;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
//! # type KeyExchange = opaque_ke::TripleDh<p256::NistP256, sha2::Sha256>;
//! # type Ksf = opaque_ke::ksf::Identity;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
@@ -883,8 +870,9 @@
//! # &password_file_bytes,
//! # )?;
//! # let server_login_start_result =
//! # ServerLogin::start(&mut server_rng, &server_setup, Some(password_file), client_login_start_result.message, b"[email protected]", ServerLoginStartParameters { context: None, identifiers: Identifiers { client: Some(b"Alice_the_Cryptographer"), server: Some(b"Facebook") } })?;
//! # ServerLogin::start(&mut server_rng, &server_setup, Some(password_file), client_login_start_result.message, b"[email protected]", ServerLoginParameters { context: None, identifiers: Identifiers { client: Some(b"Alice_the_Cryptographer"), server: Some(b"Facebook") } })?;
//! let client_login_finish_result = client_login_start_result.state.finish(
//! &mut client_rng,
//! b"password",
//! server_login_start_result.message,
//! ClientLoginFinishParameters::new(
@@ -899,6 +887,62 @@
//!
//! # Ok::<(), ProtocolError>(())
//! ```
//! and in [`ServerLoginParameters`] in [Server Login
//! Finish](#server-login-finish):
//! ```
//! # use opaque_ke::{
//! # errors::ProtocolError,
//! # ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, ClientLogin, ClientLoginFinishParameters, Identifiers, ServerLogin, ServerLoginParameters, CredentialFinalization, ServerSetup,
//! # ksf::Identity,
//! # };
//! # use opaque_ke::CipherSuite;
//! # struct Default;
//! # #[cfg(feature = "ristretto255")]
//! # impl CipherSuite for Default {
//! # type OprfCs = opaque_ke::Ristretto255;
//! # type KeyExchange = opaque_ke::TripleDh<opaque_ke::Ristretto255, sha2::Sha512>;
//! # type Ksf = opaque_ke::ksf::Identity;
//! # }
//! # #[cfg(not(feature = "ristretto255"))]
//! # impl CipherSuite for Default {
//! # type OprfCs = p256::NistP256;
//! # type KeyExchange = opaque_ke::TripleDh<p256::NistP256, sha2::Sha256>;
//! # type Ksf = opaque_ke::ksf::Identity;
//! # }
//! # use rand::{rngs::OsRng, RngCore};
//! # let mut client_rng = OsRng;
//! # let client_registration_start_result = ClientRegistration::<Default>::start(
//! # &mut client_rng,
//! # b"password",
//! # )?;
//! # let mut server_rng = OsRng;
//! # let server_setup = ServerSetup::<Default>::new(&mut server_rng);
//! # let server_registration_start_result = ServerRegistration::<Default>::start(&server_setup, client_registration_start_result.message, b"[email protected]")?;
//! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut client_rng, b"password", server_registration_start_result.message, ClientRegistrationFinishParameters::new(Identifiers { client: Some(b"Alice_the_Cryptographer"), server: Some(b"Facebook") }, None))?;
//! # let password_file_bytes = ServerRegistration::<Default>::finish(client_registration_finish_result.message).serialize();
//! # let client_login_start_result = ClientLogin::<Default>::start(
//! # &mut client_rng,
//! # b"password",
//! # )?;
//! # let password_file =
//! # ServerRegistration::<Default>::deserialize(
//! # &password_file_bytes,
//! # )?;
//! # let server_login_start_result =
//! # ServerLogin::start(&mut server_rng, &server_setup, Some(password_file), client_login_start_result.message, b"[email protected]", ServerLoginParameters { context: None, identifiers: Identifiers { client: Some(b"Alice_the_Cryptographer"), server: Some(b"Facebook") } })?;
//! # let client_login_finish_result = client_login_start_result.state.finish(
//! # &mut client_rng,
//! # b"password",
//! # server_login_start_result.message,
//! # ClientLoginFinishParameters::new(None, Identifiers { client: Some(b"Alice_the_Cryptographer"), server: Some(b"Facebook") }, None),
//! # )?;
//! let server_login_finish_result = server_login_start_result.state.finish(
//! client_login_finish_result.message,
//! ServerLoginParameters { context: None, identifiers: Identifiers { client: Some(b"Alice_the_Cryptographer"), server: Some(b"Facebook") } },
//! )?;
//!
//! # Ok::<(), ProtocolError>(())
//! ```
//! Failing to supply the same pair of custom identifiers in any of the three
//! steps above will result in an error in attempting to complete the protocol!
//!
@@ -912,16 +956,12 @@
//! complete, so as to bind the integrity of application-specific data or
//! configuration parameters to the security of the key exchange. During the
//! login phase, the client and server can specify this context using:
//! - The second login message, where the server can populate
//! [`ServerLoginStartParameters`], and
//! - The third login message, where the client can populate
//! [`ClientLoginFinishParameters`].
//!
//! For both of these messages, the `WithContextAndIdentifiers` variant can be
//! used to specify these fields in addition to [custom
//! identifiers](#custom-identifiers), with the ordering of the fields as
//! `WithContextAndIdentifiers(context,
//! Identifiers::ClientAndServerIdentifiers(username, server_name))`.
//! - In [Server Login Start](#server-login-start), where the server can
//! populate [`ServerLoginParameters::context`].
//! - In [Client Login Finish](#client-login-finish), where the client can
//! populate [`ClientLoginFinishParameters::context`].
//! - In [Server Login Finish](#server-login-finish), where the server can
//! populate [`ServerLoginParameters::context`].
//!
//! ## Dummy Server Login
//!
@@ -940,59 +980,59 @@
//! exposing the bytes of the private key to this library.
//! ```
//! # use generic_array::{GenericArray, typenum::U0};
//! # use opaque_ke::{CipherSuite, ClientLogin, ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, errors::ProtocolError, keypair::PrivateKey, key_exchange::{group::KeGroup as OKeGroup, tripledh::DiffieHellman}};
//! # use opaque_ke::{CipherSuite, ClientLogin, ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, keypair::{PrivateKey, PublicKey}, key_exchange::{KeyExchange, group::Group, tripledh::DiffieHellman}};
//! # use rand::rngs::OsRng;
//! # type Ristretto255 = <<Default as CipherSuite>::KeyExchange as KeyExchange>::Group;
//! # struct Default;
//! # #[cfg(feature = "ristretto255")]
//! # impl CipherSuite for Default {
//! # type OprfCs = opaque_ke::Ristretto255;
//! # type KeGroup = opaque_ke::Ristretto255;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
//! # type KeyExchange = opaque_ke::TripleDh<opaque_ke::Ristretto255, sha2::Sha512>;
//! # type Ksf = opaque_ke::ksf::Identity;
//! # }
//! # #[cfg(not(feature = "ristretto255"))]
//! # impl CipherSuite for Default {
//! # type OprfCs = p256::NistP256;
//! # type KeGroup = p256::NistP256;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
//! # type KeyExchange = opaque_ke::TripleDh<p256::NistP256, sha2::Sha256>;
//! # type Ksf = opaque_ke::ksf::Identity;
//! # }
//! # type KeGroup = <Default as CipherSuite>::KeGroup;
//! # #[derive(Debug, thiserror::Error)]
//! # #[error("test error")]
//! # struct YourRemoteKeyError;
//! # #[derive(Clone)]
//! # struct YourRemoteKey(<KeGroup as OKeGroup>::Sk);
//! # struct YourRemoteKey(<Ristretto255 as Group>::Sk);
//! # impl YourRemoteKey {
//! # fn diffie_hellman(&self, pk: &PublicKey<KeGroup>) -> Result<GenericArray<u8, <KeGroup as OKeGroup>::PkLen>, YourRemoteKeyError> {
//! # Ok(<<KeGroup as OKeGroup>::Sk as DiffieHellman<KeGroup>>::diffie_hellman(self.0, KeGroup::deserialize_pk(&pk.serialize()).unwrap()))
//! # fn diffie_hellman(&self, pk: &PublicKey<Ristretto255>) -> Result<GenericArray<u8, <Ristretto255 as Group>::PkLen>, YourRemoteKeyError> {
//! # Ok(<<Ristretto255 as Group>::Sk as DiffieHellman<Ristretto255>>::diffie_hellman(self.0, pk.to_group_type()))
//! # }
//! # }
//! use opaque_ke::{ServerLogin, ServerLoginStartParameters, ServerSetup};
//! use opaque_ke::keypair::{KeyPair, PrivateKeySerialization, PublicKey};
//! use opaque_ke::{ServerLogin, ServerLoginParameters, ServerSetup};
//! use opaque_ke::keypair::{KeyPair, PrivateKeySerialization};
//! use opaque_ke::errors::ProtocolError;
//!
//! // Implement if you intend to use `ServerSetup::de/serialize` instead of `serde`.
//! impl PrivateKeySerialization<KeGroup> for YourRemoteKey {
//! impl PrivateKeySerialization<Ristretto255> for YourRemoteKey {
//! type Error = YourRemoteKeyError;
//! type Len = U0;
//!
//! fn serialize_key_pair(_: &KeyPair<KeGroup, Self>) -> GenericArray<u8, Self::Len> {
//! fn serialize_key_pair(_: &KeyPair<Ristretto255, Self>) -> GenericArray<u8, Self::Len> {
//! unimplemented!()
//! }
//!
//! fn deserialize_key_pair(input: &[u8]) -> Result<KeyPair<KeGroup, Self>, ProtocolError<Self::Error>> {
//! fn deserialize_take_key_pair(input: &mut &[u8]) -> Result<KeyPair<Ristretto255, Self>, ProtocolError<Self::Error>> {
//! unimplemented!()
//! }
//! }
//!
//! # let sk = KeGroup::random_sk(&mut OsRng);
//! # let pk = KeGroup::public_key(sk);
//! # let pk = KeGroup::serialize_pk(pk);
//! # let sk = Ristretto255::random_sk(&mut OsRng);
//! # let pk = Ristretto255::public_key(sk);
//! # let pk = Ristretto255::serialize_pk(pk);
//! # let public_key = PublicKey::deserialize(&pk).unwrap();
//! # let remote_key = YourRemoteKey(sk);
//! # let mut server_rng = OsRng;
//! let keypair = KeyPair::new(remote_key, public_key);
//! let server_setup = ServerSetup::<Default, YourRemoteKey>::new_with_key_pair(&mut server_rng, keypair);
//!
//! # let client_registration_start_result = ClientRegistration::<Default>::start(
//! # &mut OsRng,
//! # b"password",
@@ -1004,17 +1044,152 @@
//! # &mut OsRng,
//! # b"password",
//! # )?;
//! let password_file = ServerRegistration::<Default>::deserialize(&password_file_bytes)?;
//! # let password_file = ServerRegistration::<Default>::deserialize(&password_file_bytes)?;
//! // Use `ServerLogin::builder()` instead of `ServerLogin::start()`.
//! let server_login_builder = ServerLogin::builder(
//! &mut server_rng,
//! &server_setup,
//! Some(password_file),
//! client_login_start_result.message,
//! b"[email protected]",
//! ServerLoginStartParameters::default(),
//! ServerLoginParameters::default(),
//! )?;
//!
//! // Run Diffie-Hellman on your remote key.
//! let client_e_public_key = server_login_builder.data();
//! let shared_secret = server_login_builder.private_key().diffie_hellman(&client_e_public_key)?;
//!
//! // Use the shared secret to build `ServerLogin`.
//! let server_login_start_result = server_login_builder.build(shared_secret)?;
//! # Ok::<(), anyhow::Error>(())
//! ```
//!
//! ## Remote OPRF Seeds
//!
//! In addition, the OPRF seed can be stored in an external location as well, by
//! using [`ServerRegistration::start_with_key_material()`] and
//! [`ServerLogin::builder_with_key_material()`] in combination with
//! [`ServerSetup::key_material_info()`].
//! ```
//! # use digest::Output;
//! # use generic_array::{GenericArray, typenum::U0};
//! # use hkdf::Hkdf;
//! # use opaque_ke::{CipherSuite, ClientLogin, ClientRegistration, ClientRegistrationFinishParameters, keypair::{PrivateKey, PublicKey}, key_exchange::{KeyExchange, group::Group, tripledh::DiffieHellman}};
//! # use rand::rngs::OsRng;
//! # use rand::RngCore;
//! # type Ristretto255 = <<Default as CipherSuite>::KeyExchange as KeyExchange>::Group;
//! # type Hash = <<Default as CipherSuite>::KeyExchange as KeyExchange>::Hash;
//! # type OprfGroup = <<Default as CipherSuite>::OprfCs as voprf::CipherSuite>::Group;
//! # struct Default;
//! # #[cfg(feature = "ristretto255")]
//! # impl CipherSuite for Default {
//! # type OprfCs = opaque_ke::Ristretto255;
//! # type KeyExchange = opaque_ke::TripleDh<opaque_ke::Ristretto255, sha2::Sha512>;
//! # type Ksf = opaque_ke::ksf::Identity;
//! # }
//! # #[cfg(not(feature = "ristretto255"))]
//! # impl CipherSuite for Default {
//! # type OprfCs = p256::NistP256;
//! # type KeyExchange = opaque_ke::TripleDh<p256::NistP256, sha2::Sha256>;
//! # type Ksf = opaque_ke::ksf::Identity;
//! # }
//! # #[derive(Debug, thiserror::Error)]
//! # #[error("test error")]
//! # struct YourRemoteSecretsError;
//! # #[derive(Clone)]
//! # struct YourRemoteSeed(Output<Hash>);
//! # impl YourRemoteSeed {
//! # fn hkdf(&self, info: &[&[u8]]) -> GenericArray<u8, <OprfGroup as voprf::Group>::ScalarLen> {
//! # let mut ikm = GenericArray::default();
//! # Hkdf::<Hash>::from_prk(&self.0)
//! # .unwrap()
//! # .expand_multi_info(info, &mut ikm)
//! # .unwrap();
//! # ikm
//! # }
//! # }
//! # #[derive(Clone)]
//! # struct YourRemoteKey(<Ristretto255 as Group>::Sk);
//! # impl YourRemoteKey {
//! # fn diffie_hellman(&self, pk: &PublicKey<Ristretto255>) -> Result<GenericArray<u8, <Ristretto255 as Group>::PkLen>, YourRemoteSecretsError> {
//! # Ok(<<Ristretto255 as Group>::Sk as DiffieHellman<Ristretto255>>::diffie_hellman(self.0, pk.to_group_type()))
//! # }
//! # }
//! use opaque_ke::{ServerLogin, ServerLoginParameters, ServerRegistration, ServerSetup};
//! use opaque_ke::keypair::{KeyPair, OprfSeedSerialization};
//! use opaque_ke::errors::ProtocolError;
//!
//! // Implement if you intend to use `ServerSetup::de/serialize` instead of `serde`.
//! impl OprfSeedSerialization<sha2::Sha512, YourRemoteSecretsError> for YourRemoteSeed {
//! type Len = U0;
//!
//! fn serialize(&self) -> GenericArray<u8, Self::Len> {
//! unimplemented!()
//! }
//!
//! fn deserialize_take(input: &mut &[u8]) -> Result<YourRemoteSeed, ProtocolError<YourRemoteSecretsError>> {
//! unimplemented!()
//! }
//! }
//!
//! # let mut oprf_seed = YourRemoteSeed(GenericArray::default());
//! # OsRng.fill_bytes(&mut oprf_seed.0);
//! # let sk = Ristretto255::random_sk(&mut OsRng);
//! # let pk = Ristretto255::public_key(sk);
//! # let pk = Ristretto255::serialize_pk(pk);
//! # let public_key = PublicKey::deserialize(&pk).unwrap();
//! # let remote_key = YourRemoteKey(sk);
//! # let mut server_rng = OsRng;
//! let keypair = KeyPair::new(remote_key, public_key);
//! let server_setup = ServerSetup::<Default, YourRemoteKey, YourRemoteSeed>::new_with_key_pair_and_seed(&mut server_rng, keypair, oprf_seed);
//!
//! // Incoming registration ...
//! # let client_registration_start_result = ClientRegistration::<Default>::start(
//! # &mut OsRng,
//! # b"password",
//! # )?;
//!
//! // Run HKDF on your remote OPRF seed.
//! let info = server_setup.key_material_info(b"[email protected]");
//! let key_material = info.ikm.hkdf(&info.info);
//!
//! // Use `ServerRegistration::start_with_key_material()` instead of `ServerRegistration::start()`.
//! let server_registration_start_result = ServerRegistration::<Default>::start_with_key_material(
//! &server_setup,
//! key_material,
//! client_registration_start_result.message,
//! )?;
//!
//! // Finish registration ...
//! # let client_registration_finish_result = client_registration_start_result.state.finish(&mut OsRng, b"password", server_registration_start_result.message, ClientRegistrationFinishParameters::default())?;
//!
//! // Incoming login ...
//! # let password_file_bytes = ServerRegistration::<Default>::finish(client_registration_finish_result.message).serialize();
//! # let client_login_start_result = ClientLogin::<Default>::start(
//! # &mut OsRng,
//! # b"password",
//! # )?;
//! # let password_file = ServerRegistration::<Default>::deserialize(&password_file_bytes)?;
//!
//! // Run HKDF on your remote OPRF seed.
//! let info = server_setup.key_material_info(b"[email protected]");
//! let key_material = info.ikm.hkdf(&info.info);
//!
//! // Use `ServerLogin::builder_with_key_material()` instead of `ServerLogin::start()`.
//! let server_login_builder = ServerLogin::builder_with_key_material(
//! &mut server_rng,
//! &server_setup,
//! key_material,
//! Some(password_file),
//! client_login_start_result.message,
//! ServerLoginParameters::default(),
//! )?;
//!
//! // Run Diffie-Hellman on your remote key.
//! let client_e_public_key = server_login_builder.data();
//! let shared_secret = server_login_builder.private_key().diffie_hellman(&client_e_public_key)?;
//!
//! // Use the shared secret to build `ServerLogin`.
//! let server_login_start_result = server_login_builder.build(shared_secret)?;
//! # Ok::<(), anyhow::Error>(())
//! ```
@@ -1028,11 +1203,13 @@
//! can be used.
//! ```
//! # use generic_array::GenericArray;
//! use opaque_ke::ksf::Ksf;
//!
//! #[derive(Default)]
//! struct CustomKsf(scrypt::Params);
//!
//! // The Ksf trait must be implemented to be used in the ciphersuite.
//! impl opaque_ke::ksf::Ksf for CustomKsf {
//! impl Ksf for CustomKsf {
//! fn hash<L: generic_array::ArrayLength<u8>>(
//! &self,
//! input: GenericArray<u8, L>,
@@ -1064,15 +1241,13 @@
//! # #[cfg(feature = "ristretto255")]
//! # impl CipherSuite for DefaultCipherSuite {
//! # type OprfCs = opaque_ke::Ristretto255;
//! # type KeGroup = opaque_ke::Ristretto255;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
//! # type KeyExchange = opaque_ke::TripleDh<opaque_ke::Ristretto255, sha2::Sha512>;
//! # type Ksf = argon2::Argon2<'static>;
//! # }
//! # #[cfg(not(feature = "ristretto255"))]
//! # impl CipherSuite for DefaultCipherSuite {
//! # type OprfCs = p256::NistP256;
//! # type KeGroup = p256::NistP256;
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
//! # type KeyExchange = opaque_ke::TripleDh<p256::NistP256, sha2::Sha256>;
//! # type Ksf = argon2::Argon2<'static>;
//! # }
//! #
@@ -1126,7 +1301,7 @@
//! more computationally intensive the `Ksf` function is, the more resistant
//! the server's password file records will be against offline dictionary and precomputation
//! attacks; see [the OPAQUE paper](https://eprint.iacr.org/2018/163.pdf) for
//! more details.
//! more details. The `argon2` feature requires [`alloc`].
//!
//! - The `serde` feature, enabled by default, provides convenience functions for serializing and deserializing with [serde](https://serde.rs/).
//!
@@ -1137,15 +1312,14 @@
//! - The `curve25519` feature enables Curve25519 as a `KeGroup`. To select a
//! specific backend see the [curve25519-dalek] documentation.
//!
//! - The `p256` feature enables the use of [`p256::NistP256`] as a `KeGroup`
//! and a `OprfCs` for `CipherSuite`.
//! - The `ecdsa` feature enables using [`elliptic_curve`]s with [`Ecdsa`] for
//! [`SigmaI`]s signature algorithm.
//!
//! - The `bench` feature is used only for running performance benchmarks for
//! this implementation.
//! - The `ed25519` feature enables using [`Ed25519`]s with [`PureEddsa`] and
//! [`HashEddsa`] for [`SigmaI`]s signature algorithm.
//!
//! [curve25519-dalek]:
//! (https://docs.rs/curve25519-dalek/4.0.0-pre.5/curve25519_dalek/index.html#backends)
//! [`p256::NistP256`]: https://docs.rs/p256/latest/p256/struct.NistP256.html
//! [`alloc`]: https://doc.rust-lang.org/alloc
//! [curve25519-dalek]: https://docs.rs/curve25519-dalek/4/curve25519_dalek/index.html#backends
#![no_std]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
@@ -1169,7 +1343,6 @@ pub mod ksf;
mod messages;
mod opaque;
mod serialization;
mod util;
#[cfg(test)]
mod tests;
@@ -1181,8 +1354,16 @@ pub use rand;
#[cfg(feature = "curve25519")]
pub use crate::key_exchange::group::curve25519::Curve25519;
#[cfg(feature = "ed25519")]
pub use crate::key_exchange::group::ed25519::Ed25519;
#[cfg(feature = "ristretto255")]
pub use crate::key_exchange::group::ristretto255::Ristretto255;
#[cfg(feature = "ecdsa")]
pub use crate::key_exchange::sigma_i::ecdsa::Ecdsa;
pub use crate::key_exchange::sigma_i::hash_eddsa::HashEddsa;
pub use crate::key_exchange::sigma_i::pure_eddsa::PureEddsa;
pub use crate::key_exchange::sigma_i::SigmaI;
pub use crate::key_exchange::tripledh::TripleDh;
pub use crate::messages::{
CredentialFinalization, CredentialFinalizationLen, CredentialRequest, CredentialRequestLen,
CredentialResponse, CredentialResponseLen, RegistrationRequest, RegistrationRequestLen,
@@ -1193,6 +1374,6 @@ pub use crate::opaque::{
ClientLogin, ClientLoginFinishParameters, ClientLoginFinishResult, ClientLoginStartResult,
ClientRegistration, ClientRegistrationFinishParameters, ClientRegistrationFinishResult,
ClientRegistrationStartResult, Identifiers, KeyMaterialInfo, ServerLogin,
ServerLoginFinishResult, ServerLoginStartParameters, ServerLoginStartResult,
ServerRegistration, ServerRegistrationLen, ServerRegistrationStartResult, ServerSetup,
ServerLoginFinishResult, ServerLoginParameters, ServerLoginStartResult, ServerRegistration,
ServerRegistrationLen, ServerRegistrationStartResult, ServerSetup,
};
+118 -168
View File
@@ -16,24 +16,24 @@ use generic_array::sequence::Concat;
use generic_array::typenum::{Sum, Unsigned};
use generic_array::{ArrayLength, GenericArray};
use rand::{CryptoRng, RngCore};
use subtle::ConstantTimeEq;
use voprf::Group;
use voprf::{BlindedElement, BlindedElementLen, EvaluationElement, EvaluationElementLen};
use zeroize::Zeroizing;
use crate::ciphersuite::{CipherSuite, OprfGroup, OprfHash};
use crate::ciphersuite::{CipherSuite, KeGroup, OprfGroup, OprfHash};
use crate::envelope::{Envelope, EnvelopeLen};
use crate::errors::utils::{check_slice_size, check_slice_size_atleast};
use crate::errors::ProtocolError;
use crate::hash::OutputSize;
use crate::key_exchange::group::KeGroup;
use crate::key_exchange::group::Group;
use crate::key_exchange::shared::NonceLen;
use crate::key_exchange::traits::{
Deserialize, Ke1MessageLen, Ke2MessageLen, Ke3MessageLen, KeyExchange, Serialize,
CredentialRequestParts, CredentialResponseParts, Deserialize, Ke1MessageLen, Ke2MessageLen,
Ke3MessageLen, KeyExchange, Serialize,
};
use crate::key_exchange::tripledh::NonceLen;
use crate::keypair::PublicKey;
use crate::opaque::{
MaskedResponse, MaskedResponseLen, ServerLogin, ServerLoginStartResult, ServerSetup,
};
use crate::serialization::SliceExt;
////////////////////////////
// High-level API Structs //
@@ -61,12 +61,12 @@ pub struct RegistrationRequest<CS: CipherSuite> {
serde(bound = "")
)]
#[derive_where(Clone)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; voprf::EvaluationElement<CS::OprfCs>, <CS::KeGroup as KeGroup>::Pk)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; voprf::EvaluationElement<CS::OprfCs>, <KeGroup<CS> as Group>::Pk)]
pub struct RegistrationResponse<CS: CipherSuite> {
/// The server's oprf output
pub(crate) evaluation_element: voprf::EvaluationElement<CS::OprfCs>,
/// Server's static public key
pub(crate) server_s_pk: PublicKey<CS::KeGroup>,
pub(crate) server_s_pk: PublicKey<KeGroup<CS>>,
}
/// The final message from the client, containing sealed cryptographic
@@ -77,7 +77,7 @@ pub struct RegistrationResponse<CS: CipherSuite> {
serde(bound = "")
)]
#[derive_where(Clone, ZeroizeOnDrop)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <CS::KeGroup as KeGroup>::Pk)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <KeGroup<CS> as Group>::Pk)]
pub struct RegistrationUpload<CS: CipherSuite> {
/// The "envelope" generated by the user, containing sealed cryptographic
/// identifiers
@@ -85,7 +85,7 @@ pub struct RegistrationUpload<CS: CipherSuite> {
/// The masking key used to mask the envelope
pub(crate) masking_key: Output<OprfHash<CS>>,
/// The user's public key
pub(crate) client_s_pk: PublicKey<CS::KeGroup>,
pub(crate) client_s_pk: PublicKey<KeGroup<CS>>,
}
/// The message sent by the user to the server, to initiate registration
@@ -93,21 +93,19 @@ pub struct RegistrationUpload<CS: CipherSuite> {
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound(
deserialize = "<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE1Message: \
serde::Deserialize<'de>",
serialize = "<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE1Message: \
serde::Serialize"
deserialize = "<CS::KeyExchange as KeyExchange>::KE1Message: serde::Deserialize<'de>",
serialize = "<CS::KeyExchange as KeyExchange>::KE1Message: serde::Serialize"
))
)]
#[derive_where(Clone, ZeroizeOnDrop)]
#[derive_where(
Debug, Eq, Hash, PartialEq;
voprf::BlindedElement<CS::OprfCs>,
<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE1Message,
<CS::KeyExchange as KeyExchange>::KE1Message,
)]
pub struct CredentialRequest<CS: CipherSuite> {
pub(crate) blinded_element: voprf::BlindedElement<CS::OprfCs>,
pub(crate) ke1_message: <CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE1Message,
pub(crate) ke1_message: <CS::KeyExchange as KeyExchange>::KE1Message,
}
/// Builder for [`ServerLogin`](crate::ServerLogin) when using remote keys.
@@ -115,37 +113,36 @@ pub struct CredentialRequest<CS: CipherSuite> {
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound(
deserialize = "SK: serde::Deserialize<'de>, <CS::KeyExchange as KeyExchange<OprfHash<CS>, \
CS::KeGroup>>::KE2Builder: serde::Deserialize<'de>",
serialize = "SK: serde::Serialize, <CS::KeyExchange as KeyExchange<OprfHash<CS>, \
CS::KeGroup>>::KE2Builder: serde::Serialize"
deserialize = "SK: serde::Deserialize<'de>, <CS::KeyExchange as \
KeyExchange>::KE2Builder<'a, CS>: serde::Deserialize<'de>",
serialize = "SK: serde::Serialize, <CS::KeyExchange as KeyExchange>::KE2Builder<'a, CS>: \
serde::Serialize"
))
)]
#[derive_where(Clone)]
#[derive_where(
Debug, Eq, PartialEq;
<KeGroup<CS> as Group>::Pk,
SK,
voprf::EvaluationElement<CS::OprfCs>,
<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE2Builder,
<CS::KeyExchange as KeyExchange>::KE2Builder<'a, CS>,
)]
pub struct ServerLoginBuilder<CS: CipherSuite, SK: Clone> {
pub struct ServerLoginBuilder<'a, CS: CipherSuite, SK: Clone> {
pub(crate) server_s_sk: SK,
pub(crate) evaluation_element: voprf::EvaluationElement<CS::OprfCs>,
pub(crate) masking_nonce: Zeroizing<GenericArray<u8, NonceLen>>,
pub(crate) masked_response: MaskedResponse<CS>,
#[cfg(test)]
pub(crate) oprf_key: Zeroizing<GenericArray<u8, <OprfGroup<CS> as Group>::ScalarLen>>,
pub(crate) ke2_builder: <CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE2Builder,
pub(crate) oprf_key: Zeroizing<GenericArray<u8, <OprfGroup<CS> as voprf::Group>::ScalarLen>>,
pub(crate) ke2_builder: <CS::KeyExchange as KeyExchange>::KE2Builder<'a, CS>,
}
impl<CS: CipherSuite, SK: Clone> ServerLoginBuilder<CS, SK> {
impl<CS: CipherSuite, SK: Clone> ServerLoginBuilder<'_, CS, SK> {
/// The returned data here has to be processed and the result given as an
/// input to [`ServerLoginBuilder::build()`]. To understand what kind of
/// output is expected here and how to process it, refer to the
/// documentation of your chosen [`CipherSuite::KeyExchange`].
pub fn data(
&self,
) -> <CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE2BuilderData<'_> {
pub fn data(&self) -> <CS::KeyExchange as KeyExchange>::KE2BuilderData<'_, CS> {
CS::KeyExchange::ke2_builder_data(&self.ke2_builder)
}
@@ -161,7 +158,7 @@ impl<CS: CipherSuite, SK: Clone> ServerLoginBuilder<CS, SK> {
/// See [`ServerLogin::start()`] for the regular path.
pub fn build(
self,
input: <CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE2BuilderInput,
input: <CS::KeyExchange as KeyExchange>::KE2BuilderInput<CS>,
) -> Result<ServerLoginStartResult<CS>, ProtocolError> {
ServerLogin::build(self, input)
}
@@ -173,24 +170,22 @@ impl<CS: CipherSuite, SK: Clone> ServerLoginBuilder<CS, SK> {
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound(
deserialize = "<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE2Message: \
serde::Deserialize<'de>",
serialize = "<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE2Message: \
serde::Serialize"
deserialize = "<CS::KeyExchange as KeyExchange>::KE2Message: serde::Deserialize<'de>",
serialize = "<CS::KeyExchange as KeyExchange>::KE2Message: serde::Serialize"
))
)]
#[derive_where(Clone)]
#[derive_where(
Debug, Eq, Hash, PartialEq;
voprf::EvaluationElement<CS::OprfCs>,
<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE2Message,
<CS::KeyExchange as KeyExchange>::KE2Message,
)]
pub struct CredentialResponse<CS: CipherSuite> {
/// the server's oprf output
pub(crate) evaluation_element: voprf::EvaluationElement<CS::OprfCs>,
pub(crate) masking_nonce: GenericArray<u8, NonceLen>,
pub(crate) masked_response: MaskedResponse<CS>,
pub(crate) ke2_message: <CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE2Message,
pub(crate) ke2_message: <CS::KeyExchange as KeyExchange>::KE2Message,
}
/// The answer sent by the client to the server, upon reception of the sealed
@@ -199,19 +194,17 @@ pub struct CredentialResponse<CS: CipherSuite> {
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound(
deserialize = "<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE3Message: \
serde::Deserialize<'de>",
serialize = "<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE3Message: \
serde::Serialize"
deserialize = "<CS::KeyExchange as KeyExchange>::KE3Message: serde::Deserialize<'de>",
serialize = "<CS::KeyExchange as KeyExchange>::KE3Message: serde::Serialize"
))
)]
#[derive_where(Clone)]
#[derive_where(
Debug, Eq, Hash, PartialEq;
<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE3Message,
<CS::KeyExchange as KeyExchange>::KE3Message,
)]
pub struct CredentialFinalization<CS: CipherSuite> {
pub(crate) ke3_message: <CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE3Message,
pub(crate) ke3_message: <CS::KeyExchange as KeyExchange>::KE3Message,
}
////////////////////////////////
@@ -220,18 +213,18 @@ pub struct CredentialFinalization<CS: CipherSuite> {
////////////////////////////////
/// Length of [`RegistrationRequest`] in bytes for serialization.
pub type RegistrationRequestLen<CS: CipherSuite> = <OprfGroup<CS> as Group>::ElemLen;
pub type RegistrationRequestLen<CS: CipherSuite> = <OprfGroup<CS> as voprf::Group>::ElemLen;
impl<CS: CipherSuite> RegistrationRequest<CS> {
/// Only used for testing purposes
#[cfg(test)]
pub fn get_blinded_element_for_testing(&self) -> voprf::BlindedElement<CS::OprfCs> {
pub(crate) fn get_blinded_element_for_testing(&self) -> voprf::BlindedElement<CS::OprfCs> {
self.blinded_element.clone()
}
/// Serialization into bytes
pub fn serialize(&self) -> GenericArray<u8, RegistrationRequestLen<CS>> {
<OprfGroup<CS> as Group>::serialize_elem(self.blinded_element.value())
<OprfGroup<CS> as voprf::Group>::serialize_elem(self.blinded_element.value())
}
/// Deserialization from bytes
@@ -244,40 +237,38 @@ impl<CS: CipherSuite> RegistrationRequest<CS> {
/// Length of [`RegistrationResponse`] in bytes for serialization.
pub type RegistrationResponseLen<CS: CipherSuite> =
Sum<<OprfGroup<CS> as Group>::ElemLen, <CS::KeGroup as KeGroup>::PkLen>;
Sum<<OprfGroup<CS> as voprf::Group>::ElemLen, <KeGroup<CS> as Group>::PkLen>;
impl<CS: CipherSuite> RegistrationResponse<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> GenericArray<u8, RegistrationResponseLen<CS>>
where
// RegistrationResponse: KgPk + KePk
<OprfGroup<CS> as Group>::ElemLen: Add<<CS::KeGroup as KeGroup>::PkLen>,
<OprfGroup<CS> as voprf::Group>::ElemLen: Add<<KeGroup<CS> as Group>::PkLen>,
RegistrationResponseLen<CS>: ArrayLength<u8>,
{
<OprfGroup<CS> as Group>::serialize_elem(self.evaluation_element.value())
<OprfGroup<CS> as voprf::Group>::serialize_elem(self.evaluation_element.value())
.concat(self.server_s_pk.serialize())
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let elem_len = <OprfGroup<CS> as Group>::ElemLen::USIZE;
let key_len = <CS::KeGroup as KeGroup>::PkLen::USIZE;
let checked_slice =
check_slice_size(input, elem_len + key_len, "registration_response_bytes")?;
// Ensure that public key is valid
let server_s_pk = PublicKey::deserialize(&checked_slice[elem_len..])?;
pub fn deserialize(mut input: &[u8]) -> Result<Self, ProtocolError> {
let evaluation_element = EvaluationElement::deserialize(input)?;
input = &input[EvaluationElementLen::<CS::OprfCs>::USIZE..];
Ok(Self {
evaluation_element: voprf::EvaluationElement::deserialize(&checked_slice[..elem_len])?,
server_s_pk,
evaluation_element,
server_s_pk: PublicKey::deserialize_take(&mut input)?,
})
}
#[cfg(test)]
/// Only used for tests, where we can set the beta value to test for the
/// reflection error case
pub fn set_evaluation_element_for_testing(&self, beta: <OprfGroup<CS> as Group>::Elem) -> Self {
pub(crate) fn set_evaluation_element_for_testing(
&self,
beta: <OprfGroup<CS> as voprf::Group>::Elem,
) -> Self {
Self {
evaluation_element: voprf::EvaluationElement::from_value_unchecked(beta),
server_s_pk: self.server_s_pk.clone(),
@@ -287,18 +278,15 @@ impl<CS: CipherSuite> RegistrationResponse<CS> {
/// Length of [`RegistrationUpload`] in bytes for serialization.
pub type RegistrationUploadLen<CS: CipherSuite> =
Sum<Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<OprfHash<CS>>>, EnvelopeLen<CS>>;
Sum<Sum<<KeGroup<CS> as Group>::PkLen, OutputSize<OprfHash<CS>>>, EnvelopeLen<CS>>;
impl<CS: CipherSuite> RegistrationUpload<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> GenericArray<u8, RegistrationUploadLen<CS>>
where
// Envelope: Nonce + Hash
NonceLen: Add<OutputSize<OprfHash<CS>>>,
EnvelopeLen<CS>: ArrayLength<u8>,
// RegistrationUpload: (KePk + Hash) + Envelope
<CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<OprfHash<CS>>>,
Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<OprfHash<CS>>>:
<KeGroup<CS> as Group>::PkLen: Add<OutputSize<OprfHash<CS>>>,
Sum<<KeGroup<CS> as Group>::PkLen, OutputSize<OprfHash<CS>>>:
ArrayLength<u8> + Add<EnvelopeLen<CS>>,
RegistrationUploadLen<CS>: ArrayLength<u8>,
{
@@ -309,18 +297,11 @@ impl<CS: CipherSuite> RegistrationUpload<CS> {
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let key_len = <CS::KeGroup as KeGroup>::PkLen::USIZE;
let hash_len = OutputSize::<OprfHash<CS>>::USIZE;
let checked_slice =
check_slice_size_atleast(input, key_len + hash_len, "registration_upload_bytes")?;
let envelope = Envelope::<CS>::deserialize(&checked_slice[key_len + hash_len..])?;
pub fn deserialize(mut input: &[u8]) -> Result<Self, ProtocolError> {
Ok(Self {
envelope,
masking_key: GenericArray::clone_from_slice(
&checked_slice[key_len..key_len + hash_len],
),
client_s_pk: PublicKey::deserialize(&checked_slice[..key_len])?,
client_s_pk: PublicKey::deserialize_take(&mut input)?,
masking_key: input.take_array("masking key")?,
envelope: Envelope::deserialize_take(&mut input)?,
})
}
@@ -342,57 +323,49 @@ impl<CS: CipherSuite> RegistrationUpload<CS> {
/// Length of [`CredentialRequest`] in bytes for serialization.
pub type CredentialRequestLen<CS: CipherSuite> =
Sum<<OprfGroup<CS> as Group>::ElemLen, Ke1MessageLen<CS>>;
Sum<<OprfGroup<CS> as voprf::Group>::ElemLen, Ke1MessageLen<CS>>;
impl<CS: CipherSuite> CredentialRequest<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> GenericArray<u8, CredentialRequestLen<CS>>
where
<CS::KeyExchange as KeyExchange>::KE1Message: Serialize,
// CredentialRequest: KgPk + Ke1Message
<OprfGroup<CS> as Group>::ElemLen: Add<Ke1MessageLen<CS>>,
<OprfGroup<CS> as voprf::Group>::ElemLen: Add<Ke1MessageLen<CS>>,
CredentialRequestLen<CS>: ArrayLength<u8>,
{
<OprfGroup<CS> as Group>::serialize_elem(self.blinded_element.value())
<OprfGroup<CS> as voprf::Group>::serialize_elem(self.blinded_element.value())
.concat(self.ke1_message.serialize())
}
pub(crate) fn serialize_iter<'a>(
blinded_element: &'a GenericArray<u8, <OprfGroup<CS> as Group>::ElemLen>,
ke1_message: &'a GenericArray<u8, Ke1MessageLen<CS>>,
) -> impl Iterator<Item = &'a [u8]> {
[blinded_element.as_slice(), ke1_message].into_iter()
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let elem_len = <OprfGroup<CS> as Group>::ElemLen::USIZE;
let checked_slice = check_slice_size_atleast(input, elem_len, "login_first_message_bytes")?;
// Check that the message is actually containing an element of the correct
// subgroup
let blinded_element =
voprf::BlindedElement::<CS::OprfCs>::deserialize(&checked_slice[..elem_len])?;
// Throw an error if the identity group element is encountered
if bool::from(<OprfGroup<CS> as Group>::identity_elem().ct_eq(&blinded_element.value())) {
return Err(ProtocolError::IdentityGroupElementError);
pub fn deserialize(mut input: &[u8]) -> Result<Self, ProtocolError>
where
<CS::KeyExchange as KeyExchange>::KE1Message: Deserialize,
{
Self::deserialize_take(&mut input)
}
let ke1_message =
<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE1Message::deserialize(
&checked_slice[elem_len..],
)?;
pub(crate) fn deserialize_take(input: &mut &[u8]) -> Result<Self, ProtocolError>
where
<CS::KeyExchange as KeyExchange>::KE1Message: Deserialize,
{
let blinded_element = BlindedElement::deserialize(input)?;
*input = &input[BlindedElementLen::<CS::OprfCs>::USIZE..];
Ok(Self {
blinded_element,
ke1_message,
ke1_message: <CS::KeyExchange as KeyExchange>::KE1Message::deserialize_take(input)?,
})
}
pub(crate) fn to_parts(&self) -> CredentialRequestParts<CS> {
CredentialRequestParts::new(&self.blinded_element)
}
/// Only used for testing purposes
#[cfg(test)]
pub fn get_blinded_element_for_testing(&self) -> voprf::BlindedElement<CS::OprfCs> {
pub(crate) fn get_blinded_element_for_testing(&self) -> voprf::BlindedElement<CS::OprfCs> {
self.blinded_element.clone()
}
}
@@ -402,90 +375,61 @@ pub type CredentialResponseLen<CS: CipherSuite> =
Sum<CredentialResponseWithoutKeLen<CS>, Ke2MessageLen<CS>>;
pub(crate) type CredentialResponseWithoutKeLen<CS: CipherSuite> =
Sum<Sum<<OprfGroup<CS> as Group>::ElemLen, NonceLen>, MaskedResponseLen<CS>>;
Sum<Sum<<OprfGroup<CS> as voprf::Group>::ElemLen, NonceLen>, MaskedResponseLen<CS>>;
impl<CS: CipherSuite> CredentialResponse<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> GenericArray<u8, CredentialResponseLen<CS>>
where
<CS::KeyExchange as KeyExchange>::KE2Message: Serialize,
// CredentialResponseWithoutKeLen: (KgPk + Nonce) + MaskedResponse
<OprfGroup<CS> as Group>::ElemLen: Add<NonceLen>,
Sum<<OprfGroup<CS> as Group>::ElemLen, NonceLen>:
<OprfGroup<CS> as voprf::Group>::ElemLen: Add<NonceLen>,
Sum<<OprfGroup<CS> as voprf::Group>::ElemLen, NonceLen>:
ArrayLength<u8> + Add<MaskedResponseLen<CS>>,
CredentialResponseWithoutKeLen<CS>: ArrayLength<u8>,
// MaskedResponse: (Nonce + Hash) + KePk
NonceLen: Add<OutputSize<OprfHash<CS>>>,
Sum<NonceLen, OutputSize<OprfHash<CS>>>:
ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
MaskedResponseLen<CS>: ArrayLength<u8>,
// CredentialResponse: CredentialResponseWithoutKeLen + Ke2Message
CredentialResponseWithoutKeLen<CS>: Add<Ke2MessageLen<CS>>,
CredentialResponseLen<CS>: ArrayLength<u8>,
{
<OprfGroup<CS> as Group>::serialize_elem(self.evaluation_element.value())
<OprfGroup<CS> as voprf::Group>::serialize_elem(self.evaluation_element.value())
.concat(self.masking_nonce)
.concat(self.masked_response.serialize())
.concat(self.ke2_message.serialize())
}
pub(crate) fn serialize_without_ke<'a>(
beta: &'a GenericArray<u8, <OprfGroup<CS> as Group>::ElemLen>,
masking_nonce: &'a GenericArray<u8, NonceLen>,
masked_response: &'a MaskedResponse<CS>,
) -> impl Iterator<Item = &'a [u8]> {
[beta.as_slice(), masking_nonce.as_slice()]
.into_iter()
.chain(masked_response.iter())
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let elem_len = <OprfGroup<CS> as Group>::ElemLen::USIZE;
let key_len = <CS::KeGroup as KeGroup>::PkLen::USIZE;
let nonce_len = NonceLen::USIZE;
let envelope_len = Envelope::<CS>::len();
let masked_response_len = key_len + envelope_len;
let ke2_message_len = Ke2MessageLen::<CS>::USIZE;
let checked_slice = check_slice_size_atleast(
input,
elem_len + nonce_len + masked_response_len + ke2_message_len,
"credential_response_bytes",
)?;
// Check that the message is actually containing an element of the correct
// subgroup
let beta_bytes = &checked_slice[..elem_len];
let evaluation_element = voprf::EvaluationElement::<CS::OprfCs>::deserialize(beta_bytes)?;
// Throw an error if the identity group element is encountered
if bool::from(<OprfGroup<CS> as Group>::identity_elem().ct_eq(&evaluation_element.value()))
pub fn deserialize(mut input: &[u8]) -> Result<Self, ProtocolError>
where
<CS::KeyExchange as KeyExchange>::KE2Message: Deserialize,
{
return Err(ProtocolError::IdentityGroupElementError);
}
let masking_nonce =
GenericArray::clone_from_slice(&checked_slice[elem_len..elem_len + nonce_len]);
let masked_response = MaskedResponse::deserialize(
&checked_slice[elem_len + nonce_len..elem_len + nonce_len + masked_response_len],
);
let ke2_message =
<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE2Message::deserialize(
&checked_slice[elem_len + nonce_len + masked_response_len..],
)?;
let evaluation_element = EvaluationElement::deserialize(input)?;
input = &input[voprf::EvaluationElementLen::<CS::OprfCs>::USIZE..];
Ok(Self {
evaluation_element,
masking_nonce,
masked_response,
ke2_message,
masking_nonce: input.take_array("masking nonce")?,
masked_response: MaskedResponse::deserialize_take(&mut input)?,
ke2_message: <CS::KeyExchange as KeyExchange>::KE2Message::deserialize_take(
&mut input,
)?,
})
}
pub(crate) fn to_parts(&self) -> CredentialResponseParts<CS> {
CredentialResponseParts::new(
&self.evaluation_element,
self.masking_nonce,
self.masked_response.clone(),
)
}
#[cfg(test)]
/// Only used for tests, where we can set the beta value to test for the
/// reflection error case
pub fn set_evaluation_element_for_testing(&self, beta: <OprfGroup<CS> as Group>::Elem) -> Self {
pub(crate) fn set_evaluation_element_for_testing(
&self,
beta: <OprfGroup<CS> as voprf::Group>::Elem,
) -> Self {
Self {
evaluation_element: voprf::EvaluationElement::from_value_unchecked(beta),
masking_nonce: self.masking_nonce,
@@ -500,16 +444,22 @@ pub type CredentialFinalizationLen<CS: CipherSuite> = Ke3MessageLen<CS>;
impl<CS: CipherSuite> CredentialFinalization<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> GenericArray<u8, CredentialFinalizationLen<CS>> {
pub fn serialize(&self) -> GenericArray<u8, CredentialFinalizationLen<CS>>
where
<CS::KeyExchange as KeyExchange>::KE3Message: Serialize,
{
self.ke3_message.serialize()
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let ke3_message =
<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE3Message::deserialize(
input,
)?;
Ok(Self { ke3_message })
pub fn deserialize(mut input: &[u8]) -> Result<Self, ProtocolError>
where
<CS::KeyExchange as KeyExchange>::KE3Message: Deserialize,
{
Ok(Self {
ke3_message: <CS::KeyExchange as KeyExchange>::KE3Message::deserialize_take(
&mut input,
)?,
})
}
}
+261 -300
View File
@@ -11,30 +11,32 @@
use core::ops::{Add, Deref};
use derive_where::derive_where;
use digest::{Output, OutputSizeUser};
use digest::Output;
use generic_array::sequence::Concat;
use generic_array::typenum::{Sum, Unsigned, U2};
use generic_array::typenum::{Sum, Unsigned};
use generic_array::{ArrayLength, GenericArray};
use hkdf::{Hkdf, HkdfExtract};
use rand::{CryptoRng, RngCore};
use subtle::{Choice, ConstantTimeEq, CtOption};
use voprf::Group;
use voprf::{BlindedElement, Group as _, OprfClient, OprfClientLen};
use zeroize::Zeroizing;
use crate::ciphersuite::{CipherSuite, OprfGroup, OprfHash};
use crate::ciphersuite::{CipherSuite, KeGroup, KeHash, OprfGroup, OprfHash};
use crate::envelope::{Envelope, EnvelopeLen};
use crate::errors::utils::check_slice_size;
use crate::errors::{InternalError, ProtocolError};
use crate::hash::OutputSize;
use crate::key_exchange::group::KeGroup;
use crate::key_exchange::group::Group;
use crate::key_exchange::shared::NonceLen;
use crate::key_exchange::traits::{
Deserialize, Ke1MessageLen, Ke1StateLen, Ke2StateLen, KeyExchange, Serialize,
CredentialResponseParts, Deserialize, Ke1MessageLen, Ke1StateLen, Ke2StateLen, KeyExchange,
Serialize, SerializedContext, SerializedIdentifiers,
};
use crate::keypair::{
KeyPair, OprfSeed, OprfSeedSerialization, PrivateKey, PrivateKeySerialization, PublicKey,
};
use crate::key_exchange::tripledh::NonceLen;
use crate::keypair::{KeyPair, PrivateKey, PrivateKeySerialization, PublicKey};
use crate::ksf::Ksf;
use crate::messages::{CredentialRequestLen, RegistrationUploadLen};
use crate::serialization::Input;
use crate::serialization::{GenericArrayExt, SliceExt};
use crate::{
CredentialFinalization, CredentialRequest, CredentialResponse, RegistrationRequest,
RegistrationResponse, RegistrationUpload, ServerLoginBuilder,
@@ -65,15 +67,15 @@ const STR_OPAQUE_DERIVE_KEY_PAIR: &[u8; 20] = b"OPAQUE-DeriveKeyPair";
))
)]
#[derive_where(Clone)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <CS::KeGroup as KeGroup>::Pk, <CS::KeGroup as KeGroup>::Sk, SK, OS)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <KeGroup<CS> as Group>::Pk, <KeGroup<CS> as Group>::Sk, SK, OS)]
pub struct ServerSetup<
CS: CipherSuite,
SK: Clone = PrivateKey<<CS as CipherSuite>::KeGroup>,
OS: Clone = Zeroizing<Output<OprfHash<CS>>>,
SK: Clone = PrivateKey<KeGroup<CS>>,
OS: Clone = OprfSeed<OprfHash<CS>>,
> {
oprf_seed: OS,
keypair: KeyPair<CS::KeGroup, SK>,
pub(crate) fake_keypair: KeyPair<CS::KeGroup>,
keypair: KeyPair<KeGroup<CS>, SK>,
pub(crate) fake_keypair: KeyPair<KeGroup<CS>>,
}
/// The state elements the client holds to register itself
@@ -100,7 +102,7 @@ pub struct ClientRegistration<CS: CipherSuite> {
serde(bound = "")
)]
#[derive_where(Clone, ZeroizeOnDrop)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <CS::KeGroup as KeGroup>::Pk)]
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; <KeGroup<CS> as Group>::Pk)]
pub struct ServerRegistration<CS: CipherSuite>(pub(crate) RegistrationUpload<CS>);
/// The state elements the client holds to perform a login
@@ -108,24 +110,22 @@ pub struct ServerRegistration<CS: CipherSuite>(pub(crate) RegistrationUpload<CS>
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound(
deserialize = "<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE1Message: \
serde::Deserialize<'de>, <CS::KeyExchange as KeyExchange<OprfHash<CS>, \
CS::KeGroup>>::KE1State: serde::Deserialize<'de>",
serialize = "<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE1Message: \
serde::Serialize, <CS::KeyExchange as KeyExchange<OprfHash<CS>, \
CS::KeGroup>>::KE1State: serde::Serialize"
deserialize = "<CS::KeyExchange as KeyExchange>::KE1Message: serde::Deserialize<'de>, \
<CS::KeyExchange as KeyExchange>::KE1State: serde::Deserialize<'de>",
serialize = "<CS::KeyExchange as KeyExchange>::KE1Message: serde::Serialize, \
<CS::KeyExchange as KeyExchange>::KE1State: serde::Serialize"
))
)]
#[derive_where(Clone, ZeroizeOnDrop)]
#[derive_where(
Debug, Eq, Hash, PartialEq;
voprf::OprfClient<CS::OprfCs>,
<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE1State,
<CS::KeyExchange as KeyExchange>::KE1State,
CredentialRequest<CS>,
)]
pub struct ClientLogin<CS: CipherSuite> {
pub(crate) oprf_client: voprf::OprfClient<CS::OprfCs>,
pub(crate) ke1_state: <CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE1State,
pub(crate) ke1_state: <CS::KeyExchange as KeyExchange>::KE1State,
pub(crate) credential_request: CredentialRequest<CS>,
}
@@ -134,19 +134,14 @@ pub struct ClientLogin<CS: CipherSuite> {
feature = "serde",
derive(serde::Deserialize, serde::Serialize),
serde(bound(
deserialize = "<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE2State: \
serde::Deserialize<'de>",
serialize = "<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE2State: \
serde::Serialize"
deserialize = "<CS::KeyExchange as KeyExchange>::KE2State<CS>: serde::Deserialize<'de>",
serialize = "<CS::KeyExchange as KeyExchange>::KE2State<CS>: serde::Serialize"
))
)]
#[derive_where(Clone, ZeroizeOnDrop)]
#[derive_where(
Debug, Eq, Hash, PartialEq;
<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE2State,
)]
#[derive_where(Debug, Eq, Hash, PartialEq; <CS::KeyExchange as KeyExchange>::KE2State<CS>)]
pub struct ServerLogin<CS: CipherSuite> {
ke2_state: <CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE2State,
ke2_state: <CS::KeyExchange as KeyExchange>::KE2State<CS>,
}
////////////////////////////////
@@ -157,10 +152,10 @@ pub struct ServerLogin<CS: CipherSuite> {
// Server Setup
// ============
impl<CS: CipherSuite> ServerSetup<CS, PrivateKey<CS::KeGroup>> {
impl<CS: CipherSuite> ServerSetup<CS, PrivateKey<KeGroup<CS>>> {
/// Generate a new instance of server setup
pub fn new<R: CryptoRng + RngCore>(rng: &mut R) -> Self {
let keypair = KeyPair::generate_random::<CS::OprfCs, _>(rng);
let keypair = KeyPair::random(rng);
Self::new_with_key_pair(rng, keypair)
}
}
@@ -168,9 +163,9 @@ impl<CS: CipherSuite> ServerSetup<CS, PrivateKey<CS::KeGroup>> {
/// Length of [`ServerSetup`] in bytes for serialization.
pub type ServerSetupLen<
CS: CipherSuite,
SK: PrivateKeySerialization<CS::KeGroup>,
SK: PrivateKeySerialization<KeGroup<CS>>,
OS: OprfSeedSerialization<OprfHash<CS>, SK::Error>,
> = Sum<Sum<OS::Len, SK::Len>, <CS::KeGroup as KeGroup>::SkLen>;
> = Sum<Sum<OS::Len, SK::Len>, <KeGroup<CS> as Group>::SkLen>;
impl<CS: CipherSuite, SK: Clone, OS: Clone> ServerSetup<CS, SK, OS> {
/// Create [`ServerSetup`] with the given keypair and OPRF seed.
@@ -180,13 +175,13 @@ impl<CS: CipherSuite, SK: Clone, OS: Clone> ServerSetup<CS, SK, OS> {
/// [`ServerSetup::deserialize`] for this purpose.
pub fn new_with_key_pair_and_seed<R: CryptoRng + RngCore>(
rng: &mut R,
keypair: KeyPair<CS::KeGroup, SK>,
keypair: KeyPair<KeGroup<CS>, SK>,
oprf_seed: OS,
) -> Self {
Self {
oprf_seed,
keypair,
fake_keypair: KeyPair::<CS::KeGroup>::generate_random::<CS::OprfCs, _>(rng),
fake_keypair: KeyPair::<KeGroup<CS>>::random(rng),
}
}
@@ -206,11 +201,11 @@ impl<CS: CipherSuite, SK: Clone, OS: Clone> ServerSetup<CS, SK, OS> {
/// Serialization into bytes
pub fn serialize(&self) -> GenericArray<u8, ServerSetupLen<CS, SK, OS>>
where
SK: PrivateKeySerialization<CS::KeGroup>,
SK: PrivateKeySerialization<KeGroup<CS>>,
OS: OprfSeedSerialization<OprfHash<CS>, SK::Error>,
// ServerSetup: Hash + KeSk + KeSk
OS::Len: Add<SK::Len>,
Sum<OS::Len, SK::Len>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::SkLen>,
Sum<OS::Len, SK::Len>: ArrayLength<u8> + Add<<KeGroup<CS> as Group>::SkLen>,
ServerSetupLen<CS, SK, OS>: ArrayLength<u8>,
{
self.oprf_seed
@@ -220,26 +215,21 @@ impl<CS: CipherSuite, SK: Clone, OS: Clone> ServerSetup<CS, SK, OS> {
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError<SK::Error>>
pub fn deserialize(mut input: &[u8]) -> Result<Self, ProtocolError<SK::Error>>
where
SK: PrivateKeySerialization<CS::KeGroup>,
SK: PrivateKeySerialization<KeGroup<CS>>,
OS: OprfSeedSerialization<OprfHash<CS>, SK::Error>,
{
let seed_len = OS::Len::USIZE;
let key_len = <CS::KeGroup as KeGroup>::SkLen::USIZE;
let checked_slice = check_slice_size(input, seed_len + key_len + key_len, "server_setup")
.map_err(ProtocolError::into_custom)?;
Ok(Self {
oprf_seed: OS::deserialize(&checked_slice[..seed_len])?,
keypair: SK::deserialize_key_pair(&checked_slice[seed_len..seed_len + key_len])?,
fake_keypair: PrivateKey::deserialize_key_pair(&checked_slice[seed_len + key_len..])
oprf_seed: OS::deserialize_take(&mut input)?,
keypair: SK::deserialize_take_key_pair(&mut input)?,
fake_keypair: PrivateKey::deserialize_take_key_pair(&mut input)
.map_err(ProtocolError::into_custom)?,
})
}
/// Returns the keypair
pub fn keypair(&self) -> &KeyPair<CS::KeGroup, SK> {
pub fn keypair(&self) -> &KeyPair<KeGroup<CS>, SK> {
&self.keypair
}
}
@@ -252,44 +242,12 @@ impl<CS: CipherSuite, SK: Clone> ServerSetup<CS, SK> {
/// [`ServerSetup::deserialize`] for this purpose.
pub fn new_with_key_pair<R: CryptoRng + RngCore>(
rng: &mut R,
keypair: KeyPair<CS::KeGroup, SK>,
keypair: KeyPair<KeGroup<CS>, SK>,
) -> Self {
let mut oprf_seed = GenericArray::default();
rng.fill_bytes(&mut oprf_seed);
Self {
oprf_seed: Zeroizing::new(oprf_seed),
keypair,
fake_keypair: KeyPair::<CS::KeGroup>::generate_random::<CS::OprfCs, _>(rng),
}
}
}
/// A trait to facilitate
/// [`ServerSetup::de/serialize`](crate::ServerSetup::serialize).
pub trait OprfSeedSerialization<H, E>: Sized {
/// Serialization size in bytes.
type Len: ArrayLength<u8>;
/// Serialization into bytes
fn serialize(&self) -> GenericArray<u8, Self::Len>;
/// Deserialization from bytes
fn deserialize(input: &[u8]) -> Result<Self, ProtocolError<E>>;
}
impl<H: OutputSizeUser, E> OprfSeedSerialization<H, E> for Zeroizing<Output<H>> {
type Len = H::OutputSize;
fn serialize(&self) -> GenericArray<u8, Self::Len> {
self.deref().clone()
}
fn deserialize(input: &[u8]) -> Result<Self, ProtocolError<E>> {
check_slice_size(input, H::OutputSize::USIZE, "oprf_seed")
.map_err(ProtocolError::into_custom)?;
Ok(Zeroizing::new(GenericArray::clone_from_slice(input)))
Self::new_with_key_pair_and_seed(rng, keypair, OprfSeed(oprf_seed))
}
}
@@ -297,9 +255,9 @@ impl<H: OutputSizeUser, E> OprfSeedSerialization<H, E> for Zeroizing<Output<H>>
/// [`ServerRegistration::start_with_key_material()`] and
/// [`ServerLogin::builder_with_key_material()`].
///
/// Use an HKDF, with the input key material [`ikm`](Self::ikm), expand
/// operation with [`info`](Self::info) with an output length
/// of [`CS::OprfCs::ScalarLen`](Group::ScalarLen).
/// Use a HKDF, with the input key material [`ikm`](Self::ikm), expand operation
/// with [`info`](Self::info) with an output length
/// of [`CS::OprfCs::ScalarLen`](voprf::Group::ScalarLen).
pub struct KeyMaterialInfo<'ci, OS: Clone> {
/// Input key material for the HKDF.
pub ikm: OS,
@@ -311,14 +269,14 @@ pub struct KeyMaterialInfo<'ci, OS: Clone> {
// ============
pub(crate) type ClientRegistrationLen<CS: CipherSuite> =
Sum<<OprfGroup<CS> as Group>::ScalarLen, <OprfGroup<CS> as Group>::ElemLen>;
Sum<<OprfGroup<CS> as voprf::Group>::ScalarLen, <OprfGroup<CS> as voprf::Group>::ElemLen>;
impl<CS: CipherSuite> ClientRegistration<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> GenericArray<u8, ClientRegistrationLen<CS>>
where
// ClientRegistration: KgSk + KgPk
<OprfGroup<CS> as Group>::ScalarLen: Add<<OprfGroup<CS> as Group>::ElemLen>,
<OprfGroup<CS> as voprf::Group>::ScalarLen: Add<<OprfGroup<CS> as voprf::Group>::ElemLen>,
ClientRegistrationLen<CS>: ArrayLength<u8>,
{
self.oprf_client
@@ -327,28 +285,18 @@ impl<CS: CipherSuite> ClientRegistration<CS> {
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let client_len = <OprfGroup<CS> as Group>::ScalarLen::USIZE;
let element_len = <OprfGroup<CS> as Group>::ElemLen::USIZE;
let checked_slice =
check_slice_size(input, client_len + element_len, "client_registration")?;
pub fn deserialize(mut input: &[u8]) -> Result<Self, ProtocolError> {
let oprf_client = OprfClient::deserialize(input)?;
input = &input[OprfClientLen::<CS::OprfCs>::USIZE..];
let blinded_element = BlindedElement::deserialize(input)?;
Ok(Self {
oprf_client: voprf::OprfClient::deserialize(&checked_slice[..client_len])?,
blinded_element: voprf::BlindedElement::deserialize(&checked_slice[client_len..])?,
oprf_client,
blinded_element,
})
}
/// Only used for testing zeroize
#[cfg(test)]
pub(crate) fn to_vec(&self) -> std::vec::Vec<u8> {
[
self.oprf_client.serialize().to_vec(),
self.blinded_element.serialize().to_vec(),
]
.concat()
}
/// Returns an initial "blinded" request to send to the server, as well as a
/// [`ClientRegistration`]
pub fn start<R: RngCore + CryptoRng>(
@@ -433,12 +381,9 @@ impl<CS: CipherSuite> ServerRegistration<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> GenericArray<u8, ServerRegistrationLen<CS>>
where
// Envelope: Nonce + Hash
NonceLen: Add<OutputSize<OprfHash<CS>>>,
EnvelopeLen<CS>: ArrayLength<u8>,
// RegistrationUpload: (KePk + Hash) + Envelope
<CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<OprfHash<CS>>>,
Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<OprfHash<CS>>>:
<KeGroup<CS> as Group>::PkLen: Add<OutputSize<OprfHash<CS>>>,
Sum<<KeGroup<CS> as Group>::PkLen, OutputSize<OprfHash<CS>>>:
ArrayLength<u8> + Add<EnvelopeLen<CS>>,
RegistrationUploadLen<CS>: ArrayLength<u8>,
// ServerRegistration = RegistrationUpload
@@ -457,7 +402,7 @@ impl<CS: CipherSuite> ServerRegistration<CS> {
/// See [`ServerRegistration::start()`] for the regular path.
pub fn start_with_key_material<SK: Clone, OS: Clone>(
server_setup: &ServerSetup<CS, SK, OS>,
key_material: GenericArray<u8, <OprfGroup<CS> as Group>::ScalarLen>,
key_material: GenericArray<u8, <OprfGroup<CS> as voprf::Group>::ScalarLen>,
message: RegistrationRequest<CS>,
) -> Result<ServerRegistrationStartResult<CS>, ProtocolError> {
let oprf_key = oprf_key_from_key_material::<CS>(key_material)?;
@@ -486,7 +431,7 @@ impl<CS: CipherSuite> ServerRegistration<CS> {
ikm: oprf_seed,
info,
} = server_setup.key_material_info(credential_identifier);
let key_material = oprf_key_material::<CS>(&oprf_seed, &info)?;
let key_material = oprf_key_material::<CS>(&oprf_seed.0, &info)?;
Self::start_with_key_material(server_setup, key_material, message)
}
@@ -510,18 +455,20 @@ impl<CS: CipherSuite> ServerRegistration<CS> {
// =====
pub(crate) type ClientLoginLen<CS: CipherSuite> =
Sum<Sum<<OprfGroup<CS> as Group>::ScalarLen, CredentialRequestLen<CS>>, Ke1StateLen<CS>>;
Sum<Sum<<OprfGroup<CS> as voprf::Group>::ScalarLen, CredentialRequestLen<CS>>, Ke1StateLen<CS>>;
impl<CS: CipherSuite> ClientLogin<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> GenericArray<u8, ClientLoginLen<CS>>
where
// CredentialRequest: KgPk + Ke1Message
<OprfGroup<CS> as Group>::ElemLen: Add<Ke1MessageLen<CS>>,
<CS::KeyExchange as KeyExchange>::KE1Message: Serialize,
<OprfGroup<CS> as voprf::Group>::ElemLen: Add<Ke1MessageLen<CS>>,
CredentialRequestLen<CS>: ArrayLength<u8>,
// ClientLogin: KgSk + CredentialRequest + Ke1State
<OprfGroup<CS> as Group>::ScalarLen: Add<CredentialRequestLen<CS>>,
Sum<<OprfGroup<CS> as Group>::ScalarLen, CredentialRequestLen<CS>>:
<OprfGroup<CS> as voprf::Group>::ScalarLen: Add<CredentialRequestLen<CS>>,
<CS::KeyExchange as KeyExchange>::KE1State: Serialize,
Sum<<OprfGroup<CS> as voprf::Group>::ScalarLen, CredentialRequestLen<CS>>:
ArrayLength<u8> + Add<Ke1StateLen<CS>>,
ClientLoginLen<CS>: ArrayLength<u8>,
{
@@ -532,23 +479,18 @@ impl<CS: CipherSuite> ClientLogin<CS> {
}
/// Deserialization from bytes
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError> {
let client_len = <OprfGroup<CS> as Group>::ScalarLen::USIZE;
let request_len = <OprfGroup<CS> as Group>::ElemLen::USIZE + Ke1MessageLen::<CS>::USIZE;
let state_len = Ke1StateLen::<CS>::USIZE;
let checked_slice =
check_slice_size(input, client_len + request_len + state_len, "client_login")?;
pub fn deserialize(mut input: &[u8]) -> Result<Self, ProtocolError>
where
<CS::KeyExchange as KeyExchange>::KE1Message: Deserialize + Serialize,
<CS::KeyExchange as KeyExchange>::KE1State: Deserialize + Serialize,
{
let oprf_client = OprfClient::deserialize(input)?;
input = &input[OprfClientLen::<CS::OprfCs>::USIZE..];
let ke1_state =
<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE1State::deserialize(
&checked_slice[client_len + request_len..],
)?;
Ok(Self {
oprf_client: voprf::OprfClient::deserialize(&checked_slice[..client_len])?,
credential_request: CredentialRequest::deserialize(
&checked_slice[client_len..client_len + request_len],
)?,
ke1_state,
oprf_client,
credential_request: CredentialRequest::deserialize_take(&mut input)?,
ke1_state: <CS::KeyExchange as KeyExchange>::KE1State::deserialize_take(&mut input)?,
})
}
}
@@ -561,7 +503,7 @@ impl<CS: CipherSuite> ClientLogin<CS> {
password: &[u8],
) -> Result<ClientLoginStartResult<CS>, ProtocolError> {
let blind_result = blind::<CS, _>(rng, password)?;
let (ke1_state, ke1_message) = CS::KeyExchange::generate_ke1::<CS::OprfCs, _>(rng)?;
let (ke1_state, ke1_message) = CS::KeyExchange::generate_ke1(rng)?;
let credential_request = CredentialRequest {
blinded_element: blind_result.message,
@@ -580,19 +522,13 @@ impl<CS: CipherSuite> ClientLogin<CS> {
/// "Unblinds" the server's answer and returns the opened assets from the
/// server
pub fn finish(
pub fn finish<R: CryptoRng + RngCore>(
self,
rng: &mut R,
password: &[u8],
credential_response: CredentialResponse<CS>,
params: ClientLoginFinishParameters<CS>,
) -> Result<ClientLoginFinishResult<CS>, ProtocolError>
where
// MaskedResponse: (Nonce + Hash) + KePk
NonceLen: Add<OutputSize<OprfHash<CS>>>,
Sum<NonceLen, OutputSize<OprfHash<CS>>>:
ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
MaskedResponseLen<CS>: ArrayLength<u8>,
{
) -> Result<ClientLoginFinishResult<CS>, ProtocolError> {
// Check if beta value from server is equal to alpha value from client
if self
.credential_request
@@ -639,29 +575,19 @@ impl<CS: CipherSuite> ClientLogin<CS> {
err => err,
})?;
let beta = OprfGroup::<CS>::serialize_elem(credential_response.evaluation_element.value());
let credential_response_component = CredentialResponse::<CS>::serialize_without_ke(
&beta,
&credential_response.masking_nonce,
&credential_response.masked_response,
);
let blinded_element =
OprfGroup::<CS>::serialize_elem(self.credential_request.blinded_element.value());
let ke1_message = self.credential_request.ke1_message.serialize();
let serialized_credential_request =
CredentialRequest::<CS>::serialize_iter(&blinded_element, &ke1_message);
let context = SerializedContext::from(params.context)?;
let result = CS::KeyExchange::generate_ke3(
credential_response_component,
rng,
self.credential_request.to_parts(),
self.credential_request.ke1_message.clone(),
credential_response.to_parts(),
credential_response.ke2_message,
&self.ke1_state,
serialized_credential_request,
server_s_pk.clone(),
opened_envelope.client_static_keypair.private().clone(),
opened_envelope.id_u.iter(),
opened_envelope.id_s.iter(),
params.context.unwrap_or(&[]),
opened_envelope.identifiers,
context,
)?;
Ok(ClientLoginFinishResult {
@@ -683,16 +609,22 @@ impl<CS: CipherSuite> ClientLogin<CS> {
impl<CS: CipherSuite> ServerLogin<CS> {
/// Serialization into bytes
pub fn serialize(&self) -> GenericArray<u8, Ke2StateLen<CS>> {
pub fn serialize(&self) -> GenericArray<u8, Ke2StateLen<CS>>
where
<CS::KeyExchange as KeyExchange>::KE2State<CS>: Serialize,
{
self.ke2_state.serialize()
}
/// Deserialization from bytes
pub fn deserialize(bytes: &[u8]) -> Result<Self, ProtocolError> {
pub fn deserialize(mut bytes: &[u8]) -> Result<Self, ProtocolError>
where
<CS::KeyExchange as KeyExchange>::KE2State<CS>: Deserialize,
{
Ok(Self {
ke2_state:
<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE2State::deserialize(
bytes,
<<CS::KeyExchange as KeyExchange>::KE2State<CS> as Deserialize>::deserialize_take(
&mut bytes,
)?,
})
}
@@ -703,24 +635,17 @@ impl<CS: CipherSuite> ServerLogin<CS> {
///
/// See [`ServerLogin::start()`] for the regular path. Or
/// [`ServerLogin::builder()`] with just a remote private key.
pub fn builder_with_key_material<R: RngCore + CryptoRng, SK: Clone, OS: Clone>(
pub fn builder_with_key_material<'a, R: RngCore + CryptoRng, SK: Clone, OS: Clone>(
rng: &mut R,
server_setup: &ServerSetup<CS, SK, OS>,
key_material: GenericArray<u8, <OprfGroup<CS> as Group>::ScalarLen>,
key_material: GenericArray<u8, <OprfGroup<CS> as voprf::Group>::ScalarLen>,
password_file: Option<ServerRegistration<CS>>,
credential_request: CredentialRequest<CS>,
ServerLoginStartParameters {
ServerLoginParameters {
context,
identifiers,
}: ServerLoginStartParameters,
) -> Result<ServerLoginBuilder<CS, SK>, ProtocolError>
where
// MaskedResponse: (Nonce + Hash) + KePk
NonceLen: Add<OutputSize<OprfHash<CS>>>,
Sum<NonceLen, OutputSize<OprfHash<CS>>>:
ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
MaskedResponseLen<CS>: ArrayLength<u8>,
{
}: ServerLoginParameters<'a, 'a>,
) -> Result<ServerLoginBuilder<'a, CS, SK>, ProtocolError> {
let record = CtOption::new(
ServerRegistration::dummy(rng, server_setup),
Choice::from(password_file.is_none() as u8),
@@ -729,7 +654,7 @@ impl<CS: CipherSuite> ServerLogin<CS> {
.unwrap_or_else(|| password_file.unwrap());
let client_s_pk = record.0.client_s_pk.clone();
let context = context.unwrap_or(&[]);
let context = SerializedContext::from(context)?;
let server_s_pk = server_setup.keypair.public();
let mut masking_nonce = GenericArray::<_, NonceLen>::default();
@@ -742,34 +667,31 @@ impl<CS: CipherSuite> ServerLogin<CS> {
&record.0.envelope,
)?;
let (id_u, id_s) = bytestrings_from_identifiers::<CS::KeGroup>(
let serialized_client_s_pk = client_s_pk.serialize();
let serialized_server_s_pk = server_s_pk.serialize();
let identifiers = SerializedIdentifiers::<KeGroup<CS>>::from_identifiers(
identifiers,
client_s_pk.serialize(),
server_s_pk.serialize(),
serialized_client_s_pk.clone(),
serialized_server_s_pk.clone(),
)?;
let blinded_element =
OprfGroup::<CS>::serialize_elem(credential_request.blinded_element.value());
let ke1_message = credential_request.ke1_message.serialize();
let credential_request_bytes =
CredentialRequest::<CS>::serialize_iter(&blinded_element, &ke1_message);
let oprf_key = oprf_key_from_key_material::<CS>(key_material)?;
let server = voprf::OprfServer::new_with_key(&oprf_key).map_err(ProtocolError::from)?;
let evaluation_element = server.blind_evaluate(&credential_request.blinded_element);
let beta = OprfGroup::<CS>::serialize_elem(evaluation_element.value());
let credential_response_component =
CredentialResponse::<CS>::serialize_without_ke(&beta, &masking_nonce, &masked_response);
let credential_response = CredentialResponseParts::new(
&evaluation_element,
masking_nonce,
masked_response.clone(),
);
let ke2_builder = CS::KeyExchange::ke2_builder::<CS::OprfCs, _>(
let ke2_builder = CS::KeyExchange::ke2_builder(
rng,
credential_request_bytes,
credential_response_component,
credential_request.to_parts(),
credential_request.ke1_message.clone(),
credential_response,
client_s_pk,
id_u.iter(),
id_s.iter(),
identifiers,
context,
)?;
@@ -787,26 +709,19 @@ impl<CS: CipherSuite> ServerLogin<CS> {
/// Create a [`ServerLoginBuilder`] to use with a remote private key.
///
/// See [`ServerLogin::start()`] for the regular path.
pub fn builder<R: RngCore + CryptoRng, SK: Clone>(
pub fn builder<'a, R: RngCore + CryptoRng, SK: Clone>(
rng: &mut R,
server_setup: &ServerSetup<CS, SK>,
password_file: Option<ServerRegistration<CS>>,
credential_request: CredentialRequest<CS>,
credential_identifier: &[u8],
params: ServerLoginStartParameters,
) -> Result<ServerLoginBuilder<CS, SK>, ProtocolError>
where
// MaskedResponse: (Nonce + Hash) + KePk
NonceLen: Add<OutputSize<OprfHash<CS>>>,
Sum<NonceLen, OutputSize<OprfHash<CS>>>:
ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
MaskedResponseLen<CS>: ArrayLength<u8>,
{
params: ServerLoginParameters<'a, 'a>,
) -> Result<ServerLoginBuilder<'a, CS, SK>, ProtocolError> {
let KeyMaterialInfo {
ikm: oprf_seed,
info,
} = server_setup.key_material_info(credential_identifier);
let key_material = oprf_key_material::<CS>(&oprf_seed, &info)?;
let key_material = oprf_key_material::<CS>(&oprf_seed.0, &info)?;
Self::builder_with_key_material(
rng,
@@ -820,7 +735,7 @@ impl<CS: CipherSuite> ServerLogin<CS> {
pub(crate) fn build<SK: Clone>(
builder: ServerLoginBuilder<CS, SK>,
input: <CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE2BuilderInput,
input: <CS::KeyExchange as KeyExchange>::KE2BuilderInput<CS>,
) -> Result<ServerLoginStartResult<CS>, ProtocolError> {
let result = CS::KeyExchange::build_ke2(builder.ke2_builder.clone(), input)?;
@@ -853,15 +768,8 @@ impl<CS: CipherSuite> ServerLogin<CS> {
password_file: Option<ServerRegistration<CS>>,
credential_request: CredentialRequest<CS>,
credential_identifier: &[u8],
parameters: ServerLoginStartParameters,
) -> Result<ServerLoginStartResult<CS>, ProtocolError>
where
// MaskedResponse: (Nonce + Hash) + KePk
NonceLen: Add<OutputSize<OprfHash<CS>>>,
Sum<NonceLen, OutputSize<OprfHash<CS>>>:
ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
MaskedResponseLen<CS>: ArrayLength<u8>,
{
parameters: ServerLoginParameters,
) -> Result<ServerLoginStartResult<CS>, ProtocolError> {
let builder = Self::builder(
rng,
server_setup,
@@ -872,6 +780,7 @@ impl<CS: CipherSuite> ServerLogin<CS> {
)?;
let input = CS::KeyExchange::generate_ke2_input(
&builder.ke2_builder,
rng,
server_setup.keypair.private(),
);
@@ -883,10 +792,15 @@ impl<CS: CipherSuite> ServerLogin<CS> {
pub fn finish(
self,
message: CredentialFinalization<CS>,
parameters: ServerLoginParameters,
) -> Result<ServerLoginFinishResult<CS>, ProtocolError> {
let session_key = <CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::finish_ke(
let context = SerializedContext::from(parameters.context)?;
let session_key = <CS::KeyExchange as KeyExchange>::finish_ke(
message.ke3_message,
&self.ke2_state,
parameters.identifiers,
context,
)?;
Ok(ServerLoginFinishResult {
@@ -945,7 +859,7 @@ pub struct ClientRegistrationFinishResult<CS: CipherSuite> {
/// The export key output by client registration
pub export_key: Output<OprfHash<CS>>,
/// The server's static public key
pub server_s_pk: PublicKey<CS::KeGroup>,
pub server_s_pk: PublicKey<KeGroup<CS>>,
/// Instance of the ClientRegistration, only used in tests for checking
/// zeroize
#[cfg(test)]
@@ -966,7 +880,7 @@ pub struct ServerRegistrationStartResult<CS: CipherSuite> {
pub message: RegistrationResponse<CS>,
/// OPRF key, only used in tests
#[cfg(test)]
pub oprf_key: GenericArray<u8, <OprfGroup<CS> as Group>::ScalarLen>,
pub oprf_key: GenericArray<u8, <OprfGroup<CS> as voprf::Group>::ScalarLen>,
}
/// Contains the fields that are returned by a client login start
@@ -1011,20 +925,20 @@ pub struct ClientLoginFinishResult<CS: CipherSuite> {
/// The message to send to the server to complete the protocol
pub message: CredentialFinalization<CS>,
/// The session key
pub session_key: Output<OprfHash<CS>>,
pub session_key: Output<KeHash<CS>>,
/// The client-side export key
pub export_key: Output<OprfHash<CS>>,
/// The server's static public key
pub server_s_pk: PublicKey<CS::KeGroup>,
pub server_s_pk: PublicKey<KeGroup<CS>>,
/// Instance of the ClientLogin, only used in tests for checking zeroize
#[cfg(test)]
pub state: ClientLogin<CS>,
/// Handshake secret, only used in tests
#[cfg(test)]
pub handshake_secret: Output<OprfHash<CS>>,
pub handshake_secret: Output<KeHash<CS>>,
/// Client MAC key, only used in tests
#[cfg(test)]
pub client_mac_key: Output<OprfHash<CS>>,
pub client_mac_key: Output<KeHash<CS>>,
}
/// Contains the fields that are returned by a server login finish
@@ -1033,16 +947,16 @@ pub struct ClientLoginFinishResult<CS: CipherSuite> {
#[cfg_attr(test, derive_where(Debug; ServerLogin<CS>))]
pub struct ServerLoginFinishResult<CS: CipherSuite> {
/// The session key between client and server
pub session_key: Output<OprfHash<CS>>,
pub session_key: Output<KeHash<CS>>,
/// Instance of the ClientRegistration, only used in tests for checking
/// zeroize
#[cfg(test)]
pub state: ServerLogin<CS>,
}
/// Optional parameters for server login start
/// Optional parameters for server login start and finish
#[derive(Clone, Debug, Default)]
pub struct ServerLoginStartParameters<'c, 'i> {
pub struct ServerLoginParameters<'c, 'i> {
/// Specifying a context field that the client must agree on
pub context: Option<&'c [u8]>,
/// Specifying a user identifier and server identifier that will be matched
@@ -1054,9 +968,10 @@ pub struct ServerLoginStartParameters<'c, 'i> {
#[derive_where(Clone)]
#[derive_where(
Debug;
<KeGroup<CS> as Group>::Pk,
voprf::EvaluationElement<CS::OprfCs>,
<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE2Message,
<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE2State,
<CS::KeyExchange as KeyExchange>::KE2Message,
<CS::KeyExchange as KeyExchange>::KE2State<CS>,
)]
pub struct ServerLoginStartResult<CS: CipherSuite> {
/// The message to send back to the client
@@ -1065,13 +980,13 @@ pub struct ServerLoginStartResult<CS: CipherSuite> {
pub state: ServerLogin<CS>,
/// Handshake secret, only used in tests
#[cfg(test)]
pub handshake_secret: Output<OprfHash<CS>>,
pub handshake_secret: Output<KeHash<CS>>,
/// Server MAC key, only used in tests
#[cfg(test)]
pub server_mac_key: Output<OprfHash<CS>>,
pub server_mac_key: Output<KeHash<CS>>,
/// OPRF key, only used in tests
#[cfg(test)]
pub oprf_key: GenericArray<u8, <OprfGroup<CS> as Group>::ScalarLen>,
pub oprf_key: GenericArray<u8, <OprfGroup<CS> as voprf::Group>::ScalarLen>,
}
////////////////////////////////////////////////
@@ -1105,8 +1020,8 @@ fn get_password_derived_key<CS: CipherSuite>(
fn oprf_key_material<CS: CipherSuite>(
oprf_seed: &Output<OprfHash<CS>>,
info: &[&[u8]],
) -> Result<GenericArray<u8, <OprfGroup<CS> as Group>::ScalarLen>, InternalError> {
let mut ikm = GenericArray::<_, <OprfGroup<CS> as Group>::ScalarLen>::default();
) -> Result<GenericArray<u8, <OprfGroup<CS> as voprf::Group>::ScalarLen>, InternalError> {
let mut ikm = GenericArray::<_, <OprfGroup<CS> as voprf::Group>::ScalarLen>::default();
Hkdf::<OprfHash<CS>>::from_prk(oprf_seed)
.ok()
.and_then(|hkdf| hkdf.expand_multi_info(info, &mut ikm).ok())
@@ -1116,8 +1031,8 @@ fn oprf_key_material<CS: CipherSuite>(
}
fn oprf_key_from_key_material<CS: CipherSuite>(
input: GenericArray<u8, <OprfGroup<CS> as Group>::ScalarLen>,
) -> Result<GenericArray<u8, <OprfGroup<CS> as Group>::ScalarLen>, InternalError> {
input: GenericArray<u8, <OprfGroup<CS> as voprf::Group>::ScalarLen>,
) -> Result<GenericArray<u8, <OprfGroup<CS> as voprf::Group>::ScalarLen>, InternalError> {
Ok(OprfGroup::<CS>::serialize_scalar(voprf::derive_key::<
CS::OprfCs,
>(
@@ -1132,42 +1047,31 @@ fn oprf_key_from_key_material<CS: CipherSuite>(
derive(serde::Deserialize, serde::Serialize),
serde(bound = "")
)]
#[derive_where(Clone, ZeroizeOnDrop)]
#[derive_where(Clone, Zeroize)]
#[derive_where(Debug, Eq, Hash, PartialEq)]
pub(crate) struct MaskedResponse<CS: CipherSuite> {
pub(crate) nonce: GenericArray<u8, NonceLen>,
pub(crate) hash: Output<OprfHash<CS>>,
pub(crate) pk: GenericArray<u8, <CS::KeGroup as KeGroup>::PkLen>,
pub(crate) pk: GenericArray<u8, <KeGroup<CS> as Group>::PkLen>,
}
pub(crate) type MaskedResponseLen<CS: CipherSuite> =
Sum<Sum<NonceLen, OutputSize<OprfHash<CS>>>, <CS::KeGroup as KeGroup>::PkLen>;
Sum<Sum<OutputSize<OprfHash<CS>>, NonceLen>, <KeGroup<CS> as Group>::PkLen>;
impl<CS: CipherSuite> MaskedResponse<CS> {
pub(crate) fn serialize(&self) -> GenericArray<u8, MaskedResponseLen<CS>>
where
// MaskedResponse: (Nonce + Hash) + KePk
NonceLen: Add<OutputSize<OprfHash<CS>>>,
Sum<NonceLen, OutputSize<OprfHash<CS>>>:
ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
MaskedResponseLen<CS>: ArrayLength<u8>,
{
self.nonce.concat(self.hash.clone()).concat(self.pk.clone())
pub(crate) fn serialize(&self) -> GenericArray<u8, MaskedResponseLen<CS>> {
self.nonce.concat_ext(&self.hash).concat(self.pk.clone())
}
pub(crate) fn deserialize(bytes: &[u8]) -> Self {
let nonce = NonceLen::USIZE;
let hash = nonce + OutputSize::<OprfHash<CS>>::USIZE;
let pk = hash + <CS::KeGroup as KeGroup>::PkLen::USIZE;
Self {
nonce: GenericArray::clone_from_slice(&bytes[..nonce]),
hash: GenericArray::clone_from_slice(&bytes[nonce..hash]),
pk: GenericArray::clone_from_slice(&bytes[hash..pk]),
}
pub(crate) fn deserialize_take(bytes: &mut &[u8]) -> Result<Self, ProtocolError> {
Ok(Self {
nonce: bytes.take_array("masked nonce")?,
hash: bytes.take_array("masked hash")?,
pk: bytes.take_array("masked public key")?,
})
}
pub(crate) fn iter(&self) -> impl Iterator<Item = &[u8]> {
pub(crate) fn iter(&self) -> impl Clone + Iterator<Item = &[u8]> {
[self.nonce.as_slice(), &self.hash, &self.pk].into_iter()
}
}
@@ -1175,15 +1079,9 @@ impl<CS: CipherSuite> MaskedResponse<CS> {
fn mask_response<CS: CipherSuite>(
masking_key: &[u8],
masking_nonce: &[u8],
server_s_pk: &PublicKey<CS::KeGroup>,
server_s_pk: &PublicKey<KeGroup<CS>>,
envelope: &Envelope<CS>,
) -> Result<MaskedResponse<CS>, ProtocolError>
where
// MaskedResponse: (Nonce + Hash) + KePk
NonceLen: Add<OutputSize<OprfHash<CS>>>,
Sum<NonceLen, OutputSize<OprfHash<CS>>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
MaskedResponseLen<CS>: ArrayLength<u8>,
{
) -> Result<MaskedResponse<CS>, ProtocolError> {
let mut xor_pad = GenericArray::<_, MaskedResponseLen<CS>>::default();
Hkdf::<OprfHash<CS>>::from_prk(masking_key)
@@ -1201,20 +1099,14 @@ where
*x1 ^= x2
}
Ok(MaskedResponse::deserialize(&xor_pad))
MaskedResponse::deserialize_take(&mut (xor_pad.as_slice()))
}
fn unmask_response<CS: CipherSuite>(
masking_key: &[u8],
masking_nonce: &[u8],
masked_response: &MaskedResponse<CS>,
) -> Result<(PublicKey<CS::KeGroup>, Envelope<CS>), ProtocolError>
where
// MaskedResponse: (Nonce + Hash) + KePk
NonceLen: Add<OutputSize<OprfHash<CS>>>,
Sum<NonceLen, OutputSize<OprfHash<CS>>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
MaskedResponseLen<CS>: ArrayLength<u8>,
{
) -> Result<(PublicKey<KeGroup<CS>>, Envelope<CS>), ProtocolError> {
let mut xor_pad = GenericArray::<_, MaskedResponseLen<CS>>::default();
Hkdf::<OprfHash<CS>>::from_prk(masking_key)
@@ -1226,34 +1118,14 @@ where
*x1 ^= x2
}
let key_len = <CS::KeGroup as KeGroup>::PkLen::USIZE;
let server_s_pk = PublicKey::deserialize(&xor_pad[..key_len])
.map_err(|_| ProtocolError::SerializationError)?;
let envelope = Envelope::deserialize(&xor_pad[key_len..])?;
let mut xor_pad = xor_pad.as_slice();
let server_s_pk =
PublicKey::deserialize_take(&mut xor_pad).map_err(|_| ProtocolError::SerializationError)?;
let envelope = Envelope::deserialize_take(&mut xor_pad)?;
Ok((server_s_pk, envelope))
}
#[allow(clippy::type_complexity)]
pub(crate) fn bytestrings_from_identifiers<KG: KeGroup>(
ids: Identifiers,
client_s_pk: GenericArray<u8, KG::PkLen>,
server_s_pk: GenericArray<u8, KG::PkLen>,
) -> Result<(Input<U2, KG::PkLen>, Input<U2, KG::PkLen>), ProtocolError> {
let client_identity = if let Some(client) = ids.client {
Input::<U2, _>::from(client)?
} else {
Input::<U2, _>::from_owned(client_s_pk)?
};
let server_identity = if let Some(server) = ids.server {
Input::<U2, _>::from(server)?
} else {
Input::<U2, _>::from_owned(server_s_pk)?
};
Ok((client_identity, server_identity))
}
/// Internal function for computing the blind result by calling the voprf
/// library. Note that for tests, we use the deterministic blinding in order to
/// be able to set the blinding factor directly from the passed-in rng.
@@ -1266,10 +1138,11 @@ fn blind<CS: CipherSuite, R: RngCore + CryptoRng>(
#[cfg(test)]
let result = {
let mut blind_bytes = GenericArray::<_, <OprfGroup<CS> as Group>::ScalarLen>::default();
let mut blind_bytes =
GenericArray::<_, <OprfGroup<CS> as voprf::Group>::ScalarLen>::default();
let blind = loop {
rng.fill_bytes(&mut blind_bytes);
if let Ok(scalar) = <OprfGroup<CS> as Group>::deserialize_scalar(&blind_bytes) {
if let Ok(scalar) = <OprfGroup<CS> as voprf::Group>::deserialize_scalar(&blind_bytes) {
break scalar;
}
};
@@ -1278,3 +1151,91 @@ fn blind<CS: CipherSuite, R: RngCore + CryptoRng>(
Ok(result)
}
//////////////////////////
// Test Implementations //
//===================== //
//////////////////////////
#[cfg(test)]
use crate::serialization::AssertZeroized;
#[cfg(test)]
impl<CS: CipherSuite> AssertZeroized for ClientRegistration<CS> {
fn assert_zeroized(&self) {
let Self {
oprf_client,
blinded_element,
} = self;
for byte in oprf_client
.serialize()
.iter()
.chain(&blinded_element.serialize())
{
assert_eq!(byte, &0);
}
}
}
#[cfg(test)]
impl<CS: CipherSuite> AssertZeroized for ServerRegistration<CS>
where
<KeGroup<CS> as Group>::Pk: AssertZeroized,
{
fn assert_zeroized(&self) {
let RegistrationUpload {
envelope,
masking_key,
client_s_pk,
} = &self.0;
envelope.assert_zeroized();
assert_eq!(masking_key, &GenericArray::default());
client_s_pk.assert_zeroized();
}
}
#[cfg(test)]
impl<CS: CipherSuite> AssertZeroized for ClientLogin<CS>
where
<CS::KeyExchange as KeyExchange>::KE1State: AssertZeroized,
<CS::KeyExchange as KeyExchange>::KE1Message: AssertZeroized,
{
fn assert_zeroized(&self) {
let Self {
ke1_state,
credential_request,
oprf_client,
} = self;
let CredentialRequest {
blinded_element,
ke1_message,
} = credential_request;
ke1_state.assert_zeroized();
ke1_message.assert_zeroized();
for byte in oprf_client
.serialize()
.iter()
.chain(&blinded_element.serialize())
{
assert_eq!(byte, &0);
}
}
}
#[cfg(test)]
impl<CS: CipherSuite> AssertZeroized for ServerLogin<CS>
where
PublicKey<KeGroup<CS>>: AssertZeroized,
<CS::KeyExchange as KeyExchange>::KE2State<CS>: AssertZeroized,
{
fn assert_zeroized(&self) {
let Self { ke2_state } = self;
ke2_state.assert_zeroized();
}
}
+61 -91
View File
@@ -6,12 +6,12 @@
// of this source tree. You may select, at your option, one of the above-listed
// licenses.
use core::marker::PhantomData;
use core::ops::Add;
use digest::Update;
use generic_array::typenum::{U0, U2};
use generic_array::sequence::Concat;
use generic_array::typenum::Sum;
use generic_array::{ArrayLength, GenericArray};
use hmac::Mac;
use crate::errors::ProtocolError;
@@ -44,93 +44,19 @@ pub(crate) fn os2ip(input: &[u8]) -> Result<usize, ProtocolError> {
Ok(usize::from_be_bytes(output_array))
}
/// Computes `I2OSP(len(input), max_bytes) || input` and helps hold output
/// without allocation.
pub(crate) struct Input<'a, L1: ArrayLength<u8>, L2: ArrayLength<u8> = U0, L3: ArrayLength<u8> = U0>
{
octet: GenericArray<u8, L1>,
input: InnerInput<'a, L2, L3>,
}
enum InnerInput<'a, L1: ArrayLength<u8>, L2: ArrayLength<u8>> {
Owned(GenericArray<u8, L1>),
Borrowed(&'a [u8]),
Label(([&'a [u8]; 2], PhantomData<L2>)),
}
impl<'a, L1: ArrayLength<u8>, L2: ArrayLength<u8>, L3: ArrayLength<u8>> Input<'a, L1, L2, L3> {
// Variation of `serialize` that takes a borrowed `input
pub(crate) fn from(input: &'a [u8]) -> Result<Input<'a, L1, L2>, ProtocolError> {
Ok(Input {
octet: i2osp::<L1>(input.len())?,
input: InnerInput::Borrowed(input),
})
}
// Variation of `serialize` that takes an owned `input`
pub(crate) fn from_owned(
input: GenericArray<u8, L2>,
) -> Result<Input<'a, L1, L2>, ProtocolError> {
Ok(Input {
octet: i2osp::<L1>(input.len())?,
input: InnerInput::Owned(input),
})
}
// Variation of `serialize` that takes a label
pub(crate) fn from_label(
opaque: &'a [u8],
label: &'a [u8],
) -> Result<Input<'a, L1, U0, U2>, ProtocolError> {
Ok(Input {
octet: i2osp::<L1>(opaque.len() + label.len())?,
input: InnerInput::Label(([opaque, label], PhantomData)),
})
}
pub(crate) fn iter(&self) -> impl Iterator<Item = &[u8]> {
// Some magic to make it output the same type in all branches.
[self.octet.as_slice()]
.into_iter()
.chain(match &self.input {
InnerInput::Owned(bytes) => [bytes.as_slice()],
InnerInput::Borrowed(bytes) => [*bytes],
InnerInput::Label((iter, _)) => [iter[0]],
})
.chain(if let InnerInput::Label((iter, _)) = &self.input {
Some(iter[1])
} else {
None
})
}
}
impl<L1: ArrayLength<u8>, L2: ArrayLength<u8>> Input<'_, L1, L2, U0> {
pub(crate) fn to_array_2(&self) -> [&[u8]; 2] {
let input = match &self.input {
InnerInput::Borrowed(value) => value,
InnerInput::Owned(value) => value.as_slice(),
_ => unreachable!("unexpected `Serialize` constructed with wrong generics"),
};
[self.octet.as_slice(), input]
}
}
impl<L1: ArrayLength<u8>, L2: ArrayLength<u8>> Input<'_, L1, L2, U2> {
pub(crate) fn to_array_3(&self) -> [&[u8]; 3] {
match self.input {
InnerInput::Label((label, _)) => [self.octet.as_slice(), label[0], label[1]],
_ => unreachable!("unexpected `Serialize` constructed with wrong generics"),
}
}
}
pub(crate) trait UpdateExt {
fn update_iter<'a>(&mut self, iter: impl Iterator<Item = &'a [u8]>);
fn chain_iter<'a>(self, iter: impl Iterator<Item = &'a [u8]>) -> Self;
}
impl<T: Update> UpdateExt for T {
fn update_iter<'a>(&mut self, iter: impl Iterator<Item = &'a [u8]>) {
for bytes in iter {
self.update(bytes);
}
}
fn chain_iter<'a>(self, iter: impl Iterator<Item = &'a [u8]>) -> Self {
let mut self_ = self;
@@ -142,16 +68,60 @@ impl<T: Update> UpdateExt for T {
}
}
pub(crate) trait MacExt {
fn update_iter<'a>(&mut self, iter: impl Iterator<Item = &'a [u8]>);
pub(crate) trait SliceExt {
fn take_array<L: ArrayLength<u8>>(
self: &mut &Self,
name: &'static str,
) -> Result<GenericArray<u8, L>, ProtocolError>;
}
impl<T: Mac> MacExt for T {
fn update_iter<'a>(&mut self, iter: impl Iterator<Item = &'a [u8]>) {
for bytes in iter {
self.update(bytes);
impl SliceExt for [u8] {
fn take_array<L: ArrayLength<u8>>(
self: &mut &Self,
name: &'static str,
) -> Result<GenericArray<u8, L>, ProtocolError> {
if L::USIZE > self.len() {
return Err(ProtocolError::SizeError {
name,
len: L::USIZE,
actual_len: self.len(),
});
}
let (front, back) = self.split_at(L::USIZE);
*self = back;
Ok(GenericArray::clone_from_slice(front))
}
}
pub(crate) trait GenericArrayExt<O: ArrayLength<u8>> {
type Output: ArrayLength<u8>;
/// This allows us to concat two [`GenericArray`]s but with `where` bounds
/// `Other + Self`. Because sometimes `Self + Other` doesn't imply the
/// bounds and we have to add them to every call.
fn concat_ext(&self, rest: &GenericArray<u8, O>) -> GenericArray<u8, Self::Output>;
}
impl<L: ArrayLength<u8>, O: ArrayLength<u8>> GenericArrayExt<O> for GenericArray<u8, L>
where
O: Add<L>,
Sum<O, L>: ArrayLength<u8>,
{
type Output = Sum<O, L>;
fn concat_ext(&self, other: &GenericArray<u8, O>) -> GenericArray<u8, Self::Output> {
let mut output = GenericArray::<u8, O>::default().concat(GenericArray::<u8, L>::default());
output[..L::USIZE].copy_from_slice(self);
output[L::USIZE..].copy_from_slice(other);
output
}
}
#[cfg(test)]
pub(crate) trait AssertZeroized {
fn assert_zeroized(&self);
}
#[cfg(test)]
+458 -143
View File
@@ -17,17 +17,17 @@ use proptest::collection::vec;
use proptest::prelude::*;
use rand::rngs::OsRng;
use rand::RngCore;
use voprf::Group;
use voprf::Group as _;
use crate::ciphersuite::{CipherSuite, OprfGroup, OprfHash};
use crate::ciphersuite::{CipherSuite, KeGroup, OprfGroup, OprfHash};
use crate::envelope::{Envelope, EnvelopeLen, InnerEnvelopeMode};
use crate::errors::*;
use crate::hash::OutputSize;
use crate::key_exchange::group::KeGroup;
use crate::key_exchange::group::Group;
use crate::key_exchange::shared::NonceLen;
use crate::key_exchange::traits::{
Deserialize, Ke1MessageLen, Ke1StateLen, Ke2MessageLen, KeyExchange, Serialize,
};
use crate::key_exchange::tripledh::{NonceLen, TripleDh};
use crate::keypair::KeyPair;
use crate::messages::CredentialResponseWithoutKeLen;
use crate::opaque::{ClientLoginLen, ClientRegistrationLen, MaskedResponseLen};
@@ -35,47 +35,101 @@ use crate::serialization::{i2osp, os2ip};
use crate::*;
#[cfg(feature = "ristretto255")]
struct Ristretto255;
struct TripleDhRistretto255;
#[cfg(feature = "ristretto255")]
impl CipherSuite for Ristretto255 {
type OprfCs = crate::Ristretto255;
type KeGroup = crate::Ristretto255;
type KeyExchange = TripleDh;
impl CipherSuite for TripleDhRistretto255 {
type OprfCs = Ristretto255;
type KeyExchange = TripleDh<Ristretto255, sha2::Sha512>;
type Ksf = crate::ksf::Identity;
}
struct P256;
#[cfg(all(feature = "ristretto255", feature = "curve25519"))]
struct TripleDhCurve25519;
impl CipherSuite for P256 {
#[cfg(all(feature = "ristretto255", feature = "curve25519"))]
impl CipherSuite for TripleDhCurve25519 {
type OprfCs = Ristretto255;
type KeyExchange = TripleDh<Curve25519, sha2::Sha512>;
type Ksf = crate::ksf::Identity;
}
struct TripleDhP256;
impl CipherSuite for TripleDhP256 {
type OprfCs = ::p256::NistP256;
type KeGroup = ::p256::NistP256;
type KeyExchange = TripleDh;
type KeyExchange = TripleDh<::p256::NistP256, sha2::Sha256>;
type Ksf = crate::ksf::Identity;
}
struct P384;
struct TripleDhP384;
impl CipherSuite for P384 {
impl CipherSuite for TripleDhP384 {
type OprfCs = ::p384::NistP384;
type KeGroup = ::p384::NistP384;
type KeyExchange = TripleDh;
type KeyExchange = TripleDh<::p384::NistP384, sha2::Sha384>;
type Ksf = crate::ksf::Identity;
}
struct P521;
struct TripleDhP521;
impl CipherSuite for P521 {
impl CipherSuite for TripleDhP521 {
type OprfCs = ::p521::NistP521;
type KeGroup = ::p521::NistP521;
type KeyExchange = TripleDh;
type KeyExchange = TripleDh<::p521::NistP521, sha2::Sha512>;
type Ksf = crate::ksf::Identity;
}
fn random_point<CS: CipherSuite>() -> <CS::KeGroup as KeGroup>::Pk {
#[cfg(feature = "ecdsa")]
struct SigmaIP256;
#[cfg(feature = "ecdsa")]
impl CipherSuite for SigmaIP256 {
type OprfCs = ::p256::NistP256;
type KeyExchange =
SigmaI<Ecdsa<::p256::NistP256, sha2::Sha256>, ::p256::NistP256, sha2::Sha256>;
type Ksf = crate::ksf::Identity;
}
#[cfg(feature = "ecdsa")]
struct SigmaIP384;
#[cfg(feature = "ecdsa")]
impl CipherSuite for SigmaIP384 {
type OprfCs = ::p384::NistP384;
type KeyExchange =
SigmaI<Ecdsa<::p384::NistP384, sha2::Sha384>, ::p384::NistP384, sha2::Sha384>;
type Ksf = crate::ksf::Identity;
}
#[cfg(all(feature = "ristretto255", feature = "ed25519",))]
struct SigmaIEd25519;
#[cfg(all(feature = "ristretto255", feature = "ed25519",))]
impl CipherSuite for SigmaIEd25519 {
type OprfCs = Ristretto255;
type KeyExchange = SigmaI<PureEddsa<Ed25519>, Ristretto255, sha2::Sha512>;
type Ksf = crate::ksf::Identity;
}
#[cfg(all(feature = "ristretto255", feature = "ed25519"))]
struct SigmaIEd25519Ph;
#[cfg(all(feature = "ristretto255", feature = "ed25519",))]
impl CipherSuite for SigmaIEd25519Ph {
type OprfCs = Ristretto255;
type KeyExchange = SigmaI<HashEddsa<Ed25519>, Ristretto255, sha2::Sha512>;
type Ksf = crate::ksf::Identity;
}
fn random_point<CS: CipherSuite>() -> <KeGroup<CS> as Group>::Pk {
let mut rng = OsRng;
let sk = CS::KeGroup::random_sk(&mut rng);
CS::KeGroup::public_key(sk)
let sk = KeGroup::<CS>::random_sk(&mut rng);
KeGroup::<CS>::public_key(sk)
}
fn random_element<CS: CipherSuite>() -> <OprfGroup<CS> as voprf::Group>::Elem {
let mut rng = OsRng;
let scalar = OprfGroup::<CS>::random_scalar(&mut rng);
OprfGroup::<CS>::base_elem() * &scalar
}
#[test]
@@ -83,7 +137,7 @@ fn client_registration_roundtrip() -> Result<(), ProtocolError> {
fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
where
// ClientRegistration: KgSk + KgPk
<OprfGroup<CS> as Group>::ScalarLen: Add<<OprfGroup<CS> as Group>::ElemLen>,
<OprfGroup<CS> as voprf::Group>::ScalarLen: Add<<OprfGroup<CS> as voprf::Group>::ElemLen>,
ClientRegistrationLen<CS>: ArrayLength<u8>,
{
let pw = b"hunter2";
@@ -106,10 +160,20 @@ fn client_registration_roundtrip() -> Result<(), ProtocolError> {
}
#[cfg(feature = "ristretto255")]
inner::<Ristretto255>()?;
inner::<P256>()?;
inner::<P384>()?;
inner::<P521>()?;
inner::<TripleDhRistretto255>()?;
#[cfg(all(feature = "ristretto255", feature = "curve25519"))]
inner::<TripleDhCurve25519>()?;
inner::<TripleDhP256>()?;
inner::<TripleDhP384>()?;
inner::<TripleDhP521>()?;
#[cfg(feature = "ecdsa")]
inner::<SigmaIP256>()?;
#[cfg(feature = "ecdsa")]
inner::<SigmaIP384>()?;
#[cfg(all(feature = "ristretto255", feature = "ed25519"))]
inner::<SigmaIEd25519>()?;
#[cfg(all(feature = "ristretto255", feature = "ed25519"))]
inner::<SigmaIEd25519Ph>()?;
Ok(())
}
@@ -118,12 +182,9 @@ fn client_registration_roundtrip() -> Result<(), ProtocolError> {
fn server_registration_roundtrip() -> Result<(), ProtocolError> {
fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
where
// Envelope: Nonce + Hash
NonceLen: Add<OutputSize<OprfHash<CS>>>,
EnvelopeLen<CS>: ArrayLength<u8>,
// RegistrationUpload: (KePk + Hash) + Envelope
<CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<OprfHash<CS>>>,
Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<OprfHash<CS>>>:
<KeGroup<CS> as Group>::PkLen: Add<OutputSize<OprfHash<CS>>>,
Sum<<KeGroup<CS> as Group>::PkLen, OutputSize<OprfHash<CS>>>:
ArrayLength<u8> + Add<EnvelopeLen<CS>>,
RegistrationUploadLen<CS>: ArrayLength<u8>,
// ServerRegistration = RegistrationUpload
@@ -142,7 +203,7 @@ fn server_registration_roundtrip() -> Result<(), ProtocolError> {
// length-MAC_SIZE hmac
mock_envelope_bytes.extend_from_slice(&Output::<OprfHash<CS>>::default());
let mock_client_kp = KeyPair::<CS::KeGroup>::generate_random::<CS::OprfCs, _>(&mut rng);
let mock_client_kp = KeyPair::<KeGroup<CS>>::derive_random(&mut rng);
// serialization order: oprf_key, public key, envelope
let mut bytes = Vec::<u8>::new();
bytes.extend_from_slice(&mock_client_kp.public().serialize());
@@ -155,10 +216,20 @@ fn server_registration_roundtrip() -> Result<(), ProtocolError> {
}
#[cfg(feature = "ristretto255")]
inner::<Ristretto255>()?;
inner::<P256>()?;
inner::<P384>()?;
inner::<P521>()?;
inner::<TripleDhRistretto255>()?;
#[cfg(all(feature = "ristretto255", feature = "curve25519"))]
inner::<TripleDhCurve25519>()?;
inner::<TripleDhP256>()?;
inner::<TripleDhP384>()?;
inner::<TripleDhP521>()?;
#[cfg(feature = "ecdsa")]
inner::<SigmaIP256>()?;
#[cfg(feature = "ecdsa")]
inner::<SigmaIP384>()?;
#[cfg(all(feature = "ristretto255", feature = "ed25519"))]
inner::<SigmaIEd25519>()?;
#[cfg(all(feature = "ristretto255", feature = "ed25519"))]
inner::<SigmaIEd25519Ph>()?;
Ok(())
}
@@ -166,11 +237,11 @@ fn server_registration_roundtrip() -> Result<(), ProtocolError> {
#[test]
fn registration_request_roundtrip() -> Result<(), ProtocolError> {
fn inner<CS: CipherSuite>() -> Result<(), ProtocolError> {
let pt = random_point::<CS>();
let pt_bytes = CS::KeGroup::serialize_pk(pt);
let elem = random_element::<CS>();
let elem_bytes = OprfGroup::<CS>::serialize_elem(elem);
let mut input = Vec::new();
input.extend_from_slice(&pt_bytes);
input.extend_from_slice(&elem_bytes);
let r1 = RegistrationRequest::<CS>::deserialize(&input)?;
let r1_bytes = r1.serialize();
@@ -191,10 +262,20 @@ fn registration_request_roundtrip() -> Result<(), ProtocolError> {
}
#[cfg(feature = "ristretto255")]
inner::<Ristretto255>()?;
inner::<P256>()?;
inner::<P384>()?;
inner::<P521>()?;
inner::<TripleDhRistretto255>()?;
#[cfg(all(feature = "ristretto255", feature = "curve25519"))]
inner::<TripleDhCurve25519>()?;
inner::<TripleDhP256>()?;
inner::<TripleDhP384>()?;
inner::<TripleDhP521>()?;
#[cfg(feature = "ecdsa")]
inner::<SigmaIP256>()?;
#[cfg(feature = "ecdsa")]
inner::<SigmaIP384>()?;
#[cfg(all(feature = "ristretto255", feature = "ed25519"))]
inner::<SigmaIEd25519>()?;
#[cfg(all(feature = "ristretto255", feature = "ed25519"))]
inner::<SigmaIEd25519Ph>()?;
Ok(())
}
@@ -204,13 +285,13 @@ fn registration_response_roundtrip() -> Result<(), ProtocolError> {
fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
where
// RegistrationResponse: KgPk + KePk
<OprfGroup<CS> as Group>::ElemLen: Add<<CS::KeGroup as KeGroup>::PkLen>,
<OprfGroup<CS> as voprf::Group>::ElemLen: Add<<KeGroup<CS> as Group>::PkLen>,
RegistrationResponseLen<CS>: ArrayLength<u8>,
{
let pt = random_point::<CS>();
let beta_bytes = CS::KeGroup::serialize_pk(pt);
let elem = random_element::<CS>();
let beta_bytes = OprfGroup::<CS>::serialize_elem(elem);
let mut rng = OsRng;
let skp = KeyPair::<CS::KeGroup>::generate_random::<CS::OprfCs, _>(&mut rng);
let skp = KeyPair::<KeGroup<CS>>::derive_random(&mut rng);
let pubkey_bytes = skp.public().serialize();
let mut input = Vec::new();
@@ -238,10 +319,20 @@ fn registration_response_roundtrip() -> Result<(), ProtocolError> {
}
#[cfg(feature = "ristretto255")]
inner::<Ristretto255>()?;
inner::<P256>()?;
inner::<P384>()?;
inner::<P521>()?;
inner::<TripleDhRistretto255>()?;
#[cfg(all(feature = "ristretto255", feature = "curve25519"))]
inner::<TripleDhCurve25519>()?;
inner::<TripleDhP256>()?;
inner::<TripleDhP384>()?;
inner::<TripleDhP521>()?;
#[cfg(feature = "ecdsa")]
inner::<SigmaIP256>()?;
#[cfg(feature = "ecdsa")]
inner::<SigmaIP384>()?;
#[cfg(all(feature = "ristretto255", feature = "ed25519"))]
inner::<SigmaIEd25519>()?;
#[cfg(all(feature = "ristretto255", feature = "ed25519"))]
inner::<SigmaIEd25519Ph>()?;
Ok(())
}
@@ -250,17 +341,14 @@ fn registration_response_roundtrip() -> Result<(), ProtocolError> {
fn registration_upload_roundtrip() -> Result<(), ProtocolError> {
fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
where
// Envelope: Nonce + Hash
NonceLen: Add<OutputSize<OprfHash<CS>>>,
EnvelopeLen<CS>: ArrayLength<u8>,
// RegistrationUpload: (KePk + Hash) + Envelope
<CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<OprfHash<CS>>>,
Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<OprfHash<CS>>>:
<KeGroup<CS> as Group>::PkLen: Add<OutputSize<OprfHash<CS>>>,
Sum<<KeGroup<CS> as Group>::PkLen, OutputSize<OprfHash<CS>>>:
ArrayLength<u8> + Add<EnvelopeLen<CS>>,
RegistrationUploadLen<CS>: ArrayLength<u8>,
{
let mut rng = OsRng;
let skp = KeyPair::<CS::KeGroup>::generate_random::<CS::OprfCs, _>(&mut rng);
let skp = KeyPair::<KeGroup<CS>>::derive_random(&mut rng);
let pubkey_bytes = skp.public().serialize();
let mut key = [0u8; 32];
@@ -295,27 +383,38 @@ fn registration_upload_roundtrip() -> Result<(), ProtocolError> {
}
#[cfg(feature = "ristretto255")]
inner::<Ristretto255>()?;
inner::<P256>()?;
inner::<P384>()?;
inner::<P521>()?;
inner::<TripleDhRistretto255>()?;
#[cfg(all(feature = "ristretto255", feature = "curve25519"))]
inner::<TripleDhCurve25519>()?;
inner::<TripleDhP256>()?;
inner::<TripleDhP384>()?;
inner::<TripleDhP521>()?;
#[cfg(feature = "ecdsa")]
inner::<SigmaIP256>()?;
#[cfg(feature = "ecdsa")]
inner::<SigmaIP384>()?;
#[cfg(all(feature = "ristretto255", feature = "ed25519"))]
inner::<SigmaIEd25519>()?;
#[cfg(all(feature = "ristretto255", feature = "ed25519"))]
inner::<SigmaIEd25519Ph>()?;
Ok(())
}
#[test]
fn credential_request_roundtrip() -> Result<(), ProtocolError> {
fn triple_dh_credential_request_roundtrip() -> Result<(), ProtocolError> {
fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
where
<CS::KeyExchange as KeyExchange>::KE1Message: Deserialize + Serialize,
// CredentialRequest: KgPk + Ke1Message
<OprfGroup<CS> as Group>::ElemLen: Add<Ke1MessageLen<CS>>,
<OprfGroup<CS> as voprf::Group>::ElemLen: Add<Ke1MessageLen<CS>>,
CredentialRequestLen<CS>: ArrayLength<u8>,
{
let mut rng = OsRng;
let alpha = random_point::<CS>();
let alpha_bytes = CS::KeGroup::serialize_pk(alpha);
let alpha = random_element::<CS>();
let alpha_bytes = OprfGroup::<CS>::serialize_elem(alpha);
let client_e_kp = KeyPair::<CS::KeGroup>::generate_random::<CS::OprfCs, _>(&mut rng);
let client_e_kp = KeyPair::<KeGroup<CS>>::derive_random(&mut rng);
let mut client_nonce = [0u8; NonceLen::USIZE];
rng.fill_bytes(&mut client_nonce);
@@ -348,34 +447,33 @@ fn credential_request_roundtrip() -> Result<(), ProtocolError> {
}
#[cfg(feature = "ristretto255")]
inner::<Ristretto255>()?;
inner::<P256>()?;
inner::<P384>()?;
inner::<P521>()?;
inner::<TripleDhRistretto255>()?;
#[cfg(all(feature = "ristretto255", feature = "curve25519"))]
inner::<TripleDhCurve25519>()?;
inner::<TripleDhP256>()?;
inner::<TripleDhP384>()?;
inner::<TripleDhP521>()?;
Ok(())
}
#[test]
fn credential_response_roundtrip() -> Result<(), ProtocolError> {
fn triple_dh_credential_response_roundtrip() -> Result<(), ProtocolError> {
fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
where
<CS::KeyExchange as KeyExchange>::KE2Message: Deserialize,
// CredentialResponseWithoutKeLen: (KgPk + Nonce) + MaskedResponse
<OprfGroup<CS> as Group>::ElemLen: Add<NonceLen>,
Sum<<OprfGroup<CS> as Group>::ElemLen, NonceLen>:
<OprfGroup<CS> as voprf::Group>::ElemLen: Add<NonceLen>,
Sum<<OprfGroup<CS> as voprf::Group>::ElemLen, NonceLen>:
ArrayLength<u8> + Add<MaskedResponseLen<CS>>,
CredentialResponseWithoutKeLen<CS>: ArrayLength<u8>,
// MaskedResponse: (Nonce + Hash) + KePk
NonceLen: Add<OutputSize<OprfHash<CS>>>,
Sum<NonceLen, OutputSize<OprfHash<CS>>>:
ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
MaskedResponseLen<CS>: ArrayLength<u8>,
// CredentialResponse: CredentialResponseWithoutKeLen + Ke2Message
<CS::KeyExchange as KeyExchange>::KE2Message: Serialize,
CredentialResponseWithoutKeLen<CS>: Add<Ke2MessageLen<CS>>,
CredentialResponseLen<CS>: ArrayLength<u8>,
{
let pt = random_point::<CS>();
let pt_bytes = CS::KeGroup::serialize_pk(pt);
let elem = random_element::<CS>();
let elem_bytes = OprfGroup::<CS>::serialize_elem(elem);
let mut rng = OsRng;
@@ -383,10 +481,10 @@ fn credential_response_roundtrip() -> Result<(), ProtocolError> {
rng.fill_bytes(&mut masking_nonce);
let mut masked_response =
vec![0u8; <OprfGroup<CS> as Group>::ElemLen::USIZE + Envelope::<CS>::len()];
vec![0u8; <OprfGroup<CS> as voprf::Group>::ElemLen::USIZE + Envelope::<CS>::len()];
rng.fill_bytes(&mut masked_response);
let server_e_kp = KeyPair::<CS::KeGroup>::generate_random::<CS::OprfCs, _>(&mut rng);
let server_e_kp = KeyPair::<KeGroup<CS>>::derive_random(&mut rng);
let mut mac = Output::<OprfHash<CS>>::default();
rng.fill_bytes(&mut mac);
let mut server_nonce = [0u8; NonceLen::USIZE];
@@ -399,6 +497,94 @@ fn credential_response_roundtrip() -> Result<(), ProtocolError> {
]
.concat();
let mut input = Vec::new();
input.extend_from_slice(&elem_bytes);
input.extend_from_slice(&masking_nonce);
input.extend_from_slice(&masked_response);
input.extend_from_slice(&ke2m);
let l2 = CredentialResponse::<CS>::deserialize(&input).unwrap();
let l2_bytes = l2.serialize();
assert_eq!(input, *l2_bytes);
// Assert that identity group element is rejected
let identity = OprfGroup::<CS>::identity_elem();
let identity_bytes = OprfGroup::<CS>::serialize_elem(identity).to_vec();
assert!(matches!(
CredentialResponse::<CS>::deserialize(
&[
identity_bytes,
masking_nonce.to_vec(),
masked_response,
ke2m.to_vec()
]
.concat()
),
Err(ProtocolError::LibraryError(InternalError::OprfError(
voprf::Error::Deserialization,
)))
));
Ok(())
}
#[cfg(feature = "ristretto255")]
inner::<TripleDhRistretto255>()?;
#[cfg(all(feature = "ristretto255", feature = "curve25519"))]
inner::<TripleDhCurve25519>()?;
inner::<TripleDhP256>()?;
inner::<TripleDhP384>()?;
inner::<TripleDhP521>()?;
Ok(())
}
#[test]
#[cfg(feature = "ecdsa")]
fn sigma_i_ecdsa_credential_response_roundtrip() -> Result<(), ProtocolError> {
fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
where
<CS::KeyExchange as KeyExchange>::KE2Message: Deserialize,
// CredentialResponseWithoutKeLen: (KgPk + Nonce) + MaskedResponse
<OprfGroup<CS> as voprf::Group>::ElemLen: Add<NonceLen>,
Sum<<OprfGroup<CS> as voprf::Group>::ElemLen, NonceLen>:
ArrayLength<u8> + Add<MaskedResponseLen<CS>>,
CredentialResponseWithoutKeLen<CS>: ArrayLength<u8>,
// CredentialResponse: CredentialResponseWithoutKeLen + Ke2Message
<CS::KeyExchange as KeyExchange>::KE2Message: Serialize,
CredentialResponseWithoutKeLen<CS>: Add<Ke2MessageLen<CS>>,
CredentialResponseLen<CS>: ArrayLength<u8>,
{
let pt = random_point::<CS>();
let pt_bytes = KeGroup::<CS>::serialize_pk(pt);
let mut rng = OsRng;
let mut masking_nonce = [0u8; 32];
rng.fill_bytes(&mut masking_nonce);
let mut masked_response =
vec![0u8; <OprfGroup<CS> as voprf::Group>::ElemLen::USIZE + Envelope::<CS>::len()];
rng.fill_bytes(&mut masked_response);
let server_e_kp = KeyPair::<KeGroup<CS>>::derive_random(&mut rng);
let r = KeGroup::<CS>::serialize_sk(KeGroup::<CS>::random_sk(&mut rng));
let s = KeGroup::<CS>::serialize_sk(KeGroup::<CS>::random_sk(&mut rng));
let mut mac = Output::<OprfHash<CS>>::default();
rng.fill_bytes(&mut mac);
let mut server_nonce = [0u8; NonceLen::USIZE];
rng.fill_bytes(&mut server_nonce);
let ke2m: Vec<u8> = [
server_nonce.as_ref(),
server_e_kp.public().serialize().as_ref(),
&r,
&s,
&mac,
]
.concat();
let mut input = Vec::new();
input.extend_from_slice(&pt_bytes);
input.extend_from_slice(&masking_nonce);
@@ -431,18 +617,18 @@ fn credential_response_roundtrip() -> Result<(), ProtocolError> {
Ok(())
}
#[cfg(feature = "ristretto255")]
inner::<Ristretto255>()?;
inner::<P256>()?;
inner::<P384>()?;
inner::<P521>()?;
inner::<SigmaIP256>()?;
inner::<SigmaIP384>()?;
Ok(())
}
#[test]
fn credential_finalization_roundtrip() -> Result<(), ProtocolError> {
fn inner<CS: CipherSuite>() -> Result<(), ProtocolError> {
fn triple_dh_credential_finalization_roundtrip() -> Result<(), ProtocolError> {
fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
where
<CS::KeyExchange as KeyExchange>::KE3Message: Deserialize + Serialize,
{
let mut rng = OsRng;
let mut mac = Output::<OprfHash<CS>>::default();
rng.fill_bytes(&mut mac);
@@ -457,31 +643,70 @@ fn credential_finalization_roundtrip() -> Result<(), ProtocolError> {
}
#[cfg(feature = "ristretto255")]
inner::<Ristretto255>()?;
inner::<P256>()?;
inner::<P384>()?;
inner::<P521>()?;
inner::<TripleDhRistretto255>()?;
#[cfg(all(feature = "ristretto255", feature = "curve25519"))]
inner::<TripleDhCurve25519>()?;
inner::<TripleDhP256>()?;
inner::<TripleDhP384>()?;
inner::<TripleDhP521>()?;
Ok(())
}
#[test]
fn client_login_roundtrip() -> Result<(), ProtocolError> {
#[cfg(feature = "ecdsa")]
fn sigma_i_ecdsa_credential_finalization_roundtrip() -> Result<(), ProtocolError> {
fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
where
<CS::KeyExchange as KeyExchange>::KE3Message: Deserialize + Serialize,
{
let mut rng = OsRng;
let r = KeGroup::<CS>::serialize_sk(KeGroup::<CS>::random_sk(&mut rng));
let s = KeGroup::<CS>::serialize_sk(KeGroup::<CS>::random_sk(&mut rng));
let mut mac = Output::<OprfHash<CS>>::default();
rng.fill_bytes(&mut mac);
let mut input = Vec::new();
input.extend_from_slice(&r);
input.extend_from_slice(&s);
input.extend_from_slice(&mac);
let l3 = CredentialFinalization::<CS>::deserialize(&input)?;
let l3_bytes = l3.serialize();
assert_eq!(input.as_slice(), l3_bytes.as_slice());
Ok(())
}
inner::<SigmaIP256>()?;
inner::<SigmaIP384>()?;
Ok(())
}
#[test]
fn triple_dh_client_login_roundtrip() -> Result<(), ProtocolError> {
fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
where
<CS::KeyExchange as KeyExchange>::KE1Message: Deserialize,
<CS::KeyExchange as KeyExchange>::KE1State: Deserialize,
// CredentialRequest: KgPk + Ke1Message
<OprfGroup<CS> as Group>::ElemLen: Add<Ke1MessageLen<CS>>,
<CS::KeyExchange as KeyExchange>::KE1Message: Serialize,
<OprfGroup<CS> as voprf::Group>::ElemLen: Add<Ke1MessageLen<CS>>,
CredentialRequestLen<CS>: ArrayLength<u8>,
// ClientLogin: KgSk + CredentialRequest + Ke1State
<OprfGroup<CS> as Group>::ScalarLen: Add<CredentialRequestLen<CS>>,
Sum<<OprfGroup<CS> as Group>::ScalarLen, CredentialRequestLen<CS>>:
<OprfGroup<CS> as voprf::Group>::ScalarLen: Add<CredentialRequestLen<CS>>,
<CS::KeyExchange as KeyExchange>::KE1State: Serialize,
Sum<<OprfGroup<CS> as voprf::Group>::ScalarLen, CredentialRequestLen<CS>>:
ArrayLength<u8> + Add<Ke1StateLen<CS>>,
ClientLoginLen<CS>: ArrayLength<u8>,
{
let pw = b"hunter2";
let mut rng = OsRng;
let client_e_kp = KeyPair::<CS::KeGroup>::generate_random::<CS::OprfCs, _>(&mut rng);
let client_e_kp = KeyPair::<KeGroup<CS>>::derive_random(&mut rng);
let mut client_nonce = [0; NonceLen::USIZE];
rng.fill_bytes(&mut client_nonce);
@@ -495,13 +720,13 @@ fn client_login_roundtrip() -> Result<(), ProtocolError> {
let credential_request = CredentialRequest::<CS> {
blinded_element: blind_result.message,
ke1_message:
<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE1Message::deserialize(
&[
ke1_message: <CS::KeyExchange as KeyExchange>::KE1Message::deserialize_take(
&mut ([
client_nonce.as_ref(),
client_e_kp.public().serialize().as_ref(),
]
.concat(),
.concat()
.as_slice()),
)?,
};
@@ -520,20 +745,25 @@ fn client_login_roundtrip() -> Result<(), ProtocolError> {
}
#[cfg(feature = "ristretto255")]
inner::<Ristretto255>()?;
inner::<P256>()?;
inner::<P384>()?;
inner::<P521>()?;
inner::<TripleDhRistretto255>()?;
#[cfg(all(feature = "ristretto255", feature = "curve25519"))]
inner::<TripleDhCurve25519>()?;
inner::<TripleDhP256>()?;
inner::<TripleDhP384>()?;
inner::<TripleDhP521>()?;
Ok(())
}
#[test]
fn ke1_message_roundtrip() -> Result<(), ProtocolError> {
fn inner<CS: CipherSuite>() -> Result<(), ProtocolError> {
fn triple_dh_ke1_message_roundtrip() -> Result<(), ProtocolError> {
fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
where
<CS::KeyExchange as KeyExchange>::KE1Message: Deserialize + Serialize,
{
let mut rng = OsRng;
let client_e_kp = KeyPair::<CS::KeGroup>::generate_random::<CS::OprfCs, _>(&mut rng);
let client_e_kp = KeyPair::<KeGroup<CS>>::derive_random(&mut rng);
let mut client_nonce = vec![0u8; NonceLen::USIZE];
rng.fill_bytes(&mut client_nonce);
@@ -543,9 +773,7 @@ fn ke1_message_roundtrip() -> Result<(), ProtocolError> {
]
.concat();
let reg =
<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE1Message::deserialize(
&ke1m,
)?;
<CS::KeyExchange as KeyExchange>::KE1Message::deserialize_take(&mut (ke1m.as_slice()))?;
let reg_bytes = reg.serialize();
assert_eq!(*reg_bytes, ke1m);
@@ -553,20 +781,25 @@ fn ke1_message_roundtrip() -> Result<(), ProtocolError> {
}
#[cfg(feature = "ristretto255")]
inner::<Ristretto255>()?;
inner::<P256>()?;
inner::<P384>()?;
inner::<P521>()?;
inner::<TripleDhRistretto255>()?;
#[cfg(all(feature = "ristretto255", feature = "curve25519"))]
inner::<TripleDhCurve25519>()?;
inner::<TripleDhP256>()?;
inner::<TripleDhP384>()?;
inner::<TripleDhP521>()?;
Ok(())
}
#[test]
fn ke2_message_roundtrip() -> Result<(), ProtocolError> {
fn inner<CS: CipherSuite>() -> Result<(), ProtocolError> {
fn triple_dh_ke2_message_roundtrip() -> Result<(), ProtocolError> {
fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
where
<CS::KeyExchange as KeyExchange>::KE2Message: Deserialize + Serialize,
{
let mut rng = OsRng;
let server_e_kp = KeyPair::<CS::KeGroup>::generate_random::<CS::OprfCs, _>(&mut rng);
let server_e_kp = KeyPair::<KeGroup<CS>>::derive_random(&mut rng);
let mut mac = Output::<OprfHash<CS>>::default();
rng.fill_bytes(&mut mac);
let mut server_nonce = vec![0u8; NonceLen::USIZE];
@@ -580,9 +813,7 @@ fn ke2_message_roundtrip() -> Result<(), ProtocolError> {
.concat();
let reg =
<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE2Message::deserialize(
&ke2m,
)?;
<CS::KeyExchange as KeyExchange>::KE2Message::deserialize_take(&mut (ke2m.as_slice()))?;
let reg_bytes = reg.serialize();
assert_eq!(*reg_bytes, ke2m);
@@ -590,17 +821,62 @@ fn ke2_message_roundtrip() -> Result<(), ProtocolError> {
}
#[cfg(feature = "ristretto255")]
inner::<Ristretto255>()?;
inner::<P256>()?;
inner::<P384>()?;
inner::<P521>()?;
inner::<TripleDhRistretto255>()?;
#[cfg(all(feature = "ristretto255", feature = "curve25519"))]
inner::<TripleDhCurve25519>()?;
inner::<TripleDhP256>()?;
inner::<TripleDhP384>()?;
inner::<TripleDhP521>()?;
Ok(())
}
#[test]
fn ke3_message_roundtrip() -> Result<(), ProtocolError> {
fn inner<CS: CipherSuite>() -> Result<(), ProtocolError> {
#[cfg(feature = "ecdsa")]
fn sigma_i_ecdsa_ke2_message_roundtrip() -> Result<(), ProtocolError> {
fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
where
<CS::KeyExchange as KeyExchange>::KE2Message: Deserialize + Serialize,
{
let mut rng = OsRng;
let server_e_kp = KeyPair::<KeGroup<CS>>::derive_random(&mut rng);
let mut mac = Output::<OprfHash<CS>>::default();
rng.fill_bytes(&mut mac);
let mut server_nonce = vec![0u8; NonceLen::USIZE];
rng.fill_bytes(&mut server_nonce);
let r = KeGroup::<CS>::serialize_sk(KeGroup::<CS>::random_sk(&mut rng));
let s = KeGroup::<CS>::serialize_sk(KeGroup::<CS>::random_sk(&mut rng));
let ke2m: Vec<u8> = [
server_nonce.as_slice(),
server_e_kp.public().serialize().as_ref(),
&r,
&s,
&mac,
]
.concat();
let reg =
<CS::KeyExchange as KeyExchange>::KE2Message::deserialize_take(&mut (ke2m.as_slice()))?;
let reg_bytes = reg.serialize();
assert_eq!(*reg_bytes, ke2m);
Ok(())
}
inner::<SigmaIP256>()?;
inner::<SigmaIP384>()?;
Ok(())
}
#[test]
fn triple_dh_ke3_message_roundtrip() -> Result<(), ProtocolError> {
fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
where
<CS::KeyExchange as KeyExchange>::KE3Message: Deserialize + Serialize,
{
let mut rng = OsRng;
let mut mac = Output::<OprfHash<CS>>::default();
rng.fill_bytes(&mut mac);
@@ -608,9 +884,7 @@ fn ke3_message_roundtrip() -> Result<(), ProtocolError> {
let ke3m: Vec<u8> = [mac].concat();
let reg =
<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE3Message::deserialize(
&ke3m,
)?;
<CS::KeyExchange as KeyExchange>::KE3Message::deserialize_take(&mut (ke3m.as_slice()))?;
let reg_bytes = reg.serialize();
assert_eq!(*reg_bytes, ke3m);
@@ -618,10 +892,41 @@ fn ke3_message_roundtrip() -> Result<(), ProtocolError> {
}
#[cfg(feature = "ristretto255")]
inner::<Ristretto255>()?;
inner::<P256>()?;
inner::<P384>()?;
inner::<P521>()?;
inner::<TripleDhRistretto255>()?;
#[cfg(all(feature = "ristretto255", feature = "curve25519"))]
inner::<TripleDhCurve25519>()?;
inner::<TripleDhP256>()?;
inner::<TripleDhP384>()?;
inner::<TripleDhP521>()?;
Ok(())
}
#[test]
#[cfg(feature = "ecdsa")]
fn sigma_i_ecdsa_ke3_message_roundtrip() -> Result<(), ProtocolError> {
fn inner<CS: CipherSuite>() -> Result<(), ProtocolError>
where
<CS::KeyExchange as KeyExchange>::KE3Message: Deserialize + Serialize,
{
let mut rng = OsRng;
let r = KeGroup::<CS>::serialize_sk(KeGroup::<CS>::random_sk(&mut rng));
let s = KeGroup::<CS>::serialize_sk(KeGroup::<CS>::random_sk(&mut rng));
let mut mac = Output::<OprfHash<CS>>::default();
rng.fill_bytes(&mut mac);
let ke3m: Vec<u8> = [mac.as_slice(), &r, &s].concat();
let reg =
<CS::KeyExchange as KeyExchange>::KE3Message::deserialize_take(&mut (ke3m.as_slice()))?;
let reg_bytes = reg.serialize();
assert_eq!(*reg_bytes, ke3m);
Ok(())
}
inner::<SigmaIP256>()?;
inner::<SigmaIP384>()?;
Ok(())
}
@@ -672,7 +977,7 @@ macro_rules! test {
#[test]
fn test_nocrash_credential_request(bytes in vec(any::<u8>(), 0..500)) {
let _ = CredentialRequest::<$CS>::deserialize(&bytes).map_or(true, |_| true);
let _ = CredentialRequest::<$CS>::deserialize(&mut (bytes.as_slice())).map_or(true, |_| true);
}
#[test]
@@ -710,7 +1015,17 @@ macro_rules! test {
}
#[cfg(feature = "ristretto255")]
test!(ristretto255, Ristretto255);
test!(p256, P256);
test!(p384, P384);
test!(p521, P521);
test!(triple_dh_ristretto255, TripleDhRistretto255);
#[cfg(all(feature = "ristretto255", feature = "curve25519"))]
test!(triple_dh_curve25519, TripleDhCurve25519);
test!(triple_dh_p256, TripleDhP256);
test!(triple_dh_p384, TripleDhP384);
test!(triple_dh_p521, TripleDhP521);
#[cfg(feature = "ecdsa")]
test!(sigma_i_p256, SigmaIP256);
#[cfg(feature = "ecdsa")]
test!(sigma_i_p384, SigmaIP384);
#[cfg(all(feature = "ristretto255", feature = "ed25519",))]
test!(sigma_i_ed25519, SigmaIEd25519);
#[cfg(all(feature = "ristretto255", feature = "ed25519"))]
test!(sigma_i_ed25519_ph, SigmaIEd25519Ph);
+328 -581
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-2
View File
@@ -12,6 +12,4 @@ mod full_test_vectors;
pub mod mock_rng;
mod opaque_vectors;
mod parser;
#[cfg(test_hsm)]
mod remote_key;
mod test_opaque_vectors;
+49 -64
View File
@@ -16,15 +16,16 @@ use generic_array::{ArrayLength, GenericArray};
use rand::rngs::OsRng;
use rand::RngCore;
use serde_json::Value;
use voprf::Group;
use crate::ciphersuite::{CipherSuite, OprfGroup, OprfHash};
use crate::ciphersuite::{CipherSuite, KeGroup, OprfGroup, OprfHash};
use crate::envelope::EnvelopeLen;
use crate::errors::*;
use crate::hash::OutputSize;
use crate::key_exchange::group::KeGroup;
use crate::key_exchange::traits::{Ke1MessageLen, Ke2MessageLen};
use crate::key_exchange::tripledh::{NonceLen, TripleDh};
use crate::key_exchange::group::Group;
use crate::key_exchange::shared::NonceLen;
use crate::key_exchange::traits::{
Deserialize, Ke1MessageLen, Ke2MessageLen, KeyExchange, Serialize,
};
use crate::ksf::Identity;
use crate::messages::{
CredentialRequestLen, CredentialResponseLen, CredentialResponseWithoutKeLen,
@@ -99,7 +100,7 @@ fn populate_test_vectors<CS: CipherSuite>(values: &Value) -> OpaqueTestVectorPar
dummy_private_key: {
match decode(values, "client_private_key") {
Some(value) => value,
None => CS::KeGroup::serialize_sk(CS::KeGroup::random_sk(&mut OsRng)).to_vec(),
None => KeGroup::<CS>::serialize_sk(KeGroup::<CS>::random_sk(&mut OsRng)).to_vec(),
}
},
dummy_masking_key: {
@@ -149,12 +150,9 @@ fn populate_test_vectors<CS: CipherSuite>(values: &Value) -> OpaqueTestVectorPar
fn get_password_file_bytes<CS: CipherSuite>(parameters: &OpaqueTestVectorParameters) -> Vec<u8>
where
// Envelope: Nonce + Hash
NonceLen: Add<OutputSize<OprfHash<CS>>>,
EnvelopeLen<CS>: ArrayLength<u8>,
// RegistrationUpload: (KePk + Hash) + Envelope
<CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<OprfHash<CS>>>,
Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<OprfHash<CS>>>:
<KeGroup<CS> as Group>::PkLen: Add<OutputSize<OprfHash<CS>>>,
Sum<<KeGroup<CS> as Group>::PkLen, OutputSize<OprfHash<CS>>>:
ArrayLength<u8> + Add<EnvelopeLen<CS>>,
RegistrationUploadLen<CS>: ArrayLength<u8>,
// ServerRegistration = RegistrationUpload
@@ -194,8 +192,7 @@ fn tests() -> Result<(), ProtocolError> {
struct Ristretto255Sha512NoKsf;
impl CipherSuite for Ristretto255Sha512NoKsf {
type OprfCs = crate::Ristretto255;
type KeGroup = crate::Ristretto255;
type KeyExchange = TripleDh;
type KeyExchange = TripleDh<crate::Ristretto255, sha2::Sha512>;
type Ksf = Identity;
}
@@ -233,8 +230,7 @@ fn tests() -> Result<(), ProtocolError> {
struct Ristretto255Sha512Curve25519NoKsf;
impl CipherSuite for Ristretto255Sha512Curve25519NoKsf {
type OprfCs = crate::Ristretto255;
type KeGroup = crate::Curve25519;
type KeyExchange = TripleDh;
type KeyExchange = TripleDh<crate::Curve25519, sha2::Sha512>;
type Ksf = Identity;
}
@@ -270,8 +266,7 @@ fn tests() -> Result<(), ProtocolError> {
struct P256Sha256NoKsf;
impl CipherSuite for P256Sha256NoKsf {
type OprfCs = p256::NistP256;
type KeGroup = p256::NistP256;
type KeyExchange = TripleDh;
type KeyExchange = TripleDh<p256::NistP256, sha2::Sha256>;
type Ksf = Identity;
}
@@ -325,7 +320,7 @@ fn test_registration_response<CS: CipherSuite>(
) -> Result<(), ProtocolError>
where
// RegistrationResponse: KgPk + KePk
<OprfGroup<CS> as Group>::ElemLen: Add<<CS::KeGroup as KeGroup>::PkLen>,
<OprfGroup<CS> as voprf::Group>::ElemLen: Add<<KeGroup<CS> as Group>::PkLen>,
RegistrationResponseLen<CS>: ArrayLength<u8>,
{
for parameters in tvs {
@@ -362,12 +357,9 @@ fn test_registration_upload<CS: CipherSuite>(
tvs: &[OpaqueTestVectorParameters],
) -> Result<(), ProtocolError>
where
// Envelope: Nonce + Hash
NonceLen: Add<OutputSize<OprfHash<CS>>>,
EnvelopeLen<CS>: ArrayLength<u8>,
// RegistrationUpload: (KePk + Hash) + Envelope
<CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<OprfHash<CS>>>,
Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<OprfHash<CS>>>:
<KeGroup<CS> as Group>::PkLen: Add<OutputSize<OprfHash<CS>>>,
Sum<<KeGroup<CS> as Group>::PkLen, OutputSize<OprfHash<CS>>>:
ArrayLength<u8> + Add<EnvelopeLen<CS>>,
RegistrationUploadLen<CS>: ArrayLength<u8>,
{
@@ -413,7 +405,8 @@ where
fn test_ke1<CS: CipherSuite>(tvs: &[OpaqueTestVectorParameters]) -> Result<(), ProtocolError>
where
// CredentialRequest: KgPk + Ke1Message
<OprfGroup<CS> as Group>::ElemLen: Add<Ke1MessageLen<CS>>,
<CS::KeyExchange as KeyExchange>::KE1Message: Serialize,
<OprfGroup<CS> as voprf::Group>::ElemLen: Add<Ke1MessageLen<CS>>,
CredentialRequestLen<CS>: ArrayLength<u8>,
{
for parameters in tvs {
@@ -437,28 +430,20 @@ where
fn test_ke2<CS: CipherSuite>(tvs: &[OpaqueTestVectorParameters]) -> Result<(), ProtocolError>
where
// Envelope: Nonce + Hash
NonceLen: Add<OutputSize<OprfHash<CS>>>,
EnvelopeLen<CS>: ArrayLength<u8>,
<CS::KeyExchange as KeyExchange>::KE1Message: Deserialize,
// RegistrationUpload: (KePk + Hash) + Envelope
<CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<OprfHash<CS>>>,
Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<OprfHash<CS>>>:
<KeGroup<CS> as Group>::PkLen: Add<OutputSize<OprfHash<CS>>>,
Sum<<KeGroup<CS> as Group>::PkLen, OutputSize<OprfHash<CS>>>:
ArrayLength<u8> + Add<EnvelopeLen<CS>>,
RegistrationUploadLen<CS>: ArrayLength<u8>,
// ServerRegistration = RegistrationUpload
// MaskedResponse: (Nonce + Hash) + KePk
NonceLen: Add<OutputSize<OprfHash<CS>>>,
Sum<NonceLen, OutputSize<OprfHash<CS>>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
MaskedResponseLen<CS>: ArrayLength<u8>,
// CredentialResponseWithoutKeLen: (KgPk + Nonce) + MaskedResponse
<OprfGroup<CS> as Group>::ElemLen: Add<NonceLen>,
Sum<<OprfGroup<CS> as Group>::ElemLen, NonceLen>: ArrayLength<u8> + Add<MaskedResponseLen<CS>>,
<OprfGroup<CS> as voprf::Group>::ElemLen: Add<NonceLen>,
Sum<<OprfGroup<CS> as voprf::Group>::ElemLen, NonceLen>:
ArrayLength<u8> + Add<MaskedResponseLen<CS>>,
CredentialResponseWithoutKeLen<CS>: ArrayLength<u8>,
// MaskedResponse: (Nonce + Hash) + KePk
NonceLen: Add<OutputSize<OprfHash<CS>>>,
Sum<NonceLen, OutputSize<OprfHash<CS>>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
MaskedResponseLen<CS>: ArrayLength<u8>,
// CredentialResponse: CredentialResponseWithoutKeLen + Ke2Message
<CS::KeyExchange as KeyExchange>::KE2Message: Serialize,
CredentialResponseWithoutKeLen<CS>: Add<Ke2MessageLen<CS>>,
CredentialResponseLen<CS>: ArrayLength<u8>,
{
@@ -490,7 +475,7 @@ where
Some(record),
CredentialRequest::<CS>::deserialize(&parameters.KE1).unwrap(),
&parameters.credential_identifier,
ServerLoginStartParameters {
ServerLoginParameters {
context: Some(&parameters.context),
identifiers: Identifiers {
client: parameters.client_identity.as_deref(),
@@ -520,10 +505,8 @@ where
fn test_ke3<CS: CipherSuite>(tvs: &[OpaqueTestVectorParameters]) -> Result<(), ProtocolError>
where
// MaskedResponse: (Nonce + Hash) + KePk
NonceLen: Add<OutputSize<OprfHash<CS>>>,
Sum<NonceLen, OutputSize<OprfHash<CS>>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
MaskedResponseLen<CS>: ArrayLength<u8>,
<CS::KeyExchange as KeyExchange>::KE2Message: Deserialize + Serialize,
<CS::KeyExchange as KeyExchange>::KE3Message: Serialize,
{
for parameters in tvs {
let client_login_start = [
@@ -537,6 +520,7 @@ where
ClientLogin::<CS>::start(&mut client_login_start_rng, &parameters.password)?;
let client_login_finish_result = client_login_start_result.state.finish(
&mut OsRng,
&parameters.password,
CredentialResponse::<CS>::deserialize(&parameters.KE2)?,
ClientLoginFinishParameters::new(
@@ -577,19 +561,14 @@ fn test_server_login_finish<CS: CipherSuite>(
tvs: &[OpaqueTestVectorParameters],
) -> Result<(), ProtocolError>
where
// Envelope: Nonce + Hash
NonceLen: Add<OutputSize<OprfHash<CS>>>,
EnvelopeLen<CS>: ArrayLength<u8>,
<CS::KeyExchange as KeyExchange>::KE1Message: Deserialize,
<CS::KeyExchange as KeyExchange>::KE3Message: Deserialize,
// RegistrationUpload: (KePk + Hash) + Envelope
<CS::KeGroup as KeGroup>::PkLen: Add<OutputSize<OprfHash<CS>>>,
Sum<<CS::KeGroup as KeGroup>::PkLen, OutputSize<OprfHash<CS>>>:
<KeGroup<CS> as Group>::PkLen: Add<OutputSize<OprfHash<CS>>>,
Sum<<KeGroup<CS> as Group>::PkLen, OutputSize<OprfHash<CS>>>:
ArrayLength<u8> + Add<EnvelopeLen<CS>>,
RegistrationUploadLen<CS>: ArrayLength<u8>,
// ServerRegistration = RegistrationUpload
// MaskedResponse: (Nonce + Hash) + KePk
NonceLen: Add<OutputSize<OprfHash<CS>>>,
Sum<NonceLen, OutputSize<OprfHash<CS>>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
MaskedResponseLen<CS>: ArrayLength<u8>,
{
for parameters in tvs {
let server_setup = ServerSetup::<CS>::deserialize(
@@ -619,7 +598,7 @@ where
Some(record),
CredentialRequest::<CS>::deserialize(&parameters.KE1).unwrap(),
&parameters.credential_identifier,
ServerLoginStartParameters {
ServerLoginParameters {
context: Some(&parameters.context),
identifiers: Identifiers {
client: parameters.client_identity.as_deref(),
@@ -628,9 +607,16 @@ where
},
)?;
let server_login_result = server_login_start_result
.state
.finish(CredentialFinalization::deserialize(&parameters.KE3)?)?;
let server_login_result = server_login_start_result.state.finish(
CredentialFinalization::deserialize(&parameters.KE3)?,
ServerLoginParameters {
context: Some(&parameters.context),
identifiers: Identifiers {
client: parameters.client_identity.as_deref(),
server: parameters.server_identity.as_deref(),
},
},
)?;
assert_eq!(
hex::encode(&parameters.session_key),
@@ -644,15 +630,14 @@ fn test_fake_vectors<CS: CipherSuite>(
tvs: &[OpaqueTestVectorParameters],
) -> Result<(), ProtocolError>
where
// MaskedResponse: (Nonce + Hash) + KePk
NonceLen: Add<OutputSize<OprfHash<CS>>>,
Sum<NonceLen, OutputSize<OprfHash<CS>>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
MaskedResponseLen<CS>: ArrayLength<u8>,
<CS::KeyExchange as KeyExchange>::KE1Message: Deserialize,
// CredentialResponseWithoutKeLen: (KgPk + Nonce) + MaskedResponse
<OprfGroup<CS> as Group>::ElemLen: Add<NonceLen>,
Sum<<OprfGroup<CS> as Group>::ElemLen, NonceLen>: ArrayLength<u8> + Add<MaskedResponseLen<CS>>,
<OprfGroup<CS> as voprf::Group>::ElemLen: Add<NonceLen>,
Sum<<OprfGroup<CS> as voprf::Group>::ElemLen, NonceLen>:
ArrayLength<u8> + Add<MaskedResponseLen<CS>>,
CredentialResponseWithoutKeLen<CS>: ArrayLength<u8>,
// CredentialResponse: CredentialResponseWithoutKeLen + Ke2Message
<CS::KeyExchange as KeyExchange>::KE2Message: Serialize,
CredentialResponseWithoutKeLen<CS>: Add<Ke2MessageLen<CS>>,
CredentialResponseLen<CS>: ArrayLength<u8>,
{
@@ -681,7 +666,7 @@ where
None,
CredentialRequest::<CS>::deserialize(&parameters.KE1).unwrap(),
&parameters.credential_identifier,
ServerLoginStartParameters {
ServerLoginParameters {
context: Some(&parameters.context),
identifiers: Identifiers {
client: parameters.client_identity.as_deref(),
-42
View File
@@ -1,42 +0,0 @@
// 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.
//! Utility functions.
#[cfg(test)]
pub(crate) fn test_zeroize_on_drop<T: Sized>(value: &mut T) {
drop_manually(value);
test_zeroized(value);
}
#[cfg(test)]
pub(crate) fn test_zeroized<T: Sized>(value: &mut T) {
use std::{mem, slice, vec};
let test =
unsafe { slice::from_raw_parts(value as *const _ as *const u8, mem::size_of::<T>()) };
assert_eq!(test, vec![0; mem::size_of::<T>()]);
}
#[cfg(test)]
pub(crate) fn drop_manually<T: Sized>(value: &mut T) {
use std::{mem, ptr, vec};
assert!(mem::needs_drop::<T>());
let mut test_holder = vec![value];
let ptr = &mut *test_holder[0] as *mut T;
unsafe {
test_holder.set_len(0);
ptr::drop_in_place(ptr);
}
assert_eq!(test_holder.capacity(), 1);
}
+306 -107
View File
@@ -6,113 +6,130 @@
// of this source tree. You may select, at your option, one of the above-listed
// licenses.
#![cfg(test_hsm)]
#![allow(type_alias_bounds)]
use std::env;
use std::ops::Add;
use std::sync::{LazyLock, Mutex};
use std::vec::Vec;
#[cfg(feature = "ecdsa")]
use ::ecdsa::SignatureSize;
use cryptoki::context::{CInitializeArgs, Pkcs11};
use cryptoki::mechanism::elliptic_curve::{EcKdf, Ecdh1DeriveParams};
use cryptoki::mechanism::Mechanism;
use cryptoki::object::{Attribute, AttributeType, KeyType, ObjectClass, ObjectHandle};
use cryptoki::session::{Session, UserType};
use cryptoki::types::AuthPin;
#[cfg(feature = "ecdsa")]
use digest::Digest;
use digest::OutputSizeUser;
use elliptic_curve::group::prime::PrimeCurveAffine;
use elliptic_curve::group::Curve;
use elliptic_curve::pkcs8::der::asn1::{OctetString, OctetStringRef};
use elliptic_curve::pkcs8::der::{Decode, Encode};
use elliptic_curve::pkcs8::{AssociatedOid, ObjectIdentifier};
use elliptic_curve::point::{AffineCoordinates, DecompressPoint};
use elliptic_curve::sec1::{ModulusSize, Tag, ToEncodedPoint};
use elliptic_curve::{AffinePoint, CurveArithmetic, FieldBytesSize, Group, ProjectivePoint};
use generic_array::typenum::{Sum, Unsigned};
use elliptic_curve::sec1::{FromEncodedPoint, ModulusSize, Tag, ToEncodedPoint};
#[cfg(feature = "ecdsa")]
use elliptic_curve::PrimeCurve;
use elliptic_curve::{AffinePoint, CurveArithmetic, FieldBytesSize, Group as _, ProjectivePoint};
use generic_array::typenum::Unsigned;
use generic_array::{ArrayLength, GenericArray};
#[cfg(all(feature = "ristretto255", feature = "ed25519"))]
use opaque_ke::key_exchange::group::ed25519::{self, Ed25519};
use opaque_ke::key_exchange::group::elliptic_curve::NonIdentity;
use opaque_ke::key_exchange::group::Group;
#[cfg(feature = "ecdsa")]
use opaque_ke::key_exchange::sigma_i::ecdsa::{self, Ecdsa, PreHash};
#[cfg(all(feature = "ristretto255", feature = "ed25519"))]
use opaque_ke::key_exchange::sigma_i::pure_eddsa::PureEddsa;
#[cfg(feature = "ecdsa")]
use opaque_ke::key_exchange::sigma_i::{CachedMessage, HashOutput, Message, SigmaI};
use opaque_ke::key_exchange::tripledh::TripleDh;
use opaque_ke::key_exchange::KeyExchange;
use opaque_ke::keypair::{KeyPair, PublicKey};
use opaque_ke::ksf::Identity;
use opaque_ke::{
CipherSuite, ClientLogin, ClientLoginFinishParameters, ClientLoginStartResult,
ClientRegistration, ClientRegistrationFinishParameters, ClientRegistrationStartResult,
ServerLogin, ServerLoginParameters, ServerLoginStartResult, ServerRegistration, ServerSetup,
};
#[cfg(all(feature = "curve25519", feature = "ristretto255"))]
use opaque_ke::{Curve25519, Ristretto255};
use p256::NistP256;
use p384::NistP384;
use p521::NistP521;
use rand::rngs::OsRng;
use sha2::{Sha256, Sha384, Sha512};
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
use crate::ciphersuite::{OprfGroup, OprfHash};
use crate::envelope::NonceLen;
use crate::hash::OutputSize;
use crate::key_exchange::group::KeGroup;
use crate::key_exchange::tripledh::{DiffieHellman, TripleDh};
use crate::keypair::{KeyPair, PublicKey};
use crate::ksf::Identity;
use crate::opaque::MaskedResponseLen;
use crate::{
CipherSuite, ClientLogin, ClientLoginFinishParameters, ClientLoginStartResult,
ClientRegistration, ClientRegistrationFinishParameters, ClientRegistrationStartResult,
ServerLogin, ServerLoginStartParameters, ServerLoginStartResult, ServerRegistration,
ServerSetup,
};
#[cfg(all(feature = "curve25519", feature = "ristretto255"))]
use crate::{Curve25519, Ristretto255};
type OprfGroup<CS: CipherSuite> = <CS::OprfCs as voprf::CipherSuite>::Group;
type OprfHash<CS: CipherSuite> = <CS::OprfCs as voprf::CipherSuite>::Hash;
type KeGroup<CS: CipherSuite> = <CS::KeyExchange as KeyExchange>::Group;
#[test]
fn p256() {
fn triple_dh_p256() {
struct Suite;
impl CipherSuite for Suite {
type OprfCs = NistP256;
type KeGroup = NistP256;
type KeyExchange = TripleDh;
type KeyExchange = TripleDh<NistP256, Sha256>;
type Ksf = Identity;
}
test::<Suite>(
Mechanism::EccKeyPairGen,
NistP256::OID,
Attribute::Derive(true),
Mechanism::Sha256Hmac,
);
}
#[test]
fn p384() {
fn triple_dh_p384() {
struct Suite;
impl CipherSuite for Suite {
type OprfCs = NistP384;
type KeGroup = NistP384;
type KeyExchange = TripleDh;
type KeyExchange = TripleDh<NistP384, Sha384>;
type Ksf = Identity;
}
test::<Suite>(
Mechanism::EccKeyPairGen,
NistP384::OID,
Attribute::Derive(true),
Mechanism::Sha384Hmac,
);
}
#[test]
fn p521() {
fn triple_dh_p521() {
struct Suite;
impl CipherSuite for Suite {
type OprfCs = NistP521;
type KeGroup = NistP521;
type KeyExchange = TripleDh;
type KeyExchange = TripleDh<NistP521, Sha512>;
type Ksf = Identity;
}
test::<Suite>(
Mechanism::EccKeyPairGen,
NistP521::OID,
Attribute::Derive(true),
Mechanism::Sha512Hmac,
);
}
#[test]
#[cfg(all(feature = "curve25519", feature = "ristretto255"))]
fn curve25519() {
fn triple_dh_curve25519() {
struct Suite;
impl CipherSuite for Suite {
type OprfCs = Ristretto255;
type KeGroup = Curve25519;
type KeyExchange = TripleDh;
type KeyExchange = TripleDh<Curve25519, Sha512>;
type Ksf = Identity;
}
@@ -121,6 +138,64 @@ fn curve25519() {
// implementation. See https://github.com/softhsm/SoftHSMv2/issues/647.
Mechanism::EccEdwardsKeyPairGen,
ObjectIdentifier::new("1.3.101.110").unwrap(),
Attribute::Derive(true),
Mechanism::Sha512Hmac,
);
}
#[test]
#[cfg(feature = "ecdsa")]
fn sigma_i_p256() {
struct Suite;
impl CipherSuite for Suite {
type OprfCs = NistP256;
type KeyExchange = SigmaI<Ecdsa<NistP256, Sha256>, NistP256, Sha256>;
type Ksf = Identity;
}
test::<Suite>(
Mechanism::EccKeyPairGen,
NistP256::OID,
Attribute::Sign(true),
Mechanism::Sha256Hmac,
);
}
#[test]
#[cfg(feature = "ecdsa")]
fn sigma_i_p384() {
struct Suite;
impl CipherSuite for Suite {
type OprfCs = NistP384;
type KeyExchange = SigmaI<Ecdsa<NistP384, Sha384>, NistP384, Sha384>;
type Ksf = Identity;
}
test::<Suite>(
Mechanism::EccKeyPairGen,
NistP384::OID,
Attribute::Sign(true),
Mechanism::Sha384Hmac,
);
}
#[test]
#[cfg(all(feature = "ristretto255", feature = "ed25519"))]
fn sigma_i_ed25519() {
struct Suite;
impl CipherSuite for Suite {
type OprfCs = Ristretto255;
type KeyExchange = SigmaI<PureEddsa<Ed25519>, Ristretto255, Sha512>;
type Ksf = Identity;
}
test::<Suite>(
Mechanism::EccEdwardsKeyPairGen,
ObjectIdentifier::new_unwrap("1.3.101.112"),
Attribute::Sign(true),
Mechanism::Sha512Hmac,
);
}
@@ -128,43 +203,31 @@ fn curve25519() {
#[derive(Clone)]
struct RemoteKey(ObjectHandle);
trait Pkcs11DiffieHellman<KG: KeGroup> {
fn pkcs11_diffie_hellman(
&self,
server_pk: &PublicKey<KG>,
client_pk: &PublicKey<KG>,
) -> GenericArray<u8, KG::PkLen>;
trait Pkcs11PublicKey
where
Self: Group,
{
fn pkcs11_public_key(data: &[u8]) -> PublicKey<Self>;
}
fn test<CS: CipherSuite<KeyExchange = TripleDh>>(
trait Pkcs11KeyExchange<KE: KeyExchange> {
fn pkcs11_key_exchange<CS: CipherSuite>(
&self,
server_pk: &PublicKey<KE::Group>,
data: KE::KE2BuilderData<'_, CS>,
) -> KE::KE2BuilderInput<CS>;
}
fn test<CS: 'static + CipherSuite>(
dh_mechanism: Mechanism,
oid: ObjectIdentifier,
attribute: Attribute,
hmac_mechanism: Mechanism,
) where
RemoteKey: Pkcs11DiffieHellman<CS::KeGroup>,
<CS::KeGroup as KeGroup>::Sk: DiffieHellman<CS::KeGroup>,
// MaskedResponse: (Nonce + Hash) + KePk
NonceLen: Add<OutputSize<OprfHash<CS>>>,
Sum<NonceLen, OutputSize<OprfHash<CS>>>: ArrayLength<u8> + Add<<CS::KeGroup as KeGroup>::PkLen>,
MaskedResponseLen<CS>: ArrayLength<u8>,
// Ke1State: KeSk + Nonce
<CS::KeGroup as KeGroup>::SkLen: Add<NonceLen>,
Sum<<CS::KeGroup as KeGroup>::SkLen, NonceLen>: ArrayLength<u8>,
// Ke1Message: Nonce + KePk
NonceLen: Add<<CS::KeGroup as KeGroup>::PkLen>,
Sum<NonceLen, <CS::KeGroup as KeGroup>::PkLen>: ArrayLength<u8>,
// Ke2State: (Hash + Hash) + Hash
OutputSize<OprfHash<CS>>: Add<OutputSize<OprfHash<CS>>>,
Sum<OutputSize<OprfHash<CS>>, OutputSize<OprfHash<CS>>>:
ArrayLength<u8> + Add<OutputSize<OprfHash<CS>>>,
Sum<Sum<OutputSize<OprfHash<CS>>, OutputSize<OprfHash<CS>>>, OutputSize<OprfHash<CS>>>:
ArrayLength<u8>,
// Ke2Message: (Nonce + KePk) + Hash
NonceLen: Add<<CS::KeGroup as KeGroup>::PkLen>,
Sum<NonceLen, <CS::KeGroup as KeGroup>::PkLen>: ArrayLength<u8> + Add<OutputSize<OprfHash<CS>>>,
Sum<Sum<NonceLen, <CS::KeGroup as KeGroup>::PkLen>, OutputSize<OprfHash<CS>>>: ArrayLength<u8>,
KeGroup<CS>: Pkcs11PublicKey,
RemoteKey: Pkcs11KeyExchange<CS::KeyExchange>,
{
let (remote_key, pk) = pkcs11_generate_key_pair(dh_mechanism, oid);
let (remote_key, pk) = pkcs11_generate_key_pair(dh_mechanism, oid, attribute);
let keypair = KeyPair::new(RemoteKey(remote_key), pk);
let oprf_seed = pkcs11_generate_oprf_seed(<OprfHash<CS> as OutputSizeUser>::OutputSize::U64);
@@ -212,12 +275,12 @@ fn test<CS: CipherSuite<KeyExchange = TripleDh>>(
key_material,
Some(file),
message,
ServerLoginStartParameters::default(),
ServerLoginParameters::default(),
)
.unwrap();
let shared_secret = builder
.private_key()
.pkcs11_diffie_hellman(server_setup.keypair().public(), builder.data());
.pkcs11_key_exchange(server_setup.keypair().public(), builder.data());
let ServerLoginStartResult {
message,
@@ -228,6 +291,7 @@ fn test<CS: CipherSuite<KeyExchange = TripleDh>>(
let message = client
.clone()
.finish(
&mut OsRng,
PASSWORD.as_bytes(),
message,
ClientLoginFinishParameters::default(),
@@ -235,7 +299,11 @@ fn test<CS: CipherSuite<KeyExchange = TripleDh>>(
.map(|result| result.message);
message
.map(|message| server.finish(message).unwrap())
.map(|message| {
server
.finish(message, ServerLoginParameters::default())
.unwrap()
})
.unwrap();
}
@@ -263,10 +331,11 @@ static SESSION: LazyLock<Mutex<Session>> = LazyLock::new(|| {
Mutex::new(session)
});
fn pkcs11_generate_key_pair<KG: KeGroup>(
fn pkcs11_generate_key_pair<G: Group + Pkcs11PublicKey>(
mechanism: Mechanism,
oid: ObjectIdentifier,
) -> (ObjectHandle, PublicKey<KG>) {
attribute: Attribute,
) -> (ObjectHandle, PublicKey<G>) {
let session = SESSION.lock().unwrap();
let (pk, remote_key) = session
.generate_key_pair(
@@ -275,7 +344,7 @@ fn pkcs11_generate_key_pair<KG: KeGroup>(
Attribute::Token(false),
Attribute::EcParams(oid.to_der().unwrap()),
],
&[Attribute::Token(false), Attribute::Derive(true)],
&[Attribute::Token(false), attribute],
)
.unwrap();
@@ -290,7 +359,7 @@ fn pkcs11_generate_key_pair<KG: KeGroup>(
drop(session);
let pk = OctetString::from_der(&pk).unwrap();
let pk = PublicKey::deserialize(pk.as_bytes()).unwrap();
let pk = G::pkcs11_public_key(pk.as_bytes());
(remote_key, pk)
}
@@ -342,50 +411,131 @@ fn pkcs11_hkdf<CS: CipherSuite>(
okm
}
impl Pkcs11DiffieHellman<NistP256> for RemoteKey {
fn pkcs11_diffie_hellman(
&self,
server_pk: &PublicKey<NistP256>,
client_pk: &PublicKey<NistP256>,
) -> GenericArray<u8, <NistP256 as KeGroup>::PkLen> {
ec_pkcs_11_derive_secret::<NistP256>(self.0, server_pk, client_pk)
impl Pkcs11PublicKey for NistP256 {
fn pkcs11_public_key(data: &[u8]) -> PublicKey<NistP256> {
pkcs11_ec_public_key(data)
}
}
impl Pkcs11DiffieHellman<NistP384> for RemoteKey {
fn pkcs11_diffie_hellman(
&self,
server_pk: &PublicKey<NistP384>,
client_pk: &PublicKey<NistP384>,
) -> GenericArray<u8, <NistP384 as KeGroup>::PkLen> {
ec_pkcs_11_derive_secret::<NistP384>(self.0, server_pk, client_pk)
impl Pkcs11PublicKey for NistP384 {
fn pkcs11_public_key(data: &[u8]) -> PublicKey<NistP384> {
pkcs11_ec_public_key(data)
}
}
impl Pkcs11DiffieHellman<NistP521> for RemoteKey {
fn pkcs11_diffie_hellman(
&self,
server_pk: &PublicKey<NistP521>,
client_pk: &PublicKey<NistP521>,
) -> GenericArray<u8, <NistP521 as KeGroup>::PkLen> {
ec_pkcs_11_derive_secret::<NistP521>(self.0, server_pk, client_pk)
impl Pkcs11PublicKey for NistP521 {
fn pkcs11_public_key(data: &[u8]) -> PublicKey<NistP521> {
pkcs11_ec_public_key(data)
}
}
#[cfg(all(feature = "curve25519", feature = "ristretto255"))]
impl Pkcs11DiffieHellman<Curve25519> for RemoteKey {
fn pkcs11_diffie_hellman(
impl Pkcs11PublicKey for Curve25519 {
fn pkcs11_public_key(data: &[u8]) -> PublicKey<Curve25519> {
PublicKey::deserialize(data).unwrap()
}
}
#[cfg(all(feature = "ristretto255", feature = "ed25519"))]
impl Pkcs11PublicKey for Ed25519 {
fn pkcs11_public_key(data: &[u8]) -> PublicKey<Ed25519> {
PublicKey::deserialize(data).unwrap()
}
}
impl Pkcs11KeyExchange<TripleDh<NistP256, Sha256>> for RemoteKey {
fn pkcs11_key_exchange<CS: CipherSuite>(
&self,
server_pk: &PublicKey<NistP256>,
client_pk: &PublicKey<NistP256>,
) -> GenericArray<u8, <NistP256 as Group>::PkLen> {
pkcs_11_ecdsa_derive_secret::<NistP256>(self.0, server_pk, client_pk)
}
}
impl Pkcs11KeyExchange<TripleDh<NistP384, Sha384>> for RemoteKey {
fn pkcs11_key_exchange<CS: CipherSuite>(
&self,
server_pk: &PublicKey<NistP384>,
client_pk: &PublicKey<NistP384>,
) -> GenericArray<u8, <NistP384 as Group>::PkLen> {
pkcs_11_ecdsa_derive_secret::<NistP384>(self.0, server_pk, client_pk)
}
}
impl Pkcs11KeyExchange<TripleDh<NistP521, Sha512>> for RemoteKey {
fn pkcs11_key_exchange<CS: CipherSuite>(
&self,
server_pk: &PublicKey<NistP521>,
client_pk: &PublicKey<NistP521>,
) -> GenericArray<u8, <NistP521 as Group>::PkLen> {
pkcs_11_ecdsa_derive_secret::<NistP521>(self.0, server_pk, client_pk)
}
}
#[cfg(all(feature = "curve25519", feature = "ristretto255"))]
impl Pkcs11KeyExchange<TripleDh<Curve25519, Sha512>> for RemoteKey {
fn pkcs11_key_exchange<CS: CipherSuite>(
&self,
_: &PublicKey<Curve25519>,
pk: &PublicKey<Curve25519>,
) -> GenericArray<u8, <Curve25519 as KeGroup>::PkLen> {
let shared_secret = pkcs11_derive_secret(self.0, &pk.serialize());
) -> GenericArray<u8, <Curve25519 as Group>::PkLen> {
let shared_secret = pkcs_11_dh_derive_secret(self.0, &pk.serialize());
GenericArray::clone_from_slice(&shared_secret)
}
}
fn pkcs11_derive_secret(sk: ObjectHandle, pk: &[u8]) -> Vec<u8> {
#[cfg(feature = "ecdsa")]
impl Pkcs11KeyExchange<SigmaI<Ecdsa<NistP256, Sha256>, NistP256, Sha256>> for RemoteKey {
fn pkcs11_key_exchange<CS: CipherSuite>(
&self,
_: &PublicKey<NistP256>,
message: &Message<CS, NistP256>,
) -> (ecdsa::Signature<NistP256>, PreHash<Sha256>) {
pkcs_11_ecdsa_sign::<NistP256, Sha256>(self.0, message.hash())
}
}
#[cfg(feature = "ecdsa")]
impl Pkcs11KeyExchange<SigmaI<Ecdsa<NistP384, Sha384>, NistP384, Sha384>> for RemoteKey {
fn pkcs11_key_exchange<CS: CipherSuite>(
&self,
_: &PublicKey<NistP384>,
message: &Message<CS, NistP384>,
) -> (ecdsa::Signature<NistP384>, PreHash<Sha384>) {
pkcs_11_ecdsa_sign::<NistP384, Sha384>(self.0, message.hash())
}
}
#[cfg(all(feature = "ristretto255", feature = "ed25519"))]
impl Pkcs11KeyExchange<SigmaI<PureEddsa<Ed25519>, Ristretto255, Sha512>> for RemoteKey {
fn pkcs11_key_exchange<CS: CipherSuite>(
&self,
_: &PublicKey<Ed25519>,
message: &Message<CS, Ristretto255>,
) -> (ed25519::Signature, CachedMessage<CS, Ristretto255>) {
pkcs_11_eddsa_sign(self.0, message)
}
}
fn pkcs11_ec_public_key<G>(data: &[u8]) -> PublicKey<G>
where
G: Group<Pk = NonIdentity<G>> + CurveArithmetic,
FieldBytesSize<G>: ModulusSize,
AffinePoint<G>:
FromEncodedPoint<G> + ToEncodedPoint<G> + PrimeCurveAffine<Curve = ProjectivePoint<G>>,
{
PublicKey::deserialize(
elliptic_curve::PublicKey::<G>::from_sec1_bytes(data)
.unwrap()
.to_encoded_point(true)
.as_bytes(),
)
.unwrap()
}
fn pkcs_11_dh_derive_secret(sk: ObjectHandle, pk: &[u8]) -> Vec<u8> {
let session = SESSION.lock().unwrap();
let shared_secret = session
.derive_key(
@@ -413,23 +563,23 @@ fn pkcs11_derive_secret(sk: ObjectHandle, pk: &[u8]) -> Vec<u8> {
shared_secret
}
fn ec_pkcs_11_derive_secret<KG>(
fn pkcs_11_ecdsa_derive_secret<G>(
server_sk: ObjectHandle,
server_pk: &PublicKey<KG>,
client_pk: &PublicKey<KG>,
) -> GenericArray<u8, <KG as KeGroup>::PkLen>
server_pk: &PublicKey<G>,
client_pk: &PublicKey<G>,
) -> GenericArray<u8, <G as Group>::PkLen>
where
KG: KeGroup<Pk = ProjectivePoint<KG>> + CurveArithmetic,
AffinePoint<KG>: DecompressPoint<KG> + ToEncodedPoint<KG>,
FieldBytesSize<KG>: ModulusSize,
G: Group<Pk = NonIdentity<G>> + CurveArithmetic,
AffinePoint<G>: DecompressPoint<G> + ToEncodedPoint<G>,
FieldBytesSize<G>: ModulusSize,
{
let client_pk_point = client_pk.to_group_type();
let client_pk = client_pk.serialize();
let client_pk = OctetStringRef::new(&client_pk).unwrap();
let client_pk = client_pk.to_der().unwrap();
let shared_secret_bytes = pkcs11_derive_secret(server_sk, &client_pk);
let shared_secret_point = AffinePoint::<KG>::decompress(
let shared_secret_bytes = pkcs_11_dh_derive_secret(server_sk, &client_pk);
let shared_secret_point = AffinePoint::<G>::decompress(
&GenericArray::clone_from_slice(&shared_secret_bytes),
Choice::from(0),
)
@@ -437,14 +587,14 @@ where
let mut shared_secret = GenericArray::default();
shared_secret[1..].copy_from_slice(&shared_secret_bytes);
let shifted_client_pk = client_pk_point + ProjectivePoint::<KG>::generator();
let shifted_client_pk = client_pk_point.0.to_point() + ProjectivePoint::<G>::generator();
let shifted_client_pk = shifted_client_pk.to_affine().to_encoded_point(true);
let shifted_client_pk = OctetStringRef::new(shifted_client_pk.as_bytes()).unwrap();
let shifted_client_pk = shifted_client_pk.to_der().unwrap();
let check_point = pkcs11_derive_secret(server_sk, &shifted_client_pk);
let check_point = pkcs_11_dh_derive_secret(server_sk, &shifted_client_pk);
let shifted_server_pk = server_pk.to_group_type() + shared_secret_point;
let shifted_server_pk = server_pk.to_group_type().0.to_point() + shared_secret_point;
let shifted_server_pk = shifted_server_pk.to_affine();
let tag = u8::conditional_select(
@@ -456,3 +606,52 @@ where
shared_secret
}
#[cfg(feature = "ecdsa")]
fn pkcs_11_ecdsa_sign<G: CurveArithmetic + PrimeCurve, H: Clone + Digest>(
sk: ObjectHandle,
hashes: HashOutput<H>,
) -> (ecdsa::Signature<G>, PreHash<H>)
where
SignatureSize<G>: ArrayLength<u8>,
{
let sign_pre_hash = hashes.sign.finalize();
let session = SESSION.lock().unwrap();
let signature = session.sign(&Mechanism::Ecdsa, sk, &sign_pre_hash).unwrap();
drop(session);
let signature = ::ecdsa::Signature::from_slice(&signature).unwrap();
(
ecdsa::Signature(signature),
PreHash(hashes.verify.finalize()),
)
}
#[cfg(all(feature = "ristretto255", feature = "ed25519"))]
fn pkcs_11_eddsa_sign<CS: CipherSuite>(
sk: ObjectHandle,
message: &Message<CS, Ristretto255>,
) -> (ed25519::Signature, CachedMessage<CS, Ristretto255>) {
use cryptoki::mechanism::eddsa::{EddsaParams, EddsaSignatureScheme};
let mut message_bytes = Vec::new();
message
.sign_message()
.for_each(|bytes| message_bytes.extend_from_slice(bytes));
let session = SESSION.lock().unwrap();
let signature = session
.sign(
&Mechanism::Eddsa(EddsaParams::new(EddsaSignatureScheme::Pure)),
sk,
&message_bytes,
)
.unwrap();
drop(session);
let signature = ed25519::Signature::from_slice(&signature).unwrap();
(signature, message.to_cached())
}