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.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.SessionCipher;
17import org.whispersystems.libaxolotl.UntrustedIdentityException;
18import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
19import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
20import org.whispersystems.libaxolotl.protocol.WhisperMessage;
21
22import eu.siacs.conversations.Config;
23import eu.siacs.conversations.entities.Account;
24
25public class XmppAxolotlSession implements Comparable<XmppAxolotlSession> {
26 private final SessionCipher cipher;
27 private final SQLiteAxolotlStore sqLiteAxolotlStore;
28 private final AxolotlAddress remoteAddress;
29 private final Account account;
30 private IdentityKey identityKey;
31 private Integer preKeyId = null;
32 private boolean fresh = true;
33
34 public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress, IdentityKey identityKey) {
35 this(account, store, remoteAddress);
36 this.identityKey = identityKey;
37 }
38
39 public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress) {
40 this.cipher = new SessionCipher(store, remoteAddress);
41 this.remoteAddress = remoteAddress;
42 this.sqLiteAxolotlStore = store;
43 this.account = account;
44 }
45
46 public Integer getPreKeyId() {
47 return preKeyId;
48 }
49
50 public void resetPreKeyId() {
51
52 preKeyId = null;
53 }
54
55 public String getFingerprint() {
56 return identityKey == null ? null : identityKey.getFingerprint().replaceAll("\\s", "");
57 }
58
59 public IdentityKey getIdentityKey() {
60 return identityKey;
61 }
62
63 public AxolotlAddress getRemoteAddress() {
64 return remoteAddress;
65 }
66
67 public boolean isFresh() {
68 return fresh;
69 }
70
71 public void setNotFresh() {
72 this.fresh = false;
73 }
74
75 protected void setTrust(FingerprintStatus status) {
76 sqLiteAxolotlStore.setFingerprintStatus(getFingerprint(), status);
77 }
78
79 public FingerprintStatus getTrust() {
80 FingerprintStatus status = sqLiteAxolotlStore.getFingerprintStatus(getFingerprint());
81 return (status == null) ? FingerprintStatus.createActiveUndecided() : status;
82 }
83
84 @Nullable
85 public byte[] processReceiving(AxolotlKey encryptedKey) {
86 byte[] plaintext = null;
87 FingerprintStatus status = getTrust();
88 if (!status.isCompromised()) {
89 try {
90 try {
91 PreKeyWhisperMessage message = new PreKeyWhisperMessage(encryptedKey.key);
92 if (!message.getPreKeyId().isPresent()) {
93 Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "PreKeyWhisperMessage did not contain a PreKeyId");
94 return null;
95 }
96 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "PreKeyWhisperMessage received, new session ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId());
97 IdentityKey msgIdentityKey = message.getIdentityKey();
98 if (this.identityKey != null && !this.identityKey.equals(msgIdentityKey)) {
99 Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Had session with fingerprint " + this.getFingerprint() + ", received message with fingerprint " + msgIdentityKey.getFingerprint());
100 } else {
101 this.identityKey = msgIdentityKey;
102 plaintext = cipher.decrypt(message);
103 preKeyId = message.getPreKeyId().get();
104 }
105 } catch (InvalidMessageException | InvalidVersionException e) {
106 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "WhisperMessage received");
107 WhisperMessage message = new WhisperMessage(encryptedKey.key);
108 plaintext = cipher.decrypt(message);
109 } catch (InvalidKeyException | InvalidKeyIdException | UntrustedIdentityException e) {
110 Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage());
111 }
112 } catch (LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException e) {
113 Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage());
114 }
115
116 if (plaintext != null) {
117 if (!status.isActive()) {
118 setTrust(status.toActive());
119 }
120 }
121 } else {
122 Log.d(Config.LOGTAG,account.getJid().toBareJid()+" not encrypting omemo message from fingerprint "+getFingerprint()+" because it was marked as compromised");
123 }
124 return plaintext;
125 }
126
127 @Nullable
128 public AxolotlKey processSending(@NonNull byte[] outgoingMessage) {
129 FingerprintStatus status = getTrust();
130 if (status.isTrustedAndActive()) {
131 CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage);
132 return new AxolotlKey(ciphertextMessage.serialize(),ciphertextMessage.getType() == CiphertextMessage.PREKEY_TYPE);
133 } else {
134 return null;
135 }
136 }
137
138 public Account getAccount() {
139 return account;
140 }
141
142 @Override
143 public int compareTo(XmppAxolotlSession o) {
144 return getTrust().compareTo(o.getTrust());
145 }
146
147 public static class AxolotlKey {
148
149
150 public final byte[] key;
151 public final boolean prekey;
152
153 public AxolotlKey(byte[] key, boolean prekey) {
154 this.key = key;
155 this.prekey = prekey;
156 }
157 }
158}