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