2020-10-19 16:50:25 +02:00
import { CryptoService } from 'jslib/abstractions/crypto.service' ;
import { MessagingService } from 'jslib/abstractions/messaging.service' ;
import { VaultTimeoutService } from 'jslib/abstractions/vaultTimeout.service' ;
2020-10-16 17:08:53 +02:00
import { CryptoFunctionService } from 'jslib/abstractions/cryptoFunction.service' ;
2020-10-12 18:01:34 +02:00
import { StorageService } from 'jslib/abstractions/storage.service' ;
2020-10-16 17:08:53 +02:00
import { Utils } from 'jslib/misc/utils' ;
2020-10-19 12:20:45 +02:00
import { SymmetricCryptoKey } from 'jslib/models/domain' ;
2020-10-12 18:01:34 +02:00
import { ConstantsService } from 'jslib/services' ;
2020-10-11 20:45:25 +02:00
import { BrowserApi } from '../browser/browserApi' ;
2020-10-12 18:01:34 +02:00
import RuntimeBackground from './runtime.background' ;
2020-10-19 16:50:25 +02:00
import { UserService } from 'jslib/abstractions/user.service' ;
import { I18nService } from 'jslib/abstractions/i18n.service' ;
2020-10-09 17:16:15 +02:00
2020-10-16 11:09:49 +02:00
const MessageValidTimeout = 10 * 1000 ;
2020-10-19 12:20:45 +02:00
const EncryptionAlgorithm = 'sha1' ;
2020-10-16 11:09:49 +02:00
2020-10-09 17:16:15 +02:00
export class NativeMessagingBackground {
private connected = false ;
2020-10-21 15:56:10 +02:00
private connecting : boolean ;
2020-10-09 17:16:15 +02:00
private port : browser.runtime.Port | chrome . runtime . Port ;
private resolver : any = null ;
2020-10-19 12:20:45 +02:00
private privateKey : ArrayBuffer = null ;
2020-10-16 17:08:53 +02:00
private secureSetupResolve : any = null ;
2020-10-19 12:20:45 +02:00
private sharedSecret : SymmetricCryptoKey ;
2020-10-09 17:16:15 +02:00
2020-10-12 18:01:34 +02:00
constructor ( private storageService : StorageService , private cryptoService : CryptoService ,
2020-10-16 17:08:53 +02:00
private cryptoFunctionService : CryptoFunctionService , private vaultTimeoutService : VaultTimeoutService ,
2020-10-19 16:50:25 +02:00
private runtimeBackground : RuntimeBackground , private i18nService : I18nService , private userService : UserService ,
private messagingService : MessagingService ) { }
2020-10-12 18:01:34 +02:00
2020-10-21 15:56:10 +02:00
async connect() {
return new Promise ( ( resolve , reject ) = > {
this . port = BrowserApi . connectNative ( 'com.8bit.bitwarden' ) ;
this . connecting = true ;
2020-10-21 17:18:04 +02:00
this . port . onMessage . addListener ( async ( message : any ) = > {
switch ( message . command ) {
case 'connected' :
this . connected = true ;
this . connecting = false ;
resolve ( ) ;
break ;
case 'disconnected' :
if ( this . connecting ) {
this . messagingService . send ( 'showDialog' , {
text : this.i18nService.t ( 'startDesktopDesc' ) ,
title : this.i18nService.t ( 'startDesktopTitle' ) ,
confirmText : this.i18nService.t ( 'ok' ) ,
type : 'error' ,
} ) ;
reject ( ) ;
}
this . connected = false ;
this . port . disconnect ( ) ;
break ;
case 'setupEncryption' :
const encrypted = Utils . fromB64ToArray ( message . sharedSecret ) ;
const decrypted = await this . cryptoFunctionService . rsaDecrypt ( encrypted . buffer , this . privateKey , EncryptionAlgorithm ) ;
this . sharedSecret = new SymmetricCryptoKey ( decrypted ) ;
this . secureSetupResolve ( ) ;
break ;
2020-10-23 14:40:50 +02:00
case 'invalidateEncryption' :
this . sharedSecret = null ;
this . privateKey = null ;
this . connected = false ;
this . messagingService . send ( 'showDialog' , {
text : this.i18nService.t ( 'nativeMessagingInvalidEncryptionDesc' ) ,
title : this.i18nService.t ( 'nativeMessagingInvalidEncryptionTitle' ) ,
confirmText : this.i18nService.t ( 'ok' ) ,
type : 'error' ,
} ) ;
2020-10-21 17:18:04 +02:00
default :
this . onMessage ( message ) ;
2020-10-21 15:56:10 +02:00
}
} ) ;
2020-10-21 19:23:27 +02:00
this . port . onDisconnect . addListener ( ( p : any ) = > {
let error ;
if ( BrowserApi . isWebExtensionsApi ) {
error = p . error . message ;
} else {
error = chrome . runtime . lastError . message ;
}
if ( error === 'Specified native messaging host not found.' || error === 'Access to the specified native messaging host is forbidden.' || error === 'An unexpected error occurred' ) {
2020-10-21 15:56:10 +02:00
this . messagingService . send ( 'showDialog' , {
text : this.i18nService.t ( 'desktopIntegrationDisabledDesc' ) ,
title : this.i18nService.t ( 'desktopIntegrationDisabledTitle' ) ,
confirmText : this.i18nService.t ( 'ok' ) ,
type : 'error' ,
} ) ;
}
2020-10-23 14:40:50 +02:00
this . sharedSecret = null ;
this . privateKey = null ;
2020-10-21 15:56:10 +02:00
this . connected = false ;
reject ( ) ;
} ) ;
2020-10-09 17:16:15 +02:00
} ) ;
}
2020-10-12 21:18:47 +02:00
async send ( message : any ) {
2020-10-09 17:16:15 +02:00
if ( ! this . connected ) {
2020-10-21 15:56:10 +02:00
await this . connect ( ) ;
2020-10-09 17:16:15 +02:00
}
2020-10-19 12:20:45 +02:00
if ( this . sharedSecret == null ) {
2020-10-16 17:08:53 +02:00
await this . secureCommunication ( ) ;
}
2020-10-12 21:18:47 +02:00
message . timestamp = Date . now ( ) ;
2020-10-19 12:20:45 +02:00
const encrypted = await this . cryptoService . encrypt ( JSON . stringify ( message ) , this . sharedSecret ) ;
2020-10-12 21:18:47 +02:00
this . port . postMessage ( encrypted ) ;
2020-10-09 17:16:15 +02:00
}
2020-10-21 17:18:04 +02:00
getResponse ( ) : Promise < any > {
2020-10-09 17:16:15 +02:00
return new Promise ( ( resolve , reject ) = > {
this . resolver = resolve ;
} ) ;
}
2020-10-12 18:01:34 +02:00
2020-10-12 21:18:47 +02:00
private async onMessage ( rawMessage : any ) {
2020-10-19 12:20:45 +02:00
const message = JSON . parse ( await this . cryptoService . decryptToUtf8 ( rawMessage , this . sharedSecret ) ) ;
2020-10-12 21:18:47 +02:00
2020-10-16 11:09:49 +02:00
if ( Math . abs ( message . timestamp - Date . now ( ) ) > MessageValidTimeout ) {
// tslint:disable-next-line
console . error ( 'NativeMessage is to old, ignoring.' ) ;
2020-10-12 21:18:47 +02:00
return ;
}
2020-10-16 11:09:49 +02:00
switch ( message . command ) {
2020-10-16 17:08:53 +02:00
case 'biometricUnlock' :
2020-10-12 18:01:34 +02:00
await this . storageService . remove ( ConstantsService . biometricAwaitingAcceptance ) ;
const enabled = await this . storageService . get ( ConstantsService . biometricUnlockKey ) ;
if ( enabled === null || enabled === false ) {
2020-10-12 21:18:47 +02:00
if ( message . response === 'unlocked' ) {
2020-10-12 18:01:34 +02:00
await this . storageService . save ( ConstantsService . biometricUnlockKey , true ) ;
}
2020-10-21 17:18:04 +02:00
break ;
}
2020-10-16 11:09:49 +02:00
2020-10-21 17:18:04 +02:00
// Ignore unlock if already unlockeded
if ( ! this . vaultTimeoutService . biometricLocked ) {
break ;
2020-10-12 18:01:34 +02:00
}
2020-10-21 17:18:04 +02:00
if ( message . response === 'unlocked' ) {
2020-10-19 18:34:40 +02:00
this . cryptoService . setKey ( new SymmetricCryptoKey ( Utils . fromB64ToArray ( message . keyB64 ) . buffer ) ) ;
2020-10-12 18:01:34 +02:00
this . vaultTimeoutService . biometricLocked = false ;
2020-10-19 18:34:40 +02:00
this . runtimeBackground . processMessage ( { command : 'unlocked' } , null , null ) ;
2020-10-12 18:01:34 +02:00
}
2020-10-16 17:08:53 +02:00
break ;
2020-10-16 11:09:49 +02:00
default :
// tslint:disable-next-line
console . error ( 'NativeMessage, got unknown command.' ) ;
2020-10-12 18:01:34 +02:00
}
if ( this . resolver ) {
2020-10-12 21:18:47 +02:00
this . resolver ( message ) ;
2020-10-12 18:01:34 +02:00
}
}
2020-10-16 17:08:53 +02:00
private async secureCommunication() {
2020-10-21 17:18:04 +02:00
const [ publicKey , privateKey ] = await this . cryptoFunctionService . rsaGenerateKeyPair ( 2048 ) ;
this . privateKey = privateKey ;
2020-10-16 17:08:53 +02:00
2020-10-21 17:18:04 +02:00
this . sendUnencrypted ( { command : 'setupEncryption' , publicKey : Utils.fromBufferToB64 ( publicKey ) } ) ;
const fingerprint = ( await this . cryptoService . getFingerprint ( await this . userService . getUserId ( ) , publicKey ) ) . join ( ' ' ) ;
2020-10-19 16:50:25 +02:00
this . messagingService . send ( 'showDialog' , {
2020-10-21 15:56:10 +02:00
html : ` ${ this . i18nService . t ( 'desktopIntegrationVerificationText' ) } <br><br><strong> ${ fingerprint } </strong> ` ,
2020-10-19 16:50:25 +02:00
title : this.i18nService.t ( 'desktopSyncVerificationTitle' ) ,
confirmText : this.i18nService.t ( 'ok' ) ,
type : 'warning' ,
} ) ;
2020-10-16 17:08:53 +02:00
return new Promise ( ( resolve , reject ) = > this . secureSetupResolve = resolve ) ;
}
private async sendUnencrypted ( message : any ) {
if ( ! this . connected ) {
2020-10-21 15:56:10 +02:00
await this . connect ( ) ;
2020-10-16 17:08:53 +02:00
}
message . timestamp = Date . now ( ) ;
this . port . postMessage ( message ) ;
}
2020-10-09 17:16:15 +02:00
}