From b4683bba68390b4463b37b425248963ab63f7dff Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Tue, 9 Jun 2015 22:45:24 +0200 Subject: [PATCH] Broadcasts. Receiving seems to work, but there still seems to be a problem with sending them. --- .../dissem/bitmessage/demo/Application.java | 78 +++++++++++++++--- .../java/ch/dissem/bitmessage/demo/Main.java | 63 +------------- .../dissem/bitmessage/BitmessageContext.java | 53 +++++++++++- .../bitmessage/DefaultMessageListener.java | 30 ++++--- .../ch/dissem/bitmessage/InternalContext.java | 19 +++-- .../bitmessage/entity/BitmessageAddress.java | 49 +++++++---- .../dissem/bitmessage/entity/Plaintext.java | 59 ++++++++++--- .../bitmessage/entity/PlaintextHolder.java | 21 +++++ .../bitmessage/entity/payload/Broadcast.java | 22 ++++- .../dissem/bitmessage/entity/payload/Msg.java | 8 +- .../entity/payload/V4Broadcast.java | 15 +++- .../entity/payload/V5Broadcast.java | 11 ++- .../ch/dissem/bitmessage/factory/Factory.java | 9 ++ .../dissem/bitmessage/utils/DebugUtils.java | 39 +++++++++ .../ch/dissem/bitmessage/DecryptionTest.java | 47 +++++++++++ .../entity/BitmessageAddressTest.java | 2 +- .../bitmessage/entity/SerializationTest.java | 5 +- .../ch/dissem/bitmessage/utils/TestUtils.java | 2 +- domain/src/test/resources/V4Broadcast.payload | Bin 1356 -> 396 bytes domain/src/test/resources/V5Broadcast.payload | Bin 668 -> 428 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- .../bitmessage/networking/Connection.java | 10 +-- .../repository/JdbcAddressRepository.java | 23 +++--- .../repository/JdbcMessageRepository.java | 46 ++++++----- .../migration/V1.3__Create_message_table.sql | 4 +- .../repository/JdbcMessageRepositoryTest.java | 21 ++++- 26 files changed, 466 insertions(+), 172 deletions(-) create mode 100644 domain/src/main/java/ch/dissem/bitmessage/entity/PlaintextHolder.java create mode 100644 domain/src/main/java/ch/dissem/bitmessage/utils/DebugUtils.java create mode 100644 domain/src/test/java/ch/dissem/bitmessage/DecryptionTest.java diff --git a/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java b/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java index 4489e6c..6bd271c 100644 --- a/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java +++ b/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java @@ -22,9 +22,6 @@ import ch.dissem.bitmessage.entity.Plaintext; import ch.dissem.bitmessage.entity.payload.Pubkey; import ch.dissem.bitmessage.networking.NetworkNode; import ch.dissem.bitmessage.repository.*; -import ch.dissem.bitmessage.utils.Strings; -import org.flywaydb.core.internal.util.logging.slf4j.Slf4jLog; -import org.flywaydb.core.internal.util.logging.slf4j.Slf4jLogCreator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -72,6 +69,7 @@ public class Application { System.out.println("available commands:"); System.out.println("i) identities"); System.out.println("c) contacts"); + System.out.println("s) subscriptions"); System.out.println("m) messages"); System.out.println("e) exit"); @@ -85,6 +83,9 @@ public class Application { case "c": contacts(); break; + case "s": + subscriptions(); + break; case "m": messages(); break; @@ -181,7 +182,7 @@ public class Application { command = nextCommand(); switch (command) { case "a": - addContact(); + addContact(false); contacts = ctx.addresses().getContacts(); break; case "b": @@ -197,7 +198,7 @@ public class Application { } while (!"b".equals(command)); } - private void addContact() { + private void addContact(boolean isSubscription) { System.out.println(); System.out.println("Please enter the Bitmessage address you want to add"); try { @@ -207,12 +208,56 @@ public class Application { if (alias.length() > 0) { address.setAlias(alias); } + if (isSubscription) { + ctx.addSubscribtion(address); + } ctx.addContact(address); } catch (IllegalArgumentException e) { System.out.println(e.getMessage()); } } + private void subscriptions() { + String command; + List subscriptions = ctx.addresses().getSubscriptions(); + do { + System.out.println(); + int i = 0; + for (BitmessageAddress contact : subscriptions) { + i++; + System.out.print(i + ") "); + if (contact.getAlias() != null) { + System.out.println(contact.getAlias() + " (" + contact.getAddress() + ")"); + } else { + System.out.println(contact.getAddress()); + } + } + if (i == 0) { + System.out.println("You have no subscriptions yet."); + } + System.out.println(); + System.out.println("a) add subscription"); + System.out.println("b) back"); + + command = nextCommand(); + switch (command) { + case "a": + addContact(true); + subscriptions = ctx.addresses().getSubscriptions(); + break; + case "b": + return; + default: + try { + int index = Integer.parseInt(command) - 1; + address(subscriptions.get(index)); + } catch (NumberFormatException e) { + System.out.println("Unknown command. Please try again."); + } + } + } while (!"b".equals(command)); + } + private void address(BitmessageAddress address) { System.out.println(); if (address.getAlias() != null) @@ -244,12 +289,16 @@ public class Application { } System.out.println(); System.out.println("c) compose message"); + System.out.println("s) compose broadcast"); System.out.println("b) back"); command = scanner.nextLine().trim(); switch (command) { case "c": - compose(); + compose(false); + break; + case "s": + compose(true); break; case "b": return; @@ -272,7 +321,7 @@ public class Application { System.out.println(); System.out.println(message.getText()); System.out.println(); - System.out.println("Labels: "+ message.getLabels()); + System.out.println("Labels: " + message.getLabels()); System.out.println(); String command; do { @@ -294,10 +343,10 @@ public class Application { } while (!"b".equalsIgnoreCase(command)); } - private void compose() { + private void compose(boolean broadcast) { System.out.println(); BitmessageAddress from = selectAddress(true); - BitmessageAddress to = selectAddress(false); + BitmessageAddress to = (broadcast ? null : selectAddress(false)); compose(from, to, null); } @@ -352,9 +401,12 @@ public class Application { } private void compose(BitmessageAddress from, BitmessageAddress to, String subject) { + boolean broadcast = (to == null); System.out.println(); System.out.println("From: " + from); - System.out.println("To: " + to); + if (!broadcast) { + System.out.println("To: " + to); + } if (subject != null) { System.out.println("Subject: " + subject); } else { @@ -368,7 +420,11 @@ public class Application { line = nextCommand(); message.append(line).append('\n'); } while (line.length() > 0 || !yesNo("Send message?")); - ctx.send(from, to, subject, message.toString()); + if (broadcast) { + ctx.broadcast(from, subject, message.toString()); + } else { + ctx.send(from, to, subject, message.toString()); + } } private boolean yesNo(String question) { diff --git a/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java b/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java index 1e7344b..c0c433d 100644 --- a/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java +++ b/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java @@ -16,71 +16,12 @@ package ch.dissem.bitmessage.demo; -import ch.dissem.bitmessage.BitmessageContext; -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.networking.NetworkNode; -import ch.dissem.bitmessage.repository.JdbcAddressRepository; -import ch.dissem.bitmessage.repository.JdbcInventory; -import ch.dissem.bitmessage.repository.JdbcMessageRepository; -import ch.dissem.bitmessage.repository.JdbcNodeRegistry; -import ch.dissem.bitmessage.utils.Base58; -import ch.dissem.bitmessage.utils.Encode; -import ch.dissem.bitmessage.utils.Security; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.util.Scanner; -/** - * Created by chris on 06.04.15. - */ public class Main { - public static void main(String[] args) throws IOException { - final BitmessageAddress address = new BitmessageAddress("BM-87hJ99tPAXxtetvnje7Z491YSvbEtBJVc5e"); - +// System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "TRACE"); +// System.setProperty("org.slf4j.simpleLogger.logFile", "./trace.log"); new Application(); -// -// -// List objects = new JdbcInventory().getObjects(address.getStream(), address.getVersion(), ObjectType.PUBKEY); -// System.out.println("Address version: " + address.getVersion()); -// System.out.println("Address stream: " + address.getStream()); -// for (ObjectMessage o : objects) { -//// if (!o.isSignatureValid()) System.out.println("Invalid signature."); -//// System.out.println(o.getPayload().getSignature().length); -// V4Pubkey pubkey = (V4Pubkey) o.getPayload(); -// if (Arrays.equals(address.getTag(), pubkey.getTag())) { -// System.out.println("Pubkey found!"); -// try { -// System.out.println("IV: " + o.getInventoryVector()); -// address.setPubkey(pubkey); -// } catch (Exception ignore) { -// System.out.println("But setPubkey failed? " + address.getRipe().length + "/" + pubkey.getRipe().length); -// System.out.println("Failed address: " + generateAddress(address.getStream(), address.getVersion(), pubkey.getRipe())); -// if (Arrays.equals(address.getRipe(), pubkey.getRipe())) { -// ignore.printStackTrace(); -// } -// } -// } -// } - } - - public static String generateAddress(long stream, long version, byte[] ripe) { - try { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - Encode.varInt(version, os); - Encode.varInt(stream, os); - os.write(ripe); - - byte[] checksum = Security.doubleSha512(os.toByteArray()); - os.write(checksum, 0, 4); - return "BM-" + Base58.encode(os.toByteArray()); - } catch (IOException e) { - throw new RuntimeException(e); - } } } diff --git a/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java index b3bae99..54efdf8 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java +++ b/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java @@ -19,10 +19,11 @@ package ch.dissem.bitmessage; import ch.dissem.bitmessage.entity.BitmessageAddress; import ch.dissem.bitmessage.entity.ObjectMessage; import ch.dissem.bitmessage.entity.Plaintext; -import ch.dissem.bitmessage.entity.Plaintext.Encoding; import ch.dissem.bitmessage.entity.payload.*; import ch.dissem.bitmessage.entity.payload.Pubkey.Feature; import ch.dissem.bitmessage.entity.valueobject.PrivateKey; +import ch.dissem.bitmessage.exception.DecryptionFailedException; +import ch.dissem.bitmessage.factory.Factory; import ch.dissem.bitmessage.ports.*; import ch.dissem.bitmessage.utils.Security; import ch.dissem.bitmessage.utils.UnixTime; @@ -35,6 +36,8 @@ import java.util.Collection; import java.util.TreeSet; import static ch.dissem.bitmessage.entity.Plaintext.Status.*; +import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST; +import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG; import static ch.dissem.bitmessage.utils.UnixTime.DAY; /** @@ -59,6 +62,8 @@ public class BitmessageContext { private final InternalContext ctx; + private Listener listener; + private BitmessageContext(Builder builder) { ctx = new InternalContext(builder); } @@ -89,12 +94,34 @@ public class BitmessageContext { // TODO } + public void broadcast(BitmessageAddress from, String subject, String message) { + // TODO: all this should happen in a separate thread + Plaintext msg = new Plaintext.Builder(BROADCAST) + .from(from) + .message(subject, message) + .build(); + + LOG.info("Sending message."); + msg.setStatus(DOING_PROOF_OF_WORK); + ctx.getMessageRepository().save(msg); + ctx.send( + from, + from, + Factory.getBroadcast(from, msg), + +2 * DAY, + 0, + 0 + ); + msg.setStatus(SENT); + ctx.getMessageRepository().save(msg); + } + public void send(BitmessageAddress from, BitmessageAddress to, String subject, String message) { if (from.getPrivateKey() == null) { throw new IllegalArgumentException("'From' must be an identity, i.e. have a private key."); } // TODO: all this should happen in a separate thread - Plaintext msg = new Plaintext.Builder() + Plaintext msg = new Plaintext.Builder(MSG) .from(from) .to(to) .message(subject, message) @@ -154,6 +181,7 @@ public class BitmessageContext { } public void startup(Listener listener) { + this.listener = listener; ctx.getNetworkHandler().start(new DefaultMessageListener(ctx, listener)); } @@ -176,7 +204,7 @@ public class BitmessageContext { if (address.getVersion() == 4) { V4Pubkey v4Pubkey = (V4Pubkey) pubkey; if (Arrays.equals(address.getTag(), v4Pubkey.getTag())) { - v4Pubkey.decrypt(address.getPubkeyDecryptionKey()); + v4Pubkey.decrypt(address.getPublicDecryptionKey()); if (object.isSignatureValid(v4Pubkey)) { address.setPubkey(v4Pubkey); ctx.getAddressRepo().save(address); @@ -198,6 +226,25 @@ public class BitmessageContext { } } + public void addSubscribtion(BitmessageAddress address) { + address.setSubscribed(true); + ctx.getAddressRepo().save(address); + tryToFindBroadcastsForAddress(address); + } + + private void tryToFindBroadcastsForAddress(BitmessageAddress address) { + for (ObjectMessage object : ctx.getInventory().getObjects(address.getStream(), Broadcast.getVersion(address), ObjectType.BROADCAST)) { + try { + Broadcast broadcast = (Broadcast) object.getPayload(); + broadcast.decrypt(address); + listener.receive(broadcast.getPlaintext()); + } catch (DecryptionFailedException ignore) { + } catch (Exception e) { + LOG.debug(e.getMessage(), e); + } + } + } + public interface Listener { void receive(Plaintext plaintext); } diff --git a/domain/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java b/domain/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java index 6c7175d..b9576e5 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java +++ b/domain/src/main/java/ch/dissem/bitmessage/DefaultMessageListener.java @@ -82,7 +82,7 @@ class DefaultMessageListener implements NetworkHandler.MessageListener { V4Pubkey v4Pubkey = (V4Pubkey) pubkey; address = ctx.getAddressRepo().findContact(v4Pubkey.getTag()); if (address != null) { - v4Pubkey.decrypt(address.getPubkeyDecryptionKey()); + v4Pubkey.decrypt(address.getPublicDecryptionKey()); } } else { address = ctx.getAddressRepo().findContact(pubkey.getRipe()); @@ -91,8 +91,8 @@ class DefaultMessageListener implements NetworkHandler.MessageListener { address.setPubkey(pubkey); LOG.debug("Got pubkey for contact " + address); List messages = ctx.getMessageRepository().findMessages(Plaintext.Status.PUBKEY_REQUESTED, address); + LOG.debug("Sending " + messages.size() + " messages for contact " + address); for (Plaintext msg : messages) { - // TODO: send messages enqueued for this address msg.setStatus(DOING_PROOF_OF_WORK); ctx.getMessageRepository().save(msg); ctx.send( @@ -118,14 +118,18 @@ class DefaultMessageListener implements NetworkHandler.MessageListener { try { msg.decrypt(identity.getPrivateKey().getPrivateEncryptionKey()); msg.getPlaintext().setTo(identity); - object.isSignatureValid(msg.getPlaintext().getFrom().getPubkey()); - msg.getPlaintext().setStatus(RECEIVED); - msg.getPlaintext().addLabels(ctx.getMessageRepository().getLabels(Label.Type.INBOX, Label.Type.UNREAD)); - ctx.getMessageRepository().save(msg.getPlaintext()); - listener.receive(msg.getPlaintext()); + if (!object.isSignatureValid(msg.getPlaintext().getFrom().getPubkey())) { + LOG.warn("Msg with IV " + object.getInventoryVector() + " was successfully decrypted, but signature check failed. Ignoring."); + } else { + msg.getPlaintext().setStatus(RECEIVED); + msg.getPlaintext().addLabels(ctx.getMessageRepository().getLabels(Label.Type.INBOX, Label.Type.UNREAD)); + msg.getPlaintext().setInventoryVector(object.getInventoryVector()); + ctx.getMessageRepository().save(msg.getPlaintext()); + listener.receive(msg.getPlaintext()); + } break; } catch (DecryptionFailedException ignore) { - LOG.debug(ignore.getMessage(), ignore); + LOG.trace(ignore.getMessage(), ignore); } } } @@ -135,9 +139,13 @@ class DefaultMessageListener implements NetworkHandler.MessageListener { // V5Broadcast v5 = broadcast instanceof V5Broadcast ? (V5Broadcast) broadcast : null; for (BitmessageAddress subscription : ctx.getAddressRepo().getSubscriptions()) { try { - broadcast.decrypt(subscription.getPubkeyDecryptionKey()); - object.isSignatureValid(broadcast.getPlaintext().getFrom().getPubkey()); - listener.receive(broadcast.getPlaintext()); + broadcast.decrypt(subscription.getPublicDecryptionKey()); + if (!object.isSignatureValid(broadcast.getPlaintext().getFrom().getPubkey())) { + LOG.warn("Broadcast with IV " + object.getInventoryVector() + " was successfully decrypted, but signature check failed. Ignoring."); + } else { + broadcast.getPlaintext().setInventoryVector(object.getInventoryVector()); + listener.receive(broadcast.getPlaintext()); + } } catch (DecryptionFailedException ignore) { } } diff --git a/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java b/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java index 94cf1f3..a66efd8 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java +++ b/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java @@ -16,9 +16,8 @@ package ch.dissem.bitmessage; -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.Encrypted; -import ch.dissem.bitmessage.entity.ObjectMessage; +import ch.dissem.bitmessage.entity.*; +import ch.dissem.bitmessage.entity.payload.Broadcast; import ch.dissem.bitmessage.entity.payload.GetPubkey; import ch.dissem.bitmessage.entity.payload.ObjectPayload; import ch.dissem.bitmessage.ports.*; @@ -141,6 +140,7 @@ public class InternalContext { public void send(BitmessageAddress from, BitmessageAddress to, ObjectPayload payload, long timeToLive, long nonceTrialsPerByte, long extraBytes) { try { + if (to == null) to = from; long expires = UnixTime.now(+timeToLive); LOG.info("Expires at " + expires); ObjectMessage object = new ObjectMessage.Builder() @@ -151,10 +151,17 @@ public class InternalContext { if (object.isSigned()) { object.sign(from.getPrivateKey()); } - if (payload instanceof Encrypted) { + if (payload instanceof Broadcast) { + ((Broadcast) payload).encrypt(); + } else if (payload instanceof Encrypted) { object.encrypt(to.getPubkey()); } Security.doProofOfWork(object, proofOfWorkEngine, nonceTrialsPerByte, extraBytes); + if (payload instanceof PlaintextHolder) { + Plaintext plaintext = ((PlaintextHolder) payload).getPlaintext(); + plaintext.setInventoryVector(object.getInventoryVector()); + messageRepository.save(plaintext); + } inventory.storeObject(object); networkHandler.offer(object.getInventoryVector()); } catch (IOException e) { @@ -172,13 +179,13 @@ public class InternalContext { .payload(identity.getPubkey()) .build(); response.sign(identity.getPrivateKey()); - response.encrypt(Security.createPublicKey(identity.getPubkeyDecryptionKey()).getEncoded(false)); + response.encrypt(Security.createPublicKey(identity.getPublicDecryptionKey()).getEncoded(false)); Security.doProofOfWork(response, proofOfWorkEngine, networkNonceTrialsPerByte, networkExtraBytes); if (response.isSigned()) { response.sign(identity.getPrivateKey()); } if (response instanceof Encrypted) { - response.encrypt(Security.createPublicKey(identity.getPubkeyDecryptionKey()).getEncoded(false)); + response.encrypt(Security.createPublicKey(identity.getPublicDecryptionKey()).getEncoded(false)); } inventory.storeObject(response); networkHandler.offer(response.getInventoryVector()); diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java b/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java index 767b709..923f101 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java @@ -42,7 +42,7 @@ public class BitmessageAddress { /** * Used for V4 address encryption. It's easier to just create it regardless of address version. */ - private final byte[] pubkeyDecryptionKey; + private final byte[] publicDecryptionKey; private String address; @@ -61,14 +61,20 @@ public class BitmessageAddress { ByteArrayOutputStream os = new ByteArrayOutputStream(); Encode.varInt(version, os); Encode.varInt(stream, os); - // for the tag, the checksum has to be created with 0x00 padding - byte[] checksum = Security.doubleSha512(os.toByteArray(), ripe); - this.tag = Arrays.copyOfRange(checksum, 32, 64); - this.pubkeyDecryptionKey = Arrays.copyOfRange(checksum, 0, 32); + if (version < 4) { + byte[] checksum = Security.sha512(os.toByteArray(), ripe); + this.tag = null; + this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32); + } else { + // for tag and decryption key, the checksum has to be created with 0x00 padding + byte[] checksum = Security.doubleSha512(os.toByteArray(), ripe); + this.tag = Arrays.copyOfRange(checksum, 32, 64); + this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32); + } // but for the address and its checksum they need to be stripped int offset = Bytes.numberOfLeadingZeros(ripe); os.write(ripe, offset, ripe.length - offset); - checksum = Security.doubleSha512(os.toByteArray()); + byte[] checksum = Security.doubleSha512(os.toByteArray()); os.write(checksum, 0, 4); this.address = "BM-" + Base58.encode(os.toByteArray()); } catch (IOException e) { @@ -103,9 +109,15 @@ public class BitmessageAddress { if (expectedChecksum[i] != checksum[i]) throw new IllegalArgumentException("Checksum of address failed"); } - checksum = Security.doubleSha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe); - this.tag = Arrays.copyOfRange(checksum, 32, 64); - this.pubkeyDecryptionKey = Arrays.copyOfRange(checksum, 0, 32); + if (version < 4) { + checksum = Security.sha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe); + this.tag = null; + this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32); + } else { + checksum = Security.doubleSha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe); + this.tag = Arrays.copyOfRange(checksum, 32, 64); + this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32); + } } catch (IOException e) { throw new RuntimeException(e); } @@ -145,8 +157,11 @@ public class BitmessageAddress { this.pubkey = pubkey; } - public byte[] getPubkeyDecryptionKey() { - return pubkeyDecryptionKey; + /** + * Returns the private key used to decrypt Pubkey objects (for v4 addresses) and broadcasts. + */ + public byte[] getPublicDecryptionKey() { + return publicDecryptionKey; } public PrivateKey getPrivateKey() { @@ -165,10 +180,6 @@ public class BitmessageAddress { this.alias = alias; } - public void setSubscribed(boolean subscribed) { - this.subscribed = subscribed; - } - @Override public String toString() { return alias != null ? alias : address; @@ -196,4 +207,12 @@ public class BitmessageAddress { public int hashCode() { return Arrays.hashCode(ripe); } + + public boolean isSubscribed() { + return subscribed; + } + + public void setSubscribed(boolean subscribed) { + this.subscribed = subscribed; + } } diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java b/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java index 340a774..9b5e268 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java @@ -16,6 +16,7 @@ package ch.dissem.bitmessage.entity; +import ch.dissem.bitmessage.entity.valueobject.InventoryVector; import ch.dissem.bitmessage.entity.valueobject.Label; import ch.dissem.bitmessage.factory.Factory; import ch.dissem.bitmessage.utils.Decode; @@ -28,11 +29,13 @@ import java.util.*; * The unencrypted message to be sent by 'msg' or 'broadcast'. */ public class Plaintext implements Streamable { + private final Type type; private final BitmessageAddress from; private final long encoding; private final byte[] message; private final byte[] ack; private Object id; + private InventoryVector inventoryVector; private BitmessageAddress to; private byte[] signature; private Status status; @@ -43,6 +46,8 @@ public class Plaintext implements Streamable { private Plaintext(Builder builder) { id = builder.id; + inventoryVector = builder.inventoryVector; + type = builder.type; from = builder.from; to = builder.to; encoding = builder.encoding; @@ -55,14 +60,14 @@ public class Plaintext implements Streamable { labels = builder.labels; } - public static Plaintext read(InputStream in) throws IOException { - return readWithoutSignature(in) + public static Plaintext read(Type type, InputStream in) throws IOException { + return readWithoutSignature(type, in) .signature(Decode.varBytes(in)) .build(); } - public static Plaintext.Builder readWithoutSignature(InputStream in) throws IOException { - return new Builder() + public static Plaintext.Builder readWithoutSignature(Type type, InputStream in) throws IOException { + return new Builder(type) .addressVersion(Decode.varInt(in)) .stream(Decode.varInt(in)) .behaviorBitfield(Decode.int32(in)) @@ -70,10 +75,22 @@ public class Plaintext implements Streamable { .publicEncryptionKey(Decode.bytes(in, 64)) .nonceTrialsPerByte(Decode.varInt(in)) .extraBytes(Decode.varInt(in)) - .destinationRipe(Decode.bytes(in, 20)) + .destinationRipe(type == Type.MSG ? Decode.bytes(in, 20) : null) .encoding(Decode.varInt(in)) .message(Decode.varBytes(in)) - .ack(Decode.varBytes(in)); + .ack(type == Type.MSG ? Decode.varBytes(in) : null); + } + + public InventoryVector getInventoryVector() { + return inventoryVector; + } + + public void setInventoryVector(InventoryVector inventoryVector) { + this.inventoryVector = inventoryVector; + } + + public Type getType() { + return type; } public byte[] getMessage() { @@ -121,12 +138,16 @@ public class Plaintext implements Streamable { out.write(from.getPubkey().getEncryptionKey(), 1, 64); Encode.varInt(from.getPubkey().getNonceTrialsPerByte(), out); Encode.varInt(from.getPubkey().getExtraBytes(), out); - out.write(to.getRipe()); + if (type == Type.MSG) { + out.write(to.getRipe()); + } Encode.varInt(encoding, out); Encode.varInt(message.length, out); out.write(message); - Encode.varInt(ack.length, out); - out.write(ack); + if (type == Type.MSG) { + Encode.varInt(ack.length, out); + out.write(ack); + } if (includeSignature) { if (signature == null) { Encode.varInt(0, out); @@ -249,8 +270,14 @@ public class Plaintext implements Streamable { RECEIVED } + public enum Type { + MSG, BROADCAST + } + public static final class Builder { private Object id; + private InventoryVector inventoryVector; + private Type type; private BitmessageAddress from; private BitmessageAddress to; private long addressVersion; @@ -270,7 +297,8 @@ public class Plaintext implements Streamable { private Status status; private Set<Label> labels = new HashSet<>(); - public Builder() { + public Builder(Type type) { + this.type = type; } public Builder id(Object id) { @@ -278,12 +306,19 @@ public class Plaintext implements Streamable { return this; } + public Builder IV(InventoryVector iv) { + this.inventoryVector = iv; + return this; + } + public Builder from(BitmessageAddress address) { from = address; return this; } public Builder to(BitmessageAddress address) { + if (type != Type.MSG && to != null) + throw new IllegalArgumentException("recipient address only allowed for msg"); to = address; return this; } @@ -324,6 +359,7 @@ public class Plaintext implements Streamable { } private Builder destinationRipe(byte[] ripe) { + if (type != Type.MSG && ripe != null) throw new IllegalArgumentException("ripe only allowed for msg"); this.destinationRipe = ripe; return this; } @@ -354,6 +390,7 @@ public class Plaintext implements Streamable { } public Builder ack(byte[] ack) { + if (type != Type.MSG && ack != null) throw new IllegalArgumentException("ack only allowed for msg"); this.ack = ack; return this; } @@ -395,7 +432,7 @@ public class Plaintext implements Streamable { behaviorBitfield )); } - if (to == null) { + if (to == null && type != Type.BROADCAST) { to = new BitmessageAddress(0, 0, destinationRipe); } return new Plaintext(this); diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/PlaintextHolder.java b/domain/src/main/java/ch/dissem/bitmessage/entity/PlaintextHolder.java new file mode 100644 index 0000000..3133cab --- /dev/null +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/PlaintextHolder.java @@ -0,0 +1,21 @@ +/* + * Copyright 2015 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.entity; + +public interface PlaintextHolder { + Plaintext getPlaintext(); +} diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java index 327a107..1d86310 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java @@ -16,17 +16,22 @@ package ch.dissem.bitmessage.entity.payload; +import ch.dissem.bitmessage.entity.BitmessageAddress; import ch.dissem.bitmessage.entity.Encrypted; import ch.dissem.bitmessage.entity.Plaintext; +import ch.dissem.bitmessage.entity.PlaintextHolder; import ch.dissem.bitmessage.exception.DecryptionFailedException; +import ch.dissem.bitmessage.utils.Security; import java.io.IOException; +import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST; + /** * Users who are subscribed to the sending address will see the message appear in their inbox. * Broadcasts are version 4 or 5. */ -public abstract class Broadcast extends ObjectPayload implements Encrypted { +public abstract class Broadcast extends ObjectPayload implements Encrypted, PlaintextHolder { protected final long stream; protected CryptoBox encrypted; protected Plaintext plaintext; @@ -38,11 +43,16 @@ public abstract class Broadcast extends ObjectPayload implements Encrypted { this.plaintext = plaintext; } + public static long getVersion(BitmessageAddress address) { + return address.getVersion() < 4 ? 4 : 5; + } + @Override public long getStream() { return stream; } + @Override public Plaintext getPlaintext() { return plaintext; } @@ -52,9 +62,17 @@ public abstract class Broadcast extends ObjectPayload implements Encrypted { this.encrypted = new CryptoBox(plaintext, publicKey); } + public void encrypt() throws IOException { + encrypt(Security.createPublicKey(plaintext.getFrom().getPublicDecryptionKey()).getEncoded(false)); + } + @Override public void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException { - plaintext = Plaintext.read(encrypted.decrypt(privateKey)); + plaintext = Plaintext.read(BROADCAST, encrypted.decrypt(privateKey)); + } + + public void decrypt(BitmessageAddress address) throws IOException, DecryptionFailedException { + decrypt(address.getPublicDecryptionKey()); } @Override diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java index 22175df..52c36e7 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Msg.java @@ -18,16 +18,19 @@ package ch.dissem.bitmessage.entity.payload; import ch.dissem.bitmessage.entity.Encrypted; import ch.dissem.bitmessage.entity.Plaintext; +import ch.dissem.bitmessage.entity.PlaintextHolder; import ch.dissem.bitmessage.exception.DecryptionFailedException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG; + /** * Used for person-to-person messages. */ -public class Msg extends ObjectPayload implements Encrypted { +public class Msg extends ObjectPayload implements Encrypted, PlaintextHolder { private long stream; private CryptoBox encrypted; private Plaintext plaintext; @@ -48,6 +51,7 @@ public class Msg extends ObjectPayload implements Encrypted { return new Msg(stream, CryptoBox.read(in, length)); } + @Override public Plaintext getPlaintext() { return plaintext; } @@ -89,7 +93,7 @@ public class Msg extends ObjectPayload implements Encrypted { @Override public void decrypt(byte[] privateKey) throws IOException, DecryptionFailedException { - plaintext = Plaintext.read(encrypted.decrypt(privateKey)); + plaintext = Plaintext.read(MSG, encrypted.decrypt(privateKey)); } @Override diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java index a2b6b23..f0f579e 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java @@ -16,6 +16,9 @@ package ch.dissem.bitmessage.entity.payload; +import ch.dissem.bitmessage.entity.BitmessageAddress; +import ch.dissem.bitmessage.entity.Plaintext; + import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -25,12 +28,18 @@ import java.io.OutputStream; * Broadcasts are version 4 or 5. */ public class V4Broadcast extends Broadcast { - protected V4Broadcast(long version, long stream, CryptoBox encrypted) { - super(version, stream, encrypted, null); + protected V4Broadcast(long version, long stream, CryptoBox encrypted, Plaintext plaintext) { + super(version, stream, encrypted, plaintext); + } + + public V4Broadcast(BitmessageAddress senderAddress, Plaintext plaintext) { + super(4, senderAddress.getStream(), null, plaintext); + if (senderAddress.getVersion() >= 4) + throw new IllegalArgumentException("Address version 3 or older expected, but was " + senderAddress.getVersion()); } public static V4Broadcast read(InputStream in, long stream, int length) throws IOException { - return new V4Broadcast(4, stream, CryptoBox.read(in, length)); + return new V4Broadcast(4, stream, CryptoBox.read(in, length), null); } @Override diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.java index 65e4d1a..298fd5c 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V5Broadcast.java @@ -16,6 +16,8 @@ package ch.dissem.bitmessage.entity.payload; +import ch.dissem.bitmessage.entity.BitmessageAddress; +import ch.dissem.bitmessage.entity.Plaintext; import ch.dissem.bitmessage.utils.Decode; import java.io.IOException; @@ -29,10 +31,17 @@ public class V5Broadcast extends V4Broadcast { private byte[] tag; private V5Broadcast(long stream, byte[] tag, CryptoBox encrypted) { - super(5, stream, encrypted); + super(5, stream, encrypted, null); this.tag = tag; } + public V5Broadcast(BitmessageAddress senderAddress, Plaintext plaintext) { + super(5, senderAddress.getStream(), null, plaintext); + if (senderAddress.getVersion() < 4) + throw new IllegalArgumentException("Address version 4 (or newer) expected, but was " + senderAddress.getVersion()); + this.tag = senderAddress.getTag(); + } + public static V5Broadcast read(InputStream is, long stream, int length) throws IOException { return new V5Broadcast(stream, Decode.bytes(is, 32), CryptoBox.read(is, length - 32)); } diff --git a/domain/src/main/java/ch/dissem/bitmessage/factory/Factory.java b/domain/src/main/java/ch/dissem/bitmessage/factory/Factory.java index 5595602..85878a9 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/factory/Factory.java +++ b/domain/src/main/java/ch/dissem/bitmessage/factory/Factory.java @@ -19,6 +19,7 @@ package ch.dissem.bitmessage.factory; import ch.dissem.bitmessage.entity.BitmessageAddress; import ch.dissem.bitmessage.entity.NetworkMessage; import ch.dissem.bitmessage.entity.ObjectMessage; +import ch.dissem.bitmessage.entity.Plaintext; import ch.dissem.bitmessage.entity.payload.*; import ch.dissem.bitmessage.entity.valueobject.PrivateKey; import org.slf4j.Logger; @@ -164,4 +165,12 @@ public class Factory { return GenericPayload.read(version, stream, streamNumber, length); } } + + public static ObjectPayload getBroadcast(BitmessageAddress sendingAddress, Plaintext plaintext) { + if (sendingAddress.getVersion() < 4) { + return new V4Broadcast(sendingAddress, plaintext); + } else { + return new V5Broadcast(sendingAddress, plaintext); + } + } } diff --git a/domain/src/main/java/ch/dissem/bitmessage/utils/DebugUtils.java b/domain/src/main/java/ch/dissem/bitmessage/utils/DebugUtils.java new file mode 100644 index 0000000..e651980 --- /dev/null +++ b/domain/src/main/java/ch/dissem/bitmessage/utils/DebugUtils.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.utils; + +import ch.dissem.bitmessage.entity.ObjectMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +public class DebugUtils { + private final static Logger LOG = LoggerFactory.getLogger(DebugUtils.class); + + public static void saveToFile(ObjectMessage objectMessage) { + try { + File f = new File(System.getProperty("user.home") + "/jabit.error/" + objectMessage.getInventoryVector() + ".inv"); + f.createNewFile(); + objectMessage.write(new FileOutputStream(f)); + } catch (IOException e) { + LOG.debug(e.getMessage(), e); + } + } +} diff --git a/domain/src/test/java/ch/dissem/bitmessage/DecryptionTest.java b/domain/src/test/java/ch/dissem/bitmessage/DecryptionTest.java new file mode 100644 index 0000000..8fd6df1 --- /dev/null +++ b/domain/src/test/java/ch/dissem/bitmessage/DecryptionTest.java @@ -0,0 +1,47 @@ +/* + * Copyright 2015 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage; + +import ch.dissem.bitmessage.entity.BitmessageAddress; +import ch.dissem.bitmessage.entity.ObjectMessage; +import ch.dissem.bitmessage.entity.payload.V4Broadcast; +import ch.dissem.bitmessage.entity.payload.V5Broadcast; +import ch.dissem.bitmessage.exception.DecryptionFailedException; +import ch.dissem.bitmessage.utils.TestUtils; +import org.junit.Test; + +import java.io.IOException; + +import static org.junit.Assert.assertEquals; + +public class DecryptionTest { + @Test + public void ensureV4BroadcastIsDecryptedCorrectly() throws IOException, DecryptionFailedException { + ObjectMessage objectMessage = TestUtils.loadObjectMessage(5, "V4Broadcast.payload"); + V4Broadcast broadcast = (V4Broadcast) objectMessage.getPayload(); + broadcast.decrypt(new BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ")); + assertEquals("Test-Broadcast", broadcast.getPlaintext().getSubject()); + } + + @Test + public void ensureV5BroadcastIsDecryptedCorrectly() throws IOException, DecryptionFailedException { + ObjectMessage objectMessage = TestUtils.loadObjectMessage(5, "V5Broadcast.payload"); + V5Broadcast broadcast = (V5Broadcast) objectMessage.getPayload(); + broadcast.decrypt(new BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h")); + assertEquals("Test-Broadcast", broadcast.getPlaintext().getSubject()); + } +} diff --git a/domain/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java b/domain/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java index 44357c4..85b8098 100644 --- a/domain/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java +++ b/domain/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java @@ -84,7 +84,7 @@ public class BitmessageAddressTest { public void testV4PubkeyImport() throws IOException, DecryptionFailedException { BitmessageAddress address = new BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h"); ObjectMessage object = TestUtils.loadObjectMessage(4, "V4Pubkey.payload"); - object.decrypt(address.getPubkeyDecryptionKey()); + object.decrypt(address.getPublicDecryptionKey()); V4Pubkey pubkey = (V4Pubkey) object.getPayload(); assertTrue(object.isSignatureValid(pubkey)); address.setPubkey(pubkey); diff --git a/domain/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java b/domain/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java index 406a7d6..720c9bf 100644 --- a/domain/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java +++ b/domain/src/test/java/ch/dissem/bitmessage/entity/SerializationTest.java @@ -27,6 +27,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -78,7 +79,7 @@ public class SerializationTest { @Test public void ensurePlaintextIsSerializedAndDeserializedCorrectly() throws IOException, DecryptionFailedException { - Plaintext p1 = new Plaintext.Builder() + Plaintext p1 = new Plaintext.Builder(MSG) .from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8")) .to(TestUtils.loadContact()) .message("Subject", "Message") @@ -88,7 +89,7 @@ public class SerializationTest { ByteArrayOutputStream out = new ByteArrayOutputStream(); p1.write(out); ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); - Plaintext p2 = Plaintext.read(in); + Plaintext p2 = Plaintext.read(MSG, in); assertEquals(p1, p2); } diff --git a/domain/src/test/java/ch/dissem/bitmessage/utils/TestUtils.java b/domain/src/test/java/ch/dissem/bitmessage/utils/TestUtils.java index 73ac939..18ed654 100644 --- a/domain/src/test/java/ch/dissem/bitmessage/utils/TestUtils.java +++ b/domain/src/test/java/ch/dissem/bitmessage/utils/TestUtils.java @@ -72,7 +72,7 @@ public class TestUtils { public static BitmessageAddress loadContact() throws IOException, DecryptionFailedException { BitmessageAddress address = new BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h"); ObjectMessage object = TestUtils.loadObjectMessage(4, "V4Pubkey.payload"); - object.decrypt(address.getPubkeyDecryptionKey()); + object.decrypt(address.getPublicDecryptionKey()); address.setPubkey((V4Pubkey) object.getPayload()); return address; } diff --git a/domain/src/test/resources/V4Broadcast.payload b/domain/src/test/resources/V4Broadcast.payload index 2e42aa96dc03ba6d1cdc115af0af132543cca000..ac724acfdc467921edec53ff00cefed93560b34a 100644 GIT binary patch literal 396 zcmV;70dxKU00001W;%-i00010mv18g000970S+x1S73lK=VwSPoxLC3aRSN!AQsP7 z7LlwVbCFZOqt!H{)@PeRjxu)#KdRN<f5Q|Bl>i`sG5rNYfr>A}NNZNvccHadH1}JR zJ=}e08D7_Rg8~;eYx#Wm!HlPipep)wKMiYg_dt}cS<OU7#%MyTq)-^eo9c4qizBok zSLi58fu8%lF9y_ZenR#|Mxtw`!8*cZJ~Jz2{Z$LIC5&oQ2zpcx^ySS1`@76y?~Et5 zG@0Gx3(Ck#M+y1L@L&}#dc(R_KDDr(5bcaACk|)}X|iOjU4h|v_+6hcWHk7FZCS~i zL9@YO#-+>|G){v~x57Iz&8H_YM@LK1;{QBF+ZfflF&aS|sQTfI^NTmMM9t}BW-f^B z(p|T~lEyG66(!Ww+OeLu9dot)(tqE>$XoQI+LtNInOk*!TF7NOc~zNf1nK4*!k7B` qmKw7nV#A;~LqB~^`=|iw!nAOWp$?2bC(p}x*4Q~qh+kFywBWWDmb}IQ literal 1356 zcmV-S1+)4900000Rgw7s00010Kw1_6000970aD3G&TE&3*VlpIklIgcbOOo%Anl~R zJh&z2ICRf8FgUk&bx8s)MN&hM)R$UNvA<s9@c<ylBShaCCQUf5(Y{n)ya7*eao`d2 zStcSA^Qa6>-^sTa`X{&oPHeq6^x`+0lrC<LfvXp1&J#w|pr&roZUu#Z2uAr_0~|T@ z5sW>Yr=WZ_Avlb9ATz7H%3mpt)(~EWAu{)6a}K&3(Aza6x0;oYk64rGURn%wdSi^V zpll8Wu3}oJLognt6Rdo^-qyVcU~+ZgWB%!3m?@zqoII~l-1N89jJrALXQ}$#rH)ct z6N85&EZhW@NkD%FfYiRg9=oA)h8X+EK1@hSok%ZRCOa;Yx|@29LRBD(oZb|_Gxj@C zSTo=Mv;R8XsM;$bg>TFeGiSaP=PM(@922Et0Ih#En`JZSc>fgz%Md8gEWC7V7rf$S zH~$64FcZM!MpZRf;nxeKE9`j4;If!_>4=DC;;gBlAx5Oo*5-Sa5hC4azPWneqH)r? znOtUrgS23l`fVL$Wi}}>LGmEO4};NM?Cx&V-u!K=_%mD*bj8_`jCWR&8W^-nULyMx zm(&|6!IB{?ach}8f*V(rJ;luENxbSR)}0QQoJ9O@!%kEF4dTVzZ`pEP5&X`|WM8rZ z4Gj9~?dSKx8w3$}7*FaYpg*}S81xMaE9cLdAVQ+5#QMR_f^EQ1Nijn*Q|-@movHp6 zU7ayCWi4#y#S!+`H~+;MzW#Y(`lLIN<mWt*dNaXfuK)^dr~lF<Zu(=oyr8_Vun4hb zHpcN)3>kd(fbCjxeW2;RoDKU9enQr6G}7e&;{m_AB}?+b6?5<Ks~UJd!k2<~GL1q$ zXnl##d@mXH){XO#mp5SsZW<JYE8q%Axe9D^BzO$LW`a@eev}SbR+m4zuBQn8JEkD_ z<~gKf^6(I>%ZRNPAWS@={;q%)l$a_oAufL_s<hVQmGMcrKO{O{P(XRg8A|APgQ?i1 z07h;vRsK!ivj!Z)#xND3cfY$?{`jVO?C>s!KdVwJ(nW7z#H}GtY{0IJ$fL3OU3(1> zp8)$n@jG4S{3*q4$z!!(v-1+}{BH~0L<_e2vTvue6$A9KL>Di^FbhqUVhN*F5qq~9 zShnZzVS$Tu-5pgeb^Iz-&s1RqYTDIb!(b}6%X@PEy8xoB-ST7D%ik42Sie$<^1C0j ztc#n@o_YkoA}gPIOr84A1t8<AwB{^Oc>q?1+xFlheMv{hiP>j`S3XLlY6VNad~VBR z$5iSl72bKQvF2z)k=iv<6hq6Mlbp~u+%1tGpQoJi3$ycXDDiYQEy<}C2=$o3;I;nj zlvkvzdB9LdhZc!MzMaf)xU<?BZjg9*@+^a7xjl}P%$NZ+7UbVI;1NGJd0f#vsk8Vl z*=~Bkmu7qQ)2-jA5gu!${&4*7<TEHM9_<I#<p?PdQ}fZYH8L?ZevD6&(wK2f3e4n) zf2&8K5W#TvbHeV?UBpI^B8!7Q4CjU}bKqTuCIF#Od}yi~5LSc*2=@0jD1#u+J8ev& ze4JKE=X<4|v$^f}H-HH^r6HJid_T!z;cxP~lql+0+_Sg;Rv*JxZiI9Ven7>V)8D4c z)wsb8z%y8{dVw)rjpq7#H%YYT)fbK<Gka7nB4>k8hAqJ1brKz}(hN+Uu~?^f!nYv% z#F^S|zC`U%UR?Mj3iVD^)+8FU5}FM1VIQ?obkrL4{mQ`Dk0tyY2{0+5>Uq-h!m38$ O@6{o1@%61UbGrNm^`40U diff --git a/domain/src/test/resources/V5Broadcast.payload b/domain/src/test/resources/V5Broadcast.payload index 8d508501bcb226b60893b8df85a3fdb45aa59d06..87c8c047cf284e7fe9a9f47dbbfa49c3817bbd67 100644 GIT binary patch literal 428 zcmV;d0aN|}0000137ZE100010mbs?@000980Z5r#d*j`5Pg%2ba1!PSq98oPb(^t% z;KgzK_V$mQZr&z5B~$A7VIv`j|E{Z8{Q}AWAl#f{6t$GZv!n%i@Sw{$TJpn{a!1eo z=z%8?zJ0DxK>#4bE!e}!EtnzSQlhiRA31<nlkBEW1?ukc0RzVo$=aPeG(1uw%v>jD zOO*qpGJH6?%S3d~7}HaC-c+n%AQ()G1O?D561V<bK{~!B5i!ZkfqUi*Vl9;*=cLd} z^|!{04WE_}k3ToJ5lY33>0JBm9DO>u)Qr=g+aPQVh?!%*W*81aLJ~yN;T{l*>cnUb z*B%Nj&XS3pk4Wlw@7$u`pga`dcDrVb$+tBC{(ijJ-tx`97<96j=y~}-_O<S*lRVz% zAjtz?ufP&yHlsv6E+I-8Az7^{XZ6UxkV*nf&cG^KWaM5SJC9L!F6Keo$-PX4dkD+7 zMhuNhm;|z95HzQ80@ICtWnM)SlGWW){P>HR%gtS~T6Mu$Ih4{viO7qx3xfPsp%4y? WTaKw&M!dRV|MQaOU7}MPXm%~gpv-;% literal 668 zcmV;N0%QFE00002;2zTe00010Mm1sp000980X-GPNShv2W7BT(qtBYfH~lKR5(a?g z5F9#I$N<R(S(l)`Y`pHhVw9Fx<kW_YN&?CNAVk+~wWc!@qrMf-UHnGxVPyO-WjBwz zjsc{0a2R}d&j26;NEzn2ad*qhVKe#gW2s<!?*IUrbIN<t+6~?TV2os*wV`m~&Iz#x zeNYV!$WajRNlf?X#s5n=?G++?ljHK|P6)PC(kf5bW*l}~SSj-~G+g2Oe7aA?Wj35s zs#~p(FlEhL<4qU%e-D{SQs8#*Dea1sVhjm5js)#Vfm4m@NSy?~hZeXr&!Xe#gq9@! zqIvEhXN-{mPIY}^r&P!7379*(J@OL7rfPL_A4&l}-+`|nM~7RP`+(F$x8$hwTgy%7 z0KjV?p!sii$w-bM|5AAP?@u$tmIh%n$5v&EBqWE_h$v?{KvFo53%xpIDwA}=#x|qS zF^8uv%6yvb8JW|{wqLN>cy9RFJD(@*)u)5Umu+fHg>D*P-6*;uCKmW;7vt4Ge*`Ru zP|1xX(Gf||GS`0m2;$owUsz!1jrHrxbhg21sHgU{lyv?qQybVs%=BsGUGL!IuIj)( zPgjRiHGuMwFl3e?O|$QOXqKBjUIlW}8j53%spiMEFlN5^UJ%DEs>>6ysx(+gPwU$4 zm(Bz@Z$Sw2OX%{AvEi??6!%$MSRKAF_Zf{ny@Tsry6TK#FfX1-O~vg5Bo2cX&Ln^5 zWU7OHlK5X9dk#4q3Xycmkp81Lr$!w&^X|_KGn7D=dAtHF^J^{gl(<{k_C#5XQVn2R zU<a$PP8TsUNZ2kWu>Yufw5_n(r!V?*4qwWQ*{XLBILHy2cbdZ22l`Qw)MQeOePT`T CXhh!t diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d86a2b6..f2e98f0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java b/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java index 13dafc2..bbec1d9 100644 --- a/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java +++ b/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java @@ -24,6 +24,7 @@ import ch.dissem.bitmessage.entity.valueobject.NetworkAddress; import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException; import ch.dissem.bitmessage.factory.Factory; import ch.dissem.bitmessage.ports.NetworkHandler.MessageListener; +import ch.dissem.bitmessage.utils.DebugUtils; import ch.dissem.bitmessage.utils.Security; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -160,15 +161,10 @@ public class Connection implements Runnable { listener.receive(objectMessage); ctx.getInventory().storeObject(objectMessage); } catch (InsufficientProofOfWorkException e) { - try { - File f = new File(System.getProperty("user.home") + "/jabit.error/" + objectMessage.getInventoryVector() + ".inv"); - f.createNewFile(); - objectMessage.write(new FileOutputStream(f)); - } catch (IOException e1) { - e1.printStackTrace(); - } +// DebugUtils.saveToFile(objectMessage); } catch (IOException e) { LOG.error("Stream " + objectMessage.getStream() + ", object type " + objectMessage.getType() + ": " + e.getMessage(), e); + DebugUtils.saveToFile(objectMessage); } break; case ADDR: diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcAddressRepository.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcAddressRepository.java index 372f83b..eb0117d 100644 --- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcAddressRepository.java +++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcAddressRepository.java @@ -140,24 +140,27 @@ public class JdbcAddressRepository extends JdbcHelper implements AddressReposito } private void update(BitmessageAddress address) throws IOException, SQLException { - try(Connection connection = config.getConnection()){ - PreparedStatement ps = connection.prepareStatement( - "UPDATE Address SET alias=?, public_key=?, private_key=? WHERE address=?"); - ps.setString(1, address.getAlias()); - writePubkey(ps, 2, address.getPubkey()); - writeBlob(ps, 3, address.getPrivateKey()); - ps.setString(4, address.getAddress()); - ps.executeUpdate(); - }} + try (Connection connection = config.getConnection()) { + PreparedStatement ps = connection.prepareStatement( + "UPDATE Address SET alias=?, public_key=?, private_key=?, subscribed=? WHERE address=?"); + ps.setString(1, address.getAlias()); + writePubkey(ps, 2, address.getPubkey()); + writeBlob(ps, 3, address.getPrivateKey()); + ps.setBoolean(4, address.isSubscribed()); + ps.setString(5, address.getAddress()); + ps.executeUpdate(); + } + } private void insert(BitmessageAddress address) throws IOException, SQLException { try (Connection connection = config.getConnection()) { PreparedStatement ps = connection.prepareStatement( - "INSERT INTO Address (address, alias, public_key, private_key) VALUES (?, ?, ?, ?)"); + "INSERT INTO Address (address, alias, public_key, private_key, subscribed) VALUES (?, ?, ?, ?, ?)"); ps.setString(1, address.getAddress()); ps.setString(2, address.getAlias()); writePubkey(ps, 3, address.getPubkey()); writeBlob(ps, 4, address.getPrivateKey()); + ps.setBoolean(5, address.isSubscribed()); ps.executeUpdate(); } } diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java index 8d0ca58..278163f 100644 --- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java +++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcMessageRepository.java @@ -19,6 +19,7 @@ package ch.dissem.bitmessage.repository; import ch.dissem.bitmessage.InternalContext; import ch.dissem.bitmessage.entity.BitmessageAddress; import ch.dissem.bitmessage.entity.Plaintext; +import ch.dissem.bitmessage.entity.valueobject.InventoryVector; import ch.dissem.bitmessage.entity.valueobject.Label; import ch.dissem.bitmessage.ports.MessageRepository; import org.slf4j.Logger; @@ -102,12 +103,15 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito List<Plaintext> result = new LinkedList<>(); try (Connection connection = config.getConnection()) { Statement stmt = connection.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT id, sender, recipient, data, sent, received, status FROM Message WHERE " + where); + ResultSet rs = stmt.executeQuery("SELECT id, iv, type, sender, recipient, data, sent, received, status FROM Message WHERE " + where); while (rs.next()) { + byte[] iv = rs.getBytes("iv"); Blob data = rs.getBlob("data"); - Plaintext.Builder builder = Plaintext.readWithoutSignature(data.getBinaryStream()); + Plaintext.Type type = Plaintext.Type.valueOf(rs.getString("type")); + Plaintext.Builder builder = Plaintext.readWithoutSignature(type, data.getBinaryStream()); long id = rs.getLong("id"); builder.id(id); + builder.IV(new InventoryVector(iv)); builder.from(ctx.getAddressRepo().getAddress(rs.getString("sender"))); builder.to(ctx.getAddressRepo().getAddress(rs.getString("recipient"))); builder.sent(rs.getLong("sent")); @@ -155,14 +159,14 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito // save message if (message.getId() == null) { insert(connection, message); - - // remove existing labels - Statement stmt = connection.createStatement(); - stmt.executeUpdate("DELETE FROM Message_Label WHERE message_id=" + message.getId()); } else { update(connection, message); } + // remove existing labels + Statement stmt = connection.createStatement(); + stmt.executeUpdate("DELETE FROM Message_Label WHERE message_id=" + message.getId()); + // save labels PreparedStatement ps = connection.prepareStatement("INSERT INTO Message_Label VALUES (" + message.getId() + ", ?)"); for (Label label : message.getLabels()) { @@ -186,16 +190,15 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito private void insert(Connection connection, Plaintext message) throws SQLException, IOException { PreparedStatement ps = connection.prepareStatement( - "INSERT INTO Message (sender, recipient, data, sent, received, status) VALUES (?, ?, ?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS); - ps.setString(1, message.getFrom().getAddress()); - ps.setString(2, message.getTo().getAddress()); - writeBlob(ps, 3, message); - ps.setLong(4, message.getSent()); - ps.setLong(5, message.getReceived()); - if (message.getStatus() != null) - ps.setString(6, message.getStatus().name()); - else - ps.setString(6, null); + "INSERT INTO Message (iv, type, sender, recipient, data, sent, received, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS); + ps.setBytes(1, message.getInventoryVector() != null ? message.getInventoryVector().getHash() : null); + ps.setString(2, message.getType().name()); + ps.setString(3, message.getFrom().getAddress()); + ps.setString(4, message.getTo() != null ? message.getTo().getAddress() : null); + writeBlob(ps, 5, message); + ps.setLong(6, message.getSent()); + ps.setLong(7, message.getReceived()); + ps.setString(8, message.getStatus() != null ? message.getStatus().name() : null); ps.executeUpdate(); @@ -207,11 +210,12 @@ public class JdbcMessageRepository extends JdbcHelper implements MessageReposito private void update(Connection connection, Plaintext message) throws SQLException, IOException { PreparedStatement ps = connection.prepareStatement( - "UPDATE Message SET sent=?, received=?, status=? WHERE id=?"); - ps.setLong(1, message.getSent()); - ps.setLong(2, message.getReceived()); - ps.setString(3, message.getStatus() != null ? message.getStatus().name() : null); - ps.setLong(4, (Long) message.getId()); + "UPDATE Message SET iv=?, sent=?, received=?, status=? WHERE id=?"); + ps.setBytes(1, message.getInventoryVector() != null ? message.getInventoryVector().getHash() : null); + ps.setLong(2, message.getSent()); + ps.setLong(3, message.getReceived()); + ps.setString(4, message.getStatus() != null ? message.getStatus().name() : null); + ps.setLong(5, (Long) message.getId()); ps.executeUpdate(); } diff --git a/repositories/src/main/resources/db/migration/V1.3__Create_message_table.sql b/repositories/src/main/resources/db/migration/V1.3__Create_message_table.sql index 19a0185..efd0294 100644 --- a/repositories/src/main/resources/db/migration/V1.3__Create_message_table.sql +++ b/repositories/src/main/resources/db/migration/V1.3__Create_message_table.sql @@ -1,7 +1,9 @@ CREATE TABLE Message ( id BIGINT AUTO_INCREMENT PRIMARY KEY, + iv BINARY(32) UNIQUE, + type VARCHAR(20) NOT NULL, sender VARCHAR(40) NOT NULL, - recipient VARCHAR(40) NOT NULL, + recipient VARCHAR(40), data BLOB NOT NULL, sent BIGINT, received BIGINT, diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java index 41b0b4a..585c8e3 100644 --- a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java +++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java @@ -20,16 +20,19 @@ import ch.dissem.bitmessage.BitmessageContext; import ch.dissem.bitmessage.InternalContext; import ch.dissem.bitmessage.entity.BitmessageAddress; import ch.dissem.bitmessage.entity.Plaintext; +import ch.dissem.bitmessage.entity.valueobject.InventoryVector; import ch.dissem.bitmessage.entity.valueobject.Label; import ch.dissem.bitmessage.entity.valueobject.PrivateKey; import ch.dissem.bitmessage.ports.AddressRepository; import ch.dissem.bitmessage.ports.MessageRepository; +import ch.dissem.bitmessage.utils.Security; import org.junit.Before; import org.junit.Test; import java.util.Arrays; import java.util.List; +import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -116,7 +119,8 @@ public class JdbcMessageRepositoryTest { @Test public void testSave() throws Exception { - Plaintext message = new Plaintext.Builder() + Plaintext message = new Plaintext.Builder(MSG) + .IV(new InventoryVector(Security.randomBytes(32))) .from(identity) .to(contactA) .message("Subject", "Message") @@ -132,6 +136,19 @@ public class JdbcMessageRepositoryTest { List<Plaintext> messages = repo.findMessages(Plaintext.Status.DOING_PROOF_OF_WORK); assertEquals(1, messages.size()); + assertNotNull(messages.get(0).getInventoryVector()); + } + + @Test + public void testUpdate() throws Exception { + List<Plaintext> messages = repo.findMessages(Plaintext.Status.DRAFT, contactA); + Plaintext message = messages.get(0); + message.setInventoryVector(new InventoryVector(Security.randomBytes(32))); + repo.save(message); + + messages = repo.findMessages(Plaintext.Status.DRAFT, contactA); + assertEquals(1, messages.size()); + assertNotNull(messages.get(0).getInventoryVector()); } @Test @@ -143,7 +160,7 @@ public class JdbcMessageRepositoryTest { } private void addMessage(BitmessageAddress from, BitmessageAddress to, Plaintext.Status status, Label... labels) { - Plaintext message = new Plaintext.Builder() + Plaintext message = new Plaintext.Builder(MSG) .from(from) .to(to) .message("Subject", "Message")