accounts, console: frendly card errors, support pin unblock
This commit is contained in:
parent
386943943f
commit
7d5886dcf4
@ -21,29 +21,15 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
// commandAPDU represents an application data unit sent to a smartcard.
|
||||||
claISO7816 = 0
|
type commandAPDU struct {
|
||||||
|
|
||||||
insSelect = 0xA4
|
|
||||||
insGetResponse = 0xC0
|
|
||||||
insPair = 0x12
|
|
||||||
insUnpair = 0x13
|
|
||||||
insOpenSecureChannel = 0x10
|
|
||||||
insMutuallyAuthenticate = 0x11
|
|
||||||
|
|
||||||
sw1GetResponse = 0x61
|
|
||||||
sw1Ok = 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
|
Cla, Ins, P1, P2 uint8 // Class, Instruction, Parameter 1, Parameter 2
|
||||||
Data []byte // Command data
|
Data []byte // Command data
|
||||||
Le uint8 // Command data length
|
Le uint8 // Command data length
|
||||||
}
|
}
|
||||||
|
|
||||||
// serialize serializes a command APDU.
|
// serialize serializes a command APDU.
|
||||||
func (ca CommandAPDU) serialize() ([]byte, error) {
|
func (ca commandAPDU) serialize() ([]byte, error) {
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
|
|
||||||
if err := binary.Write(buf, binary.BigEndian, ca.Cla); err != nil {
|
if err := binary.Write(buf, binary.BigEndian, ca.Cla); err != nil {
|
||||||
@ -72,14 +58,14 @@ func (ca CommandAPDU) serialize() ([]byte, error) {
|
|||||||
return buf.Bytes(), nil
|
return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResponseAPDU represents an application data unit received from a smart card.
|
// responseAPDU represents an application data unit received from a smart card.
|
||||||
type ResponseAPDU struct {
|
type responseAPDU struct {
|
||||||
Data []byte // response data
|
Data []byte // response data
|
||||||
Sw1, Sw2 uint8 // status words 1 and 2
|
Sw1, Sw2 uint8 // status words 1 and 2
|
||||||
}
|
}
|
||||||
|
|
||||||
// deserialize deserializes a response APDU.
|
// deserialize deserializes a response APDU.
|
||||||
func (ra *ResponseAPDU) deserialize(data []byte) error {
|
func (ra *responseAPDU) deserialize(data []byte) error {
|
||||||
ra.Data = make([]byte, len(data)-2)
|
ra.Data = make([]byte, len(data)-2)
|
||||||
|
|
||||||
buf := bytes.NewReader(data)
|
buf := bytes.NewReader(data)
|
||||||
|
@ -36,6 +36,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -51,7 +52,7 @@ const Scheme = "pcsc"
|
|||||||
|
|
||||||
// refreshCycle is the maximum time between wallet refreshes (if USB hotplug
|
// refreshCycle is the maximum time between wallet refreshes (if USB hotplug
|
||||||
// notifications don't work).
|
// notifications don't work).
|
||||||
const refreshCycle = 5 * time.Second
|
const refreshCycle = time.Second
|
||||||
|
|
||||||
// refreshThrottling is the minimum time between wallet refreshes to avoid thrashing.
|
// refreshThrottling is the minimum time between wallet refreshes to avoid thrashing.
|
||||||
const refreshThrottling = 500 * time.Millisecond
|
const refreshThrottling = 500 * time.Millisecond
|
||||||
@ -132,7 +133,7 @@ func (hub *Hub) writePairings() error {
|
|||||||
return pairingFile.Close()
|
return pairingFile.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hub *Hub) getPairing(wallet *Wallet) *smartcardPairing {
|
func (hub *Hub) pairing(wallet *Wallet) *smartcardPairing {
|
||||||
pairing, ok := hub.pairings[string(wallet.PublicKey)]
|
pairing, ok := hub.pairings[string(wallet.PublicKey)]
|
||||||
if ok {
|
if ok {
|
||||||
return &pairing
|
return &pairing
|
||||||
@ -182,13 +183,7 @@ func (hub *Hub) Wallets() []accounts.Wallet {
|
|||||||
for _, wallet := range hub.wallets {
|
for _, wallet := range hub.wallets {
|
||||||
cpy = append(cpy, wallet)
|
cpy = append(cpy, wallet)
|
||||||
}
|
}
|
||||||
for i := 0; i < len(cpy); i++ {
|
sort.Sort(accounts.WalletsByURL(cpy))
|
||||||
for j := i + 1; j < len(cpy); j++ {
|
|
||||||
if cpy[i].URL().Cmp(cpy[j].URL()) > 0 {
|
|
||||||
cpy[i], cpy[j] = cpy[j], cpy[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cpy
|
return cpy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
package scwallet
|
package scwallet
|
||||||
|
|
||||||
import (
|
import (
|
||||||
//"crypto/ecdsa"
|
|
||||||
"bytes"
|
"bytes"
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
@ -25,10 +24,10 @@ import (
|
|||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/sha512"
|
"crypto/sha512"
|
||||||
"fmt"
|
"fmt"
|
||||||
//"math/big"
|
|
||||||
"github.com/ebfe/scard"
|
"github.com/ebfe/scard"
|
||||||
"github.com/ethereum/go-ethereum/crypto"
|
"github.com/ethereum/go-ethereum/crypto"
|
||||||
ecdh "github.com/wsddn/go-ecdh"
|
"github.com/wsddn/go-ecdh"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -38,6 +37,11 @@ const (
|
|||||||
|
|
||||||
scSecretLength = 32
|
scSecretLength = 32
|
||||||
scBlockSize = 16
|
scBlockSize = 16
|
||||||
|
|
||||||
|
insOpenSecureChannel = 0x10
|
||||||
|
insMutuallyAuthenticate = 0x11
|
||||||
|
insPair = 0x12
|
||||||
|
insUnpair = 0x13
|
||||||
)
|
)
|
||||||
|
|
||||||
// SecureChannelSession enables secure communication with a hardware wallet.
|
// SecureChannelSession enables secure communication with a hardware wallet.
|
||||||
@ -192,8 +196,8 @@ func (s *SecureChannelSession) mutuallyAuthenticate() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// open is an internal method that sends an open APDU.
|
// open is an internal method that sends an open APDU.
|
||||||
func (s *SecureChannelSession) open() (*ResponseAPDU, error) {
|
func (s *SecureChannelSession) open() (*responseAPDU, error) {
|
||||||
return transmit(s.card, &CommandAPDU{
|
return transmit(s.card, &commandAPDU{
|
||||||
Cla: claSCWallet,
|
Cla: claSCWallet,
|
||||||
Ins: insOpenSecureChannel,
|
Ins: insOpenSecureChannel,
|
||||||
P1: s.PairingIndex,
|
P1: s.PairingIndex,
|
||||||
@ -204,8 +208,8 @@ func (s *SecureChannelSession) open() (*ResponseAPDU, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// pair is an internal method that sends a pair APDU.
|
// pair is an internal method that sends a pair APDU.
|
||||||
func (s *SecureChannelSession) pair(p1 uint8, data []byte) (*ResponseAPDU, error) {
|
func (s *SecureChannelSession) pair(p1 uint8, data []byte) (*responseAPDU, error) {
|
||||||
return transmit(s.card, &CommandAPDU{
|
return transmit(s.card, &commandAPDU{
|
||||||
Cla: claSCWallet,
|
Cla: claSCWallet,
|
||||||
Ins: insPair,
|
Ins: insPair,
|
||||||
P1: p1,
|
P1: p1,
|
||||||
@ -216,7 +220,7 @@ func (s *SecureChannelSession) pair(p1 uint8, data []byte) (*ResponseAPDU, error
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TransmitEncrypted sends an encrypted message, and decrypts and returns the response.
|
// TransmitEncrypted sends an encrypted message, and decrypts and returns the response.
|
||||||
func (s *SecureChannelSession) TransmitEncrypted(cla, ins, p1, p2 byte, data []byte) (*ResponseAPDU, error) {
|
func (s *SecureChannelSession) TransmitEncrypted(cla, ins, p1, p2 byte, data []byte) (*responseAPDU, error) {
|
||||||
if s.iv == nil {
|
if s.iv == nil {
|
||||||
return nil, fmt.Errorf("Channel not open")
|
return nil, fmt.Errorf("Channel not open")
|
||||||
}
|
}
|
||||||
@ -234,7 +238,7 @@ func (s *SecureChannelSession) TransmitEncrypted(cla, ins, p1, p2 byte, data []b
|
|||||||
copy(fulldata, s.iv)
|
copy(fulldata, s.iv)
|
||||||
copy(fulldata[len(s.iv):], data)
|
copy(fulldata[len(s.iv):], data)
|
||||||
|
|
||||||
response, err := transmit(s.card, &CommandAPDU{
|
response, err := transmit(s.card, &commandAPDU{
|
||||||
Cla: cla,
|
Cla: cla,
|
||||||
Ins: ins,
|
Ins: ins,
|
||||||
P1: p1,
|
P1: p1,
|
||||||
@ -260,7 +264,7 @@ func (s *SecureChannelSession) TransmitEncrypted(cla, ins, p1, p2 byte, data []b
|
|||||||
return nil, fmt.Errorf("Invalid MAC in response")
|
return nil, fmt.Errorf("Invalid MAC in response")
|
||||||
}
|
}
|
||||||
|
|
||||||
rapdu := &ResponseAPDU{}
|
rapdu := &responseAPDU{}
|
||||||
rapdu.deserialize(plainData)
|
rapdu.deserialize(plainData)
|
||||||
|
|
||||||
if rapdu.Sw1 != sw1Ok {
|
if rapdu.Sw1 != sw1Ok {
|
||||||
|
@ -27,6 +27,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -51,6 +52,12 @@ var ErrPUKNeeded = errors.New("smartcard: puk needed")
|
|||||||
// and send it back.
|
// and send it back.
|
||||||
var ErrPINNeeded = errors.New("smartcard: pin needed")
|
var ErrPINNeeded = errors.New("smartcard: pin needed")
|
||||||
|
|
||||||
|
// ErrPINUnblockNeeded is returned if opening the smart card requires a PIN code,
|
||||||
|
// but all PIN attempts have already been exhausted. In this case the calling
|
||||||
|
// application should request user input for the PUK and a new PIN code to set
|
||||||
|
// fo the card.
|
||||||
|
var ErrPINUnblockNeeded = errors.New("smartcard: pin unblock needed")
|
||||||
|
|
||||||
// ErrAlreadyOpen is returned if the smart card is attempted to be opened, but
|
// ErrAlreadyOpen is returned if the smart card is attempted to be opened, but
|
||||||
// there is already a paired and unlocked session.
|
// there is already a paired and unlocked session.
|
||||||
var ErrAlreadyOpen = errors.New("smartcard: already open")
|
var ErrAlreadyOpen = errors.New("smartcard: already open")
|
||||||
@ -65,8 +72,16 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
claISO7816 = 0
|
||||||
claSCWallet = 0x80
|
claSCWallet = 0x80
|
||||||
|
|
||||||
|
insSelect = 0xA4
|
||||||
|
insGetResponse = 0xC0
|
||||||
|
sw1GetResponse = 0x61
|
||||||
|
sw1Ok = 0x90
|
||||||
|
|
||||||
insVerifyPin = 0x20
|
insVerifyPin = 0x20
|
||||||
|
insUnblockPin = 0x22
|
||||||
insExportKey = 0xC2
|
insExportKey = 0xC2
|
||||||
insSign = 0xC0
|
insSign = 0xC0
|
||||||
insLoadKey = 0xD0
|
insLoadKey = 0xD0
|
||||||
@ -117,7 +132,7 @@ func NewWallet(hub *Hub, card *scard.Card) *Wallet {
|
|||||||
// transmit sends an APDU to the smartcard and receives and decodes the response.
|
// 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,
|
// 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.
|
// and returns an error if the response status code is not success.
|
||||||
func transmit(card *scard.Card, command *CommandAPDU) (*ResponseAPDU, error) {
|
func transmit(card *scard.Card, command *commandAPDU) (*responseAPDU, error) {
|
||||||
data, err := command.serialize()
|
data, err := command.serialize()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -128,14 +143,14 @@ func transmit(card *scard.Card, command *CommandAPDU) (*ResponseAPDU, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
response := new(ResponseAPDU)
|
response := new(responseAPDU)
|
||||||
if err = response.deserialize(responseData); err != nil {
|
if err = response.deserialize(responseData); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Are we being asked to fetch the response separately?
|
// Are we being asked to fetch the response separately?
|
||||||
if response.Sw1 == sw1GetResponse && (command.Cla != claISO7816 || command.Ins != insGetResponse) {
|
if response.Sw1 == sw1GetResponse && (command.Cla != claISO7816 || command.Ins != insGetResponse) {
|
||||||
return transmit(card, &CommandAPDU{
|
return transmit(card, &commandAPDU{
|
||||||
Cla: claISO7816,
|
Cla: claISO7816,
|
||||||
Ins: insGetResponse,
|
Ins: insGetResponse,
|
||||||
P1: 0,
|
P1: 0,
|
||||||
@ -186,7 +201,7 @@ func (w *Wallet) connect() error {
|
|||||||
|
|
||||||
// doselect is an internal (unlocked) function to send a SELECT APDU to the card.
|
// doselect is an internal (unlocked) function to send a SELECT APDU to the card.
|
||||||
func (w *Wallet) doselect() (*applicationInfo, error) {
|
func (w *Wallet) doselect() (*applicationInfo, error) {
|
||||||
response, err := transmit(w.card, &CommandAPDU{
|
response, err := transmit(w.card, &commandAPDU{
|
||||||
Cla: claISO7816,
|
Cla: claISO7816,
|
||||||
Ins: insSelect,
|
Ins: insSelect,
|
||||||
P1: 4,
|
P1: 4,
|
||||||
@ -209,13 +224,11 @@ func (w *Wallet) ping() error {
|
|||||||
w.lock.Lock()
|
w.lock.Lock()
|
||||||
defer w.lock.Unlock()
|
defer w.lock.Unlock()
|
||||||
|
|
||||||
if !w.session.paired() {
|
|
||||||
// We can't ping if not paired
|
// We can't ping if not paired
|
||||||
|
if !w.session.paired() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if _, err := w.session.walletStatus(); err != nil {
|
||||||
_, err := w.session.getWalletStatus()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -235,16 +248,13 @@ func (w *Wallet) pair(puk []byte) error {
|
|||||||
if w.session.paired() {
|
if w.session.paired() {
|
||||||
return fmt.Errorf("Wallet already paired")
|
return fmt.Errorf("Wallet already paired")
|
||||||
}
|
}
|
||||||
|
|
||||||
pairing, err := w.session.pair(puk)
|
pairing, err := w.session.pair(puk)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = w.Hub.setPairing(w, &pairing); err != nil {
|
if err = w.Hub.setPairing(w, &pairing); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return w.session.authenticate(pairing)
|
return w.session.authenticate(pairing)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,19 +295,26 @@ func (w *Wallet) Status() (string, error) {
|
|||||||
w.lock.Lock()
|
w.lock.Lock()
|
||||||
defer w.lock.Unlock()
|
defer w.lock.Unlock()
|
||||||
|
|
||||||
|
// If the card is not paired, we can only wait
|
||||||
if !w.session.paired() {
|
if !w.session.paired() {
|
||||||
return "Unpaired", nil
|
return "Unpaired, waiting for PUK", nil
|
||||||
}
|
}
|
||||||
|
// Yay, we have an encrypted session, retrieve the actual status
|
||||||
status, err := w.session.getWalletStatus()
|
status, err := w.session.walletStatus()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "Error", err
|
return fmt.Sprintf("Failed: %v", err), err
|
||||||
}
|
}
|
||||||
|
switch {
|
||||||
if w.session.verified {
|
case !w.session.verified && status.PinRetryCount == 0:
|
||||||
return fmt.Sprintf("Open, %s", status), nil
|
return fmt.Sprintf("Blocked, waiting for PUK and new PIN"), nil
|
||||||
} else {
|
case !w.session.verified:
|
||||||
return fmt.Sprintf("Locked, %s", status), nil
|
return fmt.Sprintf("Locked, waiting for PIN (%d attempts left)", status.PinRetryCount), nil
|
||||||
|
case !status.Initialized:
|
||||||
|
return fmt.Sprintf("Empty, waiting for initialization"), nil
|
||||||
|
case status.SupportsPKDerivation:
|
||||||
|
return fmt.Sprintf("Online, can derive public keys"), nil
|
||||||
|
default:
|
||||||
|
return fmt.Sprintf("Online, cannot derive public keys"), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,12 +341,12 @@ func (w *Wallet) Open(passphrase string) error {
|
|||||||
// pairing key or form the supplied PUK code.
|
// pairing key or form the supplied PUK code.
|
||||||
if !w.session.paired() {
|
if !w.session.paired() {
|
||||||
// If a previous pairing exists, only ever try to use that
|
// If a previous pairing exists, only ever try to use that
|
||||||
if pairing := w.Hub.getPairing(w); pairing != nil {
|
if pairing := w.Hub.pairing(w); pairing != nil {
|
||||||
if err := w.session.authenticate(*pairing); err != nil {
|
if err := w.session.authenticate(*pairing); err != nil {
|
||||||
return fmt.Errorf("failed to authenticate card %x: %s", w.PublicKey[:4], err)
|
return fmt.Errorf("failed to authenticate card %x: %s", w.PublicKey[:4], err)
|
||||||
}
|
}
|
||||||
return ErrPINNeeded
|
// Pairing still ok, fall through to PIN checks
|
||||||
}
|
} else {
|
||||||
// If no passphrase was supplied, request the PUK from the user
|
// If no passphrase was supplied, request the PUK from the user
|
||||||
if passphrase == "" {
|
if passphrase == "" {
|
||||||
return ErrPUKNeeded
|
return ErrPUKNeeded
|
||||||
@ -338,16 +355,33 @@ func (w *Wallet) Open(passphrase string) error {
|
|||||||
if err := w.pair([]byte(passphrase)); err != nil {
|
if err := w.pair([]byte(passphrase)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return ErrPINNeeded // We always need the PIN after the PUK
|
// Pairing succeeded, fall through to PIN checks. This will of course fail,
|
||||||
|
// but we can't return ErrPINNeeded directly here becase we don't know whether
|
||||||
|
// a PIN check or a PIN reset is needed.
|
||||||
|
passphrase = ""
|
||||||
}
|
}
|
||||||
// The smart card was successfully paired, request a PIN code or use the one
|
}
|
||||||
// supplied by the user
|
// The smart card was successfully paired, retrieve its status to check whether
|
||||||
if passphrase == "" {
|
// PIN verification or unblocking is needed.
|
||||||
|
status, err := w.session.walletStatus()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Request the appropriate next authentication data, or use the one supplied
|
||||||
|
switch {
|
||||||
|
case passphrase == "" && status.PinRetryCount > 0:
|
||||||
return ErrPINNeeded
|
return ErrPINNeeded
|
||||||
}
|
case passphrase == "":
|
||||||
|
return ErrPINUnblockNeeded
|
||||||
|
case status.PinRetryCount > 0:
|
||||||
if err := w.session.verifyPin([]byte(passphrase)); err != nil {
|
if err := w.session.verifyPin([]byte(passphrase)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
if err := w.session.unblockPin([]byte(passphrase)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
// Smart card paired and unlocked, initialize and register
|
// Smart card paired and unlocked, initialize and register
|
||||||
w.deriveReq = make(chan chan struct{})
|
w.deriveReq = make(chan chan struct{})
|
||||||
w.deriveQuit = make(chan chan error)
|
w.deriveQuit = make(chan chan error)
|
||||||
@ -415,7 +449,7 @@ func (w *Wallet) selfDerive() {
|
|||||||
reqc <- struct{}{}
|
reqc <- struct{}{}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
pairing := w.Hub.getPairing(w)
|
pairing := w.Hub.pairing(w)
|
||||||
|
|
||||||
// Device lock obtained, derive the next batch of accounts
|
// Device lock obtained, derive the next batch of accounts
|
||||||
var (
|
var (
|
||||||
@ -519,18 +553,12 @@ func (w *Wallet) Accounts() []accounts.Account {
|
|||||||
w.lock.Lock()
|
w.lock.Lock()
|
||||||
defer w.lock.Unlock()
|
defer w.lock.Unlock()
|
||||||
|
|
||||||
if pairing := w.Hub.getPairing(w); pairing != nil {
|
if pairing := w.Hub.pairing(w); pairing != nil {
|
||||||
ret := make([]accounts.Account, 0, len(pairing.Accounts))
|
ret := make([]accounts.Account, 0, len(pairing.Accounts))
|
||||||
for address, path := range pairing.Accounts {
|
for address, path := range pairing.Accounts {
|
||||||
ret = append(ret, w.makeAccount(address, path))
|
ret = append(ret, w.makeAccount(address, path))
|
||||||
}
|
}
|
||||||
for i := 0; i < len(ret); i++ {
|
sort.Sort(accounts.AccountsByURL(ret))
|
||||||
for j := i + 1; j < len(ret); j++ {
|
|
||||||
if ret[i].URL.Cmp(ret[j].URL) > 0 {
|
|
||||||
ret[i], ret[j] = ret[j], ret[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -548,7 +576,7 @@ func (w *Wallet) makeAccount(address common.Address, path accounts.DerivationPat
|
|||||||
|
|
||||||
// Contains returns whether an account is part of this particular wallet or not.
|
// Contains returns whether an account is part of this particular wallet or not.
|
||||||
func (w *Wallet) Contains(account accounts.Account) bool {
|
func (w *Wallet) Contains(account accounts.Account) bool {
|
||||||
if pairing := w.Hub.getPairing(w); pairing != nil {
|
if pairing := w.Hub.pairing(w); pairing != nil {
|
||||||
_, ok := pairing.Accounts[account.Address]
|
_, ok := pairing.Accounts[account.Address]
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
@ -576,7 +604,7 @@ func (w *Wallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Accoun
|
|||||||
}
|
}
|
||||||
|
|
||||||
if pin {
|
if pin {
|
||||||
pairing := w.Hub.getPairing(w)
|
pairing := w.Hub.pairing(w)
|
||||||
pairing.Accounts[account.Address] = path
|
pairing.Accounts[account.Address] = path
|
||||||
if err := w.Hub.setPairing(w, pairing); err != nil {
|
if err := w.Hub.setPairing(w, pairing); err != nil {
|
||||||
return accounts.Account{}, err
|
return accounts.Account{}, err
|
||||||
@ -684,7 +712,7 @@ func (w *Wallet) SignTxWithPassphrase(account accounts.Account, passphrase strin
|
|||||||
// It first checks for the address in the list of pinned accounts, and if it is
|
// 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.
|
// not found, attempts to parse the derivation path from the account's URL.
|
||||||
func (w *Wallet) findAccountPath(account accounts.Account) (accounts.DerivationPath, error) {
|
func (w *Wallet) findAccountPath(account accounts.Account) (accounts.DerivationPath, error) {
|
||||||
pairing := w.Hub.getPairing(w)
|
pairing := w.Hub.pairing(w)
|
||||||
if path, ok := pairing.Accounts[account.Address]; ok {
|
if path, ok := pairing.Accounts[account.Address]; ok {
|
||||||
return path, nil
|
return path, nil
|
||||||
}
|
}
|
||||||
@ -745,6 +773,16 @@ func (s *Session) verifyPin(pin []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// unblockPin unblocks a wallet with the provided puk and resets the pin to the
|
||||||
|
// new one specified.
|
||||||
|
func (s *Session) unblockPin(pukpin []byte) error {
|
||||||
|
if _, err := s.Channel.TransmitEncrypted(claSCWallet, insUnblockPin, 0, 0, pukpin); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.verified = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// release releases resources associated with the channel.
|
// release releases resources associated with the channel.
|
||||||
func (s *Session) release() error {
|
func (s *Session) release() error {
|
||||||
return s.Wallet.card.Disconnect(scard.LeaveCard)
|
return s.Wallet.card.Disconnect(scard.LeaveCard)
|
||||||
@ -773,32 +811,25 @@ type walletStatus struct {
|
|||||||
SupportsPKDerivation bool // Whether the card supports doing public key derivation itself
|
SupportsPKDerivation bool // Whether the card supports doing public key derivation itself
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w walletStatus) String() string {
|
// walletStatus fetches the wallet's status from the card.
|
||||||
return fmt.Sprintf("pinRetryCount=%d, pukRetryCount=%d, initialized=%t, supportsPkDerivation=%t", w.PinRetryCount, w.PukRetryCount, w.Initialized, w.SupportsPKDerivation)
|
func (s *Session) walletStatus() (*walletStatus, error) {
|
||||||
}
|
|
||||||
|
|
||||||
// getWalletStatus fetches the wallet's status from the card.
|
|
||||||
func (s *Session) getWalletStatus() (*walletStatus, error) {
|
|
||||||
response, err := s.Channel.TransmitEncrypted(claSCWallet, insStatus, statusP1WalletStatus, 0, nil)
|
response, err := s.Channel.TransmitEncrypted(claSCWallet, insStatus, statusP1WalletStatus, 0, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
status := new(walletStatus)
|
status := new(walletStatus)
|
||||||
if _, err := asn1.UnmarshalWithParams(response.Data, status, "tag:3"); err != nil {
|
if _, err := asn1.UnmarshalWithParams(response.Data, status, "tag:3"); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return status, nil
|
return status, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getDerivationPath fetches the wallet's current derivation path from the card.
|
// derivationPath fetches the wallet's current derivation path from the card.
|
||||||
func (s *Session) getDerivationPath() (accounts.DerivationPath, error) {
|
func (s *Session) derivationPath() (accounts.DerivationPath, error) {
|
||||||
response, err := s.Channel.TransmitEncrypted(claSCWallet, insStatus, statusP1Path, 0, nil)
|
response, err := s.Channel.TransmitEncrypted(claSCWallet, insStatus, statusP1Path, 0, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := bytes.NewReader(response.Data)
|
buf := bytes.NewReader(response.Data)
|
||||||
path := make(accounts.DerivationPath, len(response.Data)/4)
|
path := make(accounts.DerivationPath, len(response.Data)/4)
|
||||||
return path, binary.Read(buf, binary.BigEndian, &path)
|
return path, binary.Read(buf, binary.BigEndian, &path)
|
||||||
@ -845,11 +876,11 @@ func (s *Session) derive(path accounts.DerivationPath) (accounts.Account, error)
|
|||||||
// start again.
|
// start again.
|
||||||
remainingPath := path
|
remainingPath := path
|
||||||
|
|
||||||
pubkey, err := s.getPublicKey()
|
pubkey, err := s.publicKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return accounts.Account{}, err
|
return accounts.Account{}, err
|
||||||
}
|
}
|
||||||
currentPath, err := s.getDerivationPath()
|
currentPath, err := s.derivationPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return accounts.Account{}, err
|
return accounts.Account{}, err
|
||||||
}
|
}
|
||||||
@ -910,7 +941,6 @@ func (s *Session) deriveKeyAssisted(reset bool, pathComponent uint32) ([]byte, e
|
|||||||
if _, err := asn1.UnmarshalWithParams(response.Data, keyinfo, "tag:2"); err != nil {
|
if _, err := asn1.UnmarshalWithParams(response.Data, keyinfo, "tag:2"); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
rbytes, sbytes := keyinfo.Signature.R.Bytes(), keyinfo.Signature.S.Bytes()
|
rbytes, sbytes := keyinfo.Signature.R.Bytes(), keyinfo.Signature.S.Bytes()
|
||||||
sig := make([]byte, 65)
|
sig := make([]byte, 65)
|
||||||
copy(sig[32-len(rbytes):32], rbytes)
|
copy(sig[32-len(rbytes):32], rbytes)
|
||||||
@ -920,12 +950,10 @@ func (s *Session) deriveKeyAssisted(reset bool, pathComponent uint32) ([]byte, e
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = s.Channel.TransmitEncrypted(claSCWallet, insDeriveKey, deriveP1Assisted|deriveP1Append, deriveP2PublicKey, pubkey)
|
_, err = s.Channel.TransmitEncrypted(claSCWallet, insDeriveKey, deriveP1Assisted|deriveP1Append, deriveP2PublicKey, pubkey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return pubkey, nil
|
return pubkey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -935,18 +963,16 @@ type keyExport struct {
|
|||||||
PrivateKey []byte `asn1:"tag:1,optional"`
|
PrivateKey []byte `asn1:"tag:1,optional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// getPublicKey returns the public key for the current derivation path.
|
// publicKey returns the public key for the current derivation path.
|
||||||
func (s *Session) getPublicKey() ([]byte, error) {
|
func (s *Session) publicKey() ([]byte, error) {
|
||||||
response, err := s.Channel.TransmitEncrypted(claSCWallet, insExportKey, exportP1Any, exportP2Pubkey, nil)
|
response, err := s.Channel.TransmitEncrypted(claSCWallet, insExportKey, exportP1Any, exportP2Pubkey, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
keys := new(keyExport)
|
keys := new(keyExport)
|
||||||
if _, err := asn1.UnmarshalWithParams(response.Data, keys, "tag:1"); err != nil {
|
if _, err := asn1.UnmarshalWithParams(response.Data, keys, "tag:1"); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return keys.PublicKey, nil
|
return keys.PublicKey, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -974,12 +1000,10 @@ func (s *Session) sign(path accounts.DerivationPath, hash []byte) ([]byte, error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sigdata := new(signatureData)
|
sigdata := new(signatureData)
|
||||||
if _, err := asn1.UnmarshalWithParams(response.Data, sigdata, "tag:0"); err != nil {
|
if _, err := asn1.UnmarshalWithParams(response.Data, sigdata, "tag:0"); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serialize the signature
|
// Serialize the signature
|
||||||
rbytes, sbytes := sigdata.Signature.R.Bytes(), sigdata.Signature.S.Bytes()
|
rbytes, sbytes := sigdata.Signature.R.Bytes(), sigdata.Signature.S.Bytes()
|
||||||
sig := make([]byte, 65)
|
sig := make([]byte, 65)
|
||||||
@ -991,7 +1015,6 @@ func (s *Session) sign(path accounts.DerivationPath, hash []byte) ([]byte, error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug("Signed using smartcard", "deriveTime", deriveTime.Sub(startTime), "signingTime", time.Since(deriveTime))
|
log.Debug("Signed using smartcard", "deriveTime", deriveTime.Sub(startTime), "signingTime", time.Since(deriveTime))
|
||||||
|
|
||||||
return sig, nil
|
return sig, nil
|
||||||
|
31
accounts/sort.go
Normal file
31
accounts/sort.go
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package accounts
|
||||||
|
|
||||||
|
// AccountsByURL implements sort.Interface for []Account based on the URL field.
|
||||||
|
type AccountsByURL []Account
|
||||||
|
|
||||||
|
func (a AccountsByURL) Len() int { return len(a) }
|
||||||
|
func (a AccountsByURL) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
func (a AccountsByURL) Less(i, j int) bool { return a[i].URL.Cmp(a[j].URL) < 0 }
|
||||||
|
|
||||||
|
// WalletsByURL implements sort.Interface for []Wallet based on the URL field.
|
||||||
|
type WalletsByURL []Wallet
|
||||||
|
|
||||||
|
func (w WalletsByURL) Len() int { return len(w) }
|
||||||
|
func (w WalletsByURL) Swap(i, j int) { w[i], w[j] = w[j], w[i] }
|
||||||
|
func (w WalletsByURL) Less(i, j int) bool { return w[i].URL().Cmp(w[j].URL()) < 0 }
|
@ -141,6 +141,24 @@ func (b *bridge) OpenWallet(call otto.FunctionCall) (response otto.Value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case strings.HasSuffix(err.Error(), scwallet.ErrPINUnblockNeeded.Error()):
|
||||||
|
// PIN unblock requested, fetch PUK and new PIN from the user
|
||||||
|
var pukpin string
|
||||||
|
if input, err := b.prompter.PromptPassword("Please enter current PUK: "); err != nil {
|
||||||
|
throwJSException(err.Error())
|
||||||
|
} else {
|
||||||
|
pukpin = input
|
||||||
|
}
|
||||||
|
if input, err := b.prompter.PromptPassword("Please enter new PIN: "); err != nil {
|
||||||
|
throwJSException(err.Error())
|
||||||
|
} else {
|
||||||
|
pukpin += input
|
||||||
|
}
|
||||||
|
passwd, _ = otto.ToValue(pukpin)
|
||||||
|
if val, err = call.Otto.Call("jeth.openWallet", nil, wallet, passwd); err != nil {
|
||||||
|
throwJSException(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
case strings.HasSuffix(err.Error(), scwallet.ErrPINNeeded.Error()):
|
case strings.HasSuffix(err.Error(), scwallet.ErrPINNeeded.Error()):
|
||||||
// PIN input requested, fetch from the user and call open again
|
// PIN input requested, fetch from the user and call open again
|
||||||
if input, err := b.prompter.PromptPassword("Please enter current PIN: "); err != nil {
|
if input, err := b.prompter.PromptPassword("Please enter current PIN: "); err != nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user