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