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(final String content, Jid counterpart) {
1180 final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(account.getJid().asBareJid(), getOwnDeviceId());
1181 try {
1182 axolotlMessage.encrypt(content);
1183 } catch (CryptoFailedException e) {
1184 Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to encrypt message: " + e.getMessage());
1185 return null;
1186 }
1187 if (!buildHeader(axolotlMessage, counterpart)) return null;
1188 return axolotlMessage;
1189 }
1190
1191 @Nullable
1192 public XmppAxolotlMessage encrypt(final String content, Conversation conversation) {
1193 final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(account.getJid().asBareJid(), getOwnDeviceId());
1194 try {
1195 axolotlMessage.encrypt(content);
1196 } catch (CryptoFailedException e) {
1197 Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to encrypt message: " + e.getMessage());
1198 return null;
1199 }
1200 if (!buildHeader(axolotlMessage, conversation)) return null;
1201 return axolotlMessage;
1202 }
1203
1204 @Nullable
1205 public XmppAxolotlMessage encrypt(Message message) {
1206 final String content;
1207 if (message.hasFileOnRemoteHost()) {
1208 content = message.getFileParams().url;
1209 } else {
1210 content = message.getRawBody();
1211 }
1212
1213 if (message.isPrivateMessage()) {
1214 return encrypt(content, message.getTrueCounterpart());
1215 } else {
1216 return encrypt(content, (Conversation) message.getConversation());
1217 }
1218 }
1219
1220 public void preparePayloadMessage(final Message message, final boolean delay) {
1221 executor.execute(new Runnable() {
1222 @Override
1223 public void run() {
1224 XmppAxolotlMessage axolotlMessage = encrypt(message);
1225 if (axolotlMessage == null) {
1226 mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED);
1227 //mXmppConnectionService.updateConversationUi();
1228 } else {
1229 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Generated message, caching: " + message.getUuid());
1230 messageCache.put(message.getUuid(), axolotlMessage);
1231 mXmppConnectionService.resendMessage(message, delay, true);
1232 }
1233 }
1234 });
1235 }
1236
1237 private OmemoVerifiedIceUdpTransportInfo encrypt(final IceUdpTransportInfo element, final XmppAxolotlSession session) throws CryptoFailedException {
1238 final OmemoVerifiedIceUdpTransportInfo transportInfo = new OmemoVerifiedIceUdpTransportInfo();
1239 transportInfo.setAttributes(element.getAttributes());
1240 for (final Element child : element.getChildren()) {
1241 if ("fingerprint".equals(child.getName()) && Namespace.JINGLE_APPS_DTLS.equals(child.getNamespace())) {
1242 final Element fingerprint = new Element("fingerprint", Namespace.OMEMO_DTLS_SRTP_VERIFICATION);
1243 fingerprint.setAttribute("setup", child.getAttribute("setup"));
1244 fingerprint.setAttribute("hash", child.getAttribute("hash"));
1245 final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(account.getJid().asBareJid(), getOwnDeviceId());
1246 final String content = child.getContent();
1247 axolotlMessage.encrypt(content);
1248 axolotlMessage.addDevice(session, true);
1249 fingerprint.addChild(axolotlMessage.toElement());
1250 transportInfo.addChild(fingerprint);
1251 } else {
1252 transportInfo.addChild(child);
1253 }
1254 }
1255 return transportInfo;
1256 }
1257
1258
1259 public ListenableFuture<OmemoVerifiedPayload<OmemoVerifiedRtpContentMap>> encrypt(final RtpContentMap rtpContentMap, final Jid jid, final int deviceId) {
1260 return Futures.transformAsync(
1261 getSession(jid, deviceId),
1262 session -> encrypt(rtpContentMap, session),
1263 MoreExecutors.directExecutor()
1264 );
1265 }
1266
1267 private ListenableFuture<OmemoVerifiedPayload<OmemoVerifiedRtpContentMap>> encrypt(final RtpContentMap rtpContentMap, final XmppAxolotlSession session) {
1268 if (Config.REQUIRE_RTP_VERIFICATION) {
1269 requireVerification(session);
1270 }
1271 final ImmutableMap.Builder<String, DescriptionTransport<RtpDescription,IceUdpTransportInfo>> descriptionTransportBuilder = new ImmutableMap.Builder<>();
1272 final OmemoVerification omemoVerification = new OmemoVerification();
1273 omemoVerification.setDeviceId(session.getRemoteAddress().getDeviceId());
1274 omemoVerification.setSessionFingerprint(session.getFingerprint());
1275 for (final Map.Entry<String, DescriptionTransport<RtpDescription,IceUdpTransportInfo>> content : rtpContentMap.contents.entrySet()) {
1276 final DescriptionTransport<RtpDescription,IceUdpTransportInfo> descriptionTransport = content.getValue();
1277 final OmemoVerifiedIceUdpTransportInfo encryptedTransportInfo;
1278 try {
1279 encryptedTransportInfo = encrypt(descriptionTransport.transport, session);
1280 } catch (final CryptoFailedException e) {
1281 return Futures.immediateFailedFuture(e);
1282 }
1283 descriptionTransportBuilder.put(
1284 content.getKey(),
1285 new DescriptionTransport<>(descriptionTransport.senders, descriptionTransport.description, encryptedTransportInfo)
1286 );
1287 }
1288 return Futures.immediateFuture(
1289 new OmemoVerifiedPayload<>(
1290 omemoVerification,
1291 new OmemoVerifiedRtpContentMap(rtpContentMap.group, descriptionTransportBuilder.build())
1292 ));
1293 }
1294
1295 private ListenableFuture<XmppAxolotlSession> getSession(final Jid jid, final int deviceId) {
1296 final SignalProtocolAddress address = new SignalProtocolAddress(jid.asBareJid().toString(), deviceId);
1297 final XmppAxolotlSession session = sessions.get(address);
1298 if (session == null) {
1299 return buildSessionFromPEP(address);
1300 }
1301 return Futures.immediateFuture(session);
1302 }
1303
1304 public ListenableFuture<OmemoVerifiedPayload<RtpContentMap>> decrypt(OmemoVerifiedRtpContentMap omemoVerifiedRtpContentMap, final Jid from) {
1305 final ImmutableMap.Builder<String, DescriptionTransport<RtpDescription,IceUdpTransportInfo>> descriptionTransportBuilder = new ImmutableMap.Builder<>();
1306 final OmemoVerification omemoVerification = new OmemoVerification();
1307 final ImmutableList.Builder<ListenableFuture<XmppAxolotlSession>> pepVerificationFutures = new ImmutableList.Builder<>();
1308 for (final Map.Entry<String, DescriptionTransport<RtpDescription,IceUdpTransportInfo>> content : omemoVerifiedRtpContentMap.contents.entrySet()) {
1309 final DescriptionTransport<RtpDescription,IceUdpTransportInfo> descriptionTransport = content.getValue();
1310 final OmemoVerifiedPayload<IceUdpTransportInfo> decryptedTransport;
1311 try {
1312 decryptedTransport = decrypt((OmemoVerifiedIceUdpTransportInfo) descriptionTransport.transport, from, pepVerificationFutures);
1313 } catch (CryptoFailedException e) {
1314 return Futures.immediateFailedFuture(e);
1315 }
1316 omemoVerification.setOrEnsureEqual(decryptedTransport);
1317 descriptionTransportBuilder.put(
1318 content.getKey(),
1319 new DescriptionTransport<>(descriptionTransport.senders, descriptionTransport.description, decryptedTransport.payload)
1320 );
1321 }
1322 processPostponed();
1323 final ImmutableList<ListenableFuture<XmppAxolotlSession>> sessionFutures = pepVerificationFutures.build();
1324 return Futures.transform(
1325 Futures.allAsList(sessionFutures),
1326 sessions -> {
1327 if (Config.REQUIRE_RTP_VERIFICATION) {
1328 for (XmppAxolotlSession session : sessions) {
1329 requireVerification(session);
1330 }
1331 }
1332 return new OmemoVerifiedPayload<>(
1333 omemoVerification,
1334 new RtpContentMap(omemoVerifiedRtpContentMap.group, descriptionTransportBuilder.build())
1335 );
1336
1337 },
1338 MoreExecutors.directExecutor()
1339 );
1340 }
1341
1342 private OmemoVerifiedPayload<IceUdpTransportInfo> decrypt(final OmemoVerifiedIceUdpTransportInfo verifiedIceUdpTransportInfo, final Jid from, ImmutableList.Builder<ListenableFuture<XmppAxolotlSession>> pepVerificationFutures) throws CryptoFailedException {
1343 final IceUdpTransportInfo transportInfo = new IceUdpTransportInfo();
1344 transportInfo.setAttributes(verifiedIceUdpTransportInfo.getAttributes());
1345 final OmemoVerification omemoVerification = new OmemoVerification();
1346 for (final Element child : verifiedIceUdpTransportInfo.getChildren()) {
1347 if ("fingerprint".equals(child.getName()) && Namespace.OMEMO_DTLS_SRTP_VERIFICATION.equals(child.getNamespace())) {
1348 final Element fingerprint = new Element("fingerprint", Namespace.JINGLE_APPS_DTLS);
1349 fingerprint.setAttribute("setup", child.getAttribute("setup"));
1350 fingerprint.setAttribute("hash", child.getAttribute("hash"));
1351 final Element encrypted = child.findChildEnsureSingle(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX);
1352 final XmppAxolotlMessage xmppAxolotlMessage = XmppAxolotlMessage.fromElement(encrypted, from.asBareJid());
1353 final XmppAxolotlSession session = getReceivingSession(xmppAxolotlMessage);
1354 final XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintext = xmppAxolotlMessage.decrypt(session, getOwnDeviceId());
1355 final Integer preKeyId = session.getPreKeyIdAndReset();
1356 if (preKeyId != null) {
1357 postponedSessions.add(session);
1358 }
1359 if (session.isFresh()) {
1360 pepVerificationFutures.add(putFreshSession(session));
1361 } else if (Config.REQUIRE_RTP_VERIFICATION) {
1362 pepVerificationFutures.add(Futures.immediateFuture(session));
1363 }
1364 fingerprint.setContent(plaintext.getPlaintext());
1365 omemoVerification.setDeviceId(session.getRemoteAddress().getDeviceId());
1366 omemoVerification.setSessionFingerprint(plaintext.getFingerprint());
1367 transportInfo.addChild(fingerprint);
1368 } else {
1369 transportInfo.addChild(child);
1370 }
1371 }
1372 return new OmemoVerifiedPayload<>(omemoVerification, transportInfo);
1373 }
1374
1375 private static void requireVerification(final XmppAxolotlSession session) {
1376 if (session.getTrust().isVerified()) {
1377 return;
1378 }
1379 throw new NotVerifiedException(String.format(
1380 "session with %s was not verified",
1381 session.getFingerprint()
1382 ));
1383 }
1384
1385 public ListenableFuture<XmppAxolotlMessage> prepareKeyTransportMessage(final Conversation conversation) {
1386 return Futures.submit(()->{
1387 final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(account.getJid().asBareJid(), getOwnDeviceId());
1388 if (buildHeader(axolotlMessage, conversation)) {
1389 return axolotlMessage;
1390 } else {
1391 throw new IllegalStateException("No session to decrypt to");
1392 }
1393 },executor);
1394 }
1395
1396 public XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message) {
1397 XmppAxolotlMessage axolotlMessage = messageCache.get(message.getUuid());
1398 if (axolotlMessage != null) {
1399 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache hit: " + message.getUuid());
1400 messageCache.remove(message.getUuid());
1401 } else {
1402 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache miss: " + message.getUuid());
1403 }
1404 return axolotlMessage;
1405 }
1406
1407 private XmppAxolotlSession recreateUncachedSession(SignalProtocolAddress address) {
1408 IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
1409 return (identityKey != null)
1410 ? new XmppAxolotlSession(account, axolotlStore, address, identityKey)
1411 : null;
1412 }
1413
1414 private XmppAxolotlSession getReceivingSession(XmppAxolotlMessage message) {
1415 SignalProtocolAddress senderAddress = new SignalProtocolAddress(message.getFrom().toString(), message.getSenderDeviceId());
1416 return getReceivingSession(senderAddress);
1417
1418 }
1419
1420 private XmppAxolotlSession getReceivingSession(SignalProtocolAddress senderAddress) {
1421 XmppAxolotlSession session = sessions.get(senderAddress);
1422 if (session == null) {
1423 session = recreateUncachedSession(senderAddress);
1424 if (session == null) {
1425 session = new XmppAxolotlSession(account, axolotlStore, senderAddress);
1426 }
1427 }
1428 return session;
1429 }
1430
1431 public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message, boolean postponePreKeyMessageHandling) throws NotEncryptedForThisDeviceException, BrokenSessionException, OutdatedSenderException {
1432 XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null;
1433
1434 XmppAxolotlSession session = getReceivingSession(message);
1435 int ownDeviceId = getOwnDeviceId();
1436 try {
1437 plaintextMessage = message.decrypt(session, ownDeviceId);
1438 Integer preKeyId = session.getPreKeyIdAndReset();
1439 if (preKeyId != null) {
1440 postPreKeyMessageHandling(session, postponePreKeyMessageHandling);
1441 }
1442 } catch (NotEncryptedForThisDeviceException e) {
1443 if (account.getJid().asBareJid().equals(message.getFrom().asBareJid()) && message.getSenderDeviceId() == ownDeviceId) {
1444 Log.w(Config.LOGTAG, getLogprefix(account) + "Reflected omemo message received");
1445 } else {
1446 throw e;
1447 }
1448 } catch (final BrokenSessionException e) {
1449 throw e;
1450 } catch (final OutdatedSenderException e) {
1451 Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": " + e.getMessage());
1452 throw e;
1453 } catch (CryptoFailedException e) {
1454 Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message from " + message.getFrom(), e);
1455 }
1456
1457 if (session.isFresh() && plaintextMessage != null) {
1458 putFreshSession(session);
1459 }
1460
1461 return plaintextMessage;
1462 }
1463
1464 public void reportBrokenSessionException(BrokenSessionException e, boolean postpone) {
1465 Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": broken session with " + e.getSignalProtocolAddress().toString() + " detected", e);
1466 if (postpone) {
1467 postponedHealing.add(e.getSignalProtocolAddress());
1468 } else {
1469 notifyRequiresHealing(e.getSignalProtocolAddress());
1470 }
1471 }
1472
1473 private void notifyRequiresHealing(final SignalProtocolAddress signalProtocolAddress) {
1474 if (healingAttempts.add(signalProtocolAddress)) {
1475 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": attempt to heal " + signalProtocolAddress);
1476 buildSessionFromPEP(signalProtocolAddress, new OnSessionBuildFromPep() {
1477 @Override
1478 public void onSessionBuildSuccessful() {
1479 Log.d(Config.LOGTAG, "successfully build new session from pep after detecting broken session");
1480 completeSession(getReceivingSession(signalProtocolAddress));
1481 }
1482
1483 @Override
1484 public void onSessionBuildFailed() {
1485 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to build new session from pep after detecting broken session");
1486 }
1487 });
1488 } else {
1489 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": do not attempt to heal " + signalProtocolAddress + " again");
1490 }
1491 }
1492
1493 private void postPreKeyMessageHandling(final XmppAxolotlSession session, final boolean postpone) {
1494 if (postpone) {
1495 postponedSessions.add(session);
1496 } else {
1497 if (axolotlStore.flushPreKeys()) {
1498 publishBundlesIfNeeded(false, false);
1499 } else {
1500 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": nothing to flush. Not republishing key");
1501 }
1502 if (trustedOrPreviouslyResponded(session) && Config.AUTOMATICALLY_COMPLETE_SESSIONS) {
1503 completeSession(session);
1504 }
1505 }
1506 }
1507
1508 public void processPostponed() {
1509 if (postponedSessions.size() > 0) {
1510 if (axolotlStore.flushPreKeys()) {
1511 publishBundlesIfNeeded(false, false);
1512 }
1513 }
1514 final Iterator<XmppAxolotlSession> iterator = postponedSessions.iterator();
1515 while (iterator.hasNext()) {
1516 final XmppAxolotlSession session = iterator.next();
1517 if (trustedOrPreviouslyResponded(session) && Config.AUTOMATICALLY_COMPLETE_SESSIONS) {
1518 completeSession(session);
1519 }
1520 iterator.remove();
1521 }
1522 final Iterator<SignalProtocolAddress> postponedHealingAttemptsIterator = postponedHealing.iterator();
1523 while (postponedHealingAttemptsIterator.hasNext()) {
1524 notifyRequiresHealing(postponedHealingAttemptsIterator.next());
1525 postponedHealingAttemptsIterator.remove();
1526 }
1527 }
1528
1529 private boolean trustedOrPreviouslyResponded(XmppAxolotlSession session) {
1530 try {
1531 return trustedOrPreviouslyResponded(Jid.of(session.getRemoteAddress().getName()));
1532 } catch (IllegalArgumentException e) {
1533 return false;
1534 }
1535 }
1536
1537 public boolean trustedOrPreviouslyResponded(Jid jid) {
1538 final Contact contact = account.getRoster().getContact(jid);
1539 if (contact.showInRoster() || contact.isSelf()) {
1540 return true;
1541 }
1542 final Conversation conversation = mXmppConnectionService.find(account, jid);
1543 return conversation != null && conversation.sentMessagesCount() > 0;
1544 }
1545
1546 private void completeSession(XmppAxolotlSession session) {
1547 final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(account.getJid().asBareJid(), getOwnDeviceId());
1548 axolotlMessage.addDevice(session, true);
1549 try {
1550 final Jid jid = Jid.of(session.getRemoteAddress().getName());
1551 final var packet = mXmppConnectionService.getMessageGenerator().generateKeyTransportMessage(jid, axolotlMessage);
1552 mXmppConnectionService.sendMessagePacket(account, packet);
1553 } catch (IllegalArgumentException e) {
1554 throw new Error("Remote addresses are created from jid and should convert back to jid", e);
1555 }
1556 }
1557
1558 public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message, final boolean postponePreKeyMessageHandling) {
1559 final XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage;
1560 final XmppAxolotlSession session = getReceivingSession(message);
1561 try {
1562 keyTransportMessage = message.getParameters(session, getOwnDeviceId());
1563 Integer preKeyId = session.getPreKeyIdAndReset();
1564 if (preKeyId != null) {
1565 postPreKeyMessageHandling(session, postponePreKeyMessageHandling);
1566 }
1567 } catch (CryptoFailedException e) {
1568 Log.d(Config.LOGTAG, "could not decrypt keyTransport message " + e.getMessage());
1569 return null;
1570 }
1571
1572 if (session.isFresh() && keyTransportMessage != null) {
1573 putFreshSession(session);
1574 }
1575
1576 return keyTransportMessage;
1577 }
1578
1579 private ListenableFuture<XmppAxolotlSession> putFreshSession(XmppAxolotlSession session) {
1580 sessions.put(session);
1581 if (Config.X509_VERIFICATION) {
1582 if (session.getIdentityKey() != null) {
1583 return verifySessionWithPEP(session);
1584 } else {
1585 Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": identity key was empty after reloading for x509 verification");
1586 }
1587 }
1588 return Futures.immediateFuture(session);
1589 }
1590
1591 public enum FetchStatus {
1592 PENDING,
1593 SUCCESS,
1594 SUCCESS_VERIFIED,
1595 TIMEOUT,
1596 SUCCESS_TRUSTED,
1597 ERROR
1598 }
1599
1600 public interface OnDeviceIdsFetched {
1601 void fetched(Jid jid, Set<Integer> deviceIds);
1602 }
1603
1604
1605 public interface OnMultipleDeviceIdFetched {
1606 void fetched();
1607 }
1608
1609 interface OnSessionBuildFromPep {
1610 void onSessionBuildSuccessful();
1611
1612 void onSessionBuildFailed();
1613 }
1614
1615 private static class AxolotlAddressMap<T> {
1616 protected final Object MAP_LOCK = new Object();
1617 protected Map<String, Map<Integer, T>> map;
1618
1619 public AxolotlAddressMap() {
1620 this.map = new HashMap<>();
1621 }
1622
1623 public void put(SignalProtocolAddress address, T value) {
1624 synchronized (MAP_LOCK) {
1625 Map<Integer, T> devices = map.get(address.getName());
1626 if (devices == null) {
1627 devices = new HashMap<>();
1628 map.put(address.getName(), devices);
1629 }
1630 devices.put(address.getDeviceId(), value);
1631 }
1632 }
1633
1634 public T get(SignalProtocolAddress address) {
1635 synchronized (MAP_LOCK) {
1636 Map<Integer, T> devices = map.get(address.getName());
1637 if (devices == null) {
1638 return null;
1639 }
1640 return devices.get(address.getDeviceId());
1641 }
1642 }
1643
1644 public Map<Integer, T> getAll(String name) {
1645 synchronized (MAP_LOCK) {
1646 Map<Integer, T> devices = map.get(name);
1647 if (devices == null) {
1648 return new HashMap<>();
1649 }
1650 return devices;
1651 }
1652 }
1653
1654 public boolean hasAny(SignalProtocolAddress address) {
1655 synchronized (MAP_LOCK) {
1656 Map<Integer, T> devices = map.get(address.getName());
1657 return devices != null && !devices.isEmpty();
1658 }
1659 }
1660
1661 public void clear() {
1662 map.clear();
1663 }
1664
1665 }
1666
1667 private static class SessionMap extends AxolotlAddressMap<XmppAxolotlSession> {
1668 private final XmppConnectionService xmppConnectionService;
1669 private final Account account;
1670
1671 public SessionMap(XmppConnectionService service, SQLiteAxolotlStore store, Account account) {
1672 super();
1673 this.xmppConnectionService = service;
1674 this.account = account;
1675 this.fillMap(store);
1676 }
1677
1678 public Set<Jid> findCounterpartsForSourceId(Integer sid) {
1679 Set<Jid> candidates = new HashSet<>();
1680 synchronized (MAP_LOCK) {
1681 for (Map.Entry<String, Map<Integer, XmppAxolotlSession>> entry : map.entrySet()) {
1682 String key = entry.getKey();
1683 if (entry.getValue().containsKey(sid)) {
1684 candidates.add(Jid.of(key));
1685 }
1686 }
1687 }
1688 return candidates;
1689 }
1690
1691 private void putDevicesForJid(String bareJid, List<Integer> deviceIds, SQLiteAxolotlStore store) {
1692 for (Integer deviceId : deviceIds) {
1693 SignalProtocolAddress axolotlAddress = new SignalProtocolAddress(bareJid, deviceId);
1694 IdentityKey identityKey = store.loadSession(axolotlAddress).getSessionState().getRemoteIdentityKey();
1695 if (Config.X509_VERIFICATION) {
1696 X509Certificate certificate = store.getFingerprintCertificate(CryptoHelper.bytesToHex(identityKey.getPublicKey().serialize()));
1697 if (certificate != null) {
1698 Bundle information = CryptoHelper.extractCertificateInformation(certificate);
1699 try {
1700 final String cn = information.getString("subject_cn");
1701 final Jid jid = Jid.of(bareJid);
1702 Log.d(Config.LOGTAG, "setting common name for " + jid + " to " + cn);
1703 account.getRoster().getContact(jid).setCommonName(cn);
1704 } catch (final IllegalArgumentException ignored) {
1705 //ignored
1706 }
1707 }
1708 }
1709 this.put(axolotlAddress, new XmppAxolotlSession(account, store, axolotlAddress, identityKey));
1710 }
1711 }
1712
1713 private void fillMap(SQLiteAxolotlStore store) {
1714 List<Integer> deviceIds = store.getSubDeviceSessions(account.getJid().asBareJid().toString());
1715 putDevicesForJid(account.getJid().asBareJid().toString(), deviceIds, store);
1716 for (String address : store.getKnownAddresses()) {
1717 deviceIds = store.getSubDeviceSessions(address);
1718 putDevicesForJid(address, deviceIds, store);
1719 }
1720 }
1721
1722 @Override
1723 public void put(SignalProtocolAddress address, XmppAxolotlSession value) {
1724 super.put(address, value);
1725 value.setNotFresh();
1726 }
1727
1728 public void put(XmppAxolotlSession session) {
1729 this.put(session.getRemoteAddress(), session);
1730 }
1731 }
1732
1733 private static class FetchStatusMap extends AxolotlAddressMap<FetchStatus> {
1734
1735 public void clearErrorFor(Jid jid) {
1736 synchronized (MAP_LOCK) {
1737 Map<Integer, FetchStatus> devices = this.map.get(jid.asBareJid().toString());
1738 if (devices == null) {
1739 return;
1740 }
1741 for (Map.Entry<Integer, FetchStatus> entry : devices.entrySet()) {
1742 if (entry.getValue() == FetchStatus.ERROR) {
1743 Log.d(Config.LOGTAG, "resetting error for " + jid.asBareJid() + "(" + entry.getKey() + ")");
1744 entry.setValue(FetchStatus.TIMEOUT);
1745 }
1746 }
1747 }
1748 }
1749 }
1750
1751 public static class OmemoVerifiedPayload<T> {
1752 private final int deviceId;
1753 private final String fingerprint;
1754 private final T payload;
1755
1756 private OmemoVerifiedPayload(OmemoVerification omemoVerification, T payload) {
1757 this.deviceId = omemoVerification.getDeviceId();
1758 this.fingerprint = omemoVerification.getFingerprint();
1759 this.payload = payload;
1760 }
1761
1762 public int getDeviceId() {
1763 return deviceId;
1764 }
1765
1766 public String getFingerprint() {
1767 return fingerprint;
1768 }
1769
1770 public T getPayload() {
1771 return payload;
1772 }
1773 }
1774
1775 public static class NotVerifiedException extends SecurityException {
1776
1777 public NotVerifiedException(String message) {
1778 super(message);
1779 }
1780
1781 }
1782}