AxolotlService.java

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