From d581f06b321d5217130cfdfca99684b2fdc0f896 Mon Sep 17 00:00:00 2001 From: Jared Snider <116684653+JaredSnider-Bitwarden@users.noreply.github.com> Date: Wed, 3 Dec 2025 17:23:17 -0500 Subject: [PATCH 1/7] refactor(IdentityTokenResponse): [Auth/PM-3537] Remove deprecated KeyConnectorUrl from of IdentityTokenResponse + misc TDE cleanup (#17593) * PM-3537 - Remove KeyConnectorUrl from IdentityTokenResponse and clean up other flagged behavior * PM-3537 - SSO Login Strategy tests - remove key connector url * PM-3537 - Update LoginStrategyService tests to pass --- .../login-strategies/login.strategy.spec.ts | 2 +- .../common/login-strategies/login.strategy.ts | 2 +- .../sso-login.strategy.spec.ts | 61 ------------------- .../login-strategies/sso-login.strategy.ts | 12 +--- .../models/domain/user-decryption-options.ts | 21 +++---- .../login-strategy.service.spec.ts | 4 ++ .../response/identity-token.response.ts | 3 +- 7 files changed, 15 insertions(+), 90 deletions(-) diff --git a/libs/auth/src/common/login-strategies/login.strategy.spec.ts b/libs/auth/src/common/login-strategies/login.strategy.spec.ts index ceb36a44633..82e1183a1d6 100644 --- a/libs/auth/src/common/login-strategies/login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/login.strategy.spec.ts @@ -259,7 +259,7 @@ describe("LoginStrategy", () => { expect(userDecryptionOptionsService.setUserDecryptionOptionsById).toHaveBeenCalledWith( userId, - UserDecryptionOptions.fromResponse(idTokenResponse), + UserDecryptionOptions.fromIdentityTokenResponse(idTokenResponse), ); expect(masterPasswordService.mock.setMasterPasswordUnlockData).toHaveBeenCalledWith( new MasterPasswordUnlockData( diff --git a/libs/auth/src/common/login-strategies/login.strategy.ts b/libs/auth/src/common/login-strategies/login.strategy.ts index 2e3c41da900..08d5ae6246f 100644 --- a/libs/auth/src/common/login-strategies/login.strategy.ts +++ b/libs/auth/src/common/login-strategies/login.strategy.ts @@ -199,7 +199,7 @@ export abstract class LoginStrategy { // as the user decryption options help determine the available timeout actions. await this.userDecryptionOptionsService.setUserDecryptionOptionsById( userId, - UserDecryptionOptions.fromResponse(tokenResponse), + UserDecryptionOptions.fromIdentityTokenResponse(tokenResponse), ); if (tokenResponse.userDecryptionOptions?.masterPasswordUnlock != null) { diff --git a/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts b/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts index acbb680ae6d..3dbce6500a8 100644 --- a/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts +++ b/libs/auth/src/common/login-strategies/sso-login.strategy.spec.ts @@ -503,67 +503,6 @@ describe("SsoLoginStrategy", () => { HasMasterPassword: false, KeyConnectorOption: { KeyConnectorUrl: keyConnectorUrl }, }); - tokenResponse.keyConnectorUrl = keyConnectorUrl; - }); - - it("gets and sets the master key if Key Connector is enabled and the user doesn't have a master password", async () => { - const masterKey = new SymmetricCryptoKey( - new Uint8Array(64).buffer as CsprngArray, - ) as MasterKey; - - apiService.postIdentityToken.mockResolvedValue(tokenResponse); - masterPasswordService.masterKeySubject.next(masterKey); - - await ssoLoginStrategy.logIn(credentials); - - expect(keyConnectorService.setMasterKeyFromUrl).toHaveBeenCalledWith(keyConnectorUrl, userId); - }); - - it("converts new SSO user with no master password to Key Connector on first login", async () => { - tokenResponse.key = undefined; - tokenResponse.kdfConfig = new Argon2KdfConfig(10, 64, 4); - - apiService.postIdentityToken.mockResolvedValue(tokenResponse); - - await ssoLoginStrategy.logIn(credentials); - - expect(keyConnectorService.setNewSsoUserKeyConnectorConversionData).toHaveBeenCalledWith( - { - kdfConfig: new Argon2KdfConfig(10, 64, 4), - keyConnectorUrl: keyConnectorUrl, - organizationId: ssoOrgId, - }, - userId, - ); - }); - - it("decrypts and sets the user key if Key Connector is enabled and the user doesn't have a master password", async () => { - const userKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as UserKey; - const masterKey = new SymmetricCryptoKey( - new Uint8Array(64).buffer as CsprngArray, - ) as MasterKey; - - apiService.postIdentityToken.mockResolvedValue(tokenResponse); - masterPasswordService.masterKeySubject.next(masterKey); - masterPasswordService.mock.decryptUserKeyWithMasterKey.mockResolvedValue(userKey); - - await ssoLoginStrategy.logIn(credentials); - - expect(masterPasswordService.mock.decryptUserKeyWithMasterKey).toHaveBeenCalledWith( - masterKey, - userId, - undefined, - ); - expect(keyService.setUserKey).toHaveBeenCalledWith(userKey, userId); - }); - }); - - describe("Key Connector Pre-TDE", () => { - let tokenResponse: IdentityTokenResponse; - beforeEach(() => { - tokenResponse = identityTokenResponseFactory(); - tokenResponse.userDecryptionOptions = null; - tokenResponse.keyConnectorUrl = keyConnectorUrl; }); it("gets and sets the master key if Key Connector is enabled and the user doesn't have a master password", async () => { diff --git a/libs/auth/src/common/login-strategies/sso-login.strategy.ts b/libs/auth/src/common/login-strategies/sso-login.strategy.ts index d806f6d733e..33802765aca 100644 --- a/libs/auth/src/common/login-strategies/sso-login.strategy.ts +++ b/libs/auth/src/common/login-strategies/sso-login.strategy.ts @@ -157,22 +157,12 @@ export class SsoLoginStrategy extends LoginStrategy { // In order for us to set the master key from Key Connector, we need to have a Key Connector URL // and the user must not have a master password. return userHasKeyConnectorUrl && !userHasMasterPassword; - } else { - // In pre-TDE versions of the server, the userDecryptionOptions will not be present. - // In this case, we can determine if the user has a master password and has a Key Connector URL by - // just checking the keyConnectorUrl property. This is because the server short-circuits on the response - // and will not pass back the URL in the response if the user has a master password. - // TODO: remove compatibility check after 2023.10 release (https://bitwarden.atlassian.net/browse/PM-3537) - return tokenResponse.keyConnectorUrl != null; } } private getKeyConnectorUrl(tokenResponse: IdentityTokenResponse): string { - // TODO: remove tokenResponse.keyConnectorUrl reference after 2023.10 release (https://bitwarden.atlassian.net/browse/PM-3537) const userDecryptionOptions = tokenResponse?.userDecryptionOptions; - return ( - tokenResponse.keyConnectorUrl ?? userDecryptionOptions?.keyConnectorOption?.keyConnectorUrl - ); + return userDecryptionOptions?.keyConnectorOption?.keyConnectorUrl; } // TODO: future passkey login strategy will need to support setting user key (decrypting via TDE or admin approval request) diff --git a/libs/auth/src/common/models/domain/user-decryption-options.ts b/libs/auth/src/common/models/domain/user-decryption-options.ts index 7653a3b77ff..44d8bff4d2c 100644 --- a/libs/auth/src/common/models/domain/user-decryption-options.ts +++ b/libs/auth/src/common/models/domain/user-decryption-options.ts @@ -112,10 +112,11 @@ export class UserDecryptionOptions { * @throws If the response is nullish, this method will throw an error. User decryption options * are required for client initialization. */ - // TODO: Change response type to `UserDecryptionOptionsResponse` after 2023.10 release (https://bitwarden.atlassian.net/browse/PM-3537) - static fromResponse(response: IdentityTokenResponse): UserDecryptionOptions { + static fromIdentityTokenResponse(response: IdentityTokenResponse): UserDecryptionOptions { if (response == null) { - throw new Error("User Decryption Options are required for client initialization."); + throw new Error( + "User Decryption Options are required for client initialization. Response is nullish.", + ); } const decryptionOptions = new UserDecryptionOptions(); @@ -134,17 +135,9 @@ export class UserDecryptionOptions { responseOptions.keyConnectorOption, ); } else { - // If the response does not have userDecryptionOptions, this means it's on a pre-TDE server version and so - // we must base our decryption options on the presence of the keyConnectorUrl. - // Note that the presence of keyConnectorUrl implies that the user does not have a master password, as in pre-TDE - // server versions, a master password short-circuited the addition of the keyConnectorUrl to the response. - // TODO: remove this check after 2023.10 release (https://bitwarden.atlassian.net/browse/PM-3537) - const usingKeyConnector = response.keyConnectorUrl != null; - decryptionOptions.hasMasterPassword = !usingKeyConnector; - if (usingKeyConnector) { - decryptionOptions.keyConnectorOption = new KeyConnectorUserDecryptionOption(); - decryptionOptions.keyConnectorOption.keyConnectorUrl = response.keyConnectorUrl; - } + throw new Error( + "User Decryption Options are required for client initialization. userDecryptionOptions is missing in response.", + ); } return decryptionOptions; } diff --git a/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts b/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts index 831692c160f..20251e339a5 100644 --- a/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts +++ b/libs/auth/src/common/services/login-strategies/login-strategy.service.spec.ts @@ -10,6 +10,7 @@ import { TokenTwoFactorRequest } from "@bitwarden/common/auth/models/request/ide import { IdentityTokenResponse } from "@bitwarden/common/auth/models/response/identity-token.response"; import { IdentityTwoFactorResponse } from "@bitwarden/common/auth/models/response/identity-two-factor.response"; import { PreloginResponse } from "@bitwarden/common/auth/models/response/prelogin.response"; +import { UserDecryptionOptionsResponse } from "@bitwarden/common/auth/models/response/user-decryption-options/user-decryption-options.response"; import { TwoFactorService } from "@bitwarden/common/auth/two-factor"; import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions/account/billing-account-profile-state.service"; import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service"; @@ -496,6 +497,7 @@ describe("LoginStrategyService", () => { refresh_token: "REFRESH_TOKEN", scope: "api offline_access", token_type: "Bearer", + userDecryptionOptions: new UserDecryptionOptionsResponse({ HasMasterPassword: true }), }), ); apiService.postPrelogin.mockResolvedValue( @@ -563,6 +565,7 @@ describe("LoginStrategyService", () => { refresh_token: "REFRESH_TOKEN", scope: "api offline_access", token_type: "Bearer", + userDecryptionOptions: new UserDecryptionOptionsResponse({ HasMasterPassword: true }), }), ); @@ -692,6 +695,7 @@ describe("LoginStrategyService", () => { refresh_token: "REFRESH_TOKEN", scope: "api offline_access", token_type: "Bearer", + userDecryptionOptions: new UserDecryptionOptionsResponse({ HasMasterPassword: true }), }), ); diff --git a/libs/common/src/auth/models/response/identity-token.response.ts b/libs/common/src/auth/models/response/identity-token.response.ts index dab96f6cf8c..59e7eb98ec2 100644 --- a/libs/common/src/auth/models/response/identity-token.response.ts +++ b/libs/common/src/auth/models/response/identity-token.response.ts @@ -26,7 +26,6 @@ export class IdentityTokenResponse extends BaseResponse { forcePasswordReset: boolean; masterPasswordPolicy: MasterPasswordPolicyResponse; apiUseKeyConnector: boolean; - keyConnectorUrl: string; userDecryptionOptions?: UserDecryptionOptionsResponse; @@ -70,7 +69,7 @@ export class IdentityTokenResponse extends BaseResponse { : new Argon2KdfConfig(kdfIterations, kdfMemory, kdfParallelism); this.forcePasswordReset = this.getResponseProperty("ForcePasswordReset"); this.apiUseKeyConnector = this.getResponseProperty("ApiUseKeyConnector"); - this.keyConnectorUrl = this.getResponseProperty("KeyConnectorUrl"); + this.masterPasswordPolicy = new MasterPasswordPolicyResponse( this.getResponseProperty("MasterPasswordPolicy"), ); From 77fe04f8c7812b857c4e3b24277f1f297c5626b4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 3 Dec 2025 14:32:13 -0800 Subject: [PATCH 2/7] [deps] Vault: Update open to v11 (#17625) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/cli/package.json | 2 +- package-lock.json | 83 ++++++++++++++++++++++++++++++++++++------- package.json | 2 +- 3 files changed, 73 insertions(+), 14 deletions(-) diff --git a/apps/cli/package.json b/apps/cli/package.json index d041f818c29..7e7ae62658c 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -83,7 +83,7 @@ "multer": "2.0.2", "node-fetch": "2.6.12", "node-forge": "1.3.2", - "open": "10.1.2", + "open": "11.0.0", "papaparse": "5.5.3", "proper-lockfile": "4.1.2", "rxjs": "7.8.1", diff --git a/package-lock.json b/package-lock.json index ea662c62b6e..4dfc6c644fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -60,7 +60,7 @@ "node-fetch": "2.6.12", "node-forge": "1.3.2", "oidc-client-ts": "2.4.1", - "open": "10.1.2", + "open": "11.0.0", "papaparse": "5.5.3", "proper-lockfile": "4.1.2", "qrcode-parser": "2.1.3", @@ -219,7 +219,7 @@ "multer": "2.0.2", "node-fetch": "2.6.12", "node-forge": "1.3.2", - "open": "10.1.2", + "open": "11.0.0", "papaparse": "5.5.3", "proper-lockfile": "4.1.2", "rxjs": "7.8.1", @@ -19713,9 +19713,9 @@ } }, "node_modules/default-browser": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", - "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.4.0.tgz", + "integrity": "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==", "license": "MIT", "dependencies": { "bundle-name": "^4.1.0", @@ -24785,6 +24785,18 @@ "node": ">=0.10.0" } }, + "node_modules/is-in-ssh": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-in-ssh/-/is-in-ssh-1.0.0.tgz", + "integrity": "sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-inside-container": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", @@ -32477,18 +32489,36 @@ "integrity": "sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==" }, "node_modules/open": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/open/-/open-10.1.2.tgz", - "integrity": "sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz", + "integrity": "sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==", "license": "MIT", "dependencies": { - "default-browser": "^5.2.1", + "default-browser": "^5.4.0", "define-lazy-prop": "^3.0.0", + "is-in-ssh": "^1.0.0", "is-inside-container": "^1.0.0", - "is-wsl": "^3.1.0" + "powershell-utils": "^0.1.0", + "wsl-utils": "^0.3.0" }, "engines": { - "node": ">=18" + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open/node_modules/wsl-utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.3.0.tgz", + "integrity": "sha512-3sFIGLiaDP7rTO4xh3g+b3AzhYDIUGGywE/WsmqzJWDxus5aJXVnPTNC/6L+r2WzrwXqVOdD262OaO+cEyPMSQ==", + "license": "MIT", + "dependencies": { + "is-wsl": "^3.1.0", + "powershell-utils": "^0.1.0" + }, + "engines": { + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -34417,6 +34447,18 @@ "node": "^12.20.0 || >=14" } }, + "node_modules/powershell-utils": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", + "integrity": "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/prebuild-install": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", @@ -41311,6 +41353,24 @@ "node": ">= 0.6" } }, + "node_modules/webpack-dev-server/node_modules/open": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", + "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/webpack-dev-server/node_modules/path-to-regexp": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", @@ -41895,7 +41955,6 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", - "dev": true, "license": "MIT", "dependencies": { "is-wsl": "^3.1.0" diff --git a/package.json b/package.json index ab83b981b66..e3e4be193b7 100644 --- a/package.json +++ b/package.json @@ -194,7 +194,7 @@ "node-fetch": "2.6.12", "node-forge": "1.3.2", "oidc-client-ts": "2.4.1", - "open": "10.1.2", + "open": "11.0.0", "papaparse": "5.5.3", "proper-lockfile": "4.1.2", "qrcode-parser": "2.1.3", From 433c0ced3277e8dcae7015d97ed351118b21ce8c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 3 Dec 2025 15:27:49 -0800 Subject: [PATCH 3/7] [deps] Vault: Update koa to v3 (#17565) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- apps/cli/package.json | 2 +- package-lock.json | 204 ++++++++---------------------------------- package.json | 4 +- 3 files changed, 41 insertions(+), 169 deletions(-) diff --git a/apps/cli/package.json b/apps/cli/package.json index 7e7ae62658c..0ae4bf9ce30 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -75,7 +75,7 @@ "inquirer": "8.2.6", "jsdom": "26.1.0", "jszip": "3.10.1", - "koa": "2.16.3", + "koa": "3.1.1", "koa-bodyparser": "4.4.1", "koa-json": "2.0.2", "lowdb": "1.0.0", diff --git a/package-lock.json b/package-lock.json index 4dfc6c644fc..381515007a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -49,7 +49,7 @@ "inquirer": "8.2.6", "jsdom": "26.1.0", "jszip": "3.10.1", - "koa": "2.16.3", + "koa": "3.1.1", "koa-bodyparser": "4.4.1", "koa-json": "2.0.2", "lit": "3.3.1", @@ -102,7 +102,7 @@ "@types/inquirer": "8.2.10", "@types/jest": "29.5.14", "@types/jsdom": "21.1.7", - "@types/koa": "3.0.0", + "@types/koa": "3.0.1", "@types/koa__multer": "2.0.7", "@types/koa__router": "12.0.4", "@types/koa-bodyparser": "4.3.7", @@ -211,7 +211,7 @@ "inquirer": "8.2.6", "jsdom": "26.1.0", "jszip": "3.10.1", - "koa": "2.16.3", + "koa": "3.1.1", "koa-bodyparser": "4.4.1", "koa-json": "2.0.2", "lowdb": "1.0.0", @@ -13887,9 +13887,9 @@ } }, "node_modules/@types/koa": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/koa/-/koa-3.0.0.tgz", - "integrity": "sha512-MOcVYdVYmkSutVHZZPh8j3+dAjLyR5Tl59CN0eKgpkE1h/LBSmPAsQQuWs+bKu7WtGNn+hKfJH9Gzml+PulmDg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/koa/-/koa-3.0.1.tgz", + "integrity": "sha512-VkB6WJUQSe0zBpR+Q7/YIUESGp5wPHcaXr0xueU5W0EOUWtlSbblsl+Kl31lyRQ63nIILh0e/7gXjQ09JXJIHw==", "dev": true, "license": "MIT", "dependencies": { @@ -17675,40 +17675,6 @@ "dev": true, "license": "ISC" }, - "node_modules/cache-content-type": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", - "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==", - "license": "MIT", - "dependencies": { - "mime-types": "^2.1.18", - "ylru": "^1.2.0" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/cache-content-type/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cache-content-type/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/cacheable-lookup": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", @@ -24759,6 +24725,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -24944,6 +24911,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -27483,37 +27451,32 @@ } }, "node_modules/koa": { - "version": "2.16.3", - "resolved": "https://registry.npmjs.org/koa/-/koa-2.16.3.tgz", - "integrity": "sha512-zPPuIt+ku1iCpFBRwseMcPYQ1cJL8l60rSmKeOuGfOXyE6YnTBmf2aEFNL2HQGrD0cPcLO/t+v9RTgC+fwEh/g==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/koa/-/koa-3.1.1.tgz", + "integrity": "sha512-KDDuvpfqSK0ZKEO2gCPedNjl5wYpfj+HNiuVRlbhd1A88S3M0ySkdf2V/EJ4NWt5dwh5PXCdcenrKK2IQJAxsg==", "license": "MIT", "dependencies": { - "accepts": "^1.3.5", - "cache-content-type": "^1.0.0", - "content-disposition": "~0.5.2", - "content-type": "^1.0.4", - "cookies": "~0.9.0", - "debug": "^4.3.2", + "accepts": "^1.3.8", + "content-disposition": "~0.5.4", + "content-type": "^1.0.5", + "cookies": "~0.9.1", "delegates": "^1.0.0", - "depd": "^2.0.0", - "destroy": "^1.0.4", - "encodeurl": "^1.0.2", + "destroy": "^1.2.0", + "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "fresh": "~0.5.2", - "http-assert": "^1.3.0", - "http-errors": "^1.6.3", - "is-generator-function": "^1.0.7", + "http-assert": "^1.5.0", + "http-errors": "^2.0.0", "koa-compose": "^4.1.0", - "koa-convert": "^2.0.0", - "on-finished": "^2.3.0", - "only": "~0.0.2", - "parseurl": "^1.3.2", - "statuses": "^1.5.0", - "type-is": "^1.6.16", + "mime-types": "^3.0.1", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1", + "type-is": "^2.0.1", "vary": "^1.1.2" }, "engines": { - "node": "^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4" + "node": ">= 18" } }, "node_modules/koa-bodyparser": { @@ -27579,19 +27542,6 @@ "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==", "license": "MIT" }, - "node_modules/koa-convert": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-2.0.0.tgz", - "integrity": "sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==", - "license": "MIT", - "dependencies": { - "co": "^4.6.0", - "koa-compose": "^4.1.0" - }, - "engines": { - "node": ">= 10" - } - }, "node_modules/koa-is-json": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/koa-is-json/-/koa-is-json-1.0.0.tgz", @@ -27621,6 +27571,18 @@ "node": ">= 0.6" } }, + "node_modules/koa/node_modules/accepts/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/koa/node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -27633,15 +27595,6 @@ "node": ">= 0.6" } }, - "node_modules/koa/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/koa/node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -27651,40 +27604,6 @@ "node": ">= 0.6" } }, - "node_modules/koa/node_modules/http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", - "license": "MIT", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/koa/node_modules/http-errors/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/koa/node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/koa/node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -27694,18 +27613,6 @@ "node": ">= 0.6" } }, - "node_modules/koa/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/koa/node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -27715,28 +27622,6 @@ "node": ">= 0.6" } }, - "node_modules/koa/node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/koa/node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/launch-editor": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.10.0.tgz", @@ -32483,11 +32368,6 @@ "node": ">=6" } }, - "node_modules/only": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", - "integrity": "sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==" - }, "node_modules/open": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/open/-/open-11.0.0.tgz", @@ -35987,6 +35867,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -42072,15 +41953,6 @@ "fd-slicer": "~1.1.0" } }, - "node_modules/ylru": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.4.0.tgz", - "integrity": "sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA==", - "license": "MIT", - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index e3e4be193b7..209bbc3beb1 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "@types/inquirer": "8.2.10", "@types/jest": "29.5.14", "@types/jsdom": "21.1.7", - "@types/koa": "3.0.0", + "@types/koa": "3.0.1", "@types/koa__multer": "2.0.7", "@types/koa__router": "12.0.4", "@types/koa-bodyparser": "4.3.7", @@ -183,7 +183,7 @@ "inquirer": "8.2.6", "jsdom": "26.1.0", "jszip": "3.10.1", - "koa": "2.16.3", + "koa": "3.1.1", "koa-bodyparser": "4.4.1", "koa-json": "2.0.2", "lit": "3.3.1", From 2ef84ca460e92db9cdd12f6079a215976d0b5ab1 Mon Sep 17 00:00:00 2001 From: Bernd Schoolmann Date: Thu, 4 Dec 2025 09:14:09 +0100 Subject: [PATCH 4/7] [PM-27230] Resolve sdk breaking changes; update account init and save signed public key (#17488) * Update account init and save signed public key * Update sdk * Fix build * Fix types * Fix test * Fix test --- .../user-key-rotation.service.spec.ts | 4 ++ .../key-rotation/user-key-rotation.service.ts | 27 +++++++--- libs/common/src/key-management/types.ts | 8 ++- .../services/key-state/user-key.state.ts | 11 +++- .../services/sdk/default-sdk.service.spec.ts | 1 + .../services/sdk/default-sdk.service.ts | 52 ++++++++++++++----- .../src/platform/sync/default-sync.service.ts | 4 ++ .../src/abstractions/key.service.ts | 6 ++- libs/key-management/src/key.service.ts | 11 +++- package-lock.json | 16 +++--- package.json | 4 +- 11 files changed, 110 insertions(+), 34 deletions(-) diff --git a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts index b790fb8409a..f4b50b4a772 100644 --- a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts +++ b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.spec.ts @@ -1096,6 +1096,9 @@ describe("KeyRotationService", () => { mockKeyService.userSigningKey$.mockReturnValue( new BehaviorSubject(TEST_VECTOR_SIGNING_KEY_V2 as WrappedSigningKey), ); + mockKeyService.userSignedPublicKey$.mockReturnValue( + new BehaviorSubject(TEST_VECTOR_SIGNED_PUBLIC_KEY_V2 as SignedPublicKey), + ); mockSecurityStateService.accountSecurityState$.mockReturnValue( new BehaviorSubject(TEST_VECTOR_SECURITY_STATE_V2 as SignedSecurityState), ); @@ -1140,6 +1143,7 @@ describe("KeyRotationService", () => { publicKeyEncryptionKeyPair: { wrappedPrivateKey: TEST_VECTOR_PRIVATE_KEY_V2, publicKey: Utils.fromB64ToArray(TEST_VECTOR_PUBLIC_KEY_V2) as UnsignedPublicKey, + signedPublicKey: TEST_VECTOR_SIGNED_PUBLIC_KEY_V2 as SignedPublicKey, }, signingKey: TEST_VECTOR_SIGNING_KEY_V2 as WrappedSigningKey, securityState: TEST_VECTOR_SECURITY_STATE_V2 as SignedSecurityState, diff --git a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts index 168dbe7442e..b9bd23b12de 100644 --- a/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts +++ b/apps/web/src/app/key-management/key-rotation/user-key-rotation.service.ts @@ -10,6 +10,7 @@ import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-st import { DeviceTrustServiceAbstraction } from "@bitwarden/common/key-management/device-trust/abstractions/device-trust.service.abstraction"; import { SecurityStateService } from "@bitwarden/common/key-management/security-state/abstractions/security-state.service"; import { + SignedPublicKey, SignedSecurityState, UnsignedPublicKey, WrappedPrivateKey, @@ -308,9 +309,11 @@ export class UserKeyRotationService { userId: asUuid(userId), kdfParams: kdfConfig.toSdkConfig(), email: email, - privateKey: cryptographicStateParameters.publicKeyEncryptionKeyPair.wrappedPrivateKey, - signingKey: undefined, - securityState: undefined, + accountCryptographicState: { + V1: { + private_key: cryptographicStateParameters.publicKeyEncryptionKeyPair.wrappedPrivateKey, + }, + }, method: { decryptedKey: { decrypted_user_key: cryptographicStateParameters.userKey.toBase64() }, }, @@ -334,9 +337,15 @@ export class UserKeyRotationService { userId: asUuid(userId), kdfParams: kdfConfig.toSdkConfig(), email: email, - privateKey: cryptographicStateParameters.publicKeyEncryptionKeyPair.wrappedPrivateKey, - signingKey: cryptographicStateParameters.signingKey, - securityState: cryptographicStateParameters.securityState, + accountCryptographicState: { + V2: { + private_key: cryptographicStateParameters.publicKeyEncryptionKeyPair.wrappedPrivateKey, + signing_key: cryptographicStateParameters.signingKey, + security_state: cryptographicStateParameters.securityState, + signed_public_key: + cryptographicStateParameters.publicKeyEncryptionKeyPair.signedPublicKey, + }, + }, method: { decryptedKey: { decrypted_user_key: cryptographicStateParameters.userKey.toBase64() }, }, @@ -632,6 +641,10 @@ export class UserKeyRotationService { this.securityStateService.accountSecurityState$(user.id), "User security state", ); + const signedPublicKey = await this.firstValueFromOrThrow( + this.keyService.userSignedPublicKey$(user.id), + "User signed public key", + ); return { masterKeyKdfConfig, @@ -642,6 +655,7 @@ export class UserKeyRotationService { publicKeyEncryptionKeyPair: { wrappedPrivateKey: currentUserKeyWrappedPrivateKey, publicKey: publicKey, + signedPublicKey: signedPublicKey!, }, signingKey: signingKey!, securityState: securityState!, @@ -679,6 +693,7 @@ export type V2CryptographicStateParameters = { publicKeyEncryptionKeyPair: { wrappedPrivateKey: WrappedPrivateKey; publicKey: UnsignedPublicKey; + signedPublicKey: SignedPublicKey; }; signingKey: WrappedSigningKey; securityState: SignedSecurityState; diff --git a/libs/common/src/key-management/types.ts b/libs/common/src/key-management/types.ts index df64a3ed342..1c349e83a03 100644 --- a/libs/common/src/key-management/types.ts +++ b/libs/common/src/key-management/types.ts @@ -1,6 +1,10 @@ import { Opaque } from "type-fest"; -import { EncString, SignedSecurityState as SdkSignedSecurityState } from "@bitwarden/sdk-internal"; +import { + EncString, + SignedSecurityState as SdkSignedSecurityState, + SignedPublicKey as SdkSignedPublicKey, +} from "@bitwarden/sdk-internal"; /** * A private key, encrypted with a symmetric key. @@ -10,7 +14,7 @@ export type WrappedPrivateKey = Opaque; /** * A public key, signed with the accounts signature key. */ -export type SignedPublicKey = Opaque; +export type SignedPublicKey = Opaque; /** * A public key in base64 encoded SPKI-DER */ diff --git a/libs/common/src/platform/services/key-state/user-key.state.ts b/libs/common/src/platform/services/key-state/user-key.state.ts index 2416c211d6b..64577768c8d 100644 --- a/libs/common/src/platform/services/key-state/user-key.state.ts +++ b/libs/common/src/platform/services/key-state/user-key.state.ts @@ -1,5 +1,5 @@ import { EncryptedString } from "../../../key-management/crypto/models/enc-string"; -import { WrappedSigningKey } from "../../../key-management/types"; +import { SignedPublicKey, WrappedSigningKey } from "../../../key-management/types"; import { UserKey } from "../../../types/key"; import { SymmetricCryptoKey } from "../../models/domain/symmetric-crypto-key"; import { CRYPTO_DISK, CRYPTO_MEMORY, UserKeyDefinition } from "../../state"; @@ -35,3 +35,12 @@ export const USER_KEY_ENCRYPTED_SIGNING_KEY = new UserKeyDefinition( + CRYPTO_DISK, + "userSignedPublicKey", + { + deserializer: (obj) => obj, + clearOn: ["logout"], + }, +); diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts b/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts index dc945594079..1286ea7b7f9 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.spec.ts @@ -105,6 +105,7 @@ describe("DefaultSdkService", () => { .mockReturnValue(of("private-key" as EncryptedString)); keyService.encryptedOrgKeys$.calledWith(userId).mockReturnValue(of({})); keyService.userSigningKey$.calledWith(userId).mockReturnValue(of(null)); + keyService.userSignedPublicKey$.calledWith(userId).mockReturnValue(of(null)); securityStateService.accountSecurityState$.calledWith(userId).mockReturnValue(of(null)); }); diff --git a/libs/common/src/platform/services/sdk/default-sdk.service.ts b/libs/common/src/platform/services/sdk/default-sdk.service.ts index 6e7bcbb197d..5084f5f5f18 100644 --- a/libs/common/src/platform/services/sdk/default-sdk.service.ts +++ b/libs/common/src/platform/services/sdk/default-sdk.service.ts @@ -16,6 +16,7 @@ import { } from "rxjs"; import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service"; +import { UserKey } from "@bitwarden/common/types/key"; // This import has been flagged as unallowed for this class. It may be involved in a circular dependency loop. // eslint-disable-next-line no-restricted-imports import { KeyService, KdfConfigService, KdfConfig, KdfType } from "@bitwarden/key-management"; @@ -24,15 +25,14 @@ import { ClientSettings, TokenProvider, UnsignedSharedKey, + WrappedAccountCryptographicState, } from "@bitwarden/sdk-internal"; import { ApiService } from "../../../abstractions/api.service"; import { AccountInfo, AccountService } from "../../../auth/abstractions/account.service"; -import { EncryptedString, EncString } from "../../../key-management/crypto/models/enc-string"; +import { EncString } from "../../../key-management/crypto/models/enc-string"; import { SecurityStateService } from "../../../key-management/security-state/abstractions/security-state.service"; -import { SignedSecurityState, WrappedSigningKey } from "../../../key-management/types"; import { OrganizationId, UserId } from "../../../types/guid"; -import { UserKey } from "../../../types/key"; import { Environment, EnvironmentService } from "../../abstractions/environment.service"; import { PlatformUtilsService } from "../../abstractions/platform-utils.service"; import { SdkClientFactory } from "../../abstractions/sdk/sdk-client-factory"; @@ -174,6 +174,9 @@ export class DefaultSdkService implements SdkService { const securityState$ = this.securityStateService .accountSecurityState$(userId) .pipe(distinctUntilChanged(compareValues)); + const signedPublicKey$ = this.keyService + .userSignedPublicKey$(userId) + .pipe(distinctUntilChanged(compareValues)); const client$ = combineLatest([ this.environmentService.getEnvironment$(userId), @@ -184,11 +187,22 @@ export class DefaultSdkService implements SdkService { signingKey$, orgKeys$, securityState$, + signedPublicKey$, SdkLoadService.Ready, // Makes sure we wait (once) for the SDK to be loaded ]).pipe( // switchMap is required to allow the clean-up logic to be executed when `combineLatest` emits a new value. switchMap( - ([env, account, kdfParams, privateKey, userKey, signingKey, orgKeys, securityState]) => { + ([ + env, + account, + kdfParams, + privateKey, + userKey, + signingKey, + orgKeys, + securityState, + signedPublicKey, + ]) => { // Create our own observable to be able to implement clean-up logic return new Observable>((subscriber) => { const createAndInitializeClient = async () => { @@ -202,15 +216,31 @@ export class DefaultSdkService implements SdkService { settings, ); + let accountCryptographicState: WrappedAccountCryptographicState; + if (signingKey != null && securityState != null && signedPublicKey != null) { + accountCryptographicState = { + V2: { + private_key: privateKey, + signing_key: signingKey, + security_state: securityState, + signed_public_key: signedPublicKey, + }, + }; + } else { + accountCryptographicState = { + V1: { + private_key: privateKey, + }, + }; + } + await this.initializeClient( userId, client, account, kdfParams, - privateKey, userKey, - signingKey, - securityState, + accountCryptographicState, orgKeys, ); @@ -245,10 +275,8 @@ export class DefaultSdkService implements SdkService { client: PasswordManagerClient, account: AccountInfo, kdfParams: KdfConfig, - privateKey: EncryptedString, userKey: UserKey, - signingKey: WrappedSigningKey | null, - securityState: SignedSecurityState | null, + accountCryptographicState: WrappedAccountCryptographicState, orgKeys: Record, ) { await client.crypto().initialize_user_crypto({ @@ -265,9 +293,7 @@ export class DefaultSdkService implements SdkService { parallelism: kdfParams.parallelism, }, }, - privateKey, - signingKey: signingKey || undefined, - securityState: securityState || undefined, + accountCryptographicState: accountCryptographicState, }); // We initialize the org crypto even if the org_keys are diff --git a/libs/common/src/platform/sync/default-sync.service.ts b/libs/common/src/platform/sync/default-sync.service.ts index e599fbc1c48..910702bddd0 100644 --- a/libs/common/src/platform/sync/default-sync.service.ts +++ b/libs/common/src/platform/sync/default-sync.service.ts @@ -253,6 +253,10 @@ export class DefaultSyncService extends CoreSyncService { response.accountKeys.securityState.securityState, response.id, ); + await this.keyService.setSignedPublicKey( + response.accountKeys.publicKeyEncryptionKeyPair.signedPublicKey, + response.id, + ); } } else { await this.keyService.setPrivateKey(response.privateKey, response.id); diff --git a/libs/key-management/src/abstractions/key.service.ts b/libs/key-management/src/abstractions/key.service.ts index f86ca922c9e..feb4a38ac27 100644 --- a/libs/key-management/src/abstractions/key.service.ts +++ b/libs/key-management/src/abstractions/key.service.ts @@ -7,7 +7,7 @@ import { EncryptedString, EncString, } from "@bitwarden/common/key-management/crypto/models/enc-string"; -import { WrappedSigningKey } from "@bitwarden/common/key-management/types"; +import { SignedPublicKey, WrappedSigningKey } from "@bitwarden/common/key-management/types"; import { KeySuffixOptions, HashPurpose } from "@bitwarden/common/platform/enums"; import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key"; import { OrganizationId, ProviderId, UserId } from "@bitwarden/common/types/guid"; @@ -428,4 +428,8 @@ export abstract class KeyService { * @param userId The user id for the key */ abstract validateUserKey(key: UserKey, userId: UserId): Promise; + + abstract setSignedPublicKey(signedPublicKey: SignedPublicKey, userId: UserId): Promise; + + abstract userSignedPublicKey$(userId: UserId): Observable; } diff --git a/libs/key-management/src/key.service.ts b/libs/key-management/src/key.service.ts index f2c3c0eff7a..f0e5f6ee08e 100644 --- a/libs/key-management/src/key.service.ts +++ b/libs/key-management/src/key.service.ts @@ -28,7 +28,7 @@ import { EncryptedString, } from "@bitwarden/common/key-management/crypto/models/enc-string"; import { InternalMasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction"; -import { WrappedSigningKey } from "@bitwarden/common/key-management/types"; +import { SignedPublicKey, WrappedSigningKey } from "@bitwarden/common/key-management/types"; import { VaultTimeoutStringType } from "@bitwarden/common/key-management/vault-timeout"; import { VAULT_TIMEOUT } from "@bitwarden/common/key-management/vault-timeout/services/vault-timeout-settings.state"; import { LogService } from "@bitwarden/common/platform/abstractions/log.service"; @@ -46,6 +46,7 @@ import { USER_EVER_HAD_USER_KEY, USER_KEY, USER_KEY_ENCRYPTED_SIGNING_KEY, + USER_SIGNED_PUBLIC_KEY, } from "@bitwarden/common/platform/services/key-state/user-key.state"; import { StateProvider } from "@bitwarden/common/platform/state"; import { CsprngArray } from "@bitwarden/common/types/csprng"; @@ -1013,4 +1014,12 @@ export class DefaultKeyService implements KeyServiceAbstraction { }), ); } + + async setSignedPublicKey(signedPublicKey: SignedPublicKey, userId: UserId): Promise { + await this.stateProvider.setUserState(USER_SIGNED_PUBLIC_KEY, signedPublicKey, userId); + } + + userSignedPublicKey$(userId: UserId): Observable { + return this.stateProvider.getUserState$(USER_SIGNED_PUBLIC_KEY, userId); + } } diff --git a/package-lock.json b/package-lock.json index 381515007a2..88754d75ad0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,8 +23,8 @@ "@angular/platform-browser": "20.3.15", "@angular/platform-browser-dynamic": "20.3.15", "@angular/router": "20.3.15", - "@bitwarden/commercial-sdk-internal": "0.2.0-main.403", - "@bitwarden/sdk-internal": "0.2.0-main.403", + "@bitwarden/commercial-sdk-internal": "0.2.0-main.409", + "@bitwarden/sdk-internal": "0.2.0-main.409", "@electron/fuses": "1.8.0", "@emotion/css": "11.13.5", "@koa/multer": "4.0.0", @@ -4615,9 +4615,9 @@ "link": true }, "node_modules/@bitwarden/commercial-sdk-internal": { - "version": "0.2.0-main.403", - "resolved": "https://registry.npmjs.org/@bitwarden/commercial-sdk-internal/-/commercial-sdk-internal-0.2.0-main.403.tgz", - "integrity": "sha512-M2ZUu29oua7CaDTNK7mCwY7PhaIUbNYogAAvxLOmkJuyHNxxqvS9usjjlD2CkQVNBeTUFqvAQpaZQo9vgzEEFA==", + "version": "0.2.0-main.409", + "resolved": "https://registry.npmjs.org/@bitwarden/commercial-sdk-internal/-/commercial-sdk-internal-0.2.0-main.409.tgz", + "integrity": "sha512-86AVuOG5S9Te9ZMvozCV1FN8v98ROnUwr24nTeocqD/5OJIKoWWXk1t+g4YoVF+8AItpD6hFfO/uL05mG80jDA==", "license": "BITWARDEN SOFTWARE DEVELOPMENT KIT LICENSE AGREEMENT", "dependencies": { "type-fest": "^4.41.0" @@ -4720,9 +4720,9 @@ "link": true }, "node_modules/@bitwarden/sdk-internal": { - "version": "0.2.0-main.403", - "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.403.tgz", - "integrity": "sha512-ROEZdTbeKU68kDh9WYm9wKsLQD5jdTRclXLKl8x0aTj+Tx0nKyyXmLyUfOP+qh3EHIetij4jwPx2z3uS+7r8mg==", + "version": "0.2.0-main.409", + "resolved": "https://registry.npmjs.org/@bitwarden/sdk-internal/-/sdk-internal-0.2.0-main.409.tgz", + "integrity": "sha512-3e3hZenNos1xACJ2Bsq14RrzCnWdIilJuJhFwZYQBI6PQ+R38UGGQhGJDandKvQggfR8bDCY3re8x8n9G7MDiA==", "license": "GPL-3.0", "dependencies": { "type-fest": "^4.41.0" diff --git a/package.json b/package.json index 209bbc3beb1..b07d9f2a9e6 100644 --- a/package.json +++ b/package.json @@ -157,8 +157,8 @@ "@angular/platform-browser": "20.3.15", "@angular/platform-browser-dynamic": "20.3.15", "@angular/router": "20.3.15", - "@bitwarden/sdk-internal": "0.2.0-main.403", - "@bitwarden/commercial-sdk-internal": "0.2.0-main.403", + "@bitwarden/sdk-internal": "0.2.0-main.409", + "@bitwarden/commercial-sdk-internal": "0.2.0-main.409", "@electron/fuses": "1.8.0", "@emotion/css": "11.13.5", "@koa/multer": "4.0.0", From b9cb19a98e1ec0df2f4b57b96368ed7dae28554d Mon Sep 17 00:00:00 2001 From: adudek-bw Date: Thu, 4 Dec 2025 09:45:46 -0500 Subject: [PATCH 5/7] [PM-27081] Fix direct importers for linux (#17480) * Fix direct importers for linux --- .../chromium_importer/src/chromium/mod.rs | 32 ++++++++++--------- .../src/chromium/platform/linux.rs | 11 ++++--- .../src/chromium/platform/macos.rs | 14 ++++---- .../src/chromium/platform/windows/mod.rs | 12 +++---- 4 files changed, 37 insertions(+), 32 deletions(-) diff --git a/apps/desktop/desktop_native/chromium_importer/src/chromium/mod.rs b/apps/desktop/desktop_native/chromium_importer/src/chromium/mod.rs index e57b40b5778..7011a2cce63 100644 --- a/apps/desktop/desktop_native/chromium_importer/src/chromium/mod.rs +++ b/apps/desktop/desktop_native/chromium_importer/src/chromium/mod.rs @@ -61,8 +61,8 @@ impl InstalledBrowserRetriever for DefaultInstalledBrowserRetriever { let mut browsers = Vec::with_capacity(SUPPORTED_BROWSER_MAP.len()); for (browser, config) in SUPPORTED_BROWSER_MAP.iter() { - let data_dir = get_browser_data_dir(config)?; - if data_dir.exists() { + let data_dir = get_and_validate_data_dir(config); + if data_dir.is_ok() { browsers.push((*browser).to_string()); } } @@ -114,7 +114,7 @@ pub async fn import_logins( #[derive(Debug, Clone, Copy)] pub(crate) struct BrowserConfig { pub name: &'static str, - pub data_dir: &'static str, + pub data_dir: &'static [&'static str], } pub(crate) static SUPPORTED_BROWSER_MAP: LazyLock< @@ -126,11 +126,19 @@ pub(crate) static SUPPORTED_BROWSER_MAP: LazyLock< .collect::>() }); -fn get_browser_data_dir(config: &BrowserConfig) -> Result { - let dir = dirs::home_dir() - .ok_or_else(|| anyhow!("Home directory not found"))? - .join(config.data_dir); - Ok(dir) +fn get_and_validate_data_dir(config: &BrowserConfig) -> Result { + for data_dir in config.data_dir.iter() { + let dir = dirs::home_dir() + .ok_or_else(|| anyhow!("Home directory not found"))? + .join(data_dir); + if dir.exists() { + return Ok(dir); + } + } + Err(anyhow!( + "Browser user data directory '{:?}' not found", + config.data_dir + )) } // @@ -174,13 +182,7 @@ fn load_local_state_for_browser(browser_name: &String) -> Result<(PathBuf, Local .get(browser_name.as_str()) .ok_or_else(|| anyhow!("Unsupported browser: {}", browser_name))?; - let data_dir = get_browser_data_dir(config)?; - if !data_dir.exists() { - return Err(anyhow!( - "Browser user data directory '{}' not found", - data_dir.display() - )); - } + let data_dir = get_and_validate_data_dir(config)?; let local_state = load_local_state(&data_dir)?; diff --git a/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/linux.rs b/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/linux.rs index 14e38797640..f542e23129a 100644 --- a/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/linux.rs +++ b/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/linux.rs @@ -18,19 +18,22 @@ use crate::{ pub(crate) const SUPPORTED_BROWSERS: &[BrowserConfig] = &[ BrowserConfig { name: "Chrome", - data_dir: ".config/google-chrome", + data_dir: &[".config/google-chrome"], }, BrowserConfig { name: "Chromium", - data_dir: "snap/chromium/common/chromium", + data_dir: &["snap/chromium/common/chromium"], }, BrowserConfig { name: "Brave", - data_dir: "snap/brave/current/.config/BraveSoftware/Brave-Browser", + data_dir: &[ + "snap/brave/current/.config/BraveSoftware/Brave-Browser", + ".config/BraveSoftware/Brave-Browser", + ], }, BrowserConfig { name: "Opera", - data_dir: "snap/opera/current/.config/opera", + data_dir: &["snap/opera/current/.config/opera", ".config/opera"], }, ]; diff --git a/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/macos.rs b/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/macos.rs index 5d0b4f0c75c..6cd746d60b6 100644 --- a/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/macos.rs +++ b/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/macos.rs @@ -14,31 +14,31 @@ use crate::{ pub(crate) const SUPPORTED_BROWSERS: &[BrowserConfig] = &[ BrowserConfig { name: "Chrome", - data_dir: "Library/Application Support/Google/Chrome", + data_dir: &["Library/Application Support/Google/Chrome"], }, BrowserConfig { name: "Chromium", - data_dir: "Library/Application Support/Chromium", + data_dir: &["Library/Application Support/Chromium"], }, BrowserConfig { name: "Microsoft Edge", - data_dir: "Library/Application Support/Microsoft Edge", + data_dir: &["Library/Application Support/Microsoft Edge"], }, BrowserConfig { name: "Brave", - data_dir: "Library/Application Support/BraveSoftware/Brave-Browser", + data_dir: &["Library/Application Support/BraveSoftware/Brave-Browser"], }, BrowserConfig { name: "Arc", - data_dir: "Library/Application Support/Arc/User Data", + data_dir: &["Library/Application Support/Arc/User Data"], }, BrowserConfig { name: "Opera", - data_dir: "Library/Application Support/com.operasoftware.Opera", + data_dir: &["Library/Application Support/com.operasoftware.Opera"], }, BrowserConfig { name: "Vivaldi", - data_dir: "Library/Application Support/Vivaldi", + data_dir: &["Library/Application Support/Vivaldi"], }, ]; diff --git a/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/mod.rs b/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/mod.rs index 9cc89ed2161..524b5994873 100644 --- a/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/mod.rs +++ b/apps/desktop/desktop_native/chromium_importer/src/chromium/platform/windows/mod.rs @@ -25,27 +25,27 @@ pub use signature::*; pub(crate) const SUPPORTED_BROWSERS: &[BrowserConfig] = &[ BrowserConfig { name: "Brave", - data_dir: "AppData/Local/BraveSoftware/Brave-Browser/User Data", + data_dir: &["AppData/Local/BraveSoftware/Brave-Browser/User Data"], }, BrowserConfig { name: "Chrome", - data_dir: "AppData/Local/Google/Chrome/User Data", + data_dir: &["AppData/Local/Google/Chrome/User Data"], }, BrowserConfig { name: "Chromium", - data_dir: "AppData/Local/Chromium/User Data", + data_dir: &["AppData/Local/Chromium/User Data"], }, BrowserConfig { name: "Microsoft Edge", - data_dir: "AppData/Local/Microsoft/Edge/User Data", + data_dir: &["AppData/Local/Microsoft/Edge/User Data"], }, BrowserConfig { name: "Opera", - data_dir: "AppData/Roaming/Opera Software/Opera Stable", + data_dir: &["AppData/Roaming/Opera Software/Opera Stable"], }, BrowserConfig { name: "Vivaldi", - data_dir: "AppData/Local/Vivaldi/User Data", + data_dir: &["AppData/Local/Vivaldi/User Data"], }, ]; From 5386b58f2329eaed2acb9178560eeca9a265bb16 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Thu, 4 Dec 2025 16:06:13 +0100 Subject: [PATCH 6/7] Revert "Desktop Native compile debug builds with debug log level (#17357)" (#17815) This reverts commit a2abbd09bf40c06268ef38803b4e7148684607b3. --- apps/desktop/desktop_native/napi/package.json | 2 +- apps/desktop/desktop_native/napi/scripts/build.js | 14 -------------- apps/desktop/desktop_native/napi/src/lib.rs | 14 +++----------- 3 files changed, 4 insertions(+), 26 deletions(-) delete mode 100644 apps/desktop/desktop_native/napi/scripts/build.js diff --git a/apps/desktop/desktop_native/napi/package.json b/apps/desktop/desktop_native/napi/package.json index ca17377c9f2..d557ccfd259 100644 --- a/apps/desktop/desktop_native/napi/package.json +++ b/apps/desktop/desktop_native/napi/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "description": "", "scripts": { - "build": "node scripts/build.js", + "build": "napi build --platform --js false", "test": "cargo test" }, "author": "", diff --git a/apps/desktop/desktop_native/napi/scripts/build.js b/apps/desktop/desktop_native/napi/scripts/build.js deleted file mode 100644 index 7b3dccf81e4..00000000000 --- a/apps/desktop/desktop_native/napi/scripts/build.js +++ /dev/null @@ -1,14 +0,0 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ -const { execSync } = require('child_process'); - -const args = process.argv.slice(2); -const isRelease = args.includes('--release'); - -if (isRelease) { - console.log('Building release mode.'); -} else { - console.log('Building debug mode.'); - process.env.RUST_LOG = 'debug'; -} - -execSync(`napi build --platform --js false ${isRelease ? '--release' : ''}`, { stdio: 'inherit', env: process.env }); diff --git a/apps/desktop/desktop_native/napi/src/lib.rs b/apps/desktop/desktop_native/napi/src/lib.rs index c34e7574f68..b5dcb277a75 100644 --- a/apps/desktop/desktop_native/napi/src/lib.rs +++ b/apps/desktop/desktop_native/napi/src/lib.rs @@ -961,7 +961,7 @@ pub mod logging { }; use tracing::Level; use tracing_subscriber::{ - filter::EnvFilter, + filter::{EnvFilter, LevelFilter}, fmt::format::{DefaultVisitor, Writer}, layer::SubscriberExt, util::SubscriberInitExt, @@ -1049,17 +1049,9 @@ pub mod logging { pub fn init_napi_log(js_log_fn: ThreadsafeFunction<(LogLevel, String), CalleeHandled>) { let _ = JS_LOGGER.0.set(js_log_fn); - // the log level hierarchy is determined by: - // - if RUST_LOG is detected at runtime - // - if RUST_LOG is provided at compile time - // - default to INFO let filter = EnvFilter::builder() - .with_default_directive( - option_env!("RUST_LOG") - .unwrap_or("info") - .parse() - .expect("should provide valid log level at compile time."), - ) + // set the default log level to INFO. + .with_default_directive(LevelFilter::INFO.into()) // parse directives from the RUST_LOG environment variable, // overriding the default directive for matching targets. .from_env_lossy(); From 4155e26c28299beb112cbcce9313a614c62589f6 Mon Sep 17 00:00:00 2001 From: Vicki League Date: Thu, 4 Dec 2025 10:44:04 -0500 Subject: [PATCH 7/7] [PM-18839] Use mono font for color password component (#17785) --- libs/components/src/color-password/color-password.component.ts | 2 +- libs/components/src/color-password/color-password.stories.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/components/src/color-password/color-password.component.ts b/libs/components/src/color-password/color-password.component.ts index bd7f3beb403..eaaefd29f1d 100644 --- a/libs/components/src/color-password/color-password.component.ts +++ b/libs/components/src/color-password/color-password.component.ts @@ -14,7 +14,7 @@ type CharacterType = "letter" | "emoji" | "special" | "number"; @Component({ selector: "bit-color-password", template: `@for (character of passwordCharArray(); track $index; let i = $index) { - + {{ character }} @if (showCount()) { {{ i + 1 }} diff --git a/libs/components/src/color-password/color-password.stories.ts b/libs/components/src/color-password/color-password.stories.ts index 65b6a3c0f18..2ed5cdc4b8d 100644 --- a/libs/components/src/color-password/color-password.stories.ts +++ b/libs/components/src/color-password/color-password.stories.ts @@ -4,7 +4,7 @@ import { formatArgsForCodeSnippet } from "../../../../.storybook/format-args-for import { ColorPasswordComponent } from "./color-password.component"; -const examplePassword = "Wq$Jk😀7jlI DX#rS5Sdi!z "; +const examplePassword = "Wq$Jk😀7jlI DX#rS5Sdi!z0O "; export default { title: "Component Library/Color Password",