package eu.siacs.conversations.crypto.axolotl; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Log; import org.whispersystems.libaxolotl.AxolotlAddress; import org.whispersystems.libaxolotl.DuplicateMessageException; import org.whispersystems.libaxolotl.IdentityKey; import org.whispersystems.libaxolotl.InvalidKeyException; import org.whispersystems.libaxolotl.InvalidKeyIdException; import org.whispersystems.libaxolotl.InvalidMessageException; import org.whispersystems.libaxolotl.InvalidVersionException; import org.whispersystems.libaxolotl.LegacyMessageException; import org.whispersystems.libaxolotl.NoSessionException; import org.whispersystems.libaxolotl.SessionCipher; import org.whispersystems.libaxolotl.UntrustedIdentityException; import org.whispersystems.libaxolotl.protocol.CiphertextMessage; import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage; import org.whispersystems.libaxolotl.protocol.WhisperMessage; import java.util.HashMap; import java.util.Map; import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.Account; public class XmppAxolotlSession { private final SessionCipher cipher; private final SQLiteAxolotlStore sqLiteAxolotlStore; private final AxolotlAddress remoteAddress; private final Account account; private IdentityKey identityKey; private Integer preKeyId = null; private boolean fresh = true; public enum Trust { UNDECIDED(0), TRUSTED(1), UNTRUSTED(2), COMPROMISED(3), INACTIVE_TRUSTED(4), INACTIVE_UNDECIDED(5), INACTIVE_UNTRUSTED(6), TRUSTED_X509(7), INACTIVE_TRUSTED_X509(8); private static final Map trustsByValue = new HashMap<>(); static { for (Trust trust : Trust.values()) { trustsByValue.put(trust.getCode(), trust); } } private final int code; Trust(int code) { this.code = code; } public int getCode() { return this.code; } public String toString() { switch (this) { case UNDECIDED: return "Trust undecided " + getCode(); case TRUSTED: return "Trusted " + getCode(); case COMPROMISED: return "Compromised " + getCode(); case INACTIVE_TRUSTED: return "Inactive (Trusted)" + getCode(); case INACTIVE_UNDECIDED: return "Inactive (Undecided)" + getCode(); case INACTIVE_UNTRUSTED: return "Inactive (Untrusted)" + getCode(); case TRUSTED_X509: return "Trusted (X509) " + getCode(); case INACTIVE_TRUSTED_X509: return "Inactive (Trusted (X509)) " + getCode(); case UNTRUSTED: default: return "Untrusted " + getCode(); } } public static Trust fromBoolean(Boolean trusted) { return trusted ? TRUSTED : UNTRUSTED; } public static Trust fromCode(int code) { return trustsByValue.get(code); } public boolean trusted() { return this == TRUSTED_X509 || this == TRUSTED; } public boolean trustedInactive() { return this == INACTIVE_TRUSTED_X509 || this == INACTIVE_TRUSTED; } } public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress, IdentityKey identityKey) { this(account, store, remoteAddress); this.identityKey = identityKey; } public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress) { this.cipher = new SessionCipher(store, remoteAddress); this.remoteAddress = remoteAddress; this.sqLiteAxolotlStore = store; this.account = account; } public Integer getPreKeyId() { return preKeyId; } public void resetPreKeyId() { preKeyId = null; } public String getFingerprint() { return identityKey == null ? null : identityKey.getFingerprint().replaceAll("\\s", ""); } public IdentityKey getIdentityKey() { return identityKey; } public AxolotlAddress getRemoteAddress() { return remoteAddress; } public boolean isFresh() { return fresh; } public void setNotFresh() { this.fresh = false; } protected void setTrust(Trust trust) { sqLiteAxolotlStore.setFingerprintTrust(getFingerprint(), trust); } protected Trust getTrust() { Trust trust = sqLiteAxolotlStore.getFingerprintTrust(getFingerprint()); return (trust == null) ? Trust.UNDECIDED : trust; } @Nullable public byte[] processReceiving(byte[] encryptedKey) { byte[] plaintext = null; Trust trust = getTrust(); switch (trust) { case INACTIVE_TRUSTED: case UNDECIDED: case UNTRUSTED: case TRUSTED: case INACTIVE_TRUSTED_X509: case TRUSTED_X509: try { try { PreKeyWhisperMessage message = new PreKeyWhisperMessage(encryptedKey); Log.i(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "PreKeyWhisperMessage received, new session ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId()); IdentityKey msgIdentityKey = message.getIdentityKey(); if (this.identityKey != null && !this.identityKey.equals(msgIdentityKey)) { Log.e(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Had session with fingerprint " + this.getFingerprint() + ", received message with fingerprint " + msgIdentityKey.getFingerprint()); } else { this.identityKey = msgIdentityKey; plaintext = cipher.decrypt(message); if (message.getPreKeyId().isPresent()) { preKeyId = message.getPreKeyId().get(); } } } catch (InvalidMessageException | InvalidVersionException e) { Log.i(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "WhisperMessage received"); WhisperMessage message = new WhisperMessage(encryptedKey); plaintext = cipher.decrypt(message); } catch (InvalidKeyException | InvalidKeyIdException | UntrustedIdentityException e) { Log.w(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage()); } } catch (LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException e) { Log.w(Config.LOGTAG, AxolotlServiceImpl.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage()); } if (plaintext != null) { if (trust == Trust.INACTIVE_TRUSTED) { setTrust(Trust.TRUSTED); } else if (trust == Trust.INACTIVE_TRUSTED_X509) { setTrust(Trust.TRUSTED_X509); } } break; case COMPROMISED: default: // ignore break; } return plaintext; } @Nullable public byte[] processSending(@NonNull byte[] outgoingMessage) { Trust trust = getTrust(); if (trust.trusted()) { CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage); return ciphertextMessage.serialize(); } else { return null; } } }