When tls is on, use plain framer and handshaker to avoid double encryption (#3008)

* When tls is on, use passthrough framer and passthrough handshaker to avoid double encryption

Signed-off-by: Saravana Perumal Shanmugam <perusworld@linux.com>
pull/3014/head
Saravana Perumal Shanmugam 3 years ago committed by GitHub
parent a2f517ce32
commit bba8032883
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 67
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/MessageHandler.java
  2. 46
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/MessageType.java
  3. 54
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/PlainFramer.java
  4. 162
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/PlainHandshaker.java
  5. 51
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/PlainMessage.java
  6. 15
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/AbstractHandshakeHandler.java
  7. 10
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/HandshakeHandlerInbound.java
  8. 10
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/HandshakeHandlerOutbound.java
  9. 27
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/NettyConnectionInitializer.java
  10. 15
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/NettyTLSConnectionInitializer.java
  11. 7
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/framing/Framer.java
  12. 22
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/framing/FramerProvider.java
  13. 20
      ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/handshake/HandshakerProvider.java
  14. 48
      ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/FileBasedPasswordProvider.java
  15. 444
      ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/P2PPlainNetworkTest.java
  16. 65
      ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/plain/MessageHandlerTest.java

@ -0,0 +1,67 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.p2p.plain;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPInput;
import org.hyperledger.besu.ethereum.rlp.BytesValueRLPOutput;
import org.hyperledger.besu.ethereum.rlp.RLPInput;
import io.netty.buffer.ByteBuf;
import org.apache.tuweni.bytes.Bytes;
public class MessageHandler {
public static Bytes buildMessage(final PlainMessage message) {
final BytesValueRLPOutput ret = new BytesValueRLPOutput();
ret.startList();
ret.writeInt(message.getMessageType().getValue());
if (MessageType.DATA.equals(message.getMessageType())) {
ret.writeInt(message.getCode());
}
ret.writeBytes(message.getData());
ret.endList();
return ret.encoded();
}
public static Bytes buildMessage(final MessageType messageType, final Bytes data) {
return buildMessage(new PlainMessage(messageType, data));
}
public static Bytes buildMessage(
final MessageType messageType, final int code, final Bytes data) {
return buildMessage(new PlainMessage(messageType, code, data));
}
public static Bytes buildMessage(final MessageType messageType, final byte[] data) {
return buildMessage(new PlainMessage(messageType, data));
}
public static PlainMessage parseMessage(final ByteBuf buf) {
PlainMessage ret = null;
final ByteBuf bufferedBytes = buf.readSlice(buf.readableBytes());
final byte[] byteArr = new byte[bufferedBytes.readableBytes()];
bufferedBytes.getBytes(0, byteArr);
Bytes bytes = Bytes.wrap(byteArr);
final RLPInput input = new BytesValueRLPInput(bytes, true);
input.enterList();
MessageType type = MessageType.forNumber(input.readInt());
if (MessageType.DATA.equals(type)) {
ret = new PlainMessage(type, input.readInt(), input.readBytes());
} else {
ret = new PlainMessage(type, input.readBytes());
}
return ret;
}
}

@ -0,0 +1,46 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.p2p.plain;
public enum MessageType {
PING(0),
PONG(1),
DATA(2),
UNRECOGNIZED(-1),
;
private final int value;
private MessageType(final int value) {
this.value = value;
}
public int getValue() {
return value;
}
public static MessageType forNumber(final int value) {
switch (value) {
case 0:
return PING;
case 1:
return PONG;
case 2:
return DATA;
default:
return null;
}
}
}

@ -0,0 +1,54 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.p2p.plain;
import static com.google.common.base.Preconditions.checkState;
import org.hyperledger.besu.ethereum.p2p.rlpx.framing.Framer;
import org.hyperledger.besu.ethereum.p2p.rlpx.framing.FramingException;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.MessageData;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.RawMessage;
import io.netty.buffer.ByteBuf;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class PlainFramer extends Framer {
private static final Logger LOG = LogManager.getLogger();
public PlainFramer() {
LOG.trace("Initialising PlainFramer");
}
@Override
public synchronized MessageData deframe(final ByteBuf buf) throws FramingException {
LOG.trace("Deframing Message");
if (buf == null || !buf.isReadable()) {
return null;
}
PlainMessage message = MessageHandler.parseMessage(buf);
checkState(
MessageType.DATA.equals(message.getMessageType()), "unexpected message: needs to be data");
return new RawMessage(message.getCode(), message.getData());
}
@Override
public synchronized void frame(final MessageData message, final ByteBuf output) {
LOG.trace("Framing Message");
output.writeBytes(
MessageHandler.buildMessage(MessageType.DATA, message.getCode(), message.getData())
.toArray());
}
}

@ -0,0 +1,162 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.p2p.plain;
import static com.google.common.base.Preconditions.checkState;
import org.hyperledger.besu.crypto.NodeKey;
import org.hyperledger.besu.crypto.SECPPublicKey;
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
import org.hyperledger.besu.ethereum.p2p.rlpx.handshake.HandshakeException;
import org.hyperledger.besu.ethereum.p2p.rlpx.handshake.HandshakeSecrets;
import org.hyperledger.besu.ethereum.p2p.rlpx.handshake.Handshaker;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.tuweni.bytes.Bytes;
public class PlainHandshaker implements Handshaker {
private static final Logger LOG = LogManager.getLogger();
private final AtomicReference<Handshaker.HandshakeStatus> status =
new AtomicReference<>(Handshaker.HandshakeStatus.UNINITIALIZED);
private boolean initiator;
private NodeKey nodeKey;
private SECPPublicKey partyPubKey;
private Bytes initiatorMsg;
private Bytes responderMsg;
@Override
public void prepareInitiator(final NodeKey nodeKey, final SECPPublicKey theirPubKey) {
checkState(
status.compareAndSet(
Handshaker.HandshakeStatus.UNINITIALIZED, Handshaker.HandshakeStatus.PREPARED),
"handshake was already prepared");
this.initiator = true;
this.nodeKey = nodeKey;
this.partyPubKey = theirPubKey;
LOG.trace(
"Prepared plain handshake with node {}... under INITIATOR role",
theirPubKey.getEncodedBytes().slice(0, 16));
}
@Override
public void prepareResponder(final NodeKey nodeKey) {
checkState(
status.compareAndSet(
Handshaker.HandshakeStatus.UNINITIALIZED, Handshaker.HandshakeStatus.IN_PROGRESS),
"handshake was already prepared");
this.initiator = false;
this.nodeKey = nodeKey;
LOG.trace("Prepared plain handshake under RESPONDER role");
}
@Override
public ByteBuf firstMessage() throws HandshakeException {
checkState(initiator, "illegal invocation of firstMessage on non-initiator end of handshake");
checkState(
status.compareAndSet(
Handshaker.HandshakeStatus.PREPARED, Handshaker.HandshakeStatus.IN_PROGRESS),
"illegal invocation of firstMessage, handshake had already started");
initiatorMsg =
MessageHandler.buildMessage(MessageType.PING, nodeKey.getPublicKey().getEncoded());
LOG.trace("First plain handshake message under INITIATOR role");
return Unpooled.wrappedBuffer(initiatorMsg.toArray());
}
@Override
public Optional<ByteBuf> handleMessage(final ByteBuf buf) throws HandshakeException {
checkState(
status.get() == Handshaker.HandshakeStatus.IN_PROGRESS,
"illegal invocation of onMessage on handshake that is not in progress");
PlainMessage message = MessageHandler.parseMessage(buf);
Optional<Bytes> nextMsg = Optional.empty();
if (initiator) {
checkState(
responderMsg == null,
"unexpected message: responder message had " + "already been received");
checkState(
message.getMessageType().equals(MessageType.PONG),
"unexpected message: needs to be a pong");
responderMsg = message.getData();
LOG.trace(
"Received responder's plain handshake message from node {}...: {}",
partyPubKey.getEncodedBytes().slice(0, 16),
responderMsg);
} else {
checkState(
initiatorMsg == null,
"unexpected message: initiator message " + "had already been received");
checkState(
message.getMessageType().equals(MessageType.PING),
"unexpected message: needs to be a ping");
initiatorMsg = message.getData();
LOG.trace(
"[{}] Received initiator's plain handshake message: {}",
nodeKey.getPublicKey().getEncodedBytes(),
initiatorMsg);
partyPubKey = SignatureAlgorithmFactory.getInstance().createPublicKey(message.getData());
responderMsg =
MessageHandler.buildMessage(MessageType.PONG, nodeKey.getPublicKey().getEncoded());
LOG.trace(
"Generated responder's plain handshake message against peer {}...: {}",
partyPubKey.getEncodedBytes().slice(0, 16),
responderMsg);
nextMsg = Optional.of(Bytes.wrap(responderMsg.toArray()));
}
status.set(Handshaker.HandshakeStatus.SUCCESS);
LOG.trace("Handshake status set to {}", status.get());
return nextMsg.map(bv -> Unpooled.wrappedBuffer(bv.toArray()));
}
void computeSecrets() {}
@Override
public HandshakeStatus getStatus() {
return status.get();
}
@Override
public HandshakeSecrets secrets() {
return null;
}
@Override
public SECPPublicKey partyPubKey() {
return partyPubKey;
}
}

@ -0,0 +1,51 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.p2p.plain;
import org.apache.tuweni.bytes.Bytes;
public class PlainMessage {
private final MessageType messageType;
private final int code;
private final Bytes data;
public PlainMessage(final MessageType messageType, final byte[] data) {
this(messageType, Bytes.wrap(data));
}
public PlainMessage(final MessageType messageType, final Bytes data) {
this(messageType, -1, data);
}
public PlainMessage(final MessageType messageType, final int code, final Bytes data) {
super();
this.messageType = messageType;
this.code = code;
this.data = data;
}
public MessageType getMessageType() {
return messageType;
}
public Bytes getData() {
return data;
}
public int getCode() {
return code;
}
}

@ -19,8 +19,9 @@ import org.hyperledger.besu.ethereum.p2p.peers.Peer;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnectionEventDispatcher;
import org.hyperledger.besu.ethereum.p2p.rlpx.framing.Framer;
import org.hyperledger.besu.ethereum.p2p.rlpx.framing.FramerProvider;
import org.hyperledger.besu.ethereum.p2p.rlpx.handshake.Handshaker;
import org.hyperledger.besu.ethereum.p2p.rlpx.handshake.ecies.ECIESHandshaker;
import org.hyperledger.besu.ethereum.p2p.rlpx.handshake.HandshakerProvider;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.SubProtocol;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.HelloMessage;
@ -44,7 +45,7 @@ abstract class AbstractHandshakeHandler extends SimpleChannelInboundHandler<Byte
private static final Logger LOG = LogManager.getLogger();
protected final Handshaker handshaker = new ECIESHandshaker();
protected final Handshaker handshaker;
// The peer we are expecting to connect to, if such a peer is known
private final Optional<Peer> expectedPeer;
@ -57,19 +58,25 @@ abstract class AbstractHandshakeHandler extends SimpleChannelInboundHandler<Byte
private final MetricsSystem metricsSystem;
private final FramerProvider framerProvider;
AbstractHandshakeHandler(
final List<SubProtocol> subProtocols,
final LocalNode localNode,
final Optional<Peer> expectedPeer,
final CompletableFuture<PeerConnection> connectionFuture,
final PeerConnectionEventDispatcher connectionEventDispatcher,
final MetricsSystem metricsSystem) {
final MetricsSystem metricsSystem,
final HandshakerProvider handshakerProvider,
final FramerProvider framerProvider) {
this.subProtocols = subProtocols;
this.localNode = localNode;
this.expectedPeer = expectedPeer;
this.connectionFuture = connectionFuture;
this.connectionEventDispatcher = connectionEventDispatcher;
this.metricsSystem = metricsSystem;
this.handshaker = handshakerProvider.buildInstance();
this.framerProvider = framerProvider;
}
/**
@ -100,7 +107,7 @@ abstract class AbstractHandshakeHandler extends SimpleChannelInboundHandler<Byte
LOG.trace("Sending framed hello");
// Exchange keys done
final Framer framer = new Framer(handshaker.secrets());
final Framer framer = this.framerProvider.buildFramer(handshaker.secrets());
final ByteToMessageDecoder deFramer =
new DeFramer(

@ -18,7 +18,9 @@ import org.hyperledger.besu.crypto.NodeKey;
import org.hyperledger.besu.ethereum.p2p.peers.LocalNode;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnectionEventDispatcher;
import org.hyperledger.besu.ethereum.p2p.rlpx.framing.FramerProvider;
import org.hyperledger.besu.ethereum.p2p.rlpx.handshake.Handshaker;
import org.hyperledger.besu.ethereum.p2p.rlpx.handshake.HandshakerProvider;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.SubProtocol;
import org.hyperledger.besu.plugin.services.MetricsSystem;
@ -36,14 +38,18 @@ final class HandshakeHandlerInbound extends AbstractHandshakeHandler {
final LocalNode localNode,
final CompletableFuture<PeerConnection> connectionFuture,
final PeerConnectionEventDispatcher connectionEventDispatcher,
final MetricsSystem metricsSystem) {
final MetricsSystem metricsSystem,
final HandshakerProvider handshakerProvider,
final FramerProvider framerProvider) {
super(
subProtocols,
localNode,
Optional.empty(),
connectionFuture,
connectionEventDispatcher,
metricsSystem);
metricsSystem,
handshakerProvider,
framerProvider);
handshaker.prepareResponder(nodeKey);
}

@ -20,7 +20,9 @@ import org.hyperledger.besu.ethereum.p2p.peers.LocalNode;
import org.hyperledger.besu.ethereum.p2p.peers.Peer;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnectionEventDispatcher;
import org.hyperledger.besu.ethereum.p2p.rlpx.framing.FramerProvider;
import org.hyperledger.besu.ethereum.p2p.rlpx.handshake.Handshaker;
import org.hyperledger.besu.ethereum.p2p.rlpx.handshake.HandshakerProvider;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.SubProtocol;
import org.hyperledger.besu.plugin.services.MetricsSystem;
@ -46,14 +48,18 @@ final class HandshakeHandlerOutbound extends AbstractHandshakeHandler {
final LocalNode localNode,
final CompletableFuture<PeerConnection> connectionFuture,
final PeerConnectionEventDispatcher connectionEventDispatcher,
final MetricsSystem metricsSystem) {
final MetricsSystem metricsSystem,
final HandshakerProvider handshakerProvider,
final FramerProvider framerProvider) {
super(
subProtocols,
localNode,
Optional.of(peer),
connectionFuture,
connectionEventDispatcher,
metricsSystem);
metricsSystem,
handshakerProvider,
framerProvider);
handshaker.prepareInitiator(
nodeKey, SignatureAlgorithmFactory.getInstance().createPublicKey(peer.getId()));
this.first = handshaker.firstMessage();

@ -23,6 +23,12 @@ import org.hyperledger.besu.ethereum.p2p.rlpx.ConnectCallback;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.ConnectionInitializer;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnectionEventDispatcher;
import org.hyperledger.besu.ethereum.p2p.rlpx.framing.Framer;
import org.hyperledger.besu.ethereum.p2p.rlpx.framing.FramerProvider;
import org.hyperledger.besu.ethereum.p2p.rlpx.handshake.HandshakeSecrets;
import org.hyperledger.besu.ethereum.p2p.rlpx.handshake.Handshaker;
import org.hyperledger.besu.ethereum.p2p.rlpx.handshake.HandshakerProvider;
import org.hyperledger.besu.ethereum.p2p.rlpx.handshake.ecies.ECIESHandshaker;
import org.hyperledger.besu.metrics.BesuMetricCategory;
import org.hyperledger.besu.plugin.data.EnodeURL;
import org.hyperledger.besu.plugin.services.MetricsSystem;
@ -51,7 +57,8 @@ import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.concurrent.SingleThreadEventExecutor;
import org.jetbrains.annotations.NotNull;
public class NettyConnectionInitializer implements ConnectionInitializer {
public class NettyConnectionInitializer
implements ConnectionInitializer, HandshakerProvider, FramerProvider {
private static final int TIMEOUT_SECONDS = 10;
@ -231,7 +238,9 @@ public class NettyConnectionInitializer implements ConnectionInitializer {
localNode,
connectionFuture,
eventDispatcher,
metricsSystem);
metricsSystem,
this,
this);
}
@NotNull
@ -244,7 +253,9 @@ public class NettyConnectionInitializer implements ConnectionInitializer {
localNode,
connectionFuture,
eventDispatcher,
metricsSystem);
metricsSystem,
this,
this);
}
@NotNull
@ -269,4 +280,14 @@ public class NettyConnectionInitializer implements ConnectionInitializer {
.mapToInt(eventExecutor -> ((SingleThreadEventExecutor) eventExecutor).pendingTasks())
.sum();
}
@Override
public Handshaker buildInstance() {
return new ECIESHandshaker();
}
@Override
public Framer buildFramer(final HandshakeSecrets secrets) {
return new Framer(secrets);
}
}

@ -17,7 +17,12 @@ package org.hyperledger.besu.ethereum.p2p.rlpx.connections.netty;
import org.hyperledger.besu.crypto.NodeKey;
import org.hyperledger.besu.ethereum.p2p.config.RlpxConfiguration;
import org.hyperledger.besu.ethereum.p2p.peers.LocalNode;
import org.hyperledger.besu.ethereum.p2p.plain.PlainFramer;
import org.hyperledger.besu.ethereum.p2p.plain.PlainHandshaker;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnectionEventDispatcher;
import org.hyperledger.besu.ethereum.p2p.rlpx.framing.Framer;
import org.hyperledger.besu.ethereum.p2p.rlpx.handshake.HandshakeSecrets;
import org.hyperledger.besu.ethereum.p2p.rlpx.handshake.Handshaker;
import org.hyperledger.besu.plugin.services.MetricsSystem;
import java.io.IOException;
@ -61,4 +66,14 @@ public class NettyTLSConnectionInitializer extends NettyConnectionInitializer {
ch.pipeline().addLast("ssl", sslContext.newHandler(ch.alloc()));
}
}
@Override
public Handshaker buildInstance() {
return new PlainHandshaker();
}
@Override
public Framer buildFramer(final HandshakeSecrets secrets) {
return new PlainFramer();
}
}

@ -86,6 +86,13 @@ public class Framer {
// have we ever successfully uncompressed a packet?
private boolean compressionSuccessful = false;
protected Framer() {
this.secrets = null;
this.encryptor = null;
this.decryptor = null;
this.macEncryptor = null;
}
/**
* Creates a new framer out of the handshake secrets derived during the cryptographic handshake.
*

@ -0,0 +1,22 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.p2p.rlpx.framing;
import org.hyperledger.besu.ethereum.p2p.rlpx.handshake.HandshakeSecrets;
public interface FramerProvider {
public Framer buildFramer(HandshakeSecrets secrets);
}

@ -0,0 +1,20 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.p2p.rlpx.handshake;
public interface HandshakerProvider {
public Handshaker buildInstance();
}

@ -0,0 +1,48 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.p2p.network;
import static java.util.Objects.requireNonNull;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.function.Supplier;
import java.util.stream.Stream;
public class FileBasedPasswordProvider implements Supplier<String> {
private final Path passwordFile;
public FileBasedPasswordProvider(final Path passwordFile) {
requireNonNull(passwordFile, "Password file path cannot be null");
this.passwordFile = passwordFile;
}
@Override
public String get() {
try (final Stream<String> fileStream = Files.lines(passwordFile)) {
return fileStream
.findFirst()
.orElseThrow(
() ->
new RuntimeException(
String.format(
"Unable to read keystore password from %s", passwordFile.toString())));
} catch (final IOException e) {
throw new RuntimeException(
String.format("Unable to read keystore password file %s", passwordFile.toString()), e);
}
}
}

@ -0,0 +1,444 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.p2p.network;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.hyperledger.besu.crypto.NodeKey;
import org.hyperledger.besu.crypto.NodeKeyUtils;
import org.hyperledger.besu.ethereum.core.InMemoryKeyValueStorageProvider;
import org.hyperledger.besu.ethereum.p2p.config.DiscoveryConfiguration;
import org.hyperledger.besu.ethereum.p2p.config.NetworkingConfiguration;
import org.hyperledger.besu.ethereum.p2p.config.RlpxConfiguration;
import org.hyperledger.besu.ethereum.p2p.network.exceptions.IncompatiblePeerException;
import org.hyperledger.besu.ethereum.p2p.peers.DefaultPeer;
import org.hyperledger.besu.ethereum.p2p.peers.EnodeURLImpl;
import org.hyperledger.besu.ethereum.p2p.peers.Peer;
import org.hyperledger.besu.ethereum.p2p.permissions.PeerPermissions;
import org.hyperledger.besu.ethereum.p2p.permissions.PeerPermissionsDenylist;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.PeerConnection;
import org.hyperledger.besu.ethereum.p2p.rlpx.connections.netty.TLSConfiguration;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.Capability;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.SubProtocol;
import org.hyperledger.besu.ethereum.p2p.rlpx.wire.messages.DisconnectMessage.DisconnectReason;
import org.hyperledger.besu.metrics.noop.NoOpMetricsSystem;
import org.hyperledger.besu.pki.keystore.KeyStoreWrapper;
import org.hyperledger.besu.plugin.data.EnodeURL;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import com.google.common.base.Charsets;
import io.vertx.core.Vertx;
import org.apache.tuweni.bytes.Bytes;
import org.assertj.core.api.Assertions;
import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class P2PPlainNetworkTest {
private final Vertx vertx = Vertx.vertx();
private final NetworkingConfiguration config =
NetworkingConfiguration.create()
.setDiscovery(DiscoveryConfiguration.create().setActive(false))
.setRlpx(RlpxConfiguration.create().setBindPort(0).setSupportedProtocols(subProtocol()));
@After
public void closeVertx() {
vertx.close();
}
@Test
public void handshaking() throws Exception {
final NodeKey nodeKey = NodeKeyUtils.generate();
try (final P2PNetwork listener = builder("partner1client1").nodeKey(nodeKey).build();
final P2PNetwork connector = builder("partner2client1").build()) {
listener.start();
connector.start();
final EnodeURL listenerEnode = listener.getLocalEnode().get();
final Bytes listenId = listenerEnode.getNodeId();
final int listenPort = listenerEnode.getListeningPort().get();
Assertions.assertThat(
connector
.connect(createPeer(listenId, listenPort))
.get(30L, TimeUnit.SECONDS)
.getPeerInfo()
.getNodeId())
.isEqualTo(listenId);
}
}
@Test
public void preventMultipleConnections() throws Exception {
final NodeKey listenNodeKey = NodeKeyUtils.generate();
try (final P2PNetwork listener = builder("partner1client1").nodeKey(listenNodeKey).build();
final P2PNetwork connector = builder("partner2client1").build()) {
listener.start();
connector.start();
final EnodeURL listenerEnode = listener.getLocalEnode().get();
final Bytes listenId = listenerEnode.getNodeId();
final int listenPort = listenerEnode.getListeningPort().get();
final CompletableFuture<PeerConnection> firstFuture =
connector.connect(createPeer(listenId, listenPort));
final CompletableFuture<PeerConnection> secondFuture =
connector.connect(createPeer(listenId, listenPort));
final PeerConnection firstConnection = firstFuture.get(30L, TimeUnit.SECONDS);
final PeerConnection secondConnection = secondFuture.get(30L, TimeUnit.SECONDS);
Assertions.assertThat(firstConnection.getPeerInfo().getNodeId()).isEqualTo(listenId);
// Connections should reference the same instance - i.e. we shouldn't create 2 distinct
// connections
assertThat(firstConnection == secondConnection).isTrue();
}
}
/**
* Tests that max peers setting is honoured and inbound connections that would exceed the limit
* are correctly disconnected.
*
* @throws Exception On Failure
*/
@Test
public void limitMaxPeers() throws Exception {
final NodeKey nodeKey = NodeKeyUtils.generate();
final int maxPeers = 1;
final NetworkingConfiguration listenerConfig =
NetworkingConfiguration.create()
.setDiscovery(DiscoveryConfiguration.create().setActive(false))
.setRlpx(
RlpxConfiguration.create()
.setBindPort(0)
.setMaxPeers(maxPeers)
.setSupportedProtocols(subProtocol()));
try (final P2PNetwork listener =
builder("partner1client1").nodeKey(nodeKey).config(listenerConfig).build();
final P2PNetwork connector1 = builder("partner1client1").build();
final P2PNetwork connector2 = builder("partner2client1").build()) {
// Setup listener and first connection
listener.start();
connector1.start();
final EnodeURL listenerEnode = listener.getLocalEnode().get();
final Bytes listenId = listenerEnode.getNodeId();
final int listenPort = listenerEnode.getListeningPort().get();
final Peer listeningPeer = createPeer(listenId, listenPort);
Assertions.assertThat(
connector1
.connect(listeningPeer)
.get(30L, TimeUnit.SECONDS)
.getPeerInfo()
.getNodeId())
.isEqualTo(listenId);
// Setup second connection and check that connection is not accepted
final CompletableFuture<PeerConnection> peerFuture = new CompletableFuture<>();
final CompletableFuture<DisconnectReason> reasonFuture = new CompletableFuture<>();
connector2.subscribeDisconnect(
(peerConnection, reason, initiatedByPeer) -> {
peerFuture.complete(peerConnection);
reasonFuture.complete(reason);
});
connector2.start();
Assertions.assertThat(
connector2
.connect(listeningPeer)
.get(30L, TimeUnit.SECONDS)
.getPeerInfo()
.getNodeId())
.isEqualTo(listenId);
Assertions.assertThat(peerFuture.get(30L, TimeUnit.SECONDS).getPeerInfo().getNodeId())
.isEqualTo(listenId);
assertThat(reasonFuture.get(30L, TimeUnit.SECONDS))
.isEqualByComparingTo(DisconnectReason.TOO_MANY_PEERS);
}
}
@Test
public void rejectPeerWithNoSharedCaps() throws Exception {
final NodeKey listenerNodeKey = NodeKeyUtils.generate();
final NodeKey connectorNodeKey = NodeKeyUtils.generate();
final SubProtocol subprotocol1 = subProtocol("eth");
final Capability cap1 = Capability.create(subprotocol1.getName(), 63);
final SubProtocol subprotocol2 = subProtocol("oth");
final Capability cap2 = Capability.create(subprotocol2.getName(), 63);
try (final P2PNetwork listener =
builder("partner1client1")
.nodeKey(listenerNodeKey)
.supportedCapabilities(cap1)
.build();
final P2PNetwork connector =
builder("partner2client1")
.nodeKey(connectorNodeKey)
.supportedCapabilities(cap2)
.build()) {
listener.start();
connector.start();
final EnodeURL listenerEnode = listener.getLocalEnode().get();
final Bytes listenId = listenerEnode.getNodeId();
final int listenPort = listenerEnode.getListeningPort().get();
final Peer listenerPeer = createPeer(listenId, listenPort);
final CompletableFuture<PeerConnection> connectFuture = connector.connect(listenerPeer);
assertThatThrownBy(connectFuture::get).hasCauseInstanceOf(IncompatiblePeerException.class);
}
}
@Test
public void rejectIncomingConnectionFromBlacklistedPeer() throws Exception {
final PeerPermissionsDenylist localBlacklist = PeerPermissionsDenylist.create();
try (final P2PNetwork localNetwork =
builder("partner1client1").peerPermissions(localBlacklist).build();
final P2PNetwork remoteNetwork = builder("partner2client1").build()) {
localNetwork.start();
remoteNetwork.start();
final EnodeURL localEnode = localNetwork.getLocalEnode().get();
final Bytes localId = localEnode.getNodeId();
final int localPort = localEnode.getListeningPort().get();
final EnodeURL remoteEnode = remoteNetwork.getLocalEnode().get();
final Bytes remoteId = remoteEnode.getNodeId();
final int remotePort = remoteEnode.getListeningPort().get();
final Peer localPeer = createPeer(localId, localPort);
final Peer remotePeer = createPeer(remoteId, remotePort);
// Blacklist the remote peer
localBlacklist.add(remotePeer);
// Setup disconnect listener
final CompletableFuture<PeerConnection> peerFuture = new CompletableFuture<>();
final CompletableFuture<DisconnectReason> reasonFuture = new CompletableFuture<>();
remoteNetwork.subscribeDisconnect(
(peerConnection, reason, initiatedByPeer) -> {
peerFuture.complete(peerConnection);
reasonFuture.complete(reason);
});
// Remote connect to local
final CompletableFuture<PeerConnection> connectFuture = remoteNetwork.connect(localPeer);
// Check connection is made, and then a disconnect is registered at remote
Assertions.assertThat(connectFuture.get(5L, TimeUnit.SECONDS).getPeerInfo().getNodeId())
.isEqualTo(localId);
Assertions.assertThat(peerFuture.get(5L, TimeUnit.SECONDS).getPeerInfo().getNodeId())
.isEqualTo(localId);
assertThat(reasonFuture.get(5L, TimeUnit.SECONDS))
.isEqualByComparingTo(DisconnectReason.UNKNOWN);
}
}
@Test
public void rejectIncomingConnectionFromDisallowedPeer() throws Exception {
final PeerPermissions peerPermissions = mock(PeerPermissions.class);
when(peerPermissions.isPermitted(any(), any(), any())).thenReturn(true);
try (final P2PNetwork localNetwork =
builder("partner1client1").peerPermissions(peerPermissions).build();
final P2PNetwork remoteNetwork = builder("partner2client1").build()) {
localNetwork.start();
remoteNetwork.start();
final EnodeURL localEnode = localNetwork.getLocalEnode().get();
final Peer localPeer = DefaultPeer.fromEnodeURL(localEnode);
final Peer remotePeer = DefaultPeer.fromEnodeURL(remoteNetwork.getLocalEnode().get());
// Deny incoming connection permissions for remotePeer
when(peerPermissions.isPermitted(
eq(localPeer),
eq(remotePeer),
eq(PeerPermissions.Action.RLPX_ALLOW_NEW_INBOUND_CONNECTION)))
.thenReturn(false);
// Setup disconnect listener
final CompletableFuture<PeerConnection> peerFuture = new CompletableFuture<>();
final CompletableFuture<DisconnectReason> reasonFuture = new CompletableFuture<>();
remoteNetwork.subscribeDisconnect(
(peerConnection, reason, initiatedByPeer) -> {
peerFuture.complete(peerConnection);
reasonFuture.complete(reason);
});
// Remote connect to local
final CompletableFuture<PeerConnection> connectFuture = remoteNetwork.connect(localPeer);
// Check connection is made, and then a disconnect is registered at remote
final Bytes localId = localEnode.getNodeId();
Assertions.assertThat(connectFuture.get(5L, TimeUnit.SECONDS).getPeerInfo().getNodeId())
.isEqualTo(localId);
Assertions.assertThat(peerFuture.get(5L, TimeUnit.SECONDS).getPeerInfo().getNodeId())
.isEqualTo(localId);
assertThat(reasonFuture.get(5L, TimeUnit.SECONDS))
.isEqualByComparingTo(DisconnectReason.UNKNOWN);
}
}
private Peer createPeer(final Bytes nodeId, final int listenPort) {
return DefaultPeer.fromEnodeURL(
EnodeURLImpl.builder()
.ipAddress(InetAddress.getLoopbackAddress().getHostAddress())
.nodeId(nodeId)
.discoveryAndListeningPorts(listenPort)
.build());
}
private static SubProtocol subProtocol() {
return subProtocol("eth");
}
private static SubProtocol subProtocol(final String name) {
return new SubProtocol() {
@Override
public String getName() {
return name;
}
@Override
public int messageSpace(final int protocolVersion) {
return 8;
}
@Override
public boolean isValidMessageCode(final int protocolVersion, final int code) {
return true;
}
@Override
public String messageName(final int protocolVersion, final int code) {
return INVALID_MESSAGE_NAME;
}
};
}
private Path initNSSConfigFile(final Path srcFilePath) {
Path ret = null;
try {
final String content = Files.readString(srcFilePath);
final String updated =
content.replaceAll(
"(nssSecmodDirectory\\W*)(\\.\\/.*)",
"$1".concat(srcFilePath.toAbsolutePath().toString().replace("nss.cfg", "nssdb")));
final Path targetFilePath = createTemporaryFile("nsscfg");
Files.write(targetFilePath, updated.getBytes(Charsets.UTF_8));
ret = targetFilePath;
} catch (IOException e) {
throw new RuntimeException("Error populating nss config file", e);
}
return ret;
}
private Path createTemporaryFile(final String suffix) {
final File tempFile;
try {
tempFile = File.createTempFile("temp", suffix);
tempFile.deleteOnExit();
} catch (IOException e) {
throw new RuntimeException("Error creating temporary file", e);
}
return tempFile.toPath();
}
private static Path toPath(final String path) throws Exception {
return Path.of(P2PPlainNetworkTest.class.getResource(path).toURI());
}
public Optional<TLSConfiguration> p2pTLSEnabled(final String name, final String type) {
final TLSConfiguration.Builder builder = TLSConfiguration.Builder.tlsConfiguration();
try {
final String nsspin = "/keys/%s/nsspin.txt";
final String truststore = "/keys/%s/truststore.jks";
final String crl = "/keys/%s/crl.pem";
switch (type) {
case KeyStoreWrapper.KEYSTORE_TYPE_JKS:
builder
.withKeyStoreType(type)
.withKeyStorePath(toPath(String.format("/keys/%s/keystore.jks", name)))
.withKeyStorePasswordSupplier(
new FileBasedPasswordProvider(toPath(String.format(nsspin, name))))
.withKeyStorePasswordPath(toPath(String.format(nsspin, name)))
.withTrustStoreType(type)
.withTrustStorePath(toPath(String.format(truststore, name)))
.withTrustStorePasswordSupplier(
new FileBasedPasswordProvider(toPath(String.format(nsspin, name))))
.withTrustStorePasswordPath(toPath(String.format(nsspin, name)))
.withCrlPath(toPath(String.format(crl, name)));
break;
case KeyStoreWrapper.KEYSTORE_TYPE_PKCS12:
builder
.withKeyStoreType(type)
.withKeyStorePath(toPath(String.format("/keys/%s/keys.p12", name)))
.withKeyStorePasswordSupplier(
new FileBasedPasswordProvider(toPath(String.format(nsspin, name))))
.withKeyStorePasswordPath(toPath(String.format(nsspin, name)))
.withTrustStoreType(KeyStoreWrapper.KEYSTORE_TYPE_JKS)
.withTrustStorePath(toPath(String.format(truststore, name)))
.withTrustStorePasswordSupplier(
new FileBasedPasswordProvider(toPath(String.format(nsspin, name))))
.withTrustStorePasswordPath(toPath(String.format(nsspin, name)))
.withCrlPath(toPath(String.format(crl, name)));
break;
case KeyStoreWrapper.KEYSTORE_TYPE_PKCS11:
builder
.withKeyStoreType(type)
.withKeyStorePath(initNSSConfigFile(toPath(String.format("/keys/%s/nss.cfg", name))))
.withKeyStorePasswordSupplier(
new FileBasedPasswordProvider(toPath(String.format(nsspin, name))))
.withKeyStorePasswordPath(toPath(String.format(nsspin, name)))
.withCrlPath(toPath(String.format(crl, name)));
break;
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return Optional.of(builder.build());
}
private DefaultP2PNetwork.Builder builder(final String name) {
return DefaultP2PNetwork.builder()
.vertx(vertx)
.config(config)
.nodeKey(NodeKeyUtils.generate())
.p2pTLSConfiguration(p2pTLSEnabled(name, KeyStoreWrapper.KEYSTORE_TYPE_JKS))
.metricsSystem(new NoOpMetricsSystem())
.supportedCapabilities(Arrays.asList(Capability.create("eth", 63)))
.storageProvider(new InMemoryKeyValueStorageProvider())
.forkIdSupplier(() -> Collections.singletonList(Bytes.EMPTY));
}
}

@ -0,0 +1,65 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.p2p.plain;
import static org.assertj.core.api.Assertions.assertThat;
import io.netty.buffer.Unpooled;
import org.apache.tuweni.bytes.Bytes;
import org.junit.Test;
public class MessageHandlerTest {
private static final byte[] PING_DATA = new byte[] {0, 1, 2, 3};
private static final byte[] PONG_DATA = new byte[] {3, 2, 1, 0};
private static final byte[] MESSAGE_DATA = new byte[] {0, 1, 1, 0};
private static final Bytes MESSAGE = Bytes.wrap(MESSAGE_DATA);
private static final int DATA_CODE = 1000;
@Test
public void buildPingMessage() throws Exception {
Bytes message = MessageHandler.buildMessage(MessageType.PING, PING_DATA);
assertThat(message).isNotNull();
PlainMessage parsed = MessageHandler.parseMessage(Unpooled.wrappedBuffer(message.toArray()));
assertThat(parsed).isNotNull();
assertThat(parsed.getMessageType()).isEqualTo(MessageType.PING);
assertThat(parsed.getData().toArray()).isEqualTo(PING_DATA);
}
@Test
public void buildPongMessage() throws Exception {
Bytes message = MessageHandler.buildMessage(MessageType.PONG, PONG_DATA);
assertThat(message).isNotNull();
PlainMessage parsed = MessageHandler.parseMessage(Unpooled.wrappedBuffer(message.toArray()));
assertThat(parsed).isNotNull();
assertThat(parsed.getMessageType()).isEqualTo(MessageType.PONG);
assertThat(parsed.getData().toArray()).isEqualTo(PONG_DATA);
}
@Test
public void buildDataMessage() throws Exception {
Bytes message = MessageHandler.buildMessage(MessageType.DATA, DATA_CODE, MESSAGE);
assertThat(message).isNotNull();
PlainMessage parsed = MessageHandler.parseMessage(Unpooled.wrappedBuffer(message.toArray()));
assertThat(parsed).isNotNull();
assertThat(parsed.getMessageType()).isEqualTo(MessageType.DATA);
assertThat(parsed.getCode()).isEqualTo(DATA_CODE);
assertThat(parsed.getData().toArray()).isEqualTo(MESSAGE_DATA);
}
}
Loading…
Cancel
Save