Add signature to request to limit the feature to requests from trusted nodes (monitor)

This commit is contained in:
chimp1984 2020-10-14 23:02:43 -05:00
parent 9dab186086
commit ca7fe94c06
No known key found for this signature in database
GPG key ID: 9801B4EC591F90E3
7 changed files with 208 additions and 88 deletions

View file

@ -40,12 +40,18 @@ import bisq.common.util.MathUtils;
import bisq.common.util.Profiler;
import bisq.common.util.Utilities;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.SignatureDecodeException;
import javax.inject.Inject;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
@ -61,6 +67,7 @@ public class GetInventoryRequestHandler implements MessageListener {
private final DaoStateMonitoringService daoStateMonitoringService;
private final ProposalStateMonitoringService proposalStateMonitoringService;
private final BlindVoteStateMonitoringService blindVoteStateMonitoringService;
private final Set<String> permittedRequestersPubKey = new HashSet<>();
@Inject
public GetInventoryRequestHandler(NetworkNode networkNode,
@ -84,93 +91,121 @@ public class GetInventoryRequestHandler implements MessageListener {
@Override
public void onMessage(NetworkEnvelope networkEnvelope, Connection connection) {
if (networkEnvelope instanceof GetInventoryRequest) {
if (permittedRequestersPubKey.isEmpty()) {
return;
}
GetInventoryRequest getInventoryRequest = (GetInventoryRequest) networkEnvelope;
if (isPermitted(getInventoryRequest)) {
Map<String, Integer> dataObjects = new HashMap<>();
p2PDataStorage.getMapForDataResponse(getInventoryRequest.getVersion()).values().stream()
.map(e -> e.getClass().getSimpleName())
.forEach(className -> {
dataObjects.putIfAbsent(className, 0);
int prev = dataObjects.get(className);
dataObjects.put(className, prev + 1);
});
p2PDataStorage.getMap().values().stream()
.map(ProtectedStorageEntry::getProtectedStoragePayload)
.filter(Objects::nonNull)
.map(e -> e.getClass().getSimpleName())
.forEach(className -> {
dataObjects.putIfAbsent(className, 0);
int prev = dataObjects.get(className);
dataObjects.put(className, prev + 1);
});
Map<String, String> inventory = new HashMap<>();
dataObjects.forEach((key, value) -> inventory.put(key, String.valueOf(value)));
Map<String, Integer> dataObjects = new HashMap<>();
p2PDataStorage.getMapForDataResponse(getInventoryRequest.getVersion()).values().stream()
.map(e -> e.getClass().getSimpleName())
.forEach(className -> {
dataObjects.putIfAbsent(className, 0);
int prev = dataObjects.get(className);
dataObjects.put(className, prev + 1);
});
p2PDataStorage.getMap().values().stream()
.map(ProtectedStorageEntry::getProtectedStoragePayload)
.filter(Objects::nonNull)
.map(e -> e.getClass().getSimpleName())
.forEach(className -> {
dataObjects.putIfAbsent(className, 0);
int prev = dataObjects.get(className);
dataObjects.put(className, prev + 1);
});
Map<String, String> inventory = new HashMap<>();
dataObjects.forEach((key, value) -> inventory.put(key, String.valueOf(value)));
// DAO data
int numBsqBlocks = daoStateService.getBlocks().size();
inventory.put("numBsqBlocks", String.valueOf(numBsqBlocks));
// DAO data
int numBsqBlocks = daoStateService.getBlocks().size();
inventory.put("numBsqBlocks", String.valueOf(numBsqBlocks));
int daoStateChainHeight = daoStateService.getChainHeight();
inventory.put("daoStateChainHeight", String.valueOf(daoStateChainHeight));
int daoStateChainHeight = daoStateService.getChainHeight();
inventory.put("daoStateChainHeight", String.valueOf(daoStateChainHeight));
int walletChainHeight = btcWalletService.getBestChainHeight();
inventory.put("walletChainHeight", String.valueOf(walletChainHeight));
int walletChainHeight = btcWalletService.getBestChainHeight();
inventory.put("walletChainHeight", String.valueOf(walletChainHeight));
LinkedList<DaoStateBlock> daoStateBlockChain = daoStateMonitoringService.getDaoStateBlockChain();
if (!daoStateBlockChain.isEmpty()) {
String daoStateHash = Utilities.bytesAsHexString(daoStateBlockChain.getLast().getMyStateHash().getHash());
inventory.put("daoStateHash", daoStateHash);
} else {
inventory.put("daoStateHash", "n/a");
}
LinkedList<DaoStateBlock> daoStateBlockChain = daoStateMonitoringService.getDaoStateBlockChain();
if (!daoStateBlockChain.isEmpty()) {
String daoStateHash = Utilities.bytesAsHexString(daoStateBlockChain.getLast().getMyStateHash().getHash());
inventory.put("daoStateHash", daoStateHash);
} else {
inventory.put("daoStateHash", "n/a");
LinkedList<ProposalStateBlock> proposalStateBlockChain = proposalStateMonitoringService.getProposalStateBlockChain();
if (!proposalStateBlockChain.isEmpty()) {
String proposalHash = Utilities.bytesAsHexString(proposalStateBlockChain.getLast().getMyStateHash().getHash());
inventory.put("proposalHash", proposalHash);
} else {
inventory.put("proposalHash", "n/a");
}
LinkedList<BlindVoteStateBlock> blindVoteStateBlockChain = blindVoteStateMonitoringService.getBlindVoteStateBlockChain();
if (!blindVoteStateBlockChain.isEmpty()) {
String blindVoteHash = Utilities.bytesAsHexString(blindVoteStateBlockChain.getLast().getMyStateHash().getHash());
inventory.put("blindVoteHash", blindVoteHash);
} else {
inventory.put("blindVoteHash", "n/a");
}
// P2P network data
int numConnections = networkNode.getAllConnections().size();
inventory.put("numConnections", String.valueOf(numConnections));
long sentBytes = Statistic.totalSentBytesProperty().get();
inventory.put("sentBytes", String.valueOf(sentBytes));
long receivedBytes = Statistic.totalReceivedBytesProperty().get();
inventory.put("receivedBytes", String.valueOf(receivedBytes));
double receivedMessagesPerSec = MathUtils.roundDouble(Statistic.numTotalReceivedMessagesPerSecProperty().get(), 2);
inventory.put("receivedMessagesPerSec", String.valueOf(receivedMessagesPerSec));
double sentMessagesPerSec = MathUtils.roundDouble(Statistic.numTotalSentMessagesPerSecProperty().get(), 2);
inventory.put("sentMessagesPerSec", String.valueOf(sentMessagesPerSec));
// JVM info
long usedMemory = Profiler.getUsedMemoryInMB();
inventory.put("usedMemory", String.valueOf(usedMemory));
RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean();
long startTime = runtimeBean.getStartTime();
inventory.put("jvmStartTime", String.valueOf(startTime));
log.info("Send inventory {} to {}", inventory, connection.getPeersNodeAddressOptional());
GetInventoryResponse getInventoryResponse = new GetInventoryResponse(inventory);
networkNode.sendMessage(connection, getInventoryResponse);
}
}
}
LinkedList<ProposalStateBlock> proposalStateBlockChain = proposalStateMonitoringService.getProposalStateBlockChain();
if (!proposalStateBlockChain.isEmpty()) {
String proposalHash = Utilities.bytesAsHexString(proposalStateBlockChain.getLast().getMyStateHash().getHash());
inventory.put("proposalHash", proposalHash);
} else {
inventory.put("proposalHash", "n/a");
}
LinkedList<BlindVoteStateBlock> blindVoteStateBlockChain = blindVoteStateMonitoringService.getBlindVoteStateBlockChain();
if (!blindVoteStateBlockChain.isEmpty()) {
String blindVoteHash = Utilities.bytesAsHexString(blindVoteStateBlockChain.getLast().getMyStateHash().getHash());
inventory.put("blindVoteHash", blindVoteHash);
} else {
inventory.put("blindVoteHash", "n/a");
}
// P2P network data
int numConnections = networkNode.getAllConnections().size();
inventory.put("numConnections", String.valueOf(numConnections));
long sentBytes = Statistic.totalSentBytesProperty().get();
inventory.put("sentBytes", String.valueOf(sentBytes));
long receivedBytes = Statistic.totalReceivedBytesProperty().get();
inventory.put("receivedBytes", String.valueOf(receivedBytes));
double receivedMessagesPerSec = MathUtils.roundDouble(Statistic.numTotalReceivedMessagesPerSecProperty().get(), 2);
inventory.put("receivedMessagesPerSec", String.valueOf(receivedMessagesPerSec));
double sentMessagesPerSec = MathUtils.roundDouble(Statistic.numTotalSentMessagesPerSecProperty().get(), 2);
inventory.put("sentMessagesPerSec", String.valueOf(sentMessagesPerSec));
// JVM info
long usedMemory = Profiler.getUsedMemoryInMB();
inventory.put("usedMemory", String.valueOf(usedMemory));
RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean();
long startTime = runtimeBean.getStartTime();
inventory.put("jvmStartTime", String.valueOf(startTime));
log.info("Send inventory {} to {}", inventory, connection.getPeersNodeAddressOptional());
GetInventoryResponse getInventoryResponse = new GetInventoryResponse(inventory);
networkNode.sendMessage(connection, getInventoryResponse);
private boolean isPermitted(GetInventoryRequest getInventoryRequest) {
ECKey pubKey = ECKey.fromPublicOnly(getInventoryRequest.getPubKey());
String pubKeyAsHex = pubKey.getPublicKeyAsHex();
if (!permittedRequestersPubKey.contains(pubKeyAsHex)) {
return false;
}
byte[] nonce = getInventoryRequest.getNonce();
byte[] signature = getInventoryRequest.getSignature();
try {
ECKey.ECDSASignature ecdsaSignature = ECKey.ECDSASignature.decodeFromDER(signature).toCanonicalised();
Sha256Hash hash = Sha256Hash.of(nonce);
return pubKey.verify(hash, ecdsaSignature);
} catch (SignatureDecodeException e) {
e.printStackTrace();
log.error(e.toString());
return false;
}
}
public void shutDown() {
networkNode.removeMessageListener(this);
}
public void addPermittedRequestersPubKey(String pubKey) {
permittedRequestersPubKey.add(pubKey);
}
}

View file

@ -22,6 +22,8 @@ import bisq.network.p2p.network.NetworkNode;
import bisq.common.handlers.ErrorMessageHandler;
import org.bitcoinj.core.ECKey;
import javax.inject.Inject;
import java.util.HashMap;
@ -33,11 +35,13 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
public class GetInventoryRequestManager {
private final NetworkNode networkNode;
private final ECKey sigKey;
private final Map<NodeAddress, GetInventoryRequester> requesterMap = new HashMap<>();
@Inject
public GetInventoryRequestManager(NetworkNode networkNode) {
public GetInventoryRequestManager(NetworkNode networkNode, ECKey sigKey) {
this.networkNode = networkNode;
this.sigKey = sigKey;
}
public void request(NodeAddress nodeAddress,
@ -50,6 +54,7 @@ public class GetInventoryRequestManager {
GetInventoryRequester getInventoryRequester = new GetInventoryRequester(networkNode,
nodeAddress,
sigKey,
resultMap -> {
requesterMap.remove(nodeAddress);
resultHandler.accept(resultMap);

View file

@ -28,9 +28,13 @@ import bisq.network.p2p.network.NetworkNode;
import bisq.common.Timer;
import bisq.common.UserThread;
import bisq.common.app.Version;
import bisq.common.crypto.CryptoUtils;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.proto.network.NetworkEnvelope;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.Sha256Hash;
import java.util.Map;
import java.util.function.Consumer;
@ -42,16 +46,19 @@ public class GetInventoryRequester implements MessageListener {
private final NetworkNode networkNode;
private final NodeAddress nodeAddress;
private final ECKey sigKey;
private final Consumer<Map<String, String>> resultHandler;
private final ErrorMessageHandler errorMessageHandler;
private Timer timer;
public GetInventoryRequester(NetworkNode networkNode,
NodeAddress nodeAddress,
ECKey sigKey,
Consumer<Map<String, String>> resultHandler,
ErrorMessageHandler errorMessageHandler) {
this.networkNode = networkNode;
this.nodeAddress = nodeAddress;
this.sigKey = sigKey;
this.resultHandler = resultHandler;
this.errorMessageHandler = errorMessageHandler;
}
@ -59,7 +66,11 @@ public class GetInventoryRequester implements MessageListener {
public void request() {
networkNode.addMessageListener(this);
timer = UserThread.runAfter(this::onTimeOut, TIMEOUT_SEC);
networkNode.sendMessage(nodeAddress, new GetInventoryRequest(Version.VERSION));
byte[] nonce = CryptoUtils.getRandomBytes(32);
byte[] signature = sigKey.sign(Sha256Hash.of(nonce)).encodeToDER();
byte[] pubKey = sigKey.getPubKey();
GetInventoryRequest getInventoryRequest = new GetInventoryRequest(Version.VERSION, nonce, signature, pubKey);
networkNode.sendMessage(nodeAddress, getInventoryRequest);
}
private void onTimeOut() {

View file

@ -21,35 +21,58 @@ package bisq.core.network.p2p.inventory.messages;
import bisq.common.app.Version;
import bisq.common.proto.network.NetworkEnvelope;
import com.google.protobuf.ByteString;
import lombok.Value;
@Value
public class GetInventoryRequest extends NetworkEnvelope {
private final String version;
private final byte[] nonce;
private final byte[] signature;
// The Sig key (DSA)
private final byte[] pubKey;
public GetInventoryRequest(String version) {
this(version, Version.getP2PMessageVersion());
public GetInventoryRequest(String version,
byte[] nonce,
byte[] signature,
byte[] pubKey) {
this(version, nonce, signature, pubKey, Version.getP2PMessageVersion());
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
private GetInventoryRequest(String version, int messageVersion) {
private GetInventoryRequest(String version,
byte[] nonce,
byte[] signature,
byte[] pubKey,
int messageVersion) {
super(messageVersion);
this.version = version;
this.nonce = nonce;
this.signature = signature;
this.pubKey = pubKey;
}
@Override
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
return getNetworkEnvelopeBuilder()
.setGetInventoryRequest(protobuf.GetInventoryRequest.newBuilder()
.setVersion(version))
.setVersion(version)
.setNonce(ByteString.copyFrom(nonce))
.setSignature(ByteString.copyFrom(signature))
.setPubKey(ByteString.copyFrom(pubKey)))
.build();
}
public static GetInventoryRequest fromProto(protobuf.GetInventoryRequest proto, int messageVersion) {
return new GetInventoryRequest(proto.getVersion(), messageVersion);
return new GetInventoryRequest(proto.getVersion(),
proto.getNonce().toByteArray(),
proto.getSignature().toByteArray(),
proto.getPubKey().toByteArray(),
messageVersion);
}
}

View file

@ -34,21 +34,25 @@ import bisq.common.config.BaseCurrencyNetwork;
import bisq.common.file.JsonFileManager;
import bisq.common.util.Utilities;
import org.bitcoinj.core.ECKey;
import java.time.Clock;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
@Slf4j
public class InventoryMonitor {
@ -83,11 +87,13 @@ public class InventoryMonitor {
Capability.RECEIVE_BSQ_BLOCK
);
networkNode = new NetworkNodeProvider(new CoreNetworkProtoResolver(Clock.systemDefaultZone()),
File torDir = new File(appDir, "tor");
CoreNetworkProtoResolver networkProtoResolver = new CoreNetworkProtoResolver(Clock.systemDefaultZone());
networkNode = new NetworkNodeProvider(networkProtoResolver,
ArrayList::new,
useLocalhostForP2P,
9999,
new File(appDir, "tor"),
torDir,
null,
"",
-1,
@ -95,7 +101,17 @@ public class InventoryMonitor {
null,
false,
false).get();
getInventoryRequestManager = new GetInventoryRequestManager(networkNode);
File keyFile = new File(appDir, "sigKey");
ECKey ecKey = getPersistedSigKey(keyFile).orElseGet(() -> {
ECKey key = new ECKey();
log.info("No persisted key found. We create a new EC key");
persistSigKey(keyFile, key);
return key;
});
log.info("EC key: " + ecKey.getPublicKeyAsHex());
getInventoryRequestManager = new GetInventoryRequestManager(networkNode, ecKey);
seedNodes.addAll(DefaultSeedNodeRepository.getSeedNodeAddressesFromPropertyFile(network));
@ -128,7 +144,31 @@ public class InventoryMonitor {
});
}
@NotNull
private void persistSigKey(File keyFile, ECKey ecKey) {
try (FileOutputStream fileOutputStream = new FileOutputStream(keyFile.getAbsolutePath())) {
fileOutputStream.write(ecKey.getPrivKeyBytes());
} catch (IOException e) {
e.printStackTrace();
log.error(e.toString());
}
}
private Optional<ECKey> getPersistedSigKey(File keyFile) {
if (!keyFile.exists()) {
return Optional.empty();
}
try (FileInputStream fis = new FileInputStream(keyFile.getAbsolutePath())) {
byte[] keyBytes = new byte[(int) keyFile.length()];
fis.read(keyBytes);
return Optional.of(ECKey.fromPrivate(keyBytes));
} catch (IOException e) {
e.printStackTrace();
log.error(e.toString());
return Optional.empty();
}
}
private String getShortAddress(NodeAddress nodeAddress, boolean useLocalhostForP2P) {
return useLocalhostForP2P ?
nodeAddress.getFullAddress().replace(":", "_") :

View file

@ -144,6 +144,9 @@ message Pong {
message GetInventoryRequest {
string version = 1;
bytes nonce = 2;
bytes signature = 3;
bytes pub_key = 4;
}
message GetInventoryResponse {

View file

@ -41,6 +41,9 @@ public class SeedNode {
appSetup.start();
getInventoryRequestHandler = injector.getInstance(GetInventoryRequestHandler.class);
// TODO dev test key
getInventoryRequestHandler.addPermittedRequestersPubKey("035243719433b154a86329fd23a8389bee6369698be809f7e27f003f5b5aa9eb42");
}
public void shutDown() {