Skip to content

Commit

Permalink
Problem: mls implementation is not up to date
Browse files Browse the repository at this point in the history
mlswg/mls-protocol@d1d5f56...1d5e14d
Solution:
- updated deriviation of tree node secrets and keys
- unified KeySecret/NodeSecret to SecretValue
- other minor changes
  • Loading branch information
yihuang committed Sep 7, 2020
1 parent 112d13a commit 22d1ec6
Show file tree
Hide file tree
Showing 14 changed files with 303 additions and 327 deletions.
58 changes: 28 additions & 30 deletions chain-tx-enclave-next/mls/src/ciphersuite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ use std::fmt::Debug;

use aead::{Aead, NewAead};
use generic_array::{typenum::Unsigned, ArrayLength, GenericArray};
use secrecy::ExposeSecret;
use secrecy::{CloneableSecret, Secret};
use rand::{thread_rng, RngCore};
use secrecy::{CloneableSecret, ExposeSecret, Secret};
use sha2::digest::{FixedOutput, Update};
use static_assertions::const_assert;
use subtle::{Choice, ConstantTimeEq};
Expand Down Expand Up @@ -63,30 +63,20 @@ pub trait CipherSuite: Debug + Clone + Default + Sized {
HashValue(hasher.finalize_fixed())
}

/// spec: draft-ietf-mls-protocol.md#ratchet-tree-evolve
/// extract and expand
fn derive_path_secret(secret: &NodeSecret<Self>) -> Secret<NodeSecret<Self>> {
// extract
let hkdf = Hkdf::<Self>::new(None, secret.as_ref());
let mut okm = NodeSecret::<Self>::default();
expand_with_label::<Self>(&hkdf, "path", okm.as_mut()).expect("invariant asserted");
Secret::new(okm)
}

/// spec: draft-ietf-mls-protocol.md#key-schedule
fn extract(salt: Option<&[u8]>, ikm: &[u8]) -> GenericArray<u8, HashSize<Self>> {
let (prk, _) = Hkdf::<Self>::extract(salt, ikm);
prk
}

