Skip to main content

bitwarden_organization_crypto/
organization_shared_key.rs

1//! Organization shared key: the organization key shared with one member, so that the member can
2//! decrypt the organization's data.
3//!
4//! The API names this value the membership's `key`.
5//!
6//! # Versions
7//!
8//! - V1: the organization key encapsulated to the member's public key. Nothing authenticates the
9//!   sender, so the member cannot tell that the organization made it.
10//! - V2 (planned): the organization key sealed from the organization's identity to the member's
11//!   identity in an identity-sealed key envelope, which authenticates the organization as the
12//!   sender.
13//!
14//! [`OrganizationSharedKey::make`] takes the [`TrustProof`] for the member's public key: the
15//! fingerprint phrase that an admin compared, or the member's own private key when the member
16//! shares the key with themselves (creating an organization, or self-confirming through an invite
17//! link). Some V1 flows have no way to verify the public key, and use
18//! [`OrganizationSharedKey::make_untrusted`]. V2 has no untrusted variant.
19
20use std::str::FromStr;
21
22use bitwarden_crypto::{CryptoError, KeySlotIds, KeyStoreContext, PublicKey, UnsignedSharedKey};
23use thiserror::Error;
24
25use crate::trust_proof::{TrustProof, TrustProofError};
26
27/// Errors that can occur when parsing an organization shared key.
28#[derive(Debug, Error)]
29pub enum OrganizationSharedKeyParseError {
30    /// The value stored for the membership is not in a known organization shared key format.
31    #[error("The organization shared key is malformed")]
32    Malformed,
33}
34
35/// Errors that can occur when making an organization shared key with a trust proof.
36#[derive(Debug, Error, PartialEq)]
37pub enum OrganizationSharedKeyMakeError {
38    /// The private key of
39    /// [`CryptographicProof::OwnPrivateKey`](crate::trust_proof::CryptographicProof::OwnPrivateKey)
40    /// is not in the key store context.
41    #[error("The private key of the trust proof is not in the key store context")]
42    PrivateKeyNotInContext,
43    /// The organization key is not in the key store context.
44    #[error("The organization key could not be encapsulated to the member's public key")]
45    EncapsulationFailed,
46    /// The public key did not pass the trust proof.
47    #[error("The member's public key could not be trusted")]
48    UntrustedPublicKey,
49}
50
51impl From<TrustProofError> for OrganizationSharedKeyMakeError {
52    fn from(error: TrustProofError) -> Self {
53        match error {
54            TrustProofError::PrivateKeyNotInContext => Self::PrivateKeyNotInContext,
55            TrustProofError::UntrustedPublicKey => Self::UntrustedPublicKey,
56        }
57    }
58}
59
60/// Errors that can occur when making an organization shared key without a trust proof.
61#[derive(Debug, Error)]
62pub enum OrganizationSharedKeyMakeUntrustedError {
63    /// The organization key is not in the key store context.
64    #[error("The organization key could not be encapsulated to the member's public key")]
65    EncapsulationFailed,
66}
67
68/// Errors that can occur when unsealing an organization shared key.
69#[derive(Debug, Error)]
70pub enum OrganizationSharedKeyUnsealError {
71    /// The organization shared key was made for a different key pair, or the stored value is
72    /// corrupt. V1 cannot tell these apart.
73    #[error("The organization shared key could not be decapsulated with the member's private key")]
74    DecapsulationFailed,
75    /// The member's private key is not in the key store context.
76    #[error("The member's private key does not exist in the key store context")]
77    PrivateKeyNotInContext,
78}
79
80/// The organization key, shared with one member.
81pub enum OrganizationSharedKey {
82    /// The organization key encapsulated to the member's public key.
83    ///
84    /// Encapsulation does not authenticate the sender. Anyone who knows the member's public key can
85    /// make one, and a swapped public key hands the organization key to whoever holds the
86    /// matching private key.
87    V1(UnsignedSharedKey),
88}
89
90impl OrganizationSharedKey {
91    /// Makes an organization shared key for a member, after the member's public key passes
92    /// `trust_proof`.
93    pub fn make<Ids: KeySlotIds>(
94        organization_key: Ids::Symmetric,
95        member_public_key: &PublicKey,
96        trust_proof: &TrustProof<Ids>,
97        ctx: &KeyStoreContext<Ids>,
98    ) -> Result<Self, OrganizationSharedKeyMakeError> {
99        trust_proof.verify(member_public_key, ctx)?;
100
101        let encapsulated_key =
102            UnsignedSharedKey::encapsulate(organization_key, member_public_key, ctx)
103                .map_err(|_| OrganizationSharedKeyMakeError::EncapsulationFailed)?;
104
105        Ok(Self::V1(encapsulated_key))
106    }
107
108    /// Makes a V1 organization shared key for a member, without verifying the member's public
109    /// key.
110    ///
111    /// Only for V1 flows that have no way to verify the public key. A swapped public key hands the
112    /// organization key to whoever holds the matching private key.
113    pub fn make_untrusted<Ids: KeySlotIds>(
114        organization_key: Ids::Symmetric,
115        member_public_key: &PublicKey,
116        ctx: &KeyStoreContext<Ids>,
117    ) -> Result<Self, OrganizationSharedKeyMakeUntrustedError> {
118        let encapsulated_key =
119            UnsignedSharedKey::encapsulate(organization_key, member_public_key, ctx)
120                .map_err(|_| OrganizationSharedKeyMakeUntrustedError::EncapsulationFailed)?;
121
122        Ok(Self::V1(encapsulated_key))
123    }
124
125    /// Opens the organization key with the member's own private key, returning the id it was added
126    /// to the context under.
127    pub fn unseal<Ids: KeySlotIds>(
128        &self,
129        own_private_key: Ids::Private,
130        ctx: &mut KeyStoreContext<Ids>,
131    ) -> Result<Ids::Symmetric, OrganizationSharedKeyUnsealError> {
132        let Self::V1(encapsulated_key) = self;
133
134        encapsulated_key
135            .decapsulate(own_private_key, ctx)
136            .map_err(|error| match error {
137                CryptoError::MissingKeyId(_) => {
138                    OrganizationSharedKeyUnsealError::PrivateKeyNotInContext
139                }
140                _ => OrganizationSharedKeyUnsealError::DecapsulationFailed,
141            })
142    }
143}
144
145impl FromStr for OrganizationSharedKey {
146    type Err = OrganizationSharedKeyParseError;
147
148    fn from_str(value: &str) -> Result<Self, Self::Err> {
149        value
150            .parse()
151            .map(Self::V1)
152            .map_err(|_| OrganizationSharedKeyParseError::Malformed)
153    }
154}
155
156impl From<&OrganizationSharedKey> for String {
157    fn from(value: &OrganizationSharedKey) -> Self {
158        let OrganizationSharedKey::V1(encapsulated_key) = value;
159
160        encapsulated_key.to_string()
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use bitwarden_crypto::{
167        EncString, KeyStore, PublicKeyEncryptionAlgorithm, SymmetricCryptoKey,
168        SymmetricKeyAlgorithm::Aes256CbcHmac,
169    };
170
171    use super::*;
172    use crate::test_support::{
173        TEST_VECTOR_V1_MEMBER_USER_KEY, TEST_VECTOR_V1_ORG_KEY, TestIds, TestPrivateKey,
174        add_symmetric_key,
175    };
176
177    /// The member private key, wrapped with [`TEST_VECTOR_V1_MEMBER_USER_KEY`].
178    const TEST_VECTOR_MEMBER_WRAPPED_PRIVATE_KEY: &str = "2.Fk2Qw03uGzzqZ64D3yKYEw==|VFfJlJ9/bMF9usPNP1Czzpc57hS/mVublKvpUsgxbWohkunlBxz1lVwOknAxtVprt9a4W9rKFjT+xDY1ImpQijt+MgGRAsXqURlGWY2W7CUs7tN9f5M598R5SXopVXhIYv8uVvMPv7zMEa6sl2p0XvMNkjn/02h2UHm1R9Wpnvb8MeGkXVBwGVfP53FaBR8iHsDQu7pR6MP8nYqRUg9+ORbqId453BpUycJt6TD59JEEUOG90T57UQRWIBOPmjtN8eF/EaVMK9fzYwCbKqa187x7B7BCuqKXSzjfFdSt+xNLmZMPRRB1DZB9NvabsQF87175wsnHOFqrcTXhAORvVpse1EpVSWR+u8K3p9chqjzlGCthb72OEET1hV1MSYWv1osDnqhNl981zz9oDToRk3ZEhktZyA7Uox9g2Abuf3xVpkCCO/bb3rcpTmmhk5BP3npfluUyEbpCRNcyi1kYpAF6vtY+UYaAv4XB25Dcp5td/c2yrlIUJeM17GFCm/TQGU7kMo7S3CFcM9nmFFG60aM8eQiwF1HQZV9cMPLNiNFwDFdmUcv+m/eZ/4ZZM8WJv4raevjoFUPgb8saqOSLKYGisjGoyJ7Z9T1eZisyALEfgIVbsJrINWjjW1pVxDNMiatn8ZDA4S90rUYl2eejNie+2e+41KAYl3JSZlkqr636A2gkLzToufXpX+n655hz9yNMhv5TnfenzTaMDg1XCETqiixKQBxV8m4VsJbGBIzNVzghoJUuf8xZ90FmhWeH6z8It6f/uVwlnBLG1m690AWJXVtMmEejrwJAcmJKYntFHtZI54SUJfXyW33vaQ7tQbQ699BqU9nGQcTnDbTmdA2iyDLuJ2n06B8PYK4odSfCQJrOWHsdZpRd7P1lDp7ksxejqafJJfXsD0BvoUbw8CGe5eGotGlyv85poSduLeURqyai6y4eW0UClXJJiaUXMlXNbzrTCSeWb6aWJTxQfW/fQ0QNjIqDOB5B4xByh/mzESOiyTEnNV61Ln2HOGlpfkhARLpgGdotYvZtFQRrApqvWwRtIerP2gZfpubAN29J5lW2DHAkd6fvEFokjQ4wofqymwNxVVd5WBjueQ1RbFdo1LMIiKApEMTpQ5E4eipmtDc6foFJo26FIuXvHduKOBZm0rYfd8Iw5EgQMXyKaUE1KxU9HHdR4BFzT9JEacNPkdF96DSbV/l6lVADDc0xfRe7AAXAmPxRlHDMw4HGlTbOXlhNZQtkX7mUMyVheRudwsmOvH/BvbJajn1E+xYy4r1GdTbvJRvzAjhK1Xdniq4bAD1brVPdnkk7yhTEHT1GnGEMn3kPtBhuBIKhNSwYZdG88NcwDzwDhrssU77jSIB72RjtOpO4nlMnqmjGpwFakJmLgaonYgIArIBqaJQ7Kt/JfIUGiARkO6IVF3WF8cL5/GUI7KpyZATbBIX4bfuJXBlmEPBneKduLCt+svBW1XVctOwArsYfypCsQojmMRNQkHC8EgCumQxc8Wutl7jXh8OwIXDHZjQ6UJw8XK2vmuS6oBRQg9SwzeQlIbsz1sQRpuAx/IY7TwTZOOi0bp5KdnZzSCx6SbUP7ZDdbGQb4ZH3GCXkOgvK0MsH5GEUjnEa8afSJGh/hEulDEsFE5g=|HA79iaX6imq4ipukZLyw6Nd1PhIT7/pCInWPmgr4YKA=";
179    /// [`TEST_VECTOR_V1_ORG_KEY`] encapsulated to the pinned member public key.
180    const TEST_VECTOR_ORGANIZATION_SHARED_KEY: &str = "4.HtHhO4krfvZnoJRJFkXpf9BLLyrOO0ovfxTsmt0GNKkT7clpMVNR0W7B8VOEiautOVW6zAMDVzO2ZlFhrVR6kt5ATXL1KCqjipHBI2u/6Ww3Dh2jlUZuRTFcbZ4bYN3sOVQPkdwFaM1H++SzauUWP7pmzSc+u4sQIUKAB/KJ5OSkKn3tFYZuwUycB+wgcA4Ew8qU3c46lCbKqXY8dI3Z1ttvC5vO81cM4pryNGoHnaI7ONeEIuGIA5py8/WP58R6mQhyQWwC2uq77caFRX8GLGJd3c3yYfH1em5NvF8BZzZ1TZ/Nd+6fS4T8awUjs4IxkxJw6eU6Wn8+y4tUM9gAxw==";
181
182    /// Unwraps the member private key with the member user key.
183    fn load_member_private_key(ctx: &mut KeyStoreContext<'_, TestIds>) -> TestPrivateKey {
184        let member_user_key = add_symmetric_key(TEST_VECTOR_V1_MEMBER_USER_KEY, ctx);
185        let wrapped_private_key: EncString =
186            TEST_VECTOR_MEMBER_WRAPPED_PRIVATE_KEY.parse().unwrap();
187        ctx.unwrap_private_key(member_user_key, &wrapped_private_key)
188            .unwrap()
189    }
190
191    #[test]
192    fn test_organization_shared_key_unseals_to_the_organization_key() {
193        let key_store = KeyStore::<TestIds>::default();
194        let mut ctx = key_store.context_mut();
195        let organization_key = ctx.make_symmetric_key(Aes256CbcHmac);
196        let own_private_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
197        let own_public_key = ctx.get_public_key(own_private_key).unwrap();
198
199        let shared_key = OrganizationSharedKey::make(
200            organization_key,
201            &own_public_key,
202            &TrustProof::TestStub(Ok(())),
203            &ctx,
204        )
205        .unwrap();
206        let unsealed = shared_key.unseal(own_private_key, &mut ctx).unwrap();
207
208        ctx.assert_symmetric_keys_equal(organization_key, unsealed);
209    }
210
211    #[test]
212    fn test_untrusted_organization_shared_key_unseals_to_the_organization_key() {
213        let key_store = KeyStore::<TestIds>::default();
214        let mut ctx = key_store.context_mut();
215        let organization_key = ctx.make_symmetric_key(Aes256CbcHmac);
216        let member_private_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
217        let member_public_key = ctx.get_public_key(member_private_key).unwrap();
218
219        let shared_key =
220            OrganizationSharedKey::make_untrusted(organization_key, &member_public_key, &ctx)
221                .unwrap();
222        let unsealed = shared_key.unseal(member_private_key, &mut ctx).unwrap();
223
224        ctx.assert_symmetric_keys_equal(organization_key, unsealed);
225    }
226
227    #[test]
228    fn test_organization_shared_key_survives_a_string_round_trip() {
229        let key_store = KeyStore::<TestIds>::default();
230        let mut ctx = key_store.context_mut();
231        let organization_key = ctx.make_symmetric_key(Aes256CbcHmac);
232        let own_private_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
233        let own_public_key = ctx.get_public_key(own_private_key).unwrap();
234        let shared_key = OrganizationSharedKey::make(
235            organization_key,
236            &own_public_key,
237            &TrustProof::TestStub(Ok(())),
238            &ctx,
239        )
240        .unwrap();
241
242        let parsed: OrganizationSharedKey = String::from(&shared_key).parse().unwrap();
243
244        let unsealed = parsed.unseal(own_private_key, &mut ctx).unwrap();
245        ctx.assert_symmetric_keys_equal(organization_key, unsealed);
246    }
247
248    #[test]
249    fn test_parsing_a_key_in_no_known_format_fails() {
250        let result: Result<OrganizationSharedKey, _> = "not an encapsulated key".parse();
251
252        assert!(matches!(
253            result,
254            Err(OrganizationSharedKeyParseError::Malformed)
255        ));
256    }
257
258    #[test]
259    fn test_making_with_a_failed_trust_proof_fails_with_the_matching_error() {
260        let key_store = KeyStore::<TestIds>::default();
261        let mut ctx = key_store.context_mut();
262        let organization_key = ctx.make_symmetric_key(Aes256CbcHmac);
263        let member_private_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
264        let member_public_key = ctx.get_public_key(member_private_key).unwrap();
265
266        for (trust_proof_error, expected_error) in [
267            (
268                TrustProofError::PrivateKeyNotInContext,
269                OrganizationSharedKeyMakeError::PrivateKeyNotInContext,
270            ),
271            (
272                TrustProofError::UntrustedPublicKey,
273                OrganizationSharedKeyMakeError::UntrustedPublicKey,
274            ),
275        ] {
276            let result = OrganizationSharedKey::make(
277                organization_key,
278                &member_public_key,
279                &TrustProof::TestStub(Err(trust_proof_error)),
280                &ctx,
281            );
282
283            assert_eq!(result.err(), Some(expected_error));
284        }
285    }
286
287    #[test]
288    fn test_making_with_a_missing_organization_key_fails() {
289        let key_store = KeyStore::<TestIds>::default();
290        let mut ctx = key_store.context_mut();
291        let own_private_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
292        let own_public_key = ctx.get_public_key(own_private_key).unwrap();
293        let other_key_store = KeyStore::<TestIds>::default();
294        let mut other_ctx = other_key_store.context_mut();
295        let missing_organization_key = other_ctx.make_symmetric_key(Aes256CbcHmac);
296
297        let result = OrganizationSharedKey::make(
298            missing_organization_key,
299            &own_public_key,
300            &TrustProof::TestStub(Ok(())),
301            &ctx,
302        );
303
304        assert!(matches!(
305            result,
306            Err(OrganizationSharedKeyMakeError::EncapsulationFailed)
307        ));
308    }
309
310    #[test]
311    fn test_making_untrusted_with_a_missing_organization_key_fails() {
312        let key_store = KeyStore::<TestIds>::default();
313        let mut ctx = key_store.context_mut();
314        let member_private_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
315        let member_public_key = ctx.get_public_key(member_private_key).unwrap();
316        let other_key_store = KeyStore::<TestIds>::default();
317        let mut other_ctx = other_key_store.context_mut();
318        let missing_organization_key = other_ctx.make_symmetric_key(Aes256CbcHmac);
319
320        let result = OrganizationSharedKey::make_untrusted(
321            missing_organization_key,
322            &member_public_key,
323            &ctx,
324        );
325
326        assert!(matches!(
327            result,
328            Err(OrganizationSharedKeyMakeUntrustedError::EncapsulationFailed)
329        ));
330    }
331
332    #[test]
333    fn test_unsealing_with_another_private_key_fails() {
334        let key_store = KeyStore::<TestIds>::default();
335        let mut ctx = key_store.context_mut();
336        let organization_key = ctx.make_symmetric_key(Aes256CbcHmac);
337        let member_private_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
338        let member_public_key = ctx.get_public_key(member_private_key).unwrap();
339        let other_private_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
340        let shared_key =
341            OrganizationSharedKey::make_untrusted(organization_key, &member_public_key, &ctx)
342                .unwrap();
343
344        let result = shared_key.unseal(other_private_key, &mut ctx);
345
346        assert!(matches!(
347            result,
348            Err(OrganizationSharedKeyUnsealError::DecapsulationFailed)
349        ));
350    }
351
352    #[test]
353    fn test_unsealing_with_a_private_key_from_another_context_fails() {
354        let key_store = KeyStore::<TestIds>::default();
355        let mut ctx = key_store.context_mut();
356        let organization_key = ctx.make_symmetric_key(Aes256CbcHmac);
357        let member_private_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
358        let member_public_key = ctx.get_public_key(member_private_key).unwrap();
359        let shared_key =
360            OrganizationSharedKey::make_untrusted(organization_key, &member_public_key, &ctx)
361                .unwrap();
362        let other_key_store = KeyStore::<TestIds>::default();
363        let mut other_ctx = other_key_store.context_mut();
364
365        let result = shared_key.unseal(member_private_key, &mut other_ctx);
366
367        assert!(matches!(
368            result,
369            Err(OrganizationSharedKeyUnsealError::PrivateKeyNotInContext)
370        ));
371    }
372
373    #[test]
374    #[ignore = "Manual test to generate test vectors"]
375    fn generate_test_vectors() {
376        let key_store = KeyStore::<TestIds>::default();
377        let mut ctx = key_store.context_mut();
378        let member_user_key = SymmetricCryptoKey::make(Aes256CbcHmac);
379        let member_user_key_b64 = member_user_key.to_base64();
380        let member_user_key = ctx.add_local_symmetric_key(member_user_key);
381        let member_private_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
382        let wrapped_member_private_key = ctx
383            .wrap_private_key(member_user_key, member_private_key)
384            .unwrap();
385        let member_public_key = ctx.get_public_key(member_private_key).unwrap();
386        let organization_key = add_symmetric_key(TEST_VECTOR_V1_ORG_KEY, &mut ctx);
387
388        let shared_key =
389            OrganizationSharedKey::make_untrusted(organization_key, &member_public_key, &ctx)
390                .unwrap();
391
392        println!("const TEST_VECTOR_V1_MEMBER_USER_KEY: &str = \"{member_user_key_b64}\";");
393        println!(
394            "const TEST_VECTOR_MEMBER_WRAPPED_PRIVATE_KEY: &str = \"{}\";",
395            wrapped_member_private_key.to_string()
396        );
397        println!(
398            "const TEST_VECTOR_ORGANIZATION_SHARED_KEY: &str = \"{}\";",
399            String::from(&shared_key)
400        );
401    }
402
403    #[test]
404    fn test_organization_shared_key_test_vector() {
405        let key_store = KeyStore::<TestIds>::default();
406        let mut ctx = key_store.context_mut();
407        let member_private_key = load_member_private_key(&mut ctx);
408        let organization_key = add_symmetric_key(TEST_VECTOR_V1_ORG_KEY, &mut ctx);
409
410        let shared_key: OrganizationSharedKey =
411            TEST_VECTOR_ORGANIZATION_SHARED_KEY.parse().unwrap();
412        let unsealed = shared_key.unseal(member_private_key, &mut ctx).unwrap();
413
414        ctx.assert_symmetric_keys_equal(organization_key, unsealed);
415    }
416}