use openmls_traits::{
crypto::OpenMlsCrypto, signatures::Signer, storage::StorageProvider, types::Ciphersuite,
OpenMlsProvider,
};
use tls_codec::Serialize;
use crate::{
binary_tree::LeafNodeIndex,
components::{
vc_derivation_info::{
load_vc_epoch_state_and_tree, merge_vc_derivation_info, resolve_vc_leaf_dictionary,
DerivationInfo, DerivationInfoTbe, EpochEncryptionKey, EpochId, KeyPackageInfo,
OperationSecret, VirtualClientOperationType, VirtualClientsError,
},
vc_operation_tree::OperationSecretTree,
},
credentials::CredentialWithKey,
extensions::AppDataDictionary,
key_packages::{
errors::KeyPackageNewError, KeyPackage, KeyPackageBuilder, KeyPackageBundle,
KeyPackageLeafNodeParams,
},
};
#[derive(Debug)]
pub struct VcKeyPackageBatch {
pub generation: u32,
pub key_packages: Vec<(
KeyPackageBundle,
crate::components::vc_derivation_info::KeyPackageInfo,
)>,
}
#[derive(Debug)]
pub struct VcKeyPackageBatchBuilder {
epoch_id: EpochId,
emulation_ciphersuite: Ciphersuite,
epoch_encryption_key: EpochEncryptionKey,
emulation_leaf_index: LeafNodeIndex,
generation: u32,
operation_secret: OperationSecret,
operation_tree: OperationSecretTree,
key_packages: Vec<(KeyPackageBundle, KeyPackageInfo)>,
}
impl VcKeyPackageBatchBuilder {
pub fn new(
provider: &impl OpenMlsProvider,
epoch_id: EpochId,
) -> Result<Self, KeyPackageNewError> {
Self::with_capacity(provider, epoch_id, 0)
}
pub fn with_capacity(
provider: &impl OpenMlsProvider,
epoch_id: EpochId,
capacity: usize,
) -> Result<Self, KeyPackageNewError> {
let (state, mut operation_tree) = load_vc_epoch_state_and_tree(provider, &epoch_id)?;
let (emulation_leaf_index, epoch_encryption_key, emulation_ciphersuite) =
state.into_parts();
let (generation, operation_secret) = operation_tree.next_operation_secret(
provider.crypto(),
emulation_ciphersuite,
&epoch_id,
emulation_leaf_index,
VirtualClientOperationType::KeyPackage,
b"",
)?;
Ok(Self {
epoch_id,
emulation_ciphersuite,
epoch_encryption_key,
emulation_leaf_index,
generation,
operation_secret,
operation_tree,
key_packages: Vec::with_capacity(capacity),
})
}
pub fn add_key_package(
&mut self,
builder: KeyPackageBuilder,
ciphersuite: Ciphersuite,
crypto: &impl OpenMlsCrypto,
signer: &impl Signer,
credential_with_key: CredentialWithKey,
) -> Result<&KeyPackageInfo, KeyPackageNewError> {
if ciphersuite.signature_algorithm() != signer.signature_scheme() {
return Err(KeyPackageNewError::CiphersuiteSignatureSchemeMismatch);
}
crypto
.supports(ciphersuite)
.map_err(|_| KeyPackageNewError::UnsupportedCiphersuite(ciphersuite))?;
let resolved_dictionary = resolve_vc_leaf_dictionary(
builder.leaf_node_capabilities.as_ref(),
builder.leaf_node_extensions.as_ref(),
None,
)?;
let key_package_index = self.key_packages.len() as u32;
self.key_packages.push(self.build_vc_key_package_for_index(
builder,
ciphersuite,
crypto,
signer,
credential_with_key,
&resolved_dictionary,
key_package_index,
)?);
let (_, info) = self.key_packages.last().expect("logic error: just pushed");
Ok(info)
}
pub fn finalize(
self,
provider: &impl OpenMlsProvider,
) -> Result<VcKeyPackageBatch, KeyPackageNewError> {
if self.key_packages.is_empty() {
return Err(KeyPackageNewError::EmptyBatch);
}
provider
.storage()
.write_vc_operation_tree(&self.epoch_id, &self.operation_tree)
.map_err(|e| {
log::error!("vc: persist advanced operation tree in build_vc_batch failed: {e:?}");
VirtualClientsError::StorageError
})?;
for (full_kp, info) in &self.key_packages {
provider
.storage()
.write_key_package(&info.key_package_ref, full_kp)
.map_err(|_| KeyPackageNewError::StorageError)?;
}
Ok(VcKeyPackageBatch {
generation: self.generation,
key_packages: self.key_packages,
})
}
#[expect(clippy::too_many_arguments)]
fn build_vc_key_package_for_index(
&self,
mut builder: KeyPackageBuilder,
ciphersuite: Ciphersuite,
crypto: &impl OpenMlsCrypto,
signer: &impl Signer,
credential_with_key: CredentialWithKey,
resolved_dictionary: &AppDataDictionary,
key_package_index: u32,
) -> Result<(KeyPackageBundle, KeyPackageInfo), KeyPackageNewError> {
let seed = self.operation_secret.derive_key_package_seed_secret(
crypto,
ciphersuite,
key_package_index,
)?;
let init_key_pair = seed
.derive_init_key_secret(crypto, ciphersuite)?
.generate_init_key_pair(crypto, ciphersuite)?;
let encryption_key_pair = seed
.derive_encryption_key_secret(crypto, ciphersuite)?
.generate_encryption_key_pair(crypto, ciphersuite)?;
let leaf_encryption_key = encryption_key_pair
.public_key()
.tls_serialize_detached()
.map_err(VirtualClientsError::from)?;
let tbe = DerivationInfoTbe::KeyPackage {
leaf_index: self.emulation_leaf_index,
generation: self.generation,
key_package_index,
};
let derivation_info = DerivationInfo::encrypt(
crypto,
self.emulation_ciphersuite,
&self.epoch_encryption_key,
self.epoch_id.clone(),
&leaf_encryption_key,
&tbe,
)?;
let derivation_info_bytes = derivation_info
.tls_serialize_detached()
.map_err(VirtualClientsError::from)?;
builder.ensure_last_resort();
let leaf_node_extensions = merge_vc_derivation_info(
builder.leaf_node_extensions.as_ref(),
resolved_dictionary.clone(),
derivation_info_bytes,
)
.map_err(KeyPackageNewError::LibraryError)?;
let leaf_node_params = KeyPackageLeafNodeParams {
lifetime: builder.key_package_lifetime.unwrap_or_default(),
capabilities: builder.leaf_node_capabilities.unwrap_or_default(),
extensions: leaf_node_extensions,
};
let (key_package, encryption_key_pair) = KeyPackage::new_from_vc_keys(
ciphersuite,
signer,
credential_with_key,
builder.key_package_extensions.unwrap_or_default(),
leaf_node_params,
init_key_pair.public.into(),
encryption_key_pair,
)?;
let key_package_ref = key_package.hash_ref(crypto)?;
let full_kp = KeyPackageBundle {
key_package,
private_init_key: init_key_pair.private,
private_encryption_key: encryption_key_pair.private_key().clone(),
};
Ok((
full_kp,
KeyPackageInfo {
key_package_ref,
cipher_suite: ciphersuite,
key_package_index,
},
))
}
}