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