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