1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-15 15:53:27 +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

@@ -302,13 +302,13 @@ export class AuthService implements AuthServiceAbstraction {
(
await this.cryptoService.getKey()
).encKey,
pubKey.buffer
pubKey
);
let encryptedMasterPassword = null;
if ((await this.stateService.getKeyHash()) != null) {
encryptedMasterPassword = await this.cryptoService.rsaEncrypt(
Utils.fromUtf8ToArray(await this.stateService.getKeyHash()),
pubKey.buffer
pubKey
);
}
const request = new PasswordlessAuthRequest(

View File

@@ -4,67 +4,67 @@ import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
export abstract class CryptoFunctionService {
pbkdf2: (
password: string | ArrayBuffer,
salt: string | ArrayBuffer,
password: string | Uint8Array,
salt: string | Uint8Array,
algorithm: "sha256" | "sha512",
iterations: number
) => Promise<ArrayBuffer>;
) => Promise<Uint8Array>;
argon2: (
password: string | ArrayBuffer,
salt: string | ArrayBuffer,
password: string | Uint8Array,
salt: string | Uint8Array,
iterations: number,
memory: number,
parallelism: number
) => Promise<ArrayBuffer>;
) => Promise<Uint8Array>;
hkdf: (
ikm: ArrayBuffer,
salt: string | ArrayBuffer,
info: string | ArrayBuffer,
ikm: Uint8Array,
salt: string | Uint8Array,
info: string | Uint8Array,
outputByteSize: number,
algorithm: "sha256" | "sha512"
) => Promise<ArrayBuffer>;
) => Promise<Uint8Array>;
hkdfExpand: (
prk: ArrayBuffer,
info: string | ArrayBuffer,
prk: Uint8Array,
info: string | Uint8Array,
outputByteSize: number,
algorithm: "sha256" | "sha512"
) => Promise<ArrayBuffer>;
) => Promise<Uint8Array>;
hash: (
value: string | ArrayBuffer,
value: string | Uint8Array,
algorithm: "sha1" | "sha256" | "sha512" | "md5"
) => Promise<ArrayBuffer>;
) => Promise<Uint8Array>;
hmac: (
value: ArrayBuffer,
key: ArrayBuffer,
value: Uint8Array,
key: Uint8Array,
algorithm: "sha1" | "sha256" | "sha512"
) => Promise<ArrayBuffer>;
compare: (a: ArrayBuffer, b: ArrayBuffer) => Promise<boolean>;
) => Promise<Uint8Array>;
compare: (a: Uint8Array, b: Uint8Array) => Promise<boolean>;
hmacFast: (
value: ArrayBuffer | string,
key: ArrayBuffer | string,
value: Uint8Array | string,
key: Uint8Array | string,
algorithm: "sha1" | "sha256" | "sha512"
) => Promise<ArrayBuffer | string>;
compareFast: (a: ArrayBuffer | string, b: ArrayBuffer | string) => Promise<boolean>;
aesEncrypt: (data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer) => Promise<ArrayBuffer>;
) => Promise<Uint8Array | string>;
compareFast: (a: Uint8Array | string, b: Uint8Array | string) => Promise<boolean>;
aesEncrypt: (data: Uint8Array, iv: Uint8Array, key: Uint8Array) => Promise<Uint8Array>;
aesDecryptFastParameters: (
data: string,
iv: string,
mac: string,
key: SymmetricCryptoKey
) => DecryptParameters<ArrayBuffer | string>;
aesDecryptFast: (parameters: DecryptParameters<ArrayBuffer | string>) => Promise<string>;
aesDecrypt: (data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer) => Promise<ArrayBuffer>;
) => DecryptParameters<Uint8Array | string>;
aesDecryptFast: (parameters: DecryptParameters<Uint8Array | string>) => Promise<string>;
aesDecrypt: (data: Uint8Array, iv: Uint8Array, key: Uint8Array) => Promise<Uint8Array>;
rsaEncrypt: (
data: ArrayBuffer,
publicKey: ArrayBuffer,
data: Uint8Array,
publicKey: Uint8Array,
algorithm: "sha1" | "sha256"
) => Promise<ArrayBuffer>;
) => Promise<Uint8Array>;
rsaDecrypt: (
data: ArrayBuffer,
privateKey: ArrayBuffer,
data: Uint8Array,
privateKey: Uint8Array,
algorithm: "sha1" | "sha256"
) => Promise<ArrayBuffer>;
rsaExtractPublicKey: (privateKey: ArrayBuffer) => Promise<ArrayBuffer>;
rsaGenerateKeyPair: (length: 1024 | 2048 | 4096) => Promise<[ArrayBuffer, ArrayBuffer]>;
) => Promise<Uint8Array>;
rsaExtractPublicKey: (privateKey: Uint8Array) => Promise<Uint8Array>;
rsaGenerateKeyPair: (length: 1024 | 2048 | 4096) => Promise<[Uint8Array, Uint8Array]>;
randomBytes: (length: number) => Promise<CsprngArray>;
}

View File

