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 java.util.HashMap;
23import java.util.Map;
24
25import eu.siacs.conversations.Config;
26import eu.siacs.conversations.entities.Account;
27
28public class XmppAxolotlSession {
29 private final SessionCipher cipher;
30 private final SQLiteAxolotlStore sqLiteAxolotlStore;
31 private final AxolotlAddress remoteAddress;
32 private final Account account;
33 private IdentityKey identityKey;
34 private Integer preKeyId = null;
35 private boolean fresh = true;
36
37 public enum Trust {
38 UNDECIDED(0),
39 TRUSTED(1),
40 UNTRUSTED(2),
41 COMPROMISED(3),
42 INACTIVE_TRUSTED(4),
43 INACTIVE_UNDECIDED(5),
44 INACTIVE_UNTRUSTED(6),
45 TRUSTED_X509(7),
46 INACTIVE_TRUSTED_X509(8);
47
48 private static final Map<Integer, Trust> trustsByValue = new HashMap<>();
49
50 static {
51 for (Trust trust : Trust.values()) {
52 trustsByValue.put(trust.getCode(), trust);
53 }
54 }
55
56 private final int code;
57
58 Trust(int code) {
59 this.code = code;
60 }
61
62 public int getCode() {
63 return this.code;
64 }
65
66 public String toString() {
67 switch (this) {
68 case UNDECIDED:
69 return "Trust undecided " + getCode();
70 case TRUSTED:
71 return "Trusted " + getCode();
72 case COMPROMISED:
73 return "Compromised " + getCode();
74 case INACTIVE_TRUSTED:
75 return "Inactive (Trusted)" + getCode();
76 case INACTIVE_UNDECIDED:
77 return "Inactive (Undecided)" + getCode();
78 case INACTIVE_UNTRUSTED:
79 return "Inactive (Untrusted)" + getCode();
80 case TRUSTED_X509:
81 return "Trusted (X509) " + getCode();
82 case INACTIVE_TRUSTED_X509:
83 return "Inactive (Trusted (X509)) " + getCode();
84 case UNTRUSTED:
85 default:
86 return "Untrusted " + getCode();
87 }
88 }
89
90 public static Trust fromBoolean(Boolean trusted) {
91 return trusted ? TRUSTED : UNTRUSTED;
92 }
93
94 public static Trust fromCode(int code) {
95 return trustsByValue.get(code);
96 }
97
98 public boolean trusted() {
99 return this == TRUSTED_X509 || this == TRUSTED;
100 }
101
102 public boolean trustedInactive() {
103 return this == INACTIVE_TRUSTED_X509 || this == INACTIVE_TRUSTED;
104 }
105 }
106
107 public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress, IdentityKey identityKey) {
108 this(account, store, remoteAddress);
109 this.identityKey = identityKey;
110 }
111
112 public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress) {
113 this.cipher = new SessionCipher(store, remoteAddress);
114 this.remoteAddress = remoteAddress;
115 this.sqLiteAxolotlStore = store;
116 this.account = account;
117 }
118
119 public Integer getPreKeyId() {
120 return preKeyId;
121 }
122
123 public void resetPreKeyId() {
124
125 preKeyId = null;
126 }
127
128 public String getFingerprint() {
129 return identityKey == null ? null : identityKey.getFingerprint().replaceAll("\\s", "");
130 }
131
132 public IdentityKey getIdentityKey() {
133 return identityKey;
134 }
135
136 public AxolotlAddress getRemoteAddress() {
137 return remoteAddress;
138 }
139
140 public boolean isFresh() {
141 return fresh;
142 }
143
144 public void setNotFresh() {
145 this.fresh = false;
146 }
147
148 protected void setTrust(Trust trust) {
149 sqLiteAxolotlStore.setFingerprintTrust(getFingerprint(), trust);
150 }
151
152 protected Trust getTrust() {
153 Trust trust = sqLiteAxolotlStore.getFingerprintTrust(getFingerprint());
154 return (trust == null) ? Trust.UNDECIDED : trust;
155 }
156
157 @Nullable
158 public byte[] processReceiving(byte[] encryptedKey) {
159 byte[] plaintext = null;
160 Trust trust = getTrust();
161 switch (trust) {
162 case INACTIVE_TRUSTED:
163 case UNDECIDED:
164 case UNTRUSTED:
165 case TRUSTED:
166 case INACTIVE_TRUSTED_X509:
167 case TRUSTED_X509:
168 try {
169 try {
170 PreKeyWhisperMessage message = new PreKeyWhisperMessage(encryptedKey);
171 if (!message.getPreKeyId().isPresent()) {
172 Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "PreKeyWhisperMessage did not contain a PreKeyId");
173 break;
174 }
175 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "PreKeyWhisperMessage received, new session ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId());
176 IdentityKey msgIdentityKey = message.getIdentityKey();
177 if (this.identityKey != null && !this.identityKey.equals(msgIdentityKey)) {
178 Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Had session with fingerprint " + this.getFingerprint() + ", received message with fingerprint " + msgIdentityKey.getFingerprint());
179 } else {
180 this.identityKey = msgIdentityKey;
181 plaintext = cipher.decrypt(message);
182 preKeyId = message.getPreKeyId().get();
183 }
184 } catch (InvalidMessageException | InvalidVersionException e) {
185 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "WhisperMessage received");
186 WhisperMessage message = new WhisperMessage(encryptedKey);
187 plaintext = cipher.decrypt(message);
188 } catch (InvalidKeyException | InvalidKeyIdException | UntrustedIdentityException e) {
189 Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage());
190 }
191 } catch (LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException e) {
192 Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage());
193 }
194
195 if (plaintext != null) {
196 if (trust == Trust.INACTIVE_TRUSTED) {
197 setTrust(Trust.TRUSTED);
198 } else if (trust == Trust.INACTIVE_TRUSTED_X509) {
199 setTrust(Trust.TRUSTED_X509);
200 }
201 }
202
203 break;
204
205 case COMPROMISED:
206 default:
207 // ignore
208 break;
209 }
210 return plaintext;
211 }
212
213 @Nullable
214 public byte[] processSending(@NonNull byte[] outgoingMessage) {
215 Trust trust = getTrust();
216 if (trust.trusted()) {
217 CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage);
218 return ciphertextMessage.serialize();
219 } else {
220 return null;
221 }
222 }
223}