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.libaxolotl.AxolotlAddress;
12import org.whispersystems.libaxolotl.IdentityKey;
13import org.whispersystems.libaxolotl.IdentityKeyPair;
14import org.whispersystems.libaxolotl.InvalidKeyException;
15import org.whispersystems.libaxolotl.InvalidKeyIdException;
16import org.whispersystems.libaxolotl.SessionBuilder;
17import org.whispersystems.libaxolotl.UntrustedIdentityException;
18import org.whispersystems.libaxolotl.ecc.ECPublicKey;
19import org.whispersystems.libaxolotl.state.PreKeyBundle;
20import org.whispersystems.libaxolotl.state.PreKeyRecord;
21import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
22import org.whispersystems.libaxolotl.util.KeyHelper;
23
24import java.security.PrivateKey;
25import java.security.Security;
26import java.security.Signature;
27import java.security.cert.X509Certificate;
28import java.util.Arrays;
29import java.util.HashMap;
30import java.util.HashSet;
31import java.util.List;
32import java.util.Map;
33import java.util.Random;
34import java.util.Set;
35
36import eu.siacs.conversations.Config;
37import eu.siacs.conversations.entities.Account;
38import eu.siacs.conversations.entities.Contact;
39import eu.siacs.conversations.entities.Conversation;
40import eu.siacs.conversations.entities.Message;
41import eu.siacs.conversations.parser.IqParser;
42import eu.siacs.conversations.services.XmppConnectionService;
43import eu.siacs.conversations.utils.CryptoHelper;
44import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
45import eu.siacs.conversations.xml.Element;
46import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded;
47import eu.siacs.conversations.xmpp.OnIqPacketReceived;
48import eu.siacs.conversations.xmpp.jid.InvalidJidException;
49import eu.siacs.conversations.xmpp.jid.Jid;
50import eu.siacs.conversations.xmpp.stanzas.IqPacket;
51
52public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
53
54 public static final String PEP_PREFIX = "eu.siacs.conversations.axolotl";
55 public static final String PEP_DEVICE_LIST = PEP_PREFIX + ".devicelist";
56 public static final String PEP_BUNDLES = PEP_PREFIX + ".bundles";
57 public static final String PEP_VERIFICATION = PEP_PREFIX + ".verification";
58
59 public static final String LOGPREFIX = "AxolotlService";
60
61 public static final int NUM_KEYS_TO_PUBLISH = 100;
62 public static final int publishTriesThreshold = 3;
63
64 private final Account account;
65 private final XmppConnectionService mXmppConnectionService;
66 private final SQLiteAxolotlStore axolotlStore;
67 private final SessionMap sessions;
68 private final Map<Jid, Set<Integer>> deviceIds;
69 private final Map<String, XmppAxolotlMessage> messageCache;
70 private final FetchStatusMap fetchStatusMap;
71 private final SerialSingleThreadExecutor executor;
72 private int numPublishTriesOnEmptyPep = 0;
73 private boolean pepBroken = false;
74
75 @Override
76 public void onAdvancedStreamFeaturesAvailable(Account account) {
77 if (account.getXmppConnection() != null && account.getXmppConnection().getFeatures().pep()) {
78 publishBundlesIfNeeded(true, false);
79 } else {
80 Log.d(Config.LOGTAG,account.getJid().toBareJid()+": skipping OMEMO initialization");
81 }
82 }
83
84 public boolean fetchMapHasErrors(List<Jid> jids) {
85 for(Jid jid : jids) {
86 if (deviceIds.get(jid) != null) {
87 for (Integer foreignId : this.deviceIds.get(jid)) {
88 AxolotlAddress address = new AxolotlAddress(jid.toString(), foreignId);
89 if (fetchStatusMap.getAll(address).containsValue(FetchStatus.ERROR)) {
90 return true;
91 }
92 }
93 }
94 }
95 return false;
96 }
97
98 private static class AxolotlAddressMap<T> {
99 protected Map<String, Map<Integer, T>> map;
100 protected final Object MAP_LOCK = new Object();
101
102 public AxolotlAddressMap() {
103 this.map = new HashMap<>();
104 }
105
106 public void put(AxolotlAddress address, T value) {
107 synchronized (MAP_LOCK) {
108 Map<Integer, T> devices = map.get(address.getName());
109 if (devices == null) {
110 devices = new HashMap<>();
111 map.put(address.getName(), devices);
112 }
113 devices.put(address.getDeviceId(), value);
114 }
115 }
116
117 public T get(AxolotlAddress address) {
118 synchronized (MAP_LOCK) {
119 Map<Integer, T> devices = map.get(address.getName());
120 if (devices == null) {
121 return null;
122 }
123 return devices.get(address.getDeviceId());
124 }
125 }
126
127 public Map<Integer, T> getAll(AxolotlAddress address) {
128 synchronized (MAP_LOCK) {
129 Map<Integer, T> devices = map.get(address.getName());
130 if (devices == null) {
131 return new HashMap<>();
132 }
133 return devices;
134 }
135 }
136
137 public boolean hasAny(AxolotlAddress address) {
138 synchronized (MAP_LOCK) {
139 Map<Integer, T> devices = map.get(address.getName());
140 return devices != null && !devices.isEmpty();
141 }
142 }
143
144 public void clear() {
145 map.clear();
146 }
147
148 }
149
150 private static class SessionMap extends AxolotlAddressMap<XmppAxolotlSession> {
151 private final XmppConnectionService xmppConnectionService;
152 private final Account account;
153
154 public SessionMap(XmppConnectionService service, SQLiteAxolotlStore store, Account account) {
155 super();
156 this.xmppConnectionService = service;
157 this.account = account;
158 this.fillMap(store);
159 }
160
161 private void putDevicesForJid(String bareJid, List<Integer> deviceIds, SQLiteAxolotlStore store) {
162 for (Integer deviceId : deviceIds) {
163 AxolotlAddress axolotlAddress = new AxolotlAddress(bareJid, deviceId);
164 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building session for remote address: " + axolotlAddress.toString());
165 IdentityKey identityKey = store.loadSession(axolotlAddress).getSessionState().getRemoteIdentityKey();
166 if(Config.X509_VERIFICATION) {
167 X509Certificate certificate = store.getFingerprintCertificate(identityKey.getFingerprint().replaceAll("\\s", ""));
168 if (certificate != null) {
169 Bundle information = CryptoHelper.extractCertificateInformation(certificate);
170 try {
171 final String cn = information.getString("subject_cn");
172 final Jid jid = Jid.fromString(bareJid);
173 Log.d(Config.LOGTAG,"setting common name for "+jid+" to "+cn);
174 account.getRoster().getContact(jid).setCommonName(cn);
175 } catch (final InvalidJidException ignored) {
176 //ignored
177 }
178 }
179 }
180 this.put(axolotlAddress, new XmppAxolotlSession(account, store, axolotlAddress, identityKey));
181 }
182 }
183
184 private void fillMap(SQLiteAxolotlStore store) {
185 List<Integer> deviceIds = store.getSubDeviceSessions(account.getJid().toBareJid().toString());
186 putDevicesForJid(account.getJid().toBareJid().toString(), deviceIds, store);
187 for (Contact contact : account.getRoster().getContacts()) {
188 Jid bareJid = contact.getJid().toBareJid();
189 String address = bareJid.toString();
190 deviceIds = store.getSubDeviceSessions(address);
191 putDevicesForJid(address, deviceIds, store);
192 }
193
194 }
195
196 @Override
197 public void put(AxolotlAddress address, XmppAxolotlSession value) {
198 super.put(address, value);
199 value.setNotFresh();
200 xmppConnectionService.syncRosterToDisk(account);
201 }
202
203 public void put(XmppAxolotlSession session) {
204 this.put(session.getRemoteAddress(), session);
205 }
206 }
207
208 public enum FetchStatus {
209 PENDING,
210 SUCCESS,
211 SUCCESS_VERIFIED,
212 TIMEOUT,
213 ERROR
214 }
215
216 private static class FetchStatusMap extends AxolotlAddressMap<FetchStatus> {
217
218 }
219
220 public static String getLogprefix(Account account) {
221 return LOGPREFIX + " (" + account.getJid().toBareJid().toString() + "): ";
222 }
223
224 public AxolotlService(Account account, XmppConnectionService connectionService) {
225 if (Security.getProvider("BC") == null) {
226 Security.addProvider(new BouncyCastleProvider());
227 }
228 this.mXmppConnectionService = connectionService;
229 this.account = account;
230 this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService);
231 this.deviceIds = new HashMap<>();
232 this.messageCache = new HashMap<>();
233 this.sessions = new SessionMap(mXmppConnectionService, axolotlStore, account);
234 this.fetchStatusMap = new FetchStatusMap();
235 this.executor = new SerialSingleThreadExecutor();
236 }
237
238 public String getOwnFingerprint() {
239 return axolotlStore.getIdentityKeyPair().getPublicKey().getFingerprint().replaceAll("\\s", "");
240 }
241
242 public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust) {
243 return axolotlStore.getContactKeysWithTrust(account.getJid().toBareJid().toString(), trust);
244 }
245
246 public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, Jid jid) {
247 return axolotlStore.getContactKeysWithTrust(jid.toBareJid().toString(), trust);
248 }
249
250 public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust, List<Jid> jids) {
251 Set<IdentityKey> keys = new HashSet<>();
252 for(Jid jid : jids) {
253 keys.addAll(axolotlStore.getContactKeysWithTrust(jid.toString(), trust));
254 }
255 return keys;
256 }
257
258 public long getNumTrustedKeys(Jid jid) {
259 return axolotlStore.getContactNumTrustedKeys(jid.toBareJid().toString());
260 }
261
262 public boolean anyTargetHasNoTrustedKeys(List<Jid> jids) {
263 for(Jid jid : jids) {
264 if (axolotlStore.getContactNumTrustedKeys(jid.toBareJid().toString()) == 0) {
265 return true;
266 }
267 }
268 return false;
269 }
270
271 private AxolotlAddress getAddressForJid(Jid jid) {
272 return new AxolotlAddress(jid.toString(), 0);
273 }
274
275 private Set<XmppAxolotlSession> findOwnSessions() {
276 AxolotlAddress ownAddress = getAddressForJid(account.getJid().toBareJid());
277 return new HashSet<>(this.sessions.getAll(ownAddress).values());
278 }
279
280 private Set<XmppAxolotlSession> findSessionsForContact(Contact contact) {
281 AxolotlAddress contactAddress = getAddressForJid(contact.getJid());
282 return new HashSet<>(this.sessions.getAll(contactAddress).values());
283 }
284
285 private Set<XmppAxolotlSession> findSessionsForConversation(Conversation conversation) {
286 HashSet<XmppAxolotlSession> sessions = new HashSet<>();
287 for(Jid jid : getCryptoTargets(conversation)) {
288 sessions.addAll(this.sessions.getAll(getAddressForJid(jid)).values());
289 }
290 return sessions;
291 }
292
293 public Set<String> getFingerprintsForOwnSessions() {
294 Set<String> fingerprints = new HashSet<>();
295 for (XmppAxolotlSession session : findOwnSessions()) {
296 fingerprints.add(session.getFingerprint());
297 }
298 return fingerprints;
299 }
300
301 public Set<String> getFingerprintsForContact(final Contact contact) {
302 Set<String> fingerprints = new HashSet<>();
303 for (XmppAxolotlSession session : findSessionsForContact(contact)) {
304 fingerprints.add(session.getFingerprint());
305 }
306 return fingerprints;
307 }
308
309 private boolean hasAny(Jid jid) {
310 return sessions.hasAny(getAddressForJid(jid));
311 }
312
313 public boolean isPepBroken() {
314 return this.pepBroken;
315 }
316
317 public void regenerateKeys(boolean wipeOther) {
318 axolotlStore.regenerate();
319 sessions.clear();
320 fetchStatusMap.clear();
321 publishBundlesIfNeeded(true, wipeOther);
322 }
323
324 public int getOwnDeviceId() {
325 return axolotlStore.getLocalRegistrationId();
326 }
327
328 public Set<Integer> getOwnDeviceIds() {
329 return this.deviceIds.get(account.getJid().toBareJid());
330 }
331
332 private void setTrustOnSessions(final Jid jid, @NonNull final Set<Integer> deviceIds,
333 final XmppAxolotlSession.Trust from,
334 final XmppAxolotlSession.Trust to) {
335 for (Integer deviceId : deviceIds) {
336 AxolotlAddress address = new AxolotlAddress(jid.toBareJid().toString(), deviceId);
337 XmppAxolotlSession session = sessions.get(address);
338 if (session != null && session.getFingerprint() != null
339 && session.getTrust() == from) {
340 session.setTrust(to);
341 }
342 }
343 }
344
345 public void registerDevices(final Jid jid, @NonNull final Set<Integer> deviceIds) {
346 if (jid.toBareJid().equals(account.getJid().toBareJid())) {
347 if (!deviceIds.isEmpty()) {
348 Log.d(Config.LOGTAG, getLogprefix(account) + "Received non-empty own device list. Resetting publish attemps and pepBroken status.");
349 pepBroken = false;
350 numPublishTriesOnEmptyPep = 0;
351 }
352 if (deviceIds.contains(getOwnDeviceId())) {
353 deviceIds.remove(getOwnDeviceId());
354 } else {
355 publishOwnDeviceId(deviceIds);
356 }
357 for (Integer deviceId : deviceIds) {
358 AxolotlAddress ownDeviceAddress = new AxolotlAddress(jid.toBareJid().toString(), deviceId);
359 if (sessions.get(ownDeviceAddress) == null) {
360 buildSessionFromPEP(ownDeviceAddress);
361 }
362 }
363 }
364 Set<Integer> expiredDevices = new HashSet<>(axolotlStore.getSubDeviceSessions(jid.toBareJid().toString()));
365 expiredDevices.removeAll(deviceIds);
366 setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.TRUSTED,
367 XmppAxolotlSession.Trust.INACTIVE_TRUSTED);
368 setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.TRUSTED_X509,
369 XmppAxolotlSession.Trust.INACTIVE_TRUSTED_X509);
370 setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.UNDECIDED,
371 XmppAxolotlSession.Trust.INACTIVE_UNDECIDED);
372 setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.UNTRUSTED,
373 XmppAxolotlSession.Trust.INACTIVE_UNTRUSTED);
374 Set<Integer> newDevices = new HashSet<>(deviceIds);
375 setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_TRUSTED,
376 XmppAxolotlSession.Trust.TRUSTED);
377 setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_TRUSTED_X509,
378 XmppAxolotlSession.Trust.TRUSTED_X509);
379 setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_UNDECIDED,
380 XmppAxolotlSession.Trust.UNDECIDED);
381 setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_UNTRUSTED,
382 XmppAxolotlSession.Trust.UNTRUSTED);
383 this.deviceIds.put(jid, deviceIds);
384 mXmppConnectionService.keyStatusUpdated(null);
385 }
386
387 public void wipeOtherPepDevices() {
388 if (pepBroken) {
389 Log.d(Config.LOGTAG, getLogprefix(account) + "wipeOtherPepDevices called, but PEP is broken. Ignoring... ");
390 return;
391 }
392 Set<Integer> deviceIds = new HashSet<>();
393 deviceIds.add(getOwnDeviceId());
394 IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIds);
395 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Wiping all other devices from Pep:" + publish);
396 mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
397 @Override
398 public void onIqPacketReceived(Account account, IqPacket packet) {
399 // TODO: implement this!
400 }
401 });
402 }
403
404 public void purgeKey(final String fingerprint) {
405 axolotlStore.setFingerprintTrust(fingerprint.replaceAll("\\s", ""), XmppAxolotlSession.Trust.COMPROMISED);
406 }
407
408 public void publishOwnDeviceIdIfNeeded() {
409 if (pepBroken) {
410 Log.d(Config.LOGTAG, getLogprefix(account) + "publishOwnDeviceIdIfNeeded called, but PEP is broken. Ignoring... ");
411 return;
412 }
413 IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().toBareJid());
414 mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
415 @Override
416 public void onIqPacketReceived(Account account, IqPacket packet) {
417 if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
418 Log.d(Config.LOGTAG, getLogprefix(account) + "Timeout received while retrieving own Device Ids.");
419 } else {
420 Element item = mXmppConnectionService.getIqParser().getItem(packet);
421 Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
422 if (!deviceIds.contains(getOwnDeviceId())) {
423 publishOwnDeviceId(deviceIds);
424 }
425 }
426 }
427 });
428 }
429
430 public void publishOwnDeviceId(Set<Integer> deviceIds) {
431 Set<Integer> deviceIdsCopy = new HashSet<>(deviceIds);
432 if (!deviceIdsCopy.contains(getOwnDeviceId())) {
433 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Own device " + getOwnDeviceId() + " not in PEP devicelist.");
434 if (deviceIdsCopy.isEmpty()) {
435 if (numPublishTriesOnEmptyPep >= publishTriesThreshold) {
436 Log.w(Config.LOGTAG, getLogprefix(account) + "Own device publish attempt threshold exceeded, aborting...");
437 pepBroken = true;
438 return;
439 } else {
440 numPublishTriesOnEmptyPep++;
441 Log.w(Config.LOGTAG, getLogprefix(account) + "Own device list empty, attempting to publish (try " + numPublishTriesOnEmptyPep + ")");
442 }
443 } else {
444 numPublishTriesOnEmptyPep = 0;
445 }
446 deviceIdsCopy.add(getOwnDeviceId());
447 IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIdsCopy);
448 mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
449 @Override
450 public void onIqPacketReceived(Account account, IqPacket packet) {
451 if (packet.getType() != IqPacket.TYPE.RESULT) {
452 Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing own device id" + packet.findChild("error"));
453 }
454 }
455 });
456 }
457 }
458
459 public void publishDeviceVerificationAndBundle(final SignedPreKeyRecord signedPreKeyRecord,
460 final Set<PreKeyRecord> preKeyRecords,
461 final boolean announceAfter,
462 final boolean wipe) {
463 try {
464 IdentityKey axolotlPublicKey = axolotlStore.getIdentityKeyPair().getPublicKey();
465 PrivateKey x509PrivateKey = KeyChain.getPrivateKey(mXmppConnectionService, account.getPrivateKeyAlias());
466 X509Certificate[] chain = KeyChain.getCertificateChain(mXmppConnectionService, account.getPrivateKeyAlias());
467 Signature verifier = Signature.getInstance("sha256WithRSA");
468 verifier.initSign(x509PrivateKey,mXmppConnectionService.getRNG());
469 verifier.update(axolotlPublicKey.serialize());
470 byte[] signature = verifier.sign();
471 IqPacket packet = mXmppConnectionService.getIqGenerator().publishVerification(signature, chain, getOwnDeviceId());
472 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": publish verification for device "+getOwnDeviceId());
473 mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
474 @Override
475 public void onIqPacketReceived(Account account, IqPacket packet) {
476 publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe);
477 }
478 });
479 } catch (Exception e) {
480 e.printStackTrace();
481 }
482 }
483
484 public void publishBundlesIfNeeded(final boolean announce, final boolean wipe) {
485 if (pepBroken) {
486 Log.d(Config.LOGTAG, getLogprefix(account) + "publishBundlesIfNeeded called, but PEP is broken. Ignoring... ");
487 return;
488 }
489 IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(account.getJid().toBareJid(), getOwnDeviceId());
490 mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
491 @Override
492 public void onIqPacketReceived(Account account, IqPacket packet) {
493
494 if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
495 return; //ignore timeout. do nothing
496 }
497
498 if (packet.getType() == IqPacket.TYPE.ERROR) {
499 Element error = packet.findChild("error");
500 if (error == null || !error.hasChild("item-not-found")) {
501 pepBroken = true;
502 Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "request for device bundles came back with something other than item-not-found" + packet);
503 return;
504 }
505 }
506
507 PreKeyBundle bundle = mXmppConnectionService.getIqParser().bundle(packet);
508 Map<Integer, ECPublicKey> keys = mXmppConnectionService.getIqParser().preKeyPublics(packet);
509 boolean flush = false;
510 if (bundle == null) {
511 Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid bundle:" + packet);
512 bundle = new PreKeyBundle(-1, -1, -1, null, -1, null, null, null);
513 flush = true;
514 }
515 if (keys == null) {
516 Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid prekeys:" + packet);
517 }
518 try {
519 boolean changed = false;
520 // Validate IdentityKey
521 IdentityKeyPair identityKeyPair = axolotlStore.getIdentityKeyPair();
522 if (flush || !identityKeyPair.getPublicKey().equals(bundle.getIdentityKey())) {
523 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding own IdentityKey " + identityKeyPair.getPublicKey() + " to PEP.");
524 changed = true;
525 }
526
527 // Validate signedPreKeyRecord + ID
528 SignedPreKeyRecord signedPreKeyRecord;
529 int numSignedPreKeys = axolotlStore.loadSignedPreKeys().size();
530 try {
531 signedPreKeyRecord = axolotlStore.loadSignedPreKey(bundle.getSignedPreKeyId());
532 if (flush
533 || !bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey())
534 || !Arrays.equals(bundle.getSignedPreKeySignature(), signedPreKeyRecord.getSignature())) {
535 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
536 signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
537 axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
538 changed = true;
539 }
540 } catch (InvalidKeyIdException e) {
541 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
542 signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
543 axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
544 changed = true;
545 }
546
547 // Validate PreKeys
548 Set<PreKeyRecord> preKeyRecords = new HashSet<>();
549 if (keys != null) {
550 for (Integer id : keys.keySet()) {
551 try {
552 PreKeyRecord preKeyRecord = axolotlStore.loadPreKey(id);
553 if (preKeyRecord.getKeyPair().getPublicKey().equals(keys.get(id))) {
554 preKeyRecords.add(preKeyRecord);
555 }
556 } catch (InvalidKeyIdException ignored) {
557 }
558 }
559 }
560 int newKeys = NUM_KEYS_TO_PUBLISH - preKeyRecords.size();
561 if (newKeys > 0) {
562 List<PreKeyRecord> newRecords = KeyHelper.generatePreKeys(
563 axolotlStore.getCurrentPreKeyId() + 1, newKeys);
564 preKeyRecords.addAll(newRecords);
565 for (PreKeyRecord record : newRecords) {
566 axolotlStore.storePreKey(record.getId(), record);
567 }
568 changed = true;
569 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding " + newKeys + " new preKeys to PEP.");
570 }
571
572
573 if (changed) {
574 if (account.getPrivateKeyAlias() != null && Config.X509_VERIFICATION) {
575 mXmppConnectionService.publishDisplayName(account);
576 publishDeviceVerificationAndBundle(signedPreKeyRecord, preKeyRecords, announce, wipe);
577 } else {
578 publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announce, wipe);
579 }
580 } else {
581 Log.d(Config.LOGTAG, getLogprefix(account) + "Bundle " + getOwnDeviceId() + " in PEP was current");
582 if (wipe) {
583 wipeOtherPepDevices();
584 } else if (announce) {
585 Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
586 publishOwnDeviceIdIfNeeded();
587 }
588 }
589 } catch (InvalidKeyException e) {
590 Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage());
591 }
592 }
593 });
594 }
595
596 private void publishDeviceBundle(SignedPreKeyRecord signedPreKeyRecord,
597 Set<PreKeyRecord> preKeyRecords,
598 final boolean announceAfter,
599 final boolean wipe) {
600 IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles(
601 signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(),
602 preKeyRecords, getOwnDeviceId());
603 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing: " + publish);
604 mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
605 @Override
606 public void onIqPacketReceived(Account account, IqPacket packet) {
607 if (packet.getType() == IqPacket.TYPE.RESULT) {
608 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Successfully published bundle. ");
609 if (wipe) {
610 wipeOtherPepDevices();
611 } else if (announceAfter) {
612 Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
613 publishOwnDeviceIdIfNeeded();
614 }
615 } else {
616 Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing bundle: " + packet.findChild("error"));
617 }
618 }
619 });
620 }
621
622 public boolean isConversationAxolotlCapable(Conversation conversation) {
623 final List<Jid> jids = getCryptoTargets(conversation);
624 for(Jid jid : jids) {
625 if (!hasAny(jid) && (!deviceIds.containsKey(jid) || deviceIds.get(jid).isEmpty())) {
626 return false;
627 }
628 }
629 return jids.size() > 0;
630 }
631
632 public List<Jid> getCryptoTargets(Conversation conversation) {
633 final List<Jid> jids;
634 if (conversation.getMode() == Conversation.MODE_SINGLE) {
635 jids = Arrays.asList(conversation.getJid().toBareJid());
636 } else {
637 jids = conversation.getMucOptions().getMembers();
638 jids.remove(account.getJid().toBareJid());
639 }
640 return jids;
641 }
642
643 public XmppAxolotlSession.Trust getFingerprintTrust(String fingerprint) {
644 return axolotlStore.getFingerprintTrust(fingerprint);
645 }
646
647 public X509Certificate getFingerprintCertificate(String fingerprint) {
648 return axolotlStore.getFingerprintCertificate(fingerprint);
649 }
650
651 public void setFingerprintTrust(String fingerprint, XmppAxolotlSession.Trust trust) {
652 axolotlStore.setFingerprintTrust(fingerprint, trust);
653 }
654
655 private void verifySessionWithPEP(final XmppAxolotlSession session) {
656 Log.d(Config.LOGTAG, "trying to verify fresh session (" + session.getRemoteAddress().getName() + ") with pep");
657 final AxolotlAddress address = session.getRemoteAddress();
658 final IdentityKey identityKey = session.getIdentityKey();
659 try {
660 IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(Jid.fromString(address.getName()), address.getDeviceId());
661 mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
662 @Override
663 public void onIqPacketReceived(Account account, IqPacket packet) {
664 Pair<X509Certificate[],byte[]> verification = mXmppConnectionService.getIqParser().verification(packet);
665 if (verification != null) {
666 try {
667 Signature verifier = Signature.getInstance("sha256WithRSA");
668 verifier.initVerify(verification.first[0]);
669 verifier.update(identityKey.serialize());
670 if (verifier.verify(verification.second)) {
671 try {
672 mXmppConnectionService.getMemorizingTrustManager().getNonInteractive().checkClientTrusted(verification.first, "RSA");
673 String fingerprint = session.getFingerprint();
674 Log.d(Config.LOGTAG, "verified session with x.509 signature. fingerprint was: "+fingerprint);
675 setFingerprintTrust(fingerprint, XmppAxolotlSession.Trust.TRUSTED_X509);
676 axolotlStore.setFingerprintCertificate(fingerprint, verification.first[0]);
677 fetchStatusMap.put(address, FetchStatus.SUCCESS_VERIFIED);
678 Bundle information = CryptoHelper.extractCertificateInformation(verification.first[0]);
679 try {
680 final String cn = information.getString("subject_cn");
681 final Jid jid = Jid.fromString(address.getName());
682 Log.d(Config.LOGTAG,"setting common name for "+jid+" to "+cn);
683 account.getRoster().getContact(jid).setCommonName(cn);
684 } catch (final InvalidJidException ignored) {
685 //ignored
686 }
687 finishBuildingSessionsFromPEP(address);
688 return;
689 } catch (Exception e) {
690 Log.d(Config.LOGTAG,"could not verify certificate");
691 }
692 }
693 } catch (Exception e) {
694 Log.d(Config.LOGTAG, "error during verification " + e.getMessage());
695 }
696 } else {
697 Log.d(Config.LOGTAG,"no verification found");
698 }
699 fetchStatusMap.put(address, FetchStatus.SUCCESS);
700 finishBuildingSessionsFromPEP(address);
701 }
702 });
703 } catch (InvalidJidException e) {
704 fetchStatusMap.put(address, FetchStatus.SUCCESS);
705 finishBuildingSessionsFromPEP(address);
706 }
707 }
708
709 private void finishBuildingSessionsFromPEP(final AxolotlAddress address) {
710 AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), 0);
711 if (!fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)
712 && !fetchStatusMap.getAll(address).containsValue(FetchStatus.PENDING)) {
713 FetchStatus report = null;
714 if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.SUCCESS_VERIFIED)
715 | fetchStatusMap.getAll(address).containsValue(FetchStatus.SUCCESS_VERIFIED)) {
716 report = FetchStatus.SUCCESS_VERIFIED;
717 } else if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.ERROR)
718 || fetchStatusMap.getAll(address).containsValue(FetchStatus.ERROR)) {
719 report = FetchStatus.ERROR;
720 }
721 mXmppConnectionService.keyStatusUpdated(report);
722 }
723 }
724
725 private void buildSessionFromPEP(final AxolotlAddress address) {
726 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new sesstion for " + address.toString());
727 if (address.getDeviceId() == getOwnDeviceId()) {
728 throw new AssertionError("We should NEVER build a session with ourselves. What happened here?!");
729 }
730
731 try {
732 IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(
733 Jid.fromString(address.getName()), address.getDeviceId());
734 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Retrieving bundle: " + bundlesPacket);
735 mXmppConnectionService.sendIqPacket(account, bundlesPacket, new OnIqPacketReceived() {
736
737 @Override
738 public void onIqPacketReceived(Account account, IqPacket packet) {
739 if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
740 fetchStatusMap.put(address, FetchStatus.TIMEOUT);
741 } else if (packet.getType() == IqPacket.TYPE.RESULT) {
742 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received preKey IQ packet, processing...");
743 final IqParser parser = mXmppConnectionService.getIqParser();
744 final List<PreKeyBundle> preKeyBundleList = parser.preKeys(packet);
745 final PreKeyBundle bundle = parser.bundle(packet);
746 if (preKeyBundleList.isEmpty() || bundle == null) {
747 Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "preKey IQ packet invalid: " + packet);
748 fetchStatusMap.put(address, FetchStatus.ERROR);
749 finishBuildingSessionsFromPEP(address);
750 return;
751 }
752 Random random = new Random();
753 final PreKeyBundle preKey = preKeyBundleList.get(random.nextInt(preKeyBundleList.size()));
754 if (preKey == null) {
755 //should never happen
756 fetchStatusMap.put(address, FetchStatus.ERROR);
757 finishBuildingSessionsFromPEP(address);
758 return;
759 }
760
761 final PreKeyBundle preKeyBundle = new PreKeyBundle(0, address.getDeviceId(),
762 preKey.getPreKeyId(), preKey.getPreKey(),
763 bundle.getSignedPreKeyId(), bundle.getSignedPreKey(),
764 bundle.getSignedPreKeySignature(), bundle.getIdentityKey());
765
766 try {
767 SessionBuilder builder = new SessionBuilder(axolotlStore, address);
768 builder.process(preKeyBundle);
769 XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey());
770 sessions.put(address, session);
771 if (Config.X509_VERIFICATION) {
772 verifySessionWithPEP(session);
773 } else {
774 fetchStatusMap.put(address, FetchStatus.SUCCESS);
775 finishBuildingSessionsFromPEP(address);
776 }
777 } catch (UntrustedIdentityException | InvalidKeyException e) {
778 Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error building session for " + address + ": "
779 + e.getClass().getName() + ", " + e.getMessage());
780 fetchStatusMap.put(address, FetchStatus.ERROR);
781 finishBuildingSessionsFromPEP(address);
782 }
783 } else {
784 fetchStatusMap.put(address, FetchStatus.ERROR);
785 Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while building session:" + packet.findChild("error"));
786 finishBuildingSessionsFromPEP(address);
787 }
788 }
789 });
790 } catch (InvalidJidException e) {
791 Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Got address with invalid jid: " + address.getName());
792 }
793 }
794
795 public Set<AxolotlAddress> findDevicesWithoutSession(final Conversation conversation) {
796 Set<AxolotlAddress> addresses = new HashSet<>();
797 for(Jid jid : getCryptoTargets(conversation)) {
798 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Finding devices without session for " + jid);
799 if (deviceIds.get(jid) != null) {
800 for (Integer foreignId : this.deviceIds.get(jid)) {
801 AxolotlAddress address = new AxolotlAddress(jid.toString(), foreignId);
802 if (sessions.get(address) == null) {
803 IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
804 if (identityKey != null) {
805 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
806 XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
807 sessions.put(address, session);
808 } else {
809 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + jid + ":" + foreignId);
810 if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
811 addresses.add(address);
812 } else {
813 Log.d(Config.LOGTAG, getLogprefix(account) + "skipping over " + address + " because it's broken");
814 }
815 }
816 }
817 }
818 } else {
819 Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Have no target devices in PEP!");
820 }
821 }
822 if (deviceIds.get(account.getJid().toBareJid()) != null) {
823 for (Integer ownId : this.deviceIds.get(account.getJid().toBareJid())) {
824 AxolotlAddress address = new AxolotlAddress(account.getJid().toBareJid().toString(), ownId);
825 if (sessions.get(address) == null) {
826 IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
827 if (identityKey != null) {
828 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
829 XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
830 sessions.put(address, session);
831 } else {
832 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + account.getJid().toBareJid() + ":" + ownId);
833 if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
834 addresses.add(address);
835 } else {
836 Log.d(Config.LOGTAG,getLogprefix(account)+"skipping over "+address+" because it's broken");
837 }
838 }
839 }
840 }
841 }
842
843 return addresses;
844 }
845
846 public boolean createSessionsIfNeeded(final Conversation conversation) {
847 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Creating axolotl sessions if needed...");
848 boolean newSessions = false;
849 Set<AxolotlAddress> addresses = findDevicesWithoutSession(conversation);
850 for (AxolotlAddress address : addresses) {
851 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Processing device: " + address.toString());
852 FetchStatus status = fetchStatusMap.get(address);
853 if (status == null || status == FetchStatus.TIMEOUT) {
854 fetchStatusMap.put(address, FetchStatus.PENDING);
855 this.buildSessionFromPEP(address);
856 newSessions = true;
857 } else if (status == FetchStatus.PENDING) {
858 newSessions = true;
859 } else {
860 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already fetching bundle for " + address.toString());
861 }
862 }
863
864 return newSessions;
865 }
866
867 public boolean trustedSessionVerified(final Conversation conversation) {
868 Set<XmppAxolotlSession> sessions = findSessionsForConversation(conversation);
869 sessions.addAll(findOwnSessions());
870 boolean verified = false;
871 for(XmppAxolotlSession session : sessions) {
872 if (session.getTrust().trusted()) {
873 if (session.getTrust() == XmppAxolotlSession.Trust.TRUSTED_X509) {
874 verified = true;
875 } else {
876 return false;
877 }
878 }
879 }
880 return verified;
881 }
882
883 public boolean hasPendingKeyFetches(Account account, List<Jid> jids) {
884 AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), 0);
885 if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)) {
886 return true;
887 }
888 for(Jid jid : jids) {
889 AxolotlAddress foreignAddress = new AxolotlAddress(jid.toBareJid().toString(), 0);
890 if (fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING)) {
891 return true;
892 }
893 }
894 return false;
895 }
896
897 @Nullable
898 private XmppAxolotlMessage buildHeader(Conversation conversation) {
899 final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(
900 account.getJid().toBareJid(), getOwnDeviceId());
901
902 Set<XmppAxolotlSession> remoteSessions = findSessionsForConversation(conversation);
903 Set<XmppAxolotlSession> ownSessions = findOwnSessions();
904 if (remoteSessions.isEmpty()) {
905 return null;
906 }
907 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building axolotl foreign keyElements...");
908 for (XmppAxolotlSession session : remoteSessions) {
909 Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account) + session.getRemoteAddress().toString());
910 axolotlMessage.addDevice(session);
911 }
912 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building axolotl own keyElements...");
913 for (XmppAxolotlSession session : ownSessions) {
914 Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account) + session.getRemoteAddress().toString());
915 axolotlMessage.addDevice(session);
916 }
917
918 return axolotlMessage;
919 }
920
921 @Nullable
922 public XmppAxolotlMessage encrypt(Message message) {
923 XmppAxolotlMessage axolotlMessage = buildHeader(message.getConversation());
924
925 if (axolotlMessage != null) {
926 final String content;
927 if (message.hasFileOnRemoteHost()) {
928 content = message.getFileParams().url.toString();
929 } else {
930 content = message.getBody();
931 }
932 try {
933 axolotlMessage.encrypt(content);
934 } catch (CryptoFailedException e) {
935 Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to encrypt message: " + e.getMessage());
936 return null;
937 }
938 }
939
940 return axolotlMessage;
941 }
942
943 public void preparePayloadMessage(final Message message, final boolean delay) {
944 executor.execute(new Runnable() {
945 @Override
946 public void run() {
947 XmppAxolotlMessage axolotlMessage = encrypt(message);
948 if (axolotlMessage == null) {
949 mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED);
950 //mXmppConnectionService.updateConversationUi();
951 } else {
952 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Generated message, caching: " + message.getUuid());
953 messageCache.put(message.getUuid(), axolotlMessage);
954 mXmppConnectionService.resendMessage(message, delay);
955 }
956 }
957 });
958 }
959
960 public void prepareKeyTransportMessage(final Conversation conversation, final OnMessageCreatedCallback onMessageCreatedCallback) {
961 executor.execute(new Runnable() {
962 @Override
963 public void run() {
964 XmppAxolotlMessage axolotlMessage = buildHeader(conversation);
965 onMessageCreatedCallback.run(axolotlMessage);
966 }
967 });
968 }
969
970 public XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message) {
971 XmppAxolotlMessage axolotlMessage = messageCache.get(message.getUuid());
972 if (axolotlMessage != null) {
973 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache hit: " + message.getUuid());
974 messageCache.remove(message.getUuid());
975 } else {
976 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache miss: " + message.getUuid());
977 }
978 return axolotlMessage;
979 }
980
981 private XmppAxolotlSession recreateUncachedSession(AxolotlAddress address) {
982 IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
983 return (identityKey != null)
984 ? new XmppAxolotlSession(account, axolotlStore, address, identityKey)
985 : null;
986 }
987
988 private XmppAxolotlSession getReceivingSession(XmppAxolotlMessage message) {
989 AxolotlAddress senderAddress = new AxolotlAddress(message.getFrom().toString(),
990 message.getSenderDeviceId());
991 XmppAxolotlSession session = sessions.get(senderAddress);
992 if (session == null) {
993 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Account: " + account.getJid() + " No axolotl session found while parsing received message " + message);
994 session = recreateUncachedSession(senderAddress);
995 if (session == null) {
996 session = new XmppAxolotlSession(account, axolotlStore, senderAddress);
997 }
998 }
999 return session;
1000 }
1001
1002 public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message) {
1003 XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null;
1004
1005 XmppAxolotlSession session = getReceivingSession(message);
1006 try {
1007 plaintextMessage = message.decrypt(session, getOwnDeviceId());
1008 Integer preKeyId = session.getPreKeyId();
1009 if (preKeyId != null) {
1010 publishBundlesIfNeeded(false, false);
1011 session.resetPreKeyId();
1012 }
1013 } catch (CryptoFailedException e) {
1014 Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message: " + e.getMessage());
1015 }
1016
1017 if (session.isFresh() && plaintextMessage != null) {
1018 putFreshSession(session);
1019 }
1020
1021 return plaintextMessage;
1022 }
1023
1024 public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message) {
1025 XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage;
1026
1027 XmppAxolotlSession session = getReceivingSession(message);
1028 keyTransportMessage = message.getParameters(session, getOwnDeviceId());
1029
1030 if (session.isFresh() && keyTransportMessage != null) {
1031 putFreshSession(session);
1032 }
1033
1034 return keyTransportMessage;
1035 }
1036
1037 private void putFreshSession(XmppAxolotlSession session) {
1038 Log.d(Config.LOGTAG,"put fresh session");
1039 sessions.put(session);
1040 if (Config.X509_VERIFICATION) {
1041 if (session.getIdentityKey() != null) {
1042 verifySessionWithPEP(session);
1043 } else {
1044 Log.e(Config.LOGTAG,account.getJid().toBareJid()+": identity key was empty after reloading for x509 verification");
1045 }
1046 }
1047 }
1048}