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 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "PreKeyWhisperMessage received, new session ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId());
172 IdentityKey msgIdentityKey = message.getIdentityKey();
173 if (this.identityKey != null && !this.identityKey.equals(msgIdentityKey)) {
174 Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Had session with fingerprint " + this.getFingerprint() + ", received message with fingerprint " + msgIdentityKey.getFingerprint());
175 } else {
176 this.identityKey = msgIdentityKey;
177 plaintext = cipher.decrypt(message);
178 if (message.getPreKeyId().isPresent()) {
179 preKeyId = message.getPreKeyId().get();
180 }
181 }
182 } catch (InvalidMessageException | InvalidVersionException e) {
183 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "WhisperMessage received");
184 WhisperMessage message = new WhisperMessage(encryptedKey);
185 plaintext = cipher.decrypt(message);
186 } catch (InvalidKeyException | InvalidKeyIdException | UntrustedIdentityException e) {
187 Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage());
188 }
189 } catch (LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException e) {
190 Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage());
191 }
192
193 if (plaintext != null) {
194 if (trust == Trust.INACTIVE_TRUSTED) {
195 setTrust(Trust.TRUSTED);
196 } else if (trust == Trust.INACTIVE_TRUSTED_X509) {
197 setTrust(Trust.TRUSTED_X509);
198 }
199 }
200
201 break;
202
203 case COMPROMISED:
204 default:
205 // ignore
206 break;
207 }
208 return plaintext;
209 }
210
211 @Nullable
212 public byte[] processSending(@NonNull byte[] outgoingMessage) {
213 Trust trust = getTrust();
214 if (trust.trusted()) {
215 CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage);
216 return ciphertextMessage.serialize();
217 } else {
218 return null;
219 }
220 }
221}