move network code to module

This commit is contained in:
Manfred Karrer 2015-10-28 02:11:59 +01:00
parent 105a63847a
commit c6ece486ed
384 changed files with 11571 additions and 21763 deletions

View file

@ -0,0 +1,135 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.crypto;
import io.bitsquare.common.crypto.CryptoException;
import io.bitsquare.common.crypto.CryptoUtil;
import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.common.crypto.PubKeyRing;
import io.bitsquare.common.util.Utilities;
import io.bitsquare.p2p.Message;
import io.bitsquare.p2p.messaging.DecryptedMessageWithPubKey;
import io.bitsquare.p2p.messaging.SealedAndSignedMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import javax.crypto.*;
import javax.inject.Inject;
import java.io.IOException;
import java.security.*;
public class EncryptionService {
private static final Logger log = LoggerFactory.getLogger(EncryptionService.class);
@Nullable
private final KeyRing keyRing;
@Inject
public EncryptionService(KeyRing keyRing) {
this.keyRing = keyRing;
}
public SealedAndSignedMessage encryptAndSignMessage(PubKeyRing pubKeyRing, Message message) throws CryptoException {
log.trace("encryptAndSignMessage message = " + message);
//long ts = System.currentTimeMillis();
try {
// Create symmetric key
KeyGenerator keyGenerator = KeyGenerator.getInstance(CryptoUtil.SYM_ENCR_KEY_ALGO);
// TODO consider 256 bit as key length
keyGenerator.init(128);
SecretKey secretKey = keyGenerator.generateKey();
// Encrypt secretKey with peers pubKey using SealedObject
Cipher cipherAsym = Cipher.getInstance(CryptoUtil.ASYM_CIPHER);
cipherAsym.init(Cipher.ENCRYPT_MODE, pubKeyRing.getMsgEncryptionPubKey());
SealedObject sealedSecretKey = new SealedObject(secretKey, cipherAsym);
// Sign (hash of) message and pack it into SignedObject
SignedObject signedMessage = new SignedObject(message, keyRing.getMsgSignatureKeyPair().getPrivate(), Signature.getInstance(CryptoUtil.MSG_SIGN_ALGO));
// // Encrypt signedMessage with secretKey using SealedObject
Cipher cipherSym = Cipher.getInstance(CryptoUtil.SYM_CIPHER);
cipherSym.init(Cipher.ENCRYPT_MODE, secretKey);
SealedObject sealedMessage = new SealedObject(signedMessage, cipherSym);
SealedAndSignedMessage sealedAndSignedMessage = new SealedAndSignedMessage(sealedSecretKey,
sealedMessage,
keyRing.getMsgSignatureKeyPair().getPublic()
);
//log.trace("Encryption needed {} ms", System.currentTimeMillis() - ts);
log.trace("sealedAndSignedMessage size " + Utilities.objectToByteArray(sealedAndSignedMessage).length);
return sealedAndSignedMessage;
} catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException
| IllegalBlockSizeException | IOException | SignatureException e) {
throw new CryptoException(e);
}
}
public DecryptedMessageWithPubKey decryptAndVerifyMessage(SealedAndSignedMessage sealedAndSignedMessage) throws CryptoException {
// long ts = System.currentTimeMillis();
try {
if (keyRing == null)
throw new CryptoException("keyRing is null");
SealedObject sealedSecretKey = sealedAndSignedMessage.sealedSecretKey;
SealedObject sealedMessage = sealedAndSignedMessage.sealedMessage;
PublicKey signaturePubKey = sealedAndSignedMessage.signaturePubKey;
// Decrypt secretKey with my privKey
Cipher cipherAsym = Cipher.getInstance(CryptoUtil.ASYM_CIPHER);
cipherAsym.init(Cipher.DECRYPT_MODE, keyRing.getMsgEncryptionKeyPair().getPrivate());
Object secretKeyObject = sealedSecretKey.getObject(cipherAsym);
if (secretKeyObject instanceof SecretKey) {
SecretKey secretKey = (SecretKey) secretKeyObject;
// Decrypt signedMessage with secretKey
Cipher cipherSym = Cipher.getInstance(CryptoUtil.SYM_CIPHER);
cipherSym.init(Cipher.DECRYPT_MODE, secretKey);
Object signedMessageObject = sealedMessage.getObject(cipherSym);
if (signedMessageObject instanceof SignedObject) {
SignedObject signedMessage = (SignedObject) signedMessageObject;
// Verify message with peers pubKey
if (signedMessage.verify(signaturePubKey, Signature.getInstance(CryptoUtil.MSG_SIGN_ALGO))) {
// Get message
Object messageObject = signedMessage.getObject();
if (messageObject instanceof Message) {
//log.trace("Decryption needed {} ms", System.currentTimeMillis() - ts);
return new DecryptedMessageWithPubKey((Message) messageObject, signaturePubKey);
} else {
throw new CryptoException("messageObject is not instance of Message");
}
} else {
throw new CryptoException("Signature is not valid");
}
} else {
throw new CryptoException("signedMessageObject is not instance of SignedObject");
}
} else {
throw new CryptoException("secretKeyObject is not instance of SecretKey");
}
} catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | BadPaddingException |
ClassNotFoundException | IllegalBlockSizeException | IOException | SignatureException e) {
throw new CryptoException(e);
}
}
}

