From b25da7c3f4af27df080770779dd75b4802c53cd9 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 23 Dec 2015 01:52:40 +0100 Subject: [release/1.3.4] p2p/discover: EIP-8 changes Conflicts: p2p/discover/udp_test.go --- p2p/discover/udp.go | 11 ++++- p2p/discover/udp_test.go | 113 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 1 deletion(-) (limited to 'p2p') diff --git a/p2p/discover/udp.go b/p2p/discover/udp.go index 20f69cf08..2477a37e0 100644 --- a/p2p/discover/udp.go +++ b/p2p/discover/udp.go @@ -67,6 +67,8 @@ type ( Version uint From, To rpcEndpoint Expiration uint64 + // Ignore additional fields (for forward compatibility). + Rest []rlp.RawValue `rlp:"tail"` } // pong is the reply to ping. @@ -78,18 +80,24 @@ type ( ReplyTok []byte // This contains the hash of the ping packet. Expiration uint64 // Absolute timestamp at which the packet becomes invalid. + // Ignore additional fields (for forward compatibility). + Rest []rlp.RawValue `rlp:"tail"` } // findnode is a query for nodes close to the given target. findnode struct { Target NodeID // doesn't need to be an actual public key Expiration uint64 + // Ignore additional fields (for forward compatibility). + Rest []rlp.RawValue `rlp:"tail"` } // reply to findnode neighbors struct { Nodes []rpcNode Expiration uint64 + // Ignore additional fields (for forward compatibility). + Rest []rlp.RawValue `rlp:"tail"` } rpcNode struct { @@ -513,7 +521,8 @@ func decodePacket(buf []byte) (packet, NodeID, []byte, error) { default: return nil, fromID, hash, fmt.Errorf("unknown type: %d", ptype) } - err = rlp.DecodeBytes(sigdata[1:], req) + s := rlp.NewStream(bytes.NewReader(sigdata[1:]), 0) + err = s.Decode(req) return req, fromID, hash, err } diff --git a/p2p/discover/udp_test.go b/p2p/discover/udp_test.go index 913199c26..6686e6ff7 100644 --- a/p2p/discover/udp_test.go +++ b/p2p/discover/udp_test.go @@ -20,6 +20,7 @@ import ( "bytes" "crypto/ecdsa" "encoding/binary" + "encoding/hex" "errors" "fmt" "io" @@ -34,8 +35,11 @@ import ( "testing" "time" + "github.com/davecgh/go-spew/spew" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/rlp" ) func init() { @@ -385,6 +389,115 @@ func TestUDP_successfulPing(t *testing.T) { } } +var testPackets = []struct { + input string + wantPacket interface{} +}{ + { + input: "71dbda3a79554728d4f94411e42ee1f8b0d561c10e1e5f5893367948c6a7d70bb87b235fa28a77070271b6c164a2dce8c7e13a5739b53b5e96f2e5acb0e458a02902f5965d55ecbeb2ebb6cabb8b2b232896a36b737666c55265ad0a68412f250001ea04cb847f000001820cfa8215a8d790000000000000000000000000000000018208ae820d058443b9a355", + wantPacket: &ping{ + Version: 4, + From: rpcEndpoint{net.ParseIP("127.0.0.1").To4(), 3322, 5544}, + To: rpcEndpoint{net.ParseIP("::1"), 2222, 3333}, + Expiration: 1136239445, + Rest: []rlp.RawValue{}, + }, + }, + { + input: "e9614ccfd9fc3e74360018522d30e1419a143407ffcce748de3e22116b7e8dc92ff74788c0b6663aaa3d67d641936511c8f8d6ad8698b820a7cf9e1be7155e9a241f556658c55428ec0563514365799a4be2be5a685a80971ddcfa80cb422cdd0101ec04cb847f000001820cfa8215a8d790000000000000000000000000000000018208ae820d058443b9a3550102", + wantPacket: &ping{ + Version: 4, + From: rpcEndpoint{net.ParseIP("127.0.0.1").To4(), 3322, 5544}, + To: rpcEndpoint{net.ParseIP("::1"), 2222, 3333}, + Expiration: 1136239445, + Rest: []rlp.RawValue{{0x01}, {0x02}}, + }, + }, + { + input: "577be4349c4dd26768081f58de4c6f375a7a22f3f7adda654d1428637412c3d7fe917cadc56d4e5e7ffae1dbe3efffb9849feb71b262de37977e7c7a44e677295680e9e38ab26bee2fcbae207fba3ff3d74069a50b902a82c9903ed37cc993c50001f83e82022bd79020010db83c4d001500000000abcdef12820cfa8215a8d79020010db885a308d313198a2e037073488208ae82823a8443b9a355c5010203040531b9019afde696e582a78fa8d95ea13ce3297d4afb8ba6433e4154caa5ac6431af1b80ba76023fa4090c408f6b4bc3701562c031041d4702971d102c9ab7fa5eed4cd6bab8f7af956f7d565ee1917084a95398b6a21eac920fe3dd1345ec0a7ef39367ee69ddf092cbfe5b93e5e568ebc491983c09c76d922dc3", + wantPacket: &ping{ + Version: 555, + From: rpcEndpoint{net.ParseIP("2001:db8:3c4d:15::abcd:ef12"), 3322, 5544}, + To: rpcEndpoint{net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348"), 2222, 33338}, + Expiration: 1136239445, + Rest: []rlp.RawValue{{0xC5, 0x01, 0x02, 0x03, 0x04, 0x05}}, + }, + }, + { + input: "09b2428d83348d27cdf7064ad9024f526cebc19e4958f0fdad87c15eb598dd61d08423e0bf66b2069869e1724125f820d851c136684082774f870e614d95a2855d000f05d1648b2d5945470bc187c2d2216fbe870f43ed0909009882e176a46b0102f846d79020010db885a308d313198a2e037073488208ae82823aa0fbc914b16819237dcd8801d7e53f69e9719adecb3cc0e790c57e91ca4461c9548443b9a355c6010203c2040506a0c969a58f6f9095004c0177a6b47f451530cab38966a25cca5cb58f055542124e", + wantPacket: &pong{ + To: rpcEndpoint{net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348"), 2222, 33338}, + ReplyTok: common.Hex2Bytes("fbc914b16819237dcd8801d7e53f69e9719adecb3cc0e790c57e91ca4461c954"), + Expiration: 1136239445, + Rest: []rlp.RawValue{{0xC6, 0x01, 0x02, 0x03, 0xC2, 0x04, 0x05}, {0x06}}, + }, + }, + { + input: "c7c44041b9f7c7e41934417ebac9a8e1a4c6298f74553f2fcfdcae6ed6fe53163eb3d2b52e39fe91831b8a927bf4fc222c3902202027e5e9eb812195f95d20061ef5cd31d502e47ecb61183f74a504fe04c51e73df81f25c4d506b26db4517490103f84eb840ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be00812904767bf5ccd1fc7f8443b9a35582999983999999280dc62cc8255c73471e0a61da0c89acdc0e035e260add7fc0c04ad9ebf3919644c91cb247affc82b69bd2ca235c71eab8e49737c937a2c396", + wantPacket: &findnode{ + Target: MustHexID("ca634cae0d49acb401d8a4c6b6fe8c55b70d115bf400769cc1400f3258cd31387574077f301b421bc84df7266c44e9e6d569fc56be00812904767bf5ccd1fc7f"), + Expiration: 1136239445, + Rest: []rlp.RawValue{{0x82, 0x99, 0x99}, {0x83, 0x99, 0x99, 0x99}}, + }, + }, + { + input: "c679fc8fe0b8b12f06577f2e802d34f6fa257e6137a995f6f4cbfc9ee50ed3710faf6e66f932c4c8d81d64343f429651328758b47d3dbc02c4042f0fff6946a50f4a49037a72bb550f3a7872363a83e1b9ee6469856c24eb4ef80b7535bcf99c0004f9015bf90150f84d846321163782115c82115db8403155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32f84984010203040101b840312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069dbf8599020010db83c4d001500000000abcdef12820d05820d05b84038643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aacf8599020010db885a308d313198a2e037073488203e78203e8b8408dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df738443b9a355010203b525a138aa34383fec3d2719a0", + wantPacket: &neighbors{ + Nodes: []rpcNode{ + { + ID: MustHexID("3155e1427f85f10a5c9a7755877748041af1bcd8d474ec065eb33df57a97babf54bfd2103575fa829115d224c523596b401065a97f74010610fce76382c0bf32"), + IP: net.ParseIP("99.33.22.55").To4(), + UDP: 4444, + TCP: 4445, + }, + { + ID: MustHexID("312c55512422cf9b8a4097e9a6ad79402e87a15ae909a4bfefa22398f03d20951933beea1e4dfa6f968212385e829f04c2d314fc2d4e255e0d3bc08792b069db"), + IP: net.ParseIP("1.2.3.4").To4(), + UDP: 1, + TCP: 1, + }, + { + ID: MustHexID("38643200b172dcfef857492156971f0e6aa2c538d8b74010f8e140811d53b98c765dd2d96126051913f44582e8c199ad7c6d6819e9a56483f637feaac9448aac"), + IP: net.ParseIP("2001:db8:3c4d:15::abcd:ef12"), + UDP: 3333, + TCP: 3333, + }, + { + ID: MustHexID("8dcab8618c3253b558d459da53bd8fa68935a719aff8b811197101a4b2b47dd2d47295286fc00cc081bb542d760717d1bdd6bec2c37cd72eca367d6dd3b9df73"), + IP: net.ParseIP("2001:db8:85a3:8d3:1319:8a2e:370:7348"), + UDP: 999, + TCP: 1000, + }, + }, + Expiration: 1136239445, + Rest: []rlp.RawValue{{0x01}, {0x02}, {0x03}}, + }, + }, +} + +func TestForwardCompatibility(t *testing.T) { + testkey, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + wantNodeID := PubkeyID(&testkey.PublicKey) + + for _, test := range testPackets { + input, err := hex.DecodeString(test.input) + if err != nil { + t.Fatalf("invalid hex: %s", test.input) + } + packet, nodeid, _, err := decodePacket(input) + if err != nil { + t.Errorf("did not accept packet %s\n%v", test.input, err) + continue + } + if !reflect.DeepEqual(packet, test.wantPacket) { + t.Errorf("got %s\nwant %s", spew.Sdump(packet), spew.Sdump(test.wantPacket)) + } + if nodeid != wantNodeID { + t.Errorf("got id %v\nwant id %v", nodeid, wantNodeID) + } + } +} + // dgramPipe is a fake UDP socket. It queues all sent datagrams. type dgramPipe struct { mu *sync.Mutex -- cgit v1.2.3 From b5a0cf488c4336ded975ae13576748e721a2a1ab Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Wed, 23 Dec 2015 01:48:55 +0100 Subject: [release/1.3.4] p2p: EIP-8 changes Conflicts: p2p/rlpx.go --- p2p/message_test.go | 3 +- p2p/peer.go | 3 + p2p/rlpx.go | 351 +++++++++++++++++++++++++++++++--------------------- p2p/rlpx_test.go | 235 ++++++++++++++++++++++++++++++++++- 4 files changed, 443 insertions(+), 149 deletions(-) (limited to 'p2p') diff --git a/p2p/message_test.go b/p2p/message_test.go index 8599b7e87..013214e21 100644 --- a/p2p/message_test.go +++ b/p2p/message_test.go @@ -143,7 +143,8 @@ func TestEOFSignal(t *testing.T) { } func unhex(str string) []byte { - b, err := hex.DecodeString(strings.Replace(str, "\n", "", -1)) + r := strings.NewReplacer("\t", "", " ", "", "\n", "") + b, err := hex.DecodeString(r.Replace(str)) if err != nil { panic(fmt.Sprintf("invalid hex string: %q", str)) } diff --git a/p2p/peer.go b/p2p/peer.go index 72ed4069c..b9d6c099d 100644 --- a/p2p/peer.go +++ b/p2p/peer.go @@ -56,6 +56,9 @@ type protoHandshake struct { Caps []Cap ListenPort uint64 ID discover.NodeID + + // Ignore additional fields (for forward compatibility). + Rest []rlp.RawValue `rlp:"tail"` } // Peer represents a connected remote node. diff --git a/p2p/rlpx.go b/p2p/rlpx.go index aaa733854..9d6cba5b6 100644 --- a/p2p/rlpx.go +++ b/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, crypto.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: - // ecdh-shared-secret^nonce for new peers - // token^nonce for old peers + // Sign known message: static-shared-secret ^ nonce + token, err = h.staticSharedSecret(prv) + if err != nil { + return nil, err + } 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 { + authMsg := new(authMsgV4) + authPacket, err := readHandshakeMsg(authMsg, encAuthMsgLen, prv, conn) + if err != nil { return s, err } - h, err := decodeAuthMsg(prv, token, auth) - if err != nil { + 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 { - return s, err + var authRespPacket []byte + if authMsg.gotPlain { + authRespPacket, err = authRespMsg.sealPlain(h) + } else { + authRespPacket, err = sealEIP8(authRespMsg, h) } - - return h.secrets(auth, resp) -} - -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, crypto.S256(), nil) if err != nil { - return nil, err - } - // generate random nonce - h.respNonce = make([]byte, shaLen) - if _, err = rand.Read(h.respNonce); err != nil { - return nil, err + return s, err } - - msg, err := crypto.Decrypt(prv, auth) - if err != nil { - return nil, fmt.Errorf("could not decrypt auth message (%v)", err) + if _, err = conn.Write(authRespPacket); err != nil { + return s, err } + return h.secrets(authPacket, authRespPacket) +} - // 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]) +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, fmt.Errorf("bad remoteID: %#v", err) + return 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 + // 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[:sigLen]) + 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 } - // 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") + msg = new(authRespV4) + copy(msg.Nonce[:], h.respNonce) + copy(msg.RandomPubkey[:], exportPubkey(&h.randomPrivKey.PublicKey)) + msg.Version = 4 + return msg, nil +} + +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 } + // 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)) - h.remoteRandomPub, _ = importPublicKey(remoteRandomPub) - return h, 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 + 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 } - // encrypt using remote-pubk - return ecies.Encrypt(rand.Reader, h.remotePub, resp, nil, 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 { diff --git a/p2p/rlpx_test.go b/p2p/rlpx_test.go index 900353f0e..a90db44a4 100644 --- a/p2p/rlpx_test.go +++ b/p2p/rlpx_test.go @@ -21,6 +21,7 @@ import ( "crypto/rand" "errors" "fmt" + "io" "io/ioutil" "net" "reflect" @@ -160,6 +161,7 @@ func TestProtocolHandshake(t *testing.T) { wg.Add(2) go func() { defer wg.Done() + defer fd1.Close() rlpx := newRLPX(fd0) remid, err := rlpx.doEncHandshake(prv0, node1) if err != nil { @@ -176,6 +178,7 @@ func TestProtocolHandshake(t *testing.T) { t.Errorf("dial side proto handshake error: %v", err) return } + phs.Rest = nil if !reflect.DeepEqual(phs, hs1) { t.Errorf("dial side proto handshake mismatch:\ngot: %s\nwant: %s\n", spew.Sdump(phs), spew.Sdump(hs1)) return @@ -184,6 +187,7 @@ func TestProtocolHandshake(t *testing.T) { }() go func() { defer wg.Done() + defer fd1.Close() rlpx := newRLPX(fd1) remid, err := rlpx.doEncHandshake(prv1, nil) if err != nil { @@ -200,6 +204,7 @@ func TestProtocolHandshake(t *testing.T) { t.Errorf("listen side proto handshake error: %v", err) return } + phs.Rest = nil if !reflect.DeepEqual(phs, hs0) { t.Errorf("listen side proto handshake mismatch:\ngot: %s\nwant: %s\n", spew.Sdump(phs), spew.Sdump(hs0)) return @@ -214,7 +219,6 @@ func TestProtocolHandshake(t *testing.T) { func TestProtocolHandshakeErrors(t *testing.T) { our := &protoHandshake{Version: 3, Caps: []Cap{{"foo", 2}, {"bar", 3}}, Name: "quux"} - id := randomID() tests := []struct { code uint64 msg interface{} @@ -240,11 +244,6 @@ func TestProtocolHandshakeErrors(t *testing.T) { msg: []byte{1, 2, 3}, err: newPeerError(errInvalidMsg, "(code 0) (size 4) rlp: expected input list for p2p.protoHandshake"), }, - { - code: handshakeMsg, - msg: &protoHandshake{Version: 9944, ID: id}, - err: DiscIncompatibleVersion, - }, { code: handshakeMsg, msg: &protoHandshake{Version: 3}, @@ -372,3 +371,227 @@ func TestRLPXFrameRW(t *testing.T) { } } } + +type handshakeAuthTest struct { + input string + isPlain bool + wantVersion uint + wantRest []rlp.RawValue +} + +var eip8HandshakeAuthTests = []handshakeAuthTest{ + // (Auth₁) RLPx v4 plain encoding + { + input: ` + 048ca79ad18e4b0659fab4853fe5bc58eb83992980f4c9cc147d2aa31532efd29a3d3dc6a3d89eaf + 913150cfc777ce0ce4af2758bf4810235f6e6ceccfee1acc6b22c005e9e3a49d6448610a58e98744 + ba3ac0399e82692d67c1f58849050b3024e21a52c9d3b01d871ff5f210817912773e610443a9ef14 + 2e91cdba0bd77b5fdf0769b05671fc35f83d83e4d3b0b000c6b2a1b1bba89e0fc51bf4e460df3105 + c444f14be226458940d6061c296350937ffd5e3acaceeaaefd3c6f74be8e23e0f45163cc7ebd7622 + 0f0128410fd05250273156d548a414444ae2f7dea4dfca2d43c057adb701a715bf59f6fb66b2d1d2 + 0f2c703f851cbf5ac47396d9ca65b6260bd141ac4d53e2de585a73d1750780db4c9ee4cd4d225173 + a4592ee77e2bd94d0be3691f3b406f9bba9b591fc63facc016bfa8 + `, + isPlain: true, + wantVersion: 4, + }, + // (Auth₂) EIP-8 encoding + { + input: ` + 01b304ab7578555167be8154d5cc456f567d5ba302662433674222360f08d5f1534499d3678b513b + 0fca474f3a514b18e75683032eb63fccb16c156dc6eb2c0b1593f0d84ac74f6e475f1b8d56116b84 + 9634a8c458705bf83a626ea0384d4d7341aae591fae42ce6bd5c850bfe0b999a694a49bbbaf3ef6c + da61110601d3b4c02ab6c30437257a6e0117792631a4b47c1d52fc0f8f89caadeb7d02770bf999cc + 147d2df3b62e1ffb2c9d8c125a3984865356266bca11ce7d3a688663a51d82defaa8aad69da39ab6 + d5470e81ec5f2a7a47fb865ff7cca21516f9299a07b1bc63ba56c7a1a892112841ca44b6e0034dee + 70c9adabc15d76a54f443593fafdc3b27af8059703f88928e199cb122362a4b35f62386da7caad09 + c001edaeb5f8a06d2b26fb6cb93c52a9fca51853b68193916982358fe1e5369e249875bb8d0d0ec3 + 6f917bc5e1eafd5896d46bd61ff23f1a863a8a8dcd54c7b109b771c8e61ec9c8908c733c0263440e + 2aa067241aaa433f0bb053c7b31a838504b148f570c0ad62837129e547678c5190341e4f1693956c + 3bf7678318e2d5b5340c9e488eefea198576344afbdf66db5f51204a6961a63ce072c8926c + `, + wantVersion: 4, + wantRest: []rlp.RawValue{}, + }, + // (Auth₃) RLPx v4 EIP-8 encoding with version 56, additional list elements + { + input: ` + 01b8044c6c312173685d1edd268aa95e1d495474c6959bcdd10067ba4c9013df9e40ff45f5bfd6f7 + 2471f93a91b493f8e00abc4b80f682973de715d77ba3a005a242eb859f9a211d93a347fa64b597bf + 280a6b88e26299cf263b01b8dfdb712278464fd1c25840b995e84d367d743f66c0e54a586725b7bb + f12acca27170ae3283c1073adda4b6d79f27656993aefccf16e0d0409fe07db2dc398a1b7e8ee93b + cd181485fd332f381d6a050fba4c7641a5112ac1b0b61168d20f01b479e19adf7fdbfa0905f63352 + bfc7e23cf3357657455119d879c78d3cf8c8c06375f3f7d4861aa02a122467e069acaf513025ff19 + 6641f6d2810ce493f51bee9c966b15c5043505350392b57645385a18c78f14669cc4d960446c1757 + 1b7c5d725021babbcd786957f3d17089c084907bda22c2b2675b4378b114c601d858802a55345a15 + 116bc61da4193996187ed70d16730e9ae6b3bb8787ebcaea1871d850997ddc08b4f4ea668fbf3740 + 7ac044b55be0908ecb94d4ed172ece66fd31bfdadf2b97a8bc690163ee11f5b575a4b44e36e2bfb2 + f0fce91676fd64c7773bac6a003f481fddd0bae0a1f31aa27504e2a533af4cef3b623f4791b2cca6 + d490 + `, + wantVersion: 56, + wantRest: []rlp.RawValue{{0x01}, {0x02}, {0xC2, 0x04, 0x05}}, + }, +} + +type handshakeAckTest struct { + input string + wantVersion uint + wantRest []rlp.RawValue +} + +var eip8HandshakeRespTests = []handshakeAckTest{ + // (Ack₁) RLPx v4 plain encoding + { + input: ` + 049f8abcfa9c0dc65b982e98af921bc0ba6e4243169348a236abe9df5f93aa69d99cadddaa387662 + b0ff2c08e9006d5a11a278b1b3331e5aaabf0a32f01281b6f4ede0e09a2d5f585b26513cb794d963 + 5a57563921c04a9090b4f14ee42be1a5461049af4ea7a7f49bf4c97a352d39c8d02ee4acc416388c + 1c66cec761d2bc1c72da6ba143477f049c9d2dde846c252c111b904f630ac98e51609b3b1f58168d + dca6505b7196532e5f85b259a20c45e1979491683fee108e9660edbf38f3add489ae73e3dda2c71b + d1497113d5c755e942d1 + `, + wantVersion: 4, + }, + // (Ack₂) EIP-8 encoding + { + input: ` + 01ea0451958701280a56482929d3b0757da8f7fbe5286784beead59d95089c217c9b917788989470 + b0e330cc6e4fb383c0340ed85fab836ec9fb8a49672712aeabbdfd1e837c1ff4cace34311cd7f4de + 05d59279e3524ab26ef753a0095637ac88f2b499b9914b5f64e143eae548a1066e14cd2f4bd7f814 + c4652f11b254f8a2d0191e2f5546fae6055694aed14d906df79ad3b407d94692694e259191cde171 + ad542fc588fa2b7333313d82a9f887332f1dfc36cea03f831cb9a23fea05b33deb999e85489e645f + 6aab1872475d488d7bd6c7c120caf28dbfc5d6833888155ed69d34dbdc39c1f299be1057810f34fb + e754d021bfca14dc989753d61c413d261934e1a9c67ee060a25eefb54e81a4d14baff922180c395d + 3f998d70f46f6b58306f969627ae364497e73fc27f6d17ae45a413d322cb8814276be6ddd13b885b + 201b943213656cde498fa0e9ddc8e0b8f8a53824fbd82254f3e2c17e8eaea009c38b4aa0a3f306e8 + 797db43c25d68e86f262e564086f59a2fc60511c42abfb3057c247a8a8fe4fb3ccbadde17514b7ac + 8000cdb6a912778426260c47f38919a91f25f4b5ffb455d6aaaf150f7e5529c100ce62d6d92826a7 + 1778d809bdf60232ae21ce8a437eca8223f45ac37f6487452ce626f549b3b5fdee26afd2072e4bc7 + 5833c2464c805246155289f4 + `, + wantVersion: 4, + wantRest: []rlp.RawValue{}, + }, + // (Ack₃) EIP-8 encoding with version 57, additional list elements + { + input: ` + 01f004076e58aae772bb101ab1a8e64e01ee96e64857ce82b1113817c6cdd52c09d26f7b90981cd7 + ae835aeac72e1573b8a0225dd56d157a010846d888dac7464baf53f2ad4e3d584531fa203658fab0 + 3a06c9fd5e35737e417bc28c1cbf5e5dfc666de7090f69c3b29754725f84f75382891c561040ea1d + dc0d8f381ed1b9d0d4ad2a0ec021421d847820d6fa0ba66eaf58175f1b235e851c7e2124069fbc20 + 2888ddb3ac4d56bcbd1b9b7eab59e78f2e2d400905050f4a92dec1c4bdf797b3fc9b2f8e84a482f3 + d800386186712dae00d5c386ec9387a5e9c9a1aca5a573ca91082c7d68421f388e79127a5177d4f8 + 590237364fd348c9611fa39f78dcdceee3f390f07991b7b47e1daa3ebcb6ccc9607811cb17ce51f1 + c8c2c5098dbdd28fca547b3f58c01a424ac05f869f49c6a34672ea2cbbc558428aa1fe48bbfd6115 + 8b1b735a65d99f21e70dbc020bfdface9f724a0d1fb5895db971cc81aa7608baa0920abb0a565c9c + 436e2fd13323428296c86385f2384e408a31e104670df0791d93e743a3a5194ee6b076fb6323ca59 + 3011b7348c16cf58f66b9633906ba54a2ee803187344b394f75dd2e663a57b956cb830dd7a908d4f + 39a2336a61ef9fda549180d4ccde21514d117b6c6fd07a9102b5efe710a32af4eeacae2cb3b1dec0 + 35b9593b48b9d3ca4c13d245d5f04169b0b1 + `, + wantVersion: 57, + wantRest: []rlp.RawValue{{0x06}, {0xC2, 0x07, 0x08}, {0x81, 0xFA}}, + }, +} + +func TestHandshakeForwardCompatibility(t *testing.T) { + var ( + keyA, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") + keyB, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + pubA = crypto.FromECDSAPub(&keyA.PublicKey)[1:] + pubB = crypto.FromECDSAPub(&keyB.PublicKey)[1:] + ephA, _ = crypto.HexToECDSA("869d6ecf5211f1cc60418a13b9d870b22959d0c16f02bec714c960dd2298a32d") + ephB, _ = crypto.HexToECDSA("e238eb8e04fee6511ab04c6dd3c89ce097b11f25d584863ac2b6d5b35b1847e4") + ephPubA = crypto.FromECDSAPub(&ephA.PublicKey)[1:] + ephPubB = crypto.FromECDSAPub(&ephB.PublicKey)[1:] + nonceA = unhex("7e968bba13b6c50e2c4cd7f241cc0d64d1ac25c7f5952df231ac6a2bda8ee5d6") + nonceB = unhex("559aead08264d5795d3909718cdd05abd49572e84fe55590eef31a88a08fdffd") + _, _, _, _ = pubA, pubB, ephPubA, ephPubB + authSignature = unhex("299ca6acfd35e3d72d8ba3d1e2b60b5561d5af5218eb5bc182045769eb4226910a301acae3b369fffc4a4899d6b02531e89fd4fe36a2cf0d93607ba470b50f7800") + _ = authSignature + ) + makeAuth := func(test handshakeAuthTest) *authMsgV4 { + msg := &authMsgV4{Version: test.wantVersion, Rest: test.wantRest, gotPlain: test.isPlain} + copy(msg.Signature[:], authSignature) + copy(msg.InitiatorPubkey[:], pubA) + copy(msg.Nonce[:], nonceA) + return msg + } + makeAck := func(test handshakeAckTest) *authRespV4 { + msg := &authRespV4{Version: test.wantVersion, Rest: test.wantRest} + copy(msg.RandomPubkey[:], ephPubB) + copy(msg.Nonce[:], nonceB) + return msg + } + + // check auth msg parsing + for _, test := range eip8HandshakeAuthTests { + r := bytes.NewReader(unhex(test.input)) + msg := new(authMsgV4) + ciphertext, err := readHandshakeMsg(msg, encAuthMsgLen, keyB, r) + if err != nil { + t.Errorf("error for input %x:\n %v", unhex(test.input), err) + continue + } + if !bytes.Equal(ciphertext, unhex(test.input)) { + t.Errorf("wrong ciphertext for input %x:\n %x", unhex(test.input), ciphertext) + } + want := makeAuth(test) + if !reflect.DeepEqual(msg, want) { + t.Errorf("wrong msg for input %x:\ngot %s\nwant %s", unhex(test.input), spew.Sdump(msg), spew.Sdump(want)) + } + } + + // check auth resp parsing + for _, test := range eip8HandshakeRespTests { + input := unhex(test.input) + r := bytes.NewReader(input) + msg := new(authRespV4) + ciphertext, err := readHandshakeMsg(msg, encAuthRespLen, keyA, r) + if err != nil { + t.Errorf("error for input %x:\n %v", input, err) + continue + } + if !bytes.Equal(ciphertext, input) { + t.Errorf("wrong ciphertext for input %x:\n %x", input, err) + } + want := makeAck(test) + if !reflect.DeepEqual(msg, want) { + t.Errorf("wrong msg for input %x:\ngot %s\nwant %s", input, spew.Sdump(msg), spew.Sdump(want)) + } + } + + // check derivation for (Auth₂, Ack₂) on recipient side + var ( + hs = &encHandshake{ + initiator: false, + respNonce: nonceB, + randomPrivKey: ecies.ImportECDSA(ephB), + } + authCiphertext = unhex(eip8HandshakeAuthTests[1].input) + authRespCiphertext = unhex(eip8HandshakeRespTests[1].input) + authMsg = makeAuth(eip8HandshakeAuthTests[1]) + wantAES = unhex("80e8632c05fed6fc2a13b0f8d31a3cf645366239170ea067065aba8e28bac487") + wantMAC = unhex("2ea74ec5dae199227dff1af715362700e989d889d7a493cb0639691efb8e5f98") + wantFooIngressHash = unhex("0c7ec6340062cc46f5e9f1e3cf86f8c8c403c5a0964f5df0ebd34a75ddc86db5") + ) + if err := hs.handleAuthMsg(authMsg, keyB); err != nil { + t.Fatalf("handleAuthMsg: %v", err) + } + derived, err := hs.secrets(authCiphertext, authRespCiphertext) + if err != nil { + t.Fatalf("secrets: %v", err) + } + if !bytes.Equal(derived.AES, wantAES) { + t.Errorf("aes-secret mismatch:\ngot %x\nwant %x", derived.AES, wantAES) + } + if !bytes.Equal(derived.MAC, wantMAC) { + t.Errorf("mac-secret mismatch:\ngot %x\nwant %x", derived.MAC, wantMAC) + } + io.WriteString(derived.IngressMAC, "foo") + fooIngressHash := derived.IngressMAC.Sum(nil) + if !bytes.Equal(fooIngressHash, wantFooIngressHash) { + t.Errorf("ingress-mac('foo') mismatch:\ngot %x\nwant %x", fooIngressHash, wantFooIngressHash) + } +} -- cgit v1.2.3 From 0c17be92fb95909b2b02a609b0399586d5f02fa5 Mon Sep 17 00:00:00 2001 From: Jeffrey Wilcke Date: Wed, 24 Feb 2016 12:24:02 +0100 Subject: [release/1.3.4] p2p: backward fix for S256 curve --- p2p/rlpx.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'p2p') diff --git a/p2p/rlpx.go b/p2p/rlpx.go index 9d6cba5b6..533b98c67 100644 --- a/p2p/rlpx.go +++ b/p2p/rlpx.go @@ -311,7 +311,7 @@ func (h *encHandshake) makeAuthMsg(prv *ecdsa.PrivateKey, token []byte) (*authMs return nil, err } // Generate random keypair to for ECDH. - h.randomPrivKey, err = ecies.GenerateKey(rand.Reader, secp256k1.S256(), nil) + h.randomPrivKey, err = ecies.GenerateKey(rand.Reader, crypto.S256(), nil) if err != nil { return nil, err } @@ -389,7 +389,7 @@ func (h *encHandshake) handleAuthMsg(msg *authMsgV4, prv *ecdsa.PrivateKey) erro // 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) + h.randomPrivKey, err = ecies.GenerateKey(rand.Reader, crypto.S256(), nil) if err != nil { return err } -- cgit v1.2.3 From 4ce7970340da03a6c185f6e83ebfbb4d1e0d6e49 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Fri, 22 Jan 2016 16:08:23 +0100 Subject: [release/1.3.4] p2p/discover: fix Windows-specific issue for larger-than-buffer packets On Windows, UDPConn.ReadFrom returns an error for packets larger than the receive buffer. The error is not marked temporary, causing our loop to exit when the first oversized packet arrived. The fix is to treat this particular error as temporary. Fixes: #1579, #2087 Updates: #2082 Conflicts: p2p/discover/udp_test.go --- p2p/discover/udp.go | 9 ++++--- p2p/discover/udp_notwindows.go | 26 ++++++++++++++++++++ p2p/discover/udp_test.go | 55 +++++++++++++++++++++++++++++++++++++++--- p2p/discover/udp_windows.go | 40 ++++++++++++++++++++++++++++++ 4 files changed, 123 insertions(+), 7 deletions(-) create mode 100644 p2p/discover/udp_notwindows.go create mode 100644 p2p/discover/udp_windows.go (limited to 'p2p') diff --git a/p2p/discover/udp.go b/p2p/discover/udp.go index 2477a37e0..5fa26ec39 100644 --- a/p2p/discover/udp.go +++ b/p2p/discover/udp.go @@ -455,8 +455,11 @@ func encodePacket(priv *ecdsa.PrivateKey, ptype byte, req interface{}) ([]byte, return packet, nil } -type tempError interface { - Temporary() bool +func isTemporaryError(err error) bool { + tempErr, ok := err.(interface { + Temporary() bool + }) + return ok && tempErr.Temporary() || isPacketTooBig(err) } // readLoop runs in its own goroutine. it handles incoming UDP packets. @@ -468,7 +471,7 @@ func (t *udp) readLoop() { buf := make([]byte, 1280) for { nbytes, from, err := t.conn.ReadFromUDP(buf) - if tempErr, ok := err.(tempError); ok && tempErr.Temporary() { + if isTemporaryError(err) { // Ignore temporary read errors. glog.V(logger.Debug).Infof("Temporary read error: %v", err) continue diff --git a/p2p/discover/udp_notwindows.go b/p2p/discover/udp_notwindows.go new file mode 100644 index 000000000..e9de83aa9 --- /dev/null +++ b/p2p/discover/udp_notwindows.go @@ -0,0 +1,26 @@ +// Copyright 2016 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 . + +//+build !windows + +package discover + +// reports whether err indicates that a UDP packet didn't +// fit the receive buffer. There is no such error on +// non-Windows platforms. +func isPacketTooBig(err error) bool { + return false +} diff --git a/p2p/discover/udp_test.go b/p2p/discover/udp_test.go index 6686e6ff7..b1d07ca01 100644 --- a/p2p/discover/udp_test.go +++ b/p2p/discover/udp_test.go @@ -24,10 +24,8 @@ import ( "errors" "fmt" "io" - logpkg "log" "math/rand" "net" - "os" "path/filepath" "reflect" "runtime" @@ -38,12 +36,61 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/rlp" ) func init() { - logger.AddLogSystem(logger.NewStdLogSystem(os.Stdout, logpkg.LstdFlags, logger.ErrorLevel)) + spew.Config.DisableMethods = true +} + +// This test checks that isPacketTooBig correctly identifies +// errors that result from receiving a UDP packet larger +// than the supplied receive buffer. +func TestIsPacketTooBig(t *testing.T) { + listener, err := net.ListenPacket("udp", "127.0.0.1:0") + if err != nil { + t.Fatal(err) + } + defer listener.Close() + sender, err := net.Dial("udp", listener.LocalAddr().String()) + if err != nil { + t.Fatal(err) + } + defer sender.Close() + + sendN := 1800 + recvN := 300 + for i := 0; i < 20; i++ { + go func() { + buf := make([]byte, sendN) + for i := range buf { + buf[i] = byte(i) + } + sender.Write(buf) + }() + + buf := make([]byte, recvN) + listener.SetDeadline(time.Now().Add(1 * time.Second)) + n, _, err := listener.ReadFrom(buf) + if err != nil { + if nerr, ok := err.(net.Error); ok && nerr.Timeout() { + continue + } + if !isPacketTooBig(err) { + t.Fatal("unexpected read error:", spew.Sdump(err)) + } + continue + } + if n != recvN { + t.Fatalf("short read: %d, want %d", n, recvN) + } + for i := range buf { + if buf[i] != byte(i) { + t.Fatalf("error in pattern") + break + } + } + } } // shared test variables diff --git a/p2p/discover/udp_windows.go b/p2p/discover/udp_windows.go new file mode 100644 index 000000000..66bbf9597 --- /dev/null +++ b/p2p/discover/udp_windows.go @@ -0,0 +1,40 @@ +// Copyright 2016 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 . + +//+build windows + +package discover + +import ( + "net" + "os" + "syscall" +) + +const _WSAEMSGSIZE = syscall.Errno(10040) + +// reports whether err indicates that a UDP packet didn't +// fit the receive buffer. On Windows, WSARecvFrom returns +// code WSAEMSGSIZE and no data if this happens. +func isPacketTooBig(err error) bool { + if opErr, ok := err.(*net.OpError); ok { + if scErr, ok := opErr.Err.(*os.SyscallError); ok { + return scErr.Err == _WSAEMSGSIZE + } + return opErr.Err == _WSAEMSGSIZE + } + return false +} -- cgit v1.2.3