Skip to content

Commit

Permalink
Keycard 3.1 support (#16)
Browse files Browse the repository at this point in the history
* add certificate validation

* add factory reset command

* do not apply secure channel to factory reset
  • Loading branch information
bitgamma authored Aug 17, 2023
1 parent 104f9de commit 0d3f4e8
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 19 deletions.
22 changes: 22 additions & 0 deletions command_set.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,22 @@ func (cs *CommandSet) Unpair(index uint8) error {
return cs.checkOK(resp, err)
}

func (cs *CommandSet) Identify() ([]byte, error) {
challenge := make([]byte, 32)
if _, err := rand.Read(challenge); err != nil {
return nil, err
}

cmd := NewCommandIdentify(challenge)
resp, err := cs.sc.Send(cmd)

if err = cs.checkOK(resp, err); err != nil {
return nil, err
}

return types.VerifyIdentity(challenge, resp.Data)
}

func (cs *CommandSet) OpenSecureChannel() error {
if cs.ApplicationInfo == nil {
return errors.New("cannot open secure channel without setting PairingInfo")
Expand Down Expand Up @@ -407,6 +423,12 @@ func (cs *CommandSet) StoreData(typ uint8, data []byte) error {
return cs.checkOK(resp, err)
}

func (cs *CommandSet) FactoryReset() error {
cmd := NewCommandFactoryReset()
resp, err := cs.c.Send(cmd)
return cs.checkOK(resp, err)
}

func (cs *CommandSet) mutualAuthenticate() error {
data := make([]byte, 32)
if _, err := rand.Read(data); err != nil {
Expand Down
30 changes: 28 additions & 2 deletions commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ import (

const (
InsInit = 0xFE
InsFactoryReset = 0xFD
InsOpenSecureChannel = 0x10
InsMutuallyAuthenticate = 0x11
InsPair = 0x12
InsUnpair = 0x13
InsIdentify = 0x14
InsGetStatus = 0xF2
InsGenerateKey = 0xD4
InsRemoveKey = 0xD3
Expand Down Expand Up @@ -53,7 +55,10 @@ const (
P1ExportKeyDeriveAndMakeCurrent = 0x02
P2ExportKeyPrivateAndPublic = 0x00
P2ExportKeyPublicOnly = 0x01
P2ExportKeyExtendedPublic = 0x02
P1LoadKeySeed = 0x03
P1FactoryResetMagic = 0xAA
P2FactoryResetMagic = 0x55

SwNoAvailablePairingSlots = 0x6A84
)
Expand Down Expand Up @@ -98,6 +103,16 @@ func NewCommandUnpair(index uint8) *apdu.Command {
)
}

func NewCommandIdentify(challenge []byte) *apdu.Command {
return apdu.NewCommand(
globalplatform.ClaGp,
InsIdentify,
0,
0,
challenge,
)
}

func NewCommandOpenSecureChannel(pairingIndex uint8, pubKey []byte) *apdu.Command {
return apdu.NewCommand(
globalplatform.ClaGp,
Expand Down Expand Up @@ -247,13 +262,14 @@ func NewCommandDeriveKey(pathStr string) (*apdu.Command, error) {

// Export a key
//
// @param {p1}
// @param {p1}
// 0x00: current key - returns the key that is currently loaded and ready for signing. Does not use derivation path
// 0x01: derive - returns derived key
// 0x02: derive and make current - returns derived key and also sets it to the current key
// @param {p2}
// 0x00: return public and private key pair
// 0x01: return only the public key
// 0x02: return extended public key
// @param {pathStr}
// Derivation path of format "m/x/x/x/x/x", e.g. "m/44'/0'/0'/0/0"
func NewCommandExportKey(p1 uint8, p2 uint8, pathStr string) (*apdu.Command, error) {
Expand Down Expand Up @@ -334,7 +350,7 @@ func NewCommandSign(data []byte, p1 uint8, pathStr string) (*apdu.Command, error
globalplatform.ClaGp,
InsSign,
p1,
0,
1,
data,
), nil
}
Expand All @@ -359,6 +375,16 @@ func NewCommandStoreData(typ uint8, data []byte) *apdu.Command {
)
}

func NewCommandFactoryReset() *apdu.Command {
return apdu.NewCommand(
globalplatform.ClaGp,
InsFactoryReset,
P1FactoryResetMagic,
P2FactoryResetMagic,
[]byte{},
)
}

// Internal function. Get the type of starting point for the derivation path.
// Used for both DeriveKey and ExportKey
func derivationP1FromStartingPoint(s derivationpath.StartingPoint) (uint8, error) {
Expand Down
8 changes: 7 additions & 1 deletion types/application_info.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ const (
CapabilityKeyManagement
CapabilityCredentialsManagement
CapabilityNDEF
CapabilityFactoryReset

CapabilityAll = CapabilitySecureChannel |
CapabilityKeyManagement |
CapabilityCredentialsManagement |
CapabilityNDEF
CapabilityNDEF |
CapabilityFactoryReset
)

type ApplicationInfo struct {
Expand Down Expand Up @@ -62,6 +64,10 @@ func (a *ApplicationInfo) HasNDEFCapability() bool {
return a.HasCapability(CapabilityNDEF)
}

func (a *ApplicationInfo) HasFactoryResetCapability() bool {
return a.HasCapability(CapabilityFactoryReset)
}

func ParseApplicationInfo(data []byte) (*ApplicationInfo, error) {
info := &ApplicationInfo{
Installed: true,
Expand Down
82 changes: 82 additions & 0 deletions types/certificate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package types

import (
"crypto/sha256"
"errors"

"github.com/ethereum/go-ethereum/crypto"
"github.com/status-im/keycard-go/apdu"
)

type Certificate struct {
identPub []byte
signature *Signature
}

var (
TagCertificate = uint8(0x8A)
)

func ParseCertificate(data []byte) (*Certificate, error) {
if len(data) != 98 {
return nil, errors.New("certificate must be 98 byte long")
}

identPub := data[0:33]
sigData := data[33:97]
msg := sha256.Sum256(identPub)

sig, err := ParseRecoverableSignature(msg[:], sigData)
if err != nil {
return nil, err
}

return &Certificate{
identPub: identPub,
signature: sig,
}, nil
}

func VerifyIdentity(challenge []byte, tlvData []byte) ([]byte, error) {
template, err := apdu.FindTag(tlvData, apdu.Tag{TagSignatureTemplate})
if err != nil {
return nil, err
}

certData, err := apdu.FindTag(template, apdu.Tag{TagCertificate})
if err != nil {
return nil, err
}

cert, err := ParseCertificate(certData)
if err != nil {
return nil, err
}

r, s, err := DERSignatureToRS(template)
if err != nil {
return nil, err
}

sig := append(r, s...)

if !crypto.VerifySignature(cert.identPub, challenge, sig) {
return nil, errors.New("invalid signature")
}

return compressPublicKey(cert.signature.pubKey), nil
}

func compressPublicKey(pubKey []byte) []byte {
if len(pubKey) == 33 {
return pubKey
}

if (pubKey[63] & 1) == 1 {
pubKey[0] = 3
} else {
pubKey[0] = 2
}

return pubKey[0:33]
}
77 changes: 61 additions & 16 deletions types/signature.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ package types

import (
"bytes"
"errors"

"github.com/ethereum/go-ethereum/crypto"
"github.com/status-im/keycard-go/apdu"
)

var (
TagSignatureTemplate = uint8(0xA0)
TagRawSignature = uint8(0x80)
)

type Signature struct {
Expand All @@ -19,40 +21,59 @@ type Signature struct {
}

func ParseSignature(message, resp []byte) (*Signature, error) {
pubKey, err := apdu.FindTag(resp, apdu.Tag{TagSignatureTemplate}, apdu.Tag{0x80})
// check for old template first because TagRawSignature matches the pubkey tag
template, err := apdu.FindTag(resp, apdu.Tag{TagSignatureTemplate})
if err == nil {
return parseLegacySignature(message, template)
}

sig, err := apdu.FindTag(resp, apdu.Tag{TagRawSignature})

if err != nil {
return nil, err
}

r, err := apdu.FindTagN(resp, 0, apdu.Tag{TagSignatureTemplate}, apdu.Tag{0x30}, apdu.Tag{0x02})
return ParseRecoverableSignature(message, sig)
}

func ParseRecoverableSignature(message, sig []byte) (*Signature, error) {
if len(sig) != 65 {
return nil, errors.New("invalid signature")
}

pubKey, err := crypto.Ecrecover(message, sig)
if err != nil {
return nil, err
}

return &Signature{
pubKey: pubKey,
r: sig[0:32],
s: sig[32:64],
v: sig[64],
}, nil
}

func DERSignatureToRS(tlv []byte) ([]byte, []byte, error) {
r, err := apdu.FindTagN(tlv, 0, apdu.Tag{0x30}, apdu.Tag{0x02})
if err != nil {
return nil, nil, err
}

if len(r) > 32 {
r = r[len(r)-32:]
}

s, err := apdu.FindTagN(resp, 1, apdu.Tag{TagSignatureTemplate}, apdu.Tag{0x30}, apdu.Tag{0x02})
s, err := apdu.FindTagN(tlv, 1, apdu.Tag{0x30}, apdu.Tag{0x02})
if err != nil {
return nil, err
return nil, nil, err
}

if len(s) > 32 {
s = s[len(s)-32:]
}

v, err := calculateV(message, pubKey, r, s)
if err != nil {
return nil, err
}

return &Signature{
pubKey: pubKey,
r: r,
s: s,
v: v,
}, nil
return r, s, nil
}

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

func parseLegacySignature(message, template []byte) (*Signature, error) {
pubKey, err := apdu.FindTag(template, apdu.Tag{0x80})
if err != nil {
return nil, err
}

r, s, err := DERSignatureToRS(template)
if err != nil {
return nil, err
}

v, err := calculateV(message, pubKey, r, s)
if err != nil {
return nil, err
}

return &Signature{
pubKey: pubKey,
r: r,
s: s,
v: v,
}, nil
}

func calculateV(message, pubKey, r, s []byte) (v byte, err error) {
rs := append(r, s...)
for i := 0; i < 2; i++ {
for i := 0; i < 4; i++ {
v = byte(i)
sig := append(rs, v)
rec, err := crypto.Ecrecover(message, sig)
Expand Down

0 comments on commit 0d3f4e8

Please sign in to comment.