From 99266712faeff0b392557512f73a751dd4fa7d7b Mon Sep 17 00:00:00 2001 From: Christian Basler Date: Sat, 28 Nov 2015 20:27:05 +0100 Subject: [PATCH] Some extensions for server POW --- .../dissem/bitmessage/BitmessageContext.java | 18 ++- .../ch/dissem/bitmessage/InternalContext.java | 6 + .../bitmessage/entity/BitmessageAddress.java | 2 +- .../bitmessage/entity/CustomMessage.java | 68 +++++++++ .../bitmessage/entity/MessagePayload.java | 2 +- .../bitmessage/entity/payload/Broadcast.java | 1 - .../bitmessage/entity/payload/CryptoBox.java | 9 +- .../bitmessage/factory/V3MessageFactory.java | 6 + .../ports/CustomCommandHandler.java | 27 ++++ .../ch/dissem/bitmessage/utils/Encode.java | 16 +- extensions/build.gradle | 36 +++++ .../extensions/CryptoCustomMessage.java | 139 ++++++++++++++++++ .../extensions/pow/ProofOfWorkRequest.java | 86 +++++++++++ .../extensions/CryptoCustomMessageTest.java | 58 ++++++++ .../bitmessage/networking/Connection.java | 6 + .../bitmessage/repository/JdbcHelper.java | 2 +- settings.gradle | 2 + 17 files changed, 472 insertions(+), 12 deletions(-) create mode 100644 domain/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java create mode 100644 domain/src/main/java/ch/dissem/bitmessage/ports/CustomCommandHandler.java create mode 100644 extensions/build.gradle create mode 100644 extensions/src/main/java/ch/dissem/bitmessage/extensions/CryptoCustomMessage.java create mode 100644 extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java create mode 100644 extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java diff --git a/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java b/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java index 9f4d9a3..9d4abd7 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java +++ b/domain/src/main/java/ch/dissem/bitmessage/BitmessageContext.java @@ -16,9 +16,7 @@ package ch.dissem.bitmessage; -import ch.dissem.bitmessage.entity.BitmessageAddress; -import ch.dissem.bitmessage.entity.ObjectMessage; -import ch.dissem.bitmessage.entity.Plaintext; +import ch.dissem.bitmessage.entity.*; import ch.dissem.bitmessage.entity.payload.*; import ch.dissem.bitmessage.entity.payload.Pubkey.Feature; import ch.dissem.bitmessage.entity.valueobject.InventoryVector; @@ -297,6 +295,7 @@ public class BitmessageContext { ProofOfWorkEngine proofOfWorkEngine; Security security; MessageCallback messageCallback; + CustomCommandHandler customCommandHandler; Listener listener; int connectionLimit = 150; long connectionTTL = 12 * HOUR; @@ -344,6 +343,11 @@ public class BitmessageContext { return this; } + public Builder customCommandHandler(CustomCommandHandler handler) { + this.customCommandHandler = handler; + return this; + } + public Builder proofOfWorkEngine(ProofOfWorkEngine proofOfWorkEngine) { this.proofOfWorkEngine = proofOfWorkEngine; return this; @@ -392,6 +396,14 @@ public class BitmessageContext { } }; } + if (customCommandHandler == null) { + customCommandHandler = new CustomCommandHandler() { + @Override + public MessagePayload handle(CustomMessage request) { + throw new RuntimeException("Received custom request, but no custom command handler configured."); + } + }; + } return new BitmessageContext(this); } diff --git a/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java b/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java index 7a89978..95cd8d8 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java +++ b/domain/src/main/java/ch/dissem/bitmessage/InternalContext.java @@ -50,6 +50,7 @@ public class InternalContext { private final MessageRepository messageRepository; private final ProofOfWorkEngine proofOfWorkEngine; private final MessageCallback messageCallback; + private final CustomCommandHandler customCommandHandler; private final TreeSet streams = new TreeSet<>(); private final int port; @@ -69,6 +70,7 @@ public class InternalContext { this.proofOfWorkEngine = builder.proofOfWorkEngine; this.clientNonce = security.randomNonce(); this.messageCallback = builder.messageCallback; + this.customCommandHandler = builder.customCommandHandler; this.port = builder.port; this.connectionLimit = builder.connectionLimit; this.connectionTTL = builder.connectionTTL; @@ -263,6 +265,10 @@ public class InternalContext { return connectionLimit; } + public CustomCommandHandler getCustomCommandHandler() { + return customCommandHandler; + } + 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 0441e6e..931776c 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/BitmessageAddress.java @@ -87,7 +87,7 @@ public class BitmessageAddress implements Serializable { } } - BitmessageAddress(Pubkey publicKey) { + public BitmessageAddress(Pubkey publicKey) { this(publicKey.getVersion(), publicKey.getStream(), publicKey.getRipe()); this.pubkey = publicKey; } diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java b/domain/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java new file mode 100644 index 0000000..b31c9f5 --- /dev/null +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/CustomMessage.java @@ -0,0 +1,68 @@ +/* + * Copyright 2015 Christian Basler + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ch.dissem.bitmessage.entity; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import static ch.dissem.bitmessage.utils.Decode.bytes; + +/** + * @author Christian Basler + */ +public class CustomMessage implements MessagePayload { + private final byte[] data; + + public CustomMessage() { + this.data = null; + } + + public CustomMessage(byte[] data) { + this.data = data; + } + + public static MessagePayload read(InputStream in, int length) throws IOException { + return new CustomMessage(bytes(in, length)); + } + + @Override + public Command getCommand() { + return Command.CUSTOM; + } + + public byte[] getData() throws IOException { + if (data != null) { + return data; + } else { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + write(out); + return out.toByteArray(); + } + } + + @Override + public void write(OutputStream out) throws IOException { + if (data != null) { + out.write(data); + } else { + throw new RuntimeException("Tried to write custom message without data. " + + "Programmer: did you forget to override #write()?"); + } + } +} diff --git a/domain/src/main/java/ch/dissem/bitmessage/entity/MessagePayload.java b/domain/src/main/java/ch/dissem/bitmessage/entity/MessagePayload.java index e6f6f0a..994952b 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/entity/MessagePayload.java +++ b/domain/src/main/java/ch/dissem/bitmessage/entity/MessagePayload.java @@ -23,6 +23,6 @@ public interface MessagePayload extends Streamable { Command getCommand(); enum Command { - VERSION, VERACK, ADDR, INV, GETDATA, OBJECT + VERSION, VERACK, ADDR, INV, GETDATA, OBJECT, CUSTOM } } 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 bf5ff7f..47bf539 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,7 +21,6 @@ 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.ports.Security; import java.io.IOException; 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 a870b32..fe45ac5 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 @@ -38,7 +38,14 @@ public class CryptoBox implements Streamable { private final byte[] mac; private byte[] encrypted; + private long addressVersion; + + public CryptoBox(Streamable data, byte[] K) throws IOException { + this(Encode.bytes(data), K); + } + + public CryptoBox(byte[] data, byte[] K) throws IOException { curveType = 0x02CA; // 1. The destination public key is called K. @@ -58,7 +65,7 @@ public class CryptoBox implements Streamable { 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 = security().crypt(true, Encode.bytes(data), key_e, initializationVector); + encrypted = security().crypt(true, 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); 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 8dca6d2..9e15b3d 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java +++ b/domain/src/main/java/ch/dissem/bitmessage/factory/V3MessageFactory.java @@ -73,12 +73,18 @@ class V3MessageFactory { return parseGetData(stream); case "object": return readObject(stream, length); + case "custom": + return readCustom(stream, length); default: LOG.debug("Unknown command: " + command); return null; } } + private static MessagePayload readCustom(InputStream in, int length) throws IOException { + return CustomMessage.read(in, length); + } + public static ObjectMessage readObject(InputStream in, int length) throws IOException { AccessCounter counter = new AccessCounter(); byte nonce[] = Decode.bytes(in, 8, counter); diff --git a/domain/src/main/java/ch/dissem/bitmessage/ports/CustomCommandHandler.java b/domain/src/main/java/ch/dissem/bitmessage/ports/CustomCommandHandler.java new file mode 100644 index 0000000..8e49586 --- /dev/null +++ b/domain/src/main/java/ch/dissem/bitmessage/ports/CustomCommandHandler.java @@ -0,0 +1,27 @@ +/* + * 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.CustomMessage; +import ch.dissem.bitmessage.entity.MessagePayload; + +/** + * @author Christian Basler + */ +public interface CustomCommandHandler { + MessagePayload handle(CustomMessage request); +} diff --git a/domain/src/main/java/ch/dissem/bitmessage/utils/Encode.java b/domain/src/main/java/ch/dissem/bitmessage/utils/Encode.java index a78d03f..2cdc262 100644 --- a/domain/src/main/java/ch/dissem/bitmessage/utils/Encode.java +++ b/domain/src/main/java/ch/dissem/bitmessage/utils/Encode.java @@ -103,15 +103,23 @@ public class Encode { inc(counter, 8); } - public static void varString(String value, OutputStream stream) throws IOException { + public static void varString(String value, OutputStream out) throws IOException { byte[] bytes = value.getBytes("utf-8"); - // FIXME: technically, it says the length in characters, but I think this one might be correct + // Technically, it says the length in characters, but I think this one might be correct. + // It doesn't really matter, as only ASCII characters are being used. // see also Decode#varString() - varInt(bytes.length, stream); - stream.write(bytes); + varInt(bytes.length, out); + out.write(bytes); + } + + public static void varBytes(byte[] data, OutputStream out) throws IOException { + varInt(data.length, out); + out.write(data); } /** + * Serializes a {@link Streamable} object and returns the byte array. + * * @param streamable the object to be serialized * @return an array of bytes representing the given streamable object. * @throws IOException if an I/O error occurs. diff --git a/extensions/build.gradle b/extensions/build.gradle new file mode 100644 index 0000000..0ae9fd4 --- /dev/null +++ b/extensions/build.gradle @@ -0,0 +1,36 @@ +/* + * 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. + */ + +uploadArchives { + repositories { + mavenDeployer { + pom.project { + name 'Jabit Extensions' + artifactId = 'jabit-extensions' + description 'Protocol extensions used for some extended features, e.g. server and mobile client.' + } + } + } +} + +dependencies { + compile project(':domain') + testCompile 'junit:junit:4.11' + testCompile 'org.slf4j:slf4j-simple:1.7.12' + testCompile 'org.mockito:mockito-core:1.10.19' + testCompile project(path: ':domain', configuration: 'testArtifacts') + testCompile project(':security-bc') +} diff --git a/extensions/src/main/java/ch/dissem/bitmessage/extensions/CryptoCustomMessage.java b/extensions/src/main/java/ch/dissem/bitmessage/extensions/CryptoCustomMessage.java new file mode 100644 index 0000000..5d82f4d --- /dev/null +++ b/extensions/src/main/java/ch/dissem/bitmessage/extensions/CryptoCustomMessage.java @@ -0,0 +1,139 @@ +/* + * 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.extensions; + +import ch.dissem.bitmessage.entity.BitmessageAddress; +import ch.dissem.bitmessage.entity.CustomMessage; +import ch.dissem.bitmessage.entity.Streamable; +import ch.dissem.bitmessage.entity.payload.CryptoBox; +import ch.dissem.bitmessage.entity.payload.Pubkey; +import ch.dissem.bitmessage.exception.DecryptionFailedException; +import ch.dissem.bitmessage.factory.Factory; +import ch.dissem.bitmessage.utils.Encode; + +import java.io.*; + +import static ch.dissem.bitmessage.utils.Decode.*; +import static ch.dissem.bitmessage.utils.Singleton.security; + +/** + * A {@link CustomMessage} implementation that contains signed and encrypted data. + * + * @author Christian Basler + */ +public class CryptoCustomMessage extends CustomMessage { + private final Reader dataReader; + private CryptoBox container; + private BitmessageAddress sender; + private T data; + + public CryptoCustomMessage(T data) throws IOException { + this.data = data; + this.dataReader = null; + } + + private CryptoCustomMessage(CryptoBox container, Reader dataReader) { + this.container = container; + this.dataReader = dataReader; + } + + public static CryptoCustomMessage read(byte[] data, Reader dataReader) throws IOException { + CryptoBox cryptoBox = CryptoBox.read(new ByteArrayInputStream(data), data.length); + return new CryptoCustomMessage<>(cryptoBox, dataReader); + } + + public BitmessageAddress getSender() { + return sender; + } + + public void signAndEncrypt(BitmessageAddress identity, byte[] publicKey) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + Encode.varInt(identity.getVersion(), out); + Encode.varInt(identity.getStream(), out); + Encode.int32(identity.getPubkey().getBehaviorBitfield(), out); + out.write(identity.getPubkey().getSigningKey(), 1, 64); + out.write(identity.getPubkey().getEncryptionKey(), 1, 64); + if (identity.getVersion() >= 3) { + Encode.varInt(identity.getPubkey().getNonceTrialsPerByte(), out); + Encode.varInt(identity.getPubkey().getExtraBytes(), out); + } + + data.write(out); + Encode.varBytes(security().getSignature(out.toByteArray(), identity.getPrivateKey()), out); + container = new CryptoBox(out.toByteArray(), publicKey); + } + + public T decrypt(byte[] privateKey) throws IOException, DecryptionFailedException { + SignatureCheckingInputStream in = new SignatureCheckingInputStream(container.decrypt(privateKey)); + + long addressVersion = varInt(in); + long stream = varInt(in); + int behaviorBitfield = int32(in); + byte[] publicSigningKey = bytes(in, 64); + byte[] publicEncryptionKey = bytes(in, 64); + long nonceTrialsPerByte = addressVersion >= 3 ? varInt(in) : 0; + long extraBytes = addressVersion >= 3 ? varInt(in) : 0; + + sender = new BitmessageAddress(Factory.createPubkey( + addressVersion, + stream, + publicSigningKey, + publicEncryptionKey, + nonceTrialsPerByte, + extraBytes, + behaviorBitfield + )); + + data = dataReader.read(sender, in); + + in.checkSignature(sender.getPubkey()); + + return data; + } + + @Override + public void write(OutputStream out) throws IOException { + container.write(out); + } + + public interface Reader { + T read(BitmessageAddress sender, InputStream in) throws IOException; + } + + private class SignatureCheckingInputStream extends InputStream { + private final ByteArrayOutputStream out = new ByteArrayOutputStream(); + private final InputStream wrapped; + + private SignatureCheckingInputStream(InputStream wrapped) { + this.wrapped = wrapped; + } + + @Override + public int read() throws IOException { + int read = wrapped.read(); + if (read >= 0) out.write(read); + return read; + } + + public void checkSignature(Pubkey pubkey) throws IOException, RuntimeException { + if (!security().isSignatureValid(out.toByteArray(), varBytes(wrapped), pubkey)) { + throw new RuntimeException("Signature check failed"); + } + } + } +} diff --git a/extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java b/extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java new file mode 100644 index 0000000..b247b59 --- /dev/null +++ b/extensions/src/main/java/ch/dissem/bitmessage/extensions/pow/ProofOfWorkRequest.java @@ -0,0 +1,86 @@ +/* + * 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.extensions.pow; + +import ch.dissem.bitmessage.entity.BitmessageAddress; +import ch.dissem.bitmessage.entity.Streamable; +import ch.dissem.bitmessage.utils.Encode; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import static ch.dissem.bitmessage.utils.Decode.*; + +/** + * @author Christian Basler + */ +public class ProofOfWorkRequest implements Streamable { + private final BitmessageAddress sender; + private final byte[] initialHash; + private final Request request; + private final byte[] data; + + private ProofOfWorkRequest(BitmessageAddress sender, byte[] initialHash, Request request, byte[] data) { + this.sender = sender; + this.initialHash = initialHash; + this.request = request; + this.data = data; + } + + public static ProofOfWorkRequest read(BitmessageAddress client, InputStream in) throws IOException { + return new ProofOfWorkRequest( + client, + bytes(in, 64), + Request.valueOf(varString(in)), + varBytes(in) + ); + } + + public BitmessageAddress getSender() { + return sender; + } + + public byte[] getInitialHash() { + return initialHash; + } + + public Request getRequest() { + return request; + } + + public byte[] getData() { + return data; + } + + @Override + public void write(OutputStream out) throws IOException { + out.write(initialHash); + Encode.varString(request.name(), out); + Encode.varBytes(data, out); + } + + public enum Request { + CALCULATE, + QUERY, + ERROR, + OK, + QUEUED, + CALCULATING, + COMPLETE + } +} diff --git a/extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java b/extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java new file mode 100644 index 0000000..98e97a1 --- /dev/null +++ b/extensions/src/test/java/ch/dissem/bitmessage/extensions/CryptoCustomMessageTest.java @@ -0,0 +1,58 @@ +/* + * 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.extensions; + +import ch.dissem.bitmessage.entity.BitmessageAddress; +import ch.dissem.bitmessage.entity.payload.GenericPayload; +import ch.dissem.bitmessage.entity.valueobject.PrivateKey; +import ch.dissem.bitmessage.utils.TestBase; +import ch.dissem.bitmessage.utils.TestUtils; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import static ch.dissem.bitmessage.utils.Singleton.security; +import static org.junit.Assert.assertEquals; + +public class CryptoCustomMessageTest extends TestBase { + @Test + public void testEncryptThenDecrypt() throws Exception { + PrivateKey privateKey = PrivateKey.read(TestUtils.getResource("BM-2cSqjfJ8xK6UUn5Rw3RpdGQ9RsDkBhWnS8.privkey")); + BitmessageAddress sendingIdentity = new BitmessageAddress(privateKey); + + GenericPayload payloadBefore = new GenericPayload(0, 1, security().randomBytes(100)); + CryptoCustomMessage messageBefore = new CryptoCustomMessage<>(payloadBefore); + messageBefore.signAndEncrypt(sendingIdentity, security().createPublicKey(sendingIdentity.getPublicDecryptionKey())); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + messageBefore.write(out); + ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); + + CryptoCustomMessage messageAfter = CryptoCustomMessage.read(out.toByteArray(), new CryptoCustomMessage.Reader() { + @Override + public GenericPayload read(BitmessageAddress ignore, InputStream in) throws IOException { + return GenericPayload.read(0, in, 1, 100); + } + }); + GenericPayload payloadAfter = messageAfter.decrypt(sendingIdentity.getPublicDecryptionKey()); + + assertEquals(payloadBefore, payloadAfter); + } +} 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 8ed2fb4..a95adba 100644 --- a/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java +++ b/networking/src/main/java/ch/dissem/bitmessage/networking/Connection.java @@ -259,6 +259,12 @@ public class Connection { LOG.debug("Received " + addr.getAddresses().size() + " addresses."); ctx.getNodeRegistry().offerAddresses(addr.getAddresses()); break; + case CUSTOM: + MessagePayload response = ctx.getCustomCommandHandler().handle((CustomMessage) messagePayload); + if (response != null) { + send(response); + } + break; case VERACK: case VERSION: throw new RuntimeException("Unexpectedly received '" + messagePayload.getCommand() + "' command"); 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 601ce29..d583a71 100644 --- a/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java +++ b/repositories/src/main/java/ch/dissem/bitmessage/repository/JdbcHelper.java @@ -31,7 +31,7 @@ import static ch.dissem.bitmessage.utils.Strings.hex; /** * Helper class that does Flyway migration, provides JDBC connections and some helper methods. */ -abstract class JdbcHelper { +public abstract class JdbcHelper { private static final Logger LOG = LoggerFactory.getLogger(JdbcHelper.class); protected final JdbcConfig config; diff --git a/settings.gradle b/settings.gradle index 2d3e1f6..adecd45 100644 --- a/settings.gradle +++ b/settings.gradle @@ -13,3 +13,5 @@ include 'wif' include 'security-sc' include 'security-bc' + +include 'extensions' \ No newline at end of file