2022-07-26 14:48:11 +02:00
|
|
|
import { mockReset, mock } from "jest-mock-extended";
|
2022-07-26 03:40:32 +02:00
|
|
|
|
|
|
|
import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service";
|
|
|
|
import { LogService } from "@bitwarden/common/abstractions/log.service";
|
[AC-1266] Enums filename conventions (#5140)
* refactor: update clientType enum
* refactor: update deviceType filename
* refactor: update encryptedExportType filename
* refactor: update encryptionType filename
* refactor: update eventType filename
* refactor: update fieldType filename
* refactor: update fileUploadType filename
* refactor: update hashPurpose filename
* refactor: update htmlStorageLocation filename
* refactor: update kdfType filename
* refactor: update keySuffixOptions filename
* refactor: update linkedIdType filename
* refactor: update logLevelType filename
* refactor: update nativeMessagingVersion filename
* refactor: update notificationType filename
* refactor: update productType filename
* refactor: update secureNoteType filename
* refactor: update stateVersion filename
* refactor: update storageLocation filename
* refactor: update themeType filename
* refactor: update uriMatchType filename
* fix: update kdfType classes missed in initial pass, refs AC-1266
* fix: missing import update for device-type
* refactor: add barrel file for enums and update pathed import statements, refs AC-1266
* fix: incorrect import statements for web, refs AC-1266
* fix: missed import statement updates (browser), refs AC-1266
* fix: missed import statement changes (cli), refs AC-1266
* fix: missed import statement changes (desktop), refs AC-1266
* fix: prettier, refs AC-1266
* refactor: (libs) update relative paths to use barrel file, refs AC-1266
* fix: missed find/replace import statements for SecureNoteType, refs AC-1266
* refactor: apply .enum suffix to enums folder and modify leftover relative paths, refs AC-1266
* fix: find/replace errors for native-messaging-version, refs AC-1266
2023-04-05 05:42:21 +02:00
|
|
|
import { EncryptionType } from "@bitwarden/common/enums";
|
2022-10-14 18:25:50 +02:00
|
|
|
import { EncArrayBuffer } from "@bitwarden/common/models/domain/enc-array-buffer";
|
|
|
|
import { EncString } from "@bitwarden/common/models/domain/enc-string";
|
|
|
|
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetric-crypto-key";
|
2022-10-27 23:38:54 +02:00
|
|
|
import { EncryptServiceImplementation } from "@bitwarden/common/services/cryptography/encrypt.service.implementation";
|
2022-07-26 03:40:32 +02:00
|
|
|
|
|
|
|
import { makeStaticByteArray } from "../utils";
|
|
|
|
|
|
|
|
describe("EncryptService", () => {
|
|
|
|
const cryptoFunctionService = mock<CryptoFunctionService>();
|
|
|
|
const logService = mock<LogService>();
|
|
|
|
|
2022-10-27 23:38:54 +02:00
|
|
|
let encryptService: EncryptServiceImplementation;
|
2022-07-26 03:40:32 +02:00
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
mockReset(cryptoFunctionService);
|
|
|
|
mockReset(logService);
|
|
|
|
|
2022-10-27 23:38:54 +02:00
|
|
|
encryptService = new EncryptServiceImplementation(cryptoFunctionService, logService, true);
|
2022-07-26 03:40:32 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
describe("encryptToBytes", () => {
|
|
|
|
const plainValue = makeStaticByteArray(16, 1);
|
|
|
|
const iv = makeStaticByteArray(16, 30);
|
|
|
|
const mac = makeStaticByteArray(32, 40);
|
|
|
|
const encryptedData = makeStaticByteArray(20, 50);
|
|
|
|
|
|
|
|
it("throws if no key is provided", () => {
|
|
|
|
return expect(encryptService.encryptToBytes(plainValue, null)).rejects.toThrow(
|
|
|
|
"No encryption key"
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("encrypts data", () => {
|
|
|
|
beforeEach(() => {
|
|
|
|
cryptoFunctionService.randomBytes.calledWith(16).mockResolvedValueOnce(iv.buffer);
|
|
|
|
cryptoFunctionService.aesEncrypt.mockResolvedValue(encryptedData.buffer);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("using a key which supports mac", async () => {
|
|
|
|
const key = mock<SymmetricCryptoKey>();
|
|
|
|
const encType = EncryptionType.AesCbc128_HmacSha256_B64;
|
|
|
|
key.encType = encType;
|
|
|
|
|
|
|
|
key.macKey = makeStaticByteArray(16, 20);
|
|
|
|
|
|
|
|
cryptoFunctionService.hmac.mockResolvedValue(mac.buffer);
|
|
|
|
|
|
|
|
const actual = await encryptService.encryptToBytes(plainValue, key);
|
|
|
|
|
|
|
|
expect(actual.encryptionType).toEqual(encType);
|
|
|
|
expect(actual.ivBytes).toEqualBuffer(iv);
|
|
|
|
expect(actual.macBytes).toEqualBuffer(mac);
|
|
|
|
expect(actual.dataBytes).toEqualBuffer(encryptedData);
|
|
|
|
expect(actual.buffer.byteLength).toEqual(
|
|
|
|
1 + iv.byteLength + mac.byteLength + encryptedData.byteLength
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("using a key which doesn't support mac", async () => {
|
|
|
|
const key = mock<SymmetricCryptoKey>();
|
|
|
|
const encType = EncryptionType.AesCbc256_B64;
|
|
|
|
key.encType = encType;
|
|
|
|
|
|
|
|
key.macKey = null;
|
|
|
|
|
|
|
|
const actual = await encryptService.encryptToBytes(plainValue, key);
|
|
|
|
|
|
|
|
expect(cryptoFunctionService.hmac).not.toBeCalled();
|
|
|
|
|
|
|
|
expect(actual.encryptionType).toEqual(encType);
|
|
|
|
expect(actual.ivBytes).toEqualBuffer(iv);
|
|
|
|
expect(actual.macBytes).toBeNull();
|
|
|
|
expect(actual.dataBytes).toEqualBuffer(encryptedData);
|
|
|
|
expect(actual.buffer.byteLength).toEqual(1 + iv.byteLength + encryptedData.byteLength);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe("decryptToBytes", () => {
|
|
|
|
const encType = EncryptionType.AesCbc256_HmacSha256_B64;
|
|
|
|
const key = new SymmetricCryptoKey(makeStaticByteArray(64, 100), encType);
|
|
|
|
const computedMac = new Uint8Array(1).buffer;
|
|
|
|
const encBuffer = new EncArrayBuffer(makeStaticByteArray(60, encType));
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
cryptoFunctionService.hmac.mockResolvedValue(computedMac);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("throws if no key is provided", () => {
|
|
|
|
return expect(encryptService.decryptToBytes(encBuffer, null)).rejects.toThrow(
|
|
|
|
"No encryption key"
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("throws if no encrypted value is provided", () => {
|
|
|
|
return expect(encryptService.decryptToBytes(null, key)).rejects.toThrow(
|
|
|
|
"Nothing provided for decryption"
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("decrypts data with provided key", async () => {
|
|
|
|
const decryptedBytes = makeStaticByteArray(10, 200).buffer;
|
|
|
|
|
|
|
|
cryptoFunctionService.hmac.mockResolvedValue(makeStaticByteArray(1).buffer);
|
|
|
|
cryptoFunctionService.compare.mockResolvedValue(true);
|
|
|
|
cryptoFunctionService.aesDecrypt.mockResolvedValueOnce(decryptedBytes);
|
|
|
|
|
|
|
|
const actual = await encryptService.decryptToBytes(encBuffer, key);
|
|
|
|
|
|
|
|
expect(cryptoFunctionService.aesDecrypt).toBeCalledWith(
|
|
|
|
expect.toEqualBuffer(encBuffer.dataBytes),
|
|
|
|
expect.toEqualBuffer(encBuffer.ivBytes),
|
|
|
|
expect.toEqualBuffer(key.encKey)
|
|
|
|
);
|
|
|
|
|
|
|
|
expect(actual).toEqualBuffer(decryptedBytes);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("compares macs using CryptoFunctionService", async () => {
|
|
|
|
const expectedMacData = new Uint8Array(
|
|
|
|
encBuffer.ivBytes.byteLength + encBuffer.dataBytes.byteLength
|
|
|
|
);
|
|
|
|
expectedMacData.set(new Uint8Array(encBuffer.ivBytes));
|
|
|
|
expectedMacData.set(new Uint8Array(encBuffer.dataBytes), encBuffer.ivBytes.byteLength);
|
|
|
|
|
|
|
|
await encryptService.decryptToBytes(encBuffer, key);
|
|
|
|
|
|
|
|
expect(cryptoFunctionService.hmac).toBeCalledWith(
|
|
|
|
expect.toEqualBuffer(expectedMacData),
|
|
|
|
key.macKey,
|
|
|
|
"sha256"
|
|
|
|
);
|
|
|
|
|
|
|
|
expect(cryptoFunctionService.compare).toBeCalledWith(
|
|
|
|
expect.toEqualBuffer(encBuffer.macBytes),
|
|
|
|
expect.toEqualBuffer(computedMac)
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("returns null if macs don't match", async () => {
|
|
|
|
cryptoFunctionService.compare.mockResolvedValue(false);
|
|
|
|
|
|
|
|
const actual = await encryptService.decryptToBytes(encBuffer, key);
|
|
|
|
expect(cryptoFunctionService.compare).toHaveBeenCalled();
|
|
|
|
expect(cryptoFunctionService.aesDecrypt).not.toHaveBeenCalled();
|
|
|
|
expect(actual).toBeNull();
|
|
|
|
});
|
|
|
|
|
|
|
|
it("returns null if encTypes don't match", async () => {
|
|
|
|
key.encType = EncryptionType.AesCbc256_B64;
|
|
|
|
cryptoFunctionService.compare.mockResolvedValue(true);
|
|
|
|
|
|
|
|
const actual = await encryptService.decryptToBytes(encBuffer, key);
|
|
|
|
|
|
|
|
expect(actual).toBeNull();
|
|
|
|
expect(cryptoFunctionService.aesDecrypt).not.toHaveBeenCalled();
|
|
|
|
});
|
|
|
|
});
|
2022-08-03 23:09:36 +02:00
|
|
|
|
|
|
|
describe("resolveLegacyKey", () => {
|
|
|
|
it("creates a legacy key if required", async () => {
|
|
|
|
const key = new SymmetricCryptoKey(makeStaticByteArray(32), EncryptionType.AesCbc256_B64);
|
|
|
|
const encString = mock<EncString>();
|
|
|
|
encString.encryptionType = EncryptionType.AesCbc128_HmacSha256_B64;
|
|
|
|
|
|
|
|
const actual = encryptService.resolveLegacyKey(key, encString);
|
|
|
|
|
|
|
|
const expected = new SymmetricCryptoKey(key.key, EncryptionType.AesCbc128_HmacSha256_B64);
|
|
|
|
expect(actual).toEqual(expected);
|
|
|
|
});
|
|
|
|
|
|
|
|
it("does not create a legacy key if not required", async () => {
|
|
|
|
const encType = EncryptionType.AesCbc256_HmacSha256_B64;
|
|
|
|
const key = new SymmetricCryptoKey(makeStaticByteArray(64), encType);
|
|
|
|
const encString = mock<EncString>();
|
|
|
|
encString.encryptionType = encType;
|
|
|
|
|
|
|
|
const actual = encryptService.resolveLegacyKey(key, encString);
|
|
|
|
|
|
|
|
expect(actual).toEqual(key);
|
|
|
|
});
|
|
|
|
});
|
2022-07-26 03:40:32 +02:00
|
|
|
});
|