1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-10 05:13:29 +00:00

Move asymmetric crypto functions out of crypto service (#10903)

This commit is contained in:
Bernd Schoolmann
2024-10-01 08:47:41 -07:00
committed by GitHub
parent f2339b0586
commit dafe795854
36 changed files with 126 additions and 152 deletions

View File

@@ -144,7 +144,7 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction {
deviceKeyEncryptedDevicePrivateKey,
] = await Promise.all([
// Encrypt user key with the DevicePublicKey
this.cryptoService.rsaEncrypt(userKey.key, devicePublicKey),
this.encryptService.rsaEncrypt(userKey.key, devicePublicKey),
// Encrypt devicePublicKey with user key
this.encryptService.encrypt(devicePublicKey, userKey),
@@ -206,7 +206,7 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction {
);
// Encrypt the brand new user key with the now-decrypted public key for the device
const encryptedNewUserKey = await this.cryptoService.rsaEncrypt(
const encryptedNewUserKey = await this.encryptService.rsaEncrypt(
newUserKey.key,
decryptedDevicePublicKey,
);
@@ -317,8 +317,8 @@ export class DeviceTrustService implements DeviceTrustServiceAbstraction {
);
// Attempt to decrypt encryptedUserDataKey with devicePrivateKey
const userKey = await this.cryptoService.rsaDecrypt(
encryptedUserKey.encryptedString,
const userKey = await this.encryptService.rsaDecrypt(
new EncString(encryptedUserKey.encryptedString),
devicePrivateKey,
);

View File

@@ -372,7 +372,7 @@ describe("deviceTrustService", () => {
.mockResolvedValue(mockUserKey);
cryptoSvcRsaEncryptSpy = jest
.spyOn(cryptoService, "rsaEncrypt")
.spyOn(encryptService, "rsaEncrypt")
.mockResolvedValue(mockDevicePublicKeyEncryptedUserKey);
encryptServiceEncryptSpy = jest
@@ -577,7 +577,7 @@ describe("deviceTrustService", () => {
.spyOn(encryptService, "decryptToBytes")
.mockResolvedValue(new Uint8Array(userKeyBytesLength));
const rsaDecryptSpy = jest
.spyOn(cryptoService, "rsaDecrypt")
.spyOn(encryptService, "rsaDecrypt")
.mockResolvedValue(new Uint8Array(userKeyBytesLength));
const result = await deviceTrustService.decryptUserKeyWithDeviceKey(
@@ -696,7 +696,7 @@ describe("deviceTrustService", () => {
});
// Mock the encryption of the new user key with the decrypted public key
cryptoService.rsaEncrypt.mockImplementationOnce((data, publicKey) => {
encryptService.rsaEncrypt.mockImplementationOnce((data, publicKey) => {
expect(data.byteLength).toBe(64); // New key should also be 64 bytes
expect(new Uint8Array(data)[0]).toBe(FakeNewUserKeyMarker); // New key should have the first byte be '1';

View File

@@ -2,6 +2,7 @@ import { mock, MockProxy } from "jest-mock-extended";
import { BehaviorSubject } from "rxjs";
import { OrganizationUserApiService } from "@bitwarden/admin-console/common";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { UserId } from "../../../../common/src/types/guid";
import { OrganizationApiServiceAbstraction } from "../../admin-console/abstractions/organization/organization-api.service.abstraction";
@@ -18,6 +19,7 @@ describe("PasswordResetEnrollmentServiceImplementation", () => {
let organizationApiService: MockProxy<OrganizationApiServiceAbstraction>;
let accountService: MockProxy<AccountService>;
let cryptoService: MockProxy<CryptoService>;
let encryptService: MockProxy<EncryptService>;
let organizationUserApiService: MockProxy<OrganizationUserApiService>;
let i18nService: MockProxy<I18nService>;
let service: PasswordResetEnrollmentServiceImplementation;
@@ -27,12 +29,14 @@ describe("PasswordResetEnrollmentServiceImplementation", () => {
accountService = mock<AccountService>();
accountService.activeAccount$ = activeAccountSubject;
cryptoService = mock<CryptoService>();
encryptService = mock<EncryptService>();
organizationUserApiService = mock<OrganizationUserApiService>();
i18nService = mock<I18nService>();
service = new PasswordResetEnrollmentServiceImplementation(
organizationApiService,
accountService,
cryptoService,
encryptService,
organizationUserApiService,
i18nService,
);
@@ -96,7 +100,7 @@ describe("PasswordResetEnrollmentServiceImplementation", () => {
activeAccountSubject.next(Object.assign(user1AccountInfo, { id: "userId" as UserId }));
cryptoService.getUserKey.mockResolvedValue({ key: "key" } as any);
cryptoService.rsaEncrypt.mockResolvedValue(encryptedKey as any);
encryptService.rsaEncrypt.mockResolvedValue(encryptedKey as any);
await service.enroll("orgId");
@@ -118,7 +122,7 @@ describe("PasswordResetEnrollmentServiceImplementation", () => {
};
const encryptedKey = { encryptedString: "encryptedString" };
organizationApiService.getKeys.mockResolvedValue(orgKeyResponse as any);
cryptoService.rsaEncrypt.mockResolvedValue(encryptedKey as any);
encryptService.rsaEncrypt.mockResolvedValue(encryptedKey as any);
await service.enroll("orgId", "userId", { key: "key" } as any);

View File

@@ -4,6 +4,7 @@ import {
OrganizationUserApiService,
OrganizationUserResetPasswordEnrollmentRequest,
} from "@bitwarden/admin-console/common";
import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.service";
import { OrganizationApiServiceAbstraction } from "../../admin-console/abstractions/organization/organization-api.service.abstraction";
import { CryptoService } from "../../platform/abstractions/crypto.service";
@@ -20,6 +21,7 @@ export class PasswordResetEnrollmentServiceImplementation
protected organizationApiService: OrganizationApiServiceAbstraction,
protected accountService: AccountService,
protected cryptoService: CryptoService,
protected encryptService: EncryptService,
protected organizationUserApiService: OrganizationUserApiService,
protected i18nService: I18nService,
) {}
@@ -47,7 +49,7 @@ export class PasswordResetEnrollmentServiceImplementation
userId ?? (await firstValueFrom(this.accountService.activeAccount$.pipe(map((a) => a?.id))));
userKey = userKey ?? (await this.cryptoService.getUserKey(userId));
// RSA Encrypt user's userKey.key with organization public key
const encryptedKey = await this.cryptoService.rsaEncrypt(userKey.key, orgPublicKey);
const encryptedKey = await this.encryptService.rsaEncrypt(userKey.key, orgPublicKey);
const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest();
resetRequest.resetPasswordKey = encryptedKey.encryptedString;

View File

@@ -329,22 +329,6 @@ export abstract class CryptoService {
* @param userId The user's Id
*/
abstract clearKeys(userId?: string): Promise<any>;
/**
* RSA encrypts a value.
* @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>;
/**
* Decrypts a value using RSA.
* @param encValue The encrypted value to decrypt
* @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, privateKey: Uint8Array): Promise<Uint8Array>;
abstract randomNumber(min: number, max: number): Promise<number>;
/**
* Generates a new cipher key

View File

@@ -45,7 +45,7 @@ import { KeyGenerationService } from "../abstractions/key-generation.service";
import { LogService } from "../abstractions/log.service";
import { PlatformUtilsService } from "../abstractions/platform-utils.service";
import { StateService } from "../abstractions/state.service";
import { KeySuffixOptions, HashPurpose, EncryptionType } from "../enums";
import { KeySuffixOptions, HashPurpose } from "../enums";
import { convertValues } from "../misc/convert-values";
import { EFFLongWordList } from "../misc/wordlist";
import { EncString, EncryptedString } from "../models/domain/enc-string";
@@ -441,7 +441,7 @@ export class CryptoService implements CryptoServiceAbstraction {
const shareKey = await this.keyGenerationService.createKey(512);
userId ??= await firstValueFrom(this.stateProvider.activeUserId$);
const publicKey = await firstValueFrom(this.userPublicKey$(userId));
const encShareKey = await this.rsaEncrypt(shareKey.key, publicKey);
const encShareKey = await this.encryptService.rsaEncrypt(shareKey.key, publicKey);
return [encShareKey, shareKey as T];
}
@@ -550,68 +550,6 @@ export class CryptoService implements CryptoServiceAbstraction {
await this.stateProvider.setUserState(USER_EVER_HAD_USER_KEY, null, userId);
}
async rsaEncrypt(data: Uint8Array, publicKey: Uint8Array): Promise<EncString> {
if (publicKey == null) {
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, 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[];
if (headerPieces.length === 1) {
encType = EncryptionType.Rsa2048_OaepSha256_B64;
encPieces = [headerPieces[0]];
} else if (headerPieces.length === 2) {
try {
encType = parseInt(headerPieces[0], null);
encPieces = headerPieces[1].split("|");
} catch (e) {
this.logService.error(e);
}
}
switch (encType) {
case EncryptionType.Rsa2048_OaepSha256_B64:
case EncryptionType.Rsa2048_OaepSha1_B64:
case EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64: // HmacSha256 types are deprecated
case EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64:
break;
default:
throw new Error("encType unavailable.");
}
if (encPieces == null || encPieces.length <= 0) {
throw new Error("encPieces unavailable.");
}
const data = Utils.fromB64ToArray(encPieces[0]);
let alg: "sha1" | "sha256" = "sha1";
switch (encType) {
case EncryptionType.Rsa2048_OaepSha256_B64:
case EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64:
alg = "sha256";
break;
case EncryptionType.Rsa2048_OaepSha1_B64:
case EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64:
break;
default:
throw new Error("encType unavailable.");
}
return this.cryptoFunctionService.rsaDecrypt(data, privateKey, alg);
}
// EFForg/OpenWireless
// ref https://github.com/EFForg/OpenWireless/blob/master/app/js/diceware.js
async randomNumber(min: number, max: number): Promise<number> {