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 2e42aa9..ac724ac 100644 Binary files a/domain/src/test/resources/V4Broadcast.payload and b/domain/src/test/resources/V4Broadcast.payload differ diff --git a/domain/src/test/resources/V5Broadcast.payload b/domain/src/test/resources/V5Broadcast.payload index 8d50850..87c8c04 100644 Binary files a/domain/src/test/resources/V5Broadcast.payload and b/domain/src/test/resources/V5Broadcast.payload differ 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")