From 81f7bd7b76061f1526a2922cfcf52fadbbef903a Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Tue, 17 Apr 2018 19:02:58 -0400 Subject: [PATCH] hmac implementation for web crypto --- src/abstractions/cryptoFunction.service.ts | 1 + src/services/nodeCryptoFunction.service.ts | 4 +++ .../webCryptoFunction.service.spec.ts | 24 +++++++++++++++++ src/services/webCryptoFunction.service.ts | 27 +++++++++++++++++-- 4 files changed, 54 insertions(+), 2 deletions(-) diff --git a/src/abstractions/cryptoFunction.service.ts b/src/abstractions/cryptoFunction.service.ts index a992da8379..1012090bb0 100644 --- a/src/abstractions/cryptoFunction.service.ts +++ b/src/abstractions/cryptoFunction.service.ts @@ -2,4 +2,5 @@ export abstract class CryptoFunctionService { pbkdf2: (password: string | ArrayBuffer, salt: string | ArrayBuffer, algorithm: 'sha256' | 'sha512', iterations: number, length: number) => Promise; hash: (value: string | ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512') => Promise; + hmac: (value: ArrayBuffer, key: ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512') => Promise; } diff --git a/src/services/nodeCryptoFunction.service.ts b/src/services/nodeCryptoFunction.service.ts index 0c0d4aaf00..d9a54774e7 100644 --- a/src/services/nodeCryptoFunction.service.ts +++ b/src/services/nodeCryptoFunction.service.ts @@ -25,6 +25,10 @@ export class NodeCryptoFunctionService implements CryptoFunctionService { return hash.digest().buffer; } + async hmac(value: ArrayBuffer, key: ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512'): Promise { + return new Uint8Array([]).buffer; + } + private toNodeValue(value: string | ArrayBuffer): string | Buffer { let nodeValue: string | Buffer; if (typeof (value) === 'string') { diff --git a/src/services/webCryptoFunction.service.spec.ts b/src/services/webCryptoFunction.service.spec.ts index 8d48e72aed..4da6ae5865 100644 --- a/src/services/webCryptoFunction.service.spec.ts +++ b/src/services/webCryptoFunction.service.spec.ts @@ -47,6 +47,21 @@ describe('WebCrypto Function Service', () => { testHash(true, 'sha256', regular256Hash, utf8256Hash, unicode256Hash); testHash(true, 'sha512', regular512Hash, utf8512Hash, unicode512Hash); }); + + describe('hmac', () => { + const sha1Mac = '4d4c223f95dc577b665ec4ccbcb680b80a397038'; + const sha256Mac = '6be3caa84922e12aaaaa2f16c40d44433bb081ef323db584eb616333ab4e874f'; + const sha512Mac = '21910e341fa12106ca35758a2285374509326c9fbe0bd64e7b99c898f841dc948c58ce66d3504d8883c' + + '5ea7817a0b7c5d4d9b00364ccd214669131fc17fe4aca'; + + testHmac(false, 'sha1', sha1Mac); + testHmac(false, 'sha256', sha256Mac); + testHmac(false, 'sha512', sha512Mac); + + testHmac(true, 'sha1', sha1Mac); + testHmac(true, 'sha256', sha256Mac); + testHmac(true, 'sha512', sha512Mac); + }); }); function testPbkdf2ValidKey(edge: boolean, algorithm: 'sha256' | 'sha512', regularKey: string, @@ -117,6 +132,15 @@ function testHash(edge: boolean, algorithm: 'sha1' | 'sha256' | 'sha512', regula }); } +function testHmac(edge: boolean, algorithm: 'sha1' | 'sha256' | 'sha512', mac: string) { + it('should create valid ' + algorithm + ' hmac' + (edge ? ' for edge' : ''), async () => { + const webCryptoFunctionService = getWebCryptoFunctionService(edge); + const computedMac = await webCryptoFunctionService.hmac(UtilsService.fromUtf8ToArray('SignMe!!').buffer, + UtilsService.fromUtf8ToArray('secretkey').buffer, algorithm); + expect(UtilsService.fromBufferToHex(computedMac)).toBe(mac); + }); +} + function getWebCryptoFunctionService(edge = false) { const platformUtilsService = new BrowserPlatformUtilsService(edge); return new WebCryptoFunctionService(window, platformUtilsService); diff --git a/src/services/webCryptoFunction.service.ts b/src/services/webCryptoFunction.service.ts index 72bd719136..639a191f6d 100644 --- a/src/services/webCryptoFunction.service.ts +++ b/src/services/webCryptoFunction.service.ts @@ -35,7 +35,7 @@ export class WebCryptoFunctionService implements CryptoFunctionService { name: 'PBKDF2', salt: saltBuf, iterations: iterations, - hash: { name: algorithm === 'sha256' ? 'SHA-256' : 'SHA-512' }, + hash: { name: this.toWebCryptoAlgorithm(algorithm) }, }; const keyType: AesDerivedKeyParams = { @@ -65,10 +65,29 @@ export class WebCryptoFunctionService implements CryptoFunctionService { const valueBuf = this.toBuf(value); return await this.subtle.digest({ - name: algorithm === 'sha1' ? 'SHA-1' : algorithm === 'sha256' ? 'SHA-256' : 'SHA-512' + name: this.toWebCryptoAlgorithm(algorithm) }, valueBuf); } + async hmac(value: ArrayBuffer, key: ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512'): Promise { + if (this.isEdge) { + const valueBytes = this.toForgeBytes(value); + const keyBytes = this.toForgeBytes(key); + const hmac = (forge as any).hmac.create(); + hmac.start(algorithm, keyBytes); + hmac.update(valueBytes); + return this.fromForgeBytesToBuf(hmac.digest().getBytes()); + } + + const signingAlgorithm = { + name: 'HMAC', + hash: { name: this.toWebCryptoAlgorithm(algorithm) }, + }; + + const importedKey = await this.subtle.importKey('raw', key, signingAlgorithm, false, ['sign']); + return await this.subtle.sign(signingAlgorithm, importedKey, value); + } + private toBuf(value: string | ArrayBuffer): ArrayBuffer { let buf: ArrayBuffer; if (typeof (value) === 'string') { @@ -94,4 +113,8 @@ export class WebCryptoFunctionService implements CryptoFunctionService { const b64 = forge.util.encode64(byteString); return UtilsService.fromB64ToArray(b64).buffer; } + + private toWebCryptoAlgorithm(algorithm: 'sha1' | 'sha256' | 'sha512'): string { + return algorithm === 'sha1' ? 'SHA-1' : algorithm === 'sha256' ? 'SHA-256' : 'SHA-512'; + } }