1package keycard
2
3import (
4	"crypto/rand"
5	"crypto/sha256"
6	"errors"
7
8	"github.com/status-im/keycard-go/apdu"
9	"github.com/status-im/keycard-go/crypto"
10	"github.com/status-im/keycard-go/globalplatform"
11	"github.com/status-im/keycard-go/identifiers"
12	"github.com/status-im/keycard-go/types"
13)
14
15type CommandSet struct {
16	c               types.Channel
17	sc              *SecureChannel
18	ApplicationInfo *types.ApplicationInfo
19	PairingInfo     *types.PairingInfo
20}
21
22func NewCommandSet(c types.Channel) *CommandSet {
23	return &CommandSet{
24		c:  c,
25		sc: NewSecureChannel(c),
26	}
27}
28
29func (cs *CommandSet) SetPairingInfo(key []byte, index int) {
30	cs.PairingInfo = &types.PairingInfo{
31		Key:   key,
32		Index: index,
33	}
34}
35
36func (cs *CommandSet) Select() error {
37	instanceAID, err := identifiers.KeycardInstanceAID(identifiers.KeycardDefaultInstanceIndex)
38	if err != nil {
39		return err
40	}
41
42	cmd := apdu.NewCommand(
43		0x00,
44		globalplatform.InsSelect,
45		uint8(0x04),
46		uint8(0x00),
47		instanceAID,
48	)
49
50	cmd.SetLe(0)
51	resp, err := cs.c.Send(cmd)
52	if err = cs.checkOK(resp, err); err != nil {
53		cs.ApplicationInfo = &types.ApplicationInfo{}
54		return err
55	}
56
57	appInfo, err := types.ParseApplicationInfo(resp.Data)
58	if err != nil {
59		return err
60	}
61
62	cs.ApplicationInfo = appInfo
63
64	if cs.ApplicationInfo.HasSecureChannelCapability() {
65		err = cs.sc.GenerateSecret(cs.ApplicationInfo.SecureChannelPublicKey)
66		if err != nil {
67			return err
68		}
69
70		cs.sc.Reset()
71	}
72
73	return nil
74}
75
76func (cs *CommandSet) Init(secrets *Secrets) error {
77	data, err := cs.sc.OneShotEncrypt(secrets)
78	if err != nil {
79		return err
80	}
81
82	init := NewCommandInit(data)
83	resp, err := cs.c.Send(init)
84
85	return cs.checkOK(resp, err)
86}
87
88func (cs *CommandSet) Pair(pairingPass string) error {
89	challenge := make([]byte, 32)
90	if _, err := rand.Read(challenge); err != nil {
91		return err
92	}
93
94	cmd := NewCommandPairFirstStep(challenge)
95	resp, err := cs.c.Send(cmd)
96	if err = cs.checkOK(resp, err); err != nil {
97		return err
98	}
99
100	cardCryptogram := resp.Data[:32]
101	cardChallenge := resp.Data[32:]
102
103	secretHash, err := crypto.VerifyCryptogram(challenge, pairingPass, cardCryptogram)
104	if err != nil {
105		return err
106	}
107
108	h := sha256.New()
109	h.Write(secretHash[:])
110	h.Write(cardChallenge)
111	cmd = NewCommandPairFinalStep(h.Sum(nil))
112	resp, err = cs.c.Send(cmd)
113	if err = cs.checkOK(resp, err); err != nil {
114		return err
115	}
116
117	h.Reset()
118	h.Write(secretHash[:])
119	h.Write(resp.Data[1:])
120
121	pairingKey := h.Sum(nil)
122	pairingIndex := resp.Data[0]
123
124	cs.PairingInfo = &types.PairingInfo{
125		Key:   pairingKey,
126		Index: int(pairingIndex),
127	}
128
129	return nil
130}
131
132func (cs *CommandSet) OpenSecureChannel() error {
133	if cs.ApplicationInfo == nil {
134		return errors.New("cannot open secure channel without setting PairingInfo")
135	}
136
137	cmd := NewCommandOpenSecureChannel(uint8(cs.PairingInfo.Index), cs.sc.RawPublicKey())
138	resp, err := cs.c.Send(cmd)
139	if err = cs.checkOK(resp, err); err != nil {
140		return err
141	}
142
143	encKey, macKey, iv := crypto.DeriveSessionKeys(cs.sc.Secret(), cs.PairingInfo.Key, resp.Data)
144	cs.sc.Init(iv, encKey, macKey)
145
146	err = cs.mutualAuthenticate()
147	if err != nil {
148		return err
149	}
150
151	return nil
152}
153
154func (cs *CommandSet) GetStatus(info uint8) (*types.ApplicationStatus, error) {
155	cmd := NewCommandGetStatus(info)
156	resp, err := cs.sc.Send(cmd)
157	if err = cs.checkOK(resp, err); err != nil {
158		return nil, err
159	}
160
161	return types.ParseApplicationStatus(resp.Data)
162}
163
164func (cs *CommandSet) GetStatusApplication() (*types.ApplicationStatus, error) {
165	return cs.GetStatus(P1GetStatusApplication)
166}
167
168func (cs *CommandSet) GetStatusKeyPath() (*types.ApplicationStatus, error) {
169	return cs.GetStatus(P1GetStatusKeyPath)
170}
171
172func (cs *CommandSet) VerifyPIN(pin string) error {
173	cmd := NewCommandVerifyPIN(pin)
174	resp, err := cs.sc.Send(cmd)
175
176	return cs.checkOK(resp, err)
177}
178
179func (cs *CommandSet) GenerateKey() ([]byte, error) {
180	cmd := NewCommandGenerateKey()
181	resp, err := cs.sc.Send(cmd)
182	if err = cs.checkOK(resp, err); err != nil {
183		return nil, err
184	}
185
186	return resp.Data, nil
187}
188
189func (cs *CommandSet) DeriveKey(path string) error {
190	cmd, err := NewCommandDeriveKey(path)
191	if err != nil {
192		return err
193	}
194
195	resp, err := cs.sc.Send(cmd)
196	return cs.checkOK(resp, err)
197}
198
199func (cs *CommandSet) SetPinlessPath(path string) error {
200	cmd, err := NewCommandDeriveKey(path)
201	if err != nil {
202		return err
203	}
204
205	resp, err := cs.sc.Send(cmd)
206	return cs.checkOK(resp, err)
207}
208
209func (cs *CommandSet) Sign(data []byte) (*types.Signature, error) {
210	cmd, err := NewCommandSign(data)
211	if err != nil {
212		return nil, err
213	}
214
215	resp, err := cs.sc.Send(cmd)
216	if err = cs.checkOK(resp, err); err != nil {
217		return nil, err
218	}
219
220	return types.ParseSignature(data, resp.Data)
221}
222
223func (cs *CommandSet) mutualAuthenticate() error {
224	data := make([]byte, 32)
225	if _, err := rand.Read(data); err != nil {
226		return err
227	}
228
229	cmd := NewCommandMutuallyAuthenticate(data)
230	resp, err := cs.sc.Send(cmd)
231
232	return cs.checkOK(resp, err)
233}
234
235func (cs *CommandSet) checkOK(resp *apdu.Response, err error, allowedResponses ...uint16) error {
236	if err != nil {
237		return err
238	}
239
240	if len(allowedResponses) == 0 {
241		allowedResponses = []uint16{apdu.SwOK}
242	}
243
244	for _, code := range allowedResponses {
245		if code == resp.Sw {
246			return nil
247		}
248	}
249
250	return apdu.NewErrBadResponse(resp.Sw, "unexpected response")
251}
252