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