From 76725db9bf1754d80d3b79bb36365baf3c4e2e5c Mon Sep 17 00:00:00 2001 From: JC Martin Date: Sun, 17 May 2020 09:55:01 -0700 Subject: [PATCH 1/5] Initial CHUID reading --- piv/piv.go | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/piv/piv.go b/piv/piv.go index 9f381d3..b96fd3d 100644 --- a/piv/piv.go +++ b/piv/piv.go @@ -846,3 +846,38 @@ func ykSetProtectedMetadata(tx *scTx, key [24]byte, m *Metadata) error { } return nil } + +// Card Holder Unique Identifier +type CardId []byte + +func (yk *YubiKey) CardId() (CardId, error) { + return ykGetCardId(yk.tx) + +} +func ykGetCardId(tx *scTx) (CardId, error) { + // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=17 + // OID for CardId is 5FC102 + + cmd := apdu{ + instruction: insGetData, + param1: 0x3f, + param2: 0xff, + data: []byte{ + 0x5c, // Tag list + 0x03, + 0x5f, + 0xc1, + 0x02, + }, + } + resp, err := tx.Transmit(cmd) + 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 { + return nil, fmt.Errorf("unmarshaling response: %v", err) + } + return obj, nil +} From 7d1c44c9fffebaaea93c99c2e68612efb7357637 Mon Sep 17 00:00:00 2001 From: JC Martin Date: Sun, 17 May 2020 14:32:33 -0700 Subject: [PATCH 2/5] Add set CHUID --- piv/piv.go | 105 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 94 insertions(+), 11 deletions(-) diff --git a/piv/piv.go b/piv/piv.go index b96fd3d..97f758e 100644 --- a/piv/piv.go +++ b/piv/piv.go @@ -847,14 +847,47 @@ func ykSetProtectedMetadata(tx *scTx, key [24]byte, m *Metadata) error { return nil } -// Card Holder Unique Identifier -type CardId []byte - -func (yk *YubiKey) CardId() (CardId, error) { - return ykGetCardId(yk.tx) - -} -func ykGetCardId(tx *scTx) (CardId, error) { +// CardID is the Card Holder Unique Identifier with settable GUID +// Raw contains the whole object +// GUID contains the Card Universally Unique Identifier. +type CardID struct { + Raw []byte + GUID [16]byte +} + +// CardID returns the card CHUID with the GUID extracted +func (yk *YubiKey) CardID() (CardID, error) { + return ykGetCardID(yk.tx) + +} + +/* + * From https://github.com/Yubico/yubico-piv-tool/blob/ebee7f63b85fe4373efc4d8d44cbe5fe321c158c/lib/util.c#L44 + * Format defined in SP-800-73-4, Appendix A, Table 9 + * + * FASC-N containing S9999F9999F999999F0F1F0000000000300001E encoded in + * 4-bit BCD with 1 bit parity. run through the tools/fasc.pl script to get + * bytes. This CHUID has an expiry of 2030-01-01. + * + * Defined fields: + * - 0x30: FASC-N (hard-coded) + * - 0x34: Card UUID / GUID (settable) + * - 0x35: Exp. Date (hard-coded) + * - 0x3e: Signature (hard-coded, empty) + * - 0xfe: Error Detection Code (hard-coded) + */ + +var chuidTemplate = []byte{ + 0x30, 0x19, 0xd4, 0xe7, 0x39, 0xda, 0x73, 0x9c, 0xed, 0x39, 0xce, 0x73, 0x9d, + 0x83, 0x68, 0x58, 0x21, 0x08, 0x42, 0x10, 0x84, 0x21, 0xc8, 0x42, 0x10, 0xc3, + 0xeb, 0x34, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x08, 0x32, 0x30, 0x33, 0x30, 0x30, + 0x31, 0x30, 0x31, 0x3e, 0x00, 0xfe, 0x00, +} + +const uuidOffset = 29 + +func ykGetCardID(tx *scTx) (id CardID, err error) { // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=17 // OID for CardId is 5FC102 @@ -872,12 +905,62 @@ func ykGetCardId(tx *scTx) (CardId, error) { } resp, err := tx.Transmit(cmd) if err != nil { - return nil, fmt.Errorf("command failed: %w", err) + return id, 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 { - return nil, fmt.Errorf("unmarshaling response: %v", err) + return id, fmt.Errorf("unmarshaling response: %v", err) } - return obj, nil + id.Raw = obj + if obj[27] == 0x34 { + endPos := uuidOffset + int(obj[28]) + copy(id.GUID[:], obj[uuidOffset:endPos]) + } + return +} + +// SetCardID initialize the CHUID card object using a predefined template defined as +// * Defined fields: +// - 0x30: FASC-N (hard-coded) +// - 0x34: Card UUID / GUID (settable) +// - 0x35: Exp. Date (hard-coded) +// - 0x3e: Signature (hard-coded, empty) +// - 0xfe: Error Detection Code (hard-coded) +func (yk *YubiKey) SetCardID(GUID [16]byte, key [24]byte) (CardID, error) { + return ykSetCardID(yk.tx, key, GUID) + +} + +func ykSetCardID(tx *scTx, key [24]byte, guid [16]byte) (id CardID, err error) { + + id.Raw = make([]byte, len(chuidTemplate)) + copy(id.Raw, chuidTemplate) + copy(id.Raw[uuidOffset:], guid[:]) + + data := append([]byte{ + 0x5c, // Tag list + 0x03, + 0x5f, + 0xc1, + 0x02, + }, marshalASN1(0x53, id.Raw)...) + + cmd := apdu{ + instruction: insPutData, + param1: 0x3f, + param2: 0xff, + data: data, + } + + if err := ykAuthenticate(tx, key, rand.Reader); err != nil { + return id, fmt.Errorf("authenticating with key: %w", err) + } + + _, err = tx.Transmit(cmd) + if err != nil { + return id, fmt.Errorf("command failed: %w", err) + } + + return } From efc9245ae1695743b43aa12c85abb612f1e4798b Mon Sep 17 00:00:00 2001 From: JC Martin Date: Mon, 18 May 2020 08:31:07 -0700 Subject: [PATCH 3/5] Added CardID test --- piv/piv_test.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/piv/piv_test.go b/piv/piv_test.go index a568fdc..4023f00 100644 --- a/piv/piv_test.go +++ b/piv/piv_test.go @@ -471,3 +471,30 @@ func TestMetadataAdditoinalFields(t *testing.T) { t.Errorf("(*Metadata.marshal, got=0x%x, want=0x%x", got, want) } } + +func TestYubiKeyCardId(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + yk, close := newTestYubiKey(t) + defer close() + if err := yk.Reset(); err != nil { + t.Fatalf("resetting yubikey: %v", err) + } + if _, err := yk.CardID(); !errors.Is(err, ErrNotFound) { + t.Fatalf("expecting not found chuid") + } + var guid = [16]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0xff} + _, err := yk.SetCardID(guid, DefaultManagementKey) + if err != nil { + t.Fatal(err) + } + chuid, err := yk.CardID() + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(guid[:], chuid.GUID[:]) { + t.Errorf("(*CardID, got=0x%x, want=0x%x", chuid.GUID, guid) + } +} From 8d2016b52d67b8ed8c581d15c841ab6aac106f33 Mon Sep 17 00:00:00 2001 From: JC Martin Date: Mon, 18 May 2020 10:14:25 -0700 Subject: [PATCH 4/5] Address review comments --- piv/piv.go | 60 ++++++++++++++++++++++++------------------------- piv/piv_test.go | 14 ++++++------ 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/piv/piv.go b/piv/piv.go index 97f758e..3a6547c 100644 --- a/piv/piv.go +++ b/piv/piv.go @@ -851,14 +851,14 @@ func ykSetProtectedMetadata(tx *scTx, key [24]byte, m *Metadata) error { // Raw contains the whole object // GUID contains the Card Universally Unique Identifier. type CardID struct { - Raw []byte + raw []byte GUID [16]byte } // CardID returns the card CHUID with the GUID extracted -func (yk *YubiKey) CardID() (CardID, error) { +// If the CHUID is not set, the returned error wraps ErrNotFound. +func (yk *YubiKey) CardID() (*CardID, error) { return ykGetCardID(yk.tx) - } /* @@ -887,7 +887,7 @@ var chuidTemplate = []byte{ const uuidOffset = 29 -func ykGetCardID(tx *scTx) (id CardID, err error) { +func ykGetCardID(tx *scTx) (*CardID, error) { // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=17 // OID for CardId is 5FC102 @@ -905,38 +905,38 @@ func ykGetCardID(tx *scTx) (id CardID, err error) { } resp, err := tx.Transmit(cmd) if err != nil { - return id, fmt.Errorf("command failed: %w", err) + 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 { - return id, fmt.Errorf("unmarshaling response: %v", err) + return nil, fmt.Errorf("unmarshaling response: %v", err) } - id.Raw = obj - if obj[27] == 0x34 { - endPos := uuidOffset + int(obj[28]) - copy(id.GUID[:], obj[uuidOffset:endPos]) + var id CardID + id.raw = obj + if len(obj) > uuidOffset { + // UUID is a TLV at position 27 with type equal to 0x34 + if obj[27] == 0x34 { + if obj[28] != 0x10 { + return nil, fmt.Errorf("unexpected uuid length value: %d", obj[28]) + } + endPos := uuidOffset + int(obj[28]) + copy(id.GUID[:], obj[uuidOffset:endPos]) + } } - return + return &id, nil } -// SetCardID initialize the CHUID card object using a predefined template defined as -// * Defined fields: -// - 0x30: FASC-N (hard-coded) -// - 0x34: Card UUID / GUID (settable) -// - 0x35: Exp. Date (hard-coded) -// - 0x3e: Signature (hard-coded, empty) -// - 0xfe: Error Detection Code (hard-coded) -func (yk *YubiKey) SetCardID(GUID [16]byte, key [24]byte) (CardID, error) { - return ykSetCardID(yk.tx, key, GUID) - +// SetCardID initialize the CHUID card object using a predefined template +func (yk *YubiKey) SetCardID(key [24]byte, id *CardID) error { + return ykSetCardID(yk.tx, key, id) } -func ykSetCardID(tx *scTx, key [24]byte, guid [16]byte) (id CardID, err error) { +func ykSetCardID(tx *scTx, key [24]byte, id *CardID) error { - id.Raw = make([]byte, len(chuidTemplate)) - copy(id.Raw, chuidTemplate) - copy(id.Raw[uuidOffset:], guid[:]) + id.raw = make([]byte, len(chuidTemplate)) + copy(id.raw, chuidTemplate) + copy(id.raw[uuidOffset:], id.GUID[:]) data := append([]byte{ 0x5c, // Tag list @@ -944,7 +944,7 @@ func ykSetCardID(tx *scTx, key [24]byte, guid [16]byte) (id CardID, err error) { 0x5f, 0xc1, 0x02, - }, marshalASN1(0x53, id.Raw)...) + }, marshalASN1(0x53, id.raw)...) cmd := apdu{ instruction: insPutData, @@ -954,13 +954,13 @@ func ykSetCardID(tx *scTx, key [24]byte, guid [16]byte) (id CardID, err error) { } if err := ykAuthenticate(tx, key, rand.Reader); err != nil { - return id, fmt.Errorf("authenticating with key: %w", err) + return fmt.Errorf("authenticating with key: %w", err) } - _, err = tx.Transmit(cmd) + _, err := tx.Transmit(cmd) if err != nil { - return id, fmt.Errorf("command failed: %w", err) + return fmt.Errorf("command failed: %w", err) } - return + return nil } diff --git a/piv/piv_test.go b/piv/piv_test.go index 4023f00..8b36845 100644 --- a/piv/piv_test.go +++ b/piv/piv_test.go @@ -484,17 +484,17 @@ func TestYubiKeyCardId(t *testing.T) { if _, err := yk.CardID(); !errors.Is(err, ErrNotFound) { t.Fatalf("expecting not found chuid") } - var guid = [16]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, - 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0xff} - _, err := yk.SetCardID(guid, DefaultManagementKey) - if err != nil { + var cardID = CardID{GUID: [16]byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0xff}, + } + if err := yk.SetCardID(DefaultManagementKey, &cardID); err != nil { t.Fatal(err) } - chuid, err := yk.CardID() + newCID, err := yk.CardID() if err != nil { t.Fatal(err) } - if !bytes.Equal(guid[:], chuid.GUID[:]) { - t.Errorf("(*CardID, got=0x%x, want=0x%x", chuid.GUID, guid) + if !bytes.Equal(newCID.GUID[:], cardID.GUID[:]) { + t.Errorf("(*CardID, got=0x%x, want=0x%x", newCID.GUID, cardID.GUID) } } From 7a2b76ee15765829704985942082a72e76f6080a Mon Sep 17 00:00:00 2001 From: JC Martin Date: Tue, 19 May 2020 16:42:50 -0700 Subject: [PATCH 5/5] Improve GUID logic --- piv/pcsc_unix.go | 2 +- piv/piv.go | 37 +++++++++++++++++++++++++++++-------- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/piv/pcsc_unix.go b/piv/pcsc_unix.go index f9261ee..69663f3 100644 --- a/piv/pcsc_unix.go +++ b/piv/pcsc_unix.go @@ -92,7 +92,7 @@ func (c *scContext) Connect(reader string) (*scHandle, error) { activeProtocol C.DWORD ) rc := C.SCardConnect(c.ctx, C.CString(reader), - C.SCARD_SHARE_EXCLUSIVE, C.SCARD_PROTOCOL_T1, + C.SCARD_SHARE_SHARED, C.SCARD_PROTOCOL_T1, &handle, &activeProtocol) if err := scCheck(rc); err != nil { return nil, err diff --git a/piv/piv.go b/piv/piv.go index 3a6547c..9459ed5 100644 --- a/piv/piv.go +++ b/piv/piv.go @@ -912,18 +912,39 @@ func ykGetCardID(tx *scTx) (*CardID, error) { if err != nil { return nil, fmt.Errorf("unmarshaling response: %v", err) } - var id CardID + + var ( + id CardID + guid []byte + v asn1.RawValue + ) id.raw = obj - if len(obj) > uuidOffset { - // UUID is a TLV at position 27 with type equal to 0x34 - if obj[27] == 0x34 { - if obj[28] != 0x10 { - return nil, fmt.Errorf("unexpected uuid length value: %d", obj[28]) + d := obj + // if the GUID is present assign it + for len(d) > 0 { + rest, err := asn1.Unmarshal(d, &v) + if err != nil { + return nil, fmt.Errorf("unmarshaling CHUID: %v", err) + } + // GUID is tag 34 / len of 16 + if bytes.HasPrefix(v.FullBytes, []byte{0x34}) { + if len(v.Bytes) != 16 { + return nil, fmt.Errorf("incorrect guid length") } - endPos := uuidOffset + int(obj[28]) - copy(id.GUID[:], obj[uuidOffset:endPos]) + guid = v.Bytes + break } + d = rest + } + // https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-73-4.pdf#page=19 + // The Global Unique Identification number (GUID) field must be present, and shall include a + // Card Universally Unique Identifier (UUID) + if len(guid) == 0 { + return nil, fmt.Errorf("missing guid") } + + copy(id.GUID[:], guid) + return &id, nil }