1package main
2
3import (
4	"bufio"
5	"encoding/hex"
6	"errors"
7	"flag"
8	"fmt"
9	stdlog "log"
10	"os"
11	"strconv"
12	"strings"
13
14	"github.com/ebfe/scard"
15	"github.com/ethereum/go-ethereum/log"
16)
17
18type commandFunc func(*scard.Card) error
19
20var (
21	logger = log.New("package", "status-go/cmd/keycard")
22
23	commands map[string]commandFunc
24	command  string
25
26	flagCapFile   = flag.String("a", "", "applet cap file path")
27	flagOverwrite = flag.Bool("f", false, "force applet installation if already installed")
28	flagLogLevel  = flag.String("l", "", `Log level, one of: "error", "warn", "info", "debug", and "trace"`)
29)
30
31func initLogger() {
32	if *flagLogLevel == "" {
33		*flagLogLevel = "info"
34	}
35
36	level, err := log.LvlFromString(strings.ToLower(*flagLogLevel))
37	if err != nil {
38		stdlog.Fatal(err)
39	}
40
41	handler := log.StreamHandler(os.Stderr, log.TerminalFormat(true))
42	filteredHandler := log.LvlFilterHandler(level, handler)
43	log.Root().SetHandler(filteredHandler)
44}
45
46func init() {
47	commands = map[string]commandFunc{
48		"install": commandInstall,
49		"info":    commandInfo,
50		"delete":  commandDelete,
51		"init":    commandInit,
52		"pair":    commandPair,
53		"status":  commandStatus,
54		"shell":   commandShell,
55	}
56
57	if len(os.Args) < 2 {
58		usage()
59	}
60
61	command = os.Args[1]
62	if len(os.Args) > 2 {
63		flag.CommandLine.Parse(os.Args[2:])
64	}
65
66	initLogger()
67}
68
69func usage() {
70	fmt.Printf("\nUsage:\n  keycard COMMAND [FLAGS]\n\nAvailable commands:\n")
71	for name := range commands {
72		fmt.Printf("  %s\n", name)
73	}
74	fmt.Print("\nFlags:\n\n")
75	flag.PrintDefaults()
76	os.Exit(1)
77}
78
79func fail(msg string, ctx ...interface{}) {
80	logger.Error(msg, ctx...)
81	os.Exit(1)
82}
83
84func main() {
85	ctx, err := scard.EstablishContext()
86	if err != nil {
87		fail("error establishing card context", "error", err)
88	}
89	defer func() {
90		if err := ctx.Release(); err != nil {
91			logger.Error("error releasing context", "error", err)
92		}
93	}()
94
95	readers, err := ctx.ListReaders()
96	if err != nil {
97		fail("error getting readers", "error", err)
98	}
99
100	if len(readers) == 0 {
101		fail("couldn't find any reader")
102	}
103
104	if len(readers) > 1 {
105		fail("too many readers found")
106	}
107
108	reader := readers[0]
109	logger.Debug("using reader", "name", reader)
110	logger.Debug("connecting to card", "reader", reader)
111	card, err := ctx.Connect(reader, scard.ShareShared, scard.ProtocolAny)
112	if err != nil {
113		fail("error connecting to card", "error", err)
114	}
115	defer func() {
116		if err := card.Disconnect(scard.ResetCard); err != nil {
117			logger.Error("error disconnecting card", "error", err)
118		}
119	}()
120
121	status, err := card.Status()
122	if err != nil {
123		fail("error getting card status", "error", err)
124	}
125
126	switch status.ActiveProtocol {
127	case scard.ProtocolT0:
128		logger.Debug("card protocol", "T", "0")
129	case scard.ProtocolT1:
130		logger.Debug("card protocol", "T", "1")
131	default:
132		logger.Debug("card protocol", "T", "unknown")
133	}
134
135	if f, ok := commands[command]; ok {
136		err = f(card)
137		if err != nil {
138			logger.Error("error executing command", "command", command, "error", err)
139			os.Exit(1)
140		}
141		os.Exit(0)
142	}
143
144	fail("unknown command", "command", command)
145	usage()
146}
147
148func ask(description string) string {
149	r := bufio.NewReader(os.Stdin)
150	fmt.Printf("%s: ", description)
151	text, err := r.ReadString('\n')
152	if err != nil {
153		stdlog.Fatal(err)
154	}
155
156	return strings.TrimSpace(text)
157}
158
159func askHex(description string) []byte {
160	s := ask(description)
161	if s[:2] == "0x" {
162		s = s[2:]
163	}
164
165	data, err := hex.DecodeString(s)
166	if err != nil {
167		stdlog.Fatal(err)
168	}
169
170	return data
171}
172
173func askInt(description string) int {
174	s := ask(description)
175	i, err := strconv.ParseInt(s, 10, 8)
176	if err != nil {
177		stdlog.Fatal(err)
178	}
179
180	return int(i)
181}
182
183func commandInstall(card *scard.Card) error {
184	if *flagCapFile == "" {
185		logger.Error("you must specify a cap file path with the -f flag\n")
186		usage()
187	}
188
189	f, err := os.Open(*flagCapFile)
190	if err != nil {
191		fail("error opening cap file", "error", err)
192	}
193	defer f.Close()
194
195	i := NewInstaller(card)
196
197	return i.Install(f, *flagOverwrite)
198}
199
200func commandInfo(card *scard.Card) error {
201	i := NewInitializer(card)
202	info, err := i.Info()
203	if err != nil {
204		return err
205	}
206
207	fmt.Printf("Installed: %+v\n", info.Installed)
208	fmt.Printf("Initialized: %+v\n", info.Initialized)
209	fmt.Printf("InstanceUID: 0x%x\n", info.InstanceUID)
210	fmt.Printf("SecureChannelPublicKey: 0x%x\n", info.SecureChannelPublicKey)
211	fmt.Printf("Version: 0x%x\n", info.Version)
212	fmt.Printf("AvailableSlots: 0x%x\n", info.AvailableSlots)
213	fmt.Printf("KeyUID: 0x%x\n", info.KeyUID)
214	fmt.Printf("Capabilities:\n")
215	fmt.Printf("  Secure channel:%v\n", info.HasSecureChannelCapability())
216	fmt.Printf("  Key management:%v\n", info.HasKeyManagementCapability())
217	fmt.Printf("  Credentials Management:%v\n", info.HasCredentialsManagementCapability())
218	fmt.Printf("  NDEF:%v\n", info.HasNDEFCapability())
219
220	return nil
221}
222
223func commandDelete(card *scard.Card) error {
224	i := NewInstaller(card)
225	err := i.Delete()
226	if err != nil {
227		return err
228	}
229
230	fmt.Printf("applet deleted\n")
231
232	return nil
233}
234
235func commandInit(card *scard.Card) error {
236	i := NewInitializer(card)
237	secrets, err := i.Init()
238	if err != nil {
239		return err
240	}
241
242	fmt.Printf("PIN %s\n", secrets.Pin())
243	fmt.Printf("PUK %s\n", secrets.Puk())
244	fmt.Printf("Pairing password: %s\n", secrets.PairingPass())
245
246	return nil
247}
248
249func commandPair(card *scard.Card) error {
250	i := NewInitializer(card)
251	pairingPass := ask("Pairing password")
252	info, err := i.Pair(pairingPass)
253	if err != nil {
254		return err
255	}
256
257	fmt.Printf("Pairing key 0x%x\n", info.Key)
258	fmt.Printf("Pairing Index %d\n", info.Index)
259
260	return nil
261}
262
263func commandStatus(card *scard.Card) error {
264	i := NewInitializer(card)
265	key := askHex("Pairing key")
266	index := askInt("Pairing index")
267
268	appStatus, err := i.Status(key, index)
269	if err != nil {
270		return err
271	}
272
273	fmt.Printf("Pin retry count: %d\n", appStatus.PinRetryCount)
274	fmt.Printf("PUK retry count: %d\n", appStatus.PUKRetryCount)
275	fmt.Printf("Key initialized: %v\n", appStatus.KeyInitialized)
276
277	return nil
278}
279
280func commandShell(card *scard.Card) error {
281	fi, _ := os.Stdin.Stat()
282	if (fi.Mode() & os.ModeCharDevice) == 0 {
283		s := NewShell(card)
284		return s.Run()
285	} else {
286		return errors.New("non interactive shell. you must pipe commands")
287	}
288}
289