bitwarden_crypto/keys/
rotateable_key_set.rs1use serde::{Deserialize, Serialize};
2
3use crate::{
4 CryptoError, EncString, KeyDecryptable, KeyEncryptable, KeySlotIds, KeyStoreContext,
5 Pkcs8PrivateKeyBytes, PrivateKey, PublicKey, SpkiPublicKeyBytes, SymmetricCryptoKey,
6 UnsignedSharedKey,
7};
8
9#[derive(Serialize, Deserialize, Debug)]
20#[serde(rename_all = "camelCase", deny_unknown_fields)]
21#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
22#[bitwarden_ffi::wasm_record]
23pub struct RotateableKeySet {
24 pub encapsulated_downstream_key: UnsignedSharedKey,
26 pub encrypted_encapsulation_key: EncString,
28 pub encrypted_decapsulation_key: EncString,
30}
31
32impl RotateableKeySet {
33 pub fn new<Ids: KeySlotIds>(
36 ctx: &KeyStoreContext<Ids>,
37 upstream_key: &SymmetricCryptoKey,
38 downstream_key_id: Ids::Symmetric,
39 ) -> Result<Self, CryptoError> {
40 let key_pair = PrivateKey::make(crate::PublicKeyEncryptionAlgorithm::RsaOaepSha1);
41
42 #[allow(deprecated)]
46 let downstream_key = ctx.dangerous_get_symmetric_key(downstream_key_id)?;
47 #[expect(deprecated)]
49 let encapsulated_downstream_key =
50 UnsignedSharedKey::encapsulate_key_unsigned(downstream_key, &key_pair.to_public_key())?;
51
52 let encrypted_decapsulation_key = key_pair.to_der()?.encrypt_with_key(upstream_key)?;
54
55 let encrypted_encapsulation_key = key_pair
61 .to_public_key()
62 .to_der()?
63 .encrypt_with_key(downstream_key)?;
64
65 Ok(RotateableKeySet {
66 encapsulated_downstream_key,
67 encrypted_encapsulation_key,
68 encrypted_decapsulation_key,
69 })
70 }
71
72 #[allow(dead_code)]
75 fn unlock<Ids: KeySlotIds>(
76 &self,
77 ctx: &mut KeyStoreContext<Ids>,
78 upstream_key: &SymmetricCryptoKey,
79 downstream_key_id: Ids::Symmetric,
80 ) -> Result<(), CryptoError> {
81 let priv_key_bytes: Vec<u8> = self
82 .encrypted_decapsulation_key
83 .decrypt_with_key(upstream_key)?;
84 let decapsulation_key = PrivateKey::from_der(&Pkcs8PrivateKeyBytes::from(priv_key_bytes))?;
85 #[expect(deprecated)]
86 let downstream_key = self
87 .encapsulated_downstream_key
88 .decapsulate_key_unsigned(&decapsulation_key)?;
89 #[allow(deprecated)]
90 ctx.set_symmetric_key(downstream_key_id, downstream_key)?;
91 Ok(())
92 }
93}
94
95#[allow(dead_code)]
96fn rotate_key_set<Ids: KeySlotIds>(
97 ctx: &KeyStoreContext<Ids>,
98 key_set: RotateableKeySet,
99 old_downstream_key_id: Ids::Symmetric,
100 new_downstream_key_id: Ids::Symmetric,
101) -> Result<RotateableKeySet, CryptoError> {
102 let pub_key_bytes = ctx.decrypt_data_with_symmetric_key(
103 old_downstream_key_id,
104 &key_set.encrypted_encapsulation_key,
105 )?;
106 let pub_key = SpkiPublicKeyBytes::from(pub_key_bytes);
107 let encapsulation_key = PublicKey::from_der(&pub_key)?;
108 #[allow(deprecated)]
111 let new_downstream_key = ctx.dangerous_get_symmetric_key(new_downstream_key_id)?;
112 #[expect(deprecated)]
113 let new_encapsulated_key =
114 UnsignedSharedKey::encapsulate_key_unsigned(new_downstream_key, &encapsulation_key)?;
115 let new_encrypted_encapsulation_key = pub_key.encrypt_with_key(new_downstream_key)?;
116 Ok(RotateableKeySet {
117 encapsulated_downstream_key: new_encapsulated_key,
118 encrypted_encapsulation_key: new_encrypted_encapsulation_key,
119 encrypted_decapsulation_key: key_set.encrypted_decapsulation_key,
120 })
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126 use crate::{
127 KeyStore,
128 traits::tests::{TestIds, TestSymmKey},
129 };
130
131 #[test]
132 fn test_rotateable_key_set_can_unlock() {
133 let upstream_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
135 let store: KeyStore<TestIds> = KeyStore::default();
137 let mut ctx = store.context_mut();
138 let original_downstream_key_id = ctx.generate_symmetric_key();
139
140 let key_set =
142 RotateableKeySet::new(&ctx, &upstream_key, original_downstream_key_id).unwrap();
143
144 let unwrapped_downstream_key_id = TestSymmKey::A(1);
146 key_set
147 .unlock(&mut ctx, &upstream_key, unwrapped_downstream_key_id)
148 .unwrap();
149
150 #[allow(deprecated)]
151 let original_downstream_key = ctx
152 .dangerous_get_symmetric_key(original_downstream_key_id)
153 .unwrap();
154 #[allow(deprecated)]
155 let unwrapped_downstream_key = ctx
156 .dangerous_get_symmetric_key(unwrapped_downstream_key_id)
157 .unwrap();
158 assert_eq!(original_downstream_key, unwrapped_downstream_key);
159 }
160
161 #[test]
162 fn test_rotateable_key_set_rotation() {
163 let upstream_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
165 let store: KeyStore<TestIds> = KeyStore::default();
167 let mut ctx = store.context_mut();
168 let original_downstream_key_id = ctx.generate_symmetric_key();
169
170 let key_set =
172 RotateableKeySet::new(&ctx, &upstream_key, original_downstream_key_id).unwrap();
173
174 let new_downstream_key_id = ctx.generate_symmetric_key();
176 let new_key_set = rotate_key_set(
177 &ctx,
178 key_set,
179 original_downstream_key_id,
180 new_downstream_key_id,
181 )
182 .unwrap();
183
184 let unwrapped_downstream_key_id = TestSymmKey::A(2_2);
187 new_key_set
188 .unlock(&mut ctx, &upstream_key, unwrapped_downstream_key_id)
189 .unwrap();
190 #[allow(deprecated)]
191 let new_downstream_key = ctx
192 .dangerous_get_symmetric_key(new_downstream_key_id)
193 .unwrap();
194 #[allow(deprecated)]
195 let unwrapped_downstream_key = ctx
196 .dangerous_get_symmetric_key(unwrapped_downstream_key_id)
197 .unwrap();
198 assert_eq!(new_downstream_key, unwrapped_downstream_key);
199 }
200}