From 0dbc9b85302d24e35c094bb54febad7de408dbd5 Mon Sep 17 00:00:00 2001 From: vanitasvitae Date: Tue, 30 May 2017 18:20:13 +0200 Subject: [PATCH] Add Hash classes (XEP-0300) --- smack-experimental/build.gradle | 2 + .../jivesoftware/smackx/hash/HashUtil.java | 186 ++++++++++++++++++ .../smackx/hash/element/HashElement.java | 91 +++++++++ .../smackx/hash/element/package-info.java | 22 +++ .../smackx/hash/package-info.java | 21 ++ .../hash/provider/HashElementProvider.java | 35 ++++ .../smackx/hash/provider/package-info.java | 22 +++ .../smackx/hash/HashElementTest.java | 28 +++ .../smackx/hash/HashUtilTest.java | 119 +++++++++++ 9 files changed, 526 insertions(+) create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/hash/HashUtil.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/hash/element/HashElement.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/hash/element/package-info.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/hash/package-info.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/hash/provider/HashElementProvider.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/hash/provider/package-info.java create mode 100644 smack-experimental/src/test/java/org/jivesoftware/smackx/hash/HashElementTest.java create mode 100644 smack-experimental/src/test/java/org/jivesoftware/smackx/hash/HashUtilTest.java diff --git a/smack-experimental/build.gradle b/smack-experimental/build.gradle index 51315f23a..2f4d4e0d5 100644 --- a/smack-experimental/build.gradle +++ b/smack-experimental/build.gradle @@ -10,4 +10,6 @@ dependencies { testCompile project(path: ":smack-core", configuration: "testRuntime") testCompile project(path: ":smack-core", configuration: "archives") testCompile project(path: ":smack-extensions", configuration: "testRuntime") + + compile "org.bouncycastle:bcprov-jdk15on:1.57" } diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/hash/HashUtil.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/hash/HashUtil.java new file mode 100644 index 000000000..8af6d4b85 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/hash/HashUtil.java @@ -0,0 +1,186 @@ +/** + * + * Copyright © 2017 Paul Schaub + * + * 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 org.jivesoftware.smackx.hash; + +import org.bouncycastle.jcajce.provider.digest.Blake2b; +import org.bouncycastle.jcajce.provider.digest.SHA224; +import org.bouncycastle.jcajce.provider.digest.SHA256; +import org.bouncycastle.jcajce.provider.digest.SHA3; +import org.bouncycastle.jcajce.provider.digest.SHA384; +import org.bouncycastle.jcajce.provider.digest.SHA512; +import org.jivesoftware.smack.util.MD5; +import org.jivesoftware.smack.util.SHA1; + +import java.math.BigInteger; + +/** + * Class that provides hash functionality. + */ +public final class HashUtil { + + public enum ALGORITHM { + MD5 ("md5"), + SHA_1 ("sha-1"), + SHA_224 ("sha-224"), + SHA_256 ("sha-256"), + SHA_384 ("sha-384"), + SHA_512 ("sha-512"), + SHA3_224 ("sha3-224"), + SHA3_256 ("sha3-256"), + SHA3_384 ("sha3-384"), + SHA3_512 ("sha3-512"), + ID_BLAKE2B160 ("id-blake2b160"), + ID_BLAKE2B256 ("id-blake2b256"), + ID_BLAKE2B384 ("id-blake2b384"), + ID_BLAKE2B512 ("id-blake2b512"); + + private final String name; + + ALGORITHM(String name) { + this.name = name; + } + + public boolean equalsName(String otherName) { + return name.equals(otherName); + } + + @Override + public String toString() { + return this.name; + } + + public static ALGORITHM get(String s) { + for (ALGORITHM a : ALGORITHM.values()) { + if (s.equals(a.toString())) { + return a; + } + } + throw new IllegalArgumentException("No ALGORITHM enum with this name found."); + } + } + + public static byte[] hash(ALGORITHM algorithm, byte[] data) { + byte[] hash; + switch (algorithm) { + case MD5: + hash = md5(data); + break; + case SHA_1: + hash = sha_1(data); + break; + case SHA_224: + hash = sha_224(data); + break; + case SHA_256: + hash = sha_256(data); + break; + case SHA_384: + hash = sha_384(data); + break; + case SHA_512: + hash = sha_512(data); + break; + case SHA3_224: + hash = sha3_224(data); + break; + case SHA3_256: + hash = sha3_256(data); + break; + case SHA3_384: + hash = sha3_384(data); + break; + case SHA3_512: + hash = sha3_512(data); + break; + case ID_BLAKE2B160: + hash = id_blake2b160(data); + break; + case ID_BLAKE2B256: + hash = id_blake2b256(data); + break; + case ID_BLAKE2B384: + hash = id_blake2b384(data); + break; + case ID_BLAKE2B512: + hash = id_blake2b512(data); + break; + default: + throw new AssertionError("Invalid enum value."); + } + return hash; + } + + public static byte[] md5(byte[] data) { + return MD5.bytes(data); + } + + public static byte[] sha_1(byte[] data) { + return SHA1.bytes(data); + } + + public static byte[] sha_224(byte[] data) { + return new SHA224.Digest().digest(data); + } + + public static byte[] sha_256(byte[] data) { + return new SHA256.Digest().digest(data); + } + + public static byte[] sha_384(byte[] data) { + return new SHA384.Digest().digest(data); + } + + public static byte[] sha_512(byte[] data) { + return new SHA512.Digest().digest(data); + } + + public static byte[] sha3_224(byte[] data) { + return new SHA3.Digest224().digest(data); + } + + public static byte[] sha3_256(byte[] data) { + return new SHA3.Digest256().digest(data); + } + + public static byte[] sha3_384(byte[] data) { + return new SHA3.Digest384().digest(data); + } + + public static byte[] sha3_512(byte[] data) { + return new SHA3.Digest512().digest(data); + } + + public static byte[] id_blake2b160(byte[] data) { + return new Blake2b.Blake2b160().digest(data); + } + + public static byte[] id_blake2b256(byte[] data) { + return new Blake2b.Blake2b256().digest(data); + } + + public static byte[] id_blake2b384(byte[] data) { + return new Blake2b.Blake2b384().digest(data); + } + + public static byte[] id_blake2b512(byte[] data) { + return new Blake2b.Blake2b512().digest(data); + } + + public static String hex(byte[] hash) { + return new BigInteger(1, hash).toString(16); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/hash/element/HashElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/hash/element/HashElement.java new file mode 100644 index 000000000..b9de53adc --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/hash/element/HashElement.java @@ -0,0 +1,91 @@ +/** + * + * Copyright © 2017 Paul Schaub + * + * 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 org.jivesoftware.smackx.hash.element; + + +import org.jivesoftware.smack.packet.ExtensionElement; +import org.jivesoftware.smack.util.XmlStringBuilder; +import org.jivesoftware.smack.util.stringencoder.Base64; +import org.jivesoftware.smackx.hash.HashUtil; + +import static org.jivesoftware.smack.util.Objects.requireNonNull; + +/** + * Represent a hash element. + * + * @author Paul Schaub + */ +public class HashElement implements ExtensionElement { + + + + public static final String ELEMENT = "hash"; + public static final String NAMESPACE = "urn:xmpp:hashes:2"; + public static final String ALGO = "algo"; + + private final HashUtil.ALGORITHM algorithm; + private final byte[] hash; + private final String hashB64; + + public HashElement(HashUtil.ALGORITHM type, byte[] hash) { + this.algorithm = requireNonNull(type); + this.hash = requireNonNull(hash); + hashB64 = Base64.encodeToString(hash); + } + + public HashElement(HashUtil.ALGORITHM type, String hashB64) { + this.algorithm = type; + this.hash = Base64.decode(hashB64); + this.hashB64 = hashB64; + } + + public static HashElement fromData(HashUtil.ALGORITHM algorithm, byte[] data) { + return new HashElement(algorithm, HashUtil.hash(algorithm, data)); + } + + public HashUtil.ALGORITHM getAlgorithm() { + return algorithm; + } + + public byte[] getHash() { + return hash; + } + + public String getHashB64() { + return hashB64; + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public CharSequence toXML() { + XmlStringBuilder sb = new XmlStringBuilder(this); + sb.attribute(ALGO, algorithm.toString()); + sb.rightAngleBracket(); + sb.append(hashB64); + sb.closeElement(this); + return sb; + } + + @Override + public String getNamespace() { + return NAMESPACE; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/hash/element/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/hash/element/package-info.java new file mode 100644 index 000000000..08af8deec --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/hash/element/package-info.java @@ -0,0 +1,22 @@ +/** + * + * Copyright © 2014 Florian Schmaus + * + * 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. + */ + +/** + * XEP-0300 - Use of cryptographic hash functions. + * Element classes. + */ +package org.jivesoftware.smackx.hash.element; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/hash/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/hash/package-info.java new file mode 100644 index 000000000..0021d7196 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/hash/package-info.java @@ -0,0 +1,21 @@ +/** + * + * Copyright © 2014 Florian Schmaus + * + * 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. + */ + +/** + * XEP-0300 - Use of cryptographic hash functions. + */ +package org.jivesoftware.smackx.hash; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/hash/provider/HashElementProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/hash/provider/HashElementProvider.java new file mode 100644 index 000000000..5d22eae36 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/hash/provider/HashElementProvider.java @@ -0,0 +1,35 @@ +/** + * + * Copyright © 2017 Paul Schaub + * + * 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 org.jivesoftware.smackx.hash.provider; + +import org.jivesoftware.smack.provider.ExtensionElementProvider; +import org.jivesoftware.smackx.hash.HashUtil; +import org.jivesoftware.smackx.hash.element.HashElement; +import org.xmlpull.v1.XmlPullParser; + +/** + * Provider for HashElements + */ +public class HashElementProvider extends ExtensionElementProvider { + + @Override + public HashElement parse(XmlPullParser parser, int initialDepth) throws Exception { + String algo = parser.getAttributeValue(null, HashElement.ALGO); + String hashB64 = parser.nextText(); + return new HashElement(HashUtil.ALGORITHM.get(algo), hashB64); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/hash/provider/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/hash/provider/package-info.java new file mode 100644 index 000000000..aac6fd4ae --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/hash/provider/package-info.java @@ -0,0 +1,22 @@ +/** + * + * Copyright © 2014 Florian Schmaus + * + * 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. + */ + +/** + * XEP-0300 - Use of cryptographic hash functions. + * Provider classes. + */ +package org.jivesoftware.smackx.hash.provider; diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/hash/HashElementTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/hash/HashElementTest.java new file mode 100644 index 000000000..a2df65668 --- /dev/null +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/hash/HashElementTest.java @@ -0,0 +1,28 @@ +package org.jivesoftware.smackx.hash; + +import org.jivesoftware.smack.test.util.SmackTestSuite; +import org.jivesoftware.smack.test.util.TestUtils; +import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smackx.hash.element.HashElement; +import org.jivesoftware.smackx.hash.provider.HashElementProvider; +import org.junit.Test; + +import static junit.framework.TestCase.assertEquals; + +/** + * Created by vanitas on 30.05.17. + */ +public class HashElementTest extends SmackTestSuite { + + @Test + public void stanzaTest() throws Exception { + String message = "Hello World!"; + HashElement element = HashElement.fromData(HashUtil.ALGORITHM.SHA_256, message.getBytes(StringUtils.UTF8)); + String expected = "f4OxZX/x/FO5LcGBSKHWXfwtSx+j1ncoSt3SABJtkGk="; + assertEquals(expected, element.toXML().toString()); + + HashElement parsed = new HashElementProvider().parse(TestUtils.getParser(expected)); + assertEquals(expected, parsed.toXML().toString()); + } + +} diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/hash/HashUtilTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/hash/HashUtilTest.java new file mode 100644 index 000000000..02c669a0e --- /dev/null +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/hash/HashUtilTest.java @@ -0,0 +1,119 @@ +package org.jivesoftware.smackx.hash; + +import org.jivesoftware.smack.test.util.SmackTestSuite; +import org.jivesoftware.smack.util.StringUtils; +import org.junit.Test; + +import java.io.UnsupportedEncodingException; + +import static junit.framework.TestCase.assertEquals; + +/** + * Test HashUtil functionality. + */ +public class HashUtilTest extends SmackTestSuite { + + public static final String testString = "Hello World!"; + + private byte[] array() { + try { + return testString.getBytes(StringUtils.UTF8); + } catch (UnsupportedEncodingException e) { + throw new AssertionError("UTF8 MUST be supported."); + } + } + + @Test + public void md5Test() { + String expected = "ed076287532e86365e841e92bfc50d8c"; + String actual = HashUtil.hex(HashUtil.md5(array())); + assertEquals(expected, actual); + } + + @Test + public void sha1Test() { + String expected = "2ef7bde608ce5404e97d5f042f95f89f1c232871"; + String actual = HashUtil.hex(HashUtil.sha_1(array())); + assertEquals(expected, actual); + } + + @Test + public void sha224Test() { + String expected = "4575bb4ec129df6380cedde6d71217fe0536f8ffc4e18bca530a7a1b"; + String actual = HashUtil.hex(HashUtil.sha_224(array())); + assertEquals(expected, actual); + } + + @Test + public void sha256Test() { + String expected = "7f83b1657ff1fc53b92dc18148a1d65dfc2d4b1fa3d677284addd200126d9069"; + String actual = HashUtil.hex(HashUtil.sha_256(array())); + assertEquals(expected, actual); + } + + @Test + public void sha384Test() { + String expected = "bfd76c0ebbd006fee583410547c1887b0292be76d582d96c242d2a792723e3fd6fd061f9d5cfd13b8f961358e6adba4a"; + String actual = HashUtil.hex(HashUtil.sha_384(array())); + assertEquals(expected, actual); + } + + @Test + public void sha512Test() { + String expected = "861844d6704e8573fec34d967e20bcfef3d424cf48be04e6dc08f2bd58c729743371015ead891cc3cf1c9d34b49264b510751b1ff9e537937bc46b5d6ff4ecc8"; + String actual = HashUtil.hex(HashUtil.sha_512(array())); + assertEquals(expected, actual); + } + + @Test + public void sha3_224Test() { + String expected = "716596afadfa17cd1cb35133829a02b03e4eed398ce029ce78a2161d"; + String actual = HashUtil.hex(HashUtil.sha3_224(array())); + assertEquals(expected, actual); + } + + @Test + public void sha3_256Test() { + String expected = "d0e47486bbf4c16acac26f8b653592973c1362909f90262877089f9c8a4536af"; + String actual = HashUtil.hex(HashUtil.sha3_256(array())); + assertEquals(expected, actual); + } + + @Test + public void sha3_384Test() { + String expected = "f324cbd421326a2abaedf6f395d1a51e189d4a71c755f531289e519f079b224664961e385afcc37da348bd859f34fd1c"; + String actual = HashUtil.hex(HashUtil.sha3_384(array())); + assertEquals(expected, actual); + } + + @Test + public void sha3_512Test() { + String expected = "32400b5e89822de254e8d5d94252c52bdcb27a3562ca593e980364d9848b8041b98eabe16c1a6797484941d2376864a1b0e248b0f7af8b1555a778c336a5bf48"; + String actual = HashUtil.hex(HashUtil.sha3_512(array())); + assertEquals(expected, actual); + } + + public void id_blake2b160Test() { + String expected = "e7338d05e5aa2b5e4943389f9475fce2525b92f2"; + String actual = HashUtil.hex(HashUtil.id_blake2b160(array())); + assertEquals(expected, actual); + } + + public void id_blake2b256Test() { + String expected = "bf56c0728fd4e9cf64bfaf6dabab81554103298cdee5cc4d580433aa25e98b00"; + String actual = HashUtil.hex(HashUtil.id_blake2b256(array())); + assertEquals(expected, actual); + } + + public void id_blake2b384Test() { + String expected = "53fd759520545fe93270e61bac03b243b686af32ed39a4aa635555be47a89004851d6a13ece00d95b7bdf9910cb71071"; + String actual = HashUtil.hex(HashUtil.id_blake2b384(array())); + assertEquals(expected, actual); + } + + public void id_blake2b512Test() { + String expected = "54b113f499799d2f3c0711da174e3bc724737ad18f63feb286184f0597e1466436705d6c8e8c7d3d3b88f5a22e83496e0043c44a3c2b1700e0e02259f8ac468e"; + String actual = HashUtil.hex(HashUtil.id_blake2b512(array())); + assertEquals(expected, actual); + } +}