1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-13 23:03:32 +00:00

move sandbox code into macos.rs

This commit is contained in:
John Harrington
2025-11-20 09:09:11 -07:00
parent 4539589326
commit 61b26241ad
3 changed files with 79 additions and 242 deletions

View File

@@ -2,10 +2,87 @@ use anyhow::{anyhow, Result};
use async_trait::async_trait; use async_trait::async_trait;
use security_framework::passwords::get_generic_password; use security_framework::passwords::get_generic_password;
use crate::chromium::{BrowserConfig, CryptoService, LocalState}; #[cfg(feature = "sandbox")]
use std::ffi::CString;
#[cfg(feature = "sandbox")]
use std::os::raw::c_char;
use crate::chromium::{BrowserConfig, CryptoService, LocalState};
use crate::util; use crate::util;
//
// Sandbox
//
#[cfg(feature = "sandbox")]
extern "C" {
fn requestBrowserAccess(browser_name: *const c_char) -> *mut c_char;
fn hasStoredBrowserAccess(browser_name: *const c_char) -> bool;
fn startBrowserAccess(browser_name: *const c_char) -> *mut c_char;
fn stopBrowserAccess(browser_name: *const c_char);
}
#[cfg(feature = "sandbox")]
pub struct ScopedBrowserAccess {
browser_name: String,
}
#[cfg(feature = "sandbox")]
impl ScopedBrowserAccess {
pub fn request_only(browser_name: &str) -> Result<()> {
let c_name = CString::new(browser_name)?;
let bookmark_ptr = unsafe { requestBrowserAccess(c_name.as_ptr()) };
if bookmark_ptr.is_null() {
return Err(anyhow!("User declined access or browser not found"));
}
unsafe { libc::free(bookmark_ptr as *mut libc::c_void) };
Ok(())
}
pub fn request_and_start(browser_name: &str) -> Result<Self> {
Self::request_only(browser_name)?;
Self::resume(browser_name)
}
/// Resume access using previously stored bookmark
pub fn resume(browser_name: &str) -> Result<Self> {
let c_name = CString::new(browser_name)?;
if !unsafe { hasStoredBrowserAccess(c_name.as_ptr()) } {
return Err(anyhow!("No stored access for this browser"));
}
let path_ptr = unsafe { startBrowserAccess(c_name.as_ptr()) };
if path_ptr.is_null() {
return Err(anyhow!("Failed to resume access (bookmark may be stale)"));
}
unsafe { libc::free(path_ptr as *mut libc::c_void) };
Ok(Self {
browser_name: browser_name.to_string(),
})
}
pub fn has_stored_access(browser_name: &str) -> bool {
let Ok(c_name) = CString::new(browser_name) else {
return false;
};
unsafe { hasStoredBrowserAccess(c_name.as_ptr()) }
}
}
#[cfg(feature = "sandbox")]
impl Drop for ScopedBrowserAccess {
fn drop(&mut self) {
let Ok(c_name) = CString::new(self.browser_name.as_str()) else {
return;
};
unsafe { stopBrowserAccess(c_name.as_ptr()) };
}
}
// //
// Public API // Public API
// //

View File

