add support for metadata
This commit is contained in:
parent
b0e0482ba9
commit
e9b2ca2eac
|
@ -13,17 +13,17 @@ If you only need a tool to initialize your card, check out [keycard-cli](https:/
|
|||
- [x] PAIR
|
||||
- [x] UNPAIR
|
||||
- [x] GET STATUS
|
||||
- [ ] SET NDEF
|
||||
- [x] STORE DATA
|
||||
- [x] GET DATA
|
||||
- [x] VERIFY PIN
|
||||
- [x] CHANGE PIN
|
||||
- [ ] UNBLOCK PIN
|
||||
- [ ] LOAD KEY
|
||||
- [x] UNBLOCK PIN
|
||||
- [x] LOAD KEY
|
||||
- [x] DERIVE KEY
|
||||
- [x] GENERATE MNEMONIC
|
||||
- [x] REMOVE KEY
|
||||
- [x] GENERATE KEY
|
||||
- [x] INIT
|
||||
- [ ] DUPLICATE KEY
|
||||
- [x] SIGN
|
||||
- [ ] SET PINLESS PATH
|
||||
- [x] EXPORT KEY
|
||||
|
|
|
@ -50,7 +50,7 @@ func findTag(raw []byte, occurrence int, tags ...Tag) ([]byte, error) {
|
|||
)
|
||||
|
||||
for {
|
||||
tag, buf, err = parseTag(buf)
|
||||
tag, err = parseTag(buf)
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
return []byte{}, &ErrTagNotFound{target}
|
||||
|
@ -58,7 +58,7 @@ func findTag(raw []byte, occurrence int, tags ...Tag) ([]byte, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
length, buf, err = parseLength(buf)
|
||||
length, err = ParseLength(buf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -87,59 +87,83 @@ func findTag(raw []byte, occurrence int, tags ...Tag) ([]byte, error) {
|
|||
}
|
||||
}
|
||||
|
||||
func parseLength(buf *bytes.Buffer) (uint32, *bytes.Buffer, error) {
|
||||
func ParseLength(buf *bytes.Buffer) (uint32, error) {
|
||||
length, err := buf.ReadByte()
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if length == 0x80 {
|
||||
return 0, nil, ErrUnsupportedLenth80
|
||||
return 0, ErrUnsupportedLenth80
|
||||
}
|
||||
|
||||
if length > 0x80 {
|
||||
lengthSize := length - 0x80
|
||||
if lengthSize > 3 {
|
||||
return 0, nil, ErrLengthTooBig
|
||||
return 0, ErrLengthTooBig
|
||||
}
|
||||
|
||||
data := make([]byte, lengthSize)
|
||||
_, err = buf.Read(data)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
return 0, err
|
||||
}
|
||||
|
||||
num := make([]byte, 4)
|
||||
copy(num[4-lengthSize:], data)
|
||||
|
||||
return binary.BigEndian.Uint32(num), buf, nil
|
||||
return binary.BigEndian.Uint32(num), nil
|
||||
}
|
||||
|
||||
return uint32(length), buf, nil
|
||||
return uint32(length), nil
|
||||
}
|
||||
|
||||
func parseTag(buf *bytes.Buffer) (Tag, *bytes.Buffer, error) {
|
||||
func WriteLength(buf *bytes.Buffer, length uint32) {
|
||||
if length < 0x80 {
|
||||
buf.WriteByte(byte(length))
|
||||
} else if length < 0x100 {
|
||||
buf.WriteByte(0x81)
|
||||
buf.WriteByte(byte(length))
|
||||
} else if length < 0x10000 {
|
||||
buf.WriteByte(0x82)
|
||||
buf.WriteByte(byte(length >> 8))
|
||||
buf.WriteByte(byte(length))
|
||||
} else if length < 0x1000000 {
|
||||
buf.WriteByte(0x83)
|
||||
buf.WriteByte(byte(length >> 16))
|
||||
buf.WriteByte(byte(length >> 8))
|
||||
buf.WriteByte(byte(length))
|
||||
} else {
|
||||
buf.WriteByte(0x84)
|
||||
buf.WriteByte(byte(length >> 24))
|
||||
buf.WriteByte(byte(length >> 16))
|
||||
buf.WriteByte(byte(length >> 8))
|
||||
buf.WriteByte(byte(length))
|
||||
}
|
||||
}
|
||||
|
||||
func parseTag(buf *bytes.Buffer) (Tag, error) {
|
||||
tag := make(Tag, 0)
|
||||
b, err := buf.ReadByte()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tag = append(tag, b)
|
||||
if b&0x1F != 0x1F {
|
||||
return tag, buf, nil
|
||||
return tag, nil
|
||||
}
|
||||
|
||||
for {
|
||||
b, err = buf.ReadByte()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tag = append(tag, b)
|
||||
|
||||
if b&0x80 != 0x80 {
|
||||
return tag, buf, nil
|
||||
return tag, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,7 +89,7 @@ func TestParseLength(t *testing.T) {
|
|||
|
||||
for _, s := range scenarios {
|
||||
buf := bytes.NewBuffer(s.data)
|
||||
length, _, err := parseLength(buf)
|
||||
length, err := ParseLength(buf)
|
||||
if s.err == nil {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, s.expectedLength, length)
|
||||
|
@ -128,7 +128,7 @@ func TestParseTag(t *testing.T) {
|
|||
|
||||
for _, s := range scenarios {
|
||||
buf := bytes.NewBuffer(s.rawTag)
|
||||
tag, _, err := parseTag(buf)
|
||||
tag, err := parseTag(buf)
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, s.expectedTag, tag)
|
||||
}
|
||||
|
|
|
@ -301,15 +301,15 @@ func (cs *CommandSet) DeriveKey(path string) error {
|
|||
|
||||
func (cs *CommandSet) ExportKey(derive bool, makeCurrent bool, onlyPublic bool, path string) ([]byte, []byte, error) {
|
||||
var p1 uint8
|
||||
if derive == false {
|
||||
if !derive {
|
||||
p1 = P1ExportKeyCurrent
|
||||
} else if makeCurrent == false {
|
||||
} else if !makeCurrent {
|
||||
p1 = P1ExportKeyDerive
|
||||
} else {
|
||||
p1 = P1ExportKeyDeriveAndMakeCurrent
|
||||
}
|
||||
var p2 uint8
|
||||
if onlyPublic == true {
|
||||
if onlyPublic {
|
||||
p2 = P2ExportKeyPublicOnly
|
||||
} else {
|
||||
p2 = P2ExportKeyPrivateAndPublic
|
||||
|
@ -391,6 +391,22 @@ func (cs *CommandSet) LoadSeed(seed []byte) ([]byte, error) {
|
|||
return resp.Data, nil
|
||||
}
|
||||
|
||||
func (cs *CommandSet) GetData(typ uint8) ([]byte, error) {
|
||||
cmd := NewCommandGetData(typ)
|
||||
resp, err := cs.sc.Send(cmd)
|
||||
if err = cs.checkOK(resp, err); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp.Data, nil
|
||||
}
|
||||
|
||||
func (cs *CommandSet) StoreData(typ uint8, data []byte) error {
|
||||
cmd := NewCommandStoreData(typ, data)
|
||||
resp, err := cs.sc.Send(cmd)
|
||||
return cs.checkOK(resp, err)
|
||||
}
|
||||
|
||||
func (cs *CommandSet) mutualAuthenticate() error {
|
||||
data := make([]byte, 32)
|
||||
if _, err := rand.Read(data); err != nil {
|
||||
|
|
25
commands.go
25
commands.go
|
@ -26,8 +26,10 @@ const (
|
|||
InsExportKey = 0xC2
|
||||
InsSign = 0xC0
|
||||
InsSetPinlessPath = 0xC1
|
||||
InsGetData = 0xCA
|
||||
InsLoadKey = 0xD0
|
||||
InsGenerateMnemonic = 0xD2
|
||||
InsStoreData = 0xE2
|
||||
|
||||
P1PairingFirstStep = 0x00
|
||||
P1PairingFinalStep = 0x01
|
||||
|
@ -43,6 +45,9 @@ const (
|
|||
P1SignDerive = 0x01
|
||||
P1SignDeriveAndMakeCurrent = 0x02
|
||||
P1SignPinless = 0x03
|
||||
P1StoreDataPublic = 0x00
|
||||
P1StoreDataNDEF = 0x01
|
||||
P1StoreDataCash = 0x02
|
||||
P1ExportKeyCurrent = 0x00
|
||||
P1ExportKeyDerive = 0x01
|
||||
P1ExportKeyDeriveAndMakeCurrent = 0x02
|
||||
|
@ -333,6 +338,26 @@ func NewCommandSign(data []byte, p1 uint8, pathStr string) (*apdu.Command, error
|
|||
), nil
|
||||
}
|
||||
|
||||
func NewCommandGetData(typ uint8) *apdu.Command {
|
||||
return apdu.NewCommand(
|
||||
globalplatform.ClaGp,
|
||||
InsGetData,
|
||||
typ,
|
||||
0,
|
||||
[]byte{},
|
||||
)
|
||||
}
|
||||
|
||||
func NewCommandStoreData(typ uint8, data []byte) *apdu.Command {
|
||||
return apdu.NewCommand(
|
||||
globalplatform.ClaGp,
|
||||
InsStoreData,
|
||||
typ,
|
||||
0,
|
||||
data,
|
||||
)
|
||||
}
|
||||
|
||||
// Internal function. Get the type of starting point for the derivation path.
|
||||
// Used for both DeriveKey and ExportKey
|
||||
func derivationP1FromStartingPoint(s derivationpath.StartingPoint) (uint8, error) {
|
||||
|
|
|
@ -73,8 +73,6 @@ func (d *decoder) parse() (StartingPoint, []uint32, error) {
|
|||
return d.start, d.path, err
|
||||
}
|
||||
}
|
||||
|
||||
return d.start, d.path, nil
|
||||
}
|
||||
|
||||
func (d *decoder) readByte() (byte, error) {
|
||||
|
@ -168,7 +166,7 @@ func (d *decoder) parseSeparator() error {
|
|||
return d.saveSegment()
|
||||
}
|
||||
|
||||
return fmt.Errorf("expected %s, got %s", string(tokenSeparator), string(b))
|
||||
return fmt.Errorf("expected %c, got %c", tokenSeparator, b)
|
||||
}
|
||||
|
||||
func (d *decoder) parseSegment() error {
|
||||
|
|
|
@ -8,20 +8,20 @@ import (
|
|||
)
|
||||
|
||||
func Encode(rawPath []uint32) string {
|
||||
segments := []string{string(tokenMaster)}
|
||||
segments := []string{string(rune(tokenMaster))}
|
||||
|
||||
for _, i := range rawPath {
|
||||
suffix := ""
|
||||
|
||||
if i >= hardenedStart {
|
||||
i = i - hardenedStart
|
||||
suffix = string(tokenHardened)
|
||||
suffix = string(rune(tokenHardened))
|
||||
}
|
||||
|
||||
segments = append(segments, fmt.Sprintf("%d%s", i, suffix))
|
||||
}
|
||||
|
||||
return strings.Join(segments, string(tokenSeparator))
|
||||
return strings.Join(segments, string(rune(tokenSeparator)))
|
||||
}
|
||||
|
||||
func EncodeFromBytes(data []byte) (string, error) {
|
||||
|
|
|
@ -56,7 +56,7 @@ func TestNewCommandExternalAuthenticate(t *testing.T) {
|
|||
|
||||
func TestNewCommandDelete(t *testing.T) {
|
||||
aid := hexutils.HexToBytes("0102030405")
|
||||
cmd := NewCommandDelete(aid)
|
||||
cmd := NewCommandDelete(aid, P2DeleteObject)
|
||||
assert.Equal(t, uint8(0x80), cmd.Cla)
|
||||
assert.Equal(t, uint8(0xE4), cmd.Ins)
|
||||
assert.Equal(t, uint8(0x00), cmd.P1)
|
||||
|
|
|
@ -96,19 +96,29 @@ func (sc *SecureChannel) Send(cmd *apdu.Command) (*apdu.Response, error) {
|
|||
return nil, apdu.NewErrBadResponse(resp.Sw, "unexpected sw in secure channel")
|
||||
}
|
||||
|
||||
rmeta := []byte{byte(len(resp.Data)), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
rmac := resp.Data[:len(sc.iv)]
|
||||
rdata := resp.Data[len(sc.iv):]
|
||||
plainData, err := crypto.DecryptData(rdata, sc.encKey, sc.iv)
|
||||
if err = sc.updateIV(rmeta, rdata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var plainData []byte
|
||||
|
||||
if !bytes.Equal(sc.iv, rmac) {
|
||||
return nil, ErrInvalidResponseMAC
|
||||
}
|
||||
if sc.open {
|
||||
rmeta := []byte{byte(len(resp.Data)), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
rmac := resp.Data[:len(sc.iv)]
|
||||
rdata := resp.Data[len(sc.iv):]
|
||||
|
||||
logger.Debug("apdu response decrypted", "hex", hexutils.BytesToHexWithSpaces(plainData))
|
||||
if plainData, err = crypto.DecryptData(rdata, sc.encKey, sc.iv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = sc.updateIV(rmeta, rdata); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !bytes.Equal(sc.iv, rmac) {
|
||||
return nil, ErrInvalidResponseMAC
|
||||
}
|
||||
|
||||
logger.Debug("apdu response decrypted", "hex", hexutils.BytesToHexWithSpaces(plainData))
|
||||
} else {
|
||||
plainData = resp.Data
|
||||
}
|
||||
|
||||
return apdu.ParseResponse(plainData)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,201 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"container/list"
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/status-im/keycard-go/apdu"
|
||||
)
|
||||
|
||||
type Metadata struct {
|
||||
name string
|
||||
paths *list.List
|
||||
}
|
||||
|
||||
func EmptyMetadata() *Metadata {
|
||||
return &Metadata{"", list.New()}
|
||||
}
|
||||
|
||||
func NewMetadata(name string, paths []uint32) (*Metadata, error) {
|
||||
m := EmptyMetadata()
|
||||
|
||||
if err := m.SetName(name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := 0; i < len(paths); i++ {
|
||||
m.AddPath(paths[i])
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func ParseMetadata(data []byte) (*Metadata, error) {
|
||||
buf := bytes.NewBuffer(data)
|
||||
header, err := buf.ReadByte()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
version := header >> 5
|
||||
|
||||
if version != 1 {
|
||||
return nil, errors.New("invalid version")
|
||||
}
|
||||
|
||||
namelen := int(header & 0x1f)
|
||||
cardName := string(buf.Next(namelen))
|
||||
|
||||
list := list.New()
|
||||
|
||||
for {
|
||||
start, err := apdu.ParseLength(buf)
|
||||
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
count, err := apdu.ParseLength(buf)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := start; i <= (start + count); i++ {
|
||||
insertOrderedNoDups(list, i)
|
||||
}
|
||||
}
|
||||
|
||||
return &Metadata{cardName, list}, nil
|
||||
}
|
||||
|
||||
func insertOrderedNoDups(list *list.List, num uint32) {
|
||||
le := list.Back()
|
||||
|
||||
for le != nil {
|
||||
val := le.Value.(uint32)
|
||||
|
||||
if num > val {
|
||||
break
|
||||
} else if num == val {
|
||||
return
|
||||
}
|
||||
|
||||
le = le.Prev()
|
||||
}
|
||||
|
||||
if le == nil {
|
||||
list.PushFront(num)
|
||||
} else {
|
||||
list.InsertAfter(num, le)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Metadata) Name() string {
|
||||
return m.name
|
||||
}
|
||||
|
||||
func (m *Metadata) SetName(name string) error {
|
||||
if len(name) > 20 {
|
||||
return errors.New("name longer than 20 chars")
|
||||
}
|
||||
|
||||
m.name = name
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Metadata) Paths() []uint32 {
|
||||
listlen := m.paths.Len()
|
||||
paths := make([]uint32, listlen)
|
||||
e := m.paths.Front()
|
||||
|
||||
for i := 0; i < listlen; i++ {
|
||||
paths[i] = e.Value.(uint32)
|
||||
e = e.Next()
|
||||
}
|
||||
|
||||
return paths
|
||||
}
|
||||
|
||||
func (m *Metadata) AddPath(path uint32) {
|
||||
insertOrderedNoDups(m.paths, path)
|
||||
}
|
||||
|
||||
func (m *Metadata) RemovePath(path uint32) {
|
||||
for le := m.paths.Front(); le != nil; le = le.Next() {
|
||||
if path == le.Value.(uint32) {
|
||||
m.paths.Remove(le)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Metadata) Serialize() []byte {
|
||||
buf := new(bytes.Buffer)
|
||||
buf.WriteByte(0x20 | byte(len(m.name)))
|
||||
buf.WriteString(m.name)
|
||||
|
||||
le := m.paths.Front()
|
||||
|
||||
if le == nil {
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
start := le.Value.(uint32)
|
||||
len := uint32(0)
|
||||
|
||||
for le = le.Next(); le != nil; le = le.Next() {
|
||||
w := le.Value.(uint32)
|
||||
|
||||
if w == (start + len + 1) {
|
||||
len++
|
||||
} else {
|
||||
apdu.WriteLength(buf, start)
|
||||
apdu.WriteLength(buf, len)
|
||||
start = w
|
||||
len = 0
|
||||
}
|
||||
}
|
||||
|
||||
apdu.WriteLength(buf, start)
|
||||
apdu.WriteLength(buf, len)
|
||||
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
/*
|
||||
public byte[] toByteArray() {
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
byte[] name = this.cardName.getBytes(Charset.forName("US-ASCII"));
|
||||
os.write(0x20 | name.length);
|
||||
os.write(name, 0, name.length);
|
||||
|
||||
if (wallets.isEmpty()) {
|
||||
return os.toByteArray();
|
||||
}
|
||||
|
||||
long start = wallets.first();
|
||||
int len = 0;
|
||||
|
||||
for (Long w : wallets.tailSet(start + 1)) {
|
||||
if (w == (start + len + 1)) {
|
||||
len++;
|
||||
} else {
|
||||
TinyBERTLV.writeNum(os, (int) start);
|
||||
TinyBERTLV.writeNum(os, len);
|
||||
len = 0;
|
||||
start = w;
|
||||
}
|
||||
}
|
||||
|
||||
TinyBERTLV.writeNum(os, (int) start);
|
||||
TinyBERTLV.writeNum(os, len);
|
||||
|
||||
return os.toByteArray();
|
||||
}
|
||||
*/
|
|
@ -0,0 +1,20 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseMetadata(t *testing.T) {
|
||||
m, err := ParseMetadata([]byte{0x23, 0x31, 0x32, 0x33, 0x00, 0x00, 0x04, 0x03, 0x82, 0x7a, 0x28, 0x01})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "123", m.Name())
|
||||
assert.Equal(t, []uint32{0x00, 0x04, 0x05, 0x06, 0x07, 0x7a28, 0x7a29}, m.Paths())
|
||||
}
|
||||
|
||||
func TestSerialize(t *testing.T) {
|
||||
m, err := NewMetadata("123", []uint32{0x00, 0x04, 0x05, 0x06, 0x07, 0x7a28, 0x7a29})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []byte{0x23, 0x31, 0x32, 0x33, 0x00, 0x00, 0x04, 0x03, 0x82, 0x7a, 0x28, 0x01}, m.Serialize())
|
||||
}
|
Loading…
Reference in New Issue