* feat: Add cryptfs storage backend for filename encryption
This commit introduces a new storage backend called `cryptfs`. This backend encrypts the filenames of secrets to enhance privacy while maintaining compatibility with existing VCS backends like Git.
Key features:
- For each secret, a cryptographically secure hash (SHA-256) of its name is generated and used as the filename for the underlying storage.
- A mapping from the original secret name to the hashed filename is maintained in an encrypted file (`.gopass-mapping.age`) within the repository.
- The mapping file is encrypted using the `age` encryption backend, with recipients read from the store's `.age-recipients` file.
- The `cryptfs` backend is implemented as a wrapper around any existing storage backend (e.g., `gitfs`, `fs`), which can be configured by the user.
- The backend is registered with gopass and can be enabled by setting `storage: cryptfs` in the store's configuration.
This implementation addresses issue #2634.
* [fix] Fix lint errors
Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
* [chore] Fix the remaining tests and add some docs.
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>
* feat: Add regression test for issue #2571
This commit adds a regression test for issue #2571. The issue describes a scenario where `gopass sync` incorrectly removes public keys for sub-stores under certain conditions.
The new integration test in `tests/sync_test.go` reproduces the steps outlined in the GitHub issue to ensure that the public key is not deleted after running `gopass sync`.
* [fix] Fix lint errors
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>
* feat(age): Add unlock command to age agent
This change introduces a proper lock/unlock mechanism for the age agent.
The issue was that after locking the agent with `gopass age lock`, there was no way to unlock it without restarting the agent. This made the lock command mostly useless.
This change introduces a new `unlock` command for the agent and a `locked` state.
- The `lock` command now sets a `locked` flag to `true` in addition to clearing identities.
- The `decrypt` function in the agent now checks this `locked` flag and returns an error if the agent is locked.
- When the gopass client receives the "agent is locked" error, it will ask the user for their passphrase, reload the identities, and send them to the agent.
- A new `gopass age agent unlock` CLI command is added to trigger this new functionality.
- The `gopass age agent status` command is enhanced to report whether the agent is locked.
- The old top-level `gopass age lock` command is hidden, and a new `gopass age agent lock` command is introduced for consistency.
Fixes#3242
* feat(age): Add unlock command to age agent
This change introduces a proper lock/unlock mechanism for the age agent.
The issue was that after locking the agent with `gopass age lock`, there was no way to unlock it without restarting the agent. This made the lock command mostly useless.
This change introduces a new `unlock` command for the agent and a `locked` state.
- The `lock` command now sets a `locked` flag to `true` in addition to clearing identities.
- The `decrypt` function in the agent now checks this `locked` flag and returns an error if the agent is locked.
- When the gopass client receives the "agent is locked" error, it will ask the user for their passphrase, reload the identities, and send them to the agent.
- A new `gopass age agent unlock` CLI command is added to trigger this new functionality.
- The `gopass age agent status` command is enhanced to report whether the agent is locked.
- The old top-level `gopass age lock` command is hidden, and a new `gopass age agent lock` command is introduced for consistency.
I have also addressed the PR comment about the import alias. I have removed the alias and used a dot import instead to avoid the name collision.
Fixes#3242
* feat(age): Add unlock command to age agent
This change introduces a proper lock/unlock mechanism for the age agent.
The issue was that after locking the agent with `gopass age lock`, there was no way to unlock it without restarting the agent. This made the lock command mostly useless.
This change introduces a new `unlock` command for the agent and a `locked` state.
- The `lock` command now sets a `locked` flag to `true` in addition to clearing identities.
- The `decrypt` function in the agent now checks this `locked` flag and returns an error if the agent is locked.
- When the gopass client receives the "agent is locked" error, it will ask the user for their passphrase, reload the identities, and send them to the agent.
- A new `gopass age agent unlock` CLI command is added to trigger this new functionality.
- The `gopass age agent status` command is enhanced to report whether the agent is locked.
- The old top-level `gopass age lock` command is hidden, and a new `gopass age agent lock` command is introduced for consistency.
To avoid name collisions with the imported `filippo.io/age` package, the local `age` package has been renamed to `agecrypto`.
Fixes#3242
* feat(age): Add auto-lock feature to age agent
This change introduces an auto-lock feature for the age agent. The agent will now automatically lock itself after a configurable period of inactivity.
This change also includes the initial fix for issue #3242, which introduced a proper lock/unlock mechanism for the age agent.
- A new config option `age.agent-timeout` is added to specify the inactivity timeout in seconds.
- The agent now has a timer that is reset on every successful decryption operation.
- If the timer expires, the agent locks itself.
- A new `set-timeout` command is added to the agent protocol to configure the timeout.
- The gopass client sends the timeout to the agent when it starts or when it unlocks the agent.
- A new test `TestAgentAutoLock` is added to verify the new functionality.
To avoid name collisions with the imported `filippo.io/age` package, the local `age` package has been renamed to `agecrypto`.
Fixes#3242
* [fix] Fix lint issues
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>
* fix(gpg): Opportunistic key comparison on import
When importing keys gopass was already checking if the key was already present.
However, GPG is very flexible in which kind of key IDs it accepts. So it could happen that gopass would ask to import a key that is already present in the keyring, but referenced by a different ID.
This change makes the check more robust by checking the key's fingerprint before asking for import. If a key with the same fingerprint is already present, the import is skipped.
* fix(gpg): Opportunistic key comparison on import
When importing keys gopass was already checking if the key was already present.
However, GPG is very flexible in which kind of key IDs it accepts. So it could happen that gopass would ask to import a key that is already present in the keyring, but referenced by a different ID.
This change makes the check more robust by checking the key's fingerprint before asking for import. If a key with the same fingerprint is already present, the import is skipped.
---------
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
* 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>
* [fix] Fix test regressions
This change fixes some tests that did start to fail at some point.
I can't tell for sure when they broke (didn't bisect) and if that
breakage is isolated to my system or not. But I need to fix them
anyway.
Fixes#3115
Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
* Add missing files.
Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
* Fix test failure
Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
* Fix one more version test
Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
---------
Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
* specify ssh dir
Signed-off-by: Joel Lau <joellau@protonmail.com>
* added documentation, prefer GOPASS_SSHDIR
Signed-off-by: Joel Lau <joellau@protonmail.com>
* check for empty path
Signed-off-by: Joel Lau <joellau@protonmail.com>
---------
Signed-off-by: Joel Lau <joellau@protonmail.com>
* [FEATURE] Allow for non-interactive age setup
Also updates Go to Go 1.23.2 and get rid of min and max functions
Signed-off-by: Yolan Romailler <AnomalRoil@users.noreply.github.com>
* [n/a] also renaming clear for Windows
Signed-off-by: Yolan Romailler <AnomalRoil@users.noreply.github.com>
* [n/a] bumping our GHA to Go 1.23
Signed-off-by: Yolan Romailler <AnomalRoil@users.noreply.github.com>
* [n/a] make our harden runner softer
Signed-off-by: Yolan Romailler <AnomalRoil@users.noreply.github.com>
* [n/a] make our harden runner accept go.dev
Signed-off-by: Yolan Romailler <AnomalRoil@users.noreply.github.com>
* [n/a] applying code review changes
Signed-off-by: Yolan Romailler <AnomalRoil@users.noreply.github.com>
---------
Signed-off-by: Yolan Romailler <AnomalRoil@users.noreply.github.com>
When gopass-jsonapi is running these functions, the stderr of the gpg
command won't reach anywhere visible without writing it to the log as
well. Also adding 2 --verbose arguments to gpg helps debugging potential
issues.
Signed-off-by: Doron Behar <doron.behar@gmail.com>
* gpg: Log gpg output to LogWriter
Signed-off-by: Doron Behar <doron.behar@gmail.com>
* gpg/cli: Add more log messages
Return a different error message if no trustable keys were found.
Signed-off-by: Doron Behar <doron.behar@gmail.com>
* Make leaf/write test always trust recipients
Signed-off-by: Doron Behar <doron.behar@gmail.com>
---------
Signed-off-by: Doron Behar <doron.behar@gmail.com>
We can not parse and check the secret for suppressed keys if
we are not supposed to parse the secret.
Fixes#2737
Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
* [CLEANUP] Moving options to the correct config section
This adds an easy migration path to our config handling, which should
allow us to migrate option names around much more easily in the future.
Any system level config or env variables options are not migrated.
This also fixes a bug in our test code, where the root mount path was
not properly set in our config, because we used "path:" instead of
"path=" to set it.
Signed-off-by: Yolan Romailler <anomalroil@users.noreply.github.com>
* [DOCUMENTATION] Document legacy options and their migration path
This also makes sure that legacy options aren't used in the code anymore using the docs test and its regexp
Signed-off-by: Yolan Romailler <anomalroil@users.noreply.github.com>
* [n/a] Removing weird spaces from changelog
Signed-off-by: Yolan Romailler <anomalroil@users.noreply.github.com>
* [DOCUMENTATION] Reformatting our Markdown tables properly
Signed-off-by: Yolan Romailler <anomalroil@users.noreply.github.com>
* [TESTING] Patching a timezone bug in tests
This is a fun one where if your Timezone isn't UTC and you are past midnight but it's not past midnight UTC, the tests would fail because you're not using the right date to validate it.
Signed-off-by: Yolan Romailler <anomalroil@users.noreply.github.com>
* [n/a] Fix a typo and use the correct Env variables in the doc about the custom Env variables
Signed-off-by: Yolan Romailler <anomalroil@users.noreply.github.com>
* [BREAKING] Custom Env options moved from GOPASS_CONFIG_CONFIG_KEY_i to GOPASS_CONFIG_KEY_i
As discussed in #2617, this actually reflects the way GIT_CONFIG works.
It also fixes a potential Panic in our codebase when IsSet was called
without any Preset config on a non-existing key.
Signed-off-by: Yolan Romailler <anomalroil@users.noreply.github.com>
* [CLEANUP] Patching all of the new linter complaints
Signed-off-by: Yolan Romailler <anomalroil@users.noreply.github.com>
* [CLEANUP] Use Go1.21 everywhere
Signed-off-by: Yolan Romailler <anomalroil@users.noreply.github.com>
* [n/a] Increase our Golangci timeout
Signed-off-by: Yolan Romailler <anomalroil@users.noreply.github.com>
* [n/a] code review comment and extra regression test
Signed-off-by: Yolan Romailler <anomalroil@users.noreply.github.com>
---------
Signed-off-by: Yolan Romailler <anomalroil@users.noreply.github.com>
The new recipients check for unusable GPG keys did not work for
age. In fact most age keys can be used as-is. At the same time
this is cleaning up the recipients handling for age a bit.
Fixes#2544
RELEASE_NOTES=[BUGFIX] Fix recipients check for age.
Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
* Check existing recipients before trying to add a new one
Fixes#1918
RELEASE_NOTES=[ENHANCEMENT] Check recipients before adding a new one.
Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
* Add test for CheckRecipients with an invalid key.
Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
* Add custom error type and a better error message.
Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
* Initialize InvalidRecipientsError
Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
* Skip CheckRecipients tests on Windows
Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
This commit replaces `os.Setenv` with `t.Setenv` in tests. The
environment variable is automatically restored to its original value
when the test and all its subtests complete.
Reference: https://pkg.go.dev/testing#T.Setenv
Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
This commit adds yet another config handler for gopass. It is based on
the format used by git itself. This has the potential to address a lot
of long standing issues, but it also causes a lot of changes to how we
handle configuration, so bugs are inevitable.
Fixes#1567Fixes#1764Fixes#1819Fixes#1878Fixes#2387Fixes#2418
RELEASE_NOTES=[BREAKING] New config format based on git config.
Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
Co-authored-by: Yolan Romailler <AnomalRoil@users.noreply.github.com>
address comments
Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
This commit replaces `os.MkdirTemp` with `t.TempDir` in tests. The
directory created by `t.TempDir` is automatically removed when the test
and all its subtests complete.
Prior to this commit, temporary directory created using `os.MkdirTemp`
needs to be removed manually by calling `os.RemoveAll`, which is omitted
in some tests. The error handling boilerplate e.g.
defer func() {
if err := os.RemoveAll(dir); err != nil {
t.Fatal(err)
}
}
is also tedious, but `t.TempDir` handles this for us nicely.
Reference: https://pkg.go.dev/testing#T.TempDir
Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
* Improve passage support
This commit adds parsing of unencrypted passage identities and
automatically mounts an existing passage store under the mount
point passage/ if it's detected during setup (not later).
Fixes#2059
RELEASE_NOTES=[ENHANCEMENT] Improve passage support
Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
* Fix lint checks
Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
Signed-off-by: Dominik Schulz <dominik.schulz@gauner.org>
* Use OS Keyring to cache age passphrases
* Add askpass for age
Fixes#2350
RELEASE_NOTES=[ENHANCEMENT] Use OS keychain for age passphrase caching (new config option, off by default).