Skip to content

Commit

Permalink
feat: implement subkey signing support
Browse files Browse the repository at this point in the history
  • Loading branch information
smlx committed Aug 12, 2021
1 parent e3a8b08 commit 676c673
Show file tree
Hide file tree
Showing 6 changed files with 42 additions and 25 deletions.
2 changes: 1 addition & 1 deletion cmd/piv-agent/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
48 changes: 28 additions & 20 deletions internal/assuan/assuan.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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 <SCD>\n")
Expand Down Expand Up @@ -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))
}
Expand Down Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions internal/assuan/fsm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
4 changes: 2 additions & 2 deletions internal/assuan/readkey.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions internal/securitykey/string.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions internal/server/gpg.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 676c673

Please sign in to comment.