AxolotlService.java

   1package eu.siacs.conversations.crypto.axolotl;
   2
   3import static eu.siacs.conversations.utils.Random.SECURE_RANDOM;
   4
   5import android.os.Bundle;
   6import android.security.KeyChain;
   7import android.util.Log;
   8import android.util.Pair;
   9
  10import androidx.annotation.NonNull;
  11import androidx.annotation.Nullable;
  12
  13import com.google.common.collect.ImmutableList;
  14import com.google.common.collect.ImmutableMap;
  15import com.google.common.util.concurrent.Futures;
  16import com.google.common.util.concurrent.ListenableFuture;
  17import com.google.common.util.concurrent.MoreExecutors;
  18import com.google.common.util.concurrent.SettableFuture;
  19
  20import org.bouncycastle.jce.provider.BouncyCastleProvider;
  21import org.whispersystems.libsignal.IdentityKey;
  22import org.whispersystems.libsignal.IdentityKeyPair;
  23import org.whispersystems.libsignal.InvalidKeyException;
  24import org.whispersystems.libsignal.InvalidKeyIdException;
  25import org.whispersystems.libsignal.SessionBuilder;
  26import org.whispersystems.libsignal.SignalProtocolAddress;
  27import org.whispersystems.libsignal.UntrustedIdentityException;
  28import org.whispersystems.libsignal.ecc.ECPublicKey;
  29import org.whispersystems.libsignal.state.PreKeyBundle;
  30import org.whispersystems.libsignal.state.PreKeyRecord;
  31import org.whispersystems.libsignal.state.SignedPreKeyRecord;
  32import org.whispersystems.libsignal.util.KeyHelper;
  33
  34import java.security.PrivateKey;
  35import java.security.Security;
  36import java.security.Signature;
  37import java.security.cert.X509Certificate;
  38import java.util.ArrayList;
  39import java.util.Arrays;
  40import java.util.Collection;
  41import java.util.Collections;
  42import java.util.HashMap;
  43import java.util.HashSet;
  44import java.util.Iterator;
  45import java.util.List;
  46import java.util.Map;
  47import java.util.Random;
  48import java.util.Set;
  49import java.util.concurrent.atomic.AtomicBoolean;
  50
  51import eu.siacs.conversations.Config;
  52import eu.siacs.conversations.entities.Account;
  53import eu.siacs.conversations.entities.Contact;
  54import eu.siacs.conversations.entities.Conversation;
  55import eu.siacs.conversations.entities.Message;
  56import eu.siacs.conversations.parser.IqParser;
  57import eu.siacs.conversations.services.XmppConnectionService;
  58import eu.siacs.conversations.utils.CryptoHelper;
  59import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
  60import eu.siacs.conversations.xml.Element;
  61import eu.siacs.conversations.xml.Namespace;
  62import eu.siacs.conversations.xmpp.Jid;
  63import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded;
  64import eu.siacs.conversations.xmpp.OnIqPacketReceived;
  65import eu.siacs.conversations.xmpp.jingle.OmemoVerification;
  66import eu.siacs.conversations.xmpp.jingle.OmemoVerifiedRtpContentMap;
  67import eu.siacs.conversations.xmpp.jingle.RtpContentMap;
  68import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo;
  69import eu.siacs.conversations.xmpp.jingle.stanzas.OmemoVerifiedIceUdpTransportInfo;
  70import eu.siacs.conversations.xmpp.pep.PublishOptions;
  71import eu.siacs.conversations.xmpp.stanzas.IqPacket;
  72import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
  73
  74public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
  75
  76    public static final String PEP_PREFIX = "eu.siacs.conversations.axolotl";
  77    public static final String PEP_DEVICE_LIST = PEP_PREFIX + ".devicelist";
  78    public static final String PEP_DEVICE_LIST_NOTIFY = PEP_DEVICE_LIST + "+notify";
  79    public static final String PEP_BUNDLES = PEP_PREFIX + ".bundles";
  80    public static final String PEP_VERIFICATION = PEP_PREFIX + ".verification";
  81    public static final String PEP_OMEMO_WHITELISTED = PEP_PREFIX + ".whitelisted";
  82
  83    public static final String LOGPREFIX = "AxolotlService";
  84
  85    private static final int NUM_KEYS_TO_PUBLISH = 100;
  86    private static final int publishTriesThreshold = 3;
  87
  88    private final Account account;
  89    private final XmppConnectionService mXmppConnectionService;
  90    private final SQLiteAxolotlStore axolotlStore;
  91    private final SessionMap sessions;
  92    private final Map<Jid, Set<Integer>> deviceIds;
  93    private final Map<String, XmppAxolotlMessage> messageCache;
  94    private final FetchStatusMap fetchStatusMap;
  95    private final Map<Jid, Boolean> fetchDeviceListStatus = new HashMap<>();
  96    private final HashMap<Jid, List<OnDeviceIdsFetched>> fetchDeviceIdsMap = new HashMap<>();
  97    private final SerialSingleThreadExecutor executor;
  98    private final Set<SignalProtocolAddress> healingAttempts = new HashSet<>();
  99    private final HashSet<Integer> cleanedOwnDeviceIds = new HashSet<>();
 100    private final Set<Integer> PREVIOUSLY_REMOVED_FROM_ANNOUNCEMENT = new HashSet<>();
 101    private int numPublishTriesOnEmptyPep = 0;
 102    private boolean pepBroken = false;
 103    private int lastDeviceListNotificationHash = 0;
 104    private final Set<XmppAxolotlSession> postponedSessions = new HashSet<>(); //sessions stored here will receive after mam catchup treatment
 105    private final Set<SignalProtocolAddress> postponedHealing = new HashSet<>(); //addresses stored here will need a healing notification after mam catchup
 106    private final AtomicBoolean changeAccessMode = new AtomicBoolean(false);
 107
 108    public AxolotlService(Account account, XmppConnectionService connectionService) {
 109        if (account == null || connectionService == null) {
 110            throw new IllegalArgumentException("account and service cannot be null");
 111        }
 112        if (Security.getProvider("BC") == null) {
 113            Security.addProvider(new BouncyCastleProvider());
 114        }
 115        this.mXmppConnectionService = connectionService;
 116        this.account = account;
 117        this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService);
 118        this.deviceIds = new HashMap<>();
 119        this.messageCache = new HashMap<>();
 120        this.sessions = new SessionMap(mXmppConnectionService, axolotlStore, account);
 121        this.fetchStatusMap = new FetchStatusMap();
 122        this.executor = new SerialSingleThreadExecutor("Axolotl");
 123    }
 124
 125    public static String getLogprefix(Account account) {
 126        return LOGPREFIX + " (" + account.getJid().asBareJid().toString() + "): ";
 127    }
 128
 129    @Override
 130    public void onAdvancedStreamFeaturesAvailable(Account account) {
 131        if (Config.supportOmemo()
 132                && account.getXmppConnection() != null
 133                && account.getXmppConnection().getFeatures().pep()) {
 134            publishBundlesIfNeeded(true, false);
 135        } else {
 136            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": skipping OMEMO initialization");
 137        }
 138    }
 139
 140    private boolean hasErrorFetchingDeviceList(Jid jid) {
 141        Boolean status = fetchDeviceListStatus.get(jid);
 142        return status != null && !status;
 143    }
 144
 145    public boolean hasErrorFetchingDeviceList(List<Jid> jids) {
 146        for (Jid jid : jids) {
 147            if (hasErrorFetchingDeviceList(jid)) {
 148                return true;
 149            }
 150        }
 151        return false;
 152    }
 153
 154    public boolean fetchMapHasErrors(List<Jid> jids) {
 155        for (Jid jid : jids) {
 156            if (deviceIds.get(jid) != null) {
 157                for (Integer foreignId : this.deviceIds.get(jid)) {
 158                    SignalProtocolAddress address = new SignalProtocolAddress(jid.toString(), foreignId);
 159                    if (fetchStatusMap.getAll(address.getName()).containsValue(FetchStatus.ERROR)) {
 160                        return true;
 161                    }
 162                }
 163            }
 164        }
 165        return false;
 166    }
 167
 168    public void preVerifyFingerprint(Contact contact, String fingerprint) {
 169        axolotlStore.preVerifyFingerprint(contact.getAccount(), contact.getJid().asBareJid().toString(), fingerprint);
 170    }
 171
 172    public void preVerifyFingerprint(Account account, String fingerprint) {
 173        axolotlStore.preVerifyFingerprint(account, account.getJid().asBareJid().toString(), fingerprint);
 174    }
 175
 176    public boolean hasVerifiedKeys(String name) {
 177        for (XmppAxolotlSession session : this.sessions.getAll(name).values()) {
 178            if (session.getTrust().isVerified()) {
 179                return true;
 180            }
 181        }
 182        return false;
 183    }
 184
 185    public String getOwnFingerprint() {
 186        return CryptoHelper.bytesToHex(axolotlStore.getIdentityKeyPair().getPublicKey().serialize());
 187    }
 188
 189    public Set<IdentityKey> getKeysWithTrust(FingerprintStatus status) {
 190        return axolotlStore.getContactKeysWithTrust(account.getJid().asBareJid().toString(), status);
 191    }
 192
 193    public Set<IdentityKey> getKeysWithTrust(FingerprintStatus status, Jid jid) {
 194        return axolotlStore.getContactKeysWithTrust(jid.asBareJid().toString(), status);
 195    }
 196
 197    public Set<IdentityKey> getKeysWithTrust(FingerprintStatus status, List<Jid> jids) {
 198        Set<IdentityKey> keys = new HashSet<>();
 199        for (Jid jid : jids) {
 200            keys.addAll(axolotlStore.getContactKeysWithTrust(jid.toString(), status));
 201        }
 202        return keys;
 203    }
 204
 205    public Set<Jid> findCounterpartsBySourceId(int sid) {
 206        return sessions.findCounterpartsForSourceId(sid);
 207    }
 208
 209    public long getNumTrustedKeys(Jid jid) {
 210        return axolotlStore.getContactNumTrustedKeys(jid.asBareJid().toString());
 211    }
 212
 213    public boolean anyTargetHasNoTrustedKeys(List<Jid> jids) {
 214        for (Jid jid : jids) {
 215            if (axolotlStore.getContactNumTrustedKeys(jid.asBareJid().toString()) == 0) {
 216                return true;
 217            }
 218        }
 219        return false;
 220    }
 221
 222    private SignalProtocolAddress getAddressForJid(Jid jid) {
 223        return new SignalProtocolAddress(jid.toString(), 0);
 224    }
 225
 226    public Collection<XmppAxolotlSession> findOwnSessions() {
 227        SignalProtocolAddress ownAddress = getAddressForJid(account.getJid().asBareJid());
 228        ArrayList<XmppAxolotlSession> s = new ArrayList<>(this.sessions.getAll(ownAddress.getName()).values());
 229        Collections.sort(s);
 230        return s;
 231    }
 232
 233    public Collection<XmppAxolotlSession> findSessionsForContact(Contact contact) {
 234        SignalProtocolAddress contactAddress = getAddressForJid(contact.getJid());
 235        ArrayList<XmppAxolotlSession> s = new ArrayList<>(this.sessions.getAll(contactAddress.getName()).values());
 236        Collections.sort(s);
 237        return s;
 238    }
 239
 240    private Set<XmppAxolotlSession> findSessionsForConversation(Conversation conversation) {
 241        if (conversation.getContact().isSelf()) {
 242            //will be added in findOwnSessions()
 243            return Collections.emptySet();
 244        }
 245        HashSet<XmppAxolotlSession> sessions = new HashSet<>();
 246        for (Jid jid : conversation.getAcceptedCryptoTargets()) {
 247            sessions.addAll(this.sessions.getAll(getAddressForJid(jid).getName()).values());
 248        }
 249        return sessions;
 250    }
 251
 252    private boolean hasAny(Jid jid) {
 253        return sessions.hasAny(getAddressForJid(jid));
 254    }
 255
 256    public boolean isPepBroken() {
 257        return this.pepBroken;
 258    }
 259
 260    public void resetBrokenness() {
 261        this.pepBroken = false;
 262        this.numPublishTriesOnEmptyPep = 0;
 263        this.lastDeviceListNotificationHash = 0;
 264        this.healingAttempts.clear();
 265    }
 266
 267    public void clearErrorsInFetchStatusMap(Jid jid) {
 268        fetchStatusMap.clearErrorFor(jid);
 269        fetchDeviceListStatus.remove(jid);
 270    }
 271
 272    public void regenerateKeys(boolean wipeOther) {
 273        axolotlStore.regenerate();
 274        sessions.clear();
 275        fetchStatusMap.clear();
 276        fetchDeviceIdsMap.clear();
 277        fetchDeviceListStatus.clear();
 278        publishBundlesIfNeeded(true, wipeOther);
 279    }
 280
 281    public void destroy() {
 282        Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": destroying old axolotl service. no longer in use");
 283        mXmppConnectionService.databaseBackend.wipeAxolotlDb(account);
 284    }
 285
 286    public AxolotlService makeNew() {
 287        Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": make new axolotl service");
 288        return new AxolotlService(this.account, this.mXmppConnectionService);
 289    }
 290
 291    public int getOwnDeviceId() {
 292        return axolotlStore.getLocalRegistrationId();
 293    }
 294
 295    public SignalProtocolAddress getOwnAxolotlAddress() {
 296        return new SignalProtocolAddress(account.getJid().asBareJid().toString(), getOwnDeviceId());
 297    }
 298
 299    public Set<Integer> getOwnDeviceIds() {
 300        return this.deviceIds.get(account.getJid().asBareJid());
 301    }
 302
 303    public void registerDevices(final Jid jid, @NonNull final Set<Integer> deviceIds) {
 304        final int hash = deviceIds.hashCode();
 305        final boolean me = jid.asBareJid().equals(account.getJid().asBareJid());
 306        if (me) {
 307            if (hash != 0 && hash == this.lastDeviceListNotificationHash) {
 308                Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring duplicate own device id list");
 309                return;
 310            }
 311            this.lastDeviceListNotificationHash = hash;
 312        }
 313        boolean needsPublishing = me && !deviceIds.contains(getOwnDeviceId());
 314        if (me) {
 315            deviceIds.remove(getOwnDeviceId());
 316        }
 317        Set<Integer> expiredDevices = new HashSet<>(axolotlStore.getSubDeviceSessions(jid.asBareJid().toString()));
 318        expiredDevices.removeAll(deviceIds);
 319        for (Integer deviceId : expiredDevices) {
 320            SignalProtocolAddress address = new SignalProtocolAddress(jid.asBareJid().toString(), deviceId);
 321            XmppAxolotlSession session = sessions.get(address);
 322            if (session != null && session.getFingerprint() != null) {
 323                if (session.getTrust().isActive()) {
 324                    session.setTrust(session.getTrust().toInactive());
 325                }
 326            }
 327        }
 328        Set<Integer> newDevices = new HashSet<>(deviceIds);
 329        for (Integer deviceId : newDevices) {
 330            SignalProtocolAddress address = new SignalProtocolAddress(jid.asBareJid().toString(), deviceId);
 331            XmppAxolotlSession session = sessions.get(address);
 332            if (session != null && session.getFingerprint() != null) {
 333                if (!session.getTrust().isActive()) {
 334                    Log.d(Config.LOGTAG, "reactivating device with fingerprint " + session.getFingerprint());
 335                    session.setTrust(session.getTrust().toActive());
 336                }
 337            }
 338        }
 339        if (me) {
 340            if (Config.OMEMO_AUTO_EXPIRY != 0) {
 341                needsPublishing |= deviceIds.removeAll(getExpiredDevices());
 342            }
 343            needsPublishing |= this.changeAccessMode.get();
 344            for (Integer deviceId : deviceIds) {
 345                SignalProtocolAddress ownDeviceAddress = new SignalProtocolAddress(jid.asBareJid().toString(), deviceId);
 346                if (sessions.get(ownDeviceAddress) == null) {
 347                    FetchStatus status = fetchStatusMap.get(ownDeviceAddress);
 348                    if (status == null || status == FetchStatus.TIMEOUT) {
 349                        fetchStatusMap.put(ownDeviceAddress, FetchStatus.PENDING);
 350                        this.buildSessionFromPEP(ownDeviceAddress);
 351                    }
 352                }
 353            }
 354            if (needsPublishing) {
 355                publishOwnDeviceId(deviceIds);
 356            }
 357        }
 358        final Set<Integer> oldSet = this.deviceIds.get(jid);
 359        final boolean changed = oldSet == null || oldSet.hashCode() != hash;
 360        this.deviceIds.put(jid, deviceIds);
 361        if (changed) {
 362            mXmppConnectionService.updateConversationUi(); //update the lock icon
 363            mXmppConnectionService.keyStatusUpdated(null);
 364            if (me) {
 365                mXmppConnectionService.updateAccountUi();
 366            }
 367        } else {
 368            Log.d(Config.LOGTAG, "skipped device list update because it hasn't changed");
 369        }
 370    }
 371
 372    public void wipeOtherPepDevices() {
 373        if (pepBroken) {
 374            Log.d(Config.LOGTAG, getLogprefix(account) + "wipeOtherPepDevices called, but PEP is broken. Ignoring... ");
 375            return;
 376        }
 377        Set<Integer> deviceIds = new HashSet<>();
 378        deviceIds.add(getOwnDeviceId());
 379        publishDeviceIdsAndRefineAccessModel(deviceIds);
 380    }
 381
 382    public void distrustFingerprint(final String fingerprint) {
 383        final String fp = fingerprint.replaceAll("\\s", "");
 384        final FingerprintStatus fingerprintStatus = axolotlStore.getFingerprintStatus(fp);
 385        axolotlStore.setFingerprintStatus(fp, fingerprintStatus.toUntrusted());
 386    }
 387
 388    private void publishOwnDeviceIdIfNeeded() {
 389        if (pepBroken) {
 390            Log.d(Config.LOGTAG, getLogprefix(account) + "publishOwnDeviceIdIfNeeded called, but PEP is broken. Ignoring... ");
 391            return;
 392        }
 393        IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().asBareJid());
 394        mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
 395            @Override
 396            public void onIqPacketReceived(Account account, IqPacket packet) {
 397                if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
 398                    Log.d(Config.LOGTAG, getLogprefix(account) + "Timeout received while retrieving own Device Ids.");
 399                } else {
 400                    //TODO consider calling registerDevices only after item-not-found to account for broken PEPs
 401                    Element item = mXmppConnectionService.getIqParser().getItem(packet);
 402                    Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
 403                    Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": retrieved own device list: " + deviceIds);
 404                    registerDevices(account.getJid().asBareJid(), deviceIds);
 405                }
 406            }
 407        });
 408    }
 409
 410    private Set<Integer> getExpiredDevices() {
 411        Set<Integer> devices = new HashSet<>();
 412        for (XmppAxolotlSession session : findOwnSessions()) {
 413            if (session.getTrust().isActive()) {
 414                long diff = System.currentTimeMillis() - session.getTrust().getLastActivation();
 415                if (diff > Config.OMEMO_AUTO_EXPIRY) {
 416                    long lastMessageDiff = System.currentTimeMillis() - mXmppConnectionService.databaseBackend.getLastTimeFingerprintUsed(account, session.getFingerprint());
 417                    long hours = Math.round(lastMessageDiff / (1000 * 60.0 * 60.0));
 418                    if (lastMessageDiff > Config.OMEMO_AUTO_EXPIRY) {
 419                        devices.add(session.getRemoteAddress().getDeviceId());
 420                        session.setTrust(session.getTrust().toInactive());
 421                        Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": added own device " + session.getFingerprint() + " to list of expired devices. Last message received " + hours + " hours ago");
 422                    } else {
 423                        Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": own device " + session.getFingerprint() + " was active " + hours + " hours ago");
 424                    }
 425                } //TODO print last activation diff
 426            }
 427        }
 428        return devices;
 429    }
 430
 431    private void publishOwnDeviceId(Set<Integer> deviceIds) {
 432        Set<Integer> deviceIdsCopy = new HashSet<>(deviceIds);
 433        Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "publishing own device ids");
 434        if (deviceIdsCopy.isEmpty()) {
 435            if (numPublishTriesOnEmptyPep >= publishTriesThreshold) {
 436                Log.w(Config.LOGTAG, getLogprefix(account) + "Own device publish attempt threshold exceeded, aborting...");
 437                pepBroken = true;
 438                return;
 439            } else {
 440                numPublishTriesOnEmptyPep++;
 441                Log.w(Config.LOGTAG, getLogprefix(account) + "Own device list empty, attempting to publish (try " + numPublishTriesOnEmptyPep + ")");
 442            }
 443        } else {
 444            numPublishTriesOnEmptyPep = 0;
 445        }
 446        deviceIdsCopy.add(getOwnDeviceId());
 447        publishDeviceIdsAndRefineAccessModel(deviceIdsCopy);
 448    }
 449
 450    private void publishDeviceIdsAndRefineAccessModel(Set<Integer> ids) {
 451        publishDeviceIdsAndRefineAccessModel(ids, true);
 452    }
 453
 454    private void publishDeviceIdsAndRefineAccessModel(final Set<Integer> ids, final boolean firstAttempt) {
 455        final Bundle publishOptions = account.getXmppConnection().getFeatures().pepPublishOptions() ? PublishOptions.openAccess() : null;
 456        IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(ids, publishOptions);
 457        mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
 458            @Override
 459            public void onIqPacketReceived(Account account, IqPacket packet) {
 460                final Element error = packet.getType() == IqPacket.TYPE.ERROR ? packet.findChild("error") : null;
 461                final boolean preConditionNotMet = PublishOptions.preconditionNotMet(packet);
 462                if (firstAttempt && preConditionNotMet) {
 463                    Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": precondition wasn't met for device list. pushing node configuration");
 464                    mXmppConnectionService.pushNodeConfiguration(account, AxolotlService.PEP_DEVICE_LIST, publishOptions, new XmppConnectionService.OnConfigurationPushed() {
 465                        @Override
 466                        public void onPushSucceeded() {
 467                            publishDeviceIdsAndRefineAccessModel(ids, false);
 468                        }
 469
 470                        @Override
 471                        public void onPushFailed() {
 472                            publishDeviceIdsAndRefineAccessModel(ids, false);
 473                        }
 474                    });
 475                } else {
 476                    if (AxolotlService.this.changeAccessMode.compareAndSet(true, false)) {
 477                        Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": done changing access mode");
 478                        account.setOption(Account.OPTION_REQUIRES_ACCESS_MODE_CHANGE, false);
 479                        mXmppConnectionService.databaseBackend.updateAccount(account);
 480                    }
 481                    if (packet.getType() == IqPacket.TYPE.ERROR) {
 482                        if (preConditionNotMet) {
 483                            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": device list pre condition still not met on second attempt");
 484                        } else if (error != null) {
 485                            pepBroken = true;
 486                            Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing own device id" + packet.findChild("error"));
 487                        }
 488
 489                    }
 490                }
 491            }
 492        });
 493    }
 494
 495    public void publishDeviceVerificationAndBundle(final SignedPreKeyRecord signedPreKeyRecord,
 496                                                   final Set<PreKeyRecord> preKeyRecords,
 497                                                   final boolean announceAfter,
 498                                                   final boolean wipe) {
 499        try {
 500            IdentityKey axolotlPublicKey = axolotlStore.getIdentityKeyPair().getPublicKey();
 501            PrivateKey x509PrivateKey = KeyChain.getPrivateKey(mXmppConnectionService, account.getPrivateKeyAlias());
 502            X509Certificate[] chain = KeyChain.getCertificateChain(mXmppConnectionService, account.getPrivateKeyAlias());
 503            Signature verifier = Signature.getInstance("sha256WithRSA");
 504            verifier.initSign(x509PrivateKey, SECURE_RANDOM);
 505            verifier.update(axolotlPublicKey.serialize());
 506            byte[] signature = verifier.sign();
 507            IqPacket packet = mXmppConnectionService.getIqGenerator().publishVerification(signature, chain, getOwnDeviceId());
 508            Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": publish verification for device " + getOwnDeviceId());
 509            mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
 510                @Override
 511                public void onIqPacketReceived(final Account account, IqPacket packet) {
 512                    String node = AxolotlService.PEP_VERIFICATION + ":" + getOwnDeviceId();
 513                    mXmppConnectionService.pushNodeConfiguration(account, node, PublishOptions.openAccess(), new XmppConnectionService.OnConfigurationPushed() {
 514                        @Override
 515                        public void onPushSucceeded() {
 516                            Log.d(Config.LOGTAG, getLogprefix(account) + "configured verification node to be world readable");
 517                            publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe);
 518                        }
 519
 520                        @Override
 521                        public void onPushFailed() {
 522                            Log.d(Config.LOGTAG, getLogprefix(account) + "unable to set access model on verification node");
 523                            publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe);
 524                        }
 525                    });
 526                }
 527            });
 528        } catch (Exception e) {
 529            e.printStackTrace();
 530        }
 531    }
 532
 533    public void publishBundlesIfNeeded(final boolean announce, final boolean wipe) {
 534        if (pepBroken) {
 535            Log.d(Config.LOGTAG, getLogprefix(account) + "publishBundlesIfNeeded called, but PEP is broken. Ignoring... ");
 536            return;
 537        }
 538
 539        if (account.getXmppConnection().getFeatures().pepPublishOptions()) {
 540            this.changeAccessMode.set(account.isOptionSet(Account.OPTION_REQUIRES_ACCESS_MODE_CHANGE));
 541        } else {
 542            if (account.setOption(Account.OPTION_REQUIRES_ACCESS_MODE_CHANGE, true)) {
 543                Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": server doesn’t support publish-options. setting for later access mode change");
 544                mXmppConnectionService.databaseBackend.updateAccount(account);
 545            }
 546        }
 547        if (this.changeAccessMode.get()) {
 548            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": server gained publish-options capabilities. changing access model");
 549        }
 550        IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(account.getJid().asBareJid(), getOwnDeviceId());
 551        mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
 552            @Override
 553            public void onIqPacketReceived(Account account, IqPacket packet) {
 554
 555                if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
 556                    return; //ignore timeout. do nothing
 557                }
 558
 559                if (packet.getType() == IqPacket.TYPE.ERROR) {
 560                    Element error = packet.findChild("error");
 561                    if (error == null || !error.hasChild("item-not-found")) {
 562                        pepBroken = true;
 563                        Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "request for device bundles came back with something other than item-not-found" + packet);
 564                        return;
 565                    }
 566                }
 567
 568                PreKeyBundle bundle = mXmppConnectionService.getIqParser().bundle(packet);
 569                Map<Integer, ECPublicKey> keys = mXmppConnectionService.getIqParser().preKeyPublics(packet);
 570                boolean flush = false;
 571                if (bundle == null) {
 572                    Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid bundle:" + packet);
 573                    bundle = new PreKeyBundle(-1, -1, -1, null, -1, null, null, null);
 574                    flush = true;
 575                }
 576                if (keys == null) {
 577                    Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid prekeys:" + packet);
 578                }
 579                try {
 580                    boolean changed = false;
 581                    // Validate IdentityKey
 582                    IdentityKeyPair identityKeyPair = axolotlStore.getIdentityKeyPair();
 583                    if (flush || !identityKeyPair.getPublicKey().equals(bundle.getIdentityKey())) {
 584                        Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding own IdentityKey " + identityKeyPair.getPublicKey() + " to PEP.");
 585                        changed = true;
 586                    }
 587
 588                    // Validate signedPreKeyRecord + ID
 589                    SignedPreKeyRecord signedPreKeyRecord;
 590                    int numSignedPreKeys = axolotlStore.getSignedPreKeysCount();
 591                    try {
 592                        signedPreKeyRecord = axolotlStore.loadSignedPreKey(bundle.getSignedPreKeyId());
 593                        if (flush
 594                                || !bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey())
 595                                || !Arrays.equals(bundle.getSignedPreKeySignature(), signedPreKeyRecord.getSignature())) {
 596                            Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
 597                            signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
 598                            axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
 599                            changed = true;
 600                        }
 601                    } catch (InvalidKeyIdException e) {
 602                        Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
 603                        signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
 604                        axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
 605                        changed = true;
 606                    }
 607
 608                    // Validate PreKeys
 609                    Set<PreKeyRecord> preKeyRecords = new HashSet<>();
 610                    if (keys != null) {
 611                        for (Integer id : keys.keySet()) {
 612                            try {
 613                                PreKeyRecord preKeyRecord = axolotlStore.loadPreKey(id);
 614                                if (preKeyRecord.getKeyPair().getPublicKey().equals(keys.get(id))) {
 615                                    preKeyRecords.add(preKeyRecord);
 616                                }
 617                            } catch (InvalidKeyIdException ignored) {
 618                            }
 619                        }
 620                    }
 621                    int newKeys = NUM_KEYS_TO_PUBLISH - preKeyRecords.size();
 622                    if (newKeys > 0) {
 623                        List<PreKeyRecord> newRecords = KeyHelper.generatePreKeys(
 624                                axolotlStore.getCurrentPreKeyId() + 1, newKeys);
 625                        preKeyRecords.addAll(newRecords);
 626                        for (PreKeyRecord record : newRecords) {
 627                            axolotlStore.storePreKey(record.getId(), record);
 628                        }
 629                        changed = true;
 630                        Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding " + newKeys + " new preKeys to PEP.");
 631                    }
 632
 633
 634                    if (changed || changeAccessMode.get()) {
 635                        if (account.getPrivateKeyAlias() != null && Config.X509_VERIFICATION) {
 636                            mXmppConnectionService.publishDisplayName(account);
 637                            publishDeviceVerificationAndBundle(signedPreKeyRecord, preKeyRecords, announce, wipe);
 638                        } else {
 639                            publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announce, wipe);
 640                        }
 641                    } else {
 642                        Log.d(Config.LOGTAG, getLogprefix(account) + "Bundle " + getOwnDeviceId() + " in PEP was current");
 643                        if (wipe) {
 644                            wipeOtherPepDevices();
 645                        } else if (announce) {
 646                            Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
 647                            publishOwnDeviceIdIfNeeded();
 648                        }
 649                    }
 650                } catch (InvalidKeyException e) {
 651                    Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage());
 652                }
 653            }
 654        });
 655    }
 656
 657    private void publishDeviceBundle(SignedPreKeyRecord signedPreKeyRecord,
 658                                     Set<PreKeyRecord> preKeyRecords,
 659                                     final boolean announceAfter,
 660                                     final boolean wipe) {
 661        publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe, true);
 662    }
 663
 664    private void publishDeviceBundle(final SignedPreKeyRecord signedPreKeyRecord,
 665                                     final Set<PreKeyRecord> preKeyRecords,
 666                                     final boolean announceAfter,
 667                                     final boolean wipe,
 668                                     final boolean firstAttempt) {
 669        final Bundle publishOptions = account.getXmppConnection().getFeatures().pepPublishOptions() ? PublishOptions.openAccess() : null;
 670        final IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles(
 671                signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(),
 672                preKeyRecords, getOwnDeviceId(), publishOptions);
 673        Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing...");
 674        mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
 675            @Override
 676            public void onIqPacketReceived(final Account account, IqPacket packet) {
 677                final boolean preconditionNotMet = PublishOptions.preconditionNotMet(packet);
 678                if (firstAttempt && preconditionNotMet) {
 679                    Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": precondition wasn't met for bundle. pushing node configuration");
 680                    final String node = AxolotlService.PEP_BUNDLES + ":" + getOwnDeviceId();
 681                    mXmppConnectionService.pushNodeConfiguration(account, node, publishOptions, new XmppConnectionService.OnConfigurationPushed() {
 682                        @Override
 683                        public void onPushSucceeded() {
 684                            publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe, false);
 685                        }
 686
 687                        @Override
 688                        public void onPushFailed() {
 689                            publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe, false);
 690                        }
 691                    });
 692                } else if (packet.getType() == IqPacket.TYPE.RESULT) {
 693                    Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Successfully published bundle. ");
 694                    if (wipe) {
 695                        wipeOtherPepDevices();
 696                    } else if (announceAfter) {
 697                        Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
 698                        publishOwnDeviceIdIfNeeded();
 699                    }
 700                } else if (packet.getType() == IqPacket.TYPE.ERROR) {
 701                    if (preconditionNotMet) {
 702                        Log.d(Config.LOGTAG, getLogprefix(account) + "bundle precondition still not met after second attempt");
 703                    } else {
 704                        Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing bundle: " + packet.toString());
 705                    }
 706                    pepBroken = true;
 707                }
 708            }
 709        });
 710    }
 711
 712    public void deleteOmemoIdentity() {
 713        mXmppConnectionService.deletePepNode(
 714                account, AxolotlService.PEP_BUNDLES + ":" + getOwnDeviceId());
 715        final Set<Integer> ownDeviceIds = getOwnDeviceIds();
 716        publishDeviceIdsAndRefineAccessModel(
 717                ownDeviceIds == null ? Collections.emptySet() : ownDeviceIds);
 718    }
 719
 720    public List<Jid> getCryptoTargets(Conversation conversation) {
 721        final List<Jid> jids;
 722        if (conversation.getMode() == Conversation.MODE_SINGLE) {
 723            jids = new ArrayList<>();
 724            jids.add(conversation.getJid().asBareJid());
 725        } else {
 726            jids = conversation.getMucOptions().getMembers(false);
 727        }
 728        return jids;
 729    }
 730
 731    public FingerprintStatus getFingerprintTrust(String fingerprint) {
 732        return axolotlStore.getFingerprintStatus(fingerprint);
 733    }
 734
 735    public X509Certificate getFingerprintCertificate(String fingerprint) {
 736        return axolotlStore.getFingerprintCertificate(fingerprint);
 737    }
 738
 739    public void setFingerprintTrust(final String fingerprint, final FingerprintStatus status) {
 740        axolotlStore.setFingerprintStatus(fingerprint, status);
 741        // TODO we decided to call this after a fingerprint gets toggled to update the 'your contact
 742        //  is using unverified devices text'; however this means the entire screen gets redrawn
 743        //  after a toggle which might be annoying or cause other weird UI glitches
 744        mXmppConnectionService.updateAccountUi();
 745    }
 746
 747    private ListenableFuture<XmppAxolotlSession> verifySessionWithPEP(final XmppAxolotlSession session) {
 748        Log.d(Config.LOGTAG, "trying to verify fresh session (" + session.getRemoteAddress().getName() + ") with pep");
 749        final SignalProtocolAddress address = session.getRemoteAddress();
 750        final IdentityKey identityKey = session.getIdentityKey();
 751        final Jid jid;
 752        try {
 753            jid = Jid.of(address.getName());
 754        } catch (final IllegalArgumentException e) {
 755            fetchStatusMap.put(address, FetchStatus.SUCCESS);
 756            finishBuildingSessionsFromPEP(address);
 757            return Futures.immediateFuture(session);
 758        }
 759        final SettableFuture<XmppAxolotlSession> future = SettableFuture.create();
 760        final IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(jid, address.getDeviceId());
 761        mXmppConnectionService.sendIqPacket(account, packet, (account, response) -> {
 762            Pair<X509Certificate[], byte[]> verification = mXmppConnectionService.getIqParser().verification(response);
 763            if (verification != null) {
 764                try {
 765                    Signature verifier = Signature.getInstance("sha256WithRSA");
 766                    verifier.initVerify(verification.first[0]);
 767                    verifier.update(identityKey.serialize());
 768                    if (verifier.verify(verification.second)) {
 769                        try {
 770                            mXmppConnectionService.getMemorizingTrustManager().getNonInteractive().checkClientTrusted(verification.first, "RSA");
 771                            String fingerprint = session.getFingerprint();
 772                            Log.d(Config.LOGTAG, "verified session with x.509 signature. fingerprint was: " + fingerprint);
 773                            setFingerprintTrust(fingerprint, FingerprintStatus.createActiveVerified(true));
 774                            axolotlStore.setFingerprintCertificate(fingerprint, verification.first[0]);
 775                            fetchStatusMap.put(address, FetchStatus.SUCCESS_VERIFIED);
 776                            Bundle information = CryptoHelper.extractCertificateInformation(verification.first[0]);
 777                            try {
 778                                final String cn = information.getString("subject_cn");
 779                                final Jid jid1 = Jid.of(address.getName());
 780                                Log.d(Config.LOGTAG, "setting common name for " + jid1 + " to " + cn);
 781                                account.getRoster().getContact(jid1).setCommonName(cn);
 782                            } catch (final IllegalArgumentException ignored) {
 783                                //ignored
 784                            }
 785                            finishBuildingSessionsFromPEP(address);
 786                            future.set(session);
 787                            return;
 788                        } catch (Exception e) {
 789                            Log.d(Config.LOGTAG, "could not verify certificate");
 790                        }
 791                    }
 792                } catch (Exception e) {
 793                    Log.d(Config.LOGTAG, "error during verification " + e.getMessage());
 794                }
 795            } else {
 796                Log.d(Config.LOGTAG, "no verification found");
 797            }
 798            fetchStatusMap.put(address, FetchStatus.SUCCESS);
 799            finishBuildingSessionsFromPEP(address);
 800            future.set(session);
 801        });
 802        return future;
 803    }
 804
 805    private void finishBuildingSessionsFromPEP(final SignalProtocolAddress address) {
 806        SignalProtocolAddress ownAddress = new SignalProtocolAddress(account.getJid().asBareJid().toString(), 0);
 807        Map<Integer, FetchStatus> own = fetchStatusMap.getAll(ownAddress.getName());
 808        Map<Integer, FetchStatus> remote = fetchStatusMap.getAll(address.getName());
 809        if (!own.containsValue(FetchStatus.PENDING) && !remote.containsValue(FetchStatus.PENDING)) {
 810            FetchStatus report = null;
 811            if (own.containsValue(FetchStatus.SUCCESS) || remote.containsValue(FetchStatus.SUCCESS)) {
 812                report = FetchStatus.SUCCESS;
 813            } else if (own.containsValue(FetchStatus.SUCCESS_VERIFIED) || remote.containsValue(FetchStatus.SUCCESS_VERIFIED)) {
 814                report = FetchStatus.SUCCESS_VERIFIED;
 815            } else if (own.containsValue(FetchStatus.SUCCESS_TRUSTED) || remote.containsValue(FetchStatus.SUCCESS_TRUSTED)) {
 816                report = FetchStatus.SUCCESS_TRUSTED;
 817            } else if (own.containsValue(FetchStatus.ERROR) || remote.containsValue(FetchStatus.ERROR)) {
 818                report = FetchStatus.ERROR;
 819            }
 820            mXmppConnectionService.keyStatusUpdated(report);
 821        }
 822        if (Config.REMOVE_BROKEN_DEVICES) {
 823            Set<Integer> ownDeviceIds = new HashSet<>(getOwnDeviceIds());
 824            boolean publish = false;
 825            for (Map.Entry<Integer, FetchStatus> entry : own.entrySet()) {
 826                int id = entry.getKey();
 827                if (entry.getValue() == FetchStatus.ERROR && PREVIOUSLY_REMOVED_FROM_ANNOUNCEMENT.add(id) && ownDeviceIds.remove(id)) {
 828                    publish = true;
 829                    Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": error fetching own device with id " + id + ". removing from announcement");
 830                }
 831            }
 832            if (publish) {
 833                publishOwnDeviceId(ownDeviceIds);
 834            }
 835        }
 836    }
 837
 838    public boolean hasEmptyDeviceList(Jid jid) {
 839        return !hasAny(jid) && (!deviceIds.containsKey(jid) || deviceIds.get(jid).isEmpty());
 840    }
 841
 842    public void fetchDeviceIds(final Jid jid) {
 843        fetchDeviceIds(jid, null);
 844    }
 845
 846    private void fetchDeviceIds(final Jid jid, OnDeviceIdsFetched callback) {
 847        IqPacket packet;
 848        synchronized (this.fetchDeviceIdsMap) {
 849            List<OnDeviceIdsFetched> callbacks = this.fetchDeviceIdsMap.get(jid);
 850            if (callbacks != null) {
 851                if (callback != null) {
 852                    callbacks.add(callback);
 853                }
 854                Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": fetching device ids for " + jid + " already running. adding callback");
 855                packet = null;
 856            } else {
 857                callbacks = new ArrayList<>();
 858                if (callback != null) {
 859                    callbacks.add(callback);
 860                }
 861                this.fetchDeviceIdsMap.put(jid, callbacks);
 862                Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": fetching device ids for " + jid);
 863                packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(jid);
 864            }
 865        }
 866        if (packet != null) {
 867            mXmppConnectionService.sendIqPacket(account, packet, (account, response) -> {
 868                if (response.getType() == IqPacket.TYPE.RESULT) {
 869                    fetchDeviceListStatus.put(jid, true);
 870                    Element item = mXmppConnectionService.getIqParser().getItem(response);
 871                    Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
 872                    registerDevices(jid, deviceIds);
 873                    final List<OnDeviceIdsFetched> callbacks;
 874                    synchronized (fetchDeviceIdsMap) {
 875                        callbacks = fetchDeviceIdsMap.remove(jid);
 876                    }
 877                    if (callbacks != null) {
 878                        for (OnDeviceIdsFetched c : callbacks) {
 879                            c.fetched(jid, deviceIds);
 880                        }
 881                    }
 882                } else {
 883                    if (response.getType() == IqPacket.TYPE.TIMEOUT) {
 884                        fetchDeviceListStatus.remove(jid);
 885                    } else {
 886                        fetchDeviceListStatus.put(jid, false);
 887                    }
 888                    final List<OnDeviceIdsFetched> callbacks;
 889                    synchronized (fetchDeviceIdsMap) {
 890                        callbacks = fetchDeviceIdsMap.remove(jid);
 891                    }
 892                    if (callbacks != null) {
 893                        for (OnDeviceIdsFetched c : callbacks) {
 894                            c.fetched(jid, null);
 895                        }
 896                    }
 897                }
 898            });
 899        }
 900    }
 901
 902    private void fetchDeviceIds(List<Jid> jids, final OnMultipleDeviceIdFetched callback) {
 903        final ArrayList<Jid> unfinishedJids = new ArrayList<>(jids);
 904        synchronized (unfinishedJids) {
 905            for (Jid jid : unfinishedJids) {
 906                fetchDeviceIds(jid, (j, deviceIds) -> {
 907                    synchronized (unfinishedJids) {
 908                        unfinishedJids.remove(j);
 909                        if (unfinishedJids.size() == 0 && callback != null) {
 910                            callback.fetched();
 911                        }
 912                    }
 913                });
 914            }
 915        }
 916    }
 917
 918    private ListenableFuture<XmppAxolotlSession> buildSessionFromPEP(final SignalProtocolAddress address) {
 919        return buildSessionFromPEP(address, null);
 920    }
 921
 922    private ListenableFuture<XmppAxolotlSession> buildSessionFromPEP(final SignalProtocolAddress address, OnSessionBuildFromPep callback) {
 923        final SettableFuture<XmppAxolotlSession> sessionSettableFuture = SettableFuture.create();
 924        Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new session for " + address.toString());
 925        if (address.equals(getOwnAxolotlAddress())) {
 926            throw new AssertionError("We should NEVER build a session with ourselves. What happened here?!");
 927        }
 928        final Jid jid = Jid.of(address.getName());
 929        final boolean oneOfOurs = jid.asBareJid().equals(account.getJid().asBareJid());
 930        IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(jid, address.getDeviceId());
 931        mXmppConnectionService.sendIqPacket(account, bundlesPacket, (account, packet) -> {
 932            if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
 933                fetchStatusMap.put(address, FetchStatus.TIMEOUT);
 934                sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. Timeout"));
 935            } else if (packet.getType() == IqPacket.TYPE.RESULT) {
 936                Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received preKey IQ packet, processing...");
 937                final IqParser parser = mXmppConnectionService.getIqParser();
 938                final List<PreKeyBundle> preKeyBundleList = parser.preKeys(packet);
 939                final PreKeyBundle bundle = parser.bundle(packet);
 940                if (preKeyBundleList.isEmpty() || bundle == null) {
 941                    Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "preKey IQ packet invalid: " + packet);
 942                    fetchStatusMap.put(address, FetchStatus.ERROR);
 943                    finishBuildingSessionsFromPEP(address);
 944                    if (callback != null) {
 945                        callback.onSessionBuildFailed();
 946                    }
 947                    sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. IQ Packet Invalid"));
 948                    return;
 949                }
 950                Random random = new Random();
 951                final PreKeyBundle preKey = preKeyBundleList.get(random.nextInt(preKeyBundleList.size()));
 952                if (preKey == null) {
 953                    //should never happen
 954                    fetchStatusMap.put(address, FetchStatus.ERROR);
 955                    finishBuildingSessionsFromPEP(address);
 956                    if (callback != null) {
 957                        callback.onSessionBuildFailed();
 958                    }
 959                    sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. No suitable PreKey found"));
 960                    return;
 961                }
 962
 963                final PreKeyBundle preKeyBundle = new PreKeyBundle(0, address.getDeviceId(),
 964                        preKey.getPreKeyId(), preKey.getPreKey(),
 965                        bundle.getSignedPreKeyId(), bundle.getSignedPreKey(),
 966                        bundle.getSignedPreKeySignature(), bundle.getIdentityKey());
 967
 968                try {
 969                    SessionBuilder builder = new SessionBuilder(axolotlStore, address);
 970                    builder.process(preKeyBundle);
 971                    XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey());
 972                    sessions.put(address, session);
 973                    if (Config.X509_VERIFICATION) {
 974                        sessionSettableFuture.setFuture(verifySessionWithPEP(session)); //TODO; maybe inject callback in here too
 975                    } else {
 976                        FingerprintStatus status = getFingerprintTrust(CryptoHelper.bytesToHex(bundle.getIdentityKey().getPublicKey().serialize()));
 977                        FetchStatus fetchStatus;
 978                        if (status != null && status.isVerified()) {
 979                            fetchStatus = FetchStatus.SUCCESS_VERIFIED;
 980                        } else if (status != null && status.isTrusted()) {
 981                            fetchStatus = FetchStatus.SUCCESS_TRUSTED;
 982                        } else {
 983                            fetchStatus = FetchStatus.SUCCESS;
 984                        }
 985                        fetchStatusMap.put(address, fetchStatus);
 986                        finishBuildingSessionsFromPEP(address);
 987                        if (callback != null) {
 988                            callback.onSessionBuildSuccessful();
 989                        }
 990                        sessionSettableFuture.set(session);
 991                    }
 992                } catch (UntrustedIdentityException | InvalidKeyException e) {
 993                    Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error building session for " + address + ": "
 994                            + e.getClass().getName() + ", " + e.getMessage());
 995                    fetchStatusMap.put(address, FetchStatus.ERROR);
 996                    finishBuildingSessionsFromPEP(address);
 997                    if (oneOfOurs && cleanedOwnDeviceIds.add(address.getDeviceId())) {
 998                        removeFromDeviceAnnouncement(address.getDeviceId());
 999                    }
1000                    if (callback != null) {
1001                        callback.onSessionBuildFailed();
1002                    }
1003                    sessionSettableFuture.setException(new CryptoFailedException(e));
1004                }
1005            } else {
1006                fetchStatusMap.put(address, FetchStatus.ERROR);
1007                Element error = packet.findChild("error");
1008                boolean itemNotFound = error != null && error.hasChild("item-not-found");
1009                Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while building session:" + packet.findChild("error"));
1010                finishBuildingSessionsFromPEP(address);
1011                if (oneOfOurs && itemNotFound && cleanedOwnDeviceIds.add(address.getDeviceId())) {
1012                    removeFromDeviceAnnouncement(address.getDeviceId());
1013                }
1014                if (callback != null) {
1015                    callback.onSessionBuildFailed();
1016                }
1017                sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. IQ Packet Error"));
1018            }
1019        });
1020        return sessionSettableFuture;
1021    }
1022
1023    private void removeFromDeviceAnnouncement(Integer id) {
1024        HashSet<Integer> temp = new HashSet<>(getOwnDeviceIds());
1025        if (temp.remove(id)) {
1026            Log.d(Config.LOGTAG, account.getJid().asBareJid() + " remove own device id " + id + " from announcement. devices left:" + temp);
1027            publishOwnDeviceId(temp);
1028        }
1029    }
1030
1031    public Set<SignalProtocolAddress> findDevicesWithoutSession(final Conversation conversation) {
1032        Set<SignalProtocolAddress> addresses = new HashSet<>();
1033        for (Jid jid : getCryptoTargets(conversation)) {
1034            Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Finding devices without session for " + jid);
1035            final Set<Integer> ids = deviceIds.get(jid);
1036            if (ids != null && !ids.isEmpty()) {
1037                for (Integer foreignId : ids) {
1038                    SignalProtocolAddress address = new SignalProtocolAddress(jid.toString(), foreignId);
1039                    if (sessions.get(address) == null) {
1040                        IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
1041                        if (identityKey != null) {
1042                            Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
1043                            XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
1044                            sessions.put(address, session);
1045                        } else {
1046                            Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + jid + ":" + foreignId);
1047                            if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
1048                                addresses.add(address);
1049                            } else {
1050                                Log.d(Config.LOGTAG, getLogprefix(account) + "skipping over " + address + " because it's broken");
1051                            }
1052                        }
1053                    }
1054                }
1055            } else {
1056                mXmppConnectionService.keyStatusUpdated(FetchStatus.ERROR);
1057                Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Have no target devices in PEP!");
1058            }
1059        }
1060        Set<Integer> ownIds = this.deviceIds.get(account.getJid().asBareJid());
1061        for (Integer ownId : (ownIds != null ? ownIds : new HashSet<Integer>())) {
1062            SignalProtocolAddress address = new SignalProtocolAddress(account.getJid().asBareJid().toString(), ownId);
1063            if (sessions.get(address) == null) {
1064                IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
1065                if (identityKey != null) {
1066                    Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
1067                    XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
1068                    sessions.put(address, session);
1069                } else {
1070                    Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + account.getJid().asBareJid() + ":" + ownId);
1071                    if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
1072                        addresses.add(address);
1073                    } else {
1074                        Log.d(Config.LOGTAG, getLogprefix(account) + "skipping over " + address + " because it's broken");
1075                    }
1076                }
1077            }
1078        }
1079
1080        return addresses;
1081    }
1082
1083    public boolean createSessionsIfNeeded(final Conversation conversation) {
1084        final List<Jid> jidsWithEmptyDeviceList = getCryptoTargets(conversation);
1085        for (Iterator<Jid> iterator = jidsWithEmptyDeviceList.iterator(); iterator.hasNext(); ) {
1086            final Jid jid = iterator.next();
1087            if (!hasEmptyDeviceList(jid)) {
1088                iterator.remove();
1089            }
1090        }
1091        Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": createSessionsIfNeeded() - jids with empty device list: " + jidsWithEmptyDeviceList);
1092        if (jidsWithEmptyDeviceList.size() > 0) {
1093            fetchDeviceIds(jidsWithEmptyDeviceList, () -> createSessionsIfNeededActual(conversation));
1094            return true;
1095        } else {
1096            return createSessionsIfNeededActual(conversation);
1097        }
1098    }
1099
1100    private boolean createSessionsIfNeededActual(final Conversation conversation) {
1101        Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Creating axolotl sessions if needed...");
1102        boolean newSessions = false;
1103        Set<SignalProtocolAddress> addresses = findDevicesWithoutSession(conversation);
1104        for (SignalProtocolAddress address : addresses) {
1105            Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Processing device: " + address.toString());
1106            FetchStatus status = fetchStatusMap.get(address);
1107            if (status == null || status == FetchStatus.TIMEOUT) {
1108                fetchStatusMap.put(address, FetchStatus.PENDING);
1109                this.buildSessionFromPEP(address);
1110                newSessions = true;
1111            } else if (status == FetchStatus.PENDING) {
1112                newSessions = true;
1113            } else {
1114                Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already fetching bundle for " + address.toString());
1115            }
1116        }
1117
1118        return newSessions;
1119    }
1120
1121    public boolean trustedSessionVerified(final Conversation conversation) {
1122        final Set<XmppAxolotlSession> sessions = new HashSet<>();
1123        sessions.addAll(findSessionsForConversation(conversation));
1124        sessions.addAll(findOwnSessions());
1125        boolean verified = false;
1126        for (XmppAxolotlSession session : sessions) {
1127            if (session.getTrust().isTrustedAndActive()) {
1128                if (session.getTrust().getTrust() == FingerprintStatus.Trust.VERIFIED_X509) {
1129                    verified = true;
1130                } else {
1131                    return false;
1132                }
1133            }
1134        }
1135        return verified;
1136    }
1137
1138    public boolean hasPendingKeyFetches(List<Jid> jids) {
1139        SignalProtocolAddress ownAddress = new SignalProtocolAddress(account.getJid().asBareJid().toString(), 0);
1140        if (fetchStatusMap.getAll(ownAddress.getName()).containsValue(FetchStatus.PENDING)) {
1141            return true;
1142        }
1143        synchronized (this.fetchDeviceIdsMap) {
1144            for (Jid jid : jids) {
1145                SignalProtocolAddress foreignAddress = new SignalProtocolAddress(jid.asBareJid().toString(), 0);
1146                if (fetchStatusMap.getAll(foreignAddress.getName()).containsValue(FetchStatus.PENDING) || this.fetchDeviceIdsMap.containsKey(jid)) {
1147                    return true;
1148                }
1149            }
1150        }
1151        return false;
1152    }
1153
1154    @Nullable
1155    private boolean buildHeader(XmppAxolotlMessage axolotlMessage, Conversation c) {
1156        Set<XmppAxolotlSession> remoteSessions = findSessionsForConversation(c);
1157        final boolean acceptEmpty = (c.getMode() == Conversation.MODE_MULTI && c.getMucOptions().getUserCount() == 0) || c.getContact().isSelf();
1158        Collection<XmppAxolotlSession> ownSessions = findOwnSessions();
1159        if (remoteSessions.isEmpty() && !acceptEmpty) {
1160            return false;
1161        }
1162        for (XmppAxolotlSession session : remoteSessions) {
1163            axolotlMessage.addDevice(session);
1164        }
1165        for (XmppAxolotlSession session : ownSessions) {
1166            axolotlMessage.addDevice(session);
1167        }
1168
1169        return true;
1170    }
1171
1172    //this is being used for private muc messages only
1173    private boolean buildHeader(XmppAxolotlMessage axolotlMessage, Jid jid) {
1174        if (jid == null) {
1175            return false;
1176        }
1177        HashSet<XmppAxolotlSession> sessions = new HashSet<>();
1178        sessions.addAll(this.sessions.getAll(getAddressForJid(jid).getName()).values());
1179        if (sessions.isEmpty()) {
1180            return false;
1181        }
1182        sessions.addAll(findOwnSessions());
1183        for (XmppAxolotlSession session : sessions) {
1184            axolotlMessage.addDevice(session);
1185        }
1186        return true;
1187    }
1188
1189    @Nullable
1190    public XmppAxolotlMessage encrypt(Message message) {
1191        final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(account.getJid().asBareJid(), getOwnDeviceId());
1192        final String content;
1193        if (message.hasFileOnRemoteHost()) {
1194            content = message.getFileParams().url;
1195        } else {
1196            content = message.getBody();
1197        }
1198        try {
1199            axolotlMessage.encrypt(content);
1200        } catch (CryptoFailedException e) {
1201            Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to encrypt message: " + e.getMessage());
1202            return null;
1203        }
1204
1205        final boolean success;
1206        if (message.isPrivateMessage()) {
1207            success = buildHeader(axolotlMessage, message.getTrueCounterpart());
1208        } else {
1209            success = buildHeader(axolotlMessage, (Conversation) message.getConversation());
1210        }
1211        return success ? axolotlMessage : null;
1212    }
1213
1214    public void preparePayloadMessage(final Message message, final boolean delay) {
1215        executor.execute(new Runnable() {
1216            @Override
1217            public void run() {
1218                XmppAxolotlMessage axolotlMessage = encrypt(message);
1219                if (axolotlMessage == null) {
1220                    mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED);
1221                    //mXmppConnectionService.updateConversationUi();
1222                } else {
1223                    Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Generated message, caching: " + message.getUuid());
1224                    messageCache.put(message.getUuid(), axolotlMessage);
1225                    mXmppConnectionService.resendMessage(message, delay);
1226                }
1227            }
1228        });
1229    }
1230
1231    private OmemoVerifiedIceUdpTransportInfo encrypt(final IceUdpTransportInfo element, final XmppAxolotlSession session) throws CryptoFailedException {
1232        final OmemoVerifiedIceUdpTransportInfo transportInfo = new OmemoVerifiedIceUdpTransportInfo();
1233        transportInfo.setAttributes(element.getAttributes());
1234        for (final Element child : element.getChildren()) {
1235            if ("fingerprint".equals(child.getName()) && Namespace.JINGLE_APPS_DTLS.equals(child.getNamespace())) {
1236                final Element fingerprint = new Element("fingerprint", Namespace.OMEMO_DTLS_SRTP_VERIFICATION);
1237                fingerprint.setAttribute("setup", child.getAttribute("setup"));
1238                fingerprint.setAttribute("hash", child.getAttribute("hash"));
1239                final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(account.getJid().asBareJid(), getOwnDeviceId());
1240                final String content = child.getContent();
1241                axolotlMessage.encrypt(content);
1242                axolotlMessage.addDevice(session, true);
1243                fingerprint.addChild(axolotlMessage.toElement());
1244                transportInfo.addChild(fingerprint);
1245            } else {
1246                transportInfo.addChild(child);
1247            }
1248        }
1249        return transportInfo;
1250    }
1251
1252
1253    public ListenableFuture<OmemoVerifiedPayload<OmemoVerifiedRtpContentMap>> encrypt(final RtpContentMap rtpContentMap, final Jid jid, final int deviceId) {
1254        return Futures.transformAsync(
1255                getSession(jid, deviceId),
1256                session -> encrypt(rtpContentMap, session),
1257                MoreExecutors.directExecutor()
1258        );
1259    }
1260
1261    private ListenableFuture<OmemoVerifiedPayload<OmemoVerifiedRtpContentMap>> encrypt(final RtpContentMap rtpContentMap, final XmppAxolotlSession session) {
1262        if (Config.REQUIRE_RTP_VERIFICATION) {
1263            requireVerification(session);
1264        }
1265        final ImmutableMap.Builder<String, RtpContentMap.DescriptionTransport> descriptionTransportBuilder = new ImmutableMap.Builder<>();
1266        final OmemoVerification omemoVerification = new OmemoVerification();
1267        omemoVerification.setDeviceId(session.getRemoteAddress().getDeviceId());
1268        omemoVerification.setSessionFingerprint(session.getFingerprint());
1269        for (final Map.Entry<String, RtpContentMap.DescriptionTransport> content : rtpContentMap.contents.entrySet()) {
1270            final RtpContentMap.DescriptionTransport descriptionTransport = content.getValue();
1271            final OmemoVerifiedIceUdpTransportInfo encryptedTransportInfo;
1272            try {
1273                encryptedTransportInfo = encrypt(descriptionTransport.transport, session);
1274            } catch (final CryptoFailedException e) {
1275                return Futures.immediateFailedFuture(e);
1276            }
1277            descriptionTransportBuilder.put(
1278                    content.getKey(),
1279                    new RtpContentMap.DescriptionTransport(descriptionTransport.senders, descriptionTransport.description, encryptedTransportInfo)
1280            );
1281        }
1282        return Futures.immediateFuture(
1283                new OmemoVerifiedPayload<>(
1284                        omemoVerification,
1285                        new OmemoVerifiedRtpContentMap(rtpContentMap.group, descriptionTransportBuilder.build())
1286                ));
1287    }
1288
1289    private ListenableFuture<XmppAxolotlSession> getSession(final Jid jid, final int deviceId) {
1290        final SignalProtocolAddress address = new SignalProtocolAddress(jid.asBareJid().toString(), deviceId);
1291        final XmppAxolotlSession session = sessions.get(address);
1292        if (session == null) {
1293            return buildSessionFromPEP(address);
1294        }
1295        return Futures.immediateFuture(session);
1296    }
1297
1298    public ListenableFuture<OmemoVerifiedPayload<RtpContentMap>> decrypt(OmemoVerifiedRtpContentMap omemoVerifiedRtpContentMap, final Jid from) {
1299        final ImmutableMap.Builder<String, RtpContentMap.DescriptionTransport> descriptionTransportBuilder = new ImmutableMap.Builder<>();
1300        final OmemoVerification omemoVerification = new OmemoVerification();
1301        final ImmutableList.Builder<ListenableFuture<XmppAxolotlSession>> pepVerificationFutures = new ImmutableList.Builder<>();
1302        for (final Map.Entry<String, RtpContentMap.DescriptionTransport> content : omemoVerifiedRtpContentMap.contents.entrySet()) {
1303            final RtpContentMap.DescriptionTransport descriptionTransport = content.getValue();
1304            final OmemoVerifiedPayload<IceUdpTransportInfo> decryptedTransport;
1305            try {
1306                decryptedTransport = decrypt((OmemoVerifiedIceUdpTransportInfo) descriptionTransport.transport, from, pepVerificationFutures);
1307            } catch (CryptoFailedException e) {
1308                return Futures.immediateFailedFuture(e);
1309            }
1310            omemoVerification.setOrEnsureEqual(decryptedTransport);
1311            descriptionTransportBuilder.put(
1312                    content.getKey(),
1313                    new RtpContentMap.DescriptionTransport(descriptionTransport.senders, descriptionTransport.description, decryptedTransport.payload)
1314            );
1315        }
1316        processPostponed();
1317        final ImmutableList<ListenableFuture<XmppAxolotlSession>> sessionFutures = pepVerificationFutures.build();
1318        return Futures.transform(
1319                Futures.allAsList(sessionFutures),
1320                sessions -> {
1321                    if (Config.REQUIRE_RTP_VERIFICATION) {
1322                        for (XmppAxolotlSession session : sessions) {
1323                            requireVerification(session);
1324                        }
1325                    }
1326                    return new OmemoVerifiedPayload<>(
1327                            omemoVerification,
1328                            new RtpContentMap(omemoVerifiedRtpContentMap.group, descriptionTransportBuilder.build())
1329                    );
1330
1331                },
1332                MoreExecutors.directExecutor()
1333        );
1334    }
1335
1336    private OmemoVerifiedPayload<IceUdpTransportInfo> decrypt(final OmemoVerifiedIceUdpTransportInfo verifiedIceUdpTransportInfo, final Jid from, ImmutableList.Builder<ListenableFuture<XmppAxolotlSession>> pepVerificationFutures) throws CryptoFailedException {
1337        final IceUdpTransportInfo transportInfo = new IceUdpTransportInfo();
1338        transportInfo.setAttributes(verifiedIceUdpTransportInfo.getAttributes());
1339        final OmemoVerification omemoVerification = new OmemoVerification();
1340        for (final Element child : verifiedIceUdpTransportInfo.getChildren()) {
1341            if ("fingerprint".equals(child.getName()) && Namespace.OMEMO_DTLS_SRTP_VERIFICATION.equals(child.getNamespace())) {
1342                final Element fingerprint = new Element("fingerprint", Namespace.JINGLE_APPS_DTLS);
1343                fingerprint.setAttribute("setup", child.getAttribute("setup"));
1344                fingerprint.setAttribute("hash", child.getAttribute("hash"));
1345                final Element encrypted = child.findChildEnsureSingle(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX);
1346                final XmppAxolotlMessage xmppAxolotlMessage = XmppAxolotlMessage.fromElement(encrypted, from.asBareJid());
1347                final XmppAxolotlSession session = getReceivingSession(xmppAxolotlMessage);
1348                final XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintext = xmppAxolotlMessage.decrypt(session, getOwnDeviceId());
1349                final Integer preKeyId = session.getPreKeyIdAndReset();
1350                if (preKeyId != null) {
1351                    postponedSessions.add(session);
1352                }
1353                if (session.isFresh()) {
1354                    pepVerificationFutures.add(putFreshSession(session));
1355                } else if (Config.REQUIRE_RTP_VERIFICATION) {
1356                    pepVerificationFutures.add(Futures.immediateFuture(session));
1357                }
1358                fingerprint.setContent(plaintext.getPlaintext());
1359                omemoVerification.setDeviceId(session.getRemoteAddress().getDeviceId());
1360                omemoVerification.setSessionFingerprint(plaintext.getFingerprint());
1361                transportInfo.addChild(fingerprint);
1362            } else {
1363                transportInfo.addChild(child);
1364            }
1365        }
1366        return new OmemoVerifiedPayload<>(omemoVerification, transportInfo);
1367    }
1368
1369    private static void requireVerification(final XmppAxolotlSession session) {
1370        if (session.getTrust().isVerified()) {
1371            return;
1372        }
1373        throw new NotVerifiedException(String.format(
1374                "session with %s was not verified",
1375                session.getFingerprint()
1376        ));
1377    }
1378
1379    public void prepareKeyTransportMessage(final Conversation conversation, final OnMessageCreatedCallback onMessageCreatedCallback) {
1380        executor.execute(new Runnable() {
1381            @Override
1382            public void run() {
1383                final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(account.getJid().asBareJid(), getOwnDeviceId());
1384                if (buildHeader(axolotlMessage, conversation)) {
1385                    onMessageCreatedCallback.run(axolotlMessage);
1386                } else {
1387                    onMessageCreatedCallback.run(null);
1388                }
1389            }
1390        });
1391    }
1392
1393    public XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message) {
1394        XmppAxolotlMessage axolotlMessage = messageCache.get(message.getUuid());
1395        if (axolotlMessage != null) {
1396            Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache hit: " + message.getUuid());
1397            messageCache.remove(message.getUuid());
1398        } else {
1399            Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache miss: " + message.getUuid());
1400        }
1401        return axolotlMessage;
1402    }
1403
1404    private XmppAxolotlSession recreateUncachedSession(SignalProtocolAddress address) {
1405        IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
1406        return (identityKey != null)
1407                ? new XmppAxolotlSession(account, axolotlStore, address, identityKey)
1408                : null;
1409    }
1410
1411    private XmppAxolotlSession getReceivingSession(XmppAxolotlMessage message) {
1412        SignalProtocolAddress senderAddress = new SignalProtocolAddress(message.getFrom().toString(), message.getSenderDeviceId());
1413        return getReceivingSession(senderAddress);
1414
1415    }
1416
1417    private XmppAxolotlSession getReceivingSession(SignalProtocolAddress senderAddress) {
1418        XmppAxolotlSession session = sessions.get(senderAddress);
1419        if (session == null) {
1420            session = recreateUncachedSession(senderAddress);
1421            if (session == null) {
1422                session = new XmppAxolotlSession(account, axolotlStore, senderAddress);
1423            }
1424        }
1425        return session;
1426    }
1427
1428    public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message, boolean postponePreKeyMessageHandling) throws NotEncryptedForThisDeviceException, BrokenSessionException, OutdatedSenderException {
1429        XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null;
1430
1431        XmppAxolotlSession session = getReceivingSession(message);
1432        int ownDeviceId = getOwnDeviceId();
1433        try {
1434            plaintextMessage = message.decrypt(session, ownDeviceId);
1435            Integer preKeyId = session.getPreKeyIdAndReset();
1436            if (preKeyId != null) {
1437                postPreKeyMessageHandling(session, postponePreKeyMessageHandling);
1438            }
1439        } catch (NotEncryptedForThisDeviceException e) {
1440            if (account.getJid().asBareJid().equals(message.getFrom().asBareJid()) && message.getSenderDeviceId() == ownDeviceId) {
1441                Log.w(Config.LOGTAG, getLogprefix(account) + "Reflected omemo message received");
1442            } else {
1443                throw e;
1444            }
1445        } catch (final BrokenSessionException e) {
1446            throw e;
1447        } catch (final OutdatedSenderException e) {
1448            Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": " + e.getMessage());
1449            throw e;
1450        } catch (CryptoFailedException e) {
1451            Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message from " + message.getFrom(), e);
1452        }
1453
1454        if (session.isFresh() && plaintextMessage != null) {
1455            putFreshSession(session);
1456        }
1457
1458        return plaintextMessage;
1459    }
1460
1461    public void reportBrokenSessionException(BrokenSessionException e, boolean postpone) {
1462        Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": broken session with " + e.getSignalProtocolAddress().toString() + " detected", e);
1463        if (postpone) {
1464            postponedHealing.add(e.getSignalProtocolAddress());
1465        } else {
1466            notifyRequiresHealing(e.getSignalProtocolAddress());
1467        }
1468    }
1469
1470    private void notifyRequiresHealing(final SignalProtocolAddress signalProtocolAddress) {
1471        if (healingAttempts.add(signalProtocolAddress)) {
1472            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": attempt to heal " + signalProtocolAddress);
1473            buildSessionFromPEP(signalProtocolAddress, new OnSessionBuildFromPep() {
1474                @Override
1475                public void onSessionBuildSuccessful() {
1476                    Log.d(Config.LOGTAG, "successfully build new session from pep after detecting broken session");
1477                    completeSession(getReceivingSession(signalProtocolAddress));
1478                }
1479
1480                @Override
1481                public void onSessionBuildFailed() {
1482                    Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to build new session from pep after detecting broken session");
1483                }
1484            });
1485        } else {
1486            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": do not attempt to heal " + signalProtocolAddress + " again");
1487        }
1488    }
1489
1490    private void postPreKeyMessageHandling(final XmppAxolotlSession session, final boolean postpone) {
1491        if (postpone) {
1492            postponedSessions.add(session);
1493        } else {
1494            if (axolotlStore.flushPreKeys()) {
1495                publishBundlesIfNeeded(false, false);
1496            } else {
1497                Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": nothing to flush. Not republishing key");
1498            }
1499            if (trustedOrPreviouslyResponded(session) && Config.AUTOMATICALLY_COMPLETE_SESSIONS) {
1500                completeSession(session);
1501            }
1502        }
1503    }
1504
1505    public void processPostponed() {
1506        if (postponedSessions.size() > 0) {
1507            if (axolotlStore.flushPreKeys()) {
1508                publishBundlesIfNeeded(false, false);
1509            }
1510        }
1511        final Iterator<XmppAxolotlSession> iterator = postponedSessions.iterator();
1512        while (iterator.hasNext()) {
1513            final XmppAxolotlSession session = iterator.next();
1514            if (trustedOrPreviouslyResponded(session) && Config.AUTOMATICALLY_COMPLETE_SESSIONS) {
1515                completeSession(session);
1516            }
1517            iterator.remove();
1518        }
1519        final Iterator<SignalProtocolAddress> postponedHealingAttemptsIterator = postponedHealing.iterator();
1520        while (postponedHealingAttemptsIterator.hasNext()) {
1521            notifyRequiresHealing(postponedHealingAttemptsIterator.next());
1522            postponedHealingAttemptsIterator.remove();
1523        }
1524    }
1525
1526    private boolean trustedOrPreviouslyResponded(XmppAxolotlSession session) {
1527        try {
1528            return trustedOrPreviouslyResponded(Jid.of(session.getRemoteAddress().getName()));
1529        } catch (IllegalArgumentException e) {
1530            return false;
1531        }
1532    }
1533
1534    public boolean trustedOrPreviouslyResponded(Jid jid) {
1535        final Contact contact = account.getRoster().getContact(jid);
1536        if (contact.showInRoster() || contact.isSelf()) {
1537            return true;
1538        }
1539        final Conversation conversation = mXmppConnectionService.find(account, jid);
1540        return conversation != null && conversation.sentMessagesCount() > 0;
1541    }
1542
1543    private void completeSession(XmppAxolotlSession session) {
1544        final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(account.getJid().asBareJid(), getOwnDeviceId());
1545        axolotlMessage.addDevice(session, true);
1546        try {
1547            final Jid jid = Jid.of(session.getRemoteAddress().getName());
1548            MessagePacket packet = mXmppConnectionService.getMessageGenerator().generateKeyTransportMessage(jid, axolotlMessage);
1549            mXmppConnectionService.sendMessagePacket(account, packet);
1550        } catch (IllegalArgumentException e) {
1551            throw new Error("Remote addresses are created from jid and should convert back to jid", e);
1552        }
1553    }
1554
1555    public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message, final boolean postponePreKeyMessageHandling) {
1556        final XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage;
1557        final XmppAxolotlSession session = getReceivingSession(message);
1558        try {
1559            keyTransportMessage = message.getParameters(session, getOwnDeviceId());
1560            Integer preKeyId = session.getPreKeyIdAndReset();
1561            if (preKeyId != null) {
1562                postPreKeyMessageHandling(session, postponePreKeyMessageHandling);
1563            }
1564        } catch (CryptoFailedException e) {
1565            Log.d(Config.LOGTAG, "could not decrypt keyTransport message " + e.getMessage());
1566            return null;
1567        }
1568
1569        if (session.isFresh() && keyTransportMessage != null) {
1570            putFreshSession(session);
1571        }
1572
1573        return keyTransportMessage;
1574    }
1575
1576    private ListenableFuture<XmppAxolotlSession> putFreshSession(XmppAxolotlSession session) {
1577        sessions.put(session);
1578        if (Config.X509_VERIFICATION) {
1579            if (session.getIdentityKey() != null) {
1580                return verifySessionWithPEP(session);
1581            } else {
1582                Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": identity key was empty after reloading for x509 verification");
1583            }
1584        }
1585        return Futures.immediateFuture(session);
1586    }
1587
1588    public enum FetchStatus {
1589        PENDING,
1590        SUCCESS,
1591        SUCCESS_VERIFIED,
1592        TIMEOUT,
1593        SUCCESS_TRUSTED,
1594        ERROR
1595    }
1596
1597    public interface OnDeviceIdsFetched {
1598        void fetched(Jid jid, Set<Integer> deviceIds);
1599    }
1600
1601
1602    public interface OnMultipleDeviceIdFetched {
1603        void fetched();
1604    }
1605
1606    interface OnSessionBuildFromPep {
1607        void onSessionBuildSuccessful();
1608
1609        void onSessionBuildFailed();
1610    }
1611
1612    private static class AxolotlAddressMap<T> {
1613        protected final Object MAP_LOCK = new Object();
1614        protected Map<String, Map<Integer, T>> map;
1615
1616        public AxolotlAddressMap() {
1617            this.map = new HashMap<>();
1618        }
1619
1620        public void put(SignalProtocolAddress address, T value) {
1621            synchronized (MAP_LOCK) {
1622                Map<Integer, T> devices = map.get(address.getName());
1623                if (devices == null) {
1624                    devices = new HashMap<>();
1625                    map.put(address.getName(), devices);
1626                }
1627                devices.put(address.getDeviceId(), value);
1628            }
1629        }
1630
1631        public T get(SignalProtocolAddress address) {
1632            synchronized (MAP_LOCK) {
1633                Map<Integer, T> devices = map.get(address.getName());
1634                if (devices == null) {
1635                    return null;
1636                }
1637                return devices.get(address.getDeviceId());
1638            }
1639        }
1640
1641        public Map<Integer, T> getAll(String name) {
1642            synchronized (MAP_LOCK) {
1643                Map<Integer, T> devices = map.get(name);
1644                if (devices == null) {
1645                    return new HashMap<>();
1646                }
1647                return devices;
1648            }
1649        }
1650
1651        public boolean hasAny(SignalProtocolAddress address) {
1652            synchronized (MAP_LOCK) {
1653                Map<Integer, T> devices = map.get(address.getName());
1654                return devices != null && !devices.isEmpty();
1655            }
1656        }
1657
1658        public void clear() {
1659            map.clear();
1660        }
1661
1662    }
1663
1664    private static class SessionMap extends AxolotlAddressMap<XmppAxolotlSession> {
1665        private final XmppConnectionService xmppConnectionService;
1666        private final Account account;
1667
1668        public SessionMap(XmppConnectionService service, SQLiteAxolotlStore store, Account account) {
1669            super();
1670            this.xmppConnectionService = service;
1671            this.account = account;
1672            this.fillMap(store);
1673        }
1674
1675        public Set<Jid> findCounterpartsForSourceId(Integer sid) {
1676            Set<Jid> candidates = new HashSet<>();
1677            synchronized (MAP_LOCK) {
1678                for (Map.Entry<String, Map<Integer, XmppAxolotlSession>> entry : map.entrySet()) {
1679                    String key = entry.getKey();
1680                    if (entry.getValue().containsKey(sid)) {
1681                        candidates.add(Jid.of(key));
1682                    }
1683                }
1684            }
1685            return candidates;
1686        }
1687
1688        private void putDevicesForJid(String bareJid, List<Integer> deviceIds, SQLiteAxolotlStore store) {
1689            for (Integer deviceId : deviceIds) {
1690                SignalProtocolAddress axolotlAddress = new SignalProtocolAddress(bareJid, deviceId);
1691                IdentityKey identityKey = store.loadSession(axolotlAddress).getSessionState().getRemoteIdentityKey();
1692                if (Config.X509_VERIFICATION) {
1693                    X509Certificate certificate = store.getFingerprintCertificate(CryptoHelper.bytesToHex(identityKey.getPublicKey().serialize()));
1694                    if (certificate != null) {
1695                        Bundle information = CryptoHelper.extractCertificateInformation(certificate);
1696                        try {
1697                            final String cn = information.getString("subject_cn");
1698                            final Jid jid = Jid.of(bareJid);
1699                            Log.d(Config.LOGTAG, "setting common name for " + jid + " to " + cn);
1700                            account.getRoster().getContact(jid).setCommonName(cn);
1701                        } catch (final IllegalArgumentException ignored) {
1702                            //ignored
1703                        }
1704                    }
1705                }
1706                this.put(axolotlAddress, new XmppAxolotlSession(account, store, axolotlAddress, identityKey));
1707            }
1708        }
1709
1710        private void fillMap(SQLiteAxolotlStore store) {
1711            List<Integer> deviceIds = store.getSubDeviceSessions(account.getJid().asBareJid().toString());
1712            putDevicesForJid(account.getJid().asBareJid().toString(), deviceIds, store);
1713            for (String address : store.getKnownAddresses()) {
1714                deviceIds = store.getSubDeviceSessions(address);
1715                putDevicesForJid(address, deviceIds, store);
1716            }
1717        }
1718
1719        @Override
1720        public void put(SignalProtocolAddress address, XmppAxolotlSession value) {
1721            super.put(address, value);
1722            value.setNotFresh();
1723        }
1724
1725        public void put(XmppAxolotlSession session) {
1726            this.put(session.getRemoteAddress(), session);
1727        }
1728    }
1729
1730    private static class FetchStatusMap extends AxolotlAddressMap<FetchStatus> {
1731
1732        public void clearErrorFor(Jid jid) {
1733            synchronized (MAP_LOCK) {
1734                Map<Integer, FetchStatus> devices = this.map.get(jid.asBareJid().toString());
1735                if (devices == null) {
1736                    return;
1737                }
1738                for (Map.Entry<Integer, FetchStatus> entry : devices.entrySet()) {
1739                    if (entry.getValue() == FetchStatus.ERROR) {
1740                        Log.d(Config.LOGTAG, "resetting error for " + jid.asBareJid() + "(" + entry.getKey() + ")");
1741                        entry.setValue(FetchStatus.TIMEOUT);
1742                    }
1743                }
1744            }
1745        }
1746    }
1747
1748    public static class OmemoVerifiedPayload<T> {
1749        private final int deviceId;
1750        private final String fingerprint;
1751        private final T payload;
1752
1753        private OmemoVerifiedPayload(OmemoVerification omemoVerification, T payload) {
1754            this.deviceId = omemoVerification.getDeviceId();
1755            this.fingerprint = omemoVerification.getFingerprint();
1756            this.payload = payload;
1757        }
1758
1759        public int getDeviceId() {
1760            return deviceId;
1761        }
1762
1763        public String getFingerprint() {
1764            return fingerprint;
1765        }
1766
1767        public T getPayload() {
1768            return payload;
1769        }
1770    }
1771
1772    public static class NotVerifiedException extends SecurityException {
1773
1774        public NotVerifiedException(String message) {
1775            super(message);
1776        }
1777
1778    }
1779}