Skip to content

Commit

Permalink
yubikey-agent: update piv-go and avoid Serial to enable PIN caching
Browse files Browse the repository at this point in the history
go-piv/piv-go#37, go-piv/piv-go#39, and go-piv/piv-go#44 add support for
"once" (per session) PIN policies.

Serial() causes the PIN cache to drop, so only call it once at the start
and replace the health check with AttestationCertificate().
  • Loading branch information
FiloSottile committed Apr 26, 2020
1 parent ef2a5a9 commit ed4b7bc
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 19 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module filippo.io/yubikey-agent
go 1.14

require (
github.com/go-piv/piv-go v1.3.0
github.com/go-piv/piv-go v1.4.1-0.20200426040337-bf7b63063bf0
github.com/gopasspw/gopass v1.8.6
github.com/pkg/errors v0.9.1 // indirect
golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
github.com/go-piv/piv-go v1.3.0 h1:yYseIwClvEU9Dv0R9ZBF1vklazq9QD7NQzen1zmuR+8=
github.com/go-piv/piv-go v1.3.0/go.mod h1:ON2WvQncm7dIkCQ7kYJs+nc3V4jHGfrrJnSF8HKy7Gk=
github.com/go-piv/piv-go v1.4.1-0.20200426040337-bf7b63063bf0 h1:Ft86hUvyWPsygFFD9HKdLvMDqVG9StpvcU3Cdh5d/ss=
github.com/go-piv/piv-go v1.4.1-0.20200426040337-bf7b63063bf0/go.mod h1:ON2WvQncm7dIkCQ7kYJs+nc3V4jHGfrrJnSF8HKy7Gk=
github.com/gopasspw/gopass v1.8.6 h1:TWF7Zsj63xR+T6qIRKZ0ZM0fRDBfSv10yd9rLn631SE=
github.com/gopasspw/gopass v1.8.6/go.mod h1:IvJpW3zL4Dmp9uPntPl4tLgX3V+CymjeSwtx0NTkf98=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
Expand Down
40 changes: 24 additions & 16 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ func main() {
}

type Agent struct {
yk *piv.YubiKey
yk *piv.YubiKey
serial uint32
}

var _ agent.ExtendedAgent = &Agent{}
Expand All @@ -73,13 +74,16 @@ func (a *Agent) serveConn(c net.Conn) {
}

func healthy(yk *piv.YubiKey) bool {
_, err := yk.Serial()
// We can't use Serial because it locks the session on older firmwares, and
// can't use Retries because it fails when the session is unlocked.
_, err := yk.AttestationCertificate()
return err == nil
}

func (a *Agent) ensureYK() error {
if a.yk == nil || !healthy(a.yk) {
if a.yk != nil {
log.Println("Reconnecting to the YubiKey...")
a.yk.Close()
}
yk, err := a.connectToYK()
Expand All @@ -100,7 +104,14 @@ func (a *Agent) connectToYK() (*piv.YubiKey, error) {
return nil, errors.New("no YubiKey detected")
}
// TODO: support multiple YubiKeys.
return piv.Open(cards[0])
yk, err := piv.Open(cards[0])
if err != nil {
return nil, err
}
// Cache the serial number locally because requesting it on older firmwares
// requires switching application, which drops the PIN cache.
a.serial, _ = yk.Serial()
return yk, nil
}

func (a *Agent) getPIN() (string, error) {
Expand All @@ -110,15 +121,12 @@ func (a *Agent) getPIN() (string, error) {
}
defer p.Close()
p.Set("title", "yubikey-agent PIN Prompt")
serial, _ := a.yk.Serial()
p.Set("desc", fmt.Sprintf("YubiKey serial number: %d", serial))
p.Set("desc", fmt.Sprintf("YubiKey serial number: %d", a.serial))
p.Set("prompt", "Please enter your PIN:")
pin, err := p.GetPin()
return string(pin), err
}

var ErrOperationUnsupported = errors.New("operation unsupported")

func (a *Agent) List() ([]*agent.Key, error) {
if err := a.ensureYK(); err != nil {
return nil, fmt.Errorf("could not reach YubiKey: %w", err)
Expand All @@ -127,16 +135,15 @@ func (a *Agent) List() ([]*agent.Key, error) {
if err != nil {
return nil, err
}
serial, _ := a.yk.Serial()
return []*agent.Key{{
Format: pk.Type(),
Blob: pk.Marshal(),
Comment: fmt.Sprintf("YubiKey #%d PIV Slot 9a", serial),
Comment: fmt.Sprintf("YubiKey #%d PIV Slot 9a", a.serial),
}}, nil
}

func getPublicKey(yk *piv.YubiKey, slot piv.Slot) (ssh.PublicKey, error) {
cert, err := yk.Attest(slot)
cert, err := yk.Certificate(slot)
if err != nil {
return nil, fmt.Errorf("could not get public key: %w", err)
}
Expand Down Expand Up @@ -174,6 +181,10 @@ func (a *Agent) Signers() ([]ssh.Signer, error) {
return []ssh.Signer{s}, nil
}

func (a *Agent) Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) {
return a.SignWithFlags(key, data, 0)
}

func (a *Agent) SignWithFlags(key ssh.PublicKey, data []byte, flags agent.SignatureFlags) (*ssh.Signature, error) {
signers, err := a.Signers()
if err != nil {
Expand All @@ -190,21 +201,18 @@ func (a *Agent) SignWithFlags(key ssh.PublicKey, data []byte, flags agent.Signat
case flags&agent.SignatureFlagRsaSha512 != 0:
alg = ssh.SigAlgoRSASHA2512
}
// TODO: the PIN is asked every time even if the policy is "once".
// This is an upstream issue: https://github.com/go-piv/piv-go/issues/35
// TODO: maybe retry if the PIN is not correct?
return s.(ssh.AlgorithmSigner).SignWithAlgorithm(rand.Reader, data, alg)
}
return nil, fmt.Errorf("no private keys match the requested public key")
}

func (a *Agent) Sign(key ssh.PublicKey, data []byte) (*ssh.Signature, error) {
return a.SignWithFlags(key, data, 0)
}

func (a *Agent) Extension(extensionType string, contents []byte) ([]byte, error) {
return nil, agent.ErrExtensionUnsupported
}

var ErrOperationUnsupported = errors.New("operation unsupported")

func (a *Agent) Add(key agent.AddedKey) error {
return ErrOperationUnsupported
}
Expand Down

0 comments on commit ed4b7bc

Please sign in to comment.