1package eu.siacs.conversations.crypto.axolotl;
2
3import android.util.Base64;
4import android.util.Log;
5
6import org.whispersystems.libaxolotl.AxolotlAddress;
7import org.whispersystems.libaxolotl.DuplicateMessageException;
8import org.whispersystems.libaxolotl.IdentityKey;
9import org.whispersystems.libaxolotl.IdentityKeyPair;
10import org.whispersystems.libaxolotl.InvalidKeyException;
11import org.whispersystems.libaxolotl.InvalidKeyIdException;
12import org.whispersystems.libaxolotl.InvalidMessageException;
13import org.whispersystems.libaxolotl.InvalidVersionException;
14import org.whispersystems.libaxolotl.LegacyMessageException;
15import org.whispersystems.libaxolotl.NoSessionException;
16import org.whispersystems.libaxolotl.SessionBuilder;
17import org.whispersystems.libaxolotl.SessionCipher;
18import org.whispersystems.libaxolotl.UntrustedIdentityException;
19import org.whispersystems.libaxolotl.ecc.Curve;
20import org.whispersystems.libaxolotl.ecc.ECKeyPair;
21import org.whispersystems.libaxolotl.ecc.ECPublicKey;
22import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
23import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
24import org.whispersystems.libaxolotl.protocol.WhisperMessage;
25import org.whispersystems.libaxolotl.state.AxolotlStore;
26import org.whispersystems.libaxolotl.state.PreKeyBundle;
27import org.whispersystems.libaxolotl.state.PreKeyRecord;
28import org.whispersystems.libaxolotl.state.SessionRecord;
29import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
30import org.whispersystems.libaxolotl.util.KeyHelper;
31
32import java.util.ArrayList;
33import java.util.HashMap;
34import java.util.HashSet;
35import java.util.List;
36import java.util.Map;
37import java.util.Random;
38import java.util.Set;
39
40import eu.siacs.conversations.Config;
41import eu.siacs.conversations.entities.Account;
42import eu.siacs.conversations.entities.Contact;
43import eu.siacs.conversations.entities.Conversation;
44import eu.siacs.conversations.services.XmppConnectionService;
45import eu.siacs.conversations.xmpp.jid.InvalidJidException;
46import eu.siacs.conversations.xmpp.jid.Jid;
47
48public class AxolotlService {
49
50 private final Account account;
51 private final XmppConnectionService mXmppConnectionService;
52 private final SQLiteAxolotlStore axolotlStore;
53 private final SessionMap sessions;
54 private int ownDeviceId;
55
56 public static class SQLiteAxolotlStore implements AxolotlStore {
57
58 public static final String PREKEY_TABLENAME = "prekeys";
59 public static final String SIGNED_PREKEY_TABLENAME = "signed_prekeys";
60 public static final String SESSION_TABLENAME = "sessions";
61 public static final String ACCOUNT = "account";
62 public static final String DEVICE_ID = "device_id";
63 public static final String ID = "id";
64 public static final String KEY = "key";
65 public static final String NAME = "name";
66 public static final String TRUSTED = "trusted";
67
68 public static final String JSONKEY_IDENTITY_KEY_PAIR = "axolotl_key";
69 public static final String JSONKEY_REGISTRATION_ID = "axolotl_reg_id";
70 public static final String JSONKEY_CURRENT_PREKEY_ID = "axolotl_cur_prekey_id";
71
72 private final Account account;
73 private final XmppConnectionService mXmppConnectionService;
74
75 private final IdentityKeyPair identityKeyPair;
76 private final int localRegistrationId;
77 private int currentPreKeyId = 0;
78
79
80 private static IdentityKeyPair generateIdentityKeyPair() {
81 Log.d(Config.LOGTAG, "Generating axolotl IdentityKeyPair...");
82 ECKeyPair identityKeyPairKeys = Curve.generateKeyPair();
83 IdentityKeyPair ownKey = new IdentityKeyPair(new IdentityKey(identityKeyPairKeys.getPublicKey()),
84 identityKeyPairKeys.getPrivateKey());
85 return ownKey;
86 }
87
88 private static int generateRegistrationId() {
89 Log.d(Config.LOGTAG, "Generating axolotl registration ID...");
90 int reg_id = KeyHelper.generateRegistrationId(false);
91 return reg_id;
92 }
93
94 public SQLiteAxolotlStore(Account account, XmppConnectionService service) {
95 this.account = account;
96 this.mXmppConnectionService = service;
97 this.identityKeyPair = loadIdentityKeyPair();
98 this.localRegistrationId = loadRegistrationId();
99 this.currentPreKeyId = loadCurrentPreKeyId();
100 for( SignedPreKeyRecord record:loadSignedPreKeys()) {
101 Log.d(Config.LOGTAG, "Got Axolotl signed prekey record:" + record.getId());
102 }
103 }
104
105 public int getCurrentPreKeyId() {
106 return currentPreKeyId;
107 }
108
109 // --------------------------------------
110 // IdentityKeyStore
111 // --------------------------------------
112
113 private IdentityKeyPair loadIdentityKeyPair() {
114 String serializedKey = this.account.getKey(JSONKEY_IDENTITY_KEY_PAIR);
115 IdentityKeyPair ownKey;
116 if( serializedKey != null ) {
117 try {
118 ownKey = new IdentityKeyPair(Base64.decode(serializedKey,Base64.DEFAULT));
119 return ownKey;
120 } catch (InvalidKeyException e) {
121 Log.d(Config.LOGTAG, "Invalid key stored for account " + account.getJid() + ": " + e.getMessage());
122// return null;
123 }
124 } //else {
125 Log.d(Config.LOGTAG, "Could not retrieve axolotl key for account " + account.getJid());
126 ownKey = generateIdentityKeyPair();
127 boolean success = this.account.setKey(JSONKEY_IDENTITY_KEY_PAIR, Base64.encodeToString(ownKey.serialize(), Base64.DEFAULT));
128 if(success) {
129 mXmppConnectionService.databaseBackend.updateAccount(account);
130 } else {
131 Log.e(Config.LOGTAG, "Failed to write new key to the database!");
132 }
133 //}
134 return ownKey;
135 }
136
137 private int loadRegistrationId() {
138 String regIdString = this.account.getKey(JSONKEY_REGISTRATION_ID);
139 int reg_id;
140 if (regIdString != null) {
141 reg_id = Integer.valueOf(regIdString);
142 } else {
143 Log.d(Config.LOGTAG, "Could not retrieve axolotl registration id for account " + account.getJid());
144 reg_id = generateRegistrationId();
145 boolean success = this.account.setKey(JSONKEY_REGISTRATION_ID,""+reg_id);
146 if(success) {
147 mXmppConnectionService.databaseBackend.updateAccount(account);
148 } else {
149 Log.e(Config.LOGTAG, "Failed to write new key to the database!");
150 }
151 }
152 return reg_id;
153 }
154
155 private int loadCurrentPreKeyId() {
156 String regIdString = this.account.getKey(JSONKEY_CURRENT_PREKEY_ID);
157 int reg_id;
158 if (regIdString != null) {
159 reg_id = Integer.valueOf(regIdString);
160 } else {
161 Log.d(Config.LOGTAG, "Could not retrieve current prekey id for account " + account.getJid());
162 reg_id = 0;
163 }
164 return reg_id;
165 }
166
167
168 /**
169 * Get the local client's identity key pair.
170 *
171 * @return The local client's persistent identity key pair.
172 */
173 @Override
174 public IdentityKeyPair getIdentityKeyPair() {
175 return identityKeyPair;
176 }
177
178 /**
179 * Return the local client's registration ID.
180 * <p/>
181 * Clients should maintain a registration ID, a random number
182 * between 1 and 16380 that's generated once at install time.
183 *
184 * @return the local client's registration ID.
185 */
186 @Override
187 public int getLocalRegistrationId() {
188 return localRegistrationId;
189 }
190
191 /**
192 * Save a remote client's identity key
193 * <p/>
194 * Store a remote client's identity key as trusted.
195 *
196 * @param name The name of the remote client.
197 * @param identityKey The remote client's identity key.
198 */
199 @Override
200 public void saveIdentity(String name, IdentityKey identityKey) {
201 try {
202 Jid contactJid = Jid.fromString(name);
203 Conversation conversation = this.mXmppConnectionService.find(this.account, contactJid);
204 if (conversation != null) {
205 conversation.getContact().addAxolotlIdentityKey(identityKey);
206 mXmppConnectionService.updateConversationUi();
207 mXmppConnectionService.syncRosterToDisk(conversation.getAccount());
208 }
209 } catch (final InvalidJidException e) {
210 Log.e(Config.LOGTAG, "Failed to save identityKey for contact name " + name + ": " + e.toString());
211 }
212 }
213
214 /**
215 * Verify a remote client's identity key.
216 * <p/>
217 * Determine whether a remote client's identity is trusted. Convention is
218 * that the TextSecure protocol is 'trust on first use.' This means that
219 * an identity key is considered 'trusted' if there is no entry for the recipient
220 * in the local store, or if it matches the saved key for a recipient in the local
221 * store. Only if it mismatches an entry in the local store is it considered
222 * 'untrusted.'
223 *
224 * @param name The name of the remote client.
225 * @param identityKey The identity key to verify.
226 * @return true if trusted, false if untrusted.
227 */
228 @Override
229 public boolean isTrustedIdentity(String name, IdentityKey identityKey) {
230 try {
231 Jid contactJid = Jid.fromString(name);
232 Conversation conversation = this.mXmppConnectionService.find(this.account, contactJid);
233 if (conversation != null) {
234 List<IdentityKey> trustedKeys = conversation.getContact().getAxolotlIdentityKeys();
235 return trustedKeys.isEmpty() || trustedKeys.contains(identityKey);
236 } else {
237 return false;
238 }
239 } catch (final InvalidJidException e) {
240 Log.e(Config.LOGTAG, "Failed to save identityKey for contact name" + name + ": " + e.toString());
241 return false;
242 }
243 }
244
245 // --------------------------------------
246 // SessionStore
247 // --------------------------------------
248
249 /**
250 * Returns a copy of the {@link SessionRecord} corresponding to the recipientId + deviceId tuple,
251 * or a new SessionRecord if one does not currently exist.
252 * <p/>
253 * It is important that implementations return a copy of the current durable information. The
254 * returned SessionRecord may be modified, but those changes should not have an effect on the
255 * durable session state (what is returned by subsequent calls to this method) without the
256 * store method being called here first.
257 *
258 * @param address The name and device ID of the remote client.
259 * @return a copy of the SessionRecord corresponding to the recipientId + deviceId tuple, or
260 * a new SessionRecord if one does not currently exist.
261 */
262 @Override
263 public SessionRecord loadSession(AxolotlAddress address) {
264 SessionRecord session = mXmppConnectionService.databaseBackend.loadSession(this.account, address);
265 return (session!=null)?session:new SessionRecord();
266 }
267
268 /**
269 * Returns all known devices with active sessions for a recipient
270 *
271 * @param name the name of the client.
272 * @return all known sub-devices with active sessions.
273 */
274 @Override
275 public List<Integer> getSubDeviceSessions(String name) {
276 return mXmppConnectionService.databaseBackend.getSubDeviceSessions(account,
277 new AxolotlAddress(name,0));
278 }
279
280 /**
281 * Commit to storage the {@link SessionRecord} for a given recipientId + deviceId tuple.
282 *
283 * @param address the address of the remote client.
284 * @param record the current SessionRecord for the remote client.
285 */
286 @Override
287 public void storeSession(AxolotlAddress address, SessionRecord record) {
288 mXmppConnectionService.databaseBackend.storeSession(account, address, record);
289 }
290
291 /**
292 * Determine whether there is a committed {@link SessionRecord} for a recipientId + deviceId tuple.
293 *
294 * @param address the address of the remote client.
295 * @return true if a {@link SessionRecord} exists, false otherwise.
296 */
297 @Override
298 public boolean containsSession(AxolotlAddress address) {
299 return mXmppConnectionService.databaseBackend.containsSession(account, address);
300 }
301
302 /**
303 * Remove a {@link SessionRecord} for a recipientId + deviceId tuple.
304 *
305 * @param address the address of the remote client.
306 */
307 @Override
308 public void deleteSession(AxolotlAddress address) {
309 mXmppConnectionService.databaseBackend.deleteSession(account, address);
310 }
311
312 /**
313 * Remove the {@link SessionRecord}s corresponding to all devices of a recipientId.
314 *
315 * @param name the name of the remote client.
316 */
317 @Override
318 public void deleteAllSessions(String name) {
319 mXmppConnectionService.databaseBackend.deleteAllSessions(account,
320 new AxolotlAddress(name, 0));
321 }
322
323 public boolean isTrustedSession(AxolotlAddress address) {
324 return mXmppConnectionService.databaseBackend.isTrustedSession(this.account, address);
325 }
326
327 public void setTrustedSession(AxolotlAddress address, boolean trusted) {
328 mXmppConnectionService.databaseBackend.setTrustedSession(this.account, address,trusted);
329 }
330
331 // --------------------------------------
332 // PreKeyStore
333 // --------------------------------------
334
335 /**
336 * Load a local PreKeyRecord.
337 *
338 * @param preKeyId the ID of the local PreKeyRecord.
339 * @return the corresponding PreKeyRecord.
340 * @throws InvalidKeyIdException when there is no corresponding PreKeyRecord.
341 */
342 @Override
343 public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException {
344 PreKeyRecord record = mXmppConnectionService.databaseBackend.loadPreKey(account, preKeyId);
345 if(record == null) {
346 throw new InvalidKeyIdException("No such PreKeyRecord: " + preKeyId);
347 }
348 return record;
349 }
350
351 /**
352 * Store a local PreKeyRecord.
353 *
354 * @param preKeyId the ID of the PreKeyRecord to store.
355 * @param record the PreKeyRecord.
356 */
357 @Override
358 public void storePreKey(int preKeyId, PreKeyRecord record) {
359 mXmppConnectionService.databaseBackend.storePreKey(account, record);
360 currentPreKeyId = preKeyId;
361 boolean success = this.account.setKey(JSONKEY_CURRENT_PREKEY_ID,Integer.toString(preKeyId));
362 if(success) {
363 mXmppConnectionService.databaseBackend.updateAccount(account);
364 } else {
365 Log.e(Config.LOGTAG, "Failed to write new prekey id to the database!");
366 }
367 }
368
369 /**
370 * @param preKeyId A PreKeyRecord ID.
371 * @return true if the store has a record for the preKeyId, otherwise false.
372 */
373 @Override
374 public boolean containsPreKey(int preKeyId) {
375 return mXmppConnectionService.databaseBackend.containsPreKey(account, preKeyId);
376 }
377
378 /**
379 * Delete a PreKeyRecord from local storage.
380 *
381 * @param preKeyId The ID of the PreKeyRecord to remove.
382 */
383 @Override
384 public void removePreKey(int preKeyId) {
385 mXmppConnectionService.databaseBackend.deletePreKey(account, preKeyId);
386 }
387
388 // --------------------------------------
389 // SignedPreKeyStore
390 // --------------------------------------
391
392 /**
393 * Load a local SignedPreKeyRecord.
394 *
395 * @param signedPreKeyId the ID of the local SignedPreKeyRecord.
396 * @return the corresponding SignedPreKeyRecord.
397 * @throws InvalidKeyIdException when there is no corresponding SignedPreKeyRecord.
398 */
399 @Override
400 public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException {
401 SignedPreKeyRecord record = mXmppConnectionService.databaseBackend.loadSignedPreKey(account, signedPreKeyId);
402 if(record == null) {
403 throw new InvalidKeyIdException("No such SignedPreKeyRecord: " + signedPreKeyId);
404 }
405 return record;
406 }
407
408 /**
409 * Load all local SignedPreKeyRecords.
410 *
411 * @return All stored SignedPreKeyRecords.
412 */
413 @Override
414 public List<SignedPreKeyRecord> loadSignedPreKeys() {
415 return mXmppConnectionService.databaseBackend.loadSignedPreKeys(account);
416 }
417
418 /**
419 * Store a local SignedPreKeyRecord.
420 *
421 * @param signedPreKeyId the ID of the SignedPreKeyRecord to store.
422 * @param record the SignedPreKeyRecord.
423 */
424 @Override
425 public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) {
426 mXmppConnectionService.databaseBackend.storeSignedPreKey(account, record);
427 }
428
429 /**
430 * @param signedPreKeyId A SignedPreKeyRecord ID.
431 * @return true if the store has a record for the signedPreKeyId, otherwise false.
432 */
433 @Override
434 public boolean containsSignedPreKey(int signedPreKeyId) {
435 return mXmppConnectionService.databaseBackend.containsSignedPreKey(account, signedPreKeyId);
436 }
437
438 /**
439 * Delete a SignedPreKeyRecord from local storage.
440 *
441 * @param signedPreKeyId The ID of the SignedPreKeyRecord to remove.
442 */
443 @Override
444 public void removeSignedPreKey(int signedPreKeyId) {
445 mXmppConnectionService.databaseBackend.deleteSignedPreKey(account, signedPreKeyId);
446 }
447 }
448
449 public static class XmppAxolotlSession {
450 private SessionCipher cipher;
451 private boolean isTrusted = false;
452 private SQLiteAxolotlStore sqLiteAxolotlStore;
453 private AxolotlAddress remoteAddress;
454
455 public XmppAxolotlSession(SQLiteAxolotlStore store, AxolotlAddress remoteAddress) {
456 this.cipher = new SessionCipher(store, remoteAddress);
457 this.remoteAddress = remoteAddress;
458 this.sqLiteAxolotlStore = store;
459 this.isTrusted = sqLiteAxolotlStore.isTrustedSession(remoteAddress);
460 }
461
462 public void trust() {
463 sqLiteAxolotlStore.setTrustedSession(remoteAddress, true);
464 this.isTrusted = true;
465 }
466
467 public boolean isTrusted() {
468 return this.isTrusted;
469 }
470
471 public byte[] processReceiving(XmppAxolotlMessage.XmppAxolotlMessageHeader incomingHeader) {
472 byte[] plaintext = null;
473 try {
474 try {
475 PreKeyWhisperMessage message = new PreKeyWhisperMessage(incomingHeader.getContents());
476 Log.d(Config.LOGTAG,"PreKeyWhisperMessage ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId());
477 plaintext = cipher.decrypt(message);
478 } catch (InvalidMessageException|InvalidVersionException e) {
479 WhisperMessage message = new WhisperMessage(incomingHeader.getContents());
480 plaintext = cipher.decrypt(message);
481 } catch (InvalidKeyException|InvalidKeyIdException| UntrustedIdentityException e) {
482 Log.d(Config.LOGTAG, "Error decrypting axolotl header: " + e.getMessage());
483 }
484 } catch (LegacyMessageException|InvalidMessageException e) {
485 Log.d(Config.LOGTAG, "Error decrypting axolotl header: " + e.getMessage());
486 } catch (DuplicateMessageException|NoSessionException e) {
487 Log.d(Config.LOGTAG, "Error decrypting axolotl header: " + e.getMessage());
488 }
489 return plaintext;
490 }
491
492 public XmppAxolotlMessage.XmppAxolotlMessageHeader processSending(byte[] outgoingMessage) {
493 CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage);
494 XmppAxolotlMessage.XmppAxolotlMessageHeader header =
495 new XmppAxolotlMessage.XmppAxolotlMessageHeader(remoteAddress.getDeviceId(),
496 ciphertextMessage.serialize());
497 return header;
498 }
499 }
500
501 private static class AxolotlAddressMap<T> {
502 protected Map<String, Map<Integer,T>> map;
503 protected final Object MAP_LOCK = new Object();
504
505 public AxolotlAddressMap() {
506 this.map = new HashMap<>();
507 }
508
509 public void put(AxolotlAddress address, T value) {
510 synchronized (MAP_LOCK) {
511 Map<Integer, T> devices = map.get(address.getName());
512 if (devices == null) {
513 devices = new HashMap<>();
514 map.put(address.getName(), devices);
515 }
516 devices.put(address.getDeviceId(), value);
517 }
518 }
519
520 public T get(AxolotlAddress address) {
521 synchronized (MAP_LOCK) {
522 Map<Integer, T> devices = map.get(address.getName());
523 if(devices == null) {
524 return null;
525 }
526 return devices.get(address.getDeviceId());
527 }
528 }
529
530 public Map<Integer, T> getAll(AxolotlAddress address) {
531 synchronized (MAP_LOCK) {
532 Map<Integer, T> devices = map.get(address.getName());
533 if(devices == null) {
534 return new HashMap<>();
535 }
536 return devices;
537 }
538 }
539
540 public boolean hasAny(AxolotlAddress address) {
541 synchronized (MAP_LOCK) {
542 Map<Integer, T> devices = map.get(address.getName());
543 return devices != null && !devices.isEmpty();
544 }
545 }
546
547
548 }
549
550 private static class SessionMap extends AxolotlAddressMap<XmppAxolotlSession> {
551
552 public SessionMap(SQLiteAxolotlStore store, Account account) {
553 super();
554 this.fillMap(store, account);
555 }
556
557 private void fillMap(SQLiteAxolotlStore store, Account account) {
558 for(Contact contact:account.getRoster().getContacts()){
559 Jid bareJid = contact.getJid().toBareJid();
560 if(bareJid == null) {
561 continue; // FIXME: handle this?
562 }
563 String address = bareJid.toString();
564 List<Integer> deviceIDs = store.getSubDeviceSessions(address);
565 for(Integer deviceId:deviceIDs) {
566 AxolotlAddress axolotlAddress = new AxolotlAddress(address, deviceId);
567 this.put(axolotlAddress, new XmppAxolotlSession(store, axolotlAddress));
568 }
569 }
570 }
571
572 }
573
574 }
575
576 public AxolotlService(Account account, XmppConnectionService connectionService) {
577 this.mXmppConnectionService = connectionService;
578 this.account = account;
579 this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService);
580 this.sessions = new SessionMap(axolotlStore, account);
581 this.ownDeviceId = axolotlStore.getLocalRegistrationId();
582 }
583
584 public void trustSession(AxolotlAddress counterpart) {
585 XmppAxolotlSession session = sessions.get(counterpart);
586 if(session != null) {
587 session.trust();
588 }
589 }
590
591 public boolean isTrustedSession(AxolotlAddress counterpart) {
592 XmppAxolotlSession session = sessions.get(counterpart);
593 return session != null && session.isTrusted();
594 }
595
596 private AxolotlAddress getAddressForJid(Jid jid) {
597 return new AxolotlAddress(jid.toString(), 0);
598 }
599
600 private Set<XmppAxolotlSession> findOwnSessions() {
601 AxolotlAddress ownAddress = getAddressForJid(account.getJid());
602 Set<XmppAxolotlSession> ownDeviceSessions = new HashSet<>(this.sessions.getAll(ownAddress).values());
603 return ownDeviceSessions;
604 }
605
606 private Set<XmppAxolotlSession> findSessionsforContact(Contact contact) {
607 AxolotlAddress contactAddress = getAddressForJid(contact.getJid());
608 Set<XmppAxolotlSession> sessions = new HashSet<>(this.sessions.getAll(contactAddress).values());
609 return sessions;
610 }
611
612 private boolean hasAny(Contact contact) {
613 AxolotlAddress contactAddress = getAddressForJid(contact.getJid());
614 return sessions.hasAny(contactAddress);
615 }
616
617 public int getOwnDeviceId() {
618 return ownDeviceId;
619 }
620
621 private void createSessionsIfNeeded(Contact contact) throws NoSessionsCreatedException {
622 }
623
624 public XmppAxolotlMessage processSending(Contact contact, String outgoingMessage) throws NoSessionsCreatedException {
625 XmppAxolotlMessage message = new XmppAxolotlMessage(contact, ownDeviceId, outgoingMessage);
626 createSessionsIfNeeded(contact);
627 Log.d(Config.LOGTAG, "Building axolotl foreign headers...");
628
629 for(XmppAxolotlSession session : findSessionsforContact(contact)) {
630// if(!session.isTrusted()) {
631 // TODO: handle this properly
632 // continue;
633 // }
634 message.addHeader(session.processSending(message.getInnerKey()));
635 }
636 Log.d(Config.LOGTAG, "Building axolotl own headers...");
637 for(XmppAxolotlSession session : findOwnSessions()) {
638 // if(!session.isTrusted()) {
639 // TODO: handle this properly
640 // continue;
641 // }
642 message.addHeader(session.processSending(message.getInnerKey()));
643 }
644
645 return message;
646 }
647
648 public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceiving(XmppAxolotlMessage message) {
649 XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null;
650 AxolotlAddress senderAddress = new AxolotlAddress(message.getContact().getJid().toBareJid().toString(),
651 message.getSenderDeviceId());
652
653 XmppAxolotlSession session = sessions.get(senderAddress);
654 if (session == null) {
655 Log.d(Config.LOGTAG, "No axolotl session found while parsing received message " + message);
656 // TODO: handle this properly
657 session = new XmppAxolotlSession(axolotlStore, senderAddress);
658
659 }
660
661 for(XmppAxolotlMessage.XmppAxolotlMessageHeader header : message.getHeaders()) {
662 if (header.getRecipientDeviceId() == ownDeviceId) {
663 Log.d(Config.LOGTAG, "Found axolotl header matching own device ID, processing...");
664 byte[] payloadKey = session.processReceiving(header);
665 if (payloadKey != null) {
666 Log.d(Config.LOGTAG, "Got payload key from axolotl header. Decrypting message...");
667 plaintextMessage = message.decrypt(session, payloadKey);
668 }
669 }
670 }
671
672 return plaintextMessage;
673 }
674}