diff --git a/src/angular/components/attachments.component.ts b/src/angular/components/attachments.component.ts index 5ef23b12bb..c05b3026ab 100644 --- a/src/angular/components/attachments.component.ts +++ b/src/angular/components/attachments.component.ts @@ -143,7 +143,8 @@ export class AttachmentsComponent implements OnInit { try { const buf = await response.arrayBuffer(); - const key = await this.cryptoService.getOrgKey(this.cipher.organizationId); + const key = attachment.key != null ? attachment.key : + await this.cryptoService.getOrgKey(this.cipher.organizationId); const decBuf = await this.cryptoService.decryptFromBytes(buf, key); this.platformUtilsService.saveFile(this.win, decBuf, null, attachment.fileName); } catch (e) { diff --git a/src/angular/components/view.component.ts b/src/angular/components/view.component.ts index c257493e6a..b645f92ff5 100644 --- a/src/angular/components/view.component.ts +++ b/src/angular/components/view.component.ts @@ -171,7 +171,8 @@ export class ViewComponent implements OnDestroy, OnInit { try { const buf = await response.arrayBuffer(); - const key = await this.cryptoService.getOrgKey(this.cipher.organizationId); + const key = attachment.key != null ? attachment.key : + await this.cryptoService.getOrgKey(this.cipher.organizationId); const decBuf = await this.cryptoService.decryptFromBytes(buf, key); this.platformUtilsService.saveFile(this.win, decBuf, null, attachment.fileName); } catch (e) { diff --git a/src/models/data/attachmentData.ts b/src/models/data/attachmentData.ts index 63b822174c..004376a8dd 100644 --- a/src/models/data/attachmentData.ts +++ b/src/models/data/attachmentData.ts @@ -4,6 +4,7 @@ export class AttachmentData { id: string; url: string; fileName: string; + key: string; size: number; sizeName: string; @@ -14,6 +15,7 @@ export class AttachmentData { this.id = response.id; this.url = response.url; this.fileName = response.fileName; + this.key = response.key; this.size = response.size; this.sizeName = response.sizeName; } diff --git a/src/models/domain/attachment.ts b/src/models/domain/attachment.ts index cf50a18c03..6db86bebdd 100644 --- a/src/models/domain/attachment.ts +++ b/src/models/domain/attachment.ts @@ -1,15 +1,21 @@ import { AttachmentData } from '../data/attachmentData'; +import { AttachmentView } from '../view/attachmentView'; + import { CipherString } from './cipherString'; import Domain from './domainBase'; +import { SymmetricCryptoKey } from './symmetricCryptoKey'; -import { AttachmentView } from '../view/attachmentView'; +import { CryptoService } from '../../abstractions/crypto.service'; + +import { Utils } from '../../misc/utils'; export class Attachment extends Domain { id: string; url: string; size: number; sizeName: string; + key: CipherString; fileName: CipherString; constructor(obj?: AttachmentData, alreadyEncrypted: boolean = false) { @@ -24,13 +30,34 @@ export class Attachment extends Domain { url: null, sizeName: null, fileName: null, + key: null, }, alreadyEncrypted, ['id', 'url', 'sizeName']); } - decrypt(orgId: string): Promise { - return this.decryptObj(new AttachmentView(this), { + async decrypt(orgId: string): Promise { + const view = await this.decryptObj(new AttachmentView(this), { fileName: null, }, orgId); + + if (this.key != null) { + let cryptoService: CryptoService; + const containerService = (Utils.global as any).bitwardenContainerService; + if (containerService) { + cryptoService = containerService.getCryptoService(); + } else { + throw new Error('global bitwardenContainerService not initialized.'); + } + + try { + const orgKey = await cryptoService.getOrgKey(orgId); + const decValue = await cryptoService.decryptToBytes(this.key, orgKey); + view.key = new SymmetricCryptoKey(decValue); + } catch (e) { + // TODO: error? + } + } + + return view; } toAttachmentData(): AttachmentData { @@ -40,6 +67,7 @@ export class Attachment extends Domain { url: null, sizeName: null, fileName: null, + key: null, }, ['id', 'url', 'sizeName']); return a; } diff --git a/src/models/request/attachmentRequest.ts b/src/models/request/attachmentRequest.ts new file mode 100644 index 0000000000..d341ae63a0 --- /dev/null +++ b/src/models/request/attachmentRequest.ts @@ -0,0 +1,4 @@ +export class AttachmentRequest { + fileName: string; + key: string; +} diff --git a/src/models/request/cipherRequest.ts b/src/models/request/cipherRequest.ts index 46d1762018..46717dd6a8 100644 --- a/src/models/request/cipherRequest.ts +++ b/src/models/request/cipherRequest.ts @@ -8,6 +8,7 @@ import { IdentityApi } from '../api/identityApi'; import { LoginApi } from '../api/loginApi'; import { SecureNoteApi } from '../api/secureNoteApi'; +import { AttachmentRequest } from './attachmentRequest'; import { PasswordHistoryRequest } from './passwordHistoryRequest'; export class CipherRequest { @@ -23,7 +24,9 @@ export class CipherRequest { identity: IdentityApi; fields: FieldApi[]; passwordHistory: PasswordHistoryRequest[]; + // Deprecated, remove at some point and rename attachments2 to attachments attachments: { [id: string]: string; }; + attachments2: { [id: string]: AttachmentRequest; }; constructor(cipher: Cipher) { this.type = cipher.type; @@ -119,8 +122,16 @@ export class CipherRequest { if (cipher.attachments) { this.attachments = {}; + this.attachments2 = {}; cipher.attachments.forEach((attachment) => { - this.attachments[attachment.id] = attachment.fileName ? attachment.fileName.encryptedString : null; + const fileName = attachment.fileName ? attachment.fileName.encryptedString : null; + this.attachments[attachment.id] = fileName; + const attachmentRequest = new AttachmentRequest(); + attachmentRequest.fileName = fileName; + if (attachment.key != null) { + attachmentRequest.key = attachment.key.encryptedString; + } + this.attachments2[attachment.id] = attachmentRequest; }); } } diff --git a/src/models/response/attachmentResponse.ts b/src/models/response/attachmentResponse.ts index 1ad530bd6d..fd45c709b6 100644 --- a/src/models/response/attachmentResponse.ts +++ b/src/models/response/attachmentResponse.ts @@ -2,6 +2,7 @@ export class AttachmentResponse { id: string; url: string; fileName: string; + key: string; size: number; sizeName: string; @@ -9,6 +10,7 @@ export class AttachmentResponse { this.id = response.Id; this.url = response.Url; this.fileName = response.FileName; + this.key = response.Key; this.size = response.Size; this.sizeName = response.SizeName; } diff --git a/src/models/view/attachmentView.ts b/src/models/view/attachmentView.ts index efb0081efa..aff342c9f8 100644 --- a/src/models/view/attachmentView.ts +++ b/src/models/view/attachmentView.ts @@ -1,6 +1,7 @@ import { View } from './view'; import { Attachment } from '../domain/attachment'; +import { SymmetricCryptoKey } from '../domain/symmetricCryptoKey'; export class AttachmentView implements View { id: string; @@ -8,6 +9,7 @@ export class AttachmentView implements View { size: number; sizeName: string; fileName: string; + key: SymmetricCryptoKey; constructor(a?: Attachment) { if (!a) { diff --git a/src/services/cipher.service.ts b/src/services/cipher.service.ts index 257d59b4a6..4926a6328a 100644 --- a/src/services/cipher.service.ts +++ b/src/services/cipher.service.ts @@ -173,7 +173,10 @@ export class CipherService implements CipherServiceAbstraction { attachment.url = model.url; const promise = this.encryptObjProperty(model, attachment, { fileName: null, - }, key).then(() => { + }, key).then(async () => { + if (model.key != null) { + attachment.key = await this.cryptoService.encrypt(model.key.key, key); + } encAttachments.push(attachment); }); promises.push(promise); @@ -519,14 +522,18 @@ export class CipherService implements CipherServiceAbstraction { data: ArrayBuffer, admin = false): Promise { const key = await this.cryptoService.getOrgKey(cipher.organizationId); const encFileName = await this.cryptoService.encrypt(filename, key); - const encData = await this.cryptoService.encryptToBytes(data, key); + + const dataEncKey = await this.cryptoService.makeEncKey(key); + const encData = await this.cryptoService.encryptToBytes(data, dataEncKey[0]); const fd = new FormData(); try { + fd.append('key', dataEncKey[1].encryptedString); const blob = new Blob([encData], { type: 'application/octet-stream' }); fd.append('data', blob, encFileName.encryptedString); } catch (e) { if (Utils.isNode && !Utils.isBrowser) { + fd.append('key', dataEncKey[1].encryptedString); fd.append('data', Buffer.from(encData) as any, { filepath: encFileName.encryptedString, contentType: 'application/octet-stream', @@ -753,15 +760,19 @@ export class CipherService implements CipherServiceAbstraction { const buf = await attachmentResponse.arrayBuffer(); const decBuf = await this.cryptoService.decryptFromBytes(buf, null); const key = await this.cryptoService.getOrgKey(organizationId); - const encData = await this.cryptoService.encryptToBytes(decBuf, key); const encFileName = await this.cryptoService.encrypt(attachmentView.fileName, key); + const dataEncKey = await this.cryptoService.makeEncKey(key); + const encData = await this.cryptoService.encryptToBytes(decBuf, dataEncKey[0]); + const fd = new FormData(); try { + fd.append('key', dataEncKey[1].encryptedString); const blob = new Blob([encData], { type: 'application/octet-stream' }); fd.append('data', blob, encFileName.encryptedString); } catch (e) { if (Utils.isNode && !Utils.isBrowser) { + fd.append('key', dataEncKey[1].encryptedString); fd.append('data', Buffer.from(encData) as any, { filepath: encFileName.encryptedString, contentType: 'application/octet-stream', diff --git a/src/services/crypto.service.ts b/src/services/crypto.service.ts index 01b4ba4b62..37a2abe43a 100644 --- a/src/services/crypto.service.ts +++ b/src/services/crypto.service.ts @@ -333,8 +333,9 @@ export class CryptoService implements CryptoServiceAbstraction { } async makeEncKey(key: SymmetricCryptoKey): Promise<[SymmetricCryptoKey, CipherString]> { + const theKey = await this.getKeyForEncryption(key); const encKey = await this.cryptoFunctionService.randomBytes(64); - return this.buildEncKey(key, encKey); + return this.buildEncKey(theKey, encKey); } async remakeEncKey(key: SymmetricCryptoKey): Promise<[SymmetricCryptoKey, CipherString]> {