From bba8032883acaa4464d3e4d314f17ea6ed08a65a Mon Sep 17 00:00:00 2001 From: Saravana Perumal Shanmugam Date: Fri, 5 Nov 2021 16:58:50 -0700 Subject: [PATCH] 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 --- .../ethereum/p2p/plain/MessageHandler.java | 67 +++ .../besu/ethereum/p2p/plain/MessageType.java | 46 ++ .../besu/ethereum/p2p/plain/PlainFramer.java | 54 +++ .../ethereum/p2p/plain/PlainHandshaker.java | 162 +++++++ .../besu/ethereum/p2p/plain/PlainMessage.java | 51 ++ .../netty/AbstractHandshakeHandler.java | 15 +- .../netty/HandshakeHandlerInbound.java | 10 +- .../netty/HandshakeHandlerOutbound.java | 10 +- .../netty/NettyConnectionInitializer.java | 27 +- .../netty/NettyTLSConnectionInitializer.java | 15 + .../ethereum/p2p/rlpx/framing/Framer.java | 7 + .../p2p/rlpx/framing/FramerProvider.java | 22 + .../rlpx/handshake/HandshakerProvider.java | 20 + .../network/FileBasedPasswordProvider.java | 48 ++ .../p2p/network/P2PPlainNetworkTest.java | 444 ++++++++++++++++++ .../p2p/plain/MessageHandlerTest.java | 65 +++ 16 files changed, 1052 insertions(+), 11 deletions(-) create mode 100644 ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/MessageHandler.java create mode 100644 ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/MessageType.java create mode 100644 ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/PlainFramer.java create mode 100644 ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/PlainHandshaker.java create mode 100644 ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/PlainMessage.java create mode 100644 ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/framing/FramerProvider.java create mode 100644 ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/handshake/HandshakerProvider.java create mode 100644 ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/FileBasedPasswordProvider.java create mode 100644 ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/P2PPlainNetworkTest.java create mode 100644 ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/plain/MessageHandlerTest.java diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/MessageHandler.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/MessageHandler.java new file mode 100644 index 0000000000..2b4efba9df --- /dev/null +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/MessageHandler.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; + } +} diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/MessageType.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/MessageType.java new file mode 100644 index 0000000000..7335b9810f --- /dev/null +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/MessageType.java @@ -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; + } + } +} diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/PlainFramer.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/PlainFramer.java new file mode 100644 index 0000000000..87d31bb37d --- /dev/null +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/PlainFramer.java @@ -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()); + } +} diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/PlainHandshaker.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/PlainHandshaker.java new file mode 100644 index 0000000000..67343dcbab --- /dev/null +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/PlainHandshaker.java @@ -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 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 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 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; + } +} diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/PlainMessage.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/PlainMessage.java new file mode 100644 index 0000000000..f212141037 --- /dev/null +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/plain/PlainMessage.java @@ -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; + } +} diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/AbstractHandshakeHandler.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/AbstractHandshakeHandler.java index 2b97963687..3782c18ad9 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/AbstractHandshakeHandler.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/AbstractHandshakeHandler.java @@ -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 expectedPeer; @@ -57,19 +58,25 @@ abstract class AbstractHandshakeHandler extends SimpleChannelInboundHandler subProtocols, final LocalNode localNode, final Optional expectedPeer, final CompletableFuture 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 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); } diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/HandshakeHandlerOutbound.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/HandshakeHandlerOutbound.java index d5c56c0677..aed5b86150 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/HandshakeHandlerOutbound.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/HandshakeHandlerOutbound.java @@ -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 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(); diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/NettyConnectionInitializer.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/NettyConnectionInitializer.java index 6bf0fc1402..b29353f0bd 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/NettyConnectionInitializer.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/NettyConnectionInitializer.java @@ -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); + } } diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/NettyTLSConnectionInitializer.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/NettyTLSConnectionInitializer.java index 0b7729dbc5..1a82fb70e8 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/NettyTLSConnectionInitializer.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/connections/netty/NettyTLSConnectionInitializer.java @@ -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(); + } } diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/framing/Framer.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/framing/Framer.java index c2ac7e846c..6fd9683897 100644 --- a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/framing/Framer.java +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/framing/Framer.java @@ -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. * diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/framing/FramerProvider.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/framing/FramerProvider.java new file mode 100644 index 0000000000..423bdccaf6 --- /dev/null +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/framing/FramerProvider.java @@ -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); +} diff --git a/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/handshake/HandshakerProvider.java b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/handshake/HandshakerProvider.java new file mode 100644 index 0000000000..e6eca77758 --- /dev/null +++ b/ethereum/p2p/src/main/java/org/hyperledger/besu/ethereum/p2p/rlpx/handshake/HandshakerProvider.java @@ -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(); +} diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/FileBasedPasswordProvider.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/FileBasedPasswordProvider.java new file mode 100644 index 0000000000..7c72a66d23 --- /dev/null +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/FileBasedPasswordProvider.java @@ -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 { + 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 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); + } + } +} diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/P2PPlainNetworkTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/P2PPlainNetworkTest.java new file mode 100644 index 0000000000..653ede61c2 --- /dev/null +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/network/P2PPlainNetworkTest.java @@ -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 firstFuture = + connector.connect(createPeer(listenId, listenPort)); + final CompletableFuture 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 peerFuture = new CompletableFuture<>(); + final CompletableFuture 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 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 peerFuture = new CompletableFuture<>(); + final CompletableFuture reasonFuture = new CompletableFuture<>(); + remoteNetwork.subscribeDisconnect( + (peerConnection, reason, initiatedByPeer) -> { + peerFuture.complete(peerConnection); + reasonFuture.complete(reason); + }); + + // Remote connect to local + final CompletableFuture 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 peerFuture = new CompletableFuture<>(); + final CompletableFuture reasonFuture = new CompletableFuture<>(); + remoteNetwork.subscribeDisconnect( + (peerConnection, reason, initiatedByPeer) -> { + peerFuture.complete(peerConnection); + reasonFuture.complete(reason); + }); + + // Remote connect to local + final CompletableFuture 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 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)); + } +} diff --git a/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/plain/MessageHandlerTest.java b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/plain/MessageHandlerTest.java new file mode 100644 index 0000000000..d550ddb5ec --- /dev/null +++ b/ethereum/p2p/src/test/java/org/hyperledger/besu/ethereum/p2p/plain/MessageHandlerTest.java @@ -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); + } +}