-
Notifications
You must be signed in to change notification settings - Fork 18
Add IPC deep dive #616
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Add IPC deep dive #616
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,247 @@ | ||||||
# Inter-Process Communication (IPC) | ||||||
|
||||||
Bitwarden uses IPC to allow communication between and within certain parts of our clients. The | ||||||
oldest use-case is the communication between the Bitwarden browser extension and the Bitwarden | ||||||
Comment on lines
+3
to
+4
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oldest from the perspective of external IPC right? Client internal IPC backdates to the origin of desktop and browser extensions. |
||||||
desktop application to allow the extension to be unlocked using biometric authentication. | ||||||
|
||||||
Bitwarden now has a generic framework for IPC provided in the SDK. This framework is used to provide | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Suggesting adding a link to disambiguate from our .NET SDK or SM SDK. |
||||||
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. | ||||||
|
||||||
## Architecture | ||||||
|
||||||
The IPC framework is split into two main parts: | ||||||
|
||||||
### Platform agnostic | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Debatable in English grammar, but I prefer it hyphenated. |
||||||
|
||||||
The platform agnostic parts of the IPC framework are written in Rust and are responsible for the | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
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. | ||||||
|
||||||
```kroki type=plantuml | ||||||
@startuml | ||||||
skinparam BackgroundColor transparent | ||||||
skinparam componentStyle rectangle | ||||||
skinparam linetype ortho | ||||||
skinparam Padding 7 | ||||||
hide members | ||||||
|
||||||
package "Platform agnostic" <<frame>> { | ||||||
struct IpcClient | ||||||
|
||||||
interface CommunicationBackend | ||||||
interface CryptoProvider | ||||||
interface SessionRepository | ||||||
|
||||||
IpcClient --> CommunicationBackend: owns | ||||||
IpcClient --> CryptoProvider: owns | ||||||
IpcClient --> SessionRepository: owns | ||||||
|
||||||
CryptoProvider .l.> CommunicationBackend: uses | ||||||
CryptoProvider .r.> SessionRepository: uses | ||||||
|
||||||
struct NoEncryptionCryptoProvider | ||||||
|
||||||
CryptoProvider <|.. NoEncryptionCryptoProvider: implements | ||||||
} | ||||||
|
||||||
@enduml | ||||||
``` | ||||||
|
||||||
### Platform specific | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
You also use a hyphen for this in a paragraph above. |
||||||
|
||||||
The platform specific parts of the IPC framework are written in different languages and are | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
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. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
You use a colon above, and I also personally prefer this as a lead-in to the image. |
||||||
|
||||||
```kroki type=plantuml | ||||||
@startuml | ||||||
skinparam BackgroundColor transparent | ||||||
skinparam componentStyle rectangle | ||||||
skinparam linetype ortho | ||||||
skinparam Padding 7 | ||||||
hide members | ||||||
|
||||||
package SDK <<frame>> { | ||||||
package "Platform agnostic" <<frame>> { | ||||||
struct IpcClient | ||||||
|
||||||
interface CommunicationBackend | ||||||
|
||||||
IpcClient --> CommunicationBackend | ||||||
} | ||||||
|
||||||
package "Platform specific (WASM)" <<frame>> { | ||||||
struct JsCommunicationBackend | ||||||
} | ||||||
} | ||||||
|
||||||
package JavaScript <<frame>> { | ||||||
class WebIpcService | ||||||
class IpcBackgroundService | ||||||
} | ||||||
|
||||||
JsCommunicationBackend -left|> CommunicationBackend | ||||||
|
||||||
note bottom of JsCommunicationBackend | ||||||
JsCommunicationBackend converts the Rust trait into a JavaScript-compatible interface | ||||||
end note | ||||||
|
||||||
WebIpcService .up|> JsCommunicationBackend: implements | ||||||
IpcBackgroundService .up|> JsCommunicationBackend: implements | ||||||
|
||||||
WebIpcService -[hidden]-> IpcBackgroundService | ||||||
JavaScript -down[hidden]-> SDK | ||||||
|
||||||
@enduml | ||||||
|
||||||
``` | ||||||
|
||||||
## 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. | ||||||
`CryptoProvider`s 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 | ||||||
|
||||||
The IPC framework provides a simple interface for sending and receiving messages between processes. | ||||||
It supports two main communication patterns: | ||||||
|
||||||
### Publish/Subscribe | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
This pattern allows consumers to subscribe to specific topics and receive messages published to | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Implied. |
||||||
those topics. It is useful for scenarios where multiple consumers need to receive the same message, | ||||||
such as notifications or updates. 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 supports conversion to and from a `Vec<u8>` representation. | ||||||
|
||||||
### Request/Response | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
This pattern allows a consumer to send a request to a producer and receive a response. It is useful | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
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 supports conversion to and from a `Vec<u8>` representation. | ||||||
|
||||||
## 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 any type that | ||||||
implements the `From<Vec<u8>>` and `Into<Vec<u8>>` traits, allowing for easy conversion between the | ||||||
raw byte representation and the desired data type. | ||||||
|
||||||
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 is 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 serialized and deserialized. This allows the framework to | ||||||
be flexible and adaptable to different platforms and use cases, while still providing a consistent | ||||||
interface for consumers. | ||||||
|
||||||
```kroki type=plantuml | ||||||
@startuml | ||||||
skinparam linetype ortho | ||||||
skinparam Padding 7 | ||||||
skinparam componentStyle rectangle | ||||||
|
||||||
component OutgoingIpcMessage { | ||||||
component "topic: Option<String>" as outgoing_topic | ||||||
component "destination: Endpoint" as outgoing_destination | ||||||
component "payload: Vec<u8>" as outgoing_payload | ||||||
|
||||||
outgoing_topic -[hidden]-> outgoing_destination | ||||||
outgoing_destination -[hidden]-> outgoing_payload | ||||||
} | ||||||
|
||||||
component IncomingIpcMessage { | ||||||
component "topic: Option<String>" as incoming_topic | ||||||
component "destination: Endpoint" as incoming_destination | ||||||
component "source: Endpoint" as incoming_source | ||||||
component "payload: Vec<u8>" as incoming_payload | ||||||
|
||||||
incoming_topic -[hidden]-> incoming_destination | ||||||
incoming_destination -[hidden]-> incoming_source | ||||||
incoming_source -[hidden]-> incoming_payload | ||||||
} | ||||||
|
||||||
@enduml | ||||||
``` | ||||||
|
||||||
### Request/Response | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
The request/response functionality is built on top of the publish/subscribe message format by using | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
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 the same topic. The response message | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
will contain the request identifier, type, and payload, allowing the framework to match the response | ||||||
with the original request and handle it accordingly. | ||||||
|
||||||
```kroki type=plantuml | ||||||
@startuml | ||||||
skinparam linetype ortho | ||||||
skinparam Padding 7 | ||||||
skinparam componentStyle rectangle | ||||||
|
||||||
component OutgoingIpcMessage { | ||||||
component "topic: "RpcRequestMessage"" as outgoing_topic | ||||||
component "destination: Endpoint" as outgoing_destination | ||||||
component "RpcRequestMessage" <<payload>> as outgoing_payload { | ||||||
component "request_id: String" as outgoing_rpc_id | ||||||
component "request_type: String" as outgoing_rpc_type | ||||||
component "request: Vec<u8>" as outgoing_rpc_data | ||||||
|
||||||
outgoing_rpc_id -[hidden]-> outgoing_rpc_type | ||||||
outgoing_rpc_type -[hidden]-> outgoing_rpc_data | ||||||
} | ||||||
|
||||||
outgoing_topic -[hidden]-> outgoing_destination | ||||||
outgoing_destination -[hidden]-> outgoing_payload | ||||||
} | ||||||
|
||||||
component IncomingIpcMessage { | ||||||
component "topic: "RpcResponseMessage"" as incoming_topic | ||||||
component "destination: Endpoint" as incoming_destination | ||||||
component "source: Endpoint" as incoming_source | ||||||
|
||||||
component "RpcResponseMessage" <<payload>> as incoming_payload { | ||||||
component "request_id: String" as incoming_rpc_id | ||||||
component "request_type: String" as incoming_rpc_type | ||||||
component "request: Vec<u8>" as incoming_rpc_data | ||||||
|
||||||
incoming_rpc_id -[hidden]-> incoming_rpc_type | ||||||
incoming_rpc_type -[hidden]-> incoming_rpc_data | ||||||
} | ||||||
|
||||||
incoming_topic -[hidden]-> incoming_destination | ||||||
incoming_destination -[hidden]-> incoming_source | ||||||
incoming_source -[hidden]-> incoming_payload | ||||||
} | ||||||
|
||||||
@enduml | ||||||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.