AxolotlService.java

   1package eu.siacs.conversations.crypto.axolotl;
   2
   3import android.os.Bundle;
   4import android.security.KeyChain;
   5import android.support.annotation.NonNull;
   6import android.support.annotation.Nullable;
   7import android.util.Log;
   8import android.util.Pair;
   9
  10import org.bouncycastle.jce.provider.BouncyCastleProvider;
  11import org.whispersystems.libaxolotl.AxolotlAddress;
  12import org.whispersystems.libaxolotl.IdentityKey;
  13import org.whispersystems.libaxolotl.IdentityKeyPair;
  14import org.whispersystems.libaxolotl.InvalidKeyException;
  15import org.whispersystems.libaxolotl.InvalidKeyIdException;
  16import org.whispersystems.libaxolotl.SessionBuilder;
  17import org.whispersystems.libaxolotl.UntrustedIdentityException;
  18import org.whispersystems.libaxolotl.ecc.ECPublicKey;
  19import org.whispersystems.libaxolotl.state.PreKeyBundle;
  20import org.whispersystems.libaxolotl.state.PreKeyRecord;
  21import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
  22import org.whispersystems.libaxolotl.util.KeyHelper;
  23
  24import java.security.PrivateKey;
  25import java.security.Security;
  26import java.security.Signature;
  27import java.security.cert.X509Certificate;
  28import java.util.ArrayList;
  29import java.util.Arrays;
  30import java.util.Collection;
  31import java.util.Collections;
  32import java.util.Comparator;
  33import java.util.HashMap;
  34import java.util.HashSet;
  35import java.util.List;
  36import java.util.Map;
  37import java.util.Random;
  38import java.util.Set;
  39
  40import eu.siacs.conversations.Config;
  41import eu.siacs.conversations.entities.Account;
  42import eu.siacs.conversations.entities.Contact;
  43import eu.siacs.conversations.entities.Conversation;
  44import eu.siacs.conversations.entities.Message;
  45import eu.siacs.conversations.parser.IqParser;
  46import eu.siacs.conversations.services.XmppConnectionService;
  47import eu.siacs.conversations.utils.CryptoHelper;
  48import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
  49import eu.siacs.conversations.xml.Element;
  50import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded;
  51import eu.siacs.conversations.xmpp.OnIqPacketReceived;
  52import eu.siacs.conversations.xmpp.jid.InvalidJidException;
  53import eu.siacs.conversations.xmpp.jid.Jid;
  54import eu.siacs.conversations.xmpp.stanzas.IqPacket;
  55
  56public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
  57
  58	public static final String PEP_PREFIX = "eu.siacs.conversations.axolotl";
  59	public static final String PEP_DEVICE_LIST = PEP_PREFIX + ".devicelist";
  60	public static final String PEP_DEVICE_LIST_NOTIFY = PEP_DEVICE_LIST + "+notify";
  61	public static final String PEP_BUNDLES = PEP_PREFIX + ".bundles";
  62	public static final String PEP_VERIFICATION = PEP_PREFIX + ".verification";
  63
  64	public static final String LOGPREFIX = "AxolotlService";
  65
  66	public static final int NUM_KEYS_TO_PUBLISH = 100;
  67	public static final int publishTriesThreshold = 3;
  68
  69	private final Account account;
  70	private final XmppConnectionService mXmppConnectionService;
  71	private final SQLiteAxolotlStore axolotlStore;
  72	private final SessionMap sessions;
  73	private final Map<Jid, Set<Integer>> deviceIds;
  74	private final Map<String, XmppAxolotlMessage> messageCache;
  75	private final FetchStatusMap fetchStatusMap;
  76	private final SerialSingleThreadExecutor executor;
  77	private int numPublishTriesOnEmptyPep = 0;
  78	private boolean pepBroken = false;
  79
  80	@Override
  81	public void onAdvancedStreamFeaturesAvailable(Account account) {
  82		if (Config.supportOmemo()
  83				&& account.getXmppConnection() != null
  84				&& account.getXmppConnection().getFeatures().pep()) {
  85			publishBundlesIfNeeded(true, false);
  86		} else {
  87			Log.d(Config.LOGTAG,account.getJid().toBareJid()+": skipping OMEMO initialization");
  88		}
  89	}
  90
  91	public boolean fetchMapHasErrors(List<Jid> jids) {
  92		for(Jid jid : jids) {
  93			if (deviceIds.get(jid) != null) {
  94				for (Integer foreignId : this.deviceIds.get(jid)) {
  95					AxolotlAddress address = new AxolotlAddress(jid.toString(), foreignId);
  96					if (fetchStatusMap.getAll(address).containsValue(FetchStatus.ERROR)) {
  97						return true;
  98					}
  99				}
 100			}
 101		}
 102		return false;
 103	}
 104
 105	public void preVerifyFingerprint(Contact contact, String fingerprint) {
 106		axolotlStore.preVerifyFingerprint(contact.getAccount(), contact.getJid().toBareJid().toPreppedString(), fingerprint);
 107	}
 108
 109	public void preVerifyFingerprint(Account account, String fingerprint) {
 110		axolotlStore.preVerifyFingerprint(account, account.getJid().toBareJid().toPreppedString(), fingerprint);
 111	}
 112
 113	private static class AxolotlAddressMap<T> {
 114		protected Map<String, Map<Integer, T>> map;
 115		protected final Object MAP_LOCK = new Object();
 116
 117		public AxolotlAddressMap() {
 118			this.map = new HashMap<>();
 119		}
 120
 121		public void put(AxolotlAddress address, T value) {
 122			synchronized (MAP_LOCK) {
 123				Map<Integer, T> devices = map.get(address.getName());
 124				if (devices == null) {
 125					devices = new HashMap<>();
 126					map.put(address.getName(), devices);
 127				}
 128				devices.put(address.getDeviceId(), value);
 129			}
 130		}
 131
 132		public T get(AxolotlAddress address) {
 133			synchronized (MAP_LOCK) {
 134				Map<Integer, T> devices = map.get(address.getName());
 135				if (devices == null) {
 136					return null;
 137				}
 138				return devices.get(address.getDeviceId());
 139			}
 140		}
 141
 142		public Map<Integer, T> getAll(AxolotlAddress address) {
 143			synchronized (MAP_LOCK) {
 144				Map<Integer, T> devices = map.get(address.getName());
 145				if (devices == null) {
 146					return new HashMap<>();
 147				}
 148				return devices;
 149			}
 150		}
 151
 152		public boolean hasAny(AxolotlAddress address) {
 153			synchronized (MAP_LOCK) {
 154				Map<Integer, T> devices = map.get(address.getName());
 155				return devices != null && !devices.isEmpty();
 156			}
 157		}
 158
 159		public void clear() {
 160			map.clear();
 161		}
 162
 163	}
 164
 165	private static class SessionMap extends AxolotlAddressMap<XmppAxolotlSession> {
 166		private final XmppConnectionService xmppConnectionService;
 167		private final Account account;
 168
 169		public SessionMap(XmppConnectionService service, SQLiteAxolotlStore store, Account account) {
 170			super();
 171			this.xmppConnectionService = service;
 172			this.account = account;
 173			this.fillMap(store);
 174		}
 175
 176		private void putDevicesForJid(String bareJid, List<Integer> deviceIds, SQLiteAxolotlStore store) {
 177			for (Integer deviceId : deviceIds) {
 178				AxolotlAddress axolotlAddress = new AxolotlAddress(bareJid, deviceId);
 179				IdentityKey identityKey = store.loadSession(axolotlAddress).getSessionState().getRemoteIdentityKey();
 180				if(Config.X509_VERIFICATION) {
 181					X509Certificate certificate = store.getFingerprintCertificate(identityKey.getFingerprint().replaceAll("\\s", ""));
 182					if (certificate != null) {
 183						Bundle information = CryptoHelper.extractCertificateInformation(certificate);
 184						try {
 185							final String cn = information.getString("subject_cn");
 186							final Jid jid = Jid.fromString(bareJid);
 187							Log.d(Config.LOGTAG,"setting common name for "+jid+" to "+cn);
 188							account.getRoster().getContact(jid).setCommonName(cn);
 189						} catch (final InvalidJidException ignored) {
 190							//ignored
 191						}
 192					}
 193				}
 194				this.put(axolotlAddress, new XmppAxolotlSession(account, store, axolotlAddress, identityKey));
 195			}
 196		}
 197
 198		private void fillMap(SQLiteAxolotlStore store) {
 199			List<Integer> deviceIds = store.getSubDeviceSessions(account.getJid().toBareJid().toPreppedString());
 200			putDevicesForJid(account.getJid().toBareJid().toPreppedString(), deviceIds, store);
 201			for (Contact contact : account.getRoster().getContacts()) {
 202				Jid bareJid = contact.getJid().toBareJid();
 203				String address = bareJid.toString();
 204				deviceIds = store.getSubDeviceSessions(address);
 205				putDevicesForJid(address, deviceIds, store);
 206			}
 207
 208		}
 209
 210		@Override
 211		public void put(AxolotlAddress address, XmppAxolotlSession value) {
 212			super.put(address, value);
 213			value.setNotFresh();
 214			xmppConnectionService.syncRosterToDisk(account); //TODO why?
 215		}
 216
 217		public void put(XmppAxolotlSession session) {
 218			this.put(session.getRemoteAddress(), session);
 219		}
 220	}
 221
 222	public enum FetchStatus {
 223		PENDING,
 224		SUCCESS,
 225		SUCCESS_VERIFIED,
 226		TIMEOUT,
 227		ERROR
 228	}
 229
 230	private static class FetchStatusMap extends AxolotlAddressMap<FetchStatus> {
 231
 232		public void clearErrorFor(Jid jid) {
 233			synchronized (MAP_LOCK) {
 234				Map<Integer, FetchStatus> devices = this.map.get(jid.toBareJid().toPreppedString());
 235				if (devices == null) {
 236					return;
 237				}
 238				for(Map.Entry<Integer, FetchStatus> entry : devices.entrySet()) {
 239					if (entry.getValue() == FetchStatus.ERROR) {
 240						Log.d(Config.LOGTAG,"resetting error for "+jid.toBareJid()+"("+entry.getKey()+")");
 241						entry.setValue(FetchStatus.TIMEOUT);
 242					}
 243				}
 244			}
 245		}
 246	}
 247
 248	public static String getLogprefix(Account account) {
 249		return LOGPREFIX + " (" + account.getJid().toBareJid().toString() + "): ";
 250	}
 251
 252	public AxolotlService(Account account, XmppConnectionService connectionService) {
 253		if (Security.getProvider("BC") == null) {
 254			Security.addProvider(new BouncyCastleProvider());
 255		}
 256		this.mXmppConnectionService = connectionService;
 257		this.account = account;
 258		this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService);
 259		this.deviceIds = new HashMap<>();
 260		this.messageCache = new HashMap<>();
 261		this.sessions = new SessionMap(mXmppConnectionService, axolotlStore, account);
 262		this.fetchStatusMap = new FetchStatusMap();
 263		this.executor = new SerialSingleThreadExecutor();
 264	}
 265
 266	public String getOwnFingerprint() {
 267		return axolotlStore.getIdentityKeyPair().getPublicKey().getFingerprint().replaceAll("\\s", "");
 268	}
 269
 270	public Set<IdentityKey> getKeysWithTrust(FingerprintStatus status) {
 271		return axolotlStore.getContactKeysWithTrust(account.getJid().toBareJid().toPreppedString(), status);
 272	}
 273
 274	public Set<IdentityKey> getKeysWithTrust(FingerprintStatus status, Jid jid) {
 275		return axolotlStore.getContactKeysWithTrust(jid.toBareJid().toPreppedString(), status);
 276	}
 277
 278	public Set<IdentityKey> getKeysWithTrust(FingerprintStatus status, List<Jid> jids) {
 279		Set<IdentityKey> keys = new HashSet<>();
 280		for(Jid jid : jids) {
 281			keys.addAll(axolotlStore.getContactKeysWithTrust(jid.toPreppedString(), status));
 282		}
 283		return keys;
 284	}
 285
 286	public long getNumTrustedKeys(Jid jid) {
 287		return axolotlStore.getContactNumTrustedKeys(jid.toBareJid().toPreppedString());
 288	}
 289
 290	public boolean anyTargetHasNoTrustedKeys(List<Jid> jids) {
 291		for(Jid jid : jids) {
 292			if (axolotlStore.getContactNumTrustedKeys(jid.toBareJid().toPreppedString()) == 0) {
 293				return true;
 294			}
 295		}
 296		return false;
 297	}
 298
 299	private AxolotlAddress getAddressForJid(Jid jid) {
 300		return new AxolotlAddress(jid.toPreppedString(), 0);
 301	}
 302
 303	public Collection<XmppAxolotlSession> findOwnSessions() {
 304		AxolotlAddress ownAddress = getAddressForJid(account.getJid().toBareJid());
 305		ArrayList<XmppAxolotlSession> s = new ArrayList<>(this.sessions.getAll(ownAddress).values());
 306		Collections.sort(s);
 307		return s;
 308	}
 309
 310
 311
 312	public Collection<XmppAxolotlSession> findSessionsForContact(Contact contact) {
 313		AxolotlAddress contactAddress = getAddressForJid(contact.getJid());
 314		ArrayList<XmppAxolotlSession> s = new ArrayList<>(this.sessions.getAll(contactAddress).values());
 315		Collections.sort(s);
 316		return s;
 317	}
 318
 319	private Set<XmppAxolotlSession> findSessionsForConversation(Conversation conversation) {
 320		HashSet<XmppAxolotlSession> sessions = new HashSet<>();
 321		for(Jid jid : conversation.getAcceptedCryptoTargets()) {
 322			sessions.addAll(this.sessions.getAll(getAddressForJid(jid)).values());
 323		}
 324		return sessions;
 325	}
 326
 327	private boolean hasAny(Jid jid) {
 328		return sessions.hasAny(getAddressForJid(jid));
 329	}
 330
 331	public boolean isPepBroken() {
 332		return this.pepBroken;
 333	}
 334
 335	public void resetBrokenness() {
 336		this.pepBroken = false;
 337		numPublishTriesOnEmptyPep = 0;
 338	}
 339
 340	public void clearErrorsInFetchStatusMap(Jid jid) {
 341		fetchStatusMap.clearErrorFor(jid);
 342	}
 343
 344	public void regenerateKeys(boolean wipeOther) {
 345		axolotlStore.regenerate();
 346		sessions.clear();
 347		fetchStatusMap.clear();
 348		publishBundlesIfNeeded(true, wipeOther);
 349	}
 350
 351	public int getOwnDeviceId() {
 352		return axolotlStore.getLocalRegistrationId();
 353	}
 354
 355	public Set<Integer> getOwnDeviceIds() {
 356		return this.deviceIds.get(account.getJid().toBareJid());
 357	}
 358
 359	public void registerDevices(final Jid jid, @NonNull final Set<Integer> deviceIds) {
 360		if (jid.toBareJid().equals(account.getJid().toBareJid())) {
 361			if (!deviceIds.isEmpty()) {
 362				Log.d(Config.LOGTAG, getLogprefix(account) + "Received non-empty own device list. Resetting publish attempts and pepBroken status.");
 363				pepBroken = false;
 364				numPublishTriesOnEmptyPep = 0;
 365			}
 366			if (deviceIds.contains(getOwnDeviceId())) {
 367				deviceIds.remove(getOwnDeviceId());
 368			} else {
 369				publishOwnDeviceId(deviceIds);
 370			}
 371			for (Integer deviceId : deviceIds) {
 372				AxolotlAddress ownDeviceAddress = new AxolotlAddress(jid.toBareJid().toPreppedString(), deviceId);
 373				if (sessions.get(ownDeviceAddress) == null) {
 374					buildSessionFromPEP(ownDeviceAddress);
 375				}
 376			}
 377		}
 378		Set<Integer> expiredDevices = new HashSet<>(axolotlStore.getSubDeviceSessions(jid.toBareJid().toPreppedString()));
 379		expiredDevices.removeAll(deviceIds);
 380		for (Integer deviceId : expiredDevices) {
 381			AxolotlAddress address = new AxolotlAddress(jid.toBareJid().toPreppedString(), deviceId);
 382			XmppAxolotlSession session = sessions.get(address);
 383			if (session != null && session.getFingerprint() != null) {
 384				if (session.getTrust().isActive()) {
 385					session.setTrust(session.getTrust().toInactive());
 386				}
 387			}
 388		}
 389		Set<Integer> newDevices = new HashSet<>(deviceIds);
 390		for (Integer deviceId : newDevices) {
 391			AxolotlAddress address = new AxolotlAddress(jid.toBareJid().toPreppedString(), deviceId);
 392			XmppAxolotlSession session = sessions.get(address);
 393			if (session != null && session.getFingerprint() != null) {
 394				if (!session.getTrust().isActive()) {
 395					session.setTrust(session.getTrust().toActive());
 396				}
 397			}
 398		}
 399		this.deviceIds.put(jid, deviceIds);
 400		mXmppConnectionService.keyStatusUpdated(null);
 401	}
 402
 403	public void wipeOtherPepDevices() {
 404		if (pepBroken) {
 405			Log.d(Config.LOGTAG, getLogprefix(account) + "wipeOtherPepDevices called, but PEP is broken. Ignoring... ");
 406			return;
 407		}
 408		Set<Integer> deviceIds = new HashSet<>();
 409		deviceIds.add(getOwnDeviceId());
 410		IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIds);
 411		Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Wiping all other devices from Pep:" + publish);
 412		mXmppConnectionService.sendIqPacket(account, publish, null);
 413	}
 414
 415	public void purgeKey(final String fingerprint) {
 416		axolotlStore.setFingerprintStatus(fingerprint.replaceAll("\\s", ""), FingerprintStatus.createCompromised());
 417	}
 418
 419	public void publishOwnDeviceIdIfNeeded() {
 420		if (pepBroken) {
 421			Log.d(Config.LOGTAG, getLogprefix(account) + "publishOwnDeviceIdIfNeeded called, but PEP is broken. Ignoring... ");
 422			return;
 423		}
 424		IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().toBareJid());
 425		mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
 426			@Override
 427			public void onIqPacketReceived(Account account, IqPacket packet) {
 428				if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
 429					Log.d(Config.LOGTAG, getLogprefix(account) + "Timeout received while retrieving own Device Ids.");
 430				} else {
 431					Element item = mXmppConnectionService.getIqParser().getItem(packet);
 432					Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
 433					if (!deviceIds.contains(getOwnDeviceId())) {
 434						publishOwnDeviceId(deviceIds);
 435					}
 436				}
 437			}
 438		});
 439	}
 440
 441	public void publishOwnDeviceId(Set<Integer> deviceIds) {
 442		Set<Integer> deviceIdsCopy = new HashSet<>(deviceIds);
 443		if (!deviceIdsCopy.contains(getOwnDeviceId())) {
 444			Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Own device " + getOwnDeviceId() + " not in PEP devicelist.");
 445			if (deviceIdsCopy.isEmpty()) {
 446				if (numPublishTriesOnEmptyPep >= publishTriesThreshold) {
 447					Log.w(Config.LOGTAG, getLogprefix(account) + "Own device publish attempt threshold exceeded, aborting...");
 448					pepBroken = true;
 449					return;
 450				} else {
 451					numPublishTriesOnEmptyPep++;
 452					Log.w(Config.LOGTAG, getLogprefix(account) + "Own device list empty, attempting to publish (try " + numPublishTriesOnEmptyPep + ")");
 453				}
 454			} else {
 455				numPublishTriesOnEmptyPep = 0;
 456			}
 457			deviceIdsCopy.add(getOwnDeviceId());
 458			IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIdsCopy);
 459			mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
 460				@Override
 461				public void onIqPacketReceived(Account account, IqPacket packet) {
 462					if (packet.getType() == IqPacket.TYPE.ERROR) {
 463						pepBroken = true;
 464						Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing own device id" + packet.findChild("error"));
 465					}
 466				}
 467			});
 468		}
 469	}
 470
 471	public void publishDeviceVerificationAndBundle(final SignedPreKeyRecord signedPreKeyRecord,
 472												   final Set<PreKeyRecord> preKeyRecords,
 473												   final boolean announceAfter,
 474												   final boolean wipe) {
 475		try {
 476			IdentityKey axolotlPublicKey = axolotlStore.getIdentityKeyPair().getPublicKey();
 477			PrivateKey x509PrivateKey = KeyChain.getPrivateKey(mXmppConnectionService, account.getPrivateKeyAlias());
 478			X509Certificate[] chain = KeyChain.getCertificateChain(mXmppConnectionService, account.getPrivateKeyAlias());
 479			Signature verifier = Signature.getInstance("sha256WithRSA");
 480			verifier.initSign(x509PrivateKey,mXmppConnectionService.getRNG());
 481			verifier.update(axolotlPublicKey.serialize());
 482			byte[] signature = verifier.sign();
 483			IqPacket packet = mXmppConnectionService.getIqGenerator().publishVerification(signature, chain, getOwnDeviceId());
 484			Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": publish verification for device "+getOwnDeviceId());
 485			mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
 486				@Override
 487				public void onIqPacketReceived(Account account, IqPacket packet) {
 488					publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe);
 489				}
 490			});
 491		} catch (Exception  e) {
 492			e.printStackTrace();
 493		}
 494	}
 495
 496	public void publishBundlesIfNeeded(final boolean announce, final boolean wipe) {
 497		if (pepBroken) {
 498			Log.d(Config.LOGTAG, getLogprefix(account) + "publishBundlesIfNeeded called, but PEP is broken. Ignoring... ");
 499			return;
 500		}
 501		IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(account.getJid().toBareJid(), getOwnDeviceId());
 502		mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
 503			@Override
 504			public void onIqPacketReceived(Account account, IqPacket packet) {
 505
 506				if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
 507					return; //ignore timeout. do nothing
 508				}
 509
 510				if (packet.getType() == IqPacket.TYPE.ERROR) {
 511					Element error = packet.findChild("error");
 512					if (error == null || !error.hasChild("item-not-found")) {
 513						pepBroken = true;
 514						Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "request for device bundles came back with something other than item-not-found" + packet);
 515						return;
 516					}
 517				}
 518
 519				PreKeyBundle bundle = mXmppConnectionService.getIqParser().bundle(packet);
 520				Map<Integer, ECPublicKey> keys = mXmppConnectionService.getIqParser().preKeyPublics(packet);
 521				boolean flush = false;
 522				if (bundle == null) {
 523					Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid bundle:" + packet);
 524					bundle = new PreKeyBundle(-1, -1, -1, null, -1, null, null, null);
 525					flush = true;
 526				}
 527				if (keys == null) {
 528					Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid prekeys:" + packet);
 529				}
 530				try {
 531					boolean changed = false;
 532					// Validate IdentityKey
 533					IdentityKeyPair identityKeyPair = axolotlStore.getIdentityKeyPair();
 534					if (flush || !identityKeyPair.getPublicKey().equals(bundle.getIdentityKey())) {
 535						Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding own IdentityKey " + identityKeyPair.getPublicKey() + " to PEP.");
 536						changed = true;
 537					}
 538
 539					// Validate signedPreKeyRecord + ID
 540					SignedPreKeyRecord signedPreKeyRecord;
 541					int numSignedPreKeys = axolotlStore.loadSignedPreKeys().size();
 542					try {
 543						signedPreKeyRecord = axolotlStore.loadSignedPreKey(bundle.getSignedPreKeyId());
 544						if (flush
 545								|| !bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey())
 546								|| !Arrays.equals(bundle.getSignedPreKeySignature(), signedPreKeyRecord.getSignature())) {
 547							Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
 548							signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
 549							axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
 550							changed = true;
 551						}
 552					} catch (InvalidKeyIdException e) {
 553						Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
 554						signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
 555						axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
 556						changed = true;
 557					}
 558
 559					// Validate PreKeys
 560					Set<PreKeyRecord> preKeyRecords = new HashSet<>();
 561					if (keys != null) {
 562						for (Integer id : keys.keySet()) {
 563							try {
 564								PreKeyRecord preKeyRecord = axolotlStore.loadPreKey(id);
 565								if (preKeyRecord.getKeyPair().getPublicKey().equals(keys.get(id))) {
 566									preKeyRecords.add(preKeyRecord);
 567								}
 568							} catch (InvalidKeyIdException ignored) {
 569							}
 570						}
 571					}
 572					int newKeys = NUM_KEYS_TO_PUBLISH - preKeyRecords.size();
 573					if (newKeys > 0) {
 574						List<PreKeyRecord> newRecords = KeyHelper.generatePreKeys(
 575								axolotlStore.getCurrentPreKeyId() + 1, newKeys);
 576						preKeyRecords.addAll(newRecords);
 577						for (PreKeyRecord record : newRecords) {
 578							axolotlStore.storePreKey(record.getId(), record);
 579						}
 580						changed = true;
 581						Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding " + newKeys + " new preKeys to PEP.");
 582					}
 583
 584
 585					if (changed) {
 586						if (account.getPrivateKeyAlias() != null && Config.X509_VERIFICATION) {
 587							mXmppConnectionService.publishDisplayName(account);
 588							publishDeviceVerificationAndBundle(signedPreKeyRecord, preKeyRecords, announce, wipe);
 589						} else {
 590							publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announce, wipe);
 591						}
 592					} else {
 593						Log.d(Config.LOGTAG, getLogprefix(account) + "Bundle " + getOwnDeviceId() + " in PEP was current");
 594						if (wipe) {
 595							wipeOtherPepDevices();
 596						} else if (announce) {
 597							Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
 598							publishOwnDeviceIdIfNeeded();
 599						}
 600					}
 601				} catch (InvalidKeyException e) {
 602					Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage());
 603				}
 604			}
 605		});
 606	}
 607
 608	private void publishDeviceBundle(SignedPreKeyRecord signedPreKeyRecord,
 609									 Set<PreKeyRecord> preKeyRecords,
 610									 final boolean announceAfter,
 611									 final boolean wipe) {
 612		IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles(
 613				signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(),
 614				preKeyRecords, getOwnDeviceId());
 615		Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing: " + publish);
 616		mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
 617			@Override
 618			public void onIqPacketReceived(Account account, IqPacket packet) {
 619				if (packet.getType() == IqPacket.TYPE.RESULT) {
 620					Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Successfully published bundle. ");
 621					if (wipe) {
 622						wipeOtherPepDevices();
 623					} else if (announceAfter) {
 624						Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
 625						publishOwnDeviceIdIfNeeded();
 626					}
 627				} else if (packet.getType() == IqPacket.TYPE.ERROR) {
 628					pepBroken = true;
 629					Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing bundle: " + packet.findChild("error"));
 630				}
 631			}
 632		});
 633	}
 634
 635	public enum AxolotlCapability {
 636		FULL,
 637		MISSING_PRESENCE,
 638		MISSING_KEYS,
 639		WRONG_CONFIGURATION,
 640		NO_MEMBERS
 641	}
 642
 643	public boolean isConversationAxolotlCapable(Conversation conversation) {
 644		return isConversationAxolotlCapableDetailed(conversation).first == AxolotlCapability.FULL;
 645	}
 646
 647	public Pair<AxolotlCapability,Jid> isConversationAxolotlCapableDetailed(Conversation conversation) {
 648		if (conversation.getMode() == Conversation.MODE_SINGLE
 649				|| (conversation.getMucOptions().membersOnly() && conversation.getMucOptions().nonanonymous())) {
 650			final List<Jid> jids = getCryptoTargets(conversation);
 651			for(Jid jid : jids) {
 652				if (!hasAny(jid) && (!deviceIds.containsKey(jid) || deviceIds.get(jid).isEmpty())) {
 653					if (conversation.getAccount().getRoster().getContact(jid).mutualPresenceSubscription()) {
 654						return new Pair<>(AxolotlCapability.MISSING_KEYS,jid);
 655					} else {
 656						return new Pair<>(AxolotlCapability.MISSING_PRESENCE,jid);
 657					}
 658				}
 659			}
 660			if (jids.size() > 0) {
 661				return new Pair<>(AxolotlCapability.FULL, null);
 662			} else {
 663				return new Pair<>(AxolotlCapability.NO_MEMBERS, null);
 664			}
 665		} else {
 666			return new Pair<>(AxolotlCapability.WRONG_CONFIGURATION, null);
 667		}
 668	}
 669
 670	public List<Jid> getCryptoTargets(Conversation conversation) {
 671		final List<Jid> jids;
 672		if (conversation.getMode() == Conversation.MODE_SINGLE) {
 673			jids = Arrays.asList(conversation.getJid().toBareJid());
 674		} else {
 675			jids = conversation.getMucOptions().getMembers();
 676		}
 677		return jids;
 678	}
 679
 680	public FingerprintStatus getFingerprintTrust(String fingerprint) {
 681		return axolotlStore.getFingerprintStatus(fingerprint);
 682	}
 683
 684	public X509Certificate getFingerprintCertificate(String fingerprint) {
 685		return axolotlStore.getFingerprintCertificate(fingerprint);
 686	}
 687
 688	public void setFingerprintTrust(String fingerprint, FingerprintStatus status) {
 689		axolotlStore.setFingerprintStatus(fingerprint, status);
 690	}
 691
 692	private void verifySessionWithPEP(final XmppAxolotlSession session) {
 693		Log.d(Config.LOGTAG, "trying to verify fresh session (" + session.getRemoteAddress().getName() + ") with pep");
 694		final AxolotlAddress address = session.getRemoteAddress();
 695		final IdentityKey identityKey = session.getIdentityKey();
 696		try {
 697			IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(Jid.fromString(address.getName()), address.getDeviceId());
 698			mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
 699				@Override
 700				public void onIqPacketReceived(Account account, IqPacket packet) {
 701					Pair<X509Certificate[],byte[]> verification = mXmppConnectionService.getIqParser().verification(packet);
 702					if (verification != null) {
 703						try {
 704							Signature verifier = Signature.getInstance("sha256WithRSA");
 705							verifier.initVerify(verification.first[0]);
 706							verifier.update(identityKey.serialize());
 707							if (verifier.verify(verification.second)) {
 708								try {
 709									mXmppConnectionService.getMemorizingTrustManager().getNonInteractive().checkClientTrusted(verification.first, "RSA");
 710									String fingerprint = session.getFingerprint();
 711									Log.d(Config.LOGTAG, "verified session with x.509 signature. fingerprint was: "+fingerprint);
 712									setFingerprintTrust(fingerprint, FingerprintStatus.createActiveVerified(true));
 713									axolotlStore.setFingerprintCertificate(fingerprint, verification.first[0]);
 714									fetchStatusMap.put(address, FetchStatus.SUCCESS_VERIFIED);
 715									Bundle information = CryptoHelper.extractCertificateInformation(verification.first[0]);
 716									try {
 717										final String cn = information.getString("subject_cn");
 718										final Jid jid = Jid.fromString(address.getName());
 719										Log.d(Config.LOGTAG,"setting common name for "+jid+" to "+cn);
 720										account.getRoster().getContact(jid).setCommonName(cn);
 721									} catch (final InvalidJidException ignored) {
 722										//ignored
 723									}
 724									finishBuildingSessionsFromPEP(address);
 725									return;
 726								} catch (Exception e) {
 727									Log.d(Config.LOGTAG,"could not verify certificate");
 728								}
 729							}
 730						} catch (Exception e) {
 731							Log.d(Config.LOGTAG, "error during verification " + e.getMessage());
 732						}
 733					} else {
 734						Log.d(Config.LOGTAG,"no verification found");
 735					}
 736					fetchStatusMap.put(address, FetchStatus.SUCCESS);
 737					finishBuildingSessionsFromPEP(address);
 738				}
 739			});
 740		} catch (InvalidJidException e) {
 741			fetchStatusMap.put(address, FetchStatus.SUCCESS);
 742			finishBuildingSessionsFromPEP(address);
 743		}
 744	}
 745
 746	private void finishBuildingSessionsFromPEP(final AxolotlAddress address) {
 747		AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toPreppedString(), 0);
 748		Map<Integer, FetchStatus> own = fetchStatusMap.getAll(ownAddress);
 749		Map<Integer, FetchStatus> remote = fetchStatusMap.getAll(address);
 750		if (!own.containsValue(FetchStatus.PENDING) && !remote.containsValue(FetchStatus.PENDING)) {
 751			FetchStatus report = null;
 752			if (own.containsValue(FetchStatus.SUCCESS) || remote.containsValue(FetchStatus.SUCCESS)) {
 753				report = FetchStatus.SUCCESS;
 754			} else if (own.containsValue(FetchStatus.SUCCESS_VERIFIED) || remote.containsValue(FetchStatus.SUCCESS_VERIFIED)) {
 755				report = FetchStatus.SUCCESS_VERIFIED;
 756			} else if (own.containsValue(FetchStatus.ERROR) || remote.containsValue(FetchStatus.ERROR)) {
 757				report = FetchStatus.ERROR;
 758			}
 759			mXmppConnectionService.keyStatusUpdated(report);
 760		}
 761	}
 762
 763	private void buildSessionFromPEP(final AxolotlAddress address) {
 764		Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new sesstion for " + address.toString());
 765		if (address.getDeviceId() == getOwnDeviceId()) {
 766			throw new AssertionError("We should NEVER build a session with ourselves. What happened here?!");
 767		}
 768
 769		try {
 770			IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(
 771					Jid.fromString(address.getName()), address.getDeviceId());
 772			Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Retrieving bundle: " + bundlesPacket);
 773			mXmppConnectionService.sendIqPacket(account, bundlesPacket, new OnIqPacketReceived() {
 774
 775				@Override
 776				public void onIqPacketReceived(Account account, IqPacket packet) {
 777					if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
 778						fetchStatusMap.put(address, FetchStatus.TIMEOUT);
 779					} else if (packet.getType() == IqPacket.TYPE.RESULT) {
 780						Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received preKey IQ packet, processing...");
 781						final IqParser parser = mXmppConnectionService.getIqParser();
 782						final List<PreKeyBundle> preKeyBundleList = parser.preKeys(packet);
 783						final PreKeyBundle bundle = parser.bundle(packet);
 784						if (preKeyBundleList.isEmpty() || bundle == null) {
 785							Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "preKey IQ packet invalid: " + packet);
 786							fetchStatusMap.put(address, FetchStatus.ERROR);
 787							finishBuildingSessionsFromPEP(address);
 788							return;
 789						}
 790						Random random = new Random();
 791						final PreKeyBundle preKey = preKeyBundleList.get(random.nextInt(preKeyBundleList.size()));
 792						if (preKey == null) {
 793							//should never happen
 794							fetchStatusMap.put(address, FetchStatus.ERROR);
 795							finishBuildingSessionsFromPEP(address);
 796							return;
 797						}
 798
 799						final PreKeyBundle preKeyBundle = new PreKeyBundle(0, address.getDeviceId(),
 800								preKey.getPreKeyId(), preKey.getPreKey(),
 801								bundle.getSignedPreKeyId(), bundle.getSignedPreKey(),
 802								bundle.getSignedPreKeySignature(), bundle.getIdentityKey());
 803
 804						try {
 805							SessionBuilder builder = new SessionBuilder(axolotlStore, address);
 806							builder.process(preKeyBundle);
 807							XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey());
 808							sessions.put(address, session);
 809							if (Config.X509_VERIFICATION) {
 810								verifySessionWithPEP(session);
 811							} else {
 812								FingerprintStatus status = getFingerprintTrust(bundle.getIdentityKey().getFingerprint().replaceAll("\\s",""));
 813								boolean verified = status != null && status.isVerified();
 814								fetchStatusMap.put(address, verified ? FetchStatus.SUCCESS_VERIFIED : FetchStatus.SUCCESS);
 815								finishBuildingSessionsFromPEP(address);
 816							}
 817						} catch (UntrustedIdentityException | InvalidKeyException e) {
 818							Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error building session for " + address + ": "
 819									+ e.getClass().getName() + ", " + e.getMessage());
 820							fetchStatusMap.put(address, FetchStatus.ERROR);
 821							finishBuildingSessionsFromPEP(address);
 822						}
 823					} else {
 824						fetchStatusMap.put(address, FetchStatus.ERROR);
 825						Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while building session:" + packet.findChild("error"));
 826						finishBuildingSessionsFromPEP(address);
 827					}
 828				}
 829			});
 830		} catch (InvalidJidException e) {
 831			Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Got address with invalid jid: " + address.getName());
 832		}
 833	}
 834
 835	public Set<AxolotlAddress> findDevicesWithoutSession(final Conversation conversation) {
 836		Set<AxolotlAddress> addresses = new HashSet<>();
 837		for(Jid jid : getCryptoTargets(conversation)) {
 838			Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Finding devices without session for " + jid);
 839			if (deviceIds.get(jid) != null) {
 840				for (Integer foreignId : this.deviceIds.get(jid)) {
 841					AxolotlAddress address = new AxolotlAddress(jid.toString(), foreignId);
 842					if (sessions.get(address) == null) {
 843						IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
 844						if (identityKey != null) {
 845							Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
 846							XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
 847							sessions.put(address, session);
 848						} else {
 849							Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + jid + ":" + foreignId);
 850							if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
 851								addresses.add(address);
 852							} else {
 853								Log.d(Config.LOGTAG, getLogprefix(account) + "skipping over " + address + " because it's broken");
 854							}
 855						}
 856					}
 857				}
 858			} else {
 859				Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Have no target devices in PEP!");
 860			}
 861		}
 862		if (deviceIds.get(account.getJid().toBareJid()) != null) {
 863			for (Integer ownId : this.deviceIds.get(account.getJid().toBareJid())) {
 864				AxolotlAddress address = new AxolotlAddress(account.getJid().toBareJid().toPreppedString(), ownId);
 865				if (sessions.get(address) == null) {
 866					IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
 867					if (identityKey != null) {
 868						Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
 869						XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
 870						sessions.put(address, session);
 871					} else {
 872						Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + account.getJid().toBareJid() + ":" + ownId);
 873						if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
 874							addresses.add(address);
 875						} else {
 876							Log.d(Config.LOGTAG,getLogprefix(account)+"skipping over "+address+" because it's broken");
 877						}
 878					}
 879				}
 880			}
 881		}
 882
 883		return addresses;
 884	}
 885
 886	public boolean createSessionsIfNeeded(final Conversation conversation) {
 887		Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Creating axolotl sessions if needed...");
 888		boolean newSessions = false;
 889		Set<AxolotlAddress> addresses = findDevicesWithoutSession(conversation);
 890		for (AxolotlAddress address : addresses) {
 891			Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Processing device: " + address.toString());
 892			FetchStatus status = fetchStatusMap.get(address);
 893			if (status == null || status == FetchStatus.TIMEOUT) {
 894				fetchStatusMap.put(address, FetchStatus.PENDING);
 895				this.buildSessionFromPEP(address);
 896				newSessions = true;
 897			} else if (status == FetchStatus.PENDING) {
 898				newSessions = true;
 899			} else {
 900				Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already fetching bundle for " + address.toString());
 901			}
 902		}
 903
 904		return newSessions;
 905	}
 906
 907	public boolean trustedSessionVerified(final Conversation conversation) {
 908		Set<XmppAxolotlSession> sessions = findSessionsForConversation(conversation);
 909		sessions.addAll(findOwnSessions());
 910		boolean verified = false;
 911		for(XmppAxolotlSession session : sessions) {
 912			if (session.getTrust().isTrustedAndActive()) {
 913				if (session.getTrust().getTrust() == FingerprintStatus.Trust.VERIFIED_X509) {
 914					verified = true;
 915				} else {
 916					return false;
 917				}
 918			}
 919		}
 920		return verified;
 921	}
 922
 923	public boolean hasPendingKeyFetches(Account account, List<Jid> jids) {
 924		AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toPreppedString(), 0);
 925		if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)) {
 926			return true;
 927		}
 928		for(Jid jid : jids) {
 929			AxolotlAddress foreignAddress = new AxolotlAddress(jid.toBareJid().toPreppedString(), 0);
 930			if (fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING)) {
 931				return true;
 932			}
 933		}
 934		return false;
 935	}
 936
 937	@Nullable
 938	private XmppAxolotlMessage buildHeader(Conversation conversation) {
 939		final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(
 940				account.getJid().toBareJid(), getOwnDeviceId());
 941
 942		Set<XmppAxolotlSession> remoteSessions = findSessionsForConversation(conversation);
 943		Collection<XmppAxolotlSession> ownSessions = findOwnSessions();
 944		if (remoteSessions.isEmpty()) {
 945			return null;
 946		}
 947		for (XmppAxolotlSession session : remoteSessions) {
 948			axolotlMessage.addDevice(session);
 949		}
 950		for (XmppAxolotlSession session : ownSessions) {
 951			axolotlMessage.addDevice(session);
 952		}
 953
 954		return axolotlMessage;
 955	}
 956
 957	@Nullable
 958	public XmppAxolotlMessage encrypt(Message message) {
 959		XmppAxolotlMessage axolotlMessage = buildHeader(message.getConversation());
 960
 961		if (axolotlMessage != null) {
 962			final String content;
 963			if (message.hasFileOnRemoteHost()) {
 964				content = message.getFileParams().url.toString();
 965			} else {
 966				content = message.getBody();
 967			}
 968			try {
 969				axolotlMessage.encrypt(content);
 970			} catch (CryptoFailedException e) {
 971				Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to encrypt message: " + e.getMessage());
 972				return null;
 973			}
 974		}
 975
 976		return axolotlMessage;
 977	}
 978
 979	public void preparePayloadMessage(final Message message, final boolean delay) {
 980		executor.execute(new Runnable() {
 981			@Override
 982			public void run() {
 983				XmppAxolotlMessage axolotlMessage = encrypt(message);
 984				if (axolotlMessage == null) {
 985					mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED);
 986					//mXmppConnectionService.updateConversationUi();
 987				} else {
 988					Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Generated message, caching: " + message.getUuid());
 989					messageCache.put(message.getUuid(), axolotlMessage);
 990					mXmppConnectionService.resendMessage(message, delay);
 991				}
 992			}
 993		});
 994	}
 995
 996	public void prepareKeyTransportMessage(final Conversation conversation, final OnMessageCreatedCallback onMessageCreatedCallback) {
 997		executor.execute(new Runnable() {
 998			@Override
 999			public void run() {
1000				XmppAxolotlMessage axolotlMessage = buildHeader(conversation);
1001				onMessageCreatedCallback.run(axolotlMessage);
1002			}
1003		});
1004	}
1005
1006	public XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message) {
1007		XmppAxolotlMessage axolotlMessage = messageCache.get(message.getUuid());
1008		if (axolotlMessage != null) {
1009			Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache hit: " + message.getUuid());
1010			messageCache.remove(message.getUuid());
1011		} else {
1012			Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache miss: " + message.getUuid());
1013		}
1014		return axolotlMessage;
1015	}
1016
1017	private XmppAxolotlSession recreateUncachedSession(AxolotlAddress address) {
1018		IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
1019		return (identityKey != null)
1020				? new XmppAxolotlSession(account, axolotlStore, address, identityKey)
1021				: null;
1022	}
1023
1024	private XmppAxolotlSession getReceivingSession(XmppAxolotlMessage message) {
1025		AxolotlAddress senderAddress = new AxolotlAddress(message.getFrom().toString(),
1026				message.getSenderDeviceId());
1027		XmppAxolotlSession session = sessions.get(senderAddress);
1028		if (session == null) {
1029			Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Account: " + account.getJid() + " No axolotl session found while parsing received message " + message);
1030			session = recreateUncachedSession(senderAddress);
1031			if (session == null) {
1032				session = new XmppAxolotlSession(account, axolotlStore, senderAddress);
1033			}
1034		}
1035		return session;
1036	}
1037
1038	public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message) {
1039		XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null;
1040
1041		XmppAxolotlSession session = getReceivingSession(message);
1042		try {
1043			plaintextMessage = message.decrypt(session, getOwnDeviceId());
1044			Integer preKeyId = session.getPreKeyId();
1045			if (preKeyId != null) {
1046				publishBundlesIfNeeded(false, false);
1047				session.resetPreKeyId();
1048			}
1049		} catch (CryptoFailedException e) {
1050			Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message: " + e.getMessage());
1051		}
1052
1053		if (session.isFresh() && plaintextMessage != null) {
1054			putFreshSession(session);
1055		}
1056
1057		return plaintextMessage;
1058	}
1059
1060	public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message) {
1061		XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage;
1062
1063		XmppAxolotlSession session = getReceivingSession(message);
1064		keyTransportMessage = message.getParameters(session, getOwnDeviceId());
1065
1066		if (session.isFresh() && keyTransportMessage != null) {
1067			putFreshSession(session);
1068		}
1069
1070		return keyTransportMessage;
1071	}
1072
1073	private void putFreshSession(XmppAxolotlSession session) {
1074		Log.d(Config.LOGTAG,"put fresh session");
1075		sessions.put(session);
1076		if (Config.X509_VERIFICATION) {
1077			if (session.getIdentityKey() != null) {
1078				verifySessionWithPEP(session);
1079			} else {
1080				Log.e(Config.LOGTAG,account.getJid().toBareJid()+": identity key was empty after reloading for x509 verification");
1081			}
1082		}
1083	}
1084}