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;
018
019import static org.jivesoftware.smackx.omemo.util.OmemoConstants.PRE_KEY_COUNT_PER_BUNDLE;
020
021import java.io.IOException;
022import java.util.Date;
023import java.util.HashMap;
024import java.util.Map;
025import java.util.SortedSet;
026import java.util.TreeMap;
027import java.util.logging.Level;
028import java.util.logging.Logger;
029
030import org.jivesoftware.smack.SmackException;
031
032import org.jivesoftware.smackx.omemo.element.OmemoBundleElement_VAxolotl;
033import org.jivesoftware.smackx.omemo.element.OmemoDeviceListElement;
034import org.jivesoftware.smackx.omemo.exceptions.CannotEstablishOmemoSessionException;
035import org.jivesoftware.smackx.omemo.exceptions.CorruptedOmemoKeyException;
036import org.jivesoftware.smackx.omemo.exceptions.NoIdentityKeyException;
037import org.jivesoftware.smackx.omemo.internal.OmemoCachedDeviceList;
038import org.jivesoftware.smackx.omemo.internal.OmemoDevice;
039import org.jivesoftware.smackx.omemo.trust.OmemoFingerprint;
040import org.jivesoftware.smackx.omemo.util.OmemoKeyUtil;
041
042import org.jxmpp.jid.BareJid;
043
044/**
045 * Class that presents some methods that are used to load/generate/store keys and session data needed for OMEMO.
046 *
047 * @param <T_IdKeyPair> IdentityKeyPair class
048 * @param <T_IdKey>     IdentityKey class
049 * @param <T_PreKey>    PreKey class
050 * @param <T_SigPreKey> SignedPreKey class
051 * @param <T_Sess>      Session class
052 * @param <T_Addr>      Address class
053 * @param <T_ECPub>     Elliptic Curve PublicKey class
054 * @param <T_Bundle>    Bundle class
055 * @param <T_Ciph>      Cipher class
056 *
057 * @author Paul Schaub
058 */
059public abstract class OmemoStore<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_Addr, T_ECPub, T_Bundle, T_Ciph> {
060    private static final Logger LOGGER = Logger.getLogger(OmemoStore.class.getName());
061
062    /**
063     * Create a new OmemoStore.
064     */
065    public OmemoStore() {
066
067    }
068
069    /**
070     * Returns a sorted set of all the deviceIds, the localUser has had data stored under in the store.
071     * Basically this returns the deviceIds of all "accounts" of localUser, which are known to the store.
072     *
073     * @param localUser BareJid of the user.
074     * @return set of deviceIds with available data.
075     */
076    public abstract SortedSet<Integer> localDeviceIdsOf(BareJid localUser);
077
078    /**
079     * Check, if our freshly generated deviceId is available (unique) in our deviceList.
080     *
081     * @param userDevice    our current device.
082     * @param id            deviceId to check for.
083     * @return true if list did not contain our id, else false
084     *
085     * @throws IOException if an I/O error occurred.
086     */
087    boolean isAvailableDeviceId(OmemoDevice userDevice, int id) throws IOException {
088        LOGGER.log(Level.INFO, "Check if id " + id + " is available...");
089
090        // Lookup local cached device list
091        BareJid ownJid = userDevice.getJid();
092        OmemoCachedDeviceList cachedDeviceList;
093
094        cachedDeviceList = loadCachedDeviceList(userDevice, ownJid);
095
096        if (cachedDeviceList == null) {
097            cachedDeviceList = new OmemoCachedDeviceList();
098        }
099        // Does the list already contain that id?
100        return !cachedDeviceList.contains(id);
101    }
102
103    /**
104     * Merge the received OmemoDeviceListElement with the one we already have. If we had none, the received one is saved.
105     *
106     * @param userDevice our OmemoDevice.
107     * @param contact Contact we received the list from.
108     * @param list    List we received.
109     *
110     * @throws IOException if an I/O error occurred.
111     */
112    OmemoCachedDeviceList mergeCachedDeviceList(OmemoDevice userDevice, BareJid contact, OmemoDeviceListElement list) throws IOException {
113        OmemoCachedDeviceList cached = loadCachedDeviceList(userDevice, contact);
114
115        if (cached == null) {
116            cached = new OmemoCachedDeviceList();
117        }
118
119        if (list == null) {
120            return cached;
121        }
122
123        for (int devId : list.getDeviceIds()) {
124            if (!cached.contains(devId)) {
125                setDateOfLastDeviceIdPublication(userDevice, new OmemoDevice(contact, devId), new Date());
126            }
127        }
128
129        cached.merge(list.getDeviceIds());
130        storeCachedDeviceList(userDevice, contact, cached);
131
132        return cached;
133    }
134
135    /**
136     * Renew our singed preKey. This should be done once every 7-14 days.
137     * The old signed PreKey should be kept for around a month or so (look it up in the XEP).
138     *
139     * @param userDevice our OmemoDevice.
140     *
141     * @throws CorruptedOmemoKeyException when our identityKey is invalid.
142     * @throws IOException if an I/O error occurred.
143     * @throws IllegalStateException when our IdentityKeyPair is null.
144     */
145    void changeSignedPreKey(OmemoDevice userDevice)
146            throws CorruptedOmemoKeyException, IOException {
147
148        T_IdKeyPair idKeyPair = loadOmemoIdentityKeyPair(userDevice);
149        if (idKeyPair == null) {
150            throw new IllegalStateException("Our IdentityKeyPair is null.");
151        }
152
153        TreeMap<Integer, T_SigPreKey> signedPreKeys = loadOmemoSignedPreKeys(userDevice);
154        if (signedPreKeys.size() == 0) {
155            T_SigPreKey newKey = generateOmemoSignedPreKey(idKeyPair, 1);
156            storeOmemoSignedPreKey(userDevice, 1, newKey);
157        } else {
158            int lastId = signedPreKeys.lastKey();
159            T_SigPreKey newKey = generateOmemoSignedPreKey(idKeyPair, lastId + 1);
160            storeOmemoSignedPreKey(userDevice, lastId + 1, newKey);
161        }
162
163        setDateOfLastSignedPreKeyRenewal(userDevice, new Date());
164        removeOldSignedPreKeys(userDevice);
165    }
166
167    /**
168     * Remove the oldest signedPreKey until there are only MAX_NUMBER_OF_STORED_SIGNED_PREKEYS left.
169     *
170     * @param userDevice our OmemoDevice.
171     *
172     * @throws IOException if an I/O error occurred.
173     */
174    private void removeOldSignedPreKeys(OmemoDevice userDevice) throws IOException {
175        if (OmemoConfiguration.getMaxNumberOfStoredSignedPreKeys() <= 0) {
176            return;
177        }
178
179        TreeMap<Integer, T_SigPreKey> signedPreKeys = loadOmemoSignedPreKeys(userDevice);
180
181        for (int i = 0; i < signedPreKeys.keySet().size() - OmemoConfiguration.getMaxNumberOfStoredSignedPreKeys(); i++) {
182            int keyId = signedPreKeys.firstKey();
183            LOGGER.log(Level.INFO, "Remove signedPreKey " + keyId + ".");
184            removeOmemoSignedPreKey(userDevice, i);
185            signedPreKeys = loadOmemoSignedPreKeys(userDevice);
186        }
187    }
188
189    /**
190     * Pack a OmemoBundleElement containing our key material.
191     *
192     * @param userDevice our OmemoDevice.
193     * @return OMEMO bundle element
194     *
195     * @throws CorruptedOmemoKeyException when a key could not be loaded
196     * @throws IOException if an I/O error occurred.
197     */
198    OmemoBundleElement_VAxolotl packOmemoBundle(OmemoDevice userDevice)
199            throws CorruptedOmemoKeyException, IOException {
200
201        int currentSignedPreKeyId = loadCurrentOmemoSignedPreKeyId(userDevice);
202        T_SigPreKey currentSignedPreKey = loadOmemoSignedPreKeys(userDevice).get(currentSignedPreKeyId);
203
204        return new OmemoBundleElement_VAxolotl(
205                currentSignedPreKeyId,
206                keyUtil().signedPreKeyPublicForBundle(currentSignedPreKey),
207                keyUtil().signedPreKeySignatureFromKey(currentSignedPreKey),
208                keyUtil().identityKeyForBundle(keyUtil().identityKeyFromPair(loadOmemoIdentityKeyPair(userDevice))),
209                keyUtil().preKeyPublicKeysForBundle(loadOmemoPreKeys(userDevice))
210        );
211    }
212
213    /**
214     * Replenish our supply of keys. If we are missing any type of keys, generate them fresh.
215     *
216     * @param userDevice our own OMEMO device
217     *
218     * @throws CorruptedOmemoKeyException if the OMEMO key is corrupted.
219     * @throws IOException if an I/O error occurred.
220     */
221    public void replenishKeys(OmemoDevice userDevice)
222            throws CorruptedOmemoKeyException, IOException {
223
224        T_IdKeyPair identityKeyPair = loadOmemoIdentityKeyPair(userDevice);
225        if (identityKeyPair == null) {
226            identityKeyPair = generateOmemoIdentityKeyPair();
227            storeOmemoIdentityKeyPair(userDevice, identityKeyPair);
228        }
229
230        TreeMap<Integer, T_SigPreKey> signedPreKeys = loadOmemoSignedPreKeys(userDevice);
231        if (signedPreKeys.size() == 0) {
232            changeSignedPreKey(userDevice);
233        }
234
235        TreeMap<Integer, T_PreKey> preKeys = loadOmemoPreKeys(userDevice);
236        int newKeysCount = PRE_KEY_COUNT_PER_BUNDLE - preKeys.size();
237        int startId = preKeys.size() == 0 ? 0 : preKeys.lastKey();
238
239        if (newKeysCount > 0) {
240            TreeMap<Integer, T_PreKey> newKeys = generateOmemoPreKeys(startId + 1, newKeysCount);
241            storeOmemoPreKeys(userDevice, newKeys);
242        }
243    }
244
245    // *sigh*
246
247    /**
248     * Generate a new IdentityKeyPair. We should always have only one pair and usually keep this for a long time.
249     *
250     * @return a fresh identityKeyPair
251     */
252    public T_IdKeyPair generateOmemoIdentityKeyPair() {
253        return keyUtil().generateOmemoIdentityKeyPair();
254    }
255
256    /**
257     * Load our identityKeyPair from storage.
258     * Return null, if we have no identityKeyPair.
259     *
260     * @param userDevice our OmemoDevice.
261     * @return loaded identityKeyPair
262     *
263     * @throws CorruptedOmemoKeyException Thrown, if the stored key is damaged (*hands up* not my fault!)
264     * @throws IOException if an I/O error occurred.
265     */
266    public abstract T_IdKeyPair loadOmemoIdentityKeyPair(OmemoDevice userDevice)
267            throws CorruptedOmemoKeyException, IOException;
268
269    /**
270     * Store our identityKeyPair in storage. It would be a cool feature, if the key could be stored in a encrypted
271     * database or something similar.
272     *
273     * @param userDevice our OmemoDevice.
274     * @param identityKeyPair identityKeyPair
275     *
276     * @throws IOException if an I/O error occurred.
277     */
278    public abstract void storeOmemoIdentityKeyPair(OmemoDevice userDevice, T_IdKeyPair identityKeyPair) throws IOException;
279
280    /**
281     * Remove the identityKeyPair of a user.
282     *
283     * @param userDevice our device.
284     */
285    public abstract void removeOmemoIdentityKeyPair(OmemoDevice userDevice);
286
287    /**
288     * Load the public identityKey of a device.
289     *
290     * @param userDevice our OmemoDevice.
291     * @param contactsDevice the device of which we want to load the identityKey.
292     * @return loaded identityKey
293     *
294     * @throws CorruptedOmemoKeyException when the key in question is corrupted and cant be deserialized.
295     * @throws IOException if an I/O error occurred.
296     */
297    public abstract T_IdKey loadOmemoIdentityKey(OmemoDevice userDevice, OmemoDevice contactsDevice)
298            throws CorruptedOmemoKeyException, IOException;
299
300    /**
301     * Store the public identityKey of the device.
302     *
303     * @param userDevice our OmemoDevice.
304     * @param contactsDevice device.
305     * @param contactsKey    identityKey belonging to the contactsDevice.
306     *
307     * @throws IOException if an I/O error occurred.
308     */
309    public abstract void storeOmemoIdentityKey(OmemoDevice userDevice, OmemoDevice contactsDevice, T_IdKey contactsKey) throws IOException;
310
311    /**
312     * Removes the identityKey of a device.
313     *
314     * @param userDevice our omemoDevice.
315     * @param contactsDevice device of which we want to delete the identityKey.
316     */
317    public abstract void removeOmemoIdentityKey(OmemoDevice userDevice, OmemoDevice contactsDevice);
318
319    /**
320     * Store the number of messages we sent to a device since we last received a message back.
321     * This counter gets reset to 0 whenever we receive a message from the contacts device.
322     *
323     * @param userDevice our omemoDevice.
324     * @param contactsDevice device of which we want to set the message counter.
325     * @param counter counter value.
326     *
327     * @throws IOException if an I/O error occurred.
328     */
329    public abstract void storeOmemoMessageCounter(OmemoDevice userDevice, OmemoDevice contactsDevice, int counter) throws IOException;
330
331    /**
332     * Return the current value of the message counter.
333     * This counter represents the number of message we sent to the contactsDevice without getting a reply back.
334     * The default value for this counter is 0.
335     *
336     * @param userDevice our omemoDevice
337     * @param contactsDevice device of which we want to get the message counter.
338     * @return counter value.
339     *
340     * @throws IOException if an I/O error occurred.
341     */
342    public abstract int loadOmemoMessageCounter(OmemoDevice userDevice, OmemoDevice contactsDevice) throws IOException;
343
344    /**
345     * Set the date of the last message that was received from a device.
346     *
347     * @param userDevice omemoManager of our device.
348     * @param contactsDevice device in question
349     * @param date date of the last received message
350     *
351     * @throws IOException if an I/O error occurred.
352     */
353    public abstract void setDateOfLastReceivedMessage(OmemoDevice userDevice, OmemoDevice contactsDevice, Date date) throws IOException;
354
355    /**
356     * Return the date of the last message that was received from device 'from'.
357     *
358     * @param userDevice our OmemoDevice.
359     * @param contactsDevice device in question
360     * @return date if existent, null
361     *
362     * @throws IOException if an I/O error occurred.
363     */
364    public abstract Date getDateOfLastReceivedMessage(OmemoDevice userDevice, OmemoDevice contactsDevice) throws IOException;
365
366    /**
367     * Set the date of the last time the deviceId was published. This method only gets called, when the deviceId
368     * was inactive/non-existent before it was published.
369     *
370     * @param userDevice our OmemoDevice
371     * @param contactsDevice OmemoDevice in question
372     * @param date date of the last publication after not being published
373     *
374     * @throws IOException if an I/O error occurred.
375     */
376    public abstract void setDateOfLastDeviceIdPublication(OmemoDevice userDevice, OmemoDevice contactsDevice, Date date) throws IOException;
377
378    /**
379     * Return the date of the last time the deviceId was published after previously being not published.
380     * (Point in time, where the status of the deviceId changed from inactive/non-existent to active).
381     *
382     * @param userDevice our OmemoDevice
383     * @param contactsDevice OmemoDevice in question
384     * @return date of the last publication after not being published
385     *
386     * @throws IOException if an I/O error occurred.
387     */
388    public abstract Date getDateOfLastDeviceIdPublication(OmemoDevice userDevice, OmemoDevice contactsDevice) throws IOException;
389
390    /**
391     * Set the date of the last time the signed preKey was renewed.
392     *
393     * @param userDevice our OmemoDevice.
394     * @param date date
395     *
396     * @throws IOException if an I/O error occurred.
397     */
398    public abstract void setDateOfLastSignedPreKeyRenewal(OmemoDevice userDevice, Date date) throws IOException;
399
400    /**
401     * Get the date of the last time the signed preKey was renewed.
402     *
403     * @param userDevice our OmemoDevice.
404     * @return date if existent, otherwise null
405     *
406     * @throws IOException if an I/O error occurred.
407     */
408    public abstract Date getDateOfLastSignedPreKeyRenewal(OmemoDevice userDevice) throws IOException;
409
410    /**
411     * Generate 'count' new PreKeys beginning with id 'startId'.
412     * These preKeys are published and can be used by contacts to establish sessions with us.
413     *
414     * @param startId start id
415     * @param count   how many keys do we want to generate
416     * @return Map of new preKeys
417     */
418    public TreeMap<Integer, T_PreKey> generateOmemoPreKeys(int startId, int count) {
419        return keyUtil().generateOmemoPreKeys(startId, count);
420    }
421
422    /**
423     * Load the preKey with id 'preKeyId' from storage.
424     *
425     * @param userDevice our OmemoDevice.
426     * @param preKeyId id of the key to be loaded
427     * @return loaded preKey
428     *
429     * @throws IOException if an I/O error occurred.
430     */
431    public abstract T_PreKey loadOmemoPreKey(OmemoDevice userDevice, int preKeyId) throws IOException;
432
433    /**
434     * Store a PreKey in storage.
435     *
436     * @param userDevice our OmemoDevice.
437     * @param preKeyId id of the key
438     * @param preKey   key
439     *
440     * @throws IOException if an I/O error occurred.
441     */
442    public abstract void storeOmemoPreKey(OmemoDevice userDevice, int preKeyId, T_PreKey preKey) throws IOException;
443
444    /**
445     * Store a whole bunch of preKeys.
446     *
447     * @param userDevice our OmemoDevice.
448     * @param preKeyHashMap HashMap of preKeys
449     *
450     * @throws IOException if an I/O error occurred.
451     */
452    public void storeOmemoPreKeys(OmemoDevice userDevice, TreeMap<Integer, T_PreKey> preKeyHashMap) throws IOException {
453        for (Map.Entry<Integer, T_PreKey> entry : preKeyHashMap.entrySet()) {
454            storeOmemoPreKey(userDevice, entry.getKey(), entry.getValue());
455        }
456    }
457
458    /**
459     * Remove a preKey from storage. This is called, when a contact used one of our preKeys to establish a session
460     * with us.
461     *
462     * @param userDevice our OmemoDevice.
463     * @param preKeyId id of the used key that will be deleted
464     */
465    public abstract void removeOmemoPreKey(OmemoDevice userDevice, int preKeyId);
466
467    /**
468     * Return all our current OmemoPreKeys.
469     *
470     * @param userDevice our OmemoDevice.
471     * @return Map containing our preKeys
472     *
473     * @throws IOException if an I/O error occurred.
474     */
475    public abstract TreeMap<Integer, T_PreKey> loadOmemoPreKeys(OmemoDevice userDevice) throws IOException;
476
477    /**
478     * Return the signedPreKey with the id 'singedPreKeyId'.
479     *
480     * @param userDevice our OmemoDevice.
481     * @param signedPreKeyId id of the key
482     * @return loaded signed preKey
483     *
484     * @throws IOException if an I/O error occurred.
485     */
486    public abstract T_SigPreKey loadOmemoSignedPreKey(OmemoDevice userDevice, int signedPreKeyId) throws IOException;
487
488    public int loadCurrentOmemoSignedPreKeyId(OmemoDevice userDevice) throws IOException {
489        return loadOmemoSignedPreKeys(userDevice).lastKey();
490    }
491
492    /**
493     * Load all our signed PreKeys.
494     *
495     * @param userDevice our OmemoDevice.
496     * @return HashMap of our singedPreKeys
497     *
498     * @throws IOException if an I/O error occurred.
499     */
500    public abstract TreeMap<Integer, T_SigPreKey> loadOmemoSignedPreKeys(OmemoDevice userDevice) throws IOException;
501
502    /**
503     * Generate a new signed preKey.
504     *
505     * @param identityKeyPair identityKeyPair used to sign the preKey
506     * @param signedPreKeyId  id that the preKey will have
507     * @return a fresh signedPreKey
508     *
509     * @throws CorruptedOmemoKeyException when something goes wrong
510     */
511    public T_SigPreKey generateOmemoSignedPreKey(T_IdKeyPair identityKeyPair, int signedPreKeyId)
512            throws CorruptedOmemoKeyException {
513        return keyUtil().generateOmemoSignedPreKey(identityKeyPair, signedPreKeyId);
514    }
515
516    /**
517     * Store a signedPreKey in storage.
518     *
519     * @param userDevice our OmemoDevice.
520     * @param signedPreKeyId id of the signedPreKey
521     * @param signedPreKey   the key itself
522     *
523     * @throws IOException if an I/O error occurred.
524     */
525    public abstract void storeOmemoSignedPreKey(OmemoDevice userDevice, int signedPreKeyId, T_SigPreKey signedPreKey) throws IOException;
526
527    /**
528     * Remove a signedPreKey from storage.
529     *
530     * @param userDevice our OmemoDevice.
531     * @param signedPreKeyId id of the key that will be removed
532     */
533    public abstract void removeOmemoSignedPreKey(OmemoDevice userDevice, int signedPreKeyId);
534
535    /**
536     * Load the crypto-lib specific session object of the device from storage.
537     *
538     * @param userDevice our OmemoDevice.
539     * @param contactsDevice device whose session we want to load
540     * @return crypto related session
541     *
542     * @throws IOException if an I/O error occurred.
543     */
544    public abstract T_Sess loadRawSession(OmemoDevice userDevice, OmemoDevice contactsDevice) throws IOException;
545
546    /**
547     * Load all crypto-lib specific session objects of contact 'contact'.
548     *
549     * @param userDevice our OmemoDevice.
550     * @param contact BareJid of the contact we want to get all sessions from
551     * @return TreeMap of deviceId and sessions of the contact
552     *
553     * @throws IOException if an I/O error occurred.
554     */
555    public abstract HashMap<Integer, T_Sess> loadAllRawSessionsOf(OmemoDevice userDevice, BareJid contact) throws IOException;
556
557    /**
558     * Store a crypto-lib specific session to storage.
559     *
560     * @param userDevice our OmemoDevice.
561     * @param contactsDevice  OmemoDevice whose session we want to store
562     * @param session session
563     *
564     * @throws IOException if an I/O error occurred.
565     */
566    public abstract void storeRawSession(OmemoDevice userDevice, OmemoDevice contactsDevice, T_Sess session) throws IOException;
567
568    /**
569     * Remove a crypto-lib specific session from storage.
570     *
571     * @param userDevice our OmemoDevice.
572     * @param contactsDevice device whose session we want to delete
573     */
574    public abstract void removeRawSession(OmemoDevice userDevice, OmemoDevice contactsDevice);
575
576    /**
577     * Remove all crypto-lib specific session of a contact.
578     *
579     * @param userDevice our OmemoDevice.
580     * @param contact BareJid of the contact
581     */
582    public abstract void removeAllRawSessionsOf(OmemoDevice userDevice, BareJid contact);
583
584    /**
585     * Return true, if we have a session with the device, otherwise false.
586     * Hint for Signal: Do not try 'return getSession() != null' since this will create a new session.
587     *
588     * @param userDevice our OmemoDevice.
589     * @param contactsDevice device
590     * @return true if we have session, otherwise false
591     */
592    public abstract boolean containsRawSession(OmemoDevice userDevice, OmemoDevice contactsDevice);
593
594    /**
595     * Load a list of deviceIds from contact 'contact' from the local cache.
596     *
597     * @param userDevice our OmemoDevice.
598     * @param contact contact we want to get the deviceList of
599     * @return CachedDeviceList of the contact
600     *
601     * @throws IOException if an I/O error occurred.
602     */
603    public abstract OmemoCachedDeviceList loadCachedDeviceList(OmemoDevice userDevice, BareJid contact) throws IOException;
604
605    /**
606     * Load a list of deviceIds from our own devices.
607     *
608     * @param userDevice our own OMEMO device
609     * @return the cached OMEMO device list.
610     *
611     * @throws IOException if an I/O error occurred.
612     */
613    public OmemoCachedDeviceList loadCachedDeviceList(OmemoDevice userDevice) throws IOException {
614        return loadCachedDeviceList(userDevice, userDevice.getJid());
615    }
616
617    /**
618     * Store the DeviceList of the contact in local storage.
619     * See this as a cache.
620     *
621     * @param userDevice our OmemoDevice.
622     * @param contact    Contact
623     * @param contactsDeviceList list of the contacts devices' ids.
624     *
625     * @throws IOException if an I/O error occurred.
626     */
627    public abstract void storeCachedDeviceList(OmemoDevice userDevice,
628                                               BareJid contact,
629                                               OmemoCachedDeviceList contactsDeviceList) throws IOException;
630
631    /**
632     * Delete this device's IdentityKey, PreKeys, SignedPreKeys and Sessions.
633     *
634     * @param userDevice our OmemoDevice.
635     */
636    public abstract void purgeOwnDeviceKeys(OmemoDevice userDevice);
637
638    /**
639     * Return a concrete KeyUtil object that we can use as a utility to create keys etc.
640     *
641     * @return KeyUtil object
642     */
643    public abstract OmemoKeyUtil<T_IdKeyPair, T_IdKey, T_PreKey, T_SigPreKey, T_Sess, T_ECPub, T_Bundle> keyUtil();
644
645    /**
646     * Return our identityKeys fingerprint.
647     *
648     * @param userDevice our OmemoDevice.
649     * @return fingerprint of our identityKeyPair
650     *
651     * @throws CorruptedOmemoKeyException if the identityKey of userDevice is corrupted.
652     * @throws IOException if an I/O error occurred.
653     */
654    public OmemoFingerprint getFingerprint(OmemoDevice userDevice)
655            throws CorruptedOmemoKeyException, IOException {
656
657        T_IdKeyPair keyPair = loadOmemoIdentityKeyPair(userDevice);
658        if (keyPair == null) {
659            return null;
660        }
661
662        return keyUtil().getFingerprintOfIdentityKey(keyUtil().identityKeyFromPair(keyPair));
663    }
664
665    /**
666     * Return the fingerprint of the identityKey belonging to contactsDevice.
667     *
668     * @param userDevice our OmemoDevice.
669     * @param contactsDevice OmemoDevice we want to have the fingerprint for.
670     * @return fingerprint of the userDevices IdentityKey.
671     *
672     * @throws CorruptedOmemoKeyException if the IdentityKey is corrupted.
673     * @throws NoIdentityKeyException if no IdentityKey for contactsDevice has been found locally.
674     * @throws IOException if an I/O error occurred.
675     */
676    public OmemoFingerprint getFingerprint(OmemoDevice userDevice, OmemoDevice contactsDevice)
677            throws CorruptedOmemoKeyException, NoIdentityKeyException, IOException {
678
679        T_IdKey identityKey = loadOmemoIdentityKey(userDevice, contactsDevice);
680        if (identityKey == null) {
681            throw new NoIdentityKeyException(contactsDevice);
682        }
683        return keyUtil().getFingerprintOfIdentityKey(identityKey);
684    }
685
686    /**
687     * Return the fingerprint of the given devices announced identityKey.
688     * If we have no local copy of the identityKey of the contact, build a fresh session in order to get the key.
689     *
690     * @param managerGuard authenticated OmemoManager
691     * @param contactsDevice OmemoDevice we want to get the fingerprint from
692     * @return fingerprint of the contacts OMEMO device
693     *
694     * @throws CannotEstablishOmemoSessionException If we have no local copy of the identityKey of the contact
695     *                                              and are unable to build a fresh session
696     * @throws CorruptedOmemoKeyException           If the identityKey we have of the contact is corrupted
697     * @throws SmackException.NotConnectedException if the XMPP connection is not connected.
698     * @throws InterruptedException if the calling thread was interrupted.
699     * @throws SmackException.NoResponseException if there was no response from the remote entity.
700     * @throws IOException if an I/O error occurred.
701     */
702    public OmemoFingerprint getFingerprintAndMaybeBuildSession(OmemoManager.LoggedInOmemoManager managerGuard, OmemoDevice contactsDevice)
703            throws CannotEstablishOmemoSessionException, CorruptedOmemoKeyException,
704            SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, IOException {
705        OmemoManager omemoManager = managerGuard.get();
706
707        // Load identityKey
708        T_IdKey identityKey = loadOmemoIdentityKey(omemoManager.getOwnDevice(), contactsDevice);
709        if (identityKey == null) {
710            // Key cannot be loaded. Maybe it doesn't exist. Fetch a bundle to get it...
711            OmemoService.getInstance().buildFreshSessionWithDevice(omemoManager.getConnection(),
712                    omemoManager.getOwnDevice(), contactsDevice);
713        }
714
715        // Load identityKey again
716        identityKey = loadOmemoIdentityKey(omemoManager.getOwnDevice(), contactsDevice);
717        if (identityKey == null) {
718            return null;
719        }
720
721        return keyUtil().getFingerprintOfIdentityKey(identityKey);
722    }
723}