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