Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

piv: have YubiKey hold on to a transaction #39

Merged
merged 1 commit into from
Apr 26, 2020
Merged
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
53 changes: 9 additions & 44 deletions piv/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -306,12 +306,7 @@ func (yk *YubiKey) AttestationCertificate() (*x509.Certificate, error) {
// certificate. This can be used to prove a key was generate on a specific
// YubiKey.
func (yk *YubiKey) Attest(slot Slot) (*x509.Certificate, error) {
tx, err := yk.begin()
if err != nil {
return nil, err
}
defer tx.Close()
return ykAttest(tx, slot)
return ykAttest(yk.tx, slot)
}

func ykAttest(tx *scTx, slot Slot) (*x509.Certificate, error) {
Expand Down Expand Up @@ -339,15 +334,6 @@ func ykAttest(tx *scTx, slot Slot) (*x509.Certificate, error) {

// Certificate returns the certifiate object stored in a given slot.
func (yk *YubiKey) Certificate(slot Slot) (*x509.Certificate, error) {
tx, err := yk.begin()
if err != nil {
return nil, err
}
defer tx.Close()
return ykGetCertificate(tx, slot)
}

func ykGetCertificate(tx *scTx, slot Slot) (*x509.Certificate, error) {
cmd := apdu{
instruction: insGetData,
param1: 0x3f,
Expand All @@ -360,7 +346,7 @@ func ykGetCertificate(tx *scTx, slot Slot) (*x509.Certificate, error) {
byte(slot.Object),
},
}
resp, err := tx.Transmit(cmd)
resp, err := yk.tx.Transmit(cmd)
if err != nil {
return nil, fmt.Errorf("command failed: %w", err)
}
Expand Down Expand Up @@ -401,15 +387,10 @@ func marshalASN1(tag byte, data []byte) []byte {
// certificate isn't required to use the associated key for signing or
// decryption.
func (yk *YubiKey) SetCertificate(key [24]byte, slot Slot, cert *x509.Certificate) error {
tx, err := yk.begin()
if err != nil {
return err
}
defer tx.Close()
if err := ykAuthenticate(tx, key, yk.rand); err != nil {
if err := ykAuthenticate(yk.tx, key, yk.rand); err != nil {
return fmt.Errorf("authenticating with management key: %w", err)
}
return ykStoreCertificate(tx, slot, cert)
return ykStoreCertificate(yk.tx, slot, cert)
}

func ykStoreCertificate(tx *scTx, slot Slot, cert *x509.Certificate) error {
Expand Down Expand Up @@ -455,15 +436,10 @@ type Key struct {
// GenerateKey generates an asymmetric key on the card, returning the key's
// public key.
func (yk *YubiKey) GenerateKey(key [24]byte, slot Slot, opts Key) (crypto.PublicKey, error) {
tx, err := yk.begin()
if err != nil {
return nil, err
}
defer tx.Close()
if err := ykAuthenticate(tx, key, yk.rand); err != nil {
if err := ykAuthenticate(yk.tx, key, yk.rand); err != nil {
return nil, fmt.Errorf("authenticating with management key: %w", err)
}
return ykGenerateKey(tx, slot, opts)
return ykGenerateKey(yk.tx, slot, opts)
}

func ykGenerateKey(tx *scTx, slot Slot, o Key) (crypto.PublicKey, error) {
Expand Down Expand Up @@ -530,33 +506,22 @@ type KeyAuth struct {
}

func (k KeyAuth) begin(yk *YubiKey) (tx *scTx, err error) {
tx, err = yk.begin()
if err != nil {
return nil, err
}
defer func() {
if err != nil {
tx.Close()
tx = nil
}
}()

// TODO(ericchiang): support cached pin and touch policies, possibly by
// attempting to sign, then prompting on specific apdu error codes.
if k.PIN != "" {
if err := ykLogin(tx, k.PIN); err != nil {
if err := ykLogin(yk.tx, k.PIN); err != nil {
return tx, fmt.Errorf("authenticating with pin: %w", err)
}
} else if k.PINPrompt != nil {
pin, err := k.PINPrompt()
if err != nil {
return tx, fmt.Errorf("pin prompt: %v", err)
}
if err := ykLogin(tx, pin); err != nil {
if err := ykLogin(yk.tx, pin); err != nil {
return tx, fmt.Errorf("authenticating with pin: %w", err)
}
}
return tx, nil
return yk.tx, nil
}

// PrivateKey is used to access signing and decryption options for the key
Expand Down
113 changes: 26 additions & 87 deletions piv/piv.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,10 +92,14 @@ const (
insGetSerial = 0xf8
)

// YubiKey is an open connection to a YubiKey smart card.
// YubiKey is an exclusive open connection to a YubiKey smart card. While open,
// no other process can query the given card.
//
// To release the connection, call the Close method.
type YubiKey struct {
ctx *scContext
h *scHandle
tx *scTx

rand io.Reader

Expand Down Expand Up @@ -152,14 +156,17 @@ func (c *client) Open(card string) (*YubiKey, error) {
ctx.Close()
return nil, fmt.Errorf("connecting to smart card: %w", err)
}

yk := &YubiKey{ctx: ctx, h: h}
tx, err := yk.begin()
tx, err := h.Begin()
if err != nil {
yk.Close()
return nil, fmt.Errorf("initializing yubikey: %w", err)
return nil, fmt.Errorf("beginning smart card transaction: %w", err)
}
v, err := ykVersion(tx)
if err := ykSelectApplication(tx, aidPIV[:]); err != nil {
tx.Close()
return nil, fmt.Errorf("selecting piv applet: %w", err)
}

yk := &YubiKey{ctx: ctx, h: h, tx: tx}
v, err := ykVersion(yk.tx)
if err != nil {
yk.Close()
return nil, fmt.Errorf("getting yubikey version: %w", err)
Expand All @@ -173,26 +180,9 @@ func (c *client) Open(card string) (*YubiKey, error) {
return yk, nil
}

func (yk *YubiKey) begin() (*scTx, error) {
tx, err := yk.h.Begin()
if err != nil {
return nil, fmt.Errorf("beginning smart card transaction: %w", err)
}
if err := ykSelectApplication(tx, aidPIV[:]); err != nil {
tx.Close()
return nil, fmt.Errorf("selecting piv applet: %w", err)
}
return tx, nil
}

// Serial returns the YubiKey's serial number.
func (yk *YubiKey) Serial() (uint32, error) {
tx, err := yk.begin()
if err != nil {
return 0, err
}
defer tx.Close()
return ykSerial(tx, yk.version)
return ykSerial(yk.tx, yk.version)
}

func encodePIN(pin string) ([]byte, error) {
Expand All @@ -219,12 +209,7 @@ func encodePIN(pin string) ([]byte, error) {
//
// Use DefaultPIN if the PIN hasn't been set.
func (yk *YubiKey) authPIN(pin string) error {
tx, err := yk.begin()
if err != nil {
return err
}
defer tx.Close()
return ykLogin(tx, pin)
return ykLogin(yk.tx, pin)
}

func ykLogin(tx *scTx, pin string) error {
Expand All @@ -242,12 +227,7 @@ func ykLogin(tx *scTx, pin string) error {

// Retries returns the number of attempts remaining to enter the correct PIN.
func (yk *YubiKey) Retries() (int, error) {
tx, err := yk.begin()
if err != nil {
return 0, err
}
defer tx.Close()
return ykPINRetries(tx)
return ykPINRetries(yk.tx)
}

func ykPINRetries(tx *scTx) (int, error) {
Expand All @@ -267,12 +247,7 @@ func ykPINRetries(tx *scTx) (int, error) {
// and resetting the PIN, PUK, and Management Key to their default values. This
// does NOT affect data on other applets, such as GPG or U2F.
func (yk *YubiKey) Reset() error {
tx, err := yk.begin()
if err != nil {
return err
}
defer tx.Close()
return ykReset(tx, yk.rand)
return ykReset(yk.tx, yk.rand)
}

func ykReset(tx *scTx, r io.Reader) error {
Expand Down Expand Up @@ -341,12 +316,7 @@ type version struct {
//
// Use DefaultManagementKey if the management key hasn't been set.
func (yk *YubiKey) authManagementKey(key [24]byte) error {
tx, err := yk.begin()
if err != nil {
return err
}
defer tx.Close()
return ykAuthenticate(tx, key, yk.rand)
return ykAuthenticate(yk.tx, key, yk.rand)
}

var (
Expand Down Expand Up @@ -464,16 +434,10 @@ func ykAuthenticate(tx *scTx, key [24]byte, rand io.Reader) error {
//
//
func (yk *YubiKey) SetManagementKey(oldKey, newKey [24]byte) error {
tx, err := yk.begin()
if err != nil {
return err
}
defer tx.Close()

if err := ykAuthenticate(tx, oldKey, yk.rand); err != nil {
if err := ykAuthenticate(yk.tx, oldKey, yk.rand); err != nil {
return fmt.Errorf("authenticating with old key: %w", err)
}
if err := ykSetManagementKey(tx, newKey, false); err != nil {
if err := ykSetManagementKey(yk.tx, newKey, false); err != nil {
return err
}
return nil
Expand Down Expand Up @@ -516,12 +480,7 @@ func ykSetManagementKey(tx *scTx, key [24]byte, touch bool) error {
// }
//
func (yk *YubiKey) SetPIN(oldPIN, newPIN string) error {
tx, err := yk.begin()
if err != nil {
return err
}
defer tx.Close()
return ykChangePIN(tx, oldPIN, newPIN)
return ykChangePIN(yk.tx, oldPIN, newPIN)
}

func ykChangePIN(tx *scTx, oldPIN, newPIN string) error {
Expand All @@ -544,12 +503,7 @@ func ykChangePIN(tx *scTx, oldPIN, newPIN string) error {

// Unblock unblocks the PIN, setting it to a new value.
func (yk *YubiKey) Unblock(puk, newPIN string) error {
tx, err := yk.begin()
if err != nil {
return err
}
defer tx.Close()
return ykUnblockPIN(tx, puk, newPIN)
return ykUnblockPIN(yk.tx, puk, newPIN)
}

func ykUnblockPIN(tx *scTx, puk, newPIN string) error {
Expand Down Expand Up @@ -587,12 +541,7 @@ func ykUnblockPIN(tx *scTx, puk, newPIN string) error {
// }
//
func (yk *YubiKey) SetPUK(oldPUK, newPUK string) error {
tx, err := yk.begin()
if err != nil {
return err
}
defer tx.Close()
return ykChangePUK(tx, oldPUK, newPUK)
return ykChangePUK(yk.tx, oldPUK, newPUK)
}

func ykChangePUK(tx *scTx, oldPUK, newPUK string) error {
Expand Down Expand Up @@ -704,12 +653,7 @@ func unmarshalDERField(b []byte, tag uint64) (obj []byte, err error) {
// Metadata returns protected data stored on the card. This can be used to
// retrieve PIN protected management keys.
func (yk *YubiKey) Metadata(pin string) (*Metadata, error) {
tx, err := yk.begin()
if err != nil {
return nil, err
}
defer tx.Close()
m, err := ykGetProtectedMetadata(tx, pin)
m, err := ykGetProtectedMetadata(yk.tx, pin)
if err != nil {
if errors.Is(err, ErrNotFound) {
return &Metadata{}, nil
Expand All @@ -723,12 +667,7 @@ func (yk *YubiKey) Metadata(pin string) (*Metadata, error) {
// store the management key on the smart card instead of managing the PIN and
// management key seperately.
func (yk *YubiKey) SetMetadata(key [24]byte, m *Metadata) error {
tx, err := yk.begin()
if err != nil {
return err
}
defer tx.Close()
return ykSetProtectedMetadata(tx, key, m)
return ykSetProtectedMetadata(yk.tx, key, m)
}

// Metadata holds protected metadata. This is primarily used by YubiKey manager
Expand Down
12 changes: 3 additions & 9 deletions piv/piv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,15 +157,9 @@ func TestYubiKeyUnblockPIN(t *testing.T) {
yk, close := newTestYubiKey(t)
defer close()

tx, err := yk.begin()
if err != nil {
t.Fatalf("begin transaction: %v", err)
}
defer tx.Close()

badPIN := "0"
for {
err := ykLogin(tx, badPIN)
err := ykLogin(yk.tx, badPIN)
if err == nil {
t.Fatalf("login with bad pin succeeded")
}
Expand All @@ -178,10 +172,10 @@ func TestYubiKeyUnblockPIN(t *testing.T) {
}
}

if err := ykUnblockPIN(tx, DefaultPUK, DefaultPIN); err != nil {
if err := yk.Unblock(DefaultPUK, DefaultPIN); err != nil {
t.Fatalf("unblocking pin: %v", err)
}
if err := ykLogin(tx, DefaultPIN); err != nil {
if err := ykLogin(yk.tx, DefaultPIN); err != nil {
t.Errorf("failed to login with pin after unblock: %v", err)
}
}
Expand Down