24768d051d
fixes #1384 closes #2509
162 lines
5.4 KiB
Java
162 lines
5.4 KiB
Java
package eu.siacs.conversations.crypto.axolotl;
|
|
|
|
import android.support.annotation.NonNull;
|
|
import android.support.annotation.Nullable;
|
|
|
|
import org.whispersystems.libsignal.SignalProtocolAddress;
|
|
import org.whispersystems.libsignal.DuplicateMessageException;
|
|
import org.whispersystems.libsignal.IdentityKey;
|
|
import org.whispersystems.libsignal.InvalidKeyException;
|
|
import org.whispersystems.libsignal.InvalidKeyIdException;
|
|
import org.whispersystems.libsignal.InvalidMessageException;
|
|
import org.whispersystems.libsignal.InvalidVersionException;
|
|
import org.whispersystems.libsignal.LegacyMessageException;
|
|
import org.whispersystems.libsignal.NoSessionException;
|
|
import org.whispersystems.libsignal.SessionCipher;
|
|
import org.whispersystems.libsignal.UntrustedIdentityException;
|
|
import org.whispersystems.libsignal.protocol.CiphertextMessage;
|
|
import org.whispersystems.libsignal.protocol.PreKeySignalMessage;
|
|
import org.whispersystems.libsignal.protocol.SignalMessage;
|
|
import org.whispersystems.libsignal.util.guava.Optional;
|
|
|
|
import eu.siacs.conversations.entities.Account;
|
|
import eu.siacs.conversations.utils.CryptoHelper;
|
|
|
|
public class XmppAxolotlSession implements Comparable<XmppAxolotlSession> {
|
|
private final SessionCipher cipher;
|
|
private final SQLiteAxolotlStore sqLiteAxolotlStore;
|
|
private final SignalProtocolAddress remoteAddress;
|
|
private final Account account;
|
|
private IdentityKey identityKey;
|
|
private Integer preKeyId = null;
|
|
private boolean fresh = true;
|
|
|
|
public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, SignalProtocolAddress remoteAddress, IdentityKey identityKey) {
|
|
this(account, store, remoteAddress);
|
|
this.identityKey = identityKey;
|
|
}
|
|
|
|
public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, SignalProtocolAddress 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 : CryptoHelper.bytesToHex(identityKey.getPublicKey().serialize());
|
|
}
|
|
|
|
public IdentityKey getIdentityKey() {
|
|
return identityKey;
|
|
}
|
|
|
|
public SignalProtocolAddress getRemoteAddress() {
|
|
return remoteAddress;
|
|
}
|
|
|
|
public boolean isFresh() {
|
|
return fresh;
|
|
}
|
|
|
|
public void setNotFresh() {
|
|
this.fresh = false;
|
|
}
|
|
|
|
protected void setTrust(FingerprintStatus status) {
|
|
sqLiteAxolotlStore.setFingerprintStatus(getFingerprint(), status);
|
|
}
|
|
|
|
public FingerprintStatus getTrust() {
|
|
FingerprintStatus status = sqLiteAxolotlStore.getFingerprintStatus(getFingerprint());
|
|
return (status == null) ? FingerprintStatus.createActiveUndecided() : status;
|
|
}
|
|
|
|
@Nullable
|
|
public byte[] processReceiving(AxolotlKey encryptedKey) throws CryptoFailedException {
|
|
byte[] plaintext;
|
|
FingerprintStatus status = getTrust();
|
|
if (!status.isCompromised()) {
|
|
try {
|
|
CiphertextMessage ciphertextMessage;
|
|
try {
|
|
ciphertextMessage = new PreKeySignalMessage(encryptedKey.key);
|
|
Optional<Integer> optionalPreKeyId = ((PreKeySignalMessage) ciphertextMessage).getPreKeyId();
|
|
IdentityKey identityKey = ((PreKeySignalMessage) ciphertextMessage).getIdentityKey();
|
|
if (!optionalPreKeyId.isPresent()) {
|
|
throw new CryptoFailedException("PreKeyWhisperMessage did not contain a PreKeyId");
|
|
}
|
|
preKeyId = optionalPreKeyId.get();
|
|
if (this.identityKey != null && !this.identityKey.equals(identityKey)) {
|
|
throw new CryptoFailedException("Received PreKeyWhisperMessage but preexisting identity key changed.");
|
|
}
|
|
this.identityKey = identityKey;
|
|
} catch (InvalidVersionException | InvalidMessageException e) {
|
|
ciphertextMessage = new SignalMessage(encryptedKey.key);
|
|
}
|
|
if (ciphertextMessage instanceof PreKeySignalMessage) {
|
|
plaintext = cipher.decrypt((PreKeySignalMessage) ciphertextMessage);
|
|
} else {
|
|
plaintext = cipher.decrypt((SignalMessage) ciphertextMessage);
|
|
}
|
|
} catch (InvalidKeyException | LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException | InvalidKeyIdException | UntrustedIdentityException e) {
|
|
if (!(e instanceof DuplicateMessageException)) {
|
|
e.printStackTrace();
|
|
}
|
|
throw new CryptoFailedException("Error decrypting WhisperMessage " + e.getClass().getSimpleName() + ": " + e.getMessage());
|
|
}
|
|
if (!status.isActive()) {
|
|
setTrust(status.toActive());
|
|
}
|
|
} else {
|
|
throw new CryptoFailedException("not encrypting omemo message from fingerprint "+getFingerprint()+" because it was marked as compromised");
|
|
}
|
|
return plaintext;
|
|
}
|
|
|
|
@Nullable
|
|
public AxolotlKey processSending(@NonNull byte[] outgoingMessage) {
|
|
FingerprintStatus status = getTrust();
|
|
if (status.isTrustedAndActive()) {
|
|
try {
|
|
CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage);
|
|
return new AxolotlKey(ciphertextMessage.serialize(),ciphertextMessage.getType() == CiphertextMessage.PREKEY_TYPE);
|
|
} catch (UntrustedIdentityException e) {
|
|
return null;
|
|
}
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public Account getAccount() {
|
|
return account;
|
|
}
|
|
|
|
@Override
|
|
public int compareTo(XmppAxolotlSession o) {
|
|
return getTrust().compareTo(o.getTrust());
|
|
}
|
|
|
|
public static class AxolotlKey {
|
|
|
|
|
|
public final byte[] key;
|
|
public final boolean prekey;
|
|
|
|
public AxolotlKey(byte[] key, boolean prekey) {
|
|
this.key = key;
|
|
this.prekey = prekey;
|
|
}
|
|
}
|
|
}
|