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

@@ -59,8 +59,8 @@ export class NativeMessagingBackground {
private port: browser.runtime.Port | chrome.runtime.Port; private port: browser.runtime.Port | chrome.runtime.Port;
private resolver: any = null; private resolver: any = null;
private privateKey: ArrayBuffer = null; private privateKey: Uint8Array = null;
private publicKey: ArrayBuffer = null; private publicKey: Uint8Array = null;
private secureSetupResolve: any = null; private secureSetupResolve: any = null;
private sharedSecret: SymmetricCryptoKey; private sharedSecret: SymmetricCryptoKey;
private appId: string; private appId: string;
@@ -129,7 +129,7 @@ export class NativeMessagingBackground {
const encrypted = Utils.fromB64ToArray(message.sharedSecret); const encrypted = Utils.fromB64ToArray(message.sharedSecret);
const decrypted = await this.cryptoFunctionService.rsaDecrypt( const decrypted = await this.cryptoFunctionService.rsaDecrypt(
encrypted.buffer, encrypted,
this.privateKey, this.privateKey,
EncryptionAlgorithm EncryptionAlgorithm
); );
@@ -321,7 +321,7 @@ export class NativeMessagingBackground {
if (message.response === "unlocked") { if (message.response === "unlocked") {
await this.cryptoService.setKey( 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 // 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 localStorage: BrowserLocalStorageService;
let sessionStorage: BrowserMemoryStorageService; let sessionStorage: BrowserMemoryStorageService;
const key = new SymmetricCryptoKey( const key = new SymmetricCryptoKey(Utils.fromUtf8ToArray("00000000000000000000000000000000"));
Utils.fromUtf8ToArray("00000000000000000000000000000000").buffer
);
let getSessionKeySpy: jest.SpyInstance; let getSessionKeySpy: jest.SpyInstance;
const mockEnc = (input: string) => Promise.resolve(new EncString("ENCRYPTED" + input)); 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 publicKeyResponse = await this.apiService.getUserPublicKey(orgUser.userId);
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey); 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(); const req = new OrganizationUserConfirmRequest();
req.key = key.encryptedString; req.key = key.encryptedString;
await this.organizationUserService.postOrganizationUserConfirm( await this.organizationUserService.postOrganizationUserConfirm(

View File

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

View File

@@ -47,7 +47,7 @@ export class NodeEnvSecureStorageService implements AbstractStorageService {
throw new Error("No session key available."); throw new Error("No session key available.");
} }
const encValue = await this.cryptoService().encryptToBytes( const encValue = await this.cryptoService().encryptToBytes(
Utils.fromB64ToArray(plainValue).buffer, Utils.fromB64ToArray(plainValue),
sessionKey sessionKey
); );
if (encValue == null) { if (encValue == null) {
@@ -81,7 +81,7 @@ export class NodeEnvSecureStorageService implements AbstractStorageService {
private getSessionKey() { private getSessionKey() {
try { try {
if (process.env.BW_SESSION != null) { 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) { if (sessionBuffer != null) {
const sessionKey = new SymmetricCryptoKey(sessionBuffer); const sessionKey = new SymmetricCryptoKey(sessionBuffer);
if (sessionBuffer != null) { 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( const passwordHash = await this.cryptoFunctionService.pbkdf2(
password, password,
keyArray, keyArray,
@@ -134,7 +134,7 @@ export class SendReceiveCommand extends DownloadCommand {
private async sendRequest( private async sendRequest(
url: string, url: string,
id: string, id: string,
key: ArrayBuffer key: Uint8Array
): Promise<Response | SendAccessView> { ): Promise<Response | SendAccessView> {
try { try {
const sendResponse = await this.sendApiService.postSendAccess( const sendResponse = await this.sendApiService.postSendAccess(

View File

@@ -225,8 +225,8 @@ export default class NativeMessageService {
} }
private async getSharedKeyForKey(key: string): Promise<SymmetricCryptoKey> { private async getSharedKeyForKey(key: string): Promise<SymmetricCryptoKey> {
const dataBuffer = Utils.fromB64ToArray(key).buffer; const dataBuffer = Utils.fromB64ToArray(key);
const privKey = Utils.fromB64ToArray(config.testRsaPrivateKey).buffer; const privKey = Utils.fromB64ToArray(config.testRsaPrivateKey);
return new SymmetricCryptoKey( return new SymmetricCryptoKey(
await this.nodeCryptoFunctionService.rsaDecrypt(dataBuffer, privKey, "sha1") 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); const publicKey = Utils.fromB64ToArray(this.authRequestResponse.publicKey);
this.email = await this.stateService.getEmail(); this.email = await this.stateService.getEmail();
this.fingerprintPhrase = ( this.fingerprintPhrase = (
await this.cryptoService.getFingerprint(this.email, publicKey.buffer) await this.cryptoService.getFingerprint(this.email, publicKey)
).join("-"); ).join("-");
this.updateTimeText(); this.updateTimeText();

View File

@@ -98,7 +98,7 @@ export class ElectronStateService
options 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> { override async setDeviceKey(value: DeviceKey, options?: StorageOptions): Promise<void> {

View File

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

View File

@@ -56,7 +56,7 @@ export class NativeMessagingService {
// Request to setup secure encryption // Request to setup secure encryption
if ("command" in rawMessage && rawMessage.command === "setupEncryption") { 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. // Validate the UserId to ensure we are logged into the same account.
const accounts = await firstValueFrom(this.stateService.accounts$); const accounts = await firstValueFrom(this.stateService.accounts$);
@@ -169,7 +169,7 @@ export class NativeMessagingService {
ipcRenderer.send("nativeMessagingReply", { appId: appId, message: encrypted }); 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); const secret = await this.cryptoFunctionService.randomBytes(64);
this.sharedSecrets.set(appId, new SymmetricCryptoKey(secret)); this.sharedSecrets.set(appId, new SymmetricCryptoKey(secret));

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -33,7 +33,7 @@ export class EmergencyAccessConfirmComponent implements OnInit {
const publicKeyResponse = await this.apiService.getUserPublicKey(this.userId); const publicKeyResponse = await this.apiService.getUserPublicKey(this.userId);
if (publicKeyResponse != null) { if (publicKeyResponse != null) {
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey); 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) { if (fingerprint != null) {
this.fingerprint = fingerprint.join("-"); this.fingerprint = fingerprint.join("-");
} }

View File

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

View File

@@ -374,7 +374,7 @@ export abstract class BasePeopleComponent<
} }
try { 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("-")}`); this.logService.info(`User's fingerprint: ${fingerprint.join("-")}`);
} catch (e) { } catch (e) {
this.logService.error(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 publicKeyResponse = await this.apiService.getUserPublicKey(details.granteeId);
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey); 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(); const updateRequest = new EmergencyAccessUpdateRequest();
updateRequest.type = details.type; updateRequest.type = details.type;
@@ -299,7 +299,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
const publicKey = Utils.fromB64ToArray(response?.publicKey); const publicKey = Utils.fromB64ToArray(response?.publicKey);
// Re-enroll - encrypt user's encKey.key with organization public key // 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 // Create/Execute request
const request = new OrganizationUserResetPasswordEnrollmentRequest(); const request = new OrganizationUserResetPasswordEnrollmentRequest();

View File

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

View File

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

View File

@@ -90,7 +90,7 @@ export class DeviceApprovalsComponent implements OnInit, OnDestroy {
const userSymKey = new SymmetricCryptoKey(decValue); const userSymKey = new SymmetricCryptoKey(decValue);
// Re-encrypt User's Symmetric Key with the Device Public Key // 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) { async approveRequest(authRequest: PendingAuthRequestView) {

View File

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

View File

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

View File

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

View File

@@ -15,10 +15,7 @@ export class FingerprintPipe {
publicKey = Utils.fromB64ToArray(publicKey); publicKey = Utils.fromB64ToArray(publicKey);
} }
const fingerprint = await this.cryptoService.getFingerprint( const fingerprint = await this.cryptoService.getFingerprint(fingerprintMaterial, publicKey);
fingerprintMaterial,
publicKey.buffer
);
if (fingerprint != null) { if (fingerprint != null) {
return fingerprint.join("-"); 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. * (and optionally, the expected value) and then call toEqual() on the resulting Uint8Arrays.
*/ */
export const toEqualBuffer: jest.CustomMatcher = function ( export const toEqualBuffer: jest.CustomMatcher = function (
received: ArrayBuffer, received: ArrayBuffer | Uint8Array,
expected: Uint8Array | ArrayBuffer expected: ArrayBuffer | Uint8Array
) { ) {
received = new Uint8Array(received); received = new Uint8Array(received);
expected = new Uint8Array(expected);
if (expected instanceof ArrayBuffer) {
expected = new Uint8Array(expected);
}
if (this.equals(received, expected)) { if (this.equals(received, expected)) {
return { return {

View File

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

View File

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

View File

@@ -22,9 +22,9 @@ export abstract class CryptoService {
getKeyHash: () => Promise<string>; getKeyHash: () => Promise<string>;
compareAndUpdateKeyHash: (masterPassword: string, key: SymmetricCryptoKey) => Promise<boolean>; compareAndUpdateKeyHash: (masterPassword: string, key: SymmetricCryptoKey) => Promise<boolean>;
getEncKey: (key?: SymmetricCryptoKey) => Promise<SymmetricCryptoKey>; getEncKey: (key?: SymmetricCryptoKey) => Promise<SymmetricCryptoKey>;
getPublicKey: () => Promise<ArrayBuffer>; getPublicKey: () => Promise<Uint8Array>;
getPrivateKey: () => Promise<ArrayBuffer>; getPrivateKey: () => Promise<Uint8Array>;
getFingerprint: (fingerprintMaterial: string, publicKey?: ArrayBuffer) => Promise<string[]>; getFingerprint: (fingerprintMaterial: string, publicKey?: Uint8Array) => Promise<string[]>;
getOrgKeys: () => Promise<Map<string, SymmetricCryptoKey>>; getOrgKeys: () => Promise<Map<string, SymmetricCryptoKey>>;
getOrgKey: (orgId: string) => Promise<SymmetricCryptoKey>; getOrgKey: (orgId: string) => Promise<SymmetricCryptoKey>;
getProviderKey: (providerId: string) => Promise<SymmetricCryptoKey>; getProviderKey: (providerId: string) => Promise<SymmetricCryptoKey>;
@@ -63,7 +63,7 @@ export abstract class CryptoService {
kdf: KdfType, kdf: KdfType,
kdfConfig: KdfConfig kdfConfig: KdfConfig
) => Promise<SymmetricCryptoKey>; ) => Promise<SymmetricCryptoKey>;
makeSendKey: (keyMaterial: ArrayBuffer) => Promise<SymmetricCryptoKey>; makeSendKey: (keyMaterial: Uint8Array) => Promise<SymmetricCryptoKey>;
hashPassword: ( hashPassword: (
password: string, password: string,
key: SymmetricCryptoKey, key: SymmetricCryptoKey,
@@ -74,13 +74,13 @@ export abstract class CryptoService {
key: SymmetricCryptoKey, key: SymmetricCryptoKey,
encKey?: SymmetricCryptoKey encKey?: SymmetricCryptoKey
) => Promise<[SymmetricCryptoKey, EncString]>; ) => Promise<[SymmetricCryptoKey, EncString]>;
encrypt: (plainValue: string | ArrayBuffer, key?: SymmetricCryptoKey) => Promise<EncString>; encrypt: (plainValue: string | Uint8Array, key?: SymmetricCryptoKey) => Promise<EncString>;
encryptToBytes: (plainValue: ArrayBuffer, key?: SymmetricCryptoKey) => Promise<EncArrayBuffer>; encryptToBytes: (plainValue: Uint8Array, key?: SymmetricCryptoKey) => Promise<EncArrayBuffer>;
rsaEncrypt: (data: ArrayBuffer, publicKey?: ArrayBuffer) => Promise<EncString>; rsaEncrypt: (data: Uint8Array, publicKey?: Uint8Array) => Promise<EncString>;
rsaDecrypt: (encValue: string, privateKeyValue?: ArrayBuffer) => Promise<ArrayBuffer>; rsaDecrypt: (encValue: string, privateKeyValue?: Uint8Array) => Promise<Uint8Array>;
decryptToBytes: (encString: EncString, key?: SymmetricCryptoKey) => Promise<ArrayBuffer>; decryptToBytes: (encString: EncString, key?: SymmetricCryptoKey) => Promise<Uint8Array>;
decryptToUtf8: (encString: EncString, key?: SymmetricCryptoKey) => Promise<string>; 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>; randomNumber: (min: number, max: number) => Promise<number>;
validateKey: (key: SymmetricCryptoKey) => Promise<boolean>; 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"; import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
export abstract class EncryptService { export abstract class EncryptService {
abstract encrypt(plainValue: string | ArrayBuffer, key: SymmetricCryptoKey): Promise<EncString>; abstract encrypt(plainValue: string | Uint8Array, key: SymmetricCryptoKey): Promise<EncString>;
abstract encryptToBytes: ( abstract encryptToBytes: (
plainValue: ArrayBuffer, plainValue: Uint8Array,
key?: SymmetricCryptoKey key?: SymmetricCryptoKey
) => Promise<EncArrayBuffer>; ) => Promise<EncArrayBuffer>;
abstract decryptToUtf8: (encString: EncString, key: SymmetricCryptoKey) => Promise<string>; 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 resolveLegacyKey: (key: SymmetricCryptoKey, encThing: Encrypted) => SymmetricCryptoKey;
abstract decryptItems: <T extends InitializerMetadata>( abstract decryptItems: <T extends InitializerMetadata>(
items: Decryptable<T>[], items: Decryptable<T>[],

View File

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

View File

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

View File

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

View File

@@ -119,8 +119,8 @@ export class AccountKeys {
any, any,
Record<string, SymmetricCryptoKey> Record<string, SymmetricCryptoKey>
>(); >();
privateKey?: EncryptionPair<string, ArrayBuffer> = new EncryptionPair<string, ArrayBuffer>(); privateKey?: EncryptionPair<string, Uint8Array> = new EncryptionPair<string, Uint8Array>();
publicKey?: ArrayBuffer; publicKey?: Uint8Array;
apiKeyClientSecret?: string; apiKeyClientSecret?: string;
toJSON() { toJSON() {
@@ -142,11 +142,10 @@ export class AccountKeys {
), ),
organizationKeys: AccountKeys.initRecordEncryptionPairsFromJSON(obj?.organizationKeys), organizationKeys: AccountKeys.initRecordEncryptionPairsFromJSON(obj?.organizationKeys),
providerKeys: AccountKeys.initRecordEncryptionPairsFromJSON(obj?.providerKeys), providerKeys: AccountKeys.initRecordEncryptionPairsFromJSON(obj?.providerKeys),
privateKey: EncryptionPair.fromJSON<string, ArrayBuffer>( privateKey: EncryptionPair.fromJSON<string, Uint8Array>(obj?.privateKey, (decObj: string) =>
obj?.privateKey, Utils.fromByteStringToArray(decObj)
(decObj: string) => Utils.fromByteStringToArray(decObj).buffer
), ),
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(mac, 1 + iv.byteLength);
array.set(data, 1 + iv.byteLength + mac.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.encryptionType).toEqual(encType);
expect(actual.ivBytes).toEqualBuffer(iv); expect(actual.ivBytes).toEqualBuffer(iv);
@@ -39,11 +39,11 @@ describe("encArrayBuffer", () => {
array.set(iv, 1); array.set(iv, 1);
array.set(data, 1 + iv.byteLength); array.set(data, 1 + iv.byteLength);
const actual = new EncArrayBuffer(array.buffer); const actual = new EncArrayBuffer(array);
expect(actual.encryptionType).toEqual(encType); expect(actual.encryptionType).toEqual(encType);
expect(actual.ivBytes).toEqualBuffer(iv); expect(actual.ivBytes).toEqual(iv);
expect(actual.dataBytes).toEqualBuffer(data); expect(actual.dataBytes).toEqual(data);
expect(actual.macBytes).toBeNull(); expect(actual.macBytes).toBeNull();
}); });
}); });
@@ -58,13 +58,11 @@ describe("encArrayBuffer", () => {
// Minus 1 to leave room for the encType, minus 1 to make it invalid // Minus 1 to leave room for the encType, minus 1 to make it invalid
const invalidBytes = makeStaticByteArray(minLength - 2); 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([encType]);
invalidArray.set(invalidBytes, 1); invalidArray.set(invalidBytes, 1);
expect(() => new EncArrayBuffer(invalidArray.buffer)).toThrow( expect(() => new EncArrayBuffer(invalidArray)).toThrow("Error parsing encrypted ArrayBuffer");
"Error parsing encrypted ArrayBuffer"
);
}); });
}); });

View File

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

View File

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

View File

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

View File

@@ -11,6 +11,13 @@ describe("EncryptionPair", () => {
expect(json.decrypted).toEqual("hello"); 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", () => { it("should serialize encrypted and decrypted", () => {
const pair = new EncryptionPair<string, string>(); const pair = new EncryptionPair<string, string>();
pair.encrypted = "hello"; pair.encrypted = "hello";

View File

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

View File

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

View File

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

View File

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

View File

@@ -37,10 +37,8 @@ describe("EncryptService", () => {
describe("encrypts data", () => { describe("encrypts data", () => {
beforeEach(() => { beforeEach(() => {
cryptoFunctionService.randomBytes cryptoFunctionService.randomBytes.calledWith(16).mockResolvedValueOnce(iv as CsprngArray);
.calledWith(16) cryptoFunctionService.aesEncrypt.mockResolvedValue(encryptedData);
.mockResolvedValueOnce(iv.buffer as CsprngArray);
cryptoFunctionService.aesEncrypt.mockResolvedValue(encryptedData.buffer);
}); });
it("using a key which supports mac", async () => { it("using a key which supports mac", async () => {
@@ -50,7 +48,7 @@ describe("EncryptService", () => {
key.macKey = makeStaticByteArray(16, 20); key.macKey = makeStaticByteArray(16, 20);
cryptoFunctionService.hmac.mockResolvedValue(mac.buffer); cryptoFunctionService.hmac.mockResolvedValue(mac);
const actual = await encryptService.encryptToBytes(plainValue, key); const actual = await encryptService.encryptToBytes(plainValue, key);
@@ -86,7 +84,7 @@ describe("EncryptService", () => {
describe("decryptToBytes", () => { describe("decryptToBytes", () => {
const encType = EncryptionType.AesCbc256_HmacSha256_B64; const encType = EncryptionType.AesCbc256_HmacSha256_B64;
const key = new SymmetricCryptoKey(makeStaticByteArray(64, 100), encType); 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)); const encBuffer = new EncArrayBuffer(makeStaticByteArray(60, encType));
beforeEach(() => { beforeEach(() => {
@@ -106,9 +104,9 @@ describe("EncryptService", () => {
}); });
it("decrypts data with provided key", async () => { 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.compare.mockResolvedValue(true);
cryptoFunctionService.aesDecrypt.mockResolvedValueOnce(decryptedBytes); 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 ( return (
await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions())) await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
)?.keys?.privateKey.decrypted; )?.keys?.privateKey.decrypted;
} }
async setDecryptedPrivateKey(value: ArrayBuffer, options?: StorageOptions): Promise<void> { async setDecryptedPrivateKey(value: Uint8Array, options?: StorageOptions): Promise<void> {
const account = await this.getAccount( const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultInMemoryOptions()) 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 = ( const keys = (
await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions())) await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
)?.keys; )?.keys;
return keys?.publicKey; return keys?.publicKey;
} }
async setPublicKey(value: ArrayBuffer, options?: StorageOptions): Promise<void> { async setPublicKey(value: Uint8Array, options?: StorageOptions): Promise<void> {
const account = await this.getAccount( const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultInMemoryOptions()) this.reconcileOptions(options, await this.defaultInMemoryOptions())
); );

View File

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

View File

@@ -20,11 +20,11 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
} }
async pbkdf2( async pbkdf2(
password: string | ArrayBuffer, password: string | Uint8Array,
salt: string | ArrayBuffer, salt: string | Uint8Array,
algorithm: "sha256" | "sha512", algorithm: "sha256" | "sha512",
iterations: number iterations: number
): Promise<ArrayBuffer> { ): Promise<Uint8Array> {
const wcLen = algorithm === "sha256" ? 256 : 512; const wcLen = algorithm === "sha256" ? 256 : 512;
const passwordBuf = this.toBuf(password); const passwordBuf = this.toBuf(password);
const saltBuf = this.toBuf(salt); const saltBuf = this.toBuf(salt);
@@ -43,16 +43,17 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
false, false,
["deriveBits"] ["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( async argon2(
password: string | ArrayBuffer, password: string | Uint8Array,
salt: string | ArrayBuffer, salt: string | Uint8Array,
iterations: number, iterations: number,
memory: number, memory: number,
parallelism: number parallelism: number
): Promise<ArrayBuffer> { ): Promise<Uint8Array> {
if (!this.wasmSupported) { if (!this.wasmSupported) {
throw "Webassembly support is required for the Argon2 KDF feature."; throw "Webassembly support is required for the Argon2 KDF feature.";
} }
@@ -74,12 +75,12 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
} }
async hkdf( async hkdf(
ikm: ArrayBuffer, ikm: Uint8Array,
salt: string | ArrayBuffer, salt: string | Uint8Array,
info: string | ArrayBuffer, info: string | Uint8Array,
outputByteSize: number, outputByteSize: number,
algorithm: "sha256" | "sha512" algorithm: "sha256" | "sha512"
): Promise<ArrayBuffer> { ): Promise<Uint8Array> {
const saltBuf = this.toBuf(salt); const saltBuf = this.toBuf(salt);
const infoBuf = this.toBuf(info); 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, [ const impKey = await this.subtle.importKey("raw", ikm, { name: "HKDF" } as any, false, [
"deriveBits", "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 // ref: https://tools.ietf.org/html/rfc5869
async hkdfExpand( async hkdfExpand(
prk: ArrayBuffer, prk: Uint8Array,
info: string | ArrayBuffer, info: string | Uint8Array,
outputByteSize: number, outputByteSize: number,
algorithm: "sha256" | "sha512" algorithm: "sha256" | "sha512"
): Promise<ArrayBuffer> { ): Promise<Uint8Array> {
const hashLen = algorithm === "sha256" ? 32 : 64; const hashLen = algorithm === "sha256" ? 32 : 64;
if (outputByteSize > 255 * hashLen) { if (outputByteSize > 255 * hashLen) {
throw new Error("outputByteSize is too large."); throw new Error("outputByteSize is too large.");
@@ -122,49 +124,54 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
t.set(previousT); t.set(previousT);
t.set(infoArr, previousT.length); t.set(infoArr, previousT.length);
t.set([i + 1], t.length - 1); 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); okm.set(previousT, runningOkmLength);
runningOkmLength += previousT.length; runningOkmLength += previousT.length;
if (runningOkmLength >= outputByteSize) { if (runningOkmLength >= outputByteSize) {
break; break;
} }
} }
return okm.slice(0, outputByteSize).buffer; return okm.slice(0, outputByteSize);
} }
async hash( async hash(
value: string | ArrayBuffer, value: string | Uint8Array,
algorithm: "sha1" | "sha256" | "sha512" | "md5" algorithm: "sha1" | "sha256" | "sha512" | "md5"
): Promise<ArrayBuffer> { ): Promise<Uint8Array> {
if (algorithm === "md5") { if (algorithm === "md5") {
const md = algorithm === "md5" ? forge.md.md5.create() : forge.md.sha1.create(); const md = algorithm === "md5" ? forge.md.md5.create() : forge.md.sha1.create();
const valueBytes = this.toByteString(value); const valueBytes = this.toByteString(value);
md.update(valueBytes, "raw"); md.update(valueBytes, "raw");
return Utils.fromByteStringToArray(md.digest().data).buffer; return Utils.fromByteStringToArray(md.digest().data);
} }
const valueBuf = this.toBuf(value); 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( async hmac(
value: ArrayBuffer, value: Uint8Array,
key: ArrayBuffer, key: Uint8Array,
algorithm: "sha1" | "sha256" | "sha512" algorithm: "sha1" | "sha256" | "sha512"
): Promise<ArrayBuffer> { ): Promise<Uint8Array> {
const signingAlgorithm = { const signingAlgorithm = {
name: "HMAC", name: "HMAC",
hash: { name: this.toWebCryptoAlgorithm(algorithm) }, hash: { name: this.toWebCryptoAlgorithm(algorithm) },
}; };
const impKey = await this.subtle.importKey("raw", key, signingAlgorithm, false, ["sign"]); 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). // 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://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 // 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 macKey = await this.randomBytes(32);
const signingAlgorithm = { const signingAlgorithm = {
name: "HMAC", name: "HMAC",
@@ -219,11 +226,12 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
return equals; 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, [ const impKey = await this.subtle.importKey("raw", key, { name: "AES-CBC" } as any, false, [
"encrypt", "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( aesDecryptFastParameters(
@@ -275,18 +283,19 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
return Promise.resolve(val); 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, [ const impKey = await this.subtle.importKey("raw", key, { name: "AES-CBC" } as any, false, [
"decrypt", "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( async rsaEncrypt(
data: ArrayBuffer, data: Uint8Array,
publicKey: ArrayBuffer, publicKey: Uint8Array,
algorithm: "sha1" | "sha256" algorithm: "sha1" | "sha256"
): Promise<ArrayBuffer> { ): Promise<Uint8Array> {
// Note: Edge browser requires that we specify name and hash for both key import and decrypt. // Note: Edge browser requires that we specify name and hash for both key import and decrypt.
// We cannot use the proper types here. // We cannot use the proper types here.
const rsaParams = { const rsaParams = {
@@ -294,14 +303,15 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
hash: { name: this.toWebCryptoAlgorithm(algorithm) }, hash: { name: this.toWebCryptoAlgorithm(algorithm) },
}; };
const impKey = await this.subtle.importKey("spki", publicKey, rsaParams, false, ["encrypt"]); 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( async rsaDecrypt(
data: ArrayBuffer, data: Uint8Array,
privateKey: ArrayBuffer, privateKey: Uint8Array,
algorithm: "sha1" | "sha256" algorithm: "sha1" | "sha256"
): Promise<ArrayBuffer> { ): Promise<Uint8Array> {
// Note: Edge browser requires that we specify name and hash for both key import and decrypt. // Note: Edge browser requires that we specify name and hash for both key import and decrypt.
// We cannot use the proper types here. // We cannot use the proper types here.
const rsaParams = { const rsaParams = {
@@ -309,10 +319,11 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
hash: { name: this.toWebCryptoAlgorithm(algorithm) }, hash: { name: this.toWebCryptoAlgorithm(algorithm) },
}; };
const impKey = await this.subtle.importKey("pkcs8", privateKey, rsaParams, false, ["decrypt"]); 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 = { const rsaParams = {
name: "RSA-OAEP", name: "RSA-OAEP",
// Have to specify some algorithm // Have to specify some algorithm
@@ -332,10 +343,11 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
const impPublicKey = await this.subtle.importKey("jwk", jwkPublicKeyParams, rsaParams, true, [ const impPublicKey = await this.subtle.importKey("jwk", jwkPublicKeyParams, rsaParams, true, [
"encrypt", "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 = { const rsaParams = {
name: "RSA-OAEP", name: "RSA-OAEP",
modulusLength: length, modulusLength: length,
@@ -349,26 +361,26 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
])) as CryptoKeyPair; ])) as CryptoKeyPair;
const publicKey = await this.subtle.exportKey("spki", keyPair.publicKey); const publicKey = await this.subtle.exportKey("spki", keyPair.publicKey);
const privateKey = await this.subtle.exportKey("pkcs8", keyPair.privateKey); const privateKey = await this.subtle.exportKey("pkcs8", keyPair.privateKey);
return [publicKey, privateKey]; return [new Uint8Array(publicKey), new Uint8Array(privateKey)];
} }
randomBytes(length: number): Promise<CsprngArray> { randomBytes(length: number): Promise<CsprngArray> {
const arr = new Uint8Array(length); const arr = new Uint8Array(length);
this.crypto.getRandomValues(arr); this.crypto.getRandomValues(arr);
return Promise.resolve(arr.buffer as CsprngArray); return Promise.resolve(arr as CsprngArray);
} }
private toBuf(value: string | ArrayBuffer): ArrayBuffer { private toBuf(value: string | Uint8Array): Uint8Array {
let buf: ArrayBuffer; let buf: Uint8Array;
if (typeof value === "string") { if (typeof value === "string") {
buf = Utils.fromUtf8ToArray(value).buffer; buf = Utils.fromUtf8ToArray(value);
} else { } else {
buf = value; buf = value;
} }
return buf; return buf;
} }
private toByteString(value: string | ArrayBuffer): string { private toByteString(value: string | Uint8Array): string {
let bytes: string; let bytes: string;
if (typeof value === "string") { if (typeof value === "string") {
bytes = forge.util.encodeUtf8(value); bytes = forge.util.encodeUtf8(value);

View File

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

View File

@@ -162,7 +162,7 @@ export class TotpService implements TotpServiceAbstraction {
timeBytes: Uint8Array, timeBytes: Uint8Array,
alg: "sha1" | "sha256" | "sha512" 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); return new Uint8Array(signature);
} }
} }

View File

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

View File

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

View File

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

View File

@@ -59,7 +59,7 @@ describe("Cipher Service", () => {
it("attachments upload encrypted file contents", async () => { it("attachments upload encrypted file contents", async () => {
const fileName = "filename"; const fileName = "filename";
const fileData = new Uint8Array(10).buffer; 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); 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 encFileName = await this.cryptoService.encrypt(filename, key);
const dataEncKey = await this.cryptoService.makeEncKey(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( const response = await this.cipherFileUploadService.upload(
cipher, cipher,

View File

@@ -12,16 +12,6 @@ expect.extend({
toEqualBuffer: toEqualBuffer, toEqualBuffer: toEqualBuffer,
}); });
interface CustomMatchers<R = unknown> { export interface CustomMatchers<R = unknown> {
toEqualBuffer(expected: Uint8Array | ArrayBuffer): R; 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", "extends": "../shared/tsconfig.libs",
"include": ["src", "spec"], "include": ["src", "spec", "./custom-matchers.d.ts"],
"exclude": ["node_modules", "dist"] "exclude": ["node_modules", "dist"]
} }

View File

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

View File

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