@@ -22,9 +22,9 @@ export abstract class CryptoService {
getKeyHash: () => Promise<string>;
compareAndUpdateKeyHash: (masterPassword: string, key: SymmetricCryptoKey) => Promise<boolean>;
getEncKey: (key?: SymmetricCryptoKey) => Promise<SymmetricCryptoKey>;
getPublicKey: () => Promise<ArrayBuffer>;
getPrivateKey: () => Promise<ArrayBuffer>;
getFingerprint: (fingerprintMaterial: string, publicKey?: ArrayBuffer) => Promise<string[]>;
getPublicKey: () => Promise<Uint8Array>;
getPrivateKey: () => Promise<Uint8Array>;
getFingerprint: (fingerprintMaterial: string, publicKey?: Uint8Array) => Promise<string[]>;
getOrgKeys: () => Promise<Map<string, SymmetricCryptoKey>>;
getOrgKey: (orgId: string) => Promise<SymmetricCryptoKey>;
getProviderKey: (providerId: string) => Promise<SymmetricCryptoKey>;
@@ -63,7 +63,7 @@ export abstract class CryptoService {
kdf: KdfType,
kdfConfig: KdfConfig
) => Promise<SymmetricCryptoKey>;
makeSendKey: (keyMaterial: ArrayBuffer) => Promise<SymmetricCryptoKey>;
makeSendKey: (keyMaterial: Uint8Array) => Promise<SymmetricCryptoKey>;
hashPassword: (
password: string,
key: SymmetricCryptoKey,
@@ -74,13 +74,13 @@ export abstract class CryptoService {
key: SymmetricCryptoKey,
encKey?: SymmetricCryptoKey
) => Promise<[SymmetricCryptoKey, EncString]>;
encrypt: (plainValue: string | ArrayBuffer, key?: SymmetricCryptoKey) => Promise<EncString>;
encryptToBytes: (plainValue: ArrayBuffer, key?: SymmetricCryptoKey) => Promise<EncArrayBuffer>;
rsaEncrypt: (data: ArrayBuffer, publicKey?: ArrayBuffer) => Promise<EncString>;
rsaDecrypt: (encValue: string, privateKeyValue?: ArrayBuffer) => Promise<ArrayBuffer>;
decryptToBytes: (encString: EncString, key?: SymmetricCryptoKey) => Promise<ArrayBuffer>;
encrypt: (plainValue: string | Uint8Array, key?: SymmetricCryptoKey) => Promise<EncString>;
encryptToBytes: (plainValue: Uint8Array, key?: SymmetricCryptoKey) => Promise<EncArrayBuffer>;
rsaEncrypt: (data: Uint8Array, publicKey?: Uint8Array) => Promise<EncString>;
rsaDecrypt: (encValue: string, privateKeyValue?: Uint8Array) => Promise<Uint8Array>;
decryptToBytes: (encString: EncString, key?: SymmetricCryptoKey) => Promise<Uint8Array>;
decryptToUtf8: (encString: EncString, key?: SymmetricCryptoKey) => Promise<string>;
decryptFromBytes: (encBuffer: EncArrayBuffer, key: SymmetricCryptoKey) => Promise<ArrayBuffer>;
decryptFromBytes: (encBuffer: EncArrayBuffer, key: SymmetricCryptoKey) => Promise<Uint8Array>;
randomNumber: (min: number, max: number) => Promise<number>;
validateKey: (key: SymmetricCryptoKey) => Promise<boolean>;
}

View File

@@ -6,13 +6,13 @@ import { EncString } from "../models/domain/enc-string";
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
export abstract class EncryptService {
abstract encrypt(plainValue: string | ArrayBuffer, key: SymmetricCryptoKey): Promise<EncString>;
abstract encrypt(plainValue: string | Uint8Array, key: SymmetricCryptoKey): Promise<EncString>;
abstract encryptToBytes: (
plainValue: ArrayBuffer,
plainValue: Uint8Array,
key?: SymmetricCryptoKey
) => Promise<EncArrayBuffer>;
abstract decryptToUtf8: (encString: EncString, key: SymmetricCryptoKey) => Promise<string>;
abstract decryptToBytes: (encThing: Encrypted, key: SymmetricCryptoKey) => Promise<ArrayBuffer>;
abstract decryptToBytes: (encThing: Encrypted, key: SymmetricCryptoKey) => Promise<Uint8Array>;
abstract resolveLegacyKey: (key: SymmetricCryptoKey, encThing: Encrypted) => SymmetricCryptoKey;
abstract decryptItems: <T extends InitializerMetadata>(
items: Decryptable<T>[],

View File

@@ -113,8 +113,8 @@ export abstract class StateService<T extends Account = Account> {
* @deprecated Do not call this, use PolicyService
*/
setDecryptedPolicies: (value: Policy[], options?: StorageOptions) => Promise<void>;
getDecryptedPrivateKey: (options?: StorageOptions) => Promise<ArrayBuffer>;
setDecryptedPrivateKey: (value: ArrayBuffer, options?: StorageOptions) => Promise<void>;
getDecryptedPrivateKey: (options?: StorageOptions) => Promise<Uint8Array>;
setDecryptedPrivateKey: (value: Uint8Array, options?: StorageOptions) => Promise<void>;
getDecryptedProviderKeys: (options?: StorageOptions) => Promise<Map<string, SymmetricCryptoKey>>;
setDecryptedProviderKeys: (
value: Map<string, SymmetricCryptoKey>,
@@ -331,8 +331,8 @@ export abstract class StateService<T extends Account = Account> {
setProtectedPin: (value: string, options?: StorageOptions) => Promise<void>;
getProviders: (options?: StorageOptions) => Promise<{ [id: string]: ProviderData }>;
setProviders: (value: { [id: string]: ProviderData }, options?: StorageOptions) => Promise<void>;
getPublicKey: (options?: StorageOptions) => Promise<ArrayBuffer>;
setPublicKey: (value: ArrayBuffer, options?: StorageOptions) => Promise<void>;
getPublicKey: (options?: StorageOptions) => Promise<Uint8Array>;
setPublicKey: (value: Uint8Array, options?: StorageOptions) => Promise<void>;
getRefreshToken: (options?: StorageOptions) => Promise<string>;
setRefreshToken: (value: string, options?: StorageOptions) => Promise<void>;
getRememberedEmail: (options?: StorageOptions) => Promise<string>;

View File

@@ -2,7 +2,7 @@ import { EncryptionType } from "../../enums";
export interface Encrypted {
encryptionType?: EncryptionType;
dataBytes: ArrayBuffer;
macBytes: ArrayBuffer;
ivBytes: ArrayBuffer;
dataBytes: Uint8Array;
macBytes: Uint8Array;
ivBytes: Uint8Array;
}

View File

@@ -8,7 +8,7 @@ describe("AccountKeys", () => {
describe("toJSON", () => {
it("should serialize itself", () => {
const keys = new AccountKeys();
const buffer = makeStaticByteArray(64).buffer;
const buffer = makeStaticByteArray(64);
keys.publicKey = buffer;
const bufferSpy = jest.spyOn(Utils, "fromBufferToByteString");
@@ -18,7 +18,7 @@ describe("AccountKeys", () => {
it("should serialize public key as a string", () => {
const keys = new AccountKeys();
keys.publicKey = Utils.fromByteStringToArray("hello").buffer;
keys.publicKey = Utils.fromByteStringToArray("hello");
const json = JSON.stringify(keys);
expect(json).toContain('"publicKey":"hello"');
});
@@ -29,7 +29,7 @@ describe("AccountKeys", () => {
const keys = AccountKeys.fromJSON({
publicKey: "hello",
});
expect(keys.publicKey).toEqual(Utils.fromByteStringToArray("hello").buffer);
expect(keys.publicKey).toEqual(Utils.fromByteStringToArray("hello"));
});
it("should deserialize cryptoMasterKey", () => {

View File

@@ -119,8 +119,8 @@ export class AccountKeys {
any,
Record<string, SymmetricCryptoKey>
>();
privateKey?: EncryptionPair<string, ArrayBuffer> = new EncryptionPair<string, ArrayBuffer>();
publicKey?: ArrayBuffer;
privateKey?: EncryptionPair<string, Uint8Array> = new EncryptionPair<string, Uint8Array>();
publicKey?: Uint8Array;
apiKeyClientSecret?: string;
toJSON() {
@@ -142,11 +142,10 @@ export class AccountKeys {
),
organizationKeys: AccountKeys.initRecordEncryptionPairsFromJSON(obj?.organizationKeys),
providerKeys: AccountKeys.initRecordEncryptionPairsFromJSON(obj?.providerKeys),
privateKey: EncryptionPair.fromJSON<string, ArrayBuffer>(
obj?.privateKey,
(decObj: string) => Utils.fromByteStringToArray(decObj).buffer
privateKey: EncryptionPair.fromJSON<string, Uint8Array>(obj?.privateKey, (decObj: string) =>
Utils.fromByteStringToArray(decObj)
),
publicKey: Utils.fromByteStringToArray(obj?.publicKey)?.buffer,
publicKey: Utils.fromByteStringToArray(obj?.publicKey),
});
}

View File

@@ -20,7 +20,7 @@ describe("encArrayBuffer", () => {
array.set(mac, 1 + iv.byteLength);
array.set(data, 1 + iv.byteLength + mac.byteLength);
const actual = new EncArrayBuffer(array.buffer);
const actual = new EncArrayBuffer(array);
expect(actual.encryptionType).toEqual(encType);
expect(actual.ivBytes).toEqualBuffer(iv);
@@ -39,11 +39,11 @@ describe("encArrayBuffer", () => {
array.set(iv, 1);
array.set(data, 1 + iv.byteLength);
const actual = new EncArrayBuffer(array.buffer);
const actual = new EncArrayBuffer(array);
expect(actual.encryptionType).toEqual(encType);
expect(actual.ivBytes).toEqualBuffer(iv);
expect(actual.dataBytes).toEqualBuffer(data);
expect(actual.ivBytes).toEqual(iv);
expect(actual.dataBytes).toEqual(data);
expect(actual.macBytes).toBeNull();
});
});
@@ -58,13 +58,11 @@ describe("encArrayBuffer", () => {
// Minus 1 to leave room for the encType, minus 1 to make it invalid
const invalidBytes = makeStaticByteArray(minLength - 2);
const invalidArray = new Uint8Array(1 + invalidBytes.buffer.byteLength);
const invalidArray = new Uint8Array(1 + invalidBytes.byteLength);
invalidArray.set([encType]);
invalidArray.set(invalidBytes, 1);
expect(() => new EncArrayBuffer(invalidArray.buffer)).toThrow(
"Error parsing encrypted ArrayBuffer"
);
expect(() => new EncArrayBuffer(invalidArray)).toThrow("Error parsing encrypted ArrayBuffer");
});
});

View File

@@ -9,12 +9,12 @@ const MIN_DATA_LENGTH = 1;
export class EncArrayBuffer implements Encrypted {
readonly encryptionType: EncryptionType = null;
readonly dataBytes: ArrayBuffer = null;
readonly ivBytes: ArrayBuffer = null;
readonly macBytes: ArrayBuffer = null;
readonly dataBytes: Uint8Array = null;
readonly ivBytes: Uint8Array = null;
readonly macBytes: Uint8Array = null;
constructor(readonly buffer: ArrayBuffer) {
const encBytes = new Uint8Array(buffer);
constructor(readonly buffer: Uint8Array) {
const encBytes = buffer;
const encType = encBytes[0];
switch (encType) {
@@ -25,12 +25,12 @@ export class EncArrayBuffer implements Encrypted {
this.throwDecryptionError();
}
this.ivBytes = encBytes.slice(ENC_TYPE_LENGTH, ENC_TYPE_LENGTH + IV_LENGTH).buffer;
this.ivBytes = encBytes.slice(ENC_TYPE_LENGTH, ENC_TYPE_LENGTH + IV_LENGTH);
this.macBytes = encBytes.slice(
ENC_TYPE_LENGTH + IV_LENGTH,
ENC_TYPE_LENGTH + IV_LENGTH + MAC_LENGTH
).buffer;
this.dataBytes = encBytes.slice(ENC_TYPE_LENGTH + IV_LENGTH + MAC_LENGTH).buffer;
);
this.dataBytes = encBytes.slice(ENC_TYPE_LENGTH + IV_LENGTH + MAC_LENGTH);
break;
}
case EncryptionType.AesCbc256_B64: {
@@ -39,8 +39,8 @@ export class EncArrayBuffer implements Encrypted {
this.throwDecryptionError();
}
this.ivBytes = encBytes.slice(ENC_TYPE_LENGTH, ENC_TYPE_LENGTH + IV_LENGTH).buffer;
this.dataBytes = encBytes.slice(ENC_TYPE_LENGTH + IV_LENGTH).buffer;
this.ivBytes = encBytes.slice(ENC_TYPE_LENGTH, ENC_TYPE_LENGTH + IV_LENGTH);
this.dataBytes = encBytes.slice(ENC_TYPE_LENGTH + IV_LENGTH);
break;
}
default:
@@ -63,11 +63,11 @@ export class EncArrayBuffer implements Encrypted {
if (buffer == null) {
throw new Error("Cannot create EncArrayBuffer from Response - Response is empty");
}
return new EncArrayBuffer(buffer);
return new EncArrayBuffer(new Uint8Array(buffer));
}
static fromB64(b64: string) {
const buffer = Utils.fromB64ToArray(b64).buffer;
const buffer = Utils.fromB64ToArray(b64);
return new EncArrayBuffer(buffer);
}
}

View File

@@ -27,16 +27,16 @@ export class EncString implements Encrypted {
}
}
get ivBytes(): ArrayBuffer {
return this.iv == null ? null : Utils.fromB64ToArray(this.iv).buffer;
get ivBytes(): Uint8Array {
return this.iv == null ? null : Utils.fromB64ToArray(this.iv);
}
get macBytes(): ArrayBuffer {
return this.mac == null ? null : Utils.fromB64ToArray(this.mac).buffer;
get macBytes(): Uint8Array {
return this.mac == null ? null : Utils.fromB64ToArray(this.mac);
}
get dataBytes(): ArrayBuffer {
return this.data == null ? null : Utils.fromB64ToArray(this.data).buffer;
get dataBytes(): Uint8Array {
return this.data == null ? null : Utils.fromB64ToArray(this.data);
}
toJSON() {

View File

@@ -1,8 +1,8 @@
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
export class EncryptedObject {
iv: ArrayBuffer;
data: ArrayBuffer;
mac: ArrayBuffer;
iv: Uint8Array;
data: Uint8Array;
mac: Uint8Array;
key: SymmetricCryptoKey;
}

View File

@@ -11,6 +11,13 @@ describe("EncryptionPair", () => {
expect(json.decrypted).toEqual("hello");
});
it("should populate decryptedSerialized for TypesArrays", () => {
const pair = new EncryptionPair<string, Uint8Array>();
pair.decrypted = Utils.fromByteStringToArray("hello");
const json = pair.toJSON();
expect(json.decrypted).toEqual(new Uint8Array([104, 101, 108, 108, 111]));
});
it("should serialize encrypted and decrypted", () => {
const pair = new EncryptionPair<string, string>();
pair.encrypted = "hello";

View File

@@ -68,7 +68,7 @@ describe("SymmetricCryptoKey", () => {
});
it("toJSON creates object for serialization", () => {
const key = new SymmetricCryptoKey(makeStaticByteArray(64).buffer);
const key = new SymmetricCryptoKey(makeStaticByteArray(64));
const actual = key.toJSON();
const expected = { keyB64: key.keyB64 };
@@ -77,7 +77,7 @@ describe("SymmetricCryptoKey", () => {
});
it("fromJSON hydrates new object", () => {
const expected = new SymmetricCryptoKey(makeStaticByteArray(64).buffer);
const expected = new SymmetricCryptoKey(makeStaticByteArray(64));
const actual = SymmetricCryptoKey.fromJSON({ keyB64: expected.keyB64 });
expect(actual).toEqual(expected);

View File

@@ -4,9 +4,9 @@ import { EncryptionType } from "../../../enums";
import { Utils } from "../../../platform/misc/utils";
export class SymmetricCryptoKey {
key: ArrayBuffer;
encKey?: ArrayBuffer;
macKey?: ArrayBuffer;
key: Uint8Array;
encKey?: Uint8Array;
macKey?: Uint8Array;
encType: EncryptionType;
keyB64: string;
@@ -15,7 +15,7 @@ export class SymmetricCryptoKey {
meta: any;
constructor(key: ArrayBuffer, encType?: EncryptionType) {
constructor(key: Uint8Array, encType?: EncryptionType) {
if (key == null) {
throw new Error("Must provide key");
}
@@ -67,7 +67,7 @@ export class SymmetricCryptoKey {
return null;
}
const arrayBuffer = Utils.fromB64ToArray(s).buffer;
const arrayBuffer = Utils.fromB64ToArray(s);
return new SymmetricCryptoKey(arrayBuffer);
}

View File

@@ -123,7 +123,7 @@ export class CryptoService implements CryptoServiceAbstraction {
): Promise<SymmetricCryptoKey> {
const key = await this.retrieveKeyFromStorage(keySuffix, userId);
if (key != null) {
const symmetricKey = new SymmetricCryptoKey(Utils.fromB64ToArray(key).buffer);
const symmetricKey = new SymmetricCryptoKey(Utils.fromB64ToArray(key));
if (!(await this.validateKey(symmetricKey))) {
this.logService.warning("Wrong key, throwing away stored key");
@@ -172,7 +172,7 @@ export class CryptoService implements CryptoServiceAbstraction {
return this.getEncKeyHelper(key);
}
async getPublicKey(): Promise<ArrayBuffer> {
async getPublicKey(): Promise<Uint8Array> {
const inMemoryPublicKey = await this.stateService.getPublicKey();
if (inMemoryPublicKey != null) {
return inMemoryPublicKey;
@@ -188,7 +188,7 @@ export class CryptoService implements CryptoServiceAbstraction {
return publicKey;
}
async getPrivateKey(): Promise<ArrayBuffer> {
async getPrivateKey(): Promise<Uint8Array> {
const decryptedPrivateKey = await this.stateService.getDecryptedPrivateKey();
if (decryptedPrivateKey != null) {
return decryptedPrivateKey;
@@ -204,7 +204,7 @@ export class CryptoService implements CryptoServiceAbstraction {
return privateKey;
}
async getFingerprint(fingerprintMaterial: string, publicKey?: ArrayBuffer): Promise<string[]> {
async getFingerprint(fingerprintMaterial: string, publicKey?: Uint8Array): Promise<string[]> {
if (publicKey == null) {
publicKey = await this.getPublicKey();
}
@@ -416,7 +416,7 @@ export class CryptoService implements CryptoServiceAbstraction {
kdf: KdfType,
kdfConfig: KdfConfig
): Promise<SymmetricCryptoKey> {
let key: ArrayBuffer = null;
let key: Uint8Array = null;
if (kdf == null || kdf === KdfType.PBKDF2_SHA256) {
if (kdfConfig.iterations == null) {
kdfConfig.iterations = 5000;
@@ -502,7 +502,7 @@ export class CryptoService implements CryptoServiceAbstraction {
return await this.stretchKey(pinKey);
}
async makeSendKey(keyMaterial: ArrayBuffer): Promise<SymmetricCryptoKey> {
async makeSendKey(keyMaterial: Uint8Array): Promise<SymmetricCryptoKey> {
const sendKey = await this.cryptoFunctionService.hkdf(
keyMaterial,
"bitwarden-send",
@@ -550,7 +550,7 @@ export class CryptoService implements CryptoServiceAbstraction {
* @deprecated July 25 2022: Get the key you need from CryptoService (getKeyForUserEncryption or getOrgKey)
* and then call encryptService.encrypt
*/
async encrypt(plainValue: string | ArrayBuffer, key?: SymmetricCryptoKey): Promise<EncString> {
async encrypt(plainValue: string | Uint8Array, key?: SymmetricCryptoKey): Promise<EncString> {
key = await this.getKeyForUserEncryption(key);
return await this.encryptService.encrypt(plainValue, key);
}
@@ -559,12 +559,12 @@ export class CryptoService implements CryptoServiceAbstraction {
* @deprecated July 25 2022: Get the key you need from CryptoService (getKeyForUserEncryption or getOrgKey)
* and then call encryptService.encryptToBytes
*/
async encryptToBytes(plainValue: ArrayBuffer, key?: SymmetricCryptoKey): Promise<EncArrayBuffer> {
async encryptToBytes(plainValue: Uint8Array, key?: SymmetricCryptoKey): Promise<EncArrayBuffer> {
key = await this.getKeyForUserEncryption(key);
return this.encryptService.encryptToBytes(plainValue, key);
}
async rsaEncrypt(data: ArrayBuffer, publicKey?: ArrayBuffer): Promise<EncString> {
async rsaEncrypt(data: Uint8Array, publicKey?: Uint8Array): Promise<EncString> {
if (publicKey == null) {
publicKey = await this.getPublicKey();
}
@@ -576,7 +576,7 @@ export class CryptoService implements CryptoServiceAbstraction {
return new EncString(EncryptionType.Rsa2048_OaepSha1_B64, Utils.fromBufferToB64(encBytes));
}
async rsaDecrypt(encValue: string, privateKeyValue?: ArrayBuffer): Promise<ArrayBuffer> {
async rsaDecrypt(encValue: string, privateKeyValue?: Uint8Array): Promise<Uint8Array> {
const headerPieces = encValue.split(".");
let encType: EncryptionType = null;
let encPieces: string[];
@@ -607,7 +607,7 @@ export class CryptoService implements CryptoServiceAbstraction {
throw new Error("encPieces unavailable.");
}
const data = Utils.fromB64ToArray(encPieces[0]).buffer;
const data = Utils.fromB64ToArray(encPieces[0]);
const privateKey = privateKeyValue ?? (await this.getPrivateKey());
if (privateKey == null) {
throw new Error("No private key.");
@@ -633,7 +633,7 @@ export class CryptoService implements CryptoServiceAbstraction {
* @deprecated July 25 2022: Get the key you need from CryptoService (getKeyForUserEncryption or getOrgKey)
* and then call encryptService.decryptToBytes
*/
async decryptToBytes(encString: EncString, key?: SymmetricCryptoKey): Promise<ArrayBuffer> {
async decryptToBytes(encString: EncString, key?: SymmetricCryptoKey): Promise<Uint8Array> {
const keyForEnc = await this.getKeyForUserEncryption(key);
return this.encryptService.decryptToBytes(encString, keyForEnc);
}
@@ -651,7 +651,7 @@ export class CryptoService implements CryptoServiceAbstraction {
* @deprecated July 25 2022: Get the key you need from CryptoService (getKeyForUserEncryption or getOrgKey)
* and then call encryptService.decryptToBytes
*/
async decryptFromBytes(encBuffer: EncArrayBuffer, key: SymmetricCryptoKey): Promise<ArrayBuffer> {
async decryptFromBytes(encBuffer: EncArrayBuffer, key: SymmetricCryptoKey): Promise<Uint8Array> {
if (encBuffer == null) {
throw new Error("No buffer provided for decryption.");
}
@@ -768,10 +768,10 @@ export class CryptoService implements CryptoServiceAbstraction {
const macKey = await this.cryptoFunctionService.hkdfExpand(key.key, "mac", 32, "sha256");
newKey.set(new Uint8Array(encKey));
newKey.set(new Uint8Array(macKey), 32);
return new SymmetricCryptoKey(newKey.buffer);
return new SymmetricCryptoKey(newKey);
}
private async hashPhrase(hash: ArrayBuffer, minimumEntropy = 64) {
private async hashPhrase(hash: Uint8Array, minimumEntropy = 64) {
const entropyPerWord = Math.log(EFFLongWordList.length) / Math.log(2);
let numWords = Math.ceil(minimumEntropy / entropyPerWord);
@@ -793,7 +793,7 @@ export class CryptoService implements CryptoServiceAbstraction {
private async buildEncKey(
key: SymmetricCryptoKey,
encKey: ArrayBuffer
encKey: Uint8Array
): Promise<[SymmetricCryptoKey, EncString]> {
let encKeyEnc: EncString = null;
if (key.key.byteLength === 32) {
@@ -830,7 +830,7 @@ export class CryptoService implements CryptoServiceAbstraction {
return null;
}
let decEncKey: ArrayBuffer;
let decEncKey: Uint8Array;
const encKeyCipher = new EncString(encKey);
if (encKeyCipher.encryptionType === EncryptionType.AesCbc256_B64) {
decEncKey = await this.decryptToBytes(encKeyCipher, key);

View File

@@ -18,7 +18,7 @@ export class EncryptServiceImplementation implements EncryptService {
protected logMacFailures: boolean
) {}
async encrypt(plainValue: string | ArrayBuffer, key: SymmetricCryptoKey): Promise<EncString> {
async encrypt(plainValue: string | Uint8Array, key: SymmetricCryptoKey): Promise<EncString> {
if (key == null) {
throw new Error("No encryption key provided.");
}
@@ -27,9 +27,9 @@ export class EncryptServiceImplementation implements EncryptService {
return Promise.resolve(null);
}
let plainBuf: ArrayBuffer;
let plainBuf: Uint8Array;
if (typeof plainValue === "string") {
plainBuf = Utils.fromUtf8ToArray(plainValue).buffer;
plainBuf = Utils.fromUtf8ToArray(plainValue);
} else {
plainBuf = plainValue;
}
@@ -41,7 +41,7 @@ export class EncryptServiceImplementation implements EncryptService {
return new EncString(encObj.key.encType, data, iv, mac);
}
async encryptToBytes(plainValue: ArrayBuffer, key: SymmetricCryptoKey): Promise<EncArrayBuffer> {
async encryptToBytes(plainValue: Uint8Array, key: SymmetricCryptoKey): Promise<EncArrayBuffer> {
if (key == null) {
throw new Error("No encryption key provided.");
}
@@ -60,7 +60,7 @@ export class EncryptServiceImplementation implements EncryptService {
}
encBytes.set(new Uint8Array(encValue.data), 1 + encValue.iv.byteLength + macLen);
return new EncArrayBuffer(encBytes.buffer);
return new EncArrayBuffer(encBytes);
}
async decryptToUtf8(encString: EncString, key: SymmetricCryptoKey): Promise<string> {
@@ -102,7 +102,7 @@ export class EncryptServiceImplementation implements EncryptService {
return await this.cryptoFunctionService.aesDecryptFast(fastParams);
}
async decryptToBytes(encThing: Encrypted, key: SymmetricCryptoKey): Promise<ArrayBuffer> {
async decryptToBytes(encThing: Encrypted, key: SymmetricCryptoKey): Promise<Uint8Array> {
if (key == null) {
throw new Error("No encryption key provided.");
}
@@ -125,11 +125,7 @@ export class EncryptServiceImplementation implements EncryptService {
const macData = new Uint8Array(encThing.ivBytes.byteLength + encThing.dataBytes.byteLength);
macData.set(new Uint8Array(encThing.ivBytes), 0);
macData.set(new Uint8Array(encThing.dataBytes), encThing.ivBytes.byteLength);
const computedMac = await this.cryptoFunctionService.hmac(
macData.buffer,
key.macKey,
"sha256"
);
const computedMac = await this.cryptoFunctionService.hmac(macData, key.macKey, "sha256");
if (computedMac === null) {
return null;
}
@@ -161,7 +157,7 @@ export class EncryptServiceImplementation implements EncryptService {
return await Promise.all(items.map((item) => item.decrypt(key)));
}
private async aesEncrypt(data: ArrayBuffer, key: SymmetricCryptoKey): Promise<EncryptedObject> {
private async aesEncrypt(data: Uint8Array, key: SymmetricCryptoKey): Promise<EncryptedObject> {
const obj = new EncryptedObject();
obj.key = key;
obj.iv = await this.cryptoFunctionService.randomBytes(16);
@@ -171,7 +167,7 @@ export class EncryptServiceImplementation implements EncryptService {
const macData = new Uint8Array(obj.iv.byteLength + obj.data.byteLength);
macData.set(new Uint8Array(obj.iv), 0);
macData.set(new Uint8Array(obj.data), obj.iv.byteLength);
obj.mac = await this.cryptoFunctionService.hmac(macData.buffer, obj.key.macKey, "sha256");
obj.mac = await this.cryptoFunctionService.hmac(macData, obj.key.macKey, "sha256");
}
return obj;

View File

@@ -37,10 +37,8 @@ describe("EncryptService", () => {
describe("encrypts data", () => {
beforeEach(() => {
cryptoFunctionService.randomBytes
.calledWith(16)
.mockResolvedValueOnce(iv.buffer as CsprngArray);
cryptoFunctionService.aesEncrypt.mockResolvedValue(encryptedData.buffer);
cryptoFunctionService.randomBytes.calledWith(16).mockResolvedValueOnce(iv as CsprngArray);
cryptoFunctionService.aesEncrypt.mockResolvedValue(encryptedData);
});
it("using a key which supports mac", async () => {
@@ -50,7 +48,7 @@ describe("EncryptService", () => {
key.macKey = makeStaticByteArray(16, 20);
cryptoFunctionService.hmac.mockResolvedValue(mac.buffer);
cryptoFunctionService.hmac.mockResolvedValue(mac);
const actual = await encryptService.encryptToBytes(plainValue, key);
@@ -86,7 +84,7 @@ describe("EncryptService", () => {
describe("decryptToBytes", () => {
const encType = EncryptionType.AesCbc256_HmacSha256_B64;
const key = new SymmetricCryptoKey(makeStaticByteArray(64, 100), encType);
const computedMac = new Uint8Array(1).buffer;
const computedMac = new Uint8Array(1);
const encBuffer = new EncArrayBuffer(makeStaticByteArray(60, encType));
beforeEach(() => {
@@ -106,9 +104,9 @@ describe("EncryptService", () => {
});
it("decrypts data with provided key", async () => {
const decryptedBytes = makeStaticByteArray(10, 200).buffer;
const decryptedBytes = makeStaticByteArray(10, 200);
cryptoFunctionService.hmac.mockResolvedValue(makeStaticByteArray(1).buffer);
cryptoFunctionService.hmac.mockResolvedValue(makeStaticByteArray(1));
cryptoFunctionService.compare.mockResolvedValue(true);
cryptoFunctionService.aesDecrypt.mockResolvedValueOnce(decryptedBytes);

View File

@@ -763,13 +763,13 @@ export class StateService<
);
}
async getDecryptedPrivateKey(options?: StorageOptions): Promise<ArrayBuffer> {
async getDecryptedPrivateKey(options?: StorageOptions): Promise<Uint8Array> {
return (
await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
)?.keys?.privateKey.decrypted;
}
async setDecryptedPrivateKey(value: ArrayBuffer, options?: StorageOptions): Promise<void> {
async setDecryptedPrivateKey(value: Uint8Array, options?: StorageOptions): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultInMemoryOptions())
);
@@ -2097,14 +2097,14 @@ export class StateService<
);
}
async getPublicKey(options?: StorageOptions): Promise<ArrayBuffer> {
async getPublicKey(options?: StorageOptions): Promise<Uint8Array> {
const keys = (
await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
)?.keys;
return keys?.publicKey;
}
async setPublicKey(value: ArrayBuffer, options?: StorageOptions): Promise<void> {
async setPublicKey(value: Uint8Array, options?: StorageOptions): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultInMemoryOptions())
);

View File

@@ -160,7 +160,7 @@ describe("WebCrypto Function Service", () => {
const a = new Uint8Array(2);
a[0] = 1;
a[1] = 2;
const equal = await cryptoFunctionService.compare(a.buffer, a.buffer);
const equal = await cryptoFunctionService.compare(a, a);
expect(equal).toBe(true);
});
@@ -172,7 +172,7 @@ describe("WebCrypto Function Service", () => {
const b = new Uint8Array(2);
b[0] = 3;
b[1] = 4;
const equal = await cryptoFunctionService.compare(a.buffer, b.buffer);
const equal = await cryptoFunctionService.compare(a, b);
expect(equal).toBe(false);
});
@@ -183,7 +183,7 @@ describe("WebCrypto Function Service", () => {
a[1] = 2;
const b = new Uint8Array(2);
b[0] = 3;
const equal = await cryptoFunctionService.compare(a.buffer, b.buffer);
const equal = await cryptoFunctionService.compare(a, b);
expect(equal).toBe(false);
});
});
@@ -200,7 +200,7 @@ describe("WebCrypto Function Service", () => {
const a = new Uint8Array(2);
a[0] = 1;
a[1] = 2;
const aByteString = Utils.fromBufferToByteString(a.buffer);
const aByteString = Utils.fromBufferToByteString(a);
const equal = await cryptoFunctionService.compareFast(aByteString, aByteString);
expect(equal).toBe(true);
});
@@ -210,11 +210,11 @@ describe("WebCrypto Function Service", () => {
const a = new Uint8Array(2);
a[0] = 1;
a[1] = 2;
const aByteString = Utils.fromBufferToByteString(a.buffer);
const aByteString = Utils.fromBufferToByteString(a);
const b = new Uint8Array(2);
b[0] = 3;
b[1] = 4;
const bByteString = Utils.fromBufferToByteString(b.buffer);
const bByteString = Utils.fromBufferToByteString(b);
const equal = await cryptoFunctionService.compareFast(aByteString, bByteString);
expect(equal).toBe(false);
});
@@ -224,10 +224,10 @@ describe("WebCrypto Function Service", () => {
const a = new Uint8Array(2);
a[0] = 1;
a[1] = 2;
const aByteString = Utils.fromBufferToByteString(a.buffer);
const aByteString = Utils.fromBufferToByteString(a);
const b = new Uint8Array(2);
b[0] = 3;
const bByteString = Utils.fromBufferToByteString(b.buffer);
const bByteString = Utils.fromBufferToByteString(b);
const equal = await cryptoFunctionService.compareFast(aByteString, bByteString);
expect(equal).toBe(false);
});
@@ -239,7 +239,7 @@ describe("WebCrypto Function Service", () => {
const iv = makeStaticByteArray(16);
const key = makeStaticByteArray(32);
const data = Utils.fromUtf8ToArray("EncryptMe!");
const encValue = await cryptoFunctionService.aesEncrypt(data.buffer, iv.buffer, key.buffer);
const encValue = await cryptoFunctionService.aesEncrypt(data, iv, key);
expect(Utils.fromBufferToB64(encValue)).toBe("ByUF8vhyX4ddU9gcooznwA==");
});
@@ -249,10 +249,10 @@ describe("WebCrypto Function Service", () => {
const key = makeStaticByteArray(32);
const value = "EncryptMe!";
const data = Utils.fromUtf8ToArray(value);
const encValue = await cryptoFunctionService.aesEncrypt(data.buffer, iv.buffer, key.buffer);
const encValue = await cryptoFunctionService.aesEncrypt(data, iv, key);
const encData = Utils.fromBufferToB64(encValue);
const b64Iv = Utils.fromBufferToB64(iv.buffer);
const symKey = new SymmetricCryptoKey(key.buffer);
const b64Iv = Utils.fromBufferToB64(iv);
const symKey = new SymmetricCryptoKey(key);
const params = cryptoFunctionService.aesDecryptFastParameters(encData, b64Iv, null, symKey);
const decValue = await cryptoFunctionService.aesDecryptFast(params);
expect(decValue).toBe(value);
@@ -264,8 +264,8 @@ describe("WebCrypto Function Service", () => {
const key = makeStaticByteArray(32);
const value = "EncryptMe!";
const data = Utils.fromUtf8ToArray(value);
const encValue = await cryptoFunctionService.aesEncrypt(data.buffer, iv.buffer, key.buffer);
const decValue = await cryptoFunctionService.aesDecrypt(encValue, iv.buffer, key.buffer);
const encValue = new Uint8Array(await cryptoFunctionService.aesEncrypt(data, iv, key));
const decValue = await cryptoFunctionService.aesDecrypt(encValue, iv, key);
expect(Utils.fromBufferToUtf8(decValue)).toBe(value);
});
});
@@ -273,8 +273,8 @@ describe("WebCrypto Function Service", () => {
describe("aesDecryptFast", () => {
it("should successfully decrypt data", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
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 = cryptoFunctionService.aesDecryptFastParameters(data, iv, null, symKey);
const decValue = await cryptoFunctionService.aesDecryptFast(params);
@@ -288,7 +288,7 @@ describe("WebCrypto Function Service", () => {
const iv = makeStaticByteArray(16);
const key = makeStaticByteArray(32);
const data = Utils.fromB64ToArray("ByUF8vhyX4ddU9gcooznwA==");
const decValue = await cryptoFunctionService.aesDecrypt(data.buffer, iv.buffer, key.buffer);
const decValue = await cryptoFunctionService.aesDecrypt(data, iv, key);
expect(Utils.fromBufferToUtf8(decValue)).toBe("EncryptMe!");
});
});
@@ -300,8 +300,8 @@ describe("WebCrypto Function Service", () => {
const privKey = Utils.fromB64ToArray(RsaPrivateKey);
const value = "EncryptMe!";
const data = Utils.fromUtf8ToArray(value);
const encValue = await cryptoFunctionService.rsaEncrypt(data.buffer, pubKey.buffer, "sha1");
const decValue = await cryptoFunctionService.rsaDecrypt(encValue, privKey.buffer, "sha1");
const encValue = new Uint8Array(await cryptoFunctionService.rsaEncrypt(data, pubKey, "sha1"));
const decValue = await cryptoFunctionService.rsaDecrypt(encValue, privKey, "sha1");
expect(Utils.fromBufferToUtf8(decValue)).toBe(value);
});
});
@@ -316,7 +316,7 @@ describe("WebCrypto Function Service", () => {
"zFOIEPF2S1zgperEP23M01mr4dWVdYN18B32YF67xdJHMbFhp5dkQwv9CmscoWq7OE5HIfOb+JAh7BEZb+CmKhM3yWJvoR/D" +
"/5jcercUtK2o+XrzNrL4UQ7yLZcFz6Bfwb/j6ICYvqd/YJwXNE6dwlL57OfwJyCdw2rRYf0/qI00t9u8Iitw=="
);
const decValue = await cryptoFunctionService.rsaDecrypt(data.buffer, privKey.buffer, "sha1");
const decValue = await cryptoFunctionService.rsaDecrypt(data, privKey, "sha1");
expect(Utils.fromBufferToUtf8(decValue)).toBe("EncryptMe!");
});
});
@@ -325,7 +325,7 @@ describe("WebCrypto Function Service", () => {
it("should successfully extract key", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const privKey = Utils.fromB64ToArray(RsaPrivateKey);
const publicKey = await cryptoFunctionService.rsaExtractPublicKey(privKey.buffer);
const publicKey = await cryptoFunctionService.rsaExtractPublicKey(privKey);
expect(Utils.fromBufferToB64(publicKey)).toBe(RsaPublicKey);
});
});
@@ -390,8 +390,8 @@ function testPbkdf2(
it("should create valid " + algorithm + " key from array buffer input", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const key = await cryptoFunctionService.pbkdf2(
Utils.fromUtf8ToArray(regularPassword).buffer,
Utils.fromUtf8ToArray(regularEmail).buffer,
Utils.fromUtf8ToArray(regularPassword),
Utils.fromUtf8ToArray(regularEmail),
algorithm,
5000
);
@@ -437,8 +437,8 @@ function testHkdf(
const cryptoFunctionService = getWebCryptoFunctionService();
const key = await cryptoFunctionService.hkdf(
ikm,
Utils.fromUtf8ToArray(regularSalt).buffer,
Utils.fromUtf8ToArray(regularInfo).buffer,
Utils.fromUtf8ToArray(regularSalt),
Utils.fromUtf8ToArray(regularInfo),
32,
algorithm
);
@@ -496,10 +496,7 @@ function testHash(
it("should create valid " + algorithm + " hash from array buffer input", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
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);
});
}
@@ -508,8 +505,8 @@ function testHmac(algorithm: "sha1" | "sha256" | "sha512", mac: string) {
it("should create valid " + algorithm + " hmac", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const computedMac = await cryptoFunctionService.hmac(
Utils.fromUtf8ToArray("SignMe!!").buffer,
Utils.fromUtf8ToArray("secretkey").buffer,
Utils.fromUtf8ToArray("SignMe!!"),
Utils.fromUtf8ToArray("secretkey"),
algorithm
);
expect(Utils.fromBufferToHex(computedMac)).toBe(mac);
@@ -519,14 +516,14 @@ function testHmac(algorithm: "sha1" | "sha256" | "sha512", mac: string) {
function testHmacFast(algorithm: "sha1" | "sha256" | "sha512", mac: string) {
it("should create valid " + algorithm + " hmac", async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const keyByteString = Utils.fromBufferToByteString(Utils.fromUtf8ToArray("secretkey").buffer);
const dataByteString = Utils.fromBufferToByteString(Utils.fromUtf8ToArray("SignMe!!").buffer);
const keyByteString = Utils.fromBufferToByteString(Utils.fromUtf8ToArray("secretkey"));
const dataByteString = Utils.fromBufferToByteString(Utils.fromUtf8ToArray("SignMe!!"));
const computedMac = await cryptoFunctionService.hmacFast(
dataByteString,
keyByteString,
algorithm
);
expect(Utils.fromBufferToHex(Utils.fromByteStringToArray(computedMac).buffer)).toBe(mac);
expect(Utils.fromBufferToHex(Utils.fromByteStringToArray(computedMac))).toBe(mac);
});
}
@@ -535,7 +532,9 @@ function testRsaGenerateKeyPair(length: 1024 | 2048 | 4096) {
"should successfully generate a " + length + " bit key pair",
async () => {
const cryptoFunctionService = getWebCryptoFunctionService();
const keyPair = await cryptoFunctionService.rsaGenerateKeyPair(length);
const keyPair = (await cryptoFunctionService.rsaGenerateKeyPair(length)).map(
(k) => new Uint8Array(k)
);
expect(keyPair[0] == null || keyPair[1] == null).toBe(false);
const publicKey = await cryptoFunctionService.rsaExtractPublicKey(keyPair[1]);
expect(Utils.fromBufferToB64(keyPair[0])).toBe(Utils.fromBufferToB64(publicKey));

View File

@@ -20,11 +20,11 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
}
async pbkdf2(
password: string | ArrayBuffer,
salt: string | ArrayBuffer,
password: string | Uint8Array,
salt: string | Uint8Array,
algorithm: "sha256" | "sha512",
iterations: number
): Promise<ArrayBuffer> {
): Promise<Uint8Array> {
const wcLen = algorithm === "sha256" ? 256 : 512;
const passwordBuf = this.toBuf(password);
const saltBuf = this.toBuf(salt);
@@ -43,16 +43,17 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
false,
["deriveBits"]
);
return await this.subtle.deriveBits(pbkdf2Params, impKey, wcLen);
const buffer = await this.subtle.deriveBits(pbkdf2Params as any, impKey, wcLen);
return new Uint8Array(buffer);
}
async argon2(
password: string | ArrayBuffer,
salt: string | ArrayBuffer,
password: string | Uint8Array,
salt: string | Uint8Array,
iterations: number,
memory: number,
parallelism: number
): Promise<ArrayBuffer> {
): Promise<Uint8Array> {
if (!this.wasmSupported) {
throw "Webassembly support is required for the Argon2 KDF feature.";
}
@@ -74,12 +75,12 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
}
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> {
): Promise<Uint8Array> {
const saltBuf = this.toBuf(salt);
const infoBuf = this.toBuf(info);
@@ -93,16 +94,17 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
const impKey = await this.subtle.importKey("raw", ikm, { name: "HKDF" } as any, false, [
"deriveBits",
]);
return await this.subtle.deriveBits(hkdfParams as any, impKey, outputByteSize * 8);
const buffer = await this.subtle.deriveBits(hkdfParams as any, impKey, outputByteSize * 8);
return new Uint8Array(buffer);
}
// 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.");
@@ -122,49 +124,54 @@ export class WebCryptoFunctionService 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 = new Uint8Array(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);
}
async hash(
value: string | ArrayBuffer,
value: string | Uint8Array,
algorithm: "sha1" | "sha256" | "sha512" | "md5"
): Promise<ArrayBuffer> {
): Promise<Uint8Array> {
if (algorithm === "md5") {
const md = algorithm === "md5" ? forge.md.md5.create() : forge.md.sha1.create();
const valueBytes = this.toByteString(value);
md.update(valueBytes, "raw");
return Utils.fromByteStringToArray(md.digest().data).buffer;
return Utils.fromByteStringToArray(md.digest().data);
}
const valueBuf = this.toBuf(value);
return await this.subtle.digest({ name: this.toWebCryptoAlgorithm(algorithm) }, valueBuf);
const buffer = await this.subtle.digest(
{ name: this.toWebCryptoAlgorithm(algorithm) },
valueBuf
);
return new Uint8Array(buffer);
}
async hmac(
value: ArrayBuffer,
key: ArrayBuffer,
value: Uint8Array,
key: Uint8Array,
algorithm: "sha1" | "sha256" | "sha512"
): Promise<ArrayBuffer> {
): Promise<Uint8Array> {
const signingAlgorithm = {
name: "HMAC",
hash: { name: this.toWebCryptoAlgorithm(algorithm) },
};
const impKey = await this.subtle.importKey("raw", key, signingAlgorithm, false, ["sign"]);
return await this.subtle.sign(signingAlgorithm, impKey, value);
const buffer = await this.subtle.sign(signingAlgorithm, impKey, value);
return new Uint8Array(buffer);
}
// Safely compare two values in a way that protects against timing attacks (Double HMAC Verification).
// ref: https://www.nccgroup.trust/us/about-us/newsroom-and-events/blog/2011/february/double-hmac-verification/
// ref: https://paragonie.com/blog/2015/11/preventing-timing-attacks-on-string-comparison-with-double-hmac-strategy
async compare(a: ArrayBuffer, b: ArrayBuffer): Promise<boolean> {
async compare(a: Uint8Array, b: Uint8Array): Promise<boolean> {
const macKey = await this.randomBytes(32);
const signingAlgorithm = {
name: "HMAC",
@@ -219,11 +226,12 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
return equals;
}
async aesEncrypt(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise<ArrayBuffer> {
async aesEncrypt(data: Uint8Array, iv: Uint8Array, key: Uint8Array): Promise<Uint8Array> {
const impKey = await this.subtle.importKey("raw", key, { name: "AES-CBC" } as any, false, [
"encrypt",
]);
return await this.subtle.encrypt({ name: "AES-CBC", iv: iv }, impKey, data);
const buffer = await this.subtle.encrypt({ name: "AES-CBC", iv: iv }, impKey, data);
return new Uint8Array(buffer);
}
aesDecryptFastParameters(
@@ -275,18 +283,19 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
return Promise.resolve(val);
}
async aesDecrypt(data: ArrayBuffer, iv: ArrayBuffer, key: ArrayBuffer): Promise<ArrayBuffer> {
async aesDecrypt(data: Uint8Array, iv: Uint8Array, key: Uint8Array): Promise<Uint8Array> {
const impKey = await this.subtle.importKey("raw", key, { name: "AES-CBC" } as any, false, [
"decrypt",
]);
return await this.subtle.decrypt({ name: "AES-CBC", iv: iv }, impKey, data);
const buffer = await this.subtle.decrypt({ name: "AES-CBC", iv: iv }, impKey, data);
return new Uint8Array(buffer);
}
async rsaEncrypt(
data: ArrayBuffer,
publicKey: ArrayBuffer,
data: Uint8Array,
publicKey: Uint8Array,
algorithm: "sha1" | "sha256"
): Promise<ArrayBuffer> {
): Promise<Uint8Array> {
// Note: Edge browser requires that we specify name and hash for both key import and decrypt.
// We cannot use the proper types here.
const rsaParams = {
@@ -294,14 +303,15 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
hash: { name: this.toWebCryptoAlgorithm(algorithm) },
};
const impKey = await this.subtle.importKey("spki", publicKey, rsaParams, false, ["encrypt"]);
return await this.subtle.encrypt(rsaParams, impKey, data);
const buffer = await this.subtle.encrypt(rsaParams, impKey, data);
return new Uint8Array(buffer);
}
async rsaDecrypt(
data: ArrayBuffer,
privateKey: ArrayBuffer,
data: Uint8Array,
privateKey: Uint8Array,
algorithm: "sha1" | "sha256"
): Promise<ArrayBuffer> {
): Promise<Uint8Array> {
// Note: Edge browser requires that we specify name and hash for both key import and decrypt.
// We cannot use the proper types here.
const rsaParams = {
@@ -309,10 +319,11 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
hash: { name: this.toWebCryptoAlgorithm(algorithm) },
};
const impKey = await this.subtle.importKey("pkcs8", privateKey, rsaParams, false, ["decrypt"]);
return await this.subtle.decrypt(rsaParams, impKey, data);
const buffer = await this.subtle.decrypt(rsaParams, impKey, data);
return new Uint8Array(buffer);
}
async rsaExtractPublicKey(privateKey: ArrayBuffer): Promise<ArrayBuffer> {
async rsaExtractPublicKey(privateKey: Uint8Array): Promise<Uint8Array> {
const rsaParams = {
name: "RSA-OAEP",
// Have to specify some algorithm
@@ -332,10 +343,11 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
const impPublicKey = await this.subtle.importKey("jwk", jwkPublicKeyParams, rsaParams, true, [
"encrypt",
]);
return await this.subtle.exportKey("spki", impPublicKey);
const buffer = await this.subtle.exportKey("spki", impPublicKey);
return new Uint8Array(buffer);
}
async rsaGenerateKeyPair(length: 1024 | 2048 | 4096): Promise<[ArrayBuffer, ArrayBuffer]> {
async rsaGenerateKeyPair(length: 1024 | 2048 | 4096): Promise<[Uint8Array, Uint8Array]> {
const rsaParams = {
name: "RSA-OAEP",
modulusLength: length,
@@ -349,26 +361,26 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
])) as CryptoKeyPair;
const publicKey = await this.subtle.exportKey("spki", keyPair.publicKey);
const privateKey = await this.subtle.exportKey("pkcs8", keyPair.privateKey);
return [publicKey, privateKey];
return [new Uint8Array(publicKey), new Uint8Array(privateKey)];
}
randomBytes(length: number): Promise<CsprngArray> {
const arr = new Uint8Array(length);
this.crypto.getRandomValues(arr);
return Promise.resolve(arr.buffer as CsprngArray);
return Promise.resolve(arr as CsprngArray);
}
private toBuf(value: string | ArrayBuffer): ArrayBuffer {
let buf: ArrayBuffer;
private toBuf(value: string | Uint8Array): Uint8Array {
let buf: Uint8Array;
if (typeof value === "string") {
buf = Utils.fromUtf8ToArray(value).buffer;
buf = Utils.fromUtf8ToArray(value);
} else {
buf = value;
}
return buf;
}
private toByteString(value: string | ArrayBuffer): string {
private toByteString(value: string | Uint8Array): string {
let bytes: string;
if (typeof value === "string") {
bytes = forge.util.encodeUtf8(value);

View File

@@ -57,10 +57,10 @@ describe("deviceCryptoService", () => {
let makeDeviceKeySpy: jest.SpyInstance;
beforeEach(() => {
mockRandomBytes = new Uint8Array(deviceKeyBytesLength).buffer as CsprngArray;
mockRandomBytes = new Uint8Array(deviceKeyBytesLength) as CsprngArray;
mockDeviceKey = new SymmetricCryptoKey(mockRandomBytes);
existingDeviceKey = new SymmetricCryptoKey(
new Uint8Array(deviceKeyBytesLength).buffer as CsprngArray
new Uint8Array(deviceKeyBytesLength) as CsprngArray
) as DeviceKey;
stateSvcGetDeviceKeySpy = jest.spyOn(stateService, "getDeviceKey");
@@ -97,7 +97,7 @@ describe("deviceCryptoService", () => {
describe("makeDeviceKey", () => {
it("creates a new non-null 64 byte device key, securely stores it, and returns it", async () => {
const mockRandomBytes = new Uint8Array(deviceKeyBytesLength).buffer as CsprngArray;
const mockRandomBytes = new Uint8Array(deviceKeyBytesLength) as CsprngArray;
const cryptoFuncSvcRandomBytesSpy = jest
.spyOn(cryptoFunctionService, "randomBytes")
@@ -128,9 +128,9 @@ describe("deviceCryptoService", () => {
let mockUserSymKey: SymmetricCryptoKey;
const deviceRsaKeyLength = 2048;
let mockDeviceRsaKeyPair: [ArrayBuffer, ArrayBuffer];
let mockDevicePrivateKey: ArrayBuffer;
let mockDevicePublicKey: ArrayBuffer;
let mockDeviceRsaKeyPair: [Uint8Array, Uint8Array];
let mockDevicePrivateKey: Uint8Array;
let mockDevicePublicKey: Uint8Array;
let mockDevicePublicKeyEncryptedUserSymKey: EncString;
let mockUserSymKeyEncryptedDevicePublicKey: EncString;
let mockDeviceKeyEncryptedDevicePrivateKey: EncString;
@@ -156,15 +156,15 @@ describe("deviceCryptoService", () => {
beforeEach(() => {
// Setup all spies and default return values for the happy path
mockDeviceKeyRandomBytes = new Uint8Array(deviceKeyBytesLength).buffer as CsprngArray;
mockDeviceKeyRandomBytes = new Uint8Array(deviceKeyBytesLength) as CsprngArray;
mockDeviceKey = new SymmetricCryptoKey(mockDeviceKeyRandomBytes) as DeviceKey;
mockUserSymKeyRandomBytes = new Uint8Array(userSymKeyBytesLength).buffer as CsprngArray;
mockUserSymKeyRandomBytes = new Uint8Array(userSymKeyBytesLength) as CsprngArray;
mockUserSymKey = new SymmetricCryptoKey(mockUserSymKeyRandomBytes);
mockDeviceRsaKeyPair = [
new ArrayBuffer(deviceRsaKeyLength),
new ArrayBuffer(deviceRsaKeyLength),
new Uint8Array(deviceRsaKeyLength),
new Uint8Array(deviceRsaKeyLength),
];
mockDevicePublicKey = mockDeviceRsaKeyPair[0];

View File

@@ -162,7 +162,7 @@ export class TotpService implements TotpServiceAbstraction {
timeBytes: Uint8Array,
alg: "sha1" | "sha256" | "sha512"
) {
const signature = await this.cryptoFunctionService.hmac(timeBytes.buffer, keyBytes.buffer, alg);
const signature = await this.cryptoFunctionService.hmac(timeBytes, keyBytes, alg);
return new Uint8Array(signature);
}
}

View File

@@ -13,7 +13,7 @@ export class SendView implements View {
accessId: string = null;
name: string = null;
notes: string = null;
key: ArrayBuffer;
key: Uint8Array;
cryptoKey: SymmetricCryptoKey;
type: SendType = null;
text = new SendTextView();
@@ -82,7 +82,7 @@ export class SendView implements View {
}
return Object.assign(new SendView(), json, {
key: Utils.fromB64ToArray(json.key)?.buffer,
key: Utils.fromB64ToArray(json.key),
cryptoKey: SymmetricCryptoKey.fromJSON(json.cryptoKey),
text: SendTextView.fromJSON(json.text),
file: SendFileView.fromJSON(json.file),

View File

@@ -241,7 +241,7 @@ export class SendService implements InternalSendServiceAbstraction {
key: SymmetricCryptoKey
): Promise<[EncString, EncArrayBuffer]> {
const encFileName = await this.cryptoService.encrypt(fileName, key);
const encFileData = await this.cryptoService.encryptToBytes(data, key);
const encFileData = await this.cryptoService.encryptToBytes(new Uint8Array(data), key);
return [encFileName, encFileData];
}

View File

@@ -4,6 +4,6 @@ import { Opaque } from "type-fest";
// represents an array or string value generated from a
// cryptographic secure pseudorandom number generator (CSPRNG)
type CsprngArray = Opaque<ArrayBuffer, "CSPRNG">;
type CsprngArray = Opaque<Uint8Array, "CSPRNG">;
type CsprngString = Opaque<string, "CSPRNG">;

View File

@@ -59,7 +59,7 @@ describe("Cipher Service", () => {
it("attachments upload encrypted file contents", async () => {
const fileName = "filename";
const fileData = new Uint8Array(10).buffer;
cryptoService.getOrgKey(Arg.any()).resolves(new SymmetricCryptoKey(new Uint8Array(32).buffer));
cryptoService.getOrgKey(Arg.any()).resolves(new SymmetricCryptoKey(new Uint8Array(32)));
await cipherService.saveAttachmentRawWithServer(new Cipher(), fileName, fileData);

View File

@@ -637,7 +637,7 @@ export class CipherService implements CipherServiceAbstraction {
const encFileName = await this.cryptoService.encrypt(filename, key);
const dataEncKey = await this.cryptoService.makeEncKey(key);
const encData = await this.cryptoService.encryptToBytes(data, dataEncKey[0]);
const encData = await this.cryptoService.encryptToBytes(new Uint8Array(data), dataEncKey[0]);
const response = await this.cipherFileUploadService.upload(
cipher,