From b8546e28afc615fb342e64f3a4c3689a6f4254bd Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Wed, 5 Aug 2015 19:52:18 +0200 Subject: [PATCH] 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) --- .../dissem/bitmessage/demo/Application.java | 4 +- .../java/ch/dissem/bitmessage/demo/Main.java | 4 +- domain/build.gradle | 7 +- .../dissem/bitmessage/BitmessageContext.java | 141 ++++--- .../ch/dissem/bitmessage/InternalContext.java | 48 ++- .../bitmessage/entity/BitmessageAddress.java | 23 +- .../dissem/bitmessage/entity/Encrypted.java | 1 + .../bitmessage/entity/NetworkMessage.java | 4 +- .../bitmessage/entity/ObjectMessage.java | 12 +- .../dissem/bitmessage/entity/Plaintext.java | 3 +- .../dissem/bitmessage/entity/Streamable.java | 2 + .../bitmessage/entity/payload/Broadcast.java | 5 +- .../bitmessage/entity/payload/CryptoBox.java | 78 +--- .../bitmessage/entity/payload/Pubkey.java | 7 +- .../entity/payload/V4Broadcast.java | 1 + .../entity/valueobject/InventoryVector.java | 3 +- .../entity/valueobject/PrivateKey.java | 24 +- .../ch/dissem/bitmessage/factory/Factory.java | 18 +- .../bitmessage/factory/V3MessageFactory.java | 5 +- .../bitmessage/ports/AbstractSecurity.java | 175 +++++++++ .../bitmessage/ports/NetworkHandler.java | 2 + .../ch/dissem/bitmessage/ports/Security.java | 206 ++++++++++ .../bitmessage/ports/SimplePOWEngine.java | 2 +- .../ch/dissem/bitmessage/utils/Points.java | 32 ++ .../ch/dissem/bitmessage/utils/Property.java | 7 +- .../ch/dissem/bitmessage/utils/Security.java | 365 ------------------ .../ch/dissem/bitmessage/utils/Singleton.java | 34 ++ .../ch/dissem/bitmessage/DecryptionTest.java | 3 +- .../ch/dissem/bitmessage/EncryptionTest.java | 8 +- .../ch/dissem/bitmessage/SignatureTest.java | 3 +- .../entity/BitmessageAddressTest.java | 7 +- .../ports/ProofOfWorkEngineTest.java | 9 +- .../ch/dissem/bitmessage/utils/BytesTest.java | 3 - .../dissem/bitmessage/utils/DecodeTest.java | 3 - .../dissem/bitmessage/utils/EncodeTest.java | 3 - .../ch/dissem/bitmessage/utils/TestBase.java | 28 ++ gradle/wrapper/gradle-wrapper.properties | 2 +- .../bitmessage/networking/Connection.java | 4 +- ...rkNode.java => DefaultNetworkHandler.java} | 11 +- ...st.java => DefaultNetworkHandlerTest.java} | 11 +- repositories/build.gradle | 4 +- .../bitmessage/repository/JdbcConfig.java | 6 +- .../bitmessage/repository/JdbcHelper.java | 9 +- .../repository/JdbcAddressRepositoryTest.java | 2 +- .../repository/JdbcInventoryTest.java | 2 +- .../repository/JdbcMessageRepositoryTest.java | 14 +- .../repository/JdbcNodeRegistryTest.java | 2 +- .../bitmessage/repository/TestBase.java | 38 ++ .../bitmessage/repository/TestJdbcConfig.java | 3 +- security-bc/build.gradle | 18 + .../security/bc/BouncySecurity.java | 153 ++++++++ .../bitmessage/security}/SecurityTest.java | 62 ++- security-sc/build.gradle | 17 + .../security/sc/SpongySecurity.java | 153 ++++++++ settings.gradle | 4 + wif/build.gradle | 1 + .../ch/dissem/bitmessage/wif/WifExporter.java | 4 +- .../ch/dissem/bitmessage/wif/WifImporter.java | 5 +- .../bitmessage/wif/WifExporterTest.java | 2 + .../bitmessage/wif/WifImporterTest.java | 2 + 60 files changed, 1168 insertions(+), 641 deletions(-) create mode 100644 domain/src/main/java/ch/dissem/bitmessage/ports/AbstractSecurity.java create mode 100644 domain/src/main/java/ch/dissem/bitmessage/ports/Security.java create mode 100644 domain/src/main/java/ch/dissem/bitmessage/utils/Points.java delete mode 100644 domain/src/main/java/ch/dissem/bitmessage/utils/Security.java create mode 100644 domain/src/main/java/ch/dissem/bitmessage/utils/Singleton.java create mode 100644 domain/src/test/java/ch/dissem/bitmessage/utils/TestBase.java rename networking/src/main/java/ch/dissem/bitmessage/networking/{NetworkNode.java => DefaultNetworkHandler.java} (96%) rename networking/src/test/java/ch/dissem/bitmessage/networking/{NetworkNodeTest.java => DefaultNetworkHandlerTest.java} (86%) create mode 100644 repositories/src/test/java/ch/dissem/bitmessage/repository/TestBase.java create mode 100644 security-bc/build.gradle create mode 100644 security-bc/src/main/java/ch/dissem/bitmessage/security/bc/BouncySecurity.java rename {domain/src/test/java/ch/dissem/bitmessage/utils => security-bc/src/test/java/ch/dissem/bitmessage/security}/SecurityTest.java (60%) create mode 100644 security-sc/build.gradle create mode 100644 security-sc/src/main/java/ch/dissem/bitmessage/security/sc/SpongySecurity.java diff --git a/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java b/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java index 0e03880..416e7b7 100644 --- a/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java +++ b/demo/src/main/java/ch/dissem/bitmessage/demo/Application.java @@ -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(); diff --git a/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java b/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java index 4ba8fa7..7f934a3 100644 --- a/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java +++ b/demo/src/main/java/ch/dissem/bitmessage/demo/Main.java @@ -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(); diff --git a/domain/build.gradle b/domain/build.gradle index eb83499..6bcbe6d 100644 --- a/domain/build.gradle +++ b/domain/build.gradle @@ -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' -} \ No newline at end of file + testCompile 'junit:junit:4.11' + testCompile 'org.mockito:mockito-core:1.10.19' + testCompile project(':security-bc') +} diff --git a/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java index 7878ada..d7c04b5 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java +++ b/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java @@ -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; diff --git a/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java b/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java index 8ac64e0..03fef80 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java +++ b/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java @@ -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); } diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java b/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java index eb348a2..0441e6e 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java @@ -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); } diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/Encrypted.java b/domain/src/main/java/ch/dissem/bitmessage/entity/Encrypted.java index 8b15371..9eaa2ab 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/Encrypted.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/Encrypted.java @@ -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; diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.java b/domain/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.java index c10f942..8790d3a 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/NetworkMessage.java @@ -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]}; } diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java b/domain/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java index 0ef3340..128084e 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/ObjectMessage.java @@ -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 diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java b/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java index 28cebfa..e73849f 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/Plaintext.java @@ -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; diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/Streamable.java b/domain/src/main/java/ch/dissem/bitmessage/entity/Streamable.java index 9ee4cf9..0601a44 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/Streamable.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/Streamable.java @@ -16,6 +16,8 @@ package ch.dissem.bitmessage.entity; +import ch.dissem.bitmessage.ports.Security; + import java.io.IOException; import java.io.OutputStream; diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java index 2491d85..bf5ff7f 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Broadcast.java @@ -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 diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java index 2018373..7f994d1 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/CryptoBox.java @@ -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 https://bitmessage.org/wiki/Encryption#Decryption */ - 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); diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java index 2bdcbb6..1243b32 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/Pubkey.java @@ -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() { diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java index 39127a8..03364d2 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/payload/V4Broadcast.java @@ -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; diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.java b/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.java index 366d26b..f87dd13 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/InventoryVector.java @@ -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 */ diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java b/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java index f0956a6..d07c859 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/valueobject/PrivateKey.java @@ -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); diff --git a/domain/src/main/java/ch/dissem/bitmessage/factory/Factory.java b/domain/src/main/java/ch/dissem/bitmessage/factory/Factory.java index a144bb6..5584ec7 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/factory/Factory.java +++ b/domain/src/main/java/ch/dissem/bitmessage/factory/Factory.java @@ -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) { diff --git a/domain/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java b/domain/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java index 57d240d..7e48746 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java +++ b/domain/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java @@ -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; diff --git a/domain/src/main/java/ch/dissem/bitmessage/ports/AbstractSecurity.java b/domain/src/main/java/ch/dissem/bitmessage/ports/AbstractSecurity.java new file mode 100644 index 0000000..00ed1f2 --- /dev/null +++ b/domain/src/main/java/ch/dissem/bitmessage/ports/AbstractSecurity.java @@ -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(); + } +} diff --git a/domain/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java b/domain/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java index ed2f38a..4b73629 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java +++ b/domain/src/main/java/ch/dissem/bitmessage/ports/NetworkHandler.java @@ -34,6 +34,8 @@ public interface NetworkHandler { Property getNetworkStatus(); + boolean isRunning(); + interface MessageListener { void receive(ObjectMessage object) throws IOException; } diff --git a/domain/src/main/java/ch/dissem/bitmessage/ports/Security.java b/domain/src/main/java/ch/dissem/bitmessage/ports/Security.java new file mode 100644 index 0000000..83ad43d --- /dev/null +++ b/domain/src/main/java/ch/dissem/bitmessage/ports/Security.java @@ -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. + *

