XmppAxolotlSession.java

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