add pairing actions

This commit is contained in:
Andrea Franz 2018-10-24 18:16:14 +02:00
parent c3885e8a76
commit 8acb1dfbc7
No known key found for this signature in database
GPG Key ID: 4F0D2F2D9DE7F29D
5 changed files with 213 additions and 39 deletions

View File

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

View File

@ -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 {

View File

@ -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,
)
}

View File

@ -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()...)

16
lightwallet/types.go Normal file
View File

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