From c9f642e491a28d9ff344ef7aed91ca07477731ec Mon Sep 17 00:00:00 2001 From: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Date: Fri, 11 Jul 2025 13:05:31 -0400 Subject: [PATCH] feat(new SendPasswordService): [Auth/PM-23700] Create KM SendPasswordService (#15570) * PM-23700 - SendPasswordService - create and test * PM-23700 - SendPassword Service comment clean up * PM-23700 - Use barrel file exports and register default service. * PM-23700 - DefaultSendPasswordService - work with Bernd to deliver better service --- .../src/services/jslib-services.module.ts | 9 +++ .../sends/abstractions/index.ts | 1 + .../abstractions/send-password.service.ts | 17 +++++ libs/common/src/key-management/sends/index.ts | 3 + .../default-send-password.service.spec.ts | 63 +++++++++++++++++++ .../services/default-send-password.service.ts | 27 ++++++++ .../key-management/sends/services/index.ts | 1 + .../src/key-management/sends/types/index.ts | 1 + .../sends/types/send-hashed-password.type.ts | 4 ++ 9 files changed, 126 insertions(+) create mode 100644 libs/common/src/key-management/sends/abstractions/index.ts create mode 100644 libs/common/src/key-management/sends/abstractions/send-password.service.ts create mode 100644 libs/common/src/key-management/sends/index.ts create mode 100644 libs/common/src/key-management/sends/services/default-send-password.service.spec.ts create mode 100644 libs/common/src/key-management/sends/services/default-send-password.service.ts create mode 100644 libs/common/src/key-management/sends/services/index.ts create mode 100644 libs/common/src/key-management/sends/types/index.ts create mode 100644 libs/common/src/key-management/sends/types/send-hashed-password.type.ts diff --git a/libs/angular/src/services/jslib-services.module.ts b/libs/angular/src/services/jslib-services.module.ts index 3af6c5b1eb1..391c20b30d6 100644 --- a/libs/angular/src/services/jslib-services.module.ts +++ b/libs/angular/src/services/jslib-services.module.ts @@ -169,6 +169,10 @@ import { MasterPasswordServiceAbstraction, } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; import { MasterPasswordService } from "@bitwarden/common/key-management/master-password/services/master-password.service"; +import { + SendPasswordService, + DefaultSendPasswordService, +} from "@bitwarden/common/key-management/sends"; import { DefaultVaultTimeoutService, DefaultVaultTimeoutSettingsService, @@ -1502,6 +1506,11 @@ const safeProviders: SafeProvider[] = [ useClass: DefaultCipherAuthorizationService, deps: [CollectionService, OrganizationServiceAbstraction, AccountServiceAbstraction], }), + safeProvider({ + provide: SendPasswordService, + useClass: DefaultSendPasswordService, + deps: [CryptoFunctionServiceAbstraction], + }), safeProvider({ provide: LoginApprovalComponentServiceAbstraction, useClass: DefaultLoginApprovalComponentService, diff --git a/libs/common/src/key-management/sends/abstractions/index.ts b/libs/common/src/key-management/sends/abstractions/index.ts new file mode 100644 index 00000000000..d8812f06ac9 --- /dev/null +++ b/libs/common/src/key-management/sends/abstractions/index.ts @@ -0,0 +1 @@ +export * from "./send-password.service"; diff --git a/libs/common/src/key-management/sends/abstractions/send-password.service.ts b/libs/common/src/key-management/sends/abstractions/send-password.service.ts new file mode 100644 index 00000000000..7ffa3169e2f --- /dev/null +++ b/libs/common/src/key-management/sends/abstractions/send-password.service.ts @@ -0,0 +1,17 @@ +import { SendHashedPassword, SendPasswordKeyMaterial } from "../types/send-hashed-password.type"; + +/** + * Service for managing passwords for sends. + */ +export abstract class SendPasswordService { + /** + * Hashes a raw send password using the provided key material + * @param password - the raw password to hash + * @param keyMaterial - the key material + * @returns a promise that resolves to the hashed password as a SendHashedPassword + */ + abstract hashPassword( + password: string, + keyMaterial: SendPasswordKeyMaterial, + ): Promise; +} diff --git a/libs/common/src/key-management/sends/index.ts b/libs/common/src/key-management/sends/index.ts new file mode 100644 index 00000000000..17299c79ea7 --- /dev/null +++ b/libs/common/src/key-management/sends/index.ts @@ -0,0 +1,3 @@ +export * from "./abstractions"; +export * from "./services"; +export * from "./types"; diff --git a/libs/common/src/key-management/sends/services/default-send-password.service.spec.ts b/libs/common/src/key-management/sends/services/default-send-password.service.spec.ts new file mode 100644 index 00000000000..54d57cb1b96 --- /dev/null +++ b/libs/common/src/key-management/sends/services/default-send-password.service.spec.ts @@ -0,0 +1,63 @@ +import { mock, MockProxy } from "jest-mock-extended"; + +import { SEND_KDF_ITERATIONS } from "../../../tools/send/send-kdf"; +import { CryptoFunctionService } from "../../crypto/abstractions/crypto-function.service"; +import { SendPasswordKeyMaterial } from "../types"; + +import { DefaultSendPasswordService } from "./default-send-password.service"; + +describe("DefaultSendPasswordService", () => { + let sendPasswordService: DefaultSendPasswordService; + let mockCryptoFunctionService: MockProxy; + + beforeEach(() => { + mockCryptoFunctionService = mock(); + + sendPasswordService = new DefaultSendPasswordService(mockCryptoFunctionService); + }); + + it("instantiates", () => { + expect(sendPasswordService).not.toBeFalsy(); + }); + + it("hashes a password with the provided key material", async () => { + // Arrange + const password = "testPassword"; + + const keyMaterial = new Uint8Array([1, 2, 3, 4, 5]) as SendPasswordKeyMaterial; + + const expectedHash = new Uint8Array([1, 2, 3, 4, 5]); // Mocked hash output + mockCryptoFunctionService.pbkdf2.mockResolvedValue(expectedHash); + + // Act + const result = await sendPasswordService.hashPassword(password, keyMaterial); + + // Assert + expect(mockCryptoFunctionService.pbkdf2).toHaveBeenCalledWith( + password, + keyMaterial, + "sha256", + SEND_KDF_ITERATIONS, + ); + + expect(result).toEqual(expectedHash); + }); + + it("throws an error if a password isn't provided", async () => { + // Arrange + const keyMaterial = new Uint8Array([1, 2, 3, 4, 5]) as SendPasswordKeyMaterial; + const expectedError = new Error("Password and key material are required."); + // Act & Assert + await expect(sendPasswordService.hashPassword("", keyMaterial)).rejects.toThrow(expectedError); + }); + + it("throws an error if key material isn't provided", async () => { + // Arrange + const password = "testPassword"; + const expectedError = new Error("Password and key material are required."); + // Act & Assert + await expect( + sendPasswordService.hashPassword(password, undefined as unknown as SendPasswordKeyMaterial), + ).rejects.toThrow(expectedError); + }); +}); diff --git a/libs/common/src/key-management/sends/services/default-send-password.service.ts b/libs/common/src/key-management/sends/services/default-send-password.service.ts new file mode 100644 index 00000000000..eba7469dd3a --- /dev/null +++ b/libs/common/src/key-management/sends/services/default-send-password.service.ts @@ -0,0 +1,27 @@ +import { SEND_KDF_ITERATIONS } from "../../../tools/send/send-kdf"; +import { CryptoFunctionService } from "../../crypto/abstractions/crypto-function.service"; +import { SendPasswordService } from "../abstractions/send-password.service"; +import { SendHashedPassword, SendPasswordKeyMaterial } from "../types/send-hashed-password.type"; + +export class DefaultSendPasswordService implements SendPasswordService { + constructor(private cryptoFunctionService: CryptoFunctionService) {} + + async hashPassword( + password: string, + keyMaterial: SendPasswordKeyMaterial, + ): Promise { + if (!password || !keyMaterial) { + throw new Error("Password and key material are required."); + } + + // Derive a password hash using the key material. + const passwordHash = await this.cryptoFunctionService.pbkdf2( + password, + keyMaterial, + "sha256", + SEND_KDF_ITERATIONS, + ); + + return passwordHash as SendHashedPassword; + } +} diff --git a/libs/common/src/key-management/sends/services/index.ts b/libs/common/src/key-management/sends/services/index.ts new file mode 100644 index 00000000000..4b42a7b46e0 --- /dev/null +++ b/libs/common/src/key-management/sends/services/index.ts @@ -0,0 +1 @@ +export * from "./default-send-password.service"; diff --git a/libs/common/src/key-management/sends/types/index.ts b/libs/common/src/key-management/sends/types/index.ts new file mode 100644 index 00000000000..c6f6567ae34 --- /dev/null +++ b/libs/common/src/key-management/sends/types/index.ts @@ -0,0 +1 @@ +export * from "./send-hashed-password.type"; diff --git a/libs/common/src/key-management/sends/types/send-hashed-password.type.ts b/libs/common/src/key-management/sends/types/send-hashed-password.type.ts new file mode 100644 index 00000000000..2b6fb34fc8f --- /dev/null +++ b/libs/common/src/key-management/sends/types/send-hashed-password.type.ts @@ -0,0 +1,4 @@ +import { Opaque } from "type-fest"; + +export type SendHashedPassword = Opaque; +export type SendPasswordKeyMaterial = Opaque;