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 getPreKeyIdAndReset() {
47 final Integer preKeyId = this.preKeyId;
48 this.preKeyId = null;
49 return preKeyId;
50 }
51
52 public String getFingerprint() {
53 return identityKey == null ? null : CryptoHelper.bytesToHex(identityKey.getPublicKey().serialize());
54 }
55
56 public IdentityKey getIdentityKey() {
57 return identityKey;
58 }
59
60 public SignalProtocolAddress getRemoteAddress() {
61 return remoteAddress;
62 }
63
64 public boolean isFresh() {
65 return fresh;
66 }
67
68 public void setNotFresh() {
69 this.fresh = false;
70 }
71
72 protected void setTrust(FingerprintStatus status) {
73 sqLiteAxolotlStore.setFingerprintStatus(getFingerprint(), status);
74 }
75
76 public FingerprintStatus getTrust() {
77 FingerprintStatus status = sqLiteAxolotlStore.getFingerprintStatus(getFingerprint());
78 return (status == null) ? FingerprintStatus.createActiveUndecided() : status;
79 }
80
81 @Nullable
82 byte[] processReceiving(AxolotlKey encryptedKey) throws CryptoFailedException {
83 byte[] plaintext;
84 FingerprintStatus status = getTrust();
85 if (!status.isCompromised()) {
86 try {
87 if (encryptedKey.prekey) {
88 PreKeySignalMessage preKeySignalMessage = new PreKeySignalMessage(encryptedKey.key);
89 Optional<Integer> optionalPreKeyId = preKeySignalMessage.getPreKeyId();
90 IdentityKey identityKey = preKeySignalMessage.getIdentityKey();
91 if (!optionalPreKeyId.isPresent()) {
92 throw new CryptoFailedException("PreKeyWhisperMessage did not contain a PreKeyId");
93 }
94 preKeyId = optionalPreKeyId.get();
95 if (this.identityKey != null && !this.identityKey.equals(identityKey)) {
96 throw new CryptoFailedException("Received PreKeyWhisperMessage but preexisting identity key changed.");
97 }
98 this.identityKey = identityKey;
99 plaintext = cipher.decrypt(preKeySignalMessage);
100 } else {
101 SignalMessage signalMessage = new SignalMessage(encryptedKey.key);
102 try {
103 plaintext = cipher.decrypt(signalMessage);
104 } catch (InvalidMessageException | NoSessionException e) {
105 throw new BrokenSessionException(this.remoteAddress, e);
106 }
107 preKeyId = null; //better safe than sorry because we use that to do special after prekey handling
108 }
109 } catch (InvalidVersionException | InvalidKeyException | LegacyMessageException | InvalidMessageException | DuplicateMessageException | InvalidKeyIdException | UntrustedIdentityException e) {
110 throw new CryptoFailedException("Error decrypting SignalMessage", e);
111 }
112 if (!status.isActive()) {
113 setTrust(status.toActive());
114 //TODO: also (re)add to device list?
115 }
116 } else {
117 throw new CryptoFailedException("not encrypting omemo message from fingerprint "+getFingerprint()+" because it was marked as compromised");
118 }
119 return plaintext;
120 }
121
122 @Nullable
123 public AxolotlKey processSending(@NonNull byte[] outgoingMessage, boolean ignoreSessionTrust) {
124 FingerprintStatus status = getTrust();
125 if (ignoreSessionTrust || status.isTrustedAndActive()) {
126 try {
127 CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage);
128 return new AxolotlKey(ciphertextMessage.serialize(),ciphertextMessage.getType() == CiphertextMessage.PREKEY_TYPE);
129 } catch (UntrustedIdentityException e) {
130 return null;
131 }
132 } else {
133 return null;
134 }
135 }
136
137 public Account getAccount() {
138 return account;
139 }
140
141 @Override
142 public int compareTo(XmppAxolotlSession o) {
143 return getTrust().compareTo(o.getTrust());
144 }
145
146 public static class AxolotlKey {
147
148
149 public final byte[] key;
150 public final boolean prekey;
151
152 public AxolotlKey(byte[] key, boolean prekey) {
153 this.key = key;
154 this.prekey = prekey;
155 }
156 }
157}