This adds an implementation of the current discovery v5 spec. There is full integration with cmd/devp2p and enode.Iterator in this version. In theory we could enable the new protocol as a replacement of discovery v4 at any time. In practice, there will likely be a few more changes to the spec and implementation before this can happen.
		
			
				
	
	
		
			660 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			660 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2019 The go-ethereum Authors
 | 
						|
// This file is part of the go-ethereum library.
 | 
						|
//
 | 
						|
// The go-ethereum library is free software: you can redistribute it and/or modify
 | 
						|
// it under the terms of the GNU Lesser General Public License as published by
 | 
						|
// the Free Software Foundation, either version 3 of the License, or
 | 
						|
// (at your option) any later version.
 | 
						|
//
 | 
						|
// The go-ethereum library is distributed in the hope that it will be useful,
 | 
						|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
						|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 | 
						|
// GNU Lesser General Public License for more details.
 | 
						|
//
 | 
						|
// 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/>.
 | 
						|
 | 
						|
package discover
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"crypto/aes"
 | 
						|
	"crypto/cipher"
 | 
						|
	"crypto/ecdsa"
 | 
						|
	"crypto/elliptic"
 | 
						|
	crand "crypto/rand"
 | 
						|
	"crypto/sha256"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"hash"
 | 
						|
	"net"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"github.com/ethereum/go-ethereum/common/math"
 | 
						|
	"github.com/ethereum/go-ethereum/common/mclock"
 | 
						|
	"github.com/ethereum/go-ethereum/crypto"
 | 
						|
	"github.com/ethereum/go-ethereum/p2p/enode"
 | 
						|
	"github.com/ethereum/go-ethereum/p2p/enr"
 | 
						|
	"github.com/ethereum/go-ethereum/rlp"
 | 
						|
	"golang.org/x/crypto/hkdf"
 | 
						|
)
 | 
						|
 | 
						|
// TODO concurrent WHOAREYOU tie-breaker
 | 
						|
// TODO deal with WHOAREYOU amplification factor (min packet size?)
 | 
						|
// TODO add counter to nonce
 | 
						|
// TODO rehandshake after X packets
 | 
						|
 | 
						|
// Discovery v5 packet types.
 | 
						|
const (
 | 
						|
	p_pingV5 byte = iota + 1
 | 
						|
	p_pongV5
 | 
						|
	p_findnodeV5
 | 
						|
	p_nodesV5
 | 
						|
	p_requestTicketV5
 | 
						|
	p_ticketV5
 | 
						|
	p_regtopicV5
 | 
						|
	p_regconfirmationV5
 | 
						|
	p_topicqueryV5
 | 
						|
	p_unknownV5   = byte(255) // any non-decryptable packet
 | 
						|
	p_whoareyouV5 = byte(254) // the WHOAREYOU packet
 | 
						|
)
 | 
						|
 | 
						|
// Discovery v5 packet structures.
 | 
						|
