Support GnuPG AEAD password entries

This commit is contained in:
Hermes Agent
2026-08-10 08:57:57 +00:00
parent d66d9b0f08
commit 977eb236da
10 changed files with 319 additions and 13 deletions

View File

@@ -38,6 +38,8 @@ encryption-capable primary key. Output uses AES-256 in a version 1
symmetrically-encrypted integrity-protected data packet and deliberately does
not add a compression packet, matching upstream `pass --compress-algo=none`.
Every resolved recipient receives a public-key encrypted session-key packet.
IronStorage continues to produce this packet-type-18 SEIPDv1/MDC profile so
existing output remains unchanged.
Decryption first examines those recipient packets. A secret is requested only
for imported protected keys that can match the message; unavailable identities
@@ -45,6 +47,37 @@ are skipped so any one recipient of a multi-recipient entry can decrypt it.
Cancellation stops immediately. Wrong secrets, malformed messages, and a lack
of matching secret keys remain distinct failures.
For compatibility with password stores written by modern GnuPG, decryption
also accepts GnuPG's packet-type-20 OCB AEAD extension; the checked-in
GnuPG 2.4.8 compatibility case uses AES-256.
This is an explicit read-compatibility profile: it does not enable historical
unauthenticated encrypted-data packets, accept unsupported packet-20 modes, or
change the format IronStorage writes. AEAD authentication must succeed before
the provider is told that an interactively supplied passphrase was accepted;
wrong passphrases and tampered ciphertext therefore cannot be persisted.
### Upstream `pass` format boundary
The format boundary was re-audited against the current upstream tools on
2026-08-10. [`pass` 1.7.4 delegates reads and writes directly to
GnuPG](https://git.zx2c4.com/password-store/plain/src/password-store.sh): reads
use `gpg -d`, while writes use `gpg -e` with `--compress-algo=none`; `pass`
does not select or parse an encrypted-data packet itself. The current
[Homebrew `pass` formula](https://formulae.brew.sh/formula/pass) packages that
same release with GnuPG 2.5.21. GnuPG's
[OpenPGP options](https://www.gnupg.org/documentation/manuals/gnupg/OpenPGP-Options.html)
state that public-key encryption selects CFB+MDC or OCB from recipient key
preferences, which explains why an ordinary current `pass` installation can
write packet type 20 without a `pass` option requesting it.
Default `pass` does not opt into unauthenticated legacy decryption. GnuPG made
missing-MDC messages a hard failure in 2.2.8 and requires the explicit,
dangerous `--ignore-mdc-error` override to recover plaintext from them. The
[GnuPG 2.2.8 security release](https://lists.gnupg.org/pipermail/gnupg-users/2018-June/060644.html)
warns against using that override unconditionally. IronStorage therefore keeps
legacy packet-type-9 decryption disabled: its default behavior matches default
`pass` while preserving the authenticated-decryption guarantee.
## Secret lifetime and recipient signatures
Decrypted data and provider-returned unlock secrets use `SecretBytes`. Its
@@ -65,12 +98,15 @@ from another imported key is rejected.
## Compatibility evidence
`crates/storage/tests/fixtures/compatibility` contains protected public and
secret exports, single- and multi-recipient `.gpg` entries, and signed
recipient files. The fixture audit established that GnuPG decrypts the packet
profile used by the generator. Production tests then exercise that same
uncompressed `MessageBuilder` profile through `KeyStore::encrypt`, independently
parse and decrypt its output, decrypt every checked-in GnuPG-audited entry, and
verify both checked-in and newly generated recipient signatures.
secret exports, single- and multi-recipient `.gpg` entries, signed recipient
files, and a GnuPG 2.4.8-produced packet-type-20 AES-256/OCB entry. The fixture
audit established that GnuPG decrypts the packet profile used by the generator.
Production tests then exercise that same uncompressed `MessageBuilder` profile
through `KeyStore::encrypt`, independently parse and decrypt its output, decrypt
every checked-in GnuPG-audited entry through the storage API, and verify both
checked-in and newly generated recipient signatures. Packet-type-20 tests also
cover the exact encryption subkey, wrong passphrases, authentication failure,
missing secret keys, unsupported modes, and deferred secret persistence.
Tests never use a real user keyring and application runtime never executes an
external cryptographic tool.