From a7c918a2e5962f2885d3f6bd6b9d8b5bcfc93111 Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Tue, 7 Apr 2026 17:51:49 +0200 Subject: [PATCH 1/2] Use shorter and more unique cache keys for GPG agent. Signed-off-by: Felix Fontein --- age/encrypted_keys.go | 7 +++---- age/keysource.go | 46 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 42 insertions(+), 11 deletions(-) diff --git a/age/encrypted_keys.go b/age/encrypted_keys.go index 01faabdaa..1d270f07a 100644 --- a/age/encrypted_keys.go +++ b/age/encrypted_keys.go @@ -104,7 +104,7 @@ func (i *LazyScryptIdentity) Unwrap(stanzas []*age.Stanza) (fileKey []byte, err return fileKey, err } -func unwrapIdentities(location string, reader io.Reader) (ParsedIdentities, error) { +func unwrapIdentities(location string, shortID string, reader io.Reader) (ParsedIdentities, error) { b := bufio.NewReader(reader) p, _ := b.Peek(14) // length of "age-encryption" and "-----BEGIN AGE" peeked := string(p) @@ -134,7 +134,7 @@ func unwrapIdentities(location string, reader io.Reader) (ParsedIdentities, erro log.Errorf("failed to close connection with gpg-agent: %s", err) } }(conn) - err = conn.RemoveFromCache(location) + err = conn.RemoveFromCache(shortID) if err != nil { log.Warnf("gpg-agent remove cache request errored: %s", err) return @@ -154,8 +154,7 @@ func unwrapIdentities(location string, reader io.Reader) (ParsedIdentities, erro }(conn) req := gpgagent.PassphraseRequest{ - // TODO is the cachekey good enough? - CacheKey: location, + CacheKey: shortID, Prompt: "Passphrase", Desc: fmt.Sprintf("Enter passphrase for identity '%s':", location), } diff --git a/age/keysource.go b/age/keysource.go index cdf6b1d69..03d2c6e6c 100644 --- a/age/keysource.go +++ b/age/keysource.go @@ -3,6 +3,8 @@ package age import ( "bufio" "bytes" + "crypto/sha256" + "encoding/base64" "errors" "fmt" "io" @@ -227,7 +229,7 @@ func formatError(msg string, err error, errs errSet, unusedLocations []string) e } else if count == 2 { unusedSuffix = fmt.Sprintf("s '%s' and '%s'", unusedLocations[0], unusedLocations[1]) } else { - unusedSuffix = fmt.Sprintf("s '%s', and '%s'", strings.Join(unusedLocations[:count - 1], "', '"), unusedLocations[count - 1]) + unusedSuffix = fmt.Sprintf("s '%s', and '%s'", strings.Join(unusedLocations[:count-1], "', '"), unusedLocations[count-1]) } unusedSuffix = fmt.Sprintf(". Did not find keys in location%s.", unusedSuffix) } @@ -399,6 +401,20 @@ func getUserConfigDir() (string, error) { return os.UserConfigDir() } +type readerData struct { + reader io.Reader + source string + path string +} + +func (d *readerData) getShortID() string { + if len(d.path) == 0 { + return fmt.Sprintf("sops-%s", d.source) + } + pathHash := sha256.Sum256([]byte(d.path)) + return fmt.Sprintf("sops-%s-%s", d.source, base64.StdEncoding.EncodeToString(pathHash[:])) +} + // loadIdentities attempts to load the age identities based on runtime // environment configurations (e.g. SopsAgeKeyEnv, SopsAgeKeyFileEnv, // SopsAgeSshPrivateKeyFileEnv, SopsAgeKeyUserConfigPath). It will load all @@ -406,10 +422,14 @@ func getUserConfigDir() (string, error) { func (key *MasterKey) loadIdentities() (ParsedIdentities, []string, errSet) { identities, unusedLocations, errs := key.loadAgeSSHIdentities() - var readers = make(map[string]io.Reader, 0) + var readers = make(map[string]readerData, 0) if ageKey, ok := os.LookupEnv(SopsAgeKeyEnv); ok { - readers[SopsAgeKeyEnv] = strings.NewReader(ageKey) + readers[SopsAgeKeyEnv] = readerData{ + reader: strings.NewReader(ageKey), + source: SopsAgeKeyEnv, + path: "", + } } else { unusedLocations = append(unusedLocations, SopsAgeKeyEnv) } @@ -420,7 +440,11 @@ func (key *MasterKey) loadIdentities() (ParsedIdentities, []string, errSet) { errs = append(errs, fmt.Errorf("failed to open %s file: %w", SopsAgeKeyFileEnv, err)) } else { defer f.Close() - readers[SopsAgeKeyFileEnv] = f + readers[SopsAgeKeyFileEnv] = readerData{ + reader: f, + source: SopsAgeKeyFileEnv, + path: "", + } } } else { unusedLocations = append(unusedLocations, SopsAgeKeyFileEnv) @@ -431,7 +455,11 @@ func (key *MasterKey) loadIdentities() (ParsedIdentities, []string, errSet) { if err != nil { errs = append(errs, err) } else { - readers[SopsAgeKeyCmdEnv] = bytes.NewReader(out) + readers[SopsAgeKeyCmdEnv] = readerData{ + reader: bytes.NewReader(out), + source: SopsAgeKeyCmdEnv, + path: "", + } } } else { unusedLocations = append(unusedLocations, SopsAgeKeyCmdEnv) @@ -449,12 +477,16 @@ func (key *MasterKey) loadIdentities() (ParsedIdentities, []string, errSet) { unusedLocations = append(unusedLocations, ageKeyFilePath) } else if err == nil { defer f.Close() - readers[ageKeyFilePath] = f + readers[ageKeyFilePath] = readerData{ + reader: f, + source: "file", + path: ageKeyFilePath, + } } } for location, r := range readers { - ids, err := unwrapIdentities(location, r) + ids, err := unwrapIdentities(location, r.getShortID(), r.reader) if err != nil { errs = append(errs, err) } else { From 9ef79530b5bdf78718960042e627bebb6fad532d Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Tue, 7 Apr 2026 22:07:01 +0200 Subject: [PATCH 2/2] Restrict key length. Signed-off-by: Felix Fontein --- age/keysource.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/age/keysource.go b/age/keysource.go index 03d2c6e6c..41f8583c6 100644 --- a/age/keysource.go +++ b/age/keysource.go @@ -412,7 +412,7 @@ func (d *readerData) getShortID() string { return fmt.Sprintf("sops-%s", d.source) } pathHash := sha256.Sum256([]byte(d.path)) - return fmt.Sprintf("sops-%s-%s", d.source, base64.StdEncoding.EncodeToString(pathHash[:])) + return fmt.Sprintf("sops-%s-%s", d.source, base64.StdEncoding.EncodeToString(pathHash[:27])) } // loadIdentities attempts to load the age identities based on runtime