AxolotlService.java

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