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
45 private static final Map<Integer, Trust> trustsByValue = new HashMap<>();
46
47 static {
48 for (Trust trust : Trust.values()) {
49 trustsByValue.put(trust.getCode(), trust);
50 }
51 }
52
53 private final int code;
54
55 Trust(int code) {
56 this.code = code;
57 }
58
59 public int getCode() {
60 return this.code;
61 }
62
63 public String toString() {
64 switch (this) {
65 case UNDECIDED:
66 return "Trust undecided " + getCode();
67 case TRUSTED:
68 return "Trusted " + getCode();
69 case COMPROMISED:
70 return "Compromised " + getCode();
71 case INACTIVE_TRUSTED:
72 return "Inactive (Trusted)" + getCode();
73 case INACTIVE_UNDECIDED:
74 return "Inactive (Undecided)" + getCode();
75 case INACTIVE_UNTRUSTED:
76 return "Inactive (Untrusted)" + getCode();
77 case UNTRUSTED:
78 default:
79 return "Untrusted " + getCode();
80 }
81 }
82
83 public static Trust fromBoolean(Boolean trusted) {
84 return trusted ? TRUSTED : UNTRUSTED;
85 }
86
87 public static Trust fromCode(int code) {
88 return trustsByValue.get(code);
89 }
90 }
91
92 public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress, String fingerprint) {
93 this(account, store, remoteAddress);
94 this.fingerprint = fingerprint.replaceAll("\\s","");
95 }
96
97 public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress) {
98 this.cipher = new SessionCipher(store, remoteAddress);
99 this.remoteAddress = remoteAddress;
100 this.sqLiteAxolotlStore = store;
101 this.account = account;
102 }
103
104 public Integer getPreKeyId() {
105 return preKeyId;
106 }
107
108 public void resetPreKeyId() {
109
110 preKeyId = null;
111 }
112
113 public String getFingerprint() {
114 return fingerprint;
115 }
116
117 public AxolotlAddress getRemoteAddress() {
118 return remoteAddress;
119 }
120
121 public boolean isFresh() {
122 return fresh;
123 }
124
125 public void setNotFresh() {
126 this.fresh = false;
127 }
128
129 protected void setTrust(Trust trust) {
130 sqLiteAxolotlStore.setFingerprintTrust(fingerprint, trust);
131 }
132
133 protected Trust getTrust() {
134 Trust trust = sqLiteAxolotlStore.getFingerprintTrust(fingerprint);
135 return (trust == null) ? Trust.UNDECIDED : trust;
136 }
137
138 @Nullable
139 public byte[] processReceiving(byte[] encryptedKey) {
140 byte[] plaintext = null;
141 Trust trust = getTrust();
142 switch (trust) {
143 case INACTIVE_TRUSTED:
144 case UNDECIDED:
145 case UNTRUSTED:
146 case TRUSTED:
147 try {
148 try {
149 PreKeyWhisperMessage message = new PreKeyWhisperMessage(encryptedKey);
150 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "PreKeyWhisperMessage received, new session ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId());
151 String fingerprint = message.getIdentityKey().getFingerprint().replaceAll("\\s", "");
152 if (this.fingerprint != null && !this.fingerprint.equals(fingerprint)) {
153 Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Had session with fingerprint " + this.fingerprint + ", received message with fingerprint " + fingerprint);
154 } else {
155 this.fingerprint = fingerprint;
156 plaintext = cipher.decrypt(message);
157 if (message.getPreKeyId().isPresent()) {
158 preKeyId = message.getPreKeyId().get();
159 }
160 }
161 } catch (InvalidMessageException | InvalidVersionException e) {
162 Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "WhisperMessage received");
163 WhisperMessage message = new WhisperMessage(encryptedKey);
164 plaintext = cipher.decrypt(message);
165 } catch (InvalidKeyException | InvalidKeyIdException | UntrustedIdentityException e) {
166 Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage());
167 }
168 } catch (LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException e) {
169 Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage());
170 }
171
172 if (plaintext != null && trust == Trust.INACTIVE_TRUSTED) {
173 setTrust(Trust.TRUSTED);
174 }
175
176 break;
177
178 case COMPROMISED:
179 default:
180 // ignore
181 break;
182 }
183 return plaintext;
184 }
185
186 @Nullable
187 public byte[] processSending(@NonNull byte[] outgoingMessage) {
188 Trust trust = getTrust();
189 if (trust == Trust.TRUSTED) {
190 CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage);
191 return ciphertextMessage.serialize();
192 } else {
193 return null;
194 }
195 }
196}