1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-25 12:15:18 +01: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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 401 additions and 424 deletions

View File

@ -59,8 +59,8 @@ export class NativeMessagingBackground {
private port: browser.runtime.Port | chrome.runtime.Port;
private resolver: any = null;
private privateKey: ArrayBuffer = null;
private publicKey: ArrayBuffer = null;
private privateKey: Uint8Array = null;
private publicKey: Uint8Array = null;
private secureSetupResolve: any = null;
private sharedSecret: SymmetricCryptoKey;
private appId: string;
@ -129,7 +129,7 @@ export class NativeMessagingBackground {
const encrypted = Utils.fromB64ToArray(message.sharedSecret);
const decrypted = await this.cryptoFunctionService.rsaDecrypt(
encrypted.buffer,
encrypted,
this.privateKey,
EncryptionAlgorithm
);
@ -321,7 +321,7 @@ export class NativeMessagingBackground {
if (message.response === "unlocked") {
await this.cryptoService.setKey(
new SymmetricCryptoKey(Utils.fromB64ToArray(message.keyB64).buffer)
new SymmetricCryptoKey(Utils.fromB64ToArray(message.keyB64))
);
// Verify key is correct by attempting to decrypt a secret

View File

@ -21,9 +21,7 @@ describe("Browser Session Storage Service", () => {
let localStorage: BrowserLocalStorageService;
let sessionStorage: BrowserMemoryStorageService;
const key = new SymmetricCryptoKey(
Utils.fromUtf8ToArray("00000000000000000000000000000000").buffer
);
const key = new SymmetricCryptoKey(Utils.fromUtf8ToArray("00000000000000000000000000000000"));
let getSessionKeySpy: jest.SpyInstance;
const mockEnc = (input: string) => Promise.resolve(new EncString("ENCRYPTED" + input));

View File

@ -51,7 +51,7 @@ export class ConfirmCommand {
}
const publicKeyResponse = await this.apiService.getUserPublicKey(orgUser.userId);
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
const key = await this.cryptoService.rsaEncrypt(orgKey.key, publicKey.buffer);
const key = await this.cryptoService.rsaEncrypt(orgKey.key, publicKey);
const req = new OrganizationUserConfirmRequest();
req.key = key.encryptedString;
await this.organizationUserService.postOrganizationUserConfirm(

View File

@ -513,7 +513,7 @@ export class GetCommand extends DownloadCommand {
try {
const response = await this.apiService.getUserPublicKey(id);
const pubKey = Utils.fromB64ToArray(response.publicKey);
fingerprint = await this.cryptoService.getFingerprint(id, pubKey.buffer);
fingerprint = await this.cryptoService.getFingerprint(id, pubKey);
} catch {
// eslint-disable-next-line
}

View File

@ -47,7 +47,7 @@ export class NodeEnvSecureStorageService implements AbstractStorageService {
throw new Error("No session key available.");
}
const encValue = await this.cryptoService().encryptToBytes(
Utils.fromB64ToArray(plainValue).buffer,
Utils.fromB64ToArray(plainValue),
sessionKey
);
if (encValue == null) {
@ -81,7 +81,7 @@ export class NodeEnvSecureStorageService implements AbstractStorageService {
private getSessionKey() {
try {
if (process.env.BW_SESSION != null) {
const sessionBuffer = Utils.fromB64ToArray(process.env.BW_SESSION).buffer;
const sessionBuffer = Utils.fromB64ToArray(process.env.BW_SESSION);
if (sessionBuffer != null) {
const sessionKey = new SymmetricCryptoKey(sessionBuffer);
if (sessionBuffer != null) {

View File

@ -121,7 +121,7 @@ export class SendReceiveCommand extends DownloadCommand {
}
}
private async getUnlockedPassword(password: string, keyArray: ArrayBuffer) {
private async getUnlockedPassword(password: string, keyArray: Uint8Array) {
const passwordHash = await this.cryptoFunctionService.pbkdf2(
password,
keyArray,
@ -134,7 +134,7 @@ export class SendReceiveCommand extends DownloadCommand {
private async sendRequest(
url: string,
id: string,
key: ArrayBuffer
key: Uint8Array
): Promise<Response | SendAccessView> {
try {
const sendResponse = await this.sendApiService.postSendAccess(

View File

@ -225,8 +225,8 @@ export default class NativeMessageService {
}
private async getSharedKeyForKey(key: string): Promise<SymmetricCryptoKey> {
const dataBuffer = Utils.fromB64ToArray(key).buffer;
const privKey = Utils.fromB64ToArray(config.testRsaPrivateKey).buffer;
const dataBuffer = Utils.fromB64ToArray(key);
const privKey = Utils.fromB64ToArray(config.testRsaPrivateKey);
return new SymmetricCryptoKey(
await this.nodeCryptoFunctionService.rsaDecrypt(dataBuffer, privKey, "sha1")

View File

@ -68,7 +68,7 @@ export class LoginApprovalComponent implements OnInit, OnDestroy {
const publicKey = Utils.fromB64ToArray(this.authRequestResponse.publicKey);
this.email = await this.stateService.getEmail();
this.fingerprintPhrase = (
await this.cryptoService.getFingerprint(this.email, publicKey.buffer)
await this.cryptoService.getFingerprint(this.email, publicKey)
).join("-");
this.updateTimeText();

View File

@ -98,7 +98,7 @@ export class ElectronStateService
options
);
return new SymmetricCryptoKey(Utils.fromB64ToArray(b64DeviceKey).buffer) as DeviceKey;
return new SymmetricCryptoKey(Utils.fromB64ToArray(b64DeviceKey)) as DeviceKey;
}
override async setDeviceKey(value: DeviceKey, options?: StorageOptions): Promise<void> {

View File

@ -70,7 +70,7 @@ export class NativeMessageHandlerService {
}
try {
const remotePublicKey = Utils.fromB64ToArray(publicKey).buffer;
const remotePublicKey = Utils.fromB64ToArray(publicKey);
const ddgEnabled = await this.stateService.getEnableDuckDuckGoBrowserIntegration();
if (!ddgEnabled) {

View File

@ -56,7 +56,7 @@ export class NativeMessagingService {
// Request to setup secure encryption
if ("command" in rawMessage && rawMessage.command === "setupEncryption") {
const remotePublicKey = Utils.fromB64ToArray(rawMessage.publicKey).buffer;
const remotePublicKey = Utils.fromB64ToArray(rawMessage.publicKey);
// Validate the UserId to ensure we are logged into the same account.
const accounts = await firstValueFrom(this.stateService.accounts$);
@ -169,7 +169,7 @@ export class NativeMessagingService {
ipcRenderer.send("nativeMessagingReply", { appId: appId, message: encrypted });
}
private async secureCommunication(remotePublicKey: ArrayBuffer, appId: string) {
private async secureCommunication(remotePublicKey: Uint8Array, appId: string) {
const secret = await this.cryptoFunctionService.randomBytes(64);
this.sharedSecrets.set(appId, new SymmetricCryptoKey(secret));

View File

@ -28,10 +28,7 @@ export class UserConfirmComponent implements OnInit {
async ngOnInit() {
try {
if (this.publicKey != null) {
const fingerprint = await this.cryptoService.getFingerprint(
this.userId,
this.publicKey.buffer
);
const fingerprint = await this.cryptoService.getFingerprint(this.userId, this.publicKey);
if (fingerprint != null) {
this.fingerprint = fingerprint.join("-");
}

View File

@ -47,7 +47,7 @@ export class BulkConfirmComponent implements OnInit {
for (const entry of response.data) {
const publicKey = Utils.fromB64ToArray(entry.key);
const fingerprint = await this.cryptoService.getFingerprint(entry.userId, publicKey.buffer);
const fingerprint = await this.cryptoService.getFingerprint(entry.userId, publicKey);
if (fingerprint != null) {
this.publicKeys.set(entry.id, publicKey);
this.fingerprints.set(entry.id, fingerprint.join("-"));
@ -67,7 +67,7 @@ export class BulkConfirmComponent implements OnInit {
if (publicKey == null) {
continue;
}
const encryptedKey = await this.cryptoService.rsaEncrypt(key.key, publicKey.buffer);
const encryptedKey = await this.cryptoService.rsaEncrypt(key.key, publicKey);
userIdsWithKeys.push({
id: user.id,
key: encryptedKey.encryptedString,

View File

@ -302,7 +302,7 @@ export class PeopleComponent
async confirmUser(user: OrganizationUserView, publicKey: Uint8Array): Promise<void> {
const orgKey = await this.cryptoService.getOrgKey(this.organization.id);
const key = await this.cryptoService.rsaEncrypt(orgKey.key, publicKey.buffer);
const key = await this.cryptoService.rsaEncrypt(orgKey.key, publicKey);
const request = new OrganizationUserConfirmRequest();
request.key = key.encryptedString;
await this.organizationUserService.postOrganizationUserConfirm(

View File

@ -61,7 +61,7 @@ export class AccountComponent {
});
protected organizationId: string;
protected publicKeyBuffer: ArrayBuffer;
protected publicKeyBuffer: Uint8Array;
private destroy$ = new Subject<void>();
@ -106,7 +106,7 @@ export class AccountComponent {
this.org = orgResponse;
// Public Key Buffer for Org Fingerprint Generation
this.publicKeyBuffer = Utils.fromB64ToArray(orgKeys?.publicKey)?.buffer;
this.publicKeyBuffer = Utils.fromB64ToArray(orgKeys?.publicKey);
// Patch existing values
this.formGroup.patchValue({

View File

@ -59,7 +59,7 @@ export class EnrollMasterPasswordReset {
// RSA Encrypt user's encKey.key with organization public key
const encKey = await this.cryptoService.getEncKey();
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey.buffer);
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey);
keyString = encryptedKey.encryptedString;
toastStringRef = "enrollPasswordResetSuccess";

View File

@ -142,7 +142,7 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent {
// RSA Encrypt user's encKey.key with organization public key
const encKey = await this.cryptoService.getEncKey();
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey.buffer);
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey);
// Add reset password key to accept request
request.resetPasswordKey = encryptedKey.encryptedString;

View File

@ -33,7 +33,7 @@ export class EmergencyAccessConfirmComponent implements OnInit {
const publicKeyResponse = await this.apiService.getUserPublicKey(this.userId);
if (publicKeyResponse != null) {
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
const fingerprint = await this.cryptoService.getFingerprint(this.userId, publicKey.buffer);
const fingerprint = await this.cryptoService.getFingerprint(this.userId, publicKey);
if (fingerprint != null) {
this.fingerprint = fingerprint.join("-");
}

View File

@ -309,13 +309,13 @@ export class EmergencyAccessComponent implements OnInit {
try {
this.logService.debug(
"User's fingerprint: " +
(await this.cryptoService.getFingerprint(details.granteeId, publicKey.buffer)).join("-")
(await this.cryptoService.getFingerprint(details.granteeId, publicKey)).join("-")
);
} catch {
// Ignore errors since it's just a debug message
}
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey.buffer);
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey);
const request = new EmergencyAccessConfirmRequest();
request.key = encryptedKey.encryptedString;
await this.apiService.postEmergencyAccessConfirm(details.id, request);

View File

@ -374,7 +374,7 @@ export abstract class BasePeopleComponent<
}
try {
const fingerprint = await this.cryptoService.getFingerprint(user.userId, publicKey.buffer);
const fingerprint = await this.cryptoService.getFingerprint(user.userId, publicKey);
this.logService.info(`User's fingerprint: ${fingerprint.join("-")}`);
} catch (e) {
this.logService.error(e);

View File

@ -274,7 +274,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
const publicKeyResponse = await this.apiService.getUserPublicKey(details.granteeId);
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey.buffer);
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey);
const updateRequest = new EmergencyAccessUpdateRequest();
updateRequest.type = details.type;
@ -299,7 +299,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
const publicKey = Utils.fromB64ToArray(response?.publicKey);
// Re-enroll - encrypt user's encKey.key with organization public key
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey.buffer);
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey);
// Create/Execute request
const request = new OrganizationUserResetPasswordEnrollmentRequest();

View File

@ -12,7 +12,7 @@ import { SharedModule } from "../../shared.module";
})
export class AccountFingerprintComponent implements OnInit {
@Input() fingerprintMaterial: string;
@Input() publicKeyBuffer: ArrayBuffer;
@Input() publicKeyBuffer: Uint8Array;
@Input() fingerprintLabel: string;
protected fingerprint: string;

View File

@ -277,7 +277,7 @@ function createCipherView(i: number, deleted = false): CipherView {
view.attachments = [attachment];
} else if (i % 5 === 0) {
const attachment = new AttachmentView();
attachment.key = new SymmetricCryptoKey(new ArrayBuffer(32));
attachment.key = new SymmetricCryptoKey(new Uint8Array(32));
view.attachments = [attachment];
}

View File

@ -90,7 +90,7 @@ export class DeviceApprovalsComponent implements OnInit, OnDestroy {
const userSymKey = new SymmetricCryptoKey(decValue);
// Re-encrypt User's Symmetric Key with the Device Public Key
return await this.cryptoService.rsaEncrypt(userSymKey.key, devicePubKey.buffer);
return await this.cryptoService.rsaEncrypt(userSymKey.key, devicePubKey);
}
async approveRequest(authRequest: PendingAuthRequestView) {

View File

@ -138,7 +138,7 @@ export class PeopleComponent
async confirmUser(user: ProviderUserUserDetailsResponse, publicKey: Uint8Array): Promise<any> {
const providerKey = await this.cryptoService.getProviderKey(this.providerId);
const key = await this.cryptoService.rsaEncrypt(providerKey.key, publicKey.buffer);
const key = await this.cryptoService.rsaEncrypt(providerKey.key, publicKey);
const request = new ProviderUserConfirmRequest();
request.key = key.encryptedString;
await this.apiService.postProviderUserConfirm(this.providerId, user.id, request);

View File

@ -46,7 +46,7 @@ export class LoginWithDeviceComponent
protected successRoute = "vault";
protected forcePasswordResetRoute = "update-temp-password";
private resendTimeout = 12000;
private authRequestKeyPair: [publicKey: ArrayBuffer, privateKey: ArrayBuffer];
private authRequestKeyPair: [publicKey: Uint8Array, privateKey: Uint8Array];
constructor(
protected router: Router,

View File

@ -133,10 +133,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
// RSA Encrypt user's encKey.key with organization public key
const userEncKey = await this.cryptoService.getEncKey();
const encryptedKey = await this.cryptoService.rsaEncrypt(
userEncKey.key,
publicKey.buffer
);
const encryptedKey = await this.cryptoService.rsaEncrypt(userEncKey.key, publicKey);
const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest();
resetRequest.masterPasswordHash = masterPasswordHash;

View File

@ -15,10 +15,7 @@ export class FingerprintPipe {
publicKey = Utils.fromB64ToArray(publicKey);
}
const fingerprint = await this.cryptoService.getFingerprint(
fingerprintMaterial,
publicKey.buffer
);
const fingerprint = await this.cryptoService.getFingerprint(fingerprintMaterial, publicKey);
if (fingerprint != null) {
return fingerprint.join("-");

15
libs/common/custom-matchers.d.ts vendored Normal file
View File

@ -0,0 +1,15 @@
import type { CustomMatchers } from "./test.setup";
// This declares the types for our custom matchers so that they're recognised by Typescript
// This file must also be included in the TS compilation (via the tsconfig.json "include" property) to be recognised by
// vscode
/* eslint-disable */
declare global {
namespace jest {
interface Expect extends CustomMatchers {}
interface Matchers<R> extends CustomMatchers<R> {}
interface InverseAsymmetricMatchers extends CustomMatchers {}
}
}
/* eslint-enable */

View File

@ -5,14 +5,11 @@
* (and optionally, the expected value) and then call toEqual() on the resulting Uint8Arrays.
*/
export const toEqualBuffer: jest.CustomMatcher = function (
received: ArrayBuffer,
expected: Uint8Array | ArrayBuffer
received: ArrayBuffer | Uint8Array,
expected: ArrayBuffer | Uint8Array
) {
received = new Uint8Array(received);
if (expected instanceof ArrayBuffer) {
expected = new Uint8Array(expected);
}
expected = new Uint8Array(expected);
if (this.equals(received, expected)) {
return {

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,

View File

@ -12,16 +12,6 @@ expect.extend({
toEqualBuffer: toEqualBuffer,
});
interface CustomMatchers<R = unknown> {
export interface CustomMatchers<R = unknown> {
toEqualBuffer(expected: Uint8Array | ArrayBuffer): R;
}
/* eslint-disable */
declare global {
namespace jest {
interface Expect extends CustomMatchers {}
interface Matchers<R> extends CustomMatchers<R> {}
interface InverseAsymmetricMatchers extends CustomMatchers {}
}
}
/* eslint-enable */

View File

@ -1,5 +1,5 @@
{
"extends": "../shared/tsconfig.libs",
"include": ["src", "spec"],
"include": ["src", "spec", "./custom-matchers.d.ts"],
"exclude": ["node_modules", "dist"]
}

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);