mirror of
https://github.com/status-im/keycard-go.git
synced 2025-01-31 14:15:09 +00:00
remove cmd
This commit is contained in:
parent
a420564d09
commit
2a3ba4fcc0
@ -1,80 +0,0 @@
|
||||
# keycard
|
||||
|
||||
`keycard` is a command line tool you can use to initialize a smartcard with the [Status Keycard](https://github.com/status-im/status-keycard).
|
||||
|
||||
## Dependencies
|
||||
|
||||
To install `keycard-go` you need `go` in your system.
|
||||
|
||||
MacOSX:
|
||||
|
||||
`brew install go`
|
||||
|
||||
## Installation
|
||||
|
||||
`go get -u github.com/status-im/keycard-go/cmd/keycard`
|
||||
|
||||
The executable will be installed in `$GOPATH/bin`.
|
||||
Check your `$GOPATH` with `go env`.
|
||||
|
||||
## Usage
|
||||
|
||||
### Install the keycard applet
|
||||
|
||||
The install command will install an applet to the card.
|
||||
You can download the status `cap` file from the [status-im/status-keycard releases page](https://github.com/status-im/status-keycard/releases).
|
||||
|
||||
```bash
|
||||
keycard install -l debug -a PATH_TO_CAP_FILE
|
||||
```
|
||||
|
||||
In case the applet is already installed and you want to force a new installation you can pass the `-f` flag.
|
||||
|
||||
### Card info
|
||||
|
||||
```bash
|
||||
keycard info -l debug
|
||||
```
|
||||
|
||||
The `info` command will print something like this:
|
||||
|
||||
```
|
||||
Installed: true
|
||||
Initialized: false
|
||||
InstanceUID: 0x
|
||||
PublicKey: 0x112233...
|
||||
Version: 0x
|
||||
AvailableSlots: 0x
|
||||
KeyUID: 0x
|
||||
```
|
||||
|
||||
### Card initialization
|
||||
|
||||
|
||||
```bash
|
||||
keycard init -l debug
|
||||
```
|
||||
|
||||
The `init` command initializes the card and generates the secrets needed to pair the card to a device.
|
||||
|
||||
```
|
||||
PIN 123456
|
||||
PUK 123456789012
|
||||
Pairing password: RandomPairingPassword
|
||||
```
|
||||
|
||||
### Deleting the applet from the card
|
||||
|
||||
:warning: **WARNING! This command will remove the applet and all the keys from the card.** :warning:
|
||||
|
||||
```bash
|
||||
keycard delete -l debug
|
||||
```
|
||||
|
||||
### Pairing
|
||||
|
||||
```bash
|
||||
keycard pair -l debug
|
||||
```
|
||||
|
||||
The process will ask for `PairingPassword` and `PIN` and will generate a pairing key you can use to interact with the card.
|
@ -1,139 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
keycard "github.com/status-im/keycard-go"
|
||||
"github.com/status-im/keycard-go/apdu"
|
||||
"github.com/status-im/keycard-go/globalplatform"
|
||||
"github.com/status-im/keycard-go/types"
|
||||
)
|
||||
|
||||
var (
|
||||
errAppletNotInstalled = errors.New("applet not installed")
|
||||
errCardNotInitialized = errors.New("card not initialized")
|
||||
errCardAlreadyInitialized = errors.New("card already initialized")
|
||||
|
||||
ErrNotInitialized = errors.New("card not initialized")
|
||||
ErrNotInstalled = errors.New("applet not initialized")
|
||||
)
|
||||
|
||||
// Initializer defines a struct with methods to install applets and initialize a card.
|
||||
type Initializer struct {
|
||||
c types.Channel
|
||||
}
|
||||
|
||||
// NewInitializer returns a new Initializer that communicates to Transmitter t.
|
||||
func NewInitializer(t globalplatform.Transmitter) *Initializer {
|
||||
return &Initializer{
|
||||
c: globalplatform.NewNormalChannel(t),
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Initializer) Init() (*keycard.Secrets, error) {
|
||||
logger.Info("initialization started")
|
||||
cmdSet := keycard.NewCommandSet(i.c)
|
||||
|
||||
secrets, err := keycard.GenerateSecrets()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logger.Info("select keycard applet")
|
||||
err = cmdSet.Select()
|
||||
if err != nil {
|
||||
logger.Error("select failed", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !cmdSet.ApplicationInfo.Installed {
|
||||
logger.Error("initialization failed", "error", errAppletNotInstalled)
|
||||
return nil, errAppletNotInstalled
|
||||
}
|
||||
|
||||
if cmdSet.ApplicationInfo.Initialized {
|
||||
logger.Error("initialization failed", "error", errCardAlreadyInitialized)
|
||||
return nil, errCardAlreadyInitialized
|
||||
}
|
||||
|
||||
logger.Info("initializing")
|
||||
err = cmdSet.Init(secrets)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return secrets, nil
|
||||
}
|
||||
|
||||
// Info returns a types.ApplicationInfo struct with info about the card.
|
||||
func (i *Initializer) Info() (*types.ApplicationInfo, error) {
|
||||
logger.Info("info started")
|
||||
cmdSet := keycard.NewCommandSet(i.c)
|
||||
|
||||
logger.Info("select keycard applet")
|
||||
err := cmdSet.Select()
|
||||
if err != nil {
|
||||
if e, ok := err.(*apdu.ErrBadResponse); ok && e.Sw == globalplatform.SwFileNotFound {
|
||||
err = nil
|
||||
} else {
|
||||
logger.Error("select failed", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
return cmdSet.ApplicationInfo, err
|
||||
}
|
||||
|
||||
func (i *Initializer) Pair(pairingPass string) (*types.PairingInfo, error) {
|
||||
logger.Info("pairing started")
|
||||
cmdSet := keycard.NewCommandSet(i.c)
|
||||
|
||||
logger.Info("select keycard applet")
|
||||
err := cmdSet.Select()
|
||||
if err != nil {
|
||||
logger.Error("select failed", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !cmdSet.ApplicationInfo.Initialized {
|
||||
logger.Error("pairing failed", "error", ErrNotInitialized)
|
||||
return nil, ErrNotInitialized
|
||||
}
|
||||
|
||||
logger.Info("pairing")
|
||||
err = cmdSet.Pair(pairingPass)
|
||||
return cmdSet.PairingInfo, err
|
||||
}
|
||||
|
||||
func (i *Initializer) Status(key []byte, index int) (*types.ApplicationStatus, error) {
|
||||
logger.Info("pairing started")
|
||||
cmdSet := keycard.NewCommandSet(i.c)
|
||||
|
||||
logger.Info("select keycard applet")
|
||||
err := cmdSet.Select()
|
||||
if err != nil {
|
||||
logger.Error("select failed", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !cmdSet.ApplicationInfo.Initialized {
|
||||
logger.Error("pairing failed", "error", ErrNotInitialized)
|
||||
return nil, ErrNotInitialized
|
||||
}
|
||||
|
||||
logger.Info("open secure channel")
|
||||
cmdSet.SetPairingInfo(key, index)
|
||||
err = cmdSet.OpenSecureChannel()
|
||||
if err != nil {
|
||||
logger.Error("open secure channel failed", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logger.Info("get status")
|
||||
appStatus, err := cmdSet.GetStatusApplication()
|
||||
if err != nil {
|
||||
logger.Error("get status failed", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return appStatus, nil
|
||||
}
|
@ -1,138 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/status-im/keycard-go/apdu"
|
||||
"github.com/status-im/keycard-go/globalplatform"
|
||||
"github.com/status-im/keycard-go/hexutils"
|
||||
"github.com/status-im/keycard-go/identifiers"
|
||||
"github.com/status-im/keycard-go/types"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrAppletAlreadyInstalled = errors.New("keycard applet already installed")
|
||||
ndefRecord = hexutils.HexToBytes("0024d40f12616e64726f69642e636f6d3a706b67696d2e7374617475732e657468657265756d")
|
||||
)
|
||||
|
||||
// Installer defines a struct with methods to install applets in a card.
|
||||
type Installer struct {
|
||||
c types.Channel
|
||||
}
|
||||
|
||||
// NewInstaller returns a new Installer that communicates to Transmitter t.
|
||||
func NewInstaller(t globalplatform.Transmitter) *Installer {
|
||||
return &Installer{
|
||||
c: globalplatform.NewNormalChannel(t),
|
||||
}
|
||||
}
|
||||
|
||||
// Install installs the applet from the specified capFile.
|
||||
func (i *Installer) Install(capFile *os.File, overwriteApplet bool) error {
|
||||
logger.Info("installation started")
|
||||
startTime := time.Now()
|
||||
cmdSet := globalplatform.NewCommandSet(i.c)
|
||||
|
||||
logger.Info("check if keycard is already installed")
|
||||
if err := i.checkAppletAlreadyInstalled(cmdSet, overwriteApplet); err != nil {
|
||||
logger.Error("check if keycard is already installed failed", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info("select ISD")
|
||||
err := cmdSet.Select()
|
||||
if err != nil {
|
||||
logger.Error("select failed", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info("opening secure channel")
|
||||
if err = cmdSet.OpenSecureChannel(); err != nil {
|
||||
logger.Error("open secure channel failed", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info("delete old version (if present)")
|
||||
if err = cmdSet.DeleteKeycardInstancesAndPackage(); err != nil {
|
||||
logger.Error("delete keycard instances and package failed", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info("loading package")
|
||||
callback := func(index, total int) {
|
||||
logger.Debug(fmt.Sprintf("loading %d/%d", index+1, total))
|
||||
}
|
||||
if err = cmdSet.LoadKeycardPackage(capFile, callback); err != nil {
|
||||
logger.Error("load failed", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info("installing NDEF applet")
|
||||
if err = cmdSet.InstallNDEFApplet(ndefRecord); err != nil {
|
||||
logger.Error("installing NDEF applet failed", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info("installing Keycard applet")
|
||||
if err = cmdSet.InstallKeycardApplet(); err != nil {
|
||||
logger.Error("installing Keycard applet failed", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
elapsed := time.Now().Sub(startTime)
|
||||
logger.Info(fmt.Sprintf("installation completed in %f seconds", elapsed.Seconds()))
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete deletes the applet from the card.
|
||||
func (i *Installer) Delete() error {
|
||||
cmdSet := globalplatform.NewCommandSet(i.c)
|
||||
|
||||
logger.Info("select ISD")
|
||||
err := cmdSet.Select()
|
||||
if err != nil {
|
||||
logger.Error("select failed", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info("opening secure channel")
|
||||
if err = cmdSet.OpenSecureChannel(); err != nil {
|
||||
logger.Error("open secure channel failed", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info("delete old version")
|
||||
if err = cmdSet.DeleteKeycardInstancesAndPackage(); err != nil {
|
||||
logger.Error("delete keycard instances and package failed", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Installer) checkAppletAlreadyInstalled(cmdSet *globalplatform.CommandSet, overwriteApplet bool) error {
|
||||
keycardInstanceAID, err := identifiers.KeycardInstanceAID(identifiers.KeycardDefaultInstanceIndex)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = cmdSet.SelectAID(keycardInstanceAID)
|
||||
switch e := err.(type) {
|
||||
case *apdu.ErrBadResponse:
|
||||
// keycard applet not found, so not installed yet.
|
||||
if e.Sw == globalplatform.SwFileNotFound {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
case nil: // selected successfully, so it's already installed
|
||||
if overwriteApplet {
|
||||
return nil
|
||||
}
|
||||
return ErrAppletAlreadyInstalled
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
@ -1,288 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
stdlog "log"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ebfe/scard"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
type commandFunc func(*scard.Card) error
|
||||
|
||||
var (
|
||||
logger = log.New("package", "status-go/cmd/keycard")
|
||||
|
||||
commands map[string]commandFunc
|
||||
command string
|
||||
|
||||
flagCapFile = flag.String("a", "", "applet cap file path")
|
||||
flagOverwrite = flag.Bool("f", false, "force applet installation if already installed")
|
||||
flagLogLevel = flag.String("l", "", `Log level, one of: "error", "warn", "info", "debug", and "trace"`)
|
||||
)
|
||||
|
||||
func initLogger() {
|
||||
if *flagLogLevel == "" {
|
||||
*flagLogLevel = "info"
|
||||
}
|
||||
|
||||
level, err := log.LvlFromString(strings.ToLower(*flagLogLevel))
|
||||
if err != nil {
|
||||
stdlog.Fatal(err)
|
||||
}
|
||||
|
||||
handler := log.StreamHandler(os.Stderr, log.TerminalFormat(true))
|
||||
filteredHandler := log.LvlFilterHandler(level, handler)
|
||||
log.Root().SetHandler(filteredHandler)
|
||||
}
|
||||
|
||||
func init() {
|
||||
commands = map[string]commandFunc{
|
||||
"install": commandInstall,
|
||||
"info": commandInfo,
|
||||
"delete": commandDelete,
|
||||
"init": commandInit,
|
||||
"pair": commandPair,
|
||||
"status": commandStatus,
|
||||
"shell": commandShell,
|
||||
}
|
||||
|
||||
if len(os.Args) < 2 {
|
||||
usage()
|
||||
}
|
||||
|
||||
command = os.Args[1]
|
||||
if len(os.Args) > 2 {
|
||||
flag.CommandLine.Parse(os.Args[2:])
|
||||
}
|
||||
|
||||
initLogger()
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Printf("\nUsage:\n keycard COMMAND [FLAGS]\n\nAvailable commands:\n")
|
||||
for name := range commands {
|
||||
fmt.Printf(" %s\n", name)
|
||||
}
|
||||
fmt.Print("\nFlags:\n\n")
|
||||
flag.PrintDefaults()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func fail(msg string, ctx ...interface{}) {
|
||||
logger.Error(msg, ctx...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func main() {
|
||||
ctx, err := scard.EstablishContext()
|
||||
if err != nil {
|
||||
fail("error establishing card context", "error", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := ctx.Release(); err != nil {
|
||||
logger.Error("error releasing context", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
readers, err := ctx.ListReaders()
|
||||
if err != nil {
|
||||
fail("error getting readers", "error", err)
|
||||
}
|
||||
|
||||
if len(readers) == 0 {
|
||||
fail("couldn't find any reader")
|
||||
}
|
||||
|
||||
if len(readers) > 1 {
|
||||
fail("too many readers found")
|
||||
}
|
||||
|
||||
reader := readers[0]
|
||||
logger.Debug("using reader", "name", reader)
|
||||
logger.Debug("connecting to card", "reader", reader)
|
||||
card, err := ctx.Connect(reader, scard.ShareShared, scard.ProtocolAny)
|
||||
if err != nil {
|
||||
fail("error connecting to card", "error", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := card.Disconnect(scard.ResetCard); err != nil {
|
||||
logger.Error("error disconnecting card", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
status, err := card.Status()
|
||||
if err != nil {
|
||||
fail("error getting card status", "error", err)
|
||||
}
|
||||
|
||||
switch status.ActiveProtocol {
|
||||
case scard.ProtocolT0:
|
||||
logger.Debug("card protocol", "T", "0")
|
||||
case scard.ProtocolT1:
|
||||
logger.Debug("card protocol", "T", "1")
|
||||
default:
|
||||
logger.Debug("card protocol", "T", "unknown")
|
||||
}
|
||||
|
||||
if f, ok := commands[command]; ok {
|
||||
err = f(card)
|
||||
if err != nil {
|
||||
logger.Error("error executing command", "command", command, "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
fail("unknown command", "command", command)
|
||||
usage()
|
||||
}
|
||||
|
||||
func ask(description string) string {
|
||||
r := bufio.NewReader(os.Stdin)
|
||||
fmt.Printf("%s: ", description)
|
||||
text, err := r.ReadString('\n')
|
||||
if err != nil {
|
||||
stdlog.Fatal(err)
|
||||
}
|
||||
|
||||
return strings.TrimSpace(text)
|
||||
}
|
||||
|
||||
func askHex(description string) []byte {
|
||||
s := ask(description)
|
||||
if s[:2] == "0x" {
|
||||
s = s[2:]
|
||||
}
|
||||
|
||||
data, err := hex.DecodeString(s)
|
||||
if err != nil {
|
||||
stdlog.Fatal(err)
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func askInt(description string) int {
|
||||
s := ask(description)
|
||||
i, err := strconv.ParseInt(s, 10, 8)
|
||||
if err != nil {
|
||||
stdlog.Fatal(err)
|
||||
}
|
||||
|
||||
return int(i)
|
||||
}
|
||||
|
||||
func commandInstall(card *scard.Card) error {
|
||||
if *flagCapFile == "" {
|
||||
logger.Error("you must specify a cap file path with the -f flag\n")
|
||||
usage()
|
||||
}
|
||||
|
||||
f, err := os.Open(*flagCapFile)
|
||||
if err != nil {
|
||||
fail("error opening cap file", "error", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
i := NewInstaller(card)
|
||||
|
||||
return i.Install(f, *flagOverwrite)
|
||||
}
|
||||
|
||||
func commandInfo(card *scard.Card) error {
|
||||
i := NewInitializer(card)
|
||||
info, err := i.Info()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Installed: %+v\n", info.Installed)
|
||||
fmt.Printf("Initialized: %+v\n", info.Initialized)
|
||||
fmt.Printf("InstanceUID: 0x%x\n", info.InstanceUID)
|
||||
fmt.Printf("SecureChannelPublicKey: 0x%x\n", info.SecureChannelPublicKey)
|
||||
fmt.Printf("Version: 0x%x\n", info.Version)
|
||||
fmt.Printf("AvailableSlots: 0x%x\n", info.AvailableSlots)
|
||||
fmt.Printf("KeyUID: 0x%x\n", info.KeyUID)
|
||||
fmt.Printf("Capabilities:\n")
|
||||
fmt.Printf(" Secure channel:%v\n", info.HasSecureChannelCapability())
|
||||
fmt.Printf(" Key management:%v\n", info.HasKeyManagementCapability())
|
||||
fmt.Printf(" Credentials Management:%v\n", info.HasCredentialsManagementCapability())
|
||||
fmt.Printf(" NDEF:%v\n", info.HasNDEFCapability())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func commandDelete(card *scard.Card) error {
|
||||
i := NewInstaller(card)
|
||||
err := i.Delete()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("applet deleted\n")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func commandInit(card *scard.Card) error {
|
||||
i := NewInitializer(card)
|
||||
secrets, err := i.Init()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("PIN %s\n", secrets.Pin())
|
||||
fmt.Printf("PUK %s\n", secrets.Puk())
|
||||
fmt.Printf("Pairing password: %s\n", secrets.PairingPass())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func commandPair(card *scard.Card) error {
|
||||
i := NewInitializer(card)
|
||||
pairingPass := ask("Pairing password")
|
||||
info, err := i.Pair(pairingPass)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Pairing key 0x%x\n", info.Key)
|
||||
fmt.Printf("Pairing Index %d\n", info.Index)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func commandStatus(card *scard.Card) error {
|
||||
i := NewInitializer(card)
|
||||
key := askHex("Pairing key")
|
||||
index := askInt("Pairing index")
|
||||
|
||||
appStatus, err := i.Status(key, index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf("Pin retry count: %d\n", appStatus.PinRetryCount)
|
||||
fmt.Printf("PUK retry count: %d\n", appStatus.PUKRetryCount)
|
||||
fmt.Printf("Key initialized: %v\n", appStatus.KeyInitialized)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func commandShell(card *scard.Card) error {
|
||||
fi, _ := os.Stdin.Stat()
|
||||
if (fi.Mode() & os.ModeCharDevice) == 0 {
|
||||
s := NewShell(card)
|
||||
return s.Run()
|
||||
} else {
|
||||
return errors.New("non interactive shell. you must pipe commands")
|
||||
}
|
||||
}
|
@ -1,485 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
keycard "github.com/status-im/keycard-go"
|
||||
"github.com/status-im/keycard-go/apdu"
|
||||
"github.com/status-im/keycard-go/globalplatform"
|
||||
"github.com/status-im/keycard-go/types"
|
||||
)
|
||||
|
||||
type shellCommand = func(args ...string) error
|
||||
|
||||
type Shell struct {
|
||||
t globalplatform.Transmitter
|
||||
c types.Channel
|
||||
secrets *keycard.Secrets
|
||||
gpCmdSet *globalplatform.CommandSet
|
||||
kCmdSet *keycard.CommandSet
|
||||
commands map[string]shellCommand
|
||||
out *bytes.Buffer
|
||||
}
|
||||
|
||||
func NewShell(t globalplatform.Transmitter) *Shell {
|
||||
c := globalplatform.NewNormalChannel(t)
|
||||
|
||||
s := &Shell{
|
||||
t: t,
|
||||
c: c,
|
||||
kCmdSet: keycard.NewCommandSet(c),
|
||||
gpCmdSet: globalplatform.NewCommandSet(c),
|
||||
out: new(bytes.Buffer),
|
||||
}
|
||||
|
||||
s.commands = map[string]shellCommand{
|
||||
"gp-send-apdu": s.commandGPSendAPDU,
|
||||
"gp-select": s.commandGPSelect,
|
||||
"gp-open-secure-channel": s.commandGPOpenSecureChannel,
|
||||
"gp-delete": s.commandGPDelete,
|
||||
"gp-load": s.commandGPLoad,
|
||||
"gp-install-for-install": s.commandGPInstallForInstall,
|
||||
"keycard-init": s.commandKeycardInit,
|
||||
"keycard-select": s.commandKeycardSelect,
|
||||
"keycard-pair": s.commandKeycardPair,
|
||||
"keycard-open-secure-channel": s.commandKeycardOpenSecureChannel,
|
||||
"keycard-get-status": s.commandKeycardGetStatus,
|
||||
"keycard-set-secrets": s.commandKeycardSetSecrets,
|
||||
"keycard-set-pairing": s.commandKeycardSetPairing,
|
||||
"keycard-verify-pin": s.commandKeycardVerifyPIN,
|
||||
"keycard-generate-key": s.commandKeycardGenerateKey,
|
||||
"keycard-derive-key": s.commandKeycardDeriveKey,
|
||||
"keycard-sign": s.commandKeycardSign,
|
||||
"keycard-set-pinless-path": s.commandKeycardSetPinlessPath,
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Shell) write(str string) {
|
||||
s.out.WriteString(str)
|
||||
}
|
||||
|
||||
func (s *Shell) flushOut() {
|
||||
io.Copy(os.Stdout, s.out)
|
||||
}
|
||||
|
||||
func (s *Shell) Run() error {
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
defer s.flushOut()
|
||||
|
||||
for {
|
||||
line, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
err = s.evalLine(line)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Shell) commandGPSendAPDU(args ...string) error {
|
||||
if err := s.requireArgs(args, 1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
apdu, err := hex.DecodeString(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info(fmt.Sprintf("send apdu %x", apdu))
|
||||
resp, err := s.t.Transmit(apdu)
|
||||
if err != nil {
|
||||
logger.Error("send apdu failed", "error", err)
|
||||
return err
|
||||
}
|
||||
logger.Info(fmt.Sprintf("raw response: %x", resp))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Shell) commandGPSelect(args ...string) error {
|
||||
if err := s.requireArgs(args, 0, 1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
logger.Info("select ISD")
|
||||
return s.gpCmdSet.Select()
|
||||
}
|
||||
|
||||
aid, err := hex.DecodeString(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info(fmt.Sprintf("select AID %x", aid))
|
||||
return s.gpCmdSet.SelectAID(aid)
|
||||
}
|
||||
|
||||
func (s *Shell) commandGPOpenSecureChannel(args ...string) error {
|
||||
if err := s.requireArgs(args, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info("open secure channel")
|
||||
return s.gpCmdSet.OpenSecureChannel()
|
||||
}
|
||||
|
||||
func (s *Shell) commandGPDelete(args ...string) error {
|
||||
if err := s.requireArgs(args, 1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
aid, err := hex.DecodeString(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info(fmt.Sprintf("delete %x", aid))
|
||||
|
||||
return s.gpCmdSet.Delete(aid)
|
||||
}
|
||||
|
||||
func (s *Shell) commandGPLoad(args ...string) error {
|
||||
if err := s.requireArgs(args, 2); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := os.Open(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
pkgAID, err := hex.DecodeString(args[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info("load package")
|
||||
callback := func(index, total int) {
|
||||
logger.Debug(fmt.Sprintf("loading %d/%d", index+1, total))
|
||||
}
|
||||
if err = s.gpCmdSet.LoadPackage(f, pkgAID, callback); err != nil {
|
||||
logger.Error("load failed", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Shell) commandGPInstallForInstall(args ...string) error {
|
||||
if err := s.requireArgs(args, 3, 4); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pkgAID, err := hex.DecodeString(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
appletAID, err := hex.DecodeString(args[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
instanceAID, err := hex.DecodeString(args[2])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var params []byte
|
||||
|
||||
if len(args) == 4 {
|
||||
params, err = hex.DecodeString(args[3])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
logger.Info("install for install", "pkg", pkgAID, "applet", appletAID, "instance", instanceAID, "params", params)
|
||||
|
||||
return s.gpCmdSet.InstallForInstall(pkgAID, appletAID, instanceAID, params)
|
||||
}
|
||||
|
||||
func (s *Shell) commandKeycardInit(args ...string) error {
|
||||
if err := s.requireArgs(args, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i := NewInitializer(s.t)
|
||||
secrets, err := i.Init()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.secrets = secrets
|
||||
s.write(fmt.Sprintf("PIN: %s\n", secrets.Pin()))
|
||||
s.write(fmt.Sprintf("PUK: %s\n", secrets.Puk()))
|
||||
s.write(fmt.Sprintf("PAIRING PASSWORD: %s\n", secrets.PairingPass()))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Shell) commandKeycardSetSecrets(args ...string) error {
|
||||
if err := s.requireArgs(args, 3); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.secrets = keycard.NewSecrets(args[0], args[1], args[2])
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Shell) commandKeycardSelect(args ...string) error {
|
||||
if err := s.requireArgs(args, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info("select keycard")
|
||||
err := s.kCmdSet.Select()
|
||||
info := s.kCmdSet.ApplicationInfo
|
||||
|
||||
s.write(fmt.Sprintf("Installed: %+v\n", info.Installed))
|
||||
s.write(fmt.Sprintf("Initialized: %+v\n", info.Initialized))
|
||||
s.write(fmt.Sprintf("InstanceUID: %x\n", info.InstanceUID))
|
||||
s.write(fmt.Sprintf("SecureChannelPublicKey: %x\n", info.SecureChannelPublicKey))
|
||||
s.write(fmt.Sprintf("Version: %x\n", info.Version))
|
||||
s.write(fmt.Sprintf("AvailableSlots: %x\n", info.AvailableSlots))
|
||||
s.write(fmt.Sprintf("KeyUID: %x\n", info.KeyUID))
|
||||
s.write(fmt.Sprintf("Capabilities:\n"))
|
||||
s.write(fmt.Sprintf(" Secure channel:%v\n", info.HasSecureChannelCapability()))
|
||||
s.write(fmt.Sprintf(" Key management:%v\n", info.HasKeyManagementCapability()))
|
||||
s.write(fmt.Sprintf(" Credentials Management:%v\n", info.HasCredentialsManagementCapability()))
|
||||
s.write(fmt.Sprintf(" NDEF:%v\n", info.HasNDEFCapability()))
|
||||
|
||||
if e, ok := err.(*apdu.ErrBadResponse); ok && e.Sw == globalplatform.SwFileNotFound {
|
||||
logger.Error("select keycard failed", "error", err)
|
||||
return ErrNotInstalled
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Shell) commandKeycardPair(args ...string) error {
|
||||
if err := s.requireArgs(args, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if s.secrets == nil {
|
||||
return errors.New("cannot pair without setting secrets")
|
||||
}
|
||||
|
||||
err := s.kCmdSet.Pair(s.secrets.PairingPass())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.write(fmt.Sprintf("PAIRING KEY: %x\n", s.kCmdSet.PairingInfo.Key))
|
||||
s.write(fmt.Sprintf("PAIRING INDEX: %v\n", s.kCmdSet.PairingInfo.Index))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Shell) commandKeycardSetPairing(args ...string) error {
|
||||
if err := s.requireArgs(args, 2); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key, err := s.parseHex(args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
index, err := strconv.ParseInt(args[1], 10, 8)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.kCmdSet.SetPairingInfo(key, int(index))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Shell) commandKeycardOpenSecureChannel(args ...string) error {
|
||||
if err := s.requireArgs(args, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if s.kCmdSet.PairingInfo == nil {
|
||||
return errors.New("cannot open secure channel without setting pairing info")
|
||||
}
|
||||
|
||||
logger.Info("open keycard secure channel")
|
||||
if err := s.kCmdSet.OpenSecureChannel(); err != nil {
|
||||
logger.Error("open keycard secure channel failed", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Shell) commandKeycardGetStatus(args ...string) error {
|
||||
if err := s.requireArgs(args, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info("get status application")
|
||||
appStatus, err := s.kCmdSet.GetStatusApplication()
|
||||
if err != nil {
|
||||
logger.Error("get status application failed", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info("get status key path")
|
||||
keyStatus, err := s.kCmdSet.GetStatusKeyPath()
|
||||
if err != nil {
|
||||
logger.Error("get status key path failed", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
s.write(fmt.Sprintf("STATUS - PIN RETRY COUNT: %d\n", appStatus.PinRetryCount))
|
||||
s.write(fmt.Sprintf("STATUS - PUK RETRY COUNT: %d\n", appStatus.PUKRetryCount))
|
||||
s.write(fmt.Sprintf("STATUS - KEY INITIALIZED: %v\n", appStatus.KeyInitialized))
|
||||
s.write(fmt.Sprintf("STATUS - KEY PATH: %v\n", keyStatus.Path))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Shell) commandKeycardVerifyPIN(args ...string) error {
|
||||
if err := s.requireArgs(args, 1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info("verify PIN")
|
||||
if err := s.kCmdSet.VerifyPIN(args[0]); err != nil {
|
||||
logger.Error("verify PIN failed", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Shell) commandKeycardGenerateKey(args ...string) error {
|
||||
if err := s.requireArgs(args, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info("generate key")
|
||||
keyUID, err := s.kCmdSet.GenerateKey()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.write(fmt.Sprintf("KEY UID %x\n", keyUID))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Shell) commandKeycardDeriveKey(args ...string) error {
|
||||
if err := s.requireArgs(args, 1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info(fmt.Sprintf("derive key %s", args[0]))
|
||||
if err := s.kCmdSet.DeriveKey(args[0]); err != nil {
|
||||
logger.Error("derive key failed", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Shell) commandKeycardSign(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.kCmdSet.Sign(data)
|
||||
if err != nil {
|
||||
logger.Error("sign failed", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
s.write(fmt.Sprintf("SIGNATURE R: %x\n", sig.R()))
|
||||
s.write(fmt.Sprintf("SIGNATURE S: %x\n", sig.S()))
|
||||
s.write(fmt.Sprintf("SIGNATURE V: %x\n", sig.V()))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Shell) commandKeycardSetPinlessPath(args ...string) error {
|
||||
if err := s.requireArgs(args, 1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info(fmt.Sprintf("set pinless path %s", args[0]))
|
||||
if err := s.kCmdSet.SetPinlessPath(args[0]); err != nil {
|
||||
logger.Error("set pinless path failed", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Shell) requireArgs(args []string, possibleArgsN ...int) error {
|
||||
for _, n := range possibleArgsN {
|
||||
if len(args) == n {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
ns := make([]string, len(possibleArgsN))
|
||||
for i, n := range possibleArgsN {
|
||||
ns[i] = fmt.Sprintf("%d", n)
|
||||
}
|
||||
|
||||
return fmt.Errorf("wrong number of argument. got: %d, expected: %v", len(args), strings.Join(ns, " | "))
|
||||
}
|
||||
|
||||
func (s *Shell) evalLine(rawLine string) error {
|
||||
line := strings.TrimSpace(rawLine)
|
||||
|
||||
if len(line) == 0 || strings.HasPrefix(line, "#") {
|
||||
return nil
|
||||
}
|
||||
|
||||
reg := regexp.MustCompile("\\s+")
|
||||
parts := reg.Split(line, -1)
|
||||
if cmd, ok := s.commands[parts[0]]; ok {
|
||||
return cmd(parts[1:]...)
|
||||
}
|
||||
|
||||
return fmt.Errorf("command not found: %s", parts[0])
|
||||
}
|
||||
|
||||
func (s *Shell) parseHex(str string) ([]byte, error) {
|
||||
if str[:2] == "0x" {
|
||||
str = str[2:]
|
||||
}
|
||||
|
||||
return hex.DecodeString(str)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user