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