From c32162c2808a9bdef841cae706a8de9bcf5391a1 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 23 Jul 2015 14:02:25 +0200 Subject: switch/case can't deal with null pointers --- src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/main') diff --git a/src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java b/src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java index 7b36fc497..398102e12 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java +++ b/src/main/java/eu/siacs/conversations/xmpp/stanzas/IqPacket.java @@ -39,6 +39,9 @@ public class IqPacket extends AbstractStanza { public TYPE getType() { final String type = getAttribute("type"); + if (type == null) { + return TYPE.INVALID; + } switch (type) { case "error": return TYPE.ERROR; -- cgit v1.2.3 From e5fae429fa45a065979103eaf95f0c8b1edc4ab4 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 24 Jul 2015 14:43:13 +0200 Subject: call refreshUi directly --- src/main/java/eu/siacs/conversations/ui/ConversationFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/main') diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 6ff14d81b..044294213 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -832,7 +832,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa } catch (final NoSuchElementException ignored) { } - activity.xmppConnectionService.updateConversationUi(); + activity.refreshUi(); } }); } -- cgit v1.2.3 From 9c94c9ad8fe10aa1ffeb122b1db114680a7d95a2 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 24 Jul 2015 19:06:47 +0200 Subject: rewrote dns fallback --- src/main/java/eu/siacs/conversations/utils/DNSHelper.java | 15 ++++++--------- .../java/eu/siacs/conversations/xmpp/XmppConnection.java | 3 +++ 2 files changed, 9 insertions(+), 9 deletions(-) (limited to 'src/main') diff --git a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java index 36d03b30a..60e1f0beb 100644 --- a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java @@ -38,17 +38,14 @@ public class DNSHelper { public static Bundle getSRVRecord(final Jid jid) throws IOException { final String host = jid.getDomainpart(); String dns[] = client.findDNS(); - - if (dns != null) { - for (String dnsserver : dns) { - InetAddress ip = InetAddress.getByName(dnsserver); - Bundle b = queryDNS(host, ip); - if (b.containsKey("values")) { - return b; - } + for (int i = 0; i < dns.length; ++i) { + InetAddress ip = InetAddress.getByName(dns[i]); + Bundle b = queryDNS(host, ip); + if (b.containsKey("values") || i == dns.length - 1) { + return b; } } - return queryDNS(host, InetAddress.getByName("8.8.8.8")); + return null; } public static Bundle queryDNS(String host, InetAddress dnsServer) { diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 7c81d988c..c41c5174e 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -164,6 +164,9 @@ public class XmppConnection implements Runnable { } } else { final Bundle result = DNSHelper.getSRVRecord(account.getServer()); + if (result == null) { + throw new IOException("unhandled exception in DNS resolver"); + } final ArrayList values = result.getParcelableArrayList("values"); if ("timeout".equals(result.getString("error"))) { throw new IOException("timeout in dns"); -- cgit v1.2.3 From ff0c114cd66b0fcae6fe8d6598fde48d9c1c5bd4 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 28 Jul 2015 12:54:54 +0200 Subject: set time on resend to current time fixes #1298 fixes #919 --- .../java/eu/siacs/conversations/services/XmppConnectionService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/main') diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index fa0a87096..cadcf8512 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -2533,8 +2533,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } for (final Message msg : messages) { + msg.setTime(System.currentTimeMillis()); markMessage(msg, Message.STATUS_WAITING); - this.resendMessage(msg,true); + this.resendMessage(msg,false); } } -- cgit v1.2.3 From 6b8e1ecb9532509ac6bca6ca3665f6ac3f560332 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 28 Jul 2015 14:44:11 +0200 Subject: log reason for message failure --- src/main/java/eu/siacs/conversations/parser/MessageParser.java | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src/main') diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 6e7b3276d..f06595110 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -202,6 +202,13 @@ public class MessageParser extends AbstractParser implements if (packet.getType() == MessagePacket.TYPE_ERROR) { Jid from = packet.getFrom(); if (from != null) { + Element error = packet.findChild("error"); + String text = error == null ? null : error.findChildContent("text"); + if (text != null) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": sending message to "+ from+ " failed - " + text); + } else if (error != null) { + Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": sending message to "+ from+ " failed - " + error); + } Message message = mXmppConnectionService.markMessage(account, from.toBareJid(), packet.getId(), -- cgit v1.2.3 From 8924c448d19d44a3a4a6f09a76d2e09130f22f87 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 28 Jul 2015 22:26:29 +0200 Subject: changed logging. (work around logcat null pointer --- src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java | 2 +- src/main/java/eu/siacs/conversations/utils/DNSHelper.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'src/main') diff --git a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java index c25bf13a2..c9fb3d8c5 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java @@ -194,7 +194,7 @@ public class HttpUploadConnection implements Transferable { fail(); } } catch (IOException e) { - Log.d(Config.LOGTAG, e.getMessage()); + Log.d(Config.LOGTAG,"http upload failed "+e.getMessage()); fail(); } finally { FileBackend.close(is); diff --git a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java index 60e1f0beb..4d0dd3dab 100644 --- a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java @@ -129,7 +129,6 @@ public class DNSHelper { } catch (SocketTimeoutException e) { bundle.putString("error", "timeout"); } catch (Exception e) { - e.printStackTrace(); bundle.putString("error", "unhandled"); } return bundle; -- cgit v1.2.3 From 8f14d2bfbd1e3e6697a5afccfc2512f98b55b702 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 28 Jul 2015 23:00:30 +0200 Subject: removed recursion in message.getMerged*() --- .../eu/siacs/conversations/entities/Message.java | 32 +++++++++++++--------- 1 file changed, 19 insertions(+), 13 deletions(-) (limited to 'src/main') diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index 698775c8a..b4bb31a84 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -416,11 +416,14 @@ public class Message extends AbstractEntity { } public String getMergedBody() { - final Message next = this.next(); - if (this.mergeable(next)) { - return getBody().trim() + MERGE_SEPARATOR + next.getMergedBody(); + StringBuilder body = new StringBuilder(this.body.trim()); + Message current = this; + while(current.mergeable(current.next())) { + current = current.next(); + body.append(MERGE_SEPARATOR); + body.append(current.getBody().trim()); } - return getBody().trim(); + return body.toString(); } public boolean hasMeCommand() { @@ -428,20 +431,23 @@ public class Message extends AbstractEntity { } public int getMergedStatus() { - final Message next = this.next(); - if (this.mergeable(next)) { - return next.getStatus(); + int status = this.status; + Message current = this; + while(current.mergeable(current.next())) { + current = current.next(); + status = current.status; } - return getStatus(); + return status; } public long getMergedTimeSent() { - Message next = this.next(); - if (this.mergeable(next)) { - return next.getMergedTimeSent(); - } else { - return getTimeSent(); + long time = this.timeSent; + Message current = this; + while(current.mergeable(current.next())) { + current = current.next(); + time = current.timeSent; } + return time; } public boolean wasMergedIntoPrevious() { -- cgit v1.2.3 From 17bc4fb6cd2f52ce7bfbc03540b88cad3ebc5f26 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 29 Jul 2015 01:57:08 +0200 Subject: show http downloaded images in notification --- .../java/eu/siacs/conversations/services/NotificationService.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'src/main') diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java index 47f0347fa..90e4d216d 100644 --- a/src/main/java/eu/siacs/conversations/services/NotificationService.java +++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java @@ -332,9 +332,10 @@ public class NotificationService { private Message getImage(final Iterable messages) { for (final Message message : messages) { - if (message.getType() == Message.TYPE_IMAGE + if (message.getType() != Message.TYPE_TEXT && message.getTransferable() == null - && message.getEncryption() != Message.ENCRYPTION_PGP) { + && message.getEncryption() != Message.ENCRYPTION_PGP + && message.getFileParams().height > 0) { return message; } } -- cgit v1.2.3 From d9bdce01043ffdf97cb4a893f8d057b3e4d5fb8f Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 29 Jul 2015 02:01:28 +0200 Subject: add new received message bubble drawable --- .../res/drawable-hdpi/message_bubble_received.9.png | Bin 765 -> 765 bytes .../message_bubble_received_warning.9.png | Bin 0 -> 757 bytes src/main/res/drawable-hdpi/message_bubble_sent.9.png | Bin 687 -> 687 bytes .../res/drawable-mdpi/message_bubble_received.9.png | Bin 594 -> 594 bytes .../message_bubble_received_warning.9.png | Bin 0 -> 598 bytes src/main/res/drawable-mdpi/message_bubble_sent.9.png | Bin 558 -> 558 bytes .../res/drawable-xhdpi/message_bubble_received.9.png | Bin 929 -> 929 bytes .../message_bubble_received_warning.9.png | Bin 0 -> 921 bytes src/main/res/drawable-xhdpi/message_bubble_sent.9.png | Bin 857 -> 857 bytes .../res/drawable-xxhdpi/message_bubble_received.9.png | Bin 1334 -> 1334 bytes .../message_bubble_received_warning.9.png | Bin 0 -> 1308 bytes .../res/drawable-xxhdpi/message_bubble_sent.9.png | Bin 1190 -> 1190 bytes .../drawable-xxxhdpi/message_bubble_received.9.png | Bin 1714 -> 1714 bytes .../message_bubble_received_warning.9.png | Bin 0 -> 1674 bytes .../res/drawable-xxxhdpi/message_bubble_sent.9.png | Bin 1499 -> 1499 bytes 15 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/main/res/drawable-hdpi/message_bubble_received_warning.9.png create mode 100644 src/main/res/drawable-mdpi/message_bubble_received_warning.9.png create mode 100644 src/main/res/drawable-xhdpi/message_bubble_received_warning.9.png create mode 100644 src/main/res/drawable-xxhdpi/message_bubble_received_warning.9.png create mode 100644 src/main/res/drawable-xxxhdpi/message_bubble_received_warning.9.png (limited to 'src/main') diff --git a/src/main/res/drawable-hdpi/message_bubble_received.9.png b/src/main/res/drawable-hdpi/message_bubble_received.9.png index 7433cd581..1a496771e 100644 Binary files a/src/main/res/drawable-hdpi/message_bubble_received.9.png and b/src/main/res/drawable-hdpi/message_bubble_received.9.png differ diff --git a/src/main/res/drawable-hdpi/message_bubble_received_warning.9.png b/src/main/res/drawable-hdpi/message_bubble_received_warning.9.png new file mode 100644 index 000000000..9cd2f825d Binary files /dev/null and b/src/main/res/drawable-hdpi/message_bubble_received_warning.9.png differ diff --git a/src/main/res/drawable-hdpi/message_bubble_sent.9.png b/src/main/res/drawable-hdpi/message_bubble_sent.9.png index 9f8a1c074..2bb4e04f6 100644 Binary files a/src/main/res/drawable-hdpi/message_bubble_sent.9.png and b/src/main/res/drawable-hdpi/message_bubble_sent.9.png differ diff --git a/src/main/res/drawable-mdpi/message_bubble_received.9.png b/src/main/res/drawable-mdpi/message_bubble_received.9.png index fd67e2450..7e134402f 100644 Binary files a/src/main/res/drawable-mdpi/message_bubble_received.9.png and b/src/main/res/drawable-mdpi/message_bubble_received.9.png differ diff --git a/src/main/res/drawable-mdpi/message_bubble_received_warning.9.png b/src/main/res/drawable-mdpi/message_bubble_received_warning.9.png new file mode 100644 index 000000000..38f75e051 Binary files /dev/null and b/src/main/res/drawable-mdpi/message_bubble_received_warning.9.png differ diff --git a/src/main/res/drawable-mdpi/message_bubble_sent.9.png b/src/main/res/drawable-mdpi/message_bubble_sent.9.png index 315c18d77..b42664a85 100644 Binary files a/src/main/res/drawable-mdpi/message_bubble_sent.9.png and b/src/main/res/drawable-mdpi/message_bubble_sent.9.png differ diff --git a/src/main/res/drawable-xhdpi/message_bubble_received.9.png b/src/main/res/drawable-xhdpi/message_bubble_received.9.png index 56f39294c..dc8ee5346 100644 Binary files a/src/main/res/drawable-xhdpi/message_bubble_received.9.png and b/src/main/res/drawable-xhdpi/message_bubble_received.9.png differ diff --git a/src/main/res/drawable-xhdpi/message_bubble_received_warning.9.png b/src/main/res/drawable-xhdpi/message_bubble_received_warning.9.png new file mode 100644 index 000000000..b5be808f8 Binary files /dev/null and b/src/main/res/drawable-xhdpi/message_bubble_received_warning.9.png differ diff --git a/src/main/res/drawable-xhdpi/message_bubble_sent.9.png b/src/main/res/drawable-xhdpi/message_bubble_sent.9.png index 293d64f81..f1c462326 100644 Binary files a/src/main/res/drawable-xhdpi/message_bubble_sent.9.png and b/src/main/res/drawable-xhdpi/message_bubble_sent.9.png differ diff --git a/src/main/res/drawable-xxhdpi/message_bubble_received.9.png b/src/main/res/drawable-xxhdpi/message_bubble_received.9.png index 42a7d69b3..6ce844871 100644 Binary files a/src/main/res/drawable-xxhdpi/message_bubble_received.9.png and b/src/main/res/drawable-xxhdpi/message_bubble_received.9.png differ diff --git a/src/main/res/drawable-xxhdpi/message_bubble_received_warning.9.png b/src/main/res/drawable-xxhdpi/message_bubble_received_warning.9.png new file mode 100644 index 000000000..a31818698 Binary files /dev/null and b/src/main/res/drawable-xxhdpi/message_bubble_received_warning.9.png differ diff --git a/src/main/res/drawable-xxhdpi/message_bubble_sent.9.png b/src/main/res/drawable-xxhdpi/message_bubble_sent.9.png index 762bcd916..9f36a6494 100644 Binary files a/src/main/res/drawable-xxhdpi/message_bubble_sent.9.png and b/src/main/res/drawable-xxhdpi/message_bubble_sent.9.png differ diff --git a/src/main/res/drawable-xxxhdpi/message_bubble_received.9.png b/src/main/res/drawable-xxxhdpi/message_bubble_received.9.png index 41d52aa73..e7fa6754b 100644 Binary files a/src/main/res/drawable-xxxhdpi/message_bubble_received.9.png and b/src/main/res/drawable-xxxhdpi/message_bubble_received.9.png differ diff --git a/src/main/res/drawable-xxxhdpi/message_bubble_received_warning.9.png b/src/main/res/drawable-xxxhdpi/message_bubble_received_warning.9.png new file mode 100644 index 000000000..398e53a2d Binary files /dev/null and b/src/main/res/drawable-xxxhdpi/message_bubble_received_warning.9.png differ diff --git a/src/main/res/drawable-xxxhdpi/message_bubble_sent.9.png b/src/main/res/drawable-xxxhdpi/message_bubble_sent.9.png index 1b9604b38..be428cd2e 100644 Binary files a/src/main/res/drawable-xxxhdpi/message_bubble_sent.9.png and b/src/main/res/drawable-xxxhdpi/message_bubble_sent.9.png differ -- cgit v1.2.3 From efcefc2e6301f6255ebcb3e11f0f0a51731bacca Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Tue, 28 Jul 2015 22:00:54 +0200 Subject: Refactor out inner classes, cache trust store Moves SQLiteAxolotlStore and XmppAxolotlSession into proper classes. IdentityKeys trust statuses are now cached in an LruCache to prevent hammering the database when rendering the UI. --- .../crypto/axolotl/AxolotlService.java | 564 +-------------------- .../crypto/axolotl/SQLiteAxolotlStore.java | 473 +++++++++++++++++ .../crypto/axolotl/XmppAxolotlMessage.java | 8 +- .../crypto/axolotl/XmppAxolotlSession.java | 131 +++++ .../eu/siacs/conversations/entities/Message.java | 4 +- .../conversations/persistance/DatabaseBackend.java | 261 +++++----- .../conversations/ui/ConversationActivity.java | 2 +- .../siacs/conversations/ui/TrustKeysActivity.java | 2 +- .../eu/siacs/conversations/ui/XmppActivity.java | 18 +- .../conversations/ui/adapter/MessageAdapter.java | 6 +- 10 files changed, 760 insertions(+), 709 deletions(-) create mode 100644 src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java create mode 100644 src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java (limited to 'src/main') 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 c6f74538a..d1bfe2d42 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -6,28 +6,15 @@ 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; import org.whispersystems.libaxolotl.IdentityKeyPair; 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.SessionBuilder; -import org.whispersystems.libaxolotl.SessionCipher; import org.whispersystems.libaxolotl.UntrustedIdentityException; -import org.whispersystems.libaxolotl.ecc.Curve; -import org.whispersystems.libaxolotl.ecc.ECKeyPair; import org.whispersystems.libaxolotl.ecc.ECPublicKey; -import org.whispersystems.libaxolotl.protocol.CiphertextMessage; -import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage; -import org.whispersystems.libaxolotl.protocol.WhisperMessage; -import org.whispersystems.libaxolotl.state.AxolotlStore; import org.whispersystems.libaxolotl.state.PreKeyBundle; import org.whispersystems.libaxolotl.state.PreKeyRecord; -import org.whispersystems.libaxolotl.state.SessionRecord; import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; import org.whispersystems.libaxolotl.util.KeyHelper; @@ -73,547 +60,6 @@ public class AxolotlService { private final FetchStatusMap fetchStatusMap; private final SerialSingleThreadExecutor executor; - public static class SQLiteAxolotlStore implements AxolotlStore { - - public static final String PREKEY_TABLENAME = "prekeys"; - public static final String SIGNED_PREKEY_TABLENAME = "signed_prekeys"; - public static final String SESSION_TABLENAME = "sessions"; - public static final String IDENTITIES_TABLENAME = "identities"; - public static final String ACCOUNT = "account"; - public static final String DEVICE_ID = "device_id"; - public static final String ID = "id"; - public static final String KEY = "key"; - public static final String FINGERPRINT = "fingerprint"; - public static final String NAME = "name"; - public static final String TRUSTED = "trusted"; - public static final String OWN = "ownkey"; - - public static final String JSONKEY_REGISTRATION_ID = "axolotl_reg_id"; - public static final String JSONKEY_CURRENT_PREKEY_ID = "axolotl_cur_prekey_id"; - - private final Account account; - private final XmppConnectionService mXmppConnectionService; - - private IdentityKeyPair identityKeyPair; - private int localRegistrationId; - private int currentPreKeyId = 0; - - public enum Trust { - UNDECIDED(0), - TRUSTED(1), - UNTRUSTED(2), - COMPROMISED(3), - INACTIVE(4); - - 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: - return "Inactive "+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); - } - }; - - private static IdentityKeyPair generateIdentityKeyPair() { - Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Generating axolotl IdentityKeyPair..."); - ECKeyPair identityKeyPairKeys = Curve.generateKeyPair(); - IdentityKeyPair ownKey = new IdentityKeyPair(new IdentityKey(identityKeyPairKeys.getPublicKey()), - identityKeyPairKeys.getPrivateKey()); - return ownKey; - } - - private static int generateRegistrationId() { - Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Generating axolotl registration ID..."); - int reg_id = KeyHelper.generateRegistrationId(true); - return reg_id; - } - - public SQLiteAxolotlStore(Account account, XmppConnectionService service) { - this.account = account; - this.mXmppConnectionService = service; - this.localRegistrationId = loadRegistrationId(); - this.currentPreKeyId = loadCurrentPreKeyId(); - for (SignedPreKeyRecord record : loadSignedPreKeys()) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Got Axolotl signed prekey record:" + record.getId()); - } - } - - public int getCurrentPreKeyId() { - return currentPreKeyId; - } - - // -------------------------------------- - // IdentityKeyStore - // -------------------------------------- - - private IdentityKeyPair loadIdentityKeyPair() { - String ownName = account.getJid().toBareJid().toString(); - IdentityKeyPair ownKey = mXmppConnectionService.databaseBackend.loadOwnIdentityKeyPair(account, - ownName); - - if (ownKey != null) { - return ownKey; - } else { - Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Could not retrieve axolotl key for account " + ownName); - ownKey = generateIdentityKeyPair(); - mXmppConnectionService.databaseBackend.storeOwnIdentityKeyPair(account, ownName, ownKey); - } - return ownKey; - } - - private int loadRegistrationId() { - return loadRegistrationId(false); - } - - private int loadRegistrationId(boolean regenerate) { - String regIdString = this.account.getKey(JSONKEY_REGISTRATION_ID); - int reg_id; - if (!regenerate && regIdString != null) { - reg_id = Integer.valueOf(regIdString); - } else { - Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Could not retrieve axolotl registration id for account " + account.getJid()); - reg_id = generateRegistrationId(); - boolean success = this.account.setKey(JSONKEY_REGISTRATION_ID, Integer.toString(reg_id)); - if (success) { - mXmppConnectionService.databaseBackend.updateAccount(account); - } else { - Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Failed to write new key to the database!"); - } - } - return reg_id; - } - - private int loadCurrentPreKeyId() { - String regIdString = this.account.getKey(JSONKEY_CURRENT_PREKEY_ID); - int reg_id; - if (regIdString != null) { - reg_id = Integer.valueOf(regIdString); - } else { - Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Could not retrieve current prekey id for account " + account.getJid()); - reg_id = 0; - } - return reg_id; - } - - public void regenerate() { - mXmppConnectionService.databaseBackend.wipeAxolotlDb(account); - account.setKey(JSONKEY_CURRENT_PREKEY_ID, Integer.toString(0)); - identityKeyPair = loadIdentityKeyPair(); - localRegistrationId = loadRegistrationId(true); - currentPreKeyId = 0; - mXmppConnectionService.updateAccountUi(); - } - - /** - * Get the local client's identity key pair. - * - * @return The local client's persistent identity key pair. - */ - @Override - public IdentityKeyPair getIdentityKeyPair() { - if(identityKeyPair == null) { - identityKeyPair = loadIdentityKeyPair(); - } - return identityKeyPair; - } - - /** - * Return the local client's registration ID. - *

- * Clients should maintain a registration ID, a random number - * between 1 and 16380 that's generated once at install time. - * - * @return the local client's registration ID. - */ - @Override - public int getLocalRegistrationId() { - return localRegistrationId; - } - - /** - * Save a remote client's identity key - *

- * Store a remote client's identity key as trusted. - * - * @param name The name of the remote client. - * @param identityKey The remote client's identity key. - */ - @Override - public void saveIdentity(String name, IdentityKey identityKey) { - if(!mXmppConnectionService.databaseBackend.loadIdentityKeys(account, name).contains(identityKey)) { - mXmppConnectionService.databaseBackend.storeIdentityKey(account, name, identityKey); - } - } - - /** - * Verify a remote client's identity key. - *

- * Determine whether a remote client's identity is trusted. Convention is - * that the TextSecure protocol is 'trust on first use.' This means that - * an identity key is considered 'trusted' if there is no entry for the recipient - * in the local store, or if it matches the saved key for a recipient in the local - * store. Only if it mismatches an entry in the local store is it considered - * 'untrusted.' - * - * @param name The name of the remote client. - * @param identityKey The identity key to verify. - * @return true if trusted, false if untrusted. - */ - @Override - public boolean isTrustedIdentity(String name, IdentityKey identityKey) { - return true; - } - - public Trust getFingerprintTrust(String fingerprint) { - return mXmppConnectionService.databaseBackend.isIdentityKeyTrusted(account, fingerprint); - } - - public void setFingerprintTrust(String fingerprint, Trust trust) { - mXmppConnectionService.databaseBackend.setIdentityKeyTrust(account, fingerprint, trust); - } - - public Set getContactUndecidedKeys(String bareJid, Trust trust) { - return mXmppConnectionService.databaseBackend.loadIdentityKeys(account, bareJid, trust); - } - - public long getContactNumTrustedKeys(String bareJid) { - return mXmppConnectionService.databaseBackend.numTrustedKeys(account, bareJid); - } - - // -------------------------------------- - // SessionStore - // -------------------------------------- - - /** - * Returns a copy of the {@link SessionRecord} corresponding to the recipientId + deviceId tuple, - * or a new SessionRecord if one does not currently exist. - *

