1
0
mirror of https://github.com/bitwarden/browser.git synced 2025-01-06 18:57:56 +01:00

Merge branch 'tde-key-model-migration' into feature/PM-1049-TDE-flow-3-login-decryption-options

This commit is contained in:
Jacob Fink 2023-06-23 13:54:15 -04:00
commit c942bc08ca
No known key found for this signature in database
GPG Key ID: C2F7ACF05859D008
116 changed files with 2690 additions and 1288 deletions

View File

@ -23,7 +23,7 @@ jobs:
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375
with:
keyvault: "bitwarden-ci"
secrets: "brew-bump-workflow-pat"

View File

@ -23,7 +23,7 @@ jobs:
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375
with:
keyvault: "bitwarden-ci"
secrets: "brew-bump-workflow-pat"

View File

@ -354,7 +354,7 @@ jobs:
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375
with:
keyvault: "bitwarden-ci"
secrets: "crowdin-api-token"
@ -416,7 +416,7 @@ jobs:
- name: Retrieve secrets
id: retrieve-secrets
if: failure()
uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375
with:
keyvault: "bitwarden-ci"
secrets: "devops-alerts-slack-webhook-url"

View File

@ -404,7 +404,7 @@ jobs:
- name: Retrieve secrets
id: retrieve-secrets
if: failure()
uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375
with:
keyvault: "bitwarden-ci"
secrets: "devops-alerts-slack-webhook-url"

View File

@ -277,7 +277,7 @@ jobs:
node-gyp install $(node -v)
- name: Install AST
uses: bitwarden/gh-actions/install-ast@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/install-ast@37ffa14164a7308bc273829edfe75c97cd562375
- name: Set up environmentF
run: choco install checksum --no-progress
@ -302,7 +302,7 @@ jobs:
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375
with:
keyvault: "bitwarden-ci"
secrets: "code-signing-vault-url,
@ -1190,7 +1190,7 @@ jobs:
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375
with:
keyvault: "bitwarden-ci"
secrets: "crowdin-api-token"
@ -1269,7 +1269,7 @@ jobs:
- name: Retrieve secrets
id: retrieve-secrets
if: failure()
uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375
with:
keyvault: "bitwarden-ci"
secrets: "devops-alerts-slack-webhook-url"

View File

@ -235,7 +235,7 @@ jobs:
- name: Retrieve github PAT secrets
id: retrieve-secret-pat
uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375
with:
keyvault: "bitwarden-ci"
secrets: "github-pat-bitwarden-devops-bot-repo-scope"
@ -243,7 +243,7 @@ jobs:
- name: Setup DCT
if: ${{ env.is_publish_branch == 'true' }}
id: setup-dct
uses: bitwarden/gh-actions/setup-docker-trust@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/setup-docker-trust@37ffa14164a7308bc273829edfe75c97cd562375
with:
azure-creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
azure-keyvault-name: "bitwarden-ci"
@ -291,7 +291,7 @@ jobs:
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375
with:
keyvault: "bitwarden-ci"
secrets: "crowdin-api-token"
@ -352,7 +352,7 @@ jobs:
- name: Retrieve secrets
id: retrieve-secrets
if: failure()
uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375
with:
keyvault: "bitwarden-ci"
secrets: "devops-alerts-slack-webhook-url"

View File

@ -32,13 +32,13 @@ jobs:
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375
with:
keyvault: "bitwarden-ci"
secrets: "crowdin-api-token, github-gpg-private-key, github-gpg-private-key-passphrase"
- name: Download translations
uses: bitwarden/gh-actions/crowdin@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/crowdin@37ffa14164a7308bc273829edfe75c97cd562375
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}

View File

@ -1,8 +1,14 @@
---
name: Deploy Web - EU Prod
name: Deploy Web to EU-PRD Cloud
on:
workflow_dispatch:
inputs:
tag:
description: "Branch name to deploy (examples: 'master', 'feature/sm')"
required: true
type: string
default: master
jobs:
azure-deploy:
@ -18,18 +24,18 @@ jobs:
- name: Retrieve Storage Account connection string
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@c86ced0dc8c9daeecf057a6333e6f318db9c5a2b
uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375
with:
keyvault: webvault-westeurope-prod
secrets: "sa-bitwarden-web-vault-dev-key-temp"
- name: Download latest cloud asset
uses: bitwarden/gh-actions/download-artifacts@850faad0cf6c02a8c0dc46eddde2363fbd6c373a
uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375
with:
workflow: build-web.yml
path: apps/web
workflow_conclusion: success
branch: ${{ github.ref_name }}
branch: ${{ github.event.inputs.tag }}
artifacts: ${{ env._WEB_ARTIFACT }}
- name: Unzip build asset

View File

@ -64,7 +64,7 @@ jobs:
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
- name: Download latest cloud asset
uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375
with:
workflow: build-web.yml
path: apps/web

View File

@ -41,7 +41,7 @@ jobs:
- name: Check Release Version
id: version
uses: bitwarden/gh-actions/release-version-check@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/release-version-check@37ffa14164a7308bc273829edfe75c97cd562375
with:
release-type: ${{ github.event.inputs.release_type }}
project-type: ts
@ -103,7 +103,7 @@ jobs:
- name: Download latest Release build artifacts
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375
with:
workflow: build-browser.yml
workflow_conclusion: success
@ -116,7 +116,7 @@ jobs:
- name: Dry Run - Download latest master build artifacts
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375
with:
workflow: build-browser.yml
workflow_conclusion: success

View File

@ -57,7 +57,7 @@ jobs:
- name: Check Release Version
id: version
uses: bitwarden/gh-actions/release-version-check@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/release-version-check@37ffa14164a7308bc273829edfe75c97cd562375
with:
release-type: ${{ github.event.inputs.release_type }}
project-type: ts
@ -78,7 +78,7 @@ jobs:
- name: Download all Release artifacts
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375
with:
workflow: build-cli.yml
path: apps/cli
@ -87,7 +87,7 @@ jobs:
- name: Dry Run - Download all artifacts
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375
with:
workflow: build-cli.yml
path: apps/cli
@ -150,7 +150,7 @@ jobs:
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375
with:
keyvault: "bitwarden-ci"
secrets: "snapcraft-store-token"
@ -162,7 +162,7 @@ jobs:
- name: Download artifacts
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375
with:
workflow: build-cli.yml
path: apps/cli
@ -172,7 +172,7 @@ jobs:
- name: Dry Run - Download artifacts
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375
with:
workflow: build-cli.yml
path: apps/cli
@ -204,7 +204,7 @@ jobs:
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375
with:
keyvault: "bitwarden-ci"
secrets: "cli-choco-api-key"
@ -220,7 +220,7 @@ jobs:
- name: Download artifacts
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375
with:
workflow: build-cli.yml
path: apps/cli/dist
@ -230,7 +230,7 @@ jobs:
- name: Dry Run - Download artifacts
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375
with:
workflow: build-cli.yml
path: apps/cli/dist
@ -263,14 +263,14 @@ jobs:
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375
with:
keyvault: "bitwarden-ci"
secrets: "npm-api-key"
- name: Download artifacts
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375
with:
workflow: build-cli.yml
path: apps/cli/build
@ -280,7 +280,7 @@ jobs:
- name: Dry Run - Download artifacts
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375
with:
workflow: build-cli.yml
path: apps/cli/build

View File

@ -47,7 +47,7 @@ jobs:
- name: Check Release Version
id: version
uses: bitwarden/gh-actions/release-version-check@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/release-version-check@37ffa14164a7308bc273829edfe75c97cd562375
with:
release-type: 'Initial Release'
project-type: ts
@ -231,7 +231,7 @@ jobs:
node-gyp install $(node -v)
- name: Install AST
uses: bitwarden/gh-actions/install-ast@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/install-ast@37ffa14164a7308bc273829edfe75c97cd562375
- name: Set up environment
run: choco install checksum --no-progress
@ -249,7 +249,7 @@ jobs:
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375
with:
keyvault: "bitwarden-ci"
secrets: "code-signing-vault-url,
@ -932,7 +932,7 @@ jobs:
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375
with:
keyvault: "bitwarden-ci"
secrets: "aws-electron-access-id,

View File

@ -67,7 +67,7 @@ jobs:
- name: Check Release Version
id: version
uses: bitwarden/gh-actions/release-version-check@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/release-version-check@37ffa14164a7308bc273829edfe75c97cd562375
with:
release-type: ${{ github.event.inputs.release_type }}
project-type: ts
@ -110,7 +110,7 @@ jobs:
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375
with:
keyvault: "bitwarden-ci"
secrets: "aws-electron-access-id,
@ -123,7 +123,7 @@ jobs:
- name: Download all artifacts
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375
with:
workflow: build-desktop.yml
workflow_conclusion: success
@ -132,7 +132,7 @@ jobs:
- name: Dry Run - Download all artifacts
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375
with:
workflow: build-desktop.yml
workflow_conclusion: success
@ -185,7 +185,7 @@ jobs:
--endpoint-url https://${CF_ACCOUNT}.r2.cloudflarestorage.com
- name: Get checksum files
uses: bitwarden/gh-actions/get-checksum@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/get-checksum@37ffa14164a7308bc273829edfe75c97cd562375
with:
packages_dir: "apps/desktop/artifacts"
file_path: "apps/desktop/artifacts/sha256-checksums.txt"
@ -263,7 +263,7 @@ jobs:
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375
with:
keyvault: "bitwarden-ci"
secrets: "snapcraft-store-token"
@ -279,7 +279,7 @@ jobs:
- name: Download Snap artifact
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375
with:
workflow: build-desktop.yml
workflow_conclusion: success
@ -289,7 +289,7 @@ jobs:
- name: Dry Run - Download Snap artifact
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375
with:
workflow: build-desktop.yml
workflow_conclusion: success
@ -329,7 +329,7 @@ jobs:
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375
with:
keyvault: "bitwarden-ci"
secrets: "cli-choco-api-key"
@ -347,7 +347,7 @@ jobs:
- name: Download choco artifact
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375
with:
workflow: build-desktop.yml
workflow_conclusion: success
@ -357,7 +357,7 @@ jobs:
- name: Dry Run - Download choco artifact
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375
with:
workflow: build-desktop.yml
workflow_conclusion: success

View File

@ -23,7 +23,7 @@ jobs:
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2
- name: Download latest cloud asset
uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375
with:
workflow: build-web.yml
path: apps/web

View File

@ -38,7 +38,7 @@ jobs:
- name: Check Release Version
id: version
uses: bitwarden/gh-actions/release-version-check@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/release-version-check@37ffa14164a7308bc273829edfe75c97cd562375
with:
release-type: ${{ github.event.inputs.release_type }}
project-type: ts
@ -70,7 +70,7 @@ jobs:
########## DockerHub ##########
- name: Setup DCT
id: setup-dct
uses: bitwarden/gh-actions/setup-docker-trust@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/setup-docker-trust@37ffa14164a7308bc273829edfe75c97cd562375
with:
azure-creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
azure-keyvault-name: "bitwarden-ci"
@ -156,7 +156,7 @@ jobs:
- name: Retrieve bot secrets
id: retrieve-bot-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375
with:
keyvault: bitwarden-ci
secrets: "github-pat-bitwarden-devops-bot-repo-scope"
@ -170,7 +170,7 @@ jobs:
- name: Download latest cloud asset
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375
with:
workflow: build-web.yml
path: assets
@ -180,7 +180,7 @@ jobs:
- name: Dry Run - Download latest cloud asset
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375
with:
workflow: build-web.yml
path: assets
@ -253,7 +253,7 @@ jobs:
- name: Download latest build artifacts
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375
with:
workflow: build-web.yml
path: apps/web/artifacts
@ -264,7 +264,7 @@ jobs:
- name: Dry Run - Download latest build artifacts
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
uses: bitwarden/gh-actions/download-artifacts@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/download-artifacts@37ffa14164a7308bc273829edfe75c97cd562375
with:
workflow: build-web.yml
path: apps/web/artifacts

View File

@ -26,7 +26,7 @@ jobs:
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375
with:
keyvault: "bitwarden-ci"
secrets: "aws-electron-access-id,

View File

@ -49,7 +49,7 @@ jobs:
- name: Retrieve secrets
id: retrieve-secrets
uses: bitwarden/gh-actions/get-keyvault-secrets@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/get-keyvault-secrets@37ffa14164a7308bc273829edfe75c97cd562375
with:
keyvault: "bitwarden-ci"
secrets: "github-gpg-private-key, github-gpg-private-key-passphrase"
@ -86,14 +86,14 @@ jobs:
- name: Bump Browser Version - Manifest
if: ${{ github.event.inputs.client == 'Browser' || github.event.inputs.client == 'All' }}
uses: bitwarden/gh-actions/version-bump@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/version-bump@37ffa14164a7308bc273829edfe75c97cd562375
with:
version: ${{ github.event.inputs.version_number }}
file_path: "apps/browser/src/manifest.json"
- name: Bump Browser Version - Manifest v3
if: ${{ github.event.inputs.client == 'Browser' || github.event.inputs.client == 'All' }}
uses: bitwarden/gh-actions/version-bump@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/version-bump@37ffa14164a7308bc273829edfe75c97cd562375
with:
version: ${{ github.event.inputs.version_number }}
file_path: "apps/browser/src/manifest.v3.json"

View File

@ -8,4 +8,4 @@ on:
jobs:
call-workflow:
uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@72594be690a4e7bfa87b1402b2aedc75acdbff12
uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@37ffa14164a7308bc273829edfe75c97cd562375

View File

@ -1599,6 +1599,12 @@
"biometricsNotSupportedDesc": {
"message": "Browser biometrics is not supported on this device."
},
"biometricsFailedTitle": {
"message": "Biometrics failed"
},
"biometricsFailedDesc": {
"message": "Biometrics cannot be completed, consider using a master password or logging out. If this persists, please contact Bitwarden support."
},
"nativeMessaginPermissionErrorTitle": {
"message": "Permission not provided"
},

View File

