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