diff --git a/cmd/piv-agent/serve.go b/cmd/piv-agent/serve.go index bb875de..cd66c97 100644 --- a/cmd/piv-agent/serve.go +++ b/cmd/piv-agent/serve.go @@ -80,7 +80,7 @@ func (cmd *ServeCmd) Run(log *zap.Logger) error { if err != nil { log.Warn("couldn't determine $HOME", zap.Error(err)) } - fallbackKeys := filepath.Join(home, ".gnupg", "piv-agent.secring.gpg") + fallbackKeys := filepath.Join(home, ".gnupg", "piv-agent.secring") if _, ok := cmd.AgentTypes["gpg"]; ok { log.Debug("starting GPG server") g.Go(func() error { diff --git a/internal/assuan/assuan.go b/internal/assuan/assuan.go index cb92fef..6b8f780 100644 --- a/internal/assuan/assuan.go +++ b/internal/assuan/assuan.go @@ -33,7 +33,7 @@ var ciphertextRegex = regexp.MustCompile( // 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 keygrip, signature []byte + var signature []byte var keygrips, hash [][]byte assuan := Assuan{ reader: bufio.NewReader(rw), @@ -86,25 +86,7 @@ func New(rw io.ReadWriter, log *zap.Logger, ks ...KeyService) *Assuan { _, err = io.WriteString(rw, "No_Secret_Key\n") } case keyinfo: - // KEYINFO arguments are a list of keygrips - // if _any_ key is available, we return info, otherwise - // No_Secret_Key. - keygrips, err = hexDecode(assuan.data...) - if err != nil { - return fmt.Errorf("couldn't decode keygrips: %v", err) - } - keyFound, keygrip, err = haveKey(ks, keygrips) - if err != nil { - _, _ = io.WriteString(rw, "ERR 1 couldn't match keygrip\n") - return fmt.Errorf("couldn't match keygrip: %v", err) - } - if keyFound { - _, err = io.WriteString(rw, - fmt.Sprintf("S KEYINFO %s D - - - - - - -\nOK\n", - strings.ToUpper(hex.EncodeToString(keygrip)))) - } else { - _, err = io.WriteString(rw, "No_Secret_Key\n") - } + err = doKeyinfo(rw, assuan.data, ks) case scd: // ignore scdaemon requests _, err = io.WriteString(rw, "ERR 100696144 No such device \n") @@ -213,6 +195,8 @@ func New(rw io.ReadWriter, log *zap.Logger, ks ...KeyService) *Assuan { return fmt.Errorf("couldn't write newline: %v", err) } _, err = io.WriteString(rw, "OK\n") + case keyinfo: + err = doKeyinfo(rw, assuan.data, ks) default: return fmt.Errorf("unknown event: %v", Event(e)) } @@ -315,6 +299,30 @@ func New(rw io.ReadWriter, log *zap.Logger, ks ...KeyService) *Assuan { return &assuan } +// 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 + // if _any_ key is available, we return info, otherwise + // No_Secret_Key. + keygrips, err := hexDecode(data...) + if err != nil { + return fmt.Errorf("couldn't decode keygrips: %v", err) + } + keyFound, keygrip, err := haveKey(ks, keygrips) + if err != nil { + _, _ = io.WriteString(rw, "ERR 1 couldn't match keygrip\n") + return fmt.Errorf("couldn't match keygrip: %v", err) + } + if keyFound { + _, err = io.WriteString(rw, + fmt.Sprintf("S KEYINFO %s D - - - - - - -\nOK\n", + strings.ToUpper(hex.EncodeToString(keygrip)))) + return err + } + _, err = io.WriteString(rw, "No_Secret_Key\n") + return err +} + // haveKey returns true if any of the keygrips refer to keys known locally, and // false otherwise. // It takes keygrips in raw byte format, so keygrip in hex-encoded form must diff --git a/internal/assuan/fsm.go b/internal/assuan/fsm.go index 48c28d2..443fade 100644 --- a/internal/assuan/fsm.go +++ b/internal/assuan/fsm.go @@ -134,6 +134,14 @@ var assuanTransitions = []fsm.Transition{ Src: fsm.State(hashIsSet), Event: fsm.Event(pksign), Dst: fsm.State(hashIsSet), + }, { + Src: fsm.State(hashIsSet), + Event: fsm.Event(keyinfo), + Dst: fsm.State(hashIsSet), + }, { + Src: fsm.State(hashIsSet), + Event: fsm.Event(reset), + Dst: fsm.State(connected), }, // decrypting transitions { diff --git a/internal/assuan/readkey.go b/internal/assuan/readkey.go index c658c66..5d7ffa9 100644 --- a/internal/assuan/readkey.go +++ b/internal/assuan/readkey.go @@ -21,14 +21,14 @@ func readKeyData(pub crypto.PublicKey) (string, error) { ei.SetInt64(int64(k.E)) e := ei.Bytes() // prefix the key with a null byte for compatibility - return fmt.Sprintf("D (10:public-key(3:rsa(1:n%d:\x00%s)(1:e%d:%s)))\n", + return fmt.Sprintf("D (10:public-key(3:rsa(1:n%d:\x00%s)(1:e%d:%s)))\nOK\n", nLen+1, n, len(e), e), nil case *ecdsa.PublicKey: switch k.Curve { case elliptic.P256(): q := elliptic.Marshal(k.Curve, k.X, k.Y) return fmt.Sprintf( - "D (10:public-key(3:ecc(5:curve10:NIST P-256)(1:q%d:%s)))\n", + "D (10:public-key(3:ecc(5:curve10:NIST P-256)(1:q%d:%s)))\nOK\n", len(q), q), nil default: return "", fmt.Errorf("unsupported curve: %T", k.Curve) diff --git a/internal/securitykey/string.go b/internal/securitykey/string.go index 57d39ee..9e5f948 100644 --- a/internal/securitykey/string.go +++ b/internal/securitykey/string.go @@ -74,6 +74,7 @@ func (k *SecurityKey) synthesizeEntities(name, email string) ([]Entity, error) { CreationTime: now, SigType: packet.SigTypePositiveCert, // TODO: determine the key type + // TODO: support ECDH PubKeyAlgo: packet.PubKeyAlgoECDSA, Hash: crypto.SHA256, IssuerKeyId: &pub.KeyId, diff --git a/internal/server/gpg.go b/internal/server/gpg.go index 4494672..9cde7ca 100644 --- a/internal/server/gpg.go +++ b/internal/server/gpg.go @@ -48,8 +48,8 @@ func (g *GPG) Serve(ctx context.Context, l net.Listener, exit *time.Ticker, } // reset the exit timer exit.Reset(timeout) - // if the client stops responding for 60 seconds, give up. - if err := conn.SetDeadline(time.Now().Add(60 * time.Second)); err != nil { + // if the client stops responding for 300 seconds, give up. + if err := conn.SetDeadline(time.Now().Add(300 * time.Second)); err != nil { return fmt.Errorf("couldn't set deadline: %v", err) } // init protocol state machine