mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-24 16:57:50 +00:00
Only use MasterSecret for local message encryption.
Not for the axolotl store. // FREEBIE
This commit is contained in:
parent
b1810e2c44
commit
8d9ae731ef
@ -467,7 +467,7 @@
|
||||
<!-- MessageNotifier -->
|
||||
<string name="MessageNotifier_d_messages_in_d_conversations">%1$d messages in %2$d conversations</string>
|
||||
<string name="MessageNotifier_most_recent_from_s">Most recent from: %1$s</string>
|
||||
<string name="MessageNotifier_encrypted_message">Encrypted message...</string>
|
||||
<string name="MessageNotifier_locked_message">Locked message...</string>
|
||||
<string name="MessageNotifier_media_message_with_text">Media message: %s</string>
|
||||
<string name="MessageNotifier_no_subject">(No subject)</string>
|
||||
<string name="MessageNotifier_message_delivery_failed">Message delivery failed.</string>
|
||||
@ -779,7 +779,7 @@
|
||||
<string name="preferences__change_my_passphrase">Change my passphrase</string>
|
||||
<string name="preferences__enable_passphrase">Enable passphrase</string>
|
||||
<string name="preferences__passphrase_summary">Passphrase %s</string>
|
||||
<string name="preferences__enable_local_encryption_of_messages_and_keys">Enable local encryption of messages and keys</string>
|
||||
<string name="preferences__enable_lock_screen_for_messages">Enable lock screen for messages</string>
|
||||
<string name="preferences__screen_security">Screen security</string>
|
||||
<string name="preferences__screen_security_summary">Screen security %s</string>
|
||||
<string name="preferences__disable_screen_security_to_allow_screen_shots">Block screenshots in the recents list and inside the app</string>
|
||||
|
@ -5,7 +5,7 @@
|
||||
android:key="pref_enable_passphrase_temporary"
|
||||
android:defaultValue="true"
|
||||
android:title="@string/preferences__enable_passphrase"
|
||||
android:summary="@string/preferences__enable_local_encryption_of_messages_and_keys"/>
|
||||
android:summary="@string/preferences__enable_lock_screen_for_messages"/>
|
||||
|
||||
<Preference android:key="pref_change_passphrase"
|
||||
android:title="@string/preferences__change_passphrase"
|
||||
|
@ -95,8 +95,7 @@ public class ConfirmIdentityDialog extends AlertDialog {
|
||||
protected Void doInBackground(Void... params) {
|
||||
IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(getContext());
|
||||
|
||||
identityDatabase.saveIdentity(masterSecret,
|
||||
mismatch.getRecipientId(),
|
||||
identityDatabase.saveIdentity(mismatch.getRecipientId(),
|
||||
mismatch.getIdentityKey());
|
||||
|
||||
processMessageRecord(messageRecord);
|
||||
|
@ -29,21 +29,15 @@ import android.widget.ProgressBar;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecurePreKeyStore;
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.PushDatabase;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
|
||||
import org.thoughtcrime.securesms.jobs.PushDecryptJob;
|
||||
import org.thoughtcrime.securesms.jobs.SmsDecryptJob;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.util.ParcelUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.VersionTracker;
|
||||
import org.whispersystems.jobqueue.EncryptionKeys;
|
||||
import org.whispersystems.textsecure.api.messages.TextSecureEnvelope;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.SortedSet;
|
||||
@ -60,6 +54,7 @@ public class DatabaseUpgradeActivity extends BaseActivity {
|
||||
public static final int SIGNED_PREKEY_VERSION = 83;
|
||||
public static final int NO_DECRYPT_QUEUE_VERSION = 113;
|
||||
public static final int PUSH_DECRYPT_SERIAL_ID_VERSION = 131;
|
||||
public static final int MIGRATE_SESSION_PLAINTEXT = 133;
|
||||
|
||||
private static final SortedSet<Integer> UPGRADE_VERSIONS = new TreeSet<Integer>() {{
|
||||
add(NO_MORE_KEY_EXCHANGE_PREFIX_VERSION);
|
||||
@ -70,6 +65,7 @@ public class DatabaseUpgradeActivity extends BaseActivity {
|
||||
add(SIGNED_PREKEY_VERSION);
|
||||
add(NO_DECRYPT_QUEUE_VERSION);
|
||||
add(PUSH_DECRYPT_SERIAL_ID_VERSION);
|
||||
add(MIGRATE_SESSION_PLAINTEXT);
|
||||
}};
|
||||
|
||||
private MasterSecret masterSecret;
|
||||
@ -90,10 +86,6 @@ public class DatabaseUpgradeActivity extends BaseActivity {
|
||||
.execute(VersionTracker.getLastSeenVersion(this));
|
||||
} else {
|
||||
VersionTracker.updateLastSeenVersion(this);
|
||||
ApplicationContext.getInstance(this)
|
||||
.getJobManager()
|
||||
.setEncryptionKeys(new EncryptionKeys(ParcelUtil.serialize(masterSecret)));
|
||||
// DecryptingQueue.schedulePendingDecrypts(DatabaseUpgradeActivity.this, masterSecret);
|
||||
MessageNotifier.updateNotification(DatabaseUpgradeActivity.this, masterSecret);
|
||||
startActivity((Intent)getIntent().getParcelableExtra("next_intent"));
|
||||
finish();
|
||||
@ -154,9 +146,7 @@ public class DatabaseUpgradeActivity extends BaseActivity {
|
||||
.onApplicationLevelUpgrade(context, masterSecret, params[0], this);
|
||||
|
||||
if (params[0] < CURVE25519_VERSION) {
|
||||
if (!IdentityKeyUtil.hasCurve25519IdentityKeys(context)) {
|
||||
IdentityKeyUtil.generateCurve25519IdentityKeys(context, masterSecret);
|
||||
}
|
||||
IdentityKeyUtil.migrateIdentityKeys(context, masterSecret);
|
||||
}
|
||||
|
||||
if (params[0] < NO_V1_VERSION) {
|
||||
@ -178,48 +168,29 @@ public class DatabaseUpgradeActivity extends BaseActivity {
|
||||
if (params[0] < SIGNED_PREKEY_VERSION) {
|
||||
ApplicationContext.getInstance(getApplicationContext())
|
||||
.getJobManager()
|
||||
.add(new CreateSignedPreKeyJob(context, masterSecret));
|
||||
.add(new CreateSignedPreKeyJob(context));
|
||||
}
|
||||
|
||||
if (params[0] < NO_DECRYPT_QUEUE_VERSION) {
|
||||
EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(getApplicationContext());
|
||||
PushDatabase pushDatabase = DatabaseFactory.getPushDatabase(getApplicationContext());
|
||||
|
||||
SmsDatabase.Reader smsReader = null;
|
||||
Cursor pushReader = null;
|
||||
|
||||
SmsMessageRecord record;
|
||||
|
||||
try {
|
||||
smsReader = smsDatabase.getDecryptInProgressMessages(masterSecret);
|
||||
|
||||
while ((record = smsReader.getNext()) != null) {
|
||||
ApplicationContext.getInstance(getApplicationContext())
|
||||
.getJobManager()
|
||||
.add(new SmsDecryptJob(getApplicationContext(), record.getId()));
|
||||
}
|
||||
} finally {
|
||||
if (smsReader != null)
|
||||
smsReader.close();
|
||||
}
|
||||
|
||||
try {
|
||||
pushReader = pushDatabase.getPending();
|
||||
|
||||
while ((pushReader != null && pushReader.moveToNext())) {
|
||||
ApplicationContext.getInstance(getApplicationContext())
|
||||
.getJobManager()
|
||||
.add(new PushDecryptJob(getApplicationContext(),
|
||||
pushReader.getLong(pushReader.getColumnIndexOrThrow(PushDatabase.ID)),
|
||||
pushReader.getString(pushReader.getColumnIndexOrThrow(PushDatabase.SOURCE))));
|
||||
}
|
||||
} finally {
|
||||
if (pushReader != null)
|
||||
pushReader.close();
|
||||
}
|
||||
scheduleMessagesInPushDatabase(context);
|
||||
}
|
||||
|
||||
if (params[0] < PUSH_DECRYPT_SERIAL_ID_VERSION) {
|
||||
scheduleMessagesInPushDatabase(context);
|
||||
}
|
||||
|
||||
if (params[0] < MIGRATE_SESSION_PLAINTEXT) {
|
||||
new TextSecureSessionStore(context, masterSecret).migrateSessions();
|
||||
new TextSecurePreKeyStore(context, masterSecret).migrateRecords();
|
||||
|
||||
IdentityKeyUtil.migrateIdentityKeys(context, masterSecret);
|
||||
scheduleMessagesInPushDatabase(context);;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void scheduleMessagesInPushDatabase(Context context) {
|
||||
PushDatabase pushDatabase = DatabaseFactory.getPushDatabase(context);
|
||||
Cursor pushReader = null;
|
||||
|
||||
@ -239,9 +210,6 @@ public class DatabaseUpgradeActivity extends BaseActivity {
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProgressUpdate(Double... update) {
|
||||
indeterminateProgress.setVisibility(View.GONE);
|
||||
@ -254,11 +222,6 @@ public class DatabaseUpgradeActivity extends BaseActivity {
|
||||
@Override
|
||||
protected void onPostExecute(Void result) {
|
||||
VersionTracker.updateLastSeenVersion(DatabaseUpgradeActivity.this);
|
||||
// DecryptingQueue.schedulePendingDecrypts(DatabaseUpgradeActivity.this, masterSecret);
|
||||
ApplicationContext.getInstance(DatabaseUpgradeActivity.this)
|
||||
.getJobManager()
|
||||
.setEncryptionKeys(new EncryptionKeys(ParcelUtil.serialize(masterSecret)));
|
||||
|
||||
MessageNotifier.updateNotification(DatabaseUpgradeActivity.this, masterSecret);
|
||||
|
||||
startActivity((Intent)getIntent().getParcelableExtra("next_intent"));
|
||||
|
@ -108,7 +108,7 @@ public class DeviceProvisioningActivity extends PassphraseRequiredActionBarActiv
|
||||
String ephemeralId = uri.getQueryParameter("uuid");
|
||||
String publicKeyEncoded = uri.getQueryParameter("pub_key");
|
||||
ECPublicKey publicKey = Curve.decodePoint(Base64.decode(publicKeyEncoded), 0);
|
||||
IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
|
||||
IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context);
|
||||
|
||||
accountManager.addDevice(ephemeralId, publicKey, identityKeyPair, verificationCode);
|
||||
return SUCCESS;
|
||||
|
@ -66,7 +66,7 @@ public class PassphraseCreateActivity extends PassphraseActivity {
|
||||
passphrase);
|
||||
|
||||
MasterSecretUtil.generateAsymmetricMasterSecret(PassphraseCreateActivity.this, masterSecret);
|
||||
IdentityKeyUtil.generateIdentityKeys(PassphraseCreateActivity.this, masterSecret);
|
||||
IdentityKeyUtil.generateIdentityKeys(PassphraseCreateActivity.this);
|
||||
VersionTracker.updateLastSeenVersion(PassphraseCreateActivity.this);
|
||||
TextSecurePreferences.setPasswordDisabled(PassphraseCreateActivity.this, true);
|
||||
|
||||
|
@ -61,39 +61,45 @@ public class AsymmetricMasterCipher {
|
||||
this.asymmetricMasterSecret = asymmetricMasterSecret;
|
||||
}
|
||||
|
||||
public String decryptBody(String body) throws IOException, InvalidMessageException {
|
||||
public byte[] encryptBytes(byte[] body) {
|
||||
try {
|
||||
ECPublicKey theirPublic = asymmetricMasterSecret.getDjbPublicKey();
|
||||
ECKeyPair ourKeyPair = Curve.generateKeyPair();
|
||||
byte[] secret = Curve.calculateAgreement(theirPublic, ourKeyPair.getPrivateKey());
|
||||
MasterCipher masterCipher = getMasterCipherForSecret(secret);
|
||||
byte[] encryptedBodyBytes = masterCipher.encryptBytes(body);
|
||||
|
||||
PublicKey ourPublicKey = new PublicKey(31337, ourKeyPair.getPublicKey());
|
||||
byte[] publicKeyBytes = ourPublicKey.serialize();
|
||||
|
||||
return Util.combine(publicKeyBytes, encryptedBodyBytes);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] decryptBytes(byte[] combined) throws IOException, InvalidMessageException {
|
||||
try {
|
||||
byte[] combined = Base64.decode(body);
|
||||
byte[][] parts = Util.split(combined, PublicKey.KEY_SIZE, combined.length - PublicKey.KEY_SIZE);
|
||||
PublicKey theirPublicKey = new PublicKey(parts[0], 0);
|
||||
|
||||
ECPrivateKey ourPrivateKey = asymmetricMasterSecret.getPrivateKey();
|
||||
byte[] secret = Curve.calculateAgreement(theirPublicKey.getKey(), ourPrivateKey);
|
||||
MasterCipher masterCipher = getMasterCipherForSecret(secret);
|
||||
byte[] decryptedBody = masterCipher.decryptBytes(parts[1]);
|
||||
|
||||
return new String(decryptedBody);
|
||||
} catch (InvalidKeyException | InvalidMessageException ike) {
|
||||
throw new InvalidMessageException(ike);
|
||||
return masterCipher.decryptBytes(parts[1]);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public String decryptBody(String body) throws IOException, InvalidMessageException {
|
||||
byte[] combined = Base64.decode(body);
|
||||
return new String(decryptBytes(combined));
|
||||
}
|
||||
|
||||
public String encryptBody(String body) {
|
||||
try {
|
||||
ECPublicKey theirPublic = asymmetricMasterSecret.getDjbPublicKey();
|
||||
ECKeyPair ourKeyPair = Curve.generateKeyPair();
|
||||
byte[] secret = Curve.calculateAgreement(theirPublic, ourKeyPair.getPrivateKey());
|
||||
MasterCipher masterCipher = getMasterCipherForSecret(secret);
|
||||
byte[] encryptedBodyBytes = masterCipher.encryptBytes(body.getBytes());
|
||||
|
||||
PublicKey ourPublicKey = new PublicKey(31337, ourKeyPair.getPublicKey());
|
||||
byte[] publicKeyBytes = ourPublicKey.serialize();
|
||||
byte[] combined = Util.combine(publicKeyBytes, encryptedBodyBytes);
|
||||
|
||||
return Base64.encodeBytes(combined);
|
||||
} catch (InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
return Base64.encodeBytes(encryptBytes(body.getBytes()));
|
||||
}
|
||||
|
||||
private MasterCipher getMasterCipherForSecret(byte[] secretBytes) {
|
||||
|
@ -20,7 +20,7 @@ package org.thoughtcrime.securesms.crypto;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
import android.util.Log;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.whispersystems.libaxolotl.IdentityKey;
|
||||
@ -40,86 +40,109 @@ import java.io.IOException;
|
||||
|
||||
public class IdentityKeyUtil {
|
||||
|
||||
private static final String IDENTITY_PUBLIC_KEY_DJB_PREF = "pref_identity_public_curve25519";
|
||||
private static final String IDENTITY_PRIVATE_KEY_DJB_PREF = "pref_identity_private_curve25519";
|
||||
private static final String TAG = IdentityKeyUtil.class.getSimpleName();
|
||||
|
||||
private static final String IDENTITY_PUBLIC_KEY_CIPHERTEXT_LEGACY_PREF = "pref_identity_public_curve25519";
|
||||
private static final String IDENTITY_PRIVATE_KEY_CIPHERTEXT_LEGACY_PREF = "pref_identity_private_curve25519";
|
||||
|
||||
private static final String IDENTITY_PUBLIC_KEY_PREF = "pref_identity_public_v3";
|
||||
private static final String IDENTITY_PRIVATE_KEY_PREF = "pref_identity_private_v3";
|
||||
|
||||
public static boolean hasIdentityKey(Context context) {
|
||||
SharedPreferences preferences = context.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0);
|
||||
|
||||
return
|
||||
preferences.contains(IDENTITY_PUBLIC_KEY_DJB_PREF) &&
|
||||
preferences.contains(IDENTITY_PRIVATE_KEY_DJB_PREF);
|
||||
preferences.contains(IDENTITY_PUBLIC_KEY_PREF) &&
|
||||
preferences.contains(IDENTITY_PRIVATE_KEY_PREF);
|
||||
}
|
||||
|
||||
public static IdentityKey getIdentityKey(Context context) {
|
||||
if (!hasIdentityKey(context)) return null;
|
||||
public static @NonNull IdentityKey getIdentityKey(@NonNull Context context) {
|
||||
if (!hasIdentityKey(context)) throw new AssertionError("There isn't one!");
|
||||
|
||||
try {
|
||||
byte[] publicKeyBytes = Base64.decode(retrieve(context, IDENTITY_PUBLIC_KEY_DJB_PREF));
|
||||
byte[] publicKeyBytes = Base64.decode(retrieve(context, IDENTITY_PUBLIC_KEY_PREF));
|
||||
return new IdentityKey(publicKeyBytes, 0);
|
||||
} catch (IOException ioe) {
|
||||
Log.w("IdentityKeyUtil", ioe);
|
||||
return null;
|
||||
} catch (InvalidKeyException e) {
|
||||
Log.w("IdentityKeyUtil", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static IdentityKeyPair getIdentityKeyPair(Context context,
|
||||
MasterSecret masterSecret)
|
||||
{
|
||||
if (!hasIdentityKey(context))
|
||||
return null;
|
||||
|
||||
try {
|
||||
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
||||
IdentityKey publicKey = getIdentityKey(context);
|
||||
ECPrivateKey privateKey = masterCipher.decryptKey(Base64.decode(retrieve(context, IDENTITY_PRIVATE_KEY_DJB_PREF)));
|
||||
|
||||
return new IdentityKeyPair(publicKey, privateKey);
|
||||
} catch (IOException | InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void generateIdentityKeys(Context context, MasterSecret masterSecret) {
|
||||
ECKeyPair djbKeyPair = Curve.generateKeyPair();
|
||||
public static @NonNull IdentityKeyPair getIdentityKeyPair(@NonNull Context context) {
|
||||
if (!hasIdentityKey(context)) throw new AssertionError("There isn't one!");
|
||||
|
||||
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
||||
IdentityKey djbIdentityKey = new IdentityKey(djbKeyPair.getPublicKey());
|
||||
byte[] djbPrivateKey = masterCipher.encryptKey(djbKeyPair.getPrivateKey());
|
||||
try {
|
||||
IdentityKey publicKey = getIdentityKey(context);
|
||||
ECPrivateKey privateKey = Curve.decodePrivatePoint(Base64.decode(retrieve(context, IDENTITY_PRIVATE_KEY_PREF)));
|
||||
|
||||
save(context, IDENTITY_PUBLIC_KEY_DJB_PREF, Base64.encodeBytes(djbIdentityKey.serialize()));
|
||||
save(context, IDENTITY_PRIVATE_KEY_DJB_PREF, Base64.encodeBytes(djbPrivateKey));
|
||||
return new IdentityKeyPair(publicKey, privateKey);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean hasCurve25519IdentityKeys(Context context) {
|
||||
public static void generateIdentityKeys(Context context) {
|
||||
ECKeyPair djbKeyPair = Curve.generateKeyPair();
|
||||
IdentityKey djbIdentityKey = new IdentityKey(djbKeyPair.getPublicKey());
|
||||
ECPrivateKey djbPrivateKey = djbKeyPair.getPrivateKey();
|
||||
|
||||
save(context, IDENTITY_PUBLIC_KEY_PREF, Base64.encodeBytes(djbIdentityKey.serialize()));
|
||||
save(context, IDENTITY_PRIVATE_KEY_PREF, Base64.encodeBytes(djbPrivateKey.serialize()));
|
||||
}
|
||||
|
||||
public static void migrateIdentityKeys(@NonNull Context context,
|
||||
@NonNull MasterSecret masterSecret)
|
||||
{
|
||||
if (!hasIdentityKey(context)) {
|
||||
if (hasLegacyIdentityKeys(context)) {
|
||||
IdentityKeyPair legacyPair = getLegacyIdentityKeyPair(context, masterSecret);
|
||||
|
||||
save(context, IDENTITY_PUBLIC_KEY_PREF, Base64.encodeBytes(legacyPair.serialize()));
|
||||
save(context, IDENTITY_PRIVATE_KEY_PREF, Base64.encodeBytes(legacyPair.serialize()));
|
||||
|
||||
delete(context, IDENTITY_PUBLIC_KEY_CIPHERTEXT_LEGACY_PREF);
|
||||
delete(context, IDENTITY_PRIVATE_KEY_CIPHERTEXT_LEGACY_PREF);
|
||||
} else {
|
||||
generateIdentityKeys(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hasLegacyIdentityKeys(Context context) {
|
||||
return
|
||||
retrieve(context, IDENTITY_PUBLIC_KEY_DJB_PREF) != null &&
|
||||
retrieve(context, IDENTITY_PRIVATE_KEY_DJB_PREF) != null;
|
||||
retrieve(context, IDENTITY_PUBLIC_KEY_CIPHERTEXT_LEGACY_PREF) != null &&
|
||||
retrieve(context, IDENTITY_PRIVATE_KEY_CIPHERTEXT_LEGACY_PREF) != null;
|
||||
}
|
||||
|
||||
public static void generateCurve25519IdentityKeys(Context context, MasterSecret masterSecret) {
|
||||
private static IdentityKeyPair getLegacyIdentityKeyPair(@NonNull Context context,
|
||||
@NonNull MasterSecret masterSecret)
|
||||
{
|
||||
try {
|
||||
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
||||
ECKeyPair djbKeyPair = Curve.generateKeyPair();
|
||||
IdentityKey djbIdentityKey = new IdentityKey(djbKeyPair.getPublicKey());
|
||||
byte[] djbPrivateKey = masterCipher.encryptKey(djbKeyPair.getPrivateKey());
|
||||
byte[] publicKeyBytes = Base64.decode(retrieve(context, IDENTITY_PUBLIC_KEY_CIPHERTEXT_LEGACY_PREF));
|
||||
IdentityKey identityKey = new IdentityKey(publicKeyBytes, 0);
|
||||
ECPrivateKey privateKey = masterCipher.decryptKey(Base64.decode(retrieve(context, IDENTITY_PRIVATE_KEY_CIPHERTEXT_LEGACY_PREF)));
|
||||
|
||||
save(context, IDENTITY_PUBLIC_KEY_DJB_PREF, Base64.encodeBytes(djbIdentityKey.serialize()));
|
||||
save(context, IDENTITY_PRIVATE_KEY_DJB_PREF, Base64.encodeBytes(djbPrivateKey));
|
||||
return new IdentityKeyPair(identityKey, privateKey);
|
||||
} catch (IOException | InvalidKeyException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String retrieve(Context context, String key) {
|
||||
private static String retrieve(Context context, String key) {
|
||||
SharedPreferences preferences = context.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0);
|
||||
return preferences.getString(key, null);
|
||||
}
|
||||
|
||||
public static void save(Context context, String key, String value) {
|
||||
private static void save(Context context, String key, String value) {
|
||||
SharedPreferences preferences = context.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0);
|
||||
Editor preferencesEditor = preferences.edit();
|
||||
|
||||
preferencesEditor.putString(key, value);
|
||||
if (!preferencesEditor.commit()) throw new AssertionError("failed to save identity key/value to shared preferences");
|
||||
}
|
||||
|
||||
private static void delete(Context context, String key) {
|
||||
context.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0).edit().remove(key).commit();
|
||||
}
|
||||
|
||||
}
|
||||
|
29
src/org/thoughtcrime/securesms/crypto/MasterSecretUnion.java
Normal file
29
src/org/thoughtcrime/securesms/crypto/MasterSecretUnion.java
Normal file
@ -0,0 +1,29 @@
|
||||
package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.whispersystems.libaxolotl.util.guava.Optional;
|
||||
|
||||
public class MasterSecretUnion {
|
||||
|
||||
private final Optional<MasterSecret> masterSecret;
|
||||
private final Optional<AsymmetricMasterSecret> asymmetricMasterSecret;
|
||||
|
||||
public MasterSecretUnion(@NonNull MasterSecret masterSecret) {
|
||||
this.masterSecret = Optional.of(masterSecret);
|
||||
this.asymmetricMasterSecret = Optional.absent();
|
||||
}
|
||||
|
||||
public MasterSecretUnion(@NonNull AsymmetricMasterSecret asymmetricMasterSecret) {
|
||||
this.masterSecret = Optional.absent();
|
||||
this.asymmetricMasterSecret = Optional.of(asymmetricMasterSecret);
|
||||
}
|
||||
|
||||
public Optional<MasterSecret> getMasterSecret() {
|
||||
return masterSecret;
|
||||
}
|
||||
|
||||
public Optional<AsymmetricMasterSecret> getAsymmetricMasterSecret() {
|
||||
return asymmetricMasterSecret;
|
||||
}
|
||||
}
|
@ -19,6 +19,8 @@ package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
@ -121,8 +123,8 @@ public class MasterSecretUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public static AsymmetricMasterSecret getAsymmetricMasterSecret(Context context,
|
||||
MasterSecret masterSecret)
|
||||
public static AsymmetricMasterSecret getAsymmetricMasterSecret(@NonNull Context context,
|
||||
@Nullable MasterSecret masterSecret)
|
||||
{
|
||||
try {
|
||||
byte[] djbPublicBytes = retrieve(context, ASYMMETRIC_LOCAL_PUBLIC_DJB);
|
||||
|
31
src/org/thoughtcrime/securesms/crypto/MediaKey.java
Normal file
31
src/org/thoughtcrime/securesms/crypto/MediaKey.java
Normal file
@ -0,0 +1,31 @@
|
||||
package org.thoughtcrime.securesms.crypto;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.whispersystems.libaxolotl.InvalidMessageException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class MediaKey {
|
||||
|
||||
public static String getEncrypted(@NonNull MasterSecretUnion masterSecret, @NonNull byte[] key) {
|
||||
if (masterSecret.getMasterSecret().isPresent()) {
|
||||
return Base64.encodeBytes(new MasterCipher(masterSecret.getMasterSecret().get()).encryptBytes(key));
|
||||
} else {
|
||||
return "?ASYNC-" + Base64.encodeBytes(new AsymmetricMasterCipher(masterSecret.getAsymmetricMasterSecret().get()).encryptBytes(key));
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] getDecrypted(@NonNull MasterSecret masterSecret,
|
||||
@NonNull AsymmetricMasterSecret asymmetricMasterSecret,
|
||||
@NonNull String encodedKey)
|
||||
throws IOException, InvalidMessageException
|
||||
{
|
||||
if (encodedKey.startsWith("?ASYNC-")) {
|
||||
return new AsymmetricMasterCipher(asymmetricMasterSecret).decryptBytes(Base64.decode(encodedKey.substring("?ASYNC-".length())));
|
||||
} else {
|
||||
return new MasterCipher(masterSecret).decryptBytes(Base64.decode(encodedKey));
|
||||
}
|
||||
}
|
||||
}
|
@ -48,8 +48,8 @@ public class PreKeyUtil {
|
||||
|
||||
public static final int BATCH_SIZE = 100;
|
||||
|
||||
public static List<PreKeyRecord> generatePreKeys(Context context, MasterSecret masterSecret) {
|
||||
PreKeyStore preKeyStore = new TextSecurePreKeyStore(context, masterSecret);
|
||||
public static List<PreKeyRecord> generatePreKeys(Context context) {
|
||||
PreKeyStore preKeyStore = new TextSecurePreKeyStore(context);
|
||||
List<PreKeyRecord> records = new LinkedList<>();
|
||||
int preKeyIdOffset = getNextPreKeyId(context);
|
||||
|
||||
@ -66,11 +66,10 @@ public class PreKeyUtil {
|
||||
return records;
|
||||
}
|
||||
|
||||
public static SignedPreKeyRecord generateSignedPreKey(Context context, MasterSecret masterSecret,
|
||||
IdentityKeyPair identityKeyPair)
|
||||
public static SignedPreKeyRecord generateSignedPreKey(Context context, IdentityKeyPair identityKeyPair)
|
||||
{
|
||||
try {
|
||||
SignedPreKeyStore signedPreKeyStore = new TextSecurePreKeyStore(context, masterSecret);
|
||||
SignedPreKeyStore signedPreKeyStore = new TextSecurePreKeyStore(context);
|
||||
int signedPreKeyId = getNextSignedPreKeyId(context);
|
||||
ECKeyPair keyPair = Curve.generateKeyPair();
|
||||
byte[] signature = Curve.calculateSignature(identityKeyPair.getPrivateKey(), keyPair.getPublicKey().serialize());
|
||||
@ -85,8 +84,8 @@ public class PreKeyUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public static PreKeyRecord generateLastResortKey(Context context, MasterSecret masterSecret) {
|
||||
PreKeyStore preKeyStore = new TextSecurePreKeyStore(context, masterSecret);
|
||||
public static PreKeyRecord generateLastResortKey(Context context) {
|
||||
PreKeyStore preKeyStore = new TextSecurePreKeyStore(context);
|
||||
|
||||
if (preKeyStore.containsPreKey(Medium.MAX_VALUE)) {
|
||||
try {
|
||||
|
@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.crypto.storage;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.whispersystems.libaxolotl.AxolotlAddress;
|
||||
import org.whispersystems.libaxolotl.IdentityKey;
|
||||
import org.whispersystems.libaxolotl.IdentityKeyPair;
|
||||
@ -25,11 +24,11 @@ public class TextSecureAxolotlStore implements AxolotlStore {
|
||||
private final IdentityKeyStore identityKeyStore;
|
||||
private final SessionStore sessionStore;
|
||||
|
||||
public TextSecureAxolotlStore(Context context, MasterSecret masterSecret) {
|
||||
this.preKeyStore = new TextSecurePreKeyStore(context, masterSecret);
|
||||
this.signedPreKeyStore = new TextSecurePreKeyStore(context, masterSecret);
|
||||
this.identityKeyStore = new TextSecureIdentityKeyStore(context, masterSecret);
|
||||
this.sessionStore = new TextSecureSessionStore(context, masterSecret);
|
||||
public TextSecureAxolotlStore(Context context) {
|
||||
this.preKeyStore = new TextSecurePreKeyStore(context);
|
||||
this.signedPreKeyStore = new TextSecurePreKeyStore(context);
|
||||
this.identityKeyStore = new TextSecureIdentityKeyStore(context);
|
||||
this.sessionStore = new TextSecureSessionStore(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.crypto.storage;
|
||||
import android.content.Context;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
@ -14,16 +13,14 @@ import org.whispersystems.libaxolotl.state.IdentityKeyStore;
|
||||
public class TextSecureIdentityKeyStore implements IdentityKeyStore {
|
||||
|
||||
private final Context context;
|
||||
private final MasterSecret masterSecret;
|
||||
|
||||
public TextSecureIdentityKeyStore(Context context, MasterSecret masterSecret) {
|
||||
public TextSecureIdentityKeyStore(Context context) {
|
||||
this.context = context;
|
||||
this.masterSecret = masterSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentityKeyPair getIdentityKeyPair() {
|
||||
return IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
|
||||
return IdentityKeyUtil.getIdentityKeyPair(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -34,13 +31,13 @@ public class TextSecureIdentityKeyStore implements IdentityKeyStore {
|
||||
@Override
|
||||
public void saveIdentity(String name, IdentityKey identityKey) {
|
||||
long recipientId = RecipientFactory.getRecipientsFromString(context, name, true).getPrimaryRecipient().getRecipientId();
|
||||
DatabaseFactory.getIdentityDatabase(context).saveIdentity(masterSecret, recipientId, identityKey);
|
||||
DatabaseFactory.getIdentityDatabase(context).saveIdentity(recipientId, identityKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTrustedIdentity(String name, IdentityKey identityKey) {
|
||||
long recipientId = RecipientFactory.getRecipientsFromString(context, name, true).getPrimaryRecipient().getRecipientId();
|
||||
return DatabaseFactory.getIdentityDatabase(context)
|
||||
.isValidIdentity(masterSecret, recipientId, identityKey);
|
||||
.isValidIdentity(recipientId, identityKey);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package org.thoughtcrime.securesms.crypto.storage;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
||||
@ -28,14 +30,19 @@ public class TextSecurePreKeyStore implements PreKeyStore, SignedPreKeyStore {
|
||||
public static final String SIGNED_PREKEY_DIRECTORY = "signed_prekeys";
|
||||
|
||||
|
||||
private static final int CURRENT_VERSION_MARKER = 1;
|
||||
private static final int PLAINTEXT_VERSION = 2;
|
||||
private static final int CURRENT_VERSION_MARKER = 2;
|
||||
private static final Object FILE_LOCK = new Object();
|
||||
private static final String TAG = TextSecurePreKeyStore.class.getSimpleName();
|
||||
|
||||
private final Context context;
|
||||
private final MasterSecret masterSecret;
|
||||
@NonNull private final Context context;
|
||||
@Nullable private final MasterSecret masterSecret;
|
||||
|
||||
public TextSecurePreKeyStore(Context context, MasterSecret masterSecret) {
|
||||
public TextSecurePreKeyStore(@NonNull Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public TextSecurePreKeyStore(@NonNull Context context, @Nullable MasterSecret masterSecret) {
|
||||
this.context = context;
|
||||
this.masterSecret = masterSecret;
|
||||
}
|
||||
@ -129,28 +136,65 @@ public class TextSecurePreKeyStore implements PreKeyStore, SignedPreKeyStore {
|
||||
record.delete();
|
||||
}
|
||||
|
||||
public void migrateRecords() {
|
||||
synchronized (FILE_LOCK) {
|
||||
File preKeyRecords = getPreKeyDirectory();
|
||||
|
||||
for (File preKeyRecord : preKeyRecords.listFiles()) {
|
||||
try {
|
||||
int preKeyId = Integer.parseInt(preKeyRecord.getName());
|
||||
PreKeyRecord record = loadPreKey(preKeyId);
|
||||
|
||||
storePreKey(preKeyId, record);
|
||||
} catch (InvalidKeyIdException | NumberFormatException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
File signedPreKeyRecords = getSignedPreKeyDirectory();
|
||||
|
||||
for (File signedPreKeyRecord : signedPreKeyRecords.listFiles()) {
|
||||
try {
|
||||
int signedPreKeyId = Integer.parseInt(signedPreKeyRecord.getName());
|
||||
SignedPreKeyRecord record = loadSignedPreKey(signedPreKeyId);
|
||||
|
||||
storeSignedPreKey(signedPreKeyId, record);
|
||||
} catch (InvalidKeyIdException | NumberFormatException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] loadSerializedRecord(File recordFile)
|
||||
throws IOException, InvalidMessageException
|
||||
{
|
||||
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
||||
FileInputStream fin = new FileInputStream(recordFile);
|
||||
int recordVersion = readInteger(fin);
|
||||
|
||||
if (recordVersion != CURRENT_VERSION_MARKER) {
|
||||
if (recordVersion > CURRENT_VERSION_MARKER) {
|
||||
throw new AssertionError("Invalid version: " + recordVersion);
|
||||
}
|
||||
|
||||
return masterCipher.decryptBytes(readBlob(fin));
|
||||
byte[] serializedRecord = readBlob(fin);
|
||||
|
||||
if (recordVersion < PLAINTEXT_VERSION && masterSecret != null) {
|
||||
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
||||
serializedRecord = masterCipher.decryptBytes(serializedRecord);
|
||||
} else if (recordVersion < PLAINTEXT_VERSION) {
|
||||
throw new AssertionError("Migration didn't happen!");
|
||||
}
|
||||
|
||||
return serializedRecord;
|
||||
}
|
||||
|
||||
private void storeSerializedRecord(File file, byte[] serialized) throws IOException {
|
||||
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
||||
RandomAccessFile recordFile = new RandomAccessFile(file, "rw");
|
||||
FileChannel out = recordFile.getChannel();
|
||||
|
||||
out.position(0);
|
||||
writeInteger(CURRENT_VERSION_MARKER, out);
|
||||
writeBlob(masterCipher.encryptBytes(serialized), out);
|
||||
writeBlob(serialized, out);
|
||||
out.truncate(out.position());
|
||||
recordFile.close();
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package org.thoughtcrime.securesms.crypto.storage;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
||||
@ -34,37 +36,46 @@ public class TextSecureSessionStore implements SessionStore {
|
||||
|
||||
private static final int SINGLE_STATE_VERSION = 1;
|
||||
private static final int ARCHIVE_STATES_VERSION = 2;
|
||||
private static final int CURRENT_VERSION = 2;
|
||||
private static final int PLAINTEXT_VERSION = 3;
|
||||
private static final int CURRENT_VERSION = 3;
|
||||
|
||||
private final Context context;
|
||||
private final MasterSecret masterSecret;
|
||||
@NonNull private final Context context;
|
||||
@Nullable private final MasterSecret masterSecret;
|
||||
|
||||
public TextSecureSessionStore(Context context, MasterSecret masterSecret) {
|
||||
public TextSecureSessionStore(@NonNull Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public TextSecureSessionStore(@NonNull Context context, @Nullable MasterSecret masterSecret) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.masterSecret = masterSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SessionRecord loadSession(AxolotlAddress address) {
|
||||
public SessionRecord loadSession(@NonNull AxolotlAddress address) {
|
||||
synchronized (FILE_LOCK) {
|
||||
try {
|
||||
MasterCipher cipher = new MasterCipher(masterSecret);
|
||||
FileInputStream in = new FileInputStream(getSessionFile(address));
|
||||
|
||||
int versionMarker = readInteger(in);
|
||||
|
||||
if (versionMarker > CURRENT_VERSION) {
|
||||
throw new AssertionError("Unknown version: " + versionMarker);
|
||||
}
|
||||
|
||||
byte[] serialized = cipher.decryptBytes(readBlob(in));
|
||||
byte[] serialized = readBlob(in);
|
||||
in.close();
|
||||
|
||||
if (versionMarker < PLAINTEXT_VERSION && masterSecret != null) {
|
||||
serialized = new MasterCipher(masterSecret).decryptBytes(serialized);
|
||||
} else if (versionMarker < PLAINTEXT_VERSION) {
|
||||
throw new AssertionError("Session didn't get migrated: (" + versionMarker + "," + address + ")");
|
||||
}
|
||||
|
||||
if (versionMarker == SINGLE_STATE_VERSION) {
|
||||
SessionStructure sessionStructure = SessionStructure.parseFrom(serialized);
|
||||
SessionState sessionState = new SessionState(sessionStructure);
|
||||
return new SessionRecord(sessionState);
|
||||
} else if (versionMarker == ARCHIVE_STATES_VERSION) {
|
||||
} else if (versionMarker >= ARCHIVE_STATES_VERSION) {
|
||||
return new SessionRecord(serialized);
|
||||
} else {
|
||||
throw new AssertionError("Unknown version: " + versionMarker);
|
||||
@ -77,16 +88,15 @@ public class TextSecureSessionStore implements SessionStore {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void storeSession(AxolotlAddress address, SessionRecord record) {
|
||||
public void storeSession(@NonNull AxolotlAddress address, @NonNull SessionRecord record) {
|
||||
synchronized (FILE_LOCK) {
|
||||
try {
|
||||
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
||||
RandomAccessFile sessionFile = new RandomAccessFile(getSessionFile(address), "rw");
|
||||
FileChannel out = sessionFile.getChannel();
|
||||
|
||||
out.position(0);
|
||||
writeInteger(CURRENT_VERSION, out);
|
||||
writeBlob(masterCipher.encryptBytes(record.serialize()), out);
|
||||
writeBlob(record.serialize(), out);
|
||||
out.truncate(out.position());
|
||||
|
||||
sessionFile.close();
|
||||
@ -143,6 +153,23 @@ public class TextSecureSessionStore implements SessionStore {
|
||||
return results;
|
||||
}
|
||||
|
||||
public void migrateSessions() {
|
||||
synchronized (FILE_LOCK) {
|
||||
File directory = getSessionDirectory();
|
||||
|
||||
for (File session : directory.listFiles()) {
|
||||
if (session.isFile()) {
|
||||
AxolotlAddress address = getAddressName(session);
|
||||
|
||||
if (address != null) {
|
||||
SessionRecord sessionRecord = loadSession(address);
|
||||
storeSession(address, sessionRecord);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private File getSessionFile(AxolotlAddress address) {
|
||||
return new File(getSessionDirectory(), getSessionName(address));
|
||||
}
|
||||
@ -168,6 +195,23 @@ public class TextSecureSessionStore implements SessionStore {
|
||||
return recipientId + (deviceId == TextSecureAddress.DEFAULT_DEVICE_ID ? "" : "." + deviceId);
|
||||
}
|
||||
|
||||
private @Nullable AxolotlAddress getAddressName(File sessionFile) {
|
||||
try {
|
||||
String[] parts = sessionFile.getName().split("[.]");
|
||||
Recipient recipient = RecipientFactory.getRecipientForId(context, Integer.valueOf(parts[0]), true);
|
||||
|
||||
int deviceId;
|
||||
|
||||
if (parts.length > 1) deviceId = Integer.parseInt(parts[1]);
|
||||
else deviceId = TextSecureAddress.DEFAULT_DEVICE_ID;
|
||||
|
||||
return new AxolotlAddress(recipient.getNumber(), deviceId);
|
||||
} catch (NumberFormatException e) {
|
||||
Log.w(TAG, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] readBlob(FileInputStream in) throws IOException {
|
||||
int length = readInteger(in);
|
||||
byte[] blobBytes = new byte[length];
|
||||
|
@ -19,12 +19,16 @@ package org.thoughtcrime.securesms.database;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.crypto.AsymmetricMasterCipher;
|
||||
import org.thoughtcrime.securesms.crypto.AsymmetricMasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||
import org.thoughtcrime.securesms.database.model.DisplayRecord;
|
||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
|
||||
@ -60,57 +64,80 @@ public class EncryptingSmsDatabase extends SmsDatabase {
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
public long insertMessageOutbox(MasterSecret masterSecret, long threadId,
|
||||
public long insertMessageOutbox(MasterSecretUnion masterSecret, long threadId,
|
||||
OutgoingTextMessage message, boolean forceSms,
|
||||
long timestamp)
|
||||
{
|
||||
long type = Types.BASE_OUTBOX_TYPE;
|
||||
message = message.withBody(getEncryptedBody(masterSecret, message.getMessageBody()));
|
||||
|
||||
if (masterSecret.getMasterSecret().isPresent()) {
|
||||
message = message.withBody(getEncryptedBody(masterSecret.getMasterSecret().get(), message.getMessageBody()));
|
||||
type |= Types.ENCRYPTION_SYMMETRIC_BIT;
|
||||
} else {
|
||||
message = message.withBody(getAsymmetricEncryptedBody(masterSecret.getAsymmetricMasterSecret().get(), message.getMessageBody()));
|
||||
type |= Types.ENCRYPTION_ASYMMETRIC_BIT;
|
||||
}
|
||||
|
||||
return insertMessageOutbox(threadId, message, type, forceSms, timestamp);
|
||||
}
|
||||
|
||||
public Pair<Long, Long> insertMessageInbox(MasterSecret masterSecret,
|
||||
IncomingTextMessage message)
|
||||
public Pair<Long, Long> insertMessageInbox(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull IncomingTextMessage message)
|
||||
{
|
||||
long type = Types.BASE_INBOX_TYPE;
|
||||
|
||||
if (masterSecret == null && message.isSecureMessage()) {
|
||||
type |= Types.ENCRYPTION_REMOTE_BIT;
|
||||
if (masterSecret.getMasterSecret().isPresent()) {
|
||||
return insertMessageInbox(masterSecret.getMasterSecret().get(), message);
|
||||
} else {
|
||||
type |= Types.ENCRYPTION_SYMMETRIC_BIT;
|
||||
message = message.withMessageBody(getEncryptedBody(masterSecret, message.getMessageBody()));
|
||||
return insertMessageInbox(masterSecret.getAsymmetricMasterSecret().get(), message);
|
||||
}
|
||||
}
|
||||
|
||||
private Pair<Long, Long> insertMessageInbox(@NonNull MasterSecret masterSecret,
|
||||
@NonNull IncomingTextMessage message)
|
||||
{
|
||||
long type = Types.BASE_INBOX_TYPE | Types.ENCRYPTION_SYMMETRIC_BIT;
|
||||
|
||||
message = message.withMessageBody(getEncryptedBody(masterSecret, message.getMessageBody()));
|
||||
|
||||
return insertMessageInbox(message, type);
|
||||
}
|
||||
|
||||
public Pair<Long, Long> insertMessageInbox(AsymmetricMasterSecret masterSecret,
|
||||
IncomingTextMessage message)
|
||||
private Pair<Long, Long> insertMessageInbox(@NonNull AsymmetricMasterSecret masterSecret,
|
||||
@NonNull IncomingTextMessage message)
|
||||
{
|
||||
long type = Types.BASE_INBOX_TYPE;
|
||||
long type = Types.BASE_INBOX_TYPE | Types.ENCRYPTION_ASYMMETRIC_BIT;
|
||||
|
||||
if (message.isSecureMessage()) {
|
||||
type |= Types.ENCRYPTION_REMOTE_BIT;
|
||||
} else {
|
||||
message = message.withMessageBody(getAsymmetricEncryptedBody(masterSecret, message.getMessageBody()));
|
||||
|
||||
return insertMessageInbox(message, type);
|
||||
}
|
||||
|
||||
public Pair<Long, Long> updateBundleMessageBody(MasterSecretUnion masterSecret, long messageId, String body) {
|
||||
long type = Types.BASE_INBOX_TYPE | Types.SECURE_MESSAGE_BIT;
|
||||
String encryptedBody;
|
||||
|
||||
if (masterSecret.getMasterSecret().isPresent()) {
|
||||
encryptedBody = getEncryptedBody(masterSecret.getMasterSecret().get(), body);
|
||||
type |= Types.ENCRYPTION_SYMMETRIC_BIT;
|
||||
} else {
|
||||
encryptedBody = getAsymmetricEncryptedBody(masterSecret.getAsymmetricMasterSecret().get(), body);
|
||||
type |= Types.ENCRYPTION_ASYMMETRIC_BIT;
|
||||
}
|
||||
|
||||
return insertMessageInbox(message, type);
|
||||
return updateMessageBodyAndType(messageId, encryptedBody, Types.TOTAL_MASK, type);
|
||||
}
|
||||
|
||||
public Pair<Long, Long> updateBundleMessageBody(MasterSecret masterSecret, long messageId, String body) {
|
||||
String encryptedBody = getEncryptedBody(masterSecret, body);
|
||||
return updateMessageBodyAndType(messageId, encryptedBody, Types.TOTAL_MASK,
|
||||
Types.BASE_INBOX_TYPE | Types.ENCRYPTION_SYMMETRIC_BIT | Types.SECURE_MESSAGE_BIT);
|
||||
public void updateMessageBody(MasterSecretUnion masterSecret, long messageId, String body) {
|
||||
long type;
|
||||
|
||||
if (masterSecret.getMasterSecret().isPresent()) {
|
||||
body = getEncryptedBody(masterSecret.getMasterSecret().get(), body);
|
||||
type = Types.ENCRYPTION_SYMMETRIC_BIT;
|
||||
} else {
|
||||
body = getAsymmetricEncryptedBody(masterSecret.getAsymmetricMasterSecret().get(), body);
|
||||
type = Types.ENCRYPTION_ASYMMETRIC_BIT;
|
||||
}
|
||||
|
||||
public void updateMessageBody(MasterSecret masterSecret, long messageId, String body) {
|
||||
String encryptedBody = getEncryptedBody(masterSecret, body);
|
||||
updateMessageBodyAndType(messageId, encryptedBody, Types.ENCRYPTION_MASK,
|
||||
Types.ENCRYPTION_SYMMETRIC_BIT);
|
||||
updateMessageBodyAndType(messageId, body, Types.ENCRYPTION_MASK, type);
|
||||
}
|
||||
|
||||
public Reader getMessages(MasterSecret masterSecret, int skip, int limit) {
|
||||
|
@ -29,8 +29,6 @@ import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.whispersystems.libaxolotl.IdentityKey;
|
||||
import org.whispersystems.libaxolotl.InvalidKeyException;
|
||||
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@ -42,13 +40,11 @@ public class IdentityDatabase extends Database {
|
||||
private static final String ID = "_id";
|
||||
public static final String RECIPIENT = "recipient";
|
||||
public static final String IDENTITY_KEY = "key";
|
||||
public static final String MAC = "mac";
|
||||
|
||||
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME +
|
||||
" (" + ID + " INTEGER PRIMARY KEY, " +
|
||||
RECIPIENT + " INTEGER UNIQUE, " +
|
||||
IDENTITY_KEY + " TEXT, " +
|
||||
MAC + " TEXT);";
|
||||
IDENTITY_KEY + " TEXT);";
|
||||
|
||||
public IdentityDatabase(Context context, SQLiteOpenHelper databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
@ -64,12 +60,10 @@ public class IdentityDatabase extends Database {
|
||||
return cursor;
|
||||
}
|
||||
|
||||
public boolean isValidIdentity(MasterSecret masterSecret,
|
||||
long recipientId,
|
||||
public boolean isValidIdentity(long recipientId,
|
||||
IdentityKey theirIdentity)
|
||||
{
|
||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
||||
Cursor cursor = null;
|
||||
|
||||
try {
|
||||
@ -78,13 +72,6 @@ public class IdentityDatabase extends Database {
|
||||
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
String serializedIdentity = cursor.getString(cursor.getColumnIndexOrThrow(IDENTITY_KEY));
|
||||
String mac = cursor.getString(cursor.getColumnIndexOrThrow(MAC));
|
||||
|
||||
if (!masterCipher.verifyMacFor(recipientId + serializedIdentity, Base64.decode(mac))) {
|
||||
Log.w("IdentityDatabase", "MAC failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
IdentityKey ourIdentity = new IdentityKey(Base64.decode(serializedIdentity), 0);
|
||||
|
||||
return ourIdentity.equals(theirIdentity);
|
||||
@ -104,18 +91,14 @@ public class IdentityDatabase extends Database {
|
||||
}
|
||||
}
|
||||
|
||||
public void saveIdentity(MasterSecret masterSecret, long recipientId, IdentityKey identityKey)
|
||||
public void saveIdentity(long recipientId, IdentityKey identityKey)
|
||||
{
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
||||
String identityKeyString = Base64.encodeBytes(identityKey.serialize());
|
||||
String macString = Base64.encodeBytes(masterCipher.getMacFor(recipientId +
|
||||
identityKeyString));
|
||||
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(RECIPIENT, recipientId);
|
||||
contentValues.put(IDENTITY_KEY, identityKeyString);
|
||||
contentValues.put(MAC, macString);
|
||||
|
||||
database.replace(TABLE_NAME, null, contentValues);
|
||||
|
||||
@ -129,17 +112,15 @@ public class IdentityDatabase extends Database {
|
||||
context.getContentResolver().notifyChange(CHANGE_URI, null);
|
||||
}
|
||||
|
||||
public Reader readerFor(MasterSecret masterSecret, Cursor cursor) {
|
||||
return new Reader(masterSecret, cursor);
|
||||
public Reader readerFor(Cursor cursor) {
|
||||
return new Reader(cursor);
|
||||
}
|
||||
|
||||
public class Reader {
|
||||
private final Cursor cursor;
|
||||
private final MasterCipher cipher;
|
||||
|
||||
public Reader(MasterSecret masterSecret, Cursor cursor) {
|
||||
public Reader(Cursor cursor) {
|
||||
this.cursor = cursor;
|
||||
this.cipher = new MasterCipher(masterSecret);
|
||||
}
|
||||
|
||||
public Identity getCurrent() {
|
||||
@ -148,13 +129,8 @@ public class IdentityDatabase extends Database {
|
||||
|
||||
try {
|
||||
String identityKeyString = cursor.getString(cursor.getColumnIndexOrThrow(IDENTITY_KEY));
|
||||
String mac = cursor.getString(cursor.getColumnIndexOrThrow(MAC));
|
||||
|
||||
if (!cipher.verifyMacFor(recipientId + identityKeyString, Base64.decode(mac))) {
|
||||
return new Identity(recipients, null);
|
||||
}
|
||||
|
||||
IdentityKey identityKey = new IdentityKey(Base64.decode(identityKeyString), 0);
|
||||
|
||||
return new Identity(recipients, identityKey);
|
||||
} catch (IOException e) {
|
||||
Log.w("IdentityDatabase", e);
|
||||
|
@ -32,8 +32,10 @@ import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.crypto.AsymmetricMasterCipher;
|
||||
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
|
||||
import org.thoughtcrime.securesms.database.documents.NetworkFailure;
|
||||
import org.thoughtcrime.securesms.database.documents.NetworkFailureList;
|
||||
import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch;
|
||||
@ -357,12 +359,11 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
return cursor;
|
||||
}
|
||||
|
||||
public void updateResponseStatus(long messageId, int status) {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(RESPONSE_STATUS, status);
|
||||
public Reader getDecryptInProgressMessages(MasterSecret masterSecret) {
|
||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||
String where = MESSAGE_BOX + " & " + (Types.ENCRYPTION_ASYMMETRIC_BIT) + " != 0";
|
||||
|
||||
database.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {messageId + ""});
|
||||
return readerFor(masterSecret, db.query(TABLE_NAME, MMS_PROJECTION, where, null, null, null, null));
|
||||
}
|
||||
|
||||
private void updateMailboxBitmask(long id, long maskOff, long maskOn) {
|
||||
@ -417,15 +418,6 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
notifyConversationListeners(getThreadIdForMessage(messageId));
|
||||
}
|
||||
|
||||
public void markDeliveryStatus(long messageId, int status) {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(STATUS, status);
|
||||
|
||||
database.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {messageId + ""});
|
||||
notifyConversationListeners(getThreadIdForMessage(messageId));
|
||||
}
|
||||
|
||||
public void markAsNoSession(long messageId, long threadId) {
|
||||
updateMailboxBitmask(messageId, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_NO_SESSION_BIT);
|
||||
notifyConversationListeners(threadId);
|
||||
@ -474,6 +466,36 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
database.update(TABLE_NAME, contentValues, null, null);
|
||||
}
|
||||
|
||||
public void updateMessageBody(MasterSecretUnion masterSecret, long messageId, String body) {
|
||||
body = getEncryptedBody(masterSecret, body);
|
||||
|
||||
long type;
|
||||
|
||||
if (masterSecret.getMasterSecret().isPresent()) {
|
||||
type = Types.ENCRYPTION_SYMMETRIC_BIT;
|
||||
} else {
|
||||
type = Types.ENCRYPTION_ASYMMETRIC_BIT;
|
||||
}
|
||||
|
||||
updateMessageBodyAndType(messageId, body, Types.ENCRYPTION_MASK, type);
|
||||
}
|
||||
|
||||
private Pair<Long, Long> updateMessageBodyAndType(long messageId, String body, long maskOff, long maskOn) {
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
db.execSQL("UPDATE " + TABLE_NAME + " SET " + BODY + " = ?, " +
|
||||
MESSAGE_BOX + " = (" + MESSAGE_BOX + " & " + (Types.TOTAL_MASK - maskOff) + " | " + maskOn + ") " +
|
||||
"WHERE " + ID + " = ?",
|
||||
new String[] {body, messageId + ""});
|
||||
|
||||
long threadId = getThreadIdForMessage(messageId);
|
||||
|
||||
DatabaseFactory.getThreadDatabase(context).update(threadId);
|
||||
notifyConversationListeners(threadId);
|
||||
notifyConversationListListeners();
|
||||
|
||||
return new Pair<>(messageId, threadId);
|
||||
}
|
||||
|
||||
public Optional<NotificationInd> getNotification(long messageId) {
|
||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||
MmsAddressDatabase addressDatabase = DatabaseFactory.getMmsAddressDatabase(context);
|
||||
@ -544,15 +566,6 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
public Reader getNotificationsWithDownloadState(MasterSecret masterSecret, long state) {
|
||||
SQLiteDatabase database = databaseHelper.getReadableDatabase();
|
||||
String selection = STATUS + " = ?";
|
||||
String[] selectionArgs = new String[]{state + ""};
|
||||
|
||||
Cursor cursor = database.query(TABLE_NAME, MMS_PROJECTION, selection, selectionArgs, null, null, null);
|
||||
return new Reader(masterSecret, cursor);
|
||||
}
|
||||
|
||||
public long copyMessageInbox(MasterSecret masterSecret, long messageId) throws MmsException {
|
||||
try {
|
||||
SendReq request = getOutgoingMessage(masterSecret, messageId);
|
||||
@ -563,15 +576,17 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
contentValues.put(READ, 1);
|
||||
contentValues.put(DATE_RECEIVED, contentValues.getAsLong(DATE_SENT));
|
||||
|
||||
return insertMediaMessage(masterSecret, request.getPduHeaders(),
|
||||
return insertMediaMessage(new MasterSecretUnion(masterSecret), request.getPduHeaders(),
|
||||
request.getBody(), contentValues);
|
||||
} catch (NoSuchMessageException e) {
|
||||
throw new MmsException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Pair<Long, Long> insertMessageInbox(MasterSecret masterSecret, IncomingMediaMessage retrieved,
|
||||
String contentLocation, long threadId, long mailbox)
|
||||
private Pair<Long, Long> insertMessageInbox(MasterSecretUnion masterSecret,
|
||||
IncomingMediaMessage retrieved,
|
||||
String contentLocation,
|
||||
long threadId, long mailbox)
|
||||
throws MmsException
|
||||
{
|
||||
PduHeaders headers = retrieved.getPduHeaders();
|
||||
@ -614,35 +629,44 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
return new Pair<>(messageId, threadId);
|
||||
}
|
||||
|
||||
public Pair<Long, Long> insertMessageInbox(MasterSecret masterSecret,
|
||||
public Pair<Long, Long> insertMessageInbox(MasterSecretUnion masterSecret,
|
||||
IncomingMediaMessage retrieved,
|
||||
String contentLocation, long threadId)
|
||||
throws MmsException
|
||||
{
|
||||
return insertMessageInbox(masterSecret, retrieved, contentLocation, threadId,
|
||||
Types.BASE_INBOX_TYPE | Types.ENCRYPTION_SYMMETRIC_BIT |
|
||||
(retrieved.isPushMessage() ? Types.PUSH_MESSAGE_BIT : 0));
|
||||
long type = Types.BASE_INBOX_TYPE;
|
||||
|
||||
if (masterSecret.getMasterSecret().isPresent()) {
|
||||
type |= Types.ENCRYPTION_SYMMETRIC_BIT;
|
||||
} else {
|
||||
type |= Types.ENCRYPTION_ASYMMETRIC_BIT;
|
||||
}
|
||||
|
||||
public Pair<Long, Long> insertSecureMessageInbox(MasterSecret masterSecret,
|
||||
IncomingMediaMessage retrieved,
|
||||
String contentLocation, long threadId)
|
||||
throws MmsException
|
||||
{
|
||||
return insertMessageInbox(masterSecret, retrieved, contentLocation, threadId,
|
||||
Types.BASE_INBOX_TYPE | Types.SECURE_MESSAGE_BIT |
|
||||
Types.ENCRYPTION_REMOTE_BIT);
|
||||
if (retrieved.isPushMessage()) {
|
||||
type |= Types.PUSH_MESSAGE_BIT;
|
||||
}
|
||||
|
||||
public Pair<Long, Long> insertSecureDecryptedMessageInbox(MasterSecret masterSecret,
|
||||
return insertMessageInbox(masterSecret, retrieved, contentLocation, threadId, type);
|
||||
}
|
||||
|
||||
public Pair<Long, Long> insertSecureDecryptedMessageInbox(MasterSecretUnion masterSecret,
|
||||
IncomingMediaMessage retrieved,
|
||||
long threadId)
|
||||
throws MmsException
|
||||
{
|
||||
return insertMessageInbox(masterSecret, retrieved, "", threadId,
|
||||
Types.BASE_INBOX_TYPE | Types.SECURE_MESSAGE_BIT |
|
||||
Types.ENCRYPTION_SYMMETRIC_BIT |
|
||||
(retrieved.isPushMessage() ? Types.PUSH_MESSAGE_BIT : 0));
|
||||
long type = Types.BASE_INBOX_TYPE | Types.SECURE_MESSAGE_BIT;
|
||||
|
||||
if (masterSecret.getMasterSecret().isPresent()) {
|
||||
type |= Types.ENCRYPTION_SYMMETRIC_BIT;
|
||||
} else {
|
||||
type |= Types.ENCRYPTION_ASYMMETRIC_BIT;
|
||||
}
|
||||
|
||||
if (retrieved.isPushMessage()) {
|
||||
type |= Types.PUSH_MESSAGE_BIT;
|
||||
}
|
||||
|
||||
return insertMessageInbox(masterSecret, retrieved, "", threadId, type);
|
||||
}
|
||||
|
||||
public Pair<Long, Long> insertMessageInbox(@NonNull NotificationInd notification) {
|
||||
@ -680,11 +704,15 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
jobManager.add(new TrimThreadJob(context, threadId));
|
||||
}
|
||||
|
||||
public long insertMessageOutbox(MasterSecret masterSecret, OutgoingMediaMessage message,
|
||||
public long insertMessageOutbox(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull OutgoingMediaMessage message,
|
||||
long threadId, boolean forceSms, long timestamp)
|
||||
throws MmsException
|
||||
{
|
||||
long type = Types.BASE_OUTBOX_TYPE | Types.ENCRYPTION_SYMMETRIC_BIT;
|
||||
long type = Types.BASE_OUTBOX_TYPE;
|
||||
|
||||
if (masterSecret.getMasterSecret().isPresent()) type |= Types.ENCRYPTION_SYMMETRIC_BIT;
|
||||
else type |= Types.ENCRYPTION_ASYMMETRIC_BIT;
|
||||
|
||||
if (message.isSecure()) type |= Types.SECURE_MESSAGE_BIT;
|
||||
if (forceSms) type |= Types.MESSAGE_FORCE_SMS_BIT;
|
||||
@ -727,14 +755,23 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
long messageId = insertMediaMessage(masterSecret, sendRequest.getPduHeaders(),
|
||||
long messageId = insertMediaMessage(masterSecret,
|
||||
sendRequest.getPduHeaders(),
|
||||
sendRequest.getBody(), contentValues);
|
||||
jobManager.add(new TrimThreadJob(context, threadId));
|
||||
|
||||
return messageId;
|
||||
}
|
||||
|
||||
private long insertMediaMessage(MasterSecret masterSecret,
|
||||
private String getEncryptedBody(MasterSecretUnion masterSecret, String body) {
|
||||
if (masterSecret.getMasterSecret().isPresent()) {
|
||||
return new MasterCipher(masterSecret.getMasterSecret().get()).encryptBody(body);
|
||||
} else {
|
||||
return new AsymmetricMasterCipher(masterSecret.getAsymmetricMasterSecret().get()).encryptBody(body);
|
||||
}
|
||||
}
|
||||
|
||||
private long insertMediaMessage(MasterSecretUnion masterSecret,
|
||||
PduHeaders headers,
|
||||
PduBody body,
|
||||
ContentValues contentValues)
|
||||
@ -744,12 +781,14 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
PartDatabase partsDatabase = DatabaseFactory.getPartDatabase(context);
|
||||
MmsAddressDatabase addressDatabase = DatabaseFactory.getMmsAddressDatabase(context);
|
||||
|
||||
if (Types.isSymmetricEncryption(contentValues.getAsLong(MESSAGE_BOX))) {
|
||||
if (Types.isSymmetricEncryption(contentValues.getAsLong(MESSAGE_BOX)) ||
|
||||
Types.isAsymmetricEncryption(contentValues.getAsLong(MESSAGE_BOX)))
|
||||
{
|
||||
String messageText = PartParser.getMessageText(body);
|
||||
body = PartParser.getSupportedMediaParts(body);
|
||||
|
||||
if (!TextUtils.isEmpty(messageText)) {
|
||||
contentValues.put(BODY, new MasterCipher(masterSecret).encryptBody(messageText));
|
||||
contentValues.put(BODY, getEncryptedBody(masterSecret, messageText));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1105,6 +1144,8 @@ public class MmsDatabase extends MessagingDatabase {
|
||||
return new DisplayRecord.Body(masterCipher.decryptBody(body), true);
|
||||
} else if (!TextUtils.isEmpty(body) && masterCipher == null && Types.isSymmetricEncryption(box)) {
|
||||
return new DisplayRecord.Body(body, false);
|
||||
} else if (!TextUtils.isEmpty(body) && Types.isAsymmetricEncryption(box)) {
|
||||
return new DisplayRecord.Body(body, false);
|
||||
} else {
|
||||
return new DisplayRecord.Body(body == null ? "" : body, true);
|
||||
}
|
||||
|
@ -175,9 +175,7 @@ public interface MmsSmsColumns {
|
||||
}
|
||||
|
||||
public static boolean isDecryptInProgressType(long type) {
|
||||
return
|
||||
(type & ENCRYPTION_REMOTE_BIT) != 0 ||
|
||||
(type & ENCRYPTION_ASYMMETRIC_BIT) != 0;
|
||||
return (type & ENCRYPTION_ASYMMETRIC_BIT) != 0;
|
||||
}
|
||||
|
||||
public static boolean isNoRemoteSessionType(long type) {
|
||||
@ -185,7 +183,8 @@ public interface MmsSmsColumns {
|
||||
}
|
||||
|
||||
public static boolean isLegacyType(long type) {
|
||||
return (type & ENCRYPTION_REMOTE_LEGACY_BIT) != 0;
|
||||
return (type & ENCRYPTION_REMOTE_LEGACY_BIT) != 0 ||
|
||||
(type & ENCRYPTION_REMOTE_BIT) != 0;
|
||||
}
|
||||
|
||||
public static long translateFromSystemBaseType(long theirType) {
|
||||
|
@ -30,6 +30,7 @@ import android.util.Pair;
|
||||
import org.thoughtcrime.securesms.crypto.DecryptingPartInputStream;
|
||||
import org.thoughtcrime.securesms.crypto.EncryptingPartOutputStream;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.util.BitmapDecodingException;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
@ -222,7 +223,7 @@ public class PartDatabase extends Database {
|
||||
}
|
||||
}
|
||||
|
||||
void insertParts(MasterSecret masterSecret, long mmsId, PduBody body) throws MmsException {
|
||||
void insertParts(MasterSecretUnion masterSecret, long mmsId, PduBody body) throws MmsException {
|
||||
for (int i=0;i<body.getPartsNum();i++) {
|
||||
PduPart part = body.getPart(i);
|
||||
PartId partId = insertPart(masterSecret, part, mmsId, part.getThumbnail());
|
||||
@ -432,13 +433,13 @@ public class PartDatabase extends Database {
|
||||
return part;
|
||||
}
|
||||
|
||||
private PartId insertPart(MasterSecret masterSecret, PduPart part, long mmsId, Bitmap thumbnail) throws MmsException {
|
||||
private PartId insertPart(MasterSecretUnion masterSecret, PduPart part, long mmsId, Bitmap thumbnail) throws MmsException {
|
||||
Log.w(TAG, "inserting part to mms " + mmsId);
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
Pair<File, Long> partData = null;
|
||||
|
||||
if (part.getData() != null || part.getDataUri() != null) {
|
||||
partData = writePartData(masterSecret, part);
|
||||
if ((part.getData() != null || part.getDataUri() != null) && masterSecret.getMasterSecret().isPresent()) {
|
||||
partData = writePartData(masterSecret.getMasterSecret().get(), part);
|
||||
Log.w(TAG, "Wrote part to file: " + partData.first.getAbsolutePath());
|
||||
}
|
||||
|
||||
@ -453,12 +454,12 @@ public class PartDatabase extends Database {
|
||||
long partRowId = database.insert(TABLE_NAME, null, contentValues);
|
||||
PartId partId = new PartId(partRowId, part.getUniqueId());
|
||||
|
||||
if (thumbnail != null) {
|
||||
if (thumbnail != null && masterSecret.getMasterSecret().isPresent()) {
|
||||
Log.w(TAG, "inserting pre-generated thumbnail");
|
||||
ThumbnailData data = new ThumbnailData(thumbnail);
|
||||
updatePartThumbnail(masterSecret, partId, part, data.toDataStream(), data.getAspectRatio());
|
||||
updatePartThumbnail(masterSecret.getMasterSecret().get(), partId, part, data.toDataStream(), data.getAspectRatio());
|
||||
} else if (!part.isInProgress()) {
|
||||
thumbnailExecutor.submit(new ThumbnailFetchCallable(masterSecret, partId));
|
||||
thumbnailExecutor.submit(new ThumbnailFetchCallable(masterSecret.getMasterSecret().get(), partId));
|
||||
}
|
||||
|
||||
return partId;
|
||||
|
@ -213,10 +213,6 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_NO_SESSION_BIT);
|
||||
}
|
||||
|
||||
public void markAsDecrypting(long id) {
|
||||
updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_BIT);
|
||||
}
|
||||
|
||||
public void markAsLegacyVersion(long id) {
|
||||
updateTypeBitmask(id, Types.ENCRYPTION_MASK, Types.ENCRYPTION_REMOTE_LEGACY_BIT);
|
||||
}
|
||||
@ -461,7 +457,7 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
}
|
||||
|
||||
public Cursor getDecryptInProgressMessages() {
|
||||
String where = TYPE + " & " + (Types.ENCRYPTION_REMOTE_BIT | Types.ENCRYPTION_ASYMMETRIC_BIT) + " != 0";
|
||||
String where = TYPE + " & " + (Types.ENCRYPTION_ASYMMETRIC_BIT) + " != 0";
|
||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||
return db.query(TABLE_NAME, MESSAGE_PROJECTION, where, null, null, null, null);
|
||||
}
|
||||
|
@ -22,13 +22,13 @@ public class AxolotlStorageModule {
|
||||
@Provides SignedPreKeyStoreFactory provideSignedPreKeyStoreFactory() {
|
||||
return new SignedPreKeyStoreFactory() {
|
||||
@Override
|
||||
public SignedPreKeyStore create(MasterSecret masterSecret) {
|
||||
return new TextSecureAxolotlStore(context, masterSecret);
|
||||
public SignedPreKeyStore create() {
|
||||
return new TextSecureAxolotlStore(context);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static interface SignedPreKeyStoreFactory {
|
||||
public SignedPreKeyStore create(MasterSecret masterSecret);
|
||||
public SignedPreKeyStore create();
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import android.content.Context;
|
||||
|
||||
import org.thoughtcrime.securesms.BuildConfig;
|
||||
import org.thoughtcrime.securesms.DeviceListActivity;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureAxolotlStore;
|
||||
import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob;
|
||||
import org.thoughtcrime.securesms.jobs.CleanPreKeysJob;
|
||||
@ -61,12 +60,12 @@ public class TextSecureCommunicationModule {
|
||||
@Provides TextSecureMessageSenderFactory provideTextSecureMessageSenderFactory() {
|
||||
return new TextSecureMessageSenderFactory() {
|
||||
@Override
|
||||
public TextSecureMessageSender create(MasterSecret masterSecret) {
|
||||
public TextSecureMessageSender create() {
|
||||
return new TextSecureMessageSender(BuildConfig.PUSH_URL,
|
||||
new TextSecurePushTrustStore(context),
|
||||
TextSecurePreferences.getLocalNumber(context),
|
||||
TextSecurePreferences.getPushServerPassword(context),
|
||||
new TextSecureAxolotlStore(context, masterSecret),
|
||||
new TextSecureAxolotlStore(context),
|
||||
Optional.of((TextSecureMessageSender.EventListener)
|
||||
new SecurityEventListener(context)));
|
||||
}
|
||||
@ -80,7 +79,7 @@ public class TextSecureCommunicationModule {
|
||||
}
|
||||
|
||||
public static interface TextSecureMessageSenderFactory {
|
||||
public TextSecureMessageSender create(MasterSecret masterSecret);
|
||||
public TextSecureMessageSender create();
|
||||
}
|
||||
|
||||
private static class DynamicCredentialsProvider implements CredentialsProvider {
|
||||
|
@ -2,6 +2,8 @@ package org.thoughtcrime.securesms.groups;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
@ -9,6 +11,8 @@ import com.google.protobuf.ByteString;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
@ -36,10 +40,10 @@ public class GroupMessageProcessor {
|
||||
|
||||
private static final String TAG = GroupMessageProcessor.class.getSimpleName();
|
||||
|
||||
public static void process(Context context,
|
||||
MasterSecret masterSecret,
|
||||
TextSecureEnvelope envelope,
|
||||
TextSecureDataMessage message)
|
||||
public static void process(@NonNull Context context,
|
||||
@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull TextSecureEnvelope envelope,
|
||||
@NonNull TextSecureDataMessage message)
|
||||
{
|
||||
if (!message.getGroupInfo().isPresent() || message.getGroupInfo().get().getGroupId() == null) {
|
||||
Log.w(TAG, "Received group message with no id! Ignoring...");
|
||||
@ -62,10 +66,10 @@ public class GroupMessageProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
private static void handleGroupCreate(Context context,
|
||||
MasterSecret masterSecret,
|
||||
TextSecureEnvelope envelope,
|
||||
TextSecureGroup group)
|
||||
private static void handleGroupCreate(@NonNull Context context,
|
||||
@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull TextSecureEnvelope envelope,
|
||||
@NonNull TextSecureGroup group)
|
||||
{
|
||||
GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
|
||||
byte[] id = group.getGroupId();
|
||||
@ -81,11 +85,11 @@ public class GroupMessageProcessor {
|
||||
storeMessage(context, masterSecret, envelope, group, builder.build());
|
||||
}
|
||||
|
||||
private static void handleGroupUpdate(Context context,
|
||||
MasterSecret masterSecret,
|
||||
TextSecureEnvelope envelope,
|
||||
TextSecureGroup group,
|
||||
GroupRecord groupRecord)
|
||||
private static void handleGroupUpdate(@NonNull Context context,
|
||||
@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull TextSecureEnvelope envelope,
|
||||
@NonNull TextSecureGroup group,
|
||||
@NonNull GroupRecord groupRecord)
|
||||
{
|
||||
|
||||
GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
|
||||
@ -131,11 +135,11 @@ public class GroupMessageProcessor {
|
||||
storeMessage(context, masterSecret, envelope, group, builder.build());
|
||||
}
|
||||
|
||||
private static void handleGroupLeave(Context context,
|
||||
MasterSecret masterSecret,
|
||||
TextSecureEnvelope envelope,
|
||||
TextSecureGroup group,
|
||||
GroupRecord record)
|
||||
private static void handleGroupLeave(@NonNull Context context,
|
||||
@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull TextSecureEnvelope envelope,
|
||||
@NonNull TextSecureGroup group,
|
||||
@NonNull GroupRecord record)
|
||||
{
|
||||
GroupDatabase database = DatabaseFactory.getGroupDatabase(context);
|
||||
byte[] id = group.getGroupId();
|
||||
@ -152,9 +156,11 @@ public class GroupMessageProcessor {
|
||||
}
|
||||
|
||||
|
||||
private static void storeMessage(Context context, MasterSecret masterSecret,
|
||||
TextSecureEnvelope envelope, TextSecureGroup group,
|
||||
GroupContext storage)
|
||||
private static void storeMessage(@NonNull Context context,
|
||||
@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull TextSecureEnvelope envelope,
|
||||
@NonNull TextSecureGroup group,
|
||||
@NonNull GroupContext storage)
|
||||
{
|
||||
if (group.getAvatar().isPresent()) {
|
||||
ApplicationContext.getInstance(context).getJobManager()
|
||||
@ -167,7 +173,7 @@ public class GroupMessageProcessor {
|
||||
IncomingGroupMessage groupMessage = new IncomingGroupMessage(incoming, storage, body);
|
||||
|
||||
Pair<Long, Long> messageAndThreadId = smsDatabase.insertMessageInbox(masterSecret, groupMessage);
|
||||
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
|
||||
MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), messageAndThreadId.second);
|
||||
}
|
||||
|
||||
private static GroupContext.Builder createGroupContext(TextSecureGroup group) {
|
||||
|
@ -3,15 +3,15 @@ package org.thoughtcrime.securesms.jobs;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
||||
import org.thoughtcrime.securesms.crypto.AsymmetricMasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||
import org.thoughtcrime.securesms.crypto.MediaKey;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.PartDatabase;
|
||||
import org.thoughtcrime.securesms.database.PartDatabase.PartId;
|
||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.jobqueue.JobParameters;
|
||||
import org.whispersystems.jobqueue.requirements.NetworkRequirement;
|
||||
@ -114,9 +114,9 @@ public class AttachmentDownloadJob extends MasterSecretJob implements Injectable
|
||||
throws InvalidPartException
|
||||
{
|
||||
try {
|
||||
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
||||
AsymmetricMasterSecret asymmetricMasterSecret = MasterSecretUtil.getAsymmetricMasterSecret(context, masterSecret);
|
||||
long id = Long.parseLong(Util.toIsoString(part.getContentLocation()));
|
||||
byte[] key = masterCipher.decryptBytes(Base64.decode(Util.toIsoString(part.getContentDisposition())));
|
||||
byte[] key = MediaKey.getDecrypted(masterSecret, asymmetricMasterSecret, Util.toIsoString(part.getContentDisposition()));
|
||||
String relay = null;
|
||||
|
||||
if (part.getName() != null) {
|
||||
|
@ -51,7 +51,7 @@ public class CleanPreKeysJob extends MasterSecretJob implements InjectableType {
|
||||
@Override
|
||||
public void onRun(MasterSecret masterSecret) throws IOException {
|
||||
try {
|
||||
SignedPreKeyStore signedPreKeyStore = signedPreKeyStoreFactory.create(masterSecret);
|
||||
SignedPreKeyStore signedPreKeyStore = signedPreKeyStoreFactory.create();
|
||||
SignedPreKeyEntity currentSignedPreKey = accountManager.getSignedPreKey();
|
||||
|
||||
if (currentSignedPreKey == null) return;
|
||||
|
@ -7,9 +7,8 @@ import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.PreKeyUtil;
|
||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||
import org.thoughtcrime.securesms.util.ParcelUtil;
|
||||
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.whispersystems.jobqueue.EncryptionKeys;
|
||||
import org.whispersystems.jobqueue.JobParameters;
|
||||
import org.whispersystems.jobqueue.requirements.NetworkRequirement;
|
||||
import org.whispersystems.libaxolotl.IdentityKeyPair;
|
||||
@ -21,17 +20,19 @@ import java.io.IOException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class CreateSignedPreKeyJob extends ContextJob implements InjectableType {
|
||||
public class CreateSignedPreKeyJob extends MasterSecretJob implements InjectableType {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String TAG = CreateSignedPreKeyJob.class.getSimpleName();
|
||||
|
||||
@Inject transient TextSecureAccountManager accountManager;
|
||||
|
||||
public CreateSignedPreKeyJob(Context context, MasterSecret masterSecret) {
|
||||
public CreateSignedPreKeyJob(Context context) {
|
||||
super(context, JobParameters.newBuilder()
|
||||
.withPersistence()
|
||||
.withRequirement(new NetworkRequirement(context))
|
||||
.withEncryption(new EncryptionKeys(ParcelUtil.serialize(masterSecret)))
|
||||
.withRequirement(new MasterSecretRequirement(context))
|
||||
.withGroupId(CreateSignedPreKeyJob.class.getSimpleName())
|
||||
.create());
|
||||
}
|
||||
@ -40,16 +41,14 @@ public class CreateSignedPreKeyJob extends ContextJob implements InjectableType
|
||||
public void onAdded() {}
|
||||
|
||||
@Override
|
||||
public void onRun() throws IOException {
|
||||
MasterSecret masterSecret = ParcelUtil.deserialize(getEncryptionKeys().getEncoded(), MasterSecret.CREATOR);
|
||||
|
||||
public void onRun(MasterSecret masterSecret) throws IOException {
|
||||
if (TextSecurePreferences.isSignedPreKeyRegistered(context)) {
|
||||
Log.w(TAG, "Signed prekey already registered...");
|
||||
return;
|
||||
}
|
||||
|
||||
IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
|
||||
SignedPreKeyRecord signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(context, masterSecret, identityKeyPair);
|
||||
IdentityKeyPair identityKeyPair = IdentityKeyUtil.getIdentityKeyPair(context);
|
||||
SignedPreKeyRecord signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(context, identityKeyPair);
|
||||
|
||||
accountManager.setSignedPreKey(signedPreKeyRecord);
|
||||
TextSecurePreferences.setSignedPreKeyRegistered(context, true);
|
||||
@ -59,7 +58,7 @@ public class CreateSignedPreKeyJob extends ContextJob implements InjectableType
|
||||
public void onCanceled() {}
|
||||
|
||||
@Override
|
||||
public boolean onShouldRetry(Exception exception) {
|
||||
public boolean onShouldRetryThrowable(Exception exception) {
|
||||
if (exception instanceof PushNetworkException) return true;
|
||||
return false;
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ public class DeliveryReceiptJob extends ContextJob implements InjectableType {
|
||||
@Override
|
||||
public void onRun() throws IOException {
|
||||
Log.w("DeliveryReceiptJob", "Sending delivery receipt...");
|
||||
TextSecureMessageSender messageSender = messageSenderFactory.create(null);
|
||||
TextSecureMessageSender messageSender = messageSenderFactory.create();
|
||||
TextSecureAddress textSecureAddress = new TextSecureAddress(destination, Optional.fromNullable(relay));
|
||||
|
||||
messageSender.sendDeliveryReceipt(textSecureAddress, timestamp);
|
||||
|
103
src/org/thoughtcrime/securesms/jobs/MasterSecretDecryptJob.java
Normal file
103
src/org/thoughtcrime/securesms/jobs/MasterSecretDecryptJob.java
Normal file
@ -0,0 +1,103 @@
|
||||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.AsymmetricMasterCipher;
|
||||
import org.thoughtcrime.securesms.crypto.AsymmetricMasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.whispersystems.jobqueue.JobParameters;
|
||||
import org.whispersystems.libaxolotl.InvalidMessageException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class MasterSecretDecryptJob extends MasterSecretJob {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final String TAG = MasterSecretDecryptJob.class.getSimpleName();
|
||||
|
||||
public MasterSecretDecryptJob(Context context) {
|
||||
super(context, JobParameters.newBuilder()
|
||||
.withRequirement(new MasterSecretRequirement(context))
|
||||
.create());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRun(MasterSecret masterSecret) {
|
||||
EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
SmsDatabase.Reader smsReader = smsDatabase.getDecryptInProgressMessages(masterSecret);
|
||||
|
||||
SmsMessageRecord smsRecord;
|
||||
|
||||
while ((smsRecord = smsReader.getNext()) != null) {
|
||||
try {
|
||||
String body = getAsymmetricDecryptedBody(masterSecret, smsRecord.getBody().getBody());
|
||||
smsDatabase.updateMessageBody(new MasterSecretUnion(masterSecret), smsRecord.getId(), body);
|
||||
} catch (InvalidMessageException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context);
|
||||
MmsDatabase.Reader mmsReader = mmsDatabase.getDecryptInProgressMessages(masterSecret);
|
||||
|
||||
MessageRecord mmsRecord;
|
||||
|
||||
while ((mmsRecord = mmsReader.getNext()) != null) {
|
||||
try {
|
||||
String body = getAsymmetricDecryptedBody(masterSecret, mmsRecord.getBody().getBody());
|
||||
mmsDatabase.updateMessageBody(new MasterSecretUnion(masterSecret), mmsRecord.getId(), body);
|
||||
} catch (InvalidMessageException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
}
|
||||
|
||||
smsReader.close();
|
||||
mmsReader.close();
|
||||
|
||||
MessageNotifier.updateNotification(context, masterSecret);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onShouldRetryThrowable(Exception exception) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdded() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanceled() {
|
||||
|
||||
}
|
||||
|
||||
private String getAsymmetricDecryptedBody(MasterSecret masterSecret, String body)
|
||||
throws InvalidMessageException
|
||||
{
|
||||
try {
|
||||
AsymmetricMasterSecret asymmetricMasterSecret = MasterSecretUtil.getAsymmetricMasterSecret(context, masterSecret);
|
||||
AsymmetricMasterCipher asymmetricMasterCipher = new AsymmetricMasterCipher(asymmetricMasterSecret);
|
||||
|
||||
if (TextUtils.isEmpty(body)) return "";
|
||||
else return asymmetricMasterCipher.decryptBody(body);
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -2,21 +2,17 @@ package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
|
||||
import org.thoughtcrime.securesms.mms.ApnUnavailableException;
|
||||
import org.thoughtcrime.securesms.mms.CompatMmsConnection;
|
||||
import org.thoughtcrime.securesms.mms.IncomingLollipopMmsConnection;
|
||||
import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
|
||||
import org.thoughtcrime.securesms.mms.IncomingLegacyMmsConnection;
|
||||
import org.thoughtcrime.securesms.mms.IncomingMmsConnection;
|
||||
import org.thoughtcrime.securesms.mms.MmsRadioException;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.protocol.WirePrefix;
|
||||
@ -154,8 +150,8 @@ public class MmsDownloadJob extends MasterSecretJob {
|
||||
database.markAsLegacyVersion(messageId, threadId);
|
||||
messageAndThreadId = new Pair<>(messageId, threadId);
|
||||
} else {
|
||||
messageAndThreadId = database.insertMessageInbox(masterSecret, message,
|
||||
contentLocation, threadId);
|
||||
messageAndThreadId = database.insertMessageInbox(new MasterSecretUnion(masterSecret),
|
||||
message, contentLocation, threadId);
|
||||
database.delete(messageId);
|
||||
}
|
||||
|
||||
|
@ -59,7 +59,7 @@ public class MultiDeviceContactUpdateJob extends MasterSecretJob implements Inje
|
||||
public void onRun(MasterSecret masterSecret)
|
||||
throws IOException, UntrustedIdentityException, NetworkException
|
||||
{
|
||||
TextSecureMessageSender messageSender = messageSenderFactory.create(masterSecret);
|
||||
TextSecureMessageSender messageSender = messageSenderFactory.create();
|
||||
File contactDataFile = createTempFile("multidevice-contact-update");
|
||||
|
||||
try {
|
||||
|
@ -47,7 +47,7 @@ public class MultiDeviceGroupUpdateJob extends MasterSecretJob implements Inject
|
||||
|
||||
@Override
|
||||
public void onRun(MasterSecret masterSecret) throws Exception {
|
||||
TextSecureMessageSender messageSender = messageSenderFactory.create(masterSecret);
|
||||
TextSecureMessageSender messageSender = messageSenderFactory.create();
|
||||
File contactDataFile = createTempFile("multidevice-contact-update");
|
||||
GroupDatabase.Reader reader = null;
|
||||
|
||||
|
@ -1,11 +1,15 @@
|
||||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||
import org.thoughtcrime.securesms.crypto.SecurityEvent;
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureAxolotlStore;
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
|
||||
@ -15,7 +19,6 @@ import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||
import org.thoughtcrime.securesms.database.PushDatabase;
|
||||
import org.thoughtcrime.securesms.groups.GroupMessageProcessor;
|
||||
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
|
||||
import org.thoughtcrime.securesms.mms.IncomingMediaMessage;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingMediaMessage;
|
||||
import org.thoughtcrime.securesms.mms.OutgoingSecureMediaMessage;
|
||||
@ -47,9 +50,9 @@ import org.whispersystems.libaxolotl.state.SessionStore;
|
||||
import org.whispersystems.libaxolotl.util.guava.Optional;
|
||||
import org.whispersystems.textsecure.api.crypto.TextSecureCipher;
|
||||
import org.whispersystems.textsecure.api.messages.TextSecureContent;
|
||||
import org.whispersystems.textsecure.api.messages.TextSecureDataMessage;
|
||||
import org.whispersystems.textsecure.api.messages.TextSecureEnvelope;
|
||||
import org.whispersystems.textsecure.api.messages.TextSecureGroup;
|
||||
import org.whispersystems.textsecure.api.messages.TextSecureDataMessage;
|
||||
import org.whispersystems.textsecure.api.messages.multidevice.RequestMessage;
|
||||
import org.whispersystems.textsecure.api.messages.multidevice.SentTranscriptMessage;
|
||||
import org.whispersystems.textsecure.api.messages.multidevice.TextSecureSyncMessage;
|
||||
@ -59,9 +62,9 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
import ws.com.google.android.mms.MmsException;
|
||||
|
||||
public class PushDecryptJob extends MasterSecretJob {
|
||||
public class PushDecryptJob extends ContextJob {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final long serialVersionUID = 2L;
|
||||
|
||||
public static final String TAG = PushDecryptJob.class.getSimpleName();
|
||||
|
||||
@ -75,7 +78,6 @@ public class PushDecryptJob extends MasterSecretJob {
|
||||
public PushDecryptJob(Context context, long pushMessageId, long smsMessageId, String sender) {
|
||||
super(context, JobParameters.newBuilder()
|
||||
.withPersistence()
|
||||
.withRequirement(new MasterSecretRequirement(context))
|
||||
.withGroupId(sender)
|
||||
.withWakeLock(true, 5, TimeUnit.SECONDS)
|
||||
.create());
|
||||
@ -91,18 +93,29 @@ public class PushDecryptJob extends MasterSecretJob {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRun(MasterSecret masterSecret) throws NoSuchMessageException {
|
||||
public void onRun() throws NoSuchMessageException {
|
||||
if (!IdentityKeyUtil.hasIdentityKey(context)) {
|
||||
Log.w(TAG, "Skipping job, waiting for migration...");
|
||||
return;
|
||||
}
|
||||
|
||||
MasterSecret masterSecret = KeyCachingService.getMasterSecret(context);
|
||||
PushDatabase database = DatabaseFactory.getPushDatabase(context);
|
||||
TextSecureEnvelope envelope = database.get(messageId);
|
||||
Optional<Long> optionalSmsMessageId = smsMessageId > 0 ? Optional.of(smsMessageId) :
|
||||
Optional.<Long>absent();
|
||||
|
||||
handleMessage(masterSecret, envelope, optionalSmsMessageId);
|
||||
MasterSecretUnion masterSecretUnion;
|
||||
|
||||
if (masterSecret == null) masterSecretUnion = new MasterSecretUnion(MasterSecretUtil.getAsymmetricMasterSecret(context, null));
|
||||
else masterSecretUnion = new MasterSecretUnion(masterSecret);
|
||||
|
||||
handleMessage(masterSecretUnion, envelope, optionalSmsMessageId);
|
||||
database.delete(messageId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onShouldRetryThrowable(Exception exception) {
|
||||
public boolean onShouldRetry(Exception exception) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -111,9 +124,9 @@ public class PushDecryptJob extends MasterSecretJob {
|
||||
|
||||
}
|
||||
|
||||
private void handleMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, Optional<Long> smsMessageId) {
|
||||
private void handleMessage(MasterSecretUnion masterSecret, TextSecureEnvelope envelope, Optional<Long> smsMessageId) {
|
||||
try {
|
||||
AxolotlStore axolotlStore = new TextSecureAxolotlStore(context, masterSecret);
|
||||
AxolotlStore axolotlStore = new TextSecureAxolotlStore(context);
|
||||
TextSecureAddress localAddress = new TextSecureAddress(TextSecurePreferences.getLocalNumber(context));
|
||||
TextSecureCipher cipher = new TextSecureCipher(localAddress, axolotlStore);
|
||||
|
||||
@ -157,8 +170,10 @@ public class PushDecryptJob extends MasterSecretJob {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleEndSessionMessage(MasterSecret masterSecret, TextSecureEnvelope envelope,
|
||||
TextSecureDataMessage message, Optional<Long> smsMessageId)
|
||||
private void handleEndSessionMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull TextSecureEnvelope envelope,
|
||||
@NonNull TextSecureDataMessage message,
|
||||
@NonNull Optional<Long> smsMessageId)
|
||||
{
|
||||
EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
IncomingTextMessage incomingTextMessage = new IncomingTextMessage(envelope.getSource(),
|
||||
@ -171,20 +186,25 @@ public class PushDecryptJob extends MasterSecretJob {
|
||||
if (!smsMessageId.isPresent()) {
|
||||
IncomingEndSessionMessage incomingEndSessionMessage = new IncomingEndSessionMessage(incomingTextMessage);
|
||||
Pair<Long, Long> messageAndThreadId = smsDatabase.insertMessageInbox(masterSecret, incomingEndSessionMessage);
|
||||
|
||||
threadId = messageAndThreadId.second;
|
||||
} else {
|
||||
smsDatabase.markAsEndSession(smsMessageId.get());
|
||||
threadId = smsDatabase.getThreadIdForMessage(smsMessageId.get());
|
||||
}
|
||||
|
||||
SessionStore sessionStore = new TextSecureSessionStore(context, masterSecret);
|
||||
SessionStore sessionStore = new TextSecureSessionStore(context);
|
||||
sessionStore.deleteAllSessions(envelope.getSource());
|
||||
|
||||
SecurityEvent.broadcastSecurityUpdateEvent(context, threadId);
|
||||
MessageNotifier.updateNotification(context, masterSecret, threadId);
|
||||
MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), threadId);
|
||||
}
|
||||
|
||||
private void handleGroupMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, TextSecureDataMessage message, Optional<Long> smsMessageId) {
|
||||
private void handleGroupMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull TextSecureEnvelope envelope,
|
||||
@NonNull TextSecureDataMessage message,
|
||||
@NonNull Optional<Long> smsMessageId)
|
||||
{
|
||||
GroupMessageProcessor.process(context, masterSecret, envelope, message);
|
||||
|
||||
if (smsMessageId.isPresent()) {
|
||||
@ -192,7 +212,9 @@ public class PushDecryptJob extends MasterSecretJob {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSynchronizeSentMessage(MasterSecret masterSecret, SentTranscriptMessage message, Optional<Long> smsMessageId)
|
||||
private void handleSynchronizeSentMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull SentTranscriptMessage message,
|
||||
@NonNull Optional<Long> smsMessageId)
|
||||
throws MmsException
|
||||
{
|
||||
if (message.getMessage().getAttachments().isPresent()) {
|
||||
@ -202,7 +224,9 @@ public class PushDecryptJob extends MasterSecretJob {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSynchronizeRequestMessage(MasterSecret masterSecret, RequestMessage message) {
|
||||
private void handleSynchronizeRequestMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull RequestMessage message)
|
||||
{
|
||||
if (message.isContactsRequest()) {
|
||||
ApplicationContext.getInstance(context)
|
||||
.getJobManager()
|
||||
@ -216,8 +240,10 @@ public class PushDecryptJob extends MasterSecretJob {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleMediaMessage(MasterSecret masterSecret, TextSecureEnvelope envelope,
|
||||
TextSecureDataMessage message, Optional<Long> smsMessageId)
|
||||
private void handleMediaMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull TextSecureEnvelope envelope,
|
||||
@NonNull TextSecureDataMessage message,
|
||||
@NonNull Optional<Long> smsMessageId)
|
||||
throws MmsException
|
||||
{
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
@ -239,12 +265,12 @@ public class PushDecryptJob extends MasterSecretJob {
|
||||
DatabaseFactory.getSmsDatabase(context).deleteMessage(smsMessageId.get());
|
||||
}
|
||||
|
||||
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
|
||||
MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), messageAndThreadId.second);
|
||||
}
|
||||
|
||||
private void handleSynchronizeSentMediaMessage(MasterSecret masterSecret,
|
||||
SentTranscriptMessage message,
|
||||
Optional<Long> smsMessageId)
|
||||
private void handleSynchronizeSentMediaMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull SentTranscriptMessage message,
|
||||
@NonNull Optional<Long> smsMessageId)
|
||||
throws MmsException
|
||||
{
|
||||
MmsDatabase database = DatabaseFactory.getMmsDatabase(context);
|
||||
@ -270,8 +296,10 @@ public class PushDecryptJob extends MasterSecretJob {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleTextMessage(MasterSecret masterSecret, TextSecureEnvelope envelope,
|
||||
TextSecureDataMessage message, Optional<Long> smsMessageId)
|
||||
private void handleTextMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull TextSecureEnvelope envelope,
|
||||
@NonNull TextSecureDataMessage message,
|
||||
@NonNull Optional<Long> smsMessageId)
|
||||
{
|
||||
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
String body = message.getBody().isPresent() ? message.getBody().get() : "";
|
||||
@ -287,16 +315,15 @@ public class PushDecryptJob extends MasterSecretJob {
|
||||
message.getGroupInfo());
|
||||
|
||||
textMessage = new IncomingEncryptedMessage(textMessage, body);
|
||||
|
||||
messageAndThreadId = database.insertMessageInbox(masterSecret, textMessage);
|
||||
}
|
||||
|
||||
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
|
||||
MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), messageAndThreadId.second);
|
||||
}
|
||||
|
||||
private void handleSynchronizeSentTextMessage(MasterSecret masterSecret,
|
||||
SentTranscriptMessage message,
|
||||
Optional<Long> smsMessageId)
|
||||
private void handleSynchronizeSentTextMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull SentTranscriptMessage message,
|
||||
@NonNull Optional<Long> smsMessageId)
|
||||
{
|
||||
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
Recipients recipients = getSyncMessageDestination(message);
|
||||
@ -315,55 +342,70 @@ public class PushDecryptJob extends MasterSecretJob {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleInvalidVersionMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, Optional<Long> smsMessageId) {
|
||||
private void handleInvalidVersionMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull TextSecureEnvelope envelope,
|
||||
@NonNull Optional<Long> smsMessageId)
|
||||
{
|
||||
EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
|
||||
if (!smsMessageId.isPresent()) {
|
||||
Pair<Long, Long> messageAndThreadId = insertPlaceholder(masterSecret, envelope);
|
||||
Pair<Long, Long> messageAndThreadId = insertPlaceholder(envelope);
|
||||
smsDatabase.markAsInvalidVersionKeyExchange(messageAndThreadId.first);
|
||||
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
|
||||
MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), messageAndThreadId.second);
|
||||
} else {
|
||||
smsDatabase.markAsInvalidVersionKeyExchange(smsMessageId.get());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleCorruptMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, Optional<Long> smsMessageId) {
|
||||
private void handleCorruptMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull TextSecureEnvelope envelope,
|
||||
@NonNull Optional<Long> smsMessageId)
|
||||
{
|
||||
EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
|
||||
if (!smsMessageId.isPresent()) {
|
||||
Pair<Long, Long> messageAndThreadId = insertPlaceholder(masterSecret, envelope);
|
||||
Pair<Long, Long> messageAndThreadId = insertPlaceholder(envelope);
|
||||
smsDatabase.markAsDecryptFailed(messageAndThreadId.first);
|
||||
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
|
||||
MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), messageAndThreadId.second);
|
||||
} else {
|
||||
smsDatabase.markAsDecryptFailed(smsMessageId.get());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleNoSessionMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, Optional<Long> smsMessageId) {
|
||||
private void handleNoSessionMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull TextSecureEnvelope envelope,
|
||||
@NonNull Optional<Long> smsMessageId)
|
||||
{
|
||||
EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
|
||||
if (!smsMessageId.isPresent()) {
|
||||
Pair<Long, Long> messageAndThreadId = insertPlaceholder(masterSecret, envelope);
|
||||
Pair<Long, Long> messageAndThreadId = insertPlaceholder(envelope);
|
||||
smsDatabase.markAsNoSession(messageAndThreadId.first);
|
||||
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
|
||||
MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), messageAndThreadId.second);
|
||||
} else {
|
||||
smsDatabase.markAsNoSession(smsMessageId.get());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleLegacyMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, Optional<Long> smsMessageId) {
|
||||
private void handleLegacyMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull TextSecureEnvelope envelope,
|
||||
@NonNull Optional<Long> smsMessageId)
|
||||
{
|
||||
EncryptingSmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
|
||||
if (!smsMessageId.isPresent()) {
|
||||
Pair<Long, Long> messageAndThreadId = insertPlaceholder(masterSecret, envelope);
|
||||
Pair<Long, Long> messageAndThreadId = insertPlaceholder(envelope);
|
||||
smsDatabase.markAsLegacyVersion(messageAndThreadId.first);
|
||||
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
|
||||
MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), messageAndThreadId.second);
|
||||
} else {
|
||||
smsDatabase.markAsLegacyVersion(smsMessageId.get());
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDuplicateMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, Optional<Long> smsMessageId) {
|
||||
private void handleDuplicateMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull TextSecureEnvelope envelope,
|
||||
@NonNull Optional<Long> smsMessageId)
|
||||
{
|
||||
// Let's start ignoring these now
|
||||
// SmsDatabase smsDatabase = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
//
|
||||
@ -376,7 +418,10 @@ public class PushDecryptJob extends MasterSecretJob {
|
||||
// }
|
||||
}
|
||||
|
||||
private void handleUntrustedIdentityMessage(MasterSecret masterSecret, TextSecureEnvelope envelope, Optional<Long> smsMessageId) {
|
||||
private void handleUntrustedIdentityMessage(@NonNull MasterSecretUnion masterSecret,
|
||||
@NonNull TextSecureEnvelope envelope,
|
||||
@NonNull Optional<Long> smsMessageId)
|
||||
{
|
||||
try {
|
||||
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
Recipients recipients = RecipientFactory.getRecipientsFromString(context, envelope.getSource(), false);
|
||||
@ -393,7 +438,7 @@ public class PushDecryptJob extends MasterSecretJob {
|
||||
Pair<Long, Long> messageAndThreadId = database.insertMessageInbox(masterSecret, bundleMessage);
|
||||
|
||||
database.setMismatchedIdentity(messageAndThreadId.first, recipientId, identityKey);
|
||||
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
|
||||
MessageNotifier.updateNotification(context, masterSecret.getMasterSecret().orNull(), messageAndThreadId.second);
|
||||
} else {
|
||||
database.updateMessageBody(masterSecret, smsMessageId.get(), encoded);
|
||||
database.markAsPreKeyBundle(smsMessageId.get());
|
||||
@ -404,16 +449,14 @@ public class PushDecryptJob extends MasterSecretJob {
|
||||
}
|
||||
}
|
||||
|
||||
private Pair<Long, Long> insertPlaceholder(MasterSecret masterSecret, TextSecureEnvelope envelope) {
|
||||
private Pair<Long, Long> insertPlaceholder(@NonNull TextSecureEnvelope envelope) {
|
||||
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
|
||||
IncomingTextMessage textMessage = new IncomingTextMessage(envelope.getSource(), envelope.getSourceDevice(),
|
||||
envelope.getTimestamp(), "",
|
||||
Optional.<TextSecureGroup>absent());
|
||||
|
||||
textMessage = new IncomingEncryptedMessage(textMessage, "");
|
||||
|
||||
return database.insertMessageInbox(masterSecret, textMessage);
|
||||
return database.insertMessageInbox(textMessage);
|
||||
}
|
||||
|
||||
private Recipients getSyncMessageDestination(SentTranscriptMessage message) {
|
||||
|
@ -135,7 +135,7 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
|
||||
{
|
||||
message = getResolvedMessage(masterSecret, message, MediaConstraints.PUSH_CONSTRAINTS, false);
|
||||
|
||||
TextSecureMessageSender messageSender = messageSenderFactory.create(masterSecret);
|
||||
TextSecureMessageSender messageSender = messageSenderFactory.create();
|
||||
byte[] groupId = GroupUtil.getDecodedId(message.getTo()[0].getString());
|
||||
Recipients recipients = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupId, false);
|
||||
List<TextSecureAttachment> attachments = getAttachments(masterSecret, message);
|
||||
|
@ -104,7 +104,7 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
|
||||
throws RetryLaterException, InsecureFallbackApprovalException, UntrustedIdentityException,
|
||||
UndeliverableMessageException
|
||||
{
|
||||
TextSecureMessageSender messageSender = messageSenderFactory.create(masterSecret);
|
||||
TextSecureMessageSender messageSender = messageSenderFactory.create();
|
||||
String destination = message.getTo()[0].getString();
|
||||
|
||||
try {
|
||||
|
@ -59,7 +59,7 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
|
||||
try {
|
||||
Log.w(TAG, "Sending message: " + messageId);
|
||||
|
||||
deliver(masterSecret, record);
|
||||
deliver(record);
|
||||
database.markAsPush(messageId);
|
||||
database.markAsSecure(messageId);
|
||||
database.markAsSent(messageId);
|
||||
@ -99,12 +99,12 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
|
||||
}
|
||||
}
|
||||
|
||||
private void deliver(MasterSecret masterSecret, SmsMessageRecord message)
|
||||
private void deliver(SmsMessageRecord message)
|
||||
throws UntrustedIdentityException, InsecureFallbackApprovalException, RetryLaterException
|
||||
{
|
||||
try {
|
||||
TextSecureAddress address = getPushAddress(message.getIndividualRecipient().getNumber());
|
||||
TextSecureMessageSender messageSender = messageSenderFactory.create(masterSecret);
|
||||
TextSecureMessageSender messageSender = messageSenderFactory.create();
|
||||
TextSecureDataMessage textSecureMessage = TextSecureDataMessage.newBuilder()
|
||||
.withTimestamp(message.getDateSent())
|
||||
.withBody(message.getBody().getBody())
|
||||
|
@ -57,10 +57,10 @@ public class RefreshPreKeysJob extends MasterSecretJob implements InjectableType
|
||||
return;
|
||||
}
|
||||
|
||||
List<PreKeyRecord> preKeyRecords = PreKeyUtil.generatePreKeys(context, masterSecret);
|
||||
PreKeyRecord lastResortKeyRecord = PreKeyUtil.generateLastResortKey(context, masterSecret);
|
||||
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context, masterSecret);
|
||||
SignedPreKeyRecord signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(context, masterSecret, identityKey);
|
||||
List<PreKeyRecord> preKeyRecords = PreKeyUtil.generatePreKeys(context);
|
||||
PreKeyRecord lastResortKeyRecord = PreKeyUtil.generateLastResortKey(context);
|
||||
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(context);
|
||||
SignedPreKeyRecord signedPreKeyRecord = PreKeyUtil.generateSignedPreKey(context, identityKey);
|
||||
|
||||
Log.w(TAG, "Registering new prekeys...");
|
||||
|
||||
|
@ -1,104 +0,0 @@
|
||||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.AsymmetricMasterCipher;
|
||||
import org.thoughtcrime.securesms.crypto.AsymmetricMasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.NoSuchMessageException;
|
||||
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage;
|
||||
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
|
||||
import org.whispersystems.jobqueue.JobParameters;
|
||||
import org.whispersystems.libaxolotl.InvalidMessageException;
|
||||
import org.whispersystems.libaxolotl.util.guava.Optional;
|
||||
import org.whispersystems.textsecure.api.messages.TextSecureGroup;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class SmsDecryptJob extends MasterSecretJob {
|
||||
|
||||
private static final String TAG = SmsDecryptJob.class.getSimpleName();
|
||||
|
||||
private final long messageId;
|
||||
|
||||
public SmsDecryptJob(Context context, long messageId) {
|
||||
super(context, JobParameters.newBuilder()
|
||||
.withPersistence()
|
||||
.withRequirement(new MasterSecretRequirement(context))
|
||||
.create());
|
||||
|
||||
this.messageId = messageId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAdded() {}
|
||||
|
||||
@Override
|
||||
public void onRun(MasterSecret masterSecret) throws NoSuchMessageException {
|
||||
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
|
||||
try {
|
||||
SmsMessageRecord record = database.getMessage(masterSecret, messageId);
|
||||
IncomingTextMessage message = createIncomingTextMessage(masterSecret, record);
|
||||
long messageId = record.getId();
|
||||
|
||||
if (message.isSecureMessage()) {
|
||||
database.markAsLegacyVersion(messageId);
|
||||
} else {
|
||||
database.updateMessageBody(masterSecret, messageId, message.getMessageBody());
|
||||
}
|
||||
|
||||
MessageNotifier.updateNotification(context, masterSecret);
|
||||
} catch (InvalidMessageException e) {
|
||||
Log.w(TAG, e);
|
||||
database.markAsDecryptFailed(messageId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onShouldRetryThrowable(Exception exception) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCanceled() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
private String getAsymmetricDecryptedBody(MasterSecret masterSecret, String body)
|
||||
throws InvalidMessageException
|
||||
{
|
||||
try {
|
||||
AsymmetricMasterSecret asymmetricMasterSecret = MasterSecretUtil.getAsymmetricMasterSecret(context, masterSecret);
|
||||
AsymmetricMasterCipher asymmetricMasterCipher = new AsymmetricMasterCipher(asymmetricMasterSecret);
|
||||
|
||||
return asymmetricMasterCipher.decryptBody(body);
|
||||
} catch (IOException e) {
|
||||
throw new InvalidMessageException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private IncomingTextMessage createIncomingTextMessage(MasterSecret masterSecret, SmsMessageRecord record)
|
||||
throws InvalidMessageException
|
||||
{
|
||||
IncomingTextMessage message = new IncomingTextMessage(record.getRecipients().getPrimaryRecipient().getNumber(),
|
||||
record.getRecipientDeviceId(),
|
||||
record.getDateSent(),
|
||||
record.getBody().getBody(),
|
||||
Optional.<TextSecureGroup>absent());
|
||||
|
||||
if (record.isAsymmetricEncryption()) {
|
||||
String plaintextBody = getAsymmetricDecryptedBody(masterSecret, record.getBody().getBody());
|
||||
return new IncomingTextMessage(message, plaintextBody);
|
||||
} else {
|
||||
return new IncomingEncryptedMessage(message, message.getMessageBody());
|
||||
}
|
||||
}
|
||||
}
|
@ -5,8 +5,8 @@ import android.telephony.SmsMessage;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
|
||||
@ -44,10 +44,19 @@ public class SmsReceiveJob extends ContextJob {
|
||||
@Override
|
||||
public void onRun() {
|
||||
Optional<IncomingTextMessage> message = assembleMessageFragments(pdus);
|
||||
MasterSecret masterSecret = KeyCachingService.getMasterSecret(context);
|
||||
|
||||
MasterSecretUnion masterSecretUnion;
|
||||
|
||||
if (masterSecret == null) {
|
||||
masterSecretUnion = new MasterSecretUnion(MasterSecretUtil.getAsymmetricMasterSecret(context, null));
|
||||
} else {
|
||||
masterSecretUnion = new MasterSecretUnion(masterSecret);
|
||||
}
|
||||
|
||||
if (message.isPresent() && !isBlocked(message.get())) {
|
||||
Pair<Long, Long> messageAndThreadId = storeMessage(message.get());
|
||||
MessageNotifier.updateNotification(context, KeyCachingService.getMasterSecret(context), messageAndThreadId.second);
|
||||
Pair<Long, Long> messageAndThreadId = storeMessage(masterSecretUnion, message.get());
|
||||
MessageNotifier.updateNotification(context, masterSecret, messageAndThreadId.second);
|
||||
} else if (message.isPresent()) {
|
||||
Log.w(TAG, "*** Received blocked SMS, ignoring...");
|
||||
}
|
||||
@ -72,21 +81,15 @@ public class SmsReceiveJob extends ContextJob {
|
||||
return false;
|
||||
}
|
||||
|
||||
private Pair<Long, Long> storeMessage(IncomingTextMessage message) {
|
||||
private Pair<Long, Long> storeMessage(MasterSecretUnion masterSecret, IncomingTextMessage message) {
|
||||
EncryptingSmsDatabase database = DatabaseFactory.getEncryptingSmsDatabase(context);
|
||||
MasterSecret masterSecret = KeyCachingService.getMasterSecret(context);
|
||||
|
||||
Pair<Long, Long> messageAndThreadId;
|
||||
|
||||
if (message.isSecureMessage()) {
|
||||
messageAndThreadId = database.insertMessageInbox((MasterSecret)null, message);
|
||||
IncomingTextMessage placeholder = new IncomingTextMessage(message, "");
|
||||
messageAndThreadId = database.insertMessageInbox(placeholder);
|
||||
database.markAsLegacyVersion(messageAndThreadId.first);
|
||||
} else if (masterSecret == null) {
|
||||
messageAndThreadId = database.insertMessageInbox(MasterSecretUtil.getAsymmetricMasterSecret(context, null), message);
|
||||
|
||||
ApplicationContext.getInstance(context)
|
||||
.getJobManager()
|
||||
.add(new SmsDecryptJob(context, messageAndThreadId.first));
|
||||
} else {
|
||||
messageAndThreadId = database.insertMessageInbox(masterSecret, message);
|
||||
}
|
||||
|
@ -2,8 +2,11 @@ package org.thoughtcrime.securesms.mms;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.AsymmetricMasterCipher;
|
||||
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
|
||||
import org.thoughtcrime.securesms.crypto.MediaKey;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
@ -34,7 +37,7 @@ public class IncomingMediaMessage {
|
||||
this.push = false;
|
||||
}
|
||||
|
||||
public IncomingMediaMessage(MasterSecret masterSecret,
|
||||
public IncomingMediaMessage(MasterSecretUnion masterSecret,
|
||||
String from,
|
||||
String to,
|
||||
long sentTimeMillis,
|
||||
@ -70,11 +73,11 @@ public class IncomingMediaMessage {
|
||||
for (TextSecureAttachment attachment : attachments.get()) {
|
||||
if (attachment.isPointer()) {
|
||||
PduPart media = new PduPart();
|
||||
byte[] encryptedKey = new MasterCipher(masterSecret).encryptBytes(attachment.asPointer().getKey());
|
||||
String encryptedKey = MediaKey.getEncrypted(masterSecret, attachment.asPointer().getKey());
|
||||
|
||||
media.setContentType(Util.toIsoBytes(attachment.getContentType()));
|
||||
media.setContentLocation(Util.toIsoBytes(String.valueOf(attachment.asPointer().getId())));
|
||||
media.setContentDisposition(Util.toIsoBytes(Base64.encodeBytes(encryptedKey)));
|
||||
media.setContentDisposition(Util.toIsoBytes(encryptedKey));
|
||||
|
||||
if (relay.isPresent()) {
|
||||
media.setName(Util.toIsoBytes(relay.get()));
|
||||
|
@ -3,11 +3,10 @@ package org.thoughtcrime.securesms.mms;
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
|
||||
import org.thoughtcrime.securesms.crypto.MediaKey;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.recipients.Recipients;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.whispersystems.textsecure.api.messages.TextSecureAttachment;
|
||||
|
||||
@ -40,7 +39,7 @@ public class OutgoingMediaMessage {
|
||||
this(context, recipients, slideDeck.toPduBody(), message, distributionType);
|
||||
}
|
||||
|
||||
public OutgoingMediaMessage(Context context, MasterSecret masterSecret,
|
||||
public OutgoingMediaMessage(Context context, MasterSecretUnion masterSecret,
|
||||
Recipients recipients, List<TextSecureAttachment> attachments,
|
||||
String message)
|
||||
{
|
||||
@ -74,17 +73,17 @@ public class OutgoingMediaMessage {
|
||||
return false;
|
||||
}
|
||||
|
||||
private static PduBody pduBodyFor(MasterSecret masterSecret, List<TextSecureAttachment> attachments) {
|
||||
private static PduBody pduBodyFor(MasterSecretUnion masterSecret, List<TextSecureAttachment> attachments) {
|
||||
PduBody body = new PduBody();
|
||||
|
||||
for (TextSecureAttachment attachment : attachments) {
|
||||
if (attachment.isPointer()) {
|
||||
PduPart media = new PduPart();
|
||||
byte[] encryptedKey = new MasterCipher(masterSecret).encryptBytes(attachment.asPointer().getKey());
|
||||
String encryptedKey = MediaKey.getEncrypted(masterSecret, attachment.asPointer().getKey());
|
||||
|
||||
media.setContentType(Util.toIsoBytes(attachment.getContentType()));
|
||||
media.setContentLocation(Util.toIsoBytes(String.valueOf(attachment.asPointer().getId())));
|
||||
media.setContentDisposition(Util.toIsoBytes(Base64.encodeBytes(encryptedKey)));
|
||||
media.setContentDisposition(Util.toIsoBytes(encryptedKey));
|
||||
|
||||
body.addPart(media);
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ import android.graphics.drawable.Drawable;
|
||||
import android.media.AudioManager;
|
||||
import android.media.MediaPlayer;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.NotificationCompat.Action;
|
||||
@ -118,7 +119,7 @@ public class MessageNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
public static void updateNotification(Context context, MasterSecret masterSecret) {
|
||||
public static void updateNotification(@NonNull Context context, @Nullable MasterSecret masterSecret) {
|
||||
if (!TextSecurePreferences.isNotificationsEnabled(context)) {
|
||||
return;
|
||||
}
|
||||
@ -126,7 +127,10 @@ public class MessageNotifier {
|
||||
updateNotification(context, masterSecret, false, 0);
|
||||
}
|
||||
|
||||
public static void updateNotification(Context context, MasterSecret masterSecret, long threadId) {
|
||||
public static void updateNotification(@NonNull Context context,
|
||||
@Nullable MasterSecret masterSecret,
|
||||
long threadId)
|
||||
{
|
||||
Recipients recipients = DatabaseFactory.getThreadDatabase(context)
|
||||
.getRecipientsForThreadId(threadId);
|
||||
|
||||
@ -146,7 +150,10 @@ public class MessageNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateNotification(Context context, MasterSecret masterSecret, boolean signal, int reminderCount) {
|
||||
private static void updateNotification(@NonNull Context context,
|
||||
@Nullable MasterSecret masterSecret,
|
||||
boolean signal, int reminderCount)
|
||||
{
|
||||
Cursor telcoCursor = null;
|
||||
Cursor pushCursor = null;
|
||||
|
||||
@ -182,9 +189,9 @@ public class MessageNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
private static void sendSingleThreadNotification(Context context,
|
||||
MasterSecret masterSecret,
|
||||
NotificationState notificationState,
|
||||
private static void sendSingleThreadNotification(@NonNull Context context,
|
||||
@Nullable MasterSecret masterSecret,
|
||||
@NonNull NotificationState notificationState,
|
||||
boolean signal)
|
||||
{
|
||||
if (notificationState.getNotifications().isEmpty()) {
|
||||
@ -266,9 +273,9 @@ public class MessageNotifier {
|
||||
.notify(NOTIFICATION_ID, builder.build());
|
||||
}
|
||||
|
||||
private static void sendMultipleThreadNotification(Context context,
|
||||
MasterSecret masterSecret,
|
||||
NotificationState notificationState,
|
||||
private static void sendMultipleThreadNotification(@NonNull Context context,
|
||||
@Nullable MasterSecret masterSecret,
|
||||
@NonNull NotificationState notificationState,
|
||||
boolean signal)
|
||||
{
|
||||
List<NotificationItem> notifications = notificationState.getNotifications();
|
||||
@ -375,10 +382,10 @@ public class MessageNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
private static void appendPushNotificationState(Context context,
|
||||
MasterSecret masterSecret,
|
||||
NotificationState notificationState,
|
||||
Cursor cursor)
|
||||
private static void appendPushNotificationState(@NonNull Context context,
|
||||
@Nullable MasterSecret masterSecret,
|
||||
@NonNull NotificationState notificationState,
|
||||
@NonNull Cursor cursor)
|
||||
{
|
||||
if (masterSecret != null) return;
|
||||
|
||||
@ -392,7 +399,7 @@ public class MessageNotifier {
|
||||
Recipients recipients = RecipientFactory.getRecipientsFromString(context, envelope.getSource(), false);
|
||||
Recipient recipient = recipients.getPrimaryRecipient();
|
||||
long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipients);
|
||||
SpannableString body = new SpannableString(context.getString(R.string.MessageNotifier_encrypted_message));
|
||||
SpannableString body = new SpannableString(context.getString(R.string.MessageNotifier_locked_message));
|
||||
body.setSpan(new StyleSpan(android.graphics.Typeface.ITALIC), 0, body.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
|
||||
if (!recipients.isMuted()) {
|
||||
@ -405,9 +412,9 @@ public class MessageNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
private static NotificationState constructNotificationState(Context context,
|
||||
MasterSecret masterSecret,
|
||||
Cursor cursor)
|
||||
private static NotificationState constructNotificationState(@NonNull Context context,
|
||||
@Nullable MasterSecret masterSecret,
|
||||
@NonNull Cursor cursor)
|
||||
{
|
||||
NotificationState notificationState = new NotificationState();
|
||||
MessageRecord record;
|
||||
@ -433,7 +440,7 @@ public class MessageNotifier {
|
||||
}
|
||||
|
||||
if (SmsDatabase.Types.isDecryptInProgressType(record.getType()) || !record.getBody().isPlaintext()) {
|
||||
body = SpanUtil.italic(context.getString(R.string.MessageNotifier_encrypted_message));
|
||||
body = SpanUtil.italic(context.getString(R.string.MessageNotifier_locked_message));
|
||||
} else if (record.isMms() && TextUtils.isEmpty(body)) {
|
||||
body = SpanUtil.italic(context.getString(R.string.MessageNotifier_media_message));
|
||||
} else if (record.isMms() && !record.isMmsNotification()) {
|
||||
|
@ -28,6 +28,7 @@ import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.os.SystemClock;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.util.Log;
|
||||
import android.widget.RemoteViews;
|
||||
@ -40,6 +41,7 @@ import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.crypto.InvalidPassphraseException;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||
import org.thoughtcrime.securesms.jobs.MasterSecretDecryptJob;
|
||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||
import org.thoughtcrime.securesms.util.ParcelUtil;
|
||||
@ -78,7 +80,7 @@ public class KeyCachingService extends Service {
|
||||
|
||||
public KeyCachingService() {}
|
||||
|
||||
public static synchronized MasterSecret getMasterSecret(Context context) {
|
||||
public static synchronized @Nullable MasterSecret getMasterSecret(Context context) {
|
||||
if (masterSecret == null && TextSecurePreferences.isPasswordDisabled(context)) {
|
||||
try {
|
||||
MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(context, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
|
||||
@ -103,13 +105,14 @@ public class KeyCachingService extends Service {
|
||||
broadcastNewSecret();
|
||||
startTimeoutIfAppropriate();
|
||||
|
||||
if (!TextSecurePreferences.isPasswordDisabled(this)) {
|
||||
ApplicationContext.getInstance(this).getJobManager().add(new MasterSecretDecryptJob(this));
|
||||
}
|
||||
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
if (!DatabaseUpgradeActivity.isUpdate(KeyCachingService.this)) {
|
||||
ApplicationContext.getInstance(KeyCachingService.this)
|
||||
.getJobManager()
|
||||
.setEncryptionKeys(new EncryptionKeys(ParcelUtil.serialize(masterSecret)));
|
||||
MessageNotifier.updateNotification(KeyCachingService.this, masterSecret);
|
||||
}
|
||||
return null;
|
||||
|
@ -14,7 +14,6 @@ import com.google.android.gms.gcm.GoogleCloudMessaging;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.PreKeyUtil;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.jobs.GcmRefreshJob;
|
||||
@ -154,14 +153,13 @@ public class RegistrationService extends Service {
|
||||
markAsVerifying(true);
|
||||
|
||||
String number = intent.getStringExtra("e164number");
|
||||
String password = intent.getStringExtra("password" );
|
||||
String password = intent.getStringExtra("password");
|
||||
String signalingKey = intent.getStringExtra("signaling_key");
|
||||
MasterSecret masterSecret = intent.getParcelableExtra("master_secret");
|
||||
|
||||
try {
|
||||
TextSecureAccountManager accountManager = TextSecureCommunicationFactory.createManager(this, number, password);
|
||||
|
||||
handleCommonRegistration(masterSecret, accountManager, number);
|
||||
handleCommonRegistration(accountManager, number);
|
||||
|
||||
markAsVerified(number, password, signalingKey);
|
||||
|
||||
@ -182,7 +180,6 @@ public class RegistrationService extends Service {
|
||||
markAsVerifying(true);
|
||||
|
||||
String number = intent.getStringExtra("e164number");
|
||||
MasterSecret masterSecret = intent.getParcelableExtra("master_secret");
|
||||
int registrationId = TextSecurePreferences.getLocalRegistrationId(this);
|
||||
|
||||
if (registrationId == 0) {
|
||||
@ -204,7 +201,7 @@ public class RegistrationService extends Service {
|
||||
String challenge = waitForChallenge();
|
||||
accountManager.verifyAccount(challenge, signalingKey, true, registrationId);
|
||||
|
||||
handleCommonRegistration(masterSecret, accountManager, number);
|
||||
handleCommonRegistration(accountManager, number);
|
||||
markAsVerified(number, password, signalingKey);
|
||||
|
||||
setState(new RegistrationState(RegistrationState.STATE_COMPLETE, number));
|
||||
@ -230,15 +227,15 @@ public class RegistrationService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
private void handleCommonRegistration(MasterSecret masterSecret, TextSecureAccountManager accountManager, String number)
|
||||
private void handleCommonRegistration(TextSecureAccountManager accountManager, String number)
|
||||
throws IOException
|
||||
{
|
||||
setState(new RegistrationState(RegistrationState.STATE_GENERATING_KEYS, number));
|
||||
Recipient self = RecipientFactory.getRecipientsFromString(this, number, false).getPrimaryRecipient();
|
||||
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(this, masterSecret);
|
||||
List<PreKeyRecord> records = PreKeyUtil.generatePreKeys(this, masterSecret);
|
||||
PreKeyRecord lastResort = PreKeyUtil.generateLastResortKey(this, masterSecret);
|
||||
SignedPreKeyRecord signedPreKey = PreKeyUtil.generateSignedPreKey(this, masterSecret, identityKey);
|
||||
IdentityKeyPair identityKey = IdentityKeyUtil.getIdentityKeyPair(this);
|
||||
List<PreKeyRecord> records = PreKeyUtil.generatePreKeys(this);
|
||||
PreKeyRecord lastResort = PreKeyUtil.generateLastResortKey(this);
|
||||
SignedPreKeyRecord signedPreKey = PreKeyUtil.generateSignedPreKey(this, identityKey);
|
||||
accountManager.setPreKeys(identityKey.getPublicKey(),lastResort, signedPreKey, records);
|
||||
|
||||
setState(new RegistrationState(RegistrationState.STATE_GCM_REGISTERING, number));
|
||||
@ -249,7 +246,7 @@ public class RegistrationService extends Service {
|
||||
TextSecurePreferences.setGcmRegistrationId(this, gcmRegistrationId);
|
||||
TextSecurePreferences.setWebsocketRegistered(this, true);
|
||||
|
||||
DatabaseFactory.getIdentityDatabase(this).saveIdentity(masterSecret, self.getRecipientId(), identityKey.getPublicKey());
|
||||
DatabaseFactory.getIdentityDatabase(this).saveIdentity(self.getRecipientId(), identityKey.getPublicKey());
|
||||
DirectoryHelper.refreshDirectory(this, accountManager, number);
|
||||
|
||||
DirectoryRefreshListener.schedule(this);
|
||||
|
@ -22,6 +22,7 @@ import android.util.Pair;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUnion;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.EncryptingSmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
@ -73,7 +74,7 @@ public class MessageSender {
|
||||
allocatedThreadId = threadId;
|
||||
}
|
||||
|
||||
long messageId = database.insertMessageOutbox(masterSecret, allocatedThreadId, message, forceSms, System.currentTimeMillis());
|
||||
long messageId = database.insertMessageOutbox(new MasterSecretUnion(masterSecret), allocatedThreadId, message, forceSms, System.currentTimeMillis());
|
||||
|
||||
sendTextMessage(context, recipients, forceSms, keyExchange, messageId);
|
||||
|
||||
@ -99,7 +100,7 @@ public class MessageSender {
|
||||
}
|
||||
|
||||
Recipients recipients = message.getRecipients();
|
||||
long messageId = database.insertMessageOutbox(masterSecret, message, allocatedThreadId, forceSms, System.currentTimeMillis());
|
||||
long messageId = database.insertMessageOutbox(new MasterSecretUnion(masterSecret), message, allocatedThreadId, forceSms, System.currentTimeMillis());
|
||||
|
||||
sendMediaMessage(context, masterSecret, recipients, forceSms, messageId);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user