diff --git a/pom.xml b/pom.xml index 72aaebe..48bb7bd 100644 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ com.ubirch ubirch-protocol-java - 2.1.4-SNAPSHOT + 2.1.5-SNAPSHOT jar @@ -74,14 +74,14 @@ 2.0.6 2.13.3 - 0.8.16 - 1.11 + 0.9.1 + 1.13 10.3 3.1.2 - 1.7.25 - 1.2.3 + 1.7.36 + 1.2.11 5.2.0 1.2.0 diff --git a/src/main/java/com/ubirch/protocol/ProtocolMessage.java b/src/main/java/com/ubirch/protocol/ProtocolMessage.java index 7c23975..94d8174 100644 --- a/src/main/java/com/ubirch/protocol/ProtocolMessage.java +++ b/src/main/java/com/ubirch/protocol/ProtocolMessage.java @@ -47,22 +47,44 @@ public class ProtocolMessage { @JsonView(ProtocolMessageViews.Default.class) protected int version = 0; + + /** + * This uuid represents the id of the device registered at Ubirch console and is + * also used to identify the key retrieved by the id-service to verify the UPP. + */ @JsonInclude(NON_NULL) @JsonView(ProtocolMessageViews.Default.class) protected UUID uuid = null; @JsonInclude(NON_NULL) @JsonView(ProtocolMessageViews.Default.class) protected byte[] chain = null; + + /** + * The hint represents the payload type of the ubirch msgPack variants as described in this documentation: + * https://github.com/ubirch/ubirch-protocol/blob/master/README_PAYLOAD.md#payload-types + */ @JsonInclude(NON_NULL) @JsonView(ProtocolMessageViews.Default.class) protected int hint = 0; + /** + * Signed contains usually everything of the msgPack except for the signature. + * Only in the case of the hashed trackle message packet (hint = 0x56) it only + * contains the already SHA-512 hashed payload, as we don't want to include the + * original signed payload in this ubirch msgPack version. + */ @JsonInclude(NON_NULL) @JsonView(ProtocolMessageViews.WithSignedData.class) protected byte[] signed; + @JsonInclude(NON_NULL) @JsonView(ProtocolMessageViews.Default.class) protected byte[] signature = null; + + /** + * The payload here describes the specific field payload of the ubirch protocol basic message format, + * as described here: https://github.com/ubirch/ubirch-protocol#basic-message-format + */ @JsonInclude(NON_NULL) @JsonView(ProtocolMessageViews.Default.class) protected JsonNode payload; diff --git a/src/main/java/com/ubirch/protocol/codec/DecoderUtil.java b/src/main/java/com/ubirch/protocol/codec/DecoderUtil.java new file mode 100644 index 0000000..41c169c --- /dev/null +++ b/src/main/java/com/ubirch/protocol/codec/DecoderUtil.java @@ -0,0 +1,82 @@ +package com.ubirch.protocol.codec; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.*; +import org.msgpack.core.ExtensionTypeHeader; +import org.msgpack.core.MessageFormat; +import org.msgpack.core.MessageNeverUsedFormatException; +import org.msgpack.core.MessageUnpacker; +import org.msgpack.value.ImmutableStringValue; +import org.msgpack.value.ValueFactory; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public final class DecoderUtil { + + private DecoderUtil() { + //not called + } + + /** + * This method analyzes of what type the payload field is and retrieves the value as a JsonNode. + * This JsonNode can be of different types, e.g. BooleanNode, BinaryNode, TextNode, ... + */ + public static JsonNode decodePayload(MessageUnpacker unpacker) throws IOException { + MessageFormat mf = unpacker.getNextFormat(); + switch (mf.getValueType()) { + case NIL: + unpacker.unpackNil(); + return NullNode.getInstance(); + case BOOLEAN: + return BooleanNode.valueOf(unpacker.unpackBoolean()); + case INTEGER: + if (mf == MessageFormat.UINT64) { + return BigIntegerNode.valueOf(unpacker.unpackBigInteger()); + } + return LongNode.valueOf(unpacker.unpackLong()); + case FLOAT: + return DoubleNode.valueOf(unpacker.unpackDouble()); + case STRING: { + int length = unpacker.unpackRawStringHeader(); + ImmutableStringValue stringValue = ValueFactory.newString(unpacker.readPayload(length), true); + if (stringValue.isRawValue()) { + return BinaryNode.valueOf(stringValue.asRawValue().asByteArray()); + } else { + return TextNode.valueOf(stringValue.asString()); + } + } + case BINARY: { + int length = unpacker.unpackBinaryHeader(); + return BinaryNode.valueOf(unpacker.readPayload(length)); + } + case ARRAY: { + int size = unpacker.unpackArrayHeader(); + List array = new ArrayList<>(size); + for (int i = 0; i < size; i++) { + array.add(decodePayload(unpacker)); + } + return new ArrayNode(null, array); + } + case MAP: { + int size = unpacker.unpackMapHeader(); + Map kvs = new HashMap<>(size); + for (int i = 0; i < size; i++) { + JsonNode kn = decodePayload(unpacker); + String key = kn.isBinary() ? new String(kn.binaryValue()) : kn.asText(); + kvs.put(key, decodePayload(unpacker)); + } + return new ObjectNode(null, kvs); + } + case EXTENSION: { + ExtensionTypeHeader extHeader = unpacker.unpackExtensionTypeHeader(); + return BinaryNode.valueOf(unpacker.readPayload(extHeader.getLength())); + } + default: + throw new MessageNeverUsedFormatException("Unknown value type"); + } + } +} diff --git a/src/main/java/com/ubirch/protocol/codec/EncoderUtil.java b/src/main/java/com/ubirch/protocol/codec/EncoderUtil.java new file mode 100644 index 0000000..264b8bc --- /dev/null +++ b/src/main/java/com/ubirch/protocol/codec/EncoderUtil.java @@ -0,0 +1,78 @@ +package com.ubirch.protocol.codec; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.*; +import com.ubirch.protocol.ProtocolException; +import com.ubirch.protocol.ProtocolMessage; +import org.msgpack.core.MessagePacker; +import org.msgpack.jackson.dataformat.MessagePackFactory; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Base64; + +public final class EncoderUtil { + + private EncoderUtil() { + //not called + } + + public static void packVersion(MessagePacker packer, ProtocolMessage pm) throws IOException { + packer.packInt(pm.getVersion()); + } + + public static void packUUID(MessagePacker packer, ProtocolMessage pm) throws IOException { + packer.packBinaryHeader(16).addPayload(UUIDUtil.uuidToBytes(pm.getUUID())); + } + + public static void packChain(MessagePacker packer, ProtocolMessage pm) throws IOException { + switch (pm.getVersion()) { + case ProtocolMessage.CHAINED: + packer.packBinaryHeader(64); + byte[] chainSignature = pm.getChain(); + if (chainSignature == null) { + packer.addPayload(new byte[64]); + } else { + packer.addPayload(chainSignature); + } + break; + case ProtocolMessage.SIGNED: + break; + default: + throw new ProtocolException(String.format("unknown protocol version: 0x%x", pm.getVersion())); + } + } + + public static void packHint(MessagePacker packer, ProtocolMessage pm) throws IOException { + packer.packInt(pm.getHint()); + } + + /** + * This method packs the msgPack depending on the JsonNode type. + */ + public static void packPayload(MessagePacker packer, ProtocolMessage pm, ByteArrayOutputStream out) throws IOException { + // https://gitlab.com/ubirch/ubirch-kafka-envelope/-/blob/master/src/main/scala/com/ubirch/kafka/package.scala#L166 + // json4s + // ------ + // To be able to return the payload as just bytes and not as base64 values, we have to + // explicitly try to decode and pack the data in the msgpack. + // There seems to be a limitation with the way json4s handles binary nodes. + // https://gitlab.com/ubirch/ubirch-kafka-envelope/-/blob/master/src/main/scala/com/ubirch/kafka/package.scala#L166 + if (pm.getPayload() instanceof BinaryNode) { + packer.packBinaryHeader(pm.getPayload().binaryValue().length); + packer.writePayload(pm.getPayload().binaryValue()); + } else if (pm.getPayload() instanceof TextNode) { + try { + byte[] bytes = Base64.getDecoder().decode(pm.getPayload().asText()); + packer.packBinaryHeader(bytes.length).addPayload(bytes); + } catch (Exception e) { + ObjectMapper mapper = new ObjectMapper(new MessagePackFactory()); + mapper.writeValue(out, pm.getPayload()); + } + } else { + ObjectMapper mapper = new ObjectMapper(new MessagePackFactory()); + mapper.writeValue(out, pm.getPayload()); + } + } + +} diff --git a/src/main/java/com/ubirch/protocol/codec/MsgPackProtocolDecoder.java b/src/main/java/com/ubirch/protocol/codec/MsgPackProtocolDecoder.java index 27b2051..f99b705 100644 --- a/src/main/java/com/ubirch/protocol/codec/MsgPackProtocolDecoder.java +++ b/src/main/java/com/ubirch/protocol/codec/MsgPackProtocolDecoder.java @@ -17,18 +17,22 @@ package com.ubirch.protocol.codec; import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.*; import com.ubirch.protocol.ProtocolException; import com.ubirch.protocol.ProtocolMessage; -import org.msgpack.core.*; +import org.apache.commons.codec.binary.Hex; +import org.msgpack.core.MessagePack; +import org.msgpack.core.MessagePackException; +import org.msgpack.core.MessageUnpacker; import org.msgpack.jackson.dataformat.MessagePackFactory; -import org.msgpack.value.*; +import org.msgpack.value.ValueType; import java.io.ByteArrayInputStream; import java.io.IOException; -import java.util.*; +import java.util.Arrays; + +import static com.ubirch.protocol.codec.DecoderUtil.decodePayload; +import static com.ubirch.protocol.codec.ProtocolHints.HASHED_TRACKLE_MSG_PACK_HINT; /** * The default msgpack ubirch protocol decoder. @@ -63,7 +67,6 @@ public static MsgPackProtocolDecoder getDecoder() { public ProtocolMessage decode(byte[] message) throws ProtocolException { boolean legacyPayloadDecoding = false; ByteArrayInputStream in = new ByteArrayInputStream(message); - MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(in); ProtocolMessage pm = new ProtocolMessage(); try { @@ -100,10 +103,8 @@ public ProtocolMessage decode(byte[] message) throws ProtocolException { pm.setPayload(decodePayload(unpacker)); } - // finally store the signed data and signature for later verification pm.setSigned(Arrays.copyOfRange(message, 0, (int) unpacker.getTotalReadBytes())); pm.setSignature(unpacker.readPayload(unpacker.unpackRawStringHeader())); - return pm; } else { throw new ProtocolException(String.format("unknown msgpack envelope format: %s[%d]", envelopeType.name(), envelopeLength)); @@ -115,8 +116,28 @@ public ProtocolMessage decode(byte[] message) throws ProtocolException { } } + public boolean isHashedTrackleMsgType(byte[] message) { + if (message.length > 133) { + byte[] hint = Arrays.copyOfRange(message, message.length - 133, message.length - 132); + return Hex.encodeHexString(hint).equals(Integer.toHexString(HASHED_TRACKLE_MSG_PACK_HINT)); + } else { + return false; + } + } + + public boolean isSignedOfHashedTrackleMsgType(byte[] message) { + if (message.length > 133) { + byte[] hint = Arrays.copyOfRange(message, message.length - 67, message.length - 66); + return Hex.encodeHexString(hint).equals(Integer.toHexString(HASHED_TRACKLE_MSG_PACK_HINT)); + } else { + return false; + } + } + /** - * Extracts the signed part and the signature out of the message pack without materializing. + * Extracts the signed part and the signature out of the message pack without materializing the other fields + * of the msgPack. + * * @param message the raw protocol message in msgpack format * @return an array of arrays where the first element is the signed data and the second element is the signature. * @throws ProtocolException if the fast extraction fails @@ -150,58 +171,4 @@ public byte[][] getDataToVerifyAndSignature(byte[] message) throws ProtocolExcep } } - private JsonNode decodePayload(MessageUnpacker unpacker) throws IOException { - MessageFormat mf = unpacker.getNextFormat(); - switch (mf.getValueType()) { - case NIL: - unpacker.unpackNil(); - return NullNode.getInstance(); - case BOOLEAN: - return BooleanNode.valueOf(unpacker.unpackBoolean()); - case INTEGER: - if (mf == MessageFormat.UINT64) { - return BigIntegerNode.valueOf(unpacker.unpackBigInteger()); - } - return LongNode.valueOf(unpacker.unpackLong()); - case FLOAT: - return DoubleNode.valueOf(unpacker.unpackDouble()); - case STRING: { - int length = unpacker.unpackRawStringHeader(); - ImmutableStringValue stringValue = ValueFactory.newString(unpacker.readPayload(length), true); - if (stringValue.isRawValue()) { - return BinaryNode.valueOf(stringValue.asRawValue().asByteArray()); - } else { - return TextNode.valueOf(stringValue.asString()); - } - } - case BINARY: { - int length = unpacker.unpackBinaryHeader(); - return BinaryNode.valueOf(unpacker.readPayload(length)); - } - case ARRAY: { - int size = unpacker.unpackArrayHeader(); - List array = new ArrayList<>(size); - for (int i = 0; i < size; i++) { - array.add(decodePayload(unpacker)); - } - return new ArrayNode(null, array); - } - case MAP: { - int size = unpacker.unpackMapHeader(); - Map kvs = new HashMap<>(size); - for (int i = 0; i < size; i++) { - JsonNode kn = decodePayload(unpacker); - String key = kn.isBinary() ? new String(kn.binaryValue()) : kn.asText(); - kvs.put(key, decodePayload(unpacker)); - } - return new ObjectNode(null, kvs); - } - case EXTENSION: { - ExtensionTypeHeader extHeader = unpacker.unpackExtensionTypeHeader(); - return BinaryNode.valueOf(unpacker.readPayload(extHeader.getLength())); - } - default: - throw new MessageNeverUsedFormatException("Unknown value type"); - } - } } diff --git a/src/main/java/com/ubirch/protocol/codec/MsgPackProtocolEncoder.java b/src/main/java/com/ubirch/protocol/codec/MsgPackProtocolEncoder.java index 03041b0..5bf1637 100644 --- a/src/main/java/com/ubirch/protocol/codec/MsgPackProtocolEncoder.java +++ b/src/main/java/com/ubirch/protocol/codec/MsgPackProtocolEncoder.java @@ -16,7 +16,6 @@ package com.ubirch.protocol.codec; -import com.fasterxml.jackson.databind.node.TextNode; import com.ubirch.protocol.ProtocolException; import com.ubirch.protocol.ProtocolMessage; import com.ubirch.protocol.ProtocolSigner; @@ -27,7 +26,9 @@ import java.io.IOException; import java.security.InvalidKeyException; import java.security.SignatureException; -import java.util.Base64; + +import static com.ubirch.protocol.codec.EncoderUtil.*; +import static com.ubirch.protocol.codec.ProtocolHints.HASHED_TRACKLE_MSG_PACK_HINT; /** * The default msgpack protocol encoder. @@ -42,28 +43,7 @@ public static MsgPackProtocolEncoder getEncoder() { return instance; } - final private MsgPackProtocolSigning protocolSigning = new MsgPackProtocolSigning() { - @Override - public void payloadConsumer(MessagePacker packer, ProtocolMessage pm, ByteArrayOutputStream out) throws IOException { - // json4s - // ------ - // To be able to return the payload as just bytes and not as base64 values, we have to - // explicitly try to decode and pack the data in the msgpack. - // There seems to be a limitation with the way json4s handles binary nodes. - // https://gitlab.com/ubirch/ubirch-kafka-envelope/-/blob/master/src/main/scala/com/ubirch/kafka/package.scala#L166 - if (pm.getPayload() instanceof TextNode) { - // write the payload - try { - byte[] bytes = Base64.getDecoder().decode(pm.getPayload().asText()); - packer.packBinaryHeader(bytes.length).addPayload(bytes); - } catch (Exception e) { - super.payloadConsumer(packer, pm, out); - } - } else { - super.payloadConsumer(packer, pm, out); - } - } - }; + final private MsgPackProtocolSigning protocolSigning = new MsgPackProtocolSigning(); /** * Encodes this protocol message into the msgpack format. Modifies the given ProtocolMessage, filling @@ -96,9 +76,20 @@ public byte[] encode(ProtocolMessage pm) throws ProtocolException { ByteArrayOutputStream out = new ByteArrayOutputStream(255); MessagePacker packer = config.newPacker(out); - try { - packer.writePayload(pm.getSigned()); + if (pm.getHint() == HASHED_TRACKLE_MSG_PACK_HINT) { + // as for a hashed trackle msg pack getSigned returns only the payload of the UPP + // every field has to become packed separately for the hashed trackle msgPack + packer.packArrayHeader(6); + packVersion(packer, pm); + packUUID(packer, pm); + packChain(packer, pm); + packHint(packer, pm); + packPayload(packer, pm, out); + } else { + packer.writePayload(pm.getSigned()); + } + if (pm.getVersion() == 1) { packer.packRawStringHeader(pm.getSignature().length); } else { diff --git a/src/main/java/com/ubirch/protocol/codec/MsgPackProtocolSigning.java b/src/main/java/com/ubirch/protocol/codec/MsgPackProtocolSigning.java index 80f8f52..89e6ea9 100644 --- a/src/main/java/com/ubirch/protocol/codec/MsgPackProtocolSigning.java +++ b/src/main/java/com/ubirch/protocol/codec/MsgPackProtocolSigning.java @@ -16,69 +16,35 @@ * limitations under the License. */ -import com.fasterxml.jackson.databind.ObjectMapper; -import com.ubirch.protocol.ProtocolException; import com.ubirch.protocol.ProtocolMessage; import com.ubirch.protocol.ProtocolSigner; import org.msgpack.core.MessagePack; import org.msgpack.core.MessagePacker; -import org.msgpack.jackson.dataformat.MessagePackFactory; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.security.InvalidKeyException; import java.security.SignatureException; +import static com.ubirch.protocol.codec.EncoderUtil.*; +import static com.ubirch.protocol.codec.ProtocolHints.HASHED_TRACKLE_MSG_PACK_HINT; + /** * This class acts a builder for the signing process: - * + *

* It defines the necessary methods for packing a 'Protocol Message' as stream of bytes * that are later signed. - * + *

* The expected way to provide a different packing process for each element of the * 'Protocol Message' is to override the method accordingly. - * - All methods that end in 'Consumer' are the building parts for the process. - * - The methods that perform the actual signing is 'sign'; + * - All methods that end in 'Consumer' are the building parts for the process. + * - The methods that perform the actual signing is 'sign'; */ public class MsgPackProtocolSigning { private static final MessagePack.PackerConfig config = new MessagePack.PackerConfig().withStr8FormatSupport(false); - public MsgPackProtocolSigning() { } - - public void versionConsumer(MessagePacker packer, ProtocolMessage pm) throws IOException { - packer.packInt(pm.getVersion()); - } - - public void uuidConsumer(MessagePacker packer, ProtocolMessage pm) throws IOException { - packer.packBinaryHeader(16).addPayload(UUIDUtil.uuidToBytes(pm.getUUID())); - } - - public void chainConsumer(MessagePacker packer, ProtocolMessage pm) throws IOException { - switch (pm.getVersion()) { - case ProtocolMessage.CHAINED: - packer.packBinaryHeader(64); - byte[] chainSignature = pm.getChain(); - if (chainSignature == null) { - packer.addPayload(new byte[64]); - } else { - packer.addPayload(chainSignature); - } - break; - case ProtocolMessage.SIGNED: - break; - default: - throw new ProtocolException(String.format("unknown protocol version: 0x%x", pm.getVersion())); - } - } - - public void hintConsumer(MessagePacker packer, ProtocolMessage pm) throws IOException { - packer.packInt(pm.getHint()); - } - - public void payloadConsumer(MessagePacker packer, ProtocolMessage pm, ByteArrayOutputStream out) throws IOException { - ObjectMapper mapper = new ObjectMapper(new MessagePackFactory()); - mapper.writeValue(out, pm.getPayload()); + public MsgPackProtocolSigning() { } public ProtocolMessage sign(ProtocolMessage pm, ProtocolSigner signer) throws IOException, SignatureException, InvalidKeyException { @@ -88,16 +54,21 @@ public ProtocolMessage sign(ProtocolMessage pm, ProtocolSigner signer) throws IO packer.packArrayHeader(5 + (pm.getVersion() & 0x0f) - 2); //We build a stream based on the proper order for the Protocol Message - versionConsumer(packer, pm); - uuidConsumer(packer, pm); - chainConsumer(packer, pm); - hintConsumer(packer, pm); + packVersion(packer, pm); + packUUID(packer, pm); + packChain(packer, pm); + packHint(packer, pm); packer.flush(); // make sure everything is in the byte buffer - payloadConsumer(packer, pm, out); + packPayload(packer, pm, out); packer.close(); // also closes out //We sign the bytes - byte[] dataToSign = out.toByteArray(); + byte[] dataToSign; + if (pm.getHint() == HASHED_TRACKLE_MSG_PACK_HINT) { + dataToSign = pm.getPayload().binaryValue(); + } else { + dataToSign = out.toByteArray(); + } byte[] signature = signer.sign(pm.getUUID(), dataToSign, 0, dataToSign.length); //We set the values into the protocol message diff --git a/src/main/java/com/ubirch/protocol/codec/ProtocolHints.java b/src/main/java/com/ubirch/protocol/codec/ProtocolHints.java new file mode 100644 index 0000000..c99ba4f --- /dev/null +++ b/src/main/java/com/ubirch/protocol/codec/ProtocolHints.java @@ -0,0 +1,13 @@ +package com.ubirch.protocol.codec; + +public class ProtocolHints { + + private ProtocolHints() { + //not called + } + + public static int HASHED_TRACKLE_MSG_PACK_HINT = 0x56; + public static int TRACKLE_MSG_PACK_HINT = 0x54; + public static int KEY_REGISTRATION_MSG_PACK_HINT = 0x01; + public static int BINARY_OR_UNKNOWN_PAYLOAD_MSG_PACK_HINT = 0x00; +} diff --git a/src/test/java/com/ubirch/protocol/DeserializationTest.java b/src/test/java/com/ubirch/protocol/DeserializationTest.java index 16e5921..ae094cd 100644 --- a/src/test/java/com/ubirch/protocol/DeserializationTest.java +++ b/src/test/java/com/ubirch/protocol/DeserializationTest.java @@ -38,6 +38,7 @@ import static com.ubirch.protocol.ProtocolMessage.CHAINED; import static com.ubirch.protocol.ProtocolMessage.SIGNED; +import static com.ubirch.protocol.codec.ProtocolHints.*; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -64,7 +65,7 @@ void testDecodeTrackleMessage() throws IOException { assertEquals(1, pm.version >> 4, "unexpected protocol version for trackle (v1) message"); assertEquals(CHAINED & 0x0f, pm.version & 0x0f); assertEquals(UUID.fromString("af931b05-acca-758b-c2aa-eb98d6f93329"), pm.uuid); - assertEquals(0x54, pm.hint); + assertEquals(TRACKLE_MSG_PACK_HINT, pm.hint); byte[] expectedSignature = Arrays.copyOfRange(message, message.length - 64, message.length); assertArrayEquals(expectedSignature, pm.signature); @@ -94,7 +95,7 @@ void testDecodeKeyRegistrationMessage() throws IOException, DecoderException { assertEquals(1, pm.version >> 4, "unexpected protocol version for v1 message"); assertEquals(SIGNED & 0x0f, pm.version & 0x0f); assertEquals(TEST_UUID, pm.uuid); - assertEquals(0x01, pm.hint); + assertEquals(KEY_REGISTRATION_MSG_PACK_HINT, pm.hint); byte[] expectedSignature = Arrays.copyOfRange(message, message.length - 64, message.length); assertArrayEquals(expectedSignature, pm.signature); @@ -122,7 +123,7 @@ void testDecodeMessageECDSAv1() throws IOException, NoSuchAlgorithmException { assertEquals(1, pm.version >> 4, "unexpected protocol version for v1 message"); assertEquals(SIGNED & 0x0f, pm.version & 0x0f); assertEquals(TEST_UUID_ECDSA, pm.uuid); - assertEquals(0x00, pm.hint); + assertEquals(BINARY_OR_UNKNOWN_PAYLOAD_MSG_PACK_HINT, pm.hint); byte[] expectedSignature = Arrays.copyOfRange(message, message.length - 64, message.length); assertArrayEquals(expectedSignature, pm.signature); @@ -143,7 +144,7 @@ void testDecodeKeyRegistrationMessageECDSAv1() throws IOException, DecoderExcept assertEquals(1, pm.version >> 4, "unexpected protocol version for v1 message"); assertEquals(SIGNED & 0x0f, pm.version & 0x0f); assertEquals(TEST_UUID_ECDSA, pm.uuid); - assertEquals(0x01, pm.hint); + assertEquals(KEY_REGISTRATION_MSG_PACK_HINT, pm.hint); byte[] expectedSignature = Arrays.copyOfRange(message, message.length - 64, message.length); assertArrayEquals(expectedSignature, pm.signature); @@ -171,7 +172,7 @@ void testDecodeMessageECDSAv2() throws IOException, NoSuchAlgorithmException { assertEquals(2, pm.version >> 4, "unexpected protocol version for v2 message"); assertEquals(SIGNED & 0x0f, pm.version & 0x0f); assertEquals(TEST_UUID_ECDSA_V2, pm.uuid); - assertEquals(0x00, pm.hint); + assertEquals(BINARY_OR_UNKNOWN_PAYLOAD_MSG_PACK_HINT, pm.hint); byte[] expectedSignature = Arrays.copyOfRange(message, message.length - 64, message.length); assertArrayEquals(expectedSignature, pm.signature); @@ -191,7 +192,7 @@ void testDecodeKeyRegistrationMessageECDSAv2() throws IOException, DecoderExcept assertEquals(2, pm.version >> 4, "unexpected protocol version for v2 message"); assertEquals(SIGNED & 0x0f, pm.version & 0x0f); assertEquals(TEST_UUID_ECDSA_V2, pm.uuid); - assertEquals(0x01, pm.hint); + assertEquals(KEY_REGISTRATION_MSG_PACK_HINT, pm.hint); byte[] expectedSignature = Arrays.copyOfRange(message, message.length - 64, message.length); assertArrayEquals(expectedSignature, pm.signature); diff --git a/src/test/java/com/ubirch/protocol/ProtocolFixtures.java b/src/test/java/com/ubirch/protocol/ProtocolFixtures.java index bca37e3..33627c9 100644 --- a/src/test/java/com/ubirch/protocol/ProtocolFixtures.java +++ b/src/test/java/com/ubirch/protocol/ProtocolFixtures.java @@ -55,6 +55,11 @@ public class ProtocolFixtures { protected static List expectedChainedMessagesJson; protected static byte[] expectedChainedMessageWithHash; // fixtures used in the test + protected static byte[] hashedTrackleMessage; + protected static byte[] hashedTrackleSignature; + protected static byte[] hashedTrackleSigned; + protected static byte[] hashedTracklePayload; + private static byte[] EdDSAKeyPrivatePart; private static byte[] EdDSAKeyPublicPart; @@ -68,9 +73,13 @@ protected static void initialize() throws DecoderException, IOException { expectedSignedMessage = Hex.decodeHex(fixtures.getProperty("signedMessage").toCharArray()); expectedSignedMessageHash = Hex.decodeHex(fixtures.getProperty("signedMessageHash").toCharArray()); - expectedSignedMessageWithHash = Hex.decodeHex(fixtures.getProperty("signedMessageWithHash").toCharArray()); + hashedTrackleMessage = Hex.decodeHex(fixtures.getProperty("hashedTrackleMsg").toCharArray()); + hashedTrackleSignature = Arrays.copyOfRange(hashedTrackleMessage, hashedTrackleMessage.length - 64, hashedTrackleMessage.length); + hashedTrackleSigned = Arrays.copyOfRange(hashedTrackleMessage, 0, hashedTrackleMessage.length - 66); + hashedTracklePayload = Arrays.copyOfRange(hashedTrackleMessage, hashedTrackleMessage.length - 130, hashedTrackleMessage.length - 66); + expectedChainedMessages = new ArrayList<>(3); expectedChainedMessages.add(Hex.decodeHex(fixtures.getProperty("chainMessage01").toCharArray())); expectedChainedMessages.add(Hex.decodeHex(fixtures.getProperty("chainMessage02").toCharArray())); @@ -140,8 +149,7 @@ public byte[] sign(UUID uuid, byte[] data, int offset, int len) throws InvalidKe } @Override - public boolean verify(UUID uuid, byte[] data, int offset, int len, byte[] signature) - throws SignatureException { + public boolean verify(UUID uuid, byte[] data, int offset, int len, byte[] signature) throws SignatureException { try { MessageDigest md = (MessageDigest) sha512.clone(); md.update(data, offset, len); @@ -162,8 +170,7 @@ public boolean verify(UUID uuid, byte[] data, int offset, int len, byte[] signat logger.error("unable to clone SHA512 instance", e); return false; } catch (SignatureException e) { - logger.warn(String.format("verification failed: m=%s s=%s", - Hex.encodeHexString(data), Hex.encodeHexString(signature))); + logger.warn(String.format("verification failed: m=%s s=%s", Hex.encodeHexString(data), Hex.encodeHexString(signature))); throw new SignatureException(e); } catch (IOException e) { throw new SignatureException(e); diff --git a/src/test/java/com/ubirch/protocol/VerificationTest.java b/src/test/java/com/ubirch/protocol/VerificationTest.java index 2de0ddc..81aa272 100644 --- a/src/test/java/com/ubirch/protocol/VerificationTest.java +++ b/src/test/java/com/ubirch/protocol/VerificationTest.java @@ -34,6 +34,7 @@ import java.util.UUID; import static com.ubirch.protocol.ProtocolMessage.SIGNED; +import static com.ubirch.protocol.codec.ProtocolHints.BINARY_OR_UNKNOWN_PAYLOAD_MSG_PACK_HINT; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -86,7 +87,7 @@ public boolean verify(UUID uuid, byte[] data, int offset, int len, byte[] signat assertEquals(2, pm.version >> 4, "unexpected protocol version for v2 message"); assertEquals(SIGNED & 0x0f, pm.version & 0x0f); assertEquals(TEST_UUID, pm.uuid); - assertEquals(0x00, pm.hint); + assertEquals(BINARY_OR_UNKNOWN_PAYLOAD_MSG_PACK_HINT, pm.hint); byte[] expectedSignature = Arrays.copyOfRange(message, message.length - 64, message.length); assertArrayEquals(expectedSignature, pm.signature); @@ -175,7 +176,7 @@ public boolean verify(UUID uuid, byte[] data, int offset, int len, byte[] signat assertEquals(2, pm.version >> 4, "unexpected protocol version for v2 message"); assertEquals(SIGNED & 0x0f, pm.version & 0x0f); assertEquals(TEST_UUID, pm.uuid); - assertEquals(0x00, pm.hint); + assertEquals(BINARY_OR_UNKNOWN_PAYLOAD_MSG_PACK_HINT, pm.hint); assertArrayEquals(Base64.decode("lJ1uJJIGuRYZXQDGBV+7PGVE0MGGaVdzm7zSmtu3PsFaBn5hjfc0MD25doVwatn3phnujc0o1LUMACFTF6xEXg=="), pm.getPayload().binaryValue()); byte[] expectedSignature = Arrays.copyOfRange(message, message.length - 64, message.length); assertArrayEquals(expectedSignature, pm.signature); diff --git a/src/test/java/com/ubirch/protocol/codec/MsgPackProtocolDecoderTest.java b/src/test/java/com/ubirch/protocol/codec/MsgPackProtocolDecoderTest.java index b9c522f..0465fb2 100644 --- a/src/test/java/com/ubirch/protocol/codec/MsgPackProtocolDecoderTest.java +++ b/src/test/java/com/ubirch/protocol/codec/MsgPackProtocolDecoderTest.java @@ -20,8 +20,6 @@ import com.ubirch.protocol.ProtocolFixtures; import com.ubirch.protocol.ProtocolMessage; import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.security.InvalidKeyException; import java.security.SignatureException; @@ -35,26 +33,25 @@ * @author Matthias L. Jugel */ class MsgPackProtocolDecoderTest extends ProtocolFixtures { - private final Logger logger = LoggerFactory.getLogger(MsgPackProtocolDecoderTest.class); private static final byte[] expectedSimpleSignature = Arrays.copyOfRange(expectedSignedMessage, expectedSignedMessage.length - 64, expectedSignedMessage.length); @Test - void testMsgPackProtocolDecoderInstance() { + void testInstance() { ProtocolDecoder decoder = MsgPackProtocolDecoder.getDecoder(); assertNotNull(decoder, "decoder should not be null"); assertEquals(decoder, MsgPackProtocolDecoder.getDecoder(), "decoder should be a singleton"); } @Test - void testMsgPackProtocolDecoderBrokenMsgPack() { + void testBrokenMsgPack() { assertThrows(ProtocolException.class, () -> MsgPackProtocolDecoder.getDecoder().decode(new byte[]{(byte) 0xEF, 44})); } @Test - void testMsgPackProtocolDecoderSignedMessage() throws ProtocolException { + void testSignedMessage() throws ProtocolException { ProtocolMessage pm = MsgPackProtocolDecoder.getDecoder().decode(expectedSignedMessage); assertEquals(ProtocolMessage.SIGNED, pm.getVersion()); assertEquals(testUUID, pm.getUUID()); @@ -65,7 +62,7 @@ void testMsgPackProtocolDecoderSignedMessage() throws ProtocolException { } @Test - void testMsgPackProtocolDecoderSignedMessageFromParts() throws ProtocolException { + void testSignedMessageFromParts() throws ProtocolException { ProtocolMessage pm = MsgPackProtocolDecoder.getDecoder().decode(expectedSignedMessage); assertEquals(ProtocolMessage.SIGNED, pm.getVersion()); assertArrayEquals(expectedSimpleSignature, pm.getSignature()); @@ -75,7 +72,38 @@ void testMsgPackProtocolDecoderSignedMessageFromParts() throws ProtocolException } @Test - void testMsgPackProtocolDecoderChainedMessage() throws ProtocolException { + void testDecodeHashedTrackleMsgPack() throws ProtocolException { + ProtocolMessage pm = MsgPackProtocolDecoder.getDecoder().decode(hashedTrackleMessage); + assertEquals(ProtocolMessage.CHAINED, pm.getVersion()); + assertArrayEquals(hashedTrackleSignature, pm.getSignature()); + byte[][] dataToVerifyAndSignature = MsgPackProtocolDecoder.getDecoder().getDataToVerifyAndSignature(hashedTrackleMessage); + assertArrayEquals(pm.getSigned(), dataToVerifyAndSignature[0]); + assertArrayEquals(pm.getSignature(), dataToVerifyAndSignature[1]); + assertArrayEquals(pm.getSigned(), hashedTrackleSigned); + } + + @Test + void testIsHashedTrackleTypePack() { + assert (MsgPackProtocolDecoder.getDecoder().isHashedTrackleMsgType(hashedTrackleMessage)); + } + + @Test + void testIsNotHashedTrackleTypePack() { + assert (!MsgPackProtocolDecoder.getDecoder().isHashedTrackleMsgType(expectedSignedMessage)); + } + + @Test + void testSignedIsHashedTrackleTypePack() { + assert (MsgPackProtocolDecoder.getDecoder().isSignedOfHashedTrackleMsgType(hashedTrackleSigned)); + } + + @Test + void testSignedIsNotHashedTrackleTypePack() { + assert (!MsgPackProtocolDecoder.getDecoder().isSignedOfHashedTrackleMsgType(expectedSignedMessage)); + } + + @Test + void testChainedMessage() throws ProtocolException { byte[] lastSignature = new byte[64]; for (int i = 0; i < 3; i++) { byte[] expectedMsg = expectedChainedMessages.get(i); @@ -92,7 +120,7 @@ void testMsgPackProtocolDecoderChainedMessage() throws ProtocolException { } @Test - void testMsgPackProtocolDecoderChainedMessageFromParts() throws ProtocolException { + void testChainedMessageFromParts() throws ProtocolException { for (int i = 0; i < 3; i++) { byte[] expectedMsg = expectedChainedMessages.get(i); ProtocolMessage pm = MsgPackProtocolDecoder.getDecoder().decode(expectedMsg); @@ -106,7 +134,7 @@ void testMsgPackProtocolDecoderChainedMessageFromParts() throws ProtocolExceptio } @Test - void testMsgPackProtocolDecoderVerifySignedMessage() throws SignatureException, ProtocolException { + void testVerifySignedMessage() throws SignatureException, ProtocolException { ProtocolMessage pm = MsgPackProtocolDecoder.getDecoder() .decode(expectedSignedMessage, (uuid, data, offset, len, signature) -> true); @@ -121,14 +149,14 @@ void testMsgPackProtocolDecoderVerifySignedMessage() throws SignatureException, } @Test - void testMsgPackProtocolDecoderVerifySignedMessageFails() { + void testVerifySignedMessageFails() { MsgPackProtocolDecoder decoder = MsgPackProtocolDecoder.getDecoder(); assertThrows(SignatureException.class, () -> decoder.decode(expectedSignedMessage, (uuid, data, offset, len, signature) -> false)); } @Test - void testMsgPackProtocolDecoderVerifySignedInvalidKey() { + void testVerifySignedInvalidKey() { MsgPackProtocolDecoder decoder = MsgPackProtocolDecoder.getDecoder(); assertThrows(ProtocolException.class, () -> decoder.decode(expectedSignedMessage, (uuid, data, offset, len, signature) -> { @@ -137,7 +165,7 @@ void testMsgPackProtocolDecoderVerifySignedInvalidKey() { } @Test - void testMsgPackProtocolDecoderVerifyMsgPackEnvelopeBroken() { + void testVerifyMsgPackEnvelopeBroken() { // create a broken json node and set the payload to force a JsonProcessingException MsgPackProtocolDecoder decoder = MsgPackProtocolDecoder.getDecoder(); assertThrows(ProtocolException.class, () -> diff --git a/src/test/java/com/ubirch/protocol/codec/MsgPackProtocolEncoderTest.java b/src/test/java/com/ubirch/protocol/codec/MsgPackProtocolEncoderTest.java index 8b60aef..f242f6e 100644 --- a/src/test/java/com/ubirch/protocol/codec/MsgPackProtocolEncoderTest.java +++ b/src/test/java/com/ubirch/protocol/codec/MsgPackProtocolEncoderTest.java @@ -19,6 +19,7 @@ import com.ubirch.protocol.ProtocolException; import com.ubirch.protocol.ProtocolFixtures; import com.ubirch.protocol.ProtocolMessage; +import org.apache.commons.codec.binary.Hex; import org.junit.jupiter.api.Test; import org.msgpack.core.MessagePack; import org.msgpack.core.MessageUnpacker; @@ -29,7 +30,9 @@ import java.security.NoSuchAlgorithmException; import java.security.SignatureException; import java.util.Arrays; +import java.util.UUID; +import static com.ubirch.protocol.codec.ProtocolHints.HASHED_TRACKLE_MSG_PACK_HINT; import static org.junit.jupiter.api.Assertions.*; /** @@ -38,29 +41,30 @@ * @author Matthias L. Jugel */ class MsgPackProtocolEncoderTest extends ProtocolFixtures { + @Test - void testMsgPackProtocolEncoderInstance() { + void testInstance() { ProtocolEncoder encoder = MsgPackProtocolEncoder.getEncoder(); assertNotNull(encoder, "encoder should not be null"); assertEquals(encoder, MsgPackProtocolEncoder.getEncoder(), "encoder should be a singleton"); } @Test - void testMsgPackProtocolEncoderEmptyEnvelopeException() { + void testEmptyEnvelopeException() { ProtocolEncoder encoder = MsgPackProtocolEncoder.getEncoder(); ProtocolMessage pm = new ProtocolMessage(); assertThrows(ProtocolException.class, () -> encoder.encode(pm, (uuid, data, offset, len) -> null)); } @Test - void testMsgPackProtocolEncoderVersionException() { + void testVersionException() { ProtocolMessage pm = new ProtocolMessage(0, testUUID, 0xEF, 1); ProtocolEncoder encoder = MsgPackProtocolEncoder.getEncoder(); assertThrows(ProtocolException.class, () -> encoder.encode(pm, (uuid, data, offset, len) -> null)); } @Test - void testMsgPackProtocolEncoderArgumentExceptions() { + void testArgumentExceptions() { ProtocolMessage pm = new ProtocolMessage(ProtocolMessage.SIGNED, testUUID, 0xEF, 1); ProtocolEncoder encoder = MsgPackProtocolEncoder.getEncoder(); @@ -71,7 +75,7 @@ void testMsgPackProtocolEncoderArgumentExceptions() { } @Test - void testMsgPackProtocolEncoderEncodeSigned() throws NoSuchAlgorithmException, SignatureException, IOException { + void testEncodeSigned() throws NoSuchAlgorithmException, SignatureException, IOException { ProtocolMessage pm = new ProtocolMessage(ProtocolMessage.SIGNED, testUUID, 0xEF, 1); ProtocolEncoder encoder = MsgPackProtocolEncoder.getEncoder(); @@ -96,7 +100,7 @@ void testMsgPackProtocolEncoderEncodeSigned() throws NoSuchAlgorithmException, S } @Test - void testMsgPackProtocolEncoderEncodeChained() throws NoSuchAlgorithmException, SignatureException, IOException { + void testEncodeChained() throws NoSuchAlgorithmException, SignatureException, IOException { byte[] lastSignature = new byte[64]; for (int i = 0; i < 3; i++) { for (int n = 0; n < lastSignature.length; n++) { @@ -134,7 +138,36 @@ void testMsgPackProtocolEncoderEncodeChained() throws NoSuchAlgorithmException, } @Test - void testMsgPackProtocolEncoderInvalidKeyException() { + void testEncodeHashedTrackleMsg() { + UUID trackleId = UUID.fromString("d3407cca-cbfa-474d-8d57-433643eb1e58"); + byte[] prevSignature = new byte[64]; + try { + prevSignature = Hex.decodeHex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + ProtocolMessage pm = new ProtocolMessage(ProtocolMessage.CHAINED, trackleId, prevSignature, HASHED_TRACKLE_MSG_PACK_HINT, hashedTracklePayload); + ProtocolEncoder encoder = MsgPackProtocolEncoder.getEncoder(); + pm.setSignature(hashedTrackleSignature); + pm.setSigned(pm.getPayload().binaryValue()); + + byte[] msgPack = encoder.encode(pm); + + assertArrayEquals(hashedTrackleMessage, msgPack); + + MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(msgPack); + assertEquals(6, unpacker.unpackArrayHeader()); + assertEquals(ProtocolMessage.CHAINED, unpacker.unpackInt()); + assertArrayEquals(UUIDUtil.uuidToBytes(trackleId), unpacker.readPayload(unpacker.unpackBinaryHeader())); + assertArrayEquals(prevSignature, unpacker.readPayload(unpacker.unpackBinaryHeader())); + assertEquals(HASHED_TRACKLE_MSG_PACK_HINT, unpacker.unpackInt()); + assertArrayEquals(hashedTracklePayload, unpacker.readPayload(unpacker.unpackBinaryHeader())); + assertArrayEquals(hashedTrackleSignature, unpacker.readPayload(unpacker.unpackBinaryHeader())); + } catch ( Exception e) { + fail("decoding previous signature failed"); + Arrays.fill(prevSignature, (byte) 9); + } + } + + @Test + void testInvalidKeyException() { ProtocolMessage pm = new ProtocolMessage(ProtocolMessage.SIGNED, testUUID, 2, 3); ProtocolEncoder encoder = MsgPackProtocolEncoder.getEncoder(); @@ -145,7 +178,7 @@ void testMsgPackProtocolEncoderInvalidKeyException() { } @Test - void testMsgPackProtocolEncoderMissingSignature() { + void testMissingSignature() { ProtocolMessage pm = new ProtocolMessage(); ProtocolEncoder encoder = MsgPackProtocolEncoder.getEncoder(); @@ -155,7 +188,7 @@ void testMsgPackProtocolEncoderMissingSignature() { } @Test - void testMsgPackProtocolEncoderMissingSigned() { + void testMissingSigned() { ProtocolMessage pm = new ProtocolMessage(); pm.setSignature(new byte[64]); @@ -166,7 +199,7 @@ void testMsgPackProtocolEncoderMissingSigned() { } @Test - void testMsgPackProtocolEncoderRecreate() throws ProtocolException { + void testRecreate() throws ProtocolException { ProtocolMessage pm = MsgPackProtocolDecoder.getDecoder().decode(expectedSignedMessage); ProtocolEncoder encoder = MsgPackProtocolEncoder.getEncoder(); diff --git a/src/test/resources/protocol_test.properties b/src/test/resources/protocol_test.properties index 46453c5..ba89e4c 100644 --- a/src/test/resources/protocol_test.properties +++ b/src/test/resources/protocol_test.properties @@ -35,3 +35,4 @@ chainMessage01.json={"chain":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA chaindMessage02.json={"chain":"YyC6ChlzkEOxL0oH98ytZz4ZOUEmE3uFlt3Ildy2X1/Pdp9BtSQvMScZKjUK6Y0berKHKR7LRYAwD7Ko+BBXCA==","hint":238,"payload":2,"signature":"vWL1A0nvlDzoFiD85pKqi+ruFrZKsH33VVRhqe+wu0FoqqIq2rgbVXy4RLhLxDTQVRi2jhzdUm4fBVLds23AAA==","uuid":"6eac4d0b-16e6-4508-8c46-22e7451ea5a1","version":35} chainMessage03.json={"chain":"vWL1A0nvlDzoFiD85pKqi+ruFrZKsH33VVRhqe+wu0FoqqIq2rgbVXy4RLhLxDTQVRi2jhzdUm4fBVLds23AAA==","hint":238,"payload":3,"signature":"42qQaAwkKux38L0q1p6PCHA2Hf/bNPQkXK5m26JobollOBrR/OqxF7RV+SZVxx8uHCP+QExplm0++Lcf45KWAg==","uuid":"6eac4d0b-16e6-4508-8c46-22e7451ea5a1","version":35} chainedMessageWithHash=9623c4106eac4d0b16e645088c4622e7451ea5a1c44000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ccefc4404dff4ea340f0a823f15d3f4f01ab62eae0e5da579ccb851f8db9dfe84c58b2b37b89903a740e1ee172da793a6e79d560e5f7f9bd058a12a280433ed6fa46510ac440dde50cad0db567dee187513e2d11bb2d9ba24a85fe6dddc4a0dc0b28fa2cd28bca72a60aa15dda962ae46488c80ae67a67445d257c56febf4f3c5221d95c2309 +hashedTrackleMsg=9623c410d3407ccacbfa474d8d57433643eb1e58c4400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000056c4401537892d9ad4d0f43f9ef0159e43c0496dd6491357a18ae9d34d8a86a714451b670a411992818ea1d5f39a8606513a3b6080f33ed647bb80a22ab768823927d7c4405521910927485e8cc201cf15fb89db4949e0b9a6d4ac9c362ca60ba90b6579b00a24b30f3e17d8d266aeb509b7f3246f8da985667819c8f5d110f679768bf104 \ No newline at end of file