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 fingerprint "+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 FetchStatus status = fetchStatusMap.get(ownDeviceAddress);
411 if (status == null || status == FetchStatus.TIMEOUT) {
412 fetchStatusMap.put(ownDeviceAddress, FetchStatus.PENDING);
413 this.buildSessionFromPEP(ownDeviceAddress);
414 }
415 }
416 }
417 if (needsPublishing) {
418 publishOwnDeviceId(deviceIds);
419 }
420 }
421 this.deviceIds.put(jid, deviceIds);
422 mXmppConnectionService.updateConversationUi(); //update the lock icon
423 mXmppConnectionService.keyStatusUpdated(null);
424 }
425
426 public void wipeOtherPepDevices() {
427 if (pepBroken) {
428 Log.d(Config.LOGTAG, getLogprefix(account) + "wipeOtherPepDevices called, but PEP is broken. Ignoring... ");
429 return;
430 }
431 Set<Integer> deviceIds = new HashSet<>();
432 deviceIds.add(getOwnDeviceId());
433 IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIds);
434 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Wiping all other devices from Pep:" + publish);
435 mXmppConnectionService.sendIqPacket(account, publish, null);
436 }
437
438 public void purgeKey(final String fingerprint) {
439 axolotlStore.setFingerprintStatus(fingerprint.replaceAll("\\s", ""), FingerprintStatus.createCompromised());
440 }
441
442 public void publishOwnDeviceIdIfNeeded() {
443 if (pepBroken) {
444 Log.d(Config.LOGTAG, getLogprefix(account) + "publishOwnDeviceIdIfNeeded called, but PEP is broken. Ignoring... ");
445 return;
446 }
447 IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().toBareJid());
448 mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
449 @Override
450 public void onIqPacketReceived(Account account, IqPacket packet) {
451 if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
452 Log.d(Config.LOGTAG, getLogprefix(account) + "Timeout received while retrieving own Device Ids.");
453 } else {
454 Element item = mXmppConnectionService.getIqParser().getItem(packet);
455 Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
456 registerDevices(account.getJid().toBareJid(),deviceIds);
457 }
458 }
459 });
460 }
461
462 private Set<Integer> getExpiredDevices() {
463 Set<Integer> devices = new HashSet<>();
464 for(XmppAxolotlSession session : findOwnSessions()) {
465 if (session.getTrust().isActive()) {
466 long diff = System.currentTimeMillis() - session.getTrust().getLastActivation();
467 if (diff > Config.OMEMO_AUTO_EXPIRY) {
468 long lastMessageDiff = System.currentTimeMillis() - mXmppConnectionService.databaseBackend.getLastTimeFingerprintUsed(account,session.getFingerprint());
469 long hours = Math.round(lastMessageDiff/(1000*60.0*60.0));
470 if (lastMessageDiff > Config.OMEMO_AUTO_EXPIRY) {
471 devices.add(session.getRemoteAddress().getDeviceId());
472 session.setTrust(session.getTrust().toInactive());
473 Log.d(Config.LOGTAG,account.getJid().toBareJid()+": added own device " + session.getFingerprint() + " to list of expired devices. Last message received "+hours+" hours ago");
474 } else {
475 Log.d(Config.LOGTAG,account.getJid().toBareJid()+": own device "+session.getFingerprint()+" was active "+hours+" hours ago");
476 }
477 }
478 }
479 }
480 return devices;
481 }
482
483 public void publishOwnDeviceId(Set<Integer> deviceIds) {
484 Set<Integer> deviceIdsCopy = new HashSet<>(deviceIds);
485 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "publishing own device ids");
486 if (deviceIdsCopy.isEmpty()) {
487 if (numPublishTriesOnEmptyPep >= publishTriesThreshold) {
488 Log.w(Config.LOGTAG, getLogprefix(account) + "Own device publish attempt threshold exceeded, aborting...");
489 pepBroken = true;
490 return;
491 } else {
492 numPublishTriesOnEmptyPep++;
493 Log.w(Config.LOGTAG, getLogprefix(account) + "Own device list empty, attempting to publish (try " + numPublishTriesOnEmptyPep + ")");
494 }
495 } else {
496 numPublishTriesOnEmptyPep = 0;
497 }
498 deviceIdsCopy.add(getOwnDeviceId());
499 IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIdsCopy);
500 ownPushPending.set(true);
501 mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
502 @Override
503 public void onIqPacketReceived(Account account, IqPacket packet) {
504 ownPushPending.set(false);
505 if (packet.getType() == IqPacket.TYPE.ERROR) {
506 pepBroken = true;
507 Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing own device id" + packet.findChild("error"));
508 }
509 }
510 });
511 }
512
513 public void publishDeviceVerificationAndBundle(final SignedPreKeyRecord signedPreKeyRecord,
514 final Set<PreKeyRecord> preKeyRecords,
515 final boolean announceAfter,
516 final boolean wipe) {
517 try {
518 IdentityKey axolotlPublicKey = axolotlStore.getIdentityKeyPair().getPublicKey();
519 PrivateKey x509PrivateKey = KeyChain.getPrivateKey(mXmppConnectionService, account.getPrivateKeyAlias());
520 X509Certificate[] chain = KeyChain.getCertificateChain(mXmppConnectionService, account.getPrivateKeyAlias());
521 Signature verifier = Signature.getInstance("sha256WithRSA");
522 verifier.initSign(x509PrivateKey,mXmppConnectionService.getRNG());
523 verifier.update(axolotlPublicKey.serialize());
524 byte[] signature = verifier.sign();
525 IqPacket packet = mXmppConnectionService.getIqGenerator().publishVerification(signature, chain, getOwnDeviceId());
526 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": publish verification for device "+getOwnDeviceId());
527 mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
528 @Override
529 public void onIqPacketReceived(Account account, IqPacket packet) {
530 publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe);
531 }
532 });
533 } catch (Exception e) {
534 e.printStackTrace();
535 }
536 }
537
538 public void publishBundlesIfNeeded(final boolean announce, final boolean wipe) {
539 if (pepBroken) {
540 Log.d(Config.LOGTAG, getLogprefix(account) + "publishBundlesIfNeeded called, but PEP is broken. Ignoring... ");
541 return;
542 }
543 IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(account.getJid().toBareJid(), getOwnDeviceId());
544 mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
545 @Override
546 public void onIqPacketReceived(Account account, IqPacket packet) {
547
548 if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
549 return; //ignore timeout. do nothing
550 }
551
552 if (packet.getType() == IqPacket.TYPE.ERROR) {
553 Element error = packet.findChild("error");
554 if (error == null || !error.hasChild("item-not-found")) {
555 pepBroken = true;
556 Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "request for device bundles came back with something other than item-not-found" + packet);
557 return;
558 }
559 }
560
561 PreKeyBundle bundle = mXmppConnectionService.getIqParser().bundle(packet);
562 Map<Integer, ECPublicKey> keys = mXmppConnectionService.getIqParser().preKeyPublics(packet);
563 boolean flush = false;
564 if (bundle == null) {
565 Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid bundle:" + packet);
566 bundle = new PreKeyBundle(-1, -1, -1, null, -1, null, null, null);
567 flush = true;
568 }
569 if (keys == null) {
570 Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid prekeys:" + packet);
571 }
572 try {
573 boolean changed = false;
574 // Validate IdentityKey
575 IdentityKeyPair identityKeyPair = axolotlStore.getIdentityKeyPair();
576 if (flush || !identityKeyPair.getPublicKey().equals(bundle.getIdentityKey())) {
577 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding own IdentityKey " + identityKeyPair.getPublicKey() + " to PEP.");
578 changed = true;
579 }
580
581 // Validate signedPreKeyRecord + ID
582 SignedPreKeyRecord signedPreKeyRecord;
583 int numSignedPreKeys = axolotlStore.loadSignedPreKeys().size();
584 try {
585 signedPreKeyRecord = axolotlStore.loadSignedPreKey(bundle.getSignedPreKeyId());
586 if (flush
587 || !bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey())
588 || !Arrays.equals(bundle.getSignedPreKeySignature(), signedPreKeyRecord.getSignature())) {
589 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
590 signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
591 axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
592 changed = true;
593 }
594 } catch (InvalidKeyIdException e) {
595 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
596 signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
597 axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
598 changed = true;
599 }
600
601 // Validate PreKeys
602 Set<PreKeyRecord> preKeyRecords = new HashSet<>();
603 if (keys != null) {
604 for (Integer id : keys.keySet()) {
605 try {
606 PreKeyRecord preKeyRecord = axolotlStore.loadPreKey(id);
607 if (preKeyRecord.getKeyPair().getPublicKey().equals(keys.get(id))) {
608 preKeyRecords.add(preKeyRecord);
609 }
610 } catch (InvalidKeyIdException ignored) {
611 }
612 }
613 }
614 int newKeys = NUM_KEYS_TO_PUBLISH - preKeyRecords.size();
615 if (newKeys > 0) {
616 List<PreKeyRecord> newRecords = KeyHelper.generatePreKeys(
617 axolotlStore.getCurrentPreKeyId() + 1, newKeys);
618 preKeyRecords.addAll(newRecords);
619 for (PreKeyRecord record : newRecords) {
620 axolotlStore.storePreKey(record.getId(), record);
621 }
622 changed = true;
623 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding " + newKeys + " new preKeys to PEP.");
624 }
625
626
627 if (changed) {
628 if (account.getPrivateKeyAlias() != null && Config.X509_VERIFICATION) {
629 mXmppConnectionService.publishDisplayName(account);
630 publishDeviceVerificationAndBundle(signedPreKeyRecord, preKeyRecords, announce, wipe);
631 } else {
632 publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announce, wipe);
633 }
634 } else {
635 Log.d(Config.LOGTAG, getLogprefix(account) + "Bundle " + getOwnDeviceId() + " in PEP was current");
636 if (wipe) {
637 wipeOtherPepDevices();
638 } else if (announce) {
639 Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
640 publishOwnDeviceIdIfNeeded();
641 }
642 }
643 } catch (InvalidKeyException e) {
644 Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage());
645 }
646 }
647 });
648 }
649
650 private void publishDeviceBundle(SignedPreKeyRecord signedPreKeyRecord,
651 Set<PreKeyRecord> preKeyRecords,
652 final boolean announceAfter,
653 final boolean wipe) {
654 IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles(
655 signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(),
656 preKeyRecords, getOwnDeviceId());
657 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing: " + publish);
658 mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
659 @Override
660 public void onIqPacketReceived(Account account, IqPacket packet) {
661 if (packet.getType() == IqPacket.TYPE.RESULT) {
662 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Successfully published bundle. ");
663 if (wipe) {
664 wipeOtherPepDevices();
665 } else if (announceAfter) {
666 Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
667 publishOwnDeviceIdIfNeeded();
668 }
669 } else if (packet.getType() == IqPacket.TYPE.ERROR) {
670 pepBroken = true;
671 Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing bundle: " + packet.findChild("error"));
672 }
673 }
674 });
675 }
676
677 public enum AxolotlCapability {
678 FULL,
679 MISSING_PRESENCE,
680 MISSING_KEYS,
681 WRONG_CONFIGURATION,
682 NO_MEMBERS
683 }
684
685 public boolean isConversationAxolotlCapable(Conversation conversation) {
686 return isConversationAxolotlCapableDetailed(conversation).first == AxolotlCapability.FULL;
687 }
688
689 public Pair<AxolotlCapability,Jid> isConversationAxolotlCapableDetailed(Conversation conversation) {
690 if (conversation.getMode() == Conversation.MODE_SINGLE
691 || (conversation.getMucOptions().membersOnly() && conversation.getMucOptions().nonanonymous())) {
692 final List<Jid> jids = getCryptoTargets(conversation);
693 for(Jid jid : jids) {
694 if (!hasAny(jid) && (!deviceIds.containsKey(jid) || deviceIds.get(jid).isEmpty())) {
695 if (conversation.getAccount().getRoster().getContact(jid).mutualPresenceSubscription()) {
696 return new Pair<>(AxolotlCapability.MISSING_KEYS,jid);
697 } else {
698 return new Pair<>(AxolotlCapability.MISSING_PRESENCE,jid);
699 }
700 }
701 }
702 if (jids.size() > 0) {
703 return new Pair<>(AxolotlCapability.FULL, null);
704 } else {
705 return new Pair<>(AxolotlCapability.NO_MEMBERS, null);
706 }
707 } else {
708 return new Pair<>(AxolotlCapability.WRONG_CONFIGURATION, null);
709 }
710 }
711
712 public List<Jid> getCryptoTargets(Conversation conversation) {
713 final List<Jid> jids;
714 if (conversation.getMode() == Conversation.MODE_SINGLE) {
715 jids = Arrays.asList(conversation.getJid().toBareJid());
716 } else {
717 jids = conversation.getMucOptions().getMembers();
718 }
719 return jids;
720 }
721
722 public FingerprintStatus getFingerprintTrust(String fingerprint) {
723 return axolotlStore.getFingerprintStatus(fingerprint);
724 }
725
726 public X509Certificate getFingerprintCertificate(String fingerprint) {
727 return axolotlStore.getFingerprintCertificate(fingerprint);
728 }
729
730 public void setFingerprintTrust(String fingerprint, FingerprintStatus status) {
731 axolotlStore.setFingerprintStatus(fingerprint, status);
732 }
733
734 private void verifySessionWithPEP(final XmppAxolotlSession session) {
735 Log.d(Config.LOGTAG, "trying to verify fresh session (" + session.getRemoteAddress().getName() + ") with pep");
736 final AxolotlAddress address = session.getRemoteAddress();
737 final IdentityKey identityKey = session.getIdentityKey();
738 try {
739 IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(Jid.fromString(address.getName()), address.getDeviceId());
740 mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
741 @Override
742 public void onIqPacketReceived(Account account, IqPacket packet) {
743 Pair<X509Certificate[],byte[]> verification = mXmppConnectionService.getIqParser().verification(packet);
744 if (verification != null) {
745 try {
746 Signature verifier = Signature.getInstance("sha256WithRSA");
747 verifier.initVerify(verification.first[0]);
748 verifier.update(identityKey.serialize());
749 if (verifier.verify(verification.second)) {
750 try {
751 mXmppConnectionService.getMemorizingTrustManager().getNonInteractive().checkClientTrusted(verification.first, "RSA");
752 String fingerprint = session.getFingerprint();
753 Log.d(Config.LOGTAG, "verified session with x.509 signature. fingerprint was: "+fingerprint);
754 setFingerprintTrust(fingerprint, FingerprintStatus.createActiveVerified(true));
755 axolotlStore.setFingerprintCertificate(fingerprint, verification.first[0]);
756 fetchStatusMap.put(address, FetchStatus.SUCCESS_VERIFIED);
757 Bundle information = CryptoHelper.extractCertificateInformation(verification.first[0]);
758 try {
759 final String cn = information.getString("subject_cn");
760 final Jid jid = Jid.fromString(address.getName());
761 Log.d(Config.LOGTAG,"setting common name for "+jid+" to "+cn);
762 account.getRoster().getContact(jid).setCommonName(cn);
763 } catch (final InvalidJidException ignored) {
764 //ignored
765 }
766 finishBuildingSessionsFromPEP(address);
767 return;
768 } catch (Exception e) {
769 Log.d(Config.LOGTAG,"could not verify certificate");
770 }
771 }
772 } catch (Exception e) {
773 Log.d(Config.LOGTAG, "error during verification " + e.getMessage());
774 }
775 } else {
776 Log.d(Config.LOGTAG,"no verification found");
777 }
778 fetchStatusMap.put(address, FetchStatus.SUCCESS);
779 finishBuildingSessionsFromPEP(address);
780 }
781 });
782 } catch (InvalidJidException e) {
783 fetchStatusMap.put(address, FetchStatus.SUCCESS);
784 finishBuildingSessionsFromPEP(address);
785 }
786 }
787
788 private void finishBuildingSessionsFromPEP(final AxolotlAddress address) {
789 AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toPreppedString(), 0);
790 Map<Integer, FetchStatus> own = fetchStatusMap.getAll(ownAddress);
791 Map<Integer, FetchStatus> remote = fetchStatusMap.getAll(address);
792 if (!own.containsValue(FetchStatus.PENDING) && !remote.containsValue(FetchStatus.PENDING)) {
793 FetchStatus report = null;
794 if (own.containsValue(FetchStatus.SUCCESS) || remote.containsValue(FetchStatus.SUCCESS)) {
795 report = FetchStatus.SUCCESS;
796 } else if (own.containsValue(FetchStatus.SUCCESS_VERIFIED) || remote.containsValue(FetchStatus.SUCCESS_VERIFIED)) {
797 report = FetchStatus.SUCCESS_VERIFIED;
798 } else if (own.containsValue(FetchStatus.SUCCESS_TRUSTED) || remote.containsValue(FetchStatus.SUCCESS_TRUSTED)) {
799 report = FetchStatus.SUCCESS_TRUSTED;
800 } else if (own.containsValue(FetchStatus.ERROR) || remote.containsValue(FetchStatus.ERROR)) {
801 report = FetchStatus.ERROR;
802 }
803 mXmppConnectionService.keyStatusUpdated(report);
804 }
805 Set<Integer> ownDeviceIds = new HashSet<>(getOwnDeviceIds());
806 boolean publish = false;
807 for(Map.Entry<Integer,FetchStatus> entry : own.entrySet()) {
808 if (entry.getValue() == FetchStatus.ERROR && ownDeviceIds.remove(entry.getKey())) {
809 publish = true;
810 Log.d(Config.LOGTAG,account.getJid().toBareJid()+": error fetching own device with id "+entry.getKey()+". removing from annoucement");
811 }
812 }
813 if (publish) {
814 publishOwnDeviceId(ownDeviceIds);
815 }
816 }
817
818 private void buildSessionFromPEP(final AxolotlAddress address) {
819 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new session for " + address.toString());
820 if (address.getDeviceId() == getOwnDeviceId()) {
821 throw new AssertionError("We should NEVER build a session with ourselves. What happened here?!");
822 }
823
824 try {
825 IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(
826 Jid.fromString(address.getName()), address.getDeviceId());
827 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Retrieving bundle: " + bundlesPacket);
828 mXmppConnectionService.sendIqPacket(account, bundlesPacket, new OnIqPacketReceived() {
829
830 @Override
831 public void onIqPacketReceived(Account account, IqPacket packet) {
832 if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
833 fetchStatusMap.put(address, FetchStatus.TIMEOUT);
834 } else if (packet.getType() == IqPacket.TYPE.RESULT) {
835 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received preKey IQ packet, processing...");
836 final IqParser parser = mXmppConnectionService.getIqParser();
837 final List<PreKeyBundle> preKeyBundleList = parser.preKeys(packet);
838 final PreKeyBundle bundle = parser.bundle(packet);
839 if (preKeyBundleList.isEmpty() || bundle == null) {
840 Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "preKey IQ packet invalid: " + packet);
841 fetchStatusMap.put(address, FetchStatus.ERROR);
842 finishBuildingSessionsFromPEP(address);
843 return;
844 }
845 Random random = new Random();
846 final PreKeyBundle preKey = preKeyBundleList.get(random.nextInt(preKeyBundleList.size()));
847 if (preKey == null) {
848 //should never happen
849 fetchStatusMap.put(address, FetchStatus.ERROR);
850 finishBuildingSessionsFromPEP(address);
851 return;
852 }
853
854 final PreKeyBundle preKeyBundle = new PreKeyBundle(0, address.getDeviceId(),
855 preKey.getPreKeyId(), preKey.getPreKey(),
856 bundle.getSignedPreKeyId(), bundle.getSignedPreKey(),
857 bundle.getSignedPreKeySignature(), bundle.getIdentityKey());
858
859 try {
860 SessionBuilder builder = new SessionBuilder(axolotlStore, address);
861 builder.process(preKeyBundle);
862 XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey());
863 sessions.put(address, session);
864 if (Config.X509_VERIFICATION) {
865 verifySessionWithPEP(session);
866 } else {
867 FingerprintStatus status = getFingerprintTrust(bundle.getIdentityKey().getFingerprint().replaceAll("\\s",""));
868 FetchStatus fetchStatus;
869 if (status != null && status.isVerified()) {
870 fetchStatus = FetchStatus.SUCCESS_VERIFIED;
871 } else if (status != null && status.isTrusted()) {
872 fetchStatus = FetchStatus.SUCCESS_TRUSTED;
873 } else {
874 fetchStatus = FetchStatus.SUCCESS;
875 }
876 fetchStatusMap.put(address, fetchStatus);
877 finishBuildingSessionsFromPEP(address);
878 }
879 } catch (UntrustedIdentityException | InvalidKeyException e) {
880 Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error building session for " + address + ": "
881 + e.getClass().getName() + ", " + e.getMessage());
882 fetchStatusMap.put(address, FetchStatus.ERROR);
883 finishBuildingSessionsFromPEP(address);
884 }
885 } else {
886 fetchStatusMap.put(address, FetchStatus.ERROR);
887 Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while building session:" + packet.findChild("error"));
888 finishBuildingSessionsFromPEP(address);
889 }
890 }
891 });
892 } catch (InvalidJidException e) {
893 Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Got address with invalid jid: " + address.getName());
894 }
895 }
896
897 public Set<AxolotlAddress> findDevicesWithoutSession(final Conversation conversation) {
898 Set<AxolotlAddress> addresses = new HashSet<>();
899 for(Jid jid : getCryptoTargets(conversation)) {
900 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Finding devices without session for " + jid);
901 if (deviceIds.get(jid) != null) {
902 for (Integer foreignId : this.deviceIds.get(jid)) {
903 AxolotlAddress address = new AxolotlAddress(jid.toPreppedString(), foreignId);
904 if (sessions.get(address) == null) {
905 IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
906 if (identityKey != null) {
907 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
908 XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
909 sessions.put(address, session);
910 } else {
911 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + jid + ":" + foreignId);
912 if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
913 addresses.add(address);
914 } else {
915 Log.d(Config.LOGTAG, getLogprefix(account) + "skipping over " + address + " because it's broken");
916 }
917 }
918 }
919 }
920 } else {
921 Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Have no target devices in PEP!");
922 }
923 }
924 if (deviceIds.get(account.getJid().toBareJid()) != null) {
925 for (Integer ownId : this.deviceIds.get(account.getJid().toBareJid())) {
926 AxolotlAddress address = new AxolotlAddress(account.getJid().toBareJid().toPreppedString(), ownId);
927 if (sessions.get(address) == null) {
928 IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
929 if (identityKey != null) {
930 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
931 XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
932 sessions.put(address, session);
933 } else {
934 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + account.getJid().toBareJid() + ":" + ownId);
935 if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
936 addresses.add(address);
937 } else {
938 Log.d(Config.LOGTAG,getLogprefix(account)+"skipping over "+address+" because it's broken");
939 }
940 }
941 }
942 }
943 }
944
945 return addresses;
946 }
947
948 public boolean createSessionsIfNeeded(final Conversation conversation) {
949 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Creating axolotl sessions if needed...");
950 boolean newSessions = false;
951 Set<AxolotlAddress> addresses = findDevicesWithoutSession(conversation);
952 for (AxolotlAddress address : addresses) {
953 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Processing device: " + address.toString());
954 FetchStatus status = fetchStatusMap.get(address);
955 if (status == null || status == FetchStatus.TIMEOUT) {
956 fetchStatusMap.put(address, FetchStatus.PENDING);
957 this.buildSessionFromPEP(address);
958 newSessions = true;
959 } else if (status == FetchStatus.PENDING) {
960 newSessions = true;
961 } else {
962 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already fetching bundle for " + address.toString());
963 }
964 }
965
966 return newSessions;
967 }
968
969 public boolean trustedSessionVerified(final Conversation conversation) {
970 Set<XmppAxolotlSession> sessions = findSessionsForConversation(conversation);
971 sessions.addAll(findOwnSessions());
972 boolean verified = false;
973 for(XmppAxolotlSession session : sessions) {
974 if (session.getTrust().isTrustedAndActive()) {
975 if (session.getTrust().getTrust() == FingerprintStatus.Trust.VERIFIED_X509) {
976 verified = true;
977 } else {
978 return false;
979 }
980 }
981 }
982 return verified;
983 }
984
985 public boolean hasPendingKeyFetches(Account account, List<Jid> jids) {
986 AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toPreppedString(), 0);
987 if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)) {
988 return true;
989 }
990 for(Jid jid : jids) {
991 AxolotlAddress foreignAddress = new AxolotlAddress(jid.toBareJid().toPreppedString(), 0);
992 if (fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING)) {
993 return true;
994 }
995 }
996 return false;
997 }
998
999 @Nullable
1000 private XmppAxolotlMessage buildHeader(Conversation conversation) {
1001 final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(
1002 account.getJid().toBareJid(), getOwnDeviceId());
1003
1004 Set<XmppAxolotlSession> remoteSessions = findSessionsForConversation(conversation);
1005 Collection<XmppAxolotlSession> ownSessions = findOwnSessions();
1006 if (remoteSessions.isEmpty()) {
1007 return null;
1008 }
1009 for (XmppAxolotlSession session : remoteSessions) {
1010 axolotlMessage.addDevice(session);
1011 }
1012 for (XmppAxolotlSession session : ownSessions) {
1013 axolotlMessage.addDevice(session);
1014 }
1015
1016 return axolotlMessage;
1017 }
1018
1019 @Nullable
1020 public XmppAxolotlMessage encrypt(Message message) {
1021 XmppAxolotlMessage axolotlMessage = buildHeader(message.getConversation());
1022
1023 if (axolotlMessage != null) {
1024 final String content;
1025 if (message.hasFileOnRemoteHost()) {
1026 content = message.getFileParams().url.toString();
1027 } else {
1028 content = message.getBody();
1029 }
1030 try {
1031 axolotlMessage.encrypt(content);
1032 } catch (CryptoFailedException e) {
1033 Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to encrypt message: " + e.getMessage());
1034 return null;
1035 }
1036 }
1037
1038 return axolotlMessage;
1039 }
1040
1041 public void preparePayloadMessage(final Message message, final boolean delay) {
1042 executor.execute(new Runnable() {
1043 @Override
1044 public void run() {
1045 XmppAxolotlMessage axolotlMessage = encrypt(message);
1046 if (axolotlMessage == null) {
1047 mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED);
1048 //mXmppConnectionService.updateConversationUi();
1049 } else {
1050 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Generated message, caching: " + message.getUuid());
1051 messageCache.put(message.getUuid(), axolotlMessage);
1052 mXmppConnectionService.resendMessage(message, delay);
1053 }
1054 }
1055 });
1056 }
1057
1058 public void prepareKeyTransportMessage(final Conversation conversation, final OnMessageCreatedCallback onMessageCreatedCallback) {
1059 executor.execute(new Runnable() {
1060 @Override
1061 public void run() {
1062 XmppAxolotlMessage axolotlMessage = buildHeader(conversation);
1063 onMessageCreatedCallback.run(axolotlMessage);
1064 }
1065 });
1066 }
1067
1068 public XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message) {
1069 XmppAxolotlMessage axolotlMessage = messageCache.get(message.getUuid());
1070 if (axolotlMessage != null) {
1071 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache hit: " + message.getUuid());
1072 messageCache.remove(message.getUuid());
1073 } else {
1074 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache miss: " + message.getUuid());
1075 }
1076 return axolotlMessage;
1077 }
1078
1079 private XmppAxolotlSession recreateUncachedSession(AxolotlAddress address) {
1080 IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
1081 return (identityKey != null)
1082 ? new XmppAxolotlSession(account, axolotlStore, address, identityKey)
1083 : null;
1084 }
1085
1086 private XmppAxolotlSession getReceivingSession(XmppAxolotlMessage message) {
1087 AxolotlAddress senderAddress = new AxolotlAddress(message.getFrom().toPreppedString(),
1088 message.getSenderDeviceId());
1089 XmppAxolotlSession session = sessions.get(senderAddress);
1090 if (session == null) {
1091 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Account: " + account.getJid() + " No axolotl session found while parsing received message " + message);
1092 session = recreateUncachedSession(senderAddress);
1093 if (session == null) {
1094 session = new XmppAxolotlSession(account, axolotlStore, senderAddress);
1095 }
1096 }
1097 return session;
1098 }
1099
1100 public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message) {
1101 XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null;
1102
1103 XmppAxolotlSession session = getReceivingSession(message);
1104 try {
1105 plaintextMessage = message.decrypt(session, getOwnDeviceId());
1106 Integer preKeyId = session.getPreKeyId();
1107 if (preKeyId != null) {
1108 publishBundlesIfNeeded(false, false);
1109 session.resetPreKeyId();
1110 }
1111 } catch (CryptoFailedException e) {
1112 Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message: " + e.getMessage());
1113 }
1114
1115 if (session.isFresh() && plaintextMessage != null) {
1116 putFreshSession(session);
1117 }
1118
1119 return plaintextMessage;
1120 }
1121
1122 public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message) {
1123 XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage;
1124
1125 XmppAxolotlSession session = getReceivingSession(message);
1126 keyTransportMessage = message.getParameters(session, getOwnDeviceId());
1127
1128 if (session.isFresh() && keyTransportMessage != null) {
1129 putFreshSession(session);
1130 }
1131
1132 return keyTransportMessage;
1133 }
1134
1135 private void putFreshSession(XmppAxolotlSession session) {
1136 Log.d(Config.LOGTAG,"put fresh session");
1137 sessions.put(session);
1138 if (Config.X509_VERIFICATION) {
1139 if (session.getIdentityKey() != null) {
1140 verifySessionWithPEP(session);
1141 } else {
1142 Log.e(Config.LOGTAG,account.getJid().toBareJid()+": identity key was empty after reloading for x509 verification");
1143 }
1144 }
1145 }
1146}