google-labs-jules[bot] baad47c7ef
feat(age): add agent for passphrase caching (#3218)
* This change introduces an agent for the age backend to cache passphrases for age identities.

The agent is a long-running process that listens on a Unix domain socket. Gopass communicates with the agent to request decryption of secrets. The agent caches the passphrases for the identities and performs the decryption, so the passphrases never leave the agent process. This addresses the security concerns with the initial implementation.

The agent can be controlled with the following commands:
- `gopass age agent`: starts the agent in the foreground.
- `gopass age lock`: locks the agent, clearing all cached passphrases.

The age backend will automatically start the agent if it's not already running and the `age.agent-enabled` configuration option is set to `true` (the default).

This change includes:
- The implementation of the age agent in `internal/backend/crypto/age/agent/`.
- Modifications to the age backend to communicate with the agent.
- A new configuration option `age.agent-enabled`.
- Unit tests for the agent.
- Updated documentation for the age backend.

The integration test for this feature (`TestAgeAgent`) is currently failing. The issue is that the test environment is non-interactive, and the code path for initializing a new age store requires a password for the identity keyring, which triggers a `pinentry` call that fails without a TTY. I have tried several approaches to work around this, including setting the `GOPASS_PASSWORD` environment variable and providing a custom pinentry script, but none have been successful so far. The core implementation of the agent is believed to be correct, but the integration test needs further work to run in a non-interactive environment.

* This change introduces an agent for the age backend to cache passphrases for age identities.

The agent is a long-running process that listens on a Unix domain socket. Gopass communicates with the agent to request decryption of secrets. The agent caches the passphrases for the identities and performs the decryption, so the passphrases never leave the agent process. This addresses the security concerns with the initial implementation.

The agent can be controlled with the following commands:
- `gopass age agent`: starts the agent in the foreground.
- `gopass age lock`: locks the agent, clearing all cached passphrases.

The age backend will automatically start the agent if it's not already running and the `age.agent-enabled` configuration option is set to `true` (the default).

This change includes:
- The implementation of the age agent in `internal/backend/crypto/age/agent/`.
- Modifications to the age backend to communicate with the agent.
- A new configuration option `age.agent-enabled`.
- Unit tests for the agent.
- Updated documentation for the age backend.

* This change introduces an agent for the age backend to cache passphrases for age identities.

The agent is a long-running process that listens on a Unix domain socket. Gopass communicates with the agent to request decryption of secrets. The agent caches the passphrases for the identities and performs the decryption, so the passphrases never leave the agent process. This addresses the security concerns with the initial implementation.

The agent can be controlled with the following commands:
- `gopass age agent`: starts the agent in the foreground.
- `gopass age lock`: locks the agent, clearing all cached passphrases.

The age backend will automatically start the agent if it's not already running and the `age.agent-enabled` configuration option is set to `true` (the default).

This change includes:
- The implementation of the age agent in `internal/backend/crypto/age/agent/`.
- Modifications to the age backend to communicate with the agent.
- A new configuration option `age.agent-enabled`.
- Unit tests for the agent.
- Updated documentation for the age backend.

* This change introduces an agent for the age backend to cache passphrases for age identities.

The agent is a long-running process that listens on a Unix domain socket. Gopass communicates with the agent to request decryption of secrets. The agent caches the passphrases for the identities and performs the decryption, so the passphrases never leave the agent process. This addresses the security concerns with the initial implementation.

The agent can be controlled with the following commands:
- `gopass age agent`: starts the agent in the foreground.
- `gopass age lock`: locks the agent, clearing all cached passphrases.

The age backend will automatically start the agent if it's not already running and the `age.agent-enabled` configuration option is set to `true` (the default).

This change includes:
- The implementation of the age agent in `internal/backend/crypto/age/agent/`.
- Modifications to the age backend to communicate with the agent.
- A new configuration option `age.agent-enabled`.
- Unit tests for the agent.
- Updated documentation for the age backend.

* Fix some test failures and add more logging.

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>

* Fix lint error

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>

* [fix] Fix integration tests

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>

---------

Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Co-authored-by: Dominik Schulz <dominik.schulz@gauner.org>
2025-09-15 22:03:33 +02:00

3.9 KiB

age crypto backend

The age backend is an experimental crypto backend based on age. It adds an encrypted keyring on top (using age in scrypt password mode). It also has (largely untested) support for specifying recipients as github users. This will use their ssh public keys for age encryption. It is well positioned to eventually replace gpg as the default crypto backend.

Getting started

WARNING: This backend is experimental and the on-disk format likely to change.

To start using the age backend initialize a new (sub) store with the --crypto=age flag:

$ gopass age identity add [AGE-... age1...]
<if you do not specify an age secret key, you'll be prompted for one>
$ gopass init --crypto age

or use the wizard that will help you create a new age key:

$ gopass setup --crypto age

This will automatically create a new age keypair and initialize the new store.

Existing stores can be migrated using gopass convert --crypto age.

N.B. for a fully scripted or non-interactive setup, you can use the GOPASS_AGE_PASSWORD env variable to set your identity file secret passphrase, and specify the age identity and recipients that should be used for encrypting/decrypting passwords as follows:

$ gopass age identity add <AGE-...> <age1...>
$  GOPASS_AGE_PASSWORD=mypassword gopass init --crypto age <age1...>

Notice the extra space in front of the command to skip most shell's history. You'll need to set your name and username using git directly if you're using it as storage backend (the default one).

You can also specify the ssh directory by setting environment variable

$  GOPASS_SSH_DIR=/Downloads/new_ssh_dir gopass init --crypto age <age1...>

Features

  • Encryption using age library, can be decrypted using the age CLI
  • Support for native age, ssh-ed25519 and ssh-rsa recipients
  • Support for encrypted ssh private keys
  • Support for using GitHub users' private keys, e.g. github:user as recipient
  • Automatic downloading and caching of SSH keys from GitHub
  • Encrypted keyring for age keypairs
  • Support for age plugins
  • Caching of passphrases via an agent

Agent

The age backend comes with an agent that can cache the passphrases for your age identities. The agent is started automatically by gopass if it's not already running. You can disable the agent by setting age.agent-enabled to false in your gopass config.

The agent performs the decryption and the passphrase never leaves the agent process. The agent listens on a unix socket at $XDG_RUNTIME_DIR/gopass/gopass-age-agent.sock.

You can interact with the agent using the following commands:

  • gopass age agent: starts the agent in the foreground.
  • gopass age lock: locks the agent, clearing all cached passphrases.

Usage with a yubikey

To use with a Yubikey, age requires the usage of the age-plugin-yubikey plugin.

Assuming you have Rust installed:

$ cargo install age-plugin-yubikey
$ age-plugin-yubikey -i
<should be empty>
$ age-plugin-yubikey
✨ Let's get your YubiKey set up for age! ✨
<follow instructions to setup a PIV slot>
$ age-plugin-yubikey -i
<should display your PIV slot information now>
$ gopass age identities add
Enter the age identity starting in AGE-:
<paste the `AGE-PLUGIN-YUBIKEY-...` identity from the previous command>
Provide the corresponding age recipient starting in age1:
<paste the `age1yubikey1...` recipient from the previous command>

If gopass tells you waiting on yubikey plugin... when decrypting secrets, it probably is waiting for you to touch your Yubikey because you've set a Touch policy when setting up your PIV slot.

Roadmap

The future of this backend largely depends on what is happening in the age project itself.

Assuming age is supporting this, we'd like to:

  • Finalize GitHub recipient support
  • Add Hardware token support
  • Make age the default gopass backend