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}