bitwarden_crypto/keys/
rotateable_key_set.rs1use serde::{Deserialize, Serialize};
2
3use crate::{
4 CryptoError, EncString, KeyDecryptable, KeyEncryptable, KeyIds, 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#[cfg_attr(
23 feature = "wasm",
24 derive(tsify::Tsify),
25 tsify(into_wasm_abi, from_wasm_abi)
26)]
27pub struct RotateableKeySet {
28 encapsulated_downstream_key: UnsignedSharedKey,
30 encrypted_encapsulation_key: EncString,
32 encrypted_decapsulation_key: EncString,
34}
35
36impl RotateableKeySet {
37 pub fn new<Ids: KeyIds>(
40 ctx: &KeyStoreContext<Ids>,
41 upstream_key: &SymmetricCryptoKey,
42 downstream_key_id: Ids::Symmetric,
43 ) -> Result<Self, CryptoError> {
44 let key_pair = PrivateKey::make(crate::PublicKeyEncryptionAlgorithm::RsaOaepSha1);
45
46 #[allow(deprecated)]
50 let downstream_key = ctx.dangerous_get_symmetric_key(downstream_key_id)?;
51 #[expect(deprecated)]
53 let encapsulated_downstream_key =
54 UnsignedSharedKey::encapsulate_key_unsigned(downstream_key, &key_pair.to_public_key())?;
55
56 let encrypted_decapsulation_key = key_pair.to_der()?.encrypt_with_key(upstream_key)?;
58
59 let encrypted_encapsulation_key = key_pair
65 .to_public_key()
66 .to_der()?
67 .encrypt_with_key(downstream_key)?;
68
69 Ok(RotateableKeySet {
70 encapsulated_downstream_key,
71 encrypted_encapsulation_key,
72 encrypted_decapsulation_key,
73 })
74 }
75
76 #[allow(dead_code)]
79 fn unlock<Ids: KeyIds>(
80 &self,
81 ctx: &mut KeyStoreContext<Ids>,
82 upstream_key: &SymmetricCryptoKey,
83 downstream_key_id: Ids::Symmetric,
84 ) -> Result<(), CryptoError> {
85 let priv_key_bytes: Vec<u8> = self
86 .encrypted_decapsulation_key
87 .decrypt_with_key(upstream_key)?;
88 let decapsulation_key = PrivateKey::from_der(&Pkcs8PrivateKeyBytes::from(priv_key_bytes))?;
89 #[expect(deprecated)]
90 let downstream_key = self
91 .encapsulated_downstream_key
92 .decapsulate_key_unsigned(&decapsulation_key)?;
93 #[allow(deprecated)]
94 ctx.set_symmetric_key(downstream_key_id, downstream_key)?;
95 Ok(())
96 }
97}
98
99#[allow(dead_code)]
100fn rotate_key_set<Ids: KeyIds>(
101 ctx: &KeyStoreContext<Ids>,
102 key_set: RotateableKeySet,
103 old_downstream_key_id: Ids::Symmetric,
104 new_downstream_key_id: Ids::Symmetric,
105) -> Result<RotateableKeySet, CryptoError> {
106 let pub_key_bytes = ctx.decrypt_data_with_symmetric_key(
107 old_downstream_key_id,
108 &key_set.encrypted_encapsulation_key,
109 )?;
110 let pub_key = SpkiPublicKeyBytes::from(pub_key_bytes);
111 let encapsulation_key = PublicKey::from_der(&pub_key)?;
112 #[allow(deprecated)]
115 let new_downstream_key = ctx.dangerous_get_symmetric_key(new_downstream_key_id)?;
116 #[expect(deprecated)]
117 let new_encapsulated_key =
118 UnsignedSharedKey::encapsulate_key_unsigned(new_downstream_key, &encapsulation_key)?;
119 let new_encrypted_encapsulation_key = pub_key.encrypt_with_key(new_downstream_key)?;
120 Ok(RotateableKeySet {
121 encapsulated_downstream_key: new_encapsulated_key,
122 encrypted_encapsulation_key: new_encrypted_encapsulation_key,
123 encrypted_decapsulation_key: key_set.encrypted_decapsulation_key,
124 })
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130 use crate::{
131 KeyStore,
132 traits::tests::{TestIds, TestSymmKey},
133 };
134
135 #[test]
136 fn test_rotateable_key_set_can_unlock() {
137 let upstream_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
139 let store: KeyStore<TestIds> = KeyStore::default();
141 let mut ctx = store.context_mut();
142 let original_downstream_key_id = ctx.generate_symmetric_key();
143
144 let key_set =
146 RotateableKeySet::new(&ctx, &upstream_key, original_downstream_key_id).unwrap();
147
148 let unwrapped_downstream_key_id = TestSymmKey::A(1);
150 key_set
151 .unlock(&mut ctx, &upstream_key, unwrapped_downstream_key_id)
152 .unwrap();
153
154 #[allow(deprecated)]
155 let original_downstream_key = ctx
156 .dangerous_get_symmetric_key(original_downstream_key_id)
157 .unwrap();
158 #[allow(deprecated)]
159 let unwrapped_downstream_key = ctx
160 .dangerous_get_symmetric_key(unwrapped_downstream_key_id)
161 .unwrap();
162 assert_eq!(original_downstream_key, unwrapped_downstream_key);
163 }
164
165 #[test]
166 fn test_rotateable_key_set_rotation() {
167 let upstream_key = SymmetricCryptoKey::make_aes256_cbc_hmac_key();
169 let store: KeyStore<TestIds> = KeyStore::default();
171 let mut ctx = store.context_mut();
172 let original_downstream_key_id = ctx.generate_symmetric_key();
173
174 let key_set =
176 RotateableKeySet::new(&ctx, &upstream_key, original_downstream_key_id).unwrap();
177
178 let new_downstream_key_id = ctx.generate_symmetric_key();
180 let new_key_set = rotate_key_set(
181 &ctx,
182 key_set,
183 original_downstream_key_id,
184 new_downstream_key_id,
185 )
186 .unwrap();
187
188 let unwrapped_downstream_key_id = TestSymmKey::A(2_2);
191 new_key_set
192 .unlock(&mut ctx, &upstream_key, unwrapped_downstream_key_id)
193 .unwrap();
194 #[allow(deprecated)]
195 let new_downstream_key = ctx
196 .dangerous_get_symmetric_key(new_downstream_key_id)
197 .unwrap();
198 #[allow(deprecated)]
199 let unwrapped_downstream_key = ctx
200 .dangerous_get_symmetric_key(unwrapped_downstream_key_id)
201 .unwrap();
202 assert_eq!(new_downstream_key, unwrapped_downstream_key);
203 }
204}