001/**
002 *
003 * Copyright © 2017 Paul Schaub, 2019-2020 Florian Schmaus
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.jivesoftware.smackx.hashes;
018
019import static org.jivesoftware.smack.util.StringUtils.encodeHex;
020import static org.jivesoftware.smack.util.StringUtils.toUtf8Bytes;
021import static org.jivesoftware.smackx.hashes.HashManager.ALGORITHM.BLAKE2B160;
022import static org.jivesoftware.smackx.hashes.HashManager.ALGORITHM.BLAKE2B256;
023import static org.jivesoftware.smackx.hashes.HashManager.ALGORITHM.BLAKE2B384;
024import static org.jivesoftware.smackx.hashes.HashManager.ALGORITHM.BLAKE2B512;
025import static org.jivesoftware.smackx.hashes.HashManager.ALGORITHM.MD5;
026import static org.jivesoftware.smackx.hashes.HashManager.ALGORITHM.SHA3_224;
027import static org.jivesoftware.smackx.hashes.HashManager.ALGORITHM.SHA3_256;
028import static org.jivesoftware.smackx.hashes.HashManager.ALGORITHM.SHA3_384;
029import static org.jivesoftware.smackx.hashes.HashManager.ALGORITHM.SHA3_512;
030import static org.jivesoftware.smackx.hashes.HashManager.ALGORITHM.SHA_1;
031import static org.jivesoftware.smackx.hashes.HashManager.ALGORITHM.SHA_224;
032import static org.jivesoftware.smackx.hashes.HashManager.ALGORITHM.SHA_256;
033import static org.jivesoftware.smackx.hashes.HashManager.ALGORITHM.SHA_384;
034import static org.jivesoftware.smackx.hashes.HashManager.ALGORITHM.SHA_512;
035
036import java.security.MessageDigest;
037import java.security.NoSuchAlgorithmException;
038import java.util.Arrays;
039import java.util.Collections;
040import java.util.List;
041import java.util.WeakHashMap;
042
043import org.jivesoftware.smack.Manager;
044import org.jivesoftware.smack.XMPPConnection;
045
046import org.jivesoftware.smackx.disco.ServiceDiscoveryManager;
047import org.jivesoftware.smackx.hashes.element.HashElement;
048
049/**
050 * Manager that can be used to determine support for hash functions. By default the Manager announces support for
051 * XEP-0300, as well as for the recommended set of hash algorithms. Those contain SHA256, SHA384, SHA512, SHA3-256,
052 * SHA3-384, SHA3-512, BLAKE2B256, BLAKE2B384 and BLAKE2B512. Those algorithms got recommended here:
053 * <a href="https://xmpp.org/extensions/xep-0300.html#recommendations">https://xmpp.org/extensions/xep-0300.html#recommendations</a>.
054 */
055public final class HashManager extends Manager {
056
057    public static final String PREFIX_NS_ALGO = "urn:xmpp:hash-function-text-names:";
058
059    public enum NAMESPACE {
060        V1 ("urn:xmpp:hashes:1"),
061        V2 ("urn:xmpp:hashes:2");
062
063        final String name;
064
065        NAMESPACE(String name) {
066            this.name = name;
067        }
068
069        @Override
070        public String toString() {
071            return this.name;
072        }
073    }
074
075    public static final List<ALGORITHM> RECOMMENDED = Collections.unmodifiableList(Arrays.asList(
076            SHA_256, SHA_384, SHA_512,
077            SHA3_256, SHA3_384, SHA3_512,
078            BLAKE2B256, BLAKE2B384, BLAKE2B512));
079
080    private static final WeakHashMap<XMPPConnection, HashManager> INSTANCES = new WeakHashMap<>();
081
082    /**
083     * Constructor of the HashManager.
084     *
085     * @param connection connection
086     */
087    private HashManager(XMPPConnection connection) {
088        super(connection);
089        ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(connection);
090        sdm.addFeature(NAMESPACE.V2.toString());
091        addAlgorithmsToFeatures(RECOMMENDED);
092    }
093
094    public static HashElement calculateHashElement(ALGORITHM algorithm, byte[] data) {
095        return new HashElement(algorithm, hash(algorithm, data));
096    }
097
098    public static HashElement assembleHashElement(ALGORITHM algorithm, byte[] hash) {
099        return new HashElement(algorithm, hash);
100    }
101
102    /**
103     * Announce support for the given list of algorithms.
104     * @param algorithms TODO javadoc me please
105     */
106    public void addAlgorithmsToFeatures(List<ALGORITHM> algorithms) {
107        ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(connection());
108        for (ALGORITHM algo : algorithms) {
109            sdm.addFeature(asFeature(algo));
110        }
111    }
112
113    /**
114     * Get an instance of the HashManager for the  given connection.
115     * @param connection TODO javadoc me please
116     * @return the manager for the given connection.
117     */
118    public static synchronized HashManager getInstanceFor(XMPPConnection connection) {
119        HashManager hashManager = INSTANCES.get(connection);
120        if (hashManager == null) {
121            hashManager = new HashManager(connection);
122            INSTANCES.put(connection, hashManager);
123        }
124        return hashManager;
125    }
126
127    /**
128     * Return the feature name of the given algorithm.
129     * @param algorithm eg. 'SHA_1'
130     * @return feature name (eg. urn:xmpp:hash-function-text-names:sha-1')
131     */
132    public static String asFeature(ALGORITHM algorithm) {
133        return PREFIX_NS_ALGO + algorithm.toString();
134    }
135
136    enum AlgorithmRecommendation {
137        unknown,
138        must_not,
139        should_not,
140        should,
141        must,
142    }
143
144    public enum ALGORITHM {
145        MD5 ("md5", AlgorithmRecommendation.must_not),
146        SHA_1 ("sha-1", AlgorithmRecommendation.should_not),
147        SHA_224 ("sha-224", AlgorithmRecommendation.unknown),
148        SHA_256 ("sha-256", AlgorithmRecommendation.must),
149        SHA_384 ("sha-384", AlgorithmRecommendation.unknown),
150        SHA_512 ("sha-512", AlgorithmRecommendation.should),
151        SHA3_224 ("sha3-224", AlgorithmRecommendation.unknown),
152        SHA3_256 ("sha3-256", AlgorithmRecommendation.must),
153        SHA3_384 ("sha3-384", AlgorithmRecommendation.unknown),
154        SHA3_512 ("sha3-512", AlgorithmRecommendation.should),
155        BLAKE2B160("id-blake2b160", AlgorithmRecommendation.unknown),
156        BLAKE2B256("id-blake2b256", AlgorithmRecommendation.must),
157        BLAKE2B384("id-blake2b384", AlgorithmRecommendation.unknown),
158        BLAKE2B512("id-blake2b512", AlgorithmRecommendation.should);
159
160        private final String name;
161        private final AlgorithmRecommendation recommendation;
162
163        ALGORITHM(String name, AlgorithmRecommendation recommendation) {
164            this.name = name;
165            this.recommendation = recommendation;
166        }
167
168        /**
169         * Return the name of the algorithm as it is used in the XEP.
170         * @return name.
171         */
172        @Override
173        public String toString() {
174            return this.name;
175        }
176
177        public AlgorithmRecommendation getRecommendation() {
178            return recommendation;
179        }
180
181        /**
182         * Compensational method for static 'valueOf' function.
183         *
184         * @param s TODO javadoc me please
185         * @return the algorithm for the given string.
186         * @throws IllegalArgumentException if no algorithm for the given string is known.
187         */
188        public static ALGORITHM valueOfName(String s) {
189            for (ALGORITHM a : ALGORITHM.values()) {
190                if (a.toString().equals(s)) {
191                    return a;
192                }
193            }
194            throw new IllegalArgumentException("No ALGORITHM enum with this name (" + s + ") found.");
195        }
196    }
197
198    /**
199     * Calculate the hash sum of data using algorithm.
200     *
201     * @param algorithm the algorithm to use.
202     * @param data the data to calculate the hash for.
203     * @return the hash value produced by the given algorithm for the given data.
204     */
205    public static byte[] hash(ALGORITHM algorithm, byte[] data) {
206        return getMessageDigest(algorithm).digest(data);
207    }
208
209    public static byte[] hash(ALGORITHM algorithm, String data) {
210        return hash(algorithm, toUtf8Bytes(data));
211    }
212
213    public static MessageDigest getMessageDigest(ALGORITHM algorithm) {
214        MessageDigest md;
215        try {
216            switch (algorithm) {
217                case MD5:
218                    md = MessageDigest.getInstance("MD5");
219                    break;
220                case SHA_1:
221                    md = MessageDigest.getInstance("SHA-1");
222                    break;
223                case SHA_224:
224                    md = MessageDigest.getInstance("SHA-224");
225                    break;
226                case SHA_256:
227                    md = MessageDigest.getInstance("SHA-256");
228                    break;
229                case SHA_384:
230                    md = MessageDigest.getInstance("SHA-384");
231                    break;
232                case SHA_512:
233                    md = MessageDigest.getInstance("SHA-512");
234                    break;
235                case SHA3_224:
236                    md = MessageDigest.getInstance("SHA3-224");
237                    break;
238                case SHA3_256:
239                    md = MessageDigest.getInstance("SHA3-256");
240                    break;
241                case SHA3_384:
242                    md = MessageDigest.getInstance("SHA3-384");
243                    break;
244                case SHA3_512:
245                    md = MessageDigest.getInstance("SHA3-512");
246                    break;
247                case BLAKE2B160:
248                    md = MessageDigest.getInstance("BLAKE2b-160");
249                    break;
250                case BLAKE2B256:
251                    md = MessageDigest.getInstance("BLAKE2b-256");
252                    break;
253                case BLAKE2B384:
254                    md = MessageDigest.getInstance("BLAKE2b-384");
255                    break;
256                case BLAKE2B512:
257                    md = MessageDigest.getInstance("BLAKE2b-512");
258                    break;
259                default:
260                    throw new AssertionError("Invalid enum value: " + algorithm);
261            }
262            return md;
263        } catch (NoSuchAlgorithmException e) {
264            throw new AssertionError(e);
265        }
266    }
267
268    public static byte[] md5(byte[] data) {
269        return getMessageDigest(MD5).digest(data);
270    }
271
272    public static byte[] md5(String data) {
273        return md5(toUtf8Bytes(data));
274    }
275
276    public static String md5HexString(byte[] data) {
277        return encodeHex(md5(data));
278    }
279
280    public static String md5HexString(String data) {
281        return encodeHex(md5(data));
282    }
283
284    public static byte[] sha_1(byte[] data) {
285        return getMessageDigest(SHA_1).digest(data);
286    }
287
288    public static byte[] sha_1(String data) {
289        return sha_1(toUtf8Bytes(data));
290    }
291
292    public static String sha_1HexString(byte[] data) {
293        return encodeHex(sha_1(data));
294    }
295
296    public static String sha_1HexString(String data) {
297        return encodeHex(sha_1(data));
298    }
299
300    public static byte[] sha_224(byte[] data) {
301        return getMessageDigest(SHA_224).digest(data);
302    }
303
304    public static byte[] sha_224(String data) {
305        return sha_224(toUtf8Bytes(data));
306    }
307
308    public static String sha_224HexString(byte[] data) {
309        return encodeHex(sha_224(data));
310    }
311
312    public static String sha_224HexString(String data) {
313        return encodeHex(sha_224(data));
314    }
315
316    public static byte[] sha_256(byte[] data) {
317        return getMessageDigest(SHA_256).digest(data);
318    }
319
320    public static byte[] sha_256(String data) {
321        return sha_256(toUtf8Bytes(data));
322    }
323
324    public static String sha_256HexString(byte[] data) {
325        return encodeHex(sha_256(data));
326    }
327
328    public static String sha_256HexString(String data) {
329        return encodeHex(sha_256(data));
330    }
331
332    public static byte[] sha_384(byte[] data) {
333        return getMessageDigest(SHA_384).digest(data);
334    }
335
336    public static byte[] sha_384(String data) {
337        return sha_384(toUtf8Bytes(data));
338    }
339
340    public static String sha_384HexString(byte[] data) {
341        return encodeHex(sha_384(data));
342    }
343
344    public static String sha_384HexString(String data) {
345        return encodeHex(sha_384(data));
346    }
347
348    public static byte[] sha_512(byte[] data) {
349        return getMessageDigest(SHA_512).digest(data);
350    }
351
352    public static byte[] sha_512(String data) {
353        return sha_512(toUtf8Bytes(data));
354    }
355
356    public static String sha_512HexString(byte[] data) {
357        return encodeHex(sha_512(data));
358    }
359
360    public static String sha_512HexString(String data) {
361        return encodeHex(sha_512(data));
362    }
363
364    public static byte[] sha3_224(byte[] data) {
365        return getMessageDigest(SHA3_224).digest(data);
366    }
367
368    public static byte[] sha3_224(String data) {
369        return sha3_224(toUtf8Bytes(data));
370    }
371
372    public static String sha3_224HexString(byte[] data) {
373        return encodeHex(sha3_224(data));
374    }
375
376    public static String sha3_224HexString(String data) {
377        return encodeHex(sha3_224(data));
378    }
379
380    public static byte[] sha3_256(byte[] data) {
381        return getMessageDigest(SHA3_256).digest(data);
382    }
383
384    public static byte[] sha3_256(String data) {
385        return sha3_256(toUtf8Bytes(data));
386    }
387
388    public static String sha3_256HexString(byte[] data) {
389        return encodeHex(sha3_256(data));
390    }
391
392    public static String sha3_256HexString(String data) {
393        return encodeHex(sha3_256(data));
394    }
395
396    public static byte[] sha3_384(byte[] data) {
397        return getMessageDigest(SHA3_384).digest(data);
398    }
399
400    public static byte[] sha3_384(String data) {
401        return sha3_384(toUtf8Bytes(data));
402    }
403
404    public static String sha3_384HexString(byte[] data) {
405        return encodeHex(sha3_384(data));
406    }
407
408    public static String sha3_384HexString(String data) {
409        return encodeHex(sha3_384(data));
410    }
411
412    public static byte[] sha3_512(byte[] data) {
413        return getMessageDigest(SHA3_512).digest(data);
414    }
415
416    public static byte[] sha3_512(String data) {
417        return sha3_512(toUtf8Bytes(data));
418    }
419
420    public static String sha3_512HexString(byte[] data) {
421        return encodeHex(sha3_512(data));
422    }
423
424    public static String sha3_512HexString(String data) {
425        return encodeHex(sha3_512(data));
426    }
427
428    public static byte[] blake2b160(byte[] data) {
429        return getMessageDigest(BLAKE2B160).digest(data);
430    }
431
432    public static byte[] blake2b160(String data) {
433        return blake2b160(toUtf8Bytes(data));
434    }
435
436    public static String blake2b160HexString(byte[] data) {
437        return encodeHex(blake2b160(data));
438    }
439
440    public static String blake2b160HexString(String data) {
441        return encodeHex(blake2b160(data));
442    }
443
444    public static byte[] blake2b256(byte[] data) {
445        return getMessageDigest(BLAKE2B256).digest(data);
446    }
447
448    public static byte[] blake2b256(String data) {
449        return blake2b256(toUtf8Bytes(data));
450    }
451
452    public static String blake2b256HexString(byte[] data) {
453        return encodeHex(blake2b256(data));
454    }
455
456    public static String blake2b256HexString(String data) {
457        return encodeHex(blake2b256(data));
458    }
459
460    public static byte[] blake2b384(byte[] data) {
461        return getMessageDigest(BLAKE2B384).digest(data);
462    }
463
464    public static byte[] blake2b384(String data) {
465        return blake2b384(toUtf8Bytes(data));
466    }
467
468    public static String blake2b384HexString(byte[] data) {
469        return encodeHex(blake2b384(data));
470    }
471
472    public static  String blake2b384HexString(String data) {
473        return encodeHex(blake2b384(data));
474    }
475
476    public static byte[] blake2b512(byte[] data) {
477        return getMessageDigest(BLAKE2B512).digest(data);
478    }
479
480    public static byte[] blake2b512(String data) {
481        return blake2b512(toUtf8Bytes(data));
482    }
483
484    public static String blake2b512HexString(byte[] data) {
485        return encodeHex(blake2b512(data));
486    }
487
488    public static String blake2b512HexString(String data) {
489        return encodeHex(blake2b512(data));
490    }
491
492}