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 final Set<Integer> PREVIOUSLY_REMOVED_FROM_ANNOUNCEMENT = new HashSet<>();
789
790 private void finishBuildingSessionsFromPEP(final AxolotlAddress address) {
791 AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toPreppedString(), 0);
792 Map<Integer, FetchStatus> own = fetchStatusMap.getAll(ownAddress);
793 Map<Integer, FetchStatus> remote = fetchStatusMap.getAll(address);
794 if (!own.containsValue(FetchStatus.PENDING) && !remote.containsValue(FetchStatus.PENDING)) {
795 FetchStatus report = null;
796 if (own.containsValue(FetchStatus.SUCCESS) || remote.containsValue(FetchStatus.SUCCESS)) {
797 report = FetchStatus.SUCCESS;
798 } else if (own.containsValue(FetchStatus.SUCCESS_VERIFIED) || remote.containsValue(FetchStatus.SUCCESS_VERIFIED)) {
799 report = FetchStatus.SUCCESS_VERIFIED;
800 } else if (own.containsValue(FetchStatus.SUCCESS_TRUSTED) || remote.containsValue(FetchStatus.SUCCESS_TRUSTED)) {
801 report = FetchStatus.SUCCESS_TRUSTED;
802 } else if (own.containsValue(FetchStatus.ERROR) || remote.containsValue(FetchStatus.ERROR)) {
803 report = FetchStatus.ERROR;
804 }
805 mXmppConnectionService.keyStatusUpdated(report);
806 }
807 if (Config.REMOVE_BROKEN_DEVICES) {
808 Set<Integer> ownDeviceIds = new HashSet<>(getOwnDeviceIds());
809 boolean publish = false;
810 for (Map.Entry<Integer, FetchStatus> entry : own.entrySet()) {
811 int id = entry.getKey();
812 if (entry.getValue() == FetchStatus.ERROR && PREVIOUSLY_REMOVED_FROM_ANNOUNCEMENT.add(id) && ownDeviceIds.remove(id)) {
813 publish = true;
814 Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": error fetching own device with id " + id + ". removing from announcement");
815 }
816 }
817 if (publish) {
818 publishOwnDeviceId(ownDeviceIds);
819 }
820 }
821 }
822
823 private void buildSessionFromPEP(final AxolotlAddress address) {
824 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new session for " + address.toString());
825 if (address.getDeviceId() == getOwnDeviceId()) {
826 throw new AssertionError("We should NEVER build a session with ourselves. What happened here?!");
827 }
828
829 try {
830 IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(
831 Jid.fromString(address.getName()), address.getDeviceId());
832 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Retrieving bundle: " + bundlesPacket);
833 mXmppConnectionService.sendIqPacket(account, bundlesPacket, new OnIqPacketReceived() {
834
835 @Override
836 public void onIqPacketReceived(Account account, IqPacket packet) {
837 if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
838 fetchStatusMap.put(address, FetchStatus.TIMEOUT);
839 } else if (packet.getType() == IqPacket.TYPE.RESULT) {
840 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received preKey IQ packet, processing...");
841 final IqParser parser = mXmppConnectionService.getIqParser();
842 final List<PreKeyBundle> preKeyBundleList = parser.preKeys(packet);
843 final PreKeyBundle bundle = parser.bundle(packet);
844 if (preKeyBundleList.isEmpty() || bundle == null) {
845 Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "preKey IQ packet invalid: " + packet);
846 fetchStatusMap.put(address, FetchStatus.ERROR);
847 finishBuildingSessionsFromPEP(address);
848 return;
849 }
850 Random random = new Random();
851 final PreKeyBundle preKey = preKeyBundleList.get(random.nextInt(preKeyBundleList.size()));
852 if (preKey == null) {
853 //should never happen
854 fetchStatusMap.put(address, FetchStatus.ERROR);
855 finishBuildingSessionsFromPEP(address);
856 return;
857 }
858
859 final PreKeyBundle preKeyBundle = new PreKeyBundle(0, address.getDeviceId(),
860 preKey.getPreKeyId(), preKey.getPreKey(),
861 bundle.getSignedPreKeyId(), bundle.getSignedPreKey(),
862 bundle.getSignedPreKeySignature(), bundle.getIdentityKey());
863
864 try {
865 SessionBuilder builder = new SessionBuilder(axolotlStore, address);
866 builder.process(preKeyBundle);
867 XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey());
868 sessions.put(address, session);
869 if (Config.X509_VERIFICATION) {
870 verifySessionWithPEP(session);
871 } else {
872 FingerprintStatus status = getFingerprintTrust(bundle.getIdentityKey().getFingerprint().replaceAll("\\s",""));
873 FetchStatus fetchStatus;
874 if (status != null && status.isVerified()) {
875 fetchStatus = FetchStatus.SUCCESS_VERIFIED;
876 } else if (status != null && status.isTrusted()) {
877 fetchStatus = FetchStatus.SUCCESS_TRUSTED;
878 } else {
879 fetchStatus = FetchStatus.SUCCESS;
880 }
881 fetchStatusMap.put(address, fetchStatus);
882 finishBuildingSessionsFromPEP(address);
883 }
884 } catch (UntrustedIdentityException | InvalidKeyException e) {
885 Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error building session for " + address + ": "
886 + e.getClass().getName() + ", " + e.getMessage());
887 fetchStatusMap.put(address, FetchStatus.ERROR);
888 finishBuildingSessionsFromPEP(address);
889 }
890 } else {
891 fetchStatusMap.put(address, FetchStatus.ERROR);
892 Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while building session:" + packet.findChild("error"));
893 finishBuildingSessionsFromPEP(address);
894 }
895 }
896 });
897 } catch (InvalidJidException e) {
898 Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Got address with invalid jid: " + address.getName());
899 }
900 }
901
902 public Set<AxolotlAddress> findDevicesWithoutSession(final Conversation conversation) {
903 Set<AxolotlAddress> addresses = new HashSet<>();
904 for(Jid jid : getCryptoTargets(conversation)) {
905 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Finding devices without session for " + jid);
906 if (deviceIds.get(jid) != null) {
907 for (Integer foreignId : this.deviceIds.get(jid)) {
908 AxolotlAddress address = new AxolotlAddress(jid.toPreppedString(), foreignId);
909 if (sessions.get(address) == null) {
910 IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
911 if (identityKey != null) {
912 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
913 XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
914 sessions.put(address, session);
915 } else {
916 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + jid + ":" + foreignId);
917 if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
918 addresses.add(address);
919 } else {
920 Log.d(Config.LOGTAG, getLogprefix(account) + "skipping over " + address + " because it's broken");
921 }
922 }
923 }
924 }
925 } else {
926 Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Have no target devices in PEP!");
927 }
928 }
929 if (deviceIds.get(account.getJid().toBareJid()) != null) {
930 for (Integer ownId : this.deviceIds.get(account.getJid().toBareJid())) {
931 AxolotlAddress address = new AxolotlAddress(account.getJid().toBareJid().toPreppedString(), ownId);
932 if (sessions.get(address) == null) {
933 IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
934 if (identityKey != null) {
935 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
936 XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
937 sessions.put(address, session);
938 } else {
939 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + account.getJid().toBareJid() + ":" + ownId);
940 if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
941 addresses.add(address);
942 } else {
943 Log.d(Config.LOGTAG,getLogprefix(account)+"skipping over "+address+" because it's broken");
944 }
945 }
946 }
947 }
948 }
949
950 return addresses;
951 }
952
953 public boolean createSessionsIfNeeded(final Conversation conversation) {
954 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Creating axolotl sessions if needed...");
955 boolean newSessions = false;
956 Set<AxolotlAddress> addresses = findDevicesWithoutSession(conversation);
957 for (AxolotlAddress address : addresses) {
958 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Processing device: " + address.toString());
959 FetchStatus status = fetchStatusMap.get(address);
960 if (status == null || status == FetchStatus.TIMEOUT) {
961 fetchStatusMap.put(address, FetchStatus.PENDING);
962 this.buildSessionFromPEP(address);
963 newSessions = true;
964 } else if (status == FetchStatus.PENDING) {
965 newSessions = true;
966 } else {
967 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already fetching bundle for " + address.toString());
968 }
969 }
970
971 return newSessions;
972 }
973
974 public boolean trustedSessionVerified(final Conversation conversation) {
975 Set<XmppAxolotlSession> sessions = findSessionsForConversation(conversation);
976 sessions.addAll(findOwnSessions());
977 boolean verified = false;
978 for(XmppAxolotlSession session : sessions) {
979 if (session.getTrust().isTrustedAndActive()) {
980 if (session.getTrust().getTrust() == FingerprintStatus.Trust.VERIFIED_X509) {
981 verified = true;
982 } else {
983 return false;
984 }
985 }
986 }
987 return verified;
988 }
989
990 public boolean hasPendingKeyFetches(Account account, List<Jid> jids) {
991 AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toPreppedString(), 0);
992 if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)) {
993 return true;
994 }
995 for(Jid jid : jids) {
996 AxolotlAddress foreignAddress = new AxolotlAddress(jid.toBareJid().toPreppedString(), 0);
997 if (fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING)) {
998 return true;
999 }
1000 }
1001 return false;
1002 }
1003
1004 @Nullable
1005 private XmppAxolotlMessage buildHeader(Conversation conversation) {
1006 final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(
1007 account.getJid().toBareJid(), getOwnDeviceId());
1008
1009 Set<XmppAxolotlSession> remoteSessions = findSessionsForConversation(conversation);
1010 Collection<XmppAxolotlSession> ownSessions = findOwnSessions();
1011 if (remoteSessions.isEmpty()) {
1012 return null;
1013 }
1014 for (XmppAxolotlSession session : remoteSessions) {
1015 axolotlMessage.addDevice(session);
1016 }
1017 for (XmppAxolotlSession session : ownSessions) {
1018 axolotlMessage.addDevice(session);
1019 }
1020
1021 return axolotlMessage;
1022 }
1023
1024 @Nullable
1025 public XmppAxolotlMessage encrypt(Message message) {
1026 XmppAxolotlMessage axolotlMessage = buildHeader(message.getConversation());
1027
1028 if (axolotlMessage != null) {
1029 final String content;
1030 if (message.hasFileOnRemoteHost()) {
1031 content = message.getFileParams().url.toString();
1032 } else {
1033 content = message.getBody();
1034 }
1035 try {
1036 axolotlMessage.encrypt(content);
1037 } catch (CryptoFailedException e) {
1038 Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to encrypt message: " + e.getMessage());
1039 return null;
1040 }
1041 }
1042
1043 return axolotlMessage;
1044 }
1045
1046 public void preparePayloadMessage(final Message message, final boolean delay) {
1047 executor.execute(new Runnable() {
1048 @Override
1049 public void run() {
1050 XmppAxolotlMessage axolotlMessage = encrypt(message);
1051 if (axolotlMessage == null) {
1052 mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED);
1053 //mXmppConnectionService.updateConversationUi();
1054 } else {
1055 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Generated message, caching: " + message.getUuid());
1056 messageCache.put(message.getUuid(), axolotlMessage);
1057 mXmppConnectionService.resendMessage(message, delay);
1058 }
1059 }
1060 });
1061 }
1062
1063 public void prepareKeyTransportMessage(final Conversation conversation, final OnMessageCreatedCallback onMessageCreatedCallback) {
1064 executor.execute(new Runnable() {
1065 @Override
1066 public void run() {
1067 XmppAxolotlMessage axolotlMessage = buildHeader(conversation);
1068 onMessageCreatedCallback.run(axolotlMessage);
1069 }
1070 });
1071 }
1072
1073 public XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message) {
1074 XmppAxolotlMessage axolotlMessage = messageCache.get(message.getUuid());
1075 if (axolotlMessage != null) {
1076 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache hit: " + message.getUuid());
1077 messageCache.remove(message.getUuid());
1078 } else {
1079 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache miss: " + message.getUuid());
1080 }
1081 return axolotlMessage;
1082 }
1083
1084 private XmppAxolotlSession recreateUncachedSession(AxolotlAddress address) {
1085 IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
1086 return (identityKey != null)
1087 ? new XmppAxolotlSession(account, axolotlStore, address, identityKey)
1088 : null;
1089 }
1090
1091 private XmppAxolotlSession getReceivingSession(XmppAxolotlMessage message) {
1092 AxolotlAddress senderAddress = new AxolotlAddress(message.getFrom().toPreppedString(),
1093 message.getSenderDeviceId());
1094 XmppAxolotlSession session = sessions.get(senderAddress);
1095 if (session == null) {
1096 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Account: " + account.getJid() + " No axolotl session found while parsing received message " + message);
1097 session = recreateUncachedSession(senderAddress);
1098 if (session == null) {
1099 session = new XmppAxolotlSession(account, axolotlStore, senderAddress);
1100 }
1101 }
1102 return session;
1103 }
1104
1105 public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message) {
1106 XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null;
1107
1108 XmppAxolotlSession session = getReceivingSession(message);
1109 try {
1110 plaintextMessage = message.decrypt(session, getOwnDeviceId());
1111 Integer preKeyId = session.getPreKeyId();
1112 if (preKeyId != null) {
1113 publishBundlesIfNeeded(false, false);
1114 session.resetPreKeyId();
1115 }
1116 } catch (CryptoFailedException e) {
1117 Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message: " + e.getMessage());
1118 }
1119
1120 if (session.isFresh() && plaintextMessage != null) {
1121 putFreshSession(session);
1122 }
1123
1124 return plaintextMessage;
1125 }
1126
1127 public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message) {
1128 XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage;
1129
1130 XmppAxolotlSession session = getReceivingSession(message);
1131 keyTransportMessage = message.getParameters(session, getOwnDeviceId());
1132
1133 if (session.isFresh() && keyTransportMessage != null) {
1134 putFreshSession(session);
1135 }
1136
1137 return keyTransportMessage;
1138 }
1139
1140 private void putFreshSession(XmppAxolotlSession session) {
1141 Log.d(Config.LOGTAG,"put fresh session");
1142 sessions.put(session);
1143 if (Config.X509_VERIFICATION) {
1144 if (session.getIdentityKey() != null) {
1145 verifySessionWithPEP(session);
1146 } else {
1147 Log.e(Config.LOGTAG,account.getJid().toBareJid()+": identity key was empty after reloading for x509 verification");
1148 }
1149 }
1150 }
1151}