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
608 if (account.getXmppConnection().getFeatures().pepPublishOptions()) {
609 this.changeAccessMode.set(account.isOptionSet(Account.OPTION_REQUIRES_ACCESS_MODE_CHANGE));
610 } else {
611 if (account.setOption(Account.OPTION_REQUIRES_ACCESS_MODE_CHANGE,true)) {
612 Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": server doesn’t support publish-options. setting for later access mode change");
613 mXmppConnectionService.databaseBackend.updateAccount(account);
614 }
615 }
616 if (this.changeAccessMode.get()) {
617 Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": server gained publish-options capabilities. changing access model");
618 }
619 IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(account.getJid().toBareJid(), getOwnDeviceId());
620 mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
621 @Override
622 public void onIqPacketReceived(Account account, IqPacket packet) {
623
624 if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
625 return; //ignore timeout. do nothing
626 }
627
628 if (packet.getType() == IqPacket.TYPE.ERROR) {
629 Element error = packet.findChild("error");
630 if (error == null || !error.hasChild("item-not-found")) {
631 pepBroken = true;
632 Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "request for device bundles came back with something other than item-not-found" + packet);
633 return;
634 }
635 }
636
637 PreKeyBundle bundle = mXmppConnectionService.getIqParser().bundle(packet);
638 Map<Integer, ECPublicKey> keys = mXmppConnectionService.getIqParser().preKeyPublics(packet);
639 boolean flush = false;
640 if (bundle == null) {
641 Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid bundle:" + packet);
642 bundle = new PreKeyBundle(-1, -1, -1, null, -1, null, null, null);
643 flush = true;
644 }
645 if (keys == null) {
646 Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid prekeys:" + packet);
647 }
648 try {
649 boolean changed = false;
650 // Validate IdentityKey
651 IdentityKeyPair identityKeyPair = axolotlStore.getIdentityKeyPair();
652 if (flush || !identityKeyPair.getPublicKey().equals(bundle.getIdentityKey())) {
653 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding own IdentityKey " + identityKeyPair.getPublicKey() + " to PEP.");
654 changed = true;
655 }
656
657 // Validate signedPreKeyRecord + ID
658 SignedPreKeyRecord signedPreKeyRecord;
659 int numSignedPreKeys = axolotlStore.getSignedPreKeysCount();
660 try {
661 signedPreKeyRecord = axolotlStore.loadSignedPreKey(bundle.getSignedPreKeyId());
662 if (flush
663 || !bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey())
664 || !Arrays.equals(bundle.getSignedPreKeySignature(), signedPreKeyRecord.getSignature())) {
665 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
666 signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
667 axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
668 changed = true;
669 }
670 } catch (InvalidKeyIdException e) {
671 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
672 signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
673 axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
674 changed = true;
675 }
676
677 // Validate PreKeys
678 Set<PreKeyRecord> preKeyRecords = new HashSet<>();
679 if (keys != null) {
680 for (Integer id : keys.keySet()) {
681 try {
682 PreKeyRecord preKeyRecord = axolotlStore.loadPreKey(id);
683 if (preKeyRecord.getKeyPair().getPublicKey().equals(keys.get(id))) {
684 preKeyRecords.add(preKeyRecord);
685 }
686 } catch (InvalidKeyIdException ignored) {
687 }
688 }
689 }
690 int newKeys = NUM_KEYS_TO_PUBLISH - preKeyRecords.size();
691 if (newKeys > 0) {
692 List<PreKeyRecord> newRecords = KeyHelper.generatePreKeys(
693 axolotlStore.getCurrentPreKeyId() + 1, newKeys);
694 preKeyRecords.addAll(newRecords);
695 for (PreKeyRecord record : newRecords) {
696 axolotlStore.storePreKey(record.getId(), record);
697 }
698 changed = true;
699 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding " + newKeys + " new preKeys to PEP.");
700 }
701
702
703 if (changed || changeAccessMode.get()) {
704 if (account.getPrivateKeyAlias() != null && Config.X509_VERIFICATION) {
705 mXmppConnectionService.publishDisplayName(account);
706 publishDeviceVerificationAndBundle(signedPreKeyRecord, preKeyRecords, announce, wipe);
707 } else {
708 publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announce, wipe);
709 }
710 } else {
711 Log.d(Config.LOGTAG, getLogprefix(account) + "Bundle " + getOwnDeviceId() + " in PEP was current");
712 if (wipe) {
713 wipeOtherPepDevices();
714 } else if (announce) {
715 Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
716 publishOwnDeviceIdIfNeeded();
717 }
718 }
719 } catch (InvalidKeyException e) {
720 Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage());
721 }
722 }
723 });
724 }
725
726 private void publishDeviceBundle(SignedPreKeyRecord signedPreKeyRecord,
727 Set<PreKeyRecord> preKeyRecords,
728 final boolean announceAfter,
729 final boolean wipe) {
730 publishDeviceBundle(signedPreKeyRecord,preKeyRecords,announceAfter,wipe,true);
731 }
732
733 private void publishDeviceBundle(final SignedPreKeyRecord signedPreKeyRecord,
734 final Set<PreKeyRecord> preKeyRecords,
735 final boolean announceAfter,
736 final boolean wipe,
737 final boolean firstAttempt) {
738 final Bundle publishOptions = account.getXmppConnection().getFeatures().pepPublishOptions() ? PublishOptions.openAccess() : null;
739 IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles(
740 signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(),
741 preKeyRecords, getOwnDeviceId(),publishOptions);
742 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing...");
743 mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
744 @Override
745 public void onIqPacketReceived(final Account account, IqPacket packet) {
746 Element error = packet.getType() == IqPacket.TYPE.ERROR ? packet.findChild("error") : null;
747 if (firstAttempt && error != null && error.hasChild("precondition-not-met", Namespace.PUBSUB_ERROR)) {
748 Log.d(Config.LOGTAG,account.getJid().toBareJid()+": precondition wasn't met for bundle. pushing node configuration");
749 final String node = AxolotlService.PEP_BUNDLES + ":" + getOwnDeviceId();
750 mXmppConnectionService.pushNodeConfiguration(account, node, publishOptions, new XmppConnectionService.OnConfigurationPushed() {
751 @Override
752 public void onPushSucceeded() {
753 publishDeviceBundle(signedPreKeyRecord,preKeyRecords, announceAfter, wipe, false);
754 }
755
756 @Override
757 public void onPushFailed() {
758 publishDeviceBundle(signedPreKeyRecord,preKeyRecords, announceAfter, wipe, false);
759 }
760 });
761 } else if (packet.getType() == IqPacket.TYPE.RESULT) {
762 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Successfully published bundle. ");
763 if (wipe) {
764 wipeOtherPepDevices();
765 } else if (announceAfter) {
766 Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
767 publishOwnDeviceIdIfNeeded();
768 }
769 } else if (packet.getType() == IqPacket.TYPE.ERROR) {
770 pepBroken = true;
771 Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing bundle: " + packet.findChild("error"));
772 }
773 }
774 });
775 }
776
777 public enum AxolotlCapability {
778 FULL,
779 MISSING_PRESENCE,
780 MISSING_KEYS,
781 WRONG_CONFIGURATION,
782 NO_MEMBERS
783 }
784
785 public boolean isConversationAxolotlCapable(Conversation conversation) {
786 return conversation.getMode() == Conversation.MODE_SINGLE || (conversation.getMucOptions().nonanonymous() && conversation.getMucOptions().membersOnly());
787 }
788
789 public Pair<AxolotlCapability,Jid> isConversationAxolotlCapableDetailed(Conversation conversation) {
790 if (conversation.getMode() == Conversation.MODE_SINGLE
791 || (conversation.getMucOptions().membersOnly() && conversation.getMucOptions().nonanonymous())) {
792 final List<Jid> jids = getCryptoTargets(conversation);
793 for(Jid jid : jids) {
794 if (!hasAny(jid) && (!deviceIds.containsKey(jid) || deviceIds.get(jid).isEmpty())) {
795 if (conversation.getAccount().getRoster().getContact(jid).mutualPresenceSubscription()) {
796 return new Pair<>(AxolotlCapability.MISSING_KEYS,jid);
797 } else {
798 return new Pair<>(AxolotlCapability.MISSING_PRESENCE,jid);
799 }
800 }
801 }
802 if (jids.size() > 0) {
803 return new Pair<>(AxolotlCapability.FULL, null);
804 } else {
805 return new Pair<>(AxolotlCapability.NO_MEMBERS, null);
806 }
807 } else {
808 return new Pair<>(AxolotlCapability.WRONG_CONFIGURATION, null);
809 }
810 }
811
812 public List<Jid> getCryptoTargets(Conversation conversation) {
813 final List<Jid> jids;
814 if (conversation.getMode() == Conversation.MODE_SINGLE) {
815 jids = new ArrayList<>();
816 jids.add(conversation.getJid().toBareJid());
817 } else {
818 jids = conversation.getMucOptions().getMembers();
819 }
820 return jids;
821 }
822
823 public FingerprintStatus getFingerprintTrust(String fingerprint) {
824 return axolotlStore.getFingerprintStatus(fingerprint);
825 }
826
827 public X509Certificate getFingerprintCertificate(String fingerprint) {
828 return axolotlStore.getFingerprintCertificate(fingerprint);
829 }
830
831 public void setFingerprintTrust(String fingerprint, FingerprintStatus status) {
832 axolotlStore.setFingerprintStatus(fingerprint, status);
833 }
834
835 private void verifySessionWithPEP(final XmppAxolotlSession session) {
836 Log.d(Config.LOGTAG, "trying to verify fresh session (" + session.getRemoteAddress().getName() + ") with pep");
837 final SignalProtocolAddress address = session.getRemoteAddress();
838 final IdentityKey identityKey = session.getIdentityKey();
839 try {
840 IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(Jid.fromString(address.getName()), address.getDeviceId());
841 mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
842 @Override
843 public void onIqPacketReceived(Account account, IqPacket packet) {
844 Pair<X509Certificate[],byte[]> verification = mXmppConnectionService.getIqParser().verification(packet);
845 if (verification != null) {
846 try {
847 Signature verifier = Signature.getInstance("sha256WithRSA");
848 verifier.initVerify(verification.first[0]);
849 verifier.update(identityKey.serialize());
850 if (verifier.verify(verification.second)) {
851 try {
852 mXmppConnectionService.getMemorizingTrustManager().getNonInteractive().checkClientTrusted(verification.first, "RSA");
853 String fingerprint = session.getFingerprint();
854 Log.d(Config.LOGTAG, "verified session with x.509 signature. fingerprint was: "+fingerprint);
855 setFingerprintTrust(fingerprint, FingerprintStatus.createActiveVerified(true));
856 axolotlStore.setFingerprintCertificate(fingerprint, verification.first[0]);
857 fetchStatusMap.put(address, FetchStatus.SUCCESS_VERIFIED);
858 Bundle information = CryptoHelper.extractCertificateInformation(verification.first[0]);
859 try {
860 final String cn = information.getString("subject_cn");
861 final Jid jid = Jid.fromString(address.getName());
862 Log.d(Config.LOGTAG,"setting common name for "+jid+" to "+cn);
863 account.getRoster().getContact(jid).setCommonName(cn);
864 } catch (final InvalidJidException ignored) {
865 //ignored
866 }
867 finishBuildingSessionsFromPEP(address);
868 return;
869 } catch (Exception e) {
870 Log.d(Config.LOGTAG,"could not verify certificate");
871 }
872 }
873 } catch (Exception e) {
874 Log.d(Config.LOGTAG, "error during verification " + e.getMessage());
875 }
876 } else {
877 Log.d(Config.LOGTAG,"no verification found");
878 }
879 fetchStatusMap.put(address, FetchStatus.SUCCESS);
880 finishBuildingSessionsFromPEP(address);
881 }
882 });
883 } catch (InvalidJidException e) {
884 fetchStatusMap.put(address, FetchStatus.SUCCESS);
885 finishBuildingSessionsFromPEP(address);
886 }
887 }
888
889 private final Set<Integer> PREVIOUSLY_REMOVED_FROM_ANNOUNCEMENT = new HashSet<>();
890
891 private void finishBuildingSessionsFromPEP(final SignalProtocolAddress address) {
892 SignalProtocolAddress ownAddress = new SignalProtocolAddress(account.getJid().toBareJid().toPreppedString(), 0);
893 Map<Integer, FetchStatus> own = fetchStatusMap.getAll(ownAddress.getName());
894 Map<Integer, FetchStatus> remote = fetchStatusMap.getAll(address.getName());
895 if (!own.containsValue(FetchStatus.PENDING) && !remote.containsValue(FetchStatus.PENDING)) {
896 FetchStatus report = null;
897 if (own.containsValue(FetchStatus.SUCCESS) || remote.containsValue(FetchStatus.SUCCESS)) {
898 report = FetchStatus.SUCCESS;
899 } else if (own.containsValue(FetchStatus.SUCCESS_VERIFIED) || remote.containsValue(FetchStatus.SUCCESS_VERIFIED)) {
900 report = FetchStatus.SUCCESS_VERIFIED;
901 } else if (own.containsValue(FetchStatus.SUCCESS_TRUSTED) || remote.containsValue(FetchStatus.SUCCESS_TRUSTED)) {
902 report = FetchStatus.SUCCESS_TRUSTED;
903 } else if (own.containsValue(FetchStatus.ERROR) || remote.containsValue(FetchStatus.ERROR)) {
904 report = FetchStatus.ERROR;
905 }
906 mXmppConnectionService.keyStatusUpdated(report);
907 }
908 if (Config.REMOVE_BROKEN_DEVICES) {
909 Set<Integer> ownDeviceIds = new HashSet<>(getOwnDeviceIds());
910 boolean publish = false;
911 for (Map.Entry<Integer, FetchStatus> entry : own.entrySet()) {
912 int id = entry.getKey();
913 if (entry.getValue() == FetchStatus.ERROR && PREVIOUSLY_REMOVED_FROM_ANNOUNCEMENT.add(id) && ownDeviceIds.remove(id)) {
914 publish = true;
915 Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": error fetching own device with id " + id + ". removing from announcement");
916 }
917 }
918 if (publish) {
919 publishOwnDeviceId(ownDeviceIds);
920 }
921 }
922 }
923
924 public boolean hasEmptyDeviceList(Jid jid) {
925 return !hasAny(jid) && (!deviceIds.containsKey(jid) || deviceIds.get(jid).isEmpty());
926 }
927
928 public interface OnDeviceIdsFetched {
929 void fetched(Jid jid, Set<Integer> deviceIds);
930 }
931
932 public interface OnMultipleDeviceIdFetched {
933 void fetched();
934 }
935
936 public void fetchDeviceIds(final Jid jid) {
937 fetchDeviceIds(jid,null);
938 }
939
940 public void fetchDeviceIds(final Jid jid, OnDeviceIdsFetched callback) {
941 synchronized (this.fetchDeviceIdsMap) {
942 List<OnDeviceIdsFetched> callbacks = this.fetchDeviceIdsMap.get(jid);
943 if (callbacks != null) {
944 if (callback != null) {
945 callbacks.add(callback);
946 }
947 Log.d(Config.LOGTAG,account.getJid().toBareJid()+": fetching device ids for "+jid+" already running. adding callback");
948 } else {
949 callbacks = new ArrayList<>();
950 if (callback != null) {
951 callbacks.add(callback);
952 }
953 this.fetchDeviceIdsMap.put(jid,callbacks);
954 Log.d(Config.LOGTAG,account.getJid().toBareJid()+": fetching device ids for " + jid);
955 IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(jid);
956 mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
957 @Override
958 public void onIqPacketReceived(Account account, IqPacket packet) {
959 synchronized (fetchDeviceIdsMap) {
960 List<OnDeviceIdsFetched> callbacks = fetchDeviceIdsMap.remove(jid);
961 if (packet.getType() == IqPacket.TYPE.RESULT) {
962 Element item = mXmppConnectionService.getIqParser().getItem(packet);
963 Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
964 registerDevices(jid, deviceIds);
965 if (callbacks != null) {
966 for(OnDeviceIdsFetched callback : callbacks) {
967 callback.fetched(jid, deviceIds);
968 }
969 }
970 } else {
971 Log.d(Config.LOGTAG, packet.toString());
972 if (callbacks != null) {
973 for(OnDeviceIdsFetched callback : callbacks) {
974 callback.fetched(jid, null);
975 }
976 }
977 }
978 }
979 }
980 });
981 }
982 }
983 }
984
985 private void fetchDeviceIds(List<Jid> jids, final OnMultipleDeviceIdFetched callback) {
986 final ArrayList<Jid> unfinishedJids = new ArrayList<>(jids);
987 synchronized (unfinishedJids) {
988 for (Jid jid : unfinishedJids) {
989 fetchDeviceIds(jid, new OnDeviceIdsFetched() {
990 @Override
991 public void fetched(Jid jid, Set<Integer> deviceIds) {
992 synchronized (unfinishedJids) {
993 unfinishedJids.remove(jid);
994 if (unfinishedJids.size() == 0 && callback != null) {
995 callback.fetched();
996 }
997 }
998 }
999 });
1000 }
1001 }
1002 }
1003
1004 private void buildSessionFromPEP(final SignalProtocolAddress address) {
1005 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new session for " + address.toString());
1006 if (address.equals(getOwnAxolotlAddress())) {
1007 throw new AssertionError("We should NEVER build a session with ourselves. What happened here?!");
1008 }
1009
1010 try {
1011 IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(
1012 Jid.fromString(address.getName()), address.getDeviceId());
1013 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Retrieving bundle: " + bundlesPacket);
1014 mXmppConnectionService.sendIqPacket(account, bundlesPacket, new OnIqPacketReceived() {
1015
1016 @Override
1017 public void onIqPacketReceived(Account account, IqPacket packet) {
1018 if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
1019 fetchStatusMap.put(address, FetchStatus.TIMEOUT);
1020 } else if (packet.getType() == IqPacket.TYPE.RESULT) {
1021 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received preKey IQ packet, processing...");
1022 final IqParser parser = mXmppConnectionService.getIqParser();
1023 final List<PreKeyBundle> preKeyBundleList = parser.preKeys(packet);
1024 final PreKeyBundle bundle = parser.bundle(packet);
1025 if (preKeyBundleList.isEmpty() || bundle == null) {
1026 Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "preKey IQ packet invalid: " + packet);
1027 fetchStatusMap.put(address, FetchStatus.ERROR);
1028 finishBuildingSessionsFromPEP(address);
1029 return;
1030 }
1031 Random random = new Random();
1032 final PreKeyBundle preKey = preKeyBundleList.get(random.nextInt(preKeyBundleList.size()));
1033 if (preKey == null) {
1034 //should never happen
1035 fetchStatusMap.put(address, FetchStatus.ERROR);
1036 finishBuildingSessionsFromPEP(address);
1037 return;
1038 }
1039
1040 final PreKeyBundle preKeyBundle = new PreKeyBundle(0, address.getDeviceId(),
1041 preKey.getPreKeyId(), preKey.getPreKey(),
1042 bundle.getSignedPreKeyId(), bundle.getSignedPreKey(),
1043 bundle.getSignedPreKeySignature(), bundle.getIdentityKey());
1044
1045 try {
1046 SessionBuilder builder = new SessionBuilder(axolotlStore, address);
1047 builder.process(preKeyBundle);
1048 XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey());
1049 sessions.put(address, session);
1050 if (Config.X509_VERIFICATION) {
1051 verifySessionWithPEP(session);
1052 } else {
1053 FingerprintStatus status = getFingerprintTrust(CryptoHelper.bytesToHex(bundle.getIdentityKey().getPublicKey().serialize()));
1054 FetchStatus fetchStatus;
1055 if (status != null && status.isVerified()) {
1056 fetchStatus = FetchStatus.SUCCESS_VERIFIED;
1057 } else if (status != null && status.isTrusted()) {
1058 fetchStatus = FetchStatus.SUCCESS_TRUSTED;
1059 } else {
1060 fetchStatus = FetchStatus.SUCCESS;
1061 }
1062 fetchStatusMap.put(address, fetchStatus);
1063 finishBuildingSessionsFromPEP(address);
1064 }
1065 } catch (UntrustedIdentityException | InvalidKeyException e) {
1066 Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error building session for " + address + ": "
1067 + e.getClass().getName() + ", " + e.getMessage());
1068 fetchStatusMap.put(address, FetchStatus.ERROR);
1069 finishBuildingSessionsFromPEP(address);
1070 }
1071 } else {
1072 fetchStatusMap.put(address, FetchStatus.ERROR);
1073 Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while building session:" + packet.findChild("error"));
1074 finishBuildingSessionsFromPEP(address);
1075 }
1076 }
1077 });
1078 } catch (InvalidJidException e) {
1079 Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Got address with invalid jid: " + address.getName());
1080 }
1081 }
1082
1083 public Set<SignalProtocolAddress> findDevicesWithoutSession(final Conversation conversation) {
1084 Set<SignalProtocolAddress> addresses = new HashSet<>();
1085 for(Jid jid : getCryptoTargets(conversation)) {
1086 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Finding devices without session for " + jid);
1087 if (deviceIds.get(jid) != null) {
1088 for (Integer foreignId : this.deviceIds.get(jid)) {
1089 SignalProtocolAddress address = new SignalProtocolAddress(jid.toPreppedString(), foreignId);
1090 if (sessions.get(address) == null) {
1091 IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
1092 if (identityKey != null) {
1093 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
1094 XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
1095 sessions.put(address, session);
1096 } else {
1097 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + jid + ":" + foreignId);
1098 if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
1099 addresses.add(address);
1100 } else {
1101 Log.d(Config.LOGTAG, getLogprefix(account) + "skipping over " + address + " because it's broken");
1102 }
1103 }
1104 }
1105 }
1106 } else {
1107 mXmppConnectionService.keyStatusUpdated(FetchStatus.ERROR);
1108 Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Have no target devices in PEP!");
1109 }
1110 }
1111 if (deviceIds.get(account.getJid().toBareJid()) != null) {
1112 for (Integer ownId : this.deviceIds.get(account.getJid().toBareJid())) {
1113 SignalProtocolAddress address = new SignalProtocolAddress(account.getJid().toBareJid().toPreppedString(), ownId);
1114 if (sessions.get(address) == null) {
1115 IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
1116 if (identityKey != null) {
1117 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
1118 XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
1119 sessions.put(address, session);
1120 } else {
1121 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + account.getJid().toBareJid() + ":" + ownId);
1122 if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
1123 addresses.add(address);
1124 } else {
1125 Log.d(Config.LOGTAG,getLogprefix(account)+"skipping over "+address+" because it's broken");
1126 }
1127 }
1128 }
1129 }
1130 }
1131
1132 return addresses;
1133 }
1134
1135 public boolean createSessionsIfNeeded(final Conversation conversation) {
1136 final List<Jid> jidsWithEmptyDeviceList = getCryptoTargets(conversation);
1137 for(Iterator<Jid> iterator = jidsWithEmptyDeviceList.iterator(); iterator.hasNext();) {
1138 final Jid jid = iterator.next();
1139 if (!hasEmptyDeviceList(jid)) {
1140 iterator.remove();
1141 }
1142 }
1143 Log.d(Config.LOGTAG,account.getJid().toBareJid()+": createSessionsIfNeeded() - jids with empty device list: "+jidsWithEmptyDeviceList);
1144 if (jidsWithEmptyDeviceList.size() > 0) {
1145 fetchDeviceIds(jidsWithEmptyDeviceList, new OnMultipleDeviceIdFetched() {
1146 @Override
1147 public void fetched() {
1148 createSessionsIfNeededActual(conversation);
1149 }
1150 });
1151 return true;
1152 } else {
1153 return createSessionsIfNeededActual(conversation);
1154 }
1155 }
1156
1157 private boolean createSessionsIfNeededActual(final Conversation conversation) {
1158 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Creating axolotl sessions if needed...");
1159 boolean newSessions = false;
1160 Set<SignalProtocolAddress> addresses = findDevicesWithoutSession(conversation);
1161 for (SignalProtocolAddress address : addresses) {
1162 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Processing device: " + address.toString());
1163 FetchStatus status = fetchStatusMap.get(address);
1164 if (status == null || status == FetchStatus.TIMEOUT) {
1165 fetchStatusMap.put(address, FetchStatus.PENDING);
1166 this.buildSessionFromPEP(address);
1167 newSessions = true;
1168 } else if (status == FetchStatus.PENDING) {
1169 newSessions = true;
1170 } else {
1171 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already fetching bundle for " + address.toString());
1172 }
1173 }
1174
1175 return newSessions;
1176 }
1177
1178 public boolean trustedSessionVerified(final Conversation conversation) {
1179 Set<XmppAxolotlSession> sessions = findSessionsForConversation(conversation);
1180 sessions.addAll(findOwnSessions());
1181 boolean verified = false;
1182 for(XmppAxolotlSession session : sessions) {
1183 if (session.getTrust().isTrustedAndActive()) {
1184 if (session.getTrust().getTrust() == FingerprintStatus.Trust.VERIFIED_X509) {
1185 verified = true;
1186 } else {
1187 return false;
1188 }
1189 }
1190 }
1191 return verified;
1192 }
1193
1194 public boolean hasPendingKeyFetches(Account account, List<Jid> jids) {
1195 SignalProtocolAddress ownAddress = new SignalProtocolAddress(account.getJid().toBareJid().toPreppedString(), 0);
1196 if (fetchStatusMap.getAll(ownAddress.getName()).containsValue(FetchStatus.PENDING)) {
1197 return true;
1198 }
1199 synchronized (this.fetchDeviceIdsMap) {
1200 for (Jid jid : jids) {
1201 SignalProtocolAddress foreignAddress = new SignalProtocolAddress(jid.toBareJid().toPreppedString(), 0);
1202 if (fetchStatusMap.getAll(foreignAddress.getName()).containsValue(FetchStatus.PENDING) || this.fetchDeviceIdsMap.containsKey(jid)) {
1203 return true;
1204 }
1205 }
1206 }
1207 return false;
1208 }
1209
1210 @Nullable
1211 private boolean buildHeader(XmppAxolotlMessage axolotlMessage, Conversation conversation) {
1212 Set<XmppAxolotlSession> remoteSessions = findSessionsForConversation(conversation);
1213 Collection<XmppAxolotlSession> ownSessions = findOwnSessions();
1214 if (remoteSessions.isEmpty()) {
1215 return false;
1216 }
1217 for (XmppAxolotlSession session : remoteSessions) {
1218 axolotlMessage.addDevice(session);
1219 }
1220 for (XmppAxolotlSession session : ownSessions) {
1221 axolotlMessage.addDevice(session);
1222 }
1223
1224 return true;
1225 }
1226
1227 @Nullable
1228 public XmppAxolotlMessage encrypt(Message message) {
1229 final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(account.getJid().toBareJid(), getOwnDeviceId());
1230 final String content;
1231 if (message.hasFileOnRemoteHost()) {
1232 content = message.getFileParams().url.toString();
1233 } else {
1234 content = message.getBody();
1235 }
1236 try {
1237 axolotlMessage.encrypt(content);
1238 } catch (CryptoFailedException e) {
1239 Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to encrypt message: " + e.getMessage());
1240 return null;
1241 }
1242 if (!buildHeader(axolotlMessage,message.getConversation())) {
1243 return null;
1244 }
1245
1246 return axolotlMessage;
1247 }
1248
1249 public void preparePayloadMessage(final Message message, final boolean delay) {
1250 executor.execute(new Runnable() {
1251 @Override
1252 public void run() {
1253 XmppAxolotlMessage axolotlMessage = encrypt(message);
1254 if (axolotlMessage == null) {
1255 mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED);
1256 //mXmppConnectionService.updateConversationUi();
1257 } else {
1258 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Generated message, caching: " + message.getUuid());
1259 messageCache.put(message.getUuid(), axolotlMessage);
1260 mXmppConnectionService.resendMessage(message, delay);
1261 }
1262 }
1263 });
1264 }
1265
1266 public void prepareKeyTransportMessage(final Conversation conversation, final OnMessageCreatedCallback onMessageCreatedCallback) {
1267 executor.execute(new Runnable() {
1268 @Override
1269 public void run() {
1270 final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(account.getJid().toBareJid(), getOwnDeviceId());
1271 if (buildHeader(axolotlMessage,conversation)) {
1272 onMessageCreatedCallback.run(axolotlMessage);
1273 } else {
1274 onMessageCreatedCallback.run(null);
1275 }
1276 }
1277 });
1278 }
1279
1280 public XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message) {
1281 XmppAxolotlMessage axolotlMessage = messageCache.get(message.getUuid());
1282 if (axolotlMessage != null) {
1283 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache hit: " + message.getUuid());
1284 messageCache.remove(message.getUuid());
1285 } else {
1286 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache miss: " + message.getUuid());
1287 }
1288 return axolotlMessage;
1289 }
1290
1291 private XmppAxolotlSession recreateUncachedSession(SignalProtocolAddress address) {
1292 IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
1293 return (identityKey != null)
1294 ? new XmppAxolotlSession(account, axolotlStore, address, identityKey)
1295 : null;
1296 }
1297
1298 private XmppAxolotlSession getReceivingSession(XmppAxolotlMessage message) {
1299 SignalProtocolAddress senderAddress = new SignalProtocolAddress(message.getFrom().toPreppedString(),
1300 message.getSenderDeviceId());
1301 XmppAxolotlSession session = sessions.get(senderAddress);
1302 if (session == null) {
1303 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Account: " + account.getJid() + " No axolotl session found while parsing received message " + message);
1304 session = recreateUncachedSession(senderAddress);
1305 if (session == null) {
1306 session = new XmppAxolotlSession(account, axolotlStore, senderAddress);
1307 }
1308 }
1309 return session;
1310 }
1311
1312 public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message) {
1313 XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null;
1314
1315 XmppAxolotlSession session = getReceivingSession(message);
1316 try {
1317 plaintextMessage = message.decrypt(session, getOwnDeviceId());
1318 Integer preKeyId = session.getPreKeyId();
1319 if (preKeyId != null) {
1320 publishBundlesIfNeeded(false, false);
1321 session.resetPreKeyId();
1322 }
1323 } catch (CryptoFailedException e) {
1324 Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message from "+message.getFrom()+": " + e.getMessage());
1325 }
1326
1327 if (session.isFresh() && plaintextMessage != null) {
1328 putFreshSession(session);
1329 }
1330
1331 return plaintextMessage;
1332 }
1333
1334 public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message) {
1335 XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage;
1336
1337 XmppAxolotlSession session = getReceivingSession(message);
1338 try {
1339 keyTransportMessage = message.getParameters(session, getOwnDeviceId());
1340 } catch (CryptoFailedException e) {
1341 Log.d(Config.LOGTAG,"could not decrypt keyTransport message "+e.getMessage());
1342 keyTransportMessage = null;
1343 }
1344
1345 if (session.isFresh() && keyTransportMessage != null) {
1346 putFreshSession(session);
1347 }
1348
1349 return keyTransportMessage;
1350 }
1351
1352 private void putFreshSession(XmppAxolotlSession session) {
1353 Log.d(Config.LOGTAG,"put fresh session");
1354 sessions.put(session);
1355 if (Config.X509_VERIFICATION) {
1356 if (session.getIdentityKey() != null) {
1357 verifySessionWithPEP(session);
1358 } else {
1359 Log.e(Config.LOGTAG,account.getJid().toBareJid()+": identity key was empty after reloading for x509 verification");
1360 }
1361 }
1362 }
1363}