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: permit use of shared smartcard connections #108

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
4 changes: 0 additions & 4 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ jobs:
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Install staticcheck
run: go install honnef.co/go/tools/cmd/[email protected]
- name: Install libpcsc
run: sudo apt-get install -y libpcsclite-dev pcscd pcsc-tools
- name: Test
Expand All @@ -42,8 +40,6 @@ jobs:
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Install staticcheck
run: go install honnef.co/go/tools/cmd/[email protected]
- name: Test
run: "make build"
env:
Expand Down
8 changes: 2 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
.PHONY: test
test: lint
test:
go test -v ./...

.PHONY: lint
lint:
staticcheck ./...

.PHONY: build
build: lint
build:
go build ./...
78 changes: 54 additions & 24 deletions piv/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -507,7 +507,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.tx(func(tx *scTx) error {
var err error
cert, err = ykAttest(tx, slot)
return err
})
if err == nil {
return cert, nil
}
Expand Down Expand Up @@ -558,7 +563,12 @@ func (yk *YubiKey) Certificate(slot Slot) (*x509.Certificate, error) {
byte(slot.Object),
},
}
resp, err := yk.tx.Transmit(cmd)
var resp []byte
err := yk.tx(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)
}
Expand Down Expand Up @@ -605,10 +615,12 @@ 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.tx(func(tx *scTx) error {
if err := ykAuthenticate(tx, key, yk.rand); 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 {
Expand Down Expand Up @@ -659,10 +671,17 @@ 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 pub crypto.PublicKey
err := yk.tx(func(tx *scTx) error {
if err := ykAuthenticate(tx, key, yk.rand); err != nil {
return fmt.Errorf("authenticating with management key: %w", err)
}

var err error
pub, err = ykGenerateKey(tx, slot, opts)
return err
})
return pub, err
}

func ykGenerateKey(tx *scTx, slot Slot, o Key) (crypto.PublicKey, error) {
Expand Down Expand Up @@ -731,8 +750,12 @@ type KeyAuth struct {
// If provided, PINPrompt is ignored.
PIN string
// PINPrompt can be used to interactively request the PIN from the user. The
// method is only called when needed. For example, if a key specifies
// PINPolicyOnce, PINPrompt will only be called once per YubiKey struct.
// method is only called when needed for exclusive connections. If a key
// specifies PINPolicyOnce, PINPrompt will only be called once per YubiKey
// struct.
//
// Clients using a shared connection will call the PIN prompt on every
// operation, regardless of PIN policy.
PINPrompt func() (pin string, err error)

// PINPolicy can be used to specify the PIN caching strategy for the slot. If
Expand All @@ -743,7 +766,7 @@ type KeyAuth struct {
PINPolicy PINPolicy
}

func (k KeyAuth) authTx(yk *YubiKey, pp PINPolicy) error {
func (k KeyAuth) authTx(tx *scTx, pp PINPolicy) error {
// PINPolicyNever shouldn't require a PIN.
if pp == PINPolicyNever {
return nil
Expand All @@ -752,7 +775,7 @@ 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) {
if pp != PINPolicyAlways && !ykLoginNeeded(tx) {
return nil
}

Expand All @@ -767,14 +790,20 @@ 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 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 b []byte
err := yk.tx(func(tx *scTx) error {
var err error
if err := k.authTx(tx, pp); err != nil {
return err
}
b, err = f(tx)
return err
})
return b, err
}

func pinPolicy(yk *YubiKey, slot Slot) (PINPolicy, error) {
Expand Down Expand Up @@ -919,11 +948,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.tx(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 {
Expand Down
2 changes: 1 addition & 1 deletion piv/pcsc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func runHandleTest(t *testing.T, f func(t *testing.T, h *scHandle)) {
if reader == "" {
t.Skip("could not find yubikey, skipping testing")
}
h, err := c.Connect(reader)
h, err := c.Connect(reader, false)
if err != nil {
t.Fatalf("connecting to %s: %v", reader, err)
}
Expand Down
8 changes: 6 additions & 2 deletions piv/pcsc_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,17 @@ type scHandle struct {
h C.SCARDHANDLE
}

func (c *scContext) Connect(reader string) (*scHandle, error) {
func (c *scContext) Connect(reader string, shared bool) (*scHandle, error) {
var (
handle C.SCARDHANDLE
activeProtocol C.DWORD
)
var mode C.DWORD = C.SCARD_SHARE_EXCLUSIVE
if shared {
mode = C.SCARD_SHARE_SHARED
}
rc := C.SCardConnect(c.ctx, C.CString(reader),
C.SCARD_SHARE_EXCLUSIVE, C.SCARD_PROTOCOL_T1,
mode, C.SCARD_PROTOCOL_T1,
&handle, &activeProtocol)
if err := scCheck(rc); err != nil {
return nil, err
Expand Down
10 changes: 8 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 Down Expand Up @@ -122,7 +123,7 @@ func (c *scContext) ListReaders() ([]string, error) {
return readers, nil
}

func (c *scContext) Connect(reader string) (*scHandle, error) {
func (c *scContext) Connect(reader string, shared bool) (*scHandle, error) {
var (
handle syscall.Handle
activeProtocol uint16
Expand All @@ -131,10 +132,15 @@ func (c *scContext) Connect(reader string) (*scHandle, error) {
if err != nil {
return nil, fmt.Errorf("invalid reader string: %v", err)
}

mode := uintptr(scardShareExclusive)
if shared {
mode = scardShareShared
}
r0, _, _ := procSCardConnectW.Call(
uintptr(c.ctx),
uintptr(unsafe.Pointer(readerPtr)),
scardShareExclusive,
mode,
scardProtocolT1,
uintptr(unsafe.Pointer(&handle)),
uintptr(activeProtocol),
Expand Down
Loading