diff options
Diffstat (limited to 'src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/XmppAxolotlSession.java')
-rw-r--r-- | src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/XmppAxolotlSession.java | 221 |
1 files changed, 221 insertions, 0 deletions
diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/XmppAxolotlSession.java b/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/XmppAxolotlSession.java new file mode 100644 index 00000000..47b2133a --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/XmppAxolotlSession.java @@ -0,0 +1,221 @@ +package de.thedevstack.conversationsplus.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 de.thedevstack.conversationsplus.Config; +import de.thedevstack.conversationsplus.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<Integer, Trust> 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; + } + } +} |