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