Group revamp (#261)
* Revamp `KeGroup` trait * Update dependencies * Fix `hash_to_scalar` using `OprfGroup` instead of `KeGroup` * Relax constraints on associated types of `KeGroup` * Improve `KeGroup` implementation on `Curve` * Improve `KeyExchange` trait * Fix new Clippy 1.59 warnings
This commit is contained in:
+74
-162
@@ -9,16 +9,14 @@
|
||||
|
||||
#![allow(unsafe_code)]
|
||||
|
||||
use core::ops::Deref;
|
||||
|
||||
use derive_where::DeriveWhere;
|
||||
use generic_array::typenum::Unsigned;
|
||||
use derive_where::derive_where;
|
||||
use generic_array::{ArrayLength, GenericArray};
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use zeroize::Zeroize;
|
||||
use zeroize::ZeroizeOnDrop;
|
||||
|
||||
use crate::errors::{InternalError, ProtocolError};
|
||||
use crate::key_exchange::group::KeGroup;
|
||||
use crate::serialization::GenericArrayExt;
|
||||
|
||||
/// A Keypair trait with public-private verification
|
||||
#[cfg_attr(
|
||||
@@ -26,15 +24,14 @@ use crate::key_exchange::group::KeGroup;
|
||||
derive(serde_::Deserialize, serde_::Serialize),
|
||||
serde(
|
||||
bound(
|
||||
deserialize = "S: serde_::Deserialize<'de>",
|
||||
serialize = "S: serde_::Serialize"
|
||||
deserialize = "KG::Pk: serde_::Deserialize<'de>, S: serde_::Deserialize<'de>",
|
||||
serialize = "KG::Pk: serde_::Serialize, S: serde_::Serialize"
|
||||
),
|
||||
crate = "serde_"
|
||||
)
|
||||
)]
|
||||
#[derive(DeriveWhere)]
|
||||
#[derive_where(Clone, Zeroize(drop))]
|
||||
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; S)]
|
||||
#[derive_where(Clone)]
|
||||
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; KG::Pk, S)]
|
||||
pub struct KeyPair<KG: KeGroup, S: SecretKey<KG> = PrivateKey<KG>> {
|
||||
pk: PublicKey<KG>,
|
||||
sk: S,
|
||||
@@ -51,14 +48,6 @@ impl<KG: KeGroup, S: SecretKey<KG>> KeyPair<KG, S> {
|
||||
&self.sk
|
||||
}
|
||||
|
||||
/// Check whether a public key is valid. This is meant to be applied on
|
||||
/// material provided through the network which fits the key representation
|
||||
/// (i.e. can be mapped to a curve point), but presents some risk - e.g.
|
||||
/// small subgroup check
|
||||
pub(crate) fn check_public_key(key: PublicKey<KG>) -> Result<PublicKey<KG>, InternalError> {
|
||||
KG::from_pk_slice(GenericArray::from_slice(&key.0)).map(|_| key)
|
||||
}
|
||||
|
||||
/// 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)?)
|
||||
@@ -77,14 +66,18 @@ impl<KG: KeGroup> KeyPair<KG> {
|
||||
let sk = KG::random_sk(rng);
|
||||
let pk = KG::public_key(&sk);
|
||||
Self {
|
||||
pk: PublicKey(Key(pk.to_arr())),
|
||||
sk: PrivateKey(Key(sk)),
|
||||
pk: PublicKey(pk),
|
||||
sk: PrivateKey(sk),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl<KG: KeGroup> KeyPair<KG> {
|
||||
impl<KG: KeGroup> KeyPair<KG>
|
||||
where
|
||||
KG::Pk: std::fmt::Debug,
|
||||
KG::Sk: std::fmt::Debug,
|
||||
{
|
||||
/// Test-only strategy returning a proptest Strategy based on
|
||||
/// generate_random
|
||||
fn uniform_keypair_strategy() -> proptest::prelude::BoxedStrategy<Self> {
|
||||
@@ -104,70 +97,39 @@ impl<KG: KeGroup> KeyPair<KG> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A minimalist key type built around a \[u8; 32\]
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde_::Deserialize, serde_::Serialize),
|
||||
serde(bound = "", crate = "serde_")
|
||||
)]
|
||||
#[derive(DeriveWhere)]
|
||||
#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize(drop))]
|
||||
pub struct Key<L: ArrayLength<u8>>(GenericArray<u8, L>);
|
||||
|
||||
impl<L: ArrayLength<u8>> Deref for Key<L> {
|
||||
type Target = GenericArray<u8, L>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
// Don't make it implement SizedBytes so that it's not constructible outside of
|
||||
// this module.
|
||||
impl<L: ArrayLength<u8>> Key<L> {
|
||||
/// Convert to bytes
|
||||
pub fn to_arr(&self) -> GenericArray<u8, L> {
|
||||
self.0.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper around a Key to enforce that it's a private one.
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde_::Deserialize, serde_::Serialize),
|
||||
serde(bound = "", crate = "serde_")
|
||||
serde(
|
||||
bound(
|
||||
deserialize = "KG::Sk: serde_::Deserialize<'de>",
|
||||
serialize = "KG::Sk: serde_::Serialize"
|
||||
),
|
||||
crate = "serde_"
|
||||
)
|
||||
)]
|
||||
#[derive(DeriveWhere)]
|
||||
#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize(drop))]
|
||||
pub struct PrivateKey<KG: KeGroup>(Key<KG::SkLen>);
|
||||
#[derive_where(Clone)]
|
||||
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; KG::Sk)]
|
||||
pub struct PrivateKey<KG: KeGroup>(KG::Sk);
|
||||
|
||||
// This can't be derived because of the use of a generic parameter
|
||||
impl<KG: KeGroup> Deref for PrivateKey<KG> {
|
||||
type Target = Key<KG::SkLen>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
impl<KG: KeGroup> Drop for PrivateKey<KG> {
|
||||
fn drop(&mut self) {
|
||||
KG::zeroize_sk_on_drop(&mut self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<KG: KeGroup> ZeroizeOnDrop for PrivateKey<KG> {}
|
||||
|
||||
impl<KG: KeGroup> PrivateKey<KG> {
|
||||
/// Convert from bytes
|
||||
pub fn from_arr(key_bytes: GenericArray<u8, KG::SkLen>) -> Self {
|
||||
PrivateKey(Key(key_bytes))
|
||||
}
|
||||
|
||||
/// Convert from slice
|
||||
pub fn from_bytes(key_bytes: &[u8]) -> Result<Self, InternalError> {
|
||||
if key_bytes.len() == KG::SkLen::USIZE {
|
||||
Ok(Self::from_arr(GenericArray::from_slice(key_bytes).clone()))
|
||||
} else {
|
||||
Err(InternalError::InvalidByteSequence)
|
||||
}
|
||||
pub fn from_bytes(key_bytes: &GenericArray<u8, KG::SkLen>) -> Result<Self, InternalError> {
|
||||
KG::deserialize_sk(key_bytes).map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait specifying the requirements for a private key container
|
||||
pub trait SecretKey<KG: KeGroup>: Clone + Sized + Zeroize {
|
||||
pub trait SecretKey<KG: KeGroup>: Clone + Sized {
|
||||
/// Custom error type that can be passed down to `InternalError::Custom`
|
||||
type Error;
|
||||
/// Serialization size in bytes.
|
||||
@@ -197,20 +159,19 @@ impl<KG: KeGroup> SecretKey<KG> for PrivateKey<KG> {
|
||||
&self,
|
||||
pk: PublicKey<KG>,
|
||||
) -> Result<GenericArray<u8, KG::PkLen>, InternalError> {
|
||||
let pk = KG::from_pk_slice(&pk)?;
|
||||
Ok(pk.diffie_hellman(self))
|
||||
Ok(KG::diffie_hellman(&pk.0, &self.0))
|
||||
}
|
||||
|
||||
fn public_key(&self) -> Result<PublicKey<KG>, InternalError> {
|
||||
Ok(PublicKey(Key(KG::public_key(&self.0).to_arr())))
|
||||
Ok(PublicKey(KG::public_key(&self.0)))
|
||||
}
|
||||
|
||||
fn serialize(&self) -> GenericArray<u8, Self::Len> {
|
||||
self.to_arr()
|
||||
KG::serialize_sk(&self.0)
|
||||
}
|
||||
|
||||
fn deserialize(input: &[u8]) -> Result<Self, InternalError> {
|
||||
PrivateKey::from_bytes(input).map_err(InternalError::from)
|
||||
GenericArray::try_from_slice(input).and_then(Self::from_bytes)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,93 +179,54 @@ impl<KG: KeGroup> SecretKey<KG> for PrivateKey<KG> {
|
||||
#[cfg_attr(
|
||||
feature = "serde",
|
||||
derive(serde_::Deserialize, serde_::Serialize),
|
||||
serde(bound = "", crate = "serde_")
|
||||
serde(
|
||||
bound(
|
||||
deserialize = "KG::Pk: serde_::Deserialize<'de>",
|
||||
serialize = "KG::Pk: serde_::Serialize"
|
||||
),
|
||||
crate = "serde_"
|
||||
)
|
||||
)]
|
||||
#[derive(DeriveWhere)]
|
||||
#[derive_where(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize(drop))]
|
||||
pub struct PublicKey<KG: KeGroup>(Key<KG::PkLen>);
|
||||
|
||||
impl<KG: KeGroup> Deref for PublicKey<KG> {
|
||||
type Target = Key<KG::PkLen>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
#[derive_where(Clone)]
|
||||
#[derive_where(Debug, Eq, Hash, Ord, PartialEq, PartialOrd; KG::Pk)]
|
||||
pub struct PublicKey<KG: KeGroup>(KG::Pk);
|
||||
|
||||
impl<KG: KeGroup> PublicKey<KG> {
|
||||
/// Convert from bytes
|
||||
pub fn from_arr(key_bytes: GenericArray<u8, KG::PkLen>) -> Self {
|
||||
Self(Key(key_bytes))
|
||||
pub fn from_bytes(key_bytes: &GenericArray<u8, KG::PkLen>) -> Result<Self, InternalError> {
|
||||
KG::deserialize_pk(key_bytes).map(Self)
|
||||
}
|
||||
|
||||
/// Convert to bytes
|
||||
pub fn to_bytes(&self) -> GenericArray<u8, KG::PkLen> {
|
||||
KG::serialize_pk(&self.0)
|
||||
}
|
||||
|
||||
/// Convert from slice
|
||||
pub fn from_bytes(key_bytes: &[u8]) -> Result<Self, InternalError> {
|
||||
if key_bytes.len() == KG::PkLen::USIZE {
|
||||
Ok(Self::from_arr(GenericArray::from_slice(key_bytes).clone()))
|
||||
} else {
|
||||
Err(InternalError::InvalidByteSequence)
|
||||
}
|
||||
pub fn deserialize(input: &[u8]) -> Result<Self, InternalError> {
|
||||
GenericArray::try_from_slice(input).and_then(Self::from_bytes)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use core::slice::from_raw_parts;
|
||||
use std::vec;
|
||||
|
||||
use generic_array::typenum::Unsigned;
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use super::*;
|
||||
use crate::errors::*;
|
||||
use crate::util;
|
||||
|
||||
#[test]
|
||||
fn test_zeroize_key() -> Result<(), ProtocolError> {
|
||||
fn inner<G: KeGroup>() -> Result<(), ProtocolError> {
|
||||
let key_len = G::PkLen::USIZE;
|
||||
let mut key = Key::<G::PkLen>(GenericArray::clone_from_slice(&vec![1u8; key_len]));
|
||||
let ptr = key.as_ptr();
|
||||
|
||||
Zeroize::zeroize(&mut key);
|
||||
|
||||
let bytes = unsafe { from_raw_parts(ptr, key_len) };
|
||||
assert!(bytes.iter().all(|&x| x == 0));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "ristretto255")]
|
||||
inner::<curve25519_dalek::ristretto::RistrettoPoint>()?;
|
||||
#[cfg(feature = "p256")]
|
||||
inner::<p256_::PublicKey>()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zeroize_keypair() {
|
||||
fn test_zeroize_key() {
|
||||
fn inner<G: KeGroup>() {
|
||||
let mut rng = OsRng;
|
||||
let mut keypair = KeyPair::<G>::generate_random(&mut rng);
|
||||
let pk_ptr = keypair.pk.as_ptr();
|
||||
let sk_ptr = keypair.sk.as_ptr();
|
||||
let pk_len = G::PkLen::USIZE;
|
||||
let sk_len = G::SkLen::USIZE;
|
||||
|
||||
Zeroize::zeroize(&mut keypair);
|
||||
|
||||
let pk_bytes = unsafe { from_raw_parts(pk_ptr, pk_len) };
|
||||
let sk_bytes = unsafe { from_raw_parts(sk_ptr, sk_len) };
|
||||
|
||||
assert!(pk_bytes.iter().all(|&x| x == 0));
|
||||
assert!(sk_bytes.iter().all(|&x| x == 0));
|
||||
let mut key = PrivateKey::<G>(G::random_sk(&mut rng));
|
||||
util::test_zeroize_on_drop(&mut key);
|
||||
}
|
||||
|
||||
#[cfg(feature = "ristretto255")]
|
||||
inner::<curve25519_dalek::ristretto::RistrettoPoint>();
|
||||
#[cfg(feature = "p256")]
|
||||
inner::<p256_::PublicKey>();
|
||||
inner::<crate::Ristretto255>();
|
||||
inner::<::p256::NistP256>();
|
||||
}
|
||||
|
||||
macro_rules! test {
|
||||
@@ -317,12 +239,6 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn check(kp in KeyPair::<$point>::uniform_keypair_strategy()) {
|
||||
let pk = kp.public();
|
||||
prop_assert!(KeyPair::<$point>::check_public_key(pk.clone()).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pub_from_priv(kp in KeyPair::<$point>::uniform_keypair_strategy()) {
|
||||
let pk = kp.public();
|
||||
@@ -342,10 +258,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn private_key_slice(kp in KeyPair::<$point>::uniform_keypair_strategy()) {
|
||||
let sk_bytes = kp.private().to_vec();
|
||||
let sk_bytes = kp.private().serialize().to_vec();
|
||||
|
||||
let kp2 = KeyPair::<$point>::from_private_key_slice(&sk_bytes)?;
|
||||
let kp2_private_bytes = kp2.private().to_vec();
|
||||
let kp2_private_bytes = kp2.private().serialize().to_vec();
|
||||
|
||||
prop_assert_eq!(sk_bytes, kp2_private_bytes);
|
||||
}
|
||||
@@ -355,16 +271,11 @@ mod tests {
|
||||
}
|
||||
|
||||
#[cfg(feature = "ristretto255")]
|
||||
test!(ristretto, curve25519_dalek::ristretto::RistrettoPoint);
|
||||
#[cfg(feature = "p256")]
|
||||
test!(p256, p256_::PublicKey);
|
||||
test!(ristretto, crate::Ristretto255);
|
||||
test!(p256, ::p256::NistP256);
|
||||
|
||||
#[test]
|
||||
fn remote_key() {
|
||||
#[cfg(feature = "ristretto255")]
|
||||
use curve25519_dalek::ristretto::RistrettoPoint as KeCurve;
|
||||
#[cfg(not(feature = "ristretto255"))]
|
||||
use p256_::PublicKey as KeCurve;
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
use crate::{
|
||||
@@ -379,19 +290,20 @@ mod tests {
|
||||
|
||||
impl CipherSuite for Default {
|
||||
#[cfg(feature = "ristretto255")]
|
||||
type OprfGroup = KeCurve;
|
||||
type OprfGroup = crate::Ristretto255;
|
||||
#[cfg(not(feature = "ristretto255"))]
|
||||
type OprfGroup = p256_::ProjectivePoint;
|
||||
type KeGroup = KeCurve;
|
||||
type KeyExchange = crate::key_exchange::tripledh::TripleDH;
|
||||
type OprfGroup = ::p256::NistP256;
|
||||
#[cfg(feature = "ristretto255")]
|
||||
type Hash = sha2::Sha512;
|
||||
type KeGroup = crate::Ristretto255;
|
||||
#[cfg(not(feature = "ristretto255"))]
|
||||
type Hash = sha2::Sha256;
|
||||
type KeGroup = ::p256::NistP256;
|
||||
type KeyExchange = crate::key_exchange::tripledh::TripleDH;
|
||||
type SlowHash = crate::slow_hash::NoOpHash;
|
||||
}
|
||||
|
||||
#[derive(Clone, Zeroize)]
|
||||
type KeCurve = <Default as CipherSuite>::KeGroup;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct RemoteKey(PrivateKey<KeCurve>);
|
||||
|
||||
impl SecretKey<KeCurve> for RemoteKey {
|
||||
@@ -422,7 +334,7 @@ mod tests {
|
||||
const PASSWORD: &str = "password";
|
||||
|
||||
let sk = KeCurve::random_sk(&mut OsRng);
|
||||
let sk = RemoteKey(PrivateKey(Key(sk)));
|
||||
let sk = RemoteKey(PrivateKey(sk));
|
||||
let keypair = KeyPair::from_private_key(sk).unwrap();
|
||||
|
||||
let server_setup = ServerSetup::<Default, RemoteKey>::new_with_key(&mut OsRng, keypair);
|
||||
|
||||
Reference in New Issue
Block a user