/// spec: draft-ietf-mls-protocol.md#key-schedule
fn extract_group_secret(salt: Option<&[u8]>, ikm: &[u8]) -> Secret<KeySecret<Self>> {
Secret::new(SecretValue(Self::extract(salt, ikm)))
fn extract_secret(salt: Option<&[u8]>, ikm: &[u8]) -> Secret<SecretValue<Self>> {
Secret::new(GenericSecret(Self::extract(salt, ikm)))
}

/// spec: draft-ietf-mls-protocol.md#key-schedule
fn derive_group_secret(prk: &KeySecret<Self>, label: &str) -> Secret<KeySecret<Self>> {
let mut okm = KeySecret::<Self>::default();
fn derive_secret(prk: &SecretValue<Self>, label: &str) -> Secret<SecretValue<Self>> {
let mut okm = SecretValue::<Self>::default();
let hkdf = Hkdf::<Self>::from_prk(prk.as_ref()).expect("size of prk == Kdf.Nk");
expand_with_label::<Self>(&hkdf, label, okm.as_mut()).expect("size of okm == Kdf.Nk");
Secret::new(okm)
Expand All @@ -95,12 +85,12 @@ pub trait CipherSuite: Debug + Clone + Default + Sized {
/// spec: draft-ietf-mls-protocol.md#welcoming-new-members
/// returns (welcome_key, welcome_nonce)
fn derive_welcome_secret(
joiner_secret: &KeySecret<Self>,
joiner_secret: &SecretValue<Self>,
) -> (
GenericArray<u8, AeadKeySize<Self>>,
GenericArray<u8, AeadNonceSize<Self>>,
) {
let prk = Self::derive_group_secret(joiner_secret, "welcome");
let prk = Self::derive_secret(joiner_secret, "welcome");
let kdf =
Hkdf::<Self>::from_prk(prk.expose_secret().as_ref()).expect("size of prk == Kdf.Nk");

Expand Down Expand Up @@ -168,35 +158,35 @@ pub type Hkdf<CS> = hkdf::Hkdf<HashImpl<CS>>;

/// Statically sized secret value
#[derive(Clone, Default)]
pub struct SecretValue<L: ArrayLength<u8>>(pub(crate) GenericArray<u8, L>);
pub struct GenericSecret<L: ArrayLength<u8>>(pub(crate) GenericArray<u8, L>);

impl<L: ArrayLength<u8>> AsMut<[u8]> for SecretValue<L> {
impl<L: ArrayLength<u8>> AsMut<[u8]> for GenericSecret<L> {
fn as_mut(&mut self) -> &mut [u8] {
&mut self.0
}
}

impl<L: ArrayLength<u8>> AsRef<[u8]> for SecretValue<L> {
impl<L: ArrayLength<u8>> AsRef<[u8]> for GenericSecret<L> {
fn as_ref(&self) -> &[u8] {
&self.0
}
}

impl<L: ArrayLength<u8>> Debug for SecretValue<L> {
impl<L: ArrayLength<u8>> Debug for GenericSecret<L> {
fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
Ok(())
}
}

impl<L: ArrayLength<u8>> Zeroize for SecretValue<L> {
impl<L: ArrayLength<u8>> Zeroize for GenericSecret<L> {
fn zeroize(&mut self) {
self.0.zeroize()
}
}

impl<L: ArrayLength<u8>> CloneableSecret for SecretValue<L> {}
impl<L: ArrayLength<u8>> CloneableSecret for GenericSecret<L> {}

impl<L: ArrayLength<u8>> Codec for SecretValue<L> {
impl<L: ArrayLength<u8>> Codec for GenericSecret<L> {
fn encode(&self, bytes: &mut Vec<u8>) {
utils::encode_vec_u8_u8(bytes, &self.0);
}
Expand All @@ -205,10 +195,18 @@ impl<L: ArrayLength<u8>> Codec for SecretValue<L> {
}
}

/// spec: draft-ietf-mls-protocol.md#ratchet-tree-evolve
pub type NodeSecret<CS> = SecretValue<SecretSize<CS>>;
impl<L: ArrayLength<u8>> GenericSecret<L> {
/// sampling a fresh random value
pub fn gen() -> Self {
let mut csprng = thread_rng();
let mut v = Self::default();
csprng.fill_bytes(v.as_mut());
v
}
}

/// spec: draft-ietf-mls-protocol.md#key-schedule
pub type KeySecret<CS> = SecretValue<HashSize<CS>>;
pub type SecretValue<CS> = GenericSecret<HashSize<CS>>;

#[derive(Clone, Debug, Default, Ord, PartialOrd, PartialEq, Eq)]
pub struct HashValue<CS: CipherSuite>(pub(crate) GenericArray<u8, HashSize<CS>>);
Expand All @@ -229,9 +227,9 @@ impl<CS: CipherSuite> Codec for HashValue<CS> {
}
}

impl<CS: CipherSuite> From<HashValue<CS>> for KeySecret<CS> {
impl<CS: CipherSuite> From<HashValue<CS>> for SecretValue<CS> {
fn from(v: HashValue<CS>) -> Self {
SecretValue(v.0)
GenericSecret(v.0)
}
}

Expand Down
15 changes: 9 additions & 6 deletions chain-tx-enclave-next/mls/src/crypto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ use hpke::{
};
use secrecy::{ExposeSecret, Secret};

use crate::ciphersuite::{AeadKeySize, AeadNonceSize, CipherSuite, Kex, NodeSecret, SecretValue};
use crate::ciphersuite::{
AeadKeySize, AeadNonceSize, CipherSuite, GenericSecret, Kex, SecretValue,
};
use crate::group::GroupInfo;
use crate::key::{HPKEPrivateKey, HPKEPublicKey};
use crate::keypackage::{KeyPackage, KeyPackageSecret};
Expand Down Expand Up @@ -47,7 +49,8 @@ pub fn open_group_secret<CS: CipherSuite>(
kp_secret: &KeyPackageSecret<CS>,
) -> Result<Option<GroupSecret<CS>>, HpkeError> {
// FIXME: errors instead of panicking
let recip_secret = kp_secret.init_private_key.kex_secret();
let init_private_key = kp_secret.init_private_key();
let recip_secret = init_private_key.kex_secret();
let encapped_key = EncappedKey::<Kex<CS>>::from_bytes(
&encrypted_group_secret.encrypted_group_secrets.kem_output,
)?;
Expand Down Expand Up @@ -98,7 +101,7 @@ pub fn encrypt_group_info<CS: CipherSuite>(

/// encrypt to public key
pub fn encrypt_path_secret<CS: CipherSuite>(
secret: &Secret<NodeSecret<CS>>,
secret: &Secret<SecretValue<CS>>,
recip_pk: &HPKEPublicKey<CS>,
aad: &[u8],
) -> Result<HPKECiphertext<CS>, HpkeError> {
Expand All @@ -123,7 +126,7 @@ pub fn decrypt_path_secret<CS: CipherSuite>(
private_key: &HPKEPrivateKey<CS>,
aad: &[u8],
ct: &HPKECiphertext<CS>,
) -> Result<Secret<NodeSecret<CS>>, HpkeError> {
) -> Result<Secret<SecretValue<CS>>, HpkeError> {
let encapped_key = EncappedKey::<Kex<CS>>::from_bytes(&ct.kem_output)?;
let mut context = hpke::setup_receiver::<CS::Aead, CS::Kdf, CS::Kem>(
&hpke::OpModeR::Base,
Expand All @@ -138,7 +141,7 @@ pub fn decrypt_with_context<CS: CipherSuite>(
context: &mut AeadCtxR<CS::Aead, CS::Kdf, CS::Kem>,
aad: &[u8],
ct: &HPKECiphertext<CS>,
) -> Result<Secret<NodeSecret<CS>>, HpkeError> {
) -> Result<Secret<SecretValue<CS>>, HpkeError> {
let split_point = ct
.ciphertext
.len()
Expand All @@ -150,5 +153,5 @@ pub fn decrypt_with_context<CS: CipherSuite>(
GenericArray::from_exact_iter(payload.iter().copied()).ok_or(HpkeError::InvalidTag)?;

context.open(&mut payload, aad, &tag)?;
Ok(Secret::new(SecretValue(payload)))
Ok(Secret::new(GenericSecret(payload)))
}
4 changes: 2 additions & 2 deletions chain-tx-enclave-next/mls/src/extras/dleq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::convert::TryInto;

use secrecy::Secret;

use crate::ciphersuite::{CipherSuite, DefaultCipherSuite, Kex, NodeSecret, PublicKey};
use crate::ciphersuite::{CipherSuite, DefaultCipherSuite, Kex, PublicKey, SecretValue};
use crate::crypto::decrypt_with_context;
use crate::key::{gen_keypair, HPKEPrivateKey, HPKEPublicKey};
use crate::message::HPKECiphertext;
Expand Down Expand Up @@ -49,7 +49,7 @@ impl NackDleqProof {
receipient_pk: &HPKEPublicKey<CS>,
ct: &HPKECiphertext<CS>,
aad: &[u8],
) -> Result<Secret<NodeSecret<CS>>, ()> {
) -> Result<Secret<SecretValue<CS>>, ()> {
let encapped_key = EncappedKey::<Kex<CS>>::from_bytes(&ct.kem_output).map_err(|_| ())?;
let shared_secret = hpke::kem::decap_external::<CS::Kem>(
&self.dh[..],
Expand Down
34 changes: 17 additions & 17 deletions chain-tx-enclave-next/mls/src/extras/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,23 +169,27 @@ impl NackMsg {
// the path secrets above(not including) the overlap node
let mut secrets = vec![];
for _ in overlap_path.iter() {
secrets.push(CS::derive_path_secret(
secrets.push(CS::derive_secret(
secrets
.last()
.unwrap_or(&overlap_path_secret)
.expose_secret(),
"path",
));
}

// verify the new path secrets match public keys
if tree
.verify_node_private_key(&overlap_path_secret, ancestor)
.verify_node_private_key(overlap_path_secret.expose_secret(), ancestor)
.is_err()
{
return Ok(NackResult::PathSecretMismatch);
}
for (secret, &parent) in secrets.iter().skip(1).zip(overlap_path) {
if tree.verify_node_private_key(secret, parent).is_err() {
if tree
.verify_node_private_key(secret.expose_secret(), parent)
.is_err()
{
return Ok(NackResult::PathSecretMismatch);
}
}
Expand All @@ -206,7 +210,7 @@ mod test {
use crate::group::test::{get_fake_keypackage, three_member_setup, MockVerifier};
use crate::group::GroupAux;
use crate::message::{ContentType, MLSPlaintextTBS};
use crate::message::{DirectPath, Proposal, ProposalId};
use crate::message::{Proposal, ProposalId, UpdatePath};
use crate::tree::TreeEvolveResult;
use assert_matches::assert_matches;
use secrecy::Secret;
Expand Down Expand Up @@ -242,19 +246,15 @@ mod test {
.expect("update tree");

// new init key
let init_private_key = sender.pending_commit.iter().next().unwrap().1;
let leaf_secret = sender.pending_commit.iter().next().unwrap().1;

// update path secrets
let TreeEvolveResult {
path_nodes,
leaf_parent_hash,
tree_secret,
} = updated_tree
.evolve(
&sender.context.get_encoding(),
sender.my_pos,
&init_private_key.marshal(),
)
.evolve(&sender.context.get_encoding(), sender.my_pos, &leaf_secret)
.expect("update tree");

let kp = updated_tree
Expand All @@ -264,7 +264,7 @@ mod test {
.put_extension(&ext::ParentHashExt(leaf_parent_hash));
kp.update_signature(&sender.pending_updates.iter().next().unwrap().1)
.expect("msg");
let mut path = DirectPath {
let mut path = UpdatePath {
leaf_key_package: kp.clone(),
nodes: path_nodes,
};
Expand All @@ -274,7 +274,7 @@ mod test {
.expect("not blank node TODO")
.public_key();
path.nodes[0].encrypted_path_secret[0] = encrypt_path_secret(
&Secret::new(SecretValue::default()),
&Secret::new(SecretValue::<CS>::default()),
&init_key,
&sender.context.get_encoding(),
)
Expand All @@ -286,12 +286,12 @@ mod test {
new_commit_content.path = Some(path);
let tree_secret = Some(tree_secret);

let (confirmation, _updated_group_context, _epoch_secrets) = sender
let (confirmation_tag, _updated_group_context, _epoch_secrets) = sender
.generate_commit_confirmation(&new_commit_content, &updated_tree, tree_secret.as_ref());

new_commit.content.content = ContentType::Commit {
commit: new_commit_content,
confirmation,
confirmation_tag,
};
let to_be_signed = MLSPlaintextTBS {
context: sender.context.clone(),
Expand Down Expand Up @@ -326,7 +326,7 @@ mod test {
.as_ref()
.expect("path");
let proof = NackDleqProof::get_nack_dleq_proof(
&member1_group.kp_secret.init_private_key,
&member1_group.kp_secret.init_private_key(),
&path.nodes[0].encrypted_path_secret[0].kem_output,
)
.expect("proof");
Expand Down Expand Up @@ -396,7 +396,7 @@ mod test {
.as_ref()
.expect("path");
let proof = NackDleqProof::get_nack_dleq_proof(
&member1_group.kp_secret.init_private_key,
&member1_group.kp_secret.init_private_key(),
&path.nodes[0].encrypted_path_secret[0].kem_output,
)
.expect("proof");
Expand Down Expand Up @@ -461,7 +461,7 @@ mod test {
.as_ref()
.expect("path");
let proof = NackDleqProof::get_nack_dleq_proof(
&member1_group.kp_secret.init_private_key,
&member1_group.kp_secret.init_private_key(),
&path.nodes[0].encrypted_path_secret[0].kem_output,
)
.expect("proof");
Expand Down
Loading

0 comments on commit 22d1ec6

Please sign in to comment.