1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-26 17:43:22 +00:00

merging master into feature branch

This commit is contained in:
CarleyDiaz-Bitwarden
2022-08-03 14:20:15 -04:00
parent a7eaa26c74
commit e287715251
371 changed files with 16725 additions and 22348 deletions

View File

@@ -1,7 +1,15 @@
import { EncString } from "@bitwarden/common/models/domain/encString";
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey";
import { IEncrypted } from "../interfaces/IEncrypted";
import { EncArrayBuffer } from "../models/domain/encArrayBuffer";
export abstract class AbstractEncryptService {
abstract encrypt(plainValue: string | ArrayBuffer, key: SymmetricCryptoKey): Promise<EncString>;
abstract decryptToUtf8(encString: EncString, key: SymmetricCryptoKey): Promise<string>;
abstract encryptToBytes: (
plainValue: ArrayBuffer,
key?: SymmetricCryptoKey
) => Promise<EncArrayBuffer>;
abstract decryptToUtf8: (encString: EncString, key: SymmetricCryptoKey) => Promise<string>;
abstract decryptToBytes: (encThing: IEncrypted, key: SymmetricCryptoKey) => Promise<ArrayBuffer>;
}

View File

@@ -0,0 +1,5 @@
import { SecretVerificationRequest } from "@bitwarden/common/models/request/secretVerificationRequest";
export abstract class AccountApiService {
abstract deleteAccount(request: SecretVerificationRequest): Promise<void>;
}

View File

@@ -0,0 +1,5 @@
import { Verification } from "../../types/verification";
export abstract class AccountService {
abstract delete(verification: Verification): Promise<any>;
}

View File

