1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-10 21:33:27 +00:00

[PM-18697] Remove old symmetric key representations in symmetriccryptokey (#13598)

* Remove AES128CBC-HMAC encryption

* Increase test coverage

* Refactor symmetric keys and increase test coverage

* Re-add type 0 encryption

* Fix ts strict warning

* Remove old symmetric key representations in symmetriccryptokey

* Fix desktop build

* Fix test

* Fix build

* Update libs/common/src/key-management/crypto/services/web-crypto-function.service.ts

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>

* Update libs/node/src/services/node-crypto-function.service.ts

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>

* Undo changes

* Remove cast

* Undo changes to tests

* Fix linting

* Undo removing new Uint8Array in aesDecryptFastParameters

* Fix merge conflicts

* Fix test

* Fix another test

* Fix test

---------

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>
This commit is contained in:
Bernd Schoolmann
2025-04-21 16:57:26 +02:00
committed by GitHub
parent a250395e6d
commit 43b1f55360
12 changed files with 111 additions and 106 deletions

View File

@@ -300,7 +300,7 @@ describe("MainBiometricsService", function () {
expect(userKey).not.toBeNull(); expect(userKey).not.toBeNull();
expect(userKey!.keyB64).toBe(biometricKey); expect(userKey!.keyB64).toBe(biometricKey);
expect(userKey!.encType).toBe(EncryptionType.AesCbc256_HmacSha256_B64); expect(userKey!.inner().type).toBe(EncryptionType.AesCbc256_HmacSha256_B64);
expect(osBiometricsService.getBiometricKey).toHaveBeenCalledWith( expect(osBiometricsService.getBiometricKey).toHaveBeenCalledWith(
"Bitwarden_biometric", "Bitwarden_biometric",
`${userId}_user_biometric`, `${userId}_user_biometric`,

View File

@@ -1,5 +1,7 @@
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { EncryptionType } from "@bitwarden/common/platform/enums";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { biometrics, passwords } from "@bitwarden/desktop-napi"; import { biometrics, passwords } from "@bitwarden/desktop-napi";
@@ -218,7 +220,13 @@ export default class OsBiometricsServiceWindows implements OsBiometricService {
symmetricKey: SymmetricCryptoKey, symmetricKey: SymmetricCryptoKey,
clientKeyPartB64: string | undefined, clientKeyPartB64: string | undefined,
): biometrics.KeyMaterial { ): biometrics.KeyMaterial {
const key = symmetricKey?.macKeyB64 ?? symmetricKey?.keyB64; let key = null;
const innerKey = symmetricKey.inner();
if (innerKey.type === EncryptionType.AesCbc256_HmacSha256_B64) {
key = Utils.fromBufferToB64(innerKey.authenticationKey);
} else {
key = Utils.fromBufferToB64(innerKey.encryptionKey);
}
const result = { const result = {
osKeyPartB64: key, osKeyPartB64: key,

View File

@@ -106,7 +106,9 @@ describe("AuthRequestService", () => {
}); });
it("should use the master key and hash if they exist", async () => { it("should use the master key and hash if they exist", async () => {
masterPasswordService.masterKeySubject.next({ encKey: new Uint8Array(64) } as MasterKey); masterPasswordService.masterKeySubject.next(
new SymmetricCryptoKey(new Uint8Array(32)) as MasterKey,
);
masterPasswordService.masterKeyHashSubject.next("MASTER_KEY_HASH"); masterPasswordService.masterKeyHashSubject.next("MASTER_KEY_HASH");
await sut.approveOrDenyAuthRequest( await sut.approveOrDenyAuthRequest(
@@ -115,7 +117,7 @@ describe("AuthRequestService", () => {
); );
expect(encryptService.encapsulateKeyUnsigned).toHaveBeenCalledWith( expect(encryptService.encapsulateKeyUnsigned).toHaveBeenCalledWith(
{ encKey: new Uint8Array(64) }, new SymmetricCryptoKey(new Uint8Array(32)),
expect.anything(), expect.anything(),
); );
}); });

View File

@@ -14,6 +14,7 @@ import { AuthRequestPushNotification } from "@bitwarden/common/models/response/n
import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service"; import { AppIdService } from "@bitwarden/common/platform/abstractions/app-id.service";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { import {
AUTH_REQUEST_DISK_LOCAL, AUTH_REQUEST_DISK_LOCAL,
StateProvider, StateProvider,
@@ -120,7 +121,10 @@ export class AuthRequestService implements AuthRequestServiceAbstraction {
keyToEncrypt = await this.keyService.getUserKey(); keyToEncrypt = await this.keyService.getUserKey();
} }
const encryptedKey = await this.encryptService.encapsulateKeyUnsigned(keyToEncrypt, pubKey); const encryptedKey = await this.encryptService.encapsulateKeyUnsigned(
keyToEncrypt as SymmetricCryptoKey,
pubKey,
);
const response = new PasswordlessAuthRequest( const response = new PasswordlessAuthRequest(
encryptedKey.encryptedString, encryptedKey.encryptedString,

View File

@@ -47,7 +47,7 @@ export class EncryptServiceImplementation implements EncryptService {
} }
if (this.blockType0) { if (this.blockType0) {
if (key.encType === EncryptionType.AesCbc256_B64 || key.key.byteLength < 64) { if (key.inner().type === EncryptionType.AesCbc256_B64 || key.key.byteLength < 64) {
throw new Error("Type 0 encryption is not supported."); throw new Error("Type 0 encryption is not supported.");
} }
} }
@@ -84,7 +84,7 @@ export class EncryptServiceImplementation implements EncryptService {
} }
if (this.blockType0) { if (this.blockType0) {
if (key.encType === EncryptionType.AesCbc256_B64 || key.key.byteLength < 64) { if (key.inner().type === EncryptionType.AesCbc256_B64 || key.key.byteLength < 64) {
throw new Error("Type 0 encryption is not supported."); throw new Error("Type 0 encryption is not supported.");
} }
} }
@@ -124,7 +124,7 @@ export class EncryptServiceImplementation implements EncryptService {
if (encString.encryptionType !== innerKey.type) { if (encString.encryptionType !== innerKey.type) {
this.logDecryptError( this.logDecryptError(
"Key encryption type does not match payload encryption type", "Key encryption type does not match payload encryption type",
key.encType, innerKey.type,
encString.encryptionType, encString.encryptionType,
decryptContext, decryptContext,
); );
@@ -148,7 +148,7 @@ export class EncryptServiceImplementation implements EncryptService {
if (!macsEqual) { if (!macsEqual) {
this.logMacFailed( this.logMacFailed(
"decryptToUtf8 MAC comparison failed. Key or payload has changed.", "decryptToUtf8 MAC comparison failed. Key or payload has changed.",
key.encType, innerKey.type,
encString.encryptionType, encString.encryptionType,
decryptContext, decryptContext,
); );
@@ -191,7 +191,7 @@ export class EncryptServiceImplementation implements EncryptService {
if (encThing.encryptionType !== inner.type) { if (encThing.encryptionType !== inner.type) {
this.logDecryptError( this.logDecryptError(
"Encryption key type mismatch", "Encryption key type mismatch",
key.encType, inner.type,
encThing.encryptionType, encThing.encryptionType,
decryptContext, decryptContext,
); );
@@ -200,19 +200,23 @@ export class EncryptServiceImplementation implements EncryptService {
if (inner.type === EncryptionType.AesCbc256_HmacSha256_B64) { if (inner.type === EncryptionType.AesCbc256_HmacSha256_B64) {
if (encThing.macBytes == null) { if (encThing.macBytes == null) {
this.logDecryptError("Mac missing", key.encType, encThing.encryptionType, decryptContext); this.logDecryptError("Mac missing", inner.type, encThing.encryptionType, decryptContext);
return null; return null;
} }
const macData = new Uint8Array(encThing.ivBytes.byteLength + encThing.dataBytes.byteLength); const macData = new Uint8Array(encThing.ivBytes.byteLength + encThing.dataBytes.byteLength);
macData.set(new Uint8Array(encThing.ivBytes), 0); macData.set(new Uint8Array(encThing.ivBytes), 0);
macData.set(new Uint8Array(encThing.dataBytes), encThing.ivBytes.byteLength); 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,
inner.authenticationKey,
"sha256",
);
const macsMatch = await this.cryptoFunctionService.compare(encThing.macBytes, computedMac); const macsMatch = await this.cryptoFunctionService.compare(encThing.macBytes, computedMac);
if (!macsMatch) { if (!macsMatch) {
this.logMacFailed( this.logMacFailed(
"MAC comparison failed. Key or payload has changed.", "MAC comparison failed. Key or payload has changed.",
key.encType, inner.type,
encThing.encryptionType, encThing.encryptionType,
decryptContext, decryptContext,
); );
@@ -222,14 +226,14 @@ export class EncryptServiceImplementation implements EncryptService {
return await this.cryptoFunctionService.aesDecrypt( return await this.cryptoFunctionService.aesDecrypt(
encThing.dataBytes, encThing.dataBytes,
encThing.ivBytes, encThing.ivBytes,
key.encKey, inner.encryptionKey,
"cbc", "cbc",
); );
} else if (inner.type === EncryptionType.AesCbc256_B64) { } else if (inner.type === EncryptionType.AesCbc256_B64) {
return await this.cryptoFunctionService.aesDecrypt( return await this.cryptoFunctionService.aesDecrypt(
encThing.dataBytes, encThing.dataBytes,
encThing.ivBytes, encThing.ivBytes,
key.encKey, inner.encryptionKey,
"cbc", "cbc",
); );
} }

View File

@@ -6,7 +6,10 @@ import { EncryptionType } from "@bitwarden/common/platform/enums";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer"; import { EncArrayBuffer } from "@bitwarden/common/platform/models/domain/enc-array-buffer";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string"; import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import {
Aes256CbcHmacKey,
SymmetricCryptoKey,
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { CsprngArray } from "@bitwarden/common/types/csprng"; import { CsprngArray } from "@bitwarden/common/types/csprng";
import { makeStaticByteArray } from "../../../../spec"; import { makeStaticByteArray } from "../../../../spec";
@@ -64,6 +67,10 @@ describe("EncryptService", () => {
const key = new SymmetricCryptoKey(makeStaticByteArray(32)); const key = new SymmetricCryptoKey(makeStaticByteArray(32));
const mock32Key = mock<SymmetricCryptoKey>(); const mock32Key = mock<SymmetricCryptoKey>();
mock32Key.key = makeStaticByteArray(32); mock32Key.key = makeStaticByteArray(32);
mock32Key.inner.mockReturnValue({
type: 0,
encryptionKey: mock32Key.key,
});
await expect(encryptService.encrypt(null!, key)).rejects.toThrow( await expect(encryptService.encrypt(null!, key)).rejects.toThrow(
"Type 0 encryption is not supported.", "Type 0 encryption is not supported.",
@@ -146,6 +153,10 @@ describe("EncryptService", () => {
const key = new SymmetricCryptoKey(makeStaticByteArray(32)); const key = new SymmetricCryptoKey(makeStaticByteArray(32));
const mock32Key = mock<SymmetricCryptoKey>(); const mock32Key = mock<SymmetricCryptoKey>();
mock32Key.key = makeStaticByteArray(32); mock32Key.key = makeStaticByteArray(32);
mock32Key.inner.mockReturnValue({
type: 0,
encryptionKey: mock32Key.key,
});
await expect(encryptService.encryptToBytes(plainValue, key)).rejects.toThrow( await expect(encryptService.encryptToBytes(plainValue, key)).rejects.toThrow(
"Type 0 encryption is not supported.", "Type 0 encryption is not supported.",
@@ -228,7 +239,7 @@ describe("EncryptService", () => {
expect(cryptoFunctionService.aesDecrypt).toBeCalledWith( expect(cryptoFunctionService.aesDecrypt).toBeCalledWith(
expect.toEqualBuffer(encBuffer.dataBytes), expect.toEqualBuffer(encBuffer.dataBytes),
expect.toEqualBuffer(encBuffer.ivBytes), expect.toEqualBuffer(encBuffer.ivBytes),
expect.toEqualBuffer(key.encKey), expect.toEqualBuffer(key.inner().encryptionKey),
"cbc", "cbc",
); );
@@ -249,7 +260,7 @@ describe("EncryptService", () => {
expect(cryptoFunctionService.aesDecrypt).toBeCalledWith( expect(cryptoFunctionService.aesDecrypt).toBeCalledWith(
expect.toEqualBuffer(encBuffer.dataBytes), expect.toEqualBuffer(encBuffer.dataBytes),
expect.toEqualBuffer(encBuffer.ivBytes), expect.toEqualBuffer(encBuffer.ivBytes),
expect.toEqualBuffer(key.encKey), expect.toEqualBuffer(key.inner().encryptionKey),
"cbc", "cbc",
); );
@@ -267,7 +278,7 @@ describe("EncryptService", () => {
expect(cryptoFunctionService.hmac).toBeCalledWith( expect(cryptoFunctionService.hmac).toBeCalledWith(
expect.toEqualBuffer(expectedMacData), expect.toEqualBuffer(expectedMacData),
key.macKey, (key.inner() as Aes256CbcHmacKey).authenticationKey,
"sha256", "sha256",
); );

View File

@@ -1,6 +1,7 @@
import * as argon2 from "argon2-browser"; import * as argon2 from "argon2-browser";
import * as forge from "node-forge"; import * as forge from "node-forge";
import { EncryptionType } from "../../../platform/enums";
import { Utils } from "../../../platform/misc/utils"; import { Utils } from "../../../platform/misc/utils";
import { import {
CbcDecryptParameters, CbcDecryptParameters,
@@ -247,37 +248,26 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
mac: string | null, mac: string | null,
key: SymmetricCryptoKey, key: SymmetricCryptoKey,
): CbcDecryptParameters<string> { ): CbcDecryptParameters<string> {
const p = {} as CbcDecryptParameters<string>; const innerKey = key.inner();
if (key.meta != null) { if (innerKey.type === EncryptionType.AesCbc256_B64) {
p.encKey = key.meta.encKeyByteString; return {
p.macKey = key.meta.macKeyByteString; iv: forge.util.decode64(iv),
data: forge.util.decode64(data),
encKey: forge.util.createBuffer(innerKey.encryptionKey).getBytes(),
} as CbcDecryptParameters<string>;
} else if (innerKey.type === EncryptionType.AesCbc256_HmacSha256_B64) {
const macData = forge.util.decode64(iv) + forge.util.decode64(data);
return {
iv: forge.util.decode64(iv),
data: forge.util.decode64(data),
encKey: forge.util.createBuffer(innerKey.encryptionKey).getBytes(),
macKey: forge.util.createBuffer(innerKey.authenticationKey).getBytes(),
mac: forge.util.decode64(mac!),
macData,
} as CbcDecryptParameters<string>;
} else {
throw new Error("Unsupported encryption type.");
} }
if (p.encKey == null) {
p.encKey = forge.util.decode64(key.encKeyB64);
}
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);
}
if (mac != null) {
p.mac = forge.util.decode64(mac);
}
// cache byte string keys for later
if (key.meta == null) {
key.meta = {};
}
if (key.meta.encKeyByteString == null) {
key.meta.encKeyByteString = p.encKey;
}
if (p.macKey != null && key.meta.macKeyByteString == null) {
key.meta.macKeyByteString = p.macKey;
}
return p;
} }
aesDecryptFast({ aesDecryptFast({

View File

@@ -252,7 +252,9 @@ describe("KeyConnectorService", () => {
const organization = organizationData(true, true, "https://key-connector-url.com", 2, false); const organization = organizationData(true, true, "https://key-connector-url.com", 2, false);
const masterKey = getMockMasterKey(); const masterKey = getMockMasterKey();
masterPasswordService.masterKeySubject.next(masterKey); masterPasswordService.masterKeySubject.next(masterKey);
const keyConnectorRequest = new KeyConnectorUserKeyRequest(masterKey.encKeyB64); const keyConnectorRequest = new KeyConnectorUserKeyRequest(
Utils.fromBufferToB64(masterKey.inner().encryptionKey),
);
jest.spyOn(keyConnectorService, "getManagingOrganization").mockResolvedValue(organization); jest.spyOn(keyConnectorService, "getManagingOrganization").mockResolvedValue(organization);
jest.spyOn(apiService, "postUserKeyToKeyConnector").mockResolvedValue(); jest.spyOn(apiService, "postUserKeyToKeyConnector").mockResolvedValue();
@@ -273,7 +275,9 @@ describe("KeyConnectorService", () => {
// Arrange // Arrange
const organization = organizationData(true, true, "https://key-connector-url.com", 2, false); const organization = organizationData(true, true, "https://key-connector-url.com", 2, false);
const masterKey = getMockMasterKey(); const masterKey = getMockMasterKey();
const keyConnectorRequest = new KeyConnectorUserKeyRequest(masterKey.encKeyB64); const keyConnectorRequest = new KeyConnectorUserKeyRequest(
Utils.fromBufferToB64(masterKey.inner().encryptionKey),
);
const error = new Error("Failed to post user key to key connector"); const error = new Error("Failed to post user key to key connector");
organizationService.organizations$.mockReturnValue(of([organization])); organizationService.organizations$.mockReturnValue(of([organization]));

View File

@@ -95,7 +95,9 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction {
userId ??= (await firstValueFrom(this.accountService.activeAccount$))?.id; userId ??= (await firstValueFrom(this.accountService.activeAccount$))?.id;
const organization = await this.getManagingOrganization(userId); const organization = await this.getManagingOrganization(userId);
const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId)); const masterKey = await firstValueFrom(this.masterPasswordService.masterKey$(userId));
const keyConnectorRequest = new KeyConnectorUserKeyRequest(masterKey.encKeyB64); const keyConnectorRequest = new KeyConnectorUserKeyRequest(
Utils.fromBufferToB64(masterKey.inner().encryptionKey),
);
try { try {
await this.apiService.postUserKeyToKeyConnector( await this.apiService.postUserKeyToKeyConnector(
@@ -157,7 +159,9 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction {
await this.tokenService.getEmail(), await this.tokenService.getEmail(),
kdfConfig, kdfConfig,
); );
const keyConnectorRequest = new KeyConnectorUserKeyRequest(masterKey.encKeyB64); const keyConnectorRequest = new KeyConnectorUserKeyRequest(
Utils.fromBufferToB64(masterKey.inner().encryptionKey),
);
await this.masterPasswordService.setMasterKey(masterKey, userId); await this.masterPasswordService.setMasterKey(masterKey, userId);
const userKey = await this.keyService.makeUserKey(masterKey); const userKey = await this.keyService.makeUserKey(masterKey);

View File

@@ -2,7 +2,7 @@ import { makeStaticByteArray } from "../../../../spec";
import { EncryptionType } from "../../enums"; import { EncryptionType } from "../../enums";
import { Utils } from "../../misc/utils"; import { Utils } from "../../misc/utils";
import { SymmetricCryptoKey } from "./symmetric-crypto-key"; import { Aes256CbcHmacKey, SymmetricCryptoKey } from "./symmetric-crypto-key";
describe("SymmetricCryptoKey", () => { describe("SymmetricCryptoKey", () => {
it("errors if no key", () => { it("errors if no key", () => {
@@ -19,13 +19,8 @@ describe("SymmetricCryptoKey", () => {
const cryptoKey = new SymmetricCryptoKey(key); const cryptoKey = new SymmetricCryptoKey(key);
expect(cryptoKey).toEqual({ expect(cryptoKey).toEqual({
encKey: key,
encKeyB64: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=",
encType: EncryptionType.AesCbc256_B64,
key: key, key: key,
keyB64: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=", keyB64: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=",
macKey: null,
macKeyB64: undefined,
innerKey: { innerKey: {
type: EncryptionType.AesCbc256_B64, type: EncryptionType.AesCbc256_B64,
encryptionKey: key, encryptionKey: key,
@@ -38,14 +33,9 @@ describe("SymmetricCryptoKey", () => {
const cryptoKey = new SymmetricCryptoKey(key); const cryptoKey = new SymmetricCryptoKey(key);
expect(cryptoKey).toEqual({ expect(cryptoKey).toEqual({
encKey: key.slice(0, 32),
encKeyB64: "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=",
encType: EncryptionType.AesCbc256_HmacSha256_B64,
key: key, key: key,
keyB64: keyB64:
"AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+Pw==", "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+Pw==",
macKey: key.slice(32, 64),
macKeyB64: "ICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj8=",
innerKey: { innerKey: {
type: EncryptionType.AesCbc256_HmacSha256_B64, type: EncryptionType.AesCbc256_HmacSha256_B64,
encryptionKey: key.slice(0, 32), encryptionKey: key.slice(0, 32),
@@ -86,8 +76,8 @@ describe("SymmetricCryptoKey", () => {
expect(actual).toEqual({ expect(actual).toEqual({
type: EncryptionType.AesCbc256_HmacSha256_B64, type: EncryptionType.AesCbc256_HmacSha256_B64,
encryptionKey: key.encKey, encryptionKey: key.inner().encryptionKey,
authenticationKey: key.macKey, authenticationKey: (key.inner() as Aes256CbcHmacKey).authenticationKey,
}); });
}); });
@@ -95,7 +85,7 @@ describe("SymmetricCryptoKey", () => {
const key = new SymmetricCryptoKey(makeStaticByteArray(32)); const key = new SymmetricCryptoKey(makeStaticByteArray(32));
const actual = key.toEncoded(); const actual = key.toEncoded();
expect(actual).toEqual(key.encKey); expect(actual).toEqual(key.inner().encryptionKey);
}); });
it("toEncoded returns encoded key for AesCbc256_HmacSha256_B64", () => { it("toEncoded returns encoded key for AesCbc256_HmacSha256_B64", () => {

View File

@@ -25,15 +25,7 @@ export class SymmetricCryptoKey {
private innerKey: Aes256CbcHmacKey | Aes256CbcKey; private innerKey: Aes256CbcHmacKey | Aes256CbcKey;
key: Uint8Array; key: Uint8Array;
encKey: Uint8Array;
macKey?: Uint8Array;
encType: EncryptionType;
keyB64: string; keyB64: string;
encKeyB64: string;
macKeyB64: string;
meta: any;
/** /**
* @param key The key in one of the permitted serialization formats * @param key The key in one of the permitted serialization formats
@@ -48,30 +40,16 @@ export class SymmetricCryptoKey {
type: EncryptionType.AesCbc256_B64, type: EncryptionType.AesCbc256_B64,
encryptionKey: key, encryptionKey: key,
}; };
this.encType = EncryptionType.AesCbc256_B64;
this.key = key; this.key = key;
this.keyB64 = Utils.fromBufferToB64(this.key); this.keyB64 = this.toBase64();
this.encKey = key;
this.encKeyB64 = Utils.fromBufferToB64(this.encKey);
this.macKey = null;
this.macKeyB64 = undefined;
} else if (key.byteLength === 64) { } else if (key.byteLength === 64) {
this.innerKey = { this.innerKey = {
type: EncryptionType.AesCbc256_HmacSha256_B64, type: EncryptionType.AesCbc256_HmacSha256_B64,
encryptionKey: key.slice(0, 32), encryptionKey: key.slice(0, 32),
authenticationKey: key.slice(32), authenticationKey: key.slice(32),
}; };
this.encType = EncryptionType.AesCbc256_HmacSha256_B64;
this.key = key; this.key = key;
this.keyB64 = Utils.fromBufferToB64(this.key); this.keyB64 = this.toBase64();
this.encKey = key.slice(0, 32);
this.encKeyB64 = Utils.fromBufferToB64(this.encKey);
this.macKey = key.slice(32);
this.macKeyB64 = Utils.fromBufferToB64(this.macKey);
} else { } else {
throw new Error(`Unsupported encType/key length ${key.byteLength}`); throw new Error(`Unsupported encType/key length ${key.byteLength}`);
} }

View File

@@ -3,6 +3,7 @@ import * as crypto from "crypto";
import * as forge from "node-forge"; import * as forge from "node-forge";
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service"; import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
import { EncryptionType } from "@bitwarden/common/platform/enums";
import { Utils } from "@bitwarden/common/platform/misc/utils"; import { Utils } from "@bitwarden/common/platform/misc/utils";
import { import {
CbcDecryptParameters, CbcDecryptParameters,
@@ -172,24 +173,33 @@ export class NodeCryptoFunctionService implements CryptoFunctionService {
mac: string | null, mac: string | null,
key: SymmetricCryptoKey, key: SymmetricCryptoKey,
): CbcDecryptParameters<Uint8Array> { ): CbcDecryptParameters<Uint8Array> {
const p = {} as CbcDecryptParameters<Uint8Array>; const dataBytes = Utils.fromB64ToArray(data);
p.encKey = key.encKey; const ivBytes = Utils.fromB64ToArray(iv);
p.data = Utils.fromB64ToArray(data); const macBytes = mac != null ? Utils.fromB64ToArray(mac) : null;
p.iv = Utils.fromB64ToArray(iv);
const macData = new Uint8Array(p.iv.byteLength + p.data.byteLength); const innerKey = key.inner();
macData.set(new Uint8Array(p.iv), 0);
macData.set(new Uint8Array(p.data), p.iv.byteLength);
p.macData = macData;
if (key.macKey != null) { if (innerKey.type === EncryptionType.AesCbc256_B64) {
p.macKey = key.macKey; return {
iv: ivBytes,
data: dataBytes,
encKey: innerKey.encryptionKey,
} as CbcDecryptParameters<Uint8Array>;
} else if (innerKey.type === EncryptionType.AesCbc256_HmacSha256_B64) {
const macData = new Uint8Array(ivBytes.byteLength + dataBytes.byteLength);
macData.set(new Uint8Array(ivBytes), 0);
macData.set(new Uint8Array(dataBytes), ivBytes.byteLength);
return {
iv: ivBytes,
data: dataBytes,
mac: macBytes,
macData: macData,
encKey: innerKey.encryptionKey,
macKey: innerKey.authenticationKey,
} as CbcDecryptParameters<Uint8Array>;
} else {
throw new Error("Unsupported encryption type");
} }
if (mac != null) {
p.mac = Utils.fromB64ToArray(mac);
}
return p;
} }
async aesDecryptFast({ async aesDecryptFast({