Use gpg.conf's default-key when unlocker add pgp has no --keyid (closes #141)
check / check (push) Waiting to run

getDefaultGPGKey took the default-key value from `gpgconf --list-options
gpg` as printed. gpgconf prints a string value with a leading `"` and
percent-escaped, so gpg rejected the key ID. The parsing moved into
parseDefaultGPGKey, which drops the `"` and percent-decodes the rest with
url.PathUnescape, which leaves `+` alone. A value that does not decode is
an error, not a fallback to the first secret key.

Rule suppressed: gosec G703 on the test's write of gpg.conf into the
GNUPGHOME directory newTestGPGKey made.

Model: opus-5-5
This commit was merged in pull request #167.
This commit is contained in:
2026-10-15 00:31:53 +02:00
parent 72cd6c30a1
commit 6d95d96477
2 changed files with 102 additions and 6 deletions
+73
View File
@@ -2,6 +2,8 @@
package cli
import (
"bytes"
"os"
"path/filepath"
"strings"
"testing"
@@ -83,6 +85,77 @@ func TestAddPGPUnlocker(t *testing.T) {
}
}
// TestAddPGPUnlockerDefaultKey is a regression test for
// https://git.eeqj.de/sneak/secret/issues/141: with default-key set in
// gpg.conf and no --keyid, `secret unlocker add pgp` uses that key.
// gpg.conf names the key by its user ID, and the printed key ID must be that
// user ID. The first secret key, which is used when gpg.conf sets no
// default-key, prints as a hex key ID.
func TestAddPGPUnlockerDefaultKey(t *testing.T) {
newTestGPGKey(t)
// The key comes from neither --keyid nor the environment.
t.Setenv(secret.EnvGPGKeyID, "")
gpgConf := filepath.Join(os.Getenv("GNUPGHOME"), "gpg.conf")
//nolint:gosec // G703: GNUPGHOME is the directory newTestGPGKey made
require.NoError(t, os.WriteFile(gpgConf,
[]byte("default-key "+unreadableTestGPGUserID+"\n"), 0o600))
fs := afero.NewMemMapFs()
_, err := vault.CreateVault(fs, listTestStateDir, listTestVaultName,
testMnemonicBuffer(t), nil)
require.NoError(t, err)
instance, cmd := newTestInstance(fs)
instance.Mnemonic = testMnemonicBuffer(t)
var output bytes.Buffer
cmd.SetOut(&output)
require.NoError(t, instance.UnlockersAdd(unlockerTypePGP, cmd))
assert.Contains(t, output.String(),
"Using default GPG key: "+unreadableTestGPGUserID+"\n")
}
// TestParseDefaultGPGKey is a regression test for
// https://git.eeqj.de/sneak/secret/issues/141: gpgconf prints the
// default-key value with a leading `"` and percent-escaped, and the key ID
// is that value decoded.
func TestParseDefaultGPGKey(t *testing.T) {
t.Parallel()
// gpgconfLine is the default-key line of `gpgconf --list-options gpg`
// up to the value, its last field.
const gpgconfLine = "default-key:0:0:use NAME as default secret key:" +
"1:1:NAME:::"
tests := []struct {
name string
value string
want string
}{
{
"fingerprint", `"56163D7F3FAFF709608855BEC1A360216D007F41`,
"56163D7F3FAFF709608855BEC1A360216D007F41",
},
{
"user ID with a comma", `"Doe%2c Alice <alice@example.com>`,
"Doe, Alice <alice@example.com>",
},
{"no default-key in gpg.conf", "", ""},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
keyID, err := parseDefaultGPGKey(gpgconfLine + test.value + "\n")
require.NoError(t, err)
assert.Equal(t, test.want, keyID)
})
}
}
// TestAddPGPUnlockerUnknownKey asserts that adding a PGP unlocker for a key
// the keyring does not hold fails at looking up the key's fingerprint and
// leaves no new unlocker directory. The error must come from the lookup: a