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