import * as bigInt from "big-integer" ; import { EncryptionType } from "../enums/encryptionType" ; import { HashPurpose } from "../enums/hashPurpose" ; import { KdfType } from "../enums/kdfType" ; import { KeySuffixOptions } from "../enums/keySuffixOptions" ; import { EncArrayBuffer } from "../models/domain/encArrayBuffer" ; import { EncryptedObject } from "../models/domain/encryptedObject" ; import { EncString } from "../models/domain/encString" ; import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey" ; import { CryptoService as CryptoServiceAbstraction } from "../abstractions/crypto.service" ; import { CryptoFunctionService } from "../abstractions/cryptoFunction.service" ; import { LogService } from "../abstractions/log.service" ; import { PlatformUtilsService } from "../abstractions/platformUtils.service" ; import { StateService } from "../abstractions/state.service" ; import { sequentialize } from "../misc/sequentialize" ; import { Utils } from "../misc/utils" ; import { EEFLongWordList } from "../misc/wordlist" ; import { ProfileOrganizationResponse } from "../models/response/profileOrganizationResponse" ; import { ProfileProviderOrganizationResponse } from "../models/response/profileProviderOrganizationResponse" ; import { ProfileProviderResponse } from "../models/response/profileProviderResponse" ; export class CryptoService implements CryptoServiceAbstraction { constructor ( private cryptoFunctionService , protected platformUtilService , protected logService , protected stateService ) { } async setKey ( key , userId ? ) { await this . stateService . setCryptoMasterKey ( key , { userId : userId } ) ; await this . storeKey ( key , userId ) ; } async setKeyHash ( keyHash ) { await this . stateService . setKeyHash ( keyHash ) ; } async setEncKey ( encKey ) { if ( encKey == null ) { return ; } await this . stateService . setDecryptedCryptoSymmetricKey ( null ) ; await this . stateService . setEncryptedCryptoSymmetricKey ( encKey ) ; } async setEncPrivateKey ( encPrivateKey ) { if ( encPrivateKey == null ) { return ; } await this . stateService . setDecryptedPrivateKey ( null ) ; await this . stateService . setEncryptedPrivateKey ( encPrivateKey ) ; } async setOrgKeys ( orgs , providerOrgs ) { const orgKeys = { } ; orgs . forEach ( ( org ) => { orgKeys [ org . id ] = org . key ; } ) ; for ( const providerOrg of providerOrgs ) { const providerKey = await this . getProviderKey ( providerOrg . providerId ) ; const decValue = await this . decryptToBytes ( new EncString ( providerOrg . key ) , providerKey ) ; orgKeys [ providerOrg . id ] = ( await this . rsaEncrypt ( decValue ) ) . encryptedString ; } await this . stateService . setDecryptedOrganizationKeys ( null ) ; return await this . stateService . setEncryptedOrganizationKeys ( orgKeys ) ; } async setProviderKeys ( providers ) { const providerKeys = { } ; providers . forEach ( ( provider ) => { providerKeys [ provider . id ] = provider . key ; } ) ; await this . stateService . setDecryptedProviderKeys ( null ) ; return await this . stateService . setEncryptedProviderKeys ( providerKeys ) ; } async getKey ( keySuffix ? , userId ? ) { const inMemoryKey = await this . stateService . getCryptoMasterKey ( { userId : userId } ) ; if ( inMemoryKey != null ) { return inMemoryKey ; } keySuffix ||= KeySuffixOptions . Auto ; const symmetricKey = await this . getKeyFromStorage ( keySuffix , userId ) ; if ( symmetricKey != null ) { this . setKey ( symmetricKey , userId ) ; } return symmetricKey ; } async getKeyFromStorage ( keySuffix , userId ? ) { const key = await this . retrieveKeyFromStorage ( keySuffix , userId ) ; if ( key != null ) { const symmetricKey = new SymmetricCryptoKey ( Utils . fromB64ToArray ( key ) . buffer ) ; if ( ! ( await this . validateKey ( symmetricKey ) ) ) { this . logService . warning ( "Wrong key, throwing away stored key" ) ; await this . clearSecretKeyStore ( userId ) ; return null ; } return symmetricKey ; } return null ; } async getKeyHash ( ) { return await this . stateService . getKeyHash ( ) ; } async compareAndUpdateKeyHash ( masterPassword , key ) { const storedKeyHash = await this . getKeyHash ( ) ; if ( masterPassword != null && storedKeyHash != null ) { const localKeyHash = await this . hashPassword ( masterPassword , key , HashPurpose . LocalAuthorization ) ; if ( localKeyHash != null && storedKeyHash === localKeyHash ) { return true ; } const serverKeyHash = await this . hashPassword ( masterPassword , key , HashPurpose . ServerAuthorization ) ; if ( serverKeyHash != null && storedKeyHash === serverKeyHash ) { await this . setKeyHash ( localKeyHash ) ; return true ; } } return false ; } @ sequentialize ( ( ) => "getEncKey" ) async getEncKey ( key = null ) { const inMemoryKey = await this . stateService . getDecryptedCryptoSymmetricKey ( ) ; if ( inMemoryKey != null ) { return inMemoryKey ; } const encKey = await this . stateService . getEncryptedCryptoSymmetricKey ( ) ; if ( encKey == null ) { return null ; } if ( key == null ) { key = await this . getKey ( ) ; } if ( key == null ) { return null ; } let decEncKey ; const encKeyCipher = new EncString ( encKey ) ; if ( encKeyCipher . encryptionType === EncryptionType . AesCbc256_B64 ) { decEncKey = await this . decryptToBytes ( encKeyCipher , key ) ; } else if ( encKeyCipher . encryptionType === EncryptionType . AesCbc256_HmacSha256_B64 ) { const newKey = await this . stretchKey ( key ) ; decEncKey = await this . decryptToBytes ( encKeyCipher , newKey ) ; } else { throw new Error ( "Unsupported encKey type." ) ; } if ( decEncKey == null ) { return null ; } const symmetricCryptoKey = new SymmetricCryptoKey ( decEncKey ) ; await this . stateService . setDecryptedCryptoSymmetricKey ( symmetricCryptoKey ) ; return symmetricCryptoKey ; } async getPublicKey ( ) { const inMemoryPublicKey = await this . stateService . getPublicKey ( ) ; if ( inMemoryPublicKey != null ) { return inMemoryPublicKey ; } const privateKey = await this . getPrivateKey ( ) ; if ( privateKey == null ) { return null ; } const publicKey = await this . cryptoFunctionService . rsaExtractPublicKey ( privateKey ) ; await this . stateService . setPublicKey ( publicKey ) ; return publicKey ; } async getPrivateKey ( ) { const decryptedPrivateKey = await this . stateService . getDecryptedPrivateKey ( ) ; if ( decryptedPrivateKey != null ) { return decryptedPrivateKey ; } const encPrivateKey = await this . stateService . getEncryptedPrivateKey ( ) ; if ( encPrivateKey == null ) { return null ; } const privateKey = await this . decryptToBytes ( new EncString ( encPrivateKey ) , null ) ; await this . stateService . setDecryptedPrivateKey ( privateKey ) ; return privateKey ; } async getFingerprint ( userId , publicKey ? ) { if ( publicKey == null ) { publicKey = await this . getPublicKey ( ) ; } if ( publicKey === null ) { throw new Error ( "No public key available." ) ; } const keyFingerprint = await this . cryptoFunctionService . hash ( publicKey , "sha256" ) ; const userFingerprint = await this . cryptoFunctionService . hkdfExpand ( keyFingerprint , userId , 32 , "sha256" ) ; return this . hashPhrase ( userFingerprint ) ; } @ sequentialize ( ( ) => "getOrgKeys" ) async getOrgKeys ( ) { const orgKeys = new Map < string , SymmetricCryptoKey > ( ) ; const decryptedOrganizationKeys = await this . stateService . getDecryptedOrganizationKeys ( ) ; if ( decryptedOrganizationKeys != null && decryptedOrganizationKeys . size > 0 ) { return decryptedOrganizationKeys ; } const encOrgKeys = await this . stateService . getEncryptedOrganizationKeys ( ) ; if ( encOrgKeys == null ) { return null ; } let setKey = false ; for ( const orgId in encOrgKeys ) { if ( ! encOrgKeys . hasOwnProperty ( orgId ) ) { continue ; } const decValue = await this . rsaDecrypt ( encOrgKeys [ orgId ] ) ; orgKeys . set ( orgId , new SymmetricCryptoKey ( decValue ) ) ; setKey = true ; } if ( setKey ) { await this . stateService . setDecryptedOrganizationKeys ( orgKeys ) ; } return orgKeys ; } async getOrgKey ( orgId ) { if ( orgId == null ) { return null ; } const orgKeys = await this . getOrgKeys ( ) ; if ( orgKeys == null || ! orgKeys . has ( orgId ) ) { return null ; } return orgKeys . get ( orgId ) ; } @ sequentialize ( ( ) => "getProviderKeys" ) async getProviderKeys ( ) { const providerKeys = new Map < string , SymmetricCryptoKey > ( ) ; const decryptedProviderKeys = await this . stateService . getDecryptedProviderKeys ( ) ; if ( decryptedProviderKeys != null && decryptedProviderKeys . size > 0 ) { return decryptedProviderKeys ; } const encProviderKeys = await this . stateService . getEncryptedProviderKeys ( ) ; if ( encProviderKeys == null ) { return null ; } let setKey = false ; for ( const orgId in encProviderKeys ) { if ( ! encProviderKeys . hasOwnProperty ( orgId ) ) { continue ; } const decValue = await this . rsaDecrypt ( encProviderKeys [ orgId ] ) ; providerKeys . set ( orgId , new SymmetricCryptoKey ( decValue ) ) ; setKey = true ; } if ( setKey ) { await this . stateService . setDecryptedProviderKeys ( providerKeys ) ; } return providerKeys ; } async getProviderKey ( providerId ) { if ( providerId == null ) { return null ; } const providerKeys = await this . getProviderKeys ( ) ; if ( providerKeys == null || ! providerKeys . has ( providerId ) ) { return null ; } return providerKeys . get ( providerId ) ; } async hasKey ( ) { return ( ( await this . hasKeyInMemory ( ) ) || ( await this . hasKeyStored ( KeySuffixOptions . Auto ) ) || ( await this . hasKeyStored ( KeySuffixOptions . Biometric ) ) ) ; } async hasKeyInMemory ( userId ? ) { return ( await this . stateService . getCryptoMasterKey ( { userId : userId } ) ) != null ; } async hasKeyStored ( keySuffix , userId ? ) { const key = keySuffix === KeySuffixOptions . Auto ? await this . stateService . getCryptoMasterKeyAuto ( { userId : userId } ) : await this . stateService . hasCryptoMasterKeyBiometric ( { userId : userId } ) ; return key != null ; } async hasEncKey ( ) { return ( await this . stateService . getEncryptedCryptoSymmetricKey ( ) ) != null ; } async clearKey ( clearSecretStorage = true , userId ? ) { await this . stateService . setCryptoMasterKey ( null , { userId : userId } ) ; await this . stateService . setLegacyEtmKey ( null , { userId : userId } ) ; if ( clearSecretStorage ) { await this . clearSecretKeyStore ( userId ) ; } } async clearStoredKey ( keySuffix ) { keySuffix === KeySuffixOptions . Auto ? await this . stateService . setCryptoMasterKeyAuto ( null ) : await this . stateService . setCryptoMasterKeyBiometric ( null ) ; } async clearKeyHash ( userId ? ) { return await this . stateService . setKeyHash ( null , { userId : userId } ) ; } async clearEncKey ( memoryOnly ? , userId ? ) { await this . stateService . setDecryptedCryptoSymmetricKey ( null , { userId : userId } ) ; if ( ! memoryOnly ) { await this . stateService . setEncryptedCryptoSymmetricKey ( null , { userId : userId } ) ; } } async clearKeyPair ( memoryOnly ? , userId ? ) { const keysToClear = [ this . stateService . setDecryptedPrivateKey ( null , { userId : userId } ) , this . stateService . setPublicKey ( null , { userId : userId } ) , ] ; if ( ! memoryOnly ) { keysToClear . push ( this . stateService . setEncryptedPrivateKey ( null , { userId : userId } ) ) ; } return Promise . all ( keysToClear ) ; } async clearOrgKeys ( memoryOnly ? , userId ? ) { await this . stateService . setDecryptedOrganizationKeys ( null , { userId : userId } ) ; if ( ! memoryOnly ) { await this . stateService . setEncryptedOrganizationKeys ( null , { userId : userId } ) ; } } async clearProviderKeys ( memoryOnly ? , userId ? ) { await this . stateService . setDecryptedProviderKeys ( null , { userId : userId } ) ; if ( ! memoryOnly ) { await this . stateService . setEncryptedProviderKeys ( null , { userId : userId } ) ; } } async clearPinProtectedKey ( userId ? ) { return await this . stateService . setEncryptedPinProtected ( null , { userId : userId } ) ; } async clearKeys ( userId ? ) { await this . clearKey ( true , userId ) ; await this . clearKeyHash ( userId ) ; await this . clearOrgKeys ( false , userId ) ; await this . clearProviderKeys ( false , userId ) ; await this . clearEncKey ( false , userId ) ; await this . clearKeyPair ( false , userId ) ; await this . clearPinProtectedKey ( userId ) ; } async toggleKey ( ) { const key = await this . getKey ( ) ; await this . setKey ( key ) ; } async makeKey ( password , salt , kdf , kdfIterations ) { let key = null ; if ( kdf == null || kdf === KdfType . PBKDF2_SHA256 ) { if ( kdfIterations == null ) { kdfIterations = 5000 ; } else if ( kdfIterations < 5000 ) { throw new Error ( "PBKDF2 iteration minimum is 5000." ) ; } key = await this . cryptoFunctionService . pbkdf2 ( password , salt , "sha256" , kdfIterations ) ; } else { throw new Error ( "Unknown Kdf." ) ; } return new SymmetricCryptoKey ( key ) ; } async makeKeyFromPin ( pin , salt , kdf , kdfIterations , protectedKeyCs = null ) { if ( protectedKeyCs == null ) { const pinProtectedKey = await this . stateService . getEncryptedPinProtected ( ) ; if ( pinProtectedKey == null ) { throw new Error ( "No PIN protected key found." ) ; } protectedKeyCs = new EncString ( pinProtectedKey ) ; } const pinKey = await this . makePinKey ( pin , salt , kdf , kdfIterations ) ; const decKey = await this . decryptToBytes ( protectedKeyCs , pinKey ) ; return new SymmetricCryptoKey ( decKey ) ; } async makeShareKey ( ) { const shareKey = await this . cryptoFunctionService . randomBytes ( 64 ) ; const publicKey = await this . getPublicKey ( ) ; const encShareKey = await this . rsaEncrypt ( shareKey , publicKey ) ; return [ encShareKey , new SymmetricCryptoKey ( shareKey ) ] ; } async makeKeyPair ( key ? ) { const keyPair = await this . cryptoFunctionService . rsaGenerateKeyPair ( 2048 ) ; const publicB64 = Utils . fromBufferToB64 ( keyPair [ 0 ] ) ; const privateEnc = await this . encrypt ( keyPair [ 1 ] , key ) ; return [ publicB64 , privateEnc ] ; } async makePinKey ( pin , salt , kdf , kdfIterations ) { const pinKey = await this . makeKey ( pin , salt , kdf , kdfIterations ) ; return await this . stretchKey ( pinKey ) ; } async makeSendKey ( keyMaterial ) { const sendKey = await this . cryptoFunctionService . hkdf ( keyMaterial , "bitwarden-send" , "send" , 64 , "sha256" ) ; return new SymmetricCryptoKey ( sendKey ) ; } async hashPassword ( password , key , hashPurpose ? ) { if ( key == null ) { key = await this . getKey ( ) ; } if ( password == null || key == null ) { throw new Error ( "Invalid parameters." ) ; } const iterations = hashPurpose === HashPurpose . LocalAuthorization ? 2 : 1 ; const hash = await this . cryptoFunctionService . pbkdf2 ( key . key , password , "sha256" , iterations ) ; return Utils . fromBufferToB64 ( hash ) ; } async makeEncKey ( key ) { const theKey = await this . getKeyForEncryption ( key ) ; const encKey = await this . cryptoFunctionService . randomBytes ( 64 ) ; return this . buildEncKey ( theKey , encKey ) ; } async remakeEncKey ( key , encKey ? ) { if ( encKey == null ) { encKey = await this . getEncKey ( ) ; } return this . buildEncKey ( key , encKey . key ) ; } async encrypt ( plainValue , key ? ) { if ( plainValue == null ) { return Promise . resolve ( null ) ; } let plainBuf ; if ( typeof plainValue === "string" ) { plainBuf = Utils . fromUtf8ToArray ( plainValue ) . buffer ; } else { plainBuf = plainValue ; } const encObj = await this . aesEncrypt ( plainBuf , key ) ; const iv = Utils . fromBufferToB64 ( encObj . iv ) ; const data = Utils . fromBufferToB64 ( encObj . data ) ; const mac = encObj . mac != null ? Utils . fromBufferToB64 ( encObj . mac ) : null ; return new EncString ( encObj . key . encType , data , iv , mac ) ; } async encryptToBytes ( plainValue , key ? ) { const encValue = await this . aesEncrypt ( plainValue , key ) ; let macLen = 0 ; if ( encValue . mac != null ) { macLen = encValue . mac . byteLength ; } const encBytes = new Uint8Array ( 1 + encValue . iv . byteLength + macLen + encValue . data . byteLength ) ; encBytes . set ( [ encValue . key . encType ] ) ; encBytes . set ( new Uint8Array ( encValue . iv ) , 1 ) ; if ( encValue . mac != null ) { encBytes . set ( new Uint8Array ( encValue . mac ) , 1 + encValue . iv . byteLength ) ; } encBytes . set ( new Uint8Array ( encValue . data ) , 1 + encValue . iv . byteLength + macLen ) ; return new EncArrayBuffer ( encBytes . buffer ) ; } async rsaEncrypt ( data , publicKey ? ) { if ( publicKey == null ) { publicKey = await this . getPublicKey ( ) ; } if ( publicKey == null ) { throw new Error ( "Public key unavailable." ) ; } const encBytes = await this . cryptoFunctionService . rsaEncrypt ( data , publicKey , "sha1" ) ; return new EncString ( EncryptionType . Rsa2048_OaepSha1_B64 , Utils . fromBufferToB64 ( encBytes ) ) ; } async rsaDecrypt ( encValue , privateKeyValue ? ) { const headerPieces = encValue . split ( "." ) ; let encType = null ; let encPieces ; if ( headerPieces . length === 1 ) { encType = EncryptionType . Rsa2048_OaepSha256_B64 ; encPieces = [ headerPieces [ 0 ] ] ; } else if ( headerPieces . length === 2 ) { try { encType = parseInt ( headerPieces [ 0 ] , null ) ; encPieces = headerPieces [ 1 ] . split ( "|" ) ; } catch ( e ) { this . logService . error ( e ) ; } } switch ( encType ) { case EncryptionType . Rsa2048_OaepSha256_B64 : case EncryptionType . Rsa2048_OaepSha1_B64 : case EncryptionType . Rsa2048_OaepSha256_HmacSha256_B64 : case EncryptionType . Rsa2048_OaepSha1_HmacSha256_B64 : break ; default : throw new Error ( "encType unavailable." ) ; } if ( encPieces == null || encPieces . length <= 0 ) { throw new Error ( "encPieces unavailable." ) ; } const data = Utils . fromB64ToArray ( encPieces [ 0 ] ) . buffer ; const privateKey = privateKeyValue ?? ( await this . getPrivateKey ( ) ) ; if ( privateKey == null ) { throw new Error ( "No private key." ) ; } let alg = "sha1" ; switch ( encType ) { case EncryptionType . Rsa2048_OaepSha256_B64 : case EncryptionType . Rsa2048_OaepSha256_HmacSha256_B64 : alg = "sha256" ; break ; case EncryptionType . Rsa2048_OaepSha1_B64 : case EncryptionType . Rsa2048_OaepSha1_HmacSha256_B64 : break ; default : throw new Error ( "encType unavailable." ) ; } return this . cryptoFunctionService . rsaDecrypt ( data , privateKey , alg ) ; } async decryptToBytes ( encString , key ? ) { const iv = Utils . fromB64ToArray ( encString . iv ) . buffer ; const data = Utils . fromB64ToArray ( encString . data ) . buffer ; const mac = encString . mac ? Utils . fromB64ToArray ( encString . mac ) . buffer : null ; const decipher = await this . aesDecryptToBytes ( encString . encryptionType , data , iv , mac , key ) ; if ( decipher == null ) { return null ; } return decipher ; } async decryptToUtf8 ( encString , key ? ) { return await this . aesDecryptToUtf8 ( encString . encryptionType , encString . data , encString . iv , encString . mac , key ) ; } async decryptFromBytes ( encBuf , key ) { if ( encBuf == null ) { throw new Error ( "no encBuf." ) ; } const encBytes = new Uint8Array ( encBuf ) ; const encType = encBytes [ 0 ] ; let ctBytes = null ; let ivBytes = null ; let macBytes = null ; switch ( encType ) { case EncryptionType . AesCbc128_HmacSha256_B64 : case EncryptionType . AesCbc256_HmacSha256_B64 : if ( encBytes . length <= 49 ) { return null ; } ivBytes = encBytes . slice ( 1 , 17 ) ; macBytes = encBytes . slice ( 17 , 49 ) ; ctBytes = encBytes . slice ( 49 ) ; break ; case EncryptionType . AesCbc256_B64 : if ( encBytes . length <= 17 ) { return null ; } ivBytes = encBytes . slice ( 1 , 17 ) ; ctBytes = encBytes . slice ( 17 ) ; break ; default : return null ; } return await this . aesDecryptToBytes ( encType , ctBytes . buffer , ivBytes . buffer , macBytes != null ? macBytes . buffer : null , key ) ; } async randomNumber ( min , max ) { let rval = 0 ; const range = max - min + 1 ; const bitsNeeded = Math . ceil ( Math . log2 ( range ) ) ; if ( bitsNeeded > 53 ) { throw new Error ( "We cannot generate numbers larger than 53 bits." ) ; } const bytesNeeded = Math . ceil ( bitsNeeded / 8 ) ; const mask = Math . pow ( 2 , bitsNeeded ) - 1 ; const byteArray = new Uint8Array ( await this . cryptoFunctionService . randomBytes ( bytesNeeded ) ) ; let p = ( bytesNeeded - 1 ) * 8 ; for ( let i = 0 ; i < bytesNeeded ; i ++ ) { rval += byteArray [ i ] * Math . pow ( 2 , p ) ; p -= 8 ; } rval = rval & mask ; if ( rval >= range ) { return this . randomNumber ( min , max ) ; } return min + rval ; } async validateKey ( key ) { try { const encPrivateKey = await this . stateService . getEncryptedPrivateKey ( ) ; const encKey = await this . getEncKey ( key ) ; if ( encPrivateKey == null || encKey == null ) { return false ; } const privateKey = await this . decryptToBytes ( new EncString ( encPrivateKey ) , encKey ) ; await this . cryptoFunctionService . rsaExtractPublicKey ( privateKey ) ; } catch ( e ) { return false ; } return true ; } protected async storeKey ( key , userId ? ) { if ( await this . shouldStoreKey ( KeySuffixOptions . Auto , userId ) ) { await this . stateService . setCryptoMasterKeyAuto ( key . keyB64 , { userId : userId } ) ; } else if ( await this . shouldStoreKey ( KeySuffixOptions . Biometric , userId ) ) { await this . stateService . setCryptoMasterKeyBiometric ( key . keyB64 , { userId : userId } ) ; } else { await this . stateService . setCryptoMasterKeyAuto ( null , { userId : userId } ) ; await this . stateService . setCryptoMasterKeyBiometric ( null , { userId : userId } ) ; } } protected async shouldStoreKey ( keySuffix , userId ? ) { let shouldStoreKey = false ; if ( keySuffix === KeySuffixOptions . Auto ) { const vaultTimeout = await this . stateService . getVaultTimeout ( { userId : userId } ) ; shouldStoreKey = vaultTimeout == null ; } else if ( keySuffix === KeySuffixOptions . Biometric ) { const biometricUnlock = await this . stateService . getBiometricUnlock ( { userId : userId } ) ; shouldStoreKey = biometricUnlock && this . platformUtilService . supportsSecureStorage ( ) ; } return shouldStoreKey ; } protected async retrieveKeyFromStorage ( keySuffix , userId ? ) { return keySuffix === KeySuffixOptions . Auto ? await this . stateService . getCryptoMasterKeyAuto ( { userId : userId } ) : await this . stateService . getCryptoMasterKeyBiometric ( { userId : userId } ) ; } private async aesEncrypt ( data , key ) { const obj = new EncryptedObject ( ) ; obj . key = await this . getKeyForEncryption ( key ) ; obj . iv = await this . cryptoFunctionService . randomBytes ( 16 ) ; obj . data = await this . cryptoFunctionService . aesEncrypt ( data , obj . iv , obj . key . encKey ) ; if ( obj . key . macKey != null ) { const macData = new Uint8Array ( obj . iv . byteLength + obj . data . byteLength ) ; macData . set ( new Uint8Array ( obj . iv ) , 0 ) ; macData . set ( new Uint8Array ( obj . data ) , obj . iv . byteLength ) ; obj . mac = await this . cryptoFunctionService . hmac ( macData . buffer , obj . key . macKey , "sha256" ) ; } return obj ; } private async aesDecryptToUtf8 ( encType , data , iv , mac , key ) { const keyForEnc = await this . getKeyForEncryption ( key ) ; const theKey = await this . resolveLegacyKey ( encType , keyForEnc ) ; if ( theKey . macKey != null && mac == null ) { this . logService . error ( "mac required." ) ; return null ; } if ( theKey . encType !== encType ) { this . logService . error ( "encType unavailable." ) ; return null ; } const fastParams = this . cryptoFunctionService . aesDecryptFastParameters ( data , iv , mac , theKey ) ; if ( fastParams . macKey != null && fastParams . mac != null ) { const computedMac = await this . cryptoFunctionService . hmacFast ( fastParams . macData , fastParams . macKey , "sha256" ) ; const macsEqual = await this . cryptoFunctionService . compareFast ( fastParams . mac , computedMac ) ; if ( ! macsEqual ) { this . logService . error ( "mac failed." ) ; return null ; } } return this . cryptoFunctionService . aesDecryptFast ( fastParams ) ; } private async aesDecryptToBytes ( encType , data , iv , mac , key ) { const keyForEnc = await this . getKeyForEncryption ( key ) ; const theKey = await this . resolveLegacyKey ( encType , keyForEnc ) ; if ( theKey . macKey != null && mac == null ) { return null ; } if ( theKey . encType !== encType ) { return null ; } if ( theKey . macKey != null && mac != null ) { const macData = new Uint8Array ( iv . byteLength + data . byteLength ) ; macData . set ( new Uint8Array ( iv ) , 0 ) ; macData . set ( new Uint8Array ( data ) , iv . byteLength ) ; const computedMac = await this . cryptoFunctionService . hmac ( macData . buffer , theKey . macKey , "sha256" ) ; if ( computedMac === null ) { return null ; } const macsMatch = await this . cryptoFunctionService . compare ( mac , computedMac ) ; if ( ! macsMatch ) { this . logService . error ( "mac failed." ) ; return null ; } } return await this . cryptoFunctionService . aesDecrypt ( data , iv , theKey . encKey ) ; } private async getKeyForEncryption ( key ? ) { if ( key != null ) { return key ; } const encKey = await this . getEncKey ( ) ; if ( encKey != null ) { return encKey ; } return await this . getKey ( ) ; } private async resolveLegacyKey ( encType , key ) { if ( encType === EncryptionType . AesCbc128_HmacSha256_B64 && key . encType === EncryptionType . AesCbc256_B64 ) { let legacyKey = await this . stateService . getLegacyEtmKey ( ) ; if ( legacyKey == null ) { legacyKey = new SymmetricCryptoKey ( key . key , EncryptionType . AesCbc128_HmacSha256_B64 ) ; await this . stateService . setLegacyEtmKey ( legacyKey ) ; } return legacyKey ; } return key ; } private async stretchKey ( key ) { const newKey = new Uint8Array ( 64 ) ; const encKey = await this . cryptoFunctionService . hkdfExpand ( key . key , "enc" , 32 , "sha256" ) ; const macKey = await this . cryptoFunctionService . hkdfExpand ( key . key , "mac" , 32 , "sha256" ) ; newKey . set ( new Uint8Array ( encKey ) ) ; newKey . set ( new Uint8Array ( macKey ) , 32 ) ; return new SymmetricCryptoKey ( newKey . buffer ) ; } private async hashPhrase ( hash , minimumEntropy = 64 ) { const entropyPerWord = Math . log ( EEFLongWordList . length ) / Math . log ( 2 ) ; let numWords = Math . ceil ( minimumEntropy / entropyPerWord ) ; const hashArr = Array . from ( new Uint8Array ( hash ) ) ; const entropyAvailable = hashArr . length * 4 ; if ( numWords * entropyPerWord > entropyAvailable ) { throw new Error ( "Output entropy of hash function is too small" ) ; } const phrase = [ ] ; let hashNumber = bigInt . fromArray ( hashArr , 256 ) ; while ( numWords -- ) { const remainder = hashNumber . mod ( EEFLongWordList . length ) ; hashNumber = hashNumber . divide ( EEFLongWordList . length ) ; phrase . push ( EEFLongWordList [ remainder as any ] ) ; } return phrase ; } private async buildEncKey ( key , encKey ) { let encKeyEnc = null ; if ( key . key . byteLength === 32 ) { const newKey = await this . stretchKey ( key ) ; encKeyEnc = await this . encrypt ( encKey , newKey ) ; } else if ( key . key . byteLength === 64 ) { encKeyEnc = await this . encrypt ( encKey , key ) ; } else { throw new Error ( "Invalid key size." ) ; } return [ new SymmetricCryptoKey ( encKey ) , encKeyEnc ] ; } private async clearSecretKeyStore ( userId ? ) { await this . stateService . setCryptoMasterKeyAuto ( null , { userId : userId } ) ; await this . stateService . setCryptoMasterKeyBiometric ( null , { userId : userId } ) ; } }