From a839ed4597f51875613a014a158c1a9bffd1733b Mon Sep 17 00:00:00 2001 From: Michele Balistreri Date: Tue, 13 Dec 2022 12:09:16 +0100 Subject: [PATCH] add certificate validation --- command_set.go | 16 ++++++++ commands.go | 17 ++++++++- types/certificate.go | 82 ++++++++++++++++++++++++++++++++++++++++ types/signature.go | 89 +++++++++++++++++++++++++++++++++----------- 4 files changed, 180 insertions(+), 24 deletions(-) create mode 100644 types/certificate.go diff --git a/command_set.go b/command_set.go index 7d5d2e5..220bed7 100644 --- a/command_set.go +++ b/command_set.go @@ -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") diff --git a/commands.go b/commands.go index c986f9e..97452fe 100644 --- a/commands.go +++ b/commands.go @@ -16,6 +16,7 @@ const ( InsMutuallyAuthenticate = 0x11 InsPair = 0x12 InsUnpair = 0x13 + InsIdentify = 0x14 InsGetStatus = 0xF2 InsGenerateKey = 0xD4 InsRemoveKey = 0xD3 @@ -53,6 +54,7 @@ const ( P1ExportKeyDeriveAndMakeCurrent = 0x02 P2ExportKeyPrivateAndPublic = 0x00 P2ExportKeyPublicOnly = 0x01 + P2ExportKeyExtendedPublic = 0x02 P1LoadKeySeed = 0x03 SwNoAvailablePairingSlots = 0x6A84 @@ -98,6 +100,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, @@ -247,13 +259,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) { @@ -334,7 +347,7 @@ func NewCommandSign(data []byte, p1 uint8, pathStr string) (*apdu.Command, error globalplatform.ClaGp, InsSign, p1, - 0, + 1, data, ), nil } diff --git a/types/certificate.go b/types/certificate.go new file mode 100644 index 0000000..8e3f8e6 --- /dev/null +++ b/types/certificate.go @@ -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] +} diff --git a/types/signature.go b/types/signature.go index 0e6c72f..82a71d6 100644 --- a/types/signature.go +++ b/types/signature.go @@ -2,6 +2,7 @@ package types import ( "bytes" + "errors" "github.com/ethereum/go-ethereum/crypto" "github.com/status-im/keycard-go/apdu" @@ -9,6 +10,7 @@ import ( var ( TagSignatureTemplate = uint8(0xA0) + TagRawSignature = uint8(0x80) ) type Signature struct { @@ -19,42 +21,61 @@ 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}) - if err != nil { - return nil, err + return ParseRecoverableSignature(message, sig) +} + +func ParseRecoverableSignature(message, sig []byte) (*Signature, error) { + if len(sig) != 65 { + return nil, errors.New("invalid signature") } - if len(r) > 32 { - r = r[len(r)-32:] - } - - s, err := apdu.FindTagN(resp, 1, apdu.Tag{TagSignatureTemplate}, apdu.Tag{0x30}, apdu.Tag{0x02}) - if err != nil { - return nil, err - } - - if len(s) > 32 { - s = s[len(s)-32:] - } - - v, err := calculateV(message, pubKey, r, s) + pubKey, err := crypto.Ecrecover(message, sig) if err != nil { return nil, err } return &Signature{ pubKey: pubKey, - r: r, - s: s, - v: v, + 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(tlv, 1, apdu.Tag{0x30}, apdu.Tag{0x02}) + if err != nil { + return nil, nil, err + } + + if len(s) > 32 { + s = s[len(s)-32:] + } + + return r, s, nil +} + func (s *Signature) PubKey() []byte { return s.pubKey } @@ -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)