Skip to content

Commit 7b7a63c

Browse files
authored
Merge pull request #656 from smallstep/mariano/fix-655
Add support for imported keys
2 parents ecf9adb + 6634e86 commit 7b7a63c

File tree

2 files changed

+60
-9
lines changed

2 files changed

+60
-9
lines changed

kms/yubikey/yubikey.go

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ type pivKey interface {
4242
Certificate(slot piv.Slot) (*x509.Certificate, error)
4343
SetCertificate(key []byte, slot piv.Slot, cert *x509.Certificate) error
4444
GenerateKey(key []byte, slot piv.Slot, opts piv.Key) (crypto.PublicKey, error)
45+
KeyInfo(slot piv.Slot) (piv.KeyInfo, error)
4546
PrivateKey(slot piv.Slot, public crypto.PublicKey, auth piv.KeyAuth) (crypto.PrivateKey, error)
4647
Attest(slot piv.Slot) (*x509.Certificate, error)
4748
Serial() (uint32, error)
@@ -381,17 +382,33 @@ func (k *YubiKey) Close() error {
381382
return nil
382383
}
383384

384-
// getPublicKey returns the public key on a slot. First it attempts to do
385-
// attestation to get a certificate with the public key in it, if this succeeds
386-
// means that the key was generated in the device. If not we'll try to get the
387-
// key from a stored certificate in the same slot.
385+
// getPublicKey returns the public key on a slot. First it attempts to use
386+
// KeyInfo to get the public key, then tries to do attestation to get a
387+
// certificate with the public key in it, if this succeeds means that the key
388+
// was generated in the device. If not we'll try to get the key from a stored
389+
// certificate in the same slot.
388390
func (k *YubiKey) getPublicKey(slot piv.Slot) (crypto.PublicKey, error) {
389-
cert, err := k.yk.Attest(slot)
391+
// YubiKey >= 5.3.0 (generated and imported keys)
392+
if ki, err := k.yk.KeyInfo(slot); err == nil && ki.PublicKey != nil {
393+
return ki.PublicKey, nil
394+
}
395+
396+
// YubiKey >= 4.3.0 (generated keys)
397+
if cert, err := k.yk.Attest(slot); err == nil {
398+
return cert.PublicKey, nil
399+
}
400+
401+
// Fallback to certificate in slot (generated and imported)
402+
cert, err := k.yk.Certificate(slot)
390403
if err != nil {
391-
if cert, err = k.yk.Certificate(slot); err != nil {
392-
return nil, errors.Wrap(err, "error retrieving public key")
404+
if errors.Is(err, piv.ErrNotFound) {
405+
return nil, apiv1.NotFoundError{
406+
Message: err.Error(),
407+
}
393408
}
409+
return nil, fmt.Errorf("error retrieving public key: %w", err)
394410
}
411+
395412
return cert.PublicKey, nil
396413
}
397414

kms/yubikey/yubikey_test.go

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"crypto/x509/pkix"
1818
"encoding/asn1"
1919
"errors"
20+
"fmt"
2021
"reflect"
2122
"sync"
2223
"testing"
@@ -33,6 +34,7 @@ type stubPivKey struct {
3334
attestCA *minica.CA
3435
attestSigner privateKey
3536
userCA *minica.CA
37+
keyInfoMap map[piv.Slot]piv.KeyInfo
3638
attestMap map[piv.Slot]*x509.Certificate
3739
certMap map[piv.Slot]*x509.Certificate
3840
signerMap map[piv.Slot]interface{}
@@ -73,8 +75,10 @@ func newStubPivKey(t *testing.T, alg symmetricAlgorithm) *stubPivKey {
7375
t.Fatal(err)
7476
}
7577

78+
var keyInfoAlgo piv.Algorithm
7679
switch alg {
7780
case ECDSA:
81+
keyInfoAlgo = piv.AlgorithmEC256
7882
attSigner, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
7983
if err != nil {
8084
t.Fatal(err)
@@ -84,6 +88,7 @@ func newStubPivKey(t *testing.T, alg symmetricAlgorithm) *stubPivKey {
8488
t.Fatal(err)
8589
}
8690
case RSA:
91+
keyInfoAlgo = piv.AlgorithmRSA2048
8792
attSigner, err = rsa.GenerateKey(rand.Reader, rsaKeySize)
8893
if err != nil {
8994
t.Fatal(err)
@@ -124,6 +129,15 @@ func newStubPivKey(t *testing.T, alg symmetricAlgorithm) *stubPivKey {
124129
attestCA: attestCA,
125130
attestSigner: attSigner,
126131
userCA: userCA,
132+
keyInfoMap: map[piv.Slot]piv.KeyInfo{
133+
piv.SlotKeyManagement: {
134+
PublicKey: attSigner.Public(),
135+
Algorithm: keyInfoAlgo,
136+
PINPolicy: piv.PINPolicyOnce,
137+
TouchPolicy: piv.TouchPolicyCached,
138+
Origin: piv.OriginGenerated,
139+
}, // 9d
140+
},
127141
attestMap: map[piv.Slot]*x509.Certificate{
128142
piv.SlotAuthentication: attCert, // 9a
129143
},
@@ -140,10 +154,21 @@ func newStubPivKey(t *testing.T, alg symmetricAlgorithm) *stubPivKey {
140154
}
141155
}
142156

157+
func (s *stubPivKey) KeyInfo(slot piv.Slot) (piv.KeyInfo, error) {
158+
keyInfo, ok := s.keyInfoMap[slot]
159+
if !ok {
160+
return piv.KeyInfo{}, errors.New("public key not found")
161+
}
162+
return keyInfo, nil
163+
}
164+
143165
func (s *stubPivKey) Certificate(slot piv.Slot) (*x509.Certificate, error) {
144166
cert, ok := s.certMap[slot]
145167
if !ok {
146-
return nil, errors.New("certificate not found")
168+
if slot == slotMapping["82"] {
169+
return nil, errors.New("command failed: some error")
170+
}
171+
return nil, fmt.Errorf("command failed: %w", piv.ErrNotFound)
147172
}
148173
return cert, nil
149174
}
@@ -523,13 +548,22 @@ func TestYubiKey_GetPublicKey(t *testing.T) {
523548
want crypto.PublicKey
524549
wantErr bool
525550
}{
526-
{"ok", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.GetPublicKeyRequest{
551+
{"ok with keyInfo", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.GetPublicKeyRequest{
552+
Name: "yubikey:slot-id=9d",
553+
}}, yk.keyInfoMap[piv.SlotKeyManagement].PublicKey, false},
554+
{"ok with Attest", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.GetPublicKeyRequest{
555+
Name: "yubikey:slot-id=9a",
556+
}}, yk.attestMap[piv.SlotAuthentication].PublicKey, false},
557+
{"ok with certificate", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.GetPublicKeyRequest{
527558
Name: "yubikey:slot-id=9c",
528559
}}, yk.certMap[piv.SlotSignature].PublicKey, false},
529560
{"fail getSlot", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.GetPublicKeyRequest{
530561
Name: "slot-id=9c",
531562
}}, nil, true},
532563
{"fail getPublicKey", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.GetPublicKeyRequest{
564+
Name: "yubikey:slot-id=82",
565+
}}, nil, true},
566+
{"fail getPublicKey not found", fields{yk, "123456", piv.DefaultManagementKey}, args{&apiv1.GetPublicKeyRequest{
533567
Name: "yubikey:slot-id=85",
534568
}}, nil, true},
535569
}

0 commit comments

Comments
 (0)