SIGMA-I Key Exchange (#378)
* Move `KeGroup` to `KeyExchange::Group` - Introduce `KeyExchange::Hash`, which separates the OPRF hash from the one used in `KeyExchange`. - Remove `De/Serialize` requirement on key exchange messages and states, which forced a lot of where bounds on downstream users. - Rename `KeGroup` to `Group`. - Replace `D` generic for hash with `H`. * Use `voprf::derive_key()` directly * Implement SIGMA-I key exchange * Improve `KeyExchange` for SIGMA-I and Ed25519 * Implement EdDSA * Un-qualify some method calls * SIGMA-I: only include client identity in client mac * SIGMA-I: include server mac in client signature * Expose key exchange types in `crate` & move modules * Implement Ed25519ph * Document `ed25519` crate feature * Remove `ristretto255-voprf` crate feature * Adjust CI crate feature testing * Fix Rustdoc * Remove unnecessary generic parameters from SIGMA-I * Properly mark to-do's with TODO * Assorted fixes * SIGMA-I: include context in signature * SIGMA-I: include identifiers in signature * Merge `ServerLoginStart/FinishParameters` * Re-export more necessary types * More carefully expose types * Add ECDSA test * SIGMA-I: share context hashing * De-duplicate client static public key storage * Hide `KeyExchange` better * Use the correct hash in the root documentation * Bump `derive-where` * Format documentation examples a bit further * Add remote OPRF seed documentation * Rename `deserialize_key_pair` to `deserialize_take_key_pair` * Add more key tests * Remove `SharedSecret` trait * SIGMA-I refactor message API * Share more implementation between 3DH and SIGMA-I * Remove unnecessary zero scalar check for Curve25519 * Use correct hash in test * Add some more TODOs * Exclude `tests` folder from Cargo publishing * Enable missing dependencies * Use right crate for testing Ed25519 * Remove unnecessary `Sized` constraints * Remove unnecessary `ecdsa` crate features * Move signature de/serialization to trait methods * Nit: move import to appropriate location * Add warning to SIGMA-I
This commit is contained in:
+67
-69
@@ -7,26 +7,25 @@
|
||||
// licenses.
|
||||
|
||||
use core::convert::TryFrom;
|
||||
use core::ops::Add;
|
||||
|
||||
use derive_where::derive_where;
|
||||
use digest::Output;
|
||||
use generic_array::sequence::Concat;
|
||||
use generic_array::typenum::{Sum, Unsigned, U2, U32};
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
use generic_array::typenum::{Sum, U32};
|
||||
use generic_array::GenericArray;
|
||||
use hkdf::Hkdf;
|
||||
use hmac::{Hmac, Mac};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
use crate::ciphersuite::{CipherSuite, OprfHash};
|
||||
use crate::errors::utils::check_slice_size;
|
||||
use crate::ciphersuite::{CipherSuite, KeGroup, OprfHash};
|
||||
use crate::errors::{InternalError, ProtocolError};
|
||||
use crate::hash::OutputSize;
|
||||
use crate::key_exchange::group::KeGroup;
|
||||
use crate::keypair::{KeyPair, PrivateKey, PrivateKeySerialization, PublicKey};
|
||||
use crate::opaque::{bytestrings_from_identifiers, Identifiers};
|
||||
use crate::serialization::{Input, MacExt};
|
||||
use crate::key_exchange::group::Group;
|
||||
use crate::key_exchange::traits::SerializedIdentifiers;
|
||||
use crate::keypair::{KeyPair, PrivateKey, PublicKey};
|
||||
use crate::opaque::Identifiers;
|
||||
use crate::serialization::{GenericArrayExt, SliceExt, UpdateExt};
|
||||
|
||||
// Constant string used as salt for HKDF computation
|
||||
const STR_AUTH_KEY: [u8; 7] = *b"AuthKey";
|
||||
@@ -82,10 +81,9 @@ pub(crate) struct Envelope<CS: CipherSuite> {
|
||||
// which is technically unrelated to the envelope's encrypted and authenticated
|
||||
// contents.
|
||||
pub(crate) struct OpenedEnvelope<'a, CS: CipherSuite> {
|
||||
pub(crate) client_static_keypair: KeyPair<CS::KeGroup>,
|
||||
pub(crate) client_static_keypair: KeyPair<KeGroup<CS>>,
|
||||
pub(crate) export_key: Output<OprfHash<CS>>,
|
||||
pub(crate) id_u: Input<'a, U2, <CS::KeGroup as KeGroup>::PkLen>,
|
||||
pub(crate) id_s: Input<'a, U2, <CS::KeGroup as KeGroup>::PkLen>,
|
||||
pub(crate) identifiers: SerializedIdentifiers<'a, KeGroup<CS>>,
|
||||
}
|
||||
|
||||
pub(crate) struct OpenedInnerEnvelope<CS: CipherSuite> {
|
||||
@@ -97,23 +95,23 @@ type SealRawResult<CS: CipherSuite> = (Envelope<CS>, Output<OprfHash<CS>>);
|
||||
#[cfg(test)]
|
||||
type SealRawResult<CS: CipherSuite> = (Envelope<CS>, Output<OprfHash<CS>>, Output<OprfHash<CS>>);
|
||||
#[cfg(not(test))]
|
||||
type SealResult<CS: CipherSuite> = (Envelope<CS>, PublicKey<CS::KeGroup>, Output<OprfHash<CS>>);
|
||||
type SealResult<CS: CipherSuite> = (Envelope<CS>, PublicKey<KeGroup<CS>>, Output<OprfHash<CS>>);
|
||||
#[cfg(test)]
|
||||
type SealResult<CS: CipherSuite> = (
|
||||
Envelope<CS>,
|
||||
PublicKey<CS::KeGroup>,
|
||||
PublicKey<KeGroup<CS>>,
|
||||
Output<OprfHash<CS>>,
|
||||
Output<OprfHash<CS>>,
|
||||
);
|
||||
|
||||
pub(crate) type EnvelopeLen<CS: CipherSuite> = Sum<NonceLen, OutputSize<OprfHash<CS>>>;
|
||||
pub(crate) type EnvelopeLen<CS: CipherSuite> = Sum<OutputSize<OprfHash<CS>>, NonceLen>;
|
||||
|
||||
impl<CS: CipherSuite> Envelope<CS> {
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub(crate) fn seal<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
randomized_pwd_hasher: Hkdf<OprfHash<CS>>,
|
||||
server_s_pk: &PublicKey<CS::KeGroup>,
|
||||
server_s_pk: &PublicKey<KeGroup<CS>>,
|
||||
ids: Identifiers,
|
||||
) -> Result<SealResult<CS>, ProtocolError> {
|
||||
let mut nonce = GenericArray::default();
|
||||
@@ -125,12 +123,16 @@ impl<CS: CipherSuite> Envelope<CS> {
|
||||
);
|
||||
|
||||
let server_s_pk_bytes = server_s_pk.serialize();
|
||||
let (id_u, id_s) = bytestrings_from_identifiers::<CS::KeGroup>(
|
||||
let identifiers = SerializedIdentifiers::<KeGroup<CS>>::from_identifiers(
|
||||
ids,
|
||||
client_s_pk.serialize(),
|
||||
server_s_pk_bytes.clone(),
|
||||
)?;
|
||||
let aad = construct_aad(id_u.iter(), id_s.iter(), &server_s_pk_bytes);
|
||||
let aad = construct_aad(
|
||||
identifiers.client.iter(),
|
||||
identifiers.server.iter(),
|
||||
&server_s_pk_bytes,
|
||||
);
|
||||
|
||||
let result = Self::seal_raw(randomized_pwd_hasher, nonce, aad, mode)?;
|
||||
Ok((
|
||||
@@ -183,7 +185,7 @@ impl<CS: CipherSuite> Envelope<CS> {
|
||||
pub(crate) fn open<'a>(
|
||||
&self,
|
||||
randomized_pwd_hasher: Hkdf<OprfHash<CS>>,
|
||||
server_s_pk: PublicKey<CS::KeGroup>,
|
||||
server_s_pk: PublicKey<KeGroup<CS>>,
|
||||
optional_ids: Identifiers<'a>,
|
||||
) -> Result<OpenedEnvelope<'a, CS>, ProtocolError> {
|
||||
let client_static_keypair = match self.mode {
|
||||
@@ -196,20 +198,23 @@ impl<CS: CipherSuite> Envelope<CS> {
|
||||
};
|
||||
|
||||
let server_s_pk_bytes = server_s_pk.serialize();
|
||||
let (id_u, id_s) = bytestrings_from_identifiers::<CS::KeGroup>(
|
||||
let identifiers = SerializedIdentifiers::<KeGroup<CS>>::from_identifiers(
|
||||
optional_ids,
|
||||
client_static_keypair.public().serialize(),
|
||||
server_s_pk_bytes.clone(),
|
||||
)?;
|
||||
let aad = construct_aad(id_u.iter(), id_s.iter(), &server_s_pk_bytes);
|
||||
let aad = construct_aad(
|
||||
identifiers.client.iter(),
|
||||
identifiers.server.iter(),
|
||||
&server_s_pk_bytes,
|
||||
);
|
||||
|
||||
let opened = self.open_raw(randomized_pwd_hasher, aad)?;
|
||||
|
||||
Ok(OpenedEnvelope {
|
||||
client_static_keypair,
|
||||
export_key: opened.export_key,
|
||||
id_u,
|
||||
id_s,
|
||||
identifiers,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -249,45 +254,22 @@ impl<CS: CipherSuite> Envelope<CS> {
|
||||
}
|
||||
}
|
||||
|
||||
fn hmac_key_size() -> usize {
|
||||
OutputSize::<OprfHash<CS>>::USIZE
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn len() -> usize {
|
||||
use generic_array::typenum::Unsigned;
|
||||
|
||||
OutputSize::<OprfHash<CS>>::USIZE + NonceLen::USIZE
|
||||
}
|
||||
|
||||
pub(crate) fn serialize(&self) -> GenericArray<u8, EnvelopeLen<CS>>
|
||||
where
|
||||
// Envelope: Nonce + Hash
|
||||
NonceLen: Add<OutputSize<OprfHash<CS>>>,
|
||||
EnvelopeLen<CS>: ArrayLength<u8>,
|
||||
{
|
||||
self.nonce.concat(self.hmac.clone())
|
||||
pub(crate) fn serialize(&self) -> GenericArray<u8, EnvelopeLen<CS>> {
|
||||
self.nonce.concat_ext(&self.hmac)
|
||||
}
|
||||
|
||||
pub(crate) fn deserialize(bytes: &[u8]) -> Result<Self, ProtocolError> {
|
||||
let mode = InnerEnvelopeMode::Internal; // Better way to hard-code this?
|
||||
|
||||
if bytes.len() < NonceLen::USIZE {
|
||||
return Err(ProtocolError::SerializationError);
|
||||
}
|
||||
let nonce = GenericArray::clone_from_slice(&bytes[..NonceLen::USIZE]);
|
||||
|
||||
let remainder = match mode {
|
||||
InnerEnvelopeMode::Zero => {
|
||||
return Err(InternalError::IncompatibleEnvelopeModeError.into())
|
||||
}
|
||||
InnerEnvelopeMode::Internal => &bytes[NonceLen::USIZE..],
|
||||
};
|
||||
|
||||
let hmac_key_size = Self::hmac_key_size();
|
||||
let hmac = check_slice_size(remainder, hmac_key_size, "hmac_key_size")?;
|
||||
|
||||
pub(crate) fn deserialize_take(bytes: &mut &[u8]) -> Result<Self, ProtocolError> {
|
||||
Ok(Self {
|
||||
mode,
|
||||
nonce,
|
||||
hmac: GenericArray::clone_from_slice(hmac),
|
||||
mode: InnerEnvelopeMode::Internal,
|
||||
nonce: bytes.take_array("nonce")?,
|
||||
hmac: bytes.take_array("hmac")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -297,33 +279,28 @@ impl<CS: CipherSuite> Envelope<CS> {
|
||||
fn build_inner_envelope_internal<CS: CipherSuite>(
|
||||
randomized_pwd_hasher: Hkdf<OprfHash<CS>>,
|
||||
nonce: GenericArray<u8, NonceLen>,
|
||||
) -> Result<PublicKey<CS::KeGroup>, ProtocolError> {
|
||||
let mut keypair_seed = GenericArray::<_, <CS::KeGroup as KeGroup>::SkLen>::default();
|
||||
) -> Result<PublicKey<KeGroup<CS>>, ProtocolError> {
|
||||
let mut keypair_seed = GenericArray::<_, <KeGroup<CS> as Group>::SkLen>::default();
|
||||
randomized_pwd_hasher
|
||||
.expand(&nonce.concat(STR_PRIVATE_KEY.into()), &mut keypair_seed)
|
||||
.map_err(|_| InternalError::HkdfError)?;
|
||||
let client_static_keypair =
|
||||
PrivateKey::<CS::KeGroup>::deserialize_key_pair(&CS::KeGroup::serialize_sk(
|
||||
CS::KeGroup::derive_auth_keypair::<CS::OprfCs>(keypair_seed)?,
|
||||
))?;
|
||||
let client_s_sk = PrivateKey::new(KeGroup::<CS>::derive_scalar(keypair_seed)?);
|
||||
|
||||
Ok(client_static_keypair.public().clone())
|
||||
Ok(client_s_sk.public_key())
|
||||
}
|
||||
|
||||
fn recover_keys_internal<CS: CipherSuite>(
|
||||
randomized_pwd_hasher: Hkdf<OprfHash<CS>>,
|
||||
nonce: GenericArray<u8, NonceLen>,
|
||||
) -> Result<KeyPair<CS::KeGroup>, ProtocolError> {
|
||||
let mut keypair_seed = GenericArray::<_, <CS::KeGroup as KeGroup>::SkLen>::default();
|
||||
) -> Result<KeyPair<KeGroup<CS>>, ProtocolError> {
|
||||
let mut keypair_seed = GenericArray::<_, <KeGroup<CS> as Group>::SkLen>::default();
|
||||
randomized_pwd_hasher
|
||||
.expand(&nonce.concat(STR_PRIVATE_KEY.into()), &mut keypair_seed)
|
||||
.map_err(|_| InternalError::HkdfError)?;
|
||||
let client_static_keypair =
|
||||
PrivateKey::<CS::KeGroup>::deserialize_key_pair(&CS::KeGroup::serialize_sk(
|
||||
CS::KeGroup::derive_auth_keypair::<CS::OprfCs>(keypair_seed)?,
|
||||
))?;
|
||||
let client_s_sk = PrivateKey::new(KeGroup::<CS>::derive_scalar(keypair_seed)?);
|
||||
let client_s_pk = client_s_sk.public_key();
|
||||
|
||||
Ok(client_static_keypair)
|
||||
Ok(KeyPair::new(client_s_sk, client_s_pk))
|
||||
}
|
||||
|
||||
fn construct_aad<'a>(
|
||||
@@ -333,3 +310,24 @@ fn construct_aad<'a>(
|
||||
) -> impl Iterator<Item = &'a [u8]> {
|
||||
[server_s_pk].into_iter().chain(id_s).chain(id_u)
|
||||
}
|
||||
|
||||
//////////////////////////
|
||||
// Test Implementations //
|
||||
//===================== //
|
||||
//////////////////////////
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::serialization::AssertZeroized;
|
||||
|
||||
#[cfg(test)]
|
||||
impl<CS: CipherSuite> AssertZeroized for Envelope<CS> {
|
||||
fn assert_zeroized(&self) {
|
||||
let Self { mode, nonce, hmac } = self;
|
||||
|
||||
assert_eq!(mode, &InnerEnvelopeMode::Zero);
|
||||
|
||||
for byte in nonce.iter().chain(hmac) {
|
||||
assert_eq!(byte, &0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user