Skip to content

Commit

Permalink
Remove the direct dependency on libpcsclite
Browse files Browse the repository at this point in the history
Instead, use a go library that communicates with pcscd over a socket.

Also update the changes introduced by @gravityblast since this PR's
inception
  • Loading branch information
gballet committed Apr 8, 2019
1 parent ae82c58 commit 5617dca
Show file tree
Hide file tree
Showing 18 changed files with 678 additions and 1,192 deletions.
10 changes: 5 additions & 5 deletions accounts/scwallet/hub.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,11 @@ import (
"sync"
"time"

"github.com/ebfe/scard"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
pcsc "github.com/gballet/go-libpcsclite"
)

// Scheme is the URI prefix for smartcard wallets.
Expand All @@ -70,7 +70,7 @@ type smartcardPairing struct {
type Hub struct {
scheme string // Protocol scheme prefixing account and wallet URLs.

context *scard.Context
context *pcsc.Client
datadir string
pairings map[string]smartcardPairing

Expand Down Expand Up @@ -152,7 +152,7 @@ func (hub *Hub) setPairing(wallet *Wallet, pairing *smartcardPairing) error {

// NewHub creates a new hardware wallet manager for smartcards.
func NewHub(scheme string, datadir string) (*Hub, error) {
context, err := scard.EstablishContext()
context, err := pcsc.EstablishContext(pcsc.ScopeSystem)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -228,15 +228,15 @@ func (hub *Hub) refreshWallets() {
delete(hub.wallets, reader)
}
// New card detected, try to connect to it
card, err := hub.context.Connect(reader, scard.ShareShared, scard.ProtocolAny)
card, err := hub.context.Connect(reader, pcsc.ShareShared, pcsc.ProtocolAny)
if err != nil {
log.Debug("Failed to open smart card", "reader", reader, "err", err)
continue
}
wallet := NewWallet(hub, card)
if err = wallet.connect(); err != nil {
log.Debug("Failed to connect to smart card", "reader", reader, "err", err)
card.Disconnect(scard.LeaveCard)
card.Disconnect(pcsc.LeaveCard)
continue
}
// Card connected, start tracking in amongs the wallets
Expand Down
32 changes: 18 additions & 14 deletions accounts/scwallet/securechannel.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,11 @@ import (
"crypto/sha512"
"fmt"

"github.com/ebfe/scard"
"github.com/ethereum/go-ethereum/crypto"
pcsc "github.com/gballet/go-libpcsclite"
"github.com/wsddn/go-ecdh"
"golang.org/x/crypto/pbkdf2"
"golang.org/x/text/unicode/norm"
)

const (
Expand All @@ -42,22 +44,24 @@ const (
insMutuallyAuthenticate = 0x11
insPair = 0x12
insUnpair = 0x13

pairingSalt = "Keycard Pairing Password Salt"
)

// 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
card *pcsc.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) {
func NewSecureChannelSession(card *pcsc.Card, keyData []byte) (*SecureChannelSession, error) {
// Generate an ECDSA keypair for ourselves
gen := ecdh.NewEllipticECDH(crypto.S256())
private, public, err := gen.GenerateKey(rand.Reader)
Expand All @@ -83,8 +87,8 @@ func NewSecureChannelSession(card *scard.Card, keyData []byte) (*SecureChannelSe
}

// Pair establishes a new pairing with the smartcard.
func (s *SecureChannelSession) Pair(sharedSecret []byte) error {
secretHash := sha256.Sum256(sharedSecret)
func (s *SecureChannelSession) Pair(pairingPassword []byte) error {
secretHash := pbkdf2.Key(norm.NFKD.Bytes([]byte(pairingPassword)), norm.NFKD.Bytes([]byte(pairingSalt)), 50000, 32, sha256.New)

challenge := make([]byte, 32)
if _, err := rand.Read(challenge); err != nil {
Expand All @@ -102,10 +106,10 @@ func (s *SecureChannelSession) Pair(sharedSecret []byte) error {

expectedCryptogram := md.Sum(nil)
cardCryptogram := response.Data[:32]
cardChallenge := response.Data[32:]
cardChallenge := response.Data[32:64]

if !bytes.Equal(expectedCryptogram, cardCryptogram) {
return fmt.Errorf("Invalid card cryptogram")
return fmt.Errorf("Invalid card cryptogram %v != %v", expectedCryptogram, cardCryptogram)
}

md.Reset()
Expand Down
31 changes: 16 additions & 15 deletions accounts/scwallet/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,20 @@ import (
"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"
pcsc "github.com/gballet/go-libpcsclite"
)

// ErrPUKNeeded is returned if opening the smart card requires pairing with a PUK
// code. In this case, the calling application should request user input to enter
// the PUK and send it back.
var ErrPUKNeeded = errors.New("smartcard: puk needed")
// ErrPairingPasswordNeeded is returned if opening the smart card requires pairing with a pairing
// password. In this case, the calling application should request user input to enter
// the pairing password and send it back.
var ErrPairingPasswordNeeded = errors.New("smartcard: pairing password needed")

// ErrPINNeeded is returned if opening the smart card requires a PIN code. In
// this case, the calling application should request user input to enter the PIN
Expand All @@ -67,7 +67,8 @@ var ErrAlreadyOpen = errors.New("smartcard: already open")
var ErrPubkeyMismatch = errors.New("smartcard: recovered public key mismatch")

var (
appletAID = []byte{0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x41, 0x70, 0x70}
// appletAID = []byte{0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x57, 0x61, 0x6C, 0x6C, 0x65, 0x74, 0x41, 0x70, 0x70}
appletAID = []byte{0xA0, 0x00, 0x00, 0x08, 0x04, 0x00, 0x01, 0x01, 0x01}
DerivationSignatureHash = sha256.Sum256([]byte("STATUS KEY DERIVATION"))
)

Expand Down Expand Up @@ -108,10 +109,10 @@ 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
lock sync.Mutex // Lock that gates access to struct fields and communication with the card
card *pcsc.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
Expand All @@ -121,7 +122,7 @@ type Wallet struct {
}

// NewWallet constructs and returns a new Wallet instance.
func NewWallet(hub *Hub, card *scard.Card) *Wallet {
func NewWallet(hub *Hub, card *pcsc.Card) *Wallet {
wallet := &Wallet{
Hub: hub,
card: card,
Expand All @@ -132,13 +133,13 @@ func NewWallet(hub *Hub, card *scard.Card) *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) {
func transmit(card *pcsc.Card, command *commandAPDU) (*responseAPDU, error) {
data, err := command.serialize()
if err != nil {
return nil, err
}

responseData, err := card.Transmit(data)
responseData, _, err := card.Transmit(data)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -349,7 +350,7 @@ func (w *Wallet) Open(passphrase string) error {
} else {
// If no passphrase was supplied, request the PUK from the user
if passphrase == "" {
return ErrPUKNeeded
return ErrPairingPasswordNeeded
}
// Attempt to pair the smart card with the user supplied PUK
if err := w.pair([]byte(passphrase)); err != nil {
Expand Down Expand Up @@ -814,7 +815,7 @@ func (s *Session) unblockPin(pukpin []byte) error {

// release releases resources associated with the channel.
func (s *Session) release() error {
return s.Wallet.card.Disconnect(scard.LeaveCard)
return s.Wallet.card.Disconnect(pcsc.LeaveCard)
}

// paired returns true if a valid pairing exists.
Expand Down
4 changes: 2 additions & 2 deletions console/bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,9 @@ func (b *bridge) OpenWallet(call otto.FunctionCall) (response otto.Value) {
throwJSException(err.Error())
}

case strings.HasSuffix(err.Error(), scwallet.ErrPUKNeeded.Error()):
case strings.HasSuffix(err.Error(), scwallet.ErrPairingPasswordNeeded.Error()):
// PUK input requested, fetch from the user and call open again
if input, err := b.prompter.PromptPassword("Please enter current PUK: "); err != nil {
if input, err := b.prompter.PromptPassword("Please enter the pairing password: "); err != nil {
throwJSException(err.Error())
} else {
passwd, _ = otto.ToValue(input)
Expand Down
23 changes: 0 additions & 23 deletions vendor/github.com/ebfe/scard/LICENSE

This file was deleted.

14 changes: 0 additions & 14 deletions vendor/github.com/ebfe/scard/README.md

This file was deleted.

Loading

0 comments on commit 5617dca

Please sign in to comment.