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:
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { SecretVerificationRequest } from "@bitwarden/common/models/request/secretVerificationRequest";
|
||||
|
||||
export abstract class AccountApiService {
|
||||
abstract deleteAccount(request: SecretVerificationRequest): Promise<void>;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { Verification } from "../../types/verification";
|
||||
|
||||
export abstract class AccountService {
|
||||
abstract delete(verification: Verification): Promise<any>;
|
||||
}
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -2,5 +2,5 @@ export enum OrganizationUserStatusType {
|
||||
Invited = 0,
|
||||
Accepted = 1,
|
||||
Confirmed = 2,
|
||||
Deactivated = -1,
|
||||
Revoked = -1,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
8
libs/common/src/interfaces/IEncrypted.ts
Normal file
8
libs/common/src/interfaces/IEncrypted.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { EncryptionType } from "../enums/encryptionType";
|
||||
|
||||
export interface IEncrypted {
|
||||
encryptionType?: EncryptionType;
|
||||
dataBytes: ArrayBuffer;
|
||||
macBytes: ArrayBuffer;
|
||||
ivBytes: ArrayBuffer;
|
||||
}
|
||||
@@ -4,4 +4,5 @@ export class EventData {
|
||||
type: EventType;
|
||||
cipherId: string;
|
||||
date: string;
|
||||
organizationId: string;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,4 +4,5 @@ export class EventRequest {
|
||||
type: EventType;
|
||||
cipherId: string;
|
||||
date: string;
|
||||
organizationId: string;
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
11
libs/common/src/services/account/account-api.service.ts
Normal file
11
libs/common/src/services/account/account-api.service.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
27
libs/common/src/services/account/account.service.ts
Normal file
27
libs/common/src/services/account/account.service.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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> {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user