AxolotlService.java

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