type (
 | 
						|
	// unknownV5 represents any packet that can't be decrypted.
 | 
						|
	unknownV5 struct {
 | 
						|
		AuthTag []byte
 | 
						|
	}
 | 
						|
 | 
						|
	// WHOAREYOU contains the handshake challenge.
 | 
						|
	whoareyouV5 struct {
 | 
						|
		AuthTag   []byte
 | 
						|
		IDNonce   [32]byte // To be signed by recipient.
 | 
						|
		RecordSeq uint64   // ENR sequence number of recipient
 | 
						|
 | 
						|
		node *enode.Node
 | 
						|
		sent mclock.AbsTime
 | 
						|
	}
 | 
						|
 | 
						|
	// PING is sent during liveness checks.
 | 
						|
	pingV5 struct {
 | 
						|
		ReqID  []byte
 | 
						|
		ENRSeq uint64
 | 
						|
	}
 | 
						|
 | 
						|
	// PONG is the reply to PING.
 | 
						|
	pongV5 struct {
 | 
						|
		ReqID  []byte
 | 
						|
		ENRSeq uint64
 | 
						|
		ToIP   net.IP // These fields should mirror the UDP envelope address of the ping
 | 
						|
		ToPort uint16 // packet, which provides a way to discover the the external address (after NAT).
 | 
						|
	}
 | 
						|
 | 
						|
	// FINDNODE is a query for nodes in the given bucket.
 | 
						|
	findnodeV5 struct {
 | 
						|
		ReqID    []byte
 | 
						|
		Distance uint
 | 
						|
	}
 | 
						|
 | 
						|
	// NODES is the reply to FINDNODE and TOPICQUERY.
 | 
						|
	nodesV5 struct {
 | 
						|
		ReqID []byte
 | 
						|
		Total uint8
 | 
						|
		Nodes []*enr.Record
 | 
						|
	}
 | 
						|
 | 
						|
	// REQUESTTICKET requests a ticket for a topic queue.
 | 
						|
	requestTicketV5 struct {
 | 
						|
		ReqID []byte
 | 
						|
		Topic []byte
 | 
						|
	}
 | 
						|
 | 
						|
	// TICKET is the response to REQUESTTICKET.
 | 
						|
	ticketV5 struct {
 | 
						|
		ReqID  []byte
 | 
						|
		Ticket []byte
 | 
						|
	}
 | 
						|
 | 
						|
	// REGTOPIC registers the sender in a topic queue using a ticket.
 | 
						|
	regtopicV5 struct {
 | 
						|
		ReqID  []byte
 | 
						|
		Ticket []byte
 | 
						|
		ENR    *enr.Record
 | 
						|
	}
 | 
						|
 | 
						|
	// REGCONFIRMATION is the reply to REGTOPIC.
 | 
						|
	regconfirmationV5 struct {
 | 
						|
		ReqID      []byte
 | 
						|
		Registered bool
 | 
						|
	}
 | 
						|
 | 
						|
	// TOPICQUERY asks for nodes with the given topic.
 | 
						|
	topicqueryV5 struct {
 | 
						|
		ReqID []byte
 | 
						|
		Topic []byte
 | 
						|
	}
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	// Encryption/authentication parameters.
 | 
						|
	authSchemeName   = "gcm"
 | 
						|
	aesKeySize       = 16
 | 
						|
	gcmNonceSize     = 12
 | 
						|
	idNoncePrefix    = "discovery-id-nonce"
 | 
						|
	handshakeTimeout = time.Second
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	errTooShort               = errors.New("packet too short")
 | 
						|
	errUnexpectedHandshake    = errors.New("unexpected auth response, not in handshake")
 | 
						|
	errHandshakeNonceMismatch = errors.New("wrong nonce in auth response")
 | 
						|
	errInvalidAuthKey         = errors.New("invalid ephemeral pubkey")
 | 
						|
	errUnknownAuthScheme      = errors.New("unknown auth scheme in handshake")
 | 
						|
	errNoRecord               = errors.New("expected ENR in handshake but none sent")
 | 
						|
	errInvalidNonceSig        = errors.New("invalid ID nonce signature")
 | 
						|
	zeroNonce                 = make([]byte, gcmNonceSize)
 | 
						|
)
 | 
						|
 | 
						|
// wireCodec encodes and decodes discovery v5 packets.
 | 
						|
type wireCodec struct {
 | 
						|
	sha256           hash.Hash
 | 
						|
	localnode        *enode.LocalNode
 | 
						|
	privkey          *ecdsa.PrivateKey
 | 
						|
	myChtagHash      enode.ID
 | 
						|
	myWhoareyouMagic []byte
 | 
						|
 | 
						|
	sc *sessionCache
 | 
						|
}
 | 
						|
 | 
						|
type handshakeSecrets struct {
 | 
						|
	writeKey, readKey, authRespKey []byte
 | 
						|
}
 | 
						|
 | 
						|
type authHeader struct {
 | 
						|
	authHeaderList
 | 
						|
	isHandshake bool
 | 
						|
}
 | 
						|
 | 
						|
type authHeaderList struct {
 | 
						|
	Auth         []byte   // authentication info of packet
 | 
						|
	IDNonce      [32]byte // IDNonce of WHOAREYOU
 | 
						|
	Scheme       string   // name of encryption/authentication scheme
 | 
						|
	EphemeralKey []byte   // ephemeral public key
 | 
						|
	Response     []byte   // encrypted authResponse
 | 
						|
}
 | 
						|
 | 
						|
type authResponse struct {
 | 
						|
	Version   uint
 | 
						|
	Signature []byte
 | 
						|
	Record    *enr.Record `rlp:"nil"` // sender's record
 | 
						|
}
 | 
						|
 | 
						|
