From 61bc1a2764dd80b7496903cb1ba5e440b1ed6cab Mon Sep 17 00:00:00 2001 From: Andrea Franz Date: Mon, 22 Oct 2018 19:33:53 +0200 Subject: [PATCH] add card secrets initialization --- cmd/applet-installer/main.go | 24 ++++++++++++--- lightwallet/commands.go | 20 +++++++++++++ lightwallet/crypto/crypto.go | 49 +++++++++++++++++++++++++++++++ lightwallet/crypto/crypto_test.go | 20 +++++++++++++ lightwallet/installer.go | 46 ++++++++++++++++++++++++----- lightwallet/secrets.go | 13 ++++++++ lightwallet/secure_channel.go | 48 ++++++++++++++++++++++++++++++ 7 files changed, 209 insertions(+), 11 deletions(-) create mode 100644 lightwallet/commands.go create mode 100644 lightwallet/crypto/crypto.go create mode 100644 lightwallet/crypto/crypto_test.go create mode 100644 lightwallet/secure_channel.go diff --git a/cmd/applet-installer/main.go b/cmd/applet-installer/main.go index d1e6f55..8f602c9 100644 --- a/cmd/applet-installer/main.go +++ b/cmd/applet-installer/main.go @@ -26,6 +26,10 @@ var ( ) func initLogger() { + if *flagLogLevel == "" { + *flagLogLevel = "info" + } + level, err := log.LvlFromString(strings.ToLower(*flagLogLevel)) if err != nil { stdlog.Fatal(err) @@ -44,6 +48,7 @@ func init() { "install": commandInstall, "status": commandStatus, "delete": commandDelete, + "init": commandInit, } } @@ -144,14 +149,12 @@ func commandInstall(i *lightwallet.Installer) error { } defer f.Close() - secrets, err := i.Install(f, *flagOverwrite) + err = i.Install(f, *flagOverwrite) if err != nil { fail("installation error", "error", err) } - fmt.Printf("\n\nPUK %s\n", secrets.Puk()) - fmt.Printf("Pairing password: %s\n", secrets.PairingPass()) - + fmt.Printf("applet installed successfully.\n") return nil } @@ -180,3 +183,16 @@ func commandDelete(i *lightwallet.Installer) error { return nil } + +func commandInit(i *lightwallet.Installer) error { + secrets, err := i.Init() + if err != nil { + return err + } + + fmt.Printf("PIN %s\n", secrets.Pin()) + fmt.Printf("PUK %s\n", secrets.Puk()) + fmt.Printf("Pairing password: %s\n", secrets.PairingPass()) + + return nil +} diff --git a/lightwallet/commands.go b/lightwallet/commands.go new file mode 100644 index 0000000..88dcb97 --- /dev/null +++ b/lightwallet/commands.go @@ -0,0 +1,20 @@ +package lightwallet + +import ( + "github.com/status-im/smartcard-go/apdu" + "github.com/status-im/smartcard-go/globalplatform" +) + +const ( + InsInit = uint8(0xFE) +) + +func NewCommandInit(data []byte) *apdu.Command { + return apdu.NewCommand( + globalplatform.ClaGp, + InsInit, + uint8(0x00), + uint8(0x00), + data, + ) +} diff --git a/lightwallet/crypto/crypto.go b/lightwallet/crypto/crypto.go new file mode 100644 index 0000000..f02b80c --- /dev/null +++ b/lightwallet/crypto/crypto.go @@ -0,0 +1,49 @@ +package crypto + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/ecdsa" + "crypto/rand" + + "github.com/ethereum/go-ethereum/crypto" +) + +func GenerateECDHSharedSecret(priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey) []byte { + x, _ := crypto.S256().ScalarMult(pub.X, pub.Y, priv.D.Bytes()) + return x.Bytes() +} + +func OneShotEncrypt(pubKeyData, secret, data []byte) ([]byte, error) { + data = appendPadding(16, data) + + iv := make([]byte, 16) + _, err := rand.Read(iv) + if err != nil { + return nil, err + } + + block, err := aes.NewCipher(secret) + if err != nil { + return nil, err + } + + ciphertext := make([]byte, len(data)) + mode := cipher.NewCBCEncrypter(block, iv) + mode.CryptBlocks(ciphertext, data) + + encrypted := append([]byte{byte(len(pubKeyData))}, pubKeyData...) + encrypted = append(encrypted, iv...) + encrypted = append(encrypted, ciphertext...) + + return encrypted, nil +} + +func appendPadding(blockSize int, data []byte) []byte { + paddingSize := blockSize - (len(data)+1)%blockSize + zeroes := bytes.Repeat([]byte{0x00}, paddingSize) + padding := append([]byte{0x80}, zeroes...) + + return append(data, padding...) +} diff --git a/lightwallet/crypto/crypto_test.go b/lightwallet/crypto/crypto_test.go new file mode 100644 index 0000000..5cce757 --- /dev/null +++ b/lightwallet/crypto/crypto_test.go @@ -0,0 +1,20 @@ +package crypto + +import ( + "testing" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/assert" +) + +func TestECDH(t *testing.T) { + pk1, err := crypto.GenerateKey() + assert.NoError(t, err) + pk2, err := crypto.GenerateKey() + assert.NoError(t, err) + + sharedSecret1 := GenerateECDHSharedSecret(pk1, &pk2.PublicKey) + sharedSecret2 := GenerateECDHSharedSecret(pk2, &pk1.PublicKey) + + assert.Equal(t, sharedSecret1, sharedSecret2) +} diff --git a/lightwallet/installer.go b/lightwallet/installer.go index f65fb42..857d376 100644 --- a/lightwallet/installer.go +++ b/lightwallet/installer.go @@ -34,34 +34,66 @@ func NewInstaller(t globalplatform.Transmitter) *Installer { } // Install installs the applet from the specified capFile. -func (i *Installer) Install(capFile *os.File, overwriteApplet bool) (*Secrets, error) { +func (i *Installer) Install(capFile *os.File, overwriteApplet bool) error { err := i.initSecureChannel(cardManagerAID) if err != nil { - return nil, err + return err } installed, err := i.isAppletInstalled() if err != nil { - return nil, err + return err } if installed && !overwriteApplet { - return nil, errors.New("applet already installed") + return errors.New("applet already installed") } err = i.deleteAID(ndefInstanceAID, walletAID, pkgAID) if err != nil { - return nil, err + return err } err = i.installApplets(capFile) + if err != nil { + return err + } + + return err +} + +func (i *Installer) Init() (*Secrets, error) { + secrets, err := NewSecrets() if err != nil { return nil, err } - secrets, err := NewSecrets() + sel := globalplatform.NewCommandSelect(walletAID) + resp, err := i.send("select applet", sel) + if err != nil { + return nil, err + } - return secrets, err + cardKeyData := resp.Data[2:] + secureChannel, err := NewSecureChannel(i.c, cardKeyData) + if err != nil { + return nil, err + } + + data, err := secureChannel.OneShotEncrypt(secrets) + if err != nil { + return nil, err + } + + cmd := NewCommandInit(data) + resp, err = i.send("init card", cmd) + if err != nil { + return nil, err + } + + fmt.Printf("RESP: %+v\n", resp) + + return secrets, nil } // Info returns if the applet is already installed in the card. diff --git a/lightwallet/secrets.go b/lightwallet/secrets.go index c5766a6..6336477 100644 --- a/lightwallet/secrets.go +++ b/lightwallet/secrets.go @@ -14,10 +14,12 @@ import ( const ( pairingTokenSalt = "Status Hardware Wallet Lite" maxPukNumber = int64(999999999999) + maxPinNumber = int64(999999) ) // Secrets contains the secret data needed to pair a client with a card. type Secrets struct { + pin string puk string pairingPass string pairingToken []byte @@ -35,13 +37,24 @@ func NewSecrets() (*Secrets, error) { return nil, err } + pin, err := rand.Int(rand.Reader, big.NewInt(maxPinNumber)) + if err != nil { + return nil, err + } + return &Secrets{ + pin: fmt.Sprintf("%06d", pin.Int64()), puk: fmt.Sprintf("%012d", puk.Int64()), pairingPass: pairingPass, pairingToken: generatePairingToken(pairingPass), }, nil } +// Pin returns the pin string. +func (s *Secrets) Pin() string { + return s.pin +} + // Puk returns the puk string. func (s *Secrets) Puk() string { return s.puk diff --git a/lightwallet/secure_channel.go b/lightwallet/secure_channel.go new file mode 100644 index 0000000..89e28cb --- /dev/null +++ b/lightwallet/secure_channel.go @@ -0,0 +1,48 @@ +package lightwallet + +import ( + "crypto/ecdsa" + + ethcrypto "github.com/ethereum/go-ethereum/crypto" + "github.com/status-im/smartcard-go/apdu" + "github.com/status-im/smartcard-go/globalplatform" + "github.com/status-im/smartcard-go/lightwallet/crypto" +) + +type SecureChannel struct { + c globalplatform.Channel + secret []byte + pubKey *ecdsa.PublicKey +} + +func NewSecureChannel(c globalplatform.Channel, cardKeyData []byte) (*SecureChannel, error) { + key, err := ethcrypto.GenerateKey() + if err != nil { + return nil, err + } + + cardPubKey, err := ethcrypto.UnmarshalPubkey(cardKeyData) + if err != nil { + return nil, err + } + + secret := crypto.GenerateECDHSharedSecret(key, cardPubKey) + + return &SecureChannel{ + c: c, + secret: secret, + pubKey: &key.PublicKey, + }, nil +} + +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) + data := append([]byte(secrets.Pin()), []byte(secrets.Puk())...) + data = append(data, secrets.PairingToken()...) + + return crypto.OneShotEncrypt(pubKeyData, sc.secret, data) +}