mirror of
https://github.com/bitwarden/browser.git
synced 2025-03-11 13:30:39 +01:00
[PM-8155] Keep crypto derive dependencies in lockstep (#9191)
* Keep derive dependencies in lockstep This reduces emissions in general due to updates of multiple inputs and removes decryption errors due to partially updated dependencies * Fix provider encrypted org keys * Fix provider state test types * Type fixes
This commit is contained in:
parent
c19a640557
commit
4ccf920da8
@ -1,11 +1,11 @@
|
|||||||
import { CryptoService } from "../../../platform/abstractions/crypto.service";
|
import { EncryptService } from "../../../platform/abstractions/encrypt.service";
|
||||||
import { EncString } from "../../../platform/models/domain/enc-string";
|
import { EncString } from "../../../platform/models/domain/enc-string";
|
||||||
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
|
||||||
import { OrgKey } from "../../../types/key";
|
import { OrgKey, UserPrivateKey } from "../../../types/key";
|
||||||
import { EncryptedOrganizationKeyData } from "../data/encrypted-organization-key.data";
|
import { EncryptedOrganizationKeyData } from "../data/encrypted-organization-key.data";
|
||||||
|
|
||||||
export abstract class BaseEncryptedOrganizationKey {
|
export abstract class BaseEncryptedOrganizationKey {
|
||||||
decrypt: (cryptoService: CryptoService) => Promise<SymmetricCryptoKey>;
|
abstract get encryptedOrganizationKey(): EncString;
|
||||||
|
|
||||||
static fromData(data: EncryptedOrganizationKeyData) {
|
static fromData(data: EncryptedOrganizationKeyData) {
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
@ -19,22 +19,26 @@ export abstract class BaseEncryptedOrganizationKey {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static isProviderEncrypted(
|
||||||
|
key: EncryptedOrganizationKey | ProviderEncryptedOrganizationKey,
|
||||||
|
): key is ProviderEncryptedOrganizationKey {
|
||||||
|
return key.toData().type === "provider";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EncryptedOrganizationKey implements BaseEncryptedOrganizationKey {
|
export class EncryptedOrganizationKey implements BaseEncryptedOrganizationKey {
|
||||||
constructor(private key: string) {}
|
constructor(private key: string) {}
|
||||||
|
|
||||||
async decrypt(cryptoService: CryptoService) {
|
async decrypt(encryptService: EncryptService, privateKey: UserPrivateKey) {
|
||||||
const activeUserPrivateKey = await cryptoService.getPrivateKey();
|
const decValue = await encryptService.rsaDecrypt(this.encryptedOrganizationKey, privateKey);
|
||||||
|
|
||||||
if (activeUserPrivateKey == null) {
|
|
||||||
throw new Error("Active user does not have a private key, cannot decrypt organization key.");
|
|
||||||
}
|
|
||||||
|
|
||||||
const decValue = await cryptoService.rsaDecrypt(this.key, activeUserPrivateKey);
|
|
||||||
return new SymmetricCryptoKey(decValue) as OrgKey;
|
return new SymmetricCryptoKey(decValue) as OrgKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get encryptedOrganizationKey() {
|
||||||
|
return new EncString(this.key);
|
||||||
|
}
|
||||||
|
|
||||||
toData(): EncryptedOrganizationKeyData {
|
toData(): EncryptedOrganizationKeyData {
|
||||||
return {
|
return {
|
||||||
type: "organization",
|
type: "organization",
|
||||||
@ -49,12 +53,18 @@ export class ProviderEncryptedOrganizationKey implements BaseEncryptedOrganizati
|
|||||||
private providerId: string,
|
private providerId: string,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async decrypt(cryptoService: CryptoService) {
|
async decrypt(encryptService: EncryptService, providerKeys: Record<string, SymmetricCryptoKey>) {
|
||||||
const providerKey = await cryptoService.getProviderKey(this.providerId);
|
const decValue = await encryptService.decryptToBytes(
|
||||||
const decValue = await cryptoService.decryptToBytes(new EncString(this.key), providerKey);
|
new EncString(this.key),
|
||||||
|
providerKeys[this.providerId],
|
||||||
|
);
|
||||||
return new SymmetricCryptoKey(decValue) as OrgKey;
|
return new SymmetricCryptoKey(decValue) as OrgKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get encryptedOrganizationKey() {
|
||||||
|
return new EncString(this.key);
|
||||||
|
}
|
||||||
|
|
||||||
toData(): EncryptedOrganizationKeyData {
|
toData(): EncryptedOrganizationKeyData {
|
||||||
return {
|
return {
|
||||||
type: "provider",
|
type: "provider",
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import * as bigInt from "big-integer";
|
import * as bigInt from "big-integer";
|
||||||
import { Observable, filter, firstValueFrom, map } from "rxjs";
|
import { Observable, filter, firstValueFrom, map, zip } from "rxjs";
|
||||||
|
|
||||||
import { PinServiceAbstraction } from "../../../../auth/src/common/abstractions";
|
import { PinServiceAbstraction } from "../../../../auth/src/common/abstractions";
|
||||||
import { EncryptedOrganizationKeyData } from "../../admin-console/models/data/encrypted-organization-key.data";
|
import { EncryptedOrganizationKeyData } from "../../admin-console/models/data/encrypted-organization-key.data";
|
||||||
@ -97,13 +97,12 @@ export class CryptoService implements CryptoServiceAbstraction {
|
|||||||
// User Asymmetric Key Pair
|
// User Asymmetric Key Pair
|
||||||
this.activeUserEncryptedPrivateKeyState = stateProvider.getActive(USER_ENCRYPTED_PRIVATE_KEY);
|
this.activeUserEncryptedPrivateKeyState = stateProvider.getActive(USER_ENCRYPTED_PRIVATE_KEY);
|
||||||
this.activeUserPrivateKeyState = stateProvider.getDerived(
|
this.activeUserPrivateKeyState = stateProvider.getDerived(
|
||||||
this.activeUserEncryptedPrivateKeyState.combinedState$.pipe(
|
zip(this.activeUserEncryptedPrivateKeyState.state$, this.activeUserKey$).pipe(
|
||||||
filter(([_userId, key]) => key != null),
|
filter(([, userKey]) => !!userKey),
|
||||||
),
|
),
|
||||||
USER_PRIVATE_KEY,
|
USER_PRIVATE_KEY,
|
||||||
{
|
{
|
||||||
encryptService: this.encryptService,
|
encryptService: this.encryptService,
|
||||||
getUserKey: (userId) => this.getUserKey(userId),
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
this.activeUserPrivateKey$ = this.activeUserPrivateKeyState.state$; // may be null
|
this.activeUserPrivateKey$ = this.activeUserPrivateKeyState.state$; // may be null
|
||||||
@ -116,27 +115,34 @@ export class CryptoService implements CryptoServiceAbstraction {
|
|||||||
);
|
);
|
||||||
this.activeUserPublicKey$ = this.activeUserPublicKeyState.state$; // may be null
|
this.activeUserPublicKey$ = this.activeUserPublicKeyState.state$; // may be null
|
||||||
|
|
||||||
// Organization keys
|
|
||||||
this.activeUserEncryptedOrgKeysState = stateProvider.getActive(
|
|
||||||
USER_ENCRYPTED_ORGANIZATION_KEYS,
|
|
||||||
);
|
|
||||||
this.activeUserOrgKeysState = stateProvider.getDerived(
|
|
||||||
this.activeUserEncryptedOrgKeysState.state$.pipe(filter((keys) => keys != null)),
|
|
||||||
USER_ORGANIZATION_KEYS,
|
|
||||||
{ cryptoService: this },
|
|
||||||
);
|
|
||||||
this.activeUserOrgKeys$ = this.activeUserOrgKeysState.state$; // null handled by `derive` function
|
|
||||||
|
|
||||||
// Provider keys
|
// Provider keys
|
||||||
this.activeUserEncryptedProviderKeysState = stateProvider.getActive(
|
this.activeUserEncryptedProviderKeysState = stateProvider.getActive(
|
||||||
USER_ENCRYPTED_PROVIDER_KEYS,
|
USER_ENCRYPTED_PROVIDER_KEYS,
|
||||||
);
|
);
|
||||||
this.activeUserProviderKeysState = stateProvider.getDerived(
|
this.activeUserProviderKeysState = stateProvider.getDerived(
|
||||||
this.activeUserEncryptedProviderKeysState.state$.pipe(filter((keys) => keys != null)),
|
zip(
|
||||||
|
this.activeUserEncryptedProviderKeysState.state$.pipe(filter((keys) => keys != null)),
|
||||||
|
this.activeUserPrivateKey$,
|
||||||
|
).pipe(filter(([, privateKey]) => !!privateKey)),
|
||||||
USER_PROVIDER_KEYS,
|
USER_PROVIDER_KEYS,
|
||||||
{ encryptService: this.encryptService, cryptoService: this },
|
{ encryptService: this.encryptService },
|
||||||
);
|
);
|
||||||
this.activeUserProviderKeys$ = this.activeUserProviderKeysState.state$; // null handled by `derive` function
|
this.activeUserProviderKeys$ = this.activeUserProviderKeysState.state$; // null handled by `derive` function
|
||||||
|
|
||||||
|
// Organization keys
|
||||||
|
this.activeUserEncryptedOrgKeysState = stateProvider.getActive(
|
||||||
|
USER_ENCRYPTED_ORGANIZATION_KEYS,
|
||||||
|
);
|
||||||
|
this.activeUserOrgKeysState = stateProvider.getDerived(
|
||||||
|
zip(
|
||||||
|
this.activeUserEncryptedOrgKeysState.state$.pipe(filter((keys) => keys != null)),
|
||||||
|
this.activeUserPrivateKey$,
|
||||||
|
this.activeUserProviderKeys$,
|
||||||
|
).pipe(filter(([, privateKey]) => !!privateKey)),
|
||||||
|
USER_ORGANIZATION_KEYS,
|
||||||
|
{ encryptService: this.encryptService },
|
||||||
|
);
|
||||||
|
this.activeUserOrgKeys$ = this.activeUserOrgKeysState.state$; // null handled by `derive` function
|
||||||
}
|
}
|
||||||
|
|
||||||
async setUserKey(key: UserKey, userId?: UserId): Promise<void> {
|
async setUserKey(key: UserKey, userId?: UserId): Promise<void> {
|
||||||
@ -656,17 +662,14 @@ export class CryptoService implements CryptoServiceAbstraction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [userId, encPrivateKey] = await firstValueFrom(
|
const encPrivateKey = await firstValueFrom(this.activeUserEncryptedPrivateKeyState.state$);
|
||||||
this.activeUserEncryptedPrivateKeyState.combinedState$,
|
|
||||||
);
|
|
||||||
if (encPrivateKey == null) {
|
if (encPrivateKey == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Can decrypt private key
|
// Can decrypt private key
|
||||||
const privateKey = await USER_PRIVATE_KEY.derive([userId, encPrivateKey], {
|
const privateKey = await USER_PRIVATE_KEY.derive([encPrivateKey, key], {
|
||||||
encryptService: this.encryptService,
|
encryptService: this.encryptService,
|
||||||
getUserKey: () => Promise.resolve(key),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (privateKey == null) {
|
if (privateKey == null) {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { mock } from "jest-mock-extended";
|
import { mock } from "jest-mock-extended";
|
||||||
|
|
||||||
import { makeEncString, makeStaticByteArray } from "../../../../spec";
|
import { makeEncString, makeStaticByteArray } from "../../../../spec";
|
||||||
import { OrgKey } from "../../../types/key";
|
import { OrgKey, UserPrivateKey } from "../../../types/key";
|
||||||
import { CryptoService } from "../../abstractions/crypto.service";
|
import { EncryptService } from "../../abstractions/encrypt.service";
|
||||||
import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key";
|
||||||
|
|
||||||
import { USER_ENCRYPTED_ORGANIZATION_KEYS, USER_ORGANIZATION_KEYS } from "./org-keys.state";
|
import { USER_ENCRYPTED_ORGANIZATION_KEYS, USER_ORGANIZATION_KEYS } from "./org-keys.state";
|
||||||
@ -30,7 +30,8 @@ describe("encrypted org keys", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("derived decrypted org keys", () => {
|
describe("derived decrypted org keys", () => {
|
||||||
const cryptoService = mock<CryptoService>();
|
const encryptService = mock<EncryptService>();
|
||||||
|
const userPrivateKey = makeStaticByteArray(64, 3) as UserPrivateKey;
|
||||||
const sut = USER_ORGANIZATION_KEYS;
|
const sut = USER_ORGANIZATION_KEYS;
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@ -65,15 +66,11 @@ describe("derived decrypted org keys", () => {
|
|||||||
"org-id-2": new SymmetricCryptoKey(makeStaticByteArray(64, 2)) as OrgKey,
|
"org-id-2": new SymmetricCryptoKey(makeStaticByteArray(64, 2)) as OrgKey,
|
||||||
};
|
};
|
||||||
|
|
||||||
const userPrivateKey = makeStaticByteArray(64, 3);
|
|
||||||
|
|
||||||
cryptoService.getPrivateKey.mockResolvedValue(userPrivateKey);
|
|
||||||
|
|
||||||
// TODO: How to not have to mock these decryptions. They are internal concerns of EncryptedOrganizationKey
|
// TODO: How to not have to mock these decryptions. They are internal concerns of EncryptedOrganizationKey
|
||||||
cryptoService.rsaDecrypt.mockResolvedValueOnce(decryptedOrgKeys["org-id-1"].key);
|
encryptService.rsaDecrypt.mockResolvedValueOnce(decryptedOrgKeys["org-id-1"].key);
|
||||||
cryptoService.rsaDecrypt.mockResolvedValueOnce(decryptedOrgKeys["org-id-2"].key);
|
encryptService.rsaDecrypt.mockResolvedValueOnce(decryptedOrgKeys["org-id-2"].key);
|
||||||
|
|
||||||
const result = await sut.derive(encryptedOrgKeys, { cryptoService });
|
const result = await sut.derive([encryptedOrgKeys, userPrivateKey, {}], { encryptService });
|
||||||
|
|
||||||
expect(result).toEqual(decryptedOrgKeys);
|
expect(result).toEqual(decryptedOrgKeys);
|
||||||
});
|
});
|
||||||
@ -92,16 +89,23 @@ describe("derived decrypted org keys", () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const providerKeys = {
|
||||||
|
"provider-id-1": new SymmetricCryptoKey(makeStaticByteArray(64, 1)),
|
||||||
|
"provider-id-2": new SymmetricCryptoKey(makeStaticByteArray(64, 2)),
|
||||||
|
};
|
||||||
|
|
||||||
const decryptedOrgKeys = {
|
const decryptedOrgKeys = {
|
||||||
"org-id-1": new SymmetricCryptoKey(makeStaticByteArray(64, 1)) as OrgKey,
|
"org-id-1": new SymmetricCryptoKey(makeStaticByteArray(64, 1)) as OrgKey,
|
||||||
"org-id-2": new SymmetricCryptoKey(makeStaticByteArray(64, 2)) as OrgKey,
|
"org-id-2": new SymmetricCryptoKey(makeStaticByteArray(64, 2)) as OrgKey,
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: How to not have to mock these decryptions. They are internal concerns of ProviderEncryptedOrganizationKey
|
// TODO: How to not have to mock these decryptions. They are internal concerns of ProviderEncryptedOrganizationKey
|
||||||
cryptoService.decryptToBytes.mockResolvedValueOnce(decryptedOrgKeys["org-id-1"].key);
|
encryptService.decryptToBytes.mockResolvedValueOnce(decryptedOrgKeys["org-id-1"].key);
|
||||||
cryptoService.decryptToBytes.mockResolvedValueOnce(decryptedOrgKeys["org-id-2"].key);
|
encryptService.decryptToBytes.mockResolvedValueOnce(decryptedOrgKeys["org-id-2"].key);
|
||||||
|
|
||||||
const result = await sut.derive(encryptedOrgKeys, { cryptoService });
|
const result = await sut.derive([encryptedOrgKeys, userPrivateKey, providerKeys], {
|
||||||
|
encryptService,
|
||||||
|
});
|
||||||
|
|
||||||
expect(result).toEqual(decryptedOrgKeys);
|
expect(result).toEqual(decryptedOrgKeys);
|
||||||
});
|
});
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { EncryptedOrganizationKeyData } from "../../../admin-console/models/data/encrypted-organization-key.data";
|
import { EncryptedOrganizationKeyData } from "../../../admin-console/models/data/encrypted-organization-key.data";
|
||||||
import { BaseEncryptedOrganizationKey } from "../../../admin-console/models/domain/encrypted-organization-key";
|
import { BaseEncryptedOrganizationKey } from "../../../admin-console/models/domain/encrypted-organization-key";
|
||||||
import { OrganizationId } from "../../../types/guid";
|
import { OrganizationId, ProviderId } from "../../../types/guid";
|
||||||
import { OrgKey } from "../../../types/key";
|
import { OrgKey, ProviderKey, UserPrivateKey } from "../../../types/key";
|
||||||
import { CryptoService } from "../../abstractions/crypto.service";
|
import { EncryptService } from "../../abstractions/encrypt.service";
|
||||||
import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key";
|
||||||
import { CRYPTO_DISK, DeriveDefinition, UserKeyDefinition } from "../../state";
|
import { CRYPTO_DISK, CRYPTO_MEMORY, DeriveDefinition, UserKeyDefinition } from "../../state";
|
||||||
|
|
||||||
export const USER_ENCRYPTED_ORGANIZATION_KEYS = UserKeyDefinition.record<
|
export const USER_ENCRYPTED_ORGANIZATION_KEYS = UserKeyDefinition.record<
|
||||||
EncryptedOrganizationKeyData,
|
EncryptedOrganizationKeyData,
|
||||||
@ -14,11 +14,15 @@ export const USER_ENCRYPTED_ORGANIZATION_KEYS = UserKeyDefinition.record<
|
|||||||
clearOn: ["logout"],
|
clearOn: ["logout"],
|
||||||
});
|
});
|
||||||
|
|
||||||
export const USER_ORGANIZATION_KEYS = DeriveDefinition.from<
|
export const USER_ORGANIZATION_KEYS = new DeriveDefinition<
|
||||||
Record<OrganizationId, EncryptedOrganizationKeyData>,
|
[
|
||||||
|
Record<OrganizationId, EncryptedOrganizationKeyData>,
|
||||||
|
UserPrivateKey,
|
||||||
|
Record<ProviderId, ProviderKey>,
|
||||||
|
],
|
||||||
Record<OrganizationId, OrgKey>,
|
Record<OrganizationId, OrgKey>,
|
||||||
{ cryptoService: CryptoService }
|
{ encryptService: EncryptService }
|
||||||
>(USER_ENCRYPTED_ORGANIZATION_KEYS, {
|
>(CRYPTO_MEMORY, "organizationKeys", {
|
||||||
deserializer: (obj) => {
|
deserializer: (obj) => {
|
||||||
const result: Record<OrganizationId, OrgKey> = {};
|
const result: Record<OrganizationId, OrgKey> = {};
|
||||||
for (const orgId of Object.keys(obj ?? {}) as OrganizationId[]) {
|
for (const orgId of Object.keys(obj ?? {}) as OrganizationId[]) {
|
||||||
@ -26,14 +30,21 @@ export const USER_ORGANIZATION_KEYS = DeriveDefinition.from<
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
derive: async (from, { cryptoService }) => {
|
derive: async ([encryptedOrgKeys, privateKey, providerKeys], { encryptService }) => {
|
||||||
const result: Record<OrganizationId, OrgKey> = {};
|
const result: Record<OrganizationId, OrgKey> = {};
|
||||||
for (const orgId of Object.keys(from ?? {}) as OrganizationId[]) {
|
for (const orgId of Object.keys(encryptedOrgKeys ?? {}) as OrganizationId[]) {
|
||||||
if (result[orgId] != null) {
|
if (result[orgId] != null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const encrypted = BaseEncryptedOrganizationKey.fromData(from[orgId]);
|
const encrypted = BaseEncryptedOrganizationKey.fromData(encryptedOrgKeys[orgId]);
|
||||||
const decrypted = await encrypted.decrypt(cryptoService);
|
|
||||||
|
let decrypted: OrgKey;
|
||||||
|
|
||||||
|
if (BaseEncryptedOrganizationKey.isProviderEncrypted(encrypted)) {
|
||||||
|
decrypted = await encrypted.decrypt(encryptService, providerKeys);
|
||||||
|
} else {
|
||||||
|
decrypted = await encrypted.decrypt(encryptService, privateKey);
|
||||||
|
}
|
||||||
|
|
||||||
result[orgId] = decrypted;
|
result[orgId] = decrypted;
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ import { ProviderKey, UserPrivateKey } from "../../../types/key";
|
|||||||
import { EncryptService } from "../../abstractions/encrypt.service";
|
import { EncryptService } from "../../abstractions/encrypt.service";
|
||||||
import { EncryptedString } from "../../models/domain/enc-string";
|
import { EncryptedString } from "../../models/domain/enc-string";
|
||||||
import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key";
|
||||||
import { CryptoService } from "../crypto.service";
|
|
||||||
|
|
||||||
import { USER_ENCRYPTED_PROVIDER_KEYS, USER_PROVIDER_KEYS } from "./provider-keys.state";
|
import { USER_ENCRYPTED_PROVIDER_KEYS, USER_PROVIDER_KEYS } from "./provider-keys.state";
|
||||||
|
|
||||||
@ -27,7 +26,6 @@ describe("encrypted provider keys", () => {
|
|||||||
|
|
||||||
describe("derived decrypted provider keys", () => {
|
describe("derived decrypted provider keys", () => {
|
||||||
const encryptService = mock<EncryptService>();
|
const encryptService = mock<EncryptService>();
|
||||||
const cryptoService = mock<CryptoService>();
|
|
||||||
const userPrivateKey = makeStaticByteArray(64, 0) as UserPrivateKey;
|
const userPrivateKey = makeStaticByteArray(64, 0) as UserPrivateKey;
|
||||||
const sut = USER_PROVIDER_KEYS;
|
const sut = USER_PROVIDER_KEYS;
|
||||||
|
|
||||||
@ -59,9 +57,8 @@ describe("derived decrypted provider keys", () => {
|
|||||||
|
|
||||||
encryptService.rsaDecrypt.mockResolvedValueOnce(decryptedProviderKeys["provider-id-1"].key);
|
encryptService.rsaDecrypt.mockResolvedValueOnce(decryptedProviderKeys["provider-id-1"].key);
|
||||||
encryptService.rsaDecrypt.mockResolvedValueOnce(decryptedProviderKeys["provider-id-2"].key);
|
encryptService.rsaDecrypt.mockResolvedValueOnce(decryptedProviderKeys["provider-id-2"].key);
|
||||||
cryptoService.getPrivateKey.mockResolvedValueOnce(userPrivateKey);
|
|
||||||
|
|
||||||
const result = await sut.derive(encryptedProviderKeys, { encryptService, cryptoService });
|
const result = await sut.derive([encryptedProviderKeys, userPrivateKey], { encryptService });
|
||||||
|
|
||||||
expect(result).toEqual(decryptedProviderKeys);
|
expect(result).toEqual(decryptedProviderKeys);
|
||||||
});
|
});
|
||||||
@ -69,7 +66,7 @@ describe("derived decrypted provider keys", () => {
|
|||||||
it("should handle null input values", async () => {
|
it("should handle null input values", async () => {
|
||||||
const encryptedProviderKeys: Record<ProviderId, EncryptedString> = null;
|
const encryptedProviderKeys: Record<ProviderId, EncryptedString> = null;
|
||||||
|
|
||||||
const result = await sut.derive(encryptedProviderKeys, { encryptService, cryptoService });
|
const result = await sut.derive([encryptedProviderKeys, userPrivateKey], { encryptService });
|
||||||
|
|
||||||
expect(result).toEqual({});
|
expect(result).toEqual({});
|
||||||
});
|
});
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import { ProviderId } from "../../../types/guid";
|
import { ProviderId } from "../../../types/guid";
|
||||||
import { ProviderKey } from "../../../types/key";
|
import { ProviderKey, UserPrivateKey } from "../../../types/key";
|
||||||
import { EncryptService } from "../../abstractions/encrypt.service";
|
import { EncryptService } from "../../abstractions/encrypt.service";
|
||||||
import { EncString, EncryptedString } from "../../models/domain/enc-string";
|
import { EncString, EncryptedString } from "../../models/domain/enc-string";
|
||||||
import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key";
|
||||||
import { CRYPTO_DISK, DeriveDefinition, UserKeyDefinition } from "../../state";
|
import { CRYPTO_DISK, CRYPTO_MEMORY, DeriveDefinition, UserKeyDefinition } from "../../state";
|
||||||
import { CryptoService } from "../crypto.service";
|
|
||||||
|
|
||||||
export const USER_ENCRYPTED_PROVIDER_KEYS = UserKeyDefinition.record<EncryptedString, ProviderId>(
|
export const USER_ENCRYPTED_PROVIDER_KEYS = UserKeyDefinition.record<EncryptedString, ProviderId>(
|
||||||
CRYPTO_DISK,
|
CRYPTO_DISK,
|
||||||
@ -15,11 +14,11 @@ export const USER_ENCRYPTED_PROVIDER_KEYS = UserKeyDefinition.record<EncryptedSt
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export const USER_PROVIDER_KEYS = DeriveDefinition.from<
|
export const USER_PROVIDER_KEYS = new DeriveDefinition<
|
||||||
Record<ProviderId, EncryptedString>,
|
[Record<ProviderId, EncryptedString>, UserPrivateKey],
|
||||||
Record<ProviderId, ProviderKey>,
|
Record<ProviderId, ProviderKey>,
|
||||||
{ encryptService: EncryptService; cryptoService: CryptoService } // TODO: This should depend on an active user private key observable directly
|
{ encryptService: EncryptService }
|
||||||
>(USER_ENCRYPTED_PROVIDER_KEYS, {
|
>(CRYPTO_MEMORY, "providerKeys", {
|
||||||
deserializer: (obj) => {
|
deserializer: (obj) => {
|
||||||
const result: Record<ProviderId, ProviderKey> = {};
|
const result: Record<ProviderId, ProviderKey> = {};
|
||||||
for (const providerId of Object.keys(obj ?? {}) as ProviderId[]) {
|
for (const providerId of Object.keys(obj ?? {}) as ProviderId[]) {
|
||||||
@ -27,14 +26,13 @@ export const USER_PROVIDER_KEYS = DeriveDefinition.from<
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
derive: async (from, { encryptService, cryptoService }) => {
|
derive: async ([encryptedProviderKeys, privateKey], { encryptService }) => {
|
||||||
const result: Record<ProviderId, ProviderKey> = {};
|
const result: Record<ProviderId, ProviderKey> = {};
|
||||||
for (const providerId of Object.keys(from ?? {}) as ProviderId[]) {
|
for (const providerId of Object.keys(encryptedProviderKeys ?? {}) as ProviderId[]) {
|
||||||
if (result[providerId] != null) {
|
if (result[providerId] != null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const encrypted = new EncString(from[providerId]);
|
const encrypted = new EncString(encryptedProviderKeys[providerId]);
|
||||||
const privateKey = await cryptoService.getPrivateKey();
|
|
||||||
const decrypted = await encryptService.rsaDecrypt(encrypted, privateKey);
|
const decrypted = await encryptService.rsaDecrypt(encrypted, privateKey);
|
||||||
const providerKey = new SymmetricCryptoKey(decrypted) as ProviderKey;
|
const providerKey = new SymmetricCryptoKey(decrypted) as ProviderKey;
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { mock } from "jest-mock-extended";
|
import { mock } from "jest-mock-extended";
|
||||||
|
|
||||||
import { makeStaticByteArray } from "../../../../spec";
|
import { makeStaticByteArray } from "../../../../spec";
|
||||||
import { UserId } from "../../../types/guid";
|
|
||||||
import { UserKey, UserPrivateKey, UserPublicKey } from "../../../types/key";
|
import { UserKey, UserPrivateKey, UserPublicKey } from "../../../types/key";
|
||||||
import { CryptoFunctionService } from "../../abstractions/crypto-function.service";
|
import { CryptoFunctionService } from "../../abstractions/crypto-function.service";
|
||||||
import { EncryptService } from "../../abstractions/encrypt.service";
|
import { EncryptService } from "../../abstractions/encrypt.service";
|
||||||
@ -70,7 +69,6 @@ describe("User public key", () => {
|
|||||||
|
|
||||||
describe("Derived decrypted private key", () => {
|
describe("Derived decrypted private key", () => {
|
||||||
const sut = USER_PRIVATE_KEY;
|
const sut = USER_PRIVATE_KEY;
|
||||||
const userId = "userId" as UserId;
|
|
||||||
const userKey = mock<UserKey>();
|
const userKey = mock<UserKey>();
|
||||||
const encryptedPrivateKey = makeEncString().encryptedString;
|
const encryptedPrivateKey = makeEncString().encryptedString;
|
||||||
const decryptedPrivateKey = makeStaticByteArray(64, 1);
|
const decryptedPrivateKey = makeStaticByteArray(64, 1);
|
||||||
@ -88,37 +86,31 @@ describe("Derived decrypted private key", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should derive decrypted private key", async () => {
|
it("should derive decrypted private key", async () => {
|
||||||
const getUserKey = jest.fn(async () => userKey);
|
|
||||||
const encryptService = mock<EncryptService>();
|
const encryptService = mock<EncryptService>();
|
||||||
encryptService.decryptToBytes.mockResolvedValue(decryptedPrivateKey);
|
encryptService.decryptToBytes.mockResolvedValue(decryptedPrivateKey);
|
||||||
|
|
||||||
const result = await sut.derive([userId, encryptedPrivateKey], {
|
const result = await sut.derive([encryptedPrivateKey, userKey], {
|
||||||
encryptService,
|
encryptService,
|
||||||
getUserKey,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toEqual(decryptedPrivateKey);
|
expect(result).toEqual(decryptedPrivateKey);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should handle null input values", async () => {
|
it("should handle null encryptedPrivateKey", async () => {
|
||||||
const getUserKey = jest.fn(async () => userKey);
|
|
||||||
const encryptService = mock<EncryptService>();
|
const encryptService = mock<EncryptService>();
|
||||||
|
|
||||||
const result = await sut.derive([userId, null], {
|
const result = await sut.derive([null, userKey], {
|
||||||
encryptService,
|
encryptService,
|
||||||
getUserKey,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toEqual(null);
|
expect(result).toEqual(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should handle null user key", async () => {
|
it("should handle null userKey", async () => {
|
||||||
const getUserKey = jest.fn(async () => null);
|
|
||||||
const encryptService = mock<EncryptService>();
|
const encryptService = mock<EncryptService>();
|
||||||
|
|
||||||
const result = await sut.derive([userId, encryptedPrivateKey], {
|
const result = await sut.derive([encryptedPrivateKey, null], {
|
||||||
encryptService,
|
encryptService,
|
||||||
getUserKey,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toEqual(null);
|
expect(result).toEqual(null);
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { UserId } from "../../../types/guid";
|
|
||||||
import { UserPrivateKey, UserPublicKey, UserKey } from "../../../types/key";
|
import { UserPrivateKey, UserPublicKey, UserKey } from "../../../types/key";
|
||||||
import { CryptoFunctionService } from "../../abstractions/crypto-function.service";
|
import { CryptoFunctionService } from "../../abstractions/crypto-function.service";
|
||||||
import { EncryptService } from "../../abstractions/encrypt.service";
|
import { EncryptService } from "../../abstractions/encrypt.service";
|
||||||
@ -24,20 +23,14 @@ export const USER_ENCRYPTED_PRIVATE_KEY = new UserKeyDefinition<EncryptedString>
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export const USER_PRIVATE_KEY = DeriveDefinition.fromWithUserId<
|
export const USER_PRIVATE_KEY = new DeriveDefinition<
|
||||||
EncryptedString,
|
[EncryptedString, UserKey],
|
||||||
UserPrivateKey,
|
UserPrivateKey,
|
||||||
// TODO: update cryptoService to user key directly
|
{ encryptService: EncryptService }
|
||||||
{ encryptService: EncryptService; getUserKey: (userId: UserId) => Promise<UserKey> }
|
>(CRYPTO_MEMORY, "privateKey", {
|
||||||
>(USER_ENCRYPTED_PRIVATE_KEY, {
|
|
||||||
deserializer: (obj) => new Uint8Array(Object.values(obj)) as UserPrivateKey,
|
deserializer: (obj) => new Uint8Array(Object.values(obj)) as UserPrivateKey,
|
||||||
derive: async ([userId, encPrivateKeyString], { encryptService, getUserKey }) => {
|
derive: async ([encPrivateKeyString, userKey], { encryptService }) => {
|
||||||
if (encPrivateKeyString == null) {
|
if (encPrivateKeyString == null || userKey == null) {
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const userKey = await getUserKey(userId);
|
|
||||||
if (userKey == null) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,6 +57,7 @@ export const USER_PUBLIC_KEY = DeriveDefinition.from<
|
|||||||
return (await cryptoFunctionService.rsaExtractPublicKey(privateKey)) as UserPublicKey;
|
return (await cryptoFunctionService.rsaExtractPublicKey(privateKey)) as UserPublicKey;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const USER_KEY = new UserKeyDefinition<UserKey>(CRYPTO_MEMORY, "userKey", {
|
export const USER_KEY = new UserKeyDefinition<UserKey>(CRYPTO_MEMORY, "userKey", {
|
||||||
deserializer: (obj) => SymmetricCryptoKey.fromJSON(obj) as UserKey,
|
deserializer: (obj) => SymmetricCryptoKey.fromJSON(obj) as UserKey,
|
||||||
clearOn: ["logout", "lock"],
|
clearOn: ["logout", "lock"],
|
||||||
|
Loading…
Reference in New Issue
Block a user