- * It is important that implementations return a copy of the current durable information. The - * returned SessionRecord may be modified, but those changes should not have an effect on the - * durable session state (what is returned by subsequent calls to this method) without the - * store method being called here first. - * - * @param address The name and device ID of the remote client. - * @return a copy of the SessionRecord corresponding to the recipientId + deviceId tuple, or - * a new SessionRecord if one does not currently exist. - */ - @Override - public SessionRecord loadSession(AxolotlAddress address) { - SessionRecord session = mXmppConnectionService.databaseBackend.loadSession(this.account, address); - return (session != null) ? session : new SessionRecord(); - } - - /** - * Returns all known devices with active sessions for a recipient - * - * @param name the name of the client. - * @return all known sub-devices with active sessions. - */ - @Override - public List getSubDeviceSessions(String name) { - return mXmppConnectionService.databaseBackend.getSubDeviceSessions(account, - new AxolotlAddress(name, 0)); - } - - /** - * Commit to storage the {@link SessionRecord} for a given recipientId + deviceId tuple. - * - * @param address the address of the remote client. - * @param record the current SessionRecord for the remote client. - */ - @Override - public void storeSession(AxolotlAddress address, SessionRecord record) { - mXmppConnectionService.databaseBackend.storeSession(account, address, record); - } - - /** - * Determine whether there is a committed {@link SessionRecord} for a recipientId + deviceId tuple. - * - * @param address the address of the remote client. - * @return true if a {@link SessionRecord} exists, false otherwise. - */ - @Override - public boolean containsSession(AxolotlAddress address) { - return mXmppConnectionService.databaseBackend.containsSession(account, address); - } - - /** - * Remove a {@link SessionRecord} for a recipientId + deviceId tuple. - * - * @param address the address of the remote client. - */ - @Override - public void deleteSession(AxolotlAddress address) { - mXmppConnectionService.databaseBackend.deleteSession(account, address); - } - - /** - * Remove the {@link SessionRecord}s corresponding to all devices of a recipientId. - * - * @param name the name of the remote client. - */ - @Override - public void deleteAllSessions(String name) { - mXmppConnectionService.databaseBackend.deleteAllSessions(account, - new AxolotlAddress(name, 0)); - } - - // -------------------------------------- - // PreKeyStore - // -------------------------------------- - - /** - * Load a local PreKeyRecord. - * - * @param preKeyId the ID of the local PreKeyRecord. - * @return the corresponding PreKeyRecord. - * @throws InvalidKeyIdException when there is no corresponding PreKeyRecord. - */ - @Override - public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException { - PreKeyRecord record = mXmppConnectionService.databaseBackend.loadPreKey(account, preKeyId); - if (record == null) { - throw new InvalidKeyIdException("No such PreKeyRecord: " + preKeyId); - } - return record; - } - - /** - * Store a local PreKeyRecord. - * - * @param preKeyId the ID of the PreKeyRecord to store. - * @param record the PreKeyRecord. - */ - @Override - public void storePreKey(int preKeyId, PreKeyRecord record) { - mXmppConnectionService.databaseBackend.storePreKey(account, record); - currentPreKeyId = preKeyId; - boolean success = this.account.setKey(JSONKEY_CURRENT_PREKEY_ID, Integer.toString(preKeyId)); - if (success) { - mXmppConnectionService.databaseBackend.updateAccount(account); - } else { - Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Failed to write new prekey id to the database!"); - } - } - - /** - * @param preKeyId A PreKeyRecord ID. - * @return true if the store has a record for the preKeyId, otherwise false. - */ - @Override - public boolean containsPreKey(int preKeyId) { - return mXmppConnectionService.databaseBackend.containsPreKey(account, preKeyId); - } - - /** - * Delete a PreKeyRecord from local storage. - * - * @param preKeyId The ID of the PreKeyRecord to remove. - */ - @Override - public void removePreKey(int preKeyId) { - mXmppConnectionService.databaseBackend.deletePreKey(account, preKeyId); - } - - // -------------------------------------- - // SignedPreKeyStore - // -------------------------------------- - - /** - * Load a local SignedPreKeyRecord. - * - * @param signedPreKeyId the ID of the local SignedPreKeyRecord. - * @return the corresponding SignedPreKeyRecord. - * @throws InvalidKeyIdException when there is no corresponding SignedPreKeyRecord. - */ - @Override - public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException { - SignedPreKeyRecord record = mXmppConnectionService.databaseBackend.loadSignedPreKey(account, signedPreKeyId); - if (record == null) { - throw new InvalidKeyIdException("No such SignedPreKeyRecord: " + signedPreKeyId); - } - return record; - } - - /** - * Load all local SignedPreKeyRecords. - * - * @return All stored SignedPreKeyRecords. - */ - @Override - public List loadSignedPreKeys() { - return mXmppConnectionService.databaseBackend.loadSignedPreKeys(account); - } - - /** - * Store a local SignedPreKeyRecord. - * - * @param signedPreKeyId the ID of the SignedPreKeyRecord to store. - * @param record the SignedPreKeyRecord. - */ - @Override - public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) { - mXmppConnectionService.databaseBackend.storeSignedPreKey(account, record); - } - - /** - * @param signedPreKeyId A SignedPreKeyRecord ID. - * @return true if the store has a record for the signedPreKeyId, otherwise false. - */ - @Override - public boolean containsSignedPreKey(int signedPreKeyId) { - return mXmppConnectionService.databaseBackend.containsSignedPreKey(account, signedPreKeyId); - } - - /** - * Delete a SignedPreKeyRecord from local storage. - * - * @param signedPreKeyId The ID of the SignedPreKeyRecord to remove. - */ - @Override - public void removeSignedPreKey(int signedPreKeyId) { - mXmppConnectionService.databaseBackend.deleteSignedPreKey(account, signedPreKeyId); - } - } - - public static class XmppAxolotlSession { - private final SessionCipher cipher; - private Integer preKeyId = null; - private final SQLiteAxolotlStore sqLiteAxolotlStore; - private final AxolotlAddress remoteAddress; - private final Account account; - private String fingerprint = null; - - public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress, String fingerprint) { - this(account, store, remoteAddress); - this.fingerprint = fingerprint; - } - - 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 fingerprint; - } - - 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; - SQLiteAxolotlStore.Trust trust = getTrust(); - switch (trust) { - case INACTIVE: - case UNDECIDED: - case UNTRUSTED: - case TRUSTED: - try { - try { - PreKeyWhisperMessage message = new PreKeyWhisperMessage(incomingHeader.getContents()); - Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"PreKeyWhisperMessage received, new session ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId()); - String fingerprint = message.getIdentityKey().getFingerprint().replaceAll("\\s", ""); - if (this.fingerprint != null && !this.fingerprint.equals(fingerprint)) { - Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Had session with fingerprint "+ this.fingerprint+", received message with fingerprint "+fingerprint); - } else { - this.fingerprint = fingerprint; - plaintext = cipher.decrypt(message); - if (message.getPreKeyId().isPresent()) { - preKeyId = message.getPreKeyId().get(); - } - } - } catch (InvalidMessageException | InvalidVersionException e) { - Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"WhisperMessage received"); - WhisperMessage message = new WhisperMessage(incomingHeader.getContents()); - plaintext = cipher.decrypt(message); - } catch (InvalidKeyException | InvalidKeyIdException | UntrustedIdentityException e) { - Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Error decrypting axolotl header, "+e.getClass().getName()+": " + e.getMessage()); - } - } catch (LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException e) { - 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: - default: - // ignore - break; - } - return plaintext; - } - - @Nullable - public XmppAxolotlMessage.XmppAxolotlMessageHeader processSending(@NonNull byte[] outgoingMessage) { - SQLiteAxolotlStore.Trust trust = getTrust(); - if (trust == SQLiteAxolotlStore.Trust.TRUSTED) { - CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage); - XmppAxolotlMessage.XmppAxolotlMessageHeader header = - new XmppAxolotlMessage.XmppAxolotlMessageHeader(remoteAddress.getDeviceId(), - ciphertextMessage.serialize()); - return header; - } else { - return null; - } - } - } - private static class AxolotlAddressMap { protected Map> map; protected final Object MAP_LOCK = new Object(); @@ -741,11 +187,11 @@ public class AxolotlService { } public Set getKeysWithTrust(SQLiteAxolotlStore.Trust trust) { - return axolotlStore.getContactUndecidedKeys(account.getJid().toBareJid().toString(), trust); + return axolotlStore.getContactKeysWithTrust(account.getJid().toBareJid().toString(), trust); } public Set getKeysWithTrust(SQLiteAxolotlStore.Trust trust, Contact contact) { - return axolotlStore.getContactUndecidedKeys(contact.getJid().toBareJid().toString(), trust); + return axolotlStore.getContactKeysWithTrust(contact.getJid().toBareJid().toString(), trust); } public long getNumTrustedKeys(Contact contact) { @@ -782,7 +228,7 @@ public class AxolotlService { } public int getOwnDeviceId() { - return axolotlStore.loadRegistrationId(); + return axolotlStore.getLocalRegistrationId(); } public Set getOwnDeviceIds() { @@ -1139,7 +585,7 @@ public class AxolotlService { } Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building axolotl foreign headers..."); for (XmppAxolotlSession session : findSessionsforContact(message.getContact())) { - Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account)+session.remoteAddress.toString()); + Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account)+session.getRemoteAddress().toString()); //if(!session.isTrusted()) { // TODO: handle this properly // continue; @@ -1148,7 +594,7 @@ public class AxolotlService { } Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building axolotl own headers..."); for (XmppAxolotlSession session : findOwnSessions()) { - Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account)+session.remoteAddress.toString()); + Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account)+session.getRemoteAddress().toString()); // if(!session.isTrusted()) { // TODO: handle this properly // continue; diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java new file mode 100644 index 000000000..0c9c4e651 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java @@ -0,0 +1,473 @@ +package eu.siacs.conversations.crypto.axolotl; + +import android.util.Log; +import android.util.LruCache; + +import org.whispersystems.libaxolotl.AxolotlAddress; +import org.whispersystems.libaxolotl.IdentityKey; +import org.whispersystems.libaxolotl.IdentityKeyPair; +import org.whispersystems.libaxolotl.InvalidKeyIdException; +import org.whispersystems.libaxolotl.ecc.Curve; +import org.whispersystems.libaxolotl.ecc.ECKeyPair; +import org.whispersystems.libaxolotl.state.AxolotlStore; +import org.whispersystems.libaxolotl.state.PreKeyRecord; +import org.whispersystems.libaxolotl.state.SessionRecord; +import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; +import org.whispersystems.libaxolotl.util.KeyHelper; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.services.XmppConnectionService; + +public class SQLiteAxolotlStore implements AxolotlStore { + + public static final String PREKEY_TABLENAME = "prekeys"; + public static final String SIGNED_PREKEY_TABLENAME = "signed_prekeys"; + public static final String SESSION_TABLENAME = "sessions"; + public static final String IDENTITIES_TABLENAME = "identities"; + public static final String ACCOUNT = "account"; + public static final String DEVICE_ID = "device_id"; + public static final String ID = "id"; + public static final String KEY = "key"; + public static final String FINGERPRINT = "fingerprint"; + public static final String NAME = "name"; + public static final String TRUSTED = "trusted"; + public static final String OWN = "ownkey"; + + public static final String JSONKEY_REGISTRATION_ID = "axolotl_reg_id"; + public static final String JSONKEY_CURRENT_PREKEY_ID = "axolotl_cur_prekey_id"; + + private static final int NUM_TRUSTS_TO_CACHE = 100; + + private final Account account; + private final XmppConnectionService mXmppConnectionService; + + private IdentityKeyPair identityKeyPair; + private int localRegistrationId; + private int currentPreKeyId = 0; + + private final LruCache trustCache = + new LruCache(NUM_TRUSTS_TO_CACHE) { + @Override + protected Trust create(String fingerprint) { + return mXmppConnectionService.databaseBackend.isIdentityKeyTrusted(account, fingerprint); + } + }; + + public enum Trust { + UNDECIDED(0), + TRUSTED(1), + UNTRUSTED(2), + COMPROMISED(3), + INACTIVE(4); + + 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: + return "Inactive " + 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); + } + } + + private static IdentityKeyPair generateIdentityKeyPair() { + Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Generating axolotl IdentityKeyPair..."); + ECKeyPair identityKeyPairKeys = Curve.generateKeyPair(); + return new IdentityKeyPair(new IdentityKey(identityKeyPairKeys.getPublicKey()), + identityKeyPairKeys.getPrivateKey()); + } + + private static int generateRegistrationId() { + Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Generating axolotl registration ID..."); + return KeyHelper.generateRegistrationId(true); + } + + public SQLiteAxolotlStore(Account account, XmppConnectionService service) { + this.account = account; + this.mXmppConnectionService = service; + this.localRegistrationId = loadRegistrationId(); + this.currentPreKeyId = loadCurrentPreKeyId(); + for (SignedPreKeyRecord record : loadSignedPreKeys()) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Got Axolotl signed prekey record:" + record.getId()); + } + } + + public int getCurrentPreKeyId() { + return currentPreKeyId; + } + + // -------------------------------------- + // IdentityKeyStore + // -------------------------------------- + + private IdentityKeyPair loadIdentityKeyPair() { + String ownName = account.getJid().toBareJid().toString(); + IdentityKeyPair ownKey = mXmppConnectionService.databaseBackend.loadOwnIdentityKeyPair(account, + ownName); + + if (ownKey != null) { + return ownKey; + } else { + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve axolotl key for account " + ownName); + ownKey = generateIdentityKeyPair(); + mXmppConnectionService.databaseBackend.storeOwnIdentityKeyPair(account, ownName, ownKey); + } + return ownKey; + } + + private int loadRegistrationId() { + return loadRegistrationId(false); + } + + private int loadRegistrationId(boolean regenerate) { + String regIdString = this.account.getKey(JSONKEY_REGISTRATION_ID); + int reg_id; + if (!regenerate && regIdString != null) { + reg_id = Integer.valueOf(regIdString); + } else { + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve axolotl registration id for account " + account.getJid()); + reg_id = generateRegistrationId(); + boolean success = this.account.setKey(JSONKEY_REGISTRATION_ID, Integer.toString(reg_id)); + if (success) { + mXmppConnectionService.databaseBackend.updateAccount(account); + } else { + Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to write new key to the database!"); + } + } + return reg_id; + } + + private int loadCurrentPreKeyId() { + String regIdString = this.account.getKey(JSONKEY_CURRENT_PREKEY_ID); + int reg_id; + if (regIdString != null) { + reg_id = Integer.valueOf(regIdString); + } else { + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve current prekey id for account " + account.getJid()); + reg_id = 0; + } + return reg_id; + } + + public void regenerate() { + mXmppConnectionService.databaseBackend.wipeAxolotlDb(account); + trustCache.evictAll(); + account.setKey(JSONKEY_CURRENT_PREKEY_ID, Integer.toString(0)); + identityKeyPair = loadIdentityKeyPair(); + localRegistrationId = loadRegistrationId(true); + currentPreKeyId = 0; + mXmppConnectionService.updateAccountUi(); + } + + /** + * Get the local client's identity key pair. + * + * @return The local client's persistent identity key pair. + */ + @Override + public IdentityKeyPair getIdentityKeyPair() { + if (identityKeyPair == null) { + identityKeyPair = loadIdentityKeyPair(); + } + return identityKeyPair; + } + + /** + * Return the local client's registration ID. + *

+ * Clients should maintain a registration ID, a random number + * between 1 and 16380 that's generated once at install time. + * + * @return the local client's registration ID. + */ + @Override + public int getLocalRegistrationId() { + return localRegistrationId; + } + + /** + * Save a remote client's identity key + *

+ * Store a remote client's identity key as trusted. + * + * @param name The name of the remote client. + * @param identityKey The remote client's identity key. + */ + @Override + public void saveIdentity(String name, IdentityKey identityKey) { + if (!mXmppConnectionService.databaseBackend.loadIdentityKeys(account, name).contains(identityKey)) { + mXmppConnectionService.databaseBackend.storeIdentityKey(account, name, identityKey); + } + } + + /** + * Verify a remote client's identity key. + *

+ * Determine whether a remote client's identity is trusted. Convention is + * that the TextSecure protocol is 'trust on first use.' This means that + * an identity key is considered 'trusted' if there is no entry for the recipient + * in the local store, or if it matches the saved key for a recipient in the local + * store. Only if it mismatches an entry in the local store is it considered + * 'untrusted.' + * + * @param name The name of the remote client. + * @param identityKey The identity key to verify. + * @return true if trusted, false if untrusted. + */ + @Override + public boolean isTrustedIdentity(String name, IdentityKey identityKey) { + return true; + } + + public Trust getFingerprintTrust(String fingerprint) { + return (fingerprint == null)? null : trustCache.get(fingerprint); + } + + public void setFingerprintTrust(String fingerprint, Trust trust) { + mXmppConnectionService.databaseBackend.setIdentityKeyTrust(account, fingerprint, trust); + trustCache.remove(fingerprint); + } + + public Set getContactKeysWithTrust(String bareJid, Trust trust) { + return mXmppConnectionService.databaseBackend.loadIdentityKeys(account, bareJid, trust); + } + + public long getContactNumTrustedKeys(String bareJid) { + return mXmppConnectionService.databaseBackend.numTrustedKeys(account, bareJid); + } + + // -------------------------------------- + // SessionStore + // -------------------------------------- + + /** + * Returns a copy of the {@link SessionRecord} corresponding to the recipientId + deviceId tuple, + * or a new SessionRecord if one does not currently exist. + *

