add pairing actions
This commit is contained in:
parent
c3885e8a76
commit
8acb1dfbc7
|
@ -1,34 +1,57 @@
|
|||
package actions
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/status-im/smartcard-go/apdu"
|
||||
"github.com/status-im/smartcard-go/globalplatform"
|
||||
"github.com/status-im/smartcard-go/lightwallet"
|
||||
"golang.org/x/crypto/pbkdf2"
|
||||
"golang.org/x/text/unicode/norm"
|
||||
)
|
||||
|
||||
const pairingSalt = "Status Hardware Wallet Lite"
|
||||
|
||||
var (
|
||||
ErrAlreadyInitialized = errors.New("card already initialized")
|
||||
ErrNotInitialized = errors.New("card not initialized")
|
||||
ErrUnknownApplicationInfoTemplate = errors.New("unknown application info template")
|
||||
ErrInvalidCardCryptogram = errors.New("invalid card cryptogram")
|
||||
)
|
||||
|
||||
type ApplicationInfo struct {
|
||||
InstanceUID []byte
|
||||
PublicKey []byte
|
||||
Version []byte
|
||||
AvailableSlots []byte
|
||||
KeyUID []byte
|
||||
}
|
||||
|
||||
func Select(c globalplatform.Channel, aid []byte) (*ApplicationInfo, error) {
|
||||
func SelectNotInitialized(c globalplatform.Channel, aid []byte) ([]byte, error) {
|
||||
sel := globalplatform.NewCommandSelect(aid)
|
||||
resp, err := c.Send(sel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = checkOKResponse(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.Data[0] != lightwallet.TagSelectResponsePreInitialized {
|
||||
return nil, ErrAlreadyInitialized
|
||||
}
|
||||
|
||||
return resp.Data[2:], nil
|
||||
}
|
||||
|
||||
func SelectInitialized(c globalplatform.Channel, aid []byte) (*lightwallet.ApplicationInfo, error) {
|
||||
sel := globalplatform.NewCommandSelect(aid)
|
||||
resp, err := c.Send(sel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = checkOKResponse(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.Data[0] == lightwallet.TagSelectResponsePreInitialized {
|
||||
return nil, ErrNotInitialized
|
||||
}
|
||||
|
@ -36,19 +59,8 @@ func Select(c globalplatform.Channel, aid []byte) (*ApplicationInfo, error) {
|
|||
return parseApplicationInfo(resp)
|
||||
}
|
||||
|
||||
func Init(c globalplatform.Channel, secrets *lightwallet.Secrets, aid []byte) error {
|
||||
sel := globalplatform.NewCommandSelect(aid)
|
||||
resp, err := c.Send(sel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.Data[0] != lightwallet.TagSelectResponsePreInitialized {
|
||||
return ErrAlreadyInitialized
|
||||
}
|
||||
|
||||
cardKeyData := resp.Data[2:]
|
||||
secureChannel, err := lightwallet.NewSecureChannel(c, cardKeyData)
|
||||
func Init(c globalplatform.Channel, cardPubKey []byte, secrets *lightwallet.Secrets, aid []byte) error {
|
||||
secureChannel, err := lightwallet.NewSecureChannel(c, cardPubKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -59,12 +71,87 @@ func Init(c globalplatform.Channel, secrets *lightwallet.Secrets, aid []byte) er
|
|||
}
|
||||
|
||||
init := lightwallet.NewCommandInit(data)
|
||||
_, err = c.Send(init)
|
||||
resp, err := c.Send(init)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
return checkOKResponse(resp)
|
||||
}
|
||||
|
||||
func parseApplicationInfo(resp *apdu.Response) (*ApplicationInfo, error) {
|
||||
func Pair(c globalplatform.Channel, pairingPass string, pin string) (*lightwallet.PairingInfo, error) {
|
||||
secretHash := pbkdf2.Key(norm.NFKD.Bytes([]byte(pairingPass)), norm.NFKD.Bytes([]byte(pairingSalt)), 50000, 32, sha256.New)
|
||||
|
||||
challenge := make([]byte, 32)
|
||||
if _, err := rand.Read(challenge); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmd := lightwallet.NewCommandPairFirstStep(challenge)
|
||||
resp, err := c.Send(cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = checkOKResponse(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h := sha256.New()
|
||||
h.Write(secretHash[:])
|
||||
h.Write(challenge)
|
||||
|
||||
expectedCryptogram := h.Sum(nil)
|
||||
cardCryptogram := resp.Data[:32]
|
||||
cardChallenge := resp.Data[32:]
|
||||
|
||||
if !bytes.Equal(expectedCryptogram, cardCryptogram) {
|
||||
return nil, ErrInvalidCardCryptogram
|
||||
}
|
||||
|
||||
h.Reset()
|
||||
h.Write(secretHash[:])
|
||||
h.Write(cardChallenge)
|
||||
cmd = lightwallet.NewCommandPairFinalStep(h.Sum(nil))
|
||||
resp, err = c.Send(cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = checkOKResponse(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
h.Reset()
|
||||
h.Write(secretHash[:])
|
||||
h.Write(resp.Data[1:])
|
||||
|
||||
pairingKey := h.Sum(nil)
|
||||
pairingIndex := resp.Data[0]
|
||||
|
||||
return &lightwallet.PairingInfo{
|
||||
PairingKey: pairingKey,
|
||||
PairingIndex: int(pairingIndex),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func OpenSecureChannel(c globalplatform.Channel, appInfo *lightwallet.ApplicationInfo, pairingIndex uint8, pairingKey []byte) error {
|
||||
sc, err := lightwallet.NewSecureChannel(c, appInfo.PublicKey)
|
||||
|
||||
cmd := lightwallet.NewCommandOpenSecureChannel(pairingIndex, sc.RawPublicKey())
|
||||
resp, err := c.Send(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = checkOKResponse(resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseApplicationInfo(resp *apdu.Response) (*lightwallet.ApplicationInfo, error) {
|
||||
if resp.Data[0] != lightwallet.TagApplicationInfoTemplate {
|
||||
return nil, ErrUnknownApplicationInfoTemplate
|
||||
}
|
||||
|
@ -94,7 +181,7 @@ func parseApplicationInfo(resp *apdu.Response) (*ApplicationInfo, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return &ApplicationInfo{
|
||||
return &lightwallet.ApplicationInfo{
|
||||
InstanceUID: instanceUID,
|
||||
PublicKey: pubKey,
|
||||
Version: appVersion,
|
||||
|
@ -102,3 +189,17 @@ func parseApplicationInfo(resp *apdu.Response) (*ApplicationInfo, error) {
|
|||
KeyUID: keyUID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func checkOKResponse(resp *apdu.Response) error {
|
||||
return checkResponse(resp, apdu.SwOK)
|
||||
}
|
||||
|
||||
func checkResponse(resp *apdu.Response, allowedResponses ...uint16) error {
|
||||
for _, code := range allowedResponses {
|
||||
if code == resp.Sw {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("unexpected response: %x", resp.Sw)
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ func NewInstaller(t globalplatform.Transmitter) *Installer {
|
|||
|
||||
// Install installs the applet from the specified capFile.
|
||||
func (i *Installer) Install(capFile *os.File, overwriteApplet bool) error {
|
||||
err := i.initSecureChannel(cardManagerAID)
|
||||
err := i.initGPSecureChannel(cardManagerAID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -70,7 +70,12 @@ func (i *Installer) Init() (*lightwallet.Secrets, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
err = actions.Init(i.c, secrets, walletAID)
|
||||
cardPubKey, err := actions.SelectNotInitialized(i.c, walletAID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = actions.Init(i.c, cardPubKey, secrets, walletAID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -78,9 +83,18 @@ func (i *Installer) Init() (*lightwallet.Secrets, error) {
|
|||
return secrets, nil
|
||||
}
|
||||
|
||||
func (i *Installer) Pair(pairingPass, pin string) (*lightwallet.PairingInfo, error) {
|
||||
_, err := actions.SelectInitialized(i.c, walletAID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return actions.Pair(i.c, pairingPass, pin)
|
||||
}
|
||||
|
||||
// Info returns if the applet is already installed in the card.
|
||||
func (i *Installer) Info() (bool, error) {
|
||||
err := i.initSecureChannel(cardManagerAID)
|
||||
err := i.initGPSecureChannel(cardManagerAID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
@ -90,7 +104,7 @@ func (i *Installer) Info() (bool, error) {
|
|||
|
||||
// Delete deletes the applet and related package from the card.
|
||||
func (i *Installer) Delete() error {
|
||||
err := i.initSecureChannel(cardManagerAID)
|
||||
err := i.initGPSecureChannel(cardManagerAID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -112,7 +126,7 @@ func (i *Installer) isAppletInstalled() (bool, error) {
|
|||
return true, nil
|
||||
}
|
||||
|
||||
func (i *Installer) initSecureChannel(sdaid []byte) error {
|
||||
func (i *Installer) initGPSecureChannel(sdaid []byte) error {
|
||||
// select card manager
|
||||
err := i.selectAID(sdaid)
|
||||
if err != nil {
|
||||
|
|
|
@ -6,10 +6,15 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
InsInit = uint8(0xFE)
|
||||
InsInit = uint8(0xFE)
|
||||
InsOpenSecureChannel = uint8(0x10)
|
||||
InsPair = uint8(0x12)
|
||||
|
||||
TagSelectResponsePreInitialized = uint8(0x80)
|
||||
TagApplicationInfoTemplate = uint8(0xA4)
|
||||
|
||||
P1PairingFirstStep = uint8(0x00)
|
||||
P1PairingFinalStep = uint8(0x01)
|
||||
)
|
||||
|
||||
func NewCommandInit(data []byte) *apdu.Command {
|
||||
|
@ -21,3 +26,33 @@ func NewCommandInit(data []byte) *apdu.Command {
|
|||
data,
|
||||
)
|
||||
}
|
||||
|
||||
func NewCommandPairFirstStep(challenge []byte) *apdu.Command {
|
||||
return apdu.NewCommand(
|
||||
globalplatform.ClaGp,
|
||||
InsPair,
|
||||
P1PairingFirstStep,
|
||||
uint8(0x00),
|
||||
challenge,
|
||||
)
|
||||
}
|
||||
|
||||
func NewCommandPairFinalStep(cryptogramHash []byte) *apdu.Command {
|
||||
return apdu.NewCommand(
|
||||
globalplatform.ClaGp,
|
||||
InsPair,
|
||||
P1PairingFinalStep,
|
||||
uint8(0x00),
|
||||
cryptogramHash,
|
||||
)
|
||||
}
|
||||
|
||||
func NewCommandOpenSecureChannel(pairingIndex uint8, pubKey []byte) *apdu.Command {
|
||||
return apdu.NewCommand(
|
||||
globalplatform.ClaGp,
|
||||
InsOpenSecureChannel,
|
||||
pairingIndex,
|
||||
uint8(0x00),
|
||||
pubKey,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -10,9 +10,9 @@ import (
|
|||
)
|
||||
|
||||
type SecureChannel struct {
|
||||
c globalplatform.Channel
|
||||
secret []byte
|
||||
pubKey *ecdsa.PublicKey
|
||||
c globalplatform.Channel
|
||||
secret []byte
|
||||
publicKey *ecdsa.PublicKey
|
||||
}
|
||||
|
||||
func NewSecureChannel(c globalplatform.Channel, cardKeyData []byte) (*SecureChannel, error) {
|
||||
|
@ -29,18 +29,26 @@ func NewSecureChannel(c globalplatform.Channel, cardKeyData []byte) (*SecureChan
|
|||
secret := crypto.GenerateECDHSharedSecret(key, cardPubKey)
|
||||
|
||||
return &SecureChannel{
|
||||
c: c,
|
||||
secret: secret,
|
||||
pubKey: &key.PublicKey,
|
||||
c: c,
|
||||
secret: secret,
|
||||
publicKey: &key.PublicKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (sc *SecureChannel) PublicKey() *ecdsa.PublicKey {
|
||||
return sc.publicKey
|
||||
}
|
||||
|
||||
func (sc *SecureChannel) RawPublicKey() []byte {
|
||||
return ethcrypto.FromECDSAPub(sc.publicKey)
|
||||
}
|
||||
|
||||
func (sc *SecureChannel) Send(cmd *apdu.Command) (*apdu.Response, error) {
|
||||
return sc.c.Send(cmd)
|
||||
}
|
||||
|
||||
func (sc *SecureChannel) OneShotEncrypt(secrets *Secrets) ([]byte, error) {
|
||||
pubKeyData := ethcrypto.FromECDSAPub(sc.pubKey)
|
||||
pubKeyData := ethcrypto.FromECDSAPub(sc.publicKey)
|
||||
data := append([]byte(secrets.Pin()), []byte(secrets.Puk())...)
|
||||
data = append(data, secrets.PairingToken()...)
|
||||
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
package lightwallet
|
||||
|
||||
type ApplicationInfo struct {
|
||||
InstanceUID []byte
|
||||
PublicKey []byte
|
||||
Version []byte
|
||||
AvailableSlots []byte
|
||||
// KeyUID is the sha256 of of the master public key on the card.
|
||||
// It's empty if the card doesn't contain any key.
|
||||
KeyUID []byte
|
||||
}
|
||||
|
||||
type PairingInfo struct {
|
||||
PairingKey []byte
|
||||
PairingIndex int
|
||||
}
|
Loading…
Reference in New Issue