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