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