diff --git a/piv/key.go b/piv/key.go index 789ba4c..073d2c3 100644 --- a/piv/key.go +++ b/piv/key.go @@ -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) { @@ -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, @@ -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) } @@ -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 { @@ -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) { @@ -530,21 +506,10 @@ 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 { @@ -552,11 +517,11 @@ func (k KeyAuth) begin(yk *YubiKey) (tx *scTx, err error) { 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 diff --git a/piv/piv.go b/piv/piv.go index 281297b..99c3a6a 100644 --- a/piv/piv.go +++ b/piv/piv.go @@ -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 @@ -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) @@ -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) { @@ -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 { @@ -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) { @@ -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 { @@ -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 ( @@ -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 @@ -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 { @@ -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 { @@ -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 { @@ -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 @@ -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 diff --git a/piv/piv_test.go b/piv/piv_test.go index cb6145f..20bac0c 100644 --- a/piv/piv_test.go +++ b/piv/piv_test.go @@ -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") } @@ -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) } }