Add (normal) macros showing how to generate SizedBytes to/from TryFrom + to_bytes
This is useful for getting serialization of the KEXState, KEXMessage formats without too much boilerplate Add client_login, login_first_message roundtrip serialization tests
This commit is contained in:
+13
-3
@@ -6,8 +6,12 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
errors::{utils::check_slice_size, InternalPakeError, PakeError, ProtocolError},
|
errors::{utils::check_slice_size, InternalPakeError, PakeError, ProtocolError},
|
||||||
keypair::{Key, KeyPair, SizedBytes},
|
keypair::{Key, KeyPair, SizedBytes},
|
||||||
|
sized_bytes_using_constant_and_try_from,
|
||||||
|
};
|
||||||
|
use generic_array::{
|
||||||
|
typenum::{U64, U96},
|
||||||
|
GenericArray,
|
||||||
};
|
};
|
||||||
use generic_array::GenericArray;
|
|
||||||
use hkdf::Hkdf;
|
use hkdf::Hkdf;
|
||||||
use hmac::{Hmac, Mac, NewMac};
|
use hmac::{Hmac, Mac, NewMac};
|
||||||
use rand_core::{CryptoRng, RngCore};
|
use rand_core::{CryptoRng, RngCore};
|
||||||
@@ -28,19 +32,21 @@ pub(crate) const KE2_MESSAGE_LEN: usize = NONCE_LEN + 2 * KEY_LEN;
|
|||||||
|
|
||||||
static STR_3DH: &[u8] = b"3DH keys";
|
static STR_3DH: &[u8] = b"3DH keys";
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq)]
|
||||||
pub(crate) struct KE1State {
|
pub(crate) struct KE1State {
|
||||||
client_e_sk: Key,
|
client_e_sk: Key,
|
||||||
client_nonce: Vec<u8>,
|
client_nonce: Vec<u8>,
|
||||||
hashed_l1: Vec<u8>,
|
hashed_l1: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq)]
|
||||||
pub(crate) struct KE1Message {
|
pub(crate) struct KE1Message {
|
||||||
pub(crate) client_nonce: Vec<u8>,
|
pub(crate) client_nonce: Vec<u8>,
|
||||||
pub(crate) client_e_pk: Key,
|
pub(crate) client_e_pk: Key,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&[u8]> for KE1State {
|
impl TryFrom<&[u8]> for KE1State {
|
||||||
type Error = ProtocolError;
|
type Error = InternalPakeError;
|
||||||
|
|
||||||
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
|
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||||
let checked_bytes = check_slice_size(bytes, KE1_STATE_LEN, "ke1_state")?;
|
let checked_bytes = check_slice_size(bytes, KE1_STATE_LEN, "ke1_state")?;
|
||||||
@@ -65,6 +71,8 @@ impl KE1State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sized_bytes_using_constant_and_try_from!(KE1State, U96);
|
||||||
|
|
||||||
impl KE1Message {
|
impl KE1Message {
|
||||||
pub fn to_bytes(&self) -> Vec<u8> {
|
pub fn to_bytes(&self) -> Vec<u8> {
|
||||||
[&self.client_nonce[..], &self.client_e_pk.to_arr()].concat()
|
[&self.client_nonce[..], &self.client_e_pk.to_arr()].concat()
|
||||||
@@ -72,7 +80,7 @@ impl KE1Message {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&[u8]> for KE1Message {
|
impl TryFrom<&[u8]> for KE1Message {
|
||||||
type Error = ProtocolError;
|
type Error = InternalPakeError;
|
||||||
|
|
||||||
fn try_from(ke1_message_bytes: &[u8]) -> Result<Self, Self::Error> {
|
fn try_from(ke1_message_bytes: &[u8]) -> Result<Self, Self::Error> {
|
||||||
let checked_bytes =
|
let checked_bytes =
|
||||||
@@ -85,6 +93,8 @@ impl TryFrom<&[u8]> for KE1Message {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sized_bytes_using_constant_and_try_from!(KE1Message, U64);
|
||||||
|
|
||||||
pub(crate) fn generate_ke1<R: RngCore + CryptoRng, KeyFormat: KeyPair<Repr = Key>>(
|
pub(crate) fn generate_ke1<R: RngCore + CryptoRng, KeyFormat: KeyPair<Repr = Key>>(
|
||||||
l1_component: Vec<u8>,
|
l1_component: Vec<u8>,
|
||||||
rng: &mut R,
|
rng: &mut R,
|
||||||
|
|||||||
+56
-8
@@ -91,6 +91,60 @@ trait KeyPairExt: KeyPair + Debug {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
impl<KP> KeyPairExt for KP where KP: KeyPair + Debug {}
|
impl<KP> KeyPairExt for KP where KP: KeyPair + Debug {}
|
||||||
|
|
||||||
|
/// This assumes you have defined:
|
||||||
|
/// - an `impl TryFrom<&[u8b], Error = InternalPakeError>` for a non-generic `T`
|
||||||
|
/// - an `fn to_bytes(&self) -> Vec<u8>` in an `impl T` block
|
||||||
|
/// and it both of the above to produce a sensible SizedBytes implementation
|
||||||
|
///
|
||||||
|
/// Because SizedBytes has a strong notion of size, and TryFrom/to_bytes does
|
||||||
|
/// not, it's better to use the macro below rather than this one, where possible.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! sized_bytes_using_constant_and_try_from {
|
||||||
|
($sized_type: ident, $len: ident) => {
|
||||||
|
impl SizedBytes for $sized_type {
|
||||||
|
type Len = $len;
|
||||||
|
|
||||||
|
fn to_arr(&self) -> generic_array::GenericArray<u8, Self::Len> {
|
||||||
|
generic_array::GenericArray::clone_from_slice(&self.to_bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_bytes(bytes: &[u8]) -> Result<Self, InternalPakeError> {
|
||||||
|
let checked_bytes = check_slice_size(
|
||||||
|
bytes,
|
||||||
|
<Self::Len as generic_array::typenum::Unsigned>::to_usize(),
|
||||||
|
"bytes",
|
||||||
|
)?;
|
||||||
|
std::convert::TryFrom::try_from(checked_bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This assumes you have defined a SizedBytes instance for a `T`, and defines:
|
||||||
|
/// - an `impl TryFrom<&[u8b], Error = InternalPakeError>` for a non-generic `T`
|
||||||
|
/// - an `fn to_bytes(&self) -> Vec<u8>` in an `impl T` block
|
||||||
|
///
|
||||||
|
/// Because SizedBytes has a strong notion of size, and TryFrom/to_bytes does
|
||||||
|
/// not, it's better to use this macro than the one above, where possible.
|
||||||
|
macro_rules! try_from_and_to_bytes_using_sized_bytes {
|
||||||
|
($sized_type: ident) => {
|
||||||
|
impl TryFrom<&[u8]> for $sized_type {
|
||||||
|
type Error = InternalPakeError;
|
||||||
|
|
||||||
|
fn try_from(bytes: &[u8]) -> Result<Self, InternalPakeError> {
|
||||||
|
<$sized_type as SizedBytes>::from_bytes(bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
impl $sized_type {
|
||||||
|
fn to_bytes(&self) -> Vec<u8> {
|
||||||
|
self.to_arr().to_vec()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// This is a blanket implementation of SizedBytes for any instance of KeyPair
|
/// This is a blanket implementation of SizedBytes for any instance of KeyPair
|
||||||
/// with any length of keys. This encodes that we serialize the public key
|
/// with any length of keys. This encodes that we serialize the public key
|
||||||
/// first, followed by the private key in binary formats (and expect it in this
|
/// first, followed by the private key in binary formats (and expect it in this
|
||||||
@@ -133,14 +187,6 @@ impl Deref for Key {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<Vec<u8>> for Key {
|
|
||||||
type Error = InternalPakeError;
|
|
||||||
|
|
||||||
fn try_from(key_bytes: Vec<u8>) -> Result<Self, Self::Error> {
|
|
||||||
Key::from_bytes(&key_bytes[..])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SizedBytes for Key {
|
impl SizedBytes for Key {
|
||||||
type Len = U32;
|
type Len = U32;
|
||||||
|
|
||||||
@@ -155,6 +201,8 @@ impl SizedBytes for Key {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try_from_and_to_bytes_using_sized_bytes!(Key);
|
||||||
|
|
||||||
/// A representation of an X25519 keypair according to RFC7748
|
/// A representation of an X25519 keypair according to RFC7748
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub struct X25519KeyPair {
|
pub struct X25519KeyPair {
|
||||||
|
|||||||
@@ -391,7 +391,7 @@ fn test_r3() -> Result<(), PakeError> {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.finish(
|
.finish(
|
||||||
RegisterSecondMessage::try_from(¶meters.r2[..]).unwrap(),
|
RegisterSecondMessage::try_from(¶meters.r2[..]).unwrap(),
|
||||||
&Key::try_from(parameters.server_s_pk).unwrap(),
|
&Key::try_from(¶meters.server_s_pk[..]).unwrap(),
|
||||||
&mut finish_registration_rng,
|
&mut finish_registration_rng,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -456,7 +456,7 @@ fn test_l2() -> Result<(), PakeError> {
|
|||||||
let mut server_e_sk_rng = CycleRng::new(parameters.server_e_sk);
|
let mut server_e_sk_rng = CycleRng::new(parameters.server_e_sk);
|
||||||
let (l2, server_login) = ServerLogin::start::<AesgcmX255193dhNoSlowHash, _>(
|
let (l2, server_login) = ServerLogin::start::<AesgcmX255193dhNoSlowHash, _>(
|
||||||
ServerRegistration::try_from(¶meters.password_file[..]).unwrap(),
|
ServerRegistration::try_from(¶meters.password_file[..]).unwrap(),
|
||||||
&Key::try_from(parameters.server_s_sk).unwrap(),
|
&Key::try_from(¶meters.server_s_sk[..]).unwrap(),
|
||||||
LoginFirstMessage::<EdwardsPoint>::try_from(¶meters.l1[..]).unwrap(),
|
LoginFirstMessage::<EdwardsPoint>::try_from(¶meters.l1[..]).unwrap(),
|
||||||
&mut server_e_sk_rng,
|
&mut server_e_sk_rng,
|
||||||
)
|
)
|
||||||
@@ -481,7 +481,7 @@ fn test_l3() -> Result<(), PakeError> {
|
|||||||
.finish(
|
.finish(
|
||||||
LoginSecondMessage::<Aes256Gcm, EdwardsPoint>::try_from(¶meters.l2[..])
|
LoginSecondMessage::<Aes256Gcm, EdwardsPoint>::try_from(¶meters.l2[..])
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
&Key::try_from(parameters.server_s_pk)?,
|
&Key::try_from(¶meters.server_s_pk[..])?,
|
||||||
&mut client_e_sk_rng,
|
&mut client_e_sk_rng,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
ciphersuite::CipherSuite,
|
ciphersuite::CipherSuite,
|
||||||
group::Group,
|
group::Group,
|
||||||
|
key_exchange::{KE1Message, NONCE_LEN},
|
||||||
keypair::{KeyPair, SizedBytes, X25519KeyPair},
|
keypair::{KeyPair, SizedBytes, X25519KeyPair},
|
||||||
opaque::*,
|
opaque::*,
|
||||||
rkr_encryption::{RKRCipher as _, RKRCiphertext},
|
rkr_encryption::{RKRCipher as _, RKRCiphertext},
|
||||||
@@ -16,7 +17,7 @@ use curve25519_dalek::ristretto::RistrettoPoint;
|
|||||||
use chacha20poly1305::ChaCha20Poly1305;
|
use chacha20poly1305::ChaCha20Poly1305;
|
||||||
use rand_core::{OsRng, RngCore};
|
use rand_core::{OsRng, RngCore};
|
||||||
|
|
||||||
use sha2::Digest;
|
use sha2::{Digest, Sha256};
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
struct Default;
|
struct Default;
|
||||||
@@ -130,3 +131,46 @@ fn register_third_message_roundtrip() {
|
|||||||
let r3_bytes = r3.to_bytes();
|
let r3_bytes = r3.to_bytes();
|
||||||
assert_eq!(message, r3_bytes);
|
assert_eq!(message, r3_bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn client_login_roundtrip() {
|
||||||
|
let pw = b"hunter2";
|
||||||
|
let mut rng = OsRng;
|
||||||
|
let sc = <RistrettoPoint as Group>::random_scalar(&mut rng);
|
||||||
|
|
||||||
|
let client_e_kp = Default::generate_random_keypair(&mut rng).unwrap();
|
||||||
|
let mut client_nonce = [0u8; NONCE_LEN];
|
||||||
|
rng.fill_bytes(&mut client_nonce);
|
||||||
|
|
||||||
|
let l1_data = [&sc.to_bytes()[..], &client_nonce, client_e_kp.public()].concat();
|
||||||
|
let mut hasher = Sha256::new();
|
||||||
|
hasher.update(l1_data);
|
||||||
|
let hashed_l1 = hasher.finalize();
|
||||||
|
|
||||||
|
// serialization order: scalar, password, ke1_state
|
||||||
|
let bytes: Vec<u8> = [
|
||||||
|
&sc.as_bytes()[..],
|
||||||
|
&pw[..],
|
||||||
|
client_e_kp.public(),
|
||||||
|
&client_nonce,
|
||||||
|
hashed_l1.as_slice(),
|
||||||
|
]
|
||||||
|
.concat();
|
||||||
|
let reg = ClientLogin::<Default>::try_from(&bytes[..]).unwrap();
|
||||||
|
let reg_bytes = reg.to_bytes();
|
||||||
|
assert_eq!(reg_bytes, bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn login_first_message_roundtrip() {
|
||||||
|
let mut rng = OsRng;
|
||||||
|
|
||||||
|
let client_e_kp = Default::generate_random_keypair(&mut rng).unwrap();
|
||||||
|
let mut client_nonce = [0u8; NONCE_LEN];
|
||||||
|
rng.fill_bytes(&mut client_nonce);
|
||||||
|
|
||||||
|
let ke1m: Vec<u8> = [&client_nonce[..], &client_e_kp.public()].concat();
|
||||||
|
let reg = KE1Message::try_from(&ke1m[..]).unwrap();
|
||||||
|
let reg_bytes = reg.to_bytes();
|
||||||
|
assert_eq!(reg_bytes, ke1m);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user