bitwarden_user_crypto_management/key_rotation/
partial_rotateable_keyset.rs1use std::str::FromStr;
2
3use bitwarden_api_api::models::{
4 OtherDeviceKeysUpdateRequestModel, PasskeyKeyDataResponseModel,
5 TrustedDeviceKeyDataResponseModel, WebAuthnLoginRotateKeyRequestModel,
6};
7#[cfg(test)]
8use bitwarden_core::key_management::PrivateKeySlotId;
9use bitwarden_core::{
10 key_management::{KeySlotIds, SymmetricKeySlotId},
11 require,
12};
13use bitwarden_crypto::{
14 Decryptable, EncString, KeyStoreContext, PrimitiveEncryptable, PublicKey, UnsignedSharedKey,
15};
16
17use crate::key_rotation::KeyRotationDataParseError;
18
19pub(super) struct PartialRotateableKeyset {
23 pub(super) id: uuid::Uuid,
24 pub(super) encrypted_public_key: EncString,
25 pub(super) encrypted_user_key: UnsignedSharedKey,
26}
27
28impl From<PartialRotateableKeyset> for OtherDeviceKeysUpdateRequestModel {
29 fn from(val: PartialRotateableKeyset) -> Self {
30 OtherDeviceKeysUpdateRequestModel {
31 device_id: val.id,
32 encrypted_public_key: val.encrypted_public_key.to_string(),
33 encrypted_user_key: val.encrypted_user_key.to_string(),
34 }
35 }
36}
37
38impl From<PartialRotateableKeyset> for WebAuthnLoginRotateKeyRequestModel {
39 fn from(val: PartialRotateableKeyset) -> Self {
40 WebAuthnLoginRotateKeyRequestModel {
41 id: val.id,
42 encrypted_public_key: val.encrypted_public_key.to_string(),
43 encrypted_user_key: val.encrypted_user_key.to_string(),
44 }
45 }
46}
47
48impl TryFrom<TrustedDeviceKeyDataResponseModel> for PartialRotateableKeyset {
49 type Error = KeyRotationDataParseError;
50
51 fn try_from(d: TrustedDeviceKeyDataResponseModel) -> Result<Self, Self::Error> {
52 Ok(Self {
53 id: require!(d.id),
54 encrypted_public_key: EncString::from_str(&require!(d.encrypted_public_key))?,
55 encrypted_user_key: UnsignedSharedKey::from_str(&require!(d.encrypted_user_key))?,
56 })
57 }
58}
59
60impl TryFrom<PasskeyKeyDataResponseModel> for PartialRotateableKeyset {
61 type Error = KeyRotationDataParseError;
62
63 fn try_from(p: PasskeyKeyDataResponseModel) -> Result<Self, Self::Error> {
64 Ok(Self {
65 id: require!(p.id),
66 encrypted_public_key: EncString::from_str(&require!(p.encrypted_public_key))?,
67 encrypted_user_key: UnsignedSharedKey::from_str(&require!(p.encrypted_user_key))?,
68 })
69 }
70}
71
72impl PartialRotateableKeyset {
73 #[bitwarden_logging::instrument(fields(current_user_key_id = ?current_user_key_id, new_user_key_id = ?new_user_key_id))]
77 pub(super) fn rotate_userkey(
78 &self,
79 current_user_key_id: SymmetricKeySlotId,
80 new_user_key_id: SymmetricKeySlotId,
81 ctx: &mut KeyStoreContext<KeySlotIds>,
82 ) -> Result<PartialRotateableKeyset, ()> {
83 let pubkey_bytes: Vec<u8> = self
84 .encrypted_public_key
85 .decrypt(ctx, current_user_key_id)
86 .map_err(|_| ())?;
87 let pubkey_der =
88 bitwarden_crypto::Bytes::<bitwarden_crypto::SpkiPublicKeyDerContentFormat>::from(
89 pubkey_bytes,
90 );
91 let pubkey = PublicKey::from_der(&pubkey_der).map_err(|_| ())?;
92 let reencrypted_user_key =
93 UnsignedSharedKey::encapsulate(new_user_key_id, &pubkey, ctx).map_err(|_| ())?;
94 let reencrypted_public_key = pubkey_der.encrypt(ctx, new_user_key_id).map_err(|_| ())?;
95 Ok(PartialRotateableKeyset {
96 id: self.id,
97 encrypted_public_key: reencrypted_public_key,
98 encrypted_user_key: reencrypted_user_key,
99 })
100 }
101
102 #[cfg(test)]
105 pub(crate) fn make_test_keyset(
106 downstream_key_id: SymmetricKeySlotId,
107 ctx: &mut KeyStoreContext<KeySlotIds>,
108 ) -> (Self, PrivateKeySlotId) {
109 use bitwarden_crypto::PublicKeyEncryptionAlgorithm;
110
111 let private_key = ctx.make_private_key(PublicKeyEncryptionAlgorithm::RsaOaepSha1);
112 let pubkey_der = ctx.get_public_key(private_key).unwrap().to_der().unwrap();
113 let encrypted_public_key = pubkey_der.encrypt(ctx, downstream_key_id).unwrap();
114 let encrypted_user_key = UnsignedSharedKey::encapsulate(
115 downstream_key_id,
116 &ctx.get_public_key(private_key).unwrap(),
117 ctx,
118 )
119 .unwrap();
120 (
121 PartialRotateableKeyset {
122 id: uuid::Uuid::new_v4(),
123 encrypted_public_key,
124 encrypted_user_key,
125 },
126 private_key,
127 )
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use bitwarden_core::key_management::KeySlotIds;
134 use bitwarden_crypto::{Bytes, KeyStore, SpkiPublicKeyDerContentFormat};
135
136 use super::*;
137
138 #[test]
139 fn test_keyset_reencrypt() {
140 let store: KeyStore<KeySlotIds> = KeyStore::default();
141 let mut ctx = store.context_mut();
142
143 let key_id_1 = ctx.generate_symmetric_key();
145 let key_id_2 = ctx.generate_symmetric_key();
146
147 let (test_keyset, private_key) =
149 PartialRotateableKeyset::make_test_keyset(key_id_1, &mut ctx);
150 let pubkey_der = {
151 let decrypted_pubkey_bytes: Vec<u8> = test_keyset
152 .encrypted_public_key
153 .decrypt(&mut ctx, key_id_1)
154 .expect("decryption should succeed");
155 Bytes::<SpkiPublicKeyDerContentFormat>::from(decrypted_pubkey_bytes)
156 };
157
158 let reencrypted = test_keyset
160 .rotate_userkey(key_id_1, key_id_2, &mut ctx)
161 .expect("reencrypt should succeed");
162
163 let decapsulated = {
165 let decapsulated = reencrypted
166 .encrypted_user_key
167 .decapsulate(private_key, &mut ctx)
168 .expect("decapsulation should succeed");
169 #[expect(deprecated)]
170 ctx.dangerous_get_symmetric_key(decapsulated)
171 .expect("key should exist")
172 .to_owned()
173 };
174
175 let key_2 = {
176 #[expect(deprecated)]
177 ctx.dangerous_get_symmetric_key(key_id_2)
178 .expect("key should exist")
179 .to_owned()
180 };
181 assert_eq!(decapsulated, key_2);
182
183 let decrypted_pubkey_bytes: Vec<u8> = reencrypted
185 .encrypted_public_key
186 .decrypt(&mut ctx, key_id_2)
187 .expect("decryption should succeed");
188 assert_eq!(decrypted_pubkey_bytes, pubkey_der.as_ref());
189 }
190}