p2p: EIP-8 changes
This commit is contained in:
363
p2p/rlpx.go
363
p2p/rlpx.go
@ -24,11 +24,14 @@ import (
|
||||
"crypto/elliptic"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
mrand "math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@ -51,9 +54,10 @@ const (
|
||||
authMsgLen = sigLen + shaLen + pubLen + shaLen + 1
|
||||
authRespLen = pubLen + shaLen + 1
|
||||
|
||||
eciesBytes = 65 + 16 + 32
|
||||
encAuthMsgLen = authMsgLen + eciesBytes // size of the final ECIES payload sent as initiator's handshake
|
||||
encAuthRespLen = authRespLen + eciesBytes // size of the final ECIES payload sent as receiver's handshake
|
||||
eciesOverhead = 65 /* pubkey */ + 16 /* IV */ + 32 /* MAC */
|
||||
|
||||
encAuthMsgLen = authMsgLen + eciesOverhead // size of encrypted pre-EIP-8 initiator handshake
|
||||
encAuthRespLen = authRespLen + eciesOverhead // size of encrypted pre-EIP-8 handshake reply
|
||||
|
||||
// total timeout for encryption handshake and protocol
|
||||
// handshake in both directions.
|
||||
@ -151,10 +155,6 @@ func readProtocolHandshake(rw MsgReader, our *protoHandshake) (*protoHandshake,
|
||||
if err := msg.Decode(&hs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// validate handshake info
|
||||
if hs.Version != our.Version {
|
||||
return nil, DiscIncompatibleVersion
|
||||
}
|
||||
if (hs.ID == discover.NodeID{}) {
|
||||
return nil, DiscInvalidIdentity
|
||||
}
|
||||
@ -200,6 +200,29 @@ type secrets struct {
|
||||
Token []byte
|
||||
}
|
||||
|
||||
// RLPx v4 handshake auth (defined in EIP-8).
|
||||
type authMsgV4 struct {
|
||||
gotPlain bool // whether read packet had plain format.
|
||||
|
||||
Signature [sigLen]byte
|
||||
InitiatorPubkey [pubLen]byte
|
||||
Nonce [shaLen]byte
|
||||
Version uint
|
||||
|
||||
// Ignore additional fields (forward-compatibility)
|
||||
Rest []rlp.RawValue `rlp:"tail"`
|
||||
}
|
||||
|
||||
// RLPx v4 handshake response (defined in EIP-8).
|
||||
type authRespV4 struct {
|
||||
RandomPubkey [pubLen]byte
|
||||
Nonce [shaLen]byte
|
||||
Version uint
|
||||
|
||||
// Ignore additional fields (forward-compatibility)
|
||||
Rest []rlp.RawValue `rlp:"tail"`
|
||||
}
|
||||
|
||||
// secrets is called after the handshake is completed.
|
||||
// It extracts the connection secrets from the handshake values.
|
||||
func (h *encHandshake) secrets(auth, authResp []byte) (secrets, error) {
|
||||
@ -215,7 +238,6 @@ func (h *encHandshake) secrets(auth, authResp []byte) (secrets, error) {
|
||||
RemoteID: h.remoteID,
|
||||
AES: aesSecret,
|
||||
MAC: crypto.Sha3(ecdheSecret, aesSecret),
|
||||
Token: crypto.Sha3(sharedSecret),
|
||||
}
|
||||
|
||||
// setup sha3 instances for the MACs
|
||||
@ -234,114 +256,89 @@ func (h *encHandshake) secrets(auth, authResp []byte) (secrets, error) {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (h *encHandshake) ecdhShared(prv *ecdsa.PrivateKey) ([]byte, error) {
|
||||
// staticSharedSecret returns the static shared secret, the result
|
||||
// of key agreement between the local and remote static node key.
|
||||
func (h *encHandshake) staticSharedSecret(prv *ecdsa.PrivateKey) ([]byte, error) {
|
||||
return ecies.ImportECDSA(prv).GenerateShared(h.remotePub, sskLen, sskLen)
|
||||
}
|
||||
|
||||
var configSendEIP = os.Getenv("RLPX_EIP8") != ""
|
||||
|
||||
// initiatorEncHandshake negotiates a session token on conn.
|
||||
// it should be called on the dialing side of the connection.
|
||||
//
|
||||
// prv is the local client's private key.
|
||||
// token is the token from a previous session with this node.
|
||||
func initiatorEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, remoteID discover.NodeID, token []byte) (s secrets, err error) {
|
||||
h, err := newInitiatorHandshake(remoteID)
|
||||
h := &encHandshake{initiator: true, remoteID: remoteID}
|
||||
authMsg, err := h.makeAuthMsg(prv, token)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
auth, err := h.authMsg(prv, token)
|
||||
var authPacket []byte
|
||||
if configSendEIP {
|
||||
authPacket, err = sealEIP8(authMsg, h)
|
||||
} else {
|
||||
authPacket, err = authMsg.sealPlain(h)
|
||||
}
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
if _, err = conn.Write(auth); err != nil {
|
||||
if _, err = conn.Write(authPacket); err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
response := make([]byte, encAuthRespLen)
|
||||
if _, err = io.ReadFull(conn, response); err != nil {
|
||||
authRespMsg := new(authRespV4)
|
||||
authRespPacket, err := readHandshakeMsg(authRespMsg, encAuthRespLen, prv, conn)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
if err := h.decodeAuthResp(response, prv); err != nil {
|
||||
if err := h.handleAuthResp(authRespMsg); err != nil {
|
||||
return s, err
|
||||
}
|
||||
return h.secrets(auth, response)
|
||||
return h.secrets(authPacket, authRespPacket)
|
||||
}
|
||||
|
||||
func newInitiatorHandshake(remoteID discover.NodeID) (*encHandshake, error) {
|
||||
rpub, err := remoteID.Pubkey()
|
||||
// makeAuthMsg creates the initiator handshake message.
|
||||
func (h *encHandshake) makeAuthMsg(prv *ecdsa.PrivateKey, token []byte) (*authMsgV4, error) {
|
||||
rpub, err := h.remoteID.Pubkey()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bad remoteID: %v", err)
|
||||
}
|
||||
// generate random initiator nonce
|
||||
n := make([]byte, shaLen)
|
||||
if _, err := rand.Read(n); err != nil {
|
||||
h.remotePub = ecies.ImportECDSAPublic(rpub)
|
||||
// Generate random initiator nonce.
|
||||
h.initNonce = make([]byte, shaLen)
|
||||
if _, err := rand.Read(h.initNonce); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// generate random keypair to use for signing
|
||||
randpriv, err := ecies.GenerateKey(rand.Reader, secp256k1.S256(), nil)
|
||||
// Generate random keypair to for ECDH.
|
||||
h.randomPrivKey, err = ecies.GenerateKey(rand.Reader, secp256k1.S256(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
h := &encHandshake{
|
||||
initiator: true,
|
||||
remoteID: remoteID,
|
||||
remotePub: ecies.ImportECDSAPublic(rpub),
|
||||
initNonce: n,
|
||||
randomPrivKey: randpriv,
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// authMsg creates an encrypted initiator handshake message.
|
||||
func (h *encHandshake) authMsg(prv *ecdsa.PrivateKey, token []byte) ([]byte, error) {
|
||||
var tokenFlag byte
|
||||
if token == nil {
|
||||
// no session token found means we need to generate shared secret.
|
||||
// ecies shared secret is used as initial session token for new peers
|
||||
// generate shared key from prv and remote pubkey
|
||||
var err error
|
||||
if token, err = h.ecdhShared(prv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// for known peers, we use stored token from the previous session
|
||||
tokenFlag = 0x01
|
||||
// Sign known message: static-shared-secret ^ nonce
|
||||
token, err = h.staticSharedSecret(prv)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// sign known message:
|
||||
// ecdh-shared-secret^nonce for new peers
|
||||
// token^nonce for old peers
|
||||
signed := xor(token, h.initNonce)
|
||||
signature, err := crypto.Sign(signed, h.randomPrivKey.ExportECDSA())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// encode auth message
|
||||
// signature || sha3(ecdhe-random-pubk) || pubk || nonce || token-flag
|
||||
msg := make([]byte, authMsgLen)
|
||||
n := copy(msg, signature)
|
||||
n += copy(msg[n:], crypto.Sha3(exportPubkey(&h.randomPrivKey.PublicKey)))
|
||||
n += copy(msg[n:], crypto.FromECDSAPub(&prv.PublicKey)[1:])
|
||||
n += copy(msg[n:], h.initNonce)
|
||||
msg[n] = tokenFlag
|
||||
|
||||
// encrypt auth message using remote-pubk
|
||||
return ecies.Encrypt(rand.Reader, h.remotePub, msg, nil, nil)
|
||||
msg := new(authMsgV4)
|
||||
copy(msg.Signature[:], signature)
|
||||
copy(msg.InitiatorPubkey[:], crypto.FromECDSAPub(&prv.PublicKey)[1:])
|
||||
copy(msg.Nonce[:], h.initNonce)
|
||||
msg.Version = 4
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// decodeAuthResp decode an encrypted authentication response message.
|
||||
func (h *encHandshake) decodeAuthResp(auth []byte, prv *ecdsa.PrivateKey) error {
|
||||
msg, err := crypto.Decrypt(prv, auth)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not decrypt auth response (%v)", err)
|
||||
}
|
||||
h.respNonce = msg[pubLen : pubLen+shaLen]
|
||||
h.remoteRandomPub, err = importPublicKey(msg[:pubLen])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// ignore token flag for now
|
||||
return nil
|
||||
func (h *encHandshake) handleAuthResp(msg *authRespV4) (err error) {
|
||||
h.respNonce = msg.Nonce[:]
|
||||
h.remoteRandomPub, err = importPublicKey(msg.RandomPubkey[:])
|
||||
return err
|
||||
}
|
||||
|
||||
// receiverEncHandshake negotiates a session token on conn.
|
||||
@ -350,99 +347,165 @@ func (h *encHandshake) decodeAuthResp(auth []byte, prv *ecdsa.PrivateKey) error
|
||||
// prv is the local client's private key.
|
||||
// token is the token from a previous session with this node.
|
||||
func receiverEncHandshake(conn io.ReadWriter, prv *ecdsa.PrivateKey, token []byte) (s secrets, err error) {
|
||||
// read remote auth sent by initiator.
|
||||
auth := make([]byte, encAuthMsgLen)
|
||||
if _, err := io.ReadFull(conn, auth); err != nil {
|
||||
return s, err
|
||||
}
|
||||
h, err := decodeAuthMsg(prv, token, auth)
|
||||
authMsg := new(authMsgV4)
|
||||
authPacket, err := readHandshakeMsg(authMsg, encAuthMsgLen, prv, conn)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
h := new(encHandshake)
|
||||
if err := h.handleAuthMsg(authMsg, prv); err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
// send auth response
|
||||
resp, err := h.authResp(prv, token)
|
||||
authRespMsg, err := h.makeAuthResp()
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
if _, err = conn.Write(resp); err != nil {
|
||||
var authRespPacket []byte
|
||||
if authMsg.gotPlain {
|
||||
authRespPacket, err = authRespMsg.sealPlain(h)
|
||||
} else {
|
||||
authRespPacket, err = sealEIP8(authRespMsg, h)
|
||||
}
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
return h.secrets(auth, resp)
|
||||
if _, err = conn.Write(authRespPacket); err != nil {
|
||||
return s, err
|
||||
}
|
||||
return h.secrets(authPacket, authRespPacket)
|
||||
}
|
||||
|
||||
func decodeAuthMsg(prv *ecdsa.PrivateKey, token []byte, auth []byte) (*encHandshake, error) {
|
||||
var err error
|
||||
h := new(encHandshake)
|
||||
// generate random keypair for session
|
||||
h.randomPrivKey, err = ecies.GenerateKey(rand.Reader, secp256k1.S256(), nil)
|
||||
func (h *encHandshake) handleAuthMsg(msg *authMsgV4, prv *ecdsa.PrivateKey) error {
|
||||
// Import the remote identity.
|
||||
h.initNonce = msg.Nonce[:]
|
||||
h.remoteID = msg.InitiatorPubkey
|
||||
rpub, err := h.remoteID.Pubkey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return fmt.Errorf("bad remoteID: %#v", err)
|
||||
}
|
||||
// generate random nonce
|
||||
h.remotePub = ecies.ImportECDSAPublic(rpub)
|
||||
|
||||
// Generate random keypair for ECDH.
|
||||
// If a private key is already set, use it instead of generating one (for testing).
|
||||
if h.randomPrivKey == nil {
|
||||
h.randomPrivKey, err = ecies.GenerateKey(rand.Reader, secp256k1.S256(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Check the signature.
|
||||
token, err := h.staticSharedSecret(prv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
signedMsg := xor(token, h.initNonce)
|
||||
remoteRandomPub, err := secp256k1.RecoverPubkey(signedMsg, msg.Signature[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.remoteRandomPub, _ = importPublicKey(remoteRandomPub)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *encHandshake) makeAuthResp() (msg *authRespV4, err error) {
|
||||
// Generate random nonce.
|
||||
h.respNonce = make([]byte, shaLen)
|
||||
if _, err = rand.Read(h.respNonce); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msg, err := crypto.Decrypt(prv, auth)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not decrypt auth message (%v)", err)
|
||||
}
|
||||
|
||||
// decode message parameters
|
||||
// signature || sha3(ecdhe-random-pubk) || pubk || nonce || token-flag
|
||||
h.initNonce = msg[authMsgLen-shaLen-1 : authMsgLen-1]
|
||||
copy(h.remoteID[:], msg[sigLen+shaLen:sigLen+shaLen+pubLen])
|
||||
rpub, err := h.remoteID.Pubkey()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bad remoteID: %#v", err)
|
||||
}
|
||||
h.remotePub = ecies.ImportECDSAPublic(rpub)
|
||||
|
||||
// recover remote random pubkey from signed message.
|
||||
if token == nil {
|
||||
// TODO: it is an error if the initiator has a token and we don't. check that.
|
||||
|
||||
// no session token means we need to generate shared secret.
|
||||
// ecies shared secret is used as initial session token for new peers.
|
||||
// generate shared key from prv and remote pubkey.
|
||||
if token, err = h.ecdhShared(prv); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
signedMsg := xor(token, h.initNonce)
|
||||
remoteRandomPub, err := secp256k1.RecoverPubkey(signedMsg, msg[:sigLen])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// validate the sha3 of recovered pubkey
|
||||
remoteRandomPubMAC := msg[sigLen : sigLen+shaLen]
|
||||
shaRemoteRandomPub := crypto.Sha3(remoteRandomPub[1:])
|
||||
if !bytes.Equal(remoteRandomPubMAC, shaRemoteRandomPub) {
|
||||
return nil, fmt.Errorf("sha3 of recovered ephemeral pubkey does not match checksum in auth message")
|
||||
}
|
||||
|
||||
h.remoteRandomPub, _ = importPublicKey(remoteRandomPub)
|
||||
return h, nil
|
||||
msg = new(authRespV4)
|
||||
copy(msg.Nonce[:], h.respNonce)
|
||||
copy(msg.RandomPubkey[:], exportPubkey(&h.randomPrivKey.PublicKey))
|
||||
msg.Version = 4
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
// authResp generates the encrypted authentication response message.
|
||||
func (h *encHandshake) authResp(prv *ecdsa.PrivateKey, token []byte) ([]byte, error) {
|
||||
// responder auth message
|
||||
// E(remote-pubk, ecdhe-random-pubk || nonce || 0x0)
|
||||
resp := make([]byte, authRespLen)
|
||||
n := copy(resp, exportPubkey(&h.randomPrivKey.PublicKey))
|
||||
n += copy(resp[n:], h.respNonce)
|
||||
if token == nil {
|
||||
resp[n] = 0
|
||||
} else {
|
||||
resp[n] = 1
|
||||
func (msg *authMsgV4) sealPlain(h *encHandshake) ([]byte, error) {
|
||||
buf := make([]byte, authMsgLen)
|
||||
n := copy(buf, msg.Signature[:])
|
||||
n += copy(buf[n:], crypto.Sha3(exportPubkey(&h.randomPrivKey.PublicKey)))
|
||||
n += copy(buf[n:], msg.InitiatorPubkey[:])
|
||||
n += copy(buf[n:], msg.Nonce[:])
|
||||
buf[n] = 0 // token-flag
|
||||
return ecies.Encrypt(rand.Reader, h.remotePub, buf, nil, nil)
|
||||
}
|
||||
|
||||
func (msg *authMsgV4) decodePlain(input []byte) {
|
||||
n := copy(msg.Signature[:], input)
|
||||
n += shaLen // skip sha3(initiator-ephemeral-pubk)
|
||||
n += copy(msg.InitiatorPubkey[:], input[n:])
|
||||
n += copy(msg.Nonce[:], input[n:])
|
||||
msg.Version = 4
|
||||
msg.gotPlain = true
|
||||
}
|
||||
|
||||
func (msg *authRespV4) sealPlain(hs *encHandshake) ([]byte, error) {
|
||||
buf := make([]byte, authRespLen)
|
||||
n := copy(buf, msg.RandomPubkey[:])
|
||||
n += copy(buf[n:], msg.Nonce[:])
|
||||
return ecies.Encrypt(rand.Reader, hs.remotePub, buf, nil, nil)
|
||||
}
|
||||
|
||||
func (msg *authRespV4) decodePlain(input []byte) {
|
||||
n := copy(msg.RandomPubkey[:], input)
|
||||
n += copy(msg.Nonce[:], input[n:])
|
||||
msg.Version = 4
|
||||
}
|
||||
|
||||
var padSpace = make([]byte, 300)
|
||||
|
||||
func sealEIP8(msg interface{}, h *encHandshake) ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
if err := rlp.Encode(buf, msg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// encrypt using remote-pubk
|
||||
return ecies.Encrypt(rand.Reader, h.remotePub, resp, nil, nil)
|
||||
// pad with random amount of data. the amount needs to be at least 100 bytes to make
|
||||
// the message distinguishable from pre-EIP-8 handshakes.
|
||||
pad := padSpace[:mrand.Intn(len(padSpace)-100)+100]
|
||||
buf.Write(pad)
|
||||
prefix := make([]byte, 2)
|
||||
binary.BigEndian.PutUint16(prefix, uint16(buf.Len()+eciesOverhead))
|
||||
|
||||
enc, err := ecies.Encrypt(rand.Reader, h.remotePub, buf.Bytes(), nil, prefix)
|
||||
return append(prefix, enc...), err
|
||||
}
|
||||
|
||||
type plainDecoder interface {
|
||||
decodePlain([]byte)
|
||||
}
|
||||
|
||||
func readHandshakeMsg(msg plainDecoder, plainSize int, prv *ecdsa.PrivateKey, r io.Reader) ([]byte, error) {
|
||||
buf := make([]byte, plainSize)
|
||||
if _, err := io.ReadFull(r, buf); err != nil {
|
||||
return buf, err
|
||||
}
|
||||
// Attempt decoding pre-EIP-8 "plain" format.
|
||||
key := ecies.ImportECDSA(prv)
|
||||
if dec, err := key.Decrypt(rand.Reader, buf, nil, nil); err == nil {
|
||||
msg.decodePlain(dec)
|
||||
return buf, nil
|
||||
}
|
||||
// Could be EIP-8 format, try that.
|
||||
prefix := buf[:2]
|
||||
size := binary.BigEndian.Uint16(prefix)
|
||||
if size < uint16(plainSize) {
|
||||
return buf, fmt.Errorf("size underflow, need at least %d bytes", plainSize)
|
||||
}
|
||||
buf = append(buf, make([]byte, size-uint16(plainSize)+2)...)
|
||||
if _, err := io.ReadFull(r, buf[plainSize:]); err != nil {
|
||||
return buf, err
|
||||
}
|
||||
dec, err := key.Decrypt(rand.Reader, buf[2:], nil, prefix)
|
||||
if err != nil {
|
||||
return buf, err
|
||||
}
|
||||
// Can't use rlp.DecodeBytes here because it rejects
|
||||
// trailing data (forward-compatibility).
|
||||
s := rlp.NewStream(bytes.NewReader(dec), 0)
|
||||
return buf, s.Decode(msg)
|
||||
}
|
||||
|
||||
// importPublicKey unmarshals 512 bit public keys.
|
||||
@ -458,7 +521,11 @@ func importPublicKey(pubKey []byte) (*ecies.PublicKey, error) {
|
||||
return nil, fmt.Errorf("invalid public key length %v (expect 64/65)", len(pubKey))
|
||||
}
|
||||
// TODO: fewer pointless conversions
|
||||
return ecies.ImportECDSAPublic(crypto.ToECDSAPub(pubKey65)), nil
|
||||
pub := crypto.ToECDSAPub(pubKey65)
|
||||
if pub.X == nil {
|
||||
return nil, fmt.Errorf("invalid public key")
|
||||
}
|
||||
return ecies.ImportECDSAPublic(pub), nil
|
||||
}
|
||||
|
||||
func exportPubkey(pub *ecies.PublicKey) []byte {
|
||||
|
Reference in New Issue
Block a user