From 448e4918b8e948527369decb96ed3bf5c40e1119 Mon Sep 17 00:00:00 2001 From: Andrea Franz Date: Thu, 18 Apr 2019 01:36:40 +0200 Subject: [PATCH] parse multi bytes ber-tlv tags --- apdu/utils.go | 42 ++++++++++++++++---- apdu/utils_test.go | 44 ++++++++++++++++----- globalplatform/command_set.go | 8 +++- types/application_info.go | 12 +++--- types/application_status.go | 8 ++-- types/card_status.go | 73 +++++++++++++++++++++++++++++++++++ types/signature.go | 6 +-- 7 files changed, 161 insertions(+), 32 deletions(-) create mode 100644 types/card_status.go diff --git a/apdu/utils.go b/apdu/utils.go index b07bcd2..f0a9394 100644 --- a/apdu/utils.go +++ b/apdu/utils.go @@ -8,6 +8,8 @@ import ( "io" ) +type Tag []byte + var ( ErrUnsupportedLenth80 = errors.New("length cannot be 0x80") ErrLengthTooBig = errors.New("length cannot be more than 3 bytes") @@ -15,7 +17,7 @@ var ( // ErrTagNotFound is an error returned if a tag is not found in a TLV sequence. type ErrTagNotFound struct { - tag uint8 + tag Tag } // Error implements the error interface @@ -24,16 +26,16 @@ func (e *ErrTagNotFound) Error() string { } // FindTag searches for a tag value within a TLV sequence. -func FindTag(raw []byte, tags ...uint8) ([]byte, error) { +func FindTag(raw []byte, tags ...Tag) ([]byte, error) { return findTag(raw, 0, tags...) } // FindTagN searches for a tag value within a TLV sequence and returns the n occurrence -func FindTagN(raw []byte, n int, tags ...uint8) ([]byte, error) { +func FindTagN(raw []byte, n int, tags ...Tag) ([]byte, error) { return findTag(raw, n, tags...) } -func findTag(raw []byte, occurrence int, tags ...uint8) ([]byte, error) { +func findTag(raw []byte, occurrence int, tags ...Tag) ([]byte, error) { if len(tags) == 0 { return raw, nil } @@ -42,13 +44,13 @@ func findTag(raw []byte, occurrence int, tags ...uint8) ([]byte, error) { buf := bytes.NewBuffer(raw) var ( - tag uint8 + tag Tag length uint32 err error ) for { - tag, err = buf.ReadByte() + tag, buf, err = parseTag(buf) switch { case err == io.EOF: return []byte{}, &ErrTagNotFound{target} @@ -69,7 +71,7 @@ func findTag(raw []byte, occurrence int, tags ...uint8) ([]byte, error) { } } - if tag == target { + if bytes.Equal(tag, target) { // if it's the last tag in the search path, we start counting the occurrences if len(tags) == 1 && occurrence > 0 { occurrence-- @@ -115,3 +117,29 @@ func parseLength(buf *bytes.Buffer) (uint32, *bytes.Buffer, error) { return uint32(length), buf, nil } + +func parseTag(buf *bytes.Buffer) (Tag, *bytes.Buffer, error) { + tag := make(Tag, 0) + b, err := buf.ReadByte() + if err != nil { + return nil, nil, err + } + + tag = append(tag, b) + if b&0x1F != 0x1F { + return tag, buf, nil + } + + for { + b, err = buf.ReadByte() + if err != nil { + return nil, nil, err + } + + tag = append(tag, b) + + if b&0x80 != 0x80 { + return tag, buf, nil + } + } +} diff --git a/apdu/utils_test.go b/apdu/utils_test.go index 32656d0..fc7360a 100644 --- a/apdu/utils_test.go +++ b/apdu/utils_test.go @@ -6,6 +6,7 @@ import ( "github.com/status-im/keycard-go/hexutils" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestFindTag(t *testing.T) { @@ -16,31 +17,31 @@ func TestFindTag(t *testing.T) { data := hexutils.HexToBytes("C1 02 BB CC C2 04 C3 02 11 22 C3 02 88 99") - tagData, err = FindTag(data, uint8(0xC1)) + tagData, err = FindTag(data, Tag{0xC1}) assert.NoError(t, err) assert.Equal(t, "BB CC", hexutils.BytesToHexWithSpaces(tagData)) - tagData, err = FindTag(data, uint8(0xC2)) + tagData, err = FindTag(data, Tag{0xC2}) assert.NoError(t, err) assert.Equal(t, "C3 02 11 22", hexutils.BytesToHexWithSpaces(tagData)) - tagData, err = FindTag(data, uint8(0xC3)) + tagData, err = FindTag(data, Tag{0xC3}) assert.NoError(t, err) assert.Equal(t, "88 99", hexutils.BytesToHexWithSpaces(tagData)) - tagData, err = FindTag(data, uint8(0xC2), uint8(0xC3)) + tagData, err = FindTag(data, Tag{0xC2}, Tag{0xC3}) assert.NoError(t, err) assert.Equal(t, "11 22", hexutils.BytesToHexWithSpaces(tagData)) // tag not found data = hexutils.HexToBytes("C1 00") - _, err = FindTag(data, uint8(0xC2)) - assert.Equal(t, &ErrTagNotFound{uint8(0xC2)}, err) + _, err = FindTag(data, Tag{0xC2}) + assert.Equal(t, &ErrTagNotFound{Tag{0xC2}}, err) // sub-tag not found data = hexutils.HexToBytes("C1 02 C2 00") - _, err = FindTag(data, uint8(0xC1), uint8(0xC3)) - assert.Equal(t, &ErrTagNotFound{uint8(0xC3)}, err) + _, err = FindTag(data, Tag{0xC1}, Tag{0xC3}) + assert.Equal(t, &ErrTagNotFound{Tag{0xC3}}, err) } func TestParseLength(t *testing.T) { @@ -101,11 +102,34 @@ func TestParseLength(t *testing.T) { func TestFindTagN(t *testing.T) { data := hexutils.HexToBytes("0A 01 A1 0A 01 A2") - tagData, err := FindTagN(data, 0, uint8(0x0A)) + tagData, err := FindTagN(data, 0, Tag{0x0A}) assert.NoError(t, err) assert.Equal(t, "A1", hexutils.BytesToHexWithSpaces(tagData)) - tagData, err = FindTagN(data, 1, uint8(0x0A)) + tagData, err = FindTagN(data, 1, Tag{0x0A}) assert.NoError(t, err) assert.Equal(t, "A2", hexutils.BytesToHexWithSpaces(tagData)) } + +func TestParseTag(t *testing.T) { + scenarios := []struct { + rawTag []byte + expectedTag Tag + }{ + { + rawTag: []byte{0x01, 0x02}, + expectedTag: Tag{0x01}, + }, + { + rawTag: []byte{0x9F, 0x70, 0x01}, + expectedTag: Tag{0x9f, 0x70}, + }, + } + + for _, s := range scenarios { + buf := bytes.NewBuffer(s.rawTag) + tag, _, err := parseTag(buf) + require.Nil(t, err) + assert.Equal(t, s.expectedTag, tag) + } +} diff --git a/globalplatform/command_set.go b/globalplatform/command_set.go index bc01e04..c7e2e75 100644 --- a/globalplatform/command_set.go +++ b/globalplatform/command_set.go @@ -143,10 +143,14 @@ func (cs *CommandSet) InstallForInstall(packageAID, appletAID, instanceAID, para return cs.checkOK(resp, err) } -func (cs *CommandSet) GetStatus() error { +func (cs *CommandSet) GetStatus() (*types.CardStatus, error) { cmd := NewCommandGetStatus([]byte{}, P1GetStatusIssuerSecurityDomain) resp, err := cs.sc.Send(cmd) - return cs.checkOK(resp, err) + if err = cs.checkOK(resp, err); err != nil { + return nil, err + } + + return types.ParseCardStatus(resp.Data) } func (cs *CommandSet) Channel() types.Channel { diff --git a/types/application_info.go b/types/application_info.go index 1c2bf9d..7eed222 100644 --- a/types/application_info.go +++ b/types/application_info.go @@ -84,33 +84,33 @@ func ParseApplicationInfo(data []byte) (*ApplicationInfo, error) { return nil, ErrWrongApplicationInfoTemplate } - instanceUID, err := apdu.FindTag(data, TagApplicationInfoTemplate, uint8(0x8F)) + instanceUID, err := apdu.FindTag(data, apdu.Tag{TagApplicationInfoTemplate}, apdu.Tag{0x8F}) if err != nil { return nil, err } - pubKey, err := apdu.FindTag(data, TagApplicationInfoTemplate, uint8(0x80)) + pubKey, err := apdu.FindTag(data, apdu.Tag{TagApplicationInfoTemplate}, apdu.Tag{0x80}) if err != nil { return nil, err } - appVersion, err := apdu.FindTag(data, TagApplicationInfoTemplate, uint8(0x02)) + appVersion, err := apdu.FindTag(data, apdu.Tag{TagApplicationInfoTemplate}, apdu.Tag{0x02}) if err != nil { return nil, err } - availableSlots, err := apdu.FindTagN(data, 1, TagApplicationInfoTemplate, uint8(0x02)) + availableSlots, err := apdu.FindTagN(data, 1, apdu.Tag{TagApplicationInfoTemplate}, apdu.Tag{0x02}) if err != nil { return nil, err } - keyUID, err := apdu.FindTagN(data, 0, TagApplicationInfoTemplate, uint8(0x8E)) + keyUID, err := apdu.FindTagN(data, 0, apdu.Tag{TagApplicationInfoTemplate}, apdu.Tag{0x8E}) if err != nil { return nil, err } capabilities := CapabilityAll - capabilitiesBytes, err := apdu.FindTag(data, TagApplicationInfoCapabilities) + capabilitiesBytes, err := apdu.FindTag(data, apdu.Tag{TagApplicationInfoCapabilities}) if err == nil && len(capabilitiesBytes) > 0 { capabilities = Capability(capabilitiesBytes[0]) } diff --git a/types/application_status.go b/types/application_status.go index 12eece3..e3ff8c3 100644 --- a/types/application_status.go +++ b/types/application_status.go @@ -20,22 +20,22 @@ type ApplicationStatus struct { } func ParseApplicationStatus(data []byte) (*ApplicationStatus, error) { - tpl, err := apdu.FindTag(data, TagApplicationStatusTemplate) + tpl, err := apdu.FindTag(data, apdu.Tag{TagApplicationStatusTemplate}) if err != nil { return parseKeyPathStatus(data) } appStatus := &ApplicationStatus{} - if pinRetryCount, err := apdu.FindTag(tpl, uint8(0x02)); err == nil && len(pinRetryCount) == 1 { + if pinRetryCount, err := apdu.FindTag(tpl, apdu.Tag{0x02}); err == nil && len(pinRetryCount) == 1 { appStatus.PinRetryCount = int(pinRetryCount[0]) } - if pukRetryCount, err := apdu.FindTagN(tpl, 1, uint8(0x02)); err == nil && len(pukRetryCount) == 1 { + if pukRetryCount, err := apdu.FindTagN(tpl, 1, apdu.Tag{0x02}); err == nil && len(pukRetryCount) == 1 { appStatus.PUKRetryCount = int(pukRetryCount[0]) } - if keyInitialized, err := apdu.FindTag(tpl, uint8(0x01)); err == nil { + if keyInitialized, err := apdu.FindTag(tpl, apdu.Tag{0x01}); err == nil { if bytes.Equal(keyInitialized, []byte{0xFF}) { appStatus.KeyInitialized = true } diff --git a/types/card_status.go b/types/card_status.go new file mode 100644 index 0000000..39d6fab --- /dev/null +++ b/types/card_status.go @@ -0,0 +1,73 @@ +package types + +import ( + "fmt" + + "github.com/status-im/keycard-go/apdu" +) + +type lifeCycle byte + +var ( + TagGetStatusTemplate = apdu.Tag{0xE3} + TagGetStatusLifeCycleState = apdu.Tag{0x9F, 0x70} +) + +const ( + LifeCycleOpReady lifeCycle = 0x01 + LifeCycleInitialized = 0x07 + LifeCycleSecured = 0x0F + LifeCycleCardLocked = 0x7F + LifeCycleTerminated = 0xFF +) + +func (lc lifeCycle) String() string { + switch lc { + case LifeCycleOpReady: + return "OP_READY" + case LifeCycleInitialized: + return "INITIALIZED" + case LifeCycleSecured: + return "SECURED" + case LifeCycleCardLocked: + return "CARD_LOCKED" + case LifeCycleTerminated: + return "TERMINATED" + default: + return "UNKNOWN" + } +} + +type ErrInvalidLifeCycleValue struct { + lc []byte +} + +func (e *ErrInvalidLifeCycleValue) Error() string { + return fmt.Sprintf("life cycle value must be 1 byte. got %d byte: %x", len(e.lc), e.lc) +} + +type CardStatus struct { + lc lifeCycle +} + +func (cs *CardStatus) LifeCycle() string { + return cs.lc.String() +} + +func ParseCardStatus(data []byte) (*CardStatus, error) { + tpl, err := apdu.FindTag(data, TagGetStatusTemplate) + if err != nil { + return nil, err + } + + lc, err := apdu.FindTag(tpl, TagGetStatusLifeCycleState) + if err != nil { + return nil, err + } + + if len(lc) != 1 { + return nil, &ErrInvalidLifeCycleValue{lc} + } + + return &CardStatus{lifeCycle(lc[0])}, nil +} diff --git a/types/signature.go b/types/signature.go index bd435e7..d54f218 100644 --- a/types/signature.go +++ b/types/signature.go @@ -19,12 +19,12 @@ type Signature struct { } func ParseSignature(message, resp []byte) (*Signature, error) { - pubKey, err := apdu.FindTag(resp, TagSignatureTemplate, uint8(0x80)) + pubKey, err := apdu.FindTag(resp, apdu.Tag{TagSignatureTemplate}, apdu.Tag{0x80}) if err != nil { return nil, err } - r, err := apdu.FindTagN(resp, 0, TagSignatureTemplate, uint8(0x30), uint8(0x02)) + r, err := apdu.FindTagN(resp, 0, apdu.Tag{TagSignatureTemplate}, apdu.Tag{0x30}, apdu.Tag{0x02}) if err != nil { return nil, err } @@ -33,7 +33,7 @@ func ParseSignature(message, resp []byte) (*Signature, error) { r = r[len(r)-32:] } - s, err := apdu.FindTagN(resp, 1, TagSignatureTemplate, uint8(0x30), uint8(0x02)) + s, err := apdu.FindTagN(resp, 1, apdu.Tag{TagSignatureTemplate}, apdu.Tag{0x30}, apdu.Tag{0x02}) if err != nil { return nil, err }