1
0
mirror of https://github.com/bitwarden/browser.git synced 2024-11-30 13:03:53 +01: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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 358 additions and 117 deletions

View File

@ -15,6 +15,11 @@ import { CollectionRequest } from '../models/request/collectionRequest';
import { DeleteRecoverRequest } from '../models/request/deleteRecoverRequest';
import { EmailRequest } from '../models/request/emailRequest';
import { EmailTokenRequest } from '../models/request/emailTokenRequest';
import { EmergencyAccessAcceptRequest } from '../models/request/emergencyAccessAcceptRequest';
import { EmergencyAccessConfirmRequest } from '../models/request/emergencyAccessConfirmRequest';
import { EmergencyAccessInviteRequest } from '../models/request/emergencyAccessInviteRequest';
import { EmergencyAccessPasswordRequest } from '../models/request/emergencyAccessPasswordRequest';
import { EmergencyAccessUpdateRequest } from '../models/request/emergencyAccessUpdateRequest';
import { EventRequest } from '../models/request/eventRequest';
import { FolderRequest } from '../models/request/folderRequest';
import { GroupRequest } from '../models/request/groupRequest';
@ -73,6 +78,12 @@ import {
CollectionResponse,
} from '../models/response/collectionResponse';
import { DomainsResponse } from '../models/response/domainsResponse';
import {
EmergencyAccessGranteeDetailsResponse,
EmergencyAccessGrantorDetailsResponse,
EmergencyAccessTakeoverResponse,
EmergencyAccessViewResponse
} from '../models/response/emergencyAccessResponse';
import { EventResponse } from '../models/response/eventResponse';
import { FolderResponse } from '../models/response/folderResponse';
import {
@ -278,6 +289,22 @@ export abstract class ApiService {
postTwoFactorEmailSetup: (request: TwoFactorEmailRequest) => Promise<any>;
postTwoFactorEmail: (request: TwoFactorEmailRequest) => Promise<any>;
getEmergencyAccessTrusted: () => Promise<ListResponse<EmergencyAccessGranteeDetailsResponse>>;
getEmergencyAccessGranted: () => Promise<ListResponse<EmergencyAccessGrantorDetailsResponse>>;
getEmergencyAccess: (id: string) => Promise<EmergencyAccessGranteeDetailsResponse>;
putEmergencyAccess: (id: string, request: EmergencyAccessUpdateRequest) => Promise<any>;
deleteEmergencyAccess: (id: string) => Promise<any>;
postEmergencyAccessInvite: (request: EmergencyAccessInviteRequest) => Promise<any>;
postEmergencyAccessReinvite: (id: string) => Promise<any>;
postEmergencyAccessAccept: (id: string, request: EmergencyAccessAcceptRequest) => Promise<any>;
postEmergencyAccessConfirm: (id: string, request: EmergencyAccessConfirmRequest) => Promise<any>;
postEmergencyAccessInitiate: (id: string) => Promise<any>;
postEmergencyAccessApprove: (id: string) => Promise<any>;
postEmergencyAccessReject: (id: string) => Promise<any>;
postEmergencyAccessTakeover: (id: string) => Promise<EmergencyAccessTakeoverResponse>;
postEmergencyAccessPassword: (id: string, request: EmergencyAccessPasswordRequest) => Promise<any>;
postEmergencyAccessView: (id: string) => Promise<EmergencyAccessViewResponse>;
getOrganization: (id: string) => Promise<OrganizationResponse>;
getOrganizationBilling: (id: string) => Promise<BillingResponse>;
getOrganizationSubscription: (id: string) => Promise<OrganizationSubscriptionResponse>;

View File

@ -38,10 +38,11 @@ export abstract class CryptoService {
makeSendKey: (keyMaterial: ArrayBuffer) => Promise<SymmetricCryptoKey>;
hashPassword: (password: string, key: SymmetricCryptoKey) => Promise<string>;
makeEncKey: (key: SymmetricCryptoKey) => Promise<[SymmetricCryptoKey, CipherString]>;
remakeEncKey: (key: SymmetricCryptoKey) => Promise<[SymmetricCryptoKey, CipherString]>;
remakeEncKey: (key: SymmetricCryptoKey, encKey?: SymmetricCryptoKey) => Promise<[SymmetricCryptoKey, CipherString]>;
encrypt: (plainValue: string | ArrayBuffer, key?: SymmetricCryptoKey) => Promise<CipherString>;
encryptToBytes: (plainValue: ArrayBuffer, key?: SymmetricCryptoKey) => Promise<ArrayBuffer>;
rsaEncrypt: (data: ArrayBuffer, publicKey?: ArrayBuffer) => Promise<CipherString>;
rsaDecrypt: (encValue: string) => Promise<ArrayBuffer>;
decryptToBytes: (cipherString: CipherString, key?: SymmetricCryptoKey) => Promise<ArrayBuffer>;
decryptToUtf8: (cipherString: CipherString, key?: SymmetricCryptoKey) => Promise<string>;
decryptFromBytes: (encBuf: ArrayBuffer, key: SymmetricCryptoKey) => Promise<ArrayBuffer>;

View File

@ -21,11 +21,11 @@ export class ChangePasswordComponent implements OnInit {
masterPasswordScore: number;
enforcedPolicyOptions: MasterPasswordPolicyOptions;
protected email: string;
protected kdf: KdfType;
protected kdfIterations: number;
private masterPasswordStrengthTimeout: any;
private email: string;
constructor(protected i18nService: I18nService, protected cryptoService: CryptoService,
protected messagingService: MessagingService, protected userService: UserService,
@ -58,43 +58,9 @@ export class ChangePasswordComponent implements OnInit {
}
async submit() {
if (this.masterPassword == null || this.masterPassword === '') {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('masterPassRequired'));
if (!await this.strongPassword()) {
return;
}
if (this.masterPassword.length < 8) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('masterPassLength'));
return;
}
if (this.masterPassword !== this.masterPasswordRetype) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('masterPassDoesntMatch'));
return;
}
const strengthResult = this.passwordGenerationService.passwordStrength(this.masterPassword,
this.getPasswordStrengthUserInput());
if (this.enforcedPolicyOptions != null &&
!this.policyService.evaluateMasterPassword(
strengthResult.score,
this.masterPassword,
this.enforcedPolicyOptions)) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('masterPasswordPolicyRequirementsNotMet'));
return;
}
if (strengthResult != null && strengthResult.score < 3) {
const result = await this.platformUtilsService.showDialog(this.i18nService.t('weakMasterPasswordDesc'),
this.i18nService.t('weakMasterPassword'), this.i18nService.t('yes'), this.i18nService.t('no'),
'warning');
if (!result) {
return;
}
}
if (!await this.setupSubmitActions()) {
return;
@ -133,6 +99,48 @@ export class ChangePasswordComponent implements OnInit {
// Override in sub-class
}
async strongPassword(): Promise<boolean> {
if (this.masterPassword == null || this.masterPassword === '') {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('masterPassRequired'));
return false;
}
if (this.masterPassword.length < 8) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('masterPassLength'));
return false;
}
if (this.masterPassword !== this.masterPasswordRetype) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('masterPassDoesntMatch'));
return false;
}
const strengthResult = this.passwordGenerationService.passwordStrength(this.masterPassword,
this.getPasswordStrengthUserInput());
if (this.enforcedPolicyOptions != null &&
!this.policyService.evaluateMasterPassword(
strengthResult.score,
this.masterPassword,
this.enforcedPolicyOptions)) {
this.platformUtilsService.showToast('error', this.i18nService.t('errorOccurred'),
this.i18nService.t('masterPasswordPolicyRequirementsNotMet'));
return false;
}
if (strengthResult != null && strengthResult.score < 3) {
const result = await this.platformUtilsService.showDialog(this.i18nService.t('weakMasterPasswordDesc'),
this.i18nService.t('weakMasterPassword'), this.i18nService.t('yes'), this.i18nService.t('no'),
'warning');
if (!result) {
return false;
}
}
return true;
}
updatePasswordStrength() {
if (this.masterPasswordStrengthTimeout != null) {
clearTimeout(this.masterPasswordStrengthTimeout);

View File

@ -0,0 +1,7 @@
export enum EmergencyAccessStatusType {
Invited = 0,
Accepted = 1,
Confirmed = 2,
RecoveryInitiated = 3,
RecoveryApproved = 4,
}

View File

@ -0,0 +1,5 @@
export enum EmergencyAccessType
{
View = 0,
Takeover = 1,
}

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

View File

@ -19,6 +19,11 @@ import { CollectionRequest } from '../models/request/collectionRequest';
import { DeleteRecoverRequest } from '../models/request/deleteRecoverRequest';
import { EmailRequest } from '../models/request/emailRequest';
import { EmailTokenRequest } from '../models/request/emailTokenRequest';
import { EmergencyAccessAcceptRequest } from '../models/request/emergencyAccessAcceptRequest';
import { EmergencyAccessConfirmRequest } from '../models/request/emergencyAccessConfirmRequest';
import { EmergencyAccessInviteRequest } from '../models/request/emergencyAccessInviteRequest';
import { EmergencyAccessPasswordRequest } from '../models/request/emergencyAccessPasswordRequest';
import { EmergencyAccessUpdateRequest } from '../models/request/emergencyAccessUpdateRequest';
import { EventRequest } from '../models/request/eventRequest';
import { FolderRequest } from '../models/request/folderRequest';
import { GroupRequest } from '../models/request/groupRequest';
@ -77,6 +82,12 @@ import {
CollectionResponse,
} from '../models/response/collectionResponse';
import { DomainsResponse } from '../models/response/domainsResponse';
import {
EmergencyAccessGranteeDetailsResponse,
EmergencyAccessGrantorDetailsResponse,
EmergencyAccessTakeoverResponse,
EmergencyAccessViewResponse
} from '../models/response/emergencyAccessResponse';
import { ErrorResponse } from '../models/response/errorResponse';
import { EventResponse } from '../models/response/eventResponse';
import { FolderResponse } from '../models/response/folderResponse';
@ -901,6 +912,73 @@ export class ApiService implements ApiServiceAbstraction {
return this.send('POST', '/two-factor/send-email-login', request, false, false);
}
// Emergency Access APIs
async getEmergencyAccessTrusted(): Promise<ListResponse<EmergencyAccessGranteeDetailsResponse>> {
const r = await this.send('GET', '/emergency-access/trusted', null, true, true);
return new ListResponse(r, EmergencyAccessGranteeDetailsResponse);
}
async getEmergencyAccessGranted(): Promise<ListResponse<EmergencyAccessGrantorDetailsResponse>> {
const r = await this.send('GET', '/emergency-access/granted', null, true, true);
return new ListResponse(r, EmergencyAccessGrantorDetailsResponse);
}
async getEmergencyAccess(id: string): Promise<EmergencyAccessGranteeDetailsResponse> {
const r = await this.send('GET', '/emergency-access/' + id, null, true, true);
return new EmergencyAccessGranteeDetailsResponse(r);
}
putEmergencyAccess(id: string, request: EmergencyAccessUpdateRequest): Promise<any> {
return this.send('PUT', '/emergency-access/' + id, request, true, false);
}
deleteEmergencyAccess(id: string): Promise<any> {
return this.send('DELETE', '/emergency-access/' + id, null, true, false);
}
postEmergencyAccessInvite(request: EmergencyAccessInviteRequest): Promise<any> {
return this.send('POST', '/emergency-access/invite', request, true, false);
}
postEmergencyAccessReinvite(id: string): Promise<any> {
return this.send('POST', '/emergency-access/' + id + '/reinvite', null, true, false);
}
postEmergencyAccessAccept(id: string, request: EmergencyAccessAcceptRequest): Promise<any> {
return this.send('POST', '/emergency-access/' + id + '/accept', request, true, false);
}
postEmergencyAccessConfirm(id: string, request: EmergencyAccessConfirmRequest): Promise<any> {
return this.send('POST', '/emergency-access/' + id + '/confirm', request, true, false);
}
postEmergencyAccessInitiate(id: string): Promise<any> {
return this.send('POST', '/emergency-access/' + id + '/initiate', null, true, false);
}
postEmergencyAccessApprove(id: string): Promise<any> {
return this.send('POST', '/emergency-access/' + id + '/approve', null, true, false);
}
postEmergencyAccessReject(id: string): Promise<any> {
return this.send('POST', '/emergency-access/' + id + '/reject', null, true, false);
}
async postEmergencyAccessTakeover(id: string): Promise<EmergencyAccessTakeoverResponse> {
const r = await this.send('POST', '/emergency-access/' + id + '/takeover', null, true, true);
return new EmergencyAccessTakeoverResponse(r);
}
async postEmergencyAccessPassword(id: string, request: EmergencyAccessPasswordRequest): Promise<any> {
const r = await this.send('POST', '/emergency-access/' + id + '/password', request, true, true);
}
async postEmergencyAccessView(id: string): Promise<EmergencyAccessViewResponse> {
const r = await this.send('POST', '/emergency-access/' + id + '/view', null, true, true);
return new EmergencyAccessViewResponse(r);
}
// Organization APIs
async getOrganization(id: string): Promise<OrganizationResponse> {

View File

@ -380,8 +380,10 @@ export class CryptoService implements CryptoServiceAbstraction {
return this.buildEncKey(theKey, encKey);
}
async remakeEncKey(key: SymmetricCryptoKey): Promise<[SymmetricCryptoKey, CipherString]> {
const encKey = await this.getEncKey();
async remakeEncKey(key: SymmetricCryptoKey, encKey?: SymmetricCryptoKey): Promise<[SymmetricCryptoKey, CipherString]> {
if (encKey == null) {
encKey = await this.getEncKey();
}
return this.buildEncKey(key, encKey.key);
}
@ -434,6 +436,58 @@ export class CryptoService implements CryptoServiceAbstraction {
return new CipherString(EncryptionType.Rsa2048_OaepSha1_B64, Utils.fromBufferToB64(encBytes));
}
async rsaDecrypt(encValue: string): Promise<ArrayBuffer> {
const headerPieces = encValue.split('.');
let encType: EncryptionType = null;
let encPieces: string[];
if (headerPieces.length === 1) {
encType = EncryptionType.Rsa2048_OaepSha256_B64;
encPieces = [headerPieces[0]];
} else if (headerPieces.length === 2) {
try {
encType = parseInt(headerPieces[0], null);
encPieces = headerPieces[1].split('|');
} catch (e) { }
}
switch (encType) {
case EncryptionType.Rsa2048_OaepSha256_B64:
case EncryptionType.Rsa2048_OaepSha1_B64:
// HmacSha256 types are deprecated
case EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64:
case EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64:
break;
default:
throw new Error('encType unavailable.');
}
if (encPieces == null || encPieces.length <= 0) {
throw new Error('encPieces unavailable.');
}
const data = Utils.fromB64ToArray(encPieces[0]).buffer;
const privateKey = await this.getPrivateKey();
if (privateKey == null) {
throw new Error('No private key.');
}
let alg: 'sha1' | 'sha256' = 'sha1';
switch (encType) {
case EncryptionType.Rsa2048_OaepSha256_B64:
case EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64:
alg = 'sha256';
break;
case EncryptionType.Rsa2048_OaepSha1_B64:
case EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64:
break;
default:
throw new Error('encType unavailable.');
}
return this.cryptoFunctionService.rsaDecrypt(data, privateKey, alg);
}
async decryptToBytes(cipherString: CipherString, key?: SymmetricCryptoKey): Promise<ArrayBuffer> {
const iv = Utils.fromB64ToArray(cipherString.iv).buffer;
const data = Utils.fromB64ToArray(cipherString.data).buffer;
@ -604,58 +658,6 @@ export class CryptoService implements CryptoServiceAbstraction {
return await this.cryptoFunctionService.aesDecrypt(data, iv, theKey.encKey);
}
private async rsaDecrypt(encValue: string): Promise<ArrayBuffer> {
const headerPieces = encValue.split('.');
let encType: EncryptionType = null;
let encPieces: string[];
if (headerPieces.length === 1) {
encType = EncryptionType.Rsa2048_OaepSha256_B64;
encPieces = [headerPieces[0]];
} else if (headerPieces.length === 2) {
try {
encType = parseInt(headerPieces[0], null);
encPieces = headerPieces[1].split('|');
} catch (e) { }
}
switch (encType) {
case EncryptionType.Rsa2048_OaepSha256_B64:
case EncryptionType.Rsa2048_OaepSha1_B64:
// HmacSha256 types are deprecated
case EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64:
case EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64:
break;
default:
throw new Error('encType unavailable.');
}
if (encPieces == null || encPieces.length <= 0) {
throw new Error('encPieces unavailable.');
}
const data = Utils.fromB64ToArray(encPieces[0]).buffer;
const privateKey = await this.getPrivateKey();
if (privateKey == null) {
throw new Error('No private key.');
}
let alg: 'sha1' | 'sha256' = 'sha1';
switch (encType) {
case EncryptionType.Rsa2048_OaepSha256_B64:
case EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64:
alg = 'sha256';
break;
case EncryptionType.Rsa2048_OaepSha1_B64:
case EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64:
break;
default:
throw new Error('encType unavailable.');
}
return this.cryptoFunctionService.rsaDecrypt(data, privateKey, alg);
}
private async getKeyForEncryption(key?: SymmetricCryptoKey): Promise<SymmetricCryptoKey> {
if (key != null) {
return key;