diff --git a/network/src/main/java/io/bitsquare/crypto/SealedAndSignedMessage.java b/network/src/main/java/io/bitsquare/crypto/SealedAndSignedMessage.java index a0679c7032..2715b54dbf 100644 --- a/network/src/main/java/io/bitsquare/crypto/SealedAndSignedMessage.java +++ b/network/src/main/java/io/bitsquare/crypto/SealedAndSignedMessage.java @@ -5,15 +5,19 @@ import io.bitsquare.common.crypto.SealedAndSigned; import io.bitsquare.p2p.Address; import io.bitsquare.p2p.messaging.MailboxMessage; +import java.util.Arrays; + public final class SealedAndSignedMessage implements MailboxMessage { // That object is sent over the wire, so we need to take care of version compatibility. private static final long serialVersionUID = Version.NETWORK_PROTOCOL_VERSION; private final int networkId = Version.NETWORK_ID; public final SealedAndSigned sealedAndSigned; + public final byte[] blurredAddressHash; - public SealedAndSignedMessage(SealedAndSigned sealedAndSigned) { + public SealedAndSignedMessage(SealedAndSigned sealedAndSigned, byte[] blurredAddressHash) { this.sealedAndSigned = sealedAndSigned; + this.blurredAddressHash = blurredAddressHash; } @Override @@ -31,6 +35,7 @@ public final class SealedAndSignedMessage implements MailboxMessage { return "SealedAndSignedMessage{" + "networkId=" + networkId + ", sealedAndSigned=" + sealedAndSigned + + ", receiverAddressMaskHash.hashCode()=" + Arrays.toString(blurredAddressHash).hashCode() + '}'; } } diff --git a/network/src/main/java/io/bitsquare/p2p/Address.java b/network/src/main/java/io/bitsquare/p2p/Address.java index f49f48b353..0a0027606d 100644 --- a/network/src/main/java/io/bitsquare/p2p/Address.java +++ b/network/src/main/java/io/bitsquare/p2p/Address.java @@ -22,6 +22,10 @@ public class Address implements Serializable { return hostName + ":" + port; } + public String getAddressMask() { + return getFullAddress().substring(0, 2); + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/network/src/main/java/io/bitsquare/p2p/P2PService.java b/network/src/main/java/io/bitsquare/p2p/P2PService.java index cb7503d22e..a1d7470795 100644 --- a/network/src/main/java/io/bitsquare/p2p/P2PService.java +++ b/network/src/main/java/io/bitsquare/p2p/P2PService.java @@ -7,10 +7,11 @@ import com.google.inject.Inject; import com.google.inject.name.Named; import io.bitsquare.app.Log; import io.bitsquare.app.ProgramArguments; +import io.bitsquare.common.UserThread; import io.bitsquare.common.crypto.CryptoException; +import io.bitsquare.common.crypto.Hash; import io.bitsquare.common.crypto.KeyRing; import io.bitsquare.common.crypto.PubKeyRing; -import io.bitsquare.common.crypto.SealedAndSigned; import io.bitsquare.crypto.EncryptionService; import io.bitsquare.crypto.SealedAndSignedMessage; import io.bitsquare.p2p.messaging.*; @@ -43,6 +44,7 @@ import java.math.BigInteger; import java.security.PublicKey; import java.util.*; import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.TimeUnit; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @@ -53,6 +55,8 @@ import static com.google.common.base.Preconditions.checkNotNull; public class P2PService implements SetupListener, MessageListener, ConnectionListener, PeerListener { private static final Logger log = LoggerFactory.getLogger(P2PService.class); + private static final int RETRY_GET_DATA = 10 * 1000; + private final SeedNodesRepository seedNodesRepository; private final int port; private final File torDir; @@ -79,6 +83,8 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis private final BooleanProperty authenticated = new SimpleBooleanProperty(); private MonadicBinding readyForAuthentication; public final IntegerProperty numAuthenticatedPeers = new SimpleIntegerProperty(0); + @Nullable + private byte[] blurredAddressHash = null; /////////////////////////////////////////////////////////////////////////////////////////// @@ -181,11 +187,15 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis if (encryptionService != null) { try { SealedAndSignedMessage sealedAndSignedMessage = (SealedAndSignedMessage) message; - DecryptedMsgWithPubKey decryptedMsgWithPubKey = encryptionService.decryptAndVerify( - sealedAndSignedMessage.sealedAndSigned); - log.info("Received SealedAndSignedMessage and decrypted it: " + decryptedMsgWithPubKey); - decryptedMailListeners.stream().forEach( - e -> e.onMailMessage(decryptedMsgWithPubKey, connection.getPeerAddress())); + if (verifyBlurredAddressHash(sealedAndSignedMessage)) { + DecryptedMsgWithPubKey decryptedMsgWithPubKey = encryptionService.decryptAndVerify( + sealedAndSignedMessage.sealedAndSigned); + log.info("Received SealedAndSignedMessage and decrypted it: " + decryptedMsgWithPubKey); + decryptedMailListeners.stream().forEach( + e -> e.onMailMessage(decryptedMsgWithPubKey, connection.getPeerAddress())); + } else { + log.info("Wrong receiverAddressMaskHash. The message is not intended for us."); + } } catch (CryptoException e) { log.info("Decryption of SealedAndSignedMessage failed. " + "That is expected if the message is not intended for us."); @@ -194,6 +204,11 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis } } + private boolean verifyBlurredAddressHash(SealedAndSignedMessage sealedAndSignedMessage) { + return blurredAddressHash != null && + Arrays.equals(blurredAddressHash, sealedAndSignedMessage.blurredAddressHash); + } + /////////////////////////////////////////////////////////////////////////////////////////// // ConnectionListener implementation @@ -277,6 +292,8 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis Log.traceCall(); checkArgument(networkNode.getAddress() != null, "Address must be set when we have the hidden service ready"); + blurredAddressHash = Hash.getHash(getAddress().getAddressMask()); + p2pServiceListeners.stream().forEach(e -> e.onHiddenServicePublished()); // 3. (or 2.). Step: Hidden service is published @@ -303,6 +320,13 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis public void onSuccess(@Nullable Connection connection) { log.info("Send GetAllDataMessage to " + candidate + " succeeded."); connectedSeedNode = candidate; + + // In case we get called by a retry we check if we need authenticate as well + if (hiddenServicePublished.get() && !authenticated.get()) { + peerGroup.authenticateSeedNode(connectedSeedNode); + } else { + log.debug("No connected seedNode available."); + } } @Override @@ -317,23 +341,26 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis } }); } else { - log.info("There is no seed node available for requesting data. That is expected for the first seed node."); + log.info("There is no seed node available for requesting data. " + + "That is expected if no seed node is available.\n" + + "We will try again after {} ms", RETRY_GET_DATA); setRequestingDataCompleted(); + + UserThread.runAfter(() -> sendGetDataRequest(peerGroup.getSeedNodeAddresses()), + RETRY_GET_DATA, TimeUnit.MILLISECONDS); } } private void setRequestingDataCompleted() { Log.traceCall(); - // 2. (or 3.) Step: We got all data loaded - if (!requestingDataCompleted.get()) - requestingDataCompleted.set(true); + // 2. (or 3.) Step: We got all data loaded (or no seed node available - should not happen in real operation) + requestingDataCompleted.set(true); } // 4. Step: hiddenServicePublished and allDataLoaded. We start authenticate to the connected seed node. private void tryAuthenticateSeedNode() { Log.traceCall(); if (connectedSeedNode != null) { - log.trace("authenticateSeedNode"); peerGroup.authenticateSeedNode(connectedSeedNode); } else { log.debug("No connected seedNode available."); @@ -436,7 +463,7 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis if (encryptionService != null) { try { SealedAndSignedMessage sealedAndSignedMessage = new SealedAndSignedMessage( - encryptionService.encryptAndSign(pubKeyRing, message)); + encryptionService.encryptAndSign(pubKeyRing, message), Hash.getHash(peerAddress.getAddressMask())); SettableFuture future = networkNode.sendMessage(peerAddress, sealedAndSignedMessage); Futures.addCallback(future, new FutureCallback() { @Override @@ -482,7 +509,7 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis if (encryptionService != null) { try { SealedAndSignedMessage sealedAndSignedMessage = new SealedAndSignedMessage( - encryptionService.encryptAndSign(peersPubKeyRing, message)); + encryptionService.encryptAndSign(peersPubKeyRing, message), Hash.getHash(peerAddress.getAddressMask())); SettableFuture future = networkNode.sendMessage(peerAddress, sealedAndSignedMessage); Futures.addCallback(future, new FutureCallback() { @Override @@ -639,22 +666,18 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis /////////////////////////////////////////////////////////////////////////////////////////// public boolean isAuthenticated() { - Log.traceCall(); return authenticated.get(); } public NetworkNode getNetworkNode() { - Log.traceCall(); return networkNode; } public PeerGroup getPeerGroup() { - Log.traceCall(); return peerGroup; } public Address getAddress() { - Log.traceCall(); return networkNode.getAddress(); } @@ -675,25 +698,34 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis private void tryDecryptMailboxData(ProtectedMailboxData mailboxData) { Log.traceCall(); if (encryptionService != null) { - ExpirablePayload data = mailboxData.expirablePayload; - if (data instanceof ExpirableMailboxPayload) { - ExpirableMailboxPayload mailboxEntry = (ExpirableMailboxPayload) data; - SealedAndSigned sealedAndSigned = mailboxEntry.sealedAndSignedMessage.sealedAndSigned; - try { - DecryptedMsgWithPubKey decryptedMsgWithPubKey = encryptionService.decryptAndVerify(sealedAndSigned); - if (decryptedMsgWithPubKey.message instanceof MailboxMessage) { - MailboxMessage mailboxMessage = (MailboxMessage) decryptedMsgWithPubKey.message; - Address senderAddress = mailboxMessage.getSenderAddress(); - checkNotNull(senderAddress, "senderAddress must not be null for mailbox messages"); + ExpirablePayload expirablePayload = mailboxData.expirablePayload; + if (expirablePayload instanceof ExpirableMailboxPayload) { + ExpirableMailboxPayload expirableMailboxPayload = (ExpirableMailboxPayload) expirablePayload; + SealedAndSignedMessage sealedAndSignedMessage = expirableMailboxPayload.sealedAndSignedMessage; + if (verifyBlurredAddressHash(sealedAndSignedMessage)) { + try { + DecryptedMsgWithPubKey decryptedMsgWithPubKey = encryptionService.decryptAndVerify( + sealedAndSignedMessage.sealedAndSigned); + if (decryptedMsgWithPubKey.message instanceof MailboxMessage) { + MailboxMessage mailboxMessage = (MailboxMessage) decryptedMsgWithPubKey.message; + Address senderAddress = mailboxMessage.getSenderAddress(); + checkNotNull(senderAddress, "senderAddress must not be null for mailbox messages"); - mailboxMap.put(decryptedMsgWithPubKey, mailboxData); - log.trace("Decryption of SealedAndSignedMessage succeeded. senderAddress=" - + senderAddress + " / my address=" + getAddress()); - decryptedMailboxListeners.stream().forEach( - e -> e.onMailboxMessageAdded(decryptedMsgWithPubKey, senderAddress)); + mailboxMap.put(decryptedMsgWithPubKey, mailboxData); + log.trace("Decryption of SealedAndSignedMessage succeeded. senderAddress=" + + senderAddress + " / my address=" + getAddress()); + decryptedMailboxListeners.stream().forEach( + e -> e.onMailboxMessageAdded(decryptedMsgWithPubKey, senderAddress)); + } else { + log.warn("tryDecryptMailboxData: Expected MailboxMessage but got other type. " + + "decryptedMsgWithPubKey.message=", decryptedMsgWithPubKey.message); + } + } catch (CryptoException e) { + log.trace("Decryption of SealedAndSignedMessage failed. " + + "That is expected if the message is not intended for us. " + e.getMessage()); } - } catch (CryptoException e) { - log.trace("Decryption of SealedAndSignedMessage failed. That is expected if the message is not intended for us. " + e.getMessage()); + } else { + log.info("Wrong blurredAddressHash. The message is not intended for us."); } } } diff --git a/network/src/main/java/io/bitsquare/p2p/network/Connection.java b/network/src/main/java/io/bitsquare/p2p/network/Connection.java index 58276fcd31..9854dd62d8 100644 --- a/network/src/main/java/io/bitsquare/p2p/network/Connection.java +++ b/network/src/main/java/io/bitsquare/p2p/network/Connection.java @@ -30,11 +30,7 @@ import java.util.concurrent.*; public class Connection implements MessageListener { private static final Logger log = LoggerFactory.getLogger(Connection.class); private static final int MAX_MSG_SIZE = 5 * 1024 * 1024; // 5 MB of compressed data - private static final int MAX_ILLEGAL_REQUESTS = 5; - private static final int SEND_MESSAGE_TIMEOUT = 10 * 1000; // 10 sec. private static final int SOCKET_TIMEOUT = 30 * 60 * 1000; // 30 min. - private InputHandler inputHandler; - private volatile boolean isAuthenticated; public static int getMaxMsgSize() { return MAX_MSG_SIZE; @@ -43,21 +39,20 @@ public class Connection implements MessageListener { private final Socket socket; private final MessageListener messageListener; private final ConnectionListener connectionListener; - private final String portInfo; private final String uid = UUID.randomUUID().toString(); - ; private final ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); + // holder of state shared between InputHandler and Connection + private final SharedSpace sharedSpace; // set in init + private InputHandler inputHandler; private ObjectOutputStream objectOutputStream; - // holder of state shared between InputHandler and Connection - private SharedSpace sharedSpace; // mutable data, set from other threads but not changed internally. @Nullable private Address peerAddress; - + private volatile boolean isAuthenticated; private volatile boolean stopped; //TODO got java.util.zip.DataFormatException: invalid distance too far back @@ -75,6 +70,8 @@ public class Connection implements MessageListener { this.messageListener = messageListener; this.connectionListener = connectionListener; + sharedSpace = new SharedSpace(this, socket); + Log.traceCall(); if (socket.getLocalPort() == 0) portInfo = "port=" + socket.getPort(); @@ -86,7 +83,7 @@ public class Connection implements MessageListener { private void init() { Log.traceCall(); - sharedSpace = new SharedSpace(this, socket); + try { socket.setSoTimeout(SOCKET_TIMEOUT); // Need to access first the ObjectOutputStream otherwise the ObjectInputStream would block diff --git a/network/src/main/java/io/bitsquare/p2p/network/NetworkNode.java b/network/src/main/java/io/bitsquare/p2p/network/NetworkNode.java index ab7127fefa..05142ad57f 100644 --- a/network/src/main/java/io/bitsquare/p2p/network/NetworkNode.java +++ b/network/src/main/java/io/bitsquare/p2p/network/NetworkNode.java @@ -124,12 +124,12 @@ public abstract class NetworkNode implements MessageListener, ConnectionListener public void run() { Thread.currentThread().setName("TimerTask-" + new Random().nextInt(10000)); future.cancel(true); - String message = "Timeout occurred when trying to create Socket."; - log.warn(message); + String message = "Timeout occurred when tried to create Socket to peer: " + peerAddress; + log.info(message); resultFuture.setException(new TimeoutException(message)); } }, CREATE_SOCKET_TIMEOUT); - + Futures.addCallback(future, new FutureCallback() { public void onSuccess(Connection connection) { UserThread.execute(() -> { diff --git a/network/src/main/java/io/bitsquare/p2p/peers/PeerGroup.java b/network/src/main/java/io/bitsquare/p2p/peers/PeerGroup.java index 2410d9fbab..054e57690a 100644 --- a/network/src/main/java/io/bitsquare/p2p/peers/PeerGroup.java +++ b/network/src/main/java/io/bitsquare/p2p/peers/PeerGroup.java @@ -44,11 +44,11 @@ public class PeerGroup implements MessageListener, ConnectionListener { MAX_CONNECTIONS = maxConnections; } - private static final int SEND_PING_INTERVAL = new Random().nextInt(2 * 60 * 1000) + 2 * 60 * 1000 * 1000; // 2-4 min. - private static final int GET_PEERS_INTERVAL = 10000 * 1000;//new Random().nextInt(1 * 60 * 1000) + 1 * 60 * 1000; // 1-2 min. - private static final int PING_AFTER_CONNECTION_INACTIVITY = 30 * 1000 * 1000; - private static final int MAX_REPORTED_PEERS = 1000 * 1000; - private static final int RETRY_FILL_AUTH_PEERS = 10000 * 1000; + private static final int SEND_PING_INTERVAL = new Random().nextInt(5 * 60 * 1000) + 5 * 60 * 1000; + private static final int PING_AFTER_CONNECTION_INACTIVITY = 30 * 1000; + private static final int GET_PEERS_INTERVAL = new Random().nextInt(1 * 60 * 1000) + 1 * 60 * 1000; // 1-2 min. + private static final int RETRY_FILL_AUTH_PEERS = GET_PEERS_INTERVAL + 5000; + private static final int MAX_REPORTED_PEERS = 1000; private final NetworkNode networkNode; private final Set
seedNodeAddresses; @@ -110,9 +110,7 @@ public class PeerGroup implements MessageListener, ConnectionListener { @Override public void onDisconnect(Reason reason, Connection connection) { log.debug("onDisconnect connection=" + connection + " / reason=" + reason); - // only removes authenticated nodes - if (connection.isAuthenticated()) - removePeer(connection.getPeerAddress()); + removePeer(connection.getPeerAddress()); } @Override @@ -512,7 +510,7 @@ public class PeerGroup implements MessageListener, ConnectionListener { }); }, 5, 10)); } else { - log.trace("No peers available for requesting data."); + log.info("No peers available for requesting data."); } } @@ -592,12 +590,10 @@ public class PeerGroup implements MessageListener, ConnectionListener { /////////////////////////////////////////////////////////////////////////////////////////// private Map getAuthenticatedPeers() { - Log.traceCall(); return authenticatedPeers; } public Set
getAllPeerAddresses() { - Log.traceCall(); Set
allPeerAddresses = new HashSet<>(reportedPeerAddresses); allPeerAddresses.addAll(authenticatedPeers.values().stream() .map(e -> e.address).collect(Collectors.toSet())); @@ -605,7 +601,6 @@ public class PeerGroup implements MessageListener, ConnectionListener { } public Set
getSeedNodeAddresses() { - Log.traceCall(); return seedNodeAddresses; } @@ -705,13 +700,11 @@ public class PeerGroup implements MessageListener, ConnectionListener { } public void printAllPeers() { - Log.traceCall(); printAuthenticatedPeers(); printReportedPeers(); } public void printAuthenticatedPeers() { - Log.traceCall(); StringBuilder result = new StringBuilder("\n\n------------------------------------------------------------\n" + "Authenticated peers for node " + getMyAddress() + ":"); authenticatedPeers.values().stream().forEach(e -> result.append("\n").append(e.address)); @@ -720,7 +713,6 @@ public class PeerGroup implements MessageListener, ConnectionListener { } public void printReportedPeers() { - Log.traceCall(); StringBuilder result = new StringBuilder("\n\n------------------------------------------------------------\n" + "Reported peers for node " + getMyAddress() + ":"); reportedPeerAddresses.stream().forEach(e -> result.append("\n").append(e)); diff --git a/network/src/main/java/io/bitsquare/p2p/storage/ProtectedExpirableDataStorage.java b/network/src/main/java/io/bitsquare/p2p/storage/ProtectedExpirableDataStorage.java index 6a82a8501a..b731438ac8 100644 --- a/network/src/main/java/io/bitsquare/p2p/storage/ProtectedExpirableDataStorage.java +++ b/network/src/main/java/io/bitsquare/p2p/storage/ProtectedExpirableDataStorage.java @@ -113,8 +113,9 @@ public class ProtectedExpirableDataStorage implements MessageListener { removeMailboxData(((RemoveMailboxDataMessage) message).data, connection.getPeerAddress()); } } else { - log.warn("Connection is not authenticated yet. We don't accept storage operations form non-authenticated nodes."); - log.warn("Connection = " + connection); + log.warn("Connection is not authenticated yet. " + + "We don't accept storage operations from non-authenticated nodes."); + log.trace("Connection = " + connection); connection.reportIllegalRequest(IllegalRequest.NotAuthenticated); } } diff --git a/network/src/test/java/io/bitsquare/crypto/EncryptionServiceTests.java b/network/src/test/java/io/bitsquare/crypto/EncryptionServiceTests.java index 92340a3659..95a753f852 100644 --- a/network/src/test/java/io/bitsquare/crypto/EncryptionServiceTests.java +++ b/network/src/test/java/io/bitsquare/crypto/EncryptionServiceTests.java @@ -19,10 +19,7 @@ package io.bitsquare.crypto; import io.bitsquare.app.Version; -import io.bitsquare.common.crypto.CryptoException; -import io.bitsquare.common.crypto.KeyRing; -import io.bitsquare.common.crypto.KeyStorage; -import io.bitsquare.common.crypto.PubKeyRing; +import io.bitsquare.common.crypto.*; import io.bitsquare.common.util.Utilities; import io.bitsquare.p2p.Address; import io.bitsquare.p2p.messaging.DecryptedMsgWithPubKey; @@ -75,7 +72,7 @@ public class EncryptionServiceTests { public void testDecryptAndVerifyMessage() throws CryptoException { EncryptionService encryptionService = new EncryptionService(keyRing); TestMessage data = new TestMessage("test"); - SealedAndSignedMessage encrypted = new SealedAndSignedMessage(encryptionService.encryptAndSign(pubKeyRing, data)); + SealedAndSignedMessage encrypted = new SealedAndSignedMessage(encryptionService.encryptAndSign(pubKeyRing, data), Hash.getHash("aa")); DecryptedMsgWithPubKey decrypted = encryptionService.decryptAndVerify(encrypted.sealedAndSigned); assertEquals(data.data, ((TestMessage) decrypted.message).data); } diff --git a/network/src/test/java/io/bitsquare/p2p/storage/ProtectedDataStorageTest.java b/network/src/test/java/io/bitsquare/p2p/storage/ProtectedDataStorageTest.java index 70219664b3..239faaab25 100644 --- a/network/src/test/java/io/bitsquare/p2p/storage/ProtectedDataStorageTest.java +++ b/network/src/test/java/io/bitsquare/p2p/storage/ProtectedDataStorageTest.java @@ -214,7 +214,7 @@ public class ProtectedDataStorageTest { KeyStoreException, IOException, CryptoException, SignatureException, InvalidKeyException, NoSuchProviderException { // sender MockMessage mockMessage = new MockMessage("MockMessage"); - SealedAndSignedMessage sealedAndSignedMessage = new SealedAndSignedMessage(encryptionService1.encryptAndSign(keyRing1.getPubKeyRing(), mockMessage)); + SealedAndSignedMessage sealedAndSignedMessage = new SealedAndSignedMessage(encryptionService1.encryptAndSign(keyRing1.getPubKeyRing(), mockMessage), Hash.getHash("aa")); ExpirableMailboxPayload expirableMailboxPayload = new ExpirableMailboxPayload(sealedAndSignedMessage, keyRing1.getSignatureKeyPair().getPublic(), keyRing2.getSignatureKeyPair().getPublic());