Skip to content

Commit

Permalink
fix: handle havekey with no prior reset
Browse files Browse the repository at this point in the history
This can occur when there is some error decrypting using the preferred
key and instead gpg falls back to the non-preferred key.

I saw this issue when using pass with multiple keyids specified for
encryption.
  • Loading branch information
smlx committed Nov 20, 2021
1 parent 6ca6c21 commit 3544891
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 41 deletions.
93 changes: 56 additions & 37 deletions internal/assuan/assuan.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ type KeyService interface {
// New initialises a new gpg-agent server assuan FSM.
// It returns a *fsm.Machine configured in the ready state.
func New(rw io.ReadWriter, log *zap.Logger, ks ...KeyService) *Assuan {
var keyFound bool
var signature []byte
var keygrips, hash [][]byte
assuan := Assuan{
Expand All @@ -49,10 +48,7 @@ func New(rw io.ReadWriter, log *zap.Logger, ks ...KeyService) *Assuan {
_, err = io.WriteString(rw,
"OK Pleased to meet you, process 123456789\n")
case reset:
assuan.signer = nil
assuan.decrypter = nil
assuan.hashAlgo = 0
assuan.hash = []byte{}
assuan.reset()
_, err = io.WriteString(rw, "OK\n")
case option:
// ignore option values - piv-agent doesn't use them
Expand All @@ -65,38 +61,7 @@ func New(rw io.ReadWriter, log *zap.Logger, ks ...KeyService) *Assuan {
err = fmt.Errorf("unknown getinfo command: %q", assuan.data[0])
}
case havekey:
// HAVEKEY arguments are either:
// * a list of keygrips; or
// * --list=1000
// if _any_ key is available, we return OK, otherwise No_Secret_Key.
// handle --list
if bytes.HasPrefix(assuan.data[0], []byte("--list")) {
var grips []byte
grips, err = allKeygrips(ks)
if err != nil {
_, _ = io.WriteString(rw, "ERR 1 couldn't list keygrips\n")
return err
}
// apply buggy libgcrypt encoding
_, err = io.WriteString(rw, fmt.Sprintf("D %s\nOK\n",
PercentEncodeSExp(grips)))
return err
}
// handle list of keygrips
keygrips, err = hexDecode(assuan.data...)
if err != nil {
return fmt.Errorf("couldn't decode keygrips: %v", err)
}
keyFound, _, err = haveKey(ks, keygrips)
if err != nil {
_, _ = io.WriteString(rw, "ERR 1 couldn't check for keygrip\n")
return err
}
if keyFound {
_, err = io.WriteString(rw, "OK\n")
} else {
_, err = io.WriteString(rw, "No_Secret_Key\n")
}
err = assuan.havekey(rw, ks)
case keyinfo:
err = doKeyinfo(rw, assuan.data, ks)
case scd:
Expand Down Expand Up @@ -291,6 +256,14 @@ func New(rw io.ReadWriter, log *zap.Logger, ks ...KeyService) *Assuan {
// ignore this event since we don't currently use the client's
// description in the prompt
_, err = io.WriteString(rw, "OK\n")
case havekey:
// gpg skips the RESET command occasionally so we have to emulate it.
assuan.reset()
// now jump straight to havekey
if err = assuan.havekey(rw, ks); err != nil {
return err
}
_, err = io.WriteString(rw, "OK\n")
default:
return fmt.Errorf("unknown event: %v", Event(e))
}
Expand All @@ -301,6 +274,52 @@ func New(rw io.ReadWriter, log *zap.Logger, ks ...KeyService) *Assuan {
return &assuan
}

func (assuan *Assuan) reset() {
assuan.signer = nil
assuan.decrypter = nil
assuan.hashAlgo = 0
assuan.hash = []byte{}
}

func (assuan *Assuan) havekey(rw io.ReadWriter, ks []KeyService) error {
var err error
var keyFound bool
var keygrips [][]byte
// HAVEKEY arguments are either:
// * a list of keygrips; or
// * --list=1000
// if _any_ key is available, we return OK, otherwise No_Secret_Key.
// handle --list
if bytes.HasPrefix(assuan.data[0], []byte("--list")) {
var grips []byte
grips, err = allKeygrips(ks)
if err != nil {
_, _ = io.WriteString(rw, "ERR 1 couldn't list keygrips\n")
return err
}
// apply buggy libgcrypt encoding
_, err = io.WriteString(rw, fmt.Sprintf("D %s\nOK\n",
PercentEncodeSExp(grips)))
return err
}
// handle list of keygrips
keygrips, err = hexDecode(assuan.data...)
if err != nil {
return fmt.Errorf("couldn't decode keygrips: %v", err)
}
keyFound, _, err = haveKey(ks, keygrips)
if err != nil {
_, _ = io.WriteString(rw, "ERR 1 couldn't check for keygrip\n")
return err
}
if keyFound {
_, err = io.WriteString(rw, "OK\n")
} else {
_, err = io.WriteString(rw, "No_Secret_Key\n")
}
return err
}

// doKeyinfo checks for key availability by keygrip, writing the result to rw.
func doKeyinfo(rw io.ReadWriter, data [][]byte, ks []KeyService) error {
// KEYINFO arguments are a list of keygrips
Expand Down
4 changes: 2 additions & 2 deletions internal/assuan/event_enumer.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions internal/assuan/fsm.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,5 +156,9 @@ var assuanTransitions = []fsm.Transition{
Src: fsm.State(decryptingKeyIsSet),
Event: fsm.Event(pkdecrypt),
Dst: fsm.State(waitingForCiphertext),
}, {
Src: fsm.State(waitingForCiphertext),
Event: fsm.Event(havekey),
Dst: fsm.State(connected),
},
}
4 changes: 2 additions & 2 deletions internal/assuan/state_enumer.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 3544891

Please sign in to comment.