Coverage Summary for Class: HandshakeHandler (org.ethereum.net.rlpx)

Class Class, % Method, % Line, %
HandshakeHandler 0% (0/1) 0% (0/16) 0% (0/172)


1 /* 2  * This file is part of RskJ 3  * Copyright (C) 2017 RSK Labs Ltd. 4  * (derived from ethereumJ library, Copyright (c) 2016 <ether.camp>) 5  * 6  * This program is free software: you can redistribute it and/or modify 7  * it under the terms of the GNU Lesser General Public License as published by 8  * the Free Software Foundation, either version 3 of the License, or 9  * (at your option) any later version. 10  * 11  * This program is distributed in the hope that it will be useful, 12  * but WITHOUT ANY WARRANTY; without even the implied warranty of 13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14  * GNU Lesser General Public License for more details. 15  * 16  * You should have received a copy of the GNU Lesser General Public License 17  * along with this program. If not, see <http://www.gnu.org/licenses/>. 18  */ 19  20 package org.ethereum.net.rlpx; 21  22 import co.rsk.net.NodeID; 23 import co.rsk.scoring.EventType; 24 import co.rsk.scoring.PeerScoringManager; 25 import com.google.common.annotations.VisibleForTesting; 26 import com.google.common.io.ByteStreams; 27 import io.netty.buffer.ByteBuf; 28 import io.netty.channel.ChannelHandlerContext; 29 import io.netty.handler.codec.ByteToMessageDecoder; 30 import org.bouncycastle.crypto.InvalidCipherTextException; 31 import org.bouncycastle.math.ec.ECPoint; 32 import org.bouncycastle.util.encoders.Hex; 33 import org.ethereum.config.SystemProperties; 34 import org.ethereum.crypto.ECIESCoder; 35 import org.ethereum.crypto.ECKey; 36 import org.ethereum.net.client.Capability; 37 import org.ethereum.net.client.ConfigCapabilities; 38 import org.ethereum.net.eth.EthVersion; 39 import org.ethereum.net.message.Message; 40 import org.ethereum.net.p2p.*; 41 import org.ethereum.net.server.Channel; 42 import org.ethereum.util.ByteUtil; 43 import org.slf4j.Logger; 44 import org.slf4j.LoggerFactory; 45  46 import java.io.IOException; 47 import java.net.InetAddress; 48 import java.net.InetSocketAddress; 49 import java.net.SocketAddress; 50 import java.util.List; 51  52 import static org.ethereum.net.eth.EthVersion.fromCode; 53 import static org.ethereum.net.rlpx.FrameCodec.Frame; 54 import static org.ethereum.util.ByteUtil.bigEndianToShort; 55  56 /** 57  * The Netty handler which manages initial negotiation with peer 58  * (when either we initiating connection or remote peer initiates) 59  * 60  * The initial handshake includes: 61  * - first AuthInitiate -> AuthResponse messages when peers exchange with secrets 62  * - second P2P Hello messages when P2P protocol and subprotocol capabilities are negotiated 63  * 64  * After the handshake is done this handler reports secrets and other data to the Channel 65  * which installs further handlers depending on the protocol parameters. 66  * This handler is finally removed from the pipeline. 67  */ 68 public class HandshakeHandler extends ByteToMessageDecoder { 69  70  private final SystemProperties config; 71  private final PeerScoringManager peerScoringManager; 72  private final P2pHandler p2pHandler; 73  private final MessageCodec messageCodec; 74  private final ConfigCapabilities configCapabilities; 75  76  private static final Logger loggerWire = LoggerFactory.getLogger("wire"); 77  private static final Logger loggerNet = LoggerFactory.getLogger("net"); 78  79  private FrameCodec frameCodec; 80  private ECKey myKey; 81  private byte[] nodeId; 82  private byte[] remoteId; 83  private EncryptionHandshake handshake; 84  private byte[] initiatePacket; 85  private Channel channel; 86  87  public HandshakeHandler( 88  SystemProperties config, 89  PeerScoringManager peerScoringManager, 90  P2pHandler p2pHandler, 91  MessageCodec messageCodec, 92  ConfigCapabilities configCapabilities) { 93  this.config = config; 94  this.peerScoringManager = peerScoringManager; 95  this.p2pHandler = p2pHandler; 96  this.messageCodec = messageCodec; 97  this.configCapabilities = configCapabilities; 98  this.myKey = config.getMyKey(); 99  } 100  101  @Override 102  public void channelActive(ChannelHandlerContext ctx) throws Exception { 103  channel.setInetSocketAddress((InetSocketAddress) ctx.channel().remoteAddress()); 104  internalChannelActive(ctx); 105  } 106  107  @VisibleForTesting 108  public void internalChannelActive(ChannelHandlerContext ctx) throws Exception { 109  if (remoteId.length == 64) { 110  channel.setNode(remoteId); 111  initiate(ctx); 112  } else { 113  handshake = new EncryptionHandshake(); 114  nodeId = myKey.getNodeId(); 115  } 116  } 117  118  protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { 119  loggerWire.debug("Decoding handshake... ({} bytes available)", in.readableBytes()); 120  decodeHandshake(ctx, in); 121  } 122  123  private void initiate(ChannelHandlerContext ctx) throws Exception { 124  125  loggerNet.trace("RLPX protocol activated"); 126  127  nodeId = myKey.getNodeId(); 128  129  byte[] remotePublicBytes = new byte[remoteId.length + 1]; 130  System.arraycopy(remoteId, 0, remotePublicBytes, 1, remoteId.length); 131  remotePublicBytes[0] = 0x04; // uncompressed 132  ECPoint remotePublic = ECKey.fromPublicOnly(remotePublicBytes).getPubKeyPoint(); 133  handshake = new EncryptionHandshake(remotePublic); 134  135  Object msg; 136  if (config.eip8()) { 137  AuthInitiateMessageV4 initiateMessage = handshake.createAuthInitiateV4(myKey); 138  initiatePacket = handshake.encryptAuthInitiateV4(initiateMessage); 139  msg = initiateMessage; 140  } else { 141  AuthInitiateMessage initiateMessage = handshake.createAuthInitiate(null, myKey); 142  initiatePacket = handshake.encryptAuthMessage(initiateMessage); 143  msg = initiateMessage; 144  } 145  146  final ByteBuf byteBufMsg = ctx.alloc().buffer(initiatePacket.length); 147  byteBufMsg.writeBytes(initiatePacket); 148  ctx.writeAndFlush(byteBufMsg).sync(); 149  150  channel.getNodeStatistics().rlpxAuthMessagesSent.add(); 151  152  loggerNet.trace("To: \t{} \tSend: \t{}", ctx.channel().remoteAddress(), msg); 153  } 154  155  // consume handshake, producing no resulting message to upper layers 156  private void decodeHandshake(final ChannelHandlerContext ctx, ByteBuf buffer) throws Exception { 157  158  if (handshake.isInitiator()) { 159  if (frameCodec == null) { 160  161  byte[] responsePacket = new byte[AuthResponseMessage.getLength() + ECIESCoder.getOverhead()]; 162  if (!buffer.isReadable(responsePacket.length)) { 163  return; 164  } 165  buffer.readBytes(responsePacket); 166  167  try { 168  169  // trying to decode as pre-EIP-8 170  171  AuthResponseMessage response = handshake.handleAuthResponse(myKey, initiatePacket, responsePacket); 172  loggerNet.trace("From: \t{} \tRecv: \t{}", ctx.channel().remoteAddress(), response); 173  174  } catch (Throwable t) { 175  176  // it must be format defined by EIP-8 then 177  178  responsePacket = readEIP8Packet(buffer, responsePacket); 179  180  if (responsePacket == null) { 181  return; 182  } 183  184  AuthResponseMessageV4 response = handshake.handleAuthResponseV4(myKey, initiatePacket, responsePacket); 185  loggerNet.trace("From: \t{} \tRecv: \t{}", ctx.channel().remoteAddress(), response); 186  } 187  188  EncryptionHandshake.Secrets secrets = this.handshake.getSecrets(); 189  this.frameCodec = new FrameCodec(secrets); 190  191  loggerNet.trace("auth exchange done"); 192  channel.sendHelloMessage(ctx, frameCodec, ByteUtil.toHexString(nodeId), null); 193  } else { 194  loggerWire.debug("MessageCodec: Buffer bytes: {}", buffer.readableBytes()); 195  List<Frame> frames = frameCodec.readFrames(buffer); 196  if (frames == null || frames.isEmpty()) { 197  return; 198  } 199  Frame frame = frames.get(0); 200  byte[] payload = ByteStreams.toByteArray(frame.getStream()); 201  if (frame.getType() == P2pMessageCodes.HELLO.asByte()) { 202  HelloMessage helloMessage = new HelloMessage(payload); 203  loggerNet.trace("From: \t{} \tRecv: \t{}", ctx.channel().remoteAddress(), helloMessage); 204  processHelloMessage(ctx, helloMessage); 205  } else { 206  DisconnectMessage message = new DisconnectMessage(payload); 207  loggerNet.trace("From: \t{} \tRecv: \t{}", channel, message); 208  channel.getNodeStatistics().nodeDisconnectedRemote(message.getReason()); 209  } 210  } 211  } else { 212  loggerWire.debug("Not initiator."); 213  if (frameCodec == null) { 214  loggerWire.debug("FrameCodec == null"); 215  byte[] authInitPacket = new byte[AuthInitiateMessage.getLength() + ECIESCoder.getOverhead()]; 216  if (!buffer.isReadable(authInitPacket.length)) { 217  return; 218  } 219  buffer.readBytes(authInitPacket); 220  221  this.handshake = new EncryptionHandshake(); 222  223  byte[] responsePacket; 224  225  try { 226  227  // trying to decode as pre-EIP-8 228  AuthInitiateMessage initiateMessage = handshake.decryptAuthInitiate(authInitPacket, myKey); 229  loggerNet.trace("From: \t{} \tRecv: \t{}", ctx.channel().remoteAddress(), initiateMessage); 230  231  AuthResponseMessage response = handshake.makeAuthInitiate(initiateMessage, myKey); 232  loggerNet.trace("To: \t{} \tSend: \t{}", ctx.channel().remoteAddress(), response); 233  responsePacket = handshake.encryptAuthResponse(response); 234  235  } catch (Throwable t) { 236  237  // it must be format defined by EIP-8 then 238  try { 239  240  authInitPacket = readEIP8Packet(buffer, authInitPacket); 241  242  if (authInitPacket == null) { 243  return; 244  } 245  246  AuthInitiateMessageV4 initiateMessage = handshake.decryptAuthInitiateV4(authInitPacket, myKey); 247  loggerNet.trace("From: \t{} \tRecv: \t{}", ctx.channel().remoteAddress(), initiateMessage); 248  249  AuthResponseMessageV4 response = handshake.makeAuthInitiateV4(initiateMessage, myKey); 250  loggerNet.trace("To: \t{} \tSend: \t{}", ctx.channel().remoteAddress(), response); 251  responsePacket = handshake.encryptAuthResponseV4(response); 252  253  } catch (InvalidCipherTextException ce) { 254  loggerNet.warn( 255  "Can't decrypt AuthInitiateMessage from {}. Most likely the remote peer used wrong public key (NodeID) to encrypt message.", 256  ctx.channel().remoteAddress() 257  ); 258  return; 259  } 260  } 261  262  handshake.agreeSecret(authInitPacket, responsePacket); 263  264  EncryptionHandshake.Secrets secrets = this.handshake.getSecrets(); 265  this.frameCodec = new FrameCodec(secrets); 266  267  ECPoint remotePubKey = this.handshake.getRemotePublicKey(); 268  269  byte[] compressed = remotePubKey.getEncoded(false); 270  271  this.remoteId = new byte[compressed.length - 1]; 272  System.arraycopy(compressed, 1, this.remoteId, 0, this.remoteId.length); 273  channel.setNode(remoteId); 274  275  final ByteBuf byteBufMsg = ctx.alloc().buffer(responsePacket.length); 276  byteBufMsg.writeBytes(responsePacket); 277  ctx.writeAndFlush(byteBufMsg).sync(); 278  } else { 279  List<Frame> frames = frameCodec.readFrames(buffer); 280  if (frames == null || frames.isEmpty()) { 281  return; 282  } 283  Frame frame = frames.get(0); 284  285  Message message = new P2pMessageFactory().create((byte) frame.getType(), 286  ByteStreams.toByteArray(frame.getStream())); 287  loggerNet.trace("From: \t{} \tRecv: \t{}", ctx.channel().remoteAddress(), message); 288  289  if (frame.getType() == P2pMessageCodes.DISCONNECT.asByte()) { 290  loggerNet.info("Active remote peer disconnected right after handshake."); 291  return; 292  } 293  294  if (frame.getType() != P2pMessageCodes.HELLO.asByte()) { 295  throw new RuntimeException("The message type is not HELLO or DISCONNECT: " + message); 296  } 297  298  HelloMessage inboundHelloMessage = (HelloMessage) message; 299  300  // Secret authentication finish here 301  channel.sendHelloMessage(ctx, frameCodec, ByteUtil.toHexString(nodeId), inboundHelloMessage); 302  processHelloMessage(ctx, inboundHelloMessage); 303  } 304  } 305  channel.getNodeStatistics().rlpxInHello.add(); 306  } 307  308  private byte[] readEIP8Packet(ByteBuf buffer, byte[] plainPacket) { 309  310  int size = bigEndianToShort(plainPacket); 311  if (size < plainPacket.length) { 312  throw new IllegalArgumentException("AuthResponse packet size is too low"); 313  } 314  315  int bytesLeft = size - plainPacket.length + 2; 316  byte[] restBytes = new byte[bytesLeft]; 317  318  if (!buffer.isReadable(restBytes.length)) { 319  return null; 320  } 321  322  buffer.readBytes(restBytes); 323  324  byte[] fullResponse = new byte[size + 2]; 325  System.arraycopy(plainPacket, 0, fullResponse, 0, plainPacket.length); 326  System.arraycopy(restBytes, 0, fullResponse, plainPacket.length, restBytes.length); 327  328  return fullResponse; 329  } 330  331  public void setRemoteId(String remoteId, Channel channel){ 332  this.remoteId = Hex.decode(remoteId); 333  this.channel = channel; 334  } 335  336  /** 337  * Generate random Key (and thus NodeID) per channel for 'anonymous' 338  * connection (e.g. for peer discovery) 339  */ 340  public void generateTempKey() { 341  myKey = new ECKey(); 342  } 343  344  @Override 345  public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 346  recordFailedHandshake(ctx); 347  if (cause instanceof IOException) { 348  loggerNet.info("Handshake failed: address {}", ctx.channel().remoteAddress(), cause); 349  } else { 350  loggerNet.warn("Handshake failed", cause); 351  } 352  ctx.close(); 353  } 354  355  private void recordFailedHandshake(ChannelHandlerContext ctx) { 356  recordEvent(ctx, EventType.FAILED_HANDSHAKE); 357  } 358  359  private void recordSuccessfulHandshake(ChannelHandlerContext ctx) { 360  recordEvent(ctx, EventType.SUCCESSFUL_HANDSHAKE); 361  } 362  363  private void recordEvent(ChannelHandlerContext ctx, EventType event) { 364  SocketAddress socketAddress = ctx.channel().remoteAddress(); 365  366  //TODO(mmarquez): what if it is not ?? 367  if (socketAddress instanceof InetSocketAddress) { 368  NodeID nodeID = channel.getNodeId(); 369  370  InetAddress address = ((InetSocketAddress)socketAddress).getAddress(); 371  372  peerScoringManager.recordEvent(nodeID, address, event); 373  } 374  } 375  376  private void processHelloMessage(ChannelHandlerContext ctx, HelloMessage helloMessage) { 377  List<Capability> capInCommon = configCapabilities.getSupportedCapabilities(helloMessage); 378  channel.initMessageCodes(capInCommon); 379  for (Capability capability : capInCommon) { 380  // It seems that the only supported capability is RSK, and everything else is ignored. 381  if (Capability.RSK.equals(capability.getName())) { 382  publicRLPxHandshakeFinished(ctx, helloMessage, fromCode(capability.getVersion())); 383  return; 384  } 385  } 386  387  throw new RuntimeException("The remote peer didn't support the RSK capability"); 388  } 389  390  private void publicRLPxHandshakeFinished(ChannelHandlerContext ctx, HelloMessage helloRemote, EthVersion ethVersion) { 391  if (!P2pHandler.isProtocolVersionSupported(helloRemote.getP2PVersion())) { 392  throw new RuntimeException(String.format( 393  "The remote peer protocol version %s isn't supported", helloRemote.getP2PVersion() 394  )); 395  } 396  397  loggerNet.debug("publicRLPxHandshakeFinished with {}", ctx.channel().remoteAddress()); 398  if (helloRemote.getP2PVersion() < 5) { 399  messageCodec.setSupportChunkedFrames(false); 400  } 401  402  FrameCodecHandler frameCodecHandler = new FrameCodecHandler(frameCodec, channel); 403  ctx.pipeline().addLast("medianFrameCodec", frameCodecHandler); 404  ctx.pipeline().addLast("messageCodec", messageCodec); 405  ctx.pipeline().addLast(Capability.P2P, p2pHandler); 406  407  p2pHandler.setChannel(channel); 408  p2pHandler.setHandshake(helloRemote); 409  channel.activateEth(ctx, ethVersion); 410  411  channel.getNodeStatistics().rlpxHandshake.add(); 412  413  recordSuccessfulHandshake(ctx); 414  415  loggerWire.debug("Handshake done, removing HandshakeHandler from pipeline."); 416  ctx.pipeline().remove(this); 417  } 418 }