func (h *authHeader) DecodeRLP(r *rlp.Stream) error {
 | 
						|
	k, _, err := r.Kind()
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	if k == rlp.Byte || k == rlp.String {
 | 
						|
		return r.Decode(&h.Auth)
 | 
						|
	}
 | 
						|
	h.isHandshake = true
 | 
						|
	return r.Decode(&h.authHeaderList)
 | 
						|
}
 | 
						|
 | 
						|
// ephemeralKey decodes the ephemeral public key in the header.
 | 
						|
func (h *authHeaderList) ephemeralKey(curve elliptic.Curve) *ecdsa.PublicKey {
 | 
						|
	var key encPubkey
 | 
						|
	copy(key[:], h.EphemeralKey)
 | 
						|
	pubkey, _ := decodePubkey(curve, key)
 | 
						|
	return pubkey
 | 
						|
}
 | 
						|
 | 
						|
// newWireCodec creates a wire codec.
 | 
						|
func newWireCodec(ln *enode.LocalNode, key *ecdsa.PrivateKey, clock mclock.Clock) *wireCodec {
 | 
						|
	c := &wireCodec{
 | 
						|
		sha256:    sha256.New(),
 | 
						|
		localnode: ln,
 | 
						|
		privkey:   key,
 | 
						|
		sc:        newSessionCache(1024, clock),
 | 
						|
	}
 | 
						|
	// Create magic strings for packet matching.
 | 
						|
	self := ln.ID()
 | 
						|
	c.myWhoareyouMagic = c.sha256sum(self[:], []byte("WHOAREYOU"))
 | 
						|
	copy(c.myChtagHash[:], c.sha256sum(self[:]))
 | 
						|
	return c
 | 
						|
}
 | 
						|
 | 
						|
// encode encodes a packet to a node. 'id' and 'addr' specify the destination node. The
 | 
						|
// 'challenge' parameter should be the most recently received WHOAREYOU packet from that
 | 
						|
// node.
 | 
						|
func (c *wireCodec) encode(id enode.ID, addr string, packet packetV5, challenge *whoareyouV5) ([]byte, []byte, error) {
 | 
						|
	if packet.kind() == p_whoareyouV5 {
 | 
						|
		p := packet.(*whoareyouV5)
 | 
						|
		enc, err := c.encodeWhoareyou(id, p)
 | 
						|
		if err == nil {
 | 
						|
			c.sc.storeSentHandshake(id, addr, p)
 | 
						|
		}
 | 
						|
		return enc, nil, err
 | 
						|
	}
 | 
						|
	// Ensure calling code sets node if needed.
 | 
						|
	if challenge != nil && challenge.node == nil {
 | 
						|
		panic("BUG: missing challenge.node in encode")
 | 
						|
	}
 | 
						|
	writeKey := c.sc.writeKey(id, addr)
 | 
						|
	if writeKey != nil || challenge != nil {
 | 
						|
		return c.encodeEncrypted(id, addr, packet, writeKey, challenge)
 | 
						|
	}
 | 
						|
	return c.encodeRandom(id)
 | 
						|
}
 | 
						|
 | 
						|
// encodeRandom encodes a random packet.
 | 
						|
func (c *wireCodec) encodeRandom(toID enode.ID) ([]byte, []byte, error) {
 | 
						|
	tag := xorTag(c.sha256sum(toID[:]), c.localnode.ID())
 | 
						|
	r := make([]byte, 44) // TODO randomize size
 | 
						|
	if _, err := crand.Read(r); err != nil {
 | 
						|
		return nil, nil, err
 | 
						|
	}
 | 
						|
	nonce := make([]byte, gcmNonceSize)
 | 
						|
	if _, err := crand.Read(nonce); err != nil {
 | 
						|
		return nil, nil, fmt.Errorf("can't get random data: %v", err)
 | 
						|
	}
 | 
						|
	b := new(bytes.Buffer)
 | 
						|
	b.Write(tag[:])
 | 
						|
	rlp.Encode(b, nonce)
 | 
						|
	b.Write(r)
 | 
						|
	return b.Bytes(), nonce, nil
 | 
						|
}
 | 
						|
 | 
						|
