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