diff --git a/src/abstractions/audit.service.ts b/src/abstractions/audit.service.ts new file mode 100644 index 0000000000..0ea1e1fe89 --- /dev/null +++ b/src/abstractions/audit.service.ts @@ -0,0 +1,3 @@ +export abstract class AuditService { + passwordLeaked: (password: string) => Promise; +} diff --git a/src/abstractions/crypto.service.ts b/src/abstractions/crypto.service.ts index dbf5f0da75..704171f2c8 100644 --- a/src/abstractions/crypto.service.ts +++ b/src/abstractions/crypto.service.ts @@ -31,4 +31,5 @@ export abstract class CryptoService { decrypt: (cipherString: CipherString, key?: SymmetricCryptoKey, outputEncoding?: string) => Promise; decryptFromBytes: (encBuf: ArrayBuffer, key: SymmetricCryptoKey) => Promise; rsaDecrypt: (encValue: string) => Promise; + sha1: (password: string) => Promise; } diff --git a/src/services/audit.service.ts b/src/services/audit.service.ts new file mode 100644 index 0000000000..f91da9c76d --- /dev/null +++ b/src/services/audit.service.ts @@ -0,0 +1,26 @@ +import { CryptoService } from '../abstractions/crypto.service'; + +const PwnedPasswordsApi = 'https://api.pwnedpasswords.com/range/'; + +export class AuditService { + + constructor(private cryptoService: CryptoService) { + } + + async passwordLeaked(password: string): Promise { + const hash = (await this.cryptoService.sha1(password)).toUpperCase(); + + const response = await fetch(PwnedPasswordsApi + hash.substr(0, 5)); + const leakedHashes = await response.text(); + + const hashEnding = hash.substr(5); + + const match = leakedHashes + .split(/\r?\n/) + .find((v) => { + return v.split(':')[0] === hashEnding; + }); + + return match ? parseInt(match.split(':')[1], 10) : 0; + } +} diff --git a/src/services/crypto.service.ts b/src/services/crypto.service.ts index e6ad642574..93dbcd3b5c 100644 --- a/src/services/crypto.service.ts +++ b/src/services/crypto.service.ts @@ -452,6 +452,17 @@ export class CryptoService implements CryptoServiceAbstraction { return b64DecValue; } + async sha1(password: string): Promise { + const hash = await Crypto.subtle.digest( + { + name: 'SHA-1', + }, + UtilsService.fromUtf8ToArray(password), + ); + + return UtilsService.fromBufferToHex(hash); + } + // Helpers private async aesEncrypt(plainValue: ArrayBuffer, key: SymmetricCryptoKey): Promise { diff --git a/src/services/utils.service.ts b/src/services/utils.service.ts index 359a743a91..a1e900e761 100644 --- a/src/services/utils.service.ts +++ b/src/services/utils.service.ts @@ -125,6 +125,14 @@ export class UtilsService implements UtilsServiceAbstraction { return decodeURIComponent(escape(encodedString)); } + // Source: Frxstrem, https://stackoverflow.com/questions/40031688/javascript-arraybuffer-to-hex + static fromBufferToHex(buffer: ArrayBuffer): string { + return Array.prototype.map.call( + new Uint8Array(buffer), + (x: number) => ('00' + x.toString(16)).slice(-2), + ).join(''); + } + static getHostname(uriString: string): string { if (uriString == null) { return null;