View file

@ -0,0 +1,34 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.crypto;
import com.google.inject.Singleton;
import io.bitsquare.app.AppModule;
import org.springframework.core.env.Environment;
public class EncryptionServiceModule extends AppModule {
public EncryptionServiceModule(Environment env) {
super(env);
}
@Override
protected void configure() {
bind(EncryptionService.class).in(Singleton.class);
}
}

View file

@ -0,0 +1,7 @@
package io.bitsquare.p2p;
import java.io.Serializable;
public interface Message extends Serializable {
}

View file

@ -0,0 +1,55 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.p2p;
import com.google.inject.Singleton;
import com.google.inject.name.Names;
import io.bitsquare.app.AppModule;
import io.bitsquare.app.ProgramArguments;
import io.bitsquare.p2p.seed.SeedNodesRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.env.Environment;
import java.io.File;
import static com.google.inject.name.Names.named;
public class P2PModule extends AppModule {
private static final Logger log = LoggerFactory.getLogger(P2PModule.class);
public P2PModule(Environment env) {
super(env);
}
@Override
protected void configure() {
bind(SeedNodesRepository.class).in(Singleton.class);
bind(P2PService.class).in(Singleton.class);
Boolean useLocalhost = env.getProperty(ProgramArguments.USE_LOCALHOST, boolean.class, false);
bind(boolean.class).annotatedWith(Names.named(ProgramArguments.USE_LOCALHOST)).toInstance(useLocalhost);
File torDir = new File(env.getRequiredProperty(ProgramArguments.TOR_DIR));
bind(File.class).annotatedWith(named(ProgramArguments.TOR_DIR)).toInstance(torDir);
Integer port = env.getProperty(ProgramArguments.PORT_KEY, int.class, Utils.findFreeSystemPort());
bind(int.class).annotatedWith(Names.named(ProgramArguments.PORT_KEY)).toInstance(port);
}
}

View file

