github.1485827954.workers.dev/ethereum/go-ethereum@v1.14.3/p2p/discover/v5wire/crypto.go (about) 1 // Copyright 2020 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 v5wire 18 19 import ( 20 "crypto/aes" 21 "crypto/cipher" 22 "crypto/ecdsa" 23 "crypto/elliptic" 24 "errors" 25 "fmt" 26 "hash" 27 28 "github.com/ethereum/go-ethereum/common/math" 29 "github.com/ethereum/go-ethereum/crypto" 30 "github.com/ethereum/go-ethereum/p2p/enode" 31 "golang.org/x/crypto/hkdf" 32 ) 33 34 const ( 35 // Encryption/authentication parameters. 36 aesKeySize = 16 37 gcmNonceSize = 12 38 ) 39 40 // Nonce represents a nonce used for AES/GCM. 41 type Nonce [gcmNonceSize]byte 42 43 // EncodePubkey encodes a public key. 44 func EncodePubkey(key *ecdsa.PublicKey) []byte { 45 switch key.Curve { 46 case crypto.S256(): 47 return crypto.CompressPubkey(key) 48 default: 49 panic("unsupported curve " + key.Curve.Params().Name + " in EncodePubkey") 50 } 51 } 52 53 // DecodePubkey decodes a public key in compressed format. 54 func DecodePubkey(curve elliptic.Curve, e []byte) (*ecdsa.PublicKey, error) { 55 switch curve { 56 case crypto.S256(): 57 if len(e) != 33 { 58 return nil, errors.New("wrong size public key data") 59 } 60 return crypto.DecompressPubkey(e) 61 default: 62 return nil, fmt.Errorf("unsupported curve %s in DecodePubkey", curve.Params().Name) 63 } 64 } 65 66 // idNonceHash computes the ID signature hash used in the handshake. 67 func idNonceHash(h hash.Hash, challenge, ephkey []byte, destID enode.ID) []byte { 68 h.Reset() 69 h.Write([]byte("discovery v5 identity proof")) 70 h.Write(challenge) 71 h.Write(ephkey) 72 h.Write(destID[:]) 73 return h.Sum(nil) 74 } 75 76 // makeIDSignature creates the ID nonce signature. 77 func makeIDSignature(hash hash.Hash, key *ecdsa.PrivateKey, challenge, ephkey []byte, destID enode.ID) ([]byte, error) { 78 input := idNonceHash(hash, challenge, ephkey, destID) 79 switch key.Curve { 80 case crypto.S256(): 81 idsig, err := crypto.Sign(input, key) 82 if err != nil { 83 return nil, err 84 } 85 return idsig[:len(idsig)-1], nil // remove recovery ID 86 default: 87 return nil, fmt.Errorf("unsupported curve %s", key.Curve.Params().Name) 88 } 89 } 90 91 // s256raw is an unparsed secp256k1 public key ENR entry. 92 type s256raw []byte 93 94 func (s256raw) ENRKey() string { return "secp256k1" } 95 96 // verifyIDSignature checks that signature over idnonce was made by the given node. 97 func verifyIDSignature(hash hash.Hash, sig []byte, n *enode.Node, challenge, ephkey []byte, destID enode.ID) error { 98 switch idscheme := n.Record().IdentityScheme(); idscheme { 99 case "v4": 100 var pubkey s256raw 101 if n.Load(&pubkey) != nil { 102 return errors.New("no secp256k1 public key in record") 103 } 104 input := idNonceHash(hash, challenge, ephkey, destID) 105 if !crypto.VerifySignature(pubkey, input, sig) { 106 return errInvalidNonceSig 107 } 108 return nil 109 default: 110 return fmt.Errorf("can't verify ID nonce signature against scheme %q", idscheme) 111 } 112 } 113 114 type hashFn func() hash.Hash 115 116 // deriveKeys creates the session keys. 117 func deriveKeys(hash hashFn, priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey, n1, n2 enode.ID, challenge []byte) *session { 118 const text = "discovery v5 key agreement" 119 var info = make([]byte, 0, len(text)+len(n1)+len(n2)) 120 info = append(info, text...) 121 info = append(info, n1[:]...) 122 info = append(info, n2[:]...) 123 124 eph := ecdh(priv, pub) 125 if eph == nil { 126 return nil 127 } 128 kdf := hkdf.New(hash, eph, challenge, info) 129 sec := session{writeKey: make([]byte, aesKeySize), readKey: make([]byte, aesKeySize)} 130 kdf.Read(sec.writeKey) 131 kdf.Read(sec.readKey) 132 clear(eph) 133 return &sec 134 } 135 136 // ecdh creates a shared secret. 137 func ecdh(privkey *ecdsa.PrivateKey, pubkey *ecdsa.PublicKey) []byte { 138 secX, secY := pubkey.ScalarMult(pubkey.X, pubkey.Y, privkey.D.Bytes()) 139 if secX == nil { 140 return nil 141 } 142 sec := make([]byte, 33) 143 sec[0] = 0x02 | byte(secY.Bit(0)) 144 math.ReadBits(secX, sec[1:]) 145 return sec 146 } 147 148 // encryptGCM encrypts pt using AES-GCM with the given key and nonce. The ciphertext is 149 // appended to dest, which must not overlap with plaintext. The resulting ciphertext is 16 150 // bytes longer than plaintext because it contains an authentication tag. 151 func encryptGCM(dest, key, nonce, plaintext, authData []byte) ([]byte, error) { 152 block, err := aes.NewCipher(key) 153 if err != nil { 154 panic(fmt.Errorf("can't create block cipher: %v", err)) 155 } 156 aesgcm, err := cipher.NewGCMWithNonceSize(block, gcmNonceSize) 157 if err != nil { 158 panic(fmt.Errorf("can't create GCM: %v", err)) 159 } 160 return aesgcm.Seal(dest, nonce, plaintext, authData), nil 161 } 162 163 // decryptGCM decrypts ct using AES-GCM with the given key and nonce. 164 func decryptGCM(key, nonce, ct, authData []byte) ([]byte, error) { 165 block, err := aes.NewCipher(key) 166 if err != nil { 167 return nil, fmt.Errorf("can't create block cipher: %v", err) 168 } 169 if len(nonce) != gcmNonceSize { 170 return nil, fmt.Errorf("invalid GCM nonce size: %d", len(nonce)) 171 } 172 aesgcm, err := cipher.NewGCMWithNonceSize(block, gcmNonceSize) 173 if err != nil { 174 return nil, fmt.Errorf("can't create GCM: %v", err) 175 } 176 pt := make([]byte, 0, len(ct)) 177 return aesgcm.Open(pt, nonce, ct, authData) 178 }