bitwarden_core/key_management/
local_user_data_key.rs1use bitwarden_crypto::{EncString, KeyStoreContext};
2use key_management::LocalUserDataKeyState;
3use thiserror::Error;
4
5use crate::{
6 key_management,
7 key_management::{KeySlotIds, SymmetricKeySlotId},
8};
9
10#[derive(Debug, Clone)]
14pub(crate) struct WrappedLocalUserDataKey(pub(crate) EncString);
15
16impl WrappedLocalUserDataKey {
17 #[bitwarden_logging::instrument(err)]
19 pub(crate) fn from_context_user_key(
20 ctx: &mut KeyStoreContext<KeySlotIds>,
21 ) -> Result<Self, LocalUserDataKeyError> {
22 let wrapped_local_user_data_key = ctx
23 .wrap_symmetric_key(SymmetricKeySlotId::User, SymmetricKeySlotId::User)
24 .map_err(|_| LocalUserDataKeyError::EncryptionFailed)?;
25 Ok(WrappedLocalUserDataKey(wrapped_local_user_data_key))
26 }
27
28 #[bitwarden_logging::instrument(err, fields(old_wrapping_key_id = ?old_wrapping_key_id))]
36 pub(crate) fn rewrap_with_user_key(
37 &self,
38 old_wrapping_key_id: SymmetricKeySlotId,
39 ctx: &mut KeyStoreContext<KeySlotIds>,
40 ) -> Result<Self, LocalUserDataKeyError> {
41 let local_id = ctx
42 .unwrap_symmetric_key(old_wrapping_key_id, &self.0)
43 .map_err(|_| LocalUserDataKeyError::DecryptionFailed)?;
44 let new_wrapped = ctx
45 .wrap_symmetric_key(SymmetricKeySlotId::User, local_id)
46 .map_err(|_| LocalUserDataKeyError::EncryptionFailed)?;
47 Ok(WrappedLocalUserDataKey(new_wrapped))
48 }
49
50 #[bitwarden_logging::instrument(err)]
53 pub(crate) fn unwrap_to_context(
54 &self,
55 ctx: &mut KeyStoreContext<KeySlotIds>,
56 ) -> Result<(), LocalUserDataKeyError> {
57 let local_id = ctx
58 .unwrap_symmetric_key(SymmetricKeySlotId::User, &self.0)
59 .map_err(|_| LocalUserDataKeyError::DecryptionFailed)?;
60 ctx.persist_symmetric_key(local_id, SymmetricKeySlotId::LocalUserData)
61 .map_err(|_| LocalUserDataKeyError::DecryptionFailed)?;
62 Ok(())
63 }
64}
65
66#[derive(Debug, Error)]
68pub enum LocalUserDataKeyError {
69 #[error("Decryption failed")]
71 DecryptionFailed,
72 #[error("Encryption failed")]
74 EncryptionFailed,
75}
76
77impl From<WrappedLocalUserDataKey> for LocalUserDataKeyState {
78 fn from(wrapped_key: WrappedLocalUserDataKey) -> Self {
79 Self {
80 wrapped_key: wrapped_key.0,
81 }
82 }
83}
84
85impl From<&LocalUserDataKeyState> for WrappedLocalUserDataKey {
86 fn from(state: &LocalUserDataKeyState) -> Self {
87 Self(state.wrapped_key.clone())
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use bitwarden_crypto::{Decryptable, KeyStore, PrimitiveEncryptable};
94
95 use super::*;
96 use crate::key_management::{KeySlotIds, SymmetricKeySlotId};
97
98 fn make_key_store_with_user_key() -> KeyStore<KeySlotIds> {
99 let key_store = KeyStore::<KeySlotIds>::default();
100 let mut ctx = key_store.context_mut();
101 let user_key = ctx.generate_symmetric_key();
102 ctx.persist_symmetric_key(user_key, SymmetricKeySlotId::User)
103 .expect("persisting user key should succeed");
104 drop(ctx);
105 key_store
106 }
107
108 #[test]
109 fn test_from_context_user_key_wraps_user_key() {
110 let key_store = make_key_store_with_user_key();
111 let mut ctx = key_store.context_mut();
112
113 let plaintext = "test data";
114 let ciphertext = plaintext
115 .encrypt(&mut ctx, SymmetricKeySlotId::User)
116 .expect("encryption with user key should succeed");
117
118 let wrapped = WrappedLocalUserDataKey::from_context_user_key(&mut ctx)
119 .expect("wrapping should succeed");
120 wrapped
121 .unwrap_to_context(&mut ctx)
122 .expect("unwrapping should succeed");
123
124 let decrypted: String = ciphertext
127 .decrypt(&mut ctx, SymmetricKeySlotId::LocalUserData)
128 .expect("decryption with local user data key should succeed");
129 assert_eq!(decrypted, plaintext);
130 }
131
132 #[test]
133 fn test_rewrap_with_user_key_preserves_inner_plaintext() {
134 use bitwarden_crypto::SymmetricKeyAlgorithm;
135
136 let key_store = KeyStore::<KeySlotIds>::default();
137 let mut ctx = key_store.context_mut();
138
139 let v1_local_key_id = ctx.make_symmetric_key(SymmetricKeyAlgorithm::Aes256CbcHmac);
141 ctx.persist_symmetric_key(v1_local_key_id, SymmetricKeySlotId::User)
142 .expect("persisting old user key should succeed");
143
144 let wrapped_v1_local_key = WrappedLocalUserDataKey::from_context_user_key(&mut ctx)
145 .expect("initial wrap should succeed");
146
147 wrapped_v1_local_key
148 .unwrap_to_context(&mut ctx)
149 .expect("unwrap with old user key should succeed");
150 let plaintext = "rewrap round-trip data";
151 let ciphertext = plaintext
152 .encrypt(&mut ctx, SymmetricKeySlotId::LocalUserData)
153 .expect("encryption with LocalUserData slot should succeed");
154
155 let v1_old_wrapping_id = ctx
159 .unwrap_symmetric_key(SymmetricKeySlotId::User, &wrapped_v1_local_key.0)
160 .expect("unwrap with v1 user key should succeed");
161
162 let new_local = ctx.make_symmetric_key(SymmetricKeyAlgorithm::XChaCha20Poly1305);
164 ctx.persist_symmetric_key(new_local, SymmetricKeySlotId::User)
165 .expect("persisting new user key should succeed");
166 let wrapped_new = wrapped_v1_local_key
167 .rewrap_with_user_key(v1_old_wrapping_id, &mut ctx)
168 .expect("rewrap should succeed");
169
170 wrapped_new
172 .unwrap_to_context(&mut ctx)
173 .expect("unwrap with new user key should succeed");
174
175 let decrypted: String = ciphertext
176 .decrypt(&mut ctx, SymmetricKeySlotId::LocalUserData)
177 .expect("decryption after rewrap should succeed");
178 assert_eq!(decrypted, plaintext);
179 }
180
181 #[test]
182 fn test_unwrap_to_context_fails_with_wrong_key() {
183 let key_store_a = make_key_store_with_user_key();
184 let wrapped = {
185 let mut ctx = key_store_a.context_mut();
186 WrappedLocalUserDataKey::from_context_user_key(&mut ctx)
187 .expect("wrapping should succeed")
188 };
189
190 let key_store_b = make_key_store_with_user_key();
191 let mut ctx_b = key_store_b.context_mut();
192 assert!(
193 wrapped.unwrap_to_context(&mut ctx_b).is_err(),
194 "unwrapping with a different key should fail"
195 );
196 }
197}