Commit 78375608 authored by Nick Johnson's avatar Nick Johnson Committed by Guillaume Ballet

accounts, internal: Changes in response to review

parent f7027dd6
......@@ -139,12 +139,12 @@ func (path DerivationPath) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf("\"%s\"", path.String())), nil
}
func (dp *DerivationPath) UnmarshalJSON(b []byte) error {
var path string
func (path *DerivationPath) UnmarshalJSON(b []byte) error {
var dp string
var err error
if err = json.Unmarshal(b, &path); err != nil {
if err = json.Unmarshal(b, &dp); err != nil {
return err
}
*dp, err = ParseDerivationPath(path)
*path, err = ParseDerivationPath(dp)
return err
}
......@@ -22,20 +22,20 @@ import (
)
const (
CLA_ISO7816 = 0
claISO7816 = 0
INS_SELECT = 0xA4
INS_GET_RESPONSE = 0xC0
INS_PAIR = 0x12
INS_UNPAIR = 0x13
INS_OPEN_SECURE_CHANNEL = 0x10
INS_MUTUALLY_AUTHENTICATE = 0x11
insSelect = 0xA4
insGetResponse = 0xC0
insPair = 0x12
insUnpair = 0x13
insOpenSecureChannel = 0x10
insMutuallyAuthenticate = 0x11
SW1_GET_RESPONSE = 0x61
SW1_OK = 0x90
sw1GetResponse = 0x61
sw1Ok = 0x90
)
// CommandAPDU represents an application data unit sent to a smartcard
// 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
......@@ -72,13 +72,13 @@ func (ca CommandAPDU) serialize() ([]byte, error) {
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 {
Data []byte // response data
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 {
ra.Data = make([]byte, len(data)-2)
......
......@@ -14,6 +14,22 @@
// 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/>.
// This package implements support for smartcard-based hardware wallets such as
// the one written by Status: https://github.com/status-im/hardware-wallet
//
// This implementation of smartcard wallets have a different interaction process
// to other types of hardware wallet. The process works like this:
//
// 1. (First use with a given client) Establish a pairing between hardware
// wallet and client. This requires a secret value called a 'PUK'. You can
// pair with an unpaired wallet with `personal.openWallet(URI, PUK)`.
// 2. (First use only) Initialize the wallet, which generates a keypair, stores
// it on the wallet, and returns it so the user can back it up. You can
// initialize a wallet with `personal.initializeWallet(URI)`.
// 3. Connect to the wallet using the pairing information established in step 1.
// You can connect to a paired wallet with `personal.openWallet(URI, PIN)`.
// 4. Interact with the wallet as normal.
package scwallet
import (
......@@ -32,6 +48,7 @@ import (
"github.com/ethereum/go-ethereum/log"
)
// Scheme is the URI prefix for smartcard wallets.
const Scheme = "pcsc"
// refreshCycle is the maximum time between wallet refreshes (if USB hotplug
......@@ -41,9 +58,9 @@ 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 {
// smartcardPairing contains information about a smart card we have paired with
// or might pair with the hub.
type smartcardPairing struct {
PublicKey []byte `json:"publicKey"`
PairingIndex uint8 `json:"pairingIndex"`
PairingKey []byte `json:"pairingKey"`
......@@ -56,7 +73,7 @@ type Hub struct {
context *scard.Context
datadir string
pairings map[string]SmartcardPairing
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
......@@ -71,7 +88,7 @@ type Hub struct {
var HubType = reflect.TypeOf(&Hub{})
func (hub *Hub) readPairings() error {
hub.pairings = make(map[string]SmartcardPairing)
hub.pairings = make(map[string]smartcardPairing)
pairingFile, err := os.Open(hub.datadir + "/smartcards.json")
if err != nil {
if os.IsNotExist(err) {
......@@ -84,7 +101,7 @@ func (hub *Hub) readPairings() error {
if err != nil {
return err
}
var pairings []SmartcardPairing
var pairings []smartcardPairing
if err := json.Unmarshal(pairingData, &pairings); err != nil {
return err
}
......@@ -101,7 +118,7 @@ func (hub *Hub) writePairings() error {
return err
}
pairings := make([]SmartcardPairing, 0, len(hub.pairings))
pairings := make([]smartcardPairing, 0, len(hub.pairings))
for _, pairing := range hub.pairings {
pairings = append(pairings, pairing)
}
......@@ -118,7 +135,7 @@ func (hub *Hub) writePairings() error {
return pairingFile.Close()
}
func (hub *Hub) getPairing(wallet *Wallet) *SmartcardPairing {
func (hub *Hub) getPairing(wallet *Wallet) *smartcardPairing {
pairing, ok := hub.pairings[string(wallet.PublicKey)]
if ok {
return &pairing
......@@ -126,7 +143,7 @@ func (hub *Hub) getPairing(wallet *Wallet) *SmartcardPairing {
return nil
}
func (hub *Hub) setPairing(wallet *Wallet, pairing *SmartcardPairing) error {
func (hub *Hub) setPairing(wallet *Wallet, pairing *smartcardPairing) error {
if pairing == nil {
delete(hub.pairings, string(wallet.PublicKey))
} else {
......@@ -158,7 +175,7 @@ func NewHub(scheme string, datadir string) (*Hub, error) {
return hub, nil
}
// Wallets implements accounts.Backend, returning all the currently tracked USB
// Wallets implements accounts.Backend, returning all the currently tracked
// devices that appear to be hardware wallets.
func (hub *Hub) Wallets() []accounts.Wallet {
// Make sure the list of wallets is up to date
......@@ -176,7 +193,7 @@ func (hub *Hub) Wallets() []accounts.Wallet {
return cpy
}
// refreshWallets scans the USB devices attached to the machine and updates the
// refreshWallets scans the 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)
......
......@@ -32,15 +32,15 @@ import (
)
const (
MAX_PAYLOAD_SIZE = 223
PAIR_P1_FIRST_STEP = 0
PAIR_P1_LAST_STEP = 1
maxPayloadSize = 223
pairP1FirstStep = 0
pairP1LastStep = 1
SC_SECRET_LENGTH = 32
SC_BLOCK_SIZE = 16
scSecretLength = 32
scBlockSize = 16
)
// SecureChannelSession enables secure communication with a hardware wallet
// 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
......@@ -52,7 +52,7 @@ type SecureChannelSession struct {
PairingIndex uint8 // The pairing index
}
// NewSecureChannelSession creates a new secure channel for the given card and public key
// 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())
......@@ -78,7 +78,7 @@ func NewSecureChannelSession(card *scard.Card, keyData []byte) (*SecureChannelSe
}, nil
}
// Pair establishes a new pairing with the smartcard
// Pair establishes a new pairing with the smartcard.
func (s *SecureChannelSession) Pair(sharedSecret []byte) error {
secretHash := sha256.Sum256(sharedSecret)
......@@ -87,7 +87,7 @@ func (s *SecureChannelSession) Pair(sharedSecret []byte) error {
return err
}
response, err := s.pair(PAIR_P1_FIRST_STEP, challenge)
response, err := s.pair(pairP1FirstStep, challenge)
if err != nil {
return err
}
......@@ -107,7 +107,7 @@ func (s *SecureChannelSession) Pair(sharedSecret []byte) error {
md.Reset()
md.Write(secretHash[:])
md.Write(cardChallenge)
response, err = s.pair(PAIR_P1_LAST_STEP, md.Sum(nil))
response, err = s.pair(pairP1LastStep, md.Sum(nil))
if err != nil {
return err
}
......@@ -121,13 +121,13 @@ func (s *SecureChannelSession) Pair(sharedSecret []byte) error {
return nil
}
// Unpair disestablishes an existing pairing
// 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{})
_, err := s.TransmitEncrypted(claSCWallet, insUnpair, s.PairingIndex, 0, []byte{})
if err != nil {
return err
}
......@@ -137,7 +137,7 @@ func (s *SecureChannelSession) Unpair() error {
return nil
}
// Open initializes the secure channel
// Open initializes the secure channel.
func (s *SecureChannelSession) Open() error {
if s.iv != nil {
return fmt.Errorf("Session already opened")
......@@ -153,13 +153,13 @@ func (s *SecureChannelSession) Open() error {
md := sha512.New()
md.Write(s.secret)
md.Write(s.PairingKey)
md.Write(response.Data[:SC_SECRET_LENGTH])
md.Write(response.Data[:scSecretLength])
keyData := md.Sum(nil)
s.sessionEncKey = keyData[:SC_SECRET_LENGTH]
s.sessionMacKey = keyData[SC_SECRET_LENGTH : SC_SECRET_LENGTH*2]
s.sessionEncKey = keyData[:scSecretLength]
s.sessionMacKey = keyData[scSecretLength : scSecretLength*2]
// The IV is the last bytes returned from the Open APDU.
s.iv = response.Data[SC_SECRET_LENGTH:]
s.iv = response.Data[scSecretLength:]
if err := s.mutuallyAuthenticate(); err != nil {
return err
......@@ -171,12 +171,12 @@ func (s *SecureChannelSession) Open() error {
// mutuallyAuthenticate is an internal method to authenticate both ends of the
// connection.
func (s *SecureChannelSession) mutuallyAuthenticate() error {
data := make([]byte, SC_SECRET_LENGTH)
data := make([]byte, scSecretLength)
if _, err := rand.Read(data); err != nil {
return err
}
response, err := s.TransmitEncrypted(CLA_SCWALLET, INS_MUTUALLY_AUTHENTICATE, 0, 0, data)
response, err := s.TransmitEncrypted(claSCWallet, insMutuallyAuthenticate, 0, 0, data)
if err != nil {
return err
}
......@@ -184,18 +184,18 @@ func (s *SecureChannelSession) mutuallyAuthenticate() error {
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)
if len(response.Data) != scSecretLength {
return fmt.Errorf("Response from MUTUALLY_AUTHENTICATE was %d bytes, expected %d", len(response.Data), scSecretLength)
}
return nil
}
// 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) {
return transmit(s.card, &CommandAPDU{
Cla: CLA_SCWALLET,
Ins: INS_OPEN_SECURE_CHANNEL,
Cla: claSCWallet,
Ins: insOpenSecureChannel,
P1: s.PairingIndex,
P2: 0,
Data: s.publicKey,
......@@ -203,11 +203,11 @@ 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) {
return transmit(s.card, &CommandAPDU{
Cla: CLA_SCWALLET,
Ins: INS_PAIR,
Cla: claSCWallet,
Ins: insPair,
P1: p1,
P2: 0,
Data: data,
......@@ -215,7 +215,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) {
if s.iv == nil {
return nil, fmt.Errorf("Channel not open")
......@@ -225,7 +225,7 @@ func (s *SecureChannelSession) TransmitEncrypted(cla, ins, p1, p2 byte, data []b
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}
meta := []byte{cla, ins, p1, p2, byte(len(data) + scBlockSize), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
if err = s.updateIV(meta, data); err != nil {
return nil, err
}
......@@ -263,17 +263,17 @@ func (s *SecureChannelSession) TransmitEncrypted(cla, ins, p1, p2 byte, data []b
rapdu := &ResponseAPDU{}
rapdu.deserialize(plainData)
if rapdu.Sw1 != SW1_OK {
if rapdu.Sw1 != sw1Ok {
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
// 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)
if len(data) > maxPayloadSize {
return nil, fmt.Errorf("Payload of %d bytes exceeds maximum of %d", len(data), maxPayloadSize)
}
data = pad(data, 0x80)
......@@ -288,7 +288,7 @@ func (s *SecureChannelSession) encryptAPDU(data []byte) ([]byte, error) {
return ret, nil
}
// pad applies message padding to a 16 byte boundary
// 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)
......@@ -296,7 +296,7 @@ func pad(data []byte, terminator byte) []byte {
return padded
}
// decryptAPDU is an internal method that decrypts and deserializes an APDU
// 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 {
......@@ -310,7 +310,7 @@ func (s *SecureChannelSession) decryptAPDU(data []byte) ([]byte, error) {
return unpad(ret, 0x80)
}
// unpad strips padding from a message
// 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] {
......
This diff is collapsed.
......@@ -473,6 +473,7 @@ func (s *PrivateAccountAPI) SignAndSendTransaction(ctx context.Context, args Sen
return s.SendTransaction(ctx, args, passwd)
}
// InitializeWallet initializes a new wallet at the provided URL, by generating and returning a new private key.
func (s *PrivateAccountAPI) InitializeWallet(ctx context.Context, url string) (string, error) {
wallet, err := s.am.Wallet(url)
if err != nil {
......@@ -499,6 +500,7 @@ func (s *PrivateAccountAPI) InitializeWallet(ctx context.Context, url string) (s
}
}
// Unpair deletes a pairing between wallet and geth.
func (s *PrivateAccountAPI) Unpair(ctx context.Context, url string, pin string) error {
wallet, err := s.am.Wallet(url)
if err != nil {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment