1package eu.siacs.conversations.crypto.axolotl;
2
3import android.util.Log;
4
5import androidx.annotation.NonNull;
6import androidx.annotation.Nullable;
7
8import org.whispersystems.libsignal.DuplicateMessageException;
9import org.whispersystems.libsignal.IdentityKey;
10import org.whispersystems.libsignal.InvalidKeyException;
11import org.whispersystems.libsignal.InvalidKeyIdException;
12import org.whispersystems.libsignal.InvalidMessageException;
13import org.whispersystems.libsignal.InvalidVersionException;
14import org.whispersystems.libsignal.LegacyMessageException;
15import org.whispersystems.libsignal.NoSessionException;
16import org.whispersystems.libsignal.SessionCipher;
17import org.whispersystems.libsignal.SignalProtocolAddress;
18import org.whispersystems.libsignal.UntrustedIdentityException;
19import org.whispersystems.libsignal.protocol.CiphertextMessage;
20import org.whispersystems.libsignal.protocol.PreKeySignalMessage;
21import org.whispersystems.libsignal.protocol.SignalMessage;
22import org.whispersystems.libsignal.util.guava.Optional;
23
24import java.util.Iterator;
25import java.util.List;
26
27import eu.siacs.conversations.Config;
28import eu.siacs.conversations.entities.Account;
29import eu.siacs.conversations.utils.CryptoHelper;
30
31public class XmppAxolotlSession implements Comparable<XmppAxolotlSession> {
32 private final SessionCipher cipher;
33 private final SQLiteAxolotlStore sqLiteAxolotlStore;
34 private final SignalProtocolAddress remoteAddress;
35 private final Account account;
36 private IdentityKey identityKey;
37 private Integer preKeyId = null;
38 private boolean fresh = true;
39
40 public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, SignalProtocolAddress remoteAddress, IdentityKey identityKey) {
41 this(account, store, remoteAddress);
42 this.identityKey = identityKey;
43 }
44
45 public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, SignalProtocolAddress remoteAddress) {
46 this.cipher = new SessionCipher(store, remoteAddress);
47 this.remoteAddress = remoteAddress;
48 this.sqLiteAxolotlStore = store;
49 this.account = account;
50 }
51
52 public Integer getPreKeyIdAndReset() {
53 final Integer preKeyId = this.preKeyId;
54 this.preKeyId = null;
55 return preKeyId;
56 }
57
58 public String getFingerprint() {
59 return identityKey == null ? null : CryptoHelper.bytesToHex(identityKey.getPublicKey().serialize());
60 }
61
62 public IdentityKey getIdentityKey() {
63 return identityKey;
64 }
65
66 public SignalProtocolAddress getRemoteAddress() {
67 return remoteAddress;
68 }
69
70 public boolean isFresh() {
71 return fresh;
72 }
73
74 public void setNotFresh() {
75 this.fresh = false;
76 }
77
78 protected void setTrust(FingerprintStatus status) {
79 sqLiteAxolotlStore.setFingerprintStatus(getFingerprint(), status);
80 }
81
82 public FingerprintStatus getTrust() {
83 FingerprintStatus status = sqLiteAxolotlStore.getFingerprintStatus(getFingerprint());
84 return (status == null) ? FingerprintStatus.createActiveUndecided() : status;
85 }
86
87 @Nullable
88 byte[] processReceiving(List<AxolotlKey> possibleKeys) throws CryptoFailedException {
89 byte[] plaintext = null;
90 FingerprintStatus status = getTrust();
91 if (!status.isCompromised()) {
92 Iterator<AxolotlKey> iterator = possibleKeys.iterator();
93 while (iterator.hasNext()) {
94 AxolotlKey encryptedKey = iterator.next();
95 try {
96 if (encryptedKey.prekey) {
97 PreKeySignalMessage preKeySignalMessage = new PreKeySignalMessage(encryptedKey.key);
98 Optional<Integer> optionalPreKeyId = preKeySignalMessage.getPreKeyId();
99 IdentityKey identityKey = preKeySignalMessage.getIdentityKey();
100 if (!optionalPreKeyId.isPresent()) {
101 if (iterator.hasNext()) {
102 continue;
103 }
104 throw new CryptoFailedException("PreKeyWhisperMessage did not contain a PreKeyId");
105 }
106 preKeyId = optionalPreKeyId.get();
107 if (this.identityKey != null && !this.identityKey.equals(identityKey)) {
108 if (iterator.hasNext()) {
109 continue;
110 }
111 throw new CryptoFailedException("Received PreKeyWhisperMessage but preexisting identity key changed.");
112 }
113 this.identityKey = identityKey;
114 plaintext = cipher.decrypt(preKeySignalMessage);
115 } else {
116 SignalMessage signalMessage = new SignalMessage(encryptedKey.key);
117 try {
118 plaintext = cipher.decrypt(signalMessage);
119 } catch (InvalidMessageException | NoSessionException e) {
120 if (iterator.hasNext()) {
121 Log.w(Config.LOGTAG,account.getJid().asBareJid()+": ignoring crypto exception because possible keys left to try",e);
122 continue;
123 }
124 throw new BrokenSessionException(this.remoteAddress, e);
125 }
126 preKeyId = null; //better safe than sorry because we use that to do special after prekey handling
127 }
128 } catch (InvalidVersionException | InvalidKeyException | LegacyMessageException | InvalidMessageException | DuplicateMessageException | InvalidKeyIdException | UntrustedIdentityException e) {
129 if (iterator.hasNext()) {
130 Log.w(Config.LOGTAG,account.getJid().asBareJid()+": ignoring crypto exception because possible keys left to try",e);
131 continue;
132 }
133 throw new CryptoFailedException("Error decrypting SignalMessage", e);
134 }
135 if (iterator.hasNext()) {
136 break;
137 }
138 }
139 if (!status.isActive()) {
140 setTrust(status.toActive());
141 //TODO: also (re)add to device list?
142 }
143 } else {
144 throw new CryptoFailedException("not encrypting omemo message from fingerprint "+getFingerprint()+" because it was marked as compromised");
145 }
146 return plaintext;
147 }
148
149 @Nullable
150 public AxolotlKey processSending(@NonNull byte[] outgoingMessage, boolean ignoreSessionTrust) {
151 FingerprintStatus status = getTrust();
152 if (ignoreSessionTrust || status.isTrustedAndActive()) {
153 try {
154 CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage);
155 return new AxolotlKey(getRemoteAddress().getDeviceId(), ciphertextMessage.serialize(),ciphertextMessage.getType() == CiphertextMessage.PREKEY_TYPE);
156 } catch (UntrustedIdentityException e) {
157 return null;
158 }
159 } else {
160 return null;
161 }
162 }
163
164 public Account getAccount() {
165 return account;
166 }
167
168 @Override
169 public int compareTo(XmppAxolotlSession o) {
170 return getTrust().compareTo(o.getTrust());
171 }
172
173 public static class AxolotlKey {
174
175
176 public final byte[] key;
177 public final boolean prekey;
178 public final int deviceId;
179
180 public AxolotlKey(int deviceId, byte[] key, boolean prekey) {
181 this.deviceId = deviceId;
182 this.key = key;
183 this.prekey = prekey;
184 }
185 }
186}