@ -0,0 +1,628 @@
package io.bitsquare.p2p;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.SettableFuture;
import com.google.inject.Inject;
import com.google.inject.name.Named;
import io.bitsquare.app.ProgramArguments;
import io.bitsquare.common.UserThread;
import io.bitsquare.common.crypto.CryptoException;
import io.bitsquare.common.crypto.KeyRing;
import io.bitsquare.common.crypto.PubKeyRing;
import io.bitsquare.crypto.EncryptionService;
import io.bitsquare.p2p.messaging.*;
import io.bitsquare.p2p.network.*;
import io.bitsquare.p2p.routing.Neighbor;
import io.bitsquare.p2p.routing.Routing;
import io.bitsquare.p2p.routing.RoutingListener;
import io.bitsquare.p2p.seed.SeedNodesRepository;
import io.bitsquare.p2p.storage.HashSetChangedListener;
import io.bitsquare.p2p.storage.ProtectedExpirableDataStorage;
import io.bitsquare.p2p.storage.data.ExpirableMailboxPayload;
import io.bitsquare.p2p.storage.data.ExpirablePayload;
import io.bitsquare.p2p.storage.data.ProtectedData;
import io.bitsquare.p2p.storage.data.ProtectedMailboxData;
import io.bitsquare.p2p.storage.messages.DataSetMessage;
import io.bitsquare.p2p.storage.messages.GetDataSetMessage;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.SignatureException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Represents our node in the P2P network
*/
public class P2PService {
private static final Logger log = LoggerFactory.getLogger(P2PService.class);
private final EncryptionService encryptionService;
private KeyRing keyRing;
private final NetworkStatistics networkStatistics;
private final NetworkNode networkNode;
private final Routing routing;
private final ProtectedExpirableDataStorage dataStorage;
private final List<DecryptedMailListener> decryptedMailListeners = new CopyOnWriteArrayList<>();
private final List<DecryptedMailboxListener> decryptedMailboxListeners = new CopyOnWriteArrayList<>();
private final List<P2PServiceListener> p2pServiceListeners = new CopyOnWriteArrayList<>();
private final Map<DecryptedMessageWithPubKey, ProtectedMailboxData> mailboxMap = new ConcurrentHashMap<>();
private volatile boolean shutDownInProgress;
private List<Address> seedNodeAddresses;
private List<Address> connectedSeedNodes = new CopyOnWriteArrayList<>();
private Set<Address> authenticatedPeerAddresses = new HashSet<>();
private boolean authenticatedToFirstPeer;
private boolean allDataReceived;
public boolean authenticated;
private boolean shutDownComplete;
private List<Runnable> shutDownResultHandlers = new CopyOnWriteArrayList<>();
private final List<Long> getDataSetMessageNonceList = new ArrayList<>();
private boolean allSeedNodesRequested;
private Timer sendGetAllDataMessageTimer;
private volatile boolean hiddenServiceReady;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public P2PService(SeedNodesRepository seedNodesRepository,
@Named(ProgramArguments.PORT_KEY) int port,
@Named(ProgramArguments.TOR_DIR) File torDir,
@Named(ProgramArguments.USE_LOCALHOST) boolean useLocalhost,
EncryptionService encryptionService,
KeyRing keyRing) {
this.encryptionService = encryptionService;
this.keyRing = keyRing;
networkStatistics = new NetworkStatistics();
// network layer
if (useLocalhost) {
networkNode = new LocalhostNetworkNode(port);
seedNodeAddresses = seedNodesRepository.getLocalhostSeedNodeAddresses();
} else {
networkNode = new TorNetworkNode(port, torDir);
seedNodeAddresses = seedNodesRepository.getTorSeedNodeAddresses();
}
// routing layer
routing = new Routing(networkNode, seedNodeAddresses);
// storage layer
dataStorage = new ProtectedExpirableDataStorage(routing, encryptionService);
// Listeners
networkNode.addMessageListener((message, connection) -> {
if (message instanceof GetDataSetMessage) {
log.trace("Received GetAllDataMessage: " + message);
// we only reply if we did not get the message form ourselves (in case we are a seed node)
if (!getDataSetMessageNonceList.contains(((GetDataSetMessage) message).nonce)) {
networkNode.sendMessage(connection, new DataSetMessage(getHashSet()));
} else {
connection.shutDown(() -> {
if (allSeedNodesRequested) dataReceived();
});
}
} else if (message instanceof DataSetMessage) {
log.trace("Received AllDataMessage: " + message);
// we keep that connection open as the bootstrapping peer will use that for the authentication
// as we are not authenticated yet the data adding will not be broadcasted
HashSet<ProtectedData> set = ((DataSetMessage) message).set;
set.stream().forEach(e -> dataStorage.add(e, connection.getPeerAddress()));
set.stream().filter(e -> e instanceof ProtectedMailboxData).forEach(e -> tryDecryptMailboxData((ProtectedMailboxData) e));
dataReceived();
} else if (message instanceof SealedAndSignedMessage) {
try {
DecryptedMessageWithPubKey decryptedMessageWithPubKey = encryptionService.decryptAndVerifyMessage((SealedAndSignedMessage) message);
UserThread.execute(() -> decryptedMailListeners.stream().forEach(e -> e.onMailMessage(decryptedMessageWithPubKey, connection.getPeerAddress())));
} catch (CryptoException e) {
log.info("Decryption of SealedAndSignedMessage failed. That is expected if the message is not intended for us.");
}
}
});
routing.addRoutingListener(new RoutingListener() {
@Override
public void onFirstNeighborAdded(Neighbor neighbor) {
log.trace("onFirstNeighbor " + neighbor.toString());
}
@Override
public void onNeighborAdded(Neighbor neighbor) {
}
@Override
public void onNeighborRemoved(Address address) {
}
@Override
public void onConnectionAuthenticated(Connection connection) {
}
});
dataStorage.addHashSetChangedListener(new HashSetChangedListener() {
@Override
public void onAdded(ProtectedData entry) {
if (entry instanceof ProtectedMailboxData)
tryDecryptMailboxData((ProtectedMailboxData) entry);
}
@Override
public void onRemoved(ProtectedData entry) {
}
});
}
public void protocol() {
// networkNode.start
// onTorNodeReady: sendGetAllDataMessage
// onHiddenServiceReady: tryStartAuthentication
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
// startup sequence
// networkNode.start
// SetupListener.onTorNodeReady: sendGetAllDataMessage
// SetupListener.onHiddenServiceReady: tryStartAuthentication
// if hiddenServiceReady && allDataReceived) routing.startAuthentication
// ConnectionListener.onPeerAddressAuthenticated
public void start() {
start(null);
}
public void start(@Nullable P2PServiceListener listener) {
if (listener != null)
addP2PServiceListener(listener);
networkNode.start(new SetupListener() {
@Override
public void onTorNodeReady() {
UserThread.execute(() -> p2pServiceListeners.stream().forEach(e -> e.onTorNodeReady()));
// we don't know yet our own address so we can not filter that from the
// seedNodeAddresses in case we are a seed node
sendGetAllDataMessage(seedNodeAddresses);
}
@Override
public void onHiddenServiceReady() {
hiddenServiceReady = true;
tryStartAuthentication();
UserThread.execute(() -> p2pServiceListeners.stream().forEach(e -> e.onHiddenServiceReady()));
}
@Override
public void onSetupFailed(Throwable throwable) {
UserThread.execute(() -> p2pServiceListeners.stream().forEach(e -> e.onSetupFailed(throwable)));
}
});
networkNode.addConnectionListener(new ConnectionListener() {
@Override
public void onConnection(Connection connection) {
}
@Override
public void onPeerAddressAuthenticated(Address peerAddress, Connection connection) {
authenticatedPeerAddresses.add(peerAddress);
authenticatedToFirstPeer = true;
P2PService.this.authenticated = true;
dataStorage.setAuthenticated(true);
UserThread.execute(() -> p2pServiceListeners.stream().forEach(e -> e.onAuthenticated()));
}
@Override
public void onDisconnect(Reason reason, Connection connection) {
Address peerAddress = connection.getPeerAddress();
if (peerAddress != null)
authenticatedPeerAddresses.remove(peerAddress);
}
@Override
public void onError(Throwable throwable) {
log.error("onError self/ConnectionException " + networkNode.getAddress() + "/" + throwable);
}
});
}
public void shutDown(Runnable shutDownCompleteHandler) {
if (!shutDownInProgress) {
shutDownInProgress = true;
shutDownResultHandlers.add(shutDownCompleteHandler);
if (sendGetAllDataMessageTimer != null)
sendGetAllDataMessageTimer.cancel();
if (dataStorage != null)
dataStorage.shutDown();
if (routing != null)
routing.shutDown();
if (networkNode != null)
networkNode.shutDown(() -> {
UserThread.execute(() -> shutDownResultHandlers.stream().forEach(e -> new Thread(e).start()));
shutDownComplete = true;
});
} else {
if (shutDownComplete)
new Thread(shutDownCompleteHandler).start();
else
shutDownResultHandlers.add(shutDownCompleteHandler);
log.warn("shutDown already in progress");
}
}
public boolean isAuthenticated() {
return authenticated;
}
public void removeEntryFromMailbox(DecryptedMessageWithPubKey decryptedMessageWithPubKey) {
log.trace("removeEntryFromMailbox");
ProtectedMailboxData mailboxData = mailboxMap.get(decryptedMessageWithPubKey);
if (mailboxData != null && mailboxData.expirablePayload instanceof ExpirableMailboxPayload) {
checkArgument(mailboxData.receiversPubKey.equals(keyRing.getStorageSignatureKeyPair().getPublic()),
"mailboxData.receiversPubKey is not matching with our key. That must not happen.");
removeMailboxData((ExpirableMailboxPayload) mailboxData.expirablePayload, mailboxData.receiversPubKey);
mailboxMap.remove(decryptedMessageWithPubKey);
log.trace("Removed successfully protectedExpirableData.");
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// Messaging
///////////////////////////////////////////////////////////////////////////////////////////
public void sendEncryptedMailMessage(Address peerAddress, PubKeyRing pubKeyRing, MailMessage message, SendMailMessageListener sendMailMessageListener) {
checkNotNull(peerAddress, "PeerAddress must not be null (sendEncryptedMailMessage)");
if (!authenticatedToFirstPeer)
throw new AuthenticationException("You must be authenticated before sending direct messages.");
if (!authenticatedPeerAddresses.contains(peerAddress))
routing.authenticateToPeer(peerAddress,
() -> doSendEncryptedMailMessage(peerAddress, pubKeyRing, message, sendMailMessageListener),
() -> UserThread.execute(() -> sendMailMessageListener.onFault()));
else
doSendEncryptedMailMessage(peerAddress, pubKeyRing, message, sendMailMessageListener);
}
private void doSendEncryptedMailMessage(Address peerAddress, PubKeyRing pubKeyRing, MailMessage message, SendMailMessageListener sendMailMessageListener) {
try {
SealedAndSignedMessage sealedAndSignedMessage = encryptionService.encryptAndSignMessage(pubKeyRing, message);
SettableFuture<Connection> future = sendMessage(peerAddress, sealedAndSignedMessage);
Futures.addCallback(future, new FutureCallback<Connection>() {
@Override
public void onSuccess(@Nullable Connection connection) {
UserThread.execute(() -> sendMailMessageListener.onArrived());
}
@Override
public void onFailure(Throwable throwable) {
throwable.printStackTrace();
UserThread.execute(() -> sendMailMessageListener.onFault());
}
});
} catch (CryptoException e) {
e.printStackTrace();
UserThread.execute(() -> sendMailMessageListener.onFault());
}
}
public void sendEncryptedMailboxMessage(Address peerAddress, PubKeyRing peersPubKeyRing, MailboxMessage message, SendMailboxMessageListener sendMailboxMessageListener) {
checkNotNull(peerAddress, "PeerAddress must not be null (sendEncryptedMailboxMessage)");
checkArgument(!keyRing.getPubKeyRing().equals(peersPubKeyRing), "We got own keyring instead of that from peer");
if (!authenticatedToFirstPeer)
throw new AuthenticationException("You must be authenticated before sending direct messages.");
if (authenticatedPeerAddresses.contains(peerAddress)) {
trySendEncryptedMailboxMessage(peerAddress, peersPubKeyRing, message, sendMailboxMessageListener);
} else {
routing.authenticateToPeer(peerAddress,
() -> trySendEncryptedMailboxMessage(peerAddress, peersPubKeyRing, message, sendMailboxMessageListener),
() -> {
log.info("We cannot authenticate to peer. Peer might be offline. We will store message in mailbox.");
trySendEncryptedMailboxMessage(peerAddress, peersPubKeyRing, message, sendMailboxMessageListener);
});
}
}
private void trySendEncryptedMailboxMessage(Address peerAddress, PubKeyRing peersPubKeyRing, MailboxMessage message, SendMailboxMessageListener sendMailboxMessageListener) {
try {
SealedAndSignedMessage sealedAndSignedMessage = encryptionService.encryptAndSignMessage(peersPubKeyRing, message);
SettableFuture<Connection> future = sendMessage(peerAddress, sealedAndSignedMessage);
Futures.addCallback(future, new FutureCallback<Connection>() {
@Override
public void onSuccess(@Nullable Connection connection) {
log.trace("SendEncryptedMailboxMessage onSuccess");
UserThread.execute(() -> sendMailboxMessageListener.onArrived());
}
@Override
public void onFailure(Throwable throwable) {
log.trace("SendEncryptedMailboxMessage onFailure");
log.debug(throwable.toString());
log.info("We cannot send message to peer. Peer might be offline. We will store message in mailbox.");
log.trace("create MailboxEntry with peerAddress " + peerAddress);
PublicKey receiverStoragePublicKey = peersPubKeyRing.getStorageSignaturePubKey();
addMailboxData(new ExpirableMailboxPayload(sealedAndSignedMessage,
keyRing.getStorageSignatureKeyPair().getPublic(),
receiverStoragePublicKey),
receiverStoragePublicKey);
UserThread.execute(() -> sendMailboxMessageListener.onStoredInMailbox());
}
});
} catch (CryptoException e) {
e.printStackTrace();
log.error("sendEncryptedMessage failed");
UserThread.execute(() -> sendMailboxMessageListener.onFault());
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// ProtectedData
///////////////////////////////////////////////////////////////////////////////////////////
public boolean addData(ExpirablePayload expirablePayload) {
if (!authenticatedToFirstPeer)
throw new AuthenticationException("You must be authenticated before adding data to the P2P network.");
try {
return dataStorage.add(dataStorage.getDataWithSignedSeqNr(expirablePayload, keyRing.getStorageSignatureKeyPair()), networkNode.getAddress());
} catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException e) {
log.error("Signing at getDataWithSignedSeqNr failed. That should never happen.");
return false;
}
}
public boolean addMailboxData(ExpirableMailboxPayload expirableMailboxPayload, PublicKey receiversPublicKey) {
if (!authenticatedToFirstPeer)
throw new AuthenticationException("You must be authenticated before adding data to the P2P network.");
try {
return dataStorage.add(dataStorage.getMailboxDataWithSignedSeqNr(expirableMailboxPayload, keyRing.getStorageSignatureKeyPair(), receiversPublicKey), networkNode.getAddress());
} catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException e) {
log.error("Signing at getDataWithSignedSeqNr failed. That should never happen.");
return false;
}
}
public boolean removeData(ExpirablePayload expirablePayload) {
if (!authenticatedToFirstPeer)
throw new AuthenticationException("You must be authenticated before removing data from the P2P network.");
try {
return dataStorage.remove(dataStorage.getDataWithSignedSeqNr(expirablePayload, keyRing.getStorageSignatureKeyPair()), networkNode.getAddress());
} catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException e) {
log.error("Signing at getDataWithSignedSeqNr failed. That should never happen.");
return false;
}
}
public boolean removeMailboxData(ExpirableMailboxPayload expirableMailboxPayload, PublicKey receiversPublicKey) {
if (!authenticatedToFirstPeer)
throw new AuthenticationException("You must be authenticated before removing data from the P2P network.");
try {
return dataStorage.removeMailboxData(dataStorage.getMailboxDataWithSignedSeqNr(expirableMailboxPayload, keyRing.getStorageSignatureKeyPair(), receiversPublicKey), networkNode.getAddress());
} catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException e) {
log.error("Signing at getDataWithSignedSeqNr failed. That should never happen.");
return false;
}
}
public Map<BigInteger, ProtectedData> getDataMap() {
return dataStorage.getMap();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Listeners
///////////////////////////////////////////////////////////////////////////////////////////
public void addMessageListener(MessageListener messageListener) {
networkNode.addMessageListener(messageListener);
}
public void removeMessageListener(MessageListener messageListener) {
networkNode.removeMessageListener(messageListener);
}
public void addDecryptedMailListener(DecryptedMailListener listener) {
decryptedMailListeners.add(listener);
}
public void removeDecryptedMailListener(DecryptedMailListener listener) {
decryptedMailListeners.remove(listener);
}
public void addDecryptedMailboxListener(DecryptedMailboxListener listener) {
decryptedMailboxListeners.add(listener);
}
public void removeDecryptedMailboxListener(DecryptedMailboxListener listener) {
decryptedMailboxListeners.remove(listener);
}
public void addP2PServiceListener(P2PServiceListener listener) {
p2pServiceListeners.add(listener);
}
public void removeP2PServiceListener(P2PServiceListener listener) {
p2pServiceListeners.remove(listener);
}
public void addHashSetChangedListener(HashSetChangedListener hashSetChangedListener) {
dataStorage.addHashSetChangedListener(hashSetChangedListener);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getters
///////////////////////////////////////////////////////////////////////////////////////////
public NetworkNode getNetworkNode() {
return networkNode;
}
public Routing getRouting() {
return routing;
}
public Address getAddress() {
return networkNode.getAddress();
}
public NetworkStatistics getNetworkStatistics() {
return networkStatistics;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
///////////////////////////////////////////////////////////////////////////////////////////
private void sendGetAllDataMessage(List<Address> seedNodeAddresses) {
Address networkNodeAddress = networkNode.getAddress();
if (networkNodeAddress != null)
seedNodeAddresses.remove(networkNodeAddress);
List<Address> remainingSeedNodeAddresses = new CopyOnWriteArrayList<>(seedNodeAddresses);
if (!seedNodeAddresses.isEmpty()) {
Collections.shuffle(remainingSeedNodeAddresses);
Address candidate = remainingSeedNodeAddresses.remove(0);
log.info("We try to send a GetAllDataMessage request to a random seed node. " + candidate);
// we use a nonce to see if we are sending to ourselves in case we are a seed node
// we don't know our own onion address at that moment so we cannot filter seed nodes
SettableFuture<Connection> future = sendMessage(candidate, new GetDataSetMessage(addToListAndGetNonce()));
Futures.addCallback(future, new FutureCallback<Connection>() {
@Override
public void onSuccess(@Nullable Connection connection) {
log.info("Send GetAllDataMessage to " + candidate + " succeeded.");
connectedSeedNodes.add(candidate);
// we try to connect to 2 seed nodes
if (connectedSeedNodes.size() < 2 && !remainingSeedNodeAddresses.isEmpty()) {
// give a random pause of 1-3 sec. before using the next
sendGetAllDataMessageTimer = new Timer();
sendGetAllDataMessageTimer.schedule(new TimerTask() {
@Override
public void run() {
sendGetAllDataMessage(remainingSeedNodeAddresses);
}
}, new Random().nextInt(2000) + 1000);
} else {
allSeedNodesRequested = true;
}
}
@Override
public void onFailure(Throwable throwable) {
log.info("Send GetAllDataMessage to " + candidate + " failed. Exception:" + throwable.getMessage());
log.trace("We try to connect another random seed node. " + remainingSeedNodeAddresses);
sendGetAllDataMessage(remainingSeedNodeAddresses);
}
});
} else {
log.info("There is no seed node available for requesting data. That is expected for the first seed node.");
dataReceived();
allSeedNodesRequested = true;
}
}
private long addToListAndGetNonce() {
long nonce = new Random().nextLong();
while (nonce == 0) {
nonce = new Random().nextLong();
}
getDataSetMessageNonceList.add(nonce);
return nonce;
}
private void dataReceived() {
if (!allDataReceived) {
allDataReceived = true;
UserThread.execute(() -> p2pServiceListeners.stream().forEach(e -> e.onAllDataReceived()));
tryStartAuthentication();
}
}
private void tryStartAuthentication() {
// we need to have both the initial data delivered and the hidden service published before we
// bootstrap and authenticate to other nodes
if (allDataReceived && hiddenServiceReady) {
// we remove ourselves in case we are a seed node
checkArgument(networkNode.getAddress() != null, "Address must be set when we are authenticated");
connectedSeedNodes.remove(networkNode.getAddress());
routing.startAuthentication(connectedSeedNodes);
}
}
private SettableFuture<Connection> sendMessage(Address peerAddress, Message message) {
return networkNode.sendMessage(peerAddress, message);
}
private HashSet<ProtectedData> getHashSet() {
return new HashSet<>(dataStorage.getMap().values());
}
private void tryDecryptMailboxData(ProtectedMailboxData mailboxData) {
ExpirablePayload data = mailboxData.expirablePayload;
if (data instanceof ExpirableMailboxPayload) {
ExpirableMailboxPayload mailboxEntry = (ExpirableMailboxPayload) data;
SealedAndSignedMessage sealedAndSignedMessage = mailboxEntry.sealedAndSignedMessage;
try {
DecryptedMessageWithPubKey decryptedMessageWithPubKey = encryptionService.decryptAndVerifyMessage(sealedAndSignedMessage);
if (decryptedMessageWithPubKey.message instanceof MailboxMessage) {
MailboxMessage mailboxMessage = (MailboxMessage) decryptedMessageWithPubKey.message;
Address senderAddress = mailboxMessage.getSenderAddress();
checkNotNull(senderAddress, "senderAddress must not be null for mailbox messages");
log.trace("mailboxData.publicKey " + mailboxData.ownerStoragePubKey.hashCode());
log.trace("keyRing.getStorageSignatureKeyPair().getPublic() " + keyRing.getStorageSignatureKeyPair().getPublic().hashCode());
log.trace("keyRing.getMsgSignatureKeyPair().getPublic() " + keyRing.getMsgSignatureKeyPair().getPublic().hashCode());
log.trace("keyRing.getMsgEncryptionKeyPair().getPublic() " + keyRing.getMsgEncryptionKeyPair().getPublic().hashCode());
mailboxMap.put(decryptedMessageWithPubKey, mailboxData);
log.trace("Decryption of SealedAndSignedMessage succeeded. senderAddress=" + senderAddress + " / my address=" + getAddress());
UserThread.execute(() -> decryptedMailboxListeners.stream().forEach(e -> e.onMailboxMessageAdded(decryptedMessageWithPubKey, senderAddress)));
}
} catch (CryptoException e) {
log.trace("Decryption of SealedAndSignedMessage failed. That is expected if the message is not intended for us.");
}
}
}
}

View file

@ -0,0 +1,91 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.crypto;
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.util.Utilities;
import io.bitsquare.p2p.Address;
import io.bitsquare.p2p.messaging.DecryptedMessageWithPubKey;
import io.bitsquare.p2p.messaging.MailboxMessage;
import io.bitsquare.p2p.messaging.SealedAndSignedMessage;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import static org.junit.Assert.assertEquals;
public class EncryptionServiceTests {
private static final Logger log = LoggerFactory.getLogger(EncryptionServiceTests.class);
@Rule
public ExpectedException thrown = ExpectedException.none();
private PubKeyRing pubKeyRing;
private KeyRing keyRing;
private File dir = new File("/tmp/bitsquare_tests");
@Before
public void setup() throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, CryptoException {
dir.mkdir();
KeyStorage keyStorage = new KeyStorage(dir);
keyRing = new KeyRing(keyStorage);
pubKeyRing = keyRing.getPubKeyRing();
}
@After
public void tearDown() throws IOException {
Utilities.deleteDirectory(dir);
}
@Test
public void testDecryptAndVerifyMessage() throws CryptoException {
EncryptionService encryptionService = new EncryptionService(keyRing);
TestMessage data = new TestMessage("test");
SealedAndSignedMessage encrypted = encryptionService.encryptAndSignMessage(pubKeyRing, data);
DecryptedMessageWithPubKey decrypted = encryptionService.decryptAndVerifyMessage(encrypted);
assertEquals(data.data, ((TestMessage) decrypted.message).data);
}
}
class TestMessage implements MailboxMessage {
public String data = "test";
public TestMessage(String data) {
this.data = data;
}
@Override
public Address getSenderAddress() {
return null;
}
}