diff --git a/apps/desktop/desktop_native/Cargo.lock b/apps/desktop/desktop_native/Cargo.lock index 05663ea7e0b..00129c1e548 100644 --- a/apps/desktop/desktop_native/Cargo.lock +++ b/apps/desktop/desktop_native/Cargo.lock @@ -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]] diff --git a/apps/desktop/desktop_native/core/Cargo.toml b/apps/desktop/desktop_native/core/Cargo.toml index 7cd67dbad6a..577524e4787 100644 --- a/apps/desktop/desktop_native/core/Cargo.toml +++ b/apps/desktop/desktop_native/core/Cargo.toml @@ -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 } diff --git a/apps/desktop/desktop_native/core/src/lib.rs b/apps/desktop/desktop_native/core/src/lib.rs index a72ec04e9c2..55663f0cc21 100644 --- a/apps/desktop/desktop_native/core/src/lib.rs +++ b/apps/desktop/desktop_native/core/src/lib.rs @@ -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; diff --git a/apps/desktop/desktop_native/core/src/secure_memory/dpapi.rs b/apps/desktop/desktop_native/core/src/secure_memory/dpapi.rs new file mode 100644 index 00000000000..f47e934f451 --- /dev/null +++ b/apps/desktop/desktop_native/core/src/secure_memory/dpapi.rs @@ -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>, +} + +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::(); + + // 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> { + 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::(); + 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)); + } +} diff --git a/apps/desktop/desktop_native/core/src/secure_memory/memfd_secret.rs b/apps/desktop/desktop_native/core/src/secure_memory/memfd_secret.rs new file mode 100644 index 00000000000..d86f365ca4c --- /dev/null +++ b/apps/desktop/desktop_native/core/src/secure_memory/memfd_secret.rs @@ -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>, +} + +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> { + 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 = LazyLock::new(|| { + let Some(ptr): Option> = (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); + } +} diff --git a/apps/desktop/desktop_native/core/src/secure_memory/mlock.rs b/apps/desktop/desktop_native/core/src/secure_memory/mlock.rs new file mode 100644 index 00000000000..29bc5fc3d70 --- /dev/null +++ b/apps/desktop/desktop_native/core/src/secure_memory/mlock.rs @@ -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>, +} + +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> { + 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); + } +} diff --git a/apps/desktop/desktop_native/core/src/secure_memory/mod.rs b/apps/desktop/desktop_native/core/src/secure_memory/mod.rs new file mode 100644 index 00000000000..7b1f1ab913f --- /dev/null +++ b/apps/desktop/desktop_native/core/src/secure_memory/mod.rs @@ -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>; + /// 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 { + #[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()) + } +} diff --git a/apps/desktop/desktop_native/napi/index.d.ts b/apps/desktop/desktop_native/napi/index.d.ts index b3c6f715e98..ef45394392c 100644 --- a/apps/desktop/desktop_native/napi/index.d.ts +++ b/apps/desktop/desktop_native/napi/index.d.ts @@ -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 + } +} diff --git a/apps/desktop/desktop_native/napi/src/lib.rs b/apps/desktop/desktop_native/napi/src/lib.rs index 079872a3b03..223ccc8c160 100644 --- a/apps/desktop/desktop_native/napi/src/lib.rs +++ b/apps/desktop/desktop_native/napi/src/lib.rs @@ -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); + + #[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 { + 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() + } + } +}