// encodeWhoareyou encodes WHOAREYOU.
 | 
						|
func (c *wireCodec) encodeWhoareyou(toID enode.ID, packet *whoareyouV5) ([]byte, error) {
 | 
						|
	// Sanity check node field to catch misbehaving callers.
 | 
						|
	if packet.RecordSeq > 0 && packet.node == nil {
 | 
						|
		panic("BUG: missing node in whoareyouV5 with non-zero seq")
 | 
						|
	}
 | 
						|
	b := new(bytes.Buffer)
 | 
						|
	b.Write(c.sha256sum(toID[:], []byte("WHOAREYOU")))
 | 
						|
	err := rlp.Encode(b, packet)
 | 
						|
	return b.Bytes(), err
 | 
						|
}
 | 
						|
 | 
						|
// encodeEncrypted encodes an encrypted packet.
 | 
						|
func (c *wireCodec) encodeEncrypted(toID enode.ID, toAddr string, packet packetV5, writeKey []byte, challenge *whoareyouV5) (enc []byte, authTag []byte, err error) {
 | 
						|
	nonce := make([]byte, gcmNonceSize)
 | 
						|
	if _, err := crand.Read(nonce); err != nil {
 | 
						|
		return nil, nil, fmt.Errorf("can't get random data: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	var headEnc []byte
 | 
						|
	if challenge == nil {
 | 
						|
		// Regular packet, use existing key and simply encode nonce.
 | 
						|
		headEnc, _ = rlp.EncodeToBytes(nonce)
 | 
						|
	} else {
 | 
						|
		// We're answering WHOAREYOU, generate new keys and encrypt with those.
 | 
						|
		header, sec, err := c.makeAuthHeader(nonce, challenge)
 | 
						|
		if err != nil {
 | 
						|
			return nil, nil, err
 | 
						|
		}
 | 
						|
		if headEnc, err = rlp.EncodeToBytes(header); err != nil {
 | 
						|
			return nil, nil, err
 | 
						|
		}
 | 
						|
		c.sc.storeNewSession(toID, toAddr, sec.readKey, sec.writeKey)
 | 
						|
		writeKey = sec.writeKey
 | 
						|
	}
 | 
						|
 | 
						|
	// Encode the packet.
 | 
						|
	body := new(bytes.Buffer)
 | 
						|
	body.WriteByte(packet.kind())
 | 
						|
	if err := rlp.Encode(body, packet); err != nil {
 | 
						|
		return nil, nil, err
 | 
						|
	}
 | 
						|
	tag := xorTag(c.sha256sum(toID[:]), c.localnode.ID())
 | 
						|
	headsize := len(tag) + len(headEnc)
 | 
						|
	headbuf := make([]byte, headsize)
 | 
						|
	copy(headbuf[:], tag[:])
 | 
						|
	copy(headbuf[len(tag):], headEnc)
 | 
						|
 | 
						|
	// Encrypt the body.
 | 
						|
	enc, err = encryptGCM(headbuf, writeKey, nonce, body.Bytes(), tag[:])
 | 
						|
	return enc, nonce, err
 | 
						|
}
 | 
						|
 | 
						|
// encodeAuthHeader creates the auth header on a call packet following WHOAREYOU.
 | 
						|
func (c *wireCodec) makeAuthHeader(nonce []byte, challenge *whoareyouV5) (*authHeaderList, *handshakeSecrets, error) {
 | 
						|
	resp := &authResponse{Version: 5}
 | 
						|
 | 
						|
	// Add our record to response if it's newer than what remote
 | 
						|
	// side has.
 | 
						|
	ln := c.localnode.Node()
 | 
						|
	if challenge.RecordSeq < ln.Seq() {
 | 
						|
		resp.Record = ln.Record()
 | 
						|
	}
 | 
						|
 | 
						|
	// Create the ephemeral key. This needs to be first because the
 | 
						|
	// key is part of the ID nonce signature.
 | 
						|
	var remotePubkey = new(ecdsa.PublicKey)
 | 
						|
	if err := challenge.node.Load((*enode.Secp256k1)(remotePubkey)); err != nil {
 | 
						|
		return nil, nil, fmt.Errorf("can't find secp256k1 key for recipient")
 | 
						|
	}
 | 
						|
	ephkey, err := crypto.GenerateKey()
 | 
						|
	if err != nil {
 | 
						|
		return nil, nil, fmt.Errorf("can't generate ephemeral key")
 | 
						|
	}
 | 
						|
	ephpubkey := encodePubkey(&ephkey.PublicKey)
 | 
						|
 | 
						|
	// Add ID nonce signature to response.
 | 
						|
	idsig, err := c.signIDNonce(challenge.IDNonce[:], ephpubkey[:])
 | 
						|
	if err != nil {
 | 
						|
		return nil, nil, fmt.Errorf("can't sign: %v", err)
 | 
						|
	}
 | 
						|
	resp.Signature = idsig
 | 
						|
 | 
						|
	// Create session keys.
 | 
						|
	sec := c.deriveKeys(c.localnode.ID(), challenge.node.ID(), ephkey, remotePubkey, challenge)
 | 
						|
	if sec == nil {
 | 
						|
		return nil, nil, fmt.Errorf("key derivation failed")
 | 
						|
	}
 | 
						|
 | 
						|
	// Encrypt the authentication response and assemble the auth header.
 | 
						|
	respRLP, err := rlp.EncodeToBytes(resp)
 | 
						|
	if err != nil {
 | 
						|
		return nil, nil, fmt.Errorf("can't encode auth response: %v", err)
 | 
						|
	}
 | 
						|
	respEnc, err := encryptGCM(nil, sec.authRespKey, zeroNonce, respRLP, nil)
 | 
						|
	if err != nil {
 | 
						|
		return nil, nil, fmt.Errorf("can't encrypt auth response: %v", err)
 | 
						|
	}
 | 
						|
	head := &authHeaderList{
 | 
						|
		Auth:         nonce,
 | 
						|
		Scheme:       authSchemeName,
 | 
						|
		IDNonce:      challenge.IDNonce,
 | 
						|
		EphemeralKey: ephpubkey[:],
 | 
						|
		Response:     respEnc,
 | 
						|
	}
 | 
						|
	return head, sec, err
 | 
						|
}
 | 
						|
 | 
						|
// deriveKeys generates session keys using elliptic-curve Diffie-Hellman key agreement.
 | 
						|
func (c *wireCodec) deriveKeys(n1, n2 enode.ID, priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey, challenge *whoareyouV5) *handshakeSecrets {
 | 
						|
	eph := ecdh(priv, pub)
 | 
						|
	if eph == nil {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
 | 
						|
	info := []byte("discovery v5 key agreement")
 | 
						|
	info = append(info, n1[:]...)
 | 
						|
	info = append(info, n2[:]...)
 | 
						|
	kdf := hkdf.New(c.sha256reset, eph, challenge.IDNonce[:], info)
 | 
						|
	sec := handshakeSecrets{
 | 
						|
		writeKey:    make([]byte, aesKeySize),
 | 
						|
		readKey:     make([]byte, aesKeySize),
 | 
						|
		authRespKey: make([]byte, aesKeySize),
 | 
						|
	}
 | 
						|
	kdf.Read(sec.writeKey)
 | 
						|
	kdf.Read(sec.readKey)
 | 
						|
	kdf.Read(sec.authRespKey)
 | 
						|
	for i := range eph {
 | 
						|
		eph[i] = 0
 | 
						|
	}
 | 
						|
	return &sec
 | 
						|
}
 | 
						|
 | 
						|
// signIDNonce creates the ID nonce signature.
 | 
						|
func (c *wireCodec) signIDNonce(nonce, ephkey []byte) ([]byte, error) {
 | 
						|
	idsig, err := crypto.Sign(c.idNonceHash(nonce, ephkey), c.privkey)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("can't sign: %v", err)
 | 
						|
	}
 | 
						|
	return idsig[:len(idsig)-1], nil // remove recovery ID
 | 
						|
}
 | 
						|
 | 
						|
// idNonceHash computes the hash of id nonce with prefix.
 | 
						|
func (c *wireCodec) idNonceHash(nonce, ephkey []byte) []byte {
 | 
						|
	h := c.sha256reset()
 | 
						|
	h.Write([]byte(idNoncePrefix))
 | 
						|
	h.Write(nonce)
 | 
						|
	h.Write(ephkey)
 | 
						|
	return h.Sum(nil)
 | 
						|
}
 | 
						|
 | 
						|
// decode decodes a discovery packet.
 | 
						|
func (c *wireCodec) decode(input []byte, addr string) (enode.ID, *enode.Node, packetV5, error) {
 | 
						|
	// Delete timed-out handshakes. This must happen before decoding to avoid
 | 
						|
	// processing the same handshake twice.
 | 
						|
	c.sc.handshakeGC()
 | 
						|
 | 
						|
	if len(input) < 32 {
 | 
						|
		return enode.ID{}, nil, nil, errTooShort
 | 
						|
	}
 | 
						|
	if bytes.HasPrefix(input, c.myWhoareyouMagic) {
 | 
						|
		p, err := c.decodeWhoareyou(input)
 | 
						|
		return enode.ID{}, nil, p, err
 | 
						|
	}
 | 
						|
	sender := xorTag(input[:32], c.myChtagHash)
 | 
						|
	p, n, err := c.decodeEncrypted(sender, addr, input)
 | 
						|
	return sender, n, p, err
 | 
						|
}
 | 
						|
 | 
						|
// decodeWhoareyou decode a WHOAREYOU packet.
 | 
						|
func (c *wireCodec) decodeWhoareyou(input []byte) (packetV5, error) {
 | 
						|
	packet := new(whoareyouV5)
 | 
						|
	err := rlp.DecodeBytes(input[32:], packet)
 | 
						|
	return packet, err
 | 
						|
}
 | 
						|
 | 
						|
// decodeEncrypted decodes an encrypted discovery packet.
 | 
						|
func (c *wireCodec) decodeEncrypted(fromID enode.ID, fromAddr string, input []byte) (packetV5, *enode.Node, error) {
 | 
						|
	// Decode packet header.
 | 
						|
	var head authHeader
 | 
						|
	r := bytes.NewReader(input[32:])
 | 
						|
	err := rlp.Decode(r, &head)
 | 
						|
	if err != nil {
 | 
						|
		return nil, nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Decrypt and process auth response.
 | 
						|
	readKey, node, err := c.decodeAuth(fromID, fromAddr, &head)
 | 
						|
	if err != nil {
 | 
						|
		return nil, nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Decrypt and decode the packet body.
 | 
						|
	headsize := len(input) - r.Len()
 | 
						|
	bodyEnc := input[headsize:]
 | 
						|
	body, err := decryptGCM(readKey, head.Auth, bodyEnc, input[:32])
 | 
						|
	if err != nil {
 | 
						|
		if !head.isHandshake {
 | 
						|
			// Can't decrypt, start handshake.
 | 
						|
			return &unknownV5{AuthTag: head.Auth}, nil, nil
 | 
						|
		}
 | 
						|
		return nil, nil, fmt.Errorf("handshake failed: %v", err)
 | 
						|
	}
 | 
						|
	if len(body) == 0 {
 | 
						|
		return nil, nil, errTooShort
 | 
						|
	}
 | 
						|
	p, err := decodePacketBodyV5(body[0], body[1:])
 | 
						|
	return p, node, err
 | 
						|
}
 | 
						|
 | 
						|
// decodeAuth processes an auth header.
 | 
						|
func (c *wireCodec) decodeAuth(fromID enode.ID, fromAddr string, head *authHeader) ([]byte, *enode.Node, error) {
 | 
						|
	if !head.isHandshake {
 | 
						|
		return c.sc.readKey(fromID, fromAddr), nil, nil
 | 
						|
	}
 | 
						|
 | 
						|
	// Remote is attempting handshake. Verify against our last WHOAREYOU.
 | 
						|
	challenge := c.sc.getHandshake(fromID, fromAddr)
 | 
						|
	if challenge == nil {
 | 
						|
		return nil, nil, errUnexpectedHandshake
 | 
						|
	}
 | 
						|
	if head.IDNonce != challenge.IDNonce {
 | 
						|
		return nil, nil, errHandshakeNonceMismatch
 | 
						|
	}
 | 
						|
	sec, n, err := c.decodeAuthResp(fromID, fromAddr, &head.authHeaderList, challenge)
 | 
						|
	if err != nil {
 | 
						|
		return nil, n, err
 | 
						|
	}
 | 
						|
	// Swap keys to match remote.
 | 
						|
	sec.readKey, sec.writeKey = sec.writeKey, sec.readKey
 | 
						|
	c.sc.storeNewSession(fromID, fromAddr, sec.readKey, sec.writeKey)
 | 
						|
	c.sc.deleteHandshake(fromID, fromAddr)
 | 
						|
	return sec.readKey, n, err
 | 
						|
}
 | 
						|
 | 
						|
// decodeAuthResp decodes and verifies an authentication response.
 | 
						|
func (c *wireCodec) decodeAuthResp(fromID enode.ID, fromAddr string, head *authHeaderList, challenge *whoareyouV5) (*handshakeSecrets, *enode.Node, error) {
 | 
						|
	// Decrypt / decode the response.
 | 
						|
	if head.Scheme != authSchemeName {
 | 
						|
		return nil, nil, errUnknownAuthScheme
 | 
						|
	}
 | 
						|
	ephkey := head.ephemeralKey(c.privkey.Curve)
 | 
						|
	if ephkey == nil {
 | 
						|
		return nil, nil, errInvalidAuthKey
 | 
						|
	}
 | 
						|
	sec := c.deriveKeys(fromID, c.localnode.ID(), c.privkey, ephkey, challenge)
 | 
						|
	respPT, err := decryptGCM(sec.authRespKey, zeroNonce, head.Response, nil)
 | 
						|
	if err != nil {
 | 
						|
		return nil, nil, fmt.Errorf("can't decrypt auth response header: %v", err)
 | 
						|
	}
 | 
						|
	var resp authResponse
 | 
						|
	if err := rlp.DecodeBytes(respPT, &resp); err != nil {
 | 
						|
		return nil, nil, fmt.Errorf("invalid auth response: %v", err)
 | 
						|
	}
 | 
						|
 | 
						|
	// Verify response node record. The remote node should include the record
 | 
						|
	// if we don't have one or if ours is older than the latest version.
 | 
						|
	node := challenge.node
 | 
						|
	if resp.Record != nil {
 | 
						|
		if node == nil || node.Seq() < resp.Record.Seq() {
 | 
						|
			n, err := enode.New(enode.ValidSchemes, resp.Record)
 | 
						|
			if err != nil {
 | 
						|
				return nil, nil, fmt.Errorf("invalid node record: %v", err)
 | 
						|
			}
 | 
						|
			if n.ID() != fromID {
 | 
						|
				return nil, nil, fmt.Errorf("record in auth respose has wrong ID: %v", n.ID())
 | 
						|
			}
 | 
						|
			node = n
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if node == nil {
 | 
						|
		return nil, nil, errNoRecord
 | 
						|
	}
 | 
						|
 | 
						|
	// Verify ID nonce signature.
 | 
						|
	err = c.verifyIDSignature(challenge.IDNonce[:], head.EphemeralKey, resp.Signature, node)
 | 
						|
	if err != nil {
 | 
						|
		return nil, nil, err
 | 
						|
	}
 | 
						|
	return sec, node, nil
 | 
						|
}
 | 
						|
 | 
						|
// verifyIDSignature checks that signature over idnonce was made by the node with given record.
 | 
						|
func (c *wireCodec) verifyIDSignature(nonce, ephkey, sig []byte, n *enode.Node) error {
 | 
						|
	switch idscheme := n.Record().IdentityScheme(); idscheme {
 | 
						|
	case "v4":
 | 
						|
		var pk ecdsa.PublicKey
 | 
						|
		n.Load((*enode.Secp256k1)(&pk)) // cannot fail because record is valid
 | 
						|
		if !crypto.VerifySignature(crypto.FromECDSAPub(&pk), c.idNonceHash(nonce, ephkey), sig) {
 | 
						|
			return errInvalidNonceSig
 | 
						|
		}
 | 
						|
		return nil
 | 
						|
	default:
 | 
						|
		return fmt.Errorf("can't verify ID nonce signature against scheme %q", idscheme)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// decodePacketBody decodes the body of an encrypted discovery packet.
 | 
						|
func decodePacketBodyV5(ptype byte, body []byte) (packetV5, error) {
 | 
						|
	var dec packetV5
 | 
						|
	switch ptype {
 | 
						|
	case p_pingV5:
 | 
						|
		dec = new(pingV5)
 | 
						|
	case p_pongV5:
 | 
						|
		dec = new(pongV5)
 | 
						|
	case p_findnodeV5:
 | 
						|
		dec = new(findnodeV5)
 | 
						|
	case p_nodesV5:
 | 
						|
		dec = new(nodesV5)
 | 
						|
	case p_requestTicketV5:
 | 
						|
		dec = new(requestTicketV5)
 | 
						|
	case p_ticketV5:
 | 
						|
		dec = new(ticketV5)
 | 
						|
	case p_regtopicV5:
 | 
						|
		dec = new(regtopicV5)
 | 
						|
	case p_regconfirmationV5:
 | 
						|
		dec = new(regconfirmationV5)
 | 
						|
	case p_topicqueryV5:
 | 
						|
		dec = new(topicqueryV5)
 | 
						|
	default:
 | 
						|
		return nil, fmt.Errorf("unknown packet type %d", ptype)
 | 
						|
	}
 | 
						|
	if err := rlp.DecodeBytes(body, dec); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return dec, nil
 | 
						|
}
 | 
						|
 | 
						|
// sha256reset returns the shared hash instance.
 | 
						|
func (c *wireCodec) sha256reset() hash.Hash {
 | 
						|
	c.sha256.Reset()
 | 
						|
	return c.sha256
 | 
						|
}
 | 
						|
 | 
						|
// sha256sum computes sha256 on the concatenation of inputs.
 | 
						|
func (c *wireCodec) sha256sum(inputs ...[]byte) []byte {
 | 
						|
	c.sha256.Reset()
 | 
						|
	for _, b := range inputs {
 | 
						|
		c.sha256.Write(b)
 | 
						|
	}
 | 
						|
	return c.sha256.Sum(nil)
 | 
						|
}
 | 
						|
 | 
						|
func xorTag(a []byte, b enode.ID) enode.ID {
 | 
						|
	var r enode.ID
 | 
						|
	for i := range r {
 | 
						|
		r[i] = a[i] ^ b[i]
 | 
						|
	}
 | 
						|
	return r
 | 
						|
}
 | 
						|
 | 
						|
// ecdh creates a shared secret.
 | 
						|
func ecdh(privkey *ecdsa.PrivateKey, pubkey *ecdsa.PublicKey) []byte {
 | 
						|
	secX, secY := pubkey.ScalarMult(pubkey.X, pubkey.Y, privkey.D.Bytes())
 | 
						|
	if secX == nil {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	sec := make([]byte, 33)
 | 
						|
	sec[0] = 0x02 | byte(secY.Bit(0))
 | 
						|
	math.ReadBits(secX, sec[1:])
 | 
						|
	return sec
 | 
						|
}
 | 
						|
 | 
						|
// encryptGCM encrypts pt using AES-GCM with the given key and nonce.
 | 
						|
func encryptGCM(dest, key, nonce, pt, authData []byte) ([]byte, error) {
 | 
						|
	block, err := aes.NewCipher(key)
 | 
						|
	if err != nil {
 | 
						|
		panic(fmt.Errorf("can't create block cipher: %v", err))
 | 
						|
	}
 | 
						|
	aesgcm, err := cipher.NewGCMWithNonceSize(block, gcmNonceSize)
 | 
						|
	if err != nil {
 | 
						|
		panic(fmt.Errorf("can't create GCM: %v", err))
 | 
						|
	}
 | 
						|
	return aesgcm.Seal(dest, nonce, pt, authData), nil
 | 
						|
}
 | 
						|
 | 
						|
// decryptGCM decrypts ct using AES-GCM with the given key and nonce.
 | 
						|
func decryptGCM(key, nonce, ct, authData []byte) ([]byte, error) {
 | 
						|
	block, err := aes.NewCipher(key)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("can't create block cipher: %v", err)
 | 
						|
	}
 | 
						|
	if len(nonce) != gcmNonceSize {
 | 
						|
		return nil, fmt.Errorf("invalid GCM nonce size: %d", len(nonce))
 | 
						|
	}
 | 
						|
	aesgcm, err := cipher.NewGCMWithNonceSize(block, gcmNonceSize)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("can't create GCM: %v", err)
 | 
						|
	}
 | 
						|
	pt := make([]byte, 0, len(ct))
 | 
						|
	return aesgcm.Open(pt, nonce, ct, authData)
 | 
						|
}
 |