1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-06 00:13:28 +00:00

[PM-23809] Add simplified interface to MP service (#15631)

* Add new mp service api

* Fix tests

* Add test coverage

* Add newline

* Fix type

* Rename to "unwrapUserKeyFromMasterPasswordUnlockData"

* Fix build

* Fix build on cli

* Fix linting

* Re-sort spec

* Add tests

* Fix test and build issues

* Fix build

* Clean up

* Remove introduced function

* Clean up comments

* Fix abstract class types

* Fix comments

* Cleanup

* Cleanup

* Update libs/common/src/key-management/master-password/types/master-password.types.ts

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>

* Update libs/common/src/key-management/master-password/services/master-password.service.ts

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>

* Update libs/common/src/key-management/master-password/abstractions/master-password.service.abstraction.ts

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>

* Update libs/common/src/key-management/master-password/types/master-password.types.ts

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>

* Update libs/common/src/key-management/master-password/abstractions/master-password.service.abstraction.ts

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>

* Add comments

* Fix build

* Add arg null check

* Cleanup

* Fix build

* Fix build on browser

* Update libs/common/src/key-management/master-password/abstractions/master-password.service.abstraction.ts

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>

* Update libs/common/src/key-management/master-password/abstractions/master-password.service.abstraction.ts

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>

* Update libs/common/src/key-management/master-password/abstractions/master-password.service.abstraction.ts

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>

* Add tests for null params

* Cleanup and deprecate more functions

* Fix formatting

* Prettier

* Clean up

* Update libs/key-management/src/abstractions/key.service.ts

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>

* Make emailToSalt private and expose abstract saltForUser

* Add tests

* Add docs

* Update libs/common/src/key-management/master-password/abstractions/master-password.service.abstraction.ts

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>

* Update libs/common/src/key-management/master-password/abstractions/master-password.service.abstraction.ts

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>

---------

Co-authored-by: Thomas Avery <43214426+Thomas-Avery@users.noreply.github.com>
This commit is contained in:
Bernd Schoolmann
2025-07-29 18:53:17 +02:00
committed by GitHub
parent 95f037390e
commit a7d3c0f5c2
11 changed files with 418 additions and 6 deletions

View File

@@ -668,6 +668,8 @@ export default class MainBackground {
this.keyGenerationService,
this.encryptService,
this.logService,
this.cryptoFunctionService,
this.accountService,
);
this.i18nService = new I18nService(BrowserApi.getUILanguage(), this.globalStateProvider);

View File

@@ -431,16 +431,17 @@ export class ServiceContainer {
migrationRunner,
);
this.kdfConfigService = new DefaultKdfConfigService(this.stateProvider);
this.masterPasswordService = new MasterPasswordService(
this.stateProvider,
this.stateService,
this.keyGenerationService,
this.encryptService,
this.logService,
this.cryptoFunctionService,
this.accountService,
);
this.kdfConfigService = new DefaultKdfConfigService(this.stateProvider);
this.pinService = new PinService(
this.accountService,
this.cryptoFunctionService,

View File

@@ -1021,6 +1021,8 @@ const safeProviders: SafeProvider[] = [
KeyGenerationServiceAbstraction,
EncryptService,
LogService,
CryptoFunctionServiceAbstraction,
AccountServiceAbstraction,
],
}),
safeProvider({

View File

@@ -1,9 +1,17 @@
import { Observable } from "rxjs";
// eslint-disable-next-line no-restricted-imports
import { KdfConfig } from "@bitwarden/key-management";
import { ForceSetPasswordReason } from "../../../auth/models/domain/force-set-password-reason";
import { UserId } from "../../../types/guid";
import { MasterKey, UserKey } from "../../../types/key";
import { EncString } from "../../crypto/models/enc-string";
import {
MasterPasswordAuthenticationData,
MasterPasswordSalt,
MasterPasswordUnlockData,
} from "../types/master-password.types";
export abstract class MasterPasswordServiceAbstraction {
/**
@@ -12,14 +20,23 @@ export abstract class MasterPasswordServiceAbstraction {
* @throws If the user ID is missing.
*/
abstract forceSetPasswordReason$: (userId: UserId) => Observable<ForceSetPasswordReason>;
/**
* An observable that emits the master password salt for the user.
* @param userId The user ID.
* @throws If the user ID is missing.
* @throws If the user ID is provided, but the user is not found.
*/
abstract saltForUser$: (userId: UserId) => Observable<MasterPasswordSalt>;
/**
* An observable that emits the master key for the user.
* @deprecated Interacting with the master-key directly is deprecated. Please use {@link makeMasterPasswordUnlockData}, {@link makeMasterPasswordAuthenticationData} or {@link unwrapUserKeyFromMasterPasswordUnlockData} instead.
* @param userId The user ID.
* @throws If the user ID is missing.
*/
abstract masterKey$: (userId: UserId) => Observable<MasterKey>;
/**
* An observable that emits the master key hash for the user.
* @deprecated Interacting with the master-key directly is deprecated. Please use {@link makeMasterPasswordAuthenticationData}.
* @param userId The user ID.
* @throws If the user ID is missing.
*/
@@ -32,6 +49,7 @@ export abstract class MasterPasswordServiceAbstraction {
abstract getMasterKeyEncryptedUserKey: (userId: UserId) => Promise<EncString>;
/**
* Decrypts the user key with the provided master key
* @deprecated Interacting with the master-key directly is deprecated. Please use {@link unwrapUserKeyFromMasterPasswordUnlockData} instead.
* @param masterKey The user's master key
* * @param userId The desired user
* @param userKey The user's encrypted symmetric key
@@ -44,12 +62,52 @@ export abstract class MasterPasswordServiceAbstraction {
userId: string,
userKey?: EncString,
) => Promise<UserKey | null>;
/**
* Makes the authentication hash for authenticating to the server with the master password.
* @param password The master password.
* @param kdf The KDF configuration.
* @param salt The master password salt to use. See {@link saltForUser$} for current salt.
* @throws If password, KDF or salt are null or undefined.
*/
abstract makeMasterPasswordAuthenticationData: (
password: string,
kdf: KdfConfig,
salt: MasterPasswordSalt,
) => Promise<MasterPasswordAuthenticationData>;
/**
* Creates a MasterPasswordUnlockData bundle that encrypts the user-key with a key derived from the password. The
* bundle also contains the KDF settings and salt used to derive the key, which are required to decrypt the user-key later.
* @param password The master password.
* @param kdf The KDF configuration.
* @param salt The master password salt to use. See {@link saltForUser$} for current salt.
* @param userKey The user's userKey to encrypt.
* @throws If password, KDF, salt, or userKey are null or undefined.
*/
abstract makeMasterPasswordUnlockData: (
password: string,
kdf: KdfConfig,
salt: MasterPasswordSalt,
userKey: UserKey,
) => Promise<MasterPasswordUnlockData>;
/**
* Unwraps a user-key that was wrapped with a password provided KDF settings. The same KDF settings and salt must be provided to unwrap the user-key, otherwise it will fail to decrypt.
* @throws If the encryption type is not supported.
* @throws If the password, KDF, or salt don't match the original wrapping parameters.
*/
abstract unwrapUserKeyFromMasterPasswordUnlockData: (
password: string,
masterPasswordUnlockData: MasterPasswordUnlockData,
) => Promise<UserKey>;
}
export abstract class InternalMasterPasswordServiceAbstraction extends MasterPasswordServiceAbstraction {
/**
* Set the master key for the user.
* Note: Use {@link clearMasterKey} to clear the master key.
* @deprecated Interacting with the master-key directly is deprecated.
* @param masterKey The master key.
* @param userId The user ID.
* @throws If the user ID or master key is missing.
@@ -57,6 +115,7 @@ export abstract class InternalMasterPasswordServiceAbstraction extends MasterPas
abstract setMasterKey: (masterKey: MasterKey, userId: UserId) => Promise<void>;
/**
* Clear the master key for the user.
* @deprecated Interacting with the master-key directly is deprecated.
* @param userId The user ID.
* @throws If the user ID is missing.
*/
@@ -64,6 +123,7 @@ export abstract class InternalMasterPasswordServiceAbstraction extends MasterPas
/**
* Set the master key hash for the user.
* Note: Use {@link clearMasterKeyHash} to clear the master key hash.
* @deprecated Interacting with the master-key directly is deprecated.
* @param masterKeyHash The master key hash.
* @param userId The user ID.
* @throws If the user ID or master key hash is missing.
@@ -71,6 +131,7 @@ export abstract class InternalMasterPasswordServiceAbstraction extends MasterPas
abstract setMasterKeyHash: (masterKeyHash: string, userId: UserId) => Promise<void>;
/**
* Clear the master key hash for the user.
* @deprecated Interacting with the master-key directly is deprecated.
* @param userId The user ID.
* @throws If the user ID is missing.
*/

View File

@@ -3,11 +3,20 @@
import { mock } from "jest-mock-extended";
import { ReplaySubject, Observable } from "rxjs";
// FIXME: Update this file to be type safe and remove this and next line
// eslint-disable-next-line no-restricted-imports
import { KdfConfig } from "@bitwarden/key-management";
import { ForceSetPasswordReason } from "../../../auth/models/domain/force-set-password-reason";
import { UserId } from "../../../types/guid";
import { MasterKey, UserKey } from "../../../types/key";
import { EncString } from "../../crypto/models/enc-string";
import { InternalMasterPasswordServiceAbstraction } from "../abstractions/master-password.service.abstraction";
import {
MasterPasswordAuthenticationData,
MasterPasswordSalt,
MasterPasswordUnlockData,
} from "../types/master-password.types";
export class FakeMasterPasswordService implements InternalMasterPasswordServiceAbstraction {
mock = mock<InternalMasterPasswordServiceAbstraction>();
@@ -24,6 +33,10 @@ export class FakeMasterPasswordService implements InternalMasterPasswordServiceA
this.masterKeyHashSubject.next(initialMasterKeyHash);
}
saltForUser$(userId: UserId): Observable<MasterPasswordSalt> {
return this.mock.saltForUser$(userId);
}
masterKey$(userId: UserId): Observable<MasterKey> {
return this.masterKeySubject.asObservable();
}
@@ -71,4 +84,28 @@ export class FakeMasterPasswordService implements InternalMasterPasswordServiceA
): Promise<UserKey> {
return this.mock.decryptUserKeyWithMasterKey(masterKey, userId, userKey);
}
makeMasterPasswordAuthenticationData(
password: string,
kdf: KdfConfig,
salt: MasterPasswordSalt,
): Promise<MasterPasswordAuthenticationData> {
return this.mock.makeMasterPasswordAuthenticationData(password, kdf, salt);
}
makeMasterPasswordUnlockData(
password: string,
kdf: KdfConfig,
salt: MasterPasswordSalt,
userKey: UserKey,
): Promise<MasterPasswordUnlockData> {
return this.mock.makeMasterPasswordUnlockData(password, kdf, salt, userKey);
}
unwrapUserKeyFromMasterPasswordUnlockData(
password: string,
masterPasswordUnlockData: MasterPasswordUnlockData,
): Promise<UserKey> {
return this.mock.unwrapUserKeyFromMasterPasswordUnlockData(password, masterPasswordUnlockData);
}
}

View File

@@ -1,8 +1,17 @@
import { mock, MockProxy } from "jest-mock-extended";
import { of } from "rxjs";
import { firstValueFrom, of } from "rxjs";
import * as rxjs from "rxjs";
import { makeSymmetricCryptoKey } from "../../../../spec";
import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
// eslint-disable-next-line no-restricted-imports
import { KdfConfig, PBKDF2KdfConfig } from "@bitwarden/key-management";
import {
FakeAccountService,
makeSymmetricCryptoKey,
mockAccountServiceWith,
} from "../../../../spec";
import { ForceSetPasswordReason } from "../../../auth/models/domain/force-set-password-reason";
import { KeyGenerationService } from "../../../platform/abstractions/key-generation.service";
import { LogService } from "../../../platform/abstractions/log.service";
@@ -10,9 +19,11 @@ import { StateService } from "../../../platform/abstractions/state.service";
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
import { StateProvider } from "../../../platform/state";
import { UserId } from "../../../types/guid";
import { MasterKey } from "../../../types/key";
import { MasterKey, UserKey } from "../../../types/key";
import { CryptoFunctionService } from "../../crypto/abstractions/crypto-function.service";
import { EncryptService } from "../../crypto/abstractions/encrypt.service";
import { EncString } from "../../crypto/models/enc-string";
import { MasterPasswordSalt } from "../types/master-password.types";
import { MasterPasswordService } from "./master-password.service";
@@ -24,8 +35,10 @@ describe("MasterPasswordService", () => {
let keyGenerationService: MockProxy<KeyGenerationService>;
let encryptService: MockProxy<EncryptService>;
let logService: MockProxy<LogService>;
let cryptoFunctionService: MockProxy<CryptoFunctionService>;
let accountService: FakeAccountService;
const userId = "user-id" as UserId;
const userId = "00000000-0000-0000-0000-000000000000" as UserId;
const mockUserState = {
state$: of(null),
update: jest.fn().mockResolvedValue(null),
@@ -45,6 +58,8 @@ describe("MasterPasswordService", () => {
keyGenerationService = mock<KeyGenerationService>();
encryptService = mock<EncryptService>();
logService = mock<LogService>();
cryptoFunctionService = mock<CryptoFunctionService>();
accountService = mockAccountServiceWith(userId);
stateProvider.getUser.mockReturnValue(mockUserState as any);
@@ -56,10 +71,33 @@ describe("MasterPasswordService", () => {
keyGenerationService,
encryptService,
logService,
cryptoFunctionService,
accountService,
);
encryptService.unwrapSymmetricKey.mockResolvedValue(makeSymmetricCryptoKey(64, 1));
keyGenerationService.stretchKey.mockResolvedValue(makeSymmetricCryptoKey(64, 3));
Object.defineProperty(SdkLoadService, "Ready", {
value: Promise.resolve(),
configurable: true,
});
});
describe("saltForUser$", () => {
it("throws when userid not present", async () => {
expect(() => {
sut.saltForUser$(null as unknown as UserId);
}).toThrow("userId is null or undefined.");
});
it("throws when userid present but not in account service", async () => {
await expect(
firstValueFrom(sut.saltForUser$("00000000-0000-0000-0000-000000000001" as UserId)),
).rejects.toThrow("Cannot read properties of undefined (reading 'email')");
});
it("returns salt", async () => {
const salt = await firstValueFrom(sut.saltForUser$(userId));
expect(salt).toBeDefined();
});
});
describe("setForceSetPasswordReason", () => {
@@ -190,4 +228,97 @@ describe("MasterPasswordService", () => {
expect(updateFn(null)).toEqual(encryptedKey.toJSON());
});
});
describe("makeMasterPasswordAuthenticationData", () => {
const password = "test-password";
const kdf: KdfConfig = new PBKDF2KdfConfig(600_000);
const salt = "test@bitwarden.com" as MasterPasswordSalt;
const masterKey = makeSymmetricCryptoKey(32, 2);
const masterKeyHash = makeSymmetricCryptoKey(32, 3).toEncoded();
beforeEach(() => {
keyGenerationService.deriveKeyFromPassword.mockResolvedValue(masterKey);
cryptoFunctionService.pbkdf2.mockResolvedValue(masterKeyHash);
});
it("derives master key and creates authentication hash", async () => {
const result = await sut.makeMasterPasswordAuthenticationData(password, kdf, salt);
expect(keyGenerationService.deriveKeyFromPassword).toHaveBeenCalledWith(password, salt, kdf);
expect(cryptoFunctionService.pbkdf2).toHaveBeenCalledWith(
masterKey.toEncoded(),
password,
"sha256",
1,
);
expect(result).toEqual({
kdf,
salt,
masterPasswordAuthenticationHash: Utils.fromBufferToB64(masterKeyHash),
});
});
it("throws if password is null", async () => {
await expect(
sut.makeMasterPasswordAuthenticationData(null as unknown as string, kdf, salt),
).rejects.toThrow();
});
it("throws if kdf is null", async () => {
await expect(
sut.makeMasterPasswordAuthenticationData(password, null as unknown as KdfConfig, salt),
).rejects.toThrow();
});
it("throws if salt is null", async () => {
await expect(
sut.makeMasterPasswordAuthenticationData(
password,
kdf,
null as unknown as MasterPasswordSalt,
),
).rejects.toThrow();
});
});
describe("wrapUnwrapUserKeyWithPassword", () => {
const password = "test-password";
const kdf: KdfConfig = new PBKDF2KdfConfig(600_000);
const salt = "test@bitwarden.com" as MasterPasswordSalt;
const userKey = makeSymmetricCryptoKey(64, 2) as UserKey;
it("wraps and unwraps user key with password", async () => {
const unlockData = await sut.makeMasterPasswordUnlockData(password, kdf, salt, userKey);
const unwrappedUserkey = await sut.unwrapUserKeyFromMasterPasswordUnlockData(
password,
unlockData,
);
expect(unwrappedUserkey).toEqual(userKey);
});
it("throws if password is null", async () => {
await expect(
sut.makeMasterPasswordUnlockData(null as unknown as string, kdf, salt, userKey),
).rejects.toThrow();
});
it("throws if kdf is null", async () => {
await expect(
sut.makeMasterPasswordUnlockData(password, null as unknown as KdfConfig, salt, userKey),
).rejects.toThrow();
});
it("throws if salt is null", async () => {
await expect(
sut.makeMasterPasswordUnlockData(
password,
kdf,
null as unknown as MasterPasswordSalt,
userKey,
),
).rejects.toThrow();
});
it("throws if userKey is null", async () => {
await expect(
sut.makeMasterPasswordUnlockData(password, kdf, salt, null as unknown as UserKey),
).rejects.toThrow();
});
});
});

View File

@@ -2,6 +2,14 @@
// @ts-strict-ignore
import { firstValueFrom, map, Observable } from "rxjs";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { assertNonNullish } from "@bitwarden/common/auth/utils";
import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
// eslint-disable-next-line no-restricted-imports
import { KdfConfig } from "@bitwarden/key-management";
import { PureCrypto } from "@bitwarden/sdk-internal";
import { ForceSetPasswordReason } from "../../../auth/models/domain/force-set-password-reason";
import { KeyGenerationService } from "../../../platform/abstractions/key-generation.service";
import { LogService } from "../../../platform/abstractions/log.service";
@@ -16,9 +24,17 @@ import {
} from "../../../platform/state";
import { UserId } from "../../../types/guid";
import { MasterKey, UserKey } from "../../../types/key";
import { CryptoFunctionService } from "../../crypto/abstractions/crypto-function.service";
import { EncryptService } from "../../crypto/abstractions/encrypt.service";
import { EncryptedString, EncString } from "../../crypto/models/enc-string";
import { InternalMasterPasswordServiceAbstraction } from "../abstractions/master-password.service.abstraction";
import {
MasterKeyWrappedUserKey,
MasterPasswordAuthenticationData,
MasterPasswordAuthenticationHash,
MasterPasswordSalt,
MasterPasswordUnlockData,
} from "../types/master-password.types";
/** Memory since master key shouldn't be available on lock */
const MASTER_KEY = new UserKeyDefinition<MasterKey>(MASTER_PASSWORD_MEMORY, "masterKey", {
@@ -59,8 +75,18 @@ export class MasterPasswordService implements InternalMasterPasswordServiceAbstr
private keyGenerationService: KeyGenerationService,
private encryptService: EncryptService,
private logService: LogService,
private cryptoFunctionService: CryptoFunctionService,
private accountService: AccountService,
) {}
saltForUser$(userId: UserId): Observable<MasterPasswordSalt> {
assertNonNullish(userId, "userId");
return this.accountService.accounts$.pipe(
map((accounts) => accounts[userId].email),
map((email) => this.emailToSalt(email)),
);
}
masterKey$(userId: UserId): Observable<MasterKey> {
if (userId == null) {
throw new Error("User ID is required.");
@@ -95,6 +121,10 @@ export class MasterPasswordService implements InternalMasterPasswordServiceAbstr
return EncString.fromJSON(key);
}
private emailToSalt(email: string): MasterPasswordSalt {
return email.toLowerCase().trim() as MasterPasswordSalt;
}
async setMasterKey(masterKey: MasterKey, userId: UserId): Promise<void> {
if (masterKey == null) {
throw new Error("Master key is required.");
@@ -202,4 +232,89 @@ export class MasterPasswordService implements InternalMasterPasswordServiceAbstr
return decUserKey as UserKey;
}
async makeMasterPasswordAuthenticationData(
password: string,
kdf: KdfConfig,
salt: MasterPasswordSalt,
): Promise<MasterPasswordAuthenticationData> {
assertNonNullish(password, "password");
assertNonNullish(kdf, "kdf");
assertNonNullish(salt, "salt");
// We don't trust callers to use masterpasswordsalt correctly. They may type assert incorrectly.
salt = salt.toLowerCase().trim() as MasterPasswordSalt;
const SERVER_AUTHENTICATION_HASH_ITERATIONS = 1;
const masterKey = (await this.keyGenerationService.deriveKeyFromPassword(
password,
salt,
kdf,
)) as MasterKey;
const masterPasswordAuthenticationHash = Utils.fromBufferToB64(
await this.cryptoFunctionService.pbkdf2(
masterKey.toEncoded(),
password,
"sha256",
SERVER_AUTHENTICATION_HASH_ITERATIONS,
),
) as MasterPasswordAuthenticationHash;
return {
salt,
kdf,
masterPasswordAuthenticationHash,
} as MasterPasswordAuthenticationData;
}
async makeMasterPasswordUnlockData(
password: string,
kdf: KdfConfig,
salt: MasterPasswordSalt,
userKey: UserKey,
): Promise<MasterPasswordUnlockData> {
assertNonNullish(password, "password");
assertNonNullish(kdf, "kdf");
assertNonNullish(salt, "salt");
assertNonNullish(userKey, "userKey");
// We don't trust callers to use masterpasswordsalt correctly. They may type assert incorrectly.
salt = salt.toLowerCase().trim() as MasterPasswordSalt;
await SdkLoadService.Ready;
const masterKeyWrappedUserKey = new EncString(
PureCrypto.encrypt_user_key_with_master_password(
userKey.toEncoded(),
password,
salt,
kdf.toSdkConfig(),
),
) as MasterKeyWrappedUserKey;
return {
salt,
kdf,
masterKeyWrappedUserKey,
};
}
async unwrapUserKeyFromMasterPasswordUnlockData(
password: string,
masterPasswordUnlockData: MasterPasswordUnlockData,
): Promise<UserKey> {
assertNonNullish(password, "password");
assertNonNullish(masterPasswordUnlockData, "masterPasswordUnlockData");
await SdkLoadService.Ready;
const userKey = new SymmetricCryptoKey(
PureCrypto.decrypt_user_key_with_master_password(
masterPasswordUnlockData.masterKeyWrappedUserKey.encryptedString,
password,
masterPasswordUnlockData.salt,
masterPasswordUnlockData.kdf.toSdkConfig(),
),
);
return userKey as UserKey;
}
}

View File

@@ -0,0 +1,34 @@
import { Opaque } from "type-fest";
// eslint-disable-next-line no-restricted-imports
import { KdfConfig } from "@bitwarden/key-management";
import { EncString } from "../../crypto/models/enc-string";
/**
* The Base64-encoded master password authentication hash, that is sent to the server for authentication.
*/
export type MasterPasswordAuthenticationHash = Opaque<string, "MasterPasswordAuthenticationHash">;
/**
* You MUST obtain this through the emailToSalt function in MasterPasswordService
*/
export type MasterPasswordSalt = Opaque<string, "MasterPasswordSalt">;
export type MasterKeyWrappedUserKey = Opaque<EncString, "MasterPasswordSalt">;
/**
* The data required to unlock with the master password.
*/
export type MasterPasswordUnlockData = {
salt: MasterPasswordSalt;
kdf: KdfConfig;
masterKeyWrappedUserKey: MasterKeyWrappedUserKey;
};
/**
* The data required to authenticate with the master password.
*/
export type MasterPasswordAuthenticationData = {
salt: MasterPasswordSalt;
kdf: KdfConfig;
masterPasswordAuthenticationHash: MasterPasswordAuthenticationHash;
};

View File

@@ -6,6 +6,7 @@ import { SymmetricCryptoKey } from "../platform/models/domain/symmetric-crypto-k
export type DeviceKey = Opaque<SymmetricCryptoKey, "DeviceKey">;
export type PrfKey = Opaque<SymmetricCryptoKey, "PrfKey">;
export type UserKey = Opaque<SymmetricCryptoKey, "UserKey">;
/** @deprecated Interacting with the master key directly is prohibited. Use a high level function from MasterPasswordService instead. */
export type MasterKey = Opaque<SymmetricCryptoKey, "MasterKey">;
export type PinKey = Opaque<SymmetricCryptoKey, "PinKey">;
export type OrgKey = Opaque<SymmetricCryptoKey, "OrgKey">;

View File

@@ -150,11 +150,18 @@ export abstract class KeyService {
/**
* Generates a new user key
* @deprecated Interacting with the master key directly is prohibited. Use {@link makeUserKeyV1} instead.
* @throws Error when master key is null and there is no active user
* @param masterKey The user's master key. When null, grabs master key from active user.
* @returns A new user key and the master key protected version of it
*/
abstract makeUserKey(masterKey: MasterKey | null): Promise<[UserKey, EncString]>;
/**
* Generates a new user key for a V1 user
* Note: This will be replaced by a higher level function to initialize a whole users cryptographic state in the near future.
* @returns A new user key
*/
abstract makeUserKeyV1(): Promise<UserKey>;
/**
* Clears the user's stored version of the user key
* @param keySuffix The desired version of the key to clear
@@ -166,6 +173,7 @@ export abstract class KeyService {
* Retrieves the user's master key if it is in state, or derives it from the provided password
* @param password The user's master password that will be used to derive a master key if one isn't found
* @param userId The desired user
* @deprecated Interacting with the master key directly is prohibited. Use a high level function from MasterPasswordService instead.
* @throws Error when userId is null/undefined.
* @throws Error when email or Kdf configuration cannot be found for the user.
* @returns The user's master key if it exists, or a newly derived master key.
@@ -173,6 +181,7 @@ export abstract class KeyService {
abstract getOrDeriveMasterKey(password: string, userId: UserId): Promise<MasterKey>;
/**
* Generates a master key from the provided password
* @deprecated Interacting with the master key directly is prohibited.
* @param password The user's master password
* @param email The user's email
* @param KdfConfig The user's key derivation function configuration
@@ -182,6 +191,7 @@ export abstract class KeyService {
/**
* Encrypts the existing (or provided) user key with the
* provided master key
* @deprecated Interacting with the master key directly is prohibited. Use a high level function from MasterPasswordService instead.
* @param masterKey The user's master key
* @param userKey The user key
* @returns The user key and the master key protected version of it
@@ -194,6 +204,7 @@ export abstract class KeyService {
* Creates a master password hash from the user's master password. Can
* be used for local authentication or for server authentication depending
* on the hashPurpose provided.
* @deprecated Interacting with the master key directly is prohibited. Use a high level function from MasterPasswordService instead.
* @param password The user's master password
* @param key The user's master key or active's user master key.
* @param hashPurpose The iterations to use for the hash. Defaults to {@link HashPurpose.ServerAuthorization}.
@@ -207,6 +218,7 @@ export abstract class KeyService {
): Promise<string>;
/**
* Compares the provided master password to the stored password hash.
* @deprecated Interacting with the master key directly is prohibited. Use a high level function from MasterPasswordService instead.
* @param masterPassword The user's master password
* @param masterKey The user's master key
* @param userId The id of the user to do the operation for.

View File

@@ -232,6 +232,11 @@ export class DefaultKeyService implements KeyServiceAbstraction {
return this.buildProtectedSymmetricKey(masterKey, newUserKey);
}
async makeUserKeyV1(): Promise<UserKey> {
const newUserKey = await this.keyGenerationService.createKey(512);
return newUserKey as UserKey;
}
/**
* Clears the user key. Clears all stored versions of the user keys as well, such as the biometrics key
* @param userId The desired user
@@ -259,6 +264,9 @@ export class DefaultKeyService implements KeyServiceAbstraction {
}
}
/**
* @deprecated Please use `makeMasterPasswordAuthenticationData`, `unwrapUserKeyFromMasterPasswordUnlockData` or `makeMasterPasswordUnlockData` in @link MasterPasswordService instead.
*/
async getOrDeriveMasterKey(password: string, userId: UserId): Promise<MasterKey> {
if (userId == null) {
throw new Error("User ID is required.");
@@ -287,6 +295,8 @@ export class DefaultKeyService implements KeyServiceAbstraction {
/**
* Derive a master key from a password and email.
*
* @deprecated Please use `makeMasterPasswordAuthenticationData`, `makeMasterPasswordAuthenticationData`, `unwrapUserKeyFromMasterPasswordUnlockData` in @link MasterPasswordService instead.
*
* @remarks
* Does not validate the kdf config to ensure it satisfies the minimum requirements for the given kdf type.
*/
@@ -304,6 +314,9 @@ export class DefaultKeyService implements KeyServiceAbstraction {
return masterKey;
}
/**
* @deprecated Please use `makeMasterPasswordUnlockData` in {@link MasterPasswordService} instead.
*/
async encryptUserKeyWithMasterKey(
masterKey: MasterKey,
userKey?: UserKey,
@@ -312,6 +325,9 @@ export class DefaultKeyService implements KeyServiceAbstraction {
return await this.buildProtectedSymmetricKey(masterKey, userKey);
}
/**
* @deprecated Please use `makeMasterPasswordAuthenticationData` in {@link MasterPasswordService} instead.
*/
async hashMasterKey(
password: string,
key: MasterKey,