1package eu.siacs.conversations.crypto.axolotl;
2
3import android.support.annotation.NonNull;
4import android.support.annotation.Nullable;
5import android.util.Log;
6
7import org.whispersystems.libaxolotl.AxolotlAddress;
8import org.whispersystems.libaxolotl.DuplicateMessageException;
9import org.whispersystems.libaxolotl.IdentityKey;
10import org.whispersystems.libaxolotl.IdentityKeyPair;
11import org.whispersystems.libaxolotl.InvalidKeyException;
12import org.whispersystems.libaxolotl.InvalidKeyIdException;
13import org.whispersystems.libaxolotl.InvalidMessageException;
14import org.whispersystems.libaxolotl.InvalidVersionException;
15import org.whispersystems.libaxolotl.LegacyMessageException;
16import org.whispersystems.libaxolotl.NoSessionException;
17import org.whispersystems.libaxolotl.SessionBuilder;
18import org.whispersystems.libaxolotl.SessionCipher;
19import org.whispersystems.libaxolotl.UntrustedIdentityException;
20import org.whispersystems.libaxolotl.ecc.Curve;
21import org.whispersystems.libaxolotl.ecc.ECKeyPair;
22import org.whispersystems.libaxolotl.ecc.ECPublicKey;
23import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
24import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
25import org.whispersystems.libaxolotl.protocol.WhisperMessage;
26import org.whispersystems.libaxolotl.state.AxolotlStore;
27import org.whispersystems.libaxolotl.state.PreKeyBundle;
28import org.whispersystems.libaxolotl.state.PreKeyRecord;
29import org.whispersystems.libaxolotl.state.SessionRecord;
30import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
31import org.whispersystems.libaxolotl.util.KeyHelper;
32
33import java.util.Arrays;
34import java.util.HashMap;
35import java.util.HashSet;
36import java.util.List;
37import java.util.Map;
38import java.util.Random;
39import java.util.Set;
40
41import eu.siacs.conversations.Config;
42import eu.siacs.conversations.entities.Account;
43import eu.siacs.conversations.entities.Contact;
44import eu.siacs.conversations.entities.Conversation;
45import eu.siacs.conversations.entities.Message;
46import eu.siacs.conversations.parser.IqParser;
47import eu.siacs.conversations.services.XmppConnectionService;
48import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
49import eu.siacs.conversations.xml.Element;
50import eu.siacs.conversations.xmpp.OnIqPacketReceived;
51import eu.siacs.conversations.xmpp.jid.InvalidJidException;
52import eu.siacs.conversations.xmpp.jid.Jid;
53import eu.siacs.conversations.xmpp.stanzas.IqPacket;
54import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
55
56public class AxolotlService {
57
58 public static final String PEP_PREFIX = "eu.siacs.conversations.axolotl";
59 public static final String PEP_DEVICE_LIST = PEP_PREFIX + ".devicelist";
60 public static final String PEP_BUNDLES = PEP_PREFIX + ".bundles";
61
62 public static final String LOGPREFIX = "AxolotlService";
63
64 public static final int NUM_KEYS_TO_PUBLISH = 10;
65
66 private final Account account;
67 private final XmppConnectionService mXmppConnectionService;
68 private final SQLiteAxolotlStore axolotlStore;
69 private final SessionMap sessions;
70 private final Map<Jid, Set<Integer>> deviceIds;
71 private final Map<String, XmppAxolotlMessage> messageCache;
72 private final FetchStatusMap fetchStatusMap;
73 private final SerialSingleThreadExecutor executor;
74
75 public static class SQLiteAxolotlStore implements AxolotlStore {
76
77 public static final String PREKEY_TABLENAME = "prekeys";
78 public static final String SIGNED_PREKEY_TABLENAME = "signed_prekeys";
79 public static final String SESSION_TABLENAME = "sessions";
80 public static final String IDENTITIES_TABLENAME = "identities";
81 public static final String ACCOUNT = "account";
82 public static final String DEVICE_ID = "device_id";
83 public static final String ID = "id";
84 public static final String KEY = "key";
85 public static final String FINGERPRINT = "fingerprint";
86 public static final String NAME = "name";
87 public static final String TRUSTED = "trusted";
88 public static final String OWN = "ownkey";
89
90 public static final String JSONKEY_REGISTRATION_ID = "axolotl_reg_id";
91 public static final String JSONKEY_CURRENT_PREKEY_ID = "axolotl_cur_prekey_id";
92
93 private final Account account;
94 private final XmppConnectionService mXmppConnectionService;
95
96 private IdentityKeyPair identityKeyPair;
97 private int localRegistrationId;
98 private int currentPreKeyId = 0;
99
100 public enum Trust {
101 UNDECIDED, // 0
102 TRUSTED,
103 UNTRUSTED,
104 COMPROMISED;
105
106 public String toString() {
107 switch(this){
108 case UNDECIDED:
109 return "Trust undecided";
110 case TRUSTED:
111 return "Trusted";
112 case UNTRUSTED:
113 default:
114 return "Untrusted";
115 }
116 }
117
118 public static Trust fromBoolean(Boolean trusted) {
119 return trusted?TRUSTED:UNTRUSTED;
120 }
121 };
122
123 private static IdentityKeyPair generateIdentityKeyPair() {
124 Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Generating axolotl IdentityKeyPair...");
125 ECKeyPair identityKeyPairKeys = Curve.generateKeyPair();
126 IdentityKeyPair ownKey = new IdentityKeyPair(new IdentityKey(identityKeyPairKeys.getPublicKey()),
127 identityKeyPairKeys.getPrivateKey());
128 return ownKey;
129 }
130
131 private static int generateRegistrationId() {
132 Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Generating axolotl registration ID...");
133 int reg_id = KeyHelper.generateRegistrationId(true);
134 return reg_id;
135 }
136
137 public SQLiteAxolotlStore(Account account, XmppConnectionService service) {
138 this.account = account;
139 this.mXmppConnectionService = service;
140 this.localRegistrationId = loadRegistrationId();
141 this.currentPreKeyId = loadCurrentPreKeyId();
142 for (SignedPreKeyRecord record : loadSignedPreKeys()) {
143 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Got Axolotl signed prekey record:" + record.getId());
144 }
145 }
146
147 public int getCurrentPreKeyId() {
148 return currentPreKeyId;
149 }
150
151 // --------------------------------------
152 // IdentityKeyStore
153 // --------------------------------------
154
155 private IdentityKeyPair loadIdentityKeyPair() {
156 String ownName = account.getJid().toBareJid().toString();
157 IdentityKeyPair ownKey = mXmppConnectionService.databaseBackend.loadOwnIdentityKeyPair(account,
158 ownName);
159
160 if (ownKey != null) {
161 return ownKey;
162 } else {
163 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Could not retrieve axolotl key for account " + ownName);
164 ownKey = generateIdentityKeyPair();
165 mXmppConnectionService.databaseBackend.storeOwnIdentityKeyPair(account, ownName, ownKey);
166 }
167 return ownKey;
168 }
169
170 private int loadRegistrationId() {
171 return loadRegistrationId(false);
172 }
173
174 private int loadRegistrationId(boolean regenerate) {
175 String regIdString = this.account.getKey(JSONKEY_REGISTRATION_ID);
176 int reg_id;
177 if (!regenerate && regIdString != null) {
178 reg_id = Integer.valueOf(regIdString);
179 } else {
180 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Could not retrieve axolotl registration id for account " + account.getJid());
181 reg_id = generateRegistrationId();
182 boolean success = this.account.setKey(JSONKEY_REGISTRATION_ID, Integer.toString(reg_id));
183 if (success) {
184 mXmppConnectionService.databaseBackend.updateAccount(account);
185 } else {
186 Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Failed to write new key to the database!");
187 }
188 }
189 return reg_id;
190 }
191
192 private int loadCurrentPreKeyId() {
193 String regIdString = this.account.getKey(JSONKEY_CURRENT_PREKEY_ID);
194 int reg_id;
195 if (regIdString != null) {
196 reg_id = Integer.valueOf(regIdString);
197 } else {
198 Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Could not retrieve current prekey id for account " + account.getJid());
199 reg_id = 0;
200 }
201 return reg_id;
202 }
203
204 public void regenerate() {
205 mXmppConnectionService.databaseBackend.wipeAxolotlDb(account);
206 account.setKey(JSONKEY_CURRENT_PREKEY_ID, Integer.toString(0));
207 identityKeyPair = loadIdentityKeyPair();
208 localRegistrationId = loadRegistrationId(true);
209 currentPreKeyId = 0;
210 mXmppConnectionService.updateAccountUi();
211 }
212
213 /**
214 * Get the local client's identity key pair.
215 *
216 * @return The local client's persistent identity key pair.
217 */
218 @Override
219 public IdentityKeyPair getIdentityKeyPair() {
220 if(identityKeyPair == null) {
221 identityKeyPair = loadIdentityKeyPair();
222 }
223 return identityKeyPair;
224 }
225
226 /**
227 * Return the local client's registration ID.
228 * <p/>
229 * Clients should maintain a registration ID, a random number
230 * between 1 and 16380 that's generated once at install time.
231 *
232 * @return the local client's registration ID.
233 */
234 @Override
235 public int getLocalRegistrationId() {
236 return localRegistrationId;
237 }
238
239 /**
240 * Save a remote client's identity key
241 * <p/>
242 * Store a remote client's identity key as trusted.
243 *
244 * @param name The name of the remote client.
245 * @param identityKey The remote client's identity key.
246 */
247 @Override
248 public void saveIdentity(String name, IdentityKey identityKey) {
249 if(!mXmppConnectionService.databaseBackend.loadIdentityKeys(account, name).contains(identityKey)) {
250 mXmppConnectionService.databaseBackend.storeIdentityKey(account, name, identityKey);
251 }
252 }
253
254 /**
255 * Verify a remote client's identity key.
256 * <p/>
257 * Determine whether a remote client's identity is trusted. Convention is
258 * that the TextSecure protocol is 'trust on first use.' This means that
259 * an identity key is considered 'trusted' if there is no entry for the recipient
260 * in the local store, or if it matches the saved key for a recipient in the local
261 * store. Only if it mismatches an entry in the local store is it considered
262 * 'untrusted.'
263 *
264 * @param name The name of the remote client.
265 * @param identityKey The identity key to verify.
266 * @return true if trusted, false if untrusted.
267 */
268 @Override
269 public boolean isTrustedIdentity(String name, IdentityKey identityKey) {
270 return true;
271 }
272
273 public Trust getFingerprintTrust(String fingerprint) {
274 return mXmppConnectionService.databaseBackend.isIdentityKeyTrusted(account, fingerprint);
275 }
276
277 public void setFingerprintTrust(String fingerprint, Trust trust) {
278 mXmppConnectionService.databaseBackend.setIdentityKeyTrust(account, fingerprint, trust);
279 }
280
281 public Set<IdentityKey> getContactUndecidedKeys(String bareJid, Trust trust) {
282 return mXmppConnectionService.databaseBackend.loadIdentityKeys(account, bareJid, trust);
283 }
284
285 public long getContactNumTrustedKeys(String bareJid) {
286 return mXmppConnectionService.databaseBackend.numTrustedKeys(account, bareJid);
287 }
288
289 // --------------------------------------
290 // SessionStore
291 // --------------------------------------
292
293 /**
294 * Returns a copy of the {@link SessionRecord} corresponding to the recipientId + deviceId tuple,
295 * or a new SessionRecord if one does not currently exist.
296 * <p/>
297 * It is important that implementations return a copy of the current durable information. The
298 * returned SessionRecord may be modified, but those changes should not have an effect on the
299 * durable session state (what is returned by subsequent calls to this method) without the
300 * store method being called here first.
301 *
302 * @param address The name and device ID of the remote client.
303 * @return a copy of the SessionRecord corresponding to the recipientId + deviceId tuple, or
304 * a new SessionRecord if one does not currently exist.
305 */
306 @Override
307 public SessionRecord loadSession(AxolotlAddress address) {
308 SessionRecord session = mXmppConnectionService.databaseBackend.loadSession(this.account, address);
309 return (session != null) ? session : new SessionRecord();
310 }
311
312 /**
313 * Returns all known devices with active sessions for a recipient
314 *
315 * @param name the name of the client.
316 * @return all known sub-devices with active sessions.
317 */
318 @Override
319 public List<Integer> getSubDeviceSessions(String name) {
320 return mXmppConnectionService.databaseBackend.getSubDeviceSessions(account,
321 new AxolotlAddress(name, 0));
322 }
323
324 /**
325 * Commit to storage the {@link SessionRecord} for a given recipientId + deviceId tuple.
326 *
327 * @param address the address of the remote client.
328 * @param record the current SessionRecord for the remote client.
329 */
330 @Override
331 public void storeSession(AxolotlAddress address, SessionRecord record) {
332 mXmppConnectionService.databaseBackend.storeSession(account, address, record);
333 }
334
335 /**
336 * Determine whether there is a committed {@link SessionRecord} for a recipientId + deviceId tuple.
337 *
338 * @param address the address of the remote client.
339 * @return true if a {@link SessionRecord} exists, false otherwise.
340 */
341 @Override
342 public boolean containsSession(AxolotlAddress address) {
343 return mXmppConnectionService.databaseBackend.containsSession(account, address);
344 }
345
346 /**
347 * Remove a {@link SessionRecord} for a recipientId + deviceId tuple.
348 *
349 * @param address the address of the remote client.
350 */
351 @Override
352 public void deleteSession(AxolotlAddress address) {
353 mXmppConnectionService.databaseBackend.deleteSession(account, address);
354 }
355
356 /**
357 * Remove the {@link SessionRecord}s corresponding to all devices of a recipientId.
358 *
359 * @param name the name of the remote client.
360 */
361 @Override
362 public void deleteAllSessions(String name) {
363 mXmppConnectionService.databaseBackend.deleteAllSessions(account,
364 new AxolotlAddress(name, 0));
365 }
366
367 // --------------------------------------
368 // PreKeyStore
369 // --------------------------------------
370
371 /**
372 * Load a local PreKeyRecord.
373 *
374 * @param preKeyId the ID of the local PreKeyRecord.
375 * @return the corresponding PreKeyRecord.
376 * @throws InvalidKeyIdException when there is no corresponding PreKeyRecord.
377 */
378 @Override
379 public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException {
380 PreKeyRecord record = mXmppConnectionService.databaseBackend.loadPreKey(account, preKeyId);
381 if (record == null) {
382 throw new InvalidKeyIdException("No such PreKeyRecord: " + preKeyId);
383 }
384 return record;
385 }
386
387 /**
388 * Store a local PreKeyRecord.
389 *
390 * @param preKeyId the ID of the PreKeyRecord to store.
391 * @param record the PreKeyRecord.
392 */
393 @Override
394 public void storePreKey(int preKeyId, PreKeyRecord record) {
395 mXmppConnectionService.databaseBackend.storePreKey(account, record);
396 currentPreKeyId = preKeyId;
397 boolean success = this.account.setKey(JSONKEY_CURRENT_PREKEY_ID, Integer.toString(preKeyId));
398 if (success) {
399 mXmppConnectionService.databaseBackend.updateAccount(account);
400 } else {
401 Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Failed to write new prekey id to the database!");
402 }
403 }
404
405 /**
406 * @param preKeyId A PreKeyRecord ID.
407 * @return true if the store has a record for the preKeyId, otherwise false.
408 */
409 @Override
410 public boolean containsPreKey(int preKeyId) {
411 return mXmppConnectionService.databaseBackend.containsPreKey(account, preKeyId);
412 }
413
414 /**
415 * Delete a PreKeyRecord from local storage.
416 *
417 * @param preKeyId The ID of the PreKeyRecord to remove.
418 */
419 @Override
420 public void removePreKey(int preKeyId) {
421 mXmppConnectionService.databaseBackend.deletePreKey(account, preKeyId);
422 }
423
424 // --------------------------------------
425 // SignedPreKeyStore
426 // --------------------------------------
427
428 /**
429 * Load a local SignedPreKeyRecord.
430 *
431 * @param signedPreKeyId the ID of the local SignedPreKeyRecord.
432 * @return the corresponding SignedPreKeyRecord.
433 * @throws InvalidKeyIdException when there is no corresponding SignedPreKeyRecord.
434 */
435 @Override
436 public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException {
437 SignedPreKeyRecord record = mXmppConnectionService.databaseBackend.loadSignedPreKey(account, signedPreKeyId);
438 if (record == null) {
439 throw new InvalidKeyIdException("No such SignedPreKeyRecord: " + signedPreKeyId);
440 }
441 return record;
442 }
443
444 /**
445 * Load all local SignedPreKeyRecords.
446 *
447 * @return All stored SignedPreKeyRecords.
448 */
449 @Override
450 public List<SignedPreKeyRecord> loadSignedPreKeys() {
451 return mXmppConnectionService.databaseBackend.loadSignedPreKeys(account);
452 }
453
454 /**
455 * Store a local SignedPreKeyRecord.
456 *
457 * @param signedPreKeyId the ID of the SignedPreKeyRecord to store.
458 * @param record the SignedPreKeyRecord.
459 */
460 @Override
461 public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) {
462 mXmppConnectionService.databaseBackend.storeSignedPreKey(account, record);
463 }
464
465 /**
466 * @param signedPreKeyId A SignedPreKeyRecord ID.
467 * @return true if the store has a record for the signedPreKeyId, otherwise false.
468 */
469 @Override
470 public boolean containsSignedPreKey(int signedPreKeyId) {
471 return mXmppConnectionService.databaseBackend.containsSignedPreKey(account, signedPreKeyId);
472 }
473
474 /**
475 * Delete a SignedPreKeyRecord from local storage.
476 *
477 * @param signedPreKeyId The ID of the SignedPreKeyRecord to remove.
478 */
479 @Override
480 public void removeSignedPreKey(int signedPreKeyId) {
481 mXmppConnectionService.databaseBackend.deleteSignedPreKey(account, signedPreKeyId);
482 }
483 }
484
485 public static class XmppAxolotlSession {
486 private final SessionCipher cipher;
487 private Integer preKeyId = null;
488 private final SQLiteAxolotlStore sqLiteAxolotlStore;
489 private final AxolotlAddress remoteAddress;
490 private final Account account;
491 private String fingerprint = null;
492
493 public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress, String fingerprint) {
494 this(account, store, remoteAddress);
495 this.fingerprint = fingerprint;
496 }
497
498 public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress) {
499 this.cipher = new SessionCipher(store, remoteAddress);
500 this.remoteAddress = remoteAddress;
501 this.sqLiteAxolotlStore = store;
502 this.account = account;
503 }
504
505 public Integer getPreKeyId() {
506 return preKeyId;
507 }
508
509 public void resetPreKeyId() {
510
511 preKeyId = null;
512 }
513
514 public String getFingerprint() {
515 return fingerprint;
516 }
517
518 private SQLiteAxolotlStore.Trust getTrust() {
519 return sqLiteAxolotlStore.getFingerprintTrust(fingerprint);
520 }
521
522 @Nullable
523 public byte[] processReceiving(XmppAxolotlMessage.XmppAxolotlMessageHeader incomingHeader) {
524 byte[] plaintext = null;
525 switch (getTrust()) {
526 case UNDECIDED:
527 case TRUSTED:
528 try {
529 try {
530 PreKeyWhisperMessage message = new PreKeyWhisperMessage(incomingHeader.getContents());
531 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"PreKeyWhisperMessage received, new session ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId());
532 String fingerprint = message.getIdentityKey().getFingerprint().replaceAll("\\s", "");
533 if (this.fingerprint != null && !this.fingerprint.equals(fingerprint)) {
534 Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Had session with fingerprint "+ this.fingerprint+", received message with fingerprint "+fingerprint);
535 } else {
536 this.fingerprint = fingerprint;
537 plaintext = cipher.decrypt(message);
538 if (message.getPreKeyId().isPresent()) {
539 preKeyId = message.getPreKeyId().get();
540 }
541 }
542 } catch (InvalidMessageException | InvalidVersionException e) {
543 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"WhisperMessage received");
544 WhisperMessage message = new WhisperMessage(incomingHeader.getContents());
545 plaintext = cipher.decrypt(message);
546 } catch (InvalidKeyException | InvalidKeyIdException | UntrustedIdentityException e) {
547 Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Error decrypting axolotl header, "+e.getClass().getName()+": " + e.getMessage());
548 }
549 } catch (LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException e) {
550 Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Error decrypting axolotl header, "+e.getClass().getName()+": " + e.getMessage());
551 }
552
553 break;
554
555 case COMPROMISED:
556 case UNTRUSTED:
557 default:
558 // ignore
559 break;
560 }
561 return plaintext;
562 }
563
564 @Nullable
565 public XmppAxolotlMessage.XmppAxolotlMessageHeader processSending(@NonNull byte[] outgoingMessage) {
566 SQLiteAxolotlStore.Trust trust = getTrust();
567 if (trust == SQLiteAxolotlStore.Trust.TRUSTED) {
568 CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage);
569 XmppAxolotlMessage.XmppAxolotlMessageHeader header =
570 new XmppAxolotlMessage.XmppAxolotlMessageHeader(remoteAddress.getDeviceId(),
571 ciphertextMessage.serialize());
572 return header;
573 } else {
574 return null;
575 }
576 }
577 }
578
579 private static class AxolotlAddressMap<T> {
580 protected Map<String, Map<Integer, T>> map;
581 protected final Object MAP_LOCK = new Object();
582
583 public AxolotlAddressMap() {
584 this.map = new HashMap<>();
585 }
586
587 public void put(AxolotlAddress address, T value) {
588 synchronized (MAP_LOCK) {
589 Map<Integer, T> devices = map.get(address.getName());
590 if (devices == null) {
591 devices = new HashMap<>();
592 map.put(address.getName(), devices);
593 }
594 devices.put(address.getDeviceId(), value);
595 }
596 }
597
598 public T get(AxolotlAddress address) {
599 synchronized (MAP_LOCK) {
600 Map<Integer, T> devices = map.get(address.getName());
601 if (devices == null) {
602 return null;
603 }
604 return devices.get(address.getDeviceId());
605 }
606 }
607
608 public Map<Integer, T> getAll(AxolotlAddress address) {
609 synchronized (MAP_LOCK) {
610 Map<Integer, T> devices = map.get(address.getName());
611 if (devices == null) {
612 return new HashMap<>();
613 }
614 return devices;
615 }
616 }
617
618 public boolean hasAny(AxolotlAddress address) {
619 synchronized (MAP_LOCK) {
620 Map<Integer, T> devices = map.get(address.getName());
621 return devices != null && !devices.isEmpty();
622 }
623 }
624
625 public void clear() {
626 map.clear();
627 }
628
629 }
630
631 private static class SessionMap extends AxolotlAddressMap<XmppAxolotlSession> {
632 private final XmppConnectionService xmppConnectionService;
633 private final Account account;
634
635 public SessionMap(XmppConnectionService service, SQLiteAxolotlStore store, Account account) {
636 super();
637 this.xmppConnectionService = service;
638 this.account = account;
639 this.fillMap(store);
640 }
641
642 private void fillMap(SQLiteAxolotlStore store) {
643 for (Contact contact : account.getRoster().getContacts()) {
644 Jid bareJid = contact.getJid().toBareJid();
645 if (bareJid == null) {
646 continue; // FIXME: handle this?
647 }
648 String address = bareJid.toString();
649 List<Integer> deviceIDs = store.getSubDeviceSessions(address);
650 for (Integer deviceId : deviceIDs) {
651 AxolotlAddress axolotlAddress = new AxolotlAddress(address, deviceId);
652 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building session for remote address: "+axolotlAddress.toString());
653 String fingerprint = store.loadSession(axolotlAddress).getSessionState().getRemoteIdentityKey().getFingerprint().replaceAll("\\s", "");
654 this.put(axolotlAddress, new XmppAxolotlSession(account, store, axolotlAddress, fingerprint));
655 }
656 }
657 }
658
659 @Override
660 public void put(AxolotlAddress address, XmppAxolotlSession value) {
661 super.put(address, value);
662 xmppConnectionService.syncRosterToDisk(account);
663 }
664 }
665
666 private static enum FetchStatus {
667 PENDING,
668 SUCCESS,
669 ERROR
670 }
671
672 private static class FetchStatusMap extends AxolotlAddressMap<FetchStatus> {
673
674 }
675
676 public static String getLogprefix(Account account) {
677 return LOGPREFIX+" ("+account.getJid().toBareJid().toString()+"): ";
678 }
679
680 public AxolotlService(Account account, XmppConnectionService connectionService) {
681 this.mXmppConnectionService = connectionService;
682 this.account = account;
683 this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService);
684 this.deviceIds = new HashMap<>();
685 this.messageCache = new HashMap<>();
686 this.sessions = new SessionMap(mXmppConnectionService, axolotlStore, account);
687 this.fetchStatusMap = new FetchStatusMap();
688 this.executor = new SerialSingleThreadExecutor();
689 }
690
691 public IdentityKey getOwnPublicKey() {
692 return axolotlStore.getIdentityKeyPair().getPublicKey();
693 }
694
695 public Set<IdentityKey> getKeysWithTrust(SQLiteAxolotlStore.Trust trust) {
696 return axolotlStore.getContactUndecidedKeys(account.getJid().toBareJid().toString(), trust);
697 }
698
699 public Set<IdentityKey> getKeysWithTrust(SQLiteAxolotlStore.Trust trust, Contact contact) {
700 return axolotlStore.getContactUndecidedKeys(contact.getJid().toBareJid().toString(), trust);
701 }
702
703 public long getNumTrustedKeys(Contact contact) {
704 return axolotlStore.getContactNumTrustedKeys(contact.getJid().toBareJid().toString());
705 }
706
707 private AxolotlAddress getAddressForJid(Jid jid) {
708 return new AxolotlAddress(jid.toString(), 0);
709 }
710
711 private Set<XmppAxolotlSession> findOwnSessions() {
712 AxolotlAddress ownAddress = getAddressForJid(account.getJid().toBareJid());
713 Set<XmppAxolotlSession> ownDeviceSessions = new HashSet<>(this.sessions.getAll(ownAddress).values());
714 return ownDeviceSessions;
715 }
716
717 private Set<XmppAxolotlSession> findSessionsforContact(Contact contact) {
718 AxolotlAddress contactAddress = getAddressForJid(contact.getJid());
719 Set<XmppAxolotlSession> sessions = new HashSet<>(this.sessions.getAll(contactAddress).values());
720 return sessions;
721 }
722
723 private boolean hasAny(Contact contact) {
724 AxolotlAddress contactAddress = getAddressForJid(contact.getJid());
725 return sessions.hasAny(contactAddress);
726 }
727
728 public void regenerateKeys() {
729 axolotlStore.regenerate();
730 sessions.clear();
731 fetchStatusMap.clear();
732 publishBundlesIfNeeded();
733 publishOwnDeviceIdIfNeeded();
734 }
735
736 public int getOwnDeviceId() {
737 return axolotlStore.loadRegistrationId();
738 }
739
740 public Set<Integer> getOwnDeviceIds() {
741 return this.deviceIds.get(account.getJid().toBareJid());
742 }
743
744 public void registerDevices(final Jid jid, @NonNull final Set<Integer> deviceIds) {
745 if(deviceIds.contains(getOwnDeviceId())) {
746 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Skipping own Device ID:"+ jid + ":"+getOwnDeviceId());
747 deviceIds.remove(getOwnDeviceId());
748 }
749 for(Integer i:deviceIds) {
750 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Adding Device ID:"+ jid + ":"+i);
751 }
752 this.deviceIds.put(jid, deviceIds);
753 publishOwnDeviceIdIfNeeded();
754 }
755
756 public void wipeOtherPepDevices() {
757 Set<Integer> deviceIds = new HashSet<>();
758 deviceIds.add(getOwnDeviceId());
759 IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIds);
760 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Wiping all other devices from Pep:" + publish);
761 mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
762 @Override
763 public void onIqPacketReceived(Account account, IqPacket packet) {
764 // TODO: implement this!
765 }
766 });
767 }
768
769 public void purgeKey(IdentityKey identityKey) {
770 axolotlStore.setFingerprintTrust(identityKey.getFingerprint().replaceAll("\\s",""), SQLiteAxolotlStore.Trust.COMPROMISED);
771 }
772
773 public void publishOwnDeviceIdIfNeeded() {
774 IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().toBareJid());
775 mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
776 @Override
777 public void onIqPacketReceived(Account account, IqPacket packet) {
778 Element item = mXmppConnectionService.getIqParser().getItem(packet);
779 Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
780 if (deviceIds == null) {
781 deviceIds = new HashSet<Integer>();
782 }
783 if (!deviceIds.contains(getOwnDeviceId())) {
784 deviceIds.add(getOwnDeviceId());
785 IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIds);
786 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Own device " + getOwnDeviceId() + " not in PEP devicelist. Publishing: " + publish);
787 mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
788 @Override
789 public void onIqPacketReceived(Account account, IqPacket packet) {
790 // TODO: implement this!
791 }
792 });
793 }
794 }
795 });
796 }
797
798 public void publishBundlesIfNeeded() {
799 IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(account.getJid().toBareJid(), getOwnDeviceId());
800 mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
801 @Override
802 public void onIqPacketReceived(Account account, IqPacket packet) {
803 PreKeyBundle bundle = mXmppConnectionService.getIqParser().bundle(packet);
804 Map<Integer, ECPublicKey> keys = mXmppConnectionService.getIqParser().preKeyPublics(packet);
805 boolean flush = false;
806 if (bundle == null) {
807 Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Received invalid bundle:" + packet);
808 bundle = new PreKeyBundle(-1, -1, -1 , null, -1, null, null, null);
809 flush = true;
810 }
811 if (keys == null) {
812 Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Received invalid prekeys:" + packet);
813 }
814 try {
815 boolean changed = false;
816 // Validate IdentityKey
817 IdentityKeyPair identityKeyPair = axolotlStore.getIdentityKeyPair();
818 if (flush || !identityKeyPair.getPublicKey().equals(bundle.getIdentityKey())) {
819 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Adding own IdentityKey " + identityKeyPair.getPublicKey() + " to PEP.");
820 changed = true;
821 }
822
823 // Validate signedPreKeyRecord + ID
824 SignedPreKeyRecord signedPreKeyRecord;
825 int numSignedPreKeys = axolotlStore.loadSignedPreKeys().size();
826 try {
827 signedPreKeyRecord = axolotlStore.loadSignedPreKey(bundle.getSignedPreKeyId());
828 if ( flush
829 ||!bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey())
830 || !Arrays.equals(bundle.getSignedPreKeySignature(), signedPreKeyRecord.getSignature())) {
831 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
832 signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
833 axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
834 changed = true;
835 }
836 } catch (InvalidKeyIdException e) {
837 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
838 signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
839 axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
840 changed = true;
841 }
842
843 // Validate PreKeys
844 Set<PreKeyRecord> preKeyRecords = new HashSet<>();
845 if (keys != null) {
846 for (Integer id : keys.keySet()) {
847 try {
848 PreKeyRecord preKeyRecord = axolotlStore.loadPreKey(id);
849 if (preKeyRecord.getKeyPair().getPublicKey().equals(keys.get(id))) {
850 preKeyRecords.add(preKeyRecord);
851 }
852 } catch (InvalidKeyIdException ignored) {
853 }
854 }
855 }
856 int newKeys = NUM_KEYS_TO_PUBLISH - preKeyRecords.size();
857 if (newKeys > 0) {
858 List<PreKeyRecord> newRecords = KeyHelper.generatePreKeys(
859 axolotlStore.getCurrentPreKeyId()+1, newKeys);
860 preKeyRecords.addAll(newRecords);
861 for (PreKeyRecord record : newRecords) {
862 axolotlStore.storePreKey(record.getId(), record);
863 }
864 changed = true;
865 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Adding " + newKeys + " new preKeys to PEP.");
866 }
867
868
869 if(changed) {
870 IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles(
871 signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(),
872 preKeyRecords, getOwnDeviceId());
873 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+ ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing: " + publish);
874 mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
875 @Override
876 public void onIqPacketReceived(Account account, IqPacket packet) {
877 // TODO: implement this!
878 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Published bundle, got: " + packet);
879 }
880 });
881 }
882 } catch (InvalidKeyException e) {
883 Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage());
884 return;
885 }
886 }
887 });
888 }
889
890 public boolean isContactAxolotlCapable(Contact contact) {
891
892 Jid jid = contact.getJid().toBareJid();
893 AxolotlAddress address = new AxolotlAddress(jid.toString(), 0);
894 return sessions.hasAny(address) ||
895 ( deviceIds.containsKey(jid) && !deviceIds.get(jid).isEmpty());
896 }
897 public SQLiteAxolotlStore.Trust getFingerprintTrust(String fingerprint) {
898 return axolotlStore.getFingerprintTrust(fingerprint);
899 }
900
901 public void setFingerprintTrust(String fingerprint, SQLiteAxolotlStore.Trust trust) {
902 axolotlStore.setFingerprintTrust(fingerprint, trust);
903 }
904
905 private void buildSessionFromPEP(final Conversation conversation, final AxolotlAddress address, final boolean flushWaitingQueueAfterFetch) {
906 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new sesstion for " + address.getDeviceId());
907
908 try {
909 IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(
910 Jid.fromString(address.getName()), address.getDeviceId());
911 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Retrieving bundle: " + bundlesPacket);
912 mXmppConnectionService.sendIqPacket(account, bundlesPacket, new OnIqPacketReceived() {
913 private void finish() {
914 AxolotlAddress ownAddress = new AxolotlAddress(conversation.getAccount().getJid().toBareJid().toString(),0);
915 AxolotlAddress foreignAddress = new AxolotlAddress(conversation.getJid().toBareJid().toString(),0);
916 if (!fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)
917 && !fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING)) {
918 if (flushWaitingQueueAfterFetch) {
919 conversation.findUnsentMessagesWithEncryption(Message.ENCRYPTION_AXOLOTL,
920 new Conversation.OnMessageFound() {
921 @Override
922 public void onMessageFound(Message message) {
923 processSending(message,false);
924 }
925 });
926 }
927 mXmppConnectionService.newKeysAvailable();
928 }
929 }
930
931 @Override
932 public void onIqPacketReceived(Account account, IqPacket packet) {
933 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Received preKey IQ packet, processing...");
934 final IqParser parser = mXmppConnectionService.getIqParser();
935 final List<PreKeyBundle> preKeyBundleList = parser.preKeys(packet);
936 final PreKeyBundle bundle = parser.bundle(packet);
937 if (preKeyBundleList.isEmpty() || bundle == null) {
938 Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"preKey IQ packet invalid: " + packet);
939 fetchStatusMap.put(address, FetchStatus.ERROR);
940 finish();
941 return;
942 }
943 Random random = new Random();
944 final PreKeyBundle preKey = preKeyBundleList.get(random.nextInt(preKeyBundleList.size()));
945 if (preKey == null) {
946 //should never happen
947 fetchStatusMap.put(address, FetchStatus.ERROR);
948 finish();
949 return;
950 }
951
952 final PreKeyBundle preKeyBundle = new PreKeyBundle(0, address.getDeviceId(),
953 preKey.getPreKeyId(), preKey.getPreKey(),
954 bundle.getSignedPreKeyId(), bundle.getSignedPreKey(),
955 bundle.getSignedPreKeySignature(), bundle.getIdentityKey());
956
957 axolotlStore.saveIdentity(address.getName(), bundle.getIdentityKey());
958
959 try {
960 SessionBuilder builder = new SessionBuilder(axolotlStore, address);
961 builder.process(preKeyBundle);
962 XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey().getFingerprint().replaceAll("\\s", ""));
963 sessions.put(address, session);
964 fetchStatusMap.put(address, FetchStatus.SUCCESS);
965 } catch (UntrustedIdentityException|InvalidKeyException e) {
966 Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Error building session for " + address + ": "
967 + e.getClass().getName() + ", " + e.getMessage());
968 fetchStatusMap.put(address, FetchStatus.ERROR);
969 }
970
971 finish();
972 }
973 });
974 } catch (InvalidJidException e) {
975 Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Got address with invalid jid: " + address.getName());
976 }
977 }
978
979 public Set<AxolotlAddress> findDevicesWithoutSession(final Conversation conversation) {
980 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Finding devices without session for " + conversation.getContact().getJid().toBareJid());
981 Jid contactJid = conversation.getContact().getJid().toBareJid();
982 Set<AxolotlAddress> addresses = new HashSet<>();
983 if(deviceIds.get(contactJid) != null) {
984 for(Integer foreignId:this.deviceIds.get(contactJid)) {
985 AxolotlAddress address = new AxolotlAddress(contactJid.toString(), foreignId);
986 if(sessions.get(address) == null) {
987 IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
988 if ( identityKey != null ) {
989 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Already have session for " + address.toString() + ", adding to cache...");
990 XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey.getFingerprint().replaceAll("\\s", ""));
991 sessions.put(address, session);
992 } else {
993 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + account.getJid().toBareJid() + ":" + foreignId);
994 addresses.add(new AxolotlAddress(contactJid.toString(), foreignId));
995 }
996 }
997 }
998 } else {
999 Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Have no target devices in PEP!");
1000 }
1001 if(deviceIds.get(account.getJid().toBareJid()) != null) {
1002 for(Integer ownId:this.deviceIds.get(account.getJid().toBareJid())) {
1003 AxolotlAddress address = new AxolotlAddress(account.getJid().toBareJid().toString(), ownId);
1004 if(sessions.get(address) == null) {
1005 IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
1006 if ( identityKey != null ) {
1007 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Already have session for " + address.toString() + ", adding to cache...");
1008 XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey.getFingerprint().replaceAll("\\s", ""));
1009 sessions.put(address, session);
1010 } else {
1011 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + account.getJid().toBareJid() + ":" + ownId);
1012 addresses.add(new AxolotlAddress(account.getJid().toBareJid().toString(), ownId));
1013 }
1014 }
1015 }
1016 }
1017
1018 return addresses;
1019 }
1020
1021 public boolean createSessionsIfNeeded(final Conversation conversation, final boolean flushWaitingQueueAfterFetch) {
1022 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Creating axolotl sessions if needed...");
1023 boolean newSessions = false;
1024 Set<AxolotlAddress> addresses = findDevicesWithoutSession(conversation);
1025 for (AxolotlAddress address : addresses) {
1026 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Processing device: " + address.toString());
1027 FetchStatus status = fetchStatusMap.get(address);
1028 if ( status == null || status == FetchStatus.ERROR ) {
1029 fetchStatusMap.put(address, FetchStatus.PENDING);
1030 this.buildSessionFromPEP(conversation, address, flushWaitingQueueAfterFetch);
1031 newSessions = true;
1032 } else {
1033 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Already fetching bundle for " + address.toString());
1034 }
1035 }
1036
1037 return newSessions;
1038 }
1039
1040 public boolean hasPendingKeyFetches(Conversation conversation) {
1041 AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(),0);
1042 AxolotlAddress foreignAddress = new AxolotlAddress(conversation.getJid().toBareJid().toString(),0);
1043 return fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)
1044 ||fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING);
1045
1046 }
1047
1048 @Nullable
1049 public XmppAxolotlMessage encrypt(Message message ){
1050 final String content;
1051 if (message.hasFileOnRemoteHost()) {
1052 content = message.getFileParams().url.toString();
1053 } else {
1054 content = message.getBody();
1055 }
1056 final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(message.getContact().getJid().toBareJid(),
1057 getOwnDeviceId(), content);
1058
1059 if(findSessionsforContact(message.getContact()).isEmpty()) {
1060 return null;
1061 }
1062 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building axolotl foreign headers...");
1063 for (XmppAxolotlSession session : findSessionsforContact(message.getContact())) {
1064 Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account)+session.remoteAddress.toString());
1065 //if(!session.isTrusted()) {
1066 // TODO: handle this properly
1067 // continue;
1068 // }
1069 axolotlMessage.addHeader(session.processSending(axolotlMessage.getInnerKey()));
1070 }
1071 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building axolotl own headers...");
1072 for (XmppAxolotlSession session : findOwnSessions()) {
1073 Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account)+session.remoteAddress.toString());
1074 // if(!session.isTrusted()) {
1075 // TODO: handle this properly
1076 // continue;
1077 // }
1078 axolotlMessage.addHeader(session.processSending(axolotlMessage.getInnerKey()));
1079 }
1080
1081 return axolotlMessage;
1082 }
1083
1084 private void processSending(final Message message, final boolean delay) {
1085 executor.execute(new Runnable() {
1086 @Override
1087 public void run() {
1088 XmppAxolotlMessage axolotlMessage = encrypt(message);
1089 if (axolotlMessage == null) {
1090 mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED);
1091 //mXmppConnectionService.updateConversationUi();
1092 } else {
1093 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Generated message, caching: " + message.getUuid());
1094 messageCache.put(message.getUuid(), axolotlMessage);
1095 mXmppConnectionService.resendMessage(message,delay);
1096 }
1097 }
1098 });
1099 }
1100
1101 public void prepareMessage(final Message message,final boolean delay) {
1102 if (!messageCache.containsKey(message.getUuid())) {
1103 boolean newSessions = createSessionsIfNeeded(message.getConversation(), true);
1104 if (!newSessions) {
1105 this.processSending(message,delay);
1106 }
1107 }
1108 }
1109
1110 public XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message) {
1111 XmppAxolotlMessage axolotlMessage = messageCache.get(message.getUuid());
1112 if (axolotlMessage != null) {
1113 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Cache hit: " + message.getUuid());
1114 messageCache.remove(message.getUuid());
1115 } else {
1116 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Cache miss: " + message.getUuid());
1117 }
1118 return axolotlMessage;
1119 }
1120
1121 public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceiving(XmppAxolotlMessage message) {
1122 XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null;
1123 AxolotlAddress senderAddress = new AxolotlAddress(message.getFrom().toString(),
1124 message.getSenderDeviceId());
1125
1126 boolean newSession = false;
1127 XmppAxolotlSession session = sessions.get(senderAddress);
1128 if (session == null) {
1129 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Account: "+account.getJid()+" No axolotl session found while parsing received message " + message);
1130 // TODO: handle this properly
1131 IdentityKey identityKey = axolotlStore.loadSession(senderAddress).getSessionState().getRemoteIdentityKey();
1132 if ( identityKey != null ) {
1133 session = new XmppAxolotlSession(account, axolotlStore, senderAddress, identityKey.getFingerprint().replaceAll("\\s", ""));
1134 } else {
1135 session = new XmppAxolotlSession(account, axolotlStore, senderAddress);
1136 }
1137 newSession = true;
1138 }
1139
1140 for (XmppAxolotlMessage.XmppAxolotlMessageHeader header : message.getHeaders()) {
1141 if (header.getRecipientDeviceId() == getOwnDeviceId()) {
1142 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Found axolotl header matching own device ID, processing...");
1143 byte[] payloadKey = session.processReceiving(header);
1144 if (payloadKey != null) {
1145 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Got payload key from axolotl header. Decrypting message...");
1146 plaintextMessage = message.decrypt(session, payloadKey, session.getFingerprint());
1147 }
1148 Integer preKeyId = session.getPreKeyId();
1149 if (preKeyId != null) {
1150 publishBundlesIfNeeded();
1151 session.resetPreKeyId();
1152 }
1153 break;
1154 }
1155 }
1156
1157 if (newSession && plaintextMessage != null) {
1158 sessions.put(senderAddress, session);
1159 }
1160
1161 return plaintextMessage;
1162 }
1163}