mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +00:00
[PM-19731] Refactor encrypt service to expose key wrapping (#14080)
* Refactor encrypt service to expose key wrapping * Fix build * Undo ts strict removal * Fix wrong method being used to encrypt key material * Rename parameters and remove todo * Add summary to encrypt * Update libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> * Update libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> * Update libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> * Update libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> * Update libs/common/src/key-management/crypto/abstractions/encrypt.service.ts Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> * Update libs/common/src/key-management/crypto/services/encrypt.service.implementation.ts Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com> * Add tests for unhappy paths * Add test coverage * Add links --------- Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>
This commit is contained in:
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 });
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
|
||||
@@ -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]);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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<EncString>;
|
||||
/**
|
||||
* 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<EncArrayBuffer>;
|
||||
|
||||
/**
|
||||
* 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<EncString>;
|
||||
/**
|
||||
* 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<EncString>;
|
||||
/**
|
||||
* 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<EncString>;
|
||||
|
||||
/**
|
||||
* 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<SymmetricCryptoKey>;
|
||||
/**
|
||||
* @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<EncString>;
|
||||
/**
|
||||
* @deprecated Use decapsulateKeyUnsigned instead
|
||||
* @deprecated Use @see {@link decapsulateKeyUnsigned} instead
|
||||
* @param data - The ciphertext to decrypt
|
||||
* @param privateKey - The privateKey to decrypt with
|
||||
*/
|
||||
|
||||
@@ -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<EncString> {
|
||||
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<EncString> {
|
||||
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<EncString> {
|
||||
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<EncString> {
|
||||
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);
|
||||
|
||||
@@ -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<ServerConfig>();
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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);
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
|
||||
@@ -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<void> {
|
||||
@@ -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<T extends SymmetricCryptoKey>(
|
||||
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<UserKey | null> {
|
||||
|
||||
Reference in New Issue
Block a user