Skip to content

Commit

Permalink
Add slot name to Attestation struct
Browse files Browse the repository at this point in the history
In some use cases, knowing what slot the attested key resides in
can be useful for determining whether to issue a certificate for
the key.

Signed-off-by: James Alseth <[email protected]>
  • Loading branch information
James Alseth authored and ericchiang committed Jul 9, 2021
1 parent b594802 commit 23adea5
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 2 deletions.
52 changes: 50 additions & 2 deletions piv/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import (
"fmt"
"io"
"math/big"
"strconv"
"strings"
)

// errMismatchingAlgorithms is returned when a cryptographic operation
Expand Down Expand Up @@ -86,8 +88,13 @@ const (
FormfactorUSBCLightningKeychain
)

// Attestation returns additional information about a key attested to be on a
// card.
// Prefix in the x509 Subject Common Name for YubiKey attestations
// https://developers.yubico.com/PIV/Introduction/PIV_attestation.html
const yubikeySubjectCNPrefix = "YubiKey PIV Attestation "

// Attestation returns additional information about a key attested to be generated
// on a card. See https://developers.yubico.com/PIV/Introduction/PIV_attestation.html
// for more information.
type Attestation struct {
// Version of the YubiKey's firmware.
Version Version
Expand All @@ -102,6 +109,11 @@ type Attestation struct {
PINPolicy PINPolicy
// TouchPolicy set on the slot.
TouchPolicy TouchPolicy

// Slot is the inferred slot the attested key resides in based on the
// common name in the attestation. If the slot cannot be determined,
// this field will be an empty struct.
Slot Slot
}

func (a *Attestation) addExt(e pkix.Extension) error {
Expand Down Expand Up @@ -210,9 +222,40 @@ func parseAttestation(slotCert *x509.Certificate) (*Attestation, error) {
return nil, fmt.Errorf("parsing extension: %v", err)
}
}

slot, ok := parseSlot(slotCert.Subject.CommonName)
if ok {
a.Slot = slot
}

return &a, nil
}

func parseSlot(commonName string) (Slot, bool) {
if !strings.HasPrefix(commonName, yubikeySubjectCNPrefix) {
return Slot{}, false
}

slotName := strings.TrimPrefix(commonName, yubikeySubjectCNPrefix)
key, err := strconv.ParseUint(slotName, 16, 32)
if err != nil {
return Slot{}, false
}

switch uint32(key) {
case SlotAuthentication.Key:
return SlotAuthentication, true
case SlotSignature.Key:
return SlotSignature, true
case SlotCardAuthentication.Key:
return SlotCardAuthentication, true
case SlotKeyManagement.Key:
return SlotKeyManagement, true
}

return RetiredKeyManagementSlot(uint32(key))
}

// yubicoPIVCAPEM is the PEM encoded attestation certificate used by Yubico.
//
// https://developers.yubico.com/PIV/Introduction/PIV_attestation.html
Expand Down Expand Up @@ -298,6 +341,11 @@ func RetiredKeyManagementSlot(key uint32) (Slot, bool) {
return slot, ok
}

// String returns the two-character hex representation of the slot
func (s Slot) String() string {
return strconv.FormatUint(uint64(s.Key), 16)
}

// Algorithm represents a specific algorithm and bit size supported by the PIV
// specification.
type Algorithm int
Expand Down
54 changes: 54 additions & 0 deletions piv/key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,12 @@ func TestYubiKeyAttestation(t *testing.T) {
if a.Version != yk.Version() {
t.Errorf("attestation version got=%#v, wanted=%#v", a.Version, yk.Version())
}
if a.Slot != SlotAuthentication {
t.Errorf("attested slot got=%v, wanted=%v", a.Slot, SlotAuthentication)
}
if a.Slot.String() != "9a" {
t.Errorf("attested slot name got=%s, wanted=%s", a.Slot.String(), "9a")
}
}

func TestYubiKeyStoreCertificate(t *testing.T) {
Expand Down Expand Up @@ -842,3 +848,51 @@ func TestSetECDSAPrivateKey(t *testing.T) {
})
}
}

func TestParseSlot(t *testing.T) {
tests := []struct {
name string
cn string
ok bool
slot Slot
}{
{
name: "Missing Yubico PIV Prefix",
cn: "invalid",
ok: false,
slot: Slot{},
},
{
name: "Invalid Slot Name",
cn: yubikeySubjectCNPrefix + "xy",
ok: false,
slot: Slot{},
},
{
name: "Valid -- SlotAuthentication",
cn: yubikeySubjectCNPrefix + "9a",
ok: true,
slot: SlotAuthentication,
},
{
name: "Valid -- Retired Management Key",
cn: yubikeySubjectCNPrefix + "89",
ok: true,
slot: retiredKeyManagementSlots[uint32(137)],
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
slot, ok := parseSlot(test.cn)

if ok != test.ok {
t.Errorf("ok status returned %v, expected %v", ok, test.ok)
}

if slot != test.slot {
t.Errorf("returned slot %+v did not match expected %+v", slot, test.slot)
}
})
}
}

0 comments on commit 23adea5

Please sign in to comment.