use openmls_traits::{
crypto::OpenMlsCrypto, random::OpenMlsRand, signatures::Signer, types::Ciphersuite,
};
use serde::{Deserialize, Serialize};
use thiserror::Error;
use tls_codec::{
Serialize as TlsSerializeTrait, TlsDeserialize, TlsDeserializeBytes, TlsSerialize, TlsSize,
VLBytes,
};
use super::encryption_keys::{EncryptionKey, EncryptionKeyPair};
use crate::{
binary_tree::array_representation::LeafNodeIndex,
ciphersuite::{
signable::{Signable, SignedStruct, Verifiable, VerifiedStruct},
Signature, SignaturePublicKey,
},
credentials::{Credential, CredentialType, CredentialWithKey},
error::LibraryError,
extensions::{ExtensionType, Extensions},
group::GroupId,
key_packages::{KeyPackage, Lifetime},
prelude::KeyPackageBundle,
storage::OpenMlsProvider,
};
use crate::treesync::errors::LeafNodeValidationError;
mod capabilities;
mod codec;
pub use capabilities::*;
pub(crate) struct NewLeafNodeParams {
pub(crate) ciphersuite: Ciphersuite,
pub(crate) credential_with_key: CredentialWithKey,
pub(crate) leaf_node_source: LeafNodeSource,
pub(crate) capabilities: Capabilities,
pub(crate) extensions: Extensions,
pub(crate) tree_info_tbs: TreeInfoTbs,
}
#[derive(Debug, PartialEq, Clone)]
pub(crate) struct UpdateLeafNodeParams {
pub(crate) credential_with_key: CredentialWithKey,
pub(crate) capabilities: Capabilities,
pub(crate) extensions: Extensions,
}
impl UpdateLeafNodeParams {
#[cfg(test)]
pub(crate) fn derive(leaf_node: &LeafNode) -> Self {
Self {
credential_with_key: CredentialWithKey {
credential: leaf_node.payload.credential.clone(),
signature_key: leaf_node.payload.signature_key.clone(),
},
capabilities: leaf_node.payload.capabilities.clone(),
extensions: leaf_node.payload.extensions.clone(),
}
}
}
#[derive(Debug, PartialEq, Clone, Default)]
pub struct LeafNodeParameters {
credential_with_key: Option<CredentialWithKey>,
capabilities: Option<Capabilities>,
extensions: Option<Extensions>,
}
impl LeafNodeParameters {
pub fn builder() -> LeafNodeParametersBuilder {
LeafNodeParametersBuilder::default()
}
pub fn credential_with_key(&self) -> Option<&CredentialWithKey> {
self.credential_with_key.as_ref()
}
pub fn capabilities(&self) -> Option<&Capabilities> {
self.capabilities.as_ref()
}
pub fn extensions(&self) -> Option<&Extensions> {
self.extensions.as_ref()
}
pub(crate) fn is_empty(&self) -> bool {
self.credential_with_key.is_none()
&& self.capabilities.is_none()
&& self.extensions.is_none()
}
}
#[derive(Debug, Default)]
pub struct LeafNodeParametersBuilder {
credential_with_key: Option<CredentialWithKey>,
capabilities: Option<Capabilities>,
extensions: Option<Extensions>,
}
impl LeafNodeParametersBuilder {
pub fn with_credential_with_key(mut self, credential_with_key: CredentialWithKey) -> Self {
self.credential_with_key = Some(credential_with_key);
self
}
pub fn with_capabilities(mut self, capabilities: Capabilities) -> Self {
self.capabilities = Some(capabilities);
self
}
pub fn with_extensions(mut self, extensions: Extensions) -> Self {
self.extensions = Some(extensions);
self
}
pub fn build(self) -> LeafNodeParameters {
LeafNodeParameters {
credential_with_key: self.credential_with_key,
capabilities: self.capabilities,
extensions: self.extensions,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TlsSerialize, TlsSize)]
pub struct LeafNode {
payload: LeafNodePayload,
signature: Signature,
}
impl LeafNode {
pub(crate) fn new(
provider: &impl OpenMlsProvider,
signer: &impl Signer,
new_leaf_node_params: NewLeafNodeParams,
) -> Result<(Self, EncryptionKeyPair), LibraryError> {
let NewLeafNodeParams {
ciphersuite,
credential_with_key,
leaf_node_source,
capabilities,
extensions,
tree_info_tbs,
} = new_leaf_node_params;
let encryption_key_pair =
EncryptionKeyPair::random(provider.rand(), provider.crypto(), ciphersuite)?;
let leaf_node = Self::new_with_key(
encryption_key_pair.public_key().clone(),
credential_with_key,
leaf_node_source,
capabilities,
extensions,
tree_info_tbs,
signer,
)?;
Ok((leaf_node, encryption_key_pair))
}
pub(crate) fn new_placeholder() -> Self {
let payload = LeafNodePayload {
encryption_key: EncryptionKey::from(Vec::new()),
signature_key: Vec::new().into(),
credential: Credential::new(CredentialType::Basic, Vec::new()),
capabilities: Capabilities::default(),
leaf_node_source: LeafNodeSource::Update,
extensions: Extensions::default(),
};
Self {
payload,
signature: Vec::new().into(),
}
}
fn new_with_key(
encryption_key: EncryptionKey,
credential_with_key: CredentialWithKey,
leaf_node_source: LeafNodeSource,
capabilities: Capabilities,
extensions: Extensions,
tree_info_tbs: TreeInfoTbs,
signer: &impl Signer,
) -> Result<Self, LibraryError> {
let leaf_node_tbs = LeafNodeTbs::new(
encryption_key,
credential_with_key,
capabilities,
leaf_node_source,
extensions,
tree_info_tbs,
);
leaf_node_tbs
.sign(signer)
.map_err(|_| LibraryError::custom("Signing failed"))
}
#[allow(clippy::too_many_arguments)]
pub(in crate::treesync) fn new_with_parent_hash(
rand: &impl OpenMlsRand,
crypto: &impl OpenMlsCrypto,
ciphersuite: Ciphersuite,
parent_hash: &[u8],
leaf_node_params: UpdateLeafNodeParams,
group_id: GroupId,
leaf_index: LeafNodeIndex,
signer: &impl Signer,
) -> Result<(Self, EncryptionKeyPair), LibraryError> {
let encryption_key_pair = EncryptionKeyPair::random(rand, crypto, ciphersuite)?;
let leaf_node_tbs = LeafNodeTbs::new(
encryption_key_pair.public_key().clone(),
leaf_node_params.credential_with_key,
leaf_node_params.capabilities,
LeafNodeSource::Commit(parent_hash.into()),
leaf_node_params.extensions,
TreeInfoTbs::Commit(TreePosition {
group_id,
leaf_index,
}),
);
let leaf_node = leaf_node_tbs
.sign(signer)
.map_err(|_| LibraryError::custom("Signing failed"))?;
Ok((leaf_node, encryption_key_pair))
}
#[cfg(test)]
pub(crate) fn generate_update<Provider: OpenMlsProvider>(
ciphersuite: Ciphersuite,
credential_with_key: CredentialWithKey,
capabilities: Capabilities,
extensions: Extensions,
tree_info_tbs: TreeInfoTbs,
provider: &Provider,
signer: &impl Signer,
) -> Result<Self, LeafNodeGenerationError<Provider::StorageError>> {
let new_leaf_node_params = NewLeafNodeParams {
ciphersuite,
credential_with_key,
leaf_node_source: LeafNodeSource::Update,
capabilities,
extensions,
tree_info_tbs,
};
let (leaf_node, encryption_key_pair) = Self::new(provider, signer, new_leaf_node_params)?;
encryption_key_pair
.write(provider.storage())
.map_err(LeafNodeGenerationError::StorageError)?;
Ok(leaf_node)
}
pub(crate) fn update<Provider: OpenMlsProvider>(
&mut self,
ciphersuite: Ciphersuite,
provider: &Provider,
signer: &impl Signer,
group_id: GroupId,
leaf_index: LeafNodeIndex,
leaf_node_parmeters: LeafNodeParameters,
) -> Result<EncryptionKeyPair, LeafNodeUpdateError<Provider::StorageError>> {
let tree_info = TreeInfoTbs::Update(TreePosition::new(group_id, leaf_index));
let mut leaf_node_tbs = LeafNodeTbs::from(self.clone(), tree_info);
if let Some(credential_with_key) = leaf_node_parmeters.credential_with_key {
leaf_node_tbs.payload.credential = credential_with_key.credential;
leaf_node_tbs.payload.signature_key = credential_with_key.signature_key;
}
if let Some(extensions) = leaf_node_parmeters.extensions {
leaf_node_tbs.payload.extensions = extensions;
}
if let Some(capabilities) = leaf_node_parmeters.capabilities {
leaf_node_tbs.payload.capabilities = capabilities;
}
let encryption_key_pair =
EncryptionKeyPair::random(provider.rand(), provider.crypto(), ciphersuite)?;
leaf_node_tbs.payload.encryption_key = encryption_key_pair.public_key().clone();
encryption_key_pair
.write(provider.storage())
.map_err(LeafNodeUpdateError::Storage)?;
leaf_node_tbs.payload.leaf_node_source = LeafNodeSource::Update;
let leaf_node = leaf_node_tbs.sign(signer)?;
self.payload = leaf_node.payload;
self.signature = leaf_node.signature;
Ok(encryption_key_pair)
}
pub fn encryption_key(&self) -> &EncryptionKey {
&self.payload.encryption_key
}
pub fn signature_key(&self) -> &SignaturePublicKey {
&self.payload.signature_key
}
pub fn credential(&self) -> &Credential {
&self.payload.credential
}
pub fn parent_hash(&self) -> Option<&[u8]> {
match &self.payload.leaf_node_source {
LeafNodeSource::Commit(ph) => Some(ph.as_slice()),
_ => None,
}
}
pub(crate) fn life_time(&self) -> Option<&Lifetime> {
if let LeafNodeSource::KeyPackage(life_time) = &self.payload.leaf_node_source {
Some(life_time)
} else {
None
}
}
pub fn signature(&self) -> &Signature {
&self.signature
}
pub fn capabilities(&self) -> &Capabilities {
&self.payload.capabilities
}
pub fn leaf_node_source(&self) -> &LeafNodeSource {
&self.payload.leaf_node_source
}
pub fn extensions(&self) -> &Extensions {
&self.payload.extensions
}
pub(crate) fn supports_extension(&self, extension_type: &ExtensionType) -> bool {
extension_type.is_default()
|| self
.payload
.capabilities
.extensions
.contains(extension_type)
}
pub(crate) fn check_extension_support(
&self,
extensions: &[ExtensionType],
) -> Result<(), LeafNodeValidationError> {
for required in extensions.iter() {
if !self.supports_extension(required) {
log::error!(
"Leaf node does not support required extension {:?}\n
Supported extensions: {:?}",
required,
self.payload.capabilities.extensions
);
return Err(LeafNodeValidationError::UnsupportedExtensions);
}
}
Ok(())
}
pub(crate) fn validate_locally(&self) -> Result<(), LeafNodeValidationError> {
let invalid_extension_types = self
.extensions()
.iter()
.filter(|ext| ext.extension_type().is_valid_in_leaf_node() == Some(false))
.collect::<Vec<_>>();
if !invalid_extension_types.is_empty() {
log::error!(
"Invalid extension used in leaf node: {:?}",
invalid_extension_types
);
return Err(LeafNodeValidationError::UnsupportedExtensions);
}
if !self.capabilities().contains_extensions(self.extensions()) {
log::error!(
"Leaf node does not support all extensions it uses\n
Supported extensions: {:?}\n
Used extensions: {:?}",
self.payload.capabilities.extensions,
self.extensions()
);
return Err(LeafNodeValidationError::UnsupportedExtensions);
}
if !self
.capabilities()
.contains_credential(self.credential().credential_type())
{
return Err(LeafNodeValidationError::UnsupportedCredentials);
}
Ok(())
}
}
#[derive(
Debug,
Clone,
PartialEq,
Eq,
Serialize,
Deserialize,
TlsSerialize,
TlsDeserialize,
TlsDeserializeBytes,
TlsSize,
)]
struct LeafNodePayload {
encryption_key: EncryptionKey,
signature_key: SignaturePublicKey,
credential: Credential,
capabilities: Capabilities,
leaf_node_source: LeafNodeSource,
extensions: Extensions,
}
#[derive(
Debug,
Clone,
PartialEq,
Eq,
Serialize,
Deserialize,
TlsSerialize,
TlsDeserialize,
TlsDeserializeBytes,
TlsSize,
)]
#[repr(u8)]
pub enum LeafNodeSource {
#[tls_codec(discriminant = 1)]
KeyPackage(Lifetime),
Update,
Commit(ParentHash),
}
pub type ParentHash = VLBytes;
#[derive(Debug, TlsSerialize, TlsSize)]
pub struct LeafNodeTbs {
payload: LeafNodePayload,
tree_info_tbs: TreeInfoTbs,
}
impl LeafNodeTbs {
pub(crate) fn from(leaf_node: LeafNode, tree_info_tbs: TreeInfoTbs) -> Self {
Self {
payload: leaf_node.payload,
tree_info_tbs,
}
}
pub(crate) fn new(
encryption_key: EncryptionKey,
credential_with_key: CredentialWithKey,
capabilities: Capabilities,
leaf_node_source: LeafNodeSource,
extensions: Extensions,
tree_info_tbs: TreeInfoTbs,
) -> Self {
let payload = LeafNodePayload {
encryption_key,
signature_key: credential_with_key.signature_key,
credential: credential_with_key.credential,
capabilities,
leaf_node_source,
extensions,
};
LeafNodeTbs {
payload,
tree_info_tbs,
}
}
}
#[derive(Debug)]
pub(crate) enum TreeInfoTbs {
KeyPackage,
Update(TreePosition),
Commit(TreePosition),
}
#[derive(Debug, Clone, PartialEq, Eq, TlsSerialize, TlsSize)]
pub(crate) struct TreePosition {
group_id: GroupId,
leaf_index: LeafNodeIndex,
}
impl TreePosition {
pub(crate) fn new(group_id: GroupId, leaf_index: LeafNodeIndex) -> Self {
Self {
group_id,
leaf_index,
}
}
#[cfg(feature = "test-utils")]
pub(crate) fn into_parts(self) -> (GroupId, LeafNodeIndex) {
(self.group_id, self.leaf_index)
}
}
const LEAF_NODE_SIGNATURE_LABEL: &str = "LeafNodeTBS";
#[derive(
Debug,
Clone,
PartialEq,
Eq,
Serialize,
Deserialize,
TlsSerialize,
TlsDeserialize,
TlsDeserializeBytes,
TlsSize,
)]
pub struct LeafNodeIn {
payload: LeafNodePayload,
signature: Signature,
}
impl LeafNodeIn {
pub(crate) fn into_verifiable_leaf_node(self) -> VerifiableLeafNode {
match self.payload.leaf_node_source {
LeafNodeSource::KeyPackage(_) => {
let verifiable = VerifiableKeyPackageLeafNode {
payload: self.payload,
signature: self.signature,
};
VerifiableLeafNode::KeyPackage(verifiable)
}
LeafNodeSource::Update => {
let verifiable = VerifiableUpdateLeafNode {
payload: self.payload,
signature: self.signature,
tree_position: None,
};
VerifiableLeafNode::Update(verifiable)
}
LeafNodeSource::Commit(_) => {
let verifiable = VerifiableCommitLeafNode {
payload: self.payload,
signature: self.signature,
tree_position: None,
};
VerifiableLeafNode::Commit(verifiable)
}
}
}
pub fn encryption_key(&self) -> &EncryptionKey {
&self.payload.encryption_key
}
pub fn signature_key(&self) -> &SignaturePublicKey {
&self.payload.signature_key
}
pub fn credential(&self) -> &Credential {
&self.payload.credential
}
}
impl From<LeafNode> for LeafNodeIn {
fn from(leaf_node: LeafNode) -> Self {
Self {
payload: leaf_node.payload,
signature: leaf_node.signature,
}
}
}
#[cfg(any(feature = "test-utils", test))]
impl From<LeafNodeIn> for LeafNode {
fn from(deserialized: LeafNodeIn) -> Self {
Self {
payload: deserialized.payload,
signature: deserialized.signature,
}
}
}
impl From<KeyPackage> for LeafNode {
fn from(key_package: KeyPackage) -> Self {
key_package.leaf_node().clone()
}
}
impl From<KeyPackageBundle> for LeafNode {
fn from(key_package: KeyPackageBundle) -> Self {
key_package.key_package().leaf_node().clone()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum VerifiableLeafNode {
KeyPackage(VerifiableKeyPackageLeafNode),
Update(VerifiableUpdateLeafNode),
Commit(VerifiableCommitLeafNode),
}
impl VerifiableLeafNode {
pub(crate) fn signature_key(&self) -> &SignaturePublicKey {
match self {
VerifiableLeafNode::KeyPackage(v) => v.signature_key(),
VerifiableLeafNode::Update(v) => v.signature_key(),
VerifiableLeafNode::Commit(v) => v.signature_key(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct VerifiableKeyPackageLeafNode {
payload: LeafNodePayload,
signature: Signature,
}
impl VerifiableKeyPackageLeafNode {
pub(crate) fn signature_key(&self) -> &SignaturePublicKey {
&self.payload.signature_key
}
}
impl Verifiable for VerifiableKeyPackageLeafNode {
type VerifiedStruct = LeafNode;
fn unsigned_payload(&self) -> Result<Vec<u8>, tls_codec::Error> {
self.payload.tls_serialize_detached()
}
fn signature(&self) -> &Signature {
&self.signature
}
fn label(&self) -> &str {
LEAF_NODE_SIGNATURE_LABEL
}
fn verify(
self,
crypto: &impl openmls_traits::crypto::OpenMlsCrypto,
pk: &crate::ciphersuite::OpenMlsSignaturePublicKey,
) -> Result<Self::VerifiedStruct, crate::ciphersuite::signable::SignatureError> {
self.verify_no_out(crypto, pk)?;
Ok(LeafNode {
payload: self.payload,
signature: self.signature,
})
}
}
impl VerifiedStruct for LeafNode {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct VerifiableUpdateLeafNode {
payload: LeafNodePayload,
signature: Signature,
tree_position: Option<TreePosition>,
}
impl VerifiableUpdateLeafNode {
pub(crate) fn add_tree_position(&mut self, tree_info: TreePosition) {
self.tree_position = Some(tree_info);
}
pub(crate) fn signature_key(&self) -> &SignaturePublicKey {
&self.payload.signature_key
}
}
impl Verifiable for VerifiableUpdateLeafNode {
type VerifiedStruct = LeafNode;
fn unsigned_payload(&self) -> Result<Vec<u8>, tls_codec::Error> {
let tree_info_tbs = match &self.tree_position {
Some(tree_position) => TreeInfoTbs::Commit(tree_position.clone()),
None => return Err(tls_codec::Error::InvalidInput),
};
let leaf_node_tbs = LeafNodeTbs {
payload: self.payload.clone(),
tree_info_tbs,
};
leaf_node_tbs.tls_serialize_detached()
}
fn signature(&self) -> &Signature {
&self.signature
}
fn label(&self) -> &str {
LEAF_NODE_SIGNATURE_LABEL
}
fn verify(
self,
crypto: &impl openmls_traits::crypto::OpenMlsCrypto,
pk: &crate::ciphersuite::OpenMlsSignaturePublicKey,
) -> Result<Self::VerifiedStruct, crate::ciphersuite::signable::SignatureError> {
self.verify_no_out(crypto, pk)?;
Ok(LeafNode {
payload: self.payload,
signature: self.signature,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct VerifiableCommitLeafNode {
payload: LeafNodePayload,
signature: Signature,
tree_position: Option<TreePosition>,
}
impl VerifiableCommitLeafNode {
pub(crate) fn add_tree_position(&mut self, tree_info: TreePosition) {
self.tree_position = Some(tree_info);
}
pub(crate) fn signature_key(&self) -> &SignaturePublicKey {
&self.payload.signature_key
}
}
impl Verifiable for VerifiableCommitLeafNode {
type VerifiedStruct = LeafNode;
fn unsigned_payload(&self) -> Result<Vec<u8>, tls_codec::Error> {
let tree_info_tbs = match &self.tree_position {
Some(tree_position) => TreeInfoTbs::Commit(tree_position.clone()),
None => return Err(tls_codec::Error::InvalidInput),
};
let leaf_node_tbs = LeafNodeTbs {
payload: self.payload.clone(),
tree_info_tbs,
};
leaf_node_tbs.tls_serialize_detached()
}
fn signature(&self) -> &Signature {
&self.signature
}
fn label(&self) -> &str {
LEAF_NODE_SIGNATURE_LABEL
}
fn verify(
self,
crypto: &impl openmls_traits::crypto::OpenMlsCrypto,
pk: &crate::ciphersuite::OpenMlsSignaturePublicKey,
) -> Result<Self::VerifiedStruct, crate::ciphersuite::signable::SignatureError> {
self.verify_no_out(crypto, pk)?;
Ok(LeafNode {
payload: self.payload,
signature: self.signature,
})
}
}
impl Signable for LeafNodeTbs {
type SignedOutput = LeafNode;
fn unsigned_payload(&self) -> Result<Vec<u8>, tls_codec::Error> {
self.tls_serialize_detached()
}
fn label(&self) -> &str {
LEAF_NODE_SIGNATURE_LABEL
}
}
impl SignedStruct<LeafNodeTbs> for LeafNode {
fn from_payload(tbs: LeafNodeTbs, signature: Signature) -> Self {
Self {
payload: tbs.payload,
signature,
}
}
}
#[cfg(test)]
#[derive(Error, Debug, PartialEq, Clone)]
pub enum LeafNodeGenerationError<StorageError> {
#[error(transparent)]
LibraryError(#[from] LibraryError),
#[error("Error storing leaf private key.")]
StorageError(StorageError),
}
#[derive(Error, Debug, PartialEq, Clone)]
pub enum LeafNodeUpdateError<StorageError> {
#[error(transparent)]
LibraryError(#[from] LibraryError),
#[error("Error storing leaf private key.")]
Storage(StorageError),
#[error(transparent)]
Signature(#[from] crate::ciphersuite::signable::SignatureError),
}