Merge branch 'master' into verify-email-for-send

This commit is contained in:
Thomas Rittson 2021-04-09 15:47:25 +10:00
commit df86fad828
9 changed files with 195 additions and 87 deletions

View File

@ -126,7 +126,7 @@ export default class MainBackground {
analytics: Analytics;
popupUtilsService: PopupUtilsService;
sendService: SendServiceAbstraction;
fileUploadService: FileUploadServiceAbstraction
fileUploadService: FileUploadServiceAbstraction;
onUpdatedRan: boolean;
onReplacedRan: boolean;
@ -190,7 +190,7 @@ export default class MainBackground {
this.storageService, this.i18nService, this.cipherService);
this.collectionService = new CollectionService(this.cryptoService, this.userService, this.storageService,
this.i18nService);
this.searchService = new SearchService(this.cipherService, this.consoleLogService);
this.searchService = new SearchService(this.cipherService, this.consoleLogService, this.i18nService);
this.sendService = new SendService(this.cryptoService, this.userService, this.apiService, this.fileUploadService,
this.storageService, this.i18nService, this.cryptoFunctionService);
this.stateService = new StateService();
@ -249,7 +249,8 @@ export default class MainBackground {
this.analytics, this.notificationsService, this.systemService, this.vaultTimeoutService,
this.environmentService, this.policyService, this.userService, this.messagingService);
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.platformUtilsService, this.analytics, this.vaultTimeoutService);

View File

