diff --git a/spec/node/services/nodeCryptoFunction.service.spec.ts b/spec/node/services/nodeCryptoFunction.service.spec.ts index e860be7126..bb70e8e89b 100644 --- a/spec/node/services/nodeCryptoFunction.service.spec.ts +++ b/spec/node/services/nodeCryptoFunction.service.spec.ts @@ -1,6 +1,6 @@ import { NodeCryptoFunctionService } from '../../../src/services/nodeCryptoFunction.service'; -import { UtilsService } from '../../../src/services/utils.service'; +import { Utils } from '../../../src/misc/utils'; describe('NodeCrypto Function Service', () => { describe('aesEncrypt', () => { @@ -8,9 +8,9 @@ describe('NodeCrypto Function Service', () => { const nodeCryptoFunctionService = new NodeCryptoFunctionService(); const iv = makeStaticByteArray(16); const key = makeStaticByteArray(32); - const data = UtilsService.fromUtf8ToArray('EncryptMe!'); + const data = Utils.fromUtf8ToArray('EncryptMe!'); const encValue = await nodeCryptoFunctionService.aesEncrypt(data.buffer, iv.buffer, key.buffer); - expect(UtilsService.fromBufferToB64(encValue)).toBe('ByUF8vhyX4ddU9gcooznwA=='); + expect(Utils.fromBufferToB64(encValue)).toBe('ByUF8vhyX4ddU9gcooznwA=='); }); }); @@ -19,9 +19,9 @@ describe('NodeCrypto Function Service', () => { const nodeCryptoFunctionService = new NodeCryptoFunctionService(); const iv = makeStaticByteArray(16); const key = makeStaticByteArray(32); - const data = UtilsService.fromB64ToArray('ByUF8vhyX4ddU9gcooznwA=='); - const decValue = await nodeCryptoFunctionService.aesDecryptLarge(data.buffer, iv.buffer, key.buffer); - expect(UtilsService.fromBufferToUtf8(decValue)).toBe('EncryptMe!'); + const data = Utils.fromB64ToArray('ByUF8vhyX4ddU9gcooznwA=='); + const decValue = await nodeCryptoFunctionService.aesDecryptSmall(data.buffer, iv.buffer, key.buffer); + expect(Utils.fromBufferToUtf8(decValue)).toBe('EncryptMe!'); }); }); @@ -30,9 +30,9 @@ describe('NodeCrypto Function Service', () => { const nodeCryptoFunctionService = new NodeCryptoFunctionService(); const iv = makeStaticByteArray(16); const key = makeStaticByteArray(32); - const data = UtilsService.fromB64ToArray('ByUF8vhyX4ddU9gcooznwA=='); + const data = Utils.fromB64ToArray('ByUF8vhyX4ddU9gcooznwA=='); const decValue = await nodeCryptoFunctionService.aesDecryptLarge(data.buffer, iv.buffer, key.buffer); - expect(UtilsService.fromBufferToUtf8(decValue)).toBe('EncryptMe!'); + expect(Utils.fromBufferToUtf8(decValue)).toBe('EncryptMe!'); }); }); diff --git a/spec/web/services/webCryptoFunction.service.spec.ts b/spec/web/services/webCryptoFunction.service.spec.ts index 3922fc24fa..8c6b5776fe 100644 --- a/spec/web/services/webCryptoFunction.service.spec.ts +++ b/spec/web/services/webCryptoFunction.service.spec.ts @@ -4,7 +4,7 @@ import { PlatformUtilsService } from '../../../src/abstractions/platformUtils.se import { WebCryptoFunctionService } from '../../../src/services/webCryptoFunction.service'; -import { UtilsService } from '../../../src/services/utils.service'; +import { Utils } from '../../../src/misc/utils'; describe('WebCrypto Function Service', () => { describe('pbkdf2', () => { @@ -71,9 +71,9 @@ describe('WebCrypto Function Service', () => { const webCryptoFunctionService = getWebCryptoFunctionService(); const iv = makeStaticByteArray(16); const key = makeStaticByteArray(32); - const data = UtilsService.fromUtf8ToArray('EncryptMe!'); + const data = Utils.fromUtf8ToArray('EncryptMe!'); const encValue = await webCryptoFunctionService.aesEncrypt(data.buffer, iv.buffer, key.buffer); - expect(UtilsService.fromBufferToB64(encValue)).toBe('ByUF8vhyX4ddU9gcooznwA=='); + expect(Utils.fromBufferToB64(encValue)).toBe('ByUF8vhyX4ddU9gcooznwA=='); }); }); @@ -82,9 +82,9 @@ describe('WebCrypto Function Service', () => { const webCryptoFunctionService = getWebCryptoFunctionService(); const iv = makeStaticByteArray(16); const key = makeStaticByteArray(32); - const data = UtilsService.fromB64ToArray('ByUF8vhyX4ddU9gcooznwA=='); + const data = Utils.fromB64ToArray('ByUF8vhyX4ddU9gcooznwA=='); const decValue = await webCryptoFunctionService.aesDecryptSmall(data.buffer, iv.buffer, key.buffer); - expect(UtilsService.fromBufferToUtf8(decValue)).toBe('EncryptMe!'); + expect(Utils.fromBufferToUtf8(decValue)).toBe('EncryptMe!'); }); }); @@ -93,9 +93,9 @@ describe('WebCrypto Function Service', () => { const webCryptoFunctionService = getWebCryptoFunctionService(); const iv = makeStaticByteArray(16); const key = makeStaticByteArray(32); - const data = UtilsService.fromB64ToArray('ByUF8vhyX4ddU9gcooznwA=='); + const data = Utils.fromB64ToArray('ByUF8vhyX4ddU9gcooznwA=='); const decValue = await webCryptoFunctionService.aesDecryptLarge(data.buffer, iv.buffer, key.buffer); - expect(UtilsService.fromBufferToUtf8(decValue)).toBe('EncryptMe!'); + expect(Utils.fromBufferToUtf8(decValue)).toBe('EncryptMe!'); }); }); @@ -128,26 +128,26 @@ function testPbkdf2(edge: boolean, algorithm: 'sha256' | 'sha512', regularKey: s it('should create valid ' + algorithm + ' key from regular input' + forEdge, async () => { const webCryptoFunctionService = getWebCryptoFunctionService(edge); const key = await webCryptoFunctionService.pbkdf2(regularPassword, regularEmail, algorithm, 5000); - expect(UtilsService.fromBufferToB64(key)).toBe(regularKey); + expect(Utils.fromBufferToB64(key)).toBe(regularKey); }); it('should create valid ' + algorithm + ' key from utf8 input' + forEdge, async () => { const webCryptoFunctionService = getWebCryptoFunctionService(edge); const key = await webCryptoFunctionService.pbkdf2(utf8Password, utf8Email, algorithm, 5000); - expect(UtilsService.fromBufferToB64(key)).toBe(utf8Key); + expect(Utils.fromBufferToB64(key)).toBe(utf8Key); }); it('should create valid ' + algorithm + ' key from unicode input' + forEdge, async () => { const webCryptoFunctionService = getWebCryptoFunctionService(edge); const key = await webCryptoFunctionService.pbkdf2(unicodePassword, regularEmail, algorithm, 5000); - expect(UtilsService.fromBufferToB64(key)).toBe(unicodeKey); + expect(Utils.fromBufferToB64(key)).toBe(unicodeKey); }); it('should create valid ' + algorithm + ' key from array buffer input' + forEdge, async () => { const webCryptoFunctionService = getWebCryptoFunctionService(edge); - const key = await webCryptoFunctionService.pbkdf2(UtilsService.fromUtf8ToArray(regularPassword).buffer, - UtilsService.fromUtf8ToArray(regularEmail).buffer, algorithm, 5000); - expect(UtilsService.fromBufferToB64(key)).toBe(regularKey); + const key = await webCryptoFunctionService.pbkdf2(Utils.fromUtf8ToArray(regularPassword).buffer, + Utils.fromUtf8ToArray(regularEmail).buffer, algorithm, 5000); + expect(Utils.fromBufferToB64(key)).toBe(regularKey); }); } @@ -161,34 +161,34 @@ function testHash(edge: boolean, algorithm: 'sha1' | 'sha256' | 'sha512', regula it('should create valid ' + algorithm + ' hash from regular input' + forEdge, async () => { const webCryptoFunctionService = getWebCryptoFunctionService(edge); const hash = await webCryptoFunctionService.hash(regularValue, algorithm); - expect(UtilsService.fromBufferToHex(hash)).toBe(regularHash); + expect(Utils.fromBufferToHex(hash)).toBe(regularHash); }); it('should create valid ' + algorithm + ' hash from utf8 input' + forEdge, async () => { const webCryptoFunctionService = getWebCryptoFunctionService(edge); const hash = await webCryptoFunctionService.hash(utf8Value, algorithm); - expect(UtilsService.fromBufferToHex(hash)).toBe(utf8Hash); + expect(Utils.fromBufferToHex(hash)).toBe(utf8Hash); }); it('should create valid ' + algorithm + ' hash from unicode input' + forEdge, async () => { const webCryptoFunctionService = getWebCryptoFunctionService(edge); const hash = await webCryptoFunctionService.hash(unicodeValue, algorithm); - expect(UtilsService.fromBufferToHex(hash)).toBe(unicodeHash); + expect(Utils.fromBufferToHex(hash)).toBe(unicodeHash); }); it('should create valid ' + algorithm + ' hash from array buffer input' + forEdge, async () => { const webCryptoFunctionService = getWebCryptoFunctionService(edge); - const hash = await webCryptoFunctionService.hash(UtilsService.fromUtf8ToArray(regularValue).buffer, algorithm); - expect(UtilsService.fromBufferToHex(hash)).toBe(regularHash); + const hash = await webCryptoFunctionService.hash(Utils.fromUtf8ToArray(regularValue).buffer, algorithm); + expect(Utils.fromBufferToHex(hash)).toBe(regularHash); }); } 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); + const computedMac = await webCryptoFunctionService.hmac(Utils.fromUtf8ToArray('SignMe!!').buffer, + Utils.fromUtf8ToArray('secretkey').buffer, algorithm); + expect(Utils.fromBufferToHex(computedMac)).toBe(mac); }); } diff --git a/src/misc/utils.ts b/src/misc/utils.ts new file mode 100644 index 0000000000..2b2c323ab2 --- /dev/null +++ b/src/misc/utils.ts @@ -0,0 +1,76 @@ +export class Utils { + static inited = false; + static isNode = false; + static isBrowser = true; + + static init() { + if (Utils.inited) { + return; + } + + Utils.inited = true; + Utils.isNode = typeof window === 'undefined'; + Utils.isBrowser = !Utils.isNode; + } + + static fromB64ToArray(str: string): Uint8Array { + if (Utils.isNode) { + return new Uint8Array(Buffer.from(str, 'base64')); + } else { + const binaryString = window.atob(str); + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + return bytes; + } + } + + static fromUtf8ToArray(str: string): Uint8Array { + if (Utils.isNode) { + return new Uint8Array(Buffer.from(str, 'utf8')); + } else { + const strUtf8 = unescape(encodeURIComponent(str)); + const arr = new Uint8Array(strUtf8.length); + for (let i = 0; i < strUtf8.length; i++) { + arr[i] = strUtf8.charCodeAt(i); + } + return arr; + } + } + + static fromBufferToB64(buffer: ArrayBuffer): string { + if (Utils.isNode) { + return new Buffer(buffer).toString('base64'); + } else { + let binary = ''; + const bytes = new Uint8Array(buffer); + for (let i = 0; i < bytes.byteLength; i++) { + binary += String.fromCharCode(bytes[i]); + } + return window.btoa(binary); + } + } + + static fromBufferToUtf8(buffer: ArrayBuffer): string { + if (Utils.isNode) { + return new Buffer(buffer).toString('utf8'); + } else { + const bytes = new Uint8Array(buffer); + const encodedString = String.fromCharCode.apply(null, bytes); + return decodeURIComponent(escape(encodedString)); + } + } + + // ref: https://stackoverflow.com/a/40031979/1090359 + static fromBufferToHex(buffer: ArrayBuffer): string { + if (Utils.isNode) { + return new Buffer(buffer).toString('hex'); + } else { + const bytes = new Uint8Array(buffer); + return Array.prototype.map.call(bytes, (x: number) => ('00' + x.toString(16)).slice(-2)).join(''); + } + } +} + +Utils.init(); diff --git a/src/services/nodeCryptoFunction.service.ts b/src/services/nodeCryptoFunction.service.ts index 6a8607f4a0..8a495b4d60 100644 --- a/src/services/nodeCryptoFunction.service.ts +++ b/src/services/nodeCryptoFunction.service.ts @@ -14,7 +14,7 @@ export class NodeCryptoFunctionService implements CryptoFunctionService { if (error != null) { reject(error); } else { - resolve(key.buffer); + resolve(this.toArrayBuffer(key)); } }); }); @@ -24,7 +24,7 @@ export class NodeCryptoFunctionService implements CryptoFunctionService { const nodeValue = this.toNodeValue(value); const hash = crypto.createHash(algorithm); hash.update(nodeValue); - return Promise.resolve(hash.digest().buffer); + return Promise.resolve(this.toArrayBuffer(hash.digest())); } hmac(value: ArrayBuffer, key: ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512'): Promise { @@ -32,7 +32,7 @@ export class NodeCryptoFunctionService implements CryptoFunctionService { const nodeKey = this.toNodeBuffer(value); const hmac = crypto.createHmac(algorithm, nodeKey); hmac.update(nodeValue); - return Promise.resolve(hmac.digest().buffer); + return Promise.resolve(this.toArrayBuffer(hmac.digest())); } aesEncrypt(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise { @@ -41,7 +41,7 @@ export class NodeCryptoFunctionService implements CryptoFunctionService { const nodeKey = this.toNodeBuffer(key); const cipher = crypto.createCipheriv('aes-256-cbc', nodeKey, nodeIv); const encBuf = Buffer.concat([cipher.update(nodeData), cipher.final()]); - return Promise.resolve(encBuf.buffer); + return Promise.resolve(this.toArrayBuffer(encBuf)); } aesDecryptSmall(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise { @@ -54,7 +54,7 @@ export class NodeCryptoFunctionService implements CryptoFunctionService { const nodeKey = this.toNodeBuffer(key); const decipher = crypto.createDecipheriv('aes-256-cbc', nodeKey, nodeIv); const decBuf = Buffer.concat([decipher.update(nodeData), decipher.final()]); - return Promise.resolve(decBuf.buffer); + return Promise.resolve(this.toArrayBuffer(decBuf)); } rsaDecrypt(data: ArrayBuffer, key: ArrayBuffer, algorithm: 'sha1' | 'sha256'): Promise { @@ -68,7 +68,7 @@ export class NodeCryptoFunctionService implements CryptoFunctionService { padding: constants.RSA_PKCS1_OAEP_PADDING, }; const decBuf = crypto.publicDecrypt(rsaKey, nodeData); - return Promise.resolve(decBuf.buffer); + return Promise.resolve(this.toArrayBuffer(decBuf)); } randomBytes(length: number): Promise { @@ -77,7 +77,7 @@ export class NodeCryptoFunctionService implements CryptoFunctionService { if (error != null) { reject(error); } else { - resolve(bytes.buffer); + resolve(this.toArrayBuffer(bytes)); } }); }); @@ -97,6 +97,10 @@ export class NodeCryptoFunctionService implements CryptoFunctionService { return Buffer.from(new Uint8Array(value) as any); } + private toArrayBuffer(buf: Buffer): ArrayBuffer { + return new Uint8Array(buf).buffer; + } + private toPem(key: ArrayBuffer): string { const b64Key = ''; // TODO: key to b84 return '-----BEGIN PRIVATE KEY-----\n' + b64Key + '\n-----END PRIVATE KEY-----'; diff --git a/src/services/webCryptoFunction.service.ts b/src/services/webCryptoFunction.service.ts index f83e36efc6..2b32078688 100644 --- a/src/services/webCryptoFunction.service.ts +++ b/src/services/webCryptoFunction.service.ts @@ -3,7 +3,7 @@ import * as forge from 'node-forge'; import { CryptoFunctionService } from '../abstractions/cryptoFunction.service'; import { PlatformUtilsService } from '../abstractions/platformUtils.service'; -import { UtilsService } from '../services/utils.service'; +import { Utils } from '../misc/utils'; export class WebCryptoFunctionService implements CryptoFunctionService { private crypto: Crypto; @@ -122,7 +122,7 @@ export class WebCryptoFunctionService implements CryptoFunctionService { private toBuf(value: string | ArrayBuffer): ArrayBuffer { let buf: ArrayBuffer; if (typeof (value) === 'string') { - buf = UtilsService.fromUtf8ToArray(value).buffer; + buf = Utils.fromUtf8ToArray(value).buffer; } else { buf = value; }