Skip to main content

Inter-Process Communication (IPC)

Bitwarden uses IPC to allow communication between and within certain parts of our clients. Examples of IPC within a client include communication to and from the background script/service worker in the browser extension and the main and renderer processes in the desktop application. IPC is also used to allow communication between different clients, such as the browser extension and the desktop application, to enable features like unlocking the extension using biometric authentication.

Bitwarden now has a generic framework for IPC provided in our SDK. This framework is used to provide a common interface for IPC across all clients. The framework is designed to be cross-platform and can be used in any client that needs to communicate with another process.

Usage

Please refer to the code documentation for more information on how to use the IPC framework:

Availability

When fully rolled out, an initialized IpcClient will be available in all TypeScript clients through the IpcService, and in the SDK through the BitwardenClient. The framework is available in the background script/service worker of the browser extension and the web vault. Due to the complex infrastructure required to run the framework, it is not recommended for use in smaller processes like content scripts or macOS extensions.

For more up-to-date information on the availability of the IPC please refer to the source code.

Architecture

The IPC framework is split into two main parts:

Platform-agnostic

The platform-agnostic parts of the IPC framework are written in Rust and are responsible for the high-level logic of the IPC communication. This includes the serialization and deserialization of messages, as well as the encryption and decryption of messages. They depend on the platform-specific parts to integrate with the underlying platform's IPC mechanisms.

plantuml

Platform-specific

The platform-specific parts of the IPC framework are written in different languages and are responsible for the low-level communication between the processes. This includes the actual sending and receiving of messages, persisting cryptographic sessions, as well as any other platform-specific details of the IPC communication. Below is an illustration of the platform-specific implementation for the WebAssembly (WASM) platform, which uses JavaScript to implement how messages are sent and received:

plantuml

Security

The IPC framework is designed with security in mind. Every message sent between processes is converted to a Vec<u8> representation and encrypted using a CryptoProvider before being sent over the communication channel. This ensures that messages are not sent in plain text and are protected from eavesdropping or tampering. For consumers of the framework the encryption is completely transparent, as the framework handles the encryption and decryption of messages automatically.

The framework also supports session management, allowing clients to securely store and retrieve cryptographic sessions. This is useful to avoid having to re-establish shared secrets or keys between processes every time they communicate. The session management is handled by the SessionRepository trait, and is implemented by the platform-specific parts of the IPC framework. CryptoProviders have full access to the CommunicationBackend which allows them to send and receive their own messages over the communication channel to establish and maintain sessions. These messages can be completely separate from the actual IPC data messages and might be completely transparent to the consumer of the framework.

Usage patterns

The IPC framework provides a simple interface for sending and receiving messages between processes. It supports two main communication patterns:

Publish / Subscribe

Allows consumers to subscribe to specific topics and receive messages published to those topics. It is useful for scenarios where multiple consumers need to receive the same message, such as notifications or updates, or for fire-and-forget scenarios where the sender does not need to receive a response. Consumers can subscribe to the raw data or a specific type of message, and the framework will handle the serialization and deserialization of the messages, as long as the type implements the appropriate serde traits.

Request / Response

Allows a consumer to send a request to a producer and receive a response. It is useful for scenarios where a consumer needs to request specific information or perform an action, such as authentication or data retrieval. The framework handles the serialization and deserialization of the messages, as long as the type implements the appropriate serde traits.

Message format

The IPC framework uses a simple message format that, while not formally defined, is designed to be flexible and extensible. Each message consists of a topic, a destination, and a payload. The topic is a string that identifies the message, the destination is an enum that specifies the target of the message, and the payload is a Vec<u8> that contains the serialized data. The topic allows messages to be categorized and filtered, while the destination allows messages to be sent to specific consumers or processes. The payload contains the actual data being sent, and can be manually assigned or automatically serialized by the framework.

When the framework returns a message to the consumer it will also contain a source, which represents where the current process received the message from. This can be used to determine the origin of the message and can be useful for determining trust. Since the field is added by the receiver you can assume that it is correct and trustworthy.

The top-level encoding and decoding of messages is handled by the CommunicationBackend trait and is not formalized by the framework itself. Instead, it is left to the platform-specific implementations to define how messages are sent across the wire. This allows the framework to be flexible and adaptable to different platforms and use cases, while still providing a consistent interface for consumers.

plantuml

Request / Response

The request / response functionality is built on top of the publish / subscribe message format by using a hard-coded topic reserved for RPC (Remote Procedure Call) messages. On receipt of a request message, the framework will automatically decode the payload as an RpcRequestMessage which contains additional metadata about the request, such as the request identifier, type, and payload. The type is used to determine which handler to call for the request, and the payload is the actual data being sent in the request. The framework will then serialize the response as an RpcResponseMessage and send it back to the consumer using a topic reserved for responses. The response message will contain the request identifier, type, and payload, allowing the framework to match the response with the original request and handle it accordingly.

plantuml