bitwarden_organization_crypto/
invite_key_bundle.rs1use std::str::FromStr;
2
3use bitwarden_crypto::{
4 BitwardenLegacyKeyBytes, EncString, KeySlotIds, KeyStoreContext, SymmetricCryptoKey,
5};
6use bitwarden_encoding::{B64, B64Url, FromStrVisitor};
7use serde::{Deserialize, Serialize};
8use subtle::{Choice, ConstantTimeEq};
9use thiserror::Error;
10
11#[derive(Debug, Error)]
13pub enum InviteKeyBundleError {
14 #[error("Decoding failed")]
16 DecodingFailed,
17 #[error("Unable to seal invite key with org key")]
19 KeySealingFailed,
20 #[error("Unable to unseal invite key with org key")]
22 KeyUnsealingFailed,
23 #[error("Missing Key for Id: {0}")]
25 MissingKeyId(String),
26}
27
28#[derive(Clone)]
38pub struct InviteKeyData(SymmetricCryptoKey);
39
40impl ConstantTimeEq for InviteKeyData {
41 fn ct_eq(&self, other: &InviteKeyData) -> Choice {
42 self.0.ct_eq(&other.0)
43 }
44}
45
46impl PartialEq for InviteKeyData {
47 fn eq(&self, other: &Self) -> bool {
48 self.ct_eq(other).into()
49 }
50}
51
52impl std::fmt::Debug for InviteKeyData {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 self.0.fmt(f)
55 }
56}
57
58impl From<&InviteKeyData> for String {
59 fn from(key_data: &InviteKeyData) -> Self {
60 B64Url::from(key_data.0.to_encoded().as_ref()).to_string()
61 }
62}
63
64impl FromStr for InviteKeyData {
65 type Err = InviteKeyBundleError;
66
67 fn from_str(s: &str) -> Result<Self, Self::Err> {
68 let data = B64Url::try_from(s).map_err(|_| InviteKeyBundleError::DecodingFailed)?;
69 Ok(InviteKeyData(
70 SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(data.as_bytes()))
71 .map_err(|_| InviteKeyBundleError::DecodingFailed)?,
72 ))
73 }
74}
75
76impl<'de> Deserialize<'de> for InviteKeyData {
77 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
78 where
79 D: serde::Deserializer<'de>,
80 {
81 deserializer.deserialize_str(FromStrVisitor::new())
82 }
83}
84
85impl Serialize for InviteKeyData {
86 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
87 where
88 S: serde::Serializer,
89 {
90 serializer.serialize_str(&String::from(self))
91 }
92}
93
94pub struct InviteKeyEnvelope(EncString);
97
98impl From<&InviteKeyEnvelope> for String {
99 fn from(key_data: &InviteKeyEnvelope) -> Self {
100 B64::from(
101 key_data
102 .0
103 .to_buffer()
104 .expect("`to_buffer` never fails for `EncString`"),
105 )
106 .to_string()
107 }
108}
109
110impl FromStr for InviteKeyEnvelope {
111 type Err = InviteKeyBundleError;
112
113 fn from_str(s: &str) -> Result<Self, Self::Err> {
114 let data = B64::try_from(s).map_err(|_| InviteKeyBundleError::DecodingFailed)?;
115 Ok(InviteKeyEnvelope(
116 EncString::from_buffer(data.as_bytes())
117 .map_err(|_| InviteKeyBundleError::DecodingFailed)?,
118 ))
119 }
120}
121
122impl<'de> Deserialize<'de> for InviteKeyEnvelope {
123 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
124 where
125 D: serde::Deserializer<'de>,
126 {
127 deserializer.deserialize_str(FromStrVisitor::new())
128 }
129}
130
131impl Serialize for InviteKeyEnvelope {
132 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
133 where
134 S: serde::Serializer,
135 {
136 serializer.serialize_str(&String::from(self))
137 }
138}
139
140impl InviteKeyEnvelope {
141 pub fn unseal<Ids: KeySlotIds>(
144 &self,
145 organization_key: Ids::Symmetric,
146 ctx: &mut KeyStoreContext<Ids>,
147 ) -> Result<InviteKeyData, InviteKeyBundleError> {
148 let key_id = ctx
149 .unwrap_symmetric_key(organization_key, &self.0)
150 .map_err(|_| InviteKeyBundleError::KeyUnsealingFailed)?;
151
152 #[allow(deprecated)]
153 Ok(InviteKeyData(
154 ctx.dangerous_get_symmetric_key(key_id)
155 .map_err(|_| InviteKeyBundleError::MissingKeyId(format!("{key_id:?}")))?
156 .clone(),
157 ))
158 }
159}
160
161pub struct InviteKeyBundle {
164 raw_key_data: InviteKeyData,
165 sealed_key_envelope: InviteKeyEnvelope,
166}
167
168impl InviteKeyBundle {
169 pub fn make<Ids: KeySlotIds>(
172 organization_key: Ids::Symmetric,
173 ctx: &mut KeyStoreContext<Ids>,
174 ) -> Result<Self, InviteKeyBundleError> {
175 let key_id =
176 ctx.make_symmetric_key(bitwarden_crypto::SymmetricKeyAlgorithm::XChaCha20Poly1305);
177
178 #[allow(deprecated)]
179 let raw_key_data = InviteKeyData(
180 ctx.dangerous_get_symmetric_key(key_id)
181 .map_err(|_| InviteKeyBundleError::MissingKeyId(format!("{key_id:?}")))?
182 .clone(),
183 );
184
185 let sealed_key_envelope = InviteKeyEnvelope(
186 ctx.wrap_symmetric_key(organization_key, key_id)
187 .map_err(|_| InviteKeyBundleError::KeySealingFailed)?,
188 );
189
190 Ok(Self {
191 raw_key_data,
192 sealed_key_envelope,
193 })
194 }
195
196 pub fn dangerous_get_raw_invite_key(&self) -> &InviteKeyData {
205 &self.raw_key_data
206 }
207
208 pub fn get_sealed_invite_key_envelope(&self) -> &InviteKeyEnvelope {
210 &self.sealed_key_envelope
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use bitwarden_crypto::{BitwardenLegacyKeyBytes, KeyStore, SymmetricCryptoKey, key_slot_ids};
217 use bitwarden_encoding::B64Url;
218
219 use crate::invite_key_bundle::{InviteKeyBundle, InviteKeyData};
220
221 #[test]
222 fn test_basic_invitation_envelope_bundle() {
223 let key_store = KeyStore::<TestIds>::default();
224 let mut ctx = key_store.context_mut();
225
226 let local_org_key_id = ctx.generate_symmetric_key();
227 ctx.persist_symmetric_key(local_org_key_id, TestSymmKey::Organization)
228 .unwrap();
229
230 let key1 = InviteKeyBundle::make(TestSymmKey::Organization, &mut ctx).unwrap();
231 let key2 = InviteKeyBundle::make(TestSymmKey::Organization, &mut ctx).unwrap();
232
233 assert_ne!(key1.raw_key_data.0, key2.raw_key_data.0);
234 }
235
236 #[test]
237 fn test_envelope_unseals_to_raw_bytes() {
238 let key_store = KeyStore::<TestIds>::default();
239 let mut ctx = key_store.context_mut();
240
241 let local_org_key_id = ctx.generate_symmetric_key();
242 ctx.persist_symmetric_key(local_org_key_id, TestSymmKey::Organization)
243 .unwrap();
244
245 let key = InviteKeyBundle::make(TestSymmKey::Organization, &mut ctx).unwrap();
246
247 let unsealed_key = ctx
248 .unwrap_symmetric_key(TestSymmKey::Organization, &key.sealed_key_envelope.0)
249 .unwrap();
250
251 #[allow(deprecated)]
252 let unsealed_key = ctx
253 .dangerous_get_symmetric_key(unsealed_key)
254 .unwrap()
255 .clone();
256
257 assert_eq!(key.dangerous_get_raw_invite_key().0, unsealed_key);
258 }
259
260 #[test]
261 fn test_envelope_unseals_to_same_key_as_raw_data() {
262 let key_store = KeyStore::<TestIds>::default();
263 let mut ctx = key_store.context_mut();
264
265 let local_org_key_id = ctx.generate_symmetric_key();
266 ctx.persist_symmetric_key(local_org_key_id, TestSymmKey::Organization)
267 .unwrap();
268
269 let key = InviteKeyBundle::make(TestSymmKey::Organization, &mut ctx).unwrap();
270
271 let raw_key_id = ctx.add_local_symmetric_key(key.dangerous_get_raw_invite_key().0.clone());
272
273 let unsealed_key = ctx
274 .unwrap_symmetric_key(TestSymmKey::Organization, &key.sealed_key_envelope.0)
275 .unwrap();
276
277 ctx.assert_symmetric_keys_equal(raw_key_id, unsealed_key);
278 }
279
280 #[test]
281 fn test_envelope_round_trip_unseals_to_key_data() {
282 let key_store = KeyStore::<TestIds>::default();
283 let mut ctx = key_store.context_mut();
284
285 let local_org_key_id = ctx.generate_symmetric_key();
286 ctx.persist_symmetric_key(local_org_key_id, TestSymmKey::Organization)
287 .unwrap();
288
289 let key_bundle = InviteKeyBundle::make(TestSymmKey::Organization, &mut ctx).unwrap();
290 let raw_key_data = key_bundle.dangerous_get_raw_invite_key();
291 let sealed_key_envelope = key_bundle.get_sealed_invite_key_envelope();
292 let unsealed_raw_key_data = sealed_key_envelope
293 .unseal(TestSymmKey::Organization, &mut ctx)
294 .unwrap();
295
296 assert_eq!(raw_key_data, &unsealed_raw_key_data);
297
298 let internal_unwrapped_key_id = ctx
299 .unwrap_symmetric_key(TestSymmKey::Organization, &key_bundle.sealed_key_envelope.0)
300 .unwrap();
301
302 #[allow(deprecated)]
303 let internal_unwrapped_key = ctx
304 .dangerous_get_symmetric_key(internal_unwrapped_key_id)
305 .unwrap()
306 .clone();
307
308 let b64url_encoded_unsealed_key =
309 B64Url::from(internal_unwrapped_key.to_encoded().as_ref());
310
311 assert_eq!(
312 String::from(&unsealed_raw_key_data),
313 b64url_encoded_unsealed_key.to_string()
314 );
315 }
316
317 #[test]
318 fn test_into_base64_url() {
319 let data = b"+/=Hello, World!AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
320 let key =
321 SymmetricCryptoKey::try_from(&BitwardenLegacyKeyBytes::from(data.to_vec())).unwrap();
322
323 let encoded = String::from(&InviteKeyData(key));
324
325 assert_eq!(
326 encoded,
327 "Ky89SGVsbG8sIFdvcmxkIUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQQ"
328 );
329 assert!(!encoded.contains('+'));
330 assert!(!encoded.contains('/'));
331 assert!(!encoded.contains('='));
332
333 let decoded = B64Url::try_from(encoded.as_str()).unwrap();
334 assert_eq!(decoded.as_bytes(), data);
335 }
336
337 key_slot_ids! {
338 #[symmetric]
339 pub enum TestSymmKey {
340 Organization,
341 #[local]
342 Local(LocalId),
343 }
344
345 #[private]
346 pub enum TestPrivateKey {
347 A(u8),
348 B,
349 #[local]
350 C(LocalId),
351 }
352
353 #[signing]
354 pub enum TestSigningKey {
355 A(u8),
356 B,
357 #[local]
358 C(LocalId),
359 }
360
361 pub TestIds => TestSymmKey, TestPrivateKey, TestSigningKey;
362 }
363}