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