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