keycard-go/apdu/utils.go

118 lines
2.2 KiB
Go

package apdu
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
)
var (
ErrUnsupportedLenth80 = errors.New("length cannot be 0x80")
ErrLengthTooBig = errors.New("length cannot be more than 3 bytes")
)
// ErrTagNotFound is an error returned if a tag is not found in a TLV sequence.
type ErrTagNotFound struct {
tag uint8
}
// Error implements the error interface
func (e *ErrTagNotFound) Error() string {
return fmt.Sprintf("tag %x not found", e.tag)
}
// FindTag searches for a tag value within a TLV sequence.
func FindTag(raw []byte, tags ...uint8) ([]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) {
return findTag(raw, n, tags...)
}
func findTag(raw []byte, occurrence int, tags ...uint8) ([]byte, error) {
if len(tags) == 0 {
return raw, nil
}
target := tags[0]
buf := bytes.NewBuffer(raw)
var (
tag uint8
length uint32
err error
)
for {
tag, err = buf.ReadByte()
switch {
case err == io.EOF:
return []byte{}, &ErrTagNotFound{target}
case err != nil:
return nil, err
}
length, buf, err = parseLength(buf)
if err != nil {
return nil, err
}
data := make([]byte, length)
if length != 0 {
_, err = buf.Read(data)
if err != nil {
return nil, err
}
}
if tag == target {
// if it's the last tag in the search path, we start counting the occurrences
if len(tags) == 1 && occurrence > 0 {
occurrence--
continue
}
if len(tags) == 1 {
return data, nil
}
return findTag(data, occurrence, tags[1:]...)
}
}
}
func parseLength(buf *bytes.Buffer) (uint32, *bytes.Buffer, error) {
length, err := buf.ReadByte()
if err != nil {
return 0, nil, err
}
if length == 0x80 {
return 0, nil, ErrUnsupportedLenth80
}
if length > 0x80 {
lengthSize := length - 0x80
if lengthSize > 3 {
return 0, nil, ErrLengthTooBig
}
data := make([]byte, lengthSize)
_, err = buf.Read(data)
if err != nil {
return 0, nil, err
}
num := make([]byte, 4)
copy(num[4-lengthSize:], data)
return binary.BigEndian.Uint32(num), buf, nil
}
return uint32(length), buf, nil
}