add certificate validation

This commit is contained in:
Michele Balistreri 2022-12-13 12:09:16 +01:00
parent 104f9ded0a
commit a839ed4597
4 changed files with 180 additions and 24 deletions

View File

@ -154,6 +154,22 @@ func (cs *CommandSet) Unpair(index uint8) error {
return cs.checkOK(resp, err) 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 { func (cs *CommandSet) OpenSecureChannel() error {
if cs.ApplicationInfo == nil { if cs.ApplicationInfo == nil {
return errors.New("cannot open secure channel without setting PairingInfo") return errors.New("cannot open secure channel without setting PairingInfo")

View File

@ -16,6 +16,7 @@ const (
InsMutuallyAuthenticate = 0x11 InsMutuallyAuthenticate = 0x11
InsPair = 0x12 InsPair = 0x12
InsUnpair = 0x13 InsUnpair = 0x13
InsIdentify = 0x14
InsGetStatus = 0xF2 InsGetStatus = 0xF2
InsGenerateKey = 0xD4 InsGenerateKey = 0xD4
InsRemoveKey = 0xD3 InsRemoveKey = 0xD3
@ -53,6 +54,7 @@ const (
P1ExportKeyDeriveAndMakeCurrent = 0x02 P1ExportKeyDeriveAndMakeCurrent = 0x02
P2ExportKeyPrivateAndPublic = 0x00 P2ExportKeyPrivateAndPublic = 0x00
P2ExportKeyPublicOnly = 0x01 P2ExportKeyPublicOnly = 0x01
P2ExportKeyExtendedPublic = 0x02
P1LoadKeySeed = 0x03 P1LoadKeySeed = 0x03
SwNoAvailablePairingSlots = 0x6A84 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 { func NewCommandOpenSecureChannel(pairingIndex uint8, pubKey []byte) *apdu.Command {
return apdu.NewCommand( return apdu.NewCommand(
globalplatform.ClaGp, globalplatform.ClaGp,
@ -247,13 +259,14 @@ func NewCommandDeriveKey(pathStr string) (*apdu.Command, error) {
// Export a key // 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 // 0x00: current key - returns the key that is currently loaded and ready for signing. Does not use derivation path
// 0x01: derive - returns derived key // 0x01: derive - returns derived key
// 0x02: derive and make current - returns derived key and also sets it to the current key // 0x02: derive and make current - returns derived key and also sets it to the current key
// @param {p2} // @param {p2}
// 0x00: return public and private key pair // 0x00: return public and private key pair
// 0x01: return only the public key // 0x01: return only the public key
// 0x02: return extended public key
// @param {pathStr} // @param {pathStr}
// Derivation path of format "m/x/x/x/x/x", e.g. "m/44'/0'/0'/0/0" // 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) { 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, globalplatform.ClaGp,
InsSign, InsSign,
p1, p1,
0, 1,
data, data,
), nil ), nil
} }

82
types/certificate.go Normal file
View File

@ -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]
}

View File

@ -2,6 +2,7 @@ package types
import ( import (
"bytes" "bytes"
"errors"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/status-im/keycard-go/apdu" "github.com/status-im/keycard-go/apdu"
@ -9,6 +10,7 @@ import (
var ( var (
TagSignatureTemplate = uint8(0xA0) TagSignatureTemplate = uint8(0xA0)
TagRawSignature = uint8(0x80)
) )
type Signature struct { type Signature struct {
@ -19,42 +21,61 @@ type Signature struct {
} }
func ParseSignature(message, resp []byte) (*Signature, error) { 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 { if err != nil {
return nil, err return nil, err
} }
r, err := apdu.FindTagN(resp, 0, apdu.Tag{TagSignatureTemplate}, apdu.Tag{0x30}, apdu.Tag{0x02}) return ParseRecoverableSignature(message, sig)
if err != nil { }
return nil, err
func ParseRecoverableSignature(message, sig []byte) (*Signature, error) {
if len(sig) != 65 {
return nil, errors.New("invalid signature")
} }
if len(r) > 32 { pubKey, err := crypto.Ecrecover(message, sig)
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)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &Signature{ return &Signature{
pubKey: pubKey, pubKey: pubKey,
r: r, r: sig[0:32],
s: s, s: sig[32:64],
v: v, v: sig[64],
}, nil }, 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 { func (s *Signature) PubKey() []byte {
return s.pubKey return s.pubKey
} }
@ -71,9 +92,33 @@ func (s *Signature) V() byte {
return s.v 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) { func calculateV(message, pubKey, r, s []byte) (v byte, err error) {
rs := append(r, s...) rs := append(r, s...)
for i := 0; i < 2; i++ { for i := 0; i < 4; i++ {
v = byte(i) v = byte(i)
sig := append(rs, v) sig := append(rs, v)
rec, err := crypto.Ecrecover(message, sig) rec, err := crypto.Ecrecover(message, sig)