2022-05-24 13:39:40 -05:00
|
|
|
// Copyright 2020 The go-ethereum Authors
|
2020-10-14 05:28:17 -05:00
|
|
|
// 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 v5wire
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"crypto/aes"
|
|
|
|
"crypto/cipher"
|
|
|
|
"crypto/ecdsa"
|
|
|
|
crand "crypto/rand"
|
|
|
|
"crypto/sha256"
|
|
|
|
"encoding/binary"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"hash"
|
|
|
|
|
|
|
|
"github.com/ethereum/go-ethereum/common/mclock"
|
|
|
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
|
|
|
"github.com/ethereum/go-ethereum/p2p/enr"
|
|
|
|
"github.com/ethereum/go-ethereum/rlp"
|
|
|
|
)
|
|
|
|
|
|
|
|
// TODO concurrent WHOAREYOU tie-breaker
|
|
|
|
// TODO rehandshake after X packets
|
|
|
|
|
|
|
|
// Header represents a packet header.
|
|
|
|
type Header struct {
|
|
|
|
IV [sizeofMaskingIV]byte
|
|
|
|
StaticHeader
|
|
|
|
AuthData []byte
|
|
|
|
|
|
|
|
src enode.ID // used by decoder
|
|
|
|
}
|
|
|
|
|
|
|
|
// StaticHeader contains the static fields of a packet header.
|
|
|
|
type StaticHeader struct {
|
|
|
|
ProtocolID [6]byte
|
|
|
|
Version uint16
|
|
|
|
Flag byte
|
|
|
|
Nonce Nonce
|
|
|
|
AuthSize uint16
|
|
|
|
}
|
|
|
|
|
|
|
|
// Authdata layouts.
|
|
|
|
type (
|
|
|
|
whoareyouAuthData struct {
|
|
|
|
IDNonce [16]byte // ID proof data
|
|
|
|
RecordSeq uint64 // highest known ENR sequence of requester
|
|
|
|
}
|
|
|
|
|
|
|
|
handshakeAuthData struct {
|
|
|
|
h struct {
|
|
|
|
SrcID enode.ID
|
|
|
|
SigSize byte // ignature data
|
|
|
|
PubkeySize byte // offset of
|
|
|
|
}
|
|
|
|
// Trailing variable-size data.
|
|
|
|
signature, pubkey, record []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
messageAuthData struct {
|
|
|
|
SrcID enode.ID
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
// Packet header flag values.
|
|
|
|
const (
|
|
|
|
flagMessage = iota
|
|
|
|
flagWhoareyou
|
|
|
|
flagHandshake
|
|
|
|
)
|
|
|
|
|
|
|
|
// Protocol constants.
|
|
|
|
const (
|
|
|
|
version = 1
|
|
|
|
minVersion = 1
|
|
|
|
sizeofMaskingIV = 16
|
|
|
|
|
|
|
|
minMessageSize = 48 // this refers to data after static headers
|
|
|
|
randomPacketMsgSize = 20
|
|
|
|
)
|
|
|
|
|
|
|
|
var protocolID = [6]byte{'d', 'i', 's', 'c', 'v', '5'}
|
|
|
|
|
|
|
|
// Errors.
|
|
|
|
var (
|
|
|
|
errTooShort = errors.New("packet too short")
|
|
|
|
errInvalidHeader = errors.New("invalid packet header")
|
|
|
|
errInvalidFlag = errors.New("invalid flag value in header")
|
|
|
|
errMinVersion = errors.New("version of packet header below minimum")
|
|
|
|
errMsgTooShort = errors.New("message/handshake packet below minimum size")
|
|
|
|
errAuthSize = errors.New("declared auth size is beyond packet length")
|
|
|
|
errUnexpectedHandshake = errors.New("unexpected auth response, not in handshake")
|
|
|
|
errInvalidAuthKey = errors.New("invalid ephemeral pubkey")
|
|
|
|
errNoRecord = errors.New("expected ENR in handshake but none sent")
|
|
|
|
errInvalidNonceSig = errors.New("invalid ID nonce signature")
|
|
|
|
errMessageTooShort = errors.New("message contains no data")
|
|
|
|
errMessageDecrypt = errors.New("cannot decrypt message")
|
|
|
|
)
|
|
|
|
|
|
|
|
// Public errors.
|
|
|
|
var (
|
|
|
|
ErrInvalidReqID = errors.New("request ID larger than 8 bytes")
|
|
|
|
)
|
|
|
|
|
|
|
|
// Packet sizes.
|
|
|
|
var (
|
|
|
|
sizeofStaticHeader = binary.Size(StaticHeader{})
|
|
|
|
sizeofWhoareyouAuthData = binary.Size(whoareyouAuthData{})
|
|
|
|
sizeofHandshakeAuthData = binary.Size(handshakeAuthData{}.h)
|
|
|
|
sizeofMessageAuthData = binary.Size(messageAuthData{})
|
|
|
|
sizeofStaticPacketData = sizeofMaskingIV + sizeofStaticHeader
|
|
|
|
)
|
|
|
|
|
|
|
|
// Codec encodes and decodes Discovery v5 packets.
|
|
|
|
// This type is not safe for concurrent use.
|
|
|
|
type Codec struct {
|
|
|
|
sha256 hash.Hash
|
|
|
|
localnode *enode.LocalNode
|
|
|
|
privkey *ecdsa.PrivateKey
|
|
|
|
sc *SessionCache
|
|
|
|
|
|
|
|
// encoder buffers
|
|
|
|
buf bytes.Buffer // whole packet
|
|
|
|
headbuf bytes.Buffer // packet header
|
|
|
|
msgbuf bytes.Buffer // message RLP plaintext
|
|
|
|
msgctbuf []byte // message data ciphertext
|
|
|
|
|
|
|
|
// decoder buffer
|
|
|
|
reader bytes.Reader
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewCodec creates a wire codec.
|
|
|
|
func NewCodec(ln *enode.LocalNode, key *ecdsa.PrivateKey, clock mclock.Clock) *Codec {
|
|
|
|
c := &Codec{
|
|
|
|
sha256: sha256.New(),
|
|
|
|
localnode: ln,
|
|
|
|
privkey: key,
|
|
|
|
sc: NewSessionCache(1024, clock),
|
|
|
|
}
|
|
|
|
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 *Codec) Encode(id enode.ID, addr string, packet Packet, challenge *Whoareyou) ([]byte, Nonce, error) {
|
|
|
|
// Create the packet header.
|
|
|
|
var (
|
|
|
|
head Header
|
|
|
|
session *session
|
|
|
|
msgData []byte
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
switch {
|
|
|
|
case packet.Kind() == WhoareyouPacket:
|
|
|
|
head, err = c.encodeWhoareyou(id, packet.(*Whoareyou))
|
|
|
|
case challenge != nil:
|
|
|
|
// We have an unanswered challenge, send handshake.
|
|
|
|
head, session, err = c.encodeHandshakeHeader(id, addr, challenge)
|
|
|
|
default:
|
|
|
|
session = c.sc.session(id, addr)
|
|
|
|
if session != nil {
|
|
|
|
// There is a session, use it.
|
|
|
|
head, err = c.encodeMessageHeader(id, session)
|
|
|
|
} else {
|
|
|
|
// No keys, send random data to kick off the handshake.
|
|
|
|
head, msgData, err = c.encodeRandom(id)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return nil, Nonce{}, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate masking IV.
|
|
|
|
if err := c.sc.maskingIVGen(head.IV[:]); err != nil {
|
|
|
|
return nil, Nonce{}, fmt.Errorf("can't generate masking IV: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Encode header data.
|
|
|
|
c.writeHeaders(&head)
|
|
|
|
|
|
|
|
// Store sent WHOAREYOU challenges.
|
|
|
|
if challenge, ok := packet.(*Whoareyou); ok {
|
|
|
|
challenge.ChallengeData = bytesCopy(&c.buf)
|
|
|
|
c.sc.storeSentHandshake(id, addr, challenge)
|
|
|
|
} else if msgData == nil {
|
|
|
|
headerData := c.buf.Bytes()
|
|
|
|
msgData, err = c.encryptMessage(session, packet, &head, headerData)
|
|
|
|
if err != nil {
|
|
|
|
return nil, Nonce{}, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
enc, err := c.EncodeRaw(id, head, msgData)
|
|
|
|
return enc, head.Nonce, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// EncodeRaw encodes a packet with the given header.
|
|
|
|
func (c *Codec) EncodeRaw(id enode.ID, head Header, msgdata []byte) ([]byte, error) {
|
|
|
|
c.writeHeaders(&head)
|
|
|
|
|
|
|
|
// Apply masking.
|
|
|
|
masked := c.buf.Bytes()[sizeofMaskingIV:]
|
|
|
|
mask := head.mask(id)
|
|
|
|
mask.XORKeyStream(masked[:], masked[:])
|
|
|
|
|
|
|
|
// Write message data.
|
|
|
|
c.buf.Write(msgdata)
|
|
|
|
return c.buf.Bytes(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Codec) writeHeaders(head *Header) {
|
|
|
|
c.buf.Reset()
|
|
|
|
c.buf.Write(head.IV[:])
|
|
|
|
binary.Write(&c.buf, binary.BigEndian, &head.StaticHeader)
|
|
|
|
c.buf.Write(head.AuthData)
|
|
|
|
}
|
|
|
|
|
|
|
|
// makeHeader creates a packet header.
|
|
|
|
func (c *Codec) makeHeader(toID enode.ID, flag byte, authsizeExtra int) Header {
|
|
|
|
var authsize int
|
|
|
|
switch flag {
|
|
|
|
case flagMessage:
|
|
|
|
authsize = sizeofMessageAuthData
|
|
|
|
case flagWhoareyou:
|
|
|
|
authsize = sizeofWhoareyouAuthData
|
|
|
|
case flagHandshake:
|
|
|
|
authsize = sizeofHandshakeAuthData
|
|
|
|
default:
|
|
|
|
panic(fmt.Errorf("BUG: invalid packet header flag %x", flag))
|
|
|
|
}
|
|
|
|
authsize += authsizeExtra
|
|
|
|
if authsize > int(^uint16(0)) {
|
|
|
|
panic(fmt.Errorf("BUG: auth size %d overflows uint16", authsize))
|
|
|
|
}
|
|
|
|
return Header{
|
|
|
|
StaticHeader: StaticHeader{
|
|
|
|
ProtocolID: protocolID,
|
|
|
|
Version: version,
|
|
|
|
Flag: flag,
|
|
|
|
AuthSize: uint16(authsize),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// encodeRandom encodes a packet with random content.
|
|
|
|
func (c *Codec) encodeRandom(toID enode.ID) (Header, []byte, error) {
|
|
|
|
head := c.makeHeader(toID, flagMessage, 0)
|
|
|
|
|
|
|
|
// Encode auth data.
|
|
|
|
auth := messageAuthData{SrcID: c.localnode.ID()}
|
|
|
|
if _, err := crand.Read(head.Nonce[:]); err != nil {
|
|
|
|
return head, nil, fmt.Errorf("can't get random data: %v", err)
|
|
|
|
}
|
|
|
|
c.headbuf.Reset()
|
|
|
|
binary.Write(&c.headbuf, binary.BigEndian, auth)
|
|
|
|
head.AuthData = c.headbuf.Bytes()
|
|
|
|
|
|
|
|
// Fill message ciphertext buffer with random bytes.
|
|
|
|
c.msgctbuf = append(c.msgctbuf[:0], make([]byte, randomPacketMsgSize)...)
|
|
|
|
crand.Read(c.msgctbuf)
|
|
|
|
return head, c.msgctbuf, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// encodeWhoareyou encodes a WHOAREYOU packet.
|
|
|
|
func (c *Codec) encodeWhoareyou(toID enode.ID, packet *Whoareyou) (Header, error) {
|
|
|
|
// Sanity check node field to catch misbehaving callers.
|
|
|
|
if packet.RecordSeq > 0 && packet.Node == nil {
|
|
|
|
panic("BUG: missing node in whoareyou with non-zero seq")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create header.
|
|
|
|
head := c.makeHeader(toID, flagWhoareyou, 0)
|
|
|
|
head.AuthData = bytesCopy(&c.buf)
|
|
|
|
head.Nonce = packet.Nonce
|
|
|
|
|
|
|
|
// Encode auth data.
|
|
|
|
auth := &whoareyouAuthData{
|
|
|
|
IDNonce: packet.IDNonce,
|
|
|
|
RecordSeq: packet.RecordSeq,
|
|
|
|
}
|
|
|
|
c.headbuf.Reset()
|
|
|
|
binary.Write(&c.headbuf, binary.BigEndian, auth)
|
|
|
|
head.AuthData = c.headbuf.Bytes()
|
|
|
|
return head, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// encodeHandshakeMessage encodes the handshake message packet header.
|
|
|
|
func (c *Codec) encodeHandshakeHeader(toID enode.ID, addr string, challenge *Whoareyou) (Header, *session, error) {
|
|
|
|
// Ensure calling code sets challenge.node.
|
|
|
|
if challenge.Node == nil {
|
|
|
|
panic("BUG: missing challenge.Node in encode")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate new secrets.
|
|
|
|
auth, session, err := c.makeHandshakeAuth(toID, addr, challenge)
|
|
|
|
if err != nil {
|
|
|
|
return Header{}, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate nonce for message.
|
|
|
|
nonce, err := c.sc.nextNonce(session)
|
|
|
|
if err != nil {
|
|
|
|
return Header{}, nil, fmt.Errorf("can't generate nonce: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: this should happen when the first authenticated message is received
|
|
|
|
c.sc.storeNewSession(toID, addr, session)
|
|
|
|
|
|
|
|
// Encode the auth header.
|
|
|
|
var (
|
|
|
|
authsizeExtra = len(auth.pubkey) + len(auth.signature) + len(auth.record)
|
|
|
|
head = c.makeHeader(toID, flagHandshake, authsizeExtra)
|
|
|
|
)
|
|
|
|
c.headbuf.Reset()
|
|
|
|
binary.Write(&c.headbuf, binary.BigEndian, &auth.h)
|
|
|
|
c.headbuf.Write(auth.signature)
|
|
|
|
c.headbuf.Write(auth.pubkey)
|
|
|
|
c.headbuf.Write(auth.record)
|
|
|
|
head.AuthData = c.headbuf.Bytes()
|
|
|
|
head.Nonce = nonce
|
|
|
|
return head, session, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// encodeAuthHeader creates the auth header on a request packet following WHOAREYOU.
|
|
|
|
func (c *Codec) makeHandshakeAuth(toID enode.ID, addr string, challenge *Whoareyou) (*handshakeAuthData, *session, error) {
|
|
|
|
auth := new(handshakeAuthData)
|
|
|
|
auth.h.SrcID = c.localnode.ID()
|
|
|
|
|
|
|
|
// 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 := c.sc.ephemeralKeyGen()
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, fmt.Errorf("can't generate ephemeral key")
|
|
|
|
}
|
|
|
|
ephpubkey := EncodePubkey(&ephkey.PublicKey)
|
|
|
|
auth.pubkey = ephpubkey[:]
|
|
|
|
auth.h.PubkeySize = byte(len(auth.pubkey))
|
|
|
|
|
|
|
|
// Add ID nonce signature to response.
|
|
|
|
cdata := challenge.ChallengeData
|
|
|
|
idsig, err := makeIDSignature(c.sha256, c.privkey, cdata, ephpubkey[:], toID)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, fmt.Errorf("can't sign: %v", err)
|
|
|
|
}
|
|
|
|
auth.signature = idsig
|
|
|
|
auth.h.SigSize = byte(len(auth.signature))
|
|
|
|
|
|
|
|
// Add our record to response if it's newer than what remote side has.
|
|
|
|
ln := c.localnode.Node()
|
|
|
|
if challenge.RecordSeq < ln.Seq() {
|
|
|
|
auth.record, _ = rlp.EncodeToBytes(ln.Record())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create session keys.
|
|
|
|
sec := deriveKeys(sha256.New, ephkey, remotePubkey, c.localnode.ID(), challenge.Node.ID(), cdata)
|
|
|
|
if sec == nil {
|
|
|
|
return nil, nil, fmt.Errorf("key derivation failed")
|
|
|
|
}
|
|
|
|
return auth, sec, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// encodeMessage encodes an encrypted message packet.
|
|
|
|
func (c *Codec) encodeMessageHeader(toID enode.ID, s *session) (Header, error) {
|
|
|
|
head := c.makeHeader(toID, flagMessage, 0)
|
|
|
|
|
|
|
|
// Create the header.
|
|
|
|
nonce, err := c.sc.nextNonce(s)
|
|
|
|
if err != nil {
|
|
|
|
return Header{}, fmt.Errorf("can't generate nonce: %v", err)
|
|
|
|
}
|
|
|
|
auth := messageAuthData{SrcID: c.localnode.ID()}
|
|
|
|
c.buf.Reset()
|
|
|
|
binary.Write(&c.buf, binary.BigEndian, &auth)
|
|
|
|
head.AuthData = bytesCopy(&c.buf)
|
|
|
|
head.Nonce = nonce
|
|
|
|
return head, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Codec) encryptMessage(s *session, p Packet, head *Header, headerData []byte) ([]byte, error) {
|
|
|
|
// Encode message plaintext.
|
|
|
|
c.msgbuf.Reset()
|
|
|
|
c.msgbuf.WriteByte(p.Kind())
|
|
|
|
if err := rlp.Encode(&c.msgbuf, p); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
messagePT := c.msgbuf.Bytes()
|
|
|
|
|
|
|
|
// Encrypt into message ciphertext buffer.
|
|
|
|
messageCT, err := encryptGCM(c.msgctbuf[:0], s.writeKey, head.Nonce[:], messagePT, headerData)
|
|
|
|
if err == nil {
|
|
|
|
c.msgctbuf = messageCT
|
|
|
|
}
|
|
|
|
return messageCT, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Decode decodes a discovery packet.
|
|
|
|
func (c *Codec) Decode(input []byte, addr string) (src enode.ID, n *enode.Node, p Packet, err error) {
|
|
|
|
// Unmask the static header.
|
|
|
|
if len(input) < sizeofStaticPacketData {
|
|
|
|
return enode.ID{}, nil, nil, errTooShort
|
|
|
|
}
|
|
|
|
var head Header
|
|
|
|
copy(head.IV[:], input[:sizeofMaskingIV])
|
|
|
|
mask := head.mask(c.localnode.ID())
|
|
|
|
staticHeader := input[sizeofMaskingIV:sizeofStaticPacketData]
|
|
|
|
mask.XORKeyStream(staticHeader, staticHeader)
|
|
|
|
|
|
|
|
// Decode and verify the static header.
|
|
|
|
c.reader.Reset(staticHeader)
|
|
|
|
binary.Read(&c.reader, binary.BigEndian, &head.StaticHeader)
|
|
|
|
remainingInput := len(input) - sizeofStaticPacketData
|
|
|
|
if err := head.checkValid(remainingInput); err != nil {
|
|
|
|
return enode.ID{}, nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Unmask auth data.
|
|
|
|
authDataEnd := sizeofStaticPacketData + int(head.AuthSize)
|
|
|
|
authData := input[sizeofStaticPacketData:authDataEnd]
|
|
|
|
mask.XORKeyStream(authData, authData)
|
|
|
|
head.AuthData = authData
|
|
|
|
|
|
|
|
// Delete timed-out handshakes. This must happen before decoding to avoid
|
|
|
|
// processing the same handshake twice.
|
|
|
|
c.sc.handshakeGC()
|
|
|
|
|
|
|
|
// Decode auth part and message.
|
|
|
|
headerData := input[:authDataEnd]
|
|
|
|
msgData := input[authDataEnd:]
|
|
|
|
switch head.Flag {
|
|
|
|
case flagWhoareyou:
|
|
|
|
p, err = c.decodeWhoareyou(&head, headerData)
|
|
|
|
case flagHandshake:
|
|
|
|
n, p, err = c.decodeHandshakeMessage(addr, &head, headerData, msgData)
|
|
|
|
case flagMessage:
|
|
|
|
p, err = c.decodeMessage(addr, &head, headerData, msgData)
|
|
|
|
default:
|
|
|
|
err = errInvalidFlag
|
|
|
|
}
|
|
|
|
return head.src, n, p, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// decodeWhoareyou reads packet data after the header as a WHOAREYOU packet.
|
|
|
|
func (c *Codec) decodeWhoareyou(head *Header, headerData []byte) (Packet, error) {
|
|
|
|
if len(head.AuthData) != sizeofWhoareyouAuthData {
|
|
|
|
return nil, fmt.Errorf("invalid auth size %d for WHOAREYOU", len(head.AuthData))
|
|
|
|
}
|
|
|
|
var auth whoareyouAuthData
|
|
|
|
c.reader.Reset(head.AuthData)
|
|
|
|
binary.Read(&c.reader, binary.BigEndian, &auth)
|
|
|
|
p := &Whoareyou{
|
|
|
|
Nonce: head.Nonce,
|
|
|
|
IDNonce: auth.IDNonce,
|
|
|
|
RecordSeq: auth.RecordSeq,
|
|
|
|
ChallengeData: make([]byte, len(headerData)),
|
|
|
|
}
|
|
|
|
copy(p.ChallengeData, headerData)
|
|
|
|
return p, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Codec) decodeHandshakeMessage(fromAddr string, head *Header, headerData, msgData []byte) (n *enode.Node, p Packet, err error) {
|
|
|
|
node, auth, session, err := c.decodeHandshake(fromAddr, head)
|
|
|
|
if err != nil {
|
|
|
|
c.sc.deleteHandshake(auth.h.SrcID, fromAddr)
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Decrypt the message using the new session keys.
|
|
|
|
msg, err := c.decryptMessage(msgData, head.Nonce[:], headerData, session.readKey)
|
|
|
|
if err != nil {
|
|
|
|
c.sc.deleteHandshake(auth.h.SrcID, fromAddr)
|
|
|
|
return node, msg, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handshake OK, drop the challenge and store the new session keys.
|
|
|
|
c.sc.storeNewSession(auth.h.SrcID, fromAddr, session)
|
|
|
|
c.sc.deleteHandshake(auth.h.SrcID, fromAddr)
|
|
|
|
return node, msg, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Codec) decodeHandshake(fromAddr string, head *Header) (n *enode.Node, auth handshakeAuthData, s *session, err error) {
|
|
|
|
if auth, err = c.decodeHandshakeAuthData(head); err != nil {
|
|
|
|
return nil, auth, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Verify against our last WHOAREYOU.
|
|
|
|
challenge := c.sc.getHandshake(auth.h.SrcID, fromAddr)
|
|
|
|
if challenge == nil {
|
|
|
|
return nil, auth, nil, errUnexpectedHandshake
|
|
|
|
}
|
|
|
|
// Get node record.
|
|
|
|
n, err = c.decodeHandshakeRecord(challenge.Node, auth.h.SrcID, auth.record)
|
|
|
|
if err != nil {
|
|
|
|
return nil, auth, nil, err
|
|
|
|
}
|
|
|
|
// Verify ID nonce signature.
|
|
|
|
sig := auth.signature
|
|
|
|
cdata := challenge.ChallengeData
|
|
|
|
err = verifyIDSignature(c.sha256, sig, n, cdata, auth.pubkey, c.localnode.ID())
|
|
|
|
if err != nil {
|
|
|
|
return nil, auth, nil, err
|
|
|
|
}
|
|
|
|
// Verify ephemeral key is on curve.
|
|
|
|
ephkey, err := DecodePubkey(c.privkey.Curve, auth.pubkey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, auth, nil, errInvalidAuthKey
|
|
|
|
}
|
|
|
|
// Derive sesssion keys.
|
|
|
|
session := deriveKeys(sha256.New, c.privkey, ephkey, auth.h.SrcID, c.localnode.ID(), cdata)
|
|
|
|
session = session.keysFlipped()
|
|
|
|
return n, auth, session, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// decodeHandshakeAuthData reads the authdata section of a handshake packet.
|
|
|
|
func (c *Codec) decodeHandshakeAuthData(head *Header) (auth handshakeAuthData, err error) {
|
|
|
|
// Decode fixed size part.
|
|
|
|
if len(head.AuthData) < sizeofHandshakeAuthData {
|
|
|
|
return auth, fmt.Errorf("header authsize %d too low for handshake", head.AuthSize)
|
|
|
|
}
|
|
|
|
c.reader.Reset(head.AuthData)
|
|
|
|
binary.Read(&c.reader, binary.BigEndian, &auth.h)
|
|
|
|
head.src = auth.h.SrcID
|
|
|
|
|
|
|
|
// Decode variable-size part.
|
|
|
|
var (
|
|
|
|
vardata = head.AuthData[sizeofHandshakeAuthData:]
|
|
|
|
sigAndKeySize = int(auth.h.SigSize) + int(auth.h.PubkeySize)
|
|
|
|
keyOffset = int(auth.h.SigSize)
|
|
|
|
recOffset = keyOffset + int(auth.h.PubkeySize)
|
|
|
|
)
|
|
|
|
if len(vardata) < sigAndKeySize {
|
|
|
|
return auth, errTooShort
|
|
|
|
}
|
|
|
|
auth.signature = vardata[:keyOffset]
|
|
|
|
auth.pubkey = vardata[keyOffset:recOffset]
|
|
|
|
auth.record = vardata[recOffset:]
|
|
|
|
return auth, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// decodeHandshakeRecord verifies the node record contained in a handshake packet. The
|
|
|
|
// remote node should include the record if we don't have one or if ours is older than the
|
|
|
|
// latest sequence number.
|
|
|
|
func (c *Codec) decodeHandshakeRecord(local *enode.Node, wantID enode.ID, remote []byte) (*enode.Node, error) {
|
|
|
|
node := local
|
|
|
|
if len(remote) > 0 {
|
|
|
|
var record enr.Record
|
|
|
|
if err := rlp.DecodeBytes(remote, &record); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if local == nil || local.Seq() < record.Seq() {
|
|
|
|
n, err := enode.New(enode.ValidSchemes, &record)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("invalid node record: %v", err)
|
|
|
|
}
|
|
|
|
if n.ID() != wantID {
|
|
|
|
return nil, fmt.Errorf("record in handshake has wrong ID: %v", n.ID())
|
|
|
|
}
|
|
|
|
node = n
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if node == nil {
|
|
|
|
return nil, errNoRecord
|
|
|
|
}
|
|
|
|
return node, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// decodeMessage reads packet data following the header as an ordinary message packet.
|
|
|
|
func (c *Codec) decodeMessage(fromAddr string, head *Header, headerData, msgData []byte) (Packet, error) {
|
|
|
|
if len(head.AuthData) != sizeofMessageAuthData {
|
|
|
|
return nil, fmt.Errorf("invalid auth size %d for message packet", len(head.AuthData))
|
|
|
|
}
|
|
|
|
var auth messageAuthData
|
|
|
|
c.reader.Reset(head.AuthData)
|
|
|
|
binary.Read(&c.reader, binary.BigEndian, &auth)
|
|
|
|
head.src = auth.SrcID
|
|
|
|
|
|
|
|
// Try decrypting the message.
|
|
|
|
key := c.sc.readKey(auth.SrcID, fromAddr)
|
|
|
|
msg, err := c.decryptMessage(msgData, head.Nonce[:], headerData, key)
|
2022-06-07 10:27:21 -05:00
|
|
|
if errors.Is(err, errMessageDecrypt) {
|
2020-10-14 05:28:17 -05:00
|
|
|
// It didn't work. Start the handshake since this is an ordinary message packet.
|
|
|
|
return &Unknown{Nonce: head.Nonce}, nil
|
|
|
|
}
|
|
|
|
return msg, err
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Codec) decryptMessage(input, nonce, headerData, readKey []byte) (Packet, error) {
|
|
|
|
msgdata, err := decryptGCM(readKey, nonce, input, headerData)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errMessageDecrypt
|
|
|
|
}
|
|
|
|
if len(msgdata) == 0 {
|
|
|
|
return nil, errMessageTooShort
|
|
|
|
}
|
|
|
|
return DecodeMessage(msgdata[0], msgdata[1:])
|
|
|
|
}
|
|
|
|
|
|
|
|
// checkValid performs some basic validity checks on the header.
|
|
|
|
// The packetLen here is the length remaining after the static header.
|
|
|
|
func (h *StaticHeader) checkValid(packetLen int) error {
|
|
|
|
if h.ProtocolID != protocolID {
|
|
|
|
return errInvalidHeader
|
|
|
|
}
|
|
|
|
if h.Version < minVersion {
|
|
|
|
return errMinVersion
|
|
|
|
}
|
|
|
|
if h.Flag != flagWhoareyou && packetLen < minMessageSize {
|
|
|
|
return errMsgTooShort
|
|
|
|
}
|
|
|
|
if int(h.AuthSize) > packetLen {
|
|
|
|
return errAuthSize
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// headerMask returns a cipher for 'masking' / 'unmasking' packet headers.
|
|
|
|
func (h *Header) mask(destID enode.ID) cipher.Stream {
|
|
|
|
block, err := aes.NewCipher(destID[:16])
|
|
|
|
if err != nil {
|
|
|
|
panic("can't create cipher")
|
|
|
|
}
|
|
|
|
return cipher.NewCTR(block, h.IV[:])
|
|
|
|
}
|
|
|
|
|
|
|
|
func bytesCopy(r *bytes.Buffer) []byte {
|
|
|
|
b := make([]byte, r.Len())
|
|
|
|
copy(b, r.Bytes())
|
|
|
|
return b
|
|
|
|
}
|