From f0cc31fbf3ceceada7c46c7468cbd568f4901844 Mon Sep 17 00:00:00 2001 From: rajnikant Date: Wed, 13 Oct 2021 12:19:27 +0530 Subject: [PATCH 01/13] adding support for shared smartcard access --- piv/pcsc_unix.go | 13 +++++++++++-- piv/pcsc_windows.go | 14 ++++++++++++-- piv/piv.go | 26 +++++++++++++++++++++++++- 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/piv/pcsc_unix.go b/piv/pcsc_unix.go index 66d590e..581efcd 100644 --- a/piv/pcsc_unix.go +++ b/piv/pcsc_unix.go @@ -38,7 +38,8 @@ import ( const rcSuccess = C.SCARD_S_SUCCESS type scContext struct { - ctx C.SCARDCONTEXT + ctx C.SCARDCONTEXT + shared bool } func newSCContext() (*scContext, error) { @@ -54,6 +55,10 @@ func (c *scContext) Close() error { return scCheck(C.SCardReleaseContext(c.ctx)) } +func (c *scContext) SetShared() { + c.shared = true +} + func (c *scContext) ListReaders() ([]string, error) { var n C.DWORD rc := C.SCardListReaders(c.ctx, nil, nil, &n) @@ -93,8 +98,12 @@ func (c *scContext) Connect(reader string) (*scHandle, error) { handle C.SCARDHANDLE activeProtocol C.DWORD ) + opt := C.SCARD_SHARE_EXCLUSIVE + if c.shared { + opt = C.SCARD_SHARE_SHARED + } rc := C.SCardConnect(c.ctx, C.CString(reader), - C.SCARD_SHARE_EXCLUSIVE, C.SCARD_PROTOCOL_T1, + C.uint(opt), C.SCARD_PROTOCOL_T1, &handle, &activeProtocol) if err := scCheck(rc); err != nil { return nil, err diff --git a/piv/pcsc_windows.go b/piv/pcsc_windows.go index 930ef38..0f85fb5 100644 --- a/piv/pcsc_windows.go +++ b/piv/pcsc_windows.go @@ -35,6 +35,7 @@ var ( const ( scardScopeSystem = 2 scardShareExclusive = 1 + scardShareShared = 2 scardLeaveCard = 0 scardProtocolT1 = 2 scardPCIT1 = 0 @@ -54,7 +55,8 @@ func isRCNoReaders(rc uintptr) bool { } type scContext struct { - ctx syscall.Handle + ctx syscall.Handle + shared bool } func newSCContext() (*scContext, error) { @@ -72,6 +74,10 @@ func newSCContext() (*scContext, error) { return &scContext{ctx: ctx}, nil } +func (c *scContext) SetShared() { + c.shared = true +} + func (c *scContext) Close() error { r0, _, _ := procSCardReleaseContext.Call(uintptr(c.ctx)) return scCheck(r0) @@ -127,10 +133,14 @@ func (c *scContext) Connect(reader string) (*scHandle, error) { handle syscall.Handle activeProtocol uint16 ) + opt := scardShareExclusive + if c.shared { + opt = scardShareShared + } r0, _, _ := procSCardConnectW.Call( uintptr(c.ctx), uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(reader))), - scardShareExclusive, + C.DWORD(opt), scardProtocolT1, uintptr(unsafe.Pointer(&handle)), uintptr(activeProtocol), diff --git a/piv/piv.go b/piv/piv.go index 001d5c3..551b60a 100644 --- a/piv/piv.go +++ b/piv/piv.go @@ -124,9 +124,26 @@ func (yk *YubiKey) Close() error { return err1 } +// clientConfigOpts holds options for creating a new ClientConfigOpt. +type clientConfigOpts struct { + // configure shared access to PIV hardware + shared bool +} + +// ClientConfigOpt configures a specific option +type ClientConfigOpt func(*clientConfigOpts) + +// WithSharedAccess configures PIV card to used in shared by multiple processes +func WithSharedAccess() ClientConfigOpt { + return func(o *clientConfigOpts) { + o.shared = true + } +} + // Open connects to a YubiKey smart card. -func Open(card string) (*YubiKey, error) { +func Open(card string, opts ...ClientConfigOpt) (*YubiKey, error) { var c client + c.setConfigOpts(opts...) return c.Open(card) } @@ -137,6 +154,13 @@ type client struct { // // If nil, defaults to crypto.Rand. Rand io.Reader + opts clientConfigOpts +} + +func (c *client) setConfigOpts(opts ...ClientConfigOpt) { + for _, v := range opts { + v(&c.opts) + } } func (c *client) Cards() ([]string, error) { From b7940e7c05a94c05cc65575cafd8a029d480796f Mon Sep 17 00:00:00 2001 From: rajnikant Date: Wed, 13 Oct 2021 12:30:15 +0530 Subject: [PATCH 02/13] setting shared option in open --- piv/piv.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/piv/piv.go b/piv/piv.go index 551b60a..6dcb469 100644 --- a/piv/piv.go +++ b/piv/piv.go @@ -178,6 +178,10 @@ func (c *client) Open(card string) (*YubiKey, error) { return nil, fmt.Errorf("connecting to smart card daemon: %w", err) } + if c.opts.shared { + ctx.SetShared() + } + h, err := ctx.Connect(card) if err != nil { ctx.Close() From cca80cfbed7c561f27c115179d07eb5ea128b80d Mon Sep 17 00:00:00 2001 From: rajnikant Date: Wed, 13 Oct 2021 12:32:20 +0530 Subject: [PATCH 03/13] setting shared option in open --- piv/piv.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/piv/piv.go b/piv/piv.go index 6dcb469..7c7dbf1 100644 --- a/piv/piv.go +++ b/piv/piv.go @@ -172,7 +172,7 @@ func (c *client) Cards() ([]string, error) { return ctx.ListReaders() } -func (c *client) Open(card string) (*YubiKey, error) { +func (c *client) Open(card string, opts ...ClientConfigOpt ) (*YubiKey, error) { ctx, err := newSCContext() if err != nil { return nil, fmt.Errorf("connecting to smart card daemon: %w", err) From b82c6544d4c70ea48415b616e52d12ee5b9b4c9a Mon Sep 17 00:00:00 2001 From: rajnikant Date: Wed, 13 Oct 2021 12:33:58 +0530 Subject: [PATCH 04/13] go fmt --- piv/piv.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/piv/piv.go b/piv/piv.go index 7c7dbf1..86e421d 100644 --- a/piv/piv.go +++ b/piv/piv.go @@ -172,7 +172,7 @@ func (c *client) Cards() ([]string, error) { return ctx.ListReaders() } -func (c *client) Open(card string, opts ...ClientConfigOpt ) (*YubiKey, error) { +func (c *client) Open(card string, opts ...ClientConfigOpt) (*YubiKey, error) { ctx, err := newSCContext() if err != nil { return nil, fmt.Errorf("connecting to smart card daemon: %w", err) From 77a78b5a00ad39864110972d443deccd6dd8ab9f Mon Sep 17 00:00:00 2001 From: rajnikant Date: Wed, 13 Oct 2021 12:40:45 +0530 Subject: [PATCH 05/13] adding tests --- piv/piv_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/piv/piv_test.go b/piv/piv_test.go index 534b259..4df51d2 100644 --- a/piv/piv_test.go +++ b/piv/piv_test.go @@ -86,7 +86,7 @@ func newTestYubiKey(t *testing.T) (*YubiKey, func()) { if !canModifyYubiKey { t.Skip("not running test that accesses yubikey, provide --wipe-yubikey flag") } - yk, err := Open(card) + yk, err := Open(card, WithSharedAccess()) if err != nil { t.Fatalf("getting new yubikey: %v", err) } From 4473c3780abfbb458d49d850490969f8d1cbcecb Mon Sep 17 00:00:00 2001 From: rajnikant Date: Wed, 13 Oct 2021 15:49:00 +0530 Subject: [PATCH 06/13] updating comment --- piv/piv.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/piv/piv.go b/piv/piv.go index 86e421d..7e64008 100644 --- a/piv/piv.go +++ b/piv/piv.go @@ -133,7 +133,7 @@ type clientConfigOpts struct { // ClientConfigOpt configures a specific option type ClientConfigOpt func(*clientConfigOpts) -// WithSharedAccess configures PIV card to used in shared by multiple processes +// WithSharedAccess configures PIV card to be shared by multiple processes func WithSharedAccess() ClientConfigOpt { return func(o *clientConfigOpts) { o.shared = true From defbed3634eb7e699d7ab1f316c0f9649bf644ac Mon Sep 17 00:00:00 2001 From: rajnikant Date: Wed, 3 Nov 2021 11:29:47 +0530 Subject: [PATCH 07/13] fixing build and addressing review commants --- piv/pcsc_darwin.go | 26 +++++++++++++++++++++++++ piv/pcsc_linux.go | 26 +++++++++++++++++++++++++ piv/pcsc_test.go | 2 +- piv/pcsc_unix.go | 12 ++++-------- piv/pcsc_windows.go | 1 + piv/piv.go | 46 +++++++++++++++++---------------------------- piv/piv_test.go | 2 +- 7 files changed, 76 insertions(+), 39 deletions(-) diff --git a/piv/pcsc_darwin.go b/piv/pcsc_darwin.go index d57a955..fd37363 100644 --- a/piv/pcsc_darwin.go +++ b/piv/pcsc_darwin.go @@ -14,6 +14,14 @@ package piv +// #cgo darwin LDFLAGS: -framework PCSC +// #cgo linux pkg-config: libpcsclite +// #cgo freebsd CFLAGS: -I/usr/local/include/ +// #cgo freebsd CFLAGS: -I/usr/local/include/PCSC +// #cgo freebsd LDFLAGS: -L/usr/local/lib/ +// #cgo freebsd LDFLAGS: -lpcsclite +// #include +// #include import "C" func scCheck(rc C.int) error { @@ -36,3 +44,21 @@ func isRCNoReaders(rc C.int) bool { // are available. return false } + +func (c *scContext) Connect(reader string) (*scHandle, error) { + var ( + handle C.SCARDHANDLE + activeProtocol C.DWORD + ) + opt := C.SCARD_SHARE_EXCLUSIVE + if c.shared { + opt = C.SCARD_SHARE_SHARED + } + rc := C.SCardConnect(c.ctx, C.CString(reader), + C.uint(opt), C.SCARD_PROTOCOL_T1, + &handle, &activeProtocol) + if err := scCheck(rc); err != nil { + return nil, err + } + return &scHandle{handle}, nil +} diff --git a/piv/pcsc_linux.go b/piv/pcsc_linux.go index 6a5c078..f7ab943 100644 --- a/piv/pcsc_linux.go +++ b/piv/pcsc_linux.go @@ -14,6 +14,14 @@ package piv +// #cgo darwin LDFLAGS: -framework PCSC +// #cgo linux pkg-config: libpcsclite +// #cgo freebsd CFLAGS: -I/usr/local/include/ +// #cgo freebsd CFLAGS: -I/usr/local/include/PCSC +// #cgo freebsd LDFLAGS: -L/usr/local/lib/ +// #cgo freebsd LDFLAGS: -lpcsclite +// #include +// #include import "C" // Return codes for PCSC are different on different platforms (int vs. long). @@ -28,3 +36,21 @@ func scCheck(rc C.long) error { func isRCNoReaders(rc C.long) bool { return C.ulong(rc) == 0x8010002E } + +func (c *scContext) Connect(reader string) (*scHandle, error) { + var ( + handle C.SCARDHANDLE + activeProtocol C.DWORD + ) + opt := C.SCARD_SHARE_EXCLUSIVE + if c.shared { + opt = C.SCARD_SHARE_SHARED + } + rc := C.SCardConnect(c.ctx, C.CString(reader), + C.ulong(opt), C.SCARD_PROTOCOL_T1, + &handle, &activeProtocol) + if err := scCheck(rc); err != nil { + return nil, err + } + return &scHandle{handle}, nil +} \ No newline at end of file diff --git a/piv/pcsc_test.go b/piv/pcsc_test.go index 5902564..56814ef 100644 --- a/piv/pcsc_test.go +++ b/piv/pcsc_test.go @@ -21,7 +21,7 @@ import ( ) func runContextTest(t *testing.T, f func(t *testing.T, c *scContext)) { - ctx, err := newSCContext() + ctx, err := newSCContext(true) if err != nil { t.Fatalf("creating context: %v", err) } diff --git a/piv/pcsc_unix.go b/piv/pcsc_unix.go index 581efcd..285440e 100644 --- a/piv/pcsc_unix.go +++ b/piv/pcsc_unix.go @@ -42,23 +42,19 @@ type scContext struct { shared bool } -func newSCContext() (*scContext, error) { +func newSCContext(shared bool) (*scContext, error) { var ctx C.SCARDCONTEXT rc := C.SCardEstablishContext(C.SCARD_SCOPE_SYSTEM, nil, nil, &ctx) if err := scCheck(rc); err != nil { return nil, err } - return &scContext{ctx: ctx}, nil + return &scContext{ctx: ctx, shared: shared}, nil } func (c *scContext) Close() error { return scCheck(C.SCardReleaseContext(c.ctx)) } -func (c *scContext) SetShared() { - c.shared = true -} - func (c *scContext) ListReaders() ([]string, error) { var n C.DWORD rc := C.SCardListReaders(c.ctx, nil, nil, &n) @@ -92,7 +88,7 @@ func (c *scContext) ListReaders() ([]string, error) { type scHandle struct { h C.SCARDHANDLE } - +/* func (c *scContext) Connect(reader string) (*scHandle, error) { var ( handle C.SCARDHANDLE @@ -109,7 +105,7 @@ func (c *scContext) Connect(reader string) (*scHandle, error) { return nil, err } return &scHandle{handle}, nil -} +}*/ func (h *scHandle) Close() error { return scCheck(C.SCardDisconnect(h.h, C.SCARD_LEAVE_CARD)) diff --git a/piv/pcsc_windows.go b/piv/pcsc_windows.go index 0f85fb5..6489607 100644 --- a/piv/pcsc_windows.go +++ b/piv/pcsc_windows.go @@ -18,6 +18,7 @@ import ( "fmt" "syscall" "unsafe" + "C" ) var ( diff --git a/piv/piv.go b/piv/piv.go index 7e64008..e87fa3c 100644 --- a/piv/piv.go +++ b/piv/piv.go @@ -124,27 +124,26 @@ func (yk *YubiKey) Close() error { return err1 } -// clientConfigOpts holds options for creating a new ClientConfigOpt. -type clientConfigOpts struct { - // configure shared access to PIV hardware - shared bool +// Client provides configuration options when connection to the smart card. +type Client struct { + // Shared enables a non-exclusive connection to the PIV application, allowing + // other applications that also request a non-exclusive connection to connect + // at the same time. + // + // Certain features, such as cached PINs, don't work when this feature is enabled. + // It's also common for other applications to require exclusive connections. + Shared bool } -// ClientConfigOpt configures a specific option -type ClientConfigOpt func(*clientConfigOpts) - -// WithSharedAccess configures PIV card to be shared by multiple processes -func WithSharedAccess() ClientConfigOpt { - return func(o *clientConfigOpts) { - o.shared = true - } +func (cl *Client) Open(card string) (*YubiKey, error) { + var c client + return c.Open(card, cl.Shared) } // Open connects to a YubiKey smart card. -func Open(card string, opts ...ClientConfigOpt) (*YubiKey, error) { +func Open(card string) (*YubiKey, error) { var c client - c.setConfigOpts(opts...) - return c.Open(card) + return c.Open(card, false) } // client is a smart card client and may be exported in the future to allow @@ -154,17 +153,10 @@ type client struct { // // If nil, defaults to crypto.Rand. Rand io.Reader - opts clientConfigOpts -} - -func (c *client) setConfigOpts(opts ...ClientConfigOpt) { - for _, v := range opts { - v(&c.opts) - } } func (c *client) Cards() ([]string, error) { - ctx, err := newSCContext() + ctx, err := newSCContext(true) if err != nil { return nil, fmt.Errorf("connecting to pscs: %w", err) } @@ -172,16 +164,12 @@ func (c *client) Cards() ([]string, error) { return ctx.ListReaders() } -func (c *client) Open(card string, opts ...ClientConfigOpt) (*YubiKey, error) { - ctx, err := newSCContext() +func (c *client) Open(card string, shared bool) (*YubiKey, error) { + ctx, err := newSCContext(shared) if err != nil { return nil, fmt.Errorf("connecting to smart card daemon: %w", err) } - if c.opts.shared { - ctx.SetShared() - } - h, err := ctx.Connect(card) if err != nil { ctx.Close() diff --git a/piv/piv_test.go b/piv/piv_test.go index 4df51d2..534b259 100644 --- a/piv/piv_test.go +++ b/piv/piv_test.go @@ -86,7 +86,7 @@ func newTestYubiKey(t *testing.T) (*YubiKey, func()) { if !canModifyYubiKey { t.Skip("not running test that accesses yubikey, provide --wipe-yubikey flag") } - yk, err := Open(card, WithSharedAccess()) + yk, err := Open(card) if err != nil { t.Fatalf("getting new yubikey: %v", err) } From 10f66c4b8a1dca5b52d572c0fceb4f777e2c1d0e Mon Sep 17 00:00:00 2001 From: rajnikant Date: Wed, 3 Nov 2021 11:35:04 +0530 Subject: [PATCH 08/13] fixing build and addressing review commants --- piv/piv.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/piv/piv.go b/piv/piv.go index e87fa3c..4e3c8d8 100644 --- a/piv/piv.go +++ b/piv/piv.go @@ -135,6 +135,8 @@ type Client struct { Shared bool } + +// Open method can be put to use for opening a smart-card with shared access. func (cl *Client) Open(card string) (*YubiKey, error) { var c client return c.Open(card, cl.Shared) From 34651e83371c5e07bad5761231924148f0fe2b46 Mon Sep 17 00:00:00 2001 From: rajnikant Date: Wed, 3 Nov 2021 12:34:38 +0530 Subject: [PATCH 09/13] fixing build and addressing review comments --- piv/key.go | 74 +++++++++++++++++++++++-------- piv/piv.go | 115 +++++++++++++++++++++++++++++++++--------------- piv/piv_test.go | 35 ++++++++++++--- 3 files changed, 165 insertions(+), 59 deletions(-) diff --git a/piv/key.go b/piv/key.go index 1cec128..387098b 100644 --- a/piv/key.go +++ b/piv/key.go @@ -511,7 +511,12 @@ func (yk *YubiKey) AttestationCertificate() (*x509.Certificate, error) { // // If the slot doesn't have a key, the returned error wraps ErrNotFound. func (yk *YubiKey) Attest(slot Slot) (*x509.Certificate, error) { - cert, err := ykAttest(yk.tx, slot) + var cert *x509.Certificate + err := yk.withTx(func(tx *scTx) error { + var err error + cert, err = ykAttest(tx, slot) + return err + }) if err == nil { return cert, nil } @@ -562,10 +567,17 @@ func (yk *YubiKey) Certificate(slot Slot) (*x509.Certificate, error) { byte(slot.Object), }, } - resp, err := yk.tx.Transmit(cmd) + + var resp []byte + err := yk.withTx(func(tx *scTx) error { + var err error + resp, err = tx.Transmit(cmd) + return err + }) if err != nil { return nil, fmt.Errorf("command failed: %w", err) } + // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=85 obj, _, err := unmarshalASN1(resp, 1, 0x13) // tag 0x53 if err != nil { @@ -609,10 +621,13 @@ 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 { - if err := ykAuthenticate(yk.tx, key, yk.rand); err != nil { - return fmt.Errorf("authenticating with management key: %w", err) - } - return ykStoreCertificate(yk.tx, slot, cert) + return yk.withTx(func(tx *scTx) error { + err := ykAuthenticate(tx, key, yk.rand) + if err != nil { + return fmt.Errorf("authenticating with management key: %w", err) + } + return ykStoreCertificate(tx, slot, cert) + }) } func ykStoreCertificate(tx *scTx, slot Slot, cert *x509.Certificate) error { @@ -663,10 +678,16 @@ 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) { - if err := ykAuthenticate(yk.tx, key, yk.rand); err != nil { - return nil, fmt.Errorf("authenticating with management key: %w", err) - } - return ykGenerateKey(yk.tx, slot, opts) + var pk crypto.PublicKey + err := yk.withTx(func(tx *scTx) error { + err := ykAuthenticate(tx, key, yk.rand) + if err != nil { + return fmt.Errorf("authenticating with management key: %w", err) + } + pk, err = ykGenerateKey(tx, slot, opts) + return err + }) + return pk, err } func ykGenerateKey(tx *scTx, slot Slot, o Key) (crypto.PublicKey, error) { @@ -764,7 +785,15 @@ func (k KeyAuth) authTx(yk *YubiKey, pp PINPolicy) error { // PINPolicyAlways should always prompt a PIN even if the key says that // login isn't needed. // https://github.com/go-piv/piv-go/issues/49 - if pp != PINPolicyAlways && !ykLoginNeeded(yk.tx) { + + var flag bool + + yk.withTx(func(tx *scTx) error { + flag = !ykLoginNeeded(tx) + return nil + }) + + if pp != PINPolicyAlways && flag { return nil } @@ -779,14 +808,22 @@ func (k KeyAuth) authTx(yk *YubiKey, pp PINPolicy) error { if pin == "" { return fmt.Errorf("pin required but wasn't provided") } - return ykLogin(yk.tx, pin) + return yk.withTx(func(tx *scTx) error { + return ykLogin(tx, pin) + }) } func (k KeyAuth) do(yk *YubiKey, pp PINPolicy, f func(tx *scTx) ([]byte, error)) ([]byte, error) { if err := k.authTx(yk, pp); err != nil { return nil, err } - return f(yk.tx) + var res []byte + err := yk.withTx(func(tx *scTx) error { + var err error + res, err = f(tx) + return err + }) + return res, err } func pinPolicy(yk *YubiKey, slot Slot) (PINPolicy, error) { @@ -931,11 +968,12 @@ func (yk *YubiKey) SetPrivateKeyInsecure(key [24]byte, slot Slot, private crypto tags = append(tags, param...) } - if err := ykAuthenticate(yk.tx, key, yk.rand); err != nil { - return fmt.Errorf("authenticating with management key: %w", err) - } - - return ykImportKey(yk.tx, tags, slot, policy) + return yk.withTx(func(tx *scTx) error { + if err := ykAuthenticate(tx, key, yk.rand); err != nil { + return fmt.Errorf("authenticating with management key: %w", err) + } + return ykImportKey(tx, tags, slot, policy) + }) } func ykImportKey(tx *scTx, tags []byte, slot Slot, o Key) error { diff --git a/piv/piv.go b/piv/piv.go index 4e3c8d8..ee2fa7f 100644 --- a/piv/piv.go +++ b/piv/piv.go @@ -102,7 +102,7 @@ const ( type YubiKey struct { ctx *scContext h *scHandle - tx *scTx + //tx *scTx rand io.Reader @@ -135,7 +135,6 @@ type Client struct { Shared bool } - // Open method can be put to use for opening a smart-card with shared access. func (cl *Client) Open(card string) (*YubiKey, error) { var c client @@ -166,6 +165,15 @@ func (c *client) Cards() ([]string, error) { return ctx.ListReaders() } +func (yk *YubiKey) withTx(fn func(tx *scTx) error) error { + tx, err := yk.h.Begin() + if err != nil { + return fmt.Errorf("beginning smart card transaction: %w", err) + } + defer tx.Close() + return fn(tx) +} + func (c *client) Open(card string, shared bool) (*YubiKey, error) { ctx, err := newSCContext(shared) if err != nil { @@ -177,27 +185,30 @@ func (c *client) Open(card string, shared bool) (*YubiKey, error) { ctx.Close() return nil, fmt.Errorf("connecting to smart card: %w", err) } - tx, err := 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) - } - yk := &YubiKey{ctx: ctx, h: h, tx: tx} - v, err := ykVersion(yk.tx) + yk := &YubiKey{ctx: ctx, h: h} + + err = yk.withTx(func(tx *scTx) error { + if err := ykSelectApplication(tx, aidPIV[:]); err != nil { + return fmt.Errorf("selecting piv applet: %w", err) + } + v, err := ykVersion(tx) + if err != nil { + return fmt.Errorf("getting yubikey version: %w", err) + } + yk.version = v + if c.Rand != nil { + yk.rand = c.Rand + } else { + yk.rand = rand.Reader + } + return nil + }) if err != nil { yk.Close() - return nil, fmt.Errorf("getting yubikey version: %w", err) - } - yk.version = v - if c.Rand != nil { - yk.rand = c.Rand - } else { - yk.rand = rand.Reader + return nil, err } + return yk, nil } @@ -216,7 +227,13 @@ func (yk *YubiKey) Version() Version { // Serial returns the YubiKey's serial number. func (yk *YubiKey) Serial() (uint32, error) { - return ykSerial(yk.tx, yk.version) + var serial uint32 + err := yk.withTx(func(tx *scTx) error { + var err error + serial, err = ykSerial(tx, yk.version) + return err + }) + return serial, err } func encodePIN(pin string) ([]byte, error) { @@ -243,7 +260,9 @@ func encodePIN(pin string) ([]byte, error) { // // Use DefaultPIN if the PIN hasn't been set. func (yk *YubiKey) authPIN(pin string) error { - return ykLogin(yk.tx, pin) + return yk.withTx(func(tx *scTx) error { + return ykLogin(tx, pin) + }) } func ykLogin(tx *scTx, pin string) error { @@ -268,7 +287,13 @@ func ykLoginNeeded(tx *scTx) bool { // Retries returns the number of attempts remaining to enter the correct PIN. func (yk *YubiKey) Retries() (int, error) { - return ykPINRetries(yk.tx) + var retry int + err := yk.withTx(func(tx *scTx) error { + var err error + retry, err = ykPINRetries(tx) + return err + }) + return retry, err } func ykPINRetries(tx *scTx) (int, error) { @@ -288,7 +313,9 @@ 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 { - return ykReset(yk.tx, yk.rand) + return yk.withTx(func(tx *scTx) error { + return ykReset(tx, yk.rand) + }) } func ykReset(tx *scTx, r io.Reader) error { @@ -357,7 +384,9 @@ type version struct { // // Use DefaultManagementKey if the management key hasn't been set. func (yk *YubiKey) authManagementKey(key [24]byte) error { - return ykAuthenticate(yk.tx, key, yk.rand) + return yk.withTx(func(tx *scTx) error { + return ykAuthenticate(tx, key, yk.rand) + }) } var ( @@ -475,13 +504,14 @@ func ykAuthenticate(tx *scTx, key [24]byte, rand io.Reader) error { // // func (yk *YubiKey) SetManagementKey(oldKey, newKey [24]byte) error { - if err := ykAuthenticate(yk.tx, oldKey, yk.rand); err != nil { - return fmt.Errorf("authenticating with old key: %w", err) - } - if err := ykSetManagementKey(yk.tx, newKey, false); err != nil { - return err - } - return nil + return yk.withTx(func(tx *scTx) error { + err := ykAuthenticate(tx, oldKey, yk.rand) + if err != nil { + return fmt.Errorf("authenticating with old key: %w", err) + } + return ykSetManagementKey(tx, newKey, false) + + }) } // ykSetManagementKey updates the management key to a new key. This requires @@ -521,7 +551,9 @@ func ykSetManagementKey(tx *scTx, key [24]byte, touch bool) error { // } // func (yk *YubiKey) SetPIN(oldPIN, newPIN string) error { - return ykChangePIN(yk.tx, oldPIN, newPIN) + return yk.withTx(func(tx *scTx) error { + return ykChangePIN(tx, oldPIN, newPIN) + }) } func ykChangePIN(tx *scTx, oldPIN, newPIN string) error { @@ -544,7 +576,9 @@ 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 { - return ykUnblockPIN(yk.tx, puk, newPIN) + return yk.withTx(func(tx *scTx) error { + return ykUnblockPIN(tx, puk, newPIN) + }) } func ykUnblockPIN(tx *scTx, puk, newPIN string) error { @@ -582,7 +616,9 @@ func ykUnblockPIN(tx *scTx, puk, newPIN string) error { // } // func (yk *YubiKey) SetPUK(oldPUK, newPUK string) error { - return ykChangePUK(yk.tx, oldPUK, newPUK) + return yk.withTx(func(tx *scTx) error { + return ykChangePUK(tx, oldPUK, newPUK) + }) } func ykChangePUK(tx *scTx, oldPUK, newPUK string) error { @@ -694,7 +730,12 @@ 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) { - m, err := ykGetProtectedMetadata(yk.tx, pin) + var m *Metadata + err := yk.withTx(func(tx *scTx) error { + var err error + m, err = ykGetProtectedMetadata(tx, pin) + return err + }) if err != nil { if errors.Is(err, ErrNotFound) { return &Metadata{}, nil @@ -708,7 +749,9 @@ 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 { - return ykSetProtectedMetadata(yk.tx, key, m) + return yk.withTx(func(tx *scTx) error { + return ykSetProtectedMetadata(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 534b259..d1f3e74 100644 --- a/piv/piv_test.go +++ b/piv/piv_test.go @@ -158,13 +158,30 @@ func TestYubiKeyLoginNeeded(t *testing.T) { testRequiresVersion(t, yk, 4, 3, 0) - if !ykLoginNeeded(yk.tx) { + var flag bool + + yk.withTx(func(tx *scTx) error { + flag = !ykLoginNeeded(tx) + return nil + }) + + if flag { t.Errorf("expected login needed") } - if err := ykLogin(yk.tx, DefaultPIN); err != nil { + + err := yk.withTx(func(tx *scTx) error { + return ykLogin(tx, DefaultPIN) + }) + if err != nil { t.Fatalf("login: %v", err) } - if ykLoginNeeded(yk.tx) { + + yk.withTx(func(tx *scTx) error { + flag = ykLoginNeeded(tx) + return nil + }) + + if flag { t.Errorf("expected no login needed") } } @@ -239,7 +256,11 @@ func TestYubiKeyUnblockPIN(t *testing.T) { badPIN := "0" for { - err := ykLogin(yk.tx, badPIN) + + err := yk.withTx(func(tx *scTx) error { + return ykLogin(tx, badPIN) + }) + if err == nil { t.Fatalf("login with bad pin succeeded") } @@ -255,7 +276,11 @@ func TestYubiKeyUnblockPIN(t *testing.T) { if err := yk.Unblock(DefaultPUK, DefaultPIN); err != nil { t.Fatalf("unblocking pin: %v", err) } - if err := ykLogin(yk.tx, DefaultPIN); err != nil { + + err := yk.withTx(func(tx *scTx) error { + return ykLogin(tx, DefaultPIN) + }) + if err != nil { t.Errorf("failed to login with pin after unblock: %v", err) } } From 75ed55a273138e26506079ea5244b191eeb37932 Mon Sep 17 00:00:00 2001 From: rajnikant Date: Thu, 4 Nov 2021 08:22:47 +0530 Subject: [PATCH 10/13] fixing build and addressing review comments for windows --- piv/pcsc_windows.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/piv/pcsc_windows.go b/piv/pcsc_windows.go index 6489607..3d0e5c0 100644 --- a/piv/pcsc_windows.go +++ b/piv/pcsc_windows.go @@ -15,10 +15,10 @@ package piv import ( + "C" "fmt" "syscall" "unsafe" - "C" ) var ( @@ -60,7 +60,7 @@ type scContext struct { shared bool } -func newSCContext() (*scContext, error) { +func newSCContext(shared bool) (*scContext, error) { var ctx syscall.Handle r0, _, _ := procSCardEstablishContext.Call( @@ -72,11 +72,7 @@ func newSCContext() (*scContext, error) { if err := scCheck(r0); err != nil { return nil, err } - return &scContext{ctx: ctx}, nil -} - -func (c *scContext) SetShared() { - c.shared = true + return &scContext{ctx: ctx, shared: shared}, nil } func (c *scContext) Close() error { From e7bdbb48ae11f026abeae61779d1a78f27546960 Mon Sep 17 00:00:00 2001 From: Rajni Kant Date: Mon, 22 Nov 2021 09:35:18 +0530 Subject: [PATCH 11/13] addressing review comments and trying to fix build failures --- piv/key.go | 135 +++++++++++++++++++++++++------------------- piv/key_test.go | 10 ++-- piv/pcsc_darwin.go | 26 --------- piv/pcsc_linux.go | 26 --------- piv/pcsc_unix.go | 27 +++++---- piv/pcsc_windows.go | 39 ++++++++----- piv/piv.go | 46 ++++++--------- 7 files changed, 142 insertions(+), 167 deletions(-) diff --git a/piv/key.go b/piv/key.go index 387098b..08a9824 100644 --- a/piv/key.go +++ b/piv/key.go @@ -776,7 +776,7 @@ func isAuthErr(err error) bool { return e.sw1 == 0x69 && e.sw2 == 0x82 // "security status not satisfied" } -func (k KeyAuth) authTx(yk *YubiKey, pp PINPolicy) error { +func (k KeyAuth) authTx(yk *YubiKey, pp PINPolicy, tx *scTx) error { // PINPolicyNever shouldn't require a PIN. if pp == PINPolicyNever { return nil @@ -788,11 +788,7 @@ func (k KeyAuth) authTx(yk *YubiKey, pp PINPolicy) error { var flag bool - yk.withTx(func(tx *scTx) error { - flag = !ykLoginNeeded(tx) - return nil - }) - + flag = !ykLoginNeeded(tx) if pp != PINPolicyAlways && flag { return nil } @@ -813,17 +809,11 @@ func (k KeyAuth) authTx(yk *YubiKey, pp PINPolicy) error { }) } -func (k KeyAuth) do(yk *YubiKey, pp PINPolicy, f func(tx *scTx) ([]byte, error)) ([]byte, error) { - if err := k.authTx(yk, pp); err != nil { +func (k KeyAuth) do(yk *YubiKey, pp PINPolicy, tx *scTx, f func() ([]byte, error)) ([]byte, error) { + if err := k.authTx(yk, pp, tx); err != nil { return nil, err } - var res []byte - err := yk.withTx(func(tx *scTx) error { - var err error - res, err = f(tx) - return err - }) - return res, err + return f() } func pinPolicy(yk *YubiKey, slot Slot) (PINPolicy, error) { @@ -1033,9 +1023,15 @@ var _ crypto.Signer = (*ECDSAPrivateKey)(nil) // Sign implements crypto.Signer. func (k *ECDSAPrivateKey) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { - return k.auth.do(k.yk, k.pp, func(tx *scTx) ([]byte, error) { - return ykSignECDSA(tx, k.slot, k.pub, digest) + var res []byte + err := k.yk.withTx(func(tx *scTx) error { + var err error + res, err = k.auth.do(k.yk, k.pp, tx, func() ([]byte, error) { + return ykSignECDSA(tx, k.slot, k.pub, digest) + }) + return err }) + return res, err } // SharedKey performs a Diffie-Hellman key agreement with the peer @@ -1052,42 +1048,49 @@ func (k *ECDSAPrivateKey) SharedKey(peer *ecdsa.PublicKey) ([]byte, error) { return nil, errMismatchingAlgorithms } msg := elliptic.Marshal(peer.Curve, peer.X, peer.Y) - return k.auth.do(k.yk, k.pp, func(tx *scTx) ([]byte, error) { - var alg byte - size := k.pub.Params().BitSize - switch size { - case 256: - alg = algECCP256 - case 384: - alg = algECCP384 - default: - return nil, unsupportedCurveError{curve: size} - } - // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=118 - // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=93 - cmd := apdu{ - instruction: insAuthenticate, - param1: alg, - param2: byte(k.slot.Key), - data: marshalASN1(0x7c, - append([]byte{0x82, 0x00}, - marshalASN1(0x85, msg)...)), - } - resp, err := tx.Transmit(cmd) - if err != nil { - return nil, fmt.Errorf("command failed: %w", err) - } - sig, _, err := unmarshalASN1(resp, 1, 0x1c) // 0x7c - if err != nil { - return nil, fmt.Errorf("unmarshal response: %v", err) - } - rs, _, err := unmarshalASN1(sig, 2, 0x02) // 0x82 - if err != nil { - return nil, fmt.Errorf("unmarshal response signature: %v", err) - } - return rs, nil + var res []byte + err := k.yk.withTx(func(tx *scTx) error { + var err error + res, err = k.auth.do(k.yk, k.pp, tx, func() ([]byte, error) { + var alg byte + size := k.pub.Params().BitSize + switch size { + case 256: + alg = algECCP256 + case 384: + alg = algECCP384 + default: + return nil, unsupportedCurveError{curve: size} + } + + // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=118 + // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=93 + cmd := apdu{ + instruction: insAuthenticate, + param1: alg, + param2: byte(k.slot.Key), + data: marshalASN1(0x7c, + append([]byte{0x82, 0x00}, + marshalASN1(0x85, msg)...)), + } + resp, err := tx.Transmit(cmd) + if err != nil { + return nil, fmt.Errorf("command failed: %w", err) + } + sig, _, err := unmarshalASN1(resp, 1, 0x1c) // 0x7c + if err != nil { + return nil, fmt.Errorf("unmarshal response: %v", err) + } + rs, _, err := unmarshalASN1(sig, 2, 0x02) // 0x82 + if err != nil { + return nil, fmt.Errorf("unmarshal response signature: %v", err) + } + return rs, nil + }) + return err }) + return res, err } type keyEd25519 struct { @@ -1103,9 +1106,15 @@ func (k *keyEd25519) Public() crypto.PublicKey { } func (k *keyEd25519) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { - return k.auth.do(k.yk, k.pp, func(tx *scTx) ([]byte, error) { - return skSignEd25519(tx, k.slot, k.pub, digest) + var res []byte + err := k.yk.withTx(func(tx *scTx) error { + var err error + res, err = k.auth.do(k.yk, k.pp, tx, func() ([]byte, error) { + return skSignEd25519(tx, k.slot, k.pub, digest) + }) + return err }) + return res, err } type keyRSA struct { @@ -1121,15 +1130,27 @@ func (k *keyRSA) Public() crypto.PublicKey { } func (k *keyRSA) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) { - return k.auth.do(k.yk, k.pp, func(tx *scTx) ([]byte, error) { - return ykSignRSA(tx, k.slot, k.pub, digest, opts) + var res []byte + err := k.yk.withTx(func(tx *scTx) error { + var err error + res, err = k.auth.do(k.yk, k.pp, tx, func() ([]byte, error) { + return ykSignRSA(tx, k.slot, k.pub, digest, opts) + }) + return err }) + return res, err } func (k *keyRSA) Decrypt(rand io.Reader, msg []byte, opts crypto.DecrypterOpts) ([]byte, error) { - return k.auth.do(k.yk, k.pp, func(tx *scTx) ([]byte, error) { - return ykDecryptRSA(tx, k.slot, k.pub, msg) + var res []byte + err := k.yk.withTx(func(tx *scTx) error { + var err error + res, err = k.auth.do(k.yk, k.pp, tx, func() ([]byte, error) { + return ykDecryptRSA(tx, k.slot, k.pub, msg) + }) + return err }) + return res, err } func ykSignECDSA(tx *scTx, slot Slot, pub *ecdsa.PublicKey, digest []byte) ([]byte, error) { diff --git a/piv/key_test.go b/piv/key_test.go index e4727f0..05d5956 100644 --- a/piv/key_test.go +++ b/piv/key_test.go @@ -250,7 +250,7 @@ func TestSlots(t *testing.T) { } tmpl := &x509.Certificate{ - Subject: pkix.Name{CommonName: "my-client"}, + Subject: pkix.Name{CommonName: "my-Client"}, SerialNumber: big.NewInt(1), NotBefore: time.Now(), NotAfter: time.Now().Add(time.Hour), @@ -483,7 +483,7 @@ func TestYubiKeyStoreCertificate(t *testing.T) { } cliTmpl := &x509.Certificate{ - Subject: pkix.Name{CommonName: "my-client"}, + Subject: pkix.Name{CommonName: "my-Client"}, SerialNumber: big.NewInt(101), NotBefore: time.Now(), NotAfter: time.Now().Add(time.Hour), @@ -492,18 +492,18 @@ func TestYubiKeyStoreCertificate(t *testing.T) { } cliCertDER, err := x509.CreateCertificate(rand.Reader, cliTmpl, caCert, pub, caPriv) if err != nil { - t.Fatalf("creating client cert: %v", err) + t.Fatalf("creating Client cert: %v", err) } cliCert, err := x509.ParseCertificate(cliCertDER) if err != nil { t.Fatalf("parsing cli cert: %v", err) } if err := yk.SetCertificate(DefaultManagementKey, slot, cliCert); err != nil { - t.Fatalf("storing client cert: %v", err) + t.Fatalf("storing Client cert: %v", err) } gotCert, err := yk.Certificate(slot) if err != nil { - t.Fatalf("getting client cert: %v", err) + t.Fatalf("getting Client cert: %v", err) } if !bytes.Equal(gotCert.Raw, cliCert.Raw) { t.Errorf("stored cert didn't match cert retrieved") diff --git a/piv/pcsc_darwin.go b/piv/pcsc_darwin.go index fd37363..d57a955 100644 --- a/piv/pcsc_darwin.go +++ b/piv/pcsc_darwin.go @@ -14,14 +14,6 @@ package piv -// #cgo darwin LDFLAGS: -framework PCSC -// #cgo linux pkg-config: libpcsclite -// #cgo freebsd CFLAGS: -I/usr/local/include/ -// #cgo freebsd CFLAGS: -I/usr/local/include/PCSC -// #cgo freebsd LDFLAGS: -L/usr/local/lib/ -// #cgo freebsd LDFLAGS: -lpcsclite -// #include -// #include import "C" func scCheck(rc C.int) error { @@ -44,21 +36,3 @@ func isRCNoReaders(rc C.int) bool { // are available. return false } - -func (c *scContext) Connect(reader string) (*scHandle, error) { - var ( - handle C.SCARDHANDLE - activeProtocol C.DWORD - ) - opt := C.SCARD_SHARE_EXCLUSIVE - if c.shared { - opt = C.SCARD_SHARE_SHARED - } - rc := C.SCardConnect(c.ctx, C.CString(reader), - C.uint(opt), C.SCARD_PROTOCOL_T1, - &handle, &activeProtocol) - if err := scCheck(rc); err != nil { - return nil, err - } - return &scHandle{handle}, nil -} diff --git a/piv/pcsc_linux.go b/piv/pcsc_linux.go index f7ab943..a8d5433 100644 --- a/piv/pcsc_linux.go +++ b/piv/pcsc_linux.go @@ -14,14 +14,6 @@ package piv -// #cgo darwin LDFLAGS: -framework PCSC -// #cgo linux pkg-config: libpcsclite -// #cgo freebsd CFLAGS: -I/usr/local/include/ -// #cgo freebsd CFLAGS: -I/usr/local/include/PCSC -// #cgo freebsd LDFLAGS: -L/usr/local/lib/ -// #cgo freebsd LDFLAGS: -lpcsclite -// #include -// #include import "C" // Return codes for PCSC are different on different platforms (int vs. long). @@ -35,22 +27,4 @@ func scCheck(rc C.long) error { func isRCNoReaders(rc C.long) bool { return C.ulong(rc) == 0x8010002E -} - -func (c *scContext) Connect(reader string) (*scHandle, error) { - var ( - handle C.SCARDHANDLE - activeProtocol C.DWORD - ) - opt := C.SCARD_SHARE_EXCLUSIVE - if c.shared { - opt = C.SCARD_SHARE_SHARED - } - rc := C.SCardConnect(c.ctx, C.CString(reader), - C.ulong(opt), C.SCARD_PROTOCOL_T1, - &handle, &activeProtocol) - if err := scCheck(rc); err != nil { - return nil, err - } - return &scHandle{handle}, nil } \ No newline at end of file diff --git a/piv/pcsc_unix.go b/piv/pcsc_unix.go index 285440e..110d66f 100644 --- a/piv/pcsc_unix.go +++ b/piv/pcsc_unix.go @@ -88,24 +88,31 @@ func (c *scContext) ListReaders() ([]string, error) { type scHandle struct { h C.SCARDHANDLE } -/* + func (c *scContext) Connect(reader string) (*scHandle, error) { var ( handle C.SCARDHANDLE activeProtocol C.DWORD ) - opt := C.SCARD_SHARE_EXCLUSIVE + if c.shared { - opt = C.SCARD_SHARE_SHARED - } - rc := C.SCardConnect(c.ctx, C.CString(reader), - C.uint(opt), C.SCARD_PROTOCOL_T1, - &handle, &activeProtocol) - if err := scCheck(rc); err != nil { - return nil, err + rc := C.SCardConnect(c.ctx, C.CString(reader), + C.SCARD_SHARE_SHARED, C.SCARD_PROTOCOL_T1, + &handle, &activeProtocol) + if err := scCheck(rc); err != nil { + return nil, err + } + } else { + rc := C.SCardConnect(c.ctx, C.CString(reader), + C.SCARD_SHARE_EXCLUSIVE, C.SCARD_PROTOCOL_T1, + &handle, &activeProtocol) + if err := scCheck(rc); err != nil { + return nil, err + } } + return &scHandle{handle}, nil -}*/ +} func (h *scHandle) Close() error { return scCheck(C.SCardDisconnect(h.h, C.SCARD_LEAVE_CARD)) diff --git a/piv/pcsc_windows.go b/piv/pcsc_windows.go index 3d0e5c0..b1d0723 100644 --- a/piv/pcsc_windows.go +++ b/piv/pcsc_windows.go @@ -15,7 +15,6 @@ package piv import ( - "C" "fmt" "syscall" "unsafe" @@ -130,21 +129,33 @@ func (c *scContext) Connect(reader string) (*scHandle, error) { handle syscall.Handle activeProtocol uint16 ) - opt := scardShareExclusive + var r0 uintptr if c.shared { - opt = scardShareShared - } - r0, _, _ := procSCardConnectW.Call( - uintptr(c.ctx), - uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(reader))), - C.DWORD(opt), - scardProtocolT1, - uintptr(unsafe.Pointer(&handle)), - uintptr(activeProtocol), - ) - if err := scCheck(r0); err != nil { - return nil, err + r0, _, _ = procSCardConnectW.Call( + uintptr(c.ctx), + uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(reader))), + scardShareShared, + scardProtocolT1, + uintptr(unsafe.Pointer(&handle)), + uintptr(activeProtocol), + ) + if err := scCheck(r0); err != nil { + return nil, err + } + } else { + r0, _, _ = procSCardConnectW.Call( + uintptr(c.ctx), + uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(reader))), + scardShareExclusive, + scardProtocolT1, + uintptr(unsafe.Pointer(&handle)), + uintptr(activeProtocol), + ) + if err := scCheck(r0); err != nil { + return nil, err + } } + return &scHandle{handle}, nil } diff --git a/piv/piv.go b/piv/piv.go index ee2fa7f..a3e0677 100644 --- a/piv/piv.go +++ b/piv/piv.go @@ -51,8 +51,8 @@ var ( // // See: https://ludovicrousseau.blogspot.com/2010/05/what-is-in-pcsc-reader-name.html func Cards() ([]string, error) { - var c client - return c.Cards() + var c Client + return c.cards() } const ( @@ -102,7 +102,6 @@ const ( type YubiKey struct { ctx *scContext h *scHandle - //tx *scTx rand io.Reader @@ -124,8 +123,14 @@ func (yk *YubiKey) Close() error { return err1 } -// Client provides configuration options when connection to the smart card. +// Client is a smart card Client and may be exported in the future to allow +// configuration for the top level Open() and Cards() APIs. type Client struct { + // Rand is a cryptographic source of randomness used for card challenges. + // + // If nil, defaults to crypto.Rand. + Rand io.Reader + // Shared enables a non-exclusive connection to the PIV application, allowing // other applications that also request a non-exclusive connection to connect // at the same time. @@ -135,29 +140,14 @@ type Client struct { Shared bool } -// Open method can be put to use for opening a smart-card with shared access. -func (cl *Client) Open(card string) (*YubiKey, error) { - var c client - return c.Open(card, cl.Shared) -} - // Open connects to a YubiKey smart card. func Open(card string) (*YubiKey, error) { - var c client - return c.Open(card, false) + var c Client + return c.Open(card) } -// client is a smart card client and may be exported in the future to allow -// configuration for the top level Open() and Cards() APIs. -type client struct { - // Rand is a cryptographic source of randomness used for card challenges. - // - // If nil, defaults to crypto.Rand. - Rand io.Reader -} - -func (c *client) Cards() ([]string, error) { - ctx, err := newSCContext(true) +func (c *Client) cards() ([]string, error) { + ctx, err := newSCContext(c.Shared) if err != nil { return nil, fmt.Errorf("connecting to pscs: %w", err) } @@ -174,8 +164,8 @@ func (yk *YubiKey) withTx(fn func(tx *scTx) error) error { return fn(tx) } -func (c *client) Open(card string, shared bool) (*YubiKey, error) { - ctx, err := newSCContext(shared) +func (c *Client) Open(card string) (*YubiKey, error) { + ctx, err := newSCContext(c.Shared) if err != nil { return nil, fmt.Errorf("connecting to smart card daemon: %w", err) } @@ -383,10 +373,8 @@ type version struct { // certificates to slots. // // Use DefaultManagementKey if the management key hasn't been set. -func (yk *YubiKey) authManagementKey(key [24]byte) error { - return yk.withTx(func(tx *scTx) error { - return ykAuthenticate(tx, key, yk.rand) - }) +func (yk *YubiKey) authManagementKey(key [24]byte, tx *scTx) error { + return ykAuthenticate(tx, key, yk.rand) } var ( From 8153f858b9091caa3b41f6cbebb7df1eb9821685 Mon Sep 17 00:00:00 2001 From: Rajni Kant Date: Mon, 22 Nov 2021 09:39:16 +0530 Subject: [PATCH 12/13] addressing review comments and trying to fix build failures and tests --- piv/piv.go | 1 + piv/piv_test.go | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/piv/piv.go b/piv/piv.go index a3e0677..fbb8916 100644 --- a/piv/piv.go +++ b/piv/piv.go @@ -164,6 +164,7 @@ func (yk *YubiKey) withTx(fn func(tx *scTx) error) error { return fn(tx) } +// Open connects to a YubiKey smart card. func (c *Client) Open(card string) (*YubiKey, error) { ctx, err := newSCContext(c.Shared) if err != nil { diff --git a/piv/piv_test.go b/piv/piv_test.go index d1f3e74..46c31c0 100644 --- a/piv/piv_test.go +++ b/piv/piv_test.go @@ -225,7 +225,11 @@ func TestYubiKeyAuthenticate(t *testing.T) { yk, close := newTestYubiKey(t) defer close() - if err := yk.authManagementKey(DefaultManagementKey); err != nil { + err := yk.withTx(func(tx *scTx) error { + return yk.authManagementKey(DefaultManagementKey, tx) + }) + + if err != nil { t.Errorf("authenticating: %v", err) } } @@ -242,9 +246,15 @@ func TestYubiKeySetManagementKey(t *testing.T) { if err := yk.SetManagementKey(DefaultManagementKey, mgmtKey); err != nil { t.Fatalf("setting management key: %v", err) } - if err := yk.authManagementKey(mgmtKey); err != nil { + + err := yk.withTx(func(tx *scTx) error { + return yk.authManagementKey(mgmtKey, tx) + }) + + if err != nil { t.Errorf("authenticating with new management key: %v", err) } + if err := yk.SetManagementKey(mgmtKey, DefaultManagementKey); err != nil { t.Fatalf("resetting management key: %v", err) } From 7ba2f8bd04dadef818f97c8204030fbe858146fa Mon Sep 17 00:00:00 2001 From: Rajni Kant Date: Mon, 22 Nov 2021 09:50:45 +0530 Subject: [PATCH 13/13] refactor windows --- piv/pcsc_windows.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/piv/pcsc_windows.go b/piv/pcsc_windows.go index b1d0723..e7d4399 100644 --- a/piv/pcsc_windows.go +++ b/piv/pcsc_windows.go @@ -139,9 +139,6 @@ func (c *scContext) Connect(reader string) (*scHandle, error) { uintptr(unsafe.Pointer(&handle)), uintptr(activeProtocol), ) - if err := scCheck(r0); err != nil { - return nil, err - } } else { r0, _, _ = procSCardConnectW.Call( uintptr(c.ctx), @@ -151,11 +148,10 @@ func (c *scContext) Connect(reader string) (*scHandle, error) { uintptr(unsafe.Pointer(&handle)), uintptr(activeProtocol), ) - if err := scCheck(r0); err != nil { - return nil, err - } } - + if err := scCheck(r0); err != nil { + return nil, err + } return &scHandle{handle}, nil }