copy commits
|
@ -1,8 +1,13 @@
|
||||||
###Changelog
|
###Changelog
|
||||||
|
|
||||||
|
####Version 1.6.0
|
||||||
|
* new multi-end-to-multi-end encryption method
|
||||||
|
* show unexpected encryption changes as red chat bubbles
|
||||||
|
* always notify in private/non-anonymous conferences
|
||||||
|
|
||||||
####Version 1.5.2
|
####Version 1.5.2
|
||||||
* added new message bubbles
|
* added new message bubbles
|
||||||
* added subtitles to chatviews in ActionBar to display typing info in single chats and participants names in conferences
|
* added subtitles to chatviews in ActionBar to display typing info in single chats and participant names in conferences
|
||||||
* some bug fixes
|
* some bug fixes
|
||||||
|
|
||||||
####Version 1.5.1
|
####Version 1.5.1
|
||||||
|
|
18
README.md
|
@ -39,24 +39,24 @@ support these extensions; therefore to get the most out of Conversations you
|
||||||
should consider either switching to an XMPP server that does or — even better —
|
should consider either switching to an XMPP server that does or — even better —
|
||||||
run your own XMPP server for you and your friends. These XEP's are:
|
run your own XMPP server for you and your friends. These XEP's are:
|
||||||
|
|
||||||
* XEP-0065: SOCKS5 Bytestreams (or mod_proxy65). Will be used to transfer
|
* [XEP-0065: SOCKS5 Bytestreams](http://xmpp.org/extensions/xep-0065.html) (or mod_proxy65). Will be used to transfer
|
||||||
files if both parties are behind a firewall (NAT).
|
files if both parties are behind a firewall (NAT).
|
||||||
* XEP-0163: Personal Eventing Protocol for avatars
|
* [XEP-0163: Personal Eventing Protocol](http://xmpp.org/extensions/xep-0163.html) for avatars
|
||||||
* XEP-0191: Blocking command lets you blacklist spammers or block contacts
|
* [XEP-0191: Blocking command](http://xmpp.org/extensions/xep-0191.html) lets you blacklist spammers or block contacts
|
||||||
without removing them from your roster.
|
without removing them from your roster.
|
||||||
* XEP-0198: Stream Management allows XMPP to survive small network outages and
|
* [XEP-0198: Stream Management](http://xmpp.org/extensions/xep-0198.html) allows XMPP to survive small network outages and
|
||||||
changes of the underlying TCP connection.
|
changes of the underlying TCP connection.
|
||||||
* XEP-0280: Message Carbons which automatically syncs the messages you send to
|
* [XEP-0280: Message Carbons](http://xmpp.org/extensions/xep-0280.html) which automatically syncs the messages you send to
|
||||||
your desktop client and thus allows you to switch seamlessly from your mobile
|
your desktop client and thus allows you to switch seamlessly from your mobile
|
||||||
client to your desktop client and back within one conversation.
|
client to your desktop client and back within one conversation.
|
||||||
* XEP-0237: Roster Versioning mainly to save bandwidth on poor mobile connections
|
* [XEP-0237: Roster Versioning](http://xmpp.org/extensions/xep-0237.html) mainly to save bandwidth on poor mobile connections
|
||||||
* XEP-0313: Message Archive Management synchronize message history with the
|
* [XEP-0313: Message Archive Management](http://xmpp.org/extensions/xep-0313.html) synchronize message history with the
|
||||||
server. Catch up with messages that were sent while Conversations was
|
server. Catch up with messages that were sent while Conversations was
|
||||||
offline.
|
offline.
|
||||||
* XEP-0352: Client State Indication lets the server know whether or not
|
* [XEP-0352: Client State Indication](http://xmpp.org/extensions/xep-0352.html) lets the server know whether or not
|
||||||
Conversations is in the background. Allows the server to save bandwidth by
|
Conversations is in the background. Allows the server to save bandwidth by
|
||||||
withholding unimportant packages.
|
withholding unimportant packages.
|
||||||
* XEP-xxxx: HttpUpload allows you to share files in conferences and with offline
|
* [XEP-xxxx: HTTP File Upload](http://xmpp.org/extensions/inbox/http-upload.html) allows you to share files in conferences and with offline
|
||||||
contacts. Requires an [additional component](https://github.com/siacs/HttpUploadComponent)
|
contacts. Requires an [additional component](https://github.com/siacs/HttpUploadComponent)
|
||||||
on your server.
|
on your server.
|
||||||
|
|
||||||
|
|
165
art/message_bubble_received_warning.svg
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="36"
|
||||||
|
height="26"
|
||||||
|
id="svg2"
|
||||||
|
version="1.1"
|
||||||
|
inkscape:version="0.48.5 r10040"
|
||||||
|
sodipodi:docname="message_bubble_received.svg">
|
||||||
|
<defs
|
||||||
|
id="defs4">
|
||||||
|
<filter
|
||||||
|
x="-0.25"
|
||||||
|
y="-0.25"
|
||||||
|
width="1.5"
|
||||||
|
height="1.5"
|
||||||
|
inkscape:label="Drop Shadow"
|
||||||
|
id="filter3811"
|
||||||
|
color-interpolation-filters="sRGB">
|
||||||
|
<feFlood
|
||||||
|
flood-opacity="0.25"
|
||||||
|
flood-color="rgb(0,0,0)"
|
||||||
|
result="flood"
|
||||||
|
id="feFlood3813" />
|
||||||
|
<feComposite
|
||||||
|
in="flood"
|
||||||
|
in2="SourceGraphic"
|
||||||
|
operator="in"
|
||||||
|
result="composite1"
|
||||||
|
id="feComposite3815" />
|
||||||
|
<feGaussianBlur
|
||||||
|
stdDeviation="0.5"
|
||||||
|
result="blur"
|
||||||
|
id="feGaussianBlur3817" />
|
||||||
|
<feOffset
|
||||||
|
dx="0"
|
||||||
|
dy="1"
|
||||||
|
result="offset"
|
||||||
|
id="feOffset3819" />
|
||||||
|
<feComposite
|
||||||
|
in="SourceGraphic"
|
||||||
|
in2="offset"
|
||||||
|
operator="over"
|
||||||
|
result="composite2"
|
||||||
|
id="feComposite3821" />
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="16"
|
||||||
|
inkscape:cx="25.745257"
|
||||||
|
inkscape:cy="9.618802"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:window-width="989"
|
||||||
|
inkscape:window-height="755"
|
||||||
|
inkscape:window-x="22"
|
||||||
|
inkscape:window-y="16"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
showguides="true"
|
||||||
|
inkscape:guide-bbox="true"
|
||||||
|
guidecolor="#000000"
|
||||||
|
guideopacity="0.49803922">
|
||||||
|
<inkscape:grid
|
||||||
|
type="xygrid"
|
||||||
|
id="grid2985"
|
||||||
|
empspacing="4"
|
||||||
|
visible="true"
|
||||||
|
enabled="true"
|
||||||
|
snapvisiblegridlinesonly="true"
|
||||||
|
spacingx="1px"
|
||||||
|
spacingy="1px"
|
||||||
|
originx="0px"
|
||||||
|
originy="0px"
|
||||||
|
color="#0000ff"
|
||||||
|
opacity="0.03137255" />
|
||||||
|
<sodipodi:guide
|
||||||
|
orientation="1,0"
|
||||||
|
position="20,26"
|
||||||
|
id="guide3060" />
|
||||||
|
<sodipodi:guide
|
||||||
|
orientation="1,0"
|
||||||
|
position="24,26"
|
||||||
|
id="guide3062" />
|
||||||
|
<sodipodi:guide
|
||||||
|
orientation="0,1"
|
||||||
|
position="36,22"
|
||||||
|
id="guide3064" />
|
||||||
|
<sodipodi:guide
|
||||||
|
orientation="0,1"
|
||||||
|
position="36,6"
|
||||||
|
id="guide3066" />
|
||||||
|
<sodipodi:guide
|
||||||
|
orientation="1,0"
|
||||||
|
position="26,0"
|
||||||
|
id="guide3068" />
|
||||||
|
<sodipodi:guide
|
||||||
|
orientation="1,0"
|
||||||
|
position="18,0"
|
||||||
|
id="guide3070" />
|
||||||
|
<sodipodi:guide
|
||||||
|
orientation="0,1"
|
||||||
|
position="0,10"
|
||||||
|
id="guide3074" />
|
||||||
|
<sodipodi:guide
|
||||||
|
orientation="0,1"
|
||||||
|
position="0,8"
|
||||||
|
id="guide3076" />
|
||||||
|
</sodipodi:namedview>
|
||||||
|
<metadata
|
||||||
|
id="metadata7">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer"
|
||||||
|
transform="translate(0,-2)">
|
||||||
|
<g
|
||||||
|
id="g3759"
|
||||||
|
style="fill:#c64545;fill-opacity:1;stroke:none;fill-rule:nonzero;filter:url(#filter3811)">
|
||||||
|
<path
|
||||||
|
style="display:none"
|
||||||
|
d="m 8,6 c 2,2 4,6 4,10 L 16,6 z"
|
||||||
|
id="path3805"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
transform="translate(0,2)"
|
||||||
|
sodipodi:nodetypes="cccc" />
|
||||||
|
<path
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
id="path2989"
|
||||||
|
d="M 4,4 16,16 16,4 z"
|
||||||
|
sodipodi:nodetypes="cccc" />
|
||||||
|
<rect
|
||||||
|
ry="2"
|
||||||
|
y="4"
|
||||||
|
x="12"
|
||||||
|
height="20"
|
||||||
|
width="20"
|
||||||
|
id="rect2987" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 4.2 KiB |
|
@ -44,6 +44,7 @@ images = {
|
||||||
'md_switch_thumb_on_normal.svg' => ['switch_thumb_on_normal', 48],
|
'md_switch_thumb_on_normal.svg' => ['switch_thumb_on_normal', 48],
|
||||||
'md_switch_thumb_on_pressed.svg' => ['switch_thumb_on_pressed', 48],
|
'md_switch_thumb_on_pressed.svg' => ['switch_thumb_on_pressed', 48],
|
||||||
'message_bubble_received.svg' => ['message_bubble_received.9', 0],
|
'message_bubble_received.svg' => ['message_bubble_received.9', 0],
|
||||||
|
'message_bubble_received_warning.svg' => ['message_bubble_received_warning.9', 0],
|
||||||
'message_bubble_sent.svg' => ['message_bubble_sent.9', 0],
|
'message_bubble_sent.svg' => ['message_bubble_sent.9', 0],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,8 +48,8 @@ android {
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 14
|
minSdkVersion 14
|
||||||
targetSdkVersion 21
|
targetSdkVersion 21
|
||||||
versionCode 80
|
versionCode 81
|
||||||
versionName "1.5.2"
|
versionName "1.6.0-beta"
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
package eu.siacs.conversations.crypto.axolotl;
|
||||||
|
|
||||||
|
public interface OnMessageCreatedCallback {
|
||||||
|
void run(XmppAxolotlMessage message);
|
||||||
|
}
|
|
@ -0,0 +1,421 @@
|
||||||
|
package eu.siacs.conversations.crypto.axolotl;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.LruCache;
|
||||||
|
|
||||||
|
import org.whispersystems.libaxolotl.AxolotlAddress;
|
||||||
|
import org.whispersystems.libaxolotl.IdentityKey;
|
||||||
|
import org.whispersystems.libaxolotl.IdentityKeyPair;
|
||||||
|
import org.whispersystems.libaxolotl.InvalidKeyIdException;
|
||||||
|
import org.whispersystems.libaxolotl.ecc.Curve;
|
||||||
|
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
|
||||||
|
import org.whispersystems.libaxolotl.state.AxolotlStore;
|
||||||
|
import org.whispersystems.libaxolotl.state.PreKeyRecord;
|
||||||
|
import org.whispersystems.libaxolotl.state.SessionRecord;
|
||||||
|
import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
|
||||||
|
import org.whispersystems.libaxolotl.util.KeyHelper;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.Config;
|
||||||
|
import eu.siacs.conversations.entities.Account;
|
||||||
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
|
|
||||||
|
public class SQLiteAxolotlStore implements AxolotlStore {
|
||||||
|
|
||||||
|
public static final String PREKEY_TABLENAME = "prekeys";
|
||||||
|
public static final String SIGNED_PREKEY_TABLENAME = "signed_prekeys";
|
||||||
|
public static final String SESSION_TABLENAME = "sessions";
|
||||||
|
public static final String IDENTITIES_TABLENAME = "identities";
|
||||||
|
public static final String ACCOUNT = "account";
|
||||||
|
public static final String DEVICE_ID = "device_id";
|
||||||
|
public static final String ID = "id";
|
||||||
|
public static final String KEY = "key";
|
||||||
|
public static final String FINGERPRINT = "fingerprint";
|
||||||
|
public static final String NAME = "name";
|
||||||
|
public static final String TRUSTED = "trusted";
|
||||||
|
public static final String OWN = "ownkey";
|
||||||
|
|
||||||
|
public static final String JSONKEY_REGISTRATION_ID = "axolotl_reg_id";
|
||||||
|
public static final String JSONKEY_CURRENT_PREKEY_ID = "axolotl_cur_prekey_id";
|
||||||
|
|
||||||
|
private static final int NUM_TRUSTS_TO_CACHE = 100;
|
||||||
|
|
||||||
|
private final Account account;
|
||||||
|
private final XmppConnectionService mXmppConnectionService;
|
||||||
|
|
||||||
|
private IdentityKeyPair identityKeyPair;
|
||||||
|
private int localRegistrationId;
|
||||||
|
private int currentPreKeyId = 0;
|
||||||
|
|
||||||
|
private final LruCache<String, XmppAxolotlSession.Trust> trustCache =
|
||||||
|
new LruCache<String, XmppAxolotlSession.Trust>(NUM_TRUSTS_TO_CACHE) {
|
||||||
|
@Override
|
||||||
|
protected XmppAxolotlSession.Trust create(String fingerprint) {
|
||||||
|
return mXmppConnectionService.databaseBackend.isIdentityKeyTrusted(account, fingerprint);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static IdentityKeyPair generateIdentityKeyPair() {
|
||||||
|
Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Generating axolotl IdentityKeyPair...");
|
||||||
|
ECKeyPair identityKeyPairKeys = Curve.generateKeyPair();
|
||||||
|
return new IdentityKeyPair(new IdentityKey(identityKeyPairKeys.getPublicKey()),
|
||||||
|
identityKeyPairKeys.getPrivateKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int generateRegistrationId() {
|
||||||
|
Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Generating axolotl registration ID...");
|
||||||
|
return KeyHelper.generateRegistrationId(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SQLiteAxolotlStore(Account account, XmppConnectionService service) {
|
||||||
|
this.account = account;
|
||||||
|
this.mXmppConnectionService = service;
|
||||||
|
this.localRegistrationId = loadRegistrationId();
|
||||||
|
this.currentPreKeyId = loadCurrentPreKeyId();
|
||||||
|
for (SignedPreKeyRecord record : loadSignedPreKeys()) {
|
||||||
|
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Got Axolotl signed prekey record:" + record.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCurrentPreKeyId() {
|
||||||
|
return currentPreKeyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------
|
||||||
|
// IdentityKeyStore
|
||||||
|
// --------------------------------------
|
||||||
|
|
||||||
|
private IdentityKeyPair loadIdentityKeyPair() {
|
||||||
|
String ownName = account.getJid().toBareJid().toString();
|
||||||
|
IdentityKeyPair ownKey = mXmppConnectionService.databaseBackend.loadOwnIdentityKeyPair(account,
|
||||||
|
ownName);
|
||||||
|
|
||||||
|
if (ownKey != null) {
|
||||||
|
return ownKey;
|
||||||
|
} else {
|
||||||
|
Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve axolotl key for account " + ownName);
|
||||||
|
ownKey = generateIdentityKeyPair();
|
||||||
|
mXmppConnectionService.databaseBackend.storeOwnIdentityKeyPair(account, ownName, ownKey);
|
||||||
|
}
|
||||||
|
return ownKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int loadRegistrationId() {
|
||||||
|
return loadRegistrationId(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int loadRegistrationId(boolean regenerate) {
|
||||||
|
String regIdString = this.account.getKey(JSONKEY_REGISTRATION_ID);
|
||||||
|
int reg_id;
|
||||||
|
if (!regenerate && regIdString != null) {
|
||||||
|
reg_id = Integer.valueOf(regIdString);
|
||||||
|
} else {
|
||||||
|
Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve axolotl registration id for account " + account.getJid());
|
||||||
|
reg_id = generateRegistrationId();
|
||||||
|
boolean success = this.account.setKey(JSONKEY_REGISTRATION_ID, Integer.toString(reg_id));
|
||||||
|
if (success) {
|
||||||
|
mXmppConnectionService.databaseBackend.updateAccount(account);
|
||||||
|
} else {
|
||||||
|
Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to write new key to the database!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reg_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int loadCurrentPreKeyId() {
|
||||||
|
String regIdString = this.account.getKey(JSONKEY_CURRENT_PREKEY_ID);
|
||||||
|
int reg_id;
|
||||||
|
if (regIdString != null) {
|
||||||
|
reg_id = Integer.valueOf(regIdString);
|
||||||
|
} else {
|
||||||
|
Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve current prekey id for account " + account.getJid());
|
||||||
|
reg_id = 0;
|
||||||
|
}
|
||||||
|
return reg_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void regenerate() {
|
||||||
|
mXmppConnectionService.databaseBackend.wipeAxolotlDb(account);
|
||||||
|
trustCache.evictAll();
|
||||||
|
account.setKey(JSONKEY_CURRENT_PREKEY_ID, Integer.toString(0));
|
||||||
|
identityKeyPair = loadIdentityKeyPair();
|
||||||
|
localRegistrationId = loadRegistrationId(true);
|
||||||
|
currentPreKeyId = 0;
|
||||||
|
mXmppConnectionService.updateAccountUi();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the local client's identity key pair.
|
||||||
|
*
|
||||||
|
* @return The local client's persistent identity key pair.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public IdentityKeyPair getIdentityKeyPair() {
|
||||||
|
if (identityKeyPair == null) {
|
||||||
|
identityKeyPair = loadIdentityKeyPair();
|
||||||
|
}
|
||||||
|
return identityKeyPair;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the local client's registration ID.
|
||||||
|
* <p/>
|
||||||
|
* Clients should maintain a registration ID, a random number
|
||||||
|
* between 1 and 16380 that's generated once at install time.
|
||||||
|
*
|
||||||
|
* @return the local client's registration ID.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getLocalRegistrationId() {
|
||||||
|
return localRegistrationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save a remote client's identity key
|
||||||
|
* <p/>
|
||||||
|
* Store a remote client's identity key as trusted.
|
||||||
|
*
|
||||||
|
* @param name The name of the remote client.
|
||||||
|
* @param identityKey The remote client's identity key.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void saveIdentity(String name, IdentityKey identityKey) {
|
||||||
|
if (!mXmppConnectionService.databaseBackend.loadIdentityKeys(account, name).contains(identityKey)) {
|
||||||
|
mXmppConnectionService.databaseBackend.storeIdentityKey(account, name, identityKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify a remote client's identity key.
|
||||||
|
* <p/>
|
||||||
|
* Determine whether a remote client's identity is trusted. Convention is
|
||||||
|
* that the TextSecure protocol is 'trust on first use.' This means that
|
||||||
|
* an identity key is considered 'trusted' if there is no entry for the recipient
|
||||||
|
* in the local store, or if it matches the saved key for a recipient in the local
|
||||||
|
* store. Only if it mismatches an entry in the local store is it considered
|
||||||
|
* 'untrusted.'
|
||||||
|
*
|
||||||
|
* @param name The name of the remote client.
|
||||||
|
* @param identityKey The identity key to verify.
|
||||||
|
* @return true if trusted, false if untrusted.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean isTrustedIdentity(String name, IdentityKey identityKey) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public XmppAxolotlSession.Trust getFingerprintTrust(String fingerprint) {
|
||||||
|
return (fingerprint == null)? null : trustCache.get(fingerprint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFingerprintTrust(String fingerprint, XmppAxolotlSession.Trust trust) {
|
||||||
|
mXmppConnectionService.databaseBackend.setIdentityKeyTrust(account, fingerprint, trust);
|
||||||
|
trustCache.remove(fingerprint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<IdentityKey> getContactKeysWithTrust(String bareJid, XmppAxolotlSession.Trust trust) {
|
||||||
|
return mXmppConnectionService.databaseBackend.loadIdentityKeys(account, bareJid, trust);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getContactNumTrustedKeys(String bareJid) {
|
||||||
|
return mXmppConnectionService.databaseBackend.numTrustedKeys(account, bareJid);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------
|
||||||
|
// SessionStore
|
||||||
|
// --------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a copy of the {@link SessionRecord} corresponding to the recipientId + deviceId tuple,
|
||||||
|
* or a new SessionRecord if one does not currently exist.
|
||||||
|
* <p/>
|
||||||
|
* It is important that implementations return a copy of the current durable information. The
|
||||||
|
* returned SessionRecord may be modified, but those changes should not have an effect on the
|
||||||
|
* durable session state (what is returned by subsequent calls to this method) without the
|
||||||
|
* store method being called here first.
|
||||||
|
*
|
||||||
|
* @param address The name and device ID of the remote client.
|
||||||
|
* @return a copy of the SessionRecord corresponding to the recipientId + deviceId tuple, or
|
||||||
|
* a new SessionRecord if one does not currently exist.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public SessionRecord loadSession(AxolotlAddress address) {
|
||||||
|
SessionRecord session = mXmppConnectionService.databaseBackend.loadSession(this.account, address);
|
||||||
|
return (session != null) ? session : new SessionRecord();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all known devices with active sessions for a recipient
|
||||||
|
*
|
||||||
|
* @param name the name of the client.
|
||||||
|
* @return all known sub-devices with active sessions.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<Integer> getSubDeviceSessions(String name) {
|
||||||
|
return mXmppConnectionService.databaseBackend.getSubDeviceSessions(account,
|
||||||
|
new AxolotlAddress(name, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Commit to storage the {@link SessionRecord} for a given recipientId + deviceId tuple.
|
||||||
|
*
|
||||||
|
* @param address the address of the remote client.
|
||||||
|
* @param record the current SessionRecord for the remote client.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void storeSession(AxolotlAddress address, SessionRecord record) {
|
||||||
|
mXmppConnectionService.databaseBackend.storeSession(account, address, record);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine whether there is a committed {@link SessionRecord} for a recipientId + deviceId tuple.
|
||||||
|
*
|
||||||
|
* @param address the address of the remote client.
|
||||||
|
* @return true if a {@link SessionRecord} exists, false otherwise.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean containsSession(AxolotlAddress address) {
|
||||||
|
return mXmppConnectionService.databaseBackend.containsSession(account, address);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a {@link SessionRecord} for a recipientId + deviceId tuple.
|
||||||
|
*
|
||||||
|
* @param address the address of the remote client.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void deleteSession(AxolotlAddress address) {
|
||||||
|
mXmppConnectionService.databaseBackend.deleteSession(account, address);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the {@link SessionRecord}s corresponding to all devices of a recipientId.
|
||||||
|
*
|
||||||
|
* @param name the name of the remote client.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void deleteAllSessions(String name) {
|
||||||
|
AxolotlAddress address = new AxolotlAddress(name, 0);
|
||||||
|
mXmppConnectionService.databaseBackend.deleteAllSessions(account,
|
||||||
|
address);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------
|
||||||
|
// PreKeyStore
|
||||||
|
// --------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a local PreKeyRecord.
|
||||||
|
*
|
||||||
|
* @param preKeyId the ID of the local PreKeyRecord.
|
||||||
|
* @return the corresponding PreKeyRecord.
|
||||||
|
* @throws InvalidKeyIdException when there is no corresponding PreKeyRecord.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException {
|
||||||
|
PreKeyRecord record = mXmppConnectionService.databaseBackend.loadPreKey(account, preKeyId);
|
||||||
|
if (record == null) {
|
||||||
|
throw new InvalidKeyIdException("No such PreKeyRecord: " + preKeyId);
|
||||||
|
}
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a local PreKeyRecord.
|
||||||
|
*
|
||||||
|
* @param preKeyId the ID of the PreKeyRecord to store.
|
||||||
|
* @param record the PreKeyRecord.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void storePreKey(int preKeyId, PreKeyRecord record) {
|
||||||
|
mXmppConnectionService.databaseBackend.storePreKey(account, record);
|
||||||
|
currentPreKeyId = preKeyId;
|
||||||
|
boolean success = this.account.setKey(JSONKEY_CURRENT_PREKEY_ID, Integer.toString(preKeyId));
|
||||||
|
if (success) {
|
||||||
|
mXmppConnectionService.databaseBackend.updateAccount(account);
|
||||||
|
} else {
|
||||||
|
Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to write new prekey id to the database!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param preKeyId A PreKeyRecord ID.
|
||||||
|
* @return true if the store has a record for the preKeyId, otherwise false.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean containsPreKey(int preKeyId) {
|
||||||
|
return mXmppConnectionService.databaseBackend.containsPreKey(account, preKeyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a PreKeyRecord from local storage.
|
||||||
|
*
|
||||||
|
* @param preKeyId The ID of the PreKeyRecord to remove.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void removePreKey(int preKeyId) {
|
||||||
|
mXmppConnectionService.databaseBackend.deletePreKey(account, preKeyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------
|
||||||
|
// SignedPreKeyStore
|
||||||
|
// --------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load a local SignedPreKeyRecord.
|
||||||
|
*
|
||||||
|
* @param signedPreKeyId the ID of the local SignedPreKeyRecord.
|
||||||
|
* @return the corresponding SignedPreKeyRecord.
|
||||||
|
* @throws InvalidKeyIdException when there is no corresponding SignedPreKeyRecord.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException {
|
||||||
|
SignedPreKeyRecord record = mXmppConnectionService.databaseBackend.loadSignedPreKey(account, signedPreKeyId);
|
||||||
|
if (record == null) {
|
||||||
|
throw new InvalidKeyIdException("No such SignedPreKeyRecord: " + signedPreKeyId);
|
||||||
|
}
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load all local SignedPreKeyRecords.
|
||||||
|
*
|
||||||
|
* @return All stored SignedPreKeyRecords.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public List<SignedPreKeyRecord> loadSignedPreKeys() {
|
||||||
|
return mXmppConnectionService.databaseBackend.loadSignedPreKeys(account);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store a local SignedPreKeyRecord.
|
||||||
|
*
|
||||||
|
* @param signedPreKeyId the ID of the SignedPreKeyRecord to store.
|
||||||
|
* @param record the SignedPreKeyRecord.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) {
|
||||||
|
mXmppConnectionService.databaseBackend.storeSignedPreKey(account, record);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param signedPreKeyId A SignedPreKeyRecord ID.
|
||||||
|
* @return true if the store has a record for the signedPreKeyId, otherwise false.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean containsSignedPreKey(int signedPreKeyId) {
|
||||||
|
return mXmppConnectionService.databaseBackend.containsSignedPreKey(account, signedPreKeyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a SignedPreKeyRecord from local storage.
|
||||||
|
*
|
||||||
|
* @param signedPreKeyId The ID of the SignedPreKeyRecord to remove.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void removeSignedPreKey(int signedPreKeyId) {
|
||||||
|
mXmppConnectionService.databaseBackend.deleteSignedPreKey(account, signedPreKeyId);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,15 +1,16 @@
|
||||||
package eu.siacs.conversations.crypto.axolotl;
|
package eu.siacs.conversations.crypto.axolotl;
|
||||||
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.security.NoSuchProviderException;
|
import java.security.NoSuchProviderException;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.HashSet;
|
import java.util.HashMap;
|
||||||
import java.util.Set;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.crypto.BadPaddingException;
|
import javax.crypto.BadPaddingException;
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
|
@ -20,59 +21,35 @@ import javax.crypto.SecretKey;
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.xml.Element;
|
import eu.siacs.conversations.xml.Element;
|
||||||
import eu.siacs.conversations.xmpp.jid.Jid;
|
import eu.siacs.conversations.xmpp.jid.Jid;
|
||||||
|
|
||||||
public class XmppAxolotlMessage {
|
public class XmppAxolotlMessage {
|
||||||
|
public static final String CONTAINERTAG = "encrypted";
|
||||||
|
public static final String HEADER = "header";
|
||||||
|
public static final String SOURCEID = "sid";
|
||||||
|
public static final String KEYTAG = "key";
|
||||||
|
public static final String REMOTEID = "rid";
|
||||||
|
public static final String IVTAG = "iv";
|
||||||
|
public static final String PAYLOAD = "payload";
|
||||||
|
|
||||||
|
private static final String KEYTYPE = "AES";
|
||||||
|
private static final String CIPHERMODE = "AES/GCM/NoPadding";
|
||||||
|
private static final String PROVIDER = "BC";
|
||||||
|
|
||||||
private byte[] innerKey;
|
private byte[] innerKey;
|
||||||
private byte[] ciphertext;
|
private byte[] ciphertext = null;
|
||||||
private byte[] iv;
|
private byte[] iv = null;
|
||||||
private final Set<XmppAxolotlMessageHeader> headers;
|
private final Map<Integer, byte[]> keys;
|
||||||
private final Jid from;
|
private final Jid from;
|
||||||
private final int sourceDeviceId;
|
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 {
|
public static class XmppAxolotlPlaintextMessage {
|
||||||
private final AxolotlService.XmppAxolotlSession session;
|
|
||||||
private final String plaintext;
|
private final String plaintext;
|
||||||
private final String fingerprint;
|
private final String fingerprint;
|
||||||
|
|
||||||
public XmppAxolotlPlaintextMessage(AxolotlService.XmppAxolotlSession session, String plaintext, String fingerprint) {
|
public XmppAxolotlPlaintextMessage(String plaintext, String fingerprint) {
|
||||||
this.session = session;
|
|
||||||
this.plaintext = plaintext;
|
this.plaintext = plaintext;
|
||||||
this.fingerprint = fingerprint;
|
this.fingerprint = fingerprint;
|
||||||
}
|
}
|
||||||
|
@ -81,51 +58,105 @@ public class XmppAxolotlMessage {
|
||||||
return plaintext;
|
return plaintext;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AxolotlService.XmppAxolotlSession getSession() {
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getFingerprint() {
|
public String getFingerprint() {
|
||||||
return fingerprint;
|
return fingerprint;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public XmppAxolotlMessage(Jid from, Element axolotlMessage) {
|
public static class XmppAxolotlKeyTransportMessage {
|
||||||
this.from = from;
|
private final String fingerprint;
|
||||||
this.sourceDeviceId = Integer.parseInt(axolotlMessage.getAttribute("id"));
|
private final byte[] key;
|
||||||
this.headers = new HashSet<>();
|
private final byte[] iv;
|
||||||
for(Element child:axolotlMessage.getChildren()) {
|
|
||||||
switch(child.getName()) {
|
public XmppAxolotlKeyTransportMessage(String fingerprint, byte[] key, byte[] iv) {
|
||||||
case "header":
|
this.fingerprint = fingerprint;
|
||||||
headers.add(new XmppAxolotlMessageHeader(child));
|
this.key = key;
|
||||||
break;
|
this.iv = iv;
|
||||||
case "message":
|
}
|
||||||
iv = Base64.decode(child.getAttribute("iv"),Base64.DEFAULT);
|
|
||||||
ciphertext = Base64.decode(child.getContent(),Base64.DEFAULT);
|
public String getFingerprint() {
|
||||||
break;
|
return fingerprint;
|
||||||
default:
|
}
|
||||||
break;
|
|
||||||
}
|
public byte[] getKey() {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getIv() {
|
||||||
|
return iv;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public XmppAxolotlMessage(Jid from, int sourceDeviceId, String plaintext) throws CryptoFailedException{
|
private XmppAxolotlMessage(final Element axolotlMessage, final Jid from) throws IllegalArgumentException {
|
||||||
this.from = from;
|
this.from = from;
|
||||||
this.sourceDeviceId = sourceDeviceId;
|
Element header = axolotlMessage.findChild(HEADER);
|
||||||
this.headers = new HashSet<>();
|
this.sourceDeviceId = Integer.parseInt(header.getAttribute(SOURCEID));
|
||||||
this.encrypt(plaintext);
|
List<Element> keyElements = header.getChildren();
|
||||||
|
this.keys = new HashMap<>(keyElements.size());
|
||||||
|
for (Element keyElement : keyElements) {
|
||||||
|
switch (keyElement.getName()) {
|
||||||
|
case KEYTAG:
|
||||||
|
try {
|
||||||
|
Integer recipientId = Integer.parseInt(keyElement.getAttribute(REMOTEID));
|
||||||
|
byte[] key = Base64.decode(keyElement.getContent(), Base64.DEFAULT);
|
||||||
|
this.keys.put(recipientId, key);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new IllegalArgumentException(e);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case IVTAG:
|
||||||
|
if (this.iv != null) {
|
||||||
|
throw new IllegalArgumentException("Duplicate iv entry");
|
||||||
|
}
|
||||||
|
iv = Base64.decode(keyElement.getContent(), Base64.DEFAULT);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Log.w(Config.LOGTAG, "Unexpected element in header: " + keyElement.toString());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Element payloadElement = axolotlMessage.findChild(PAYLOAD);
|
||||||
|
if (payloadElement != null) {
|
||||||
|
ciphertext = Base64.decode(payloadElement.getContent(), Base64.DEFAULT);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void encrypt(String plaintext) throws CryptoFailedException {
|
public XmppAxolotlMessage(Jid from, int sourceDeviceId) {
|
||||||
|
this.from = from;
|
||||||
|
this.sourceDeviceId = sourceDeviceId;
|
||||||
|
this.keys = new HashMap<>();
|
||||||
|
this.iv = generateIv();
|
||||||
|
this.innerKey = generateKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static XmppAxolotlMessage fromElement(Element element, Jid from) {
|
||||||
|
return new XmppAxolotlMessage(element, from);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] generateKey() {
|
||||||
try {
|
try {
|
||||||
KeyGenerator generator = KeyGenerator.getInstance("AES");
|
KeyGenerator generator = KeyGenerator.getInstance(KEYTYPE);
|
||||||
generator.init(128);
|
generator.init(128);
|
||||||
SecretKey secretKey = generator.generateKey();
|
return generator.generateKey().getEncoded();
|
||||||
SecureRandom random = new SecureRandom();
|
} catch (NoSuchAlgorithmException e) {
|
||||||
this.iv = new byte[16];
|
Log.e(Config.LOGTAG, e.getMessage());
|
||||||
random.nextBytes(iv);
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] generateIv() {
|
||||||
|
SecureRandom random = new SecureRandom();
|
||||||
|
byte[] iv = new byte[16];
|
||||||
|
random.nextBytes(iv);
|
||||||
|
return iv;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void encrypt(String plaintext) throws CryptoFailedException {
|
||||||
|
try {
|
||||||
|
SecretKey secretKey = new SecretKeySpec(innerKey, KEYTYPE);
|
||||||
IvParameterSpec ivSpec = new IvParameterSpec(iv);
|
IvParameterSpec ivSpec = new IvParameterSpec(iv);
|
||||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
|
Cipher cipher = Cipher.getInstance(CIPHERMODE, PROVIDER);
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
|
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
|
||||||
this.innerKey = secretKey.getEncoded();
|
this.innerKey = secretKey.getEncoded();
|
||||||
this.ciphertext = cipher.doFinal(plaintext.getBytes());
|
this.ciphertext = cipher.doFinal(plaintext.getBytes());
|
||||||
|
@ -148,17 +179,14 @@ public class XmppAxolotlMessage {
|
||||||
return ciphertext;
|
return ciphertext;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<XmppAxolotlMessageHeader> getHeaders() {
|
public void addDevice(XmppAxolotlSession session) {
|
||||||
return headers;
|
byte[] key = session.processSending(innerKey);
|
||||||
}
|
if (key != null) {
|
||||||
|
keys.put(session.getRemoteAddress().getDeviceId(), key);
|
||||||
public void addHeader(@Nullable XmppAxolotlMessageHeader header) {
|
|
||||||
if (header != null) {
|
|
||||||
headers.add(header);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getInnerKey(){
|
public byte[] getInnerKey() {
|
||||||
return innerKey;
|
return innerKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,37 +194,55 @@ public class XmppAxolotlMessage {
|
||||||
return this.iv;
|
return this.iv;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Element toXml() {
|
public Element toElement() {
|
||||||
// TODO: generate outer XML, add in header XML
|
Element encryptionElement = new Element(CONTAINERTAG, AxolotlService.PEP_PREFIX);
|
||||||
Element message= new Element("axolotl_message", AxolotlService.PEP_PREFIX);
|
Element headerElement = encryptionElement.addChild(HEADER);
|
||||||
message.setAttribute("id", sourceDeviceId);
|
headerElement.setAttribute(SOURCEID, sourceDeviceId);
|
||||||
for(XmppAxolotlMessageHeader header: headers) {
|
for (Map.Entry<Integer, byte[]> keyEntry : keys.entrySet()) {
|
||||||
message.addChild(header.toXml());
|
Element keyElement = new Element(KEYTAG);
|
||||||
|
keyElement.setAttribute(REMOTEID, keyEntry.getKey());
|
||||||
|
keyElement.setContent(Base64.encodeToString(keyEntry.getValue(), Base64.DEFAULT));
|
||||||
|
headerElement.addChild(keyElement);
|
||||||
}
|
}
|
||||||
Element payload = message.addChild("message");
|
headerElement.addChild(IVTAG).setContent(Base64.encodeToString(iv, Base64.DEFAULT));
|
||||||
payload.setAttribute("iv",Base64.encodeToString(iv, Base64.DEFAULT));
|
if (ciphertext != null) {
|
||||||
payload.setContent(Base64.encodeToString(ciphertext,Base64.DEFAULT));
|
Element payload = encryptionElement.addChild(PAYLOAD);
|
||||||
return message;
|
payload.setContent(Base64.encodeToString(ciphertext, Base64.DEFAULT));
|
||||||
|
}
|
||||||
|
return encryptionElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private byte[] unpackKey(XmppAxolotlSession session, Integer sourceDeviceId) {
|
||||||
|
byte[] encryptedKey = keys.get(sourceDeviceId);
|
||||||
|
return (encryptedKey != null) ? session.processReceiving(encryptedKey) : null;
|
||||||
|
}
|
||||||
|
|
||||||
public XmppAxolotlPlaintextMessage decrypt(AxolotlService.XmppAxolotlSession session, byte[] key, String fingerprint) throws CryptoFailedException {
|
public XmppAxolotlKeyTransportMessage getParameters(XmppAxolotlSession session, Integer sourceDeviceId) {
|
||||||
|
byte[] key = unpackKey(session, sourceDeviceId);
|
||||||
|
return (key != null)
|
||||||
|
? new XmppAxolotlKeyTransportMessage(session.getFingerprint(), key, getIV())
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public XmppAxolotlPlaintextMessage decrypt(XmppAxolotlSession session, Integer sourceDeviceId) throws CryptoFailedException {
|
||||||
XmppAxolotlPlaintextMessage plaintextMessage = null;
|
XmppAxolotlPlaintextMessage plaintextMessage = null;
|
||||||
try {
|
byte[] key = unpackKey(session, sourceDeviceId);
|
||||||
|
if (key != null) {
|
||||||
|
try {
|
||||||
|
Cipher cipher = Cipher.getInstance(CIPHERMODE, PROVIDER);
|
||||||
|
SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE);
|
||||||
|
IvParameterSpec ivSpec = new IvParameterSpec(iv);
|
||||||
|
|
||||||
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
|
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
|
||||||
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(plaintext, session.getFingerprint());
|
||||||
|
|
||||||
String plaintext = new String(cipher.doFinal(ciphertext));
|
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
|
||||||
plaintextMessage = new XmppAxolotlPlaintextMessage(session, plaintext, fingerprint);
|
| InvalidAlgorithmParameterException | IllegalBlockSizeException
|
||||||
|
| BadPaddingException | NoSuchProviderException e) {
|
||||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
|
throw new CryptoFailedException(e);
|
||||||
| InvalidAlgorithmParameterException | IllegalBlockSizeException
|
}
|
||||||
| BadPaddingException | NoSuchProviderException e) {
|
|
||||||
throw new CryptoFailedException(e);
|
|
||||||
}
|
}
|
||||||
return plaintextMessage;
|
return plaintextMessage;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,196 @@
|
||||||
|
package eu.siacs.conversations.crypto.axolotl;
|
||||||
|
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.whispersystems.libaxolotl.AxolotlAddress;
|
||||||
|
import org.whispersystems.libaxolotl.DuplicateMessageException;
|
||||||
|
import org.whispersystems.libaxolotl.InvalidKeyException;
|
||||||
|
import org.whispersystems.libaxolotl.InvalidKeyIdException;
|
||||||
|
import org.whispersystems.libaxolotl.InvalidMessageException;
|
||||||
|
import org.whispersystems.libaxolotl.InvalidVersionException;
|
||||||
|
import org.whispersystems.libaxolotl.LegacyMessageException;
|
||||||
|
import org.whispersystems.libaxolotl.NoSessionException;
|
||||||
|
import org.whispersystems.libaxolotl.SessionCipher;
|
||||||
|
import org.whispersystems.libaxolotl.UntrustedIdentityException;
|
||||||
|
import org.whispersystems.libaxolotl.protocol.CiphertextMessage;
|
||||||
|
import org.whispersystems.libaxolotl.protocol.PreKeyWhisperMessage;
|
||||||
|
import org.whispersystems.libaxolotl.protocol.WhisperMessage;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.Config;
|
||||||
|
import eu.siacs.conversations.entities.Account;
|
||||||
|
|
||||||
|
public class XmppAxolotlSession {
|
||||||
|
private final SessionCipher cipher;
|
||||||
|
private final SQLiteAxolotlStore sqLiteAxolotlStore;
|
||||||
|
private final AxolotlAddress remoteAddress;
|
||||||
|
private final Account account;
|
||||||
|
private String fingerprint = null;
|
||||||
|
private Integer preKeyId = null;
|
||||||
|
private boolean fresh = true;
|
||||||
|
|
||||||
|
public enum Trust {
|
||||||
|
UNDECIDED(0),
|
||||||
|
TRUSTED(1),
|
||||||
|
UNTRUSTED(2),
|
||||||
|
COMPROMISED(3),
|
||||||
|
INACTIVE_TRUSTED(4),
|
||||||
|
INACTIVE_UNDECIDED(5),
|
||||||
|
INACTIVE_UNTRUSTED(6);
|
||||||
|
|
||||||
|
private static final Map<Integer, Trust> trustsByValue = new HashMap<>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
for (Trust trust : Trust.values()) {
|
||||||
|
trustsByValue.put(trust.getCode(), trust);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final int code;
|
||||||
|
|
||||||
|
Trust(int code) {
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCode() {
|
||||||
|
return this.code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String toString() {
|
||||||
|
switch (this) {
|
||||||
|
case UNDECIDED:
|
||||||
|
return "Trust undecided " + getCode();
|
||||||
|
case TRUSTED:
|
||||||
|
return "Trusted " + getCode();
|
||||||
|
case COMPROMISED:
|
||||||
|
return "Compromised " + getCode();
|
||||||
|
case INACTIVE_TRUSTED:
|
||||||
|
return "Inactive (Trusted)" + getCode();
|
||||||
|
case INACTIVE_UNDECIDED:
|
||||||
|
return "Inactive (Undecided)" + getCode();
|
||||||
|
case INACTIVE_UNTRUSTED:
|
||||||
|
return "Inactive (Untrusted)" + getCode();
|
||||||
|
case UNTRUSTED:
|
||||||
|
default:
|
||||||
|
return "Untrusted " + getCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Trust fromBoolean(Boolean trusted) {
|
||||||
|
return trusted ? TRUSTED : UNTRUSTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Trust fromCode(int code) {
|
||||||
|
return trustsByValue.get(code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress, String fingerprint) {
|
||||||
|
this(account, store, remoteAddress);
|
||||||
|
this.fingerprint = fingerprint;
|
||||||
|
}
|
||||||
|
|
||||||
|
public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, AxolotlAddress remoteAddress) {
|
||||||
|
this.cipher = new SessionCipher(store, remoteAddress);
|
||||||
|
this.remoteAddress = remoteAddress;
|
||||||
|
this.sqLiteAxolotlStore = store;
|
||||||
|
this.account = account;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getPreKeyId() {
|
||||||
|
return preKeyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resetPreKeyId() {
|
||||||
|
|
||||||
|
preKeyId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFingerprint() {
|
||||||
|
return fingerprint;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AxolotlAddress getRemoteAddress() {
|
||||||
|
return remoteAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isFresh() {
|
||||||
|
return fresh;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNotFresh() {
|
||||||
|
this.fresh = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setTrust(Trust trust) {
|
||||||
|
sqLiteAxolotlStore.setFingerprintTrust(fingerprint, trust);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Trust getTrust() {
|
||||||
|
Trust trust = sqLiteAxolotlStore.getFingerprintTrust(fingerprint);
|
||||||
|
return (trust == null) ? Trust.UNDECIDED : trust;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public byte[] processReceiving(byte[] encryptedKey) {
|
||||||
|
byte[] plaintext = null;
|
||||||
|
Trust trust = getTrust();
|
||||||
|
switch (trust) {
|
||||||
|
case INACTIVE_TRUSTED:
|
||||||
|
case UNDECIDED:
|
||||||
|
case UNTRUSTED:
|
||||||
|
case TRUSTED:
|
||||||
|
try {
|
||||||
|
try {
|
||||||
|
PreKeyWhisperMessage message = new PreKeyWhisperMessage(encryptedKey);
|
||||||
|
Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "PreKeyWhisperMessage received, new session ID:" + message.getSignedPreKeyId() + "/" + message.getPreKeyId());
|
||||||
|
String fingerprint = message.getIdentityKey().getFingerprint().replaceAll("\\s", "");
|
||||||
|
if (this.fingerprint != null && !this.fingerprint.equals(fingerprint)) {
|
||||||
|
Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Had session with fingerprint " + this.fingerprint + ", received message with fingerprint " + fingerprint);
|
||||||
|
} else {
|
||||||
|
this.fingerprint = fingerprint;
|
||||||
|
plaintext = cipher.decrypt(message);
|
||||||
|
if (message.getPreKeyId().isPresent()) {
|
||||||
|
preKeyId = message.getPreKeyId().get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (InvalidMessageException | InvalidVersionException e) {
|
||||||
|
Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "WhisperMessage received");
|
||||||
|
WhisperMessage message = new WhisperMessage(encryptedKey);
|
||||||
|
plaintext = cipher.decrypt(message);
|
||||||
|
} catch (InvalidKeyException | InvalidKeyIdException | UntrustedIdentityException e) {
|
||||||
|
Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage());
|
||||||
|
}
|
||||||
|
} catch (LegacyMessageException | InvalidMessageException | DuplicateMessageException | NoSessionException e) {
|
||||||
|
Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error decrypting axolotl header, " + e.getClass().getName() + ": " + e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plaintext != null && trust == Trust.INACTIVE_TRUSTED) {
|
||||||
|
setTrust(Trust.TRUSTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case COMPROMISED:
|
||||||
|
default:
|
||||||
|
// ignore
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return plaintext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public byte[] processSending(@NonNull byte[] outgoingMessage) {
|
||||||
|
Trust trust = getTrust();
|
||||||
|
if (trust == Trust.TRUSTED) {
|
||||||
|
CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage);
|
||||||
|
return ciphertextMessage.serialize();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -563,42 +563,51 @@ public class Conversation extends AbstractEntity implements Blockable {
|
||||||
return this.nextCounterpart;
|
return this.nextCounterpart;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getLatestEncryption() {
|
private int getMostRecentlyUsedOutgoingEncryption() {
|
||||||
int latestEncryption = this.getLatestMessage().getEncryption();
|
synchronized (this.messages) {
|
||||||
if ((latestEncryption == Message.ENCRYPTION_DECRYPTED)
|
for(int i = this.messages.size() -1; i >= 0; --i) {
|
||||||
|| (latestEncryption == Message.ENCRYPTION_DECRYPTION_FAILED)) {
|
final Message m = this.messages.get(0);
|
||||||
return Message.ENCRYPTION_PGP;
|
if (!m.isCarbon() && m.getStatus() != Message.STATUS_RECEIVED) {
|
||||||
} else {
|
final int e = m.getEncryption();
|
||||||
return latestEncryption;
|
if (e == Message.ENCRYPTION_DECRYPTED || e == Message.ENCRYPTION_DECRYPTION_FAILED) {
|
||||||
}
|
return Message.ENCRYPTION_PGP;
|
||||||
}
|
|
||||||
|
|
||||||
public int getNextEncryption(boolean force) {
|
|
||||||
int next = this.getIntAttribute(ATTRIBUTE_NEXT_ENCRYPTION, -1);
|
|
||||||
if (next == -1) {
|
|
||||||
int latest = this.getLatestEncryption();
|
|
||||||
if (latest == Message.ENCRYPTION_NONE) {
|
|
||||||
if (force && getMode() == MODE_SINGLE) {
|
|
||||||
return Message.ENCRYPTION_OTR;
|
|
||||||
} else if (getContact().getPresences().size() == 1) {
|
|
||||||
if (getContact().getOtrFingerprints().size() >= 1) {
|
|
||||||
return Message.ENCRYPTION_OTR;
|
|
||||||
} else {
|
} else {
|
||||||
return latest;
|
return e;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return latest;
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return latest;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (next == Message.ENCRYPTION_NONE && force
|
return Message.ENCRYPTION_NONE;
|
||||||
&& getMode() == MODE_SINGLE) {
|
}
|
||||||
return Message.ENCRYPTION_OTR;
|
|
||||||
} else {
|
private int getMostRecentlyUsedIncomingEncryption() {
|
||||||
return next;
|
synchronized (this.messages) {
|
||||||
|
for(int i = this.messages.size() -1; i >= 0; --i) {
|
||||||
|
final Message m = this.messages.get(0);
|
||||||
|
if (m.getStatus() == Message.STATUS_RECEIVED) {
|
||||||
|
final int e = m.getEncryption();
|
||||||
|
if (e == Message.ENCRYPTION_DECRYPTED || e == Message.ENCRYPTION_DECRYPTION_FAILED) {
|
||||||
|
return Message.ENCRYPTION_PGP;
|
||||||
|
} else {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return Message.ENCRYPTION_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNextEncryption() {
|
||||||
|
int next = this.getIntAttribute(ATTRIBUTE_NEXT_ENCRYPTION, -1);
|
||||||
|
if (next == -1) {
|
||||||
|
int outgoing = this.getMostRecentlyUsedOutgoingEncryption();
|
||||||
|
if (outgoing == Message.ENCRYPTION_NONE) {
|
||||||
|
return this.getMostRecentlyUsedIncomingEncryption();
|
||||||
|
} else {
|
||||||
|
return outgoing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return next;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setNextEncryption(int encryption) {
|
public void setNextEncryption(int encryption) {
|
||||||
|
|
|
@ -1,26 +1,7 @@
|
||||||
package eu.siacs.conversations.entities;
|
package eu.siacs.conversations.entities;
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
|
||||||
import java.security.InvalidKeyException;
|
|
||||||
import java.security.Key;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
|
||||||
import javax.crypto.CipherInputStream;
|
|
||||||
import javax.crypto.CipherOutputStream;
|
|
||||||
import javax.crypto.NoSuchPaddingException;
|
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
|
||||||
import eu.siacs.conversations.utils.MimeUtils;
|
import eu.siacs.conversations.utils.MimeUtils;
|
||||||
|
|
||||||
public class DownloadableFile extends File {
|
public class DownloadableFile extends File {
|
||||||
|
@ -29,8 +10,7 @@ public class DownloadableFile extends File {
|
||||||
|
|
||||||
private long expectedSize = 0;
|
private long expectedSize = 0;
|
||||||
private String sha1sum;
|
private String sha1sum;
|
||||||
private Key aeskey;
|
private byte[] aeskey;
|
||||||
private String mime;
|
|
||||||
|
|
||||||
private byte[] iv = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
private byte[] iv = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||||
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0xf };
|
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0xf };
|
||||||
|
@ -44,15 +24,7 @@ public class DownloadableFile extends File {
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getExpectedSize() {
|
public long getExpectedSize() {
|
||||||
if (this.aeskey != null) {
|
return this.expectedSize;
|
||||||
if (this.expectedSize == 0) {
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return (this.expectedSize / 16 + 1) * 16;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return this.expectedSize;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMimeType() {
|
public String getMimeType() {
|
||||||
|
@ -78,91 +50,38 @@ public class DownloadableFile extends File {
|
||||||
this.sha1sum = sum;
|
this.sha1sum = sum;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setKey(byte[] key) {
|
public void setKeyAndIv(byte[] keyIvCombo) {
|
||||||
if (key.length == 48) {
|
if (keyIvCombo.length == 48) {
|
||||||
byte[] secretKey = new byte[32];
|
byte[] secretKey = new byte[32];
|
||||||
byte[] iv = new byte[16];
|
byte[] iv = new byte[16];
|
||||||
System.arraycopy(key, 0, iv, 0, 16);
|
System.arraycopy(keyIvCombo, 0, iv, 0, 16);
|
||||||
System.arraycopy(key, 16, secretKey, 0, 32);
|
System.arraycopy(keyIvCombo, 16, secretKey, 0, 32);
|
||||||
this.aeskey = new SecretKeySpec(secretKey, "AES");
|
this.aeskey = secretKey;
|
||||||
this.iv = iv;
|
this.iv = iv;
|
||||||
} else if (key.length >= 32) {
|
} else if (keyIvCombo.length >= 32) {
|
||||||
byte[] secretKey = new byte[32];
|
byte[] secretKey = new byte[32];
|
||||||
System.arraycopy(key, 0, secretKey, 0, 32);
|
System.arraycopy(keyIvCombo, 0, secretKey, 0, 32);
|
||||||
this.aeskey = new SecretKeySpec(secretKey, "AES");
|
this.aeskey = secretKey;
|
||||||
} else if (key.length >= 16) {
|
} else if (keyIvCombo.length >= 16) {
|
||||||
byte[] secretKey = new byte[16];
|
byte[] secretKey = new byte[16];
|
||||||
System.arraycopy(key, 0, secretKey, 0, 16);
|
System.arraycopy(keyIvCombo, 0, secretKey, 0, 16);
|
||||||
this.aeskey = new SecretKeySpec(secretKey, "AES");
|
this.aeskey = secretKey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Key getKey() {
|
public void setKey(byte[] key) {
|
||||||
|
this.aeskey = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIv(byte[] iv) {
|
||||||
|
this.iv = iv;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getKey() {
|
||||||
return this.aeskey;
|
return this.aeskey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public InputStream createInputStream() {
|
public byte[] getIv() {
|
||||||
if (this.getKey() == null) {
|
return this.iv;
|
||||||
try {
|
|
||||||
return new FileInputStream(this);
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
IvParameterSpec ips = new IvParameterSpec(iv);
|
|
||||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, this.getKey(), ips);
|
|
||||||
Log.d(Config.LOGTAG, "opening encrypted input stream");
|
|
||||||
return new CipherInputStream(new FileInputStream(this), cipher);
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
Log.d(Config.LOGTAG, "no such algo: " + e.getMessage());
|
|
||||||
return null;
|
|
||||||
} catch (NoSuchPaddingException e) {
|
|
||||||
Log.d(Config.LOGTAG, "no such padding: " + e.getMessage());
|
|
||||||
return null;
|
|
||||||
} catch (InvalidKeyException e) {
|
|
||||||
Log.d(Config.LOGTAG, "invalid key: " + e.getMessage());
|
|
||||||
return null;
|
|
||||||
} catch (InvalidAlgorithmParameterException e) {
|
|
||||||
Log.d(Config.LOGTAG, "invavid iv:" + e.getMessage());
|
|
||||||
return null;
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public OutputStream createOutputStream() {
|
|
||||||
if (this.getKey() == null) {
|
|
||||||
try {
|
|
||||||
return new FileOutputStream(this);
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
IvParameterSpec ips = new IvParameterSpec(this.iv);
|
|
||||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
|
||||||
cipher.init(Cipher.DECRYPT_MODE, this.getKey(), ips);
|
|
||||||
Log.d(Config.LOGTAG, "opening encrypted output stream");
|
|
||||||
return new CipherOutputStream(new FileOutputStream(this),
|
|
||||||
cipher);
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
Log.d(Config.LOGTAG, "no such algo: " + e.getMessage());
|
|
||||||
return null;
|
|
||||||
} catch (NoSuchPaddingException e) {
|
|
||||||
Log.d(Config.LOGTAG, "no such padding: " + e.getMessage());
|
|
||||||
return null;
|
|
||||||
} catch (InvalidKeyException e) {
|
|
||||||
Log.d(Config.LOGTAG, "invalid key: " + e.getMessage());
|
|
||||||
return null;
|
|
||||||
} catch (InvalidAlgorithmParameterException e) {
|
|
||||||
Log.d(Config.LOGTAG, "invavid iv:" + e.getMessage());
|
|
||||||
return null;
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +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.crypto.axolotl.XmppAxolotlSession;
|
||||||
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;
|
||||||
|
@ -51,6 +51,7 @@ public class Message extends AbstractEntity {
|
||||||
public static final String ENCRYPTION = "encryption";
|
public static final String ENCRYPTION = "encryption";
|
||||||
public static final String STATUS = "status";
|
public static final String STATUS = "status";
|
||||||
public static final String TYPE = "type";
|
public static final String TYPE = "type";
|
||||||
|
public static final String CARBON = "carbon";
|
||||||
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";
|
||||||
|
@ -68,6 +69,7 @@ public class Message extends AbstractEntity {
|
||||||
protected int encryption;
|
protected int encryption;
|
||||||
protected int status;
|
protected int status;
|
||||||
protected int type;
|
protected int type;
|
||||||
|
protected boolean carbon = false;
|
||||||
protected String relativeFilePath;
|
protected String relativeFilePath;
|
||||||
protected boolean read = true;
|
protected boolean read = true;
|
||||||
protected String remoteMsgId = null;
|
protected String remoteMsgId = null;
|
||||||
|
@ -85,8 +87,11 @@ public class Message extends AbstractEntity {
|
||||||
public Message(Conversation conversation, String body, int encryption) {
|
public Message(Conversation conversation, String body, int encryption) {
|
||||||
this(conversation, body, encryption, STATUS_UNSEND);
|
this(conversation, body, encryption, STATUS_UNSEND);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Message(Conversation conversation, String body, int encryption, int status) {
|
public Message(Conversation conversation, String body, int encryption, int status) {
|
||||||
|
this(conversation, body, encryption, status, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Message(Conversation conversation, String body, int encryption, int status, boolean carbon) {
|
||||||
this(java.util.UUID.randomUUID().toString(),
|
this(java.util.UUID.randomUUID().toString(),
|
||||||
conversation.getUuid(),
|
conversation.getUuid(),
|
||||||
conversation.getJid() == null ? null : conversation.getJid().toBareJid(),
|
conversation.getJid() == null ? null : conversation.getJid().toBareJid(),
|
||||||
|
@ -96,6 +101,7 @@ public class Message extends AbstractEntity {
|
||||||
encryption,
|
encryption,
|
||||||
status,
|
status,
|
||||||
TYPE_TEXT,
|
TYPE_TEXT,
|
||||||
|
false,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
|
@ -105,8 +111,9 @@ 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 boolean carbon,
|
||||||
final String relativeFilePath, final String serverMsgId, final String fingerprint) {
|
final String remoteMsgId, 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;
|
||||||
|
@ -116,6 +123,7 @@ public class Message extends AbstractEntity {
|
||||||
this.encryption = encryption;
|
this.encryption = encryption;
|
||||||
this.status = status;
|
this.status = status;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
|
this.carbon = carbon;
|
||||||
this.remoteMsgId = remoteMsgId;
|
this.remoteMsgId = remoteMsgId;
|
||||||
this.relativeFilePath = relativeFilePath;
|
this.relativeFilePath = relativeFilePath;
|
||||||
this.serverMsgId = serverMsgId;
|
this.serverMsgId = serverMsgId;
|
||||||
|
@ -154,6 +162,7 @@ public class Message extends AbstractEntity {
|
||||||
cursor.getInt(cursor.getColumnIndex(ENCRYPTION)),
|
cursor.getInt(cursor.getColumnIndex(ENCRYPTION)),
|
||||||
cursor.getInt(cursor.getColumnIndex(STATUS)),
|
cursor.getInt(cursor.getColumnIndex(STATUS)),
|
||||||
cursor.getInt(cursor.getColumnIndex(TYPE)),
|
cursor.getInt(cursor.getColumnIndex(TYPE)),
|
||||||
|
cursor.getInt(cursor.getColumnIndex(CARBON))>0,
|
||||||
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)),
|
||||||
|
@ -188,6 +197,7 @@ public class Message extends AbstractEntity {
|
||||||
values.put(ENCRYPTION, encryption);
|
values.put(ENCRYPTION, encryption);
|
||||||
values.put(STATUS, status);
|
values.put(STATUS, status);
|
||||||
values.put(TYPE, type);
|
values.put(TYPE, type);
|
||||||
|
values.put(CARBON, carbon ? 1 : 0);
|
||||||
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);
|
||||||
|
@ -312,6 +322,14 @@ public class Message extends AbstractEntity {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isCarbon() {
|
||||||
|
return carbon;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCarbon(boolean carbon) {
|
||||||
|
this.carbon = carbon;
|
||||||
|
}
|
||||||
|
|
||||||
public void setTrueCounterpart(Jid trueCounterpart) {
|
public void setTrueCounterpart(Jid trueCounterpart) {
|
||||||
this.trueCounterpart = trueCounterpart;
|
this.trueCounterpart = trueCounterpart;
|
||||||
}
|
}
|
||||||
|
@ -416,11 +434,14 @@ public class Message extends AbstractEntity {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMergedBody() {
|
public String getMergedBody() {
|
||||||
final Message next = this.next();
|
StringBuilder body = new StringBuilder(this.body.trim());
|
||||||
if (this.mergeable(next)) {
|
Message current = this;
|
||||||
return getBody().trim() + MERGE_SEPARATOR + next.getMergedBody();
|
while(current.mergeable(current.next())) {
|
||||||
|
current = current.next();
|
||||||
|
body.append(MERGE_SEPARATOR);
|
||||||
|
body.append(current.getBody().trim());
|
||||||
}
|
}
|
||||||
return getBody().trim();
|
return body.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasMeCommand() {
|
public boolean hasMeCommand() {
|
||||||
|
@ -428,20 +449,23 @@ public class Message extends AbstractEntity {
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getMergedStatus() {
|
public int getMergedStatus() {
|
||||||
final Message next = this.next();
|
int status = this.status;
|
||||||
if (this.mergeable(next)) {
|
Message current = this;
|
||||||
return next.getStatus();
|
while(current.mergeable(current.next())) {
|
||||||
|
current = current.next();
|
||||||
|
status = current.status;
|
||||||
}
|
}
|
||||||
return getStatus();
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getMergedTimeSent() {
|
public long getMergedTimeSent() {
|
||||||
Message next = this.next();
|
long time = this.timeSent;
|
||||||
if (this.mergeable(next)) {
|
Message current = this;
|
||||||
return next.getMergedTimeSent();
|
while(current.mergeable(current.next())) {
|
||||||
} else {
|
current = current.next();
|
||||||
return getTimeSent();
|
time = current.timeSent;
|
||||||
}
|
}
|
||||||
|
return time;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean wasMergedIntoPrevious() {
|
public boolean wasMergedIntoPrevious() {
|
||||||
|
@ -683,6 +707,37 @@ public class Message extends AbstractEntity {
|
||||||
|
|
||||||
public boolean isTrusted() {
|
public boolean isTrusted() {
|
||||||
return conversation.getAccount().getAxolotlService().getFingerprintTrust(axolotlFingerprint)
|
return conversation.getAccount().getAxolotlService().getFingerprintTrust(axolotlFingerprint)
|
||||||
== AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED;
|
== XmppAxolotlSession.Trust.TRUSTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getPreviousEncryption() {
|
||||||
|
for (Message iterator = this.prev(); iterator != null; iterator = iterator.prev()){
|
||||||
|
if( iterator.isCarbon() || iterator.getStatus() == STATUS_RECEIVED ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return iterator.getEncryption();
|
||||||
|
}
|
||||||
|
return ENCRYPTION_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getNextEncryption() {
|
||||||
|
for (Message iterator = this.next(); iterator != null; iterator = iterator.next()){
|
||||||
|
if( iterator.isCarbon() || iterator.getStatus() == STATUS_RECEIVED ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return iterator.getEncryption();
|
||||||
|
}
|
||||||
|
return conversation.getNextEncryption();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isValidInSession() {
|
||||||
|
int pastEncryption = this.getPreviousEncryption();
|
||||||
|
int futureEncryption = this.getNextEncryption();
|
||||||
|
|
||||||
|
boolean inUnencryptedSession = pastEncryption == ENCRYPTION_NONE
|
||||||
|
|| futureEncryption == ENCRYPTION_NONE
|
||||||
|
|| pastEncryption != futureEncryption;
|
||||||
|
|
||||||
|
return inUnencryptedSession || this.getEncryption() == pastEncryption;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -254,12 +254,15 @@ public class IqGenerator extends AbstractGenerator {
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket requestHttpUploadSlot(Jid host, DownloadableFile file) {
|
public IqPacket requestHttpUploadSlot(Jid host, DownloadableFile file, String mime) {
|
||||||
IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
|
IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
|
||||||
packet.setTo(host);
|
packet.setTo(host);
|
||||||
Element request = packet.addChild("request",Xmlns.HTTP_UPLOAD);
|
Element request = packet.addChild("request",Xmlns.HTTP_UPLOAD);
|
||||||
request.addChild("filename").setContent(file.getName());
|
request.addChild("filename").setContent(file.getName());
|
||||||
request.addChild("size").setContent(String.valueOf(file.getExpectedSize()));
|
request.addChild("size").setContent(String.valueOf(file.getExpectedSize()));
|
||||||
|
if (mime != null) {
|
||||||
|
request.addChild("content-type", mime);
|
||||||
|
}
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,7 +63,7 @@ public class MessageGenerator extends AbstractGenerator {
|
||||||
if (axolotlMessage == null) {
|
if (axolotlMessage == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
packet.setAxolotlMessage(axolotlMessage.toXml());
|
packet.setAxolotlMessage(axolotlMessage.toElement());
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ package eu.siacs.conversations.http;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.SystemClock;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
|
@ -21,6 +20,8 @@ import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.entities.DownloadableFile;
|
import eu.siacs.conversations.entities.DownloadableFile;
|
||||||
import eu.siacs.conversations.entities.Message;
|
import eu.siacs.conversations.entities.Message;
|
||||||
import eu.siacs.conversations.entities.Transferable;
|
import eu.siacs.conversations.entities.Transferable;
|
||||||
|
import eu.siacs.conversations.persistance.FileBackend;
|
||||||
|
import eu.siacs.conversations.services.AbstractConnectionManager;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
import eu.siacs.conversations.utils.CryptoHelper;
|
import eu.siacs.conversations.utils.CryptoHelper;
|
||||||
|
|
||||||
|
@ -83,7 +84,7 @@ public class HttpDownloadConnection implements Transferable {
|
||||||
this.file = mXmppConnectionService.getFileBackend().getFile(message, false);
|
this.file = mXmppConnectionService.getFileBackend().getFile(message, false);
|
||||||
String reference = mUrl.getRef();
|
String reference = mUrl.getRef();
|
||||||
if (reference != null && reference.length() == 96) {
|
if (reference != null && reference.length() == 96) {
|
||||||
this.file.setKey(CryptoHelper.hexToBytes(reference));
|
this.file.setKeyAndIv(CryptoHelper.hexToBytes(reference));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((this.message.getEncryption() == Message.ENCRYPTION_OTR
|
if ((this.message.getEncryption() == Message.ENCRYPTION_OTR
|
||||||
|
@ -187,6 +188,8 @@ public class HttpDownloadConnection implements Transferable {
|
||||||
|
|
||||||
private boolean interactive = false;
|
private boolean interactive = false;
|
||||||
|
|
||||||
|
private OutputStream os;
|
||||||
|
|
||||||
public FileDownloader(boolean interactive) {
|
public FileDownloader(boolean interactive) {
|
||||||
this.interactive = interactive;
|
this.interactive = interactive;
|
||||||
}
|
}
|
||||||
|
@ -199,14 +202,16 @@ public class HttpDownloadConnection implements Transferable {
|
||||||
updateImageBounds();
|
updateImageBounds();
|
||||||
finish();
|
finish();
|
||||||
} catch (SSLHandshakeException e) {
|
} catch (SSLHandshakeException e) {
|
||||||
|
FileBackend.close(os);
|
||||||
changeStatus(STATUS_OFFER);
|
changeStatus(STATUS_OFFER);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
FileBackend.close(os);
|
||||||
mXmppConnectionService.showErrorToastInUi(R.string.file_not_found_on_remote_host);
|
mXmppConnectionService.showErrorToastInUi(R.string.file_not_found_on_remote_host);
|
||||||
cancel();
|
cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void download() throws SSLHandshakeException, IOException {
|
private void download() throws IOException {
|
||||||
HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
|
HttpURLConnection connection = (HttpURLConnection) mUrl.openConnection();
|
||||||
if (connection instanceof HttpsURLConnection) {
|
if (connection instanceof HttpsURLConnection) {
|
||||||
mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive);
|
mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive);
|
||||||
|
@ -215,10 +220,7 @@ public class HttpDownloadConnection implements Transferable {
|
||||||
BufferedInputStream is = new BufferedInputStream(connection.getInputStream());
|
BufferedInputStream is = new BufferedInputStream(connection.getInputStream());
|
||||||
file.getParentFile().mkdirs();
|
file.getParentFile().mkdirs();
|
||||||
file.createNewFile();
|
file.createNewFile();
|
||||||
OutputStream os = file.createOutputStream();
|
os = AbstractConnectionManager.createOutputStream(file,true);
|
||||||
if (os == null) {
|
|
||||||
throw new IOException();
|
|
||||||
}
|
|
||||||
long transmitted = 0;
|
long transmitted = 0;
|
||||||
long expected = file.getExpectedSize();
|
long expected = file.getExpectedSize();
|
||||||
int count = -1;
|
int count = -1;
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
package eu.siacs.conversations.http;
|
package eu.siacs.conversations.http;
|
||||||
|
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
@ -18,6 +21,7 @@ import eu.siacs.conversations.entities.DownloadableFile;
|
||||||
import eu.siacs.conversations.entities.Message;
|
import eu.siacs.conversations.entities.Message;
|
||||||
import eu.siacs.conversations.entities.Transferable;
|
import eu.siacs.conversations.entities.Transferable;
|
||||||
import eu.siacs.conversations.persistance.FileBackend;
|
import eu.siacs.conversations.persistance.FileBackend;
|
||||||
|
import eu.siacs.conversations.services.AbstractConnectionManager;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
import eu.siacs.conversations.ui.UiCallback;
|
import eu.siacs.conversations.ui.UiCallback;
|
||||||
import eu.siacs.conversations.utils.CryptoHelper;
|
import eu.siacs.conversations.utils.CryptoHelper;
|
||||||
|
@ -37,13 +41,15 @@ public class HttpUploadConnection implements Transferable {
|
||||||
private Account account;
|
private Account account;
|
||||||
private DownloadableFile file;
|
private DownloadableFile file;
|
||||||
private Message message;
|
private Message message;
|
||||||
|
private String mime;
|
||||||
private URL mGetUrl;
|
private URL mGetUrl;
|
||||||
private URL mPutUrl;
|
private URL mPutUrl;
|
||||||
|
|
||||||
private byte[] key = null;
|
private byte[] key = null;
|
||||||
|
|
||||||
private long transmitted = 0;
|
private long transmitted = 0;
|
||||||
private long expected = 1;
|
|
||||||
|
private InputStream mFileInputStream;
|
||||||
|
|
||||||
public HttpUploadConnection(HttpConnectionManager httpConnectionManager) {
|
public HttpUploadConnection(HttpConnectionManager httpConnectionManager) {
|
||||||
this.mHttpConnectionManager = httpConnectionManager;
|
this.mHttpConnectionManager = httpConnectionManager;
|
||||||
|
@ -67,7 +73,7 @@ public class HttpUploadConnection implements Transferable {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getProgress() {
|
public int getProgress() {
|
||||||
return (int) ((((double) transmitted) / expected) * 100);
|
return (int) ((((double) transmitted) / file.getExpectedSize()) * 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -78,28 +84,30 @@ public class HttpUploadConnection implements Transferable {
|
||||||
private void fail() {
|
private void fail() {
|
||||||
mHttpConnectionManager.finishUploadConnection(this);
|
mHttpConnectionManager.finishUploadConnection(this);
|
||||||
message.setTransferable(null);
|
message.setTransferable(null);
|
||||||
mXmppConnectionService.markMessage(message,Message.STATUS_SEND_FAILED);
|
mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED);
|
||||||
|
FileBackend.close(mFileInputStream);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init(Message message, boolean delay) {
|
public void init(Message message, boolean delay) {
|
||||||
this.message = message;
|
this.message = message;
|
||||||
message.setTransferable(this);
|
message.setTransferable(this);
|
||||||
mXmppConnectionService.markMessage(message,Message.STATUS_UNSEND);
|
mXmppConnectionService.markMessage(message, Message.STATUS_UNSEND);
|
||||||
this.account = message.getConversation().getAccount();
|
this.account = message.getConversation().getAccount();
|
||||||
this.file = mXmppConnectionService.getFileBackend().getFile(message, false);
|
this.file = mXmppConnectionService.getFileBackend().getFile(message, false);
|
||||||
this.file.setExpectedSize(this.file.getSize());
|
this.mime = this.file.getMimeType();
|
||||||
this.delayed = delay;
|
this.delayed = delay;
|
||||||
|
|
||||||
if (Config.ENCRYPT_ON_HTTP_UPLOADED
|
if (Config.ENCRYPT_ON_HTTP_UPLOADED
|
||||||
|| message.getEncryption() == Message.ENCRYPTION_AXOLOTL
|
|| message.getEncryption() == Message.ENCRYPTION_AXOLOTL
|
||||||
|| message.getEncryption() == Message.ENCRYPTION_OTR) {
|
|| message.getEncryption() == Message.ENCRYPTION_OTR) {
|
||||||
this.key = new byte[48];
|
this.key = new byte[48];
|
||||||
mXmppConnectionService.getRNG().nextBytes(this.key);
|
mXmppConnectionService.getRNG().nextBytes(this.key);
|
||||||
this.file.setKey(this.key);
|
this.file.setKeyAndIv(this.key);
|
||||||
}
|
}
|
||||||
|
Pair<InputStream,Integer> pair = AbstractConnectionManager.createInputStream(file,true);
|
||||||
|
this.file.setExpectedSize(pair.second);
|
||||||
|
this.mFileInputStream = pair.first;
|
||||||
Jid host = account.getXmppConnection().findDiscoItemByFeature(Xmlns.HTTP_UPLOAD);
|
Jid host = account.getXmppConnection().findDiscoItemByFeature(Xmlns.HTTP_UPLOAD);
|
||||||
IqPacket request = mXmppConnectionService.getIqGenerator().requestHttpUploadSlot(host,file);
|
IqPacket request = mXmppConnectionService.getIqGenerator().requestHttpUploadSlot(host,file,mime);
|
||||||
mXmppConnectionService.sendIqPacket(account, request, new OnIqPacketReceived() {
|
mXmppConnectionService.sendIqPacket(account, request, new OnIqPacketReceived() {
|
||||||
@Override
|
@Override
|
||||||
public void onIqPacketReceived(Account account, IqPacket packet) {
|
public void onIqPacketReceived(Account account, IqPacket packet) {
|
||||||
|
@ -134,7 +142,6 @@ public class HttpUploadConnection implements Transferable {
|
||||||
|
|
||||||
private void upload() {
|
private void upload() {
|
||||||
OutputStream os = null;
|
OutputStream os = null;
|
||||||
InputStream is = null;
|
|
||||||
HttpURLConnection connection = null;
|
HttpURLConnection connection = null;
|
||||||
try {
|
try {
|
||||||
Log.d(Config.LOGTAG, "uploading to " + mPutUrl.toString());
|
Log.d(Config.LOGTAG, "uploading to " + mPutUrl.toString());
|
||||||
|
@ -144,30 +151,31 @@ public class HttpUploadConnection implements Transferable {
|
||||||
}
|
}
|
||||||
connection.setRequestMethod("PUT");
|
connection.setRequestMethod("PUT");
|
||||||
connection.setFixedLengthStreamingMode((int) file.getExpectedSize());
|
connection.setFixedLengthStreamingMode((int) file.getExpectedSize());
|
||||||
|
connection.setRequestProperty("Content-Type", mime == null ? "application/octet-stream" : mime);
|
||||||
connection.setDoOutput(true);
|
connection.setDoOutput(true);
|
||||||
connection.connect();
|
connection.connect();
|
||||||
os = connection.getOutputStream();
|
os = connection.getOutputStream();
|
||||||
is = file.createInputStream();
|
|
||||||
transmitted = 0;
|
transmitted = 0;
|
||||||
expected = file.getExpectedSize();
|
|
||||||
int count = -1;
|
int count = -1;
|
||||||
byte[] buffer = new byte[4096];
|
byte[] buffer = new byte[4096];
|
||||||
while (((count = is.read(buffer)) != -1) && !canceled) {
|
while (((count = mFileInputStream.read(buffer)) != -1) && !canceled) {
|
||||||
transmitted += count;
|
transmitted += count;
|
||||||
os.write(buffer, 0, count);
|
os.write(buffer, 0, count);
|
||||||
mXmppConnectionService.updateConversationUi();
|
mXmppConnectionService.updateConversationUi();
|
||||||
}
|
}
|
||||||
os.flush();
|
os.flush();
|
||||||
os.close();
|
os.close();
|
||||||
is.close();
|
mFileInputStream.close();
|
||||||
int code = connection.getResponseCode();
|
int code = connection.getResponseCode();
|
||||||
if (code == 200 || code == 201) {
|
if (code == 200 || code == 201) {
|
||||||
Log.d(Config.LOGTAG, "finished uploading file");
|
Log.d(Config.LOGTAG, "finished uploading file");
|
||||||
Message.FileParams params = message.getFileParams();
|
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
mGetUrl = new URL(mGetUrl.toString() + "#" + CryptoHelper.bytesToHex(key));
|
mGetUrl = new URL(mGetUrl.toString() + "#" + CryptoHelper.bytesToHex(key));
|
||||||
}
|
}
|
||||||
mXmppConnectionService.getFileBackend().updateFileParams(message, mGetUrl);
|
mXmppConnectionService.getFileBackend().updateFileParams(message, mGetUrl);
|
||||||
|
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
|
||||||
|
intent.setData(Uri.fromFile(file));
|
||||||
|
mXmppConnectionService.sendBroadcast(intent);
|
||||||
message.setTransferable(null);
|
message.setTransferable(null);
|
||||||
message.setCounterpart(message.getConversation().getJid().toBareJid());
|
message.setCounterpart(message.getConversation().getJid().toBareJid());
|
||||||
if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
|
if (message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
|
||||||
|
@ -188,16 +196,17 @@ public class HttpUploadConnection implements Transferable {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
mXmppConnectionService.resendMessage(message,delayed);
|
mXmppConnectionService.resendMessage(message, delayed);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fail();
|
fail();
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.d(Config.LOGTAG, e.getMessage());
|
e.printStackTrace();
|
||||||
|
Log.d(Config.LOGTAG,"http upload failed "+e.getMessage());
|
||||||
fail();
|
fail();
|
||||||
} finally {
|
} finally {
|
||||||
FileBackend.close(is);
|
FileBackend.close(mFileInputStream);
|
||||||
FileBackend.close(os);
|
FileBackend.close(os);
|
||||||
if (connection != null) {
|
if (connection != null) {
|
||||||
connection.disconnect();
|
connection.disconnect();
|
||||||
|
|
|
@ -73,11 +73,9 @@ public class MessageParser extends AbstractParser implements
|
||||||
body = otrSession.transformReceiving(body);
|
body = otrSession.transformReceiving(body);
|
||||||
SessionStatus status = otrSession.getSessionStatus();
|
SessionStatus status = otrSession.getSessionStatus();
|
||||||
if (body == null && status == SessionStatus.ENCRYPTED) {
|
if (body == null && status == SessionStatus.ENCRYPTED) {
|
||||||
conversation.setNextEncryption(Message.ENCRYPTION_OTR);
|
|
||||||
mXmppConnectionService.onOtrSessionEstablished(conversation);
|
mXmppConnectionService.onOtrSessionEstablished(conversation);
|
||||||
return null;
|
return null;
|
||||||
} else if (body == null && status == SessionStatus.FINISHED) {
|
} else if (body == null && status == SessionStatus.FINISHED) {
|
||||||
conversation.setNextEncryption(Message.ENCRYPTION_NONE);
|
|
||||||
conversation.resetOtrSession();
|
conversation.resetOtrSession();
|
||||||
mXmppConnectionService.updateConversationUi();
|
mXmppConnectionService.updateConversationUi();
|
||||||
return null;
|
return null;
|
||||||
|
@ -101,8 +99,8 @@ public class MessageParser extends AbstractParser implements
|
||||||
private Message parseAxolotlChat(Element axolotlMessage, Jid from, String id, Conversation conversation, int status) {
|
private Message parseAxolotlChat(Element axolotlMessage, Jid from, String id, Conversation conversation, int status) {
|
||||||
Message finishedMessage = null;
|
Message finishedMessage = null;
|
||||||
AxolotlService service = conversation.getAccount().getAxolotlService();
|
AxolotlService service = conversation.getAccount().getAxolotlService();
|
||||||
XmppAxolotlMessage xmppAxolotlMessage = new XmppAxolotlMessage(from.toBareJid(), axolotlMessage);
|
XmppAxolotlMessage xmppAxolotlMessage = XmppAxolotlMessage.fromElement(axolotlMessage, from.toBareJid());
|
||||||
XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = service.processReceiving(xmppAxolotlMessage);
|
XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = service.processReceivingPayloadMessage(xmppAxolotlMessage);
|
||||||
if(plaintextMessage != null) {
|
if(plaintextMessage != null) {
|
||||||
finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, status);
|
finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, status);
|
||||||
finishedMessage.setAxolotlFingerprint(plaintextMessage.getFingerprint());
|
finishedMessage.setAxolotlFingerprint(plaintextMessage.getFingerprint());
|
||||||
|
@ -202,6 +200,13 @@ public class MessageParser extends AbstractParser implements
|
||||||
if (packet.getType() == MessagePacket.TYPE_ERROR) {
|
if (packet.getType() == MessagePacket.TYPE_ERROR) {
|
||||||
Jid from = packet.getFrom();
|
Jid from = packet.getFrom();
|
||||||
if (from != null) {
|
if (from != null) {
|
||||||
|
Element error = packet.findChild("error");
|
||||||
|
String text = error == null ? null : error.findChildContent("text");
|
||||||
|
if (text != null) {
|
||||||
|
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": sending message to "+ from+ " failed - " + text);
|
||||||
|
} else if (error != null) {
|
||||||
|
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": sending message to "+ from+ " failed - " + error);
|
||||||
|
}
|
||||||
Message message = mXmppConnectionService.markMessage(account,
|
Message message = mXmppConnectionService.markMessage(account,
|
||||||
from.toBareJid(),
|
from.toBareJid(),
|
||||||
packet.getId(),
|
packet.getId(),
|
||||||
|
@ -223,6 +228,7 @@ public class MessageParser extends AbstractParser implements
|
||||||
final MessagePacket packet;
|
final MessagePacket packet;
|
||||||
Long timestamp = null;
|
Long timestamp = null;
|
||||||
final boolean isForwarded;
|
final boolean isForwarded;
|
||||||
|
boolean isCarbon = false;
|
||||||
String serverMsgId = null;
|
String serverMsgId = null;
|
||||||
final Element fin = original.findChild("fin", "urn:xmpp:mam:0");
|
final Element fin = original.findChild("fin", "urn:xmpp:mam:0");
|
||||||
if (fin != null) {
|
if (fin != null) {
|
||||||
|
@ -253,7 +259,8 @@ public class MessageParser extends AbstractParser implements
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
timestamp = f != null ? f.second : null;
|
timestamp = f != null ? f.second : null;
|
||||||
isForwarded = f != null;
|
isCarbon = f != null;
|
||||||
|
isForwarded = isCarbon;
|
||||||
} else {
|
} else {
|
||||||
packet = original;
|
packet = original;
|
||||||
isForwarded = false;
|
isForwarded = false;
|
||||||
|
@ -265,7 +272,7 @@ public class MessageParser extends AbstractParser implements
|
||||||
final String body = packet.getBody();
|
final String body = packet.getBody();
|
||||||
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 String pgpEncrypted = packet.findChildContent("x", "jabber:x:encrypted");
|
||||||
final Element axolotlEncrypted = packet.findChild("axolotl_message", AxolotlService.PEP_PREFIX);
|
final Element axolotlEncrypted = packet.findChild(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX);
|
||||||
int status;
|
int status;
|
||||||
final Jid counterpart;
|
final Jid counterpart;
|
||||||
final Jid to = packet.getTo();
|
final Jid to = packet.getTo();
|
||||||
|
@ -339,6 +346,7 @@ public class MessageParser extends AbstractParser implements
|
||||||
message.setCounterpart(counterpart);
|
message.setCounterpart(counterpart);
|
||||||
message.setRemoteMsgId(remoteMsgId);
|
message.setRemoteMsgId(remoteMsgId);
|
||||||
message.setServerMsgId(serverMsgId);
|
message.setServerMsgId(serverMsgId);
|
||||||
|
message.setCarbon(isCarbon);
|
||||||
message.setTime(timestamp);
|
message.setTime(timestamp);
|
||||||
message.markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0");
|
message.markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0");
|
||||||
if (conversation.getMode() == Conversation.MODE_MULTI) {
|
if (conversation.getMode() == Conversation.MODE_MULTI) {
|
||||||
|
|
|
@ -27,6 +27,8 @@ 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.crypto.axolotl.AxolotlService;
|
||||||
|
import eu.siacs.conversations.crypto.axolotl.SQLiteAxolotlStore;
|
||||||
|
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.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;
|
||||||
|
@ -40,7 +42,7 @@ 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 = 15;
|
private static final int DATABASE_VERSION = 16;
|
||||||
|
|
||||||
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, "
|
||||||
|
@ -55,56 +57,56 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
+ Contact.JID + ") ON CONFLICT REPLACE);";
|
+ Contact.JID + ") ON CONFLICT REPLACE);";
|
||||||
|
|
||||||
private static String CREATE_PREKEYS_STATEMENT = "CREATE TABLE "
|
private static String CREATE_PREKEYS_STATEMENT = "CREATE TABLE "
|
||||||
+ AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME + "("
|
+ SQLiteAxolotlStore.PREKEY_TABLENAME + "("
|
||||||
+ AxolotlService.SQLiteAxolotlStore.ACCOUNT + " TEXT, "
|
+ SQLiteAxolotlStore.ACCOUNT + " TEXT, "
|
||||||
+ AxolotlService.SQLiteAxolotlStore.ID + " INTEGER, "
|
+ SQLiteAxolotlStore.ID + " INTEGER, "
|
||||||
+ AxolotlService.SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
|
+ SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
|
||||||
+ AxolotlService.SQLiteAxolotlStore.ACCOUNT
|
+ SQLiteAxolotlStore.ACCOUNT
|
||||||
+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
|
+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
|
||||||
+ "UNIQUE( " + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ", "
|
+ "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
|
||||||
+ AxolotlService.SQLiteAxolotlStore.ID
|
+ SQLiteAxolotlStore.ID
|
||||||
+ ") ON CONFLICT REPLACE"
|
+ ") ON CONFLICT REPLACE"
|
||||||
+");";
|
+");";
|
||||||
|
|
||||||
private static String CREATE_SIGNED_PREKEYS_STATEMENT = "CREATE TABLE "
|
private static String CREATE_SIGNED_PREKEYS_STATEMENT = "CREATE TABLE "
|
||||||
+ AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME + "("
|
+ SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME + "("
|
||||||
+ AxolotlService.SQLiteAxolotlStore.ACCOUNT + " TEXT, "
|
+ SQLiteAxolotlStore.ACCOUNT + " TEXT, "
|
||||||
+ AxolotlService.SQLiteAxolotlStore.ID + " INTEGER, "
|
+ SQLiteAxolotlStore.ID + " INTEGER, "
|
||||||
+ AxolotlService.SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
|
+ SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
|
||||||
+ AxolotlService.SQLiteAxolotlStore.ACCOUNT
|
+ SQLiteAxolotlStore.ACCOUNT
|
||||||
+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
|
+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
|
||||||
+ "UNIQUE( " + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ", "
|
+ "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
|
||||||
+ AxolotlService.SQLiteAxolotlStore.ID
|
+ SQLiteAxolotlStore.ID
|
||||||
+ ") ON CONFLICT REPLACE"+
|
+ ") ON CONFLICT REPLACE"+
|
||||||
");";
|
");";
|
||||||
|
|
||||||
private static String CREATE_SESSIONS_STATEMENT = "CREATE TABLE "
|
private static String CREATE_SESSIONS_STATEMENT = "CREATE TABLE "
|
||||||
+ AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME + "("
|
+ SQLiteAxolotlStore.SESSION_TABLENAME + "("
|
||||||
+ AxolotlService.SQLiteAxolotlStore.ACCOUNT + " TEXT, "
|
+ SQLiteAxolotlStore.ACCOUNT + " TEXT, "
|
||||||
+ AxolotlService.SQLiteAxolotlStore.NAME + " TEXT, "
|
+ SQLiteAxolotlStore.NAME + " TEXT, "
|
||||||
+ AxolotlService.SQLiteAxolotlStore.DEVICE_ID + " INTEGER, "
|
+ SQLiteAxolotlStore.DEVICE_ID + " INTEGER, "
|
||||||
+ AxolotlService.SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
|
+ SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
|
||||||
+ AxolotlService.SQLiteAxolotlStore.ACCOUNT
|
+ SQLiteAxolotlStore.ACCOUNT
|
||||||
+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
|
+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
|
||||||
+ "UNIQUE( " + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ", "
|
+ "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
|
||||||
+ AxolotlService.SQLiteAxolotlStore.NAME + ", "
|
+ SQLiteAxolotlStore.NAME + ", "
|
||||||
+ AxolotlService.SQLiteAxolotlStore.DEVICE_ID
|
+ SQLiteAxolotlStore.DEVICE_ID
|
||||||
+ ") ON CONFLICT REPLACE"
|
+ ") ON CONFLICT REPLACE"
|
||||||
+");";
|
+");";
|
||||||
|
|
||||||
private static String CREATE_IDENTITIES_STATEMENT = "CREATE TABLE "
|
private static String CREATE_IDENTITIES_STATEMENT = "CREATE TABLE "
|
||||||
+ AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME + "("
|
+ SQLiteAxolotlStore.IDENTITIES_TABLENAME + "("
|
||||||
+ AxolotlService.SQLiteAxolotlStore.ACCOUNT + " TEXT, "
|
+ SQLiteAxolotlStore.ACCOUNT + " TEXT, "
|
||||||
+ AxolotlService.SQLiteAxolotlStore.NAME + " TEXT, "
|
+ SQLiteAxolotlStore.NAME + " TEXT, "
|
||||||
+ AxolotlService.SQLiteAxolotlStore.OWN + " INTEGER, "
|
+ SQLiteAxolotlStore.OWN + " INTEGER, "
|
||||||
+ AxolotlService.SQLiteAxolotlStore.FINGERPRINT + " TEXT, "
|
+ SQLiteAxolotlStore.FINGERPRINT + " TEXT, "
|
||||||
+ AxolotlService.SQLiteAxolotlStore.TRUSTED + " INTEGER, "
|
+ SQLiteAxolotlStore.TRUSTED + " INTEGER, "
|
||||||
+ AxolotlService.SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
|
+ SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
|
||||||
+ AxolotlService.SQLiteAxolotlStore.ACCOUNT
|
+ SQLiteAxolotlStore.ACCOUNT
|
||||||
+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
|
+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
|
||||||
+ "UNIQUE( " + AxolotlService.SQLiteAxolotlStore.ACCOUNT + ", "
|
+ "UNIQUE( " + SQLiteAxolotlStore.ACCOUNT + ", "
|
||||||
+ AxolotlService.SQLiteAxolotlStore.NAME + ", "
|
+ SQLiteAxolotlStore.NAME + ", "
|
||||||
+ AxolotlService.SQLiteAxolotlStore.FINGERPRINT
|
+ SQLiteAxolotlStore.FINGERPRINT
|
||||||
+ ") ON CONFLICT IGNORE"
|
+ ") ON CONFLICT IGNORE"
|
||||||
+");";
|
+");";
|
||||||
|
|
||||||
|
@ -139,6 +141,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
+ Message.RELATIVE_FILE_PATH + " TEXT, "
|
+ Message.RELATIVE_FILE_PATH + " TEXT, "
|
||||||
+ Message.SERVER_MSG_ID + " TEXT, "
|
+ Message.SERVER_MSG_ID + " TEXT, "
|
||||||
+ Message.FINGERPRINT + " TEXT, "
|
+ Message.FINGERPRINT + " TEXT, "
|
||||||
|
+ Message.CARBON + " INTEGER, "
|
||||||
+ 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
|
||||||
|
@ -294,6 +297,10 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
|
db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
|
||||||
+ Message.FINGERPRINT + " TEXT");
|
+ Message.FINGERPRINT + " TEXT");
|
||||||
}
|
}
|
||||||
|
if (oldVersion < 16 && newVersion >= 16) {
|
||||||
|
db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "
|
||||||
|
+ Message.CARBON + " INTEGER");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized DatabaseBackend getInstance(Context context) {
|
public static synchronized DatabaseBackend getInstance(Context context) {
|
||||||
|
@ -567,11 +574,11 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
String[] selectionArgs = {account.getUuid(),
|
String[] selectionArgs = {account.getUuid(),
|
||||||
contact.getName(),
|
contact.getName(),
|
||||||
Integer.toString(contact.getDeviceId())};
|
Integer.toString(contact.getDeviceId())};
|
||||||
Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME,
|
Cursor cursor = db.query(SQLiteAxolotlStore.SESSION_TABLENAME,
|
||||||
columns,
|
columns,
|
||||||
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND "
|
SQLiteAxolotlStore.ACCOUNT + " = ? AND "
|
||||||
+ AxolotlService.SQLiteAxolotlStore.NAME + " = ? AND "
|
+ SQLiteAxolotlStore.NAME + " = ? AND "
|
||||||
+ AxolotlService.SQLiteAxolotlStore.DEVICE_ID + " = ? ",
|
+ SQLiteAxolotlStore.DEVICE_ID + " = ? ",
|
||||||
selectionArgs,
|
selectionArgs,
|
||||||
null, null, null);
|
null, null, null);
|
||||||
|
|
||||||
|
@ -584,7 +591,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
if(cursor.getCount() != 0) {
|
if(cursor.getCount() != 0) {
|
||||||
cursor.moveToFirst();
|
cursor.moveToFirst();
|
||||||
try {
|
try {
|
||||||
session = new SessionRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT));
|
session = new SessionRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)),Base64.DEFAULT));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
cursor.close();
|
cursor.close();
|
||||||
throw new AssertionError(e);
|
throw new AssertionError(e);
|
||||||
|
@ -597,19 +604,19 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
public List<Integer> getSubDeviceSessions(Account account, AxolotlAddress contact) {
|
public List<Integer> getSubDeviceSessions(Account account, AxolotlAddress contact) {
|
||||||
List<Integer> devices = new ArrayList<>();
|
List<Integer> devices = new ArrayList<>();
|
||||||
final SQLiteDatabase db = this.getReadableDatabase();
|
final SQLiteDatabase db = this.getReadableDatabase();
|
||||||
String[] columns = {AxolotlService.SQLiteAxolotlStore.DEVICE_ID};
|
String[] columns = {SQLiteAxolotlStore.DEVICE_ID};
|
||||||
String[] selectionArgs = {account.getUuid(),
|
String[] selectionArgs = {account.getUuid(),
|
||||||
contact.getName()};
|
contact.getName()};
|
||||||
Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME,
|
Cursor cursor = db.query(SQLiteAxolotlStore.SESSION_TABLENAME,
|
||||||
columns,
|
columns,
|
||||||
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND "
|
SQLiteAxolotlStore.ACCOUNT + " = ? AND "
|
||||||
+ AxolotlService.SQLiteAxolotlStore.NAME + " = ?",
|
+ SQLiteAxolotlStore.NAME + " = ?",
|
||||||
selectionArgs,
|
selectionArgs,
|
||||||
null, null, null);
|
null, null, null);
|
||||||
|
|
||||||
while(cursor.moveToNext()) {
|
while(cursor.moveToNext()) {
|
||||||
devices.add(cursor.getInt(
|
devices.add(cursor.getInt(
|
||||||
cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.DEVICE_ID)));
|
cursor.getColumnIndex(SQLiteAxolotlStore.DEVICE_ID)));
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor.close();
|
cursor.close();
|
||||||
|
@ -626,11 +633,11 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
public void storeSession(Account account, AxolotlAddress contact, SessionRecord session) {
|
public void storeSession(Account account, AxolotlAddress contact, SessionRecord session) {
|
||||||
SQLiteDatabase db = this.getWritableDatabase();
|
SQLiteDatabase db = this.getWritableDatabase();
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
values.put(AxolotlService.SQLiteAxolotlStore.NAME, contact.getName());
|
values.put(SQLiteAxolotlStore.NAME, contact.getName());
|
||||||
values.put(AxolotlService.SQLiteAxolotlStore.DEVICE_ID, contact.getDeviceId());
|
values.put(SQLiteAxolotlStore.DEVICE_ID, contact.getDeviceId());
|
||||||
values.put(AxolotlService.SQLiteAxolotlStore.KEY, Base64.encodeToString(session.serialize(),Base64.DEFAULT));
|
values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(session.serialize(),Base64.DEFAULT));
|
||||||
values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid());
|
values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
|
||||||
db.insert(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME, null, values);
|
db.insert(SQLiteAxolotlStore.SESSION_TABLENAME, null, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deleteSession(Account account, AxolotlAddress contact) {
|
public void deleteSession(Account account, AxolotlAddress contact) {
|
||||||
|
@ -638,30 +645,30 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
String[] args = {account.getUuid(),
|
String[] args = {account.getUuid(),
|
||||||
contact.getName(),
|
contact.getName(),
|
||||||
Integer.toString(contact.getDeviceId())};
|
Integer.toString(contact.getDeviceId())};
|
||||||
db.delete(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME,
|
db.delete(SQLiteAxolotlStore.SESSION_TABLENAME,
|
||||||
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND "
|
SQLiteAxolotlStore.ACCOUNT + " = ? AND "
|
||||||
+ AxolotlService.SQLiteAxolotlStore.NAME + " = ? AND "
|
+ SQLiteAxolotlStore.NAME + " = ? AND "
|
||||||
+ AxolotlService.SQLiteAxolotlStore.DEVICE_ID + " = ? ",
|
+ SQLiteAxolotlStore.DEVICE_ID + " = ? ",
|
||||||
args);
|
args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deleteAllSessions(Account account, AxolotlAddress contact) {
|
public void deleteAllSessions(Account account, AxolotlAddress contact) {
|
||||||
SQLiteDatabase db = this.getWritableDatabase();
|
SQLiteDatabase db = this.getWritableDatabase();
|
||||||
String[] args = {account.getUuid(), contact.getName()};
|
String[] args = {account.getUuid(), contact.getName()};
|
||||||
db.delete(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME,
|
db.delete(SQLiteAxolotlStore.SESSION_TABLENAME,
|
||||||
AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND "
|
SQLiteAxolotlStore.ACCOUNT + "=? AND "
|
||||||
+ AxolotlService.SQLiteAxolotlStore.NAME + " = ?",
|
+ SQLiteAxolotlStore.NAME + " = ?",
|
||||||
args);
|
args);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Cursor getCursorForPreKey(Account account, int preKeyId) {
|
private Cursor getCursorForPreKey(Account account, int preKeyId) {
|
||||||
SQLiteDatabase db = this.getReadableDatabase();
|
SQLiteDatabase db = this.getReadableDatabase();
|
||||||
String[] columns = {AxolotlService.SQLiteAxolotlStore.KEY};
|
String[] columns = {SQLiteAxolotlStore.KEY};
|
||||||
String[] selectionArgs = {account.getUuid(), Integer.toString(preKeyId)};
|
String[] selectionArgs = {account.getUuid(), Integer.toString(preKeyId)};
|
||||||
Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME,
|
Cursor cursor = db.query(SQLiteAxolotlStore.PREKEY_TABLENAME,
|
||||||
columns,
|
columns,
|
||||||
AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND "
|
SQLiteAxolotlStore.ACCOUNT + "=? AND "
|
||||||
+ AxolotlService.SQLiteAxolotlStore.ID + "=?",
|
+ SQLiteAxolotlStore.ID + "=?",
|
||||||
selectionArgs,
|
selectionArgs,
|
||||||
null, null, null);
|
null, null, null);
|
||||||
|
|
||||||
|
@ -674,7 +681,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
if(cursor.getCount() != 0) {
|
if(cursor.getCount() != 0) {
|
||||||
cursor.moveToFirst();
|
cursor.moveToFirst();
|
||||||
try {
|
try {
|
||||||
record = new PreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT));
|
record = new PreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)),Base64.DEFAULT));
|
||||||
} catch (IOException e ) {
|
} catch (IOException e ) {
|
||||||
throw new AssertionError(e);
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
|
@ -693,28 +700,28 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
public void storePreKey(Account account, PreKeyRecord record) {
|
public void storePreKey(Account account, PreKeyRecord record) {
|
||||||
SQLiteDatabase db = this.getWritableDatabase();
|
SQLiteDatabase db = this.getWritableDatabase();
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
values.put(AxolotlService.SQLiteAxolotlStore.ID, record.getId());
|
values.put(SQLiteAxolotlStore.ID, record.getId());
|
||||||
values.put(AxolotlService.SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(),Base64.DEFAULT));
|
values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(),Base64.DEFAULT));
|
||||||
values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid());
|
values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
|
||||||
db.insert(AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME, null, values);
|
db.insert(SQLiteAxolotlStore.PREKEY_TABLENAME, null, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deletePreKey(Account account, int preKeyId) {
|
public void deletePreKey(Account account, int preKeyId) {
|
||||||
SQLiteDatabase db = this.getWritableDatabase();
|
SQLiteDatabase db = this.getWritableDatabase();
|
||||||
String[] args = {account.getUuid(), Integer.toString(preKeyId)};
|
String[] args = {account.getUuid(), Integer.toString(preKeyId)};
|
||||||
db.delete(AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME,
|
db.delete(SQLiteAxolotlStore.PREKEY_TABLENAME,
|
||||||
AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND "
|
SQLiteAxolotlStore.ACCOUNT + "=? AND "
|
||||||
+ AxolotlService.SQLiteAxolotlStore.ID + "=?",
|
+ SQLiteAxolotlStore.ID + "=?",
|
||||||
args);
|
args);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Cursor getCursorForSignedPreKey(Account account, int signedPreKeyId) {
|
private Cursor getCursorForSignedPreKey(Account account, int signedPreKeyId) {
|
||||||
SQLiteDatabase db = this.getReadableDatabase();
|
SQLiteDatabase db = this.getReadableDatabase();
|
||||||
String[] columns = {AxolotlService.SQLiteAxolotlStore.KEY};
|
String[] columns = {SQLiteAxolotlStore.KEY};
|
||||||
String[] selectionArgs = {account.getUuid(), Integer.toString(signedPreKeyId)};
|
String[] selectionArgs = {account.getUuid(), Integer.toString(signedPreKeyId)};
|
||||||
Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
|
Cursor cursor = db.query(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
|
||||||
columns,
|
columns,
|
||||||
AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND " + AxolotlService.SQLiteAxolotlStore.ID + "=?",
|
SQLiteAxolotlStore.ACCOUNT + "=? AND " + SQLiteAxolotlStore.ID + "=?",
|
||||||
selectionArgs,
|
selectionArgs,
|
||||||
null, null, null);
|
null, null, null);
|
||||||
|
|
||||||
|
@ -727,7 +734,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
if(cursor.getCount() != 0) {
|
if(cursor.getCount() != 0) {
|
||||||
cursor.moveToFirst();
|
cursor.moveToFirst();
|
||||||
try {
|
try {
|
||||||
record = new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT));
|
record = new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)),Base64.DEFAULT));
|
||||||
} catch (IOException e ) {
|
} catch (IOException e ) {
|
||||||
throw new AssertionError(e);
|
throw new AssertionError(e);
|
||||||
}
|
}
|
||||||
|
@ -739,17 +746,17 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
public List<SignedPreKeyRecord> loadSignedPreKeys(Account account) {
|
public List<SignedPreKeyRecord> loadSignedPreKeys(Account account) {
|
||||||
List<SignedPreKeyRecord> prekeys = new ArrayList<>();
|
List<SignedPreKeyRecord> prekeys = new ArrayList<>();
|
||||||
SQLiteDatabase db = this.getReadableDatabase();
|
SQLiteDatabase db = this.getReadableDatabase();
|
||||||
String[] columns = {AxolotlService.SQLiteAxolotlStore.KEY};
|
String[] columns = {SQLiteAxolotlStore.KEY};
|
||||||
String[] selectionArgs = {account.getUuid()};
|
String[] selectionArgs = {account.getUuid()};
|
||||||
Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
|
Cursor cursor = db.query(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
|
||||||
columns,
|
columns,
|
||||||
AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=?",
|
SQLiteAxolotlStore.ACCOUNT + "=?",
|
||||||
selectionArgs,
|
selectionArgs,
|
||||||
null, null, null);
|
null, null, null);
|
||||||
|
|
||||||
while(cursor.moveToNext()) {
|
while(cursor.moveToNext()) {
|
||||||
try {
|
try {
|
||||||
prekeys.add(new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)), Base64.DEFAULT)));
|
prekeys.add(new SignedPreKeyRecord(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)), Base64.DEFAULT)));
|
||||||
} catch (IOException ignored) {
|
} catch (IOException ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -767,18 +774,18 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
public void storeSignedPreKey(Account account, SignedPreKeyRecord record) {
|
public void storeSignedPreKey(Account account, SignedPreKeyRecord record) {
|
||||||
SQLiteDatabase db = this.getWritableDatabase();
|
SQLiteDatabase db = this.getWritableDatabase();
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
values.put(AxolotlService.SQLiteAxolotlStore.ID, record.getId());
|
values.put(SQLiteAxolotlStore.ID, record.getId());
|
||||||
values.put(AxolotlService.SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(),Base64.DEFAULT));
|
values.put(SQLiteAxolotlStore.KEY, Base64.encodeToString(record.serialize(),Base64.DEFAULT));
|
||||||
values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid());
|
values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
|
||||||
db.insert(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, null, values);
|
db.insert(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, null, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deleteSignedPreKey(Account account, int signedPreKeyId) {
|
public void deleteSignedPreKey(Account account, int signedPreKeyId) {
|
||||||
SQLiteDatabase db = this.getWritableDatabase();
|
SQLiteDatabase db = this.getWritableDatabase();
|
||||||
String[] args = {account.getUuid(), Integer.toString(signedPreKeyId)};
|
String[] args = {account.getUuid(), Integer.toString(signedPreKeyId)};
|
||||||
db.delete(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
|
db.delete(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
|
||||||
AxolotlService.SQLiteAxolotlStore.ACCOUNT + "=? AND "
|
SQLiteAxolotlStore.ACCOUNT + "=? AND "
|
||||||
+ AxolotlService.SQLiteAxolotlStore.ID + "=?",
|
+ SQLiteAxolotlStore.ID + "=?",
|
||||||
args);
|
args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -792,24 +799,24 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
|
|
||||||
private Cursor getIdentityKeyCursor(Account account, String name, Boolean own, String fingerprint) {
|
private Cursor getIdentityKeyCursor(Account account, String name, Boolean own, String fingerprint) {
|
||||||
final SQLiteDatabase db = this.getReadableDatabase();
|
final SQLiteDatabase db = this.getReadableDatabase();
|
||||||
String[] columns = {AxolotlService.SQLiteAxolotlStore.TRUSTED,
|
String[] columns = {SQLiteAxolotlStore.TRUSTED,
|
||||||
AxolotlService.SQLiteAxolotlStore.KEY};
|
SQLiteAxolotlStore.KEY};
|
||||||
ArrayList<String> selectionArgs = new ArrayList<>(4);
|
ArrayList<String> selectionArgs = new ArrayList<>(4);
|
||||||
selectionArgs.add(account.getUuid());
|
selectionArgs.add(account.getUuid());
|
||||||
String selectionString = AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?";
|
String selectionString = SQLiteAxolotlStore.ACCOUNT + " = ?";
|
||||||
if (name != null){
|
if (name != null){
|
||||||
selectionArgs.add(name);
|
selectionArgs.add(name);
|
||||||
selectionString += " AND " +AxolotlService.SQLiteAxolotlStore.NAME + " = ?";
|
selectionString += " AND " + SQLiteAxolotlStore.NAME + " = ?";
|
||||||
}
|
}
|
||||||
if (fingerprint != null){
|
if (fingerprint != null){
|
||||||
selectionArgs.add(fingerprint);
|
selectionArgs.add(fingerprint);
|
||||||
selectionString += " AND " +AxolotlService.SQLiteAxolotlStore.FINGERPRINT + " = ?";
|
selectionString += " AND " + SQLiteAxolotlStore.FINGERPRINT + " = ?";
|
||||||
}
|
}
|
||||||
if (own != null){
|
if (own != null){
|
||||||
selectionArgs.add(own?"1":"0");
|
selectionArgs.add(own?"1":"0");
|
||||||
selectionString += " AND " +AxolotlService.SQLiteAxolotlStore.OWN + " = ?";
|
selectionString += " AND " + SQLiteAxolotlStore.OWN + " = ?";
|
||||||
}
|
}
|
||||||
Cursor cursor = db.query(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME,
|
Cursor cursor = db.query(SQLiteAxolotlStore.IDENTITIES_TABLENAME,
|
||||||
columns,
|
columns,
|
||||||
selectionString,
|
selectionString,
|
||||||
selectionArgs.toArray(new String[selectionArgs.size()]),
|
selectionArgs.toArray(new String[selectionArgs.size()]),
|
||||||
|
@ -824,7 +831,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
if(cursor.getCount() != 0) {
|
if(cursor.getCount() != 0) {
|
||||||
cursor.moveToFirst();
|
cursor.moveToFirst();
|
||||||
try {
|
try {
|
||||||
identityKeyPair = new IdentityKeyPair(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT));
|
identityKeyPair = new IdentityKeyPair(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)),Base64.DEFAULT));
|
||||||
} catch (InvalidKeyException e) {
|
} catch (InvalidKeyException e) {
|
||||||
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name);
|
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Encountered invalid IdentityKey in database for account" + account.getJid().toBareJid() + ", address: " + name);
|
||||||
}
|
}
|
||||||
|
@ -838,18 +845,18 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
return loadIdentityKeys(account, name, null);
|
return loadIdentityKeys(account, name, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<IdentityKey> loadIdentityKeys(Account account, String name, AxolotlService.SQLiteAxolotlStore.Trust trust) {
|
public Set<IdentityKey> loadIdentityKeys(Account account, String name, XmppAxolotlSession.Trust trust) {
|
||||||
Set<IdentityKey> identityKeys = new HashSet<>();
|
Set<IdentityKey> identityKeys = new HashSet<>();
|
||||||
Cursor cursor = getIdentityKeyCursor(account, name, false);
|
Cursor cursor = getIdentityKeyCursor(account, name, false);
|
||||||
|
|
||||||
while(cursor.moveToNext()) {
|
while(cursor.moveToNext()) {
|
||||||
if ( trust != null &&
|
if ( trust != null &&
|
||||||
cursor.getInt(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.TRUSTED))
|
cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.TRUSTED))
|
||||||
!= trust.getCode()) {
|
!= trust.getCode()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
identityKeys.add(new IdentityKey(Base64.decode(cursor.getString(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.KEY)),Base64.DEFAULT),0));
|
identityKeys.add(new IdentityKey(Base64.decode(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.KEY)),Base64.DEFAULT),0));
|
||||||
} catch (InvalidKeyException e) {
|
} catch (InvalidKeyException e) {
|
||||||
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Encountered invalid IdentityKey in database for account"+account.getJid().toBareJid()+", address: "+name);
|
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account)+"Encountered invalid IdentityKey in database for account"+account.getJid().toBareJid()+", address: "+name);
|
||||||
}
|
}
|
||||||
|
@ -864,55 +871,55 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
String[] args = {
|
String[] args = {
|
||||||
account.getUuid(),
|
account.getUuid(),
|
||||||
name,
|
name,
|
||||||
String.valueOf(AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED.getCode())
|
String.valueOf(XmppAxolotlSession.Trust.TRUSTED.getCode())
|
||||||
};
|
};
|
||||||
return DatabaseUtils.queryNumEntries(db, AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME,
|
return DatabaseUtils.queryNumEntries(db, SQLiteAxolotlStore.IDENTITIES_TABLENAME,
|
||||||
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?"
|
SQLiteAxolotlStore.ACCOUNT + " = ?"
|
||||||
+ " AND " + AxolotlService.SQLiteAxolotlStore.NAME + " = ?"
|
+ " AND " + SQLiteAxolotlStore.NAME + " = ?"
|
||||||
+ " AND " + AxolotlService.SQLiteAxolotlStore.TRUSTED + " = ?",
|
+ " AND " + SQLiteAxolotlStore.TRUSTED + " = ?",
|
||||||
args
|
args
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized) {
|
private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized) {
|
||||||
storeIdentityKey(account, name, own, fingerprint, base64Serialized, AxolotlService.SQLiteAxolotlStore.Trust.UNDECIDED);
|
storeIdentityKey(account, name, own, fingerprint, base64Serialized, XmppAxolotlSession.Trust.UNDECIDED);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized, AxolotlService.SQLiteAxolotlStore.Trust trusted) {
|
private void storeIdentityKey(Account account, String name, boolean own, String fingerprint, String base64Serialized, XmppAxolotlSession.Trust trusted) {
|
||||||
SQLiteDatabase db = this.getWritableDatabase();
|
SQLiteDatabase db = this.getWritableDatabase();
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
values.put(AxolotlService.SQLiteAxolotlStore.ACCOUNT, account.getUuid());
|
values.put(SQLiteAxolotlStore.ACCOUNT, account.getUuid());
|
||||||
values.put(AxolotlService.SQLiteAxolotlStore.NAME, name);
|
values.put(SQLiteAxolotlStore.NAME, name);
|
||||||
values.put(AxolotlService.SQLiteAxolotlStore.OWN, own ? 1 : 0);
|
values.put(SQLiteAxolotlStore.OWN, own ? 1 : 0);
|
||||||
values.put(AxolotlService.SQLiteAxolotlStore.FINGERPRINT, fingerprint);
|
values.put(SQLiteAxolotlStore.FINGERPRINT, fingerprint);
|
||||||
values.put(AxolotlService.SQLiteAxolotlStore.KEY, base64Serialized);
|
values.put(SQLiteAxolotlStore.KEY, base64Serialized);
|
||||||
values.put(AxolotlService.SQLiteAxolotlStore.TRUSTED, trusted.getCode());
|
values.put(SQLiteAxolotlStore.TRUSTED, trusted.getCode());
|
||||||
db.insert(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values);
|
db.insert(SQLiteAxolotlStore.IDENTITIES_TABLENAME, null, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AxolotlService.SQLiteAxolotlStore.Trust isIdentityKeyTrusted(Account account, String fingerprint) {
|
public XmppAxolotlSession.Trust isIdentityKeyTrusted(Account account, String fingerprint) {
|
||||||
Cursor cursor = getIdentityKeyCursor(account, fingerprint);
|
Cursor cursor = getIdentityKeyCursor(account, fingerprint);
|
||||||
AxolotlService.SQLiteAxolotlStore.Trust trust = null;
|
XmppAxolotlSession.Trust trust = null;
|
||||||
if (cursor.getCount() > 0) {
|
if (cursor.getCount() > 0) {
|
||||||
cursor.moveToFirst();
|
cursor.moveToFirst();
|
||||||
int trustValue = cursor.getInt(cursor.getColumnIndex(AxolotlService.SQLiteAxolotlStore.TRUSTED));
|
int trustValue = cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.TRUSTED));
|
||||||
trust = AxolotlService.SQLiteAxolotlStore.Trust.fromCode(trustValue);
|
trust = XmppAxolotlSession.Trust.fromCode(trustValue);
|
||||||
}
|
}
|
||||||
cursor.close();
|
cursor.close();
|
||||||
return trust;
|
return trust;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean setIdentityKeyTrust(Account account, String fingerprint, AxolotlService.SQLiteAxolotlStore.Trust trust) {
|
public boolean setIdentityKeyTrust(Account account, String fingerprint, XmppAxolotlSession.Trust trust) {
|
||||||
SQLiteDatabase db = this.getWritableDatabase();
|
SQLiteDatabase db = this.getWritableDatabase();
|
||||||
String[] selectionArgs = {
|
String[] selectionArgs = {
|
||||||
account.getUuid(),
|
account.getUuid(),
|
||||||
fingerprint
|
fingerprint
|
||||||
};
|
};
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
values.put(AxolotlService.SQLiteAxolotlStore.TRUSTED, trust.getCode());
|
values.put(SQLiteAxolotlStore.TRUSTED, trust.getCode());
|
||||||
int rows = db.update(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME, values,
|
int rows = db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME, values,
|
||||||
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ? AND "
|
SQLiteAxolotlStore.ACCOUNT + " = ? AND "
|
||||||
+ AxolotlService.SQLiteAxolotlStore.FINGERPRINT + " = ? ",
|
+ SQLiteAxolotlStore.FINGERPRINT + " = ? ",
|
||||||
selectionArgs);
|
selectionArgs);
|
||||||
return rows == 1;
|
return rows == 1;
|
||||||
}
|
}
|
||||||
|
@ -922,7 +929,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void storeOwnIdentityKeyPair(Account account, String name, IdentityKeyPair identityKeyPair) {
|
public void storeOwnIdentityKeyPair(Account account, String name, IdentityKeyPair identityKeyPair) {
|
||||||
storeIdentityKey(account, name, true, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT), AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED);
|
storeIdentityKey(account, name, true, identityKeyPair.getPublicKey().getFingerprint().replaceAll("\\s", ""), Base64.encodeToString(identityKeyPair.serialize(), Base64.DEFAULT), XmppAxolotlSession.Trust.TRUSTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void recreateAxolotlDb() {
|
public void recreateAxolotlDb() {
|
||||||
|
@ -931,13 +938,13 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
|
|
||||||
public void recreateAxolotlDb(SQLiteDatabase db) {
|
public void recreateAxolotlDb(SQLiteDatabase db) {
|
||||||
Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+">>> (RE)CREATING AXOLOTL DATABASE <<<");
|
Log.d(Config.LOGTAG, AxolotlService.LOGPREFIX+" : "+">>> (RE)CREATING AXOLOTL DATABASE <<<");
|
||||||
db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME);
|
db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SESSION_TABLENAME);
|
||||||
db.execSQL(CREATE_SESSIONS_STATEMENT);
|
db.execSQL(CREATE_SESSIONS_STATEMENT);
|
||||||
db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME);
|
db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.PREKEY_TABLENAME);
|
||||||
db.execSQL(CREATE_PREKEYS_STATEMENT);
|
db.execSQL(CREATE_PREKEYS_STATEMENT);
|
||||||
db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME);
|
db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME);
|
||||||
db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT);
|
db.execSQL(CREATE_SIGNED_PREKEYS_STATEMENT);
|
||||||
db.execSQL("DROP TABLE IF EXISTS " + AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME);
|
db.execSQL("DROP TABLE IF EXISTS " + SQLiteAxolotlStore.IDENTITIES_TABLENAME);
|
||||||
db.execSQL(CREATE_IDENTITIES_STATEMENT);
|
db.execSQL(CREATE_IDENTITIES_STATEMENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -948,17 +955,17 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
String[] deleteArgs= {
|
String[] deleteArgs= {
|
||||||
accountName
|
accountName
|
||||||
};
|
};
|
||||||
db.delete(AxolotlService.SQLiteAxolotlStore.SESSION_TABLENAME,
|
db.delete(SQLiteAxolotlStore.SESSION_TABLENAME,
|
||||||
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?",
|
SQLiteAxolotlStore.ACCOUNT + " = ?",
|
||||||
deleteArgs);
|
deleteArgs);
|
||||||
db.delete(AxolotlService.SQLiteAxolotlStore.PREKEY_TABLENAME,
|
db.delete(SQLiteAxolotlStore.PREKEY_TABLENAME,
|
||||||
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?",
|
SQLiteAxolotlStore.ACCOUNT + " = ?",
|
||||||
deleteArgs);
|
deleteArgs);
|
||||||
db.delete(AxolotlService.SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
|
db.delete(SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME,
|
||||||
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?",
|
SQLiteAxolotlStore.ACCOUNT + " = ?",
|
||||||
deleteArgs);
|
deleteArgs);
|
||||||
db.delete(AxolotlService.SQLiteAxolotlStore.IDENTITIES_TABLENAME,
|
db.delete(SQLiteAxolotlStore.IDENTITIES_TABLENAME,
|
||||||
AxolotlService.SQLiteAxolotlStore.ACCOUNT + " = ?",
|
SQLiteAxolotlStore.ACCOUNT + " = ?",
|
||||||
deleteArgs);
|
deleteArgs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,33 @@
|
||||||
package eu.siacs.conversations.services;
|
package eu.siacs.conversations.services;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
|
import org.bouncycastle.crypto.engines.AESEngine;
|
||||||
|
import org.bouncycastle.crypto.modes.AEADBlockCipher;
|
||||||
|
import org.bouncycastle.crypto.modes.GCMBlockCipher;
|
||||||
|
import org.bouncycastle.crypto.params.AEADParameters;
|
||||||
|
import org.bouncycastle.crypto.params.KeyParameter;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.CipherInputStream;
|
||||||
|
import javax.crypto.CipherOutputStream;
|
||||||
|
import javax.crypto.NoSuchPaddingException;
|
||||||
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.Config;
|
||||||
|
import eu.siacs.conversations.entities.DownloadableFile;
|
||||||
|
|
||||||
public class AbstractConnectionManager {
|
public class AbstractConnectionManager {
|
||||||
protected XmppConnectionService mXmppConnectionService;
|
protected XmppConnectionService mXmppConnectionService;
|
||||||
|
|
||||||
|
@ -20,4 +48,73 @@ public class AbstractConnectionManager {
|
||||||
return 524288;
|
return 524288;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Pair<InputStream,Integer> createInputStream(DownloadableFile file, boolean gcm) {
|
||||||
|
FileInputStream is;
|
||||||
|
int size;
|
||||||
|
try {
|
||||||
|
is = new FileInputStream(file);
|
||||||
|
size = (int) file.getSize();
|
||||||
|
if (file.getKey() == null) {
|
||||||
|
return new Pair<InputStream,Integer>(is,size);
|
||||||
|
}
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (gcm) {
|
||||||
|
AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine());
|
||||||
|
cipher.init(true, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv()));
|
||||||
|
InputStream cis = new org.bouncycastle.crypto.io.CipherInputStream(is, cipher);
|
||||||
|
return new Pair<>(cis, cipher.getOutputSize(size));
|
||||||
|
} else {
|
||||||
|
IvParameterSpec ips = new IvParameterSpec(file.getIv());
|
||||||
|
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(file.getKey(), "AES"), ips);
|
||||||
|
Log.d(Config.LOGTAG, "opening encrypted input stream");
|
||||||
|
return new Pair<InputStream,Integer>(new CipherInputStream(is, cipher),(size / 16 + 1) * 16);
|
||||||
|
}
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
return null;
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
return null;
|
||||||
|
} catch (NoSuchPaddingException e) {
|
||||||
|
return null;
|
||||||
|
} catch (InvalidAlgorithmParameterException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static OutputStream createOutputStream(DownloadableFile file, boolean gcm) {
|
||||||
|
FileOutputStream os;
|
||||||
|
try {
|
||||||
|
os = new FileOutputStream(file);
|
||||||
|
if (file.getKey() == null) {
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (gcm) {
|
||||||
|
AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine());
|
||||||
|
cipher.init(false, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv()));
|
||||||
|
return new org.bouncycastle.crypto.io.CipherOutputStream(os, cipher);
|
||||||
|
} else {
|
||||||
|
IvParameterSpec ips = new IvParameterSpec(file.getIv());
|
||||||
|
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(file.getKey(), "AES"), ips);
|
||||||
|
Log.d(Config.LOGTAG, "opening encrypted output stream");
|
||||||
|
return new CipherOutputStream(os, cipher);
|
||||||
|
}
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
return null;
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
return null;
|
||||||
|
} catch (NoSuchPaddingException e) {
|
||||||
|
return null;
|
||||||
|
} catch (InvalidAlgorithmParameterException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -330,9 +330,10 @@ public class NotificationService {
|
||||||
|
|
||||||
private Message getImage(final Iterable<Message> messages) {
|
private Message getImage(final Iterable<Message> messages) {
|
||||||
for (final Message message : messages) {
|
for (final Message message : messages) {
|
||||||
if (message.getType() == Message.TYPE_IMAGE
|
if (message.getType() != Message.TYPE_TEXT
|
||||||
&& message.getTransferable() == null
|
&& message.getTransferable() == null
|
||||||
&& message.getEncryption() != Message.ENCRYPTION_PGP) {
|
&& message.getEncryption() != Message.ENCRYPTION_PGP
|
||||||
|
&& message.getFileParams().height > 0) {
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -349,7 +349,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||||
public void attachLocationToConversation(final Conversation conversation,
|
public void attachLocationToConversation(final Conversation conversation,
|
||||||
final Uri uri,
|
final Uri uri,
|
||||||
final UiCallback<Message> callback) {
|
final UiCallback<Message> callback) {
|
||||||
int encryption = conversation.getNextEncryption(forceEncryption());
|
int encryption = conversation.getNextEncryption();
|
||||||
if (encryption == Message.ENCRYPTION_PGP) {
|
if (encryption == Message.ENCRYPTION_PGP) {
|
||||||
encryption = Message.ENCRYPTION_DECRYPTED;
|
encryption = Message.ENCRYPTION_DECRYPTED;
|
||||||
}
|
}
|
||||||
|
@ -368,12 +368,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||||
final Uri uri,
|
final Uri uri,
|
||||||
final UiCallback<Message> callback) {
|
final UiCallback<Message> callback) {
|
||||||
final Message message;
|
final Message message;
|
||||||
if (conversation.getNextEncryption(forceEncryption()) == Message.ENCRYPTION_PGP) {
|
if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
|
||||||
message = new Message(conversation, "",
|
message = new Message(conversation, "", Message.ENCRYPTION_DECRYPTED);
|
||||||
Message.ENCRYPTION_DECRYPTED);
|
|
||||||
} else {
|
} else {
|
||||||
message = new Message(conversation, "",
|
message = new Message(conversation, "", conversation.getNextEncryption());
|
||||||
conversation.getNextEncryption(forceEncryption()));
|
|
||||||
}
|
}
|
||||||
message.setCounterpart(conversation.getNextCounterpart());
|
message.setCounterpart(conversation.getNextCounterpart());
|
||||||
message.setType(Message.TYPE_FILE);
|
message.setType(Message.TYPE_FILE);
|
||||||
|
@ -409,12 +407,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||||
public void attachImageToConversation(final Conversation conversation,
|
public void attachImageToConversation(final Conversation conversation,
|
||||||
final Uri uri, final UiCallback<Message> callback) {
|
final Uri uri, final UiCallback<Message> callback) {
|
||||||
final Message message;
|
final Message message;
|
||||||
if (conversation.getNextEncryption(forceEncryption()) == Message.ENCRYPTION_PGP) {
|
if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
|
||||||
message = new Message(conversation, "",
|
message = new Message(conversation, "", Message.ENCRYPTION_DECRYPTED);
|
||||||
Message.ENCRYPTION_DECRYPTED);
|
|
||||||
} else {
|
} else {
|
||||||
message = new Message(conversation, "",
|
message = new Message(conversation, "",conversation.getNextEncryption());
|
||||||
conversation.getNextEncryption(forceEncryption()));
|
|
||||||
}
|
}
|
||||||
message.setCounterpart(conversation.getNextCounterpart());
|
message.setCounterpart(conversation.getNextCounterpart());
|
||||||
message.setType(Message.TYPE_IMAGE);
|
message.setType(Message.TYPE_IMAGE);
|
||||||
|
@ -424,7 +420,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
getFileBackend().copyImageToPrivateStorage(message, uri);
|
getFileBackend().copyImageToPrivateStorage(message, uri);
|
||||||
if (conversation.getNextEncryption(forceEncryption()) == Message.ENCRYPTION_PGP) {
|
if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
|
||||||
getPgpEngine().encrypt(message, callback);
|
getPgpEngine().encrypt(message, callback);
|
||||||
} else {
|
} else {
|
||||||
callback.success(message);
|
callback.success(message);
|
||||||
|
@ -759,6 +755,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Message.ENCRYPTION_AXOLOTL:
|
case Message.ENCRYPTION_AXOLOTL:
|
||||||
|
message.setAxolotlFingerprint(account.getAxolotlService().getOwnPublicKey().getFingerprint().replaceAll("\\s", ""));
|
||||||
if (message.needsUploading()) {
|
if (message.needsUploading()) {
|
||||||
if (account.httpUploadAvailable() || message.fixCounterpart()) {
|
if (account.httpUploadAvailable() || message.fixCounterpart()) {
|
||||||
this.sendFileMessage(message,delay);
|
this.sendFileMessage(message,delay);
|
||||||
|
@ -768,8 +765,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||||
} else {
|
} else {
|
||||||
XmppAxolotlMessage axolotlMessage = account.getAxolotlService().fetchAxolotlMessageFromCache(message);
|
XmppAxolotlMessage axolotlMessage = account.getAxolotlService().fetchAxolotlMessageFromCache(message);
|
||||||
if (axolotlMessage == null) {
|
if (axolotlMessage == null) {
|
||||||
account.getAxolotlService().prepareMessage(message,delay);
|
account.getAxolotlService().preparePayloadMessage(message, delay);
|
||||||
message.setAxolotlFingerprint(account.getAxolotlService().getOwnPublicKey().getFingerprint().replaceAll("\\s", ""));
|
|
||||||
} else {
|
} else {
|
||||||
packet = mMessageGenerator.generateAxolotlChat(message, axolotlMessage);
|
packet = mMessageGenerator.generateAxolotlChat(message, axolotlMessage);
|
||||||
}
|
}
|
||||||
|
@ -2533,8 +2529,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (final Message msg : messages) {
|
for (final Message msg : messages) {
|
||||||
|
msg.setTime(System.currentTimeMillis());
|
||||||
markMessage(msg, Message.STATUS_WAITING);
|
markMessage(msg, Message.STATUS_WAITING);
|
||||||
this.resendMessage(msg,true);
|
this.resendMessage(msg,false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -111,6 +111,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
|
||||||
private LinearLayout keys;
|
private LinearLayout keys;
|
||||||
private LinearLayout tags;
|
private LinearLayout tags;
|
||||||
private boolean showDynamicTags;
|
private boolean showDynamicTags;
|
||||||
|
private String messageFingerprint;
|
||||||
|
|
||||||
private DialogInterface.OnClickListener addToPhonebook = new DialogInterface.OnClickListener() {
|
private DialogInterface.OnClickListener addToPhonebook = new DialogInterface.OnClickListener() {
|
||||||
|
|
||||||
|
@ -193,6 +194,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
|
||||||
} catch (final InvalidJidException ignored) {
|
} catch (final InvalidJidException ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.messageFingerprint = getIntent().getStringExtra("fingerprint");
|
||||||
setContentView(R.layout.activity_contact_details);
|
setContentView(R.layout.activity_contact_details);
|
||||||
|
|
||||||
contactJidTv = (TextView) findViewById(R.id.details_contactjid);
|
contactJidTv = (TextView) findViewById(R.id.details_contactjid);
|
||||||
|
@ -386,7 +388,8 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
|
||||||
}
|
}
|
||||||
for(final IdentityKey identityKey : xmppConnectionService.databaseBackend.loadIdentityKeys(
|
for(final IdentityKey identityKey : xmppConnectionService.databaseBackend.loadIdentityKeys(
|
||||||
contact.getAccount(), contact.getJid().toBareJid().toString())) {
|
contact.getAccount(), contact.getJid().toBareJid().toString())) {
|
||||||
hasKeys |= addFingerprintRow(keys, contact.getAccount(), identityKey);
|
boolean highlight = identityKey.getFingerprint().replaceAll("\\s", "").equals(messageFingerprint);
|
||||||
|
hasKeys |= addFingerprintRow(keys, contact.getAccount(), identityKey, highlight);
|
||||||
}
|
}
|
||||||
if (contact.getPgpKeyId() != 0) {
|
if (contact.getPgpKeyId() != 0) {
|
||||||
hasKeys = true;
|
hasKeys = true;
|
||||||
|
|
|
@ -39,7 +39,7 @@ import de.timroes.android.listview.EnhancedListView;
|
||||||
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.axolotl.AxolotlService;
|
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
||||||
import eu.siacs.conversations.crypto.axolotl.AxolotlService.SQLiteAxolotlStore.Trust;
|
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.entities.Blockable;
|
import eu.siacs.conversations.entities.Blockable;
|
||||||
import eu.siacs.conversations.entities.Contact;
|
import eu.siacs.conversations.entities.Contact;
|
||||||
|
@ -402,7 +402,7 @@ public class ConversationActivity extends XmppActivity
|
||||||
} else {
|
} else {
|
||||||
menuAdd.setVisible(!isConversationsOverviewHideable());
|
menuAdd.setVisible(!isConversationsOverviewHideable());
|
||||||
if (this.getSelectedConversation() != null) {
|
if (this.getSelectedConversation() != null) {
|
||||||
if (this.getSelectedConversation().getNextEncryption(forceEncryption()) != Message.ENCRYPTION_NONE) {
|
if (this.getSelectedConversation().getNextEncryption() != Message.ENCRYPTION_NONE) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
menuSecure.setIcon(R.drawable.ic_lock_white_24dp);
|
menuSecure.setIcon(R.drawable.ic_lock_white_24dp);
|
||||||
} else {
|
} else {
|
||||||
|
@ -515,7 +515,7 @@ public class ConversationActivity extends XmppActivity
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
final Conversation conversation = getSelectedConversation();
|
final Conversation conversation = getSelectedConversation();
|
||||||
final int encryption = conversation.getNextEncryption(forceEncryption());
|
final int encryption = conversation.getNextEncryption();
|
||||||
if (encryption == Message.ENCRYPTION_PGP) {
|
if (encryption == Message.ENCRYPTION_PGP) {
|
||||||
if (hasPgp()) {
|
if (hasPgp()) {
|
||||||
if (conversation.getContact().getPgpKeyId() != 0) {
|
if (conversation.getContact().getPgpKeyId() != 0) {
|
||||||
|
@ -792,6 +792,7 @@ public class ConversationActivity extends XmppActivity
|
||||||
xmppConnectionService.databaseBackend.updateConversation(conversation);
|
xmppConnectionService.databaseBackend.updateConversation(conversation);
|
||||||
fragment.updateChatMsgHint();
|
fragment.updateChatMsgHint();
|
||||||
invalidateOptionsMenu();
|
invalidateOptionsMenu();
|
||||||
|
refreshUi();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -803,15 +804,10 @@ public class ConversationActivity extends XmppActivity
|
||||||
if (conversation.getMode() == Conversation.MODE_MULTI) {
|
if (conversation.getMode() == Conversation.MODE_MULTI) {
|
||||||
otr.setEnabled(false);
|
otr.setEnabled(false);
|
||||||
axolotl.setEnabled(false);
|
axolotl.setEnabled(false);
|
||||||
} else {
|
} else if (!conversation.getAccount().getAxolotlService().isContactAxolotlCapable(conversation.getContact())) {
|
||||||
if (forceEncryption()) {
|
|
||||||
none.setVisible(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!conversation.getAccount().getAxolotlService().isContactAxolotlCapable(conversation.getContact())) {
|
|
||||||
axolotl.setEnabled(false);
|
axolotl.setEnabled(false);
|
||||||
}
|
}
|
||||||
switch (conversation.getNextEncryption(forceEncryption())) {
|
switch (conversation.getNextEncryption()) {
|
||||||
case Message.ENCRYPTION_NONE:
|
case Message.ENCRYPTION_NONE:
|
||||||
none.setChecked(true);
|
none.setChecked(true);
|
||||||
break;
|
break;
|
||||||
|
@ -822,8 +818,7 @@ public class ConversationActivity extends XmppActivity
|
||||||
pgp.setChecked(true);
|
pgp.setChecked(true);
|
||||||
break;
|
break;
|
||||||
case Message.ENCRYPTION_AXOLOTL:
|
case Message.ENCRYPTION_AXOLOTL:
|
||||||
popup.getMenu().findItem(R.id.encryption_choice_axolotl)
|
axolotl.setChecked(true);
|
||||||
.setChecked(true);
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
none.setChecked(true);
|
none.setChecked(true);
|
||||||
|
@ -836,8 +831,7 @@ public class ConversationActivity extends XmppActivity
|
||||||
protected void muteConversationDialog(final Conversation conversation) {
|
protected void muteConversationDialog(final Conversation conversation) {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
builder.setTitle(R.string.disable_notifications);
|
builder.setTitle(R.string.disable_notifications);
|
||||||
final int[] durations = getResources().getIntArray(
|
final int[] durations = getResources().getIntArray(R.array.mute_options_durations);
|
||||||
R.array.mute_options_durations);
|
|
||||||
builder.setItems(R.array.mute_options_descriptions,
|
builder.setItems(R.array.mute_options_descriptions,
|
||||||
new OnClickListener() {
|
new OnClickListener() {
|
||||||
|
|
||||||
|
@ -1269,10 +1263,6 @@ public class ConversationActivity extends XmppActivity
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean forceEncryption() {
|
|
||||||
return getPreferences().getBoolean("force_encryption", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean useSendButtonToIndicateStatus() {
|
public boolean useSendButtonToIndicateStatus() {
|
||||||
return getPreferences().getBoolean("send_button_status", false);
|
return getPreferences().getBoolean("send_button_status", false);
|
||||||
}
|
}
|
||||||
|
@ -1287,12 +1277,12 @@ public class ConversationActivity extends XmppActivity
|
||||||
|
|
||||||
protected boolean trustKeysIfNeeded(int requestCode, int attachmentChoice) {
|
protected boolean trustKeysIfNeeded(int requestCode, int attachmentChoice) {
|
||||||
AxolotlService axolotlService = mSelectedConversation.getAccount().getAxolotlService();
|
AxolotlService axolotlService = mSelectedConversation.getAccount().getAxolotlService();
|
||||||
boolean hasPendingKeys = !axolotlService.getKeysWithTrust(Trust.UNDECIDED,
|
boolean hasPendingKeys = !axolotlService.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED,
|
||||||
mSelectedConversation.getContact()).isEmpty()
|
mSelectedConversation.getContact()).isEmpty()
|
||||||
|| !axolotlService.findDevicesWithoutSession(mSelectedConversation).isEmpty();
|
|| !axolotlService.findDevicesWithoutSession(mSelectedConversation).isEmpty();
|
||||||
boolean hasNoTrustedKeys = axolotlService.getNumTrustedKeys(mSelectedConversation.getContact()) == 0;
|
boolean hasNoTrustedKeys = axolotlService.getNumTrustedKeys(mSelectedConversation.getContact()) == 0;
|
||||||
if( hasPendingKeys || hasNoTrustedKeys) {
|
if( hasPendingKeys || hasNoTrustedKeys) {
|
||||||
axolotlService.createSessionsIfNeeded(mSelectedConversation, false);
|
axolotlService.createSessionsIfNeeded(mSelectedConversation);
|
||||||
Intent intent = new Intent(getApplicationContext(), TrustKeysActivity.class);
|
Intent intent = new Intent(getApplicationContext(), TrustKeysActivity.class);
|
||||||
intent.putExtra("contact", mSelectedConversation.getContact().getJid().toBareJid().toString());
|
intent.putExtra("contact", mSelectedConversation.getContact().getJid().toBareJid().toString());
|
||||||
intent.putExtra("account", mSelectedConversation.getAccount().getJid().toBareJid().toString());
|
intent.putExtra("account", mSelectedConversation.getAccount().getJid().toBareJid().toString());
|
||||||
|
|
|
@ -293,23 +293,27 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
|
||||||
if (body.length() == 0 || this.conversation == null) {
|
if (body.length() == 0 || this.conversation == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Message message = new Message(conversation, body, conversation.getNextEncryption(activity.forceEncryption()));
|
Message message = new Message(conversation, body, conversation.getNextEncryption());
|
||||||
if (conversation.getMode() == Conversation.MODE_MULTI) {
|
if (conversation.getMode() == Conversation.MODE_MULTI) {
|
||||||
if (conversation.getNextCounterpart() != null) {
|
if (conversation.getNextCounterpart() != null) {
|
||||||
message.setCounterpart(conversation.getNextCounterpart());
|
message.setCounterpart(conversation.getNextCounterpart());
|
||||||
message.setType(Message.TYPE_PRIVATE);
|
message.setType(Message.TYPE_PRIVATE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (conversation.getNextEncryption(activity.forceEncryption()) == Message.ENCRYPTION_OTR) {
|
switch (conversation.getNextEncryption()) {
|
||||||
sendOtrMessage(message);
|
case Message.ENCRYPTION_OTR:
|
||||||
} else if (conversation.getNextEncryption(activity.forceEncryption()) == Message.ENCRYPTION_PGP) {
|
sendOtrMessage(message);
|
||||||
sendPgpMessage(message);
|
break;
|
||||||
} else if (conversation.getNextEncryption(activity.forceEncryption()) == Message.ENCRYPTION_AXOLOTL) {
|
case Message.ENCRYPTION_PGP:
|
||||||
if(!activity.trustKeysIfNeeded(ConversationActivity.REQUEST_TRUST_KEYS_TEXT)) {
|
sendPgpMessage(message);
|
||||||
sendAxolotlMessage(message);
|
break;
|
||||||
}
|
case Message.ENCRYPTION_AXOLOTL:
|
||||||
} else {
|
if(!activity.trustKeysIfNeeded(ConversationActivity.REQUEST_TRUST_KEYS_TEXT)) {
|
||||||
sendPlainTextMessage(message);
|
sendAxolotlMessage(message);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
sendPlainTextMessage(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -320,7 +324,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
|
||||||
R.string.send_private_message_to,
|
R.string.send_private_message_to,
|
||||||
conversation.getNextCounterpart().getResourcepart()));
|
conversation.getNextCounterpart().getResourcepart()));
|
||||||
} else {
|
} else {
|
||||||
switch (conversation.getNextEncryption(activity.forceEncryption())) {
|
switch (conversation.getNextEncryption()) {
|
||||||
case Message.ENCRYPTION_NONE:
|
case Message.ENCRYPTION_NONE:
|
||||||
mEditMessage
|
mEditMessage
|
||||||
.setHint(getString(R.string.send_plain_text_message));
|
.setHint(getString(R.string.send_plain_text_message));
|
||||||
|
@ -392,12 +396,13 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
|
||||||
highlightInConference(user);
|
highlightInConference(user);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
activity.switchToContactDetails(message.getContact());
|
activity.switchToContactDetails(message.getContact(), message.getAxolotlFingerprint());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Account account = message.getConversation().getAccount();
|
Account account = message.getConversation().getAccount();
|
||||||
Intent intent = new Intent(activity, EditAccountActivity.class);
|
Intent intent = new Intent(activity, EditAccountActivity.class);
|
||||||
intent.putExtra("jid", account.getJid().toBareJid().toString());
|
intent.putExtra("jid", account.getJid().toBareJid().toString());
|
||||||
|
intent.putExtra("fingerprint", message.getAxolotlFingerprint());
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -832,7 +837,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
|
||||||
} catch (final NoSuchElementException ignored) {
|
} catch (final NoSuchElementException ignored) {
|
||||||
|
|
||||||
}
|
}
|
||||||
activity.xmppConnectionService.updateConversationUi();
|
activity.refreshUi();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1210,11 +1215,11 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
if (requestCode == ConversationActivity.REQUEST_TRUST_KEYS_TEXT) {
|
if (requestCode == ConversationActivity.REQUEST_TRUST_KEYS_TEXT) {
|
||||||
final String body = mEditMessage.getText().toString();
|
final String body = mEditMessage.getText().toString();
|
||||||
Message message = new Message(conversation, body, conversation.getNextEncryption(activity.forceEncryption()));
|
Message message = new Message(conversation, body, conversation.getNextEncryption());
|
||||||
sendAxolotlMessage(message);
|
sendAxolotlMessage(message);
|
||||||
} else if (requestCode == ConversationActivity.REQUEST_TRUST_KEYS_MENU) {
|
} else if (requestCode == ConversationActivity.REQUEST_TRUST_KEYS_MENU) {
|
||||||
int choice = data.getIntExtra("choice", ConversationActivity.ATTACHMENT_CHOICE_INVALID);
|
int choice = data.getIntExtra("choice", ConversationActivity.ATTACHMENT_CHOICE_INVALID);
|
||||||
activity.selectPresenceToAttachFile(choice, conversation.getNextEncryption(activity.forceEncryption()));
|
activity.selectPresenceToAttachFile(choice, conversation.getNextEncryption());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,6 +74,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
|
||||||
|
|
||||||
private Jid jidToEdit;
|
private Jid jidToEdit;
|
||||||
private Account mAccount;
|
private Account mAccount;
|
||||||
|
private String messageFingerprint;
|
||||||
|
|
||||||
private boolean mFetchingAvatar = false;
|
private boolean mFetchingAvatar = false;
|
||||||
|
|
||||||
|
@ -388,6 +389,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
|
||||||
} catch (final InvalidJidException | NullPointerException ignored) {
|
} catch (final InvalidJidException | NullPointerException ignored) {
|
||||||
this.jidToEdit = null;
|
this.jidToEdit = null;
|
||||||
}
|
}
|
||||||
|
this.messageFingerprint = getIntent().getStringExtra("fingerprint");
|
||||||
if (this.jidToEdit != null) {
|
if (this.jidToEdit != null) {
|
||||||
this.mRegisterNew.setVisibility(View.GONE);
|
this.mRegisterNew.setVisibility(View.GONE);
|
||||||
if (getActionBar() != null) {
|
if (getActionBar() != null) {
|
||||||
|
@ -571,7 +573,8 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
|
||||||
if(ownKey.equals(identityKey)) {
|
if(ownKey.equals(identityKey)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
hasKeys |= addFingerprintRow(keys, mAccount, identityKey);
|
boolean highlight = identityKey.getFingerprint().replaceAll("\\s", "").equals(messageFingerprint);
|
||||||
|
hasKeys |= addFingerprintRow(keys, mAccount, identityKey, highlight);
|
||||||
}
|
}
|
||||||
if (hasKeys) {
|
if (hasKeys) {
|
||||||
keysCard.setVisibility(View.VISIBLE);
|
keysCard.setVisibility(View.VISIBLE);
|
||||||
|
|
|
@ -16,7 +16,7 @@ import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.crypto.axolotl.AxolotlService.SQLiteAxolotlStore.Trust;
|
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
|
||||||
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;
|
||||||
|
@ -118,8 +118,8 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
|
||||||
boolean hasForeignKeys = false;
|
boolean hasForeignKeys = false;
|
||||||
for(final IdentityKey identityKey : ownKeysToTrust.keySet()) {
|
for(final IdentityKey identityKey : ownKeysToTrust.keySet()) {
|
||||||
hasOwnKeys = true;
|
hasOwnKeys = true;
|
||||||
addFingerprintRowWithListeners(ownKeys, contact.getAccount(), identityKey,
|
addFingerprintRowWithListeners(ownKeys, contact.getAccount(), identityKey, false,
|
||||||
Trust.fromBoolean(ownKeysToTrust.get(identityKey)), false,
|
XmppAxolotlSession.Trust.fromBoolean(ownKeysToTrust.get(identityKey)), false,
|
||||||
new CompoundButton.OnCheckedChangeListener() {
|
new CompoundButton.OnCheckedChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||||
|
@ -134,8 +134,8 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
|
||||||
}
|
}
|
||||||
for(final IdentityKey identityKey : foreignKeysToTrust.keySet()) {
|
for(final IdentityKey identityKey : foreignKeysToTrust.keySet()) {
|
||||||
hasForeignKeys = true;
|
hasForeignKeys = true;
|
||||||
addFingerprintRowWithListeners(foreignKeys, contact.getAccount(), identityKey,
|
addFingerprintRowWithListeners(foreignKeys, contact.getAccount(), identityKey, false,
|
||||||
Trust.fromBoolean(foreignKeysToTrust.get(identityKey)), false,
|
XmppAxolotlSession.Trust.fromBoolean(foreignKeysToTrust.get(identityKey)), false,
|
||||||
new CompoundButton.OnCheckedChangeListener() {
|
new CompoundButton.OnCheckedChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||||
|
@ -171,11 +171,11 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getFingerprints(final Account account) {
|
private void getFingerprints(final Account account) {
|
||||||
Set<IdentityKey> ownKeysSet = account.getAxolotlService().getKeysWithTrust(Trust.UNDECIDED);
|
Set<IdentityKey> ownKeysSet = account.getAxolotlService().getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED);
|
||||||
Set<IdentityKey> foreignKeysSet = account.getAxolotlService().getKeysWithTrust(Trust.UNDECIDED, contact);
|
Set<IdentityKey> foreignKeysSet = account.getAxolotlService().getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED, contact);
|
||||||
if (hasNoTrustedKeys) {
|
if (hasNoTrustedKeys) {
|
||||||
ownKeysSet.addAll(account.getAxolotlService().getKeysWithTrust(Trust.UNTRUSTED));
|
ownKeysSet.addAll(account.getAxolotlService().getKeysWithTrust(XmppAxolotlSession.Trust.UNTRUSTED));
|
||||||
foreignKeysSet.addAll(account.getAxolotlService().getKeysWithTrust(Trust.UNTRUSTED, contact));
|
foreignKeysSet.addAll(account.getAxolotlService().getKeysWithTrust(XmppAxolotlSession.Trust.UNTRUSTED, contact));
|
||||||
}
|
}
|
||||||
for(final IdentityKey identityKey : ownKeysSet) {
|
for(final IdentityKey identityKey : ownKeysSet) {
|
||||||
if(!ownKeysToTrust.containsKey(identityKey)) {
|
if(!ownKeysToTrust.containsKey(identityKey)) {
|
||||||
|
@ -226,12 +226,12 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
|
||||||
for(IdentityKey identityKey:ownKeysToTrust.keySet()) {
|
for(IdentityKey identityKey:ownKeysToTrust.keySet()) {
|
||||||
contact.getAccount().getAxolotlService().setFingerprintTrust(
|
contact.getAccount().getAxolotlService().setFingerprintTrust(
|
||||||
identityKey.getFingerprint().replaceAll("\\s", ""),
|
identityKey.getFingerprint().replaceAll("\\s", ""),
|
||||||
Trust.fromBoolean(ownKeysToTrust.get(identityKey)));
|
XmppAxolotlSession.Trust.fromBoolean(ownKeysToTrust.get(identityKey)));
|
||||||
}
|
}
|
||||||
for(IdentityKey identityKey:foreignKeysToTrust.keySet()) {
|
for(IdentityKey identityKey:foreignKeysToTrust.keySet()) {
|
||||||
contact.getAccount().getAxolotlService().setFingerprintTrust(
|
contact.getAccount().getAxolotlService().setFingerprintTrust(
|
||||||
identityKey.getFingerprint().replaceAll("\\s", ""),
|
identityKey.getFingerprint().replaceAll("\\s", ""),
|
||||||
Trust.fromBoolean(foreignKeysToTrust.get(identityKey)));
|
XmppAxolotlSession.Trust.fromBoolean(foreignKeysToTrust.get(identityKey)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,7 @@ import java.util.concurrent.RejectedExecutionException;
|
||||||
|
|
||||||
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.axolotl.AxolotlService;
|
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
|
||||||
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;
|
||||||
|
@ -351,7 +351,7 @@ public abstract class XmppActivity extends Activity {
|
||||||
mPrimaryTextColor = getResources().getColor(R.color.black87);
|
mPrimaryTextColor = getResources().getColor(R.color.black87);
|
||||||
mSecondaryTextColor = getResources().getColor(R.color.black54);
|
mSecondaryTextColor = getResources().getColor(R.color.black54);
|
||||||
mTertiaryTextColor = getResources().getColor(R.color.black12);
|
mTertiaryTextColor = getResources().getColor(R.color.black12);
|
||||||
mColorRed = getResources().getColor(R.color.red500);
|
mColorRed = getResources().getColor(R.color.red800);
|
||||||
mColorOrange = getResources().getColor(R.color.orange500);
|
mColorOrange = getResources().getColor(R.color.orange500);
|
||||||
mColorGreen = getResources().getColor(R.color.green500);
|
mColorGreen = getResources().getColor(R.color.green500);
|
||||||
mPrimaryColor = getResources().getColor(R.color.green500);
|
mPrimaryColor = getResources().getColor(R.color.green500);
|
||||||
|
@ -424,10 +424,15 @@ public abstract class XmppActivity extends Activity {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void switchToContactDetails(Contact contact) {
|
public void switchToContactDetails(Contact contact) {
|
||||||
|
switchToContactDetails(contact, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void switchToContactDetails(Contact contact, String messageFingerprint) {
|
||||||
Intent intent = new Intent(this, ContactDetailsActivity.class);
|
Intent intent = new Intent(this, ContactDetailsActivity.class);
|
||||||
intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT);
|
intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT);
|
||||||
intent.putExtra("account", contact.getAccount().getJid().toBareJid().toString());
|
intent.putExtra("account", contact.getAccount().getJid().toBareJid().toString());
|
||||||
intent.putExtra("contact", contact.getJid().toString());
|
intent.putExtra("contact", contact.getJid().toString());
|
||||||
|
intent.putExtra("fingerprint", messageFingerprint);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -608,32 +613,25 @@ public abstract class XmppActivity extends Activity {
|
||||||
builder.create().show();
|
builder.create().show();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean addFingerprintRow(LinearLayout keys, final Account account, IdentityKey identityKey) {
|
protected boolean addFingerprintRow(LinearLayout keys, final Account account, IdentityKey identityKey, boolean highlight) {
|
||||||
final String fingerprint = identityKey.getFingerprint().replaceAll("\\s", "");
|
final String fingerprint = identityKey.getFingerprint().replaceAll("\\s", "");
|
||||||
final AxolotlService.SQLiteAxolotlStore.Trust trust = account.getAxolotlService()
|
final XmppAxolotlSession.Trust trust = account.getAxolotlService()
|
||||||
.getFingerprintTrust(fingerprint);
|
.getFingerprintTrust(fingerprint);
|
||||||
return addFingerprintRowWithListeners(keys, account, identityKey, trust, true,
|
return addFingerprintRowWithListeners(keys, account, identityKey, highlight, trust, true,
|
||||||
new CompoundButton.OnCheckedChangeListener() {
|
new CompoundButton.OnCheckedChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||||
if (isChecked != (trust == AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED)) {
|
account.getAxolotlService().setFingerprintTrust(fingerprint,
|
||||||
account.getAxolotlService().setFingerprintTrust(fingerprint,
|
(isChecked) ? XmppAxolotlSession.Trust.TRUSTED :
|
||||||
(isChecked) ? AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED :
|
XmppAxolotlSession.Trust.UNTRUSTED);
|
||||||
AxolotlService.SQLiteAxolotlStore.Trust.UNTRUSTED);
|
|
||||||
}
|
|
||||||
refreshUi();
|
|
||||||
xmppConnectionService.updateAccountUi();
|
|
||||||
xmppConnectionService.updateConversationUi();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new View.OnClickListener() {
|
new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
account.getAxolotlService().setFingerprintTrust(fingerprint,
|
account.getAxolotlService().setFingerprintTrust(fingerprint,
|
||||||
AxolotlService.SQLiteAxolotlStore.Trust.UNTRUSTED);
|
XmppAxolotlSession.Trust.UNTRUSTED);
|
||||||
refreshUi();
|
v.setEnabled(true);
|
||||||
xmppConnectionService.updateAccountUi();
|
|
||||||
xmppConnectionService.updateConversationUi();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -641,13 +639,14 @@ public abstract class XmppActivity extends Activity {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean addFingerprintRowWithListeners(LinearLayout keys, final Account account,
|
protected boolean addFingerprintRowWithListeners(LinearLayout keys, final Account account,
|
||||||
final IdentityKey identityKey,
|
final IdentityKey identityKey,
|
||||||
AxolotlService.SQLiteAxolotlStore.Trust trust,
|
boolean highlight,
|
||||||
boolean showTag,
|
XmppAxolotlSession.Trust trust,
|
||||||
CompoundButton.OnCheckedChangeListener
|
boolean showTag,
|
||||||
onCheckedChangeListener,
|
CompoundButton.OnCheckedChangeListener
|
||||||
View.OnClickListener onClickListener) {
|
onCheckedChangeListener,
|
||||||
if (trust == AxolotlService.SQLiteAxolotlStore.Trust.COMPROMISED) {
|
View.OnClickListener onClickListener) {
|
||||||
|
if (trust == XmppAxolotlSession.Trust.COMPROMISED) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
View view = getLayoutInflater().inflate(R.layout.contact_key, keys, false);
|
View view = getLayoutInflater().inflate(R.layout.contact_key, keys, false);
|
||||||
|
@ -668,7 +667,7 @@ public abstract class XmppActivity extends Activity {
|
||||||
switch (trust) {
|
switch (trust) {
|
||||||
case UNTRUSTED:
|
case UNTRUSTED:
|
||||||
case TRUSTED:
|
case TRUSTED:
|
||||||
trustToggle.setChecked(trust == AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED, false);
|
trustToggle.setChecked(trust == XmppAxolotlSession.Trust.TRUSTED, false);
|
||||||
trustToggle.setEnabled(true);
|
trustToggle.setEnabled(true);
|
||||||
key.setTextColor(getPrimaryTextColor());
|
key.setTextColor(getPrimaryTextColor());
|
||||||
keyType.setTextColor(getSecondaryTextColor());
|
keyType.setTextColor(getSecondaryTextColor());
|
||||||
|
@ -679,7 +678,15 @@ public abstract class XmppActivity extends Activity {
|
||||||
key.setTextColor(getPrimaryTextColor());
|
key.setTextColor(getPrimaryTextColor());
|
||||||
keyType.setTextColor(getSecondaryTextColor());
|
keyType.setTextColor(getSecondaryTextColor());
|
||||||
break;
|
break;
|
||||||
case INACTIVE:
|
case INACTIVE_UNTRUSTED:
|
||||||
|
case INACTIVE_UNDECIDED:
|
||||||
|
trustToggle.setOnClickListener(null);
|
||||||
|
trustToggle.setChecked(false, false);
|
||||||
|
trustToggle.setEnabled(false);
|
||||||
|
key.setTextColor(getTertiaryTextColor());
|
||||||
|
keyType.setTextColor(getTertiaryTextColor());
|
||||||
|
break;
|
||||||
|
case INACTIVE_TRUSTED:
|
||||||
trustToggle.setOnClickListener(null);
|
trustToggle.setOnClickListener(null);
|
||||||
trustToggle.setChecked(true, false);
|
trustToggle.setChecked(true, false);
|
||||||
trustToggle.setEnabled(false);
|
trustToggle.setEnabled(false);
|
||||||
|
@ -693,6 +700,12 @@ public abstract class XmppActivity extends Activity {
|
||||||
} else {
|
} else {
|
||||||
keyType.setVisibility(View.GONE);
|
keyType.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
if (highlight) {
|
||||||
|
keyType.setTextColor(getResources().getColor(R.color.accent));
|
||||||
|
keyType.setText(getString(R.string.axolotl_fingerprint_selected_message));
|
||||||
|
} else {
|
||||||
|
keyType.setText(getString(R.string.axolotl_fingerprint));
|
||||||
|
}
|
||||||
|
|
||||||
key.setText(CryptoHelper.prettifyFingerprint(identityKey.getFingerprint()));
|
key.setText(CryptoHelper.prettifyFingerprint(identityKey.getFingerprint()));
|
||||||
keys.addView(view);
|
keys.addView(view);
|
||||||
|
|
|
@ -27,7 +27,7 @@ import android.widget.Toast;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
|
||||||
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;
|
||||||
|
@ -96,19 +96,15 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
return this.getItemViewType(getItem(position));
|
return this.getItemViewType(getItem(position));
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getMessageTextColor(Message message) {
|
private int getMessageTextColor(int type, boolean primary) {
|
||||||
int type = this.getItemViewType(message);
|
|
||||||
|
|
||||||
if (type == SENT) {
|
if (type == SENT) {
|
||||||
return activity.getResources().getColor(R.color.black87);
|
return activity.getResources().getColor(primary ? R.color.black87 : R.color.black54);
|
||||||
} else if (type == RECEIVED) {
|
} else {
|
||||||
return activity.getResources().getColor(R.color.white);
|
return activity.getResources().getColor(primary ? R.color.white : R.color.white70);
|
||||||
}
|
}
|
||||||
|
|
||||||
return activity.getPrimaryTextColor();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void displayStatus(ViewHolder viewHolder, Message message) {
|
private void displayStatus(ViewHolder viewHolder, Message message, int type) {
|
||||||
String filesize = null;
|
String filesize = null;
|
||||||
String info = null;
|
String info = null;
|
||||||
boolean error = false;
|
boolean error = false;
|
||||||
|
@ -163,24 +159,37 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (error) {
|
if (error && type == SENT) {
|
||||||
viewHolder.time.setTextColor(activity.getWarningTextColor());
|
viewHolder.time.setTextColor(activity.getWarningTextColor());
|
||||||
} else {
|
} else {
|
||||||
viewHolder.time.setTextColor(this.getMessageTextColor(message));
|
viewHolder.time.setTextColor(this.getMessageTextColor(type,false));
|
||||||
}
|
}
|
||||||
if (message.getEncryption() == Message.ENCRYPTION_NONE) {
|
if (message.getEncryption() == Message.ENCRYPTION_NONE) {
|
||||||
viewHolder.indicator.setVisibility(View.GONE);
|
viewHolder.indicator.setVisibility(View.GONE);
|
||||||
} else {
|
} else {
|
||||||
viewHolder.indicator.setVisibility(View.VISIBLE);
|
viewHolder.indicator.setVisibility(View.VISIBLE);
|
||||||
if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) {
|
if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) {
|
||||||
AxolotlService.SQLiteAxolotlStore.Trust trust = message.getConversation()
|
XmppAxolotlSession.Trust trust = message.getConversation()
|
||||||
.getAccount().getAxolotlService().getFingerprintTrust(
|
.getAccount().getAxolotlService().getFingerprintTrust(
|
||||||
message.getAxolotlFingerprint());
|
message.getAxolotlFingerprint());
|
||||||
|
|
||||||
if(trust == null || trust != AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED) {
|
if(trust == null || trust != XmppAxolotlSession.Trust.TRUSTED) {
|
||||||
viewHolder.indicator.setColorFilter(Color.RED);
|
viewHolder.indicator.setColorFilter(activity.getWarningTextColor());
|
||||||
|
viewHolder.indicator.setAlpha(1.0f);
|
||||||
} else {
|
} else {
|
||||||
viewHolder.indicator.clearColorFilter();
|
viewHolder.indicator.clearColorFilter();
|
||||||
|
if (type == SENT) {
|
||||||
|
viewHolder.indicator.setAlpha(0.57f);
|
||||||
|
} else {
|
||||||
|
viewHolder.indicator.setAlpha(0.7f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
viewHolder.indicator.clearColorFilter();
|
||||||
|
if (type == SENT) {
|
||||||
|
viewHolder.indicator.setAlpha(0.57f);
|
||||||
|
} else {
|
||||||
|
viewHolder.indicator.setAlpha(0.7f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -214,19 +223,19 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void displayInfoMessage(ViewHolder viewHolder, String text) {
|
private void displayInfoMessage(ViewHolder viewHolder, String text, int type) {
|
||||||
if (viewHolder.download_button != null) {
|
if (viewHolder.download_button != null) {
|
||||||
viewHolder.download_button.setVisibility(View.GONE);
|
viewHolder.download_button.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
viewHolder.image.setVisibility(View.GONE);
|
viewHolder.image.setVisibility(View.GONE);
|
||||||
viewHolder.messageBody.setVisibility(View.VISIBLE);
|
viewHolder.messageBody.setVisibility(View.VISIBLE);
|
||||||
viewHolder.messageBody.setText(text);
|
viewHolder.messageBody.setText(text);
|
||||||
viewHolder.messageBody.setTextColor(activity.getSecondaryTextColor());
|
viewHolder.messageBody.setTextColor(getMessageTextColor(type,false));
|
||||||
viewHolder.messageBody.setTypeface(null, Typeface.ITALIC);
|
viewHolder.messageBody.setTypeface(null, Typeface.ITALIC);
|
||||||
viewHolder.messageBody.setTextIsSelectable(false);
|
viewHolder.messageBody.setTextIsSelectable(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void displayDecryptionFailed(ViewHolder viewHolder) {
|
private void displayDecryptionFailed(ViewHolder viewHolder, int type) {
|
||||||
if (viewHolder.download_button != null) {
|
if (viewHolder.download_button != null) {
|
||||||
viewHolder.download_button.setVisibility(View.GONE);
|
viewHolder.download_button.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
@ -234,7 +243,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
viewHolder.messageBody.setVisibility(View.VISIBLE);
|
viewHolder.messageBody.setVisibility(View.VISIBLE);
|
||||||
viewHolder.messageBody.setText(getContext().getString(
|
viewHolder.messageBody.setText(getContext().getString(
|
||||||
R.string.decryption_failed));
|
R.string.decryption_failed));
|
||||||
viewHolder.messageBody.setTextColor(activity.getWarningTextColor());
|
viewHolder.messageBody.setTextColor(getMessageTextColor(type,false));
|
||||||
viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
|
viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
|
||||||
viewHolder.messageBody.setTextIsSelectable(false);
|
viewHolder.messageBody.setTextIsSelectable(false);
|
||||||
}
|
}
|
||||||
|
@ -252,7 +261,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
viewHolder.messageBody.setText(span);
|
viewHolder.messageBody.setText(span);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void displayTextMessage(final ViewHolder viewHolder, final Message message) {
|
private void displayTextMessage(final ViewHolder viewHolder, final Message message, int type) {
|
||||||
if (viewHolder.download_button != null) {
|
if (viewHolder.download_button != null) {
|
||||||
viewHolder.download_button.setVisibility(View.GONE);
|
viewHolder.download_button.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
@ -310,7 +319,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
} else {
|
} else {
|
||||||
viewHolder.messageBody.setText("");
|
viewHolder.messageBody.setText("");
|
||||||
}
|
}
|
||||||
viewHolder.messageBody.setTextColor(this.getMessageTextColor(message));
|
viewHolder.messageBody.setTextColor(this.getMessageTextColor(type,true));
|
||||||
viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
|
viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
|
||||||
viewHolder.messageBody.setTextIsSelectable(true);
|
viewHolder.messageBody.setTextIsSelectable(true);
|
||||||
}
|
}
|
||||||
|
@ -519,7 +528,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
} else if (transferable.getStatus() == Transferable.STATUS_OFFER_CHECK_FILESIZE) {
|
} else if (transferable.getStatus() == Transferable.STATUS_OFFER_CHECK_FILESIZE) {
|
||||||
displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message)));
|
displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message)));
|
||||||
} else {
|
} else {
|
||||||
displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first);
|
displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first,type);
|
||||||
}
|
}
|
||||||
} else if (message.getType() == Message.TYPE_IMAGE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
|
} else if (message.getType() == Message.TYPE_IMAGE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
|
||||||
displayImageMessage(viewHolder, message);
|
displayImageMessage(viewHolder, message);
|
||||||
|
@ -531,10 +540,9 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
}
|
}
|
||||||
} else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
|
} else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
|
||||||
if (activity.hasPgp()) {
|
if (activity.hasPgp()) {
|
||||||
displayInfoMessage(viewHolder,activity.getString(R.string.encrypted_message));
|
displayInfoMessage(viewHolder,activity.getString(R.string.encrypted_message),type);
|
||||||
} else {
|
} else {
|
||||||
displayInfoMessage(viewHolder,
|
displayInfoMessage(viewHolder,activity.getString(R.string.install_openkeychain),type);
|
||||||
activity.getString(R.string.install_openkeychain));
|
|
||||||
if (viewHolder != null) {
|
if (viewHolder != null) {
|
||||||
viewHolder.message_box
|
viewHolder.message_box
|
||||||
.setOnClickListener(new OnClickListener() {
|
.setOnClickListener(new OnClickListener() {
|
||||||
|
@ -547,7 +555,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
|
} else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
|
||||||
displayDecryptionFailed(viewHolder);
|
displayDecryptionFailed(viewHolder,type);
|
||||||
} else {
|
} else {
|
||||||
if (GeoHelper.isGeoUri(message.getBody())) {
|
if (GeoHelper.isGeoUri(message.getBody())) {
|
||||||
displayLocationMessage(viewHolder,message);
|
displayLocationMessage(viewHolder,message);
|
||||||
|
@ -556,11 +564,19 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
} else if (message.treatAsDownloadable() == Message.Decision.MUST) {
|
} else if (message.treatAsDownloadable() == Message.Decision.MUST) {
|
||||||
displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message)));
|
displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message)));
|
||||||
} else {
|
} else {
|
||||||
displayTextMessage(viewHolder, message);
|
displayTextMessage(viewHolder, message, type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
displayStatus(viewHolder, message);
|
if (type == RECEIVED) {
|
||||||
|
if(message.isValidInSession()) {
|
||||||
|
viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received);
|
||||||
|
} else {
|
||||||
|
viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received_warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
displayStatus(viewHolder, message, type);
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,17 +38,14 @@ public class DNSHelper {
|
||||||
public static Bundle getSRVRecord(final Jid jid) throws IOException {
|
public static Bundle getSRVRecord(final Jid jid) throws IOException {
|
||||||
final String host = jid.getDomainpart();
|
final String host = jid.getDomainpart();
|
||||||
String dns[] = client.findDNS();
|
String dns[] = client.findDNS();
|
||||||
|
for (int i = 0; i < dns.length; ++i) {
|
||||||
if (dns != null) {
|
InetAddress ip = InetAddress.getByName(dns[i]);
|
||||||
for (String dnsserver : dns) {
|
Bundle b = queryDNS(host, ip);
|
||||||
InetAddress ip = InetAddress.getByName(dnsserver);
|
if (b.containsKey("values") || i == dns.length - 1) {
|
||||||
Bundle b = queryDNS(host, ip);
|
return b;
|
||||||
if (b.containsKey("values")) {
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return queryDNS(host, InetAddress.getByName("8.8.8.8"));
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Bundle queryDNS(String host, InetAddress dnsServer) {
|
public static Bundle queryDNS(String host, InetAddress dnsServer) {
|
||||||
|
@ -132,7 +129,6 @@ public class DNSHelper {
|
||||||
} catch (SocketTimeoutException e) {
|
} catch (SocketTimeoutException e) {
|
||||||
bundle.putString("error", "timeout");
|
bundle.putString("error", "timeout");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
|
||||||
bundle.putString("error", "unhandled");
|
bundle.putString("error", "unhandled");
|
||||||
}
|
}
|
||||||
return bundle;
|
return bundle;
|
||||||
|
|
|
@ -164,6 +164,9 @@ public class XmppConnection implements Runnable {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
final Bundle result = DNSHelper.getSRVRecord(account.getServer());
|
final Bundle result = DNSHelper.getSRVRecord(account.getServer());
|
||||||
|
if (result == null) {
|
||||||
|
throw new IOException("unhandled exception in DNS resolver");
|
||||||
|
}
|
||||||
final ArrayList<Parcelable> values = result.getParcelableArrayList("values");
|
final ArrayList<Parcelable> values = result.getParcelableArrayList("values");
|
||||||
if ("timeout".equals(result.getString("error"))) {
|
if ("timeout".equals(result.getString("error"))) {
|
||||||
throw new IOException("timeout in dns");
|
throw new IOException("timeout in dns");
|
||||||
|
|
|
@ -2,9 +2,11 @@ package eu.siacs.conversations.xmpp.jingle;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.SystemClock;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
@ -14,13 +16,19 @@ import java.util.Map.Entry;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
|
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
||||||
|
import eu.siacs.conversations.crypto.axolotl.OnMessageCreatedCallback;
|
||||||
|
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.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;
|
||||||
import eu.siacs.conversations.entities.Message;
|
import eu.siacs.conversations.entities.Message;
|
||||||
import eu.siacs.conversations.entities.Transferable;
|
import eu.siacs.conversations.entities.Transferable;
|
||||||
import eu.siacs.conversations.entities.TransferablePlaceholder;
|
import eu.siacs.conversations.entities.TransferablePlaceholder;
|
||||||
|
import eu.siacs.conversations.persistance.FileBackend;
|
||||||
|
import eu.siacs.conversations.services.AbstractConnectionManager;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
|
import eu.siacs.conversations.utils.Xmlns;
|
||||||
import eu.siacs.conversations.xml.Element;
|
import eu.siacs.conversations.xml.Element;
|
||||||
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
|
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
|
||||||
import eu.siacs.conversations.xmpp.jid.Jid;
|
import eu.siacs.conversations.xmpp.jid.Jid;
|
||||||
|
@ -66,8 +74,13 @@ public class JingleConnection implements Transferable {
|
||||||
|
|
||||||
private boolean acceptedAutomatically = false;
|
private boolean acceptedAutomatically = false;
|
||||||
|
|
||||||
|
private XmppAxolotlMessage mXmppAxolotlMessage;
|
||||||
|
|
||||||
private JingleTransport transport = null;
|
private JingleTransport transport = null;
|
||||||
|
|
||||||
|
private OutputStream mFileOutputStream;
|
||||||
|
private InputStream mFileInputStream;
|
||||||
|
|
||||||
private OnIqPacketReceived responseListener = new OnIqPacketReceived() {
|
private OnIqPacketReceived responseListener = new OnIqPacketReceived() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -113,6 +126,14 @@ public class JingleConnection implements Transferable {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public InputStream getFileInputStream() {
|
||||||
|
return this.mFileInputStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OutputStream getFileOutputStream() {
|
||||||
|
return this.mFileOutputStream;
|
||||||
|
}
|
||||||
|
|
||||||
private OnProxyActivated onProxyActivated = new OnProxyActivated() {
|
private OnProxyActivated onProxyActivated = new OnProxyActivated() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -194,7 +215,22 @@ public class JingleConnection implements Transferable {
|
||||||
mXmppConnectionService.sendIqPacket(account,response,null);
|
mXmppConnectionService.sendIqPacket(account,response,null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init(Message message) {
|
public void init(final Message message) {
|
||||||
|
if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) {
|
||||||
|
Conversation conversation = message.getConversation();
|
||||||
|
conversation.getAccount().getAxolotlService().prepareKeyTransportMessage(conversation.getContact(), new OnMessageCreatedCallback() {
|
||||||
|
@Override
|
||||||
|
public void run(XmppAxolotlMessage xmppAxolotlMessage) {
|
||||||
|
init(message, xmppAxolotlMessage);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
init(message, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void init(Message message, XmppAxolotlMessage xmppAxolotlMessage) {
|
||||||
|
this.mXmppAxolotlMessage = xmppAxolotlMessage;
|
||||||
this.contentCreator = "initiator";
|
this.contentCreator = "initiator";
|
||||||
this.contentName = this.mJingleConnectionManager.nextRandomId();
|
this.contentName = this.mJingleConnectionManager.nextRandomId();
|
||||||
this.message = message;
|
this.message = message;
|
||||||
|
@ -238,8 +274,7 @@ public class JingleConnection implements Transferable {
|
||||||
});
|
});
|
||||||
mergeCandidate(candidate);
|
mergeCandidate(candidate);
|
||||||
} else {
|
} else {
|
||||||
Log.d(Config.LOGTAG,
|
Log.d(Config.LOGTAG,"no primary candidate of our own was found");
|
||||||
"no primary candidate of our own was found");
|
|
||||||
sendInitRequest();
|
sendInitRequest();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -267,13 +302,16 @@ public class JingleConnection implements Transferable {
|
||||||
this.contentCreator = content.getAttribute("creator");
|
this.contentCreator = content.getAttribute("creator");
|
||||||
this.contentName = content.getAttribute("name");
|
this.contentName = content.getAttribute("name");
|
||||||
this.transportId = content.getTransportId();
|
this.transportId = content.getTransportId();
|
||||||
this.mergeCandidates(JingleCandidate.parse(content.socks5transport()
|
this.mergeCandidates(JingleCandidate.parse(content.socks5transport().getChildren()));
|
||||||
.getChildren()));
|
|
||||||
this.fileOffer = packet.getJingleContent().getFileOffer();
|
this.fileOffer = packet.getJingleContent().getFileOffer();
|
||||||
|
|
||||||
mXmppConnectionService.sendIqPacket(account,packet.generateResponse(IqPacket.TYPE.RESULT),null);
|
mXmppConnectionService.sendIqPacket(account,packet.generateResponse(IqPacket.TYPE.RESULT),null);
|
||||||
|
|
||||||
if (fileOffer != null) {
|
if (fileOffer != null) {
|
||||||
|
Element encrypted = fileOffer.findChild("encrypted", AxolotlService.PEP_PREFIX);
|
||||||
|
if (encrypted != null) {
|
||||||
|
this.mXmppAxolotlMessage = XmppAxolotlMessage.fromElement(encrypted, packet.getFrom().toBareJid());
|
||||||
|
}
|
||||||
Element fileSize = fileOffer.findChild("size");
|
Element fileSize = fileOffer.findChild("size");
|
||||||
Element fileNameElement = fileOffer.findChild("name");
|
Element fileNameElement = fileOffer.findChild("name");
|
||||||
if (fileNameElement != null) {
|
if (fileNameElement != null) {
|
||||||
|
@ -319,10 +357,8 @@ public class JingleConnection implements Transferable {
|
||||||
message.setBody(Long.toString(size));
|
message.setBody(Long.toString(size));
|
||||||
conversation.add(message);
|
conversation.add(message);
|
||||||
mXmppConnectionService.updateConversationUi();
|
mXmppConnectionService.updateConversationUi();
|
||||||
if (size < this.mJingleConnectionManager
|
if (size < this.mJingleConnectionManager.getAutoAcceptFileSize()) {
|
||||||
.getAutoAcceptFileSize()) {
|
Log.d(Config.LOGTAG, "auto accepting file from "+ packet.getFrom());
|
||||||
Log.d(Config.LOGTAG, "auto accepting file from "
|
|
||||||
+ packet.getFrom());
|
|
||||||
this.acceptedAutomatically = true;
|
this.acceptedAutomatically = true;
|
||||||
this.sendAccept();
|
this.sendAccept();
|
||||||
} else {
|
} else {
|
||||||
|
@ -333,22 +369,32 @@ public class JingleConnection implements Transferable {
|
||||||
+ " allowed size:"
|
+ " allowed size:"
|
||||||
+ this.mJingleConnectionManager
|
+ this.mJingleConnectionManager
|
||||||
.getAutoAcceptFileSize());
|
.getAutoAcceptFileSize());
|
||||||
this.mXmppConnectionService.getNotificationService()
|
this.mXmppConnectionService.getNotificationService().push(message);
|
||||||
.push(message);
|
|
||||||
}
|
}
|
||||||
this.file = this.mXmppConnectionService.getFileBackend()
|
this.file = this.mXmppConnectionService.getFileBackend().getFile(message, false);
|
||||||
.getFile(message, false);
|
if (mXmppAxolotlMessage != null) {
|
||||||
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
|
XmppAxolotlMessage.XmppAxolotlKeyTransportMessage transportMessage = account.getAxolotlService().processReceivingKeyTransportMessage(mXmppAxolotlMessage);
|
||||||
|
if (transportMessage != null) {
|
||||||
|
message.setEncryption(Message.ENCRYPTION_AXOLOTL);
|
||||||
|
this.file.setKey(transportMessage.getKey());
|
||||||
|
this.file.setIv(transportMessage.getIv());
|
||||||
|
message.setAxolotlFingerprint(transportMessage.getFingerprint());
|
||||||
|
} else {
|
||||||
|
Log.d(Config.LOGTAG,"could not process KeyTransportMessage");
|
||||||
|
}
|
||||||
|
} else if (message.getEncryption() == Message.ENCRYPTION_OTR) {
|
||||||
byte[] key = conversation.getSymmetricKey();
|
byte[] key = conversation.getSymmetricKey();
|
||||||
if (key == null) {
|
if (key == null) {
|
||||||
this.sendCancel();
|
this.sendCancel();
|
||||||
this.fail();
|
this.fail();
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
this.file.setKey(key);
|
this.file.setKeyAndIv(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.mFileOutputStream = AbstractConnectionManager.createOutputStream(this.file,message.getEncryption() == Message.ENCRYPTION_AXOLOTL);
|
||||||
this.file.setExpectedSize(size);
|
this.file.setExpectedSize(size);
|
||||||
|
Log.d(Config.LOGTAG, "receiving file: expecting size of " + this.file.getExpectedSize());
|
||||||
} else {
|
} else {
|
||||||
this.sendCancel();
|
this.sendCancel();
|
||||||
this.fail();
|
this.fail();
|
||||||
|
@ -364,19 +410,30 @@ public class JingleConnection implements Transferable {
|
||||||
Content content = new Content(this.contentCreator, this.contentName);
|
Content content = new Content(this.contentCreator, this.contentName);
|
||||||
if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
|
if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) {
|
||||||
content.setTransportId(this.transportId);
|
content.setTransportId(this.transportId);
|
||||||
this.file = this.mXmppConnectionService.getFileBackend().getFile(
|
this.file = this.mXmppConnectionService.getFileBackend().getFile(message, false);
|
||||||
message, false);
|
Pair<InputStream,Integer> pair;
|
||||||
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
|
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
|
||||||
Conversation conversation = this.message.getConversation();
|
Conversation conversation = this.message.getConversation();
|
||||||
if (!this.mXmppConnectionService.renewSymmetricKey(conversation)) {
|
if (!this.mXmppConnectionService.renewSymmetricKey(conversation)) {
|
||||||
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not set symmetric key");
|
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not set symmetric key");
|
||||||
cancel();
|
cancel();
|
||||||
}
|
}
|
||||||
|
this.file.setKeyAndIv(conversation.getSymmetricKey());
|
||||||
|
pair = AbstractConnectionManager.createInputStream(this.file,false);
|
||||||
|
this.file.setExpectedSize(pair.second);
|
||||||
content.setFileOffer(this.file, true);
|
content.setFileOffer(this.file, true);
|
||||||
this.file.setKey(conversation.getSymmetricKey());
|
} else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) {
|
||||||
|
this.file.setKey(mXmppAxolotlMessage.getInnerKey());
|
||||||
|
this.file.setIv(mXmppAxolotlMessage.getIV());
|
||||||
|
pair = AbstractConnectionManager.createInputStream(this.file,true);
|
||||||
|
this.file.setExpectedSize(pair.second);
|
||||||
|
content.setFileOffer(this.file, false).addChild(mXmppAxolotlMessage.toElement());
|
||||||
} else {
|
} else {
|
||||||
|
pair = AbstractConnectionManager.createInputStream(this.file,false);
|
||||||
|
this.file.setExpectedSize(pair.second);
|
||||||
content.setFileOffer(this.file, false);
|
content.setFileOffer(this.file, false);
|
||||||
}
|
}
|
||||||
|
this.mFileInputStream = pair.first;
|
||||||
this.transportId = this.mJingleConnectionManager.nextRandomId();
|
this.transportId = this.mJingleConnectionManager.nextRandomId();
|
||||||
content.setTransportId(this.transportId);
|
content.setTransportId(this.transportId);
|
||||||
content.socks5transport().setChildren(getCandidatesAsElements());
|
content.socks5transport().setChildren(getCandidatesAsElements());
|
||||||
|
@ -748,6 +805,8 @@ public class JingleConnection implements Transferable {
|
||||||
if (this.transport != null && this.transport instanceof JingleInbandTransport) {
|
if (this.transport != null && this.transport instanceof JingleInbandTransport) {
|
||||||
this.transport.disconnect();
|
this.transport.disconnect();
|
||||||
}
|
}
|
||||||
|
FileBackend.close(mFileInputStream);
|
||||||
|
FileBackend.close(mFileOutputStream);
|
||||||
if (this.message != null) {
|
if (this.message != null) {
|
||||||
if (this.responder.equals(account.getJid())) {
|
if (this.responder.equals(account.getJid())) {
|
||||||
this.message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_FAILED));
|
this.message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_FAILED));
|
||||||
|
|
|
@ -93,7 +93,7 @@ public class JingleInbandTransport extends JingleTransport {
|
||||||
digest.reset();
|
digest.reset();
|
||||||
file.getParentFile().mkdirs();
|
file.getParentFile().mkdirs();
|
||||||
file.createNewFile();
|
file.createNewFile();
|
||||||
this.fileOutputStream = file.createOutputStream();
|
this.fileOutputStream = connection.getFileOutputStream();
|
||||||
if (this.fileOutputStream == null) {
|
if (this.fileOutputStream == null) {
|
||||||
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not create output stream");
|
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not create output stream");
|
||||||
callback.onFileTransferAborted();
|
callback.onFileTransferAborted();
|
||||||
|
@ -112,15 +112,11 @@ public class JingleInbandTransport extends JingleTransport {
|
||||||
this.onFileTransmissionStatusChanged = callback;
|
this.onFileTransmissionStatusChanged = callback;
|
||||||
this.file = file;
|
this.file = file;
|
||||||
try {
|
try {
|
||||||
if (this.file.getKey() != null) {
|
this.remainingSize = this.file.getExpectedSize();
|
||||||
this.remainingSize = (this.file.getSize() / 16 + 1) * 16;
|
|
||||||
} else {
|
|
||||||
this.remainingSize = this.file.getSize();
|
|
||||||
}
|
|
||||||
this.fileSize = this.remainingSize;
|
this.fileSize = this.remainingSize;
|
||||||
this.digest = MessageDigest.getInstance("SHA-1");
|
this.digest = MessageDigest.getInstance("SHA-1");
|
||||||
this.digest.reset();
|
this.digest.reset();
|
||||||
fileInputStream = this.file.createInputStream();
|
fileInputStream = connection.getFileInputStream();
|
||||||
if (fileInputStream == null) {
|
if (fileInputStream == null) {
|
||||||
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could no create input stream");
|
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could no create input stream");
|
||||||
callback.onFileTransferAborted();
|
callback.onFileTransferAborted();
|
||||||
|
|
|
@ -106,13 +106,13 @@ public class JingleSocks5Transport extends JingleTransport {
|
||||||
try {
|
try {
|
||||||
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||||
digest.reset();
|
digest.reset();
|
||||||
fileInputStream = file.createInputStream();
|
fileInputStream = connection.getFileInputStream();
|
||||||
if (fileInputStream == null) {
|
if (fileInputStream == null) {
|
||||||
Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create input stream");
|
Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create input stream");
|
||||||
callback.onFileTransferAborted();
|
callback.onFileTransferAborted();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
long size = file.getSize();
|
long size = file.getExpectedSize();
|
||||||
long transmitted = 0;
|
long transmitted = 0;
|
||||||
int count;
|
int count;
|
||||||
byte[] buffer = new byte[8192];
|
byte[] buffer = new byte[8192];
|
||||||
|
@ -157,7 +157,7 @@ public class JingleSocks5Transport extends JingleTransport {
|
||||||
socket.setSoTimeout(30000);
|
socket.setSoTimeout(30000);
|
||||||
file.getParentFile().mkdirs();
|
file.getParentFile().mkdirs();
|
||||||
file.createNewFile();
|
file.createNewFile();
|
||||||
fileOutputStream = file.createOutputStream();
|
fileOutputStream = connection.getFileOutputStream();
|
||||||
if (fileOutputStream == null) {
|
if (fileOutputStream == null) {
|
||||||
callback.onFileTransferAborted();
|
callback.onFileTransferAborted();
|
||||||
Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create output stream");
|
Log.d(Config.LOGTAG, connection.getAccount().getJid().toBareJid() + ": could not create output stream");
|
||||||
|
|
|
@ -1,5 +1,31 @@
|
||||||
package eu.siacs.conversations.xmpp.jingle;
|
package eu.siacs.conversations.xmpp.jingle;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
|
import org.bouncycastle.crypto.engines.AESEngine;
|
||||||
|
import org.bouncycastle.crypto.modes.AEADBlockCipher;
|
||||||
|
import org.bouncycastle.crypto.modes.GCMBlockCipher;
|
||||||
|
import org.bouncycastle.crypto.params.AEADParameters;
|
||||||
|
import org.bouncycastle.crypto.params.KeyParameter;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.security.InvalidAlgorithmParameterException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.CipherInputStream;
|
||||||
|
import javax.crypto.CipherOutputStream;
|
||||||
|
import javax.crypto.NoSuchPaddingException;
|
||||||
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.entities.DownloadableFile;
|
import eu.siacs.conversations.entities.DownloadableFile;
|
||||||
|
|
||||||
public abstract class JingleTransport {
|
public abstract class JingleTransport {
|
||||||
|
|
|
@ -25,17 +25,18 @@ public class Content extends Element {
|
||||||
this.transportId = sid;
|
this.transportId = sid;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFileOffer(DownloadableFile actualFile, boolean otr) {
|
public Element setFileOffer(DownloadableFile actualFile, boolean otr) {
|
||||||
Element description = this.addChild("description",
|
Element description = this.addChild("description",
|
||||||
"urn:xmpp:jingle:apps:file-transfer:3");
|
"urn:xmpp:jingle:apps:file-transfer:3");
|
||||||
Element offer = description.addChild("offer");
|
Element offer = description.addChild("offer");
|
||||||
Element file = offer.addChild("file");
|
Element file = offer.addChild("file");
|
||||||
file.addChild("size").setContent(Long.toString(actualFile.getSize()));
|
file.addChild("size").setContent(Long.toString(actualFile.getExpectedSize()));
|
||||||
if (otr) {
|
if (otr) {
|
||||||
file.addChild("name").setContent(actualFile.getName() + ".otr");
|
file.addChild("name").setContent(actualFile.getName() + ".otr");
|
||||||
} else {
|
} else {
|
||||||
file.addChild("name").setContent(actualFile.getName());
|
file.addChild("name").setContent(actualFile.getName());
|
||||||
}
|
}
|
||||||
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Element getFileOffer() {
|
public Element getFileOffer() {
|
||||||
|
|
|
@ -39,6 +39,9 @@ public class IqPacket extends AbstractStanza {
|
||||||
|
|
||||||
public TYPE getType() {
|
public TYPE getType() {
|
||||||
final String type = getAttribute("type");
|
final String type = getAttribute("type");
|
||||||
|
if (type == null) {
|
||||||
|
return TYPE.INVALID;
|
||||||
|
}
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case "error":
|
case "error":
|
||||||
return TYPE.ERROR;
|
return TYPE.ERROR;
|
||||||
|
|
Before Width: | Height: | Size: 765 B After Width: | Height: | Size: 765 B |
BIN
src/main/res/drawable-hdpi/message_bubble_received_warning.9.png
Normal file
After Width: | Height: | Size: 757 B |
Before Width: | Height: | Size: 687 B After Width: | Height: | Size: 687 B |
Before Width: | Height: | Size: 594 B After Width: | Height: | Size: 594 B |
BIN
src/main/res/drawable-mdpi/message_bubble_received_warning.9.png
Normal file
After Width: | Height: | Size: 598 B |
Before Width: | Height: | Size: 558 B After Width: | Height: | Size: 558 B |
Before Width: | Height: | Size: 929 B After Width: | Height: | Size: 929 B |
After Width: | Height: | Size: 921 B |
Before Width: | Height: | Size: 857 B After Width: | Height: | Size: 857 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
@ -28,6 +28,7 @@
|
||||||
android:textColor="@color/black54"
|
android:textColor="@color/black54"
|
||||||
android:layout_alignParentLeft="true"
|
android:layout_alignParentLeft="true"
|
||||||
android:layout_below="@+id/key"
|
android:layout_below="@+id/key"
|
||||||
|
android:maxLines="1"
|
||||||
android:textSize="?attr/TextSizeInfo"/>
|
android:textSize="?attr/TextSizeInfo"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
|
|
@ -91,7 +91,6 @@
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:text="@string/sending"
|
android:text="@string/sending"
|
||||||
android:textColor="@color/white70"
|
android:textColor="@color/white70"
|
||||||
android:alpha="0.70"
|
|
||||||
android:textSize="?attr/TextSizeInfo" />
|
android:textSize="?attr/TextSizeInfo" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
|
@ -81,7 +81,6 @@
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:text="@string/sending"
|
android:text="@string/sending"
|
||||||
android:textColor="@color/black54"
|
android:textColor="@color/black54"
|
||||||
android:alpha="0.54"
|
|
||||||
android:textSize="?attr/TextSizeInfo" />
|
android:textSize="?attr/TextSizeInfo" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
|
|
|
@ -13,5 +13,6 @@
|
||||||
<color name="grey200">#ffeeeeee</color>
|
<color name="grey200">#ffeeeeee</color>
|
||||||
<color name="grey800">#ff424242</color>
|
<color name="grey800">#ff424242</color>
|
||||||
<color name="red500">#fff44336</color>
|
<color name="red500">#fff44336</color>
|
||||||
|
<color name="red800">#ffc62828</color>
|
||||||
<color name="orange500">#ffff9800</color>
|
<color name="orange500">#ffff9800</color>
|
||||||
</resources>
|
</resources>
|
|
@ -80,7 +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_axolotl_message">Send Multi-End 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>
|
||||||
|
@ -155,7 +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="encryption_choice_axolotl">Multi-End</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>
|
||||||
|
@ -208,13 +208,14 @@
|
||||||
<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_fingerprint">Multi-End fingerprint</string>
|
||||||
<string name="this_device_axolotl_fingerprint">Own Axolotl fingerprint</string>
|
<string name="axolotl_fingerprint_selected_message">Multi-End fingerprint of message</string>
|
||||||
|
<string name="this_device_axolotl_fingerprint">Own Multi-End fingerprint</string>
|
||||||
<string name="other_devices">Other devices</string>
|
<string name="other_devices">Other devices</string>
|
||||||
<string name="trust_keys">Trust Axolotl Keys</string>
|
<string name="trust_keys">Trust Multi-End Keys</string>
|
||||||
<string name="fetching_keys">Fetching keys...</string>
|
<string name="fetching_keys">Fetching keys...</string>
|
||||||
<string name="done">Done</string>
|
<string name="done">Done</string>
|
||||||
<string name="axolotl_devicelist">Other own Axolotl Devices</string>
|
<string name="axolotl_devicelist">Other own Multi-End 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>
|
||||||
|
@ -321,7 +322,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="toast_message_axolotl_fingerprint">Multi-End 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>
|
||||||
|
@ -388,11 +389,11 @@
|
||||||
<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="copy_axolotl_clipboard_description">Copy Multi-End fingerprint to clipboard</string>
|
||||||
<string name="regenerate_axolotl_key">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="wipe_axolotl_pep">Wipe other devices from PEP</string>
|
||||||
<string name="clear_other_devices">Clear devices</string>
|
<string name="clear_other_devices">Clear devices</string>
|
||||||
<string name="clear_other_devices_desc">Are you sure you want to clear all other devices from the axolotl announcement? The next time your devices connect, they will reannounce themselves, but they might not receive messages sent in the meantime.</string>
|
<string name="clear_other_devices_desc">Are you sure you want to clear all other devices from the Multi-End announcement? The next time your devices connect, they will reannounce themselves, but they might not receive messages sent in the meantime.</string>
|
||||||
<string name="purge_key">Purge key</string>
|
<string name="purge_key">Purge key</string>
|
||||||
<string name="purge_key_desc_part1">Are you sure you want to purge this key?</string>
|
<string name="purge_key_desc_part1">Are you sure you want to purge this key?</string>
|
||||||
<string name="purge_key_desc_part2">It will irreversibly be considered compromised, and you can never build a session with it again.</string>
|
<string name="purge_key_desc_part2">It will irreversibly be considered compromised, and you can never build a session with it again.</string>
|
||||||
|
@ -473,7 +474,6 @@
|
||||||
<string name="received_location">Received location</string>
|
<string name="received_location">Received location</string>
|
||||||
<string name="title_undo_swipe_out_conversation">Conversation closed</string>
|
<string name="title_undo_swipe_out_conversation">Conversation closed</string>
|
||||||
<string name="title_undo_swipe_out_muc">Left conference</string>
|
<string name="title_undo_swipe_out_muc">Left conference</string>
|
||||||
<string name="pref_certificate_options">Certificate options</string>
|
|
||||||
<string name="pref_dont_trust_system_cas_title">Don’t trust system CAs</string>
|
<string name="pref_dont_trust_system_cas_title">Don’t trust system CAs</string>
|
||||||
<string name="pref_dont_trust_system_cas_summary">All certificates must be manually approved</string>
|
<string name="pref_dont_trust_system_cas_summary">All certificates must be manually approved</string>
|
||||||
<string name="pref_remove_trusted_certificates_title">Remove certificates</string>
|
<string name="pref_remove_trusted_certificates_title">Remove certificates</string>
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
|
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
<PreferenceCategory android:title="@string/pref_general" >
|
<PreferenceCategory android:title="@string/pref_general">
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
android:key="grant_new_contacts"
|
android:key="grant_new_contacts"
|
||||||
android:summary="@string/pref_grant_presence_updates_summary"
|
android:summary="@string/pref_grant_presence_updates_summary"
|
||||||
android:title="@string/pref_grant_presence_updates" />
|
android:title="@string/pref_grant_presence_updates"/>
|
||||||
|
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:defaultValue="Mobile"
|
android:defaultValue="Mobile"
|
||||||
|
@ -14,65 +14,65 @@
|
||||||
android:entryValues="@array/resources"
|
android:entryValues="@array/resources"
|
||||||
android:key="resource"
|
android:key="resource"
|
||||||
android:summary="@string/pref_xmpp_resource_summary"
|
android:summary="@string/pref_xmpp_resource_summary"
|
||||||
android:title="@string/pref_xmpp_resource" />
|
android:title="@string/pref_xmpp_resource"/>
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:defaultValue="1048576"
|
android:defaultValue="1048576"
|
||||||
android:entries="@array/filesizes"
|
android:entries="@array/filesizes"
|
||||||
android:entryValues="@array/filesizes_values"
|
android:entryValues="@array/filesizes_values"
|
||||||
android:key="auto_accept_file_size"
|
android:key="auto_accept_file_size"
|
||||||
android:summary="@string/pref_accept_files_summary"
|
android:summary="@string/pref_accept_files_summary"
|
||||||
android:title="@string/pref_accept_files" />
|
android:title="@string/pref_accept_files"/>
|
||||||
|
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
android:key="confirm_messages"
|
android:key="confirm_messages"
|
||||||
android:summary="@string/pref_confirm_messages_summary"
|
android:summary="@string/pref_confirm_messages_summary"
|
||||||
android:title="@string/pref_confirm_messages" />
|
android:title="@string/pref_confirm_messages"/>
|
||||||
|
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
android:key="chat_states"
|
android:key="chat_states"
|
||||||
android:summary="@string/pref_chat_states_summary"
|
android:summary="@string/pref_chat_states_summary"
|
||||||
android:title="@string/pref_chat_states" />
|
android:title="@string/pref_chat_states"/>
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
android:title="@string/pref_notification_settings"
|
android:key="notifications"
|
||||||
android:key="notifications">
|
android:title="@string/pref_notification_settings">
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
android:key="show_notification"
|
android:key="show_notification"
|
||||||
android:summary="@string/pref_notifications_summary"
|
android:summary="@string/pref_notifications_summary"
|
||||||
android:title="@string/pref_notifications" />
|
android:title="@string/pref_notifications"/>
|
||||||
<PreferenceScreen
|
<PreferenceScreen
|
||||||
android:dependency="show_notification"
|
android:dependency="show_notification"
|
||||||
|
android:key="quiet_hours"
|
||||||
android:summary="@string/pref_quiet_hours_summary"
|
android:summary="@string/pref_quiet_hours_summary"
|
||||||
android:title="@string/title_pref_quiet_hours"
|
android:title="@string/title_pref_quiet_hours">
|
||||||
android:key="quiet_hours">
|
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:key="enable_quiet_hours"
|
android:key="enable_quiet_hours"
|
||||||
android:summary="@string/pref_quiet_hours_summary"
|
android:summary="@string/pref_quiet_hours_summary"
|
||||||
android:title="@string/title_pref_enable_quiet_hours" />
|
android:title="@string/title_pref_enable_quiet_hours"/>
|
||||||
<eu.siacs.conversations.ui.TimePreference
|
<eu.siacs.conversations.ui.TimePreference
|
||||||
android:dependency="enable_quiet_hours"
|
android:dependency="enable_quiet_hours"
|
||||||
android:key="quiet_hours_start"
|
android:key="quiet_hours_start"
|
||||||
android:negativeButtonText="@string/cancel"
|
android:negativeButtonText="@string/cancel"
|
||||||
android:positiveButtonText="@string/set"
|
android:positiveButtonText="@string/set"
|
||||||
android:title="@string/title_pref_quiet_hours_start_time" />
|
android:title="@string/title_pref_quiet_hours_start_time"/>
|
||||||
<eu.siacs.conversations.ui.TimePreference
|
<eu.siacs.conversations.ui.TimePreference
|
||||||
android:dependency="enable_quiet_hours"
|
android:dependency="enable_quiet_hours"
|
||||||
android:key="quiet_hours_end"
|
android:key="quiet_hours_end"
|
||||||
android:negativeButtonText="@string/cancel"
|
android:negativeButtonText="@string/cancel"
|
||||||
android:positiveButtonText="@string/set"
|
android:positiveButtonText="@string/set"
|
||||||
android:title="@string/title_pref_quiet_hours_end_time" />
|
android:title="@string/title_pref_quiet_hours_end_time"/>
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
android:dependency="show_notification"
|
android:dependency="show_notification"
|
||||||
android:key="vibrate_on_notification"
|
android:key="vibrate_on_notification"
|
||||||
android:summary="@string/pref_vibrate_summary"
|
android:summary="@string/pref_vibrate_summary"
|
||||||
android:title="@string/pref_vibrate" />
|
android:title="@string/pref_vibrate"/>
|
||||||
|
|
||||||
<RingtonePreference
|
<RingtonePreference
|
||||||
android:defaultValue="content://settings/system/notification_sound"
|
android:defaultValue="content://settings/system/notification_sound"
|
||||||
|
@ -80,98 +80,91 @@
|
||||||
android:key="notification_ringtone"
|
android:key="notification_ringtone"
|
||||||
android:ringtoneType="notification"
|
android:ringtoneType="notification"
|
||||||
android:summary="@string/pref_sound_summary"
|
android:summary="@string/pref_sound_summary"
|
||||||
android:title="@string/pref_sound" />
|
android:title="@string/pref_sound"/>
|
||||||
|
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:dependency="show_notification"
|
android:dependency="show_notification"
|
||||||
android:key="always_notify_in_conference"
|
android:key="always_notify_in_conference"
|
||||||
android:summary="@string/pref_conference_notifications_summary"
|
android:summary="@string/pref_conference_notifications_summary"
|
||||||
android:title="@string/pref_conference_notifications" />
|
android:title="@string/pref_conference_notifications"/>
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
<PreferenceCategory android:title="@string/pref_ui_options" >
|
<PreferenceCategory android:title="@string/pref_ui_options">
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
android:key="use_subject"
|
android:key="use_subject"
|
||||||
android:summary="@string/pref_conference_name_summary"
|
android:summary="@string/pref_conference_name_summary"
|
||||||
android:title="@string/pref_conference_name" />
|
android:title="@string/pref_conference_name"/>
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:key="use_larger_font"
|
android:key="use_larger_font"
|
||||||
android:summary="@string/pref_use_larger_font_summary"
|
android:summary="@string/pref_use_larger_font_summary"
|
||||||
android:title="@string/pref_use_larger_font" />
|
android:title="@string/pref_use_larger_font"/>
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
android:key="send_button_status"
|
android:key="send_button_status"
|
||||||
android:summary="@string/pref_use_send_button_to_indicate_status_summary"
|
android:summary="@string/pref_use_send_button_to_indicate_status_summary"
|
||||||
android:title="@string/pref_use_send_button_to_indicate_status" />
|
android:title="@string/pref_use_send_button_to_indicate_status"/>
|
||||||
<ListPreference
|
<ListPreference
|
||||||
android:key="quick_action"
|
|
||||||
android:defaultValue="recent"
|
android:defaultValue="recent"
|
||||||
|
android:dialogTitle="@string/choose_quick_action"
|
||||||
android:entries="@array/quick_actions"
|
android:entries="@array/quick_actions"
|
||||||
android:entryValues="@array/quick_action_values"
|
android:entryValues="@array/quick_action_values"
|
||||||
|
android:key="quick_action"
|
||||||
android:summary="@string/pref_quick_action_summary"
|
android:summary="@string/pref_quick_action_summary"
|
||||||
android:title="@string/pref_quick_action"
|
android:title="@string/pref_quick_action"/>
|
||||||
android:dialogTitle="@string/choose_quick_action"/>
|
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:key="show_dynamic_tags"
|
android:key="show_dynamic_tags"
|
||||||
android:summary="@string/pref_show_dynamic_tags_summary"
|
android:summary="@string/pref_show_dynamic_tags_summary"
|
||||||
android:title="@string/pref_show_dynamic_tags" />
|
android:title="@string/pref_show_dynamic_tags"/>
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
<PreferenceCategory
|
<PreferenceCategory
|
||||||
android:title="@string/pref_advanced_options"
|
android:key="advanced"
|
||||||
android:key="advanced">
|
android:title="@string/pref_advanced_options">
|
||||||
<PreferenceScreen
|
<PreferenceScreen
|
||||||
|
android:key="expert"
|
||||||
android:summary="@string/pref_expert_options_summary"
|
android:summary="@string/pref_expert_options_summary"
|
||||||
android:title="@string/pref_expert_options"
|
android:title="@string/pref_expert_options">
|
||||||
android:key="expert">
|
<PreferenceCategory android:title="@string/pref_encryption_settings">
|
||||||
<PreferenceCategory android:title="@string/pref_encryption_settings" >
|
|
||||||
<CheckBoxPreference
|
|
||||||
android:defaultValue="false"
|
|
||||||
android:key="force_encryption"
|
|
||||||
android:summary="@string/pref_force_encryption_summary"
|
|
||||||
android:title="@string/pref_force_encryption" />
|
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:key="dont_save_encrypted"
|
android:key="dont_save_encrypted"
|
||||||
android:summary="@string/pref_dont_save_encrypted_summary"
|
android:summary="@string/pref_dont_save_encrypted_summary"
|
||||||
android:title="@string/pref_dont_save_encrypted" />
|
android:title="@string/pref_dont_save_encrypted"/>
|
||||||
|
<CheckBoxPreference
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:key="dont_trust_system_cas"
|
||||||
|
android:summary="@string/pref_dont_trust_system_cas_summary"
|
||||||
|
android:title="@string/pref_dont_trust_system_cas_title"/>
|
||||||
|
<Preference
|
||||||
|
android:key="remove_trusted_certificates"
|
||||||
|
android:summary="@string/pref_remove_trusted_certificates_summary"
|
||||||
|
android:title="@string/pref_remove_trusted_certificates_title"/>
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
<PreferenceCategory android:title="@string/pref_input_options">
|
<PreferenceCategory android:title="@string/pref_input_options">
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:key="enter_is_send"
|
android:key="enter_is_send"
|
||||||
android:title="@string/pref_enter_is_send"
|
android:summary="@string/pref_enter_is_send_summary"
|
||||||
android:summary="@string/pref_enter_is_send_summary" />
|
android:title="@string/pref_enter_is_send"/>
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:key="display_enter_key"
|
android:key="display_enter_key"
|
||||||
android:title="@string/pref_display_enter_key"
|
android:summary="@string/pref_display_enter_key_summary"
|
||||||
android:summary="@string/pref_display_enter_key_summary" />
|
android:title="@string/pref_display_enter_key"/>
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
<PreferenceCategory android:title="@string/pref_certificate_options">
|
<PreferenceCategory android:title="@string/pref_expert_options_other">
|
||||||
<CheckBoxPreference
|
|
||||||
android:defaultValue="false"
|
|
||||||
android:key="dont_trust_system_cas"
|
|
||||||
android:title="@string/pref_dont_trust_system_cas_title"
|
|
||||||
android:summary="@string/pref_dont_trust_system_cas_summary" />
|
|
||||||
<Preference
|
|
||||||
android:key="remove_trusted_certificates"
|
|
||||||
android:title="@string/pref_remove_trusted_certificates_title"
|
|
||||||
android:summary="@string/pref_remove_trusted_certificates_summary" />
|
|
||||||
</PreferenceCategory>
|
|
||||||
<PreferenceCategory android:title="@string/pref_expert_options_other" >
|
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
android:key="indicate_received"
|
android:key="indicate_received"
|
||||||
android:summary="@string/pref_use_indicate_received_summary"
|
android:summary="@string/pref_use_indicate_received_summary"
|
||||||
android:title="@string/pref_use_indicate_received" />
|
android:title="@string/pref_use_indicate_received"/>
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
android:key="keep_foreground_service"
|
android:key="keep_foreground_service"
|
||||||
android:title="@string/pref_keep_foreground_service"
|
android:summary="@string/pref_keep_foreground_service_summary"
|
||||||
android:summary="@string/pref_keep_foreground_service_summary" />
|
android:title="@string/pref_keep_foreground_service"/>
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|
||||||
|
@ -179,9 +172,9 @@
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
android:key="never_send"
|
android:key="never_send"
|
||||||
android:summary="@string/pref_never_send_crash_summary"
|
android:summary="@string/pref_never_send_crash_summary"
|
||||||
android:title="@string/pref_never_send_crash" />
|
android:title="@string/pref_never_send_crash"/>
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
<eu.siacs.conversations.ui.AboutPreference
|
<eu.siacs.conversations.ui.AboutPreference
|
||||||
android:summary="@string/pref_about_conversations_summary"
|
android:summary="@string/pref_about_conversations_summary"
|
||||||
android:title="@string/title_activity_about" />
|
android:title="@string/title_activity_about"/>
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
|
|