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