Add support for biometrics to Safari (#1775)
* Add Biometrics support to Safari
This commit is contained in:
parent
eeb61b019b
commit
ae4c1b29d2
|
@ -249,7 +249,8 @@ export default class MainBackground {
|
||||||
this.analytics, this.notificationsService, this.systemService, this.vaultTimeoutService,
|
this.analytics, this.notificationsService, this.systemService, this.vaultTimeoutService,
|
||||||
this.environmentService, this.policyService, this.userService, this.messagingService);
|
this.environmentService, this.policyService, this.userService, this.messagingService);
|
||||||
this.nativeMessagingBackground = new NativeMessagingBackground(this.storageService, this.cryptoService, this.cryptoFunctionService,
|
this.nativeMessagingBackground = new NativeMessagingBackground(this.storageService, this.cryptoService, this.cryptoFunctionService,
|
||||||
this.vaultTimeoutService, this.runtimeBackground, this.i18nService, this.userService, this.messagingService, this.appIdService);
|
this.vaultTimeoutService, this.runtimeBackground, this.i18nService, this.userService, this.messagingService, this.appIdService,
|
||||||
|
this.platformUtilsService);
|
||||||
this.commandsBackground = new CommandsBackground(this, this.passwordGenerationService,
|
this.commandsBackground = new CommandsBackground(this, this.passwordGenerationService,
|
||||||
this.platformUtilsService, this.analytics, this.vaultTimeoutService);
|
this.platformUtilsService, this.analytics, this.vaultTimeoutService);
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { CryptoService } from 'jslib/abstractions/crypto.service';
|
||||||
import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service';
|
import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service';
|
||||||
import { I18nService } from 'jslib/abstractions/i18n.service';
|
import { I18nService } from 'jslib/abstractions/i18n.service';
|
||||||
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
import { MessagingService } from 'jslib/abstractions/messaging.service';
|
||||||
|
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
|
||||||
import { StorageService } from 'jslib/abstractions/storage.service';
|
import { StorageService } from 'jslib/abstractions/storage.service';
|
||||||
import { UserService } from 'jslib/abstractions/user.service';
|
import { UserService } from 'jslib/abstractions/user.service';
|
||||||
import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service';
|
import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service';
|
||||||
|
@ -33,7 +34,8 @@ export class NativeMessagingBackground {
|
||||||
constructor(private storageService: StorageService, private cryptoService: CryptoService,
|
constructor(private storageService: StorageService, private cryptoService: CryptoService,
|
||||||
private cryptoFunctionService: CryptoFunctionService, private vaultTimeoutService: VaultTimeoutService,
|
private cryptoFunctionService: CryptoFunctionService, private vaultTimeoutService: VaultTimeoutService,
|
||||||
private runtimeBackground: RuntimeBackground, private i18nService: I18nService, private userService: UserService,
|
private runtimeBackground: RuntimeBackground, private i18nService: I18nService, private userService: UserService,
|
||||||
private messagingService: MessagingService, private appIdService: AppIdService) {
|
private messagingService: MessagingService, private appIdService: AppIdService,
|
||||||
|
private platformUtilsService: PlatformUtilsService) {
|
||||||
this.storageService.save(ConstantsService.biometricFingerprintValidated, false);
|
this.storageService.save(ConstantsService.biometricFingerprintValidated, false);
|
||||||
|
|
||||||
if (chrome?.permissions?.onAdded) {
|
if (chrome?.permissions?.onAdded) {
|
||||||
|
@ -48,17 +50,27 @@ export class NativeMessagingBackground {
|
||||||
this.appId = await this.appIdService.getAppId();
|
this.appId = await this.appIdService.getAppId();
|
||||||
this.storageService.save(ConstantsService.biometricFingerprintValidated, false);
|
this.storageService.save(ConstantsService.biometricFingerprintValidated, false);
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
this.port = BrowserApi.connectNative('com.8bit.bitwarden');
|
this.port = BrowserApi.connectNative('com.8bit.bitwarden');
|
||||||
|
|
||||||
this.connecting = true;
|
this.connecting = true;
|
||||||
|
|
||||||
this.port.onMessage.addListener(async (message: any) => {
|
const connectedCallback = () => {
|
||||||
switch (message.command) {
|
|
||||||
case 'connected':
|
|
||||||
this.connected = true;
|
this.connected = true;
|
||||||
this.connecting = false;
|
this.connecting = false;
|
||||||
resolve();
|
resolve();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Safari has a bundled native component which is always available, no need to
|
||||||
|
// check if the desktop app is running.
|
||||||
|
if (this.platformUtilsService.isSafari()) {
|
||||||
|
connectedCallback();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.port.onMessage.addListener(async (message: any) => {
|
||||||
|
switch (message.command) {
|
||||||
|
case 'connected':
|
||||||
|
connectedCallback();
|
||||||
break;
|
break;
|
||||||
case 'disconnected':
|
case 'disconnected':
|
||||||
if (this.connecting) {
|
if (this.connecting) {
|
||||||
|
@ -114,15 +126,10 @@ export class NativeMessagingBackground {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'wrongUserId':
|
case 'wrongUserId':
|
||||||
this.messagingService.send('showDialog', {
|
this.showWrongUserDialog();
|
||||||
text: this.i18nService.t('nativeMessagingWrongUserDesc'),
|
|
||||||
title: this.i18nService.t('nativeMessagingWrongUserTitle'),
|
|
||||||
confirmText: this.i18nService.t('ok'),
|
|
||||||
type: 'error',
|
|
||||||
});
|
|
||||||
default:
|
default:
|
||||||
// Ignore since it belongs to another device
|
// Ignore since it belongs to another device
|
||||||
if (message.appId !== this.appId) {
|
if (!this.platformUtilsService.isSafari() && message.appId !== this.appId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,19 +161,35 @@ export class NativeMessagingBackground {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showWrongUserDialog() {
|
||||||
|
this.messagingService.send('showDialog', {
|
||||||
|
text: this.i18nService.t('nativeMessagingWrongUserDesc'),
|
||||||
|
title: this.i18nService.t('nativeMessagingWrongUserTitle'),
|
||||||
|
confirmText: this.i18nService.t('ok'),
|
||||||
|
type: 'error',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async send(message: any) {
|
async send(message: any) {
|
||||||
if (!this.connected) {
|
if (!this.connected) {
|
||||||
await this.connect();
|
await this.connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.platformUtilsService.isSafari()) {
|
||||||
|
this.postMessage(message);
|
||||||
|
} else {
|
||||||
|
this.postMessage({appId: this.appId, message: await this.encryptMessage(message)});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async encryptMessage(message: any) {
|
||||||
if (this.sharedSecret == null) {
|
if (this.sharedSecret == null) {
|
||||||
await this.secureCommunication();
|
await this.secureCommunication();
|
||||||
}
|
}
|
||||||
|
|
||||||
message.timestamp = Date.now();
|
message.timestamp = Date.now();
|
||||||
|
|
||||||
const encrypted = await this.cryptoService.encrypt(JSON.stringify(message), this.sharedSecret);
|
return await this.cryptoService.encrypt(JSON.stringify(message), this.sharedSecret);
|
||||||
this.postMessage({appId: this.appId, message: encrypted});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getResponse(): Promise<any> {
|
getResponse(): Promise<any> {
|
||||||
|
@ -197,7 +220,10 @@ export class NativeMessagingBackground {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async onMessage(rawMessage: any) {
|
private async onMessage(rawMessage: any) {
|
||||||
const message = JSON.parse(await this.cryptoService.decryptToUtf8(rawMessage, this.sharedSecret));
|
let message = rawMessage;
|
||||||
|
if (!this.platformUtilsService.isSafari()) {
|
||||||
|
message = JSON.parse(await this.cryptoService.decryptToUtf8(rawMessage, this.sharedSecret));
|
||||||
|
}
|
||||||
|
|
||||||
if (Math.abs(message.timestamp - Date.now()) > MessageValidTimeout) {
|
if (Math.abs(message.timestamp - Date.now()) > MessageValidTimeout) {
|
||||||
// tslint:disable-next-line
|
// tslint:disable-next-line
|
||||||
|
@ -241,7 +267,21 @@ export class NativeMessagingBackground {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message.response === 'unlocked') {
|
if (message.response === 'unlocked') {
|
||||||
this.cryptoService.setKey(new SymmetricCryptoKey(Utils.fromB64ToArray(message.keyB64).buffer));
|
await this.cryptoService.setKey(new SymmetricCryptoKey(Utils.fromB64ToArray(message.keyB64).buffer));
|
||||||
|
|
||||||
|
// Verify key is correct by attempting to decrypt a secret
|
||||||
|
try {
|
||||||
|
await this.cryptoService.getFingerprint(await this.userService.getUserId());
|
||||||
|
} catch (e) {
|
||||||
|
// tslint:disable-next-line
|
||||||
|
console.error('Unable to verify key:', e);
|
||||||
|
await this.cryptoService.clearKey();
|
||||||
|
this.showWrongUserDialog();
|
||||||
|
|
||||||
|
message = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
this.vaultTimeoutService.biometricLocked = false;
|
this.vaultTimeoutService.biometricLocked = false;
|
||||||
this.runtimeBackground.processMessage({command: 'unlocked'}, null, null);
|
this.runtimeBackground.processMessage({command: 'unlocked'}, null, null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
containerPortal = 55E037402577FA6B00979016 /* Project object */;
|
containerPortal = 55E037402577FA6B00979016 /* Project object */;
|
||||||
proxyType = 1;
|
proxyType = 1;
|
||||||
remoteGlobalIDString = 55E037592577FA6F00979016;
|
remoteGlobalIDString = 55E037592577FA6F00979016;
|
||||||
remoteInfo = "safari";
|
remoteInfo = safari;
|
||||||
};
|
};
|
||||||
/* End PBXContainerItemProxy section */
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
|
@ -50,6 +50,7 @@
|
||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
5508DD7926051B5900A85C58 /* libswiftAppKit.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libswiftAppKit.tbd; path = usr/lib/swift/libswiftAppKit.tbd; sourceTree = SDKROOT; };
|
||||||
55E037482577FA6B00979016 /* desktop.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = desktop.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
55E037482577FA6B00979016 /* desktop.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = desktop.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
55E0374B2577FA6B00979016 /* desktop.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = desktop.entitlements; sourceTree = "<group>"; };
|
55E0374B2577FA6B00979016 /* desktop.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = desktop.entitlements; sourceTree = "<group>"; };
|
||||||
55E0374C2577FA6B00979016 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
55E0374C2577FA6B00979016 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
@ -127,6 +128,7 @@
|
||||||
55E0375E2577FA6F00979016 /* Frameworks */ = {
|
55E0375E2577FA6F00979016 /* Frameworks */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
5508DD7926051B5900A85C58 /* libswiftAppKit.tbd */,
|
||||||
55E0375F2577FA6F00979016 /* Cocoa.framework */,
|
55E0375F2577FA6F00979016 /* Cocoa.framework */,
|
||||||
);
|
);
|
||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
|
@ -157,7 +159,7 @@
|
||||||
55E037782577FA6F00979016 /* _locales */,
|
55E037782577FA6F00979016 /* _locales */,
|
||||||
);
|
);
|
||||||
name = Resources;
|
name = Resources;
|
||||||
path = "safari";
|
path = safari;
|
||||||
sourceTree = SOURCE_ROOT;
|
sourceTree = SOURCE_ROOT;
|
||||||
};
|
};
|
||||||
/* End PBXGroup section */
|
/* End PBXGroup section */
|
||||||
|
@ -195,7 +197,7 @@
|
||||||
dependencies = (
|
dependencies = (
|
||||||
);
|
);
|
||||||
name = safari;
|
name = safari;
|
||||||
productName = "safari";
|
productName = safari;
|
||||||
productReference = 55E0375A2577FA6F00979016 /* safari.appex */;
|
productReference = 55E0375A2577FA6F00979016 /* safari.appex */;
|
||||||
productType = "com.apple.product-type.app-extension";
|
productType = "com.apple.product-type.app-extension";
|
||||||
};
|
};
|
||||||
|
@ -426,6 +428,53 @@
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
|
55E037692577FA6F00979016 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = safari/safari.entitlements;
|
||||||
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
DEVELOPMENT_TEAM = HGT9YVMPAL;
|
||||||
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
|
INFOPLIST_FILE = safari/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/../Frameworks",
|
||||||
|
"@executable_path/../../../../Frameworks",
|
||||||
|
);
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.bitwarden.desktop.safari;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
55E0376A2577FA6F00979016 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = safari/safari.entitlements;
|
||||||
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
|
CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO;
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
DEVELOPMENT_TEAM = HGT9YVMPAL;
|
||||||
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
|
INFOPLIST_FILE = safari/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/../Frameworks",
|
||||||
|
"@executable_path/../../../../Frameworks",
|
||||||
|
);
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.bitwarden.desktop.safari;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SKIP_INSTALL = YES;
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
55E0376D2577FA6F00979016 /* Debug */ = {
|
55E0376D2577FA6F00979016 /* Debug */ = {
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
|
@ -435,7 +484,7 @@
|
||||||
CODE_SIGN_ENTITLEMENTS = desktop/desktop.entitlements;
|
CODE_SIGN_ENTITLEMENTS = desktop/desktop.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
DEVELOPMENT_TEAM = LTZ2PFU5D6;
|
DEVELOPMENT_TEAM = HGT9YVMPAL;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
INFOPLIST_FILE = desktop/Info.plist;
|
INFOPLIST_FILE = desktop/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
@ -459,7 +508,7 @@
|
||||||
CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO;
|
CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
DEVELOPMENT_TEAM = LTZ2PFU5D6;
|
DEVELOPMENT_TEAM = HGT9YVMPAL;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
INFOPLIST_FILE = desktop/Info.plist;
|
INFOPLIST_FILE = desktop/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
@ -473,51 +522,6 @@
|
||||||
};
|
};
|
||||||
name = Release;
|
name = Release;
|
||||||
};
|
};
|
||||||
55E037692577FA6F00979016 /* Debug */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
|
||||||
CODE_SIGN_ENTITLEMENTS = "safari/safari.entitlements";
|
|
||||||
CODE_SIGN_STYLE = Automatic;
|
|
||||||
DEVELOPMENT_TEAM = LTZ2PFU5D6;
|
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
|
||||||
INFOPLIST_FILE = "safari/Info.plist";
|
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
|
||||||
"$(inherited)",
|
|
||||||
"@executable_path/../Frameworks",
|
|
||||||
"@executable_path/../../../../Frameworks",
|
|
||||||
);
|
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.bitwarden.desktop.safari;
|
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
||||||
SKIP_INSTALL = YES;
|
|
||||||
SWIFT_VERSION = 5.0;
|
|
||||||
};
|
|
||||||
name = Debug;
|
|
||||||
};
|
|
||||||
55E0376A2577FA6F00979016 /* Release */ = {
|
|
||||||
isa = XCBuildConfiguration;
|
|
||||||
buildSettings = {
|
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
|
||||||
CODE_SIGN_ENTITLEMENTS = "safari/safari.entitlements";
|
|
||||||
CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO;
|
|
||||||
CODE_SIGN_STYLE = Automatic;
|
|
||||||
DEVELOPMENT_TEAM = LTZ2PFU5D6;
|
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
|
||||||
INFOPLIST_FILE = "safari/Info.plist";
|
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
|
||||||
"$(inherited)",
|
|
||||||
"@executable_path/../Frameworks",
|
|
||||||
"@executable_path/../../../../Frameworks",
|
|
||||||
);
|
|
||||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.bitwarden.desktop.safari;
|
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
|
||||||
SKIP_INSTALL = YES;
|
|
||||||
SWIFT_VERSION = 5.0;
|
|
||||||
};
|
|
||||||
name = Release;
|
|
||||||
};
|
|
||||||
/* End XCBuildConfiguration section */
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
/* Begin XCConfigurationList section */
|
/* Begin XCConfigurationList section */
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import SafariServices
|
import SafariServices
|
||||||
import os.log
|
import os.log
|
||||||
|
import LocalAuthentication
|
||||||
|
|
||||||
let SFExtensionMessageKey = "message"
|
let SFExtensionMessageKey = "message"
|
||||||
|
let ServiceName = "Bitwarden"
|
||||||
|
|
||||||
class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
|
class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
|
||||||
|
|
||||||
|
@ -78,7 +80,61 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
|
||||||
context.completeRequest(returningItems: [response], completionHandler: nil)
|
context.completeRequest(returningItems: [response], completionHandler: nil)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
case "biometricUnlock":
|
||||||
|
|
||||||
|
var error: NSError?
|
||||||
|
let laContext = LAContext()
|
||||||
|
|
||||||
|
guard laContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) else {
|
||||||
|
response.userInfo = [
|
||||||
|
SFExtensionMessageKey: [
|
||||||
|
"message": [
|
||||||
|
"command": "biometricUnlock",
|
||||||
|
"response": "not supported",
|
||||||
|
"timestamp": Int64(NSDate().timeIntervalSince1970 * 1000),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
laContext.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: "Bitwarden Safari Extension") { (success, error) in
|
||||||
|
if success {
|
||||||
|
let passwordName = "key"
|
||||||
|
var passwordLength: UInt32 = 0
|
||||||
|
var passwordPtr: UnsafeMutableRawPointer? = nil
|
||||||
|
|
||||||
|
let status = SecKeychainFindGenericPassword(nil, UInt32(ServiceName.utf8.count), ServiceName, UInt32(passwordName.utf8.count), passwordName, &passwordLength, &passwordPtr, nil)
|
||||||
|
|
||||||
|
if status == errSecSuccess {
|
||||||
|
let result = NSString(bytes: passwordPtr!, length: Int(passwordLength), encoding: String.Encoding.utf8.rawValue) as String?
|
||||||
|
SecKeychainItemFreeContent(nil, passwordPtr)
|
||||||
|
|
||||||
|
response.userInfo = [ SFExtensionMessageKey: [
|
||||||
|
"message": [
|
||||||
|
"command": "biometricUnlock",
|
||||||
|
"response": "unlocked",
|
||||||
|
"timestamp": Int64(NSDate().timeIntervalSince1970 * 1000),
|
||||||
|
"keyB64": result!.replacingOccurrences(of: "\"", with: ""),
|
||||||
|
],
|
||||||
|
]]
|
||||||
|
} else {
|
||||||
|
response.userInfo = [
|
||||||
|
SFExtensionMessageKey: [
|
||||||
|
"message": [
|
||||||
|
"command": "biometricUnlock",
|
||||||
|
"response": "not enabled",
|
||||||
|
"timestamp": Int64(NSDate().timeIntervalSince1970 * 1000),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.completeRequest(returningItems: [response], completionHandler: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
default:
|
default:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -289,8 +289,11 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService
|
||||||
}
|
}
|
||||||
|
|
||||||
async supportsBiometric() {
|
async supportsBiometric() {
|
||||||
const isUnsuportedFirefox = this.isFirefox() && parseInt((await browser.runtime.getBrowserInfo()).version.split('.')[0], 10) < 87;
|
if (this.isFirefox()) {
|
||||||
return !isUnsuportedFirefox && !this.isSafari();
|
return parseInt((await browser.runtime.getBrowserInfo()).version.split('.')[0], 10) >= 87;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
authenticateBiometric() {
|
authenticateBiometric() {
|
||||||
|
|
Loading…
Reference in New Issue