walletkit_core/storage/
keys.rs1use secrecy::SecretBox;
10use zeroize::{Zeroize, ZeroizeOnDrop};
11
12use super::{
13 error::StorageResult,
14 traits::{AtomicBlobStore, DeviceKeystore},
15 ACCOUNT_KEYS_FILENAME, ACCOUNT_KEY_ENVELOPE_AD,
16};
17use walletkit_db::Lock;
18
19#[derive(Zeroize, ZeroizeOnDrop)]
23#[allow(clippy::struct_field_names)]
24pub struct StorageKeys {
25 intermediate_key: SecretBox<[u8; 32]>,
26}
27
28impl StorageKeys {
29 pub fn init(
36 keystore: &dyn DeviceKeystore,
37 blob_store: &dyn AtomicBlobStore,
38 lock: &Lock,
39 now: u64,
40 ) -> StorageResult<Self> {
41 let intermediate_key = walletkit_db::init_or_open_envelope_key(
42 &Ks(keystore),
43 &Bs(blob_store),
44 lock,
45 ACCOUNT_KEYS_FILENAME,
46 ACCOUNT_KEY_ENVELOPE_AD,
47 now,
48 )?;
49 Ok(Self { intermediate_key })
50 }
51
52 #[must_use]
54 pub const fn intermediate_key(&self) -> &SecretBox<[u8; 32]> {
55 &self.intermediate_key
56 }
57}
58
59struct Ks<'a>(&'a dyn DeviceKeystore);
70impl walletkit_db::Keystore for Ks<'_> {
71 fn seal(&self, aad: &[u8], pt: &[u8]) -> walletkit_db::StoreResult<Vec<u8>> {
72 self.0
73 .seal(aad.to_vec(), pt.to_vec())
74 .map_err(|e| walletkit_db::StoreError::Keystore(e.to_string()))
75 }
76 fn open_sealed(
77 &self,
78 aad: Vec<u8>,
79 ct: Vec<u8>,
80 ) -> walletkit_db::StoreResult<Vec<u8>> {
81 self.0
82 .open_sealed(aad, ct)
83 .map_err(|e| walletkit_db::StoreError::Keystore(e.to_string()))
84 }
85}
86
87struct Bs<'a>(&'a dyn AtomicBlobStore);
88impl walletkit_db::AtomicBlobStore for Bs<'_> {
89 fn read(&self, path: String) -> walletkit_db::StoreResult<Option<Vec<u8>>> {
90 self.0
91 .read(path)
92 .map_err(|e| walletkit_db::StoreError::BlobStore(e.to_string()))
93 }
94 fn write_atomic(
95 &self,
96 path: String,
97 bytes: Vec<u8>,
98 ) -> walletkit_db::StoreResult<()> {
99 self.0
100 .write_atomic(path, bytes)
101 .map_err(|e| walletkit_db::StoreError::BlobStore(e.to_string()))
102 }
103 fn delete(&self, path: String) -> walletkit_db::StoreResult<()> {
104 self.0
105 .delete(path)
106 .map_err(|e| walletkit_db::StoreError::BlobStore(e.to_string()))
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113 use crate::storage::error::StorageError;
114 use crate::storage::tests_utils::{InMemoryBlobStore, InMemoryKeystore};
115 use secrecy::ExposeSecret;
116 use uuid::Uuid;
117 use walletkit_db::Lock;
118
119 fn temp_lock_path() -> std::path::PathBuf {
120 let mut path = std::env::temp_dir();
121 path.push(format!("walletkit-keys-lock-{}.lock", Uuid::new_v4()));
122 path
123 }
124
125 #[test]
126 fn test_storage_keys_round_trip() {
127 let keystore = InMemoryKeystore::new();
128 let blob_store = InMemoryBlobStore::new();
129 let lock_path = temp_lock_path();
130 let lock = Lock::open(&lock_path).expect("open lock");
131 let keys_first =
132 StorageKeys::init(&keystore, &blob_store, &lock, 100).expect("init");
133 let keys_second =
134 StorageKeys::init(&keystore, &blob_store, &lock, 200).expect("init");
135
136 assert_eq!(
137 keys_first.intermediate_key.expose_secret(),
138 keys_second.intermediate_key.expose_secret()
139 );
140 let _ = std::fs::remove_file(lock_path);
141 }
142
143 #[test]
144 fn test_storage_keys_keystore_mismatch_fails() {
145 let keystore = InMemoryKeystore::new();
146 let blob_store = InMemoryBlobStore::new();
147 let lock_path = temp_lock_path();
148 let lock = Lock::open(&lock_path).expect("open lock");
149 StorageKeys::init(&keystore, &blob_store, &lock, 123).expect("init");
150
151 let other_keystore = InMemoryKeystore::new();
152 match StorageKeys::init(&other_keystore, &blob_store, &lock, 456) {
153 Err(
154 StorageError::Crypto(_)
155 | StorageError::InvalidEnvelope(_)
156 | StorageError::Keystore(_),
157 ) => {}
158 Err(err) => panic!("unexpected error: {err}"),
159 Ok(_) => panic!("expected error"),
160 }
161 let _ = std::fs::remove_file(lock_path);
162 }
163
164 #[test]
165 fn test_storage_keys_tampered_envelope_fails() {
166 let keystore = InMemoryKeystore::new();
167 let blob_store = InMemoryBlobStore::new();
168 let lock_path = temp_lock_path();
169 let lock = Lock::open(&lock_path).expect("open lock");
170 StorageKeys::init(&keystore, &blob_store, &lock, 123).expect("init");
171
172 let mut bytes = blob_store
173 .read(ACCOUNT_KEYS_FILENAME.to_string())
174 .expect("read")
175 .expect("present");
176 bytes[0] ^= 0xFF;
177 blob_store
178 .write_atomic(ACCOUNT_KEYS_FILENAME.to_string(), bytes)
179 .expect("write");
180
181 match StorageKeys::init(&keystore, &blob_store, &lock, 456) {
182 Err(
183 StorageError::Serialization(_)
184 | StorageError::Crypto(_)
185 | StorageError::UnsupportedEnvelopeVersion(_),
186 ) => {}
187 Err(err) => panic!("unexpected error: {err}"),
188 Ok(_) => panic!("expected error"),
189 }
190 let _ = std::fs::remove_file(lock_path);
191 }
192}