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, resolve_vc_leaf_dictionary,
14 DerivationInfo, DerivationInfoTbe, EpochEncryptionKey, EpochId, KeyPackageInfo,
15 OperationSecret, VirtualClientOperationType, VirtualClientsError,
16 },
17 vc_operation_tree::OperationSecretTree,
18 },
19 credentials::CredentialWithKey,
20 extensions::AppDataDictionary,
21 key_packages::{
22 errors::KeyPackageNewError, KeyPackage, KeyPackageBuilder, KeyPackageBundle,
23 KeyPackageLeafNodeParams,
24 },
25};
26
27#[derive(Debug)]
31pub struct VcKeyPackageBatch {
32 pub generation: u32,
34 pub key_packages: Vec<(
36 KeyPackageBundle,
37 crate::components::vc_derivation_info::KeyPackageInfo,
38 )>,
39}
40
41#[derive(Debug)]
47pub struct VcKeyPackageBatchBuilder {
48 epoch_id: EpochId,
49 emulation_ciphersuite: Ciphersuite,
51 epoch_encryption_key: EpochEncryptionKey,
52 emulation_leaf_index: LeafNodeIndex,
53 generation: u32,
54 operation_secret: OperationSecret,
55 operation_tree: OperationSecretTree,
59 key_packages: Vec<(KeyPackageBundle, KeyPackageInfo)>,
60}
61
62impl VcKeyPackageBatchBuilder {
63 pub fn new(
68 provider: &impl OpenMlsProvider,
69 epoch_id: EpochId,
70 ) -> Result<Self, KeyPackageNewError> {
71 Self::with_capacity(provider, epoch_id, 0)
72 }
73
74 pub fn with_capacity(
76 provider: &impl OpenMlsProvider,
77 epoch_id: EpochId,
78 capacity: usize,
79 ) -> Result<Self, KeyPackageNewError> {
80 let (state, mut operation_tree) = load_vc_epoch_state_and_tree(provider, &epoch_id)?;
81 let (emulation_leaf_index, epoch_encryption_key, emulation_ciphersuite) =
82 state.into_parts();
83 let (generation, operation_secret) = operation_tree.next_operation_secret(
84 provider.crypto(),
85 emulation_ciphersuite,
86 &epoch_id,
87 emulation_leaf_index,
88 VirtualClientOperationType::KeyPackage,
89 b"",
90 )?;
91 Ok(Self {
92 epoch_id,
93 emulation_ciphersuite,
94 epoch_encryption_key,
95 emulation_leaf_index,
96 generation,
97 operation_secret,
98 operation_tree,
99 key_packages: Vec::with_capacity(capacity),
100 })
101 }
102
103 pub fn add_key_package(
114 &mut self,
115 builder: KeyPackageBuilder,
116 ciphersuite: Ciphersuite,
117 crypto: &impl OpenMlsCrypto,
118 signer: &impl Signer,
119 credential_with_key: CredentialWithKey,
120 ) -> Result<&KeyPackageInfo, KeyPackageNewError> {
121 if ciphersuite.signature_algorithm() != signer.signature_scheme() {
122 return Err(KeyPackageNewError::CiphersuiteSignatureSchemeMismatch);
123 }
124
125 crypto
126 .supports(ciphersuite)
127 .map_err(|_| KeyPackageNewError::UnsupportedCiphersuite(ciphersuite))?;
128
129 let resolved_dictionary = resolve_vc_leaf_dictionary(
131 builder.leaf_node_capabilities.as_ref(),
132 builder.leaf_node_extensions.as_ref(),
133 None,
134 )?;
135
136 let key_package_index = self.key_packages.len() as u32;
137 self.key_packages.push(self.build_vc_key_package_for_index(
138 builder,
139 ciphersuite,
140 crypto,
141 signer,
142 credential_with_key,
143 &resolved_dictionary,
144 key_package_index,
145 )?);
146
147 let (_, info) = self.key_packages.last().expect("logic error: just pushed");
148 Ok(info)
149 }
150
151 pub fn finalize(
157 self,
158 provider: &impl OpenMlsProvider,
159 ) -> Result<VcKeyPackageBatch, KeyPackageNewError> {
160 if self.key_packages.is_empty() {
161 return Err(KeyPackageNewError::EmptyBatch);
162 }
163
164 provider
169 .storage()
170 .write_vc_operation_tree(&self.epoch_id, &self.operation_tree)
171 .map_err(|e| {
172 log::error!("vc: persist advanced operation tree in build_vc_batch failed: {e:?}");
173 VirtualClientsError::StorageError
174 })?;
175 for (full_kp, info) in &self.key_packages {
176 provider
177 .storage()
178 .write_key_package(&info.key_package_ref, full_kp)
179 .map_err(|_| KeyPackageNewError::StorageError)?;
180 }
181
182 Ok(VcKeyPackageBatch {
183 generation: self.generation,
184 key_packages: self.key_packages,
185 })
186 }
187
188 #[expect(clippy::too_many_arguments)]
190 fn build_vc_key_package_for_index(
191 &self,
192 mut builder: KeyPackageBuilder,
193 ciphersuite: Ciphersuite,
194 crypto: &impl OpenMlsCrypto,
195 signer: &impl Signer,
196 credential_with_key: CredentialWithKey,
197 resolved_dictionary: &AppDataDictionary,
198 key_package_index: u32,
199 ) -> Result<(KeyPackageBundle, KeyPackageInfo), KeyPackageNewError> {
200 let seed = self.operation_secret.derive_key_package_seed_secret(
204 crypto,
205 self.emulation_ciphersuite,
206 key_package_index,
207 )?;
208 let init_key_pair = seed
209 .derive_init_key_secret(crypto, ciphersuite)?
210 .generate_init_key_pair(crypto, ciphersuite)?;
211 let encryption_key_pair = seed
212 .derive_encryption_key_secret(crypto, ciphersuite)?
213 .generate_encryption_key_pair(crypto, ciphersuite)?;
214
215 let leaf_encryption_key = encryption_key_pair
219 .public_key()
220 .tls_serialize_detached()
221 .map_err(VirtualClientsError::from)?;
222 let tbe = DerivationInfoTbe::KeyPackage {
223 leaf_index: self.emulation_leaf_index,
224 generation: self.generation,
225 key_package_index,
226 };
227 let derivation_info = DerivationInfo::encrypt(
228 crypto,
229 self.emulation_ciphersuite,
230 &self.epoch_encryption_key,
231 self.epoch_id.clone(),
232 &leaf_encryption_key,
233 &tbe,
234 )?;
235 let derivation_info_bytes = derivation_info
236 .tls_serialize_detached()
237 .map_err(VirtualClientsError::from)?;
238 builder.ensure_last_resort();
239 let leaf_node_extensions = merge_vc_derivation_info(
240 builder.leaf_node_extensions.as_ref(),
241 resolved_dictionary.clone(),
242 derivation_info_bytes,
243 )
244 .map_err(KeyPackageNewError::LibraryError)?;
245
246 let leaf_node_params = KeyPackageLeafNodeParams {
247 lifetime: builder.key_package_lifetime.unwrap_or_default(),
248 capabilities: builder.leaf_node_capabilities.unwrap_or_default(),
249 extensions: leaf_node_extensions,
250 };
251 let (key_package, encryption_key_pair) = KeyPackage::new_from_vc_keys(
252 ciphersuite,
253 signer,
254 credential_with_key,
255 builder.key_package_extensions.unwrap_or_default(),
256 leaf_node_params,
257 init_key_pair.public.into(),
258 encryption_key_pair,
259 )?;
260
261 let key_package_ref = key_package.hash_ref(crypto)?;
262 let full_kp = KeyPackageBundle {
263 key_package,
264 private_init_key: init_key_pair.private,
265 private_encryption_key: encryption_key_pair.private_key().clone(),
266 };
267
268 Ok((
269 full_kp,
270 KeyPackageInfo {
271 key_package_ref,
272 key_package_index,
273 },
274 ))
275 }
276}