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