github.com/jimmyx0x/go-ethereum@v1.10.28/p2p/discover/v5wire/session.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/ecdsa"
    21  	crand "crypto/rand"
    22  	"encoding/binary"
    23  	"time"
    24  
    25  	"github.com/ethereum/go-ethereum/common/lru"
    26  	"github.com/ethereum/go-ethereum/common/mclock"
    27  	"github.com/ethereum/go-ethereum/crypto"
    28  	"github.com/ethereum/go-ethereum/p2p/enode"
    29  )
    30  
    31  const handshakeTimeout = time.Second
    32  
    33  // The SessionCache keeps negotiated encryption keys and
    34  // state for in-progress handshakes in the Discovery v5 wire protocol.
    35  type SessionCache struct {
    36  	sessions   lru.BasicLRU[sessionID, *session]
    37  	handshakes map[sessionID]*Whoareyou
    38  	clock      mclock.Clock
    39  
    40  	// hooks for overriding randomness.
    41  	nonceGen        func(uint32) (Nonce, error)
    42  	maskingIVGen    func([]byte) error
    43  	ephemeralKeyGen func() (*ecdsa.PrivateKey, error)
    44  }
    45  
    46  // sessionID identifies a session or handshake.
    47  type sessionID struct {
    48  	id   enode.ID
    49  	addr string
    50  }
    51  
    52  // session contains session information
    53  type session struct {
    54  	writeKey     []byte
    55  	readKey      []byte
    56  	nonceCounter uint32
    57  }
    58  
    59  // keysFlipped returns a copy of s with the read and write keys flipped.
    60  func (s *session) keysFlipped() *session {
    61  	return &session{s.readKey, s.writeKey, s.nonceCounter}
    62  }
    63  
    64  func NewSessionCache(maxItems int, clock mclock.Clock) *SessionCache {
    65  	return &SessionCache{
    66  		sessions:        lru.NewBasicLRU[sessionID, *session](maxItems),
    67  		handshakes:      make(map[sessionID]*Whoareyou),
    68  		clock:           clock,
    69  		nonceGen:        generateNonce,
    70  		maskingIVGen:    generateMaskingIV,
    71  		ephemeralKeyGen: crypto.GenerateKey,
    72  	}
    73  }
    74  
    75  func generateNonce(counter uint32) (n Nonce, err error) {
    76  	binary.BigEndian.PutUint32(n[:4], counter)
    77  	_, err = crand.Read(n[4:])
    78  	return n, err
    79  }
    80  
    81  func generateMaskingIV(buf []byte) error {
    82  	_, err := crand.Read(buf)
    83  	return err
    84  }
    85  
    86  // nextNonce creates a nonce for encrypting a message to the given session.
    87  func (sc *SessionCache) nextNonce(s *session) (Nonce, error) {
    88  	s.nonceCounter++
    89  	return sc.nonceGen(s.nonceCounter)
    90  }
    91  
    92  // session returns the current session for the given node, if any.
    93  func (sc *SessionCache) session(id enode.ID, addr string) *session {
    94  	item, _ := sc.sessions.Get(sessionID{id, addr})
    95  	return item
    96  }
    97  
    98  // readKey returns the current read key for the given node.
    99  func (sc *SessionCache) readKey(id enode.ID, addr string) []byte {
   100  	if s := sc.session(id, addr); s != nil {
   101  		return s.readKey
   102  	}
   103  	return nil
   104  }
   105  
   106  // storeNewSession stores new encryption keys in the cache.
   107  func (sc *SessionCache) storeNewSession(id enode.ID, addr string, s *session) {
   108  	sc.sessions.Add(sessionID{id, addr}, s)
   109  }
   110  
   111  // getHandshake gets the handshake challenge we previously sent to the given remote node.
   112  func (sc *SessionCache) getHandshake(id enode.ID, addr string) *Whoareyou {
   113  	return sc.handshakes[sessionID{id, addr}]
   114  }
   115  
   116  // storeSentHandshake stores the handshake challenge sent to the given remote node.
   117  func (sc *SessionCache) storeSentHandshake(id enode.ID, addr string, challenge *Whoareyou) {
   118  	challenge.sent = sc.clock.Now()
   119  	sc.handshakes[sessionID{id, addr}] = challenge
   120  }
   121  
   122  // deleteHandshake deletes handshake data for the given node.
   123  func (sc *SessionCache) deleteHandshake(id enode.ID, addr string) {
   124  	delete(sc.handshakes, sessionID{id, addr})
   125  }
   126  
   127  // handshakeGC deletes timed-out handshakes.
   128  func (sc *SessionCache) handshakeGC() {
   129  	deadline := sc.clock.Now().Add(-handshakeTimeout)
   130  	for key, challenge := range sc.handshakes {
   131  		if challenge.sent < deadline {
   132  			delete(sc.handshakes, key)
   133  		}
   134  	}
   135  }