Skip to content

Commit

Permalink
feat: cache decryption passphrases for keyfiles
Browse files Browse the repository at this point in the history
  • Loading branch information
smlx committed Aug 4, 2021
1 parent 3be1b9b commit 1766e12
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 18 deletions.
7 changes: 6 additions & 1 deletion internal/assuan/assuan_test.go
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
}
Expand Down
54 changes: 37 additions & 17 deletions internal/server/gpg.go
Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
Expand Down

0 comments on commit 1766e12

Please sign in to comment.