[PM-7907] No more optional `privateKey` (#9029)

* Update Emergency Access To Get Their Own Key

* Migrate Organization Keys To Get Their Own Key

* Remove Optional Parameters

* Update Abstraction Parameter Name to Match Implementation

* Add @throws Doc
This commit is contained in:
Justin Baur 2024-05-03 14:30:45 -04:00 committed by GitHub
parent a4d5717283
commit b46766affd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 60 additions and 16 deletions

View File

@ -153,6 +153,7 @@ describe("EmergencyAccessService", () => {
} as EmergencyAccessTakeoverResponse);
const mockDecryptedGrantorUserKey = new Uint8Array(64);
cryptoService.getPrivateKey.mockResolvedValue(new Uint8Array(64));
cryptoService.rsaDecrypt.mockResolvedValueOnce(mockDecryptedGrantorUserKey);
const mockMasterKey = new SymmetricCryptoKey(new Uint8Array(64) as CsprngArray) as MasterKey;
@ -197,6 +198,7 @@ describe("EmergencyAccessService", () => {
kdf: KdfType.PBKDF2_SHA256,
kdfIterations: 500,
} as EmergencyAccessTakeoverResponse);
cryptoService.getPrivateKey.mockResolvedValue(new Uint8Array(64));
await expect(
emergencyAccessService.takeover(mockId, mockEmail, mockName),
@ -204,6 +206,21 @@ describe("EmergencyAccessService", () => {
expect(emergencyAccessApiService.postEmergencyAccessPassword).not.toHaveBeenCalled();
});
it("should throw an error if the users private key cannot be retrieved", async () => {
emergencyAccessApiService.postEmergencyAccessTakeover.mockResolvedValueOnce({
keyEncrypted: "EncryptedKey",
kdf: KdfType.PBKDF2_SHA256,
kdfIterations: 500,
} as EmergencyAccessTakeoverResponse);
cryptoService.getPrivateKey.mockResolvedValue(null);
await expect(emergencyAccessService.takeover(mockId, mockEmail, mockName)).rejects.toThrow(
"user does not have a private key",
);
expect(emergencyAccessApiService.postEmergencyAccessPassword).not.toHaveBeenCalled();
});
});
describe("getRotatedKeys", () => {

View File

@ -209,7 +209,16 @@ export class EmergencyAccessService {
async getViewOnlyCiphers(id: string): Promise<CipherView[]> {
const response = await this.emergencyAccessApiService.postEmergencyAccessView(id);
const grantorKeyBuffer = await this.cryptoService.rsaDecrypt(response.keyEncrypted);
const activeUserPrivateKey = await this.cryptoService.getPrivateKey();
if (activeUserPrivateKey == null) {
throw new Error("Active user does not have a private key, cannot get view only ciphers.");
}
const grantorKeyBuffer = await this.cryptoService.rsaDecrypt(
response.keyEncrypted,
activeUserPrivateKey,
);
const grantorUserKey = new SymmetricCryptoKey(grantorKeyBuffer) as UserKey;
const ciphers = await this.encryptService.decryptItems(
@ -229,7 +238,16 @@ export class EmergencyAccessService {
async takeover(id: string, masterPassword: string, email: string) {
const takeoverResponse = await this.emergencyAccessApiService.postEmergencyAccessTakeover(id);
const grantorKeyBuffer = await this.cryptoService.rsaDecrypt(takeoverResponse.keyEncrypted);
const activeUserPrivateKey = await this.cryptoService.getPrivateKey();
if (activeUserPrivateKey == null) {
throw new Error("Active user does not have a private key, cannot complete a takeover.");
}
const grantorKeyBuffer = await this.cryptoService.rsaDecrypt(
takeoverResponse.keyEncrypted,
activeUserPrivateKey,
);
if (grantorKeyBuffer == null) {
throw new Error("Failed to decrypt grantor key");
}

View File

@ -25,7 +25,13 @@ export class EncryptedOrganizationKey implements BaseEncryptedOrganizationKey {
constructor(private key: string) {}
async decrypt(cryptoService: CryptoService) {
const decValue = await cryptoService.rsaDecrypt(this.key);
const activeUserPrivateKey = await cryptoService.getPrivateKey();
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;
}

View File

@ -311,15 +311,17 @@ export abstract class CryptoService {
* @param data The data to encrypt
* @param publicKey The public key to use for encryption, if not provided, the user's public key will be used
* @returns The encrypted data
* @throws If the given publicKey is a null-ish value.
*/
abstract rsaEncrypt(data: Uint8Array, publicKey?: Uint8Array): Promise<EncString>;
abstract rsaEncrypt(data: Uint8Array, publicKey: Uint8Array): Promise<EncString>;
/**
* Decrypts a value using RSA.
* @param encValue The encrypted value to decrypt
* @param privateKeyValue The private key to use for decryption
* @param privateKey The private key to use for decryption
* @returns The decrypted value
* @throws If the given privateKey is a null-ish value.
*/
abstract rsaDecrypt(encValue: string, privateKeyValue?: Uint8Array): Promise<Uint8Array>;
abstract rsaDecrypt(encValue: string, privateKey: Uint8Array): Promise<Uint8Array>;
abstract randomNumber(min: number, max: number): Promise<number>;
/**
* Generates a new cipher key

View File

@ -621,19 +621,20 @@ export class CryptoService implements CryptoServiceAbstraction {
await this.stateProvider.setUserState(USER_EVER_HAD_USER_KEY, null, userId);
}
async rsaEncrypt(data: Uint8Array, publicKey?: Uint8Array): Promise<EncString> {
async rsaEncrypt(data: Uint8Array, publicKey: Uint8Array): Promise<EncString> {
if (publicKey == null) {
publicKey = await this.getPublicKey();
}
if (publicKey == null) {
throw new Error("Public key unavailable.");
throw new Error("'publicKey' is a required parameter and must be non-null");
}
const encBytes = await this.cryptoFunctionService.rsaEncrypt(data, publicKey, "sha1");
return new EncString(EncryptionType.Rsa2048_OaepSha1_B64, Utils.fromBufferToB64(encBytes));
}
async rsaDecrypt(encValue: string, privateKeyValue?: Uint8Array): Promise<Uint8Array> {
async rsaDecrypt(encValue: string, privateKey: Uint8Array): Promise<Uint8Array> {
if (privateKey == null) {
throw new Error("'privateKey' is a required parameter and must be non-null");
}
const headerPieces = encValue.split(".");
let encType: EncryptionType = null;
let encPieces: string[];
@ -665,10 +666,6 @@ export class CryptoService implements CryptoServiceAbstraction {
}
const data = Utils.fromB64ToArray(encPieces[0]);
const privateKey = privateKeyValue ?? (await this.getPrivateKey());
if (privateKey == null) {
throw new Error("No private key.");
}
let alg: "sha1" | "sha256" = "sha1";
switch (encType) {

View File

@ -65,6 +65,10 @@ describe("derived decrypted org keys", () => {
"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
cryptoService.rsaDecrypt.mockResolvedValueOnce(decryptedOrgKeys["org-id-1"].key);
cryptoService.rsaDecrypt.mockResolvedValueOnce(decryptedOrgKeys["org-id-2"].key);