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