1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-17 16:53:34 +00:00

Fix failing crypto tests (#5948)

* Change everything to Uint8Array

related to https://github.com/jestjs/jest/issues/14379

* Work on failing type tests

* Revert changes to custom matcher setup

* Remove last BufferArrays from tests

* Fix custom matcher type errors in vscode

* Remove errant `.buffer` calls on Uint8Arrays

* Encryption Pair should serialize Array Buffer and Uint8Array

* Fix EncArrayBuffer encryption

---------

Co-authored-by: Thomas Rittson <trittson@bitwarden.com>
This commit is contained in:
Matt Gibson
2023-08-03 22:13:33 -04:00
committed by GitHub
parent efb26e3e27
commit 36b7d30804
62 changed files with 401 additions and 424 deletions

View File

@@ -170,11 +170,7 @@ describe("NodeCrypto Function Service", () => {
const iv = makeStaticByteArray(16);
const key = makeStaticByteArray(32);
const data = Utils.fromUtf8ToArray("EncryptMe!");
const encValue = await nodeCryptoFunctionService.aesEncrypt(
data.buffer,
iv.buffer,
key.buffer
);
const encValue = await nodeCryptoFunctionService.aesEncrypt(data, iv, key);
expect(Utils.fromBufferToB64(encValue)).toBe("ByUF8vhyX4ddU9gcooznwA==");
});
@@ -184,12 +180,8 @@ describe("NodeCrypto Function Service", () => {
const key = makeStaticByteArray(32);
const value = "EncryptMe!";
const data = Utils.fromUtf8ToArray(value);
const encValue = await nodeCryptoFunctionService.aesEncrypt(
data.buffer,
iv.buffer,
key.buffer
);
const decValue = await nodeCryptoFunctionService.aesDecrypt(encValue, iv.buffer, key.buffer);
const encValue = await nodeCryptoFunctionService.aesEncrypt(data, iv, key);
const decValue = await nodeCryptoFunctionService.aesDecrypt(encValue, iv, key);
expect(Utils.fromBufferToUtf8(decValue)).toBe(value);
});
});
@@ -197,8 +189,8 @@ describe("NodeCrypto Function Service", () => {
describe("aesDecryptFast", () => {
it("should successfully decrypt data", async () => {
const nodeCryptoFunctionService = new NodeCryptoFunctionService();
const iv = Utils.fromBufferToB64(makeStaticByteArray(16).buffer);
const symKey = new SymmetricCryptoKey(makeStaticByteArray(32).buffer);
const iv = Utils.fromBufferToB64(makeStaticByteArray(16));
const symKey = new SymmetricCryptoKey(makeStaticByteArray(32));
const data = "ByUF8vhyX4ddU9gcooznwA==";
const params = nodeCryptoFunctionService.aesDecryptFastParameters(data, iv, null, symKey);
const decValue = await nodeCryptoFunctionService.aesDecryptFast(params);
@@ -212,11 +204,7 @@ describe("NodeCrypto Function Service", () => {
const iv = makeStaticByteArray(16);
const key = makeStaticByteArray(32);
const data = Utils.fromB64ToArray("ByUF8vhyX4ddU9gcooznwA==");
const decValue = await nodeCryptoFunctionService.aesDecrypt(
data.buffer,
iv.buffer,
key.buffer
);
const decValue = await nodeCryptoFunctionService.aesDecrypt(data, iv, key);
expect(Utils.fromBufferToUtf8(decValue)).toBe("EncryptMe!");
});
});
@@ -228,12 +216,8 @@ describe("NodeCrypto Function Service", () => {
const privKey = Utils.fromB64ToArray(RsaPrivateKey);
const value = "EncryptMe!";
const data = Utils.fromUtf8ToArray(value);
const encValue = await nodeCryptoFunctionService.rsaEncrypt(
data.buffer,
pubKey.buffer,
"sha1"
);
const decValue = await nodeCryptoFunctionService.rsaDecrypt(encValue, privKey.buffer, "sha1");
const encValue = await nodeCryptoFunctionService.rsaEncrypt(data, pubKey, "sha1");
const decValue = await nodeCryptoFunctionService.rsaDecrypt(encValue, privKey, "sha1");
expect(Utils.fromBufferToUtf8(decValue)).toBe(value);
});
});
@@ -248,11 +232,7 @@ describe("NodeCrypto Function Service", () => {
"zFOIEPF2S1zgperEP23M01mr4dWVdYN18B32YF67xdJHMbFhp5dkQwv9CmscoWq7OE5HIfOb+JAh7BEZb+CmKhM3yWJvoR/D" +
"/5jcercUtK2o+XrzNrL4UQ7yLZcFz6Bfwb/j6ICYvqd/YJwXNE6dwlL57OfwJyCdw2rRYf0/qI00t9u8Iitw=="
);
const decValue = await nodeCryptoFunctionService.rsaDecrypt(
data.buffer,
privKey.buffer,
"sha1"
);
const decValue = await nodeCryptoFunctionService.rsaDecrypt(data, privKey, "sha1");
expect(Utils.fromBufferToUtf8(decValue)).toBe("EncryptMe!");
});
});
@@ -261,7 +241,7 @@ describe("NodeCrypto Function Service", () => {
it("should successfully extract key", async () => {
const nodeCryptoFunctionService = new NodeCryptoFunctionService();
const privKey = Utils.fromB64ToArray(RsaPrivateKey);
const publicKey = await nodeCryptoFunctionService.rsaExtractPublicKey(privKey.buffer);
const publicKey = await nodeCryptoFunctionService.rsaExtractPublicKey(privKey);
expect(Utils.fromBufferToB64(publicKey)).toBe(RsaPublicKey);
});
});
@@ -327,8 +307,8 @@ function testPbkdf2(
it("should create valid " + algorithm + " key from array buffer input", async () => {
const cryptoFunctionService = new NodeCryptoFunctionService();
const key = await cryptoFunctionService.pbkdf2(
Utils.fromUtf8ToArray(regularPassword).buffer,
Utils.fromUtf8ToArray(regularEmail).buffer,
Utils.fromUtf8ToArray(regularPassword),
Utils.fromUtf8ToArray(regularEmail),
algorithm,
5000
);
@@ -374,8 +354,8 @@ function testHkdf(
const cryptoFunctionService = new NodeCryptoFunctionService();
const key = await cryptoFunctionService.hkdf(
ikm,
Utils.fromUtf8ToArray(regularSalt).buffer,
Utils.fromUtf8ToArray(regularInfo).buffer,
Utils.fromUtf8ToArray(regularSalt),
Utils.fromUtf8ToArray(regularInfo),
32,
algorithm
);
@@ -433,10 +413,7 @@ function testHash(
it("should create valid " + algorithm + " hash from array buffer input", async () => {
const cryptoFunctionService = new NodeCryptoFunctionService();
const hash = await cryptoFunctionService.hash(
Utils.fromUtf8ToArray(regularValue).buffer,
algorithm
);
const hash = await cryptoFunctionService.hash(Utils.fromUtf8ToArray(regularValue), algorithm);
expect(Utils.fromBufferToHex(hash)).toBe(regularHash);
});
}
@@ -444,8 +421,8 @@ function testHash(
function testHmac(algorithm: "sha1" | "sha256" | "sha512", mac: string, fast = false) {
it("should create valid " + algorithm + " hmac", async () => {
const cryptoFunctionService = new NodeCryptoFunctionService();
const value = Utils.fromUtf8ToArray("SignMe!!").buffer;
const key = Utils.fromUtf8ToArray("secretkey").buffer;
const value = Utils.fromUtf8ToArray("SignMe!!");
const key = Utils.fromUtf8ToArray("secretkey");
let computedMac: ArrayBuffer = null;
if (fast) {
computedMac = await cryptoFunctionService.hmacFast(value, key, algorithm);
@@ -463,8 +440,8 @@ function testCompare(fast = false) {
a[0] = 1;
a[1] = 2;
const equal = fast
? await cryptoFunctionService.compareFast(a.buffer, a.buffer)
: await cryptoFunctionService.compare(a.buffer, a.buffer);
? await cryptoFunctionService.compareFast(a, a)
: await cryptoFunctionService.compare(a, a);
expect(equal).toBe(true);
});
@@ -477,8 +454,8 @@ function testCompare(fast = false) {
b[0] = 3;
b[1] = 4;
const equal = fast
? await cryptoFunctionService.compareFast(a.buffer, b.buffer)
: await cryptoFunctionService.compare(a.buffer, b.buffer);
? await cryptoFunctionService.compareFast(a, b)
: await cryptoFunctionService.compare(a, b);
expect(equal).toBe(false);
});
@@ -490,8 +467,8 @@ function testCompare(fast = false) {
const b = new Uint8Array(2);
b[0] = 3;
const equal = fast
? await cryptoFunctionService.compareFast(a.buffer, b.buffer)
: await cryptoFunctionService.compare(a.buffer, b.buffer);
? await cryptoFunctionService.compareFast(a, b)
: await cryptoFunctionService.compare(a, b);
expect(equal).toBe(false);
});
}

View File

@@ -11,34 +11,34 @@ import { CsprngArray } from "@bitwarden/common/types/csprng";
export class NodeCryptoFunctionService implements CryptoFunctionService {
pbkdf2(
password: string | ArrayBuffer,
salt: string | ArrayBuffer,
password: string | Uint8Array,
salt: string | Uint8Array,
algorithm: "sha256" | "sha512",
iterations: number
): Promise<ArrayBuffer> {
): Promise<Uint8Array> {
const len = algorithm === "sha256" ? 32 : 64;
const nodePassword = this.toNodeValue(password);
const nodeSalt = this.toNodeValue(salt);
return new Promise<ArrayBuffer>((resolve, reject) => {
return new Promise<Uint8Array>((resolve, reject) => {
crypto.pbkdf2(nodePassword, nodeSalt, iterations, len, algorithm, (error, key) => {
if (error != null) {
reject(error);
} else {
resolve(this.toArrayBuffer(key));
resolve(this.toUint8Buffer(key));
}
});
});
}
async argon2(
password: string | ArrayBuffer,
salt: string | ArrayBuffer,
password: string | Uint8Array,
salt: string | Uint8Array,
iterations: number,
memory: number,
parallelism: number
): Promise<ArrayBuffer> {
): Promise<Uint8Array> {
const nodePassword = this.toNodeValue(password);
const nodeSalt = this.toNodeBuffer(this.toArrayBuffer(salt));
const nodeSalt = this.toNodeBuffer(this.toUint8Buffer(salt));
const hash = await argon2.hash(nodePassword, {
salt: nodeSalt,
@@ -49,29 +49,29 @@ export class NodeCryptoFunctionService implements CryptoFunctionService {
parallelism: parallelism,
type: argon2.argon2id,
});
return this.toArrayBuffer(hash);
return this.toUint8Buffer(hash);
}
// ref: https://tools.ietf.org/html/rfc5869
async hkdf(
ikm: ArrayBuffer,
salt: string | ArrayBuffer,
info: string | ArrayBuffer,
ikm: Uint8Array,
salt: string | Uint8Array,
info: string | Uint8Array,
outputByteSize: number,
algorithm: "sha256" | "sha512"
): Promise<ArrayBuffer> {
const saltBuf = this.toArrayBuffer(salt);
): Promise<Uint8Array> {
const saltBuf = this.toUint8Buffer(salt);
const prk = await this.hmac(ikm, saltBuf, algorithm);
return this.hkdfExpand(prk, info, outputByteSize, algorithm);
}
// ref: https://tools.ietf.org/html/rfc5869
async hkdfExpand(
prk: ArrayBuffer,
info: string | ArrayBuffer,
prk: Uint8Array,
info: string | Uint8Array,
outputByteSize: number,
algorithm: "sha256" | "sha512"
): Promise<ArrayBuffer> {
): Promise<Uint8Array> {
const hashLen = algorithm === "sha256" ? 32 : 64;
if (outputByteSize > 255 * hashLen) {
throw new Error("outputByteSize is too large.");
@@ -80,7 +80,7 @@ export class NodeCryptoFunctionService implements CryptoFunctionService {
if (prkArr.length < hashLen) {
throw new Error("prk is too small.");
}
const infoBuf = this.toArrayBuffer(info);
const infoBuf = this.toUint8Buffer(info);
const infoArr = new Uint8Array(infoBuf);
let runningOkmLength = 0;
let previousT = new Uint8Array(0);
@@ -91,39 +91,39 @@ export class NodeCryptoFunctionService implements CryptoFunctionService {
t.set(previousT);
t.set(infoArr, previousT.length);
t.set([i + 1], t.length - 1);
previousT = new Uint8Array(await this.hmac(t.buffer, prk, algorithm));
previousT = await this.hmac(t, prk, algorithm);
okm.set(previousT, runningOkmLength);
runningOkmLength += previousT.length;
if (runningOkmLength >= outputByteSize) {
break;
}
}
return okm.slice(0, outputByteSize).buffer;
return okm.slice(0, outputByteSize);
}
hash(
value: string | ArrayBuffer,
value: string | Uint8Array,
algorithm: "sha1" | "sha256" | "sha512" | "md5"
): Promise<ArrayBuffer> {
): Promise<Uint8Array> {
const nodeValue = this.toNodeValue(value);
const hash = crypto.createHash(algorithm);
hash.update(nodeValue);
return Promise.resolve(this.toArrayBuffer(hash.digest()));
return Promise.resolve(this.toUint8Buffer(hash.digest()));
}
hmac(
value: ArrayBuffer,
key: ArrayBuffer,
value: Uint8Array,
key: Uint8Array,
algorithm: "sha1" | "sha256" | "sha512"
): Promise<ArrayBuffer> {
): Promise<Uint8Array> {
const nodeValue = this.toNodeBuffer(value);
const nodeKey = this.toNodeBuffer(key);
const hmac = crypto.createHmac(algorithm, nodeKey);
hmac.update(nodeValue);
return Promise.resolve(this.toArrayBuffer(hmac.digest()));
return Promise.resolve(this.toUint8Buffer(hmac.digest()));
}
async compare(a: ArrayBuffer, b: ArrayBuffer): Promise<boolean> {
async compare(a: Uint8Array, b: Uint8Array): Promise<boolean> {
const key = await this.randomBytes(32);
const mac1 = await this.hmac(a, key, "sha256");
const mac2 = await this.hmac(b, key, "sha256");
@@ -143,24 +143,24 @@ export class NodeCryptoFunctionService implements CryptoFunctionService {
}
hmacFast(
value: ArrayBuffer,
key: ArrayBuffer,
value: Uint8Array,
key: Uint8Array,
algorithm: "sha1" | "sha256" | "sha512"
): Promise<ArrayBuffer> {
): Promise<Uint8Array> {
return this.hmac(value, key, algorithm);
}
compareFast(a: ArrayBuffer, b: ArrayBuffer): Promise<boolean> {
compareFast(a: Uint8Array, b: Uint8Array): Promise<boolean> {
return this.compare(a, b);
}
aesEncrypt(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise<ArrayBuffer> {
aesEncrypt(data: Uint8Array, iv: Uint8Array, key: Uint8Array): Promise<Uint8Array> {
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));
return Promise.resolve(this.toUint8Buffer(encBuf));
}
aesDecryptFastParameters(
@@ -168,70 +168,70 @@ export class NodeCryptoFunctionService implements CryptoFunctionService {
iv: string,
mac: string,
key: SymmetricCryptoKey
): DecryptParameters<ArrayBuffer> {
const p = new DecryptParameters<ArrayBuffer>();
): DecryptParameters<Uint8Array> {
const p = new DecryptParameters<Uint8Array>();
p.encKey = key.encKey;
p.data = Utils.fromB64ToArray(data).buffer;
p.iv = Utils.fromB64ToArray(iv).buffer;
p.data = Utils.fromB64ToArray(data);
p.iv = Utils.fromB64ToArray(iv);
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;
p.macData = macData;
if (key.macKey != null) {
p.macKey = key.macKey;
}
if (mac != null) {
p.mac = Utils.fromB64ToArray(mac).buffer;
p.mac = Utils.fromB64ToArray(mac);
}
return p;
}
async aesDecryptFast(parameters: DecryptParameters<ArrayBuffer>): Promise<string> {
async aesDecryptFast(parameters: DecryptParameters<Uint8Array>): Promise<string> {
const decBuf = await this.aesDecrypt(parameters.data, parameters.iv, parameters.encKey);
return Utils.fromBufferToUtf8(decBuf);
}
aesDecrypt(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise<ArrayBuffer> {
aesDecrypt(data: Uint8Array, iv: Uint8Array, key: Uint8Array): Promise<Uint8Array> {
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));
return Promise.resolve(this.toUint8Buffer(decBuf));
}
rsaEncrypt(
data: ArrayBuffer,
publicKey: ArrayBuffer,
data: Uint8Array,
publicKey: Uint8Array,
algorithm: "sha1" | "sha256"
): Promise<ArrayBuffer> {
): Promise<Uint8Array> {
if (algorithm === "sha256") {
throw new Error("Node crypto does not support RSA-OAEP SHA-256");
}
const pem = this.toPemPublicKey(publicKey);
const decipher = crypto.publicEncrypt(pem, this.toNodeBuffer(data));
return Promise.resolve(this.toArrayBuffer(decipher));
return Promise.resolve(this.toUint8Buffer(decipher));
}
rsaDecrypt(
data: ArrayBuffer,
privateKey: ArrayBuffer,
data: Uint8Array,
privateKey: Uint8Array,
algorithm: "sha1" | "sha256"
): Promise<ArrayBuffer> {
): Promise<Uint8Array> {
if (algorithm === "sha256") {
throw new Error("Node crypto does not support RSA-OAEP SHA-256");
}
const pem = this.toPemPrivateKey(privateKey);
const decipher = crypto.privateDecrypt(pem, this.toNodeBuffer(data));
return Promise.resolve(this.toArrayBuffer(decipher));
return Promise.resolve(this.toUint8Buffer(decipher));
}
rsaExtractPublicKey(privateKey: ArrayBuffer): Promise<ArrayBuffer> {
rsaExtractPublicKey(privateKey: Uint8Array): Promise<Uint8Array> {
const privateKeyByteString = Utils.fromBufferToByteString(privateKey);
const privateKeyAsn1 = forge.asn1.fromDer(privateKeyByteString);
const forgePrivateKey: any = forge.pki.privateKeyFromAsn1(privateKeyAsn1);
@@ -239,11 +239,11 @@ export class NodeCryptoFunctionService implements CryptoFunctionService {
const publicKeyAsn1 = forge.pki.publicKeyToAsn1(forgePublicKey);
const publicKeyByteString = forge.asn1.toDer(publicKeyAsn1).data;
const publicKeyArray = Utils.fromByteStringToArray(publicKeyByteString);
return Promise.resolve(publicKeyArray.buffer);
return Promise.resolve(publicKeyArray);
}
async rsaGenerateKeyPair(length: 1024 | 2048 | 4096): Promise<[ArrayBuffer, ArrayBuffer]> {
return new Promise<[ArrayBuffer, ArrayBuffer]>((resolve, reject) => {
async rsaGenerateKeyPair(length: 1024 | 2048 | 4096): Promise<[Uint8Array, Uint8Array]> {
return new Promise<[Uint8Array, Uint8Array]>((resolve, reject) => {
forge.pki.rsa.generateKeyPair(
{
bits: length,
@@ -265,7 +265,7 @@ export class NodeCryptoFunctionService implements CryptoFunctionService {
const privateKeyByteString = forge.asn1.toDer(privateKeyPkcs8).getBytes();
const privateKey = Utils.fromByteStringToArray(privateKeyByteString);
resolve([publicKey.buffer, privateKey.buffer]);
resolve([publicKey, privateKey]);
}
);
});
@@ -277,13 +277,13 @@ export class NodeCryptoFunctionService implements CryptoFunctionService {
if (error != null) {
reject(error);
} else {
resolve(this.toArrayBuffer(bytes) as CsprngArray);
resolve(this.toUint8Buffer(bytes) as CsprngArray);
}
});
});
}
private toNodeValue(value: string | ArrayBuffer): string | Buffer {
private toNodeValue(value: string | Uint8Array): string | Buffer {
let nodeValue: string | Buffer;
if (typeof value === "string") {
nodeValue = value;
@@ -293,21 +293,21 @@ export class NodeCryptoFunctionService implements CryptoFunctionService {
return nodeValue;
}
private toNodeBuffer(value: ArrayBuffer): Buffer {
return Buffer.from(new Uint8Array(value) as any);
private toNodeBuffer(value: Uint8Array): Buffer {
return Buffer.from(value);
}
private toArrayBuffer(value: Buffer | string | ArrayBuffer): ArrayBuffer {
let buf: ArrayBuffer;
private toUint8Buffer(value: Buffer | string | Uint8Array): Uint8Array {
let buf: Uint8Array;
if (typeof value === "string") {
buf = Utils.fromUtf8ToArray(value).buffer;
buf = Utils.fromUtf8ToArray(value);
} else {
buf = new Uint8Array(value).buffer;
buf = value;
}
return buf;
}
private toPemPrivateKey(key: ArrayBuffer): string {
private toPemPrivateKey(key: Uint8Array): string {
const byteString = Utils.fromBufferToByteString(key);
const asn1 = forge.asn1.fromDer(byteString);
const privateKey = forge.pki.privateKeyFromAsn1(asn1);
@@ -316,7 +316,7 @@ export class NodeCryptoFunctionService implements CryptoFunctionService {
return forge.pki.privateKeyInfoToPem(privateKeyInfo);
}
private toPemPublicKey(key: ArrayBuffer): string {
private toPemPublicKey(key: Uint8Array): string {
const byteString = Utils.fromBufferToByteString(key);
const asn1 = forge.asn1.fromDer(byteString);
const publicKey = forge.pki.publicKeyFromAsn1(asn1);