From 1766e127acba2cde257f721b2270ad0572f38a78 Mon Sep 17 00:00:00 2001 From: Scott Leggett Date: Wed, 28 Jul 2021 21:31:39 +0800 Subject: [PATCH] feat: cache decryption passphrases for keyfiles --- internal/assuan/assuan_test.go | 7 ++++- internal/server/gpg.go | 54 +++++++++++++++++++++++----------- 2 files changed, 43 insertions(+), 18 deletions(-) diff --git a/internal/assuan/assuan_test.go b/internal/assuan/assuan_test.go index a5fd9fd..9683a2c 100644 --- a/internal/assuan/assuan_test.go +++ b/internal/assuan/assuan_test.go @@ -17,6 +17,7 @@ import ( "github.com/smlx/piv-agent/internal/pivservice" "github.com/smlx/piv-agent/internal/securitykey" "github.com/smlx/piv-agent/internal/server" + "go.uber.org/zap" "golang.org/x/crypto/cryptobyte" "golang.org/x/crypto/cryptobyte/asn1" "golang.org/x/crypto/openpgp" @@ -282,7 +283,11 @@ func TestKeyfileSigner(t *testing.T) { if tc.protected { mockPES.EXPECT().GetPGPPassphrase(gomock.Any()).Return([]byte("trustno1"), nil) } - g := server.NewGPG(nil, mockPES, nil, tc.path) + log, err := zap.NewDevelopment() + if err != nil { + tt.Fatal(err) + } + g := server.NewGPG(nil, mockPES, log, tc.path) if _, err := assuan.KeyfileSigner(g, tc.keygrip); err != nil { tt.Fatalf("couldn't find keygrip: %v", err) } diff --git a/internal/server/gpg.go b/internal/server/gpg.go index 90d36d1..19c34ff 100644 --- a/internal/server/gpg.go +++ b/internal/server/gpg.go @@ -31,6 +31,8 @@ type GPG struct { log *zap.Logger fallbackPrivKeys []*packet.PrivateKey pinentry PINEntryService + // cache passphrases used for decryption + passphrases [][]byte } // LoadFallbackKeys reads the given path and returns any private keys found. @@ -76,33 +78,51 @@ func NewGPG(p *pivservice.PIVService, pe PINEntryService, l *zap.Logger, path st // GetKey returns a matching private RSA key if the keygrip matches, and nil // otherwise. func (g *GPG) GetKey(keygrip []byte) *rsa.PrivateKey { + var pass []byte + var err error for _, k := range g.fallbackPrivKeys { pubKey, ok := k.PublicKey.PublicKey.(*rsa.PublicKey) if !ok { continue } - if bytes.Equal(keygrip, gpg.KeygripRSA(pubKey)) { - if k.Encrypted { - pass, err := g.pinentry.GetPGPPassphrase( - fmt.Sprintf("%X %X %X %X", k.Fingerprint[:5], k.Fingerprint[5:10], - k.Fingerprint[10:15], k.Fingerprint[15:])) - if err != nil { - g.log.Warn("couldn't get passphrase for key", - zap.String("fingerprint", k.KeyIdString()), zap.Error(err)) - return nil - } - if err = k.Decrypt(pass); err != nil { - g.log.Warn("couldn't decrypt key", - zap.String("fingerprint", k.KeyIdString()), zap.Error(err)) + if !bytes.Equal(keygrip, gpg.KeygripRSA(pubKey)) { + continue + } + if k.Encrypted { + // try existing passphrases + for _, pass := range g.passphrases { + if err = k.Decrypt(pass); err == nil { + g.log.Debug("decrypted using cached passphrase", + zap.String("fingerprint", k.KeyIdString())) + break } } - privKey, ok := k.PrivateKey.(*rsa.PrivateKey) - if !ok { - g.log.Info("not an RSA key", zap.String("fingerprint", k.KeyIdString())) + } + if k.Encrypted { + // ask for a passphrase + pass, err = g.pinentry.GetPGPPassphrase( + fmt.Sprintf("%X %X %X %X", k.Fingerprint[:5], k.Fingerprint[5:10], + k.Fingerprint[10:15], k.Fingerprint[15:])) + if err != nil { + g.log.Warn("couldn't get passphrase for key", + zap.String("fingerprint", k.KeyIdString()), zap.Error(err)) return nil } - return privKey + g.passphrases = append(g.passphrases, pass) + if err = k.Decrypt(pass); err != nil { + g.log.Warn("couldn't decrypt key", + zap.String("fingerprint", k.KeyIdString()), zap.Error(err)) + return nil + } + g.log.Debug("decrypted using passphrase", + zap.String("fingerprint", k.KeyIdString())) + } + privKey, ok := k.PrivateKey.(*rsa.PrivateKey) + if !ok { + g.log.Info("not an RSA key", zap.String("fingerprint", k.KeyIdString())) + return nil } + return privKey } return nil }