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