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