add cash applet features

This commit is contained in:
Andrea Franz 2019-11-19 12:55:43 +01:00
parent cc4e5638f2
commit 90247e62fd
No known key found for this signature in database
GPG Key ID: 4F0D2F2D9DE7F29D
13 changed files with 216 additions and 84 deletions

4
Gopkg.lock generated
View File

@ -42,7 +42,7 @@
[[projects]]
branch = "develop"
digest = "1:428d2c2f86c07c78f39d379b809c317b3792d0248e76d60bf78dcf101596c770"
digest = "1:9b4833a900fe5a52f64346bde7749ea58797e8d010ca05d5a78e2ba5ca1864cd"
name = "github.com/status-im/keycard-go"
packages = [
".",
@ -57,7 +57,7 @@
"types",
]
pruneopts = "NUT"
revision = "9d48af884d5b92339229bf57df18d0f882ba70be"
revision = "6dd40a46baa0d4c8b6fcb69f7e2362e21d89df4d"
[[projects]]
branch = "master"

View File

@ -1 +1 @@
0.1.0
0.2.0

View File

@ -7,9 +7,12 @@ gp-select
gp-open-secure-channel
gp-delete D2760000850101
gp-delete A00000080400010101
gp-delete A00000080400010301
gp-delete A0000008040001
gp-load _assets/keycard_v2.2.1.cap A0000008040001
gp-load _assets/keycard_v3.0.cap A0000008040001
# NDEF applet
gp-install-for-install A0000008040001 A000000804000102 D2760000850101 0024d40f12616e64726f69642e636f6d3a706b67696d2e7374617475732e657468657265756d
# Keycard applet
gp-install-for-install A0000008040001 A000000804000101 A00000080400010101
# Cash applet
gp-install-for-install A0000008040001 A000000804000103 A00000080400010301

View File

@ -1,43 +0,0 @@
# select with raw apdu command
gp-send-apdu 00A4040000
# install
gp-open-secure-channel
gp-delete D2760000850101
gp-delete A00000080400010101
gp-delete A0000008040001
gp-load _assets/keycard_v2.2.1.cap A0000008040001
gp-install-for-install A0000008040001 A000000804000102 D2760000850101 0024d40f12616e64726f69642e636f6d3a706b67696d2e7374617475732e657468657265756d
gp-install-for-install A0000008040001 A000000804000101 A00000080400010101
# init
keycard-select
keycard-init
# pair
keycard-select
keycard-pair
keycard-open-secure-channel
# get status
keycard-get-status
# verify PIN
keycard-verify-pin {{ session_pin }}
# change secrets
keycard-change-pin 888888
keycard-change-puk 111222333444
keycard-change-pairing-secret foobarbaz
# sign
keycard-generate-key
keycard-derive-key m/44'/60'/0'/0/0
keycard-sign 0000000000000000000000000000000000000000000000000000000000000000
# remove master key
keycard-remove-key
# unpair and check card status
keycard-unpair {{ session_pairing_index }}
keycard-select

View File

@ -15,8 +15,9 @@ var (
errCardNotInitialized = errors.New("card not initialized")
errCardAlreadyInitialized = errors.New("card already initialized")
ErrNotInitialized = errors.New("card not initialized")
ErrNotInstalled = errors.New("applet not initialized")
ErrNotInitialized = errors.New("card not initialized")
ErrNotInstalled = errors.New("applet not initialized")
ErrCashNotInstalled = errors.New("cash applet not initialized")
)
// Initializer defines a struct with methods to install applets and initialize a card.

View File

@ -83,6 +83,12 @@ func (i *Installer) Install(capFile *os.File, overwriteApplet bool) error {
return err
}
logger.Info("installing Cash applet")
if err = cmdSet.InstallCashApplet(); err != nil {
logger.Error("installing Cash applet failed", "error", err)
return err
}
elapsed := time.Now().Sub(startTime)
logger.Info(fmt.Sprintf("installation completed in %f seconds", elapsed.Seconds()))
return err

View File

