A simple command line application (WIP), and a few tests. Unfotrunately, receiving messages doesn't seem to work yet.
This commit is contained in:
@ -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);
|
||||
}
|
||||
|
@ -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) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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];
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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++) {
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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.*;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user