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