From 6731516dfe0242339a5e6999b469d62d6b86c252 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rikard=20H=C3=B6glund?= Date: Tue, 25 Jul 2023 15:23:18 -0700 Subject: [PATCH] Align with latest EDHOC code from its repository MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now aligned with commit 19bca97 in the branch for EDHOC. Signed-off-by: Rikard Höglund --- .../californium/edhoc/MessageProcessor.java | 142 ++++++++---------- .../edhoc/MessageProcessorTest.java | 48 +++--- 2 files changed, 86 insertions(+), 104 deletions(-) diff --git a/californium-extended/cf-edhoc/src/main/java/org/eclipse/californium/edhoc/MessageProcessor.java b/californium-extended/cf-edhoc/src/main/java/org/eclipse/californium/edhoc/MessageProcessor.java index e762736d..7a112ba4 100644 --- a/californium-extended/cf-edhoc/src/main/java/org/eclipse/californium/edhoc/MessageProcessor.java +++ b/californium-extended/cf-edhoc/src/main/java/org/eclipse/californium/edhoc/MessageProcessor.java @@ -303,7 +303,7 @@ public static List readMessage1(byte[] sequence, boolean isReq, responseCode = ResponseCode.BAD_REQUEST; error = true; } - else { + if (error == false) { // If the received message is a request (i.e. the CoAP client is the initiator), the first element // before the actual message_1 is the CBOR simple value 'true', i.e. the byte 0xf5, and it can be skipped if (isReq) { @@ -753,48 +753,9 @@ else if (session.getCurrentStep() != Constants.EDHOC_SENT_M1) { // CIPHERTEXT_2 ciphertext2 = new byte[ciphetertext2Length]; System.arraycopy(gY_Ciphertext2, gYLength, ciphertext2, 0, ciphetertext2Length); - - // Move to the next element of the CBOR sequence, i.e., C_R - index++; - - } - - // C_R - if (error == false) { - cR = objectList[index]; - - if (cR.getType() != CBORType.ByteString && cR.getType() != CBORType.Integer) { - errMsg = new String("C_R must be a byte string or an integer"); - responseCode = ResponseCode.BAD_REQUEST; - error = true; - } - if (error == false) { - connectionIdentifierResponder = decodeIdentifier(cR); - if (connectionIdentifierResponder == null) { - errMsg = new String("Invalid encoding of C_R"); - responseCode = ResponseCode.BAD_REQUEST; - error = true; - } - else { - session.setPeerConnectionId(connectionIdentifierResponder); - if (debugPrint) { - Util.nicePrint("Connection Identifier of the Responder", connectionIdentifierResponder); - Util.nicePrint("C_R", cR.EncodeToBytes()); - } - } - } - if (error == false) { - if (session.getApplicationProfile().getUsedForOSCORE() == true && - Arrays.equals(connectionIdentifierInitiator, connectionIdentifierResponder) == true) { - errMsg = new String("C_R must be different from C_I"); - responseCode = ResponseCode.BAD_REQUEST; - error = true; - } - } } - /* Return an EDHOC Error Message */ if (error == true) { @@ -815,9 +776,8 @@ else if (session.getCurrentStep() != Constants.EDHOC_SENT_M1) { objectListData2.add(objectList[i]); byte[] hashMessage1SerializedCBOR = CBORObject.FromObject(hashMessage1).EncodeToBytes(); byte[] gYSerializedCBOR = CBORObject.FromObject(gY).EncodeToBytes(); - byte[] cRSerializedCBOR = cR.EncodeToBytes(); - th2 = computeTH2(session, gYSerializedCBOR, cRSerializedCBOR, hashMessage1SerializedCBOR); + th2 = computeTH2(session, gYSerializedCBOR, hashMessage1SerializedCBOR); if (th2 == null) { errMsg = new String("Error when computing TH2"); responseCode = ResponseCode.INTERNAL_SERVER_ERROR; @@ -906,21 +866,54 @@ else if (debugPrint) { responseCode = ResponseCode.BAD_REQUEST; error = true; } - if (error == false && plaintextElementList.length < 2) { + if (error == false && plaintextElementList.length < 3) { errMsg = new String("Invalid format of the content encrypted as CIPHERTEXT_2"); responseCode = ResponseCode.BAD_REQUEST; error = true; } - else if (error == false && - plaintextElementList[0].getType() != CBORType.ByteString && - plaintextElementList[0].getType() != CBORType.Integer && - plaintextElementList[0].getType() != CBORType.Map) { + if (error == false) { + cR = plaintextElementList[0]; + + if (cR.getType() != CBORType.ByteString && cR.getType() != CBORType.Integer) { + errMsg = new String("C_R must be a byte string or an integer"); + responseCode = ResponseCode.BAD_REQUEST; + error = true; + } + if (error == false) { + connectionIdentifierResponder = decodeIdentifier(cR); + if (connectionIdentifierResponder == null) { + errMsg = new String("Invalid encoding of C_R"); + responseCode = ResponseCode.BAD_REQUEST; + error = true; + } + else { + session.setPeerConnectionId(connectionIdentifierResponder); + if (debugPrint) { + Util.nicePrint("Connection Identifier of the Responder", connectionIdentifierResponder); + Util.nicePrint("C_R", cR.EncodeToBytes()); + } + } + } + if (error == false) { + if (session.getApplicationProfile().getUsedForOSCORE() == true && + Arrays.equals(connectionIdentifierInitiator, connectionIdentifierResponder) == true) { + errMsg = new String("C_R must be different from C_I"); + responseCode = ResponseCode.BAD_REQUEST; + error = true; + } + } + + } + if (error == false && + plaintextElementList[1].getType() != CBORType.ByteString && + plaintextElementList[1].getType() != CBORType.Integer && + plaintextElementList[1].getType() != CBORType.Map) { errMsg = new String("Invalid format of ID_CRED_R"); responseCode = ResponseCode.BAD_REQUEST; error = true; } - else if (error == false) { - CBORObject rawIdCredR = plaintextElementList[0]; + if (error == false) { + CBORObject rawIdCredR = plaintextElementList[1]; // ID_CRED_R is a CBOR map with 'kid', and only 'kid' was transported if (rawIdCredR.getType() == CBORType.ByteString || rawIdCredR.getType() == CBORType.Integer) { @@ -941,14 +934,14 @@ else if (rawIdCredR.getType() == CBORType.Map) { responseCode = ResponseCode.BAD_REQUEST; error = true; } - else if (error == false && plaintextElementList[1].getType() != CBORType.ByteString) { + if (error == false && plaintextElementList[2].getType() != CBORType.ByteString) { errMsg = new String("Signature_or_MAC_2 must be a byte string"); responseCode = ResponseCode.BAD_REQUEST; error = true; } - else if (error == false && plaintextElementList.length > 2) { + if (error == false && plaintextElementList.length > 3) { // EAD_2 is present - ead2 = preParseEAD(plaintextElementList, 2, 2, session.getSupportedEADs()); + ead2 = preParseEAD(plaintextElementList, 3, 2, session.getSupportedEADs()); if (ead2.length == 1 && ead2[0].getType() == CBORType.TextString) { errMsg = new String(ead2[0].toString()); @@ -965,8 +958,6 @@ else if (error == false && plaintextElementList.length > 2) { } error = false; - - CBORObject peerCredentialCBOR = null; @@ -1064,7 +1055,7 @@ else if (debugPrint) { } // Verify Signature_or_MAC_2 - byte[] signatureOrMac2 = plaintextElementList[1].GetByteString(); + byte[] signatureOrMac2 = plaintextElementList[2].GetByteString(); if (debugPrint && signatureOrMac2 != null) { Util.nicePrint("Signature_or_MAC_2", signatureOrMac2); } @@ -1123,9 +1114,7 @@ else if (debugPrint) { errMsg, null, responseCode); } - - - + // Store PLAINTEXT_2 for the later computation of TH_3 session.setPlaintext2(plaintext2); @@ -1369,15 +1358,15 @@ else if (debugPrint) { responseCode = ResponseCode.BAD_REQUEST; error = true; } - else if (error == false && + if (error == false && plaintextElementList[0].getType() != CBORType.ByteString && plaintextElementList[0].getType() != CBORType.Integer && plaintextElementList[0].getType() != CBORType.Map) { - errMsg = new String("Invalid format of ID_CRED_I"); - responseCode = ResponseCode.BAD_REQUEST; - error = true; + errMsg = new String("Invalid format of ID_CRED_I"); + responseCode = ResponseCode.BAD_REQUEST; + error = true; } - else if (error == false) { + if (error == false) { CBORObject rawIdCredI = plaintextElementList[0]; // ID_CRED_I is a CBOR map with 'kid', and only 'kid' was transported @@ -1394,12 +1383,12 @@ else if (rawIdCredI.getType() == CBORType.Map) { error = true; } } - else if (error == false && plaintextElementList[1].getType() != CBORType.ByteString) { + if (error == false && plaintextElementList[1].getType() != CBORType.ByteString) { errMsg = new String("Signature_or_MAC_3 must be a byte string"); responseCode = ResponseCode.BAD_REQUEST; error = true; } - else if (error == false && plaintextElementList.length > 2) { + if (error == false && plaintextElementList.length > 2) { // EAD_3 is present ead3 = preParseEAD(plaintextElementList, 2, 3, session.getSupportedEADs()); @@ -2292,9 +2281,8 @@ else if (selectedSuite == Constants.EDHOC_CIPHER_SUITE_2 || selectedSuite == Con byte[] hashMessage1 = session.getHashMessage1(); // the hash of message_1, as plain bytes byte[] hashMessage1SerializedCBOR = CBORObject.FromObject(hashMessage1).EncodeToBytes(); byte[] gYSerializedCBOR = gY.EncodeToBytes(); - byte[] cRSerializedCBOR = cR.EncodeToBytes(); - byte[] th2 = computeTH2(session, gYSerializedCBOR, cRSerializedCBOR, hashMessage1SerializedCBOR); + byte[] th2 = computeTH2(session, gYSerializedCBOR, hashMessage1SerializedCBOR); if (th2 == null) { System.err.println("Error when computing TH_2"); errMsg = new String("Error when computing TH_2"); @@ -2446,6 +2434,7 @@ else if (debugPrint) { if (error == false) { // Prepare the plaintext List plaintextElementList = new ArrayList<>(); + plaintextElementList.add(cR); CBORObject plaintextElement = null; if (session.getIdCred().ContainsKey(HeaderKeys.KID.AsCBOR())) { // ID_CRED_R uses 'kid', whose value is the only thing to include in the plaintext @@ -2482,7 +2471,7 @@ else if (debugPrint) { // Compute CIPHERTEXT_2 - if (error == false) { + if (error == false) { byte[] ciphertext2 = Util.arrayXor(plaintext2, keystream2); @@ -2508,9 +2497,7 @@ else if (debugPrint) { if (debugPrint) { Util.nicePrint("G_Y | CIPHERTEXT_2", gY_Ciphertext2); } - - // The outer CBOR sequence finishes with the connection identifier C_R - objectList.add(cR); + } @@ -4321,24 +4308,19 @@ else if (authenticationMethod == Constants.EDHOC_AUTH_METHOD_0 || authentication * Compute the transcript hash TH2 * @param session The used EDHOC session * @param gY The G_Y ephemeral key from the EDHOC Message 2, as a serialized CBOR byte string - * @param cR The C_R connection identifier from the EDHOC Message 2, as a serialized CBOR Object * @param hashMessage1 The hash of EDHOC Message 1, as a serialized CBOR byte string * @return The computed TH2 */ - public static byte[] computeTH2(EdhocSession session, byte[] gY, byte[] cR, byte[] hashMessage1) { + public static byte[] computeTH2(EdhocSession session, byte[] gY, byte[] hashMessage1) { byte[] th2 = null; int selectedCipherSuite = session.getSelectedCipherSuite(); String hashAlgorithm = EdhocSession.getEdhocHashAlg(selectedCipherSuite); - int offset = 0; - byte[] hashInput = new byte[gY.length + cR.length + hashMessage1.length]; - System.arraycopy(gY, 0, hashInput, offset, gY.length); - offset += gY.length; - System.arraycopy(cR, 0, hashInput, offset, cR.length); - offset += cR.length; - System.arraycopy(hashMessage1, 0, hashInput, offset, hashMessage1.length); + byte[] hashInput = new byte[gY.length + hashMessage1.length]; + System.arraycopy(gY, 0, hashInput, 0, gY.length); + System.arraycopy(hashMessage1, 0, hashInput, gY.length, hashMessage1.length); Util.nicePrint("Input to calculate TH_2", hashInput); try { th2 = Util.computeHash(hashInput, hashAlgorithm); diff --git a/californium-extended/cf-edhoc/src/test/java/org/eclipse/californium/edhoc/MessageProcessorTest.java b/californium-extended/cf-edhoc/src/test/java/org/eclipse/californium/edhoc/MessageProcessorTest.java index 48d2e53d..44517487 100644 --- a/californium-extended/cf-edhoc/src/test/java/org/eclipse/californium/edhoc/MessageProcessorTest.java +++ b/californium-extended/cf-edhoc/src/test/java/org/eclipse/californium/edhoc/MessageProcessorTest.java @@ -526,7 +526,7 @@ public void testWriteMessage2CipherSuite0Method0() { // Compare with the expected value from the test vectors byte[] expectedMessage2 = StringUtil.hex2ByteArray( - "5870dc88d2d51da5ed67fc4616356bc8ca74ef9ebe8b387e623a360ba480b9b29d1c67b9cf55e7b74dd29cdce68e5c7f429c5ff7ed8f1ac3fb4035bd3254b26b63a857295afb2fdd4e479134223a9cd9992f306faf5c8bd721dc4fb2db37d376bbe4324e49b01ba434c2ddcca34431a71c734118"); + "5872dc88d2d51da5ed67fc4616356bc8ca74ef9ebe8b387e623a360ba480b9b29d1cbc26dd270fe9c02c44ce3934794b1cc62ba2ad5669fc0755c2a16b7e42ed14225fef1e451e453c21421d4d373f256b81b1937f5b199d67330521d025a0be4d26a3c20b828e9e0ef565a9343d81d9bbbda988"); Assert.assertArrayEquals(expectedMessage2, message2); @@ -608,9 +608,9 @@ public void testWriteMessage3CipherSuite0Method0() { /* Status from after receiving EDHOC Message 2 */ byte[] th2 = StringUtil.hex2ByteArray( - "3ab11700841fce193c323911edb317b046dcf24b9950fd624884f7f57cd98b07"); + "c6405c154c567466ab1df20369500e540e9f14bd3a796a0652cae66c9061688d"); byte[] prk3e2m = StringUtil.hex2ByteArray( - "2ae2421de9a72a7ae6715fb518f3ed30058fd9ca58b62568cafe7cdaa15a41f7"); + "d584ac2e5dad5a77d14b53ebe72ef1d5daa8860d399373bf2c240afa7ba804da"); @@ -676,7 +676,7 @@ public void testWriteMessage3CipherSuite0Method0() { // Set PLAINTEXT_2 from the previous protocol step byte[] plaintext2 = StringUtil.hex2ByteArray( - "a11822822e4879f2a41b510c1f9b5840af7381f19ae1fe0f53895b18e5818b1fe3e3463072c02ad39f202d3828aa6237c10b08668fc476964124031fed9f944e6a78797f5c084958db0f2089c21c5202"); + "4118a11822822e4879f2a41b510c1f9b584041e691275b840424255acb87e633d75dda71502da2e3da5fceeec4e3f76074486f87e66f2acaa1bbd48ce0e66a5d64389154482f9a5e572270633159f2b17e0e"); session.setPlaintext2(plaintext2); // Set PRK_3e2m from the previous protocol step @@ -693,7 +693,7 @@ public void testWriteMessage3CipherSuite0Method0() { // Note: the actual EDHOC message 3 starts with 0x58. The bytes 0x4118 (CBOR encoding for h'18') is prepended as C_R, // in order to pass the check against what returned by the EDHOC engine, to be sent as a CoAP request payload. byte[] expectedMessage3 = StringUtil.hex2ByteArray( - "4118" + "5858ba5e0e745bfa2a871d20cb02c800200771434b6e1ac98977ec733ec94c0633cb3ec0207898597f2c49d3a40f4c1451b43d0bcae4847a0d6cd32d5e8a3554f43f7a982904b077c5029b3dc7f05eedede3b02157c324c0db3e"); + "4118" + "5858aa966a1aa4fa449a172a160b96e644f6a33329f27c6af5bbefc61158d0addd99069b9a197ff7c90e62f3b55664c583747b9a402ccd68907fe458b16ad52d63a00e5a85df95ee7b1b498ac98342008c0471c1ae8d75825044"); Assert.assertArrayEquals(expectedMessage3, message3); @@ -705,8 +705,8 @@ public void testWriteMessage3CipherSuite0Method0() { // Compare with the expected value from the test vectors - byte[] expectedMasterSecret = StringUtil.hex2ByteArray("09c36661cf68f8c3ad216443cf6291e6"); - byte[] expectedMasterSalt = StringUtil.hex2ByteArray("1382bf719ee65c32"); + byte[] expectedMasterSecret = StringUtil.hex2ByteArray("fc9cfb0563ca3e28f880483b9c06bd03"); + byte[] expectedMasterSalt = StringUtil.hex2ByteArray("0ec09d453b089834"); Util.nicePrint("OSCORE Master Secret", masterSecret); Util.nicePrint("OSCORE Master Salt", masterSalt); @@ -738,8 +738,8 @@ public void testWriteMessage3CipherSuite0Method0() { Util.nicePrint("OSCORE Master Secret", masterSecret); Util.nicePrint("OSCORE Master Salt", masterSalt); - expectedMasterSecret = StringUtil.hex2ByteArray("f005280c948a64c46e33e9ea8de93115"); - expectedMasterSalt = StringUtil.hex2ByteArray("0b0af32aa49b3ce3"); + expectedMasterSecret = StringUtil.hex2ByteArray("50486d75823a592d1efd286a707fe87d"); + expectedMasterSalt = StringUtil.hex2ByteArray("6195cbb1ce031cae"); Assert.assertArrayEquals(expectedMasterSecret, masterSecret); Assert.assertArrayEquals(expectedMasterSalt, masterSalt); @@ -871,11 +871,11 @@ public void testWriteMessage4CipherSuite0Method0() { session.setPeerConnectionId(connectionIdentifierInitiator); // Store PRK_4e3m computed from the previous protocol step - byte[] prk4e3m = StringUtil.hex2ByteArray("2ae2421de9a72a7ae6715fb518f3ed30058fd9ca58b62568cafe7cdaa15a41f7"); + byte[] prk4e3m = StringUtil.hex2ByteArray("d584ac2e5dad5a77d14b53ebe72ef1d5daa8860d399373bf2c240afa7ba804da"); session.setPRK4e3m(prk4e3m); // Store TH_4 computed from the previous protocol step - byte[] th4 = StringUtil.hex2ByteArray("38e2e6f4641e814b721814c05b51ef0aa38bdb36074f981239e6474d9cccddc8"); + byte[] th4 = StringUtil.hex2ByteArray("6b13325a49bd9f970d3191ee317962df1d4438c66415eaa4cedd62b5b49d7bb7"); session.setTH4(th4); // Now write EDHOC message 4 @@ -883,7 +883,7 @@ public void testWriteMessage4CipherSuite0Method0() { // Compare with the expected value from the test vectors - byte[] expectedMessage4 = StringUtil.hex2ByteArray("48d5417c474cb4a302"); + byte[] expectedMessage4 = StringUtil.hex2ByteArray("48ee120e8b5e2a008f"); Assert.assertArrayEquals(expectedMessage4, message4); @@ -1130,7 +1130,7 @@ public void testWriteMessage2CipherSuite2Method3() { // Compare with the expected value from the test vectors byte[] expectedMessage2 = StringUtil.hex2ByteArray( - "582a419701d7f00a26c2dc587a36dd752549f33763c893422c8ea0f955a13a4ff5d5042459e2da6c75143f3527"); + "582b419701d7f00a26c2dc587a36dd752549f33763c893422c8ea0f955a13a4ff5d59862a11de42a95d785386a"); Assert.assertArrayEquals(expectedMessage2, message2); @@ -1213,9 +1213,9 @@ public void testWriteMessage3CipherSuite2Method3() { /* Status from after receiving EDHOC Message 2 */ - byte[] th2 = StringUtil.hex2ByteArray("9d2af3a3d3fc06aea8110f14ba12ad0b4fb7e5cdf59c7df1cf2dfe9c2024439c"); + byte[] th2 = StringUtil.hex2ByteArray("356efd53771425e008f3fe3a86c83ff4c6b16e57028ff39d5236c182b202084b"); - byte[] prk3e2m = StringUtil.hex2ByteArray("412d60cdf99dc7490754c969ad4c46b1350b908433ebf3fe063be8627fb35b3b"); + byte[] prk3e2m = StringUtil.hex2ByteArray("0ca3d3398296b3c03900987620c11f6fce70781c1d1219720f9ec08c122d8434"); /* Set up the session to use */ @@ -1279,7 +1279,7 @@ public void testWriteMessage3CipherSuite2Method3() { session.setTH2(th2); // Set PLAINTEXT_2 from the previous protocol step - byte[] plaintext2 = StringUtil.hex2ByteArray("3248d0d1a594797d0aaf"); + byte[] plaintext2 = StringUtil.hex2ByteArray("273248fa5efa2ebf920bf3"); session.setPlaintext2(plaintext2); // Set PRK_3e2m from the previous protocol step @@ -1296,7 +1296,7 @@ public void testWriteMessage3CipherSuite2Method3() { // Note: the actual EDHOC message 3 starts with 0x52. The byte 0x27 (CBOR encoding for -8) is prepended as C_R, // in order to pass the check against what returned by the EDHOC engine, to be sent as a CoAP request payload. - byte[] expectedMessage3 = StringUtil.hex2ByteArray("27" + "52c2b62835dc9b1f53419c1d3a2261eeed3505"); + byte[] expectedMessage3 = StringUtil.hex2ByteArray("27" + "52473dd16077dd71d65b56e6bd71e7a49d6012"); Assert.assertArrayEquals(expectedMessage3, message3); @@ -1308,8 +1308,8 @@ public void testWriteMessage3CipherSuite2Method3() { // Compare with the expected value from the test vectors - byte[] expectedMasterSecret = StringUtil.hex2ByteArray("07ce22f2638fca404dded72a25fa45f4"); - byte[] expectedMasterSalt = StringUtil.hex2ByteArray("5be3825f5a5284b7"); + byte[] expectedMasterSecret = StringUtil.hex2ByteArray("8c409a332223ad900e44f3434d2d2ce3"); + byte[] expectedMasterSalt = StringUtil.hex2ByteArray("6163f44be862adfa"); Util.nicePrint("OSCORE Master Secret", masterSecret); Util.nicePrint("OSCORE Master Salt", masterSalt); @@ -1338,8 +1338,8 @@ public void testWriteMessage3CipherSuite2Method3() { // Compare with the expected value from the test vectors - expectedMasterSecret = StringUtil.hex2ByteArray("4c75696cba179ca9f68707eedcde76e0"); - expectedMasterSalt = StringUtil.hex2ByteArray("9d954fc2e7abb4d0"); + expectedMasterSecret = StringUtil.hex2ByteArray("c91b164c810b29a63fcb73e51bc455f3"); + expectedMasterSalt = StringUtil.hex2ByteArray("73ce792459403680"); Util.nicePrint("OSCORE Master Secret", masterSecret); Util.nicePrint("OSCORE Master Salt", masterSalt); @@ -1478,11 +1478,11 @@ public void testWriteMessage4CipherSuite2Method3() { // Store PRK_4e3m computed from the previous protocol step - byte[] prk4e3m = StringUtil.hex2ByteArray("7d0159bbe45473c9402e0d42dbceb45dca05b744cae1e083e58315b8aa47ceec"); + byte[] prk4e3m = StringUtil.hex2ByteArray("e9cb832a240095d3d0643dbe12e9e2e7b18f0360a3172cea7ac0013ee240e072"); session.setPRK4e3m(prk4e3m); // Store TH_4 computed from the previous protocol step - byte[] th4 = StringUtil.hex2ByteArray("1f57dabf8f26da0657d9840c9b1077c1d4c47db243a8b41360a98ec4cb706b70"); + byte[] th4 = StringUtil.hex2ByteArray("baf60adbc500fce789af25b108ada2275575056c52c1c2036a2da4a643891cb4"); session.setTH4(th4); // Now write EDHOC message 4 @@ -1490,7 +1490,7 @@ public void testWriteMessage4CipherSuite2Method3() { // Compare with the expected value from the test vectors - byte[] expectedMessage4 = StringUtil.hex2ByteArray("486359ad21f077a9d1"); + byte[] expectedMessage4 = StringUtil.hex2ByteArray("488907436470a6e19f"); Assert.assertArrayEquals(expectedMessage4, message4);