A simple command line application (WIP), and a few tests. Unfotrunately, receiving messages doesn't seem to work yet.

This commit is contained in:
2015-05-22 20:51:57 +02:00
parent 648afbbc75
commit 6b3b361aa3
36 changed files with 757 additions and 298 deletions

View File

@ -17,16 +17,17 @@
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.Plaintext;
import ch.dissem.bitmessage.entity.Plaintext.Encoding;
import ch.dissem.bitmessage.entity.payload.GetPubkey;
import ch.dissem.bitmessage.entity.payload.Msg;
import ch.dissem.bitmessage.entity.payload.ObjectPayload;
import ch.dissem.bitmessage.entity.payload.ObjectType;
import ch.dissem.bitmessage.entity.payload.Pubkey.Feature;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.ports.*;
import ch.dissem.bitmessage.ports.NetworkHandler.MessageListener;
import ch.dissem.bitmessage.utils.Security;
import ch.dissem.bitmessage.utils.UnixTime;
import org.slf4j.Logger;
@ -34,10 +35,12 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.TreeSet;
import static ch.dissem.bitmessage.entity.Plaintext.Status.*;
import static ch.dissem.bitmessage.entity.payload.ObjectType.GET_PUBKEY;
import static ch.dissem.bitmessage.entity.payload.ObjectType.MSG;
import static ch.dissem.bitmessage.entity.payload.ObjectType.PUBKEY;
import static ch.dissem.bitmessage.utils.UnixTime.DAY;
/**
@ -53,8 +56,12 @@ public class BitmessageContext {
ctx = new InternalContext(builder);
}
public List<BitmessageAddress> getIdentities() {
return ctx.getAddressRepo().getIdentities();
public AddressRepository addresses() {
return ctx.getAddressRepo();
}
public MessageRepository messages() {
return ctx.getMessageRepository();
}
public BitmessageAddress createIdentity(boolean shorter, Feature... features) {
@ -66,6 +73,7 @@ public class BitmessageContext {
features
));
ctx.getAddressRepo().save(identity);
ctx.sendPubkey(identity, identity.getStream());
return identity;
}
@ -134,13 +142,23 @@ public class BitmessageContext {
}
public void startup(Listener listener) {
ctx.getNetworkHandler().start(new DefaultMessageListener(ctx, listener));
MessageListener messageListener = new DefaultMessageListener(ctx, listener);
for (ObjectMessage object : ctx.getInventory().getObjects(0, 0, PUBKEY, MSG)) {
messageListener.receive(object);
}
ctx.getNetworkHandler().start(messageListener);
}
public void shutdown() {
ctx.getNetworkHandler().stop();
}
public void addContact(BitmessageAddress contact) {
ctx.getAddressRepo().save(contact);
// TODO: search pubkey in inventory
ctx.requestPubkey(contact);
}
public interface Listener {
void receive(Plaintext plaintext);
}

View File

@ -17,13 +17,10 @@
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.Plaintext;
import ch.dissem.bitmessage.entity.payload.*;
import ch.dissem.bitmessage.ports.NetworkHandler;
import ch.dissem.bitmessage.utils.Security;
import ch.dissem.bitmessage.utils.UnixTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -47,6 +44,8 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
@Override
public void receive(ObjectMessage object) {
ObjectPayload payload = object.getPayload();
if (payload.getType() == null) return;
switch (payload.getType()) {
case GET_PUBKEY: {
receive(object, (GetPubkey) payload);
@ -70,28 +69,8 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
protected void receive(ObjectMessage object, GetPubkey getPubkey) {
BitmessageAddress identity = ctx.getAddressRepo().findIdentity(getPubkey.getRipeTag());
if (identity != null && identity.getPrivateKey() != null) {
try {
long expires = UnixTime.now(+28 * DAY);
LOG.info("Expires at " + expires);
ObjectMessage response = new ObjectMessage.Builder()
.stream(object.getStream())
.version(identity.getVersion())
.expiresTime(expires)
.payload(identity.getPubkey())
.build();
Security.doProofOfWork(response, ctx.getProofOfWorkEngine(),
ctx.getNetworkNonceTrialsPerByte(), ctx.getNetworkExtraBytes());
if (response.isSigned()) {
response.sign(identity.getPrivateKey());
}
if (response instanceof Encrypted) {
response.encrypt(Security.createPublicKey(identity.getPubkeyDecryptionKey()).getEncoded(false));
}
ctx.getInventory().storeObject(response);
ctx.getNetworkHandler().offer(response.getInventoryVector());
} catch (IOException e) {
throw new RuntimeException(e);
}
LOG.debug("Got pubkey request for identity " + identity);
ctx.sendPubkey(identity, object.getStream());
}
}
@ -109,8 +88,9 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
}
if (address != null) {
address.setPubkey(pubkey);
LOG.debug("Got pubkey for contact " + address);
List<Plaintext> messages = ctx.getMessageRepository().findMessages(Plaintext.Status.PUBKEY_REQUESTED, address);
for (Plaintext msg:messages){
for (Plaintext msg : messages) {
// TODO: send messages enqueued for this address
msg.setStatus(DOING_PROOF_OF_WORK);
ctx.getMessageRepository().save(msg);
@ -140,7 +120,7 @@ class DefaultMessageListener implements NetworkHandler.MessageListener {
ctx.getMessageRepository().save(msg.getPlaintext());
listener.receive(msg.getPlaintext());
break;
} catch (IOException ignore) {
} catch (IOException | RuntimeException ignore) {
}
}
}

View File

@ -19,6 +19,7 @@ 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.payload.GetPubkey;
import ch.dissem.bitmessage.entity.payload.ObjectPayload;
import ch.dissem.bitmessage.ports.*;
import ch.dissem.bitmessage.utils.Security;
@ -29,6 +30,8 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.TreeSet;
import static ch.dissem.bitmessage.utils.UnixTime.DAY;
/**
* The internal context should normally only be used for port implementations. If you need it in your client
* implementation, you're either doing something wrong, something very weird, or the BitmessageContext should
@ -160,6 +163,51 @@ public class InternalContext {
}
}
public void sendPubkey(BitmessageAddress identity, long targetStream) {
try {
long expires = UnixTime.now(+28 * DAY);
LOG.info("Expires at " + expires);
ObjectMessage response = new ObjectMessage.Builder()
.stream(targetStream)
.version(identity.getVersion())
.expiresTime(expires)
.payload(identity.getPubkey())
.build();
response.sign(identity.getPrivateKey());
response.encrypt(Security.createPublicKey(identity.getPubkeyDecryptionKey()).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));
}
inventory.storeObject(response);
networkHandler.offer(response.getInventoryVector());
// TODO: save that the pubkey was just sent, and on which stream!
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void requestPubkey(BitmessageAddress contact) {
try {
long expires = UnixTime.now(+2 * DAY);
LOG.info("Expires at " + expires);
ObjectMessage response = new ObjectMessage.Builder()
.stream(contact.getStream())
.version(contact.getVersion())
.expiresTime(expires)
.payload(new GetPubkey(contact))
.build();
Security.doProofOfWork(response, proofOfWorkEngine, networkNonceTrialsPerByte, networkExtraBytes);
inventory.storeObject(response);
networkHandler.offer(response.getInventoryVector());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public interface ContextHolder {
void setContext(InternalContext context);
}

View File

@ -25,6 +25,7 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Objects;
import static ch.dissem.bitmessage.utils.Decode.bytes;
import static ch.dissem.bitmessage.utils.Decode.varInt;
@ -180,4 +181,19 @@ public class BitmessageAddress {
public byte[] getTag() {
return tag;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BitmessageAddress address = (BitmessageAddress) o;
return Objects.equals(version, address.version) &&
Objects.equals(stream, address.stream) &&
Arrays.equals(ripe, address.ripe);
}
@Override
public int hashCode() {
return Arrays.hashCode(ripe);
}
}

View File

@ -60,15 +60,15 @@ public class NetworkMessage implements Streamable {
}
@Override
public void write(OutputStream stream) throws IOException {
public void write(OutputStream out) throws IOException {
// magic
Encode.int32(MAGIC, stream);
Encode.int32(MAGIC, out);
// ASCII string identifying the packet content, NULL padded (non-NULL padding results in packet rejected)
String command = payload.getCommand().name().toLowerCase();
stream.write(command.getBytes("ASCII"));
out.write(command.getBytes("ASCII"));
for (int i = command.length(); i < 12; i++) {
stream.write('\0');
out.write('\0');
}
ByteArrayOutputStream payloadStream = new ByteArrayOutputStream();
@ -78,16 +78,16 @@ public class NetworkMessage implements Streamable {
// Length of payload in number of bytes. Because of other restrictions, there is no reason why this length would
// ever be larger than 1600003 bytes. Some clients include a sanity-check to avoid processing messages which are
// larger than this.
Encode.int32(payloadBytes.length, stream);
Encode.int32(payloadBytes.length, out);
// checksum
try {
stream.write(getChecksum(payloadBytes));
out.write(getChecksum(payloadBytes));
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
// message payload
stream.write(payloadBytes);
out.write(payloadBytes);
}
}

View File

@ -22,10 +22,7 @@ import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.utils.Decode;
import ch.dissem.bitmessage.utils.Encode;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.io.*;
import java.util.*;
/**
@ -121,8 +118,8 @@ public class Plaintext implements Streamable {
Encode.varInt(from.getVersion(), out);
Encode.varInt(from.getStream(), out);
Encode.int32(from.getPubkey().getBehaviorBitfield(), out);
out.write(from.getPubkey().getSigningKey());
out.write(from.getPubkey().getEncryptionKey());
out.write(from.getPubkey().getSigningKey(), 1, 64);
out.write(from.getPubkey().getEncryptionKey(), 1, 64);
Encode.varInt(from.getPubkey().getNonceTrialsPerByte(), out);
Encode.varInt(from.getPubkey().getExtraBytes(), out);
out.write(to.getRipe());
@ -167,6 +164,30 @@ public class Plaintext implements Streamable {
this.status = status;
}
public String getSubject() {
Scanner s = new Scanner(new ByteArrayInputStream(message), "UTF-8");
String firstLine = s.nextLine();
if (encoding == 2) {
return firstLine.substring("Subject:".length()).trim();
} else if (firstLine.length() > 50) {
return firstLine.substring(0, 50).trim() + "...";
} else {
return firstLine;
}
}
public String getText() {
try {
String text = new String(message, "UTF-8");
if (encoding == 2) {
return text.substring(text.indexOf("\nBody:") + 6);
}
return text;
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
public enum Encoding {
IGNORE(0), TRIVIAL(1), SIMPLE(2);
@ -176,23 +197,21 @@ public class Plaintext implements Streamable {
this.code = code;
}
public static Encoding fromCode(long code) {
for (Encoding e : values()) {
if (e.getCode() == code) return e;
}
return null;
}
public long getCode() {
return code;
}
}
public enum Status {
// For sent messages
PUBKEY_REQUESTED,
DOING_PROOF_OF_WORK,
SENT,
ACKNOWLEDGED
SENT_ACKNOWLEDGED,
// For received messages
RECEIVED,
READ
}
public static final class Builder {
@ -340,7 +359,7 @@ public class Plaintext implements Streamable {
publicEncryptionKey,
nonceTrialsPerByte,
extraBytes,
Pubkey.Feature.features(behaviorBitfield)
behaviorBitfield
));
}
if (to == null) {
@ -349,4 +368,26 @@ public class Plaintext implements Streamable {
return new Plaintext(this);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Plaintext plaintext = (Plaintext) o;
return Objects.equals(encoding, plaintext.encoding) &&
Objects.equals(from, plaintext.from) &&
Arrays.equals(message, plaintext.message) &&
Arrays.equals(ack, plaintext.ack) &&
Arrays.equals(to.getRipe(), plaintext.to.getRipe()) &&
Arrays.equals(signature, plaintext.signature) &&
Objects.equals(status, plaintext.status) &&
Objects.equals(sent, plaintext.sent) &&
Objects.equals(received, plaintext.received) &&
Objects.equals(labels, plaintext.labels);
}
@Override
public int hashCode() {
return Objects.hash(from, encoding, message, ack, to, signature, status, sent, received, labels);
}
}

View File

@ -28,16 +28,17 @@ import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.math.ec.ECPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.math.BigInteger;
import java.util.Arrays;
/**
* Created by chris on 09.04.15.
*/
public class CryptoBox implements Streamable {
private static final Logger LOG = LoggerFactory.getLogger(CryptoBox.class);
private final byte[] initializationVector;
private final int curveType;
private final ECPoint R;
@ -182,7 +183,7 @@ public class CryptoBox implements Streamable {
}
public Builder curveType(int curveType) {
if (curveType != 0x2CA) System.out.println("Unexpected curve type " + curveType);
if (curveType != 0x2CA) LOG.debug("Unexpected curve type " + curveType);
this.curveType = curveType;
return this;
}

View File

@ -16,6 +16,8 @@
package ch.dissem.bitmessage.entity.payload;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import static ch.dissem.bitmessage.utils.Security.ripemd160;
@ -51,6 +53,10 @@ public abstract class Pubkey extends ObjectPayload {
return 0;
}
public void writeUnencrypted(OutputStream out) throws IOException {
write(out);
}
protected byte[] add0x04(byte[] key) {
if (key.length == 65) return key;
byte[] result = new byte[65];

View File

@ -22,6 +22,8 @@ import ch.dissem.bitmessage.utils.Encode;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Objects;
/**
* A version 3 public key.
@ -100,7 +102,7 @@ public class V3Pubkey extends V2Pubkey {
private byte[] publicEncryptionKey;
private long nonceTrialsPerByte;
private long extraBytes;
private byte[] signature;
private byte[] signature = new byte[0];
public Builder() {
}
@ -144,4 +146,22 @@ public class V3Pubkey extends V2Pubkey {
return new V3Pubkey(this);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
V3Pubkey pubkey = (V3Pubkey) o;
return Objects.equals(nonceTrialsPerByte, pubkey.nonceTrialsPerByte) &&
Objects.equals(extraBytes, pubkey.extraBytes) &&
stream == pubkey.stream &&
behaviorBitfield == pubkey.behaviorBitfield &&
Arrays.equals(publicSigningKey, pubkey.publicSigningKey) &&
Arrays.equals(publicEncryptionKey, pubkey.publicEncryptionKey);
}
@Override
public int hashCode() {
return Objects.hash(nonceTrialsPerByte, extraBytes);
}
}

View File

@ -23,6 +23,7 @@ import ch.dissem.bitmessage.utils.Decode;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
/**
* A version 4 public key. When version 4 pubkeys are created, most of the data in the pubkey is encrypted. This is
@ -43,15 +44,18 @@ public class V4Pubkey extends Pubkey implements Encrypted {
}
public V4Pubkey(V3Pubkey decrypted) {
this.decrypted = decrypted;
this.stream = decrypted.stream;
this.tag = BitmessageAddress.calculateTag(4, decrypted.getStream(), decrypted.getRipe());
this.decrypted = decrypted;
}
public static V4Pubkey read(InputStream in, long stream, int length) throws IOException {
return new V4Pubkey(stream,
Decode.bytes(in, 32),
CryptoBox.read(in, length - 32));
public static V4Pubkey read(InputStream in, long stream, int length, boolean encrypted) throws IOException {
if (encrypted)
return new V4Pubkey(stream,
Decode.bytes(in, 32),
CryptoBox.read(in, length - 32));
else
return new V4Pubkey(V3Pubkey.read(in, stream));
}
@Override
@ -76,6 +80,11 @@ public class V4Pubkey extends Pubkey implements Encrypted {
encrypted.write(stream);
}
@Override
public void writeUnencrypted(OutputStream out) throws IOException {
decrypted.write(out);
}
@Override
public void writeBytesToSign(OutputStream out) throws IOException {
out.write(tag);
@ -141,4 +150,25 @@ public class V4Pubkey extends Pubkey implements Encrypted {
public long getExtraBytes() {
return decrypted.getExtraBytes();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
V4Pubkey v4Pubkey = (V4Pubkey) o;
if (stream != v4Pubkey.stream) return false;
if (!Arrays.equals(tag, v4Pubkey.tag)) return false;
return !(decrypted != null ? !decrypted.equals(v4Pubkey.decrypted) : v4Pubkey.decrypted != null);
}
@Override
public int hashCode() {
int result = (int) (stream ^ (stream >>> 32));
result = 31 * result + Arrays.hashCode(tag);
result = 31 * result + (decrypted != null ? decrypted.hashCode() : 0);
return result;
}
}

View File

@ -18,6 +18,8 @@ package ch.dissem.bitmessage.entity.valueobject;
import ch.dissem.bitmessage.entity.Streamable;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.payload.V3Pubkey;
import ch.dissem.bitmessage.entity.payload.V4Pubkey;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.utils.Bytes;
import ch.dissem.bitmessage.utils.Decode;
@ -76,7 +78,7 @@ public class PrivateKey implements Streamable {
int version = (int) Decode.varInt(is);
long stream = Decode.varInt(is);
int len = (int) Decode.varInt(is);
Pubkey pubkey = Factory.readPubkey(version, stream, is, len);
Pubkey pubkey = Factory.readPubkey(version, stream, is, len, false);
len = (int) Decode.varInt(is);
byte[] signingKey = Decode.bytes(is, len);
len = (int) Decode.varInt(is);
@ -97,16 +99,16 @@ public class PrivateKey implements Streamable {
}
@Override
public void write(OutputStream os) throws IOException {
Encode.varInt(pubkey.getVersion(), os);
Encode.varInt(pubkey.getStream(), os);
public void write(OutputStream out) throws IOException {
Encode.varInt(pubkey.getVersion(), out);
Encode.varInt(pubkey.getStream(), out);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
pubkey.write(baos);
Encode.varInt(baos.size(), os);
os.write(baos.toByteArray());
Encode.varInt(privateSigningKey.length, os);
os.write(privateSigningKey);
Encode.varInt(privateEncryptionKey.length, os);
os.write(privateEncryptionKey);
pubkey.writeUnencrypted(baos);
Encode.varInt(baos.size(), out);
out.write(baos.toByteArray());
Encode.varInt(privateSigningKey.length, out);
out.write(privateSigningKey);
Encode.varInt(privateEncryptionKey.length, out);
out.write(privateEncryptionKey);
}
}

View File

@ -56,6 +56,12 @@ public class Factory {
public static Pubkey createPubkey(long version, long stream, byte[] publicSigningKey, byte[] publicEncryptionKey,
long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) {
return createPubkey(version, stream, publicSigningKey, publicEncryptionKey, nonceTrialsPerByte, extraBytes,
Pubkey.Feature.bitfield(features));
}
public static Pubkey createPubkey(long version, long stream, byte[] publicSigningKey, byte[] publicEncryptionKey,
long nonceTrialsPerByte, long extraBytes, int behaviourBitfield) {
if (publicSigningKey.length != 64 && publicSigningKey.length != 65)
throw new IllegalArgumentException("64 bytes signing key expected, but it was "
+ publicSigningKey.length + " bytes long.");
@ -69,14 +75,14 @@ public class Factory {
.stream(stream)
.publicSigningKey(publicSigningKey)
.publicEncryptionKey(publicEncryptionKey)
.behaviorBitfield(Pubkey.Feature.bitfield(features))
.behaviorBitfield(behaviourBitfield)
.build();
case 3:
return new V3Pubkey.Builder()
.stream(stream)
.publicSigningKey(publicSigningKey)
.publicEncryptionKey(publicEncryptionKey)
.behaviorBitfield(Pubkey.Feature.bitfield(features))
.behaviorBitfield(behaviourBitfield)
.nonceTrialsPerByte(nonceTrialsPerByte)
.extraBytes(extraBytes)
.build();
@ -86,7 +92,7 @@ public class Factory {
.stream(stream)
.publicSigningKey(publicSigningKey)
.publicEncryptionKey(publicEncryptionKey)
.behaviorBitfield(Pubkey.Feature.bitfield(features))
.behaviorBitfield(behaviourBitfield)
.nonceTrialsPerByte(nonceTrialsPerByte)
.extraBytes(extraBytes)
.build()
@ -125,21 +131,21 @@ public class Factory {
return GetPubkey.read(stream, streamNumber, length, version);
}
public static Pubkey readPubkey(long version, long stream, InputStream is, int length) throws IOException {
public static Pubkey readPubkey(long version, long stream, InputStream is, int length, boolean encrypted) throws IOException {
switch ((int) version) {
case 2:
return V2Pubkey.read(is, stream);
case 3:
return V3Pubkey.read(is, stream);
case 4:
return V4Pubkey.read(is, stream, length);
return V4Pubkey.read(is, stream, length, encrypted);
}
LOG.debug("Unexpected pubkey version " + version + ", handling as generic payload object");
return null;
}
private static ObjectPayload parsePubkey(long version, long streamNumber, InputStream stream, int length) throws IOException {
Pubkey pubkey = readPubkey(version, streamNumber, stream, length);
Pubkey pubkey = readPubkey(version, streamNumber, stream, length, true);
return pubkey != null ? pubkey : GenericPayload.read(stream, streamNumber, length);
}

View File

@ -32,7 +32,7 @@ public interface Inventory {
ObjectMessage getObject(InventoryVector vector);
List<ObjectMessage> getObjects(long stream, long version, ObjectType type);
List<ObjectMessage> getObjects(long stream, long version, ObjectType... types);
void storeObject(ObjectMessage object);

View File

@ -17,7 +17,7 @@
package ch.dissem.bitmessage.utils;
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.payload.*;
import ch.dissem.bitmessage.factory.Factory;
import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
import org.bouncycastle.asn1.x9.X9ECParameters;
@ -35,6 +35,7 @@ import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.math.BigInteger;
import java.security.*;
import java.security.PrivateKey;
import java.security.spec.KeySpec;
import java.util.Arrays;

View File

@ -16,6 +16,8 @@
package ch.dissem.bitmessage.utils;
import ch.dissem.bitmessage.entity.payload.ObjectType;
/**
* Created by chris on 13.04.15.
*/
@ -38,6 +40,15 @@ public class Strings {
return streamList;
}
public static StringBuilder join(ObjectType... types) {
StringBuilder streamList = new StringBuilder();
for (int i = 0; i < types.length; i++) {
if (i > 0) streamList.append(", ");
streamList.append(types[i].getNumber());
}
return streamList;
}
public static StringBuilder join(Object... objects) {
StringBuilder streamList = new StringBuilder();
for (int i = 0; i < objects.length; i++) {

View File

@ -16,21 +16,23 @@
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.payload.CryptoBox;
import ch.dissem.bitmessage.entity.payload.GenericPayload;
import ch.dissem.bitmessage.entity.payload.Msg;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.utils.Security;
import org.junit.Ignore;
import ch.dissem.bitmessage.utils.TestUtils;
import org.junit.Test;
import java.io.IOException;
import java.security.KeyPair;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
/**
* Created by chris on 10.05.15.
*/
public class EncryptionTest {
@Test
public void ensureDecryptedDataIsSameAsBeforeEncryption() throws IOException {
@ -43,4 +45,19 @@ public class EncryptionTest {
assertEquals(before, after);
}
@Test
public void ensureMessageCanBeDecrypted() throws IOException {
PrivateKey privateKey = PrivateKey.read(TestUtils.getResource("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey"));
BitmessageAddress identity = new BitmessageAddress(privateKey);
assertEquals("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8", identity.getAddress());
ObjectMessage object = TestUtils.loadObjectMessage(3, "V1Msg.payload");
Msg msg = (Msg) object.getPayload();
msg.decrypt(privateKey.getPrivateEncryptionKey());
Plaintext plaintext = msg.getPlaintext();
assertNotNull(plaintext);
assertEquals("Test", plaintext.getSubject());
assertEquals("Hallo, das ist ein Test von der v4-Adresse", plaintext.getText());
}
}

View File

@ -18,15 +18,19 @@ 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.payload.Msg;
import ch.dissem.bitmessage.entity.payload.ObjectType;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.payload.V4Pubkey;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.utils.TestUtils;
import org.junit.Test;
import java.io.IOException;
import java.util.Date;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
public class SignatureTest {
@Test
@ -39,7 +43,6 @@ public class SignatureTest {
@Test
public void ensureSigningWorks() throws IOException {
PrivateKey privateKey = new PrivateKey(false, 1, 1000, 1000);
BitmessageAddress address = new BitmessageAddress(privateKey);
ObjectMessage objectMessage = new ObjectMessage.Builder()
.objectType(ObjectType.PUBKEY)
@ -51,4 +54,25 @@ public class SignatureTest {
assertTrue(objectMessage.isSignatureValid(privateKey.getPubkey()));
}
@Test
public void ensureMessageIsProperlySigned() throws IOException {
BitmessageAddress identity = TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8");
ObjectMessage object = TestUtils.loadObjectMessage(3, "V1Msg.payload");
Msg msg = (Msg) object.getPayload();
msg.decrypt(identity.getPrivateKey().getPrivateEncryptionKey());
Plaintext plaintext = msg.getPlaintext();
assertEquals(0, object.getExpiresTime());
assertEquals(loadPubkey(), plaintext.getFrom().getPubkey());
assertNotNull(plaintext);
assertTrue(object.isSignatureValid(plaintext.getFrom().getPubkey()));
}
private V4Pubkey loadPubkey() throws IOException {
BitmessageAddress address = new BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h");
ObjectMessage object = TestUtils.loadObjectMessage(4, "V4Pubkey.payload");
object.decrypt(address.getPubkeyDecryptionKey());
return (V4Pubkey) object.getPayload();
}
}

View File

@ -17,7 +17,6 @@
package ch.dissem.bitmessage.entity;
import ch.dissem.bitmessage.entity.payload.Pubkey;
import ch.dissem.bitmessage.entity.payload.V3Pubkey;
import ch.dissem.bitmessage.entity.payload.V4Pubkey;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.utils.*;

View File

@ -75,6 +75,23 @@ public class SerializationTest {
doTest("V1MsgStrangeData.payload", 1, GenericPayload.class);
}
@Test
public void ensurePlaintextIsSerializedAndDeserializedCorrectly() throws IOException {
Plaintext p1 = new Plaintext.Builder()
.from(TestUtils.loadIdentity("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8"))
.to(TestUtils.loadContact())
.encoding(Plaintext.Encoding.SIMPLE)
.message("Subject", "Message")
.ack("ack".getBytes())
.signature(new byte[0])
.build();
ByteArrayOutputStream out = new ByteArrayOutputStream();
p1.write(out);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
Plaintext p2 = Plaintext.read(in);
assertEquals(p1, p2);
}
private void doTest(String resourceName, int version, Class<?> expectedPayloadType) throws IOException {
byte[] data = TestUtils.getBytes(resourceName);
InputStream in = new ByteArrayInputStream(data);

View File

@ -25,9 +25,8 @@ import javax.xml.bind.DatatypeConverter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.KeyPairGenerator;
import java.util.Calendar;
import java.util.GregorianCalendar;
import static ch.dissem.bitmessage.utils.UnixTime.DAY;
import static org.junit.Assert.assertArrayEquals;
/**
@ -72,7 +71,8 @@ public class SecurityTest {
public void testProofOfWorkFails() throws IOException {
ObjectMessage objectMessage = new ObjectMessage.Builder()
.nonce(new byte[8])
.expiresTime(UnixTime.now() + 300) // 5 minutes
.expiresTime(UnixTime.now(+2 * DAY)) // 5 minutes
.objectType(0)
.payload(GenericPayload.read(new ByteArrayInputStream(new byte[0]), 1, 0))
.build();
Security.checkProofOfWork(objectMessage, 1000, 1000);
@ -80,11 +80,10 @@ public class SecurityTest {
@Test
public void testDoProofOfWork() throws IOException {
Calendar expires = new GregorianCalendar();
expires.add(1, Calendar.HOUR);
ObjectMessage objectMessage = new ObjectMessage.Builder()
.nonce(new byte[8])
.expiresTime(expires.getTimeInMillis() / 1000)
.expiresTime(UnixTime.now(+2 * DAY))
.objectType(0)
.payload(GenericPayload.read(new ByteArrayInputStream(new byte[0]), 1, 0))
.build();
Security.doProofOfWork(objectMessage, new MultiThreadedPOWEngine(), 1000, 1000);

View File

@ -16,7 +16,10 @@
package ch.dissem.bitmessage.utils;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.ObjectMessage;
import ch.dissem.bitmessage.entity.payload.V4Pubkey;
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
import ch.dissem.bitmessage.factory.Factory;
import java.io.ByteArrayInputStream;
@ -24,6 +27,8 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import static org.junit.Assert.assertEquals;
/**
* If there's ever a need for this in production code, it should be rewritten to be more efficient.
*/
@ -51,4 +56,23 @@ public class TestUtils {
}
return out.toByteArray();
}
public static InputStream getResource(String resourceName) {
return TestUtils.class.getClassLoader().getResourceAsStream(resourceName);
}
public static BitmessageAddress loadIdentity(String address) throws IOException {
PrivateKey privateKey = PrivateKey.read(TestUtils.getResource(address + ".privkey"));
BitmessageAddress identity = new BitmessageAddress(privateKey);
assertEquals(address, identity.getAddress());
return identity;
}
public static BitmessageAddress loadContact() throws IOException {
BitmessageAddress address = new BitmessageAddress("BM-2cXxfcSetKnbHJX2Y85rSkaVpsdNUZ5q9h");
ObjectMessage object = TestUtils.loadObjectMessage(4, "V4Pubkey.payload");
object.decrypt(address.getPubkeyDecryptionKey());
address.setPubkey((V4Pubkey) object.getPayload());
return address;
}
}