diff --git a/.vscode/settings.json b/.vscode/settings.json index 295c290a37..8f89bc03b8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,5 @@ { - "cSpell.words": ["Csprng", "decryptable", "Popout", "Reprompt", "takeuntil"], + "cSpell.words": ["Csprng", "Decapsulation", "decryptable", "Popout", "Reprompt", "takeuntil"], "search.exclude": { "**/locales/[^e]*/messages.json": true, "**/locales/*[^n]/messages.json": true, diff --git a/apps/web/src/app/auth/core/services/rotateable-key-set.service.spec.ts b/apps/web/src/app/auth/core/services/rotateable-key-set.service.spec.ts index 1a83fed37b..8579c4c1dc 100644 --- a/apps/web/src/app/auth/core/services/rotateable-key-set.service.spec.ts +++ b/apps/web/src/app/auth/core/services/rotateable-key-set.service.spec.ts @@ -36,7 +36,7 @@ describe("RotateableKeySetService", () => { keyService.makeKeyPair.mockResolvedValue(["publicKey", encryptedPrivateKey as any]); keyService.getUserKey.mockResolvedValue({ key: userKey.key } as any); encryptService.encapsulateKeyUnsigned.mockResolvedValue(encryptedUserKey as any); - encryptService.encrypt.mockResolvedValue(encryptedPublicKey as any); + encryptService.wrapEncapsulationKey.mockResolvedValue(encryptedPublicKey as any); const result = await service.createKeySet(externalKey as any); diff --git a/apps/web/src/app/auth/core/services/rotateable-key-set.service.ts b/apps/web/src/app/auth/core/services/rotateable-key-set.service.ts index 8510aa1c29..ef78e09e6b 100644 --- a/apps/web/src/app/auth/core/services/rotateable-key-set.service.ts +++ b/apps/web/src/app/auth/core/services/rotateable-key-set.service.ts @@ -29,7 +29,10 @@ export class RotateableKeySetService { userKey, rawPublicKey, ); - const encryptedPublicKey = await this.encryptService.encrypt(rawPublicKey, userKey); + const encryptedPublicKey = await this.encryptService.wrapEncapsulationKey( + rawPublicKey, + userKey, + ); return new RotateableKeySet(encryptedUserKey, encryptedPublicKey, encryptedPrivateKey); } @@ -62,7 +65,10 @@ export class RotateableKeySetService { if (publicKey == null) { throw new Error("failed to rotate key set: could not decrypt public key"); } - const newEncryptedPublicKey = await this.encryptService.encrypt(publicKey, newUserKey); + const newEncryptedPublicKey = await this.encryptService.wrapEncapsulationKey( + publicKey, + newUserKey, + ); const newEncryptedUserKey = await this.encryptService.encapsulateKeyUnsigned( newUserKey, publicKey, diff --git a/apps/web/src/app/auth/organization-invite/accept-organization.service.spec.ts b/apps/web/src/app/auth/organization-invite/accept-organization.service.spec.ts index 67dbee223d..0a30aa1647 100644 --- a/apps/web/src/app/auth/organization-invite/accept-organization.service.spec.ts +++ b/apps/web/src/app/auth/organization-invite/accept-organization.service.spec.ts @@ -92,6 +92,9 @@ describe("AcceptOrganizationInviteService", () => { "orgPublicKey", { encryptedString: "string" } as EncString, ]); + encryptService.wrapDecapsulationKey.mockResolvedValue({ + encryptedString: "string", + } as EncString); encryptService.encrypt.mockResolvedValue({ encryptedString: "string" } as EncString); const invite = createOrgInvite({ initOrganization: true }); diff --git a/apps/web/src/app/billing/organizations/organization-plans.component.ts b/apps/web/src/app/billing/organizations/organization-plans.component.ts index 59f8dd34c3..e373b0d4de 100644 --- a/apps/web/src/app/billing/organizations/organization-plans.component.ts +++ b/apps/web/src/app/billing/organizations/organization-plans.component.ts @@ -812,7 +812,7 @@ export class OrganizationPlansComponent implements OnInit, OnDestroy { ); const providerKey = await this.keyService.getProviderKey(this.providerId); providerRequest.organizationCreateRequest.key = ( - await this.encryptService.encrypt(orgKey.key, providerKey) + await this.encryptService.wrapSymmetricKey(orgKey, providerKey) ).encryptedString; const orgId = ( await this.apiService.postProviderCreateOrganization(this.providerId, providerRequest) diff --git a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts index 18ee9462f4..dac5afa191 100644 --- a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts +++ b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts @@ -183,7 +183,10 @@ describe("KeyRotationService", () => { mockKeyService.hashMasterKey.mockResolvedValue("mockMasterPasswordHash"); mockConfigService.getFeatureFlag.mockResolvedValue(true); - mockEncryptService.encrypt.mockResolvedValue({ + mockEncryptService.wrapSymmetricKey.mockResolvedValue({ + encryptedString: "mockEncryptedData", + } as any); + mockEncryptService.wrapDecapsulationKey.mockResolvedValue({ encryptedString: "mockEncryptedData", } as any); diff --git a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts index 7d00e970ad..ec38f49bae 100644 --- a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts +++ b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts @@ -145,7 +145,9 @@ export class UserKeyRotationService { const { privateKey, publicKey } = keyPair; const accountKeysRequest = new AccountKeysRequest( - (await this.encryptService.encrypt(privateKey, newUnencryptedUserKey)).encryptedString!, + ( + await this.encryptService.wrapDecapsulationKey(privateKey, newUnencryptedUserKey) + ).encryptedString!, Utils.fromBufferToB64(publicKey), ); @@ -427,6 +429,6 @@ export class UserKeyRotationService { if (privateKey == null) { throw new Error("No private key found for user key rotation"); } - return (await this.encryptService.encrypt(privateKey, newUserKey)).encryptedString; + return (await this.encryptService.wrapDecapsulationKey(privateKey, newUserKey)).encryptedString; } } diff --git a/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts b/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts index d3482ea67a..844c6b779a 100644 --- a/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts +++ b/bitwarden_license/bit-web/src/app/admin-console/providers/services/web-provider.service.ts @@ -36,7 +36,7 @@ export class WebProviderService { const orgKey = await this.keyService.getOrgKey(organizationId); const providerKey = await this.keyService.getProviderKey(providerId); - const encryptedOrgKey = await this.encryptService.encrypt(orgKey.key, providerKey); + const encryptedOrgKey = await this.encryptService.wrapSymmetricKey(orgKey, providerKey); const request = new ProviderAddOrganizationRequest(); request.organizationId = organizationId; @@ -55,7 +55,7 @@ export class WebProviderService { ), ); const providerKey = await this.keyService.getProviderKey(providerId); - const encryptedOrgKey = await this.encryptService.encrypt(orgKey.key, providerKey); + const encryptedOrgKey = await this.encryptService.wrapSymmetricKey(orgKey, providerKey); await this.providerApiService.addOrganizationToProvider(providerId, { key: encryptedOrgKey.encryptedString, organizationId, @@ -81,8 +81,8 @@ export class WebProviderService { const providerKey = await this.keyService.getProviderKey(providerId); - const encryptedProviderKey = await this.encryptService.encrypt( - organizationKey.key, + const encryptedProviderKey = await this.encryptService.wrapSymmetricKey( + organizationKey, providerKey, ); diff --git a/libs/angular/src/auth/components/set-password.component.ts b/libs/angular/src/auth/components/set-password.component.ts index ee0756355c..230be90b7a 100644 --- a/libs/angular/src/auth/components/set-password.component.ts +++ b/libs/angular/src/auth/components/set-password.component.ts @@ -178,7 +178,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent implements const existingUserPublicKeyB64 = Utils.fromBufferToB64(existingUserPublicKey); newKeyPair = [ existingUserPublicKeyB64, - await this.encryptService.encrypt(existingUserPrivateKey, userKey[0]), + await this.encryptService.wrapDecapsulationKey(existingUserPrivateKey, userKey[0]), ]; } else { newKeyPair = await this.keyService.makeKeyPair(userKey[0]); diff --git a/libs/auth/src/common/services/pin/pin.service.implementation.ts b/libs/auth/src/common/services/pin/pin.service.implementation.ts index 31f0c5b017..c0034020de 100644 --- a/libs/auth/src/common/services/pin/pin.service.implementation.ts +++ b/libs/auth/src/common/services/pin/pin.service.implementation.ts @@ -174,7 +174,8 @@ export class PinService implements PinServiceAbstraction { ); const kdfConfig = await this.kdfConfigService.getKdfConfig(); const pinKey = await this.makePinKey(pin, email, kdfConfig); - return await this.encryptService.encrypt(userKey.key, pinKey); + + return await this.encryptService.wrapSymmetricKey(userKey, pinKey); } async storePinKeyEncryptedUserKey( diff --git a/libs/auth/src/common/services/pin/pin.service.spec.ts b/libs/auth/src/common/services/pin/pin.service.spec.ts index e0b18c74bd..fd33f5d207 100644 --- a/libs/auth/src/common/services/pin/pin.service.spec.ts +++ b/libs/auth/src/common/services/pin/pin.service.spec.ts @@ -170,7 +170,7 @@ describe("PinService", () => { await sut.createPinKeyEncryptedUserKey(mockPin, mockUserKey, mockUserId); // Assert - expect(encryptService.encrypt).toHaveBeenCalledWith(mockUserKey.key, mockPinKey); + expect(encryptService.wrapSymmetricKey).toHaveBeenCalledWith(mockUserKey, mockPinKey); }); }); diff --git a/libs/common/src/key-management/crypto/abstractions/encrypt.service.ts b/libs/common/src/key-management/crypto/abstractions/encrypt.service.ts index a1e54f7064..5f21d86bc6 100644 --- a/libs/common/src/key-management/crypto/abstractions/encrypt.service.ts +++ b/libs/common/src/key-management/crypto/abstractions/encrypt.service.ts @@ -7,8 +7,50 @@ import { EncString } from "../../../platform/models/domain/enc-string"; import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key"; export abstract class EncryptService { + /** + * Encrypts a string or Uint8Array to an EncString + * @param plainValue - The value to encrypt + * @param key - The key to encrypt the value with + */ abstract encrypt(plainValue: string | Uint8Array, key: SymmetricCryptoKey): Promise; + /** + * Encrypts a value to a Uint8Array + * @param plainValue - The value to encrypt + * @param key - The key to encrypt the value with + */ abstract encryptToBytes(plainValue: Uint8Array, key: SymmetricCryptoKey): Promise; + + /** + * Wraps a decapsulation key (Private key) with a symmetric key + * @see {@link https://en.wikipedia.org/wiki/Key_wrap} + * @param decapsulationKeyPcks8 - The private key in PKCS8 format + * @param wrappingKey - The symmetric key to wrap the private key with + */ + abstract wrapDecapsulationKey( + decapsulationKeyPcks8: Uint8Array, + wrappingKey: SymmetricCryptoKey, + ): Promise; + /** + * Wraps an encapsulation key (Public key) with a symmetric key + * @see {@link https://en.wikipedia.org/wiki/Key_wrap} + * @param encapsulationKeySpki - The public key in SPKI format + * @param wrappingKey - The symmetric key to wrap the public key with + */ + abstract wrapEncapsulationKey( + encapsulationKeySpki: Uint8Array, + wrappingKey: SymmetricCryptoKey, + ): Promise; + /** + * Wraps a symmetric key with another symmetric key + * @see {@link https://en.wikipedia.org/wiki/Key_wrap} + * @param keyToBeWrapped - The symmetric key to wrap + * @param wrappingKey - The symmetric key to wrap the encapsulated key with + */ + abstract wrapSymmetricKey( + keyToBeWrapped: SymmetricCryptoKey, + wrappingKey: SymmetricCryptoKey, + ): Promise; + /** * Decrypts an EncString to a string * @param encString - The EncString to decrypt @@ -39,6 +81,7 @@ export abstract class EncryptService { /** * Encapsulates a symmetric key with an asymmetric public key * Note: This does not establish sender authenticity + * @see {@link https://en.wikipedia.org/wiki/Key_encapsulation_mechanism} * @param sharedKey - The symmetric key that is to be shared * @param encapsulationKey - The encapsulation key (public key) of the receiver that the key is shared with */ @@ -49,6 +92,7 @@ export abstract class EncryptService { /** * Decapsulates a shared symmetric key with an asymmetric private key * Note: This does not establish sender authenticity + * @see {@link https://en.wikipedia.org/wiki/Key_encapsulation_mechanism} * @param encryptedSharedKey - The encrypted shared symmetric key * @param decapsulationKey - The key to decapsulate with (private key) */ @@ -57,13 +101,13 @@ export abstract class EncryptService { decapsulationKey: Uint8Array, ): Promise; /** - * @deprecated Use encapsulateKeyUnsigned instead + * @deprecated Use @see {@link encapsulateKeyUnsigned} instead * @param data - The data to encrypt * @param publicKey - The public key to encrypt with */ abstract rsaEncrypt(data: Uint8Array, publicKey: Uint8Array): Promise; /** - * @deprecated Use decapsulateKeyUnsigned instead + * @deprecated Use @see {@link decapsulateKeyUnsigned} instead * @param data - The ciphertext to decrypt * @param privateKey - The privateKey to decrypt with */ diff --git a/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts b/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts index e9d8c1c30a..f431884051 100644 --- a/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts +++ b/libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts @@ -56,22 +56,79 @@ export class EncryptServiceImplementation implements EncryptService { return Promise.resolve(null); } - let plainBuf: Uint8Array; if (typeof plainValue === "string") { - plainBuf = Utils.fromUtf8ToArray(plainValue); + return this.encryptUint8Array(Utils.fromUtf8ToArray(plainValue), key); } else { - plainBuf = plainValue; + return this.encryptUint8Array(plainValue, key); + } + } + + async wrapDecapsulationKey( + decapsulationKeyPkcs8: Uint8Array, + wrappingKey: SymmetricCryptoKey, + ): Promise { + if (decapsulationKeyPkcs8 == null) { + throw new Error("No decapsulation key provided for wrapping."); + } + + if (wrappingKey == null) { + throw new Error("No wrappingKey provided for wrapping."); + } + + return await this.encryptUint8Array(decapsulationKeyPkcs8, wrappingKey); + } + + async wrapEncapsulationKey( + encapsulationKeySpki: Uint8Array, + wrappingKey: SymmetricCryptoKey, + ): Promise { + if (encapsulationKeySpki == null) { + throw new Error("No encapsulation key provided for wrapping."); + } + + if (wrappingKey == null) { + throw new Error("No wrappingKey provided for wrapping."); + } + + return await this.encryptUint8Array(encapsulationKeySpki, wrappingKey); + } + + async wrapSymmetricKey( + keyToBeWrapped: SymmetricCryptoKey, + wrappingKey: SymmetricCryptoKey, + ): Promise { + if (keyToBeWrapped == null) { + throw new Error("No keyToBeWrapped provided for wrapping."); + } + + if (wrappingKey == null) { + throw new Error("No wrappingKey provided for wrapping."); + } + + return await this.encryptUint8Array(keyToBeWrapped.key, wrappingKey); + } + + private async encryptUint8Array( + plainValue: Uint8Array, + key: SymmetricCryptoKey, + ): Promise { + if (key == null) { + throw new Error("No encryption key provided."); + } + + if (plainValue == null) { + return Promise.resolve(null); } const innerKey = key.inner(); if (innerKey.type === EncryptionType.AesCbc256_HmacSha256_B64) { - const encObj = await this.aesEncrypt(plainBuf, innerKey); + const encObj = await this.aesEncrypt(plainValue, innerKey); const iv = Utils.fromBufferToB64(encObj.iv); const data = Utils.fromBufferToB64(encObj.data); const mac = Utils.fromBufferToB64(encObj.mac); return new EncString(innerKey.type, data, iv, mac); } else if (innerKey.type === EncryptionType.AesCbc256_B64) { - const encObj = await this.aesEncryptLegacy(plainBuf, innerKey); + const encObj = await this.aesEncryptLegacy(plainValue, innerKey); const iv = Utils.fromBufferToB64(encObj.iv); const data = Utils.fromBufferToB64(encObj.data); return new EncString(innerKey.type, data, iv); diff --git a/libs/common/src/key-management/crypto/services/encrypt.service.spec.ts b/libs/common/src/key-management/crypto/services/encrypt.service.spec.ts index fb55fa9260..6b2851ad11 100644 --- a/libs/common/src/key-management/crypto/services/encrypt.service.spec.ts +++ b/libs/common/src/key-management/crypto/services/encrypt.service.spec.ts @@ -31,6 +31,88 @@ describe("EncryptService", () => { encryptService = new EncryptServiceImplementation(cryptoFunctionService, logService, true); }); + describe("wrapSymmetricKey", () => { + it("roundtrip encrypts and decrypts a symmetric key", async () => { + cryptoFunctionService.aesEncrypt.mockResolvedValue(makeStaticByteArray(64, 0)); + cryptoFunctionService.randomBytes.mockResolvedValue(makeStaticByteArray(16) as CsprngArray); + cryptoFunctionService.hmac.mockResolvedValue(makeStaticByteArray(32)); + + const key = new SymmetricCryptoKey(makeStaticByteArray(64)); + const wrappingKey = new SymmetricCryptoKey(makeStaticByteArray(64)); + const encString = await encryptService.wrapSymmetricKey(key, wrappingKey); + expect(encString.encryptionType).toEqual(EncryptionType.AesCbc256_HmacSha256_B64); + expect(encString.data).toEqual(Utils.fromBufferToB64(makeStaticByteArray(64, 0))); + }); + it("fails if key toBeWrapped is null", async () => { + const wrappingKey = new SymmetricCryptoKey(makeStaticByteArray(64)); + await expect(encryptService.wrapSymmetricKey(null, wrappingKey)).rejects.toThrow( + "No keyToBeWrapped provided for wrapping.", + ); + }); + it("fails if wrapping key is null", async () => { + const key = new SymmetricCryptoKey(makeStaticByteArray(64)); + await expect(encryptService.wrapSymmetricKey(key, null)).rejects.toThrow( + "No wrappingKey provided for wrapping.", + ); + }); + }); + + describe("wrapDecapsulationKey", () => { + it("roundtrip encrypts and decrypts a decapsulation key", async () => { + cryptoFunctionService.aesEncrypt.mockResolvedValue(makeStaticByteArray(64, 0)); + cryptoFunctionService.randomBytes.mockResolvedValue(makeStaticByteArray(16) as CsprngArray); + cryptoFunctionService.hmac.mockResolvedValue(makeStaticByteArray(32)); + + const wrappingKey = new SymmetricCryptoKey(makeStaticByteArray(64)); + const encString = await encryptService.wrapDecapsulationKey( + makeStaticByteArray(64), + wrappingKey, + ); + expect(encString.encryptionType).toEqual(EncryptionType.AesCbc256_HmacSha256_B64); + expect(encString.data).toEqual(Utils.fromBufferToB64(makeStaticByteArray(64, 0))); + }); + it("fails if decapsulation key is null", async () => { + const wrappingKey = new SymmetricCryptoKey(makeStaticByteArray(64)); + await expect(encryptService.wrapDecapsulationKey(null, wrappingKey)).rejects.toThrow( + "No decapsulation key provided for wrapping.", + ); + }); + it("fails if wrapping key is null", async () => { + const decapsulationKey = makeStaticByteArray(64); + await expect(encryptService.wrapDecapsulationKey(decapsulationKey, null)).rejects.toThrow( + "No wrappingKey provided for wrapping.", + ); + }); + }); + + describe("wrapEncapsulationKey", () => { + it("roundtrip encrypts and decrypts an encapsulationKey key", async () => { + cryptoFunctionService.aesEncrypt.mockResolvedValue(makeStaticByteArray(64, 0)); + cryptoFunctionService.randomBytes.mockResolvedValue(makeStaticByteArray(16) as CsprngArray); + cryptoFunctionService.hmac.mockResolvedValue(makeStaticByteArray(32)); + + const wrappingKey = new SymmetricCryptoKey(makeStaticByteArray(64)); + const encString = await encryptService.wrapEncapsulationKey( + makeStaticByteArray(64), + wrappingKey, + ); + expect(encString.encryptionType).toEqual(EncryptionType.AesCbc256_HmacSha256_B64); + expect(encString.data).toEqual(Utils.fromBufferToB64(makeStaticByteArray(64, 0))); + }); + it("fails if encapsulation key is null", async () => { + const wrappingKey = new SymmetricCryptoKey(makeStaticByteArray(64)); + await expect(encryptService.wrapEncapsulationKey(null, wrappingKey)).rejects.toThrow( + "No encapsulation key provided for wrapping.", + ); + }); + it("fails if wrapping key is null", async () => { + const encapsulationKey = makeStaticByteArray(64); + await expect(encryptService.wrapEncapsulationKey(encapsulationKey, null)).rejects.toThrow( + "No wrappingKey provided for wrapping.", + ); + }); + }); + describe("onServerConfigChange", () => { const newConfig = mock(); @@ -461,6 +543,12 @@ describe("EncryptService", () => { expect(actual).toEqual(encString); expect(actual.dataBytes).toEqualBuffer(encryptedData); }); + + it("throws if no data was provided", () => { + return expect(encryptService.rsaEncrypt(null, new Uint8Array(32))).rejects.toThrow( + "No data provided for encryption", + ); + }); }); describe("decapsulateKeyUnsigned", () => { diff --git a/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts b/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts index c82efa0c57..205f332d0f 100644 --- a/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts +++ b/libs/common/src/key-management/device-trust/services/device-trust.service.implementation.ts @@ -164,10 +164,10 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { this.encryptService.encapsulateKeyUnsigned(userKey, devicePublicKey), // Encrypt devicePublicKey with user key - this.encryptService.encrypt(devicePublicKey, userKey), + this.encryptService.wrapEncapsulationKey(devicePublicKey, userKey), // Encrypt devicePrivateKey with deviceKey - this.encryptService.encrypt(devicePrivateKey, deviceKey), + this.encryptService.wrapDecapsulationKey(devicePrivateKey, deviceKey), ]); // Send encrypted keys to server @@ -290,7 +290,7 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction { ); // Re-encrypt the device public key with the new user key - const encryptedDevicePublicKey = await this.encryptService.encrypt( + const encryptedDevicePublicKey = await this.encryptService.wrapEncapsulationKey( decryptedDevicePublicKey, newUserKey, ); diff --git a/libs/common/src/key-management/device-trust/services/device-trust.service.spec.ts b/libs/common/src/key-management/device-trust/services/device-trust.service.spec.ts index 8431fe4cc3..e78fc01b69 100644 --- a/libs/common/src/key-management/device-trust/services/device-trust.service.spec.ts +++ b/libs/common/src/key-management/device-trust/services/device-trust.service.spec.ts @@ -346,8 +346,6 @@ describe("deviceTrustService", () => { const deviceRsaKeyLength = 2048; let mockDeviceRsaKeyPair: [Uint8Array, Uint8Array]; - let mockDevicePrivateKey: Uint8Array; - let mockDevicePublicKey: Uint8Array; let mockDevicePublicKeyEncryptedUserKey: EncString; let mockUserKeyEncryptedDevicePublicKey: EncString; let mockDeviceKeyEncryptedDevicePrivateKey: EncString; @@ -366,7 +364,8 @@ describe("deviceTrustService", () => { let rsaGenerateKeyPairSpy: jest.SpyInstance; let cryptoSvcGetUserKeySpy: jest.SpyInstance; let cryptoSvcRsaEncryptSpy: jest.SpyInstance; - let encryptServiceEncryptSpy: jest.SpyInstance; + let encryptServiceWrapDecapsulationKeySpy: jest.SpyInstance; + let encryptServiceWrapEncapsulationKeySpy: jest.SpyInstance; let appIdServiceGetAppIdSpy: jest.SpyInstance; let devicesApiServiceUpdateTrustedDeviceKeysSpy: jest.SpyInstance; @@ -384,9 +383,6 @@ describe("deviceTrustService", () => { new Uint8Array(deviceRsaKeyLength), ]; - mockDevicePublicKey = mockDeviceRsaKeyPair[0]; - mockDevicePrivateKey = mockDeviceRsaKeyPair[1]; - mockDevicePublicKeyEncryptedUserKey = new EncString( EncryptionType.Rsa2048_OaepSha1_B64, "mockDevicePublicKeyEncryptedUserKey", @@ -419,13 +415,17 @@ describe("deviceTrustService", () => { .spyOn(encryptService, "encapsulateKeyUnsigned") .mockResolvedValue(mockDevicePublicKeyEncryptedUserKey); - encryptServiceEncryptSpy = jest - .spyOn(encryptService, "encrypt") + encryptServiceWrapEncapsulationKeySpy = jest + .spyOn(encryptService, "wrapEncapsulationKey") .mockImplementation((plainValue, key) => { - if (plainValue === mockDevicePublicKey && key === mockUserKey) { + if (plainValue instanceof Uint8Array && key instanceof SymmetricCryptoKey) { return Promise.resolve(mockUserKeyEncryptedDevicePublicKey); } - if (plainValue === mockDevicePrivateKey && key === mockDeviceKey) { + }); + encryptServiceWrapDecapsulationKeySpy = jest + .spyOn(encryptService, "wrapDecapsulationKey") + .mockImplementation((plainValue, key) => { + if (plainValue instanceof Uint8Array && key instanceof SymmetricCryptoKey) { return Promise.resolve(mockDeviceKeyEncryptedDevicePrivateKey); } }); @@ -452,7 +452,8 @@ describe("deviceTrustService", () => { const userKey = cryptoSvcRsaEncryptSpy.mock.calls[0][0]; expect(userKey.key.byteLength).toBe(64); - expect(encryptServiceEncryptSpy).toHaveBeenCalledTimes(2); + expect(encryptServiceWrapDecapsulationKeySpy).toHaveBeenCalledTimes(1); + expect(encryptServiceWrapEncapsulationKeySpy).toHaveBeenCalledTimes(1); expect(appIdServiceGetAppIdSpy).toHaveBeenCalledTimes(1); expect(devicesApiServiceUpdateTrustedDeviceKeysSpy).toHaveBeenCalledTimes(1); @@ -508,9 +509,14 @@ describe("deviceTrustService", () => { errorText: "rsaEncrypt error", }, { - method: "encryptService.encrypt", - spy: () => encryptServiceEncryptSpy, - errorText: "encryptService.encrypt error", + method: "encryptService.wrapEncapsulationKey", + spy: () => encryptServiceWrapEncapsulationKeySpy, + errorText: "encryptService.wrapEncapsulationKey error", + }, + { + method: "encryptService.wrapDecapsulationKey", + spy: () => encryptServiceWrapDecapsulationKeySpy, + errorText: "encryptService.wrapDecapsulationKey error", }, ]; @@ -872,7 +878,7 @@ describe("deviceTrustService", () => { }); // Mock the reencryption of the device public key with the new user key - encryptService.encrypt.mockImplementationOnce((plainValue, key) => { + encryptService.wrapEncapsulationKey.mockImplementationOnce((plainValue, key) => { expect(plainValue).toBeInstanceOf(Uint8Array); expect(new Uint8Array(plainValue as Uint8Array)[0]).toBe(FakeDecryptedPublicKeyMarker); diff --git a/libs/common/src/tools/send/services/send.service.spec.ts b/libs/common/src/tools/send/services/send.service.spec.ts index 26cc0a4670..cd8d52fe37 100644 --- a/libs/common/src/tools/send/services/send.service.spec.ts +++ b/libs/common/src/tools/send/services/send.service.spec.ts @@ -479,7 +479,7 @@ describe("SendService", () => { beforeEach(() => { encryptService.decryptToBytes.mockResolvedValue(new Uint8Array(32)); encryptedKey = new EncString("Re-encrypted Send Key"); - encryptService.encrypt.mockResolvedValue(encryptedKey); + encryptService.wrapSymmetricKey.mockResolvedValue(encryptedKey); }); it("returns re-encrypted user sends", async () => { diff --git a/libs/common/src/tools/send/services/send.service.ts b/libs/common/src/tools/send/services/send.service.ts index 1b5e5f6aa3..8d6e62e3b8 100644 --- a/libs/common/src/tools/send/services/send.service.ts +++ b/libs/common/src/tools/send/services/send.service.ts @@ -81,6 +81,7 @@ export class SendService implements InternalSendServiceAbstraction { if (key == null) { key = await this.keyService.getUserKey(); } + // Key is not a SymmetricCryptoKey, but key material used to derive the cryptoKey send.key = await this.encryptService.encrypt(model.key, key); send.name = await this.encryptService.encrypt(model.name, model.cryptoKey); send.notes = await this.encryptService.encrypt(model.notes, model.cryptoKey); @@ -287,8 +288,10 @@ export class SendService implements InternalSendServiceAbstraction { ) { const requests = await Promise.all( sends.map(async (send) => { - const sendKey = await this.encryptService.decryptToBytes(send.key, originalUserKey); - send.key = await this.encryptService.encrypt(sendKey, rotateUserKey); + const sendKey = new SymmetricCryptoKey( + await this.encryptService.decryptToBytes(send.key, originalUserKey), + ); + send.key = await this.encryptService.wrapSymmetricKey(sendKey, rotateUserKey); return new SendWithIdRequest(send); }), ); diff --git a/libs/common/src/vault/services/cipher.service.spec.ts b/libs/common/src/vault/services/cipher.service.spec.ts index 24903e229c..57df5f2a37 100644 --- a/libs/common/src/vault/services/cipher.service.spec.ts +++ b/libs/common/src/vault/services/cipher.service.spec.ts @@ -280,6 +280,7 @@ describe("Cipher Service", () => { Promise.resolve(new SymmetricCryptoKey(makeStaticByteArray(64)) as CipherKey), ); encryptService.encrypt.mockImplementation(encryptText); + encryptService.wrapSymmetricKey.mockResolvedValue(new EncString("Re-encrypted Cipher Key")); jest.spyOn(cipherService as any, "getAutofillOnPageLoadDefault").mockResolvedValue(true); }); @@ -436,7 +437,7 @@ describe("Cipher Service", () => { encryptService.decryptToBytes.mockResolvedValue(new Uint8Array(32)); encryptedKey = new EncString("Re-encrypted Cipher Key"); - encryptService.encrypt.mockResolvedValue(encryptedKey); + encryptService.wrapSymmetricKey.mockResolvedValue(encryptedKey); keyService.makeCipherKey.mockResolvedValue( new SymmetricCryptoKey(new Uint8Array(32)) as CipherKey, diff --git a/libs/common/src/vault/services/cipher.service.ts b/libs/common/src/vault/services/cipher.service.ts index c192876c83..455be3babe 100644 --- a/libs/common/src/vault/services/cipher.service.ts +++ b/libs/common/src/vault/services/cipher.service.ts @@ -266,7 +266,7 @@ export class CipherService implements CipherServiceAbstraction { key, ).then(async () => { if (model.key != null) { - attachment.key = await this.encryptService.encrypt(model.key.key, key); + attachment.key = await this.encryptService.wrapSymmetricKey(model.key, key); } encAttachments.push(attachment); }); @@ -1820,8 +1820,8 @@ export class CipherService implements CipherServiceAbstraction { } // Then, we have to encrypt the cipher key with the proper key. - cipher.key = await this.encryptService.encrypt( - decryptedCipherKey.key, + cipher.key = await this.encryptService.wrapSymmetricKey( + decryptedCipherKey, keyForCipherKeyEncryption, ); diff --git a/libs/key-management/src/key.service.ts b/libs/key-management/src/key.service.ts index baf1b86e16..5dec206b86 100644 --- a/libs/key-management/src/key.service.ts +++ b/libs/key-management/src/key.service.ts @@ -232,7 +232,7 @@ export class DefaultKeyService implements KeyServiceAbstraction { } const newUserKey = await this.keyGenerationService.createKey(512); - return this.buildProtectedSymmetricKey(masterKey, newUserKey.key); + return this.buildProtectedSymmetricKey(masterKey, newUserKey); } /** @@ -323,7 +323,7 @@ export class DefaultKeyService implements KeyServiceAbstraction { userKey?: UserKey, ): Promise<[UserKey, EncString]> { userKey ||= await this.getUserKey(); - return await this.buildProtectedSymmetricKey(masterKey, userKey.key); + return await this.buildProtectedSymmetricKey(masterKey, userKey); } // TODO: move to MasterPasswordService @@ -433,7 +433,7 @@ export class DefaultKeyService implements KeyServiceAbstraction { } const newSymKey = await this.keyGenerationService.createKey(512); - return this.buildProtectedSymmetricKey(key, newSymKey.key); + return this.buildProtectedSymmetricKey(key, newSymKey); } private async clearOrgKeys(userId: UserId): Promise { @@ -547,7 +547,7 @@ export class DefaultKeyService implements KeyServiceAbstraction { const keyPair = await this.cryptoFunctionService.rsaGenerateKeyPair(2048); const publicB64 = Utils.fromBufferToB64(keyPair[0]); - const privateEnc = await this.encryptService.encrypt(keyPair[1], key); + const privateEnc = await this.encryptService.wrapDecapsulationKey(keyPair[1], key); return [publicB64, privateEnc]; } @@ -820,18 +820,21 @@ export class DefaultKeyService implements KeyServiceAbstraction { private async buildProtectedSymmetricKey( encryptionKey: SymmetricCryptoKey, - newSymKey: Uint8Array, + newSymKey: SymmetricCryptoKey, ): Promise<[T, EncString]> { let protectedSymKey: EncString; if (encryptionKey.key.byteLength === 32) { const stretchedEncryptionKey = await this.keyGenerationService.stretchKey(encryptionKey); - protectedSymKey = await this.encryptService.encrypt(newSymKey, stretchedEncryptionKey); + protectedSymKey = await this.encryptService.wrapSymmetricKey( + newSymKey, + stretchedEncryptionKey, + ); } else if (encryptionKey.key.byteLength === 64) { - protectedSymKey = await this.encryptService.encrypt(newSymKey, encryptionKey); + protectedSymKey = await this.encryptService.wrapSymmetricKey(newSymKey, encryptionKey); } else { throw new Error("Invalid key size."); } - return [new SymmetricCryptoKey(newSymKey) as T, protectedSymKey]; + return [newSymKey as T, protectedSymKey]; } userKey$(userId: UserId): Observable {