mirror of
https://github.com/bitwarden/browser
synced 2026-02-06 19:53:59 +00:00
Implement secure memory kv store
This commit is contained in:
227
apps/desktop/desktop_native/Cargo.lock
generated
227
apps/desktop/desktop_native/Cargo.lock
generated
@@ -162,7 +162,7 @@ dependencies = [
|
||||
"serde_repr",
|
||||
"tokio",
|
||||
"url",
|
||||
"zbus 5.6.0",
|
||||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -879,6 +879,7 @@ dependencies = [
|
||||
"keytar",
|
||||
"libc",
|
||||
"log",
|
||||
"memsec",
|
||||
"oo7",
|
||||
"pin-project",
|
||||
"pkcs8",
|
||||
@@ -900,7 +901,7 @@ dependencies = [
|
||||
"widestring",
|
||||
"windows 0.61.1",
|
||||
"windows-future",
|
||||
"zbus 4.4.0",
|
||||
"zbus",
|
||||
"zbus_polkit",
|
||||
"zeroizing-alloc",
|
||||
]
|
||||
@@ -1701,6 +1702,17 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memsec"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c797b9d6bb23aab2fc369c65f871be49214f5c759af65bde26ffaaa2b646b492"
|
||||
dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
"libc",
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.17"
|
||||
@@ -2063,10 +2075,10 @@ dependencies = [
|
||||
"sha2",
|
||||
"subtle",
|
||||
"tokio",
|
||||
"zbus 5.6.0",
|
||||
"zbus_macros 5.6.0",
|
||||
"zbus",
|
||||
"zbus_macros",
|
||||
"zeroize",
|
||||
"zvariant 5.5.1",
|
||||
"zvariant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2715,17 +2727,6 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.8"
|
||||
@@ -3677,6 +3678,15 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
|
||||
dependencies = [
|
||||
"windows-targets 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
@@ -3695,6 +3705,21 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.42.2",
|
||||
"windows_aarch64_msvc 0.42.2",
|
||||
"windows_i686_gnu 0.42.2",
|
||||
"windows_i686_msvc 0.42.2",
|
||||
"windows_x86_64_gnu 0.42.2",
|
||||
"windows_x86_64_gnullvm 0.42.2",
|
||||
"windows_x86_64_msvc 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
@@ -3726,6 +3751,12 @@ dependencies = [
|
||||
"windows_x86_64_msvc 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
@@ -3738,6 +3769,12 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
@@ -3750,6 +3787,12 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
@@ -3768,6 +3811,12 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
@@ -3789,6 +3838,12 @@ dependencies = [
|
||||
"windows-core 0.61.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
@@ -3801,6 +3856,12 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
@@ -3813,6 +3874,12 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
@@ -3921,9 +3988,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zbus"
|
||||
version = "4.4.0"
|
||||
version = "5.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725"
|
||||
checksum = "59c333f648ea1b647bc95dc1d34807c8e25ed7a6feff3394034dc4776054b236"
|
||||
dependencies = [
|
||||
"async-broadcast",
|
||||
"async-executor",
|
||||
@@ -3938,90 +4005,37 @@ dependencies = [
|
||||
"enumflags2",
|
||||
"event-listener",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"hex",
|
||||
"nix",
|
||||
"ordered-stream",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"sha1",
|
||||
"static_assertions",
|
||||
"tracing",
|
||||
"uds_windows",
|
||||
"windows-sys 0.52.0",
|
||||
"xdg-home",
|
||||
"zbus_macros 4.4.0",
|
||||
"zbus_names 3.0.0",
|
||||
"zvariant 4.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus"
|
||||
version = "5.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2522b82023923eecb0b366da727ec883ace092e7887b61d3da5139f26b44da58"
|
||||
dependencies = [
|
||||
"async-broadcast",
|
||||
"async-recursion",
|
||||
"async-trait",
|
||||
"enumflags2",
|
||||
"event-listener",
|
||||
"futures-core",
|
||||
"futures-lite",
|
||||
"hex",
|
||||
"nix",
|
||||
"ordered-stream",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"static_assertions",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"uds_windows",
|
||||
"windows-sys 0.59.0",
|
||||
"winnow",
|
||||
"zbus_macros 5.6.0",
|
||||
"zbus_names 4.2.0",
|
||||
"zvariant 5.5.1",
|
||||
"xdg-home",
|
||||
"zbus_macros",
|
||||
"zbus_names",
|
||||
"zvariant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus_macros"
|
||||
version = "4.4.0"
|
||||
version = "5.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e"
|
||||
checksum = "f325ad10eb0d0a3eb060203494c3b7ec3162a01a59db75d2deee100339709fc0"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"zvariant_utils 2.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus_macros"
|
||||
version = "5.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05d2e12843c75108c00c618c2e8ef9675b50b6ec095b36dc965f2e5aed463c15"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"zbus_names 4.2.0",
|
||||
"zvariant 5.5.1",
|
||||
"zvariant_utils 3.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus_names"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"zvariant 4.2.0",
|
||||
"zbus_names",
|
||||
"zvariant",
|
||||
"zvariant_utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4033,20 +4047,20 @@ dependencies = [
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"winnow",
|
||||
"zvariant 5.5.1",
|
||||
"zvariant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zbus_polkit"
|
||||
version = "4.0.0"
|
||||
version = "5.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00a29bfa927b29f91b7feb4e1990f2dd1b4604072f493dc2f074cf59e4e0ba90"
|
||||
checksum = "ad23d5c4d198c7e2641b33e6e0d1f866f117408ba66fe80bbe52e289eeb77c52"
|
||||
dependencies = [
|
||||
"enumflags2",
|
||||
"serde",
|
||||
"serde_repr",
|
||||
"static_assertions",
|
||||
"zbus 4.4.0",
|
||||
"zbus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4149,19 +4163,6 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant"
|
||||
version = "4.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe"
|
||||
dependencies = [
|
||||
"endi",
|
||||
"enumflags2",
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"zvariant_derive 4.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant"
|
||||
version = "5.5.1"
|
||||
@@ -4173,21 +4174,8 @@ dependencies = [
|
||||
"serde",
|
||||
"url",
|
||||
"winnow",
|
||||
"zvariant_derive 5.5.1",
|
||||
"zvariant_utils 3.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant_derive"
|
||||
version = "4.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449"
|
||||
dependencies = [
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"zvariant_utils 2.1.0",
|
||||
"zvariant_derive",
|
||||
"zvariant_utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4200,18 +4188,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"zvariant_utils 3.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zvariant_utils"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"zvariant_utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -56,6 +56,7 @@ ed25519 = { workspace = true, features = ["pkcs8"] }
|
||||
bytes = { workspace = true }
|
||||
sysinfo = { workspace = true, features = ["windows"] }
|
||||
zeroizing-alloc = { workspace = true }
|
||||
memsec = { version = "0.7.0", features = ["alloc_ext"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
widestring = { workspace = true, optional = true }
|
||||
|
||||
@@ -8,6 +8,7 @@ pub mod ipc;
|
||||
pub mod password;
|
||||
pub mod powermonitor;
|
||||
pub mod process_isolation;
|
||||
pub mod secure_memory;
|
||||
pub mod ssh_agent;
|
||||
|
||||
use zeroizing_alloc::ZeroAlloc;
|
||||
|
||||
125
apps/desktop/desktop_native/core/src/secure_memory/dpapi.rs
Normal file
125
apps/desktop/desktop_native/core/src/secure_memory/dpapi.rs
Normal file
@@ -0,0 +1,125 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use pkcs8::der::zeroize::Zeroize;
|
||||
use windows::Win32::Security::Cryptography::{
|
||||
CryptProtectMemory, CryptUnprotectMemory, CRYPTPROTECTMEMORY_BLOCK_SIZE,
|
||||
CRYPTPROTECTMEMORY_SAME_PROCESS,
|
||||
};
|
||||
|
||||
use crate::secure_memory::SecureMemoryStore;
|
||||
|
||||
/// https://learn.microsoft.com/en-us/windows/win32/api/dpapi/nf-dpapi-cryptprotectdata
|
||||
/// The DPAPI store encrypts data using the Windows Data Protection API (DPAPI). The key is bound
|
||||
/// to the current process, and cannot be decrypted by other user-mode processes.
|
||||
///
|
||||
/// Note: Admin processes can still decrypt this memory:
|
||||
/// https://blog.slowerzs.net/posts/cryptdecryptmemory/
|
||||
pub(super) struct DpapiSecretKVStore {
|
||||
map: HashMap<String, Vec<u8>>,
|
||||
}
|
||||
|
||||
impl DpapiSecretKVStore {
|
||||
pub(super) fn new() -> Self {
|
||||
DpapiSecretKVStore {
|
||||
map: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SecureMemoryStore for DpapiSecretKVStore {
|
||||
fn put(&mut self, key: String, value: &mut [u8]) {
|
||||
let length_header_len = std::mem::size_of::<usize>();
|
||||
|
||||
// The allocated data has to be a multiple of CRYPTPROTECTMEMORY_BLOCK_SIZE, so we pad it and write the length in front
|
||||
// We are storing LENGTH|DATA|00..00, where LENGTH is the length of DATA, the total length is a multiple
|
||||
// of CRYPTPROTECTMEMORY_BLOCK_SIZE, and the padding is filled with zeros.
|
||||
|
||||
let data_len = value.len();
|
||||
let len_with_header = data_len + length_header_len;
|
||||
let padded_length = len_with_header + CRYPTPROTECTMEMORY_BLOCK_SIZE as usize
|
||||
- (len_with_header % CRYPTPROTECTMEMORY_BLOCK_SIZE as usize);
|
||||
let mut padded_data = vec![0u8; padded_length];
|
||||
padded_data[..length_header_len].copy_from_slice(&data_len.to_le_bytes());
|
||||
padded_data[length_header_len..][..data_len].copy_from_slice(value);
|
||||
|
||||
// Protect the memory using DPAPI
|
||||
unsafe {
|
||||
CryptProtectMemory(
|
||||
padded_data.as_mut_ptr() as *mut core::ffi::c_void,
|
||||
padded_length as u32,
|
||||
CRYPTPROTECTMEMORY_SAME_PROCESS,
|
||||
)
|
||||
}
|
||||
.expect("crypt_protect_memory should work");
|
||||
|
||||
self.map.insert(key, padded_data);
|
||||
}
|
||||
|
||||
fn get(&self, key: &str) -> Option<Vec<u8>> {
|
||||
self.map.get(key).map(|data| {
|
||||
// A copy is created, that is then mutated by the DPAPI unprotect function.
|
||||
let mut data = data.clone();
|
||||
unsafe {
|
||||
CryptUnprotectMemory(
|
||||
data.as_mut_ptr() as *mut core::ffi::c_void,
|
||||
data.len() as u32,
|
||||
CRYPTPROTECTMEMORY_SAME_PROCESS,
|
||||
)
|
||||
}
|
||||
.expect("crypt_unprotect_memory should work");
|
||||
|
||||
// Unpad the data to retrieve the original value
|
||||
let length_header_size = std::mem::size_of::<usize>();
|
||||
let length_bytes = &data[..length_header_size];
|
||||
let data_length = usize::from_le_bytes(
|
||||
length_bytes
|
||||
.try_into()
|
||||
.expect("length header should be usize"),
|
||||
);
|
||||
|
||||
data[length_header_size..length_header_size + data_length].to_vec()
|
||||
})
|
||||
}
|
||||
|
||||
fn has(&self, key: &str) -> bool {
|
||||
self.map.contains_key(key)
|
||||
}
|
||||
|
||||
fn remove(&mut self, key: &str) {
|
||||
if let Some(value) = self.map.remove(key) {
|
||||
unsafe {
|
||||
std::ptr::write_bytes(value.as_mut_ptr(), 0, value.len());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
for (_, value) in self.map.drain() {
|
||||
unsafe {
|
||||
std::ptr::write_bytes(value.as_mut_ptr(), 0, value.len());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for DpapiSecretKVStore {
|
||||
fn drop(&mut self) {
|
||||
self.clear();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_dpapi_secret_kv_store() {
|
||||
let mut store = DpapiSecretKVStore::new();
|
||||
let key = "test_key".to_string();
|
||||
let mut value = vec![1, 2, 3, 4, 5];
|
||||
|
||||
store.put(key.clone(), &mut value);
|
||||
assert!(store.has(&key));
|
||||
assert_eq!(store.get(&key), Some(value));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
use std::{collections::HashMap, ptr::NonNull, sync::LazyLock};
|
||||
|
||||
use crate::secure_memory::SecureMemoryStore;
|
||||
|
||||
/// https://man.archlinux.org/man/memfd_secret.2.en
|
||||
/// The memfd_secret store protects the data using the `memfd_secret` syscall. The
|
||||
/// data is inaccessible to other user-mode processes, and even to root in most cases.
|
||||
/// If arbitrary data can be executed in the kernel, the data can still be retrieved:
|
||||
/// https://github.com/JonathonReinhart/nosecmem
|
||||
pub(super) struct MemfdSecretKVStore {
|
||||
map: HashMap<String, std::ptr::NonNull<[u8]>>,
|
||||
}
|
||||
|
||||
impl MemfdSecretKVStore {
|
||||
pub(super) fn new() -> Self {
|
||||
MemfdSecretKVStore {
|
||||
map: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SecureMemoryStore for MemfdSecretKVStore {
|
||||
fn put(&mut self, key: String, value: &[u8]) {
|
||||
let mut ptr: std::ptr::NonNull<[u8]> = unsafe {
|
||||
memsec::memfd_secret_sized(value.len()).expect("memfd_secret_sized should work")
|
||||
};
|
||||
unsafe {
|
||||
std::ptr::copy_nonoverlapping(value.as_ptr(), ptr.as_mut().as_mut_ptr(), value.len());
|
||||
}
|
||||
self.map.insert(key, ptr);
|
||||
}
|
||||
|
||||
fn get(&self, key: &str) -> Option<Vec<u8>> {
|
||||
let ptr = self.map.get(key)?;
|
||||
let value = unsafe { ptr.as_ref() };
|
||||
Some(value.to_vec())
|
||||
}
|
||||
|
||||
fn has(&self, key: &str) -> bool {
|
||||
self.map.contains_key(key)
|
||||
}
|
||||
|
||||
fn remove(&mut self, key: &str) {
|
||||
if let Some(value) = self.map.remove(key) {
|
||||
unsafe {
|
||||
memsec::free_memfd_secret(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
for (_, value) in self.map.drain() {
|
||||
unsafe {
|
||||
memsec::free_memfd_secret(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MemfdSecretKVStore {
|
||||
fn drop(&mut self) {
|
||||
self.clear();
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn is_supported() -> bool {
|
||||
// To test if memfd_secret is supported, we try to allocate a 1 byte and see if that
|
||||
// succeeds.
|
||||
static IS_SUPPORTED: LazyLock<bool> = LazyLock::new(|| {
|
||||
let Some(ptr): Option<NonNull<[u8]>> = (unsafe { memsec::memfd_secret_sized(1) }) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
// Check that the pointer is readable and writable
|
||||
let result = unsafe {
|
||||
let ptr = ptr.as_ptr() as *mut u8;
|
||||
*ptr = 30;
|
||||
*ptr += 107;
|
||||
*ptr == 137
|
||||
};
|
||||
|
||||
unsafe { memsec::free_memfd_secret(ptr) };
|
||||
result
|
||||
});
|
||||
*IS_SUPPORTED
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_memfd_secret_kv_store() {
|
||||
let mut store = MemfdSecretKVStore::new();
|
||||
let key = "test_key".to_string();
|
||||
let value = vec![1, 2, 3, 4, 5];
|
||||
|
||||
store.put(key.clone(), &value);
|
||||
assert!(store.has(&key));
|
||||
assert_eq!(store.get(&key), Some(value.clone()));
|
||||
|
||||
store.remove(&key);
|
||||
assert!(!store.has(&key));
|
||||
assert_eq!(store.get(&key), None);
|
||||
}
|
||||
}
|
||||
80
apps/desktop/desktop_native/core/src/secure_memory/mlock.rs
Normal file
80
apps/desktop/desktop_native/core/src/secure_memory/mlock.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::secure_memory::SecureMemoryStore;
|
||||
|
||||
/// The mlock store protects the data using the `mlock`. This prevents swapping to disk
|
||||
/// but does not provide protection against user-mode memory dumps or debugger access.
|
||||
pub(super) struct MlockSecretKVStore {
|
||||
map: HashMap<String, std::ptr::NonNull<[u8]>>,
|
||||
}
|
||||
|
||||
impl MlockSecretKVStore {
|
||||
pub(super) fn new() -> Self {
|
||||
MlockSecretKVStore {
|
||||
map: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SecureMemoryStore for MlockSecretKVStore {
|
||||
fn put(&mut self, key: String, value: &[u8]) {
|
||||
let mut ptr: std::ptr::NonNull<[u8]> =
|
||||
unsafe { memsec::malloc_sized(value.len()).expect("malloc_sized should work") };
|
||||
unsafe {
|
||||
std::ptr::copy_nonoverlapping(value.as_ptr(), ptr.as_mut().as_mut_ptr(), value.len());
|
||||
}
|
||||
self.map.insert(key, ptr);
|
||||
}
|
||||
|
||||
fn get(&self, key: &str) -> Option<Vec<u8>> {
|
||||
let ptr = self.map.get(key)?;
|
||||
let value = unsafe { ptr.as_ref() };
|
||||
Some(value.to_vec())
|
||||
}
|
||||
|
||||
fn has(&self, key: &str) -> bool {
|
||||
self.map.contains_key(key)
|
||||
}
|
||||
|
||||
fn remove(&mut self, key: &str) {
|
||||
if let Some(value) = self.map.remove(key) {
|
||||
unsafe {
|
||||
memsec::free(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
for (_, value) in self.map.drain() {
|
||||
unsafe {
|
||||
memsec::free(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MlockSecretKVStore {
|
||||
fn drop(&mut self) {
|
||||
self.clear();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_mlock_secret_kv_store() {
|
||||
let mut store = MlockSecretKVStore::new();
|
||||
let key = "test_key".to_string();
|
||||
let value = vec![1, 2, 3, 4, 5];
|
||||
|
||||
store.put(key.clone(), &value);
|
||||
assert!(store.has(&key));
|
||||
assert_eq!(store.get(&key), Some(value.clone()));
|
||||
|
||||
store.remove(&key);
|
||||
assert!(!store.has(&key));
|
||||
assert_eq!(store.get(&key), None);
|
||||
}
|
||||
}
|
||||
44
apps/desktop/desktop_native/core/src/secure_memory/mod.rs
Normal file
44
apps/desktop/desktop_native/core/src/secure_memory/mod.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
#[cfg(target_os = "windows")]
|
||||
mod dpapi;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod memfd_secret;
|
||||
mod mlock;
|
||||
|
||||
/// The secure memory store provides an ephemeral key-value store for sensitive data.
|
||||
/// Data stored in this store is prevented from being swapped to disk and zeroed out. Additionally,
|
||||
/// platform-specific protections are applied to prevent memory dumps or debugger access from
|
||||
/// reading the stored values.
|
||||
pub trait SecureMemoryStore {
|
||||
/// Stores a copy of the provided value in secure memory.
|
||||
fn put(&mut self, key: String, value: &[u8]);
|
||||
/// Retrieves a copy of the value associated with the given key from secure memory.
|
||||
/// This copy does not have additional memory protections applied, and should be zeroed when no
|
||||
/// longer needed.
|
||||
fn get(&self, key: &str) -> Option<Vec<u8>>;
|
||||
/// Checks if a value is stored under the given key.
|
||||
fn has(&self, key: &str) -> bool;
|
||||
/// Removes the value associated with the given key from secure memory.
|
||||
fn remove(&mut self, key: &str);
|
||||
/// Clears all values stored in secure memory.
|
||||
fn clear(&mut self);
|
||||
}
|
||||
|
||||
/// Creates a new secure memory store based on the platform.
|
||||
pub fn create_secure_memory_store() -> Box<dyn SecureMemoryStore> {
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if memfd_secret::is_supported() {
|
||||
Box::new(memfd_secret::MemfdSecretKVStore::new())
|
||||
} else {
|
||||
Box::new(mlock::MlockSecretKVStore::new())
|
||||
}
|
||||
}
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
Box::new(dpapi::DpapiSecretKVStore::new())
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
Box::new(mlock::MlockSecretKVStore::new())
|
||||
}
|
||||
}
|
||||
10
apps/desktop/desktop_native/napi/index.d.ts
vendored
10
apps/desktop/desktop_native/napi/index.d.ts
vendored
@@ -198,3 +198,13 @@ export declare namespace logging {
|
||||
}
|
||||
export function initNapiLog(jsLogFn: (err: Error | null, arg0: LogLevel, arg1: string) => any): void
|
||||
}
|
||||
export declare namespace secure_memory {
|
||||
export class SecureMemoryStoreWrapper {
|
||||
constructor()
|
||||
set(key: string, value: Uint8Array): void
|
||||
get(key: string): Buffer | null
|
||||
has(key: string): boolean
|
||||
remove(key: string): void
|
||||
clear(): void
|
||||
}
|
||||
}
|
||||
|
||||
@@ -875,3 +875,48 @@ pub mod logging {
|
||||
fn flush(&self) {}
|
||||
}
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub mod secure_memory {
|
||||
use desktop_core::secure_memory::SecureMemoryStore;
|
||||
use napi::bindgen_prelude::Buffer;
|
||||
|
||||
#[napi]
|
||||
pub struct SecureMemoryStoreWrapper(Box<dyn SecureMemoryStore>);
|
||||
|
||||
#[napi]
|
||||
impl SecureMemoryStoreWrapper {
|
||||
#[napi(constructor)]
|
||||
pub fn new() -> Self {
|
||||
let memory_store = desktop_core::secure_memory::create_secure_memory_store();
|
||||
SecureMemoryStoreWrapper(memory_store)
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn set(&mut self, key: String, value: &[u8]) {
|
||||
self.0.put(key, value);
|
||||
}
|
||||
#[napi]
|
||||
pub fn get(&self, key: String) -> Option<Buffer> {
|
||||
self.0.get(&key).map(Buffer::from)
|
||||
}
|
||||
#[napi]
|
||||
pub fn has(&self, key: String) -> bool {
|
||||
self.0.has(&key)
|
||||
}
|
||||
#[napi]
|
||||
pub fn remove(&mut self, key: String) {
|
||||
self.0.remove(&key);
|
||||
}
|
||||
#[napi]
|
||||
pub fn clear(&mut self) {
|
||||
self.0.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SecureMemoryStoreWrapper {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user