1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-19 01:33:33 +00:00

Add support for Emergency Access (#204)

* Add support for Emergency Access

* Resolve review comments
This commit is contained in:
Oscar Hinton
2020-12-22 16:53:48 +01:00
committed by GitHub
parent 12321e53b9
commit 573eea66ee
22 changed files with 358 additions and 117 deletions

View File

@@ -34,10 +34,10 @@ export class Attachment extends Domain {
}, alreadyEncrypted, ['id', 'url', 'sizeName']);
}
async decrypt(orgId: string): Promise<AttachmentView> {
async decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise<AttachmentView> {
const view = await this.decryptObj(new AttachmentView(this), {
fileName: null,
}, orgId);
}, orgId, encKey);
if (this.key != null) {
let cryptoService: CryptoService;
@@ -50,7 +50,7 @@ export class Attachment extends Domain {
try {
const orgKey = await cryptoService.getOrgKey(orgId);
const decValue = await cryptoService.decryptToBytes(this.key, orgKey);
const decValue = await cryptoService.decryptToBytes(this.key, orgKey ?? encKey);
view.key = new SymmetricCryptoKey(decValue);
} catch (e) {
// TODO: error?

View File

@@ -4,6 +4,7 @@ import { CipherString } from './cipherString';
import Domain from './domainBase';
import { CardView } from '../view/cardView';
import { SymmetricCryptoKey } from './symmetricCryptoKey';
export class Card extends Domain {
cardholderName: CipherString;
@@ -29,7 +30,7 @@ export class Card extends Domain {
}, alreadyEncrypted, []);
}
decrypt(orgId: string): Promise<CardView> {
decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise<CardView> {
return this.decryptObj(new CardView(this), {
cardholderName: null,
brand: null,
@@ -37,7 +38,7 @@ export class Card extends Domain {
expMonth: null,
expYear: null,
code: null,
}, orgId);
}, orgId, encKey);
}
toCardData(): CardData {

View File

@@ -13,6 +13,7 @@ import { Identity } from './identity';
import { Login } from './login';
import { Password } from './password';
import { SecureNote } from './secureNote';
import { SymmetricCryptoKey } from './symmetricCryptoKey';
export class Cipher extends Domain {
id: string;
@@ -102,26 +103,26 @@ export class Cipher extends Domain {
}
}
async decrypt(): Promise<CipherView> {
async decrypt(encKey?: SymmetricCryptoKey): Promise<CipherView> {
const model = new CipherView(this);
await this.decryptObj(model, {
name: null,
notes: null,
}, this.organizationId);
}, this.organizationId, encKey);
switch (this.type) {
case CipherType.Login:
model.login = await this.login.decrypt(this.organizationId);
model.login = await this.login.decrypt(this.organizationId, encKey);
break;
case CipherType.SecureNote:
model.secureNote = await this.secureNote.decrypt(this.organizationId);
model.secureNote = await this.secureNote.decrypt(this.organizationId, encKey);
break;
case CipherType.Card:
model.card = await this.card.decrypt(this.organizationId);
model.card = await this.card.decrypt(this.organizationId, encKey);
break;
case CipherType.Identity:
model.identity = await this.identity.decrypt(this.organizationId);
model.identity = await this.identity.decrypt(this.organizationId, encKey);
break;
default:
break;
@@ -133,7 +134,7 @@ export class Cipher extends Domain {
const attachments: any[] = [];
await this.attachments.reduce((promise, attachment) => {
return promise.then(() => {
return attachment.decrypt(orgId);
return attachment.decrypt(orgId, encKey);
}).then((decAttachment) => {
attachments.push(decAttachment);
});
@@ -145,7 +146,7 @@ export class Cipher extends Domain {
const fields: any[] = [];
await this.fields.reduce((promise, field) => {
return promise.then(() => {
return field.decrypt(orgId);
return field.decrypt(orgId, encKey);
}).then((decField) => {
fields.push(decField);
});
@@ -157,7 +158,7 @@ export class Cipher extends Domain {
const passwordHistory: any[] = [];
await this.passwordHistory.reduce((promise, ph) => {
return promise.then(() => {
return ph.decrypt(orgId);
return ph.decrypt(orgId, encKey);
}).then((decPh) => {
passwordHistory.push(decPh);
});

View File

@@ -6,6 +6,7 @@ import { CipherString } from './cipherString';
import Domain from './domainBase';
import { FieldView } from '../view/fieldView';
import { SymmetricCryptoKey } from './symmetricCryptoKey';
export class Field extends Domain {
name: CipherString;
@@ -25,11 +26,11 @@ export class Field extends Domain {
}, alreadyEncrypted, []);
}
decrypt(orgId: string): Promise<FieldView> {
decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise<FieldView> {
return this.decryptObj(new FieldView(this), {
name: null,
value: null,
}, orgId);
}, orgId, encKey);
}
toFieldData(): FieldData {

View File

@@ -2,6 +2,7 @@ import { IdentityData } from '../data/identityData';
import { CipherString } from './cipherString';
import Domain from './domainBase';
import { SymmetricCryptoKey } from './symmetricCryptoKey';
import { IdentityView } from '../view/identityView';
@@ -53,7 +54,7 @@ export class Identity extends Domain {
}, alreadyEncrypted, []);
}
decrypt(orgId: string): Promise<IdentityView> {
decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise<IdentityView> {
return this.decryptObj(new IdentityView(this), {
title: null,
firstName: null,
@@ -73,7 +74,7 @@ export class Identity extends Domain {
username: null,
passportNumber: null,
licenseNumber: null,
}, orgId);
}, orgId, encKey);
}
toIdentityData(): IdentityData {

View File

@@ -6,6 +6,7 @@ import { LoginView } from '../view/loginView';
import { CipherString } from './cipherString';
import Domain from './domainBase';
import { SymmetricCryptoKey } from './symmetricCryptoKey';
export class Login extends Domain {
uris: LoginUri[];
@@ -35,17 +36,17 @@ export class Login extends Domain {
}
}
async decrypt(orgId: string): Promise<LoginView> {
async decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise<LoginView> {
const view = await this.decryptObj(new LoginView(this), {
username: null,
password: null,
totp: null,
}, orgId);
}, orgId, encKey);
if (this.uris != null) {
view.uris = [];
for (let i = 0; i < this.uris.length; i++) {
const uri = await this.uris[i].decrypt(orgId);
const uri = await this.uris[i].decrypt(orgId, encKey);
view.uris.push(uri);
}
}

View File

@@ -6,6 +6,7 @@ import { LoginUriView } from '../view/loginUriView';
import { CipherString } from './cipherString';
import Domain from './domainBase';
import { SymmetricCryptoKey } from './symmetricCryptoKey';
export class LoginUri extends Domain {
uri: CipherString;
@@ -23,10 +24,10 @@ export class LoginUri extends Domain {
}, alreadyEncrypted, []);
}
decrypt(orgId: string): Promise<LoginUriView> {
decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise<LoginUriView> {
return this.decryptObj(new LoginUriView(this), {
uri: null,
}, orgId);
}, orgId, encKey);
}
toLoginUriData(): LoginUriData {

View File

@@ -4,6 +4,7 @@ import { CipherString } from './cipherString';
import Domain from './domainBase';
import { PasswordHistoryView } from '../view/passwordHistoryView';
import { SymmetricCryptoKey } from './symmetricCryptoKey';
export class Password extends Domain {
password: CipherString;
@@ -21,10 +22,10 @@ export class Password extends Domain {
this.lastUsedDate = new Date(obj.lastUsedDate);
}
decrypt(orgId: string): Promise<PasswordHistoryView> {
decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise<PasswordHistoryView> {
return this.decryptObj(new PasswordHistoryView(this), {
password: null,
}, orgId);
}, orgId, encKey);
}
toPasswordHistoryData(): PasswordHistoryData {

View File

@@ -5,6 +5,7 @@ import { SecureNoteData } from '../data/secureNoteData';
import Domain from './domainBase';
import { SecureNoteView } from '../view/secureNoteView';
import { SymmetricCryptoKey } from './symmetricCryptoKey';
export class SecureNote extends Domain {
type: SecureNoteType;
@@ -18,7 +19,7 @@ export class SecureNote extends Domain {
this.type = obj.type;
}
decrypt(orgId: string): Promise<SecureNoteView> {
decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise<SecureNoteView> {
return Promise.resolve(new SecureNoteView(this));
}

View File

@@ -0,0 +1,3 @@
export class EmergencyAccessAcceptRequest {
token: string;
}

View File

@@ -0,0 +1,3 @@
export class EmergencyAccessConfirmRequest {
key: string;
}

View File

@@ -0,0 +1,7 @@
import { EmergencyAccessType } from '../../enums/emergencyAccessType';
export class EmergencyAccessInviteRequest {
email: string;
type: EmergencyAccessType;
waitTimeDays: number;
}

View File

@@ -0,0 +1,4 @@
export class EmergencyAccessPasswordRequest {
newMasterPasswordHash: string;
key: string;
}

View File

@@ -0,0 +1,7 @@
import { EmergencyAccessType } from '../../enums/emergencyAccessType';
export class EmergencyAccessUpdateRequest {
type: EmergencyAccessType;
waitTimeDays: number;
keyEncrypted?: string;
}

View File

@@ -0,0 +1,81 @@
import { EmergencyAccessStatusType } from '../../enums/emergencyAccessStatusType';
import { EmergencyAccessType } from '../../enums/emergencyAccessType';
import { KdfType } from '../../enums/kdfType';
import { BaseResponse } from './baseResponse';
import { CipherResponse } from './cipherResponse';
export class EmergencyAccessGranteeDetailsResponse extends BaseResponse {
id: string;
granteeId: string;
name: string;
email: string;
type: EmergencyAccessType;
status: EmergencyAccessStatusType;
waitTimeDays: number;
creationDate: string;
constructor(response: any) {
super(response);
this.id = this.getResponseProperty('Id');
this.granteeId = this.getResponseProperty('GranteeId');
this.name = this.getResponseProperty('Name');
this.email = this.getResponseProperty('Email');
this.type = this.getResponseProperty('Type');
this.status = this.getResponseProperty('Status');
this.waitTimeDays = this.getResponseProperty('WaitTimeDays');
this.creationDate = this.getResponseProperty('CreationDate');
}
}
export class EmergencyAccessGrantorDetailsResponse extends BaseResponse {
id: string;
grantorId: string;
name: string;
email: string;
type: EmergencyAccessType;
status: EmergencyAccessStatusType;
waitTimeDays: number;
creationDate: string;
constructor(response: any) {
super(response);
this.id = this.getResponseProperty('Id');
this.grantorId = this.getResponseProperty('GrantorId');
this.name = this.getResponseProperty('Name');
this.email = this.getResponseProperty('Email');
this.type = this.getResponseProperty('Type');
this.status = this.getResponseProperty('Status');
this.waitTimeDays = this.getResponseProperty('WaitTimeDays');
this.creationDate = this.getResponseProperty('CreationDate');
}
}
export class EmergencyAccessTakeoverResponse extends BaseResponse {
keyEncrypted: string;
kdf: KdfType;
kdfIterations: number;
constructor(response: any) {
super(response);
this.keyEncrypted = this.getResponseProperty('KeyEncrypted');
this.kdf = this.getResponseProperty('Kdf');
this.kdfIterations = this.getResponseProperty('KdfIterations');
}
}
export class EmergencyAccessViewResponse extends BaseResponse {
keyEncrypted: string;
ciphers: CipherResponse[] = [];
constructor(response: any) {
super(response);
this.keyEncrypted = this.getResponseProperty('KeyEncrypted');
const ciphers = this.getResponseProperty('Ciphers');
if (ciphers != null) {
this.ciphers = ciphers.map((c: any) => new CipherResponse(c));
}
}
}