Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 23 additions & 2 deletions kms/yubikey/yubikey.go
Original file line number Diff line number Diff line change
Expand Up @@ -717,7 +717,8 @@ type syncSigner struct {
func (s *syncSigner) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
m.Lock()
defer m.Unlock()
return s.Signer.Sign(rand, digest, opts)
sig, err := s.Signer.Sign(rand, digest, opts)
return sig, wrapTouchHint(err)
}

// syncDecrypter wraps a crypto.Decrypter with a mutex to avoid the error "smart
Expand All @@ -730,7 +731,27 @@ type syncDecrypter struct {
func (s *syncDecrypter) Decrypt(rand io.Reader, msg []byte, opts crypto.DecrypterOpts) ([]byte, error) {
m.Lock()
defer m.Unlock()
return s.Decrypter.Decrypt(rand, msg, opts)
plaintext, err := s.Decrypter.Decrypt(rand, msg, opts)
return plaintext, wrapTouchHint(err)
}

// wrapTouchHint annotates a 0x6982 "security status not satisfied" card error
// with a hint that the slot's touch policy may be waiting for a physical touch.
// The status word is not touch-specific -- it can also mean an unsatisfied PIN
// or, without the mutex above, concurrent access -- so the hint is conditional.
//
// The status is detected through an anonymous interface because piv-go's error
// type is unexported. If a future piv-go release exposes a sentinel for this
// status, this can become a plain errors.Is check.
func wrapTouchHint(err error) error {
if err == nil {
return nil
}
var sc interface{ Status() uint16 }
if errors.As(err, &sc) && sc.Status() == 0x6982 {
return fmt.Errorf("%w; if the key's slot has a touch policy, touch the blinking YubiKey and retry", err)
}
return err
}

var _ apiv1.CertificateManager = (*YubiKey)(nil)
27 changes: 27 additions & 0 deletions kms/yubikey/yubikey_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1459,3 +1459,30 @@ Vpmq2Sh/xT5HiFuhf4wJb0bK
}

}

func TestWrapTouchHint(t *testing.T) {
// A 0x6982 "security status not satisfied" error gets the touch hint, and the
// original error stays unwrappable.
base := cardStatusErr(0x6982)
got := wrapTouchHint(base)
assert.Contains(t, got.Error(), "touch")
assert.ErrorIs(t, got, base)

// A different status word is passed through unchanged.
other := cardStatusErr(0x6a80)
assert.Equal(t, other.Error(), wrapTouchHint(other).Error())
assert.NotContains(t, wrapTouchHint(other).Error(), "touch")

// A nil error stays nil; a plain error without Status() passes through.
assert.NoError(t, wrapTouchHint(nil))
plain := errors.New("boom")
assert.ErrorIs(t, wrapTouchHint(plain), plain)
assert.NotContains(t, wrapTouchHint(plain).Error(), "touch")
}

// cardStatusErr is a minimal card-style error that exposes Status() like piv-go's
// unexported apduErr, so wrapTouchHint can be tested without hardware.
type cardStatusErr uint16

func (e cardStatusErr) Error() string { return fmt.Sprintf("smart card error %04x", uint16(e)) }
func (e cardStatusErr) Status() uint16 { return uint16(e) }