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

adding Shared access to multiple processes #100

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
185 changes: 122 additions & 63 deletions piv/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -755,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
Expand All @@ -764,7 +785,11 @@ 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

flag = !ykLoginNeeded(tx)
if pp != PINPolicyAlways && flag {
return nil
}

Expand All @@ -779,14 +804,16 @@ 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 {
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
}
return f(yk.tx)
return f()
}

func pinPolicy(yk *YubiKey, slot Slot) (PINPolicy, error) {
Expand Down Expand Up @@ -931,11 +958,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 {
Expand Down Expand Up @@ -995,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
Expand All @@ -1014,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 {
Expand All @@ -1065,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 {
Expand All @@ -1083,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) {
Expand Down
10 changes: 5 additions & 5 deletions piv/key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand All @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion piv/pcsc_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ func scCheck(rc C.long) error {

func isRCNoReaders(rc C.long) bool {
return C.ulong(rc) == 0x8010002E
}
}
2 changes: 1 addition & 1 deletion piv/pcsc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
28 changes: 20 additions & 8 deletions piv/pcsc_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,17 @@ import (
const rcSuccess = C.SCARD_S_SUCCESS

type scContext struct {
ctx C.SCARDCONTEXT
ctx C.SCARDCONTEXT
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 {
Expand Down Expand Up @@ -93,12 +94,23 @@ func (c *scContext) Connect(reader string) (*scHandle, error) {
handle C.SCARDHANDLE
activeProtocol C.DWORD
)
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

if c.shared {
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
}

Expand Down
Loading