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