diff --git a/accounts/hd.go b/accounts/hd.go index 6ed631807..d14dbc842 100644 --- a/accounts/hd.go +++ b/accounts/hd.go @@ -17,6 +17,7 @@ package accounts import ( + "encoding/json" "errors" "fmt" "math" @@ -133,3 +134,17 @@ func (path DerivationPath) String() string { } return result } + +func (path DerivationPath) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf("\"%s\"", path.String())), nil +} + +func (dp *DerivationPath) UnmarshalJSON(b []byte) error { + var path string + var err error + if err = json.Unmarshal(b, &path); err != nil { + return err + } + *dp, err = ParseDerivationPath(path) + return err +} diff --git a/accounts/scwallet/apdu.go b/accounts/scwallet/apdu.go new file mode 100644 index 000000000..f9a34a91c --- /dev/null +++ b/accounts/scwallet/apdu.go @@ -0,0 +1,96 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package scwallet + +import ( + "bytes" + "encoding/binary" +) + +const ( + CLA_ISO7816 = 0 + + INS_SELECT = 0xA4 + INS_GET_RESPONSE = 0xC0 + INS_PAIR = 0x12 + INS_UNPAIR = 0x13 + INS_OPEN_SECURE_CHANNEL = 0x10 + INS_MUTUALLY_AUTHENTICATE = 0x11 + + SW1_GET_RESPONSE = 0x61 + SW1_OK = 0x90 +) + +// CommandAPDU represents an application data unit sent to a smartcard +type CommandAPDU struct { + Cla, Ins, P1, P2 uint8 // Class, Instruction, Parameter 1, Parameter 2 + Data []byte // Command data + Le uint8 // Command data length +} + +// serialize serializes a command APDU. +func (ca CommandAPDU) serialize() ([]byte, error) { + buf := new(bytes.Buffer) + + if err := binary.Write(buf, binary.BigEndian, ca.Cla); err != nil { + return nil, err + } + if err := binary.Write(buf, binary.BigEndian, ca.Ins); err != nil { + return nil, err + } + if err := binary.Write(buf, binary.BigEndian, ca.P1); err != nil { + return nil, err + } + if err := binary.Write(buf, binary.BigEndian, ca.P2); err != nil { + return nil, err + } + if len(ca.Data) > 0 { + if err := binary.Write(buf, binary.BigEndian, uint8(len(ca.Data))); err != nil { + return nil, err + } + if err := binary.Write(buf, binary.BigEndian, ca.Data); err != nil { + return nil, err + } + } + if err := binary.Write(buf, binary.BigEndian, ca.Le); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +// ResponseAPDU represents an application data unit received from a smart card +type ResponseAPDU struct { + Data []byte // response data + Sw1, Sw2 uint8 // status words 1 and 2 +} + +// deserialize deserializes a response APDU +func (ra *ResponseAPDU) deserialize(data []byte) error { + ra.Data = make([]byte, len(data)-2) + + buf := bytes.NewReader(data) + if err := binary.Read(buf, binary.BigEndian, &ra.Data); err != nil { + return err + } + if err := binary.Read(buf, binary.BigEndian, &ra.Sw1); err != nil { + return err + } + if err := binary.Read(buf, binary.BigEndian, &ra.Sw2); err != nil { + return err + } + return nil +} diff --git a/accounts/scwallet/hub.go b/accounts/scwallet/hub.go new file mode 100644 index 000000000..6b81cd501 --- /dev/null +++ b/accounts/scwallet/hub.go @@ -0,0 +1,277 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package scwallet + +import ( + "encoding/json" + "io/ioutil" + "os" + "reflect" + "sync" + "time" + + "github.com/ebfe/scard" + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/log" +) + +const Scheme = "pcsc" + +// refreshCycle is the maximum time between wallet refreshes (if USB hotplug +// notifications don't work). +const refreshCycle = 5 * time.Second + +// refreshThrottling is the minimum time between wallet refreshes to avoid thrashing. +const refreshThrottling = 500 * time.Millisecond + +// SmartcardPairing contains information about a smart card we have paired with +// or might pair withub. +type SmartcardPairing struct { + PublicKey []byte `json:"publicKey"` + PairingIndex uint8 `json:"pairingIndex"` + PairingKey []byte `json:"pairingKey"` + Accounts map[common.Address]accounts.DerivationPath `json:"accounts"` +} + +// Hub is a accounts.Backend that can find and handle generic PC/SC hardware wallets. +type Hub struct { + scheme string // Protocol scheme prefixing account and wallet URLs. + + context *scard.Context + datadir string + pairings map[string]SmartcardPairing + refreshed time.Time // Time instance when the list of wallets was last refreshed + wallets map[string]*Wallet // Mapping from reader names to wallet instances + updateFeed event.Feed // Event feed to notify wallet additions/removals + updateScope event.SubscriptionScope // Subscription scope tracking current live listeners + updating bool // Whether the event notification loop is running + + quit chan chan error + + stateLock sync.Mutex // Protects the internals of the hub from racey access +} + +var HubType = reflect.TypeOf(&Hub{}) + +func (hub *Hub) readPairings() error { + hub.pairings = make(map[string]SmartcardPairing) + pairingFile, err := os.Open(hub.datadir + "/smartcards.json") + if err != nil { + if os.IsNotExist(err) { + return nil + } + return err + } + + pairingData, err := ioutil.ReadAll(pairingFile) + if err != nil { + return err + } + var pairings []SmartcardPairing + if err := json.Unmarshal(pairingData, &pairings); err != nil { + return err + } + + for _, pairing := range pairings { + hub.pairings[string(pairing.PublicKey)] = pairing + } + return nil +} + +func (hub *Hub) writePairings() error { + pairingFile, err := os.OpenFile(hub.datadir+"/smartcards.json", os.O_RDWR|os.O_CREATE, 0755) + if err != nil { + return err + } + + pairings := make([]SmartcardPairing, 0, len(hub.pairings)) + for _, pairing := range hub.pairings { + pairings = append(pairings, pairing) + } + + pairingData, err := json.Marshal(pairings) + if err != nil { + return err + } + + if _, err := pairingFile.Write(pairingData); err != nil { + return err + } + + return pairingFile.Close() +} + +func (hub *Hub) getPairing(wallet *Wallet) *SmartcardPairing { + pairing, ok := hub.pairings[string(wallet.PublicKey)] + if ok { + return &pairing + } + return nil +} + +func (hub *Hub) setPairing(wallet *Wallet, pairing *SmartcardPairing) error { + if pairing == nil { + delete(hub.pairings, string(wallet.PublicKey)) + } else { + hub.pairings[string(wallet.PublicKey)] = *pairing + } + return hub.writePairings() +} + +// NewHub creates a new hardware wallet manager for smartcards. +func NewHub(scheme string, datadir string) (*Hub, error) { + context, err := scard.EstablishContext() + if err != nil { + return nil, err + } + + hub := &Hub{ + scheme: scheme, + context: context, + datadir: datadir, + wallets: make(map[string]*Wallet), + quit: make(chan chan error), + } + + if err := hub.readPairings(); err != nil { + return nil, err + } + + hub.refreshWallets() + return hub, nil +} + +// Wallets implements accounts.Backend, returning all the currently tracked USB +// devices that appear to be hardware wallets. +func (hub *Hub) Wallets() []accounts.Wallet { + // Make sure the list of wallets is up to date + hub.stateLock.Lock() + defer hub.stateLock.Unlock() + + hub.refreshWallets() + + cpy := make([]accounts.Wallet, 0, len(hub.wallets)) + for _, wallet := range hub.wallets { + if wallet != nil { + cpy = append(cpy, wallet) + } + } + return cpy +} + +// refreshWallets scans the USB devices attached to the machine and updates the +// list of wallets based on the found devices. +func (hub *Hub) refreshWallets() { + elapsed := time.Since(hub.refreshed) + if elapsed < refreshThrottling { + return + } + + readers, err := hub.context.ListReaders() + if err != nil { + log.Error("Error listing readers", "err", err) + } + + events := []accounts.WalletEvent{} + seen := make(map[string]struct{}) + for _, reader := range readers { + if wallet, ok := hub.wallets[reader]; ok { + // We already know about this card; check it's still present + if err := wallet.ping(); err != nil { + log.Debug("Got error pinging wallet", "reader", reader, "err", err) + } else { + seen[reader] = struct{}{} + } + continue + } + seen[reader] = struct{}{} + + card, err := hub.context.Connect(reader, scard.ShareShared, scard.ProtocolAny) + if err != nil { + log.Debug("Error opening card", "reader", reader, "err", err) + continue + } + + wallet := NewWallet(hub, card) + err = wallet.connect() + if err != nil { + log.Debug("Error connecting to wallet", "reader", reader, "err", err) + card.Disconnect(scard.LeaveCard) + continue + } + + hub.wallets[reader] = wallet + events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletArrived}) + log.Info("Found new smartcard wallet", "reader", reader, "publicKey", hexutil.Encode(wallet.PublicKey[:4])) + } + + // Remove any wallets we no longer see + for k, wallet := range hub.wallets { + if _, ok := seen[k]; !ok { + log.Info("Wallet disconnected", "pubkey", hexutil.Encode(wallet.PublicKey[:4]), "reader", k) + wallet.Close() + events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletDropped}) + delete(hub.wallets, k) + } + } + + for _, event := range events { + hub.updateFeed.Send(event) + } + hub.refreshed = time.Now() +} + +// Subscribe implements accounts.Backend, creating an async subscription to +// receive notifications on the addition or removal of wallets. +func (hub *Hub) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription { + // We need the mutex to reliably start/stop the update loop + hub.stateLock.Lock() + defer hub.stateLock.Unlock() + + // Subscribe the caller and track the subscriber count + sub := hub.updateScope.Track(hub.updateFeed.Subscribe(sink)) + + // Subscribers require an active notification loop, start it + if !hub.updating { + hub.updating = true + go hub.updater() + } + return sub +} + +// updater is responsible for maintaining an up-to-date list of wallets managed +// by the hub, and for firing wallet addition/removal events. +func (hub *Hub) updater() { + for { + time.Sleep(refreshCycle) + + // Run the wallet refresher + hub.stateLock.Lock() + hub.refreshWallets() + + // If all our subscribers left, stop the updater + if hub.updateScope.Count() == 0 { + hub.updating = false + hub.stateLock.Unlock() + return + } + hub.stateLock.Unlock() + } +} diff --git a/accounts/scwallet/securechannel.go b/accounts/scwallet/securechannel.go new file mode 100644 index 000000000..f614362a9 --- /dev/null +++ b/accounts/scwallet/securechannel.go @@ -0,0 +1,342 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package scwallet + +import ( + //"crypto/ecdsa" + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha256" + "crypto/sha512" + "fmt" + //"math/big" + "github.com/ebfe/scard" + "github.com/ethereum/go-ethereum/crypto" + ecdh "github.com/wsddn/go-ecdh" +) + +const ( + MAX_PAYLOAD_SIZE = 223 + PAIR_P1_FIRST_STEP = 0 + PAIR_P1_LAST_STEP = 1 + + SC_SECRET_LENGTH = 32 + SC_BLOCK_SIZE = 16 +) + +// SecureChannelSession enables secure communication with a hardware wallet +type SecureChannelSession struct { + card *scard.Card // A handle to the smartcard for communication + secret []byte // A shared secret generated from our ECDSA keys + publicKey []byte // Our own ephemeral public key + PairingKey []byte // A permanent shared secret for a pairing, if present + sessionEncKey []byte // The current session encryption key + sessionMacKey []byte // The current session MAC key + iv []byte // The current IV + PairingIndex uint8 // The pairing index +} + +// NewSecureChannelSession creates a new secure channel for the given card and public key +func NewSecureChannelSession(card *scard.Card, keyData []byte) (*SecureChannelSession, error) { + // Generate an ECDSA keypair for ourselves + gen := ecdh.NewEllipticECDH(crypto.S256()) + private, public, err := gen.GenerateKey(rand.Reader) + if err != nil { + return nil, err + } + + cardPublic, ok := gen.Unmarshal(keyData) + if !ok { + return nil, fmt.Errorf("Could not unmarshal public key from card") + } + + secret, err := gen.GenerateSharedSecret(private, cardPublic) + if err != nil { + return nil, err + } + + return &SecureChannelSession{ + card: card, + secret: secret, + publicKey: gen.Marshal(public), + }, nil +} + +// Pair establishes a new pairing with the smartcard +func (s *SecureChannelSession) Pair(sharedSecret []byte) error { + secretHash := sha256.Sum256(sharedSecret) + + challenge := make([]byte, 32) + if _, err := rand.Read(challenge); err != nil { + return err + } + + response, err := s.pair(PAIR_P1_FIRST_STEP, challenge) + if err != nil { + return err + } + + md := sha256.New() + md.Write(secretHash[:]) + md.Write(challenge) + + expectedCryptogram := md.Sum(nil) + cardCryptogram := response.Data[:32] + cardChallenge := response.Data[32:] + + if !bytes.Equal(expectedCryptogram, cardCryptogram) { + return fmt.Errorf("Invalid card cryptogram") + } + + md.Reset() + md.Write(secretHash[:]) + md.Write(cardChallenge) + response, err = s.pair(PAIR_P1_LAST_STEP, md.Sum(nil)) + if err != nil { + return err + } + + md.Reset() + md.Write(secretHash[:]) + md.Write(response.Data[1:]) + s.PairingKey = md.Sum(nil) + s.PairingIndex = response.Data[0] + + return nil +} + +// Unpair disestablishes an existing pairing +func (s *SecureChannelSession) Unpair() error { + if s.PairingKey == nil { + return fmt.Errorf("Cannot unpair: not paired") + } + + _, err := s.TransmitEncrypted(CLA_SCWALLET, INS_UNPAIR, s.PairingIndex, 0, []byte{}) + if err != nil { + return err + } + s.PairingKey = nil + // Close channel + s.iv = nil + return nil +} + +// Open initializes the secure channel +func (s *SecureChannelSession) Open() error { + if s.iv != nil { + return fmt.Errorf("Session already opened") + } + + response, err := s.open() + if err != nil { + return err + } + + // Generate the encryption/mac key by hashing our shared secret, + // pairing key, and the first bytes returned from the Open APDU. + md := sha512.New() + md.Write(s.secret) + md.Write(s.PairingKey) + md.Write(response.Data[:SC_SECRET_LENGTH]) + keyData := md.Sum(nil) + s.sessionEncKey = keyData[:SC_SECRET_LENGTH] + s.sessionMacKey = keyData[SC_SECRET_LENGTH : SC_SECRET_LENGTH*2] + + // The IV is the last bytes returned from the Open APDU. + s.iv = response.Data[SC_SECRET_LENGTH:] + + if err := s.mutuallyAuthenticate(); err != nil { + return err + } + + return nil +} + +// mutuallyAuthenticate is an internal method to authenticate both ends of the +// connection. +func (s *SecureChannelSession) mutuallyAuthenticate() error { + data := make([]byte, SC_SECRET_LENGTH) + if _, err := rand.Read(data); err != nil { + return err + } + + response, err := s.TransmitEncrypted(CLA_SCWALLET, INS_MUTUALLY_AUTHENTICATE, 0, 0, data) + if err != nil { + return err + } + if response.Sw1 != 0x90 || response.Sw2 != 0x00 { + return fmt.Errorf("Got unexpected response from MUTUALLY_AUTHENTICATE: 0x%x%x", response.Sw1, response.Sw2) + } + + if len(response.Data) != SC_SECRET_LENGTH { + return fmt.Errorf("Response from MUTUALLY_AUTHENTICATE was %d bytes, expected %d", len(response.Data), SC_SECRET_LENGTH) + } + + return nil +} + +// open is an internal method that sends an open APDU +func (s *SecureChannelSession) open() (*ResponseAPDU, error) { + return transmit(s.card, &CommandAPDU{ + Cla: CLA_SCWALLET, + Ins: INS_OPEN_SECURE_CHANNEL, + P1: s.PairingIndex, + P2: 0, + Data: s.publicKey, + Le: 0, + }) +} + +// pair is an internal method that sends a pair APDU +func (s *SecureChannelSession) pair(p1 uint8, data []byte) (*ResponseAPDU, error) { + return transmit(s.card, &CommandAPDU{ + Cla: CLA_SCWALLET, + Ins: INS_PAIR, + P1: p1, + P2: 0, + Data: data, + Le: 0, + }) +} + +// TransmitEncrypted sends an encrypted message, and decrypts and returns the response +func (s *SecureChannelSession) TransmitEncrypted(cla, ins, p1, p2 byte, data []byte) (*ResponseAPDU, error) { + if s.iv == nil { + return nil, fmt.Errorf("Channel not open") + } + + data, err := s.encryptAPDU(data) + if err != nil { + return nil, err + } + meta := []byte{cla, ins, p1, p2, byte(len(data) + SC_BLOCK_SIZE), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + if err = s.updateIV(meta, data); err != nil { + return nil, err + } + + fulldata := make([]byte, len(s.iv)+len(data)) + copy(fulldata, s.iv) + copy(fulldata[len(s.iv):], data) + + response, err := transmit(s.card, &CommandAPDU{ + Cla: cla, + Ins: ins, + P1: p1, + P2: p2, + Data: fulldata, + }) + if err != nil { + return nil, err + } + + rmeta := []byte{byte(len(response.Data)), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + rmac := response.Data[:len(s.iv)] + rdata := response.Data[len(s.iv):] + plainData, err := s.decryptAPDU(rdata) + if err != nil { + return nil, err + } + + if err = s.updateIV(rmeta, rdata); err != nil { + return nil, err + } + if !bytes.Equal(s.iv, rmac) { + return nil, fmt.Errorf("Invalid MAC in response") + } + + rapdu := &ResponseAPDU{} + rapdu.deserialize(plainData) + + if rapdu.Sw1 != SW1_OK { + return nil, fmt.Errorf("Unexpected response status Cla=0x%x, Ins=0x%x, Sw=0x%x%x", cla, ins, rapdu.Sw1, rapdu.Sw2) + } + + return rapdu, nil +} + +// encryptAPDU is an internal method that serializes and encrypts an APDU +func (s *SecureChannelSession) encryptAPDU(data []byte) ([]byte, error) { + if len(data) > MAX_PAYLOAD_SIZE { + return nil, fmt.Errorf("Payload of %d bytes exceeds maximum of %d", len(data), MAX_PAYLOAD_SIZE) + } + data = pad(data, 0x80) + + ret := make([]byte, len(data)) + + a, err := aes.NewCipher(s.sessionEncKey) + if err != nil { + return nil, err + } + crypter := cipher.NewCBCEncrypter(a, s.iv) + crypter.CryptBlocks(ret, data) + return ret, nil +} + +// pad applies message padding to a 16 byte boundary +func pad(data []byte, terminator byte) []byte { + padded := make([]byte, (len(data)/16+1)*16) + copy(padded, data) + padded[len(data)] = terminator + return padded +} + +// decryptAPDU is an internal method that decrypts and deserializes an APDU +func (s *SecureChannelSession) decryptAPDU(data []byte) ([]byte, error) { + a, err := aes.NewCipher(s.sessionEncKey) + if err != nil { + return nil, err + } + + ret := make([]byte, len(data)) + + crypter := cipher.NewCBCDecrypter(a, s.iv) + crypter.CryptBlocks(ret, data) + return unpad(ret, 0x80) +} + +// unpad strips padding from a message +func unpad(data []byte, terminator byte) ([]byte, error) { + for i := 1; i <= 16; i++ { + switch data[len(data)-i] { + case 0: + continue + case terminator: + return data[:len(data)-i], nil + default: + return nil, fmt.Errorf("Expected end of padding, got %d", data[len(data)-i]) + } + } + return nil, fmt.Errorf("Expected end of padding, got 0") +} + +// updateIV is an internal method that updates the initialization vector after +// each message exchanged. +func (s *SecureChannelSession) updateIV(meta, data []byte) error { + data = pad(data, 0) + a, err := aes.NewCipher(s.sessionMacKey) + if err != nil { + return err + } + crypter := cipher.NewCBCEncrypter(a, make([]byte, 16)) + crypter.CryptBlocks(meta, meta) + crypter.CryptBlocks(data, data) + // The first 16 bytes of the last block is the MAC + s.iv = data[len(data)-32 : len(data)-16] + return nil +} diff --git a/accounts/scwallet/wallet.go b/accounts/scwallet/wallet.go new file mode 100644 index 000000000..8a8018c6e --- /dev/null +++ b/accounts/scwallet/wallet.go @@ -0,0 +1,1011 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package scwallet + +import ( + "bytes" + "context" + "crypto/hmac" + "crypto/sha256" + "crypto/sha512" + "encoding/asn1" + "encoding/binary" + "errors" + "fmt" + "math/big" + "strings" + "sync" + "time" + + "github.com/ebfe/scard" + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/crypto/secp256k1" + "github.com/ethereum/go-ethereum/log" +) + +var ( + APPLET_AID = []byte{0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x41, 0x70, 0x70} + AlreadyOpenError = errors.New("Wallet already open") + PairingRequiredError = errors.New("Pairing required with personal.openWallet(puk)") + PinRequiredError = errors.New("Must unlock with personal.openWallet(pin)") + PubkeyMismatchError = errors.New("Could not recover matching public key from signature") + WalletChangedError = errors.New("Smartcard has been changed") + DerivationSignatureHash = sha256.Sum256([]byte("STATUS KEY DERIVATION")) +) + +const ( + CLA_SCWALLET = 0x80 + INS_VERIFY_PIN = 0x20 + INS_EXPORT_KEY = 0xC2 + INS_SIGN = 0xC0 + INS_LOAD_KEY = 0xD0 + INS_DERIVE_KEY = 0xD1 + INS_STATUS = 0xF2 + DERIVE_P1_ASSISTED = uint8(0x01) + DERIVE_P1_APPEND = uint8(0x80) + DERIVE_P2_KEY_PATH = uint8(0x00) + DERIVE_P2_PUBLIC_KEY = uint8(0x01) + STATUS_P1_WALLET_STATUS = uint8(0x00) + STATUS_P1_PATH = uint8(0x01) + SIGN_P1_PRECOMPUTED_HASH = uint8(0x01) + SIGN_P2_ONLY_BLOCK = uint8(0x81) + EXPORT_P1_ANY = uint8(0x00) + EXPORT_P2_PUBKEY = uint8(0x01) + + // Minimum time to wait between self derivation attempts, even it the user is + // requesting accounts like crazy. + selfDeriveThrottling = time.Second +) + +// Wallet represents a smartcard wallet instance. +type Wallet struct { + Hub *Hub // A handle to the Hub that instantiated this wallet. + PublicKey []byte // The wallet's public key (used for communication and identification, not signing!) + + lock sync.Mutex // Lock that gates access to struct fields and communication with the card + card *scard.Card // A handle to the smartcard interface for the wallet. + session *Session // The secure communication session with the card + log log.Logger // Contextual logger to tag the base with its id + deriveNextPath accounts.DerivationPath // Next derivation path for account auto-discovery + deriveNextAddr common.Address // Next derived account address for auto-discovery + deriveChain ethereum.ChainStateReader // Blockchain state reader to discover used account with + deriveReq chan chan struct{} // Channel to request a self-derivation on + deriveQuit chan chan error // Channel to terminate the self-deriver with +} + +// NewWallet constructs and returns a new Wallet instance. +func NewWallet(hub *Hub, card *scard.Card) *Wallet { + wallet := &Wallet{ + Hub: hub, + card: card, + } + return wallet +} + +// transmit sends an APDU to the smartcard and receives and decodes the response. +// It automatically handles requests by the card to fetch the return data separately, +// and returns an error if the response status code is not success. +func transmit(card *scard.Card, command *CommandAPDU) (*ResponseAPDU, error) { + data, err := command.serialize() + if err != nil { + return nil, err + } + + responseData, err := card.Transmit(data) + if err != nil { + return nil, err + } + + response := new(ResponseAPDU) + if err = response.deserialize(responseData); err != nil { + return nil, err + } + + // Are we being asked to fetch the response separately? + if response.Sw1 == SW1_GET_RESPONSE && (command.Cla != CLA_ISO7816 || command.Ins != INS_GET_RESPONSE) { + return transmit(card, &CommandAPDU{ + Cla: CLA_ISO7816, + Ins: INS_GET_RESPONSE, + P1: 0, + P2: 0, + Data: nil, + Le: response.Sw2, + }) + } + + if response.Sw1 != SW1_OK { + return nil, fmt.Errorf("Unexpected insecure response status Cla=0x%x, Ins=0x%x, Sw=0x%x%x", command.Cla, command.Ins, response.Sw1, response.Sw2) + } + + return response, nil +} + +// applicationInfo encodes information about the smartcard application - its +// instance UID and public key. +type applicationInfo struct { + InstanceUID []byte `asn1:"tag:15"` + PublicKey []byte `asn1:"tag:0"` +} + +// connect connects to the wallet application and establishes a secure channel with it. +// must be called before any other interaction with the wallet. +func (w *Wallet) connect() error { + w.lock.Lock() + defer w.lock.Unlock() + + appinfo, err := w.doselect() + if err != nil { + return err + } + + channel, err := NewSecureChannelSession(w.card, appinfo.PublicKey) + if err != nil { + return err + } + + w.PublicKey = appinfo.PublicKey + w.log = log.New("url", w.URL()) + w.session = &Session{ + Wallet: w, + Channel: channel, + } + return nil +} + +// doselect is an internal (unlocked) function to send a SELECT APDU to the card. +func (w *Wallet) doselect() (*applicationInfo, error) { + response, err := transmit(w.card, &CommandAPDU{ + Cla: CLA_ISO7816, + Ins: INS_SELECT, + P1: 4, + P2: 0, + Data: APPLET_AID, + }) + if err != nil { + return nil, err + } + + appinfo := new(applicationInfo) + if _, err := asn1.UnmarshalWithParams(response.Data, appinfo, "tag:4"); err != nil { + return nil, err + } + return appinfo, nil +} + +// ping checks the card's status and returns an error if unsuccessful. +func (w *Wallet) ping() error { + w.lock.Lock() + defer w.lock.Unlock() + + if !w.session.paired() { + // We can't ping if not paired + return nil + } + + _, err := w.session.getWalletStatus() + if err != nil { + return err + } + return nil +} + +// release releases any resources held by an open wallet instance. +func (w *Wallet) release() error { + if w.session != nil { + return w.session.release() + } + return nil +} + +// pair is an internal (unlocked) function for establishing a new pairing +// with the wallet. +func (w *Wallet) pair(puk []byte) error { + if w.session.paired() { + return fmt.Errorf("Wallet already paired") + } + + pairing, err := w.session.pair(puk) + if err != nil { + return err + } + + if err = w.Hub.setPairing(w, &pairing); err != nil { + return err + } + + return w.session.authenticate(pairing) +} + +// Unpair deletes an existing wallet pairing. +func (w *Wallet) Unpair(pin []byte) error { + w.lock.Lock() + defer w.lock.Unlock() + + if !w.session.paired() { + return fmt.Errorf("Wallet %x not paired", w.PublicKey) + } + + if err := w.session.verifyPin(pin); err != nil { + return fmt.Errorf("Error verifying pin: %s", err) + } + + if err := w.session.unpair(); err != nil { + return fmt.Errorf("Error unpairing: %s", err) + } + + if err := w.Hub.setPairing(w, nil); err != nil { + return err + } + + return nil +} + +// URL retrieves the canonical path under which this wallet is reachable. It is +// user by upper layers to define a sorting order over all wallets from multiple +// backends. +func (w *Wallet) URL() accounts.URL { + return accounts.URL{ + Scheme: w.Hub.scheme, + Path: fmt.Sprintf("%x", w.PublicKey[1:3]), + } +} + +// Status returns a textual status to aid the user in the current state of the +// wallet. It also returns an error indicating any failure the wallet might have +// encountered. +func (w *Wallet) Status() (string, error) { + w.lock.Lock() + defer w.lock.Unlock() + + if !w.session.paired() { + return "Unpaired", nil + } + + status, err := w.session.getWalletStatus() + if err != nil { + return "Error", err + } + + if w.session.verified { + return fmt.Sprintf("Open, %s", status), nil + } else { + return fmt.Sprintf("Locked, %s", status), nil + } +} + +// Open initializes access to a wallet instance. It is not meant to unlock or +// decrypt account keys, rather simply to establish a connection to hardware +// wallets and/or to access derivation seeds. +// +// The passphrase parameter may or may not be used by the implementation of a +// particular wallet instance. The reason there is no passwordless open method +// is to strive towards a uniform wallet handling, oblivious to the different +// backend providers. +// +// Please note, if you open a wallet, you must close it to release any allocated +// resources (especially important when working with hardware wallets). +func (w *Wallet) Open(passphrase string) error { + w.lock.Lock() + defer w.lock.Unlock() + + if w.session.verified { + // Already open + return AlreadyOpenError + } + + if !w.session.paired() { + // Unpaired. + if pairing := w.Hub.getPairing(w); pairing != nil { + // Authenticate with the existing pairing. + if err := w.session.authenticate(*pairing); err != nil { + return fmt.Errorf("Could not authenticate with paired card %x: %s", w.PublicKey[:4], err) + } + } else if passphrase != "" { + // Establish a new pairing + return w.pair([]byte(passphrase)) + } else { + return PairingRequiredError + } + } + + if passphrase == "" { + return PinRequiredError + } + // Verify pin + if err := w.session.verifyPin([]byte(passphrase)); err != nil { + return err + } + + w.deriveReq = make(chan chan struct{}) + w.deriveQuit = make(chan chan error) + + go w.selfDerive() + + // Notify anyone listening for wallet events that a new device is accessible + go w.Hub.updateFeed.Send(accounts.WalletEvent{Wallet: w, Kind: accounts.WalletOpened}) + + return nil +} + +// Close stops and closes the wallet, freeing any resources. +func (w *Wallet) Close() error { + // Ensure the wallet was opened + w.lock.Lock() + dQuit := w.deriveQuit + w.lock.Unlock() + + // Terminate the self-derivations + var derr error + if dQuit != nil { + errc := make(chan error) + dQuit <- errc + derr = <-errc // Save for later, we *must* close the USB + } + // Terminate the device connection + w.lock.Lock() + defer w.lock.Unlock() + + w.deriveQuit = nil + w.deriveReq = nil + + if err := w.release(); err != nil { + return err + } + return derr +} + +// selfDerive is an account derivation loop that upon request attempts to find +// new non-zero accounts. +func (w *Wallet) selfDerive() { + w.log.Debug("Smartcard wallet self-derivation started") + defer w.log.Debug("Smartcard wallet self-derivation stopped") + + // Execute self-derivations until termination or error + var ( + reqc chan struct{} + errc chan error + err error + ) + for errc == nil && err == nil { + // Wait until either derivation or termination is requested + select { + case errc = <-w.deriveQuit: + // Termination requested + continue + case reqc = <-w.deriveReq: + // Account discovery requested + } + // Derivation needs a chain and device access, skip if either unavailable + w.lock.Lock() + if w.session == nil || w.deriveChain == nil { + w.lock.Unlock() + reqc <- struct{}{} + continue + } + pairing := w.Hub.getPairing(w) + + // Device lock obtained, derive the next batch of accounts + var ( + paths []accounts.DerivationPath + nextAccount accounts.Account + nextAddr = w.deriveNextAddr + nextPath = w.deriveNextPath + + context = context.Background() + ) + for empty := false; !empty; { + // Retrieve the next derived Ethereum account + if nextAddr == (common.Address{}) { + if nextAccount, err = w.session.derive(nextPath); err != nil { + w.log.Warn("Smartcard wallet account derivation failed", "err", err) + break + } + nextAddr = nextAccount.Address + } + // Check the account's status against the current chain state + var ( + balance *big.Int + nonce uint64 + ) + balance, err = w.deriveChain.BalanceAt(context, nextAddr, nil) + if err != nil { + w.log.Warn("Smartcard wallet balance retrieval failed", "err", err) + break + } + nonce, err = w.deriveChain.NonceAt(context, nextAddr, nil) + if err != nil { + w.log.Warn("Smartcard wallet nonce retrieval failed", "err", err) + break + } + // If the next account is empty, stop self-derivation, but add it nonetheless + if balance.Sign() == 0 && nonce == 0 { + empty = true + } + // We've just self-derived a new account, start tracking it locally + path := make(accounts.DerivationPath, len(nextPath)) + copy(path[:], nextPath[:]) + paths = append(paths, path) + + // Display a log message to the user for new (or previously empty accounts) + if _, known := pairing.Accounts[nextAddr]; !known || (!empty && nextAddr == w.deriveNextAddr) { + w.log.Info("Smartcard wallet discovered new account", "address", nextAccount.Address, "path", path, "balance", balance, "nonce", nonce) + } + pairing.Accounts[nextAddr] = path + + // Fetch the next potential account + if !empty { + nextAddr = common.Address{} + nextPath[len(nextPath)-1]++ + } + } + + // If there are new accounts, write them out + if len(paths) > 0 { + err = w.Hub.setPairing(w, pairing) + } + + // Shift the self-derivation forward + w.deriveNextAddr = nextAddr + w.deriveNextPath = nextPath + + // Self derivation complete, release device lock + w.lock.Unlock() + + // Notify the user of termination and loop after a bit of time (to avoid trashing) + reqc <- struct{}{} + if err == nil { + select { + case errc = <-w.deriveQuit: + // Termination requested, abort + case <-time.After(selfDeriveThrottling): + // Waited enough, willing to self-derive again + } + } + } + // In case of error, wait for termination + if err != nil { + w.log.Debug("Smartcard wallet self-derivation failed", "err", err) + errc = <-w.deriveQuit + } + errc <- err +} + +// Accounts retrieves the list of signing accounts the wallet is currently aware +// of. For hierarchical deterministic wallets, the list will not be exhaustive, +// rather only contain the accounts explicitly pinned during account derivation. +func (w *Wallet) Accounts() []accounts.Account { + // Attempt self-derivation if it's running + reqc := make(chan struct{}, 1) + select { + case w.deriveReq <- reqc: + // Self-derivation request accepted, wait for it + <-reqc + default: + // Self-derivation offline, throttled or busy, skip + } + + w.lock.Lock() + defer w.lock.Unlock() + + if pairing := w.Hub.getPairing(w); pairing != nil { + ret := make([]accounts.Account, 0, len(pairing.Accounts)) + for address, path := range pairing.Accounts { + ret = append(ret, w.makeAccount(address, path)) + } + return ret + } + return nil +} + +func (w *Wallet) makeAccount(address common.Address, path accounts.DerivationPath) accounts.Account { + return accounts.Account{ + Address: address, + URL: accounts.URL{ + Scheme: w.Hub.scheme, + Path: fmt.Sprintf("%x/%s", w.PublicKey[1:3], path.String()), + }, + } +} + +// Contains returns whether an account is part of this particular wallet or not. +func (w *Wallet) Contains(account accounts.Account) bool { + if pairing := w.Hub.getPairing(w); pairing != nil { + _, ok := pairing.Accounts[account.Address] + return ok + } + return false +} + +// Initialize installs a keypair generated from the provided key into the wallet. +func (w *Wallet) Initialize(seed []byte) error { + w.lock.Lock() + defer w.lock.Unlock() + + return w.session.initialize(seed) +} + +// Derive attempts to explicitly derive a hierarchical deterministic account at +// the specified derivation path. If requested, the derived account will be added +// to the wallet's tracked account list. +func (w *Wallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) { + w.lock.Lock() + defer w.lock.Unlock() + + account, err := w.session.derive(path) + if err != nil { + return accounts.Account{}, err + } + + if pin { + pairing := w.Hub.getPairing(w) + pairing.Accounts[account.Address] = path + if err := w.Hub.setPairing(w, pairing); err != nil { + return accounts.Account{}, err + } + } + + return account, nil +} + +// SelfDerive sets a base account derivation path from which the wallet attempts +// to discover non zero accounts and automatically add them to list of tracked +// accounts. +// +// Note, self derivaton will increment the last component of the specified path +// opposed to decending into a child path to allow discovering accounts starting +// from non zero components. +// +// You can disable automatic account discovery by calling SelfDerive with a nil +// chain state reader. +func (w *Wallet) SelfDerive(base accounts.DerivationPath, chain ethereum.ChainStateReader) { + w.lock.Lock() + defer w.lock.Unlock() + + w.deriveNextPath = make(accounts.DerivationPath, len(base)) + copy(w.deriveNextPath[:], base[:]) + + w.deriveNextAddr = common.Address{} + w.deriveChain = chain +} + +// SignHash requests the wallet to sign the given hash. +// +// It looks up the account specified either solely via its address contained within, +// or optionally with the aid of any location metadata from the embedded URL field. +// +// If the wallet requires additional authentication to sign the request (e.g. +// a password to decrypt the account, or a PIN code o verify the transaction), +// an AuthNeededError instance will be returned, containing infos for the user +// about which fields or actions are needed. The user may retry by providing +// the needed details via SignHashWithPassphrase, or by other means (e.g. unlock +// the account in a keystore). +func (w *Wallet) SignHash(account accounts.Account, hash []byte) ([]byte, error) { + w.lock.Lock() + defer w.lock.Unlock() + + path, err := w.findAccountPath(account) + if err != nil { + return nil, err + } + + return w.session.sign(path, hash) +} + +// SignTx requests the wallet to sign the given transaction. +// +// It looks up the account specified either solely via its address contained within, +// or optionally with the aid of any location metadata from the embedded URL field. +// +// If the wallet requires additional authentication to sign the request (e.g. +// a password to decrypt the account, or a PIN code o verify the transaction), +// an AuthNeededError instance will be returned, containing infos for the user +// about which fields or actions are needed. The user may retry by providing +// the needed details via SignTxWithPassphrase, or by other means (e.g. unlock +// the account in a keystore). +func (w *Wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + signer := types.NewEIP155Signer(chainID) + hash := signer.Hash(tx) + sig, err := w.SignHash(account, hash[:]) + if err != nil { + return nil, err + } + return tx.WithSignature(signer, sig) +} + +// SignHashWithPassphrase requests the wallet to sign the given hash with the +// given passphrase as extra authentication information. +// +// It looks up the account specified either solely via its address contained within, +// or optionally with the aid of any location metadata from the embedded URL field. +func (w *Wallet) SignHashWithPassphrase(account accounts.Account, passphrase string, hash []byte) ([]byte, error) { + if !w.session.verified { + if err := w.Open(passphrase); err != nil { + return nil, err + } + } + + return w.SignHash(account, hash) +} + +// SignTxWithPassphrase requests the wallet to sign the given transaction, with the +// given passphrase as extra authentication information. +// +// It looks up the account specified either solely via its address contained within, +// or optionally with the aid of any location metadata from the embedded URL field. +func (w *Wallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { + if !w.session.verified { + if err := w.Open(passphrase); err != nil { + return nil, err + } + } + return w.SignTx(account, tx, chainID) +} + +// findAccountPath returns the derivation path for the provided account. +// It first checks for the address in the list of pinned accounts, and if it is +// not found, attempts to parse the derivation path from the account's URL. +func (w *Wallet) findAccountPath(account accounts.Account) (accounts.DerivationPath, error) { + pairing := w.Hub.getPairing(w) + if path, ok := pairing.Accounts[account.Address]; ok { + return path, nil + } + + // Look for the path in the URL + if account.URL.Scheme != w.Hub.scheme { + return nil, fmt.Errorf("Scheme %s does not match wallet scheme %s", account.URL.Scheme, w.Hub.scheme) + } + + parts := strings.SplitN(account.URL.Path, "/", 2) + if len(parts) != 2 { + return nil, fmt.Errorf("Invalid URL format: %s", account.URL) + } + + if parts[0] != fmt.Sprintf("%x", w.PublicKey[1:3]) { + return nil, fmt.Errorf("URL %s is not for this wallet", account.URL) + } + + return accounts.ParseDerivationPath(parts[1]) +} + +// Session represents a secured communication session with the wallet +type Session struct { + Wallet *Wallet // A handle to the wallet that opened the session + Channel *SecureChannelSession // A secure channel for encrypted messages + verified bool // Whether the pin has been verified in this session. +} + +// pair establishes a new pairing over this channel, using the provided secret. +func (s *Session) pair(secret []byte) (SmartcardPairing, error) { + err := s.Channel.Pair(secret) + if err != nil { + return SmartcardPairing{}, err + } + + return SmartcardPairing{ + PublicKey: s.Wallet.PublicKey, + PairingIndex: s.Channel.PairingIndex, + PairingKey: s.Channel.PairingKey, + Accounts: make(map[common.Address]accounts.DerivationPath), + }, nil +} + +// unpair deletes an existing pairing +func (s *Session) unpair() error { + if !s.verified { + return fmt.Errorf("Unpair requires that the PIN be verified") + } + return s.Channel.Unpair() +} + +// verifyPin unlocks a wallet with the provided pin +func (s *Session) verifyPin(pin []byte) error { + if _, err := s.Channel.TransmitEncrypted(CLA_SCWALLET, INS_VERIFY_PIN, 0, 0, pin); err != nil { + return err + } + s.verified = true + return nil +} + +// release releases resources associated with the channel +func (s *Session) release() error { + return s.Wallet.card.Disconnect(scard.LeaveCard) +} + +// paired returns true if a valid pairing exists +func (s *Session) paired() bool { + return s.Channel.PairingKey != nil +} + +// authenticate uses an existing pairing to establish a secure channel +func (s *Session) authenticate(pairing SmartcardPairing) error { + if !bytes.Equal(s.Wallet.PublicKey, pairing.PublicKey) { + return fmt.Errorf("Cannot pair using another wallet's pairing; %x != %x", s.Wallet.PublicKey, pairing.PublicKey) + } + s.Channel.PairingKey = pairing.PairingKey + s.Channel.PairingIndex = pairing.PairingIndex + return s.Channel.Open() +} + +// walletStatus describes a smartcard wallet's status information +type walletStatus struct { + PinRetryCount int // Number of remaining PIN retries + PukRetryCount int // Number of remaining PUK retries + Initialized bool // Whether the card has been initialized with a private key + SupportsPKDerivation bool // Whether the card supports doing public key derivation itself +} + +func (w walletStatus) String() string { + return fmt.Sprintf("pinRetryCount=%d, pukRetryCount=%d, initialized=%t, supportsPkDerivation=%t", w.PinRetryCount, w.PukRetryCount, w.Initialized, w.SupportsPKDerivation) +} + +// getWalletStatus fetches the wallet's status from the card +func (s *Session) getWalletStatus() (*walletStatus, error) { + response, err := s.Channel.TransmitEncrypted(CLA_SCWALLET, INS_STATUS, STATUS_P1_WALLET_STATUS, 0, nil) + if err != nil { + return nil, err + } + + status := new(walletStatus) + if _, err := asn1.UnmarshalWithParams(response.Data, status, "tag:3"); err != nil { + return nil, err + } + + return status, nil +} + +// getDerivationPath fetches the wallet's current derivation path from the card +func (s *Session) getDerivationPath() (accounts.DerivationPath, error) { + response, err := s.Channel.TransmitEncrypted(CLA_SCWALLET, INS_STATUS, STATUS_P1_PATH, 0, nil) + if err != nil { + return nil, err + } + + buf := bytes.NewReader(response.Data) + path := make(accounts.DerivationPath, len(response.Data)/4) + return path, binary.Read(buf, binary.BigEndian, &path) +} + +// initializeData contains data needed to initialize the smartcard wallet +type initializeData struct { + PublicKey []byte `asn1:"tag:0"` + PrivateKey []byte `asn1:"tag:1"` + ChainCode []byte `asn1:"tag:2"` +} + +// initialize initializes the card with new key data +func (s *Session) initialize(seed []byte) error { + // HMAC the seed to produce the private key and chain code + mac := hmac.New(sha512.New, []byte("Bitcoin seed")) + mac.Write(seed) + seed = mac.Sum(nil) + + key, err := crypto.ToECDSA(seed[:32]) + if err != nil { + return err + } + + id := initializeData{} + id.PublicKey = crypto.FromECDSAPub(&key.PublicKey) + id.PrivateKey = seed[:32] + id.ChainCode = seed[32:] + data, err := asn1.Marshal(id) + if err != nil { + return err + } + + // Nasty hack to force the top-level struct tag to be context-specific + data[0] = 0xA1 + + _, err = s.Channel.TransmitEncrypted(CLA_SCWALLET, INS_LOAD_KEY, 0x02, 0, data) + return err +} + +// derive derives a new HD key path on the card +func (s *Session) derive(path accounts.DerivationPath) (accounts.Account, error) { + // If the current path is a prefix of the desired path, we don't have to + // start again. + remainingPath := path + + pubkey, err := s.getPublicKey() + if err != nil { + return accounts.Account{}, err + } + currentPath, err := s.getDerivationPath() + if err != nil { + return accounts.Account{}, err + } + + reset := false + if len(currentPath) <= len(path) { + for i := 0; i < len(currentPath); i++ { + if path[i] != currentPath[i] { + reset = true + break + } + } + if !reset { + remainingPath = path[len(currentPath):] + } + } else { + reset = true + } + + for _, pathComponent := range remainingPath { + pubkey, err = s.deriveKeyAssisted(reset, pathComponent) + reset = false + if err != nil { + return accounts.Account{}, err + } + } + return s.Wallet.makeAccount(crypto.PubkeyToAddress(*crypto.ToECDSAPub(pubkey)), path), nil +} + +// keyDerivationInfo contains information on the current key derivation step +type keyDerivationInfo struct { + PublicKeyX []byte `asn1:"tag:3"` // The X coordinate of the current public key + Signature struct { + R *big.Int + S *big.Int + } +} + +// deriveKeyAssisted does one step of assisted key generation, asking the card to generate +// a specific path, and performing the necessary computations to finish the public key +// generation step. +func (s *Session) deriveKeyAssisted(reset bool, pathComponent uint32) ([]byte, error) { + p1 := DERIVE_P1_ASSISTED + if !reset { + p1 |= DERIVE_P1_APPEND + } + + buf := new(bytes.Buffer) + if err := binary.Write(buf, binary.BigEndian, pathComponent); err != nil { + return nil, err + } + response, err := s.Channel.TransmitEncrypted(CLA_SCWALLET, INS_DERIVE_KEY, p1, DERIVE_P2_KEY_PATH, buf.Bytes()) + if err != nil { + return nil, err + } + + keyinfo := new(keyDerivationInfo) + if _, err := asn1.UnmarshalWithParams(response.Data, keyinfo, "tag:2"); err != nil { + return nil, err + } + + rbytes, sbytes := keyinfo.Signature.R.Bytes(), keyinfo.Signature.S.Bytes() + sig := make([]byte, 65) + copy(sig[32-len(rbytes):32], rbytes) + copy(sig[64-len(sbytes):64], sbytes) + + pubkey, err := determinePublicKey(sig, keyinfo.PublicKeyX) + if err != nil { + return nil, err + } + + _, err = s.Channel.TransmitEncrypted(CLA_SCWALLET, INS_DERIVE_KEY, DERIVE_P1_ASSISTED|DERIVE_P1_APPEND, DERIVE_P2_PUBLIC_KEY, pubkey) + if err != nil { + return nil, err + } + + return pubkey, nil +} + +// keyExport contains information on an exported keypair +type keyExport struct { + PublicKey []byte `asn1:"tag:0"` + PrivateKey []byte `asn1:"tag:1,optional"` +} + +// getPublicKey returns the public key for the current derivation path +func (s *Session) getPublicKey() ([]byte, error) { + response, err := s.Channel.TransmitEncrypted(CLA_SCWALLET, INS_EXPORT_KEY, EXPORT_P1_ANY, EXPORT_P2_PUBKEY, nil) + if err != nil { + return nil, err + } + + keys := new(keyExport) + if _, err := asn1.UnmarshalWithParams(response.Data, keys, "tag:1"); err != nil { + return nil, err + } + + return keys.PublicKey, nil +} + +// signatureData contains information on a signature - the signature itself and +// the corresponding public key +type signatureData struct { + PublicKey []byte `asn1:"tag:0"` + Signature struct { + R *big.Int + S *big.Int + } +} + +// sign asks the card to sign a message, and returns a valid signature after +// recovering the v value. +func (s *Session) sign(path accounts.DerivationPath, hash []byte) ([]byte, error) { + startTime := time.Now() + _, err := s.derive(path) + if err != nil { + return nil, err + } + deriveTime := time.Now() + + response, err := s.Channel.TransmitEncrypted(CLA_SCWALLET, INS_SIGN, SIGN_P1_PRECOMPUTED_HASH, SIGN_P2_ONLY_BLOCK, hash) + if err != nil { + return nil, err + } + + sigdata := new(signatureData) + if _, err := asn1.UnmarshalWithParams(response.Data, sigdata, "tag:0"); err != nil { + return nil, err + } + + // Serialize the signature + rbytes, sbytes := sigdata.Signature.R.Bytes(), sigdata.Signature.S.Bytes() + sig := make([]byte, 65) + copy(sig[32-len(rbytes):32], rbytes) + copy(sig[64-len(sbytes):64], sbytes) + + // Recover the V value. + sig, err = makeRecoverableSignature(hash, sig, sigdata.PublicKey) + if err != nil { + return nil, err + } + + log.Debug("Signed using smartcard", "deriveTime", deriveTime.Sub(startTime), "signingTime", time.Since(deriveTime)) + + return sig, nil +} + +// determinePublicKey uses a signature and the X component of a public key to +// recover the entire public key. +func determinePublicKey(sig, pubkeyX []byte) ([]byte, error) { + for v := 0; v < 2; v++ { + sig[64] = byte(v) + pubkey, err := crypto.Ecrecover(DerivationSignatureHash[:], sig) + if err == nil { + if bytes.Compare(pubkey[1:33], pubkeyX) == 0 { + return pubkey, nil + } + } else if v == 1 || err != secp256k1.ErrRecoverFailed { + return nil, err + } + } + return nil, PubkeyMismatchError +} + +// makeRecoverableSignature uses a signature and an expected public key to +// recover the v value and produce a recoverable signature. +func makeRecoverableSignature(hash, sig, expectedPubkey []byte) ([]byte, error) { + for v := 0; v < 2; v++ { + sig[64] = byte(v) + pubkey, err := crypto.Ecrecover(hash, sig) + if err == nil { + if bytes.Compare(pubkey, expectedPubkey) == 0 { + return sig, nil + } + } else if v == 1 || err != secp256k1.ErrRecoverFailed { + return nil, err + } + } + return nil, PubkeyMismatchError +} diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index e5a8124b1..7938b264a 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -28,6 +28,7 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/ethereum/go-ethereum/accounts/scwallet" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" @@ -44,6 +45,7 @@ import ( "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" "github.com/syndtr/goleveldb/leveldb" + "github.com/tyler-smith/go-bip39" ) const ( @@ -471,6 +473,46 @@ func (s *PrivateAccountAPI) SignAndSendTransaction(ctx context.Context, args Sen return s.SendTransaction(ctx, args, passwd) } +func (s *PrivateAccountAPI) InitializeWallet(ctx context.Context, url string) (string, error) { + wallet, err := s.am.Wallet(url) + if err != nil { + return "", err + } + + entropy, err := bip39.NewEntropy(256) + if err != nil { + return "", err + } + + mnemonic, err := bip39.NewMnemonic(entropy) + if err != nil { + return "", err + } + + seed := bip39.NewSeed(mnemonic, "") + + switch wallet := wallet.(type) { + case *scwallet.Wallet: + return mnemonic, wallet.Initialize(seed) + default: + return "", fmt.Errorf("Specified wallet does not support initialization") + } +} + +func (s *PrivateAccountAPI) Unpair(ctx context.Context, url string, pin string) error { + wallet, err := s.am.Wallet(url) + if err != nil { + return err + } + + switch wallet := wallet.(type) { + case *scwallet.Wallet: + return wallet.Unpair([]byte(pin)) + default: + return fmt.Errorf("Specified wallet does not support pairing") + } +} + // PublicBlockChainAPI provides an API to access the Ethereum blockchain. // It offers only methods that operate on public data that is freely available to anyone. type PublicBlockChainAPI struct { diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 36fde166b..31c0c57ec 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -613,6 +613,16 @@ web3._extend({ params: 2, inputFormatter: [web3._extend.formatters.inputTransactionFormatter, null] }), + new web3._extend.Method({ + name: 'unpair', + call: 'personal_unpair', + params: 2 + }), + new web3._extend.Method({ + name: 'initializeWallet', + call: 'personal_initializeWallet', + params: 1 + }) ], properties: [ new web3._extend.Property({ diff --git a/node/config.go b/node/config.go index 46876c157..244b15459 100644 --- a/node/config.go +++ b/node/config.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts/external" "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/ethereum/go-ethereum/accounts/scwallet" "github.com/ethereum/go-ethereum/accounts/usbwallet" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" @@ -504,6 +505,12 @@ func makeAccountManager(conf *Config) (*accounts.Manager, string, error) { backends = append(backends, trezorhub) } } + // Start a smart card hub + if schub, err := scwallet.NewHub(scwallet.Scheme, keydir); err != nil { + log.Warn(fmt.Sprintf("Failed to start smart card hub, disabling: %v", err)) + } else { + backends = append(backends, schub) + } } return accounts.NewManager(&accounts.Config{InsecureUnlockAllowed: conf.InsecureUnlockAllowed}, backends...), ephemeral, nil diff --git a/vendor/github.com/ebfe/scard/LICENSE b/vendor/github.com/ebfe/scard/LICENSE new file mode 100644 index 000000000..0d220e7ae --- /dev/null +++ b/vendor/github.com/ebfe/scard/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2016, Michael Gehring +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or + other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/ebfe/scard/README.md b/vendor/github.com/ebfe/scard/README.md new file mode 100644 index 000000000..cdafb2c7b --- /dev/null +++ b/vendor/github.com/ebfe/scard/README.md @@ -0,0 +1,14 @@ +scard +===== + +[![GoDoc](https://godoc.org/github.com/ebfe/scard?status.svg)](https://godoc.org/github.com/ebfe/scard) + +Go bindings to the PC/SC API. + +## Installation + + go get github.com/ebfe/scard + +## Bugs + + - Memory layouts/GC needs a thorough review. diff --git a/vendor/github.com/ebfe/scard/scard.go b/vendor/github.com/ebfe/scard/scard.go new file mode 100644 index 000000000..a5f0ae16b --- /dev/null +++ b/vendor/github.com/ebfe/scard/scard.go @@ -0,0 +1,283 @@ +// Package scard provides bindings to the PC/SC API. +package scard + +import ( + "time" + "unsafe" +) + +type CardStatus struct { + Reader string + State State + ActiveProtocol Protocol + Atr []byte +} + +type ReaderState struct { + Reader string + UserData interface{} + CurrentState StateFlag + EventState StateFlag + Atr []byte +} + +type Context struct { + ctx uintptr +} + +type Card struct { + handle uintptr + activeProtocol Protocol +} + +// wraps SCardEstablishContext +func EstablishContext() (*Context, error) { + ctx, r := scardEstablishContext(ScopeSystem, 0, 0) + if r != ErrSuccess { + return nil, r + } + + return &Context{ctx: ctx}, nil +} + +// wraps SCardIsValidContext +func (ctx *Context) IsValid() (bool, error) { + r := scardIsValidContext(ctx.ctx) + switch r { + case ErrSuccess: + return true, nil + case ErrInvalidHandle: + return false, nil + default: + return false, r + } +} + +// wraps SCardCancel +func (ctx *Context) Cancel() error { + r := scardCancel(ctx.ctx) + if r != ErrSuccess { + return r + } + return nil +} + +// wraps SCardReleaseContext +func (ctx *Context) Release() error { + r := scardReleaseContext(ctx.ctx) + if r != ErrSuccess { + return r + } + return nil +} + +// wraps SCardListReaders +func (ctx *Context) ListReaders() ([]string, error) { + needed, r := scardListReaders(ctx.ctx, nil, nil, 0) + if r != ErrSuccess { + return nil, r + } + + buf := make(strbuf, needed) + n, r := scardListReaders(ctx.ctx, nil, buf.ptr(), uint32(len(buf))) + if r != ErrSuccess { + return nil, r + } + return decodemstr(buf[:n]), nil +} + +// wraps SCardListReaderGroups +func (ctx *Context) ListReaderGroups() ([]string, error) { + needed, r := scardListReaderGroups(ctx.ctx, nil, 0) + if r != ErrSuccess { + return nil, r + } + + buf := make(strbuf, needed) + n, r := scardListReaderGroups(ctx.ctx, buf.ptr(), uint32(len(buf))) + if r != ErrSuccess { + return nil, r + } + return decodemstr(buf[:n]), nil +} + +// wraps SCardGetStatusChange +func (ctx *Context) GetStatusChange(readerStates []ReaderState, timeout time.Duration) error { + + dwTimeout := durationToTimeout(timeout) + states := make([]scardReaderState, len(readerStates)) + + for i := range readerStates { + var err error + states[i], err = readerStates[i].toSys() + if err != nil { + return err + } + } + + r := scardGetStatusChange(ctx.ctx, dwTimeout, states) + if r != ErrSuccess { + return r + } + + for i := range readerStates { + (&readerStates[i]).update(&states[i]) + } + + return nil +} + +// wraps SCardConnect +func (ctx *Context) Connect(reader string, mode ShareMode, proto Protocol) (*Card, error) { + creader, err := encodestr(reader) + if err != nil { + return nil, err + } + handle, activeProtocol, r := scardConnect(ctx.ctx, creader.ptr(), mode, proto) + if r != ErrSuccess { + return nil, r + } + return &Card{handle: handle, activeProtocol: activeProtocol}, nil +} + +// wraps SCardDisconnect +func (card *Card) Disconnect(d Disposition) error { + r := scardDisconnect(card.handle, d) + if r != ErrSuccess { + return r + } + return nil +} + +// wraps SCardReconnect +func (card *Card) Reconnect(mode ShareMode, proto Protocol, disp Disposition) error { + activeProtocol, r := scardReconnect(card.handle, mode, proto, disp) + if r != ErrSuccess { + return r + } + card.activeProtocol = activeProtocol + return nil +} + +// wraps SCardBeginTransaction +func (card *Card) BeginTransaction() error { + r := scardBeginTransaction(card.handle) + if r != ErrSuccess { + return r + } + return nil +} + +// wraps SCardEndTransaction +func (card *Card) EndTransaction(disp Disposition) error { + r := scardEndTransaction(card.handle, disp) + if r != ErrSuccess { + return r + } + return nil +} + +// wraps SCardStatus +func (card *Card) Status() (*CardStatus, error) { + reader, state, proto, atr, err := scardCardStatus(card.handle) + if err != ErrSuccess { + return nil, err + } + return &CardStatus{Reader: reader, State: state, ActiveProtocol: proto, Atr: atr}, nil +} + +// wraps SCardTransmit +func (card *Card) Transmit(cmd []byte) ([]byte, error) { + rsp := make([]byte, maxBufferSizeExtended) + rspLen, err := scardTransmit(card.handle, card.activeProtocol, cmd, rsp) + if err != ErrSuccess { + return nil, err + } + return rsp[:rspLen], nil +} + +// wraps SCardControl +func (card *Card) Control(ioctl uint32, in []byte) ([]byte, error) { + var out [0xffff]byte + outLen, err := scardControl(card.handle, ioctl, in, out[:]) + if err != ErrSuccess { + return nil, err + } + return out[:outLen], nil +} + +// wraps SCardGetAttrib +func (card *Card) GetAttrib(id Attrib) ([]byte, error) { + needed, err := scardGetAttrib(card.handle, id, nil) + if err != ErrSuccess { + return nil, err + } + + var attrib = make([]byte, needed) + n, err := scardGetAttrib(card.handle, id, attrib) + if err != ErrSuccess { + return nil, err + } + return attrib[:n], nil +} + +// wraps SCardSetAttrib +func (card *Card) SetAttrib(id Attrib, data []byte) error { + err := scardSetAttrib(card.handle, id, data) + if err != ErrSuccess { + return err + } + return nil +} + +func durationToTimeout(timeout time.Duration) uint32 { + switch { + case timeout < 0: + return infiniteTimeout + case timeout > time.Duration(infiniteTimeout)*time.Millisecond: + return infiniteTimeout - 1 + default: + return uint32(timeout / time.Millisecond) + } +} + +func (buf strbuf) ptr() unsafe.Pointer { + return unsafe.Pointer(&buf[0]) +} + +func (buf strbuf) split() []strbuf { + var chunks []strbuf + for len(buf) > 0 && buf[0] != 0 { + i := 0 + for i = range buf { + if buf[i] == 0 { + break + } + } + chunks = append(chunks, buf[:i+1]) + buf = buf[i+1:] + } + + return chunks +} + +func encodemstr(strings ...string) (strbuf, error) { + var buf strbuf + for _, s := range strings { + utf16, err := encodestr(s) + if err != nil { + return nil, err + } + buf = append(buf, utf16...) + } + buf = append(buf, 0) + return buf, nil +} + +func decodemstr(buf strbuf) []string { + var strings []string + for _, chunk := range buf.split() { + strings = append(strings, decodestr(chunk)) + } + return strings +} diff --git a/vendor/github.com/ebfe/scard/scard_darwin.go b/vendor/github.com/ebfe/scard/scard_darwin.go new file mode 100644 index 000000000..83a1597b3 --- /dev/null +++ b/vendor/github.com/ebfe/scard/scard_darwin.go @@ -0,0 +1,219 @@ +// +build darwin + +package scard + +// #cgo LDFLAGS: -framework PCSC +// #cgo CFLAGS: -I /usr/include +// #include +// #include +// #include +import "C" + +import ( + "unsafe" +) + +func (e Error) Error() string { + return "scard: " + C.GoString(C.pcsc_stringify_error(C.int32_t(e))) +} + +// Version returns the libpcsclite version string +func Version() string { + return C.PCSCLITE_VERSION_NUMBER +} + +func scardEstablishContext(scope Scope, reserved1, reserved2 uintptr) (uintptr, Error) { + var ctx C.SCARDCONTEXT + r := C.SCardEstablishContext(C.uint32_t(scope), unsafe.Pointer(reserved1), unsafe.Pointer(reserved2), &ctx) + return uintptr(ctx), Error(r) +} + +func scardIsValidContext(ctx uintptr) Error { + r := C.SCardIsValidContext(C.SCARDCONTEXT(ctx)) + return Error(r) +} + +func scardCancel(ctx uintptr) Error { + r := C.SCardCancel(C.SCARDCONTEXT(ctx)) + return Error(r) +} + +func scardReleaseContext(ctx uintptr) Error { + r := C.SCardReleaseContext(C.SCARDCONTEXT(ctx)) + return Error(r) +} + +func scardListReaders(ctx uintptr, groups, buf unsafe.Pointer, bufLen uint32) (uint32, Error) { + dwBufLen := C.uint32_t(bufLen) + r := C.SCardListReaders(C.SCARDCONTEXT(ctx), (C.LPCSTR)(groups), (C.LPSTR)(buf), &dwBufLen) + return uint32(dwBufLen), Error(r) +} + +func scardListReaderGroups(ctx uintptr, buf unsafe.Pointer, bufLen uint32) (uint32, Error) { + dwBufLen := C.uint32_t(bufLen) + r := C.SCardListReaderGroups(C.SCARDCONTEXT(ctx), (C.LPSTR)(buf), &dwBufLen) + return uint32(dwBufLen), Error(r) +} + +func scardGetStatusChange(ctx uintptr, timeout uint32, states []scardReaderState) Error { + // In darwin, the LPSCARD_READERSTATE_A has 1 byte alignment and hence + // has no trailing padding. Go does add 3 bytes of padding (on both 32 + // and 64 bits), so we pack an array manually instead. + const size = int(unsafe.Sizeof(states[0])) - 3 + buf := make([]byte, size*len(states)) + for i, _ := range states { + copy(buf[i*size:(i+1)*size], (*(*[size]byte)(unsafe.Pointer(&states[i])))[:]) + } + r := C.SCardGetStatusChange(C.SCARDCONTEXT(ctx), C.uint32_t(timeout), (C.LPSCARD_READERSTATE_A)(unsafe.Pointer(&buf[0])), C.uint32_t(len(states))) + for i, _ := range states { + copy((*(*[size]byte)(unsafe.Pointer(&states[i])))[:], buf[i*size:(i+1)*size]) + } + return Error(r) +} + +func scardConnect(ctx uintptr, reader unsafe.Pointer, shareMode ShareMode, proto Protocol) (uintptr, Protocol, Error) { + var handle C.SCARDHANDLE + var activeProto C.uint32_t + + r := C.SCardConnect(C.SCARDCONTEXT(ctx), C.LPCSTR(reader), C.uint32_t(shareMode), C.uint32_t(proto), &handle, &activeProto) + + return uintptr(handle), Protocol(activeProto), Error(r) +} + +func scardDisconnect(card uintptr, d Disposition) Error { + r := C.SCardDisconnect(C.SCARDHANDLE(card), C.uint32_t(d)) + return Error(r) +} + +func scardReconnect(card uintptr, mode ShareMode, proto Protocol, disp Disposition) (Protocol, Error) { + var activeProtocol C.uint32_t + r := C.SCardReconnect(C.SCARDHANDLE(card), C.uint32_t(mode), C.uint32_t(proto), C.uint32_t(disp), &activeProtocol) + return Protocol(activeProtocol), Error(r) +} + +func scardBeginTransaction(card uintptr) Error { + r := C.SCardBeginTransaction(C.SCARDHANDLE(card)) + return Error(r) +} + +func scardEndTransaction(card uintptr, disp Disposition) Error { + r := C.SCardEndTransaction(C.SCARDHANDLE(card), C.uint32_t(disp)) + return Error(r) +} + +func scardCardStatus(card uintptr) (string, State, Protocol, []byte, Error) { + var readerBuf [C.MAX_READERNAME + 1]byte + var readerLen = C.uint32_t(len(readerBuf)) + var state, proto C.uint32_t + var atr [maxAtrSize]byte + var atrLen = C.uint32_t(len(atr)) + + r := C.SCardStatus(C.SCARDHANDLE(card), (C.LPSTR)(unsafe.Pointer(&readerBuf[0])), &readerLen, &state, &proto, (*C.uchar)(&atr[0]), &atrLen) + + return decodestr(readerBuf[:readerLen]), State(state), Protocol(proto), atr[:atrLen], Error(r) +} + +func scardTransmit(card uintptr, proto Protocol, cmd []byte, rsp []byte) (uint32, Error) { + var sendpci C.SCARD_IO_REQUEST + var recvpci C.SCARD_IO_REQUEST + var rspLen = C.uint32_t(len(rsp)) + + switch proto { + case ProtocolT0, ProtocolT1: + sendpci.dwProtocol = C.uint32_t(proto) + default: + panic("unknown protocol") + } + sendpci.cbPciLength = C.sizeof_SCARD_IO_REQUEST + + r := C.SCardTransmit(C.SCARDHANDLE(card), &sendpci, (*C.uchar)(&cmd[0]), C.uint32_t(len(cmd)), &recvpci, (*C.uchar)(&rsp[0]), &rspLen) + + return uint32(rspLen), Error(r) +} + +func scardControl(card uintptr, ioctl uint32, in, out []byte) (uint32, Error) { + var ptrIn unsafe.Pointer + var outLen = C.uint32_t(len(out)) + + if len(in) != 0 { + ptrIn = unsafe.Pointer(&in[0]) + } + + r := C.SCardControl(C.SCARDHANDLE(card), C.uint32_t(ioctl), ptrIn, C.uint32_t(len(in)), unsafe.Pointer(&out[0]), C.uint32_t(len(out)), &outLen) + return uint32(outLen), Error(r) +} + +func scardGetAttrib(card uintptr, id Attrib, buf []byte) (uint32, Error) { + var ptr *C.uint8_t + + if len(buf) != 0 { + ptr = (*C.uint8_t)(&buf[0]) + } + + bufLen := C.uint32_t(len(buf)) + r := C.SCardGetAttrib(C.SCARDHANDLE(card), C.uint32_t(id), ptr, &bufLen) + + return uint32(bufLen), Error(r) +} + +func scardSetAttrib(card uintptr, id Attrib, buf []byte) Error { + r := C.SCardSetAttrib(C.SCARDHANDLE(card), C.uint32_t(id), ((*C.uint8_t)(&buf[0])), C.uint32_t(len(buf))) + return Error(r) +} + +type strbuf []byte + +func encodestr(s string) (strbuf, error) { + buf := strbuf(s + "\x00") + return buf, nil +} + +func decodestr(buf strbuf) string { + if len(buf) == 0 { + return "" + } + + if buf[len(buf)-1] == 0 { + buf = buf[:len(buf)-1] + } + + return string(buf) +} + +type scardReaderState struct { + szReader uintptr + pvUserData uintptr + dwCurrentState uint32 + dwEventState uint32 + cbAtr uint32 + rgbAtr [33]byte +} + +var pinned = map[string]*strbuf{} + +func (rs *ReaderState) toSys() (scardReaderState, error) { + var sys scardReaderState + + creader, err := encodestr(rs.Reader) + if err != nil { + return scardReaderState{}, err + } + pinned[rs.Reader] = &creader + sys.szReader = uintptr(creader.ptr()) + sys.dwCurrentState = uint32(rs.CurrentState) + sys.cbAtr = uint32(len(rs.Atr)) + for i, v := range rs.Atr { + sys.rgbAtr[i] = byte(v) + } + return sys, nil +} + +func (rs *ReaderState) update(sys *scardReaderState) { + rs.EventState = StateFlag(sys.dwEventState) + if sys.cbAtr > 0 { + rs.Atr = make([]byte, int(sys.cbAtr)) + for i := 0; i < int(sys.cbAtr); i++ { + rs.Atr[i] = byte(sys.rgbAtr[i]) + } + } +} diff --git a/vendor/github.com/ebfe/scard/scard_unix.go b/vendor/github.com/ebfe/scard/scard_unix.go new file mode 100644 index 000000000..21910318f --- /dev/null +++ b/vendor/github.com/ebfe/scard/scard_unix.go @@ -0,0 +1,206 @@ +// +build !windows,!darwin + +package scard + +// #cgo pkg-config: libpcsclite +// #include +// #include +import "C" + +import ( + "unsafe" +) + +func (e Error) Error() string { + return "scard: " + C.GoString(C.pcsc_stringify_error(C.LONG(e))) +} + +// Version returns the libpcsclite version string +func Version() string { + return C.PCSCLITE_VERSION_NUMBER +} + +func scardEstablishContext(scope Scope, reserved1, reserved2 uintptr) (uintptr, Error) { + var ctx C.SCARDCONTEXT + r := C.SCardEstablishContext(C.DWORD(scope), C.LPCVOID(reserved1), C.LPCVOID(reserved2), &ctx) + return uintptr(ctx), Error(r) +} + +func scardIsValidContext(ctx uintptr) Error { + r := C.SCardIsValidContext(C.SCARDCONTEXT(ctx)) + return Error(r) +} + +func scardCancel(ctx uintptr) Error { + r := C.SCardCancel(C.SCARDCONTEXT(ctx)) + return Error(r) +} + +func scardReleaseContext(ctx uintptr) Error { + r := C.SCardReleaseContext(C.SCARDCONTEXT(ctx)) + return Error(r) +} + +func scardListReaders(ctx uintptr, groups, buf unsafe.Pointer, bufLen uint32) (uint32, Error) { + dwBufLen := C.DWORD(bufLen) + r := C.SCardListReaders(C.SCARDCONTEXT(ctx), (C.LPCSTR)(groups), (C.LPSTR)(buf), &dwBufLen) + return uint32(dwBufLen), Error(r) +} + +func scardListReaderGroups(ctx uintptr, buf unsafe.Pointer, bufLen uint32) (uint32, Error) { + dwBufLen := C.DWORD(bufLen) + r := C.SCardListReaderGroups(C.SCARDCONTEXT(ctx), (C.LPSTR)(buf), &dwBufLen) + return uint32(dwBufLen), Error(r) +} + +func scardGetStatusChange(ctx uintptr, timeout uint32, states []scardReaderState) Error { + r := C.SCardGetStatusChange(C.SCARDCONTEXT(ctx), C.DWORD(timeout), (C.LPSCARD_READERSTATE)(unsafe.Pointer(&states[0])), C.DWORD(len(states))) + return Error(r) +} + +func scardConnect(ctx uintptr, reader unsafe.Pointer, shareMode ShareMode, proto Protocol) (uintptr, Protocol, Error) { + var handle C.SCARDHANDLE + var activeProto C.DWORD + + r := C.SCardConnect(C.SCARDCONTEXT(ctx), C.LPCSTR(reader), C.DWORD(shareMode), C.DWORD(proto), &handle, &activeProto) + + return uintptr(handle), Protocol(activeProto), Error(r) +} + +func scardDisconnect(card uintptr, d Disposition) Error { + r := C.SCardDisconnect(C.SCARDHANDLE(card), C.DWORD(d)) + return Error(r) +} + +func scardReconnect(card uintptr, mode ShareMode, proto Protocol, disp Disposition) (Protocol, Error) { + var activeProtocol C.DWORD + r := C.SCardReconnect(C.SCARDHANDLE(card), C.DWORD(mode), C.DWORD(proto), C.DWORD(disp), &activeProtocol) + return Protocol(activeProtocol), Error(r) +} + +func scardBeginTransaction(card uintptr) Error { + r := C.SCardBeginTransaction(C.SCARDHANDLE(card)) + return Error(r) +} + +func scardEndTransaction(card uintptr, disp Disposition) Error { + r := C.SCardEndTransaction(C.SCARDHANDLE(card), C.DWORD(disp)) + return Error(r) +} + +func scardCardStatus(card uintptr) (string, State, Protocol, []byte, Error) { + var readerBuf [C.MAX_READERNAME + 1]byte + var readerLen = C.DWORD(len(readerBuf)) + var state, proto C.DWORD + var atr [maxAtrSize]byte + var atrLen = C.DWORD(len(atr)) + + r := C.SCardStatus(C.SCARDHANDLE(card), (C.LPSTR)(unsafe.Pointer(&readerBuf[0])), &readerLen, &state, &proto, (*C.BYTE)(&atr[0]), &atrLen) + + return decodestr(readerBuf[:readerLen]), State(state), Protocol(proto), atr[:atrLen], Error(r) +} + +func scardTransmit(card uintptr, proto Protocol, cmd []byte, rsp []byte) (uint32, Error) { + var sendpci C.SCARD_IO_REQUEST + var recvpci C.SCARD_IO_REQUEST + var rspLen = C.DWORD(len(rsp)) + + switch proto { + case ProtocolT0, ProtocolT1: + sendpci.dwProtocol = C.ulong(proto) + default: + panic("unknown protocol") + } + sendpci.cbPciLength = C.sizeof_SCARD_IO_REQUEST + + r := C.SCardTransmit(C.SCARDHANDLE(card), &sendpci, (*C.BYTE)(&cmd[0]), C.DWORD(len(cmd)), &recvpci, (*C.BYTE)(&rsp[0]), &rspLen) + + return uint32(rspLen), Error(r) +} + +func scardControl(card uintptr, ioctl uint32, in, out []byte) (uint32, Error) { + var ptrIn C.LPCVOID + var outLen = C.DWORD(len(out)) + + if len(in) != 0 { + ptrIn = C.LPCVOID(unsafe.Pointer(&in[0])) + } + + r := C.SCardControl(C.SCARDHANDLE(card), C.DWORD(ioctl), ptrIn, C.DWORD(len(in)), (C.LPVOID)(unsafe.Pointer(&out[0])), C.DWORD(len(out)), &outLen) + return uint32(outLen), Error(r) +} + +func scardGetAttrib(card uintptr, id Attrib, buf []byte) (uint32, Error) { + var ptr C.LPBYTE + + if len(buf) != 0 { + ptr = C.LPBYTE(unsafe.Pointer(&buf[0])) + } + + bufLen := C.DWORD(len(buf)) + r := C.SCardGetAttrib(C.SCARDHANDLE(card), C.DWORD(id), ptr, &bufLen) + + return uint32(bufLen), Error(r) +} + +func scardSetAttrib(card uintptr, id Attrib, buf []byte) Error { + r := C.SCardSetAttrib(C.SCARDHANDLE(card), C.DWORD(id), (*C.BYTE)(unsafe.Pointer(&buf[0])), C.DWORD(len(buf))) + return Error(r) +} + +type strbuf []byte + +func encodestr(s string) (strbuf, error) { + buf := strbuf(s + "\x00") + return buf, nil +} + +func decodestr(buf strbuf) string { + if len(buf) == 0 { + return "" + } + + if buf[len(buf)-1] == 0 { + buf = buf[:len(buf)-1] + } + + return string(buf) +} + +type scardReaderState struct { + szReader uintptr + pvUserData uintptr + dwCurrentState uintptr + dwEventState uintptr + cbAtr uintptr + rgbAtr [33]byte +} + +var pinned = map[string]*strbuf{} + +func (rs *ReaderState) toSys() (scardReaderState, error) { + var sys scardReaderState + + creader, err := encodestr(rs.Reader) + if err != nil { + return scardReaderState{}, err + } + pinned[rs.Reader] = &creader + sys.szReader = uintptr(creader.ptr()) + sys.dwCurrentState = uintptr(rs.CurrentState) + sys.cbAtr = uintptr(len(rs.Atr)) + for i, v := range rs.Atr { + sys.rgbAtr[i] = byte(v) + } + return sys, nil +} + +func (rs *ReaderState) update(sys *scardReaderState) { + rs.EventState = StateFlag(sys.dwEventState) + if sys.cbAtr > 0 { + rs.Atr = make([]byte, int(sys.cbAtr)) + for i := 0; i < int(sys.cbAtr); i++ { + rs.Atr[i] = byte(sys.rgbAtr[i]) + } + } +} diff --git a/vendor/github.com/ebfe/scard/scard_windows.go b/vendor/github.com/ebfe/scard/scard_windows.go new file mode 100644 index 000000000..bc852da04 --- /dev/null +++ b/vendor/github.com/ebfe/scard/scard_windows.go @@ -0,0 +1,221 @@ +package scard + +import ( + "fmt" + "syscall" + "unsafe" +) + +var ( + modwinscard = syscall.NewLazyDLL("winscard.dll") + + procEstablishContext = modwinscard.NewProc("SCardEstablishContext") + procReleaseContext = modwinscard.NewProc("SCardReleaseContext") + procIsValidContext = modwinscard.NewProc("SCardIsValidContext") + procCancel = modwinscard.NewProc("SCardCancel") + procListReaders = modwinscard.NewProc("SCardListReadersW") + procListReaderGroups = modwinscard.NewProc("SCardListReaderGroupsW") + procGetStatusChange = modwinscard.NewProc("SCardGetStatusChangeW") + procConnect = modwinscard.NewProc("SCardConnectW") + procDisconnect = modwinscard.NewProc("SCardDisconnect") + procReconnect = modwinscard.NewProc("SCardReconnect") + procBeginTransaction = modwinscard.NewProc("SCardBeginTransaction") + procEndTransaction = modwinscard.NewProc("SCardEndTransaction") + procStatus = modwinscard.NewProc("SCardStatusW") + procTransmit = modwinscard.NewProc("SCardTransmit") + procControl = modwinscard.NewProc("SCardControl") + procGetAttrib = modwinscard.NewProc("SCardGetAttrib") + procSetAttrib = modwinscard.NewProc("SCardSetAttrib") + + dataT0Pci = modwinscard.NewProc("g_rgSCardT0Pci") + dataT1Pci = modwinscard.NewProc("g_rgSCardT1Pci") +) + +var scardIoReqT0 uintptr +var scardIoReqT1 uintptr + +func init() { + if err := dataT0Pci.Find(); err != nil { + panic(err) + } + scardIoReqT0 = dataT0Pci.Addr() + if err := dataT1Pci.Find(); err != nil { + panic(err) + } + scardIoReqT1 = dataT1Pci.Addr() +} + +func (e Error) Error() string { + err := syscall.Errno(e) + return fmt.Sprintf("scard: error(%x): %s", uintptr(e), err.Error()) +} + +func scardEstablishContext(scope Scope, reserved1, reserved2 uintptr) (uintptr, Error) { + var ctx uintptr + r, _, _ := procEstablishContext.Call(uintptr(scope), reserved1, reserved2, uintptr(unsafe.Pointer(&ctx))) + return ctx, Error(r) +} + +func scardIsValidContext(ctx uintptr) Error { + r, _, _ := procIsValidContext.Call(ctx) + return Error(r) +} + +func scardCancel(ctx uintptr) Error { + r, _, _ := procCancel.Call(ctx) + return Error(r) +} + +func scardReleaseContext(ctx uintptr) Error { + r, _, _ := procReleaseContext.Call(ctx) + return Error(r) +} + +func scardListReaders(ctx uintptr, groups, buf unsafe.Pointer, bufLen uint32) (uint32, Error) { + dwBufLen := uint32(bufLen) + r, _, _ := procListReaders.Call(ctx, uintptr(groups), uintptr(buf), uintptr(unsafe.Pointer(&dwBufLen))) + return dwBufLen, Error(r) +} + +func scardListReaderGroups(ctx uintptr, buf unsafe.Pointer, bufLen uint32) (uint32, Error) { + dwBufLen := uint32(bufLen) + r, _, _ := procListReaderGroups.Call(ctx, uintptr(buf), uintptr(unsafe.Pointer(&dwBufLen))) + return dwBufLen, Error(r) +} + +func scardGetStatusChange(ctx uintptr, timeout uint32, states []scardReaderState) Error { + r, _, _ := procGetStatusChange.Call(ctx, uintptr(timeout), uintptr(unsafe.Pointer(&states[0])), uintptr(len(states))) + return Error(r) +} + +func scardConnect(ctx uintptr, reader unsafe.Pointer, shareMode ShareMode, proto Protocol) (uintptr, Protocol, Error) { + var handle uintptr + var activeProto uint32 + + r, _, _ := procConnect.Call(ctx, uintptr(reader), uintptr(shareMode), uintptr(proto), uintptr(unsafe.Pointer(&handle)), uintptr(unsafe.Pointer(&activeProto))) + + return handle, Protocol(activeProto), Error(r) +} + +func scardDisconnect(card uintptr, d Disposition) Error { + r, _, _ := procDisconnect.Call(card, uintptr(d)) + return Error(r) +} + +func scardReconnect(card uintptr, mode ShareMode, proto Protocol, disp Disposition) (Protocol, Error) { + var activeProtocol uint32 + r, _, _ := procReconnect.Call(card, uintptr(mode), uintptr(proto), uintptr(disp), uintptr(unsafe.Pointer(&activeProtocol))) + return Protocol(activeProtocol), Error(r) +} + +func scardBeginTransaction(card uintptr) Error { + r, _, _ := procBeginTransaction.Call(card) + return Error(r) +} + +func scardEndTransaction(card uintptr, disp Disposition) Error { + r, _, _ := procEndTransaction.Call(card, uintptr(disp)) + return Error(r) +} + +func scardCardStatus(card uintptr) (string, State, Protocol, []byte, Error) { + var state, proto uint32 + var atr [maxAtrSize]byte + var atrLen = uint32(len(atr)) + + reader := make(strbuf, maxReadername+1) + readerLen := uint32(len(reader)) + + r, _, _ := procStatus.Call(card, uintptr(reader.ptr()), uintptr(unsafe.Pointer(&readerLen)), uintptr(unsafe.Pointer(&state)), uintptr(unsafe.Pointer(&proto)), uintptr(unsafe.Pointer(&atr[0])), uintptr(unsafe.Pointer(&atrLen))) + + return decodestr(reader[:readerLen]), State(state), Protocol(proto), atr[:atrLen], Error(r) +} + +func scardTransmit(card uintptr, proto Protocol, cmd []byte, rsp []byte) (uint32, Error) { + var sendpci uintptr + var rspLen = uint32(len(rsp)) + + switch proto { + case ProtocolT0: + sendpci = scardIoReqT0 + case ProtocolT1: + sendpci = scardIoReqT1 + default: + panic("unknown protocol") + } + + r, _, _ := procTransmit.Call(card, sendpci, uintptr(unsafe.Pointer(&cmd[0])), uintptr(len(cmd)), uintptr(0), uintptr(unsafe.Pointer(&rsp[0])), uintptr(unsafe.Pointer(&rspLen))) + + return rspLen, Error(r) +} + +func scardControl(card uintptr, ioctl uint32, in, out []byte) (uint32, Error) { + var ptrIn uintptr + var outLen = uint32(len(out)) + + if len(in) != 0 { + ptrIn = uintptr(unsafe.Pointer(&in[0])) + } + + r, _, _ := procControl.Call(card, uintptr(ioctl), ptrIn, uintptr(len(in)), uintptr(unsafe.Pointer(&out[0])), uintptr(len(out)), uintptr(unsafe.Pointer(&outLen))) + return outLen, Error(r) +} + +func scardGetAttrib(card uintptr, id Attrib, buf []byte) (uint32, Error) { + var ptr uintptr + + if len(buf) != 0 { + ptr = uintptr(unsafe.Pointer(&buf[0])) + } + + bufLen := uint32(len(buf)) + r, _, _ := procGetAttrib.Call(card, uintptr(id), ptr, uintptr(unsafe.Pointer(&bufLen))) + + return bufLen, Error(r) +} + +func scardSetAttrib(card uintptr, id Attrib, buf []byte) Error { + r, _, _ := procSetAttrib.Call(card, uintptr(id), uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf))) + return Error(r) +} + +type scardReaderState struct { + szReader uintptr + pvUserData uintptr + dwCurrentState uint32 + dwEventState uint32 + cbAtr uint32 + rgbAtr [36]byte +} + +func (rs *ReaderState) toSys() (scardReaderState, error) { + var sys scardReaderState + creader, err := encodestr(rs.Reader) + if err != nil { + return scardReaderState{}, err + } + sys.szReader = uintptr(creader.ptr()) + sys.dwCurrentState = uint32(rs.CurrentState) + sys.cbAtr = uint32(len(rs.Atr)) + copy(sys.rgbAtr[:], rs.Atr) + return sys, nil +} + +func (rs *ReaderState) update(sys *scardReaderState) { + rs.EventState = StateFlag(sys.dwEventState) + if sys.cbAtr > 0 { + rs.Atr = make([]byte, int(sys.cbAtr)) + copy(rs.Atr, sys.rgbAtr[:]) + } +} + +type strbuf []uint16 + +func encodestr(s string) (strbuf, error) { + utf16, err := syscall.UTF16FromString(s) + return strbuf(utf16), err +} + +func decodestr(buf strbuf) string { + return syscall.UTF16ToString(buf) +} diff --git a/vendor/github.com/ebfe/scard/zconst.go b/vendor/github.com/ebfe/scard/zconst.go new file mode 100644 index 000000000..675db21dc --- /dev/null +++ b/vendor/github.com/ebfe/scard/zconst.go @@ -0,0 +1,190 @@ +// Created by cgo -godefs - DO NOT EDIT +// cgo -godefs -- -I /usr/include/PCSC/ const.go + +package scard + +type Attrib uint32 + +const ( + AttrVendorName Attrib = 0x10100 + AttrVendorIfdType Attrib = 0x10101 + AttrVendorIfdVersion Attrib = 0x10102 + AttrVendorIfdSerialNo Attrib = 0x10103 + AttrChannelId Attrib = 0x20110 + AttrAsyncProtocolTypes Attrib = 0x30120 + AttrDefaultClk Attrib = 0x30121 + AttrMaxClk Attrib = 0x30122 + AttrDefaultDataRate Attrib = 0x30123 + AttrMaxDataRate Attrib = 0x30124 + AttrMaxIfsd Attrib = 0x30125 + AttrSyncProtocolTypes Attrib = 0x30126 + AttrPowerMgmtSupport Attrib = 0x40131 + AttrUserToCardAuthDevice Attrib = 0x50140 + AttrUserAuthInputDevice Attrib = 0x50142 + AttrCharacteristics Attrib = 0x60150 + AttrCurrentProtocolType Attrib = 0x80201 + AttrCurrentClk Attrib = 0x80202 + AttrCurrentF Attrib = 0x80203 + AttrCurrentD Attrib = 0x80204 + AttrCurrentN Attrib = 0x80205 + AttrCurrentW Attrib = 0x80206 + AttrCurrentIfsc Attrib = 0x80207 + AttrCurrentIfsd Attrib = 0x80208 + AttrCurrentBwt Attrib = 0x80209 + AttrCurrentCwt Attrib = 0x8020a + AttrCurrentEbcEncoding Attrib = 0x8020b + AttrExtendedBwt Attrib = 0x8020c + AttrIccPresence Attrib = 0x90300 + AttrIccInterfaceStatus Attrib = 0x90301 + AttrCurrentIoState Attrib = 0x90302 + AttrAtrString Attrib = 0x90303 + AttrIccTypePerAtr Attrib = 0x90304 + AttrEscReset Attrib = 0x7a000 + AttrEscCancel Attrib = 0x7a003 + AttrEscAuthrequest Attrib = 0x7a005 + AttrMaxinput Attrib = 0x7a007 + AttrDeviceUnit Attrib = 0x7fff0001 + AttrDeviceInUse Attrib = 0x7fff0002 + AttrDeviceFriendlyName Attrib = 0x7fff0003 + AttrDeviceSystemName Attrib = 0x7fff0004 + AttrSupressT1IfsRequest Attrib = 0x7fff0007 +) + +type Error uint32 + +const ( + ErrSuccess Error = 0x0 + ErrInternalError Error = 0x80100001 + ErrCancelled Error = 0x80100002 + ErrInvalidHandle Error = 0x80100003 + ErrInvalidParameter Error = 0x80100004 + ErrInvalidTarget Error = 0x80100005 + ErrNoMemory Error = 0x80100006 + ErrWaitedTooLong Error = 0x80100007 + ErrInsufficientBuffer Error = 0x80100008 + ErrUnknownReader Error = 0x80100009 + ErrTimeout Error = 0x8010000a + ErrSharingViolation Error = 0x8010000b + ErrNoSmartcard Error = 0x8010000c + ErrUnknownCard Error = 0x8010000d + ErrCantDispose Error = 0x8010000e + ErrProtoMismatch Error = 0x8010000f + ErrNotReady Error = 0x80100010 + ErrInvalidValue Error = 0x80100011 + ErrSystemCancelled Error = 0x80100012 + ErrCommError Error = 0x80100013 + ErrUnknownError Error = 0x80100014 + ErrInvalidAtr Error = 0x80100015 + ErrNotTransacted Error = 0x80100016 + ErrReaderUnavailable Error = 0x80100017 + ErrShutdown Error = 0x80100018 + ErrPciTooSmall Error = 0x80100019 + ErrReaderUnsupported Error = 0x8010001a + ErrDuplicateReader Error = 0x8010001b + ErrCardUnsupported Error = 0x8010001c + ErrNoService Error = 0x8010001d + ErrServiceStopped Error = 0x8010001e + ErrUnexpected Error = 0x8010001f + ErrUnsupportedFeature Error = 0x8010001f + ErrIccInstallation Error = 0x80100020 + ErrIccCreateorder Error = 0x80100021 + ErrFileNotFound Error = 0x80100024 + ErrNoDir Error = 0x80100025 + ErrNoFile Error = 0x80100026 + ErrNoAccess Error = 0x80100027 + ErrWriteTooMany Error = 0x80100028 + ErrBadSeek Error = 0x80100029 + ErrInvalidChv Error = 0x8010002a + ErrUnknownResMng Error = 0x8010002b + ErrNoSuchCertificate Error = 0x8010002c + ErrCertificateUnavailable Error = 0x8010002d + ErrNoReadersAvailable Error = 0x8010002e + ErrCommDataLost Error = 0x8010002f + ErrNoKeyContainer Error = 0x80100030 + ErrServerTooBusy Error = 0x80100031 + ErrUnsupportedCard Error = 0x80100065 + ErrUnresponsiveCard Error = 0x80100066 + ErrUnpoweredCard Error = 0x80100067 + ErrResetCard Error = 0x80100068 + ErrRemovedCard Error = 0x80100069 + ErrSecurityViolation Error = 0x8010006a + ErrWrongChv Error = 0x8010006b + ErrChvBlocked Error = 0x8010006c + ErrEof Error = 0x8010006d + ErrCancelledByUser Error = 0x8010006e + ErrCardNotAuthenticated Error = 0x8010006f +) + +type Protocol uint32 + +const ( + ProtocolUndefined Protocol = 0x0 + ProtocolT0 Protocol = 0x1 + ProtocolT1 Protocol = 0x2 + ProtocolAny Protocol = ProtocolT0 | ProtocolT1 +) + +type ShareMode uint32 + +const ( + ShareExclusive ShareMode = 0x1 + ShareShared ShareMode = 0x2 + ShareDirect ShareMode = 0x3 +) + +type Disposition uint32 + +const ( + LeaveCard Disposition = 0x0 + ResetCard Disposition = 0x1 + UnpowerCard Disposition = 0x2 + EjectCard Disposition = 0x3 +) + +type Scope uint32 + +const ( + ScopeUser Scope = 0x0 + ScopeTerminal Scope = 0x1 + ScopeSystem Scope = 0x2 +) + +type State uint32 + +const ( + Unknown State = 0x1 + Absent State = 0x2 + Present State = 0x4 + Swallowed State = 0x8 + Powered State = 0x10 + Negotiable State = 0x20 + Specific State = 0x40 +) + +type StateFlag uint32 + +const ( + StateUnaware StateFlag = 0x0 + StateIgnore StateFlag = 0x1 + StateChanged StateFlag = 0x2 + StateUnknown StateFlag = 0x4 + StateUnavailable StateFlag = 0x8 + StateEmpty StateFlag = 0x10 + StatePresent StateFlag = 0x20 + StateAtrmatch StateFlag = 0x40 + StateExclusive StateFlag = 0x80 + StateInuse StateFlag = 0x100 + StateMute StateFlag = 0x200 + StateUnpowered StateFlag = 0x400 +) + +const ( + maxBufferSize = 0x108 + maxBufferSizeExtended = 0x1000c + maxReadername = 0x80 + maxAtrSize = 0x21 +) + +const ( + infiniteTimeout = 0xffffffff +) diff --git a/vendor/github.com/tyler-smith/go-bip39/LICENSE b/vendor/github.com/tyler-smith/go-bip39/LICENSE new file mode 100644 index 000000000..44de214f0 --- /dev/null +++ b/vendor/github.com/tyler-smith/go-bip39/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Tyler Smith + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/tyler-smith/go-bip39/README.md b/vendor/github.com/tyler-smith/go-bip39/README.md new file mode 100644 index 000000000..11e62af1c --- /dev/null +++ b/vendor/github.com/tyler-smith/go-bip39/README.md @@ -0,0 +1,38 @@ +# go-bip39 + +A golang implementation of the BIP0039 spec for mnemonic seeds + + +## Credits + +English wordlist and test vectors are from the standard Python BIP0039 implementation +from the Trezor guys: [https://github.com/trezor/python-mnemonic](https://github.com/trezor/python-mnemonic) + +## Example + +```go +package main + +import ( + "github.com/tyler-smith/go-bip39" + "github.com/tyler-smith/go-bip32" + "fmt" +) + +func main(){ + // Generate a mnemonic for memorization or user-friendly seeds + entropy, _ := bip39.NewEntropy(256) + mnemonic, _ := bip39.NewMnemonic(entropy) + + // Generate a Bip32 HD wallet for the mnemonic and a user supplied password + seed := bip39.NewSeed(mnemonic, "Secret Passphrase") + + masterKey, _ := bip32.NewMasterKey(seed) + publicKey := masterKey.PublicKey() + + // Display mnemonic and keys + fmt.Println("Mnemonic: ", mnemonic) + fmt.Println("Master private key: ", masterKey) + fmt.Println("Master public key: ", publicKey) +} +``` diff --git a/vendor/github.com/tyler-smith/go-bip39/bip39.go b/vendor/github.com/tyler-smith/go-bip39/bip39.go new file mode 100644 index 000000000..4d281ba46 --- /dev/null +++ b/vendor/github.com/tyler-smith/go-bip39/bip39.go @@ -0,0 +1,249 @@ +package bip39 + +import ( + "crypto/rand" + "crypto/sha256" + "crypto/sha512" + "encoding/binary" + "errors" + "fmt" + "math/big" + "strings" + + "golang.org/x/crypto/pbkdf2" +) + +// Some bitwise operands for working with big.Ints +var ( + Last11BitsMask = big.NewInt(2047) + RightShift11BitsDivider = big.NewInt(2048) + BigOne = big.NewInt(1) + BigTwo = big.NewInt(2) +) + +// NewEntropy will create random entropy bytes +// so long as the requested size bitSize is an appropriate size. +func NewEntropy(bitSize int) ([]byte, error) { + err := validateEntropyBitSize(bitSize) + if err != nil { + return nil, err + } + + entropy := make([]byte, bitSize/8) + _, err = rand.Read(entropy) + return entropy, err +} + +// NewMnemonic will return a string consisting of the mnemonic words for +// the given entropy. +// If the provide entropy is invalid, an error will be returned. +func NewMnemonic(entropy []byte) (string, error) { + // Compute some lengths for convenience + entropyBitLength := len(entropy) * 8 + checksumBitLength := entropyBitLength / 32 + sentenceLength := (entropyBitLength + checksumBitLength) / 11 + + err := validateEntropyBitSize(entropyBitLength) + if err != nil { + return "", err + } + + // Add checksum to entropy + entropy = addChecksum(entropy) + + // Break entropy up into sentenceLength chunks of 11 bits + // For each word AND mask the rightmost 11 bits and find the word at that index + // Then bitshift entropy 11 bits right and repeat + // Add to the last empty slot so we can work with LSBs instead of MSB + + // Entropy as an int so we can bitmask without worrying about bytes slices + entropyInt := new(big.Int).SetBytes(entropy) + + // Slice to hold words in + words := make([]string, sentenceLength) + + // Throw away big int for AND masking + word := big.NewInt(0) + + for i := sentenceLength - 1; i >= 0; i-- { + // Get 11 right most bits and bitshift 11 to the right for next time + word.And(entropyInt, Last11BitsMask) + entropyInt.Div(entropyInt, RightShift11BitsDivider) + + // Get the bytes representing the 11 bits as a 2 byte slice + wordBytes := padByteSlice(word.Bytes(), 2) + + // Convert bytes to an index and add that word to the list + words[i] = WordList[binary.BigEndian.Uint16(wordBytes)] + } + + return strings.Join(words, " "), nil +} + +// MnemonicToByteArray takes a mnemonic string and turns it into a byte array +// suitable for creating another mnemonic. +// An error is returned if the mnemonic is invalid. +// FIXME +// This does not work for all values in +// the test vectors. Namely +// Vectors 0, 4, and 8. +// This is not really important because BIP39 doesnt really define a conversion +// from string to bytes. +func MnemonicToByteArray(mnemonic string) ([]byte, error) { + if IsMnemonicValid(mnemonic) == false { + return nil, fmt.Errorf("Invalid mnemonic") + } + mnemonicSlice := strings.Split(mnemonic, " ") + + bitSize := len(mnemonicSlice) * 11 + err := validateEntropyWithChecksumBitSize(bitSize) + if err != nil { + return nil, err + } + checksumSize := bitSize % 32 + + b := big.NewInt(0) + modulo := big.NewInt(2048) + for _, v := range mnemonicSlice { + index, found := ReverseWordMap[v] + if found == false { + return nil, fmt.Errorf("Word `%v` not found in reverse map", v) + } + add := big.NewInt(int64(index)) + b = b.Mul(b, modulo) + b = b.Add(b, add) + } + hex := b.Bytes() + checksumModulo := big.NewInt(0).Exp(big.NewInt(2), big.NewInt(int64(checksumSize)), nil) + entropy, _ := big.NewInt(0).DivMod(b, checksumModulo, big.NewInt(0)) + + entropyHex := entropy.Bytes() + + byteSize := bitSize/8 + 1 + if len(hex) != byteSize { + tmp := make([]byte, byteSize) + diff := byteSize - len(hex) + for i := 0; i < len(hex); i++ { + tmp[i+diff] = hex[i] + } + hex = tmp + } + + validationHex := addChecksum(entropyHex) + if len(validationHex) != byteSize { + tmp2 := make([]byte, byteSize) + diff2 := byteSize - len(validationHex) + for i := 0; i < len(validationHex); i++ { + tmp2[i+diff2] = validationHex[i] + } + validationHex = tmp2 + } + + if len(hex) != len(validationHex) { + panic("[]byte len mismatch - it shouldn't happen") + } + for i := range validationHex { + if hex[i] != validationHex[i] { + return nil, fmt.Errorf("Invalid byte at position %v", i) + } + } + return hex, nil +} + +// NewSeedWithErrorChecking creates a hashed seed output given the mnemonic string and a password. +// An error is returned if the mnemonic is not convertible to a byte array. +func NewSeedWithErrorChecking(mnemonic string, password string) ([]byte, error) { + _, err := MnemonicToByteArray(mnemonic) + if err != nil { + return nil, err + } + return NewSeed(mnemonic, password), nil +} + +// NewSeed creates a hashed seed output given a provided string and password. +// No checking is performed to validate that the string provided is a valid mnemonic. +func NewSeed(mnemonic string, password string) []byte { + return pbkdf2.Key([]byte(mnemonic), []byte("mnemonic"+password), 2048, 64, sha512.New) +} + +// Appends to data the first (len(data) / 32)bits of the result of sha256(data) +// Currently only supports data up to 32 bytes +func addChecksum(data []byte) []byte { + // Get first byte of sha256 + hasher := sha256.New() + hasher.Write(data) + hash := hasher.Sum(nil) + firstChecksumByte := hash[0] + + // len() is in bytes so we divide by 4 + checksumBitLength := uint(len(data) / 4) + + // For each bit of check sum we want we shift the data one the left + // and then set the (new) right most bit equal to checksum bit at that index + // staring from the left + dataBigInt := new(big.Int).SetBytes(data) + for i := uint(0); i < checksumBitLength; i++ { + // Bitshift 1 left + dataBigInt.Mul(dataBigInt, BigTwo) + + // Set rightmost bit if leftmost checksum bit is set + if uint8(firstChecksumByte&(1<<(7-i))) > 0 { + dataBigInt.Or(dataBigInt, BigOne) + } + } + + return dataBigInt.Bytes() +} + +func padByteSlice(slice []byte, length int) []byte { + newSlice := make([]byte, length-len(slice)) + return append(newSlice, slice...) +} + +func validateEntropyBitSize(bitSize int) error { + if (bitSize%32) != 0 || bitSize < 128 || bitSize > 256 { + return errors.New("Entropy length must be [128, 256] and a multiple of 32") + } + return nil +} + +func validateEntropyWithChecksumBitSize(bitSize int) error { + if (bitSize != 128+4) && (bitSize != 160+5) && (bitSize != 192+6) && (bitSize != 224+7) && (bitSize != 256+8) { + return fmt.Errorf("Wrong entropy + checksum size - expected %v, got %v", int((bitSize-bitSize%32)+(bitSize-bitSize%32)/32), bitSize) + } + return nil +} + +// IsMnemonicValid attempts to verify that the provided mnemonic is valid. +// Validity is determined by both the number of words being appropriate, +// and that all the words in the mnemonic are present in the word list. +func IsMnemonicValid(mnemonic string) bool { + // Create a list of all the words in the mnemonic sentence + words := strings.Fields(mnemonic) + + //Get num of words + numOfWords := len(words) + + // The number of words should be 12, 15, 18, 21 or 24 + if numOfWords%3 != 0 || numOfWords < 12 || numOfWords > 24 { + return false + } + + // Check if all words belong in the wordlist + for i := 0; i < numOfWords; i++ { + if !contains(WordList, words[i]) { + return false + } + } + + return true +} + +func contains(s []string, e string) bool { + for _, a := range s { + if a == e { + return true + } + } + return false +} diff --git a/vendor/github.com/tyler-smith/go-bip39/coverage.txt b/vendor/github.com/tyler-smith/go-bip39/coverage.txt new file mode 100644 index 000000000..7b1ce02f1 --- /dev/null +++ b/vendor/github.com/tyler-smith/go-bip39/coverage.txt @@ -0,0 +1,15 @@ + +github.com/CrowBits/go-bip39/bip39.go addChecksum 100.00% (11/11) +github.com/CrowBits/go-bip39/bip39.go IsMnemonicValid 100.00% (8/8) +github.com/CrowBits/go-bip39/bip39.go NewEntropy 100.00% (6/6) +github.com/CrowBits/go-bip39/bip39.go contains 100.00% (4/4) +github.com/CrowBits/go-bip39/bip39.go NewSeedWithErrorChecking 100.00% (4/4) +github.com/CrowBits/go-bip39/bip39.go validateEntropyBitSize 100.00% (3/3) +github.com/CrowBits/go-bip39/bip39.go validateEntropyWithChecksumBitSize 100.00% (3/3) +github.com/CrowBits/go-bip39/bip39.go padByteSlice 100.00% (2/2) +github.com/CrowBits/go-bip39/wordlist.go init 100.00% (2/2) +github.com/CrowBits/go-bip39/bip39.go NewSeed 100.00% (1/1) +github.com/CrowBits/go-bip39/bip39.go NewMnemonic 93.75% (15/16) +github.com/CrowBits/go-bip39/bip39.go MnemonicToByteArray 90.24% (37/41) +github.com/CrowBits/go-bip39 ---------------------------------- 95.05% (96/101) + diff --git a/vendor/github.com/tyler-smith/go-bip39/wordlist.go b/vendor/github.com/tyler-smith/go-bip39/wordlist.go new file mode 100644 index 000000000..aaed291c3 --- /dev/null +++ b/vendor/github.com/tyler-smith/go-bip39/wordlist.go @@ -0,0 +1,2067 @@ +package bip39 + +import ( + "strings" +) + +// The wordlist to use +var WordList = EnglishWordList + +var ReverseWordMap map[string]int = map[string]int{} + +func init() { + for i, v := range WordList { + ReverseWordMap[v] = i + } +} + +// Language-specific wordlists +var EnglishWordList = strings.Split(englishWordList, "\n") +var englishWordList = `abandon +ability +able +about +above +absent +absorb +abstract +absurd +abuse +access +accident +account +accuse +achieve +acid +acoustic +acquire +across +act +action +actor +actress +actual +adapt +add +addict +address +adjust +admit +adult +advance +advice +aerobic +affair +afford +afraid +again +age +agent +agree +ahead +aim +air +airport +aisle +alarm +album +alcohol +alert +alien +all +alley +allow +almost +alone +alpha +already +also +alter +always +amateur +amazing +among +amount +amused +analyst +anchor +ancient +anger +angle +angry +animal +ankle +announce +annual +another +answer +antenna +antique +anxiety +any +apart +apology +appear +apple +approve +april +arch +arctic +area +arena +argue +arm +armed +armor +army +around +arrange +arrest +arrive +arrow +art +artefact +artist +artwork +ask +aspect +assault +asset +assist +assume +asthma +athlete +atom +attack +attend +attitude +attract +auction +audit +august +aunt +author +auto +autumn +average +avocado +avoid +awake +aware +away +awesome +awful +awkward +axis +baby +bachelor +bacon +badge +bag +balance +balcony +ball +bamboo +banana +banner +bar +barely +bargain +barrel +base +basic +basket +battle +beach +bean +beauty +because +become +beef +before +begin +behave +behind +believe +below +belt +bench +benefit +best +betray +better +between +beyond +bicycle +bid +bike +bind +biology +bird +birth +bitter +black +blade +blame +blanket +blast +bleak +bless +blind +blood +blossom +blouse +blue +blur +blush +board +boat +body +boil +bomb +bone +bonus +book +boost +border +boring +borrow +boss +bottom +bounce +box +boy +bracket +brain +brand +brass +brave +bread +breeze +brick +bridge +brief +bright +bring +brisk +broccoli +broken +bronze +broom +brother +brown +brush +bubble +buddy +budget +buffalo +build +bulb +bulk +bullet +bundle +bunker +burden +burger +burst +bus +business +busy +butter +buyer +buzz +cabbage +cabin +cable +cactus +cage +cake +call +calm +camera +camp +can +canal +cancel +candy +cannon +canoe +canvas +canyon +capable +capital +captain +car +carbon +card +cargo +carpet +carry +cart +case +cash +casino +castle +casual +cat +catalog +catch +category +cattle +caught +cause +caution +cave +ceiling +celery +cement +census +century +cereal +certain +chair +chalk +champion +change +chaos +chapter +charge +chase +chat +cheap +check +cheese +chef +cherry +chest +chicken +chief +child +chimney +choice +choose +chronic +chuckle +chunk +churn +cigar +cinnamon +circle +citizen +city +civil +claim +clap +clarify +claw +clay +clean +clerk +clever +click +client +cliff +climb +clinic +clip +clock +clog +close +cloth +cloud +clown +club +clump +cluster +clutch +coach +coast +coconut +code +coffee +coil +coin +collect +color +column +combine +come +comfort +comic +common +company +concert +conduct +confirm +congress +connect +consider +control +convince +cook +cool +copper +copy +coral +core +corn +correct +cost +cotton +couch +country +couple +course +cousin +cover +coyote +crack +cradle +craft +cram +crane +crash +crater +crawl +crazy +cream +credit +creek +crew +cricket +crime +crisp +critic +crop +cross +crouch +crowd +crucial +cruel +cruise +crumble +crunch +crush +cry +crystal +cube +culture +cup +cupboard +curious +current +curtain +curve +cushion +custom +cute +cycle +dad +damage +damp +dance +danger +daring +dash +daughter +dawn +day +deal +debate +debris +decade +december +decide +decline +decorate +decrease +deer +defense +define +defy +degree +delay +deliver +demand +demise +denial +dentist +deny +depart +depend +deposit +depth +deputy +derive +describe +desert +design +desk +despair +destroy +detail +detect +develop +device +devote +diagram +dial +diamond +diary +dice +diesel +diet +differ +digital +dignity +dilemma +dinner +dinosaur +direct +dirt +disagree +discover +disease +dish +dismiss +disorder +display +distance +divert +divide +divorce +dizzy +doctor +document +dog +doll +dolphin +domain +donate +donkey +donor +door +dose +double +dove +draft +dragon +drama +drastic +draw +dream +dress +drift +drill +drink +drip +drive +drop +drum +dry +duck +dumb +dune +during +dust +dutch +duty +dwarf +dynamic +eager +eagle +early +earn +earth +easily +east +easy +echo +ecology +economy +edge +edit +educate +effort +egg +eight +either +elbow +elder +electric +elegant +element +elephant +elevator +elite +else +embark +embody +embrace +emerge +emotion +employ +empower +empty +enable +enact +end +endless +endorse +enemy +energy +enforce +engage +engine +enhance +enjoy +enlist +enough +enrich +enroll +ensure +enter +entire +entry +envelope +episode +equal +equip +era +erase +erode +erosion +error +erupt +escape +essay +essence +estate +eternal +ethics +evidence +evil +evoke +evolve +exact +example +excess +exchange +excite +exclude +excuse +execute +exercise +exhaust +exhibit +exile +exist +exit +exotic +expand +expect +expire +explain +expose +express +extend +extra +eye +eyebrow +fabric +face +faculty +fade +faint +faith +fall +false +fame +family +famous +fan +fancy +fantasy +farm +fashion +fat +fatal +father +fatigue +fault +favorite +feature +february +federal +fee +feed +feel +female +fence +festival +fetch +fever +few +fiber +fiction +field +figure +file +film +filter +final +find +fine +finger +finish +fire +firm +first +fiscal +fish +fit +fitness +fix +flag +flame +flash +flat +flavor +flee +flight +flip +float +flock +floor +flower +fluid +flush +fly +foam +focus +fog +foil +fold +follow +food +foot +force +forest +forget +fork +fortune +forum +forward +fossil +foster +found +fox +fragile +frame +frequent +fresh +friend +fringe +frog +front +frost +frown +frozen +fruit +fuel +fun +funny +furnace +fury +future +gadget +gain +galaxy +gallery +game +gap +garage +garbage +garden +garlic +garment +gas +gasp +gate +gather +gauge +gaze +general +genius +genre +gentle +genuine +gesture +ghost +giant +gift +giggle +ginger +giraffe +girl +give +glad +glance +glare +glass +glide +glimpse +globe +gloom +glory +glove +glow +glue +goat +goddess +gold +good +goose +gorilla +gospel +gossip +govern +gown +grab +grace +grain +grant +grape +grass +gravity +great +green +grid +grief +grit +grocery +group +grow +grunt +guard +guess +guide +guilt +guitar +gun +gym +habit +hair +half +hammer +hamster +hand +happy +harbor +hard +harsh +harvest +hat +have +hawk +hazard +head +health +heart +heavy +hedgehog +height +hello +helmet +help +hen +hero +hidden +high +hill +hint +hip +hire +history +hobby +hockey +hold +hole +holiday +hollow +home +honey +hood +hope +horn +horror +horse +hospital +host +hotel +hour +hover +hub +huge +human +humble +humor +hundred +hungry +hunt +hurdle +hurry +hurt +husband +hybrid +ice +icon +idea +identify +idle +ignore +ill +illegal +illness +image +imitate +immense +immune +impact +impose +improve +impulse +inch +include +income +increase +index +indicate +indoor +industry +infant +inflict +inform +inhale +inherit +initial +inject +injury +inmate +inner +innocent +input +inquiry +insane +insect +inside +inspire +install +intact +interest +into +invest +invite +involve +iron +island +isolate +issue +item +ivory +jacket +jaguar +jar +jazz +jealous +jeans +jelly +jewel +job +join +joke +journey +joy +judge +juice +jump +jungle +junior +junk +just +kangaroo +keen +keep +ketchup +key +kick +kid +kidney +kind +kingdom +kiss +kit +kitchen +kite +kitten +kiwi +knee +knife +knock +know +lab +label +labor +ladder +lady +lake +lamp +language +laptop +large +later +latin +laugh +laundry +lava +law +lawn +lawsuit +layer +lazy +leader +leaf +learn +leave +lecture +left +leg +legal +legend +leisure +lemon +lend +length +lens +leopard +lesson +letter +level +liar +liberty +library +license +life +lift +light +like +limb +limit +link +lion +liquid +list +little +live +lizard +load +loan +lobster +local +lock +logic +lonely +long +loop +lottery +loud +lounge +love +loyal +lucky +luggage +lumber +lunar +lunch +luxury +lyrics +machine +mad +magic +magnet +maid +mail +main +major +make +mammal +man +manage +mandate +mango +mansion +manual +maple +marble +march +margin +marine +market +marriage +mask +mass +master +match +material +math +matrix +matter +maximum +maze +meadow +mean +measure +meat +mechanic +medal +media +melody +melt +member +memory +mention +menu +mercy +merge +merit +merry +mesh +message +metal +method +middle +midnight +milk +million +mimic +mind +minimum +minor +minute +miracle +mirror +misery +miss +mistake +mix +mixed +mixture +mobile +model +modify +mom +moment +monitor +monkey +monster +month +moon +moral +more +morning +mosquito +mother +motion +motor +mountain +mouse +move +movie +much +muffin +mule +multiply +muscle +museum +mushroom +music +must +mutual +myself +mystery +myth +naive +name +napkin +narrow +nasty +nation +nature +near +neck +need +negative +neglect +neither +nephew +nerve +nest +net +network +neutral +never +news +next +nice +night +noble +noise +nominee +noodle +normal +north +nose +notable +note +nothing +notice +novel +now +nuclear +number +nurse +nut +oak +obey +object +oblige +obscure +observe +obtain +obvious +occur +ocean +october +odor +off +offer +office +often +oil +okay +old +olive +olympic +omit +once +one +onion +online +only +open +opera +opinion +oppose +option +orange +orbit +orchard +order +ordinary +organ +orient +original +orphan +ostrich +other +outdoor +outer +output +outside +oval +oven +over +own +owner +oxygen +oyster +ozone +pact +paddle +page +pair +palace +palm +panda +panel +panic +panther +paper +parade +parent +park +parrot +party +pass +patch +path +patient +patrol +pattern +pause +pave +payment +peace +peanut +pear +peasant +pelican +pen +penalty +pencil +people +pepper +perfect +permit +person +pet +phone +photo +phrase +physical +piano +picnic +picture +piece +pig +pigeon +pill +pilot +pink +pioneer +pipe +pistol +pitch +pizza +place +planet +plastic +plate +play +please +pledge +pluck +plug +plunge +poem +poet +point +polar +pole +police +pond +pony +pool +popular +portion +position +possible +post +potato +pottery +poverty +powder +power +practice +praise +predict +prefer +prepare +present +pretty +prevent +price +pride +primary +print +priority +prison +private +prize +problem +process +produce +profit +program +project +promote +proof +property +prosper +protect +proud +provide +public +pudding +pull +pulp +pulse +pumpkin +punch +pupil +puppy +purchase +purity +purpose +purse +push +put +puzzle +pyramid +quality +quantum +quarter +question +quick +quit +quiz +quote +rabbit +raccoon +race +rack +radar +radio +rail +rain +raise +rally +ramp +ranch +random +range +rapid +rare +rate +rather +raven +raw +razor +ready +real +reason +rebel +rebuild +recall +receive +recipe +record +recycle +reduce +reflect +reform +refuse +region +regret +regular +reject +relax +release +relief +rely +remain +remember +remind +remove +render +renew +rent +reopen +repair +repeat +replace +report +require +rescue +resemble +resist +resource +response +result +retire +retreat +return +reunion +reveal +review +reward +rhythm +rib +ribbon +rice +rich +ride +ridge +rifle +right +rigid +ring +riot +ripple +risk +ritual +rival +river +road +roast +robot +robust +rocket +romance +roof +rookie +room +rose +rotate +rough +round +route +royal +rubber +rude +rug +rule +run +runway +rural +sad +saddle +sadness +safe +sail +salad +salmon +salon +salt +salute +same +sample +sand +satisfy +satoshi +sauce +sausage +save +say +scale +scan +scare +scatter +scene +scheme +school +science +scissors +scorpion +scout +scrap +screen +script +scrub +sea +search +season +seat +second +secret +section +security +seed +seek +segment +select +sell +seminar +senior +sense +sentence +series +service +session +settle +setup +seven +shadow +shaft +shallow +share +shed +shell +sheriff +shield +shift +shine +ship +shiver +shock +shoe +shoot +shop +short +shoulder +shove +shrimp +shrug +shuffle +shy +sibling +sick +side +siege +sight +sign +silent +silk +silly +silver +similar +simple +since +sing +siren +sister +situate +six +size +skate +sketch +ski +skill +skin +skirt +skull +slab +slam +sleep +slender +slice +slide +slight +slim +slogan +slot +slow +slush +small +smart +smile +smoke +smooth +snack +snake +snap +sniff +snow +soap +soccer +social +sock +soda +soft +solar +soldier +solid +solution +solve +someone +song +soon +sorry +sort +soul +sound +soup +source +south +space +spare +spatial +spawn +speak +special +speed +spell +spend +sphere +spice +spider +spike +spin +spirit +split +spoil +sponsor +spoon +sport +spot +spray +spread +spring +spy +square +squeeze +squirrel +stable +stadium +staff +stage +stairs +stamp +stand +start +state +stay +steak +steel +stem +step +stereo +stick +still +sting +stock +stomach +stone +stool +story +stove +strategy +street +strike +strong +struggle +student +stuff +stumble +style +subject +submit +subway +success +such +sudden +suffer +sugar +suggest +suit +summer +sun +sunny +sunset +super +supply +supreme +sure +surface +surge +surprise +surround +survey +suspect +sustain +swallow +swamp +swap +swarm +swear +sweet +swift +swim +swing +switch +sword +symbol +symptom +syrup +system +table +tackle +tag +tail +talent +talk +tank +tape +target +task +taste +tattoo +taxi +teach +team +tell +ten +tenant +tennis +tent +term +test +text +thank +that +theme +then +theory +there +they +thing +this +thought +three +thrive +throw +thumb +thunder +ticket +tide +tiger +tilt +timber +time +tiny +tip +tired +tissue +title +toast +tobacco +today +toddler +toe +together +toilet +token +tomato +tomorrow +tone +tongue +tonight +tool +tooth +top +topic +topple +torch +tornado +tortoise +toss +total +tourist +toward +tower +town +toy +track +trade +traffic +tragic +train +transfer +trap +trash +travel +tray +treat +tree +trend +trial +tribe +trick +trigger +trim +trip +trophy +trouble +truck +true +truly +trumpet +trust +truth +try +tube +tuition +tumble +tuna +tunnel +turkey +turn +turtle +twelve +twenty +twice +twin +twist +two +type +typical +ugly +umbrella +unable +unaware +uncle +uncover +under +undo +unfair +unfold +unhappy +uniform +unique +unit +universe +unknown +unlock +until +unusual +unveil +update +upgrade +uphold +upon +upper +upset +urban +urge +usage +use +used +useful +useless +usual +utility +vacant +vacuum +vague +valid +valley +valve +van +vanish +vapor +various +vast +vault +vehicle +velvet +vendor +venture +venue +verb +verify +version +very +vessel +veteran +viable +vibrant +vicious +victory +video +view +village +vintage +violin +virtual +virus +visa +visit +visual +vital +vivid +vocal +voice +void +volcano +volume +vote +voyage +wage +wagon +wait +walk +wall +walnut +want +warfare +warm +warrior +wash +wasp +waste +water +wave +way +wealth +weapon +wear +weasel +weather +web +wedding +weekend +weird +welcome +west +wet +whale +what +wheat +wheel +when +where +whip +whisper +wide +width +wife +wild +will +win +window +wine +wing +wink +winner +winter +wire +wisdom +wise +wish +witness +wolf +woman +wonder +wood +wool +word +work +world +worry +worth +wrap +wreck +wrestle +wrist +write +wrong +yard +year +yellow +you +young +youth +zebra +zero +zone +zoo` diff --git a/vendor/vendor.json b/vendor/vendor.json index c08278c3b..f222d95dc 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -103,6 +103,12 @@ "revision": "8e610b2b55bfd1bfa9436ab110d311f5e8a74dcb", "revisionTime": "2018-06-25T18:44:42Z" }, + { + "checksumSHA1": "B1sVd5XOmjuSFYivGhBd+vPlc1w=", + "path": "github.com/ebfe/scard", + "revision": "0147d7ead790ac8a5ecc91ee6de68beb7b17c4e9", + "revisionTime": "2017-12-31T19:32:11Z" + }, { "checksumSHA1": "zYnPsNAVm1/ViwCkN++dX2JQhBo=", "path": "github.com/edsrzf/mmap-go", @@ -615,6 +621,18 @@ "revision": "a51202d6f4a7e5a219e3841a43614ff7187ae7f1", "revisionTime": "2018-06-15T20:27:29Z" }, + { + "checksumSHA1": "vW7IiPtoA4hQQ/ScHlbmRktY89U=", + "path": "github.com/tyler-smith/go-bip39", + "revision": "8e7a99b3e716f36d3b080a9a70f9eb45abe4edcc", + "revisionTime": "2016-06-29T16:38:56Z" + }, + { + "checksumSHA1": "GLCPuvePAkWT+opcWq3mNdhOfGM=", + "path": "github.com/wsddn/go-ecdh", + "revision": "48726bab92085232373de4ec5c51ce7b441c63a0", + "revisionTime": "2016-12-11T03:23:59Z" + }, { "checksumSHA1": "TT1rac6kpQp2vz24m5yDGUNQ/QQ=", "path": "golang.org/x/crypto/cast5",