Adding identity element checks and ensuring non-zero scalar selection

This commit is contained in:
Kevin Lewi
2021-06-15 18:39:33 -07:00
committed by Kevin Lewi
parent 210e0e99df
commit 98f1821897
8 changed files with 148 additions and 24 deletions
+2
View File
@@ -73,6 +73,8 @@ pub enum PakeError {
InvalidLoginError, InvalidLoginError,
/// Error with serializing / deserializing protocol messages /// Error with serializing / deserializing protocol messages
SerializationError, SerializationError,
/// Identity group element was encountered during deserialization, which is invalid
IdentityGroupElementError,
} }
// This is meant to express future(ly) non-trivial ways of converting the // This is meant to express future(ly) non-trivial ways of converting the
+31 -14
View File
@@ -12,6 +12,7 @@ use curve25519_dalek::{
constants::RISTRETTO_BASEPOINT_POINT, constants::RISTRETTO_BASEPOINT_POINT,
ristretto::{CompressedRistretto, RistrettoPoint}, ristretto::{CompressedRistretto, RistrettoPoint},
scalar::Scalar, scalar::Scalar,
traits::Identity,
}; };
use generic_array::{ use generic_array::{
typenum::{U32, U64}, typenum::{U32, U64},
@@ -35,7 +36,7 @@ pub trait Group: Copy + Sized + for<'a> Mul<&'a <Self as Group>::Scalar, Output
scalar_bits: &GenericArray<u8, Self::ScalarLen>, scalar_bits: &GenericArray<u8, Self::ScalarLen>,
) -> Result<Self::Scalar, InternalPakeError>; ) -> Result<Self::Scalar, InternalPakeError>;
/// picks a scalar at random /// picks a scalar at random
fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar; fn random_nonzero_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar;
/// Serializes a scalar to bytes /// Serializes a scalar to bytes
fn scalar_as_bytes(scalar: &Self::Scalar) -> &GenericArray<u8, Self::ScalarLen>; fn scalar_as_bytes(scalar: &Self::Scalar) -> &GenericArray<u8, Self::ScalarLen>;
/// The multiplicative inverse of this scalar /// The multiplicative inverse of this scalar
@@ -64,6 +65,9 @@ pub trait Group: Copy + Sized + for<'a> Mul<&'a <Self as Group>::Scalar, Output
/// Multiply the point by a scalar, represented as a slice /// Multiply the point by a scalar, represented as a slice
fn mult_by_slice(&self, scalar: &GenericArray<u8, Self::ScalarLen>) -> Self; fn mult_by_slice(&self, scalar: &GenericArray<u8, Self::ScalarLen>) -> Self;
/// Returns if the group element is equal to the identity (1)
fn is_identity(&self) -> bool;
} }
/// The implementation of such a subgroup for Ristretto /// The implementation of such a subgroup for Ristretto
@@ -77,20 +81,28 @@ impl Group for RistrettoPoint {
bits.copy_from_slice(scalar_bits); bits.copy_from_slice(scalar_bits);
Ok(Scalar::from_bytes_mod_order(bits)) Ok(Scalar::from_bytes_mod_order(bits))
} }
fn random_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar { fn random_nonzero_scalar<R: RngCore + CryptoRng>(rng: &mut R) -> Self::Scalar {
#[cfg(not(test))] loop {
{ let scalar = {
let mut scalar_bytes = [0u8; 64]; #[cfg(not(test))]
rng.fill_bytes(&mut scalar_bytes); {
Scalar::from_bytes_mod_order_wide(&scalar_bytes) let mut scalar_bytes = [0u8; 64];
} rng.fill_bytes(&mut scalar_bytes);
Scalar::from_bytes_mod_order_wide(&scalar_bytes)
}
// Tests need an exact conversion from bytes to scalar, sampling only 32 bytes from rng // Tests need an exact conversion from bytes to scalar, sampling only 32 bytes from rng
#[cfg(test)] #[cfg(test)]
{ {
let mut scalar_bytes = [0u8; 32]; let mut scalar_bytes = [0u8; 32];
rng.fill_bytes(&mut scalar_bytes); rng.fill_bytes(&mut scalar_bytes);
Scalar::from_bytes_mod_order(scalar_bytes) Scalar::from_bytes_mod_order(scalar_bytes)
}
};
if scalar != Scalar::zero() {
break scalar;
}
} }
} }
fn scalar_as_bytes(scalar: &Self::Scalar) -> &GenericArray<u8, Self::ScalarLen> { fn scalar_as_bytes(scalar: &Self::Scalar) -> &GenericArray<u8, Self::ScalarLen> {
@@ -134,4 +146,9 @@ impl Group for RistrettoPoint {
let arr: [u8; 32] = scalar.as_slice().try_into().expect("Wrong length"); let arr: [u8; 32] = scalar.as_slice().try_into().expect("Wrong length");
self * Scalar::from_bits(arr) self * Scalar::from_bits(arr)
} }
/// Returns if the group element is equal to the identity (1)
fn is_identity(&self) -> bool {
self == &Self::identity()
}
} }
+2 -1
View File
@@ -79,7 +79,7 @@ impl<G: Group> KeyPair<G> {
/// Generating a random key pair given a cryptographic rng /// Generating a random key pair given a cryptographic rng
pub(crate) fn generate_random<R: RngCore + CryptoRng>(rng: &mut R) -> Self { pub(crate) fn generate_random<R: RngCore + CryptoRng>(rng: &mut R) -> Self {
let sk = G::random_scalar(rng); let sk = G::random_nonzero_scalar(rng);
let sk_bytes = G::scalar_as_bytes(&sk); let sk_bytes = G::scalar_as_bytes(&sk);
let pk = G::base_point().mult_by_slice(sk_bytes); let pk = G::base_point().mult_by_slice(sk_bytes);
Self { Self {
@@ -174,6 +174,7 @@ impl Key {
GenericArray::clone_from_slice(&self.0[..]) GenericArray::clone_from_slice(&self.0[..])
} }
#[allow(clippy::unnecessary_wraps)]
fn from_arr(key_bytes: &GenericArray<u8, KeyLen>) -> Result<Self, TryFromSizedBytesError> { fn from_arr(key_bytes: &GenericArray<u8, KeyLen>) -> Result<Self, TryFromSizedBytesError> {
Ok(Key(key_bytes.to_vec())) Ok(Key(key_bytes.to_vec()))
} }
+30
View File
@@ -28,6 +28,14 @@ pub struct RegistrationRequest<CS: CipherSuite> {
pub(crate) alpha: CS::Group, pub(crate) alpha: CS::Group,
} }
impl<CS: CipherSuite> RegistrationRequest<CS> {
/// Only used for testing purposes
#[cfg(test)]
pub fn get_alpha_for_testing(&self) -> CS::Group {
self.alpha
}
}
// Cannot be derived because it would require for CS to be Clone. // Cannot be derived because it would require for CS to be Clone.
impl<CS: CipherSuite> Clone for RegistrationRequest<CS> { impl<CS: CipherSuite> Clone for RegistrationRequest<CS> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
@@ -49,6 +57,11 @@ impl<CS: CipherSuite> RegistrationRequest<CS> {
// correct subgroup // correct subgroup
let arr = GenericArray::from_slice(checked_slice); let arr = GenericArray::from_slice(checked_slice);
let alpha = CS::Group::from_element_slice(arr)?; let alpha = CS::Group::from_element_slice(arr)?;
// Throw an error if the identity group element is encountered
if alpha.is_identity() {
return Err(PakeError::IdentityGroupElementError.into());
}
Ok(Self { alpha }) Ok(Self { alpha })
} }
} }
@@ -91,6 +104,13 @@ impl<CS: CipherSuite> RegistrationResponse<CS> {
// correct subgroup // correct subgroup
let arr = GenericArray::from_slice(&checked_slice[..elem_len]); let arr = GenericArray::from_slice(&checked_slice[..elem_len]);
let beta = CS::Group::from_element_slice(arr)?; let beta = CS::Group::from_element_slice(arr)?;
// Throw an error if the identity group element is encountered
if beta.is_identity() {
return Err(PakeError::IdentityGroupElementError.into());
}
// Ensure that public key is valid
let server_s_pk = KeyPair::<CS::Group>::check_public_key(PublicKey::from_bytes( let server_s_pk = KeyPair::<CS::Group>::check_public_key(PublicKey::from_bytes(
&checked_slice[elem_len..], &checked_slice[elem_len..],
)?)?; )?)?;
@@ -188,6 +208,11 @@ impl<CS: CipherSuite> CredentialRequest<CS> {
let arr = GenericArray::from_slice(&checked_slice[..elem_len]); let arr = GenericArray::from_slice(&checked_slice[..elem_len]);
let alpha = CS::Group::from_element_slice(arr)?; let alpha = CS::Group::from_element_slice(arr)?;
// Throw an error if the identity group element is encountered
if alpha.is_identity() {
return Err(PakeError::IdentityGroupElementError.into());
}
let ke1_message = let ke1_message =
<CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE1Message::from_bytes::<CS>( <CS::KeyExchange as KeyExchange<CS::Hash, CS::Group>>::KE1Message::from_bytes::<CS>(
&checked_slice[elem_len..], &checked_slice[elem_len..],
@@ -258,6 +283,11 @@ impl<CS: CipherSuite> CredentialResponse<CS> {
let arr = GenericArray::from_slice(beta_bytes); let arr = GenericArray::from_slice(beta_bytes);
let beta = CS::Group::from_element_slice(arr)?; let beta = CS::Group::from_element_slice(arr)?;
// Throw an error if the identity group element is encountered
if beta.is_identity() {
return Err(PakeError::IdentityGroupElementError.into());
}
let unchecked_server_s_pk = let unchecked_server_s_pk =
PublicKey::from_bytes(&checked_slice[elem_len..elem_len + key_len])?; PublicKey::from_bytes(&checked_slice[elem_len..elem_len + key_len])?;
let server_s_pk = KeyPair::<CS::Group>::check_public_key(unchecked_server_s_pk)?; let server_s_pk = KeyPair::<CS::Group>::check_public_key(unchecked_server_s_pk)?;
+1 -1
View File
@@ -383,7 +383,7 @@ impl<CS: CipherSuite> ServerRegistration<CS> {
server_s_pk: &PublicKey, server_s_pk: &PublicKey,
) -> Result<ServerRegistrationStartResult<CS>, ProtocolError> { ) -> Result<ServerRegistrationStartResult<CS>, ProtocolError> {
// RFC: generate oprf_key (salt) and v_u = g^oprf_key // RFC: generate oprf_key (salt) and v_u = g^oprf_key
let oprf_key = CS::Group::random_scalar(rng); let oprf_key = CS::Group::random_nonzero_scalar(rng);
// Compute beta = alpha^oprf_key // Compute beta = alpha^oprf_key
let beta = oprf::evaluate::<CS::Group>(message.alpha, &oprf_key); let beta = oprf::evaluate::<CS::Group>(message.alpha, &oprf_key);
+2 -1
View File
@@ -30,7 +30,8 @@ pub(crate) fn blind<R: RngCore + CryptoRng, G: GroupWithMapToCurve, H: Hash>(
input: &[u8], input: &[u8],
blinding_factor_rng: &mut R, blinding_factor_rng: &mut R,
) -> Result<(Token<G>, G), InternalPakeError> { ) -> Result<(Token<G>, G), InternalPakeError> {
let blind = G::random_scalar(blinding_factor_rng); // Choose a random scalar that must be non-zero
let blind = G::random_nonzero_scalar(blinding_factor_rng);
let dst = [STR_VOPRF, &G::get_context_string(MODE_BASE)].concat(); let dst = [STR_VOPRF, &G::get_context_string(MODE_BASE)].concat();
let mapped_point = G::map_to_curve::<H>(input, &dst)?; let mapped_point = G::map_to_curve::<H>(input, &dst)?;
let blind_token = mapped_point * &blind; let blind_token = mapped_point * &blind;
+58 -5
View File
@@ -6,6 +6,7 @@
use crate::{ use crate::{
ciphersuite::CipherSuite, ciphersuite::CipherSuite,
envelope::{Envelope, InnerEnvelopeMode}, envelope::{Envelope, InnerEnvelopeMode},
errors::*,
group::Group, group::Group,
key_exchange::{ key_exchange::{
traits::{FromBytes, KeyExchange, ToBytes}, traits::{FromBytes, KeyExchange, ToBytes},
@@ -16,7 +17,7 @@ use crate::{
*, *,
}; };
use curve25519_dalek::ristretto::RistrettoPoint; use curve25519_dalek::{ristretto::RistrettoPoint, traits::Identity};
use generic_array::typenum::Unsigned; use generic_array::typenum::Unsigned;
use generic_bytes::SizedBytes; use generic_bytes::SizedBytes;
use proptest::{collection::vec, prelude::*}; use proptest::{collection::vec, prelude::*};
@@ -53,7 +54,7 @@ fn random_ristretto_point() -> RistrettoPoint {
fn client_registration_roundtrip() { fn client_registration_roundtrip() {
let pw = b"hunter2"; let pw = b"hunter2";
let mut rng = OsRng; let mut rng = OsRng;
let sc = <RistrettoPoint as Group>::random_scalar(&mut rng); let sc = <RistrettoPoint as Group>::random_nonzero_scalar(&mut rng);
// serialization order: scalar, password // serialization order: scalar, password
let bytes: Vec<u8> = [&sc.as_bytes()[..], &pw[..]].concat(); let bytes: Vec<u8> = [&sc.as_bytes()[..], &pw[..]].concat();
@@ -67,7 +68,7 @@ fn server_registration_roundtrip() {
// If we don't have envelope and client_pk, the server registration just // If we don't have envelope and client_pk, the server registration just
// contains the prf key // contains the prf key
let mut rng = OsRng; let mut rng = OsRng;
let oprf_key = <RistrettoPoint as Group>::random_scalar(&mut rng); let oprf_key = <RistrettoPoint as Group>::random_nonzero_scalar(&mut rng);
let mut oprf_bytes: Vec<u8> = vec![]; let mut oprf_bytes: Vec<u8> = vec![];
oprf_bytes.extend_from_slice(oprf_key.as_bytes()); oprf_bytes.extend_from_slice(oprf_key.as_bytes());
let reg = ServerRegistration::<Default>::deserialize(&oprf_bytes[..]).unwrap(); let reg = ServerRegistration::<Default>::deserialize(&oprf_bytes[..]).unwrap();
@@ -106,6 +107,17 @@ fn registration_request_roundtrip() {
let r1 = RegistrationRequest::<Default>::deserialize(input.as_slice()).unwrap(); let r1 = RegistrationRequest::<Default>::deserialize(input.as_slice()).unwrap();
let r1_bytes = r1.serialize(); let r1_bytes = r1.serialize();
assert_eq!(input, r1_bytes); assert_eq!(input, r1_bytes);
// Assert that identity group element is rejected
let identity = RistrettoPoint::identity();
let identity_bytes = identity.to_arr().to_vec();
assert!(
match RegistrationRequest::<Default>::deserialize(identity_bytes.as_slice()) {
Err(ProtocolError::VerificationError(PakeError::IdentityGroupElementError)) => true,
_ => false,
}
);
} }
#[test] #[test]
@@ -123,6 +135,17 @@ fn registration_response_roundtrip() {
let r2 = RegistrationResponse::<Default>::deserialize(input.as_slice()).unwrap(); let r2 = RegistrationResponse::<Default>::deserialize(input.as_slice()).unwrap();
let r2_bytes = r2.serialize(); let r2_bytes = r2.serialize();
assert_eq!(input, r2_bytes); assert_eq!(input, r2_bytes);
// Assert that identity group element is rejected
let identity = RistrettoPoint::identity();
let identity_bytes = identity.to_arr().to_vec();
assert!(match RegistrationResponse::<Default>::deserialize(
&[identity_bytes, pubkey_bytes.to_vec()].concat()
) {
Err(ProtocolError::VerificationError(PakeError::IdentityGroupElementError)) => true,
_ => false,
});
} }
#[test] #[test]
@@ -183,6 +206,17 @@ fn credential_request_roundtrip() {
let l1 = CredentialRequest::<Default>::deserialize(input.as_slice()).unwrap(); let l1 = CredentialRequest::<Default>::deserialize(input.as_slice()).unwrap();
let l1_bytes = l1.serialize(); let l1_bytes = l1.serialize();
assert_eq!(input, l1_bytes); assert_eq!(input, l1_bytes);
// Assert that identity group element is rejected
let identity = RistrettoPoint::identity();
let identity_bytes = identity.to_arr().to_vec();
assert!(match CredentialRequest::<Default>::deserialize(
&[identity_bytes, ke1m.to_vec()].concat()
) {
Err(ProtocolError::VerificationError(PakeError::IdentityGroupElementError)) => true,
_ => false,
});
} }
#[test] #[test]
@@ -226,15 +260,34 @@ fn credential_response_roundtrip() {
] ]
.concat(); .concat();
let serialized_envelope = envelope.serialize();
let mut input = Vec::new(); let mut input = Vec::new();
input.extend_from_slice(pt_bytes.as_slice()); input.extend_from_slice(pt_bytes.as_slice());
input.extend_from_slice(&pubkey_bytes.as_slice()); input.extend_from_slice(&pubkey_bytes.as_slice());
input.extend_from_slice(&envelope.serialize()); input.extend_from_slice(&serialized_envelope);
input.extend_from_slice(&ke2m[..]); input.extend_from_slice(&ke2m[..]);
let l2 = CredentialResponse::<Default>::deserialize(&input).unwrap(); let l2 = CredentialResponse::<Default>::deserialize(&input).unwrap();
let l2_bytes = l2.serialize(); let l2_bytes = l2.serialize();
assert_eq!(input, l2_bytes); assert_eq!(input, l2_bytes);
// Assert that identity group element is rejected
let identity = RistrettoPoint::identity();
let identity_bytes = identity.to_arr().to_vec();
assert!(match CredentialRequest::<Default>::deserialize(
&[
identity_bytes,
pubkey_bytes.to_vec(),
serialized_envelope,
ke2m.to_vec()
]
.concat()
) {
Err(ProtocolError::VerificationError(PakeError::IdentityGroupElementError)) => true,
_ => false,
});
} }
#[test] #[test]
@@ -254,7 +307,7 @@ fn login_third_message_roundtrip() {
fn client_login_roundtrip() { fn client_login_roundtrip() {
let pw = b"hunter2"; let pw = b"hunter2";
let mut rng = OsRng; let mut rng = OsRng;
let sc = <RistrettoPoint as Group>::random_scalar(&mut rng); let sc = <RistrettoPoint as Group>::random_nonzero_scalar(&mut rng);
let client_e_kp = Default::generate_random_keypair(&mut rng); let client_e_kp = Default::generate_random_keypair(&mut rng);
let mut client_nonce = vec![0u8; NonceLen::to_usize()]; let mut client_nonce = vec![0u8; NonceLen::to_usize()];
+22 -2
View File
@@ -16,7 +16,7 @@ use crate::{
tests::mock_rng::CycleRng, tests::mock_rng::CycleRng,
*, *,
}; };
use curve25519_dalek::ristretto::RistrettoPoint; use curve25519_dalek::{ristretto::RistrettoPoint, traits::Identity};
use generic_array::typenum::Unsigned; use generic_array::typenum::Unsigned;
use generic_bytes::SizedBytes; use generic_bytes::SizedBytes;
use rand::{rngs::OsRng, RngCore}; use rand::{rngs::OsRng, RngCore};
@@ -285,7 +285,7 @@ fn generate_parameters<CS: CipherSuite>() -> TestVectorParameters {
let mut server_nonce = vec![0u8; NonceLen::to_usize()]; let mut server_nonce = vec![0u8; NonceLen::to_usize()];
rng.fill_bytes(&mut server_nonce); rng.fill_bytes(&mut server_nonce);
let blinding_factor = CS::Group::random_scalar(&mut rng); let blinding_factor = CS::Group::random_nonzero_scalar(&mut rng);
let blinding_factor_bytes = CS::Group::scalar_as_bytes(&blinding_factor).clone(); let blinding_factor_bytes = CS::Group::scalar_as_bytes(&blinding_factor).clone();
let info1 = b"info1"; let info1 = b"info1";
@@ -1050,3 +1050,23 @@ fn test_zeroize_server_login_finish() -> Result<(), ProtocolError> {
Ok(()) Ok(())
} }
#[test]
fn test_scalar_always_nonzero() -> Result<(), ProtocolError> {
// Start out with a bunch of zeros to force resampling of scalar
let mut client_registration_rng = CycleRng::new([vec![0u8; 128], vec![1u8; 128]].concat());
let client_registration_start_result =
ClientRegistration::<RistrettoSha5123dhNoSlowHash>::start(
&mut client_registration_rng,
STR_PASSWORD.as_bytes(),
)?;
assert_ne!(
RistrettoPoint::identity(),
client_registration_start_result
.message
.get_alpha_for_testing()
);
Ok(())
}