+ * 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. + *

+ * + * @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. + *

+ * 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. + *

+ * + * @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. + *

+ * 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. + *

+ * + * @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. + *

+ * 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. + *

+ * + * @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); +} diff --git a/domain/src/main/java/ch/dissem/bitmessage/ports/SimplePOWEngine.java b/domain/src/main/java/ch/dissem/bitmessage/ports/SimplePOWEngine.java index 0d9d392..211df53 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/ports/SimplePOWEngine.java +++ b/domain/src/main/java/ch/dissem/bitmessage/ports/SimplePOWEngine.java @@ -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 diff --git a/domain/src/main/java/ch/dissem/bitmessage/utils/Points.java b/domain/src/main/java/ch/dissem/bitmessage/utils/Points.java new file mode 100644 index 0000000..937fe20 --- /dev/null +++ b/domain/src/main/java/ch/dissem/bitmessage/utils/Points.java @@ -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); + } +} diff --git a/domain/src/main/java/ch/dissem/bitmessage/utils/Property.java b/domain/src/main/java/ch/dissem/bitmessage/utils/Property.java index 1030513..6fa0ec4 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/utils/Property.java +++ b/domain/src/main/java/ch/dissem/bitmessage/utils/Property.java @@ -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. + *

+ * If you need a real JSON representation, please add a method toJson(). + *

