mirror of
https://github.com/bitwarden/browser
synced 2025-12-13 14:53:33 +00:00
move sandbox code into macos.rs
This commit is contained in:
@@ -2,10 +2,87 @@ use anyhow::{anyhow, Result};
|
||||
use async_trait::async_trait;
|
||||
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;
|
||||
|
||||
//
|
||||
// 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
|
||||
//
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -1,11 +1,7 @@
|
||||
// Platform-specific code
|
||||
#[cfg_attr(target_os = "linux", path = "linux.rs")]
|
||||
#[cfg_attr(target_os = "windows", path = "windows/mod.rs")]
|
||||
#[cfg_attr(all(target_os = "macos", not(feature = "sandbox")), 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")]
|
||||
|
||||
#[cfg_attr(target_os = "macos", path = "macos.rs")]
|
||||
mod native;
|
||||
|
||||
// Windows exposes public const
|
||||
|
||||
Reference in New Issue
Block a user