From 4f06780ab25b185abad02d63f11cb8b7bbe888a5 Mon Sep 17 00:00:00 2001 From: "Dr. Uwe Daub" Date: Tue, 14 Jul 2020 21:25:06 +0200 Subject: [PATCH] Fix endless loop in x/crypto/openpgp func ReadMessage (#690) * Fix tests * Fix endless loop in x/crypto/openpgp func ReadMessage This fixes https://github.com/mozilla/sops/issues/665 See also https://github.com/golang/go/issues/28786 In some strange situations it can happen, that openpgp.ReadMessage() runs into a endless loop. This seems to be triggered by a slightly inconsistency in key settings. It happened to me, but I wasn't able to reproduce it with a fresh key. A proposed solution from the x/crypto community was, to break this loop in the callback passphrasePrompt. * Revert "Fix tests" This reverts commit 285f4dc8a1bf5e2c774fe48ae0daee2ac1511e6d. * Improve error description https://github.com/mozilla/sops/pull/690#discussion_r451630193 --- pgp/keysource.go | 67 +++++++++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/pgp/keysource.go b/pgp/keysource.go index 153709220..5f111df2c 100644 --- a/pgp/keysource.go +++ b/pgp/keysource.go @@ -216,7 +216,7 @@ func (key *MasterKey) decryptWithCryptoOpenpgp() ([]byte, error) { if err != nil { return nil, fmt.Errorf("Armor decoding failed: %s", err) } - md, err := openpgp.ReadMessage(block.Body, ring, key.passphrasePrompt, nil) + md, err := openpgp.ReadMessage(block.Body, ring, key.passphrasePrompt(), nil) if err != nil { return nil, fmt.Errorf("Reading PGP message failed: %s", err) } @@ -318,39 +318,48 @@ func (key *MasterKey) fingerprintMap(ring openpgp.EntityList) map[string]openpgp return fps } -func (key *MasterKey) passphrasePrompt(keys []openpgp.Key, symmetric bool) ([]byte, error) { - conn, err := gpgagent.NewConn() - if err == gpgagent.ErrNoAgent { - log.Infof("gpg-agent not found, continuing with manual passphrase " + - "input...") - fmt.Print("Enter PGP key passphrase: ") - pass, err := gopass.GetPasswd() - if err != nil { - return nil, err - } - for _, k := range keys { - k.PrivateKey.Decrypt(pass) +func (key *MasterKey) passphrasePrompt() func(keys []openpgp.Key, symmetric bool) ([]byte, error) { + callCounter := 0 + maxCalls := 3 + return func(keys []openpgp.Key, symmetric bool) ([]byte, error) { + if callCounter >= maxCalls { + return nil, fmt.Errorf("function passphrasePrompt called too many times") } - return pass, err - } - if err != nil { - return nil, fmt.Errorf("Could not establish connection with gpg-agent: %s", err) - } - defer conn.Close() - for _, k := range keys { - req := gpgagent.PassphraseRequest{ - CacheKey: k.PublicKey.KeyIdShortString(), - Prompt: "Passphrase", - Desc: fmt.Sprintf("Unlock key %s to decrypt sops's key", k.PublicKey.KeyIdShortString()), + callCounter++ + + conn, err := gpgagent.NewConn() + if err == gpgagent.ErrNoAgent { + log.Infof("gpg-agent not found, continuing with manual passphrase " + + "input...") + fmt.Print("Enter PGP key passphrase: ") + pass, err := gopass.GetPasswd() + if err != nil { + return nil, err + } + for _, k := range keys { + k.PrivateKey.Decrypt(pass) + } + return pass, err } - pass, err := conn.GetPassphrase(&req) if err != nil { - return nil, fmt.Errorf("gpg-agent passphrase request errored: %s", err) + return nil, fmt.Errorf("Could not establish connection with gpg-agent: %s", err) + } + defer conn.Close() + for _, k := range keys { + req := gpgagent.PassphraseRequest{ + CacheKey: k.PublicKey.KeyIdShortString(), + Prompt: "Passphrase", + Desc: fmt.Sprintf("Unlock key %s to decrypt sops's key", k.PublicKey.KeyIdShortString()), + } + pass, err := conn.GetPassphrase(&req) + if err != nil { + return nil, fmt.Errorf("gpg-agent passphrase request errored: %s", err) + } + k.PrivateKey.Decrypt([]byte(pass)) + return []byte(pass), nil } - k.PrivateKey.Decrypt([]byte(pass)) - return []byte(pass), nil + return nil, fmt.Errorf("No key to unlock") } - return nil, fmt.Errorf("No key to unlock") } // ToMap converts the MasterKey into a map for serialization purposes