@@ -130,6 +130,7 @@ import {
OrganizationConnectionConfigApis,
OrganizationConnectionResponse,
} from "../models/response/organizationConnectionResponse";
import { OrganizationExportResponse } from "../models/response/organizationExportResponse";
import { OrganizationKeysResponse } from "../models/response/organizationKeysResponse";
import { OrganizationResponse } from "../models/response/organizationResponse";
import { OrganizationSponsorshipSyncStatusResponse } from "../models/response/organizationSponsorshipSyncStatusResponse";
@@ -208,7 +209,6 @@ export abstract class ApiService {
setPassword: (request: SetPasswordRequest) => Promise<any>;
postSetKeyConnectorKey: (request: SetKeyConnectorKeyRequest) => Promise<any>;
postSecurityStamp: (request: SecretVerificationRequest) => Promise<any>;
deleteAccount: (request: SecretVerificationRequest) => Promise<any>;
getAccountRevisionDate: () => Promise<number>;
postPasswordHint: (request: PasswordHintRequest) => Promise<any>;
postRegister: (request: RegisterRequest) => Promise<any>;
@@ -449,13 +449,13 @@ export abstract class ApiService {
organizationId: string,
request: OrganizationUserBulkRequest
) => Promise<ListResponse<OrganizationUserBulkResponse>>;
deactivateOrganizationUser: (organizationId: string, id: string) => Promise<any>;
deactivateManyOrganizationUsers: (
revokeOrganizationUser: (organizationId: string, id: string) => Promise<any>;
revokeManyOrganizationUsers: (
organizationId: string,
request: OrganizationUserBulkRequest
) => Promise<ListResponse<OrganizationUserBulkResponse>>;
activateOrganizationUser: (organizationId: string, id: string) => Promise<any>;
activateManyOrganizationUsers: (
restoreOrganizationUser: (organizationId: string, id: string) => Promise<any>;
restoreManyOrganizationUsers: (
organizationId: string,
request: OrganizationUserBulkRequest
) => Promise<ListResponse<OrganizationUserBulkResponse>>;
@@ -734,4 +734,5 @@ export abstract class ApiService {
request: KeyConnectorUserKeyRequest
) => Promise<void>;
getKeyConnectorAlive: (keyConnectorUrl: string) => Promise<void>;
getOrganizationExport: (organizationId: string) => Promise<OrganizationExportResponse>;
}

View File

@@ -80,7 +80,7 @@ export abstract class CryptoService {
rsaDecrypt: (encValue: string, privateKeyValue?: ArrayBuffer) => Promise<ArrayBuffer>;
decryptToBytes: (encString: EncString, key?: SymmetricCryptoKey) => Promise<ArrayBuffer>;
decryptToUtf8: (encString: EncString, key?: SymmetricCryptoKey) => Promise<string>;
decryptFromBytes: (encBuf: ArrayBuffer, key: SymmetricCryptoKey) => Promise<ArrayBuffer>;
decryptFromBytes: (encBuffer: EncArrayBuffer, key: SymmetricCryptoKey) => Promise<ArrayBuffer>;
randomNumber: (min: number, max: number) => Promise<number>;
validateKey: (key: SymmetricCryptoKey) => Promise<boolean>;
}

View File

@@ -1,7 +1,12 @@
import { EventType } from "../enums/eventType";
export abstract class EventService {
collect: (eventType: EventType, cipherId?: string, uploadImmediately?: boolean) => Promise<any>;
collect: (
eventType: EventType,
cipherId?: string,
uploadImmediately?: boolean,
organizationId?: string
) => Promise<any>;
uploadEvents: (userId?: string) => Promise<any>;
clearEvents: (userId?: string) => Promise<any>;
}

View File

@@ -48,12 +48,12 @@ export enum EventType {
OrganizationUser_AdminResetPassword = 1508,
OrganizationUser_ResetSsoLink = 1509,
OrganizationUser_FirstSsoLogin = 1510,
OrganizationUser_Deactivated = 1511,
OrganizationUser_Activated = 1512,
OrganizationUser_Revoked = 1511,
OrganizationUser_Restored = 1512,
Organization_Updated = 1600,
Organization_PurgedVault = 1601,
// Organization_ClientExportedVault = 1602,
Organization_ClientExportedVault = 1602,
Organization_VaultAccessed = 1603,
Organization_EnabledSso = 1604,
Organization_DisabledSso = 1605,

View File

@@ -2,5 +2,5 @@ export enum OrganizationUserStatusType {
Invited = 0,
Accepted = 1,
Confirmed = 2,
Deactivated = -1,
Revoked = -1,
}

View File

@@ -2,5 +2,5 @@ export enum ProviderUserStatusType {
Invited = 0,
Accepted = 1,
Confirmed = 2,
Deactivated = -1, // Not used, compile-time support only
Revoked = -1, // Not used, compile-time support only
}

View File

@@ -0,0 +1,8 @@
import { EncryptionType } from "../enums/encryptionType";
export interface IEncrypted {
encryptionType?: EncryptionType;
dataBytes: ArrayBuffer;
macBytes: ArrayBuffer;
ivBytes: ArrayBuffer;
}

View File

@@ -4,4 +4,5 @@ export class EventData {
type: EventType;
cipherId: string;
date: string;
organizationId: string;
}

View File

@@ -1,3 +1,73 @@
export class EncArrayBuffer {
constructor(public buffer: ArrayBuffer) {}
import { EncryptionType } from "@bitwarden/common/enums/encryptionType";
import { IEncrypted } from "@bitwarden/common/interfaces/IEncrypted";
import { Utils } from "@bitwarden/common/misc/utils";
const ENC_TYPE_LENGTH = 1;
const IV_LENGTH = 16;
const MAC_LENGTH = 32;
const MIN_DATA_LENGTH = 1;
export class EncArrayBuffer implements IEncrypted {
readonly encryptionType: EncryptionType = null;
readonly dataBytes: ArrayBuffer = null;
readonly ivBytes: ArrayBuffer = null;
readonly macBytes: ArrayBuffer = null;
constructor(readonly buffer: ArrayBuffer) {
const encBytes = new Uint8Array(buffer);
const encType = encBytes[0];
switch (encType) {
case EncryptionType.AesCbc128_HmacSha256_B64:
case EncryptionType.AesCbc256_HmacSha256_B64: {
const minimumLength = ENC_TYPE_LENGTH + IV_LENGTH + MAC_LENGTH + MIN_DATA_LENGTH;
if (encBytes.length < minimumLength) {
this.throwDecryptionError();
}
this.ivBytes = encBytes.slice(ENC_TYPE_LENGTH, ENC_TYPE_LENGTH + IV_LENGTH).buffer;
this.macBytes = encBytes.slice(
ENC_TYPE_LENGTH + IV_LENGTH,
ENC_TYPE_LENGTH + IV_LENGTH + MAC_LENGTH
).buffer;
this.dataBytes = encBytes.slice(ENC_TYPE_LENGTH + IV_LENGTH + MAC_LENGTH).buffer;
break;
}
case EncryptionType.AesCbc256_B64: {
const minimumLength = ENC_TYPE_LENGTH + IV_LENGTH + MIN_DATA_LENGTH;
if (encBytes.length < minimumLength) {
this.throwDecryptionError();
}
this.ivBytes = encBytes.slice(ENC_TYPE_LENGTH, ENC_TYPE_LENGTH + IV_LENGTH).buffer;
this.dataBytes = encBytes.slice(ENC_TYPE_LENGTH + IV_LENGTH).buffer;
break;
}
default:
this.throwDecryptionError();
}
this.encryptionType = encType;
}
private throwDecryptionError() {
throw new Error(
"Error parsing encrypted ArrayBuffer: data is corrupted or has an invalid format."
);
}
static async fromResponse(response: {
arrayBuffer: () => Promise<ArrayBuffer>;
}): Promise<EncArrayBuffer> {
const buffer = await response.arrayBuffer();
if (buffer == null) {
throw new Error("Cannot create EncArrayBuffer from Response - Response is empty");
}
return new EncArrayBuffer(buffer);
}
static fromB64(b64: string) {
const buffer = Utils.fromB64ToArray(b64).buffer;
return new EncArrayBuffer(buffer);
}
}

View File

@@ -1,10 +1,12 @@
import { IEncrypted } from "@bitwarden/common/interfaces/IEncrypted";
import { CryptoService } from "../../abstractions/crypto.service";
import { EncryptionType } from "../../enums/encryptionType";
import { Utils } from "../../misc/utils";
import { SymmetricCryptoKey } from "./symmetricCryptoKey";
export class EncString {
export class EncString implements IEncrypted {
encryptedString?: string;
encryptionType?: EncryptionType;
decryptedValue?: string;
@@ -119,4 +121,16 @@ export class EncString {
}
return this.decryptedValue;
}
get ivBytes(): ArrayBuffer {
return this.iv == null ? null : Utils.fromB64ToArray(this.iv).buffer;
}
get macBytes(): ArrayBuffer {
return this.mac == null ? null : Utils.fromB64ToArray(this.mac).buffer;
}
get dataBytes(): ArrayBuffer {
return this.data == null ? null : Utils.fromB64ToArray(this.data).buffer;
}
}

View File

@@ -4,4 +4,5 @@ export class EventRequest {
type: EventType;
cipherId: string;
date: string;
organizationId: string;
}

View File

@@ -0,0 +1,15 @@
import { BaseResponse } from "./baseResponse";
import { CipherResponse } from "./cipherResponse";
import { CollectionResponse } from "./collectionResponse";
import { ListResponse } from "./listResponse";
export class OrganizationExportResponse extends BaseResponse {
collections: ListResponse<CollectionResponse>;
ciphers: ListResponse<CipherResponse>;
constructor(response: any) {
super(response);
this.collections = this.getResponseProperty("Collections");
this.ciphers = this.getResponseProperty("Ciphers");
}
}

View File

@@ -8,6 +8,9 @@ export class ProviderOrganizationResponse extends BaseResponse {
settings: string;
creationDate: string;
revisionDate: string;
userCount: number;
seats?: number;
plan?: string;
constructor(response: any) {
super(response);
@@ -18,6 +21,9 @@ export class ProviderOrganizationResponse extends BaseResponse {
this.settings = this.getResponseProperty("Settings");
this.creationDate = this.getResponseProperty("CreationDate");
this.revisionDate = this.getResponseProperty("RevisionDate");
this.userCount = this.getResponseProperty("UserCount");
this.seats = this.getResponseProperty("Seats");
this.plan = this.getResponseProperty("Plan");
}
}

View File

@@ -0,0 +1,11 @@
import { AccountApiService as AccountApiServiceAbstraction } from "@bitwarden/common/abstractions/account/account-api.service.abstraction";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { SecretVerificationRequest } from "@bitwarden/common/models/request/secretVerificationRequest";
export class AccountApiService implements AccountApiServiceAbstraction {
constructor(private apiService: ApiService) {}
deleteAccount(request: SecretVerificationRequest): Promise<void> {
return this.apiService.send("DELETE", "/accounts", request, true, false);
}
}

View File

@@ -0,0 +1,27 @@
import { AccountApiService } from "@bitwarden/common/abstractions/account/account-api.service.abstraction";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
import { UserVerificationService } from "@bitwarden/common/abstractions/userVerification.service";
import { AccountService as AccountServiceAbstraction } from "../../abstractions/account/account.service.abstraction";
import { Verification } from "../../types/verification";
export class AccountService implements AccountServiceAbstraction {
constructor(
private accountApiService: AccountApiService,
private userVerificationService: UserVerificationService,
private messagingService: MessagingService,
private logService: LogService
) {}
async delete(verification: Verification): Promise<any> {
try {
const verificationRequest = await this.userVerificationService.buildRequest(verification);
await this.accountApiService.deleteAccount(verificationRequest);
this.messagingService.send("logout");
} catch (e) {
this.logService.error(e);
throw e;
}
}
}

View File

@@ -139,6 +139,7 @@ import {
OrganizationConnectionConfigApis,
OrganizationConnectionResponse,
} from "../models/response/organizationConnectionResponse";
import { OrganizationExportResponse } from "../models/response/organizationExportResponse";
import { OrganizationKeysResponse } from "../models/response/organizationKeysResponse";
import { OrganizationResponse } from "../models/response/organizationResponse";
import { OrganizationSponsorshipSyncStatusResponse } from "../models/response/organizationSponsorshipSyncStatusResponse";
@@ -349,10 +350,6 @@ export class ApiService implements ApiServiceAbstraction {
return this.send("POST", "/accounts/security-stamp", request, true, false);
}
deleteAccount(request: SecretVerificationRequest): Promise<any> {
return this.send("DELETE", "/accounts", request, true, false);
}
async getAccountRevisionDate(): Promise<number> {
const r = await this.send("GET", "/accounts/revision-date", null, true, true);
return r as number;
@@ -1344,23 +1341,23 @@ export class ApiService implements ApiServiceAbstraction {
return new ListResponse(r, OrganizationUserBulkResponse);
}
deactivateOrganizationUser(organizationId: string, id: string): Promise<any> {
revokeOrganizationUser(organizationId: string, id: string): Promise<any> {
return this.send(
"PUT",
"/organizations/" + organizationId + "/users/" + id + "/deactivate",
"/organizations/" + organizationId + "/users/" + id + "/revoke",
null,
true,
false
);
}
async deactivateManyOrganizationUsers(
async revokeManyOrganizationUsers(
organizationId: string,
request: OrganizationUserBulkRequest
): Promise<ListResponse<OrganizationUserBulkResponse>> {
const r = await this.send(
"PUT",
"/organizations/" + organizationId + "/users/deactivate",
"/organizations/" + organizationId + "/users/revoke",
request,
true,
true
@@ -1368,23 +1365,23 @@ export class ApiService implements ApiServiceAbstraction {
return new ListResponse(r, OrganizationUserBulkResponse);
}
activateOrganizationUser(organizationId: string, id: string): Promise<any> {
restoreOrganizationUser(organizationId: string, id: string): Promise<any> {
return this.send(
"PUT",
"/organizations/" + organizationId + "/users/" + id + "/activate",
"/organizations/" + organizationId + "/users/" + id + "/restore",
null,
true,
false
);
}
async activateManyOrganizationUsers(
async restoreManyOrganizationUsers(
organizationId: string,
request: OrganizationUserBulkRequest
): Promise<ListResponse<OrganizationUserBulkResponse>> {
const r = await this.send(
"PUT",
"/organizations/" + organizationId + "/users/activate",
"/organizations/" + organizationId + "/users/restore",
request,
true,
true
@@ -2323,6 +2320,17 @@ export class ApiService implements ApiServiceAbstraction {
}
}
async getOrganizationExport(organizationId: string): Promise<OrganizationExportResponse> {
const r = await this.send(
"GET",
"/organizations/" + organizationId + "/export",
null,
true,
true
);
return new OrganizationExportResponse(r);
}
// Helpers
async getActiveBearerToken(): Promise<string> {

View File

@@ -1058,8 +1058,8 @@ export class CipherService implements CipherServiceAbstraction {
throw Error("Failed to download attachment: " + attachmentResponse.status.toString());
}
const buf = await attachmentResponse.arrayBuffer();
const decBuf = await this.cryptoService.decryptFromBytes(buf, null);
const encBuf = await EncArrayBuffer.fromResponse(attachmentResponse);
const decBuf = await this.cryptoService.decryptFromBytes(encBuf, null);
const key = await this.cryptoService.getOrgKey(organizationId);
const encFileName = await this.cryptoService.encrypt(attachmentView.fileName, key);

View File

@@ -3,11 +3,6 @@ import { CryptoService } from "../abstractions/crypto.service";
export class ContainerService {
constructor(private cryptoService: CryptoService) {}
// deprecated, use attachToGlobal instead
attachToWindow(win: any) {
this.attachToGlobal(win);
}
attachToGlobal(global: any) {
if (!global.bitwardenContainerService) {
global.bitwardenContainerService = this;

View File

@@ -16,7 +16,6 @@ import { EEFLongWordList } from "../misc/wordlist";
import { EncryptedOrganizationKeyData } from "../models/data/encryptedOrganizationKeyData";
import { EncArrayBuffer } from "../models/domain/encArrayBuffer";
import { EncString } from "../models/domain/encString";
import { EncryptedObject } from "../models/domain/encryptedObject";
import { BaseEncryptedOrganizationKey } from "../models/domain/encryptedOrganizationKey";
import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey";
import { ProfileOrganizationResponse } from "../models/response/profileOrganizationResponse";
@@ -513,33 +512,14 @@ export class CryptoService implements CryptoServiceAbstraction {
return this.buildEncKey(key, encKey.key);
}
/**
* @deprecated June 22 2022: This method has been moved to encryptService.
* All callers should use this service to grab the relevant key and use encryptService for encryption instead.
* This method will be removed once all existing code has been refactored to use encryptService.
*/
async encrypt(plainValue: string | ArrayBuffer, key?: SymmetricCryptoKey): Promise<EncString> {
key = await this.getKeyForEncryption(key);
return await this.encryptService.encrypt(plainValue, key);
}
async encryptToBytes(plainValue: ArrayBuffer, key?: SymmetricCryptoKey): Promise<EncArrayBuffer> {
const encValue = await this.aesEncrypt(plainValue, key);
let macLen = 0;
if (encValue.mac != null) {
macLen = encValue.mac.byteLength;
}
const encBytes = new Uint8Array(1 + encValue.iv.byteLength + macLen + encValue.data.byteLength);
encBytes.set([encValue.key.encType]);
encBytes.set(new Uint8Array(encValue.iv), 1);
if (encValue.mac != null) {
encBytes.set(new Uint8Array(encValue.mac), 1 + encValue.iv.byteLength);
}
encBytes.set(new Uint8Array(encValue.data), 1 + encValue.iv.byteLength + macLen);
return new EncArrayBuffer(encBytes.buffer);
key = await this.getKeyForEncryption(key);
return this.encryptService.encryptToBytes(plainValue, key);
}
async rsaEncrypt(data: ArrayBuffer, publicKey?: ArrayBuffer): Promise<EncString> {
@@ -608,15 +588,9 @@ export class CryptoService implements CryptoServiceAbstraction {
}
async decryptToBytes(encString: EncString, key?: SymmetricCryptoKey): Promise<ArrayBuffer> {
const iv = Utils.fromB64ToArray(encString.iv).buffer;
const data = Utils.fromB64ToArray(encString.data).buffer;
const mac = encString.mac ? Utils.fromB64ToArray(encString.mac).buffer : null;
const decipher = await this.aesDecryptToBytes(encString.encryptionType, data, iv, mac, key);
if (decipher == null) {
return null;
}
return decipher;
const keyForEnc = await this.getKeyForEncryption(key);
const theKey = await this.resolveLegacyKey(encString.encryptionType, keyForEnc);
return this.encryptService.decryptToBytes(encString, theKey);
}
async decryptToUtf8(encString: EncString, key?: SymmetricCryptoKey): Promise<string> {
@@ -625,49 +599,15 @@ export class CryptoService implements CryptoServiceAbstraction {
return await this.encryptService.decryptToUtf8(encString, key);
}
async decryptFromBytes(encBuf: ArrayBuffer, key: SymmetricCryptoKey): Promise<ArrayBuffer> {
if (encBuf == null) {
throw new Error("no encBuf.");
async decryptFromBytes(encBuffer: EncArrayBuffer, key: SymmetricCryptoKey): Promise<ArrayBuffer> {
if (encBuffer == null) {
throw new Error("No buffer provided for decryption.");
}
const encBytes = new Uint8Array(encBuf);
const encType = encBytes[0];
let ctBytes: Uint8Array = null;
let ivBytes: Uint8Array = null;
let macBytes: Uint8Array = null;
key = await this.getKeyForEncryption(key);
key = await this.resolveLegacyKey(encBuffer.encryptionType, key);
switch (encType) {
case EncryptionType.AesCbc128_HmacSha256_B64:
case EncryptionType.AesCbc256_HmacSha256_B64:
if (encBytes.length <= 49) {
// 1 + 16 + 32 + ctLength
return null;
}
ivBytes = encBytes.slice(1, 17);
macBytes = encBytes.slice(17, 49);
ctBytes = encBytes.slice(49);
break;
case EncryptionType.AesCbc256_B64:
if (encBytes.length <= 17) {
// 1 + 16 + ctLength
return null;
}
ivBytes = encBytes.slice(1, 17);
ctBytes = encBytes.slice(17);
break;
default:
return null;
}
return await this.aesDecryptToBytes(
encType,
ctBytes.buffer,
ivBytes.buffer,
macBytes != null ? macBytes.buffer : null,
key
);
return this.encryptService.decryptToBytes(encBuffer, key);
}
// EFForg/OpenWireless
@@ -722,7 +662,8 @@ export class CryptoService implements CryptoServiceAbstraction {
return true;
}
// Helpers
// ---HELPERS---
protected async storeKey(key: SymmetricCryptoKey, userId?: string) {
if (await this.shouldStoreKey(KeySuffixOptions.Auto, userId)) {
await this.stateService.setCryptoMasterKeyAuto(key.keyB64, { userId: userId });
@@ -752,67 +693,6 @@ export class CryptoService implements CryptoServiceAbstraction {
: await this.stateService.getCryptoMasterKeyBiometric({ userId: userId });
}
/**
* @deprecated June 22 2022: This method has been moved to encryptService.
* All callers should use encryptService instead. This method will be removed once all existing code has been refactored to use encryptService.
*/
private async aesEncrypt(data: ArrayBuffer, key: SymmetricCryptoKey): Promise<EncryptedObject> {
const obj = new EncryptedObject();
obj.key = await this.getKeyForEncryption(key);
obj.iv = await this.cryptoFunctionService.randomBytes(16);
obj.data = await this.cryptoFunctionService.aesEncrypt(data, obj.iv, obj.key.encKey);
if (obj.key.macKey != null) {
const macData = new Uint8Array(obj.iv.byteLength + obj.data.byteLength);
macData.set(new Uint8Array(obj.iv), 0);
macData.set(new Uint8Array(obj.data), obj.iv.byteLength);
obj.mac = await this.cryptoFunctionService.hmac(macData.buffer, obj.key.macKey, "sha256");
}
return obj;
}
private async aesDecryptToBytes(
encType: EncryptionType,
data: ArrayBuffer,
iv: ArrayBuffer,
mac: ArrayBuffer,
key: SymmetricCryptoKey
): Promise<ArrayBuffer> {
const keyForEnc = await this.getKeyForEncryption(key);
const theKey = await this.resolveLegacyKey(encType, keyForEnc);
if (theKey.macKey != null && mac == null) {
return null;
}
if (theKey.encType !== encType) {
return null;
}
if (theKey.macKey != null && mac != null) {
const macData = new Uint8Array(iv.byteLength + data.byteLength);
macData.set(new Uint8Array(iv), 0);
macData.set(new Uint8Array(data), iv.byteLength);
const computedMac = await this.cryptoFunctionService.hmac(
macData.buffer,
theKey.macKey,
"sha256"
);
if (computedMac === null) {
return null;
}
const macsMatch = await this.cryptoFunctionService.compare(mac, computedMac);
if (!macsMatch) {
this.logService.error("mac failed.");
return null;
}
}
return await this.cryptoFunctionService.aesDecrypt(data, iv, theKey.encKey);
}
private async getKeyForEncryption(key?: SymmetricCryptoKey): Promise<SymmetricCryptoKey> {
if (key != null) {
return key;

View File

@@ -6,6 +6,8 @@ import { EncryptedObject } from "@bitwarden/common/models/domain/encryptedObject
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey";
import { AbstractEncryptService } from "../abstractions/abstractEncrypt.service";
import { IEncrypted } from "../interfaces/IEncrypted";
import { EncArrayBuffer } from "../models/domain/encArrayBuffer";
export class EncryptService implements AbstractEncryptService {
constructor(
@@ -16,7 +18,7 @@ export class EncryptService implements AbstractEncryptService {
async encrypt(plainValue: string | ArrayBuffer, key: SymmetricCryptoKey): Promise<EncString> {
if (key == null) {
throw new Error("no encryption key provided.");
throw new Error("No encryption key provided.");
}
if (plainValue == null) {
@@ -37,8 +39,34 @@ export class EncryptService implements AbstractEncryptService {
return new EncString(encObj.key.encType, data, iv, mac);
}
async encryptToBytes(plainValue: ArrayBuffer, key: SymmetricCryptoKey): Promise<EncArrayBuffer> {
if (key == null) {
throw new Error("No encryption key provided.");
}
const encValue = await this.aesEncrypt(plainValue, key);
let macLen = 0;
if (encValue.mac != null) {
macLen = encValue.mac.byteLength;
}
const encBytes = new Uint8Array(1 + encValue.iv.byteLength + macLen + encValue.data.byteLength);
encBytes.set([encValue.key.encType]);
encBytes.set(new Uint8Array(encValue.iv), 1);
if (encValue.mac != null) {
encBytes.set(new Uint8Array(encValue.mac), 1 + encValue.iv.byteLength);
}
encBytes.set(new Uint8Array(encValue.data), 1 + encValue.iv.byteLength + macLen);
return new EncArrayBuffer(encBytes.buffer);
}
async decryptToUtf8(encString: EncString, key: SymmetricCryptoKey): Promise<string> {
if (key?.macKey != null && encString?.mac == null) {
if (key == null) {
throw new Error("No encryption key provided.");
}
if (key.macKey != null && encString?.mac == null) {
this.logService.error("mac required.");
return null;
}
@@ -70,6 +98,52 @@ export class EncryptService implements AbstractEncryptService {
return this.cryptoFunctionService.aesDecryptFast(fastParams);
}
async decryptToBytes(encThing: IEncrypted, key: SymmetricCryptoKey): Promise<ArrayBuffer> {
if (key == null) {
throw new Error("No encryption key provided.");
}
if (encThing == null) {
throw new Error("Nothing provided for decryption.");
}
if (key.macKey != null && encThing.macBytes == null) {
return null;
}
if (key.encType !== encThing.encryptionType) {
return null;
}
if (key.macKey != null && encThing.macBytes != null) {
const macData = new Uint8Array(encThing.ivBytes.byteLength + encThing.dataBytes.byteLength);
macData.set(new Uint8Array(encThing.ivBytes), 0);
macData.set(new Uint8Array(encThing.dataBytes), encThing.ivBytes.byteLength);
const computedMac = await this.cryptoFunctionService.hmac(
macData.buffer,
key.macKey,
"sha256"
);
if (computedMac === null) {
return null;
}
const macsMatch = await this.cryptoFunctionService.compare(encThing.macBytes, computedMac);
if (!macsMatch) {
this.logMacFailed("mac failed.");
return null;
}
}
const result = await this.cryptoFunctionService.aesDecrypt(
encThing.dataBytes,
encThing.ivBytes,
key.encKey
);
return result ?? null;
}
private async aesEncrypt(data: ArrayBuffer, key: SymmetricCryptoKey): Promise<EncryptedObject> {
const obj = new EncryptedObject();
obj.key = key;

View File

@@ -34,7 +34,8 @@ export class EventService implements EventServiceAbstraction {
async collect(
eventType: EventType,
cipherId: string = null,
uploadImmediately = false
uploadImmediately = false,
organizationId: string = null
): Promise<any> {
const authed = await this.stateService.getIsAuthenticated();
if (!authed) {
@@ -54,6 +55,11 @@ export class EventService implements EventServiceAbstraction {
return;
}
}
if (organizationId != null) {
if (!orgIds.has(organizationId)) {
return;
}
}
let eventCollection = await this.stateService.getEventCollection();
if (eventCollection == null) {
eventCollection = [];
@@ -62,6 +68,7 @@ export class EventService implements EventServiceAbstraction {
event.type = eventType;
event.cipherId = cipherId;
event.date = new Date().toISOString();
event.organizationId = organizationId;
eventCollection.push(event);
await this.stateService.setEventCollection(eventCollection);
if (uploadImmediately) {
@@ -83,6 +90,7 @@ export class EventService implements EventServiceAbstraction {
req.type = e.type;
req.cipherId = e.cipherId;
req.date = e.date;
req.organizationId = e.organizationId;
return req;
});
try {

View File

@@ -245,38 +245,41 @@ export class ExportService implements ExportServiceAbstraction {
const promises = [];
promises.push(
this.apiService.getCollections(organizationId).then((collections) => {
const collectionPromises: any = [];
if (collections != null && collections.data != null && collections.data.length > 0) {
collections.data.forEach((c) => {
const collection = new Collection(new CollectionData(c as CollectionDetailsResponse));
collectionPromises.push(
collection.decrypt().then((decCol) => {
decCollections.push(decCol);
})
);
});
}
return Promise.all(collectionPromises);
})
);
promises.push(
this.apiService.getCiphersOrganization(organizationId).then((ciphers) => {
const cipherPromises: any = [];
if (ciphers != null && ciphers.data != null && ciphers.data.length > 0) {
ciphers.data
.filter((c) => c.deletedDate === null)
.forEach((c) => {
const cipher = new Cipher(new CipherData(c));
cipherPromises.push(
cipher.decrypt().then((decCipher) => {
decCiphers.push(decCipher);
this.apiService.getOrganizationExport(organizationId).then((exportData) => {
const exportPromises: any = [];
if (exportData != null) {
if (
exportData.collections != null &&
exportData.collections.data != null &&
exportData.collections.data.length > 0
) {
exportData.collections.data.forEach((c) => {
const collection = new Collection(new CollectionData(c as CollectionDetailsResponse));
exportPromises.push(
collection.decrypt().then((decCol) => {
decCollections.push(decCol);
})
);
});
}
if (
exportData.ciphers != null &&
exportData.ciphers.data != null &&
exportData.ciphers.data.length > 0
) {
exportData.ciphers.data
.filter((c) => c.deletedDate === null)
.forEach((c) => {
const cipher = new Cipher(new CipherData(c));
exportPromises.push(
cipher.decrypt().then((decCipher) => {
decCiphers.push(decCipher);
})
);
});
}
}
return Promise.all(cipherPromises);
return Promise.all(exportPromises);
})
);

View File

@@ -1,4 +1,4 @@
import { FormGroup, ValidationErrors } from "@angular/forms";
import { UntypedFormGroup, ValidationErrors } from "@angular/forms";
import {
FormGroupControls,
@@ -11,7 +11,7 @@ export class FormValidationErrorsService implements FormValidationErrorsAbstract
let errors: AllValidationErrors[] = [];
Object.keys(controls).forEach((key) => {
const control = controls[key];
if (control instanceof FormGroup) {
if (control instanceof UntypedFormGroup) {
errors = errors.concat(this.getFormValidationErrors(control.controls));
}

View File

@@ -380,6 +380,7 @@ export class PasswordGenerationService implements PasswordGenerationServiceAbstr
}
const newHistory = await this.encryptHistory(currentHistory);
await this.stateService.setDecryptedPasswordGenerationHistory(currentHistory);
return await this.stateService.setEncryptedPasswordGenerationHistory(newHistory);
}

View File

@@ -2674,10 +2674,9 @@ export class StateService<
protected async clearDecryptedDataForActiveUser(): Promise<void> {
await this.updateState(async (state) => {
const userId = state?.activeUserId;
if (userId == null || state?.accounts[userId]?.data == null) {
return;
if (userId != null && state?.accounts[userId]?.data != null) {
state.accounts[userId].data = new AccountData();
}
state.accounts[userId].data = new AccountData();
return state;
});
@@ -2756,6 +2755,9 @@ export class StateService<
) {
await this.state().then(async (state) => {
const updatedState = await stateUpdater(state);
if (updatedState == null) {
throw new Error("Attempted to update state to null value");
}
await this.setState(updatedState);
});