mirror of
https://github.com/bitwarden/browser
synced 2025-12-15 07:43:35 +00:00
migrate native messaging for biometrics to use new key model
- support backwards compatibility - update safari web extension to send user key - add error handling
This commit is contained in:
@@ -1599,6 +1599,12 @@
|
|||||||
"biometricsNotSupportedDesc": {
|
"biometricsNotSupportedDesc": {
|
||||||
"message": "Browser biometrics is not supported on this device."
|
"message": "Browser biometrics is not supported on this device."
|
||||||
},
|
},
|
||||||
|
"biometricsFailedTitle": {
|
||||||
|
"message": "Biometrics failed"
|
||||||
|
},
|
||||||
|
"biometricsFailedDesc": {
|
||||||
|
"message": "Biometrics cannot be completed, consider using a master password or logging out. If this persists, please contact Bitwarden support."
|
||||||
|
},
|
||||||
"nativeMessaginPermissionErrorTitle": {
|
"nativeMessaginPermissionErrorTitle": {
|
||||||
"message": "Permission not provided"
|
"message": "Permission not provided"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
|
|||||||
import {
|
import {
|
||||||
MasterKey,
|
MasterKey,
|
||||||
SymmetricCryptoKey,
|
SymmetricCryptoKey,
|
||||||
|
UserSymKey,
|
||||||
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||||
|
|
||||||
import { BrowserApi } from "../platform/browser/browser-api";
|
import { BrowserApi } from "../platform/browser/browser-api";
|
||||||
@@ -45,6 +46,7 @@ type ReceiveMessage = {
|
|||||||
|
|
||||||
// Unlock key
|
// Unlock key
|
||||||
keyB64?: string;
|
keyB64?: string;
|
||||||
|
userKeyB64?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ReceiveMessageOuter = {
|
type ReceiveMessageOuter = {
|
||||||
@@ -323,9 +325,41 @@ export class NativeMessagingBackground {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (message.response === "unlocked") {
|
if (message.response === "unlocked") {
|
||||||
await this.cryptoService.setMasterKey(
|
try {
|
||||||
new SymmetricCryptoKey(Utils.fromB64ToArray(message.keyB64).buffer) as MasterKey
|
if (message.userKeyB64) {
|
||||||
);
|
const userKey = new SymmetricCryptoKey(
|
||||||
|
Utils.fromB64ToArray(message.userKeyB64).buffer
|
||||||
|
) as UserSymKey;
|
||||||
|
await this.cryptoService.setUserKey(userKey);
|
||||||
|
} else if (message.keyB64) {
|
||||||
|
// backwards compatibility
|
||||||
|
let encUserKey = await this.stateService.getEncryptedCryptoSymmetricKey();
|
||||||
|
encUserKey ||= await this.stateService.getUserSymKeyMasterKey();
|
||||||
|
if (!encUserKey) {
|
||||||
|
throw new Error("No encrypted user key found");
|
||||||
|
}
|
||||||
|
const masterKey = new SymmetricCryptoKey(
|
||||||
|
Utils.fromB64ToArray(message.keyB64).buffer
|
||||||
|
) as MasterKey;
|
||||||
|
const userKey = await this.cryptoService.decryptUserSymKeyWithMasterKey(
|
||||||
|
masterKey,
|
||||||
|
new EncString(encUserKey)
|
||||||
|
);
|
||||||
|
await this.cryptoService.setMasterKey(masterKey);
|
||||||
|
await this.cryptoService.setUserKey(userKey);
|
||||||
|
} else {
|
||||||
|
throw new Error("No key received");
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.logService.error("Unable to set key: " + e);
|
||||||
|
this.messagingService.send("showDialog", {
|
||||||
|
title: { key: "biometricsFailedTitle" },
|
||||||
|
content: { key: "biometricsFailedDesc" },
|
||||||
|
acceptButtonText: { key: "ok" },
|
||||||
|
cancelButtonText: null,
|
||||||
|
type: "danger",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Verify key is correct by attempting to decrypt a secret
|
// Verify key is correct by attempting to decrypt a secret
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,11 +1,22 @@
|
|||||||
import { KeySuffixOptions } from "@bitwarden/common/enums";
|
import { KeySuffixOptions } from "@bitwarden/common/enums";
|
||||||
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
|
import {
|
||||||
|
SymmetricCryptoKey,
|
||||||
|
UserSymKey,
|
||||||
|
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||||
import { CryptoService } from "@bitwarden/common/platform/services/crypto.service";
|
import { CryptoService } from "@bitwarden/common/platform/services/crypto.service";
|
||||||
|
|
||||||
export class BrowserCryptoService extends CryptoService {
|
export class BrowserCryptoService extends CryptoService {
|
||||||
protected async retrieveKeyFromStorage(keySuffix: KeySuffixOptions) {
|
protected override async retrieveUserKeyFromStorage(
|
||||||
|
keySuffix: KeySuffixOptions,
|
||||||
|
userId?: string
|
||||||
|
): Promise<UserSymKey> {
|
||||||
if (keySuffix === KeySuffixOptions.Biometric) {
|
if (keySuffix === KeySuffixOptions.Biometric) {
|
||||||
await this.platformUtilService.authenticateBiometric();
|
await this.platformUtilService.authenticateBiometric();
|
||||||
return (await this.getUserKeyFromMemory())?.keyB64;
|
const userKey = await this.getUserKeyFromMemory();
|
||||||
|
if (userKey) {
|
||||||
|
return new SymmetricCryptoKey(Utils.fromB64ToArray(userKey.keyB64).buffer) as UserSymKey;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return await super.retrieveUserKeyFromStorage(keySuffix);
|
return await super.retrieveUserKeyFromStorage(keySuffix);
|
||||||
|
|||||||
@@ -19,11 +19,11 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
|
|||||||
os_log(.default, "Received message from browser.runtime.sendNativeMessage: %@", message as! CVarArg)
|
os_log(.default, "Received message from browser.runtime.sendNativeMessage: %@", message as! CVarArg)
|
||||||
|
|
||||||
let response = NSExtensionItem()
|
let response = NSExtensionItem()
|
||||||
|
|
||||||
guard let command = message?["command"] as? String else {
|
guard let command = message?["command"] as? String else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case "readFromClipboard":
|
case "readFromClipboard":
|
||||||
let pasteboard = NSPasteboard.general
|
let pasteboard = NSPasteboard.general
|
||||||
@@ -59,12 +59,12 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
|
|||||||
guard let data = blobData else {
|
guard let data = blobData else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let panel = NSSavePanel()
|
let panel = NSSavePanel()
|
||||||
panel.canCreateDirectories = true
|
panel.canCreateDirectories = true
|
||||||
panel.nameFieldStringValue = dlMsg.fileName
|
panel.nameFieldStringValue = dlMsg.fileName
|
||||||
let response = panel.runModal();
|
let response = panel.runModal();
|
||||||
|
|
||||||
if response == NSApplication.ModalResponse.OK {
|
if response == NSApplication.ModalResponse.OK {
|
||||||
if let url = panel.url {
|
if let url = panel.url {
|
||||||
do {
|
do {
|
||||||
@@ -87,12 +87,12 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
case "biometricUnlock":
|
case "biometricUnlock":
|
||||||
|
|
||||||
var error: NSError?
|
var error: NSError?
|
||||||
let laContext = LAContext()
|
let laContext = LAContext()
|
||||||
|
|
||||||
laContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error)
|
laContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error)
|
||||||
|
|
||||||
if let e = error, e.code != kLAErrorBiometryLockout {
|
if let e = error, e.code != kLAErrorBiometryLockout {
|
||||||
response.userInfo = [
|
response.userInfo = [
|
||||||
SFExtensionMessageKey: [
|
SFExtensionMessageKey: [
|
||||||
@@ -123,26 +123,26 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
|
|||||||
guard let userId = message?["userId"] as? String else {
|
guard let userId = message?["userId"] as? String else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let passwordName = userId + "_masterkey_biometric"
|
let passwordName = userId + "_user_biometric"
|
||||||
var passwordLength: UInt32 = 0
|
var passwordLength: UInt32 = 0
|
||||||
var passwordPtr: UnsafeMutableRawPointer? = nil
|
var passwordPtr: UnsafeMutableRawPointer? = nil
|
||||||
|
|
||||||
var status = SecKeychainFindGenericPassword(nil, UInt32(ServiceNameBiometric.utf8.count), ServiceNameBiometric, UInt32(passwordName.utf8.count), passwordName, &passwordLength, &passwordPtr, nil)
|
var status = SecKeychainFindGenericPassword(nil, UInt32(ServiceNameBiometric.utf8.count), ServiceNameBiometric, UInt32(passwordName.utf8.count), passwordName, &passwordLength, &passwordPtr, nil)
|
||||||
if status != errSecSuccess {
|
if status != errSecSuccess {
|
||||||
let fallbackName = "key"
|
let fallbackName = "key"
|
||||||
status = SecKeychainFindGenericPassword(nil, UInt32(ServiceNameBiometric.utf8.count), ServiceNameBiometric, UInt32(fallbackName.utf8.count), fallbackName, &passwordLength, &passwordPtr, nil)
|
status = SecKeychainFindGenericPassword(nil, UInt32(ServiceNameBiometric.utf8.count), ServiceNameBiometric, UInt32(fallbackName.utf8.count), fallbackName, &passwordLength, &passwordPtr, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
if status == errSecSuccess {
|
if status == errSecSuccess {
|
||||||
let result = NSString(bytes: passwordPtr!, length: Int(passwordLength), encoding: String.Encoding.utf8.rawValue) as String?
|
let result = NSString(bytes: passwordPtr!, length: Int(passwordLength), encoding: String.Encoding.utf8.rawValue) as String?
|
||||||
SecKeychainItemFreeContent(nil, passwordPtr)
|
SecKeychainItemFreeContent(nil, passwordPtr)
|
||||||
|
|
||||||
response.userInfo = [ SFExtensionMessageKey: [
|
response.userInfo = [ SFExtensionMessageKey: [
|
||||||
"message": [
|
"message": [
|
||||||
"command": "biometricUnlock",
|
"command": "biometricUnlock",
|
||||||
"response": "unlocked",
|
"response": "unlocked",
|
||||||
"timestamp": Int64(NSDate().timeIntervalSince1970 * 1000),
|
"timestamp": Int64(NSDate().timeIntervalSince1970 * 1000),
|
||||||
"keyB64": result!.replacingOccurrences(of: "\"", with: ""),
|
"userKeyB64": result!.replacingOccurrences(of: "\"", with: ""),
|
||||||
],
|
],
|
||||||
]]
|
]]
|
||||||
} else {
|
} else {
|
||||||
@@ -157,10 +157,10 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context.completeRequest(returningItems: [response], completionHandler: nil)
|
context.completeRequest(returningItems: [response], completionHandler: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -136,14 +136,22 @@ export class NativeMessagingService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = await this.cryptoService.getUserKeyFromStorage(
|
const userKey = await this.cryptoService.getUserKeyFromStorage(
|
||||||
KeySuffixOptions.Biometric,
|
KeySuffixOptions.Biometric,
|
||||||
message.userId
|
message.userId
|
||||||
);
|
);
|
||||||
|
const masterKey = await this.cryptoService.getMasterKey(message.userId);
|
||||||
|
|
||||||
if (key != null) {
|
if (userKey != null) {
|
||||||
|
// we send the master key still for backwards compatibility
|
||||||
|
// with older browser extensions
|
||||||
this.send(
|
this.send(
|
||||||
{ command: "biometricUnlock", response: "unlocked", keyB64: key.keyB64 },
|
{
|
||||||
|
command: "biometricUnlock",
|
||||||
|
response: "unlocked",
|
||||||
|
keyB64: masterKey.keyB64,
|
||||||
|
userKeyB64: userKey.keyB64,
|
||||||
|
},
|
||||||
appId
|
appId
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -739,10 +739,12 @@ export class CryptoService implements CryptoServiceAbstraction {
|
|||||||
keySuffix: KeySuffixOptions,
|
keySuffix: KeySuffixOptions,
|
||||||
userId?: string
|
userId?: string
|
||||||
): Promise<UserSymKey> {
|
): Promise<UserSymKey> {
|
||||||
if (keySuffix === KeySuffixOptions.Pin) {
|
if (keySuffix === KeySuffixOptions.Auto) {
|
||||||
await this.migrateAutoKeyIfNeeded(userId);
|
await this.migrateAutoKeyIfNeeded(userId);
|
||||||
const userKey = await this.stateService.getUserSymKeyAuto({ userId: userId });
|
const userKey = await this.stateService.getUserSymKeyAuto({ userId: userId });
|
||||||
return new SymmetricCryptoKey(Utils.fromB64ToArray(userKey).buffer) as UserSymKey;
|
if (userKey) {
|
||||||
|
return new SymmetricCryptoKey(Utils.fromB64ToArray(userKey).buffer) as UserSymKey;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user