mirror of
https://github.com/bitwarden/browser
synced 2026-02-08 12:40:26 +00:00
Fix merge conflict
This commit is contained in:
26
apps/desktop/desktop_native/Cargo.lock
generated
26
apps/desktop/desktop_native/Cargo.lock
generated
@@ -937,6 +937,7 @@ dependencies = [
|
||||
"rsa",
|
||||
"russh-cryptovec",
|
||||
"scopeguard",
|
||||
"secmem-proc",
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"serde",
|
||||
@@ -2797,6 +2798,16 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix-linux-procfs"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fc84bf7e9aa16c4f2c758f27412dc9841341e16aa682d9c7ac308fe3ee12056"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"rustix 1.0.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.20"
|
||||
@@ -2875,6 +2886,21 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "secmem-proc"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "473559b1d28f530c3a9b5f91a2866053e2b1c528a0e43dae83048139c99490c2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"rustix 1.0.7",
|
||||
"rustix-linux-procfs",
|
||||
"thiserror 2.0.12",
|
||||
"windows 0.61.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "3.1.0"
|
||||
|
||||
@@ -50,6 +50,7 @@ rand = "=0.9.1"
|
||||
rsa = "=0.9.6"
|
||||
russh-cryptovec = "=0.7.3"
|
||||
scopeguard = "=1.2.0"
|
||||
secmem-proc = "=0.3.7"
|
||||
security-framework = "=3.1.0"
|
||||
security-framework-sys = "=2.13.0"
|
||||
serde = "=1.0.209"
|
||||
|
||||
@@ -40,8 +40,9 @@ rand = { workspace = true }
|
||||
rsa = { workspace = true }
|
||||
russh-cryptovec = { workspace = true }
|
||||
scopeguard = { workspace = true }
|
||||
secmem-proc = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json.workspace = true
|
||||
serde_json {workspace = true }
|
||||
sha2 = { workspace = true }
|
||||
ssh-encoding = { workspace = true }
|
||||
ssh-key = { workspace = true, features = [
|
||||
|
||||
@@ -20,6 +20,8 @@ pub fn disable_coredumps() -> Result<()> {
|
||||
rlim_cur: 0,
|
||||
rlim_max: 0,
|
||||
};
|
||||
println!("[Process Isolation] Disabling core dumps via setrlimit");
|
||||
|
||||
if unsafe { libc::setrlimit(RLIMIT_CORE, &rlimit) } != 0 {
|
||||
let e = std::io::Error::last_os_error();
|
||||
return Err(anyhow::anyhow!(
|
||||
@@ -44,11 +46,17 @@ pub fn is_core_dumping_disabled() -> Result<bool> {
|
||||
Ok(rlimit.rlim_cur == 0 && rlimit.rlim_max == 0)
|
||||
}
|
||||
|
||||
pub fn disable_memory_access() -> Result<()> {
|
||||
pub fn isolate_process() -> Result<()> {
|
||||
let pid = std::process::id();
|
||||
println!(
|
||||
"[Process Isolation] Disabling ptrace and memory access for main ({}) via PR_SET_DUMPABLE",
|
||||
pid
|
||||
);
|
||||
|
||||
if unsafe { libc::prctl(PR_SET_DUMPABLE, 0) } != 0 {
|
||||
let e = std::io::Error::last_os_error();
|
||||
return Err(anyhow::anyhow!(
|
||||
"failed to disable memory dumping, memory is dumpable by other processes {}",
|
||||
"failed to disable memory dumping, memory may be accessible by other processes {}",
|
||||
e
|
||||
));
|
||||
}
|
||||
|
||||
@@ -8,6 +8,17 @@ pub fn is_core_dumping_disabled() -> Result<bool> {
|
||||
bail!("Not implemented on Mac")
|
||||
}
|
||||
|
||||
pub fn disable_memory_access() -> Result<()> {
|
||||
bail!("Not implemented on Mac")
|
||||
pub fn isolate_process() -> Result<()> {
|
||||
let pid: u32 = std::process::id();
|
||||
println!(
|
||||
"[Process Isolation] Disabling ptrace on main process ({}) via PT_DENY_ATTACH",
|
||||
pid
|
||||
);
|
||||
|
||||
secmem_proc::harden_process().map_err(|e| {
|
||||
anyhow::anyhow!(
|
||||
"failed to disable memory dumping, memory may be accessible by other processes {}",
|
||||
e
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,3 +1,16 @@
|
||||
//! This module implements process isolation, which aims to protect
|
||||
//! a process from dumping memory to disk when crashing, and from
|
||||
//! userspace memory access.
|
||||
//!
|
||||
//! On Windows, by default most userspace apps can read the memory of all
|
||||
//! other apps, and attach debuggers. On Mac, this is not possible, and only
|
||||
//! after granting developer permissions can an app attach to processes via
|
||||
//! ptrace / read memory. On Linux, this depends on the distro / configuration of yama
|
||||
//! `https://linux-audit.com/protect-ptrace-processes-kernel-yama-ptrace_scope/`
|
||||
//! For instance, ubuntu prevents ptrace of other processes by default.
|
||||
//! On Fedora, there are change proposals but ptracing is still possible unless
|
||||
//! otherwise configured.
|
||||
|
||||
#[allow(clippy::module_inception)]
|
||||
#[cfg_attr(target_os = "linux", path = "linux.rs")]
|
||||
#[cfg_attr(target_os = "windows", path = "windows.rs")]
|
||||
|
||||
@@ -8,6 +8,17 @@ pub fn is_core_dumping_disabled() -> Result<bool> {
|
||||
bail!("Not implemented on Windows")
|
||||
}
|
||||
|
||||
pub fn disable_memory_access() -> Result<()> {
|
||||
bail!("Not implemented on Windows")
|
||||
pub fn isolate_process() -> Result<()> {
|
||||
let pid: u32 = std::process::id();
|
||||
println!(
|
||||
"[Process Isolation] Isolating main process via DACL {}",
|
||||
pid
|
||||
);
|
||||
|
||||
secmem_proc::harden_process().map_err(|e| {
|
||||
anyhow::anyhow!(
|
||||
"failed to isolate process, memory may be accessible by other processes {}",
|
||||
e
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
2
apps/desktop/desktop_native/napi/index.d.ts
vendored
2
apps/desktop/desktop_native/napi/index.d.ts
vendored
@@ -94,7 +94,7 @@ export declare namespace sshagent {
|
||||
export declare namespace processisolations {
|
||||
export function disableCoredumps(): Promise<void>
|
||||
export function isCoreDumpingDisabled(): Promise<boolean>
|
||||
export function disableMemoryAccess(): Promise<void>
|
||||
export function isolateProcess(): Promise<void>
|
||||
}
|
||||
export declare namespace powermonitors {
|
||||
export function onLock(callback: (err: Error | null, ) => any): Promise<void>
|
||||
|
||||
@@ -455,8 +455,8 @@ pub mod processisolations {
|
||||
|
||||
#[allow(clippy::unused_async)] // FIXME: Remove unused async!
|
||||
#[napi]
|
||||
pub async fn disable_memory_access() -> napi::Result<()> {
|
||||
desktop_core::process_isolation::disable_memory_access()
|
||||
pub async fn isolate_process() -> napi::Result<()> {
|
||||
desktop_core::process_isolation::isolate_process()
|
||||
.map_err(|e| napi::Error::from_reason(e.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ export class WindowMain {
|
||||
private windowStateChangeTimer: NodeJS.Timeout;
|
||||
private windowStates: { [key: string]: WindowState } = {};
|
||||
private enableAlwaysOnTop = false;
|
||||
private enableRendererProcessForceCrashReload = false;
|
||||
private enableRendererProcessForceCrashReload = true;
|
||||
session: Electron.Session;
|
||||
|
||||
readonly defaultWidth = 950;
|
||||
@@ -154,28 +154,31 @@ export class WindowMain {
|
||||
// initialization and is ready to create browser windows.
|
||||
// Some APIs can only be used after this event occurs.
|
||||
app.on("ready", async () => {
|
||||
if (isMac() || isWindows()) {
|
||||
this.enableRendererProcessForceCrashReload = true;
|
||||
} else if (isLinux() && !isDev()) {
|
||||
if (await processisolations.isCoreDumpingDisabled()) {
|
||||
this.logService.info("Coredumps are disabled in renderer process");
|
||||
this.enableRendererProcessForceCrashReload = true;
|
||||
} else {
|
||||
this.logService.info("Disabling coredumps in main process");
|
||||
if (!isDev()) {
|
||||
// This currently breaks the file portal for snap https://github.com/flatpak/xdg-desktop-portal/issues/785
|
||||
if (!isSnapStore()) {
|
||||
this.logService.info(
|
||||
"[Process Isolation] Isolating process from debuggers and memory dumps",
|
||||
);
|
||||
try {
|
||||
await processisolations.disableCoredumps();
|
||||
await processisolations.isolateProcess();
|
||||
} catch (e) {
|
||||
this.logService.error("Failed to disable coredumps", e);
|
||||
this.logService.error("[Process Isolation] Failed to isolate main process", e);
|
||||
}
|
||||
}
|
||||
|
||||
// this currently breaks the file portal for snap https://github.com/flatpak/xdg-desktop-portal/issues/785
|
||||
if (!isSnapStore()) {
|
||||
this.logService.info("Disabling memory dumps in main process");
|
||||
try {
|
||||
await processisolations.disableMemoryAccess();
|
||||
} catch (e) {
|
||||
this.logService.error("Failed to disable memory dumps", e);
|
||||
if (isLinux()) {
|
||||
if (await processisolations.isCoreDumpingDisabled()) {
|
||||
this.logService.info("Coredumps are disabled in renderer process");
|
||||
} else {
|
||||
this.enableRendererProcessForceCrashReload = false;
|
||||
this.logService.info("Disabling coredumps in main process");
|
||||
try {
|
||||
await processisolations.disableCoredumps();
|
||||
this.enableRendererProcessForceCrashReload = true;
|
||||
} catch (e) {
|
||||
this.logService.error("Failed to disable coredumps", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,6 +172,7 @@ export class DefaultServerNotificationsService implements ServerNotificationsSer
|
||||
|
||||
private hasAccessToken$(userId: UserId) {
|
||||
return this.configService.getFeatureFlag$(FeatureFlag.PushNotificationsWhenLocked).pipe(
|
||||
distinctUntilChanged(),
|
||||
switchMap((featureFlagEnabled) => {
|
||||
if (featureFlagEnabled) {
|
||||
return this.authService.authStatusFor$(userId).pipe(
|
||||
@@ -305,11 +306,23 @@ export class DefaultServerNotificationsService implements ServerNotificationsSer
|
||||
startListening() {
|
||||
return this.notifications$
|
||||
.pipe(
|
||||
mergeMap(async ([notification, userId]) => this.processNotification(notification, userId)),
|
||||
mergeMap(async ([notification, userId]) => {
|
||||
try {
|
||||
await this.processNotification(notification, userId);
|
||||
} catch (err: unknown) {
|
||||
this.logService.error(
|
||||
`Problem processing notification of type ${notification.type}`,
|
||||
err,
|
||||
);
|
||||
}
|
||||
}),
|
||||
)
|
||||
.subscribe({
|
||||
error: (e: unknown) =>
|
||||
this.logService.warning("Error in server notifications$ observable", e),
|
||||
error: (err: unknown) =>
|
||||
this.logService.error(
|
||||
"Fatal error in server notifications$ observable, notifications won't be recieved anymore.",
|
||||
err,
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -27,10 +27,9 @@ import {
|
||||
UnsignedSharedKey,
|
||||
} from "@bitwarden/sdk-internal";
|
||||
|
||||
import { EncryptedOrganizationKeyData } from "../../../admin-console/models/data/encrypted-organization-key.data";
|
||||
import { AccountInfo, AccountService } from "../../../auth/abstractions/account.service";
|
||||
import { DeviceType } from "../../../enums/device-type.enum";
|
||||
import { EncryptedString } from "../../../key-management/crypto/models/enc-string";
|
||||
import { EncryptedString, EncString } from "../../../key-management/crypto/models/enc-string";
|
||||
import { OrganizationId, UserId } from "../../../types/guid";
|
||||
import { UserKey } from "../../../types/key";
|
||||
import { Environment, EnvironmentService } from "../../abstractions/environment.service";
|
||||
@@ -220,7 +219,7 @@ export class DefaultSdkService implements SdkService {
|
||||
kdfParams: KdfConfig,
|
||||
privateKey: EncryptedString,
|
||||
userKey: UserKey,
|
||||
orgKeys: Record<OrganizationId, EncryptedOrganizationKeyData> | null,
|
||||
orgKeys: Record<OrganizationId, EncString>,
|
||||
) {
|
||||
await client.crypto().initialize_user_crypto({
|
||||
userId: asUuid(userId),
|
||||
@@ -245,9 +244,7 @@ export class DefaultSdkService implements SdkService {
|
||||
// null to make sure any existing org keys are cleared.
|
||||
await client.crypto().initialize_org_crypto({
|
||||
organizationKeys: new Map(
|
||||
Object.entries(orgKeys ?? {})
|
||||
.filter(([_, v]) => v.type === "organization")
|
||||
.map(([k, v]) => [asUuid(k), v.key as UnsignedSharedKey]),
|
||||
Object.entries(orgKeys).map(([k, v]) => [asUuid(k), v.toJSON() as UnsignedSharedKey]),
|
||||
),
|
||||
});
|
||||
|
||||
|
||||
@@ -257,6 +257,10 @@ export abstract class CipherService implements UserKeyRotationDataProvider<Ciphe
|
||||
* @param attachment The attachment view object
|
||||
* @param response The response object containing the encrypted content
|
||||
* @param userId The user ID whose key will be used for decryption
|
||||
* @param useLegacyDecryption When true, forces the use of the legacy decryption method
|
||||
* even when the SDK feature is enabled. This is helpful for domains of
|
||||
* the application that have yet to be moved into the SDK, i.e. emergency access.
|
||||
* TODO: PM-25469 - this should be obsolete once emergency access is moved to the SDK.
|
||||
*
|
||||
* @returns A promise that resolves to the decrypted content
|
||||
*/
|
||||
@@ -265,6 +269,7 @@ export abstract class CipherService implements UserKeyRotationDataProvider<Ciphe
|
||||
attachment: AttachmentView,
|
||||
response: Response,
|
||||
userId: UserId,
|
||||
useLegacyDecryption?: boolean,
|
||||
): Promise<Uint8Array | null>;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1544,11 +1544,13 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
return encryptedCiphers;
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
async getDecryptedAttachmentBuffer(
|
||||
cipherId: CipherId,
|
||||
attachment: AttachmentView,
|
||||
response: Response,
|
||||
userId: UserId,
|
||||
useLegacyDecryption?: boolean,
|
||||
): Promise<Uint8Array> {
|
||||
const useSdkDecryption = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.PM19941MigrateCipherDomainToSdk,
|
||||
@@ -1558,7 +1560,7 @@ export class CipherService implements CipherServiceAbstraction {
|
||||
this.ciphers$(userId).pipe(map((ciphersData) => new Cipher(ciphersData[cipherId]))),
|
||||
);
|
||||
|
||||
if (useSdkDecryption) {
|
||||
if (useSdkDecryption && !useLegacyDecryption) {
|
||||
const encryptedContent = await response.arrayBuffer();
|
||||
return this.cipherEncryptionService.decryptAttachmentContent(
|
||||
cipherDomain,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { EncryptedOrganizationKeyData } from "@bitwarden/common/admin-console/models/data/encrypted-organization-key.data";
|
||||
import { ProfileOrganizationResponse } from "@bitwarden/common/admin-console/models/response/profile-organization.response";
|
||||
import { ProfileProviderOrganizationResponse } from "@bitwarden/common/admin-console/models/response/profile-provider-organization.response";
|
||||
import { ProfileProviderResponse } from "@bitwarden/common/admin-console/models/response/profile-provider.response";
|
||||
@@ -406,9 +405,7 @@ export abstract class KeyService {
|
||||
* @deprecated Temporary function to allow the SDK to be initialized after the login process, it
|
||||
* will be removed when auth has been migrated to the SDK.
|
||||
*/
|
||||
abstract encryptedOrgKeys$(
|
||||
userId: UserId,
|
||||
): Observable<Record<OrganizationId, EncryptedOrganizationKeyData> | null>;
|
||||
abstract encryptedOrgKeys$(userId: UserId): Observable<Record<OrganizationId, EncString>>;
|
||||
|
||||
/**
|
||||
* Gets an observable stream of the users public key. If the user is does not have
|
||||
|
||||
@@ -907,10 +907,57 @@ export class DefaultKeyService implements KeyServiceAbstraction {
|
||||
return this.cipherDecryptionKeys$(userId).pipe(map((keys) => keys?.orgKeys ?? null));
|
||||
}
|
||||
|
||||
encryptedOrgKeys$(
|
||||
userId: UserId,
|
||||
): Observable<Record<OrganizationId, EncryptedOrganizationKeyData> | null> {
|
||||
return this.stateProvider.getUser(userId, USER_ENCRYPTED_ORGANIZATION_KEYS).state$;
|
||||
encryptedOrgKeys$(userId: UserId): Observable<Record<OrganizationId, EncString>> {
|
||||
return this.userPrivateKey$(userId)?.pipe(
|
||||
switchMap((userPrivateKey) => {
|
||||
if (userPrivateKey == null) {
|
||||
// We can't do any org based decryption
|
||||
return of({});
|
||||
}
|
||||
|
||||
return combineLatest([
|
||||
this.stateProvider.getUser(userId, USER_ENCRYPTED_ORGANIZATION_KEYS).state$,
|
||||
this.providerKeysHelper$(userId, userPrivateKey),
|
||||
]).pipe(
|
||||
switchMap(async ([encryptedOrgKeys, providerKeys]) => {
|
||||
const userPubKey = await this.derivePublicKey(userPrivateKey);
|
||||
|
||||
const result: Record<OrganizationId, EncString> = {};
|
||||
encryptedOrgKeys = encryptedOrgKeys ?? {};
|
||||
for (const orgId of Object.keys(encryptedOrgKeys) as OrganizationId[]) {
|
||||
if (result[orgId] != null) {
|
||||
continue;
|
||||
}
|
||||
const encrypted = BaseEncryptedOrganizationKey.fromData(encryptedOrgKeys[orgId]);
|
||||
if (encrypted == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let orgKey: EncString;
|
||||
|
||||
// Because the SDK only supports user encrypted org keys, we need to re-encrypt
|
||||
// any provider encrypted org keys with the user's public key. This should be removed
|
||||
// once the SDK has support for provider keys.
|
||||
if (BaseEncryptedOrganizationKey.isProviderEncrypted(encrypted)) {
|
||||
if (providerKeys == null) {
|
||||
continue;
|
||||
}
|
||||
orgKey = await this.encryptService.encapsulateKeyUnsigned(
|
||||
await encrypted.decrypt(this.encryptService, providerKeys!),
|
||||
userPubKey!,
|
||||
);
|
||||
} else {
|
||||
orgKey = encrypted.encryptedOrganizationKey;
|
||||
}
|
||||
|
||||
result[orgId] = orgKey;
|
||||
}
|
||||
|
||||
return result;
|
||||
}),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
cipherDecryptionKeys$(userId: UserId): Observable<CipherDecryptionKeys | null> {
|
||||
|
||||
@@ -92,6 +92,9 @@ export class DownloadAttachmentComponent {
|
||||
this.attachment,
|
||||
response,
|
||||
userId,
|
||||
// When the emergency access ID is present, the cipher is being viewed via emergency access.
|
||||
// Force legacy decryption in these cases.
|
||||
this.emergencyAccessId ? true : false,
|
||||
);
|
||||
|
||||
this.fileDownloadService.download({
|
||||
|
||||
Reference in New Issue
Block a user