mirror of
https://github.com/bitwarden/browser
synced 2025-12-13 14:53:33 +00:00
[SG-520] Native messaging handler (#3566)
* [SG-523] Base test runner app for native messages (#3269) * Base test runner app for native messages * Remove default test script * Add case for canceled status * Modify to allow usage of libs crypto services and functions * Small adjustments * Handshake request (#3277) * Handshake request * Fix capitalization * Update info text * lock node-ipc to 9.2.1 * [SG-569] Native Messaging settings bug (#3285) * Fix bug where updating setting wasn't starting the native messaging listener * Update test runner error message * [SG-532] Implement Status command in Native Messaging Service (#3310) * Status command start * Refactor ipc test service and add status command * fixed linter errors * Move types into a model file * Cleanup and comments * Fix auth status condition * Remove .vscode settings file. Fix this in a separate work item * Add active field to status response * Extract native messaging types into their own files * Remove experimental decorators * Turn off no console lint rule for the test runner * Casing fix * Models import casing fixes * Remove in progress file (merge error) * Move models to their own folder and add index.ts * Remove file that got un-deleted * Remove file that will be added in separate command * Fix imports that got borked * [SG-533] Implement bw-credential-retrieval (#3334) * Status command start * Refactor ipc test service and add status command * fixed linter errors * Move types into a model file * Cleanup and comments * Fix auth status condition * Remove .vscode settings file. Fix this in a separate work item * Implement bw-credential-retrieval * Add active field to status response * Extract native messaging types into their own files * Remove experimental decorators * Turn off no console lint rule for the test runner * Casing fix * Models import casing fixes * Add error handling for passing a bad public key to handshake * [SG-534] and [SG-535] Implement Credential Create and Update commands (#3342) * Status command start * Refactor ipc test service and add status command * fixed linter errors * Move types into a model file * Cleanup and comments * Fix auth status condition * Remove .vscode settings file. Fix this in a separate work item * Implement bw-credential-retrieval * Add active field to status response * Add bw-credential-create * Better response handling in test runner * Extract native messaging types into their own files * Remove experimental decorators * Turn off no console lint rule for the test runner * Casing fix * Models import casing fixes * bw-cipher-create move type into its own file * Use LogUtils for all logging * Implement bw-credential-update * Give naming conventions for types * Rename file correctly * Update handleEncyptedMessage with EncString changes * [SG-626] Fix Desktop app not showing updated credentials from native messages (#3380) * Add MessagingService to send messages on login create and update * Add `not-active-user` error to create and update and other refactors * [SG-536] Implement bw-generate-password (#3370) * implement bw-generate-password * Fix merge conflict resolution errors * Update apps/desktop/native-messaging-test-runner/src/bw-generate-password.ts Co-authored-by: Addison Beck <addisonbeck1@gmail.com> * Logging improvements * Add NativeMessagingVersion enum * Add version check in NativeMessagingHandler Co-authored-by: Addison Beck <addisonbeck1@gmail.com> * Refactor account status checks and check for locked state in generate command (#3461) * Add feawture flag to show/hide ddg setting (#3506) * [SG-649] Add confirmation dialog and tweak shared key retrieval (#3451) * Add confirmation dialog when completing handshake * Copy updates for dialog * HandshakeResponse type fixes * Add longer timeout for handshake command * [SG-663] RefactorNativeMessagingHandlerService and strengthen typing (#3551) * NativeMessageHandlerService refactor and additional types * Return empty array if no uri to retrieve command * Move commands from test runner into a separate folder * Fix bug where confirmation dialog messes with styling * Enable DDG feature * Fix generated password not saving to history * Take credentialId as parameter to update * Add applicationName to handshake payload * Add warning text to confirmation modal Co-authored-by: Addison Beck <addisonbeck1@gmail.com>
This commit is contained in:
@@ -1,4 +1,6 @@
|
|||||||
{
|
{
|
||||||
"devFlags": {},
|
"devFlags": {},
|
||||||
"flags": {}
|
"flags": {
|
||||||
|
"showDDGSetting": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
{
|
{
|
||||||
"flags": {}
|
"flags": {
|
||||||
|
"showDDGSetting": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
5
apps/desktop/native-messaging-test-runner/.eslintrc.json
Normal file
5
apps/desktop/native-messaging-test-runner/.eslintrc.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"rules": {
|
||||||
|
"no-console": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
681
apps/desktop/native-messaging-test-runner/package-lock.json
generated
Normal file
681
apps/desktop/native-messaging-test-runner/package-lock.json
generated
Normal file
@@ -0,0 +1,681 @@
|
|||||||
|
{
|
||||||
|
"name": "native-messaging-test-runner",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 2,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "native-messaging-test-runner",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "GPL-3.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@bitwarden/common": "file:../../../libs/common",
|
||||||
|
"@bitwarden/node": "file:../../../libs/node",
|
||||||
|
"module-alias": "^2.2.2",
|
||||||
|
"node-ipc": "9.2.1",
|
||||||
|
"ts-node": "^10.9.1",
|
||||||
|
"uuid": "^8.3.2",
|
||||||
|
"yargs": "^17.5.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tsconfig/node16": "^1.0.3",
|
||||||
|
"@types/node": "^18.6.5",
|
||||||
|
"@types/node-ipc": "9.2.0",
|
||||||
|
"typescript": "^4.7.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"../../../libs/common": {
|
||||||
|
"name": "@bitwarden/common",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"license": "GPL-3.0"
|
||||||
|
},
|
||||||
|
"../../../libs/node": {
|
||||||
|
"name": "@bitwarden/node",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"license": "GPL-3.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@bitwarden/common": "file:../common"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@bitwarden/common": {
|
||||||
|
"resolved": "../../../libs/common",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
|
"node_modules/@bitwarden/node": {
|
||||||
|
"resolved": "../../../libs/node",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
|
"node_modules/@cspotcode/source-map-support": {
|
||||||
|
"version": "0.8.1",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/trace-mapping": "0.3.9"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@jridgewell/resolve-uri": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@jridgewell/sourcemap-codec": {
|
||||||
|
"version": "1.4.14",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@jridgewell/trace-mapping": {
|
||||||
|
"version": "0.3.9",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@jridgewell/resolve-uri": "^3.0.3",
|
||||||
|
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tsconfig/node10": {
|
||||||
|
"version": "1.0.9",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@tsconfig/node12": {
|
||||||
|
"version": "1.0.11",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@tsconfig/node14": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@tsconfig/node16": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "18.6.5",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@types/node-ipc": {
|
||||||
|
"version": "9.2.0",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/acorn": {
|
||||||
|
"version": "8.8.0",
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"acorn": "bin/acorn"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/acorn-walk": {
|
||||||
|
"version": "8.2.0",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ansi-regex": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ansi-styles": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||||
|
"dependencies": {
|
||||||
|
"color-convert": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/arg": {
|
||||||
|
"version": "4.1.3",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/cliui": {
|
||||||
|
"version": "7.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
|
||||||
|
"integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"string-width": "^4.2.0",
|
||||||
|
"strip-ansi": "^6.0.0",
|
||||||
|
"wrap-ansi": "^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/color-convert": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"color-name": "~1.1.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/color-name": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||||
|
},
|
||||||
|
"node_modules/create-require": {
|
||||||
|
"version": "1.1.1",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/diff": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"license": "BSD-3-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.3.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/easy-stack": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/easy-stack/-/easy-stack-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-wK2sCs4feiiJeFXn3zvY0p41mdU5VUgbgs1rNsc/y5ngFUijdWd+iIN8eoyuZHKB8xN6BL4PdWmzqFmxNg6V2w==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/emoji-regex": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
||||||
|
},
|
||||||
|
"node_modules/escalade": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/event-pubsub": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/event-pubsub/-/event-pubsub-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-z7IyloorXvKbFx9Bpie2+vMJKKx1fH1EN5yiTfp8CiLOTptSYy1g8H4yDpGlEdshL1PBiFtBHepF2cNsqeEeFQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/get-caller-file": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||||
|
"engines": {
|
||||||
|
"node": "6.* || 8.* || >= 10.*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/is-fullwidth-code-point": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/js-message": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-message/-/js-message-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-efJLHhLjIyKRewNS9EGZ4UpI8NguuL6fKkhRxVuMmrGV2xN/0APGdQYwLFky5w9naebSZ0OwAGp0G6/2Cg90rA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.6.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/js-queue": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-queue/-/js-queue-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-pbKLsbCfi7kriM3s1J4DDCo7jQkI58zPLHi0heXPzPlj0hjUsm+FesPUbE0DSbIVIK503A36aUBoCN7eMFedkA==",
|
||||||
|
"dependencies": {
|
||||||
|
"easy-stack": "^1.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/make-error": {
|
||||||
|
"version": "1.3.6",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/module-alias": {
|
||||||
|
"version": "2.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.2.tgz",
|
||||||
|
"integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q=="
|
||||||
|
},
|
||||||
|
"node_modules/node-ipc": {
|
||||||
|
"version": "9.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-ipc/-/node-ipc-9.2.1.tgz",
|
||||||
|
"integrity": "sha512-mJzaM6O3xHf9VT8BULvJSbdVbmHUKRNOH7zDDkCrA1/T+CVjq2WVIDfLt0azZRXpgArJtl3rtmEozrbXPZ9GaQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"event-pubsub": "4.3.0",
|
||||||
|
"js-message": "1.0.7",
|
||||||
|
"js-queue": "2.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/require-directory": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/string-width": {
|
||||||
|
"version": "4.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||||
|
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||||
|
"dependencies": {
|
||||||
|
"emoji-regex": "^8.0.0",
|
||||||
|
"is-fullwidth-code-point": "^3.0.0",
|
||||||
|
"strip-ansi": "^6.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/strip-ansi": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-regex": "^5.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ts-node": {
|
||||||
|
"version": "10.9.1",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@cspotcode/source-map-support": "^0.8.0",
|
||||||
|
"@tsconfig/node10": "^1.0.7",
|
||||||
|
"@tsconfig/node12": "^1.0.7",
|
||||||
|
"@tsconfig/node14": "^1.0.0",
|
||||||
|
"@tsconfig/node16": "^1.0.2",
|
||||||
|
"acorn": "^8.4.1",
|
||||||
|
"acorn-walk": "^8.1.1",
|
||||||
|
"arg": "^4.1.0",
|
||||||
|
"create-require": "^1.1.0",
|
||||||
|
"diff": "^4.0.1",
|
||||||
|
"make-error": "^1.1.1",
|
||||||
|
"v8-compile-cache-lib": "^3.0.1",
|
||||||
|
"yn": "3.1.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"ts-node": "dist/bin.js",
|
||||||
|
"ts-node-cwd": "dist/bin-cwd.js",
|
||||||
|
"ts-node-esm": "dist/bin-esm.js",
|
||||||
|
"ts-node-script": "dist/bin-script.js",
|
||||||
|
"ts-node-transpile-only": "dist/bin-transpile.js",
|
||||||
|
"ts-script": "dist/bin-script-deprecated.js"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@swc/core": ">=1.2.50",
|
||||||
|
"@swc/wasm": ">=1.2.50",
|
||||||
|
"@types/node": "*",
|
||||||
|
"typescript": ">=2.7"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@swc/core": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@swc/wasm": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/typescript": {
|
||||||
|
"version": "4.7.4",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"tsc": "bin/tsc",
|
||||||
|
"tsserver": "bin/tsserver"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/uuid": {
|
||||||
|
"version": "8.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||||
|
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/v8-compile-cache-lib": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/wrap-ansi": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-styles": "^4.0.0",
|
||||||
|
"string-width": "^4.1.0",
|
||||||
|
"strip-ansi": "^6.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/y18n": {
|
||||||
|
"version": "5.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||||
|
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yargs": {
|
||||||
|
"version": "17.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz",
|
||||||
|
"integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==",
|
||||||
|
"dependencies": {
|
||||||
|
"cliui": "^7.0.2",
|
||||||
|
"escalade": "^3.1.1",
|
||||||
|
"get-caller-file": "^2.0.5",
|
||||||
|
"require-directory": "^2.1.1",
|
||||||
|
"string-width": "^4.2.3",
|
||||||
|
"y18n": "^5.0.5",
|
||||||
|
"yargs-parser": "^21.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yargs-parser": {
|
||||||
|
"version": "21.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
||||||
|
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yn": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@bitwarden/common": {
|
||||||
|
"version": "file:../../../libs/common"
|
||||||
|
},
|
||||||
|
"@bitwarden/node": {
|
||||||
|
"version": "file:../../../libs/node",
|
||||||
|
"requires": {
|
||||||
|
"@bitwarden/common": "file:../common"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@cspotcode/source-map-support": {
|
||||||
|
"version": "0.8.1",
|
||||||
|
"requires": {
|
||||||
|
"@jridgewell/trace-mapping": "0.3.9"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@jridgewell/resolve-uri": {
|
||||||
|
"version": "3.1.0"
|
||||||
|
},
|
||||||
|
"@jridgewell/sourcemap-codec": {
|
||||||
|
"version": "1.4.14"
|
||||||
|
},
|
||||||
|
"@jridgewell/trace-mapping": {
|
||||||
|
"version": "0.3.9",
|
||||||
|
"requires": {
|
||||||
|
"@jridgewell/resolve-uri": "^3.0.3",
|
||||||
|
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@tsconfig/node10": {
|
||||||
|
"version": "1.0.9"
|
||||||
|
},
|
||||||
|
"@tsconfig/node12": {
|
||||||
|
"version": "1.0.11"
|
||||||
|
},
|
||||||
|
"@tsconfig/node14": {
|
||||||
|
"version": "1.0.3"
|
||||||
|
},
|
||||||
|
"@tsconfig/node16": {
|
||||||
|
"version": "1.0.3"
|
||||||
|
},
|
||||||
|
"@types/node": {
|
||||||
|
"version": "18.6.5"
|
||||||
|
},
|
||||||
|
"@types/node-ipc": {
|
||||||
|
"version": "9.2.0",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"acorn": {
|
||||||
|
"version": "8.8.0"
|
||||||
|
},
|
||||||
|
"acorn-walk": {
|
||||||
|
"version": "8.2.0"
|
||||||
|
},
|
||||||
|
"ansi-regex": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
|
||||||
|
},
|
||||||
|
"ansi-styles": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||||
|
"requires": {
|
||||||
|
"color-convert": "^2.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"arg": {
|
||||||
|
"version": "4.1.3"
|
||||||
|
},
|
||||||
|
"cliui": {
|
||||||
|
"version": "7.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
|
||||||
|
"integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
|
||||||
|
"requires": {
|
||||||
|
"string-width": "^4.2.0",
|
||||||
|
"strip-ansi": "^6.0.0",
|
||||||
|
"wrap-ansi": "^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color-convert": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||||
|
"requires": {
|
||||||
|
"color-name": "~1.1.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color-name": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||||
|
},
|
||||||
|
"create-require": {
|
||||||
|
"version": "1.1.1"
|
||||||
|
},
|
||||||
|
"diff": {
|
||||||
|
"version": "4.0.2"
|
||||||
|
},
|
||||||
|
"easy-stack": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/easy-stack/-/easy-stack-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-wK2sCs4feiiJeFXn3zvY0p41mdU5VUgbgs1rNsc/y5ngFUijdWd+iIN8eoyuZHKB8xN6BL4PdWmzqFmxNg6V2w=="
|
||||||
|
},
|
||||||
|
"emoji-regex": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
|
||||||
|
},
|
||||||
|
"escalade": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw=="
|
||||||
|
},
|
||||||
|
"event-pubsub": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/event-pubsub/-/event-pubsub-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-z7IyloorXvKbFx9Bpie2+vMJKKx1fH1EN5yiTfp8CiLOTptSYy1g8H4yDpGlEdshL1PBiFtBHepF2cNsqeEeFQ=="
|
||||||
|
},
|
||||||
|
"get-caller-file": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
|
||||||
|
},
|
||||||
|
"is-fullwidth-code-point": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
|
||||||
|
},
|
||||||
|
"js-message": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-message/-/js-message-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-efJLHhLjIyKRewNS9EGZ4UpI8NguuL6fKkhRxVuMmrGV2xN/0APGdQYwLFky5w9naebSZ0OwAGp0G6/2Cg90rA==",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-message/-/js-message-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-efJLHhLjIyKRewNS9EGZ4UpI8NguuL6fKkhRxVuMmrGV2xN/0APGdQYwLFky5w9naebSZ0OwAGp0G6/2Cg90rA=="
|
||||||
|
},
|
||||||
|
"js-queue": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-queue/-/js-queue-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-pbKLsbCfi7kriM3s1J4DDCo7jQkI58zPLHi0heXPzPlj0hjUsm+FesPUbE0DSbIVIK503A36aUBoCN7eMFedkA==",
|
||||||
|
"resolved": "https://registry.npmjs.org/js-queue/-/js-queue-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-pbKLsbCfi7kriM3s1J4DDCo7jQkI58zPLHi0heXPzPlj0hjUsm+FesPUbE0DSbIVIK503A36aUBoCN7eMFedkA==",
|
||||||
|
"requires": {
|
||||||
|
"easy-stack": "^1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"make-error": {
|
||||||
|
"version": "1.3.6"
|
||||||
|
},
|
||||||
|
"module-alias": {
|
||||||
|
"version": "2.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.2.tgz",
|
||||||
|
"integrity": "sha512-A/78XjoX2EmNvppVWEhM2oGk3x4lLxnkEA4jTbaK97QKSDjkIoOsKQlfylt/d3kKKi596Qy3NP5XrXJ6fZIC9Q=="
|
||||||
|
},
|
||||||
|
"node-ipc": {
|
||||||
|
"version": "9.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-ipc/-/node-ipc-9.2.1.tgz",
|
||||||
|
"integrity": "sha512-mJzaM6O3xHf9VT8BULvJSbdVbmHUKRNOH7zDDkCrA1/T+CVjq2WVIDfLt0azZRXpgArJtl3rtmEozrbXPZ9GaQ==",
|
||||||
|
"requires": {
|
||||||
|
"event-pubsub": "4.3.0",
|
||||||
|
"js-message": "1.0.7",
|
||||||
|
"js-queue": "2.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"require-directory": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="
|
||||||
|
},
|
||||||
|
"string-width": {
|
||||||
|
"version": "4.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||||
|
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||||
|
"requires": {
|
||||||
|
"emoji-regex": "^8.0.0",
|
||||||
|
"is-fullwidth-code-point": "^3.0.0",
|
||||||
|
"strip-ansi": "^6.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"strip-ansi": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||||
|
"requires": {
|
||||||
|
"ansi-regex": "^5.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ts-node": {
|
||||||
|
"version": "10.9.1",
|
||||||
|
"requires": {
|
||||||
|
"@cspotcode/source-map-support": "^0.8.0",
|
||||||
|
"@tsconfig/node10": "^1.0.7",
|
||||||
|
"@tsconfig/node12": "^1.0.7",
|
||||||
|
"@tsconfig/node14": "^1.0.0",
|
||||||
|
"@tsconfig/node16": "^1.0.2",
|
||||||
|
"acorn": "^8.4.1",
|
||||||
|
"acorn-walk": "^8.1.1",
|
||||||
|
"arg": "^4.1.0",
|
||||||
|
"create-require": "^1.1.0",
|
||||||
|
"diff": "^4.0.1",
|
||||||
|
"make-error": "^1.1.1",
|
||||||
|
"v8-compile-cache-lib": "^3.0.1",
|
||||||
|
"yn": "3.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"typescript": {
|
||||||
|
"version": "4.7.4"
|
||||||
|
},
|
||||||
|
"uuid": {
|
||||||
|
"version": "8.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||||
|
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
|
||||||
|
},
|
||||||
|
"v8-compile-cache-lib": {
|
||||||
|
"version": "3.0.1"
|
||||||
|
},
|
||||||
|
"wrap-ansi": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||||
|
"requires": {
|
||||||
|
"ansi-styles": "^4.0.0",
|
||||||
|
"string-width": "^4.1.0",
|
||||||
|
"strip-ansi": "^6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"y18n": {
|
||||||
|
"version": "5.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||||
|
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="
|
||||||
|
},
|
||||||
|
"yargs": {
|
||||||
|
"version": "17.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz",
|
||||||
|
"integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==",
|
||||||
|
"requires": {
|
||||||
|
"cliui": "^7.0.2",
|
||||||
|
"escalade": "^3.1.1",
|
||||||
|
"get-caller-file": "^2.0.5",
|
||||||
|
"require-directory": "^2.1.1",
|
||||||
|
"string-width": "^4.2.3",
|
||||||
|
"y18n": "^5.0.5",
|
||||||
|
"yargs-parser": "^21.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"yargs-parser": {
|
||||||
|
"version": "21.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
||||||
|
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="
|
||||||
|
},
|
||||||
|
"yn": {
|
||||||
|
"version": "3.1.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
35
apps/desktop/native-messaging-test-runner/package.json
Normal file
35
apps/desktop/native-messaging-test-runner/package.json
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"name": "native-messaging-test-runner",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Test runner for Desktop native messaging",
|
||||||
|
"main": "dist/bw-handshake.ts",
|
||||||
|
"scripts": {
|
||||||
|
"handshake": "tsc && node dist/apps/desktop/native-messaging-test-runner/src/commands/bw-handshake.js",
|
||||||
|
"status": "tsc && node dist/apps/desktop/native-messaging-test-runner/src/commands/bw-status.js",
|
||||||
|
"retrieve": "tsc && node dist/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-retrieval.js",
|
||||||
|
"create": "tsc && node dist/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-create.js",
|
||||||
|
"update": "tsc && node dist/apps/desktop/native-messaging-test-runner/src/commands/bw-credential-update.js",
|
||||||
|
"generate": "tsc && node dist/apps/desktop/native-messaging-test-runner/src/commands/bw-generate-password.js"
|
||||||
|
},
|
||||||
|
"author": "Bitwarden Inc. <hello@bitwarden.com> (https://bitwarden.com)",
|
||||||
|
"license": "GPL-3.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@bitwarden/common": "file:../../../libs/common",
|
||||||
|
"@bitwarden/node": "file:../../../libs/node",
|
||||||
|
"module-alias": "^2.2.2",
|
||||||
|
"node-ipc": "9.2.1",
|
||||||
|
"ts-node": "^10.9.1",
|
||||||
|
"uuid": "^8.3.2",
|
||||||
|
"yargs": "^17.5.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tsconfig/node16": "^1.0.3",
|
||||||
|
"@types/node": "^18.6.5",
|
||||||
|
"@types/node-ipc": "9.2.0",
|
||||||
|
"typescript": "^4.7.4"
|
||||||
|
},
|
||||||
|
"_moduleAliases": {
|
||||||
|
"@bitwarden/common": "dist/libs/common/src",
|
||||||
|
"@bitwarden/node/services/nodeCryptoFunction.service": "dist/libs/node/src/services/nodeCryptoFunction.service"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import "module-alias/register";
|
||||||
|
|
||||||
|
import yargs from "yargs";
|
||||||
|
import { hideBin } from "yargs/helpers";
|
||||||
|
|
||||||
|
import { NativeMessagingVersion } from "@bitwarden/common/enums/nativeMessagingVersion";
|
||||||
|
|
||||||
|
import { CredentialCreatePayload } from "../../../src/models/nativeMessaging/encryptedMessagePayloads/credentialCreatePayload";
|
||||||
|
import { LogUtils } from "../logUtils";
|
||||||
|
import NativeMessageService from "../nativeMessageService";
|
||||||
|
import * as config from "../variables";
|
||||||
|
|
||||||
|
const argv: any = yargs(hideBin(process.argv)).option("name", {
|
||||||
|
alias: "n",
|
||||||
|
demand: true,
|
||||||
|
describe: "Name that the created login will be given",
|
||||||
|
type: "string",
|
||||||
|
}).argv;
|
||||||
|
|
||||||
|
const { name } = argv;
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const nativeMessageService = new NativeMessageService(NativeMessagingVersion.One);
|
||||||
|
// Handshake
|
||||||
|
LogUtils.logInfo("Sending Handshake");
|
||||||
|
const handshakeResponse = await nativeMessageService.sendHandshake(
|
||||||
|
config.testRsaPublicKey,
|
||||||
|
config.applicationName
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!handshakeResponse.status) {
|
||||||
|
LogUtils.logError(" Handshake failed. Error was: " + handshakeResponse.error);
|
||||||
|
nativeMessageService.disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get active account userId
|
||||||
|
const status = await nativeMessageService.checkStatus(handshakeResponse.sharedKey);
|
||||||
|
|
||||||
|
const activeUser = status.payload.filter((a) => a.active === true && a.status === "unlocked")[0];
|
||||||
|
if (activeUser === undefined) {
|
||||||
|
LogUtils.logError("No active or unlocked user");
|
||||||
|
}
|
||||||
|
LogUtils.logInfo("Active userId: " + activeUser.id);
|
||||||
|
|
||||||
|
LogUtils.logSuccess("Handshake success response");
|
||||||
|
const response = await nativeMessageService.credentialCreation(handshakeResponse.sharedKey, {
|
||||||
|
name: name,
|
||||||
|
userName: "SuperAwesomeUser",
|
||||||
|
password: "dolhpin",
|
||||||
|
uri: "google.com",
|
||||||
|
userId: activeUser.id,
|
||||||
|
} as CredentialCreatePayload);
|
||||||
|
|
||||||
|
if (response.payload.status === "failure") {
|
||||||
|
LogUtils.logError("Failure response returned ");
|
||||||
|
} else if (response.payload.status === "success") {
|
||||||
|
LogUtils.logSuccess("Success response returned ");
|
||||||
|
} else if (response.payload.error === "locked") {
|
||||||
|
LogUtils.logError("Error: vault is locked");
|
||||||
|
} else {
|
||||||
|
LogUtils.logWarning("Other response: ", response);
|
||||||
|
}
|
||||||
|
|
||||||
|
nativeMessageService.disconnect();
|
||||||
|
})();
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import "module-alias/register";
|
||||||
|
|
||||||
|
import yargs from "yargs";
|
||||||
|
import { hideBin } from "yargs/helpers";
|
||||||
|
|
||||||
|
import { NativeMessagingVersion } from "@bitwarden/common/enums/nativeMessagingVersion";
|
||||||
|
|
||||||
|
import { LogUtils } from "../logUtils";
|
||||||
|
import NativeMessageService from "../nativeMessageService";
|
||||||
|
import * as config from "../variables";
|
||||||
|
|
||||||
|
const argv: any = yargs(hideBin(process.argv)).option("uri", {
|
||||||
|
alias: "u",
|
||||||
|
demand: true,
|
||||||
|
describe: "The uri to retrieve logins for",
|
||||||
|
type: "string",
|
||||||
|
}).argv;
|
||||||
|
|
||||||
|
const { uri } = argv;
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const nativeMessageService = new NativeMessageService(NativeMessagingVersion.One);
|
||||||
|
// Handshake
|
||||||
|
LogUtils.logInfo("Sending Handshake");
|
||||||
|
const handshakeResponse = await nativeMessageService.sendHandshake(
|
||||||
|
config.testRsaPublicKey,
|
||||||
|
config.applicationName
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!handshakeResponse.status) {
|
||||||
|
LogUtils.logError(" Handshake failed. Error was: " + handshakeResponse.error);
|
||||||
|
nativeMessageService.disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogUtils.logSuccess("Handshake success response");
|
||||||
|
const response = await nativeMessageService.credentialRetrieval(handshakeResponse.sharedKey, uri);
|
||||||
|
|
||||||
|
if (response.payload.error != null) {
|
||||||
|
LogUtils.logError("Error response returned: ", response.payload.error);
|
||||||
|
} else {
|
||||||
|
LogUtils.logSuccess("Credentials returned ", response);
|
||||||
|
}
|
||||||
|
|
||||||
|
nativeMessageService.disconnect();
|
||||||
|
})();
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import "module-alias/register";
|
||||||
|
|
||||||
|
import yargs from "yargs";
|
||||||
|
import { hideBin } from "yargs/helpers";
|
||||||
|
|
||||||
|
import { NativeMessagingVersion } from "@bitwarden/common/enums/nativeMessagingVersion";
|
||||||
|
|
||||||
|
import { CredentialUpdatePayload } from "../../../src/models/nativeMessaging/encryptedMessagePayloads/credentialUpdatePayload";
|
||||||
|
import { LogUtils } from "../logUtils";
|
||||||
|
import NativeMessageService from "../nativeMessageService";
|
||||||
|
import * as config from "../variables";
|
||||||
|
|
||||||
|
// Command line arguments
|
||||||
|
const argv: any = yargs(hideBin(process.argv))
|
||||||
|
.option("name", {
|
||||||
|
alias: "n",
|
||||||
|
demand: true,
|
||||||
|
describe: "Name that the updated login will be given",
|
||||||
|
type: "string",
|
||||||
|
})
|
||||||
|
.option("username", {
|
||||||
|
alias: "u",
|
||||||
|
demand: true,
|
||||||
|
describe: "Username that the login will be given",
|
||||||
|
type: "string",
|
||||||
|
})
|
||||||
|
.option("password", {
|
||||||
|
alias: "p",
|
||||||
|
demand: true,
|
||||||
|
describe: "Password that the login will be given",
|
||||||
|
type: "string",
|
||||||
|
})
|
||||||
|
.option("uri", {
|
||||||
|
demand: true,
|
||||||
|
describe: "Uri that the login will be given",
|
||||||
|
type: "string",
|
||||||
|
})
|
||||||
|
.option("credentialId", {
|
||||||
|
demand: true,
|
||||||
|
describe: "GUID of the credential to update",
|
||||||
|
type: "string",
|
||||||
|
}).argv;
|
||||||
|
|
||||||
|
const { name, username, password, uri, credentialId } = argv;
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const nativeMessageService = new NativeMessageService(NativeMessagingVersion.One);
|
||||||
|
// Handshake
|
||||||
|
LogUtils.logInfo("Sending Handshake");
|
||||||
|
const handshakeResponse = await nativeMessageService.sendHandshake(
|
||||||
|
config.testRsaPublicKey,
|
||||||
|
config.applicationName
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!handshakeResponse.status) {
|
||||||
|
LogUtils.logError(" Handshake failed. Error was: " + handshakeResponse.error);
|
||||||
|
nativeMessageService.disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LogUtils.logSuccess("Handshake success response");
|
||||||
|
|
||||||
|
// Get active account userId
|
||||||
|
const status = await nativeMessageService.checkStatus(handshakeResponse.sharedKey);
|
||||||
|
|
||||||
|
const activeUser = status.payload.filter((a) => a.active === true && a.status === "unlocked")[0];
|
||||||
|
if (activeUser === undefined) {
|
||||||
|
LogUtils.logError("No active or unlocked user");
|
||||||
|
}
|
||||||
|
LogUtils.logInfo("Active userId: " + activeUser.id);
|
||||||
|
|
||||||
|
const response = await nativeMessageService.credentialUpdate(handshakeResponse.sharedKey, {
|
||||||
|
name: name,
|
||||||
|
password: password,
|
||||||
|
userName: username,
|
||||||
|
uri: uri,
|
||||||
|
userId: activeUser.id,
|
||||||
|
credentialId: credentialId,
|
||||||
|
} as CredentialUpdatePayload);
|
||||||
|
|
||||||
|
if (response.payload.status === "failure") {
|
||||||
|
LogUtils.logError("Failure response returned ");
|
||||||
|
} else if (response.payload.status === "success") {
|
||||||
|
LogUtils.logSuccess("Success response returned ");
|
||||||
|
} else {
|
||||||
|
LogUtils.logWarning("Other response: ", response);
|
||||||
|
}
|
||||||
|
|
||||||
|
nativeMessageService.disconnect();
|
||||||
|
})();
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import "module-alias/register";
|
||||||
|
|
||||||
|
import yargs from "yargs";
|
||||||
|
import { hideBin } from "yargs/helpers";
|
||||||
|
|
||||||
|
import { NativeMessagingVersion } from "@bitwarden/common/enums/nativeMessagingVersion";
|
||||||
|
|
||||||
|
import { LogUtils } from "../logUtils";
|
||||||
|
import NativeMessageService from "../nativeMessageService";
|
||||||
|
import * as config from "../variables";
|
||||||
|
|
||||||
|
const argv: any = yargs(hideBin(process.argv)).option("userId", {
|
||||||
|
alias: "u",
|
||||||
|
demand: true,
|
||||||
|
describe: "UserId to generate password for",
|
||||||
|
type: "string",
|
||||||
|
}).argv;
|
||||||
|
|
||||||
|
const { userId } = argv;
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const nativeMessageService = new NativeMessageService(NativeMessagingVersion.One);
|
||||||
|
// Handshake
|
||||||
|
LogUtils.logInfo("Sending Handshake");
|
||||||
|
const handshakeResponse = await nativeMessageService.sendHandshake(
|
||||||
|
config.testRsaPublicKey,
|
||||||
|
config.applicationName
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!handshakeResponse.status) {
|
||||||
|
LogUtils.logError(" Handshake failed. Error was: " + handshakeResponse.error);
|
||||||
|
nativeMessageService.disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogUtils.logSuccess("Handshake success response");
|
||||||
|
const response = await nativeMessageService.generatePassword(handshakeResponse.sharedKey, userId);
|
||||||
|
|
||||||
|
if (response.payload.error != null) {
|
||||||
|
LogUtils.logError("Error response returned: ", response.payload.error);
|
||||||
|
} else {
|
||||||
|
LogUtils.logSuccess("Response: ", response);
|
||||||
|
}
|
||||||
|
|
||||||
|
nativeMessageService.disconnect();
|
||||||
|
})();
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import "module-alias/register";
|
||||||
|
|
||||||
|
import { NativeMessagingVersion } from "@bitwarden/common/enums/nativeMessagingVersion";
|
||||||
|
|
||||||
|
import { LogUtils } from "../logUtils";
|
||||||
|
import NativeMessageService from "../nativeMessageService";
|
||||||
|
import * as config from "../variables";
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const nativeMessageService = new NativeMessageService(NativeMessagingVersion.One);
|
||||||
|
|
||||||
|
const response = await nativeMessageService.sendHandshake(
|
||||||
|
config.testRsaPublicKey,
|
||||||
|
config.applicationName
|
||||||
|
);
|
||||||
|
LogUtils.logSuccess("Received response to handshake request");
|
||||||
|
if (response.status) {
|
||||||
|
LogUtils.logSuccess("Handshake success response");
|
||||||
|
} else if (response.error === "canceled") {
|
||||||
|
LogUtils.logWarning("Handshake canceled by user");
|
||||||
|
} else {
|
||||||
|
LogUtils.logError("Handshake failure response");
|
||||||
|
}
|
||||||
|
nativeMessageService.disconnect();
|
||||||
|
})();
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import "module-alias/register";
|
||||||
|
|
||||||
|
import { NativeMessagingVersion } from "@bitwarden/common/enums/nativeMessagingVersion";
|
||||||
|
|
||||||
|
import { LogUtils } from "../logUtils";
|
||||||
|
import NativeMessageService from "../nativeMessageService";
|
||||||
|
import * as config from "../variables";
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const nativeMessageService = new NativeMessageService(NativeMessagingVersion.One);
|
||||||
|
|
||||||
|
LogUtils.logInfo("Sending Handshake");
|
||||||
|
const handshakeResponse = await nativeMessageService.sendHandshake(
|
||||||
|
config.testRsaPublicKey,
|
||||||
|
config.applicationName
|
||||||
|
);
|
||||||
|
LogUtils.logSuccess("Received response to handshake request");
|
||||||
|
|
||||||
|
if (!handshakeResponse.status) {
|
||||||
|
LogUtils.logError(" Handshake failed. Error was: " + handshakeResponse.error);
|
||||||
|
nativeMessageService.disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LogUtils.logSuccess("Handshake success response");
|
||||||
|
const status = await nativeMessageService.checkStatus(handshakeResponse.sharedKey);
|
||||||
|
|
||||||
|
LogUtils.logSuccess("Status output is: ", status);
|
||||||
|
nativeMessageService.disconnect();
|
||||||
|
})();
|
||||||
26
apps/desktop/native-messaging-test-runner/src/deferred.ts
Normal file
26
apps/desktop/native-messaging-test-runner/src/deferred.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// Wrapper for a promise that we can await the promise in one case
|
||||||
|
// while allowing an unrelated event to fulfill it elsewhere.
|
||||||
|
export default class Deferred<T> {
|
||||||
|
private promise: Promise<T>;
|
||||||
|
private resolver: (T?) => void;
|
||||||
|
private rejecter: (Error?) => void;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.promise = new Promise<T>((resolve, reject) => {
|
||||||
|
this.resolver = resolve;
|
||||||
|
this.rejecter = reject;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(value?: T) {
|
||||||
|
this.resolver(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
reject(error?: Error) {
|
||||||
|
this.rejecter(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
getPromise(): Promise<T> {
|
||||||
|
return this.promise;
|
||||||
|
}
|
||||||
|
}
|
||||||
166
apps/desktop/native-messaging-test-runner/src/ipcService.ts
Normal file
166
apps/desktop/native-messaging-test-runner/src/ipcService.ts
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
import { homedir } from "os";
|
||||||
|
|
||||||
|
import * as NodeIPC from "node-ipc";
|
||||||
|
|
||||||
|
import { MessageCommon } from "../../src/models/nativeMessaging/messageCommon";
|
||||||
|
import { UnencryptedMessageResponse } from "../../src/models/nativeMessaging/unencryptedMessageResponse";
|
||||||
|
|
||||||
|
import Deferred from "./deferred";
|
||||||
|
import { race } from "./race";
|
||||||
|
|
||||||
|
NodeIPC.config.id = "native-messaging-test-runner";
|
||||||
|
NodeIPC.config.maxRetries = 0;
|
||||||
|
NodeIPC.config.silent = true;
|
||||||
|
|
||||||
|
const DESKTOP_APP_PATH = `${homedir}/tmp/app.bitwarden`;
|
||||||
|
const DEFAULT_MESSAGE_TIMEOUT = 10 * 1000; // 10 seconds
|
||||||
|
|
||||||
|
export type MessageHandler = (MessageCommon) => void;
|
||||||
|
|
||||||
|
export enum IPCConnectionState {
|
||||||
|
Disconnected = "disconnected",
|
||||||
|
Connecting = "connecting",
|
||||||
|
Connected = "connected",
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IPCOptions = {
|
||||||
|
overrideTimeout?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class IPCService {
|
||||||
|
// The current connection state of the socket.
|
||||||
|
private connectionState: IPCConnectionState = IPCConnectionState.Disconnected;
|
||||||
|
|
||||||
|
// Messages that have been sent, but have not yet received responses
|
||||||
|
private pendingMessages = new Map<string, Deferred<UnencryptedMessageResponse>>();
|
||||||
|
|
||||||
|
// A set of deferred promises that are awaiting socket connection
|
||||||
|
private awaitingConnection = new Set<Deferred<void>>();
|
||||||
|
|
||||||
|
constructor(private socketName: string, private messageHandler: MessageHandler) {}
|
||||||
|
|
||||||
|
async connect(): Promise<void> {
|
||||||
|
console.log("[IPCService] connecting...");
|
||||||
|
if (this.connectionState === IPCConnectionState.Connected) {
|
||||||
|
// Socket is already connected. Don't throw, just allow the callsite to proceed
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const deferredConnections = new Deferred<void>();
|
||||||
|
|
||||||
|
this.awaitingConnection.add(deferredConnections);
|
||||||
|
|
||||||
|
// If the current connection state is disconnected, we should start trying to connect.
|
||||||
|
// The only other possible connection state at this point is "connecting" and if this
|
||||||
|
// is the case, we just want to add a deferred promise to the awaitingConnection collection
|
||||||
|
// and not try to initiate the connection again.
|
||||||
|
if (this.connectionState === IPCConnectionState.Disconnected) {
|
||||||
|
this._connect();
|
||||||
|
}
|
||||||
|
|
||||||
|
return deferredConnections.getPromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _connect() {
|
||||||
|
this.connectionState = IPCConnectionState.Connecting;
|
||||||
|
|
||||||
|
NodeIPC.connectTo(this.socketName, DESKTOP_APP_PATH, () => {
|
||||||
|
// Process incoming message
|
||||||
|
this.getSocket().on("message", (message: any) => {
|
||||||
|
this.processMessage(message);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.getSocket().on("error", (error: Error) => {
|
||||||
|
// Only makes sense as long as config.maxRetries stays set to 0. Otherwise this will be
|
||||||
|
// invoked multiple times each time a connection error happens
|
||||||
|
console.log("[IPCService] errored");
|
||||||
|
console.log(
|
||||||
|
"\x1b[33m Please make sure the desktop app is running locally and 'Allow DuckDuckGo browser integration' setting is enabled \x1b[0m"
|
||||||
|
);
|
||||||
|
this.awaitingConnection.forEach((deferred) => {
|
||||||
|
console.log(`rejecting: ${deferred}`);
|
||||||
|
deferred.reject(error);
|
||||||
|
});
|
||||||
|
this.awaitingConnection.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.getSocket().on("connect", () => {
|
||||||
|
console.log("[IPCService] connected");
|
||||||
|
this.connectionState = IPCConnectionState.Connected;
|
||||||
|
|
||||||
|
this.awaitingConnection.forEach((deferred) => {
|
||||||
|
deferred.resolve(null);
|
||||||
|
});
|
||||||
|
this.awaitingConnection.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.getSocket().on("disconnect", () => {
|
||||||
|
console.log("[IPCService] disconnected");
|
||||||
|
this.connectionState = IPCConnectionState.Disconnected;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect() {
|
||||||
|
console.log("[IPCService] disconnecting...");
|
||||||
|
if (this.connectionState !== IPCConnectionState.Disconnected) {
|
||||||
|
NodeIPC.disconnect(this.socketName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendMessage(
|
||||||
|
message: MessageCommon,
|
||||||
|
options: IPCOptions = {}
|
||||||
|
): Promise<UnencryptedMessageResponse> {
|
||||||
|
console.log("[IPCService] sendMessage");
|
||||||
|
if (this.pendingMessages.has(message.messageId)) {
|
||||||
|
throw new Error(`A message with the id: ${message.messageId} has already been sent.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new deferred promise that allows us to convert a message received over the IPC socket
|
||||||
|
// into a response for a message that we previously sent. This mechanism relies on the fact that we
|
||||||
|
// create a unique message id and attach it with each message. Response messages are expected to
|
||||||
|
// include the message id of the message they are responding to.
|
||||||
|
const deferred = new Deferred<UnencryptedMessageResponse>();
|
||||||
|
|
||||||
|
this.pendingMessages.set(message.messageId, deferred);
|
||||||
|
|
||||||
|
this.getSocket().emit("message", message);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Since we can not guarentee that a response message will ever be sent, we put a timeout
|
||||||
|
// on messages
|
||||||
|
return race({
|
||||||
|
promise: deferred.getPromise(),
|
||||||
|
timeout: options?.overrideTimeout ?? DEFAULT_MESSAGE_TIMEOUT,
|
||||||
|
error: new Error(`Message: ${message.messageId} timed out`),
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
// If there is a timeout, remove the message from the pending messages set
|
||||||
|
// before triggering error handling elsewhere.
|
||||||
|
this.pendingMessages.delete(message.messageId);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getSocket() {
|
||||||
|
return NodeIPC.of[this.socketName];
|
||||||
|
}
|
||||||
|
|
||||||
|
private processMessage(message: any) {
|
||||||
|
// If the message is a response to a previous message, resolve the deferred promise that
|
||||||
|
// is awaiting that response. Otherwise, assume this was a new message that wasn't sent as a
|
||||||
|
// response and invoke the message handler.
|
||||||
|
if (message.messageId && this.pendingMessages.has(message.messageId)) {
|
||||||
|
const deferred = this.pendingMessages.get(message.messageId);
|
||||||
|
|
||||||
|
// In the future, this could be improved to add ability to reject, but most messages coming in are
|
||||||
|
// encrypted at this point so we're unable to determine if they contain error info.
|
||||||
|
deferred.resolve(message);
|
||||||
|
|
||||||
|
this.pendingMessages.delete(message.messageId);
|
||||||
|
} else {
|
||||||
|
this.messageHandler(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
apps/desktop/native-messaging-test-runner/src/logUtils.ts
Normal file
29
apps/desktop/native-messaging-test-runner/src/logUtils.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
|
||||||
|
// Class for logging messages with colors for ease of reading important info
|
||||||
|
// Reference: https://stackoverflow.com/a/41407246
|
||||||
|
export class LogUtils {
|
||||||
|
static logSuccess(message: string, payload?: any): void {
|
||||||
|
this.logFormat(message, "32", payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
static logWarning(message: string, payload?: any): void {
|
||||||
|
this.logFormat(message, "33", payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
static logError(message: string, payload?: any): void {
|
||||||
|
this.logFormat(message, "31", payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
static logInfo(message: string, payload?: any): void {
|
||||||
|
this.logFormat(message, "36", payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static logFormat(message: string, color: string, payload?: any) {
|
||||||
|
if (payload) {
|
||||||
|
console.log(`\x1b[${color}m ${message} \x1b[0m`, payload);
|
||||||
|
} else {
|
||||||
|
console.log(`\x1b[${color}m ${message} \x1b[0m`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,235 @@
|
|||||||
|
import "module-alias/register";
|
||||||
|
|
||||||
|
import { v4 as uuidv4 } from "uuid";
|
||||||
|
|
||||||
|
import { Utils } from "@bitwarden/common/misc/utils";
|
||||||
|
import { EncString } from "@bitwarden/common/models/domain/encString";
|
||||||
|
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey";
|
||||||
|
import { ConsoleLogService } from "@bitwarden/common/services/consoleLog.service";
|
||||||
|
import { EncryptService } from "@bitwarden/common/services/encrypt.service";
|
||||||
|
import { NodeCryptoFunctionService } from "@bitwarden/node/services/nodeCryptoFunction.service";
|
||||||
|
|
||||||
|
import { DecryptedCommandData } from "../../src/models/nativeMessaging/decryptedCommandData";
|
||||||
|
import { EncryptedMessage } from "../../src/models/nativeMessaging/encryptedMessage";
|
||||||
|
import { CredentialCreatePayload } from "../../src/models/nativeMessaging/encryptedMessagePayloads/credentialCreatePayload";
|
||||||
|
import { CredentialUpdatePayload } from "../../src/models/nativeMessaging/encryptedMessagePayloads/credentialUpdatePayload";
|
||||||
|
import { EncryptedMessageResponse } from "../../src/models/nativeMessaging/encryptedMessageResponse";
|
||||||
|
import { MessageCommon } from "../../src/models/nativeMessaging/messageCommon";
|
||||||
|
import { UnencryptedMessage } from "../../src/models/nativeMessaging/unencryptedMessage";
|
||||||
|
import { UnencryptedMessageResponse } from "../../src/models/nativeMessaging/unencryptedMessageResponse";
|
||||||
|
|
||||||
|
import IPCService, { IPCOptions } from "./ipcService";
|
||||||
|
import * as config from "./variables";
|
||||||
|
|
||||||
|
type HandshakeResponse = {
|
||||||
|
status: boolean;
|
||||||
|
sharedKey: string;
|
||||||
|
error?: "canceled" | "cannot-decrypt";
|
||||||
|
};
|
||||||
|
|
||||||
|
const CONFIRMATION_MESSAGE_TIMEOUT = 100 * 1000; // 100 seconds
|
||||||
|
|
||||||
|
export default class NativeMessageService {
|
||||||
|
private ipcService: IPCService;
|
||||||
|
private nodeCryptoFunctionService: NodeCryptoFunctionService;
|
||||||
|
private encryptService: EncryptService;
|
||||||
|
|
||||||
|
constructor(private apiVersion: number) {
|
||||||
|
console.log("Starting native messaging service");
|
||||||
|
this.ipcService = new IPCService(`bitwarden`, (rawMessage) => {
|
||||||
|
console.log(`Received unexpected: `, rawMessage);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.nodeCryptoFunctionService = new NodeCryptoFunctionService();
|
||||||
|
this.encryptService = new EncryptService(
|
||||||
|
this.nodeCryptoFunctionService,
|
||||||
|
new ConsoleLogService(false),
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commands
|
||||||
|
|
||||||
|
async sendHandshake(publicKey: string, applicationName: string): Promise<HandshakeResponse> {
|
||||||
|
const rawResponse = await this.sendUnencryptedMessage(
|
||||||
|
{
|
||||||
|
command: "bw-handshake",
|
||||||
|
payload: {
|
||||||
|
publicKey,
|
||||||
|
applicationName: applicationName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
overrideTimeout: CONFIRMATION_MESSAGE_TIMEOUT,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return rawResponse.payload as HandshakeResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
async checkStatus(key: string): Promise<DecryptedCommandData> {
|
||||||
|
const encryptedCommand = await this.encryptCommandData(
|
||||||
|
{
|
||||||
|
command: "bw-status",
|
||||||
|
},
|
||||||
|
key
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await this.sendEncryptedMessage({
|
||||||
|
encryptedCommand,
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.decryptResponsePayload(response.encryptedPayload, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
async credentialRetrieval(key: string, uri: string): Promise<DecryptedCommandData> {
|
||||||
|
const encryptedCommand = await this.encryptCommandData(
|
||||||
|
{
|
||||||
|
command: "bw-credential-retrieval",
|
||||||
|
payload: {
|
||||||
|
uri: uri,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
key
|
||||||
|
);
|
||||||
|
const response = await this.sendEncryptedMessage({
|
||||||
|
encryptedCommand,
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.decryptResponsePayload(response.encryptedPayload, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
async credentialCreation(
|
||||||
|
key: string,
|
||||||
|
credentialData: CredentialCreatePayload
|
||||||
|
): Promise<DecryptedCommandData> {
|
||||||
|
const encryptedCommand = await this.encryptCommandData(
|
||||||
|
{
|
||||||
|
command: "bw-credential-create",
|
||||||
|
payload: credentialData,
|
||||||
|
},
|
||||||
|
key
|
||||||
|
);
|
||||||
|
const response = await this.sendEncryptedMessage({
|
||||||
|
encryptedCommand,
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.decryptResponsePayload(response.encryptedPayload, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
async credentialUpdate(
|
||||||
|
key: string,
|
||||||
|
credentialData: CredentialUpdatePayload
|
||||||
|
): Promise<DecryptedCommandData> {
|
||||||
|
const encryptedCommand = await this.encryptCommandData(
|
||||||
|
{
|
||||||
|
command: "bw-credential-update",
|
||||||
|
payload: credentialData,
|
||||||
|
},
|
||||||
|
key
|
||||||
|
);
|
||||||
|
const response = await this.sendEncryptedMessage({
|
||||||
|
encryptedCommand,
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.decryptResponsePayload(response.encryptedPayload, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
async generatePassword(key: string, userId: string): Promise<DecryptedCommandData> {
|
||||||
|
const encryptedCommand = await this.encryptCommandData(
|
||||||
|
{
|
||||||
|
command: "bw-generate-password",
|
||||||
|
payload: {
|
||||||
|
userId: userId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
key
|
||||||
|
);
|
||||||
|
const response = await this.sendEncryptedMessage({
|
||||||
|
encryptedCommand,
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.decryptResponsePayload(response.encryptedPayload, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private message sending
|
||||||
|
|
||||||
|
private async sendEncryptedMessage(
|
||||||
|
message: Omit<EncryptedMessage, keyof MessageCommon>,
|
||||||
|
options: IPCOptions = {}
|
||||||
|
): Promise<EncryptedMessageResponse> {
|
||||||
|
const result = await this.sendMessage(message, options);
|
||||||
|
return result as EncryptedMessageResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async sendUnencryptedMessage(
|
||||||
|
message: Omit<UnencryptedMessage, keyof MessageCommon>,
|
||||||
|
options: IPCOptions = {}
|
||||||
|
): Promise<UnencryptedMessageResponse> {
|
||||||
|
const result = await this.sendMessage(message, options);
|
||||||
|
return result as UnencryptedMessageResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async sendMessage(
|
||||||
|
message:
|
||||||
|
| Omit<UnencryptedMessage, keyof MessageCommon>
|
||||||
|
| Omit<EncryptedMessage, keyof MessageCommon>,
|
||||||
|
options: IPCOptions
|
||||||
|
): Promise<EncryptedMessageResponse | UnencryptedMessageResponse> {
|
||||||
|
// Attempt to connect before sending any messages. If the connection has already
|
||||||
|
// been made, this is a NOOP within the IPCService.
|
||||||
|
await this.ipcService.connect();
|
||||||
|
|
||||||
|
const commonFields: MessageCommon = {
|
||||||
|
// Create a messageId that can be used as a lookup when we get a response
|
||||||
|
messageId: uuidv4(),
|
||||||
|
version: this.apiVersion,
|
||||||
|
};
|
||||||
|
const fullMessage: UnencryptedMessage | EncryptedMessage = {
|
||||||
|
...message,
|
||||||
|
...commonFields,
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log(`[NativeMessageService] sendMessage with id: ${fullMessage.messageId}`);
|
||||||
|
|
||||||
|
const response = await this.ipcService.sendMessage(fullMessage, options);
|
||||||
|
|
||||||
|
console.log(`[NativeMessageService] received response for message: ${fullMessage.messageId}`);
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect() {
|
||||||
|
this.ipcService.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data Encryption
|
||||||
|
private async encryptCommandData(
|
||||||
|
commandData: DecryptedCommandData,
|
||||||
|
key: string
|
||||||
|
): Promise<EncString> {
|
||||||
|
const commandDataString = JSON.stringify(commandData);
|
||||||
|
|
||||||
|
const sharedKey = await this.getSharedKeyForKey(key);
|
||||||
|
|
||||||
|
return this.encryptService.encrypt(commandDataString, sharedKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async decryptResponsePayload(
|
||||||
|
payload: EncString,
|
||||||
|
key: string
|
||||||
|
): Promise<DecryptedCommandData> {
|
||||||
|
const sharedKey = await this.getSharedKeyForKey(key);
|
||||||
|
const decrypted = await this.encryptService.decryptToUtf8(payload, sharedKey);
|
||||||
|
|
||||||
|
return JSON.parse(decrypted);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getSharedKeyForKey(key: string): Promise<SymmetricCryptoKey> {
|
||||||
|
const dataBuffer = Utils.fromB64ToArray(key).buffer;
|
||||||
|
const privKey = Utils.fromB64ToArray(config.testRsaPrivateKey).buffer;
|
||||||
|
|
||||||
|
return new SymmetricCryptoKey(
|
||||||
|
await this.nodeCryptoFunctionService.rsaDecrypt(dataBuffer, privKey, "sha1")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
25
apps/desktop/native-messaging-test-runner/src/race.ts
Normal file
25
apps/desktop/native-messaging-test-runner/src/race.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
export const race = <T>({
|
||||||
|
promise,
|
||||||
|
timeout,
|
||||||
|
error,
|
||||||
|
}: {
|
||||||
|
promise: Promise<T>;
|
||||||
|
timeout: number;
|
||||||
|
error?: Error;
|
||||||
|
}) => {
|
||||||
|
let timer = null;
|
||||||
|
|
||||||
|
// Similar to Promise.all, but instead of waiting for all, it resolves once one promise finishes.
|
||||||
|
// Using this so we can reject if the timeout threshold is hit
|
||||||
|
return Promise.race([
|
||||||
|
new Promise<T>((_, reject) => {
|
||||||
|
timer = setTimeout(reject, timeout, error);
|
||||||
|
return timer;
|
||||||
|
}),
|
||||||
|
|
||||||
|
promise.then((value) => {
|
||||||
|
clearTimeout(timer);
|
||||||
|
return value;
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
};
|
||||||
27
apps/desktop/native-messaging-test-runner/src/variables.ts
Normal file
27
apps/desktop/native-messaging-test-runner/src/variables.ts
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
export const applicationName = "Native Messaging Test Runner";
|
||||||
|
export const encryptionAlogrithm = "sha1";
|
||||||
|
export const testRsaPublicKey =
|
||||||
|
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl0Vawl/toXzkEvB82FEtqHP" +
|
||||||
|
"4xlU2ab/v0crqIfXfIoWF/XXdHGIdrZeilnRXPPJT1B9dTsasttEZNnua/0Rek/cjNDHtzT52irfoZYS7X6HNIfOi54Q+egP" +
|
||||||
|
"RQ1H7iNHVZz3K8Db9GCSKPeC8MbW6gVCzb15esCe1gGzg6wkMuWYDFYPoh/oBqcIqrGah7firqB1nDedzEjw32heP2DAffVN" +
|
||||||
|
"084iTDjiWrJNUxBJ2pDD5Z9dT3MzQ2s09ew1yMWK2z37rT3YerC7OgEDmo3WYo3xL3qYJznu3EO2nmrYjiRa40wKSjxsTlUc" +
|
||||||
|
"xDF+F0uMW8oR9EMUHgepdepfAtLsSAQIDAQAB";
|
||||||
|
export const testRsaPrivateKey =
|
||||||
|
"MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXRVrCX+2hfOQS8Hz" +
|
||||||
|
"YUS2oc/jGVTZpv+/Ryuoh9d8ihYX9dd0cYh2tl6KWdFc88lPUH11Oxqy20Rk2e5r/RF6T9yM0Me3NPnaKt+hlhLtfoc0h86L" +
|
||||||
|
"nhD56A9FDUfuI0dVnPcrwNv0YJIo94LwxtbqBULNvXl6wJ7WAbODrCQy5ZgMVg+iH+gGpwiqsZqHt+KuoHWcN53MSPDfaF4/" +
|
||||||
|
"YMB99U3TziJMOOJask1TEEnakMPln11PczNDazT17DXIxYrbPfutPdh6sLs6AQOajdZijfEvepgnOe7cQ7aeatiOJFrjTApK" +
|
||||||
|
"PGxOVRzEMX4XS4xbyhH0QxQeB6l16l8C0uxIBAgMBAAECggEASaWfeVDA3cVzOPFSpvJm20OTE+R6uGOU+7vh36TX/POq92q" +
|
||||||
|
"Buwbd0h0oMD32FxsXywd2IxtBDUSiFM9699qufTVuM0Q3tZw6lHDTOVG08+tPdr8qSbMtw7PGFxN79fHLBxejjO4IrM9lapj" +
|
||||||
|
"WpxEF+11x7r+wM+0xRZQ8sNFYG46aPfIaty4BGbL0I2DQ2y8I57iBCAy69eht59NLMm27fRWGJIWCuBIjlpfzET1j2HLXUIh" +
|
||||||
|
"5bTBNzqaN039WH49HczGE3mQKVEJZc/efk3HaVd0a1Sjzyn0QY+N1jtZN3jTRbuDWA1AknkX1LX/0tUhuS3/7C3ejHxjw4Dk" +
|
||||||
|
"1ZLo5/QKBgQDIWvqFn0+IKRSu6Ua2hDsufIHHUNLelbfLUMmFthxabcUn4zlvIscJO00Tq/ezopSRRvbGiqnxjv/mYxucvOU" +
|
||||||
|
"BeZtlus0Q9RTACBtw9TGoNTmQbEunJ2FOSlqbQxkBBAjgGEppRPt30iGj/VjAhCATq2MYOa/X4dVR51BqQAFIEwKBgQDBSIf" +
|
||||||
|
"TFKC/hDk6FKZlgwvupWYJyU9RkyfstPErZFmzoKhPkQ3YORo2oeAYmVUbS9I2iIYpYpYQJHX8jMuCbCz4ONxTCuSIXYQYUcU" +
|
||||||
|
"q4PglCKp31xBAE6TN8SvhfME9/MvuDssnQinAHuF0GDAhF646T3LLS1not6Vszv7brwSoGwKBgQC88v/8cGfi80ssQZeMnVv" +
|
||||||
|
"q1UTXIeQcQnoY5lGHJl3K8mbS3TnXE6c9j417Fdz+rj8KWzBzwWXQB5pSPflWcdZO886Xu/mVGmy9RWgLuVFhXwCwsVEPjNX" +
|
||||||
|
"5ramRb0/vY0yzenUCninBsIxFSbIfrPtLUYCc4hpxr+sr2Mg/y6jpvQKBgBezMRRs3xkcuXepuI2R+BCXL1/b02IJTUf1F+1" +
|
||||||
|
"eLLGd7YV0H+J3fgNc7gGWK51hOrF9JBZHBGeOUPlaukmPwiPdtQZpu4QNE3l37VlIpKTF30E6mb+BqR+nht3rUjarnMXgAoE" +
|
||||||
|
"Z18y6/KIjpSMpqC92Nnk/EBM9EYe6Cf4eA9ApAoGAeqEUg46UTlJySkBKURGpIs3v1kkf5I0X8DnOhwb+HPxNaiEdmO7ckm8" +
|
||||||
|
"+tPVgppLcG0+tMdLjigFQiDUQk2y3WjyxP5ZvXu7U96jaJRI8PFMoE06WeVYcdIzrID2HvqH+w0UQJFrLJ/0Mn4stFAEzXKZ" +
|
||||||
|
"BokBGnjFnTnKcs7nv/O8=";
|
||||||
17
apps/desktop/native-messaging-test-runner/tsconfig.json
Normal file
17
apps/desktop/native-messaging-test-runner/tsconfig.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": "./",
|
||||||
|
"outDir": "dist",
|
||||||
|
"target": "es6",
|
||||||
|
"module": "CommonJS",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"sourceMap": false,
|
||||||
|
"declaration": false,
|
||||||
|
"paths": {
|
||||||
|
"@src/*": ["src/*"],
|
||||||
|
"@bitwarden/node/*": ["../../../libs/node/src/*"],
|
||||||
|
"@bitwarden/common/*": ["../../../libs/common/src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
@@ -309,6 +309,23 @@
|
|||||||
</div>
|
</div>
|
||||||
<small class="help-block">{{ "enableBrowserIntegrationDesc" | i18n }}</small>
|
<small class="help-block">{{ "enableBrowserIntegrationDesc" | i18n }}</small>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group" *ngIf="showDuckDuckGoIntegrationOption">
|
||||||
|
<div class="checkbox">
|
||||||
|
<label for="enableDuckDuckGoBrowserIntegration">
|
||||||
|
<input
|
||||||
|
id="enableDuckDuckGoBrowserIntegration"
|
||||||
|
type="checkbox"
|
||||||
|
name="enableDuckDuckGoBrowserIntegration"
|
||||||
|
[(ngModel)]="enableDuckDuckGoBrowserIntegration"
|
||||||
|
(change)="saveDdgBrowserIntegration()"
|
||||||
|
/>
|
||||||
|
{{ "enableDuckDuckGoBrowserIntegration" | i18n }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<small class="help-block">{{
|
||||||
|
"enableDuckDuckGoBrowserIntegrationDesc" | i18n
|
||||||
|
}}</small>
|
||||||
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<label for="enableBrowserIntegrationFingerprint">
|
<label for="enableBrowserIntegrationFingerprint">
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { ThemeType } from "@bitwarden/common/enums/themeType";
|
|||||||
import { Utils } from "@bitwarden/common/misc/utils";
|
import { Utils } from "@bitwarden/common/misc/utils";
|
||||||
import { isWindowsStore } from "@bitwarden/electron/utils";
|
import { isWindowsStore } from "@bitwarden/electron/utils";
|
||||||
|
|
||||||
|
import { flagEnabled } from "../../flags";
|
||||||
import { SetPinComponent } from "../components/set-pin.component";
|
import { SetPinComponent } from "../components/set-pin.component";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -28,6 +29,7 @@ export class SettingsComponent implements OnInit {
|
|||||||
pin: boolean = null;
|
pin: boolean = null;
|
||||||
enableFavicons = false;
|
enableFavicons = false;
|
||||||
enableBrowserIntegration = false;
|
enableBrowserIntegration = false;
|
||||||
|
enableDuckDuckGoBrowserIntegration = false;
|
||||||
enableBrowserIntegrationFingerprint = false;
|
enableBrowserIntegrationFingerprint = false;
|
||||||
enableMinToTray = false;
|
enableMinToTray = false;
|
||||||
enableCloseToTray = false;
|
enableCloseToTray = false;
|
||||||
@@ -51,6 +53,7 @@ export class SettingsComponent implements OnInit {
|
|||||||
showAlwaysShowDock = false;
|
showAlwaysShowDock = false;
|
||||||
openAtLogin: boolean;
|
openAtLogin: boolean;
|
||||||
requireEnableTray = false;
|
requireEnableTray = false;
|
||||||
|
showDuckDuckGoIntegrationOption = false;
|
||||||
|
|
||||||
enableTrayText: string;
|
enableTrayText: string;
|
||||||
enableTrayDescText: string;
|
enableTrayDescText: string;
|
||||||
@@ -102,6 +105,9 @@ export class SettingsComponent implements OnInit {
|
|||||||
this.startToTrayText = this.i18nService.t(startToTrayKey);
|
this.startToTrayText = this.i18nService.t(startToTrayKey);
|
||||||
this.startToTrayDescText = this.i18nService.t(startToTrayKey + "Desc");
|
this.startToTrayDescText = this.i18nService.t(startToTrayKey + "Desc");
|
||||||
|
|
||||||
|
// DuckDuckGo browser is only for macos initially
|
||||||
|
this.showDuckDuckGoIntegrationOption = flagEnabled("showDDGSetting") && isMac;
|
||||||
|
|
||||||
this.vaultTimeouts = [
|
this.vaultTimeouts = [
|
||||||
// { name: i18nService.t('immediately'), value: 0 },
|
// { name: i18nService.t('immediately'), value: 0 },
|
||||||
{ name: i18nService.t("oneMinute"), value: 1 },
|
{ name: i18nService.t("oneMinute"), value: 1 },
|
||||||
@@ -188,6 +194,8 @@ export class SettingsComponent implements OnInit {
|
|||||||
// Account preferences
|
// Account preferences
|
||||||
this.enableFavicons = !(await this.stateService.getDisableFavicon());
|
this.enableFavicons = !(await this.stateService.getDisableFavicon());
|
||||||
this.enableBrowserIntegration = await this.stateService.getEnableBrowserIntegration();
|
this.enableBrowserIntegration = await this.stateService.getEnableBrowserIntegration();
|
||||||
|
this.enableDuckDuckGoBrowserIntegration =
|
||||||
|
await this.stateService.getEnableDuckDuckGoBrowserIntegration();
|
||||||
this.enableBrowserIntegrationFingerprint =
|
this.enableBrowserIntegrationFingerprint =
|
||||||
await this.stateService.getEnableBrowserIntegrationFingerprint();
|
await this.stateService.getEnableBrowserIntegrationFingerprint();
|
||||||
this.clearClipboard = await this.stateService.getClearClipboard();
|
this.clearClipboard = await this.stateService.getClearClipboard();
|
||||||
@@ -432,6 +440,22 @@ export class SettingsComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async saveDdgBrowserIntegration() {
|
||||||
|
await this.stateService.setEnableDuckDuckGoBrowserIntegration(
|
||||||
|
this.enableDuckDuckGoBrowserIntegration
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!this.enableBrowserIntegration) {
|
||||||
|
await this.stateService.setDuckDuckGoSharedKey(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.messagingService.send(
|
||||||
|
this.enableDuckDuckGoBrowserIntegration
|
||||||
|
? "enableDuckDuckGoBrowserIntegration"
|
||||||
|
: "disableDuckDuckGoBrowserIntegration"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async saveBrowserIntegrationFingerprint() {
|
async saveBrowserIntegrationFingerprint() {
|
||||||
await this.stateService.setEnableBrowserIntegrationFingerprint(
|
await this.stateService.setEnableBrowserIntegrationFingerprint(
|
||||||
this.enableBrowserIntegrationFingerprint
|
this.enableBrowserIntegrationFingerprint
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ import {
|
|||||||
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
|
import { JslibServicesModule } from "@bitwarden/angular/services/jslib-services.module";
|
||||||
import { AbstractThemingService } from "@bitwarden/angular/services/theming/theming.service.abstraction";
|
import { AbstractThemingService } from "@bitwarden/angular/services/theming/theming.service.abstraction";
|
||||||
import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service";
|
import { AbstractEncryptService } from "@bitwarden/common/abstractions/abstractEncrypt.service";
|
||||||
|
import { AuthService as AuthServiceAbstraction } from "@bitwarden/common/abstractions/auth.service";
|
||||||
import { BroadcasterService as BroadcasterServiceAbstraction } from "@bitwarden/common/abstractions/broadcaster.service";
|
import { BroadcasterService as BroadcasterServiceAbstraction } from "@bitwarden/common/abstractions/broadcaster.service";
|
||||||
|
import { CipherService as CipherServiceAbstraction } from "@bitwarden/common/abstractions/cipher.service";
|
||||||
import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/abstractions/crypto.service";
|
import { CryptoService as CryptoServiceAbstraction } from "@bitwarden/common/abstractions/crypto.service";
|
||||||
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/abstractions/cryptoFunction.service";
|
import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@bitwarden/common/abstractions/cryptoFunction.service";
|
||||||
import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload/fileDownload.service";
|
import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload/fileDownload.service";
|
||||||
@@ -22,8 +24,10 @@ import {
|
|||||||
LogService as LogServiceAbstraction,
|
LogService as LogServiceAbstraction,
|
||||||
} from "@bitwarden/common/abstractions/log.service";
|
} from "@bitwarden/common/abstractions/log.service";
|
||||||
import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/abstractions/messaging.service";
|
import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/abstractions/messaging.service";
|
||||||
|
import { PasswordGenerationService as PasswordGenerationServiceAbstraction } from "@bitwarden/common/abstractions/passwordGeneration.service";
|
||||||
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "@bitwarden/common/abstractions/passwordReprompt.service";
|
import { PasswordRepromptService as PasswordRepromptServiceAbstraction } from "@bitwarden/common/abstractions/passwordReprompt.service";
|
||||||
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/abstractions/platformUtils.service";
|
import { PlatformUtilsService as PlatformUtilsServiceAbstraction } from "@bitwarden/common/abstractions/platformUtils.service";
|
||||||
|
import { PolicyService as PolicyServiceAbstraction } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
|
||||||
import { StateService as StateServiceAbstraction } from "@bitwarden/common/abstractions/state.service";
|
import { StateService as StateServiceAbstraction } from "@bitwarden/common/abstractions/state.service";
|
||||||
import { StateMigrationService as StateMigrationServiceAbstraction } from "@bitwarden/common/abstractions/stateMigration.service";
|
import { StateMigrationService as StateMigrationServiceAbstraction } from "@bitwarden/common/abstractions/stateMigration.service";
|
||||||
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
|
import { AbstractStorageService } from "@bitwarden/common/abstractions/storage.service";
|
||||||
@@ -41,7 +45,9 @@ import { ElectronRendererSecureStorageService } from "@bitwarden/electron/servic
|
|||||||
import { ElectronRendererStorageService } from "@bitwarden/electron/services/electronRendererStorage.service";
|
import { ElectronRendererStorageService } from "@bitwarden/electron/services/electronRendererStorage.service";
|
||||||
|
|
||||||
import { Account } from "../../models/account";
|
import { Account } from "../../models/account";
|
||||||
|
import { EncryptedMessageHandlerService } from "../../services/encryptedMessageHandlerService";
|
||||||
import { I18nService } from "../../services/i18n.service";
|
import { I18nService } from "../../services/i18n.service";
|
||||||
|
import { NativeMessageHandlerService } from "../../services/nativeMessageHandler.service";
|
||||||
import { NativeMessagingService } from "../../services/nativeMessaging.service";
|
import { NativeMessagingService } from "../../services/nativeMessaging.service";
|
||||||
import { PasswordRepromptService } from "../../services/passwordReprompt.service";
|
import { PasswordRepromptService } from "../../services/passwordReprompt.service";
|
||||||
import { StateService } from "../../services/state.service";
|
import { StateService } from "../../services/state.service";
|
||||||
@@ -147,6 +153,28 @@ const RELOAD_CALLBACK = new InjectionToken<() => any>("RELOAD_CALLBACK");
|
|||||||
provide: AbstractThemingService,
|
provide: AbstractThemingService,
|
||||||
useClass: DesktopThemingService,
|
useClass: DesktopThemingService,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: EncryptedMessageHandlerService,
|
||||||
|
deps: [
|
||||||
|
StateServiceAbstraction,
|
||||||
|
AuthServiceAbstraction,
|
||||||
|
CipherServiceAbstraction,
|
||||||
|
PolicyServiceAbstraction,
|
||||||
|
MessagingServiceAbstraction,
|
||||||
|
PasswordGenerationServiceAbstraction,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: NativeMessageHandlerService,
|
||||||
|
deps: [
|
||||||
|
StateServiceAbstraction,
|
||||||
|
CryptoServiceAbstraction,
|
||||||
|
CryptoFunctionServiceAbstraction,
|
||||||
|
MessagingServiceAbstraction,
|
||||||
|
I18nServiceAbstraction,
|
||||||
|
EncryptedMessageHandlerService,
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class ServicesModule {}
|
export class ServicesModule {}
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ import {
|
|||||||
|
|
||||||
// required to avoid linting errors when there are no flags
|
// required to avoid linting errors when there are no flags
|
||||||
/* eslint-disable-next-line @typescript-eslint/ban-types */
|
/* eslint-disable-next-line @typescript-eslint/ban-types */
|
||||||
export type Flags = {} & SharedFlags;
|
export type Flags = {
|
||||||
|
showDDGSetting?: boolean;
|
||||||
|
} & SharedFlags;
|
||||||
|
|
||||||
// required to avoid linting errors when there are no flags
|
// required to avoid linting errors when there are no flags
|
||||||
/* eslint-disable-next-line @typescript-eslint/ban-types */
|
/* eslint-disable-next-line @typescript-eslint/ban-types */
|
||||||
|
|||||||
@@ -1570,6 +1570,12 @@
|
|||||||
"enableBrowserIntegrationDesc": {
|
"enableBrowserIntegrationDesc": {
|
||||||
"message": "Used for biometrics in browser."
|
"message": "Used for biometrics in browser."
|
||||||
},
|
},
|
||||||
|
"enableDuckDuckGoBrowserIntegration": {
|
||||||
|
"message": "Allow DuckDuckGo browser integration"
|
||||||
|
},
|
||||||
|
"enableDuckDuckGoBrowserIntegrationDesc": {
|
||||||
|
"message": "Use your Bitwarden vault when browsing with DuckDuckGo."
|
||||||
|
},
|
||||||
"browserIntegrationUnsupportedTitle": {
|
"browserIntegrationUnsupportedTitle": {
|
||||||
"message": "Browser integration not supported"
|
"message": "Browser integration not supported"
|
||||||
},
|
},
|
||||||
@@ -1597,6 +1603,21 @@
|
|||||||
"verifyBrowserDesc": {
|
"verifyBrowserDesc": {
|
||||||
"message": "Please ensure the shown fingerprint is identical to the fingerprint showed in the browser extension."
|
"message": "Please ensure the shown fingerprint is identical to the fingerprint showed in the browser extension."
|
||||||
},
|
},
|
||||||
|
"verifyNativeMessagingConnectionTitle": {
|
||||||
|
"message": "$APPID$ wants to connect to Bitwarden",
|
||||||
|
"placeholders": {
|
||||||
|
"appid": {
|
||||||
|
"content": "$1",
|
||||||
|
"example": "My App"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"verifyNativeMessagingConnectionDesc": {
|
||||||
|
"message": "Would you like to approve this request?"
|
||||||
|
},
|
||||||
|
"verifyNativeMessagingConnectionWarning": {
|
||||||
|
"message": "If you did not initiate this request, do not approve it."
|
||||||
|
},
|
||||||
"biometricsNotEnabledTitle": {
|
"biometricsNotEnabledTitle": {
|
||||||
"message": "Biometrics not enabled"
|
"message": "Biometrics not enabled"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -171,7 +171,10 @@ export class Main {
|
|||||||
await this.biometricMain.init();
|
await this.biometricMain.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await this.stateService.getEnableBrowserIntegration()) {
|
if (
|
||||||
|
(await this.stateService.getEnableBrowserIntegration()) ||
|
||||||
|
(await this.stateService.getEnableDuckDuckGoBrowserIntegration())
|
||||||
|
) {
|
||||||
this.nativeMessagingMain.listen();
|
this.nativeMessagingMain.listen();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,10 +68,18 @@ export class MessagingMain {
|
|||||||
this.main.nativeMessagingMain.generateManifests();
|
this.main.nativeMessagingMain.generateManifests();
|
||||||
this.main.nativeMessagingMain.listen();
|
this.main.nativeMessagingMain.listen();
|
||||||
break;
|
break;
|
||||||
|
case "enableDuckDuckGoBrowserIntegration":
|
||||||
|
this.main.nativeMessagingMain.generateDdgManifests();
|
||||||
|
this.main.nativeMessagingMain.listen();
|
||||||
|
break;
|
||||||
case "disableBrowserIntegration":
|
case "disableBrowserIntegration":
|
||||||
this.main.nativeMessagingMain.removeManifests();
|
this.main.nativeMessagingMain.removeManifests();
|
||||||
this.main.nativeMessagingMain.stop();
|
this.main.nativeMessagingMain.stop();
|
||||||
break;
|
break;
|
||||||
|
case "disableDuckDuckGoBrowserIntegration":
|
||||||
|
this.main.nativeMessagingMain.removeDdgManifests();
|
||||||
|
this.main.nativeMessagingMain.stop();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -163,6 +163,27 @@ export class NativeMessagingMain {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
generateDdgManifests() {
|
||||||
|
const manifest = {
|
||||||
|
name: "com.8bit.bitwarden",
|
||||||
|
description: "Bitwarden desktop <-> DuckDuckGo bridge",
|
||||||
|
path: this.binaryPath(),
|
||||||
|
type: "stdio",
|
||||||
|
};
|
||||||
|
switch (process.platform) {
|
||||||
|
case "darwin": {
|
||||||
|
/* eslint-disable-next-line no-useless-escape */
|
||||||
|
const path = `${this.homedir()}/Library/Containers/com.duckduckgo.macos.browser/Data/Library/Application\ Support/NativeMessagingHosts/com.8bit.bitwarden.json`;
|
||||||
|
this.writeManifest(path, manifest).catch((e) =>
|
||||||
|
this.logService.error(`Error writing manifest for DuckDuckGo. ${e}`)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
removeManifests() {
|
removeManifests() {
|
||||||
switch (process.platform) {
|
switch (process.platform) {
|
||||||
case "win32":
|
case "win32":
|
||||||
@@ -217,6 +238,21 @@ export class NativeMessagingMain {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
removeDdgManifests() {
|
||||||
|
switch (process.platform) {
|
||||||
|
case "darwin": {
|
||||||
|
/* eslint-disable-next-line no-useless-escape */
|
||||||
|
const path = `${this.homedir()}/Library/Containers/com.duckduckgo.macos.browser/Data/Library/Application\ Support/NativeMessagingHosts/com.8bit.bitwarden.json`;
|
||||||
|
if (existsSync(path)) {
|
||||||
|
fs.unlink(path);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private getDarwinNMHS() {
|
private getDarwinNMHS() {
|
||||||
/* eslint-disable no-useless-escape */
|
/* eslint-disable no-useless-escape */
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { EncryptedCommand } from "./encryptedCommand";
|
||||||
|
|
||||||
|
export type DecryptedCommandData = {
|
||||||
|
command: EncryptedCommand;
|
||||||
|
payload?: any;
|
||||||
|
};
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export type EncryptedCommand =
|
||||||
|
| "bw-status"
|
||||||
|
| "bw-credential-retrieval"
|
||||||
|
| "bw-credential-create"
|
||||||
|
| "bw-credential-update"
|
||||||
|
| "bw-generate-password";
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { EncString } from "@bitwarden/common/models/domain/encString";
|
||||||
|
|
||||||
|
import { MessageCommon } from "./messageCommon";
|
||||||
|
|
||||||
|
export type EncryptedMessage = MessageCommon & {
|
||||||
|
// Will decrypt to a DecryptedCommandData object
|
||||||
|
encryptedCommand: EncString;
|
||||||
|
};
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export type CredentialCreatePayload = {
|
||||||
|
userId: string;
|
||||||
|
userName: string;
|
||||||
|
password: string;
|
||||||
|
name: string;
|
||||||
|
uri: string;
|
||||||
|
};
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export type CredentialRetrievePayload = {
|
||||||
|
userId: string;
|
||||||
|
uri: string;
|
||||||
|
};
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export type CredentialUpdatePayload = {
|
||||||
|
userId: string;
|
||||||
|
userName: string;
|
||||||
|
password: string;
|
||||||
|
name: string;
|
||||||
|
uri: string;
|
||||||
|
credentialId: string;
|
||||||
|
};
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export type PasswordGeneratePayload = {
|
||||||
|
userId: string;
|
||||||
|
};
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { EncString } from "@bitwarden/common/models/domain/encString";
|
||||||
|
|
||||||
|
import { MessageCommon } from "./messageCommon";
|
||||||
|
|
||||||
|
export type EncryptedMessageResponse = MessageCommon & {
|
||||||
|
encryptedPayload: EncString;
|
||||||
|
};
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export type AccountStatusResponse = {
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
status: "locked" | "unlocked";
|
||||||
|
active: boolean;
|
||||||
|
};
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export type CannotDecryptErrorResponse = {
|
||||||
|
error: "cannot-decrypt";
|
||||||
|
};
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export type CipherResponse = {
|
||||||
|
userId: string;
|
||||||
|
credentialId: string;
|
||||||
|
userName: string;
|
||||||
|
password: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { AccountStatusResponse } from "./accountStatusResponse";
|
||||||
|
import { CannotDecryptErrorResponse } from "./cannotDecryptErrorResponse";
|
||||||
|
import { CipherResponse } from "./cipherResponse";
|
||||||
|
import { FailureStatusResponse } from "./failureStatusResponse";
|
||||||
|
import { GenerateResponse } from "./generateResponse";
|
||||||
|
import { SuccessStatusResponse } from "./successStatusResponse";
|
||||||
|
import { UserStatusErrorResponse } from "./userStatusErrorResponse";
|
||||||
|
|
||||||
|
export type EncyptedMessageResponse =
|
||||||
|
| AccountStatusResponse[]
|
||||||
|
| CannotDecryptErrorResponse
|
||||||
|
| CipherResponse[]
|
||||||
|
| FailureStatusResponse
|
||||||
|
| GenerateResponse
|
||||||
|
| SuccessStatusResponse
|
||||||
|
| UserStatusErrorResponse;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export type FailureStatusResponse = {
|
||||||
|
status: "failure";
|
||||||
|
};
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export type GenerateResponse = {
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export type SuccessStatusResponse = {
|
||||||
|
status: "success";
|
||||||
|
};
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export type UserStatusErrorResponse = {
|
||||||
|
error: "locked" | "not-active-user";
|
||||||
|
};
|
||||||
25
apps/desktop/src/models/nativeMessaging/index.ts
Normal file
25
apps/desktop/src/models/nativeMessaging/index.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
export * from "./encryptedMessagePayloads/credentialCreatePayload";
|
||||||
|
export * from "./encryptedMessagePayloads/credentialRetrievePayload";
|
||||||
|
export * from "./encryptedMessagePayloads/credentialUpdatePayload";
|
||||||
|
export * from "./encryptedMessagePayloads/passwordGeneratePayload";
|
||||||
|
|
||||||
|
export * from "./encryptedMessageResponses/accountStatusResponse";
|
||||||
|
export * from "./encryptedMessageResponses/cannotDecryptErrorResponse";
|
||||||
|
export * from "./encryptedMessageResponses/cipherResponse";
|
||||||
|
export * from "./encryptedMessageResponses/encryptedMessageResponse";
|
||||||
|
export * from "./encryptedMessageResponses/failureStatusResponse";
|
||||||
|
export * from "./encryptedMessageResponses/generateResponse";
|
||||||
|
export * from "./encryptedMessageResponses/successStatusResponse";
|
||||||
|
export * from "./encryptedMessageResponses/userStatusErrorResponse";
|
||||||
|
|
||||||
|
export * from "./decryptedCommandData";
|
||||||
|
export * from "./encryptedCommand";
|
||||||
|
export * from "./encryptedMessage";
|
||||||
|
export * from "./encryptedMessageResponse";
|
||||||
|
export * from "./legacyMessage";
|
||||||
|
export * from "./legacyMessageWrapper";
|
||||||
|
export * from "./message";
|
||||||
|
export * from "./messageCommon";
|
||||||
|
export * from "./unencryptedCommand";
|
||||||
|
export * from "./unencryptedMessage";
|
||||||
|
export * from "./unencryptedMessageResponse";
|
||||||
8
apps/desktop/src/models/nativeMessaging/legacyMessage.ts
Normal file
8
apps/desktop/src/models/nativeMessaging/legacyMessage.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export type LegacyMessage = {
|
||||||
|
command: string;
|
||||||
|
|
||||||
|
userId?: string;
|
||||||
|
timestamp?: number;
|
||||||
|
|
||||||
|
publicKey?: string;
|
||||||
|
};
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { EncString } from "@bitwarden/common/models/domain/encString";
|
||||||
|
|
||||||
|
import { LegacyMessage } from "./legacyMessage";
|
||||||
|
|
||||||
|
export type LegacyMessageWrapper = {
|
||||||
|
message: LegacyMessage | EncString;
|
||||||
|
appId: string;
|
||||||
|
};
|
||||||
4
apps/desktop/src/models/nativeMessaging/message.ts
Normal file
4
apps/desktop/src/models/nativeMessaging/message.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { EncryptedMessage } from "./encryptedMessage";
|
||||||
|
import { UnencryptedMessage } from "./unencryptedMessage";
|
||||||
|
|
||||||
|
export type Message = UnencryptedMessage | EncryptedMessage;
|
||||||
4
apps/desktop/src/models/nativeMessaging/messageCommon.ts
Normal file
4
apps/desktop/src/models/nativeMessaging/messageCommon.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export interface MessageCommon {
|
||||||
|
version: number;
|
||||||
|
messageId: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export type UnencryptedCommand = "bw-handshake";
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { MessageCommon } from "./messageCommon";
|
||||||
|
import { UnencryptedCommand } from "./unencryptedCommand";
|
||||||
|
|
||||||
|
export type UnencryptedMessage = MessageCommon & {
|
||||||
|
command: UnencryptedCommand;
|
||||||
|
payload: {
|
||||||
|
publicKey: string;
|
||||||
|
applicationName: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { MessageCommon } from "./messageCommon";
|
||||||
|
|
||||||
|
export type UnencryptedMessageResponse = MessageCommon &
|
||||||
|
(
|
||||||
|
| {
|
||||||
|
payload: {
|
||||||
|
status: "success";
|
||||||
|
sharedKey: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
payload: {
|
||||||
|
error: "canceled" | "locked" | "cannot-decrypt" | "version-discrepancy";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
228
apps/desktop/src/services/encryptedMessageHandlerService.ts
Normal file
228
apps/desktop/src/services/encryptedMessageHandlerService.ts
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
import { AuthService } from "@bitwarden/common/abstractions/auth.service";
|
||||||
|
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
|
||||||
|
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
|
||||||
|
import { PasswordGenerationService } from "@bitwarden/common/abstractions/passwordGeneration.service";
|
||||||
|
import { PolicyService } from "@bitwarden/common/abstractions/policy/policy.service.abstraction";
|
||||||
|
import { AuthenticationStatus } from "@bitwarden/common/enums/authenticationStatus";
|
||||||
|
import { CipherType } from "@bitwarden/common/enums/cipherType";
|
||||||
|
import { PolicyType } from "@bitwarden/common/enums/policyType";
|
||||||
|
import { CipherView } from "@bitwarden/common/models/view/cipherView";
|
||||||
|
import { LoginUriView } from "@bitwarden/common/models/view/loginUriView";
|
||||||
|
import { LoginView } from "@bitwarden/common/models/view/loginView";
|
||||||
|
|
||||||
|
import { DecryptedCommandData } from "src/models/nativeMessaging/decryptedCommandData";
|
||||||
|
import { CredentialCreatePayload } from "src/models/nativeMessaging/encryptedMessagePayloads/credentialCreatePayload";
|
||||||
|
import { CredentialRetrievePayload } from "src/models/nativeMessaging/encryptedMessagePayloads/credentialRetrievePayload";
|
||||||
|
import { CredentialUpdatePayload } from "src/models/nativeMessaging/encryptedMessagePayloads/credentialUpdatePayload";
|
||||||
|
import { PasswordGeneratePayload } from "src/models/nativeMessaging/encryptedMessagePayloads/passwordGeneratePayload";
|
||||||
|
import { AccountStatusResponse } from "src/models/nativeMessaging/encryptedMessageResponses/accountStatusResponse";
|
||||||
|
import { CipherResponse } from "src/models/nativeMessaging/encryptedMessageResponses/cipherResponse";
|
||||||
|
import { EncyptedMessageResponse } from "src/models/nativeMessaging/encryptedMessageResponses/encryptedMessageResponse";
|
||||||
|
import { FailureStatusResponse } from "src/models/nativeMessaging/encryptedMessageResponses/failureStatusResponse";
|
||||||
|
import { GenerateResponse } from "src/models/nativeMessaging/encryptedMessageResponses/generateResponse";
|
||||||
|
import { SuccessStatusResponse } from "src/models/nativeMessaging/encryptedMessageResponses/successStatusResponse";
|
||||||
|
import { UserStatusErrorResponse } from "src/models/nativeMessaging/encryptedMessageResponses/userStatusErrorResponse";
|
||||||
|
|
||||||
|
import { StateService } from "./state.service";
|
||||||
|
|
||||||
|
export class EncryptedMessageHandlerService {
|
||||||
|
constructor(
|
||||||
|
private stateService: StateService,
|
||||||
|
private authService: AuthService,
|
||||||
|
private cipherService: CipherService,
|
||||||
|
private policyService: PolicyService,
|
||||||
|
private messagingService: MessagingService,
|
||||||
|
private passwordGenerationService: PasswordGenerationService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async responseDataForCommand(
|
||||||
|
commandData: DecryptedCommandData
|
||||||
|
): Promise<EncyptedMessageResponse> {
|
||||||
|
const { command, payload } = commandData;
|
||||||
|
switch (command) {
|
||||||
|
case "bw-status": {
|
||||||
|
return await this.statusCommandHandler();
|
||||||
|
}
|
||||||
|
case "bw-credential-retrieval": {
|
||||||
|
return await this.credentialretreivalCommandHandler(payload as CredentialRetrievePayload);
|
||||||
|
}
|
||||||
|
case "bw-credential-create": {
|
||||||
|
return await this.credentialCreateCommandHandler(payload as CredentialCreatePayload);
|
||||||
|
}
|
||||||
|
case "bw-credential-update": {
|
||||||
|
return await this.credentialUpdateCommandHandler(payload as CredentialUpdatePayload);
|
||||||
|
}
|
||||||
|
case "bw-generate-password": {
|
||||||
|
return await this.generateCommandHandler(payload as PasswordGeneratePayload);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
error: "cannot-decrypt",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async checkUserStatus(userId: string): Promise<string> {
|
||||||
|
const activeUserId = await this.stateService.getUserId();
|
||||||
|
|
||||||
|
if (userId !== activeUserId) {
|
||||||
|
return "not-active-user";
|
||||||
|
}
|
||||||
|
|
||||||
|
const authStatus = await this.authService.getAuthStatus(activeUserId);
|
||||||
|
if (authStatus !== AuthenticationStatus.Unlocked) {
|
||||||
|
return "locked";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "valid";
|
||||||
|
}
|
||||||
|
|
||||||
|
private async statusCommandHandler(): Promise<AccountStatusResponse[]> {
|
||||||
|
const accounts = this.stateService.accounts.getValue();
|
||||||
|
const activeUserId = await this.stateService.getUserId();
|
||||||
|
|
||||||
|
if (!accounts || !Object.keys(accounts)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(
|
||||||
|
Object.keys(accounts).map(async (userId) => {
|
||||||
|
const authStatus = await this.authService.getAuthStatus(userId);
|
||||||
|
const email = await this.stateService.getEmail({ userId });
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: userId,
|
||||||
|
email,
|
||||||
|
status: authStatus === AuthenticationStatus.Unlocked ? "unlocked" : "locked",
|
||||||
|
active: userId === activeUserId,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async credentialretreivalCommandHandler(
|
||||||
|
payload: CredentialRetrievePayload
|
||||||
|
): Promise<CipherResponse[] | UserStatusErrorResponse> {
|
||||||
|
if (payload.uri == null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const ciphersResponse: CipherResponse[] = [];
|
||||||
|
const activeUserId = await this.stateService.getUserId();
|
||||||
|
const authStatus = await this.authService.getAuthStatus(activeUserId);
|
||||||
|
|
||||||
|
if (authStatus !== AuthenticationStatus.Unlocked) {
|
||||||
|
return { error: "locked" };
|
||||||
|
}
|
||||||
|
|
||||||
|
const ciphers = await this.cipherService.getAllDecryptedForUrl(payload.uri);
|
||||||
|
ciphers.sort((a, b) => this.cipherService.sortCiphersByLastUsedThenName(a, b));
|
||||||
|
|
||||||
|
ciphers.forEach((c) => {
|
||||||
|
ciphersResponse.push({
|
||||||
|
userId: activeUserId,
|
||||||
|
credentialId: c.id,
|
||||||
|
userName: c.login.username,
|
||||||
|
password: c.login.password,
|
||||||
|
name: c.name,
|
||||||
|
} as CipherResponse);
|
||||||
|
});
|
||||||
|
|
||||||
|
return ciphersResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async credentialCreateCommandHandler(
|
||||||
|
payload: CredentialCreatePayload
|
||||||
|
): Promise<SuccessStatusResponse | FailureStatusResponse | UserStatusErrorResponse> {
|
||||||
|
const userStatus = await this.checkUserStatus(payload.userId);
|
||||||
|
if (userStatus !== "valid") {
|
||||||
|
return { error: userStatus } as UserStatusErrorResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
const credentialCreatePayload = payload as CredentialCreatePayload;
|
||||||
|
|
||||||
|
if (
|
||||||
|
credentialCreatePayload.name == null ||
|
||||||
|
(await this.policyService.policyAppliesToUser(PolicyType.PersonalOwnership))
|
||||||
|
) {
|
||||||
|
return { status: "failure" };
|
||||||
|
}
|
||||||
|
|
||||||
|
const cipherView = new CipherView();
|
||||||
|
cipherView.type = CipherType.Login;
|
||||||
|
cipherView.name = payload.name;
|
||||||
|
cipherView.login = new LoginView();
|
||||||
|
cipherView.login.password = credentialCreatePayload.password;
|
||||||
|
cipherView.login.username = credentialCreatePayload.userName;
|
||||||
|
cipherView.login.uris = [new LoginUriView()];
|
||||||
|
cipherView.login.uris[0].uri = credentialCreatePayload.uri;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const encrypted = await this.cipherService.encrypt(cipherView);
|
||||||
|
await this.cipherService.saveWithServer(encrypted);
|
||||||
|
|
||||||
|
// Notify other clients of new login
|
||||||
|
await this.messagingService.send("addedCipher");
|
||||||
|
// Refresh Desktop ciphers list
|
||||||
|
await this.messagingService.send("refreshCiphers");
|
||||||
|
|
||||||
|
return { status: "success" };
|
||||||
|
} catch (error) {
|
||||||
|
return { status: "failure" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async credentialUpdateCommandHandler(
|
||||||
|
payload: CredentialUpdatePayload
|
||||||
|
): Promise<SuccessStatusResponse | FailureStatusResponse | UserStatusErrorResponse> {
|
||||||
|
const userStatus = await this.checkUserStatus(payload.userId);
|
||||||
|
if (userStatus !== "valid") {
|
||||||
|
return { error: userStatus } as UserStatusErrorResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
const credentialUpdatePayload = payload as CredentialUpdatePayload;
|
||||||
|
|
||||||
|
if (credentialUpdatePayload.name == null) {
|
||||||
|
return { status: "failure" };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const cipher = await this.cipherService.get(credentialUpdatePayload.credentialId);
|
||||||
|
if (cipher === null) {
|
||||||
|
return { status: "failure" };
|
||||||
|
}
|
||||||
|
const cipherView = await cipher.decrypt();
|
||||||
|
cipherView.name = credentialUpdatePayload.name;
|
||||||
|
cipherView.login.password = credentialUpdatePayload.password;
|
||||||
|
cipherView.login.username = credentialUpdatePayload.userName;
|
||||||
|
cipherView.login.uris[0].uri = credentialUpdatePayload.uri;
|
||||||
|
const encrypted = await this.cipherService.encrypt(cipherView);
|
||||||
|
|
||||||
|
await this.cipherService.saveWithServer(encrypted);
|
||||||
|
|
||||||
|
// Notify other clients of update
|
||||||
|
await this.messagingService.send("editedCipher");
|
||||||
|
// Refresh Desktop ciphers list
|
||||||
|
await this.messagingService.send("refreshCiphers");
|
||||||
|
|
||||||
|
return { status: "success" };
|
||||||
|
} catch (error) {
|
||||||
|
return { status: "failure" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async generateCommandHandler(
|
||||||
|
payload: PasswordGeneratePayload
|
||||||
|
): Promise<GenerateResponse | UserStatusErrorResponse> {
|
||||||
|
const userStatus = await this.checkUserStatus(payload.userId);
|
||||||
|
if (userStatus !== "valid") {
|
||||||
|
return { error: userStatus } as UserStatusErrorResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = (await this.passwordGenerationService.getOptions())[0];
|
||||||
|
const generatedValue = await this.passwordGenerationService.generatePassword(options);
|
||||||
|
await this.passwordGenerationService.addHistory(generatedValue);
|
||||||
|
|
||||||
|
return { password: generatedValue };
|
||||||
|
}
|
||||||
|
}
|
||||||
221
apps/desktop/src/services/nativeMessageHandler.service.ts
Normal file
221
apps/desktop/src/services/nativeMessageHandler.service.ts
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
import { ipcRenderer } from "electron";
|
||||||
|
import Swal from "sweetalert2";
|
||||||
|
|
||||||
|
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
|
||||||
|
import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service";
|
||||||
|
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
|
||||||
|
import { MessagingService } from "@bitwarden/common/abstractions/messaging.service";
|
||||||
|
import { NativeMessagingVersion } from "@bitwarden/common/enums/nativeMessagingVersion";
|
||||||
|
import { Utils } from "@bitwarden/common/misc/utils";
|
||||||
|
import { EncString } from "@bitwarden/common/models/domain/encString";
|
||||||
|
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey";
|
||||||
|
import { StateService } from "@bitwarden/common/services/state.service";
|
||||||
|
|
||||||
|
import { DecryptedCommandData } from "src/models/nativeMessaging/decryptedCommandData";
|
||||||
|
import { EncryptedMessage } from "src/models/nativeMessaging/encryptedMessage";
|
||||||
|
import { EncryptedMessageResponse } from "src/models/nativeMessaging/encryptedMessageResponse";
|
||||||
|
import { Message } from "src/models/nativeMessaging/message";
|
||||||
|
import { UnencryptedMessage } from "src/models/nativeMessaging/unencryptedMessage";
|
||||||
|
import { UnencryptedMessageResponse } from "src/models/nativeMessaging/unencryptedMessageResponse";
|
||||||
|
|
||||||
|
import { EncryptedMessageHandlerService } from "./encryptedMessageHandlerService";
|
||||||
|
|
||||||
|
const EncryptionAlgorithm = "sha1";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class NativeMessageHandlerService {
|
||||||
|
private ddgSharedSecret: SymmetricCryptoKey;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private stateService: StateService,
|
||||||
|
private cryptoService: CryptoService,
|
||||||
|
private cryptoFunctionService: CryptoFunctionService,
|
||||||
|
private messagingService: MessagingService,
|
||||||
|
private i18nService: I18nService,
|
||||||
|
private encryptedMessageHandlerService: EncryptedMessageHandlerService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async handleMessage(message: Message) {
|
||||||
|
const decryptedCommand = message as UnencryptedMessage;
|
||||||
|
if (message.version != NativeMessagingVersion.Latest) {
|
||||||
|
this.sendResponse({
|
||||||
|
messageId: message.messageId,
|
||||||
|
version: NativeMessagingVersion.Latest,
|
||||||
|
payload: {
|
||||||
|
error: "version-discrepancy",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (decryptedCommand.command === "bw-handshake") {
|
||||||
|
await this.handleDecryptedMessage(decryptedCommand);
|
||||||
|
} else {
|
||||||
|
await this.handleEncryptedMessage(message as EncryptedMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleDecryptedMessage(message: UnencryptedMessage) {
|
||||||
|
const { messageId, payload } = message;
|
||||||
|
const { publicKey, applicationName } = payload;
|
||||||
|
if (!publicKey) {
|
||||||
|
this.sendResponse({
|
||||||
|
messageId: messageId,
|
||||||
|
version: NativeMessagingVersion.Latest,
|
||||||
|
payload: {
|
||||||
|
error: "cannot-decrypt",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const remotePublicKey = Utils.fromB64ToArray(publicKey).buffer;
|
||||||
|
const ddgEnabled = await this.stateService.getEnableDuckDuckGoBrowserIntegration();
|
||||||
|
|
||||||
|
if (!ddgEnabled) {
|
||||||
|
this.sendResponse({
|
||||||
|
messageId: messageId,
|
||||||
|
version: NativeMessagingVersion.Latest,
|
||||||
|
payload: {
|
||||||
|
error: "canceled",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ask for confirmation from user
|
||||||
|
this.messagingService.send("setFocus");
|
||||||
|
const submitted = await Swal.fire({
|
||||||
|
heightAuto: false,
|
||||||
|
titleText: this.i18nService.t("verifyNativeMessagingConnectionTitle", applicationName),
|
||||||
|
html: `${this.i18nService.t("verifyNativeMessagingConnectionDesc")}<br>${this.i18nService.t(
|
||||||
|
"verifyNativeMessagingConnectionWarning"
|
||||||
|
)}`,
|
||||||
|
showCancelButton: true,
|
||||||
|
cancelButtonText: this.i18nService.t("no"),
|
||||||
|
showConfirmButton: true,
|
||||||
|
confirmButtonText: this.i18nService.t("yes"),
|
||||||
|
allowOutsideClick: false,
|
||||||
|
focusCancel: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (submitted.value !== true) {
|
||||||
|
this.sendResponse({
|
||||||
|
messageId: messageId,
|
||||||
|
version: NativeMessagingVersion.Latest,
|
||||||
|
payload: {
|
||||||
|
error: "canceled",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const secret = await this.cryptoFunctionService.randomBytes(64);
|
||||||
|
this.ddgSharedSecret = new SymmetricCryptoKey(secret);
|
||||||
|
const sharedKeyB64 = new SymmetricCryptoKey(secret).toJSON().keyB64;
|
||||||
|
|
||||||
|
await this.stateService.setDuckDuckGoSharedKey(sharedKeyB64);
|
||||||
|
|
||||||
|
const encryptedSecret = await this.cryptoFunctionService.rsaEncrypt(
|
||||||
|
secret,
|
||||||
|
remotePublicKey,
|
||||||
|
EncryptionAlgorithm
|
||||||
|
);
|
||||||
|
|
||||||
|
this.sendResponse({
|
||||||
|
messageId: messageId,
|
||||||
|
version: NativeMessagingVersion.Latest,
|
||||||
|
payload: {
|
||||||
|
status: "success",
|
||||||
|
sharedKey: Utils.fromBufferToB64(encryptedSecret),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
this.sendResponse({
|
||||||
|
messageId: messageId,
|
||||||
|
version: NativeMessagingVersion.Latest,
|
||||||
|
payload: {
|
||||||
|
error: "cannot-decrypt",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleEncryptedMessage(message: EncryptedMessage) {
|
||||||
|
message.encryptedCommand = EncString.fromJSON(message.encryptedCommand.toString());
|
||||||
|
const decryptedCommandData = await this.decryptPayload(message);
|
||||||
|
const { command } = decryptedCommandData;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const responseData = await this.encryptedMessageHandlerService.responseDataForCommand(
|
||||||
|
decryptedCommandData
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.sendEncryptedResponse(message, { command, payload: responseData });
|
||||||
|
} catch (error) {
|
||||||
|
this.sendEncryptedResponse(message, { command, payload: {} });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async encryptPayload(
|
||||||
|
payload: DecryptedCommandData,
|
||||||
|
key: SymmetricCryptoKey
|
||||||
|
): Promise<EncString> {
|
||||||
|
return await this.cryptoService.encrypt(JSON.stringify(payload), key);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async decryptPayload(message: EncryptedMessage): Promise<DecryptedCommandData> {
|
||||||
|
if (!this.ddgSharedSecret) {
|
||||||
|
const storedKey = await this.stateService.getDuckDuckGoSharedKey();
|
||||||
|
if (storedKey == null) {
|
||||||
|
this.sendResponse({
|
||||||
|
messageId: message.messageId,
|
||||||
|
version: NativeMessagingVersion.Latest,
|
||||||
|
payload: {
|
||||||
|
error: "cannot-decrypt",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.ddgSharedSecret = SymmetricCryptoKey.fromJSON({ keyB64: storedKey });
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.parse(
|
||||||
|
await this.cryptoService.decryptToUtf8(
|
||||||
|
message.encryptedCommand as EncString,
|
||||||
|
this.ddgSharedSecret
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async sendEncryptedResponse(
|
||||||
|
originalMessage: EncryptedMessage,
|
||||||
|
response: DecryptedCommandData
|
||||||
|
) {
|
||||||
|
if (!this.ddgSharedSecret) {
|
||||||
|
this.sendResponse({
|
||||||
|
messageId: originalMessage.messageId,
|
||||||
|
version: NativeMessagingVersion.Latest,
|
||||||
|
payload: {
|
||||||
|
error: "cannot-decrypt",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const encryptedPayload = await this.encryptPayload(response, this.ddgSharedSecret);
|
||||||
|
|
||||||
|
this.sendResponse({
|
||||||
|
messageId: originalMessage.messageId,
|
||||||
|
version: NativeMessagingVersion.Latest,
|
||||||
|
encryptedPayload,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private sendResponse(response: EncryptedMessageResponse | UnencryptedMessageResponse) {
|
||||||
|
ipcRenderer.send("nativeMessagingReply", response);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,23 +14,15 @@ import { Utils } from "@bitwarden/common/misc/utils";
|
|||||||
import { EncString } from "@bitwarden/common/models/domain/encString";
|
import { EncString } from "@bitwarden/common/models/domain/encString";
|
||||||
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey";
|
import { SymmetricCryptoKey } from "@bitwarden/common/models/domain/symmetricCryptoKey";
|
||||||
|
|
||||||
|
import { LegacyMessage } from "src/models/nativeMessaging/legacyMessage";
|
||||||
|
import { LegacyMessageWrapper } from "src/models/nativeMessaging/legacyMessageWrapper";
|
||||||
|
import { Message } from "src/models/nativeMessaging/message";
|
||||||
|
|
||||||
|
import { NativeMessageHandlerService } from "./nativeMessageHandler.service";
|
||||||
|
|
||||||
const MessageValidTimeout = 10 * 1000;
|
const MessageValidTimeout = 10 * 1000;
|
||||||
const EncryptionAlgorithm = "sha1";
|
const EncryptionAlgorithm = "sha1";
|
||||||
|
|
||||||
type Message = {
|
|
||||||
command: string;
|
|
||||||
|
|
||||||
userId?: string;
|
|
||||||
timestamp?: number;
|
|
||||||
|
|
||||||
publicKey?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type OuterMessage = {
|
|
||||||
message: Message | EncString;
|
|
||||||
appId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class NativeMessagingService {
|
export class NativeMessagingService {
|
||||||
private sharedSecrets = new Map<string, SymmetricCryptoKey>();
|
private sharedSecrets = new Map<string, SymmetricCryptoKey>();
|
||||||
@@ -42,7 +34,8 @@ export class NativeMessagingService {
|
|||||||
private logService: LogService,
|
private logService: LogService,
|
||||||
private i18nService: I18nService,
|
private i18nService: I18nService,
|
||||||
private messagingService: MessagingService,
|
private messagingService: MessagingService,
|
||||||
private stateService: StateService
|
private stateService: StateService,
|
||||||
|
private nativeMessageHandler: NativeMessageHandlerService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
@@ -51,15 +44,20 @@ export class NativeMessagingService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async messageHandler(msg: OuterMessage) {
|
private async messageHandler(msg: LegacyMessageWrapper | Message) {
|
||||||
const appId = msg.appId;
|
const outerMessage = msg as Message;
|
||||||
const rawMessage = msg.message;
|
if (outerMessage.version) {
|
||||||
|
this.nativeMessageHandler.handleMessage(outerMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { appId, message: rawMessage } = msg as LegacyMessageWrapper;
|
||||||
|
|
||||||
// Request to setup secure encryption
|
// Request to setup secure encryption
|
||||||
if ("command" in rawMessage && rawMessage.command === "setupEncryption") {
|
if ("command" in rawMessage && rawMessage.command === "setupEncryption") {
|
||||||
const remotePublicKey = Utils.fromB64ToArray(rawMessage.publicKey).buffer;
|
const remotePublicKey = Utils.fromB64ToArray(rawMessage.publicKey).buffer;
|
||||||
|
|
||||||
// Valudate the UserId to ensure we are logged into the same account.
|
// Validate the UserId to ensure we are logged into the same account.
|
||||||
const userIds = Object.keys(this.stateService.accounts.getValue());
|
const userIds = Object.keys(this.stateService.accounts.getValue());
|
||||||
if (!userIds.includes(rawMessage.userId)) {
|
if (!userIds.includes(rawMessage.userId)) {
|
||||||
ipcRenderer.send("nativeMessagingReply", { command: "wrongUserId", appId: appId });
|
ipcRenderer.send("nativeMessagingReply", { command: "wrongUserId", appId: appId });
|
||||||
@@ -103,7 +101,7 @@ export class NativeMessagingService {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const message: Message = JSON.parse(
|
const message: LegacyMessage = JSON.parse(
|
||||||
await this.cryptoService.decryptToUtf8(rawMessage as EncString, this.sharedSecrets.get(appId))
|
await this.cryptoService.decryptToUtf8(rawMessage as EncString, this.sharedSecrets.get(appId))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -139,6 +139,8 @@ export abstract class StateService<T extends Account = Account> {
|
|||||||
setDontShowCardsCurrentTab: (value: boolean, options?: StorageOptions) => Promise<void>;
|
setDontShowCardsCurrentTab: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||||
getDontShowIdentitiesCurrentTab: (options?: StorageOptions) => Promise<boolean>;
|
getDontShowIdentitiesCurrentTab: (options?: StorageOptions) => Promise<boolean>;
|
||||||
setDontShowIdentitiesCurrentTab: (value: boolean, options?: StorageOptions) => Promise<void>;
|
setDontShowIdentitiesCurrentTab: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||||
|
getDuckDuckGoSharedKey: (options?: StorageOptions) => Promise<string>;
|
||||||
|
setDuckDuckGoSharedKey: (value: string, options?: StorageOptions) => Promise<void>;
|
||||||
getEmail: (options?: StorageOptions) => Promise<string>;
|
getEmail: (options?: StorageOptions) => Promise<string>;
|
||||||
setEmail: (value: string, options?: StorageOptions) => Promise<void>;
|
setEmail: (value: string, options?: StorageOptions) => Promise<void>;
|
||||||
getEmailVerified: (options?: StorageOptions) => Promise<boolean>;
|
getEmailVerified: (options?: StorageOptions) => Promise<boolean>;
|
||||||
@@ -158,6 +160,11 @@ export abstract class StateService<T extends Account = Account> {
|
|||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
getEnableCloseToTray: (options?: StorageOptions) => Promise<boolean>;
|
getEnableCloseToTray: (options?: StorageOptions) => Promise<boolean>;
|
||||||
setEnableCloseToTray: (value: boolean, options?: StorageOptions) => Promise<void>;
|
setEnableCloseToTray: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||||
|
getEnableDuckDuckGoBrowserIntegration: (options?: StorageOptions) => Promise<boolean>;
|
||||||
|
setEnableDuckDuckGoBrowserIntegration: (
|
||||||
|
value: boolean,
|
||||||
|
options?: StorageOptions
|
||||||
|
) => Promise<void>;
|
||||||
getEnableFullWidth: (options?: StorageOptions) => Promise<boolean>;
|
getEnableFullWidth: (options?: StorageOptions) => Promise<boolean>;
|
||||||
setEnableFullWidth: (value: boolean, options?: StorageOptions) => Promise<void>;
|
setEnableFullWidth: (value: boolean, options?: StorageOptions) => Promise<void>;
|
||||||
getEnableGravitars: (options?: StorageOptions) => Promise<boolean>;
|
getEnableGravitars: (options?: StorageOptions) => Promise<boolean>;
|
||||||
|
|||||||
4
libs/common/src/enums/nativeMessagingVersion.ts
Normal file
4
libs/common/src/enums/nativeMessagingVersion.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export enum NativeMessagingVersion {
|
||||||
|
One = 1, // Original implementation
|
||||||
|
Latest = One,
|
||||||
|
}
|
||||||
@@ -37,4 +37,5 @@ export class GlobalState {
|
|||||||
alwaysShowDock?: boolean;
|
alwaysShowDock?: boolean;
|
||||||
enableBrowserIntegration?: boolean;
|
enableBrowserIntegration?: boolean;
|
||||||
enableBrowserIntegrationFingerprint?: boolean;
|
enableBrowserIntegrationFingerprint?: boolean;
|
||||||
|
enableDuckDuckGoBrowserIntegration?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ const partialKeys = {
|
|||||||
masterKey: "_masterkey",
|
masterKey: "_masterkey",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const DDG_SHARED_KEY = "DuckDuckGoSharedKey";
|
||||||
|
|
||||||
export class StateService<
|
export class StateService<
|
||||||
TGlobalState extends GlobalState = GlobalState,
|
TGlobalState extends GlobalState = GlobalState,
|
||||||
TAccount extends Account = Account
|
TAccount extends Account = Account
|
||||||
@@ -1008,6 +1010,24 @@ export class StateService<
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getDuckDuckGoSharedKey(options?: StorageOptions): Promise<string> {
|
||||||
|
options = this.reconcileOptions(options, await this.defaultSecureStorageOptions());
|
||||||
|
if (options?.userId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return await this.secureStorageService.get<string>(DDG_SHARED_KEY, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
async setDuckDuckGoSharedKey(value: string, options?: StorageOptions): Promise<void> {
|
||||||
|
options = this.reconcileOptions(options, await this.defaultSecureStorageOptions());
|
||||||
|
if (options?.userId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
value == null
|
||||||
|
? await this.secureStorageService.remove(DDG_SHARED_KEY, options)
|
||||||
|
: await this.secureStorageService.save(DDG_SHARED_KEY, value, options);
|
||||||
|
}
|
||||||
|
|
||||||
async getEmail(options?: StorageOptions): Promise<string> {
|
async getEmail(options?: StorageOptions): Promise<string> {
|
||||||
return (
|
return (
|
||||||
await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
|
await this.getAccount(this.reconcileOptions(options, await this.defaultInMemoryOptions()))
|
||||||
@@ -1166,6 +1186,27 @@ export class StateService<
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getEnableDuckDuckGoBrowserIntegration(options?: StorageOptions): Promise<boolean> {
|
||||||
|
return (
|
||||||
|
(await this.getGlobals(this.reconcileOptions(options, await this.defaultOnDiskOptions())))
|
||||||
|
?.enableDuckDuckGoBrowserIntegration ?? false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async setEnableDuckDuckGoBrowserIntegration(
|
||||||
|
value: boolean,
|
||||||
|
options?: StorageOptions
|
||||||
|
): Promise<void> {
|
||||||
|
const globals = await this.getGlobals(
|
||||||
|
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||||
|
);
|
||||||
|
globals.enableDuckDuckGoBrowserIntegration = value;
|
||||||
|
await this.saveGlobals(
|
||||||
|
globals,
|
||||||
|
this.reconcileOptions(options, await this.defaultOnDiskOptions())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async getEnableFullWidth(options?: StorageOptions): Promise<boolean> {
|
async getEnableFullWidth(options?: StorageOptions): Promise<boolean> {
|
||||||
return (
|
return (
|
||||||
(
|
(
|
||||||
|
|||||||
Reference in New Issue
Block a user