Moving "Security" to a separate port, so there can be a Bouncycastle and a Spongycastle implementation. (BC doesn't work on Android, SC can't be used on Oracle's JVM)
This commit is contained in:
parent
6542bd1451
commit
b8546e28af
@ -20,7 +20,7 @@ import ch.dissem.bitmessage.BitmessageContext;
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.entity.Plaintext;
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
||||
import ch.dissem.bitmessage.networking.NetworkNode;
|
||||
import ch.dissem.bitmessage.networking.DefaultNetworkHandler;
|
||||
import ch.dissem.bitmessage.repository.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -45,7 +45,7 @@ public class Application {
|
||||
.inventory(new JdbcInventory(jdbcConfig))
|
||||
.nodeRegistry(new MemoryNodeRegistry())
|
||||
.messageRepo(new JdbcMessageRepository(jdbcConfig))
|
||||
.networkHandler(new NetworkNode())
|
||||
.networkHandler(new DefaultNetworkHandler())
|
||||
.port(48444)
|
||||
.build();
|
||||
|
||||
|
@ -17,7 +17,7 @@
|
||||
package ch.dissem.bitmessage.demo;
|
||||
|
||||
import ch.dissem.bitmessage.BitmessageContext;
|
||||
import ch.dissem.bitmessage.networking.NetworkNode;
|
||||
import ch.dissem.bitmessage.networking.DefaultNetworkHandler;
|
||||
import ch.dissem.bitmessage.repository.*;
|
||||
import ch.dissem.bitmessage.wif.WifExporter;
|
||||
import ch.dissem.bitmessage.wif.WifImporter;
|
||||
@ -49,7 +49,7 @@ public class Main {
|
||||
.inventory(new JdbcInventory(jdbcConfig))
|
||||
.nodeRegistry(new MemoryNodeRegistry())
|
||||
.messageRepo(new JdbcMessageRepository(jdbcConfig))
|
||||
.networkHandler(new NetworkNode())
|
||||
.networkHandler(new DefaultNetworkHandler())
|
||||
.port(48444)
|
||||
.build();
|
||||
|
||||
|
@ -12,6 +12,7 @@ uploadArchives {
|
||||
|
||||
dependencies {
|
||||
compile 'org.slf4j:slf4j-api:1.7.12'
|
||||
compile 'org.bouncycastle:bcprov-jdk15on:1.52'
|
||||
testCompile group: 'junit', name: 'junit', version: '4.11'
|
||||
}
|
||||
testCompile 'junit:junit:4.11'
|
||||
testCompile 'org.mockito:mockito-core:1.10.19'
|
||||
testCompile project(':security-bc')
|
||||
}
|
||||
|
@ -27,12 +27,13 @@ import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
||||
import ch.dissem.bitmessage.factory.Factory;
|
||||
import ch.dissem.bitmessage.ports.*;
|
||||
import ch.dissem.bitmessage.utils.Property;
|
||||
import ch.dissem.bitmessage.utils.Security;
|
||||
import ch.dissem.bitmessage.utils.UnixTime;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import static ch.dissem.bitmessage.entity.Plaintext.Status.*;
|
||||
import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST;
|
||||
@ -57,12 +58,17 @@ public class BitmessageContext {
|
||||
public static final int CURRENT_VERSION = 3;
|
||||
private final static Logger LOG = LoggerFactory.getLogger(BitmessageContext.class);
|
||||
|
||||
private final ExecutorService pool;
|
||||
|
||||
private final InternalContext ctx;
|
||||
|
||||
private Listener listener;
|
||||
|
||||
private BitmessageContext(Builder builder) {
|
||||
ctx = new InternalContext(builder);
|
||||
// As this thread is used for parts that do POW, which itself uses parallel threads, only
|
||||
// one should be executed at any time.
|
||||
pool = Executors.newFixedThreadPool(1);
|
||||
}
|
||||
|
||||
public AddressRepository addresses() {
|
||||
@ -74,7 +80,7 @@ public class BitmessageContext {
|
||||
}
|
||||
|
||||
public BitmessageAddress createIdentity(boolean shorter, Feature... features) {
|
||||
BitmessageAddress identity = new BitmessageAddress(new PrivateKey(
|
||||
final BitmessageAddress identity = new BitmessageAddress(new PrivateKey(
|
||||
shorter,
|
||||
ctx.getStreams()[0],
|
||||
ctx.getNetworkNonceTrialsPerByte(),
|
||||
@ -82,8 +88,12 @@ public class BitmessageContext {
|
||||
features
|
||||
));
|
||||
ctx.getAddressRepo().save(identity);
|
||||
// TODO: this should happen in a separate thread
|
||||
ctx.sendPubkey(identity, identity.getStream());
|
||||
pool.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ctx.sendPubkey(identity, identity.getStream());
|
||||
}
|
||||
});
|
||||
return identity;
|
||||
}
|
||||
|
||||
@ -91,63 +101,71 @@ 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();
|
||||
public void broadcast(final BitmessageAddress from, final String subject, final String message) {
|
||||
pool.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
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);
|
||||
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.BROADCAST, Label.Type.SENT));
|
||||
ctx.getMessageRepository().save(msg);
|
||||
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);
|
||||
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.BROADCAST, Label.Type.SENT));
|
||||
ctx.getMessageRepository().save(msg);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void send(BitmessageAddress from, BitmessageAddress to, String subject, String message) {
|
||||
public void send(final BitmessageAddress from, final BitmessageAddress to, final String subject, final 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(MSG)
|
||||
.from(from)
|
||||
.to(to)
|
||||
.message(subject, message)
|
||||
.build();
|
||||
if (to.getPubkey() == null) {
|
||||
tryToFindMatchingPubkey(to);
|
||||
}
|
||||
if (to.getPubkey() == null) {
|
||||
LOG.info("Public key is missing from recipient. Requesting.");
|
||||
requestPubkey(from, to);
|
||||
msg.setStatus(PUBKEY_REQUESTED);
|
||||
ctx.getMessageRepository().save(msg);
|
||||
} else {
|
||||
LOG.info("Sending message.");
|
||||
msg.setStatus(DOING_PROOF_OF_WORK);
|
||||
ctx.getMessageRepository().save(msg);
|
||||
ctx.send(
|
||||
from,
|
||||
to,
|
||||
new Msg(msg),
|
||||
+2 * DAY,
|
||||
ctx.getNonceTrialsPerByte(to),
|
||||
ctx.getExtraBytes(to)
|
||||
);
|
||||
msg.setStatus(SENT);
|
||||
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.SENT));
|
||||
ctx.getMessageRepository().save(msg);
|
||||
}
|
||||
pool.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Plaintext msg = new Plaintext.Builder(MSG)
|
||||
.from(from)
|
||||
.to(to)
|
||||
.message(subject, message)
|
||||
.build();
|
||||
if (to.getPubkey() == null) {
|
||||
tryToFindMatchingPubkey(to);
|
||||
}
|
||||
if (to.getPubkey() == null) {
|
||||
LOG.info("Public key is missing from recipient. Requesting.");
|
||||
requestPubkey(from, to);
|
||||
msg.setStatus(PUBKEY_REQUESTED);
|
||||
ctx.getMessageRepository().save(msg);
|
||||
} else {
|
||||
LOG.info("Sending message.");
|
||||
msg.setStatus(DOING_PROOF_OF_WORK);
|
||||
ctx.getMessageRepository().save(msg);
|
||||
ctx.send(
|
||||
from,
|
||||
to,
|
||||
new Msg(msg),
|
||||
+2 * DAY,
|
||||
ctx.getNonceTrialsPerByte(to),
|
||||
ctx.getExtraBytes(to)
|
||||
);
|
||||
msg.setStatus(SENT);
|
||||
msg.addLabels(ctx.getMessageRepository().getLabels(Label.Type.SENT));
|
||||
ctx.getMessageRepository().save(msg);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void requestPubkey(BitmessageAddress requestingIdentity, BitmessageAddress address) {
|
||||
@ -169,8 +187,7 @@ public class BitmessageContext {
|
||||
.expiresTime(expires)
|
||||
.payload(payload)
|
||||
.build();
|
||||
Security.doProofOfWork(object, ctx.getProofOfWorkEngine(),
|
||||
ctx.getNetworkNonceTrialsPerByte(), ctx.getNetworkExtraBytes());
|
||||
ctx.getSecurity().doProofOfWork(object, ctx.getNetworkNonceTrialsPerByte(), ctx.getNetworkExtraBytes());
|
||||
ctx.getInventory().storeObject(object);
|
||||
ctx.getNetworkHandler().offer(object.getInventoryVector());
|
||||
}
|
||||
@ -184,6 +201,10 @@ public class BitmessageContext {
|
||||
ctx.getNetworkHandler().stop();
|
||||
}
|
||||
|
||||
public boolean isRunning() {
|
||||
return ctx.getNetworkHandler().isRunning();
|
||||
}
|
||||
|
||||
public void addContact(BitmessageAddress contact) {
|
||||
ctx.getAddressRepo().save(contact);
|
||||
tryToFindMatchingPubkey(contact);
|
||||
@ -258,6 +279,7 @@ public class BitmessageContext {
|
||||
AddressRepository addressRepo;
|
||||
MessageRepository messageRepo;
|
||||
ProofOfWorkEngine proofOfWorkEngine;
|
||||
Security security;
|
||||
|
||||
public Builder() {
|
||||
}
|
||||
@ -292,6 +314,11 @@ public class BitmessageContext {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder security(Security security) {
|
||||
this.security = security;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder proofOfWorkEngine(ProofOfWorkEngine proofOfWorkEngine) {
|
||||
this.proofOfWorkEngine = proofOfWorkEngine;
|
||||
return this;
|
||||
|
@ -21,7 +21,7 @@ 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.*;
|
||||
import ch.dissem.bitmessage.utils.Security;
|
||||
import ch.dissem.bitmessage.utils.Singleton;
|
||||
import ch.dissem.bitmessage.utils.UnixTime;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -42,6 +42,7 @@ import static ch.dissem.bitmessage.utils.UnixTime.DAY;
|
||||
public class InternalContext {
|
||||
private final static Logger LOG = LoggerFactory.getLogger(InternalContext.class);
|
||||
|
||||
private final Security security;
|
||||
private final Inventory inventory;
|
||||
private final NodeRegistry nodeRegistry;
|
||||
private final NetworkHandler networkHandler;
|
||||
@ -56,16 +57,29 @@ public class InternalContext {
|
||||
private long clientNonce;
|
||||
|
||||
public InternalContext(BitmessageContext.Builder builder) {
|
||||
this.security = builder.security;
|
||||
this.inventory = builder.inventory;
|
||||
this.nodeRegistry = builder.nodeRegistry;
|
||||
this.networkHandler = builder.networkHandler;
|
||||
this.addressRepository = builder.addressRepo;
|
||||
this.messageRepository = builder.messageRepo;
|
||||
this.proofOfWorkEngine = builder.proofOfWorkEngine;
|
||||
this.clientNonce = Security.randomNonce();
|
||||
this.clientNonce = security.randomNonce();
|
||||
|
||||
Singleton.initialize(security);
|
||||
|
||||
port = builder.port;
|
||||
streams.add(1L); // FIXME
|
||||
|
||||
// TODO: streams of new identities and subscriptions should also be added. This works only after a restart.
|
||||
for (BitmessageAddress address : addressRepository.getIdentities()) {
|
||||
streams.add(address.getStream());
|
||||
}
|
||||
for (BitmessageAddress address : addressRepository.getSubscriptions()) {
|
||||
streams.add(address.getStream());
|
||||
}
|
||||
if (streams.isEmpty()) {
|
||||
streams.add(1L);
|
||||
}
|
||||
|
||||
init(inventory, nodeRegistry, networkHandler, addressRepository, messageRepository, proofOfWorkEngine);
|
||||
}
|
||||
@ -78,6 +92,10 @@ public class InternalContext {
|
||||
}
|
||||
}
|
||||
|
||||
public Security getSecurity() {
|
||||
return security;
|
||||
}
|
||||
|
||||
public Inventory getInventory() {
|
||||
return inventory;
|
||||
}
|
||||
@ -111,14 +129,6 @@ public class InternalContext {
|
||||
return result;
|
||||
}
|
||||
|
||||
public void addStream(long stream) {
|
||||
streams.add(stream);
|
||||
}
|
||||
|
||||
public void removeStream(long stream) {
|
||||
streams.remove(stream);
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
@ -152,14 +162,14 @@ public class InternalContext {
|
||||
.payload(payload)
|
||||
.build();
|
||||
if (object.isSigned()) {
|
||||
object.sign(from.getPrivateKey());
|
||||
object.sign( from.getPrivateKey());
|
||||
}
|
||||
if (payload instanceof Broadcast) {
|
||||
((Broadcast) payload).encrypt();
|
||||
} else if (payload instanceof Encrypted) {
|
||||
object.encrypt(to.getPubkey());
|
||||
}
|
||||
Security.doProofOfWork(object, proofOfWorkEngine, nonceTrialsPerByte, extraBytes);
|
||||
security.doProofOfWork(object, nonceTrialsPerByte, extraBytes);
|
||||
if (payload instanceof PlaintextHolder) {
|
||||
Plaintext plaintext = ((PlaintextHolder) payload).getPlaintext();
|
||||
plaintext.setInventoryVector(object.getInventoryVector());
|
||||
@ -182,13 +192,13 @@ public class InternalContext {
|
||||
.payload(identity.getPubkey())
|
||||
.build();
|
||||
response.sign(identity.getPrivateKey());
|
||||
response.encrypt(Security.createPublicKey(identity.getPublicDecryptionKey()).getEncoded(false));
|
||||
Security.doProofOfWork(response, proofOfWorkEngine, networkNonceTrialsPerByte, networkExtraBytes);
|
||||
response.encrypt(security.createPublicKey(identity.getPublicDecryptionKey()));
|
||||
security.doProofOfWork(response, networkNonceTrialsPerByte, networkExtraBytes);
|
||||
if (response.isSigned()) {
|
||||
response.sign(identity.getPrivateKey());
|
||||
}
|
||||
if (response instanceof Encrypted) {
|
||||
response.encrypt(Security.createPublicKey(identity.getPublicDecryptionKey()).getEncoded(false));
|
||||
response.encrypt(security.createPublicKey(identity.getPublicDecryptionKey()));
|
||||
}
|
||||
inventory.storeObject(response);
|
||||
networkHandler.offer(response.getInventoryVector());
|
||||
@ -206,7 +216,7 @@ public class InternalContext {
|
||||
.expiresTime(expires)
|
||||
.payload(new GetPubkey(contact))
|
||||
.build();
|
||||
Security.doProofOfWork(response, proofOfWorkEngine, networkNonceTrialsPerByte, networkExtraBytes);
|
||||
security.doProofOfWork(response, networkNonceTrialsPerByte, networkExtraBytes);
|
||||
inventory.storeObject(response);
|
||||
networkHandler.offer(response.getInventoryVector());
|
||||
}
|
||||
@ -215,10 +225,6 @@ public class InternalContext {
|
||||
return clientNonce;
|
||||
}
|
||||
|
||||
public void setClientNonce(long clientNonce) {
|
||||
this.clientNonce = clientNonce;
|
||||
}
|
||||
|
||||
public interface ContextHolder {
|
||||
void setContext(InternalContext context);
|
||||
}
|
||||
|
@ -19,22 +19,27 @@ package ch.dissem.bitmessage.entity;
|
||||
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.*;
|
||||
import ch.dissem.bitmessage.utils.AccessCounter;
|
||||
import ch.dissem.bitmessage.utils.Base58;
|
||||
import ch.dissem.bitmessage.utils.Bytes;
|
||||
import ch.dissem.bitmessage.utils.Encode;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.Decode.bytes;
|
||||
import static ch.dissem.bitmessage.utils.Decode.varInt;
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
|
||||
/**
|
||||
* A Bitmessage address. Can be a user's private address, an address string without public keys or a recipient's address
|
||||
* holding private keys.
|
||||
*/
|
||||
public class BitmessageAddress {
|
||||
public class BitmessageAddress implements Serializable {
|
||||
private final long version;
|
||||
private final long stream;
|
||||
private final byte[] ripe;
|
||||
@ -62,19 +67,19 @@ public class BitmessageAddress {
|
||||
Encode.varInt(version, os);
|
||||
Encode.varInt(stream, os);
|
||||
if (version < 4) {
|
||||
byte[] checksum = Security.sha512(os.toByteArray(), ripe);
|
||||
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);
|
||||
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);
|
||||
byte[] 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,18 +108,18 @@ public class BitmessageAddress {
|
||||
this.ripe = Bytes.expand(bytes(in, bytes.length - counter.length() - 4), 20);
|
||||
|
||||
// test checksum
|
||||
byte[] checksum = Security.doubleSha512(bytes, bytes.length - 4);
|
||||
byte[] checksum = security().doubleSha512(bytes, bytes.length - 4);
|
||||
byte[] expectedChecksum = bytes(in, 4);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (expectedChecksum[i] != checksum[i])
|
||||
throw new IllegalArgumentException("Checksum of address failed");
|
||||
}
|
||||
if (version < 4) {
|
||||
checksum = Security.sha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe);
|
||||
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);
|
||||
checksum = security().doubleSha512(Arrays.copyOfRange(bytes, 0, counter.length()), ripe);
|
||||
this.tag = Arrays.copyOfRange(checksum, 32, 64);
|
||||
this.publicDecryptionKey = Arrays.copyOfRange(checksum, 0, 32);
|
||||
}
|
||||
@ -129,7 +134,7 @@ public class BitmessageAddress {
|
||||
Encode.varInt(version, out);
|
||||
Encode.varInt(stream, out);
|
||||
out.write(ripe);
|
||||
return Arrays.copyOfRange(Security.doubleSha512(out.toByteArray()), 32, 64);
|
||||
return Arrays.copyOfRange(security().doubleSha512(out.toByteArray()), 32, 64);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
package ch.dissem.bitmessage.entity;
|
||||
|
||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
||||
import ch.dissem.bitmessage.ports.Security;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
@ -26,7 +26,7 @@ import java.security.GeneralSecurityException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.Security.sha512;
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
|
||||
/**
|
||||
* A network message is exchanged between two nodes.
|
||||
@ -48,7 +48,7 @@ public class NetworkMessage implements Streamable {
|
||||
* First 4 bytes of sha512(payload)
|
||||
*/
|
||||
private byte[] getChecksum(byte[] bytes) throws NoSuchProviderException, NoSuchAlgorithmException {
|
||||
byte[] d = sha512(bytes);
|
||||
byte[] d = security().sha512(bytes);
|
||||
return new byte[]{d[0], d[1], d[2], d[3]};
|
||||
}
|
||||
|
||||
|
@ -22,14 +22,16 @@ import ch.dissem.bitmessage.entity.payload.Pubkey;
|
||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
||||
import ch.dissem.bitmessage.ports.Security;
|
||||
import ch.dissem.bitmessage.utils.Bytes;
|
||||
import ch.dissem.bitmessage.utils.Encode;
|
||||
import ch.dissem.bitmessage.utils.Security;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
|
||||
/**
|
||||
* The 'object' command sends an object that is shared throughout the network.
|
||||
*/
|
||||
@ -89,7 +91,9 @@ public class ObjectMessage implements MessagePayload {
|
||||
}
|
||||
|
||||
public InventoryVector getInventoryVector() {
|
||||
return new InventoryVector(Bytes.truncate(Security.doubleSha512(nonce, getPayloadBytesWithoutNonce()), 32));
|
||||
return new InventoryVector(
|
||||
Bytes.truncate(security().doubleSha512(nonce, getPayloadBytesWithoutNonce()), 32)
|
||||
);
|
||||
}
|
||||
|
||||
private boolean isEncrypted() {
|
||||
@ -113,7 +117,7 @@ public class ObjectMessage implements MessagePayload {
|
||||
|
||||
public void sign(PrivateKey key) {
|
||||
if (payload.isSigned()) {
|
||||
payload.setSignature(Security.getSignature(getBytesToSign(), key));
|
||||
payload.setSignature(security().getSignature(getBytesToSign(), key));
|
||||
}
|
||||
}
|
||||
|
||||
@ -147,7 +151,7 @@ public class ObjectMessage implements MessagePayload {
|
||||
|
||||
public boolean isSignatureValid(Pubkey pubkey) throws IOException {
|
||||
if (isEncrypted()) throw new IllegalStateException("Payload must be decrypted first");
|
||||
return Security.isSignatureValid(getBytesToSign(), payload.getSignature(), pubkey);
|
||||
return security().isSignatureValid(getBytesToSign(), payload.getSignature(), pubkey);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -19,6 +19,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.ports.Security;
|
||||
import ch.dissem.bitmessage.utils.Decode;
|
||||
import ch.dissem.bitmessage.utils.Encode;
|
||||
|
||||
@ -28,7 +29,7 @@ import java.util.*;
|
||||
/**
|
||||
* The unencrypted message to be sent by 'msg' or 'broadcast'.
|
||||
*/
|
||||
public class Plaintext implements Streamable {
|
||||
public class Plaintext implements Streamable, Serializable {
|
||||
private final Type type;
|
||||
private final BitmessageAddress from;
|
||||
private final long encoding;
|
||||
|
@ -16,6 +16,8 @@
|
||||
|
||||
package ch.dissem.bitmessage.entity;
|
||||
|
||||
import ch.dissem.bitmessage.ports.Security;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
|
@ -21,11 +21,12 @@ 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 ch.dissem.bitmessage.ports.Security;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static ch.dissem.bitmessage.entity.Plaintext.Type.BROADCAST;
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
|
||||
/**
|
||||
* Users who are subscribed to the sending address will see the message appear in their inbox.
|
||||
@ -78,7 +79,7 @@ public abstract class Broadcast extends ObjectPayload implements Encrypted, Plai
|
||||
}
|
||||
|
||||
public void encrypt() throws IOException {
|
||||
encrypt(Security.createPublicKey(plaintext.getFrom().getPublicDecryptionKey()).getEncoded(false));
|
||||
encrypt(security().createPublicKey(plaintext.getFrom().getPublicDecryptionKey()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -17,28 +17,16 @@
|
||||
package ch.dissem.bitmessage.entity.payload;
|
||||
|
||||
import ch.dissem.bitmessage.entity.Streamable;
|
||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
||||
import ch.dissem.bitmessage.exception.DecryptionFailedException;
|
||||
import ch.dissem.bitmessage.utils.*;
|
||||
import org.bouncycastle.crypto.BufferedBlockCipher;
|
||||
import org.bouncycastle.crypto.CipherParameters;
|
||||
import org.bouncycastle.crypto.InvalidCipherTextException;
|
||||
import org.bouncycastle.crypto.engines.AESEngine;
|
||||
import org.bouncycastle.crypto.modes.CBCBlockCipher;
|
||||
import org.bouncycastle.crypto.paddings.PKCS7Padding;
|
||||
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
import org.bouncycastle.crypto.params.ParametersWithIV;
|
||||
import org.bouncycastle.math.ec.ECFieldElement;
|
||||
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;
|
||||
|
||||
import static ch.dissem.bitmessage.entity.valueobject.PrivateKey.PRIVATE_KEY_SIZE;
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
|
||||
|
||||
public class CryptoBox implements Streamable {
|
||||
@ -46,35 +34,31 @@ public class CryptoBox implements Streamable {
|
||||
|
||||
private final byte[] initializationVector;
|
||||
private final int curveType;
|
||||
private final ECPoint R;
|
||||
private final byte[] R;
|
||||
private final byte[] mac;
|
||||
private byte[] encrypted;
|
||||
|
||||
public CryptoBox(Streamable data, byte[] encryptionKey) throws IOException {
|
||||
this(data, Security.keyToPoint(encryptionKey));
|
||||
}
|
||||
|
||||
public CryptoBox(Streamable data, ECPoint K) throws IOException {
|
||||
public CryptoBox(Streamable data, byte[] K) throws IOException {
|
||||
curveType = 0x02CA;
|
||||
|
||||
// 1. The destination public key is called K.
|
||||
// 2. Generate 16 random bytes using a secure random number generator. Call them IV.
|
||||
initializationVector = Security.randomBytes(16);
|
||||
initializationVector = security().randomBytes(16);
|
||||
|
||||
// 3. Generate a new random EC key pair with private key called r and public key called R.
|
||||
byte[] r = Security.randomBytes(PRIVATE_KEY_SIZE);
|
||||
R = Security.createPublicKey(r);
|
||||
byte[] r = security().randomBytes(PRIVATE_KEY_SIZE);
|
||||
R = security().createPublicKey(r);
|
||||
// 4. Do an EC point multiply with public key K and private key r. This gives you public key P.
|
||||
ECPoint P = K.multiply(Security.keyToBigInt(r)).normalize();
|
||||
byte[] X = P.getXCoord().getEncoded();
|
||||
byte[] P = security().multiply(K, r);
|
||||
byte[] X = Points.getX(P);
|
||||
// 5. Use the X component of public key P and calculate the SHA512 hash H.
|
||||
byte[] H = Security.sha512(X);
|
||||
byte[] H = security().sha512(X);
|
||||
// 6. The first 32 bytes of H are called key_e and the last 32 bytes are called key_m.
|
||||
byte[] key_e = Arrays.copyOfRange(H, 0, 32);
|
||||
byte[] key_m = Arrays.copyOfRange(H, 32, 64);
|
||||
// 7. Pad the input text to a multiple of 16 bytes, in accordance to PKCS7.
|
||||
// 8. Encrypt the data with AES-256-CBC, using IV as initialization vector, key_e as encryption key and the padded input text as payload. Call the output cipher text.
|
||||
encrypted = crypt(true, Encode.bytes(data), key_e);
|
||||
encrypted = security().crypt(true, Encode.bytes(data), key_e, initializationVector);
|
||||
// 9. Calculate a 32 byte MAC with HMACSHA256, using key_m as salt and IV + R + cipher text as data. Call the output MAC.
|
||||
mac = calculateMac(key_m);
|
||||
|
||||
@ -84,7 +68,7 @@ public class CryptoBox implements Streamable {
|
||||
private CryptoBox(Builder builder) {
|
||||
initializationVector = builder.initializationVector;
|
||||
curveType = builder.curveType;
|
||||
R = Security.createPoint(builder.xComponent, builder.yComponent);
|
||||
R = security().createPoint(builder.xComponent, builder.yComponent);
|
||||
encrypted = builder.encrypted;
|
||||
mac = builder.mac;
|
||||
}
|
||||
@ -102,18 +86,17 @@ public class CryptoBox implements Streamable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param privateKey a private key, typically should be 32 bytes long
|
||||
* @param k a private key, typically should be 32 bytes long
|
||||
* @return an InputStream yielding the decrypted data
|
||||
* @throws DecryptionFailedException if the payload can't be decrypted using this private key
|
||||
* @see <a href='https://bitmessage.org/wiki/Encryption#Decryption'>https://bitmessage.org/wiki/Encryption#Decryption</a>
|
||||
*/
|
||||
public InputStream decrypt(byte[] privateKey) throws DecryptionFailedException {
|
||||
public InputStream decrypt(byte[] k) throws DecryptionFailedException {
|
||||
// 1. The private key used to decrypt is called k.
|
||||
BigInteger k = Security.keyToBigInt(privateKey);
|
||||
// 2. Do an EC point multiply with private key k and public key R. This gives you public key P.
|
||||
ECPoint P = R.multiply(k).normalize();
|
||||
byte[] P = security().multiply(R, k);
|
||||
// 3. Use the X component of public key P and calculate the SHA512 hash H.
|
||||
byte[] H = Security.sha512(P.getXCoord().getEncoded());
|
||||
byte[] H = security().sha512(Arrays.copyOfRange(P, 1, 33));
|
||||
// 4. The first 32 bytes of H are called key_e and the last 32 bytes are called key_m.
|
||||
byte[] key_e = Arrays.copyOfRange(H, 0, 32);
|
||||
byte[] key_m = Arrays.copyOfRange(H, 32, 64);
|
||||
@ -126,49 +109,28 @@ public class CryptoBox implements Streamable {
|
||||
|
||||
// 7. Decrypt the cipher text with AES-256-CBC, using IV as initialization vector, key_e as decryption key
|
||||
// and the cipher text as payload. The output is the padded input text.
|
||||
return new ByteArrayInputStream(crypt(false, encrypted, key_e));
|
||||
return new ByteArrayInputStream(security().crypt(false, encrypted, key_e, initializationVector));
|
||||
}
|
||||
|
||||
private byte[] calculateMac(byte[] key_m) {
|
||||
try {
|
||||
ByteArrayOutputStream macData = new ByteArrayOutputStream();
|
||||
writeWithoutMAC(macData);
|
||||
return Security.mac(key_m, macData.toByteArray());
|
||||
return security().mac(key_m, macData.toByteArray());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] crypt(boolean encrypt, byte[] data, byte[] key_e) {
|
||||
BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()), new PKCS7Padding());
|
||||
|
||||
CipherParameters params = new ParametersWithIV(new KeyParameter(key_e), initializationVector);
|
||||
|
||||
cipher.init(encrypt, params);
|
||||
|
||||
byte[] buffer = new byte[cipher.getOutputSize(data.length)];
|
||||
int length = cipher.processBytes(data, 0, data.length, buffer, 0);
|
||||
try {
|
||||
length += cipher.doFinal(buffer, length);
|
||||
} catch (InvalidCipherTextException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
if (length < buffer.length) {
|
||||
return Arrays.copyOfRange(buffer, 0, length);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private void writeWithoutMAC(OutputStream out) throws IOException {
|
||||
out.write(initializationVector);
|
||||
Encode.int16(curveType, out);
|
||||
writeCoordinateComponent(out, R.getXCoord());
|
||||
writeCoordinateComponent(out, R.getYCoord());
|
||||
writeCoordinateComponent(out, Points.getX(R));
|
||||
writeCoordinateComponent(out, Points.getY(R));
|
||||
out.write(encrypted);
|
||||
}
|
||||
|
||||
private void writeCoordinateComponent(OutputStream out, ECFieldElement coord) throws IOException {
|
||||
byte[] x = coord.getEncoded();
|
||||
private void writeCoordinateComponent(OutputStream out, byte[] x) throws IOException {
|
||||
int offset = Bytes.numberOfLeadingZeros(x);
|
||||
int length = x.length - offset;
|
||||
Encode.int16(length, out);
|
||||
|
@ -20,8 +20,7 @@ import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.Security.ripemd160;
|
||||
import static ch.dissem.bitmessage.utils.Security.sha512;
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
|
||||
/**
|
||||
* Public keys for signing and encryption, the answer to a 'getpubkey' request.
|
||||
@ -34,7 +33,7 @@ public abstract class Pubkey extends ObjectPayload {
|
||||
}
|
||||
|
||||
public static byte[] getRipe(byte[] publicSigningKey, byte[] publicEncryptionKey) {
|
||||
return ripemd160(sha512(publicSigningKey, publicEncryptionKey));
|
||||
return security().ripemd160(security().sha512(publicSigningKey, publicEncryptionKey));
|
||||
}
|
||||
|
||||
public abstract byte[] getSigningKey();
|
||||
@ -44,7 +43,7 @@ public abstract class Pubkey extends ObjectPayload {
|
||||
public abstract int getBehaviorBitfield();
|
||||
|
||||
public byte[] getRipe() {
|
||||
return ripemd160(sha512(getSigningKey(), getEncryptionKey()));
|
||||
return security().ripemd160(security().sha512(getSigningKey(), getEncryptionKey()));
|
||||
}
|
||||
|
||||
public long getNonceTrialsPerByte() {
|
||||
|
@ -18,6 +18,7 @@ package ch.dissem.bitmessage.entity.payload;
|
||||
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.entity.Plaintext;
|
||||
import ch.dissem.bitmessage.ports.Security;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
@ -21,9 +21,10 @@ import ch.dissem.bitmessage.utils.Strings;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class InventoryVector implements Streamable {
|
||||
public class InventoryVector implements Streamable, Serializable {
|
||||
/**
|
||||
* Hash of the object
|
||||
*/
|
||||
|
@ -18,18 +18,18 @@ 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;
|
||||
import ch.dissem.bitmessage.utils.Encode;
|
||||
import ch.dissem.bitmessage.utils.Security;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
|
||||
/**
|
||||
* Created by chris on 18.04.15.
|
||||
* Represents a private key. Additional information (stream, version, features, ...) is stored in the accompanying
|
||||
* {@link Pubkey} object.
|
||||
*/
|
||||
public class PrivateKey implements Streamable {
|
||||
public static final int PRIVATE_KEY_SIZE = 32;
|
||||
@ -45,15 +45,15 @@ public class PrivateKey implements Streamable {
|
||||
byte[] pubEK;
|
||||
byte[] ripe;
|
||||
do {
|
||||
privSK = Security.randomBytes(PRIVATE_KEY_SIZE);
|
||||
privEK = Security.randomBytes(PRIVATE_KEY_SIZE);
|
||||
pubSK = Security.createPublicKey(privSK).getEncoded(false);
|
||||
pubEK = Security.createPublicKey(privEK).getEncoded(false);
|
||||
privSK = security().randomBytes(PRIVATE_KEY_SIZE);
|
||||
privEK = security().randomBytes(PRIVATE_KEY_SIZE);
|
||||
pubSK = security().createPublicKey(privSK);
|
||||
pubEK = security().createPublicKey(privEK);
|
||||
ripe = Pubkey.getRipe(pubSK, pubEK);
|
||||
} while (ripe[0] != 0 || (shorter && ripe[1] != 0));
|
||||
this.privateSigningKey = privSK;
|
||||
this.privateEncryptionKey = privEK;
|
||||
this.pubkey = Security.createPubkey(Pubkey.LATEST_VERSION, stream, privateSigningKey, privateEncryptionKey,
|
||||
this.pubkey = security().createPubkey(Pubkey.LATEST_VERSION, stream, privateSigningKey, privateEncryptionKey,
|
||||
nonceTrialsPerByte, extraBytes, features);
|
||||
}
|
||||
|
||||
@ -66,9 +66,9 @@ public class PrivateKey implements Streamable {
|
||||
public PrivateKey(long version, long stream, String passphrase, long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) {
|
||||
try {
|
||||
// FIXME: this is most definitely wrong
|
||||
this.privateSigningKey = Bytes.truncate(Security.sha512(passphrase.getBytes("UTF-8"), new byte[]{0}), 32);
|
||||
this.privateEncryptionKey = Bytes.truncate(Security.sha512(passphrase.getBytes("UTF-8"), new byte[]{1}), 32);
|
||||
this.pubkey = Security.createPubkey(version, stream, privateSigningKey, privateEncryptionKey,
|
||||
this.privateSigningKey = Bytes.truncate(security().sha512(passphrase.getBytes("UTF-8"), new byte[]{0}), 32);
|
||||
this.privateEncryptionKey = Bytes.truncate(security().sha512(passphrase.getBytes("UTF-8"), new byte[]{1}), 32);
|
||||
this.pubkey = security().createPubkey(version, stream, privateSigningKey, privateEncryptionKey,
|
||||
nonceTrialsPerByte, extraBytes, features);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
|
@ -23,7 +23,7 @@ import ch.dissem.bitmessage.entity.Plaintext;
|
||||
import ch.dissem.bitmessage.entity.payload.*;
|
||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
||||
import ch.dissem.bitmessage.exception.NodeException;
|
||||
import ch.dissem.bitmessage.utils.Security;
|
||||
import ch.dissem.bitmessage.ports.Security;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -32,6 +32,8 @@ import java.io.InputStream;
|
||||
import java.net.SocketException;
|
||||
import java.net.SocketTimeoutException;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
|
||||
/**
|
||||
* Creates {@link NetworkMessage} objects from {@link InputStream InputStreams}
|
||||
*/
|
||||
@ -115,8 +117,8 @@ public class Factory {
|
||||
BitmessageAddress temp = new BitmessageAddress(address);
|
||||
PrivateKey privateKey = new PrivateKey(privateSigningKey, privateEncryptionKey,
|
||||
createPubkey(temp.getVersion(), temp.getStream(),
|
||||
Security.createPublicKey(privateSigningKey).getEncoded(false),
|
||||
Security.createPublicKey(privateEncryptionKey).getEncoded(false),
|
||||
security().createPublicKey(privateSigningKey),
|
||||
security().createPublicKey(privateEncryptionKey),
|
||||
nonceTrialsPerByte, extraBytes, behaviourBitfield));
|
||||
BitmessageAddress result = new BitmessageAddress(privateKey);
|
||||
if (!result.getAddress().equals(address)) {
|
||||
@ -126,11 +128,17 @@ public class Factory {
|
||||
return result;
|
||||
}
|
||||
|
||||
public static BitmessageAddress generatePrivateAddress(boolean shorter, long stream, Pubkey.Feature... features) {
|
||||
public static BitmessageAddress generatePrivateAddress(boolean shorter,
|
||||
long stream,
|
||||
Pubkey.Feature... features) {
|
||||
return new BitmessageAddress(new PrivateKey(shorter, stream, 1000, 1000, features));
|
||||
}
|
||||
|
||||
static ObjectPayload getObjectPayload(long objectType, long version, long streamNumber, InputStream stream, int length) throws IOException {
|
||||
static ObjectPayload getObjectPayload(long objectType,
|
||||
long version,
|
||||
long streamNumber,
|
||||
InputStream stream,
|
||||
int length) throws IOException {
|
||||
ObjectType type = ObjectType.fromNumber(objectType);
|
||||
if (type != null) {
|
||||
switch (type) {
|
||||
|
@ -22,9 +22,9 @@ import ch.dissem.bitmessage.entity.payload.ObjectPayload;
|
||||
import ch.dissem.bitmessage.entity.valueobject.InventoryVector;
|
||||
import ch.dissem.bitmessage.entity.valueobject.NetworkAddress;
|
||||
import ch.dissem.bitmessage.exception.NodeException;
|
||||
import ch.dissem.bitmessage.ports.Security;
|
||||
import ch.dissem.bitmessage.utils.AccessCounter;
|
||||
import ch.dissem.bitmessage.utils.Decode;
|
||||
import ch.dissem.bitmessage.utils.Security;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -33,6 +33,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import static ch.dissem.bitmessage.entity.NetworkMessage.MAGIC_BYTES;
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
|
||||
/**
|
||||
* Creates protocol v3 network messages from {@link InputStream InputStreams}
|
||||
@ -174,7 +175,7 @@ class V3MessageFactory {
|
||||
}
|
||||
|
||||
private static boolean testChecksum(byte[] checksum, byte[] payload) {
|
||||
byte[] payloadChecksum = Security.sha512(payload);
|
||||
byte[] payloadChecksum = security().sha512(payload);
|
||||
for (int i = 0; i < checksum.length; i++) {
|
||||
if (checksum[i] != payloadChecksum[i]) {
|
||||
return false;
|
||||
|
@ -0,0 +1,175 @@
|
||||
/*
|
||||
* 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.ports;
|
||||
|
||||
import ch.dissem.bitmessage.InternalContext;
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
||||
import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException;
|
||||
import ch.dissem.bitmessage.factory.Factory;
|
||||
import ch.dissem.bitmessage.utils.Bytes;
|
||||
import ch.dissem.bitmessage.utils.UnixTime;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
* Implements everything that isn't directly dependent on either Spongy- or Bouncycastle.
|
||||
*/
|
||||
public abstract class AbstractSecurity implements Security, InternalContext.ContextHolder {
|
||||
public static final Logger LOG = LoggerFactory.getLogger(Security.class);
|
||||
private static final SecureRandom RANDOM = new SecureRandom();
|
||||
private static final BigInteger TWO = BigInteger.valueOf(2);
|
||||
|
||||
private final String provider;
|
||||
private InternalContext context;
|
||||
|
||||
protected AbstractSecurity(String provider) {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContext(InternalContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public byte[] sha512(byte[]... data) {
|
||||
return hash("SHA-512", data);
|
||||
}
|
||||
|
||||
public byte[] doubleSha512(byte[]... data) {
|
||||
MessageDigest mda = md("SHA-512");
|
||||
for (byte[] d : data) {
|
||||
mda.update(d);
|
||||
}
|
||||
return mda.digest(mda.digest());
|
||||
}
|
||||
|
||||
public byte[] doubleSha512(byte[] data, int length) {
|
||||
MessageDigest mda = md("SHA-512");
|
||||
mda.update(data, 0, length);
|
||||
return mda.digest(mda.digest());
|
||||
}
|
||||
|
||||
public byte[] ripemd160(byte[]... data) {
|
||||
return hash("RIPEMD160", data);
|
||||
}
|
||||
|
||||
public byte[] doubleSha256(byte[] data, int length) {
|
||||
MessageDigest mda = md("SHA-256");
|
||||
mda.update(data, 0, length);
|
||||
return mda.digest(mda.digest());
|
||||
}
|
||||
|
||||
public byte[] sha1(byte[]... data) {
|
||||
return hash("SHA-1", data);
|
||||
}
|
||||
|
||||
public byte[] randomBytes(int length) {
|
||||
byte[] result = new byte[length];
|
||||
RANDOM.nextBytes(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public void doProofOfWork(ObjectMessage object, long nonceTrialsPerByte,
|
||||
long extraBytes) {
|
||||
try {
|
||||
if (nonceTrialsPerByte < 1000) nonceTrialsPerByte = 1000;
|
||||
if (extraBytes < 1000) extraBytes = 1000;
|
||||
|
||||
byte[] initialHash = getInitialHash(object);
|
||||
|
||||
byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes);
|
||||
|
||||
byte[] nonce = context.getProofOfWorkEngine().calculateNonce(initialHash, target);
|
||||
object.setNonce(nonce);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void checkProofOfWork(ObjectMessage object, long nonceTrialsPerByte, long extraBytes)
|
||||
throws IOException {
|
||||
byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes);
|
||||
byte[] value = doubleSha512(object.getNonce(), getInitialHash(object));
|
||||
if (Bytes.lt(target, value, 8)) {
|
||||
throw new InsufficientProofOfWorkException(target, value);
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] getInitialHash(ObjectMessage object) throws IOException {
|
||||
return sha512(object.getPayloadBytesWithoutNonce());
|
||||
}
|
||||
|
||||
private byte[] getProofOfWorkTarget(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) throws IOException {
|
||||
BigInteger TTL = BigInteger.valueOf(object.getExpiresTime() - UnixTime.now());
|
||||
LOG.debug("TTL: " + TTL + "s");
|
||||
BigInteger numerator = TWO.pow(64);
|
||||
BigInteger powLength = BigInteger.valueOf(object.getPayloadBytesWithoutNonce().length + extraBytes);
|
||||
BigInteger denominator = BigInteger.valueOf(nonceTrialsPerByte).multiply(powLength.add(powLength.multiply(TTL).divide(BigInteger.valueOf(2).pow(16))));
|
||||
return Bytes.expand(numerator.divide(denominator).toByteArray(), 8);
|
||||
}
|
||||
|
||||
private byte[] hash(String algorithm, byte[]... data) {
|
||||
MessageDigest mda = md(algorithm);
|
||||
for (byte[] d : data) {
|
||||
mda.update(d);
|
||||
}
|
||||
return mda.digest();
|
||||
}
|
||||
|
||||
private MessageDigest md(String algorithm) {
|
||||
try {
|
||||
return MessageDigest.getInstance(algorithm, provider);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] mac(byte[] key_m, byte[] data) {
|
||||
try {
|
||||
Mac mac = Mac.getInstance("HmacSHA256", provider);
|
||||
mac.init(new SecretKeySpec(key_m, "HmacSHA256"));
|
||||
return mac.doFinal(data);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Pubkey createPubkey(long version, long stream, byte[] privateSigningKey, byte[] privateEncryptionKey,
|
||||
long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) {
|
||||
return Factory.createPubkey(version, stream,
|
||||
createPublicKey(privateSigningKey),
|
||||
createPublicKey(privateEncryptionKey),
|
||||
nonceTrialsPerByte, extraBytes, features);
|
||||
}
|
||||
|
||||
public BigInteger keyToBigInt(byte[] privateKey) {
|
||||
return new BigInteger(1, privateKey);
|
||||
}
|
||||
|
||||
public long randomNonce() {
|
||||
return RANDOM.nextLong();
|
||||
}
|
||||
}
|
@ -34,6 +34,8 @@ public interface NetworkHandler {
|
||||
|
||||
Property getNetworkStatus();
|
||||
|
||||
boolean isRunning();
|
||||
|
||||
interface MessageListener {
|
||||
void receive(ObjectMessage object) throws IOException;
|
||||
}
|
||||
|
206
domain/src/main/java/ch/dissem/bitmessage/ports/Security.java
Normal file
206
domain/src/main/java/ch/dissem/bitmessage/ports/Security.java
Normal file
@ -0,0 +1,206 @@
|
||||
/*
|
||||
* 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.ports;
|
||||
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
||||
import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
/**
|
||||
* Provides some methods to help with hashing and encryption. All randoms are created using {@link SecureRandom},
|
||||
* which should be secure enough.
|
||||
*/
|
||||
public interface Security {
|
||||
/**
|
||||
* A helper method to calculate SHA-512 hashes. Please note that a new {@link MessageDigest} object is created at
|
||||
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in
|
||||
* success on the same thread.
|
||||
*
|
||||
* @param data to get hashed
|
||||
* @return SHA-512 hash of data
|
||||
*/
|
||||
byte[] sha512(byte[]... data);
|
||||
|
||||
/**
|
||||
* A helper method to calculate doubleSHA-512 hashes. Please note that a new {@link MessageDigest} object is created
|
||||
* at each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in
|
||||
* success on the same thread.
|
||||
*
|
||||
* @param data to get hashed
|
||||
* @return SHA-512 hash of data
|
||||
*/
|
||||
byte[] doubleSha512(byte[]... data);
|
||||
|
||||
/**
|
||||
* A helper method to calculate double SHA-512 hashes. This method allows to only use a part of the available bytes
|
||||
* to use for the hash calculation.
|
||||
* <p>
|
||||
* Please note that a new {@link MessageDigest} object is created at each call (to ensure thread safety), so you
|
||||
* shouldn't use this if you need to do many hash calculations in short order on the same thread.
|
||||
* </p>
|
||||
*
|
||||
* @param data to get hashed
|
||||
* @param length number of bytes to be taken into account
|
||||
* @return SHA-512 hash of data
|
||||
*/
|
||||
byte[] doubleSha512(byte[] data, int length);
|
||||
|
||||
/**
|
||||
* A helper method to calculate RIPEMD-160 hashes. Supplying multiple byte arrays has the same result as a
|
||||
* concatenation of all arrays, but might perform better.
|
||||
* <p>
|
||||
* Please note that a new {@link MessageDigest} object is created at
|
||||
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short
|
||||
* order on the same thread.
|
||||
* </p>
|
||||
*
|
||||
* @param data to get hashed
|
||||
* @return RIPEMD-160 hash of data
|
||||
*/
|
||||
byte[] ripemd160(byte[]... data);
|
||||
|
||||
/**
|
||||
* A helper method to calculate double SHA-256 hashes. This method allows to only use a part of the available bytes
|
||||
* to use for the hash calculation.
|
||||
* <p>
|
||||
* Please note that a new {@link MessageDigest} object is created at
|
||||
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short
|
||||
* order on the same thread.
|
||||
* </p>
|
||||
*
|
||||
* @param data to get hashed
|
||||
* @param length number of bytes to be taken into account
|
||||
* @return SHA-256 hash of data
|
||||
*/
|
||||
byte[] doubleSha256(byte[] data, int length);
|
||||
|
||||
/**
|
||||
* A helper method to calculate SHA-1 hashes. Supplying multiple byte arrays has the same result as a
|
||||
* concatenation of all arrays, but might perform better.
|
||||
* <p>
|
||||
* Please note that a new {@link MessageDigest} object is created at
|
||||
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short
|
||||
* order on the same thread.
|
||||
* </p>
|
||||
*
|
||||
* @param data to get hashed
|
||||
* @return SHA hash of data
|
||||
*/
|
||||
byte[] sha1(byte[]... data);
|
||||
|
||||
/**
|
||||
* @param length number of bytes to return
|
||||
* @return an array of the given size containing random bytes
|
||||
*/
|
||||
byte[] randomBytes(int length);
|
||||
|
||||
/**
|
||||
* Calculates the proof of work. This might take a long time, depending on the hardware, message size and time to
|
||||
* live.
|
||||
*
|
||||
* @param object to do the proof of work for
|
||||
* @param nonceTrialsPerByte difficulty
|
||||
* @param extraBytes bytes to add to the object size (makes it more difficult to send small messages)
|
||||
*/
|
||||
void doProofOfWork(ObjectMessage object, long nonceTrialsPerByte,
|
||||
long extraBytes);
|
||||
|
||||
/**
|
||||
* @param object to be checked
|
||||
* @param nonceTrialsPerByte difficulty
|
||||
* @param extraBytes bytes to add to the object size
|
||||
* @throws InsufficientProofOfWorkException if proof of work doesn't check out (makes it more difficult to send small messages)
|
||||
*/
|
||||
void checkProofOfWork(ObjectMessage object, long nonceTrialsPerByte, long extraBytes)
|
||||
throws IOException;
|
||||
|
||||
/**
|
||||
* Calculates the MAC for a message (data)
|
||||
*
|
||||
* @param key_m the symmetric key used
|
||||
* @param data the message data to calculate the MAC for
|
||||
* @return the MAC
|
||||
*/
|
||||
byte[] mac(byte[] key_m, byte[] data);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param encrypt if true, encrypts data, otherwise tries to decrypt it.
|
||||
* @param data
|
||||
* @param key_e
|
||||
* @return
|
||||
*/
|
||||
byte[] crypt(boolean encrypt, byte[] data, byte[] key_e, byte[] initializationVector);
|
||||
|
||||
/**
|
||||
* Create a new public key fom given private keys.
|
||||
*
|
||||
* @param version of the public key / address
|
||||
* @param stream of the address
|
||||
* @param privateSigningKey private key used for signing
|
||||
* @param privateEncryptionKey private key used for encryption
|
||||
* @param nonceTrialsPerByte proof of work difficulty
|
||||
* @param extraBytes bytes to add for the proof of work (make it harder for small messages)
|
||||
* @param features of the address
|
||||
* @return a public key object
|
||||
*/
|
||||
Pubkey createPubkey(long version, long stream, byte[] privateSigningKey, byte[] privateEncryptionKey,
|
||||
long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features);
|
||||
|
||||
/**
|
||||
* @param privateKey private key as byte array
|
||||
* @return a public key corresponding to the given private key
|
||||
*/
|
||||
byte[] createPublicKey(byte[] privateKey);
|
||||
|
||||
/**
|
||||
* @param privateKey private key as byte array
|
||||
* @return a big integer representation (unsigned) of the given bytes
|
||||
*/
|
||||
BigInteger keyToBigInt(byte[] privateKey);
|
||||
|
||||
/**
|
||||
* @param data to check
|
||||
* @param signature the signature of the message
|
||||
* @param pubkey the sender's public key
|
||||
* @return true if the signature is valid, false otherwise
|
||||
*/
|
||||
boolean isSignatureValid(byte[] data, byte[] signature, Pubkey pubkey);
|
||||
|
||||
/**
|
||||
* Calculate the signature of data, using the given private key.
|
||||
*
|
||||
* @param data to be signed
|
||||
* @param privateKey to be used for signing
|
||||
* @return the signature
|
||||
*/
|
||||
byte[] getSignature(byte[] data, ch.dissem.bitmessage.entity.valueobject.PrivateKey privateKey);
|
||||
|
||||
/**
|
||||
* @return a random number of type long
|
||||
*/
|
||||
long randomNonce();
|
||||
|
||||
byte[] multiply(byte[] k, byte[] r);
|
||||
|
||||
byte[] createPoint(byte[] x, byte[] y);
|
||||
}
|
@ -23,7 +23,7 @@ import java.security.MessageDigest;
|
||||
import static ch.dissem.bitmessage.utils.Bytes.inc;
|
||||
|
||||
/**
|
||||
* Created by chris on 14.04.15.
|
||||
* You should really use the MultiThreadedPOWEngine, but this one might help you grok the other one.
|
||||
*/
|
||||
public class SimplePOWEngine implements ProofOfWorkEngine {
|
||||
@Override
|
||||
|
32
domain/src/main/java/ch/dissem/bitmessage/utils/Points.java
Normal file
32
domain/src/main/java/ch/dissem/bitmessage/utils/Points.java
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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 java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Created by chris on 20.07.15.
|
||||
*/
|
||||
public class Points {
|
||||
public static byte[] getX(byte[] P) {
|
||||
return Arrays.copyOfRange(P, 1, ((P.length - 1) / 2) + 1);
|
||||
}
|
||||
|
||||
public static byte[] getY(byte[] P) {
|
||||
return Arrays.copyOfRange(P, ((P.length - 1) / 2) + 1, P.length);
|
||||
}
|
||||
}
|
@ -17,7 +17,12 @@
|
||||
package ch.dissem.bitmessage.utils;
|
||||
|
||||
/**
|
||||
* Created by chris on 14.06.15.
|
||||
* Some property that has a name, a value and/or other properties. This can be used for any purpose, but is for now
|
||||
* used to contain different status information. It is by default displayed in some JSON inspired human readable
|
||||
* notation, but you might only want to rely on the 'human readable' part.
|
||||
* <p>
|
||||
* If you need a real JSON representation, please add a method <code>toJson()</code>.
|
||||
* </p>
|
||||
*/
|
||||
public class Property {
|
||||
private String name;
|
||||
|
@ -1,365 +0,0 @@
|
||||
/*
|
||||
* 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 ch.dissem.bitmessage.entity.payload.Pubkey;
|
||||
import ch.dissem.bitmessage.exception.InsufficientProofOfWorkException;
|
||||
import ch.dissem.bitmessage.factory.Factory;
|
||||
import ch.dissem.bitmessage.ports.ProofOfWorkEngine;
|
||||
import org.bouncycastle.asn1.x9.X9ECParameters;
|
||||
import org.bouncycastle.crypto.ec.CustomNamedCurves;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.jce.spec.ECParameterSpec;
|
||||
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
|
||||
import org.bouncycastle.jce.spec.ECPublicKeySpec;
|
||||
import org.bouncycastle.math.ec.ECPoint;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.security.*;
|
||||
import java.security.spec.KeySpec;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Provides some methods to help with hashing and encryption. All randoms are created using {@link SecureRandom},
|
||||
* which should be secure enough.
|
||||
*/
|
||||
public class Security {
|
||||
public static final Logger LOG = LoggerFactory.getLogger(Security.class);
|
||||
private static final SecureRandom RANDOM = new SecureRandom();
|
||||
private static final BigInteger TWO = BigInteger.valueOf(2);
|
||||
private static final X9ECParameters EC_CURVE_PARAMETERS = CustomNamedCurves.getByName("secp256k1");
|
||||
|
||||
static {
|
||||
java.security.Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper method to calculate SHA-512 hashes. Please note that a new {@link MessageDigest} object is created at
|
||||
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in
|
||||
* success on the same thread.
|
||||
*
|
||||
* @param data to get hashed
|
||||
* @return SHA-512 hash of data
|
||||
*/
|
||||
public static byte[] sha512(byte[]... data) {
|
||||
return hash("SHA-512", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper method to calculate doubleSHA-512 hashes. Please note that a new {@link MessageDigest} object is created
|
||||
* at each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in
|
||||
* success on the same thread.
|
||||
*
|
||||
* @param data to get hashed
|
||||
* @return SHA-512 hash of data
|
||||
*/
|
||||
public static byte[] doubleSha512(byte[]... data) {
|
||||
MessageDigest mda = md("SHA-512");
|
||||
for (byte[] d : data) {
|
||||
mda.update(d);
|
||||
}
|
||||
return mda.digest(mda.digest());
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper method to calculate double SHA-512 hashes. This method allows to only use a part of the available bytes
|
||||
* to use for the hash calculation.
|
||||
* <p>
|
||||
* Please note that a new {@link MessageDigest} object is created at each call (to ensure thread safety), so you
|
||||
* shouldn't use this if you need to do many hash calculations in short order on the same thread.
|
||||
* </p>
|
||||
*
|
||||
* @param data to get hashed
|
||||
* @param length number of bytes to be taken into account
|
||||
* @return SHA-512 hash of data
|
||||
*/
|
||||
public static byte[] doubleSha512(byte[] data, int length) {
|
||||
MessageDigest mda = md("SHA-512");
|
||||
mda.update(data, 0, length);
|
||||
return mda.digest(mda.digest());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* A helper method to calculate RIPEMD-160 hashes. Supplying multiple byte arrays has the same result as a
|
||||
* concatenation of all arrays, but might perform better.
|
||||
* <p>
|
||||
* Please note that a new {@link MessageDigest} object is created at
|
||||
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short
|
||||
* order on the same thread.
|
||||
* </p>
|
||||
*
|
||||
* @param data to get hashed
|
||||
* @return RIPEMD-160 hash of data
|
||||
*/
|
||||
public static byte[] ripemd160(byte[]... data) {
|
||||
return hash("RIPEMD160", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper method to calculate double SHA-256 hashes. This method allows to only use a part of the available bytes
|
||||
* to use for the hash calculation.
|
||||
* <p>
|
||||
* Please note that a new {@link MessageDigest} object is created at
|
||||
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short
|
||||
* order on the same thread.
|
||||
* </p>
|
||||
*
|
||||
* @param data to get hashed
|
||||
* @param length number of bytes to be taken into account
|
||||
* @return SHA-256 hash of data
|
||||
*/
|
||||
public static byte[] doubleSha256(byte[] data, int length) {
|
||||
MessageDigest mda = md("SHA-256");
|
||||
mda.update(data, 0, length);
|
||||
return mda.digest(mda.digest());
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper method to calculate SHA-1 hashes. Supplying multiple byte arrays has the same result as a
|
||||
* concatenation of all arrays, but might perform better.
|
||||
* <p>
|
||||
* Please note that a new {@link MessageDigest} object is created at
|
||||
* each call (to ensure thread safety), so you shouldn't use this if you need to do many hash calculations in short
|
||||
* order on the same thread.
|
||||
* </p>
|
||||
*
|
||||
* @param data to get hashed
|
||||
* @return SHA hash of data
|
||||
*/
|
||||
public static byte[] sha1(byte[]... data) {
|
||||
return hash("SHA-1", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param length number of bytes to return
|
||||
* @return an array of the given size containing random bytes
|
||||
*/
|
||||
public static byte[] randomBytes(int length) {
|
||||
byte[] result = new byte[length];
|
||||
RANDOM.nextBytes(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the proof of work. This might take a long time, depending on the hardware, message size and time to
|
||||
* live.
|
||||
*
|
||||
* @param object to do the proof of work for
|
||||
* @param worker doing the actual proof of work
|
||||
* @param nonceTrialsPerByte difficulty
|
||||
* @param extraBytes bytes to add to the object size (makes it more difficult to send small messages)
|
||||
*/
|
||||
public static void doProofOfWork(ObjectMessage object, ProofOfWorkEngine worker, long nonceTrialsPerByte,
|
||||
long extraBytes) {
|
||||
try {
|
||||
if (nonceTrialsPerByte < 1000) nonceTrialsPerByte = 1000;
|
||||
if (extraBytes < 1000) extraBytes = 1000;
|
||||
|
||||
byte[] initialHash = getInitialHash(object);
|
||||
|
||||
byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes);
|
||||
|
||||
byte[] nonce = worker.calculateNonce(initialHash, target);
|
||||
object.setNonce(nonce);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param object to be checked
|
||||
* @param nonceTrialsPerByte difficulty
|
||||
* @param extraBytes bytes to add to the object size
|
||||
* @throws InsufficientProofOfWorkException if proof of work doesn't check out (makes it more difficult to send small messages)
|
||||
*/
|
||||
public static void checkProofOfWork(ObjectMessage object, long nonceTrialsPerByte, long extraBytes)
|
||||
throws IOException {
|
||||
byte[] target = getProofOfWorkTarget(object, nonceTrialsPerByte, extraBytes);
|
||||
byte[] value = Security.doubleSha512(object.getNonce(), getInitialHash(object));
|
||||
if (Bytes.lt(target, value, 8)) {
|
||||
throw new InsufficientProofOfWorkException(target, value);
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] getInitialHash(ObjectMessage object) throws IOException {
|
||||
return Security.sha512(object.getPayloadBytesWithoutNonce());
|
||||
}
|
||||
|
||||
private static byte[] getProofOfWorkTarget(ObjectMessage object, long nonceTrialsPerByte, long extraBytes) throws IOException {
|
||||
BigInteger TTL = BigInteger.valueOf(object.getExpiresTime() - UnixTime.now());
|
||||
LOG.debug("TTL: " + TTL + "s");
|
||||
BigInteger numerator = TWO.pow(64);
|
||||
BigInteger powLength = BigInteger.valueOf(object.getPayloadBytesWithoutNonce().length + extraBytes);
|
||||
BigInteger denominator = BigInteger.valueOf(nonceTrialsPerByte).multiply(powLength.add(powLength.multiply(TTL).divide(BigInteger.valueOf(2).pow(16))));
|
||||
return Bytes.expand(numerator.divide(denominator).toByteArray(), 8);
|
||||
}
|
||||
|
||||
private static byte[] hash(String algorithm, byte[]... data) {
|
||||
MessageDigest mda = md(algorithm);
|
||||
for (byte[] d : data) {
|
||||
mda.update(d);
|
||||
}
|
||||
return mda.digest();
|
||||
}
|
||||
|
||||
private static MessageDigest md(String algorithm) {
|
||||
try {
|
||||
return MessageDigest.getInstance(algorithm, "BC");
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the MAC for a message (data)
|
||||
*
|
||||
* @param key_m the symmetric key used
|
||||
* @param data the message data to calculate the MAC for
|
||||
* @return the MAC
|
||||
*/
|
||||
public static byte[] mac(byte[] key_m, byte[] data) {
|
||||
try {
|
||||
Mac mac = Mac.getInstance("HmacSHA256", "BC");
|
||||
mac.init(new SecretKeySpec(key_m, "HmacSHA256"));
|
||||
return mac.doFinal(data);
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new public key fom given private keys.
|
||||
*
|
||||
* @param version of the public key / address
|
||||
* @param stream of the address
|
||||
* @param privateSigningKey private key used for signing
|
||||
* @param privateEncryptionKey private key used for encryption
|
||||
* @param nonceTrialsPerByte proof of work difficulty
|
||||
* @param extraBytes bytes to add for the proof of work (make it harder for small messages)
|
||||
* @param features of the address
|
||||
* @return a public key object
|
||||
*/
|
||||
public static Pubkey createPubkey(long version, long stream, byte[] privateSigningKey, byte[] privateEncryptionKey,
|
||||
long nonceTrialsPerByte, long extraBytes, Pubkey.Feature... features) {
|
||||
return Factory.createPubkey(version, stream,
|
||||
createPublicKey(privateSigningKey).getEncoded(false),
|
||||
createPublicKey(privateEncryptionKey).getEncoded(false),
|
||||
nonceTrialsPerByte, extraBytes, features);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param privateKey private key as byte array
|
||||
* @return a public key corresponding to the given private key
|
||||
*/
|
||||
public static ECPoint createPublicKey(byte[] privateKey) {
|
||||
return EC_CURVE_PARAMETERS.getG().multiply(keyToBigInt(privateKey)).normalize();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param privateKey private key as byte array
|
||||
* @return a big integer representation (unsigned) of the given bytes
|
||||
*/
|
||||
public static BigInteger keyToBigInt(byte[] privateKey) {
|
||||
return new BigInteger(1, privateKey);
|
||||
}
|
||||
|
||||
public static ECPoint keyToPoint(byte[] publicKey) {
|
||||
BigInteger x = new BigInteger(1, Arrays.copyOfRange(publicKey, 1, 33));
|
||||
BigInteger y = new BigInteger(1, Arrays.copyOfRange(publicKey, 33, 65));
|
||||
return EC_CURVE_PARAMETERS.getCurve().createPoint(x, y);
|
||||
}
|
||||
|
||||
public static ECPoint createPoint(byte[] x, byte[] y) {
|
||||
return EC_CURVE_PARAMETERS.getCurve().createPoint(
|
||||
new BigInteger(1, x),
|
||||
new BigInteger(1, y)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param data to check
|
||||
* @param signature the signature of the message
|
||||
* @param pubkey the sender's public key
|
||||
* @return true if the signature is valid, false otherwise
|
||||
*/
|
||||
public static boolean isSignatureValid(byte[] data, byte[] signature, Pubkey pubkey) {
|
||||
try {
|
||||
ECParameterSpec spec = new ECParameterSpec(
|
||||
EC_CURVE_PARAMETERS.getCurve(),
|
||||
EC_CURVE_PARAMETERS.getG(),
|
||||
EC_CURVE_PARAMETERS.getN(),
|
||||
EC_CURVE_PARAMETERS.getH(),
|
||||
EC_CURVE_PARAMETERS.getSeed()
|
||||
);
|
||||
|
||||
ECPoint Q = keyToPoint(pubkey.getSigningKey());
|
||||
KeySpec keySpec = new ECPublicKeySpec(Q, spec);
|
||||
PublicKey publicKey = KeyFactory.getInstance("ECDSA", "BC").generatePublic(keySpec);
|
||||
|
||||
Signature sig = Signature.getInstance("ECDSA", "BC");
|
||||
sig.initVerify(publicKey);
|
||||
sig.update(data);
|
||||
return sig.verify(signature);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the signature of data, using the given private key.
|
||||
*
|
||||
* @param data to be signed
|
||||
* @param privateKey to be used for signing
|
||||
* @return the signature
|
||||
*/
|
||||
public static byte[] getSignature(byte[] data, ch.dissem.bitmessage.entity.valueobject.PrivateKey privateKey) {
|
||||
try {
|
||||
ECParameterSpec spec = new ECParameterSpec(
|
||||
EC_CURVE_PARAMETERS.getCurve(),
|
||||
EC_CURVE_PARAMETERS.getG(),
|
||||
EC_CURVE_PARAMETERS.getN(),
|
||||
EC_CURVE_PARAMETERS.getH(),
|
||||
EC_CURVE_PARAMETERS.getSeed()
|
||||
);
|
||||
|
||||
BigInteger d = keyToBigInt(privateKey.getPrivateSigningKey());
|
||||
KeySpec keySpec = new ECPrivateKeySpec(d, spec);
|
||||
PrivateKey privKey = KeyFactory.getInstance("ECDSA", "BC").generatePrivate(keySpec);
|
||||
|
||||
Signature sig = Signature.getInstance("ECDSA", "BC");
|
||||
sig.initSign(privKey);
|
||||
sig.update(data);
|
||||
return sig.sign();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a random number of type long
|
||||
*/
|
||||
public static long randomNonce() {
|
||||
return RANDOM.nextLong();
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.ports.Security;
|
||||
|
||||
/**
|
||||
* Created by chris on 20.07.15.
|
||||
*/
|
||||
public class Singleton {
|
||||
private static Security security;
|
||||
|
||||
public static void initialize(Security security) {
|
||||
Singleton.security = security;
|
||||
}
|
||||
|
||||
public static Security security() {
|
||||
return security;
|
||||
}
|
||||
}
|
@ -21,6 +21,7 @@ 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.TestBase;
|
||||
import ch.dissem.bitmessage.utils.TestUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
@ -29,7 +30,7 @@ import java.io.IOException;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class DecryptionTest {
|
||||
public class DecryptionTest extends TestBase {
|
||||
@Test
|
||||
public void ensureV4BroadcastIsDecryptedCorrectly() throws IOException, DecryptionFailedException {
|
||||
BitmessageAddress address = new BitmessageAddress("BM-2D9Vc5rFxxR5vTi53T9gkLfemViHRMVLQZ");
|
||||
|
@ -24,20 +24,20 @@ 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.exception.DecryptionFailedException;
|
||||
import ch.dissem.bitmessage.utils.Security;
|
||||
import ch.dissem.bitmessage.utils.TestBase;
|
||||
import ch.dissem.bitmessage.utils.TestUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class EncryptionTest {
|
||||
public class EncryptionTest extends TestBase {
|
||||
@Test
|
||||
public void ensureDecryptedDataIsSameAsBeforeEncryption() throws IOException, DecryptionFailedException {
|
||||
GenericPayload before = new GenericPayload(0, 1, Security.randomBytes(100));
|
||||
GenericPayload before = new GenericPayload(0, 1, security().randomBytes(100));
|
||||
|
||||
PrivateKey privateKey = new PrivateKey(false, 1, 1000, 1000);
|
||||
CryptoBox cryptoBox = new CryptoBox(before, privateKey.getPubkey().getEncryptionKey());
|
||||
|
@ -25,6 +25,7 @@ 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.exception.DecryptionFailedException;
|
||||
import ch.dissem.bitmessage.utils.TestBase;
|
||||
import ch.dissem.bitmessage.utils.TestUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
@ -33,7 +34,7 @@ import java.util.Date;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class SignatureTest {
|
||||
public class SignatureTest extends TestBase {
|
||||
@Test
|
||||
public void ensureValidationWorks() throws IOException {
|
||||
ObjectMessage object = TestUtils.loadObjectMessage(3, "V3Pubkey.payload");
|
||||
|
@ -27,6 +27,7 @@ import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static ch.dissem.bitmessage.entity.payload.Pubkey.Feature.DOES_ACK;
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class BitmessageAddressTest {
|
||||
@ -102,7 +103,7 @@ public class BitmessageAddressTest {
|
||||
System.out.println("\n\n" + Strings.hex(privsigningkey) + "\n\n");
|
||||
|
||||
BitmessageAddress address = new BitmessageAddress(new PrivateKey(privsigningkey, privencryptionkey,
|
||||
Security.createPubkey(3, 1, privsigningkey, privencryptionkey, 320, 14000)));
|
||||
security().createPubkey(3, 1, privsigningkey, privencryptionkey, 320, 14000)));
|
||||
assertEquals(address_string, address.getAddress());
|
||||
}
|
||||
|
||||
@ -119,7 +120,7 @@ public class BitmessageAddressTest {
|
||||
if (bytes.length != 37)
|
||||
throw new IOException("Unknown format: 37 bytes expected, but secret " + walletImportFormat + " was " + bytes.length + " long");
|
||||
|
||||
byte[] hash = Security.doubleSha256(bytes, 33);
|
||||
byte[] hash = security().doubleSha256(bytes, 33);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (hash[i] != bytes[33 + i]) throw new IOException("Hash check failed for secret " + walletImportFormat);
|
||||
}
|
||||
@ -132,7 +133,7 @@ public class BitmessageAddressTest {
|
||||
byte[] privsigningkey = getSecret("5KMWqfCyJZGFgW6QrnPJ6L9Gatz25B51y7ErgqNr1nXUVbtZbdU");
|
||||
byte[] privencryptionkey = getSecret("5JXXWEuhHQEPk414SzEZk1PHDRi8kCuZd895J7EnKeQSahJPxGz");
|
||||
BitmessageAddress address = new BitmessageAddress(new PrivateKey(privsigningkey, privencryptionkey,
|
||||
Security.createPubkey(4, 1, privsigningkey, privencryptionkey, 320, 14000)));
|
||||
security().createPubkey(4, 1, privsigningkey, privencryptionkey, 320, 14000)));
|
||||
assertEquals("BM-2cV5f9EpzaYARxtoruSpa6pDoucSf9ZNke", address.getAddress());
|
||||
}
|
||||
|
||||
|
@ -17,14 +17,11 @@
|
||||
package ch.dissem.bitmessage.ports;
|
||||
|
||||
import ch.dissem.bitmessage.utils.Bytes;
|
||||
import ch.dissem.bitmessage.utils.Security;
|
||||
import org.junit.Test;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* Created by chris on 17.04.15.
|
||||
*/
|
||||
public class ProofOfWorkEngineTest {
|
||||
@Test
|
||||
public void testSimplePOWEngine() {
|
||||
@ -38,11 +35,11 @@ public class ProofOfWorkEngineTest {
|
||||
|
||||
private void testPOW(ProofOfWorkEngine engine) {
|
||||
long time = System.currentTimeMillis();
|
||||
byte[] initialHash = Security.sha512(new byte[]{1, 3, 6, 4});
|
||||
byte[] initialHash = security().sha512(new byte[]{1, 3, 6, 4});
|
||||
byte[] target = {0, 0, -1, -1, -1, -1, -1, -1};
|
||||
|
||||
byte[] nonce = engine.calculateNonce(initialHash, target);
|
||||
System.out.println("Calculating nonce took " + (System.currentTimeMillis() - time) + "ms");
|
||||
assertTrue(Bytes.lt(Security.doubleSha512(nonce, initialHash), target, 8));
|
||||
assertTrue(Bytes.lt(security().doubleSha512(nonce, initialHash), target, 8));
|
||||
}
|
||||
}
|
||||
|
@ -25,9 +25,6 @@ import java.util.Random;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Created by chris on 10.04.15.
|
||||
*/
|
||||
public class BytesTest {
|
||||
public static final Random rnd = new Random();
|
||||
|
||||
|
@ -22,9 +22,6 @@ import java.io.*;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Created by chris on 20.03.15.
|
||||
*/
|
||||
public class DecodeTest {
|
||||
@Test
|
||||
public void ensureDecodingWorks() throws Exception {
|
||||
|
@ -23,9 +23,6 @@ import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Created by chris on 13.03.15.
|
||||
*/
|
||||
public class EncodeTest {
|
||||
@Test
|
||||
public void testUint8() throws IOException {
|
||||
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* 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.security.bc.BouncySecurity;
|
||||
|
||||
/**
|
||||
* Created by chris on 20.07.15.
|
||||
*/
|
||||
public class TestBase {
|
||||
static {
|
||||
Singleton.initialize(new BouncySecurity());
|
||||
}
|
||||
}
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -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.4-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip
|
||||
|
@ -26,7 +26,6 @@ import ch.dissem.bitmessage.exception.NodeException;
|
||||
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 ch.dissem.bitmessage.utils.UnixTime;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -44,6 +43,7 @@ import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import static ch.dissem.bitmessage.networking.Connection.Mode.CLIENT;
|
||||
import static ch.dissem.bitmessage.networking.Connection.State.*;
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
import static ch.dissem.bitmessage.utils.UnixTime.MINUTE;
|
||||
|
||||
/**
|
||||
@ -275,7 +275,7 @@ public class Connection implements Runnable {
|
||||
ObjectMessage objectMessage = (ObjectMessage) messagePayload;
|
||||
try {
|
||||
LOG.debug("Received object " + objectMessage.getInventoryVector());
|
||||
Security.checkProofOfWork(objectMessage, ctx.getNetworkNonceTrialsPerByte(), ctx.getNetworkExtraBytes());
|
||||
security().checkProofOfWork(objectMessage, ctx.getNetworkNonceTrialsPerByte(), ctx.getNetworkExtraBytes());
|
||||
listener.receive(objectMessage);
|
||||
ctx.getInventory().storeObject(objectMessage);
|
||||
// offer object to some random nodes so it gets distributed throughout the network:
|
||||
|
@ -44,9 +44,9 @@ import static ch.dissem.bitmessage.utils.DebugUtils.inc;
|
||||
/**
|
||||
* Handles all the networky stuff.
|
||||
*/
|
||||
public class NetworkNode implements NetworkHandler, ContextHolder {
|
||||
public class DefaultNetworkHandler implements NetworkHandler, ContextHolder {
|
||||
public final static int NETWORK_MAGIC_NUMBER = 8;
|
||||
private final static Logger LOG = LoggerFactory.getLogger(NetworkNode.class);
|
||||
private final static Logger LOG = LoggerFactory.getLogger(DefaultNetworkHandler.class);
|
||||
private final ExecutorService pool;
|
||||
private final List<Connection> connections = new LinkedList<>();
|
||||
private InternalContext ctx;
|
||||
@ -56,7 +56,7 @@ public class NetworkNode implements NetworkHandler, ContextHolder {
|
||||
|
||||
private ConcurrentMap<InventoryVector, Long> requestedObjects = new ConcurrentHashMap<>();
|
||||
|
||||
public NetworkNode() {
|
||||
public DefaultNetworkHandler() {
|
||||
pool = Executors.newCachedThreadPool();
|
||||
}
|
||||
|
||||
@ -128,6 +128,11 @@ public class NetworkNode implements NetworkHandler, ContextHolder {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRunning() {
|
||||
return connectionManager != null && connectionManager.isAlive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
connectionManager.interrupt();
|
@ -24,16 +24,21 @@ import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Created by chris on 20.03.15.
|
||||
* FIXME: there really should be sensible tests for the network handler
|
||||
*/
|
||||
public class NetworkNodeTest {
|
||||
public class DefaultNetworkHandlerTest {
|
||||
private NetworkAddress localhost = new NetworkAddress.Builder().ipv4(127, 0, 0, 1).port(8444).build();
|
||||
|
||||
// void start(MessageListener listener);
|
||||
// void stop();
|
||||
// void offer(InventoryVector iv);
|
||||
// Property getNetworkStatus();
|
||||
|
||||
@Ignore
|
||||
@Test(expected = InterruptedException.class)
|
||||
public void testSendMessage() throws Exception {
|
||||
final Thread baseThread = Thread.currentThread();
|
||||
NetworkNode net = new NetworkNode();
|
||||
DefaultNetworkHandler net = new DefaultNetworkHandler();
|
||||
// net.setListener(localhost, new NetworkHandler.MessageListener() {
|
||||
// @Override
|
||||
// public void receive(ObjectPayload payload) {
|
@ -12,7 +12,9 @@ uploadArchives {
|
||||
|
||||
dependencies {
|
||||
compile project(':domain')
|
||||
compile 'com.h2database:h2:1.4.187'
|
||||
compile 'org.flywaydb:flyway-core:3.2.1'
|
||||
testCompile 'junit:junit:4.11'
|
||||
testCompile 'com.h2database:h2:1.4.187'
|
||||
testCompile 'org.mockito:mockito-core:1.10.19'
|
||||
testCompile project(':security-bc')
|
||||
}
|
@ -28,9 +28,9 @@ import java.sql.SQLException;
|
||||
*/
|
||||
public class JdbcConfig {
|
||||
protected final Flyway flyway;
|
||||
private final String dbUrl;
|
||||
private final String dbUser;
|
||||
private final String dbPassword;
|
||||
protected final String dbUrl;
|
||||
protected final String dbUser;
|
||||
protected final String dbPassword;
|
||||
|
||||
public JdbcConfig(String dbUrl, String dbUser, String dbPassword) {
|
||||
this.dbUrl = dbUrl;
|
||||
|
@ -18,14 +18,15 @@ package ch.dissem.bitmessage.repository;
|
||||
|
||||
import ch.dissem.bitmessage.entity.Streamable;
|
||||
import ch.dissem.bitmessage.entity.payload.ObjectType;
|
||||
import org.flywaydb.core.Flyway;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.sql.*;
|
||||
import java.sql.Blob;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.Strings.hex;
|
||||
|
||||
@ -81,8 +82,8 @@ abstract class JdbcHelper {
|
||||
if (data != null) {
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
data.write(os);
|
||||
ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
|
||||
ps.setBlob(parameterIndex, is);
|
||||
byte[] bytes = os.toByteArray();
|
||||
ps.setBinaryStream(parameterIndex, new ByteArrayInputStream(bytes), bytes.length);
|
||||
} else {
|
||||
ps.setBlob(parameterIndex, (Blob) null);
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ import java.util.List;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class JdbcAddressRepositoryTest {
|
||||
public class JdbcAddressRepositoryTest extends TestBase {
|
||||
public static final String CONTACT_A = "BM-2cW7cD5cDQJDNkE7ibmyTxfvGAmnPqa9Vt";
|
||||
public static final String CONTACT_B = "BM-2cTtkBnb4BUYDndTKun6D9PjtueP2h1bQj";
|
||||
public static final String CONTACT_C = "BM-2cV5f9EpzaYARxtoruSpa6pDoucSf9ZNke";
|
||||
|
@ -34,7 +34,7 @@ import static ch.dissem.bitmessage.utils.UnixTime.DAY;
|
||||
import static ch.dissem.bitmessage.utils.UnixTime.now;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class JdbcInventoryTest {
|
||||
public class JdbcInventoryTest extends TestBase {
|
||||
private TestJdbcConfig config;
|
||||
private Inventory inventory;
|
||||
|
||||
|
@ -25,7 +25,6 @@ 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;
|
||||
|
||||
@ -33,10 +32,11 @@ import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static ch.dissem.bitmessage.entity.Plaintext.Type.MSG;
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
public class JdbcMessageRepositoryTest {
|
||||
public class JdbcMessageRepositoryTest extends TestBase {
|
||||
private BitmessageAddress contactA;
|
||||
private BitmessageAddress contactB;
|
||||
private BitmessageAddress identity;
|
||||
@ -54,7 +54,11 @@ public class JdbcMessageRepositoryTest {
|
||||
config.reset();
|
||||
addressRepo = new JdbcAddressRepository(config);
|
||||
repo = new JdbcMessageRepository(config);
|
||||
new InternalContext(new BitmessageContext.Builder().addressRepo(addressRepo).messageRepo(repo));
|
||||
new InternalContext(new BitmessageContext.Builder()
|
||||
.security(security())
|
||||
.addressRepo(addressRepo)
|
||||
.messageRepo(repo)
|
||||
);
|
||||
|
||||
BitmessageAddress tmp = new BitmessageAddress(new PrivateKey(false, 1, 1000, 1000));
|
||||
contactA = new BitmessageAddress(tmp.getAddress());
|
||||
@ -120,7 +124,7 @@ public class JdbcMessageRepositoryTest {
|
||||
@Test
|
||||
public void testSave() throws Exception {
|
||||
Plaintext message = new Plaintext.Builder(MSG)
|
||||
.IV(new InventoryVector(Security.randomBytes(32)))
|
||||
.IV(new InventoryVector(security().randomBytes(32)))
|
||||
.from(identity)
|
||||
.to(contactA)
|
||||
.message("Subject", "Message")
|
||||
@ -143,7 +147,7 @@ public class JdbcMessageRepositoryTest {
|
||||
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)));
|
||||
message.setInventoryVector(new InventoryVector(security().randomBytes(32)));
|
||||
repo.save(message);
|
||||
|
||||
messages = repo.findMessages(Plaintext.Status.DRAFT, contactA);
|
||||
|
@ -27,7 +27,7 @@ import java.util.List;
|
||||
import static ch.dissem.bitmessage.utils.UnixTime.now;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class JdbcNodeRegistryTest {
|
||||
public class JdbcNodeRegistryTest extends TestBase {
|
||||
private TestJdbcConfig config;
|
||||
private NodeRegistry registry;
|
||||
|
||||
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* 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.repository;
|
||||
|
||||
import ch.dissem.bitmessage.InternalContext;
|
||||
import ch.dissem.bitmessage.ports.MultiThreadedPOWEngine;
|
||||
import ch.dissem.bitmessage.security.bc.BouncySecurity;
|
||||
import ch.dissem.bitmessage.utils.Singleton;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Created by chris on 20.07.15.
|
||||
*/
|
||||
public class TestBase {
|
||||
static {
|
||||
BouncySecurity security = new BouncySecurity();
|
||||
Singleton.initialize(security);
|
||||
InternalContext ctx = mock(InternalContext.class);
|
||||
when(ctx.getProofOfWorkEngine()).thenReturn(new MultiThreadedPOWEngine());
|
||||
security.setContext(ctx);
|
||||
}
|
||||
}
|
@ -17,7 +17,8 @@
|
||||
package ch.dissem.bitmessage.repository;
|
||||
|
||||
/**
|
||||
* Created by chris on 02.06.15.
|
||||
* JdbcConfig to be used for tests. Uses an in-memory database and adds a useful {@link #reset()} method resetting
|
||||
* the database.
|
||||
*/
|
||||
public class TestJdbcConfig extends JdbcConfig {
|
||||
public TestJdbcConfig() {
|
||||
|
18
security-bc/build.gradle
Normal file
18
security-bc/build.gradle
Normal file
@ -0,0 +1,18 @@
|
||||
uploadArchives {
|
||||
repositories {
|
||||
mavenDeployer {
|
||||
pom.project {
|
||||
name 'Jabit Spongy Security'
|
||||
artifactId = 'jabit-security-spongy'
|
||||
description 'The Security implementation using spongy castle (needed for Android)'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':domain')
|
||||
compile 'org.bouncycastle:bcprov-jdk15on:1.52'
|
||||
testCompile 'junit:junit:4.11'
|
||||
testCompile 'org.mockito:mockito-core:1.10.19'
|
||||
}
|
@ -0,0 +1,153 @@
|
||||
/*
|
||||
* 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.security.bc;
|
||||
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
||||
import ch.dissem.bitmessage.ports.AbstractSecurity;
|
||||
import org.bouncycastle.asn1.x9.X9ECParameters;
|
||||
import org.bouncycastle.crypto.BufferedBlockCipher;
|
||||
import org.bouncycastle.crypto.CipherParameters;
|
||||
import org.bouncycastle.crypto.InvalidCipherTextException;
|
||||
import org.bouncycastle.crypto.ec.CustomNamedCurves;
|
||||
import org.bouncycastle.crypto.engines.AESEngine;
|
||||
import org.bouncycastle.crypto.modes.CBCBlockCipher;
|
||||
import org.bouncycastle.crypto.paddings.PKCS7Padding;
|
||||
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
|
||||
import org.bouncycastle.crypto.params.KeyParameter;
|
||||
import org.bouncycastle.crypto.params.ParametersWithIV;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.jce.spec.ECParameterSpec;
|
||||
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
|
||||
import org.bouncycastle.jce.spec.ECPublicKeySpec;
|
||||
import org.bouncycastle.math.ec.ECPoint;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.spec.KeySpec;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* As Spongycastle can't be used on the Oracle JVM, and Bouncycastle doesn't work properly on Android (thanks, Google),
|
||||
* this is the Bouncycastle implementation.
|
||||
*/
|
||||
public class BouncySecurity extends AbstractSecurity {
|
||||
private static final X9ECParameters EC_CURVE_PARAMETERS = CustomNamedCurves.getByName("secp256k1");
|
||||
|
||||
static {
|
||||
java.security.Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
|
||||
public BouncySecurity() {
|
||||
super("BC");
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] crypt(boolean encrypt, byte[] data, byte[] key_e, byte[] initializationVector) {
|
||||
BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()), new PKCS7Padding());
|
||||
|
||||
CipherParameters params = new ParametersWithIV(new KeyParameter(key_e), initializationVector);
|
||||
|
||||
cipher.init(encrypt, params);
|
||||
|
||||
byte[] buffer = new byte[cipher.getOutputSize(data.length)];
|
||||
int length = cipher.processBytes(data, 0, data.length, buffer, 0);
|
||||
try {
|
||||
length += cipher.doFinal(buffer, length);
|
||||
} catch (InvalidCipherTextException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
if (length < buffer.length) {
|
||||
return Arrays.copyOfRange(buffer, 0, length);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] createPublicKey(byte[] privateKey) {
|
||||
return EC_CURVE_PARAMETERS.getG().multiply(keyToBigInt(privateKey)).normalize().getEncoded(false);
|
||||
}
|
||||
|
||||
private ECPoint keyToPoint(byte[] publicKey) {
|
||||
BigInteger x = new BigInteger(1, Arrays.copyOfRange(publicKey, 1, 33));
|
||||
BigInteger y = new BigInteger(1, Arrays.copyOfRange(publicKey, 33, 65));
|
||||
return EC_CURVE_PARAMETERS.getCurve().createPoint(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSignatureValid(byte[] data, byte[] signature, Pubkey pubkey) {
|
||||
try {
|
||||
ECParameterSpec spec = new ECParameterSpec(
|
||||
EC_CURVE_PARAMETERS.getCurve(),
|
||||
EC_CURVE_PARAMETERS.getG(),
|
||||
EC_CURVE_PARAMETERS.getN(),
|
||||
EC_CURVE_PARAMETERS.getH(),
|
||||
EC_CURVE_PARAMETERS.getSeed()
|
||||
);
|
||||
|
||||
ECPoint Q = keyToPoint(pubkey.getSigningKey());
|
||||
KeySpec keySpec = new ECPublicKeySpec(Q, spec);
|
||||
PublicKey publicKey = KeyFactory.getInstance("ECDSA", "BC").generatePublic(keySpec);
|
||||
|
||||
Signature sig = Signature.getInstance("ECDSA", "BC");
|
||||
sig.initVerify(publicKey);
|
||||
sig.update(data);
|
||||
return sig.verify(signature);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getSignature(byte[] data, PrivateKey privateKey) {
|
||||
try {
|
||||
ECParameterSpec spec = new ECParameterSpec(
|
||||
EC_CURVE_PARAMETERS.getCurve(),
|
||||
EC_CURVE_PARAMETERS.getG(),
|
||||
EC_CURVE_PARAMETERS.getN(),
|
||||
EC_CURVE_PARAMETERS.getH(),
|
||||
EC_CURVE_PARAMETERS.getSeed()
|
||||
);
|
||||
|
||||
BigInteger d = keyToBigInt(privateKey.getPrivateSigningKey());
|
||||
KeySpec keySpec = new ECPrivateKeySpec(d, spec);
|
||||
java.security.PrivateKey privKey = KeyFactory.getInstance("ECDSA", "BC").generatePrivate(keySpec);
|
||||
|
||||
Signature sig = Signature.getInstance("ECDSA", "BC");
|
||||
sig.initSign(privKey);
|
||||
sig.update(data);
|
||||
return sig.sign();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] multiply(byte[] K, byte[] r) {
|
||||
return keyToPoint(K).multiply(keyToBigInt(r)).normalize().getEncoded(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] createPoint(byte[] x, byte[] y) {
|
||||
return EC_CURVE_PARAMETERS.getCurve().createPoint(
|
||||
new BigInteger(1, x),
|
||||
new BigInteger(1, y)
|
||||
).getEncoded(false);
|
||||
}
|
||||
}
|
@ -1,36 +1,25 @@
|
||||
/*
|
||||
* 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;
|
||||
package ch.dissem.bitmessage.security;
|
||||
|
||||
import ch.dissem.bitmessage.InternalContext;
|
||||
import ch.dissem.bitmessage.entity.ObjectMessage;
|
||||
import ch.dissem.bitmessage.entity.payload.GenericPayload;
|
||||
import ch.dissem.bitmessage.ports.MultiThreadedPOWEngine;
|
||||
import ch.dissem.bitmessage.security.bc.BouncySecurity;
|
||||
import ch.dissem.bitmessage.utils.Singleton;
|
||||
import ch.dissem.bitmessage.utils.UnixTime;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyPairGenerator;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.UnixTime.DAY;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Created by chris on 10.04.15.
|
||||
* Created by chris on 19.07.15.
|
||||
*/
|
||||
public class SecurityTest {
|
||||
public static final byte[] TEST_VALUE = "teststring".getBytes();
|
||||
@ -42,29 +31,39 @@ public class SecurityTest {
|
||||
public static final byte[] TEST_RIPEMD160 = DatatypeConverter.parseHexBinary(""
|
||||
+ "cd566972b5e50104011a92b59fa8e0b1234851ae");
|
||||
|
||||
private static BouncySecurity security;
|
||||
|
||||
public SecurityTest() {
|
||||
security = new BouncySecurity();
|
||||
Singleton.initialize(security);
|
||||
InternalContext ctx = mock(InternalContext.class);
|
||||
when(ctx.getProofOfWorkEngine()).thenReturn(new MultiThreadedPOWEngine());
|
||||
security.setContext(ctx);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRipemd160() {
|
||||
assertArrayEquals(TEST_RIPEMD160, Security.ripemd160(TEST_VALUE));
|
||||
assertArrayEquals(TEST_RIPEMD160, security.ripemd160(TEST_VALUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSha1() {
|
||||
assertArrayEquals(TEST_SHA1, Security.sha1(TEST_VALUE));
|
||||
assertArrayEquals(TEST_SHA1, security.sha1(TEST_VALUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSha512() {
|
||||
assertArrayEquals(TEST_SHA512, Security.sha512(TEST_VALUE));
|
||||
assertArrayEquals(TEST_SHA512, security.sha512(TEST_VALUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChaining() {
|
||||
assertArrayEquals(TEST_SHA512, Security.sha512("test".getBytes(), "string".getBytes()));
|
||||
assertArrayEquals(TEST_SHA512, security.sha512("test".getBytes(), "string".getBytes()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDoubleHash() {
|
||||
assertArrayEquals(Security.sha512(TEST_SHA512), Security.doubleSha512(TEST_VALUE));
|
||||
assertArrayEquals(security.sha512(TEST_SHA512), security.doubleSha512(TEST_VALUE));
|
||||
}
|
||||
|
||||
@Test(expected = IOException.class)
|
||||
@ -75,7 +74,7 @@ public class SecurityTest {
|
||||
.objectType(0)
|
||||
.payload(GenericPayload.read(0, new ByteArrayInputStream(new byte[0]), 1, 0))
|
||||
.build();
|
||||
Security.checkProofOfWork(objectMessage, 1000, 1000);
|
||||
security.checkProofOfWork(objectMessage, 1000, 1000);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -86,14 +85,7 @@ public class SecurityTest {
|
||||
.objectType(0)
|
||||
.payload(GenericPayload.read(0, new ByteArrayInputStream(new byte[0]), 1, 0))
|
||||
.build();
|
||||
Security.doProofOfWork(objectMessage, new MultiThreadedPOWEngine(), 1000, 1000);
|
||||
Security.checkProofOfWork(objectMessage, 1000, 1000);
|
||||
security.doProofOfWork(objectMessage, 1000, 1000);
|
||||
security.checkProofOfWork(objectMessage, 1000, 1000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testECIES() throws Exception {
|
||||
KeyPairGenerator kpg = KeyPairGenerator.getInstance("ECIES", "BC");
|
||||
// kpg.initialize();
|
||||
kpg.generateKeyPair();
|
||||
}
|
||||
}
|
||||
}
|
17
security-sc/build.gradle
Normal file
17
security-sc/build.gradle
Normal file
@ -0,0 +1,17 @@
|
||||
uploadArchives {
|
||||
repositories {
|
||||
mavenDeployer {
|
||||
pom.project {
|
||||
name 'Jabit Spongy Security'
|
||||
artifactId = 'jabit-security-spongy'
|
||||
description 'The Security implementation using spongy castle (needed for Android)'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':domain')
|
||||
compile 'com.madgag.spongycastle:prov:1.52.0.0'
|
||||
testCompile 'junit:junit:4.11'
|
||||
}
|
@ -0,0 +1,153 @@
|
||||
/*
|
||||
* 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.security.sc;
|
||||
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
||||
import ch.dissem.bitmessage.entity.valueobject.PrivateKey;
|
||||
import ch.dissem.bitmessage.ports.AbstractSecurity;
|
||||
import org.spongycastle.asn1.x9.X9ECParameters;
|
||||
import org.spongycastle.crypto.BufferedBlockCipher;
|
||||
import org.spongycastle.crypto.CipherParameters;
|
||||
import org.spongycastle.crypto.InvalidCipherTextException;
|
||||
import org.spongycastle.crypto.ec.CustomNamedCurves;
|
||||
import org.spongycastle.crypto.engines.AESEngine;
|
||||
import org.spongycastle.crypto.modes.CBCBlockCipher;
|
||||
import org.spongycastle.crypto.paddings.PKCS7Padding;
|
||||
import org.spongycastle.crypto.paddings.PaddedBufferedBlockCipher;
|
||||
import org.spongycastle.crypto.params.KeyParameter;
|
||||
import org.spongycastle.crypto.params.ParametersWithIV;
|
||||
import org.spongycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.spongycastle.jce.spec.ECParameterSpec;
|
||||
import org.spongycastle.jce.spec.ECPrivateKeySpec;
|
||||
import org.spongycastle.jce.spec.ECPublicKeySpec;
|
||||
import org.spongycastle.math.ec.ECPoint;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.spec.KeySpec;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* As Spongycastle can't be used on the Oracle JVM, and Bouncycastle doesn't work properly on Android (thanks, Google),
|
||||
* this is the Spongycastle implementation.
|
||||
*/
|
||||
public class SpongySecurity extends AbstractSecurity {
|
||||
private static final X9ECParameters EC_CURVE_PARAMETERS = CustomNamedCurves.getByName("secp256k1");
|
||||
|
||||
static {
|
||||
java.security.Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
|
||||
public SpongySecurity() {
|
||||
super("SC");
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] crypt(boolean encrypt, byte[] data, byte[] key_e, byte[] initializationVector) {
|
||||
BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()), new PKCS7Padding());
|
||||
|
||||
CipherParameters params = new ParametersWithIV(new KeyParameter(key_e), initializationVector);
|
||||
|
||||
cipher.init(encrypt, params);
|
||||
|
||||
byte[] buffer = new byte[cipher.getOutputSize(data.length)];
|
||||
int length = cipher.processBytes(data, 0, data.length, buffer, 0);
|
||||
try {
|
||||
length += cipher.doFinal(buffer, length);
|
||||
} catch (InvalidCipherTextException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
if (length < buffer.length) {
|
||||
return Arrays.copyOfRange(buffer, 0, length);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] createPublicKey(byte[] privateKey) {
|
||||
return EC_CURVE_PARAMETERS.getG().multiply(keyToBigInt(privateKey)).normalize().getEncoded(false);
|
||||
}
|
||||
|
||||
private ECPoint keyToPoint(byte[] publicKey) {
|
||||
BigInteger x = new BigInteger(1, Arrays.copyOfRange(publicKey, 1, 33));
|
||||
BigInteger y = new BigInteger(1, Arrays.copyOfRange(publicKey, 33, 65));
|
||||
return EC_CURVE_PARAMETERS.getCurve().createPoint(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSignatureValid(byte[] data, byte[] signature, Pubkey pubkey) {
|
||||
try {
|
||||
ECParameterSpec spec = new ECParameterSpec(
|
||||
EC_CURVE_PARAMETERS.getCurve(),
|
||||
EC_CURVE_PARAMETERS.getG(),
|
||||
EC_CURVE_PARAMETERS.getN(),
|
||||
EC_CURVE_PARAMETERS.getH(),
|
||||
EC_CURVE_PARAMETERS.getSeed()
|
||||
);
|
||||
|
||||
ECPoint Q = keyToPoint(pubkey.getSigningKey());
|
||||
KeySpec keySpec = new ECPublicKeySpec(Q, spec);
|
||||
PublicKey publicKey = KeyFactory.getInstance("ECDSA", "SC").generatePublic(keySpec);
|
||||
|
||||
Signature sig = Signature.getInstance("ECDSA", "SC");
|
||||
sig.initVerify(publicKey);
|
||||
sig.update(data);
|
||||
return sig.verify(signature);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getSignature(byte[] data, PrivateKey privateKey) {
|
||||
try {
|
||||
ECParameterSpec spec = new ECParameterSpec(
|
||||
EC_CURVE_PARAMETERS.getCurve(),
|
||||
EC_CURVE_PARAMETERS.getG(),
|
||||
EC_CURVE_PARAMETERS.getN(),
|
||||
EC_CURVE_PARAMETERS.getH(),
|
||||
EC_CURVE_PARAMETERS.getSeed()
|
||||
);
|
||||
|
||||
BigInteger d = keyToBigInt(privateKey.getPrivateSigningKey());
|
||||
KeySpec keySpec = new ECPrivateKeySpec(d, spec);
|
||||
java.security.PrivateKey privKey = KeyFactory.getInstance("ECDSA", "SC").generatePrivate(keySpec);
|
||||
|
||||
Signature sig = Signature.getInstance("ECDSA", "SC");
|
||||
sig.initSign(privKey);
|
||||
sig.update(data);
|
||||
return sig.sign();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] multiply(byte[] K, byte[] r) {
|
||||
return keyToPoint(K).multiply(keyToBigInt(r)).normalize().getEncoded(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] createPoint(byte[] x, byte[] y) {
|
||||
return EC_CURVE_PARAMETERS.getCurve().createPoint(
|
||||
new BigInteger(1, x),
|
||||
new BigInteger(1, y)
|
||||
).getEncoded(false);
|
||||
}
|
||||
}
|
@ -9,3 +9,7 @@ include 'repositories'
|
||||
include 'demo'
|
||||
|
||||
include 'wif'
|
||||
|
||||
include 'security-sc'
|
||||
|
||||
include 'security-bc'
|
||||
|
@ -15,4 +15,5 @@ dependencies {
|
||||
compile 'org.ini4j:ini4j:0.5.4'
|
||||
testCompile 'junit:junit:4.11'
|
||||
testCompile 'org.mockito:mockito-core:1.10.19'
|
||||
testCompile project(':security-bc')
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ package ch.dissem.bitmessage.wif;
|
||||
import ch.dissem.bitmessage.BitmessageContext;
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.utils.Base58;
|
||||
import ch.dissem.bitmessage.utils.Security;
|
||||
import org.ini4j.Ini;
|
||||
import org.ini4j.Profile;
|
||||
|
||||
@ -27,6 +26,7 @@ import java.io.*;
|
||||
import java.util.Collection;
|
||||
|
||||
import static ch.dissem.bitmessage.entity.valueobject.PrivateKey.PRIVATE_KEY_SIZE;
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
|
||||
/**
|
||||
* @author Christian Basler
|
||||
@ -73,7 +73,7 @@ public class WifExporter {
|
||||
byte[] result = new byte[37];
|
||||
result[0] = (byte) 0x80;
|
||||
System.arraycopy(privateKey, 0, result, 1, PRIVATE_KEY_SIZE);
|
||||
byte[] hash = Security.doubleSha256(result, PRIVATE_KEY_SIZE + 1);
|
||||
byte[] hash = security().doubleSha256(result, PRIVATE_KEY_SIZE + 1);
|
||||
System.arraycopy(hash, 0, result, PRIVATE_KEY_SIZE + 1, 4);
|
||||
return Base58.encode(result);
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.entity.payload.Pubkey;
|
||||
import ch.dissem.bitmessage.factory.Factory;
|
||||
import ch.dissem.bitmessage.utils.Base58;
|
||||
import ch.dissem.bitmessage.utils.Security;
|
||||
import org.ini4j.Ini;
|
||||
import org.ini4j.Profile;
|
||||
import org.slf4j.Logger;
|
||||
@ -34,6 +33,8 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import static ch.dissem.bitmessage.utils.Singleton.security;
|
||||
|
||||
/**
|
||||
* @author Christian Basler
|
||||
*/
|
||||
@ -84,7 +85,7 @@ public class WifImporter {
|
||||
if (bytes.length != 37)
|
||||
throw new IOException("Unknown format: 37 bytes expected, but secret " + walletImportFormat + " was " + bytes.length + " long");
|
||||
|
||||
byte[] hash = Security.doubleSha256(bytes, 33);
|
||||
byte[] hash = security().doubleSha256(bytes, 33);
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (hash[i] != bytes[33 + i]) throw new IOException("Hash check failed for secret " + walletImportFormat);
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ package ch.dissem.bitmessage.wif;
|
||||
|
||||
import ch.dissem.bitmessage.BitmessageContext;
|
||||
import ch.dissem.bitmessage.ports.*;
|
||||
import ch.dissem.bitmessage.security.bc.BouncySecurity;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
@ -34,6 +35,7 @@ public class WifExporterTest {
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
ctx = new BitmessageContext.Builder()
|
||||
.security(new BouncySecurity())
|
||||
.networkHandler(mock(NetworkHandler.class))
|
||||
.inventory(mock(Inventory.class))
|
||||
.messageRepo(mock(MessageRepository.class))
|
||||
|
@ -19,6 +19,7 @@ package ch.dissem.bitmessage.wif;
|
||||
import ch.dissem.bitmessage.BitmessageContext;
|
||||
import ch.dissem.bitmessage.entity.BitmessageAddress;
|
||||
import ch.dissem.bitmessage.ports.*;
|
||||
import ch.dissem.bitmessage.security.bc.BouncySecurity;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
@ -37,6 +38,7 @@ public class WifImporterTest {
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
ctx = new BitmessageContext.Builder()
|
||||
.security(new BouncySecurity())
|
||||
.networkHandler(mock(NetworkHandler.class))
|
||||
.inventory(mock(Inventory.class))
|
||||
.messageRepo(mock(MessageRepository.class))
|
||||
|
Loading…
Reference in New Issue
Block a user