1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-20 02:03:39 +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,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")
);
}
}