Skip to content
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