github.com/bcnmy/go-ethereum@v1.10.27/accounts/scwallet/securechannel.go (about)

     1  // Copyright 2018 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The go-ethereum library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package scwallet
    18  
    19  import (
    20  	"bytes"
    21  	"crypto/aes"
    22  	"crypto/cipher"
    23  	"crypto/elliptic"
    24  	"crypto/rand"
    25  	"crypto/sha256"
    26  	"crypto/sha512"
    27  	"fmt"
    28  
    29  	"github.com/ethereum/go-ethereum/crypto"
    30  	pcsc "github.com/gballet/go-libpcsclite"
    31  	"golang.org/x/crypto/pbkdf2"
    32  	"golang.org/x/text/unicode/norm"
    33  )
    34  
    35  const (
    36  	maxPayloadSize  = 223
    37  	pairP1FirstStep = 0
    38  	pairP1LastStep  = 1
    39  
    40  	scSecretLength = 32
    41  	scBlockSize    = 16
    42  
    43  	insOpenSecureChannel    = 0x10
    44  	insMutuallyAuthenticate = 0x11
    45  	insPair                 = 0x12
    46  	insUnpair               = 0x13
    47  
    48  	pairingSalt = "Keycard Pairing Password Salt"
    49  )
    50  
    51  // SecureChannelSession enables secure communication with a hardware wallet.
    52  type SecureChannelSession struct {
    53  	card          *pcsc.Card // A handle to the smartcard for communication
    54  	secret        []byte     // A shared secret generated from our ECDSA keys
    55  	publicKey     []byte     // Our own ephemeral public key
    56  	PairingKey    []byte     // A permanent shared secret for a pairing, if present
    57  	sessionEncKey []byte     // The current session encryption key
    58  	sessionMacKey []byte     // The current session MAC key
    59  	iv            []byte     // The current IV
    60  	PairingIndex  uint8      // The pairing index
    61  }
    62  
    63  // NewSecureChannelSession creates a new secure channel for the given card and public key.
    64  func NewSecureChannelSession(card *pcsc.Card, keyData []byte) (*SecureChannelSession, error) {
    65  	// Generate an ECDSA keypair for ourselves
    66  	key, err := crypto.GenerateKey()
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  	cardPublic, err := crypto.UnmarshalPubkey(keyData)
    71  	if err != nil {
    72  		return nil, fmt.Errorf("could not unmarshal public key from card: %v", err)
    73  	}
    74  	secret, _ := key.Curve.ScalarMult(cardPublic.X, cardPublic.Y, key.D.Bytes())
    75  	return &SecureChannelSession{
    76  		card:      card,
    77  		secret:    secret.Bytes(),
    78  		publicKey: elliptic.Marshal(crypto.S256(), key.PublicKey.X, key.PublicKey.Y),
    79  	}, nil
    80  }
    81  
    82  // Pair establishes a new pairing with the smartcard.
    83  func (s *SecureChannelSession) Pair(pairingPassword []byte) error {
    84  	secretHash := pbkdf2.Key(norm.NFKD.Bytes(pairingPassword), norm.NFKD.Bytes([]byte(pairingSalt)), 50000, 32, sha256.New)
    85  
    86  	challenge := make([]byte, 32)
    87  	if _, err := rand.Read(challenge); err != nil {
    88  		return err
    89  	}
    90  
    91  	response, err := s.pair(pairP1FirstStep, challenge)
    92  	if err != nil {
    93  		return err
    94  	}
    95  
    96  	md := sha256.New()
    97  	md.Write(secretHash[:])
    98  	md.Write(challenge)
    99  
   100  	expectedCryptogram := md.Sum(nil)
   101  	cardCryptogram := response.Data[:32]
   102  	cardChallenge := response.Data[32:64]
   103  
   104  	if !bytes.Equal(expectedCryptogram, cardCryptogram) {
   105  		return fmt.Errorf("invalid card cryptogram %v != %v", expectedCryptogram, cardCryptogram)
   106  	}
   107  
   108  	md.Reset()
   109  	md.Write(secretHash[:])
   110  	md.Write(cardChallenge)
   111  	response, err = s.pair(pairP1LastStep, md.Sum(nil))
   112  	if err != nil {
   113  		return err
   114  	}
   115  
   116  	md.Reset()
   117  	md.Write(secretHash[:])
   118  	md.Write(response.Data[1:])
   119  	s.PairingKey = md.Sum(nil)
   120  	s.PairingIndex = response.Data[0]
   121  
   122  	return nil
   123  }
   124  
   125  // Unpair disestablishes an existing pairing.
   126  func (s *SecureChannelSession) Unpair() error {
   127  	if s.PairingKey == nil {
   128  		return fmt.Errorf("cannot unpair: not paired")
   129  	}
   130  
   131  	_, err := s.transmitEncrypted(claSCWallet, insUnpair, s.PairingIndex, 0, []byte{})
   132  	if err != nil {
   133  		return err
   134  	}
   135  	s.PairingKey = nil
   136  	// Close channel
   137  	s.iv = nil
   138  	return nil
   139  }
   140  
   141  // Open initializes the secure channel.
   142  func (s *SecureChannelSession) Open() error {
   143  	if s.iv != nil {
   144  		return fmt.Errorf("session already opened")
   145  	}
   146  
   147  	response, err := s.open()
   148  	if err != nil {
   149  		return err
   150  	}
   151  
   152  	// Generate the encryption/mac key by hashing our shared secret,
   153  	// pairing key, and the first bytes returned from the Open APDU.
   154  	md := sha512.New()
   155  	md.Write(s.secret)
   156  	md.Write(s.PairingKey)
   157  	md.Write(response.Data[:scSecretLength])
   158  	keyData := md.Sum(nil)
   159  	s.sessionEncKey = keyData[:scSecretLength]
   160  	s.sessionMacKey = keyData[scSecretLength : scSecretLength*2]
   161  
   162  	// The IV is the last bytes returned from the Open APDU.
   163  	s.iv = response.Data[scSecretLength:]
   164  
   165  	return s.mutuallyAuthenticate()
   166  }
   167  
   168  // mutuallyAuthenticate is an internal method to authenticate both ends of the
   169  // connection.
   170  func (s *SecureChannelSession) mutuallyAuthenticate() error {
   171  	data := make([]byte, scSecretLength)
   172  	if _, err := rand.Read(data); err != nil {
   173  		return err
   174  	}
   175  
   176  	response, err := s.transmitEncrypted(claSCWallet, insMutuallyAuthenticate, 0, 0, data)
   177  	if err != nil {
   178  		return err
   179  	}
   180  	if response.Sw1 != 0x90 || response.Sw2 != 0x00 {
   181  		return fmt.Errorf("got unexpected response from MUTUALLY_AUTHENTICATE: %#x%x", response.Sw1, response.Sw2)
   182  	}
   183  
   184  	if len(response.Data) != scSecretLength {
   185  		return fmt.Errorf("response from MUTUALLY_AUTHENTICATE was %d bytes, expected %d", len(response.Data), scSecretLength)
   186  	}
   187  
   188  	return nil
   189  }
   190  
   191  // open is an internal method that sends an open APDU.
   192  func (s *SecureChannelSession) open() (*responseAPDU, error) {
   193  	return transmit(s.card, &commandAPDU{
   194  		Cla:  claSCWallet,
   195  		Ins:  insOpenSecureChannel,
   196  		P1:   s.PairingIndex,
   197  		P2:   0,
   198  		Data: s.publicKey,
   199  		Le:   0,
   200  	})
   201  }
   202  
   203  // pair is an internal method that sends a pair APDU.
   204  func (s *SecureChannelSession) pair(p1 uint8, data []byte) (*responseAPDU, error) {
   205  	return transmit(s.card, &commandAPDU{
   206  		Cla:  claSCWallet,
   207  		Ins:  insPair,
   208  		P1:   p1,
   209  		P2:   0,
   210  		Data: data,
   211  		Le:   0,
   212  	})
   213  }
   214  
   215  // transmitEncrypted sends an encrypted message, and decrypts and returns the response.
   216  func (s *SecureChannelSession) transmitEncrypted(cla, ins, p1, p2 byte, data []byte) (*responseAPDU, error) {
   217  	if s.iv == nil {
   218  		return nil, fmt.Errorf("channel not open")
   219  	}
   220  
   221  	data, err := s.encryptAPDU(data)
   222  	if err != nil {
   223  		return nil, err
   224  	}
   225  	meta := [16]byte{cla, ins, p1, p2, byte(len(data) + scBlockSize)}
   226  	if err = s.updateIV(meta[:], data); err != nil {
   227  		return nil, err
   228  	}
   229  
   230  	fulldata := make([]byte, len(s.iv)+len(data))
   231  	copy(fulldata, s.iv)
   232  	copy(fulldata[len(s.iv):], data)
   233  
   234  	response, err := transmit(s.card, &commandAPDU{
   235  		Cla:  cla,
   236  		Ins:  ins,
   237  		P1:   p1,
   238  		P2:   p2,
   239  		Data: fulldata,
   240  	})
   241  	if err != nil {
   242  		return nil, err
   243  	}
   244  
   245  	rmeta := [16]byte{byte(len(response.Data))}
   246  	rmac := response.Data[:len(s.iv)]
   247  	rdata := response.Data[len(s.iv):]
   248  	plainData, err := s.decryptAPDU(rdata)
   249  	if err != nil {
   250  		return nil, err
   251  	}
   252  
   253  	if err = s.updateIV(rmeta[:], rdata); err != nil {
   254  		return nil, err
   255  	}
   256  	if !bytes.Equal(s.iv, rmac) {
   257  		return nil, fmt.Errorf("invalid MAC in response")
   258  	}
   259  
   260  	rapdu := &responseAPDU{}
   261  	rapdu.deserialize(plainData)
   262  
   263  	if rapdu.Sw1 != sw1Ok {
   264  		return nil, fmt.Errorf("unexpected response status Cla=%#x, Ins=%#x, Sw=%#x%x", cla, ins, rapdu.Sw1, rapdu.Sw2)
   265  	}
   266  
   267  	return rapdu, nil
   268  }
   269  
   270  // encryptAPDU is an internal method that serializes and encrypts an APDU.
   271  func (s *SecureChannelSession) encryptAPDU(data []byte) ([]byte, error) {
   272  	if len(data) > maxPayloadSize {
   273  		return nil, fmt.Errorf("payload of %d bytes exceeds maximum of %d", len(data), maxPayloadSize)
   274  	}
   275  	data = pad(data, 0x80)
   276  
   277  	ret := make([]byte, len(data))
   278  
   279  	a, err := aes.NewCipher(s.sessionEncKey)
   280  	if err != nil {
   281  		return nil, err
   282  	}
   283  	crypter := cipher.NewCBCEncrypter(a, s.iv)
   284  	crypter.CryptBlocks(ret, data)
   285  	return ret, nil
   286  }
   287  
   288  // pad applies message padding to a 16 byte boundary.
   289  func pad(data []byte, terminator byte) []byte {
   290  	padded := make([]byte, (len(data)/16+1)*16)
   291  	copy(padded, data)
   292  	padded[len(data)] = terminator
   293  	return padded
   294  }
   295  
   296  // decryptAPDU is an internal method that decrypts and deserializes an APDU.
   297  func (s *SecureChannelSession) decryptAPDU(data []byte) ([]byte, error) {
   298  	a, err := aes.NewCipher(s.sessionEncKey)
   299  	if err != nil {
   300  		return nil, err
   301  	}
   302  
   303  	ret := make([]byte, len(data))
   304  
   305  	crypter := cipher.NewCBCDecrypter(a, s.iv)
   306  	crypter.CryptBlocks(ret, data)
   307  	return unpad(ret, 0x80)
   308  }
   309  
   310  // unpad strips padding from a message.
   311  func unpad(data []byte, terminator byte) ([]byte, error) {
   312  	for i := 1; i <= 16; i++ {
   313  		switch data[len(data)-i] {
   314  		case 0:
   315  			continue
   316  		case terminator:
   317  			return data[:len(data)-i], nil
   318  		default:
   319  			return nil, fmt.Errorf("expected end of padding, got %d", data[len(data)-i])
   320  		}
   321  	}
   322  	return nil, fmt.Errorf("expected end of padding, got 0")
   323  }
   324  
   325  // updateIV is an internal method that updates the initialization vector after
   326  // each message exchanged.
   327  func (s *SecureChannelSession) updateIV(meta, data []byte) error {
   328  	data = pad(data, 0)
   329  	a, err := aes.NewCipher(s.sessionMacKey)
   330  	if err != nil {
   331  		return err
   332  	}
   333  	crypter := cipher.NewCBCEncrypter(a, make([]byte, 16))
   334  	crypter.CryptBlocks(meta, meta)
   335  	crypter.CryptBlocks(data, data)
   336  	// The first 16 bytes of the last block is the MAC
   337  	s.iv = data[len(data)-32 : len(data)-16]
   338  	return nil
   339  }