1package eu.siacs.conversations.crypto.axolotl;
2
3import static eu.siacs.conversations.utils.Random.SECURE_RANDOM;
4
5import android.os.Bundle;
6import android.security.KeyChain;
7import android.util.Log;
8import android.util.Pair;
9import androidx.annotation.NonNull;
10import androidx.annotation.Nullable;
11import com.google.common.collect.ImmutableList;
12import com.google.common.collect.ImmutableMap;
13import com.google.common.collect.ImmutableSet;
14import com.google.common.util.concurrent.Futures;
15import com.google.common.util.concurrent.ListenableFuture;
16import com.google.common.util.concurrent.MoreExecutors;
17import com.google.common.util.concurrent.SettableFuture;
18import eu.siacs.conversations.Config;
19import eu.siacs.conversations.entities.Account;
20import eu.siacs.conversations.entities.Contact;
21import eu.siacs.conversations.entities.Conversation;
22import eu.siacs.conversations.entities.Message;
23import eu.siacs.conversations.parser.IqParser;
24import eu.siacs.conversations.services.XmppConnectionService;
25import eu.siacs.conversations.utils.CryptoHelper;
26import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
27import eu.siacs.conversations.xml.Element;
28import eu.siacs.conversations.xml.Namespace;
29import eu.siacs.conversations.xmpp.Jid;
30import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded;
31import eu.siacs.conversations.xmpp.jingle.DescriptionTransport;
32import eu.siacs.conversations.xmpp.jingle.OmemoVerification;
33import eu.siacs.conversations.xmpp.jingle.OmemoVerifiedRtpContentMap;
34import eu.siacs.conversations.xmpp.jingle.RtpContentMap;
35import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo;
36import eu.siacs.conversations.xmpp.jingle.stanzas.OmemoVerifiedIceUdpTransportInfo;
37import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription;
38import eu.siacs.conversations.xmpp.pep.PublishOptions;
39import im.conversations.android.xmpp.model.stanza.Iq;
40import java.security.PrivateKey;
41import java.security.Security;
42import java.security.Signature;
43import java.security.cert.X509Certificate;
44import java.util.ArrayList;
45import java.util.Arrays;
46import java.util.Collection;
47import java.util.Collections;
48import java.util.HashMap;
49import java.util.HashSet;
50import java.util.Iterator;
51import java.util.List;
52import java.util.Map;
53import java.util.Random;
54import java.util.Set;
55import java.util.concurrent.atomic.AtomicBoolean;
56import org.bouncycastle.jce.provider.BouncyCastleProvider;
57import org.whispersystems.libsignal.IdentityKey;
58import org.whispersystems.libsignal.IdentityKeyPair;
59import org.whispersystems.libsignal.InvalidKeyException;
60import org.whispersystems.libsignal.InvalidKeyIdException;
61import org.whispersystems.libsignal.SessionBuilder;
62import org.whispersystems.libsignal.SignalProtocolAddress;
63import org.whispersystems.libsignal.UntrustedIdentityException;
64import org.whispersystems.libsignal.ecc.ECPublicKey;
65import org.whispersystems.libsignal.state.PreKeyBundle;
66import org.whispersystems.libsignal.state.PreKeyRecord;
67import org.whispersystems.libsignal.state.SignedPreKeyRecord;
68import org.whispersystems.libsignal.util.KeyHelper;
69
70public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
71
72 public static final String PEP_PREFIX = "eu.siacs.conversations.axolotl";
73 public static final String PEP_DEVICE_LIST = PEP_PREFIX + ".devicelist";
74 public static final String PEP_DEVICE_LIST_NOTIFY = PEP_DEVICE_LIST + "+notify";
75 public static final String PEP_BUNDLES = PEP_PREFIX + ".bundles";
76 public static final String PEP_VERIFICATION = PEP_PREFIX + ".verification";
77 public static final String PEP_OMEMO_WHITELISTED = PEP_PREFIX + ".whitelisted";
78
79 public static final String LOGPREFIX = "AxolotlService";
80
81 private static final int NUM_KEYS_TO_PUBLISH = 100;
82 private static final int publishTriesThreshold = 3;
83
84 private final Account account;
85 private final XmppConnectionService mXmppConnectionService;
86 private final SQLiteAxolotlStore axolotlStore;
87 private final SessionMap sessions;
88 private final Map<Jid, Set<Integer>> deviceIds;
89 private final Map<String, XmppAxolotlMessage> messageCache;
90 private final FetchStatusMap fetchStatusMap;
91 private final Map<Jid, Boolean> fetchDeviceListStatus = new HashMap<>();
92 private final HashMap<Jid, List<OnDeviceIdsFetched>> fetchDeviceIdsMap = new HashMap<>();
93 private final SerialSingleThreadExecutor executor;
94 private final Set<SignalProtocolAddress> healingAttempts = new HashSet<>();
95 private final HashSet<Integer> cleanedOwnDeviceIds = new HashSet<>();
96 private final Set<Integer> PREVIOUSLY_REMOVED_FROM_ANNOUNCEMENT = new HashSet<>();
97 private int numPublishTriesOnEmptyPep = 0;
98 private boolean pepBroken = false;
99 private int lastDeviceListNotificationHash = 0;
100 private final Set<XmppAxolotlSession> postponedSessions =
101 new HashSet<>(); // sessions stored here will receive after mam catchup treatment
102 private final Set<SignalProtocolAddress> postponedHealing =
103 new HashSet<>(); // addresses stored here will need a healing notification after mam
104 // catchup
105 private final AtomicBoolean changeAccessMode = new AtomicBoolean(false);
106
107 public AxolotlService(Account account, XmppConnectionService connectionService) {
108 if (account == null || connectionService == null) {
109 throw new IllegalArgumentException("account and service cannot be null");
110 }
111 if (Security.getProvider("BC") == null) {
112 Security.addProvider(new BouncyCastleProvider());
113 }
114 this.mXmppConnectionService = connectionService;
115 this.account = account;
116 this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService);
117 this.deviceIds = new HashMap<>();
118 this.messageCache = new HashMap<>();
119 this.sessions = new SessionMap(mXmppConnectionService, axolotlStore, account);
120 this.fetchStatusMap = new FetchStatusMap();
121 this.executor = new SerialSingleThreadExecutor("Axolotl");
122 }
123
124 public static String getLogprefix(Account account) {
125 return LOGPREFIX + " (" + account.getJid().asBareJid().toString() + "): ";
126 }
127
128 @Override
129 public void onAdvancedStreamFeaturesAvailable(Account account) {
130 if (Config.supportOmemo()
131 && account.getXmppConnection() != null
132 && account.getXmppConnection().getFeatures().pep()) {
133 publishBundlesIfNeeded(true, false);
134 } else {
135 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": skipping OMEMO initialization");
136 }
137 }
138
139 private boolean hasErrorFetchingDeviceList(Jid jid) {
140 Boolean status = fetchDeviceListStatus.get(jid);
141 return status != null && !status;
142 }
143
144 public boolean hasErrorFetchingDeviceList(List<Jid> jids) {
145 for (Jid jid : jids) {
146 if (hasErrorFetchingDeviceList(jid)) {
147 return true;
148 }
149 }
150 return false;
151 }
152
153 public boolean fetchMapHasErrors(List<Jid> jids) {
154 for (Jid jid : jids) {
155 if (deviceIds.get(jid) != null) {
156 for (Integer foreignId : this.deviceIds.get(jid)) {
157 SignalProtocolAddress address =
158 new SignalProtocolAddress(jid.toString(), foreignId);
159 if (fetchStatusMap.getAll(address.getName()).containsValue(FetchStatus.ERROR)) {
160 return true;
161 }
162 }
163 }
164 }
165 return false;
166 }
167
168 public void preVerifyFingerprint(Contact contact, String fingerprint) {
169 axolotlStore.preVerifyFingerprint(
170 contact.getAccount(), contact.getJid().asBareJid().toString(), fingerprint);
171 }
172
173 public void preVerifyFingerprint(Account account, String fingerprint) {
174 axolotlStore.preVerifyFingerprint(
175 account, account.getJid().asBareJid().toString(), fingerprint);
176 }
177
178 public boolean hasVerifiedKeys(String name) {
179 for (XmppAxolotlSession session : this.sessions.getAll(name).values()) {
180 if (session.getTrust().isVerified()) {
181 return true;
182 }
183 }
184 return false;
185 }
186
187 public String getOwnFingerprint() {
188 return CryptoHelper.bytesToHex(
189 axolotlStore.getIdentityKeyPair().getPublicKey().serialize());
190 }
191
192 public Set<IdentityKey> getKeysWithTrust(FingerprintStatus status) {
193 return axolotlStore.getContactKeysWithTrust(
194 account.getJid().asBareJid().toString(), status);
195 }
196
197 public Set<IdentityKey> getKeysWithTrust(FingerprintStatus status, Jid jid) {
198 return axolotlStore.getContactKeysWithTrust(jid.asBareJid().toString(), status);
199 }
200
201 public Set<IdentityKey> getKeysWithTrust(FingerprintStatus status, List<Jid> jids) {
202 Set<IdentityKey> keys = new HashSet<>();
203 for (Jid jid : jids) {
204 keys.addAll(axolotlStore.getContactKeysWithTrust(jid.toString(), status));
205 }
206 return keys;
207 }
208
209 public Set<Jid> findCounterpartsBySourceId(int sid) {
210 return sessions.findCounterpartsForSourceId(sid);
211 }
212
213 public long getNumTrustedKeys(Jid jid) {
214 return axolotlStore.getContactNumTrustedKeys(jid.asBareJid().toString());
215 }
216
217 public boolean anyTargetHasNoTrustedKeys(List<Jid> jids) {
218 for (Jid jid : jids) {
219 if (axolotlStore.getContactNumTrustedKeys(jid.asBareJid().toString()) == 0) {
220 return true;
221 }
222 }
223 return false;
224 }
225
226 private SignalProtocolAddress getAddressForJid(Jid jid) {
227 return new SignalProtocolAddress(jid.toString(), 0);
228 }
229
230 public Collection<XmppAxolotlSession> findOwnSessions() {
231 SignalProtocolAddress ownAddress = getAddressForJid(account.getJid().asBareJid());
232 ArrayList<XmppAxolotlSession> s =
233 new ArrayList<>(this.sessions.getAll(ownAddress.getName()).values());
234 Collections.sort(s);
235 return s;
236 }
237
238 public Collection<XmppAxolotlSession> findSessionsForContact(Contact contact) {
239 SignalProtocolAddress contactAddress = getAddressForJid(contact.getJid());
240 ArrayList<XmppAxolotlSession> s =
241 new ArrayList<>(this.sessions.getAll(contactAddress.getName()).values());
242 Collections.sort(s);
243 return s;
244 }
245
246 private Set<XmppAxolotlSession> findSessionsForConversation(Conversation conversation) {
247 if (conversation.getContact().isSelf()) {
248 // will be added in findOwnSessions()
249 return Collections.emptySet();
250 }
251 HashSet<XmppAxolotlSession> sessions = new HashSet<>();
252 for (Jid jid : conversation.getAcceptedCryptoTargets()) {
253 sessions.addAll(this.sessions.getAll(getAddressForJid(jid).getName()).values());
254 }
255 return sessions;
256 }
257
258 private boolean hasAny(Jid jid) {
259 return sessions.hasAny(getAddressForJid(jid));
260 }
261
262 public boolean isPepBroken() {
263 return this.pepBroken;
264 }
265
266 public void resetBrokenness() {
267 this.pepBroken = false;
268 this.numPublishTriesOnEmptyPep = 0;
269 this.lastDeviceListNotificationHash = 0;
270 this.healingAttempts.clear();
271 }
272
273 public void clearErrorsInFetchStatusMap(Jid jid) {
274 fetchStatusMap.clearErrorFor(jid);
275 fetchDeviceListStatus.remove(jid);
276 }
277
278 public void regenerateKeys(boolean wipeOther) {
279 axolotlStore.regenerate();
280 sessions.clear();
281 fetchStatusMap.clear();
282 fetchDeviceIdsMap.clear();
283 fetchDeviceListStatus.clear();
284 publishBundlesIfNeeded(true, wipeOther);
285 }
286
287 public void destroy() {
288 Log.d(
289 Config.LOGTAG,
290 account.getJid().asBareJid()
291 + ": destroying old axolotl service. no longer in use");
292 mXmppConnectionService.databaseBackend.wipeAxolotlDb(account);
293 }
294
295 public AxolotlService makeNew() {
296 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": make new axolotl service");
297 return new AxolotlService(this.account, this.mXmppConnectionService);
298 }
299
300 public int getOwnDeviceId() {
301 return axolotlStore.getLocalRegistrationId();
302 }
303
304 public SignalProtocolAddress getOwnAxolotlAddress() {
305 return new SignalProtocolAddress(account.getJid().asBareJid().toString(), getOwnDeviceId());
306 }
307
308 public Set<Integer> getOwnDeviceIds() {
309 return this.deviceIds.get(account.getJid().asBareJid());
310 }
311
312 public void registerDevices(final Jid jid, @NonNull final Set<Integer> deviceIds) {
313 final int hash = deviceIds.hashCode();
314 final boolean me = jid.asBareJid().equals(account.getJid().asBareJid());
315 if (me) {
316 if (hash != 0 && hash == this.lastDeviceListNotificationHash) {
317 Log.d(
318 Config.LOGTAG,
319 account.getJid().asBareJid() + ": ignoring duplicate own device id list");
320 return;
321 }
322 this.lastDeviceListNotificationHash = hash;
323 }
324 boolean needsPublishing = me && !deviceIds.contains(getOwnDeviceId());
325 if (me) {
326 deviceIds.remove(getOwnDeviceId());
327 }
328 final Set<Integer> expiredDevices =
329 new HashSet<>(axolotlStore.getSubDeviceSessions(jid.asBareJid().toString()));
330 expiredDevices.removeAll(deviceIds);
331 for (Integer deviceId : expiredDevices) {
332 SignalProtocolAddress address =
333 new SignalProtocolAddress(jid.asBareJid().toString(), deviceId);
334 XmppAxolotlSession session = sessions.get(address);
335 if (session != null && session.getFingerprint() != null) {
336 if (session.getTrust().isActive()) {
337 session.setTrust(session.getTrust().toInactive());
338 }
339 }
340 }
341 final Set<Integer> newDevices = ImmutableSet.copyOf(deviceIds);
342 for (final Integer deviceId : newDevices) {
343 SignalProtocolAddress address =
344 new SignalProtocolAddress(jid.asBareJid().toString(), deviceId);
345 XmppAxolotlSession session = sessions.get(address);
346 if (session != null && session.getFingerprint() != null) {
347 if (!session.getTrust().isActive()) {
348 Log.d(
349 Config.LOGTAG,
350 "reactivating device with fingerprint " + session.getFingerprint());
351 session.setTrust(session.getTrust().toActive());
352 }
353 }
354 }
355 if (me) {
356 if (Config.OMEMO_AUTO_EXPIRY != 0) {
357 needsPublishing |= deviceIds.removeAll(getExpiredDevices());
358 }
359 needsPublishing |= this.changeAccessMode.get();
360 for (final Integer deviceId : deviceIds) {
361 SignalProtocolAddress ownDeviceAddress =
362 new SignalProtocolAddress(jid.asBareJid().toString(), deviceId);
363 if (sessions.get(ownDeviceAddress) == null) {
364 FetchStatus status = fetchStatusMap.get(ownDeviceAddress);
365 if (status == null || status == FetchStatus.TIMEOUT) {
366 fetchStatusMap.put(ownDeviceAddress, FetchStatus.PENDING);
367 this.buildSessionFromPEP(ownDeviceAddress);
368 }
369 }
370 }
371 if (needsPublishing) {
372 // do not run next device list update notification through de-duplication (might get
373 // skipped by CSI)
374 this.lastDeviceListNotificationHash = 0;
375 publishOwnDeviceId(deviceIds);
376 }
377 }
378 final Set<Integer> oldSet = this.deviceIds.get(jid);
379 final boolean changed = oldSet == null || oldSet.hashCode() != hash;
380 this.deviceIds.put(jid, deviceIds);
381 if (changed) {
382 mXmppConnectionService.updateConversationUi(); // update the lock icon
383 mXmppConnectionService.keyStatusUpdated(null);
384 if (me) {
385 mXmppConnectionService.updateAccountUi();
386 }
387 } else {
388 Log.d(Config.LOGTAG, "skipped device list update because it hasn't changed");
389 }
390 }
391
392 public void wipeOtherPepDevices() {
393 if (pepBroken) {
394 Log.d(
395 Config.LOGTAG,
396 getLogprefix(account)
397 + "wipeOtherPepDevices called, but PEP is broken. Ignoring... ");
398 return;
399 }
400 Set<Integer> deviceIds = new HashSet<>();
401 deviceIds.add(getOwnDeviceId());
402 publishDeviceIdsAndRefineAccessModel(deviceIds);
403 }
404
405 public void distrustFingerprint(final String fingerprint) {
406 final String fp = fingerprint.replaceAll("\\s", "");
407 final FingerprintStatus fingerprintStatus = axolotlStore.getFingerprintStatus(fp);
408 axolotlStore.setFingerprintStatus(fp, fingerprintStatus.toUntrusted());
409 }
410
411 private void publishOwnDeviceIdIfNeeded() {
412 if (pepBroken) {
413 Log.d(
414 Config.LOGTAG,
415 getLogprefix(account)
416 + "publishOwnDeviceIdIfNeeded called, but PEP is broken. Ignoring... ");
417 return;
418 }
419 Iq packet =
420 mXmppConnectionService
421 .getIqGenerator()
422 .retrieveDeviceIds(account.getJid().asBareJid());
423 mXmppConnectionService.sendIqPacket(
424 account,
425 packet,
426 response -> {
427 if (response.getType() == Iq.Type.TIMEOUT) {
428 Log.d(
429 Config.LOGTAG,
430 getLogprefix(account)
431 + "Timeout received while retrieving own Device Ids.");
432 } else {
433 // TODO consider calling registerDevices only after item-not-found to
434 // account for broken PEPs
435 // TODO use new API
436 final Element item = IqParser.getItem(response);
437 final Set<Integer> deviceIds = IqParser.deviceIds(item);
438 Log.d(
439 Config.LOGTAG,
440 account.getJid().asBareJid()
441 + ": retrieved own device list: "
442 + deviceIds);
443 registerDevices(account.getJid().asBareJid(), deviceIds);
444 }
445 });
446 }
447
448 private Set<Integer> getExpiredDevices() {
449 Set<Integer> devices = new HashSet<>();
450 for (XmppAxolotlSession session : findOwnSessions()) {
451 if (session.getTrust().isActive()) {
452 long diff = System.currentTimeMillis() - session.getTrust().getLastActivation();
453 if (diff > Config.OMEMO_AUTO_EXPIRY) {
454 long lastMessageDiff =
455 System.currentTimeMillis()
456 - mXmppConnectionService.databaseBackend
457 .getLastTimeFingerprintUsed(
458 account, session.getFingerprint());
459 long hours = Math.round(lastMessageDiff / (1000 * 60.0 * 60.0));
460 if (lastMessageDiff > Config.OMEMO_AUTO_EXPIRY) {
461 devices.add(session.getRemoteAddress().getDeviceId());
462 session.setTrust(session.getTrust().toInactive());
463 Log.d(
464 Config.LOGTAG,
465 account.getJid().asBareJid()
466 + ": added own device "
467 + session.getFingerprint()
468 + " to list of expired devices. Last message received "
469 + hours
470 + " hours ago");
471 } else {
472 Log.d(
473 Config.LOGTAG,
474 account.getJid().asBareJid()
475 + ": own device "
476 + session.getFingerprint()
477 + " was active "
478 + hours
479 + " hours ago");
480 }
481 } // TODO print last activation diff
482 }
483 }
484 return devices;
485 }
486
487 private void publishOwnDeviceId(final Set<Integer> deviceIds) {
488 final Set<Integer> deviceIdsCopy = new HashSet<>(deviceIds);
489 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "publishing own device ids");
490 if (deviceIdsCopy.isEmpty()) {
491 if (numPublishTriesOnEmptyPep >= publishTriesThreshold) {
492 Log.w(
493 Config.LOGTAG,
494 getLogprefix(account)
495 + "Own device publish attempt threshold exceeded, aborting...");
496 pepBroken = true;
497 return;
498 } else {
499 numPublishTriesOnEmptyPep++;
500 Log.w(
501 Config.LOGTAG,
502 getLogprefix(account)
503 + "Own device list empty, attempting to publish (try "
504 + numPublishTriesOnEmptyPep
505 + ")");
506 }
507 } else {
508 numPublishTriesOnEmptyPep = 0;
509 }
510 deviceIdsCopy.add(getOwnDeviceId());
511 publishDeviceIdsAndRefineAccessModel(deviceIdsCopy);
512 }
513
514 private void publishDeviceIdsAndRefineAccessModel(Set<Integer> ids) {
515 publishDeviceIdsAndRefineAccessModel(ids, true);
516 }
517
518 private void publishDeviceIdsAndRefineAccessModel(
519 final Set<Integer> ids, final boolean firstAttempt) {
520 final Bundle publishOptions =
521 account.getXmppConnection().getFeatures().pepPublishOptions()
522 ? PublishOptions.openAccess()
523 : null;
524 final var publish =
525 mXmppConnectionService.getIqGenerator().publishDeviceIds(ids, publishOptions);
526 mXmppConnectionService.sendIqPacket(
527 account,
528 publish,
529 response -> {
530 final Element error =
531 response.getType() == Iq.Type.ERROR
532 ? response.findChild("error")
533 : null;
534 final boolean preConditionNotMet = PublishOptions.preconditionNotMet(response);
535 if (firstAttempt && preConditionNotMet) {
536 Log.d(
537 Config.LOGTAG,
538 account.getJid().asBareJid()
539 + ": precondition wasn't met for device list. pushing node"
540 + " configuration");
541 mXmppConnectionService.pushNodeConfiguration(
542 account,
543 AxolotlService.PEP_DEVICE_LIST,
544 publishOptions,
545 new XmppConnectionService.OnConfigurationPushed() {
546 @Override
547 public void onPushSucceeded() {
548 publishDeviceIdsAndRefineAccessModel(ids, false);
549 }
550
551 @Override
552 public void onPushFailed() {
553 publishDeviceIdsAndRefineAccessModel(ids, false);
554 }
555 });
556 } else {
557 if (AxolotlService.this.changeAccessMode.compareAndSet(true, false)) {
558 Log.d(
559 Config.LOGTAG,
560 account.getJid().asBareJid() + ": done changing access mode");
561 account.setOption(Account.OPTION_REQUIRES_ACCESS_MODE_CHANGE, false);
562 mXmppConnectionService.databaseBackend.updateAccount(account);
563 }
564 if (response.getType() == Iq.Type.ERROR) {
565 if (preConditionNotMet) {
566 Log.d(
567 Config.LOGTAG,
568 account.getJid().asBareJid()
569 + ": device list pre condition still not met on"
570 + " second attempt");
571 } else if (error != null) {
572 pepBroken = true;
573 Log.d(
574 Config.LOGTAG,
575 getLogprefix(account)
576 + "Error received while publishing own device id"
577 + response.findChild("error"));
578 }
579 }
580 }
581 });
582 }
583
584 public void publishDeviceVerificationAndBundle(
585 final SignedPreKeyRecord signedPreKeyRecord,
586 final Set<PreKeyRecord> preKeyRecords,
587 final boolean announceAfter,
588 final boolean wipe) {
589 try {
590 IdentityKey axolotlPublicKey = axolotlStore.getIdentityKeyPair().getPublicKey();
591 PrivateKey x509PrivateKey =
592 KeyChain.getPrivateKey(mXmppConnectionService, account.getPrivateKeyAlias());
593 X509Certificate[] chain =
594 KeyChain.getCertificateChain(
595 mXmppConnectionService, account.getPrivateKeyAlias());
596 Signature verifier = Signature.getInstance("sha256WithRSA");
597 verifier.initSign(x509PrivateKey, SECURE_RANDOM);
598 verifier.update(axolotlPublicKey.serialize());
599 byte[] signature = verifier.sign();
600 final Iq packet =
601 mXmppConnectionService
602 .getIqGenerator()
603 .publishVerification(signature, chain, getOwnDeviceId());
604 Log.d(
605 Config.LOGTAG,
606 AxolotlService.getLogprefix(account)
607 + ": publish verification for device "
608 + getOwnDeviceId());
609 mXmppConnectionService.sendIqPacket(
610 account,
611 packet,
612 response -> {
613 String node = AxolotlService.PEP_VERIFICATION + ":" + getOwnDeviceId();
614 mXmppConnectionService.pushNodeConfiguration(
615 account,
616 node,
617 PublishOptions.openAccess(),
618 new XmppConnectionService.OnConfigurationPushed() {
619 @Override
620 public void onPushSucceeded() {
621 Log.d(
622 Config.LOGTAG,
623 getLogprefix(account)
624 + "configured verification node to be world"
625 + " readable");
626 publishDeviceBundle(
627 signedPreKeyRecord,
628 preKeyRecords,
629 announceAfter,
630 wipe);
631 }
632
633 @Override
634 public void onPushFailed() {
635 Log.d(
636 Config.LOGTAG,
637 getLogprefix(account)
638 + "unable to set access model on"
639 + " verification node");
640 publishDeviceBundle(
641 signedPreKeyRecord,
642 preKeyRecords,
643 announceAfter,
644 wipe);
645 }
646 });
647 });
648 } catch (Exception e) {
649 e.printStackTrace();
650 }
651 }
652
653 public void publishBundlesIfNeeded(final boolean announce, final boolean wipe) {
654 if (pepBroken) {
655 Log.d(
656 Config.LOGTAG,
657 getLogprefix(account)
658 + "publishBundlesIfNeeded called, but PEP is broken. Ignoring... ");
659 return;
660 }
661
662 if (account.getXmppConnection().getFeatures().pepPublishOptions()) {
663 this.changeAccessMode.set(
664 account.isOptionSet(Account.OPTION_REQUIRES_ACCESS_MODE_CHANGE));
665 } else {
666 if (account.setOption(Account.OPTION_REQUIRES_ACCESS_MODE_CHANGE, true)) {
667 Log.d(
668 Config.LOGTAG,
669 account.getJid().asBareJid()
670 + ": server doesn’t support publish-options. setting for later"
671 + " access mode change");
672 mXmppConnectionService.databaseBackend.updateAccount(account);
673 }
674 }
675 if (this.changeAccessMode.get()) {
676 Log.d(
677 Config.LOGTAG,
678 account.getJid().asBareJid()
679 + ": server gained publish-options capabilities. changing access"
680 + " model");
681 }
682 final Iq packet =
683 mXmppConnectionService
684 .getIqGenerator()
685 .retrieveBundlesForDevice(account.getJid().asBareJid(), getOwnDeviceId());
686 mXmppConnectionService.sendIqPacket(
687 account,
688 packet,
689 response -> {
690 if (response.getType() == Iq.Type.TIMEOUT) {
691 return; // ignore timeout. do nothing
692 }
693
694 if (response.getType() == Iq.Type.ERROR) {
695 Element error = response.findChild("error");
696 if (error == null || !error.hasChild("item-not-found")) {
697 pepBroken = true;
698 Log.w(
699 Config.LOGTAG,
700 AxolotlService.getLogprefix(account)
701 + "request for device bundles came back with something"
702 + " other than item-not-found"
703 + response);
704 return;
705 }
706 }
707
708 PreKeyBundle bundle = IqParser.bundle(response);
709 final Map<Integer, ECPublicKey> keys = IqParser.preKeyPublics(response);
710 boolean flush = false;
711 if (bundle == null) {
712 Log.w(
713 Config.LOGTAG,
714 AxolotlService.getLogprefix(account)
715 + "Received invalid bundle:"
716 + response);
717 bundle = new PreKeyBundle(-1, -1, -1, null, -1, null, null, null);
718 flush = true;
719 }
720 if (keys == null) {
721 Log.w(
722 Config.LOGTAG,
723 AxolotlService.getLogprefix(account)
724 + "Received invalid prekeys:"
725 + response);
726 }
727 try {
728 boolean changed = false;
729 // Validate IdentityKey
730 IdentityKeyPair identityKeyPair = axolotlStore.getIdentityKeyPair();
731 if (flush
732 || !identityKeyPair
733 .getPublicKey()
734 .equals(bundle.getIdentityKey())) {
735 Log.i(
736 Config.LOGTAG,
737 AxolotlService.getLogprefix(account)
738 + "Adding own IdentityKey "
739 + identityKeyPair.getPublicKey()
740 + " to PEP.");
741 changed = true;
742 }
743
744 // Validate signedPreKeyRecord + ID
745 SignedPreKeyRecord signedPreKeyRecord;
746 int numSignedPreKeys = axolotlStore.getSignedPreKeysCount();
747 try {
748 signedPreKeyRecord =
749 axolotlStore.loadSignedPreKey(bundle.getSignedPreKeyId());
750 if (flush
751 || !bundle.getSignedPreKey()
752 .equals(signedPreKeyRecord.getKeyPair().getPublicKey())
753 || !Arrays.equals(
754 bundle.getSignedPreKeySignature(),
755 signedPreKeyRecord.getSignature())) {
756 Log.i(
757 Config.LOGTAG,
758 AxolotlService.getLogprefix(account)
759 + "Adding new signedPreKey with ID "
760 + (numSignedPreKeys + 1)
761 + " to PEP.");
762 signedPreKeyRecord =
763 KeyHelper.generateSignedPreKey(
764 identityKeyPair, numSignedPreKeys + 1);
765 axolotlStore.storeSignedPreKey(
766 signedPreKeyRecord.getId(), signedPreKeyRecord);
767 changed = true;
768 }
769 } catch (InvalidKeyIdException e) {
770 Log.i(
771 Config.LOGTAG,
772 AxolotlService.getLogprefix(account)
773 + "Adding new signedPreKey with ID "
774 + (numSignedPreKeys + 1)
775 + " to PEP.");
776 signedPreKeyRecord =
777 KeyHelper.generateSignedPreKey(
778 identityKeyPair, numSignedPreKeys + 1);
779 axolotlStore.storeSignedPreKey(
780 signedPreKeyRecord.getId(), signedPreKeyRecord);
781 changed = true;
782 }
783
784 // Validate PreKeys
785 Set<PreKeyRecord> preKeyRecords = new HashSet<>();
786 if (keys != null) {
787 for (Integer id : keys.keySet()) {
788 try {
789 PreKeyRecord preKeyRecord = axolotlStore.loadPreKey(id);
790 if (preKeyRecord
791 .getKeyPair()
792 .getPublicKey()
793 .equals(keys.get(id))) {
794 preKeyRecords.add(preKeyRecord);
795 }
796 } catch (InvalidKeyIdException ignored) {
797 }
798 }
799 }
800 int newKeys = NUM_KEYS_TO_PUBLISH - preKeyRecords.size();
801 if (newKeys > 0) {
802 List<PreKeyRecord> newRecords =
803 KeyHelper.generatePreKeys(
804 axolotlStore.getCurrentPreKeyId() + 1, newKeys);
805 preKeyRecords.addAll(newRecords);
806 for (PreKeyRecord record : newRecords) {
807 axolotlStore.storePreKey(record.getId(), record);
808 }
809 changed = true;
810 Log.i(
811 Config.LOGTAG,
812 AxolotlService.getLogprefix(account)
813 + "Adding "
814 + newKeys
815 + " new preKeys to PEP.");
816 }
817
818 if (changed || changeAccessMode.get()) {
819 if (account.getPrivateKeyAlias() != null && Config.X509_VERIFICATION) {
820 mXmppConnectionService.publishDisplayName(account);
821 publishDeviceVerificationAndBundle(
822 signedPreKeyRecord, preKeyRecords, announce, wipe);
823 } else {
824 publishDeviceBundle(
825 signedPreKeyRecord, preKeyRecords, announce, wipe);
826 }
827 } else {
828 Log.d(
829 Config.LOGTAG,
830 getLogprefix(account)
831 + "Bundle "
832 + getOwnDeviceId()
833 + " in PEP was current");
834 if (wipe) {
835 wipeOtherPepDevices();
836 } else if (announce) {
837 Log.d(
838 Config.LOGTAG,
839 getLogprefix(account)
840 + "Announcing device "
841 + getOwnDeviceId());
842 publishOwnDeviceIdIfNeeded();
843 }
844 }
845 } catch (InvalidKeyException e) {
846 Log.e(
847 Config.LOGTAG,
848 AxolotlService.getLogprefix(account)
849 + "Failed to publish bundle "
850 + getOwnDeviceId()
851 + ", reason: "
852 + e.getMessage());
853 }
854 });
855 }
856
857 private void publishDeviceBundle(
858 SignedPreKeyRecord signedPreKeyRecord,
859 Set<PreKeyRecord> preKeyRecords,
860 final boolean announceAfter,
861 final boolean wipe) {
862 publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe, true);
863 }
864
865 private void publishDeviceBundle(
866 final SignedPreKeyRecord signedPreKeyRecord,
867 final Set<PreKeyRecord> preKeyRecords,
868 final boolean announceAfter,
869 final boolean wipe,
870 final boolean firstAttempt) {
871 final Bundle publishOptions =
872 account.getXmppConnection().getFeatures().pepPublishOptions()
873 ? PublishOptions.openAccess()
874 : null;
875 final Iq publish =
876 mXmppConnectionService
877 .getIqGenerator()
878 .publishBundles(
879 signedPreKeyRecord,
880 axolotlStore.getIdentityKeyPair().getPublicKey(),
881 preKeyRecords,
882 getOwnDeviceId(),
883 publishOptions);
884 Log.d(
885 Config.LOGTAG,
886 AxolotlService.getLogprefix(account)
887 + ": Bundle "
888 + getOwnDeviceId()
889 + " in PEP not current. Publishing...");
890 mXmppConnectionService.sendIqPacket(
891 account,
892 publish,
893 response -> {
894 final boolean preconditionNotMet = PublishOptions.preconditionNotMet(response);
895 if (firstAttempt && preconditionNotMet) {
896 Log.d(
897 Config.LOGTAG,
898 account.getJid().asBareJid()
899 + ": precondition wasn't met for bundle. pushing node"
900 + " configuration");
901 final String node = AxolotlService.PEP_BUNDLES + ":" + getOwnDeviceId();
902 mXmppConnectionService.pushNodeConfiguration(
903 account,
904 node,
905 publishOptions,
906 new XmppConnectionService.OnConfigurationPushed() {
907 @Override
908 public void onPushSucceeded() {
909 publishDeviceBundle(
910 signedPreKeyRecord,
911 preKeyRecords,
912 announceAfter,
913 wipe,
914 false);
915 }
916
917 @Override
918 public void onPushFailed() {
919 publishDeviceBundle(
920 signedPreKeyRecord,
921 preKeyRecords,
922 announceAfter,
923 wipe,
924 false);
925 }
926 });
927 } else if (response.getType() == Iq.Type.RESULT) {
928 Log.d(
929 Config.LOGTAG,
930 AxolotlService.getLogprefix(account)
931 + "Successfully published bundle. ");
932 if (wipe) {
933 wipeOtherPepDevices();
934 } else if (announceAfter) {
935 Log.d(
936 Config.LOGTAG,
937 getLogprefix(account)
938 + "Announcing device "
939 + getOwnDeviceId());
940 publishOwnDeviceIdIfNeeded();
941 }
942 } else if (response.getType() == Iq.Type.ERROR) {
943 if (preconditionNotMet) {
944 Log.d(
945 Config.LOGTAG,
946 getLogprefix(account)
947 + "bundle precondition still not met after second"
948 + " attempt");
949 } else {
950 Log.d(
951 Config.LOGTAG,
952 getLogprefix(account)
953 + "Error received while publishing bundle: "
954 + response);
955 }
956 pepBroken = true;
957 }
958 });
959 }
960
961 public void deleteOmemoIdentity() {
962 mXmppConnectionService.deletePepNode(
963 account, AxolotlService.PEP_BUNDLES + ":" + getOwnDeviceId());
964 final Set<Integer> ownDeviceIds = getOwnDeviceIds();
965 publishDeviceIdsAndRefineAccessModel(
966 ownDeviceIds == null ? Collections.emptySet() : ownDeviceIds);
967 }
968
969 public List<Jid> getCryptoTargets(Conversation conversation) {
970 final List<Jid> jids;
971 if (conversation.getMode() == Conversation.MODE_SINGLE) {
972 jids = new ArrayList<>();
973 jids.add(conversation.getJid().asBareJid());
974 } else {
975 jids = conversation.getMucOptions().getMembers(false);
976 }
977 return jids;
978 }
979
980 public FingerprintStatus getFingerprintTrust(String fingerprint) {
981 return axolotlStore.getFingerprintStatus(fingerprint);
982 }
983
984 public X509Certificate getFingerprintCertificate(String fingerprint) {
985 return axolotlStore.getFingerprintCertificate(fingerprint);
986 }
987
988 public void setFingerprintTrust(final String fingerprint, final FingerprintStatus status) {
989 axolotlStore.setFingerprintStatus(fingerprint, status);
990 // TODO we decided to call this after a fingerprint gets toggled to update the 'your contact
991 // is using unverified devices text'; however this means the entire screen gets redrawn
992 // after a toggle which might be annoying or cause other weird UI glitches
993 mXmppConnectionService.updateAccountUi();
994 }
995
996 private ListenableFuture<XmppAxolotlSession> verifySessionWithPEP(
997 final XmppAxolotlSession session) {
998 Log.d(
999 Config.LOGTAG,
1000 "trying to verify fresh session ("
1001 + session.getRemoteAddress().getName()
1002 + ") with pep");
1003 final SignalProtocolAddress address = session.getRemoteAddress();
1004 final IdentityKey identityKey = session.getIdentityKey();
1005 final Jid jid;
1006 try {
1007 jid = Jid.of(address.getName());
1008 } catch (final IllegalArgumentException e) {
1009 fetchStatusMap.put(address, FetchStatus.SUCCESS);
1010 finishBuildingSessionsFromPEP(address);
1011 return Futures.immediateFuture(session);
1012 }
1013 final SettableFuture<XmppAxolotlSession> future = SettableFuture.create();
1014 final Iq packet =
1015 mXmppConnectionService
1016 .getIqGenerator()
1017 .retrieveVerificationForDevice(jid, address.getDeviceId());
1018 mXmppConnectionService.sendIqPacket(
1019 account,
1020 packet,
1021 response -> {
1022 Pair<X509Certificate[], byte[]> verification = IqParser.verification(response);
1023 if (verification != null) {
1024 try {
1025 Signature verifier = Signature.getInstance("sha256WithRSA");
1026 verifier.initVerify(verification.first[0]);
1027 verifier.update(identityKey.serialize());
1028 if (verifier.verify(verification.second)) {
1029 try {
1030 mXmppConnectionService
1031 .getMemorizingTrustManager()
1032 .getNonInteractive()
1033 .checkClientTrusted(verification.first, "RSA");
1034 String fingerprint = session.getFingerprint();
1035 Log.d(
1036 Config.LOGTAG,
1037 "verified session with x.509 signature. fingerprint"
1038 + " was: "
1039 + fingerprint);
1040 setFingerprintTrust(
1041 fingerprint,
1042 FingerprintStatus.createActiveVerified(true));
1043 axolotlStore.setFingerprintCertificate(
1044 fingerprint, verification.first[0]);
1045 fetchStatusMap.put(address, FetchStatus.SUCCESS_VERIFIED);
1046 Bundle information =
1047 CryptoHelper.extractCertificateInformation(
1048 verification.first[0]);
1049 try {
1050 final String cn = information.getString("subject_cn");
1051 final Jid jid1 = Jid.of(address.getName());
1052 Log.d(
1053 Config.LOGTAG,
1054 "setting common name for " + jid1 + " to " + cn);
1055 account.getRoster().getContact(jid1).setCommonName(cn);
1056 } catch (final IllegalArgumentException ignored) {
1057 // ignored
1058 }
1059 finishBuildingSessionsFromPEP(address);
1060 future.set(session);
1061 return;
1062 } catch (Exception e) {
1063 Log.d(Config.LOGTAG, "could not verify certificate");
1064 }
1065 }
1066 } catch (Exception e) {
1067 Log.d(Config.LOGTAG, "error during verification " + e.getMessage());
1068 }
1069 } else {
1070 Log.d(Config.LOGTAG, "no verification found");
1071 }
1072 fetchStatusMap.put(address, FetchStatus.SUCCESS);
1073 finishBuildingSessionsFromPEP(address);
1074 future.set(session);
1075 });
1076 return future;
1077 }
1078
1079 private void finishBuildingSessionsFromPEP(final SignalProtocolAddress address) {
1080 SignalProtocolAddress ownAddress =
1081 new SignalProtocolAddress(account.getJid().asBareJid().toString(), 0);
1082 Map<Integer, FetchStatus> own = fetchStatusMap.getAll(ownAddress.getName());
1083 Map<Integer, FetchStatus> remote = fetchStatusMap.getAll(address.getName());
1084 if (!own.containsValue(FetchStatus.PENDING) && !remote.containsValue(FetchStatus.PENDING)) {
1085 FetchStatus report = null;
1086 if (own.containsValue(FetchStatus.SUCCESS)
1087 || remote.containsValue(FetchStatus.SUCCESS)) {
1088 report = FetchStatus.SUCCESS;
1089 } else if (own.containsValue(FetchStatus.SUCCESS_VERIFIED)
1090 || remote.containsValue(FetchStatus.SUCCESS_VERIFIED)) {
1091 report = FetchStatus.SUCCESS_VERIFIED;
1092 } else if (own.containsValue(FetchStatus.SUCCESS_TRUSTED)
1093 || remote.containsValue(FetchStatus.SUCCESS_TRUSTED)) {
1094 report = FetchStatus.SUCCESS_TRUSTED;
1095 } else if (own.containsValue(FetchStatus.ERROR)
1096 || remote.containsValue(FetchStatus.ERROR)) {
1097 report = FetchStatus.ERROR;
1098 }
1099 mXmppConnectionService.keyStatusUpdated(report);
1100 }
1101 if (Config.REMOVE_BROKEN_DEVICES) {
1102 Set<Integer> ownDeviceIds = new HashSet<>(getOwnDeviceIds());
1103 boolean publish = false;
1104 for (Map.Entry<Integer, FetchStatus> entry : own.entrySet()) {
1105 int id = entry.getKey();
1106 if (entry.getValue() == FetchStatus.ERROR
1107 && PREVIOUSLY_REMOVED_FROM_ANNOUNCEMENT.add(id)
1108 && ownDeviceIds.remove(id)) {
1109 publish = true;
1110 Log.d(
1111 Config.LOGTAG,
1112 account.getJid().asBareJid()
1113 + ": error fetching own device with id "
1114 + id
1115 + ". removing from announcement");
1116 }
1117 }
1118 if (publish) {
1119 publishOwnDeviceId(ownDeviceIds);
1120 }
1121 }
1122 }
1123
1124 public boolean hasEmptyDeviceList(Jid jid) {
1125 return !hasAny(jid) && (!deviceIds.containsKey(jid) || deviceIds.get(jid).isEmpty());
1126 }
1127
1128 public void fetchDeviceIds(final Jid jid) {
1129 fetchDeviceIds(jid, null);
1130 }
1131
1132 private void fetchDeviceIds(final Jid jid, OnDeviceIdsFetched callback) {
1133 final Iq packet;
1134 synchronized (this.fetchDeviceIdsMap) {
1135 List<OnDeviceIdsFetched> callbacks = this.fetchDeviceIdsMap.get(jid);
1136 if (callbacks != null) {
1137 if (callback != null) {
1138 callbacks.add(callback);
1139 }
1140 Log.d(
1141 Config.LOGTAG,
1142 account.getJid().asBareJid()
1143 + ": fetching device ids for "
1144 + jid
1145 + " already running. adding callback");
1146 packet = null;
1147 } else {
1148 callbacks = new ArrayList<>();
1149 if (callback != null) {
1150 callbacks.add(callback);
1151 }
1152 this.fetchDeviceIdsMap.put(jid, callbacks);
1153 Log.d(
1154 Config.LOGTAG,
1155 account.getJid().asBareJid() + ": fetching device ids for " + jid);
1156 packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(jid);
1157 }
1158 }
1159 if (packet != null) {
1160 mXmppConnectionService.sendIqPacket(
1161 account,
1162 packet,
1163 response -> {
1164 if (response.getType() == Iq.Type.RESULT) {
1165 fetchDeviceListStatus.put(jid, true);
1166 // TODO use new API
1167 final Element item = IqParser.getItem(response);
1168 final Set<Integer> deviceIds = IqParser.deviceIds(item);
1169 registerDevices(jid, deviceIds);
1170 final List<OnDeviceIdsFetched> callbacks;
1171 synchronized (fetchDeviceIdsMap) {
1172 callbacks = fetchDeviceIdsMap.remove(jid);
1173 }
1174 if (callbacks != null) {
1175 for (OnDeviceIdsFetched c : callbacks) {
1176 c.fetched(jid, deviceIds);
1177 }
1178 }
1179 } else {
1180 if (response.getType() == Iq.Type.TIMEOUT) {
1181 fetchDeviceListStatus.remove(jid);
1182 } else {
1183 fetchDeviceListStatus.put(jid, false);
1184 }
1185 final List<OnDeviceIdsFetched> callbacks;
1186 synchronized (fetchDeviceIdsMap) {
1187 callbacks = fetchDeviceIdsMap.remove(jid);
1188 }
1189 if (callbacks != null) {
1190 for (OnDeviceIdsFetched c : callbacks) {
1191 c.fetched(jid, null);
1192 }
1193 }
1194 }
1195 });
1196 }
1197 }
1198
1199 private void fetchDeviceIds(List<Jid> jids, final OnMultipleDeviceIdFetched callback) {
1200 final ArrayList<Jid> unfinishedJids = new ArrayList<>(jids);
1201 synchronized (unfinishedJids) {
1202 for (Jid jid : unfinishedJids) {
1203 fetchDeviceIds(
1204 jid,
1205 (j, deviceIds) -> {
1206 synchronized (unfinishedJids) {
1207 unfinishedJids.remove(j);
1208 if (unfinishedJids.size() == 0 && callback != null) {
1209 callback.fetched();
1210 }
1211 }
1212 });
1213 }
1214 }
1215 }
1216
1217 private ListenableFuture<XmppAxolotlSession> buildSessionFromPEP(
1218 final SignalProtocolAddress address) {
1219 return buildSessionFromPEP(address, null);
1220 }
1221
1222 private ListenableFuture<XmppAxolotlSession> buildSessionFromPEP(
1223 final SignalProtocolAddress address, OnSessionBuildFromPep callback) {
1224 final SettableFuture<XmppAxolotlSession> sessionSettableFuture = SettableFuture.create();
1225 Log.i(
1226 Config.LOGTAG,
1227 AxolotlService.getLogprefix(account)
1228 + "Building new session for "
1229 + address.toString());
1230 if (address.equals(getOwnAxolotlAddress())) {
1231 throw new AssertionError(
1232 "We should NEVER build a session with ourselves. What happened here?!");
1233 }
1234 final Jid jid = Jid.of(address.getName());
1235 final boolean oneOfOurs = jid.asBareJid().equals(account.getJid().asBareJid());
1236 final Iq bundlesPacket =
1237 mXmppConnectionService
1238 .getIqGenerator()
1239 .retrieveBundlesForDevice(jid, address.getDeviceId());
1240 mXmppConnectionService.sendIqPacket(
1241 account,
1242 bundlesPacket,
1243 packet -> {
1244 if (packet.getType() == Iq.Type.TIMEOUT) {
1245 fetchStatusMap.put(address, FetchStatus.TIMEOUT);
1246 sessionSettableFuture.setException(
1247 new CryptoFailedException("Unable to build session. Timeout"));
1248 } else if (packet.getType() == Iq.Type.RESULT) {
1249 Log.d(
1250 Config.LOGTAG,
1251 AxolotlService.getLogprefix(account)
1252 + "Received preKey IQ packet, processing...");
1253 final List<PreKeyBundle> preKeyBundleList = IqParser.preKeys(packet);
1254 final PreKeyBundle bundle = IqParser.bundle(packet);
1255 if (preKeyBundleList.isEmpty() || bundle == null) {
1256 Log.e(
1257 Config.LOGTAG,
1258 AxolotlService.getLogprefix(account)
1259 + "preKey IQ packet invalid: "
1260 + packet);
1261 fetchStatusMap.put(address, FetchStatus.ERROR);
1262 finishBuildingSessionsFromPEP(address);
1263 if (callback != null) {
1264 callback.onSessionBuildFailed();
1265 }
1266 sessionSettableFuture.setException(
1267 new CryptoFailedException(
1268 "Unable to build session. IQ Packet Invalid"));
1269 return;
1270 }
1271 Random random = new Random();
1272 final PreKeyBundle preKey =
1273 preKeyBundleList.get(random.nextInt(preKeyBundleList.size()));
1274 if (preKey == null) {
1275 // should never happen
1276 fetchStatusMap.put(address, FetchStatus.ERROR);
1277 finishBuildingSessionsFromPEP(address);
1278 if (callback != null) {
1279 callback.onSessionBuildFailed();
1280 }
1281 sessionSettableFuture.setException(
1282 new CryptoFailedException(
1283 "Unable to build session. No suitable PreKey found"));
1284 return;
1285 }
1286
1287 final PreKeyBundle preKeyBundle =
1288 new PreKeyBundle(
1289 0,
1290 address.getDeviceId(),
1291 preKey.getPreKeyId(),
1292 preKey.getPreKey(),
1293 bundle.getSignedPreKeyId(),
1294 bundle.getSignedPreKey(),
1295 bundle.getSignedPreKeySignature(),
1296 bundle.getIdentityKey());
1297
1298 try {
1299 SessionBuilder builder = new SessionBuilder(axolotlStore, address);
1300 builder.process(preKeyBundle);
1301 XmppAxolotlSession session =
1302 new XmppAxolotlSession(
1303 account,
1304 axolotlStore,
1305 address,
1306 bundle.getIdentityKey());
1307 sessions.put(address, session);
1308 if (Config.X509_VERIFICATION) {
1309 sessionSettableFuture.setFuture(
1310 verifySessionWithPEP(
1311 session)); // TODO; maybe inject callback in here
1312 // too
1313 } else {
1314 FingerprintStatus status =
1315 getFingerprintTrust(
1316 CryptoHelper.bytesToHex(
1317 bundle.getIdentityKey()
1318 .getPublicKey()
1319 .serialize()));
1320 FetchStatus fetchStatus;
1321 if (status != null && status.isVerified()) {
1322 fetchStatus = FetchStatus.SUCCESS_VERIFIED;
1323 } else if (status != null && status.isTrusted()) {
1324 fetchStatus = FetchStatus.SUCCESS_TRUSTED;
1325 } else {
1326 fetchStatus = FetchStatus.SUCCESS;
1327 }
1328 fetchStatusMap.put(address, fetchStatus);
1329 finishBuildingSessionsFromPEP(address);
1330 if (callback != null) {
1331 callback.onSessionBuildSuccessful();
1332 }
1333 sessionSettableFuture.set(session);
1334 }
1335 } catch (UntrustedIdentityException | InvalidKeyException e) {
1336 Log.e(
1337 Config.LOGTAG,
1338 AxolotlService.getLogprefix(account)
1339 + "Error building session for "
1340 + address
1341 + ": "
1342 + e.getClass().getName()
1343 + ", "
1344 + e.getMessage());
1345 fetchStatusMap.put(address, FetchStatus.ERROR);
1346 finishBuildingSessionsFromPEP(address);
1347 if (oneOfOurs && cleanedOwnDeviceIds.add(address.getDeviceId())) {
1348 removeFromDeviceAnnouncement(address.getDeviceId());
1349 }
1350 if (callback != null) {
1351 callback.onSessionBuildFailed();
1352 }
1353 sessionSettableFuture.setException(new CryptoFailedException(e));
1354 }
1355 } else {
1356 fetchStatusMap.put(address, FetchStatus.ERROR);
1357 Element error = packet.findChild("error");
1358 boolean itemNotFound = error != null && error.hasChild("item-not-found");
1359 Log.d(
1360 Config.LOGTAG,
1361 getLogprefix(account)
1362 + "Error received while building session:"
1363 + packet.findChild("error"));
1364 finishBuildingSessionsFromPEP(address);
1365 if (oneOfOurs
1366 && itemNotFound
1367 && cleanedOwnDeviceIds.add(address.getDeviceId())) {
1368 removeFromDeviceAnnouncement(address.getDeviceId());
1369 }
1370 if (callback != null) {
1371 callback.onSessionBuildFailed();
1372 }
1373 sessionSettableFuture.setException(
1374 new CryptoFailedException(
1375 "Unable to build session. IQ Packet Error"));
1376 }
1377 });
1378 return sessionSettableFuture;
1379 }
1380
1381 private void removeFromDeviceAnnouncement(Integer id) {
1382 HashSet<Integer> temp = new HashSet<>(getOwnDeviceIds());
1383 if (temp.remove(id)) {
1384 Log.d(
1385 Config.LOGTAG,
1386 account.getJid().asBareJid()
1387 + " remove own device id "
1388 + id
1389 + " from announcement. devices left:"
1390 + temp);
1391 publishOwnDeviceId(temp);
1392 }
1393 }
1394
1395 public Set<SignalProtocolAddress> findDevicesWithoutSession(final Conversation conversation) {
1396 Set<SignalProtocolAddress> addresses = new HashSet<>();
1397 for (Jid jid : getCryptoTargets(conversation)) {
1398 Log.d(
1399 Config.LOGTAG,
1400 AxolotlService.getLogprefix(account)
1401 + "Finding devices without session for "
1402 + jid);
1403 final Set<Integer> ids = deviceIds.get(jid);
1404 if (ids != null && !ids.isEmpty()) {
1405 for (Integer foreignId : ids) {
1406 SignalProtocolAddress address =
1407 new SignalProtocolAddress(jid.toString(), foreignId);
1408 if (sessions.get(address) == null) {
1409 IdentityKey identityKey =
1410 axolotlStore
1411 .loadSession(address)
1412 .getSessionState()
1413 .getRemoteIdentityKey();
1414 if (identityKey != null) {
1415 Log.d(
1416 Config.LOGTAG,
1417 AxolotlService.getLogprefix(account)
1418 + "Already have session for "
1419 + address
1420 + ", adding to cache...");
1421 XmppAxolotlSession session =
1422 new XmppAxolotlSession(
1423 account, axolotlStore, address, identityKey);
1424 sessions.put(address, session);
1425 } else {
1426 Log.d(
1427 Config.LOGTAG,
1428 AxolotlService.getLogprefix(account)
1429 + "Found device "
1430 + jid
1431 + ":"
1432 + foreignId);
1433 if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
1434 addresses.add(address);
1435 } else {
1436 Log.d(
1437 Config.LOGTAG,
1438 getLogprefix(account)
1439 + "skipping over "
1440 + address
1441 + " because it's broken");
1442 }
1443 }
1444 }
1445 }
1446 } else {
1447 mXmppConnectionService.keyStatusUpdated(FetchStatus.ERROR);
1448 Log.w(
1449 Config.LOGTAG,
1450 AxolotlService.getLogprefix(account) + "Have no target devices in PEP!");
1451 }
1452 }
1453 Set<Integer> ownIds = this.deviceIds.get(account.getJid().asBareJid());
1454 for (Integer ownId : (ownIds != null ? ownIds : new HashSet<Integer>())) {
1455 SignalProtocolAddress address =
1456 new SignalProtocolAddress(account.getJid().asBareJid().toString(), ownId);
1457 if (sessions.get(address) == null) {
1458 IdentityKey identityKey =
1459 axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
1460 if (identityKey != null) {
1461 Log.d(
1462 Config.LOGTAG,
1463 AxolotlService.getLogprefix(account)
1464 + "Already have session for "
1465 + address
1466 + ", adding to cache...");
1467 XmppAxolotlSession session =
1468 new XmppAxolotlSession(account, axolotlStore, address, identityKey);
1469 sessions.put(address, session);
1470 } else {
1471 Log.d(
1472 Config.LOGTAG,
1473 AxolotlService.getLogprefix(account)
1474 + "Found device "
1475 + account.getJid().asBareJid()
1476 + ":"
1477 + ownId);
1478 if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
1479 addresses.add(address);
1480 } else {
1481 Log.d(
1482 Config.LOGTAG,
1483 getLogprefix(account)
1484 + "skipping over "
1485 + address
1486 + " because it's broken");
1487 }
1488 }
1489 }
1490 }
1491
1492 return addresses;
1493 }
1494
1495 public boolean createSessionsIfNeeded(final Conversation conversation) {
1496 final List<Jid> jidsWithEmptyDeviceList = getCryptoTargets(conversation);
1497 for (Iterator<Jid> iterator = jidsWithEmptyDeviceList.iterator(); iterator.hasNext(); ) {
1498 final Jid jid = iterator.next();
1499 if (!hasEmptyDeviceList(jid)) {
1500 iterator.remove();
1501 }
1502 }
1503 Log.d(
1504 Config.LOGTAG,
1505 account.getJid().asBareJid()
1506 + ": createSessionsIfNeeded() - jids with empty device list: "
1507 + jidsWithEmptyDeviceList);
1508 if (jidsWithEmptyDeviceList.size() > 0) {
1509 fetchDeviceIds(
1510 jidsWithEmptyDeviceList, () -> createSessionsIfNeededActual(conversation));
1511 return true;
1512 } else {
1513 return createSessionsIfNeededActual(conversation);
1514 }
1515 }
1516
1517 private boolean createSessionsIfNeededActual(final Conversation conversation) {
1518 Log.i(
1519 Config.LOGTAG,
1520 AxolotlService.getLogprefix(account) + "Creating axolotl sessions if needed...");
1521 boolean newSessions = false;
1522 Set<SignalProtocolAddress> addresses = findDevicesWithoutSession(conversation);
1523 for (SignalProtocolAddress address : addresses) {
1524 Log.d(
1525 Config.LOGTAG,
1526 AxolotlService.getLogprefix(account)
1527 + "Processing device: "
1528 + address.toString());
1529 FetchStatus status = fetchStatusMap.get(address);
1530 if (status == null || status == FetchStatus.TIMEOUT) {
1531 fetchStatusMap.put(address, FetchStatus.PENDING);
1532 this.buildSessionFromPEP(address);
1533 newSessions = true;
1534 } else if (status == FetchStatus.PENDING) {
1535 newSessions = true;
1536 } else {
1537 Log.d(
1538 Config.LOGTAG,
1539 AxolotlService.getLogprefix(account)
1540 + "Already fetching bundle for "
1541 + address);
1542 }
1543 }
1544
1545 return newSessions;
1546 }
1547
1548 public boolean trustedSessionVerified(final Conversation conversation) {
1549 final Set<XmppAxolotlSession> sessions = new HashSet<>();
1550 sessions.addAll(findSessionsForConversation(conversation));
1551 sessions.addAll(findOwnSessions());
1552 boolean verified = false;
1553 for (XmppAxolotlSession session : sessions) {
1554 if (session.getTrust().isTrustedAndActive()) {
1555 if (session.getTrust().getTrust() == FingerprintStatus.Trust.VERIFIED_X509) {
1556 verified = true;
1557 } else {
1558 return false;
1559 }
1560 }
1561 }
1562 return verified;
1563 }
1564
1565 public boolean hasPendingKeyFetches(List<Jid> jids) {
1566 SignalProtocolAddress ownAddress =
1567 new SignalProtocolAddress(account.getJid().asBareJid().toString(), 0);
1568 if (fetchStatusMap.getAll(ownAddress.getName()).containsValue(FetchStatus.PENDING)) {
1569 return true;
1570 }
1571 synchronized (this.fetchDeviceIdsMap) {
1572 for (Jid jid : jids) {
1573 SignalProtocolAddress foreignAddress =
1574 new SignalProtocolAddress(jid.asBareJid().toString(), 0);
1575 if (fetchStatusMap
1576 .getAll(foreignAddress.getName())
1577 .containsValue(FetchStatus.PENDING)
1578 || this.fetchDeviceIdsMap.containsKey(jid)) {
1579 return true;
1580 }
1581 }
1582 }
1583 return false;
1584 }
1585
1586 @Nullable
1587 private boolean buildHeader(XmppAxolotlMessage axolotlMessage, Conversation c) {
1588 Set<XmppAxolotlSession> remoteSessions = findSessionsForConversation(c);
1589 final boolean acceptEmpty =
1590 (c.getMode() == Conversation.MODE_MULTI && c.getMucOptions().getUserCount() == 0)
1591 || c.getContact().isSelf();
1592 Collection<XmppAxolotlSession> ownSessions = findOwnSessions();
1593 if (remoteSessions.isEmpty() && !acceptEmpty) {
1594 return false;
1595 }
1596 for (XmppAxolotlSession session : remoteSessions) {
1597 axolotlMessage.addDevice(session);
1598 }
1599 for (XmppAxolotlSession session : ownSessions) {
1600 axolotlMessage.addDevice(session);
1601 }
1602
1603 return true;
1604 }
1605
1606 // this is being used for private muc messages only
1607 private boolean buildHeader(XmppAxolotlMessage axolotlMessage, Jid jid) {
1608 if (jid == null) {
1609 return false;
1610 }
1611 HashSet<XmppAxolotlSession> sessions = new HashSet<>();
1612 sessions.addAll(this.sessions.getAll(getAddressForJid(jid).getName()).values());
1613 if (sessions.isEmpty()) {
1614 return false;
1615 }
1616 sessions.addAll(findOwnSessions());
1617 for (XmppAxolotlSession session : sessions) {
1618 axolotlMessage.addDevice(session);
1619 }
1620 return true;
1621 }
1622
1623 @Nullable
1624 public XmppAxolotlMessage encrypt(Message message) {
1625 final XmppAxolotlMessage axolotlMessage =
1626 new XmppAxolotlMessage(account.getJid().asBareJid(), getOwnDeviceId());
1627 final String content;
1628 if (message.hasFileOnRemoteHost()) {
1629 content = message.getFileParams().url;
1630 } else {
1631 content = message.getRawBody();
1632 }
1633 try {
1634 axolotlMessage.encrypt(content);
1635 } catch (CryptoFailedException e) {
1636 Log.w(
1637 Config.LOGTAG,
1638 getLogprefix(account) + "Failed to encrypt message: " + e.getMessage());
1639 return null;
1640 }
1641
1642 final boolean success;
1643 if (message.isPrivateMessage()) {
1644 success = buildHeader(axolotlMessage, message.getTrueCounterpart());
1645 } else {
1646 success = buildHeader(axolotlMessage, (Conversation) message.getConversation());
1647 }
1648 return success ? axolotlMessage : null;
1649 }
1650
1651 @Nullable
1652 public XmppAxolotlMessage encrypt(final String content, Conversation conversation) {
1653 final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(account.getJid().asBareJid(), getOwnDeviceId());
1654 try {
1655 axolotlMessage.encrypt(content);
1656 } catch (CryptoFailedException e) {
1657 Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to encrypt message: " + e.getMessage());
1658 return null;
1659 }
1660 if (!buildHeader(axolotlMessage, conversation)) return null;
1661 return axolotlMessage;
1662 }
1663
1664 public void preparePayloadMessage(final Message message, final boolean delay) {
1665 executor.execute(
1666 new Runnable() {
1667 @Override
1668 public void run() {
1669 XmppAxolotlMessage axolotlMessage = encrypt(message);
1670 if (axolotlMessage == null) {
1671 mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED);
1672 // mXmppConnectionService.updateConversationUi();
1673 } else {
1674 Log.d(
1675 Config.LOGTAG,
1676 AxolotlService.getLogprefix(account)
1677 + "Generated message, caching: "
1678 + message.getUuid());
1679 messageCache.put(message.getUuid(), axolotlMessage);
1680 mXmppConnectionService.resendMessage(message, delay, true);
1681 }
1682 }
1683 });
1684 }
1685
1686 private OmemoVerifiedIceUdpTransportInfo encrypt(
1687 final IceUdpTransportInfo element, final XmppAxolotlSession session)
1688 throws CryptoFailedException {
1689 final OmemoVerifiedIceUdpTransportInfo transportInfo =
1690 new OmemoVerifiedIceUdpTransportInfo();
1691 transportInfo.setAttributes(element.getAttributes());
1692 for (final Element child : element.getChildren()) {
1693 if ("fingerprint".equals(child.getName())
1694 && Namespace.JINGLE_APPS_DTLS.equals(child.getNamespace())) {
1695 final Element fingerprint =
1696 new Element("fingerprint", Namespace.OMEMO_DTLS_SRTP_VERIFICATION);
1697 fingerprint.setAttribute("setup", child.getAttribute("setup"));
1698 fingerprint.setAttribute("hash", child.getAttribute("hash"));
1699 final XmppAxolotlMessage axolotlMessage =
1700 new XmppAxolotlMessage(account.getJid().asBareJid(), getOwnDeviceId());
1701 final String content = child.getContent();
1702 axolotlMessage.encrypt(content);
1703 axolotlMessage.addDevice(session, true);
1704 fingerprint.addChild(axolotlMessage.toElement());
1705 transportInfo.addChild(fingerprint);
1706 } else {
1707 transportInfo.addChild(child);
1708 }
1709 }
1710 return transportInfo;
1711 }
1712
1713 public ListenableFuture<OmemoVerifiedPayload<OmemoVerifiedRtpContentMap>> encrypt(
1714 final RtpContentMap rtpContentMap, final Jid jid, final int deviceId) {
1715 return Futures.transformAsync(
1716 getSession(jid, deviceId),
1717 session -> encrypt(rtpContentMap, session),
1718 MoreExecutors.directExecutor());
1719 }
1720
1721 private ListenableFuture<OmemoVerifiedPayload<OmemoVerifiedRtpContentMap>> encrypt(
1722 final RtpContentMap rtpContentMap, final XmppAxolotlSession session) {
1723 if (Config.REQUIRE_RTP_VERIFICATION) {
1724 requireVerification(session);
1725 }
1726 final ImmutableMap.Builder<
1727 String, DescriptionTransport<RtpDescription, IceUdpTransportInfo>>
1728 descriptionTransportBuilder = new ImmutableMap.Builder<>();
1729 final OmemoVerification omemoVerification = new OmemoVerification();
1730 omemoVerification.setDeviceId(session.getRemoteAddress().getDeviceId());
1731 omemoVerification.setSessionFingerprint(session.getFingerprint());
1732 for (final Map.Entry<String, DescriptionTransport<RtpDescription, IceUdpTransportInfo>>
1733 content : rtpContentMap.contents.entrySet()) {
1734 final DescriptionTransport<RtpDescription, IceUdpTransportInfo> descriptionTransport =
1735 content.getValue();
1736 final OmemoVerifiedIceUdpTransportInfo encryptedTransportInfo;
1737 try {
1738 encryptedTransportInfo = encrypt(descriptionTransport.transport, session);
1739 } catch (final CryptoFailedException e) {
1740 return Futures.immediateFailedFuture(e);
1741 }
1742 descriptionTransportBuilder.put(
1743 content.getKey(),
1744 new DescriptionTransport<>(
1745 descriptionTransport.senders,
1746 descriptionTransport.description,
1747 encryptedTransportInfo));
1748 }
1749 return Futures.immediateFuture(
1750 new OmemoVerifiedPayload<>(
1751 omemoVerification,
1752 new OmemoVerifiedRtpContentMap(
1753 rtpContentMap.group, descriptionTransportBuilder.build())));
1754 }
1755
1756 private ListenableFuture<XmppAxolotlSession> getSession(final Jid jid, final int deviceId) {
1757 final SignalProtocolAddress address =
1758 new SignalProtocolAddress(jid.asBareJid().toString(), deviceId);
1759 final XmppAxolotlSession session = sessions.get(address);
1760 if (session == null) {
1761 return buildSessionFromPEP(address);
1762 }
1763 return Futures.immediateFuture(session);
1764 }
1765
1766 public ListenableFuture<OmemoVerifiedPayload<RtpContentMap>> decrypt(
1767 OmemoVerifiedRtpContentMap omemoVerifiedRtpContentMap, final Jid from) {
1768 final ImmutableMap.Builder<
1769 String, DescriptionTransport<RtpDescription, IceUdpTransportInfo>>
1770 descriptionTransportBuilder = new ImmutableMap.Builder<>();
1771 final OmemoVerification omemoVerification = new OmemoVerification();
1772 final ImmutableList.Builder<ListenableFuture<XmppAxolotlSession>> pepVerificationFutures =
1773 new ImmutableList.Builder<>();
1774 for (final Map.Entry<String, DescriptionTransport<RtpDescription, IceUdpTransportInfo>>
1775 content : omemoVerifiedRtpContentMap.contents.entrySet()) {
1776 final DescriptionTransport<RtpDescription, IceUdpTransportInfo> descriptionTransport =
1777 content.getValue();
1778 final OmemoVerifiedPayload<IceUdpTransportInfo> decryptedTransport;
1779 try {
1780 decryptedTransport =
1781 decrypt(
1782 (OmemoVerifiedIceUdpTransportInfo) descriptionTransport.transport,
1783 from,
1784 pepVerificationFutures);
1785 } catch (CryptoFailedException e) {
1786 return Futures.immediateFailedFuture(e);
1787 }
1788 omemoVerification.setOrEnsureEqual(decryptedTransport);
1789 descriptionTransportBuilder.put(
1790 content.getKey(),
1791 new DescriptionTransport<>(
1792 descriptionTransport.senders,
1793 descriptionTransport.description,
1794 decryptedTransport.payload));
1795 }
1796 processPostponed();
1797 final ImmutableList<ListenableFuture<XmppAxolotlSession>> sessionFutures =
1798 pepVerificationFutures.build();
1799 return Futures.transform(
1800 Futures.allAsList(sessionFutures),
1801 sessions -> {
1802 if (Config.REQUIRE_RTP_VERIFICATION) {
1803 for (XmppAxolotlSession session : sessions) {
1804 requireVerification(session);
1805 }
1806 }
1807 return new OmemoVerifiedPayload<>(
1808 omemoVerification,
1809 new RtpContentMap(
1810 omemoVerifiedRtpContentMap.group,
1811 descriptionTransportBuilder.build()));
1812 },
1813 MoreExecutors.directExecutor());
1814 }
1815
1816 private OmemoVerifiedPayload<IceUdpTransportInfo> decrypt(
1817 final OmemoVerifiedIceUdpTransportInfo verifiedIceUdpTransportInfo,
1818 final Jid from,
1819 ImmutableList.Builder<ListenableFuture<XmppAxolotlSession>> pepVerificationFutures)
1820 throws CryptoFailedException {
1821 final IceUdpTransportInfo transportInfo = new IceUdpTransportInfo();
1822 transportInfo.setAttributes(verifiedIceUdpTransportInfo.getAttributes());
1823 final OmemoVerification omemoVerification = new OmemoVerification();
1824 for (final Element child : verifiedIceUdpTransportInfo.getChildren()) {
1825 if ("fingerprint".equals(child.getName())
1826 && Namespace.OMEMO_DTLS_SRTP_VERIFICATION.equals(child.getNamespace())) {
1827 final Element fingerprint = new Element("fingerprint", Namespace.JINGLE_APPS_DTLS);
1828 fingerprint.setAttribute("setup", child.getAttribute("setup"));
1829 fingerprint.setAttribute("hash", child.getAttribute("hash"));
1830 final Element encrypted =
1831 child.findChildEnsureSingle(
1832 XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX);
1833 final XmppAxolotlMessage xmppAxolotlMessage =
1834 XmppAxolotlMessage.fromElement(encrypted, from.asBareJid());
1835 final XmppAxolotlSession session = getReceivingSession(xmppAxolotlMessage);
1836 final XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintext =
1837 xmppAxolotlMessage.decrypt(session, getOwnDeviceId());
1838 final Integer preKeyId = session.getPreKeyIdAndReset();
1839 if (preKeyId != null) {
1840 postponedSessions.add(session);
1841 }
1842 if (session.isFresh()) {
1843 pepVerificationFutures.add(putFreshSession(session));
1844 } else if (Config.REQUIRE_RTP_VERIFICATION) {
1845 pepVerificationFutures.add(Futures.immediateFuture(session));
1846 }
1847 fingerprint.setContent(plaintext.getPlaintext());
1848 omemoVerification.setDeviceId(session.getRemoteAddress().getDeviceId());
1849 omemoVerification.setSessionFingerprint(plaintext.getFingerprint());
1850 transportInfo.addChild(fingerprint);
1851 } else {
1852 transportInfo.addChild(child);
1853 }
1854 }
1855 return new OmemoVerifiedPayload<>(omemoVerification, transportInfo);
1856 }
1857
1858 private static void requireVerification(final XmppAxolotlSession session) {
1859 if (session.getTrust().isVerified()) {
1860 return;
1861 }
1862 throw new NotVerifiedException(
1863 String.format("session with %s was not verified", session.getFingerprint()));
1864 }
1865
1866 public ListenableFuture<XmppAxolotlMessage> prepareKeyTransportMessage(
1867 final Conversation conversation) {
1868 return Futures.submit(
1869 () -> {
1870 final XmppAxolotlMessage axolotlMessage =
1871 new XmppAxolotlMessage(account.getJid().asBareJid(), getOwnDeviceId());
1872 if (buildHeader(axolotlMessage, conversation)) {
1873 return axolotlMessage;
1874 } else {
1875 throw new IllegalStateException("No session to decrypt to");
1876 }
1877 },
1878 executor);
1879 }
1880
1881 public XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message) {
1882 XmppAxolotlMessage axolotlMessage = messageCache.get(message.getUuid());
1883 if (axolotlMessage != null) {
1884 Log.d(
1885 Config.LOGTAG,
1886 AxolotlService.getLogprefix(account) + "Cache hit: " + message.getUuid());
1887 messageCache.remove(message.getUuid());
1888 } else {
1889 Log.d(
1890 Config.LOGTAG,
1891 AxolotlService.getLogprefix(account) + "Cache miss: " + message.getUuid());
1892 }
1893 return axolotlMessage;
1894 }
1895
1896 private XmppAxolotlSession recreateUncachedSession(SignalProtocolAddress address) {
1897 IdentityKey identityKey =
1898 axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
1899 return (identityKey != null)
1900 ? new XmppAxolotlSession(account, axolotlStore, address, identityKey)
1901 : null;
1902 }
1903
1904 private XmppAxolotlSession getReceivingSession(XmppAxolotlMessage message) {
1905 SignalProtocolAddress senderAddress =
1906 new SignalProtocolAddress(
1907 message.getFrom().toString(), message.getSenderDeviceId());
1908 return getReceivingSession(senderAddress);
1909 }
1910
1911 private XmppAxolotlSession getReceivingSession(SignalProtocolAddress senderAddress) {
1912 XmppAxolotlSession session = sessions.get(senderAddress);
1913 if (session == null) {
1914 session = recreateUncachedSession(senderAddress);
1915 if (session == null) {
1916 session = new XmppAxolotlSession(account, axolotlStore, senderAddress);
1917 }
1918 }
1919 return session;
1920 }
1921
1922 public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(
1923 XmppAxolotlMessage message, boolean postponePreKeyMessageHandling)
1924 throws NotEncryptedForThisDeviceException,
1925 BrokenSessionException,
1926 OutdatedSenderException {
1927 XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null;
1928
1929 XmppAxolotlSession session = getReceivingSession(message);
1930 int ownDeviceId = getOwnDeviceId();
1931 try {
1932 plaintextMessage = message.decrypt(session, ownDeviceId);
1933 Integer preKeyId = session.getPreKeyIdAndReset();
1934 if (preKeyId != null) {
1935 postPreKeyMessageHandling(session, postponePreKeyMessageHandling);
1936 }
1937 } catch (NotEncryptedForThisDeviceException e) {
1938 if (account.getJid().asBareJid().equals(message.getFrom().asBareJid())
1939 && message.getSenderDeviceId() == ownDeviceId) {
1940 Log.w(Config.LOGTAG, getLogprefix(account) + "Reflected omemo message received");
1941 } else {
1942 throw e;
1943 }
1944 } catch (final BrokenSessionException e) {
1945 throw e;
1946 } catch (final OutdatedSenderException e) {
1947 Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": " + e.getMessage());
1948 throw e;
1949 } catch (CryptoFailedException e) {
1950 Log.w(
1951 Config.LOGTAG,
1952 getLogprefix(account) + "Failed to decrypt message from " + message.getFrom(),
1953 e);
1954 }
1955
1956 if (session.isFresh() && plaintextMessage != null) {
1957 putFreshSession(session);
1958 }
1959
1960 return plaintextMessage;
1961 }
1962
1963 public void reportBrokenSessionException(BrokenSessionException e, boolean postpone) {
1964 Log.e(
1965 Config.LOGTAG,
1966 account.getJid().asBareJid()
1967 + ": broken session with "
1968 + e.getSignalProtocolAddress().toString()
1969 + " detected",
1970 e);
1971 if (postpone) {
1972 postponedHealing.add(e.getSignalProtocolAddress());
1973 } else {
1974 notifyRequiresHealing(e.getSignalProtocolAddress());
1975 }
1976 }
1977
1978 private void notifyRequiresHealing(final SignalProtocolAddress signalProtocolAddress) {
1979 if (healingAttempts.add(signalProtocolAddress)) {
1980 Log.d(
1981 Config.LOGTAG,
1982 account.getJid().asBareJid() + ": attempt to heal " + signalProtocolAddress);
1983 buildSessionFromPEP(
1984 signalProtocolAddress,
1985 new OnSessionBuildFromPep() {
1986 @Override
1987 public void onSessionBuildSuccessful() {
1988 Log.d(
1989 Config.LOGTAG,
1990 "successfully build new session from pep after detecting broken"
1991 + " session");
1992 completeSession(getReceivingSession(signalProtocolAddress));
1993 }
1994
1995 @Override
1996 public void onSessionBuildFailed() {
1997 Log.d(
1998 Config.LOGTAG,
1999 account.getJid().asBareJid()
2000 + ": unable to build new session from pep after"
2001 + " detecting broken session");
2002 }
2003 });
2004 } else {
2005 Log.d(
2006 Config.LOGTAG,
2007 account.getJid().asBareJid()
2008 + ": do not attempt to heal "
2009 + signalProtocolAddress
2010 + " again");
2011 }
2012 }
2013
2014 private void postPreKeyMessageHandling(
2015 final XmppAxolotlSession session, final boolean postpone) {
2016 if (postpone) {
2017 postponedSessions.add(session);
2018 } else {
2019 if (axolotlStore.flushPreKeys()) {
2020 publishBundlesIfNeeded(false, false);
2021 } else {
2022 Log.d(
2023 Config.LOGTAG,
2024 account.getJid().asBareJid() + ": nothing to flush. Not republishing key");
2025 }
2026 if (trustedOrPreviouslyResponded(session) && Config.AUTOMATICALLY_COMPLETE_SESSIONS) {
2027 completeSession(session);
2028 }
2029 }
2030 }
2031
2032 public void processPostponed() {
2033 if (postponedSessions.size() > 0) {
2034 if (axolotlStore.flushPreKeys()) {
2035 publishBundlesIfNeeded(false, false);
2036 }
2037 }
2038 final Iterator<XmppAxolotlSession> iterator = postponedSessions.iterator();
2039 while (iterator.hasNext()) {
2040 final XmppAxolotlSession session = iterator.next();
2041 if (trustedOrPreviouslyResponded(session) && Config.AUTOMATICALLY_COMPLETE_SESSIONS) {
2042 completeSession(session);
2043 }
2044 iterator.remove();
2045 }
2046 final Iterator<SignalProtocolAddress> postponedHealingAttemptsIterator =
2047 postponedHealing.iterator();
2048 while (postponedHealingAttemptsIterator.hasNext()) {
2049 notifyRequiresHealing(postponedHealingAttemptsIterator.next());
2050 postponedHealingAttemptsIterator.remove();
2051 }
2052 }
2053
2054 private boolean trustedOrPreviouslyResponded(XmppAxolotlSession session) {
2055 try {
2056 return trustedOrPreviouslyResponded(Jid.of(session.getRemoteAddress().getName()));
2057 } catch (IllegalArgumentException e) {
2058 return false;
2059 }
2060 }
2061
2062 public boolean trustedOrPreviouslyResponded(Jid jid) {
2063 final Contact contact = account.getRoster().getContact(jid);
2064 if (contact.showInRoster() || contact.isSelf()) {
2065 return true;
2066 }
2067 final Conversation conversation = mXmppConnectionService.find(account, jid);
2068 return conversation != null && conversation.sentMessagesCount() > 0;
2069 }
2070
2071 private void completeSession(XmppAxolotlSession session) {
2072 final XmppAxolotlMessage axolotlMessage =
2073 new XmppAxolotlMessage(account.getJid().asBareJid(), getOwnDeviceId());
2074 axolotlMessage.addDevice(session, true);
2075 try {
2076 final Jid jid = Jid.of(session.getRemoteAddress().getName());
2077 final var packet =
2078 mXmppConnectionService
2079 .getMessageGenerator()
2080 .generateKeyTransportMessage(jid, axolotlMessage);
2081 mXmppConnectionService.sendMessagePacket(account, packet);
2082 } catch (IllegalArgumentException e) {
2083 throw new Error(
2084 "Remote addresses are created from jid and should convert back to jid", e);
2085 }
2086 }
2087
2088 public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(
2089 XmppAxolotlMessage message, final boolean postponePreKeyMessageHandling) {
2090 final XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage;
2091 final XmppAxolotlSession session = getReceivingSession(message);
2092 try {
2093 keyTransportMessage = message.getParameters(session, getOwnDeviceId());
2094 Integer preKeyId = session.getPreKeyIdAndReset();
2095 if (preKeyId != null) {
2096 postPreKeyMessageHandling(session, postponePreKeyMessageHandling);
2097 }
2098 } catch (CryptoFailedException e) {
2099 Log.d(Config.LOGTAG, "could not decrypt keyTransport message " + e.getMessage());
2100 return null;
2101 }
2102
2103 if (session.isFresh() && keyTransportMessage != null) {
2104 putFreshSession(session);
2105 }
2106
2107 return keyTransportMessage;
2108 }
2109
2110 private ListenableFuture<XmppAxolotlSession> putFreshSession(XmppAxolotlSession session) {
2111 sessions.put(session);
2112 if (Config.X509_VERIFICATION) {
2113 if (session.getIdentityKey() != null) {
2114 return verifySessionWithPEP(session);
2115 } else {
2116 Log.e(
2117 Config.LOGTAG,
2118 account.getJid().asBareJid()
2119 + ": identity key was empty after reloading for x509 verification");
2120 }
2121 }
2122 return Futures.immediateFuture(session);
2123 }
2124
2125 public enum FetchStatus {
2126 PENDING,
2127 SUCCESS,
2128 SUCCESS_VERIFIED,
2129 TIMEOUT,
2130 SUCCESS_TRUSTED,
2131 ERROR
2132 }
2133
2134 public interface OnDeviceIdsFetched {
2135 void fetched(Jid jid, Set<Integer> deviceIds);
2136 }
2137
2138 public interface OnMultipleDeviceIdFetched {
2139 void fetched();
2140 }
2141
2142 interface OnSessionBuildFromPep {
2143 void onSessionBuildSuccessful();
2144
2145 void onSessionBuildFailed();
2146 }
2147
2148 private static class AxolotlAddressMap<T> {
2149 protected final Object MAP_LOCK = new Object();
2150 protected Map<String, Map<Integer, T>> map;
2151
2152 public AxolotlAddressMap() {
2153 this.map = new HashMap<>();
2154 }
2155
2156 public void put(SignalProtocolAddress address, T value) {
2157 synchronized (MAP_LOCK) {
2158 Map<Integer, T> devices = map.get(address.getName());
2159 if (devices == null) {
2160 devices = new HashMap<>();
2161 map.put(address.getName(), devices);
2162 }
2163 devices.put(address.getDeviceId(), value);
2164 }
2165 }
2166
2167 public T get(SignalProtocolAddress address) {
2168 synchronized (MAP_LOCK) {
2169 Map<Integer, T> devices = map.get(address.getName());
2170 if (devices == null) {
2171 return null;
2172 }
2173 return devices.get(address.getDeviceId());
2174 }
2175 }
2176
2177 public Map<Integer, T> getAll(String name) {
2178 synchronized (MAP_LOCK) {
2179 Map<Integer, T> devices = map.get(name);
2180 if (devices == null) {
2181 return new HashMap<>();
2182 }
2183 return devices;
2184 }
2185 }
2186
2187 public boolean hasAny(SignalProtocolAddress address) {
2188 synchronized (MAP_LOCK) {
2189 Map<Integer, T> devices = map.get(address.getName());
2190 return devices != null && !devices.isEmpty();
2191 }
2192 }
2193
2194 public void clear() {
2195 map.clear();
2196 }
2197 }
2198
2199 private static class SessionMap extends AxolotlAddressMap<XmppAxolotlSession> {
2200 private final XmppConnectionService xmppConnectionService;
2201 private final Account account;
2202
2203 public SessionMap(
2204 XmppConnectionService service, SQLiteAxolotlStore store, Account account) {
2205 super();
2206 this.xmppConnectionService = service;
2207 this.account = account;
2208 this.fillMap(store);
2209 }
2210
2211 public Set<Jid> findCounterpartsForSourceId(Integer sid) {
2212 Set<Jid> candidates = new HashSet<>();
2213 synchronized (MAP_LOCK) {
2214 for (Map.Entry<String, Map<Integer, XmppAxolotlSession>> entry : map.entrySet()) {
2215 String key = entry.getKey();
2216 if (entry.getValue().containsKey(sid)) {
2217 candidates.add(Jid.of(key));
2218 }
2219 }
2220 }
2221 return candidates;
2222 }
2223
2224 private void putDevicesForJid(
2225 String bareJid, List<Integer> deviceIds, SQLiteAxolotlStore store) {
2226 for (Integer deviceId : deviceIds) {
2227 SignalProtocolAddress axolotlAddress = new SignalProtocolAddress(bareJid, deviceId);
2228 IdentityKey identityKey =
2229 store.loadSession(axolotlAddress).getSessionState().getRemoteIdentityKey();
2230 if (Config.X509_VERIFICATION) {
2231 X509Certificate certificate =
2232 store.getFingerprintCertificate(
2233 CryptoHelper.bytesToHex(
2234 identityKey.getPublicKey().serialize()));
2235 if (certificate != null) {
2236 Bundle information =
2237 CryptoHelper.extractCertificateInformation(certificate);
2238 try {
2239 final String cn = information.getString("subject_cn");
2240 final Jid jid = Jid.of(bareJid);
2241 Log.d(Config.LOGTAG, "setting common name for " + jid + " to " + cn);
2242 account.getRoster().getContact(jid).setCommonName(cn);
2243 } catch (final IllegalArgumentException ignored) {
2244 // ignored
2245 }
2246 }
2247 }
2248 this.put(
2249 axolotlAddress,
2250 new XmppAxolotlSession(account, store, axolotlAddress, identityKey));
2251 }
2252 }
2253
2254 private void fillMap(SQLiteAxolotlStore store) {
2255 List<Integer> deviceIds =
2256 store.getSubDeviceSessions(account.getJid().asBareJid().toString());
2257 putDevicesForJid(account.getJid().asBareJid().toString(), deviceIds, store);
2258 for (String address : store.getKnownAddresses()) {
2259 deviceIds = store.getSubDeviceSessions(address);
2260 putDevicesForJid(address, deviceIds, store);
2261 }
2262 }
2263
2264 @Override
2265 public void put(SignalProtocolAddress address, XmppAxolotlSession value) {
2266 super.put(address, value);
2267 value.setNotFresh();
2268 }
2269
2270 public void put(XmppAxolotlSession session) {
2271 this.put(session.getRemoteAddress(), session);
2272 }
2273 }
2274
2275 private static class FetchStatusMap extends AxolotlAddressMap<FetchStatus> {
2276
2277 public void clearErrorFor(Jid jid) {
2278 synchronized (MAP_LOCK) {
2279 Map<Integer, FetchStatus> devices = this.map.get(jid.asBareJid().toString());
2280 if (devices == null) {
2281 return;
2282 }
2283 for (Map.Entry<Integer, FetchStatus> entry : devices.entrySet()) {
2284 if (entry.getValue() == FetchStatus.ERROR) {
2285 Log.d(
2286 Config.LOGTAG,
2287 "resetting error for "
2288 + jid.asBareJid()
2289 + "("
2290 + entry.getKey()
2291 + ")");
2292 entry.setValue(FetchStatus.TIMEOUT);
2293 }
2294 }
2295 }
2296 }
2297 }
2298
2299 public static class OmemoVerifiedPayload<T> {
2300 private final int deviceId;
2301 private final String fingerprint;
2302 private final T payload;
2303
2304 private OmemoVerifiedPayload(OmemoVerification omemoVerification, T payload) {
2305 this.deviceId = omemoVerification.getDeviceId();
2306 this.fingerprint = omemoVerification.getFingerprint();
2307 this.payload = payload;
2308 }
2309
2310 public int getDeviceId() {
2311 return deviceId;
2312 }
2313
2314 public String getFingerprint() {
2315 return fingerprint;
2316 }
2317
2318 public T getPayload() {
2319 return payload;
2320 }
2321 }
2322
2323 public static class NotVerifiedException extends SecurityException {
2324
2325 public NotVerifiedException(String message) {
2326 super(message);
2327 }
2328 }
2329}