Rework SecretKey API to facilitate async (#371)

* Rework `SecretKey` API to facilitate async

* Remove left-over constraints
This commit is contained in:
daxpedda
2025-04-22 00:08:17 -07:00
committed by GitHub
parent 6b69e93dc9
commit d324584d79
20 changed files with 952 additions and 382 deletions
+92 -43
View File
@@ -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>,