@ -10,7 +10,11 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import {
MasterKey,
SymmetricCryptoKey,
UserKey,
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { BrowserApi } from "../platform/browser/browser-api";
@ -42,6 +46,7 @@ type ReceiveMessage = {
// Unlock key
keyB64?: string;
userKeyB64?: string;
};
type ReceiveMessageOuter = {
@ -320,16 +325,54 @@ export class NativeMessagingBackground {
}
if (message.response === "unlocked") {
await this.cryptoService.setKey(
new SymmetricCryptoKey(Utils.fromB64ToArray(message.keyB64).buffer)
);
try {
if (message.userKeyB64) {
const userKey = new SymmetricCryptoKey(
Utils.fromB64ToArray(message.userKeyB64).buffer
) as UserKey;
await this.cryptoService.setUserKey(userKey);
} else if (message.keyB64) {
// backwards compatibility
let encUserKey = await this.stateService.getEncryptedCryptoSymmetricKey();
encUserKey ||= await this.stateService.getUserKeyMasterKey();
if (!encUserKey) {
throw new Error("No encrypted user key found");
}
const masterKey = new SymmetricCryptoKey(
Utils.fromB64ToArray(message.keyB64).buffer
) as MasterKey;
const userKey = await this.cryptoService.decryptUserKeyWithMasterKey(
masterKey,
new EncString(encUserKey)
);
await this.cryptoService.setMasterKey(masterKey);
await this.cryptoService.setUserKey(userKey);
} else {
throw new Error("No key received");
}
} catch (e) {
this.logService.error("Unable to set key: " + e);
this.messagingService.send("showDialog", {
title: { key: "biometricsFailedTitle" },
content: { key: "biometricsFailedDesc" },
acceptButtonText: { key: "ok" },
cancelButtonText: null,
type: "danger",
});
// Exit early
if (this.resolver) {
this.resolver(message);
}
return;
}
// Verify key is correct by attempting to decrypt a secret
try {
await this.cryptoService.getFingerprint(await this.stateService.getUserId());
} catch (e) {
this.logService.error("Unable to verify key: " + e);
await this.cryptoService.clearKey();
await this.cryptoService.clearKeys();
this.showWrongUserDialog();
// Exit early

View File

@ -1,13 +1,23 @@
import { KeySuffixOptions } from "@bitwarden/common/enums";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import {
SymmetricCryptoKey,
UserKey,
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { CryptoService } from "@bitwarden/common/platform/services/crypto.service";
export class BrowserCryptoService extends CryptoService {
protected async retrieveKeyFromStorage(keySuffix: KeySuffixOptions) {
if (keySuffix === "biometric") {
protected override async retrieveUserKeyFromStorage(
keySuffix: KeySuffixOptions
): Promise<UserKey> {
if (keySuffix === KeySuffixOptions.Biometric) {
await this.platformUtilService.authenticateBiometric();
return (await this.getKey())?.keyB64;
const userKey = await this.getUserKeyFromMemory();
if (userKey) {
return new SymmetricCryptoKey(Utils.fromB64ToArray(userKey.keyB64).buffer) as UserKey;
}
}
return await super.retrieveKeyFromStorage(keySuffix);
return await super.retrieveUserKeyFromStorage(keySuffix);
}
}

View File

@ -1,5 +1,12 @@
import { BehaviorSubject } from "rxjs";
import { BehaviorSubject, Observable } from "rxjs";
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { StateMigrationService } from "@bitwarden/common/platform/abstractions/state-migration.service";
import {
AbstractStorageService,
AbstractMemoryStorageService,
} from "@bitwarden/common/platform/abstractions/storage.service";
import { StateFactory } from "@bitwarden/common/platform/factories/state-factory";
import { GlobalState } from "@bitwarden/common/platform/models/domain/global-state";
import { StorageOptions } from "@bitwarden/common/platform/models/domain/storage-options";
import { StateService as BaseStateService } from "@bitwarden/common/platform/services/state.service";
@ -26,14 +33,36 @@ export class BrowserStateService
protected activeAccountSubject: BehaviorSubject<string>;
@sessionSync({ initializer: (b: boolean) => b })
protected activeAccountUnlockedSubject: BehaviorSubject<boolean>;
@sessionSync({
initializer: Account.fromJSON as any, // TODO: Remove this any when all any types are removed from Account
initializeAs: "record",
})
protected accountDiskCache: BehaviorSubject<Record<string, Account>>;
protected accountDeserializer = Account.fromJSON;
constructor(
storageService: AbstractStorageService,
secureStorageService: AbstractStorageService,
memoryStorageService: AbstractMemoryStorageService,
logService: LogService,
stateMigrationService: StateMigrationService,
stateFactory: StateFactory<GlobalState, Account>,
useAccountCache = true,
accountCache: Observable<Record<string, Account>> = null
) {
super(
storageService,
secureStorageService,
memoryStorageService,
logService,
stateMigrationService,
stateFactory,
useAccountCache
);
// Hack to allow shared disk cache between contexts on browser
// TODO: Remove when services are consolidated to a single context
if (useAccountCache && accountCache) {
accountCache.subscribe(this.accountDiskCacheSubject);
}
}
async addAccount(account: Account) {
// Apply browser overrides to default account values
account = new Account(account);

View File

@ -440,13 +440,20 @@ function getBgService<T>(service: keyof MainBackground) {
logService: LogServiceAbstraction,
stateMigrationService: StateMigrationService
) => {
// hack to share the disk cache between the two contexts.
// TODO: we need to figure out a better way of sharing/syncing
// the disk cache
const bgStateService = getBgService<StateServiceAbstraction>("stateService");
const bgDiskCache = bgStateService().accountDiskCache$;
return new BrowserStateService(
storageService,
secureStorageService,
memoryStorageService,
logService,
stateMigrationService,
new StateFactory(GlobalState, Account)
new StateFactory(GlobalState, Account),
true,
bgDiskCache
);
},
deps: [

View File

@ -19,11 +19,11 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
os_log(.default, "Received message from browser.runtime.sendNativeMessage: %@", message as! CVarArg)
let response = NSExtensionItem()
guard let command = message?["command"] as? String else {
return
}
switch (command) {
case "readFromClipboard":
let pasteboard = NSPasteboard.general
@ -59,12 +59,12 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
guard let data = blobData else {
return
}
let panel = NSSavePanel()
panel.canCreateDirectories = true
panel.nameFieldStringValue = dlMsg.fileName
let response = panel.runModal();
if response == NSApplication.ModalResponse.OK {
if let url = panel.url {
do {
@ -87,12 +87,12 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
}
return
case "biometricUnlock":
var error: NSError?
let laContext = LAContext()
laContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error)
if let e = error, e.code != kLAErrorBiometryLockout {
response.userInfo = [
SFExtensionMessageKey: [
@ -123,26 +123,26 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
guard let userId = message?["userId"] as? String else {
return
}
let passwordName = userId + "_masterkey_biometric"
let passwordName = userId + "_user_biometric"
var passwordLength: UInt32 = 0
var passwordPtr: UnsafeMutableRawPointer? = nil
var status = SecKeychainFindGenericPassword(nil, UInt32(ServiceNameBiometric.utf8.count), ServiceNameBiometric, UInt32(passwordName.utf8.count), passwordName, &passwordLength, &passwordPtr, nil)
if status != errSecSuccess {
let fallbackName = "key"
status = SecKeychainFindGenericPassword(nil, UInt32(ServiceNameBiometric.utf8.count), ServiceNameBiometric, UInt32(fallbackName.utf8.count), fallbackName, &passwordLength, &passwordPtr, nil)
}
if status == errSecSuccess {
let result = NSString(bytes: passwordPtr!, length: Int(passwordLength), encoding: String.Encoding.utf8.rawValue) as String?
SecKeychainItemFreeContent(nil, passwordPtr)
response.userInfo = [ SFExtensionMessageKey: [
"message": [
"command": "biometricUnlock",
"response": "unlocked",
"timestamp": Int64(NSDate().timeIntervalSince1970 * 1000),
"keyB64": result!.replacingOccurrences(of: "\"", with: ""),
"userKeyB64": result!.replacingOccurrences(of: "\"", with: ""),
],
]]
} else {
@ -157,10 +157,10 @@ class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
]
}
}
context.completeRequest(returningItems: [response], completionHandler: nil)
}
return
default:
return

View File

@ -406,7 +406,7 @@ export class LoginCommand {
}
try {
const { newPasswordHash, newEncKey, hint } = await this.collectNewMasterPasswordDetails(
const { newPasswordHash, newUserKey, hint } = await this.collectNewMasterPasswordDetails(
"Your master password does not meet one or more of your organization policies. In order to access the vault, you must update your master password now."
);
@ -414,7 +414,7 @@ export class LoginCommand {
request.masterPasswordHash = await this.cryptoService.hashPassword(currentPassword, null);
request.masterPasswordHint = hint;
request.newMasterPasswordHash = newPasswordHash;
request.key = newEncKey[1].encryptedString;
request.key = newUserKey[1].encryptedString;
await this.apiService.postPassword(request);
@ -444,12 +444,12 @@ export class LoginCommand {
}
try {
const { newPasswordHash, newEncKey, hint } = await this.collectNewMasterPasswordDetails(
const { newPasswordHash, newUserKey, hint } = await this.collectNewMasterPasswordDetails(
"An organization administrator recently changed your master password. In order to access the vault, you must update your master password now."
);
const request = new UpdateTempPasswordRequest();
request.key = newEncKey[1].encryptedString;
request.key = newUserKey[1].encryptedString;
request.newMasterPasswordHash = newPasswordHash;
request.masterPasswordHint = hint;
@ -467,8 +467,8 @@ export class LoginCommand {
/**
* Collect new master password and hint from the CLI. The collected password
* is validated against any applicable master password policies and a new encryption
* key is generated
* is validated against any applicable master password policies, a new master
* key is generated, and we use it to re-encrypt the user key
* @param prompt - Message that is displayed during the initial prompt
* @param error
*/
@ -477,7 +477,7 @@ export class LoginCommand {
error?: string
): Promise<{
newPasswordHash: string;
newEncKey: [SymmetricCryptoKey, EncString];
newUserKey: [SymmetricCryptoKey, EncString];
hint?: string;
}> {
if (this.email == null || this.email === "undefined") {
@ -559,21 +559,24 @@ export class LoginCommand {
const kdfConfig = await this.stateService.getKdfConfig();
// Create new key and hash new password
const newKey = await this.cryptoService.makeKey(
const newMasterKey = await this.cryptoService.makeMasterKey(
masterPassword,
this.email.trim().toLowerCase(),
kdf,
kdfConfig
);
const newPasswordHash = await this.cryptoService.hashPassword(masterPassword, newKey);
const newPasswordHash = await this.cryptoService.hashPassword(masterPassword, newMasterKey);
// Grab user's current enc key
const userEncKey = await this.cryptoService.getEncKey();
// Grab user key
const userKey = await this.cryptoService.getUserKeyFromMemory();
if (!userKey) {
throw new Error("User key not found.");
}
// Create new encKey for the User
const newEncKey = await this.cryptoService.remakeEncKey(newKey, userEncKey);
// Re-encrypt user key with new master key
const newUserKey = await this.cryptoService.encryptUserKeyWithMasterKey(newMasterKey, userKey);
return { newPasswordHash, newEncKey, hint: masterPasswordHint };
return { newPasswordHash, newUserKey: newUserKey, hint: masterPasswordHint };
}
private async handleCaptchaRequired(

View File

@ -44,17 +44,17 @@ export class UnlockCommand {
const email = await this.stateService.getEmail();
const kdf = await this.stateService.getKdfType();
const kdfConfig = await this.stateService.getKdfConfig();
const key = await this.cryptoService.makeKey(password, email, kdf, kdfConfig);
const masterKey = await this.cryptoService.makeMasterKey(password, email, kdf, kdfConfig);
const storedKeyHash = await this.cryptoService.getKeyHash();
let passwordValid = false;
if (key != null) {
if (masterKey != null) {
if (storedKeyHash != null) {
passwordValid = await this.cryptoService.compareAndUpdateKeyHash(password, key);
passwordValid = await this.cryptoService.compareAndUpdateKeyHash(password, masterKey);
} else {
const serverKeyHash = await this.cryptoService.hashPassword(
password,
key,
masterKey,
HashPurpose.ServerAuthorization
);
const request = new SecretVerificationRequest();
@ -64,7 +64,7 @@ export class UnlockCommand {
passwordValid = true;
const localKeyHash = await this.cryptoService.hashPassword(
password,
key,
masterKey,
HashPurpose.LocalAuthorization
);
await this.cryptoService.setKeyHash(localKeyHash);
@ -75,7 +75,9 @@ export class UnlockCommand {
}
if (passwordValid) {
await this.cryptoService.setKey(key);
await this.cryptoService.setMasterKey(masterKey);
const userKey = await this.cryptoService.decryptUserKeyWithMasterKey(masterKey);
await this.cryptoService.setUserKey(userKey);
if (await this.keyConnectorService.getConvertAccountRequired()) {
const convertToKeyConnectorCommand = new ConvertToKeyConnectorCommand(

View File

@ -334,7 +334,7 @@ export class Main {
);
const lockedCallback = async () =>
await this.cryptoService.clearStoredKey(KeySuffixOptions.Auto);
await this.cryptoService.clearStoredUserKey(KeySuffixOptions.Auto);
this.vaultTimeoutSettingsService = new VaultTimeoutSettingsService(
this.cryptoService,

View File

@ -425,11 +425,14 @@ export class ServeCommand {
this.processResponse(res, Response.error("You are not logged in."));
return true;
}
if (await this.main.cryptoService.hasKeyInMemory()) {
if (await this.main.cryptoService.hasUserKeyInMemory()) {
return false;
} else if (await this.main.cryptoService.hasKeyStored(KeySuffixOptions.Auto)) {
} else if (await this.main.cryptoService.hasUserKeyStored(KeySuffixOptions.Auto)) {
// load key into memory
await this.main.cryptoService.getKey();
const userAutoKey = await this.main.cryptoService.getUserKeyFromStorage(
KeySuffixOptions.Auto
);
await this.main.cryptoService.setUserKey(userAutoKey);
return false;
}
this.processResponse(res, Response.error("Vault is locked."));

View File

@ -597,11 +597,14 @@ export class Program {
protected async exitIfLocked() {
await this.exitIfNotAuthed();
if (await this.main.cryptoService.hasKeyInMemory()) {
if (await this.main.cryptoService.hasUserKeyInMemory()) {
return;
} else if (await this.main.cryptoService.hasKeyStored(KeySuffixOptions.Auto)) {
} else if (await this.main.cryptoService.hasUserKeyStored(KeySuffixOptions.Auto)) {
// load key into memory
await this.main.cryptoService.getKey();
const userAutoKey = await this.main.cryptoService.getUserKeyFromStorage(
KeySuffixOptions.Auto
);
await this.main.cryptoService.setUserKey(userAutoKey);
} else if (process.env.BW_NOINTERACTION !== "true") {
// must unlock
if (await this.main.keyConnectorService.getUsesKeyConnector()) {

View File

@ -126,8 +126,8 @@ export class CreateCommand {
return Response.error("Premium status is required to use this feature.");
}
const encKey = await this.cryptoService.getEncKey();
if (encKey == null) {
const userKey = await this.cryptoService.getUserKeyFromMemory();
if (userKey == null) {
return Response.error(
"You must update your encryption key before you can use this feature. " +
"See https://help.bitwarden.com/article/update-encryption-key/"

View File

@ -404,7 +404,7 @@ export class SettingsComponent implements OnInit {
await this.cryptoService.toggleKey();
// Validate the key is stored in case biometrics fail.
const biometricSet = await this.cryptoService.hasKeyStored(KeySuffixOptions.Biometric);
const biometricSet = await this.cryptoService.hasUserKeyStored(KeySuffixOptions.Biometric);
this.form.controls.biometric.setValue(biometricSet);
if (!biometricSet) {
await this.stateService.setBiometricUnlock(null);

View File

@ -6,13 +6,13 @@
"message": "Filtriloj"
},
"allItems": {
"message": "All items"
"message": "Ĉiuj Eroj"
},
"favorites": {
"message": "Favorites"
"message": "Plej ŝatataj"
},
"types": {
"message": "Types"
"message": "Tipoj"
},
"typeLogin": {
"message": "Saluto"
@ -21,7 +21,7 @@
"message": "Karto"
},
"typeIdentity": {
"message": "Identity"
"message": "Idento"
},
"typeSecureNote": {
"message": "Sekura noto"
@ -30,10 +30,10 @@
"message": "Dosierujoj"
},
"collections": {
"message": "Collections"
"message": "Kolektoj"
},
"searchVault": {
"message": "Search vault"
"message": "Traserĉu trezorejon"
},
"addItem": {
"message": "Aldoni elementon"
@ -45,10 +45,10 @@
"message": "Kundividi"
},
"moveToOrganization": {
"message": "Move to organization"
"message": "Movu al organizo"
},
"movedItemToOrg": {
"message": "$ITEMNAME$ moved to $ORGNAME$",
"message": "$ITEMNAME$ moviĝis al $ORGNAME$",
"placeholders": {
"itemname": {
"content": "$1",
@ -61,7 +61,7 @@
}
},
"moveToOrgDesc": {
"message": "Choose an organization that you wish to move this item to. Moving to an organization transfers ownership of the item to that organization. You will no longer be the direct owner of this item once it has been moved."
"message": "Elektu organizon kun kiu vi volas dividi ĉi tiun eron. Dividado transdonas posedon de la ero al la organizo. Vi ne plu estos la rekta posedanto de ĉi tiu ero post kiam ĝi estos dividita."
},
"attachments": {
"message": "Aldonaĵoj"
@ -104,10 +104,10 @@
"message": "Retpoŝta adreso"
},
"verificationCodeTotp": {
"message": "Verification code (TOTP)"
"message": "Kontrola kodo (TOTP)"
},
"website": {
"message": "Website"
"message": "Retejo"
},
"notes": {
"message": "Notoj"
@ -116,10 +116,10 @@
"message": "Propraj kampoj"
},
"launch": {
"message": "Launch"
"message": "Lanĉo"
},
"copyValue": {
"message": "Copy value",
"message": "Kopii valoron",
"description": "Copy value to clipboard"
},
"minimizeOnCopyToClipboard": {

View File

@ -962,7 +962,7 @@
"message": "Käynnistä automaattisesti kirjauduttaessa"
},
"openAtLoginDesc": {
"message": "Käynnistä sovellus automaattisesti kirjautumisen yhteydessä."
"message": "Käynnistä Bitwarden-sovellus automaattisesti kirjautumisen yhteydessä."
},
"alwaysShowDock": {
"message": "Näytä aina Dockissa"

View File

@ -4,7 +4,12 @@ import { EncryptService } from "@bitwarden/common/platform/abstractions/encrypt.
import { LogService } from "@bitwarden/common/platform/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import {
MasterKey,
SymmetricCryptoKey,
UserKey,
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { CryptoService } from "@bitwarden/common/platform/services/crypto.service";
import { CsprngString } from "@bitwarden/common/types/csprng";
@ -21,24 +26,36 @@ export class ElectronCryptoService extends CryptoService {
super(cryptoFunctionService, encryptService, platformUtilsService, logService, stateService);
}
protected override async storeKey(key: SymmetricCryptoKey, userId?: string) {
await super.storeKey(key, userId);
protected override async storeAdditionalKeys(key: UserKey, userId?: string) {
await super.storeAdditionalKeys(key, userId);
const storeBiometricKey = await this.shouldStoreKey(KeySuffixOptions.Biometric, userId);
if (storeBiometricKey) {
await this.storeBiometricKey(key, userId);
} else {
await this.stateService.setCryptoMasterKeyBiometric(null, { userId: userId });
await this.stateService.setUserKeyBiometric(null, { userId: userId });
}
}
protected async storeBiometricKey(key: SymmetricCryptoKey, userId?: string): Promise<void> {
protected override async retrieveUserKeyFromStorage(
keySuffix: KeySuffixOptions,
userId?: string
): Promise<UserKey> {
if (keySuffix === KeySuffixOptions.Biometric) {
await this.migrateBiometricKeyIfNeeded(userId);
const userKey = await this.stateService.getUserKeyBiometric({ userId: userId });
return new SymmetricCryptoKey(Utils.fromB64ToArray(userKey).buffer) as UserKey;
}
return await super.retrieveUserKeyFromStorage(keySuffix, userId);
}
protected async storeBiometricKey(key: UserKey, userId?: string): Promise<void> {
let clientEncKeyHalf: CsprngString = null;
if (await this.stateService.getBiometricRequirePasswordOnStart({ userId })) {
clientEncKeyHalf = await this.getBiometricEncryptionClientKeyHalf(userId);
}
await this.stateService.setCryptoMasterKeyBiometric(
await this.stateService.setUserKeyBiometric(
{ key: key.keyB64, clientEncKeyHalf },
{ userId: userId }
);
@ -63,4 +80,21 @@ export class ElectronCryptoService extends CryptoService {
return null;
}
}
private async migrateBiometricKeyIfNeeded(userId?: string) {
if (await this.stateService.hasCryptoMasterKeyBiometric({ userId })) {
const oldBiometricKey = await this.stateService.getCryptoMasterKeyBiometric({ userId });
// decrypt
const masterKey = new SymmetricCryptoKey(Utils.fromB64ToArray(oldBiometricKey)) as MasterKey;
let encUserKey = await this.stateService.getEncryptedCryptoSymmetricKey();
encUserKey = encUserKey ?? (await this.stateService.getUserKeyMasterKey());
if (!encUserKey) {
throw new Error("No user key found during biometric migration");
}
const userKey = await this.decryptUserKeyWithMasterKey(masterKey, new EncString(encUserKey));
// migrate
await this.storeBiometricKey(userKey, userId);
await this.stateService.setCryptoMasterKeyBiometric(null, { userId });
}
}
}

View File

@ -8,7 +8,7 @@ import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.se
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { EncryptedString, EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { StateService } from "@bitwarden/common/platform/services/state.service";
@ -144,7 +144,9 @@ export class NativeMessageHandlerService {
}
private async handleEncryptedMessage(message: EncryptedMessage) {
message.encryptedCommand = EncString.fromJSON(message.encryptedCommand.toString());
message.encryptedCommand = EncString.fromJSON(
message.encryptedCommand.toString() as EncryptedString
);
const decryptedCommandData = await this.decryptPayload(message);
const { command } = decryptedCommandData;

View File

@ -136,14 +136,22 @@ export class NativeMessagingService {
});
}
const key = await this.cryptoService.getKeyFromStorage(
const userKey = await this.cryptoService.getUserKeyFromStorage(
KeySuffixOptions.Biometric,
message.userId
);
const masterKey = await this.cryptoService.getMasterKey(message.userId);
if (key != null) {
if (userKey != null) {
// we send the master key still for backwards compatibility
// with older browser extensions
this.send(
{ command: "biometricUnlock", response: "unlocked", keyB64: key.keyB64 },
{
command: "biometricUnlock",
response: "unlocked",
keyB64: masterKey.keyB64,
userKeyB64: userKey.keyB64,
},
appId
);
} else {

View File

@ -1,8 +1,8 @@
{
"urls": {
"icons": "https://icons.bitwarden.net",
"notifications": "https://notifications.bitwarden.net",
"scim": "https://scim.bitwarden.net"
"notifications": "https://notifications.beta.bitwarden.net",
"scim": "https://scim.beta.bitwarden.net"
},
"flags": {
"secretsManager": true,

View File

@ -7,6 +7,7 @@ import { OrganizationUserStatusType } from "@bitwarden/common/admin-console/enum
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { BulkUserDetails } from "./bulk-status.component";
@ -98,7 +99,7 @@ export class BulkConfirmComponent implements OnInit {
);
}
protected getCryptoKey() {
protected getCryptoKey(): Promise<SymmetricCryptoKey> {
return this.cryptoService.getOrgKey(this.organizationId);
}

View File

@ -23,7 +23,10 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import {
SymmetricCryptoKey,
UserKey,
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
@Component({
@ -171,25 +174,31 @@ export class ResetPasswordComponent implements OnInit, OnDestroy {
orgSymKey
);
// Decrypt User's Reset Password Key to get EncKey
// Decrypt User's Reset Password Key to get UserKey
const decValue = await this.cryptoService.rsaDecrypt(resetPasswordKey, decPrivateKey);
const userEncKey = new SymmetricCryptoKey(decValue);
const existingUserKey = new SymmetricCryptoKey(decValue) as UserKey;
// Create new key and hash new password
const newKey = await this.cryptoService.makeKey(
// Create new master key and hash new password
const newMasterKey = await this.cryptoService.makeMasterKey(
this.newPassword,
this.email.trim().toLowerCase(),
kdfType,
new KdfConfig(kdfIterations, kdfMemory, kdfParallelism)
);
const newPasswordHash = await this.cryptoService.hashPassword(this.newPassword, newKey);
const newPasswordHash = await this.cryptoService.hashPassword(
this.newPassword,
newMasterKey
);
// Create new encKey for the User
const newEncKey = await this.cryptoService.remakeEncKey(newKey, userEncKey);
// Create new encrypted user key for the User
const newUserKey = await this.cryptoService.encryptUserKeyWithMasterKey(
newMasterKey,
existingUserKey
);
// Create request
const request = new OrganizationUserResetPasswordRequest();
request.key = newEncKey[1].encryptedString;
request.key = newUserKey[1].encryptedString;
request.newMasterPasswordHash = newPasswordHash;
// Change user's password

View File

@ -58,8 +58,8 @@ export class EnrollMasterPasswordReset {
const publicKey = Utils.fromB64ToArray(orgKeys.publicKey);
// RSA Encrypt user's encKey.key with organization public key
const encKey = await this.cryptoService.getEncKey();
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey.buffer);
const userKey = await this.cryptoService.getUserKeyFromMemory();
const encryptedKey = await this.cryptoService.rsaEncrypt(userKey.key, publicKey.buffer);
keyString = encryptedKey.encryptedString;
toastStringRef = "enrollPasswordResetSuccess";

View File

@ -141,8 +141,8 @@ export class AcceptOrganizationComponent extends BaseAcceptComponent {
const publicKey = Utils.fromB64ToArray(response.publicKey);
// RSA Encrypt user's encKey.key with organization public key
const encKey = await this.cryptoService.getEncKey();
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey.buffer);
const userKey = await this.cryptoService.getUserKeyFromMemory();
const encryptedKey = await this.cryptoService.rsaEncrypt(userKey.key, publicKey.buffer);
// Add reset password key to accept request
request.resetPasswordKey = encryptedKey.encryptedString;

View File

@ -17,7 +17,10 @@ import { LogService } from "@bitwarden/common/platform/abstractions/log.service"
import { MessagingService } from "@bitwarden/common/platform/abstractions/messaging.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import {
SymmetricCryptoKey,
UserKey,
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
@Component({
@ -91,9 +94,9 @@ export class EmergencyAccessTakeoverComponent
);
const oldKeyBuffer = await this.cryptoService.rsaDecrypt(takeoverResponse.keyEncrypted);
const oldEncKey = new SymmetricCryptoKey(oldKeyBuffer);
const oldUserKey = new SymmetricCryptoKey(oldKeyBuffer) as UserKey;
if (oldEncKey == null) {
if (oldUserKey == null) {
this.platformUtilsService.showToast(
"error",
this.i18nService.t("errorOccurred"),
@ -102,7 +105,7 @@ export class EmergencyAccessTakeoverComponent
return;
}
const key = await this.cryptoService.makeKey(
const masterKey = await this.cryptoService.makeMasterKey(
this.masterPassword,
this.email,
takeoverResponse.kdf,
@ -112,9 +115,12 @@ export class EmergencyAccessTakeoverComponent
takeoverResponse.kdfParallelism
)
);
const masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, key);
const masterPasswordHash = await this.cryptoService.hashPassword(
this.masterPassword,
masterKey
);
const encKey = await this.cryptoService.remakeEncKey(key, oldEncKey);
const encKey = await this.cryptoService.encryptUserKeyWithMasterKey(masterKey, oldUserKey);
const request = new EmergencyAccessPasswordRequest();
request.newMasterPasswordHash = masterPasswordHash;

View File

@ -5,7 +5,10 @@ import { ModalService } from "@bitwarden/angular/services/modal.service";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { EmergencyAccessViewResponse } from "@bitwarden/common/auth/models/response/emergency-access.response";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import {
SymmetricCryptoKey,
UserKey,
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherData } from "@bitwarden/common/vault/models/data/cipher.data";
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
@ -87,13 +90,13 @@ export class EmergencyAccessViewComponent implements OnInit {
const decCiphers: CipherView[] = [];
const oldKeyBuffer = await this.cryptoService.rsaDecrypt(response.keyEncrypted);
const oldEncKey = new SymmetricCryptoKey(oldKeyBuffer);
const oldUserKey = new SymmetricCryptoKey(oldKeyBuffer) as UserKey;
const promises: any[] = [];
ciphers.forEach((cipherResponse) => {
const cipherData = new CipherData(cipherResponse);
const cipher = new Cipher(cipherData);
promises.push(cipher.decrypt(oldEncKey).then((c) => decCiphers.push(c)));
promises.push(cipher.decrypt(oldUserKey).then((c) => decCiphers.push(c)));
});
await Promise.all(promises);

View File

@ -300,9 +300,9 @@ export class EmergencyAccessComponent implements OnInit {
}
}
// Encrypt the master password hash using the grantees public key, and send it to bitwarden for escrow.
// Encrypt the user key with the grantees public key, and send it to bitwarden for escrow.
private async doConfirmation(details: EmergencyAccessGranteeDetailsResponse) {
const encKey = await this.cryptoService.getEncKey();
const userKey = await this.cryptoService.getUserKeyFromMemory();
const publicKeyResponse = await this.apiService.getUserPublicKey(details.granteeId);
const publicKey = Utils.fromB64ToArray(publicKeyResponse.publicKey);
@ -315,7 +315,7 @@ export class EmergencyAccessComponent implements OnInit {
// Ignore errors since it's just a debug message
}
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey.buffer);
const encryptedKey = await this.cryptoService.rsaEncrypt(userKey.key, publicKey.buffer);
const request = new EmergencyAccessConfirmRequest();
request.key = encryptedKey.encryptedString;
await this.apiService.postEmergencyAccessConfirm(details.id, request);

View File

@ -42,8 +42,8 @@ export class ChangeEmailComponent implements OnInit {
}
async submit() {
const hasEncKey = await this.cryptoService.hasEncKey();
if (!hasEncKey) {
const hasUserKey = await this.cryptoService.hasUserKey();
if (!hasUserKey) {
this.platformUtilsService.showToast("error", null, this.i18nService.t("updateKey"));
return;
}
@ -67,7 +67,7 @@ export class ChangeEmailComponent implements OnInit {
request.masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, null);
const kdf = await this.stateService.getKdfType();
const kdfConfig = await this.stateService.getKdfConfig();
const newKey = await this.cryptoService.makeKey(
const newMasterKey = await this.cryptoService.makeMasterKey(
this.masterPassword,
this.newEmail,
kdf,
@ -75,10 +75,10 @@ export class ChangeEmailComponent implements OnInit {
);
request.newMasterPasswordHash = await this.cryptoService.hashPassword(
this.masterPassword,
newKey
newMasterKey
);
const newEncKey = await this.cryptoService.remakeEncKey(newKey);
request.key = newEncKey[1].encryptedString;
const newUserKey = await this.cryptoService.encryptUserKeyWithMasterKey(newMasterKey);
request.key = newUserKey[1].encryptedString;
try {
this.formPromise = this.apiService.postEmail(request);
await this.formPromise;

View File

@ -46,8 +46,8 @@ export class ChangeKdfConfirmationComponent {
async submit() {
this.loading = true;
const hasEncKey = await this.cryptoService.hasEncKey();
if (!hasEncKey) {
const hasUserKey = await this.cryptoService.hasUserKey();
if (!hasUserKey) {
this.platformUtilsService.showToast("error", null, this.i18nService.t("updateKey"));
return;
}
@ -77,15 +77,18 @@ export class ChangeKdfConfirmationComponent {
request.kdfParallelism = this.kdfConfig.parallelism;
request.masterPasswordHash = await this.cryptoService.hashPassword(masterPassword, null);
const email = await this.stateService.getEmail();
const newKey = await this.cryptoService.makeKey(
const newMasterKey = await this.cryptoService.makeMasterKey(
masterPassword,
email,
this.kdf,
this.kdfConfig
);
request.newMasterPasswordHash = await this.cryptoService.hashPassword(masterPassword, newKey);
const newEncKey = await this.cryptoService.remakeEncKey(newKey);
request.key = newEncKey[1].encryptedString;
request.newMasterPasswordHash = await this.cryptoService.hashPassword(
masterPassword,
newMasterKey
);
const newUserKey = await this.cryptoService.encryptUserKeyWithMasterKey(newMasterKey);
request.key = newUserKey[1].encryptedString;
await this.apiService.postAccountKdf(request);
}

View File

@ -89,12 +89,12 @@
<input
class="form-check-input"
type="checkbox"
id="rotateEncKey"
name="RotateEncKey"
[(ngModel)]="rotateEncKey"
(change)="rotateEncKeyClicked()"
id="rotateUserKey"
name="RotateUserKey"
[(ngModel)]="rotateUserKey"
(change)="rotateUserKeyClicked()"
/>
<label class="form-check-label" for="rotateEncKey">
<label class="form-check-label" for="rotateUserKey">
{{ "rotateAccountEncKey" | i18n }}
</label>
<a

View File

@ -23,7 +23,11 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import {
MasterKey,
SymmetricCryptoKey,
UserKey,
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { SendWithIdRequest } from "@bitwarden/common/tools/send/models/request/send-with-id.request";
import { SendService } from "@bitwarden/common/tools/send/services/send.service.abstraction";
@ -38,7 +42,7 @@ import { FolderWithIdRequest } from "@bitwarden/common/vault/models/request/fold
templateUrl: "change-password.component.html",
})
export class ChangePasswordComponent extends BaseChangePasswordComponent {
rotateEncKey = false;
rotateUserKey = false;
currentMasterPassword: string;
masterPasswordHint: string;
checkForBreaches = true;
@ -88,8 +92,8 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
this.characterMinimumMessage = this.i18nService.t("characterMinimum", this.minimumLength);
}
async rotateEncKeyClicked() {
if (this.rotateEncKey) {
async rotateUserKeyClicked() {
if (this.rotateUserKey) {
const ciphers = await this.cipherService.getAllDecrypted();
let hasOldAttachments = false;
if (ciphers != null) {
@ -115,7 +119,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
"https://bitwarden.com/help/attachments/#add-storage-space"
);
}
this.rotateEncKey = false;
this.rotateUserKey = false;
return;
}
@ -131,14 +135,14 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
});
if (!result) {
this.rotateEncKey = false;
this.rotateUserKey = false;
}
}
}
async submit() {
const hasEncKey = await this.cryptoService.hasEncKey();
if (!hasEncKey) {
const hasUserKey = await this.cryptoService.hasUserKey();
if (!hasUserKey) {
this.platformUtilsService.showToast("error", null, this.i18nService.t("updateKey"));
return;
}
@ -170,7 +174,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
return false;
}
if (this.rotateEncKey) {
if (this.rotateUserKey) {
await this.syncService.fullSync(true);
}
@ -179,8 +183,8 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
async performSubmitActions(
newMasterPasswordHash: string,
newKey: SymmetricCryptoKey,
newEncKey: [SymmetricCryptoKey, EncString]
newMasterKey: MasterKey,
newUserKey: [UserKey, EncString]
) {
const request = new PasswordRequest();
request.masterPasswordHash = await this.cryptoService.hashPassword(
@ -189,12 +193,12 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
);
request.masterPasswordHint = this.masterPasswordHint;
request.newMasterPasswordHash = newMasterPasswordHash;
request.key = newEncKey[1].encryptedString;
request.key = newUserKey[1].encryptedString;
try {
if (this.rotateEncKey) {
if (this.rotateUserKey) {
this.formPromise = this.apiService.postPassword(request).then(() => {
return this.updateKey(newKey, request.newMasterPasswordHash);
return this.updateKey(newMasterKey, request.newMasterPasswordHash);
});
} else {
this.formPromise = this.apiService.postPassword(request);
@ -213,16 +217,16 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
}
}
private async updateKey(key: SymmetricCryptoKey, masterPasswordHash: string) {
const encKey = await this.cryptoService.makeEncKey(key);
private async updateKey(masterKey: MasterKey, masterPasswordHash: string) {
const userKey = await this.cryptoService.makeUserKey(masterKey);
const privateKey = await this.cryptoService.getPrivateKey();
let encPrivateKey: EncString = null;
if (privateKey != null) {
encPrivateKey = await this.cryptoService.encrypt(privateKey, encKey[0]);
encPrivateKey = await this.cryptoService.encrypt(privateKey, userKey[0]);
}
const request = new UpdateKeyRequest();
request.privateKey = encPrivateKey != null ? encPrivateKey.encryptedString : null;
request.key = encKey[1].encryptedString;
request.key = userKey[1].encryptedString;
request.masterPasswordHash = masterPasswordHash;
const folders = await firstValueFrom(this.folderService.folderViews$);
@ -230,7 +234,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
if (folders[i].id == null) {
continue;
}
const folder = await this.folderService.encrypt(folders[i], encKey[0]);
const folder = await this.folderService.encrypt(folders[i], userKey[0]);
request.folders.push(new FolderWithIdRequest(folder));
}
@ -240,24 +244,24 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
continue;
}
const cipher = await this.cipherService.encrypt(ciphers[i], encKey[0]);
const cipher = await this.cipherService.encrypt(ciphers[i], userKey[0]);
request.ciphers.push(new CipherWithIdRequest(cipher));
}
const sends = await firstValueFrom(this.sendService.sends$);
await Promise.all(
sends.map(async (send) => {
const cryptoKey = await this.cryptoService.decryptToBytes(send.key, null);
send.key = (await this.cryptoService.encrypt(cryptoKey, encKey[0])) ?? send.key;
const sendKey = await this.cryptoService.decryptToBytes(send.key, null);
send.key = (await this.cryptoService.encrypt(sendKey, userKey[0])) ?? send.key;
request.sends.push(new SendWithIdRequest(send));
})
);
await this.apiService.postAccountKey(request);
await this.updateEmergencyAccesses(encKey[0]);
await this.updateEmergencyAccesses(userKey[0]);
await this.updateAllResetPasswordKeys(encKey[0], masterPasswordHash);
await this.updateAllResetPasswordKeys(userKey[0], masterPasswordHash);
}
private async updateEmergencyAccesses(encKey: SymmetricCryptoKey) {
@ -285,7 +289,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
}
}
private async updateAllResetPasswordKeys(encKey: SymmetricCryptoKey, masterPasswordHash: string) {
private async updateAllResetPasswordKeys(userKey: UserKey, masterPasswordHash: string) {
const orgs = await this.organizationService.getAll();
for (const org of orgs) {
@ -299,7 +303,7 @@ export class ChangePasswordComponent extends BaseChangePasswordComponent {
const publicKey = Utils.fromB64ToArray(response?.publicKey);
// Re-enroll - encrypt user's encKey.key with organization public key
const encryptedKey = await this.cryptoService.rsaEncrypt(encKey.key, publicKey.buffer);
const encryptedKey = await this.cryptoService.rsaEncrypt(userKey.key, publicKey.buffer);
// Create/Execute request
const request = new OrganizationUserResetPasswordEnrollmentRequest();

View File

@ -1,4 +1,4 @@
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="updateEncKeyTitle">
<div class="modal fade" role="dialog" aria-modal="true" aria-labelledby="updateUserKeyTitle">
<div class="modal-dialog modal-dialog-scrollable" role="document">
<form
class="modal-content"
@ -8,7 +8,7 @@
ngNativeValidate
>
<div class="modal-header">
<h1 class="modal-title" id="updateEncKeyTitle">{{ "updateEncryptionKey" | i18n }}</h1>
<h1 class="modal-title" id="updateUserKeyTitle">{{ "updateEncryptionKey" | i18n }}</h1>
<button
type="button"
class="close"

View File

@ -36,8 +36,8 @@ export class UpdateKeyComponent {
) {}
async submit() {
const hasEncKey = await this.cryptoService.hasEncKey();
if (hasEncKey) {
const hasUserKey = await this.cryptoService.hasUserKey();
if (hasUserKey) {
return;
}
@ -68,16 +68,16 @@ export class UpdateKeyComponent {
}
private async makeRequest(): Promise<UpdateKeyRequest> {
const key = await this.cryptoService.getKey();
const encKey = await this.cryptoService.makeEncKey(key);
const masterKey = await this.cryptoService.getMasterKey();
const newUserKey = await this.cryptoService.makeUserKey(masterKey);
const privateKey = await this.cryptoService.getPrivateKey();
let encPrivateKey: EncString = null;
if (privateKey != null) {
encPrivateKey = await this.cryptoService.encrypt(privateKey, encKey[0]);
encPrivateKey = await this.cryptoService.encrypt(privateKey, newUserKey[0]);
}
const request = new UpdateKeyRequest();
request.privateKey = encPrivateKey != null ? encPrivateKey.encryptedString : null;
request.key = encKey[1].encryptedString;
request.key = newUserKey[1].encryptedString;
request.masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, null);
await this.syncService.fullSync(true);
@ -87,7 +87,7 @@ export class UpdateKeyComponent {
if (folders[i].id == null) {
continue;
}
const folder = await this.folderService.encrypt(folders[i], encKey[0]);
const folder = await this.folderService.encrypt(folders[i], newUserKey[0]);
request.folders.push(new FolderWithIdRequest(folder));
}
@ -96,7 +96,7 @@ export class UpdateKeyComponent {
if (ciphers[i].organizationId != null) {
continue;
}
const cipher = await this.cipherService.encrypt(ciphers[i], encKey[0]);
const cipher = await this.cipherService.encrypt(ciphers[i], newUserKey[0]);
request.ciphers.push(new CipherWithIdRequest(cipher));
}

View File

@ -194,7 +194,7 @@ export class VaultComponent implements OnInit, OnDestroy {
const canAccessPremium = await this.stateService.getCanAccessPremium();
this.showPremiumCallout =
!this.showVerifyEmail && !canAccessPremium && !this.platformUtilsService.isSelfHost();
this.showUpdateKey = !(await this.cryptoService.hasEncKey());
this.showUpdateKey = !(await this.cryptoService.hasUserKey());
const cipherId = getCipherIdFromParams(params);
if (!cipherId) {

View File

@ -658,7 +658,7 @@
"message": "Hovedadgangskoden er den adgangskode, du bruger, når du tilgår din boks. Det er meget vigtigt, at hovedadgangskoden ikke glemmes, da der ikke er nogen måde, hvorpå den kan genoprettes."
},
"masterPassImportant": {
"message": "Hovedadgangskoder kan ikke gendannes, hvis du glemmer dem!"
"message": "Hovedadgangskoden kan ikke gendannes, hvis den glemmes!"
},
"masterPassHintDesc": {
"message": "Et hovedadgangskodetip kan bidrage til at komme i tanke om adgangskoden, hvis den glemmes."
@ -5231,7 +5231,7 @@
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Connect login with SSO to your self-hosted decryption key server. Using this option, members wont need to use their master passwords to decrypt vault data. The require SSO authentication and single organization policies are required to set up Key Connector decryption. Contact Bitwarden Support for set up assistance.'"
},
"memberDecryptionKeyConnectorDescLink": {
"message": "require SSO authentication and single organization policies",
"message": "kræver SSO-godkendelse samt enkeltorganisationspolitikker",
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Connect login with SSO to your self-hosted decryption key server. Using this option, members wont need to use their master passwords to decrypt vault data. The require SSO authentication and single organization policies are required to set up Key Connector decryption. Contact Bitwarden Support for set up assistance.'"
},
"memberDecryptionKeyConnectorDescEnd": {

View File

@ -6553,7 +6553,7 @@
"message": "Laskutuksen synkronoinnin ohje"
},
"licensePaidFeaturesHelp": {
"message": "Maksullisen lisenssin oiminaisuusopas"
"message": "Maksullisen lisenssin ominaisuusopas"
},
"selfHostGracePeriodHelp": {
"message": "Kun tilauksesi päättyy, sinulla on 60 päivää aikaa päivittää organisaatiosi lisenssitiedosto ajan tasalle. Varoaika päättyy $GRACE_PERIOD_END_DATE$.",
@ -6830,7 +6830,7 @@
"description": "This will be used as part of a larger sentence, broken up to include links. The full sentence will read 'Once authenticated, members will decrypt vault data using a key stored on their device. The master password reset policy with automatic enrollment will turn on when this option is used.'"
},
"notFound": {
"message": "$RESOURCE$ ei löytynyt",
"message": "Resurssia $RESOURCE$ ei löytynyt",
"placeholders": {
"resource": {
"content": "$1",

View File

@ -574,7 +574,7 @@
"message": "Êtes-vous sûr de vouloir écraser le mot de passe actuel ?"
},
"editedFolder": {
"message": "Dossier modifié"
"message": "Dossier enregistré"
},
"addedFolder": {
"message": "Dossier ajouté"
@ -634,7 +634,7 @@
"message": "Commencer la Période d'Essai"
},
"logIn": {
"message": "S'identifier"
"message": "Se connecter"
},
"logInInitiated": {
"message": "Connexion initiée"
@ -676,7 +676,7 @@
"message": "Paramètres"
},
"passwordHint": {
"message": "Indice du mot de passe"
"message": "Indice de mot de passe"
},
"enterEmailToGetHint": {
"message": "Saisissez l'adresse électronique de votre compte pour recevoir l'indice de votre mot de passe principal."
@ -6900,6 +6900,6 @@
"message": "Aucun mot de passe principal"
},
"removeMembersWithoutMasterPasswordWarning": {
"message": "La suppression des membres qui n'ont pas de mot de passe principal sans leur en définir un, peut restreindre l'accès à leur compte dans soin intégralité."
"message": "La suppression des membres qui n'ont pas de mot de passe principal sans leur en définir un, peut restreindre l'accès à leur compte dans son intégralité."
}
}

View File

@ -6849,7 +6849,7 @@
"description": "A 'fingerprint phrase' is a unique word phrase (similar to a passphrase) that a user can use to authenticate their organization's public key with another user, for the purposes of sharing."
},
"deviceApprovals": {
"message": "Approvazioni dispositivi"
"message": "Approva dispositivi"
},
"deviceApprovalsDesc": {
"message": "Approva le richieste di accesso qui sotto per consentire ai membri di completare l'accesso. Le richieste non approvate scadono dopo 1 settimana. Verifica le informazioni del membro prima di approvare."
@ -6870,7 +6870,7 @@
"message": "Approva richiesta"
},
"noDeviceRequests": {
"message": "Nessuna richiesta dispositivo"
"message": "Nessuna richiesta da approvare"
},
"noDeviceRequestsDesc": {
"message": "Le richieste di approvazione dei dispositivi dei membri appariranno qui"

View File

@ -1001,7 +1001,7 @@
"message": "Confirmar a palavra-passe do ficheiro"
},
"accountRestrictedOptionDescription": {
"message": "Use your account encryption key, derived from your account's username and Master Password, to encrypt the export and restrict import to only the current Bitwarden account."
"message": "Utilize a chave de encriptação da sua conta, derivada do nome de utilizador e da palavra-passe mestra da sua conta, para encriptar a exportação e restringir a importação apenas à conta Bitwarden atual."
},
"passwordProtectedOptionDescription": {
"message": "Defina uma palavra-passe do ficheiro para encriptar a exportação e importe-a para qualquer conta Bitwarden utilizando a palavra-passe de desencriptação."
@ -1256,7 +1256,7 @@
"message": "Dados importados com sucesso"
},
"importSuccessNumberOfItems": {
"message": "A total of $AMOUNT$ items were imported.",
"message": "Foi importado um total de $AMOUNT$ itens.",
"placeholders": {
"amount": {
"content": "$1",
@ -1919,7 +1919,7 @@
}
},
"premiumPriceWithFamilyPlan": {
"message": "Go premium for just $PRICE$ /year, or get premium accounts for $FAMILYPLANUSERCOUNT$ users and unlimited family sharing with a ",
"message": "Adquira uma conta Premium por apenas $PRICE$ /ano, ou obtenha contas Premium para $FAMILYPLANUSERCOUNT$ utilizadores e partilha familiar ilimitada com um ",
"placeholders": {
"price": {
"content": "$1",
@ -1932,7 +1932,7 @@
}
},
"bitwardenFamiliesPlan": {
"message": "Bitwarden Families plan."
"message": "plano Familiar do Bitwarden."
},
"addons": {
"message": "Addons"
@ -2029,7 +2029,7 @@
"message": "Cancelar subscrição"
},
"subscriptionExpiration": {
"message": "Subscription expiration"
"message": "Validade da subscrição"
},
"subscriptionCanceled": {
"message": "A subscrição foi cancelada."
@ -2265,7 +2265,7 @@
}
},
"planNameFamilies": {
"message": "Famílias"
"message": "Familiar"
},
"planDescFamilies": {
"message": "Para uso pessoal, para partilhar com a família e amigos."
@ -2472,7 +2472,7 @@
"message": "Tem a certeza de que pretende eliminar este grupo?"
},
"deleteMultipleGroupsConfirmation": {
"message": "Are you sure you want to delete the following $QUANTITY$ group(s)?",
"message": "Tem a certeza de que pretende eliminar o(s) seguinte(s) $QUANTITY$ grupo(s)?",
"placeholders": {
"quantity": {
"content": "$1",
@ -2532,7 +2532,7 @@
"message": "Editar membro"
},
"fieldOnTabRequiresAttention": {
"message": "A field on the '$TAB$' tab requires your attention.",
"message": "Um campo no separador '$TAB$' precisa da sua atenção.",
"placeholders": {
"tab": {
"content": "$1",
@ -2823,7 +2823,7 @@
}
},
"deletedCollections": {
"message": "Deleted collections"
"message": "Coleções eliminadas"
},
"deletedCollectionId": {
"message": "Coleção $ID$ eliminada.",
@ -2871,7 +2871,7 @@
}
},
"deletedManyGroups": {
"message": "Deleted $QUANTITY$ group(s).",
"message": "$QUANTITY$ grupo(s) eliminados.",
"placeholders": {
"quantity": {
"content": "$1",
@ -3363,7 +3363,7 @@
}
},
"subscriptionFreePlan": {
"message": "You cannot invite more than $COUNT$ members without upgrading your plan.",
"message": "Não pode convidar mais do que $COUNT$ membros sem atualizar o seu plano.",
"placeholders": {
"count": {
"content": "$1",
@ -3372,7 +3372,7 @@
}
},
"subscriptionFamiliesPlan": {
"message": "You cannot invite more than $COUNT$ members without upgrading your plan. Please contact Customer Support to upgrade.",
"message": "Não pode convidar mais do que $COUNT$ membros sem atualizar o seu plano. Contacte o Apoio ao cliente para atualizar.",
"placeholders": {
"count": {
"content": "$1",
@ -3381,7 +3381,7 @@
}
},
"subscriptionSponsoredFamiliesPlan": {
"message": "Your subscription allows for a total of $COUNT$ members. Your plan is sponsored and billed to an external organization.",
"message": "A sua subscrição permite um total de $COUNT$ membros. O seu plano é patrocinado e faturado por uma organização externa.",
"placeholders": {
"count": {
"content": "$1",
@ -5443,7 +5443,7 @@
"message": "Histórico de faturação"
},
"backToReports": {
"message": "Back to reports"
"message": "Voltar aos relatórios"
},
"organizationPicker": {
"message": "Organization picker"
@ -5503,7 +5503,7 @@
"message": "Palavra aleatória"
},
"service": {
"message": "Service"
"message": "Serviço"
},
"unknownCipher": {
"message": "Item desconhecido, poderá ser necessário pedir autorização para aceder a este item."
@ -5961,7 +5961,7 @@
"message": "A eliminação de contas de serviço é permanente e irreversível."
},
"deleteServiceAccountsConfirmMessage": {
"message": "Delete $COUNT$ service accounts",
"message": "Eliminar $COUNT$ contas de serviço",
"placeholders": {
"count": {
"content": "$1",
@ -5970,13 +5970,13 @@
}
},
"deleteServiceAccountToast": {
"message": "Service account deleted"
"message": "Conta de serviço eliminada"
},
"deleteServiceAccountsToast": {
"message": "Service accounts deleted"
"message": "Contas de serviço eliminadas"
},
"searchServiceAccounts": {
"message": "Search service accounts",
"message": "Procurar contas de serviço",
"description": "Placeholder text for searching service accounts."
},
"editServiceAccount": {
@ -6071,7 +6071,7 @@
}
},
"deleteProjectInputLabel": {
"message": "Type \"$CONFIRM$\" to continue",
"message": "Escreva \"$CONFIRM$\" para continuar",
"description": "Users are prompted to type 'confirm' to delete a project",
"placeholders": {
"confirm": {
@ -6081,7 +6081,7 @@
}
},
"deleteProjectConfirmMessage": {
"message": "Delete $PROJECT$",
"message": "Eliminar $PROJECT$",
"description": "Confirmation prompt to delete a specific project, where '$PROJECT$' is a placeholder for the name of the project.",
"placeholders": {
"project": {
@ -6091,7 +6091,7 @@
}
},
"deleteProjectsConfirmMessage": {
"message": "Delete $COUNT$ Projects",
"message": "Eliminar $COUNT$ projetos",
"description": "Confirmation prompt to delete multiple projects, where '$COUNT$' is a placeholder for the number of projects to be deleted.",
"placeholders": {
"count": {
@ -6113,22 +6113,22 @@
"description": "Message to be displayed when there are no projects to display in the list."
},
"smConfirmationRequired": {
"message": "Confirmation required",
"message": "Confirmação necessária",
"description": "Indicates that user confirmation is required for an action to proceed."
},
"bulkDeleteProjectsErrorMessage": {
"message": "The following projects could not be deleted:",
"message": "Os seguintes projetos não puderam ser eliminados:",
"description": "Message to be displayed when there is an error during bulk project deletion."
},
"softDeleteSuccessToast": {
"message": "Secret sent to trash",
"message": "Segredo movido para o lixo",
"description": "Notification to be displayed when a secret is successfully sent to the trash."
},
"hardDeleteSuccessToast": {
"message": "Secret permanently deleted"
"message": "Segredo eliminado permanentemente"
},
"accessTokens": {
"message": "Access tokens",
"message": "Tokens de acesso",
"description": "Title for the section displaying access tokens."
},
"newAccessToken": {
@ -6136,7 +6136,7 @@
"description": "Button label for creating a new access token."
},
"expires": {
"message": "Expires",
"message": "Expira a",
"description": "Label for the expiration date of an access token."
},
"canRead": {
@ -6144,11 +6144,11 @@
"description": "Label for the access level of an access token (Read only)."
},
"accessTokensNoItemsTitle": {
"message": "No access tokens to show",
"message": "Sem tokens de acesso para mostrar",
"description": "Title to be displayed when there are no access tokens to display in the list."
},
"accessTokensNoItemsDesc": {
"message": "To get started, create an access token",
"message": "Para começar, crie um token de acesso",
"description": "Message to be displayed when there are no access tokens to display in the list."
},
"downloadAccessToken": {
@ -6156,7 +6156,7 @@
"description": "Message to be displayed before closing an access token, reminding the user to download or copy it."
},
"expiresOnAccessToken": {
"message": "Expires on:",
"message": "Expira a:",
"description": "Label for the expiration date of an access token."
},
"accessTokenCallOutTitle": {
@ -6164,15 +6164,15 @@
"description": "Notification to inform the user that access tokens are only displayed once and cannot be retrieved again."
},
"copyToken": {
"message": "Copy token",
"message": "Copiar token",
"description": "Copies the generated access token to the user's clipboard."
},
"accessToken": {
"message": "Access token",
"message": "Token de acesso",
"description": "A unique string that gives a client application (eg. CLI) access to a secret or set of secrets."
},
"accessTokenExpirationRequired": {
"message": "Expiration date required",
"message": "Data de validade necessária",
"description": "Error message indicating that an expiration date for the access token must be set."
},
"accessTokenCreatedAndCopied": {
@ -6226,7 +6226,7 @@
}
},
"groupInfo": {
"message": "Group info"
"message": "Informações do grupo"
},
"editGroupMembersDesc": {
"message": "Grant members access to the group's assigned collections."
@ -6241,22 +6241,22 @@
"message": "If checked, this will replace all other collection permissions."
},
"selectMembers": {
"message": "Select members"
"message": "Selecionar membros"
},
"selectCollections": {
"message": "Select collections"
"message": "Selecionar coleções"
},
"role": {
"message": "Função"
},
"removeMember": {
"message": "Remove member"
"message": "Remover membro"
},
"collection": {
"message": "Collection"
"message": "Coleção"
},
"noCollection": {
"message": "No collection"
"message": "Sem coleções"
},
"canView": {
"message": "Pode ver"
@ -6271,7 +6271,7 @@
"message": "Pode editar, excepto palavras-passe"
},
"noCollectionsAdded": {
"message": "No collections added"
"message": "Sem coleções adicionadas"
},
"noMembersAdded": {
"message": "No members added"
@ -6364,28 +6364,28 @@
}
},
"domainStatusVerified": {
"message": "Verified"
"message": "Verificado"
},
"domainStatusUnverified": {
"message": "Unverified"
"message": "Não verificado"
},
"domainNameTh": {
"message": "Name"
"message": "Nome"
},
"domainStatusTh": {
"message": "Status"
"message": "Estado"
},
"lastChecked": {
"message": "Last checked"
},
"editDomain": {
"message": "Edit domain"
"message": "Editar domínio"
},
"domainFormInvalid": {
"message": "There are form errors that need your attention"
},
"addedDomain": {
"message": "Added domain $DOMAIN$",
"message": "Domínio $DOMAIN$ adicionado",
"placeholders": {
"DOMAIN": {
"content": "$1",
@ -6421,16 +6421,16 @@
}
},
"membersColumnHeader": {
"message": "Member/Group"
"message": "Membro/Grupo"
},
"groupAndMemberColumnHeader": {
"message": "Member"
"message": "Membro"
},
"selectGroupsAndMembers": {
"message": "Select groups and members"
"message": "Selecionar grupos e membros"
},
"selectGroups": {
"message": "Select groups"
"message": "Selecionar grupos"
},
"userPermissionOverrideHelper": {
"message": "Permissions set for a member will replace permissions set by that member's group"
@ -6439,13 +6439,13 @@
"message": "No members or groups added"
},
"deleted": {
"message": "Deleted"
"message": "Eliminado"
},
"memberStatusFilter": {
"message": "Member status filter"
},
"inviteMember": {
"message": "Invite member"
"message": "Convidar membro"
},
"needsConfirmation": {
"message": "Needs confirmation"
@ -6496,10 +6496,10 @@
}
},
"server": {
"message": "Server"
"message": "Servidor"
},
"exportData": {
"message": "Export data"
"message": "Exportar dados"
},
"exportingOrganizationSecretDataTitle": {
"message": "Exporting Organization Secret Data"
@ -6619,13 +6619,13 @@
"message": "This user can access the Secrets Manager Beta"
},
"important": {
"message": "Important:"
"message": "Importante:"
},
"viewAll": {
"message": "View all"
"message": "Ver tudo"
},
"showingPortionOfTotal": {
"message": "Showing $PORTION$ of $TOTAL$",
"message": "A mostrar $PORTION$ de $TOTAL$",
"placeholders": {
"portion": {
"content": "$1",
@ -6638,16 +6638,16 @@
}
},
"resolveTheErrorsBelowAndTryAgain": {
"message": "Resolve the errors below and try again."
"message": "Resolva os erros abaixo e tente novamente."
},
"description": {
"message": "Description"
"message": "Descrição"
},
"errorReadingImportFile": {
"message": "An error occurred when trying to read the import file"
"message": "Ocorreu um erro ao tentar ler o ficheiro de importação"
},
"accessedSecret": {
"message": "Accessed secret $SECRET_ID$.",
"message": "Segredo $SECRET_ID$ acedido.",
"placeholders": {
"secret_id": {
"content": "$1",
@ -6660,29 +6660,29 @@
"description": "Software Development Kit"
},
"createSecret": {
"message": "Create a secret"
"message": "Criar um segredo"
},
"createProject": {
"message": "Create a project"
"message": "Criar um projeto"
},
"createServiceAccount": {
"message": "Create a service account"
"message": "Criar uma conta de serviço"
},
"downloadThe": {
"message": "Download the",
"message": "Descarregar o",
"description": "Link to a downloadable resource. This will be used as part of a larger phrase. Example: Download the Secrets Manager CLI"
},
"smCLI": {
"message": "Secrets Manager CLI"
"message": "gestor de segredos CLI"
},
"importSecrets": {
"message": "Import secrets"
"message": "Importar segredos"
},
"getStarted": {
"message": "Get started"
"message": "Começar"
},
"complete": {
"message": "$COMPLETED$/$TOTAL$ Complete",
"message": "$COMPLETED$/$TOTAL$ concluídos",
"placeholders": {
"COMPLETED": {
"content": "$1",
@ -6695,25 +6695,25 @@
}
},
"restoreSecret": {
"message": "Restore secret"
"message": "Restaurar segredo"
},
"restoreSecrets": {
"message": "Restore secrets"
"message": "Restaurar segredos"
},
"restoreSecretPrompt": {
"message": "Are you sure you want to restore this secret?"
"message": "Tem a certeza de que quer recuperar este segredo?"
},
"restoreSecretsPrompt": {
"message": "Are you sure you want to restore these secrets?"
"message": "Tem a certeza de que quer recuperar estes segredos?"
},
"secretRestoredSuccessToast": {
"message": "Secret restored"
"message": "Segredo restaurado"
},
"secretsRestoredSuccessToast": {
"message": "Secrets restored"
"message": "Segredos restaurados"
},
"selectionIsRequired": {
"message": "Selection is required."
"message": "É necessária uma seleção."
},
"secretsManagerSubscriptionDesc": {
"message": "Turn on organization access to the Secrets Manager at no charge during the Beta program. Users can be granted access to the Beta in Members."
@ -6740,25 +6740,25 @@
"message": "This action will remove your access to the service account."
},
"removeAccess": {
"message": "Remove access"
"message": "Remover acesso"
},
"checkForBreaches": {
"message": "Check known data breaches for this password"
"message": "Verificar violações de dados conhecidas para esta palavra-passe"
},
"exposedMasterPassword": {
"message": "Exposed Master Password"
"message": "Palavra-passe mestra exposta"
},
"exposedMasterPasswordDesc": {
"message": "Password found in a data breach. Use a unique password to protect your account. Are you sure you want to use an exposed password?"
"message": "Palavra-passe encontrada numa violação de dados. Utilize uma palavra-passe única para proteger a sua conta. Tem a certeza de que pretende utilizar uma palavra-passe exposta?"
},
"weakAndExposedMasterPassword": {
"message": "Weak and Exposed Master Password"
"message": "Palavra-passe mestra fraca e exposta"
},
"weakAndBreachedMasterPasswordDesc": {
"message": "Weak password identified and found in a data breach. Use a strong and unique password to protect your account. Are you sure you want to use this password?"
"message": "Palavra-passe fraca identificada e encontrada numa violação de dados. Utilize uma palavra-passe forte e única para proteger a sua conta. Tem a certeza de que pretende utilizar esta palavra-passe?"
},
"characterMinimum": {
"message": "$LENGTH$ character minimum",
"message": "$LENGTH$ caracteres no mínimo",
"placeholders": {
"length": {
"content": "$1",
@ -6783,14 +6783,14 @@
"message": "Dispensar"
},
"notAvailableForFreeOrganization": {
"message": "This feature is not available for free organizations. Contact your organization owner to upgrade."
"message": "Esta funcionalidade não está disponível para organizações gratuitas. Contacte o proprietário da organização para atualizar."
},
"smProjectSecretsNoItemsNoAccess": {
"message": "Contact your organization's admin to manage secrets for this project.",
"message": "Contacte o administrador da sua organização para gerir os segredos deste projeto.",
"description": "The message shown to the user under a project's secrets tab when the user only has read access to the project."
},
"enforceOnLoginDesc": {
"message": "Require existing members to change their passwords"
"message": "Exigir que os membros existentes alterem as suas palavras-passe"
},
"region": {
"message": "Região"
@ -6804,18 +6804,18 @@
"description": "United States"
},
"smProjectDeleteAccessRestricted": {
"message": "You don't have permissions to delete this project",
"message": "Não tem permissões para eliminar este projeto",
"description": "The individual description shown to the user when the user doesn't have access to delete a project."
},
"smProjectsDeleteBulkConfirmation": {
"message": "The following projects can not be deleted. Would you like to continue?",
"message": "Os seguintes projetos não podem ser eliminados. Gostaria de continuar?",
"description": "The message shown to the user when bulk deleting projects and the user doesn't have access to some projects."
},
"updateKdfSettings": {
"message": "Update KDF settings"
"message": "Atualizar definições KDF"
},
"trustedDeviceEncryption": {
"message": "Trusted device encryption"
"message": "Encriptação de dispositivo de confiança"
},
"memberDecryptionTdeDescriptionStart": {
"message": "Uma vez autenticados, os membros desencriptam os dados do cofre utilizando uma chave armazenada no seu dispositivo. A",

View File

@ -2,7 +2,7 @@ export class AdminAuthRequestUpdateRequest {
/**
*
* @param requestApproved - Whether the request was approved/denied. If true, the key must be provided.
* @param encryptedUserKey The user's symmetric key that has been encrypted with a device public key if the request was approved.
* @param encryptedUserKey The user key that has been encrypted with a device public key if the request was approved.
*/
constructor(public requestApproved: boolean, public encryptedUserKey?: string) {}
}

View File

@ -65,16 +65,16 @@ export class DeviceApprovalsComponent implements OnInit, OnDestroy {
}
/**
* Creates a copy of the user's symmetric key that has been encrypted with the provided device's public key.
* Creates a copy of the user key that has been encrypted with the provided device's public key.
* @param devicePublicKey
* @param resetPasswordDetails
* @private
*/
private async getEncryptedUserSymKey(
private async getEncryptedUserKey(
devicePublicKey: string,
resetPasswordDetails: OrganizationUserResetPasswordDetailsResponse
): Promise<EncString> {
const encryptedUserSymKey = resetPasswordDetails.resetPasswordKey;
const encryptedUserKey = resetPasswordDetails.resetPasswordKey;
const encryptedOrgPrivateKey = resetPasswordDetails.encryptedPrivateKey;
const devicePubKey = Utils.fromB64ToArray(devicePublicKey);
@ -85,12 +85,12 @@ export class DeviceApprovalsComponent implements OnInit, OnDestroy {
orgSymKey
);
// Decrypt User's symmetric key with decrypted org private key
const decValue = await this.cryptoService.rsaDecrypt(encryptedUserSymKey, decOrgPrivateKey);
const userSymKey = new SymmetricCryptoKey(decValue);
// Decrypt user key with decrypted org private key
const decValue = await this.cryptoService.rsaDecrypt(encryptedUserKey, decOrgPrivateKey);
const userKey = new SymmetricCryptoKey(decValue);
// Re-encrypt User's Symmetric Key with the Device Public Key
return await this.cryptoService.rsaEncrypt(userSymKey.key, devicePubKey.buffer);
// Re-encrypt user Key with the Device Public Key
return await this.cryptoService.rsaEncrypt(userKey.key, devicePubKey.buffer);
}
async approveRequest(authRequest: PendingAuthRequestView) {
@ -110,7 +110,7 @@ export class DeviceApprovalsComponent implements OnInit, OnDestroy {
return;
}
const encryptedKey = await this.getEncryptedUserSymKey(authRequest.publicKey, details);
const encryptedKey = await this.getEncryptedUserKey(authRequest.publicKey, details);
await this.organizationAuthRequestService.approvePendingRequest(
this.organizationId,

View File

@ -3,6 +3,7 @@ import { Component, Input } from "@angular/core";
import { ProviderUserStatusType } from "@bitwarden/common/admin-console/enums";
import { ProviderUserBulkConfirmRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-user-bulk-confirm.request";
import { ProviderUserBulkRequest } from "@bitwarden/common/admin-console/models/request/provider/provider-user-bulk.request";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { BulkConfirmComponent as OrganizationBulkConfirmComponent } from "@bitwarden/web-vault/app/admin-console/organizations/members/components/bulk/bulk-confirm.component";
import { BulkUserDetails } from "@bitwarden/web-vault/app/admin-console/organizations/members/components/bulk/bulk-status.component";
@ -13,20 +14,20 @@ import { BulkUserDetails } from "@bitwarden/web-vault/app/admin-console/organiza
export class BulkConfirmComponent extends OrganizationBulkConfirmComponent {
@Input() providerId: string;
protected isAccepted(user: BulkUserDetails) {
protected override isAccepted(user: BulkUserDetails) {
return user.status === ProviderUserStatusType.Accepted;
}
protected async getPublicKeys() {
protected override async getPublicKeys() {
const request = new ProviderUserBulkRequest(this.filteredUsers.map((user) => user.id));
return await this.apiService.postProviderUsersPublicKey(this.providerId, request);
}
protected getCryptoKey() {
protected override getCryptoKey(): Promise<SymmetricCryptoKey> {
return this.cryptoService.getProviderKey(this.providerId);
}
protected async postConfirmRequest(userIdsWithKeys: any[]) {
protected override async postConfirmRequest(userIdsWithKeys: any[]) {
const request = new ProviderUserBulkConfirmRequest(userIdsWithKeys);
return await this.apiService.postProviderUserBulkConfirm(this.providerId, request);
}

View File

@ -12,7 +12,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { MasterKey, UserKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { DialogServiceAbstraction, SimpleDialogType } from "../../services/dialog";
@ -79,23 +79,28 @@ export class ChangePasswordComponent implements OnInit, OnDestroy {
if (this.kdfConfig == null) {
this.kdfConfig = await this.stateService.getKdfConfig();
}
const key = await this.cryptoService.makeKey(
// Create new master key
const newMasterKey = await this.cryptoService.makeMasterKey(
this.masterPassword,
email.trim().toLowerCase(),
this.kdf,
this.kdfConfig
);
const masterPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, key);
const newMasterPasswordHash = await this.cryptoService.hashPassword(
this.masterPassword,
newMasterKey
);
let encKey: [SymmetricCryptoKey, EncString] = null;
const existingEncKey = await this.cryptoService.getEncKey();
if (existingEncKey == null) {
encKey = await this.cryptoService.makeEncKey(key);
let newProtectedUserKey: [UserKey, EncString] = null;
const userKey = await this.cryptoService.getUserKeyFromMemory();
if (userKey == null) {
newProtectedUserKey = await this.cryptoService.makeUserKey(newMasterKey);
} else {
encKey = await this.cryptoService.remakeEncKey(key);
newProtectedUserKey = await this.cryptoService.encryptUserKeyWithMasterKey(newMasterKey);
}
await this.performSubmitActions(masterPasswordHash, key, encKey);
await this.performSubmitActions(newMasterPasswordHash, newMasterKey, newProtectedUserKey);
}
async setupSubmitActions(): Promise<boolean> {
@ -106,8 +111,8 @@ export class ChangePasswordComponent implements OnInit, OnDestroy {
async performSubmitActions(
masterPasswordHash: string,
key: SymmetricCryptoKey,
encKey: [SymmetricCryptoKey, EncString]
masterKey: MasterKey,
userKey: [UserKey, EncString]
) {
// Override in sub-class
}

View File

@ -11,9 +11,10 @@ import { InternalPolicyService } from "@bitwarden/common/admin-console/abstracti
import { MasterPasswordPolicyOptions } from "@bitwarden/common/admin-console/models/domain/master-password-policy-options";
import { KeyConnectorService } from "@bitwarden/common/auth/abstractions/key-connector.service";
import { ForceResetPasswordReason } from "@bitwarden/common/auth/models/domain/force-reset-password-reason";
import { KdfConfig } from "@bitwarden/common/auth/models/domain/kdf-config";
import { SecretVerificationRequest } from "@bitwarden/common/auth/models/request/secret-verification.request";
import { MasterPasswordPolicyResponse } from "@bitwarden/common/auth/models/response/master-password-policy.response";
import { HashPurpose, KeySuffixOptions } from "@bitwarden/common/enums";
import { HashPurpose, KdfType, KeySuffixOptions } from "@bitwarden/common/enums";
import { CryptoService } from "@bitwarden/common/platform/abstractions/crypto.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
@ -23,7 +24,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { UserKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { PasswordStrengthServiceAbstraction } from "@bitwarden/common/tools/password-strength";
import { DialogServiceAbstraction, SimpleDialogType } from "../../services/dialog";
@ -115,13 +116,13 @@ export class LockComponent implements OnInit, OnDestroy {
return;
}
const success = (await this.cryptoService.getKey(KeySuffixOptions.Biometric)) != null;
const userKey = await this.cryptoService.getUserKeyFromStorage(KeySuffixOptions.Biometric);
if (success) {
await this.doContinue(false);
if (userKey) {
await this.setKeyAndContinue(userKey, false);
}
return success;
return !!userKey;
}
togglePassword() {
@ -152,25 +153,41 @@ export class LockComponent implements OnInit, OnDestroy {
try {
const kdf = await this.stateService.getKdfType();
const kdfConfig = await this.stateService.getKdfConfig();
let userKeyPin: EncString;
let oldPinProtected: EncString;
if (this.pinSet[0]) {
const key = await this.cryptoService.makeKeyFromPin(
// MP on restart enabled
userKeyPin = await this.stateService.getUserKeyPinEphemeral();
oldPinProtected = await this.stateService.getDecryptedPinProtected();
} else {
// MP on restart disabled
userKeyPin = await this.stateService.getUserKeyPin();
const oldEncryptedKey = await this.stateService.getEncryptedPinProtected();
oldPinProtected = oldEncryptedKey ? new EncString(oldEncryptedKey) : undefined;
}
let userKey: UserKey;
if (oldPinProtected) {
userKey = await this.decryptAndMigrateOldPinKey(true, kdf, kdfConfig, oldPinProtected);
} else {
userKey = await this.cryptoService.decryptUserKeyWithPin(
this.pin,
this.email,
kdf,
kdfConfig,
await this.stateService.getDecryptedPinProtected()
userKeyPin
);
const encKey = await this.cryptoService.getEncKey(key);
const protectedPin = await this.stateService.getProtectedPin();
const decPin = await this.cryptoService.decryptToUtf8(new EncString(protectedPin), encKey);
failed = decPin !== this.pin;
if (!failed) {
await this.setKeyAndContinue(key);
}
} else {
const key = await this.cryptoService.makeKeyFromPin(this.pin, this.email, kdf, kdfConfig);
failed = false;
await this.setKeyAndContinue(key);
}
const protectedPin = await this.stateService.getProtectedPin();
const decryptedPin = await this.cryptoService.decryptToUtf8(
new EncString(protectedPin),
userKey
);
failed = decryptedPin !== this.pin;
if (!failed) {
await this.setKeyAndContinue(userKey);
}
} catch {
failed = true;
@ -206,18 +223,28 @@ export class LockComponent implements OnInit, OnDestroy {
const kdf = await this.stateService.getKdfType();
const kdfConfig = await this.stateService.getKdfConfig();
const key = await this.cryptoService.makeKey(this.masterPassword, this.email, kdf, kdfConfig);
const masterKey = await this.cryptoService.makeMasterKey(
this.masterPassword,
this.email,
kdf,
kdfConfig
);
const storedKeyHash = await this.cryptoService.getKeyHash();
let passwordValid = false;
if (storedKeyHash != null) {
passwordValid = await this.cryptoService.compareAndUpdateKeyHash(this.masterPassword, key);
// Offline unlock possible
passwordValid = await this.cryptoService.compareAndUpdateKeyHash(
this.masterPassword,
masterKey
);
} else {
// Online only
const request = new SecretVerificationRequest();
const serverKeyHash = await this.cryptoService.hashPassword(
this.masterPassword,
key,
masterKey,
HashPurpose.ServerAuthorization
);
request.masterPasswordHash = serverKeyHash;
@ -228,12 +255,14 @@ export class LockComponent implements OnInit, OnDestroy {
passwordValid = true;
const localKeyHash = await this.cryptoService.hashPassword(
this.masterPassword,
key,
masterKey,
HashPurpose.LocalAuthorization
);
await this.cryptoService.setKeyHash(localKeyHash);
} catch (e) {
this.logService.error(e);
} finally {
this.formPromise = null;
}
}
@ -246,19 +275,23 @@ export class LockComponent implements OnInit, OnDestroy {
return;
}
const userKey = await this.cryptoService.decryptUserKeyWithMasterKey(masterKey);
// if MP on restart is enabled, use it to get the PIN and store the ephemeral
// pin protected user key
if (this.pinSet[0]) {
const protectedPin = await this.stateService.getProtectedPin();
const encKey = await this.cryptoService.getEncKey(key);
const decPin = await this.cryptoService.decryptToUtf8(new EncString(protectedPin), encKey);
const pinKey = await this.cryptoService.makePinKey(decPin, this.email, kdf, kdfConfig);
await this.stateService.setDecryptedPinProtected(
await this.cryptoService.encrypt(key.key, pinKey)
const pin = await this.cryptoService.decryptToUtf8(new EncString(protectedPin), userKey);
const pinKey = await this.cryptoService.makePinKey(pin, this.email, kdf, kdfConfig);
await this.stateService.setUserKeyPinEphemeral(
await this.cryptoService.encrypt(userKey.key, pinKey)
);
}
await this.setKeyAndContinue(key, true);
await this.setKeyAndContinue(userKey, true);
}
private async setKeyAndContinue(key: SymmetricCryptoKey, evaluatePasswordAfterUnlock = false) {
await this.cryptoService.setKey(key);
private async setKeyAndContinue(key: UserKey, evaluatePasswordAfterUnlock = false) {
await this.cryptoService.setUserKey(key);
await this.doContinue(evaluatePasswordAfterUnlock);
}
@ -297,13 +330,15 @@ export class LockComponent implements OnInit, OnDestroy {
private async load() {
this.pinSet = await this.vaultTimeoutSettingsService.isPinLockSet();
this.pinLock =
(this.pinSet[0] && (await this.stateService.getDecryptedPinProtected()) != null) ||
this.pinSet[1];
let ephemeralPinSet = await this.stateService.getUserKeyPinEphemeral();
ephemeralPinSet ||= await this.stateService.getDecryptedPinProtected();
this.pinLock = (this.pinSet[0] && !!ephemeralPinSet) || this.pinSet[1];
this.supportsBiometric = await this.platformUtilsService.supportsBiometric();
this.biometricLock =
(await this.vaultTimeoutSettingsService.isBiometricLockSet()) &&
((await this.cryptoService.hasKeyStored(KeySuffixOptions.Biometric)) ||
((await this.cryptoService.hasUserKeyStored(KeySuffixOptions.Biometric)) ||
!this.platformUtilsService.supportsSecureStorage());
this.biometricText = await this.stateService.getBiometricText();
this.email = await this.stateService.getEmail();
@ -347,4 +382,53 @@ export class LockComponent implements OnInit, OnDestroy {
this.enforcedMasterPasswordOptions
);
}
/**
* Creates a new Pin key that encrypts the user key instead of the
* master key. Clears the old Pin key from state.
* @param masterPasswordOnRestart True if Master Password on Restart is enabled
* @param kdf User's KdfType
* @param kdfConfig User's KdfConfig
* @param oldPinProtected The old Pin key from state (retrieved from different
* places depending on if Master Password on Restart was enabled)
* @returns The user key
*/
private async decryptAndMigrateOldPinKey(
masterPasswordOnRestart: boolean,
kdf: KdfType,
kdfConfig: KdfConfig,
oldPinProtected?: EncString
): Promise<UserKey> {
// Decrypt
const masterKey = await this.cryptoService.decryptMasterKeyWithPin(
this.pin,
this.email,
kdf,
kdfConfig,
oldPinProtected
);
const encUserKey = await this.stateService.getEncryptedCryptoSymmetricKey();
const userKey = await this.cryptoService.decryptUserKeyWithMasterKey(
masterKey,
new EncString(encUserKey)
);
// Migrate
const pinKey = await this.cryptoService.makePinKey(this.pin, this.email, kdf, kdfConfig);
const pinProtectedKey = await this.cryptoService.encrypt(userKey.key, pinKey);
if (masterPasswordOnRestart) {
await this.stateService.setDecryptedPinProtected(null);
await this.stateService.setUserKeyPinEphemeral(pinProtectedKey);
} else {
await this.stateService.setEncryptedPinProtected(null);
await this.stateService.setUserKeyPin(pinProtectedKey);
// We previously only set the protected pin if MP on Restart was enabled
// now we set it regardless
const encPin = await this.cryptoService.encrypt(this.pin, userKey);
await this.stateService.setProtectedPin(encPin.encryptedString);
}
// This also clears the old Biometrics key since the new Biometrics key will
// be created when the user key is set.
await this.stateService.setCryptoMasterKeyBiometric(null);
return userKey;
}
}

View File

@ -22,7 +22,10 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { ValidationService } from "@bitwarden/common/platform/abstractions/validation.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import {
MasterKey,
SymmetricCryptoKey,
} from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { CaptchaProtectedComponent } from "./captcha-protected.component";
@ -207,19 +210,22 @@ export class LoginWithDeviceComponent
requestId: string,
response: AuthRequestResponse
): Promise<PasswordlessLogInCredentials> {
const decKey = await this.cryptoService.rsaDecrypt(response.key, this.authRequestKeyPair[1]);
const decMasterKeyArray = await this.cryptoService.rsaDecrypt(
response.key,
this.authRequestKeyPair[1]
);
const decMasterPasswordHash = await this.cryptoService.rsaDecrypt(
response.masterPasswordHash,
this.authRequestKeyPair[1]
);
const key = new SymmetricCryptoKey(decKey);
const decMasterKey = new SymmetricCryptoKey(decMasterKeyArray) as MasterKey;
const localHashedPassword = Utils.fromBufferToUtf8(decMasterPasswordHash);
return new PasswordlessLogInCredentials(
this.email,
this.passwordlessRequest.accessCode,
requestId,
key,
decMasterKey,
localHashedPassword
);
}

View File

@ -14,7 +14,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { MasterKey, UserKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { Verification } from "@bitwarden/common/types/verification";
@ -96,8 +96,8 @@ export class UpdatePasswordComponent extends BaseChangePasswordComponent {
async performSubmitActions(
masterPasswordHash: string,
key: SymmetricCryptoKey,
encKey: [SymmetricCryptoKey, EncString]
masterKey: MasterKey,
userKey: [UserKey, EncString]
) {
try {
// Create Request
@ -107,7 +107,7 @@ export class UpdatePasswordComponent extends BaseChangePasswordComponent {
null
);
request.newMasterPasswordHash = masterPasswordHash;
request.key = encKey[1].encryptedString;
request.key = userKey[1].encryptedString;
// Update user's password
this.apiService.postPassword(request);

View File

@ -16,7 +16,7 @@ import { MessagingService } from "@bitwarden/common/platform/abstractions/messag
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { MasterKey, UserKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { Verification } from "@bitwarden/common/types/verification";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
@ -114,21 +114,27 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent {
try {
// Create new key and hash new password
const newKey = await this.cryptoService.makeKey(
const newMasterKey = await this.cryptoService.makeMasterKey(
this.masterPassword,
this.email.trim().toLowerCase(),
this.kdf,
this.kdfConfig
);
const newPasswordHash = await this.cryptoService.hashPassword(this.masterPassword, newKey);
const newPasswordHash = await this.cryptoService.hashPassword(
this.masterPassword,
newMasterKey
);
// Grab user's current enc key
const userEncKey = await this.cryptoService.getEncKey();
// Grab user key
const userKey = await this.cryptoService.getUserKeyFromMemory();
// Create new encKey for the User
const newEncKey = await this.cryptoService.remakeEncKey(newKey, userEncKey);
// Encrypt user key with new master key
const newProtectedUserKey = await this.cryptoService.encryptUserKeyWithMasterKey(
newMasterKey,
userKey
);
await this.performSubmitActions(newPasswordHash, newKey, newEncKey);
await this.performSubmitActions(newPasswordHash, newMasterKey, newProtectedUserKey);
} catch (e) {
this.logService.error(e);
}
@ -136,16 +142,16 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent {
async performSubmitActions(
masterPasswordHash: string,
key: SymmetricCryptoKey,
encKey: [SymmetricCryptoKey, EncString]
masterKey: MasterKey,
userKey: [UserKey, EncString]
) {
try {
switch (this.reason) {
case ForceResetPasswordReason.AdminForcePasswordReset:
this.formPromise = this.updateTempPassword(masterPasswordHash, encKey);
this.formPromise = this.updateTempPassword(masterPasswordHash, userKey);
break;
case ForceResetPasswordReason.WeakMasterPassword:
this.formPromise = this.updatePassword(masterPasswordHash, encKey);
this.formPromise = this.updatePassword(masterPasswordHash, userKey);
break;
}
@ -167,29 +173,23 @@ export class UpdateTempPasswordComponent extends BaseChangePasswordComponent {
this.logService.error(e);
}
}
private async updateTempPassword(
masterPasswordHash: string,
encKey: [SymmetricCryptoKey, EncString]
) {
private async updateTempPassword(masterPasswordHash: string, userKey: [UserKey, EncString]) {
const request = new UpdateTempPasswordRequest();
request.key = encKey[1].encryptedString;
request.key = userKey[1].encryptedString;
request.newMasterPasswordHash = masterPasswordHash;
request.masterPasswordHint = this.hint;
return this.apiService.putUpdateTempPassword(request);
}
private async updatePassword(
newMasterPasswordHash: string,
encKey: [SymmetricCryptoKey, EncString]
) {
private async updatePassword(newMasterPasswordHash: string, userKey: [UserKey, EncString]) {
const request = await this.userVerificationService.buildRequest(
this.verification,
PasswordRequest
);
request.masterPasswordHint = this.hint;
request.newMasterPasswordHash = newMasterPasswordHash;
request.key = encKey[1].encryptedString;
request.key = userKey[1].encryptedString;
return this.apiService.postPassword(request);
}

View File

@ -271,16 +271,16 @@ export class RegisterComponent extends CaptchaProtectedComponent implements OnIn
const hint = this.formGroup.value.hint;
const kdf = DEFAULT_KDF_TYPE;
const kdfConfig = DEFAULT_KDF_CONFIG;
const key = await this.cryptoService.makeKey(masterPassword, email, kdf, kdfConfig);
const encKey = await this.cryptoService.makeEncKey(key);
const key = await this.cryptoService.makeMasterKey(masterPassword, email, kdf, kdfConfig);
const newUserKey = await this.cryptoService.makeUserKey(key);
const hashedPassword = await this.cryptoService.hashPassword(masterPassword, key);
const keys = await this.cryptoService.makeKeyPair(encKey[0]);
const keys = await this.cryptoService.makeKeyPair(newUserKey[0]);
const request = new RegisterRequest(
email,
name,
hashedPassword,
hint,
encKey[1].encryptedString,
newUserKey[1].encryptedString,
this.referenceData,
this.captchaToken,
kdf,

View File

@ -18,7 +18,7 @@ import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/pl
import { StateService } from "@bitwarden/common/platform/abstractions/state.service";
import { Utils } from "@bitwarden/common/platform/misc/utils";
import { EncString } from "@bitwarden/common/platform/models/domain/enc-string";
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { MasterKey, UserKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
import { PasswordGenerationServiceAbstraction } from "@bitwarden/common/tools/generator/password";
import { SyncService } from "@bitwarden/common/vault/abstractions/sync/sync.service.abstraction";
@ -101,16 +101,16 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
async performSubmitActions(
masterPasswordHash: string,
key: SymmetricCryptoKey,
encKey: [SymmetricCryptoKey, EncString]
masterKey: MasterKey,
userKey: [UserKey, EncString]
) {
const keys = await this.cryptoService.makeKeyPair(encKey[0]);
const newKeyPair = await this.cryptoService.makeKeyPair(userKey[0]);
const request = new SetPasswordRequest(
masterPasswordHash,
encKey[1].encryptedString,
userKey[1].encryptedString,
this.hint,
this.identifier,
new KeysRequest(keys[0], keys[1].encryptedString),
new KeysRequest(newKeyPair[0], newKeyPair[1].encryptedString),
this.kdf,
this.kdfConfig.iterations,
this.kdfConfig.memory,
@ -121,7 +121,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
this.formPromise = this.apiService
.setPassword(request)
.then(async () => {
await this.onSetPasswordSuccess(key, encKey, keys);
await this.onSetPasswordSuccess(masterKey, userKey, newKeyPair);
return this.organizationApiService.getKeys(this.orgId);
})
.then(async (response) => {
@ -131,16 +131,16 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
const userId = await this.stateService.getUserId();
const publicKey = Utils.fromB64ToArray(response.publicKey);
// RSA Encrypt user's encKey.key with organization public key
const userEncKey = await this.cryptoService.getEncKey();
const encryptedKey = await this.cryptoService.rsaEncrypt(
userEncKey.key,
// RSA Encrypt user key with organization public key
const userKey = await this.cryptoService.getUserKeyFromMemory();
const encryptedUserKey = await this.cryptoService.rsaEncrypt(
userKey.key,
publicKey.buffer
);
const resetRequest = new OrganizationUserResetPasswordEnrollmentRequest();
resetRequest.masterPasswordHash = masterPasswordHash;
resetRequest.resetPasswordKey = encryptedKey.encryptedString;
resetRequest.resetPasswordKey = encryptedUserKey.encryptedString;
return this.organizationUserService.putOrganizationUserResetPasswordEnrollment(
this.orgId,
@ -150,7 +150,7 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
});
} else {
this.formPromise = this.apiService.setPassword(request).then(async () => {
await this.onSetPasswordSuccess(key, encKey, keys);
await this.onSetPasswordSuccess(masterKey, userKey, newKeyPair);
});
}
@ -172,19 +172,19 @@ export class SetPasswordComponent extends BaseChangePasswordComponent {
}
private async onSetPasswordSuccess(
key: SymmetricCryptoKey,
encKey: [SymmetricCryptoKey, EncString],
keys: [string, EncString]
masterKey: MasterKey,
userKey: [UserKey, EncString],
keyPair: [string, EncString]
) {
await this.stateService.setKdfType(this.kdf);
await this.stateService.setKdfConfig(this.kdfConfig);
await this.cryptoService.setKey(key);
await this.cryptoService.setEncKey(encKey[1].encryptedString);
await this.cryptoService.setEncPrivateKey(keys[1].encryptedString);
await this.cryptoService.setMasterKey(masterKey);
await this.cryptoService.setUserKey(userKey[0]);
await this.cryptoService.setPrivateKey(keyPair[1].encryptedString);
const localKeyHash = await this.cryptoService.hashPassword(
this.masterPassword,
key,
masterKey,
HashPurpose.LocalAuthorization
);
await this.cryptoService.setKeyHash(localKeyHash);

View File

@ -35,19 +35,22 @@ export class SetPinComponent implements OnInit {
this.modalRef.close(false);
}
const kdf = await this.stateService.getKdfType();
const kdfConfig = await this.stateService.getKdfConfig();
const email = await this.stateService.getEmail();
const pinKey = await this.cryptoService.makePinKey(this.pin, email, kdf, kdfConfig);
const key = await this.cryptoService.getKey();
const pinProtectedKey = await this.cryptoService.encrypt(key.key, pinKey);
const pinKey = await this.cryptoService.makePinKey(
this.pin,
await this.stateService.getEmail(),
await this.stateService.getKdfType(),
await this.stateService.getKdfConfig()
);
const userKey = await this.cryptoService.getUserKeyFromMemory();
const pinProtectedKey = await this.cryptoService.encrypt(userKey.key, pinKey);
const encPin = await this.cryptoService.encrypt(this.pin, userKey);
await this.stateService.setProtectedPin(encPin.encryptedString);
if (this.masterPassOnRestart) {
const encPin = await this.cryptoService.encrypt(this.pin);
await this.stateService.setProtectedPin(encPin.encryptedString);
await this.stateService.setDecryptedPinProtected(pinProtectedKey);
await this.stateService.setUserKeyPinEphemeral(pinProtectedKey);
} else {
await this.stateService.setEncryptedPinProtected(pinProtectedKey.encryptedString);
await this.stateService.setUserKeyPin(pinProtectedKey);
}
await this.cryptoService.clearOldPinKeys();
this.modalRef.close(true);
}

View File

@ -192,7 +192,7 @@ export class AttachmentsComponent implements OnInit {
this.cipherDomain = await this.loadCipher();
this.cipher = await this.cipherDomain.decrypt();
this.hasUpdatedKey = await this.cryptoService.hasEncKey();
this.hasUpdatedKey = await this.cryptoService.hasUserKey();
const canAccessPremium = await this.stateService.getCanAccessPremium();
this.canAccessAttachments = canAccessPremium || this.cipher.organizationId != null;

View File

@ -11,8 +11,8 @@ export abstract class DevicesApiServiceAbstraction {
updateTrustedDeviceKeys: (
deviceIdentifier: string,
devicePublicKeyEncryptedUserSymKey: string,
userSymKeyEncryptedDevicePublicKey: string,
devicePublicKeyEncryptedUserKey: string,
userKeyEncryptedDevicePublicKey: string,
deviceKeyEncryptedDevicePrivateKey: string
) => Promise<DeviceResponse>;
}

View File

@ -7,6 +7,10 @@ export abstract class VaultTimeoutSettingsService {
) => Promise<void>;
getVaultTimeout: (userId?: string) => Promise<number>;
getVaultTimeoutAction: (userId?: string) => Promise<VaultTimeoutAction>;
/**
* Has the user enabled unlock with Pin.
* @returns [Pin with MP on Restart enabled, Pin without MP on Restart enabled]
*/
isPinLockSet: () => Promise<[boolean, boolean]>;
isBiometricLockSet: () => Promise<boolean>;
clear: (userId?: string) => Promise<void>;

View File

@ -1,7 +1,7 @@
import { Observable } from "rxjs";
import { AuthRequestPushNotification } from "../../models/response/notification.response";
import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key";
import { MasterKey } from "../../platform/models/domain/symmetric-crypto-key";
import { AuthenticationStatus } from "../enums/authentication-status";
import { AuthResult } from "../models/domain/auth-result";
import {
@ -32,7 +32,7 @@ export abstract class AuthService {
captchaResponse: string
) => Promise<AuthResult>;
logOut: (callback: () => void) => void;
makePreloginKey: (masterPassword: string, email: string) => Promise<SymmetricCryptoKey>;
makePreloginKey: (masterPassword: string, email: string) => Promise<MasterKey>;
authingWithUserApiKey: () => boolean;
authingWithSso: () => boolean;
authingWithPassword: () => boolean;

View File

@ -2,7 +2,7 @@ import { Organization } from "../../admin-console/models/domain/organization";
import { IdentityTokenResponse } from "../models/response/identity-token.response";
export abstract class KeyConnectorService {
getAndSetKey: (url?: string) => Promise<void>;
getAndSetMasterKey: (url?: string) => Promise<void>;
getManagingOrganization: () => Promise<Organization>;
getUsesKeyConnector: () => Promise<boolean>;
migrateUser: () => Promise<void>;

View File

@ -11,10 +11,16 @@ import { StateService } from "../../platform/abstractions/state.service";
import { Utils } from "../../platform/misc/utils";
import { Account, AccountProfile, AccountTokens } from "../../platform/models/domain/account";
import { EncString } from "../../platform/models/domain/enc-string";
import {
MasterKey,
SymmetricCryptoKey,
UserKey,
} from "../../platform/models/domain/symmetric-crypto-key";
import {
PasswordStrengthService,
PasswordStrengthServiceAbstraction,
} from "../../tools/password-strength";
import { CsprngArray } from "../../types/csprng";
import { AuthService } from "../abstractions/auth.service";
import { TokenService } from "../abstractions/token.service";
import { TwoFactorService } from "../abstractions/two-factor.service";
@ -37,7 +43,7 @@ const masterPassword = "password";
const deviceId = Utils.newGuid();
const accessToken = "ACCESS_TOKEN";
const refreshToken = "REFRESH_TOKEN";
const encKey = "ENC_KEY";
const userKey = "USER_KEY";
const privateKey = "PRIVATE_KEY";
const captchaSiteKey = "CAPTCHA_SITE_KEY";
const kdf = 0;
@ -64,7 +70,7 @@ export function identityTokenResponseFactory(
ForcePasswordReset: false,
Kdf: kdf,
KdfIterations: kdfIterations,
Key: encKey,
Key: userKey,
PrivateKey: privateKey,
ResetMasterPassword: false,
access_token: accessToken,
@ -129,6 +135,20 @@ describe("LogInStrategy", () => {
});
describe("base class", () => {
const userKeyBytesLength = 64;
const masterKeyBytesLength = 64;
let userKey: UserKey;
let masterKey: MasterKey;
beforeEach(() => {
userKey = new SymmetricCryptoKey(
new Uint8Array(userKeyBytesLength).buffer as CsprngArray
) as UserKey;
masterKey = new SymmetricCryptoKey(
new Uint8Array(masterKeyBytesLength).buffer as CsprngArray
) as MasterKey;
});
it("sets the local environment after a successful login", async () => {
apiService.postIdentityToken.mockResolvedValue(identityTokenResponseFactory());
@ -156,8 +176,6 @@ describe("LogInStrategy", () => {
},
})
);
expect(cryptoService.setEncKey).toHaveBeenCalledWith(encKey);
expect(cryptoService.setEncPrivateKey).toHaveBeenCalledWith(privateKey);
expect(messagingService.send).toHaveBeenCalledWith("loggedIn");
});
@ -187,6 +205,8 @@ describe("LogInStrategy", () => {
});
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
cryptoService.getMasterKey.mockResolvedValue(masterKey);
cryptoService.decryptUserKeyWithMasterKey.mockResolvedValue(userKey);
const result = await passwordLogInStrategy.logIn(credentials);
@ -204,13 +224,15 @@ describe("LogInStrategy", () => {
cryptoService.makeKeyPair.mockResolvedValue(["PUBLIC_KEY", new EncString("PRIVATE_KEY")]);
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
cryptoService.getMasterKey.mockResolvedValue(masterKey);
cryptoService.decryptUserKeyWithMasterKey.mockResolvedValue(userKey);
await passwordLogInStrategy.logIn(credentials);
// User key must be set before the new RSA keypair is generated, otherwise we can't decrypt the EncKey
expect(cryptoService.setKey).toHaveBeenCalled();
// User symmetric key must be set before the new RSA keypair is generated
expect(cryptoService.setUserKey).toHaveBeenCalled();
expect(cryptoService.makeKeyPair).toHaveBeenCalled();
expect(cryptoService.setKey.mock.invocationCallOrder[0]).toBeLessThan(
expect(cryptoService.setUserKey.mock.invocationCallOrder[0]).toBeLessThan(
cryptoService.makeKeyPair.mock.invocationCallOrder[0]
);

View File

@ -58,9 +58,6 @@ export abstract class LogInStrategy {
| PasswordlessLogInCredentials
): Promise<AuthResult>;
// The user key comes from different sources depending on the login strategy
protected abstract setUserKey(response: IdentityTokenResponse): Promise<void>;
async logInTwoFactor(
twoFactor: TokenTwoFactorRequest,
captchaResponse: string = null
@ -144,28 +141,41 @@ export abstract class LogInStrategy {
result.forcePasswordReset = ForceResetPasswordReason.AdminForcePasswordReset;
}
// Must come before setting keys, user key needs email to update additional keys
await this.saveAccountInformation(response);
if (response.twoFactorToken != null) {
await this.tokenService.setTwoFactorToken(response);
}
await this.setMasterKey(response);
await this.setUserKey(response);
// Must come after the user Key is set, otherwise createKeyPairForOldAccount will fail
const newSsoUser = response.key == null;
if (!newSsoUser) {
await this.cryptoService.setEncKey(response.key);
await this.cryptoService.setEncPrivateKey(
response.privateKey ?? (await this.createKeyPairForOldAccount())
);
}
await this.setPrivateKey(response);
this.messagingService.send("loggedIn");
return result;
}
// The keys comes from different sources depending on the login strategy
protected abstract setMasterKey(response: IdentityTokenResponse): Promise<void>;
protected abstract setUserKey(response: IdentityTokenResponse): Promise<void>;
protected abstract setPrivateKey(response: IdentityTokenResponse): Promise<void>;
protected async createKeyPairForOldAccount() {
try {
const [publicKey, privateKey] = await this.cryptoService.makeKeyPair();
await this.apiService.postAccountKeys(new KeysRequest(publicKey, privateKey.encryptedString));
return privateKey.encryptedString;
} catch (e) {
this.logService.error(e);
}
}
private async processTwoFactorResponse(response: IdentityTwoFactorResponse): Promise<AuthResult> {
const result = new AuthResult();
result.twoFactorProviders = response.twoFactorProviders2;
@ -182,14 +192,4 @@ export abstract class LogInStrategy {
result.captchaSiteKey = response.siteKey;
return result;
}
private async createKeyPairForOldAccount() {
try {
const [publicKey, privateKey] = await this.cryptoService.makeKeyPair();
await this.apiService.postAccountKeys(new KeysRequest(publicKey, privateKey.encryptedString));
return privateKey.encryptedString;
} catch (e) {
this.logService.error(e);
}
}
}

View File

@ -10,17 +10,23 @@ import { MessagingService } from "../../platform/abstractions/messaging.service"
import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service";
import { StateService } from "../../platform/abstractions/state.service";
import { Utils } from "../../platform/misc/utils";
import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key";
import {
MasterKey,
SymmetricCryptoKey,
UserKey,
} from "../../platform/models/domain/symmetric-crypto-key";
import {
PasswordStrengthService,
PasswordStrengthServiceAbstraction,
} from "../../tools/password-strength";
import { CsprngArray } from "../../types/csprng";
import { AuthService } from "../abstractions/auth.service";
import { TokenService } from "../abstractions/token.service";
import { TwoFactorService } from "../abstractions/two-factor.service";
import { TwoFactorProviderType } from "../enums/two-factor-provider-type";
import { ForceResetPasswordReason } from "../models/domain/force-reset-password-reason";
import { PasswordLogInCredentials } from "../models/domain/log-in-credentials";
import { IdentityTokenResponse } from "../models/response/identity-token.response";
import { IdentityTwoFactorResponse } from "../models/response/identity-two-factor.response";
import { MasterPasswordPolicyResponse } from "../models/response/master-password-policy.response";
@ -31,11 +37,11 @@ const email = "hello@world.com";
const masterPassword = "password";
const hashedPassword = "HASHED_PASSWORD";
const localHashedPassword = "LOCAL_HASHED_PASSWORD";
const preloginKey = new SymmetricCryptoKey(
const masterKey = new SymmetricCryptoKey(
Utils.fromB64ToArray(
"N2KWjlLpfi5uHjv+YcfUKIpZ1l+W+6HRensmIqD+BFYBf6N/dvFpJfWwYnVBdgFCK2tJTAIMLhqzIQQEUmGFgg=="
)
);
) as MasterKey;
const deviceId = Utils.newGuid();
const masterPasswordPolicy = new MasterPasswordPolicyResponse({
EnforceOnLogin: true,
@ -58,6 +64,7 @@ describe("PasswordLogInStrategy", () => {
let passwordLogInStrategy: PasswordLogInStrategy;
let credentials: PasswordLogInCredentials;
let tokenResponse: IdentityTokenResponse;
beforeEach(async () => {
cryptoService = mock<CryptoService>();
@ -76,7 +83,7 @@ describe("PasswordLogInStrategy", () => {
appIdService.getAppId.mockResolvedValue(deviceId);
tokenService.decodeToken.mockResolvedValue({});
authService.makePreloginKey.mockResolvedValue(preloginKey);
authService.makePreloginKey.mockResolvedValue(masterKey);
cryptoService.hashPassword
.calledWith(masterPassword, expect.anything(), undefined)
@ -102,10 +109,9 @@ describe("PasswordLogInStrategy", () => {
authService
);
credentials = new PasswordLogInCredentials(email, masterPassword);
tokenResponse = identityTokenResponseFactory(masterPasswordPolicy);
apiService.postIdentityToken.mockResolvedValue(
identityTokenResponseFactory(masterPasswordPolicy)
);
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
});
it("sends master password credentials to the server", async () => {
@ -127,15 +133,23 @@ describe("PasswordLogInStrategy", () => {
);
});
it("sets the local environment after a successful login", async () => {
it("sets keys after a successful authentication", async () => {
const userKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as UserKey;
cryptoService.getMasterKey.mockResolvedValue(masterKey);
cryptoService.decryptUserKeyWithMasterKey.mockResolvedValue(userKey);
await passwordLogInStrategy.logIn(credentials);
expect(cryptoService.setKey).toHaveBeenCalledWith(preloginKey);
expect(cryptoService.setMasterKey).toHaveBeenCalledWith(masterKey);
expect(cryptoService.setKeyHash).toHaveBeenCalledWith(localHashedPassword);
expect(cryptoService.setUserKeyMasterKey).toHaveBeenCalledWith(tokenResponse.key);
expect(cryptoService.setUserKey).toHaveBeenCalledWith(userKey);
expect(cryptoService.setPrivateKey).toHaveBeenCalledWith(tokenResponse.privateKey);
});
it("does not force the user to update their master password when there are no requirements", async () => {
apiService.postIdentityToken.mockResolvedValueOnce(identityTokenResponseFactory(null));
apiService.postIdentityToken.mockResolvedValueOnce(identityTokenResponseFactory());
const result = await passwordLogInStrategy.logIn(credentials);

View File

@ -8,7 +8,7 @@ import { LogService } from "../../platform/abstractions/log.service";
import { MessagingService } from "../../platform/abstractions/messaging.service";
import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service";
import { StateService } from "../../platform/abstractions/state.service";
import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key";
import { MasterKey } from "../../platform/models/domain/symmetric-crypto-key";
import { PasswordStrengthServiceAbstraction } from "../../tools/password-strength";
import { AuthService } from "../abstractions/auth.service";
import { TokenService } from "../abstractions/token.service";
@ -36,7 +36,7 @@ export class PasswordLogInStrategy extends LogInStrategy {
tokenRequest: PasswordTokenRequest;
private localHashedPassword: string;
private key: SymmetricCryptoKey;
private masterKey: MasterKey;
/**
* Options to track if the user needs to update their password due to a password that does not meet an organization's
@ -71,12 +71,7 @@ export class PasswordLogInStrategy extends LogInStrategy {
);
}
async setUserKey() {
await this.cryptoService.setKey(this.key);
await this.cryptoService.setKeyHash(this.localHashedPassword);
}
async logInTwoFactor(
override async logInTwoFactor(
twoFactor: TokenTwoFactorRequest,
captchaResponse: string
): Promise<AuthResult> {
@ -96,18 +91,18 @@ export class PasswordLogInStrategy extends LogInStrategy {
return result;
}
async logIn(credentials: PasswordLogInCredentials) {
override async logIn(credentials: PasswordLogInCredentials) {
const { email, masterPassword, captchaToken, twoFactor } = credentials;
this.key = await this.authService.makePreloginKey(masterPassword, email);
this.masterKey = await this.authService.makePreloginKey(masterPassword, email);
// Hash the password early (before authentication) so we don't persist it in memory in plaintext
this.localHashedPassword = await this.cryptoService.hashPassword(
masterPassword,
this.key,
this.masterKey,
HashPurpose.LocalAuthorization
);
const hashedPassword = await this.cryptoService.hashPassword(masterPassword, this.key);
const hashedPassword = await this.cryptoService.hashPassword(masterPassword, this.masterKey);
this.tokenRequest = new PasswordTokenRequest(
email,
@ -118,6 +113,7 @@ export class PasswordLogInStrategy extends LogInStrategy {
);
const [authResult, identityResponse] = await this.startLogIn();
const masterPasswordPolicyOptions =
this.getMasterPasswordPolicyOptionsFromResponse(identityResponse);
@ -145,6 +141,27 @@ export class PasswordLogInStrategy extends LogInStrategy {
return authResult;
}
protected override async setMasterKey(response: IdentityTokenResponse) {
await this.cryptoService.setMasterKey(this.masterKey);
await this.cryptoService.setKeyHash(this.localHashedPassword);
}
protected override async setUserKey(response: IdentityTokenResponse): Promise<void> {
await this.cryptoService.setUserKeyMasterKey(response.key);
const masterKey = await this.cryptoService.getMasterKey();
if (masterKey) {
const userKey = await this.cryptoService.decryptUserKeyWithMasterKey(masterKey);
await this.cryptoService.setUserKey(userKey);
}
}
protected override async setPrivateKey(response: IdentityTokenResponse): Promise<void> {
await this.cryptoService.setPrivateKey(
response.privateKey ?? (await this.createKeyPairForOldAccount())
);
}
private getMasterPasswordPolicyOptionsFromResponse(
response: IdentityTokenResponse | IdentityTwoFactorResponse | IdentityCaptchaResponse
): MasterPasswordPolicyOptions {

View File

@ -0,0 +1,101 @@
import { mock, MockProxy } from "jest-mock-extended";
import { ApiService } from "../../abstractions/api.service";
import { AppIdService } from "../../platform/abstractions/app-id.service";
import { CryptoService } from "../../platform/abstractions/crypto.service";
import { LogService } from "../../platform/abstractions/log.service";
import { MessagingService } from "../../platform/abstractions/messaging.service";
import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service";
import { StateService } from "../../platform/abstractions/state.service";
import { Utils } from "../../platform/misc/utils";
import {
MasterKey,
SymmetricCryptoKey,
UserKey,
} from "../../platform/models/domain/symmetric-crypto-key";
import { CsprngArray } from "../../types/csprng";
import { TokenService } from "../abstractions/token.service";
import { TwoFactorService } from "../abstractions/two-factor.service";
import { PasswordlessLogInCredentials } from "../models/domain/log-in-credentials";
import { IdentityTokenResponse } from "../models/response/identity-token.response";
import { identityTokenResponseFactory } from "./login.strategy.spec";
import { PasswordlessLogInStrategy } from "./passwordless-login.strategy";
describe("SsoLogInStrategy", () => {
let cryptoService: MockProxy<CryptoService>;
let apiService: MockProxy<ApiService>;
let tokenService: MockProxy<TokenService>;
let appIdService: MockProxy<AppIdService>;
let platformUtilsService: MockProxy<PlatformUtilsService>;
let messagingService: MockProxy<MessagingService>;
let logService: MockProxy<LogService>;
let stateService: MockProxy<StateService>;
let twoFactorService: MockProxy<TwoFactorService>;
let passwordlessLoginStrategy: PasswordlessLogInStrategy;
let credentials: PasswordlessLogInCredentials;
let tokenResponse: IdentityTokenResponse;
const deviceId = Utils.newGuid();
const email = "EMAIL";
const accessCode = "ACCESS_CODE";
const authRequestId = "AUTH_REQUEST_ID";
const decKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as MasterKey;
const localPasswordHash = "LOCAL_PASSWORD_HASH";
beforeEach(async () => {
cryptoService = mock<CryptoService>();
apiService = mock<ApiService>();
tokenService = mock<TokenService>();
appIdService = mock<AppIdService>();
platformUtilsService = mock<PlatformUtilsService>();
messagingService = mock<MessagingService>();
logService = mock<LogService>();
stateService = mock<StateService>();
twoFactorService = mock<TwoFactorService>();
tokenService.getTwoFactorToken.mockResolvedValue(null);
appIdService.getAppId.mockResolvedValue(deviceId);
tokenService.decodeToken.mockResolvedValue({});
passwordlessLoginStrategy = new PasswordlessLogInStrategy(
cryptoService,
apiService,
tokenService,
appIdService,
platformUtilsService,
messagingService,
logService,
stateService,
twoFactorService
);
credentials = new PasswordlessLogInCredentials(
email,
accessCode,
authRequestId,
decKey,
localPasswordHash
);
tokenResponse = identityTokenResponseFactory();
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
});
it("sets keys after a successful authentication", async () => {
const masterKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as MasterKey;
const userKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as UserKey;
cryptoService.getMasterKey.mockResolvedValue(masterKey);
cryptoService.decryptUserKeyWithMasterKey.mockResolvedValue(userKey);
await passwordlessLoginStrategy.logIn(credentials);
expect(cryptoService.setMasterKey).toHaveBeenCalledWith(masterKey);
expect(cryptoService.setKeyHash).toHaveBeenCalledWith(localPasswordHash);
expect(cryptoService.setUserKeyMasterKey).toHaveBeenCalledWith(tokenResponse.key);
expect(cryptoService.setUserKey).toHaveBeenCalledWith(userKey);
expect(cryptoService.setPrivateKey).toHaveBeenCalledWith(tokenResponse.privateKey);
});
});

View File

@ -5,13 +5,13 @@ import { LogService } from "../../platform/abstractions/log.service";
import { MessagingService } from "../../platform/abstractions/messaging.service";
import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service";
import { StateService } from "../../platform/abstractions/state.service";
import { AuthService } from "../abstractions/auth.service";
import { TokenService } from "../abstractions/token.service";
import { TwoFactorService } from "../abstractions/two-factor.service";
import { AuthResult } from "../models/domain/auth-result";
import { PasswordlessLogInCredentials } from "../models/domain/log-in-credentials";
import { PasswordTokenRequest } from "../models/request/identity-token/password-token.request";
import { TokenTwoFactorRequest } from "../models/request/identity-token/token-two-factor.request";
import { IdentityTokenResponse } from "../models/response/identity-token.response";
import { LogInStrategy } from "./login.strategy";
@ -40,8 +40,7 @@ export class PasswordlessLogInStrategy extends LogInStrategy {
messagingService: MessagingService,
logService: LogService,
stateService: StateService,
twoFactorService: TwoFactorService,
private authService: AuthService
twoFactorService: TwoFactorService
) {
super(
cryptoService,
@ -56,20 +55,7 @@ export class PasswordlessLogInStrategy extends LogInStrategy {
);
}
async setUserKey() {
await this.cryptoService.setKey(this.passwordlessCredentials.decKey);
await this.cryptoService.setKeyHash(this.passwordlessCredentials.localPasswordHash);
}
async logInTwoFactor(
twoFactor: TokenTwoFactorRequest,
captchaResponse: string
): Promise<AuthResult> {
this.tokenRequest.captchaResponse = captchaResponse ?? this.captchaBypassToken;
return super.logInTwoFactor(twoFactor);
}
async logIn(credentials: PasswordlessLogInCredentials) {
override async logIn(credentials: PasswordlessLogInCredentials) {
this.passwordlessCredentials = credentials;
this.tokenRequest = new PasswordTokenRequest(
@ -84,4 +70,33 @@ export class PasswordlessLogInStrategy extends LogInStrategy {
const [authResult] = await this.startLogIn();
return authResult;
}
override async logInTwoFactor(
twoFactor: TokenTwoFactorRequest,
captchaResponse: string
): Promise<AuthResult> {
this.tokenRequest.captchaResponse = captchaResponse ?? this.captchaBypassToken;
return super.logInTwoFactor(twoFactor);
}
protected override async setMasterKey(response: IdentityTokenResponse) {
await this.cryptoService.setMasterKey(this.passwordlessCredentials.decKey);
await this.cryptoService.setKeyHash(this.passwordlessCredentials.localPasswordHash);
}
protected override async setUserKey(response: IdentityTokenResponse): Promise<void> {
await this.cryptoService.setUserKeyMasterKey(response.key);
const masterKey = await this.cryptoService.getMasterKey();
if (masterKey) {
const userKey = await this.cryptoService.decryptUserKeyWithMasterKey(masterKey);
await this.cryptoService.setUserKey(userKey);
}
}
protected override async setPrivateKey(response: IdentityTokenResponse): Promise<void> {
await this.cryptoService.setPrivateKey(
response.privateKey ?? (await this.createKeyPairForOldAccount())
);
}
}

View File

@ -8,10 +8,17 @@ import { MessagingService } from "../../platform/abstractions/messaging.service"
import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service";
import { StateService } from "../../platform/abstractions/state.service";
import { Utils } from "../../platform/misc/utils";
import {
MasterKey,
SymmetricCryptoKey,
UserKey,
} from "../../platform/models/domain/symmetric-crypto-key";
import { CsprngArray } from "../../types/csprng";
import { KeyConnectorService } from "../abstractions/key-connector.service";
import { TokenService } from "../abstractions/token.service";
import { TwoFactorService } from "../abstractions/two-factor.service";
import { SsoLogInCredentials } from "../models/domain/log-in-credentials";
import { IdentityTokenResponse } from "../models/response/identity-token.response";
import { identityTokenResponseFactory } from "./login.strategy.spec";
import { SsoLogInStrategy } from "./sso-login.strategy";
@ -98,33 +105,58 @@ describe("SsoLogInStrategy", () => {
await ssoLogInStrategy.logIn(credentials);
expect(cryptoService.setEncPrivateKey).not.toHaveBeenCalled();
expect(cryptoService.setEncKey).not.toHaveBeenCalled();
expect(cryptoService.setMasterKey).not.toHaveBeenCalled();
expect(cryptoService.setUserKey).not.toHaveBeenCalled();
expect(cryptoService.setPrivateKey).not.toHaveBeenCalled();
});
it("gets and sets KeyConnector key for enrolled user", async () => {
const tokenResponse = identityTokenResponseFactory();
tokenResponse.keyConnectorUrl = keyConnectorUrl;
describe("Key Connector", () => {
let tokenResponse: IdentityTokenResponse;
beforeEach(() => {
tokenResponse = identityTokenResponseFactory();
tokenResponse.keyConnectorUrl = keyConnectorUrl;
});
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
it("gets and sets the master key if Key Connector is enabled", async () => {
const masterKey = new SymmetricCryptoKey(
new Uint8Array(64).buffer as CsprngArray
) as MasterKey;
await ssoLogInStrategy.logIn(credentials);
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
cryptoService.getMasterKey.mockResolvedValue(masterKey);
expect(keyConnectorService.getAndSetKey).toHaveBeenCalledWith(keyConnectorUrl);
});
await ssoLogInStrategy.logIn(credentials);
it("converts new SSO user to Key Connector on first login", async () => {
const tokenResponse = identityTokenResponseFactory();
tokenResponse.keyConnectorUrl = keyConnectorUrl;
tokenResponse.key = null;
expect(keyConnectorService.getAndSetMasterKey).toHaveBeenCalledWith(keyConnectorUrl);
});
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
it("converts new SSO user to Key Connector on first login", async () => {
tokenResponse.key = null;
await ssoLogInStrategy.logIn(credentials);
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
expect(keyConnectorService.convertNewSsoUserToKeyConnector).toHaveBeenCalledWith(
tokenResponse,
ssoOrgId
);
await ssoLogInStrategy.logIn(credentials);
expect(keyConnectorService.convertNewSsoUserToKeyConnector).toHaveBeenCalledWith(
tokenResponse,
ssoOrgId
);
});
it("decrypts and sets the user key if Key Connector is enabled", 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);
cryptoService.getMasterKey.mockResolvedValue(masterKey);
cryptoService.decryptUserKeyWithMasterKey.mockResolvedValue(userKey);
await ssoLogInStrategy.logIn(credentials);
expect(cryptoService.decryptUserKeyWithMasterKey).toHaveBeenCalledWith(masterKey);
expect(cryptoService.setUserKey).toHaveBeenCalledWith(userKey);
});
});
});

View File

@ -49,18 +49,6 @@ export class SsoLogInStrategy extends LogInStrategy {
);
}
async setUserKey(tokenResponse: IdentityTokenResponse) {
const newSsoUser = tokenResponse.key == null;
if (tokenResponse.keyConnectorUrl != null) {
if (!newSsoUser) {
await this.keyConnectorService.getAndSetKey(tokenResponse.keyConnectorUrl);
} else {
await this.keyConnectorService.convertNewSsoUserToKeyConnector(tokenResponse, this.orgId);
}
}
}
async logIn(credentials: SsoLogInCredentials) {
this.orgId = credentials.orgId;
this.tokenRequest = new SsoTokenRequest(
@ -78,4 +66,43 @@ export class SsoLogInStrategy extends LogInStrategy {
return ssoAuthResult;
}
protected override async setMasterKey(tokenResponse: IdentityTokenResponse) {
const newSsoUser = tokenResponse.key == null;
if (tokenResponse.keyConnectorUrl != null) {
if (!newSsoUser) {
await this.keyConnectorService.getAndSetMasterKey(tokenResponse.keyConnectorUrl);
} else {
await this.keyConnectorService.convertNewSsoUserToKeyConnector(tokenResponse, this.orgId);
}
}
}
protected override async setUserKey(tokenResponse: IdentityTokenResponse): Promise<void> {
const newSsoUser = tokenResponse.key == null;
if (!newSsoUser) {
await this.cryptoService.setUserKeyMasterKey(tokenResponse.key);
if (tokenResponse.keyConnectorUrl != null) {
const masterKey = await this.cryptoService.getMasterKey();
if (!masterKey) {
throw new Error("Master key not found");
}
const userKey = await this.cryptoService.decryptUserKeyWithMasterKey(masterKey);
await this.cryptoService.setUserKey(userKey);
}
}
}
protected override async setPrivateKey(tokenResponse: IdentityTokenResponse): Promise<void> {
const newSsoUser = tokenResponse.key == null;
if (!newSsoUser) {
await this.cryptoService.setPrivateKey(
tokenResponse.privateKey ?? (await this.createKeyPairForOldAccount())
);
}
}
}

View File

@ -9,6 +9,12 @@ import { MessagingService } from "../../platform/abstractions/messaging.service"
import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service";
import { StateService } from "../../platform/abstractions/state.service";
import { Utils } from "../../platform/misc/utils";
import {
MasterKey,
SymmetricCryptoKey,
UserKey,
} from "../../platform/models/domain/symmetric-crypto-key";
import { CsprngArray } from "../../types/csprng";
import { KeyConnectorService } from "../abstractions/key-connector.service";
import { TokenService } from "../abstractions/token.service";
import { TwoFactorService } from "../abstractions/two-factor.service";
@ -101,7 +107,18 @@ describe("UserApiLogInStrategy", () => {
expect(stateService.addAccount).toHaveBeenCalled();
});
it("gets and sets the Key Connector key from environmentUrl", async () => {
it("sets the encrypted user key and private key from the identity token response", async () => {
const tokenResponse = identityTokenResponseFactory();
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
await apiLogInStrategy.logIn(credentials);
expect(cryptoService.setUserKeyMasterKey).toHaveBeenCalledWith(tokenResponse.key);
expect(cryptoService.setPrivateKey).toHaveBeenCalledWith(tokenResponse.privateKey);
});
it("gets and sets the master key if Key Connector is enabled", async () => {
const tokenResponse = identityTokenResponseFactory();
tokenResponse.apiUseKeyConnector = true;
@ -110,6 +127,24 @@ describe("UserApiLogInStrategy", () => {
await apiLogInStrategy.logIn(credentials);
expect(keyConnectorService.getAndSetKey).toHaveBeenCalledWith(keyConnectorUrl);
expect(keyConnectorService.getAndSetMasterKey).toHaveBeenCalledWith(keyConnectorUrl);
});
it("decrypts and sets the user key if Key Connector is enabled", 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;
const tokenResponse = identityTokenResponseFactory();
tokenResponse.apiUseKeyConnector = true;
apiService.postIdentityToken.mockResolvedValue(tokenResponse);
environmentService.getKeyConnectorUrl.mockReturnValue(keyConnectorUrl);
cryptoService.getMasterKey.mockResolvedValue(masterKey);
cryptoService.decryptUserKeyWithMasterKey.mockResolvedValue(userKey);
await apiLogInStrategy.logIn(credentials);
expect(cryptoService.decryptUserKeyWithMasterKey).toHaveBeenCalledWith(masterKey);
expect(cryptoService.setUserKey).toHaveBeenCalledWith(userKey);
});
});

View File

@ -44,14 +44,7 @@ export class UserApiLogInStrategy extends LogInStrategy {
);
}
async setUserKey(tokenResponse: IdentityTokenResponse) {
if (tokenResponse.apiUseKeyConnector) {
const keyConnectorUrl = this.environmentService.getKeyConnectorUrl();
await this.keyConnectorService.getAndSetKey(keyConnectorUrl);
}
}
async logIn(credentials: UserApiLogInCredentials) {
override async logIn(credentials: UserApiLogInCredentials) {
this.tokenRequest = new UserApiTokenRequest(
credentials.clientId,
credentials.clientSecret,
@ -63,6 +56,31 @@ export class UserApiLogInStrategy extends LogInStrategy {
return authResult;
}
protected override async setMasterKey(response: IdentityTokenResponse) {
if (response.apiUseKeyConnector) {
const keyConnectorUrl = this.environmentService.getKeyConnectorUrl();
await this.keyConnectorService.getAndSetMasterKey(keyConnectorUrl);
}
}
protected override async setUserKey(response: IdentityTokenResponse): Promise<void> {
await this.cryptoService.setUserKeyMasterKey(response.key);
if (response.apiUseKeyConnector) {
const masterKey = await this.cryptoService.getMasterKey();
if (masterKey) {
const userKey = await this.cryptoService.decryptUserKeyWithMasterKey(masterKey);
await this.cryptoService.setUserKey(userKey);
}
}
}
protected override async setPrivateKey(response: IdentityTokenResponse): Promise<void> {
await this.cryptoService.setPrivateKey(
response.privateKey ?? (await this.createKeyPairForOldAccount())
);
}
protected async saveAccountInformation(tokenResponse: IdentityTokenResponse) {
await super.saveAccountInformation(tokenResponse);
await this.stateService.setApiKeyClientId(this.tokenRequest.clientId);

View File

@ -1,4 +1,4 @@
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
import { MasterKey } from "../../../platform/models/domain/symmetric-crypto-key";
import { AuthenticationType } from "../../enums/authentication-type";
import { TokenTwoFactorRequest } from "../request/identity-token/token-two-factor.request";
@ -38,7 +38,7 @@ export class PasswordlessLogInCredentials {
public email: string,
public accessCode: string,
public authRequestId: string,
public decKey: SymmetricCryptoKey,
public decKey: MasterKey,
public localPasswordHash: string,
public twoFactor?: TokenTwoFactorRequest
) {}

View File

@ -16,7 +16,7 @@ import { MessagingService } from "../../platform/abstractions/messaging.service"
import { PlatformUtilsService } from "../../platform/abstractions/platform-utils.service";
import { StateService } from "../../platform/abstractions/state.service";
import { Utils } from "../../platform/misc/utils";
import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key";
import { MasterKey } from "../../platform/models/domain/symmetric-crypto-key";
import { PasswordStrengthServiceAbstraction } from "../../tools/password-strength";
import { AuthService as AuthServiceAbstraction } from "../abstractions/auth.service";
import { KeyConnectorService } from "../abstractions/key-connector.service";
@ -177,8 +177,7 @@ export class AuthService implements AuthServiceAbstraction {
this.messagingService,
this.logService,
this.stateService,
this.twoFactorService,
this
this.twoFactorService
);
break;
}
@ -246,15 +245,15 @@ export class AuthService implements AuthServiceAbstraction {
// Keys aren't stored for a device that is locked or logged out
// Make sure we're logged in before checking this, otherwise we could mix up those states
const neverLock =
(await this.cryptoService.hasKeyStored(KeySuffixOptions.Auto, userId)) &&
(await this.cryptoService.hasUserKeyStored(KeySuffixOptions.Auto, userId)) &&
!(await this.stateService.getEverBeenUnlocked({ userId: userId }));
if (neverLock) {
// TODO: This also _sets_ the key so when we check memory in the next line it finds a key.
// We should refactor here.
await this.cryptoService.getKey(KeySuffixOptions.Auto, userId);
// Get the key from storage and set it in memory
const userKey = await this.cryptoService.getUserKeyFromStorage(KeySuffixOptions.Auto, userId);
await this.cryptoService.setUserKey(userKey);
}
const hasKeyInMemory = await this.cryptoService.hasKeyInMemory(userId);
const hasKeyInMemory = await this.cryptoService.hasUserKeyInMemory(userId);
if (!hasKeyInMemory) {
return AuthenticationStatus.Locked;
}
@ -262,7 +261,7 @@ export class AuthService implements AuthServiceAbstraction {
return AuthenticationStatus.Unlocked;
}
async makePreloginKey(masterPassword: string, email: string): Promise<SymmetricCryptoKey> {
async makePreloginKey(masterPassword: string, email: string): Promise<MasterKey> {
email = email.trim().toLowerCase();
let kdf: KdfType = null;
let kdfConfig: KdfConfig = null;
@ -281,7 +280,7 @@ export class AuthService implements AuthServiceAbstraction {
throw e;
}
}
return this.cryptoService.makeKey(masterPassword, email, kdf, kdfConfig);
return await this.cryptoService.makeMasterKey(masterPassword, email, kdf, kdfConfig);
}
async authResponsePushNotification(notification: AuthRequestPushNotification): Promise<any> {
@ -297,20 +296,24 @@ export class AuthService implements AuthServiceAbstraction {
key: string,
requestApproved: boolean
): Promise<AuthRequestResponse> {
// TODO: This currently depends on always having the Master Key and MP Hash
// We need to change this to using a different method (possibly server auth code + user key)
const pubKey = Utils.fromB64ToArray(key);
const encryptedKey = await this.cryptoService.rsaEncrypt(
(
await this.cryptoService.getKey()
).encKey,
pubKey.buffer
);
const encryptedMasterPassword = await this.cryptoService.rsaEncrypt(
Utils.fromUtf8ToArray(await this.stateService.getKeyHash()),
pubKey.buffer
);
const masterKey = await this.cryptoService.getMasterKey();
if (!masterKey) {
throw new Error("Master key not found");
}
const encryptedKey = await this.cryptoService.rsaEncrypt(masterKey.encKey, pubKey.buffer);
let encryptedMasterPasswordHash = null;
if ((await this.stateService.getKeyHash()) != null) {
encryptedMasterPasswordHash = await this.cryptoService.rsaEncrypt(
Utils.fromUtf8ToArray(await this.stateService.getKeyHash()),
pubKey.buffer
);
}
const request = new PasswordlessAuthRequest(
encryptedKey.encryptedString,
encryptedMasterPassword.encryptedString,
encryptedMasterPasswordHash.encryptedString,
await this.appIdService.getAppId(),
requestApproved
);

View File

@ -7,7 +7,7 @@ import { CryptoService } from "../../platform/abstractions/crypto.service";
import { LogService } from "../../platform/abstractions/log.service";
import { StateService } from "../../platform/abstractions/state.service";
import { Utils } from "../../platform/misc/utils";
import { SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key";
import { MasterKey, SymmetricCryptoKey } from "../../platform/models/domain/symmetric-crypto-key";
import { KeyConnectorService as KeyConnectorServiceAbstraction } from "../abstractions/key-connector.service";
import { TokenService } from "../abstractions/token.service";
import { KdfConfig } from "../models/domain/kdf-config";
@ -45,8 +45,8 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction {
async migrateUser() {
const organization = await this.getManagingOrganization();
const key = await this.cryptoService.getKey();
const keyConnectorRequest = new KeyConnectorUserKeyRequest(key.encKeyB64);
const masterKey = await this.cryptoService.getMasterKey();
const keyConnectorRequest = new KeyConnectorUserKeyRequest(masterKey.encKeyB64);
try {
await this.apiService.postUserKeyToKeyConnector(
@ -60,12 +60,13 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction {
await this.apiService.postConvertToKeyConnector();
}
async getAndSetKey(url: string) {
// TODO: UserKey should be renamed to MasterKey and typed accordingly
async getAndSetMasterKey(url: string) {
try {
const userKeyResponse = await this.apiService.getUserKeyFromKeyConnector(url);
const keyArr = Utils.fromB64ToArray(userKeyResponse.key);
const k = new SymmetricCryptoKey(keyArr);
await this.cryptoService.setKey(k);
const masterKeyResponse = await this.apiService.getUserKeyFromKeyConnector(url);
const keyArr = Utils.fromB64ToArray(masterKeyResponse.key);
const masterKey = new SymmetricCryptoKey(keyArr) as MasterKey;
await this.cryptoService.setMasterKey(masterKey);
} catch (e) {
this.handleKeyConnectorError(e);
}
@ -87,17 +88,18 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction {
const password = await this.cryptoFunctionService.randomBytes(64);
const kdfConfig = new KdfConfig(kdfIterations, kdfMemory, kdfParallelism);
const k = await this.cryptoService.makeKey(
const masterKey = await this.cryptoService.makeMasterKey(
Utils.fromBufferToB64(password),
await this.tokenService.getEmail(),
kdf,
kdfConfig
);
const keyConnectorRequest = new KeyConnectorUserKeyRequest(k.encKeyB64);
await this.cryptoService.setKey(k);
const keyConnectorRequest = new KeyConnectorUserKeyRequest(masterKey.encKeyB64);
await this.cryptoService.setMasterKey(masterKey);
const encKey = await this.cryptoService.makeEncKey(k);
await this.cryptoService.setEncKey(encKey[1].encryptedString);
const userKey = await this.cryptoService.makeUserKey(masterKey);
await this.cryptoService.setUserKey(userKey[0]);
await this.cryptoService.setUserKeyMasterKey(userKey[1].encryptedString);
const [pubKey, privKey] = await this.cryptoService.makeKeyPair();
@ -109,7 +111,7 @@ export class KeyConnectorService implements KeyConnectorServiceAbstraction {
const keys = new KeysRequest(pubKey, privKey.encryptedString);
const setPasswordRequest = new SetKeyConnectorKeyRequest(
encKey[1].encryptedString,
userKey[1].encryptedString,
kdf,
kdfConfig,
orgId,

View File

@ -1,4 +1,5 @@
export enum KeySuffixOptions {
Auto = "auto",
Biometric = "biometric",
Pin = "pin",
}

View File

@ -5,82 +5,370 @@ import { KdfConfig } from "../../auth/models/domain/kdf-config";
import { KeySuffixOptions, KdfType, HashPurpose } from "../../enums";
import { EncArrayBuffer } from "../models/domain/enc-array-buffer";
import { EncString } from "../models/domain/enc-string";
import { SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
import {
MasterKey,
OrgKey,
PinKey,
SymmetricCryptoKey,
UserKey,
} from "../models/domain/symmetric-crypto-key";
export abstract class CryptoService {
setKey: (key: SymmetricCryptoKey) => Promise<any>;
/**
* Use for encryption/decryption of data in order to support legacy
* encryption models. It will return the user key if available,
* if not it will return the master key.
*/
getKeyForUserEncryption: (key?: SymmetricCryptoKey) => Promise<SymmetricCryptoKey>;
/**
* Sets the provided user key and stores
* any other necessary versions, such as biometrics
* @param key The user key to set
* @param userId The desired user
*/
setUserKey: (key: UserKey) => Promise<void>;
/**
* Gets the user key from memory and sets it again,
* kicking off a refresh of any additional keys that are needed.
*/
toggleKey: () => Promise<void>;
/**
* Retrieves the user key
* @param keySuffix The desired version of the user's key to retrieve
* from storage if it is not available in memory
* @param userId The desired user
* @returns The user key
*/
getUserKeyFromMemory: (userId?: string) => Promise<UserKey>;
/**
* Retrieves the user key from storage
* @param keySuffix The desired version of the user's key to retrieve
* @param userId The desired user
* @returns The user key
*/
getUserKeyFromStorage: (
keySuffix: KeySuffixOptions.Auto | KeySuffixOptions.Biometric,
userId?: string
) => Promise<UserKey>;
/**
* @returns True if any version of the user key is available
*/
hasUserKey: () => Promise<boolean>;
/**
* @param userId The desired user
* @returns True if the user key is set in memory
*/
hasUserKeyInMemory: (userId?: string) => Promise<boolean>;
/**
* @param keySuffix The desired version of the user's key to check
* @param userId The desired user
* @returns True if the provided version of the user key is stored
*/
hasUserKeyStored: (
keySuffix?: KeySuffixOptions.Auto | KeySuffixOptions.Biometric,
userId?: string
) => Promise<boolean>;
/**
* Generates a new user key
* @param masterKey The user's master key
* @returns A new user key and the master key protected version of it
*/
makeUserKey: (key: MasterKey) => Promise<[UserKey, EncString]>;
/**
* Clears the user key
* @param clearStoredKeys Clears all stored versions of the user keys as well,
* such as the biometrics key
* @param userId The desired user
*/
clearUserKey: (clearSecretStorage?: boolean, userId?: string) => Promise<void>;
/**
* Clears the user's stored version of the user key
* @param keySuffix The desired version of the key to clear
* @param userId The desired user
*/
clearStoredUserKey: (keySuffix: KeySuffixOptions, userId?: string) => Promise<void>;
/**
* Stores the master key encrypted user key
* @param userKeyMasterKey The master key encrypted user key to set
* @param userId The desired user
*/
setUserKeyMasterKey: (UserKeyMasterKey: string, userId?: string) => Promise<void>;
/**
* Sets the user's master key
* @param key The user's master key to set
* @param userId The desired user
*/
setMasterKey: (key: MasterKey, userId?: string) => Promise<void>;
/**
* @param userId The desired user
* @returns The user's master key
*/
getMasterKey: (userId?: string) => Promise<MasterKey>;
/**
* Generates a master key from the provided password
* @param password The user's master password
* @param email The user's email
* @param kdf The user's selected key derivation function to use
* @param KdfConfig The user's key derivation function configuration
* @returns A master key derived from the provided password
*/
makeMasterKey: (
password: string,
email: string,
kdf: KdfType,
KdfConfig: KdfConfig
) => Promise<MasterKey>;
/**
* Clears the user's master key
* @param userId The desired user
*/
clearMasterKey: (userId?: string) => Promise<void>;
/**
* Encrypts the existing (or provided) user key with the
* provided master key
* @param masterKey The user's master key
* @param userKey The user key
* @returns The user key and the master key protected version of it
*/
encryptUserKeyWithMasterKey: (
masterKey: MasterKey,
userKey?: UserKey
) => Promise<[UserKey, EncString]>;
/**
* Decrypts the user key with the provided master key
* @param masterKey The user's master key
* @param userKey The user's encrypted symmetric key
* @param userId The desired user
* @returns The user key
*/
decryptUserKeyWithMasterKey: (
masterKey: MasterKey,
userKey?: EncString,
userId?: string
) => Promise<UserKey>;
/**
* 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.
* @param password The user's master password
* @param key The user's master key
* @param hashPurpose The iterations to use for the hash
* @returns The user's master password hash
*/
hashPassword: (password: string, key: MasterKey, hashPurpose?: HashPurpose) => Promise<string>;
/**
* Sets the user's key hash
* @param keyHash The user's key hash to set
*/
setKeyHash: (keyHash: string) => Promise<void>;
setEncKey: (encKey: string) => Promise<void>;
setEncPrivateKey: (encPrivateKey: string) => Promise<void>;
/**
* @returns The user's key hash
*/
getKeyHash: () => Promise<string>;
/**
* Clears the user's stored key hash
* @param userId The desired user
*/
clearKeyHash: () => Promise<void>;
/**
* Compares the provided master password to the stored key hash and updates
* if the stored hash is outdated
* @param masterPassword The user's master password
* @param key The user's master key
* @returns True if the provided master password matches either the stored
* key hash
*/
compareAndUpdateKeyHash: (masterPassword: string, key: MasterKey) => Promise<boolean>;
/**
* Stores the encrypted organization keys and clears any decrypted
* organization keys currently in memory
* @param orgs The organizations to set keys for
* @param providerOrgs The provider organizations to set keys for
*/
setOrgKeys: (
orgs: ProfileOrganizationResponse[],
providerOrgs: ProfileProviderOrganizationResponse[]
) => Promise<void>;
setProviderKeys: (orgs: ProfileProviderResponse[]) => Promise<void>;
getKey: (keySuffix?: KeySuffixOptions, userId?: string) => Promise<SymmetricCryptoKey>;
getKeyFromStorage: (keySuffix: KeySuffixOptions, userId?: string) => Promise<SymmetricCryptoKey>;
getKeyHash: () => Promise<string>;
compareAndUpdateKeyHash: (masterPassword: string, key: SymmetricCryptoKey) => Promise<boolean>;
getEncKey: (key?: SymmetricCryptoKey) => Promise<SymmetricCryptoKey>;
getPublicKey: () => Promise<ArrayBuffer>;
getPrivateKey: () => Promise<ArrayBuffer>;
getFingerprint: (fingerprintMaterial: string, publicKey?: ArrayBuffer) => Promise<string[]>;
/**
* Returns the organization's symmetric key
* @param orgId The desired organization
* @returns The organization's symmetric key
*/
getOrgKey: (orgId: string) => Promise<OrgKey>;
/**
* @returns A map of the organization Ids to their symmetric keys
*/
getOrgKeys: () => Promise<Map<string, SymmetricCryptoKey>>;
getOrgKey: (orgId: string) => Promise<SymmetricCryptoKey>;
/**
* Uses the org key to derive a new symmetric key for encrypting data
* @param orgKey The organization's symmetric key
*/
makeOrgDataEncKey: (orgKey: OrgKey) => Promise<[SymmetricCryptoKey, EncString]>;
/**
* Clears the user's stored organization keys
* @param memoryOnly Clear only the in-memory keys
* @param userId The desired user
*/
clearOrgKeys: (memoryOnly?: boolean, userId?: string) => Promise<void>;
/**
* Stores the encrypted provider keys and clears any decrypted
* provider keys currently in memory
* @param providers The providers to set keys for
*/
setProviderKeys: (orgs: ProfileProviderResponse[]) => Promise<void>;
/**
* @param providerId The desired provider
* @returns The provider's symmetric key
*/
getProviderKey: (providerId: string) => Promise<SymmetricCryptoKey>;
getKeyForUserEncryption: (key?: SymmetricCryptoKey) => Promise<SymmetricCryptoKey>;
hasKey: () => Promise<boolean>;
hasKeyInMemory: (userId?: string) => Promise<boolean>;
hasKeyStored: (keySuffix?: KeySuffixOptions, userId?: string) => Promise<boolean>;
hasEncKey: () => Promise<boolean>;
clearKey: (clearSecretStorage?: boolean, userId?: string) => Promise<any>;
clearKeyHash: () => Promise<any>;
clearEncKey: (memoryOnly?: boolean, userId?: string) => Promise<any>;
clearKeyPair: (memoryOnly?: boolean, userId?: string) => Promise<any>;
clearOrgKeys: (memoryOnly?: boolean, userId?: string) => Promise<any>;
clearProviderKeys: (memoryOnly?: boolean) => Promise<any>;
clearPinProtectedKey: () => Promise<any>;
clearKeys: (userId?: string) => Promise<any>;
toggleKey: () => Promise<any>;
makeKey: (
password: string,
salt: string,
kdf: KdfType,
kdfConfig: KdfConfig
) => Promise<SymmetricCryptoKey>;
makeKeyFromPin: (
/**
* @returns A map of the provider Ids to their symmetric keys
*/
getProviderKeys: () => Promise<Map<string, SymmetricCryptoKey>>;
/**
* @param memoryOnly Clear only the in-memory keys
* @param userId The desired user
*/
clearProviderKeys: (memoryOnly?: boolean) => Promise<void>;
/**
* Returns the public key from memory. If not available, extracts it
* from the private key and stores it in memory
* @returns The user's public key
*/
getPublicKey: () => Promise<ArrayBuffer>;
/**
* Create's a new 64 byte key and encrypts it with the user's public key
* @returns The new encrypted share key and the decrypted key itself
*/
makeShareKey: () => Promise<[EncString, SymmetricCryptoKey]>;
/**
* Sets the the user's encrypted private key in storage and
* clears the decrypted private key from memory
* Note: does not clear the private key if null is provided
* @param encPrivateKey An encrypted private key
*/
setPrivateKey: (encPrivateKey: string) => Promise<void>;
/**
* Returns the private key from memory. If not available, decrypts it
* from storage and stores it in memory
* @returns The user's private key
*/
getPrivateKey: () => Promise<ArrayBuffer>;
/**
* Generates a fingerprint phrase for the user based on their public key
* @param fingerprintMaterial Fingerprint material
* @param publicKey The user's public key
* @returns The user's fingerprint phrase
*/
getFingerprint: (fingerprintMaterial: string, publicKey?: ArrayBuffer) => Promise<string[]>;
/**
* Generates a new keypair
* @param key A key to encrypt the private key with. If not provided,
* defaults to the user key
* @returns A new keypair: [publicKey in Base64, encrypted privateKey]
*/
makeKeyPair: (key?: SymmetricCryptoKey) => Promise<[string, EncString]>;
/**
* Clears the user's key pair
* @param memoryOnly Clear only the in-memory keys
* @param userId The desired user
*/
clearKeyPair: (memoryOnly?: boolean, userId?: string) => Promise<void[]>;
/**
* @param pin The user's pin
* @param salt The user's salt
* @param kdf The user's kdf
* @param kdfConfig The user's kdf config
* @returns A key derived from the user's pin
*/
makePinKey: (pin: string, salt: string, kdf: KdfType, kdfConfig: KdfConfig) => Promise<PinKey>;
/**
* Clears the user's pin protected user key
* @param userId The desired user
*/
clearPinProtectedKey: (userId?: string) => Promise<void>;
/**
* Clears the user's old pin keys from storage
* @param userId The desired user
*/
clearOldPinKeys: (userId?: string) => Promise<void>;
/**
* Decrypts the user key with their pin
* @param pin The user's PIN
* @param salt The user's salt
* @param kdf The user's KDF
* @param kdfConfig The user's KDF config
* @param pinProtectedUserKey The user's PIN protected symmetric key, if not provided
* it will be retrieved from storage
* @returns The decrypted user key
*/
decryptUserKeyWithPin: (
pin: string,
salt: string,
kdf: KdfType,
kdfConfig: KdfConfig,
protectedKeyCs?: EncString
) => Promise<SymmetricCryptoKey>;
makeShareKey: () => Promise<[EncString, SymmetricCryptoKey]>;
makeKeyPair: (key?: SymmetricCryptoKey) => Promise<[string, EncString]>;
makePinKey: (
) => Promise<UserKey>;
/**
* @param keyMaterial The key material to derive the send key from
* @returns A new send key
*/
makeSendKey: (keyMaterial: ArrayBuffer) => Promise<SymmetricCryptoKey>;
/**
* Clears all of the user's keys from storage
* @param userId The user's Id
*/
clearKeys: (userId?: string) => Promise<any>;
/**
* RSA encrypts a value.
* @param data The data to encrypt
* @param publicKey The public key to use for encryption, if not provided, the user's public key will be used
* @returns The encrypted data
*/
rsaEncrypt: (data: ArrayBuffer, publicKey?: ArrayBuffer) => Promise<EncString>;
/**
* Decrypts a value using RSA.
* @param encValue The encrypted value to decrypt
* @param privateKeyValue The private key to use for decryption
* @returns The decrypted value
*/
rsaDecrypt: (encValue: string, privateKeyValue?: ArrayBuffer) => Promise<ArrayBuffer>;
randomNumber: (min: number, max: number) => Promise<number>;
/**
* @deprecated Left for migration purposes. Use decryptUserKeyWithPin instead.
*/
decryptMasterKeyWithPin: (
pin: string,
salt: string,
kdf: KdfType,
kdfConfig: KdfConfig
) => Promise<SymmetricCryptoKey>;
makeSendKey: (keyMaterial: ArrayBuffer) => Promise<SymmetricCryptoKey>;
hashPassword: (
password: string,
key: SymmetricCryptoKey,
hashPurpose?: HashPurpose
) => Promise<string>;
makeEncKey: (key: SymmetricCryptoKey) => Promise<[SymmetricCryptoKey, EncString]>;
remakeEncKey: (
key: SymmetricCryptoKey,
encKey?: SymmetricCryptoKey
) => Promise<[SymmetricCryptoKey, EncString]>;
kdfConfig: KdfConfig,
protectedKeyCs?: EncString
) => Promise<MasterKey>;
/**
* @deprecated July 25 2022: Get the key you need from CryptoService (getKeyForUserEncryption or getOrgKey)
* and then call encryptService.encrypt
*/
encrypt: (plainValue: string | ArrayBuffer, key?: SymmetricCryptoKey) => Promise<EncString>;
/**
* @deprecated July 25 2022: Get the key you need from CryptoService (getKeyForUserEncryption or getOrgKey)
* and then call encryptService.encryptToBytes
*/
encryptToBytes: (plainValue: ArrayBuffer, key?: SymmetricCryptoKey) => Promise<EncArrayBuffer>;
rsaEncrypt: (data: ArrayBuffer, publicKey?: ArrayBuffer) => Promise<EncString>;
rsaDecrypt: (encValue: string, privateKeyValue?: ArrayBuffer) => Promise<ArrayBuffer>;
/**
* @deprecated July 25 2022: Get the key you need from CryptoService (getKeyForUserEncryption or getOrgKey)
* and then call encryptService.decryptToBytes
*/
decryptToBytes: (encString: EncString, key?: SymmetricCryptoKey) => Promise<ArrayBuffer>;
/**
* @deprecated July 25 2022: Get the key you need from CryptoService (getKeyForUserEncryption or getOrgKey)
* and then call encryptService.decryptToUtf8
*/
decryptToUtf8: (encString: EncString, key?: SymmetricCryptoKey) => Promise<string>;
/**
* @deprecated July 25 2022: Get the key you need from CryptoService (getKeyForUserEncryption or getOrgKey)
* and then call encryptService.decryptToBytes
*/
decryptFromBytes: (encBuffer: EncArrayBuffer, key: SymmetricCryptoKey) => Promise<ArrayBuffer>;
randomNumber: (min: number, max: number) => Promise<number>;
validateKey: (key: SymmetricCryptoKey) => Promise<boolean>;
}

View File

@ -30,12 +30,18 @@ import {
} from "../models/domain/account";
import { EncString } from "../models/domain/enc-string";
import { StorageOptions } from "../models/domain/storage-options";
import { DeviceKey, SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
import {
DeviceKey,
MasterKey,
SymmetricCryptoKey,
UserKey,
} from "../models/domain/symmetric-crypto-key";
export abstract class StateService<T extends Account = Account> {
accounts$: Observable<{ [userId: string]: T }>;
activeAccount$: Observable<string>;
activeAccountUnlocked$: Observable<boolean>;
accountDiskCache$: Observable<Record<string, T>>;
addAccount: (account: T) => Promise<void>;
setActiveUser: (userId: string) => Promise<void>;
@ -75,24 +81,106 @@ export abstract class StateService<T extends Account = Account> {
setCollapsedGroupings: (value: string[], options?: StorageOptions) => Promise<void>;
getConvertAccountToKeyConnector: (options?: StorageOptions) => Promise<boolean>;
setConvertAccountToKeyConnector: (value: boolean, options?: StorageOptions) => Promise<void>;
/**
* gets the user key
*/
getUserKey: (options?: StorageOptions) => Promise<UserKey>;
/**
* Sets the user key
*/
setUserKey: (value: UserKey, options?: StorageOptions) => Promise<void>;
/**
* Gets the user's master key
*/
getMasterKey: (options?: StorageOptions) => Promise<MasterKey>;
/**
* Sets the user's master key
*/
setMasterKey: (value: MasterKey, options?: StorageOptions) => Promise<void>;
/**
* Gets the user key encrypted by the master key
*/
getUserKeyMasterKey: (options?: StorageOptions) => Promise<string>;
/**
* Sets the user key encrypted by the master key
*/
setUserKeyMasterKey: (value: string, options?: StorageOptions) => Promise<void>;
/**
* Gets the user's auto key
*/
getUserKeyAuto: (options?: StorageOptions) => Promise<string>;
/**
* Sets the user's auto key
*/
setUserKeyAuto: (value: string, options?: StorageOptions) => Promise<void>;
/**
* Gets the user's biometric key
*/
getUserKeyBiometric: (options?: StorageOptions) => Promise<string>;
/**
* Checks if the user has a biometric key available
*/
hasUserKeyBiometric: (options?: StorageOptions) => Promise<boolean>;
/**
* Sets the user's biometric key
*/
setUserKeyBiometric: (value: BiometricKey, options?: StorageOptions) => Promise<void>;
/**
* Gets the user key encrypted by the Pin key.
* Used when Master Password on Reset is disabled
*/
getUserKeyPin: (options?: StorageOptions) => Promise<EncString>;
/**
* Sets the user key encrypted by the Pin key.
* Used when Master Password on Reset is disabled
*/
setUserKeyPin: (value: EncString, options?: StorageOptions) => Promise<void>;
/**
* Gets the ephemeral version of the user key encrypted by the Pin key.
* Used when Master Password on Reset is enabled
*/
getUserKeyPinEphemeral: (options?: StorageOptions) => Promise<EncString>;
/**
* Sets the ephemeral version of the user key encrypted by the Pin key.
* Used when Master Password on Reset is enabled
*/
setUserKeyPinEphemeral: (value: EncString, options?: StorageOptions) => Promise<void>;
/**
* @deprecated For migration purposes only, use getUserKeyMasterKey instead
*/
getEncryptedCryptoSymmetricKey: (options?: StorageOptions) => Promise<string>;
/**
* @deprecated For migration purposes only, use setUserKeyMasterKey instead
*/
setEncryptedCryptoSymmetricKey: (value: string, options?: StorageOptions) => Promise<void>;
/**
* @deprecated For legacy purposes only, use getMasterKey instead
*/
getCryptoMasterKey: (options?: StorageOptions) => Promise<SymmetricCryptoKey>;
setCryptoMasterKey: (value: SymmetricCryptoKey, options?: StorageOptions) => Promise<void>;
/**
* @deprecated For migration purposes only, use getUserKeyAuto instead
*/
getCryptoMasterKeyAuto: (options?: StorageOptions) => Promise<string>;
/**
* @deprecated For migration purposes only, use setUserKeyAuto instead
*/
setCryptoMasterKeyAuto: (value: string, options?: StorageOptions) => Promise<void>;
getCryptoMasterKeyB64: (options?: StorageOptions) => Promise<string>;
setCryptoMasterKeyB64: (value: string, options?: StorageOptions) => Promise<void>;
/**
* @deprecated For migration purposes only, use getUserKeyBiometric instead
*/
getCryptoMasterKeyBiometric: (options?: StorageOptions) => Promise<string>;
/**
* @deprecated For migration purposes only, use hasUserKeyBiometric instead
*/
hasCryptoMasterKeyBiometric: (options?: StorageOptions) => Promise<boolean>;
/**
* @deprecated For migration purposes only, use setUserKeyBiometric instead
*/
setCryptoMasterKeyBiometric: (value: BiometricKey, options?: StorageOptions) => Promise<void>;
getDecryptedCiphers: (options?: StorageOptions) => Promise<CipherView[]>;
setDecryptedCiphers: (value: CipherView[], options?: StorageOptions) => Promise<void>;
getDecryptedCollections: (options?: StorageOptions) => Promise<CollectionView[]>;
setDecryptedCollections: (value: CollectionView[], options?: StorageOptions) => Promise<void>;
getDecryptedCryptoSymmetricKey: (options?: StorageOptions) => Promise<SymmetricCryptoKey>;
setDecryptedCryptoSymmetricKey: (
value: SymmetricCryptoKey,
options?: StorageOptions
) => Promise<void>;
getDecryptedOrganizationKeys: (
options?: StorageOptions
) => Promise<Map<string, SymmetricCryptoKey>>;
@ -107,7 +195,13 @@ export abstract class StateService<T extends Account = Account> {
value: GeneratedPasswordHistory[],
options?: StorageOptions
) => Promise<void>;
/**
* @deprecated For migration purposes only, use getDecryptedUserKeyPin instead
*/
getDecryptedPinProtected: (options?: StorageOptions) => Promise<EncString>;
/**
* @deprecated For migration purposes only, use setDecryptedUserKeyPin instead
*/
setDecryptedPinProtected: (value: EncString, options?: StorageOptions) => Promise<void>;
/**
* @deprecated Do not call this, use PolicyService
@ -214,8 +308,6 @@ export abstract class StateService<T extends Account = Account> {
value: { [id: string]: CollectionData },
options?: StorageOptions
) => Promise<void>;
getEncryptedCryptoSymmetricKey: (options?: StorageOptions) => Promise<string>;
setEncryptedCryptoSymmetricKey: (value: string, options?: StorageOptions) => Promise<void>;
/**
* @deprecated Do not call this directly, use FolderService
*/
@ -241,7 +333,13 @@ export abstract class StateService<T extends Account = Account> {
value: GeneratedPasswordHistory[],
options?: StorageOptions
) => Promise<void>;
/**
* @deprecated For migration purposes only, use getEncryptedUserKeyPin instead
*/
getEncryptedPinProtected: (options?: StorageOptions) => Promise<string>;
/**
* @deprecated For migration purposes only, use setEncryptedUserKeyPin instead
*/
setEncryptedPinProtected: (value: string, options?: StorageOptions) => Promise<void>;
/**
* @deprecated Do not call this directly, use PolicyService
@ -336,7 +434,13 @@ export abstract class StateService<T extends Account = Account> {
setUsernameGenerationOptions: (value: any, options?: StorageOptions) => Promise<void>;
getGeneratorOptions: (options?: StorageOptions) => Promise<any>;
setGeneratorOptions: (value: any, options?: StorageOptions) => Promise<void>;
/**
* Gets the user's Pin, encrypted by the user key
*/
getProtectedPin: (options?: StorageOptions) => Promise<string>;
/**
* Sets the user's Pin, encrypted by the user key
*/
setProtectedPin: (value: string, options?: StorageOptions) => Promise<void>;
getProviders: (options?: StorageOptions) => Promise<{ [id: string]: ProviderData }>;
setProviders: (value: { [id: string]: ProviderData }, options?: StorageOptions) => Promise<void>;

View File

@ -25,8 +25,8 @@ import { CollectionView } from "../../../vault/models/view/collection.view";
import { Utils } from "../../misc/utils";
import { ServerConfigData } from "../../models/data/server-config.data";
import { EncString } from "./enc-string";
import { DeviceKey, SymmetricCryptoKey } from "./symmetric-crypto-key";
import { EncryptedString, EncString } from "./enc-string";
import { DeviceKey, MasterKey, SymmetricCryptoKey, UserKey } from "./symmetric-crypto-key";
export class EncryptionPair<TEncrypted, TDecrypted> {
encrypted?: TEncrypted;
@ -102,14 +102,20 @@ export class AccountData {
}
export class AccountKeys {
userKey?: UserKey;
masterKey?: MasterKey;
userKeyMasterKey?: string;
userKeyAuto?: string;
userKeyBiometric?: string;
// deprecated keys
cryptoMasterKey?: SymmetricCryptoKey;
cryptoMasterKeyAuto?: string;
cryptoMasterKeyB64?: string;
cryptoMasterKeyBiometric?: string;
cryptoSymmetricKey?: EncryptionPair<string, SymmetricCryptoKey> = new EncryptionPair<
string,
SymmetricCryptoKey
>();
// end deprecated keys
deviceKey?: DeviceKey;
organizationKeys?: EncryptionPair<
{ [orgId: string]: EncryptedOrganizationKeyData },
@ -138,6 +144,8 @@ export class AccountKeys {
}
return Object.assign(new AccountKeys(), {
userKey: SymmetricCryptoKey.fromJSON(obj?.userKey),
masterKey: SymmetricCryptoKey.fromJSON(obj?.masterKey),
cryptoMasterKey: SymmetricCryptoKey.fromJSON(obj?.cryptoMasterKey),
cryptoSymmetricKey: EncryptionPair.fromJSON(
obj?.cryptoSymmetricKey,
@ -227,8 +235,10 @@ export class AccountSettings {
passwordGenerationOptions?: any;
usernameGenerationOptions?: any;
generatorOptions?: any;
pinProtected?: EncryptionPair<string, EncString> = new EncryptionPair<string, EncString>();
userKeyPin?: EncryptedString;
userKeyPinEphemeral?: EncryptedString;
protectedPin?: string;
pinProtected?: EncryptionPair<string, EncString> = new EncryptionPair<string, EncString>(); // Deprecated
settings?: AccountSettingsSettings; // TODO: Merge whatever is going on here into the AccountSettings model properly
vaultTimeout?: number;
vaultTimeoutAction?: string = "lock";

View File

@ -4,7 +4,7 @@ import { mock, MockProxy } from "jest-mock-extended";
import { EncryptionType } from "../../../enums";
import { EncryptService } from "../../../platform/abstractions/encrypt.service";
import { SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
import { OrgKey, SymmetricCryptoKey } from "../../../platform/models/domain/symmetric-crypto-key";
import { CryptoService } from "../../abstractions/crypto.service";
import { ContainerService } from "../../services/container.service";
@ -230,7 +230,7 @@ describe("EncString", () => {
});
it("gets an organization key if required", async () => {
const orgKey = mock<SymmetricCryptoKey>();
const orgKey = mock<OrgKey>();
cryptoService.getOrgKey.calledWith("orgId").mockResolvedValue(orgKey);

View File

@ -1,4 +1,4 @@
import { Jsonify } from "type-fest";
import { Jsonify, Opaque } from "type-fest";
import { EncryptionType, EXPECTED_NUM_PARTS_BY_ENCRYPTION_TYPE } from "../../../enums";
import { Utils } from "../../../platform/misc/utils";
@ -7,7 +7,7 @@ import { Encrypted } from "../../interfaces/encrypted";
import { SymmetricCryptoKey } from "./symmetric-crypto-key";
export class EncString implements Encrypted {
encryptedString?: string;
encryptedString?: EncryptedString;
encryptionType?: EncryptionType;
decryptedValue?: string;
data?: string;
@ -53,14 +53,14 @@ export class EncString implements Encrypted {
private initFromData(encType: EncryptionType, data: string, iv: string, mac: string) {
if (iv != null) {
this.encryptedString = encType + "." + iv + "|" + data;
this.encryptedString = (encType + "." + iv + "|" + data) as EncryptedString;
} else {
this.encryptedString = encType + "." + data;
this.encryptedString = (encType + "." + data) as EncryptedString;
}
// mac
if (mac != null) {
this.encryptedString += "|" + mac;
this.encryptedString = (this.encryptedString + "|" + mac) as EncryptedString;
}
this.encryptionType = encType;
@ -70,7 +70,7 @@ export class EncString implements Encrypted {
}
private initFromEncryptedString(encryptedString: string) {
this.encryptedString = encryptedString as string;
this.encryptedString = encryptedString as EncryptedString;
if (!this.encryptedString) {
return;
}
@ -165,3 +165,5 @@ export class EncString implements Encrypted {
: await cryptoService.getKeyForUserEncryption();
}
}
export type EncryptedString = Opaque<string, "EncString">;

View File

@ -78,3 +78,7 @@ export class SymmetricCryptoKey {
// Setup all separate key types as opaque types
export type DeviceKey = Opaque<SymmetricCryptoKey, "DeviceKey">;
export type UserKey = Opaque<SymmetricCryptoKey, "UserKey">;
export type MasterKey = Opaque<SymmetricCryptoKey, "MasterKey">;
export type PinKey = Opaque<SymmetricCryptoKey, "PinKey">;
export type OrgKey = Opaque<SymmetricCryptoKey, "OrgKey">;

View File

@ -1,10 +1,17 @@
import { mock, mockReset } from "jest-mock-extended";
import { CsprngArray } from "../../types/csprng";
import { CryptoFunctionService } from "../abstractions/crypto-function.service";
import { EncryptService } from "../abstractions/encrypt.service";
import { LogService } from "../abstractions/log.service";
import { PlatformUtilsService } from "../abstractions/platform-utils.service";
import { StateService } from "../abstractions/state.service";
import {
MasterKey,
PinKey,
SymmetricCryptoKey,
UserKey,
} from "../models/domain/symmetric-crypto-key";
import { CryptoService } from "../services/crypto.service";
describe("cryptoService", () => {
@ -35,4 +42,72 @@ describe("cryptoService", () => {
it("instantiates", () => {
expect(cryptoService).not.toBeFalsy();
});
describe("getKeyForUserDecryption", () => {
let mockUserKey: UserKey;
let mockMasterKey: MasterKey;
let stateSvcGetUserKey: jest.SpyInstance;
let stateSvcGetMasterKey: jest.SpyInstance;
beforeEach(() => {
const mockRandomBytes = new Uint8Array(64).buffer as CsprngArray;
mockUserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey;
mockMasterKey = new SymmetricCryptoKey(new Uint8Array(64).buffer as CsprngArray) as MasterKey;
stateSvcGetUserKey = jest.spyOn(stateService, "getUserKey");
stateSvcGetMasterKey = jest.spyOn(stateService, "getMasterKey");
});
it("returns the user key if available", async () => {
stateSvcGetUserKey.mockResolvedValue(mockUserKey);
const encryptionKey = await cryptoService.getKeyForUserEncryption();
expect(stateSvcGetUserKey).toHaveBeenCalled();
expect(stateSvcGetMasterKey).not.toHaveBeenCalled();
expect(encryptionKey).toEqual(mockUserKey);
});
it("returns the user's master key when symmetric key is not available", async () => {
stateSvcGetUserKey.mockResolvedValue(null);
stateSvcGetMasterKey.mockResolvedValue(mockMasterKey);
const encryptionKey = await cryptoService.getKeyForUserEncryption();
expect(stateSvcGetMasterKey).toHaveBeenCalled();
expect(encryptionKey).toEqual(mockMasterKey);
});
});
describe("setUserKey", () => {
const mockUserId = "example user id";
let mockUserKey: UserKey;
beforeEach(() => {
const mockRandomBytes = new Uint8Array(64).buffer as CsprngArray;
mockUserKey = new SymmetricCryptoKey(mockRandomBytes) as UserKey;
});
it("saves an Auto key if needed", async () => {
stateService.getVaultTimeout.mockResolvedValue(null);
await cryptoService.setUserKey(mockUserKey, mockUserId);
expect(stateService.setUserKeyAuto).toHaveBeenCalled();
expect(stateService.setUserKeyAuto).not.toHaveBeenCalledWith(null, { userId: mockUserId });
});
it("saves a Pin key if needed", async () => {
stateService.getUserKeyPinEphemeral.mockResolvedValue(null);
const cryptoSvcMakePinKey = jest.spyOn(cryptoService, "makePinKey");
cryptoSvcMakePinKey.mockResolvedValue(
new SymmetricCryptoKey(new Uint8Array(64).buffer) as PinKey
);
await cryptoService.setUserKey(mockUserKey, mockUserId);
expect(stateService.setUserKeyPin).toHaveBeenCalled();
});
});
});

File diff suppressed because it is too large Load Diff

View File

@ -51,7 +51,12 @@ import { EncString } from "../models/domain/enc-string";
import { GlobalState } from "../models/domain/global-state";
import { State } from "../models/domain/state";
import { StorageOptions } from "../models/domain/storage-options";
import { DeviceKey, SymmetricCryptoKey } from "../models/domain/symmetric-crypto-key";
import {
DeviceKey,
MasterKey,
SymmetricCryptoKey,
UserKey,
} from "../models/domain/symmetric-crypto-key";
const keys = {
state: "state",
@ -63,6 +68,10 @@ const keys = {
};
const partialKeys = {
userAutoKey: "_user_auto",
userBiometricKey: "_user_biometric",
userKey: "_user_key",
autoKey: "_masterkey_auto",
biometricKey: "_masterkey_biometric",
masterKey: "_masterkey",
@ -87,7 +96,8 @@ export class StateService<
private hasBeenInited = false;
private isRecoveredSession = false;
protected accountDiskCache = new BehaviorSubject<Record<string, TAccount>>({});
protected accountDiskCacheSubject = new BehaviorSubject<Record<string, TAccount>>({});
accountDiskCache$ = this.accountDiskCacheSubject.asObservable();
// default account serializer, must be overridden by child class
protected accountDeserializer = Account.fromJSON as (json: Jsonify<TAccount>) => TAccount;
@ -113,7 +123,7 @@ export class StateService<
// FIXME: This should be refactored into AuthService or a similar service,
// as checking for the existence of the crypto key is a low level
// implementation detail.
this.activeAccountUnlockedSubject.next((await this.getCryptoMasterKey()) != null);
this.activeAccountUnlockedSubject.next((await this.getUserKey()) != null);
})
)
.subscribe();
@ -516,6 +526,9 @@ export class StateService<
);
}
/**
* @deprecated Do not save the Master Key. Use the User Symmetric Key instead
*/
async getCryptoMasterKey(options?: StorageOptions): Promise<SymmetricCryptoKey> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultInMemoryOptions())
@ -523,6 +536,9 @@ export class StateService<
return account?.keys?.cryptoMasterKey;
}
/**
* @deprecated Do not save the Master Key. Use the User Symmetric Key instead
*/
async setCryptoMasterKey(value: SymmetricCryptoKey, options?: StorageOptions): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultInMemoryOptions())
@ -543,6 +559,200 @@ export class StateService<
}
}
/**
* user key used to encrypt/decrypt data
*/
async getUserKey(options?: StorageOptions): Promise<UserKey> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultInMemoryOptions())
);
return account?.keys?.userKey as UserKey;
}
/**
* user key used to encrypt/decrypt data
*/
async setUserKey(value: UserKey, options?: StorageOptions): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultInMemoryOptions())
);
account.keys.userKey = value;
await this.saveAccount(
account,
this.reconcileOptions(options, await this.defaultInMemoryOptions())
);
if (options.userId == this.activeAccountSubject.getValue()) {
const nextValue = value != null;
// Avoid emitting if we are already unlocked
if (this.activeAccountUnlockedSubject.getValue() != nextValue) {
this.activeAccountUnlockedSubject.next(nextValue);
}
}
}
/**
* User's master key derived from MP, saved only if we decrypted with MP
*/
async getMasterKey(options?: StorageOptions): Promise<MasterKey> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultInMemoryOptions())
);
return account?.keys?.masterKey;
}
/**
* User's master key derived from MP, saved only if we decrypted with MP
*/
async setMasterKey(value: MasterKey, options?: StorageOptions): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultInMemoryOptions())
);
account.keys.masterKey = value;
await this.saveAccount(
account,
this.reconcileOptions(options, await this.defaultInMemoryOptions())
);
}
/**
* The master key encrypted User symmetric key, saved on every auth
* so we can unlock with MP offline
*/
async getUserKeyMasterKey(options?: StorageOptions): Promise<string> {
return (
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
)?.keys.userKeyMasterKey;
}
/**
* The master key encrypted User symmetric key, saved on every auth
* so we can unlock with MP offline
*/
async setUserKeyMasterKey(value: string, options?: StorageOptions): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultOnDiskOptions())
);
account.keys.userKeyMasterKey = value;
await this.saveAccount(
account,
this.reconcileOptions(options, await this.defaultOnDiskOptions())
);
}
/**
* user key when using the "never" option of vault timeout
*/
async getUserKeyAuto(options?: StorageOptions): Promise<string> {
options = this.reconcileOptions(
this.reconcileOptions(options, { keySuffix: "auto" }),
await this.defaultSecureStorageOptions()
);
if (options?.userId == null) {
return null;
}
return await this.secureStorageService.get<string>(
`${options.userId}${partialKeys.userAutoKey}`,
options
);
}
/**
* user key when using the "never" option of vault timeout
*/
async setUserKeyAuto(value: string, options?: StorageOptions): Promise<void> {
options = this.reconcileOptions(
this.reconcileOptions(options, { keySuffix: "auto" }),
await this.defaultSecureStorageOptions()
);
if (options?.userId == null) {
return;
}
await this.saveSecureStorageKey(partialKeys.userAutoKey, value, options);
}
/**
* User's encrypted symmetric key when using biometrics
*/
async getUserKeyBiometric(options?: StorageOptions): Promise<string> {
options = this.reconcileOptions(
this.reconcileOptions(options, { keySuffix: "biometric" }),
await this.defaultSecureStorageOptions()
);
if (options?.userId == null) {
return null;
}
return await this.secureStorageService.get<string>(
`${options.userId}${partialKeys.userBiometricKey}`,
options
);
}
async hasUserKeyBiometric(options?: StorageOptions): Promise<boolean> {
options = this.reconcileOptions(
this.reconcileOptions(options, { keySuffix: "biometric" }),
await this.defaultSecureStorageOptions()
);
if (options?.userId == null) {
return false;
}
return await this.secureStorageService.has(
`${options.userId}${partialKeys.userBiometricKey}`,
options
);
}
async setUserKeyBiometric(value: BiometricKey, options?: StorageOptions): Promise<void> {
options = this.reconcileOptions(
this.reconcileOptions(options, { keySuffix: "biometric" }),
await this.defaultSecureStorageOptions()
);
if (options?.userId == null) {
return;
}
await this.saveSecureStorageKey(partialKeys.userBiometricKey, value, options);
}
async getUserKeyPin(options?: StorageOptions): Promise<EncString> {
return EncString.fromJSON(
(await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions())))
?.settings?.userKeyPin
);
}
async setUserKeyPin(value: EncString, options?: StorageOptions): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultOnDiskOptions())
);
account.settings.userKeyPin = value?.encryptedString;
await this.saveAccount(
account,
this.reconcileOptions(options, await this.defaultOnDiskOptions())
);
}
async getUserKeyPinEphemeral(options?: StorageOptions): Promise<EncString> {
return EncString.fromJSON(
(await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions())))
?.settings?.userKeyPinEphemeral
);
}
async setUserKeyPinEphemeral(value: EncString, options?: StorageOptions): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultInMemoryOptions())
);
account.settings.userKeyPinEphemeral = value?.encryptedString;
await this.saveAccount(
account,
this.reconcileOptions(options, await this.defaultInMemoryOptions())
);
}
/**
* @deprecated Use UserKeyAuto instead
*/
async getCryptoMasterKeyAuto(options?: StorageOptions): Promise<string> {
options = this.reconcileOptions(
this.reconcileOptions(options, { keySuffix: "auto" }),
@ -557,6 +767,9 @@ export class StateService<
);
}
/**
* @deprecated Use UserKeyAuto instead
*/
async setCryptoMasterKeyAuto(value: string, options?: StorageOptions): Promise<void> {
options = this.reconcileOptions(
this.reconcileOptions(options, { keySuffix: "auto" }),
@ -568,6 +781,9 @@ export class StateService<
await this.saveSecureStorageKey(partialKeys.autoKey, value, options);
}
/**
* @deprecated I don't see where this is even used
*/
async getCryptoMasterKeyB64(options?: StorageOptions): Promise<string> {
options = this.reconcileOptions(options, await this.defaultSecureStorageOptions());
if (options?.userId == null) {
@ -579,6 +795,9 @@ export class StateService<
);
}
/**
* @deprecated I don't see where this is even used
*/
async setCryptoMasterKeyB64(value: string, options?: StorageOptions): Promise<void> {
options = this.reconcileOptions(options, await this.defaultSecureStorageOptions());
if (options?.userId == null) {
@ -587,6 +806,9 @@ export class StateService<
await this.saveSecureStorageKey(partialKeys.masterKey, value, options);
}
/**
* @deprecated Use UserKeyBiometric instead
*/
async getCryptoMasterKeyBiometric(options?: StorageOptions): Promise<string> {
options = this.reconcileOptions(
this.reconcileOptions(options, { keySuffix: "biometric" }),
@ -601,6 +823,9 @@ export class StateService<
);
}
/**
* @deprecated Use UserKeyBiometric instead
*/
async hasCryptoMasterKeyBiometric(options?: StorageOptions): Promise<boolean> {
options = this.reconcileOptions(
this.reconcileOptions(options, { keySuffix: "biometric" }),
@ -615,6 +840,9 @@ export class StateService<
);
}
/**
* @deprecated Use UserKeyBiometric instead
*/
async setCryptoMasterKeyBiometric(value: BiometricKey, options?: StorageOptions): Promise<void> {
options = this.reconcileOptions(
this.reconcileOptions(options, { keySuffix: "biometric" }),
@ -662,6 +890,9 @@ export class StateService<
);
}
/**
* @deprecated Use UserKey instead
*/
async getDecryptedCryptoSymmetricKey(options?: StorageOptions): Promise<SymmetricCryptoKey> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultInMemoryOptions())
@ -669,6 +900,9 @@ export class StateService<
return account?.keys?.cryptoSymmetricKey?.decrypted;
}
/**
* @deprecated Use UserKey instead
*/
async setDecryptedCryptoSymmetricKey(
value: SymmetricCryptoKey,
options?: StorageOptions
@ -1398,12 +1632,18 @@ export class StateService<
);
}
/**
* @deprecated Use UserKey instead
*/
async getEncryptedCryptoSymmetricKey(options?: StorageOptions): Promise<string> {
return (
await this.getAccount(this.reconcileOptions(options, await this.defaultOnDiskOptions()))
)?.keys.cryptoSymmetricKey.encrypted;
}
/**
* @deprecated Use UserKey instead
*/
async setEncryptedCryptoSymmetricKey(value: string, options?: StorageOptions): Promise<void> {
const account = await this.getAccount(
this.reconcileOptions(options, await this.defaultOnDiskOptions())
@ -2561,7 +2801,7 @@ export class StateService<
}
if (this.useAccountCache) {
const cachedAccount = this.accountDiskCache.value[options.userId];
const cachedAccount = this.accountDiskCacheSubject.value[options.userId];
if (cachedAccount != null) {
return cachedAccount;
}
@ -2810,6 +3050,8 @@ export class StateService<
protected async removeAccountFromSecureStorage(userId: string = null): Promise<void> {
userId = userId ?? (await this.state())?.activeUserId;
await this.setUserKeyAuto(null, { userId: userId });
await this.setUserKeyBiometric(null, { userId: userId });
await this.setCryptoMasterKeyAuto(null, { userId: userId });
await this.setCryptoMasterKeyBiometric(null, { userId: userId });
await this.setCryptoMasterKeyB64(null, { userId: userId });
@ -2955,15 +3197,15 @@ export class StateService<
private setDiskCache(key: string, value: TAccount, options?: StorageOptions) {
if (this.useAccountCache) {
this.accountDiskCache.value[key] = value;
this.accountDiskCache.next(this.accountDiskCache.value);
this.accountDiskCacheSubject.value[key] = value;
this.accountDiskCacheSubject.next(this.accountDiskCacheSubject.value);
}
}
private deleteDiskCache(key: string) {
if (this.useAccountCache) {
delete this.accountDiskCache.value[key];
this.accountDiskCache.next(this.accountDiskCache.value);
delete this.accountDiskCacheSubject.value[key];
this.accountDiskCacheSubject.next(this.accountDiskCacheSubject.value);
}
}
}

View File

@ -39,8 +39,8 @@ export class SystemService implements SystemServiceAbstraction {
}
// User has set a PIN, with ask for master password on restart, to protect their vault
const decryptedPinProtected = await this.stateService.getDecryptedPinProtected();
if (decryptedPinProtected != null) {
const ephemeralPin = await this.stateService.getUserKeyPinEphemeral();
if (ephemeralPin != null) {
return;
}

View File

@ -6,7 +6,11 @@ import { CryptoFunctionService } from "../platform/abstractions/crypto-function.
import { CryptoService } from "../platform/abstractions/crypto.service";
import { EncryptService } from "../platform/abstractions/encrypt.service";
import { StateService } from "../platform/abstractions/state.service";
import { SymmetricCryptoKey, DeviceKey } from "../platform/models/domain/symmetric-crypto-key";
import {
SymmetricCryptoKey,
DeviceKey,
UserKey,
} from "../platform/models/domain/symmetric-crypto-key";
import { CsprngArray } from "../types/csprng";
export class DeviceCryptoService implements DeviceCryptoServiceAbstraction {
@ -20,11 +24,11 @@ export class DeviceCryptoService implements DeviceCryptoServiceAbstraction {
) {}
async trustDevice(): Promise<DeviceResponse> {
// Attempt to get user symmetric key
const userSymKey: SymmetricCryptoKey = await this.cryptoService.getEncKey();
// Attempt to get user key
const userKey: UserKey = await this.cryptoService.getUserKeyFromMemory();
// If user symmetric key is not found, throw error
if (!userSymKey) {
// If user key is not found, throw error
if (!userKey) {
throw new Error("User symmetric key not found");
}
@ -37,15 +41,15 @@ export class DeviceCryptoService implements DeviceCryptoServiceAbstraction {
);
const [
devicePublicKeyEncryptedUserSymKey,
userSymKeyEncryptedDevicePublicKey,
devicePublicKeyEncryptedUserKey,
userKeyEncryptedDevicePublicKey,
deviceKeyEncryptedDevicePrivateKey,
] = await Promise.all([
// Encrypt user symmetric key with the DevicePublicKey
this.cryptoService.rsaEncrypt(userSymKey.encKey, devicePublicKey),
// Encrypt user key with the DevicePublicKey
this.cryptoService.rsaEncrypt(userKey.encKey, devicePublicKey),
// Encrypt devicePublicKey with user symmetric key
this.encryptService.encrypt(devicePublicKey, userSymKey),
// Encrypt devicePublicKey with user key
this.encryptService.encrypt(devicePublicKey, userKey),
// Encrypt devicePrivateKey with deviceKey
this.encryptService.encrypt(devicePrivateKey, deviceKey),
@ -55,8 +59,8 @@ export class DeviceCryptoService implements DeviceCryptoServiceAbstraction {
const deviceIdentifier = await this.appIdService.getAppId();
return this.devicesApiService.updateTrustedDeviceKeys(
deviceIdentifier,
devicePublicKeyEncryptedUserSymKey.encryptedString,
userSymKeyEncryptedDevicePublicKey.encryptedString,
devicePublicKeyEncryptedUserKey.encryptedString,
userKeyEncryptedDevicePublicKey.encryptedString,
deviceKeyEncryptedDevicePrivateKey.encryptedString
);
}

View File

@ -8,7 +8,11 @@ import { CryptoFunctionService } from "../platform/abstractions/crypto-function.
import { EncryptService } from "../platform/abstractions/encrypt.service";
import { StateService } from "../platform/abstractions/state.service";
import { EncString } from "../platform/models/domain/enc-string";
import { SymmetricCryptoKey, DeviceKey } from "../platform/models/domain/symmetric-crypto-key";
import {
SymmetricCryptoKey,
DeviceKey,
UserKey,
} from "../platform/models/domain/symmetric-crypto-key";
import { CryptoService } from "../platform/services/crypto.service";
import { CsprngArray } from "../types/csprng";
@ -47,7 +51,7 @@ describe("deviceCryptoService", () => {
describe("Trusted Device Encryption", () => {
const deviceKeyBytesLength = 64;
const userSymKeyBytesLength = 64;
const userKeyBytesLength = 64;
describe("getDeviceKey", () => {
let mockRandomBytes: CsprngArray;
@ -124,15 +128,15 @@ describe("deviceCryptoService", () => {
let mockDeviceKeyRandomBytes: CsprngArray;
let mockDeviceKey: DeviceKey;
let mockUserSymKeyRandomBytes: CsprngArray;
let mockUserSymKey: SymmetricCryptoKey;
let mockUserKeyRandomBytes: CsprngArray;
let mockUserKey: UserKey;
const deviceRsaKeyLength = 2048;
let mockDeviceRsaKeyPair: [ArrayBuffer, ArrayBuffer];
let mockDevicePrivateKey: ArrayBuffer;
let mockDevicePublicKey: ArrayBuffer;
let mockDevicePublicKeyEncryptedUserSymKey: EncString;
let mockUserSymKeyEncryptedDevicePublicKey: EncString;
let mockDevicePublicKeyEncryptedUserKey: EncString;
let mockUserKeyEncryptedDevicePublicKey: EncString;
let mockDeviceKeyEncryptedDevicePrivateKey: EncString;
const mockDeviceResponse: DeviceResponse = new DeviceResponse({
@ -147,7 +151,7 @@ describe("deviceCryptoService", () => {
let makeDeviceKeySpy: jest.SpyInstance;
let rsaGenerateKeyPairSpy: jest.SpyInstance;
let cryptoSvcGetEncKeySpy: jest.SpyInstance;
let cryptoSvcGetUserKeyFromMemorySpy: jest.SpyInstance;
let cryptoSvcRsaEncryptSpy: jest.SpyInstance;
let encryptServiceEncryptSpy: jest.SpyInstance;
let appIdServiceGetAppIdSpy: jest.SpyInstance;
@ -159,8 +163,8 @@ describe("deviceCryptoService", () => {
mockDeviceKeyRandomBytes = new Uint8Array(deviceKeyBytesLength).buffer as CsprngArray;
mockDeviceKey = new SymmetricCryptoKey(mockDeviceKeyRandomBytes) as DeviceKey;
mockUserSymKeyRandomBytes = new Uint8Array(userSymKeyBytesLength).buffer as CsprngArray;
mockUserSymKey = new SymmetricCryptoKey(mockUserSymKeyRandomBytes);
mockUserKeyRandomBytes = new Uint8Array(userKeyBytesLength).buffer as CsprngArray;
mockUserKey = new SymmetricCryptoKey(mockUserKeyRandomBytes) as UserKey;
mockDeviceRsaKeyPair = [
new ArrayBuffer(deviceRsaKeyLength),
@ -170,14 +174,14 @@ describe("deviceCryptoService", () => {
mockDevicePublicKey = mockDeviceRsaKeyPair[0];
mockDevicePrivateKey = mockDeviceRsaKeyPair[1];
mockDevicePublicKeyEncryptedUserSymKey = new EncString(
mockDevicePublicKeyEncryptedUserKey = new EncString(
EncryptionType.Rsa2048_OaepSha1_B64,
"mockDevicePublicKeyEncryptedUserSymKey"
"mockDevicePublicKeyEncryptedUserKey"
);
mockUserSymKeyEncryptedDevicePublicKey = new EncString(
mockUserKeyEncryptedDevicePublicKey = new EncString(
EncryptionType.AesCbc256_HmacSha256_B64,
"mockUserSymKeyEncryptedDevicePublicKey"
"mockUserKeyEncryptedDevicePublicKey"
);
mockDeviceKeyEncryptedDevicePrivateKey = new EncString(
@ -194,19 +198,19 @@ describe("deviceCryptoService", () => {
.spyOn(cryptoFunctionService, "rsaGenerateKeyPair")
.mockResolvedValue(mockDeviceRsaKeyPair);
cryptoSvcGetEncKeySpy = jest
.spyOn(cryptoService, "getEncKey")
.mockResolvedValue(mockUserSymKey);
cryptoSvcGetUserKeyFromMemorySpy = jest
.spyOn(cryptoService, "getUserKeyFromMemory")
.mockResolvedValue(mockUserKey);
cryptoSvcRsaEncryptSpy = jest
.spyOn(cryptoService, "rsaEncrypt")
.mockResolvedValue(mockDevicePublicKeyEncryptedUserSymKey);
.mockResolvedValue(mockDevicePublicKeyEncryptedUserKey);
encryptServiceEncryptSpy = jest
.spyOn(encryptService, "encrypt")
.mockImplementation((plainValue, key) => {
if (plainValue === mockDevicePublicKey && key === mockUserSymKey) {
return Promise.resolve(mockUserSymKeyEncryptedDevicePublicKey);
if (plainValue === mockDevicePublicKey && key === mockUserKey) {
return Promise.resolve(mockUserKeyEncryptedDevicePublicKey);
}
if (plainValue === mockDevicePrivateKey && key === mockDeviceKey) {
return Promise.resolve(mockDeviceKeyEncryptedDevicePrivateKey);
@ -227,7 +231,7 @@ describe("deviceCryptoService", () => {
expect(makeDeviceKeySpy).toHaveBeenCalledTimes(1);
expect(rsaGenerateKeyPairSpy).toHaveBeenCalledTimes(1);
expect(cryptoSvcGetEncKeySpy).toHaveBeenCalledTimes(1);
expect(cryptoSvcGetUserKeyFromMemorySpy).toHaveBeenCalledTimes(1);
expect(cryptoSvcRsaEncryptSpy).toHaveBeenCalledTimes(1);
expect(encryptServiceEncryptSpy).toHaveBeenCalledTimes(2);
@ -236,8 +240,8 @@ describe("deviceCryptoService", () => {
expect(devicesApiServiceUpdateTrustedDeviceKeysSpy).toHaveBeenCalledTimes(1);
expect(devicesApiServiceUpdateTrustedDeviceKeysSpy).toHaveBeenCalledWith(
mockDeviceId,
mockDevicePublicKeyEncryptedUserSymKey.encryptedString,
mockUserSymKeyEncryptedDevicePublicKey.encryptedString,
mockDevicePublicKeyEncryptedUserKey.encryptedString,
mockUserKeyEncryptedDevicePublicKey.encryptedString,
mockDeviceKeyEncryptedDevicePrivateKey.encryptedString
);
@ -245,19 +249,19 @@ describe("deviceCryptoService", () => {
expect(response).toEqual(mockDeviceResponse);
});
it("throws specific error if user symmetric key is not found", async () => {
it("throws specific error if user key is not found", async () => {
// setup the spy to return null
cryptoSvcGetEncKeySpy.mockResolvedValue(null);
cryptoSvcGetUserKeyFromMemorySpy.mockResolvedValue(null);
// check if the expected error is thrown
await expect(deviceCryptoService.trustDevice()).rejects.toThrow(
"User symmetric key not found"
);
// reset the spy
cryptoSvcGetEncKeySpy.mockReset();
cryptoSvcGetUserKeyFromMemorySpy.mockReset();
// setup the spy to return undefined
cryptoSvcGetEncKeySpy.mockResolvedValue(undefined);
cryptoSvcGetUserKeyFromMemorySpy.mockResolvedValue(undefined);
// check if the expected error is thrown
await expect(deviceCryptoService.trustDevice()).rejects.toThrow(
"User symmetric key not found"
@ -276,9 +280,9 @@ describe("deviceCryptoService", () => {
errorText: "rsaGenerateKeyPair error",
},
{
method: "getEncKey",
spy: () => cryptoSvcGetEncKeySpy,
errorText: "getEncKey error",
method: "getUserKeyFromMemory",
spy: () => cryptoSvcGetUserKeyFromMemorySpy,
errorText: "getUserKeyFromMemory error",
},
{
method: "rsaEncrypt",

View File

@ -47,13 +47,13 @@ export class DevicesApiServiceImplementation implements DevicesApiServiceAbstrac
async updateTrustedDeviceKeys(
deviceIdentifier: string,
devicePublicKeyEncryptedUserSymKey: string,
userSymKeyEncryptedDevicePublicKey: string,
devicePublicKeyEncryptedUserKey: string,
userKeyEncryptedDevicePublicKey: string,
deviceKeyEncryptedDevicePrivateKey: string
): Promise<DeviceResponse> {
const request = new TrustedDeviceKeysRequest(
devicePublicKeyEncryptedUserSymKey,
userSymKeyEncryptedDevicePublicKey,
devicePublicKeyEncryptedUserKey,
userKeyEncryptedDevicePublicKey,
deviceKeyEncryptedDevicePrivateKey
);

View File

@ -71,8 +71,10 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
if (await this.keyConnectorService.getUsesKeyConnector()) {
const pinSet = await this.vaultTimeoutSettingsService.isPinLockSet();
const pinLock =
(pinSet[0] && (await this.stateService.getDecryptedPinProtected()) != null) || pinSet[1];
let ephemeralPinSet = await this.stateService.getUserKeyPinEphemeral();
ephemeralPinSet ||= await this.stateService.getDecryptedPinProtected();
const pinLock = (pinSet[0] && ephemeralPinSet != null) || pinSet[1];
if (!pinLock && !(await this.vaultTimeoutSettingsService.isBiometricLockSet())) {
await this.logOut(userId);
@ -85,12 +87,13 @@ export class VaultTimeoutService implements VaultTimeoutServiceAbstraction {
}
await this.stateService.setEverBeenUnlocked(true, { userId: userId });
await this.stateService.setUserKeyAuto(null, { userId: userId });
await this.stateService.setCryptoMasterKeyAuto(null, { userId: userId });
await this.cryptoService.clearKey(false, userId);
await this.cryptoService.clearUserKey(false, userId);
await this.cryptoService.clearMasterKey(userId);
await this.cryptoService.clearOrgKeys(true, userId);
await this.cryptoService.clearKeyPair(true, userId);
await this.cryptoService.clearEncKey(true, userId);
await this.cipherService.clearCache(userId);
await this.collectionService.clearCache(userId);

Some files were not shown because too many files have changed in this diff Show More