Compare commits
37 Commits
develop
...
axolotl-al
Author | SHA1 | Date |
---|---|---|
![]() |
2afb36d891 | |
![]() |
bdc9f9a44f | |
![]() |
b1e719bd8b | |
![]() |
ce4b86e6d4 | |
![]() |
34f90f2eb7 | |
![]() |
2f487c7947 | |
![]() |
121919def1 | |
![]() |
6b0d286518 | |
![]() |
9e3419722b | |
![]() |
4f6ca6fb63 | |
![]() |
d5b3557157 | |
![]() |
9d780a382a | |
![]() |
7cdf2a9946 | |
![]() |
30403a70f2 | |
![]() |
4b0279a6ef | |
![]() |
3b8dfafecd | |
![]() |
835584ae3b | |
![]() |
4cc4e81b8e | |
![]() |
72619de889 | |
![]() |
12fc24dd42 | |
![]() |
c5596b34bc | |
![]() |
9206a49b79 | |
![]() |
7680a24180 | |
![]() |
c1116b6066 | |
![]() |
ab2f85d2e8 | |
![]() |
a58d5e8ce3 | |
![]() |
ae75c571df | |
![]() |
ba9520729f | |
![]() |
287ce131d8 | |
![]() |
046a2d6045 | |
![]() |
71c0a75ec9 | |
![]() |
0423852cb8 | |
![]() |
e8e126f2ce | |
![]() |
61f18d4dfc | |
![]() |
6805abbef0 | |
![]() |
0917a75705 | |
![]() |
f16b77d382 |
|
@ -36,6 +36,7 @@ dependencies {
|
||||||
compile 'de.measite.minidns:minidns:0.1.3'
|
compile 'de.measite.minidns:minidns:0.1.3'
|
||||||
compile 'de.timroes.android:EnhancedListView:0.3.4'
|
compile 'de.timroes.android:EnhancedListView:0.3.4'
|
||||||
compile 'me.leolin:ShortcutBadger:1.1.1@aar'
|
compile 'me.leolin:ShortcutBadger:1.1.1@aar'
|
||||||
|
compile 'org.whispersystems:axolotl-android:1.3.4'
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,4 @@
|
||||||
|
package eu.siacs.conversations.crypto.axolotl;
|
||||||
|
|
||||||
|
public class NoSessionsCreatedException extends Throwable{
|
||||||
|
}
|
|
@ -0,0 +1,195 @@
|
||||||
|
package eu.siacs.conversations.crypto.axolotl;
|
||||||
|
|
||||||
|
import android.util.Base64;
|
||||||
|
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.crypto.BadPaddingException;
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
|
import javax.crypto.KeyGenerator;
|
||||||
|
import javax.crypto.NoSuchPaddingException;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.entities.Contact;
|
||||||
|
import eu.siacs.conversations.xml.Element;
|
||||||
|
import eu.siacs.conversations.xmpp.jid.Jid;
|
||||||
|
|
||||||
|
public class XmppAxolotlMessage {
|
||||||
|
private byte[] innerKey;
|
||||||
|
private byte[] ciphertext;
|
||||||
|
private byte[] iv;
|
||||||
|
private final Set<XmppAxolotlMessageHeader> headers;
|
||||||
|
private final Jid from;
|
||||||
|
private final int sourceDeviceId;
|
||||||
|
|
||||||
|
public static class XmppAxolotlMessageHeader {
|
||||||
|
private final int recipientDeviceId;
|
||||||
|
private final byte[] content;
|
||||||
|
|
||||||
|
public XmppAxolotlMessageHeader(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);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Argument not a <header> Element!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getRecipientDeviceId() {
|
||||||
|
return recipientDeviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getContents() {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Element toXml() {
|
||||||
|
Element headerElement = new Element("header");
|
||||||
|
// TODO: generate XML
|
||||||
|
headerElement.setAttribute("rid", getRecipientDeviceId());
|
||||||
|
headerElement.setContent(Base64.encodeToString(getContents(), Base64.DEFAULT));
|
||||||
|
return headerElement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class XmppAxolotlPlaintextMessage {
|
||||||
|
private final AxolotlService.XmppAxolotlSession session;
|
||||||
|
private final String plaintext;
|
||||||
|
private final String fingerprint;
|
||||||
|
|
||||||
|
public XmppAxolotlPlaintextMessage(AxolotlService.XmppAxolotlSession session, String plaintext, String fingerprint) {
|
||||||
|
this.session = session;
|
||||||
|
this.plaintext = plaintext;
|
||||||
|
this.fingerprint = fingerprint;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPlaintext() {
|
||||||
|
return plaintext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AxolotlService.XmppAxolotlSession getSession() {
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFingerprint() {
|
||||||
|
return fingerprint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public XmppAxolotlMessage(Jid from, Element axolotlMessage) {
|
||||||
|
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));
|
||||||
|
break;
|
||||||
|
case "message":
|
||||||
|
iv = Base64.decode(child.getAttribute("iv"),Base64.DEFAULT);
|
||||||
|
ciphertext = Base64.decode(child.getContent(),Base64.DEFAULT);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public XmppAxolotlMessage(Jid from, int sourceDeviceId, String plaintext) {
|
||||||
|
this.from = from;
|
||||||
|
this.sourceDeviceId = sourceDeviceId;
|
||||||
|
this.headers = new HashSet<>();
|
||||||
|
this.encrypt(plaintext);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void encrypt(String plaintext) {
|
||||||
|
try {
|
||||||
|
KeyGenerator generator = KeyGenerator.getInstance("AES");
|
||||||
|
generator.init(128);
|
||||||
|
SecretKey secretKey = generator.generateKey();
|
||||||
|
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
|
||||||
|
this.innerKey = secretKey.getEncoded();
|
||||||
|
this.iv = cipher.getIV();
|
||||||
|
this.ciphertext = cipher.doFinal(plaintext.getBytes());
|
||||||
|
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
|
||||||
|
| IllegalBlockSizeException | BadPaddingException e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Jid getFrom() {
|
||||||
|
return this.from;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSenderDeviceId() {
|
||||||
|
return sourceDeviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getCiphertext() {
|
||||||
|
return ciphertext;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<XmppAxolotlMessageHeader> getHeaders() {
|
||||||
|
return headers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addHeader(XmppAxolotlMessageHeader header) {
|
||||||
|
headers.add(header);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getInnerKey(){
|
||||||
|
return innerKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getIV() {
|
||||||
|
return this.iv;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 payload = message.addChild("message");
|
||||||
|
payload.setAttribute("iv",Base64.encodeToString(iv, Base64.DEFAULT));
|
||||||
|
payload.setContent(Base64.encodeToString(ciphertext,Base64.DEFAULT));
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public XmppAxolotlPlaintextMessage decrypt(AxolotlService.XmppAxolotlSession session, byte[] key, String fingerprint) {
|
||||||
|
XmppAxolotlPlaintextMessage plaintextMessage = null;
|
||||||
|
try {
|
||||||
|
|
||||||
|
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
|
||||||
|
SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
|
||||||
|
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 e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
return plaintextMessage;
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.crypto.OtrService;
|
import eu.siacs.conversations.crypto.OtrService;
|
||||||
|
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
import eu.siacs.conversations.xmpp.XmppConnection;
|
import eu.siacs.conversations.xmpp.XmppConnection;
|
||||||
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
|
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
|
||||||
|
@ -122,6 +123,7 @@ public class Account extends AbstractEntity {
|
||||||
protected String avatar;
|
protected String avatar;
|
||||||
protected boolean online = false;
|
protected boolean online = false;
|
||||||
private OtrService mOtrService = null;
|
private OtrService mOtrService = null;
|
||||||
|
private AxolotlService axolotlService = null;
|
||||||
private XmppConnection xmppConnection = null;
|
private XmppConnection xmppConnection = null;
|
||||||
private long mEndGracePeriod = 0L;
|
private long mEndGracePeriod = 0L;
|
||||||
private String otrFingerprint;
|
private String otrFingerprint;
|
||||||
|
@ -254,6 +256,10 @@ public class Account extends AbstractEntity {
|
||||||
return keys;
|
return keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getKey(final String name) {
|
||||||
|
return this.keys.optString(name, null);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean setKey(final String keyName, final String keyValue) {
|
public boolean setKey(final String keyName, final String keyValue) {
|
||||||
try {
|
try {
|
||||||
this.keys.put(keyName, keyValue);
|
this.keys.put(keyName, keyValue);
|
||||||
|
@ -277,8 +283,13 @@ public class Account extends AbstractEntity {
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AxolotlService getAxolotlService() {
|
||||||
|
return axolotlService;
|
||||||
|
}
|
||||||
|
|
||||||
public void initAccountServices(final XmppConnectionService context) {
|
public void initAccountServices(final XmppConnectionService context) {
|
||||||
this.mOtrService = new OtrService(context, this);
|
this.mOtrService = new OtrService(context, this);
|
||||||
|
this.axolotlService = new AxolotlService(this, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public OtrService getOtrService() {
|
public OtrService getOtrService() {
|
||||||
|
|
|
@ -2,15 +2,20 @@ package eu.siacs.conversations.entities;
|
||||||
|
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
import android.util.Base64;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
import org.whispersystems.libaxolotl.IdentityKey;
|
||||||
|
import org.whispersystems.libaxolotl.InvalidKeyException;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.utils.UIHelper;
|
import eu.siacs.conversations.utils.UIHelper;
|
||||||
import eu.siacs.conversations.xml.Element;
|
import eu.siacs.conversations.xml.Element;
|
||||||
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
|
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
|
||||||
|
@ -183,6 +188,7 @@ public class Contact implements ListItem, Blockable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public ContentValues getContentValues() {
|
public ContentValues getContentValues() {
|
||||||
|
synchronized (this.keys) {
|
||||||
final ContentValues values = new ContentValues();
|
final ContentValues values = new ContentValues();
|
||||||
values.put(ACCOUNT, accountUuid);
|
values.put(ACCOUNT, accountUuid);
|
||||||
values.put(SYSTEMNAME, systemName);
|
values.put(SYSTEMNAME, systemName);
|
||||||
|
@ -198,6 +204,7 @@ public class Contact implements ListItem, Blockable {
|
||||||
values.put(GROUPS, groups.toString());
|
values.put(GROUPS, groups.toString());
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public int getSubscription() {
|
public int getSubscription() {
|
||||||
return this.subscription;
|
return this.subscription;
|
||||||
|
@ -281,6 +288,7 @@ public class Contact implements ListItem, Blockable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public ArrayList<String> getOtrFingerprints() {
|
public ArrayList<String> getOtrFingerprints() {
|
||||||
|
synchronized (this.keys) {
|
||||||
final ArrayList<String> fingerprints = new ArrayList<String>();
|
final ArrayList<String> fingerprints = new ArrayList<String>();
|
||||||
try {
|
try {
|
||||||
if (this.keys.has("otr_fingerprints")) {
|
if (this.keys.has("otr_fingerprints")) {
|
||||||
|
@ -297,8 +305,9 @@ public class Contact implements ListItem, Blockable {
|
||||||
}
|
}
|
||||||
return fingerprints;
|
return fingerprints;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
public boolean addOtrFingerprint(String print) {
|
public boolean addOtrFingerprint(String print) {
|
||||||
|
synchronized (this.keys) {
|
||||||
if (getOtrFingerprints().contains(print)) {
|
if (getOtrFingerprints().contains(print)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -306,7 +315,6 @@ public class Contact implements ListItem, Blockable {
|
||||||
JSONArray fingerprints;
|
JSONArray fingerprints;
|
||||||
if (!this.keys.has("otr_fingerprints")) {
|
if (!this.keys.has("otr_fingerprints")) {
|
||||||
fingerprints = new JSONArray();
|
fingerprints = new JSONArray();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
fingerprints = this.keys.getJSONArray("otr_fingerprints");
|
fingerprints = this.keys.getJSONArray("otr_fingerprints");
|
||||||
}
|
}
|
||||||
|
@ -317,8 +325,10 @@ public class Contact implements ListItem, Blockable {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public long getPgpKeyId() {
|
public long getPgpKeyId() {
|
||||||
|
synchronized (this.keys) {
|
||||||
if (this.keys.has("pgp_keyid")) {
|
if (this.keys.has("pgp_keyid")) {
|
||||||
try {
|
try {
|
||||||
return this.keys.getLong("pgp_keyid");
|
return this.keys.getLong("pgp_keyid");
|
||||||
|
@ -329,12 +339,14 @@ public class Contact implements ListItem, Blockable {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void setPgpKeyId(long keyId) {
|
public void setPgpKeyId(long keyId) {
|
||||||
|
synchronized (this.keys) {
|
||||||
try {
|
try {
|
||||||
this.keys.put("pgp_keyid", keyId);
|
this.keys.put("pgp_keyid", keyId);
|
||||||
} catch (final JSONException ignored) {
|
} catch (final JSONException ignored) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -441,6 +453,7 @@ public class Contact implements ListItem, Blockable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean deleteOtrFingerprint(String fingerprint) {
|
public boolean deleteOtrFingerprint(String fingerprint) {
|
||||||
|
synchronized (this.keys) {
|
||||||
boolean success = false;
|
boolean success = false;
|
||||||
try {
|
try {
|
||||||
if (this.keys.has("otr_fingerprints")) {
|
if (this.keys.has("otr_fingerprints")) {
|
||||||
|
@ -461,6 +474,7 @@ public class Contact implements ListItem, Blockable {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public boolean trusted() {
|
public boolean trusted() {
|
||||||
return getOption(Options.FROM) && getOption(Options.TO);
|
return getOption(Options.FROM) && getOption(Options.TO);
|
||||||
|
|
|
@ -179,11 +179,11 @@ public class Conversation extends AbstractEntity implements Blockable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void findUnsentMessagesWithOtrEncryption(OnMessageFound onMessageFound) {
|
public void findUnsentMessagesWithEncryption(int encryptionType, OnMessageFound onMessageFound) {
|
||||||
synchronized (this.messages) {
|
synchronized (this.messages) {
|
||||||
for (Message message : this.messages) {
|
for (Message message : this.messages) {
|
||||||
if ((message.getStatus() == Message.STATUS_UNSEND || message.getStatus() == Message.STATUS_WAITING)
|
if ((message.getStatus() == Message.STATUS_UNSEND || message.getStatus() == Message.STATUS_WAITING)
|
||||||
&& (message.getEncryption() == Message.ENCRYPTION_OTR)) {
|
&& (message.getEncryption() == encryptionType)) {
|
||||||
onMessageFound.onMessageFound(message);
|
onMessageFound.onMessageFound(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import java.net.URL;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
|
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
||||||
import eu.siacs.conversations.utils.GeoHelper;
|
import eu.siacs.conversations.utils.GeoHelper;
|
||||||
import eu.siacs.conversations.utils.MimeUtils;
|
import eu.siacs.conversations.utils.MimeUtils;
|
||||||
import eu.siacs.conversations.utils.UIHelper;
|
import eu.siacs.conversations.utils.UIHelper;
|
||||||
|
@ -34,6 +35,7 @@ public class Message extends AbstractEntity {
|
||||||
public static final int ENCRYPTION_OTR = 2;
|
public static final int ENCRYPTION_OTR = 2;
|
||||||
public static final int ENCRYPTION_DECRYPTED = 3;
|
public static final int ENCRYPTION_DECRYPTED = 3;
|
||||||
public static final int ENCRYPTION_DECRYPTION_FAILED = 4;
|
public static final int ENCRYPTION_DECRYPTION_FAILED = 4;
|
||||||
|
public static final int ENCRYPTION_AXOLOTL = 5;
|
||||||
|
|
||||||
public static final int TYPE_TEXT = 0;
|
public static final int TYPE_TEXT = 0;
|
||||||
public static final int TYPE_IMAGE = 1;
|
public static final int TYPE_IMAGE = 1;
|
||||||
|
@ -52,6 +54,7 @@ public class Message extends AbstractEntity {
|
||||||
public static final String REMOTE_MSG_ID = "remoteMsgId";
|
public static final String REMOTE_MSG_ID = "remoteMsgId";
|
||||||
public static final String SERVER_MSG_ID = "serverMsgId";
|
public static final String SERVER_MSG_ID = "serverMsgId";
|
||||||
public static final String RELATIVE_FILE_PATH = "relativeFilePath";
|
public static final String RELATIVE_FILE_PATH = "relativeFilePath";
|
||||||
|
public static final String FINGERPRINT = "axolotl_fingerprint";
|
||||||
public static final String ME_COMMAND = "/me ";
|
public static final String ME_COMMAND = "/me ";
|
||||||
|
|
||||||
|
|
||||||
|
@ -73,6 +76,7 @@ public class Message extends AbstractEntity {
|
||||||
protected Downloadable downloadable = null;
|
protected Downloadable downloadable = null;
|
||||||
private Message mNextMessage = null;
|
private Message mNextMessage = null;
|
||||||
private Message mPreviousMessage = null;
|
private Message mPreviousMessage = null;
|
||||||
|
private String axolotlFingerprint = null;
|
||||||
|
|
||||||
private Message() {
|
private Message() {
|
||||||
|
|
||||||
|
@ -94,6 +98,7 @@ public class Message extends AbstractEntity {
|
||||||
TYPE_TEXT,
|
TYPE_TEXT,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
null,
|
||||||
null);
|
null);
|
||||||
this.conversation = conversation;
|
this.conversation = conversation;
|
||||||
}
|
}
|
||||||
|
@ -101,7 +106,7 @@ public class Message extends AbstractEntity {
|
||||||
private Message(final String uuid, final String conversationUUid, final Jid counterpart,
|
private Message(final String uuid, final String conversationUUid, final Jid counterpart,
|
||||||
final Jid trueCounterpart, final String body, final long timeSent,
|
final Jid trueCounterpart, final String body, final long timeSent,
|
||||||
final int encryption, final int status, final int type, final String remoteMsgId,
|
final int encryption, final int status, final int type, final String remoteMsgId,
|
||||||
final String relativeFilePath, final String serverMsgId) {
|
final String relativeFilePath, final String serverMsgId, final String fingerprint) {
|
||||||
this.uuid = uuid;
|
this.uuid = uuid;
|
||||||
this.conversationUuid = conversationUUid;
|
this.conversationUuid = conversationUUid;
|
||||||
this.counterpart = counterpart;
|
this.counterpart = counterpart;
|
||||||
|
@ -114,6 +119,7 @@ public class Message extends AbstractEntity {
|
||||||
this.remoteMsgId = remoteMsgId;
|
this.remoteMsgId = remoteMsgId;
|
||||||
this.relativeFilePath = relativeFilePath;
|
this.relativeFilePath = relativeFilePath;
|
||||||
this.serverMsgId = serverMsgId;
|
this.serverMsgId = serverMsgId;
|
||||||
|
this.axolotlFingerprint = fingerprint;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Message fromCursor(Cursor cursor) {
|
public static Message fromCursor(Cursor cursor) {
|
||||||
|
@ -150,7 +156,8 @@ public class Message extends AbstractEntity {
|
||||||
cursor.getInt(cursor.getColumnIndex(TYPE)),
|
cursor.getInt(cursor.getColumnIndex(TYPE)),
|
||||||
cursor.getString(cursor.getColumnIndex(REMOTE_MSG_ID)),
|
cursor.getString(cursor.getColumnIndex(REMOTE_MSG_ID)),
|
||||||
cursor.getString(cursor.getColumnIndex(RELATIVE_FILE_PATH)),
|
cursor.getString(cursor.getColumnIndex(RELATIVE_FILE_PATH)),
|
||||||
cursor.getString(cursor.getColumnIndex(SERVER_MSG_ID)));
|
cursor.getString(cursor.getColumnIndex(SERVER_MSG_ID)),
|
||||||
|
cursor.getString(cursor.getColumnIndex(FINGERPRINT)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Message createStatusMessage(Conversation conversation, String body) {
|
public static Message createStatusMessage(Conversation conversation, String body) {
|
||||||
|
@ -184,6 +191,7 @@ public class Message extends AbstractEntity {
|
||||||
values.put(REMOTE_MSG_ID, remoteMsgId);
|
values.put(REMOTE_MSG_ID, remoteMsgId);
|
||||||
values.put(RELATIVE_FILE_PATH, relativeFilePath);
|
values.put(RELATIVE_FILE_PATH, relativeFilePath);
|
||||||
values.put(SERVER_MSG_ID, serverMsgId);
|
values.put(SERVER_MSG_ID, serverMsgId);
|
||||||
|
values.put(FINGERPRINT, axolotlFingerprint);
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -653,4 +661,8 @@ public class Message extends AbstractEntity {
|
||||||
public int width = 0;
|
public int width = 0;
|
||||||
public int height = 0;
|
public int height = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setAxolotlFingerprint(String fingerprint) {
|
||||||
|
this.axolotlFingerprint = fingerprint;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
import eu.siacs.conversations.utils.PhoneHelper;
|
import eu.siacs.conversations.utils.PhoneHelper;
|
||||||
|
|
||||||
|
@ -28,7 +29,8 @@ public abstract class AbstractGenerator {
|
||||||
"urn:xmpp:avatar:metadata+notify",
|
"urn:xmpp:avatar:metadata+notify",
|
||||||
"urn:xmpp:ping",
|
"urn:xmpp:ping",
|
||||||
"jabber:iq:version",
|
"jabber:iq:version",
|
||||||
"http://jabber.org/protocol/chatstates"};
|
"http://jabber.org/protocol/chatstates",
|
||||||
|
AxolotlService.PEP_DEVICE_LIST+"+notify"};
|
||||||
private final String[] MESSAGE_CONFIRMATION_FEATURES = {
|
private final String[] MESSAGE_CONFIRMATION_FEATURES = {
|
||||||
"urn:xmpp:chat-markers:0",
|
"urn:xmpp:chat-markers:0",
|
||||||
"urn:xmpp:receipts"
|
"urn:xmpp:receipts"
|
||||||
|
|
|
@ -1,9 +1,18 @@
|
||||||
package eu.siacs.conversations.generator;
|
package eu.siacs.conversations.generator;
|
||||||
|
|
||||||
|
|
||||||
|
import android.util.Base64;
|
||||||
|
|
||||||
|
import org.whispersystems.libaxolotl.IdentityKey;
|
||||||
|
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
|
||||||
|
import org.whispersystems.libaxolotl.state.PreKeyRecord;
|
||||||
|
import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.entities.Conversation;
|
import eu.siacs.conversations.entities.Conversation;
|
||||||
import eu.siacs.conversations.entities.DownloadableFile;
|
import eu.siacs.conversations.entities.DownloadableFile;
|
||||||
|
@ -115,6 +124,56 @@ public class IqGenerator extends AbstractGenerator {
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IqPacket retrieveDeviceIds(final Jid to) {
|
||||||
|
final IqPacket packet = retrieve(AxolotlService.PEP_DEVICE_LIST, null);
|
||||||
|
if(to != null) {
|
||||||
|
packet.setTo(to);
|
||||||
|
}
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IqPacket retrieveBundlesForDevice(final Jid to, final int deviceid) {
|
||||||
|
final IqPacket packet = retrieve(AxolotlService.PEP_BUNDLES+":"+deviceid, null);
|
||||||
|
if(to != null) {
|
||||||
|
packet.setTo(to);
|
||||||
|
}
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IqPacket publishDeviceIds(final Set<Integer> ids) {
|
||||||
|
final Element item = new Element("item");
|
||||||
|
final Element list = item.addChild("list", AxolotlService.PEP_PREFIX);
|
||||||
|
for(Integer id:ids) {
|
||||||
|
final Element device = new Element("device");
|
||||||
|
device.setAttribute("id", id);
|
||||||
|
list.addChild(device);
|
||||||
|
}
|
||||||
|
return publish(AxolotlService.PEP_DEVICE_LIST, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IqPacket publishBundles(final SignedPreKeyRecord signedPreKeyRecord, final IdentityKey identityKey,
|
||||||
|
final Set<PreKeyRecord> preKeyRecords, final int deviceId) {
|
||||||
|
final Element item = new Element("item");
|
||||||
|
final Element bundle = item.addChild("bundle", AxolotlService.PEP_PREFIX);
|
||||||
|
final Element signedPreKeyPublic = bundle.addChild("signedPreKeyPublic");
|
||||||
|
signedPreKeyPublic.setAttribute("signedPreKeyId", signedPreKeyRecord.getId());
|
||||||
|
ECPublicKey publicKey = signedPreKeyRecord.getKeyPair().getPublicKey();
|
||||||
|
signedPreKeyPublic.setContent(Base64.encodeToString(publicKey.serialize(),Base64.DEFAULT));
|
||||||
|
final Element signedPreKeySignature = bundle.addChild("signedPreKeySignature");
|
||||||
|
signedPreKeySignature.setContent(Base64.encodeToString(signedPreKeyRecord.getSignature(),Base64.DEFAULT));
|
||||||
|
final Element identityKeyElement = bundle.addChild("identityKey");
|
||||||
|
identityKeyElement.setContent(Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT));
|
||||||
|
|
||||||
|
final Element prekeys = bundle.addChild("prekeys", AxolotlService.PEP_PREFIX);
|
||||||
|
for(PreKeyRecord preKeyRecord:preKeyRecords) {
|
||||||
|
final Element prekey = prekeys.addChild("preKeyPublic");
|
||||||
|
prekey.setAttribute("preKeyId", preKeyRecord.getId());
|
||||||
|
prekey.setContent(Base64.encodeToString(preKeyRecord.getKeyPair().getPublicKey().serialize(), Base64.DEFAULT));
|
||||||
|
}
|
||||||
|
|
||||||
|
return publish(AxolotlService.PEP_BUNDLES+":"+deviceId, item);
|
||||||
|
}
|
||||||
|
|
||||||
public IqPacket queryMessageArchiveManagement(final MessageArchiveService.Query mam) {
|
public IqPacket queryMessageArchiveManagement(final MessageArchiveService.Query mam) {
|
||||||
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
||||||
final Element query = packet.query("urn:xmpp:mam:0");
|
final Element query = packet.query("urn:xmpp:mam:0");
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package eu.siacs.conversations.generator;
|
package eu.siacs.conversations.generator;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
@ -7,6 +9,11 @@ import java.util.TimeZone;
|
||||||
|
|
||||||
import net.java.otr4j.OtrException;
|
import net.java.otr4j.OtrException;
|
||||||
import net.java.otr4j.session.Session;
|
import net.java.otr4j.session.Session;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.Config;
|
||||||
|
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
||||||
|
import eu.siacs.conversations.crypto.axolotl.NoSessionsCreatedException;
|
||||||
|
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.entities.Conversation;
|
import eu.siacs.conversations.entities.Conversation;
|
||||||
import eu.siacs.conversations.entities.Message;
|
import eu.siacs.conversations.entities.Message;
|
||||||
|
@ -59,6 +66,22 @@ public class MessageGenerator extends AbstractGenerator {
|
||||||
delay.setAttribute("stamp", mDateFormat.format(date));
|
delay.setAttribute("stamp", mDateFormat.format(date));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MessagePacket generateAxolotlChat(Message message) {
|
||||||
|
return generateAxolotlChat(message, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessagePacket generateAxolotlChat(Message message, boolean addDelay) {
|
||||||
|
MessagePacket packet = preparePacket(message, addDelay);
|
||||||
|
AxolotlService service = message.getConversation().getAccount().getAxolotlService();
|
||||||
|
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(message.getConversation().getAccount())+"Submitting message to axolotl service for send processing...");
|
||||||
|
XmppAxolotlMessage axolotlMessage = service.encrypt(message);
|
||||||
|
if (axolotlMessage == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
packet.setAxolotlMessage(axolotlMessage.toXml());
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
|
|
||||||
public MessagePacket generateOtrChat(Message message) {
|
public MessagePacket generateOtrChat(Message message) {
|
||||||
return generateOtrChat(message, false);
|
return generateOtrChat(message, false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,25 @@
|
||||||
package eu.siacs.conversations.parser;
|
package eu.siacs.conversations.parser;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.util.Base64;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.whispersystems.libaxolotl.IdentityKey;
|
||||||
|
import org.whispersystems.libaxolotl.InvalidKeyException;
|
||||||
|
import org.whispersystems.libaxolotl.ecc.Curve;
|
||||||
|
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
|
||||||
|
import org.whispersystems.libaxolotl.state.PreKeyBundle;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
|
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.entities.Contact;
|
import eu.siacs.conversations.entities.Contact;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
|
@ -71,6 +85,155 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||||
return super.avatarData(items);
|
return super.avatarData(items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Element getItem(final IqPacket packet) {
|
||||||
|
final Element pubsub = packet.findChild("pubsub",
|
||||||
|
"http://jabber.org/protocol/pubsub");
|
||||||
|
if (pubsub == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final Element items = pubsub.findChild("items");
|
||||||
|
if (items == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return items.findChild("item");
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Set<Integer> deviceIds(final Element item) {
|
||||||
|
Set<Integer> deviceIds = new HashSet<>();
|
||||||
|
if (item != null) {
|
||||||
|
final Element list = item.findChild("list");
|
||||||
|
if (list != null) {
|
||||||
|
for (Element device : list.getChildren()) {
|
||||||
|
if (!device.getName().equals("device")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
Integer id = Integer.valueOf(device.getAttribute("id"));
|
||||||
|
deviceIds.add(id);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Encountered nvalid <device> node in PEP:" + device.toString()
|
||||||
|
+ ", skipping...");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return deviceIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer signedPreKeyId(final Element bundle) {
|
||||||
|
final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic");
|
||||||
|
if(signedPreKeyPublic == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Integer.valueOf(signedPreKeyPublic.getAttribute("signedPreKeyId"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ECPublicKey signedPreKeyPublic(final Element bundle) {
|
||||||
|
ECPublicKey publicKey = null;
|
||||||
|
final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic");
|
||||||
|
if(signedPreKeyPublic == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
publicKey = Curve.decodePoint(Base64.decode(signedPreKeyPublic.getContent(),Base64.DEFAULT), 0);
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Invalid signedPreKeyPublic in PEP: " + e.getMessage());
|
||||||
|
}
|
||||||
|
return publicKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] signedPreKeySignature(final Element bundle) {
|
||||||
|
final Element signedPreKeySignature = bundle.findChild("signedPreKeySignature");
|
||||||
|
if(signedPreKeySignature == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Base64.decode(signedPreKeySignature.getContent(),Base64.DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IdentityKey identityKey(final Element bundle) {
|
||||||
|
IdentityKey identityKey = null;
|
||||||
|
final Element identityKeyElement = bundle.findChild("identityKey");
|
||||||
|
if(identityKeyElement == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
identityKey = new IdentityKey(Base64.decode(identityKeyElement.getContent(), Base64.DEFAULT), 0);
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
Log.e(Config.LOGTAG,AxolotlService.LOGPREFIX+" : "+"Invalid identityKey in PEP: "+e.getMessage());
|
||||||
|
}
|
||||||
|
return identityKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<Integer, ECPublicKey> preKeyPublics(final IqPacket packet) {
|
||||||
|
Map<Integer, ECPublicKey> preKeyRecords = new HashMap<>();
|
||||||
|
Element item = getItem(packet);
|
||||||
|
if (item == null) {
|
||||||
|
Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Couldn't find <item> in bundle IQ packet: " + packet);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final Element bundleElement = item.findChild("bundle");
|
||||||
|
if(bundleElement == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final Element prekeysElement = bundleElement.findChild("prekeys");
|
||||||
|
if(prekeysElement == null) {
|
||||||
|
Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Couldn't find <prekeys> in bundle IQ packet: " + packet);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for(Element preKeyPublicElement : prekeysElement.getChildren()) {
|
||||||
|
if(!preKeyPublicElement.getName().equals("preKeyPublic")){
|
||||||
|
Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Encountered unexpected tag in prekeys list: " + preKeyPublicElement);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Integer preKeyId = Integer.valueOf(preKeyPublicElement.getAttribute("preKeyId"));
|
||||||
|
try {
|
||||||
|
ECPublicKey preKeyPublic = Curve.decodePoint(Base64.decode(preKeyPublicElement.getContent(), Base64.DEFAULT), 0);
|
||||||
|
preKeyRecords.put(preKeyId, preKeyPublic);
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
Log.e(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+"Invalid preKeyPublic (ID="+preKeyId+") in PEP: "+ e.getMessage()+", skipping...");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return preKeyRecords;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PreKeyBundle bundle(final IqPacket bundle) {
|
||||||
|
Element bundleItem = getItem(bundle);
|
||||||
|
if(bundleItem == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final Element bundleElement = bundleItem.findChild("bundle");
|
||||||
|
if(bundleElement == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ECPublicKey signedPreKeyPublic = signedPreKeyPublic(bundleElement);
|
||||||
|
Integer signedPreKeyId = signedPreKeyId(bundleElement);
|
||||||
|
byte[] signedPreKeySignature = signedPreKeySignature(bundleElement);
|
||||||
|
IdentityKey identityKey = identityKey(bundleElement);
|
||||||
|
if(signedPreKeyPublic == null || identityKey == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new PreKeyBundle(0, 0, 0, null,
|
||||||
|
signedPreKeyId, signedPreKeyPublic, signedPreKeySignature, identityKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<PreKeyBundle> preKeys(final IqPacket preKeys) {
|
||||||
|
List<PreKeyBundle> bundles = new ArrayList<>();
|
||||||
|
Map<Integer, ECPublicKey> preKeyPublics = preKeyPublics(preKeys);
|
||||||
|
if ( preKeyPublics != null) {
|
||||||
|
for (Integer preKeyId : preKeyPublics.keySet()) {
|
||||||
|
ECPublicKey preKeyPublic = preKeyPublics.get(preKeyId);
|
||||||
|
bundles.add(new PreKeyBundle(0, 0, preKeyId, preKeyPublic,
|
||||||
|
0, null, null, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bundles;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onIqPacketReceived(final Account account, final IqPacket packet) {
|
public void onIqPacketReceived(final Account account, final IqPacket packet) {
|
||||||
if (packet.hasChild("query", Xmlns.ROSTER) && packet.fromServer(account)) {
|
if (packet.hasChild("query", Xmlns.ROSTER) && packet.fromServer(account)) {
|
||||||
|
|
|
@ -6,7 +6,12 @@ import android.util.Pair;
|
||||||
import net.java.otr4j.session.Session;
|
import net.java.otr4j.session.Session;
|
||||||
import net.java.otr4j.session.SessionStatus;
|
import net.java.otr4j.session.SessionStatus;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
|
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
||||||
|
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.entities.Contact;
|
import eu.siacs.conversations.entities.Contact;
|
||||||
import eu.siacs.conversations.entities.Conversation;
|
import eu.siacs.conversations.entities.Conversation;
|
||||||
|
@ -94,6 +99,20 @@ 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.XmppAxolotlPlaintextMessage plaintextMessage = service.processReceiving(xmppAxolotlMessage);
|
||||||
|
if(plaintextMessage != null) {
|
||||||
|
finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, status);
|
||||||
|
finishedMessage.setAxolotlFingerprint(plaintextMessage.getFingerprint());
|
||||||
|
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(finishedMessage.getConversation().getAccount())+" Received Message with session fingerprint: "+plaintextMessage.getFingerprint());
|
||||||
|
}
|
||||||
|
|
||||||
|
return finishedMessage;
|
||||||
|
}
|
||||||
|
|
||||||
private class Invite {
|
private class Invite {
|
||||||
Jid jid;
|
Jid jid;
|
||||||
String password;
|
String password;
|
||||||
|
@ -170,6 +189,13 @@ public class MessageParser extends AbstractParser implements
|
||||||
mXmppConnectionService.updateConversationUi();
|
mXmppConnectionService.updateConversationUi();
|
||||||
mXmppConnectionService.updateAccountUi();
|
mXmppConnectionService.updateAccountUi();
|
||||||
}
|
}
|
||||||
|
} else if (AxolotlService.PEP_DEVICE_LIST.equals(node)) {
|
||||||
|
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Received PEP device list update from "+ from + ", processing...");
|
||||||
|
Element item = items.findChild("item");
|
||||||
|
Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
|
||||||
|
AxolotlService axolotlService = account.getAxolotlService();
|
||||||
|
axolotlService.registerDevices(from, deviceIds);
|
||||||
|
mXmppConnectionService.updateAccountUi();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,8 +258,9 @@ public class MessageParser extends AbstractParser implements
|
||||||
timestamp = AbstractParser.getTimestamp(packet, System.currentTimeMillis());
|
timestamp = AbstractParser.getTimestamp(packet, System.currentTimeMillis());
|
||||||
}
|
}
|
||||||
final String body = packet.getBody();
|
final String body = packet.getBody();
|
||||||
final String encrypted = packet.findChildContent("x", "jabber:x:encrypted");
|
|
||||||
final Element mucUserElement = packet.findChild("x","http://jabber.org/protocol/muc#user");
|
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);
|
||||||
int status;
|
int status;
|
||||||
final Jid counterpart;
|
final Jid counterpart;
|
||||||
final Jid to = packet.getTo();
|
final Jid to = packet.getTo();
|
||||||
|
@ -261,11 +288,11 @@ public class MessageParser extends AbstractParser implements
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (extractChatState(mXmppConnectionService.find(account,from), packet)) {
|
if (extractChatState(mXmppConnectionService.find(account, from), packet)) {
|
||||||
mXmppConnectionService.updateConversationUi();
|
mXmppConnectionService.updateConversationUi();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((body != null || encrypted != null) && !isMucStatusMessage) {
|
if ((body != null || pgpEncrypted != null || axolotlEncrypted != null) && !isMucStatusMessage) {
|
||||||
Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, counterpart.toBareJid(), isTypeGroupChat);
|
Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, counterpart.toBareJid(), isTypeGroupChat);
|
||||||
if (isTypeGroupChat) {
|
if (isTypeGroupChat) {
|
||||||
if (counterpart.getResourcepart().equals(conversation.getMucOptions().getActualNick())) {
|
if (counterpart.getResourcepart().equals(conversation.getMucOptions().getActualNick())) {
|
||||||
|
@ -294,8 +321,13 @@ public class MessageParser extends AbstractParser implements
|
||||||
} else {
|
} else {
|
||||||
message = new Message(conversation, body, Message.ENCRYPTION_NONE, status);
|
message = new Message(conversation, body, Message.ENCRYPTION_NONE, status);
|
||||||
}
|
}
|
||||||
} else if (encrypted != null) {
|
} else if (pgpEncrypted != null) {
|
||||||
message = new Message(conversation, encrypted, Message.ENCRYPTION_PGP, status);
|
message = new Message(conversation, pgpEncrypted, Message.ENCRYPTION_PGP, status);
|
||||||
|
} else if (axolotlEncrypted != null) {
|
||||||
|
message = parseAxolotlChat(axolotlEncrypted, from, remoteMsgId, conversation, status);
|
||||||
|
if (message == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
message = new Message(conversation, body, Message.ENCRYPTION_NONE, status);
|
message = new Message(conversation, body, Message.ENCRYPTION_NONE, status);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,31 @@
|
||||||
package eu.siacs.conversations.persistance;
|
package eu.siacs.conversations.persistance;
|
||||||
|
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.sqlite.SQLiteCantOpenDatabaseException;
|
||||||
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
|
import android.database.sqlite.SQLiteOpenHelper;
|
||||||
|
import android.util.Base64;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.whispersystems.libaxolotl.AxolotlAddress;
|
||||||
|
import org.whispersystems.libaxolotl.IdentityKey;
|
||||||
|
import org.whispersystems.libaxolotl.IdentityKeyPair;
|
||||||
|
import org.whispersystems.libaxolotl.InvalidKeyException;
|
||||||
|
import org.whispersystems.libaxolotl.state.PreKeyRecord;
|
||||||
|
import org.whispersystems.libaxolotl.state.SessionRecord;
|
||||||
|
import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
|
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.entities.Contact;
|
import eu.siacs.conversations.entities.Contact;
|
||||||
import eu.siacs.conversations.entities.Conversation;
|
import eu.siacs.conversations.entities.Conversation;
|
||||||
|
@ -13,19 +34,12 @@ import eu.siacs.conversations.entities.Roster;
|
||||||
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
|
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
|
||||||
import eu.siacs.conversations.xmpp.jid.Jid;
|
import eu.siacs.conversations.xmpp.jid.Jid;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import android.database.sqlite.SQLiteCantOpenDatabaseException;
|
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
|
||||||
import android.database.sqlite.SQLiteOpenHelper;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
public class DatabaseBackend extends SQLiteOpenHelper {
|
public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
|
|
||||||
private static DatabaseBackend instance = null;
|
private static DatabaseBackend instance = null;
|
||||||
|
|
||||||
private static final String DATABASE_NAME = "history";
|
private static final String DATABASE_NAME = "history";
|
||||||
private static final int DATABASE_VERSION = 14;
|
private static final int DATABASE_VERSION = 15;
|
||||||
|
|
||||||
private static String CREATE_CONTATCS_STATEMENT = "create table "
|
private static String CREATE_CONTATCS_STATEMENT = "create table "
|
||||||
+ Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, "
|
+ Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, "
|
||||||
|
@ -39,6 +53,56 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
+ ") ON DELETE CASCADE, UNIQUE(" + Contact.ACCOUNT + ", "
|
+ ") ON DELETE CASCADE, UNIQUE(" + Contact.ACCOUNT + ", "
|
||||||
+ Contact.JID + ") ON CONFLICT REPLACE);";
|
+ 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
|
||||||
|
+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
|
||||||
|
+ "UNIQUE( " + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ", "
|
||||||
|
+ AxolotlService.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
|
||||||
|
+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
|
||||||
|
+ "UNIQUE( " + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ", "
|
||||||
|
+ AxolotlService.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
|
||||||
|
+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
|
||||||
|
+ "UNIQUE( " + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ", "
|
||||||
|
+ AxolotlService.SQLiteAxolotlStore.NAME + ", "
|
||||||
|
+ AxolotlService.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 PRIMARY KEY ON CONFLICT IGNORE, "
|
||||||
|
+ AxolotlService.SQLiteAxolotlStore.TRUSTED + " INTEGER, "
|
||||||
|
+ AxolotlService.SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
|
||||||
|
+ AxolotlService.SQLiteAxolotlStore.ACCOUNT
|
||||||
|
+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE "
|
||||||
|
+");";
|
||||||
|
|
||||||
private DatabaseBackend(Context context) {
|
private DatabaseBackend(Context context) {
|
||||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||||
}
|
}
|
||||||
|
@ -69,12 +133,17 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
+ Message.STATUS + " NUMBER," + Message.TYPE + " NUMBER, "
|
+ Message.STATUS + " NUMBER," + Message.TYPE + " NUMBER, "
|
||||||
+ Message.RELATIVE_FILE_PATH + " TEXT, "
|
+ Message.RELATIVE_FILE_PATH + " TEXT, "
|
||||||
+ Message.SERVER_MSG_ID + " TEXT, "
|
+ Message.SERVER_MSG_ID + " TEXT, "
|
||||||
|
+ Message.FINGERPRINT + " TEXT, "
|
||||||
+ Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY("
|
+ Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY("
|
||||||
+ Message.CONVERSATION + ") REFERENCES "
|
+ Message.CONVERSATION + ") REFERENCES "
|
||||||
+ Conversation.TABLENAME + "(" + Conversation.UUID
|
+ Conversation.TABLENAME + "(" + Conversation.UUID
|
||||||
+ ") ON DELETE CASCADE);");
|
+ ") ON DELETE CASCADE);");
|
||||||
|
|
||||||
db.execSQL(CREATE_CONTATCS_STATEMENT);
|
db.execSQL(CREATE_CONTATCS_STATEMENT);
|
||||||
|
db.execSQL(CREATE_SESSIONS_STATEMENT);
|
||||||
|
db.execSQL(CREATE_PREKEYS_STATEMENT);
|
||||||
|
db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT);
|
||||||
|
db.execSQL(CREATE_IDENTITIES_STATEMENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -215,6 +284,11 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
}
|
}
|
||||||
cursor.close();
|
cursor.close();
|
||||||
}
|
}
|
||||||
|
if (oldVersion < 15 && newVersion >= 15) {
|
||||||
|
recreateAxolotlDb();
|
||||||
|
db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
|
||||||
|
+ Message.FINGERPRINT + " TEXT");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized DatabaseBackend getInstance(Context context) {
|
public static synchronized DatabaseBackend getInstance(Context context) {
|
||||||
|
@ -311,7 +385,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
};
|
};
|
||||||
Cursor cursor = db.query(Conversation.TABLENAME, null,
|
Cursor cursor = db.query(Conversation.TABLENAME, null,
|
||||||
Conversation.ACCOUNT + "=? AND (" + Conversation.CONTACTJID
|
Conversation.ACCOUNT + "=? AND (" + Conversation.CONTACTJID
|
||||||
+ " like ? OR "+Conversation.CONTACTJID+"=?)", selectionArgs, null, null, null);
|
+ " like ? OR " + Conversation.CONTACTJID + "=?)", selectionArgs, null, null, null);
|
||||||
if (cursor.getCount() == 0)
|
if (cursor.getCount() == 0)
|
||||||
return null;
|
return null;
|
||||||
cursor.moveToFirst();
|
cursor.moveToFirst();
|
||||||
|
@ -481,4 +555,367 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
cursor.close();
|
cursor.close();
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Cursor getCursorForSession(Account account, AxolotlAddress contact) {
|
||||||
|
final SQLiteDatabase db = this.getReadableDatabase();
|
||||||
|
String[] columns = null;
|
||||||
|
String[] selectionArgs = {account.getUuid(),
|
||||||
|
contact.getName(),
|
||||||
|
Integer.toString(contact.getDeviceId())};
|
||||||
|
Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME,
|
||||||
|
columns,
|
||||||
|
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND "
|
||||||
|
+ AxolotlService.SQLiteAxolotlStore.NAME + " = ? AND "
|
||||||
|
+ AxolotlService.SQLiteAxolotlStore.DEVICE_ID + " = ? ",
|
||||||
|
selectionArgs,
|
||||||
|
null, null, null);
|
||||||
|
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SessionRecord loadSession(Account account, AxolotlAddress contact) {
|
||||||
|
SessionRecord session = null;
|
||||||
|
Cursor cursor = getCursorForSession(account, contact);
|
||||||
|
if(cursor.getCount() != 0) {
|
||||||
|
cursor.moveToFirst();
|
||||||
|
try {
|
||||||
|
session = new SessionRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT));
|
||||||
|
} catch (IOException e) {
|
||||||
|
cursor.close();
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cursor.close();
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Integer> getSubDeviceSessions(Account account, AxolotlAddress contact) {
|
||||||
|
List<Integer> devices = new ArrayList<>();
|
||||||
|
final SQLiteDatabase db = this.getReadableDatabase();
|
||||||
|
String[] columns = {AxolotlService.SQLiteAxolotlStore.DEVICE_ID};
|
||||||
|
String[] selectionArgs = {account.getUuid(),
|
||||||
|
contact.getName()};
|
||||||
|
Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME,
|
||||||
|
columns,
|
||||||
|
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND "
|
||||||
|
+ AxolotlService.SQLiteAxolotlStore.NAME + " = ?",
|
||||||
|
selectionArgs,
|
||||||
|
null, null, null);
|
||||||
|
|
||||||
|
while(cursor.moveToNext()) {
|
||||||
|
devices.add(cursor.getInt(
|
||||||
|
cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.DEVICE_ID)));
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor.close();
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean containsSession(Account account, AxolotlAddress contact) {
|
||||||
|
Cursor cursor = getCursorForSession(account, contact);
|
||||||
|
int count = cursor.getCount();
|
||||||
|
cursor.close();
|
||||||
|
return count != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteSession(Account account, AxolotlAddress contact) {
|
||||||
|
SQLiteDatabase db = this.getWritableDatabase();
|
||||||
|
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 + " = ? ",
|
||||||
|
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 + " = ?",
|
||||||
|
args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Cursor getCursorForPreKey(Account account, int preKeyId) {
|
||||||
|
SQLiteDatabase db = this.getReadableDatabase();
|
||||||
|
String[] columns = {AxolotlService.SQLiteAxolotlStore.KEY};
|
||||||
|
String[] selectionArgs = {account.getUuid(), Integer.toString(preKeyId)};
|
||||||
|
Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME,
|
||||||
|
columns,
|
||||||
|
AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND "
|
||||||
|
+ AxolotlService.SQLiteAxolotlStore.ID + "=?",
|
||||||
|
selectionArgs,
|
||||||
|
null, null, null);
|
||||||
|
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PreKeyRecord loadPreKey(Account account, int preKeyId) {
|
||||||
|
PreKeyRecord record = null;
|
||||||
|
Cursor cursor = getCursorForPreKey(account, preKeyId);
|
||||||
|
if(cursor.getCount() != 0) {
|
||||||
|
cursor.moveToFirst();
|
||||||
|
try {
|
||||||
|
record = new PreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT));
|
||||||
|
} catch (IOException e ) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cursor.close();
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean containsPreKey(Account account, int preKeyId) {
|
||||||
|
Cursor cursor = getCursorForPreKey(account, preKeyId);
|
||||||
|
int count = cursor.getCount();
|
||||||
|
cursor.close();
|
||||||
|
return count != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 + "=?",
|
||||||
|
args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Cursor getCursorForSignedPreKey(Account account, int signedPreKeyId) {
|
||||||
|
SQLiteDatabase db = this.getReadableDatabase();
|
||||||
|
String[] columns = {AxolotlService.SQLiteAxolotlStore.KEY};
|
||||||
|
String[] selectionArgs = {account.getUuid(), Integer.toString(signedPreKeyId)};
|
||||||
|
Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
|
||||||
|
columns,
|
||||||
|
AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND " + AxolotlService.SQLiteAxolotlStore.ID + "=?",
|
||||||
|
selectionArgs,
|
||||||
|
null, null, null);
|
||||||
|
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SignedPreKeyRecord loadSignedPreKey(Account account, int signedPreKeyId) {
|
||||||
|
SignedPreKeyRecord record = null;
|
||||||
|
Cursor cursor = getCursorForSignedPreKey(account, signedPreKeyId);
|
||||||
|
if(cursor.getCount() != 0) {
|
||||||
|
cursor.moveToFirst();
|
||||||
|
try {
|
||||||
|
record = new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT));
|
||||||
|
} catch (IOException e ) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cursor.close();
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<SignedPreKeyRecord> loadSignedPreKeys(Account account) {
|
||||||
|
List<SignedPreKeyRecord> prekeys = new ArrayList<>();
|
||||||
|
SQLiteDatabase db = this.getReadableDatabase();
|
||||||
|
String[] columns = {AxolotlService.SQLiteAxolotlStore.KEY};
|
||||||
|
String[] selectionArgs = {account.getUuid()};
|
||||||
|
Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
|
||||||
|
columns,
|
||||||
|
AxolotlService.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)));
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cursor.close();
|
||||||
|
return prekeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean containsSignedPreKey(Account account, int signedPreKeyId) {
|
||||||
|
Cursor cursor = getCursorForPreKey(account, signedPreKeyId);
|
||||||
|
int count = cursor.getCount();
|
||||||
|
cursor.close();
|
||||||
|
return count != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 + "=?",
|
||||||
|
args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Cursor getIdentityKeyCursor(Account account, String name, boolean own) {
|
||||||
|
return getIdentityKeyCursor(account, name, own, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Cursor getIdentityKeyCursor(Account account, String name, boolean own, String fingerprint) {
|
||||||
|
final SQLiteDatabase db = this.getReadableDatabase();
|
||||||
|
String[] columns = {AxolotlService.SQLiteAxolotlStore.TRUSTED,
|
||||||
|
AxolotlService.SQLiteAxolotlStore.KEY};
|
||||||
|
ArrayList<String> selectionArgs = new ArrayList<>(4);
|
||||||
|
selectionArgs.add(account.getUuid());
|
||||||
|
selectionArgs.add(name);
|
||||||
|
selectionArgs.add(own?"1":"0");
|
||||||
|
String selectionString = AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND "
|
||||||
|
+ AxolotlService.SQLiteAxolotlStore.NAME + " = ? AND "
|
||||||
|
+ AxolotlService.SQLiteAxolotlStore.OWN + " = ? ";
|
||||||
|
if (fingerprint != null){
|
||||||
|
selectionArgs.add(fingerprint);
|
||||||
|
selectionString += "AND " +AxolotlService.SQLiteAxolotlStore.FINGERPRINT + " = ? ";
|
||||||
|
}
|
||||||
|
Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME,
|
||||||
|
columns,
|
||||||
|
selectionString,
|
||||||
|
selectionArgs.toArray(new String[selectionArgs.size()]),
|
||||||
|
null, null, null);
|
||||||
|
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IdentityKeyPair loadOwnIdentityKeyPair(Account account, String name) {
|
||||||
|
IdentityKeyPair identityKeyPair = null;
|
||||||
|
Cursor cursor = getIdentityKeyCursor(account, name, true);
|
||||||
|
if(cursor.getCount() != 0) {
|
||||||
|
cursor.moveToFirst();
|
||||||
|
try {
|
||||||
|
identityKeyPair = new IdentityKeyPair(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cursor.close();
|
||||||
|
|
||||||
|
return identityKeyPair;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<IdentityKey> loadIdentityKeys(Account account, String name) {
|
||||||
|
Set<IdentityKey> identityKeys = new HashSet<>();
|
||||||
|
Cursor cursor = getIdentityKeyCursor(account, name, false);
|
||||||
|
|
||||||
|
while(cursor.moveToNext()) {
|
||||||
|
try {
|
||||||
|
identityKeys.add(new IdentityKey(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cursor.close();
|
||||||
|
|
||||||
|
return identityKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized) {
|
||||||
|
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);
|
||||||
|
db.insert(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AxolotlService.SQLiteAxolotlStore.Trust isIdentityKeyTrusted(Account account, String name, String fingerprint) {
|
||||||
|
Cursor cursor = getIdentityKeyCursor(account, name, false, fingerprint);
|
||||||
|
AxolotlService.SQLiteAxolotlStore.Trust trust = null;
|
||||||
|
if (cursor.getCount() > 0) {
|
||||||
|
cursor.moveToFirst();
|
||||||
|
int trustValue = cursor.getInt(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.TRUSTED));
|
||||||
|
trust = AxolotlService.SQLiteAxolotlStore.Trust.values()[trustValue];
|
||||||
|
}
|
||||||
|
cursor.close();
|
||||||
|
return trust;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean setIdentityKeyTrust(Account account, String name, String fingerprint, AxolotlService.SQLiteAxolotlStore.Trust trust) {
|
||||||
|
SQLiteDatabase db = this.getWritableDatabase();
|
||||||
|
String[] selectionArgs = {
|
||||||
|
account.getUuid(),
|
||||||
|
name,
|
||||||
|
fingerprint
|
||||||
|
};
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
values.put(AxolotlService.SQLiteAxolotlStore.TRUSTED, trust.ordinal());
|
||||||
|
int rows = db.update(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, values,
|
||||||
|
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? "
|
||||||
|
+ AxolotlService.SQLiteAxolotlStore.NAME + " = ? "
|
||||||
|
+ AxolotlService.SQLiteAxolotlStore.FINGERPRINT + " = ? ",
|
||||||
|
selectionArgs);
|
||||||
|
return rows == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void storeIdentityKey(Account account, String name, IdentityKey identityKey) {
|
||||||
|
storeIdentityKey(account, name, false, identityKey.getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void storeOwnIdentityKeyPair(Account account, String name, IdentityKeyPair identityKeyPair) {
|
||||||
|
storeIdentityKey(account, name, true, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void recreateAxolotlDb() {
|
||||||
|
Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+">>> (RE)CREATING AXOLOTL DATABASE <<<");
|
||||||
|
SQLiteDatabase db = this.getWritableDatabase();
|
||||||
|
db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME);
|
||||||
|
db.execSQL(CREATE_SESSIONS_STATEMENT);
|
||||||
|
db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME);
|
||||||
|
db.execSQL(CREATE_PREKEYS_STATEMENT);
|
||||||
|
db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME);
|
||||||
|
db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT);
|
||||||
|
db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME);
|
||||||
|
db.execSQL(CREATE_IDENTITIES_STATEMENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void wipeAxolotlDb(Account account) {
|
||||||
|
String accountName = account.getUuid();
|
||||||
|
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+">>> WIPING AXOLOTL DATABASE FOR ACCOUNT " + accountName + " <<<");
|
||||||
|
SQLiteDatabase db = this.getWritableDatabase();
|
||||||
|
String[] deleteArgs= {
|
||||||
|
accountName
|
||||||
|
};
|
||||||
|
db.delete(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME,
|
||||||
|
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?",
|
||||||
|
deleteArgs);
|
||||||
|
db.delete(AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME,
|
||||||
|
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?",
|
||||||
|
deleteArgs);
|
||||||
|
db.delete(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
|
||||||
|
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?",
|
||||||
|
deleteArgs);
|
||||||
|
db.delete(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME,
|
||||||
|
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?",
|
||||||
|
deleteArgs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -272,7 +272,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
syncDirtyContacts(account);
|
syncDirtyContacts(account);
|
||||||
scheduleWakeUpCall(Config.PING_MAX_INTERVAL,account.getUuid().hashCode());
|
account.getAxolotlService().publishOwnDeviceIdIfNeeded();
|
||||||
|
account.getAxolotlService().publishBundlesIfNeeded();
|
||||||
|
|
||||||
|
scheduleWakeUpCall(Config.PING_MAX_INTERVAL, account.getUuid().hashCode());
|
||||||
} else if (account.getStatus() == Account.State.OFFLINE) {
|
} else if (account.getStatus() == Account.State.OFFLINE) {
|
||||||
resetSendingToWaiting(account);
|
resetSendingToWaiting(account);
|
||||||
if (!account.isOptionSet(Account.OPTION_DISABLED)) {
|
if (!account.isOptionSet(Account.OPTION_DISABLED)) {
|
||||||
|
@ -590,9 +593,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||||
this.databaseBackend = DatabaseBackend.getInstance(getApplicationContext());
|
this.databaseBackend = DatabaseBackend.getInstance(getApplicationContext());
|
||||||
this.accounts = databaseBackend.getAccounts();
|
this.accounts = databaseBackend.getAccounts();
|
||||||
|
|
||||||
for (final Account account : this.accounts) {
|
|
||||||
account.initAccountServices(this);
|
|
||||||
}
|
|
||||||
restoreFromDatabase();
|
restoreFromDatabase();
|
||||||
|
|
||||||
getContentResolver().registerContentObserver(ContactsContract.Contacts.CONTENT_URI, true, contactObserver);
|
getContentResolver().registerContentObserver(ContactsContract.Contacts.CONTENT_URI, true, contactObserver);
|
||||||
|
@ -698,7 +698,8 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||||
|
|
||||||
if (!resend && message.getEncryption() != Message.ENCRYPTION_OTR) {
|
if (!resend && message.getEncryption() != Message.ENCRYPTION_OTR) {
|
||||||
message.getConversation().endOtrIfNeeded();
|
message.getConversation().endOtrIfNeeded();
|
||||||
message.getConversation().findUnsentMessagesWithOtrEncryption(new Conversation.OnMessageFound() {
|
message.getConversation().findUnsentMessagesWithEncryption(Message.ENCRYPTION_OTR,
|
||||||
|
new Conversation.OnMessageFound() {
|
||||||
@Override
|
@Override
|
||||||
public void onMessageFound(Message message) {
|
public void onMessageFound(Message message) {
|
||||||
markMessage(message,Message.STATUS_SEND_FAILED);
|
markMessage(message,Message.STATUS_SEND_FAILED);
|
||||||
|
@ -752,6 +753,14 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case Message.ENCRYPTION_AXOLOTL:
|
||||||
|
message.setStatus(Message.STATUS_WAITING);
|
||||||
|
packet = account.getAxolotlService().fetchPacketFromCache(message);
|
||||||
|
if (packet == null && account.isOnlineAndConnected()) {
|
||||||
|
account.getAxolotlService().prepareMessage(message);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
if (packet != null) {
|
if (packet != null) {
|
||||||
if (account.getXmppConnection().getFeatures().sm() || conversation.getMode() == Conversation.MODE_MULTI) {
|
if (account.getXmppConnection().getFeatures().sm() || conversation.getMode() == Conversation.MODE_MULTI) {
|
||||||
|
@ -943,6 +952,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||||
Log.d(Config.LOGTAG,"restoring roster");
|
Log.d(Config.LOGTAG,"restoring roster");
|
||||||
for(Account account : accounts) {
|
for(Account account : accounts) {
|
||||||
databaseBackend.readRoster(account.getRoster());
|
databaseBackend.readRoster(account.getRoster());
|
||||||
|
account.initAccountServices(XmppConnectionService.this);
|
||||||
}
|
}
|
||||||
getBitmapCache().evictAll();
|
getBitmapCache().evictAll();
|
||||||
Looper.prepare();
|
Looper.prepare();
|
||||||
|
@ -1757,7 +1767,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||||
account.getJid().toBareJid() + " otr session established with "
|
account.getJid().toBareJid() + " otr session established with "
|
||||||
+ conversation.getJid() + "/"
|
+ conversation.getJid() + "/"
|
||||||
+ otrSession.getSessionID().getUserID());
|
+ otrSession.getSessionID().getUserID());
|
||||||
conversation.findUnsentMessagesWithOtrEncryption(new Conversation.OnMessageFound() {
|
conversation.findUnsentMessagesWithEncryption(Message.ENCRYPTION_OTR, new Conversation.OnMessageFound() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMessageFound(Message message) {
|
public void onMessageFound(Message message) {
|
||||||
|
|
|
@ -29,6 +29,7 @@ import android.widget.QuickContactBadge;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.openintents.openpgp.util.OpenPgpUtils;
|
import org.openintents.openpgp.util.OpenPgpUtils;
|
||||||
|
import org.whispersystems.libaxolotl.IdentityKey;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -376,6 +377,29 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
for(final IdentityKey identityKey : xmppConnectionService.databaseBackend.loadIdentityKeys(
|
||||||
|
contact.getAccount(), contact.getJid().toBareJid().toString())) {
|
||||||
|
hasKeys = true;
|
||||||
|
View view = inflater.inflate(R.layout.contact_key, keys, false);
|
||||||
|
TextView key = (TextView) view.findViewById(R.id.key);
|
||||||
|
TextView keyType = (TextView) view.findViewById(R.id.key_type);
|
||||||
|
TextView keyTrust = (TextView) view.findViewById(R.id.key_trust);
|
||||||
|
ImageButton remove = (ImageButton) view
|
||||||
|
.findViewById(R.id.button_remove);
|
||||||
|
remove.setVisibility(View.VISIBLE);
|
||||||
|
keyTrust.setVisibility(View.VISIBLE);
|
||||||
|
keyType.setText("Axolotl Fingerprint");
|
||||||
|
key.setText(CryptoHelper.prettifyFingerprint(identityKey.getFingerprint()));
|
||||||
|
keyTrust.setText(contact.getAccount().getAxolotlService().getFingerprintTrust(contact.getJid().toBareJid().toString(), identityKey.getFingerprint().replaceAll("\\s","")).toString());
|
||||||
|
keys.addView(view);
|
||||||
|
remove.setOnClickListener(new OnClickListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
//confirmToDeleteFingerprint(otrFingerprint);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
if (contact.getPgpKeyId() != 0) {
|
if (contact.getPgpKeyId() != 0) {
|
||||||
hasKeys = true;
|
hasKeys = true;
|
||||||
View view = inflater.inflate(R.layout.contact_key, keys, false);
|
View view = inflater.inflate(R.layout.contact_key, keys, false);
|
||||||
|
|
|
@ -16,6 +16,7 @@ import android.os.Bundle;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
import android.support.v4.widget.SlidingPaneLayout;
|
import android.support.v4.widget.SlidingPaneLayout;
|
||||||
import android.support.v4.widget.SlidingPaneLayout.PanelSlideListener;
|
import android.support.v4.widget.SlidingPaneLayout.PanelSlideListener;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
@ -34,6 +35,7 @@ import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.entities.Blockable;
|
import eu.siacs.conversations.entities.Blockable;
|
||||||
|
@ -752,6 +754,17 @@ public class ConversationActivity extends XmppActivity
|
||||||
showInstallPgpDialog();
|
showInstallPgpDialog();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case R.id.encryption_choice_axolotl:
|
||||||
|
Log.d(Config.LOGTAG, "Trying to enable axolotl...");
|
||||||
|
if(conversation.getAccount().getAxolotlService().isContactAxolotlCapable(conversation.getContact())) {
|
||||||
|
Log.d(Config.LOGTAG, "Enabled axolotl for Contact " + conversation.getContact().getJid() );
|
||||||
|
conversation.setNextEncryption(Message.ENCRYPTION_AXOLOTL);
|
||||||
|
item.setChecked(true);
|
||||||
|
} else {
|
||||||
|
Log.d(Config.LOGTAG, "Contact " + conversation.getContact().getJid() + " not axolotl capable!");
|
||||||
|
showAxolotlNoSessionsDialog();
|
||||||
|
}
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
conversation.setNextEncryption(Message.ENCRYPTION_NONE);
|
conversation.setNextEncryption(Message.ENCRYPTION_NONE);
|
||||||
break;
|
break;
|
||||||
|
@ -784,6 +797,11 @@ public class ConversationActivity extends XmppActivity
|
||||||
popup.getMenu().findItem(R.id.encryption_choice_pgp)
|
popup.getMenu().findItem(R.id.encryption_choice_pgp)
|
||||||
.setChecked(true);
|
.setChecked(true);
|
||||||
break;
|
break;
|
||||||
|
case Message.ENCRYPTION_AXOLOTL:
|
||||||
|
Log.d(Config.LOGTAG, "Axolotl confirmed. Setting menu item checked!");
|
||||||
|
popup.getMenu().findItem(R.id.encryption_choice_axolotl)
|
||||||
|
.setChecked(true);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
popup.getMenu().findItem(R.id.encryption_choice_none)
|
popup.getMenu().findItem(R.id.encryption_choice_none)
|
||||||
.setChecked(true);
|
.setChecked(true);
|
||||||
|
|
|
@ -302,6 +302,8 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
|
||||||
sendOtrMessage(message);
|
sendOtrMessage(message);
|
||||||
} else if (conversation.getNextEncryption(activity.forceEncryption()) == Message.ENCRYPTION_PGP) {
|
} else if (conversation.getNextEncryption(activity.forceEncryption()) == Message.ENCRYPTION_PGP) {
|
||||||
sendPgpMessage(message);
|
sendPgpMessage(message);
|
||||||
|
} else if (conversation.getNextEncryption(activity.forceEncryption()) == Message.ENCRYPTION_AXOLOTL) {
|
||||||
|
sendAxolotlMessage(message);
|
||||||
} else {
|
} else {
|
||||||
sendPlainTextMessage(message);
|
sendPlainTextMessage(message);
|
||||||
}
|
}
|
||||||
|
@ -322,6 +324,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
|
||||||
case Message.ENCRYPTION_OTR:
|
case Message.ENCRYPTION_OTR:
|
||||||
mEditMessage.setHint(getString(R.string.send_otr_message));
|
mEditMessage.setHint(getString(R.string.send_otr_message));
|
||||||
break;
|
break;
|
||||||
|
case Message.ENCRYPTION_AXOLOTL:
|
||||||
|
mEditMessage.setHint(getString(R.string.send_axolotl_message));
|
||||||
|
break;
|
||||||
case Message.ENCRYPTION_PGP:
|
case Message.ENCRYPTION_PGP:
|
||||||
mEditMessage.setHint(getString(R.string.send_pgp_message));
|
mEditMessage.setHint(getString(R.string.send_pgp_message));
|
||||||
break;
|
break;
|
||||||
|
@ -1114,6 +1119,14 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
|
||||||
builder.create().show();
|
builder.create().show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void sendAxolotlMessage(final Message message) {
|
||||||
|
final ConversationActivity activity = (ConversationActivity) getActivity();
|
||||||
|
final XmppConnectionService xmppService = activity.xmppConnectionService;
|
||||||
|
//message.setCounterpart(conversation.getNextCounterpart());
|
||||||
|
xmppService.sendMessage(message);
|
||||||
|
messageSent();
|
||||||
|
}
|
||||||
|
|
||||||
protected void sendOtrMessage(final Message message) {
|
protected void sendOtrMessage(final Message message) {
|
||||||
final ConversationActivity activity = (ConversationActivity) getActivity();
|
final ConversationActivity activity = (ConversationActivity) getActivity();
|
||||||
final XmppConnectionService xmppService = activity.xmppConnectionService;
|
final XmppConnectionService xmppService = activity.xmppConnectionService;
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
package eu.siacs.conversations.ui;
|
package eu.siacs.conversations.ui;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.app.AlertDialog.Builder;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
@ -23,6 +27,8 @@ import android.widget.TableLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
|
import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
|
||||||
|
@ -54,9 +60,16 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
|
||||||
private TextView mServerInfoPep;
|
private TextView mServerInfoPep;
|
||||||
private TextView mSessionEst;
|
private TextView mSessionEst;
|
||||||
private TextView mOtrFingerprint;
|
private TextView mOtrFingerprint;
|
||||||
|
private TextView mAxolotlFingerprint;
|
||||||
|
private TextView mAxolotlDevicelist;
|
||||||
private ImageView mAvatar;
|
private ImageView mAvatar;
|
||||||
private RelativeLayout mOtrFingerprintBox;
|
private RelativeLayout mOtrFingerprintBox;
|
||||||
|
private RelativeLayout mAxolotlFingerprintBox;
|
||||||
|
private RelativeLayout mAxolotlDevicelistBox;
|
||||||
private ImageButton mOtrFingerprintToClipboardButton;
|
private ImageButton mOtrFingerprintToClipboardButton;
|
||||||
|
private ImageButton mAxolotlFingerprintToClipboardButton;
|
||||||
|
private ImageButton mWipeAxolotlPepButton;
|
||||||
|
private ImageButton mRegenerateAxolotlKeyButton;
|
||||||
|
|
||||||
private Jid jidToEdit;
|
private Jid jidToEdit;
|
||||||
private Account mAccount;
|
private Account mAccount;
|
||||||
|
@ -310,6 +323,13 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
|
||||||
this.mOtrFingerprint = (TextView) findViewById(R.id.otr_fingerprint);
|
this.mOtrFingerprint = (TextView) findViewById(R.id.otr_fingerprint);
|
||||||
this.mOtrFingerprintBox = (RelativeLayout) findViewById(R.id.otr_fingerprint_box);
|
this.mOtrFingerprintBox = (RelativeLayout) findViewById(R.id.otr_fingerprint_box);
|
||||||
this.mOtrFingerprintToClipboardButton = (ImageButton) findViewById(R.id.action_copy_to_clipboard);
|
this.mOtrFingerprintToClipboardButton = (ImageButton) findViewById(R.id.action_copy_to_clipboard);
|
||||||
|
this.mAxolotlFingerprint = (TextView) findViewById(R.id.axolotl_fingerprint);
|
||||||
|
this.mAxolotlFingerprintBox = (RelativeLayout) findViewById(R.id.axolotl_fingerprint_box);
|
||||||
|
this.mAxolotlFingerprintToClipboardButton = (ImageButton) findViewById(R.id.action_copy_to_clipboard);
|
||||||
|
this.mRegenerateAxolotlKeyButton = (ImageButton) findViewById(R.id.action_regenerate_axolotl_key);
|
||||||
|
this.mAxolotlDevicelist = (TextView) findViewById(R.id.axolotl_devicelist);
|
||||||
|
this.mAxolotlDevicelistBox = (RelativeLayout) findViewById(R.id.axolotl_devices_box);
|
||||||
|
this.mWipeAxolotlPepButton = (ImageButton) findViewById(R.id.action_wipe_axolotl_pep);
|
||||||
this.mSaveButton = (Button) findViewById(R.id.save_button);
|
this.mSaveButton = (Button) findViewById(R.id.save_button);
|
||||||
this.mCancelButton = (Button) findViewById(R.id.cancel_button);
|
this.mCancelButton = (Button) findViewById(R.id.cancel_button);
|
||||||
this.mSaveButton.setOnClickListener(this.mSaveButtonClickListener);
|
this.mSaveButton.setOnClickListener(this.mSaveButtonClickListener);
|
||||||
|
@ -475,10 +495,10 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
|
||||||
} else {
|
} else {
|
||||||
this.mServerInfoPep.setText(R.string.server_info_unavailable);
|
this.mServerInfoPep.setText(R.string.server_info_unavailable);
|
||||||
}
|
}
|
||||||
final String fingerprint = this.mAccount.getOtrFingerprint();
|
final String otrFingerprint = this.mAccount.getOtrFingerprint();
|
||||||
if (fingerprint != null) {
|
if (otrFingerprint != null) {
|
||||||
this.mOtrFingerprintBox.setVisibility(View.VISIBLE);
|
this.mOtrFingerprintBox.setVisibility(View.VISIBLE);
|
||||||
this.mOtrFingerprint.setText(CryptoHelper.prettifyFingerprint(fingerprint));
|
this.mOtrFingerprint.setText(CryptoHelper.prettifyFingerprint(otrFingerprint));
|
||||||
this.mOtrFingerprintToClipboardButton
|
this.mOtrFingerprintToClipboardButton
|
||||||
.setVisibility(View.VISIBLE);
|
.setVisibility(View.VISIBLE);
|
||||||
this.mOtrFingerprintToClipboardButton
|
this.mOtrFingerprintToClipboardButton
|
||||||
|
@ -487,7 +507,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
|
||||||
@Override
|
@Override
|
||||||
public void onClick(final View v) {
|
public void onClick(final View v) {
|
||||||
|
|
||||||
if (copyTextToClipboard(fingerprint, R.string.otr_fingerprint)) {
|
if (copyTextToClipboard(otrFingerprint, R.string.otr_fingerprint)) {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
EditAccountActivity.this,
|
EditAccountActivity.this,
|
||||||
R.string.toast_message_otr_fingerprint,
|
R.string.toast_message_otr_fingerprint,
|
||||||
|
@ -498,6 +518,55 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
|
||||||
} else {
|
} else {
|
||||||
this.mOtrFingerprintBox.setVisibility(View.GONE);
|
this.mOtrFingerprintBox.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
final Set<Integer> ownDevices = this.mAccount.getAxolotlService().getOwnDeviceIds();
|
||||||
|
if (ownDevices != null && !ownDevices.isEmpty()) {
|
||||||
|
this.mAxolotlDevicelistBox.setVisibility(View.VISIBLE);
|
||||||
|
this.mAxolotlDevicelist.setText(TextUtils.join(", ", ownDevices));
|
||||||
|
this.mWipeAxolotlPepButton
|
||||||
|
.setVisibility(View.VISIBLE);
|
||||||
|
this.mWipeAxolotlPepButton
|
||||||
|
.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(final View v) {
|
||||||
|
showWipePepDialog();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.mAxolotlDevicelistBox.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
final String axolotlFingerprint = this.mAccount.getAxolotlService().getOwnPublicKey().getFingerprint();
|
||||||
|
if (axolotlFingerprint != null) {
|
||||||
|
this.mAxolotlFingerprintBox.setVisibility(View.VISIBLE);
|
||||||
|
this.mAxolotlFingerprint.setText(CryptoHelper.prettifyFingerprint(axolotlFingerprint));
|
||||||
|
this.mAxolotlFingerprintToClipboardButton
|
||||||
|
.setVisibility(View.VISIBLE);
|
||||||
|
this.mAxolotlFingerprintToClipboardButton
|
||||||
|
.setOnClickListener(new View.OnClickListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(final View v) {
|
||||||
|
|
||||||
|
if (copyTextToClipboard(axolotlFingerprint, R.string.axolotl_fingerprint)) {
|
||||||
|
Toast.makeText(
|
||||||
|
EditAccountActivity.this,
|
||||||
|
R.string.toast_message_axolotl_fingerprint,
|
||||||
|
Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.mRegenerateAxolotlKeyButton
|
||||||
|
.setVisibility(View.VISIBLE);
|
||||||
|
this.mRegenerateAxolotlKeyButton
|
||||||
|
.setOnClickListener(new View.OnClickListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(final View v) {
|
||||||
|
showRegenerateAxolotlKeyDialog();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.mAxolotlFingerprintBox.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (this.mAccount.errorStatus()) {
|
if (this.mAccount.errorStatus()) {
|
||||||
this.mAccountJid.setError(getString(this.mAccount.getStatus().getReadableId()));
|
this.mAccountJid.setError(getString(this.mAccount.getStatus().getReadableId()));
|
||||||
|
@ -508,4 +577,36 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
|
||||||
this.mStats.setVisibility(View.GONE);
|
this.mStats.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void showRegenerateAxolotlKeyDialog() {
|
||||||
|
Builder builder = new Builder(this);
|
||||||
|
builder.setTitle("Regenerate Key");
|
||||||
|
builder.setIconAttribute(android.R.attr.alertDialogIcon);
|
||||||
|
builder.setMessage("Are you sure you want to regenerate your Identity Key? (This will also wipe all established sessions and contact Identity Keys)");
|
||||||
|
builder.setNegativeButton(getString(R.string.cancel), null);
|
||||||
|
builder.setPositiveButton("Yes",
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
mAccount.getAxolotlService().regenerateKeys();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.create().show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void showWipePepDialog() {
|
||||||
|
Builder builder = new Builder(this);
|
||||||
|
builder.setTitle("Wipe PEP");
|
||||||
|
builder.setIconAttribute(android.R.attr.alertDialogIcon);
|
||||||
|
builder.setMessage("Are you sure you want to wipe all other devices from the PEP device ID list?");
|
||||||
|
builder.setNegativeButton(getString(R.string.cancel), null);
|
||||||
|
builder.setPositiveButton("Yes",
|
||||||
|
new DialogInterface.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
mAccount.getAxolotlService().wipeOtherPepDevices();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.create().show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -266,6 +266,29 @@ public abstract class XmppActivity extends Activity {
|
||||||
builder.create().show();
|
builder.create().show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void showAxolotlNoSessionsDialog() {
|
||||||
|
Builder builder = new AlertDialog.Builder(this);
|
||||||
|
builder.setTitle("No Sessions");
|
||||||
|
builder.setIconAttribute(android.R.attr.alertDialogIcon);
|
||||||
|
builder.setMessage("Your contact is not Axolotl-capable!");
|
||||||
|
builder.setNegativeButton(getString(R.string.cancel), null);
|
||||||
|
builder.setNeutralButton("Foo",
|
||||||
|
new OnClickListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.setPositiveButton("Bar",
|
||||||
|
new OnClickListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.create().show();
|
||||||
|
}
|
||||||
|
|
||||||
abstract void onBackendConnected();
|
abstract void onBackendConnected();
|
||||||
|
|
||||||
protected void registerListeners() {
|
protected void registerListeners() {
|
||||||
|
|
|
@ -94,11 +94,10 @@ public final class CryptoHelper {
|
||||||
if (fingerprint.length() < 40) {
|
if (fingerprint.length() < 40) {
|
||||||
return fingerprint;
|
return fingerprint;
|
||||||
}
|
}
|
||||||
StringBuilder builder = new StringBuilder(fingerprint);
|
StringBuilder builder = new StringBuilder(fingerprint.replaceAll("\\s",""));
|
||||||
builder.insert(8, " ");
|
for(int i=8;i<builder.length();i+=9) {
|
||||||
builder.insert(17, " ");
|
builder.insert(i, ' ');
|
||||||
builder.insert(26, " ");
|
}
|
||||||
builder.insert(35, " ");
|
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,11 @@ public class Element {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Element(String name, String xmlns) {
|
||||||
|
this.name = name;
|
||||||
|
this.setAttribute("xmlns", xmlns);
|
||||||
|
}
|
||||||
|
|
||||||
public Element addChild(Element child) {
|
public Element addChild(Element child) {
|
||||||
this.content = null;
|
this.content = null;
|
||||||
children.add(child);
|
children.add(child);
|
||||||
|
|
|
@ -29,6 +29,11 @@ public class MessagePacket extends AbstractStanza {
|
||||||
this.children.add(0, body);
|
this.children.add(0, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setAxolotlMessage(Element axolotlMessage) {
|
||||||
|
this.children.remove(findChild("body"));
|
||||||
|
this.children.add(0, axolotlMessage);
|
||||||
|
}
|
||||||
|
|
||||||
public void setType(int type) {
|
public void setType(int type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case TYPE_CHAT:
|
case TYPE_CHAT:
|
||||||
|
|
|
@ -341,6 +341,107 @@
|
||||||
android:src="?attr/icon_copy"
|
android:src="?attr/icon_copy"
|
||||||
android:visibility="visible"
|
android:visibility="visible"
|
||||||
android:contentDescription="@string/copy_otr_clipboard_description"/>
|
android:contentDescription="@string/copy_otr_clipboard_description"/>
|
||||||
|
</RelativeLayout>
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:id="@+id/axolotl_fingerprint_box"
|
||||||
|
android:layout_marginTop="32dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_toLeftOf="@+id/axolotl_actions"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/axolotl_fingerprint"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/primarytext"
|
||||||
|
android:textSize="?attr/TextSizeBody"
|
||||||
|
android:typeface="monospace" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/secondarytext"
|
||||||
|
android:textSize="?attr/TextSizeInfo"
|
||||||
|
android:text="@string/axolotl_fingerprint"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/axolotl_actions"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/action_copy_axolotl_to_clipboard"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?android:selectableItemBackground"
|
||||||
|
android:padding="@dimen/image_button_padding"
|
||||||
|
android:src="?attr/icon_copy"
|
||||||
|
android:visibility="visible"
|
||||||
|
android:contentDescription="@string/copy_axolotl_clipboard_description"/>
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/action_regenerate_axolotl_key"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?android:selectableItemBackground"
|
||||||
|
android:padding="@dimen/image_button_padding"
|
||||||
|
android:src="?attr/icon_refresh"
|
||||||
|
android:visibility="visible"
|
||||||
|
android:contentDescription="@string/regenerate_axolotl_key"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</RelativeLayout>
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/axolotl_devices_box"
|
||||||
|
android:layout_marginTop="32dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_toLeftOf="@+id/action_wipe_axolotl_pep"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/axolotl_devicelist"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/primarytext"
|
||||||
|
android:textSize="?attr/TextSizeBody"
|
||||||
|
android:typeface="monospace" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/secondarytext"
|
||||||
|
android:textSize="?attr/TextSizeInfo"
|
||||||
|
android:text="@string/axolotl_devicelist"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/action_wipe_axolotl_pep"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:background="?android:selectableItemBackground"
|
||||||
|
android:padding="@dimen/image_button_padding"
|
||||||
|
android:src="?attr/icon_remove"
|
||||||
|
android:visibility="visible"
|
||||||
|
android:contentDescription="@string/wipe_axolotl_pep"/>
|
||||||
|
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
|
@ -3,18 +3,18 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent" >
|
android:layout_height="match_parent" >
|
||||||
|
|
||||||
<LinearLayout
|
<RelativeLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignParentLeft="true"
|
android:layout_alignParentLeft="true"
|
||||||
android:layout_toLeftOf="@+id/button_remove"
|
android:layout_toLeftOf="@+id/button_remove"
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="8dp" >
|
android:padding="8dp" >
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/key"
|
android:id="@+id/key"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
android:textColor="@color/primarytext"
|
android:textColor="@color/primarytext"
|
||||||
android:textSize="?attr/TextSizeBody"
|
android:textSize="?attr/TextSizeBody"
|
||||||
android:typeface="monospace" />
|
android:typeface="monospace" />
|
||||||
|
@ -23,9 +23,21 @@
|
||||||
android:id="@+id/key_type"
|
android:id="@+id/key_type"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentLeft="true"
|
||||||
|
android:layout_below="@+id/key"
|
||||||
android:textColor="@color/secondarytext"
|
android:textColor="@color/secondarytext"
|
||||||
android:textSize="?attr/TextSizeInfo"/>
|
android:textSize="?attr/TextSizeInfo"/>
|
||||||
</LinearLayout>
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/key_trust"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentRight="true"
|
||||||
|
android:layout_below="@+id/key"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:textColor="@color/secondarytext"
|
||||||
|
android:textSize="?attr/TextSizeInfo"/>
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
<ImageButton
|
<ImageButton
|
||||||
android:id="@+id/button_remove"
|
android:id="@+id/button_remove"
|
||||||
|
|
|
@ -11,6 +11,9 @@
|
||||||
<item
|
<item
|
||||||
android:id="@+id/encryption_choice_pgp"
|
android:id="@+id/encryption_choice_pgp"
|
||||||
android:title="@string/encryption_choice_pgp"/>
|
android:title="@string/encryption_choice_pgp"/>
|
||||||
|
<item
|
||||||
|
android:id="@+id/encryption_choice_axolotl"
|
||||||
|
android:title="@string/encryption_choice_axolotl"/>
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
</menu>
|
</menu>
|
|
@ -80,6 +80,7 @@
|
||||||
<string name="choose_presence">Choose presence to contact</string>
|
<string name="choose_presence">Choose presence to contact</string>
|
||||||
<string name="send_plain_text_message">Send plain text message</string>
|
<string name="send_plain_text_message">Send plain text message</string>
|
||||||
<string name="send_otr_message">Send OTR encrypted message</string>
|
<string name="send_otr_message">Send OTR encrypted message</string>
|
||||||
|
<string name="send_axolotl_message">Send Axolotl encrypted message</string>
|
||||||
<string name="send_pgp_message">Send OpenPGP encrypted message</string>
|
<string name="send_pgp_message">Send OpenPGP encrypted message</string>
|
||||||
<string name="your_nick_has_been_changed">Your nickname has been changed</string>
|
<string name="your_nick_has_been_changed">Your nickname has been changed</string>
|
||||||
<string name="send_unencrypted">Send unencrypted</string>
|
<string name="send_unencrypted">Send unencrypted</string>
|
||||||
|
@ -154,6 +155,7 @@
|
||||||
<string name="encryption_choice_none">Plain text</string>
|
<string name="encryption_choice_none">Plain text</string>
|
||||||
<string name="encryption_choice_otr">OTR</string>
|
<string name="encryption_choice_otr">OTR</string>
|
||||||
<string name="encryption_choice_pgp">OpenPGP</string>
|
<string name="encryption_choice_pgp">OpenPGP</string>
|
||||||
|
<string name="encryption_choice_axolotl">Axolotl</string>
|
||||||
<string name="mgmt_account_edit">Edit account</string>
|
<string name="mgmt_account_edit">Edit account</string>
|
||||||
<string name="mgmt_account_delete">Delete account</string>
|
<string name="mgmt_account_delete">Delete account</string>
|
||||||
<string name="mgmt_account_disable">Temporarily disable</string>
|
<string name="mgmt_account_disable">Temporarily disable</string>
|
||||||
|
@ -206,6 +208,8 @@
|
||||||
<string name="reception_failed">Reception failed</string>
|
<string name="reception_failed">Reception failed</string>
|
||||||
<string name="your_fingerprint">Your fingerprint</string>
|
<string name="your_fingerprint">Your fingerprint</string>
|
||||||
<string name="otr_fingerprint">OTR fingerprint</string>
|
<string name="otr_fingerprint">OTR fingerprint</string>
|
||||||
|
<string name="axolotl_fingerprint">Axolotl fingerprint</string>
|
||||||
|
<string name="axolotl_devicelist">Other own Axolotl Devices</string>
|
||||||
<string name="verify">Verify</string>
|
<string name="verify">Verify</string>
|
||||||
<string name="decrypt">Decrypt</string>
|
<string name="decrypt">Decrypt</string>
|
||||||
<string name="conferences">Conferences</string>
|
<string name="conferences">Conferences</string>
|
||||||
|
@ -312,6 +316,7 @@
|
||||||
<string name="pref_conference_name">Conference name</string>
|
<string name="pref_conference_name">Conference name</string>
|
||||||
<string name="pref_conference_name_summary">Use room’s subject instead of JID to identify conferences</string>
|
<string name="pref_conference_name_summary">Use room’s subject instead of JID to identify conferences</string>
|
||||||
<string name="toast_message_otr_fingerprint">OTR fingerprint copied to clipboard!</string>
|
<string name="toast_message_otr_fingerprint">OTR fingerprint copied to clipboard!</string>
|
||||||
|
<string name="toast_message_axolotl_fingerprint">Axolotl fingerprint copied to clipboard!</string>
|
||||||
<string name="conference_banned">You are banned from this conference</string>
|
<string name="conference_banned">You are banned from this conference</string>
|
||||||
<string name="conference_members_only">This conference is members only</string>
|
<string name="conference_members_only">This conference is members only</string>
|
||||||
<string name="conference_kicked">You have been kicked from this conference</string>
|
<string name="conference_kicked">You have been kicked from this conference</string>
|
||||||
|
@ -379,6 +384,9 @@
|
||||||
<string name="reset">Reset</string>
|
<string name="reset">Reset</string>
|
||||||
<string name="account_image_description">Account avatar</string>
|
<string name="account_image_description">Account avatar</string>
|
||||||
<string name="copy_otr_clipboard_description">Copy OTR fingerprint to clipboard</string>
|
<string name="copy_otr_clipboard_description">Copy OTR fingerprint to clipboard</string>
|
||||||
|
<string name="copy_axolotl_clipboard_description">Copy Axolotl fingerprint to clipboard</string>
|
||||||
|
<string name="regenerate_axolotl_key">Copy Axolotl fingerprint to clipboard</string>
|
||||||
|
<string name="wipe_axolotl_pep">Wipe other devices from PEP</string>
|
||||||
<string name="fetching_history_from_server">Fetching history from server</string>
|
<string name="fetching_history_from_server">Fetching history from server</string>
|
||||||
<string name="no_more_history_on_server">No more history on server</string>
|
<string name="no_more_history_on_server">No more history on server</string>
|
||||||
<string name="updating">Updating…</string>
|
<string name="updating">Updating…</string>
|
||||||
|
|
Loading…
Reference in New Issue