add certificate validation
This commit is contained in:
parent
104f9ded0a
commit
a839ed4597
|
@ -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")
|
||||||
|
|
17
commands.go
17
commands.go
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue