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 }
647 return jids;
648 }
649
650 public XmppAxolotlSession.Trust getFingerprintTrust(String fingerprint) {
651 return axolotlStore.getFingerprintTrust(fingerprint);
652 }
653
654 public X509Certificate getFingerprintCertificate(String fingerprint) {
655 return axolotlStore.getFingerprintCertificate(fingerprint);
656 }
657
658 public void setFingerprintTrust(String fingerprint, XmppAxolotlSession.Trust trust) {
659 axolotlStore.setFingerprintTrust(fingerprint, trust);
660 }
661
662 private void verifySessionWithPEP(final XmppAxolotlSession session) {
663 Log.d(Config.LOGTAG, "trying to verify fresh session (" + session.getRemoteAddress().getName() + ") with pep");
664 final AxolotlAddress address = session.getRemoteAddress();
665 final IdentityKey identityKey = session.getIdentityKey();
666 try {
667 IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(Jid.fromString(address.getName()), address.getDeviceId());
668 mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
669 @Override
670 public void onIqPacketReceived(Account account, IqPacket packet) {
671 Pair<X509Certificate[],byte[]> verification = mXmppConnectionService.getIqParser().verification(packet);
672 if (verification != null) {
673 try {
674 Signature verifier = Signature.getInstance("sha256WithRSA");
675 verifier.initVerify(verification.first[0]);
676 verifier.update(identityKey.serialize());
677 if (verifier.verify(verification.second)) {
678 try {
679 mXmppConnectionService.getMemorizingTrustManager().getNonInteractive().checkClientTrusted(verification.first, "RSA");
680 String fingerprint = session.getFingerprint();
681 Log.d(Config.LOGTAG, "verified session with x.509 signature. fingerprint was: "+fingerprint);
682 setFingerprintTrust(fingerprint, XmppAxolotlSession.Trust.TRUSTED_X509);
683 axolotlStore.setFingerprintCertificate(fingerprint, verification.first[0]);
684 fetchStatusMap.put(address, FetchStatus.SUCCESS_VERIFIED);
685 Bundle information = CryptoHelper.extractCertificateInformation(verification.first[0]);
686 try {
687 final String cn = information.getString("subject_cn");
688 final Jid jid = Jid.fromString(address.getName());
689 Log.d(Config.LOGTAG,"setting common name for "+jid+" to "+cn);
690 account.getRoster().getContact(jid).setCommonName(cn);
691 } catch (final InvalidJidException ignored) {
692 //ignored
693 }
694 finishBuildingSessionsFromPEP(address);
695 return;
696 } catch (Exception e) {
697 Log.d(Config.LOGTAG,"could not verify certificate");
698 }
699 }
700 } catch (Exception e) {
701 Log.d(Config.LOGTAG, "error during verification " + e.getMessage());
702 }
703 } else {
704 Log.d(Config.LOGTAG,"no verification found");
705 }
706 fetchStatusMap.put(address, FetchStatus.SUCCESS);
707 finishBuildingSessionsFromPEP(address);
708 }
709 });
710 } catch (InvalidJidException e) {
711 fetchStatusMap.put(address, FetchStatus.SUCCESS);
712 finishBuildingSessionsFromPEP(address);
713 }
714 }
715
716 private void finishBuildingSessionsFromPEP(final AxolotlAddress address) {
717 AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), 0);
718 if (!fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)
719 && !fetchStatusMap.getAll(address).containsValue(FetchStatus.PENDING)) {
720 FetchStatus report = null;
721 if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.SUCCESS_VERIFIED)
722 | fetchStatusMap.getAll(address).containsValue(FetchStatus.SUCCESS_VERIFIED)) {
723 report = FetchStatus.SUCCESS_VERIFIED;
724 } else if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.ERROR)
725 || fetchStatusMap.getAll(address).containsValue(FetchStatus.ERROR)) {
726 report = FetchStatus.ERROR;
727 }
728 mXmppConnectionService.keyStatusUpdated(report);
729 }
730 }
731
732 private void buildSessionFromPEP(final AxolotlAddress address) {
733 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new sesstion for " + address.toString());
734 if (address.getDeviceId() == getOwnDeviceId()) {
735 throw new AssertionError("We should NEVER build a session with ourselves. What happened here?!");
736 }
737
738 try {
739 IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(
740 Jid.fromString(address.getName()), address.getDeviceId());
741 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Retrieving bundle: " + bundlesPacket);
742 mXmppConnectionService.sendIqPacket(account, bundlesPacket, new OnIqPacketReceived() {
743
744 @Override
745 public void onIqPacketReceived(Account account, IqPacket packet) {
746 if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
747 fetchStatusMap.put(address, FetchStatus.TIMEOUT);
748 } else if (packet.getType() == IqPacket.TYPE.RESULT) {
749 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received preKey IQ packet, processing...");
750 final IqParser parser = mXmppConnectionService.getIqParser();
751 final List<PreKeyBundle> preKeyBundleList = parser.preKeys(packet);
752 final PreKeyBundle bundle = parser.bundle(packet);
753 if (preKeyBundleList.isEmpty() || bundle == null) {
754 Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "preKey IQ packet invalid: " + packet);
755 fetchStatusMap.put(address, FetchStatus.ERROR);
756 finishBuildingSessionsFromPEP(address);
757 return;
758 }
759 Random random = new Random();
760 final PreKeyBundle preKey = preKeyBundleList.get(random.nextInt(preKeyBundleList.size()));
761 if (preKey == null) {
762 //should never happen
763 fetchStatusMap.put(address, FetchStatus.ERROR);
764 finishBuildingSessionsFromPEP(address);
765 return;
766 }
767
768 final PreKeyBundle preKeyBundle = new PreKeyBundle(0, address.getDeviceId(),
769 preKey.getPreKeyId(), preKey.getPreKey(),
770 bundle.getSignedPreKeyId(), bundle.getSignedPreKey(),
771 bundle.getSignedPreKeySignature(), bundle.getIdentityKey());
772
773 try {
774 SessionBuilder builder = new SessionBuilder(axolotlStore, address);
775 builder.process(preKeyBundle);
776 XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey());
777 sessions.put(address, session);
778 if (Config.X509_VERIFICATION) {
779 verifySessionWithPEP(session);
780 } else {
781 fetchStatusMap.put(address, FetchStatus.SUCCESS);
782 finishBuildingSessionsFromPEP(address);
783 }
784 } catch (UntrustedIdentityException | InvalidKeyException e) {
785 Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error building session for " + address + ": "
786 + e.getClass().getName() + ", " + e.getMessage());
787 fetchStatusMap.put(address, FetchStatus.ERROR);
788 finishBuildingSessionsFromPEP(address);
789 }
790 } else {
791 fetchStatusMap.put(address, FetchStatus.ERROR);
792 Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while building session:" + packet.findChild("error"));
793 finishBuildingSessionsFromPEP(address);
794 }
795 }
796 });
797 } catch (InvalidJidException e) {
798 Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Got address with invalid jid: " + address.getName());
799 }
800 }
801
802 public Set<AxolotlAddress> findDevicesWithoutSession(final Conversation conversation) {
803 Set<AxolotlAddress> addresses = new HashSet<>();
804 for(Jid jid : getCryptoTargets(conversation)) {
805 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Finding devices without session for " + jid);
806 if (deviceIds.get(jid) != null) {
807 for (Integer foreignId : this.deviceIds.get(jid)) {
808 AxolotlAddress address = new AxolotlAddress(jid.toString(), foreignId);
809 if (sessions.get(address) == null) {
810 IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
811 if (identityKey != null) {
812 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
813 XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
814 sessions.put(address, session);
815 } else {
816 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + jid + ":" + foreignId);
817 if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
818 addresses.add(address);
819 } else {
820 Log.d(Config.LOGTAG, getLogprefix(account) + "skipping over " + address + " because it's broken");
821 }
822 }
823 }
824 }
825 } else {
826 Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Have no target devices in PEP!");
827 }
828 }
829 if (deviceIds.get(account.getJid().toBareJid()) != null) {
830 for (Integer ownId : this.deviceIds.get(account.getJid().toBareJid())) {
831 AxolotlAddress address = new AxolotlAddress(account.getJid().toBareJid().toString(), ownId);
832 if (sessions.get(address) == null) {
833 IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
834 if (identityKey != null) {
835 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
836 XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
837 sessions.put(address, session);
838 } else {
839 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + account.getJid().toBareJid() + ":" + ownId);
840 if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
841 addresses.add(address);
842 } else {
843 Log.d(Config.LOGTAG,getLogprefix(account)+"skipping over "+address+" because it's broken");
844 }
845 }
846 }
847 }
848 }
849
850 return addresses;
851 }
852
853 public boolean createSessionsIfNeeded(final Conversation conversation) {
854 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Creating axolotl sessions if needed...");
855 boolean newSessions = false;
856 Set<AxolotlAddress> addresses = findDevicesWithoutSession(conversation);
857 for (AxolotlAddress address : addresses) {
858 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Processing device: " + address.toString());
859 FetchStatus status = fetchStatusMap.get(address);
860 if (status == null || status == FetchStatus.TIMEOUT) {
861 fetchStatusMap.put(address, FetchStatus.PENDING);
862 this.buildSessionFromPEP(address);
863 newSessions = true;
864 } else if (status == FetchStatus.PENDING) {
865 newSessions = true;
866 } else {
867 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already fetching bundle for " + address.toString());
868 }
869 }
870
871 return newSessions;
872 }
873
874 public boolean trustedSessionVerified(final Conversation conversation) {
875 Set<XmppAxolotlSession> sessions = findSessionsForConversation(conversation);
876 sessions.addAll(findOwnSessions());
877 boolean verified = false;
878 for(XmppAxolotlSession session : sessions) {
879 if (session.getTrust().trusted()) {
880 if (session.getTrust() == XmppAxolotlSession.Trust.TRUSTED_X509) {
881 verified = true;
882 } else {
883 return false;
884 }
885 }
886 }
887 return verified;
888 }
889
890 public boolean hasPendingKeyFetches(Account account, List<Jid> jids) {
891 AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), 0);
892 if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)) {
893 return true;
894 }
895 for(Jid jid : jids) {
896 AxolotlAddress foreignAddress = new AxolotlAddress(jid.toBareJid().toString(), 0);
897 if (fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING)) {
898 return true;
899 }
900 }
901 return false;
902 }
903
904 @Nullable
905 private XmppAxolotlMessage buildHeader(Conversation conversation) {
906 final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(
907 account.getJid().toBareJid(), getOwnDeviceId());
908
909 Set<XmppAxolotlSession> remoteSessions = findSessionsForConversation(conversation);
910 Set<XmppAxolotlSession> ownSessions = findOwnSessions();
911 if (remoteSessions.isEmpty()) {
912 return null;
913 }
914 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building axolotl foreign keyElements...");
915 for (XmppAxolotlSession session : remoteSessions) {
916 Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account) + session.getRemoteAddress().toString());
917 axolotlMessage.addDevice(session);
918 }
919 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building axolotl own keyElements...");
920 for (XmppAxolotlSession session : ownSessions) {
921 Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account) + session.getRemoteAddress().toString());
922 axolotlMessage.addDevice(session);
923 }
924
925 return axolotlMessage;
926 }
927
928 @Nullable
929 public XmppAxolotlMessage encrypt(Message message) {
930 XmppAxolotlMessage axolotlMessage = buildHeader(message.getConversation());
931
932 if (axolotlMessage != null) {
933 final String content;
934 if (message.hasFileOnRemoteHost()) {
935 content = message.getFileParams().url.toString();
936 } else {
937 content = message.getBody();
938 }
939 try {
940 axolotlMessage.encrypt(content);
941 } catch (CryptoFailedException e) {
942 Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to encrypt message: " + e.getMessage());
943 return null;
944 }
945 }
946
947 return axolotlMessage;
948 }
949
950 public void preparePayloadMessage(final Message message, final boolean delay) {
951 executor.execute(new Runnable() {
952 @Override
953 public void run() {
954 XmppAxolotlMessage axolotlMessage = encrypt(message);
955 if (axolotlMessage == null) {
956 mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED);
957 //mXmppConnectionService.updateConversationUi();
958 } else {
959 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Generated message, caching: " + message.getUuid());
960 messageCache.put(message.getUuid(), axolotlMessage);
961 mXmppConnectionService.resendMessage(message, delay);
962 }
963 }
964 });
965 }
966
967 public void prepareKeyTransportMessage(final Conversation conversation, final OnMessageCreatedCallback onMessageCreatedCallback) {
968 executor.execute(new Runnable() {
969 @Override
970 public void run() {
971 XmppAxolotlMessage axolotlMessage = buildHeader(conversation);
972 onMessageCreatedCallback.run(axolotlMessage);
973 }
974 });
975 }
976
977 public XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message) {
978 XmppAxolotlMessage axolotlMessage = messageCache.get(message.getUuid());
979 if (axolotlMessage != null) {
980 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache hit: " + message.getUuid());
981 messageCache.remove(message.getUuid());
982 } else {
983 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache miss: " + message.getUuid());
984 }
985 return axolotlMessage;
986 }
987
988 private XmppAxolotlSession recreateUncachedSession(AxolotlAddress address) {
989 IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
990 return (identityKey != null)
991 ? new XmppAxolotlSession(account, axolotlStore, address, identityKey)
992 : null;
993 }
994
995 private XmppAxolotlSession getReceivingSession(XmppAxolotlMessage message) {
996 AxolotlAddress senderAddress = new AxolotlAddress(message.getFrom().toString(),
997 message.getSenderDeviceId());
998 XmppAxolotlSession session = sessions.get(senderAddress);
999 if (session == null) {
1000 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Account: " + account.getJid() + " No axolotl session found while parsing received message " + message);
1001 session = recreateUncachedSession(senderAddress);
1002 if (session == null) {
1003 session = new XmppAxolotlSession(account, axolotlStore, senderAddress);
1004 }
1005 }
1006 return session;
1007 }
1008
1009 public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message) {
1010 XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null;
1011
1012 XmppAxolotlSession session = getReceivingSession(message);
1013 try {
1014 plaintextMessage = message.decrypt(session, getOwnDeviceId());
1015 Integer preKeyId = session.getPreKeyId();
1016 if (preKeyId != null) {
1017 publishBundlesIfNeeded(false, false);
1018 session.resetPreKeyId();
1019 }
1020 } catch (CryptoFailedException e) {
1021 Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message: " + e.getMessage());
1022 }
1023
1024 if (session.isFresh() && plaintextMessage != null) {
1025 putFreshSession(session);
1026 }
1027
1028 return plaintextMessage;
1029 }
1030
1031 public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message) {
1032 XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage;
1033
1034 XmppAxolotlSession session = getReceivingSession(message);
1035 keyTransportMessage = message.getParameters(session, getOwnDeviceId());
1036
1037 if (session.isFresh() && keyTransportMessage != null) {
1038 putFreshSession(session);
1039 }
1040
1041 return keyTransportMessage;
1042 }
1043
1044 private void putFreshSession(XmppAxolotlSession session) {
1045 Log.d(Config.LOGTAG,"put fresh session");
1046 sessions.put(session);
1047 if (Config.X509_VERIFICATION) {
1048 if (session.getIdentityKey() != null) {
1049 verifySessionWithPEP(session);
1050 } else {
1051 Log.e(Config.LOGTAG,account.getJid().toBareJid()+": identity key was empty after reloading for x509 verification");
1052 }
1053 }
1054 }
1055}