aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/eu/siacs/conversations/crypto/axolotl
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/eu/siacs/conversations/crypto/axolotl')
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java170
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/CryptoFailedException.java7
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java28
3 files changed, 150 insertions, 55 deletions
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java
index 72d1d14a0..c6f74538a 100644
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java
@@ -4,6 +4,7 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.whispersystems.libaxolotl.AxolotlAddress;
import org.whispersystems.libaxolotl.DuplicateMessageException;
import org.whispersystems.libaxolotl.IdentityKey;
@@ -30,6 +31,7 @@ import org.whispersystems.libaxolotl.state.SessionRecord;
import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
import org.whispersystems.libaxolotl.util.KeyHelper;
+import java.security.Security;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
@@ -51,7 +53,6 @@ import eu.siacs.conversations.xmpp.OnIqPacketReceived;
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
import eu.siacs.conversations.xmpp.jid.Jid;
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
-import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
public class AxolotlService {
@@ -68,7 +69,7 @@ public class AxolotlService {
private final SQLiteAxolotlStore axolotlStore;
private final SessionMap sessions;
private final Map<Jid, Set<Integer>> deviceIds;
- private final Map<String, MessagePacket> messageCache;
+ private final Map<String, XmppAxolotlMessage> messageCache;
private final FetchStatusMap fetchStatusMap;
private final SerialSingleThreadExecutor executor;
@@ -98,26 +99,53 @@ public class AxolotlService {
private int currentPreKeyId = 0;
public enum Trust {
- UNDECIDED, // 0
- TRUSTED,
- UNTRUSTED,
- COMPROMISED;
+ UNDECIDED(0),
+ TRUSTED(1),
+ UNTRUSTED(2),
+ COMPROMISED(3),
+ INACTIVE(4);
+
+ 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";
+ return "Trust undecided "+getCode();
case TRUSTED:
- return "Trusted";
+ return "Trusted "+getCode();
+ case COMPROMISED:
+ return "Compromised "+getCode();
+ case INACTIVE:
+ return "Inactive "+getCode();
case UNTRUSTED:
default:
- return "Untrusted";
+ return "Untrusted "+getCode();
}
}
public static Trust fromBoolean(Boolean trusted) {
return trusted?TRUSTED:UNTRUSTED;
}
+
+ public static Trust fromCode(int code) {
+ return trustsByValue.get(code);
+ }
};
private static IdentityKeyPair generateIdentityKeyPair() {
@@ -515,15 +543,22 @@ public class AxolotlService {
return fingerprint;
}
- private SQLiteAxolotlStore.Trust getTrust() {
+ protected void setTrust(SQLiteAxolotlStore.Trust trust) {
+ sqLiteAxolotlStore.setFingerprintTrust(fingerprint, trust);
+ }
+
+ protected SQLiteAxolotlStore.Trust getTrust() {
return sqLiteAxolotlStore.getFingerprintTrust(fingerprint);
}
@Nullable
public byte[] processReceiving(XmppAxolotlMessage.XmppAxolotlMessageHeader incomingHeader) {
byte[] plaintext = null;
- switch (getTrust()) {
+ SQLiteAxolotlStore.Trust trust = getTrust();
+ switch (trust) {
+ case INACTIVE:
case UNDECIDED:
+ case UNTRUSTED:
case TRUSTED:
try {
try {
@@ -550,10 +585,13 @@ public class AxolotlService {
Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Error decrypting axolotl header, "+e.getClass().getName()+": " + e.getMessage());
}
+ if (plaintext != null && trust == SQLiteAxolotlStore.Trust.INACTIVE) {
+ setTrust(SQLiteAxolotlStore.Trust.TRUSTED);
+ }
+
break;
case COMPROMISED:
- case UNTRUSTED:
default:
// ignore
break;
@@ -639,21 +677,28 @@ public class AxolotlService {
this.fillMap(store);
}
+ private void putDevicesForJid(String bareJid, List<Integer> deviceIds, SQLiteAxolotlStore store) {
+ for (Integer deviceId : deviceIds) {
+ AxolotlAddress axolotlAddress = new AxolotlAddress(bareJid, deviceId);
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building session for remote address: "+axolotlAddress.toString());
+ String fingerprint = store.loadSession(axolotlAddress).getSessionState().getRemoteIdentityKey().getFingerprint().replaceAll("\\s", "");
+ this.put(axolotlAddress, new XmppAxolotlSession(account, store, axolotlAddress, fingerprint));
+ }
+ }
+
private void fillMap(SQLiteAxolotlStore store) {
+ List<Integer> deviceIds = store.getSubDeviceSessions(account.getJid().toBareJid().toString());
+ putDevicesForJid(account.getJid().toBareJid().toString(), deviceIds, store);
for (Contact contact : account.getRoster().getContacts()) {
Jid bareJid = contact.getJid().toBareJid();
if (bareJid == null) {
continue; // FIXME: handle this?
}
String address = bareJid.toString();
- List<Integer> deviceIDs = store.getSubDeviceSessions(address);
- for (Integer deviceId : deviceIDs) {
- AxolotlAddress axolotlAddress = new AxolotlAddress(address, deviceId);
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building session for remote address: "+axolotlAddress.toString());
- String fingerprint = store.loadSession(axolotlAddress).getSessionState().getRemoteIdentityKey().getFingerprint().replaceAll("\\s", "");
- this.put(axolotlAddress, new XmppAxolotlSession(account, store, axolotlAddress, fingerprint));
- }
+ deviceIds = store.getSubDeviceSessions(address);
+ putDevicesForJid(address, deviceIds, store);
}
+
}
@Override
@@ -678,6 +723,9 @@ public class AxolotlService {
}
public AxolotlService(Account account, XmppConnectionService connectionService) {
+ if (Security.getProvider("BC") == null) {
+ Security.addProvider(new BouncyCastleProvider());
+ }
this.mXmppConnectionService = connectionService;
this.account = account;
this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService);
@@ -741,15 +789,40 @@ public class AxolotlService {
return this.deviceIds.get(account.getJid().toBareJid());
}
- public void registerDevices(final Jid jid, @NonNull final Set<Integer> deviceIds) {
- if(deviceIds.contains(getOwnDeviceId())) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Skipping own Device ID:"+ jid + ":"+getOwnDeviceId());
- deviceIds.remove(getOwnDeviceId());
+ private void setTrustOnSessions(final Jid jid, @NonNull final Set<Integer> deviceIds,
+ final SQLiteAxolotlStore.Trust from,
+ final SQLiteAxolotlStore.Trust to) {
+ for(Integer deviceId:deviceIds) {
+ AxolotlAddress address = new AxolotlAddress(jid.toBareJid().toString(), deviceId);
+ XmppAxolotlSession session = sessions.get(address);
+ if (session != null && session.getFingerprint() != null
+ && session.getTrust() == from) {
+ session.setTrust(to);
+ }
}
- for(Integer i:deviceIds) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Adding Device ID:"+ jid + ":"+i);
+ }
+
+ public void registerDevices(final Jid jid, @NonNull final Set<Integer> deviceIds) {
+ if(jid.toBareJid().equals(account.getJid().toBareJid())) {
+ if (deviceIds.contains(getOwnDeviceId())) {
+ deviceIds.remove(getOwnDeviceId());
+ }
+ for(Integer deviceId : deviceIds) {
+ AxolotlAddress ownDeviceAddress = new AxolotlAddress(jid.toBareJid().toString(),deviceId);
+ if(sessions.get(ownDeviceAddress) == null) {
+ buildSessionFromPEP(null, ownDeviceAddress, false);
+ }
+ }
}
+ Set<Integer> expiredDevices = new HashSet<>(axolotlStore.getSubDeviceSessions(jid.toBareJid().toString()));
+ expiredDevices.removeAll(deviceIds);
+ setTrustOnSessions(jid, expiredDevices, SQLiteAxolotlStore.Trust.TRUSTED,
+ SQLiteAxolotlStore.Trust.INACTIVE);
+ Set<Integer> newDevices = new HashSet<>(deviceIds);
+ setTrustOnSessions(jid, newDevices, SQLiteAxolotlStore.Trust.INACTIVE,
+ SQLiteAxolotlStore.Trust.TRUSTED);
this.deviceIds.put(jid, deviceIds);
+ mXmppConnectionService.keyStatusUpdated();
publishOwnDeviceIdIfNeeded();
}
@@ -911,11 +984,10 @@ public class AxolotlService {
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Retrieving bundle: " + bundlesPacket);
mXmppConnectionService.sendIqPacket(account, bundlesPacket, new OnIqPacketReceived() {
private void finish() {
- AxolotlAddress ownAddress = new AxolotlAddress(conversation.getAccount().getJid().toBareJid().toString(),0);
- AxolotlAddress foreignAddress = new AxolotlAddress(conversation.getJid().toBareJid().toString(),0);
+ AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(),0);
if (!fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)
- && !fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING)) {
- if (flushWaitingQueueAfterFetch) {
+ && !fetchStatusMap.getAll(address).containsValue(FetchStatus.PENDING)) {
+ if (flushWaitingQueueAfterFetch && conversation != null) {
conversation.findUnsentMessagesWithEncryption(Message.ENCRYPTION_AXOLOTL,
new Conversation.OnMessageFound() {
@Override
@@ -924,7 +996,7 @@ public class AxolotlService {
}
});
}
- mXmppConnectionService.newKeysAvailable();
+ mXmppConnectionService.keyStatusUpdated();
}
}
@@ -1050,11 +1122,17 @@ public class AxolotlService {
final String content;
if (message.hasFileOnRemoteHost()) {
content = message.getFileParams().url.toString();
- } else {
- content = message.getBody();
- }
- final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(message.getContact().getJid().toBareJid(),
- getOwnDeviceId(), content);
+ } else {
+ content = message.getBody();
+ }
+ final XmppAxolotlMessage axolotlMessage;
+ try {
+ axolotlMessage = new XmppAxolotlMessage(message.getContact().getJid().toBareJid(),
+ getOwnDeviceId(), content);
+ } catch (CryptoFailedException e) {
+ Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to encrypt message: " + e.getMessage());
+ return null;
+ }
if(findSessionsforContact(message.getContact()).isEmpty()) {
return null;
@@ -1085,14 +1163,13 @@ public class AxolotlService {
executor.execute(new Runnable() {
@Override
public void run() {
- MessagePacket packet = mXmppConnectionService.getMessageGenerator()
- .generateAxolotlChat(message);
- if (packet == null) {
+ XmppAxolotlMessage axolotlMessage = encrypt(message);
+ if (axolotlMessage == null) {
mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED);
//mXmppConnectionService.updateConversationUi();
} else {
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Generated message, caching: " + message.getUuid());
- messageCache.put(message.getUuid(), packet);
+ messageCache.put(message.getUuid(), axolotlMessage);
mXmppConnectionService.resendMessage(message,delay);
}
}
@@ -1108,15 +1185,15 @@ public class AxolotlService {
}
}
- public MessagePacket fetchPacketFromCache(Message message) {
- MessagePacket packet = messageCache.get(message.getUuid());
- if (packet != null) {
+ public XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message) {
+ XmppAxolotlMessage axolotlMessage = messageCache.get(message.getUuid());
+ if (axolotlMessage != null) {
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Cache hit: " + message.getUuid());
messageCache.remove(message.getUuid());
} else {
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Cache miss: " + message.getUuid());
}
- return packet;
+ return axolotlMessage;
}
public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceiving(XmppAxolotlMessage message) {
@@ -1144,7 +1221,12 @@ public class AxolotlService {
byte[] payloadKey = session.processReceiving(header);
if (payloadKey != null) {
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Got payload key from axolotl header. Decrypting message...");
- plaintextMessage = message.decrypt(session, payloadKey, session.getFingerprint());
+ try{
+ plaintextMessage = message.decrypt(session, payloadKey, session.getFingerprint());
+ } catch (CryptoFailedException e) {
+ Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message: " + e.getMessage());
+ break;
+ }
}
Integer preKeyId = session.getPreKeyId();
if (preKeyId != null) {
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/CryptoFailedException.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/CryptoFailedException.java
new file mode 100644
index 000000000..5796ef30e
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/CryptoFailedException.java
@@ -0,0 +1,7 @@
+package eu.siacs.conversations.crypto.axolotl;
+
+public class CryptoFailedException extends Exception {
+ public CryptoFailedException(Exception e){
+ super(e);
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java
index ec068ec7e..24afeaeae 100644
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java
@@ -6,6 +6,8 @@ import android.util.Base64;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.SecureRandom;
import java.util.HashSet;
import java.util.Set;
@@ -107,26 +109,30 @@ public class XmppAxolotlMessage {
}
}
- public XmppAxolotlMessage(Jid from, int sourceDeviceId, String plaintext) {
+ public XmppAxolotlMessage(Jid from, int sourceDeviceId, String plaintext) throws CryptoFailedException{
this.from = from;
this.sourceDeviceId = sourceDeviceId;
this.headers = new HashSet<>();
this.encrypt(plaintext);
}
- private void encrypt(String plaintext) {
+ private void encrypt(String plaintext) throws CryptoFailedException {
try {
KeyGenerator generator = KeyGenerator.getInstance("AES");
generator.init(128);
SecretKey secretKey = generator.generateKey();
- Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
- cipher.init(Cipher.ENCRYPT_MODE, secretKey);
+ SecureRandom random = new SecureRandom();
+ this.iv = new byte[16];
+ random.nextBytes(iv);
+ IvParameterSpec ivSpec = new IvParameterSpec(iv);
+ Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
+ cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
this.innerKey = secretKey.getEncoded();
- this.iv = cipher.getIV();
this.ciphertext = cipher.doFinal(plaintext.getBytes());
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
- | IllegalBlockSizeException | BadPaddingException e) {
-
+ | IllegalBlockSizeException | BadPaddingException | NoSuchProviderException
+ | InvalidAlgorithmParameterException e) {
+ throw new CryptoFailedException(e);
}
}
@@ -174,11 +180,11 @@ public class XmppAxolotlMessage {
}
- public XmppAxolotlPlaintextMessage decrypt(AxolotlService.XmppAxolotlSession session, byte[] key, String fingerprint) {
+ public XmppAxolotlPlaintextMessage decrypt(AxolotlService.XmppAxolotlSession session, byte[] key, String fingerprint) throws CryptoFailedException {
XmppAxolotlPlaintextMessage plaintextMessage = null;
try {
- Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
+ Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
@@ -189,8 +195,8 @@ public class XmppAxolotlMessage {
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
| InvalidAlgorithmParameterException | IllegalBlockSizeException
- | BadPaddingException e) {
- throw new AssertionError(e);
+ | BadPaddingException | NoSuchProviderException e) {
+ throw new CryptoFailedException(e);
}
return plaintextMessage;
}