mirror of
https://github.com/bitwarden/browser
synced 2026-01-10 12:33:26 +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:
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 { 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 EncryptionAlgorithm = "sha1";
|
||||
|
||||
type Message = {
|
||||
command: string;
|
||||
|
||||
userId?: string;
|
||||
timestamp?: number;
|
||||
|
||||
publicKey?: string;
|
||||
};
|
||||
|
||||
type OuterMessage = {
|
||||
message: Message | EncString;
|
||||
appId: string;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class NativeMessagingService {
|
||||
private sharedSecrets = new Map<string, SymmetricCryptoKey>();
|
||||
@@ -42,7 +34,8 @@ export class NativeMessagingService {
|
||||
private logService: LogService,
|
||||
private i18nService: I18nService,
|
||||
private messagingService: MessagingService,
|
||||
private stateService: StateService
|
||||
private stateService: StateService,
|
||||
private nativeMessageHandler: NativeMessageHandlerService
|
||||
) {}
|
||||
|
||||
init() {
|
||||
@@ -51,15 +44,20 @@ export class NativeMessagingService {
|
||||
});
|
||||
}
|
||||
|
||||
private async messageHandler(msg: OuterMessage) {
|
||||
const appId = msg.appId;
|
||||
const rawMessage = msg.message;
|
||||
private async messageHandler(msg: LegacyMessageWrapper | Message) {
|
||||
const outerMessage = msg as Message;
|
||||
if (outerMessage.version) {
|
||||
this.nativeMessageHandler.handleMessage(outerMessage);
|
||||
return;
|
||||
}
|
||||
|
||||
const { appId, message: rawMessage } = msg as LegacyMessageWrapper;
|
||||
|
||||
// Request to setup secure encryption
|
||||
if ("command" in rawMessage && rawMessage.command === "setupEncryption") {
|
||||
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());
|
||||
if (!userIds.includes(rawMessage.userId)) {
|
||||
ipcRenderer.send("nativeMessagingReply", { command: "wrongUserId", appId: appId });
|
||||
@@ -103,7 +101,7 @@ export class NativeMessagingService {
|
||||
return;
|
||||
}
|
||||
|
||||
const message: Message = JSON.parse(
|
||||
const message: LegacyMessage = JSON.parse(
|
||||
await this.cryptoService.decryptToUtf8(rawMessage as EncString, this.sharedSecrets.get(appId))
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user