1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-10 13:40:06 +00:00
This commit is contained in:
Bernd Schoolmann
2025-02-25 13:50:27 +01:00
parent 8ee5fb2cbc
commit 649e6c5b1d
7 changed files with 102 additions and 96 deletions

View File

@@ -13,7 +13,7 @@ import { EncryptService } from "@bitwarden/common/key-management/crypto/abstract
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { KeyGenerationService } from "@bitwarden/common/platform/abstractions/key-generation.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { EncryptionType, HashPurpose } from "@bitwarden/common/platform/enums";
import { HashPurpose } from "@bitwarden/common/platform/enums";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncryptedString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
@@ -105,22 +105,25 @@ export class UserKeyRotationService {
await this.keyService.makeUserKey(newMasterKey);
const userKey = CryptoClient.generate_user_key();
this.logService.info("[Userkey rotation] Encrypting user key in new format");
this.logService.info("[Userkey rotation] Encrypting user key in new format" + userKey);
const userkeyEncodedBytes = Utils.fromB64ToArray(userKey);
const stretchedMasterKey = await this.keyGenerationService.stretchKey(newMasterKey);
const userkeyEncrypted = await this.encryptService.encrypt(
userkeyEncodedBytes,
stretchedMasterKey,
);
const userkeyBytes = Utils.fromB64ToArray(CryptoClient.decode_userkey(userKey).Aes256CbcHmac);
newUnencryptedUserKey = new SymmetricCryptoKey(userkeyBytes, EncryptionType.AesCbc256_HmacSha256_B64) as UserKey;
newMasterKeyEncryptedUserKey = userkeyEncrypted;
this.logService.info("[Userkey rotation] User key encrypted in new format" + userkeyEncrypted.encryptedString);
newUnencryptedUserKey = new SymmetricCryptoKey(userkeyEncodedBytes) as UserKey;
newMasterKeyEncryptedUserKey
= userkeyEncrypted;
if (!newUnencryptedUserKey || !newMasterKeyEncryptedUserKey) {
this.logService.info("[Userkey rotation] User key could not be created. Aborting!");
throw new Error("User key could not be created");
}
this.logService.info("[Userkey rotation] User key created successfully");
const newMasterKeyAuthenticationHash = await this.keyService.hashMasterKey(
newMasterPassword,
newMasterKey,

View File

@@ -295,7 +295,7 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction {
return;
}
await this.stateProvider.setUserState(DEVICE_KEY, deviceKey?.toJSON(), userId);
//await this.stateProvider.setUserState(DEVICE_KEY, deviceKey?.toJSON(), userId);
} catch (e) {
this.logService.error("Failed to set device key", e);
}
@@ -304,7 +304,6 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction {
private async makeDeviceKey(): Promise<DeviceKey> {
// Create 512-bit device key
const deviceKey = (await this.keyGenerationService.createKey(512)) as DeviceKey;
return deviceKey;
}

View File

@@ -2,9 +2,6 @@
// @ts-strict-ignore
import { firstValueFrom, map, Observable } from "rxjs";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { CryptoClient } from "@bitwarden/sdk-internal";
import { EncryptService } from "../../../key-management/crypto/abstractions/encrypt.service";
import { KeyGenerationService } from "../../../platform/abstractions/key-generation.service";
import { LogService } from "../../../platform/abstractions/log.service";
@@ -198,11 +195,7 @@ export class MasterPasswordService implements InternalMasterPasswordServiceAbstr
return new SymmetricCryptoKey(decUserKey) as UserKey;
} else {
this.logService.info("[MasterPasswordService] Userkey in new format; using sdk");
const decodedKey = CryptoClient.decode_userkey(decUserKey);
if (decodedKey.Aes256CbcHmac != null) {
const key = Utils.fromB64ToArray(decodedKey.Aes256CbcHmac);
return new SymmetricCryptoKey(key) as UserKey;
}
return new SymmetricCryptoKey(decUserKey) as UserKey;
}
} else {
throw new Error("Unsupported encryption type.");

View File

@@ -40,11 +40,15 @@ export class EncryptServiceImplementation implements EncryptService {
plainBuf = plainValue;
}
const encObj = await this.aesEncrypt(plainBuf, key);
const iv = Utils.fromBufferToB64(encObj.iv);
const data = Utils.fromBufferToB64(encObj.data);
const mac = encObj.mac != null ? Utils.fromBufferToB64(encObj.mac) : null;
return new EncString(encObj.key.encryptionType(), data, iv, mac);
if (key.encryptionType() === EncryptionType.AesCbc256_HmacSha256_B64) {
const encObj = await this.aesEncrypt(plainBuf, key);
const iv = Utils.fromBufferToB64(encObj.iv);
const data = Utils.fromBufferToB64(encObj.data);
const mac = Utils.fromBufferToB64(encObj.mac);
return new EncString(key.encryptionType(), data, iv, mac);
} else {
throw new Error("Unsupported encryption type.");
}
}
async encryptToBytes(plainValue: Uint8Array, key: SymmetricCryptoKey): Promise<EncArrayBuffer> {
@@ -52,21 +56,17 @@ export class EncryptServiceImplementation implements EncryptService {
throw new Error("No encryption key provided.");
}
const encValue = await this.aesEncrypt(plainValue, key);
let macLen = 0;
if (encValue.mac != null) {
macLen = encValue.mac.byteLength;
}
const encBytes = new Uint8Array(1 + encValue.iv.byteLength + macLen + encValue.data.byteLength);
encBytes.set([encValue.key.encryptionType()]);
encBytes.set(new Uint8Array(encValue.iv), 1);
if (encValue.mac != null) {
if (key.encryptionType() !== EncryptionType.AesCbc256_HmacSha256_B64) {
const encValue = await this.aesEncrypt(plainValue, key);
const encBytes = new Uint8Array(1 + encValue.iv.byteLength + encValue.mac.byteLength + encValue.data.byteLength);
encBytes.set([key.encryptionType()]);
encBytes.set(new Uint8Array(encValue.iv), 1);
encBytes.set(new Uint8Array(encValue.mac), 1 + encValue.iv.byteLength);
encBytes.set(new Uint8Array(encValue.data), 1 + encValue.iv.byteLength + encValue.mac.byteLength);
return new EncArrayBuffer(encBytes);
} else {
throw new Error("Unsupported encryption type.");
}
encBytes.set(new Uint8Array(encValue.data), 1 + encValue.iv.byteLength + macLen);
return new EncArrayBuffer(encBytes);
}
async decryptToUtf8(
@@ -79,35 +79,35 @@ export class EncryptServiceImplementation implements EncryptService {
}
// DO NOT REMOVE OR MOVE. This prevents downgrade to mac-less CBC, which would compromise integrity and confidentiality.
if (key.macKey != null && encString?.mac == null) {
this.logService.error(
"[Encrypt service] Key has mac key but payload is missing mac bytes. Key type " +
encryptionTypeName(key.encryptionType()) +
"Payload type " +
encryptionTypeName(encString.encryptionType),
"Decrypt context: " + decryptContext,
);
return null;
}
if (key.encryptionType() === EncryptionType.AesCbc256_HmacSha256_B64) {
if (encString?.mac == null) {
this.logService.error(
"[Encrypt service] Key has mac key but payload is missing mac bytes. Key type " +
encryptionTypeName(key.encryptionType()) +
"Payload type " +
encryptionTypeName(encString.encryptionType),
"Decrypt context: " + decryptContext,
);
return null;
}
if (key.encryptionType() !== encString.encryptionType) {
this.logService.error(
"[Encrypt service] Key encryption type does not match payload encryption type. Key type " +
encryptionTypeName(key.encryptionType()) +
"Payload type " +
encryptionTypeName(encString.encryptionType),
"Decrypt context: " + decryptContext,
);
return null;
}
if (encString.encryptionType !== EncryptionType.AesCbc256_HmacSha256_B64) {
this.logService.error(
"[Encrypt service] Key encryption type does not match payload encryption type. Key type " +
encryptionTypeName(key.encryptionType()) +
"Payload type " +
encryptionTypeName(encString.encryptionType),
"Decrypt context: " + decryptContext,
);
return null;
}
const fastParams = this.cryptoFunctionService.aesDecryptFastParameters(
encString.data,
encString.iv,
encString.mac,
key,
);
if (fastParams.macKey != null && fastParams.mac != null) {
const fastParams = this.cryptoFunctionService.aesDecryptFastParameters(
encString.data,
encString.iv,
encString.mac,
key,
);
const computedMac = await this.cryptoFunctionService.hmacFast(
fastParams.macData,
fastParams.macKey,
@@ -125,9 +125,14 @@ export class EncryptServiceImplementation implements EncryptService {
);
return null;
}
return await this.cryptoFunctionService.aesDecryptFast({ mode: "cbc", parameters: fastParams });
} else if (key.encryptionType() === EncryptionType.AesCbc256_B64) {
return Utils.fromBufferToByteString(await this.cryptoFunctionService.aesDecrypt(encString.dataBytes, encString.ivBytes, key.getInnerKey().encryptionKey, "cbc"));
} else {
throw new Error("Unsupported encryption type.");
}
return await this.cryptoFunctionService.aesDecryptFast({ mode: "cbc", parameters: fastParams });
}
async decryptToBytes(
@@ -143,36 +148,37 @@ export class EncryptServiceImplementation implements EncryptService {
throw new Error("Nothing provided for decryption.");
}
// DO NOT REMOVE OR MOVE. This prevents downgrade to mac-less CBC, which would compromise integrity and confidentiality.
if (key.macKey != null && encThing.macBytes == null) {
this.logService.error(
"[Encrypt service] Key has mac key but payload is missing mac bytes. Key type " +
encryptionTypeName(key.encryptionType()) +
" Payload type " +
encryptionTypeName(encThing.encryptionType) +
" Decrypt context: " +
decryptContext,
);
return null;
}
const innerKey = key.getInnerKey();
if (innerKey.type == EncryptionType.AesCbc256_HmacSha256_B64) {
// DO NOT REMOVE OR MOVE. This prevents downgrade to mac-less CBC, which would compromise integrity and confidentiality.
if (encThing.macBytes == null) {
this.logService.error(
"[Encrypt service] Key has mac key but payload is missing mac bytes. Key type " +
encryptionTypeName(key.encryptionType()) +
" Payload type " +
encryptionTypeName(encThing.encryptionType) +
" Decrypt context: " +
decryptContext,
);
return null;
}
if (key.encryptionType() !== encThing.encryptionType) {
this.logService.error(
"[Encrypt service] Key encryption type does not match payload encryption type. Key type " +
encryptionTypeName(key.encryptionType()) +
" Payload type " +
encryptionTypeName(encThing.encryptionType) +
" Decrypt context: " +
decryptContext,
);
return null;
}
if (encThing.encryptionType !== EncryptionType.AesCbc256_HmacSha256_B64) {
this.logService.error(
"[Encrypt service] Key encryption type does not match payload encryption type. Key type " +
encryptionTypeName(key.encryptionType()) +
" Payload type " +
encryptionTypeName(encThing.encryptionType) +
" Decrypt context: " +
decryptContext,
);
return null;
}
if (key.macKey != null && encThing.macBytes != null) {
const macData = new Uint8Array(encThing.ivBytes.byteLength + encThing.dataBytes.byteLength);
macData.set(new Uint8Array(encThing.ivBytes), 0);
macData.set(new Uint8Array(encThing.dataBytes), encThing.ivBytes.byteLength);
const computedMac = await this.cryptoFunctionService.hmac(macData, key.macKey, "sha256");
const computedMac = await this.cryptoFunctionService.hmac(macData, innerKey.authenticationKey, "sha256");
if (computedMac === null) {
this.logMacFailed(
"[Encrypt service#decryptToBytes] Failed to compute MAC." +
@@ -204,7 +210,7 @@ export class EncryptServiceImplementation implements EncryptService {
const result = await this.cryptoFunctionService.aesDecrypt(
encThing.dataBytes,
encThing.ivBytes,
key.encryptionType()
key.getInnerKey().encryptionKey,
"cbc",
);

View File

@@ -70,10 +70,6 @@ export class AccountKeys {
return null;
}
return Object.assign(new AccountKeys(), obj, {
cryptoSymmetricKey: EncryptionPair.fromJSON(
obj?.cryptoSymmetricKey,
SymmetricCryptoKey.fromJSON,
),
publicKey: Utils.fromByteStringToArray(obj?.publicKey),
});
}

View File

@@ -24,7 +24,7 @@ export class SymmetricCryptoKey {
meta: any;
constructor(key: Uint8Array, new_format = false) {
constructor(key: Uint8Array) {
if (key == null) {
throw new Error("Must provide key");
}
@@ -41,6 +41,7 @@ export class SymmetricCryptoKey {
authenticationKey: key.slice(32),
}
} else if (key.byteLength > 64) {
this.newFormat = true;
const decoded_key = CryptoClient.decode_userkey(key).Aes256CbcHmac;
this.key = {
type: EncryptionType.AesCbc256_HmacSha256_B64,
@@ -56,7 +57,6 @@ export class SymmetricCryptoKey {
return this.key;
}
static fromString(s: string): SymmetricCryptoKey {
if (s == null) {
return null;
@@ -68,8 +68,15 @@ export class SymmetricCryptoKey {
// For test only
toJSON() {
const innerKey = this.getInnerKey();
// The whole object is constructed from the initial key, so just store the B64 key
return { keyB64: Utils.fromBufferToB64(this) };
if (innerKey.type === EncryptionType.AesCbc256_B64) {
return { keyB64: Utils.fromBufferToB64(this.key.encryptionKey) };
} else if (innerKey.type === EncryptionType.AesCbc256_HmacSha256_B64) {
return { keyB64: Utils.fromBufferToB64(new Uint8Array([...innerKey.encryptionKey, ...innerKey.authenticationKey])) };
} else {
throw new Error("Unsupported encryption type.");
}
}
// For test only

View File

@@ -4,6 +4,7 @@ import * as forge from "node-forge";
import { Utils } from "../../platform/misc/utils";
import { CsprngArray } from "../../types/csprng";
import { CryptoFunctionService } from "../abstractions/crypto-function.service";
import { EncryptionType } from "../enums";
import { CbcDecryptParameters, EcbDecryptParameters } from "../models/domain/decrypt-parameters";
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
@@ -256,8 +257,9 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
p.data = forge.util.decode64(data);
p.iv = forge.util.decode64(iv);
p.macData = p.iv + p.data;
if (p.macKey == null && key.macKeyB64 != null) {
p.macKey = forge.util.decode64(key.macKeyB64);
const innerKey = key.getInnerKey();
if (p.macKey == null && innerKey.type === EncryptionType.AesCbc256_HmacSha256_B64) {
p.macKey = forge.util.decode64(Utils.fromBufferToB64(innerKey.authenticationKey));
}
if (mac != null) {
p.mac = forge.util.decode64(mac);