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