1use std::str::FromStr;
33
34use bitwarden_crypto::{
35 CoseKeyThumbprint, CoseKeyThumbprintExt, EncString, KeySlotIds, KeyStoreContext,
36 generate_versioned_sealable,
37 safe::{
38 DataEnvelope, DataEnvelopeNamespace, HighEntropySecret, HighEntropySecretSource,
39 KeyEncryptionKey, SealableData, SealableVersionedData, SecretProtectedKeyEnvelope,
40 SecretProtectedKeyEnvelopeNamespace, SymmetricKeyEnvelope, SymmetricKeyEnvelopeNamespace,
41 },
42};
43use bitwarden_encoding::{B64Url, FromStrVisitor};
44use bitwarden_sensitive_value::{Sensitive, SensitiveSlice};
45use rand::Rng;
46use serde::{Deserialize, Serialize};
47use subtle::{Choice, ConstantTimeEq};
48use thiserror::Error;
49use zeroize::Zeroizing;
50
51const INVITE_SECRET_LEN: usize = 32;
54
55#[derive(Debug, Error)]
57pub enum InviteKeyBundleError {
58 #[error("Decoding failed")]
60 DecodingFailed,
61 #[error("Unable to seal invite")]
63 KeySealingFailed,
64 #[error("Unable to unseal invite")]
66 KeyUnsealingFailed,
67 #[error("Missing Key for Id: {0}")]
69 MissingKeyId(String),
70 #[error("Invalid organization private key")]
73 InvalidPrivateKey,
74 #[error("Confirmation is not enabled on this invite")]
76 ConfirmationNotEnabled,
77}
78
79#[cfg(feature = "wasm")]
80#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)]
81const TS_INVITE_SECRET: &'static str = r#"
82export type InviteSecret = Tagged<string, "InviteSecret">;
83"#;
84
85#[derive(Clone)]
89pub struct InviteSecret(Zeroizing<[u8; INVITE_SECRET_LEN]>);
90
91impl InviteSecret {
92 fn make() -> Self {
94 let mut bytes = Zeroizing::new([0u8; INVITE_SECRET_LEN]);
95 bitwarden_random::rng().fill_bytes(bytes.as_mut_slice());
96 InviteSecret(bytes)
97 }
98}
99
100impl ConstantTimeEq for InviteSecret {
101 fn ct_eq(&self, other: &InviteSecret) -> Choice {
102 self.0.as_slice().ct_eq(other.0.as_slice())
103 }
104}
105
106impl PartialEq for InviteSecret {
107 fn eq(&self, other: &Self) -> bool {
108 self.ct_eq(other).into()
109 }
110}
111
112impl std::fmt::Debug for InviteSecret {
114 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115 f.debug_struct("InviteSecret").finish()
116 }
117}
118
119impl HighEntropySecretSource for InviteSecret {
122 fn provide_high_entropy_bytes(&self) -> SensitiveSlice<'_> {
123 Sensitive::from(self.0.as_slice())
124 }
125}
126
127impl From<&InviteSecret> for String {
128 fn from(secret: &InviteSecret) -> Self {
129 B64Url::from(secret.0.as_slice()).to_string()
130 }
131}
132
133impl FromStr for InviteSecret {
134 type Err = InviteKeyBundleError;
135
136 fn from_str(s: &str) -> Result<Self, Self::Err> {
137 let data = B64Url::try_from(s).map_err(|_| InviteKeyBundleError::DecodingFailed)?;
138 let bytes: [u8; INVITE_SECRET_LEN] = data
139 .as_bytes()
140 .try_into()
141 .map_err(|_| InviteKeyBundleError::DecodingFailed)?;
142 Ok(InviteSecret(Zeroizing::new(bytes)))
143 }
144}
145
146impl<'de> Deserialize<'de> for InviteSecret {
147 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
148 where
149 D: serde::Deserializer<'de>,
150 {
151 deserializer.deserialize_str(FromStrVisitor::new())
152 }
153}
154
155impl Serialize for InviteSecret {
156 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
157 where
158 S: serde::Serializer,
159 {
160 serializer.serialize_str(&String::from(self))
161 }
162}
163
164#[derive(Serialize, Deserialize, Debug, PartialEq)]
170struct InviteDataV1 {
171 public_key_thumbprint: [u8; 32],
173 invite_secret: [u8; INVITE_SECRET_LEN],
175}
176impl SealableData for InviteDataV1 {}
177
178generate_versioned_sealable!(
179 InviteData,
180 DataEnvelopeNamespace::OrganizationInvite,
181 [
182 InviteDataV1 => "1",
183 ]
184);
185
186#[cfg(feature = "wasm")]
187#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)]
188const TS_INVITE: &'static str = r#"
189export type Invite = Tagged<string, "Invite">;
190"#;
191
192#[derive(Clone)]
195pub struct Invite {
196 sealed_invite_data: DataEnvelope,
199 invite_key_sealed_invite_data_cek: SymmetricKeyEnvelope,
202 invite_secret_sealed_invite_key: SecretProtectedKeyEnvelope,
205 invite_key_sealed_organization_key: Option<SymmetricKeyEnvelope>,
211 organization_key_sealed_invite_key: EncString,
214}
215
216#[derive(Serialize, Deserialize)]
223struct SerializedInvite {
224 sealed_invite_data: DataEnvelope,
225 invite_key_sealed_invite_data_cek: SymmetricKeyEnvelope,
226 invite_secret_sealed_invite_key: SecretProtectedKeyEnvelope,
227 invite_key_sealed_organization_key: Option<SymmetricKeyEnvelope>,
228 organization_key_sealed_invite_key: EncString,
229}
230
231impl From<&Invite> for SerializedInvite {
232 fn from(invite: &Invite) -> Self {
233 SerializedInvite {
234 sealed_invite_data: invite.sealed_invite_data.clone(),
235 invite_key_sealed_invite_data_cek: invite.invite_key_sealed_invite_data_cek.clone(),
236 invite_secret_sealed_invite_key: invite.invite_secret_sealed_invite_key.clone(),
237 invite_key_sealed_organization_key: invite.invite_key_sealed_organization_key.clone(),
238 organization_key_sealed_invite_key: invite.organization_key_sealed_invite_key.clone(),
239 }
240 }
241}
242
243impl From<&Invite> for String {
244 fn from(invite: &Invite) -> Self {
245 serde_json::to_string(&SerializedInvite::from(invite))
246 .expect("JSON serialization of Invite never fails")
247 }
248}
249
250impl FromStr for Invite {
251 type Err = InviteKeyBundleError;
252
253 fn from_str(s: &str) -> Result<Self, Self::Err> {
254 let data: SerializedInvite =
255 serde_json::from_str(s).map_err(|_| InviteKeyBundleError::DecodingFailed)?;
256 Ok(Invite {
257 sealed_invite_data: data.sealed_invite_data,
258 invite_key_sealed_invite_data_cek: data.invite_key_sealed_invite_data_cek,
259 invite_secret_sealed_invite_key: data.invite_secret_sealed_invite_key,
260 invite_key_sealed_organization_key: data.invite_key_sealed_organization_key,
261 organization_key_sealed_invite_key: data.organization_key_sealed_invite_key,
262 })
263 }
264}
265
266impl<'de> Deserialize<'de> for Invite {
267 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
268 where
269 D: serde::Deserializer<'de>,
270 {
271 deserializer.deserialize_str(FromStrVisitor::new())
272 }
273}
274
275impl Serialize for Invite {
276 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
277 where
278 S: serde::Serializer,
279 {
280 serializer.serialize_str(&String::from(self))
281 }
282}
283
284impl std::fmt::Debug for Invite {
287 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
288 f.debug_struct("Invite")
289 .field("sealed_invite_data", &self.sealed_invite_data)
290 .field(
291 "invite_key_sealed_invite_data_cek",
292 &self.invite_key_sealed_invite_data_cek,
293 )
294 .field(
295 "invite_secret_sealed_invite_key",
296 &self.invite_secret_sealed_invite_key,
297 )
298 .field(
299 "invite_key_sealed_organization_key",
300 &self.invite_key_sealed_organization_key,
301 )
302 .field(
303 "organization_key_sealed_invite_key",
304 &self.organization_key_sealed_invite_key,
305 )
306 .finish()
307 }
308}
309
310impl Invite {
311 pub fn unseal_invite_key_with_organization_key<Ids: KeySlotIds>(
313 &self,
314 organization_key: Ids::Symmetric,
315 ctx: &mut KeyStoreContext<Ids>,
316 ) -> Result<Ids::Symmetric, InviteKeyBundleError> {
317 ctx.unwrap_symmetric_key(organization_key, &self.organization_key_sealed_invite_key)
318 .map_err(|_| InviteKeyBundleError::KeyUnsealingFailed)
319 }
320
321 pub fn unseal_invite_key_with_invite_secret<Ids: KeySlotIds>(
323 &self,
324 invite_secret: &InviteSecret,
325 ctx: &mut KeyStoreContext<Ids>,
326 ) -> Result<Ids::Symmetric, InviteKeyBundleError> {
327 let secret = HighEntropySecret::from(invite_secret.clone());
328 self.invite_secret_sealed_invite_key
329 .unseal(
330 &secret,
331 SecretProtectedKeyEnvelopeNamespace::OrganizationInvite,
332 ctx,
333 )
334 .map_err(|_| InviteKeyBundleError::KeyUnsealingFailed)
335 }
336
337 fn unseal_invite_data<Ids: KeySlotIds>(
339 &self,
340 invite_key: Ids::Symmetric,
341 ctx: &mut KeyStoreContext<Ids>,
342 ) -> Result<InviteDataV1, InviteKeyBundleError> {
343 let cek = self
345 .invite_key_sealed_invite_data_cek
346 .unseal(
347 invite_key,
348 SymmetricKeyEnvelopeNamespace::OrganizationInvite,
349 ctx,
350 )
351 .map_err(|_| InviteKeyBundleError::KeyUnsealingFailed)?;
352 let data: InviteData = self
353 .sealed_invite_data
354 .unseal(cek, ctx)
355 .map_err(|_| InviteKeyBundleError::KeyUnsealingFailed)?;
356 let InviteData::InviteDataV1(data) = data;
357 Ok(data)
358 }
359
360 pub fn get_invite_secret<Ids: KeySlotIds>(
362 &self,
363 invite_key: Ids::Symmetric,
364 ctx: &mut KeyStoreContext<Ids>,
365 ) -> Result<InviteSecret, InviteKeyBundleError> {
366 let data = self.unseal_invite_data(invite_key, ctx)?;
367 Ok(InviteSecret(Zeroizing::new(data.invite_secret)))
368 }
369
370 pub fn get_public_key_thumbprint<Ids: KeySlotIds>(
372 &self,
373 invite_key: Ids::Symmetric,
374 ctx: &mut KeyStoreContext<Ids>,
375 ) -> Result<CoseKeyThumbprint, InviteKeyBundleError> {
376 let data = self.unseal_invite_data(invite_key, ctx)?;
377 Ok(CoseKeyThumbprint::from_bytes(data.public_key_thumbprint))
378 }
379
380 pub fn supports_confirmation(&self) -> bool {
383 self.invite_key_sealed_organization_key.is_some()
384 }
385
386 pub fn unseal_organization_key<Ids: KeySlotIds>(
389 &self,
390 invite_key: Ids::Symmetric,
391 ctx: &mut KeyStoreContext<Ids>,
392 ) -> Result<Ids::Symmetric, InviteKeyBundleError> {
393 self.invite_key_sealed_organization_key
394 .as_ref()
395 .ok_or(InviteKeyBundleError::ConfirmationNotEnabled)?
396 .unseal(
397 invite_key,
398 SymmetricKeyEnvelopeNamespace::OrganizationInvite,
399 ctx,
400 )
401 .map_err(|_| InviteKeyBundleError::KeyUnsealingFailed)
402 }
403
404 pub fn enable_confirmation<Ids: KeySlotIds>(
406 &mut self,
407 organization_key: Ids::Symmetric,
408 ctx: &mut KeyStoreContext<Ids>,
409 ) -> Result<(), InviteKeyBundleError> {
410 let invite_key = self.unseal_invite_key_with_organization_key(organization_key, ctx)?;
411 let envelope = SymmetricKeyEnvelope::seal(
412 organization_key,
413 invite_key,
414 SymmetricKeyEnvelopeNamespace::OrganizationInvite,
415 ctx,
416 )
417 .map_err(|_| InviteKeyBundleError::KeySealingFailed)?;
418 self.invite_key_sealed_organization_key = Some(envelope);
419 Ok(())
420 }
421
422 pub fn disable_confirmation(&mut self) {
424 self.invite_key_sealed_organization_key = None;
425 }
426
427 pub fn make_for_private_key<Ids: KeySlotIds>(
437 organization_key: Ids::Symmetric,
438 wrapped_organization_private_key: &EncString,
439 ctx: &mut KeyStoreContext<Ids>,
440 ) -> Result<(InviteSecret, Invite), InviteKeyBundleError> {
441 let private_key_id = ctx
443 .unwrap_private_key(organization_key, wrapped_organization_private_key)
444 .map_err(|_| InviteKeyBundleError::InvalidPrivateKey)?;
445 let thumbprint = ctx
446 .get_public_key(private_key_id)
447 .map_err(|_| InviteKeyBundleError::InvalidPrivateKey)?
448 .thumbprint()
449 .map_err(|_| InviteKeyBundleError::InvalidPrivateKey)?;
450
451 let invite_secret = InviteSecret::make();
452 let invite_key = KeyEncryptionKey::make(ctx);
453
454 let invite_data: InviteData = InviteDataV1 {
458 public_key_thumbprint: *thumbprint.as_bytes(),
459 invite_secret: *invite_secret.0,
460 }
461 .into();
462 let (sealed_invite_data, invite_data_cek) = DataEnvelope::seal(invite_data, ctx)
463 .map_err(|_| InviteKeyBundleError::KeySealingFailed)?;
464 let invite_key_sealed_invite_data_cek = SymmetricKeyEnvelope::seal(
465 invite_data_cek,
466 invite_key,
467 SymmetricKeyEnvelopeNamespace::OrganizationInvite,
468 ctx,
469 )
470 .map_err(|_| InviteKeyBundleError::KeySealingFailed)?;
471
472 let secret = HighEntropySecret::from(invite_secret.clone());
474 let invite_secret_sealed_invite_key = SecretProtectedKeyEnvelope::seal(
475 invite_key,
476 &secret,
477 SecretProtectedKeyEnvelopeNamespace::OrganizationInvite,
478 ctx,
479 )
480 .map_err(|_| InviteKeyBundleError::KeySealingFailed)?;
481
482 let invite_key_sealed_organization_key = Some(
486 SymmetricKeyEnvelope::seal(
487 organization_key,
488 invite_key,
489 SymmetricKeyEnvelopeNamespace::OrganizationInvite,
490 ctx,
491 )
492 .map_err(|_| InviteKeyBundleError::KeySealingFailed)?,
493 );
494
495 let organization_key_sealed_invite_key = ctx
497 .wrap_symmetric_key(organization_key, invite_key)
498 .map_err(|_| InviteKeyBundleError::KeySealingFailed)?;
499
500 Ok((
501 invite_secret,
502 Invite {
503 sealed_invite_data,
504 invite_key_sealed_invite_data_cek,
505 invite_secret_sealed_invite_key,
506 invite_key_sealed_organization_key,
507 organization_key_sealed_invite_key,
508 },
509 ))
510 }
511}
512
513#[cfg(test)]
514mod tests {
515 use bitwarden_crypto::{
516 CoseKeyThumbprintExt, EncString, KeyStore, KeyStoreContext, PublicKeyEncryptionAlgorithm,
517 SymmetricCryptoKey, key_slot_ids,
518 };
519 use bitwarden_encoding::{B64, B64Url};
520
521 use crate::invite::{Invite, InviteKeyBundleError, InviteSecret};
522
523 const TEST_VECTOR_ORG_KEY: &str =
527 "KGP9Nc2/91w+42Z9VzY0m7h18avuZcq4ICM8Rhdc3BD92LbWS2TQkVBzavvUM684WKXiC22NJi2EwaiDW4YTAA==";
528 const TEST_VECTOR_WRAPPED_PRIVATE_KEY: &str = "2.bi9TWF/zrujUg1y+v8ECtQ==|kEMkuRt42j65YZnPEc4bLOT0/WDZwWSNJNGlUMdr/LRF3qi/vCnZ7eT0+7MruTccmyoAjKmsXdoBcufrOdPBUguFQn1LQMGqHqCyyB3SIijOlLyOOmxWYqoLUjihy8o8URGWjrAGWZnkeYWHTlFZP09Fag2xCwiQ/qS32Q+qTGGHDs0FiwjplcPkW9knlmgCXbuyPqDnEYoa0Qs/CC1hUCzFFrWs7QkE+5eLaNHxuBPpsrY6y795kEu9ve38F3piY9b6lQpc/iPGv8Zfh1isI7Mpy1zMwntXGSHjUOy17nPxCqkgYufuNGnwGNwsGjkLAl7e7bD39SYfEpTDaRUgmTl8UrZDx274e6Um1LLvokf3HiL1tboJ9/TW8IiMuAdrb3PLTH6Sep8lqZ3WhNADfMMJle9kCojHp6XSB14in1JqP0636exYeJu+FhUC1TfrthuQN2QDQ8LAvgZR7YvzkTJX3Sc5jP7m/yCmCbHhIqIAaGqJsRwAee0EMsKcALz3/akVoyjHU2LHD3dzQnyMyszyRNYViBNYAM9qBN2DqwWRDOtM171xNVJcTFsAh4mBSLiLOlDsXqLqHVKu2VJNE1XhTQ5Szubqefa/Or7nfxXcxDvivqZ7d5NfDFEskUMqh5yq1KoLK0oK5c+KvY/COIZr/kct+qtfZsXo3w5xJnPOqrAKGm+9CF4OINpLM8Z3csdZf9l5XjlmO1kIuBbquQZ0EZCHzD/GEfXRGB8BEkugdTfpnTDtmmuAJXkIY6t6e6pRUU9u4/sl+U7Iuh22fOA59SuQOElr5Lxre+hQBrRfJS3tSMEtMjYhVmltrngH+SLRxMxH/evbe2uvNaaxlFJe6EK1vqchyTX6nM8Z+2Yjb5pOAzrKQYqwwmVys9IHfjXhybqv10gFpDXBE/eq8u9xs9LbQQ03EbveQUtqdh/ms8SxLOQA9Sm9JwHEL0Zni+8NdAa5orDYzOi53bQLgrs+uUldgB/KOW2goTnKGm4YTbMAXEbum8pST8EXB9jNDXyofyN8IQUoLRvVkEgzbSPBS1sYpkkKdLZy3ojOCKnMnHXIVtzJojFckiutbj6d927X5w51E/RDMoAdylGRVKnmqLKysFRqL+pZK8Cyo+ECr7notG8kr7pVnofzigjKZS9qkRmqEa9bju1GgI20g4cxro8/0O0XnU1o0Mx46qH3niORY59i5bdMwaDS6H2c6+rmf9bIFwwgyAZvHlVdcGNoBGPR3ZXHThwI1OmWSslLWVW0IaS4utB1jL4zvHPCqh/ButA8HeRmU/NYSfaqb9YXyzn+C7ED15MWXkYmZzeE38HHxhqs12+oj+WFcg4d5/e2UcccuVi36SWhA0xWk8Kk2D6e1Pz1lmaw20vpb0eUq4AS5ZnMmWTEiKORFeGNTIROq9vuPuitLrREedu9PGjf5aeKcNqlq2nr7fOaxyi2ocKs7pLqVBUH1G7zHpCo3Rt1+o18guXFHT56vQFfkzoUNXiS6TRM4Mkl3s0TyGgBhxZkNJleTI6y4xhfH1iathBnLcfelLbxZhDB1wh3RXowS32jpM/J4pSUuNEmSLSqRQtRJY41BG00nYY02qEbakkgk6pS6a0/CWphyLfHAzUWbabOhqR1iPN9/ZiecjI=|eoklmBw+LYy2NNwjLOuA4+szKH5SzLGPlrhIJ/vfmW4=";
529 const TEST_VECTOR_INVITE: &str = r#"{"sealed_invite_data":"g1hHpQEDA3gjYXBwbGljYXRpb24veC5iaXR3YXJkZW4uY2Jvci1wYWRkZWQEUPw60HtcnAwO6kRKd7MnQz86AAE4gQI6AAE4gAKhBUz+fzObmLDLKRDeBJJYxX9qAxhXIe1Ri1CJw3ojv7WUEwBpVWeMEGTK7HHbp9WnTDjK849psMx6EOpQ7B/BYGiO28Zn0tjtQ85zwShii4FRK39mtJFE8XxV46hxW9+LlH6EPt4yfzHVkTNZ3vgOiSXcs6zyKXIhQaz6yh8eyA653m4DJEHV00JjdQ/jSfTEfyDf6LiflGntDZ7gSYlQMRxuS71yVP9Mgm79yFY4aIw703G/HUjvIEt4XlWCLzvaLDmegpDz9z4eQCVk8v8JM1fS7BsA","invite_key_sealed_invite_data_cek":"g1g+pgE6AAEReQMYZQRQ7raDdes1HnAgiuiZWXyS9ToAARVcUPw60HtcnAwO6kRKd7MnQz86AAE4gQM6AAE4gAKhBVgYvxSL4XPZLwEXHPnEWweqeeKjyH9r85/ZWE3rsZy1MLmzkFOCJa8a0o20b1P3IH6c5NYtT3v1a50+X0GmgDKiMf/omjhwRRQ7ua6wK0O2JBlhM4JIE8jmtsyMd46mFDbURK1kGRvl7Q==","invite_secret_sealed_invite_key":"hFgopQEDAxhlOgABFVxQ7raDdes1HnAgiuiZWXyS9ToAATiBBjoAATiAAaEFTJvCToVTNSe1uWbD5FhUf061kAAj1sMGWjJ8IWPV2e2xk5Z+ownZ1RqUXG2jv9h2vnsEFZ2yglvKaSCGTsOuvOXv0ESwQk6eUtFAaZjxV/Rajyuu6YgEa712Tfb9Jz13QLGngYNAogEpM1gg1MsuVokUsK8WkqggHRdJ4jvzFsbN/bP4g0l+j6f97qb2","invite_key_sealed_organization_key":"g1hKpQE6AAEReQN4ImFwcGxpY2F0aW9uL3guYml0d2FyZGVuLmxlZ2FjeS1rZXkEUO62g3XrNR5wIIromVl8kvU6AAE4gQM6AAE4gAKhBVgYoth3hg+yUTLXF4ksaeT42IWGKuTv27B4WFAKV8Z7uKGZNHOONGgZQLbQMozgYX9tseuet413M314W0sV3cBKZIRSEZfj3NvHU8tE/6b2oGxPQIPKP3Tyhl84zhI4uG+Mo5WKkvtdPonD0A==","organization_key_sealed_invite_key":"2.XjZXnAwXK/cXCmHNgCPQzw==|WEGa37JPdNWVnMPWlfinyvZdXpMW8kpTBypXPWf/abbG4/+6vp/WJQmVlkVEflEkuZwjKWSkMWJPcACGBoabeBHgzqpkSYf3kyQb7VhePHQ=|kZLDCLGm1nCJhlVuNahFiZOecy5tKG2fXGCNZZzbDjk="}"#;
530 const TEST_VECTOR_INVITE_SECRET: &str = "Ttas45_CvZi1yoFJ3bMCHx0DAAGGxDi-1BhHCutwDjI";
531
532 fn load_test_vectors(ctx: &mut KeyStoreContext<'_, TestIds>) -> EncString {
536 let org_key =
537 SymmetricCryptoKey::try_from(B64::try_from(TEST_VECTOR_ORG_KEY).unwrap()).unwrap();
538 let local_org_key_id = ctx.add_local_symmetric_key(org_key);
539 ctx.persist_symmetric_key(local_org_key_id, TestSymmKey::Organization)
540 .unwrap();
541 TEST_VECTOR_WRAPPED_PRIVATE_KEY.parse().unwrap()
542 }
543
544 #[test]
545 fn test_basic_invitation_bundle() {
546 let key_store = KeyStore::<TestIds>::default();
547 let mut ctx = key_store.context_mut();
548 let wrapped_private_key = load_test_vectors(&mut ctx);
549
550 let (secret1, _) =
551 Invite::make_for_private_key(TestSymmKey::Organization, &wrapped_private_key, &mut ctx)
552 .unwrap();
553 let (secret2, _) =
554 Invite::make_for_private_key(TestSymmKey::Organization, &wrapped_private_key, &mut ctx)
555 .unwrap();
556
557 assert_ne!(secret1, secret2);
558 }
559
560 #[test]
561 fn test_admin_recovers_invite_secret() {
562 let key_store = KeyStore::<TestIds>::default();
563 let mut ctx = key_store.context_mut();
564 let wrapped_private_key = load_test_vectors(&mut ctx);
565
566 let (invite_secret, invite) =
567 Invite::make_for_private_key(TestSymmKey::Organization, &wrapped_private_key, &mut ctx)
568 .unwrap();
569
570 let invite_key = invite
571 .unseal_invite_key_with_organization_key(TestSymmKey::Organization, &mut ctx)
572 .unwrap();
573 let recovered = invite.get_invite_secret(invite_key, &mut ctx).unwrap();
574
575 assert_eq!(invite_secret, recovered);
576 }
577
578 #[test]
579 fn test_admin_recovers_thumbprint() {
580 let key_store = KeyStore::<TestIds>::default();
581 let mut ctx = key_store.context_mut();
582 let wrapped_private_key = load_test_vectors(&mut ctx);
583
584 let private_key_id = ctx
587 .unwrap_private_key(TestSymmKey::Organization, &wrapped_private_key)
588 .unwrap();
589 let expected = ctx
590 .get_public_key(private_key_id)
591 .unwrap()
592 .thumbprint()
593 .unwrap();
594
595 let (_, invite) =
596 Invite::make_for_private_key(TestSymmKey::Organization, &wrapped_private_key, &mut ctx)
597 .unwrap();
598
599 let invite_key = invite
600 .unseal_invite_key_with_organization_key(TestSymmKey::Organization, &mut ctx)
601 .unwrap();
602 let recovered = invite
603 .get_public_key_thumbprint(invite_key, &mut ctx)
604 .unwrap();
605
606 assert_eq!(recovered, expected);
607 }
608
609 #[test]
610 fn test_invitee_recovers_organization_key() {
611 let key_store = KeyStore::<TestIds>::default();
612 let mut ctx = key_store.context_mut();
613 let wrapped_private_key = load_test_vectors(&mut ctx);
614
615 let (invite_secret, invite) =
616 Invite::make_for_private_key(TestSymmKey::Organization, &wrapped_private_key, &mut ctx)
617 .unwrap();
618
619 let invite_key = invite
622 .unseal_invite_key_with_invite_secret(&invite_secret, &mut ctx)
623 .unwrap();
624 let recovered_org_key_id = invite
625 .unseal_organization_key(invite_key, &mut ctx)
626 .unwrap();
627
628 ctx.assert_symmetric_keys_equal(recovered_org_key_id, TestSymmKey::Organization);
629 }
630
631 #[test]
632 fn test_confirmation_toggle() {
633 let key_store = KeyStore::<TestIds>::default();
634 let mut ctx = key_store.context_mut();
635 let wrapped_private_key = load_test_vectors(&mut ctx);
636
637 let (invite_secret, mut invite) =
638 Invite::make_for_private_key(TestSymmKey::Organization, &wrapped_private_key, &mut ctx)
639 .unwrap();
640
641 assert!(invite.supports_confirmation());
643
644 invite.disable_confirmation();
647 assert!(!invite.supports_confirmation());
648 let invite_key = invite
649 .unseal_invite_key_with_invite_secret(&invite_secret, &mut ctx)
650 .unwrap();
651 assert!(matches!(
652 invite.unseal_organization_key(invite_key, &mut ctx),
653 Err(InviteKeyBundleError::ConfirmationNotEnabled)
654 ));
655
656 invite
658 .enable_confirmation(TestSymmKey::Organization, &mut ctx)
659 .unwrap();
660 assert!(invite.supports_confirmation());
661 let invite_key = invite
662 .unseal_invite_key_with_invite_secret(&invite_secret, &mut ctx)
663 .unwrap();
664 let recovered_org_key_id = invite
665 .unseal_organization_key(invite_key, &mut ctx)
666 .unwrap();
667 ctx.assert_symmetric_keys_equal(recovered_org_key_id, TestSymmKey::Organization);
668 }
669
670 #[test]
671 fn test_invite_string_round_trip() {
672 let key_store = KeyStore::<TestIds>::default();
673 let mut ctx = key_store.context_mut();
674 let wrapped_private_key = load_test_vectors(&mut ctx);
675
676 let (invite_secret, invite) =
677 Invite::make_for_private_key(TestSymmKey::Organization, &wrapped_private_key, &mut ctx)
678 .unwrap();
679
680 let encoded = String::from(&invite);
681 let decoded: Invite = encoded.parse().unwrap();
682 assert_eq!(String::from(&decoded), encoded);
683
684 let invite_key = decoded
686 .unseal_invite_key_with_organization_key(TestSymmKey::Organization, &mut ctx)
687 .unwrap();
688 let recovered = decoded.get_invite_secret(invite_key, &mut ctx).unwrap();
689 assert_eq!(invite_secret, recovered);
690
691 let json = serde_json::to_string(&invite).unwrap();
695 assert_eq!(json, serde_json::to_string(&encoded).unwrap());
696 let from_json: Invite = serde_json::from_str(&json).unwrap();
697 assert_eq!(String::from(&from_json), encoded);
698 }
699
700 #[test]
701 fn test_wrong_invite_secret_fails() {
702 let key_store = KeyStore::<TestIds>::default();
703 let mut ctx = key_store.context_mut();
704 let wrapped_private_key = load_test_vectors(&mut ctx);
705
706 let (_, invite) =
707 Invite::make_for_private_key(TestSymmKey::Organization, &wrapped_private_key, &mut ctx)
708 .unwrap();
709
710 let wrong_secret = InviteSecret(zeroize::Zeroizing::new([7u8; 32]));
712 assert!(matches!(
713 invite.unseal_invite_key_with_invite_secret(&wrong_secret, &mut ctx),
714 Err(InviteKeyBundleError::KeyUnsealingFailed)
715 ));
716 }
717
718 #[test]
719 fn test_wrong_organization_key_fails() {
720 let key_store = KeyStore::<TestIds>::default();
721 let mut ctx = key_store.context_mut();
722 let wrapped_private_key = load_test_vectors(&mut ctx);
723
724 let (_, invite) =
725 Invite::make_for_private_key(TestSymmKey::Organization, &wrapped_private_key, &mut ctx)
726 .unwrap();
727
728 let wrong_org_key = ctx.generate_symmetric_key();
730 assert!(matches!(
731 invite.unseal_invite_key_with_organization_key(wrong_org_key, &mut ctx),
732 Err(InviteKeyBundleError::KeyUnsealingFailed)
733 ));
734 }
735
736 #[test]
737 fn test_invitee_recovers_thumbprint() {
738 let key_store = KeyStore::<TestIds>::default();
739 let mut ctx = key_store.context_mut();
740 let wrapped_private_key = load_test_vectors(&mut ctx);
741
742 let private_key_id = ctx
743 .unwrap_private_key(TestSymmKey::Organization, &wrapped_private_key)
744 .unwrap();
745 let expected = ctx
746 .get_public_key(private_key_id)
747 .unwrap()
748 .thumbprint()
749 .unwrap();
750
751 let (invite_secret, invite) =
752 Invite::make_for_private_key(TestSymmKey::Organization, &wrapped_private_key, &mut ctx)
753 .unwrap();
754
755 let invite_key = invite
758 .unseal_invite_key_with_invite_secret(&invite_secret, &mut ctx)
759 .unwrap();
760 let recovered = invite
761 .get_public_key_thumbprint(invite_key, &mut ctx)
762 .unwrap();
763
764 assert_eq!(recovered, expected);
765 }
766
767 #[test]
768 fn test_malformed_invite_string_fails() {
769 assert!(matches!(
771 "not valid json !!!".parse::<Invite>(),
772 Err(InviteKeyBundleError::DecodingFailed)
773 ));
774
775 assert!(matches!(
777 "{}".parse::<Invite>(),
778 Err(InviteKeyBundleError::DecodingFailed)
779 ));
780 }
781
782 #[test]
783 fn test_invalid_wrapped_private_key_fails() {
784 let key_store = KeyStore::<TestIds>::default();
785 let mut ctx = key_store.context_mut();
786 let wrapped_private_key = load_test_vectors(&mut ctx);
787
788 let wrong_org_key = ctx.generate_symmetric_key();
791 assert!(matches!(
792 Invite::make_for_private_key(wrong_org_key, &wrapped_private_key, &mut ctx),
793 Err(InviteKeyBundleError::InvalidPrivateKey)
794 ));
795 }
796
797 #[test]
798 fn test_invite_secret_into_base64_url() {
799 let data: [u8; 32] = *b"+/=Hello, World!AAAAAAAAAAAAAAAA";
800 let secret = InviteSecret(zeroize::Zeroizing::new(data));
801
802 let encoded = String::from(&secret);
803
804 assert_eq!(encoded, "Ky89SGVsbG8sIFdvcmxkIUFBQUFBQUFBQUFBQUFBQUE");
805 assert!(!encoded.contains('+'));
806 assert!(!encoded.contains('/'));
807 assert!(!encoded.contains('='));
808
809 let decoded = B64Url::try_from(encoded.as_str()).unwrap();
810 assert_eq!(decoded.as_bytes(), data);
811
812 let reparsed: InviteSecret = encoded.parse().unwrap();
814 assert_eq!(reparsed, secret);
815 }
816
817 #[test]
818 #[ignore = "Manual test to generate test vectors"]
819 fn generate_test_vectors() {
820 let key_store = KeyStore::<TestIds>::default();
821 let mut ctx = key_store.context_mut();
822
823 let org_key =
824 SymmetricCryptoKey::try_from(B64::try_from(TEST_VECTOR_ORG_KEY).unwrap()).unwrap();
825 let org_key_id = ctx.add_local_symmetric_key(org_key);
826
827 let private_key_id = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
829 let wrapped_private_key = ctx.wrap_private_key(org_key_id, private_key_id).unwrap();
830
831 let (invite_secret, invite) =
832 Invite::make_for_private_key(org_key_id, &wrapped_private_key, &mut ctx).unwrap();
833
834 println!(
835 "const TEST_VECTOR_WRAPPED_PRIVATE_KEY: &str = \"{}\";",
836 wrapped_private_key.to_string()
837 );
838 println!(
839 "const TEST_VECTOR_INVITE: &str = r#\"{}\"#;",
840 String::from(&invite)
841 );
842 println!(
843 "const TEST_VECTOR_INVITE_SECRET: &str = \"{}\";",
844 String::from(&invite_secret)
845 );
846 }
847
848 #[test]
849 fn test_invite_test_vector() {
850 let key_store = KeyStore::<TestIds>::default();
851 let mut ctx = key_store.context_mut();
852
853 let org_key =
854 SymmetricCryptoKey::try_from(B64::try_from(TEST_VECTOR_ORG_KEY).unwrap()).unwrap();
855 let org_key_id = ctx.add_local_symmetric_key(org_key);
856
857 let invite: Invite = TEST_VECTOR_INVITE.parse().unwrap();
858 let invite_key = invite
859 .unseal_invite_key_with_organization_key(org_key_id, &mut ctx)
860 .unwrap();
861 let recovered = invite.get_invite_secret(invite_key, &mut ctx).unwrap();
862
863 assert_eq!(String::from(&recovered), TEST_VECTOR_INVITE_SECRET);
864 }
865
866 #[test]
867 #[ignore = "Manual test to verify debug format"]
868 fn test_debug() {
869 let key_store = KeyStore::<TestIds>::default();
870 let mut ctx = key_store.context_mut();
871 let wrapped_private_key = load_test_vectors(&mut ctx);
872
873 let (invite_secret, invite) =
874 Invite::make_for_private_key(TestSymmKey::Organization, &wrapped_private_key, &mut ctx)
875 .unwrap();
876 println!("{invite_secret:?}");
877 println!("{invite:?}");
878 }
879
880 key_slot_ids! {
881 #[symmetric]
882 pub enum TestSymmKey {
883 Organization,
884 #[local]
885 Local(LocalId),
886 }
887
888 #[private]
889 pub enum TestPrivateKey {
890 A(u8),
891 B,
892 #[local]
893 C(LocalId),
894 }
895
896 #[signing]
897 pub enum TestSigningKey {
898 A(u8),
899 B,
900 #[local]
901 C(LocalId),
902 }
903
904 pub TestIds => TestSymmKey, TestPrivateKey, TestSigningKey;
905 }
906}