diff --git a/actions.go b/actions.go index dbada6f..9208917 100644 --- a/actions.go +++ b/actions.go @@ -18,7 +18,7 @@ var ( ErrApplicationStatusTemplateNotFound = errors.New("application status template not found") ) -func Pair(c types.Channel, pairingPass string, pin string) (*types.PairingInfo, error) { +func Pair(c types.Channel, pairingPass string) (*types.PairingInfo, error) { challenge := make([]byte, 32) if _, err := rand.Read(challenge); err != nil { return nil, err @@ -61,7 +61,7 @@ func Pair(c types.Channel, pairingPass string, pin string) (*types.PairingInfo, } func OpenSecureChannel(c types.Channel, appInfo *types.ApplicationInfo, pairingIndex uint8, pairingKey []byte) (*SecureChannel, error) { - sc, err := NewSecureChannel(c, appInfo.PublicKey) + sc := NewSecureChannel(c) cmd := NewCommandOpenSecureChannel(pairingIndex, sc.RawPublicKey()) resp, err := c.Send(cmd) if err = checkOKResponse(err, resp); err != nil { diff --git a/cmd/keycard/initializer.go b/cmd/keycard/initializer.go index fe647cf..9627ef1 100644 --- a/cmd/keycard/initializer.go +++ b/cmd/keycard/initializer.go @@ -4,7 +4,6 @@ import ( "crypto/rand" "errors" "fmt" - "os" keycard "github.com/status-im/keycard-go" "github.com/status-im/keycard-go/apdu" @@ -59,6 +58,7 @@ func (i *Initializer) Init() (*keycard.Secrets, error) { return nil, errCardAlreadyInitialized } + logger.Info("initializing") err = cmdSet.Init(secrets) if err != nil { return nil, err @@ -69,16 +69,41 @@ func (i *Initializer) Init() (*keycard.Secrets, error) { // Info returns a types.ApplicationInfo struct with info about the card. func (i *Initializer) Info() (types.ApplicationInfo, error) { + logger.Info("info started") cmdSet := keycard.NewCommandSet(i.c) + logger.Info("select keycard applet") err := cmdSet.Select() if e, ok := err.(*apdu.ErrBadResponse); ok && e.Sw == globalplatform.SwFileNotFound { err = nil + } else { + logger.Error("select failed", "error", err) } return cmdSet.ApplicationInfo, err } +func (i *Initializer) Pair(pairingPass string) (*types.PairingInfo, error) { + logger.Info("pairing started") + cmdSet := keycard.NewCommandSet(i.c) + + logger.Info("select keycard applet") + err := cmdSet.Select() + if err != nil { + logger.Error("select failed", "error", err) + return nil, err + } + + if !cmdSet.ApplicationInfo.Initialized { + logger.Error("pairing failed", "error", ErrNotInitialized) + return nil, ErrNotInitialized + } + + logger.Info("pairing") + err = cmdSet.Pair(pairingPass) + return cmdSet.PairingInfo, err +} + func (i *Initializer) initGPSecureChannel(sdaid []byte) error { // select card manager err := i.selectAID(sdaid) @@ -136,57 +161,6 @@ func (i *Initializer) externalAuthenticate(session *globalplatform.Session) erro return err } -func (i *Initializer) deleteAID(aids ...[]byte) error { - for _, aid := range aids { - del := globalplatform.NewCommandDelete(aid) - _, err := i.send("delete", del, globalplatform.SwOK, globalplatform.SwReferencedDataNotFound) - if err != nil { - return err - } - } - - return nil -} - -func (i *Initializer) installApplets(capFile *os.File) error { - // install for load - preLoad := globalplatform.NewCommandInstallForLoad(identifiers.PackageAID, identifiers.CardManagerAID) - _, err := i.send("install for load", preLoad) - if err != nil { - return err - } - - // load - load, err := globalplatform.NewLoadCommandStream(capFile) - if err != nil { - return err - } - - for load.Next() { - cmd := load.GetCommand() - _, err = i.send(fmt.Sprintf("load %d of 40", load.Index()+1), cmd) - if err != nil { - return err - } - } - - installNdef := globalplatform.NewCommandInstallForInstall(identifiers.PackageAID, identifiers.NdefAID, identifiers.NdefInstanceAID, []byte{}) - _, err = i.send("install for install (ndef)", installNdef) - if err != nil { - return err - } - - instanceAID, err := identifiers.KeycardInstanceAID(1) - if err != nil { - return err - } - - installWallet := globalplatform.NewCommandInstallForInstall(identifiers.PackageAID, identifiers.KeycardAID, instanceAID, []byte{}) - _, err = i.send("install for install (wallet)", installWallet) - - return err -} - func (i *Initializer) send(description string, cmd *apdu.Command, allowedResponses ...uint16) (*apdu.Response, error) { logger.Debug("sending apdu command", "name", description) resp, err := i.c.Send(cmd) diff --git a/cmd/keycard/main.go b/cmd/keycard/main.go index d3c405f..379aae8 100644 --- a/cmd/keycard/main.go +++ b/cmd/keycard/main.go @@ -48,8 +48,8 @@ func init() { "info": commandInfo, "delete": commandDelete, "init": commandInit, - // "pair": commandPair, - // "status": commandStatus, + "pair": commandPair, + // "status": commandStatus, } if len(os.Args) < 2 { @@ -244,20 +244,19 @@ func commandInit(card *scard.Card) error { return nil } -// func commandPair(card *scard.Card) error { -// i := NewInitializer(card) -// pairingPass := ask("Pairing password") -// pin := ask("PIN") -// info, err := i.Pair(pairingPass, pin) -// if err != nil { -// return err -// } +func commandPair(card *scard.Card) error { + i := NewInitializer(card) + pairingPass := ask("Pairing password") + info, err := i.Pair(pairingPass) + if err != nil { + return err + } -// fmt.Printf("Pairing key 0x%x\n", info.Key) -// fmt.Printf("Pairing Index %d\n", info.Index) + fmt.Printf("Pairing key 0x%x\n", info.Key) + fmt.Printf("Pairing Index %d\n", info.Index) -// return nil -// } + return nil +} // func commandStatus(card *scard.Card) error { // i := NewInitializer(card) diff --git a/command_set.go b/command_set.go index fec8523..22cc08b 100644 --- a/command_set.go +++ b/command_set.go @@ -1,7 +1,11 @@ package keycard import ( + "crypto/rand" + "crypto/sha256" + "github.com/status-im/keycard-go/apdu" + "github.com/status-im/keycard-go/crypto" "github.com/status-im/keycard-go/globalplatform" "github.com/status-im/keycard-go/identifiers" "github.com/status-im/keycard-go/types" @@ -9,12 +13,15 @@ import ( type CommandSet struct { c types.Channel + sc *SecureChannel ApplicationInfo types.ApplicationInfo + PairingInfo *types.PairingInfo } func NewCommandSet(c types.Channel) *CommandSet { return &CommandSet{ - c: c, + c: c, + sc: NewSecureChannel(c), } } @@ -34,29 +41,31 @@ func (cs *CommandSet) Select() error { cmd.SetLe(0) resp, err := cs.c.Send(cmd) - - err = cs.checkOK(resp, err) - if err == nil { - appInfo, err := types.ParseApplicationInfo(resp.Data) - if err != nil { - return err - } - - cs.ApplicationInfo = appInfo - - return nil + if err = cs.checkOK(resp, err); err != nil { + return err } - return err -} - -func (cs *CommandSet) Init(secrets *Secrets) error { - secureChannel, err := NewSecureChannel(cs.c, cs.ApplicationInfo.PublicKey) + appInfo, err := types.ParseApplicationInfo(resp.Data) if err != nil { return err } - data, err := secureChannel.OneShotEncrypt(secrets) + cs.ApplicationInfo = appInfo + + if cs.ApplicationInfo.HasSecureChannelCapability() { + err = cs.sc.GenerateSecret(cs.ApplicationInfo.PublicKey) + if err != nil { + return err + } + + cs.sc.Reset() + } + + return nil +} + +func (cs *CommandSet) Init(secrets *Secrets) error { + data, err := cs.sc.OneShotEncrypt(secrets) if err != nil { return err } @@ -67,6 +76,50 @@ func (cs *CommandSet) Init(secrets *Secrets) error { return cs.checkOK(resp, err) } +func (cs *CommandSet) Pair(pairingPass string) error { + challenge := make([]byte, 32) + if _, err := rand.Read(challenge); err != nil { + return err + } + + cmd := NewCommandPairFirstStep(challenge) + resp, err := cs.c.Send(cmd) + if err = cs.checkOK(resp, err); err != nil { + return err + } + + cardCryptogram := resp.Data[:32] + cardChallenge := resp.Data[32:] + + secretHash, err := crypto.VerifyCryptogram(challenge, pairingPass, cardCryptogram) + if err != nil { + return err + } + + h := sha256.New() + h.Write(secretHash[:]) + h.Write(cardChallenge) + cmd = NewCommandPairFinalStep(h.Sum(nil)) + resp, err = cs.c.Send(cmd) + if err = cs.checkOK(resp, err); err != nil { + return err + } + + h.Reset() + h.Write(secretHash[:]) + h.Write(resp.Data[1:]) + + pairingKey := h.Sum(nil) + pairingIndex := resp.Data[0] + + cs.PairingInfo = &types.PairingInfo{ + Key: pairingKey, + Index: int(pairingIndex), + } + + return nil +} + func (cs *CommandSet) checkOK(resp *apdu.Response, err error, allowedResponses ...uint16) error { if len(allowedResponses) == 0 { allowedResponses = []uint16{apdu.SwOK} diff --git a/secure_channel.go b/secure_channel.go index d1cd6c0..3b9c85d 100644 --- a/secure_channel.go +++ b/secure_channel.go @@ -16,6 +16,7 @@ var ErrInvalidResponseMAC = errors.New("invalid response MAC") type SecureChannel struct { c types.Channel + open bool secret []byte publicKey *ecdsa.PublicKey encKey []byte @@ -23,30 +24,38 @@ type SecureChannel struct { iv []byte } -func NewSecureChannel(c types.Channel, cardKeyData []byte) (*SecureChannel, error) { +func NewSecureChannel(c types.Channel) *SecureChannel { + return &SecureChannel{ + c: c, + } +} + +func (sc *SecureChannel) GenerateSecret(cardPubKeyData []byte) error { key, err := ethcrypto.GenerateKey() if err != nil { - return nil, err + return err } - cardPubKey, err := ethcrypto.UnmarshalPubkey(cardKeyData) + cardPubKey, err := ethcrypto.UnmarshalPubkey(cardPubKeyData) if err != nil { - return nil, err + return err } - secret := crypto.GenerateECDHSharedSecret(key, cardPubKey) + sc.publicKey = &key.PublicKey + sc.secret = crypto.GenerateECDHSharedSecret(key, cardPubKey) - return &SecureChannel{ - c: c, - secret: secret, - publicKey: &key.PublicKey, - }, nil + return nil +} + +func (sc *SecureChannel) Reset() { + sc.open = false } func (sc *SecureChannel) Init(iv, encKey, macKey []byte) { sc.iv = iv sc.encKey = encKey sc.macKey = macKey + sc.open = true } func (sc *SecureChannel) Secret() []byte { @@ -62,18 +71,20 @@ func (sc *SecureChannel) RawPublicKey() []byte { } func (sc *SecureChannel) Send(cmd *apdu.Command) (*apdu.Response, error) { - encData, err := crypto.EncryptData(cmd.Data, sc.encKey, sc.iv) - if err != nil { - return nil, err - } + if sc.open { + encData, err := crypto.EncryptData(cmd.Data, sc.encKey, sc.iv) + if err != nil { + return nil, err + } - meta := []byte{cmd.Cla, cmd.Ins, cmd.P1, cmd.P2, byte(len(encData) + 16), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} - if err = sc.updateIV(meta, encData); err != nil { - return nil, err - } + meta := []byte{cmd.Cla, cmd.Ins, cmd.P1, cmd.P2, byte(len(encData) + 16), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + if err = sc.updateIV(meta, encData); err != nil { + return nil, err + } - newData := append(sc.iv, encData...) - cmd.Data = newData + newData := append(sc.iv, encData...) + cmd.Data = newData + } resp, err := sc.c.Send(cmd) if err != nil { diff --git a/types/application_info.go b/types/application_info.go index aa1f117..d42b40b 100644 --- a/types/application_info.go +++ b/types/application_info.go @@ -67,6 +67,11 @@ func ParseApplicationInfo(data []byte) (info ApplicationInfo, err error) { if data[0] == TagSelectResponsePreInitialized { info.PublicKey = data[2:] info.Capabilities = CapabilityCredentialsManagement + + if len(info.PublicKey) > 0 { + info.Capabilities = info.Capabilities | CapabilitySecureChannel + } + return info, nil }