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