1use openmls_traits::{
4 crypto::OpenMlsCrypto, signatures::Signer, storage::StorageProvider, types::Ciphersuite,
5 OpenMlsProvider,
6};
7use tls_codec::Serialize;
8
9use crate::{
10 binary_tree::LeafNodeIndex,
11 components::{
12 vc_derivation_info::{
13 load_vc_epoch_state_and_tree, merge_vc_derivation_info,
14 require_newest_vc_derivation_epoch, resolve_vc_leaf_dictionary, DerivationInfo,
15 DerivationInfoTbe, EpochEncryptionKey, EpochId, KeyPackageInfo, OperationSecret,
16 RetainedKeyPackageMaterial, VirtualClientOperationType, VirtualClientsError,
17 },
18 vc_operation_tree::OperationSecretTree,
19 },
20 credentials::CredentialWithKey,
21 extensions::AppDataDictionary,
22 group::GroupId,
23 key_packages::{
24 errors::KeyPackageNewError, KeyPackage, KeyPackageBuilder, KeyPackageBundle,
25 KeyPackageLeafNodeParams,
26 },
27 treesync::node::leaf_node::resolve_capabilities,
28};
29
30#[derive(Debug)]
34pub struct VcKeyPackageBatch {
35 pub epoch_id: EpochId,
42 pub generation: u32,
44 pub key_packages: Vec<(
46 KeyPackageBundle,
47 crate::components::vc_derivation_info::KeyPackageInfo,
48 )>,
49}
50
51#[derive(Debug)]
57pub struct VcKeyPackageBatchBuilder {
58 epoch_id: EpochId,
59 emulation_ciphersuite: Ciphersuite,
61 epoch_encryption_key: EpochEncryptionKey,
62 emulation_leaf_index: LeafNodeIndex,
63 generation: u32,
64 operation_secret: OperationSecret,
65 operation_tree: OperationSecretTree,
69 key_packages: Vec<(KeyPackageBundle, KeyPackageInfo)>,
70}
71
72impl VcKeyPackageBatchBuilder {
73 #[openmls_traits::maybe_async]
84 pub async fn new(
85 provider: &impl OpenMlsProvider,
86 emulation_group_id: &GroupId,
87 ) -> Result<Self, KeyPackageNewError> {
88 Self::with_capacity(provider, emulation_group_id, 0).await
89 }
90
91 #[openmls_traits::maybe_async]
93 pub async fn with_capacity(
94 provider: &impl OpenMlsProvider,
95 emulation_group_id: &GroupId,
96 capacity: usize,
97 ) -> Result<Self, KeyPackageNewError> {
98 let epoch_id =
99 require_newest_vc_derivation_epoch(provider.storage(), emulation_group_id).await?;
100 Self::with_capacity_at_epoch(provider, epoch_id, capacity).await
101 }
102
103 #[openmls_traits::maybe_async]
106 pub(crate) async fn with_capacity_at_epoch(
107 provider: &impl OpenMlsProvider,
108 epoch_id: EpochId,
109 capacity: usize,
110 ) -> Result<Self, KeyPackageNewError> {
111 let (state, mut operation_tree) = load_vc_epoch_state_and_tree(provider, &epoch_id).await?;
112 let (emulation_leaf_index, epoch_encryption_key, emulation_ciphersuite) =
113 state.into_parts();
114 let (generation, operation_secret) = operation_tree.next_operation_secret(
115 provider.crypto(),
116 emulation_ciphersuite,
117 &epoch_id,
118 emulation_leaf_index,
119 VirtualClientOperationType::KeyPackage,
120 b"",
121 )?;
122 Ok(Self {
123 epoch_id,
124 emulation_ciphersuite,
125 epoch_encryption_key,
126 emulation_leaf_index,
127 generation,
128 operation_secret,
129 operation_tree,
130 key_packages: Vec::with_capacity(capacity),
131 })
132 }
133
134 pub fn add_key_package(
145 &mut self,
146 builder: KeyPackageBuilder,
147 ciphersuite: Ciphersuite,
148 crypto: &impl OpenMlsCrypto,
149 signer: &impl Signer,
150 credential_with_key: CredentialWithKey,
151 ) -> Result<&KeyPackageInfo, KeyPackageNewError> {
152 if ciphersuite.signature_algorithm() != signer.signature_scheme() {
153 return Err(KeyPackageNewError::CiphersuiteSignatureSchemeMismatch);
154 }
155
156 crypto
157 .supports(ciphersuite)
158 .map_err(|_| KeyPackageNewError::UnsupportedCiphersuite(ciphersuite))?;
159
160 let resolved_dictionary = resolve_vc_leaf_dictionary(
162 builder.leaf_node_capabilities.as_ref(),
163 builder.leaf_node_extensions.as_ref(),
164 None,
165 )?;
166
167 let key_package_index = self.key_packages.len() as u32;
168 self.key_packages.push(self.build_vc_key_package_for_index(
169 builder,
170 ciphersuite,
171 crypto,
172 signer,
173 credential_with_key,
174 &resolved_dictionary,
175 key_package_index,
176 )?);
177
178 let (_, info) = self.key_packages.last().expect("logic error: just pushed");
179 Ok(info)
180 }
181
182 #[openmls_traits::maybe_async]
197 pub async fn finalize(
198 self,
199 provider: &impl OpenMlsProvider,
200 ) -> Result<VcKeyPackageBatch, KeyPackageNewError> {
201 if self.key_packages.is_empty() {
202 return Err(KeyPackageNewError::EmptyBatch);
203 }
204
205 let mut materials = Vec::with_capacity(self.key_packages.len());
206 for (_, info) in &self.key_packages {
207 let key_package_seed_secret = self.operation_secret.derive_key_package_seed_secret(
208 provider.crypto(),
209 info.cipher_suite,
210 info.key_package_index,
211 )?;
212 let material = RetainedKeyPackageMaterial {
213 epoch_id: self.epoch_id.clone(),
214 leaf_index: self.emulation_leaf_index,
215 generation: self.generation,
216 key_package_ciphersuite: info.cipher_suite,
217 key_package_index: info.key_package_index,
218 key_package_seed_secret,
219 key_package_extensions: info.extensions.clone(),
220 };
221 materials.push((info.key_package_ref.clone(), material));
222 }
223
224 provider
229 .storage()
230 .write_retained_key_package_material_batch(
231 &self.epoch_id,
232 &self.operation_tree,
233 &materials,
234 )
235 .await
236 .map_err(|e| {
237 log::error!(
238 "vc: persist batch key package material in build_vc_batch failed: {e:?}"
239 );
240 VirtualClientsError::StorageError
241 })?;
242 for (full_kp, info) in &self.key_packages {
243 provider
244 .storage()
245 .write_key_package(&info.key_package_ref, full_kp)
246 .await
247 .map_err(|_| KeyPackageNewError::StorageError)?;
248 }
249
250 Ok(VcKeyPackageBatch {
251 epoch_id: self.epoch_id,
252 generation: self.generation,
253 key_packages: self.key_packages,
254 })
255 }
256
257 #[expect(clippy::too_many_arguments)]
259 fn build_vc_key_package_for_index(
260 &self,
261 mut builder: KeyPackageBuilder,
262 ciphersuite: Ciphersuite,
263 crypto: &impl OpenMlsCrypto,
264 signer: &impl Signer,
265 credential_with_key: CredentialWithKey,
266 resolved_dictionary: &AppDataDictionary,
267 key_package_index: u32,
268 ) -> Result<(KeyPackageBundle, KeyPackageInfo), KeyPackageNewError> {
269 let seed = self.operation_secret.derive_key_package_seed_secret(
270 crypto,
271 ciphersuite,
272 key_package_index,
273 )?;
274 let init_key_pair = seed
275 .derive_init_key_secret(crypto, ciphersuite)?
276 .generate_init_key_pair(crypto, ciphersuite)?;
277 let encryption_key_pair = seed
278 .derive_encryption_key_secret(crypto, ciphersuite)?
279 .generate_encryption_key_pair(crypto, ciphersuite)?;
280
281 let leaf_encryption_key = encryption_key_pair
285 .public_key()
286 .tls_serialize_detached()
287 .map_err(VirtualClientsError::from)?;
288 let tbe = DerivationInfoTbe::KeyPackage {
289 leaf_index: self.emulation_leaf_index,
290 generation: self.generation,
291 key_package_index,
292 };
293 let derivation_info = DerivationInfo::encrypt(
294 crypto,
295 self.emulation_ciphersuite,
296 &self.epoch_encryption_key,
297 self.epoch_id.clone(),
298 &leaf_encryption_key,
299 &tbe,
300 )?;
301 let derivation_info_bytes = derivation_info
302 .tls_serialize_detached()
303 .map_err(VirtualClientsError::from)?;
304 builder.ensure_last_resort()?;
305 let leaf_node_extensions = merge_vc_derivation_info(
306 builder.leaf_node_extensions.as_ref(),
307 resolved_dictionary.clone(),
308 derivation_info_bytes,
309 )
310 .map_err(KeyPackageNewError::LibraryError)?;
311
312 let (capabilities, capabilities_policy) = resolve_capabilities(
313 builder.leaf_node_capabilities.clone(),
314 builder.capabilities_policy,
315 );
316 let leaf_node_params = KeyPackageLeafNodeParams {
317 lifetime: builder.key_package_lifetime.unwrap_or_default(),
318 capabilities,
319 extensions: leaf_node_extensions,
320 capabilities_policy,
321 };
322 let (key_package, encryption_key_pair) = KeyPackage::new_from_vc_keys(
323 ciphersuite,
324 signer,
325 credential_with_key,
326 builder.key_package_extensions.unwrap_or_default(),
327 leaf_node_params,
328 init_key_pair.public.into(),
329 encryption_key_pair,
330 )?;
331
332 let key_package_ref = key_package.hash_ref(crypto)?;
333 let extensions = key_package.extensions().clone();
334 let full_kp = KeyPackageBundle {
335 key_package,
336 private_init_key: init_key_pair.private,
337 private_encryption_key: encryption_key_pair.private_key().clone(),
338 };
339
340 Ok((
341 full_kp,
342 KeyPackageInfo {
343 key_package_ref,
344 cipher_suite: ciphersuite,
345 key_package_index,
346 extensions,
347 },
348 ))
349 }
350}