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