1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-16 00:03:56 +00:00

[PM-16699] Add decrypt trace for decrypt failures (#12749)

* Improve decrypt failure logging

* Rename decryptcontext to decrypttrace

* Improve docs

* Revert changes to decrypt logic

* Revert keyservice decryption logic change

* Undo one more change to decrypt logic
This commit is contained in:
Bernd Schoolmann
2025-01-09 20:23:55 +01:00
committed by GitHub
parent bb8e649048
commit 8cabb36c99
18 changed files with 165 additions and 43 deletions

View File

@@ -101,7 +101,7 @@ describe("Attachment", () => {
it("uses the provided key without depending on KeyService", async () => {
const providedKey = mock<SymmetricCryptoKey>();
await attachment.decrypt(null, providedKey);
await attachment.decrypt(null, "", providedKey);
expect(keyService.getUserKeyWithLegacySupport).not.toHaveBeenCalled();
expect(encryptService.decryptToBytes).toHaveBeenCalledWith(attachment.key, providedKey);
@@ -111,7 +111,7 @@ describe("Attachment", () => {
const orgKey = mock<OrgKey>();
keyService.getOrgKey.calledWith("orgId").mockResolvedValue(orgKey);
await attachment.decrypt("orgId", null);
await attachment.decrypt("orgId", "", null);
expect(keyService.getOrgKey).toHaveBeenCalledWith("orgId");
expect(encryptService.decryptToBytes).toHaveBeenCalledWith(attachment.key, orgKey);
@@ -121,7 +121,7 @@ describe("Attachment", () => {
const userKey = mock<UserKey>();
keyService.getUserKeyWithLegacySupport.mockResolvedValue(userKey);
await attachment.decrypt(null, null);
await attachment.decrypt(null, "", null);
expect(keyService.getUserKeyWithLegacySupport).toHaveBeenCalled();
expect(encryptService.decryptToBytes).toHaveBeenCalledWith(attachment.key, userKey);

View File

@@ -38,7 +38,11 @@ export class Attachment extends Domain {
);
}
async decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise<AttachmentView> {
async decrypt(
orgId: string,
context = "No Cipher Context",
encKey?: SymmetricCryptoKey,
): Promise<AttachmentView> {
const view = await this.decryptObj(
new AttachmentView(this),
{
@@ -46,6 +50,7 @@ export class Attachment extends Domain {
},
orgId,
encKey,
"DomainType: Attachment; " + context,
);
if (this.key != null) {

View File

@@ -37,7 +37,11 @@ export class Card extends Domain {
);
}
decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise<CardView> {
async decrypt(
orgId: string,
context = "No Cipher Context",
encKey?: SymmetricCryptoKey,
): Promise<CardView> {
return this.decryptObj(
new CardView(),
{
@@ -50,6 +54,7 @@ export class Card extends Domain {
},
orgId,
encKey,
"DomainType: Card; " + context,
);
}

View File

@@ -136,7 +136,11 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
if (this.key != null) {
const encryptService = Utils.getContainerService().getEncryptService();
const keyBytes = await encryptService.decryptToBytes(this.key, encKey);
const keyBytes = await encryptService.decryptToBytes(
this.key,
encKey,
`Cipher Id: ${this.id}; Content: CipherKey; IsEncryptedByOrgKey: ${this.organizationId != null}`,
);
if (keyBytes == null) {
model.name = "[error: cannot decrypt]";
model.decryptionFailure = true;
@@ -158,19 +162,36 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
switch (this.type) {
case CipherType.Login:
model.login = await this.login.decrypt(this.organizationId, bypassValidation, encKey);
model.login = await this.login.decrypt(
this.organizationId,
bypassValidation,
`Cipher Id: ${this.id}`,
encKey,
);
break;
case CipherType.SecureNote:
model.secureNote = await this.secureNote.decrypt(this.organizationId, encKey);
model.secureNote = await this.secureNote.decrypt(
this.organizationId,
`Cipher Id: ${this.id}`,
encKey,
);
break;
case CipherType.Card:
model.card = await this.card.decrypt(this.organizationId, encKey);
model.card = await this.card.decrypt(this.organizationId, `Cipher Id: ${this.id}`, encKey);
break;
case CipherType.Identity:
model.identity = await this.identity.decrypt(this.organizationId, encKey);
model.identity = await this.identity.decrypt(
this.organizationId,
`Cipher Id: ${this.id}`,
encKey,
);
break;
case CipherType.SshKey:
model.sshKey = await this.sshKey.decrypt(this.organizationId, encKey);
model.sshKey = await this.sshKey.decrypt(
this.organizationId,
`Cipher Id: ${this.id}`,
encKey,
);
break;
default:
break;
@@ -181,7 +202,7 @@ export class Cipher extends Domain implements Decryptable<CipherView> {
await this.attachments.reduce((promise, attachment) => {
return promise
.then(() => {
return attachment.decrypt(this.organizationId, encKey);
return attachment.decrypt(this.organizationId, `Cipher Id: ${this.id}`, encKey);
})
.then((decAttachment) => {
attachments.push(decAttachment);

View File

@@ -61,7 +61,11 @@ export class Identity extends Domain {
);
}
decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise<IdentityView> {
decrypt(
orgId: string,
context: string = "No Cipher Context",
encKey?: SymmetricCryptoKey,
): Promise<IdentityView> {
return this.decryptObj(
new IdentityView(),
{
@@ -86,6 +90,7 @@ export class Identity extends Domain {
},
orgId,
encKey,
"DomainType: Identity; " + context,
);
}

View File

@@ -33,7 +33,11 @@ export class LoginUri extends Domain {
);
}
decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise<LoginUriView> {
decrypt(
orgId: string,
context: string = "No Cipher Context",
encKey?: SymmetricCryptoKey,
): Promise<LoginUriView> {
return this.decryptObj(
new LoginUriView(this),
{
@@ -41,6 +45,7 @@ export class LoginUri extends Domain {
},
orgId,
encKey,
context,
);
}

View File

@@ -55,6 +55,7 @@ export class Login extends Domain {
async decrypt(
orgId: string,
bypassValidation: boolean,
context: string = "No Cipher Context",
encKey?: SymmetricCryptoKey,
): Promise<LoginView> {
const view = await this.decryptObj(
@@ -66,6 +67,7 @@ export class Login extends Domain {
},
orgId,
encKey,
`DomainType: Login; ${context}`,
);
if (this.uris != null) {
@@ -76,7 +78,7 @@ export class Login extends Domain {
continue;
}
const uri = await this.uris[i].decrypt(orgId, encKey);
const uri = await this.uris[i].decrypt(orgId, context, encKey);
// URIs are shared remotely after decryption
// we need to validate that the string hasn't been changed by a compromised server
// This validation is tied to the existence of cypher.key for backwards compatibility

View File

@@ -32,6 +32,7 @@ export class Password extends Domain {
},
orgId,
encKey,
"DomainType: PasswordHistory",
);
}

View File

@@ -20,8 +20,12 @@ export class SecureNote extends Domain {
this.type = obj.type;
}
decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise<SecureNoteView> {
return Promise.resolve(new SecureNoteView(this));
async decrypt(
orgId: string,
context = "No Cipher Context",
encKey?: SymmetricCryptoKey,
): Promise<SecureNoteView> {
return new SecureNoteView(this);
}
toSecureNoteData(): SecureNoteData {

View File

@@ -32,7 +32,11 @@ export class SshKey extends Domain {
);
}
decrypt(orgId: string, encKey?: SymmetricCryptoKey): Promise<SshKeyView> {
decrypt(
orgId: string,
context = "No Cipher Context",
encKey?: SymmetricCryptoKey,
): Promise<SshKeyView> {
return this.decryptObj(
new SshKeyView(),
{
@@ -42,6 +46,7 @@ export class SshKey extends Domain {
},
orgId,
encKey,
"DomainType: SshKey; " + context,
);
}