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.omemo.util;
018
019import java.io.IOException;
020import java.util.HashMap;
021import java.util.Map;
022import java.util.logging.Level;
023import java.util.logging.Logger;
024
025import org.jivesoftware.smackx.omemo.OmemoFingerprint;
026import org.jivesoftware.smackx.omemo.OmemoManager;
027import org.jivesoftware.smackx.omemo.OmemoStore;
028import org.jivesoftware.smackx.omemo.element.OmemoBundleVAxolotlElement;
029import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
030import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
031import org.jivesoftware.smackx.omemo.internal.OmemoSession;
032
033import org.jxmpp.stringprep.XmppStringprepException;
034
035/**
036 * Class that is used to convert bytes to keys and vice versa.
037 *
038 * @param <T_IdKeyPair> IdentityKeyPair class
039 * @param <T_IdKey>     IdentityKey class
040 * @param <T_PreKey>    PreKey class
041 * @param <T_SigPreKey> SignedPreKey class
042 * @param <T_Sess>      Session class
043 * @param <T_Addr>      Address class
044 * @param <T_ECPub>     Elliptic Curve PublicKey class
045 * @param <T_Bundle>    Bundle class
046 * @param <T_Ciph>      Cipher class
047 * @author Paul Schaub
048 */
049public abstract class OmemoKeyUtil<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> {
050    private static final Logger LOGGER = Logger.getLogger(OmemoKeyUtil.class.getName());
051
052    public final Bundle BUNDLE = new Bundle();
053
054    /**
055     * Bundle related methods.
056     */
057    public class Bundle {
058
059        /**
060         * Extract an IdentityKey from a OmemoBundleElement.
061         *
062         * @param bundle OmemoBundleElement
063         * @return identityKey
064         * @throws CorruptedOmemoKeyException if the key is damaged/malformed
065         */
066        public T_IdKey identityKey(OmemoBundleVAxolotlElement bundle) throws CorruptedOmemoKeyException {
067            return identityKeyFromBytes(bundle.getIdentityKey());
068        }
069
070        /**
071         * Extract a signedPreKey from an OmemoBundleElement.
072         *
073         * @param bundle OmemoBundleElement
074         * @return singedPreKey
075         * @throws CorruptedOmemoKeyException if the key is damaged/malformed
076         */
077        public T_ECPub signedPreKeyPublic(OmemoBundleVAxolotlElement bundle) throws CorruptedOmemoKeyException {
078            return signedPreKeyPublicFromBytes(bundle.getSignedPreKey());
079        }
080
081        /**
082         * Extract the id of the transported signedPreKey from the bundle.
083         *
084         * @param bundle OmemoBundleElement
085         * @return signedPreKeyId
086         */
087        public int signedPreKeyId(OmemoBundleVAxolotlElement bundle) {
088            return bundle.getSignedPreKeyId();
089        }
090
091        /**
092         * Extract the signature of the signedPreKey in the bundle as a byte array.
093         *
094         * @param bundle OmemoBundleElement
095         * @return signature
096         */
097        public byte[] signedPreKeySignature(OmemoBundleVAxolotlElement bundle) {
098            return bundle.getSignedPreKeySignature();
099        }
100
101        /**
102         * Extract the preKey with id 'keyId' from the bundle.
103         *
104         * @param bundle OmemoBundleElement
105         * @param keyId  id of the preKey
106         * @return the preKey
107         * @throws CorruptedOmemoKeyException when the key cannot be parsed from bytes
108         */
109        public T_ECPub preKeyPublic(OmemoBundleVAxolotlElement bundle, int keyId) throws CorruptedOmemoKeyException {
110            return preKeyPublicFromBytes(bundle.getPreKey(keyId));
111        }
112
113        /**
114         * Break up the OmemoBundleElement into a list of crypto-lib specific bundles (T_PreKey).
115         * In case of the signal library, we break the OmemoBundleElement in ~100 PreKeyBundles (one for every transported
116         * preKey).
117         *
118         * @param bundle  OmemoBundleElement containing multiple PreKeys
119         * @param contact Contact that the bundle belongs to
120         * @return a HashMap with one T_Bundle per preKey and the preKeyId as key
121         * @throws CorruptedOmemoKeyException when one of the keys cannot be parsed
122         */
123        public HashMap<Integer, T_Bundle> bundles(OmemoBundleVAxolotlElement bundle, OmemoDevice contact) throws CorruptedOmemoKeyException {
124            HashMap<Integer, T_Bundle> bundles = new HashMap<>();
125            for (int deviceId : bundle.getPreKeys().keySet()) {
126                try {
127                    bundles.put(deviceId, bundleFromOmemoBundle(bundle, contact, deviceId));
128                } catch (CorruptedOmemoKeyException e) {
129                    LOGGER.log(Level.INFO, "Cannot parse PreKeyBundle: " + e.getMessage());
130                }
131            }
132            if (bundles.size() == 0) {
133                throw new CorruptedOmemoKeyException("Bundle contained no valid preKeys.");
134            }
135            return bundles;
136        }
137    }
138
139    /**
140     * Deserialize an identityKeyPair from a byte array.
141     *
142     * @param data byte array
143     * @return IdentityKeyPair (T_IdKeyPair)
144     * @throws CorruptedOmemoKeyException if the key is damaged of malformed
145     */
146    public abstract T_IdKeyPair identityKeyPairFromBytes(byte[] data) throws CorruptedOmemoKeyException;
147
148    /**
149     * Deserialize an identityKey from a byte array.
150     *
151     * @param data byte array
152     * @return identityKey (T_IdKey)
153     * @throws CorruptedOmemoKeyException if the key is damaged or malformed
154     */
155    public abstract T_IdKey identityKeyFromBytes(byte[] data) throws CorruptedOmemoKeyException;
156
157    /**
158     * Serialize an identityKey into bytes.
159     *
160     * @param identityKey idKey
161     * @return bytes
162     */
163    public abstract byte[] identityKeyToBytes(T_IdKey identityKey);
164
165    /**
166     * Deserialize an elliptic curve public key from bytes.
167     *
168     * @param data bytes
169     * @return elliptic curve public key (T_ECPub)
170     * @throws CorruptedOmemoKeyException if the key is damaged or malformed
171     */
172    public abstract T_ECPub ellipticCurvePublicKeyFromBytes(byte[] data) throws CorruptedOmemoKeyException;
173
174    /**
175     * Deserialize a public preKey from bytes.
176     *
177     * @param data preKey as bytes
178     * @return preKey
179     * @throws CorruptedOmemoKeyException if the key is damaged or malformed
180     */
181    public T_ECPub preKeyPublicFromBytes(byte[] data) throws CorruptedOmemoKeyException {
182        return ellipticCurvePublicKeyFromBytes(data);
183    }
184
185    /**
186     * Serialize a preKey into a byte array.
187     *
188     * @param preKey preKey
189     * @return byte[]
190     */
191    public abstract byte[] preKeyToBytes(T_PreKey preKey);
192
193    /**
194     * Deserialize a preKey from a byte array.
195     *
196     * @param bytes byte array
197     * @return deserialized preKey
198     * @throws IOException when something goes wrong
199     */
200    public abstract T_PreKey preKeyFromBytes(byte[] bytes) throws IOException;
201
202    /**
203     * Generate 'count' new PreKeys beginning with id 'startId'.
204     * These preKeys are published and can be used by contacts to establish sessions with us.
205     *
206     * @param startId start id
207     * @param count   how many keys do we want to generate
208     * @return Map of new preKeys
209     */
210    public abstract HashMap<Integer, T_PreKey> generateOmemoPreKeys(int startId, int count);
211
212    /**
213     * Generate a new signed preKey.
214     *
215     * @param identityKeyPair identityKeyPair used to sign the preKey
216     * @param signedPreKeyId  id that the preKey will have
217     * @return signedPreKey
218     * @throws CorruptedOmemoKeyException when the identityKeyPair is invalid
219     */
220    public abstract T_SigPreKey generateOmemoSignedPreKey(T_IdKeyPair identityKeyPair, int signedPreKeyId) throws CorruptedOmemoKeyException;
221
222
223    /**
224     * Deserialize a public signedPreKey from bytes.
225     *
226     * @param data bytes
227     * @return signedPreKey
228     * @throws CorruptedOmemoKeyException if the key is damaged or malformed
229     */
230    public T_ECPub signedPreKeyPublicFromBytes(byte[] data) throws CorruptedOmemoKeyException {
231        return ellipticCurvePublicKeyFromBytes(data);
232    }
233
234    /**
235     * Deserialize a signedPreKey from a byte array.
236     *
237     * @param data byte array
238     * @return deserialized signed preKey
239     * @throws IOException when something goes wrong
240     */
241    public abstract T_SigPreKey signedPreKeyFromBytes(byte[] data) throws IOException;
242
243    /**
244     * Serialize a signedPreKey into a byte array.
245     *
246     * @param sigPreKey signedPreKey
247     * @return byte array
248     */
249    public abstract byte[] signedPreKeyToBytes(T_SigPreKey sigPreKey);
250
251    /**
252     * Build a crypto-lib specific PreKeyBundle (T_Bundle) using a PreKey from the OmemoBundleElement 'bundle'.
253     * The PreKeyBundle will contain the identityKey, signedPreKey and signature, as well as a preKey
254     * from the OmemoBundleElement.
255     *
256     * @param bundle  OmemoBundleElement
257     * @param contact Contact that the bundle belongs to
258     * @param keyId   id of the preKey that will be selected from the OmemoBundleElement and that the PreKeyBundle will contain
259     * @return PreKeyBundle (T_PreKey)
260     * @throws CorruptedOmemoKeyException if some key is damaged or malformed
261     */
262    public abstract T_Bundle bundleFromOmemoBundle(OmemoBundleVAxolotlElement bundle, OmemoDevice contact, int keyId) throws CorruptedOmemoKeyException;
263
264    /**
265     * Extract the signature from a signedPreKey.
266     *
267     * @param signedPreKey signedPreKey
268     * @return signature as byte array
269     */
270    public abstract byte[] signedPreKeySignatureFromKey(T_SigPreKey signedPreKey);
271
272    /**
273     * Generate a new IdentityKeyPair. We should always have only one pair and usually keep this for a long time.
274     *
275     * @return identityKeyPair
276     */
277    public abstract T_IdKeyPair generateOmemoIdentityKeyPair();
278
279    /**
280     * return the id of the given signedPreKey.
281     *
282     * @param signedPreKey key
283     * @return id of the key
284     */
285    public abstract int signedPreKeyIdFromKey(T_SigPreKey signedPreKey);
286
287    /**
288     * serialize an identityKeyPair into bytes.
289     *
290     * @param identityKeyPair identityKeyPair
291     * @return byte array
292     */
293    public abstract byte[] identityKeyPairToBytes(T_IdKeyPair identityKeyPair);
294
295    /**
296     * Extract the public identityKey from an identityKeyPair.
297     *
298     * @param pair keyPair
299     * @return public key of the pair
300     */
301    public abstract T_IdKey identityKeyFromPair(T_IdKeyPair pair);
302
303    /**
304     * Prepare an identityKey for transport in an OmemoBundleElement (serialize it).
305     *
306     * @param identityKey identityKey that will be transported
307     * @return key as byte array
308     */
309    public abstract byte[] identityKeyForBundle(T_IdKey identityKey);
310
311    /**
312     * Prepare an elliptic curve preKey for transport in an OmemoBundleElement.
313     *
314     * @param preKey key
315     * @return key as byte array
316     */
317    public abstract byte[] preKeyPublicKeyForBundle(T_ECPub preKey);
318
319    /**
320     * Prepare a preKey for transport in an OmemoBundleElement.
321     *
322     * @param preKey preKey
323     * @return key as byte array
324     */
325    public abstract byte[] preKeyForBundle(T_PreKey preKey);
326
327    /**
328     * Prepare a whole bunche of preKeys for transport.
329     *
330     * @param preKeyHashMap HashMap of preKeys
331     * @return HashMap of byte arrays but with the same keyIds as key
332     */
333    public HashMap<Integer, byte[]> preKeyPublisKeysForBundle(HashMap<Integer, T_PreKey> preKeyHashMap) {
334        HashMap<Integer, byte[]> out = new HashMap<>();
335        for (Map.Entry<Integer, T_PreKey> e : preKeyHashMap.entrySet()) {
336            out.put(e.getKey(), preKeyForBundle(e.getValue()));
337        }
338        return out;
339    }
340
341    /**
342     * Prepare a public signedPreKey for transport in a bundle.
343     *
344     * @param signedPreKey signedPrekey
345     * @return signedPreKey as byte array
346     */
347    public abstract byte[] signedPreKeyPublicForBundle(T_SigPreKey signedPreKey);
348
349    /**
350     * Return the fingerprint of an identityKey.
351     *
352     * @param identityKey identityKey
353     * @return fingerprint of the key
354     */
355    public abstract OmemoFingerprint getFingerprint(T_IdKey identityKey);
356
357    /**
358     * Create a new crypto-specific Session object.
359     *
360     * @param omemoManager  omemoManager of our device.
361     * @param omemoStore    omemoStore where we can save the session, get keys from etc.
362     * @param from          the device we want to create the session with.
363     * @return a new session
364     */
365    public abstract OmemoSession<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>
366    createOmemoSession(OmemoManager omemoManager, OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> omemoStore,
367                       OmemoDevice from);
368
369    /**
370     * Create a new concrete OmemoSession with a contact.
371     *
372     * @param omemoManager  omemoManager of our device.
373     * @param omemoStore    omemoStore
374     * @param device        device to establish the session with
375     * @param identityKey   identityKey of the device
376     * @return concrete OmemoSession
377     */
378    public abstract OmemoSession<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph>
379    createOmemoSession(OmemoManager omemoManager, OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> omemoStore,
380                       OmemoDevice device, T_IdKey identityKey);
381
382    /**
383     * Deserialize a raw OMEMO Session from bytes.
384     *
385     * @param data bytes
386     * @return raw OMEMO Session
387     * @throws IOException when something goes wrong
388     */
389    public abstract T_Sess rawSessionFromBytes(byte[] data) throws IOException;
390
391    /**
392     * Serialize a raw OMEMO session into a byte array.
393     *
394     * @param session raw session
395     * @return byte array
396     */
397    public abstract byte[] rawSessionToBytes(T_Sess session);
398
399    /**
400     * Convert an OmemoDevice to a crypto-lib specific contact format.
401     *
402     * @param contact omemoContact
403     * @return crypto-lib specific contact object
404     */
405    public abstract T_Addr omemoDeviceAsAddress(OmemoDevice contact);
406
407    /**
408     * Convert a crypto-lib specific contact object into an OmemoDevice.
409     *
410     * @param address contact
411     * @return as OmemoDevice
412     * @throws XmppStringprepException if the address is not a valid BareJid
413     */
414    public abstract OmemoDevice addressAsOmemoDevice(T_Addr address) throws XmppStringprepException;
415
416    public static String prettyFingerprint(OmemoFingerprint fingerprint) {
417        return prettyFingerprint(fingerprint.toString());
418    }
419
420    /**
421     * Split the fingerprint in blocks of 8 characters with spaces between.
422     *
423     * @param ugly fingerprint as continuous string
424     * @return fingerprint with spaces for better readability
425     */
426    public static String prettyFingerprint(String ugly) {
427        if (ugly == null) return null;
428        String pretty = "";
429        for (int i = 0; i < 8; i++) {
430            if (i != 0) pretty += " ";
431            pretty += ugly.substring(8 * i, 8 * (i + 1));
432        }
433        return pretty;
434    }
435
436    /**
437     * Add integers modulo MAX_VALUE.
438     *
439     * @param value base integer
440     * @param added value that is added to the base value
441     * @return (value plus added) modulo Integer.MAX_VALUE
442     */
443    public static int addInBounds(int value, int added) {
444        int avail = Integer.MAX_VALUE - value;
445        if (avail < added) {
446            return added - avail;
447        } else {
448            return value + added;
449        }
450    }
451}