1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-11 05:43:41 +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:
Robyn MacCallum
2022-09-23 15:47:17 -04:00
committed by GitHub
parent 32eac70c82
commit f4e61d1cec
57 changed files with 2386 additions and 27 deletions

View File

@@ -0,0 +1,5 @@
{
"rules": {
"no-console": "off"
}
}

View 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"
}
}
}

View 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"
}
}

View File

@@ -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();
})();

View File

@@ -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();
})();

View File

@@ -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();
})();

View File

@@ -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();
})();

View File

@@ -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();
})();

View File

@@ -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();
})();

View 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;
}
}

View 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);
}
}
}

View 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`);
}
}
}

View File

@@ -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")
);
}
}

View 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;
}),
]);
};

View 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=";

View 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"]
}