diff --git a/package-lock.json b/package-lock.json index f10106e59d..857b8d673b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -768,6 +768,11 @@ "callsite": "1.0.0" } }, + "big-integer": { + "version": "1.6.36", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.36.tgz", + "integrity": "sha512-t70bfa7HYEA1D9idDbmuv7YbsbVkQ+Hp+8KFSul4aE5e/i1bjCNIRYJZlA8Q8p0r9T8cF/RVvwUgRA//FydEyg==" + }, "binary-extensions": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", @@ -776,7 +781,7 @@ }, "bl": { "version": "1.2.2", - "resolved": "http://registry.npmjs.org/bl/-/bl-1.2.2.tgz", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.2.2.tgz", "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", "requires": { "readable-stream": "^2.3.5", diff --git a/package.json b/package.json index 305763fe70..f2f5666ed6 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "@angular/upgrade": "6.1.7", "@aspnet/signalr": "1.0.4", "@aspnet/signalr-protocol-msgpack": "1.0.4", + "big-integer": "1.6.36", "core-js": "2.5.7", "electron-log": "2.2.14", "electron-updater": "3.0.3", diff --git a/src/abstractions/crypto.service.ts b/src/abstractions/crypto.service.ts index 3dc13669fa..59ada585e1 100644 --- a/src/abstractions/crypto.service.ts +++ b/src/abstractions/crypto.service.ts @@ -16,6 +16,7 @@ export abstract class CryptoService { getEncKey: () => Promise; getPublicKey: () => Promise; getPrivateKey: () => Promise; + getFingerprint: () => Promise; getOrgKeys: () => Promise>; getOrgKey: (orgId: string) => Promise; hasKey: () => Promise; diff --git a/src/services/crypto.service.ts b/src/services/crypto.service.ts index b6a9097884..101b3bc271 100644 --- a/src/services/crypto.service.ts +++ b/src/services/crypto.service.ts @@ -1,3 +1,5 @@ +import * as bigInt from 'big-integer'; + import { EncryptionType } from '../enums/encryptionType'; import { KdfType } from '../enums/kdfType'; @@ -14,6 +16,7 @@ import { ConstantsService } from './constants.service'; import { sequentialize } from '../misc/sequentialize'; import { Utils } from '../misc/utils'; +import { EEFLongWordList } from '../misc/wordlist'; const Keys = { key: 'key', @@ -163,6 +166,16 @@ export class CryptoService implements CryptoServiceAbstraction { return this.privateKey; } + async getFingerprint(): Promise { + const 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.hkdfExpand(keyFingerprint, Utils.fromUtf8ToArray('USER-ID'), 32); + return this.hashPhrase(userFingerprint.buffer); + } + @sequentialize(() => 'getOrgKeys') async getOrgKeys(): Promise> { if (this.orgKeys != null && this.orgKeys.size > 0) { @@ -675,6 +688,28 @@ export class CryptoService implements CryptoServiceAbstraction { return okm; } + private async hashPhrase(data: ArrayBuffer, minimumEntropy: number = 64) { + const wordListLength = EEFLongWordList.length; + const entropyPerWord = Math.log(wordListLength) / Math.log(2); + let numWords = Math.ceil(minimumEntropy / entropyPerWord); + + const hashBuffer = await this.cryptoFunctionService.pbkdf2(data, '', 'sha256', 50000); + const hash = Array.from(new Uint8Array(hashBuffer)); + const entropyAvailable = hash.length * 4; + if (numWords * entropyPerWord > entropyAvailable) { + throw new Error('Output entropy of hash function is too small'); + } + + const phrase: string[] = []; + let hashNumber = bigInt.fromArray(hash, 256); + while (numWords--) { + const remainder = hashNumber.mod(wordListLength); + hashNumber = hashNumber.divide(wordListLength); + phrase.push(EEFLongWordList[remainder as any]); + } + return phrase; + } + private async buildEncKey(key: SymmetricCryptoKey, encKey: ArrayBuffer = null) : Promise<[SymmetricCryptoKey, CipherString]> { let encKeyEnc: CipherString = null;