Metadata-Version: 2.4
Name: ssh-agent-keyring
Version: 0.6.0
Summary: A Python keyring backend unlocked by ssh-agent signatures
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: cryptography>=41.0.0
Requires-Dist: keyring>=25.0.0
Requires-Dist: paramiko>=3.0.0

# ssh-agent-keyring

`ssh-agent-keyring` is a Python [`keyring`](https://pypi.org/project/keyring/) backend that keeps secrets encrypted at rest and unlocks them transparently through `ssh-agent`.

It is designed for SSH-first environments such as headless Linux servers, remote development machines, and terminal-centric workflows where desktop keyring services are not available or not desirable.

## Why this exists

On a plain SSH server, the usual choices are often unsatisfying:

- desktop-oriented keyring backends are unavailable
- file-based backends usually require a separate keyring password
- plaintext storage is convenient, but unacceptable for real secrets

`ssh-agent-keyring` takes a different approach:

- secrets are stored **encrypted at rest**
- unlock happens through a configured SSH key already available in `ssh-agent`
- `keyring set/get/del` work in ordinary SSH sessions without a second keyring password

## Features

- `keyring` backend implementation: `ssh_agent_keyring.backend.SSHAgentKeyring`
- encrypted local store
- no plaintext secret storage
- no plaintext `service` / `username` metadata in the store
- no root or system service dependency
- works well in SSH sessions when `SSH_AUTH_SOCK` is available
- simple user-space configuration

## How it works

The backend stores a random data-encryption key (DEK) in wrapped form. To unwrap it, the backend:

1. signs a backend-specific challenge directly through the `ssh-agent` wire protocol (via `paramiko`), producing output byte-for-byte identical to `ssh-keygen -Y sign` with a configured SSH public key
2. relies on `ssh-agent` to produce a deterministic signature over that challenge
3. derives a key-encryption key from that signature with HKDF-SHA256
4. unwraps the DEK
5. uses the DEK to encrypt and decrypt secret records with `ChaCha20Poly1305`

Record identifiers are derived with HMAC, so the on-disk store does not expose service and username pairs in plaintext.

Because the key-encryption key is derived from the raw signature bytes, only key types with deterministic signing are supported: `ssh-ed25519` and `ssh-rsa`. ECDSA (`ecdsa-*`) and security-key (`sk-*`) types are rejected — OpenSSH's ECDSA signing uses a random per-signature nonce (no RFC 6979), so the same challenge does not yield the same signature and the store could not be unlocked reliably.

## Security notes

This package improves on plaintext file storage, but it intentionally shifts trust to the SSH agent session.

Important implications:

- anyone able to use your `SSH_AUTH_SOCK` can potentially unlock the keyring during that session
- agent forwarding policy matters: a remote host that receives a forwarded agent socket can proxy
  and inspect its traffic, even if it cannot read the SSH private key
- the security of the backend depends on the security of the selected SSH key and the agent socket
- this is not a hardware-backed secret manager or multi-user secret vault
- the derived signature (which acts as the store's master key) is only as safe as the agent
  process itself; a compromised or malicious `ssh-agent` on the other end of `SSH_AUTH_SOCK` can
  always observe what it is asked to sign, regardless of how the request reaches it

In particular, an interceptor that sees the deterministic unlock signature and has a copy of the
store can derive the key-encryption key, unwrap the data-encryption key, and decrypt the stored
records, see [PoC](https://github.com/monperrus/ssh_agent_keyring/issues/2#issuecomment-5382981552). It does not need to extract the SSH private key. **Do not forward an agent to an untrusted
host while using this backend**, and treat a forwarded agent socket as granting access to the
keyring store.

## Requirements

- Python 3.10+
- [`keyring`](https://pypi.org/project/keyring/)
- [`cryptography`](https://pypi.org/project/cryptography/)
- [`paramiko`](https://pypi.org/project/paramiko/)
- a running `ssh-agent`
- `SSH_AUTH_SOCK` set in the current environment
- the configured SSH key loaded in the agent

## Installation

From PyPI:

```bash
pip install ssh-agent-keyring
```

If you are installing in a user environment on a remote machine, a typical setup is:

```bash
python3 -m pip install --user ssh-agent-keyring
```

## Configuration

The backend reads configuration from:

```text
~/.config/ssh-agent-keyring/config.json
```

Example:

```json
{
  "public_key": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIExampleBase64KeyMaterial",
  "store_path": "~/.local/share/ssh-agent-keyring/store.json"
}
```

Then select the backend in:

```text
~/.config/python_keyring/keyringrc.cfg
```

```ini
[backend]
default-keyring=ssh_agent_keyring.backend.SSHAgentKeyring
```

### Configuration fields

- `public_key`: SSH public key to use for unlocking the store
- `public_key_path`: path to a public key file; useful instead of embedding the key in JSON
- `store_path`: path to the encrypted JSON store

### Environment variable overrides

These override file-based configuration:

- `SSH_AGENT_KEYRING_PUBLIC_KEY`
- `SSH_AGENT_KEYRING_PUBLIC_KEY_PATH`
- `SSH_AGENT_KEYRING_STORE_PATH`

## Usage

With the Python API:

```python
import keyring

keyring.set_password("example-service", "alice", "s3cr3t")
print(keyring.get_password("example-service", "alice"))
keyring.delete_password("example-service", "alice")
```

With the CLI:

```bash
keyring set example-service alice
keyring get example-service alice
keyring del example-service alice
```

With this backend, `keyring set` prompts only for the secret being stored. It does **not** prompt for a separate keyring password.

## Limitations

- this backend depends on the standard `ssh-agent` wire protocol
- it is aimed at Unix-like environments
- it does not provide secret sharing, rotation workflows, audit logs, or remote synchronization
- it assumes the configured SSH key can be used for agent-backed signing

## Testing

Run the test suite with:

```bash
python3 -m unittest discover -s tests -v
```

The project includes tests for roundtrips, tampering, key mismatch, signer failures, and store handling edge cases.

`tests/test_real_agent.py` additionally spins up a real `ssh-agent` and verifies, against real
`ssh-keygen -Y sign` output, that the backend's own agent-protocol signing is byte-for-byte
compatible. It's skipped automatically if `ssh-agent`/`ssh-keygen`/`ssh-add` aren't on `PATH`.

## Development

Project layout:

- `ssh_agent_keyring/agent.py` - agent and signing integration
- `ssh_agent_keyring/crypto.py` - key derivation and encryption helpers
- `ssh_agent_keyring/backend.py` - `keyring` backend implementation
- `tests/test_backend.py` - automated test suite
- `tests/test_real_agent.py` - real `ssh-agent`/`ssh-keygen` compatibility tests

## License

License information has not been added yet.
