2018-10-22 17:33:53 +00:00
|
|
|
package lightwallet
|
|
|
|
|
|
|
|
import (
|
2018-11-06 17:38:13 +00:00
|
|
|
"bytes"
|
2018-10-22 17:33:53 +00:00
|
|
|
"crypto/ecdsa"
|
2018-11-06 17:38:13 +00:00
|
|
|
"errors"
|
2018-10-22 17:33:53 +00:00
|
|
|
|
|
|
|
ethcrypto "github.com/ethereum/go-ethereum/crypto"
|
2018-11-06 09:25:54 +00:00
|
|
|
"github.com/status-im/hardware-wallet-go/apdu"
|
|
|
|
"github.com/status-im/hardware-wallet-go/globalplatform"
|
|
|
|
"github.com/status-im/hardware-wallet-go/lightwallet/crypto"
|
2018-10-22 17:33:53 +00:00
|
|
|
)
|
|
|
|
|
2018-11-06 17:38:13 +00:00
|
|
|
var ErrInvalidResponseMAC = errors.New("invalid response MAC")
|
|
|
|
|
2018-10-22 17:33:53 +00:00
|
|
|
type SecureChannel struct {
|
2018-10-24 16:16:14 +00:00
|
|
|
c globalplatform.Channel
|
|
|
|
secret []byte
|
|
|
|
publicKey *ecdsa.PublicKey
|
2018-10-27 16:52:39 +00:00
|
|
|
encKey []byte
|
|
|
|
macKey []byte
|
|
|
|
iv []byte
|
2018-10-22 17:33:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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{
|
2018-10-24 16:16:14 +00:00
|
|
|
c: c,
|
|
|
|
secret: secret,
|
|
|
|
publicKey: &key.PublicKey,
|
2018-10-22 17:33:53 +00:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2018-10-27 16:52:39 +00:00
|
|
|
func (sc *SecureChannel) Init(iv, encKey, macKey []byte) {
|
|
|
|
sc.iv = iv
|
|
|
|
sc.encKey = encKey
|
|
|
|
sc.macKey = macKey
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sc *SecureChannel) Secret() []byte {
|
|
|
|
return sc.secret
|
|
|
|
}
|
|
|
|
|
2018-10-24 16:16:14 +00:00
|
|
|
func (sc *SecureChannel) PublicKey() *ecdsa.PublicKey {
|
|
|
|
return sc.publicKey
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sc *SecureChannel) RawPublicKey() []byte {
|
|
|
|
return ethcrypto.FromECDSAPub(sc.publicKey)
|
|
|
|
}
|
|
|
|
|
2018-10-22 17:33:53 +00:00
|
|
|
func (sc *SecureChannel) Send(cmd *apdu.Command) (*apdu.Response, error) {
|
2018-11-06 17:38:13 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
newData := append(sc.iv, encData...)
|
|
|
|
cmd.Data = newData
|
|
|
|
|
|
|
|
resp, err := sc.c.Send(cmd)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if resp.Sw != globalplatform.SwOK {
|
|
|
|
return nil, apdu.NewErrBadResponse(resp.Sw, "unexpected sw in secure channel")
|
|
|
|
}
|
|
|
|
|
|
|
|
rmeta := []byte{byte(len(resp.Data)), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
|
|
|
rmac := resp.Data[:len(sc.iv)]
|
|
|
|
rdata := resp.Data[len(sc.iv):]
|
|
|
|
plainData, err := crypto.DecryptData(rdata, sc.encKey, sc.iv)
|
|
|
|
if err = sc.updateIV(rmeta, rdata); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !bytes.Equal(sc.iv, rmac) {
|
|
|
|
return nil, ErrInvalidResponseMAC
|
|
|
|
}
|
|
|
|
|
|
|
|
return apdu.ParseResponse(plainData)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (sc *SecureChannel) updateIV(meta, data []byte) error {
|
|
|
|
mac, err := crypto.CalculateMac(meta, data, sc.macKey)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
sc.iv = mac
|
|
|
|
|
|
|
|
return nil
|
2018-10-22 17:33:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (sc *SecureChannel) OneShotEncrypt(secrets *Secrets) ([]byte, error) {
|
2018-10-24 16:16:14 +00:00
|
|
|
pubKeyData := ethcrypto.FromECDSAPub(sc.publicKey)
|
2018-10-22 17:33:53 +00:00
|
|
|
data := append([]byte(secrets.Pin()), []byte(secrets.Puk())...)
|
|
|
|
data = append(data, secrets.PairingToken()...)
|
|
|
|
|
|
|
|
return crypto.OneShotEncrypt(pubKeyData, sc.secret, data)
|
|
|
|
}
|