XmppAxolotlSession.java

  1package eu.siacs.conversations.crypto.axolotl;
  2
  3import android.support.annotation.NonNull;
  4import android.support.annotation.Nullable;
  5import android.util.Log;
  6
  7import org.whispersystems.libaxolotl.AxolotlAddress;
  8import org.whispersystems.libaxolotl.DuplicateMessageException;
  9import org.whispersystems.libaxolotl.IdentityKey;
 10import org.whispersystems.libaxolotl.InvalidKeyException;
 11import org.whispersystems.libaxolotl.InvalidKeyIdException;
 12import org.whispersystems.libaxolotl.InvalidMessageException;
 13import org.whispersystems.libaxolotl.InvalidVersionException;
 14import org.whispersystems.libaxolotl.LegacyMessageException;
 15import org.whispersystems.libaxolotl.NoSessionException;
 16import org.whispersystems.libaxolotl.SessionCipher;
 17import org.whispersystems.libaxolotl.UntrustedIdentityException;
 18import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
 19import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
 20import org.whispersystems.libaxolotl.protocol.WhisperMessage;
 21
 22import java.util.HashMap;
 23import java.util.Map;
 24
 25import eu.siacs.conversations.Config;
 26import eu.siacs.conversations.entities.Account;
 27
 28public class 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 enum Trust {
 38		UNDECIDED(0),
 39		TRUSTED(1),
 40		UNTRUSTED(2),
 41		COMPROMISED(3),
 42		INACTIVE_TRUSTED(4),
 43		INACTIVE_UNDECIDED(5),
 44		INACTIVE_UNTRUSTED(6),
 45		TRUSTED_X509(7),
 46		INACTIVE_TRUSTED_X509(8);
 47
 48		private static final Map<Integer, Trust> trustsByValue = new HashMap<>();
 49
 50		static {
 51			for (Trust trust : Trust.values()) {
 52				trustsByValue.put(trust.getCode(), trust);
 53			}
 54		}
 55
 56		private final int code;
 57
 58		Trust(int code) {
 59			this.code = code;
 60		}
 61
 62		public int getCode() {
 63			return this.code;
 64		}
 65
 66		public String toString() {
 67			switch (this) {
 68				case UNDECIDED:
 69					return "Trust undecided " + getCode();
 70				case TRUSTED:
 71					return "Trusted " + getCode();
 72				case COMPROMISED:
 73					return "Compromised " + getCode();
 74				case INACTIVE_TRUSTED:
 75					return "Inactive (Trusted)" + getCode();
 76				case INACTIVE_UNDECIDED:
 77					return "Inactive (Undecided)" + getCode();
 78				case INACTIVE_UNTRUSTED:
 79					return "Inactive (Untrusted)" + getCode();
 80				case TRUSTED_X509:
 81					return "Trusted (X509) " + getCode();
 82				case INACTIVE_TRUSTED_X509:
 83					return "Inactive (Trusted (X509)) " + getCode();
 84				case UNTRUSTED:
 85				default:
 86					return "Untrusted " + getCode();
 87			}
 88		}
 89
 90		public static Trust fromBoolean(Boolean trusted) {
 91			return trusted ? TRUSTED : UNTRUSTED;
 92		}
 93
 94		public static Trust fromCode(int code) {
 95			return trustsByValue.get(code);
 96		}
 97
 98		public boolean trusted() {
 99			return this == TRUSTED_X509 || this == TRUSTED;
100		}
101
102		public boolean trustedInactive() {
103			return this == INACTIVE_TRUSTED_X509 || this == INACTIVE_TRUSTED;
104		}
105	}
106
107	public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress, IdentityKey identityKey) {
108		this(account, store, remoteAddress);
109		this.identityKey = identityKey;
110	}
111
112	public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress) {
113		this.cipher = new SessionCipher(store, remoteAddress);
114		this.remoteAddress = remoteAddress;
115		this.sqLiteAxolotlStore = store;
116		this.account = account;
117	}
118
119	public Integer getPreKeyId() {
120		return preKeyId;
121	}
122
123	public void resetPreKeyId() {
124
125		preKeyId = null;
126	}
127
128	public String getFingerprint() {
129		return identityKey == null ? null : identityKey.getFingerprint().replaceAll("\\s", "");
130	}
131
132	public IdentityKey getIdentityKey() {
133		return identityKey;
134	}
135
136	public AxolotlAddress getRemoteAddress() {
137		return remoteAddress;
138	}
139
140	public boolean isFresh() {
141		return fresh;
142	}
143
144	public void setNotFresh() {
145		this.fresh = false;
146	}
147
148	protected void setTrust(Trust trust) {
149		sqLiteAxolotlStore.setFingerprintTrust(getFingerprint(), trust);
150	}
151
152	protected Trust getTrust() {
153		Trust trust = sqLiteAxolotlStore.getFingerprintTrust(getFingerprint());
154		return (trust == null) ? Trust.UNDECIDED : trust;
155	}
156
157	@Nullable
158	public byte[] processReceiving(byte[] encryptedKey) {
159		byte[] plaintext = null;
160		Trust trust = getTrust();
161		switch (trust) {
162			case INACTIVE_TRUSTED:
163			case UNDECIDED:
164			case UNTRUSTED:
165			case TRUSTED:
166			case INACTIVE_TRUSTED_X509:
167			case TRUSTED_X509:
168				try {
169					try {
170						PreKeyWhisperMessage message = new PreKeyWhisperMessage(encryptedKey);
171						if (!message.getPreKeyId().isPresent()) {
172							Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "PreKeyWhisperMessage did not contain a PreKeyId");
173							break;
174						}
175						Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "PreKeyWhisperMessage received, new session ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId());
176						IdentityKey msgIdentityKey = message.getIdentityKey();
177						if (this.identityKey != null && !this.identityKey.equals(msgIdentityKey)) {
178							Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Had session with fingerprint " + this.getFingerprint() + ", received message with fingerprint " + msgIdentityKey.getFingerprint());
179						} else {
180							this.identityKey = msgIdentityKey;
181							plaintext = cipher.decrypt(message);
182							preKeyId = message.getPreKeyId().get();
183						}
184					} catch (InvalidMessageException | InvalidVersionException e) {
185						Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "WhisperMessage received");
186						WhisperMessage message = new WhisperMessage(encryptedKey);
187						plaintext = cipher.decrypt(message);
188					} catch (InvalidKeyException | InvalidKeyIdException | UntrustedIdentityException e) {
189						Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage());
190					}
191				} catch (LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException e) {
192					Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage());
193				}
194
195				if (plaintext != null) {
196					if (trust == Trust.INACTIVE_TRUSTED) {
197						setTrust(Trust.TRUSTED);
198					} else if (trust == Trust.INACTIVE_TRUSTED_X509) {
199						setTrust(Trust.TRUSTED_X509);
200					}
201				}
202
203				break;
204
205			case COMPROMISED:
206			default:
207				// ignore
208				break;
209		}
210		return plaintext;
211	}
212
213	@Nullable
214	public byte[] processSending(@NonNull byte[] outgoingMessage) {
215		Trust trust = getTrust();
216		if (trust.trusted()) {
217			CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage);
218			return ciphertextMessage.serialize();
219		} else {
220			return null;
221		}
222	}
223}