@ -94,6 +94,7 @@ type Shell struct {
Secrets *keycard.Secrets
gpCmdSet *globalplatform.CommandSet
kCmdSet *keycard.CommandSet
cashCmdSet *keycard.CashCommandSet
commands map[string]shellCommand
out *bytes.Buffer
tplFuncMap template.FuncMap
@ -103,11 +104,12 @@ func NewShell(t keycardio.Transmitter) *Shell {
c := keycardio.NewNormalChannel(t)
s := &Shell{
t: t,
c: c,
kCmdSet: keycard.NewCommandSet(c),
gpCmdSet: globalplatform.NewCommandSet(c),
out: new(bytes.Buffer),
t: t,
c: c,
kCmdSet: keycard.NewCommandSet(c),
cashCmdSet: keycard.NewCashCommandSet(c),
gpCmdSet: globalplatform.NewCommandSet(c),
out: new(bytes.Buffer),
}
tplFuncs := &TemplateFuncs{s}
@ -143,6 +145,8 @@ func NewShell(t keycardio.Transmitter) *Shell {
"keycard-sign-pinless": s.commandKeycardSignPinless,
"keycard-sign-message-pinless": s.commandKeycardSignMessagePinless,
"keycard-set-pinless-path": s.commandKeycardSetPinlessPath,
"cash-select": s.commandCashSelect,
"cash-sign": s.commandCashSign,
}
return s
@ -259,7 +263,7 @@ func (s *Shell) commandGPDelete(args ...string) error {
logger.Info(fmt.Sprintf("delete %x", aid))
return s.gpCmdSet.Delete(aid)
return s.gpCmdSet.DeleteObject(aid)
}
func (s *Shell) commandGPLoad(args ...string) error {
@ -781,6 +785,51 @@ func (s *Shell) commandKeycardSetPinlessPath(args ...string) error {
return nil
}
func (s *Shell) commandCashSelect(args ...string) error {
if err := s.requireArgs(args, 0); err != nil {
return err
}
logger.Info("select cash")
err := s.cashCmdSet.Select()
info := s.cashCmdSet.CashApplicationInfo
s.write(fmt.Sprintf("Installed: %v\n", info.Installed))
s.write(fmt.Sprintf("PublicKey: %x\n", info.PublicKey))
s.write(fmt.Sprintf("PublicKeyData: %x\n", info.PublicKeyData))
s.write(fmt.Sprintf("Version: %x\n\n", info.Version))
if e, ok := err.(*apdu.ErrBadResponse); ok && e.Sw == globalplatform.SwFileNotFound {
logger.Error("select cash failed", "error", err)
return ErrCashNotInstalled
}
return err
}
func (s *Shell) commandCashSign(args ...string) error {
if err := s.requireArgs(args, 1); err != nil {
return err
}
data, err := s.parseHex(args[0])
if err != nil {
logger.Error("failed parsing hex data", "error", err)
return err
}
logger.Info("sign")
sig, err := s.cashCmdSet.Sign(data)
if err != nil {
logger.Error("sign failed", "error", err)
return err
}
s.writeSignatureInfo(sig)
return nil
}
func (s *Shell) requireArgs(args []string, possibleArgsN ...int) error {
for _, n := range possibleArgsN {
if len(args) == n {

View File

@ -0,0 +1,70 @@
package keycard
import (
"github.com/status-im/keycard-go/apdu"
"github.com/status-im/keycard-go/globalplatform"
"github.com/status-im/keycard-go/identifiers"
"github.com/status-im/keycard-go/types"
)
type CashCommandSet struct {
c types.Channel
CashApplicationInfo *types.CashApplicationInfo
}
func NewCashCommandSet(c types.Channel) *CashCommandSet {
return &CashCommandSet{
c: c,
CashApplicationInfo: &types.CashApplicationInfo{},
}
}
func (cs *CashCommandSet) Select() error {
cmd := globalplatform.NewCommandSelect(identifiers.CashInstanceAID)
cmd.SetLe(0)
resp, err := cs.c.Send(cmd)
if err = cs.checkOK(resp, err); err != nil {
return err
}
appInfo, err := types.ParseCashApplicationInfo(resp.Data)
if err != nil {
return err
}
cs.CashApplicationInfo = appInfo
return nil
}
func (cs *CashCommandSet) Sign(data []byte) (*types.Signature, error) {
cmd, err := NewCommandSign(data, 0x00, "")
if err != nil {
return nil, err
}
resp, err := cs.c.Send(cmd)
if err = cs.checkOK(resp, err); err != nil {
return nil, err
}
return types.ParseSignature(data, resp.Data)
}
func (cs *CashCommandSet) checkOK(resp *apdu.Response, err error, allowedResponses ...uint16) error {
if err != nil {
return err
}
if len(allowedResponses) == 0 {
allowedResponses = []uint16{apdu.SwOK}
}
for _, code := range allowedResponses {
if code == resp.Sw {
return nil
}
}
return apdu.NewErrBadResponse(resp.Sw, "unexpected response")
}

View File

@ -23,8 +23,9 @@ type CommandSet struct {
func NewCommandSet(c types.Channel) *CommandSet {
return &CommandSet{
c: c,
sc: NewSecureChannel(c),
c: c,
sc: NewSecureChannel(c),
ApplicationInfo: &types.ApplicationInfo{},
}
}
@ -45,7 +46,6 @@ func (cs *CommandSet) Select() error {
cmd.SetLe(0)
resp, err := cs.c.Send(cmd)
if err = cs.checkOK(resp, err); err != nil {
cs.ApplicationInfo = &types.ApplicationInfo{}
return err
}

View File

@ -57,29 +57,19 @@ func (cs *CommandSet) DeleteKeycardInstancesAndPackage() error {
return ErrSecureChannelNotOpen
}
instanceAID, err := identifiers.KeycardInstanceAID(identifiers.KeycardDefaultInstanceIndex)
if err != nil {
return err
}
ids := [][]byte{
identifiers.NdefInstanceAID,
instanceAID,
identifiers.PackageAID,
}
for _, aid := range ids {
err := cs.Delete(aid)
if err != nil {
return err
}
}
return nil
return cs.DeleteObjectAndRelatedObject(identifiers.PackageAID)
}
func (cs *CommandSet) Delete(aid []byte) error {
cmd := NewCommandDelete(aid)
func (cs *CommandSet) DeleteObject(aid []byte) error {
return cs.Delete(aid, P2DeleteObject)
}
func (cs *CommandSet) DeleteObjectAndRelatedObject(aid []byte) error {
return cs.Delete(aid, P2DeleteObjectAndRelatedObject)
}
func (cs *CommandSet) Delete(aid []byte, p2 uint8) error {
cmd := NewCommandDelete(aid, p2)
resp, err := cs.sc.Send(cmd)
return cs.checkOK(resp, err, SwOK, SwReferencedDataNotFound)
}
@ -137,6 +127,14 @@ func (cs *CommandSet) InstallKeycardApplet() error {
[]byte{})
}
func (cs *CommandSet) InstallCashApplet() error {
return cs.InstallForInstall(
identifiers.PackageAID,
identifiers.CashAID,
identifiers.CashInstanceAID,
[]byte{})
}
func (cs *CommandSet) InstallForInstall(packageAID, appletAID, instanceAID, params []byte) error {
cmd := NewCommandInstallForInstall(packageAID, appletAID, instanceAID, params)
resp, err := cs.sc.Send(cmd)

View File

@ -31,7 +31,9 @@ const (
P1GetStatusExecLoadFiles = 0x20
P1GetStatusExecLoadFilesAndModules = 0x10
P2GetStatusTLVData = 0x02
P2GetStatusTLVData = 0x02
P2DeleteObject = 0x00
P2DeleteObjectAndRelatedObject = 0x80
Sw1ResponseDataIncomplete = 0x61
@ -108,7 +110,7 @@ func NewCommandGetResponse(length uint8) *apdu.Command {
}
// NewCommandDelete returns a Delete command as defined in the globalplatform specifications.
func NewCommandDelete(aid []byte) *apdu.Command {
func NewCommandDelete(aid []byte, p2 uint8) *apdu.Command {
data := []byte{tagDeleteAID, byte(len(aid))}
data = append(data, aid...)
@ -116,7 +118,7 @@ func NewCommandDelete(aid []byte) *apdu.Command {
ClaGp,
InsDelete,
0,
0,
p2,
data,
)
}

View File

@ -6,11 +6,16 @@ var (
GlobalPlatformDefaultKey = []byte{0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f}
KeycardDevelopmentKey = []byte{0xc2, 0x12, 0xe0, 0x73, 0xff, 0x8b, 0x4b, 0xbf, 0xaf, 0xf4, 0xde, 0x8a, 0xb6, 0x55, 0x22, 0x1f}
PackageAID = []byte{0xA0, 0x00, 0x00, 0x08, 0x04, 0x00, 0x01}
KeycardAID = []byte{0xA0, 0x00, 0x00, 0x08, 0x04, 0x00, 0x01, 0x01}
PackageAID = []byte{0xA0, 0x00, 0x00, 0x08, 0x04, 0x00, 0x01}
KeycardAID = []byte{0xA0, 0x00, 0x00, 0x08, 0x04, 0x00, 0x01, 0x01}
NdefAID = []byte{0xA0, 0x00, 0x00, 0x08, 0x04, 0x00, 0x01, 0x02}
NdefInstanceAID = []byte{0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01}
CashAID = []byte{0xA0, 0x00, 0x00, 0x08, 0x04, 0x00, 0x01, 0x03}
CashInstanceAID = []byte{0xA0, 0x00, 0x00, 0x08, 0x04, 0x00, 0x01, 0x03, 0x01}
KeycardDefaultInstanceIndex = 1
ErrInvalidInstanceIndex = errors.New("instance index must be between 1 and 255")

View File

@ -0,0 +1,41 @@
package types
import "github.com/status-im/keycard-go/apdu"
type CashApplicationInfo struct {
Installed bool
PublicKey []byte
PublicKeyData []byte
Version []byte
}
func ParseCashApplicationInfo(data []byte) (*CashApplicationInfo, error) {
info := &CashApplicationInfo{}
if data[0] != TagApplicationInfoTemplate {
return nil, ErrWrongApplicationInfoTemplate
}
info.Installed = true
pubKey, err := apdu.FindTag(data, apdu.Tag{TagApplicationInfoTemplate}, apdu.Tag{0x80})
if err != nil {
return nil, err
}
pubKeyData, err := apdu.FindTag(data, apdu.Tag{TagApplicationInfoTemplate}, apdu.Tag{0x82})
if err != nil {
return nil, err
}
appVersion, err := apdu.FindTag(data, apdu.Tag{TagApplicationInfoTemplate}, apdu.Tag{0x02})
if err != nil {
return nil, err
}
info.PublicKey = pubKey
info.PublicKeyData = pubKeyData
info.Version = appVersion
return info, nil
}