*/ public class Property { private String name; diff --git a/domain/src/main/java/ch/dissem/bitmessage/utils/Security.java b/domain/src/main/java/ch/dissem/bitmessage/utils/Security.java deleted file mode 100644 index 25e248e..0000000 --- a/domain/src/main/java/ch/dissem/bitmessage/utils/Security.java +++ /dev/null @@ -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. - *

- * 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. - *

- * - * @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. - *

- * 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. - *

- * - * @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. - *

- * 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. - *

- * - * @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. - *

- * 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. - *

- * - * @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(); - } -} diff --git a/domain/src/main/java/ch/dissem/bitmessage/utils/Singleton.java b/domain/src/main/java/ch/dissem/bitmessage/utils/Singleton.java new file mode 100644 index 0000000..169db77 --- /dev/null +++ b/domain/src/main/java/ch/dissem/bitmessage/utils/Singleton.java @@ -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; + } +} diff --git a/domain/src/test/java/ch/dissem/bitmessage/DecryptionTest.java b/domain/src/test/java/ch/dissem/bitmessage/DecryptionTest.java index ebb3707..21f9506 100644 --- a/domain/src/test/java/ch/dissem/bitmessage/DecryptionTest.java +++ b/domain/src/test/java/ch/dissem/bitmessage/DecryptionTest.java @@ -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"); diff --git a/domain/src/test/java/ch/dissem/bitmessage/EncryptionTest.java b/domain/src/test/java/ch/dissem/bitmessage/EncryptionTest.java index f0f8ddb..9a24842 100644 --- a/domain/src/test/java/ch/dissem/bitmessage/EncryptionTest.java +++ b/domain/src/test/java/ch/dissem/bitmessage/EncryptionTest.java @@ -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()); diff --git a/domain/src/test/java/ch/dissem/bitmessage/SignatureTest.java b/domain/src/test/java/ch/dissem/bitmessage/SignatureTest.java index dc2eb85..71b7d2a 100644 --- a/domain/src/test/java/ch/dissem/bitmessage/SignatureTest.java +++ b/domain/src/test/java/ch/dissem/bitmessage/SignatureTest.java @@ -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"); diff --git a/domain/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java b/domain/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java index 85b8098..e1fcc7f 100644 --- a/domain/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java +++ b/domain/src/test/java/ch/dissem/bitmessage/entity/BitmessageAddressTest.java @@ -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()); } diff --git a/domain/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.java b/domain/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.java index aed5722..ca17a23 100644 --- a/domain/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.java +++ b/domain/src/test/java/ch/dissem/bitmessage/ports/ProofOfWorkEngineTest.java @@ -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)); } } diff --git a/domain/src/test/java/ch/dissem/bitmessage/utils/BytesTest.java b/domain/src/test/java/ch/dissem/bitmessage/utils/BytesTest.java index 7c83724..52eb106 100644 --- a/domain/src/test/java/ch/dissem/bitmessage/utils/BytesTest.java +++ b/domain/src/test/java/ch/dissem/bitmessage/utils/BytesTest.java @@ -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(); diff --git a/domain/src/test/java/ch/dissem/bitmessage/utils/DecodeTest.java b/domain/src/test/java/ch/dissem/bitmessage/utils/DecodeTest.java index 8c18ee7..60d882f 100644 --- a/domain/src/test/java/ch/dissem/bitmessage/utils/DecodeTest.java +++ b/domain/src/test/java/ch/dissem/bitmessage/utils/DecodeTest.java @@ -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 { diff --git a/domain/src/test/java/ch/dissem/bitmessage/utils/EncodeTest.java b/domain/src/test/java/ch/dissem/bitmessage/utils/EncodeTest.java index 3489112..aaba5bf 100644 --- a/domain/src/test/java/ch/dissem/bitmessage/utils/EncodeTest.java +++ b/domain/src/test/java/ch/dissem/bitmessage/utils/EncodeTest.java @@ -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 { diff --git a/domain/src/test/java/ch/dissem/bitmessage/utils/TestBase.java b/domain/src/test/java/ch/dissem/bitmessage/utils/TestBase.java new file mode 100644 index 0000000..1dd1335 --- /dev/null +++ b/domain/src/test/java/ch/dissem/bitmessage/utils/TestBase.java @@ -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()); + } +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index f2e98f0..66e6c70 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java b/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java index 0eb0f09..d8054ff 100644 --- a/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java +++ b/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java @@ -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: diff --git a/networking/src/main/java/ch/dissem/bitmessage/networking/NetworkNode.java b/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java similarity index 96% rename from networking/src/main/java/ch/dissem/bitmessage/networking/NetworkNode.java rename to networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java index f27f7ff..558d097 100644 --- a/networking/src/main/java/ch/dissem/bitmessage/networking/NetworkNode.java +++ b/networking/src/main/java/ch/dissem/bitmessage/networking/DefaultNetworkHandler.java @@ -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 connections = new LinkedList<>(); private InternalContext ctx; @@ -56,7 +56,7 @@ public class NetworkNode implements NetworkHandler, ContextHolder { private ConcurrentMap 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(); diff --git a/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkNodeTest.java b/networking/src/test/java/ch/dissem/bitmessage/networking/DefaultNetworkHandlerTest.java similarity index 86% rename from networking/src/test/java/ch/dissem/bitmessage/networking/NetworkNodeTest.java rename to networking/src/test/java/ch/dissem/bitmessage/networking/DefaultNetworkHandlerTest.java index 6819311..3bec993 100644 --- a/networking/src/test/java/ch/dissem/bitmessage/networking/NetworkNodeTest.java +++ b/networking/src/test/java/ch/dissem/bitmessage/networking/DefaultNetworkHandlerTest.java @@ -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) { diff --git a/repositories/build.gradle b/repositories/build.gradle index 2f467b8..76509fd 100644 --- a/repositories/build.gradle +++ b/repositories/build.gradle @@ -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') } \ No newline at end of file diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcConfig.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcConfig.java index 80e079b..7448b19 100644 --- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcConfig.java +++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcConfig.java @@ -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; diff --git a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java index 8795388..4fbea30 100644 --- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java +++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java @@ -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); } diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcAddressRepositoryTest.java b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcAddressRepositoryTest.java index 80740ca..18fc83c 100644 --- a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcAddressRepositoryTest.java +++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcAddressRepositoryTest.java @@ -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"; diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcInventoryTest.java b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcInventoryTest.java index 8b23566..3954766 100644 --- a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcInventoryTest.java +++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcInventoryTest.java @@ -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; diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java index b99e44f..3dceb24 100644 --- a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java +++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcMessageRepositoryTest.java @@ -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 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); diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcNodeRegistryTest.java b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcNodeRegistryTest.java index d668822..ca4bcd7 100644 --- a/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcNodeRegistryTest.java +++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/JdbcNodeRegistryTest.java @@ -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; diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/TestBase.java b/repositories/src/test/java/ch/dissem/bitmessage/repository/TestBase.java new file mode 100644 index 0000000..c6baaa2 --- /dev/null +++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/TestBase.java @@ -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); + } +} diff --git a/repositories/src/test/java/ch/dissem/bitmessage/repository/TestJdbcConfig.java b/repositories/src/test/java/ch/dissem/bitmessage/repository/TestJdbcConfig.java index 909ba75..99a26c0 100644 --- a/repositories/src/test/java/ch/dissem/bitmessage/repository/TestJdbcConfig.java +++ b/repositories/src/test/java/ch/dissem/bitmessage/repository/TestJdbcConfig.java @@ -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() { diff --git a/security-bc/build.gradle b/security-bc/build.gradle new file mode 100644 index 0000000..48f14d1 --- /dev/null +++ b/security-bc/build.gradle @@ -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' +} diff --git a/security-bc/src/main/java/ch/dissem/bitmessage/security/bc/BouncySecurity.java b/security-bc/src/main/java/ch/dissem/bitmessage/security/bc/BouncySecurity.java new file mode 100644 index 0000000..a125049 --- /dev/null +++ b/security-bc/src/main/java/ch/dissem/bitmessage/security/bc/BouncySecurity.java @@ -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); + } +} diff --git a/domain/src/test/java/ch/dissem/bitmessage/utils/SecurityTest.java b/security-bc/src/test/java/ch/dissem/bitmessage/security/SecurityTest.java similarity index 60% rename from domain/src/test/java/ch/dissem/bitmessage/utils/SecurityTest.java rename to security-bc/src/test/java/ch/dissem/bitmessage/security/SecurityTest.java index 9b97eb1..94f0ede 100644 --- a/domain/src/test/java/ch/dissem/bitmessage/utils/SecurityTest.java +++ b/security-bc/src/test/java/ch/dissem/bitmessage/security/SecurityTest.java @@ -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(); - } -} +} \ No newline at end of file diff --git a/security-sc/build.gradle b/security-sc/build.gradle new file mode 100644 index 0000000..bfbdcab --- /dev/null +++ b/security-sc/build.gradle @@ -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' +} diff --git a/security-sc/src/main/java/ch/dissem/bitmessage/security/sc/SpongySecurity.java b/security-sc/src/main/java/ch/dissem/bitmessage/security/sc/SpongySecurity.java new file mode 100644 index 0000000..70a0743 --- /dev/null +++ b/security-sc/src/main/java/ch/dissem/bitmessage/security/sc/SpongySecurity.java @@ -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); + } +} diff --git a/settings.gradle b/settings.gradle index 6452ec0..2d3e1f6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -9,3 +9,7 @@ include 'repositories' include 'demo' include 'wif' + +include 'security-sc' + +include 'security-bc' diff --git a/wif/build.gradle b/wif/build.gradle index 393928f..5b32a21 100644 --- a/wif/build.gradle +++ b/wif/build.gradle @@ -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') } diff --git a/wif/src/main/java/ch/dissem/bitmessage/wif/WifExporter.java b/wif/src/main/java/ch/dissem/bitmessage/wif/WifExporter.java index 26b6d63..b41d645 100644 --- a/wif/src/main/java/ch/dissem/bitmessage/wif/WifExporter.java +++ b/wif/src/main/java/ch/dissem/bitmessage/wif/WifExporter.java @@ -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); } diff --git a/wif/src/main/java/ch/dissem/bitmessage/wif/WifImporter.java b/wif/src/main/java/ch/dissem/bitmessage/wif/WifImporter.java index 45bbb4d..e88eaa4 100644 --- a/wif/src/main/java/ch/dissem/bitmessage/wif/WifImporter.java +++ b/wif/src/main/java/ch/dissem/bitmessage/wif/WifImporter.java @@ -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); } diff --git a/wif/src/test/java/ch/dissem/bitmessage/wif/WifExporterTest.java b/wif/src/test/java/ch/dissem/bitmessage/wif/WifExporterTest.java index 8cb9264..5ed9025 100644 --- a/wif/src/test/java/ch/dissem/bitmessage/wif/WifExporterTest.java +++ b/wif/src/test/java/ch/dissem/bitmessage/wif/WifExporterTest.java @@ -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)) diff --git a/wif/src/test/java/ch/dissem/bitmessage/wif/WifImporterTest.java b/wif/src/test/java/ch/dissem/bitmessage/wif/WifImporterTest.java index 2973efc..862b3e3 100644 --- a/wif/src/test/java/ch/dissem/bitmessage/wif/WifImporterTest.java +++ b/wif/src/test/java/ch/dissem/bitmessage/wif/WifImporterTest.java @@ -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))