2018-04-19 06:00:53 +02:00
|
|
|
import * as constants from 'constants';
|
2018-04-20 05:12:06 +02:00
|
|
|
import * as crypto from 'crypto';
|
2018-04-20 21:32:25 +02:00
|
|
|
import * as forge from 'node-forge';
|
2018-04-15 05:20:37 +02:00
|
|
|
|
|
|
|
import { CryptoFunctionService } from '../abstractions/cryptoFunction.service';
|
|
|
|
|
2018-04-20 21:32:25 +02:00
|
|
|
import { Utils } from '../misc/utils';
|
|
|
|
|
2018-05-07 15:00:49 +02:00
|
|
|
import { DecryptParameters } from '../models/domain/decryptParameters';
|
|
|
|
import { SymmetricCryptoKey } from '../models/domain/symmetricCryptoKey';
|
|
|
|
|
2018-04-15 05:20:37 +02:00
|
|
|
export class NodeCryptoFunctionService implements CryptoFunctionService {
|
2018-04-18 14:50:02 +02:00
|
|
|
pbkdf2(password: string | ArrayBuffer, salt: string | ArrayBuffer, algorithm: 'sha256' | 'sha512',
|
2018-04-18 03:18:47 +02:00
|
|
|
iterations: number): Promise<ArrayBuffer> {
|
2018-04-25 22:30:47 +02:00
|
|
|
const len = algorithm === 'sha256' ? 32 : 64;
|
2018-04-17 21:59:38 +02:00
|
|
|
const nodePassword = this.toNodeValue(password);
|
|
|
|
const nodeSalt = this.toNodeValue(salt);
|
2018-04-15 05:20:37 +02:00
|
|
|
return new Promise<ArrayBuffer>((resolve, reject) => {
|
2018-04-25 22:30:47 +02:00
|
|
|
crypto.pbkdf2(nodePassword, nodeSalt, iterations, len, algorithm, (error, key) => {
|
2018-04-15 05:20:37 +02:00
|
|
|
if (error != null) {
|
|
|
|
reject(error);
|
|
|
|
} else {
|
2018-04-20 16:59:55 +02:00
|
|
|
resolve(this.toArrayBuffer(key));
|
2018-04-15 05:20:37 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2018-04-17 21:59:38 +02:00
|
|
|
|
2018-07-31 05:29:30 +02:00
|
|
|
hash(value: string | ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512' | 'md5'): Promise<ArrayBuffer> {
|
2018-04-17 21:59:38 +02:00
|
|
|
const nodeValue = this.toNodeValue(value);
|
|
|
|
const hash = crypto.createHash(algorithm);
|
|
|
|
hash.update(nodeValue);
|
2018-04-20 16:59:55 +02:00
|
|
|
return Promise.resolve(this.toArrayBuffer(hash.digest()));
|
2018-04-17 21:59:38 +02:00
|
|
|
}
|
|
|
|
|
2018-04-18 14:50:02 +02:00
|
|
|
hmac(value: ArrayBuffer, key: ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512'): Promise<ArrayBuffer> {
|
|
|
|
const nodeValue = this.toNodeBuffer(value);
|
2018-04-22 05:14:04 +02:00
|
|
|
const nodeKey = this.toNodeBuffer(key);
|
2018-04-18 14:50:02 +02:00
|
|
|
const hmac = crypto.createHmac(algorithm, nodeKey);
|
|
|
|
hmac.update(nodeValue);
|
2018-04-20 16:59:55 +02:00
|
|
|
return Promise.resolve(this.toArrayBuffer(hmac.digest()));
|
2018-04-18 14:50:02 +02:00
|
|
|
}
|
|
|
|
|
2018-05-07 18:14:40 +02:00
|
|
|
async compare(a: ArrayBuffer, b: ArrayBuffer): Promise<boolean> {
|
2018-05-07 15:00:49 +02:00
|
|
|
const key = await this.randomBytes(32);
|
|
|
|
const mac1 = await this.hmac(a, key, 'sha256');
|
|
|
|
const mac2 = await this.hmac(b, key, 'sha256');
|
|
|
|
if (mac1.byteLength !== mac2.byteLength) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const arr1 = new Uint8Array(mac1);
|
|
|
|
const arr2 = new Uint8Array(mac2);
|
|
|
|
for (let i = 0; i < arr2.length; i++) {
|
|
|
|
if (arr1[i] !== arr2[i]) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
hmacFast(value: ArrayBuffer, key: ArrayBuffer, algorithm: 'sha1' | 'sha256' | 'sha512'): Promise<ArrayBuffer> {
|
|
|
|
return this.hmac(value, key, algorithm);
|
|
|
|
}
|
|
|
|
|
2018-05-07 18:14:40 +02:00
|
|
|
compareFast(a: ArrayBuffer, b: ArrayBuffer): Promise<boolean> {
|
|
|
|
return this.compare(a, b);
|
2018-05-07 15:00:49 +02:00
|
|
|
}
|
|
|
|
|
2018-04-18 14:50:02 +02:00
|
|
|
aesEncrypt(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise<ArrayBuffer> {
|
|
|
|
const nodeData = this.toNodeBuffer(data);
|
|
|
|
const nodeIv = this.toNodeBuffer(iv);
|
|
|
|
const nodeKey = this.toNodeBuffer(key);
|
|
|
|
const cipher = crypto.createCipheriv('aes-256-cbc', nodeKey, nodeIv);
|
|
|
|
const encBuf = Buffer.concat([cipher.update(nodeData), cipher.final()]);
|
2018-04-20 16:59:55 +02:00
|
|
|
return Promise.resolve(this.toArrayBuffer(encBuf));
|
2018-04-18 14:50:02 +02:00
|
|
|
}
|
|
|
|
|
2018-05-07 15:00:49 +02:00
|
|
|
aesDecryptFastParameters(data: string, iv: string, mac: string, key: SymmetricCryptoKey):
|
|
|
|
DecryptParameters<ArrayBuffer> {
|
|
|
|
const p = new DecryptParameters<ArrayBuffer>();
|
|
|
|
p.encKey = key.encKey;
|
|
|
|
p.data = Utils.fromB64ToArray(data).buffer;
|
|
|
|
p.iv = Utils.fromB64ToArray(iv).buffer;
|
|
|
|
|
|
|
|
const macData = new Uint8Array(p.iv.byteLength + p.data.byteLength);
|
|
|
|
macData.set(new Uint8Array(p.iv), 0);
|
|
|
|
macData.set(new Uint8Array(p.data), p.iv.byteLength);
|
|
|
|
p.macData = macData.buffer;
|
|
|
|
|
|
|
|
if (key.macKey != null) {
|
|
|
|
p.macKey = key.macKey;
|
|
|
|
}
|
|
|
|
if (mac != null) {
|
|
|
|
p.mac = Utils.fromB64ToArray(mac).buffer;
|
|
|
|
}
|
|
|
|
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
|
|
|
async aesDecryptFast(parameters: DecryptParameters<ArrayBuffer>): Promise<string> {
|
2018-05-07 18:14:40 +02:00
|
|
|
const decBuf = await this.aesDecrypt(parameters.data, parameters.iv, parameters.encKey);
|
2018-05-07 15:00:49 +02:00
|
|
|
return Utils.fromBufferToUtf8(decBuf);
|
2018-04-18 14:50:02 +02:00
|
|
|
}
|
|
|
|
|
2018-05-07 18:14:40 +02:00
|
|
|
aesDecrypt(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise<ArrayBuffer> {
|
2018-04-18 14:50:02 +02:00
|
|
|
const nodeData = this.toNodeBuffer(data);
|
|
|
|
const nodeIv = this.toNodeBuffer(iv);
|
|
|
|
const nodeKey = this.toNodeBuffer(key);
|
|
|
|
const decipher = crypto.createDecipheriv('aes-256-cbc', nodeKey, nodeIv);
|
|
|
|
const decBuf = Buffer.concat([decipher.update(nodeData), decipher.final()]);
|
2018-04-20 16:59:55 +02:00
|
|
|
return Promise.resolve(this.toArrayBuffer(decBuf));
|
2018-04-18 14:50:02 +02:00
|
|
|
}
|
|
|
|
|
2018-04-20 21:32:25 +02:00
|
|
|
rsaEncrypt(data: ArrayBuffer, publicKey: ArrayBuffer, algorithm: 'sha1' | 'sha256'): Promise<ArrayBuffer> {
|
|
|
|
if (algorithm === 'sha256') {
|
2018-05-21 15:10:13 +02:00
|
|
|
throw new Error('Node crypto does not support RSA-OAEP SHA-256');
|
2018-04-19 06:00:53 +02:00
|
|
|
}
|
|
|
|
|
2018-05-21 15:10:13 +02:00
|
|
|
const pem = this.toPemPublicKey(publicKey);
|
|
|
|
const decipher = crypto.publicEncrypt(pem, this.toNodeBuffer(data));
|
|
|
|
return Promise.resolve(this.toArrayBuffer(decipher));
|
2018-04-20 21:32:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
rsaDecrypt(data: ArrayBuffer, privateKey: ArrayBuffer, algorithm: 'sha1' | 'sha256'): Promise<ArrayBuffer> {
|
|
|
|
if (algorithm === 'sha256') {
|
2018-05-21 15:10:13 +02:00
|
|
|
throw new Error('Node crypto does not support RSA-OAEP SHA-256');
|
2018-04-20 21:32:25 +02:00
|
|
|
}
|
|
|
|
|
2018-05-21 15:10:13 +02:00
|
|
|
const pem = this.toPemPrivateKey(privateKey);
|
|
|
|
const decipher = crypto.privateDecrypt(pem, this.toNodeBuffer(data));
|
|
|
|
return Promise.resolve(this.toArrayBuffer(decipher));
|
2018-04-19 06:00:53 +02:00
|
|
|
}
|
|
|
|
|
2018-07-03 05:57:22 +02:00
|
|
|
rsaExtractPublicKey(privateKey: ArrayBuffer): Promise<ArrayBuffer> {
|
2018-07-03 05:53:44 +02:00
|
|
|
const privateKeyByteString = Utils.fromBufferToByteString(privateKey);
|
|
|
|
const privateKeyAsn1 = forge.asn1.fromDer(privateKeyByteString);
|
|
|
|
const forgePrivateKey = (forge as any).pki.privateKeyFromAsn1(privateKeyAsn1);
|
|
|
|
const forgePublicKey = (forge.pki as any).setRsaPublicKey(forgePrivateKey.n, forgePrivateKey.e);
|
|
|
|
const publicKeyAsn1 = (forge.pki as any).publicKeyToAsn1(forgePublicKey);
|
|
|
|
const publicKeyByteString = forge.asn1.toDer(publicKeyAsn1).data;
|
2018-07-03 05:57:22 +02:00
|
|
|
const publicKeyArray = Utils.fromByteStringToArray(publicKeyByteString);
|
|
|
|
return Promise.resolve(publicKeyArray.buffer);
|
2018-07-03 05:53:44 +02:00
|
|
|
}
|
|
|
|
|
2018-07-03 17:41:55 +02:00
|
|
|
async rsaGenerateKeyPair(length: 1024 | 2048 | 4096): Promise<[ArrayBuffer, ArrayBuffer]> {
|
|
|
|
return new Promise<[ArrayBuffer, ArrayBuffer]>((resolve, reject) => {
|
|
|
|
forge.pki.rsa.generateKeyPair({
|
|
|
|
bits: length,
|
|
|
|
workers: -1,
|
|
|
|
e: 0x10001, // 65537
|
|
|
|
}, (error, keyPair) => {
|
|
|
|
if (error != null) {
|
|
|
|
reject(error);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const publicKeyAsn1 = (forge.pki as any).publicKeyToAsn1(keyPair.publicKey);
|
|
|
|
const publicKeyByteString = forge.asn1.toDer(publicKeyAsn1).getBytes();
|
|
|
|
const publicKey = Utils.fromByteStringToArray(publicKeyByteString);
|
|
|
|
|
|
|
|
const privateKeyAsn1 = (forge.pki as any).privateKeyToAsn1(keyPair.privateKey);
|
|
|
|
const privateKeyPkcs8 = (forge.pki as any).wrapRsaPrivateKey(privateKeyAsn1);
|
|
|
|
const privateKeyByteString = forge.asn1.toDer(privateKeyPkcs8).getBytes();
|
|
|
|
const privateKey = Utils.fromByteStringToArray(privateKeyByteString);
|
|
|
|
|
|
|
|
resolve([publicKey.buffer, privateKey.buffer]);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-04-18 14:50:02 +02:00
|
|
|
randomBytes(length: number): Promise<ArrayBuffer> {
|
|
|
|
return new Promise<ArrayBuffer>((resolve, reject) => {
|
|
|
|
crypto.randomBytes(length, (error, bytes) => {
|
|
|
|
if (error != null) {
|
|
|
|
reject(error);
|
|
|
|
} else {
|
2018-04-20 16:59:55 +02:00
|
|
|
resolve(this.toArrayBuffer(bytes));
|
2018-04-18 14:50:02 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
2018-04-18 01:02:58 +02:00
|
|
|
}
|
|
|
|
|
2018-04-17 21:59:38 +02:00
|
|
|
private toNodeValue(value: string | ArrayBuffer): string | Buffer {
|
|
|
|
let nodeValue: string | Buffer;
|
|
|
|
if (typeof (value) === 'string') {
|
|
|
|
nodeValue = value;
|
|
|
|
} else {
|
2018-04-18 14:50:02 +02:00
|
|
|
nodeValue = this.toNodeBuffer(value);
|
2018-04-17 21:59:38 +02:00
|
|
|
}
|
|
|
|
return nodeValue;
|
|
|
|
}
|
2018-04-18 14:50:02 +02:00
|
|
|
|
|
|
|
private toNodeBuffer(value: ArrayBuffer): Buffer {
|
2018-04-18 19:43:42 +02:00
|
|
|
return Buffer.from(new Uint8Array(value) as any);
|
2018-04-18 14:50:02 +02:00
|
|
|
}
|
2018-04-19 06:00:53 +02:00
|
|
|
|
2018-04-20 16:59:55 +02:00
|
|
|
private toArrayBuffer(buf: Buffer): ArrayBuffer {
|
|
|
|
return new Uint8Array(buf).buffer;
|
|
|
|
}
|
|
|
|
|
2018-05-21 15:10:13 +02:00
|
|
|
private toPemPrivateKey(key: ArrayBuffer): string {
|
2018-04-20 21:32:25 +02:00
|
|
|
const byteString = Utils.fromBufferToByteString(key);
|
|
|
|
const asn1 = forge.asn1.fromDer(byteString);
|
2018-05-21 15:10:13 +02:00
|
|
|
const privateKey = (forge as any).pki.privateKeyFromAsn1(asn1);
|
|
|
|
const rsaPrivateKey = (forge.pki as any).privateKeyToAsn1(privateKey);
|
|
|
|
const privateKeyInfo = (forge.pki as any).wrapRsaPrivateKey(rsaPrivateKey);
|
|
|
|
return (forge.pki as any).privateKeyInfoToPem(privateKeyInfo);
|
2018-04-20 21:32:25 +02:00
|
|
|
}
|
|
|
|
|
2018-05-21 15:10:13 +02:00
|
|
|
private toPemPublicKey(key: ArrayBuffer): string {
|
2018-04-20 21:32:25 +02:00
|
|
|
const byteString = Utils.fromBufferToByteString(key);
|
|
|
|
const asn1 = forge.asn1.fromDer(byteString);
|
2018-05-21 15:10:13 +02:00
|
|
|
const publicKey = (forge as any).pki.publicKeyFromAsn1(asn1);
|
|
|
|
return (forge.pki as any).publicKeyToPem(publicKey);
|
2018-04-19 06:00:53 +02:00
|
|
|
}
|
2018-04-15 05:20:37 +02:00
|
|
|
}
|