Skip to content

Commit

Permalink
accounts/usbwallet: support trezor passphrases (ethereum#16503)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ngutman authored and fjl committed Jan 24, 2019
1 parent 7696570 commit 6f45fa6
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 24 deletions.
64 changes: 45 additions & 19 deletions accounts/usbwallet/trezor.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,23 @@ 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.
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.
Expand All @@ -79,19 +83,21 @@ 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
// error to notify the user that a second open phase is needed.
// * 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
Expand All @@ -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
}

Expand Down
35 changes: 30 additions & 5 deletions console/bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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
Expand Down

0 comments on commit 6f45fa6

Please sign in to comment.