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
51use crate::organization_private_key::OrganizationPrivateKey;
52
53const INVITE_SECRET_LEN: usize = 32;
56
57#[derive(Debug, Error)]
59pub enum InviteKeyBundleError {
60 #[error("Decoding failed")]
62 DecodingFailed,
63 #[error("Unable to seal invite")]
65 KeySealingFailed,
66 #[error("Unable to unseal invite")]
68 KeyUnsealingFailed,
69 #[error("Missing Key for Id: {0}")]
71 MissingKeyId(String),
72 #[error("Invalid organization private key")]
75 InvalidPrivateKey,
76 #[error("Confirmation is not enabled on this invite")]
78 ConfirmationNotEnabled,
79}
80
81#[cfg(feature = "wasm")]
82#[wasm_bindgen::prelude::wasm_bindgen(typescript_custom_section)]
83const TS_INVITE_SECRET: &'static str = r#"
84export type InviteSecret = Tagged<string, "InviteSecret">;
85"#;
86
87#[derive(Clone)]
91pub struct InviteSecret(Zeroizing<[u8; INVITE_SECRET_LEN]>);
92
93impl InviteSecret {
94 fn make() -> Self {
96 let mut bytes = Zeroizing::new([0u8; INVITE_SECRET_LEN]);
97 bitwarden_random::rng().fill_bytes(bytes.as_mut_slice());
98 InviteSecret(bytes)
99 }
100}
101
102impl ConstantTimeEq for InviteSecret {
103 fn ct_eq(&self, other: &InviteSecret) -> Choice {
104 self.0.as_slice().ct_eq(other.0.as_slice())
105 }
106}
107
108impl PartialEq for InviteSecret {
109 fn eq(&self, other: &Self) -> bool {
110 self.ct_eq(other).into()
111 }
112}
113
114impl std::fmt::Debug for InviteSecret {
116 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117 f.debug_struct("InviteSecret").finish()
118 }
119}
120
121impl HighEntropySecretSource for InviteSecret {
124 fn provide_high_entropy_bytes(&self) -> SensitiveSlice<'_> {
125 Sensitive::from(self.0.as_slice())
126 }
127}
128
129impl From<&InviteSecret> for String {
130 fn from(secret: &InviteSecret) -> Self {
131 B64Url::from(secret.0.as_slice()).to_string()
132 }
133}
134
135impl FromStr for InviteSecret {
136 type Err = InviteKeyBundleError;
137
138 fn from_str(s: &str) -> Result<Self, Self::Err> {
139 let data = B64Url::try_from(s).map_err(|_| InviteKeyBundleError::DecodingFailed)?;
140 let bytes: [u8; INVITE_SECRET_LEN] = data
141 .as_bytes()
142 .try_into()
143 .map_err(|_| InviteKeyBundleError::DecodingFailed)?;
144 Ok(InviteSecret(Zeroizing::new(bytes)))
145 }
146}
147
148impl<'de> Deserialize<'de> for InviteSecret {
149 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
150 where
151 D: serde::Deserializer<'de>,
152 {
153 deserializer.deserialize_str(FromStrVisitor::new())
154 }
155}
156
157impl Serialize for InviteSecret {
158 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
159 where
160 S: serde::Serializer,
161 {
162 serializer.serialize_str(&String::from(self))
163 }
164}
165
166#[derive(Serialize, Deserialize, Debug, PartialEq)]
172struct InviteDataV1 {
173 public_key_thumbprint: [u8; 32],
175 invite_secret: [u8; INVITE_SECRET_LEN],
177}
178impl SealableData for InviteDataV1 {}
179
180generate_versioned_sealable!(
181 InviteData,
182 DataEnvelopeNamespace::OrganizationInvite,
183 [
184 InviteDataV1 => "1",
185 ]
186);
187
188#[derive(Clone)]
191pub struct Invite {
192 sealed_invite_data: DataEnvelope,
195 invite_key_sealed_invite_data_cek: SymmetricKeyEnvelope,
198 invite_secret_sealed_invite_key: SecretProtectedKeyEnvelope,
201 invite_key_sealed_organization_key: Option<SymmetricKeyEnvelope>,
207 organization_key_sealed_invite_key: EncString,
210}
211
212#[derive(Serialize, Deserialize)]
218struct SerializedInvite {
219 sealed_invite_data: DataEnvelope,
220 invite_key_sealed_invite_data_cek: SymmetricKeyEnvelope,
221 invite_secret_sealed_invite_key: SecretProtectedKeyEnvelope,
222 invite_key_sealed_organization_key: Option<SymmetricKeyEnvelope>,
223 organization_key_sealed_invite_key: EncString,
224}
225
226impl From<&Invite> for SerializedInvite {
227 fn from(invite: &Invite) -> Self {
228 SerializedInvite {
229 sealed_invite_data: invite.sealed_invite_data.clone(),
230 invite_key_sealed_invite_data_cek: invite.invite_key_sealed_invite_data_cek.clone(),
231 invite_secret_sealed_invite_key: invite.invite_secret_sealed_invite_key.clone(),
232 invite_key_sealed_organization_key: invite.invite_key_sealed_organization_key.clone(),
233 organization_key_sealed_invite_key: invite.organization_key_sealed_invite_key.clone(),
234 }
235 }
236}
237
238impl From<&Invite> for String {
239 fn from(invite: &Invite) -> Self {
240 serde_json::to_string(&SerializedInvite::from(invite))
241 .expect("JSON serialization of Invite never fails")
242 }
243}
244
245impl FromStr for Invite {
246 type Err = InviteKeyBundleError;
247
248 fn from_str(s: &str) -> Result<Self, Self::Err> {
249 let data: SerializedInvite =
250 serde_json::from_str(s).map_err(|_| InviteKeyBundleError::DecodingFailed)?;
251 Ok(Invite {
252 sealed_invite_data: data.sealed_invite_data,
253 invite_key_sealed_invite_data_cek: data.invite_key_sealed_invite_data_cek,
254 invite_secret_sealed_invite_key: data.invite_secret_sealed_invite_key,
255 invite_key_sealed_organization_key: data.invite_key_sealed_organization_key,
256 organization_key_sealed_invite_key: data.organization_key_sealed_invite_key,
257 })
258 }
259}
260
261impl<'de> Deserialize<'de> for Invite {
262 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
263 where
264 D: serde::Deserializer<'de>,
265 {
266 deserializer.deserialize_str(FromStrVisitor::new())
267 }
268}
269
270impl Serialize for Invite {
271 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
272 where
273 S: serde::Serializer,
274 {
275 serializer.serialize_str(&String::from(self))
276 }
277}
278
279impl std::fmt::Debug for Invite {
282 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
283 f.debug_struct("Invite")
284 .field("sealed_invite_data", &self.sealed_invite_data)
285 .field(
286 "invite_key_sealed_invite_data_cek",
287 &self.invite_key_sealed_invite_data_cek,
288 )
289 .field(
290 "invite_secret_sealed_invite_key",
291 &self.invite_secret_sealed_invite_key,
292 )
293 .field(
294 "invite_key_sealed_organization_key",
295 &self.invite_key_sealed_organization_key,
296 )
297 .field(
298 "organization_key_sealed_invite_key",
299 &self.organization_key_sealed_invite_key,
300 )
301 .finish()
302 }
303}
304
305impl Invite {
306 pub fn unseal_invite_key_with_organization_key<Ids: KeySlotIds>(
308 &self,
309 organization_key: Ids::Symmetric,
310 ctx: &mut KeyStoreContext<Ids>,
311 ) -> Result<Ids::Symmetric, InviteKeyBundleError> {
312 ctx.unwrap_symmetric_key(organization_key, &self.organization_key_sealed_invite_key)
313 .map_err(|_| InviteKeyBundleError::KeyUnsealingFailed)
314 }
315
316 pub fn unseal_invite_key_with_invite_secret<Ids: KeySlotIds>(
318 &self,
319 invite_secret: &InviteSecret,
320 ctx: &mut KeyStoreContext<Ids>,
321 ) -> Result<Ids::Symmetric, InviteKeyBundleError> {
322 let secret = HighEntropySecret::from(invite_secret.clone());
323 self.invite_secret_sealed_invite_key
324 .unseal(
325 &secret,
326 SecretProtectedKeyEnvelopeNamespace::OrganizationInvite,
327 ctx,
328 )
329 .map_err(|_| InviteKeyBundleError::KeyUnsealingFailed)
330 }
331
332 fn unseal_invite_data<Ids: KeySlotIds>(
334 &self,
335 invite_key: Ids::Symmetric,
336 ctx: &mut KeyStoreContext<Ids>,
337 ) -> Result<InviteDataV1, InviteKeyBundleError> {
338 let cek = self
340 .invite_key_sealed_invite_data_cek
341 .unseal(
342 invite_key,
343 SymmetricKeyEnvelopeNamespace::OrganizationInvite,
344 ctx,
345 )
346 .map_err(|_| InviteKeyBundleError::KeyUnsealingFailed)?;
347 let data: InviteData = self
348 .sealed_invite_data
349 .unseal(cek, ctx)
350 .map_err(|_| InviteKeyBundleError::KeyUnsealingFailed)?;
351 let InviteData::InviteDataV1(data) = data;
352 Ok(data)
353 }
354
355 pub fn get_invite_secret<Ids: KeySlotIds>(
357 &self,
358 invite_key: Ids::Symmetric,
359 ctx: &mut KeyStoreContext<Ids>,
360 ) -> Result<InviteSecret, InviteKeyBundleError> {
361 let data = self.unseal_invite_data(invite_key, ctx)?;
362 Ok(InviteSecret(Zeroizing::new(data.invite_secret)))
363 }
364
365 pub fn get_public_key_thumbprint<Ids: KeySlotIds>(
367 &self,
368 invite_key: Ids::Symmetric,
369 ctx: &mut KeyStoreContext<Ids>,
370 ) -> Result<CoseKeyThumbprint, InviteKeyBundleError> {
371 let data = self.unseal_invite_data(invite_key, ctx)?;
372 Ok(CoseKeyThumbprint::from_bytes(data.public_key_thumbprint))
373 }
374
375 pub fn supports_confirmation(&self) -> bool {
378 self.invite_key_sealed_organization_key.is_some()
379 }
380
381 pub fn unseal_organization_key<Ids: KeySlotIds>(
384 &self,
385 invite_key: Ids::Symmetric,
386 ctx: &mut KeyStoreContext<Ids>,
387 ) -> Result<Ids::Symmetric, InviteKeyBundleError> {
388 self.invite_key_sealed_organization_key
389 .as_ref()
390 .ok_or(InviteKeyBundleError::ConfirmationNotEnabled)?
391 .unseal(
392 invite_key,
393 SymmetricKeyEnvelopeNamespace::OrganizationInvite,
394 ctx,
395 )
396 .map_err(|_| InviteKeyBundleError::KeyUnsealingFailed)
397 }
398
399 pub fn enable_confirmation<Ids: KeySlotIds>(
401 &mut self,
402 organization_key: Ids::Symmetric,
403 ctx: &mut KeyStoreContext<Ids>,
404 ) -> Result<(), InviteKeyBundleError> {
405 let invite_key = self.unseal_invite_key_with_organization_key(organization_key, ctx)?;
406 let envelope = SymmetricKeyEnvelope::seal(
407 organization_key,
408 invite_key,
409 SymmetricKeyEnvelopeNamespace::OrganizationInvite,
410 ctx,
411 )
412 .map_err(|_| InviteKeyBundleError::KeySealingFailed)?;
413 self.invite_key_sealed_organization_key = Some(envelope);
414 Ok(())
415 }
416
417 pub fn disable_confirmation(&mut self) {
419 self.invite_key_sealed_organization_key = None;
420 }
421
422 pub fn make_for_private_key<Ids: KeySlotIds>(
432 organization_key: Ids::Symmetric,
433 wrapped_organization_private_key: &EncString,
434 ctx: &mut KeyStoreContext<Ids>,
435 ) -> Result<(InviteSecret, Invite), InviteKeyBundleError> {
436 let organization_private_key = OrganizationPrivateKey::unwrap_with_organization_key(
438 organization_key,
439 wrapped_organization_private_key,
440 ctx,
441 )
442 .map_err(|_| InviteKeyBundleError::InvalidPrivateKey)?;
443 let thumbprint = organization_private_key
444 .public_key(ctx)
445 .map_err(|_| InviteKeyBundleError::InvalidPrivateKey)?
446 .thumbprint()
447 .map_err(|_| InviteKeyBundleError::InvalidPrivateKey)?;
448
449 let invite_secret = InviteSecret::make();
450 let invite_key = KeyEncryptionKey::make(ctx);
451
452 let invite_data: InviteData = InviteDataV1 {
456 public_key_thumbprint: *thumbprint.as_bytes(),
457 invite_secret: *invite_secret.0,
458 }
459 .into();
460 let (sealed_invite_data, invite_data_cek) = DataEnvelope::seal(invite_data, ctx)
461 .map_err(|_| InviteKeyBundleError::KeySealingFailed)?;
462 let invite_key_sealed_invite_data_cek = SymmetricKeyEnvelope::seal(
463 invite_data_cek,
464 invite_key,
465 SymmetricKeyEnvelopeNamespace::OrganizationInvite,
466 ctx,
467 )
468 .map_err(|_| InviteKeyBundleError::KeySealingFailed)?;
469
470 let secret = HighEntropySecret::from(invite_secret.clone());
472 let invite_secret_sealed_invite_key = SecretProtectedKeyEnvelope::seal(
473 invite_key,
474 &secret,
475 SecretProtectedKeyEnvelopeNamespace::OrganizationInvite,
476 ctx,
477 )
478 .map_err(|_| InviteKeyBundleError::KeySealingFailed)?;
479
480 let invite_key_sealed_organization_key = Some(
484 SymmetricKeyEnvelope::seal(
485 organization_key,
486 invite_key,
487 SymmetricKeyEnvelopeNamespace::OrganizationInvite,
488 ctx,
489 )
490 .map_err(|_| InviteKeyBundleError::KeySealingFailed)?,
491 );
492
493 let organization_key_sealed_invite_key = ctx
495 .wrap_symmetric_key(organization_key, invite_key)
496 .map_err(|_| InviteKeyBundleError::KeySealingFailed)?;
497
498 Ok((
499 invite_secret,
500 Invite {
501 sealed_invite_data,
502 invite_key_sealed_invite_data_cek,
503 invite_secret_sealed_invite_key,
504 invite_key_sealed_organization_key,
505 organization_key_sealed_invite_key,
506 },
507 ))
508 }
509}
510
511#[cfg(test)]
512mod tests {
513 use bitwarden_crypto::{CoseKeyThumbprintExt, KeyStore, PublicKeyEncryptionAlgorithm};
514 use bitwarden_encoding::B64Url;
515
516 use crate::{
517 invite::{Invite, InviteKeyBundleError, InviteSecret},
518 test_support::{
519 TEST_VECTOR_V1_ORG_KEY, TestIds, TestSymmKey, add_symmetric_key, load_v1_test_vectors,
520 },
521 };
522
523 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="}"#;
524 const TEST_VECTOR_INVITE_SECRET: &str = "Ttas45_CvZi1yoFJ3bMCHx0DAAGGxDi-1BhHCutwDjI";
525
526 #[test]
527 fn test_basic_invitation_bundle() {
528 let key_store = KeyStore::<TestIds>::default();
529 let mut ctx = key_store.context_mut();
530 let wrapped_private_key = load_v1_test_vectors(&mut ctx);
531
532 let (secret1, _) =
533 Invite::make_for_private_key(TestSymmKey::Organization, &wrapped_private_key, &mut ctx)
534 .unwrap();
535 let (secret2, _) =
536 Invite::make_for_private_key(TestSymmKey::Organization, &wrapped_private_key, &mut ctx)
537 .unwrap();
538
539 assert_ne!(secret1, secret2);
540 }
541
542 #[test]
543 fn test_admin_recovers_invite_secret() {
544 let key_store = KeyStore::<TestIds>::default();
545 let mut ctx = key_store.context_mut();
546 let wrapped_private_key = load_v1_test_vectors(&mut ctx);
547
548 let (invite_secret, invite) =
549 Invite::make_for_private_key(TestSymmKey::Organization, &wrapped_private_key, &mut ctx)
550 .unwrap();
551
552 let invite_key = invite
553 .unseal_invite_key_with_organization_key(TestSymmKey::Organization, &mut ctx)
554 .unwrap();
555 let recovered = invite.get_invite_secret(invite_key, &mut ctx).unwrap();
556
557 assert_eq!(invite_secret, recovered);
558 }
559
560 #[test]
561 fn test_admin_recovers_thumbprint() {
562 let key_store = KeyStore::<TestIds>::default();
563 let mut ctx = key_store.context_mut();
564 let wrapped_private_key = load_v1_test_vectors(&mut ctx);
565
566 let private_key_id = ctx
569 .unwrap_private_key(TestSymmKey::Organization, &wrapped_private_key)
570 .unwrap();
571 let expected = ctx
572 .get_public_key(private_key_id)
573 .unwrap()
574 .thumbprint()
575 .unwrap();
576
577 let (_, invite) =
578 Invite::make_for_private_key(TestSymmKey::Organization, &wrapped_private_key, &mut ctx)
579 .unwrap();
580
581 let invite_key = invite
582 .unseal_invite_key_with_organization_key(TestSymmKey::Organization, &mut ctx)
583 .unwrap();
584 let recovered = invite
585 .get_public_key_thumbprint(invite_key, &mut ctx)
586 .unwrap();
587
588 assert_eq!(recovered, expected);
589 }
590
591 #[test]
592 fn test_invitee_recovers_organization_key() {
593 let key_store = KeyStore::<TestIds>::default();
594 let mut ctx = key_store.context_mut();
595 let wrapped_private_key = load_v1_test_vectors(&mut ctx);
596
597 let (invite_secret, invite) =
598 Invite::make_for_private_key(TestSymmKey::Organization, &wrapped_private_key, &mut ctx)
599 .unwrap();
600
601 let invite_key = invite
604 .unseal_invite_key_with_invite_secret(&invite_secret, &mut ctx)
605 .unwrap();
606 let recovered_org_key_id = invite
607 .unseal_organization_key(invite_key, &mut ctx)
608 .unwrap();
609
610 ctx.assert_symmetric_keys_equal(recovered_org_key_id, TestSymmKey::Organization);
611 }
612
613 #[test]
614 fn test_confirmation_toggle() {
615 let key_store = KeyStore::<TestIds>::default();
616 let mut ctx = key_store.context_mut();
617 let wrapped_private_key = load_v1_test_vectors(&mut ctx);
618
619 let (invite_secret, mut invite) =
620 Invite::make_for_private_key(TestSymmKey::Organization, &wrapped_private_key, &mut ctx)
621 .unwrap();
622
623 assert!(invite.supports_confirmation());
625
626 invite.disable_confirmation();
629 assert!(!invite.supports_confirmation());
630 let invite_key = invite
631 .unseal_invite_key_with_invite_secret(&invite_secret, &mut ctx)
632 .unwrap();
633 assert!(matches!(
634 invite.unseal_organization_key(invite_key, &mut ctx),
635 Err(InviteKeyBundleError::ConfirmationNotEnabled)
636 ));
637
638 invite
640 .enable_confirmation(TestSymmKey::Organization, &mut ctx)
641 .unwrap();
642 assert!(invite.supports_confirmation());
643 let invite_key = invite
644 .unseal_invite_key_with_invite_secret(&invite_secret, &mut ctx)
645 .unwrap();
646 let recovered_org_key_id = invite
647 .unseal_organization_key(invite_key, &mut ctx)
648 .unwrap();
649 ctx.assert_symmetric_keys_equal(recovered_org_key_id, TestSymmKey::Organization);
650 }
651
652 #[test]
653 fn test_invite_string_round_trip() {
654 let key_store = KeyStore::<TestIds>::default();
655 let mut ctx = key_store.context_mut();
656 let wrapped_private_key = load_v1_test_vectors(&mut ctx);
657
658 let (invite_secret, invite) =
659 Invite::make_for_private_key(TestSymmKey::Organization, &wrapped_private_key, &mut ctx)
660 .unwrap();
661
662 let encoded = String::from(&invite);
663 let decoded: Invite = encoded.parse().unwrap();
664 assert_eq!(String::from(&decoded), encoded);
665
666 let invite_key = decoded
668 .unseal_invite_key_with_organization_key(TestSymmKey::Organization, &mut ctx)
669 .unwrap();
670 let recovered = decoded.get_invite_secret(invite_key, &mut ctx).unwrap();
671 assert_eq!(invite_secret, recovered);
672
673 let json = serde_json::to_string(&invite).unwrap();
676 assert_eq!(json, serde_json::to_string(&encoded).unwrap());
677 let from_json: Invite = serde_json::from_str(&json).unwrap();
678 assert_eq!(String::from(&from_json), encoded);
679 }
680
681 #[test]
682 fn test_wrong_invite_secret_fails() {
683 let key_store = KeyStore::<TestIds>::default();
684 let mut ctx = key_store.context_mut();
685 let wrapped_private_key = load_v1_test_vectors(&mut ctx);
686
687 let (_, invite) =
688 Invite::make_for_private_key(TestSymmKey::Organization, &wrapped_private_key, &mut ctx)
689 .unwrap();
690
691 let wrong_secret = InviteSecret(zeroize::Zeroizing::new([7u8; 32]));
693 assert!(matches!(
694 invite.unseal_invite_key_with_invite_secret(&wrong_secret, &mut ctx),
695 Err(InviteKeyBundleError::KeyUnsealingFailed)
696 ));
697 }
698
699 #[test]
700 fn test_wrong_organization_key_fails() {
701 let key_store = KeyStore::<TestIds>::default();
702 let mut ctx = key_store.context_mut();
703 let wrapped_private_key = load_v1_test_vectors(&mut ctx);
704
705 let (_, invite) =
706 Invite::make_for_private_key(TestSymmKey::Organization, &wrapped_private_key, &mut ctx)
707 .unwrap();
708
709 let wrong_org_key = ctx.generate_symmetric_key();
711 assert!(matches!(
712 invite.unseal_invite_key_with_organization_key(wrong_org_key, &mut ctx),
713 Err(InviteKeyBundleError::KeyUnsealingFailed)
714 ));
715 }
716
717 #[test]
718 fn test_invitee_recovers_thumbprint() {
719 let key_store = KeyStore::<TestIds>::default();
720 let mut ctx = key_store.context_mut();
721 let wrapped_private_key = load_v1_test_vectors(&mut ctx);
722
723 let private_key_id = ctx
724 .unwrap_private_key(TestSymmKey::Organization, &wrapped_private_key)
725 .unwrap();
726 let expected = ctx
727 .get_public_key(private_key_id)
728 .unwrap()
729 .thumbprint()
730 .unwrap();
731
732 let (invite_secret, invite) =
733 Invite::make_for_private_key(TestSymmKey::Organization, &wrapped_private_key, &mut ctx)
734 .unwrap();
735
736 let invite_key = invite
739 .unseal_invite_key_with_invite_secret(&invite_secret, &mut ctx)
740 .unwrap();
741 let recovered = invite
742 .get_public_key_thumbprint(invite_key, &mut ctx)
743 .unwrap();
744
745 assert_eq!(recovered, expected);
746 }
747
748 #[test]
749 fn test_malformed_invite_string_fails() {
750 assert!(matches!(
752 "not valid json !!!".parse::<Invite>(),
753 Err(InviteKeyBundleError::DecodingFailed)
754 ));
755
756 assert!(matches!(
758 "{}".parse::<Invite>(),
759 Err(InviteKeyBundleError::DecodingFailed)
760 ));
761 }
762
763 #[test]
764 fn test_invalid_wrapped_private_key_fails() {
765 let key_store = KeyStore::<TestIds>::default();
766 let mut ctx = key_store.context_mut();
767 let wrapped_private_key = load_v1_test_vectors(&mut ctx);
768
769 let wrong_org_key = ctx.generate_symmetric_key();
772 assert!(matches!(
773 Invite::make_for_private_key(wrong_org_key, &wrapped_private_key, &mut ctx),
774 Err(InviteKeyBundleError::InvalidPrivateKey)
775 ));
776 }
777
778 #[test]
779 fn test_invite_secret_into_base64_url() {
780 let data: [u8; 32] = *b"+/=Hello, World!AAAAAAAAAAAAAAAA";
781 let secret = InviteSecret(zeroize::Zeroizing::new(data));
782
783 let encoded = String::from(&secret);
784
785 assert_eq!(encoded, "Ky89SGVsbG8sIFdvcmxkIUFBQUFBQUFBQUFBQUFBQUE");
786 assert!(!encoded.contains('+'));
787 assert!(!encoded.contains('/'));
788 assert!(!encoded.contains('='));
789
790 let decoded = B64Url::try_from(encoded.as_str()).unwrap();
791 assert_eq!(decoded.as_bytes(), data);
792
793 let reparsed: InviteSecret = encoded.parse().unwrap();
795 assert_eq!(reparsed, secret);
796 }
797
798 #[test]
799 #[ignore = "Manual test to generate test vectors"]
800 fn generate_test_vectors() {
801 let key_store = KeyStore::<TestIds>::default();
802 let mut ctx = key_store.context_mut();
803
804 let org_key_id = add_symmetric_key(TEST_VECTOR_V1_ORG_KEY, &mut ctx);
805
806 let private_key_id = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
808 let wrapped_private_key = ctx.wrap_private_key(org_key_id, private_key_id).unwrap();
809
810 let (invite_secret, invite) =
811 Invite::make_for_private_key(org_key_id, &wrapped_private_key, &mut ctx).unwrap();
812
813 println!(
814 "const TEST_VECTOR_V1_WRAPPED_PRIVATE_KEY: &str = \"{}\";",
815 wrapped_private_key.to_string()
816 );
817 println!(
818 "const TEST_VECTOR_INVITE: &str = r#\"{}\"#;",
819 String::from(&invite)
820 );
821 println!(
822 "const TEST_VECTOR_INVITE_SECRET: &str = \"{}\";",
823 String::from(&invite_secret)
824 );
825 }
826
827 #[test]
828 fn test_invite_test_vector() {
829 let key_store = KeyStore::<TestIds>::default();
830 let mut ctx = key_store.context_mut();
831
832 let org_key_id = add_symmetric_key(TEST_VECTOR_V1_ORG_KEY, &mut ctx);
833
834 let invite: Invite = TEST_VECTOR_INVITE.parse().unwrap();
835 let invite_key = invite
836 .unseal_invite_key_with_organization_key(org_key_id, &mut ctx)
837 .unwrap();
838 let recovered = invite.get_invite_secret(invite_key, &mut ctx).unwrap();
839
840 assert_eq!(String::from(&recovered), TEST_VECTOR_INVITE_SECRET);
841 }
842
843 #[test]
844 #[ignore = "Manual test to verify debug format"]
845 fn test_debug() {
846 let key_store = KeyStore::<TestIds>::default();
847 let mut ctx = key_store.context_mut();
848 let wrapped_private_key = load_v1_test_vectors(&mut ctx);
849
850 let (invite_secret, invite) =
851 Invite::make_for_private_key(TestSymmKey::Organization, &wrapped_private_key, &mut ctx)
852 .unwrap();
853 println!("{invite_secret:?}");
854 println!("{invite:?}");
855 }
856}