1package keycard
2
3import (
4	"bytes"
5	"crypto/ecdsa"
6	"errors"
7
8	ethcrypto "github.com/ethereum/go-ethereum/crypto"
9	"github.com/status-im/keycard-go/apdu"
10	"github.com/status-im/keycard-go/crypto"
11	"github.com/status-im/keycard-go/globalplatform"
12	"github.com/status-im/keycard-go/types"
13)
14
15var ErrInvalidResponseMAC = errors.New("invalid response MAC")
16
17type SecureChannel struct {
18	c         types.Channel
19	open      bool
20	secret    []byte
21	publicKey *ecdsa.PublicKey
22	encKey    []byte
23	macKey    []byte
24	iv        []byte
25}
26
27func NewSecureChannel(c types.Channel) *SecureChannel {
28	return &SecureChannel{
29		c: c,
30	}
31}
32
33func (sc *SecureChannel) GenerateSecret(cardPubKeyData []byte) error {
34	key, err := ethcrypto.GenerateKey()
35	if err != nil {
36		return err
37	}
38
39	cardPubKey, err := ethcrypto.UnmarshalPubkey(cardPubKeyData)
40	if err != nil {
41		return err
42	}
43
44	sc.publicKey = &key.PublicKey
45	sc.secret = crypto.GenerateECDHSharedSecret(key, cardPubKey)
46
47	return nil
48}
49
50func (sc *SecureChannel) Reset() {
51	sc.open = false
52}
53
54func (sc *SecureChannel) Init(iv, encKey, macKey []byte) {
55	sc.iv = iv
56	sc.encKey = encKey
57	sc.macKey = macKey
58	sc.open = true
59}
60
61func (sc *SecureChannel) Secret() []byte {
62	return sc.secret
63}
64
65func (sc *SecureChannel) PublicKey() *ecdsa.PublicKey {
66	return sc.publicKey
67}
68
69func (sc *SecureChannel) RawPublicKey() []byte {
70	return ethcrypto.FromECDSAPub(sc.publicKey)
71}
72
73func (sc *SecureChannel) Send(cmd *apdu.Command) (*apdu.Response, error) {
74	if sc.open {
75		encData, err := crypto.EncryptData(cmd.Data, sc.encKey, sc.iv)
76		if err != nil {
77			return nil, err
78		}
79
80		meta := []byte{cmd.Cla, cmd.Ins, cmd.P1, cmd.P2, byte(len(encData) + 16), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
81		if err = sc.updateIV(meta, encData); err != nil {
82			return nil, err
83		}
84
85		newData := append(sc.iv, encData...)
86		cmd.Data = newData
87	}
88
89	resp, err := sc.c.Send(cmd)
90	if err != nil {
91		return nil, err
92	}
93
94	if resp.Sw != globalplatform.SwOK {
95		return nil, apdu.NewErrBadResponse(resp.Sw, "unexpected sw in secure channel")
96	}
97
98	rmeta := []byte{byte(len(resp.Data)), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
99	rmac := resp.Data[:len(sc.iv)]
100	rdata := resp.Data[len(sc.iv):]
101	plainData, err := crypto.DecryptData(rdata, sc.encKey, sc.iv)
102	if err = sc.updateIV(rmeta, rdata); err != nil {
103		return nil, err
104	}
105
106	if !bytes.Equal(sc.iv, rmac) {
107		return nil, ErrInvalidResponseMAC
108	}
109
110	return apdu.ParseResponse(plainData)
111}
112
113func (sc *SecureChannel) updateIV(meta, data []byte) error {
114	mac, err := crypto.CalculateMac(meta, data, sc.macKey)
115	if err != nil {
116		return err
117	}
118
119	sc.iv = mac
120
121	return nil
122}
123
124func (sc *SecureChannel) OneShotEncrypt(secrets *Secrets) ([]byte, error) {
125	pubKeyData := ethcrypto.FromECDSAPub(sc.publicKey)
126	data := append([]byte(secrets.Pin()), []byte(secrets.Puk())...)
127	data = append(data, secrets.PairingToken()...)
128
129	return crypto.OneShotEncrypt(pubKeyData, sc.secret, data)
130}
131