Skip to content

Commit 0d3f4e8

Browse files
authored
Keycard 3.1 support (#16)
* add certificate validation * add factory reset command * do not apply secure channel to factory reset
1 parent 104f9de commit 0d3f4e8

File tree

5 files changed

+200
-19
lines changed

5 files changed

+200
-19
lines changed

command_set.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,22 @@ func (cs *CommandSet) Unpair(index uint8) error {
154154
return cs.checkOK(resp, err)
155155
}
156156

157+
func (cs *CommandSet) Identify() ([]byte, error) {
158+
challenge := make([]byte, 32)
159+
if _, err := rand.Read(challenge); err != nil {
160+
return nil, err
161+
}
162+
163+
cmd := NewCommandIdentify(challenge)
164+
resp, err := cs.sc.Send(cmd)
165+
166+
if err = cs.checkOK(resp, err); err != nil {
167+
return nil, err
168+
}
169+
170+
return types.VerifyIdentity(challenge, resp.Data)
171+
}
172+
157173
func (cs *CommandSet) OpenSecureChannel() error {
158174
if cs.ApplicationInfo == nil {
159175
return errors.New("cannot open secure channel without setting PairingInfo")
@@ -407,6 +423,12 @@ func (cs *CommandSet) StoreData(typ uint8, data []byte) error {
407423
return cs.checkOK(resp, err)
408424
}
409425

426+
func (cs *CommandSet) FactoryReset() error {
427+
cmd := NewCommandFactoryReset()
428+
resp, err := cs.c.Send(cmd)
429+
return cs.checkOK(resp, err)
430+
}
431+
410432
func (cs *CommandSet) mutualAuthenticate() error {
411433
data := make([]byte, 32)
412434
if _, err := rand.Read(data); err != nil {

commands.go

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ import (
1212

1313
const (
1414
InsInit = 0xFE
15+
InsFactoryReset = 0xFD
1516
InsOpenSecureChannel = 0x10
1617
InsMutuallyAuthenticate = 0x11
1718
InsPair = 0x12
1819
InsUnpair = 0x13
20+
InsIdentify = 0x14
1921
InsGetStatus = 0xF2
2022
InsGenerateKey = 0xD4
2123
InsRemoveKey = 0xD3
@@ -53,7 +55,10 @@ const (
5355
P1ExportKeyDeriveAndMakeCurrent = 0x02
5456
P2ExportKeyPrivateAndPublic = 0x00
5557
P2ExportKeyPublicOnly = 0x01
58+
P2ExportKeyExtendedPublic = 0x02
5659
P1LoadKeySeed = 0x03
60+
P1FactoryResetMagic = 0xAA
61+
P2FactoryResetMagic = 0x55
5762

5863
SwNoAvailablePairingSlots = 0x6A84
5964
)
@@ -98,6 +103,16 @@ func NewCommandUnpair(index uint8) *apdu.Command {
98103
)
99104
}
100105

106+
func NewCommandIdentify(challenge []byte) *apdu.Command {
107+
return apdu.NewCommand(
108+
globalplatform.ClaGp,
109+
InsIdentify,
110+
0,
111+
0,
112+
challenge,
113+
)
114+
}
115+
101116
func NewCommandOpenSecureChannel(pairingIndex uint8, pubKey []byte) *apdu.Command {
102117
return apdu.NewCommand(
103118
globalplatform.ClaGp,
@@ -247,13 +262,14 @@ func NewCommandDeriveKey(pathStr string) (*apdu.Command, error) {
247262

248263
// Export a key
249264
//
250-
// @param {p1}
265+
// @param {p1}
251266
// 0x00: current key - returns the key that is currently loaded and ready for signing. Does not use derivation path
252267
// 0x01: derive - returns derived key
253268
// 0x02: derive and make current - returns derived key and also sets it to the current key
254269
// @param {p2}
255270
// 0x00: return public and private key pair
256271
// 0x01: return only the public key
272+
// 0x02: return extended public key
257273
// @param {pathStr}
258274
// Derivation path of format "m/x/x/x/x/x", e.g. "m/44'/0'/0'/0/0"
259275
func NewCommandExportKey(p1 uint8, p2 uint8, pathStr string) (*apdu.Command, error) {
@@ -334,7 +350,7 @@ func NewCommandSign(data []byte, p1 uint8, pathStr string) (*apdu.Command, error
334350
globalplatform.ClaGp,
335351
InsSign,
336352
p1,
337-
0,
353+
1,
338354
data,
339355
), nil
340356
}
@@ -359,6 +375,16 @@ func NewCommandStoreData(typ uint8, data []byte) *apdu.Command {
359375
)
360376
}
361377

378+
func NewCommandFactoryReset() *apdu.Command {
379+
return apdu.NewCommand(
380+
globalplatform.ClaGp,
381+
InsFactoryReset,
382+
P1FactoryResetMagic,
383+
P2FactoryResetMagic,
384+
[]byte{},
385+
)
386+
}
387+
362388
// Internal function. Get the type of starting point for the derivation path.
363389
// Used for both DeriveKey and ExportKey
364390
func derivationP1FromStartingPoint(s derivationpath.StartingPoint) (uint8, error) {

types/application_info.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@ const (
2222
CapabilityKeyManagement
2323
CapabilityCredentialsManagement
2424
CapabilityNDEF
25+
CapabilityFactoryReset
2526

2627
CapabilityAll = CapabilitySecureChannel |
2728
CapabilityKeyManagement |
2829
CapabilityCredentialsManagement |
29-
CapabilityNDEF
30+
CapabilityNDEF |
31+
CapabilityFactoryReset
3032
)
3133

3234
type ApplicationInfo struct {
@@ -62,6 +64,10 @@ func (a *ApplicationInfo) HasNDEFCapability() bool {
6264
return a.HasCapability(CapabilityNDEF)
6365
}
6466

67+
func (a *ApplicationInfo) HasFactoryResetCapability() bool {
68+
return a.HasCapability(CapabilityFactoryReset)
69+
}
70+
6571
func ParseApplicationInfo(data []byte) (*ApplicationInfo, error) {
6672
info := &ApplicationInfo{
6773
Installed: true,

types/certificate.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package types
2+
3+
import (
4+
"crypto/sha256"
5+
"errors"
6+
7+
"github.com/ethereum/go-ethereum/crypto"
8+
"github.com/status-im/keycard-go/apdu"
9+
)
10+
11+
type Certificate struct {
12+
identPub []byte
13+
signature *Signature
14+
}
15+
16+
var (
17+
TagCertificate = uint8(0x8A)
18+
)
19+
20+
func ParseCertificate(data []byte) (*Certificate, error) {
21+
if len(data) != 98 {
22+
return nil, errors.New("certificate must be 98 byte long")
23+
}
24+
25+
identPub := data[0:33]
26+
sigData := data[33:97]
27+
msg := sha256.Sum256(identPub)
28+
29+
sig, err := ParseRecoverableSignature(msg[:], sigData)
30+
if err != nil {
31+
return nil, err
32+
}
33+
34+
return &Certificate{
35+
identPub: identPub,
36+
signature: sig,
37+
}, nil
38+
}
39+
40+
func VerifyIdentity(challenge []byte, tlvData []byte) ([]byte, error) {
41+
template, err := apdu.FindTag(tlvData, apdu.Tag{TagSignatureTemplate})
42+
if err != nil {
43+
return nil, err
44+
}
45+
46+
certData, err := apdu.FindTag(template, apdu.Tag{TagCertificate})
47+
if err != nil {
48+
return nil, err
49+
}
50+
51+
cert, err := ParseCertificate(certData)
52+
if err != nil {
53+
return nil, err
54+
}
55+
56+
r, s, err := DERSignatureToRS(template)
57+
if err != nil {
58+
return nil, err
59+
}
60+
61+
sig := append(r, s...)
62+
63+
if !crypto.VerifySignature(cert.identPub, challenge, sig) {
64+
return nil, errors.New("invalid signature")
65+
}
66+
67+
return compressPublicKey(cert.signature.pubKey), nil
68+
}
69+
70+
func compressPublicKey(pubKey []byte) []byte {
71+
if len(pubKey) == 33 {
72+
return pubKey
73+
}
74+
75+
if (pubKey[63] & 1) == 1 {
76+
pubKey[0] = 3
77+
} else {
78+
pubKey[0] = 2
79+
}
80+
81+
return pubKey[0:33]
82+
}

types/signature.go

Lines changed: 61 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ package types
22

33
import (
44
"bytes"
5+
"errors"
56

67
"github.com/ethereum/go-ethereum/crypto"
78
"github.com/status-im/keycard-go/apdu"
89
)
910

1011
var (
1112
TagSignatureTemplate = uint8(0xA0)
13+
TagRawSignature = uint8(0x80)
1214
)
1315

1416
type Signature struct {
@@ -19,40 +21,59 @@ type Signature struct {
1921
}
2022

2123
func ParseSignature(message, resp []byte) (*Signature, error) {
22-
pubKey, err := apdu.FindTag(resp, apdu.Tag{TagSignatureTemplate}, apdu.Tag{0x80})
24+
// check for old template first because TagRawSignature matches the pubkey tag
25+
template, err := apdu.FindTag(resp, apdu.Tag{TagSignatureTemplate})
26+
if err == nil {
27+
return parseLegacySignature(message, template)
28+
}
29+
30+
sig, err := apdu.FindTag(resp, apdu.Tag{TagRawSignature})
31+
2332
if err != nil {
2433
return nil, err
2534
}
2635

27-
r, err := apdu.FindTagN(resp, 0, apdu.Tag{TagSignatureTemplate}, apdu.Tag{0x30}, apdu.Tag{0x02})
36+
return ParseRecoverableSignature(message, sig)
37+
}
38+
39+
func ParseRecoverableSignature(message, sig []byte) (*Signature, error) {
40+
if len(sig) != 65 {
41+
return nil, errors.New("invalid signature")
42+
}
43+
44+
pubKey, err := crypto.Ecrecover(message, sig)
2845
if err != nil {
2946
return nil, err
3047
}
3148

49+
return &Signature{
50+
pubKey: pubKey,
51+
r: sig[0:32],
52+
s: sig[32:64],
53+
v: sig[64],
54+
}, nil
55+
}
56+
57+
func DERSignatureToRS(tlv []byte) ([]byte, []byte, error) {
58+
r, err := apdu.FindTagN(tlv, 0, apdu.Tag{0x30}, apdu.Tag{0x02})
59+
if err != nil {
60+
return nil, nil, err
61+
}
62+
3263
if len(r) > 32 {
3364
r = r[len(r)-32:]
3465
}
3566

36-
s, err := apdu.FindTagN(resp, 1, apdu.Tag{TagSignatureTemplate}, apdu.Tag{0x30}, apdu.Tag{0x02})
67+
s, err := apdu.FindTagN(tlv, 1, apdu.Tag{0x30}, apdu.Tag{0x02})
3768
if err != nil {
38-
return nil, err
69+
return nil, nil, err
3970
}
4071

4172
if len(s) > 32 {
4273
s = s[len(s)-32:]
4374
}
4475

45-
v, err := calculateV(message, pubKey, r, s)
46-
if err != nil {
47-
return nil, err
48-
}
49-
50-
return &Signature{
51-
pubKey: pubKey,
52-
r: r,
53-
s: s,
54-
v: v,
55-
}, nil
76+
return r, s, nil
5677
}
5778

5879
func (s *Signature) PubKey() []byte {
@@ -71,9 +92,33 @@ func (s *Signature) V() byte {
7192
return s.v
7293
}
7394

95+
func parseLegacySignature(message, template []byte) (*Signature, error) {
96+
pubKey, err := apdu.FindTag(template, apdu.Tag{0x80})
97+
if err != nil {
98+
return nil, err
99+
}
100+
101+
r, s, err := DERSignatureToRS(template)
102+
if err != nil {
103+
return nil, err
104+
}
105+
106+
v, err := calculateV(message, pubKey, r, s)
107+
if err != nil {
108+
return nil, err
109+
}
110+
111+
return &Signature{
112+
pubKey: pubKey,
113+
r: r,
114+
s: s,
115+
v: v,
116+
}, nil
117+
}
118+
74119
func calculateV(message, pubKey, r, s []byte) (v byte, err error) {
75120
rs := append(r, s...)
76-
for i := 0; i < 2; i++ {
121+
for i := 0; i < 4; i++ {
77122
v = byte(i)
78123
sig := append(rs, v)
79124
rec, err := crypto.Ecrecover(message, sig)

0 commit comments

Comments
 (0)