Skip to content

Commit cfab90e

Browse files
authored
Merge pull request #463 from smallstep/josh/yubikey-piv-serial
Add 'Serial' struct method returning serial number directly from Yubi…
2 parents eec50b0 + ea7114e commit cfab90e

File tree

2 files changed

+67
-10
lines changed

2 files changed

+67
-10
lines changed

kms/yubikey/yubikey.go

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"crypto/x509"
1010
"encoding/asn1"
1111
"encoding/hex"
12+
"fmt"
1213
"io"
1314
"net/url"
1415
"strconv"
@@ -42,6 +43,7 @@ type pivKey interface {
4243
GenerateKey(key [24]byte, slot piv.Slot, opts piv.Key) (crypto.PublicKey, error)
4344
PrivateKey(slot piv.Slot, public crypto.PublicKey, auth piv.KeyAuth) (crypto.PrivateKey, error)
4445
Attest(slot piv.Slot) (*x509.Certificate, error)
46+
Serial() (uint32, error)
4547
Close() error
4648
}
4749

@@ -141,8 +143,8 @@ func New(_ context.Context, opts apiv1.Options) (*YubiKey, error) {
141143
// Attempt to locate the yubikey with the given serial.
142144
for _, name := range cards {
143145
if k, err := openCard(name); err == nil {
144-
if cert, err := k.Attest(piv.SlotAuthentication); err == nil {
145-
if serial == getSerialNumber(cert) {
146+
if s, err := k.Serial(); err == nil {
147+
if serial == strconv.FormatUint(uint64(s), 10) {
146148
yk = k
147149
card = name
148150
break
@@ -353,10 +355,22 @@ func (k *YubiKey) CreateAttestation(req *apiv1.CreateAttestationRequest) (*apiv1
353355
Certificate: cert,
354356
CertificateChain: []*x509.Certificate{cert, intermediate},
355357
PublicKey: cert.PublicKey,
356-
PermanentIdentifier: getSerialNumber(cert),
358+
PermanentIdentifier: getAttestedSerial(cert),
357359
}, nil
358360
}
359361

362+
// Serial returns the serial number of the PIV card or and empty
363+
// string if retrieval fails
364+
func (k *YubiKey) Serial() (string, error) {
365+
serial, err := k.yk.Serial()
366+
367+
if err != nil {
368+
return "", fmt.Errorf("error getting Yubikey's serial number: %w", err)
369+
}
370+
371+
return strconv.FormatUint(uint64(serial), 10), nil
372+
}
373+
360374
// Close releases the connection to the YubiKey.
361375
func (k *YubiKey) Close() error {
362376
if err := k.yk.Close(); err != nil {
@@ -505,10 +519,10 @@ func getPolicies(req *apiv1.CreateKeyRequest) (piv.PINPolicy, piv.TouchPolicy) {
505519
return pin, touch
506520
}
507521

508-
// getSerialNumber returns the serial number from an attestation certificate. It
522+
// getAttestedSerial returns the serial number from an attestation certificate. It
509523
// will return an empty string if the serial number extension does not exist
510524
// or if it is malformed.
511-
func getSerialNumber(cert *x509.Certificate) string {
525+
func getAttestedSerial(cert *x509.Certificate) string {
512526
for _, ext := range cert.Extensions {
513527
if ext.Id.Equal(oidYubicoSerialNumber) {
514528
var serialNumber int
@@ -519,6 +533,7 @@ func getSerialNumber(cert *x509.Certificate) string {
519533
return strconv.Itoa(serialNumber)
520534
}
521535
}
536+
522537
return ""
523538
}
524539

kms/yubikey/yubikey_test.go

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ type stubPivKey struct {
3636
certMap map[piv.Slot]*x509.Certificate
3737
signerMap map[piv.Slot]interface{}
3838
keyOptionsMap map[piv.Slot]piv.Key
39+
serial uint32
40+
serialErr error
3941
closeErr error
4042
}
4143

@@ -93,15 +95,16 @@ func newStubPivKey(t *testing.T, alg symmetricAlgorithm) *stubPivKey {
9395
t.Fatal(errors.New("unknown alg"))
9496
}
9597

96-
serialNumber, err := asn1.Marshal(112233)
98+
sn := 112233
99+
snAsn1, err := asn1.Marshal(sn)
97100
if err != nil {
98101
t.Fatal(err)
99102
}
100103
attCert, err := attestCA.Sign(&x509.Certificate{
101104
Subject: pkix.Name{CommonName: "attested certificate"},
102105
PublicKey: attSigner.Public(),
103106
ExtraExtensions: []pkix.Extension{
104-
{Id: oidYubicoSerialNumber, Value: serialNumber},
107+
{Id: oidYubicoSerialNumber, Value: snAsn1},
105108
},
106109
})
107110
if err != nil {
@@ -132,6 +135,7 @@ func newStubPivKey(t *testing.T, alg symmetricAlgorithm) *stubPivKey {
132135
piv.SlotSignature: userSigner, // 9c
133136
},
134137
keyOptionsMap: map[piv.Slot]piv.Key{},
138+
serial: uint32(sn),
135139
}
136140
}
137141

@@ -220,6 +224,13 @@ func (s *stubPivKey) Close() error {
220224
return s.closeErr
221225
}
222226

227+
func (s *stubPivKey) Serial() (uint32, error) {
228+
if s.serialErr != nil {
229+
return 0, s.serialErr
230+
}
231+
return s.serial, nil
232+
}
233+
223234
func TestRegister(t *testing.T) {
224235
pCards := pivCards
225236
t.Cleanup(func() {
@@ -1029,6 +1040,37 @@ func TestYubiKey_CreateAttestation(t *testing.T) {
10291040
}
10301041
}
10311042

1043+
func TestYubiKey_Serial(t *testing.T) {
1044+
yk1 := newStubPivKey(t, RSA)
1045+
yk2 := newStubPivKey(t, RSA)
1046+
yk2.serialErr = errors.New("some error")
1047+
1048+
tests := []struct {
1049+
name string
1050+
yk pivKey
1051+
want string
1052+
wantErr bool
1053+
}{
1054+
{"ok", yk1, "112233", false},
1055+
{"fail", yk2, "", true},
1056+
}
1057+
for _, tt := range tests {
1058+
t.Run(tt.name, func(t *testing.T) {
1059+
k := &YubiKey{
1060+
yk: tt.yk,
1061+
}
1062+
got, err := k.Serial()
1063+
if (err != nil) != tt.wantErr {
1064+
t.Errorf("YubiKey.Serial() error = %v, wantErr %v", err, tt.wantErr)
1065+
return
1066+
}
1067+
if !reflect.DeepEqual(got, tt.want) {
1068+
t.Errorf("YubiKey.Serial() = %v, want %v", got, tt.want)
1069+
}
1070+
})
1071+
}
1072+
}
1073+
10321074
func TestYubiKey_Close(t *testing.T) {
10331075
yk1 := newStubPivKey(t, ECDSA)
10341076
yk2 := newStubPivKey(t, RSA)
@@ -1061,7 +1103,7 @@ func TestYubiKey_Close(t *testing.T) {
10611103
}
10621104
}
10631105

1064-
func Test_getSerialNumber(t *testing.T) {
1106+
func Test_getAttestedSerial(t *testing.T) {
10651107
serialNumber, err := asn1.Marshal(112233)
10661108
if err != nil {
10671109
t.Fatal(err)
@@ -1107,8 +1149,8 @@ func Test_getSerialNumber(t *testing.T) {
11071149
}
11081150
for _, tt := range tests {
11091151
t.Run(tt.name, func(t *testing.T) {
1110-
if got := getSerialNumber(tt.args.cert); got != tt.want {
1111-
t.Errorf("getSerialNumber() = %v, want %v", got, tt.want)
1152+
if got := getAttestedSerial(tt.args.cert); got != tt.want {
1153+
t.Errorf("getAttestedSerial() = %v, want %v", got, tt.want)
11121154
}
11131155
})
11141156
}

0 commit comments

Comments
 (0)