Skip to content

Commit

Permalink
feat: make the pinentry binary name configurable
Browse files Browse the repository at this point in the history
Some environments do not implement the alternatives system, so make the
pinentry binary name configurable. Defaults to `pinentry`.
  • Loading branch information
smlx committed Oct 21, 2022
1 parent 1cb2cc2 commit 6d0edd7
Show file tree
Hide file tree
Showing 9 changed files with 49 additions and 23 deletions.
3 changes: 2 additions & 1 deletion cmd/piv-agent/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"strings"

"github.com/smlx/piv-agent/internal/keyservice/piv"
"github.com/smlx/piv-agent/internal/pinentry"
"go.uber.org/zap"
)

Expand All @@ -17,7 +18,7 @@ type ListCmd struct {

// Run the list command.
func (cmd *ListCmd) Run(l *zap.Logger) error {
p := piv.New(l)
p := piv.New(l, pinentry.New("pinentry"))
securityKeys, err := p.SecurityKeys()
if err != nil {
return fmt.Errorf("couldn't get security keys: %w", err)
Expand Down
18 changes: 10 additions & 8 deletions cmd/piv-agent/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ type agentTypeFlag map[string]uint

// ServeCmd represents the listen command.
type ServeCmd struct {
LoadKeyfile bool `kong:"default=true,help='Load the key file from ~/.ssh/id_ed25519'"`
ExitTimeout time.Duration `kong:"default=12h,help='Exit after this period to drop transaction and key file passphrase cache, even if service is in use'"`
IdleTimeout time.Duration `kong:"default=32m,help='Exit after this period of disuse'"`
TouchNotifyDelay time.Duration `kong:"default=6s,help='Display a notification after this period when waiting for a touch'"`
AgentTypes agentTypeFlag `kong:"default='ssh=0;gpg=1',help='Agent types to handle'"`
LoadKeyfile bool `kong:"default=true,help='Load the key file from ~/.ssh/id_ed25519'"`
ExitTimeout time.Duration `kong:"default=12h,help='Exit after this period to drop transaction and key file passphrase cache, even if service is in use'"`
IdleTimeout time.Duration `kong:"default=32m,help='Exit after this period of disuse'"`
TouchNotifyDelay time.Duration `kong:"default=6s,help='Display a notification after this period when waiting for a touch'"`
PinentryBinaryName string `kong:"default='pinentry',help='Pinentry binary which will be used, must be in $PATH'"`
AgentTypes agentTypeFlag `kong:"default='ssh=0;gpg=1',help='Agent types to handle'"`
}

// validAgents is the list of agents supported by piv-agent.
Expand All @@ -51,7 +52,8 @@ func (flagAgents *agentTypeFlag) AfterApply() error {
func (cmd *ServeCmd) Run(log *zap.Logger) error {
log.Info("startup", zap.String("version", version),
zap.String("build date", date))
p := piv.New(log)
pe := pinentry.New(cmd.PinentryBinaryName)
p := piv.New(log, pe)
defer p.CloseAll()
// use FDs passed via socket activation
ls, err := sockets.Get(validAgents)
Expand All @@ -74,7 +76,7 @@ func (cmd *ServeCmd) Run(log *zap.Logger) error {
log.Debug("starting SSH server")
g.Go(func() error {
s := server.NewSSH(log)
a := ssh.NewAgent(p, log, cmd.LoadKeyfile, n, cancel)
a := ssh.NewAgent(p, pe, log, cmd.LoadKeyfile, n, cancel)
err := s.Serve(ctx, a, ls[cmd.AgentTypes["ssh"]], idle, cmd.IdleTimeout)
if err != nil {
log.Debug("exiting SSH server", zap.Error(err))
Expand All @@ -94,7 +96,7 @@ func (cmd *ServeCmd) Run(log *zap.Logger) error {
if _, ok := cmd.AgentTypes["gpg"]; ok {
log.Debug("starting GPG server")
g.Go(func() error {
s := server.NewGPG(p, &pinentry.PINEntry{}, log, fallbackKeys, n)
s := server.NewGPG(p, pe, log, fallbackKeys, n)
err := s.Serve(ctx, ls[cmd.AgentTypes["gpg"]], idle, cmd.IdleTimeout)
if err != nil {
log.Debug("exiting GPG server", zap.Error(err))
Expand Down
3 changes: 2 additions & 1 deletion cmd/piv-agent/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"os"
"strconv"

"github.com/smlx/piv-agent/internal/pinentry"
"github.com/smlx/piv-agent/internal/securitykey"
"golang.org/x/crypto/ssh/terminal"
)
Expand Down Expand Up @@ -56,7 +57,7 @@ func (cmd *SetupCmd) Run() error {
if cmd.PIN < 100000 || cmd.PIN > 99999999 {
return fmt.Errorf("invalid PIN, must be 6-8 digits")
}
k, err := securitykey.New(cmd.Card)
k, err := securitykey.New(cmd.Card, pinentry.New("pinentry"))
if err != nil {
return fmt.Errorf("couldn't get security key: %v", err)
}
Expand Down
3 changes: 2 additions & 1 deletion cmd/piv-agent/setupslots.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"strconv"

"github.com/smlx/piv-agent/internal/pinentry"
"github.com/smlx/piv-agent/internal/securitykey"
)

Expand All @@ -30,7 +31,7 @@ func (cmd *SetupSlotsCmd) Run() error {
if cmd.PIN < 100000 || cmd.PIN > 99999999 {
return fmt.Errorf("invalid PIN, must be 6-8 digits")
}
k, err := securitykey.New(cmd.Card)
k, err := securitykey.New(cmd.Card, pinentry.New("pinentry"))
if err != nil {
return fmt.Errorf("couldn't get security key: %v", err)
}
Expand Down
8 changes: 6 additions & 2 deletions internal/keyservice/piv/keyservice.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Package piv implements the PIV keyservice.
package piv

import (
Expand All @@ -9,6 +10,7 @@ import (

pivgo "github.com/go-piv/piv-go/piv"
"github.com/smlx/piv-agent/internal/keyservice/gpg"
"github.com/smlx/piv-agent/internal/pinentry"
"go.uber.org/zap"
)

Expand All @@ -17,13 +19,15 @@ import (
type KeyService struct {
mu sync.Mutex
log *zap.Logger
pinentry *pinentry.PINEntry
securityKeys []SecurityKey
}

// New constructs a PIV and returns it.
func New(l *zap.Logger) *KeyService {
func New(l *zap.Logger, pe *pinentry.PINEntry) *KeyService {
return &KeyService{
log: l,
log: l,
pinentry: pe,
}
}

Expand Down
3 changes: 2 additions & 1 deletion internal/keyservice/piv/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"fmt"

"github.com/go-piv/piv-go/piv"
"github.com/smlx/piv-agent/internal/pinentry"
"github.com/smlx/piv-agent/internal/securitykey"
"go.uber.org/zap"
)
Expand Down Expand Up @@ -39,7 +40,7 @@ func (p *KeyService) reloadSecurityKeys() error {
return fmt.Errorf("couldn't get cards: %v", err)
}
for _, card := range cards {
sk, err := securitykey.New(card)
sk, err := securitykey.New(card, pinentry.New("pinentry"))
if err != nil {
p.log.Warn("couldn't get SecurityKey", zap.String("card", card),
zap.Error(err))
Expand Down
18 changes: 15 additions & 3 deletions internal/pinentry/pinentry.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Package pinentry implements a PIN/passphrase entry dialog.
package pinentry

import (
Expand All @@ -14,16 +15,26 @@ type SecurityKey interface {
}

// PINEntry implements useful pinentry service methods.
type PINEntry struct{}
type PINEntry struct {
binaryName string
}

// New initialises a new PINEntry.
func New(binaryName string) *PINEntry {
return &PINEntry{
binaryName: binaryName,
}
}

// GetPin uses pinentry to get the pin of the given token.
func GetPin(k SecurityKey) func() (string, error) {
func (pe *PINEntry) GetPin(k SecurityKey) func() (string, error) {
return func() (string, error) {
r, err := k.Retries()
if err != nil {
return "", fmt.Errorf("couldn't get retries for security key: %w", err)
}
c, err := gpm.NewClient(
gpm.WithBinaryName(pe.binaryName),
gpm.WithTitle("piv-agent PIN Prompt"),
gpm.WithPrompt("Please enter PIN:"),
gpm.WithDesc(
Expand All @@ -43,8 +54,9 @@ func GetPin(k SecurityKey) func() (string, error) {
}

// GetPassphrase uses pinentry to get the passphrase of the given key file.
func (*PINEntry) GetPassphrase(desc, keyID string, tries int) ([]byte, error) {
func (pe *PINEntry) GetPassphrase(desc, keyID string, tries int) ([]byte, error) {
c, err := gpm.NewClient(
gpm.WithBinaryName(pe.binaryName),
gpm.WithTitle("piv-agent Passphrase Prompt"),
gpm.WithPrompt("Please enter passphrase"),
gpm.WithDesc(fmt.Sprintf("%s\r(%d attempts remaining)", desc, tries)),
Expand Down
8 changes: 6 additions & 2 deletions internal/securitykey/securitykey.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Package securitykey provides an interface to a physical security key such as
// a Yubikey.
package securitykey

import (
Expand All @@ -19,6 +21,7 @@ type SecurityKey struct {
signingKeys []SigningKey
decryptingKeys []DecryptingKey
cryptoKeys []CryptoKey
pinentry *pinentry.PINEntry
}

// CryptoKey represents a cryptographic key on a hardware security device.
Expand All @@ -28,7 +31,7 @@ type CryptoKey struct {
}

// New returns a security key identified by card string.
func New(card string) (*SecurityKey, error) {
func New(card string, pe *pinentry.PINEntry) (*SecurityKey, error) {
yk, err := piv.Open(card)
if err != nil {
return nil, fmt.Errorf(`couldn't open card "%s": %v`, card, err)
Expand Down Expand Up @@ -64,6 +67,7 @@ func New(card string) (*SecurityKey, error) {
signingKeys: sks,
decryptingKeys: dks,
cryptoKeys: cryptoKeys,
pinentry: pe,
}, nil
}

Expand Down Expand Up @@ -98,7 +102,7 @@ func (k *SecurityKey) CryptoKeys() []CryptoKey {
// PrivateKey returns the private key of the given public signing key.
func (k *SecurityKey) PrivateKey(c *CryptoKey) (crypto.PrivateKey, error) {
return k.yubikey.PrivateKey(c.SlotSpec.Slot, c.Public,
piv.KeyAuth{PINPrompt: pinentry.GetPin(k)})
piv.KeyAuth{PINPrompt: k.pinentry.GetPin(k)})
}

// Close closes the underlying yubikey.
Expand Down
8 changes: 4 additions & 4 deletions internal/ssh/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ var ErrUnknownKey = errors.New("requested signature of unknown key")
var passphrases = map[string][]byte{}

// NewAgent returns a new Agent.
func NewAgent(p *piv.KeyService, log *zap.Logger, loadKeyfile bool,
n *notify.Notify, cancel context.CancelFunc) *Agent {
return &Agent{piv: p, log: log, notify: n, loadKeyfile: loadKeyfile,
cancel: cancel}
func NewAgent(p *piv.KeyService, pe *pinentry.PINEntry, log *zap.Logger,
loadKeyfile bool, n *notify.Notify, cancel context.CancelFunc) *Agent {
return &Agent{piv: p, pinentry: pe, log: log, notify: n,
loadKeyfile: loadKeyfile, cancel: cancel}
}

// List returns the identities known to the agent.
Expand Down

0 comments on commit 6d0edd7

Please sign in to comment.