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

@@ -8,12 +8,32 @@ import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
export abstract class EncryptService {
abstract encrypt(plainValue: string | Uint8Array, key: SymmetricCryptoKey): Promise<EncString>;
abstract encryptToBytes(plainValue: Uint8Array, key: SymmetricCryptoKey): Promise<EncArrayBuffer>;
/**
* Decrypts an EncString to a string
* @param encString - The EncString to decrypt
* @param key - The key to decrypt the EncString with
* @param decryptTrace - A string to identify the context of the object being decrypted. This can include: field name, encryption type, cipher id, key type, but should not include
* sensitive information like encryption keys or data. This is used for logging when decryption errors occur in order to identify what failed to decrypt
* @returns The decrypted string
*/
abstract decryptToUtf8(
encString: EncString,
key: SymmetricCryptoKey,
decryptContext?: string,
decryptTrace?: string,
): Promise<string>;
abstract decryptToBytes(encThing: Encrypted, key: SymmetricCryptoKey): Promise<Uint8Array>;
/**
* Decrypts an Encrypted object to a Uint8Array
* @param encThing - The Encrypted object to decrypt
* @param key - The key to decrypt the Encrypted object with
* @param decryptTrace - A string to identify the context of the object being decrypted. This can include: field name, encryption type, cipher id, key type, but should not include
* sensitive information like encryption keys or data. This is used for logging when decryption errors occur in order to identify what failed to decrypt
* @returns The decrypted Uint8Array
*/
abstract decryptToBytes(
encThing: Encrypted,
key: SymmetricCryptoKey,
decryptTrace?: string,
): Promise<Uint8Array>;
abstract rsaEncrypt(data: Uint8Array, publicKey: Uint8Array): Promise<EncString>;
abstract rsaDecrypt(data: EncString, privateKey: Uint8Array): Promise<Uint8Array>;
abstract resolveLegacyKey(key: SymmetricCryptoKey, encThing: Encrypted): SymmetricCryptoKey;

View File

@@ -63,6 +63,7 @@ export default class Domain {
map: any,
orgId: string,
key: SymmetricCryptoKey = null,
objectContext: string = "No Domain Context",
): Promise<T> {
const promises = [];
const self: any = this;
@@ -78,7 +79,11 @@ export default class Domain {
.then(() => {
const mapProp = map[theProp] || theProp;
if (self[mapProp]) {
return self[mapProp].decrypt(orgId, key);
return self[mapProp].decrypt(
orgId,
key,
`Property: ${prop}; ObjectContext: ${objectContext}`,
);
}
return null;
})
@@ -114,12 +119,21 @@ export default class Domain {
key: SymmetricCryptoKey,
encryptService: EncryptService,
_: Constructor<TThis> = this.constructor as Constructor<TThis>,
objectContext: string = "No Domain Context",
): Promise<DecryptedObject<TThis, TEncryptedKeys>> {
const promises = [];
for (const prop of encryptedProperties) {
const value = (this as any)[prop] as EncString;
promises.push(this.decryptProperty(prop, value, key, encryptService));
promises.push(
this.decryptProperty(
prop,
value,
key,
encryptService,
`Property: ${prop.toString()}; ObjectContext: ${objectContext}`,
),
);
}
const decryptedObjects = await Promise.all(promises);
@@ -137,10 +151,11 @@ export default class Domain {
value: EncString,
key: SymmetricCryptoKey,
encryptService: EncryptService,
decryptTrace: string,
) {
let decrypted: string = null;
if (value) {
decrypted = await value.decryptWithKey(key, encryptService);
decrypted = await value.decryptWithKey(key, encryptService, decryptTrace);
} else {
decrypted = null;
}

View File

@@ -156,21 +156,21 @@ export class EncString implements Encrypted {
return EXPECTED_NUM_PARTS_BY_ENCRYPTION_TYPE[encType] === encPieces.length;
}
async decrypt(orgId: string, key: SymmetricCryptoKey = null): Promise<string> {
async decrypt(orgId: string, key: SymmetricCryptoKey = null, context?: string): Promise<string> {
if (this.decryptedValue != null) {
return this.decryptedValue;
}
let keyContext = "provided-key";
let decryptTrace = "provided-key";
try {
if (key == null) {
key = await this.getKeyForDecryption(orgId);
keyContext = orgId == null ? `domain-orgkey-${orgId}` : "domain-userkey|masterkey";
decryptTrace = orgId == null ? `domain-orgkey-${orgId}` : "domain-userkey|masterkey";
if (orgId != null) {
keyContext = `domain-orgkey-${orgId}`;
decryptTrace = `domain-orgkey-${orgId}`;
} else {
const cryptoService = Utils.getContainerService().getKeyService();
keyContext =
decryptTrace =
(await cryptoService.getUserKey()) == null
? "domain-withlegacysupport-masterkey"
: "domain-withlegacysupport-userkey";
@@ -181,20 +181,28 @@ export class EncString implements Encrypted {
}
const encryptService = Utils.getContainerService().getEncryptService();
this.decryptedValue = await encryptService.decryptToUtf8(this, key, keyContext);
this.decryptedValue = await encryptService.decryptToUtf8(
this,
key,
decryptTrace == null ? context : `${decryptTrace}${context || ""}`,
);
} catch (e) {
this.decryptedValue = DECRYPT_ERROR;
}
return this.decryptedValue;
}
async decryptWithKey(key: SymmetricCryptoKey, encryptService: EncryptService) {
async decryptWithKey(
key: SymmetricCryptoKey,
encryptService: EncryptService,
decryptTrace: string = "domain-withkey",
): Promise<string> {
try {
if (key == null) {
throw new Error("No key to decrypt EncString");
}
this.decryptedValue = await encryptService.decryptToUtf8(this, key, "domain-withkey");
this.decryptedValue = await encryptService.decryptToUtf8(this, key, decryptTrace);
} catch (e) {
this.decryptedValue = DECRYPT_ERROR;
}

View File

@@ -114,7 +114,7 @@ export class EncryptServiceImplementation implements EncryptService {
const macsEqual = await this.cryptoFunctionService.compareFast(fastParams.mac, computedMac);
if (!macsEqual) {
this.logMacFailed(
"[Encrypt service] MAC comparison failed. Key or payload has changed. Key type " +
"[Encrypt service] decryptToUtf8 MAC comparison failed. Key or payload has changed. Key type " +
encryptionTypeName(key.encType) +
"Payload type " +
encryptionTypeName(encString.encryptionType) +
@@ -128,7 +128,11 @@ export class EncryptServiceImplementation implements EncryptService {
return await this.cryptoFunctionService.aesDecryptFast(fastParams, "cbc");
}
async decryptToBytes(encThing: Encrypted, key: SymmetricCryptoKey): Promise<Uint8Array> {
async decryptToBytes(
encThing: Encrypted,
key: SymmetricCryptoKey,
decryptContext: string = "no context",
): Promise<Uint8Array> {
if (key == null) {
throw new Error("No encryption key provided.");
}
@@ -145,7 +149,9 @@ export class EncryptServiceImplementation implements EncryptService {
"[Encrypt service] Key has mac key but payload is missing mac bytes. Key type " +
encryptionTypeName(key.encType) +
" Payload type " +
encryptionTypeName(encThing.encryptionType),
encryptionTypeName(encThing.encryptionType) +
" Decrypt context: " +
decryptContext,
);
return null;
}
@@ -155,7 +161,9 @@ export class EncryptServiceImplementation implements EncryptService {
"[Encrypt service] Key encryption type does not match payload encryption type. Key type " +
encryptionTypeName(key.encType) +
" Payload type " +
encryptionTypeName(encThing.encryptionType),
encryptionTypeName(encThing.encryptionType) +
" Decrypt context: " +
decryptContext,
);
return null;
}
@@ -167,11 +175,13 @@ export class EncryptServiceImplementation implements EncryptService {
const computedMac = await this.cryptoFunctionService.hmac(macData, key.macKey, "sha256");
if (computedMac === null) {
this.logMacFailed(
"[Encrypt service] Failed to compute MAC." +
"[Encrypt service#decryptToBytes] Failed to compute MAC." +
" Key type " +
encryptionTypeName(key.encType) +
" Payload type " +
encryptionTypeName(encThing.encryptionType),
encryptionTypeName(encThing.encryptionType) +
" Decrypt context: " +
decryptContext,
);
return null;
}
@@ -179,11 +189,13 @@ export class EncryptServiceImplementation implements EncryptService {
const macsMatch = await this.cryptoFunctionService.compare(encThing.macBytes, computedMac);
if (!macsMatch) {
this.logMacFailed(
"[Encrypt service] MAC comparison failed. Key or payload has changed." +
"[Encrypt service#decryptToBytes]: MAC comparison failed. Key or payload has changed." +
" Key type " +
encryptionTypeName(key.encType) +
" Payload type " +
encryptionTypeName(encThing.encryptionType),
encryptionTypeName(encThing.encryptionType) +
" Decrypt context: " +
decryptContext,
);
return null;
}