Skip to main content

bitwarden_send/
rotate.rs

1#[cfg(feature = "wasm")]
2use bitwarden_core::key_management::KeySlotIds;
3#[cfg(feature = "wasm")]
4use bitwarden_crypto::{CompositeEncryptable, KeyStore, SymmetricCryptoKey};
5#[cfg(feature = "wasm")]
6use bitwarden_encoding::B64;
7#[cfg(feature = "wasm")]
8use wasm_bindgen::prelude::*;
9
10#[cfg(feature = "wasm")]
11use crate::{Send, SendView, send_client::SendClient, send_client::SendEncryptError};
12
13/// Re-wraps a send's per-item key under `new_key`; the send's content stays under its own
14/// unchanged key.
15#[cfg(feature = "wasm")]
16fn encrypt_send_for_rotation(
17    key_store: &KeyStore<KeySlotIds>,
18    send_view: SendView,
19    new_key: B64,
20) -> Result<Send, SendEncryptError> {
21    let new_key = SymmetricCryptoKey::try_from(new_key)?;
22
23    let mut ctx = key_store.context();
24
25    // `Local` slot, not the view's natural `User` slot — passed explicitly to `encrypt_composite`.
26    let new_key_id = ctx.add_local_symmetric_key(new_key);
27
28    Ok(send_view.encrypt_composite(&mut ctx, new_key_id)?)
29}
30
31#[cfg(feature = "wasm")]
32#[wasm_bindgen]
33impl SendClient {
34    /// Encrypt a send with the provided key. This should only be used when rotating encryption
35    /// keys in the Web client.
36    ///
37    /// Until Typescript based key-rotation is removed after completing the rollout of sdk-based
38    /// key-rotation, this method must be provided the new symmetric key in base64 format. See
39    /// PM-23084
40    // `async` mirrors `encrypt_cipher_for_rotation`; this rotation does no async work itself.
41    #[allow(clippy::unused_async)]
42    pub async fn encrypt_send_for_rotation(
43        &self,
44        send_view: SendView,
45        new_key: B64,
46    ) -> Result<Send, SendEncryptError> {
47        let key_store = self.client.internal.get_key_store();
48
49        encrypt_send_for_rotation(key_store, send_view, new_key)
50    }
51}
52
53#[cfg(test)]
54#[cfg(feature = "wasm")]
55mod tests {
56    use bitwarden_core::key_management::create_test_crypto_with_user_key;
57    use bitwarden_crypto::{CryptoError, SymmetricCryptoKey, SymmetricKeyAlgorithm};
58
59    use super::*;
60    use crate::{AuthType, SendTextView, SendType};
61
62    fn test_send_view() -> SendView {
63        SendView {
64            id: "3d80dd72-2d14-4f26-812c-b0f0018aa144".parse().ok(),
65            access_id: Some("ct2APRQtJk-BLLDwAYqhRA".to_owned()),
66            name: "Test".to_string(),
67            notes: None,
68            key: Some("Pgui0FK85cNhBGWHAlBHBw".to_owned()),
69            new_password: None,
70            has_password: false,
71            r#type: SendType::Text,
72            file: None,
73            text: Some(SendTextView {
74                text: Some("This is a test".to_owned()),
75                hidden: false,
76            }),
77            data: None,
78            max_access_count: None,
79            access_count: 0,
80            disabled: false,
81            hide_email: false,
82            revision_date: "2024-01-07T23:56:48.207363Z".parse().unwrap(),
83            deletion_date: "2024-01-14T23:56:48Z".parse().unwrap(),
84            expiration_date: None,
85            emails: Vec::new(),
86            auth_type: AuthType::None,
87        }
88    }
89
90    #[test]
91    fn test_encrypt_send_for_rotation() {
92        let user_key = SymmetricCryptoKey::make(SymmetricKeyAlgorithm::Aes256CbcHmac);
93        let store = create_test_crypto_with_user_key(user_key);
94
95        let new_key = SymmetricCryptoKey::make(SymmetricKeyAlgorithm::Aes256CbcHmac);
96        let new_key_b64 = new_key.to_base64();
97
98        let send_view = test_send_view();
99
100        let rotated = encrypt_send_for_rotation(&store, send_view.clone(), new_key_b64).unwrap();
101
102        // Round-trips under the new key.
103        let new_store = create_test_crypto_with_user_key(new_key);
104        let decrypted: SendView = new_store.decrypt(&rotated).unwrap();
105        assert_eq!(decrypted.name, send_view.name);
106        assert_eq!(decrypted.text, send_view.text);
107
108        // Fails under the old key, since the send key was re-wrapped.
109        assert!(matches!(
110            store.decrypt::<_, Send, SendView>(&rotated).err(),
111            Some(CryptoError::Decrypt)
112        ));
113    }
114}