Skip to content

Commit

Permalink
piv: permit use of shared smartcard connections
Browse files Browse the repository at this point in the history
  • Loading branch information
ericchiang committed Oct 20, 2022
1 parent 1902689 commit cfcea3d
Show file tree
Hide file tree
Showing 8 changed files with 419 additions and 271 deletions.
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

0 comments on commit cfcea3d

Please sign in to comment.