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(String fingerprint, FingerprintStatus status) {
740 axolotlStore.setFingerprintStatus(fingerprint, status);
741 }
742
743 private ListenableFuture<XmppAxolotlSession> verifySessionWithPEP(final XmppAxolotlSession session) {
744 Log.d(Config.LOGTAG, "trying to verify fresh session (" + session.getRemoteAddress().getName() + ") with pep");
745 final SignalProtocolAddress address = session.getRemoteAddress();
746 final IdentityKey identityKey = session.getIdentityKey();
747 final Jid jid;
748 try {
749 jid = Jid.of(address.getName());
750 } catch (final IllegalArgumentException e) {
751 fetchStatusMap.put(address, FetchStatus.SUCCESS);
752 finishBuildingSessionsFromPEP(address);
753 return Futures.immediateFuture(session);
754 }
755 final SettableFuture<XmppAxolotlSession> future = SettableFuture.create();
756 final IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(jid, address.getDeviceId());
757 mXmppConnectionService.sendIqPacket(account, packet, (account, response) -> {
758 Pair<X509Certificate[], byte[]> verification = mXmppConnectionService.getIqParser().verification(response);
759 if (verification != null) {
760 try {
761 Signature verifier = Signature.getInstance("sha256WithRSA");
762 verifier.initVerify(verification.first[0]);
763 verifier.update(identityKey.serialize());
764 if (verifier.verify(verification.second)) {
765 try {
766 mXmppConnectionService.getMemorizingTrustManager().getNonInteractive().checkClientTrusted(verification.first, "RSA");
767 String fingerprint = session.getFingerprint();
768 Log.d(Config.LOGTAG, "verified session with x.509 signature. fingerprint was: " + fingerprint);
769 setFingerprintTrust(fingerprint, FingerprintStatus.createActiveVerified(true));
770 axolotlStore.setFingerprintCertificate(fingerprint, verification.first[0]);
771 fetchStatusMap.put(address, FetchStatus.SUCCESS_VERIFIED);
772 Bundle information = CryptoHelper.extractCertificateInformation(verification.first[0]);
773 try {
774 final String cn = information.getString("subject_cn");
775 final Jid jid1 = Jid.of(address.getName());
776 Log.d(Config.LOGTAG, "setting common name for " + jid1 + " to " + cn);
777 account.getRoster().getContact(jid1).setCommonName(cn);
778 } catch (final IllegalArgumentException ignored) {
779 //ignored
780 }
781 finishBuildingSessionsFromPEP(address);
782 future.set(session);
783 return;
784 } catch (Exception e) {
785 Log.d(Config.LOGTAG, "could not verify certificate");
786 }
787 }
788 } catch (Exception e) {
789 Log.d(Config.LOGTAG, "error during verification " + e.getMessage());
790 }
791 } else {
792 Log.d(Config.LOGTAG, "no verification found");
793 }
794 fetchStatusMap.put(address, FetchStatus.SUCCESS);
795 finishBuildingSessionsFromPEP(address);
796 future.set(session);
797 });
798 return future;
799 }
800
801 private void finishBuildingSessionsFromPEP(final SignalProtocolAddress address) {
802 SignalProtocolAddress ownAddress = new SignalProtocolAddress(account.getJid().asBareJid().toString(), 0);
803 Map<Integer, FetchStatus> own = fetchStatusMap.getAll(ownAddress.getName());
804 Map<Integer, FetchStatus> remote = fetchStatusMap.getAll(address.getName());
805 if (!own.containsValue(FetchStatus.PENDING) && !remote.containsValue(FetchStatus.PENDING)) {
806 FetchStatus report = null;
807 if (own.containsValue(FetchStatus.SUCCESS) || remote.containsValue(FetchStatus.SUCCESS)) {
808 report = FetchStatus.SUCCESS;
809 } else if (own.containsValue(FetchStatus.SUCCESS_VERIFIED) || remote.containsValue(FetchStatus.SUCCESS_VERIFIED)) {
810 report = FetchStatus.SUCCESS_VERIFIED;
811 } else if (own.containsValue(FetchStatus.SUCCESS_TRUSTED) || remote.containsValue(FetchStatus.SUCCESS_TRUSTED)) {
812 report = FetchStatus.SUCCESS_TRUSTED;
813 } else if (own.containsValue(FetchStatus.ERROR) || remote.containsValue(FetchStatus.ERROR)) {
814 report = FetchStatus.ERROR;
815 }
816 mXmppConnectionService.keyStatusUpdated(report);
817 }
818 if (Config.REMOVE_BROKEN_DEVICES) {
819 Set<Integer> ownDeviceIds = new HashSet<>(getOwnDeviceIds());
820 boolean publish = false;
821 for (Map.Entry<Integer, FetchStatus> entry : own.entrySet()) {
822 int id = entry.getKey();
823 if (entry.getValue() == FetchStatus.ERROR && PREVIOUSLY_REMOVED_FROM_ANNOUNCEMENT.add(id) && ownDeviceIds.remove(id)) {
824 publish = true;
825 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": error fetching own device with id " + id + ". removing from announcement");
826 }
827 }
828 if (publish) {
829 publishOwnDeviceId(ownDeviceIds);
830 }
831 }
832 }
833
834 public boolean hasEmptyDeviceList(Jid jid) {
835 return !hasAny(jid) && (!deviceIds.containsKey(jid) || deviceIds.get(jid).isEmpty());
836 }
837
838 public void fetchDeviceIds(final Jid jid) {
839 fetchDeviceIds(jid, null);
840 }
841
842 private void fetchDeviceIds(final Jid jid, OnDeviceIdsFetched callback) {
843 IqPacket packet;
844 synchronized (this.fetchDeviceIdsMap) {
845 List<OnDeviceIdsFetched> callbacks = this.fetchDeviceIdsMap.get(jid);
846 if (callbacks != null) {
847 if (callback != null) {
848 callbacks.add(callback);
849 }
850 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": fetching device ids for " + jid + " already running. adding callback");
851 packet = null;
852 } else {
853 callbacks = new ArrayList<>();
854 if (callback != null) {
855 callbacks.add(callback);
856 }
857 this.fetchDeviceIdsMap.put(jid, callbacks);
858 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": fetching device ids for " + jid);
859 packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(jid);
860 }
861 }
862 if (packet != null) {
863 mXmppConnectionService.sendIqPacket(account, packet, (account, response) -> {
864 if (response.getType() == IqPacket.TYPE.RESULT) {
865 fetchDeviceListStatus.put(jid, true);
866 Element item = mXmppConnectionService.getIqParser().getItem(response);
867 Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
868 registerDevices(jid, deviceIds);
869 final List<OnDeviceIdsFetched> callbacks;
870 synchronized (fetchDeviceIdsMap) {
871 callbacks = fetchDeviceIdsMap.remove(jid);
872 }
873 if (callbacks != null) {
874 for (OnDeviceIdsFetched c : callbacks) {
875 c.fetched(jid, deviceIds);
876 }
877 }
878 } else {
879 if (response.getType() == IqPacket.TYPE.TIMEOUT) {
880 fetchDeviceListStatus.remove(jid);
881 } else {
882 fetchDeviceListStatus.put(jid, false);
883 }
884 final List<OnDeviceIdsFetched> callbacks;
885 synchronized (fetchDeviceIdsMap) {
886 callbacks = fetchDeviceIdsMap.remove(jid);
887 }
888 if (callbacks != null) {
889 for (OnDeviceIdsFetched c : callbacks) {
890 c.fetched(jid, null);
891 }
892 }
893 }
894 });
895 }
896 }
897
898 private void fetchDeviceIds(List<Jid> jids, final OnMultipleDeviceIdFetched callback) {
899 final ArrayList<Jid> unfinishedJids = new ArrayList<>(jids);
900 synchronized (unfinishedJids) {
901 for (Jid jid : unfinishedJids) {
902 fetchDeviceIds(jid, (j, deviceIds) -> {
903 synchronized (unfinishedJids) {
904 unfinishedJids.remove(j);
905 if (unfinishedJids.size() == 0 && callback != null) {
906 callback.fetched();
907 }
908 }
909 });
910 }
911 }
912 }
913
914 private ListenableFuture<XmppAxolotlSession> buildSessionFromPEP(final SignalProtocolAddress address) {
915 return buildSessionFromPEP(address, null);
916 }
917
918 private ListenableFuture<XmppAxolotlSession> buildSessionFromPEP(final SignalProtocolAddress address, OnSessionBuildFromPep callback) {
919 final SettableFuture<XmppAxolotlSession> sessionSettableFuture = SettableFuture.create();
920 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new session for " + address.toString());
921 if (address.equals(getOwnAxolotlAddress())) {
922 throw new AssertionError("We should NEVER build a session with ourselves. What happened here?!");
923 }
924 final Jid jid = Jid.of(address.getName());
925 final boolean oneOfOurs = jid.asBareJid().equals(account.getJid().asBareJid());
926 IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(jid, address.getDeviceId());
927 mXmppConnectionService.sendIqPacket(account, bundlesPacket, (account, packet) -> {
928 if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
929 fetchStatusMap.put(address, FetchStatus.TIMEOUT);
930 sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. Timeout"));
931 } else if (packet.getType() == IqPacket.TYPE.RESULT) {
932 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received preKey IQ packet, processing...");
933 final IqParser parser = mXmppConnectionService.getIqParser();
934 final List<PreKeyBundle> preKeyBundleList = parser.preKeys(packet);
935 final PreKeyBundle bundle = parser.bundle(packet);
936 if (preKeyBundleList.isEmpty() || bundle == null) {
937 Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "preKey IQ packet invalid: " + packet);
938 fetchStatusMap.put(address, FetchStatus.ERROR);
939 finishBuildingSessionsFromPEP(address);
940 if (callback != null) {
941 callback.onSessionBuildFailed();
942 }
943 sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. IQ Packet Invalid"));
944 return;
945 }
946 Random random = new Random();
947 final PreKeyBundle preKey = preKeyBundleList.get(random.nextInt(preKeyBundleList.size()));
948 if (preKey == null) {
949 //should never happen
950 fetchStatusMap.put(address, FetchStatus.ERROR);
951 finishBuildingSessionsFromPEP(address);
952 if (callback != null) {
953 callback.onSessionBuildFailed();
954 }
955 sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. No suitable PreKey found"));
956 return;
957 }
958
959 final PreKeyBundle preKeyBundle = new PreKeyBundle(0, address.getDeviceId(),
960 preKey.getPreKeyId(), preKey.getPreKey(),
961 bundle.getSignedPreKeyId(), bundle.getSignedPreKey(),
962 bundle.getSignedPreKeySignature(), bundle.getIdentityKey());
963
964 try {
965 SessionBuilder builder = new SessionBuilder(axolotlStore, address);
966 builder.process(preKeyBundle);
967 XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey());
968 sessions.put(address, session);
969 if (Config.X509_VERIFICATION) {
970 sessionSettableFuture.setFuture(verifySessionWithPEP(session)); //TODO; maybe inject callback in here too
971 } else {
972 FingerprintStatus status = getFingerprintTrust(CryptoHelper.bytesToHex(bundle.getIdentityKey().getPublicKey().serialize()));
973 FetchStatus fetchStatus;
974 if (status != null && status.isVerified()) {
975 fetchStatus = FetchStatus.SUCCESS_VERIFIED;
976 } else if (status != null && status.isTrusted()) {
977 fetchStatus = FetchStatus.SUCCESS_TRUSTED;
978 } else {
979 fetchStatus = FetchStatus.SUCCESS;
980 }
981 fetchStatusMap.put(address, fetchStatus);
982 finishBuildingSessionsFromPEP(address);
983 if (callback != null) {
984 callback.onSessionBuildSuccessful();
985 }
986 sessionSettableFuture.set(session);
987 }
988 } catch (UntrustedIdentityException | InvalidKeyException e) {
989 Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error building session for " + address + ": "
990 + e.getClass().getName() + ", " + e.getMessage());
991 fetchStatusMap.put(address, FetchStatus.ERROR);
992 finishBuildingSessionsFromPEP(address);
993 if (oneOfOurs && cleanedOwnDeviceIds.add(address.getDeviceId())) {
994 removeFromDeviceAnnouncement(address.getDeviceId());
995 }
996 if (callback != null) {
997 callback.onSessionBuildFailed();
998 }
999 sessionSettableFuture.setException(new CryptoFailedException(e));
1000 }
1001 } else {
1002 fetchStatusMap.put(address, FetchStatus.ERROR);
1003 Element error = packet.findChild("error");
1004 boolean itemNotFound = error != null && error.hasChild("item-not-found");
1005 Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while building session:" + packet.findChild("error"));
1006 finishBuildingSessionsFromPEP(address);
1007 if (oneOfOurs && itemNotFound && cleanedOwnDeviceIds.add(address.getDeviceId())) {
1008 removeFromDeviceAnnouncement(address.getDeviceId());
1009 }
1010 if (callback != null) {
1011 callback.onSessionBuildFailed();
1012 }
1013 sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. IQ Packet Error"));
1014 }
1015 });
1016 return sessionSettableFuture;
1017 }
1018
1019 private void removeFromDeviceAnnouncement(Integer id) {
1020 HashSet<Integer> temp = new HashSet<>(getOwnDeviceIds());
1021 if (temp.remove(id)) {
1022 Log.d(Config.LOGTAG, account.getJid().asBareJid() + " remove own device id " + id + " from announcement. devices left:" + temp);
1023 publishOwnDeviceId(temp);
1024 }
1025 }
1026
1027 public Set<SignalProtocolAddress> findDevicesWithoutSession(final Conversation conversation) {
1028 Set<SignalProtocolAddress> addresses = new HashSet<>();
1029 for (Jid jid : getCryptoTargets(conversation)) {
1030 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Finding devices without session for " + jid);
1031 final Set<Integer> ids = deviceIds.get(jid);
1032 if (ids != null && !ids.isEmpty()) {
1033 for (Integer foreignId : ids) {
1034 SignalProtocolAddress address = new SignalProtocolAddress(jid.toString(), foreignId);
1035 if (sessions.get(address) == null) {
1036 IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
1037 if (identityKey != null) {
1038 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
1039 XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
1040 sessions.put(address, session);
1041 } else {
1042 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + jid + ":" + foreignId);
1043 if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
1044 addresses.add(address);
1045 } else {
1046 Log.d(Config.LOGTAG, getLogprefix(account) + "skipping over " + address + " because it's broken");
1047 }
1048 }
1049 }
1050 }
1051 } else {
1052 mXmppConnectionService.keyStatusUpdated(FetchStatus.ERROR);
1053 Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Have no target devices in PEP!");
1054 }
1055 }
1056 Set<Integer> ownIds = this.deviceIds.get(account.getJid().asBareJid());
1057 for (Integer ownId : (ownIds != null ? ownIds : new HashSet<Integer>())) {
1058 SignalProtocolAddress address = new SignalProtocolAddress(account.getJid().asBareJid().toString(), ownId);
1059 if (sessions.get(address) == null) {
1060 IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
1061 if (identityKey != null) {
1062 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
1063 XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
1064 sessions.put(address, session);
1065 } else {
1066 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + account.getJid().asBareJid() + ":" + ownId);
1067 if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
1068 addresses.add(address);
1069 } else {
1070 Log.d(Config.LOGTAG, getLogprefix(account) + "skipping over " + address + " because it's broken");
1071 }
1072 }
1073 }
1074 }
1075
1076 return addresses;
1077 }
1078
1079 public boolean createSessionsIfNeeded(final Conversation conversation) {
1080 final List<Jid> jidsWithEmptyDeviceList = getCryptoTargets(conversation);
1081 for (Iterator<Jid> iterator = jidsWithEmptyDeviceList.iterator(); iterator.hasNext(); ) {
1082 final Jid jid = iterator.next();
1083 if (!hasEmptyDeviceList(jid)) {
1084 iterator.remove();
1085 }
1086 }
1087 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": createSessionsIfNeeded() - jids with empty device list: " + jidsWithEmptyDeviceList);
1088 if (jidsWithEmptyDeviceList.size() > 0) {
1089 fetchDeviceIds(jidsWithEmptyDeviceList, () -> createSessionsIfNeededActual(conversation));
1090 return true;
1091 } else {
1092 return createSessionsIfNeededActual(conversation);
1093 }
1094 }
1095
1096 private boolean createSessionsIfNeededActual(final Conversation conversation) {
1097 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Creating axolotl sessions if needed...");
1098 boolean newSessions = false;
1099 Set<SignalProtocolAddress> addresses = findDevicesWithoutSession(conversation);
1100 for (SignalProtocolAddress address : addresses) {
1101 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Processing device: " + address.toString());
1102 FetchStatus status = fetchStatusMap.get(address);
1103 if (status == null || status == FetchStatus.TIMEOUT) {
1104 fetchStatusMap.put(address, FetchStatus.PENDING);
1105 this.buildSessionFromPEP(address);
1106 newSessions = true;
1107 } else if (status == FetchStatus.PENDING) {
1108 newSessions = true;
1109 } else {
1110 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already fetching bundle for " + address.toString());
1111 }
1112 }
1113
1114 return newSessions;
1115 }
1116
1117 public boolean trustedSessionVerified(final Conversation conversation) {
1118 final Set<XmppAxolotlSession> sessions = new HashSet<>();
1119 sessions.addAll(findSessionsForConversation(conversation));
1120 sessions.addAll(findOwnSessions());
1121 boolean verified = false;
1122 for (XmppAxolotlSession session : sessions) {
1123 if (session.getTrust().isTrustedAndActive()) {
1124 if (session.getTrust().getTrust() == FingerprintStatus.Trust.VERIFIED_X509) {
1125 verified = true;
1126 } else {
1127 return false;
1128 }
1129 }
1130 }
1131 return verified;
1132 }
1133
1134 public boolean hasPendingKeyFetches(List<Jid> jids) {
1135 SignalProtocolAddress ownAddress = new SignalProtocolAddress(account.getJid().asBareJid().toString(), 0);
1136 if (fetchStatusMap.getAll(ownAddress.getName()).containsValue(FetchStatus.PENDING)) {
1137 return true;
1138 }
1139 synchronized (this.fetchDeviceIdsMap) {
1140 for (Jid jid : jids) {
1141 SignalProtocolAddress foreignAddress = new SignalProtocolAddress(jid.asBareJid().toString(), 0);
1142 if (fetchStatusMap.getAll(foreignAddress.getName()).containsValue(FetchStatus.PENDING) || this.fetchDeviceIdsMap.containsKey(jid)) {
1143 return true;
1144 }
1145 }
1146 }
1147 return false;
1148 }
1149
1150 @Nullable
1151 private boolean buildHeader(XmppAxolotlMessage axolotlMessage, Conversation c) {
1152 Set<XmppAxolotlSession> remoteSessions = findSessionsForConversation(c);
1153 final boolean acceptEmpty = (c.getMode() == Conversation.MODE_MULTI && c.getMucOptions().getUserCount() == 0) || c.getContact().isSelf();
1154 Collection<XmppAxolotlSession> ownSessions = findOwnSessions();
1155 if (remoteSessions.isEmpty() && !acceptEmpty) {
1156 return false;
1157 }
1158 for (XmppAxolotlSession session : remoteSessions) {
1159 axolotlMessage.addDevice(session);
1160 }
1161 for (XmppAxolotlSession session : ownSessions) {
1162 axolotlMessage.addDevice(session);
1163 }
1164
1165 return true;
1166 }
1167
1168 //this is being used for private muc messages only
1169 private boolean buildHeader(XmppAxolotlMessage axolotlMessage, Jid jid) {
1170 if (jid == null) {
1171 return false;
1172 }
1173 HashSet<XmppAxolotlSession> sessions = new HashSet<>();
1174 sessions.addAll(this.sessions.getAll(getAddressForJid(jid).getName()).values());
1175 if (sessions.isEmpty()) {
1176 return false;
1177 }
1178 sessions.addAll(findOwnSessions());
1179 for (XmppAxolotlSession session : sessions) {
1180 axolotlMessage.addDevice(session);
1181 }
1182 return true;
1183 }
1184
1185 @Nullable
1186 public XmppAxolotlMessage encrypt(Message message) {
1187 final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(account.getJid().asBareJid(), getOwnDeviceId());
1188 final String content;
1189 if (message.hasFileOnRemoteHost()) {
1190 content = message.getFileParams().url;
1191 } else {
1192 content = message.getBody();
1193 }
1194 try {
1195 axolotlMessage.encrypt(content);
1196 } catch (CryptoFailedException e) {
1197 Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to encrypt message: " + e.getMessage());
1198 return null;
1199 }
1200
1201 final boolean success;
1202 if (message.isPrivateMessage()) {
1203 success = buildHeader(axolotlMessage, message.getTrueCounterpart());
1204 } else {
1205 success = buildHeader(axolotlMessage, (Conversation) message.getConversation());
1206 }
1207 return success ? axolotlMessage : null;
1208 }
1209
1210 public void preparePayloadMessage(final Message message, final boolean delay) {
1211 executor.execute(new Runnable() {
1212 @Override
1213 public void run() {
1214 XmppAxolotlMessage axolotlMessage = encrypt(message);
1215 if (axolotlMessage == null) {
1216 mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED);
1217 //mXmppConnectionService.updateConversationUi();
1218 } else {
1219 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Generated message, caching: " + message.getUuid());
1220 messageCache.put(message.getUuid(), axolotlMessage);
1221 mXmppConnectionService.resendMessage(message, delay);
1222 }
1223 }
1224 });
1225 }
1226
1227 private OmemoVerifiedIceUdpTransportInfo encrypt(final IceUdpTransportInfo element, final XmppAxolotlSession session) throws CryptoFailedException {
1228 final OmemoVerifiedIceUdpTransportInfo transportInfo = new OmemoVerifiedIceUdpTransportInfo();
1229 transportInfo.setAttributes(element.getAttributes());
1230 for (final Element child : element.getChildren()) {
1231 if ("fingerprint".equals(child.getName()) && Namespace.JINGLE_APPS_DTLS.equals(child.getNamespace())) {
1232 final Element fingerprint = new Element("fingerprint", Namespace.OMEMO_DTLS_SRTP_VERIFICATION);
1233 fingerprint.setAttribute("setup", child.getAttribute("setup"));
1234 fingerprint.setAttribute("hash", child.getAttribute("hash"));
1235 final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(account.getJid().asBareJid(), getOwnDeviceId());
1236 final String content = child.getContent();
1237 axolotlMessage.encrypt(content);
1238 axolotlMessage.addDevice(session, true);
1239 fingerprint.addChild(axolotlMessage.toElement());
1240 transportInfo.addChild(fingerprint);
1241 } else {
1242 transportInfo.addChild(child);
1243 }
1244 }
1245 return transportInfo;
1246 }
1247
1248
1249 public ListenableFuture<OmemoVerifiedPayload<OmemoVerifiedRtpContentMap>> encrypt(final RtpContentMap rtpContentMap, final Jid jid, final int deviceId) {
1250 return Futures.transformAsync(
1251 getSession(jid, deviceId),
1252 session -> encrypt(rtpContentMap, session),
1253 MoreExecutors.directExecutor()
1254 );
1255 }
1256
1257 private ListenableFuture<OmemoVerifiedPayload<OmemoVerifiedRtpContentMap>> encrypt(final RtpContentMap rtpContentMap, final XmppAxolotlSession session) {
1258 if (Config.REQUIRE_RTP_VERIFICATION) {
1259 requireVerification(session);
1260 }
1261 final ImmutableMap.Builder<String, RtpContentMap.DescriptionTransport> descriptionTransportBuilder = new ImmutableMap.Builder<>();
1262 final OmemoVerification omemoVerification = new OmemoVerification();
1263 omemoVerification.setDeviceId(session.getRemoteAddress().getDeviceId());
1264 omemoVerification.setSessionFingerprint(session.getFingerprint());
1265 for (final Map.Entry<String, RtpContentMap.DescriptionTransport> content : rtpContentMap.contents.entrySet()) {
1266 final RtpContentMap.DescriptionTransport descriptionTransport = content.getValue();
1267 final OmemoVerifiedIceUdpTransportInfo encryptedTransportInfo;
1268 try {
1269 encryptedTransportInfo = encrypt(descriptionTransport.transport, session);
1270 } catch (final CryptoFailedException e) {
1271 return Futures.immediateFailedFuture(e);
1272 }
1273 descriptionTransportBuilder.put(
1274 content.getKey(),
1275 new RtpContentMap.DescriptionTransport(descriptionTransport.senders, descriptionTransport.description, encryptedTransportInfo)
1276 );
1277 }
1278 return Futures.immediateFuture(
1279 new OmemoVerifiedPayload<>(
1280 omemoVerification,
1281 new OmemoVerifiedRtpContentMap(rtpContentMap.group, descriptionTransportBuilder.build())
1282 ));
1283 }
1284
1285 private ListenableFuture<XmppAxolotlSession> getSession(final Jid jid, final int deviceId) {
1286 final SignalProtocolAddress address = new SignalProtocolAddress(jid.asBareJid().toString(), deviceId);
1287 final XmppAxolotlSession session = sessions.get(address);
1288 if (session == null) {
1289 return buildSessionFromPEP(address);
1290 }
1291 return Futures.immediateFuture(session);
1292 }
1293
1294 public ListenableFuture<OmemoVerifiedPayload<RtpContentMap>> decrypt(OmemoVerifiedRtpContentMap omemoVerifiedRtpContentMap, final Jid from) {
1295 final ImmutableMap.Builder<String, RtpContentMap.DescriptionTransport> descriptionTransportBuilder = new ImmutableMap.Builder<>();
1296 final OmemoVerification omemoVerification = new OmemoVerification();
1297 final ImmutableList.Builder<ListenableFuture<XmppAxolotlSession>> pepVerificationFutures = new ImmutableList.Builder<>();
1298 for (final Map.Entry<String, RtpContentMap.DescriptionTransport> content : omemoVerifiedRtpContentMap.contents.entrySet()) {
1299 final RtpContentMap.DescriptionTransport descriptionTransport = content.getValue();
1300 final OmemoVerifiedPayload<IceUdpTransportInfo> decryptedTransport;
1301 try {
1302 decryptedTransport = decrypt((OmemoVerifiedIceUdpTransportInfo) descriptionTransport.transport, from, pepVerificationFutures);
1303 } catch (CryptoFailedException e) {
1304 return Futures.immediateFailedFuture(e);
1305 }
1306 omemoVerification.setOrEnsureEqual(decryptedTransport);
1307 descriptionTransportBuilder.put(
1308 content.getKey(),
1309 new RtpContentMap.DescriptionTransport(descriptionTransport.senders, descriptionTransport.description, decryptedTransport.payload)
1310 );
1311 }
1312 processPostponed();
1313 final ImmutableList<ListenableFuture<XmppAxolotlSession>> sessionFutures = pepVerificationFutures.build();
1314 return Futures.transform(
1315 Futures.allAsList(sessionFutures),
1316 sessions -> {
1317 if (Config.REQUIRE_RTP_VERIFICATION) {
1318 for (XmppAxolotlSession session : sessions) {
1319 requireVerification(session);
1320 }
1321 }
1322 return new OmemoVerifiedPayload<>(
1323 omemoVerification,
1324 new RtpContentMap(omemoVerifiedRtpContentMap.group, descriptionTransportBuilder.build())
1325 );
1326
1327 },
1328 MoreExecutors.directExecutor()
1329 );
1330 }
1331
1332 private OmemoVerifiedPayload<IceUdpTransportInfo> decrypt(final OmemoVerifiedIceUdpTransportInfo verifiedIceUdpTransportInfo, final Jid from, ImmutableList.Builder<ListenableFuture<XmppAxolotlSession>> pepVerificationFutures) throws CryptoFailedException {
1333 final IceUdpTransportInfo transportInfo = new IceUdpTransportInfo();
1334 transportInfo.setAttributes(verifiedIceUdpTransportInfo.getAttributes());
1335 final OmemoVerification omemoVerification = new OmemoVerification();
1336 for (final Element child : verifiedIceUdpTransportInfo.getChildren()) {
1337 if ("fingerprint".equals(child.getName()) && Namespace.OMEMO_DTLS_SRTP_VERIFICATION.equals(child.getNamespace())) {
1338 final Element fingerprint = new Element("fingerprint", Namespace.JINGLE_APPS_DTLS);
1339 fingerprint.setAttribute("setup", child.getAttribute("setup"));
1340 fingerprint.setAttribute("hash", child.getAttribute("hash"));
1341 final Element encrypted = child.findChildEnsureSingle(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX);
1342 final XmppAxolotlMessage xmppAxolotlMessage = XmppAxolotlMessage.fromElement(encrypted, from.asBareJid());
1343 final XmppAxolotlSession session = getReceivingSession(xmppAxolotlMessage);
1344 final XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintext = xmppAxolotlMessage.decrypt(session, getOwnDeviceId());
1345 final Integer preKeyId = session.getPreKeyIdAndReset();
1346 if (preKeyId != null) {
1347 postponedSessions.add(session);
1348 }
1349 if (session.isFresh()) {
1350 pepVerificationFutures.add(putFreshSession(session));
1351 } else if (Config.REQUIRE_RTP_VERIFICATION) {
1352 pepVerificationFutures.add(Futures.immediateFuture(session));
1353 }
1354 fingerprint.setContent(plaintext.getPlaintext());
1355 omemoVerification.setDeviceId(session.getRemoteAddress().getDeviceId());
1356 omemoVerification.setSessionFingerprint(plaintext.getFingerprint());
1357 transportInfo.addChild(fingerprint);
1358 } else {
1359 transportInfo.addChild(child);
1360 }
1361 }
1362 return new OmemoVerifiedPayload<>(omemoVerification, transportInfo);
1363 }
1364
1365 private static void requireVerification(final XmppAxolotlSession session) {
1366 if (session.getTrust().isVerified()) {
1367 return;
1368 }
1369 throw new NotVerifiedException(String.format(
1370 "session with %s was not verified",
1371 session.getFingerprint()
1372 ));
1373 }
1374
1375 public void prepareKeyTransportMessage(final Conversation conversation, final OnMessageCreatedCallback onMessageCreatedCallback) {
1376 executor.execute(new Runnable() {
1377 @Override
1378 public void run() {
1379 final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(account.getJid().asBareJid(), getOwnDeviceId());
1380 if (buildHeader(axolotlMessage, conversation)) {
1381 onMessageCreatedCallback.run(axolotlMessage);
1382 } else {
1383 onMessageCreatedCallback.run(null);
1384 }
1385 }
1386 });
1387 }
1388
1389 public XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message) {
1390 XmppAxolotlMessage axolotlMessage = messageCache.get(message.getUuid());
1391 if (axolotlMessage != null) {
1392 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache hit: " + message.getUuid());
1393 messageCache.remove(message.getUuid());
1394 } else {
1395 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache miss: " + message.getUuid());
1396 }
1397 return axolotlMessage;
1398 }
1399
1400 private XmppAxolotlSession recreateUncachedSession(SignalProtocolAddress address) {
1401 IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
1402 return (identityKey != null)
1403 ? new XmppAxolotlSession(account, axolotlStore, address, identityKey)
1404 : null;
1405 }
1406
1407 private XmppAxolotlSession getReceivingSession(XmppAxolotlMessage message) {
1408 SignalProtocolAddress senderAddress = new SignalProtocolAddress(message.getFrom().toString(), message.getSenderDeviceId());
1409 return getReceivingSession(senderAddress);
1410
1411 }
1412
1413 private XmppAxolotlSession getReceivingSession(SignalProtocolAddress senderAddress) {
1414 XmppAxolotlSession session = sessions.get(senderAddress);
1415 if (session == null) {
1416 session = recreateUncachedSession(senderAddress);
1417 if (session == null) {
1418 session = new XmppAxolotlSession(account, axolotlStore, senderAddress);
1419 }
1420 }
1421 return session;
1422 }
1423
1424 public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message, boolean postponePreKeyMessageHandling) throws NotEncryptedForThisDeviceException, BrokenSessionException, OutdatedSenderException {
1425 XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null;
1426
1427 XmppAxolotlSession session = getReceivingSession(message);
1428 int ownDeviceId = getOwnDeviceId();
1429 try {
1430 plaintextMessage = message.decrypt(session, ownDeviceId);
1431 Integer preKeyId = session.getPreKeyIdAndReset();
1432 if (preKeyId != null) {
1433 postPreKeyMessageHandling(session, postponePreKeyMessageHandling);
1434 }
1435 } catch (NotEncryptedForThisDeviceException e) {
1436 if (account.getJid().asBareJid().equals(message.getFrom().asBareJid()) && message.getSenderDeviceId() == ownDeviceId) {
1437 Log.w(Config.LOGTAG, getLogprefix(account) + "Reflected omemo message received");
1438 } else {
1439 throw e;
1440 }
1441 } catch (final BrokenSessionException e) {
1442 throw e;
1443 } catch (final OutdatedSenderException e) {
1444 Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": " + e.getMessage());
1445 throw e;
1446 } catch (CryptoFailedException e) {
1447 Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message from " + message.getFrom(), e);
1448 }
1449
1450 if (session.isFresh() && plaintextMessage != null) {
1451 putFreshSession(session);
1452 }
1453
1454 return plaintextMessage;
1455 }
1456
1457 public void reportBrokenSessionException(BrokenSessionException e, boolean postpone) {
1458 Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": broken session with " + e.getSignalProtocolAddress().toString() + " detected", e);
1459 if (postpone) {
1460 postponedHealing.add(e.getSignalProtocolAddress());
1461 } else {
1462 notifyRequiresHealing(e.getSignalProtocolAddress());
1463 }
1464 }
1465
1466 private void notifyRequiresHealing(final SignalProtocolAddress signalProtocolAddress) {
1467 if (healingAttempts.add(signalProtocolAddress)) {
1468 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": attempt to heal " + signalProtocolAddress);
1469 buildSessionFromPEP(signalProtocolAddress, new OnSessionBuildFromPep() {
1470 @Override
1471 public void onSessionBuildSuccessful() {
1472 Log.d(Config.LOGTAG, "successfully build new session from pep after detecting broken session");
1473 completeSession(getReceivingSession(signalProtocolAddress));
1474 }
1475
1476 @Override
1477 public void onSessionBuildFailed() {
1478 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to build new session from pep after detecting broken session");
1479 }
1480 });
1481 } else {
1482 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": do not attempt to heal " + signalProtocolAddress + " again");
1483 }
1484 }
1485
1486 private void postPreKeyMessageHandling(final XmppAxolotlSession session, final boolean postpone) {
1487 if (postpone) {
1488 postponedSessions.add(session);
1489 } else {
1490 if (axolotlStore.flushPreKeys()) {
1491 publishBundlesIfNeeded(false, false);
1492 } else {
1493 Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": nothing to flush. Not republishing key");
1494 }
1495 if (trustedOrPreviouslyResponded(session) && Config.AUTOMATICALLY_COMPLETE_SESSIONS) {
1496 completeSession(session);
1497 }
1498 }
1499 }
1500
1501 public void processPostponed() {
1502 if (postponedSessions.size() > 0) {
1503 if (axolotlStore.flushPreKeys()) {
1504 publishBundlesIfNeeded(false, false);
1505 }
1506 }
1507 final Iterator<XmppAxolotlSession> iterator = postponedSessions.iterator();
1508 while (iterator.hasNext()) {
1509 final XmppAxolotlSession session = iterator.next();
1510 if (trustedOrPreviouslyResponded(session) && Config.AUTOMATICALLY_COMPLETE_SESSIONS) {
1511 completeSession(session);
1512 }
1513 iterator.remove();
1514 }
1515 final Iterator<SignalProtocolAddress> postponedHealingAttemptsIterator = postponedHealing.iterator();
1516 while (postponedHealingAttemptsIterator.hasNext()) {
1517 notifyRequiresHealing(postponedHealingAttemptsIterator.next());
1518 postponedHealingAttemptsIterator.remove();
1519 }
1520 }
1521
1522 private boolean trustedOrPreviouslyResponded(XmppAxolotlSession session) {
1523 try {
1524 return trustedOrPreviouslyResponded(Jid.of(session.getRemoteAddress().getName()));
1525 } catch (IllegalArgumentException e) {
1526 return false;
1527 }
1528 }
1529
1530 public boolean trustedOrPreviouslyResponded(Jid jid) {
1531 final Contact contact = account.getRoster().getContact(jid);
1532 if (contact.showInRoster() || contact.isSelf()) {
1533 return true;
1534 }
1535 final Conversation conversation = mXmppConnectionService.find(account, jid);
1536 return conversation != null && conversation.sentMessagesCount() > 0;
1537 }
1538
1539 private void completeSession(XmppAxolotlSession session) {
1540 final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(account.getJid().asBareJid(), getOwnDeviceId());
1541 axolotlMessage.addDevice(session, true);
1542 try {
1543 final Jid jid = Jid.of(session.getRemoteAddress().getName());
1544 MessagePacket packet = mXmppConnectionService.getMessageGenerator().generateKeyTransportMessage(jid, axolotlMessage);
1545 mXmppConnectionService.sendMessagePacket(account, packet);
1546 } catch (IllegalArgumentException e) {
1547 throw new Error("Remote addresses are created from jid and should convert back to jid", e);
1548 }
1549 }
1550
1551 public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message, final boolean postponePreKeyMessageHandling) {
1552 final XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage;
1553 final XmppAxolotlSession session = getReceivingSession(message);
1554 try {
1555 keyTransportMessage = message.getParameters(session, getOwnDeviceId());
1556 Integer preKeyId = session.getPreKeyIdAndReset();
1557 if (preKeyId != null) {
1558 postPreKeyMessageHandling(session, postponePreKeyMessageHandling);
1559 }
1560 } catch (CryptoFailedException e) {
1561 Log.d(Config.LOGTAG, "could not decrypt keyTransport message " + e.getMessage());
1562 return null;
1563 }
1564
1565 if (session.isFresh() && keyTransportMessage != null) {
1566 putFreshSession(session);
1567 }
1568
1569 return keyTransportMessage;
1570 }
1571
1572 private ListenableFuture<XmppAxolotlSession> putFreshSession(XmppAxolotlSession session) {
1573 sessions.put(session);
1574 if (Config.X509_VERIFICATION) {
1575 if (session.getIdentityKey() != null) {
1576 return verifySessionWithPEP(session);
1577 } else {
1578 Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": identity key was empty after reloading for x509 verification");
1579 }
1580 }
1581 return Futures.immediateFuture(session);
1582 }
1583
1584 public enum FetchStatus {
1585 PENDING,
1586 SUCCESS,
1587 SUCCESS_VERIFIED,
1588 TIMEOUT,
1589 SUCCESS_TRUSTED,
1590 ERROR
1591 }
1592
1593 public interface OnDeviceIdsFetched {
1594 void fetched(Jid jid, Set<Integer> deviceIds);
1595 }
1596
1597
1598 public interface OnMultipleDeviceIdFetched {
1599 void fetched();
1600 }
1601
1602 interface OnSessionBuildFromPep {
1603 void onSessionBuildSuccessful();
1604
1605 void onSessionBuildFailed();
1606 }
1607
1608 private static class AxolotlAddressMap<T> {
1609 protected final Object MAP_LOCK = new Object();
1610 protected Map<String, Map<Integer, T>> map;
1611
1612 public AxolotlAddressMap() {
1613 this.map = new HashMap<>();
1614 }
1615
1616 public void put(SignalProtocolAddress address, T value) {
1617 synchronized (MAP_LOCK) {
1618 Map<Integer, T> devices = map.get(address.getName());
1619 if (devices == null) {
1620 devices = new HashMap<>();
1621 map.put(address.getName(), devices);
1622 }
1623 devices.put(address.getDeviceId(), value);
1624 }
1625 }
1626
1627 public T get(SignalProtocolAddress address) {
1628 synchronized (MAP_LOCK) {
1629 Map<Integer, T> devices = map.get(address.getName());
1630 if (devices == null) {
1631 return null;
1632 }
1633 return devices.get(address.getDeviceId());
1634 }
1635 }
1636
1637 public Map<Integer, T> getAll(String name) {
1638 synchronized (MAP_LOCK) {
1639 Map<Integer, T> devices = map.get(name);
1640 if (devices == null) {
1641 return new HashMap<>();
1642 }
1643 return devices;
1644 }
1645 }
1646
1647 public boolean hasAny(SignalProtocolAddress address) {
1648 synchronized (MAP_LOCK) {
1649 Map<Integer, T> devices = map.get(address.getName());
1650 return devices != null && !devices.isEmpty();
1651 }
1652 }
1653
1654 public void clear() {
1655 map.clear();
1656 }
1657
1658 }
1659
1660 private static class SessionMap extends AxolotlAddressMap<XmppAxolotlSession> {
1661 private final XmppConnectionService xmppConnectionService;
1662 private final Account account;
1663
1664 public SessionMap(XmppConnectionService service, SQLiteAxolotlStore store, Account account) {
1665 super();
1666 this.xmppConnectionService = service;
1667 this.account = account;
1668 this.fillMap(store);
1669 }
1670
1671 public Set<Jid> findCounterpartsForSourceId(Integer sid) {
1672 Set<Jid> candidates = new HashSet<>();
1673 synchronized (MAP_LOCK) {
1674 for (Map.Entry<String, Map<Integer, XmppAxolotlSession>> entry : map.entrySet()) {
1675 String key = entry.getKey();
1676 if (entry.getValue().containsKey(sid)) {
1677 candidates.add(Jid.of(key));
1678 }
1679 }
1680 }
1681 return candidates;
1682 }
1683
1684 private void putDevicesForJid(String bareJid, List<Integer> deviceIds, SQLiteAxolotlStore store) {
1685 for (Integer deviceId : deviceIds) {
1686 SignalProtocolAddress axolotlAddress = new SignalProtocolAddress(bareJid, deviceId);
1687 IdentityKey identityKey = store.loadSession(axolotlAddress).getSessionState().getRemoteIdentityKey();
1688 if (Config.X509_VERIFICATION) {
1689 X509Certificate certificate = store.getFingerprintCertificate(CryptoHelper.bytesToHex(identityKey.getPublicKey().serialize()));
1690 if (certificate != null) {
1691 Bundle information = CryptoHelper.extractCertificateInformation(certificate);
1692 try {
1693 final String cn = information.getString("subject_cn");
1694 final Jid jid = Jid.of(bareJid);
1695 Log.d(Config.LOGTAG, "setting common name for " + jid + " to " + cn);
1696 account.getRoster().getContact(jid).setCommonName(cn);
1697 } catch (final IllegalArgumentException ignored) {
1698 //ignored
1699 }
1700 }
1701 }
1702 this.put(axolotlAddress, new XmppAxolotlSession(account, store, axolotlAddress, identityKey));
1703 }
1704 }
1705
1706 private void fillMap(SQLiteAxolotlStore store) {
1707 List<Integer> deviceIds = store.getSubDeviceSessions(account.getJid().asBareJid().toString());
1708 putDevicesForJid(account.getJid().asBareJid().toString(), deviceIds, store);
1709 for (String address : store.getKnownAddresses()) {
1710 deviceIds = store.getSubDeviceSessions(address);
1711 putDevicesForJid(address, deviceIds, store);
1712 }
1713 }
1714
1715 @Override
1716 public void put(SignalProtocolAddress address, XmppAxolotlSession value) {
1717 super.put(address, value);
1718 value.setNotFresh();
1719 }
1720
1721 public void put(XmppAxolotlSession session) {
1722 this.put(session.getRemoteAddress(), session);
1723 }
1724 }
1725
1726 private static class FetchStatusMap extends AxolotlAddressMap<FetchStatus> {
1727
1728 public void clearErrorFor(Jid jid) {
1729 synchronized (MAP_LOCK) {
1730 Map<Integer, FetchStatus> devices = this.map.get(jid.asBareJid().toString());
1731 if (devices == null) {
1732 return;
1733 }
1734 for (Map.Entry<Integer, FetchStatus> entry : devices.entrySet()) {
1735 if (entry.getValue() == FetchStatus.ERROR) {
1736 Log.d(Config.LOGTAG, "resetting error for " + jid.asBareJid() + "(" + entry.getKey() + ")");
1737 entry.setValue(FetchStatus.TIMEOUT);
1738 }
1739 }
1740 }
1741 }
1742 }
1743
1744 public static class OmemoVerifiedPayload<T> {
1745 private final int deviceId;
1746 private final String fingerprint;
1747 private final T payload;
1748
1749 private OmemoVerifiedPayload(OmemoVerification omemoVerification, T payload) {
1750 this.deviceId = omemoVerification.getDeviceId();
1751 this.fingerprint = omemoVerification.getFingerprint();
1752 this.payload = payload;
1753 }
1754
1755 public int getDeviceId() {
1756 return deviceId;
1757 }
1758
1759 public String getFingerprint() {
1760 return fingerprint;
1761 }
1762
1763 public T getPayload() {
1764 return payload;
1765 }
1766 }
1767
1768 public static class NotVerifiedException extends SecurityException {
1769
1770 public NotVerifiedException(String message) {
1771 super(message);
1772 }
1773
1774 }
1775}