Skip to content

Commit

Permalink
Prompt user to set pin/puk from default; cache pin after first prompt.
Browse files Browse the repository at this point in the history
  • Loading branch information
Joerger committed Sep 21, 2023
1 parent 2b2b536 commit 157a6f7
Showing 1 changed file with 112 additions and 4 deletions.
116 changes: 112 additions & 4 deletions api/utils/keys/yubikey.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ func (y *YubiKeyPrivateKey) Sign(rand io.Reader, digest []byte, opts crypto.Sign
defer touchPromptDelayTimer.Reset(signTouchPromptDelay)
}
}
return y.promptPIN(signCtx)
return y.promptOrSetPIN(signCtx, yk)
}

auth := piv.KeyAuth{
Expand Down Expand Up @@ -322,6 +322,9 @@ type yubiKey struct {
card string
// serialNumber is the yubiKey's 8 digit serial number.
serialNumber uint32
// pin is the user's PIV PIN. In order to avoid re-prompting for long standing commands,
// such as 'tsh proxy kube', we cache it here to use in future PIN prompts automatically.
pin string
}

func newYubiKey(card string) (*yubiKey, error) {
Expand Down Expand Up @@ -638,7 +641,112 @@ const (
signTouchPromptDelay = time.Millisecond * 200
)

// promptPIN prompts the user for PIN.
func (y *yubiKey) promptPIN(ctx context.Context) (string, error) {
return prompt.Password(ctx, os.Stderr, prompt.Stdin(), "Enter your YubiKey PIV PIN")
// promptOrSetPIN prompts the user for PIN. If the user provides the default PIN,
// they will be prompted to set a non-default PIN and PUK before continuing.
func (y *yubiKey) promptOrSetPIN(ctx context.Context, yk *piv.YubiKey) (string, error) {
if y.pin != "" {
return y.pin, nil
}

pin, err := prompt.Password(ctx, os.Stderr, prompt.Stdin(), "Enter your YubiKey PIV PIN")
if err != nil {
return "", trace.Wrap(err)
}

if pin == piv.DefaultPIN {
if err = y.setPINAndPUKFromDefault(ctx, yk); err != nil {
return "", trace.Wrap(err)
}
return y.pin, nil
}

y.pin = pin
return pin, nil
}

func (y *yubiKey) setPINAndPUKFromDefault(ctx context.Context, yk *piv.YubiKey) error {
fmt.Fprintf(os.Stderr, "The default PIN %q is not supported.\n", piv.DefaultPIN)

isValid := func(pin string) bool {
if len(pin) < 6 || len(pin) > 8 {
return false
}
for _, c := range pin {
if c < '0' || c > '9' {
return false
}
}
return true
}

var puk string
for {
fmt.Fprintf(os.Stderr, "Please set a new 6-8 digit PIV PUK (used to reset PIN).\n")
newPUK, err := prompt.Password(ctx, os.Stderr, prompt.Stdin(), "Enter your new YubiKey PIV PUK")
if err != nil {
return trace.Wrap(err)
}
newPUKConfirm, err := prompt.Password(ctx, os.Stderr, prompt.Stdin(), "Enter your new YubiKey PIV PUK again to confirm")
if err != nil {
return trace.Wrap(err)
}

if newPUK != newPUKConfirm {
fmt.Fprintf(os.Stderr, "PUKs do not match.\n")
continue
}

if newPUK == piv.DefaultPUK {
fmt.Fprintf(os.Stderr, "The default PUK %q is not supported.\n", piv.DefaultPUK)
continue
}

if !isValid(newPUK) {
fmt.Fprintf(os.Stderr, "PUK must be 6-8 digits.\n")
continue
}

if err := yk.SetPUK(piv.DefaultPUK, newPUK); err != nil {
return trace.Wrap(err)
}

puk = newPUK
break
}

for {
fmt.Fprintf(os.Stderr, "Please set a new 6-8 digit PIN.\n")
newPIN, err := prompt.Password(ctx, os.Stderr, prompt.Stdin(), "Enter your new YubiKey PIV PIN")
if err != nil {
return trace.Wrap(err)
}
newPINConfirm, err := prompt.Password(ctx, os.Stderr, prompt.Stdin(), "Enter your new YubiKey PIV PIN again to confirm")
if err != nil {
return trace.Wrap(err)
}

if newPIN != newPINConfirm {
fmt.Fprintf(os.Stderr, "PINs do not match.\n")
continue
}

if newPIN == piv.DefaultPIN {
fmt.Fprintf(os.Stderr, "The default PIN %q is not supported.\n", piv.DefaultPIN)
continue
}

if !isValid(newPIN) {
fmt.Fprintf(os.Stderr, "PIN must be 6-8 digits.\n")
continue
}

if err := yk.Unblock(puk, newPIN); err != nil {
return trace.Wrap(err)
}

y.pin = newPIN
break
}

return nil
}

0 comments on commit 157a6f7

Please sign in to comment.