1package eu.siacs.conversations.crypto.axolotl;
2
3import android.support.annotation.NonNull;
4import android.support.annotation.Nullable;
5import android.util.Log;
6
7import org.bouncycastle.math.ec.PreCompInfo;
8import org.whispersystems.libaxolotl.AxolotlAddress;
9import org.whispersystems.libaxolotl.DuplicateMessageException;
10import org.whispersystems.libaxolotl.IdentityKey;
11import org.whispersystems.libaxolotl.InvalidKeyException;
12import org.whispersystems.libaxolotl.InvalidKeyIdException;
13import org.whispersystems.libaxolotl.InvalidMessageException;
14import org.whispersystems.libaxolotl.InvalidVersionException;
15import org.whispersystems.libaxolotl.LegacyMessageException;
16import org.whispersystems.libaxolotl.NoSessionException;
17import org.whispersystems.libaxolotl.SessionCipher;
18import org.whispersystems.libaxolotl.UntrustedIdentityException;
19import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
20import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
21import org.whispersystems.libaxolotl.protocol.WhisperMessage;
22import org.whispersystems.libaxolotl.util.guava.Optional;
23
24import eu.siacs.conversations.Config;
25import eu.siacs.conversations.entities.Account;
26import eu.siacs.conversations.utils.CryptoHelper;
27
28public class XmppAxolotlSession implements Comparable<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 XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress, IdentityKey identityKey) {
38 this(account, store, remoteAddress);
39 this.identityKey = identityKey;
40 }
41
42 public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress) {
43 this.cipher = new SessionCipher(store, remoteAddress);
44 this.remoteAddress = remoteAddress;
45 this.sqLiteAxolotlStore = store;
46 this.account = account;
47 }
48
49 public Integer getPreKeyId() {
50 return preKeyId;
51 }
52
53 public void resetPreKeyId() {
54
55 preKeyId = null;
56 }
57
58 public String getFingerprint() {
59 return identityKey == null ? null : identityKey.getFingerprint().replaceAll("\\s", "");
60 }
61
62 public IdentityKey getIdentityKey() {
63 return identityKey;
64 }
65
66 public AxolotlAddress 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 public byte[] processReceiving(AxolotlKey encryptedKey) throws CryptoFailedException {
89 byte[] plaintext;
90 FingerprintStatus status = getTrust();
91 if (!status.isCompromised()) {
92 try {
93 CiphertextMessage ciphertextMessage;
94 try {
95 ciphertextMessage = new PreKeyWhisperMessage(encryptedKey.key);
96 Optional<Integer> optionalPreKeyId = ((PreKeyWhisperMessage) ciphertextMessage).getPreKeyId();
97 IdentityKey identityKey = ((PreKeyWhisperMessage) ciphertextMessage).getIdentityKey();
98 if (!optionalPreKeyId.isPresent()) {
99 throw new CryptoFailedException("PreKeyWhisperMessage did not contain a PreKeyId");
100 }
101 preKeyId = optionalPreKeyId.get();
102 if (this.identityKey != null && !this.identityKey.equals(identityKey)) {
103 throw new CryptoFailedException("Received PreKeyWhisperMessage but preexisting identity key changed.");
104 }
105 this.identityKey = identityKey;
106 } catch (InvalidVersionException | InvalidMessageException e) {
107 ciphertextMessage = new WhisperMessage(encryptedKey.key);
108 }
109 if (ciphertextMessage instanceof PreKeyWhisperMessage) {
110 plaintext = cipher.decrypt((PreKeyWhisperMessage) ciphertextMessage);
111 } else {
112 plaintext = cipher.decrypt((WhisperMessage) ciphertextMessage);
113 }
114 } catch (InvalidKeyException | LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException | InvalidKeyIdException | UntrustedIdentityException e) {
115 if (!(e instanceof DuplicateMessageException)) {
116 e.printStackTrace();
117 }
118 throw new CryptoFailedException("Error decrypting WhisperMessage " + e.getClass().getSimpleName() + ": " + e.getMessage());
119 }
120 if (!status.isActive()) {
121 setTrust(status.toActive());
122 }
123 } else {
124 throw new CryptoFailedException("not encrypting omemo message from fingerprint "+getFingerprint()+" because it was marked as compromised");
125 }
126 return plaintext;
127 }
128
129 @Nullable
130 public AxolotlKey processSending(@NonNull byte[] outgoingMessage) {
131 FingerprintStatus status = getTrust();
132 if (status.isTrustedAndActive()) {
133 CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage);
134 return new AxolotlKey(ciphertextMessage.serialize(),ciphertextMessage.getType() == CiphertextMessage.PREKEY_TYPE);
135 } else {
136 return null;
137 }
138 }
139
140 public Account getAccount() {
141 return account;
142 }
143
144 @Override
145 public int compareTo(XmppAxolotlSession o) {
146 return getTrust().compareTo(o.getTrust());
147 }
148
149 public static class AxolotlKey {
150
151
152 public final byte[] key;
153 public final boolean prekey;
154
155 public AxolotlKey(byte[] key, boolean prekey) {
156 this.key = key;
157 this.prekey = prekey;
158 }
159 }
160}