Add blurred address hash for encrypted msg

This commit is contained in:
Manfred Karrer 2015-11-10 19:19:34 +01:00
parent 3c3a148b57
commit b827a9812d
9 changed files with 99 additions and 71 deletions

View File

@ -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() +
'}';
}
}

View File

@ -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;

View File

@ -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<Boolean> 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<Connection> future = networkNode.sendMessage(peerAddress, sealedAndSignedMessage);
Futures.addCallback(future, new FutureCallback<Connection>() {
@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<Connection> future = networkNode.sendMessage(peerAddress, sealedAndSignedMessage);
Futures.addCallback(future, new FutureCallback<Connection>() {
@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.");
}
}
}

View File

@ -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

View File

@ -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<Connection>() {
public void onSuccess(Connection connection) {
UserThread.execute(() -> {

View File

@ -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<Address> 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<Address, Peer> getAuthenticatedPeers() {
Log.traceCall();
return authenticatedPeers;
}
public Set<Address> getAllPeerAddresses() {
Log.traceCall();
Set<Address> 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<Address> 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));

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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());