Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions docs/architecture/deep-dives/lock-states.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,12 @@ locked (via the `EncryptedMemoryStore` abstraction, see

### SSH agent

The [SSH agent](./ssh/agent.md) holds the private keys of the active, unlocked account and wipes
them on lock. The public keys, however, are kept on lock so that listing still works. This means
that in the AFU state a client can list the available keys, the correct key for a server can be
chosen, and a signing request can be made - which then prompts the user to unlock their vault to
approve the request. In the BFU state no keys are present, so listing returns nothing.
The [SSH agent](./ssh/bitwarden-ssh-agent-architecture.md) holds the private keys of the active,
unlocked account and wipes them on lock. The public keys, however, are kept on lock so that listing
still works. This means that in the AFU state a client can list the available keys, the correct key
for a server can be chosen, and a signing request can be made - which then prompts the user to
unlock their vault to approve the request. In the BFU state no keys are present, so listing returns
nothing.

## Why secrets are re-hydrated rather than persisted

Expand Down
188 changes: 188 additions & 0 deletions docs/architecture/deep-dives/ssh/bitwarden-ssh-agent-architecture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
# Bitwarden SSH Agent architecture

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Per my other comments, why can't this just be one SSH agent architecture page? I don't think we need the reorganization and it would be prudent to go the other way and be simpler.


![`Bitwarden SSH Agent high level`](./ssh-agent-v2-highlevel.png#center)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ We use Mermaid for all documentation diagrams (via code) now.


Bitwarden’s SSH Agent is primarily implemented in the Rust language, as a server that accepts ssh
client connections over the SSH Agent protocol. This server interfaces with the Desktop UI through
napi bindings and the Electron services.

Our agent is unique from native OS ones in that:

- true storage of keys is on a (remote) server which individual Desktop clients interface with
- users of the agent manage keys with a UI
- users of the agent (optionally) approve requests with a UI

---

## End to end data flow

![`Bitwarden SSH Agent end-to-end`](./ssh-agent-v2-e2e.png)

When the user enables the SSH Agent feature in user settings, the SSH key vault items are retrieved
from the vault and sent to the agent. The agent server is started and it’s keystore populated with
the new keys.

The Bitwarden SSH Agent server spawns a thread to handle any each new client connection. Each

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎨 SUGGESTED: A few grammar typos in the new prose worth cleaning up.

Details
  • Line 22: "it's keystore" β†’ "its keystore" (possessive)
  • Line 25: "spawns a thread to handle any each new client connection" β†’ "spawns a thread to handle each new client connection"
  • Line 76: "is a the orchestrator" β†’ "is the orchestrator"
  • Line 106: "based on it's authorization policy" β†’ "based on its authorization policy"

These are not caught by spellcheck since each word is individually valid.

connection results in an async callback chain.

The Electron app replaces the keys in the agent’s keystore when there are vault changes to SSH key
items.

---

## The SSH Agent

![`Bitwarden SSH Agent`](./ssh-agent-v2.png)

The core agent implementation itself is part of the Rust Desktop Native layer in the Desktop client.
It is an almost entirely self-contained workspace crate within desktop_native.

The architecture of the agent was designed with these goals:

- efficient adherence to the SSH agent protocol spec
- clean interfaces isolating the server protocol, Bitwarden’s business logic, and user interface
- maintainability
- testability

### Modules

#### server

Low level agent protocol implementation. It is responsible for listening for and managing new
connections (clients), handling requests from connections and returning responses.

#### crypto

Cryptographic primitive type definitions for public and private key types that we support and
handling of the signing of requests and any key-based operation.

#### storage

Logic that handles the storage and management of data that the agent uses, effectively the SSH keys.
Currently our implementation of a key store is in-memory.

#### authorization

Defines the concrete policy which our agent enforces for authorization of requests. The
authorization of requests is requested by the server.

#### approval

Interface for our agent to get approval for requests via an external entity (in our present case,
Electron).

#### agent

The agent crate itself is a the orchestrator of all the above components. It starts and stops the
server, updates the storage of keys, enforces the authorization policy, and interfaces with the
external approver of requests.

## Abstractions

There are two critical abstractions to understand about the agent implementation. These abstractions
both serve two purposes:

1. Low coupling ; high cohesion

2. Testability

### AuthPolicy

The server requests authorization for requests that it receives from client connections.

The server does not need to know about the business logic that goes into whether a request is
authorized.

This interface provides an abstraction from the raw server and the agent’s business logic.

```rust
pub trait AuthPolicy: Send + Sync {
async fn authorize(&self, request: &AuthRequest) -> Result<bool, AuthError>;
}
```

### ApprovalRequester

The agent requests approval from an external entity, when needed based on it’s authorization policy.

The agent does not need to know about the logic or complexities of the external entity that it
requests approvals through.

```rust
pub trait ApprovalRequester: Send + Sync {
async fn request_sign_approval(
&self,
request: SignApprovalRequest,
) -> Result<bool, ApprovalError>;

async fn request_list_approval(&self) -> Result<bool, ApprovalError>;
}
```

---

## Napi bindings

The `sshagent_v2` module contains the definitions for the napi bindings which act as the bridge
between the Rust implementation of the agent, and the Electron UI.

This layer is intentionally a pass-through. There is no unique logic. A wrapper around the agent is
provided to expose it to Electron.

The ElectronApprovalRequester is our concrete implementation of the ApprovalRequester trait. It
simply provides a mechanism to wire-up the async callbacks that we use in the Electron services to
answer the approval requests that are cascaded up from the server.

---

## Electron services

### Main service

The Main SSH Agent service is primarily a pass-through for exchanging request/response calls between
Render and agent.

It maintains a request ID counter and stores the pending requests keyed by that ID.

### Render service

The Render SSH Agent service is the primary business logic that interfaces between the vault data,
the user interactions, and the downstream agent.

It has the following responsibilities:

- subscribes to changes in the user setting for enabling the ssh agent feature, and correspondingly
starts/stops the agent.
- receives the vault ssh key items when any ssh key item is changed, and updates the agent with the
new key data.
- subscribes to active account changes, and either updates the key data or stops the server
depending on the new account’s state.
- receives authorization requests via the main service, and depending on the user’s setting for
authorization requests, may prompt the user to authorize the request, or grant it based on cached
approval, or automatically approve if authorizations aren’t configured.

---

## Vault lock state behavior

This section describes how the agent behaves in the various [vault states](../lock-states.md). For
simplicity, assume only one account is active on the client and that the feature is enabled in user
settings.

### Logged out

The agent is not running.

### Locked Before First Unlock (BFU)

The agent is running. All requests result in a toast prompt to unlock the vault.

### Locked After First Unlock (AFU)

The agent is running. List requests are supported. Sign requests are supported- if authorization is
required based on user setting, user is prompted to unlock their vault first, then prompted to
approve the request.

### Unlocked

The agent is running, all functionality supported.
4 changes: 4 additions & 0 deletions docs/architecture/deep-dives/ssh/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# SSH Keys and SSH Agent

This section contains pages that build an understanding of what SSH keys are, what an SSH Agent is,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⛏️ Please remove all the "this section" language -- it's implied. Go right into the content.

and how the Bitwarden SSH Agent is architected and implemented.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ QUESTION: The sidebar for this section is autogenerated and ordered alphabetically, so pages will appear as bitwarden-ssh-agent-architecture β†’ what-are-ssh-keys β†’ what-is-an-ssh-agent. That surfaces the advanced architecture doc before the introductory pages, which inverts the "keys β†’ agent β†’ architecture" progression this index describes. Was that ordering intentional, or would sidebar_position frontmatter help present them in reading order?

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
# SSH Keys and Agent

:::info

For more information on how Bitwarden implements its SSH agent, see [SSH Agent](./agent).

:::

## What are SSH Keys?
# What are SSH keys?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❌ Explaining what an SSH key is, briefly, was acceptable as preamble to the prior main content but ... does not need to be something we explain ourselves. You can get rid of key and agent explanation.


SSH keys are asymmetric key pairs that can be used for signing. Historically they were intended for
logging into servers, but recently they can also be used for signing arbitrary data such as Git
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SSH Agent
# What is an SSH Agent?

An SSH agent acts as a program that holds a set of private keys and provides a way to sign
challenges with those keys, without ever letting the private keys leave the vault. These challenges
Expand Down
Loading