Rework SecretKey API to facilitate async (#371)
* Rework `SecretKey` API to facilitate async * Remove left-over constraints
This commit is contained in:
@@ -25,6 +25,10 @@ jobs:
|
||||
- stable
|
||||
- 1.83.0
|
||||
name: test
|
||||
env:
|
||||
PKCS11_MODULE: /usr/lib/softhsm/libsofthsm2.so
|
||||
SOFTHSM2_CONF: /tmp/softhsm2.conf
|
||||
RUSTFLAGS: --cfg test_hsm
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@main
|
||||
@@ -36,6 +40,13 @@ jobs:
|
||||
toolchain: ${{ matrix.toolchain }}
|
||||
override: true
|
||||
|
||||
- name: Install SoftHSM
|
||||
run: |
|
||||
sudo apt-get update -y -qq &&
|
||||
sudo apt-get install -y -qq libsofthsm2 &&
|
||||
mkdir /tmp/tokens
|
||||
echo "directories.tokendir = /tmp/tokens" > /tmp/softhsm2.conf
|
||||
|
||||
- name: Run cargo test
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
|
||||
+12
-2
@@ -17,7 +17,7 @@ curve25519 = ["dep:curve25519-dalek"]
|
||||
default = ["ristretto255-voprf", "serde"]
|
||||
ristretto255 = ["dep:curve25519-dalek", "voprf/ristretto255"]
|
||||
ristretto255-voprf = ["ristretto255", "voprf/ristretto255-ciphersuite"]
|
||||
serde = ["dep:serde", "generic-array/serde", "voprf/serde"]
|
||||
serde = ["dep:serde", "generic-array/serde", "voprf/serde", "zeroize/serde"]
|
||||
std = ["dep:getrandom"]
|
||||
|
||||
[dependencies]
|
||||
@@ -27,7 +27,7 @@ argon2 = { version = "0.5", default-features = false, features = [
|
||||
curve25519-dalek = { version = "4", default-features = false, features = [
|
||||
"zeroize",
|
||||
], optional = true }
|
||||
derive-where = { version = "1", features = ["zeroize-on-drop"] }
|
||||
derive-where = { version = "1.3", features = ["zeroize-on-drop"] }
|
||||
digest = "0.10"
|
||||
displaydoc = { version = "0.2", default-features = false }
|
||||
elliptic-curve = { version = "0.13", features = ["hash2curve", "sec1"] }
|
||||
@@ -46,25 +46,32 @@ zeroize = { version = "1.8", features = ["zeroize_derive"] }
|
||||
getrandom = { version = "0.2", features = ["js"], optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1"
|
||||
bincode = "1"
|
||||
chacha20poly1305 = "0.10"
|
||||
criterion = "0.5"
|
||||
cryptoki = "0.9"
|
||||
elliptic-curve = { version = "0.13", features = ["alloc", "pkcs8"] }
|
||||
hex = "0.4"
|
||||
p256 = { version = "0.13", default-features = false, features = [
|
||||
"hash2curve",
|
||||
"pkcs8",
|
||||
"voprf",
|
||||
] }
|
||||
p384 = { version = "0.13", default-features = false, features = [
|
||||
"hash2curve",
|
||||
"pkcs8",
|
||||
"voprf",
|
||||
] }
|
||||
p521 = { version = "0.13.3", default-features = false, features = [
|
||||
"hash2curve",
|
||||
"pkcs8",
|
||||
"voprf",
|
||||
] }
|
||||
proptest = "1"
|
||||
rand = "0.8"
|
||||
regex = "1"
|
||||
thiserror = "2"
|
||||
# MSRV
|
||||
rustyline = "15"
|
||||
scrypt = "0.11"
|
||||
@@ -82,3 +89,6 @@ targets = []
|
||||
[[example]]
|
||||
name = "simple_login"
|
||||
required-features = ["argon2"]
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(test_hsm)'] }
|
||||
|
||||
@@ -42,7 +42,8 @@ yanked = "warn"
|
||||
# A list of advisory IDs to ignore. Note that ignored advisories will still
|
||||
# output a note when they are encountered.
|
||||
ignore = [
|
||||
#"RUSTSEC-0000-0000",
|
||||
# dev-dependency
|
||||
"RUSTSEC-2024-0436",
|
||||
]
|
||||
# Threshold for security vulnerabilities, any vulnerability with a CVSS score
|
||||
# lower than the range specified will be ignored. Note that ignored advisories
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ where
|
||||
/// A VOPRF ciphersuite, see [`voprf::CipherSuite`].
|
||||
type OprfCs: voprf::CipherSuite;
|
||||
/// A `Group` used for the `KeyExchange`.
|
||||
type KeGroup: KeGroup;
|
||||
type KeGroup: 'static + KeGroup;
|
||||
/// A key exchange protocol
|
||||
type KeyExchange: KeyExchange<OprfHash<Self>, Self::KeGroup>;
|
||||
/// A key stretching function, typically used for password hashing
|
||||
|
||||
+3
-3
@@ -24,7 +24,7 @@ use crate::errors::utils::check_slice_size;
|
||||
use crate::errors::{InternalError, ProtocolError};
|
||||
use crate::hash::OutputSize;
|
||||
use crate::key_exchange::group::KeGroup;
|
||||
use crate::keypair::{KeyPair, PublicKey};
|
||||
use crate::keypair::{KeyPair, PrivateKey, PrivateKeySerialization, PublicKey};
|
||||
use crate::opaque::{bytestrings_from_identifiers, Identifiers};
|
||||
use crate::serialization::{Input, MacExt};
|
||||
|
||||
@@ -303,7 +303,7 @@ fn build_inner_envelope_internal<CS: CipherSuite>(
|
||||
.expand(&nonce.concat(STR_PRIVATE_KEY.into()), &mut keypair_seed)
|
||||
.map_err(|_| InternalError::HkdfError)?;
|
||||
let client_static_keypair =
|
||||
KeyPair::<CS::KeGroup>::from_private_key_slice(&CS::KeGroup::serialize_sk(
|
||||
PrivateKey::<CS::KeGroup>::deserialize_key_pair(&CS::KeGroup::serialize_sk(
|
||||
CS::KeGroup::derive_auth_keypair::<CS::OprfCs>(keypair_seed)?,
|
||||
))?;
|
||||
|
||||
@@ -319,7 +319,7 @@ fn recover_keys_internal<CS: CipherSuite>(
|
||||
.expand(&nonce.concat(STR_PRIVATE_KEY.into()), &mut keypair_seed)
|
||||
.map_err(|_| InternalError::HkdfError)?;
|
||||
let client_static_keypair =
|
||||
KeyPair::<CS::KeGroup>::from_private_key_slice(&CS::KeGroup::serialize_sk(
|
||||
PrivateKey::<CS::KeGroup>::deserialize_key_pair(&CS::KeGroup::serialize_sk(
|
||||
CS::KeGroup::derive_auth_keypair::<CS::OprfCs>(keypair_seed)?,
|
||||
))?;
|
||||
|
||||
|
||||
+43
-71
@@ -15,23 +15,7 @@ use displaydoc::Display;
|
||||
|
||||
/// Represents an error in the manipulation of internal cryptographic data
|
||||
#[derive(Clone, Copy, Display, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub enum InternalError<T = Infallible> {
|
||||
/// Custom [`SecretKey`](crate::keypair::SecretKey) error type
|
||||
Custom(T),
|
||||
/// Deserializing from a byte sequence failed
|
||||
InvalidByteSequence,
|
||||
#[allow(clippy::doc_markdown)]
|
||||
/// Invalid length for {name}: expected {len}, but is actually {actual_len}.
|
||||
SizeError {
|
||||
/// name
|
||||
name: &'static str,
|
||||
/// length
|
||||
len: usize,
|
||||
/// actual
|
||||
actual_len: usize,
|
||||
},
|
||||
/// Could not decompress point.
|
||||
PointError,
|
||||
pub enum InternalError {
|
||||
/// Size of input is empty or longer then [`u16::MAX`].
|
||||
HashToScalar,
|
||||
/// Computing HKDF failed while deriving subkeys
|
||||
@@ -52,22 +36,9 @@ pub enum InternalError<T = Infallible> {
|
||||
OprfInternalError(voprf::InternalError),
|
||||
}
|
||||
|
||||
impl<T: Debug> Debug for InternalError<T> {
|
||||
impl Debug for InternalError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
Self::Custom(custom) => f.debug_tuple("InvalidByteSequence").field(custom).finish(),
|
||||
Self::InvalidByteSequence => f.debug_tuple("InvalidByteSequence").finish(),
|
||||
Self::SizeError {
|
||||
name,
|
||||
len,
|
||||
actual_len,
|
||||
} => f
|
||||
.debug_struct("SizeError")
|
||||
.field("name", name)
|
||||
.field("len", len)
|
||||
.field("actual_len", actual_len)
|
||||
.finish(),
|
||||
Self::PointError => f.debug_tuple("PointError").finish(),
|
||||
Self::HashToScalar => f.debug_tuple("HashToScalar").finish(),
|
||||
Self::HkdfError => f.debug_tuple("HkdfError").finish(),
|
||||
Self::HmacError => f.debug_tuple("HmacError").finish(),
|
||||
@@ -84,35 +55,7 @@ impl<T: Debug> Debug for InternalError<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Error> Error for InternalError<T> {}
|
||||
|
||||
impl InternalError {
|
||||
/// Convert `InternalError<Infallible>` into `InternalError<T>`
|
||||
pub fn into_custom<T>(self) -> InternalError<T> {
|
||||
match self {
|
||||
Self::Custom(_) => unreachable!(),
|
||||
Self::InvalidByteSequence => InternalError::InvalidByteSequence,
|
||||
Self::SizeError {
|
||||
name,
|
||||
len,
|
||||
actual_len,
|
||||
} => InternalError::SizeError {
|
||||
name,
|
||||
len,
|
||||
actual_len,
|
||||
},
|
||||
Self::PointError => InternalError::PointError,
|
||||
Self::HashToScalar => InternalError::HashToScalar,
|
||||
Self::HkdfError => InternalError::HkdfError,
|
||||
Self::HmacError => InternalError::HmacError,
|
||||
Self::KsfError => InternalError::KsfError,
|
||||
Self::SealOpenHmacError => InternalError::SealOpenHmacError,
|
||||
Self::IncompatibleEnvelopeModeError => InternalError::IncompatibleEnvelopeModeError,
|
||||
Self::OprfError(error) => InternalError::OprfError(error),
|
||||
Self::OprfInternalError(error) => InternalError::OprfInternalError(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Error for InternalError {}
|
||||
|
||||
impl From<voprf::Error> for InternalError {
|
||||
fn from(voprf_error: voprf::Error) -> Self {
|
||||
@@ -136,17 +79,28 @@ impl From<voprf::InternalError> for ProtocolError {
|
||||
#[derive(Clone, Copy, Display, Eq, Hash, Ord, PartialEq, PartialOrd)]
|
||||
pub enum ProtocolError<T = Infallible> {
|
||||
/// Internal error encountered
|
||||
LibraryError(InternalError<T>),
|
||||
LibraryError(InternalError),
|
||||
/// Error in validating credentials
|
||||
InvalidLoginError,
|
||||
/// Error with serializing / deserializing protocol messages
|
||||
SerializationError,
|
||||
/// Invalid length for `name`: expected `len`, but is actually `actual_len`.
|
||||
SizeError {
|
||||
/// name
|
||||
name: &'static str,
|
||||
/// length
|
||||
len: usize,
|
||||
/// actual
|
||||
actual_len: usize,
|
||||
},
|
||||
/** 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),
|
||||
}
|
||||
|
||||
impl<T: Debug> Debug for ProtocolError<T> {
|
||||
@@ -157,8 +111,19 @@ impl<T: Debug> Debug for ProtocolError<T> {
|
||||
}
|
||||
Self::InvalidLoginError => f.debug_tuple("InvalidLoginError").finish(),
|
||||
Self::SerializationError => f.debug_tuple("SerializationError").finish(),
|
||||
Self::SizeError {
|
||||
name,
|
||||
len,
|
||||
actual_len,
|
||||
} => f
|
||||
.debug_struct("SizeError")
|
||||
.field("name", name)
|
||||
.field("len", len)
|
||||
.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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,8 +132,8 @@ impl<T: Error> Error for ProtocolError<T> {}
|
||||
|
||||
// This is meant to express future(ly) non-trivial ways of converting the
|
||||
// internal error into a ProtocolError
|
||||
impl<T> From<InternalError<T>> for ProtocolError<T> {
|
||||
fn from(e: InternalError<T>) -> ProtocolError<T> {
|
||||
impl<T> From<InternalError> for ProtocolError<T> {
|
||||
fn from(e: InternalError) -> ProtocolError<T> {
|
||||
Self::LibraryError(e)
|
||||
}
|
||||
}
|
||||
@@ -186,11 +151,18 @@ impl ProtocolError {
|
||||
/// Convert `ProtocolError<Infallible>` into `ProtocolError<T>`
|
||||
pub fn into_custom<T>(self) -> ProtocolError<T> {
|
||||
match self {
|
||||
Self::LibraryError(internal_error) => {
|
||||
ProtocolError::LibraryError(internal_error.into_custom())
|
||||
}
|
||||
Self::LibraryError(internal_error) => ProtocolError::LibraryError(internal_error),
|
||||
Self::InvalidLoginError => ProtocolError::InvalidLoginError,
|
||||
Self::SerializationError => ProtocolError::SerializationError,
|
||||
Self::SizeError {
|
||||
name,
|
||||
len,
|
||||
actual_len,
|
||||
} => ProtocolError::SizeError {
|
||||
name,
|
||||
len,
|
||||
actual_len,
|
||||
},
|
||||
Self::ReflectedValueError => ProtocolError::ReflectedValueError,
|
||||
Self::IdentityGroupElementError => ProtocolError::IdentityGroupElementError,
|
||||
}
|
||||
@@ -200,13 +172,13 @@ impl ProtocolError {
|
||||
pub(crate) mod utils {
|
||||
use super::*;
|
||||
|
||||
pub fn check_slice_size<'a, T>(
|
||||
pub fn check_slice_size<'a>(
|
||||
slice: &'a [u8],
|
||||
expected_len: usize,
|
||||
arg_name: &'static str,
|
||||
) -> Result<&'a [u8], InternalError<T>> {
|
||||
) -> Result<&'a [u8], ProtocolError> {
|
||||
if slice.len() != expected_len {
|
||||
return Err(InternalError::SizeError {
|
||||
return Err(ProtocolError::SizeError {
|
||||
name: arg_name,
|
||||
len: expected_len,
|
||||
actual_len: slice.len(),
|
||||
@@ -219,9 +191,9 @@ pub(crate) mod utils {
|
||||
slice: &'a [u8],
|
||||
expected_len: usize,
|
||||
arg_name: &'static str,
|
||||
) -> Result<&'a [u8], InternalError> {
|
||||
) -> Result<&'a [u8], ProtocolError> {
|
||||
if slice.len() < expected_len {
|
||||
return Err(InternalError::SizeError {
|
||||
return Err(ProtocolError::SizeError {
|
||||
name: arg_name,
|
||||
len: expected_len,
|
||||
actual_len: slice.len(),
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
//! Key Exchange group implementation for Curve25519
|
||||
|
||||
use curve25519_dalek::montgomery::MontgomeryPoint;
|
||||
use curve25519_dalek::scalar::{self, Scalar};
|
||||
use curve25519_dalek::scalar;
|
||||
use curve25519_dalek::traits::Identity;
|
||||
use digest::core_api::BlockSizeUser;
|
||||
use digest::{FixedOutput, HashMarker, OutputSizeUser};
|
||||
@@ -17,9 +17,11 @@ use generic_array::typenum::{IsLess, IsLessOrEqual, U256, U32};
|
||||
use generic_array::GenericArray;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use subtle::ConstantTimeEq;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use super::KeGroup;
|
||||
use crate::errors::InternalError;
|
||||
use crate::errors::{InternalError, ProtocolError};
|
||||
use crate::key_exchange::tripledh::DiffieHellman;
|
||||
|
||||
/// Implementation for Curve25519.
|
||||
pub struct Curve25519;
|
||||
@@ -28,20 +30,20 @@ pub struct Curve25519;
|
||||
impl KeGroup for Curve25519 {
|
||||
type Pk = MontgomeryPoint;
|
||||
type PkLen = U32;
|
||||
type Sk = [u8; 32];
|
||||
type Sk = Scalar;
|
||||
type SkLen = U32;
|
||||
|
||||
fn serialize_pk(pk: Self::Pk) -> GenericArray<u8, Self::PkLen> {
|
||||
pk.to_bytes().into()
|
||||
}
|
||||
|
||||
fn deserialize_pk(bytes: &[u8]) -> Result<Self::Pk, InternalError> {
|
||||
fn deserialize_pk(bytes: &[u8]) -> Result<Self::Pk, ProtocolError> {
|
||||
bytes
|
||||
.try_into()
|
||||
.ok()
|
||||
.map(MontgomeryPoint)
|
||||
.filter(|pk| pk != &MontgomeryPoint::identity())
|
||||
.ok_or(InternalError::PointError)
|
||||
.ok_or(ProtocolError::SerializationError)
|
||||
}
|
||||
|
||||
fn random_sk<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Sk {
|
||||
@@ -51,8 +53,8 @@ impl KeGroup for Curve25519 {
|
||||
rng.fill_bytes(&mut scalar_bytes);
|
||||
let scalar = scalar::clamp_integer(scalar_bytes);
|
||||
|
||||
if scalar != Scalar::ZERO.to_bytes() {
|
||||
break scalar;
|
||||
if scalar != curve25519_dalek::Scalar::ZERO.to_bytes() {
|
||||
break Scalar(scalar);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,26 +74,22 @@ impl KeGroup for Curve25519 {
|
||||
<CS::Hash as OutputSizeUser>::OutputSize:
|
||||
IsLess<U256> + IsLessOrEqual<<CS::Hash as BlockSizeUser>::BlockSize>,
|
||||
{
|
||||
Ok(scalar::clamp_integer(seed.into()))
|
||||
Ok(Scalar(scalar::clamp_integer(seed.into())))
|
||||
}
|
||||
|
||||
fn is_zero_scalar(scalar: Self::Sk) -> subtle::Choice {
|
||||
scalar.ct_eq(&Scalar::ZERO.to_bytes())
|
||||
scalar.0.ct_eq(&curve25519_dalek::Scalar::ZERO.to_bytes())
|
||||
}
|
||||
|
||||
fn public_key(sk: Self::Sk) -> Self::Pk {
|
||||
MontgomeryPoint::mul_base_clamped(sk)
|
||||
}
|
||||
|
||||
fn diffie_hellman(pk: Self::Pk, sk: Self::Sk) -> GenericArray<u8, Self::PkLen> {
|
||||
Self::serialize_pk(pk.mul_clamped(sk))
|
||||
MontgomeryPoint::mul_base_clamped(sk.0)
|
||||
}
|
||||
|
||||
fn serialize_sk(sk: Self::Sk) -> GenericArray<u8, Self::SkLen> {
|
||||
sk.into()
|
||||
sk.0.into()
|
||||
}
|
||||
|
||||
fn deserialize_sk(bytes: &[u8]) -> Result<Self::Sk, InternalError> {
|
||||
fn deserialize_sk(bytes: &[u8]) -> Result<Self::Sk, ProtocolError> {
|
||||
bytes
|
||||
.try_into()
|
||||
.ok()
|
||||
@@ -99,7 +97,18 @@ impl KeGroup for Curve25519 {
|
||||
let scalar = scalar::clamp_integer(bytes);
|
||||
(scalar == bytes).then_some(scalar)
|
||||
})
|
||||
.filter(|scalar| scalar != &Scalar::ZERO.to_bytes())
|
||||
.ok_or(InternalError::PointError)
|
||||
.filter(|scalar| scalar != &curve25519_dalek::Scalar::ZERO.to_bytes())
|
||||
.map(Scalar)
|
||||
.ok_or(ProtocolError::SerializationError)
|
||||
}
|
||||
}
|
||||
|
||||
/// Curve25519 scalar.
|
||||
#[derive(Clone, Copy, Zeroize)]
|
||||
pub struct Scalar([u8; 32]);
|
||||
|
||||
impl DiffieHellman<Curve25519> for Scalar {
|
||||
fn diffie_hellman(self, pk: MontgomeryPoint) -> GenericArray<u8, U32> {
|
||||
Curve25519::serialize_pk(pk.mul_clamped(self.0))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@ use generic_array::GenericArray;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
|
||||
use super::KeGroup;
|
||||
use crate::errors::InternalError;
|
||||
use crate::errors::{InternalError, ProtocolError};
|
||||
use crate::key_exchange::tripledh::DiffieHellman;
|
||||
|
||||
impl<G> KeGroup for G
|
||||
where
|
||||
@@ -41,10 +42,10 @@ where
|
||||
GenericArray::clone_from_slice(pk.to_encoded_point(true).as_bytes())
|
||||
}
|
||||
|
||||
fn deserialize_pk(bytes: &[u8]) -> Result<Self::Pk, InternalError> {
|
||||
fn deserialize_pk(bytes: &[u8]) -> Result<Self::Pk, ProtocolError> {
|
||||
PublicKey::<Self>::from_sec1_bytes(bytes)
|
||||
.map(|public_key| public_key.to_projective())
|
||||
.map_err(|_| InternalError::PointError)
|
||||
.map_err(|_| ProtocolError::SerializationError)
|
||||
}
|
||||
|
||||
fn random_sk<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Sk {
|
||||
@@ -77,17 +78,29 @@ where
|
||||
scalar.is_zero()
|
||||
}
|
||||
|
||||
fn diffie_hellman(pk: Self::Pk, sk: Self::Sk) -> GenericArray<u8, Self::PkLen> {
|
||||
Self::serialize_pk(pk * sk)
|
||||
}
|
||||
|
||||
fn serialize_sk(sk: Self::Sk) -> GenericArray<u8, Self::SkLen> {
|
||||
sk.into()
|
||||
}
|
||||
|
||||
fn deserialize_sk(bytes: &[u8]) -> Result<Self::Sk, InternalError> {
|
||||
fn deserialize_sk(bytes: &[u8]) -> Result<Self::Sk, ProtocolError> {
|
||||
SecretKey::<Self>::from_slice(bytes)
|
||||
.map(|secret_key| *secret_key.to_nonzero_scalar())
|
||||
.map_err(|_| InternalError::PointError)
|
||||
.map_err(|_| ProtocolError::SerializationError)
|
||||
}
|
||||
}
|
||||
|
||||
impl<G> DiffieHellman<G> for Scalar<G>
|
||||
where
|
||||
G: GroupDigest,
|
||||
FieldBytesSize<G>: ModulusSize,
|
||||
AffinePoint<G>: FromEncodedPoint<G> + ToEncodedPoint<G>,
|
||||
ProjectivePoint<G>: CofactorGroup + ToEncodedPoint<G>,
|
||||
Scalar<G>: FromOkm,
|
||||
{
|
||||
fn diffie_hellman(
|
||||
self,
|
||||
pk: ProjectivePoint<G>,
|
||||
) -> GenericArray<u8, <FieldBytesSize<G> as ModulusSize>::CompressedPointSize> {
|
||||
GenericArray::clone_from_slice((pk * self).to_encoded_point(true).as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ use generic_array::{ArrayLength, GenericArray};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use crate::errors::InternalError;
|
||||
use crate::errors::{InternalError, ProtocolError};
|
||||
|
||||
const STR_OPAQUE_DERIVE_AUTH_KEY_PAIR: [u8; 33] = *b"OPAQUE-DeriveDiffieHellmanKeyPair";
|
||||
|
||||
@@ -41,7 +41,7 @@ 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, InternalError>;
|
||||
fn deserialize_pk(bytes: &[u8]) -> Result<Self::Pk, ProtocolError>;
|
||||
|
||||
/// Generate a random secret key
|
||||
fn random_sk<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Sk;
|
||||
@@ -104,14 +104,11 @@ pub trait KeGroup {
|
||||
/// Return a public key from its secret key
|
||||
fn public_key(sk: Self::Sk) -> Self::Pk;
|
||||
|
||||
/// Diffie-Hellman key exchange
|
||||
fn diffie_hellman(pk: Self::Pk, sk: Self::Sk) -> GenericArray<u8, Self::PkLen>;
|
||||
|
||||
/// Serializes `self`
|
||||
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, InternalError>;
|
||||
fn deserialize_sk(bytes: &[u8]) -> Result<Self::Sk, ProtocolError>;
|
||||
}
|
||||
|
||||
// Helper functions used to compute DeriveAuthKeyPair() (taken from the voprf
|
||||
|
||||
@@ -21,7 +21,8 @@ use subtle::ConstantTimeEq;
|
||||
use voprf::Group;
|
||||
|
||||
use super::KeGroup;
|
||||
use crate::errors::InternalError;
|
||||
use crate::errors::{InternalError, ProtocolError};
|
||||
use crate::key_exchange::tripledh::DiffieHellman;
|
||||
|
||||
/// Implementation for Ristretto255.
|
||||
// This is necessary because Rust lacks specialization, otherwise we could
|
||||
@@ -38,12 +39,12 @@ impl KeGroup for Ristretto255 {
|
||||
pk.compress().to_bytes().into()
|
||||
}
|
||||
|
||||
fn deserialize_pk(bytes: &[u8]) -> Result<Self::Pk, InternalError> {
|
||||
fn deserialize_pk(bytes: &[u8]) -> Result<Self::Pk, ProtocolError> {
|
||||
CompressedRistretto::from_slice(bytes)
|
||||
.map_err(|_| InternalError::PointError)?
|
||||
.map_err(|_| ProtocolError::SerializationError)?
|
||||
.decompress()
|
||||
.filter(|point| point != &RistrettoPoint::identity())
|
||||
.ok_or(InternalError::PointError)
|
||||
.ok_or(ProtocolError::SerializationError)
|
||||
}
|
||||
|
||||
fn random_sk<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Sk {
|
||||
@@ -89,21 +90,17 @@ impl KeGroup for Ristretto255 {
|
||||
RISTRETTO_BASEPOINT_POINT * sk
|
||||
}
|
||||
|
||||
fn diffie_hellman(pk: Self::Pk, sk: Self::Sk) -> GenericArray<u8, Self::PkLen> {
|
||||
Self::serialize_pk(pk * sk)
|
||||
}
|
||||
|
||||
fn serialize_sk(sk: Self::Sk) -> GenericArray<u8, Self::SkLen> {
|
||||
sk.to_bytes().into()
|
||||
}
|
||||
|
||||
fn deserialize_sk(bytes: &[u8]) -> Result<Self::Sk, InternalError> {
|
||||
fn deserialize_sk(bytes: &[u8]) -> Result<Self::Sk, ProtocolError> {
|
||||
bytes
|
||||
.try_into()
|
||||
.ok()
|
||||
.and_then(|bytes| Scalar::from_canonical_bytes(bytes).into())
|
||||
.filter(|scalar| scalar != &Scalar::ZERO)
|
||||
.ok_or(InternalError::PointError)
|
||||
.ok_or(ProtocolError::SerializationError)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,3 +180,9 @@ impl Group for Ristretto255 {
|
||||
<voprf::Ristretto255 as Group>::deserialize_scalar(scalar_bits)
|
||||
}
|
||||
}
|
||||
|
||||
impl DiffieHellman<Ristretto255> for Scalar {
|
||||
fn diffie_hellman(self, pk: RistrettoPoint) -> GenericArray<u8, U32> {
|
||||
Ristretto255::serialize_pk(pk * self)
|
||||
}
|
||||
}
|
||||
|
||||
+20
-14
@@ -17,7 +17,7 @@ use crate::ciphersuite::{CipherSuite, OprfHash};
|
||||
use crate::errors::ProtocolError;
|
||||
use crate::hash::{Hash, ProxyHash};
|
||||
use crate::key_exchange::group::KeGroup;
|
||||
use crate::keypair::{PrivateKey, PublicKey, SecretKey};
|
||||
use crate::keypair::{PrivateKey, PublicKey};
|
||||
|
||||
pub trait KeyExchange<D: Hash, G: KeGroup>
|
||||
where
|
||||
@@ -28,6 +28,9 @@ where
|
||||
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;
|
||||
|
||||
@@ -36,25 +39,28 @@ where
|
||||
) -> Result<(Self::KE1State, Self::KE1Message), ProtocolError>;
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn generate_ke2<
|
||||
'a,
|
||||
'b,
|
||||
'c,
|
||||
'd,
|
||||
OprfCs: voprf::CipherSuite,
|
||||
R: RngCore + CryptoRng,
|
||||
S: SecretKey<G>,
|
||||
>(
|
||||
fn ke2_builder<'a, 'b, 'c, 'd, OprfCs: voprf::CipherSuite, R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
l1_bytes: impl Iterator<Item = &'a [u8]>,
|
||||
l2_bytes: impl Iterator<Item = &'b [u8]>,
|
||||
serialized_credential_request: impl Iterator<Item = &'a [u8]>,
|
||||
serialized_credential_response: impl Iterator<Item = &'b [u8]>,
|
||||
ke1_message: Self::KE1Message,
|
||||
client_s_pk: PublicKey<G>,
|
||||
server_s_sk: S,
|
||||
id_u: impl Iterator<Item = &'c [u8]>,
|
||||
id_s: impl Iterator<Item = &'d [u8]>,
|
||||
context: &[u8],
|
||||
) -> Result<GenerateKe2Result<Self, D, G>, ProtocolError<S::Error>>;
|
||||
) -> Result<Self::KE2Builder, ProtocolError>;
|
||||
|
||||
fn ke2_builder_data(builder: &Self::KE2Builder) -> Self::KE2BuilderData<'_>;
|
||||
|
||||
fn generate_ke2_input(
|
||||
builder: &Self::KE2Builder,
|
||||
server_s_sk: &PrivateKey<G>,
|
||||
) -> Self::KE2BuilderInput;
|
||||
|
||||
fn build_ke2(
|
||||
builder: Self::KE2Builder,
|
||||
input: Self::KE2BuilderInput,
|
||||
) -> Result<GenerateKe2Result<Self, D, G>, ProtocolError>;
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn generate_ke3<'a, 'b, 'c, 'd>(
|
||||
|
||||
+138
-80
@@ -19,6 +19,7 @@ use generic_array::{ArrayLength, GenericArray};
|
||||
use hkdf::{Hkdf, HkdfExtract};
|
||||
use hmac::{Hmac, Mac};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
use crate::errors::utils::{check_slice_size, check_slice_size_atleast};
|
||||
use crate::errors::{InternalError, ProtocolError};
|
||||
@@ -27,7 +28,7 @@ use crate::key_exchange::group::KeGroup;
|
||||
use crate::key_exchange::traits::{
|
||||
Deserialize, GenerateKe2Result, GenerateKe3Result, KeyExchange, Serialize,
|
||||
};
|
||||
use crate::keypair::{KeyPair, PrivateKey, PublicKey, SecretKey};
|
||||
use crate::keypair::{KeyPair, PrivateKey, PublicKey};
|
||||
use crate::serialization::{Input, UpdateExt};
|
||||
|
||||
///////////////
|
||||
@@ -49,6 +50,14 @@ static STR_OPAQUE: &[u8] = b"OPAQUE-";
|
||||
////////////////////////////
|
||||
|
||||
/// The Triple Diffie-Hellman key exchange implementation
|
||||
///
|
||||
/// # Remote Key
|
||||
///
|
||||
/// [`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
|
||||
@@ -95,6 +104,31 @@ where
|
||||
session_key: Output<D>,
|
||||
}
|
||||
|
||||
/// Builder for the second key exchange message
|
||||
#[cfg_attr(
|
||||
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",
|
||||
))
|
||||
)]
|
||||
#[derive_where(Clone)]
|
||||
#[derive_where(Debug, Eq, Hash, PartialEq; D, PublicKey<KG>)]
|
||||
pub struct Ke2Builder<D: Hash, KG: KeGroup>
|
||||
where
|
||||
D::Core: ProxyHash,
|
||||
<D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<D::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>,
|
||||
}
|
||||
|
||||
/// The second key exchange message
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
@@ -130,13 +164,20 @@ where
|
||||
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>;
|
||||
}
|
||||
|
||||
////////////////////////////////
|
||||
// High-level Implementations //
|
||||
// ========================== //
|
||||
////////////////////////////////
|
||||
|
||||
impl<D: Hash, KG: KeGroup> KeyExchange<D, KG> for TripleDh
|
||||
impl<D: Hash, KG: KeGroup + 'static> KeyExchange<D, KG> for TripleDh
|
||||
where
|
||||
KG::Sk: DiffieHellman<KG>,
|
||||
D::Core: ProxyHash,
|
||||
<D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
@@ -158,6 +199,9 @@ where
|
||||
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>;
|
||||
|
||||
@@ -181,71 +225,82 @@ where
|
||||
))
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn generate_ke2<
|
||||
'a,
|
||||
'b,
|
||||
'c,
|
||||
'd,
|
||||
OprfCs: voprf::CipherSuite,
|
||||
R: RngCore + CryptoRng,
|
||||
S: SecretKey<KG>,
|
||||
>(
|
||||
fn ke2_builder<'a, 'b, 'c, 'd, OprfCs: voprf::CipherSuite, R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
serialized_credential_request: impl Iterator<Item = &'a [u8]>,
|
||||
l2_bytes: impl Iterator<Item = &'b [u8]>,
|
||||
serialized_credential_response: impl Iterator<Item = &'b [u8]>,
|
||||
ke1_message: Self::KE1Message,
|
||||
client_s_pk: PublicKey<KG>,
|
||||
server_s_sk: S,
|
||||
id_u: impl Iterator<Item = &'c [u8]>,
|
||||
id_s: impl Iterator<Item = &'d [u8]>,
|
||||
context: &[u8],
|
||||
) -> Result<GenerateKe2Result<Self, D, KG>, ProtocolError<S::Error>> {
|
||||
let server_e_kp = KeyPair::<KG>::generate_random::<OprfCs, _>(rng);
|
||||
) -> Result<Self::KE2Builder, ProtocolError> {
|
||||
let server_e = KeyPair::<KG>::generate_random::<OprfCs, _>(rng);
|
||||
let server_nonce = generate_nonce::<R>(rng);
|
||||
|
||||
let mut transcript_hasher = D::new()
|
||||
let transcript_hasher = D::new()
|
||||
.chain(STR_CONTEXT)
|
||||
.chain_iter(
|
||||
Input::<U2>::from(context)
|
||||
.map_err(ProtocolError::into_custom)?
|
||||
.iter(),
|
||||
)
|
||||
.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(l2_bytes)
|
||||
.chain_iter(serialized_credential_response)
|
||||
.chain(server_nonce)
|
||||
.chain(server_e_kp.public().serialize());
|
||||
.chain(server_e.public().serialize());
|
||||
|
||||
let result = derive_3dh_keys::<D, KG, S>(
|
||||
TripleDhComponents {
|
||||
pk1: ke1_message.client_e_pk.clone(),
|
||||
sk1: server_e_kp.private().clone(),
|
||||
pk2: ke1_message.client_e_pk.clone(),
|
||||
sk2: server_s_sk,
|
||||
pk3: client_s_pk,
|
||||
sk3: server_e_kp.private().clone(),
|
||||
},
|
||||
&transcript_hasher.clone().finalize(),
|
||||
let shared_secret_1 = server_e
|
||||
.private()
|
||||
.ke_diffie_hellman(&ke1_message.client_e_pk);
|
||||
let shared_secret_3 = server_e.private().ke_diffie_hellman(&client_s_pk);
|
||||
|
||||
Ok(Ke2Builder {
|
||||
server_nonce,
|
||||
transcript_hasher,
|
||||
client_e_pk: ke1_message.client_e_pk.clone(),
|
||||
server_e_pk: server_e.public().clone(),
|
||||
shared_secret_1,
|
||||
shared_secret_3,
|
||||
})
|
||||
}
|
||||
|
||||
fn ke2_builder_data(builder: &Self::KE2Builder) -> Self::KE2BuilderData<'_> {
|
||||
&builder.client_e_pk
|
||||
}
|
||||
|
||||
fn generate_ke2_input(
|
||||
builder: &Self::KE2Builder,
|
||||
server_s_sk: &PrivateKey<KG>,
|
||||
) -> Self::KE2BuilderInput {
|
||||
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(),
|
||||
&builder.transcript_hasher.clone().finalize(),
|
||||
)?;
|
||||
|
||||
let mut mac_hasher =
|
||||
Hmac::<D>::new_from_slice(&result.1).map_err(|_| InternalError::HmacError)?;
|
||||
mac_hasher.update(&transcript_hasher.clone().finalize());
|
||||
mac_hasher.update(&builder.transcript_hasher.clone().finalize());
|
||||
let mac = mac_hasher.finalize().into_bytes();
|
||||
|
||||
Digest::update(&mut transcript_hasher, &mac);
|
||||
Digest::update(&mut builder.transcript_hasher, &mac);
|
||||
|
||||
Ok((
|
||||
Ke2State {
|
||||
km3: result.2,
|
||||
hashed_transcript: transcript_hasher.finalize(),
|
||||
hashed_transcript: builder.transcript_hasher.clone().finalize(),
|
||||
session_key: result.0,
|
||||
},
|
||||
Ke2Message {
|
||||
server_nonce,
|
||||
server_e_pk: server_e_kp.public().clone(),
|
||||
server_nonce: builder.server_nonce,
|
||||
server_e_pk: builder.server_e_pk.clone(),
|
||||
mac,
|
||||
},
|
||||
#[cfg(test)]
|
||||
@@ -276,15 +331,12 @@ where
|
||||
.chain_iter(l2_component)
|
||||
.chain(ke2_message.to_bytes_without_mac());
|
||||
|
||||
let result = derive_3dh_keys::<D, KG, PrivateKey<KG>>(
|
||||
TripleDhComponents {
|
||||
pk1: ke2_message.server_e_pk.clone(),
|
||||
sk1: ke1_state.client_e_sk.clone(),
|
||||
pk2: server_s_pk,
|
||||
sk2: ke1_state.client_e_sk.clone(),
|
||||
pk3: ke2_message.server_e_pk.clone(),
|
||||
sk3: client_s_sk,
|
||||
},
|
||||
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),
|
||||
&transcript_hasher.clone().finalize(),
|
||||
)?;
|
||||
|
||||
@@ -335,16 +387,6 @@ where
|
||||
//==================== //
|
||||
/////////////////////////
|
||||
|
||||
// The triple of public and private components used in the 3DH computation
|
||||
struct TripleDhComponents<KG: KeGroup, S: SecretKey<KG>> {
|
||||
pk1: PublicKey<KG>,
|
||||
sk1: PrivateKey<KG>,
|
||||
pk2: PublicKey<KG>,
|
||||
sk2: S,
|
||||
pk3: PublicKey<KG>,
|
||||
sk3: PrivateKey<KG>,
|
||||
}
|
||||
|
||||
// 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>);
|
||||
@@ -361,10 +403,12 @@ type TripleDhDerivationResult<D> = (Output<D>, Output<D>, Output<D>, Output<D>);
|
||||
// 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, S: SecretKey<KG>>(
|
||||
dh: TripleDhComponents<KG, S>,
|
||||
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<S::Error>>
|
||||
) -> Result<TripleDhDerivationResult<D>, ProtocolError>
|
||||
where
|
||||
D::Core: ProxyHash,
|
||||
<D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
@@ -372,36 +416,24 @@ where
|
||||
{
|
||||
let mut hkdf = HkdfExtract::<D>::new(None);
|
||||
|
||||
hkdf.input_ikm(
|
||||
&dh.sk1
|
||||
.diffie_hellman(dh.pk1)
|
||||
.map_err(InternalError::into_custom)?,
|
||||
);
|
||||
hkdf.input_ikm(&dh.sk2.diffie_hellman(dh.pk2)?);
|
||||
hkdf.input_ikm(
|
||||
&dh.sk3
|
||||
.diffie_hellman(dh.pk3)
|
||||
.map_err(InternalError::into_custom)?,
|
||||
);
|
||||
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,
|
||||
)
|
||||
.map_err(ProtocolError::into_custom)?;
|
||||
)?;
|
||||
let session_key = derive_secrets::<D>(
|
||||
&extracted_ikm,
|
||||
STR_SESSION_KEY,
|
||||
hashed_derivation_transcript,
|
||||
)
|
||||
.map_err(ProtocolError::into_custom)?;
|
||||
)?;
|
||||
|
||||
let km2 = hkdf_expand_label::<D>(&handshake_secret, STR_SERVER_MAC, b"")
|
||||
.map_err(ProtocolError::into_custom)?;
|
||||
let km3 = hkdf_expand_label::<D>(&handshake_secret, STR_CLIENT_MAC, b"")
|
||||
.map_err(ProtocolError::into_custom)?;
|
||||
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,
|
||||
@@ -579,6 +611,32 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
{
|
||||
fn drop(&mut self) {
|
||||
struct AssertZeroizeOnDrop<'a, T: ZeroizeOnDrop>(#[allow(unused)] &'a T);
|
||||
|
||||
self.server_nonce.zeroize();
|
||||
self.transcript_hasher.reset();
|
||||
let _ = AssertZeroizeOnDrop(&self.client_e_pk);
|
||||
let _ = AssertZeroizeOnDrop(&self.server_e_pk);
|
||||
self.shared_secret_1.zeroize();
|
||||
self.shared_secret_3.zeroize();
|
||||
}
|
||||
}
|
||||
|
||||
impl<KG: KeGroup, D: Hash> ZeroizeOnDrop for Ke2Builder<D, KG>
|
||||
where
|
||||
D::Core: ProxyHash,
|
||||
<D::Core as BlockSizeUser>::BlockSize: IsLess<U256>,
|
||||
Le<<D::Core as BlockSizeUser>::BlockSize, U256>: NonZero,
|
||||
{
|
||||
}
|
||||
|
||||
impl<KG: KeGroup, D: Hash> Deserialize for Ke2Message<D, KG>
|
||||
where
|
||||
D::Core: ProxyHash,
|
||||
|
||||
+71
-86
@@ -14,8 +14,9 @@ use derive_where::derive_where;
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
|
||||
use crate::errors::{InternalError, ProtocolError};
|
||||
use crate::errors::ProtocolError;
|
||||
use crate::key_exchange::group::KeGroup;
|
||||
use crate::key_exchange::tripledh::DiffieHellman;
|
||||
|
||||
/// A Keypair trait with public-private verification
|
||||
#[cfg_attr(
|
||||
@@ -28,12 +29,17 @@ use crate::key_exchange::group::KeGroup;
|
||||
)]
|
||||
#[derive_where(Clone)]
|
||||
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; KG::Pk, S)]
|
||||
pub struct KeyPair<KG: KeGroup, S: SecretKey<KG> = PrivateKey<KG>> {
|
||||
pub struct KeyPair<KG: KeGroup, S: Clone = PrivateKey<KG>> {
|
||||
pk: PublicKey<KG>,
|
||||
sk: S,
|
||||
}
|
||||
|
||||
impl<KG: KeGroup, S: SecretKey<KG>> KeyPair<KG, S> {
|
||||
impl<KG: KeGroup, S: Clone> KeyPair<KG, S> {
|
||||
/// Creates a new [`KeyPair`] from the given keys.
|
||||
pub fn new(sk: S, pk: PublicKey<KG>) -> Self {
|
||||
Self { pk, sk }
|
||||
}
|
||||
|
||||
/// The public key component
|
||||
pub fn public(&self) -> &PublicKey<KG> {
|
||||
&self.pk
|
||||
@@ -43,20 +49,6 @@ impl<KG: KeGroup, S: SecretKey<KG>> KeyPair<KG, S> {
|
||||
pub fn private(&self) -> &S {
|
||||
&self.sk
|
||||
}
|
||||
|
||||
/// Obtains a [`KeyPair`] from a slice representing the private key
|
||||
pub fn from_private_key_slice(input: &[u8]) -> Result<Self, ProtocolError<S::Error>> {
|
||||
Self::from_private_key(S::deserialize(input)?)
|
||||
}
|
||||
|
||||
/// Obtains a [`KeyPair`] from a private key
|
||||
pub fn from_private_key(private_key: S) -> Result<Self, ProtocolError<S::Error>> {
|
||||
let pk = private_key.public_key()?;
|
||||
Ok(Self {
|
||||
pk,
|
||||
sk: private_key,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<KG: KeGroup> KeyPair<KG> {
|
||||
@@ -106,50 +98,60 @@ where
|
||||
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; KG::Sk)]
|
||||
pub struct PrivateKey<KG: KeGroup>(KG::Sk);
|
||||
|
||||
/// A trait specifying the requirements for a private key container
|
||||
pub trait SecretKey<KG: KeGroup>: Clone + Sized {
|
||||
/// Custom error type that can be passed down to `InternalError::Custom`
|
||||
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>
|
||||
where
|
||||
KG::Sk: DiffieHellman<KG>,
|
||||
{
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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>;
|
||||
|
||||
/// Diffie-Hellman key exchange implementation
|
||||
fn diffie_hellman(
|
||||
&self,
|
||||
pk: PublicKey<KG>,
|
||||
) -> Result<GenericArray<u8, KG::PkLen>, InternalError<Self::Error>>;
|
||||
|
||||
/// Returns public key from private key
|
||||
fn public_key(&self) -> Result<PublicKey<KG>, InternalError<Self::Error>>;
|
||||
|
||||
/// Serialization into bytes
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len>;
|
||||
fn serialize_key_pair(key_pair: &KeyPair<KG, Self>) -> GenericArray<u8, Self::Len>;
|
||||
|
||||
/// Deserialization from bytes
|
||||
fn deserialize(input: &[u8]) -> Result<Self, InternalError<Self::Error>>;
|
||||
fn deserialize_key_pair(input: &[u8]) -> Result<KeyPair<KG, Self>, ProtocolError<Self::Error>>;
|
||||
}
|
||||
|
||||
impl<KG: KeGroup> SecretKey<KG> for PrivateKey<KG> {
|
||||
impl<KG: KeGroup> PrivateKeySerialization<KG> for PrivateKey<KG> {
|
||||
type Error = core::convert::Infallible;
|
||||
type Len = KG::SkLen;
|
||||
|
||||
fn diffie_hellman(
|
||||
&self,
|
||||
pk: PublicKey<KG>,
|
||||
) -> Result<GenericArray<u8, KG::PkLen>, InternalError> {
|
||||
Ok(KG::diffie_hellman(pk.0, self.0))
|
||||
fn serialize_key_pair(key_pair: &KeyPair<KG, Self>) -> GenericArray<u8, Self::Len> {
|
||||
key_pair.private().serialize()
|
||||
}
|
||||
|
||||
fn public_key(&self) -> Result<PublicKey<KG>, InternalError> {
|
||||
Ok(PublicKey(KG::public_key(self.0)))
|
||||
}
|
||||
fn deserialize_key_pair(input: &[u8]) -> Result<KeyPair<KG, Self>, ProtocolError> {
|
||||
let sk = PrivateKey::deserialize(input)?;
|
||||
let pk = sk.public_key();
|
||||
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len> {
|
||||
KG::serialize_sk(self.0)
|
||||
}
|
||||
|
||||
fn deserialize(input: &[u8]) -> Result<Self, InternalError> {
|
||||
KG::deserialize_sk(input).map(Self)
|
||||
Ok(KeyPair::new(sk, pk))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +186,7 @@ pub struct PublicKey<KG: KeGroup>(KG::Pk);
|
||||
|
||||
impl<KG: KeGroup> PublicKey<KG> {
|
||||
/// Convert from bytes
|
||||
pub fn deserialize(key_bytes: &[u8]) -> Result<Self, InternalError> {
|
||||
pub fn deserialize(key_bytes: &[u8]) -> Result<Self, ProtocolError> {
|
||||
KG::deserialize_pk(key_bytes).map(Self)
|
||||
}
|
||||
|
||||
@@ -192,6 +194,11 @@ impl<KG: KeGroup> PublicKey<KG> {
|
||||
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")]
|
||||
@@ -223,7 +230,6 @@ mod tests {
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use super::*;
|
||||
use crate::errors::*;
|
||||
use crate::util;
|
||||
|
||||
#[test]
|
||||
@@ -255,15 +261,15 @@ mod tests {
|
||||
fn pub_from_priv(kp in KeyPair::<$point>::uniform_keypair_strategy::<$point>()) {
|
||||
let pk = kp.public();
|
||||
let sk = kp.private();
|
||||
prop_assert_eq!(&sk.public_key()?, pk);
|
||||
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>()) {
|
||||
|
||||
let dh1 = kp2.private().diffie_hellman(kp1.public().clone())?;
|
||||
let dh2 = kp1.private().diffie_hellman(kp2.public().clone())?;
|
||||
let dh1 = kp2.private().ke_diffie_hellman(&kp1.public());
|
||||
let dh2 = kp1.private().ke_diffie_hellman(kp2.public());
|
||||
|
||||
prop_assert_eq!(dh1, dh2);
|
||||
}
|
||||
@@ -272,7 +278,7 @@ mod tests {
|
||||
fn private_key_slice(kp in KeyPair::<$point>::uniform_keypair_strategy::<$point>()) {
|
||||
let sk_bytes = kp.private().serialize().to_vec();
|
||||
|
||||
let kp2 = KeyPair::<$point>::from_private_key_slice(&sk_bytes)?;
|
||||
let kp2 = PrivateKey::<$point>::deserialize_key_pair(&sk_bytes)?;
|
||||
let kp2_private_bytes = kp2.private().serialize().to_vec();
|
||||
|
||||
prop_assert_eq!(sk_bytes, kp2_private_bytes);
|
||||
@@ -320,38 +326,15 @@ mod tests {
|
||||
#[derive(Clone)]
|
||||
struct RemoteKey(PrivateKey<KeCurve>);
|
||||
|
||||
impl SecretKey<KeCurve> for RemoteKey {
|
||||
type Error = core::convert::Infallible;
|
||||
type Len = <KeCurve as KeGroup>::SkLen;
|
||||
|
||||
fn diffie_hellman(
|
||||
&self,
|
||||
pk: PublicKey<KeCurve>,
|
||||
) -> Result<GenericArray<u8, <KeCurve as KeGroup>::PkLen>, InternalError<Self::Error>>
|
||||
{
|
||||
self.0.diffie_hellman(pk)
|
||||
}
|
||||
|
||||
fn public_key(&self) -> Result<PublicKey<KeCurve>, InternalError<Self::Error>> {
|
||||
self.0.public_key()
|
||||
}
|
||||
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len> {
|
||||
self.0.serialize()
|
||||
}
|
||||
|
||||
fn deserialize(input: &[u8]) -> Result<Self, InternalError<Self::Error>> {
|
||||
PrivateKey::deserialize(input).map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
const PASSWORD: &str = "password";
|
||||
|
||||
let sk = KeCurve::random_sk(&mut OsRng);
|
||||
let sk = RemoteKey(PrivateKey(sk));
|
||||
let keypair = KeyPair::from_private_key(sk).unwrap();
|
||||
let sk = PrivateKey(KeCurve::random_sk(&mut OsRng));
|
||||
let pk = sk.public_key();
|
||||
let sk = RemoteKey(sk);
|
||||
let keypair = KeyPair::new(sk, pk);
|
||||
|
||||
let server_setup = ServerSetup::<Default, RemoteKey>::new_with_key(&mut OsRng, keypair);
|
||||
let server_setup =
|
||||
ServerSetup::<Default, RemoteKey>::new_with_key_pair(&mut OsRng, keypair);
|
||||
|
||||
let ClientRegistrationStartResult {
|
||||
message,
|
||||
@@ -373,11 +356,7 @@ mod tests {
|
||||
message,
|
||||
state: client,
|
||||
} = ClientLogin::<Default>::start(&mut OsRng, PASSWORD.as_bytes()).unwrap();
|
||||
let ServerLoginStartResult {
|
||||
message,
|
||||
state: server,
|
||||
..
|
||||
} = ServerLogin::start(
|
||||
let builder = ServerLogin::builder(
|
||||
&mut OsRng,
|
||||
&server_setup,
|
||||
Some(file),
|
||||
@@ -386,6 +365,12 @@ mod tests {
|
||||
ServerLoginStartParameters::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(
|
||||
PASSWORD.as_bytes(),
|
||||
|
||||
+52
-32
@@ -936,14 +936,12 @@
|
||||
//! ## Remote Private Keys
|
||||
//!
|
||||
//! Servers that want to store their private key in an external location (e.g.
|
||||
//! in an HSM or vault) can do so with the [`SecretKey`](keypair::SecretKey`)
|
||||
//! trait. This allows [`ServerSetup`] to be constructed using an existing
|
||||
//! keypair without exposing the bytes of the private key to this library.
|
||||
//! in an HSM or vault) can do so with [`ServerLogin::builder()`] without
|
||||
//! exposing the bytes of the private key to this library.
|
||||
//! ```
|
||||
//! # use generic_array::{GenericArray, typenum::U0};
|
||||
//! # use opaque_ke::{CipherSuite, errors::{InternalError}, key_exchange::group::KeGroup, keypair::{KeyPair, PrivateKey, PublicKey, SecretKey}, ServerSetup};
|
||||
//! # use opaque_ke::{CipherSuite, ClientLogin, ClientRegistration, ClientRegistrationFinishParameters, ServerRegistration, errors::ProtocolError, keypair::PrivateKey, key_exchange::{group::KeGroup as OKeGroup, tripledh::DiffieHellman}};
|
||||
//! # use rand::rngs::OsRng;
|
||||
//! # use zeroize::Zeroize;
|
||||
//! # struct Default;
|
||||
//! # #[cfg(feature = "ristretto255")]
|
||||
//! # impl CipherSuite for Default {
|
||||
@@ -959,45 +957,66 @@
|
||||
//! # type KeyExchange = opaque_ke::key_exchange::tripledh::TripleDh;
|
||||
//! # type Ksf = opaque_ke::ksf::Identity;
|
||||
//! # }
|
||||
//! # #[derive(Debug)]
|
||||
//! # type KeGroup = <Default as CipherSuite>::KeGroup;
|
||||
//! # #[derive(Debug, thiserror::Error)]
|
||||
//! # #[error("test error")]
|
||||
//! # struct YourRemoteKeyError;
|
||||
//! # #[derive(Clone)]
|
||||
//! # struct YourRemoteKey(<<Default as CipherSuite>::KeGroup as KeGroup>::Sk);
|
||||
//! # struct YourRemoteKey(<KeGroup as OKeGroup>::Sk);
|
||||
//! # impl YourRemoteKey {
|
||||
//! # fn diffie_hellman(&self, pk: &[u8]) -> Result<GenericArray<u8, <<Default as CipherSuite>::KeGroup as KeGroup>::PkLen>, YourRemoteKeyError> { todo!() }
|
||||
//! # fn public_key(&self) -> Result<GenericArray<u8, <<Default as CipherSuite>::KeGroup as KeGroup>::PkLen>, YourRemoteKeyError> { Ok(<<Default as CipherSuite>::KeGroup>::serialize_pk(<<Default as CipherSuite>::KeGroup>::public_key(self.0))) }
|
||||
//! # 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()))
|
||||
//! # }
|
||||
//! # }
|
||||
//! impl SecretKey<<Default as CipherSuite>::KeGroup> for YourRemoteKey {
|
||||
//! use opaque_ke::{ServerLogin, ServerLoginStartParameters, ServerSetup};
|
||||
//! use opaque_ke::keypair::{KeyPair, PrivateKeySerialization, PublicKey};
|
||||
//!
|
||||
//! // Implement if you intend to use `ServerSetup::de/serialize` instead of `serde`.
|
||||
//! impl PrivateKeySerialization<KeGroup> for YourRemoteKey {
|
||||
//! type Error = YourRemoteKeyError;
|
||||
//! type Len = U0;
|
||||
//!
|
||||
//! fn diffie_hellman(
|
||||
//! &self,
|
||||
//! pk: PublicKey<<Default as CipherSuite>::KeGroup>,
|
||||
//! ) -> Result<GenericArray<u8, <<Default as CipherSuite>::KeGroup as KeGroup>::PkLen>, InternalError<Self::Error>> {
|
||||
//! YourRemoteKey::diffie_hellman(self, &pk.serialize()).map_err(InternalError::Custom)
|
||||
//! fn serialize_key_pair(_: &KeyPair<KeGroup, Self>) -> GenericArray<u8, Self::Len> {
|
||||
//! unimplemented!()
|
||||
//! }
|
||||
//!
|
||||
//! fn public_key(
|
||||
//! &self
|
||||
//! ) -> Result<PublicKey<<Default as CipherSuite>::KeGroup>, InternalError<Self::Error>> {
|
||||
//! PublicKey::deserialize(&YourRemoteKey::public_key(self).map_err(InternalError::Custom)?).map_err(InternalError::into_custom)
|
||||
//! }
|
||||
//!
|
||||
//! fn serialize(&self) -> GenericArray<u8, Self::Len> {
|
||||
//! // if you use Serde and the "serde" crate feature, you won't need this
|
||||
//! todo!()
|
||||
//! }
|
||||
//!
|
||||
//! fn deserialize(input: &[u8]) -> Result<Self, InternalError<Self::Error>> {
|
||||
//! // if you use Serde and the "serde" crate feature, you won't need this
|
||||
//! todo!()
|
||||
//! fn deserialize_key_pair(input: &[u8]) -> Result<KeyPair<KeGroup, Self>, ProtocolError<Self::Error>> {
|
||||
//! unimplemented!()
|
||||
//! }
|
||||
//! }
|
||||
//!
|
||||
//! # let remote_key = YourRemoteKey(<<Default as CipherSuite>::KeGroup>::random_sk(&mut OsRng));
|
||||
//! let keypair = KeyPair::from_private_key(remote_key).unwrap();
|
||||
//! let server_setup = ServerSetup::<Default, YourRemoteKey>::new_with_key(&mut OsRng, keypair);
|
||||
//! # let sk = KeGroup::random_sk(&mut OsRng);
|
||||
//! # let pk = KeGroup::public_key(sk);
|
||||
//! # let pk = KeGroup::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",
|
||||
//! # )?;
|
||||
//! # 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 OsRng, b"password", server_registration_start_result.message, ClientRegistrationFinishParameters::default())?;
|
||||
//! # 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)?;
|
||||
//! let server_login_builder = ServerLogin::builder(
|
||||
//! &mut server_rng,
|
||||
//! &server_setup,
|
||||
//! Some(password_file),
|
||||
//! client_login_start_result.message,
|
||||
//! b"[email protected]",
|
||||
//! ServerLoginStartParameters::default(),
|
||||
//! )?;
|
||||
//! let client_e_public_key = server_login_builder.data();
|
||||
//! let shared_secret = server_login_builder.private_key().diffie_hellman(&client_e_public_key)?;
|
||||
//! let server_login_start_result = server_login_builder.build(shared_secret)?;
|
||||
//! # Ok::<(), anyhow::Error>(())
|
||||
//! ```
|
||||
//!
|
||||
//! ## Custom KSF and Parameters
|
||||
@@ -1168,6 +1187,7 @@ pub use crate::messages::{
|
||||
CredentialFinalization, CredentialFinalizationLen, CredentialRequest, CredentialRequestLen,
|
||||
CredentialResponse, CredentialResponseLen, RegistrationRequest, RegistrationRequestLen,
|
||||
RegistrationResponse, RegistrationResponseLen, RegistrationUpload, RegistrationUploadLen,
|
||||
ServerLoginBuilder,
|
||||
};
|
||||
pub use crate::opaque::{
|
||||
ClientLogin, ClientLoginFinishParameters, ClientLoginFinishResult, ClientLoginStartResult,
|
||||
|
||||
+63
-3
@@ -18,6 +18,7 @@ use generic_array::{ArrayLength, GenericArray};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use subtle::ConstantTimeEq;
|
||||
use voprf::Group;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
use crate::ciphersuite::{CipherSuite, OprfGroup, OprfHash};
|
||||
use crate::envelope::{Envelope, EnvelopeLen};
|
||||
@@ -29,8 +30,10 @@ use crate::key_exchange::traits::{
|
||||
Deserialize, Ke1MessageLen, Ke2MessageLen, Ke3MessageLen, KeyExchange, Serialize,
|
||||
};
|
||||
use crate::key_exchange::tripledh::NonceLen;
|
||||
use crate::keypair::{PublicKey, SecretKey};
|
||||
use crate::opaque::{MaskedResponse, MaskedResponseLen, ServerSetup};
|
||||
use crate::keypair::PublicKey;
|
||||
use crate::opaque::{
|
||||
MaskedResponse, MaskedResponseLen, ServerLogin, ServerLoginStartResult, ServerSetup,
|
||||
};
|
||||
|
||||
////////////////////////////
|
||||
// High-level API Structs //
|
||||
@@ -107,6 +110,63 @@ pub struct CredentialRequest<CS: CipherSuite> {
|
||||
pub(crate) ke1_message: <CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE1Message,
|
||||
}
|
||||
|
||||
/// Builder for [`ServerLogin`](crate::ServerLogin) when using remote keys.
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound(
|
||||
deserialize = "S: serde::Deserialize<'de>, <CS::KeyExchange as KeyExchange<OprfHash<CS>, \
|
||||
CS::KeGroup>>::KE2Builder: serde::Deserialize<'de>",
|
||||
serialize = "S: serde::Serialize, <CS::KeyExchange as KeyExchange<OprfHash<CS>, \
|
||||
CS::KeGroup>>::KE2Builder: serde::Serialize"
|
||||
))
|
||||
)]
|
||||
#[derive_where(Clone)]
|
||||
#[derive_where(
|
||||
Debug, Eq, PartialEq;
|
||||
S,
|
||||
voprf::EvaluationElement<CS::OprfCs>,
|
||||
<CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE2Builder,
|
||||
)]
|
||||
pub struct ServerLoginBuilder<CS: CipherSuite, S: Clone> {
|
||||
pub(crate) server_s_sk: S,
|
||||
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,
|
||||
}
|
||||
|
||||
impl<CS: CipherSuite, S: Clone> ServerLoginBuilder<CS, S> {
|
||||
/// 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<'_> {
|
||||
CS::KeyExchange::ke2_builder_data(&self.ke2_builder)
|
||||
}
|
||||
|
||||
/// The handle to the corresponding [`ServerSetup`]s private key.
|
||||
pub fn private_key(&self) -> &S {
|
||||
&self.server_s_sk
|
||||
}
|
||||
|
||||
/// Build [`ServerLogin`] after attaining the input for the key exchange. To
|
||||
/// understand what kind of input is expected here, refer to the
|
||||
/// documentation of your chosen [`CipherSuite::KeyExchange`].
|
||||
///
|
||||
/// See [`ServerLogin::start()`] for the regular path.
|
||||
pub fn build(
|
||||
self,
|
||||
input: <CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE2BuilderInput,
|
||||
) -> Result<ServerLoginStartResult<CS>, ProtocolError> {
|
||||
ServerLogin::build(self, input)
|
||||
}
|
||||
}
|
||||
|
||||
/// The answer sent by the server to the user, upon reception of the login
|
||||
/// attempt
|
||||
#[cfg_attr(
|
||||
@@ -265,7 +325,7 @@ impl<CS: CipherSuite> RegistrationUpload<CS> {
|
||||
}
|
||||
|
||||
// Creates a dummy instance used for faking a [CredentialResponse]
|
||||
pub(crate) fn dummy<R: RngCore + CryptoRng, S: SecretKey<CS::KeGroup>>(
|
||||
pub(crate) fn dummy<R: RngCore + CryptoRng, S: Clone>(
|
||||
rng: &mut R,
|
||||
server_setup: &ServerSetup<CS, S>,
|
||||
) -> Self {
|
||||
|
||||
+92
-43
@@ -8,7 +8,7 @@
|
||||
|
||||
//! Provides the main OPAQUE API
|
||||
|
||||
use core::ops::Add;
|
||||
use core::ops::{Add, Deref};
|
||||
|
||||
use derive_where::derive_where;
|
||||
use digest::Output;
|
||||
@@ -19,6 +19,7 @@ use hkdf::{Hkdf, HkdfExtract};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use subtle::ConstantTimeEq;
|
||||
use voprf::Group;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
use crate::ciphersuite::{CipherSuite, OprfGroup, OprfHash};
|
||||
use crate::envelope::{Envelope, EnvelopeLen};
|
||||
@@ -30,13 +31,13 @@ use crate::key_exchange::traits::{
|
||||
Deserialize, Ke1MessageLen, Ke1StateLen, Ke2StateLen, KeyExchange, Serialize,
|
||||
};
|
||||
use crate::key_exchange::tripledh::NonceLen;
|
||||
use crate::keypair::{KeyPair, PrivateKey, PublicKey, SecretKey};
|
||||
use crate::keypair::{KeyPair, PrivateKey, PrivateKeySerialization, PublicKey};
|
||||
use crate::ksf::Ksf;
|
||||
use crate::messages::{CredentialRequestLen, RegistrationUploadLen};
|
||||
use crate::serialization::Input;
|
||||
use crate::{
|
||||
CredentialFinalization, CredentialRequest, CredentialResponse, RegistrationRequest,
|
||||
RegistrationResponse, RegistrationUpload,
|
||||
RegistrationResponse, RegistrationUpload, ServerLoginBuilder,
|
||||
};
|
||||
|
||||
///////////////
|
||||
@@ -64,12 +65,9 @@ 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, S)]
|
||||
pub struct ServerSetup<
|
||||
CS: CipherSuite,
|
||||
S: SecretKey<CS::KeGroup> = PrivateKey<<CS as CipherSuite>::KeGroup>,
|
||||
> {
|
||||
oprf_seed: Output<OprfHash<CS>>,
|
||||
#[derive_where(Debug, Eq, PartialEq; <CS::KeGroup as KeGroup>::Pk, <CS::KeGroup as KeGroup>::Sk, S)]
|
||||
pub struct ServerSetup<CS: CipherSuite, S: Clone = PrivateKey<<CS as CipherSuite>::KeGroup>> {
|
||||
oprf_seed: Zeroizing<Output<OprfHash<CS>>>,
|
||||
keypair: KeyPair<CS::KeGroup, S>,
|
||||
pub(crate) fake_keypair: KeyPair<CS::KeGroup>,
|
||||
}
|
||||
@@ -159,21 +157,21 @@ impl<CS: CipherSuite> ServerSetup<CS, PrivateKey<CS::KeGroup>> {
|
||||
/// 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);
|
||||
Self::new_with_key(rng, keypair)
|
||||
Self::new_with_key_pair(rng, keypair)
|
||||
}
|
||||
}
|
||||
|
||||
/// Length of [`ServerSetup`] in bytes for serialization.
|
||||
pub type ServerSetupLen<CS: CipherSuite, S: SecretKey<CS::KeGroup>> =
|
||||
pub type ServerSetupLen<CS: CipherSuite, S: PrivateKeySerialization<CS::KeGroup>> =
|
||||
Sum<Sum<OutputSize<OprfHash<CS>>, S::Len>, <CS::KeGroup as KeGroup>::SkLen>;
|
||||
|
||||
impl<CS: CipherSuite, S: SecretKey<CS::KeGroup>> ServerSetup<CS, S> {
|
||||
impl<CS: CipherSuite, S: Clone> ServerSetup<CS, S> {
|
||||
/// Create [`ServerSetup`] with the given keypair
|
||||
///
|
||||
/// This function should not be used to restore a previously-existing
|
||||
/// instance of [`ServerSetup`]. Instead, use [`ServerSetup::serialize`] and
|
||||
/// [`ServerSetup::deserialize`] for this purpose.
|
||||
pub fn new_with_key<R: CryptoRng + RngCore>(
|
||||
pub fn new_with_key_pair<R: CryptoRng + RngCore>(
|
||||
rng: &mut R,
|
||||
keypair: KeyPair<CS::KeGroup, S>,
|
||||
) -> Self {
|
||||
@@ -181,7 +179,7 @@ impl<CS: CipherSuite, S: SecretKey<CS::KeGroup>> ServerSetup<CS, S> {
|
||||
rng.fill_bytes(&mut oprf_seed);
|
||||
|
||||
Self {
|
||||
oprf_seed,
|
||||
oprf_seed: Zeroizing::new(oprf_seed),
|
||||
keypair,
|
||||
fake_keypair: KeyPair::<CS::KeGroup>::generate_random::<CS::OprfCs, _>(rng),
|
||||
}
|
||||
@@ -190,6 +188,7 @@ impl<CS: CipherSuite, S: SecretKey<CS::KeGroup>> ServerSetup<CS, S> {
|
||||
/// Serialization into bytes
|
||||
pub fn serialize(&self) -> GenericArray<u8, ServerSetupLen<CS, S>>
|
||||
where
|
||||
S: PrivateKeySerialization<CS::KeGroup>,
|
||||
// ServerSetup: Hash + KeSk + KeSk
|
||||
OutputSize<OprfHash<CS>>: Add<S::Len>,
|
||||
Sum<OutputSize<OprfHash<CS>>, S::Len>:
|
||||
@@ -197,21 +196,26 @@ impl<CS: CipherSuite, S: SecretKey<CS::KeGroup>> ServerSetup<CS, S> {
|
||||
ServerSetupLen<CS, S>: ArrayLength<u8>,
|
||||
{
|
||||
self.oprf_seed
|
||||
.deref()
|
||||
.clone()
|
||||
.concat(self.keypair.private().serialize())
|
||||
.concat(S::serialize_key_pair(&self.keypair))
|
||||
.concat(self.fake_keypair.private().serialize())
|
||||
}
|
||||
|
||||
/// Deserialization from bytes
|
||||
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError<S::Error>> {
|
||||
pub fn deserialize(input: &[u8]) -> Result<Self, ProtocolError<S::Error>>
|
||||
where
|
||||
S: PrivateKeySerialization<CS::KeGroup>,
|
||||
{
|
||||
let seed_len = OutputSize::<OprfHash<CS>>::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")?;
|
||||
let checked_slice = check_slice_size(input, seed_len + key_len + key_len, "server_setup")
|
||||
.map_err(ProtocolError::into_custom)?;
|
||||
|
||||
Ok(Self {
|
||||
oprf_seed: GenericArray::clone_from_slice(&checked_slice[..seed_len]),
|
||||
keypair: KeyPair::from_private_key_slice(&checked_slice[seed_len..seed_len + key_len])?,
|
||||
fake_keypair: KeyPair::from_private_key_slice(&checked_slice[seed_len + key_len..])
|
||||
oprf_seed: Zeroizing::new(GenericArray::clone_from_slice(&checked_slice[..seed_len])),
|
||||
keypair: S::deserialize_key_pair(&checked_slice[seed_len..seed_len + key_len])?,
|
||||
fake_keypair: PrivateKey::deserialize_key_pair(&checked_slice[seed_len + key_len..])
|
||||
.map_err(ProtocolError::into_custom)?,
|
||||
})
|
||||
}
|
||||
@@ -368,7 +372,7 @@ impl<CS: CipherSuite> ServerRegistration<CS> {
|
||||
|
||||
/// From the client's "blinded" password, returns a response to be sent back
|
||||
/// to the client, as well as a [`ServerRegistration`]
|
||||
pub fn start<S: SecretKey<CS::KeGroup>>(
|
||||
pub fn start<S: Clone>(
|
||||
server_setup: &ServerSetup<CS, S>,
|
||||
message: RegistrationRequest<CS>,
|
||||
credential_identifier: &[u8],
|
||||
@@ -395,7 +399,7 @@ impl<CS: CipherSuite> ServerRegistration<CS> {
|
||||
}
|
||||
|
||||
// Creates a dummy instance used for faking a [CredentialResponse]
|
||||
pub(crate) fn dummy<R: RngCore + CryptoRng, S: SecretKey<CS::KeGroup>>(
|
||||
pub(crate) fn dummy<R: RngCore + CryptoRng, S: Clone>(
|
||||
rng: &mut R,
|
||||
server_setup: &ServerSetup<CS, S>,
|
||||
) -> Self {
|
||||
@@ -594,9 +598,10 @@ impl<CS: CipherSuite> ServerLogin<CS> {
|
||||
})
|
||||
}
|
||||
|
||||
/// From the client's "blinded" password, returns a challenge to be sent
|
||||
/// back to the client, as well as a [`ServerLogin`]
|
||||
pub fn start<R: RngCore + CryptoRng, S: SecretKey<CS::KeGroup>>(
|
||||
/// Create a [`ServerLoginBuilder`] to use with a remote private key.
|
||||
///
|
||||
/// See [`ServerLogin::start()`] for the regular path.
|
||||
pub fn builder<R: RngCore + CryptoRng, S: Clone>(
|
||||
rng: &mut R,
|
||||
server_setup: &ServerSetup<CS, S>,
|
||||
password_file: Option<ServerRegistration<CS>>,
|
||||
@@ -606,7 +611,7 @@ impl<CS: CipherSuite> ServerLogin<CS> {
|
||||
context,
|
||||
identifiers,
|
||||
}: ServerLoginStartParameters,
|
||||
) -> Result<ServerLoginStartResult<CS>, ProtocolError<S::Error>>
|
||||
) -> Result<ServerLoginBuilder<CS, S>, ProtocolError>
|
||||
where
|
||||
// MaskedResponse: (Nonce + Hash) + KePk
|
||||
NonceLen: Add<OutputSize<OprfHash<CS>>>,
|
||||
@@ -621,8 +626,7 @@ impl<CS: CipherSuite> ServerLogin<CS> {
|
||||
|
||||
let client_s_pk = record.0.client_s_pk.clone();
|
||||
let context = context.unwrap_or(&[]);
|
||||
let server_s_sk = server_setup.keypair.private();
|
||||
let server_s_pk = server_s_sk.public_key()?;
|
||||
let server_s_pk = server_setup.keypair.public();
|
||||
|
||||
let mut masking_nonce = GenericArray::<_, NonceLen>::default();
|
||||
rng.fill_bytes(&mut masking_nonce);
|
||||
@@ -630,17 +634,15 @@ impl<CS: CipherSuite> ServerLogin<CS> {
|
||||
let masked_response = mask_response(
|
||||
&record.0.masking_key,
|
||||
masking_nonce.as_slice(),
|
||||
&server_s_pk,
|
||||
server_s_pk,
|
||||
&record.0.envelope,
|
||||
)
|
||||
.map_err(ProtocolError::into_custom)?;
|
||||
)?;
|
||||
|
||||
let (id_u, id_s) = bytestrings_from_identifiers::<CS::KeGroup>(
|
||||
identifiers,
|
||||
client_s_pk.serialize(),
|
||||
server_s_pk.serialize(),
|
||||
)
|
||||
.map_err(ProtocolError::into_custom)?;
|
||||
)?;
|
||||
|
||||
let blinded_element =
|
||||
OprfGroup::<CS>::serialize_elem(credential_request.blinded_element.value());
|
||||
@@ -648,32 +650,46 @@ impl<CS: CipherSuite> ServerLogin<CS> {
|
||||
let credential_request_bytes =
|
||||
CredentialRequest::<CS>::serialize_iter(&blinded_element, &ke1_message);
|
||||
|
||||
let oprf_key = oprf_key_from_seed::<CS>(&server_setup.oprf_seed, credential_identifier)
|
||||
.map_err(ProtocolError::into_custom)?;
|
||||
let server = voprf::OprfServer::new_with_key(&oprf_key)
|
||||
.map_err(|e| ProtocolError::into_custom(e.into()))?;
|
||||
let oprf_key = oprf_key_from_seed::<CS>(&server_setup.oprf_seed, credential_identifier)?;
|
||||
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 result = CS::KeyExchange::generate_ke2::<CS::OprfCs, _, _>(
|
||||
let ke2_builder = CS::KeyExchange::ke2_builder::<CS::OprfCs, _>(
|
||||
rng,
|
||||
credential_request_bytes,
|
||||
credential_response_component,
|
||||
credential_request.ke1_message.clone(),
|
||||
client_s_pk,
|
||||
server_s_sk.clone(),
|
||||
id_u.iter(),
|
||||
id_s.iter(),
|
||||
context,
|
||||
)?;
|
||||
|
||||
let credential_response = CredentialResponse {
|
||||
Ok(ServerLoginBuilder {
|
||||
server_s_sk: server_setup.keypair().private().clone(),
|
||||
evaluation_element,
|
||||
masking_nonce,
|
||||
masking_nonce: Zeroizing::new(masking_nonce),
|
||||
masked_response,
|
||||
#[cfg(test)]
|
||||
oprf_key: Zeroizing::new(oprf_key),
|
||||
ke2_builder,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn build<S: Clone>(
|
||||
builder: ServerLoginBuilder<CS, S>,
|
||||
input: <CS::KeyExchange as KeyExchange<OprfHash<CS>, CS::KeGroup>>::KE2BuilderInput,
|
||||
) -> Result<ServerLoginStartResult<CS>, ProtocolError> {
|
||||
let result = CS::KeyExchange::build_ke2(builder.ke2_builder.clone(), input)?;
|
||||
|
||||
let credential_response = CredentialResponse {
|
||||
evaluation_element: builder.evaluation_element.clone(),
|
||||
masking_nonce: *builder.masking_nonce.deref(),
|
||||
masked_response: builder.masked_response.clone(),
|
||||
ke2_message: result.1,
|
||||
};
|
||||
|
||||
@@ -687,10 +703,43 @@ impl<CS: CipherSuite> ServerLogin<CS> {
|
||||
#[cfg(test)]
|
||||
server_mac_key: result.3,
|
||||
#[cfg(test)]
|
||||
oprf_key,
|
||||
oprf_key: builder.oprf_key.deref().clone(),
|
||||
})
|
||||
}
|
||||
|
||||
/// From the client's "blinded" password, returns a challenge to be sent
|
||||
/// back to the client, as well as a [`ServerLogin`]
|
||||
pub fn start<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
server_setup: &ServerSetup<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>,
|
||||
{
|
||||
let builder = Self::builder(
|
||||
rng,
|
||||
server_setup,
|
||||
password_file,
|
||||
credential_request,
|
||||
credential_identifier,
|
||||
parameters,
|
||||
)?;
|
||||
let input = CS::KeyExchange::generate_ke2_input(
|
||||
&builder.ke2_builder,
|
||||
server_setup.keypair.private(),
|
||||
);
|
||||
|
||||
Self::build(builder, input)
|
||||
}
|
||||
|
||||
/// From the client's second and final message, check the client's
|
||||
/// authentication and produce a message transport
|
||||
pub fn finish(
|
||||
@@ -942,7 +991,7 @@ fn oprf_key_from_seed<CS: CipherSuite>(
|
||||
derive(serde::Deserialize, serde::Serialize),
|
||||
serde(bound = "")
|
||||
)]
|
||||
#[derive_where(Clone)]
|
||||
#[derive_where(Clone, ZeroizeOnDrop)]
|
||||
#[derive_where(Debug, Eq, Hash, PartialEq)]
|
||||
pub(crate) struct MaskedResponse<CS: CipherSuite> {
|
||||
pub(crate) nonce: GenericArray<u8, NonceLen>,
|
||||
|
||||
@@ -28,7 +28,7 @@ use crate::key_exchange::traits::{
|
||||
Deserialize, Ke1MessageLen, Ke1StateLen, Ke2MessageLen, KeyExchange, Serialize,
|
||||
};
|
||||
use crate::key_exchange::tripledh::{NonceLen, TripleDh};
|
||||
use crate::keypair::{KeyPair, SecretKey};
|
||||
use crate::keypair::KeyPair;
|
||||
use crate::messages::CredentialResponseWithoutKeLen;
|
||||
use crate::opaque::{ClientLoginLen, ClientRegistrationLen, MaskedResponseLen};
|
||||
use crate::serialization::{i2osp, os2ip};
|
||||
|
||||
@@ -27,8 +27,7 @@ use crate::errors::*;
|
||||
use crate::hash::OutputSize;
|
||||
use crate::key_exchange::group::KeGroup;
|
||||
use crate::key_exchange::traits::{Ke1MessageLen, Ke1StateLen, Ke2MessageLen};
|
||||
use crate::key_exchange::tripledh::{NonceLen, TripleDh};
|
||||
use crate::keypair::SecretKey;
|
||||
use crate::key_exchange::tripledh::{DiffieHellman, NonceLen, TripleDh};
|
||||
use crate::ksf::Identity;
|
||||
use crate::messages::{
|
||||
CredentialRequestLen, CredentialResponseLen, CredentialResponseWithoutKeLen,
|
||||
@@ -1497,6 +1496,7 @@ fn test_zeroize_client_login_start() -> Result<(), ProtocolError> {
|
||||
_test_vector: &str,
|
||||
) -> Result<(), ProtocolError>
|
||||
where
|
||||
<CS::KeGroup as KeGroup>::Sk: DiffieHellman<CS::KeGroup>,
|
||||
// CredentialRequest: KgPk + Ke1Message
|
||||
<OprfGroup<CS> as Group>::ElemLen: Add<Sum<NonceLen, <CS::KeGroup as KeGroup>::PkLen>>,
|
||||
CredentialRequestLen<CS>: ArrayLength<u8>,
|
||||
@@ -1595,6 +1595,7 @@ fn test_zeroize_client_login_finish() -> Result<(), ProtocolError> {
|
||||
_test_vector: &str,
|
||||
) -> Result<(), ProtocolError>
|
||||
where
|
||||
<CS::KeGroup as KeGroup>::Sk: DiffieHellman<CS::KeGroup>,
|
||||
// MaskedResponse: (Nonce + Hash) + KePk
|
||||
NonceLen: Add<OutputSize<OprfHash<CS>>>,
|
||||
Sum<NonceLen, OutputSize<OprfHash<CS>>>:
|
||||
|
||||
@@ -12,4 +12,6 @@ mod full_test_vectors;
|
||||
pub mod mock_rng;
|
||||
mod opaque_vectors;
|
||||
mod parser;
|
||||
#[cfg(test_hsm)]
|
||||
mod remote_key;
|
||||
mod test_opaque_vectors;
|
||||
|
||||
@@ -0,0 +1,373 @@
|
||||
use std::env;
|
||||
use std::ops::Add;
|
||||
use std::sync::{LazyLock, Mutex};
|
||||
use std::vec::Vec;
|
||||
|
||||
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;
|
||||
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;
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
use p256::NistP256;
|
||||
use p384::NistP384;
|
||||
use p521::NistP521;
|
||||
use rand::rngs::OsRng;
|
||||
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq};
|
||||
|
||||
use crate::ciphersuite::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};
|
||||
|
||||
#[test]
|
||||
fn p256() {
|
||||
struct Suite;
|
||||
|
||||
impl CipherSuite for Suite {
|
||||
type OprfCs = NistP256;
|
||||
type KeGroup = NistP256;
|
||||
type KeyExchange = TripleDh;
|
||||
type Ksf = Identity;
|
||||
}
|
||||
|
||||
test::<Suite>(Mechanism::EccKeyPairGen, NistP256::OID);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn p384() {
|
||||
struct Suite;
|
||||
|
||||
impl CipherSuite for Suite {
|
||||
type OprfCs = NistP384;
|
||||
type KeGroup = NistP384;
|
||||
type KeyExchange = TripleDh;
|
||||
type Ksf = Identity;
|
||||
}
|
||||
|
||||
test::<Suite>(Mechanism::EccKeyPairGen, NistP384::OID);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn p521() {
|
||||
struct Suite;
|
||||
|
||||
impl CipherSuite for Suite {
|
||||
type OprfCs = NistP521;
|
||||
type KeGroup = NistP521;
|
||||
type KeyExchange = TripleDh;
|
||||
type Ksf = Identity;
|
||||
}
|
||||
|
||||
test::<Suite>(Mechanism::EccKeyPairGen, NistP521::OID);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(feature = "curve25519", feature = "ristretto255"))]
|
||||
fn curve25519() {
|
||||
struct Suite;
|
||||
|
||||
impl CipherSuite for Suite {
|
||||
type OprfCs = Ristretto255;
|
||||
type KeGroup = Curve25519;
|
||||
type KeyExchange = TripleDh;
|
||||
type Ksf = Identity;
|
||||
}
|
||||
|
||||
test::<Suite>(
|
||||
// This should be [`Mechanism::EccMontgomeryKeyPairGen`], but SoftHSM has an incorrect
|
||||
// implementation. See https://github.com/softhsm/SoftHSMv2/issues/647.
|
||||
Mechanism::EccEdwardsKeyPairGen,
|
||||
ObjectIdentifier::new("1.3.101.110").unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
#[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>;
|
||||
}
|
||||
|
||||
fn test<CS: CipherSuite<KeyExchange = TripleDh>>(mechanism: Mechanism, oid: ObjectIdentifier)
|
||||
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>,
|
||||
{
|
||||
let (remote_key, pk) = pkcs11_generate_key_pair(mechanism, oid);
|
||||
|
||||
let keypair = KeyPair::new(RemoteKey(remote_key), pk);
|
||||
let server_setup = ServerSetup::new_with_key_pair(&mut OsRng, keypair);
|
||||
|
||||
const PASSWORD: &str = "password";
|
||||
|
||||
let ClientRegistrationStartResult {
|
||||
message,
|
||||
state: client,
|
||||
} = ClientRegistration::<CS>::start(&mut OsRng, PASSWORD.as_bytes()).unwrap();
|
||||
let message = ServerRegistration::start(&server_setup, message, &[])
|
||||
.unwrap()
|
||||
.message;
|
||||
let message = client
|
||||
.finish(
|
||||
&mut OsRng,
|
||||
PASSWORD.as_bytes(),
|
||||
message,
|
||||
ClientRegistrationFinishParameters::default(),
|
||||
)
|
||||
.unwrap()
|
||||
.message;
|
||||
let file = ServerRegistration::finish(message);
|
||||
|
||||
let ClientLoginStartResult {
|
||||
message,
|
||||
state: client,
|
||||
} = ClientLogin::<CS>::start(&mut OsRng, PASSWORD.as_bytes()).unwrap();
|
||||
let builder = ServerLogin::builder(
|
||||
&mut OsRng,
|
||||
&server_setup,
|
||||
Some(file),
|
||||
message,
|
||||
&[],
|
||||
ServerLoginStartParameters::default(),
|
||||
)
|
||||
.unwrap();
|
||||
let shared_secret = builder
|
||||
.private_key()
|
||||
.pkcs11_diffie_hellman(server_setup.keypair().public(), builder.data());
|
||||
|
||||
let ServerLoginStartResult {
|
||||
message,
|
||||
state: server,
|
||||
..
|
||||
} = builder.clone().build(shared_secret).unwrap();
|
||||
|
||||
let message = client
|
||||
.clone()
|
||||
.finish(
|
||||
PASSWORD.as_bytes(),
|
||||
message,
|
||||
ClientLoginFinishParameters::default(),
|
||||
)
|
||||
.map(|result| result.message);
|
||||
|
||||
message
|
||||
.map(|message| server.finish(message).unwrap())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
static SESSION: LazyLock<Mutex<Session>> = LazyLock::new(|| {
|
||||
let module = env::var("PKCS11_MODULE").expect("`PKCS11_MODULE` environment variable");
|
||||
let pkcs11 = Pkcs11::new(module).unwrap();
|
||||
pkcs11.initialize(CInitializeArgs::OsThreads).unwrap();
|
||||
|
||||
let slot = pkcs11.get_slots_with_token().unwrap()[0];
|
||||
|
||||
let so_pin = AuthPin::new("abcdef".into());
|
||||
pkcs11.init_token(slot, &so_pin, "Test Token").unwrap();
|
||||
|
||||
let user_pin = AuthPin::new("fedcba".into());
|
||||
|
||||
{
|
||||
let session = pkcs11.open_rw_session(slot).unwrap();
|
||||
session.login(UserType::So, Some(&so_pin)).unwrap();
|
||||
session.init_pin(&user_pin).unwrap();
|
||||
}
|
||||
|
||||
let session = pkcs11.open_rw_session(slot).unwrap();
|
||||
session.login(UserType::User, Some(&user_pin)).unwrap();
|
||||
|
||||
Mutex::new(session)
|
||||
});
|
||||
|
||||
fn pkcs11_generate_key_pair<KG: KeGroup>(
|
||||
mechanism: Mechanism,
|
||||
oid: ObjectIdentifier,
|
||||
) -> (ObjectHandle, PublicKey<KG>) {
|
||||
let session = SESSION.lock().unwrap();
|
||||
let (pk, remote_key) = session
|
||||
.generate_key_pair(
|
||||
&mechanism,
|
||||
&[
|
||||
Attribute::Token(false),
|
||||
Attribute::EcParams(oid.to_der().unwrap()),
|
||||
],
|
||||
&[Attribute::Token(false), Attribute::Derive(true)],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let Attribute::EcPoint(pk) = session
|
||||
.get_attributes(pk, &[AttributeType::EcPoint])
|
||||
.unwrap()
|
||||
.pop()
|
||||
.unwrap()
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
drop(session);
|
||||
|
||||
let pk = OctetString::from_der(&pk).unwrap();
|
||||
let pk = PublicKey::deserialize(pk.as_bytes()).unwrap();
|
||||
|
||||
(remote_key, pk)
|
||||
}
|
||||
|
||||
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 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 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)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "curve25519", feature = "ristretto255"))]
|
||||
impl Pkcs11DiffieHellman<Curve25519> for RemoteKey {
|
||||
fn pkcs11_diffie_hellman(
|
||||
&self,
|
||||
_: &PublicKey<Curve25519>,
|
||||
pk: &PublicKey<Curve25519>,
|
||||
) -> GenericArray<u8, <Curve25519 as KeGroup>::PkLen> {
|
||||
let shared_secret = pkcs11_derive_secret(self.0, &pk.serialize());
|
||||
|
||||
GenericArray::clone_from_slice(&shared_secret)
|
||||
}
|
||||
}
|
||||
|
||||
fn pkcs11_derive_secret(sk: ObjectHandle, pk: &[u8]) -> Vec<u8> {
|
||||
let session = SESSION.lock().unwrap();
|
||||
let shared_secret = session
|
||||
.derive_key(
|
||||
&Mechanism::Ecdh1Derive(Ecdh1DeriveParams::new(EcKdf::null(), pk)),
|
||||
sk,
|
||||
&[
|
||||
Attribute::Token(false),
|
||||
Attribute::KeyType(KeyType::GENERIC_SECRET),
|
||||
Attribute::Class(ObjectClass::SECRET_KEY),
|
||||
Attribute::Extractable(true),
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let Attribute::Value(shared_secret) = session
|
||||
.get_attributes(shared_secret, &[AttributeType::Value])
|
||||
.unwrap()
|
||||
.pop()
|
||||
.unwrap()
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
drop(session);
|
||||
|
||||
shared_secret
|
||||
}
|
||||
|
||||
fn ec_pkcs_11_derive_secret<KG>(
|
||||
server_sk: ObjectHandle,
|
||||
server_pk: &PublicKey<KG>,
|
||||
client_pk: &PublicKey<KG>,
|
||||
) -> GenericArray<u8, <KG as KeGroup>::PkLen>
|
||||
where
|
||||
KG: KeGroup<Pk = ProjectivePoint<KG>> + CurveArithmetic,
|
||||
AffinePoint<KG>: DecompressPoint<KG> + ToEncodedPoint<KG>,
|
||||
FieldBytesSize<KG>: 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(
|
||||
&GenericArray::clone_from_slice(&shared_secret_bytes),
|
||||
Choice::from(0),
|
||||
)
|
||||
.unwrap();
|
||||
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 = 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 shifted_server_pk = server_pk.to_group_type() + shared_secret_point;
|
||||
let shifted_server_pk = shifted_server_pk.to_affine();
|
||||
|
||||
let tag = u8::conditional_select(
|
||||
&(Tag::CompressedEvenY as u8),
|
||||
&(Tag::CompressedOddY as u8),
|
||||
check_point.ct_ne(&shifted_server_pk.x()),
|
||||
);
|
||||
shared_secret[0] = tag;
|
||||
|
||||
shared_secret
|
||||
}
|
||||
Reference in New Issue
Block a user