@@ -1,236 +0,0 @@
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use security_framework::passwords::get_generic_password;
use std::ffi::CString;
use std::os::raw::c_char;
use crate::chromium::{BrowserConfig, CryptoService, LocalState};
use crate::util;
//
// Sandbox
//
// FFI declarations
extern "C" {
fn requestBrowserAccess(browser_name: *const c_char) -> *mut c_char;
fn hasStoredBrowserAccess(browser_name: *const c_char) -> bool;
fn startBrowserAccess(browser_name: *const c_char) -> *mut c_char;
fn stopBrowserAccess(browser_name: *const c_char);
}
pub struct ScopedBrowserAccess {
browser_name: String,
}
impl ScopedBrowserAccess {
pub fn request_only(browser_name: &str) -> Result<()> {
let c_name = CString::new(browser_name)?;
let bookmark_ptr = unsafe { requestBrowserAccess(c_name.as_ptr()) };
if bookmark_ptr.is_null() {
return Err(anyhow!("User declined access or browser not found"));
}
unsafe { libc::free(bookmark_ptr as *mut libc::c_void) };
Ok(())
}
pub fn request_and_start(browser_name: &str) -> Result<Self> {
Self::request_only(browser_name)?;
Self::resume(browser_name)
}
/// Resume access using previously stored bookmark
pub fn resume(browser_name: &str) -> Result<Self> {
let c_name = CString::new(browser_name)?;
if !unsafe { hasStoredBrowserAccess(c_name.as_ptr()) } {
return Err(anyhow!("No stored access for this browser"));
}
let path_ptr = unsafe { startBrowserAccess(c_name.as_ptr()) };
if path_ptr.is_null() {
return Err(anyhow!("Failed to resume access (bookmark may be stale)"));
}
unsafe { libc::free(path_ptr as *mut libc::c_void) };
Ok(Self {
browser_name: browser_name.to_string(),
})
}
pub fn has_stored_access(browser_name: &str) -> bool {
let Ok(c_name) = CString::new(browser_name) else {
return false;
};
unsafe { hasStoredBrowserAccess(c_name.as_ptr()) }
}
}
impl Drop for ScopedBrowserAccess {
fn drop(&mut self) {
let Ok(c_name) = CString::new(self.browser_name.as_str()) else {
return;
};
unsafe { stopBrowserAccess(c_name.as_ptr()) };
}
}
//
// Existing Public API
// TODO the rest of this file exactly matches macos.rs, move sandbox code there and cfg gate it behind sandbox feature
//
pub(crate) const SUPPORTED_BROWSERS: &[BrowserConfig] = &[
BrowserConfig {
name: "Chrome",
data_dir: "Library/Application Support/Google/Chrome",
},
BrowserConfig {
name: "Chromium",
data_dir: "Library/Application Support/Chromium",
},
BrowserConfig {
name: "Microsoft Edge",
data_dir: "Library/Application Support/Microsoft Edge",
},
BrowserConfig {
name: "Brave",
data_dir: "Library/Application Support/BraveSoftware/Brave-Browser",
},
BrowserConfig {
name: "Arc",
data_dir: "Library/Application Support/Arc/User Data",
},
BrowserConfig {
name: "Opera",
data_dir: "Library/Application Support/com.operasoftware.Opera",
},
BrowserConfig {
name: "Vivaldi",
data_dir: "Library/Application Support/Vivaldi",
},
];
pub(crate) fn get_crypto_service(
browser_name: &String,
_local_state: &LocalState,
) -> Result<Box<dyn CryptoService>> {
let config = KEYCHAIN_CONFIG
.iter()
.find(|b| b.browser == browser_name)
.ok_or_else(|| anyhow!("Unsupported browser: {}", browser_name))?;
Ok(Box::new(MacCryptoService::new(config)))
}
//
// Private
//
#[derive(Debug)]
struct KeychainConfig {
browser: &'static str,
service: &'static str,
account: &'static str,
}
const KEYCHAIN_CONFIG: [KeychainConfig; SUPPORTED_BROWSERS.len()] = [
KeychainConfig {
browser: "Chrome",
service: "Chrome Safe Storage",
account: "Chrome",
},
KeychainConfig {
browser: "Chromium",
service: "Chromium Safe Storage",
account: "Chromium",
},
KeychainConfig {
browser: "Microsoft Edge",
service: "Microsoft Edge Safe Storage",
account: "Microsoft Edge",
},
KeychainConfig {
browser: "Brave",
service: "Brave Safe Storage",
account: "Brave",
},
KeychainConfig {
browser: "Arc",
service: "Arc Safe Storage",
account: "Arc",
},
KeychainConfig {
browser: "Opera",
service: "Opera Safe Storage",
account: "Opera",
},
KeychainConfig {
browser: "Vivaldi",
service: "Vivaldi Safe Storage",
account: "Vivaldi",
},
];
const IV: [u8; 16] = [0x20; 16]; // 16 bytes of 0x20 (space character)
//
// CryptoService
//
struct MacCryptoService {
config: &'static KeychainConfig,
master_key: Option<Vec<u8>>,
}
impl MacCryptoService {
fn new(config: &'static KeychainConfig) -> Self {
Self {
config,
master_key: None,
}
}
}
#[async_trait]
impl CryptoService for MacCryptoService {
async fn decrypt_to_string(&mut self, encrypted: &[u8]) -> Result<String> {
if encrypted.is_empty() {
return Ok(String::new());
}
// On macOS only v10 is supported
let (_, no_prefix) = util::split_encrypted_string_and_validate(encrypted, &["v10"])?;
// This might bring up the admin password prompt
if self.master_key.is_none() {
self.master_key = Some(get_master_key(self.config.service, self.config.account)?);
}
let key = self
.master_key
.as_ref()
.ok_or_else(|| anyhow!("Failed to retrieve key"))?;
let plaintext = util::decrypt_aes_128_cbc(key, &IV, no_prefix)
.map_err(|e| anyhow!("Failed to decrypt: {}", e))?;
let plaintext =
String::from_utf8(plaintext).map_err(|e| anyhow!("Invalid UTF-8: {}", e))?;
Ok(plaintext)
}
}
fn get_master_key(service: &str, account: &str) -> Result<Vec<u8>> {
let master_password = get_master_password(service, account)?;
let key = util::derive_saltysalt(&master_password, 1003)?;
Ok(key)
}
fn get_master_password(service: &str, account: &str) -> Result<Vec<u8>> {
let password = get_generic_password(service, account)
.map_err(|e| anyhow!("Failed to get password from keychain: {}", e))?;
Ok(password)
}

View File

@@ -1,11 +1,7 @@
// Platform-specific code // Platform-specific code
#[cfg_attr(target_os = "linux", path = "linux.rs")] #[cfg_attr(target_os = "linux", path = "linux.rs")]
#[cfg_attr(target_os = "windows", path = "windows/mod.rs")] #[cfg_attr(target_os = "windows", path = "windows/mod.rs")]
#[cfg_attr(all(target_os = "macos", not(feature = "sandbox")), path = "macos.rs")] #[cfg_attr(target_os = "macos", path = "macos.rs")]
// TODO rework this to place sandbox code in macos and cfg gate it there
#[cfg_attr(all(target_os = "macos", feature = "sandbox"), path = "macos_sandbox.rs")]
mod native; mod native;
// Windows exposes public const // Windows exposes public const