bitwarden-browser/libs/node/src/services/node-crypto-function.servic...

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

325 lines
11 KiB
TypeScript
Raw Normal View History

2018-04-20 05:12:06 +02:00
import * as crypto from "crypto";
2022-02-22 15:39:11 +01:00
import * as argon2 from "argon2";
2018-04-20 21:32:25 +02:00
import * as forge from "node-forge";
2022-06-14 17:10:53 +02:00
import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service";
import { Utils } from "@bitwarden/common/misc/utils";
import { DecryptParameters } from "@bitwarden/common/models/domain/decrypt-parameters";
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key";
2018-04-20 21:32:25 +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",
iterations: number
): Promise<ArrayBuffer> {
const len = algorithm === "sha256" ? 32 : 64;
const nodePassword = this.toNodeValue(password);
const nodeSalt = this.toNodeValue(salt);
2018-04-20 16:59:55 +02:00
return new Promise<ArrayBuffer>((resolve, reject) => {
2018-04-18 14:50:02 +02:00
crypto.pbkdf2(nodePassword, nodeSalt, iterations, len, algorithm, (error, key) => {
2018-05-07 15:00:49 +02:00
if (error != null) {
reject(error);
} else {
resolve(this.toArrayBuffer(key));
}
2021-12-16 13:36:21 +01:00
});
2018-05-07 15:00:49 +02:00
});
2021-12-16 13:36:21 +01:00
}
async argon2(
password: string | ArrayBuffer,
salt: string | ArrayBuffer,
iterations: number,
memory: number,
parallelism: number
): Promise<ArrayBuffer> {
const nodePassword = this.toNodeValue(password);
const nodeSalt = this.toNodeBuffer(this.toArrayBuffer(salt));
const hash = await argon2.hash(nodePassword, {
salt: nodeSalt,
raw: true,
hashLength: 32,
timeCost: iterations,
memoryCost: memory,
parallelism: parallelism,
type: argon2.argon2id,
});
return this.toArrayBuffer(hash);
}
2018-05-07 15:00:49 +02:00
// ref: https://tools.ietf.org/html/rfc5869
async hkdf(
ikm: ArrayBuffer,
salt: string | ArrayBuffer,
info: string | ArrayBuffer,
2018-05-07 15:00:49 +02:00
outputByteSize: number,
algorithm: "sha256" | "sha512"
): Promise<ArrayBuffer> {
const saltBuf = this.toArrayBuffer(salt);
2018-05-07 15:00:49 +02:00
const prk = await this.hmac(ikm, saltBuf, algorithm);
return this.hkdfExpand(prk, info, outputByteSize, algorithm);
}
2021-12-16 13:36:21 +01:00
2018-05-07 18:14:40 +02:00
// ref: https://tools.ietf.org/html/rfc5869
async hkdfExpand(
prk: ArrayBuffer,
2018-04-18 14:50:02 +02:00
info: string | ArrayBuffer,
outputByteSize: number,
algorithm: "sha256" | "sha512"
): Promise<ArrayBuffer> {
const hashLen = algorithm === "sha256" ? 32 : 64;
if (outputByteSize > 255 * hashLen) {
2018-04-20 16:59:55 +02:00
throw new Error("outputByteSize is too large.");
2018-04-18 14:50:02 +02:00
}
2018-05-07 15:00:49 +02:00
const prkArr = new Uint8Array(prk);
if (prkArr.length < hashLen) {
throw new Error("prk is too small.");
}
const infoBuf = this.toArrayBuffer(info);
const infoArr = new Uint8Array(infoBuf);
let runningOkmLength = 0;
2018-05-07 15:00:49 +02:00
let previousT = new Uint8Array(0);
const n = Math.ceil(outputByteSize / hashLen);
const okm = new Uint8Array(n * hashLen);
for (let i = 0; i < n; i++) {
const t = new Uint8Array(previousT.length + infoArr.length + 1);
t.set(previousT);
2018-05-07 15:00:49 +02:00
t.set(infoArr, previousT.length);
2018-05-07 18:14:40 +02:00
t.set([i + 1], t.length - 1);
previousT = new Uint8Array(await this.hmac(t.buffer, prk, algorithm));
2018-05-07 15:00:49 +02:00
okm.set(previousT, runningOkmLength);
runningOkmLength += previousT.length;
2018-05-07 15:00:49 +02:00
if (runningOkmLength >= outputByteSize) {
2021-12-16 13:36:21 +01:00
break;
}
2018-04-18 14:50:02 +02:00
}
2018-05-07 18:14:40 +02:00
return okm.slice(0, outputByteSize).buffer;
2021-12-16 13:36:21 +01:00
}
hash(
2018-05-07 18:14:40 +02:00
value: string | ArrayBuffer,
algorithm: "sha1" | "sha256" | "sha512" | "md5"
): Promise<ArrayBuffer> {
2018-04-18 14:50:02 +02:00
const nodeValue = this.toNodeValue(value);
const hash = crypto.createHash(algorithm);
hash.update(nodeValue);
return Promise.resolve(this.toArrayBuffer(hash.digest()));
2021-12-16 13:36:21 +01:00
}
hmac(
2018-04-18 14:50:02 +02:00
value: ArrayBuffer,
key: ArrayBuffer,
algorithm: "sha1" | "sha256" | "sha512"
): Promise<ArrayBuffer> {
const nodeValue = this.toNodeBuffer(value);
const nodeKey = this.toNodeBuffer(key);
const hmac = crypto.createHmac(algorithm, nodeKey);
hmac.update(nodeValue);
2018-04-20 16:59:55 +02:00
return Promise.resolve(this.toArrayBuffer(hmac.digest()));
2021-12-16 13:36:21 +01:00
}
2018-04-20 16:59:55 +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;
2018-04-18 14:50:02 +02:00
}
2018-04-20 21:32:25 +02:00
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;
2021-12-16 13:36:21 +01:00
}
2018-04-20 21:32:25 +02:00
}
return true;
2021-12-16 13:36:21 +01:00
}
hmacFast(
2018-04-20 21:32:25 +02:00
value: ArrayBuffer,
2018-04-18 14:50:02 +02:00
key: ArrayBuffer,
2018-04-20 21:32:25 +02:00
algorithm: "sha1" | "sha256" | "sha512"
): Promise<ArrayBuffer> {
return this.hmac(value, key, algorithm);
2021-12-16 13:36:21 +01:00
}
2018-05-07 18:14:40 +02:00
compareFast(a: ArrayBuffer, b: ArrayBuffer): Promise<boolean> {
return this.compare(a, b);
2021-12-16 13:36:21 +01:00
}
2018-04-20 21:32:25 +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()]);
return Promise.resolve(this.toArrayBuffer(encBuf));
2021-12-16 13:36:21 +01:00
}
2018-05-07 15:00:49 +02:00
aesDecryptFastParameters(
data: string,
iv: string,
mac: string,
key: SymmetricCryptoKey
): DecryptParameters<ArrayBuffer> {
2018-05-07 15:00:49 +02:00
const p = new DecryptParameters<ArrayBuffer>();
p.encKey = key.encKey;
p.data = Utils.fromB64ToArray(data).buffer;
2018-05-07 15:00:49 +02:00
p.iv = Utils.fromB64ToArray(iv).buffer;
2021-12-16 13:36:21 +01:00
const macData = new Uint8Array(p.iv.byteLength + p.data.byteLength);
2018-05-07 15:00:49 +02:00
macData.set(new Uint8Array(p.iv), 0);
macData.set(new Uint8Array(p.data), p.iv.byteLength);
p.macData = macData.buffer;
2021-12-16 13:36:21 +01:00
2018-05-07 15:00:49 +02:00
if (key.macKey != null) {
p.macKey = key.macKey;
2018-04-19 06:00:53 +02:00
}
if (mac != null) {
p.mac = Utils.fromB64ToArray(mac).buffer;
2018-07-03 05:53:44 +02:00
}
2018-07-03 17:41:55 +02:00
return p;
2021-12-16 13:36:21 +01:00
}
2018-07-03 17:41:55 +02:00
async aesDecryptFast(parameters: DecryptParameters<ArrayBuffer>): Promise<string> {
const decBuf = await this.aesDecrypt(parameters.data, parameters.iv, parameters.encKey);
return Utils.fromBufferToUtf8(decBuf);
2021-12-16 13:36:21 +01:00
}
2018-07-03 17:41:55 +02:00
aesDecrypt(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise<ArrayBuffer> {
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()]);
return Promise.resolve(this.toArrayBuffer(decBuf));
2021-12-16 13:36:21 +01:00
}
2018-04-20 21:32:25 +02:00
rsaEncrypt(
2018-07-03 17:41:55 +02:00
data: ArrayBuffer,
publicKey: ArrayBuffer,
algorithm: "sha1" | "sha256"
2018-04-18 14:50:02 +02:00
): Promise<ArrayBuffer> {
2018-04-20 21:32:25 +02:00
if (algorithm === "sha256") {
2018-07-03 17:41:55 +02:00
throw new Error("Node crypto does not support RSA-OAEP SHA-256");
}
2018-04-18 14:50:02 +02:00
const pem = this.toPemPublicKey(publicKey);
const decipher = crypto.publicEncrypt(pem, this.toNodeBuffer(data));
return Promise.resolve(this.toArrayBuffer(decipher));
2021-12-16 13:36:21 +01:00
}
2018-04-18 14:50:02 +02:00
rsaDecrypt(
data: ArrayBuffer,
2018-04-20 16:59:55 +02:00
privateKey: ArrayBuffer,
2018-04-18 14:50:02 +02:00
algorithm: "sha1" | "sha256"
2018-04-20 16:59:55 +02:00
): Promise<ArrayBuffer> {
2018-04-20 21:32:25 +02:00
if (algorithm === "sha256") {
2018-04-20 16:59:55 +02:00
throw new Error("Node crypto does not support RSA-OAEP SHA-256");
2018-04-18 01:02:58 +02:00
}
const pem = this.toPemPrivateKey(privateKey);
const decipher = crypto.privateDecrypt(pem, this.toNodeBuffer(data));
return Promise.resolve(this.toArrayBuffer(decipher));
2021-12-16 13:36:21 +01:00
}
rsaExtractPublicKey(privateKey: ArrayBuffer): Promise<ArrayBuffer> {
const privateKeyByteString = Utils.fromBufferToByteString(privateKey);
const privateKeyAsn1 = forge.asn1.fromDer(privateKeyByteString);
2022-03-22 14:05:55 +01:00
const forgePrivateKey: any = forge.pki.privateKeyFromAsn1(privateKeyAsn1);
const forgePublicKey = (forge.pki as any).setRsaPublicKey(forgePrivateKey.n, forgePrivateKey.e);
2022-03-22 14:05:55 +01:00
const publicKeyAsn1 = forge.pki.publicKeyToAsn1(forgePublicKey);
2018-07-03 05:53:44 +02:00
const publicKeyByteString = forge.asn1.toDer(publicKeyAsn1).data;
2018-07-03 17:41:55 +02:00
const publicKeyArray = Utils.fromByteStringToArray(publicKeyByteString);
return Promise.resolve(publicKeyArray.buffer);
2021-12-16 13:36:21 +01:00
}
2018-04-18 14:50:02 +02:00
async rsaGenerateKeyPair(length: 1024 | 2048 | 4096): Promise<[ArrayBuffer, ArrayBuffer]> {
return new Promise<[ArrayBuffer, ArrayBuffer]>((resolve, reject) => {
2018-07-03 17:41:55 +02:00
forge.pki.rsa.generateKeyPair(
2021-12-16 13:36:21 +01:00
{
2018-07-03 17:41:55 +02:00
bits: length,
workers: -1,
e: 0x10001, // 65537
2021-12-16 13:36:21 +01:00
},
(error, keyPair) => {
if (error != null) {
2018-04-18 14:50:02 +02:00
reject(error);
2021-12-16 13:36:21 +01:00
return;
}
2022-03-22 14:05:55 +01:00
const publicKeyAsn1 = forge.pki.publicKeyToAsn1(keyPair.publicKey);
const publicKeyByteString = forge.asn1.toDer(publicKeyAsn1).getBytes();
2018-07-03 17:41:55 +02:00
const publicKey = Utils.fromByteStringToArray(publicKeyByteString);
2021-12-16 13:36:21 +01:00
2022-03-22 14:05:55 +01:00
const privateKeyAsn1 = forge.pki.privateKeyToAsn1(keyPair.privateKey);
const privateKeyPkcs8 = forge.pki.wrapRsaPrivateKey(privateKeyAsn1);
2018-04-18 14:50:02 +02:00
const privateKeyByteString = forge.asn1.toDer(privateKeyPkcs8).getBytes();
const privateKey = Utils.fromByteStringToArray(privateKeyByteString);
2021-12-16 13:36:21 +01:00
2018-04-18 14:50:02 +02:00
resolve([publicKey.buffer, privateKey.buffer]);
}
);
2021-12-16 13:36:21 +01:00
});
}
2021-12-16 13:36:21 +01:00
2018-04-18 14:50:02 +02:00
randomBytes(length: number): Promise<ArrayBuffer> {
2018-04-18 19:43:42 +02:00
return new Promise<ArrayBuffer>((resolve, reject) => {
crypto.randomBytes(length, (error, bytes) => {
if (error != null) {
reject(error);
} else {
resolve(this.toArrayBuffer(bytes));
}
2021-12-16 13:36:21 +01:00
});
});
}
private toNodeValue(value: string | ArrayBuffer): string | Buffer {
let nodeValue: string | Buffer;
if (typeof value === "string") {
2018-04-18 14:50:02 +02:00
nodeValue = value;
2021-12-16 13:36:21 +01:00
} else {
2018-04-18 14:50:02 +02:00
nodeValue = this.toNodeBuffer(value);
2018-04-20 16:59:55 +02:00
}
return nodeValue;
2021-12-16 13:36:21 +01:00
}
private toNodeBuffer(value: ArrayBuffer): Buffer {
return Buffer.from(new Uint8Array(value) as any);
2021-12-16 13:36:21 +01:00
}
private toArrayBuffer(value: Buffer | string | ArrayBuffer): ArrayBuffer {
let buf: ArrayBuffer;
if (typeof value === "string") {
2018-04-20 21:32:25 +02:00
buf = Utils.fromUtf8ToArray(value).buffer;
2021-12-16 13:36:21 +01:00
} else {
2018-04-20 21:32:25 +02:00
buf = new Uint8Array(value).buffer;
2018-04-19 06:00:53 +02:00
}
return buf;
2021-12-16 13:36:21 +01:00
}
2018-07-03 05:53:44 +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);
2022-03-22 14:05:55 +01:00
const privateKey = forge.pki.privateKeyFromAsn1(asn1);
const rsaPrivateKey = forge.pki.privateKeyToAsn1(privateKey);
const privateKeyInfo = forge.pki.wrapRsaPrivateKey(rsaPrivateKey);
return forge.pki.privateKeyInfoToPem(privateKeyInfo);
2021-12-16 13:36:21 +01: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);
2022-03-22 14:05:55 +01:00
const publicKey = forge.pki.publicKeyFromAsn1(asn1);
return forge.pki.publicKeyToPem(publicKey);
2021-12-16 13:36:21 +01:00
}
}