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