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(String fingerprint, FingerprintStatus status) {
 740        axolotlStore.setFingerprintStatus(fingerprint, status);
 741    }
 742
 743    private ListenableFuture<XmppAxolotlSession> verifySessionWithPEP(final XmppAxolotlSession session) {
 744        Log.d(Config.LOGTAG, "trying to verify fresh session (" + session.getRemoteAddress().getName() + ") with pep");
 745        final SignalProtocolAddress address = session.getRemoteAddress();
 746        final IdentityKey identityKey = session.getIdentityKey();
 747        final Jid jid;
 748        try {
 749            jid = Jid.of(address.getName());
 750        } catch (final IllegalArgumentException e) {
 751            fetchStatusMap.put(address, FetchStatus.SUCCESS);
 752            finishBuildingSessionsFromPEP(address);
 753            return Futures.immediateFuture(session);
 754        }
 755        final SettableFuture<XmppAxolotlSession> future = SettableFuture.create();
 756        final IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(jid, address.getDeviceId());
 757        mXmppConnectionService.sendIqPacket(account, packet, (account, response) -> {
 758            Pair<X509Certificate[], byte[]> verification = mXmppConnectionService.getIqParser().verification(response);
 759            if (verification != null) {
 760                try {
 761                    Signature verifier = Signature.getInstance("sha256WithRSA");
 762                    verifier.initVerify(verification.first[0]);
 763                    verifier.update(identityKey.serialize());
 764                    if (verifier.verify(verification.second)) {
 765                        try {
 766                            mXmppConnectionService.getMemorizingTrustManager().getNonInteractive().checkClientTrusted(verification.first, "RSA");
 767                            String fingerprint = session.getFingerprint();
 768                            Log.d(Config.LOGTAG, "verified session with x.509 signature. fingerprint was: " + fingerprint);
 769                            setFingerprintTrust(fingerprint, FingerprintStatus.createActiveVerified(true));
 770                            axolotlStore.setFingerprintCertificate(fingerprint, verification.first[0]);
 771                            fetchStatusMap.put(address, FetchStatus.SUCCESS_VERIFIED);
 772                            Bundle information = CryptoHelper.extractCertificateInformation(verification.first[0]);
 773                            try {
 774                                final String cn = information.getString("subject_cn");
 775                                final Jid jid1 = Jid.of(address.getName());
 776                                Log.d(Config.LOGTAG, "setting common name for " + jid1 + " to " + cn);
 777                                account.getRoster().getContact(jid1).setCommonName(cn);
 778                            } catch (final IllegalArgumentException ignored) {
 779                                //ignored
 780                            }
 781                            finishBuildingSessionsFromPEP(address);
 782                            future.set(session);
 783                            return;
 784                        } catch (Exception e) {
 785                            Log.d(Config.LOGTAG, "could not verify certificate");
 786                        }
 787                    }
 788                } catch (Exception e) {
 789                    Log.d(Config.LOGTAG, "error during verification " + e.getMessage());
 790                }
 791            } else {
 792                Log.d(Config.LOGTAG, "no verification found");
 793            }
 794            fetchStatusMap.put(address, FetchStatus.SUCCESS);
 795            finishBuildingSessionsFromPEP(address);
 796            future.set(session);
 797        });
 798        return future;
 799    }
 800
 801    private void finishBuildingSessionsFromPEP(final SignalProtocolAddress address) {
 802        SignalProtocolAddress ownAddress = new SignalProtocolAddress(account.getJid().asBareJid().toString(), 0);
 803        Map<Integer, FetchStatus> own = fetchStatusMap.getAll(ownAddress.getName());
 804        Map<Integer, FetchStatus> remote = fetchStatusMap.getAll(address.getName());
 805        if (!own.containsValue(FetchStatus.PENDING) && !remote.containsValue(FetchStatus.PENDING)) {
 806            FetchStatus report = null;
 807            if (own.containsValue(FetchStatus.SUCCESS) || remote.containsValue(FetchStatus.SUCCESS)) {
 808                report = FetchStatus.SUCCESS;
 809            } else if (own.containsValue(FetchStatus.SUCCESS_VERIFIED) || remote.containsValue(FetchStatus.SUCCESS_VERIFIED)) {
 810                report = FetchStatus.SUCCESS_VERIFIED;
 811            } else if (own.containsValue(FetchStatus.SUCCESS_TRUSTED) || remote.containsValue(FetchStatus.SUCCESS_TRUSTED)) {
 812                report = FetchStatus.SUCCESS_TRUSTED;
 813            } else if (own.containsValue(FetchStatus.ERROR) || remote.containsValue(FetchStatus.ERROR)) {
 814                report = FetchStatus.ERROR;
 815            }
 816            mXmppConnectionService.keyStatusUpdated(report);
 817        }
 818        if (Config.REMOVE_BROKEN_DEVICES) {
 819            Set<Integer> ownDeviceIds = new HashSet<>(getOwnDeviceIds());
 820            boolean publish = false;
 821            for (Map.Entry<Integer, FetchStatus> entry : own.entrySet()) {
 822                int id = entry.getKey();
 823                if (entry.getValue() == FetchStatus.ERROR && PREVIOUSLY_REMOVED_FROM_ANNOUNCEMENT.add(id) && ownDeviceIds.remove(id)) {
 824                    publish = true;
 825                    Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": error fetching own device with id " + id + ". removing from announcement");
 826                }
 827            }
 828            if (publish) {
 829                publishOwnDeviceId(ownDeviceIds);
 830            }
 831        }
 832    }
 833
 834    public boolean hasEmptyDeviceList(Jid jid) {
 835        return !hasAny(jid) && (!deviceIds.containsKey(jid) || deviceIds.get(jid).isEmpty());
 836    }
 837
 838    public void fetchDeviceIds(final Jid jid) {
 839        fetchDeviceIds(jid, null);
 840    }
 841
 842    private void fetchDeviceIds(final Jid jid, OnDeviceIdsFetched callback) {
 843        IqPacket packet;
 844        synchronized (this.fetchDeviceIdsMap) {
 845            List<OnDeviceIdsFetched> callbacks = this.fetchDeviceIdsMap.get(jid);
 846            if (callbacks != null) {
 847                if (callback != null) {
 848                    callbacks.add(callback);
 849                }
 850                Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": fetching device ids for " + jid + " already running. adding callback");
 851                packet = null;
 852            } else {
 853                callbacks = new ArrayList<>();
 854                if (callback != null) {
 855                    callbacks.add(callback);
 856                }
 857                this.fetchDeviceIdsMap.put(jid, callbacks);
 858                Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": fetching device ids for " + jid);
 859                packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(jid);
 860            }
 861        }
 862        if (packet != null) {
 863            mXmppConnectionService.sendIqPacket(account, packet, (account, response) -> {
 864                if (response.getType() == IqPacket.TYPE.RESULT) {
 865                    fetchDeviceListStatus.put(jid, true);
 866                    Element item = mXmppConnectionService.getIqParser().getItem(response);
 867                    Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
 868                    registerDevices(jid, deviceIds);
 869                    final List<OnDeviceIdsFetched> callbacks;
 870                    synchronized (fetchDeviceIdsMap) {
 871                        callbacks = fetchDeviceIdsMap.remove(jid);
 872                    }
 873                    if (callbacks != null) {
 874                        for (OnDeviceIdsFetched c : callbacks) {
 875                            c.fetched(jid, deviceIds);
 876                        }
 877                    }
 878                } else {
 879                    if (response.getType() == IqPacket.TYPE.TIMEOUT) {
 880                        fetchDeviceListStatus.remove(jid);
 881                    } else {
 882                        fetchDeviceListStatus.put(jid, false);
 883                    }
 884                    final List<OnDeviceIdsFetched> callbacks;
 885                    synchronized (fetchDeviceIdsMap) {
 886                        callbacks = fetchDeviceIdsMap.remove(jid);
 887                    }
 888                    if (callbacks != null) {
 889                        for (OnDeviceIdsFetched c : callbacks) {
 890                            c.fetched(jid, null);
 891                        }
 892                    }
 893                }
 894            });
 895        }
 896    }
 897
 898    private void fetchDeviceIds(List<Jid> jids, final OnMultipleDeviceIdFetched callback) {
 899        final ArrayList<Jid> unfinishedJids = new ArrayList<>(jids);
 900        synchronized (unfinishedJids) {
 901            for (Jid jid : unfinishedJids) {
 902                fetchDeviceIds(jid, (j, deviceIds) -> {
 903                    synchronized (unfinishedJids) {
 904                        unfinishedJids.remove(j);
 905                        if (unfinishedJids.size() == 0 && callback != null) {
 906                            callback.fetched();
 907                        }
 908                    }
 909                });
 910            }
 911        }
 912    }
 913
 914    private ListenableFuture<XmppAxolotlSession> buildSessionFromPEP(final SignalProtocolAddress address) {
 915        return buildSessionFromPEP(address, null);
 916    }
 917
 918    private ListenableFuture<XmppAxolotlSession> buildSessionFromPEP(final SignalProtocolAddress address, OnSessionBuildFromPep callback) {
 919        final SettableFuture<XmppAxolotlSession> sessionSettableFuture = SettableFuture.create();
 920        Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new session for " + address.toString());
 921        if (address.equals(getOwnAxolotlAddress())) {
 922            throw new AssertionError("We should NEVER build a session with ourselves. What happened here?!");
 923        }
 924        final Jid jid = Jid.of(address.getName());
 925        final boolean oneOfOurs = jid.asBareJid().equals(account.getJid().asBareJid());
 926        IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(jid, address.getDeviceId());
 927        mXmppConnectionService.sendIqPacket(account, bundlesPacket, (account, packet) -> {
 928            if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
 929                fetchStatusMap.put(address, FetchStatus.TIMEOUT);
 930                sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. Timeout"));
 931            } else if (packet.getType() == IqPacket.TYPE.RESULT) {
 932                Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received preKey IQ packet, processing...");
 933                final IqParser parser = mXmppConnectionService.getIqParser();
 934                final List<PreKeyBundle> preKeyBundleList = parser.preKeys(packet);
 935                final PreKeyBundle bundle = parser.bundle(packet);
 936                if (preKeyBundleList.isEmpty() || bundle == null) {
 937                    Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "preKey IQ packet invalid: " + packet);
 938                    fetchStatusMap.put(address, FetchStatus.ERROR);
 939                    finishBuildingSessionsFromPEP(address);
 940                    if (callback != null) {
 941                        callback.onSessionBuildFailed();
 942                    }
 943                    sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. IQ Packet Invalid"));
 944                    return;
 945                }
 946                Random random = new Random();
 947                final PreKeyBundle preKey = preKeyBundleList.get(random.nextInt(preKeyBundleList.size()));
 948                if (preKey == null) {
 949                    //should never happen
 950                    fetchStatusMap.put(address, FetchStatus.ERROR);
 951                    finishBuildingSessionsFromPEP(address);
 952                    if (callback != null) {
 953                        callback.onSessionBuildFailed();
 954                    }
 955                    sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. No suitable PreKey found"));
 956                    return;
 957                }
 958
 959                final PreKeyBundle preKeyBundle = new PreKeyBundle(0, address.getDeviceId(),
 960                        preKey.getPreKeyId(), preKey.getPreKey(),
 961                        bundle.getSignedPreKeyId(), bundle.getSignedPreKey(),
 962                        bundle.getSignedPreKeySignature(), bundle.getIdentityKey());
 963
 964                try {
 965                    SessionBuilder builder = new SessionBuilder(axolotlStore, address);
 966                    builder.process(preKeyBundle);
 967                    XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey());
 968                    sessions.put(address, session);
 969                    if (Config.X509_VERIFICATION) {
 970                        sessionSettableFuture.setFuture(verifySessionWithPEP(session)); //TODO; maybe inject callback in here too
 971                    } else {
 972                        FingerprintStatus status = getFingerprintTrust(CryptoHelper.bytesToHex(bundle.getIdentityKey().getPublicKey().serialize()));
 973                        FetchStatus fetchStatus;
 974                        if (status != null && status.isVerified()) {
 975                            fetchStatus = FetchStatus.SUCCESS_VERIFIED;
 976                        } else if (status != null && status.isTrusted()) {
 977                            fetchStatus = FetchStatus.SUCCESS_TRUSTED;
 978                        } else {
 979                            fetchStatus = FetchStatus.SUCCESS;
 980                        }
 981                        fetchStatusMap.put(address, fetchStatus);
 982                        finishBuildingSessionsFromPEP(address);
 983                        if (callback != null) {
 984                            callback.onSessionBuildSuccessful();
 985                        }
 986                        sessionSettableFuture.set(session);
 987                    }
 988                } catch (UntrustedIdentityException | InvalidKeyException e) {
 989                    Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error building session for " + address + ": "
 990                            + e.getClass().getName() + ", " + e.getMessage());
 991                    fetchStatusMap.put(address, FetchStatus.ERROR);
 992                    finishBuildingSessionsFromPEP(address);
 993                    if (oneOfOurs && cleanedOwnDeviceIds.add(address.getDeviceId())) {
 994                        removeFromDeviceAnnouncement(address.getDeviceId());
 995                    }
 996                    if (callback != null) {
 997                        callback.onSessionBuildFailed();
 998                    }
 999                    sessionSettableFuture.setException(new CryptoFailedException(e));
1000                }
1001            } else {
1002                fetchStatusMap.put(address, FetchStatus.ERROR);
1003                Element error = packet.findChild("error");
1004                boolean itemNotFound = error != null && error.hasChild("item-not-found");
1005                Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while building session:" + packet.findChild("error"));
1006                finishBuildingSessionsFromPEP(address);
1007                if (oneOfOurs && itemNotFound && cleanedOwnDeviceIds.add(address.getDeviceId())) {
1008                    removeFromDeviceAnnouncement(address.getDeviceId());
1009                }
1010                if (callback != null) {
1011                    callback.onSessionBuildFailed();
1012                }
1013                sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. IQ Packet Error"));
1014            }
1015        });
1016        return sessionSettableFuture;
1017    }
1018
1019    private void removeFromDeviceAnnouncement(Integer id) {
1020        HashSet<Integer> temp = new HashSet<>(getOwnDeviceIds());
1021        if (temp.remove(id)) {
1022            Log.d(Config.LOGTAG, account.getJid().asBareJid() + " remove own device id " + id + " from announcement. devices left:" + temp);
1023            publishOwnDeviceId(temp);
1024        }
1025    }
1026
1027    public Set<SignalProtocolAddress> findDevicesWithoutSession(final Conversation conversation) {
1028        Set<SignalProtocolAddress> addresses = new HashSet<>();
1029        for (Jid jid : getCryptoTargets(conversation)) {
1030            Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Finding devices without session for " + jid);
1031            final Set<Integer> ids = deviceIds.get(jid);
1032            if (ids != null && !ids.isEmpty()) {
1033                for (Integer foreignId : ids) {
1034                    SignalProtocolAddress address = new SignalProtocolAddress(jid.toString(), foreignId);
1035                    if (sessions.get(address) == null) {
1036                        IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
1037                        if (identityKey != null) {
1038                            Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
1039                            XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
1040                            sessions.put(address, session);
1041                        } else {
1042                            Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + jid + ":" + foreignId);
1043                            if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
1044                                addresses.add(address);
1045                            } else {
1046                                Log.d(Config.LOGTAG, getLogprefix(account) + "skipping over " + address + " because it's broken");
1047                            }
1048                        }
1049                    }
1050                }
1051            } else {
1052                mXmppConnectionService.keyStatusUpdated(FetchStatus.ERROR);
1053                Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Have no target devices in PEP!");
1054            }
1055        }
1056        Set<Integer> ownIds = this.deviceIds.get(account.getJid().asBareJid());
1057        for (Integer ownId : (ownIds != null ? ownIds : new HashSet<Integer>())) {
1058            SignalProtocolAddress address = new SignalProtocolAddress(account.getJid().asBareJid().toString(), ownId);
1059            if (sessions.get(address) == null) {
1060                IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
1061                if (identityKey != null) {
1062                    Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
1063                    XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
1064                    sessions.put(address, session);
1065                } else {
1066                    Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + account.getJid().asBareJid() + ":" + ownId);
1067                    if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
1068                        addresses.add(address);
1069                    } else {
1070                        Log.d(Config.LOGTAG, getLogprefix(account) + "skipping over " + address + " because it's broken");
1071                    }
1072                }
1073            }
1074        }
1075
1076        return addresses;
1077    }
1078
1079    public boolean createSessionsIfNeeded(final Conversation conversation) {
1080        final List<Jid> jidsWithEmptyDeviceList = getCryptoTargets(conversation);
1081        for (Iterator<Jid> iterator = jidsWithEmptyDeviceList.iterator(); iterator.hasNext(); ) {
1082            final Jid jid = iterator.next();
1083            if (!hasEmptyDeviceList(jid)) {
1084                iterator.remove();
1085            }
1086        }
1087        Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": createSessionsIfNeeded() - jids with empty device list: " + jidsWithEmptyDeviceList);
1088        if (jidsWithEmptyDeviceList.size() > 0) {
1089            fetchDeviceIds(jidsWithEmptyDeviceList, () -> createSessionsIfNeededActual(conversation));
1090            return true;
1091        } else {
1092            return createSessionsIfNeededActual(conversation);
1093        }
1094    }
1095
1096    private boolean createSessionsIfNeededActual(final Conversation conversation) {
1097        Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Creating axolotl sessions if needed...");
1098        boolean newSessions = false;
1099        Set<SignalProtocolAddress> addresses = findDevicesWithoutSession(conversation);
1100        for (SignalProtocolAddress address : addresses) {
1101            Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Processing device: " + address.toString());
1102            FetchStatus status = fetchStatusMap.get(address);
1103            if (status == null || status == FetchStatus.TIMEOUT) {
1104                fetchStatusMap.put(address, FetchStatus.PENDING);
1105                this.buildSessionFromPEP(address);
1106                newSessions = true;
1107            } else if (status == FetchStatus.PENDING) {
1108                newSessions = true;
1109            } else {
1110                Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already fetching bundle for " + address.toString());
1111            }
1112        }
1113
1114        return newSessions;
1115    }
1116
1117    public boolean trustedSessionVerified(final Conversation conversation) {
1118        final Set<XmppAxolotlSession> sessions = new HashSet<>();
1119        sessions.addAll(findSessionsForConversation(conversation));
1120        sessions.addAll(findOwnSessions());
1121        boolean verified = false;
1122        for (XmppAxolotlSession session : sessions) {
1123            if (session.getTrust().isTrustedAndActive()) {
1124                if (session.getTrust().getTrust() == FingerprintStatus.Trust.VERIFIED_X509) {
1125                    verified = true;
1126                } else {
1127                    return false;
1128                }
1129            }
1130        }
1131        return verified;
1132    }
1133
1134    public boolean hasPendingKeyFetches(List<Jid> jids) {
1135        SignalProtocolAddress ownAddress = new SignalProtocolAddress(account.getJid().asBareJid().toString(), 0);
1136        if (fetchStatusMap.getAll(ownAddress.getName()).containsValue(FetchStatus.PENDING)) {
1137            return true;
1138        }
1139        synchronized (this.fetchDeviceIdsMap) {
1140            for (Jid jid : jids) {
1141                SignalProtocolAddress foreignAddress = new SignalProtocolAddress(jid.asBareJid().toString(), 0);
1142                if (fetchStatusMap.getAll(foreignAddress.getName()).containsValue(FetchStatus.PENDING) || this.fetchDeviceIdsMap.containsKey(jid)) {
1143                    return true;
1144                }
1145            }
1146        }
1147        return false;
1148    }
1149
1150    @Nullable
1151    private boolean buildHeader(XmppAxolotlMessage axolotlMessage, Conversation c) {
1152        Set<XmppAxolotlSession> remoteSessions = findSessionsForConversation(c);
1153        final boolean acceptEmpty = (c.getMode() == Conversation.MODE_MULTI && c.getMucOptions().getUserCount() == 0) || c.getContact().isSelf();
1154        Collection<XmppAxolotlSession> ownSessions = findOwnSessions();
1155        if (remoteSessions.isEmpty() && !acceptEmpty) {
1156            return false;
1157        }
1158        for (XmppAxolotlSession session : remoteSessions) {
1159            axolotlMessage.addDevice(session);
1160        }
1161        for (XmppAxolotlSession session : ownSessions) {
1162            axolotlMessage.addDevice(session);
1163        }
1164
1165        return true;
1166    }
1167
1168    //this is being used for private muc messages only
1169    private boolean buildHeader(XmppAxolotlMessage axolotlMessage, Jid jid) {
1170        if (jid == null) {
1171            return false;
1172        }
1173        HashSet<XmppAxolotlSession> sessions = new HashSet<>();
1174        sessions.addAll(this.sessions.getAll(getAddressForJid(jid).getName()).values());
1175        if (sessions.isEmpty()) {
1176            return false;
1177        }
1178        sessions.addAll(findOwnSessions());
1179        for (XmppAxolotlSession session : sessions) {
1180            axolotlMessage.addDevice(session);
1181        }
1182        return true;
1183    }
1184
1185    @Nullable
1186    public XmppAxolotlMessage encrypt(Message message) {
1187        final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(account.getJid().asBareJid(), getOwnDeviceId());
1188        final String content;
1189        if (message.hasFileOnRemoteHost()) {
1190            content = message.getFileParams().url;
1191        } else {
1192            content = message.getBody();
1193        }
1194        try {
1195            axolotlMessage.encrypt(content);
1196        } catch (CryptoFailedException e) {
1197            Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to encrypt message: " + e.getMessage());
1198            return null;
1199        }
1200
1201        final boolean success;
1202        if (message.isPrivateMessage()) {
1203            success = buildHeader(axolotlMessage, message.getTrueCounterpart());
1204        } else {
1205            success = buildHeader(axolotlMessage, (Conversation) message.getConversation());
1206        }
1207        return success ? axolotlMessage : null;
1208    }
1209
1210    public void preparePayloadMessage(final Message message, final boolean delay) {
1211        executor.execute(new Runnable() {
1212            @Override
1213            public void run() {
1214                XmppAxolotlMessage axolotlMessage = encrypt(message);
1215                if (axolotlMessage == null) {
1216                    mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED);
1217                    //mXmppConnectionService.updateConversationUi();
1218                } else {
1219                    Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Generated message, caching: " + message.getUuid());
1220                    messageCache.put(message.getUuid(), axolotlMessage);
1221                    mXmppConnectionService.resendMessage(message, delay);
1222                }
1223            }
1224        });
1225    }
1226
1227    private OmemoVerifiedIceUdpTransportInfo encrypt(final IceUdpTransportInfo element, final XmppAxolotlSession session) throws CryptoFailedException {
1228        final OmemoVerifiedIceUdpTransportInfo transportInfo = new OmemoVerifiedIceUdpTransportInfo();
1229        transportInfo.setAttributes(element.getAttributes());
1230        for (final Element child : element.getChildren()) {
1231            if ("fingerprint".equals(child.getName()) && Namespace.JINGLE_APPS_DTLS.equals(child.getNamespace())) {
1232                final Element fingerprint = new Element("fingerprint", Namespace.OMEMO_DTLS_SRTP_VERIFICATION);
1233                fingerprint.setAttribute("setup", child.getAttribute("setup"));
1234                fingerprint.setAttribute("hash", child.getAttribute("hash"));
1235                final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(account.getJid().asBareJid(), getOwnDeviceId());
1236                final String content = child.getContent();
1237                axolotlMessage.encrypt(content);
1238                axolotlMessage.addDevice(session, true);
1239                fingerprint.addChild(axolotlMessage.toElement());
1240                transportInfo.addChild(fingerprint);
1241            } else {
1242                transportInfo.addChild(child);
1243            }
1244        }
1245        return transportInfo;
1246    }
1247
1248
1249    public ListenableFuture<OmemoVerifiedPayload<OmemoVerifiedRtpContentMap>> encrypt(final RtpContentMap rtpContentMap, final Jid jid, final int deviceId) {
1250        return Futures.transformAsync(
1251                getSession(jid, deviceId),
1252                session -> encrypt(rtpContentMap, session),
1253                MoreExecutors.directExecutor()
1254        );
1255    }
1256
1257    private ListenableFuture<OmemoVerifiedPayload<OmemoVerifiedRtpContentMap>> encrypt(final RtpContentMap rtpContentMap, final XmppAxolotlSession session) {
1258        if (Config.REQUIRE_RTP_VERIFICATION) {
1259            requireVerification(session);
1260        }
1261        final ImmutableMap.Builder<String, RtpContentMap.DescriptionTransport> descriptionTransportBuilder = new ImmutableMap.Builder<>();
1262        final OmemoVerification omemoVerification = new OmemoVerification();
1263        omemoVerification.setDeviceId(session.getRemoteAddress().getDeviceId());
1264        omemoVerification.setSessionFingerprint(session.getFingerprint());
1265        for (final Map.Entry<String, RtpContentMap.DescriptionTransport> content : rtpContentMap.contents.entrySet()) {
1266            final RtpContentMap.DescriptionTransport descriptionTransport = content.getValue();
1267            final OmemoVerifiedIceUdpTransportInfo encryptedTransportInfo;
1268            try {
1269                encryptedTransportInfo = encrypt(descriptionTransport.transport, session);
1270            } catch (final CryptoFailedException e) {
1271                return Futures.immediateFailedFuture(e);
1272            }
1273            descriptionTransportBuilder.put(
1274                    content.getKey(),
1275                    new RtpContentMap.DescriptionTransport(descriptionTransport.senders, descriptionTransport.description, encryptedTransportInfo)
1276            );
1277        }
1278        return Futures.immediateFuture(
1279                new OmemoVerifiedPayload<>(
1280                        omemoVerification,
1281                        new OmemoVerifiedRtpContentMap(rtpContentMap.group, descriptionTransportBuilder.build())
1282                ));
1283    }
1284
1285    private ListenableFuture<XmppAxolotlSession> getSession(final Jid jid, final int deviceId) {
1286        final SignalProtocolAddress address = new SignalProtocolAddress(jid.asBareJid().toString(), deviceId);
1287        final XmppAxolotlSession session = sessions.get(address);
1288        if (session == null) {
1289            return buildSessionFromPEP(address);
1290        }
1291        return Futures.immediateFuture(session);
1292    }
1293
1294    public ListenableFuture<OmemoVerifiedPayload<RtpContentMap>> decrypt(OmemoVerifiedRtpContentMap omemoVerifiedRtpContentMap, final Jid from) {
1295        final ImmutableMap.Builder<String, RtpContentMap.DescriptionTransport> descriptionTransportBuilder = new ImmutableMap.Builder<>();
1296        final OmemoVerification omemoVerification = new OmemoVerification();
1297        final ImmutableList.Builder<ListenableFuture<XmppAxolotlSession>> pepVerificationFutures = new ImmutableList.Builder<>();
1298        for (final Map.Entry<String, RtpContentMap.DescriptionTransport> content : omemoVerifiedRtpContentMap.contents.entrySet()) {
1299            final RtpContentMap.DescriptionTransport descriptionTransport = content.getValue();
1300            final OmemoVerifiedPayload<IceUdpTransportInfo> decryptedTransport;
1301            try {
1302                decryptedTransport = decrypt((OmemoVerifiedIceUdpTransportInfo) descriptionTransport.transport, from, pepVerificationFutures);
1303            } catch (CryptoFailedException e) {
1304                return Futures.immediateFailedFuture(e);
1305            }
1306            omemoVerification.setOrEnsureEqual(decryptedTransport);
1307            descriptionTransportBuilder.put(
1308                    content.getKey(),
1309                    new RtpContentMap.DescriptionTransport(descriptionTransport.senders, descriptionTransport.description, decryptedTransport.payload)
1310            );
1311        }
1312        processPostponed();
1313        final ImmutableList<ListenableFuture<XmppAxolotlSession>> sessionFutures = pepVerificationFutures.build();
1314        return Futures.transform(
1315                Futures.allAsList(sessionFutures),
1316                sessions -> {
1317                    if (Config.REQUIRE_RTP_VERIFICATION) {
1318                        for (XmppAxolotlSession session : sessions) {
1319                            requireVerification(session);
1320                        }
1321                    }
1322                    return new OmemoVerifiedPayload<>(
1323                            omemoVerification,
1324                            new RtpContentMap(omemoVerifiedRtpContentMap.group, descriptionTransportBuilder.build())
1325                    );
1326
1327                },
1328                MoreExecutors.directExecutor()
1329        );
1330    }
1331
1332    private OmemoVerifiedPayload<IceUdpTransportInfo> decrypt(final OmemoVerifiedIceUdpTransportInfo verifiedIceUdpTransportInfo, final Jid from, ImmutableList.Builder<ListenableFuture<XmppAxolotlSession>> pepVerificationFutures) throws CryptoFailedException {
1333        final IceUdpTransportInfo transportInfo = new IceUdpTransportInfo();
1334        transportInfo.setAttributes(verifiedIceUdpTransportInfo.getAttributes());
1335        final OmemoVerification omemoVerification = new OmemoVerification();
1336        for (final Element child : verifiedIceUdpTransportInfo.getChildren()) {
1337            if ("fingerprint".equals(child.getName()) && Namespace.OMEMO_DTLS_SRTP_VERIFICATION.equals(child.getNamespace())) {
1338                final Element fingerprint = new Element("fingerprint", Namespace.JINGLE_APPS_DTLS);
1339                fingerprint.setAttribute("setup", child.getAttribute("setup"));
1340                fingerprint.setAttribute("hash", child.getAttribute("hash"));
1341                final Element encrypted = child.findChildEnsureSingle(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX);
1342                final XmppAxolotlMessage xmppAxolotlMessage = XmppAxolotlMessage.fromElement(encrypted, from.asBareJid());
1343                final XmppAxolotlSession session = getReceivingSession(xmppAxolotlMessage);
1344                final XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintext = xmppAxolotlMessage.decrypt(session, getOwnDeviceId());
1345                final Integer preKeyId = session.getPreKeyIdAndReset();
1346                if (preKeyId != null) {
1347                    postponedSessions.add(session);
1348                }
1349                if (session.isFresh()) {
1350                    pepVerificationFutures.add(putFreshSession(session));
1351                } else if (Config.REQUIRE_RTP_VERIFICATION) {
1352                    pepVerificationFutures.add(Futures.immediateFuture(session));
1353                }
1354                fingerprint.setContent(plaintext.getPlaintext());
1355                omemoVerification.setDeviceId(session.getRemoteAddress().getDeviceId());
1356                omemoVerification.setSessionFingerprint(plaintext.getFingerprint());
1357                transportInfo.addChild(fingerprint);
1358            } else {
1359                transportInfo.addChild(child);
1360            }
1361        }
1362        return new OmemoVerifiedPayload<>(omemoVerification, transportInfo);
1363    }
1364
1365    private static void requireVerification(final XmppAxolotlSession session) {
1366        if (session.getTrust().isVerified()) {
1367            return;
1368        }
1369        throw new NotVerifiedException(String.format(
1370                "session with %s was not verified",
1371                session.getFingerprint()
1372        ));
1373    }
1374
1375    public void prepareKeyTransportMessage(final Conversation conversation, final OnMessageCreatedCallback onMessageCreatedCallback) {
1376        executor.execute(new Runnable() {
1377            @Override
1378            public void run() {
1379                final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(account.getJid().asBareJid(), getOwnDeviceId());
1380                if (buildHeader(axolotlMessage, conversation)) {
1381                    onMessageCreatedCallback.run(axolotlMessage);
1382                } else {
1383                    onMessageCreatedCallback.run(null);
1384                }
1385            }
1386        });
1387    }
1388
1389    public XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message) {
1390        XmppAxolotlMessage axolotlMessage = messageCache.get(message.getUuid());
1391        if (axolotlMessage != null) {
1392            Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache hit: " + message.getUuid());
1393            messageCache.remove(message.getUuid());
1394        } else {
1395            Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache miss: " + message.getUuid());
1396        }
1397        return axolotlMessage;
1398    }
1399
1400    private XmppAxolotlSession recreateUncachedSession(SignalProtocolAddress address) {
1401        IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
1402        return (identityKey != null)
1403                ? new XmppAxolotlSession(account, axolotlStore, address, identityKey)
1404                : null;
1405    }
1406
1407    private XmppAxolotlSession getReceivingSession(XmppAxolotlMessage message) {
1408        SignalProtocolAddress senderAddress = new SignalProtocolAddress(message.getFrom().toString(), message.getSenderDeviceId());
1409        return getReceivingSession(senderAddress);
1410
1411    }
1412
1413    private XmppAxolotlSession getReceivingSession(SignalProtocolAddress senderAddress) {
1414        XmppAxolotlSession session = sessions.get(senderAddress);
1415        if (session == null) {
1416            session = recreateUncachedSession(senderAddress);
1417            if (session == null) {
1418                session = new XmppAxolotlSession(account, axolotlStore, senderAddress);
1419            }
1420        }
1421        return session;
1422    }
1423
1424    public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message, boolean postponePreKeyMessageHandling) throws NotEncryptedForThisDeviceException, BrokenSessionException, OutdatedSenderException {
1425        XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null;
1426
1427        XmppAxolotlSession session = getReceivingSession(message);
1428        int ownDeviceId = getOwnDeviceId();
1429        try {
1430            plaintextMessage = message.decrypt(session, ownDeviceId);
1431            Integer preKeyId = session.getPreKeyIdAndReset();
1432            if (preKeyId != null) {
1433                postPreKeyMessageHandling(session, postponePreKeyMessageHandling);
1434            }
1435        } catch (NotEncryptedForThisDeviceException e) {
1436            if (account.getJid().asBareJid().equals(message.getFrom().asBareJid()) && message.getSenderDeviceId() == ownDeviceId) {
1437                Log.w(Config.LOGTAG, getLogprefix(account) + "Reflected omemo message received");
1438            } else {
1439                throw e;
1440            }
1441        } catch (final BrokenSessionException e) {
1442            throw e;
1443        } catch (final OutdatedSenderException e) {
1444            Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": " + e.getMessage());
1445            throw e;
1446        } catch (CryptoFailedException e) {
1447            Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message from " + message.getFrom(), e);
1448        }
1449
1450        if (session.isFresh() && plaintextMessage != null) {
1451            putFreshSession(session);
1452        }
1453
1454        return plaintextMessage;
1455    }
1456
1457    public void reportBrokenSessionException(BrokenSessionException e, boolean postpone) {
1458        Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": broken session with " + e.getSignalProtocolAddress().toString() + " detected", e);
1459        if (postpone) {
1460            postponedHealing.add(e.getSignalProtocolAddress());
1461        } else {
1462            notifyRequiresHealing(e.getSignalProtocolAddress());
1463        }
1464    }
1465
1466    private void notifyRequiresHealing(final SignalProtocolAddress signalProtocolAddress) {
1467        if (healingAttempts.add(signalProtocolAddress)) {
1468            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": attempt to heal " + signalProtocolAddress);
1469            buildSessionFromPEP(signalProtocolAddress, new OnSessionBuildFromPep() {
1470                @Override
1471                public void onSessionBuildSuccessful() {
1472                    Log.d(Config.LOGTAG, "successfully build new session from pep after detecting broken session");
1473                    completeSession(getReceivingSession(signalProtocolAddress));
1474                }
1475
1476                @Override
1477                public void onSessionBuildFailed() {
1478                    Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to build new session from pep after detecting broken session");
1479                }
1480            });
1481        } else {
1482            Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": do not attempt to heal " + signalProtocolAddress + " again");
1483        }
1484    }
1485
1486    private void postPreKeyMessageHandling(final XmppAxolotlSession session, final boolean postpone) {
1487        if (postpone) {
1488            postponedSessions.add(session);
1489        } else {
1490            if (axolotlStore.flushPreKeys()) {
1491                publishBundlesIfNeeded(false, false);
1492            } else {
1493                Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": nothing to flush. Not republishing key");
1494            }
1495            if (trustedOrPreviouslyResponded(session) && Config.AUTOMATICALLY_COMPLETE_SESSIONS) {
1496                completeSession(session);
1497            }
1498        }
1499    }
1500
1501    public void processPostponed() {
1502        if (postponedSessions.size() > 0) {
1503            if (axolotlStore.flushPreKeys()) {
1504                publishBundlesIfNeeded(false, false);
1505            }
1506        }
1507        final Iterator<XmppAxolotlSession> iterator = postponedSessions.iterator();
1508        while (iterator.hasNext()) {
1509            final XmppAxolotlSession session = iterator.next();
1510            if (trustedOrPreviouslyResponded(session) && Config.AUTOMATICALLY_COMPLETE_SESSIONS) {
1511                completeSession(session);
1512            }
1513            iterator.remove();
1514        }
1515        final Iterator<SignalProtocolAddress> postponedHealingAttemptsIterator = postponedHealing.iterator();
1516        while (postponedHealingAttemptsIterator.hasNext()) {
1517            notifyRequiresHealing(postponedHealingAttemptsIterator.next());
1518            postponedHealingAttemptsIterator.remove();
1519        }
1520    }
1521
1522    private boolean trustedOrPreviouslyResponded(XmppAxolotlSession session) {
1523        try {
1524            return trustedOrPreviouslyResponded(Jid.of(session.getRemoteAddress().getName()));
1525        } catch (IllegalArgumentException e) {
1526            return false;
1527        }
1528    }
1529
1530    public boolean trustedOrPreviouslyResponded(Jid jid) {
1531        final Contact contact = account.getRoster().getContact(jid);
1532        if (contact.showInRoster() || contact.isSelf()) {
1533            return true;
1534        }
1535        final Conversation conversation = mXmppConnectionService.find(account, jid);
1536        return conversation != null && conversation.sentMessagesCount() > 0;
1537    }
1538
1539    private void completeSession(XmppAxolotlSession session) {
1540        final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(account.getJid().asBareJid(), getOwnDeviceId());
1541        axolotlMessage.addDevice(session, true);
1542        try {
1543            final Jid jid = Jid.of(session.getRemoteAddress().getName());
1544            MessagePacket packet = mXmppConnectionService.getMessageGenerator().generateKeyTransportMessage(jid, axolotlMessage);
1545            mXmppConnectionService.sendMessagePacket(account, packet);
1546        } catch (IllegalArgumentException e) {
1547            throw new Error("Remote addresses are created from jid and should convert back to jid", e);
1548        }
1549    }
1550
1551    public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message, final boolean postponePreKeyMessageHandling) {
1552        final XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage;
1553        final XmppAxolotlSession session = getReceivingSession(message);
1554        try {
1555            keyTransportMessage = message.getParameters(session, getOwnDeviceId());
1556            Integer preKeyId = session.getPreKeyIdAndReset();
1557            if (preKeyId != null) {
1558                postPreKeyMessageHandling(session, postponePreKeyMessageHandling);
1559            }
1560        } catch (CryptoFailedException e) {
1561            Log.d(Config.LOGTAG, "could not decrypt keyTransport message " + e.getMessage());
1562            return null;
1563        }
1564
1565        if (session.isFresh() && keyTransportMessage != null) {
1566            putFreshSession(session);
1567        }
1568
1569        return keyTransportMessage;
1570    }
1571
1572    private ListenableFuture<XmppAxolotlSession> putFreshSession(XmppAxolotlSession session) {
1573        sessions.put(session);
1574        if (Config.X509_VERIFICATION) {
1575            if (session.getIdentityKey() != null) {
1576                return verifySessionWithPEP(session);
1577            } else {
1578                Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": identity key was empty after reloading for x509 verification");
1579            }
1580        }
1581        return Futures.immediateFuture(session);
1582    }
1583
1584    public enum FetchStatus {
1585        PENDING,
1586        SUCCESS,
1587        SUCCESS_VERIFIED,
1588        TIMEOUT,
1589        SUCCESS_TRUSTED,
1590        ERROR
1591    }
1592
1593    public interface OnDeviceIdsFetched {
1594        void fetched(Jid jid, Set<Integer> deviceIds);
1595    }
1596
1597
1598    public interface OnMultipleDeviceIdFetched {
1599        void fetched();
1600    }
1601
1602    interface OnSessionBuildFromPep {
1603        void onSessionBuildSuccessful();
1604
1605        void onSessionBuildFailed();
1606    }
1607
1608    private static class AxolotlAddressMap<T> {
1609        protected final Object MAP_LOCK = new Object();
1610        protected Map<String, Map<Integer, T>> map;
1611
1612        public AxolotlAddressMap() {
1613            this.map = new HashMap<>();
1614        }
1615
1616        public void put(SignalProtocolAddress address, T value) {
1617            synchronized (MAP_LOCK) {
1618                Map<Integer, T> devices = map.get(address.getName());
1619                if (devices == null) {
1620                    devices = new HashMap<>();
1621                    map.put(address.getName(), devices);
1622                }
1623                devices.put(address.getDeviceId(), value);
1624            }
1625        }
1626
1627        public T get(SignalProtocolAddress address) {
1628            synchronized (MAP_LOCK) {
1629                Map<Integer, T> devices = map.get(address.getName());
1630                if (devices == null) {
1631                    return null;
1632                }
1633                return devices.get(address.getDeviceId());
1634            }
1635        }
1636
1637        public Map<Integer, T> getAll(String name) {
1638            synchronized (MAP_LOCK) {
1639                Map<Integer, T> devices = map.get(name);
1640                if (devices == null) {
1641                    return new HashMap<>();
1642                }
1643                return devices;
1644            }
1645        }
1646
1647        public boolean hasAny(SignalProtocolAddress address) {
1648            synchronized (MAP_LOCK) {
1649                Map<Integer, T> devices = map.get(address.getName());
1650                return devices != null && !devices.isEmpty();
1651            }
1652        }
1653
1654        public void clear() {
1655            map.clear();
1656        }
1657
1658    }
1659
1660    private static class SessionMap extends AxolotlAddressMap<XmppAxolotlSession> {
1661        private final XmppConnectionService xmppConnectionService;
1662        private final Account account;
1663
1664        public SessionMap(XmppConnectionService service, SQLiteAxolotlStore store, Account account) {
1665            super();
1666            this.xmppConnectionService = service;
1667            this.account = account;
1668            this.fillMap(store);
1669        }
1670
1671        public Set<Jid> findCounterpartsForSourceId(Integer sid) {
1672            Set<Jid> candidates = new HashSet<>();
1673            synchronized (MAP_LOCK) {
1674                for (Map.Entry<String, Map<Integer, XmppAxolotlSession>> entry : map.entrySet()) {
1675                    String key = entry.getKey();
1676                    if (entry.getValue().containsKey(sid)) {
1677                        candidates.add(Jid.of(key));
1678                    }
1679                }
1680            }
1681            return candidates;
1682        }
1683
1684        private void putDevicesForJid(String bareJid, List<Integer> deviceIds, SQLiteAxolotlStore store) {
1685            for (Integer deviceId : deviceIds) {
1686                SignalProtocolAddress axolotlAddress = new SignalProtocolAddress(bareJid, deviceId);
1687                IdentityKey identityKey = store.loadSession(axolotlAddress).getSessionState().getRemoteIdentityKey();
1688                if (Config.X509_VERIFICATION) {
1689                    X509Certificate certificate = store.getFingerprintCertificate(CryptoHelper.bytesToHex(identityKey.getPublicKey().serialize()));
1690                    if (certificate != null) {
1691                        Bundle information = CryptoHelper.extractCertificateInformation(certificate);
1692                        try {
1693                            final String cn = information.getString("subject_cn");
1694                            final Jid jid = Jid.of(bareJid);
1695                            Log.d(Config.LOGTAG, "setting common name for " + jid + " to " + cn);
1696                            account.getRoster().getContact(jid).setCommonName(cn);
1697                        } catch (final IllegalArgumentException ignored) {
1698                            //ignored
1699                        }
1700                    }
1701                }
1702                this.put(axolotlAddress, new XmppAxolotlSession(account, store, axolotlAddress, identityKey));
1703            }
1704        }
1705
1706        private void fillMap(SQLiteAxolotlStore store) {
1707            List<Integer> deviceIds = store.getSubDeviceSessions(account.getJid().asBareJid().toString());
1708            putDevicesForJid(account.getJid().asBareJid().toString(), deviceIds, store);
1709            for (String address : store.getKnownAddresses()) {
1710                deviceIds = store.getSubDeviceSessions(address);
1711                putDevicesForJid(address, deviceIds, store);
1712            }
1713        }
1714
1715        @Override
1716        public void put(SignalProtocolAddress address, XmppAxolotlSession value) {
1717            super.put(address, value);
1718            value.setNotFresh();
1719        }
1720
1721        public void put(XmppAxolotlSession session) {
1722            this.put(session.getRemoteAddress(), session);
1723        }
1724    }
1725
1726    private static class FetchStatusMap extends AxolotlAddressMap<FetchStatus> {
1727
1728        public void clearErrorFor(Jid jid) {
1729            synchronized (MAP_LOCK) {
1730                Map<Integer, FetchStatus> devices = this.map.get(jid.asBareJid().toString());
1731                if (devices == null) {
1732                    return;
1733                }
1734                for (Map.Entry<Integer, FetchStatus> entry : devices.entrySet()) {
1735                    if (entry.getValue() == FetchStatus.ERROR) {
1736                        Log.d(Config.LOGTAG, "resetting error for " + jid.asBareJid() + "(" + entry.getKey() + ")");
1737                        entry.setValue(FetchStatus.TIMEOUT);
1738                    }
1739                }
1740            }
1741        }
1742    }
1743
1744    public static class OmemoVerifiedPayload<T> {
1745        private final int deviceId;
1746        private final String fingerprint;
1747        private final T payload;
1748
1749        private OmemoVerifiedPayload(OmemoVerification omemoVerification, T payload) {
1750            this.deviceId = omemoVerification.getDeviceId();
1751            this.fingerprint = omemoVerification.getFingerprint();
1752            this.payload = payload;
1753        }
1754
1755        public int getDeviceId() {
1756            return deviceId;
1757        }
1758
1759        public String getFingerprint() {
1760            return fingerprint;
1761        }
1762
1763        public T getPayload() {
1764            return payload;
1765        }
1766    }
1767
1768    public static class NotVerifiedException extends SecurityException {
1769
1770        public NotVerifiedException(String message) {
1771            super(message);
1772        }
1773
1774    }
1775}