XmppAxolotlSession.java

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