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:
commit
c942bc08ca
2
.github/workflows/brew-bump-cli.yml
vendored
2
.github/workflows/brew-bump-cli.yml
vendored
@ -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"
|
||||
|
2
.github/workflows/brew-bump-desktop.yml
vendored
2
.github/workflows/brew-bump-desktop.yml
vendored
@ -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"
|
||||
|
4
.github/workflows/build-browser.yml
vendored
4
.github/workflows/build-browser.yml
vendored
@ -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"
|
||||
|
2
.github/workflows/build-cli.yml
vendored
2
.github/workflows/build-cli.yml
vendored
@ -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"
|
||||
|
8
.github/workflows/build-desktop.yml
vendored
8
.github/workflows/build-desktop.yml
vendored
@ -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"
|
||||
|
8
.github/workflows/build-web.yml
vendored
8
.github/workflows/build-web.yml
vendored
@ -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"
|
||||
|
4
.github/workflows/crowdin-pull.yml
vendored
4
.github/workflows/crowdin-pull.yml
vendored
@ -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 }}
|
||||
|
14
.github/workflows/deploy-eu-prod-web.yml
vendored
14
.github/workflows/deploy-eu-prod-web.yml
vendored
@ -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
|
||||
|
2
.github/workflows/deploy-non-prod-web.yml
vendored
2
.github/workflows/deploy-non-prod-web.yml
vendored
@ -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
|
||||
|
6
.github/workflows/release-browser.yml
vendored
6
.github/workflows/release-browser.yml
vendored
@ -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
|
||||
|
24
.github/workflows/release-cli.yml
vendored
24
.github/workflows/release-cli.yml
vendored
@ -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
|
||||
|
8
.github/workflows/release-desktop-beta.yml
vendored
8
.github/workflows/release-desktop-beta.yml
vendored
@ -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,
|
||||
|
22
.github/workflows/release-desktop.yml
vendored
22
.github/workflows/release-desktop.yml
vendored
@ -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
|
||||
|
2
.github/workflows/release-qa-web.yml
vendored
2
.github/workflows/release-qa-web.yml
vendored
@ -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
|
||||
|
14
.github/workflows/release-web.yml
vendored
14
.github/workflows/release-web.yml
vendored
@ -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
|
||||
|
2
.github/workflows/staged-rollout-desktop.yml
vendored
2
.github/workflows/staged-rollout-desktop.yml
vendored
@ -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,
|
||||
|
6
.github/workflows/version-bump.yml
vendored
6
.github/workflows/version-bump.yml
vendored
@ -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"
|
||||
|
2
.github/workflows/workflow-linter.yml
vendored
2
.github/workflows/workflow-linter.yml
vendored
@ -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
|
||||
|
@ -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"
|
||||
},
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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: [
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
|
@ -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."));
|
||||
|
@ -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()) {
|
||||
|
@ -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/"
|
||||
|
@ -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);
|
||||
|
@ -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": {
|
||||
|
@ -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"
|
||||
|
@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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";
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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"
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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 won’t 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 won’t 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": {
|
||||
|
@ -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",
|
||||
|
@ -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é."
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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",
|
||||
|
@ -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) {}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -11,8 +11,8 @@ export abstract class DevicesApiServiceAbstraction {
|
||||
|
||||
updateTrustedDeviceKeys: (
|
||||
deviceIdentifier: string,
|
||||
devicePublicKeyEncryptedUserSymKey: string,
|
||||
userSymKeyEncryptedDevicePublicKey: string,
|
||||
devicePublicKeyEncryptedUserKey: string,
|
||||
userKeyEncryptedDevicePublicKey: string,
|
||||
deviceKeyEncryptedDevicePrivateKey: string
|
||||
) => Promise<DeviceResponse>;
|
||||
}
|
||||
|
@ -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>;
|
||||
|
@ -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;
|
||||
|
@ -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>;
|
||||
|
@ -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]
|
||||
);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
@ -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())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
) {}
|
||||
|
@ -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
|
||||
);
|
||||
|
@ -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,
|
||||
|
@ -1,4 +1,5 @@
|
||||
export enum KeySuffixOptions {
|
||||
Auto = "auto",
|
||||
Biometric = "biometric",
|
||||
Pin = "pin",
|
||||
}
|
||||
|
@ -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>;
|
||||
}
|
||||
|
@ -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>;
|
||||
|
@ -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";
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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">;
|
||||
|
@ -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">;
|
||||
|
@ -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
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
);
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
);
|
||||
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user