@ -3,6 +3,7 @@ import { CryptoService } from 'jslib/abstractions/crypto.service';
import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { MessagingService } from 'jslib/abstractions/messaging.service';
import { PlatformUtilsService } from 'jslib/abstractions/platformUtils.service';
import { StorageService } from 'jslib/abstractions/storage.service';
import { UserService } from 'jslib/abstractions/user.service';
import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service';
@ -33,7 +34,8 @@ export class NativeMessagingBackground {
constructor(private storageService: StorageService, private cryptoService: CryptoService,
private cryptoFunctionService: CryptoFunctionService, private vaultTimeoutService: VaultTimeoutService,
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);
if (chrome?.permissions?.onAdded) {
@ -48,17 +50,27 @@ export class NativeMessagingBackground {
this.appId = await this.appIdService.getAppId();
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.connecting = true;
const connectedCallback = () => {
this.connected = true;
this.connecting = false;
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':
this.connected = true;
this.connecting = false;
resolve();
connectedCallback();
break;
case 'disconnected':
if (this.connecting) {
@ -114,15 +126,10 @@ export class NativeMessagingBackground {
break;
}
case 'wrongUserId':
this.messagingService.send('showDialog', {
text: this.i18nService.t('nativeMessagingWrongUserDesc'),
title: this.i18nService.t('nativeMessagingWrongUserTitle'),
confirmText: this.i18nService.t('ok'),
type: 'error',
});
this.showWrongUserDialog();
default:
// Ignore since it belongs to another device
if (message.appId !== this.appId) {
if (!this.platformUtilsService.isSafari() && message.appId !== this.appId) {
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) {
if (!this.connected) {
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) {
await this.secureCommunication();
}
message.timestamp = Date.now();
const encrypted = await this.cryptoService.encrypt(JSON.stringify(message), this.sharedSecret);
this.postMessage({appId: this.appId, message: encrypted});
return await this.cryptoService.encrypt(JSON.stringify(message), this.sharedSecret);
}
getResponse(): Promise<any> {
@ -197,7 +220,10 @@ export class NativeMessagingBackground {
}
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) {
// tslint:disable-next-line
@ -241,7 +267,21 @@ export class NativeMessagingBackground {
}
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.runtimeBackground.processMessage({command: 'unlocked'}, null, null);
}

View File

@ -25,7 +25,7 @@
<app-callout type="warning" icon="fa fa-external-link fa-rotate-270 fa-fw" [clickable]="true"
title="{{'sendFileCalloutHeader' | i18n}}"
*ngIf="showFilePopoutMessage && send.type === sendType.File && !disableSend" (click)="popOutWindow()">
<div *ngIf="showLinuxChromiumFileWarning">{{'sendLinuxChromiumFileWarning' | i18n}}</div>
<div *ngIf="showChromiumFileWarning">{{'sendLinuxChromiumFileWarning' | i18n}}</div>
<div *ngIf="showFirefoxFileWarning">{{'sendFirefoxFileWarning' | i18n}}</div>
<div *ngIf="showSafariFileWarning">{{'sendSafariFileWarning' | i18n}}</div>
</app-callout>

View File

@ -35,6 +35,7 @@ export class SendAddEditComponent extends BaseAddEditComponent {
inPopout = false;
inSidebar = false;
isLinux = false;
isUnsupportedMac = false;
constructor(i18nService: I18nService, platformUtilsService: PlatformUtilsService,
userService: UserService, messagingService: MessagingService, policyService: PolicyService,
@ -46,14 +47,14 @@ export class SendAddEditComponent extends BaseAddEditComponent {
}
get showFileSelector(): boolean {
return !this.editMode && (!this.isFirefox && !this.isSafari && !this.isLinux) ||
return !this.editMode && (!this.isFirefox && !this.isSafari && !this.isLinux && !this.isUnsupportedMac) ||
(this.isFirefox && (this.inSidebar || this.inPopout)) ||
(this.isSafari && this.inPopout) ||
(this.isLinux && !this.isFirefox && (this.inSidebar || this.inPopout));
((this.isLinux || this.isUnsupportedMac) && !this.isFirefox && (this.inSidebar || this.inPopout));
}
get showFilePopoutMessage(): boolean {
return !this.editMode && (this.showFirefoxFileWarning || this.showSafariFileWarning || this.showLinuxChromiumFileWarning);
return !this.editMode && (this.showFirefoxFileWarning || this.showSafariFileWarning || this.showChromiumFileWarning);
}
get showFirefoxFileWarning(): boolean {
@ -64,9 +65,9 @@ export class SendAddEditComponent extends BaseAddEditComponent {
return this.isSafari && !this.inPopout;
}
// Only show this for Chromium based browsers in Linux
get showLinuxChromiumFileWarning(): boolean {
return this.isLinux && !this.isFirefox && !(this.inSidebar || this.inPopout);
// Only show this for Chromium based browsers in Linux and Mac > Big Sur
get showChromiumFileWarning(): boolean {
return (this.isLinux || this.isUnsupportedMac) && !this.isFirefox && !(this.inSidebar || this.inPopout);
}
popOutWindow() {
@ -79,6 +80,7 @@ export class SendAddEditComponent extends BaseAddEditComponent {
this.inPopout = this.popupUtilsService.inPopout(window);
this.inSidebar = this.popupUtilsService.inSidebar(window);
this.isLinux = window?.navigator?.userAgent.indexOf('Linux') !== -1;
this.isUnsupportedMac = this.platformUtilsService.isChrome() && window?.navigator?.appVersion.includes('Mac OS X 11');
const queryParamsSub = this.route.queryParams.subscribe(async params => {
if (params.sendId) {

View File

@ -1,12 +1,13 @@
import { CipherService } from 'jslib/abstractions/cipher.service';
import { I18nService } from 'jslib/abstractions/i18n.service';
import { ConsoleLogService } from 'jslib/services/consoleLog.service';
import { SearchService } from 'jslib/services/search.service';
export class PopupSearchService extends SearchService {
constructor(private mainSearchService: SearchService, cipherService: CipherService,
consoleLogService: ConsoleLogService) {
super(cipherService, consoleLogService);
consoleLogService: ConsoleLogService, i18nService: I18nService) {
super(cipherService, consoleLogService, i18nService);
}
clearIndex() {

View File

@ -68,7 +68,8 @@ function getBgService<T>(service: string) {
export const stateService = new StateService();
export const messagingService = new BrowserMessagingService();
export const searchService = new PopupSearchService(getBgService<SearchService>('searchService')(),
getBgService<CipherService>('cipherService')(), getBgService<ConsoleLogService>('consoleLogService')());
getBgService<CipherService>('cipherService')(), getBgService<ConsoleLogService>('consoleLogService')(),
getBgService<I18nService>('i18nService')());
export function initFactory(platformUtilsService: PlatformUtilsService, i18nService: I18nService, storageService: StorageService,
popupUtilsService: PopupUtilsService): Function {
@ -90,7 +91,7 @@ export function initFactory(platformUtilsService: PlatformUtilsService, i18nServ
let theme = await storageService.get<string>(ConstantsService.themeKey);
if (theme == null) {
theme = platformUtilsService.getDefaultSystemTheme();
theme = await platformUtilsService.getDefaultSystemTheme();
platformUtilsService.onDefaultSystemThemeChange(sysTheme => {
window.document.documentElement.classList.remove('theme_light', 'theme_dark');

View File

@ -31,7 +31,7 @@
containerPortal = 55E037402577FA6B00979016 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 55E037592577FA6F00979016;
remoteInfo = "safari";
remoteInfo = safari;
};
/* End PBXContainerItemProxy section */
@ -50,6 +50,7 @@
/* End PBXCopyFilesBuildPhase 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; };
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>"; };
@ -127,6 +128,7 @@
55E0375E2577FA6F00979016 /* Frameworks */ = {
isa = PBXGroup;
children = (
5508DD7926051B5900A85C58 /* libswiftAppKit.tbd */,
55E0375F2577FA6F00979016 /* Cocoa.framework */,
);
name = Frameworks;
@ -157,7 +159,7 @@
55E037782577FA6F00979016 /* _locales */,
);
name = Resources;
path = "safari";
path = safari;
sourceTree = SOURCE_ROOT;
};
/* End PBXGroup section */
@ -195,7 +197,7 @@
dependencies = (
);
name = safari;
productName = "safari";
productName = safari;
productReference = 55E0375A2577FA6F00979016 /* safari.appex */;
productType = "com.apple.product-type.app-extension";
};
@ -426,6 +428,53 @@
};
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 */ = {
isa = XCBuildConfiguration;
buildSettings = {
@ -435,7 +484,7 @@
CODE_SIGN_ENTITLEMENTS = desktop/desktop.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = LTZ2PFU5D6;
DEVELOPMENT_TEAM = HGT9YVMPAL;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = desktop/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@ -459,7 +508,7 @@
CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = LTZ2PFU5D6;
DEVELOPMENT_TEAM = HGT9YVMPAL;
ENABLE_HARDENED_RUNTIME = YES;
INFOPLIST_FILE = desktop/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@ -473,51 +522,6 @@
};
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 */
/* Begin XCConfigurationList section */

View File

@ -1,7 +1,9 @@
import SafariServices
import os.log
import LocalAuthentication
let SFExtensionMessageKey = "message"
let ServiceName = "Bitwarden"
class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
@ -78,14 +80,68 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
context.completeRequest(returningItems: [response], completionHandler: nil)
}
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:
return
}
context.completeRequest(returningItems: [response], completionHandler: nil)
}
}
func jsonSerialize<T: Encodable>(obj: T?) -> String? {

View File

@ -122,8 +122,8 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService
BrowserApi.downloadFile(win, blobData, blobOptions, fileName);
}
getApplicationVersion(): string {
return BrowserApi.getApplicationVersion();
getApplicationVersion(): Promise<string> {
return Promise.resolve(BrowserApi.getApplicationVersion());
}
supportsWebAuthn(win: Window): boolean {
@ -289,8 +289,11 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService
}
async supportsBiometric() {
const isUnsuportedFirefox = this.isFirefox() && parseInt((await browser.runtime.getBrowserInfo()).version.split('.')[0], 10) < 87;
return !isUnsuportedFirefox && !this.isSafari();
if (this.isFirefox()) {
return parseInt((await browser.runtime.getBrowserInfo()).version.split('.')[0], 10) >= 87;
}
return true;
}
authenticateBiometric() {
@ -311,8 +314,8 @@ export default class BrowserPlatformUtilsService implements PlatformUtilsService
return false;
}
getDefaultSystemTheme() {
return this.prefersColorSchemeDark.matches ? 'dark' : 'light';
getDefaultSystemTheme(): Promise<'light' | 'dark'> {
return Promise.resolve(this.prefersColorSchemeDark.matches ? 'dark' : 'light');
}
onDefaultSystemThemeChange(callback: ((theme: 'light' | 'dark') => unknown)) {