aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNimrod Gutman <nimrod.gutman@gmail.com>2019-01-24 19:21:38 +0800
committerFelix Lange <fjl@users.noreply.github.com>2019-01-24 19:21:38 +0800
commit6f45fa66d8661036c518a186fb8cc5ede0c0b805 (patch)
tree706462508bad1fa8b8e9bce38028deb0a6afca23
parent769657060e888612e7d585c6b6eae16a64c4ad19 (diff)
downloadgo-tangerine-6f45fa66d8661036c518a186fb8cc5ede0c0b805.tar
go-tangerine-6f45fa66d8661036c518a186fb8cc5ede0c0b805.tar.gz
go-tangerine-6f45fa66d8661036c518a186fb8cc5ede0c0b805.tar.bz2
go-tangerine-6f45fa66d8661036c518a186fb8cc5ede0c0b805.tar.lz
go-tangerine-6f45fa66d8661036c518a186fb8cc5ede0c0b805.tar.xz
go-tangerine-6f45fa66d8661036c518a186fb8cc5ede0c0b805.tar.zst
go-tangerine-6f45fa66d8661036c518a186fb8cc5ede0c0b805.zip
accounts/usbwallet: support trezor passphrases (#16503)
When opening the wallet, ask for passphrase as well as for the PIN and return the relevant error (PIN/passphrase required). Open must then be called again with either PIN or passphrase to advance the process. This also updates the console bridge to support passphrase authentication.
-rw-r--r--accounts/usbwallet/trezor.go64
-rw-r--r--console/bridge.go35
2 files changed, 75 insertions, 24 deletions
diff --git a/accounts/usbwallet/trezor.go b/accounts/usbwallet/trezor.go
index a9d2e9959..82525a20c 100644
--- a/accounts/usbwallet/trezor.go
+++ b/accounts/usbwallet/trezor.go
@@ -41,6 +41,9 @@ import (
// encoded passphrase.
var ErrTrezorPINNeeded = errors.New("trezor: pin needed")
+// ErrTrezorPassphraseNeeded is returned if opening the trezor requires a passphrase
+var ErrTrezorPassphraseNeeded = errors.New("trezor: passphrase needed")
+
// errTrezorReplyInvalidHeader is the error message returned by a Trezor data exchange
// if the device replies with a mismatching header. This usually means the device
// is in browser mode.
@@ -48,12 +51,13 @@ var errTrezorReplyInvalidHeader = errors.New("trezor: invalid reply header")
// trezorDriver implements the communication with a Trezor hardware wallet.
type trezorDriver struct {
- device io.ReadWriter // USB device connection to communicate through
- version [3]uint32 // Current version of the Trezor firmware
- label string // Current textual label of the Trezor device
- pinwait bool // Flags whether the device is waiting for PIN entry
- failure error // Any failure that would make the device unusable
- log log.Logger // Contextual logger to tag the trezor with its id
+ device io.ReadWriter // USB device connection to communicate through
+ version [3]uint32 // Current version of the Trezor firmware
+ label string // Current textual label of the Trezor device
+ pinwait bool // Flags whether the device is waiting for PIN entry
+ passphrasewait bool // Flags whether the device is waiting for passphrase entry
+ failure error // Any failure that would make the device unusable
+ log log.Logger // Contextual logger to tag the trezor with its id
}
// newTrezorDriver creates a new instance of a Trezor USB protocol driver.
@@ -79,7 +83,7 @@ func (w *trezorDriver) Status() (string, error) {
}
// Open implements usbwallet.driver, attempting to initialize the connection to
-// the Trezor hardware wallet. Initializing the Trezor is a two phase operation:
+// the Trezor hardware wallet. Initializing the Trezor is a two or three phase operation:
// * The first phase is to initialize the connection and read the wallet's
// features. This phase is invoked is the provided passphrase is empty. The
// device will display the pinpad as a result and will return an appropriate
@@ -87,11 +91,13 @@ func (w *trezorDriver) Status() (string, error) {
// * The second phase is to unlock access to the Trezor, which is done by the
// user actually providing a passphrase mapping a keyboard keypad to the pin
// number of the user (shuffled according to the pinpad displayed).
+// * If needed the device will ask for passphrase which will require calling
+// open again with the actual passphrase (3rd phase)
func (w *trezorDriver) Open(device io.ReadWriter, passphrase string) error {
w.device, w.failure = device, nil
// If phase 1 is requested, init the connection and wait for user callback
- if passphrase == "" {
+ if passphrase == "" && !w.passphrasewait {
// If we're already waiting for a PIN entry, insta-return
if w.pinwait {
return ErrTrezorPINNeeded
@@ -104,26 +110,46 @@ func (w *trezorDriver) Open(device io.ReadWriter, passphrase string) error {
w.version = [3]uint32{features.GetMajorVersion(), features.GetMinorVersion(), features.GetPatchVersion()}
w.label = features.GetLabel()
- // Do a manual ping, forcing the device to ask for its PIN
+ // Do a manual ping, forcing the device to ask for its PIN and Passphrase
askPin := true
- res, err := w.trezorExchange(&trezor.Ping{PinProtection: &askPin}, new(trezor.PinMatrixRequest), new(trezor.Success))
+ askPassphrase := true
+ res, err := w.trezorExchange(&trezor.Ping{PinProtection: &askPin, PassphraseProtection: &askPassphrase}, new(trezor.PinMatrixRequest), new(trezor.PassphraseRequest), new(trezor.Success))
if err != nil {
return err
}
// Only return the PIN request if the device wasn't unlocked until now
- if res == 1 {
- return nil // Device responded with trezor.Success
+ switch res {
+ case 0:
+ w.pinwait = true
+ return ErrTrezorPINNeeded
+ case 1:
+ w.pinwait = false
+ w.passphrasewait = true
+ return ErrTrezorPassphraseNeeded
+ case 2:
+ return nil // responded with trezor.Success
}
- w.pinwait = true
- return ErrTrezorPINNeeded
}
// Phase 2 requested with actual PIN entry
- w.pinwait = false
-
- if _, err := w.trezorExchange(&trezor.PinMatrixAck{Pin: &passphrase}, new(trezor.Success)); err != nil {
- w.failure = err
- return err
+ if w.pinwait {
+ w.pinwait = false
+ res, err := w.trezorExchange(&trezor.PinMatrixAck{Pin: &passphrase}, new(trezor.Success), new(trezor.PassphraseRequest))
+ if err != nil {
+ w.failure = err
+ return err
+ }
+ if res == 1 {
+ w.passphrasewait = true
+ return ErrTrezorPassphraseNeeded
+ }
+ } else if w.passphrasewait {
+ w.passphrasewait = false
+ if _, err := w.trezorExchange(&trezor.PassphraseAck{Passphrase: &passphrase}, new(trezor.Success)); err != nil {
+ w.failure = err
+ return err
+ }
}
+
return nil
}
diff --git a/console/bridge.go b/console/bridge.go
index b0b4d3798..33277cf6e 100644
--- a/console/bridge.go
+++ b/console/bridge.go
@@ -105,9 +105,37 @@ func (b *bridge) OpenWallet(call otto.FunctionCall) (response otto.Value) {
return val
}
// Wallet open failed, report error unless it's a PIN entry
- if !strings.HasSuffix(err.Error(), usbwallet.ErrTrezorPINNeeded.Error()) {
+ if strings.HasSuffix(err.Error(), usbwallet.ErrTrezorPINNeeded.Error()) {
+ val, err = b.readPinAndReopenWallet(call)
+ if err == nil {
+ return val
+ }
+ }
+ // Check if the user needs to input a passphrase
+ if !strings.HasSuffix(err.Error(), usbwallet.ErrTrezorPassphraseNeeded.Error()) {
+ throwJSException(err.Error())
+ }
+ val, err = b.readPassphraseAndReopenWallet(call)
+ if err != nil {
throwJSException(err.Error())
}
+ return val
+}
+
+func (b *bridge) readPassphraseAndReopenWallet(call otto.FunctionCall) (otto.Value, error) {
+ var passwd otto.Value
+ wallet := call.Argument(0)
+ if input, err := b.prompter.PromptPassword("Please enter your passphrase: "); err != nil {
+ throwJSException(err.Error())
+ } else {
+ passwd, _ = otto.ToValue(input)
+ }
+ return call.Otto.Call("jeth.openWallet", nil, wallet, passwd)
+}
+
+func (b *bridge) readPinAndReopenWallet(call otto.FunctionCall) (otto.Value, error) {
+ var passwd otto.Value
+ wallet := call.Argument(0)
// Trezor PIN matrix input requested, display the matrix to the user and fetch the data
fmt.Fprintf(b.printer, "Look at the device for number positions\n\n")
fmt.Fprintf(b.printer, "7 | 8 | 9\n")
@@ -121,10 +149,7 @@ func (b *bridge) OpenWallet(call otto.FunctionCall) (response otto.Value) {
} else {
passwd, _ = otto.ToValue(input)
}
- if val, err = call.Otto.Call("jeth.openWallet", nil, wallet, passwd); err != nil {
- throwJSException(err.Error())
- }
- return val
+ return call.Otto.Call("jeth.openWallet", nil, wallet, passwd)
}
// UnlockAccount is a wrapper around the personal.unlockAccount RPC method that