+ * It is important that implementations return a copy of the current durable information. The + * returned SessionRecord may be modified, but those changes should not have an effect on the + * durable session state (what is returned by subsequent calls to this method) without the + * store method being called here first. + * + * @param address The name and device ID of the remote client. + * @return a copy of the SessionRecord corresponding to the recipientId + deviceId tuple, or + * a new SessionRecord if one does not currently exist. + */ + @Override + public SessionRecord loadSession(AxolotlAddress address) { + SessionRecord session = mXmppConnectionService.databaseBackend.loadSession(this.account, address); + return (session != null) ? session : new SessionRecord(); + } + + /** + * Returns all known devices with active sessions for a recipient + * + * @param name the name of the client. + * @return all known sub-devices with active sessions. + */ + @Override + public List getSubDeviceSessions(String name) { + return mXmppConnectionService.databaseBackend.getSubDeviceSessions(account, + new AxolotlAddress(name, 0)); + } + + /** + * Commit to storage the {@link SessionRecord} for a given recipientId + deviceId tuple. + * + * @param address the address of the remote client. + * @param record the current SessionRecord for the remote client. + */ + @Override + public void storeSession(AxolotlAddress address, SessionRecord record) { + mXmppConnectionService.databaseBackend.storeSession(account, address, record); + } + + /** + * Determine whether there is a committed {@link SessionRecord} for a recipientId + deviceId tuple. + * + * @param address the address of the remote client. + * @return true if a {@link SessionRecord} exists, false otherwise. + */ + @Override + public boolean containsSession(AxolotlAddress address) { + return mXmppConnectionService.databaseBackend.containsSession(account, address); + } + + /** + * Remove a {@link SessionRecord} for a recipientId + deviceId tuple. + * + * @param address the address of the remote client. + */ + @Override + public void deleteSession(AxolotlAddress address) { + mXmppConnectionService.databaseBackend.deleteSession(account, address); + } + + /** + * Remove the {@link SessionRecord}s corresponding to all devices of a recipientId. + * + * @param name the name of the remote client. + */ + @Override + public void deleteAllSessions(String name) { + AxolotlAddress address = new AxolotlAddress(name, 0); + mXmppConnectionService.databaseBackend.deleteAllSessions(account, + address); + } + + // -------------------------------------- + // PreKeyStore + // -------------------------------------- + + /** + * Load a local PreKeyRecord. + * + * @param preKeyId the ID of the local PreKeyRecord. + * @return the corresponding PreKeyRecord. + * @throws InvalidKeyIdException when there is no corresponding PreKeyRecord. + */ + @Override + public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException { + PreKeyRecord record = mXmppConnectionService.databaseBackend.loadPreKey(account, preKeyId); + if (record == null) { + throw new InvalidKeyIdException("No such PreKeyRecord: " + preKeyId); + } + return record; + } + + /** + * Store a local PreKeyRecord. + * + * @param preKeyId the ID of the PreKeyRecord to store. + * @param record the PreKeyRecord. + */ + @Override + public void storePreKey(int preKeyId, PreKeyRecord record) { + mXmppConnectionService.databaseBackend.storePreKey(account, record); + currentPreKeyId = preKeyId; + boolean success = this.account.setKey(JSONKEY_CURRENT_PREKEY_ID, Integer.toString(preKeyId)); + if (success) { + mXmppConnectionService.databaseBackend.updateAccount(account); + } else { + Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to write new prekey id to the database!"); + } + } + + /** + * @param preKeyId A PreKeyRecord ID. + * @return true if the store has a record for the preKeyId, otherwise false. + */ + @Override + public boolean containsPreKey(int preKeyId) { + return mXmppConnectionService.databaseBackend.containsPreKey(account, preKeyId); + } + + /** + * Delete a PreKeyRecord from local storage. + * + * @param preKeyId The ID of the PreKeyRecord to remove. + */ + @Override + public void removePreKey(int preKeyId) { + mXmppConnectionService.databaseBackend.deletePreKey(account, preKeyId); + } + + // -------------------------------------- + // SignedPreKeyStore + // -------------------------------------- + + /** + * Load a local SignedPreKeyRecord. + * + * @param signedPreKeyId the ID of the local SignedPreKeyRecord. + * @return the corresponding SignedPreKeyRecord. + * @throws InvalidKeyIdException when there is no corresponding SignedPreKeyRecord. + */ + @Override + public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException { + SignedPreKeyRecord record = mXmppConnectionService.databaseBackend.loadSignedPreKey(account, signedPreKeyId); + if (record == null) { + throw new InvalidKeyIdException("No such SignedPreKeyRecord: " + signedPreKeyId); + } + return record; + } + + /** + * Load all local SignedPreKeyRecords. + * + * @return All stored SignedPreKeyRecords. + */ + @Override + public List loadSignedPreKeys() { + return mXmppConnectionService.databaseBackend.loadSignedPreKeys(account); + } + + /** + * Store a local SignedPreKeyRecord. + * + * @param signedPreKeyId the ID of the SignedPreKeyRecord to store. + * @param record the SignedPreKeyRecord. + */ + @Override + public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) { + mXmppConnectionService.databaseBackend.storeSignedPreKey(account, record); + } + + /** + * @param signedPreKeyId A SignedPreKeyRecord ID. + * @return true if the store has a record for the signedPreKeyId, otherwise false. + */ + @Override + public boolean containsSignedPreKey(int signedPreKeyId) { + return mXmppConnectionService.databaseBackend.containsSignedPreKey(account, signedPreKeyId); + } + + /** + * Delete a SignedPreKeyRecord from local storage. + * + * @param signedPreKeyId The ID of the SignedPreKeyRecord to remove. + */ + @Override + public void removeSignedPreKey(int signedPreKeyId) { + mXmppConnectionService.databaseBackend.deleteSignedPreKey(account, signedPreKeyId); + } +} 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 24afeaeae..182c8f128 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java @@ -67,11 +67,11 @@ public class XmppAxolotlMessage { } public static class XmppAxolotlPlaintextMessage { - private final AxolotlService.XmppAxolotlSession session; + private final XmppAxolotlSession session; private final String plaintext; private final String fingerprint; - public XmppAxolotlPlaintextMessage(AxolotlService.XmppAxolotlSession session, String plaintext, String fingerprint) { + public XmppAxolotlPlaintextMessage(XmppAxolotlSession session, String plaintext, String fingerprint) { this.session = session; this.plaintext = plaintext; this.fingerprint = fingerprint; @@ -81,7 +81,7 @@ public class XmppAxolotlMessage { return plaintext; } - public AxolotlService.XmppAxolotlSession getSession() { + public XmppAxolotlSession getSession() { return session; } @@ -180,7 +180,7 @@ public class XmppAxolotlMessage { } - public XmppAxolotlPlaintextMessage decrypt(AxolotlService.XmppAxolotlSession session, byte[] key, String fingerprint) throws CryptoFailedException { + public XmppAxolotlPlaintextMessage decrypt(XmppAxolotlSession session, byte[] key, String fingerprint) throws CryptoFailedException { XmppAxolotlPlaintextMessage plaintextMessage = null; try { diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java new file mode 100644 index 000000000..b8d7dcbaf --- /dev/null +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java @@ -0,0 +1,131 @@ +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.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 eu.siacs.conversations.Config; +import eu.siacs.conversations.entities.Account; + +public class XmppAxolotlSession { + private final SessionCipher cipher; + private Integer preKeyId = null; + private final SQLiteAxolotlStore sqLiteAxolotlStore; + + public AxolotlAddress getRemoteAddress() { + return remoteAddress; + } + + private final AxolotlAddress remoteAddress; + private final Account account; + private String fingerprint = null; + + public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress, String fingerprint) { + this(account, store, remoteAddress); + this.fingerprint = fingerprint; + } + + 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 fingerprint; + } + + 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; + SQLiteAxolotlStore.Trust trust = getTrust(); + switch (trust) { + case INACTIVE: + case UNDECIDED: + case UNTRUSTED: + case TRUSTED: + try { + try { + PreKeyWhisperMessage message = new PreKeyWhisperMessage(incomingHeader.getContents()); + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "PreKeyWhisperMessage received, new session ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId()); + String fingerprint = message.getIdentityKey().getFingerprint().replaceAll("\\s", ""); + if (this.fingerprint != null && !this.fingerprint.equals(fingerprint)) { + Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Had session with fingerprint " + this.fingerprint + ", received message with fingerprint " + fingerprint); + } else { + this.fingerprint = fingerprint; + plaintext = cipher.decrypt(message); + if (message.getPreKeyId().isPresent()) { + preKeyId = message.getPreKeyId().get(); + } + } + } catch (InvalidMessageException | InvalidVersionException e) { + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "WhisperMessage received"); + WhisperMessage message = new WhisperMessage(incomingHeader.getContents()); + plaintext = cipher.decrypt(message); + } catch (InvalidKeyException | InvalidKeyIdException | UntrustedIdentityException e) { + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage()); + } + } catch (LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException e) { + 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: + default: + // ignore + break; + } + return plaintext; + } + + @Nullable + public XmppAxolotlMessage.XmppAxolotlMessageHeader processSending(@NonNull byte[] outgoingMessage) { + SQLiteAxolotlStore.Trust trust = getTrust(); + if (trust == SQLiteAxolotlStore.Trust.TRUSTED) { + CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage); + XmppAxolotlMessage.XmppAxolotlMessageHeader header = + new XmppAxolotlMessage.XmppAxolotlMessageHeader(remoteAddress.getDeviceId(), + ciphertextMessage.serialize()); + return header; + } else { + return null; + } + } +} diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index b4bb31a84..14f962960 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -8,7 +8,7 @@ import java.net.URL; import java.util.Arrays; import eu.siacs.conversations.Config; -import eu.siacs.conversations.crypto.axolotl.AxolotlService; +import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore; import eu.siacs.conversations.utils.GeoHelper; import eu.siacs.conversations.utils.MimeUtils; import eu.siacs.conversations.utils.UIHelper; @@ -689,6 +689,6 @@ public class Message extends AbstractEntity { public boolean isTrusted() { return conversation.getAccount().getAxolotlService().getFingerprintTrust(axolotlFingerprint) - == AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED; + == SQLiteAxolotlStore.Trust.TRUSTED; } } diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 3120c0085..99dbcf34e 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -27,6 +27,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import eu.siacs.conversations.Config; import eu.siacs.conversations.crypto.axolotl.AxolotlService; +import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; @@ -55,56 +56,56 @@ public class DatabaseBackend extends SQLiteOpenHelper { + Contact.JID + ") ON CONFLICT REPLACE);"; private static String CREATE_PREKEYS_STATEMENT = "CREATE TABLE " - + AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME + "(" - + AxolotlService.SQLiteAxolotlStore.ACCOUNT + " TEXT, " - + AxolotlService.SQLiteAxolotlStore.ID + " INTEGER, " - + AxolotlService.SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" - + AxolotlService.SQLiteAxolotlStore.ACCOUNT + + SQLiteAxolotlStore.PREKEY_TABLENAME + "(" + + SQLiteAxolotlStore.ACCOUNT + " TEXT, " + + SQLiteAxolotlStore.ID + " INTEGER, " + + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" + + SQLiteAxolotlStore.ACCOUNT + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, " - + "UNIQUE( " + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ", " - + AxolotlService.SQLiteAxolotlStore.ID + + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", " + + SQLiteAxolotlStore.ID + ") ON CONFLICT REPLACE" +");"; private static String CREATE_SIGNED_PREKEYS_STATEMENT = "CREATE TABLE " - + AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME + "(" - + AxolotlService.SQLiteAxolotlStore.ACCOUNT + " TEXT, " - + AxolotlService.SQLiteAxolotlStore.ID + " INTEGER, " - + AxolotlService.SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" - + AxolotlService.SQLiteAxolotlStore.ACCOUNT + + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME + "(" + + SQLiteAxolotlStore.ACCOUNT + " TEXT, " + + SQLiteAxolotlStore.ID + " INTEGER, " + + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" + + SQLiteAxolotlStore.ACCOUNT + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, " - + "UNIQUE( " + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ", " - + AxolotlService.SQLiteAxolotlStore.ID + + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", " + + SQLiteAxolotlStore.ID + ") ON CONFLICT REPLACE"+ ");"; private static String CREATE_SESSIONS_STATEMENT = "CREATE TABLE " - + AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME + "(" - + AxolotlService.SQLiteAxolotlStore.ACCOUNT + " TEXT, " - + AxolotlService.SQLiteAxolotlStore.NAME + " TEXT, " - + AxolotlService.SQLiteAxolotlStore.DEVICE_ID + " INTEGER, " - + AxolotlService.SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" - + AxolotlService.SQLiteAxolotlStore.ACCOUNT + + SQLiteAxolotlStore.SESSION_TABLENAME + "(" + + SQLiteAxolotlStore.ACCOUNT + " TEXT, " + + SQLiteAxolotlStore.NAME + " TEXT, " + + SQLiteAxolotlStore.DEVICE_ID + " INTEGER, " + + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" + + SQLiteAxolotlStore.ACCOUNT + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, " - + "UNIQUE( " + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ", " - + AxolotlService.SQLiteAxolotlStore.NAME + ", " - + AxolotlService.SQLiteAxolotlStore.DEVICE_ID + + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", " + + SQLiteAxolotlStore.NAME + ", " + + SQLiteAxolotlStore.DEVICE_ID + ") ON CONFLICT REPLACE" +");"; private static String CREATE_IDENTITIES_STATEMENT = "CREATE TABLE " - + AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME + "(" - + AxolotlService.SQLiteAxolotlStore.ACCOUNT + " TEXT, " - + AxolotlService.SQLiteAxolotlStore.NAME + " TEXT, " - + AxolotlService.SQLiteAxolotlStore.OWN + " INTEGER, " - + AxolotlService.SQLiteAxolotlStore.FINGERPRINT + " TEXT, " - + AxolotlService.SQLiteAxolotlStore.TRUSTED + " INTEGER, " - + AxolotlService.SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" - + AxolotlService.SQLiteAxolotlStore.ACCOUNT + + SQLiteAxolotlStore.IDENTITIES_TABLENAME + "(" + + SQLiteAxolotlStore.ACCOUNT + " TEXT, " + + SQLiteAxolotlStore.NAME + " TEXT, " + + SQLiteAxolotlStore.OWN + " INTEGER, " + + SQLiteAxolotlStore.FINGERPRINT + " TEXT, " + + SQLiteAxolotlStore.TRUSTED + " INTEGER, " + + SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY(" + + SQLiteAxolotlStore.ACCOUNT + ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, " - + "UNIQUE( " + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ", " - + AxolotlService.SQLiteAxolotlStore.NAME + ", " - + AxolotlService.SQLiteAxolotlStore.FINGERPRINT + + "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", " + + SQLiteAxolotlStore.NAME + ", " + + SQLiteAxolotlStore.FINGERPRINT + ") ON CONFLICT IGNORE" +");"; @@ -567,11 +568,11 @@ public class DatabaseBackend extends SQLiteOpenHelper { String[] selectionArgs = {account.getUuid(), contact.getName(), Integer.toString(contact.getDeviceId())}; - Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME, + Cursor cursor = db.query(SQLiteAxolotlStore.SESSION_TABLENAME, columns, - AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND " - + AxolotlService.SQLiteAxolotlStore.NAME + " = ? AND " - + AxolotlService.SQLiteAxolotlStore.DEVICE_ID + " = ? ", + SQLiteAxolotlStore.ACCOUNT + " = ? AND " + + SQLiteAxolotlStore.NAME + " = ? AND " + + SQLiteAxolotlStore.DEVICE_ID + " = ? ", selectionArgs, null, null, null); @@ -584,7 +585,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { if(cursor.getCount() != 0) { cursor.moveToFirst(); try { - session = new SessionRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT)); + session = new SessionRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)),Base64.DEFAULT)); } catch (IOException e) { cursor.close(); throw new AssertionError(e); @@ -597,19 +598,19 @@ public class DatabaseBackend extends SQLiteOpenHelper { public List getSubDeviceSessions(Account account, AxolotlAddress contact) { List devices = new ArrayList<>(); final SQLiteDatabase db = this.getReadableDatabase(); - String[] columns = {AxolotlService.SQLiteAxolotlStore.DEVICE_ID}; + String[] columns = {SQLiteAxolotlStore.DEVICE_ID}; String[] selectionArgs = {account.getUuid(), contact.getName()}; - Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME, + Cursor cursor = db.query(SQLiteAxolotlStore.SESSION_TABLENAME, columns, - AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND " - + AxolotlService.SQLiteAxolotlStore.NAME + " = ?", + SQLiteAxolotlStore.ACCOUNT + " = ? AND " + + SQLiteAxolotlStore.NAME + " = ?", selectionArgs, null, null, null); while(cursor.moveToNext()) { devices.add(cursor.getInt( - cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.DEVICE_ID))); + cursor.getColumnIndex(SQLiteAxolotlStore.DEVICE_ID))); } cursor.close(); @@ -626,11 +627,11 @@ public class DatabaseBackend extends SQLiteOpenHelper { public void storeSession(Account account, AxolotlAddress contact, SessionRecord session) { SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); - values.put(AxolotlService.SQLiteAxolotlStore.NAME, contact.getName()); - values.put(AxolotlService.SQLiteAxolotlStore.DEVICE_ID, contact.getDeviceId()); - values.put(AxolotlService.SQLiteAxolotlStore.KEY, Base64.encodeToString(session.serialize(),Base64.DEFAULT)); - values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid()); - db.insert(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME, null, values); + values.put(SQLiteAxolotlStore.NAME, contact.getName()); + values.put(SQLiteAxolotlStore.DEVICE_ID, contact.getDeviceId()); + values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(session.serialize(),Base64.DEFAULT)); + values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid()); + db.insert(SQLiteAxolotlStore.SESSION_TABLENAME, null, values); } public void deleteSession(Account account, AxolotlAddress contact) { @@ -638,30 +639,30 @@ public class DatabaseBackend extends SQLiteOpenHelper { String[] args = {account.getUuid(), contact.getName(), Integer.toString(contact.getDeviceId())}; - db.delete(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME, - AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND " - + AxolotlService.SQLiteAxolotlStore.NAME + " = ? AND " - + AxolotlService.SQLiteAxolotlStore.DEVICE_ID + " = ? ", + db.delete(SQLiteAxolotlStore.SESSION_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + " = ? AND " + + SQLiteAxolotlStore.NAME + " = ? AND " + + SQLiteAxolotlStore.DEVICE_ID + " = ? ", args); } public void deleteAllSessions(Account account, AxolotlAddress contact) { SQLiteDatabase db = this.getWritableDatabase(); String[] args = {account.getUuid(), contact.getName()}; - db.delete(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME, - AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND " - + AxolotlService.SQLiteAxolotlStore.NAME + " = ?", + db.delete(SQLiteAxolotlStore.SESSION_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + "=? AND " + + SQLiteAxolotlStore.NAME + " = ?", args); } private Cursor getCursorForPreKey(Account account, int preKeyId) { SQLiteDatabase db = this.getReadableDatabase(); - String[] columns = {AxolotlService.SQLiteAxolotlStore.KEY}; + String[] columns = {SQLiteAxolotlStore.KEY}; String[] selectionArgs = {account.getUuid(), Integer.toString(preKeyId)}; - Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME, + Cursor cursor = db.query(SQLiteAxolotlStore.PREKEY_TABLENAME, columns, - AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND " - + AxolotlService.SQLiteAxolotlStore.ID + "=?", + SQLiteAxolotlStore.ACCOUNT + "=? AND " + + SQLiteAxolotlStore.ID + "=?", selectionArgs, null, null, null); @@ -674,7 +675,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { if(cursor.getCount() != 0) { cursor.moveToFirst(); try { - record = new PreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT)); + record = new PreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)),Base64.DEFAULT)); } catch (IOException e ) { throw new AssertionError(e); } @@ -693,28 +694,28 @@ public class DatabaseBackend extends SQLiteOpenHelper { public void storePreKey(Account account, PreKeyRecord record) { SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); - values.put(AxolotlService.SQLiteAxolotlStore.ID, record.getId()); - values.put(AxolotlService.SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(),Base64.DEFAULT)); - values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid()); - db.insert(AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME, null, values); + values.put(SQLiteAxolotlStore.ID, record.getId()); + values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(),Base64.DEFAULT)); + values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid()); + db.insert(SQLiteAxolotlStore.PREKEY_TABLENAME, null, values); } public void deletePreKey(Account account, int preKeyId) { SQLiteDatabase db = this.getWritableDatabase(); String[] args = {account.getUuid(), Integer.toString(preKeyId)}; - db.delete(AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME, - AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND " - + AxolotlService.SQLiteAxolotlStore.ID + "=?", + db.delete(SQLiteAxolotlStore.PREKEY_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + "=? AND " + + SQLiteAxolotlStore.ID + "=?", args); } private Cursor getCursorForSignedPreKey(Account account, int signedPreKeyId) { SQLiteDatabase db = this.getReadableDatabase(); - String[] columns = {AxolotlService.SQLiteAxolotlStore.KEY}; + String[] columns = {SQLiteAxolotlStore.KEY}; String[] selectionArgs = {account.getUuid(), Integer.toString(signedPreKeyId)}; - Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, + Cursor cursor = db.query(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, columns, - AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND " + AxolotlService.SQLiteAxolotlStore.ID + "=?", + SQLiteAxolotlStore.ACCOUNT + "=? AND " + SQLiteAxolotlStore.ID + "=?", selectionArgs, null, null, null); @@ -727,7 +728,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { if(cursor.getCount() != 0) { cursor.moveToFirst(); try { - record = new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT)); + record = new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)),Base64.DEFAULT)); } catch (IOException e ) { throw new AssertionError(e); } @@ -739,17 +740,17 @@ public class DatabaseBackend extends SQLiteOpenHelper { public List loadSignedPreKeys(Account account) { List prekeys = new ArrayList<>(); SQLiteDatabase db = this.getReadableDatabase(); - String[] columns = {AxolotlService.SQLiteAxolotlStore.KEY}; + String[] columns = {SQLiteAxolotlStore.KEY}; String[] selectionArgs = {account.getUuid()}; - Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, + Cursor cursor = db.query(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, columns, - AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=?", + SQLiteAxolotlStore.ACCOUNT + "=?", selectionArgs, null, null, null); while(cursor.moveToNext()) { try { - prekeys.add(new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)), Base64.DEFAULT))); + prekeys.add(new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT))); } catch (IOException ignored) { } } @@ -767,18 +768,18 @@ public class DatabaseBackend extends SQLiteOpenHelper { public void storeSignedPreKey(Account account, SignedPreKeyRecord record) { SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); - values.put(AxolotlService.SQLiteAxolotlStore.ID, record.getId()); - values.put(AxolotlService.SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(),Base64.DEFAULT)); - values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid()); - db.insert(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, null, values); + values.put(SQLiteAxolotlStore.ID, record.getId()); + values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(),Base64.DEFAULT)); + values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid()); + db.insert(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, null, values); } public void deleteSignedPreKey(Account account, int signedPreKeyId) { SQLiteDatabase db = this.getWritableDatabase(); String[] args = {account.getUuid(), Integer.toString(signedPreKeyId)}; - db.delete(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, - AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND " - + AxolotlService.SQLiteAxolotlStore.ID + "=?", + db.delete(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + "=? AND " + + SQLiteAxolotlStore.ID + "=?", args); } @@ -792,24 +793,24 @@ public class DatabaseBackend extends SQLiteOpenHelper { private Cursor getIdentityKeyCursor(Account account, String name, Boolean own, String fingerprint) { final SQLiteDatabase db = this.getReadableDatabase(); - String[] columns = {AxolotlService.SQLiteAxolotlStore.TRUSTED, - AxolotlService.SQLiteAxolotlStore.KEY}; + String[] columns = {SQLiteAxolotlStore.TRUSTED, + SQLiteAxolotlStore.KEY}; ArrayList selectionArgs = new ArrayList<>(4); selectionArgs.add(account.getUuid()); - String selectionString = AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?"; + String selectionString = SQLiteAxolotlStore.ACCOUNT + " = ?"; if (name != null){ selectionArgs.add(name); - selectionString += " AND " +AxolotlService.SQLiteAxolotlStore.NAME + " = ?"; + selectionString += " AND " + SQLiteAxolotlStore.NAME + " = ?"; } if (fingerprint != null){ selectionArgs.add(fingerprint); - selectionString += " AND " +AxolotlService.SQLiteAxolotlStore.FINGERPRINT + " = ?"; + selectionString += " AND " + SQLiteAxolotlStore.FINGERPRINT + " = ?"; } if (own != null){ selectionArgs.add(own?"1":"0"); - selectionString += " AND " +AxolotlService.SQLiteAxolotlStore.OWN + " = ?"; + selectionString += " AND " + SQLiteAxolotlStore.OWN + " = ?"; } - Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, + Cursor cursor = db.query(SQLiteAxolotlStore.IDENTITIES_TABLENAME, columns, selectionString, selectionArgs.toArray(new String[selectionArgs.size()]), @@ -824,7 +825,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { if(cursor.getCount() != 0) { cursor.moveToFirst(); try { - identityKeyPair = new IdentityKeyPair(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT)); + identityKeyPair = new IdentityKeyPair(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)),Base64.DEFAULT)); } catch (InvalidKeyException e) { Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name); } @@ -838,18 +839,18 @@ public class DatabaseBackend extends SQLiteOpenHelper { return loadIdentityKeys(account, name, null); } - public Set loadIdentityKeys(Account account, String name, AxolotlService.SQLiteAxolotlStore.Trust trust) { + public Set loadIdentityKeys(Account account, String name, SQLiteAxolotlStore.Trust trust) { Set identityKeys = new HashSet<>(); Cursor cursor = getIdentityKeyCursor(account, name, false); while(cursor.moveToNext()) { if ( trust != null && - cursor.getInt(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.TRUSTED)) + cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.TRUSTED)) != trust.getCode()) { continue; } try { - identityKeys.add(new IdentityKey(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT),0)); + identityKeys.add(new IdentityKey(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)),Base64.DEFAULT),0)); } catch (InvalidKeyException e) { Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Encountered invalid IdentityKey in database for account"+account.getJid().toBareJid()+", address: "+name); } @@ -864,55 +865,55 @@ public class DatabaseBackend extends SQLiteOpenHelper { String[] args = { account.getUuid(), name, - String.valueOf(AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED.getCode()) + String.valueOf(SQLiteAxolotlStore.Trust.TRUSTED.getCode()) }; - return DatabaseUtils.queryNumEntries(db, AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, - AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?" - + " AND " + AxolotlService.SQLiteAxolotlStore.NAME + " = ?" - + " AND " + AxolotlService.SQLiteAxolotlStore.TRUSTED + " = ?", + return DatabaseUtils.queryNumEntries(db, SQLiteAxolotlStore.IDENTITIES_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + " = ?" + + " AND " + SQLiteAxolotlStore.NAME + " = ?" + + " AND " + SQLiteAxolotlStore.TRUSTED + " = ?", args ); } private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized) { - storeIdentityKey(account, name, own, fingerprint, base64Serialized, AxolotlService.SQLiteAxolotlStore.Trust.UNDECIDED); + storeIdentityKey(account, name, own, fingerprint, base64Serialized, SQLiteAxolotlStore.Trust.UNDECIDED); } - private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized, AxolotlService.SQLiteAxolotlStore.Trust trusted) { + private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized, SQLiteAxolotlStore.Trust trusted) { SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); - values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid()); - values.put(AxolotlService.SQLiteAxolotlStore.NAME, name); - values.put(AxolotlService.SQLiteAxolotlStore.OWN, own ? 1 : 0); - values.put(AxolotlService.SQLiteAxolotlStore.FINGERPRINT, fingerprint); - values.put(AxolotlService.SQLiteAxolotlStore.KEY, base64Serialized); - values.put(AxolotlService.SQLiteAxolotlStore.TRUSTED, trusted.getCode()); - db.insert(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values); + values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid()); + values.put(SQLiteAxolotlStore.NAME, name); + values.put(SQLiteAxolotlStore.OWN, own ? 1 : 0); + values.put(SQLiteAxolotlStore.FINGERPRINT, fingerprint); + values.put(SQLiteAxolotlStore.KEY, base64Serialized); + values.put(SQLiteAxolotlStore.TRUSTED, trusted.getCode()); + db.insert(SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values); } - public AxolotlService.SQLiteAxolotlStore.Trust isIdentityKeyTrusted(Account account, String fingerprint) { + public SQLiteAxolotlStore.Trust isIdentityKeyTrusted(Account account, String fingerprint) { Cursor cursor = getIdentityKeyCursor(account, fingerprint); - AxolotlService.SQLiteAxolotlStore.Trust trust = null; + SQLiteAxolotlStore.Trust trust = null; if (cursor.getCount() > 0) { cursor.moveToFirst(); - int trustValue = cursor.getInt(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.TRUSTED)); - trust = AxolotlService.SQLiteAxolotlStore.Trust.fromCode(trustValue); + int trustValue = cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.TRUSTED)); + trust = SQLiteAxolotlStore.Trust.fromCode(trustValue); } cursor.close(); return trust; } - public boolean setIdentityKeyTrust(Account account, String fingerprint, AxolotlService.SQLiteAxolotlStore.Trust trust) { + public boolean setIdentityKeyTrust(Account account, String fingerprint, SQLiteAxolotlStore.Trust trust) { SQLiteDatabase db = this.getWritableDatabase(); String[] selectionArgs = { account.getUuid(), fingerprint }; ContentValues values = new ContentValues(); - values.put(AxolotlService.SQLiteAxolotlStore.TRUSTED, trust.getCode()); - int rows = db.update(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, values, - AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND " - + AxolotlService.SQLiteAxolotlStore.FINGERPRINT + " = ? ", + values.put(SQLiteAxolotlStore.TRUSTED, trust.getCode()); + int rows = db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values, + SQLiteAxolotlStore.ACCOUNT + " = ? AND " + + SQLiteAxolotlStore.FINGERPRINT + " = ? ", selectionArgs); return rows == 1; } @@ -922,7 +923,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { } public void storeOwnIdentityKeyPair(Account account, String name, IdentityKeyPair identityKeyPair) { - storeIdentityKey(account, name, true, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT), AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED); + storeIdentityKey(account, name, true, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT), SQLiteAxolotlStore.Trust.TRUSTED); } public void recreateAxolotlDb() { @@ -931,13 +932,13 @@ public class DatabaseBackend extends SQLiteOpenHelper { public void recreateAxolotlDb(SQLiteDatabase db) { Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+">>> (RE)CREATING AXOLOTL DATABASE <<<"); - db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME); + db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SESSION_TABLENAME); db.execSQL(CREATE_SESSIONS_STATEMENT); - db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME); + db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.PREKEY_TABLENAME); db.execSQL(CREATE_PREKEYS_STATEMENT); - db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME); + db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME); db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT); - db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME); + db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.IDENTITIES_TABLENAME); db.execSQL(CREATE_IDENTITIES_STATEMENT); } @@ -948,17 +949,17 @@ public class DatabaseBackend extends SQLiteOpenHelper { String[] deleteArgs= { accountName }; - db.delete(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME, - AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?", + db.delete(SQLiteAxolotlStore.SESSION_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + " = ?", deleteArgs); - db.delete(AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME, - AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?", + db.delete(SQLiteAxolotlStore.PREKEY_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + " = ?", deleteArgs); - db.delete(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, - AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?", + db.delete(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + " = ?", deleteArgs); - db.delete(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, - AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?", + db.delete(SQLiteAxolotlStore.IDENTITIES_TABLENAME, + SQLiteAxolotlStore.ACCOUNT + " = ?", deleteArgs); } } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index 92b8e5443..515e814bd 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -38,7 +38,7 @@ import de.timroes.android.listview.EnhancedListView; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.axolotl.AxolotlService; -import eu.siacs.conversations.crypto.axolotl.AxolotlService.SQLiteAxolotlStore.Trust; +import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore.Trust; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Blockable; import eu.siacs.conversations.entities.Contact; diff --git a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java index 1bf07f3ea..0b5d092b9 100644 --- a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java @@ -16,7 +16,7 @@ import java.util.Map; import java.util.Set; import eu.siacs.conversations.R; -import eu.siacs.conversations.crypto.axolotl.AxolotlService.SQLiteAxolotlStore.Trust; +import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore.Trust; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 835685107..52c2e4f47 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -70,7 +70,7 @@ import java.util.concurrent.RejectedExecutionException; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; -import eu.siacs.conversations.crypto.axolotl.AxolotlService; +import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; @@ -610,16 +610,16 @@ public abstract class XmppActivity extends Activity { protected boolean addFingerprintRow(LinearLayout keys, final Account account, IdentityKey identityKey) { final String fingerprint = identityKey.getFingerprint().replaceAll("\\s", ""); - final AxolotlService.SQLiteAxolotlStore.Trust trust = account.getAxolotlService() + final SQLiteAxolotlStore.Trust trust = account.getAxolotlService() .getFingerprintTrust(fingerprint); return addFingerprintRowWithListeners(keys, account, identityKey, trust, true, new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - if (isChecked != (trust == AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED)) { + if (isChecked != (trust == SQLiteAxolotlStore.Trust.TRUSTED)) { account.getAxolotlService().setFingerprintTrust(fingerprint, - (isChecked) ? AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED : - AxolotlService.SQLiteAxolotlStore.Trust.UNTRUSTED); + (isChecked) ? SQLiteAxolotlStore.Trust.TRUSTED : + SQLiteAxolotlStore.Trust.UNTRUSTED); } refreshUi(); xmppConnectionService.updateAccountUi(); @@ -630,7 +630,7 @@ public abstract class XmppActivity extends Activity { @Override public void onClick(View v) { account.getAxolotlService().setFingerprintTrust(fingerprint, - AxolotlService.SQLiteAxolotlStore.Trust.UNTRUSTED); + SQLiteAxolotlStore.Trust.UNTRUSTED); refreshUi(); xmppConnectionService.updateAccountUi(); xmppConnectionService.updateConversationUi(); @@ -642,12 +642,12 @@ public abstract class XmppActivity extends Activity { protected boolean addFingerprintRowWithListeners(LinearLayout keys, final Account account, final IdentityKey identityKey, - AxolotlService.SQLiteAxolotlStore.Trust trust, + SQLiteAxolotlStore.Trust trust, boolean showTag, CompoundButton.OnCheckedChangeListener onCheckedChangeListener, View.OnClickListener onClickListener) { - if (trust == AxolotlService.SQLiteAxolotlStore.Trust.COMPROMISED) { + if (trust == SQLiteAxolotlStore.Trust.COMPROMISED) { return false; } View view = getLayoutInflater().inflate(R.layout.contact_key, keys, false); @@ -668,7 +668,7 @@ public abstract class XmppActivity extends Activity { switch (trust) { case UNTRUSTED: case TRUSTED: - trustToggle.setChecked(trust == AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED, false); + trustToggle.setChecked(trust == SQLiteAxolotlStore.Trust.TRUSTED, false); trustToggle.setEnabled(true); key.setTextColor(getPrimaryTextColor()); keyType.setTextColor(getSecondaryTextColor()); diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index b158f0fe4..f55094af6 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -27,7 +27,7 @@ import android.widget.Toast; import java.util.List; import eu.siacs.conversations.R; -import eu.siacs.conversations.crypto.axolotl.AxolotlService; +import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; @@ -173,11 +173,11 @@ public class MessageAdapter extends ArrayAdapter { } else { viewHolder.indicator.setVisibility(View.VISIBLE); if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { - AxolotlService.SQLiteAxolotlStore.Trust trust = message.getConversation() + SQLiteAxolotlStore.Trust trust = message.getConversation() .getAccount().getAxolotlService().getFingerprintTrust( message.getAxolotlFingerprint()); - if(trust == null || trust != AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED) { + if(trust == null || trust != SQLiteAxolotlStore.Trust.TRUSTED) { viewHolder.indicator.setColorFilter(Color.RED); } else { viewHolder.indicator.clearColorFilter(); -- cgit v1.2.3 From a3991d59c908c151fa2b1f6ea9f865313d7c8cce Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Tue, 28 Jul 2015 22:29:19 +0200 Subject: Improve trust toggle responsiveness Removed unnecessary UI refreshes, explicitly update UI where needed. --- src/main/java/eu/siacs/conversations/ui/XmppActivity.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'src/main') diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 52c2e4f47..857a1dc43 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -621,9 +621,6 @@ public abstract class XmppActivity extends Activity { (isChecked) ? SQLiteAxolotlStore.Trust.TRUSTED : SQLiteAxolotlStore.Trust.UNTRUSTED); } - refreshUi(); - xmppConnectionService.updateAccountUi(); - xmppConnectionService.updateConversationUi(); } }, new View.OnClickListener() { @@ -631,9 +628,7 @@ public abstract class XmppActivity extends Activity { public void onClick(View v) { account.getAxolotlService().setFingerprintTrust(fingerprint, SQLiteAxolotlStore.Trust.UNTRUSTED); - refreshUi(); - xmppConnectionService.updateAccountUi(); - xmppConnectionService.updateConversationUi(); + v.setEnabled(true); } } -- cgit v1.2.3 From 77920c7aa608bd92391ade653eb8971b977eb7d5 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Wed, 29 Jul 2015 02:49:14 +0200 Subject: Color plaintext messages in encrypted sessions red Plaintext messages that were received while in an encrypted session are now colored red. We define "in an encrypted session" if a) the last message sent by our own device before the message under consideration (or any message received between then and now) was encrypted AND b) the next message will be sent encrypted or the next message sent after the one under consideration was sent encrypted --- .../conversations/ui/adapter/MessageAdapter.java | 30 ++++++++++++++++++++++ 1 file changed, 30 insertions(+) (limited to 'src/main') diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index f55094af6..c44796bac 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -560,6 +560,36 @@ public class MessageAdapter extends ArrayAdapter { } } + if (type == RECEIVED) { + boolean wasEncrypted = false; + for (Message iterator = message.prev(); iterator != null; iterator = iterator.prev()){ + if (iterator.getEncryption() != Message.ENCRYPTION_NONE) { + wasEncrypted = true; + break; + } + if (iterator.getRemoteMsgId() == null && iterator.getType() == SENT) { + break; + } + } + boolean willBeEncrypted = conversation.getNextEncryption(false) != Message.ENCRYPTION_NONE; + for (Message iterator = message.next(); iterator != null; iterator = iterator.next()){ + if (iterator.getEncryption() != Message.ENCRYPTION_NONE) { + willBeEncrypted = true; + break; + } + if (iterator.getRemoteMsgId() == null && iterator.getType() == SENT) { + break; + } + } + + if ( willBeEncrypted && wasEncrypted + && message.getEncryption() == Message.ENCRYPTION_NONE) { + viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received_warning); + } else { + viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received); + } + } + displayStatus(viewHolder, message); return view; -- cgit v1.2.3 From e10a6c5b87d5d4b4871fdf31bbc9a7ef8b8d6ff1 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Wed, 29 Jul 2015 02:56:47 +0200 Subject: Fix NPE: consider unknown keys UNDECIDED --- .../java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/main') diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java index b8d7dcbaf..d58b2dd8f 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java @@ -64,7 +64,8 @@ public class XmppAxolotlSession { } protected SQLiteAxolotlStore.Trust getTrust() { - return sqLiteAxolotlStore.getFingerprintTrust(fingerprint); + SQLiteAxolotlStore.Trust trust = sqLiteAxolotlStore.getFingerprintTrust(fingerprint); + return (trust == null)? SQLiteAxolotlStore.Trust.UNDECIDED : trust; } @Nullable -- cgit v1.2.3 From e6df4d81d23e4b7c0c3798f114714dd3caebfe8c Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Wed, 29 Jul 2015 16:41:58 +0200 Subject: Tag carbon messages in parser, adapt session logic Messages sent from another device of the own account are now explicitly tagged as carboned message. The session detection logic now uses this tag to find "session borders". --- .../eu/siacs/conversations/entities/Message.java | 24 +++++++++++++++++++--- .../siacs/conversations/parser/MessageParser.java | 5 ++++- .../conversations/persistance/DatabaseBackend.java | 6 +++++- .../conversations/ui/adapter/MessageAdapter.java | 4 ++-- 4 files changed, 32 insertions(+), 7 deletions(-) (limited to 'src/main') diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index 14f962960..99b78d14f 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -51,6 +51,7 @@ public class Message extends AbstractEntity { public static final String ENCRYPTION = "encryption"; public static final String STATUS = "status"; public static final String TYPE = "type"; + public static final String CARBON = "carbon"; public static final String REMOTE_MSG_ID = "remoteMsgId"; public static final String SERVER_MSG_ID = "serverMsgId"; public static final String RELATIVE_FILE_PATH = "relativeFilePath"; @@ -68,6 +69,7 @@ public class Message extends AbstractEntity { protected int encryption; protected int status; protected int type; + protected boolean carbon = false; protected String relativeFilePath; protected boolean read = true; protected String remoteMsgId = null; @@ -85,8 +87,11 @@ public class Message extends AbstractEntity { public Message(Conversation conversation, String body, int encryption) { this(conversation, body, encryption, STATUS_UNSEND); } - public Message(Conversation conversation, String body, int encryption, int status) { + this(conversation, body, encryption, status, false); + } + + public Message(Conversation conversation, String body, int encryption, int status, boolean carbon) { this(java.util.UUID.randomUUID().toString(), conversation.getUuid(), conversation.getJid() == null ? null : conversation.getJid().toBareJid(), @@ -96,6 +101,7 @@ public class Message extends AbstractEntity { encryption, status, TYPE_TEXT, + false, null, null, null, @@ -105,8 +111,9 @@ public class Message extends AbstractEntity { private Message(final String uuid, final String conversationUUid, final Jid counterpart, final Jid trueCounterpart, final String body, final long timeSent, - final int encryption, final int status, final int type, final String remoteMsgId, - final String relativeFilePath, final String serverMsgId, final String fingerprint) { + final int encryption, final int status, final int type, final boolean carbon, + final String remoteMsgId, final String relativeFilePath, + final String serverMsgId, final String fingerprint) { this.uuid = uuid; this.conversationUuid = conversationUUid; this.counterpart = counterpart; @@ -116,6 +123,7 @@ public class Message extends AbstractEntity { this.encryption = encryption; this.status = status; this.type = type; + this.carbon = carbon; this.remoteMsgId = remoteMsgId; this.relativeFilePath = relativeFilePath; this.serverMsgId = serverMsgId; @@ -154,6 +162,7 @@ public class Message extends AbstractEntity { cursor.getInt(cursor.getColumnIndex(ENCRYPTION)), cursor.getInt(cursor.getColumnIndex(STATUS)), cursor.getInt(cursor.getColumnIndex(TYPE)), + cursor.getInt(cursor.getColumnIndex(CARBON))>0, cursor.getString(cursor.getColumnIndex(REMOTE_MSG_ID)), cursor.getString(cursor.getColumnIndex(RELATIVE_FILE_PATH)), cursor.getString(cursor.getColumnIndex(SERVER_MSG_ID)), @@ -188,6 +197,7 @@ public class Message extends AbstractEntity { values.put(ENCRYPTION, encryption); values.put(STATUS, status); values.put(TYPE, type); + values.put(CARBON, carbon ? 1 : 0); values.put(REMOTE_MSG_ID, remoteMsgId); values.put(RELATIVE_FILE_PATH, relativeFilePath); values.put(SERVER_MSG_ID, serverMsgId); @@ -312,6 +322,14 @@ public class Message extends AbstractEntity { this.type = type; } + public boolean isCarbon() { + return carbon; + } + + public void setCarbon(boolean carbon) { + this.carbon = carbon; + } + public void setTrueCounterpart(Jid trueCounterpart) { this.trueCounterpart = trueCounterpart; } diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index f06595110..3aaa3ce74 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -230,6 +230,7 @@ public class MessageParser extends AbstractParser implements final MessagePacket packet; Long timestamp = null; final boolean isForwarded; + boolean isCarbon = false; String serverMsgId = null; final Element fin = original.findChild("fin", "urn:xmpp:mam:0"); if (fin != null) { @@ -260,7 +261,8 @@ public class MessageParser extends AbstractParser implements return; } timestamp = f != null ? f.second : null; - isForwarded = f != null; + isCarbon = f != null; + isForwarded = isCarbon; } else { packet = original; isForwarded = false; @@ -346,6 +348,7 @@ public class MessageParser extends AbstractParser implements message.setCounterpart(counterpart); message.setRemoteMsgId(remoteMsgId); message.setServerMsgId(serverMsgId); + message.setCarbon(isCarbon); message.setTime(timestamp); message.markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0"); if (conversation.getMode() == Conversation.MODE_MULTI) { diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 99dbcf34e..b3fce845a 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -41,7 +41,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { private static DatabaseBackend instance = null; private static final String DATABASE_NAME = "history"; - private static final int DATABASE_VERSION = 15; + private static final int DATABASE_VERSION = 16; private static String CREATE_CONTATCS_STATEMENT = "create table " + Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, " @@ -295,6 +295,10 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.FINGERPRINT + " TEXT"); } + if (oldVersion < 16 && newVersion >= 16) { + db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + + Message.CARBON + " INTEGER"); + } } public static synchronized DatabaseBackend getInstance(Context context) { diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index c44796bac..c4b1e6fc3 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -567,7 +567,7 @@ public class MessageAdapter extends ArrayAdapter { wasEncrypted = true; break; } - if (iterator.getRemoteMsgId() == null && iterator.getType() == SENT) { + if (!iterator.isCarbon() && iterator.getType() == SENT) { break; } } @@ -577,7 +577,7 @@ public class MessageAdapter extends ArrayAdapter { willBeEncrypted = true; break; } - if (iterator.getRemoteMsgId() == null && iterator.getType() == SENT) { + if (!iterator.isCarbon() && iterator.getType() == SENT) { break; } } -- cgit v1.2.3 From e07853ea6267304b5380e77b794a3f906b06aca4 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Wed, 29 Jul 2015 20:10:21 +0200 Subject: Rerender message bubbles on encryption change --- src/main/java/eu/siacs/conversations/ui/ConversationActivity.java | 1 + 1 file changed, 1 insertion(+) (limited to 'src/main') diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index 515e814bd..147ae824f 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -775,6 +775,7 @@ public class ConversationActivity extends XmppActivity xmppConnectionService.databaseBackend.updateConversation(conversation); fragment.updateChatMsgHint(); invalidateOptionsMenu(); + refreshUi(); return true; } }); -- cgit v1.2.3 From 2b3bb0226121acb173080e4893421eefef4a9571 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Wed, 29 Jul 2015 20:21:37 +0200 Subject: Highlight selected message's fingerprint in list --- .../conversations/ui/ContactDetailsActivity.java | 5 +++- .../conversations/ui/ConversationFragment.java | 3 ++- .../conversations/ui/EditAccountActivity.java | 5 +++- .../siacs/conversations/ui/TrustKeysActivity.java | 4 ++-- .../eu/siacs/conversations/ui/XmppActivity.java | 28 +++++++++++++++------- src/main/res/layout/contact_key.xml | 1 + src/main/res/values/strings.xml | 1 + 7 files changed, 34 insertions(+), 13 deletions(-) (limited to 'src/main') diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java index a0e02c1b8..4c3923bba 100644 --- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java @@ -110,6 +110,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd private LinearLayout keys; private LinearLayout tags; private boolean showDynamicTags; + private String messageFingerprint; private DialogInterface.OnClickListener addToPhonebook = new DialogInterface.OnClickListener() { @@ -192,6 +193,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd } catch (final InvalidJidException ignored) { } } + this.messageFingerprint = getIntent().getStringExtra("fingerprint"); setContentView(R.layout.activity_contact_details); contactJidTv = (TextView) findViewById(R.id.details_contactjid); @@ -385,7 +387,8 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd } for(final IdentityKey identityKey : xmppConnectionService.databaseBackend.loadIdentityKeys( contact.getAccount(), contact.getJid().toBareJid().toString())) { - hasKeys |= addFingerprintRow(keys, contact.getAccount(), identityKey); + boolean highlight = identityKey.getFingerprint().replaceAll("\\s", "").equals(messageFingerprint); + hasKeys |= addFingerprintRow(keys, contact.getAccount(), identityKey, highlight); } if (contact.getPgpKeyId() != 0) { hasKeys = true; diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 044294213..47c6a764d 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -392,12 +392,13 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa highlightInConference(user); } } else { - activity.switchToContactDetails(message.getContact()); + activity.switchToContactDetails(message.getContact(), message.getAxolotlFingerprint()); } } else { Account account = message.getConversation().getAccount(); Intent intent = new Intent(activity, EditAccountActivity.class); intent.putExtra("jid", account.getJid().toBareJid().toString()); + intent.putExtra("fingerprint", message.getAxolotlFingerprint()); startActivity(intent); } } diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index 3c1f155df..a1c28df8c 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -74,6 +74,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate private Jid jidToEdit; private Account mAccount; + private String messageFingerprint; private boolean mFetchingAvatar = false; @@ -388,6 +389,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } catch (final InvalidJidException | NullPointerException ignored) { this.jidToEdit = null; } + this.messageFingerprint = getIntent().getStringExtra("fingerprint"); if (this.jidToEdit != null) { this.mRegisterNew.setVisibility(View.GONE); if (getActionBar() != null) { @@ -571,7 +573,8 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate if(ownKey.equals(identityKey)) { continue; } - hasKeys |= addFingerprintRow(keys, mAccount, identityKey); + boolean highlight = identityKey.getFingerprint().replaceAll("\\s", "").equals(messageFingerprint); + hasKeys |= addFingerprintRow(keys, mAccount, identityKey, highlight); } if (hasKeys) { keysCard.setVisibility(View.VISIBLE); diff --git a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java index 0b5d092b9..d5959b7a8 100644 --- a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java @@ -118,7 +118,7 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate boolean hasForeignKeys = false; for(final IdentityKey identityKey : ownKeysToTrust.keySet()) { hasOwnKeys = true; - addFingerprintRowWithListeners(ownKeys, contact.getAccount(), identityKey, + addFingerprintRowWithListeners(ownKeys, contact.getAccount(), identityKey, false, Trust.fromBoolean(ownKeysToTrust.get(identityKey)), false, new CompoundButton.OnCheckedChangeListener() { @Override @@ -134,7 +134,7 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate } for(final IdentityKey identityKey : foreignKeysToTrust.keySet()) { hasForeignKeys = true; - addFingerprintRowWithListeners(foreignKeys, contact.getAccount(), identityKey, + addFingerprintRowWithListeners(foreignKeys, contact.getAccount(), identityKey, false, Trust.fromBoolean(foreignKeysToTrust.get(identityKey)), false, new CompoundButton.OnCheckedChangeListener() { @Override diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 857a1dc43..3093db467 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -424,10 +424,15 @@ public abstract class XmppActivity extends Activity { } public void switchToContactDetails(Contact contact) { + switchToContactDetails(contact, null); + } + + public void switchToContactDetails(Contact contact, String messageFingerprint) { Intent intent = new Intent(this, ContactDetailsActivity.class); intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT); intent.putExtra("account", contact.getAccount().getJid().toBareJid().toString()); intent.putExtra("contact", contact.getJid().toString()); + intent.putExtra("fingerprint", messageFingerprint); startActivity(intent); } @@ -608,11 +613,11 @@ public abstract class XmppActivity extends Activity { builder.create().show(); } - protected boolean addFingerprintRow(LinearLayout keys, final Account account, IdentityKey identityKey) { + protected boolean addFingerprintRow(LinearLayout keys, final Account account, IdentityKey identityKey, boolean highlight) { final String fingerprint = identityKey.getFingerprint().replaceAll("\\s", ""); final SQLiteAxolotlStore.Trust trust = account.getAxolotlService() .getFingerprintTrust(fingerprint); - return addFingerprintRowWithListeners(keys, account, identityKey, trust, true, + return addFingerprintRowWithListeners(keys, account, identityKey, highlight, trust, true, new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { @@ -636,12 +641,13 @@ public abstract class XmppActivity extends Activity { } protected boolean addFingerprintRowWithListeners(LinearLayout keys, final Account account, - final IdentityKey identityKey, - SQLiteAxolotlStore.Trust trust, - boolean showTag, - CompoundButton.OnCheckedChangeListener - onCheckedChangeListener, - View.OnClickListener onClickListener) { + final IdentityKey identityKey, + boolean highlight, + SQLiteAxolotlStore.Trust trust, + boolean showTag, + CompoundButton.OnCheckedChangeListener + onCheckedChangeListener, + View.OnClickListener onClickListener) { if (trust == SQLiteAxolotlStore.Trust.COMPROMISED) { return false; } @@ -688,6 +694,12 @@ public abstract class XmppActivity extends Activity { } else { keyType.setVisibility(View.GONE); } + if (highlight) { + keyType.setTextColor(getResources().getColor(R.color.accent)); + keyType.setText(getString(R.string.axolotl_fingerprint_selected_message)); + } else { + keyType.setText(getString(R.string.axolotl_fingerprint)); + } key.setText(CryptoHelper.prettifyFingerprint(identityKey.getFingerprint())); keys.addView(view); diff --git a/src/main/res/layout/contact_key.xml b/src/main/res/layout/contact_key.xml index 6b86f80a5..0f61a15f1 100644 --- a/src/main/res/layout/contact_key.xml +++ b/src/main/res/layout/contact_key.xml @@ -28,6 +28,7 @@ android:textColor="@color/black54" android:layout_alignParentLeft="true" android:layout_below="@+id/key" + android:maxLines="1" android:textSize="?attr/TextSizeInfo"/> Your fingerprint OTR fingerprint Axolotl fingerprint + Axolotl fingerprint of message Own Axolotl fingerprint Other devices Trust Axolotl Keys -- cgit v1.2.3 From b7c64cd19d011ad24d64e13ab9154ae07f06a872 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Wed, 29 Jul 2015 20:25:14 +0200 Subject: Reset lock color for non-axolotl messages --- src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/main') diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index c4b1e6fc3..2bd89ccb8 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -182,6 +182,8 @@ public class MessageAdapter extends ArrayAdapter { } else { viewHolder.indicator.clearColorFilter(); } + } else { + viewHolder.indicator.clearColorFilter(); } } -- cgit v1.2.3 From 58d80f58be2f75b858e93766c61857a78d161005 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 29 Jul 2015 23:45:37 +0200 Subject: use gcm for file encryption over http --- .../conversations/entities/DownloadableFile.java | 97 ++-------------------- .../conversations/http/HttpDownloadConnection.java | 21 +++-- .../conversations/http/HttpUploadConnection.java | 32 +++++-- .../xmpp/jingle/JingleInbandTransport.java | 4 +- .../xmpp/jingle/JingleSocks5Transport.java | 4 +- .../conversations/xmpp/jingle/JingleTransport.java | 73 ++++++++++++++++ 6 files changed, 127 insertions(+), 104 deletions(-) (limited to 'src/main') diff --git a/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java b/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java index ca3254484..452bf815c 100644 --- a/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java +++ b/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java @@ -1,26 +1,7 @@ package eu.siacs.conversations.entities; -import android.util.Log; - import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.io.OutputStream; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.Key; -import java.security.NoSuchAlgorithmException; - -import javax.crypto.Cipher; -import javax.crypto.CipherInputStream; -import javax.crypto.CipherOutputStream; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; - -import eu.siacs.conversations.Config; + import eu.siacs.conversations.utils.MimeUtils; public class DownloadableFile extends File { @@ -29,8 +10,7 @@ public class DownloadableFile extends File { private long expectedSize = 0; private String sha1sum; - private Key aeskey; - private String mime; + private byte[] aeskey; private byte[] iv = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0xf }; @@ -84,85 +64,24 @@ public class DownloadableFile extends File { byte[] iv = new byte[16]; System.arraycopy(key, 0, iv, 0, 16); System.arraycopy(key, 16, secretKey, 0, 32); - this.aeskey = new SecretKeySpec(secretKey, "AES"); + this.aeskey = secretKey; this.iv = iv; } else if (key.length >= 32) { byte[] secretKey = new byte[32]; System.arraycopy(key, 0, secretKey, 0, 32); - this.aeskey = new SecretKeySpec(secretKey, "AES"); + this.aeskey = secretKey; } else if (key.length >= 16) { byte[] secretKey = new byte[16]; System.arraycopy(key, 0, secretKey, 0, 16); - this.aeskey = new SecretKeySpec(secretKey, "AES"); + this.aeskey = secretKey; } } - public Key getKey() { + public byte[] getKey() { return this.aeskey; } - public InputStream createInputStream() { - if (this.getKey() == null) { - try { - return new FileInputStream(this); - } catch (FileNotFoundException e) { - return null; - } - } else { - try { - IvParameterSpec ips = new IvParameterSpec(iv); - Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - cipher.init(Cipher.ENCRYPT_MODE, this.getKey(), ips); - Log.d(Config.LOGTAG, "opening encrypted input stream"); - return new CipherInputStream(new FileInputStream(this), cipher); - } catch (NoSuchAlgorithmException e) { - Log.d(Config.LOGTAG, "no such algo: " + e.getMessage()); - return null; - } catch (NoSuchPaddingException e) { - Log.d(Config.LOGTAG, "no such padding: " + e.getMessage()); - return null; - } catch (InvalidKeyException e) { - Log.d(Config.LOGTAG, "invalid key: " + e.getMessage()); - return null; - } catch (InvalidAlgorithmParameterException e) { - Log.d(Config.LOGTAG, "invavid iv:" + e.getMessage()); - return null; - } catch (FileNotFoundException e) { - return null; - } - } - } - - public OutputStream createOutputStream() { - if (this.getKey() == null) { - try { - return new FileOutputStream(this); - } catch (FileNotFoundException e) { - return null; - } - } else { - try { - IvParameterSpec ips = new IvParameterSpec(this.iv); - Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - cipher.init(Cipher.DECRYPT_MODE, this.getKey(), ips); - Log.d(Config.LOGTAG, "opening encrypted output stream"); - return new CipherOutputStream(new FileOutputStream(this), - cipher); - } catch (NoSuchAlgorithmException e) { - Log.d(Config.LOGTAG, "no such algo: " + e.getMessage()); - return null; - } catch (NoSuchPaddingException e) { - Log.d(Config.LOGTAG, "no such padding: " + e.getMessage()); - return null; - } catch (InvalidKeyException e) { - Log.d(Config.LOGTAG, "invalid key: " + e.getMessage()); - return null; - } catch (InvalidAlgorithmParameterException e) { - Log.d(Config.LOGTAG, "invavid iv:" + e.getMessage()); - return null; - } catch (FileNotFoundException e) { - return null; - } - } + public byte[] getIv() { + return this.iv; } } diff --git a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java index 30d9a393a..51479836a 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java @@ -2,10 +2,17 @@ package eu.siacs.conversations.http; import android.content.Intent; import android.net.Uri; -import android.os.SystemClock; import android.util.Log; +import org.bouncycastle.crypto.engines.AESEngine; +import org.bouncycastle.crypto.io.CipherOutputStream; +import org.bouncycastle.crypto.modes.AEADBlockCipher; +import org.bouncycastle.crypto.modes.GCMBlockCipher; +import org.bouncycastle.crypto.params.AEADParameters; +import org.bouncycastle.crypto.params.KeyParameter; + import java.io.BufferedInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.HttpURLConnection; @@ -206,7 +213,7 @@ public class HttpDownloadConnection implements Transferable { } } - private void download() throws SSLHandshakeException, IOException { + private void download() throws IOException { HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection(); if (connection instanceof HttpsURLConnection) { mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive); @@ -215,9 +222,13 @@ public class HttpDownloadConnection implements Transferable { BufferedInputStream is = new BufferedInputStream(connection.getInputStream()); file.getParentFile().mkdirs(); file.createNewFile(); - OutputStream os = file.createOutputStream(); - if (os == null) { - throw new IOException(); + OutputStream os; + if (file.getKey() != null) { + AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine()); + cipher.init(false, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv())); + os = new CipherOutputStream(new FileOutputStream(file), cipher); + } else { + os = new FileOutputStream(file); } long transmitted = 0; long expected = file.getExpectedSize(); diff --git a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java index c9fb3d8c5..70dcc6421 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java @@ -1,8 +1,18 @@ package eu.siacs.conversations.http; import android.app.PendingIntent; +import android.content.Intent; +import android.net.Uri; import android.util.Log; +import org.bouncycastle.crypto.engines.AESEngine; +import org.bouncycastle.crypto.io.CipherInputStream; +import org.bouncycastle.crypto.modes.AEADBlockCipher; +import org.bouncycastle.crypto.modes.GCMBlockCipher; +import org.bouncycastle.crypto.params.AEADParameters; +import org.bouncycastle.crypto.params.KeyParameter; + +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -43,7 +53,7 @@ public class HttpUploadConnection implements Transferable { private byte[] key = null; private long transmitted = 0; - private long expected = 1; + private int expected = 1; public HttpUploadConnection(HttpConnectionManager httpConnectionManager) { this.mHttpConnectionManager = httpConnectionManager; @@ -142,14 +152,21 @@ public class HttpUploadConnection implements Transferable { if (connection instanceof HttpsURLConnection) { mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, true); } + if (file.getKey() != null) { + AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine()); + cipher.init(true, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv())); + expected = cipher.getOutputSize((int) file.getSize()); + is = new CipherInputStream(new FileInputStream(file), cipher); + } else { + expected = (int) file.getSize(); + is = new FileInputStream(file); + } connection.setRequestMethod("PUT"); - connection.setFixedLengthStreamingMode((int) file.getExpectedSize()); + connection.setFixedLengthStreamingMode(expected); connection.setDoOutput(true); connection.connect(); os = connection.getOutputStream(); - is = file.createInputStream(); transmitted = 0; - expected = file.getExpectedSize(); int count = -1; byte[] buffer = new byte[4096]; while (((count = is.read(buffer)) != -1) && !canceled) { @@ -163,11 +180,13 @@ public class HttpUploadConnection implements Transferable { int code = connection.getResponseCode(); if (code == 200 || code == 201) { Log.d(Config.LOGTAG, "finished uploading file"); - Message.FileParams params = message.getFileParams(); if (key != null) { mGetUrl = new URL(mGetUrl.toString() + "#" + CryptoHelper.bytesToHex(key)); } mXmppConnectionService.getFileBackend().updateFileParams(message, mGetUrl); + Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); + intent.setData(Uri.fromFile(file)); + mXmppConnectionService.sendBroadcast(intent); message.setTransferable(null); message.setCounterpart(message.getConversation().getJid().toBareJid()); if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) { @@ -188,12 +207,13 @@ public class HttpUploadConnection implements Transferable { } }); } else { - mXmppConnectionService.resendMessage(message,delayed); + mXmppConnectionService.resendMessage(message, delayed); } } else { fail(); } } catch (IOException e) { + e.printStackTrace(); Log.d(Config.LOGTAG,"http upload failed "+e.getMessage()); fail(); } finally { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java index 6f31f1639..11055b8f4 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java @@ -93,7 +93,7 @@ public class JingleInbandTransport extends JingleTransport { digest.reset(); file.getParentFile().mkdirs(); file.createNewFile(); - this.fileOutputStream = file.createOutputStream(); + this.fileOutputStream = createOutputStream(file); if (this.fileOutputStream == null) { Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not create output stream"); callback.onFileTransferAborted(); @@ -120,7 +120,7 @@ public class JingleInbandTransport extends JingleTransport { this.fileSize = this.remainingSize; this.digest = MessageDigest.getInstance("SHA-1"); this.digest.reset(); - fileInputStream = this.file.createInputStream(); + fileInputStream = createInputStream(this.file); if (fileInputStream == null) { Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could no create input stream"); callback.onFileTransferAborted(); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java index 8d74f44e3..55039070b 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java @@ -106,7 +106,7 @@ public class JingleSocks5Transport extends JingleTransport { try { MessageDigest digest = MessageDigest.getInstance("SHA-1"); digest.reset(); - fileInputStream = file.createInputStream(); + fileInputStream = createInputStream(file); //file.createInputStream(); if (fileInputStream == null) { Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create input stream"); callback.onFileTransferAborted(); @@ -157,7 +157,7 @@ public class JingleSocks5Transport extends JingleTransport { socket.setSoTimeout(30000); file.getParentFile().mkdirs(); file.createNewFile(); - fileOutputStream = file.createOutputStream(); + fileOutputStream = createOutputStream(file); if (fileOutputStream == null) { callback.onFileTransferAborted(); Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create output stream"); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleTransport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleTransport.java index e832d3f58..1219794fd 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleTransport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleTransport.java @@ -1,5 +1,24 @@ package eu.siacs.conversations.xmpp.jingle; +import android.util.Log; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.CipherOutputStream; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import eu.siacs.conversations.Config; import eu.siacs.conversations.entities.DownloadableFile; public abstract class JingleTransport { @@ -12,4 +31,58 @@ public abstract class JingleTransport { final OnFileTransmissionStatusChanged callback); public abstract void disconnect(); + + protected InputStream createInputStream(DownloadableFile file) { + FileInputStream is; + try { + is = new FileInputStream(file); + if (file.getKey() == null) { + return is; + } + } catch (FileNotFoundException e) { + return null; + } + try { + IvParameterSpec ips = new IvParameterSpec(file.getIv()); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(file.getKey(), "AES"), ips); + Log.d(Config.LOGTAG, "opening encrypted input stream"); + return new CipherInputStream(is, cipher); + } catch (InvalidKeyException e) { + return null; + } catch (NoSuchAlgorithmException e) { + return null; + } catch (NoSuchPaddingException e) { + return null; + } catch (InvalidAlgorithmParameterException e) { + return null; + } + } + + protected OutputStream createOutputStream(DownloadableFile file) { + FileOutputStream os; + try { + os = new FileOutputStream(file); + if (file.getKey() == null) { + return os; + } + } catch (FileNotFoundException e) { + return null; + } + try { + IvParameterSpec ips = new IvParameterSpec(file.getIv()); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(file.getKey(), "AES"), ips); + Log.d(Config.LOGTAG, "opening encrypted output stream"); + return new CipherOutputStream(os, cipher); + } catch (InvalidKeyException e) { + return null; + } catch (NoSuchAlgorithmException e) { + return null; + } catch (NoSuchPaddingException e) { + return null; + } catch (InvalidAlgorithmParameterException e) { + return null; + } + } } -- cgit v1.2.3 From 1ed550b5c54ff873375c30fd94e4f20523f15187 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 30 Jul 2015 12:40:50 +0200 Subject: fixed some colors in message adapter --- .../eu/siacs/conversations/ui/XmppActivity.java | 2 +- .../conversations/ui/adapter/MessageAdapter.java | 56 ++++++++++++---------- src/main/res/layout/message_received.xml | 1 - src/main/res/layout/message_sent.xml | 1 - src/main/res/values/colors.xml | 1 + 5 files changed, 33 insertions(+), 28 deletions(-) (limited to 'src/main') diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 3093db467..64ead283e 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -351,7 +351,7 @@ public abstract class XmppActivity extends Activity { mPrimaryTextColor = getResources().getColor(R.color.black87); mSecondaryTextColor = getResources().getColor(R.color.black54); mTertiaryTextColor = getResources().getColor(R.color.black12); - mColorRed = getResources().getColor(R.color.red500); + mColorRed = getResources().getColor(R.color.red800); mColorOrange = getResources().getColor(R.color.orange500); mColorGreen = getResources().getColor(R.color.green500); mPrimaryColor = getResources().getColor(R.color.green500); diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index 2bd89ccb8..4d1260e83 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -96,19 +96,15 @@ public class MessageAdapter extends ArrayAdapter { return this.getItemViewType(getItem(position)); } - private int getMessageTextColor(Message message) { - int type = this.getItemViewType(message); - + private int getMessageTextColor(int type, boolean primary) { if (type == SENT) { - return activity.getResources().getColor(R.color.black87); - } else if (type == RECEIVED) { - return activity.getResources().getColor(R.color.white); + return activity.getResources().getColor(primary ? R.color.black87 : R.color.black54); + } else { + return activity.getResources().getColor(primary ? R.color.white : R.color.white70); } - - return activity.getPrimaryTextColor(); } - private void displayStatus(ViewHolder viewHolder, Message message) { + private void displayStatus(ViewHolder viewHolder, Message message, int type) { String filesize = null; String info = null; boolean error = false; @@ -163,10 +159,10 @@ public class MessageAdapter extends ArrayAdapter { } break; } - if (error) { + if (error && type == SENT) { viewHolder.time.setTextColor(activity.getWarningTextColor()); } else { - viewHolder.time.setTextColor(this.getMessageTextColor(message)); + viewHolder.time.setTextColor(this.getMessageTextColor(type,false)); } if (message.getEncryption() == Message.ENCRYPTION_NONE) { viewHolder.indicator.setVisibility(View.GONE); @@ -178,12 +174,23 @@ public class MessageAdapter extends ArrayAdapter { message.getAxolotlFingerprint()); if(trust == null || trust != SQLiteAxolotlStore.Trust.TRUSTED) { - viewHolder.indicator.setColorFilter(Color.RED); + viewHolder.indicator.setColorFilter(activity.getWarningTextColor()); + viewHolder.indicator.setAlpha(1.0f); } else { viewHolder.indicator.clearColorFilter(); + if (type == SENT) { + viewHolder.indicator.setAlpha(0.57f); + } else { + viewHolder.indicator.setAlpha(0.7f); + } } } else { viewHolder.indicator.clearColorFilter(); + if (type == SENT) { + viewHolder.indicator.setAlpha(0.57f); + } else { + viewHolder.indicator.setAlpha(0.7f); + } } } @@ -216,19 +223,19 @@ public class MessageAdapter extends ArrayAdapter { } } - private void displayInfoMessage(ViewHolder viewHolder, String text) { + private void displayInfoMessage(ViewHolder viewHolder, String text, int type) { if (viewHolder.download_button != null) { viewHolder.download_button.setVisibility(View.GONE); } viewHolder.image.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.VISIBLE); viewHolder.messageBody.setText(text); - viewHolder.messageBody.setTextColor(activity.getSecondaryTextColor()); + viewHolder.messageBody.setTextColor(getMessageTextColor(type,false)); viewHolder.messageBody.setTypeface(null, Typeface.ITALIC); viewHolder.messageBody.setTextIsSelectable(false); } - private void displayDecryptionFailed(ViewHolder viewHolder) { + private void displayDecryptionFailed(ViewHolder viewHolder, int type) { if (viewHolder.download_button != null) { viewHolder.download_button.setVisibility(View.GONE); } @@ -236,7 +243,7 @@ public class MessageAdapter extends ArrayAdapter { viewHolder.messageBody.setVisibility(View.VISIBLE); viewHolder.messageBody.setText(getContext().getString( R.string.decryption_failed)); - viewHolder.messageBody.setTextColor(activity.getWarningTextColor()); + viewHolder.messageBody.setTextColor(getMessageTextColor(type,false)); viewHolder.messageBody.setTypeface(null, Typeface.NORMAL); viewHolder.messageBody.setTextIsSelectable(false); } @@ -254,7 +261,7 @@ public class MessageAdapter extends ArrayAdapter { viewHolder.messageBody.setText(span); } - private void displayTextMessage(final ViewHolder viewHolder, final Message message) { + private void displayTextMessage(final ViewHolder viewHolder, final Message message, int type) { if (viewHolder.download_button != null) { viewHolder.download_button.setVisibility(View.GONE); } @@ -312,7 +319,7 @@ public class MessageAdapter extends ArrayAdapter { } else { viewHolder.messageBody.setText(""); } - viewHolder.messageBody.setTextColor(this.getMessageTextColor(message)); + viewHolder.messageBody.setTextColor(this.getMessageTextColor(type,true)); viewHolder.messageBody.setTypeface(null, Typeface.NORMAL); viewHolder.messageBody.setTextIsSelectable(true); } @@ -521,7 +528,7 @@ public class MessageAdapter extends ArrayAdapter { } else if (transferable.getStatus() == Transferable.STATUS_OFFER_CHECK_FILESIZE) { displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message))); } else { - displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first); + displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first,type); } } else if (message.getType() == Message.TYPE_IMAGE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) { displayImageMessage(viewHolder, message); @@ -533,10 +540,9 @@ public class MessageAdapter extends ArrayAdapter { } } else if (message.getEncryption() == Message.ENCRYPTION_PGP) { if (activity.hasPgp()) { - displayInfoMessage(viewHolder,activity.getString(R.string.encrypted_message)); + displayInfoMessage(viewHolder,activity.getString(R.string.encrypted_message),type); } else { - displayInfoMessage(viewHolder, - activity.getString(R.string.install_openkeychain)); + displayInfoMessage(viewHolder,activity.getString(R.string.install_openkeychain),type); if (viewHolder != null) { viewHolder.message_box .setOnClickListener(new OnClickListener() { @@ -549,7 +555,7 @@ public class MessageAdapter extends ArrayAdapter { } } } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) { - displayDecryptionFailed(viewHolder); + displayDecryptionFailed(viewHolder,type); } else { if (GeoHelper.isGeoUri(message.getBody())) { displayLocationMessage(viewHolder,message); @@ -558,7 +564,7 @@ public class MessageAdapter extends ArrayAdapter { } else if (message.treatAsDownloadable() == Message.Decision.MUST) { displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message))); } else { - displayTextMessage(viewHolder, message); + displayTextMessage(viewHolder, message, type); } } @@ -592,7 +598,7 @@ public class MessageAdapter extends ArrayAdapter { } } - displayStatus(viewHolder, message); + displayStatus(viewHolder, message, type); return view; } diff --git a/src/main/res/layout/message_received.xml b/src/main/res/layout/message_received.xml index 1919c0753..f78b82cbe 100644 --- a/src/main/res/layout/message_received.xml +++ b/src/main/res/layout/message_received.xml @@ -89,7 +89,6 @@ android:gravity="center_vertical" android:text="@string/sending" android:textColor="@color/white70" - android:alpha="0.70" android:textSize="?attr/TextSizeInfo" /> diff --git a/src/main/res/layout/message_sent.xml b/src/main/res/layout/message_sent.xml index 24750c9ba..553689298 100644 --- a/src/main/res/layout/message_sent.xml +++ b/src/main/res/layout/message_sent.xml @@ -80,7 +80,6 @@ android:gravity="center_vertical" android:text="@string/sending" android:textColor="@color/black54" - android:alpha="0.54" android:textSize="?attr/TextSizeInfo" /> #ffeeeeee #ff424242 #fff44336 + #ffc62828 #ffff9800 \ No newline at end of file -- cgit v1.2.3 From 74ab36fda2d977e1b97b755a8298c488e6d74ba2 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Thu, 30 Jul 2015 19:16:58 +0200 Subject: Fix session logic: enforce same type of encryption --- .../eu/siacs/conversations/entities/Message.java | 31 ++++++++++++++++++++++ .../conversations/ui/adapter/MessageAdapter.java | 28 +++---------------- 2 files changed, 34 insertions(+), 25 deletions(-) (limited to 'src/main') diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index 99b78d14f..e66877012 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -709,4 +709,35 @@ public class Message extends AbstractEntity { return conversation.getAccount().getAxolotlService().getFingerprintTrust(axolotlFingerprint) == SQLiteAxolotlStore.Trust.TRUSTED; } + + private int getPreviousEncryption() { + for (Message iterator = this.prev(); iterator != null; iterator = iterator.prev()){ + if( iterator.isCarbon() || iterator.getStatus() == STATUS_RECEIVED ) { + continue; + } + return iterator.getEncryption(); + } + return ENCRYPTION_NONE; + } + + private int getNextEncryption() { + for (Message iterator = this.next(); iterator != null; iterator = iterator.next()){ + if( iterator.isCarbon() || iterator.getStatus() == STATUS_RECEIVED ) { + continue; + } + return iterator.getEncryption(); + } + return conversation.getNextEncryption(false); + } + + public boolean isValidInSession() { + int pastEncryption = this.getPreviousEncryption(); + int futureEncryption = this.getNextEncryption(); + + boolean inUnencryptedSession = pastEncryption == ENCRYPTION_NONE + || futureEncryption == ENCRYPTION_NONE + || pastEncryption != futureEncryption; + + return inUnencryptedSession || this.getEncryption() == pastEncryption; + } } diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index 4d1260e83..76da42c4c 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -569,32 +569,10 @@ public class MessageAdapter extends ArrayAdapter { } if (type == RECEIVED) { - boolean wasEncrypted = false; - for (Message iterator = message.prev(); iterator != null; iterator = iterator.prev()){ - if (iterator.getEncryption() != Message.ENCRYPTION_NONE) { - wasEncrypted = true; - break; - } - if (!iterator.isCarbon() && iterator.getType() == SENT) { - break; - } - } - boolean willBeEncrypted = conversation.getNextEncryption(false) != Message.ENCRYPTION_NONE; - for (Message iterator = message.next(); iterator != null; iterator = iterator.next()){ - if (iterator.getEncryption() != Message.ENCRYPTION_NONE) { - willBeEncrypted = true; - break; - } - if (!iterator.isCarbon() && iterator.getType() == SENT) { - break; - } - } - - if ( willBeEncrypted && wasEncrypted - && message.getEncryption() == Message.ENCRYPTION_NONE) { - viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received_warning); - } else { + if(message.isValidInSession()) { viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received); + } else { + viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received_warning); } } -- cgit v1.2.3 From 5191e4ce95031b83353ec9ceacbbd57f53f01c6a Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 31 Jul 2015 00:51:30 +0200 Subject: removed expert setting to 'force encryption'. merged certifcate and encryption settings into same category --- src/main/res/values/strings.xml | 1 - src/main/res/xml/preferences.xml | 157 +++++++++++++++++++-------------------- 2 files changed, 75 insertions(+), 83 deletions(-) (limited to 'src/main') diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index e07c72fe9..e0e472bf9 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -474,7 +474,6 @@ Received location Conversation closed Left conference - Certificate options Don’t trust system CAs All certificates must be manually approved Remove certificates diff --git a/src/main/res/xml/preferences.xml b/src/main/res/xml/preferences.xml index 13598cb6f..8ab35c098 100644 --- a/src/main/res/xml/preferences.xml +++ b/src/main/res/xml/preferences.xml @@ -1,12 +1,12 @@ - + - + + android:title="@string/pref_grant_presence_updates"/> + android:title="@string/pref_xmpp_resource"/> + android:title="@string/pref_accept_files"/> + android:title="@string/pref_confirm_messages"/> + android:title="@string/pref_chat_states"/> + android:key="notifications" + android:title="@string/pref_notification_settings"> - + + android:title="@string/title_pref_quiet_hours"> + android:defaultValue="false" + android:key="enable_quiet_hours" + android:summary="@string/pref_quiet_hours_summary" + android:title="@string/title_pref_enable_quiet_hours"/> + android:dependency="enable_quiet_hours" + android:key="quiet_hours_start" + android:negativeButtonText="@string/cancel" + android:positiveButtonText="@string/set" + android:title="@string/title_pref_quiet_hours_start_time"/> - - + + + android:title="@string/pref_vibrate"/> + android:title="@string/pref_sound"/> + android:title="@string/pref_conference_notifications"/> - + + android:title="@string/pref_conference_name"/> + android:title="@string/pref_use_larger_font"/> + android:title="@string/pref_use_send_button_to_indicate_status"/> + android:title="@string/pref_quick_action"/> + android:title="@string/pref_show_dynamic_tags"/> + android:key="advanced" + android:title="@string/pref_advanced_options"> - - + android:title="@string/pref_expert_options"> + + android:title="@string/pref_dont_save_encrypted"/> + + + + + + - - - - - - - - - + + android:title="@string/pref_use_indicate_received"/> + android:summary="@string/pref_keep_foreground_service_summary" + android:title="@string/pref_keep_foreground_service"/> @@ -179,9 +172,9 @@ android:defaultValue="false" android:key="never_send" android:summary="@string/pref_never_send_crash_summary" - android:title="@string/pref_never_send_crash" /> + android:title="@string/pref_never_send_crash"/> - + -- cgit v1.2.3 From 658919f239e9c816f958d17df9ba5ee79b6549b2 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 31 Jul 2015 00:52:46 +0200 Subject: improved 'next encryption' selection --- .../siacs/conversations/entities/Conversation.java | 63 ++++++++++++---------- .../eu/siacs/conversations/entities/Message.java | 2 +- .../siacs/conversations/parser/MessageParser.java | 2 - .../services/XmppConnectionService.java | 20 +++---- .../conversations/ui/ConversationActivity.java | 23 +++----- .../conversations/ui/ConversationFragment.java | 32 ++++++----- 6 files changed, 69 insertions(+), 73 deletions(-) (limited to 'src/main') diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index 6d99e3588..9f9f34cf8 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -549,42 +549,51 @@ public class Conversation extends AbstractEntity implements Blockable { return this.nextCounterpart; } - public int getLatestEncryption() { - int latestEncryption = this.getLatestMessage().getEncryption(); - if ((latestEncryption == Message.ENCRYPTION_DECRYPTED) - || (latestEncryption == Message.ENCRYPTION_DECRYPTION_FAILED)) { - return Message.ENCRYPTION_PGP; - } else { - return latestEncryption; + private int getMostRecentlyUsedOutgoingEncryption() { + synchronized (this.messages) { + for(int i = this.messages.size() -1; i >= 0; --i) { + final Message m = this.messages.get(0); + if (!m.isCarbon() && m.getStatus() != Message.STATUS_RECEIVED) { + final int e = m.getEncryption(); + if (e == Message.ENCRYPTION_DECRYPTED || e == Message.ENCRYPTION_DECRYPTION_FAILED) { + return Message.ENCRYPTION_PGP; + } else { + return e; + } + } + } } + return Message.ENCRYPTION_NONE; } - public int getNextEncryption(boolean force) { - int next = this.getIntAttribute(ATTRIBUTE_NEXT_ENCRYPTION, -1); - if (next == -1) { - int latest = this.getLatestEncryption(); - if (latest == Message.ENCRYPTION_NONE) { - if (force && getMode() == MODE_SINGLE) { - return Message.ENCRYPTION_OTR; - } else if (getContact().getPresences().size() == 1) { - if (getContact().getOtrFingerprints().size() >= 1) { - return Message.ENCRYPTION_OTR; + private int getMostRecentlyUsedIncomingEncryption() { + synchronized (this.messages) { + for(int i = this.messages.size() -1; i >= 0; --i) { + final Message m = this.messages.get(0); + if (m.getStatus() == Message.STATUS_RECEIVED) { + final int e = m.getEncryption(); + if (e == Message.ENCRYPTION_DECRYPTED || e == Message.ENCRYPTION_DECRYPTION_FAILED) { + return Message.ENCRYPTION_PGP; } else { - return latest; + return e; } - } else { - return latest; } - } else { - return latest; } } - if (next == Message.ENCRYPTION_NONE && force - && getMode() == MODE_SINGLE) { - return Message.ENCRYPTION_OTR; - } else { - return next; + return Message.ENCRYPTION_NONE; + } + + public int getNextEncryption() { + int next = this.getIntAttribute(ATTRIBUTE_NEXT_ENCRYPTION, -1); + if (next == -1) { + int outgoing = this.getMostRecentlyUsedOutgoingEncryption(); + if (outgoing == Message.ENCRYPTION_NONE) { + return this.getMostRecentlyUsedIncomingEncryption(); + } else { + return outgoing; + } } + return next; } public void setNextEncryption(int encryption) { diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index e66877012..6c2a1cc03 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -727,7 +727,7 @@ public class Message extends AbstractEntity { } return iterator.getEncryption(); } - return conversation.getNextEncryption(false); + return conversation.getNextEncryption(); } public boolean isValidInSession() { diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 3aaa3ce74..4bee34a99 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -73,11 +73,9 @@ public class MessageParser extends AbstractParser implements body = otrSession.transformReceiving(body); SessionStatus status = otrSession.getSessionStatus(); if (body == null && status == SessionStatus.ENCRYPTED) { - conversation.setNextEncryption(Message.ENCRYPTION_OTR); mXmppConnectionService.onOtrSessionEstablished(conversation); return null; } else if (body == null && status == SessionStatus.FINISHED) { - conversation.setNextEncryption(Message.ENCRYPTION_NONE); conversation.resetOtrSession(); mXmppConnectionService.updateConversationUi(); return null; diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index cadcf8512..13b810905 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -349,7 +349,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa public void attachLocationToConversation(final Conversation conversation, final Uri uri, final UiCallback callback) { - int encryption = conversation.getNextEncryption(forceEncryption()); + int encryption = conversation.getNextEncryption(); if (encryption == Message.ENCRYPTION_PGP) { encryption = Message.ENCRYPTION_DECRYPTED; } @@ -368,12 +368,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa final Uri uri, final UiCallback callback) { final Message message; - if (conversation.getNextEncryption(forceEncryption()) == Message.ENCRYPTION_PGP) { - message = new Message(conversation, "", - Message.ENCRYPTION_DECRYPTED); + if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { + message = new Message(conversation, "", Message.ENCRYPTION_DECRYPTED); } else { - message = new Message(conversation, "", - conversation.getNextEncryption(forceEncryption())); + message = new Message(conversation, "", conversation.getNextEncryption()); } message.setCounterpart(conversation.getNextCounterpart()); message.setType(Message.TYPE_FILE); @@ -409,12 +407,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa public void attachImageToConversation(final Conversation conversation, final Uri uri, final UiCallback callback) { final Message message; - if (conversation.getNextEncryption(forceEncryption()) == Message.ENCRYPTION_PGP) { - message = new Message(conversation, "", - Message.ENCRYPTION_DECRYPTED); + if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { + message = new Message(conversation, "", Message.ENCRYPTION_DECRYPTED); } else { - message = new Message(conversation, "", - conversation.getNextEncryption(forceEncryption())); + message = new Message(conversation, "",conversation.getNextEncryption()); } message.setCounterpart(conversation.getNextCounterpart()); message.setType(Message.TYPE_IMAGE); @@ -424,7 +420,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa public void run() { try { getFileBackend().copyImageToPrivateStorage(message, uri); - if (conversation.getNextEncryption(forceEncryption()) == Message.ENCRYPTION_PGP) { + if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { getPgpEngine().encrypt(message, callback); } else { callback.success(message); diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index 147ae824f..daf22122a 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -385,7 +385,7 @@ public class ConversationActivity extends XmppActivity } else { menuAdd.setVisible(!isConversationsOverviewHideable()); if (this.getSelectedConversation() != null) { - if (this.getSelectedConversation().getNextEncryption(forceEncryption()) != Message.ENCRYPTION_NONE) { + if (this.getSelectedConversation().getNextEncryption() != Message.ENCRYPTION_NONE) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { menuSecure.setIcon(R.drawable.ic_lock_white_24dp); } else { @@ -498,7 +498,7 @@ public class ConversationActivity extends XmppActivity break; } final Conversation conversation = getSelectedConversation(); - final int encryption = conversation.getNextEncryption(forceEncryption()); + final int encryption = conversation.getNextEncryption(); if (encryption == Message.ENCRYPTION_PGP) { if (hasPgp()) { if (conversation.getContact().getPgpKeyId() != 0) { @@ -787,15 +787,10 @@ public class ConversationActivity extends XmppActivity if (conversation.getMode() == Conversation.MODE_MULTI) { otr.setEnabled(false); axolotl.setEnabled(false); - } else { - if (forceEncryption()) { - none.setVisible(false); - } - } - if (!conversation.getAccount().getAxolotlService().isContactAxolotlCapable(conversation.getContact())) { + } else if (!conversation.getAccount().getAxolotlService().isContactAxolotlCapable(conversation.getContact())) { axolotl.setEnabled(false); } - switch (conversation.getNextEncryption(forceEncryption())) { + switch (conversation.getNextEncryption()) { case Message.ENCRYPTION_NONE: none.setChecked(true); break; @@ -806,8 +801,7 @@ public class ConversationActivity extends XmppActivity pgp.setChecked(true); break; case Message.ENCRYPTION_AXOLOTL: - popup.getMenu().findItem(R.id.encryption_choice_axolotl) - .setChecked(true); + axolotl.setChecked(true); break; default: none.setChecked(true); @@ -820,8 +814,7 @@ public class ConversationActivity extends XmppActivity protected void muteConversationDialog(final Conversation conversation) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.disable_notifications); - final int[] durations = getResources().getIntArray( - R.array.mute_options_durations); + final int[] durations = getResources().getIntArray(R.array.mute_options_durations); builder.setItems(R.array.mute_options_descriptions, new OnClickListener() { @@ -1253,10 +1246,6 @@ public class ConversationActivity extends XmppActivity }); } - public boolean forceEncryption() { - return getPreferences().getBoolean("force_encryption", false); - } - public boolean useSendButtonToIndicateStatus() { return getPreferences().getBoolean("send_button_status", false); } diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 47c6a764d..88504a922 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -293,23 +293,27 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (body.length() == 0 || this.conversation == null) { return; } - Message message = new Message(conversation, body, conversation.getNextEncryption(activity.forceEncryption())); + Message message = new Message(conversation, body, conversation.getNextEncryption()); if (conversation.getMode() == Conversation.MODE_MULTI) { if (conversation.getNextCounterpart() != null) { message.setCounterpart(conversation.getNextCounterpart()); message.setType(Message.TYPE_PRIVATE); } } - if (conversation.getNextEncryption(activity.forceEncryption()) == Message.ENCRYPTION_OTR) { - sendOtrMessage(message); - } else if (conversation.getNextEncryption(activity.forceEncryption()) == Message.ENCRYPTION_PGP) { - sendPgpMessage(message); - } else if (conversation.getNextEncryption(activity.forceEncryption()) == Message.ENCRYPTION_AXOLOTL) { - if(!activity.trustKeysIfNeeded(ConversationActivity.REQUEST_TRUST_KEYS_TEXT)) { - sendAxolotlMessage(message); - } - } else { - sendPlainTextMessage(message); + switch (conversation.getNextEncryption()) { + case Message.ENCRYPTION_OTR: + sendOtrMessage(message); + break; + case Message.ENCRYPTION_PGP: + sendPgpMessage(message); + break; + case Message.ENCRYPTION_AXOLOTL: + if(!activity.trustKeysIfNeeded(ConversationActivity.REQUEST_TRUST_KEYS_TEXT)) { + sendAxolotlMessage(message); + } + break; + default: + sendPlainTextMessage(message); } } @@ -320,7 +324,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa R.string.send_private_message_to, conversation.getNextCounterpart().getResourcepart())); } else { - switch (conversation.getNextEncryption(activity.forceEncryption())) { + switch (conversation.getNextEncryption()) { case Message.ENCRYPTION_NONE: mEditMessage .setHint(getString(R.string.send_plain_text_message)); @@ -1211,11 +1215,11 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa if (resultCode == Activity.RESULT_OK) { if (requestCode == ConversationActivity.REQUEST_TRUST_KEYS_TEXT) { final String body = mEditMessage.getText().toString(); - Message message = new Message(conversation, body, conversation.getNextEncryption(activity.forceEncryption())); + Message message = new Message(conversation, body, conversation.getNextEncryption()); sendAxolotlMessage(message); } else if (requestCode == ConversationActivity.REQUEST_TRUST_KEYS_MENU) { int choice = data.getIntExtra("choice", ConversationActivity.ATTACHMENT_CHOICE_INVALID); - activity.selectPresenceToAttachFile(choice, conversation.getNextEncryption(activity.forceEncryption())); + activity.selectPresenceToAttachFile(choice, conversation.getNextEncryption()); } } } -- cgit v1.2.3 From 26ac7c90306a566850139a3038a45402a964fcfd Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 31 Jul 2015 13:08:35 +0200 Subject: added missing carbon column message table create statement fixes #1310 --- src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java | 1 + 1 file changed, 1 insertion(+) (limited to 'src/main') diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index b3fce845a..8ac2884e7 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -140,6 +140,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { + Message.RELATIVE_FILE_PATH + " TEXT, " + Message.SERVER_MSG_ID + " TEXT, " + Message.FINGERPRINT + " TEXT, " + + Message.CARBON + " INTEGER, " + Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY(" + Message.CONVERSATION + ") REFERENCES " + Conversation.TABLENAME + "(" + Conversation.UUID -- cgit v1.2.3 From 5c421da1e1bd4c6008c36b17569c3dbc5d5e918b Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Fri, 31 Jul 2015 17:59:41 +0200 Subject: Change to new wire protocol version --- .../crypto/axolotl/AxolotlService.java | 27 ++-- .../crypto/axolotl/OnMessageCreatedCallback.java | 5 + .../crypto/axolotl/XmppAxolotlMessage.java | 143 ++++++++++++++------- .../crypto/axolotl/XmppAxolotlSession.java | 8 +- .../siacs/conversations/parser/MessageParser.java | 2 +- 5 files changed, 112 insertions(+), 73 deletions(-) create mode 100644 src/main/java/eu/siacs/conversations/crypto/axolotl/OnMessageCreatedCallback.java (limited to 'src/main') 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 d1bfe2d42..e44208987 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -583,23 +583,15 @@ public class AxolotlService { if(findSessionsforContact(message.getContact()).isEmpty()) { return null; } - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building axolotl foreign headers..."); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building axolotl foreign keyElements..."); for (XmppAxolotlSession session : findSessionsforContact(message.getContact())) { Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account)+session.getRemoteAddress().toString()); - //if(!session.isTrusted()) { - // TODO: handle this properly - // continue; - // } - axolotlMessage.addHeader(session.processSending(axolotlMessage.getInnerKey())); + axolotlMessage.addKeyElement(session.processSending(axolotlMessage.getInnerKey())); } - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building axolotl own headers..."); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building axolotl own keyElements..."); for (XmppAxolotlSession session : findOwnSessions()) { Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account)+session.getRemoteAddress().toString()); - // if(!session.isTrusted()) { - // TODO: handle this properly - // continue; - // } - axolotlMessage.addHeader(session.processSending(axolotlMessage.getInnerKey())); + axolotlMessage.addKeyElement(session.processSending(axolotlMessage.getInnerKey())); } return axolotlMessage; @@ -651,7 +643,6 @@ public class AxolotlService { XmppAxolotlSession session = sessions.get(senderAddress); if (session == null) { Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Account: "+account.getJid()+" No axolotl session found while parsing received message " + message); - // TODO: handle this properly IdentityKey identityKey = axolotlStore.loadSession(senderAddress).getSessionState().getRemoteIdentityKey(); if ( identityKey != null ) { session = new XmppAxolotlSession(account, axolotlStore, senderAddress, identityKey.getFingerprint().replaceAll("\\s", "")); @@ -661,12 +652,12 @@ public class AxolotlService { newSession = true; } - for (XmppAxolotlMessage.XmppAxolotlMessageHeader header : message.getHeaders()) { - if (header.getRecipientDeviceId() == getOwnDeviceId()) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Found axolotl header matching own device ID, processing..."); - byte[] payloadKey = session.processReceiving(header); + for (XmppAxolotlMessage.XmppAxolotlKeyElement keyElement : message.getKeyElements()) { + if (keyElement.getRecipientDeviceId() == getOwnDeviceId()) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Found axolotl keyElement matching own device ID, processing..."); + byte[] payloadKey = session.processReceiving(keyElement); if (payloadKey != null) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Got payload key from axolotl header. Decrypting message..."); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Got payload key from axolotl keyElement. Decrypting message..."); try{ plaintextMessage = message.decrypt(session, payloadKey, session.getFingerprint()); } catch (CryptoFailedException e) { diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/OnMessageCreatedCallback.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/OnMessageCreatedCallback.java new file mode 100644 index 000000000..3d40a4089 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/OnMessageCreatedCallback.java @@ -0,0 +1,5 @@ +package eu.siacs.conversations.crypto.axolotl; + +public interface OnMessageCreatedCallback { + void run(XmppAxolotlMessage message); +} 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 182c8f128..4725ce8aa 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java @@ -2,6 +2,7 @@ package eu.siacs.conversations.crypto.axolotl; import android.support.annotation.Nullable; import android.util.Base64; +import android.util.Log; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; @@ -20,32 +21,46 @@ import javax.crypto.SecretKey; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; +import eu.siacs.conversations.Config; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.jid.Jid; public class XmppAxolotlMessage { + public static final String TAGNAME = "encrypted"; + public static final String HEADER = "header"; + public static final String SOURCEID = "sid"; + public static final String IVTAG = "iv"; + public static final String PAYLOAD = "payload"; + + private static final String KEYTYPE = "AES"; + private static final String CIPHERMODE = "AES/GCM/NoPadding"; + private static final String PROVIDER = "BC"; + private byte[] innerKey; - private byte[] ciphertext; - private byte[] iv; - private final Set headers; + private byte[] ciphertext = null; + private byte[] iv = null; + private final Set keyElements; private final Jid from; private final int sourceDeviceId; - public static class XmppAxolotlMessageHeader { + public static class XmppAxolotlKeyElement { + public static final String TAGNAME = "key"; + public static final String REMOTEID = "rid"; + private final int recipientDeviceId; private final byte[] content; - public XmppAxolotlMessageHeader(int deviceId, byte[] content) { + public XmppAxolotlKeyElement(int deviceId, byte[] content) { this.recipientDeviceId = deviceId; this.content = content; } - public XmppAxolotlMessageHeader(Element header) { - if("header".equals(header.getName())) { - this.recipientDeviceId = Integer.parseInt(header.getAttribute("rid")); - this.content = Base64.decode(header.getContent(),Base64.DEFAULT); + public XmppAxolotlKeyElement(Element keyElement) { + if(TAGNAME.equals(keyElement.getName())) { + this.recipientDeviceId = Integer.parseInt(keyElement.getAttribute(REMOTEID)); + this.content = Base64.decode(keyElement.getContent(),Base64.DEFAULT); } else { - throw new IllegalArgumentException("Argument not a

