bitwarden_user_crypto_management/key_rotation/
partial_rotateable_keyset.rs1use 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
12pub(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 #[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 #[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 let key_id_1 = ctx.generate_symmetric_key();
114 let key_id_2 = ctx.generate_symmetric_key();
115
116 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 let reencrypted = test_keyset
129 .rotate_userkey(key_id_1, key_id_2, &mut ctx)
130 .expect("reencrypt should succeed");
131
132 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 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}