Skip to main content

bitwarden_user_crypto_management/key_rotation/
partial_rotateable_keyset.rs

1use bitwarden_api_api::models::{
2    OtherDeviceKeysUpdateRequestModel, WebAuthnLoginRotateKeyRequestModel,
3};
4#[cfg(test)]
5use bitwarden_core::key_management::PrivateKeyId;
6use bitwarden_core::key_management::{KeyIds, SymmetricKeyId};
7use bitwarden_crypto::{
8    Decryptable, EncString, KeyStoreContext, PrimitiveEncryptable, PublicKey, UnsignedSharedKey,
9};
10use tracing::instrument;
11
12/// A version of a rotateable keyset, missing the upstream-key-encrypted-private-key.
13/// This can only be used to re-share the downstream-key (in this case the user-key) with the
14/// key-set.
15pub(super) struct PartialRotateableKeyset {
16    pub(super) id: uuid::Uuid,
17    pub(super) encrypted_public_key: EncString,
18    pub(super) encrypted_user_key: UnsignedSharedKey,
19}
20
21impl From<PartialRotateableKeyset> for OtherDeviceKeysUpdateRequestModel {
22    fn from(val: PartialRotateableKeyset) -> Self {
23        OtherDeviceKeysUpdateRequestModel {
24            device_id: val.id,
25            encrypted_public_key: val.encrypted_public_key.to_string(),
26            encrypted_user_key: val.encrypted_user_key.to_string(),
27        }
28    }
29}
30
31impl From<PartialRotateableKeyset> for WebAuthnLoginRotateKeyRequestModel {
32    fn from(val: PartialRotateableKeyset) -> Self {
33        WebAuthnLoginRotateKeyRequestModel {
34            id: val.id,
35            encrypted_public_key: val.encrypted_public_key.to_string(),
36            encrypted_user_key: val.encrypted_user_key.to_string(),
37        }
38    }
39}
40
41impl PartialRotateableKeyset {
42    /// Makes a new `PartialRotateableKeyset` by re-encrypting the user-key. Specifically,
43    /// the user-key-encrypted-public-key is re-encrypted for the new user-key, and the
44    /// public-key-encrypted-user-key is re-created for the new user-key.
45    #[instrument(skip(self, ctx))]
46    pub(super) fn rotate_userkey(
47        &self,
48        current_user_key_id: SymmetricKeyId,
49        new_user_key_id: SymmetricKeyId,
50        ctx: &mut KeyStoreContext<KeyIds>,
51    ) -> Result<PartialRotateableKeyset, ()> {
52        let pubkey_bytes: Vec<u8> = self
53            .encrypted_public_key
54            .decrypt(ctx, current_user_key_id)
55            .map_err(|_| ())?;
56        let pubkey_der =
57            bitwarden_crypto::Bytes::<bitwarden_crypto::SpkiPublicKeyDerContentFormat>::from(
58                pubkey_bytes,
59            );
60        let pubkey = PublicKey::from_der(&pubkey_der).map_err(|_| ())?;
61        let reencrypted_user_key =
62            UnsignedSharedKey::encapsulate(new_user_key_id, &pubkey, ctx).map_err(|_| ())?;
63        let reencrypted_public_key = pubkey_der.encrypt(ctx, new_user_key_id).map_err(|_| ())?;
64        Ok(PartialRotateableKeyset {
65            id: self.id,
66            encrypted_public_key: reencrypted_public_key,
67            encrypted_user_key: reencrypted_user_key,
68        })
69    }
70
71    /// Makes a test `PartialRotateableKeyset` for the given downstream key.
72    /// The private key is stored on the context since it is no present on the partial keyset.
73    #[cfg(test)]
74    pub(crate) fn make_test_keyset(
75        downstream_key_id: SymmetricKeyId,
76        ctx: &mut KeyStoreContext<KeyIds>,
77    ) -> (Self, PrivateKeyId) {
78        use bitwarden_crypto::PublicKeyEncryptionAlgorithm;
79
80        let private_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
81        let pubkey_der = ctx.get_public_key(private_key).unwrap().to_der().unwrap();
82        let encrypted_public_key = pubkey_der.encrypt(ctx, downstream_key_id).unwrap();
83        let encrypted_user_key = UnsignedSharedKey::encapsulate(
84            downstream_key_id,
85            &ctx.get_public_key(private_key).unwrap(),
86            ctx,
87        )
88        .unwrap();
89        (
90            PartialRotateableKeyset {
91                id: uuid::Uuid::new_v4(),
92                encrypted_public_key,
93                encrypted_user_key,
94            },
95            private_key,
96        )
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use bitwarden_core::key_management::KeyIds;
103    use bitwarden_crypto::{Bytes, KeyStore, SpkiPublicKeyDerContentFormat};
104
105    use super::*;
106
107    #[test]
108    fn test_keyset_reencrypt() {
109        let store: KeyStore<KeyIds> = KeyStore::default();
110        let mut ctx = store.context_mut();
111
112        // Generate two symmetric keys, old and new
113        let key_id_1 = ctx.generate_symmetric_key();
114        let key_id_2 = ctx.generate_symmetric_key();
115
116        // Generate an asymmetric key pair and encapsulate a symmetric key
117        let (test_keyset, private_key) =
118            PartialRotateableKeyset::make_test_keyset(key_id_1, &mut ctx);
119        let pubkey_der = {
120            let decrypted_pubkey_bytes: Vec<u8> = test_keyset
121                .encrypted_public_key
122                .decrypt(&mut ctx, key_id_1)
123                .expect("decryption should succeed");
124            Bytes::<SpkiPublicKeyDerContentFormat>::from(decrypted_pubkey_bytes)
125        };
126
127        // Re-encrypt the keyset with the new key
128        let reencrypted = test_keyset
129            .rotate_userkey(key_id_1, key_id_2, &mut ctx)
130            .expect("reencrypt should succeed");
131
132        // Check that the re-encrypted user key can be decapsulated and is the same symmetric key
133        let decapsulated = {
134            let decapsulated = reencrypted
135                .encrypted_user_key
136                .decapsulate(private_key, &mut ctx)
137                .expect("decapsulation should succeed");
138            #[expect(deprecated)]
139            ctx.dangerous_get_symmetric_key(decapsulated)
140                .expect("key should exist")
141                .to_owned()
142        };
143
144        let key_2 = {
145            #[expect(deprecated)]
146            ctx.dangerous_get_symmetric_key(key_id_2)
147                .expect("key should exist")
148                .to_owned()
149        };
150        assert_eq!(decapsulated, key_2);
151
152        // Check that the re-encyrpted public-key can be decrypted with the new key
153        let decrypted_pubkey_bytes: Vec<u8> = reencrypted
154            .encrypted_public_key
155            .decrypt(&mut ctx, key_id_2)
156            .expect("decryption should succeed");
157        assert_eq!(decrypted_pubkey_bytes, pubkey_der.as_ref());
158    }
159}