Element!"); + throw new IllegalArgumentException("Argument not a <"+TAGNAME+"> Element!"); } } @@ -58,11 +73,10 @@ public class XmppAxolotlMessage { } public Element toXml() { - Element headerElement = new Element("header"); - // TODO: generate XML - headerElement.setAttribute("rid", getRecipientDeviceId()); - headerElement.setContent(Base64.encodeToString(getContents(), Base64.DEFAULT)); - return headerElement; + Element keyElement = new Element(TAGNAME); + keyElement.setAttribute(REMOTEID, getRecipientDeviceId()); + keyElement.setContent(Base64.encodeToString(getContents(), Base64.DEFAULT)); + return keyElement; } } @@ -90,42 +104,69 @@ public class XmppAxolotlMessage { } } - public XmppAxolotlMessage(Jid from, Element axolotlMessage) { + public XmppAxolotlMessage(Jid from, Element axolotlMessage) throws IllegalArgumentException { this.from = from; - this.sourceDeviceId = Integer.parseInt(axolotlMessage.getAttribute("id")); - this.headers = new HashSet<>(); - for(Element child:axolotlMessage.getChildren()) { - switch(child.getName()) { - case "header": - headers.add(new XmppAxolotlMessageHeader(child)); + Element header = axolotlMessage.findChild(HEADER); + this.sourceDeviceId = Integer.parseInt(header.getAttribute(SOURCEID)); + this.keyElements = new HashSet<>(); + for(Element keyElement:header.getChildren()) { + switch(keyElement.getName()) { + case XmppAxolotlKeyElement.TAGNAME: + keyElements.add(new XmppAxolotlKeyElement(keyElement)); break; - case "message": - iv = Base64.decode(child.getAttribute("iv"),Base64.DEFAULT); - ciphertext = Base64.decode(child.getContent(),Base64.DEFAULT); + case IVTAG: + if ( this.iv != null) { + throw new IllegalArgumentException("Duplicate iv entry"); + } + iv = Base64.decode(keyElement.getContent(),Base64.DEFAULT); break; default: + Log.w(Config.LOGTAG, "Unexpected element in header: "+ keyElement.toString()); break; } } + Element payloadElement = axolotlMessage.findChild(PAYLOAD); + if ( payloadElement != null ) { + ciphertext = Base64.decode(payloadElement.getContent(), Base64.DEFAULT); + } } - public XmppAxolotlMessage(Jid from, int sourceDeviceId, String plaintext) throws CryptoFailedException{ + public XmppAxolotlMessage(Jid from, int sourceDeviceId) { this.from = from; this.sourceDeviceId = sourceDeviceId; - this.headers = new HashSet<>(); + this.keyElements = new HashSet<>(); + this.iv = generateIv(); + this.innerKey = generateKey(); + } + + public XmppAxolotlMessage(Jid from, int sourceDeviceId, String plaintext) throws CryptoFailedException{ + this(from, sourceDeviceId); this.encrypt(plaintext); } - private void encrypt(String plaintext) throws CryptoFailedException { + private static byte[] generateKey() { try { - KeyGenerator generator = KeyGenerator.getInstance("AES"); + KeyGenerator generator = KeyGenerator.getInstance(KEYTYPE); generator.init(128); - SecretKey secretKey = generator.generateKey(); - SecureRandom random = new SecureRandom(); - this.iv = new byte[16]; - random.nextBytes(iv); + return generator.generateKey().getEncoded(); + } catch (NoSuchAlgorithmException e) { + Log.e(Config.LOGTAG, e.getMessage()); + return null; + } + } + + private static byte[] generateIv() { + SecureRandom random = new SecureRandom(); + byte[] iv = new byte[16]; + random.nextBytes(iv); + return iv; + } + + private void encrypt(String plaintext) throws CryptoFailedException { + try { + SecretKey secretKey = new SecretKeySpec(innerKey, KEYTYPE); IvParameterSpec ivSpec = new IvParameterSpec(iv); - Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC"); + Cipher cipher = Cipher.getInstance(CIPHERMODE, PROVIDER); cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec); this.innerKey = secretKey.getEncoded(); this.ciphertext = cipher.doFinal(plaintext.getBytes()); @@ -148,13 +189,13 @@ public class XmppAxolotlMessage { return ciphertext; } - public Set getHeaders() { - return headers; + public Set getKeyElements() { + return keyElements; } - public void addHeader(@Nullable XmppAxolotlMessageHeader header) { - if (header != null) { - headers.add(header); + public void addKeyElement(@Nullable XmppAxolotlKeyElement keyElement) { + if (keyElement != null) { + keyElements.add(keyElement); } } @@ -167,16 +208,18 @@ public class XmppAxolotlMessage { } public Element toXml() { - // TODO: generate outer XML, add in header XML - Element message= new Element("axolotl_message", AxolotlService.PEP_PREFIX); - message.setAttribute("id", sourceDeviceId); - for(XmppAxolotlMessageHeader header: headers) { - message.addChild(header.toXml()); + Element encryptionElement= new Element(TAGNAME, AxolotlService.PEP_PREFIX); + Element headerElement = encryptionElement.addChild(HEADER); + headerElement.setAttribute(SOURCEID, sourceDeviceId); + for(XmppAxolotlKeyElement header: keyElements) { + headerElement.addChild(header.toXml()); + } + headerElement.addChild(IVTAG).setContent(Base64.encodeToString(iv, Base64.DEFAULT)); + if ( ciphertext != null ) { + Element payload = encryptionElement.addChild(PAYLOAD); + payload.setContent(Base64.encodeToString(ciphertext, Base64.DEFAULT)); } - Element payload = message.addChild("message"); - payload.setAttribute("iv",Base64.encodeToString(iv, Base64.DEFAULT)); - payload.setContent(Base64.encodeToString(ciphertext,Base64.DEFAULT)); - return message; + return encryptionElement; } @@ -184,8 +227,8 @@ public class XmppAxolotlMessage { XmppAxolotlPlaintextMessage plaintextMessage = null; try { - Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC"); - SecretKeySpec keySpec = new SecretKeySpec(key, "AES"); + Cipher cipher = Cipher.getInstance(CIPHERMODE, PROVIDER); + SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE); IvParameterSpec ivSpec = new IvParameterSpec(iv); cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java index d58b2dd8f..dc4c67779 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java @@ -69,7 +69,7 @@ public class XmppAxolotlSession { } @Nullable - public byte[] processReceiving(XmppAxolotlMessage.XmppAxolotlMessageHeader incomingHeader) { + public byte[] processReceiving(XmppAxolotlMessage.XmppAxolotlKeyElement incomingHeader) { byte[] plaintext = null; SQLiteAxolotlStore.Trust trust = getTrust(); switch (trust) { @@ -117,12 +117,12 @@ public class XmppAxolotlSession { } @Nullable - public XmppAxolotlMessage.XmppAxolotlMessageHeader processSending(@NonNull byte[] outgoingMessage) { + public XmppAxolotlMessage.XmppAxolotlKeyElement processSending(@NonNull byte[] outgoingMessage) { SQLiteAxolotlStore.Trust trust = getTrust(); if (trust == SQLiteAxolotlStore.Trust.TRUSTED) { CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage); - XmppAxolotlMessage.XmppAxolotlMessageHeader header = - new XmppAxolotlMessage.XmppAxolotlMessageHeader(remoteAddress.getDeviceId(), + XmppAxolotlMessage.XmppAxolotlKeyElement header = + new XmppAxolotlMessage.XmppAxolotlKeyElement(remoteAddress.getDeviceId(), ciphertextMessage.serialize()); return header; } else { diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 4bee34a99..47c320f39 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -272,7 +272,7 @@ public class MessageParser extends AbstractParser implements final String body = packet.getBody(); final Element mucUserElement = packet.findChild("x", "http://jabber.org/protocol/muc#user"); final String pgpEncrypted = packet.findChildContent("x", "jabber:x:encrypted"); - final Element axolotlEncrypted = packet.findChild("axolotl_message", AxolotlService.PEP_PREFIX); + final Element axolotlEncrypted = packet.findChild(XmppAxolotlMessage.TAGNAME, AxolotlService.PEP_PREFIX); int status; final Jid counterpart; final Jid to = packet.getTo(); -- cgit v1.2.3 From 50b14434eeda183d1d197a378239654a8db8b3a8 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Fri, 31 Jul 2015 18:05:32 +0200 Subject: Reformat code --- .../crypto/axolotl/AxolotlService.java | 143 +++++++++++---------- .../crypto/axolotl/XmppAxolotlMessage.java | 28 ++-- .../crypto/axolotl/XmppAxolotlSession.java | 2 +- 3 files changed, 87 insertions(+), 86 deletions(-) (limited to 'src/main') 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 e44208987..4cc61b37c 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -126,7 +126,7 @@ public class AxolotlService { private void putDevicesForJid(String bareJid, List 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()); + 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)); } @@ -163,9 +163,9 @@ public class AxolotlService { private static class FetchStatusMap extends AxolotlAddressMap { } - + public static String getLogprefix(Account account) { - return LOGPREFIX+" ("+account.getJid().toBareJid().toString()+"): "; + return LOGPREFIX + " (" + account.getJid().toBareJid().toString() + "): "; } public AxolotlService(Account account, XmppConnectionService connectionService) { @@ -238,7 +238,7 @@ public class AxolotlService { private void setTrustOnSessions(final Jid jid, @NonNull final Set deviceIds, final SQLiteAxolotlStore.Trust from, final SQLiteAxolotlStore.Trust to) { - for(Integer deviceId:deviceIds) { + for (Integer deviceId : deviceIds) { AxolotlAddress address = new AxolotlAddress(jid.toBareJid().toString(), deviceId); XmppAxolotlSession session = sessions.get(address); if (session != null && session.getFingerprint() != null @@ -249,13 +249,13 @@ public class AxolotlService { } public void registerDevices(final Jid jid, @NonNull final Set deviceIds) { - if(jid.toBareJid().equals(account.getJid().toBareJid())) { + 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) { + for (Integer deviceId : deviceIds) { + AxolotlAddress ownDeviceAddress = new AxolotlAddress(jid.toBareJid().toString(), deviceId); + if (sessions.get(ownDeviceAddress) == null) { buildSessionFromPEP(null, ownDeviceAddress, false); } } @@ -276,7 +276,7 @@ public class AxolotlService { Set deviceIds = new HashSet<>(); deviceIds.add(getOwnDeviceId()); IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIds); - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Wiping all other devices from Pep:" + publish); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Wiping all other devices from Pep:" + publish); mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { @@ -286,7 +286,7 @@ public class AxolotlService { } public void purgeKey(IdentityKey identityKey) { - axolotlStore.setFingerprintTrust(identityKey.getFingerprint().replaceAll("\\s",""), SQLiteAxolotlStore.Trust.COMPROMISED); + axolotlStore.setFingerprintTrust(identityKey.getFingerprint().replaceAll("\\s", ""), SQLiteAxolotlStore.Trust.COMPROMISED); } public void publishOwnDeviceIdIfNeeded() { @@ -302,7 +302,7 @@ public class AxolotlService { if (!deviceIds.contains(getOwnDeviceId())) { deviceIds.add(getOwnDeviceId()); IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIds); - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Own device " + getOwnDeviceId() + " not in PEP devicelist. Publishing: " + publish); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Own device " + getOwnDeviceId() + " not in PEP devicelist. Publishing: " + publish); mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { @@ -323,19 +323,19 @@ public class AxolotlService { Map keys = mXmppConnectionService.getIqParser().preKeyPublics(packet); boolean flush = false; if (bundle == null) { - Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Received invalid bundle:" + packet); - bundle = new PreKeyBundle(-1, -1, -1 , null, -1, null, null, null); + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid bundle:" + packet); + bundle = new PreKeyBundle(-1, -1, -1, null, -1, null, null, null); flush = true; } if (keys == null) { - Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Received invalid prekeys:" + packet); + Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid prekeys:" + packet); } try { boolean changed = false; // Validate IdentityKey IdentityKeyPair identityKeyPair = axolotlStore.getIdentityKeyPair(); if (flush || !identityKeyPair.getPublicKey().equals(bundle.getIdentityKey())) { - Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Adding own IdentityKey " + identityKeyPair.getPublicKey() + " to PEP."); + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding own IdentityKey " + identityKeyPair.getPublicKey() + " to PEP."); changed = true; } @@ -344,16 +344,16 @@ public class AxolotlService { int numSignedPreKeys = axolotlStore.loadSignedPreKeys().size(); try { signedPreKeyRecord = axolotlStore.loadSignedPreKey(bundle.getSignedPreKeyId()); - if ( flush - ||!bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey()) + if (flush + || !bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey()) || !Arrays.equals(bundle.getSignedPreKeySignature(), signedPreKeyRecord.getSignature())) { - Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP."); + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP."); signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1); axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord); changed = true; } } catch (InvalidKeyIdException e) { - Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP."); + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP."); signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1); axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord); changed = true; @@ -375,32 +375,32 @@ public class AxolotlService { int newKeys = NUM_KEYS_TO_PUBLISH - preKeyRecords.size(); if (newKeys > 0) { List newRecords = KeyHelper.generatePreKeys( - axolotlStore.getCurrentPreKeyId()+1, newKeys); + axolotlStore.getCurrentPreKeyId() + 1, newKeys); preKeyRecords.addAll(newRecords); for (PreKeyRecord record : newRecords) { axolotlStore.storePreKey(record.getId(), record); } changed = true; - Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Adding " + newKeys + " new preKeys to PEP."); + Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding " + newKeys + " new preKeys to PEP."); } - if(changed) { + if (changed) { IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles( signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(), preKeyRecords, getOwnDeviceId()); - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+ ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing: " + publish); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing: " + publish); mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { // TODO: implement this! - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Published bundle, got: " + packet); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Published bundle, got: " + packet); } }); } } catch (InvalidKeyException e) { - Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage()); - return; + Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage()); + return; } } }); @@ -411,8 +411,9 @@ public class AxolotlService { Jid jid = contact.getJid().toBareJid(); AxolotlAddress address = new AxolotlAddress(jid.toString(), 0); return sessions.hasAny(address) || - ( deviceIds.containsKey(jid) && !deviceIds.get(jid).isEmpty()); + (deviceIds.containsKey(jid) && !deviceIds.get(jid).isEmpty()); } + public SQLiteAxolotlStore.Trust getFingerprintTrust(String fingerprint) { return axolotlStore.getFingerprintTrust(fingerprint); } @@ -427,10 +428,10 @@ public class AxolotlService { try { IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice( Jid.fromString(address.getName()), address.getDeviceId()); - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Retrieving bundle: " + bundlesPacket); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Retrieving bundle: " + bundlesPacket); mXmppConnectionService.sendIqPacket(account, bundlesPacket, new OnIqPacketReceived() { private void finish() { - AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(),0); + AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), 0); if (!fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING) && !fetchStatusMap.getAll(address).containsValue(FetchStatus.PENDING)) { if (flushWaitingQueueAfterFetch && conversation != null) { @@ -438,7 +439,7 @@ public class AxolotlService { new Conversation.OnMessageFound() { @Override public void onMessageFound(Message message) { - processSending(message,false); + processSending(message, false); } }); } @@ -448,12 +449,12 @@ public class AxolotlService { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Received preKey IQ packet, processing..."); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received preKey IQ packet, processing..."); final IqParser parser = mXmppConnectionService.getIqParser(); final List preKeyBundleList = parser.preKeys(packet); final PreKeyBundle bundle = parser.bundle(packet); if (preKeyBundleList.isEmpty() || bundle == null) { - Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"preKey IQ packet invalid: " + packet); + Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "preKey IQ packet invalid: " + packet); fetchStatusMap.put(address, FetchStatus.ERROR); finish(); return; @@ -480,8 +481,8 @@ public class AxolotlService { XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey().getFingerprint().replaceAll("\\s", "")); sessions.put(address, session); fetchStatusMap.put(address, FetchStatus.SUCCESS); - } catch (UntrustedIdentityException|InvalidKeyException e) { - Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Error building session for " + address + ": " + } catch (UntrustedIdentityException | InvalidKeyException e) { + Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error building session for " + address + ": " + e.getClass().getName() + ", " + e.getMessage()); fetchStatusMap.put(address, FetchStatus.ERROR); } @@ -490,7 +491,7 @@ public class AxolotlService { } }); } catch (InvalidJidException e) { - Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Got address with invalid jid: " + address.getName()); + Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Got address with invalid jid: " + address.getName()); } } @@ -498,13 +499,13 @@ public class AxolotlService { Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Finding devices without session for " + conversation.getContact().getJid().toBareJid()); Jid contactJid = conversation.getContact().getJid().toBareJid(); Set addresses = new HashSet<>(); - if(deviceIds.get(contactJid) != null) { - for(Integer foreignId:this.deviceIds.get(contactJid)) { + if (deviceIds.get(contactJid) != null) { + for (Integer foreignId : this.deviceIds.get(contactJid)) { AxolotlAddress address = new AxolotlAddress(contactJid.toString(), foreignId); - if(sessions.get(address) == null) { + if (sessions.get(address) == null) { IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey(); - if ( identityKey != null ) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Already have session for " + address.toString() + ", adding to cache..."); + if (identityKey != null) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache..."); XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey.getFingerprint().replaceAll("\\s", "")); sessions.put(address, session); } else { @@ -516,13 +517,13 @@ public class AxolotlService { } else { Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Have no target devices in PEP!"); } - if(deviceIds.get(account.getJid().toBareJid()) != null) { - for(Integer ownId:this.deviceIds.get(account.getJid().toBareJid())) { + if (deviceIds.get(account.getJid().toBareJid()) != null) { + for (Integer ownId : this.deviceIds.get(account.getJid().toBareJid())) { AxolotlAddress address = new AxolotlAddress(account.getJid().toBareJid().toString(), ownId); - if(sessions.get(address) == null) { + if (sessions.get(address) == null) { IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey(); - if ( identityKey != null ) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Already have session for " + address.toString() + ", adding to cache..."); + if (identityKey != null) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache..."); XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey.getFingerprint().replaceAll("\\s", "")); sessions.put(address, session); } else { @@ -543,12 +544,12 @@ public class AxolotlService { for (AxolotlAddress address : addresses) { Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Processing device: " + address.toString()); FetchStatus status = fetchStatusMap.get(address); - if ( status == null || status == FetchStatus.ERROR ) { - fetchStatusMap.put(address, FetchStatus.PENDING); - this.buildSessionFromPEP(conversation, address, flushWaitingQueueAfterFetch); - newSessions = true; + if (status == null || status == FetchStatus.ERROR) { + fetchStatusMap.put(address, FetchStatus.PENDING); + this.buildSessionFromPEP(conversation, address, flushWaitingQueueAfterFetch); + newSessions = true; } else { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Already fetching bundle for " + address.toString()); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already fetching bundle for " + address.toString()); } } @@ -556,18 +557,18 @@ public class AxolotlService { } public boolean hasPendingKeyFetches(Conversation conversation) { - AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(),0); - AxolotlAddress foreignAddress = new AxolotlAddress(conversation.getJid().toBareJid().toString(),0); + AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), 0); + AxolotlAddress foreignAddress = new AxolotlAddress(conversation.getJid().toBareJid().toString(), 0); return fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING) - ||fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING); + || fetchStatusMap.getAll(foreignAddress).containsValue(FetchStatus.PENDING); } @Nullable - public XmppAxolotlMessage encrypt(Message message ){ + public XmppAxolotlMessage encrypt(Message message) { final String content; if (message.hasFileOnRemoteHost()) { - content = message.getFileParams().url.toString(); + content = message.getFileParams().url.toString(); } else { content = message.getBody(); } @@ -580,17 +581,17 @@ public class AxolotlService { return null; } - if(findSessionsforContact(message.getContact()).isEmpty()) { + if (findSessionsforContact(message.getContact()).isEmpty()) { return null; } - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building axolotl foreign keyElements..."); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building axolotl foreign keyElements..."); for (XmppAxolotlSession session : findSessionsforContact(message.getContact())) { - Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account)+session.getRemoteAddress().toString()); + Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account) + session.getRemoteAddress().toString()); axolotlMessage.addKeyElement(session.processSending(axolotlMessage.getInnerKey())); } - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Building axolotl own keyElements..."); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building axolotl own keyElements..."); for (XmppAxolotlSession session : findOwnSessions()) { - Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account)+session.getRemoteAddress().toString()); + Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account) + session.getRemoteAddress().toString()); axolotlMessage.addKeyElement(session.processSending(axolotlMessage.getInnerKey())); } @@ -606,19 +607,19 @@ public class AxolotlService { mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED); //mXmppConnectionService.updateConversationUi(); } else { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Generated message, caching: " + message.getUuid()); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Generated message, caching: " + message.getUuid()); messageCache.put(message.getUuid(), axolotlMessage); - mXmppConnectionService.resendMessage(message,delay); + mXmppConnectionService.resendMessage(message, delay); } } }); } - public void prepareMessage(final Message message,final boolean delay) { + public void prepareMessage(final Message message, final boolean delay) { if (!messageCache.containsKey(message.getUuid())) { boolean newSessions = createSessionsIfNeeded(message.getConversation(), true); if (!newSessions) { - this.processSending(message,delay); + this.processSending(message, delay); } } } @@ -626,10 +627,10 @@ public class AxolotlService { 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()); + 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()); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache miss: " + message.getUuid()); } return axolotlMessage; } @@ -642,9 +643,9 @@ public class AxolotlService { boolean newSession = false; XmppAxolotlSession session = sessions.get(senderAddress); if (session == null) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Account: "+account.getJid()+" No axolotl session found while parsing received message " + message); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Account: " + account.getJid() + " No axolotl session found while parsing received message " + message); IdentityKey identityKey = axolotlStore.loadSession(senderAddress).getSessionState().getRemoteIdentityKey(); - if ( identityKey != null ) { + if (identityKey != null) { session = new XmppAxolotlSession(account, axolotlStore, senderAddress, identityKey.getFingerprint().replaceAll("\\s", "")); } else { session = new XmppAxolotlSession(account, axolotlStore, senderAddress); @@ -654,11 +655,11 @@ public class AxolotlService { for (XmppAxolotlMessage.XmppAxolotlKeyElement keyElement : message.getKeyElements()) { if (keyElement.getRecipientDeviceId() == getOwnDeviceId()) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Found axolotl keyElement matching own device ID, processing..."); + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found axolotl keyElement matching own device ID, processing..."); byte[] payloadKey = session.processReceiving(keyElement); if (payloadKey != null) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Got payload key from axolotl keyElement. Decrypting message..."); - try{ + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Got payload key from axolotl keyElement. Decrypting message..."); + try { plaintextMessage = message.decrypt(session, payloadKey, session.getFingerprint()); } catch (CryptoFailedException e) { Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message: " + e.getMessage()); 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 4725ce8aa..4e954a72f 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java @@ -56,11 +56,11 @@ public class XmppAxolotlMessage { } public XmppAxolotlKeyElement(Element keyElement) { - if(TAGNAME.equals(keyElement.getName())) { + if (TAGNAME.equals(keyElement.getName())) { this.recipientDeviceId = Integer.parseInt(keyElement.getAttribute(REMOTEID)); - this.content = Base64.decode(keyElement.getContent(),Base64.DEFAULT); + this.content = Base64.decode(keyElement.getContent(), Base64.DEFAULT); } else { - throw new IllegalArgumentException("Argument not a <"+TAGNAME+"> Element!"); + throw new IllegalArgumentException("Argument not a <" + TAGNAME + "> Element!"); } } @@ -109,24 +109,24 @@ public class XmppAxolotlMessage { Element header = axolotlMessage.findChild(HEADER); this.sourceDeviceId = Integer.parseInt(header.getAttribute(SOURCEID)); this.keyElements = new HashSet<>(); - for(Element keyElement:header.getChildren()) { - switch(keyElement.getName()) { + for (Element keyElement : header.getChildren()) { + switch (keyElement.getName()) { case XmppAxolotlKeyElement.TAGNAME: keyElements.add(new XmppAxolotlKeyElement(keyElement)); break; case IVTAG: - if ( this.iv != null) { + if (this.iv != null) { throw new IllegalArgumentException("Duplicate iv entry"); } - iv = Base64.decode(keyElement.getContent(),Base64.DEFAULT); + iv = Base64.decode(keyElement.getContent(), Base64.DEFAULT); break; default: - Log.w(Config.LOGTAG, "Unexpected element in header: "+ keyElement.toString()); + Log.w(Config.LOGTAG, "Unexpected element in header: " + keyElement.toString()); break; } } Element payloadElement = axolotlMessage.findChild(PAYLOAD); - if ( payloadElement != null ) { + if (payloadElement != null) { ciphertext = Base64.decode(payloadElement.getContent(), Base64.DEFAULT); } } @@ -139,7 +139,7 @@ public class XmppAxolotlMessage { this.innerKey = generateKey(); } - public XmppAxolotlMessage(Jid from, int sourceDeviceId, String plaintext) throws CryptoFailedException{ + public XmppAxolotlMessage(Jid from, int sourceDeviceId, String plaintext) throws CryptoFailedException { this(from, sourceDeviceId); this.encrypt(plaintext); } @@ -199,7 +199,7 @@ public class XmppAxolotlMessage { } } - public byte[] getInnerKey(){ + public byte[] getInnerKey() { return innerKey; } @@ -208,14 +208,14 @@ public class XmppAxolotlMessage { } public Element toXml() { - Element encryptionElement= new Element(TAGNAME, AxolotlService.PEP_PREFIX); + Element encryptionElement = new Element(TAGNAME, AxolotlService.PEP_PREFIX); Element headerElement = encryptionElement.addChild(HEADER); headerElement.setAttribute(SOURCEID, sourceDeviceId); - for(XmppAxolotlKeyElement header: keyElements) { + for (XmppAxolotlKeyElement header : keyElements) { headerElement.addChild(header.toXml()); } headerElement.addChild(IVTAG).setContent(Base64.encodeToString(iv, Base64.DEFAULT)); - if ( ciphertext != null ) { + if (ciphertext != null) { Element payload = encryptionElement.addChild(PAYLOAD); payload.setContent(Base64.encodeToString(ciphertext, Base64.DEFAULT)); } diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java index dc4c67779..d60e7715a 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java @@ -65,7 +65,7 @@ public class XmppAxolotlSession { protected SQLiteAxolotlStore.Trust getTrust() { SQLiteAxolotlStore.Trust trust = sqLiteAxolotlStore.getFingerprintTrust(fingerprint); - return (trust == null)? SQLiteAxolotlStore.Trust.UNDECIDED : trust; + return (trust == null) ? SQLiteAxolotlStore.Trust.UNDECIDED : trust; } @Nullable -- cgit v1.2.3 From 909f761ca1659938cf5f9d7206ee24d54faa8550 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Fri, 31 Jul 2015 21:12:34 +0200 Subject: Refactor axolotl message processing workflow XmppAxolotlMessage is now entirely responsible for handling encryption and decryption of messages, only leveraging XmppAxolotlSession as a packing/unpacking primitive for payload keys. Removed pseudo-dead session generation code step from prepareMessage function, as sessions have been created by invoking the TrustKeysActivity for a while now. Added prepareKeyTransportMessage function, which creates a message with no payload. The key that is packed into the header keyElements can then be used for other purposes (e.g. encrypted file transfer). --- .../crypto/axolotl/AxolotlService.java | 113 ++++++++-------- .../crypto/axolotl/XmppAxolotlMessage.java | 142 +++++++++------------ .../crypto/axolotl/XmppAxolotlSession.java | 13 +- .../conversations/generator/MessageGenerator.java | 2 +- .../siacs/conversations/parser/MessageParser.java | 4 +- .../services/XmppConnectionService.java | 2 +- .../conversations/ui/ConversationActivity.java | 2 +- 7 files changed, 121 insertions(+), 157 deletions(-) (limited to 'src/main') 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 4cc61b37c..4cb1c90c1 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -256,7 +256,7 @@ public class AxolotlService { for (Integer deviceId : deviceIds) { AxolotlAddress ownDeviceAddress = new AxolotlAddress(jid.toBareJid().toString(), deviceId); if (sessions.get(ownDeviceAddress) == null) { - buildSessionFromPEP(null, ownDeviceAddress, false); + buildSessionFromPEP(ownDeviceAddress); } } } @@ -422,7 +422,7 @@ public class AxolotlService { axolotlStore.setFingerprintTrust(fingerprint, trust); } - private void buildSessionFromPEP(final Conversation conversation, final AxolotlAddress address, final boolean flushWaitingQueueAfterFetch) { + private void buildSessionFromPEP(final AxolotlAddress address) { Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new sesstion for " + address.getDeviceId()); try { @@ -434,15 +434,6 @@ public class AxolotlService { AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), 0); if (!fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING) && !fetchStatusMap.getAll(address).containsValue(FetchStatus.PENDING)) { - if (flushWaitingQueueAfterFetch && conversation != null) { - conversation.findUnsentMessagesWithEncryption(Message.ENCRYPTION_AXOLOTL, - new Conversation.OnMessageFound() { - @Override - public void onMessageFound(Message message) { - processSending(message, false); - } - }); - } mXmppConnectionService.keyStatusUpdated(); } } @@ -537,7 +528,7 @@ public class AxolotlService { return addresses; } - public boolean createSessionsIfNeeded(final Conversation conversation, final boolean flushWaitingQueueAfterFetch) { + public boolean createSessionsIfNeeded(final Conversation conversation) { Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Creating axolotl sessions if needed..."); boolean newSessions = false; Set addresses = findDevicesWithoutSession(conversation); @@ -546,7 +537,9 @@ public class AxolotlService { FetchStatus status = fetchStatusMap.get(address); if (status == null || status == FetchStatus.ERROR) { fetchStatusMap.put(address, FetchStatus.PENDING); - this.buildSessionFromPEP(conversation, address, flushWaitingQueueAfterFetch); + this.buildSessionFromPEP(address); + newSessions = true; + } else if (status == FetchStatus.PENDING) { newSessions = true; } else { Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already fetching bundle for " + address.toString()); @@ -565,40 +558,52 @@ public class AxolotlService { } @Nullable - public XmppAxolotlMessage encrypt(Message message) { - final String content; - if (message.hasFileOnRemoteHost()) { - content = message.getFileParams().url.toString(); - } 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; - } + private XmppAxolotlMessage buildHeader(Contact contact) { + final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage( + contact.getJid().toBareJid(), getOwnDeviceId()); - if (findSessionsforContact(message.getContact()).isEmpty()) { + Set contactSessions = findSessionsforContact(contact); + Set ownSessions = findOwnSessions(); + if (contactSessions.isEmpty()) { return null; } Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building axolotl foreign keyElements..."); - for (XmppAxolotlSession session : findSessionsforContact(message.getContact())) { + for (XmppAxolotlSession session : contactSessions) { Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account) + session.getRemoteAddress().toString()); - axolotlMessage.addKeyElement(session.processSending(axolotlMessage.getInnerKey())); + axolotlMessage.addDevice(session); } Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building axolotl own keyElements..."); - for (XmppAxolotlSession session : findOwnSessions()) { + for (XmppAxolotlSession session : ownSessions) { Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account) + session.getRemoteAddress().toString()); - axolotlMessage.addKeyElement(session.processSending(axolotlMessage.getInnerKey())); + axolotlMessage.addDevice(session); + } + + return axolotlMessage; + } + + @Nullable + public XmppAxolotlMessage encrypt(Message message) { + XmppAxolotlMessage axolotlMessage = buildHeader(message.getContact()); + + if (axolotlMessage != null) { + final String content; + if (message.hasFileOnRemoteHost()) { + content = message.getFileParams().url.toString(); + } else { + content = message.getBody(); + } + try { + axolotlMessage.encrypt(content); + } catch (CryptoFailedException e) { + Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to encrypt message: " + e.getMessage()); + return null; + } } return axolotlMessage; } - private void processSending(final Message message, final boolean delay) { + public void preparePayloadMessage(final Message message, final boolean delay) { executor.execute(new Runnable() { @Override public void run() { @@ -615,13 +620,14 @@ public class AxolotlService { }); } - public void prepareMessage(final Message message, final boolean delay) { - if (!messageCache.containsKey(message.getUuid())) { - boolean newSessions = createSessionsIfNeeded(message.getConversation(), true); - if (!newSessions) { - this.processSending(message, delay); + public void prepareKeyTransportMessage(final Contact contact, final OnMessageCreatedCallback onMessageCreatedCallback) { + executor.execute(new Runnable() { + @Override + public void run() { + XmppAxolotlMessage axolotlMessage = buildHeader(contact); + onMessageCreatedCallback.run(axolotlMessage); } - } + }); } public XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message) { @@ -653,26 +659,15 @@ public class AxolotlService { newSession = true; } - for (XmppAxolotlMessage.XmppAxolotlKeyElement keyElement : message.getKeyElements()) { - if (keyElement.getRecipientDeviceId() == getOwnDeviceId()) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found axolotl keyElement matching own device ID, processing..."); - byte[] payloadKey = session.processReceiving(keyElement); - if (payloadKey != null) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Got payload key from axolotl keyElement. Decrypting message..."); - 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) { - publishBundlesIfNeeded(); - session.resetPreKeyId(); - } - break; + try { + plaintextMessage = message.decrypt(session, getOwnDeviceId()); + Integer preKeyId = session.getPreKeyId(); + if (preKeyId != null) { + publishBundlesIfNeeded(); + session.resetPreKeyId(); } + } catch (CryptoFailedException e) { + Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message: " + e.getMessage()); } if (newSession && plaintextMessage != null) { 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 4e954a72f..fa6d895a2 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java @@ -1,6 +1,5 @@ package eu.siacs.conversations.crypto.axolotl; -import android.support.annotation.Nullable; import android.util.Base64; import android.util.Log; @@ -9,8 +8,9 @@ import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.SecureRandom; -import java.util.HashSet; -import java.util.Set; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; @@ -26,9 +26,11 @@ import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.jid.Jid; public class XmppAxolotlMessage { - public static final String TAGNAME = "encrypted"; + public static final String CONTAINERTAG = "encrypted"; public static final String HEADER = "header"; public static final String SOURCEID = "sid"; + public static final String KEYTAG = "key"; + public static final String REMOTEID = "rid"; public static final String IVTAG = "iv"; public static final String PAYLOAD = "payload"; @@ -39,54 +41,15 @@ public class XmppAxolotlMessage { private byte[] innerKey; private byte[] ciphertext = null; private byte[] iv = null; - private final Set keyElements; + private final Map keys; private final Jid from; private final int sourceDeviceId; - public static class XmppAxolotlKeyElement { - public static final String TAGNAME = "key"; - public static final String REMOTEID = "rid"; - - private final int recipientDeviceId; - private final byte[] content; - - public XmppAxolotlKeyElement(int deviceId, byte[] content) { - this.recipientDeviceId = deviceId; - this.content = content; - } - - public XmppAxolotlKeyElement(Element keyElement) { - if (TAGNAME.equals(keyElement.getName())) { - this.recipientDeviceId = Integer.parseInt(keyElement.getAttribute(REMOTEID)); - this.content = Base64.decode(keyElement.getContent(), Base64.DEFAULT); - } else { - throw new IllegalArgumentException("Argument not a <" + TAGNAME + "> Element!"); - } - } - - public int getRecipientDeviceId() { - return recipientDeviceId; - } - - public byte[] getContents() { - return content; - } - - public Element toXml() { - Element keyElement = new Element(TAGNAME); - keyElement.setAttribute(REMOTEID, getRecipientDeviceId()); - keyElement.setContent(Base64.encodeToString(getContents(), Base64.DEFAULT)); - return keyElement; - } - } - public static class XmppAxolotlPlaintextMessage { - private final XmppAxolotlSession session; private final String plaintext; private final String fingerprint; - public XmppAxolotlPlaintextMessage(XmppAxolotlSession session, String plaintext, String fingerprint) { - this.session = session; + public XmppAxolotlPlaintextMessage(String plaintext, String fingerprint) { this.plaintext = plaintext; this.fingerprint = fingerprint; } @@ -95,24 +58,28 @@ public class XmppAxolotlMessage { return plaintext; } - public XmppAxolotlSession getSession() { - return session; - } public String getFingerprint() { return fingerprint; } } - public XmppAxolotlMessage(Jid from, Element axolotlMessage) throws IllegalArgumentException { + private XmppAxolotlMessage(final Element axolotlMessage, final Jid from) throws IllegalArgumentException { this.from = from; Element header = axolotlMessage.findChild(HEADER); this.sourceDeviceId = Integer.parseInt(header.getAttribute(SOURCEID)); - this.keyElements = new HashSet<>(); - for (Element keyElement : header.getChildren()) { + List keyElements = header.getChildren(); + this.keys = new HashMap<>(keyElements.size()); + for (Element keyElement : keyElements) { switch (keyElement.getName()) { - case XmppAxolotlKeyElement.TAGNAME: - keyElements.add(new XmppAxolotlKeyElement(keyElement)); + case KEYTAG: + try { + Integer recipientId = Integer.parseInt(keyElement.getAttribute(REMOTEID)); + byte[] key = Base64.decode(keyElement.getContent(), Base64.DEFAULT); + this.keys.put(recipientId, key); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(e); + } break; case IVTAG: if (this.iv != null) { @@ -134,14 +101,13 @@ public class XmppAxolotlMessage { public XmppAxolotlMessage(Jid from, int sourceDeviceId) { this.from = from; this.sourceDeviceId = sourceDeviceId; - this.keyElements = new HashSet<>(); + this.keys = new HashMap<>(); this.iv = generateIv(); this.innerKey = generateKey(); } - public XmppAxolotlMessage(Jid from, int sourceDeviceId, String plaintext) throws CryptoFailedException { - this(from, sourceDeviceId); - this.encrypt(plaintext); + public static XmppAxolotlMessage fromElement(Element element, Jid from) { + return new XmppAxolotlMessage(element, from); } private static byte[] generateKey() { @@ -162,7 +128,7 @@ public class XmppAxolotlMessage { return iv; } - private void encrypt(String plaintext) throws CryptoFailedException { + public void encrypt(String plaintext) throws CryptoFailedException { try { SecretKey secretKey = new SecretKeySpec(innerKey, KEYTYPE); IvParameterSpec ivSpec = new IvParameterSpec(iv); @@ -189,13 +155,10 @@ public class XmppAxolotlMessage { return ciphertext; } - public Set getKeyElements() { - return keyElements; - } - - public void addKeyElement(@Nullable XmppAxolotlKeyElement keyElement) { - if (keyElement != null) { - keyElements.add(keyElement); + public void addDevice(XmppAxolotlSession session) { + byte[] key = session.processSending(innerKey); + if (key != null) { + keys.put(session.getRemoteAddress().getDeviceId(), key); } } @@ -207,12 +170,15 @@ public class XmppAxolotlMessage { return this.iv; } - public Element toXml() { - Element encryptionElement = new Element(TAGNAME, AxolotlService.PEP_PREFIX); + public Element toElement() { + Element encryptionElement = new Element(CONTAINERTAG, AxolotlService.PEP_PREFIX); Element headerElement = encryptionElement.addChild(HEADER); headerElement.setAttribute(SOURCEID, sourceDeviceId); - for (XmppAxolotlKeyElement header : keyElements) { - headerElement.addChild(header.toXml()); + for (Map.Entry keyEntry : keys.entrySet()) { + Element keyElement = new Element(KEYTAG); + keyElement.setAttribute(REMOTEID, keyEntry.getKey()); + keyElement.setContent(Base64.encodeToString(keyEntry.getValue(), Base64.DEFAULT)); + headerElement.addChild(keyElement); } headerElement.addChild(IVTAG).setContent(Base64.encodeToString(iv, Base64.DEFAULT)); if (ciphertext != null) { @@ -222,24 +188,30 @@ public class XmppAxolotlMessage { return encryptionElement; } + public byte[] unpackKey(XmppAxolotlSession session, Integer sourceDeviceId) { + byte[] encryptedKey = keys.get(sourceDeviceId); + return (encryptedKey != null) ? session.processReceiving(encryptedKey) : null; + } - public XmppAxolotlPlaintextMessage decrypt(XmppAxolotlSession session, byte[] key, String fingerprint) throws CryptoFailedException { + public XmppAxolotlPlaintextMessage decrypt(XmppAxolotlSession session, Integer sourceDeviceId) throws CryptoFailedException { XmppAxolotlPlaintextMessage plaintextMessage = null; - try { - - Cipher cipher = Cipher.getInstance(CIPHERMODE, PROVIDER); - SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE); - IvParameterSpec ivSpec = new IvParameterSpec(iv); - - cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); - - String plaintext = new String(cipher.doFinal(ciphertext)); - plaintextMessage = new XmppAxolotlPlaintextMessage(session, plaintext, fingerprint); - - } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException - | InvalidAlgorithmParameterException | IllegalBlockSizeException - | BadPaddingException | NoSuchProviderException e) { - throw new CryptoFailedException(e); + byte[] key = unpackKey(session, sourceDeviceId); + if (key != null) { + try { + Cipher cipher = Cipher.getInstance(CIPHERMODE, PROVIDER); + SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE); + IvParameterSpec ivSpec = new IvParameterSpec(iv); + + cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); + + String plaintext = new String(cipher.doFinal(ciphertext)); + plaintextMessage = new XmppAxolotlPlaintextMessage(plaintext, session.getFingerprint()); + + } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException + | InvalidAlgorithmParameterException | IllegalBlockSizeException + | BadPaddingException | NoSuchProviderException e) { + throw new CryptoFailedException(e); + } } return plaintextMessage; } diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java index d60e7715a..6ed73da6a 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java @@ -69,7 +69,7 @@ public class XmppAxolotlSession { } @Nullable - public byte[] processReceiving(XmppAxolotlMessage.XmppAxolotlKeyElement incomingHeader) { + public byte[] processReceiving(byte[] encryptedKey) { byte[] plaintext = null; SQLiteAxolotlStore.Trust trust = getTrust(); switch (trust) { @@ -79,7 +79,7 @@ public class XmppAxolotlSession { case TRUSTED: try { try { - PreKeyWhisperMessage message = new PreKeyWhisperMessage(incomingHeader.getContents()); + PreKeyWhisperMessage message = new PreKeyWhisperMessage(encryptedKey); Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "PreKeyWhisperMessage received, new session ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId()); String fingerprint = message.getIdentityKey().getFingerprint().replaceAll("\\s", ""); if (this.fingerprint != null && !this.fingerprint.equals(fingerprint)) { @@ -93,7 +93,7 @@ public class XmppAxolotlSession { } } catch (InvalidMessageException | InvalidVersionException e) { Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "WhisperMessage received"); - WhisperMessage message = new WhisperMessage(incomingHeader.getContents()); + WhisperMessage message = new WhisperMessage(encryptedKey); plaintext = cipher.decrypt(message); } catch (InvalidKeyException | InvalidKeyIdException | UntrustedIdentityException e) { Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage()); @@ -117,14 +117,11 @@ public class XmppAxolotlSession { } @Nullable - public XmppAxolotlMessage.XmppAxolotlKeyElement processSending(@NonNull byte[] outgoingMessage) { + public byte[] processSending(@NonNull byte[] outgoingMessage) { SQLiteAxolotlStore.Trust trust = getTrust(); if (trust == SQLiteAxolotlStore.Trust.TRUSTED) { CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage); - XmppAxolotlMessage.XmppAxolotlKeyElement header = - new XmppAxolotlMessage.XmppAxolotlKeyElement(remoteAddress.getDeviceId(), - ciphertextMessage.serialize()); - return header; + return ciphertextMessage.serialize(); } else { return null; } diff --git a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java index 045916725..a06a0dddb 100644 --- a/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/MessageGenerator.java @@ -63,7 +63,7 @@ public class MessageGenerator extends AbstractGenerator { if (axolotlMessage == null) { return null; } - packet.setAxolotlMessage(axolotlMessage.toXml()); + packet.setAxolotlMessage(axolotlMessage.toElement()); return packet; } diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 47c320f39..a53e3a507 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -99,7 +99,7 @@ public class MessageParser extends AbstractParser implements private Message parseAxolotlChat(Element axolotlMessage, Jid from, String id, Conversation conversation, int status) { Message finishedMessage = null; AxolotlService service = conversation.getAccount().getAxolotlService(); - XmppAxolotlMessage xmppAxolotlMessage = new XmppAxolotlMessage(from.toBareJid(), axolotlMessage); + XmppAxolotlMessage xmppAxolotlMessage = XmppAxolotlMessage.fromElement(axolotlMessage, from.toBareJid()); XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = service.processReceiving(xmppAxolotlMessage); if(plaintextMessage != null) { finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, status); @@ -272,7 +272,7 @@ public class MessageParser extends AbstractParser implements final String body = packet.getBody(); final Element mucUserElement = packet.findChild("x", "http://jabber.org/protocol/muc#user"); final String pgpEncrypted = packet.findChildContent("x", "jabber:x:encrypted"); - final Element axolotlEncrypted = packet.findChild(XmppAxolotlMessage.TAGNAME, AxolotlService.PEP_PREFIX); + final Element axolotlEncrypted = packet.findChild(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX); int status; final Jid counterpart; final Jid to = packet.getTo(); diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 13b810905..61453ab07 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -764,7 +764,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } else { XmppAxolotlMessage axolotlMessage = account.getAxolotlService().fetchAxolotlMessageFromCache(message); if (axolotlMessage == null) { - account.getAxolotlService().prepareMessage(message,delay); + account.getAxolotlService().preparePayloadMessage(message, delay); message.setAxolotlFingerprint(account.getAxolotlService().getOwnPublicKey().getFingerprint().replaceAll("\\s", "")); } else { packet = mMessageGenerator.generateAxolotlChat(message, axolotlMessage); diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index daf22122a..f34e1a55a 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -1265,7 +1265,7 @@ public class ConversationActivity extends XmppActivity || !axolotlService.findDevicesWithoutSession(mSelectedConversation).isEmpty(); boolean hasNoTrustedKeys = axolotlService.getNumTrustedKeys(mSelectedConversation.getContact()) == 0; if( hasPendingKeys || hasNoTrustedKeys) { - axolotlService.createSessionsIfNeeded(mSelectedConversation, false); + axolotlService.createSessionsIfNeeded(mSelectedConversation); Intent intent = new Intent(getApplicationContext(), TrustKeysActivity.class); intent.putExtra("contact", mSelectedConversation.getContact().getJid().toBareJid().toString()); intent.putExtra("account", mSelectedConversation.getAccount().getJid().toBareJid().toString()); -- cgit v1.2.3 From 6059b964569fb406bbc86a0ccb19e76851fba2b6 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Fri, 31 Jul 2015 23:28:09 +0200 Subject: Provide process function for key transport message --- .../crypto/axolotl/AxolotlService.java | 48 +++++++++++++++++----- .../crypto/axolotl/XmppAxolotlMessage.java | 33 ++++++++++++++- .../crypto/axolotl/XmppAxolotlSession.java | 20 ++++++--- .../siacs/conversations/parser/MessageParser.java | 2 +- 4 files changed, 84 insertions(+), 19 deletions(-) (limited to 'src/main') 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 4cb1c90c1..7a92a1f77 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -150,8 +150,13 @@ public class AxolotlService { @Override public void put(AxolotlAddress address, XmppAxolotlSession value) { super.put(address, value); + value.setNotFresh(); xmppConnectionService.syncRosterToDisk(account); } + + public void put(XmppAxolotlSession session) { + this.put(session.getRemoteAddress(), session); + } } private static enum FetchStatus { @@ -641,24 +646,32 @@ public class AxolotlService { return axolotlMessage; } - public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceiving(XmppAxolotlMessage message) { - XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null; + private XmppAxolotlSession recreateUncachedSession(AxolotlAddress address) { + IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey(); + return (identityKey != null) + ? new XmppAxolotlSession(account, axolotlStore, address, + identityKey.getFingerprint().replaceAll("\\s", "")) + : null; + } + + private XmppAxolotlSession getReceivingSession(XmppAxolotlMessage message) { AxolotlAddress senderAddress = new AxolotlAddress(message.getFrom().toString(), message.getSenderDeviceId()); - - boolean newSession = false; XmppAxolotlSession session = sessions.get(senderAddress); if (session == null) { Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Account: " + account.getJid() + " No axolotl session found while parsing received message " + message); - IdentityKey identityKey = axolotlStore.loadSession(senderAddress).getSessionState().getRemoteIdentityKey(); - if (identityKey != null) { - session = new XmppAxolotlSession(account, axolotlStore, senderAddress, identityKey.getFingerprint().replaceAll("\\s", "")); - } else { + session = recreateUncachedSession(senderAddress); + if (session == null) { session = new XmppAxolotlSession(account, axolotlStore, senderAddress); } - newSession = true; } + return session; + } + public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message) { + XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null; + + XmppAxolotlSession session = getReceivingSession(message); try { plaintextMessage = message.decrypt(session, getOwnDeviceId()); Integer preKeyId = session.getPreKeyId(); @@ -670,10 +683,23 @@ public class AxolotlService { Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message: " + e.getMessage()); } - if (newSession && plaintextMessage != null) { - sessions.put(senderAddress, session); + if (session.isFresh() && plaintextMessage != null) { + sessions.put(session); } return plaintextMessage; } + + public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message) { + XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage = null; + + XmppAxolotlSession session = getReceivingSession(message); + keyTransportMessage = message.getParameters(session, getOwnDeviceId()); + + if (session.isFresh() && keyTransportMessage != null) { + sessions.put(session); + } + + return keyTransportMessage; + } } 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 fa6d895a2..cf950d6da 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java @@ -64,6 +64,30 @@ public class XmppAxolotlMessage { } } + public static class XmppAxolotlKeyTransportMessage { + private final String fingerprint; + private final byte[] key; + private final byte[] iv; + + public XmppAxolotlKeyTransportMessage(String fingerprint, byte[] key, byte[] iv) { + this.fingerprint = fingerprint; + this.key = key; + this.iv = iv; + } + + public String getFingerprint() { + return fingerprint; + } + + public byte[] getKey() { + return key; + } + + public byte[] getIv() { + return iv; + } + } + private XmppAxolotlMessage(final Element axolotlMessage, final Jid from) throws IllegalArgumentException { this.from = from; Element header = axolotlMessage.findChild(HEADER); @@ -188,11 +212,18 @@ public class XmppAxolotlMessage { return encryptionElement; } - public byte[] unpackKey(XmppAxolotlSession session, Integer sourceDeviceId) { + private byte[] unpackKey(XmppAxolotlSession session, Integer sourceDeviceId) { byte[] encryptedKey = keys.get(sourceDeviceId); return (encryptedKey != null) ? session.processReceiving(encryptedKey) : null; } + public XmppAxolotlKeyTransportMessage getParameters(XmppAxolotlSession session, Integer sourceDeviceId) { + byte[] key = unpackKey(session, sourceDeviceId); + return (key != null) + ? new XmppAxolotlKeyTransportMessage(session.getFingerprint(), key, getIV()) + : null; + } + public XmppAxolotlPlaintextMessage decrypt(XmppAxolotlSession session, Integer sourceDeviceId) throws CryptoFailedException { XmppAxolotlPlaintextMessage plaintextMessage = null; byte[] key = unpackKey(session, sourceDeviceId); diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java index 6ed73da6a..46004a1a9 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java @@ -23,16 +23,12 @@ import eu.siacs.conversations.entities.Account; public class XmppAxolotlSession { private final SessionCipher cipher; - private Integer preKeyId = null; private final SQLiteAxolotlStore sqLiteAxolotlStore; - - public AxolotlAddress getRemoteAddress() { - return remoteAddress; - } - private final AxolotlAddress remoteAddress; private final Account account; private String fingerprint = null; + private Integer preKeyId = null; + private boolean fresh = true; public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress, String fingerprint) { this(account, store, remoteAddress); @@ -59,6 +55,18 @@ public class XmppAxolotlSession { return fingerprint; } + public AxolotlAddress getRemoteAddress() { + return remoteAddress; + } + + public boolean isFresh() { + return fresh; + } + + public void setNotFresh() { + this.fresh = false; + } + protected void setTrust(SQLiteAxolotlStore.Trust trust) { sqLiteAxolotlStore.setFingerprintTrust(fingerprint, trust); } diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index a53e3a507..5786487f2 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -100,7 +100,7 @@ public class MessageParser extends AbstractParser implements Message finishedMessage = null; AxolotlService service = conversation.getAccount().getAxolotlService(); XmppAxolotlMessage xmppAxolotlMessage = XmppAxolotlMessage.fromElement(axolotlMessage, from.toBareJid()); - XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = service.processReceiving(xmppAxolotlMessage); + XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = service.processReceivingPayloadMessage(xmppAxolotlMessage); if(plaintextMessage != null) { finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, status); finishedMessage.setAxolotlFingerprint(plaintextMessage.getFingerprint()); -- cgit v1.2.3 From 60cd307f73d5f31f25ba84541fbe1cce4aae2bc2 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 1 Aug 2015 01:19:16 +0200 Subject: enable axolotl encryption for jingle supported file transfers --- .../conversations/entities/DownloadableFile.java | 34 ++++---- .../conversations/http/HttpDownloadConnection.java | 25 ++---- .../conversations/http/HttpUploadConnection.java | 24 ++---- .../services/AbstractConnectionManager.java | 97 ++++++++++++++++++++++ .../services/XmppConnectionService.java | 2 +- .../xmpp/jingle/JingleConnection.java | 97 +++++++++++++++++----- .../xmpp/jingle/JingleInbandTransport.java | 10 +-- .../xmpp/jingle/JingleSocks5Transport.java | 6 +- .../conversations/xmpp/jingle/JingleTransport.java | 61 ++------------ .../conversations/xmpp/jingle/stanzas/Content.java | 5 +- 10 files changed, 223 insertions(+), 138 deletions(-) (limited to 'src/main') diff --git a/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java b/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java index 452bf815c..d35a4b017 100644 --- a/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java +++ b/src/main/java/eu/siacs/conversations/entities/DownloadableFile.java @@ -24,15 +24,7 @@ public class DownloadableFile extends File { } public long getExpectedSize() { - if (this.aeskey != null) { - if (this.expectedSize == 0) { - return 0; - } else { - return (this.expectedSize / 16 + 1) * 16; - } - } else { - return this.expectedSize; - } + return this.expectedSize; } public String getMimeType() { @@ -58,25 +50,33 @@ public class DownloadableFile extends File { this.sha1sum = sum; } - public void setKey(byte[] key) { - if (key.length == 48) { + public void setKeyAndIv(byte[] keyIvCombo) { + if (keyIvCombo.length == 48) { byte[] secretKey = new byte[32]; byte[] iv = new byte[16]; - System.arraycopy(key, 0, iv, 0, 16); - System.arraycopy(key, 16, secretKey, 0, 32); + System.arraycopy(keyIvCombo, 0, iv, 0, 16); + System.arraycopy(keyIvCombo, 16, secretKey, 0, 32); this.aeskey = secretKey; this.iv = iv; - } else if (key.length >= 32) { + } else if (keyIvCombo.length >= 32) { byte[] secretKey = new byte[32]; - System.arraycopy(key, 0, secretKey, 0, 32); + System.arraycopy(keyIvCombo, 0, secretKey, 0, 32); this.aeskey = secretKey; - } else if (key.length >= 16) { + } else if (keyIvCombo.length >= 16) { byte[] secretKey = new byte[16]; - System.arraycopy(key, 0, secretKey, 0, 16); + System.arraycopy(keyIvCombo, 0, secretKey, 0, 16); this.aeskey = secretKey; } } + public void setKey(byte[] key) { + this.aeskey = key; + } + + public void setIv(byte[] iv) { + this.iv = iv; + } + public byte[] getKey() { return this.aeskey; } diff --git a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java index 51479836a..79e4654be 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java @@ -4,15 +4,7 @@ import android.content.Intent; import android.net.Uri; import android.util.Log; -import org.bouncycastle.crypto.engines.AESEngine; -import org.bouncycastle.crypto.io.CipherOutputStream; -import org.bouncycastle.crypto.modes.AEADBlockCipher; -import org.bouncycastle.crypto.modes.GCMBlockCipher; -import org.bouncycastle.crypto.params.AEADParameters; -import org.bouncycastle.crypto.params.KeyParameter; - import java.io.BufferedInputStream; -import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.HttpURLConnection; @@ -28,6 +20,8 @@ import eu.siacs.conversations.R; import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.Transferable; +import eu.siacs.conversations.persistance.FileBackend; +import eu.siacs.conversations.services.AbstractConnectionManager; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.utils.CryptoHelper; @@ -90,7 +84,7 @@ public class HttpDownloadConnection implements Transferable { this.file = mXmppConnectionService.getFileBackend().getFile(message, false); String reference = mUrl.getRef(); if (reference != null && reference.length() == 96) { - this.file.setKey(CryptoHelper.hexToBytes(reference)); + this.file.setKeyAndIv(CryptoHelper.hexToBytes(reference)); } if ((this.message.getEncryption() == Message.ENCRYPTION_OTR @@ -194,6 +188,8 @@ public class HttpDownloadConnection implements Transferable { private boolean interactive = false; + private OutputStream os; + public FileDownloader(boolean interactive) { this.interactive = interactive; } @@ -206,8 +202,10 @@ public class HttpDownloadConnection implements Transferable { updateImageBounds(); finish(); } catch (SSLHandshakeException e) { + FileBackend.close(os); changeStatus(STATUS_OFFER); } catch (IOException e) { + FileBackend.close(os); mXmppConnectionService.showErrorToastInUi(R.string.file_not_found_on_remote_host); cancel(); } @@ -222,14 +220,7 @@ public class HttpDownloadConnection implements Transferable { BufferedInputStream is = new BufferedInputStream(connection.getInputStream()); file.getParentFile().mkdirs(); file.createNewFile(); - OutputStream os; - if (file.getKey() != null) { - AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine()); - cipher.init(false, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv())); - os = new CipherOutputStream(new FileOutputStream(file), cipher); - } else { - os = new FileOutputStream(file); - } + os = AbstractConnectionManager.createOutputStream(file,true); long transmitted = 0; long expected = file.getExpectedSize(); int count = -1; diff --git a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java index 70dcc6421..b21d0d412 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java @@ -4,15 +4,8 @@ import android.app.PendingIntent; import android.content.Intent; import android.net.Uri; import android.util.Log; +import android.util.Pair; -import org.bouncycastle.crypto.engines.AESEngine; -import org.bouncycastle.crypto.io.CipherInputStream; -import org.bouncycastle.crypto.modes.AEADBlockCipher; -import org.bouncycastle.crypto.modes.GCMBlockCipher; -import org.bouncycastle.crypto.params.AEADParameters; -import org.bouncycastle.crypto.params.KeyParameter; - -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -28,6 +21,7 @@ import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.persistance.FileBackend; +import eu.siacs.conversations.services.AbstractConnectionManager; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.ui.UiCallback; import eu.siacs.conversations.utils.CryptoHelper; @@ -105,7 +99,7 @@ public class HttpUploadConnection implements Transferable { || message.getEncryption() == Message.ENCRYPTION_OTR) { this.key = new byte[48]; mXmppConnectionService.getRNG().nextBytes(this.key); - this.file.setKey(this.key); + this.file.setKeyAndIv(this.key); } Jid host = account.getXmppConnection().findDiscoItemByFeature(Xmlns.HTTP_UPLOAD); @@ -152,15 +146,9 @@ public class HttpUploadConnection implements Transferable { if (connection instanceof HttpsURLConnection) { mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, true); } - if (file.getKey() != null) { - AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine()); - cipher.init(true, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv())); - expected = cipher.getOutputSize((int) file.getSize()); - is = new CipherInputStream(new FileInputStream(file), cipher); - } else { - expected = (int) file.getSize(); - is = new FileInputStream(file); - } + Pair pair = AbstractConnectionManager.createInputStream(file,true); + is = pair.first; + expected = pair.second; connection.setRequestMethod("PUT"); connection.setFixedLengthStreamingMode(expected); connection.setDoOutput(true); diff --git a/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java b/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java index 676a09c97..b7e7c8d3a 100644 --- a/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java @@ -1,5 +1,33 @@ package eu.siacs.conversations.services; +import android.util.Log; +import android.util.Pair; + +import org.bouncycastle.crypto.engines.AESEngine; +import org.bouncycastle.crypto.modes.AEADBlockCipher; +import org.bouncycastle.crypto.modes.GCMBlockCipher; +import org.bouncycastle.crypto.params.AEADParameters; +import org.bouncycastle.crypto.params.KeyParameter; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.CipherOutputStream; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import eu.siacs.conversations.Config; +import eu.siacs.conversations.entities.DownloadableFile; + public class AbstractConnectionManager { protected XmppConnectionService mXmppConnectionService; @@ -20,4 +48,73 @@ public class AbstractConnectionManager { return 524288; } } + + public static Pair createInputStream(DownloadableFile file, boolean gcm) { + FileInputStream is; + int size; + try { + is = new FileInputStream(file); + size = (int) file.getSize(); + if (file.getKey() == null) { + return new Pair(is,size); + } + } catch (FileNotFoundException e) { + return null; + } + try { + if (gcm) { + AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine()); + cipher.init(true, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv())); + InputStream cis = new org.bouncycastle.crypto.io.CipherInputStream(is, cipher); + return new Pair<>(cis, cipher.getOutputSize(size)); + } else { + IvParameterSpec ips = new IvParameterSpec(file.getIv()); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(file.getKey(), "AES"), ips); + Log.d(Config.LOGTAG, "opening encrypted input stream"); + return new Pair(new CipherInputStream(is, cipher),(size / 16 + 1) * 16); + } + } catch (InvalidKeyException e) { + return null; + } catch (NoSuchAlgorithmException e) { + return null; + } catch (NoSuchPaddingException e) { + return null; + } catch (InvalidAlgorithmParameterException e) { + return null; + } + } + + public static OutputStream createOutputStream(DownloadableFile file, boolean gcm) { + FileOutputStream os; + try { + os = new FileOutputStream(file); + if (file.getKey() == null) { + return os; + } + } catch (FileNotFoundException e) { + return null; + } + try { + if (gcm) { + AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine()); + cipher.init(false, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv())); + return new org.bouncycastle.crypto.io.CipherOutputStream(os, cipher); + } else { + IvParameterSpec ips = new IvParameterSpec(file.getIv()); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(file.getKey(), "AES"), ips); + Log.d(Config.LOGTAG, "opening encrypted output stream"); + return new CipherOutputStream(os, cipher); + } + } catch (InvalidKeyException e) { + return null; + } catch (NoSuchAlgorithmException e) { + return null; + } catch (NoSuchPaddingException e) { + return null; + } catch (InvalidAlgorithmParameterException e) { + return null; + } + } } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 61453ab07..ffe587d69 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -755,6 +755,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } break; case Message.ENCRYPTION_AXOLOTL: + message.setAxolotlFingerprint(account.getAxolotlService().getOwnPublicKey().getFingerprint().replaceAll("\\s", "")); if (message.needsUploading()) { if (account.httpUploadAvailable() || message.fixCounterpart()) { this.sendFileMessage(message,delay); @@ -765,7 +766,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa XmppAxolotlMessage axolotlMessage = account.getAxolotlService().fetchAxolotlMessageFromCache(message); if (axolotlMessage == null) { account.getAxolotlService().preparePayloadMessage(message, delay); - message.setAxolotlFingerprint(account.getAxolotlService().getOwnPublicKey().getFingerprint().replaceAll("\\s", "")); } else { packet = mMessageGenerator.generateAxolotlChat(message, axolotlMessage); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java index e15fc5224..e9ca66b7c 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnection.java @@ -2,9 +2,11 @@ package eu.siacs.conversations.xmpp.jingle; import android.content.Intent; import android.net.Uri; -import android.os.SystemClock; import android.util.Log; +import android.util.Pair; +import java.io.InputStream; +import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; @@ -14,13 +16,19 @@ import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import eu.siacs.conversations.Config; +import eu.siacs.conversations.crypto.axolotl.AxolotlService; +import eu.siacs.conversations.crypto.axolotl.OnMessageCreatedCallback; +import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.DownloadableFile; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.entities.TransferablePlaceholder; +import eu.siacs.conversations.persistance.FileBackend; +import eu.siacs.conversations.services.AbstractConnectionManager; import eu.siacs.conversations.services.XmppConnectionService; +import eu.siacs.conversations.utils.Xmlns; import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xmpp.OnIqPacketReceived; import eu.siacs.conversations.xmpp.jid.Jid; @@ -66,8 +74,13 @@ public class JingleConnection implements Transferable { private boolean acceptedAutomatically = false; + private XmppAxolotlMessage mXmppAxolotlMessage; + private JingleTransport transport = null; + private OutputStream mFileOutputStream; + private InputStream mFileInputStream; + private OnIqPacketReceived responseListener = new OnIqPacketReceived() { @Override @@ -113,6 +126,14 @@ public class JingleConnection implements Transferable { } }; + public InputStream getFileInputStream() { + return this.mFileInputStream; + } + + public OutputStream getFileOutputStream() { + return this.mFileOutputStream; + } + private OnProxyActivated onProxyActivated = new OnProxyActivated() { @Override @@ -194,7 +215,22 @@ public class JingleConnection implements Transferable { mXmppConnectionService.sendIqPacket(account,response,null); } - public void init(Message message) { + public void init(final Message message) { + if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { + Conversation conversation = message.getConversation(); + conversation.getAccount().getAxolotlService().prepareKeyTransportMessage(conversation.getContact(), new OnMessageCreatedCallback() { + @Override + public void run(XmppAxolotlMessage xmppAxolotlMessage) { + init(message, xmppAxolotlMessage); + } + }); + } else { + init(message, null); + } + } + + private void init(Message message, XmppAxolotlMessage xmppAxolotlMessage) { + this.mXmppAxolotlMessage = xmppAxolotlMessage; this.contentCreator = "initiator"; this.contentName = this.mJingleConnectionManager.nextRandomId(); this.message = message; @@ -238,8 +274,7 @@ public class JingleConnection implements Transferable { }); mergeCandidate(candidate); } else { - Log.d(Config.LOGTAG, - "no primary candidate of our own was found"); + Log.d(Config.LOGTAG,"no primary candidate of our own was found"); sendInitRequest(); } } @@ -267,13 +302,16 @@ public class JingleConnection implements Transferable { this.contentCreator = content.getAttribute("creator"); this.contentName = content.getAttribute("name"); this.transportId = content.getTransportId(); - this.mergeCandidates(JingleCandidate.parse(content.socks5transport() - .getChildren())); + this.mergeCandidates(JingleCandidate.parse(content.socks5transport().getChildren())); this.fileOffer = packet.getJingleContent().getFileOffer(); mXmppConnectionService.sendIqPacket(account,packet.generateResponse(IqPacket.TYPE.RESULT),null); if (fileOffer != null) { + Element encrypted = fileOffer.findChild("encrypted", AxolotlService.PEP_PREFIX); + if (encrypted != null) { + this.mXmppAxolotlMessage = XmppAxolotlMessage.fromElement(encrypted, packet.getFrom().toBareJid()); + } Element fileSize = fileOffer.findChild("size"); Element fileNameElement = fileOffer.findChild("name"); if (fileNameElement != null) { @@ -319,10 +357,8 @@ public class JingleConnection implements Transferable { message.setBody(Long.toString(size)); conversation.add(message); mXmppConnectionService.updateConversationUi(); - if (size < this.mJingleConnectionManager - .getAutoAcceptFileSize()) { - Log.d(Config.LOGTAG, "auto accepting file from " - + packet.getFrom()); + if (size < this.mJingleConnectionManager.getAutoAcceptFileSize()) { + Log.d(Config.LOGTAG, "auto accepting file from "+ packet.getFrom()); this.acceptedAutomatically = true; this.sendAccept(); } else { @@ -333,22 +369,32 @@ public class JingleConnection implements Transferable { + " allowed size:" + this.mJingleConnectionManager .getAutoAcceptFileSize()); - this.mXmppConnectionService.getNotificationService() - .push(message); + this.mXmppConnectionService.getNotificationService().push(message); } - this.file = this.mXmppConnectionService.getFileBackend() - .getFile(message, false); - if (message.getEncryption() == Message.ENCRYPTION_OTR) { + this.file = this.mXmppConnectionService.getFileBackend().getFile(message, false); + if (mXmppAxolotlMessage != null) { + XmppAxolotlMessage.XmppAxolotlKeyTransportMessage transportMessage = account.getAxolotlService().processReceivingKeyTransportMessage(mXmppAxolotlMessage); + if (transportMessage != null) { + message.setEncryption(Message.ENCRYPTION_AXOLOTL); + this.file.setKey(transportMessage.getKey()); + this.file.setIv(transportMessage.getIv()); + message.setAxolotlFingerprint(transportMessage.getFingerprint()); + } else { + Log.d(Config.LOGTAG,"could not process KeyTransportMessage"); + } + } else if (message.getEncryption() == Message.ENCRYPTION_OTR) { byte[] key = conversation.getSymmetricKey(); if (key == null) { this.sendCancel(); this.fail(); return; } else { - this.file.setKey(key); + this.file.setKeyAndIv(key); } } + this.mFileOutputStream = AbstractConnectionManager.createOutputStream(this.file,message.getEncryption() == Message.ENCRYPTION_AXOLOTL); this.file.setExpectedSize(size); + Log.d(Config.LOGTAG, "receiving file: expecting size of " + this.file.getExpectedSize()); } else { this.sendCancel(); this.fail(); @@ -364,19 +410,30 @@ public class JingleConnection implements Transferable { Content content = new Content(this.contentCreator, this.contentName); if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) { content.setTransportId(this.transportId); - this.file = this.mXmppConnectionService.getFileBackend().getFile( - message, false); + this.file = this.mXmppConnectionService.getFileBackend().getFile(message, false); + Pair pair; if (message.getEncryption() == Message.ENCRYPTION_OTR) { Conversation conversation = this.message.getConversation(); if (!this.mXmppConnectionService.renewSymmetricKey(conversation)) { Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not set symmetric key"); cancel(); } + this.file.setKeyAndIv(conversation.getSymmetricKey()); + pair = AbstractConnectionManager.createInputStream(this.file,false); + this.file.setExpectedSize(pair.second); content.setFileOffer(this.file, true); - this.file.setKey(conversation.getSymmetricKey()); + } else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { + this.file.setKey(mXmppAxolotlMessage.getInnerKey()); + this.file.setIv(mXmppAxolotlMessage.getIV()); + pair = AbstractConnectionManager.createInputStream(this.file,true); + this.file.setExpectedSize(pair.second); + content.setFileOffer(this.file, false).addChild(mXmppAxolotlMessage.toElement()); } else { + pair = AbstractConnectionManager.createInputStream(this.file,false); + this.file.setExpectedSize(pair.second); content.setFileOffer(this.file, false); } + this.mFileInputStream = pair.first; this.transportId = this.mJingleConnectionManager.nextRandomId(); content.setTransportId(this.transportId); content.socks5transport().setChildren(getCandidatesAsElements()); @@ -748,6 +805,8 @@ public class JingleConnection implements Transferable { if (this.transport != null && this.transport instanceof JingleInbandTransport) { this.transport.disconnect(); } + FileBackend.close(mFileInputStream); + FileBackend.close(mFileOutputStream); if (this.message != null) { if (this.responder.equals(account.getJid())) { this.message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_FAILED)); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java index 11055b8f4..ab7ab73b8 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleInbandTransport.java @@ -93,7 +93,7 @@ public class JingleInbandTransport extends JingleTransport { digest.reset(); file.getParentFile().mkdirs(); file.createNewFile(); - this.fileOutputStream = createOutputStream(file); + this.fileOutputStream = connection.getFileOutputStream(); if (this.fileOutputStream == null) { Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not create output stream"); callback.onFileTransferAborted(); @@ -112,15 +112,11 @@ public class JingleInbandTransport extends JingleTransport { this.onFileTransmissionStatusChanged = callback; this.file = file; try { - if (this.file.getKey() != null) { - this.remainingSize = (this.file.getSize() / 16 + 1) * 16; - } else { - this.remainingSize = this.file.getSize(); - } + this.remainingSize = this.file.getExpectedSize(); this.fileSize = this.remainingSize; this.digest = MessageDigest.getInstance("SHA-1"); this.digest.reset(); - fileInputStream = createInputStream(this.file); + fileInputStream = connection.getFileInputStream(); if (fileInputStream == null) { Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could no create input stream"); callback.onFileTransferAborted(); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java index 55039070b..7545dd646 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleSocks5Transport.java @@ -106,13 +106,13 @@ public class JingleSocks5Transport extends JingleTransport { try { MessageDigest digest = MessageDigest.getInstance("SHA-1"); digest.reset(); - fileInputStream = createInputStream(file); //file.createInputStream(); + fileInputStream = connection.getFileInputStream(); if (fileInputStream == null) { Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create input stream"); callback.onFileTransferAborted(); return; } - long size = file.getSize(); + long size = file.getExpectedSize(); long transmitted = 0; int count; byte[] buffer = new byte[8192]; @@ -157,7 +157,7 @@ public class JingleSocks5Transport extends JingleTransport { socket.setSoTimeout(30000); file.getParentFile().mkdirs(); file.createNewFile(); - fileOutputStream = createOutputStream(file); + fileOutputStream = connection.getFileOutputStream(); if (fileOutputStream == null) { callback.onFileTransferAborted(); Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create output stream"); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleTransport.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleTransport.java index 1219794fd..b32111584 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleTransport.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleTransport.java @@ -1,6 +1,13 @@ package eu.siacs.conversations.xmpp.jingle; import android.util.Log; +import android.util.Pair; + +import org.bouncycastle.crypto.engines.AESEngine; +import org.bouncycastle.crypto.modes.AEADBlockCipher; +import org.bouncycastle.crypto.modes.GCMBlockCipher; +import org.bouncycastle.crypto.params.AEADParameters; +import org.bouncycastle.crypto.params.KeyParameter; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -31,58 +38,4 @@ public abstract class JingleTransport { final OnFileTransmissionStatusChanged callback); public abstract void disconnect(); - - protected InputStream createInputStream(DownloadableFile file) { - FileInputStream is; - try { - is = new FileInputStream(file); - if (file.getKey() == null) { - return is; - } - } catch (FileNotFoundException e) { - return null; - } - try { - IvParameterSpec ips = new IvParameterSpec(file.getIv()); - Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(file.getKey(), "AES"), ips); - Log.d(Config.LOGTAG, "opening encrypted input stream"); - return new CipherInputStream(is, cipher); - } catch (InvalidKeyException e) { - return null; - } catch (NoSuchAlgorithmException e) { - return null; - } catch (NoSuchPaddingException e) { - return null; - } catch (InvalidAlgorithmParameterException e) { - return null; - } - } - - protected OutputStream createOutputStream(DownloadableFile file) { - FileOutputStream os; - try { - os = new FileOutputStream(file); - if (file.getKey() == null) { - return os; - } - } catch (FileNotFoundException e) { - return null; - } - try { - IvParameterSpec ips = new IvParameterSpec(file.getIv()); - Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); - cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(file.getKey(), "AES"), ips); - Log.d(Config.LOGTAG, "opening encrypted output stream"); - return new CipherOutputStream(os, cipher); - } catch (InvalidKeyException e) { - return null; - } catch (NoSuchAlgorithmException e) { - return null; - } catch (NoSuchPaddingException e) { - return null; - } catch (InvalidAlgorithmParameterException e) { - return null; - } - } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java index bcadbe778..f752cc5d3 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Content.java @@ -25,17 +25,18 @@ public class Content extends Element { this.transportId = sid; } - public void setFileOffer(DownloadableFile actualFile, boolean otr) { + public Element setFileOffer(DownloadableFile actualFile, boolean otr) { Element description = this.addChild("description", "urn:xmpp:jingle:apps:file-transfer:3"); Element offer = description.addChild("offer"); Element file = offer.addChild("file"); - file.addChild("size").setContent(Long.toString(actualFile.getSize())); + file.addChild("size").setContent(Long.toString(actualFile.getExpectedSize())); if (otr) { file.addChild("name").setContent(actualFile.getName() + ".otr"); } else { file.addChild("name").setContent(actualFile.getName()); } + return file; } public Element getFileOffer() { -- cgit v1.2.3 From 6cd9383e53900e03d324b227c0f01b1537881148 Mon Sep 17 00:00:00 2001 From: Andreas Straub Date: Sat, 1 Aug 2015 18:27:52 +0200 Subject: Let UNTRUSTED/UNDECIDED keys become INACTIVE --- .../crypto/axolotl/AxolotlService.java | 30 +++++--- .../crypto/axolotl/SQLiteAxolotlStore.java | 64 ++---------------- .../crypto/axolotl/XmppAxolotlSession.java | 79 +++++++++++++++++++--- .../eu/siacs/conversations/entities/Message.java | 4 +- .../conversations/persistance/DatabaseBackend.java | 19 +++--- .../conversations/ui/ConversationActivity.java | 4 +- .../siacs/conversations/ui/TrustKeysActivity.java | 18 ++--- .../eu/siacs/conversations/ui/XmppActivity.java | 30 ++++---- .../conversations/ui/adapter/MessageAdapter.java | 6 +- 9 files changed, 138 insertions(+), 116 deletions(-) (limited to 'src/main') 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 7a92a1f77..255939a4f 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -191,11 +191,11 @@ public class AxolotlService { return axolotlStore.getIdentityKeyPair().getPublicKey(); } - public Set getKeysWithTrust(SQLiteAxolotlStore.Trust trust) { + public Set getKeysWithTrust(XmppAxolotlSession.Trust trust) { return axolotlStore.getContactKeysWithTrust(account.getJid().toBareJid().toString(), trust); } - public Set getKeysWithTrust(SQLiteAxolotlStore.Trust trust, Contact contact) { + public Set getKeysWithTrust(XmppAxolotlSession.Trust trust, Contact contact) { return axolotlStore.getContactKeysWithTrust(contact.getJid().toBareJid().toString(), trust); } @@ -241,8 +241,8 @@ public class AxolotlService { } private void setTrustOnSessions(final Jid jid, @NonNull final Set deviceIds, - final SQLiteAxolotlStore.Trust from, - final SQLiteAxolotlStore.Trust to) { + final XmppAxolotlSession.Trust from, + final XmppAxolotlSession.Trust to) { for (Integer deviceId : deviceIds) { AxolotlAddress address = new AxolotlAddress(jid.toBareJid().toString(), deviceId); XmppAxolotlSession session = sessions.get(address); @@ -267,11 +267,19 @@ public class AxolotlService { } Set expiredDevices = new HashSet<>(axolotlStore.getSubDeviceSessions(jid.toBareJid().toString())); expiredDevices.removeAll(deviceIds); - setTrustOnSessions(jid, expiredDevices, SQLiteAxolotlStore.Trust.TRUSTED, - SQLiteAxolotlStore.Trust.INACTIVE); + setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.TRUSTED, + XmppAxolotlSession.Trust.INACTIVE_TRUSTED); + setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.UNDECIDED, + XmppAxolotlSession.Trust.INACTIVE_UNDECIDED); + setTrustOnSessions(jid, expiredDevices, XmppAxolotlSession.Trust.UNTRUSTED, + XmppAxolotlSession.Trust.INACTIVE_UNTRUSTED); Set newDevices = new HashSet<>(deviceIds); - setTrustOnSessions(jid, newDevices, SQLiteAxolotlStore.Trust.INACTIVE, - SQLiteAxolotlStore.Trust.TRUSTED); + setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_TRUSTED, + XmppAxolotlSession.Trust.TRUSTED); + setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_UNDECIDED, + XmppAxolotlSession.Trust.UNDECIDED); + setTrustOnSessions(jid, newDevices, XmppAxolotlSession.Trust.INACTIVE_UNTRUSTED, + XmppAxolotlSession.Trust.UNTRUSTED); this.deviceIds.put(jid, deviceIds); mXmppConnectionService.keyStatusUpdated(); publishOwnDeviceIdIfNeeded(); @@ -291,7 +299,7 @@ public class AxolotlService { } public void purgeKey(IdentityKey identityKey) { - axolotlStore.setFingerprintTrust(identityKey.getFingerprint().replaceAll("\\s", ""), SQLiteAxolotlStore.Trust.COMPROMISED); + axolotlStore.setFingerprintTrust(identityKey.getFingerprint().replaceAll("\\s", ""), XmppAxolotlSession.Trust.COMPROMISED); } public void publishOwnDeviceIdIfNeeded() { @@ -419,11 +427,11 @@ public class AxolotlService { (deviceIds.containsKey(jid) && !deviceIds.get(jid).isEmpty()); } - public SQLiteAxolotlStore.Trust getFingerprintTrust(String fingerprint) { + public XmppAxolotlSession.Trust getFingerprintTrust(String fingerprint) { return axolotlStore.getFingerprintTrust(fingerprint); } - public void setFingerprintTrust(String fingerprint, SQLiteAxolotlStore.Trust trust) { + public void setFingerprintTrust(String fingerprint, XmppAxolotlSession.Trust trust) { axolotlStore.setFingerprintTrust(fingerprint, trust); } diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java index 0c9c4e651..190eb88a8 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java @@ -15,9 +15,7 @@ import org.whispersystems.libaxolotl.state.SessionRecord; import org.whispersystems.libaxolotl.state.SignedPreKeyRecord; import org.whispersystems.libaxolotl.util.KeyHelper; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Set; import eu.siacs.conversations.Config; @@ -51,64 +49,14 @@ public class SQLiteAxolotlStore implements AxolotlStore { private int localRegistrationId; private int currentPreKeyId = 0; - private final LruCache trustCache = - new LruCache(NUM_TRUSTS_TO_CACHE) { + private final LruCache trustCache = + new LruCache(NUM_TRUSTS_TO_CACHE) { @Override - protected Trust create(String fingerprint) { + protected XmppAxolotlSession.Trust create(String fingerprint) { return mXmppConnectionService.databaseBackend.isIdentityKeyTrusted(account, fingerprint); } }; - public enum Trust { - UNDECIDED(0), - TRUSTED(1), - UNTRUSTED(2), - COMPROMISED(3), - INACTIVE(4); - - 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: - return "Inactive " + 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); - } - } - private static IdentityKeyPair generateIdentityKeyPair() { Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Generating axolotl IdentityKeyPair..."); ECKeyPair identityKeyPairKeys = Curve.generateKeyPair(); @@ -258,16 +206,16 @@ public class SQLiteAxolotlStore implements AxolotlStore { return true; } - public Trust getFingerprintTrust(String fingerprint) { + public XmppAxolotlSession.Trust getFingerprintTrust(String fingerprint) { return (fingerprint == null)? null : trustCache.get(fingerprint); } - public void setFingerprintTrust(String fingerprint, Trust trust) { + public void setFingerprintTrust(String fingerprint, XmppAxolotlSession.Trust trust) { mXmppConnectionService.databaseBackend.setIdentityKeyTrust(account, fingerprint, trust); trustCache.remove(fingerprint); } - public Set getContactKeysWithTrust(String bareJid, Trust trust) { + public Set getContactKeysWithTrust(String bareJid, XmppAxolotlSession.Trust trust) { return mXmppConnectionService.databaseBackend.loadIdentityKeys(account, bareJid, trust); } diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java index 46004a1a9..c4053854d 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java @@ -18,6 +18,9 @@ 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; @@ -30,6 +33,62 @@ public class XmppAxolotlSession { 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); + + 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 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 XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress, String fingerprint) { this(account, store, remoteAddress); this.fingerprint = fingerprint; @@ -67,21 +126,21 @@ public class XmppAxolotlSession { this.fresh = false; } - protected void setTrust(SQLiteAxolotlStore.Trust trust) { + protected void setTrust(Trust trust) { sqLiteAxolotlStore.setFingerprintTrust(fingerprint, trust); } - protected SQLiteAxolotlStore.Trust getTrust() { - SQLiteAxolotlStore.Trust trust = sqLiteAxolotlStore.getFingerprintTrust(fingerprint); - return (trust == null) ? SQLiteAxolotlStore.Trust.UNDECIDED : trust; + protected Trust getTrust() { + Trust trust = sqLiteAxolotlStore.getFingerprintTrust(fingerprint); + return (trust == null) ? Trust.UNDECIDED : trust; } @Nullable public byte[] processReceiving(byte[] encryptedKey) { byte[] plaintext = null; - SQLiteAxolotlStore.Trust trust = getTrust(); + Trust trust = getTrust(); switch (trust) { - case INACTIVE: + case INACTIVE_TRUSTED: case UNDECIDED: case UNTRUSTED: case TRUSTED: @@ -110,8 +169,8 @@ public class XmppAxolotlSession { 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); + if (plaintext != null && trust == Trust.INACTIVE_TRUSTED) { + setTrust(Trust.TRUSTED); } break; @@ -126,8 +185,8 @@ public class XmppAxolotlSession { @Nullable public byte[] processSending(@NonNull byte[] outgoingMessage) { - SQLiteAxolotlStore.Trust trust = getTrust(); - if (trust == SQLiteAxolotlStore.Trust.TRUSTED) { + Trust trust = getTrust(); + if (trust == Trust.TRUSTED) { CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage); return ciphertextMessage.serialize(); } else { diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index 6c2a1cc03..0eff99cf7 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -8,7 +8,7 @@ import java.net.URL; import java.util.Arrays; import eu.siacs.conversations.Config; -import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore; +import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; import eu.siacs.conversations.utils.GeoHelper; import eu.siacs.conversations.utils.MimeUtils; import eu.siacs.conversations.utils.UIHelper; @@ -707,7 +707,7 @@ public class Message extends AbstractEntity { public boolean isTrusted() { return conversation.getAccount().getAxolotlService().getFingerprintTrust(axolotlFingerprint) - == SQLiteAxolotlStore.Trust.TRUSTED; + == XmppAxolotlSession.Trust.TRUSTED; } private int getPreviousEncryption() { diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 8ac2884e7..9fe47512b 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -28,6 +28,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import eu.siacs.conversations.Config; import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore; +import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; @@ -844,7 +845,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { return loadIdentityKeys(account, name, null); } - public Set loadIdentityKeys(Account account, String name, SQLiteAxolotlStore.Trust trust) { + public Set loadIdentityKeys(Account account, String name, XmppAxolotlSession.Trust trust) { Set identityKeys = new HashSet<>(); Cursor cursor = getIdentityKeyCursor(account, name, false); @@ -870,7 +871,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { String[] args = { account.getUuid(), name, - String.valueOf(SQLiteAxolotlStore.Trust.TRUSTED.getCode()) + String.valueOf(XmppAxolotlSession.Trust.TRUSTED.getCode()) }; return DatabaseUtils.queryNumEntries(db, SQLiteAxolotlStore.IDENTITIES_TABLENAME, SQLiteAxolotlStore.ACCOUNT + " = ?" @@ -881,10 +882,10 @@ public class DatabaseBackend extends SQLiteOpenHelper { } private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized) { - storeIdentityKey(account, name, own, fingerprint, base64Serialized, SQLiteAxolotlStore.Trust.UNDECIDED); + storeIdentityKey(account, name, own, fingerprint, base64Serialized, XmppAxolotlSession.Trust.UNDECIDED); } - private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized, SQLiteAxolotlStore.Trust trusted) { + private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized, XmppAxolotlSession.Trust trusted) { SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid()); @@ -896,19 +897,19 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.insert(SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values); } - public SQLiteAxolotlStore.Trust isIdentityKeyTrusted(Account account, String fingerprint) { + public XmppAxolotlSession.Trust isIdentityKeyTrusted(Account account, String fingerprint) { Cursor cursor = getIdentityKeyCursor(account, fingerprint); - SQLiteAxolotlStore.Trust trust = null; + XmppAxolotlSession.Trust trust = null; if (cursor.getCount() > 0) { cursor.moveToFirst(); int trustValue = cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.TRUSTED)); - trust = SQLiteAxolotlStore.Trust.fromCode(trustValue); + trust = XmppAxolotlSession.Trust.fromCode(trustValue); } cursor.close(); return trust; } - public boolean setIdentityKeyTrust(Account account, String fingerprint, SQLiteAxolotlStore.Trust trust) { + public boolean setIdentityKeyTrust(Account account, String fingerprint, XmppAxolotlSession.Trust trust) { SQLiteDatabase db = this.getWritableDatabase(); String[] selectionArgs = { account.getUuid(), @@ -928,7 +929,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { } public void storeOwnIdentityKeyPair(Account account, String name, IdentityKeyPair identityKeyPair) { - storeIdentityKey(account, name, true, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT), SQLiteAxolotlStore.Trust.TRUSTED); + storeIdentityKey(account, name, true, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT), XmppAxolotlSession.Trust.TRUSTED); } public void recreateAxolotlDb() { diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index f34e1a55a..7cd048551 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -38,7 +38,7 @@ import de.timroes.android.listview.EnhancedListView; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.axolotl.AxolotlService; -import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore.Trust; +import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Blockable; import eu.siacs.conversations.entities.Contact; @@ -1260,7 +1260,7 @@ public class ConversationActivity extends XmppActivity protected boolean trustKeysIfNeeded(int requestCode, int attachmentChoice) { AxolotlService axolotlService = mSelectedConversation.getAccount().getAxolotlService(); - boolean hasPendingKeys = !axolotlService.getKeysWithTrust(Trust.UNDECIDED, + boolean hasPendingKeys = !axolotlService.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED, mSelectedConversation.getContact()).isEmpty() || !axolotlService.findDevicesWithoutSession(mSelectedConversation).isEmpty(); boolean hasNoTrustedKeys = axolotlService.getNumTrustedKeys(mSelectedConversation.getContact()) == 0; diff --git a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java index d5959b7a8..37ddf5906 100644 --- a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java @@ -16,7 +16,7 @@ import java.util.Map; import java.util.Set; import eu.siacs.conversations.R; -import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore.Trust; +import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; @@ -119,7 +119,7 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate for(final IdentityKey identityKey : ownKeysToTrust.keySet()) { hasOwnKeys = true; addFingerprintRowWithListeners(ownKeys, contact.getAccount(), identityKey, false, - Trust.fromBoolean(ownKeysToTrust.get(identityKey)), false, + XmppAxolotlSession.Trust.fromBoolean(ownKeysToTrust.get(identityKey)), false, new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { @@ -135,7 +135,7 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate for(final IdentityKey identityKey : foreignKeysToTrust.keySet()) { hasForeignKeys = true; addFingerprintRowWithListeners(foreignKeys, contact.getAccount(), identityKey, false, - Trust.fromBoolean(foreignKeysToTrust.get(identityKey)), false, + XmppAxolotlSession.Trust.fromBoolean(foreignKeysToTrust.get(identityKey)), false, new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { @@ -171,11 +171,11 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate } private void getFingerprints(final Account account) { - Set ownKeysSet = account.getAxolotlService().getKeysWithTrust(Trust.UNDECIDED); - Set foreignKeysSet = account.getAxolotlService().getKeysWithTrust(Trust.UNDECIDED, contact); + Set ownKeysSet = account.getAxolotlService().getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED); + Set foreignKeysSet = account.getAxolotlService().getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED, contact); if (hasNoTrustedKeys) { - ownKeysSet.addAll(account.getAxolotlService().getKeysWithTrust(Trust.UNTRUSTED)); - foreignKeysSet.addAll(account.getAxolotlService().getKeysWithTrust(Trust.UNTRUSTED, contact)); + ownKeysSet.addAll(account.getAxolotlService().getKeysWithTrust(XmppAxolotlSession.Trust.UNTRUSTED)); + foreignKeysSet.addAll(account.getAxolotlService().getKeysWithTrust(XmppAxolotlSession.Trust.UNTRUSTED, contact)); } for(final IdentityKey identityKey : ownKeysSet) { if(!ownKeysToTrust.containsKey(identityKey)) { @@ -226,12 +226,12 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate for(IdentityKey identityKey:ownKeysToTrust.keySet()) { contact.getAccount().getAxolotlService().setFingerprintTrust( identityKey.getFingerprint().replaceAll("\\s", ""), - Trust.fromBoolean(ownKeysToTrust.get(identityKey))); + XmppAxolotlSession.Trust.fromBoolean(ownKeysToTrust.get(identityKey))); } for(IdentityKey identityKey:foreignKeysToTrust.keySet()) { contact.getAccount().getAxolotlService().setFingerprintTrust( identityKey.getFingerprint().replaceAll("\\s", ""), - Trust.fromBoolean(foreignKeysToTrust.get(identityKey))); + XmppAxolotlSession.Trust.fromBoolean(foreignKeysToTrust.get(identityKey))); } } diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 64ead283e..3a163ba45 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -70,7 +70,7 @@ import java.util.concurrent.RejectedExecutionException; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; -import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore; +import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; @@ -615,24 +615,22 @@ public abstract class XmppActivity extends Activity { protected boolean addFingerprintRow(LinearLayout keys, final Account account, IdentityKey identityKey, boolean highlight) { final String fingerprint = identityKey.getFingerprint().replaceAll("\\s", ""); - final SQLiteAxolotlStore.Trust trust = account.getAxolotlService() + final XmppAxolotlSession.Trust trust = account.getAxolotlService() .getFingerprintTrust(fingerprint); return addFingerprintRowWithListeners(keys, account, identityKey, highlight, trust, true, new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - if (isChecked != (trust == SQLiteAxolotlStore.Trust.TRUSTED)) { - account.getAxolotlService().setFingerprintTrust(fingerprint, - (isChecked) ? SQLiteAxolotlStore.Trust.TRUSTED : - SQLiteAxolotlStore.Trust.UNTRUSTED); - } + account.getAxolotlService().setFingerprintTrust(fingerprint, + (isChecked) ? XmppAxolotlSession.Trust.TRUSTED : + XmppAxolotlSession.Trust.UNTRUSTED); } }, new View.OnClickListener() { @Override public void onClick(View v) { account.getAxolotlService().setFingerprintTrust(fingerprint, - SQLiteAxolotlStore.Trust.UNTRUSTED); + XmppAxolotlSession.Trust.UNTRUSTED); v.setEnabled(true); } } @@ -643,12 +641,12 @@ public abstract class XmppActivity extends Activity { protected boolean addFingerprintRowWithListeners(LinearLayout keys, final Account account, final IdentityKey identityKey, boolean highlight, - SQLiteAxolotlStore.Trust trust, + XmppAxolotlSession.Trust trust, boolean showTag, CompoundButton.OnCheckedChangeListener onCheckedChangeListener, View.OnClickListener onClickListener) { - if (trust == SQLiteAxolotlStore.Trust.COMPROMISED) { + if (trust == XmppAxolotlSession.Trust.COMPROMISED) { return false; } View view = getLayoutInflater().inflate(R.layout.contact_key, keys, false); @@ -669,7 +667,7 @@ public abstract class XmppActivity extends Activity { switch (trust) { case UNTRUSTED: case TRUSTED: - trustToggle.setChecked(trust == SQLiteAxolotlStore.Trust.TRUSTED, false); + trustToggle.setChecked(trust == XmppAxolotlSession.Trust.TRUSTED, false); trustToggle.setEnabled(true); key.setTextColor(getPrimaryTextColor()); keyType.setTextColor(getSecondaryTextColor()); @@ -680,7 +678,15 @@ public abstract class XmppActivity extends Activity { key.setTextColor(getPrimaryTextColor()); keyType.setTextColor(getSecondaryTextColor()); break; - case INACTIVE: + case INACTIVE_UNTRUSTED: + case INACTIVE_UNDECIDED: + trustToggle.setOnClickListener(null); + trustToggle.setChecked(false, false); + trustToggle.setEnabled(false); + key.setTextColor(getTertiaryTextColor()); + keyType.setTextColor(getTertiaryTextColor()); + break; + case INACTIVE_TRUSTED: trustToggle.setOnClickListener(null); trustToggle.setChecked(true, false); trustToggle.setEnabled(false); diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index 76da42c4c..aec11b762 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -27,7 +27,7 @@ import android.widget.Toast; import java.util.List; import eu.siacs.conversations.R; -import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore; +import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; @@ -169,11 +169,11 @@ public class MessageAdapter extends ArrayAdapter { } else { viewHolder.indicator.setVisibility(View.VISIBLE); if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { - SQLiteAxolotlStore.Trust trust = message.getConversation() + XmppAxolotlSession.Trust trust = message.getConversation() .getAccount().getAxolotlService().getFingerprintTrust( message.getAxolotlFingerprint()); - if(trust == null || trust != SQLiteAxolotlStore.Trust.TRUSTED) { + if(trust == null || trust != XmppAxolotlSession.Trust.TRUSTED) { viewHolder.indicator.setColorFilter(activity.getWarningTextColor()); viewHolder.indicator.setAlpha(1.0f); } else { -- cgit v1.2.3 From f9dec7cf8648ff49bb8ea3b706701b25246309bf Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 1 Aug 2015 22:23:58 +0200 Subject: fixed calculated file size in http slot request --- .../conversations/http/HttpUploadConnection.java | 28 ++++++++++------------ 1 file changed, 13 insertions(+), 15 deletions(-) (limited to 'src/main') diff --git a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java index b21d0d412..8c7a69c01 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java @@ -47,7 +47,8 @@ public class HttpUploadConnection implements Transferable { private byte[] key = null; private long transmitted = 0; - private int expected = 1; + + private InputStream mFileInputStream; public HttpUploadConnection(HttpConnectionManager httpConnectionManager) { this.mHttpConnectionManager = httpConnectionManager; @@ -71,7 +72,7 @@ public class HttpUploadConnection implements Transferable { @Override public int getProgress() { - return (int) ((((double) transmitted) / expected) * 100); + return (int) ((((double) transmitted) / file.getExpectedSize()) * 100); } @Override @@ -82,18 +83,17 @@ public class HttpUploadConnection implements Transferable { private void fail() { mHttpConnectionManager.finishUploadConnection(this); message.setTransferable(null); - mXmppConnectionService.markMessage(message,Message.STATUS_SEND_FAILED); + mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED); + FileBackend.close(mFileInputStream); } public void init(Message message, boolean delay) { this.message = message; message.setTransferable(this); - mXmppConnectionService.markMessage(message,Message.STATUS_UNSEND); + mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND); this.account = message.getConversation().getAccount(); this.file = mXmppConnectionService.getFileBackend().getFile(message, false); - this.file.setExpectedSize(this.file.getSize()); this.delayed = delay; - if (Config.ENCRYPT_ON_HTTP_UPLOADED || message.getEncryption() == Message.ENCRYPTION_AXOLOTL || message.getEncryption() == Message.ENCRYPTION_OTR) { @@ -101,7 +101,9 @@ public class HttpUploadConnection implements Transferable { mXmppConnectionService.getRNG().nextBytes(this.key); this.file.setKeyAndIv(this.key); } - + Pair pair = AbstractConnectionManager.createInputStream(file,true); + this.file.setExpectedSize(pair.second); + this.mFileInputStream = pair.first; Jid host = account.getXmppConnection().findDiscoItemByFeature(Xmlns.HTTP_UPLOAD); IqPacket request = mXmppConnectionService.getIqGenerator().requestHttpUploadSlot(host,file); mXmppConnectionService.sendIqPacket(account, request, new OnIqPacketReceived() { @@ -138,7 +140,6 @@ public class HttpUploadConnection implements Transferable { private void upload() { OutputStream os = null; - InputStream is = null; HttpURLConnection connection = null; try { Log.d(Config.LOGTAG, "uploading to " + mPutUrl.toString()); @@ -146,25 +147,22 @@ public class HttpUploadConnection implements Transferable { if (connection instanceof HttpsURLConnection) { mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, true); } - Pair pair = AbstractConnectionManager.createInputStream(file,true); - is = pair.first; - expected = pair.second; connection.setRequestMethod("PUT"); - connection.setFixedLengthStreamingMode(expected); + connection.setFixedLengthStreamingMode((int) file.getExpectedSize()); connection.setDoOutput(true); connection.connect(); os = connection.getOutputStream(); transmitted = 0; int count = -1; byte[] buffer = new byte[4096]; - while (((count = is.read(buffer)) != -1) && !canceled) { + while (((count = mFileInputStream.read(buffer)) != -1) && !canceled) { transmitted += count; os.write(buffer, 0, count); mXmppConnectionService.updateConversationUi(); } os.flush(); os.close(); - is.close(); + mFileInputStream.close(); int code = connection.getResponseCode(); if (code == 200 || code == 201) { Log.d(Config.LOGTAG, "finished uploading file"); @@ -205,7 +203,7 @@ public class HttpUploadConnection implements Transferable { Log.d(Config.LOGTAG,"http upload failed "+e.getMessage()); fail(); } finally { - FileBackend.close(is); + FileBackend.close(mFileInputStream); FileBackend.close(os); if (connection != null) { connection.disconnect(); -- cgit v1.2.3 From 5529337da38fce1f491407eb40480dc7c5782735 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 1 Aug 2015 22:37:17 +0200 Subject: use content-type in http slot request and stick with during upload --- src/main/java/eu/siacs/conversations/generator/IqGenerator.java | 5 ++++- src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) (limited to 'src/main') diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index 88ff8f46c..898d218e4 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -254,12 +254,15 @@ public class IqGenerator extends AbstractGenerator { return packet; } - public IqPacket requestHttpUploadSlot(Jid host, DownloadableFile file) { + public IqPacket requestHttpUploadSlot(Jid host, DownloadableFile file, String mime) { IqPacket packet = new IqPacket(IqPacket.TYPE.GET); packet.setTo(host); Element request = packet.addChild("request",Xmlns.HTTP_UPLOAD); request.addChild("filename").setContent(file.getName()); request.addChild("size").setContent(String.valueOf(file.getExpectedSize())); + if (mime != null) { + request.addChild("content-type", mime); + } return packet; } } diff --git a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java index 8c7a69c01..768915a94 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java @@ -41,6 +41,7 @@ public class HttpUploadConnection implements Transferable { private Account account; private DownloadableFile file; private Message message; + private String mime; private URL mGetUrl; private URL mPutUrl; @@ -93,6 +94,7 @@ public class HttpUploadConnection implements Transferable { mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND); this.account = message.getConversation().getAccount(); this.file = mXmppConnectionService.getFileBackend().getFile(message, false); + this.mime = this.file.getMimeType(); this.delayed = delay; if (Config.ENCRYPT_ON_HTTP_UPLOADED || message.getEncryption() == Message.ENCRYPTION_AXOLOTL @@ -105,7 +107,7 @@ public class HttpUploadConnection implements Transferable { this.file.setExpectedSize(pair.second); this.mFileInputStream = pair.first; Jid host = account.getXmppConnection().findDiscoItemByFeature(Xmlns.HTTP_UPLOAD); - IqPacket request = mXmppConnectionService.getIqGenerator().requestHttpUploadSlot(host,file); + IqPacket request = mXmppConnectionService.getIqGenerator().requestHttpUploadSlot(host,file,mime); mXmppConnectionService.sendIqPacket(account, request, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { @@ -149,6 +151,7 @@ public class HttpUploadConnection implements Transferable { } connection.setRequestMethod("PUT"); connection.setFixedLengthStreamingMode((int) file.getExpectedSize()); + connection.setRequestProperty("Content-Type", mime == null ? "application/octet-stream" : mime); connection.setDoOutput(true); connection.connect(); os = connection.getOutputStream(); -- cgit v1.2.3 From 2bf30bb296eb92912fc5307c4831861068c65d16 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 1 Aug 2015 23:36:59 +0200 Subject: temporarily renamed new encryption method to 'Multi-End' until a better name has been found --- src/main/res/values/strings.xml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'src/main') diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index e0e472bf9..722eec2d7 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -80,7 +80,7 @@ Choose presence to contact Send plain text message Send OTR encrypted message - Send Axolotl encrypted message + Send Multi-End encrypted message Send OpenPGP encrypted message Your nickname has been changed Send unencrypted @@ -155,7 +155,7 @@ Plain text OTR OpenPGP - Axolotl + Multi-End Edit account Delete account Temporarily disable @@ -208,14 +208,14 @@ Reception failed Your fingerprint OTR fingerprint - Axolotl fingerprint - Axolotl fingerprint of message - Own Axolotl fingerprint + Multi-End fingerprint + Multi-End fingerprint of message + Own Multi-End fingerprint Other devices - Trust Axolotl Keys + Trust Multi-End Keys Fetching keys... Done - Other own Axolotl Devices + Other own Multi-End Devices Verify Decrypt Conferences @@ -322,7 +322,7 @@ Conference name Use room’s subject instead of JID to identify conferences OTR fingerprint copied to clipboard! - Axolotl fingerprint copied to clipboard! + Multi-End fingerprint copied to clipboard! You are banned from this conference This conference is members only You have been kicked from this conference @@ -389,11 +389,11 @@ Reset Account avatar Copy OTR fingerprint to clipboard - Copy Axolotl fingerprint to clipboard + Copy Multi-End fingerprint to clipboard Copy Axolotl fingerprint to clipboard Wipe other devices from PEP Clear devices - Are you sure you want to clear all other devices from the axolotl announcement? The next time your devices connect, they will reannounce themselves, but they might not receive messages sent in the meantime. + Are you sure you want to clear all other devices from the Multi-End announcement? The next time your devices connect, they will reannounce themselves, but they might not receive messages sent in the meantime. Purge key Are you sure you want to purge this key? It will irreversibly be considered compromised, and you can never build a session with it again. -- cgit v1.2.3 From a6bbe4d4ce47bcf662e4cebd2fcae361ffad702e Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 1 Aug 2015 23:37:41 +0200 Subject: lets try jpeg for a while --- src/main/java/eu/siacs/conversations/Config.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/main') diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index d79a12f22..bef00d04d 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -20,7 +20,7 @@ public final class Config { public static final Bitmap.CompressFormat AVATAR_FORMAT = Bitmap.CompressFormat.WEBP; public static final int IMAGE_SIZE = 1920; - public static final Bitmap.CompressFormat IMAGE_FORMAT = Bitmap.CompressFormat.WEBP; + public static final Bitmap.CompressFormat IMAGE_FORMAT = Bitmap.CompressFormat.JPEG; public static final int IMAGE_QUALITY = 75; public static final int MESSAGE_MERGE_WINDOW = 20; -- cgit v1.2.3