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

Add 'Serial' struct method returning serial number directly from Yubi… #463

Merged
merged 2 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 20 additions & 5 deletions kms/yubikey/yubikey.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"crypto/x509"
"encoding/asn1"
"encoding/hex"
"fmt"
"io"
"net/url"
"strconv"
Expand Down Expand Up @@ -42,6 +43,7 @@ type pivKey interface {
GenerateKey(key [24]byte, slot piv.Slot, opts piv.Key) (crypto.PublicKey, error)
PrivateKey(slot piv.Slot, public crypto.PublicKey, auth piv.KeyAuth) (crypto.PrivateKey, error)
Attest(slot piv.Slot) (*x509.Certificate, error)
Serial() (uint32, error)
Close() error
}

Expand Down Expand Up @@ -141,8 +143,8 @@ func New(_ context.Context, opts apiv1.Options) (*YubiKey, error) {
// Attempt to locate the yubikey with the given serial.
for _, name := range cards {
if k, err := openCard(name); err == nil {
if cert, err := k.Attest(piv.SlotAuthentication); err == nil {
if serial == getSerialNumber(cert) {
if s, err := k.Serial(); err == nil {
if serial == strconv.FormatUint(uint64(s), 10) {
yk = k
card = name
break
Expand Down Expand Up @@ -353,10 +355,22 @@ func (k *YubiKey) CreateAttestation(req *apiv1.CreateAttestationRequest) (*apiv1
Certificate: cert,
CertificateChain: []*x509.Certificate{cert, intermediate},
PublicKey: cert.PublicKey,
PermanentIdentifier: getSerialNumber(cert),
PermanentIdentifier: getAttestedSerial(cert),
}, nil
}

// Serial returns the serial number of the PIV card or and empty
// string if retrieval fails
func (k *YubiKey) Serial() (string, error) {
serial, err := k.yk.Serial()

if err != nil {
return "", fmt.Errorf("error getting Yubikey's serial number: %w", err)
}

return strconv.FormatUint(uint64(serial), 10), nil
}

// Close releases the connection to the YubiKey.
func (k *YubiKey) Close() error {
if err := k.yk.Close(); err != nil {
Expand Down Expand Up @@ -505,10 +519,10 @@ func getPolicies(req *apiv1.CreateKeyRequest) (piv.PINPolicy, piv.TouchPolicy) {
return pin, touch
}

// getSerialNumber returns the serial number from an attestation certificate. It
// getAttestedSerial returns the serial number from an attestation certificate. It
// will return an empty string if the serial number extension does not exist
// or if it is malformed.
func getSerialNumber(cert *x509.Certificate) string {
func getAttestedSerial(cert *x509.Certificate) string {
for _, ext := range cert.Extensions {
if ext.Id.Equal(oidYubicoSerialNumber) {
var serialNumber int
Expand All @@ -519,6 +533,7 @@ func getSerialNumber(cert *x509.Certificate) string {
return strconv.Itoa(serialNumber)
}
}

return ""
}

Expand Down
52 changes: 47 additions & 5 deletions kms/yubikey/yubikey_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ type stubPivKey struct {
certMap map[piv.Slot]*x509.Certificate
signerMap map[piv.Slot]interface{}
keyOptionsMap map[piv.Slot]piv.Key
serial uint32
serialErr error
closeErr error
}

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

serialNumber, err := asn1.Marshal(112233)
sn := 112233
snAsn1, err := asn1.Marshal(sn)
if err != nil {
t.Fatal(err)
}
attCert, err := attestCA.Sign(&x509.Certificate{
Subject: pkix.Name{CommonName: "attested certificate"},
PublicKey: attSigner.Public(),
ExtraExtensions: []pkix.Extension{
{Id: oidYubicoSerialNumber, Value: serialNumber},
{Id: oidYubicoSerialNumber, Value: snAsn1},
},
})
if err != nil {
Expand Down Expand Up @@ -132,6 +135,7 @@ func newStubPivKey(t *testing.T, alg symmetricAlgorithm) *stubPivKey {
piv.SlotSignature: userSigner, // 9c
},
keyOptionsMap: map[piv.Slot]piv.Key{},
serial: uint32(sn),
}
}

Expand Down Expand Up @@ -220,6 +224,13 @@ func (s *stubPivKey) Close() error {
return s.closeErr
}

func (s *stubPivKey) Serial() (uint32, error) {
if s.serialErr != nil {
return 0, s.serialErr
}
return s.serial, nil
}

func TestRegister(t *testing.T) {
pCards := pivCards
t.Cleanup(func() {
Expand Down Expand Up @@ -1029,6 +1040,37 @@ func TestYubiKey_CreateAttestation(t *testing.T) {
}
}

func TestYubiKey_Serial(t *testing.T) {
yk1 := newStubPivKey(t, RSA)
yk2 := newStubPivKey(t, RSA)
yk2.serialErr = errors.New("some error")

tests := []struct {
name string
yk pivKey
want string
wantErr bool
}{
{"ok", yk1, "112233", false},
{"fail", yk2, "", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
k := &YubiKey{
yk: tt.yk,
}
got, err := k.Serial()
if (err != nil) != tt.wantErr {
t.Errorf("YubiKey.Serial() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("YubiKey.Serial() = %v, want %v", got, tt.want)
}
})
}
}

func TestYubiKey_Close(t *testing.T) {
yk1 := newStubPivKey(t, ECDSA)
yk2 := newStubPivKey(t, RSA)
Expand Down Expand Up @@ -1061,7 +1103,7 @@ func TestYubiKey_Close(t *testing.T) {
}
}

func Test_getSerialNumber(t *testing.T) {
func Test_getAttestedSerial(t *testing.T) {
serialNumber, err := asn1.Marshal(112233)
if err != nil {
t.Fatal(err)
Expand Down Expand Up @@ -1107,8 +1149,8 @@ func Test_getSerialNumber(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := getSerialNumber(tt.args.cert); got != tt.want {
t.Errorf("getSerialNumber() = %v, want %v", got, tt.want)
if got := getAttestedSerial(tt.args.cert); got != tt.want {
t.Errorf("getAttestedSerial() = %v, want %v", got, tt.want)
}
})
}
Expand Down