Skip to content
13 changes: 11 additions & 2 deletions piv/pcsc_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down
14 changes: 12 additions & 2 deletions piv/pcsc_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ var (
const (
scardScopeSystem = 2
scardShareExclusive = 1
scardShareShared = 2
scardLeaveCard = 0
scardProtocolT1 = 2
scardPCIT1 = 0
Expand All @@ -54,7 +55,8 @@ func isRCNoReaders(rc uintptr) bool {
}

type scContext struct {
ctx syscall.Handle
ctx syscall.Handle
shared bool
}

func newSCContext() (*scContext, error) {
Expand All @@ -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)
Expand Down Expand Up @@ -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),
Expand Down
32 changes: 30 additions & 2 deletions piv/piv.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 be 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)
}

Expand All @@ -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) {
Expand All @@ -148,12 +172,16 @@ 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)
}

if c.opts.shared {
ctx.SetShared()
}

h, err := ctx.Connect(card)
if err != nil {
ctx.Close()
Expand Down
2 changes: 1 addition & 1 deletion piv/piv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down