mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-30 13:35:18 +00:00
Master secret removed.
Screen lock related classes refactoring. Legacy database util classes and migrations removed.
This commit is contained in:
parent
2aa179585f
commit
e294199ea3
@ -312,10 +312,6 @@
|
|||||||
android:screenOrientation="portrait"
|
android:screenOrientation="portrait"
|
||||||
android:theme="@style/Theme.TextSecure.DayNight.NoActionBar"
|
android:theme="@style/Theme.TextSecure.DayNight.NoActionBar"
|
||||||
android:windowSoftInputMode="stateHidden" />
|
android:windowSoftInputMode="stateHidden" />
|
||||||
<activity
|
|
||||||
android:name="org.thoughtcrime.securesms.PassphraseChangeActivity"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
|
||||||
android:label="@string/AndroidManifest__change_passphrase" />
|
|
||||||
<activity
|
<activity
|
||||||
android:name="org.thoughtcrime.securesms.stickers.StickerManagementActivity"
|
android:name="org.thoughtcrime.securesms.stickers.StickerManagementActivity"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
|
@ -38,7 +38,6 @@ import org.signal.aesgcmprovider.AesGcmProvider;
|
|||||||
import org.thoughtcrime.securesms.components.TypingStatusRepository;
|
import org.thoughtcrime.securesms.components.TypingStatusRepository;
|
||||||
import org.thoughtcrime.securesms.components.TypingStatusSender;
|
import org.thoughtcrime.securesms.components.TypingStatusSender;
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
|
||||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
|
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
|
||||||
import org.thoughtcrime.securesms.database.Address;
|
import org.thoughtcrime.securesms.database.Address;
|
||||||
@ -112,7 +111,6 @@ import org.session.libsignal.service.loki.protocol.closedgroups.SharedSenderKeys
|
|||||||
import org.session.libsignal.service.loki.protocol.closedgroups.SharedSenderKeysImplementationDelegate;
|
import org.session.libsignal.service.loki.protocol.closedgroups.SharedSenderKeysImplementationDelegate;
|
||||||
import org.session.libsignal.service.loki.protocol.mentions.MentionsManager;
|
import org.session.libsignal.service.loki.protocol.mentions.MentionsManager;
|
||||||
import org.session.libsignal.service.loki.protocol.meta.SessionMetaProtocol;
|
import org.session.libsignal.service.loki.protocol.meta.SessionMetaProtocol;
|
||||||
import org.session.libsignal.service.loki.protocol.meta.TTLUtilities;
|
|
||||||
import org.session.libsignal.service.loki.protocol.sessionmanagement.SessionManagementProtocol;
|
import org.session.libsignal.service.loki.protocol.sessionmanagement.SessionManagementProtocol;
|
||||||
import org.session.libsignal.service.loki.protocol.sessionmanagement.SessionManagementProtocolDelegate;
|
import org.session.libsignal.service.loki.protocol.sessionmanagement.SessionManagementProtocolDelegate;
|
||||||
import org.session.libsignal.service.loki.protocol.shelved.multidevice.DeviceLink;
|
import org.session.libsignal.service.loki.protocol.shelved.multidevice.DeviceLink;
|
||||||
@ -582,7 +580,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
|
|||||||
boolean wasUnlinked = TextSecurePreferences.getWasUnlinked(this);
|
boolean wasUnlinked = TextSecurePreferences.getWasUnlinked(this);
|
||||||
TextSecurePreferences.clearAll(this);
|
TextSecurePreferences.clearAll(this);
|
||||||
TextSecurePreferences.setWasUnlinked(this, wasUnlinked);
|
TextSecurePreferences.setWasUnlinked(this, wasUnlinked);
|
||||||
MasterSecretUtil.clear(this);
|
// MasterSecretUtil.clear(this);
|
||||||
if (!deleteDatabase("signal.db")) {
|
if (!deleteDatabase("signal.db")) {
|
||||||
Log.d("Loki", "Failed to delete database.");
|
Log.d("Loki", "Failed to delete database.");
|
||||||
}
|
}
|
||||||
|
@ -23,15 +23,10 @@ import android.content.Intent;
|
|||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.preference.PreferenceManager;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
||||||
import org.thoughtcrime.securesms.color.MaterialColor;
|
|
||||||
import org.thoughtcrime.securesms.contacts.avatars.ContactColorsLegacy;
|
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
|
||||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||||
@ -40,17 +35,10 @@ import org.thoughtcrime.securesms.database.PushDatabase;
|
|||||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||||
import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob;
|
import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob;
|
||||||
import org.thoughtcrime.securesms.jobs.PushDecryptJob;
|
import org.thoughtcrime.securesms.jobs.PushDecryptJob;
|
||||||
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
|
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
|
||||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
|
||||||
import org.thoughtcrime.securesms.util.FileUtils;
|
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.thoughtcrime.securesms.util.VersionTracker;
|
import org.thoughtcrime.securesms.util.VersionTracker;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.SortedSet;
|
import java.util.SortedSet;
|
||||||
import java.util.TreeSet;
|
import java.util.TreeSet;
|
||||||
@ -60,72 +48,72 @@ import network.loki.messenger.R;
|
|||||||
public class DatabaseUpgradeActivity extends BaseActivity {
|
public class DatabaseUpgradeActivity extends BaseActivity {
|
||||||
private static final String TAG = DatabaseUpgradeActivity.class.getSimpleName();
|
private static final String TAG = DatabaseUpgradeActivity.class.getSimpleName();
|
||||||
|
|
||||||
public static final int NO_MORE_KEY_EXCHANGE_PREFIX_VERSION = 0; // 46
|
// public static final int NO_MORE_KEY_EXCHANGE_PREFIX_VERSION = 0; // 46
|
||||||
public static final int MMS_BODY_VERSION = 0; // 46
|
// public static final int MMS_BODY_VERSION = 0; // 46
|
||||||
public static final int TOFU_IDENTITIES_VERSION = 1; // 50
|
// public static final int TOFU_IDENTITIES_VERSION = 1; // 50
|
||||||
public static final int CURVE25519_VERSION = 2; // 63
|
// public static final int CURVE25519_VERSION = 2; // 63
|
||||||
public static final int ASYMMETRIC_MASTER_SECRET_FIX_VERSION = 3; // 73
|
// public static final int ASYMMETRIC_MASTER_SECRET_FIX_VERSION = 3; // 73
|
||||||
public static final int NO_V1_VERSION = 4; // 83
|
// public static final int NO_V1_VERSION = 4; // 83
|
||||||
public static final int SIGNED_PREKEY_VERSION = 4; // 83
|
// public static final int SIGNED_PREKEY_VERSION = 4; // 83
|
||||||
public static final int NO_DECRYPT_QUEUE_VERSION = 5; // 113
|
// public static final int NO_DECRYPT_QUEUE_VERSION = 5; // 113
|
||||||
public static final int PUSH_DECRYPT_SERIAL_ID_VERSION = 6; // 131
|
// public static final int PUSH_DECRYPT_SERIAL_ID_VERSION = 6; // 131
|
||||||
public static final int MIGRATE_SESSION_PLAINTEXT = 7; // 136
|
// public static final int MIGRATE_SESSION_PLAINTEXT = 7; // 136
|
||||||
public static final int CONTACTS_ACCOUNT_VERSION = 7; // 136
|
// public static final int CONTACTS_ACCOUNT_VERSION = 7; // 136
|
||||||
public static final int MEDIA_DOWNLOAD_CONTROLS_VERSION = 8; // 151
|
// public static final int MEDIA_DOWNLOAD_CONTROLS_VERSION = 8; // 151
|
||||||
public static final int REDPHONE_SUPPORT_VERSION = 9; // 157
|
// public static final int REDPHONE_SUPPORT_VERSION = 9; // 157
|
||||||
public static final int NO_MORE_CANONICAL_DB_VERSION = 10; // 276
|
// public static final int NO_MORE_CANONICAL_DB_VERSION = 10; // 276
|
||||||
public static final int PROFILES = 11; // 289
|
// public static final int PROFILES = 11; // 289
|
||||||
public static final int SCREENSHOTS = 12; // 300
|
// public static final int SCREENSHOTS = 12; // 300
|
||||||
public static final int PERSISTENT_BLOBS = 13; // 317
|
// public static final int PERSISTENT_BLOBS = 13; // 317
|
||||||
public static final int INTERNALIZE_CONTACTS = 13; // 317
|
// public static final int INTERNALIZE_CONTACTS = 13; // 317
|
||||||
public static final int SQLCIPHER = 14; // 334
|
// public static final int SQLCIPHER = 14; // 334
|
||||||
public static final int SQLCIPHER_COMPLETE = 15; // 352
|
// public static final int SQLCIPHER_COMPLETE = 15; // 352
|
||||||
public static final int REMOVE_JOURNAL = 16; // 353
|
// public static final int REMOVE_JOURNAL = 16; // 353
|
||||||
public static final int REMOVE_CACHE = 17; // 354
|
// public static final int REMOVE_CACHE = 17; // 354
|
||||||
public static final int FULL_TEXT_SEARCH = 18; // 358
|
// public static final int FULL_TEXT_SEARCH = 18; // 358
|
||||||
public static final int BAD_IMPORT_CLEANUP = 19; // 373
|
// public static final int BAD_IMPORT_CLEANUP = 19; // 373
|
||||||
public static final int IMAGE_CACHE_CLEANUP = 20; // 406
|
// public static final int IMAGE_CACHE_CLEANUP = 20; // 406
|
||||||
public static final int WORKMANAGER_MIGRATION = 21; // 408
|
// public static final int WORKMANAGER_MIGRATION = 21; // 408
|
||||||
public static final int COLOR_MIGRATION = 22; // 412
|
// public static final int COLOR_MIGRATION = 22; // 412
|
||||||
public static final int UNIDENTIFIED_DELIVERY = 23; // 422
|
// public static final int UNIDENTIFIED_DELIVERY = 23; // 422
|
||||||
public static final int SIGNALING_KEY_DEPRECATION = 24; // 447
|
// public static final int SIGNALING_KEY_DEPRECATION = 24; // 447
|
||||||
public static final int CONVERSATION_SEARCH = 25; // 455
|
// public static final int CONVERSATION_SEARCH = 25; // 455
|
||||||
|
|
||||||
private static final SortedSet<Integer> UPGRADE_VERSIONS = new TreeSet<Integer>() {{
|
private static final SortedSet<Integer> UPGRADE_VERSIONS = new TreeSet<Integer>() {{
|
||||||
add(NO_MORE_KEY_EXCHANGE_PREFIX_VERSION);
|
// add(NO_MORE_KEY_EXCHANGE_PREFIX_VERSION);
|
||||||
add(TOFU_IDENTITIES_VERSION);
|
// add(TOFU_IDENTITIES_VERSION);
|
||||||
add(CURVE25519_VERSION);
|
// add(CURVE25519_VERSION);
|
||||||
add(ASYMMETRIC_MASTER_SECRET_FIX_VERSION);
|
// add(ASYMMETRIC_MASTER_SECRET_FIX_VERSION);
|
||||||
add(NO_V1_VERSION);
|
// add(NO_V1_VERSION);
|
||||||
add(SIGNED_PREKEY_VERSION);
|
// add(SIGNED_PREKEY_VERSION);
|
||||||
add(NO_DECRYPT_QUEUE_VERSION);
|
// add(NO_DECRYPT_QUEUE_VERSION);
|
||||||
add(PUSH_DECRYPT_SERIAL_ID_VERSION);
|
// add(PUSH_DECRYPT_SERIAL_ID_VERSION);
|
||||||
add(MIGRATE_SESSION_PLAINTEXT);
|
// add(MIGRATE_SESSION_PLAINTEXT);
|
||||||
add(MEDIA_DOWNLOAD_CONTROLS_VERSION);
|
// add(MEDIA_DOWNLOAD_CONTROLS_VERSION);
|
||||||
add(REDPHONE_SUPPORT_VERSION);
|
// add(REDPHONE_SUPPORT_VERSION);
|
||||||
add(NO_MORE_CANONICAL_DB_VERSION);
|
// add(NO_MORE_CANONICAL_DB_VERSION);
|
||||||
add(SCREENSHOTS);
|
// add(SCREENSHOTS);
|
||||||
add(INTERNALIZE_CONTACTS);
|
// add(INTERNALIZE_CONTACTS);
|
||||||
add(PERSISTENT_BLOBS);
|
// add(PERSISTENT_BLOBS);
|
||||||
add(SQLCIPHER);
|
// add(SQLCIPHER);
|
||||||
add(SQLCIPHER_COMPLETE);
|
// add(SQLCIPHER_COMPLETE);
|
||||||
add(REMOVE_CACHE);
|
// add(REMOVE_CACHE);
|
||||||
add(FULL_TEXT_SEARCH);
|
// add(FULL_TEXT_SEARCH);
|
||||||
add(BAD_IMPORT_CLEANUP);
|
// add(BAD_IMPORT_CLEANUP);
|
||||||
add(IMAGE_CACHE_CLEANUP);
|
// add(IMAGE_CACHE_CLEANUP);
|
||||||
add(WORKMANAGER_MIGRATION);
|
// add(WORKMANAGER_MIGRATION);
|
||||||
add(COLOR_MIGRATION);
|
// add(COLOR_MIGRATION);
|
||||||
add(UNIDENTIFIED_DELIVERY);
|
// add(UNIDENTIFIED_DELIVERY);
|
||||||
add(SIGNALING_KEY_DEPRECATION);
|
// add(SIGNALING_KEY_DEPRECATION);
|
||||||
add(CONVERSATION_SEARCH);
|
// add(CONVERSATION_SEARCH);
|
||||||
}};
|
}};
|
||||||
|
|
||||||
private MasterSecret masterSecret;
|
// private MasterSecret masterSecret;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle bundle) {
|
public void onCreate(Bundle bundle) {
|
||||||
super.onCreate(bundle);
|
super.onCreate(bundle);
|
||||||
this.masterSecret = KeyCachingService.getMasterSecret(this);
|
// this.masterSecret = KeyCachingService.getMasterSecret(this);
|
||||||
|
|
||||||
if (needsUpgradeTask()) {
|
if (needsUpgradeTask()) {
|
||||||
Log.i("DatabaseUpgradeActivity", "Upgrading...");
|
Log.i("DatabaseUpgradeActivity", "Upgrading...");
|
||||||
@ -202,160 +190,160 @@ public class DatabaseUpgradeActivity extends BaseActivity {
|
|||||||
Context context = DatabaseUpgradeActivity.this.getApplicationContext();
|
Context context = DatabaseUpgradeActivity.this.getApplicationContext();
|
||||||
|
|
||||||
Log.i("DatabaseUpgradeActivity", "Running background upgrade..");
|
Log.i("DatabaseUpgradeActivity", "Running background upgrade..");
|
||||||
DatabaseFactory.getInstance(DatabaseUpgradeActivity.this)
|
// DatabaseFactory.getInstance(DatabaseUpgradeActivity.this)
|
||||||
.onApplicationLevelUpgrade(context, masterSecret, params[0], this);
|
// .onApplicationLevelUpgrade(context, params[0], this);
|
||||||
|
|
||||||
if (params[0] < CURVE25519_VERSION) {
|
// if (params[0] < CURVE25519_VERSION) {
|
||||||
IdentityKeyUtil.migrateIdentityKeys(context, masterSecret);
|
// IdentityKeyUtil.migrateIdentityKeys(context, masterSecret);
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (params[0] < NO_V1_VERSION) {
|
|
||||||
File v1sessions = new File(context.getFilesDir(), "sessions");
|
|
||||||
|
|
||||||
if (v1sessions.exists() && v1sessions.isDirectory()) {
|
|
||||||
File[] contents = v1sessions.listFiles();
|
|
||||||
|
|
||||||
if (contents != null) {
|
|
||||||
for (File session : contents) {
|
|
||||||
session.delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
v1sessions.delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params[0] < SIGNED_PREKEY_VERSION) {
|
|
||||||
// ApplicationContext.getInstance(getApplicationContext())
|
|
||||||
// .getJobManager()
|
|
||||||
// .add(new CreateSignedPreKeyJob(context));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params[0] < NO_DECRYPT_QUEUE_VERSION) {
|
|
||||||
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);;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params[0] < MEDIA_DOWNLOAD_CONTROLS_VERSION) {
|
|
||||||
schedulePendingIncomingParts(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params[0] < REDPHONE_SUPPORT_VERSION) {
|
|
||||||
ApplicationContext.getInstance(getApplicationContext())
|
|
||||||
.getJobManager()
|
|
||||||
.add(new RefreshAttributesJob());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params[0] < SCREENSHOTS) {
|
|
||||||
boolean screenSecurity = PreferenceManager.getDefaultSharedPreferences(context).getBoolean(TextSecurePreferences.SCREEN_SECURITY_PREF, true);
|
|
||||||
TextSecurePreferences.setScreenSecurityEnabled(getApplicationContext(), screenSecurity);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params[0] < PERSISTENT_BLOBS) {
|
|
||||||
File externalDir = context.getExternalFilesDir(null);
|
|
||||||
|
|
||||||
if (externalDir != null && externalDir.isDirectory() && externalDir.exists()) {
|
|
||||||
for (File blob : externalDir.listFiles()) {
|
|
||||||
if (blob.exists() && blob.isFile()) blob.delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params[0] < INTERNALIZE_CONTACTS) {
|
|
||||||
if (TextSecurePreferences.isPushRegistered(getApplicationContext())) {
|
|
||||||
TextSecurePreferences.setHasSuccessfullyRetrievedDirectory(getApplicationContext(), true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params[0] < SQLCIPHER) {
|
|
||||||
scheduleMessagesInPushDatabase(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params[0] < SQLCIPHER_COMPLETE) {
|
|
||||||
File file = context.getDatabasePath("messages.db");
|
|
||||||
if (file != null && file.exists()) file.delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params[0] < REMOVE_JOURNAL) {
|
|
||||||
File file = context.getDatabasePath("messages.db-journal");
|
|
||||||
if (file != null && file.exists()) file.delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params[0] < REMOVE_CACHE) {
|
|
||||||
try {
|
|
||||||
FileUtils.deleteDirectoryContents(context.getCacheDir());
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params[0] < IMAGE_CACHE_CLEANUP) {
|
|
||||||
try {
|
|
||||||
FileUtils.deleteDirectoryContents(context.getExternalCacheDir());
|
|
||||||
GlideApp.get(context).clearDiskCache();
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This migration became unnecessary after switching away from WorkManager
|
|
||||||
// if (params[0] < WORKMANAGER_MIGRATION) {
|
|
||||||
// Log.i(TAG, "Beginning migration of existing jobs to WorkManager");
|
|
||||||
//
|
//
|
||||||
// JobManager jobManager = ApplicationContext.getInstance(getApplicationContext()).getJobManager();
|
// if (params[0] < NO_V1_VERSION) {
|
||||||
// PersistentStorage storage = new PersistentStorage(getApplicationContext(), "TextSecureJobs", new JavaJobSerializer());
|
// File v1sessions = new File(context.getFilesDir(), "sessions");
|
||||||
//
|
//
|
||||||
// for (Job job : storage.getAllUnencrypted()) {
|
// if (v1sessions.exists() && v1sessions.isDirectory()) {
|
||||||
// jobManager.add(job);
|
// File[] contents = v1sessions.listFiles();
|
||||||
// Log.i(TAG, "Migrated job with class '" + job.getClass().getSimpleName() + "' to run on new JobManager.");
|
//
|
||||||
|
// if (contents != null) {
|
||||||
|
// for (File session : contents) {
|
||||||
|
// session.delete();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// v1sessions.delete();
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
//
|
||||||
if (params[0] < COLOR_MIGRATION) {
|
// if (params[0] < SIGNED_PREKEY_VERSION) {
|
||||||
long startTime = System.currentTimeMillis();
|
//// ApplicationContext.getInstance(getApplicationContext())
|
||||||
DatabaseFactory.getRecipientDatabase(context).updateSystemContactColors((name, color) -> {
|
//// .getJobManager()
|
||||||
if (color != null) {
|
//// .add(new CreateSignedPreKeyJob(context));
|
||||||
try {
|
// }
|
||||||
return MaterialColor.fromSerialized(color);
|
//
|
||||||
} catch (MaterialColor.UnknownColorException e) {
|
// if (params[0] < NO_DECRYPT_QUEUE_VERSION) {
|
||||||
Log.w(TAG, "Encountered an unknown color during legacy color migration.", e);
|
// scheduleMessagesInPushDatabase(context);
|
||||||
return ContactColorsLegacy.generateFor(name);
|
// }
|
||||||
}
|
//
|
||||||
}
|
// if (params[0] < PUSH_DECRYPT_SERIAL_ID_VERSION) {
|
||||||
return ContactColorsLegacy.generateFor(name);
|
// scheduleMessagesInPushDatabase(context);
|
||||||
});
|
// }
|
||||||
Log.i(TAG, "Color migration took " + (System.currentTimeMillis() - startTime) + " ms");
|
//
|
||||||
}
|
// if (params[0] < MIGRATE_SESSION_PLAINTEXT) {
|
||||||
|
//// new TextSecureSessionStore(context, masterSecret).migrateSessions();
|
||||||
if (params[0] < UNIDENTIFIED_DELIVERY) {
|
//// new TextSecurePreKeyStore(context, masterSecret).migrateRecords();
|
||||||
if (TextSecurePreferences.isMultiDevice(context)) {
|
//
|
||||||
Log.i(TAG, "MultiDevice: Disabling UD (will be re-enabled if possible after pending refresh).");
|
// IdentityKeyUtil.migrateIdentityKeys(context, masterSecret);
|
||||||
TextSecurePreferences.setIsUnidentifiedDeliveryEnabled(context, false);
|
// scheduleMessagesInPushDatabase(context);;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
Log.i(TAG, "Scheduling UD attributes refresh.");
|
// if (params[0] < MEDIA_DOWNLOAD_CONTROLS_VERSION) {
|
||||||
ApplicationContext.getInstance(context)
|
// schedulePendingIncomingParts(context);
|
||||||
.getJobManager()
|
// }
|
||||||
.add(new RefreshAttributesJob());
|
//
|
||||||
}
|
// if (params[0] < REDPHONE_SUPPORT_VERSION) {
|
||||||
|
// ApplicationContext.getInstance(getApplicationContext())
|
||||||
if (params[0] < SIGNALING_KEY_DEPRECATION) {
|
// .getJobManager()
|
||||||
Log.i(TAG, "Scheduling a RefreshAttributesJob to remove the signaling key remotely.");
|
// .add(new RefreshAttributesJob());
|
||||||
ApplicationContext.getInstance(context)
|
// }
|
||||||
.getJobManager()
|
//
|
||||||
.add(new RefreshAttributesJob());
|
// if (params[0] < SCREENSHOTS) {
|
||||||
}
|
// boolean screenSecurity = PreferenceManager.getDefaultSharedPreferences(context).getBoolean(TextSecurePreferences.SCREEN_SECURITY_PREF, true);
|
||||||
|
// TextSecurePreferences.setScreenSecurityEnabled(getApplicationContext(), screenSecurity);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (params[0] < PERSISTENT_BLOBS) {
|
||||||
|
// File externalDir = context.getExternalFilesDir(null);
|
||||||
|
//
|
||||||
|
// if (externalDir != null && externalDir.isDirectory() && externalDir.exists()) {
|
||||||
|
// for (File blob : externalDir.listFiles()) {
|
||||||
|
// if (blob.exists() && blob.isFile()) blob.delete();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (params[0] < INTERNALIZE_CONTACTS) {
|
||||||
|
// if (TextSecurePreferences.isPushRegistered(getApplicationContext())) {
|
||||||
|
// TextSecurePreferences.setHasSuccessfullyRetrievedDirectory(getApplicationContext(), true);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (params[0] < SQLCIPHER) {
|
||||||
|
// scheduleMessagesInPushDatabase(context);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (params[0] < SQLCIPHER_COMPLETE) {
|
||||||
|
// File file = context.getDatabasePath("messages.db");
|
||||||
|
// if (file != null && file.exists()) file.delete();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (params[0] < REMOVE_JOURNAL) {
|
||||||
|
// File file = context.getDatabasePath("messages.db-journal");
|
||||||
|
// if (file != null && file.exists()) file.delete();
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (params[0] < REMOVE_CACHE) {
|
||||||
|
// try {
|
||||||
|
// FileUtils.deleteDirectoryContents(context.getCacheDir());
|
||||||
|
// } catch (IOException e) {
|
||||||
|
// Log.w(TAG, e);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (params[0] < IMAGE_CACHE_CLEANUP) {
|
||||||
|
// try {
|
||||||
|
// FileUtils.deleteDirectoryContents(context.getExternalCacheDir());
|
||||||
|
// GlideApp.get(context).clearDiskCache();
|
||||||
|
// } catch (IOException e) {
|
||||||
|
// Log.w(TAG, e);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // This migration became unnecessary after switching away from WorkManager
|
||||||
|
//// if (params[0] < WORKMANAGER_MIGRATION) {
|
||||||
|
//// Log.i(TAG, "Beginning migration of existing jobs to WorkManager");
|
||||||
|
////
|
||||||
|
//// JobManager jobManager = ApplicationContext.getInstance(getApplicationContext()).getJobManager();
|
||||||
|
//// PersistentStorage storage = new PersistentStorage(getApplicationContext(), "TextSecureJobs", new JavaJobSerializer());
|
||||||
|
////
|
||||||
|
//// for (Job job : storage.getAllUnencrypted()) {
|
||||||
|
//// jobManager.add(job);
|
||||||
|
//// Log.i(TAG, "Migrated job with class '" + job.getClass().getSimpleName() + "' to run on new JobManager.");
|
||||||
|
//// }
|
||||||
|
//// }
|
||||||
|
//
|
||||||
|
// if (params[0] < COLOR_MIGRATION) {
|
||||||
|
// long startTime = System.currentTimeMillis();
|
||||||
|
// DatabaseFactory.getRecipientDatabase(context).updateSystemContactColors((name, color) -> {
|
||||||
|
// if (color != null) {
|
||||||
|
// try {
|
||||||
|
// return MaterialColor.fromSerialized(color);
|
||||||
|
// } catch (MaterialColor.UnknownColorException e) {
|
||||||
|
// Log.w(TAG, "Encountered an unknown color during legacy color migration.", e);
|
||||||
|
// return ContactColorsLegacy.generateFor(name);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// return ContactColorsLegacy.generateFor(name);
|
||||||
|
// });
|
||||||
|
// Log.i(TAG, "Color migration took " + (System.currentTimeMillis() - startTime) + " ms");
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (params[0] < UNIDENTIFIED_DELIVERY) {
|
||||||
|
// if (TextSecurePreferences.isMultiDevice(context)) {
|
||||||
|
// Log.i(TAG, "MultiDevice: Disabling UD (will be re-enabled if possible after pending refresh).");
|
||||||
|
// TextSecurePreferences.setIsUnidentifiedDeliveryEnabled(context, false);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Log.i(TAG, "Scheduling UD attributes refresh.");
|
||||||
|
// ApplicationContext.getInstance(context)
|
||||||
|
// .getJobManager()
|
||||||
|
// .add(new RefreshAttributesJob());
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (params[0] < SIGNALING_KEY_DEPRECATION) {
|
||||||
|
// Log.i(TAG, "Scheduling a RefreshAttributesJob to remove the signaling key remotely.");
|
||||||
|
// ApplicationContext.getInstance(context)
|
||||||
|
// .getJobManager()
|
||||||
|
// .add(new RefreshAttributesJob());
|
||||||
|
// }
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,6 @@ import org.thoughtcrime.securesms.mms.GlideApp;
|
|||||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.util.AttachmentUtil;
|
import org.thoughtcrime.securesms.util.AttachmentUtil;
|
||||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
|
||||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
|
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
|
||||||
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
@ -86,18 +85,11 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity {
|
|||||||
|
|
||||||
public static final String ADDRESS_EXTRA = "address";
|
public static final String ADDRESS_EXTRA = "address";
|
||||||
|
|
||||||
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
|
||||||
|
|
||||||
private Toolbar toolbar;
|
private Toolbar toolbar;
|
||||||
private TabLayout tabLayout;
|
private TabLayout tabLayout;
|
||||||
private ViewPager viewPager;
|
private ViewPager viewPager;
|
||||||
private Recipient recipient;
|
private Recipient recipient;
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPreCreate() {
|
|
||||||
dynamicLanguage.onCreate(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle bundle, boolean ready) {
|
protected void onCreate(Bundle bundle, boolean ready) {
|
||||||
setContentView(R.layout.media_overview_activity);
|
setContentView(R.layout.media_overview_activity);
|
||||||
@ -109,12 +101,6 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity {
|
|||||||
this.viewPager.setAdapter(new MediaOverviewPagerAdapter(getSupportFragmentManager()));
|
this.viewPager.setAdapter(new MediaOverviewPagerAdapter(getSupportFragmentManager()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
dynamicLanguage.onResume(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
super.onOptionsItemSelected(item);
|
super.onOptionsItemSelected(item);
|
||||||
@ -172,7 +158,7 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity {
|
|||||||
|
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
args.putString(MediaOverviewGalleryFragment.ADDRESS_EXTRA, recipient.getAddress().serialize());
|
args.putString(MediaOverviewGalleryFragment.ADDRESS_EXTRA, recipient.getAddress().serialize());
|
||||||
args.putSerializable(MediaOverviewGalleryFragment.LOCALE_EXTRA, dynamicLanguage.getCurrentLocale());
|
args.putSerializable(MediaOverviewGalleryFragment.LOCALE_EXTRA, Locale.getDefault());
|
||||||
|
|
||||||
fragment.setArguments(args);
|
fragment.setArguments(args);
|
||||||
|
|
||||||
|
@ -68,12 +68,12 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
|||||||
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
|
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
|
||||||
import org.thoughtcrime.securesms.util.AttachmentUtil;
|
import org.thoughtcrime.securesms.util.AttachmentUtil;
|
||||||
import org.thoughtcrime.securesms.util.DateUtils;
|
import org.thoughtcrime.securesms.util.DateUtils;
|
||||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
|
||||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
|
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
|
||||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment;
|
import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
import network.loki.messenger.R;
|
||||||
@ -95,8 +95,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
|
|||||||
public static final String OUTGOING_EXTRA = "outgoing";
|
public static final String OUTGOING_EXTRA = "outgoing";
|
||||||
public static final String LEFT_IS_RECENT_EXTRA = "left_is_recent";
|
public static final String LEFT_IS_RECENT_EXTRA = "left_is_recent";
|
||||||
|
|
||||||
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
|
||||||
|
|
||||||
private ViewPager mediaPager;
|
private ViewPager mediaPager;
|
||||||
private View detailsContainer;
|
private View detailsContainer;
|
||||||
private TextView caption;
|
private TextView caption;
|
||||||
@ -120,8 +118,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
|
|||||||
@SuppressWarnings("ConstantConditions")
|
@SuppressWarnings("ConstantConditions")
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle bundle, boolean ready) {
|
protected void onCreate(Bundle bundle, boolean ready) {
|
||||||
dynamicLanguage.onCreate(this);
|
|
||||||
|
|
||||||
viewModel = new ViewModelProvider(this).get(MediaPreviewViewModel.class);
|
viewModel = new ViewModelProvider(this).get(MediaPreviewViewModel.class);
|
||||||
|
|
||||||
setContentView(R.layout.media_preview_activity);
|
setContentView(R.layout.media_preview_activity);
|
||||||
@ -172,7 +168,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
|
|||||||
CharSequence relativeTimeSpan;
|
CharSequence relativeTimeSpan;
|
||||||
|
|
||||||
if (mediaItem.date > 0) {
|
if (mediaItem.date > 0) {
|
||||||
relativeTimeSpan = DateUtils.getExtendedRelativeTimeSpanString(this,dynamicLanguage.getCurrentLocale(), mediaItem.date);
|
relativeTimeSpan = DateUtils.getExtendedRelativeTimeSpanString(this, Locale.getDefault(), mediaItem.date);
|
||||||
} else {
|
} else {
|
||||||
relativeTimeSpan = getString(R.string.MediaPreviewActivity_draft);
|
relativeTimeSpan = getString(R.string.MediaPreviewActivity_draft);
|
||||||
}
|
}
|
||||||
@ -189,7 +185,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
|
|||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
dynamicLanguage.onResume(this);
|
|
||||||
initializeMedia();
|
initializeMedia();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,7 +55,6 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
|||||||
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
|
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
|
||||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||||
import org.thoughtcrime.securesms.util.DateUtils;
|
import org.thoughtcrime.securesms.util.DateUtils;
|
||||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
|
||||||
import org.thoughtcrime.securesms.util.ExpirationUtil;
|
import org.thoughtcrime.securesms.util.ExpirationUtil;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||||
@ -102,15 +101,8 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
|
|||||||
private ListView recipientsList;
|
private ListView recipientsList;
|
||||||
private LayoutInflater inflater;
|
private LayoutInflater inflater;
|
||||||
|
|
||||||
private DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
|
||||||
|
|
||||||
private boolean running;
|
private boolean running;
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPreCreate() {
|
|
||||||
dynamicLanguage.onCreate(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle bundle, boolean ready) {
|
public void onCreate(Bundle bundle, boolean ready) {
|
||||||
super.onCreate(bundle, ready);
|
super.onCreate(bundle, ready);
|
||||||
@ -125,7 +117,6 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
|
|||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
dynamicLanguage.onResume(this);
|
|
||||||
|
|
||||||
assert getSupportActionBar() != null;
|
assert getSupportActionBar() != null;
|
||||||
getSupportActionBar().setTitle("Message Details");
|
getSupportActionBar().setTitle("Message Details");
|
||||||
@ -211,7 +202,7 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
|
|||||||
sentDate.setText("-");
|
sentDate.setText("-");
|
||||||
receivedContainer.setVisibility(View.GONE);
|
receivedContainer.setVisibility(View.GONE);
|
||||||
} else {
|
} else {
|
||||||
Locale dateLocale = dynamicLanguage.getCurrentLocale();
|
Locale dateLocale = Locale.getDefault();
|
||||||
SimpleDateFormat dateFormatter = DateUtils.getDetailedDateFormatter(this, dateLocale);
|
SimpleDateFormat dateFormatter = DateUtils.getDetailedDateFormatter(this, dateLocale);
|
||||||
sentDate.setText(dateFormatter.format(new Date(messageRecord.getDateSent())));
|
sentDate.setText(dateFormatter.format(new Date(messageRecord.getDateSent())));
|
||||||
sentDate.setOnLongClickListener(v -> {
|
sentDate.setOnLongClickListener(v -> {
|
||||||
@ -271,7 +262,7 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
|
|||||||
toFrom.setVisibility(View.GONE);
|
toFrom.setVisibility(View.GONE);
|
||||||
separator.setVisibility(View.GONE);
|
separator.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
conversationItem.bind(messageRecord, Optional.absent(), Optional.absent(), glideRequests, dynamicLanguage.getCurrentLocale(), new HashSet<>(), recipient, null, false);
|
conversationItem.bind(messageRecord, Optional.absent(), Optional.absent(), glideRequests, Locale.getDefault(), new HashSet<>(), recipient, null, false);
|
||||||
recipientsList.setAdapter(new MessageDetailsRecipientAdapter(this, glideRequests, messageRecord, recipients, isPushGroup));
|
recipientsList.setAdapter(new MessageDetailsRecipientAdapter(this, glideRequests, messageRecord, recipients, isPushGroup));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,77 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (C) 2011 Whisper Systems
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package org.thoughtcrime.securesms;
|
|
||||||
|
|
||||||
import android.content.ComponentName;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.ServiceConnection;
|
|
||||||
import android.os.IBinder;
|
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
|
||||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base Activity for changing/prompting local encryption passphrase.
|
|
||||||
*
|
|
||||||
* @author Moxie Marlinspike
|
|
||||||
*/
|
|
||||||
public abstract class PassphraseActivity extends BaseActionBarActivity {
|
|
||||||
|
|
||||||
private static final String TAG = PassphraseActivity.class.getSimpleName();
|
|
||||||
|
|
||||||
private KeyCachingService keyCachingService;
|
|
||||||
private MasterSecret masterSecret;
|
|
||||||
|
|
||||||
protected void setMasterSecret(MasterSecret masterSecret) {
|
|
||||||
this.masterSecret = masterSecret;
|
|
||||||
Intent bindIntent = new Intent(this, KeyCachingService.class);
|
|
||||||
startService(bindIntent);
|
|
||||||
bindService(bindIntent, serviceConnection, Context.BIND_AUTO_CREATE);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract void cleanup();
|
|
||||||
|
|
||||||
private ServiceConnection serviceConnection = new ServiceConnection() {
|
|
||||||
@Override
|
|
||||||
public void onServiceConnected(ComponentName className, IBinder service) {
|
|
||||||
keyCachingService = ((KeyCachingService.KeySetBinder)service).getService();
|
|
||||||
keyCachingService.setMasterSecret(masterSecret);
|
|
||||||
|
|
||||||
PassphraseActivity.this.unbindService(PassphraseActivity.this.serviceConnection);
|
|
||||||
|
|
||||||
masterSecret = null;
|
|
||||||
cleanup();
|
|
||||||
|
|
||||||
Intent nextIntent = getIntent().getParcelableExtra("next_intent");
|
|
||||||
if (nextIntent != null) {
|
|
||||||
try {
|
|
||||||
startActivity(nextIntent);
|
|
||||||
} catch (java.lang.SecurityException e) {
|
|
||||||
Log.w(TAG, "Access permission not passed from PassphraseActivity, retry sharing.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void onServiceDisconnected(ComponentName name) {
|
|
||||||
keyCachingService = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,177 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (C) 2011 Whisper Systems
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package org.thoughtcrime.securesms;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.text.Editable;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.View.OnClickListener;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.EditText;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.crypto.InvalidPassphraseException;
|
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
|
||||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Activity for changing a user's local encryption passphrase.
|
|
||||||
*
|
|
||||||
* @author Moxie Marlinspike
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class PassphraseChangeActivity extends PassphraseActivity {
|
|
||||||
|
|
||||||
private DynamicTheme dynamicTheme = new DynamicTheme();
|
|
||||||
private DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
|
||||||
|
|
||||||
private EditText originalPassphrase;
|
|
||||||
private EditText newPassphrase;
|
|
||||||
private EditText repeatPassphrase;
|
|
||||||
private Button okButton;
|
|
||||||
private Button cancelButton;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
dynamicTheme.onCreate(this);
|
|
||||||
dynamicLanguage.onCreate(this);
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
setContentView(R.layout.change_passphrase_activity);
|
|
||||||
|
|
||||||
initializeResources();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
dynamicTheme.onResume(this);
|
|
||||||
dynamicLanguage.onResume(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializeResources() {
|
|
||||||
this.originalPassphrase = (EditText) findViewById(R.id.old_passphrase );
|
|
||||||
this.newPassphrase = (EditText) findViewById(R.id.new_passphrase );
|
|
||||||
this.repeatPassphrase = (EditText) findViewById(R.id.repeat_passphrase );
|
|
||||||
|
|
||||||
this.okButton = (Button ) findViewById(R.id.ok_button );
|
|
||||||
this.cancelButton = (Button ) findViewById(R.id.cancel_button );
|
|
||||||
|
|
||||||
this.okButton.setOnClickListener(new OkButtonClickListener());
|
|
||||||
this.cancelButton.setOnClickListener(new CancelButtonClickListener());
|
|
||||||
|
|
||||||
if (TextSecurePreferences.isPasswordDisabled(this)) {
|
|
||||||
this.originalPassphrase.setVisibility(View.GONE);
|
|
||||||
} else {
|
|
||||||
this.originalPassphrase.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void verifyAndSavePassphrases() {
|
|
||||||
Editable originalText = this.originalPassphrase.getText();
|
|
||||||
Editable newText = this.newPassphrase.getText();
|
|
||||||
Editable repeatText = this.repeatPassphrase.getText();
|
|
||||||
|
|
||||||
String original = (originalText == null ? "" : originalText.toString());
|
|
||||||
String passphrase = (newText == null ? "" : newText.toString());
|
|
||||||
String passphraseRepeat = (repeatText == null ? "" : repeatText.toString());
|
|
||||||
|
|
||||||
if (TextSecurePreferences.isPasswordDisabled(this)) {
|
|
||||||
original = MasterSecretUtil.UNENCRYPTED_PASSPHRASE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!passphrase.equals(passphraseRepeat)) {
|
|
||||||
this.newPassphrase.setText("");
|
|
||||||
this.repeatPassphrase.setText("");
|
|
||||||
this.newPassphrase.setError(getString(R.string.PassphraseChangeActivity_passphrases_dont_match_exclamation));
|
|
||||||
this.newPassphrase.requestFocus();
|
|
||||||
} else if (passphrase.equals("")) {
|
|
||||||
this.newPassphrase.setError(getString(R.string.PassphraseChangeActivity_enter_new_passphrase_exclamation));
|
|
||||||
this.newPassphrase.requestFocus();
|
|
||||||
} else {
|
|
||||||
new ChangePassphraseTask(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, original, passphrase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class CancelButtonClickListener implements OnClickListener {
|
|
||||||
public void onClick(View v) {
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class OkButtonClickListener implements OnClickListener {
|
|
||||||
public void onClick(View v) {
|
|
||||||
verifyAndSavePassphrases();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ChangePassphraseTask extends AsyncTask<String, Void, MasterSecret> {
|
|
||||||
private final Context context;
|
|
||||||
|
|
||||||
public ChangePassphraseTask(Context context) {
|
|
||||||
this.context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPreExecute() {
|
|
||||||
okButton.setEnabled(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected MasterSecret doInBackground(String... params) {
|
|
||||||
try {
|
|
||||||
MasterSecret masterSecret = MasterSecretUtil.changeMasterSecretPassphrase(context, params[0], params[1]);
|
|
||||||
TextSecurePreferences.setPasswordDisabled(context, false);
|
|
||||||
|
|
||||||
return masterSecret;
|
|
||||||
|
|
||||||
} catch (InvalidPassphraseException e) {
|
|
||||||
Log.w(PassphraseChangeActivity.class.getSimpleName(), e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(MasterSecret masterSecret) {
|
|
||||||
okButton.setEnabled(true);
|
|
||||||
|
|
||||||
if (masterSecret != null) {
|
|
||||||
setMasterSecret(masterSecret);
|
|
||||||
} else {
|
|
||||||
originalPassphrase.setText("");
|
|
||||||
originalPassphrase.setError(getString(R.string.PassphraseChangeActivity_incorrect_old_passphrase_exclamation));
|
|
||||||
originalPassphrase.requestFocus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void cleanup() {
|
|
||||||
this.originalPassphrase = null;
|
|
||||||
this.newPassphrase = null;
|
|
||||||
this.repeatPassphrase = null;
|
|
||||||
|
|
||||||
System.gc();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,90 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (C) 2011 Whisper Systems
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package org.thoughtcrime.securesms;
|
|
||||||
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Bundle;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
|
||||||
import org.thoughtcrime.securesms.util.VersionTracker;
|
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Activity for creating a user's local encryption passphrase.
|
|
||||||
*
|
|
||||||
* @author Moxie Marlinspike
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class PassphraseCreateActivity extends PassphraseActivity {
|
|
||||||
|
|
||||||
public PassphraseCreateActivity() { }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
setContentView(R.layout.create_passphrase_activity);
|
|
||||||
|
|
||||||
initializeResources();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializeResources() {
|
|
||||||
new SecretGenerator().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class SecretGenerator extends AsyncTask<String, Void, Void> {
|
|
||||||
private MasterSecret masterSecret;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPreExecute() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(String... params) {
|
|
||||||
String passphrase = params[0];
|
|
||||||
masterSecret = MasterSecretUtil.generateMasterSecret(PassphraseCreateActivity.this,
|
|
||||||
passphrase);
|
|
||||||
|
|
||||||
MasterSecretUtil.generateAsymmetricMasterSecret(PassphraseCreateActivity.this, masterSecret);
|
|
||||||
IdentityKeyUtil.generateIdentityKeyPair(PassphraseCreateActivity.this);
|
|
||||||
VersionTracker.updateLastSeenVersion(PassphraseCreateActivity.this);
|
|
||||||
|
|
||||||
TextSecurePreferences.setLastExperienceVersionCode(PassphraseCreateActivity.this, Util.getCanonicalVersionCode());
|
|
||||||
TextSecurePreferences.setPasswordDisabled(PassphraseCreateActivity.this, true);
|
|
||||||
TextSecurePreferences.setReadReceiptsEnabled(PassphraseCreateActivity.this, true);
|
|
||||||
TextSecurePreferences.setTypingIndicatorsEnabled(PassphraseCreateActivity.this, true);
|
|
||||||
TextSecurePreferences.setHasSeenWelcomeScreen(PassphraseCreateActivity.this, false);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPostExecute(Void param) {
|
|
||||||
setMasterSecret(masterSecret);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void cleanup() {
|
|
||||||
System.gc();
|
|
||||||
}
|
|
||||||
}
|
|
@ -18,66 +18,43 @@ package org.thoughtcrime.securesms;
|
|||||||
|
|
||||||
import android.animation.Animator;
|
import android.animation.Animator;
|
||||||
import android.app.KeyguardManager;
|
import android.app.KeyguardManager;
|
||||||
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.Editable;
|
import android.os.IBinder;
|
||||||
import android.text.InputType;
|
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.text.style.RelativeSizeSpan;
|
import android.text.style.RelativeSizeSpan;
|
||||||
import android.text.style.TypefaceSpan;
|
import android.text.style.TypefaceSpan;
|
||||||
import android.view.KeyEvent;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.View.OnClickListener;
|
|
||||||
import android.view.WindowManager;
|
|
||||||
import android.view.animation.Animation;
|
import android.view.animation.Animation;
|
||||||
import android.view.animation.BounceInterpolator;
|
import android.view.animation.BounceInterpolator;
|
||||||
import android.view.animation.TranslateAnimation;
|
import android.view.animation.TranslateAnimation;
|
||||||
import android.view.inputmethod.EditorInfo;
|
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.ImageButton;
|
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import androidx.core.hardware.fingerprint.FingerprintManagerCompat;
|
import androidx.core.hardware.fingerprint.FingerprintManagerCompat;
|
||||||
import androidx.core.os.CancellationSignal;
|
import androidx.core.os.CancellationSignal;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
|
import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
|
||||||
import org.thoughtcrime.securesms.components.AnimatingToggle;
|
import org.thoughtcrime.securesms.components.AnimatingToggle;
|
||||||
import org.thoughtcrime.securesms.crypto.InvalidPassphraseException;
|
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
|
||||||
import network.loki.messenger.R;
|
import network.loki.messenger.R;
|
||||||
|
|
||||||
/**
|
//TODO Rename to screen lock activity and refactor to Kotlin.
|
||||||
* Activity that prompts for a user's passphrase.
|
public class PassphrasePromptActivity extends BaseActionBarActivity {
|
||||||
*
|
|
||||||
* @author Moxie Marlinspike
|
|
||||||
*/
|
|
||||||
public class PassphrasePromptActivity extends PassphraseActivity {
|
|
||||||
|
|
||||||
private static final String TAG = PassphrasePromptActivity.class.getSimpleName();
|
private static final String TAG = PassphrasePromptActivity.class.getSimpleName();
|
||||||
|
|
||||||
private DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
|
||||||
|
|
||||||
private View passphraseAuthContainer;
|
|
||||||
private ImageView fingerprintPrompt;
|
private ImageView fingerprintPrompt;
|
||||||
private Button lockScreenButton;
|
private Button lockScreenButton;
|
||||||
|
|
||||||
private EditText passphraseText;
|
|
||||||
private ImageButton showButton;
|
|
||||||
private ImageButton hideButton;
|
|
||||||
private AnimatingToggle visibilityToggle;
|
private AnimatingToggle visibilityToggle;
|
||||||
|
|
||||||
private FingerprintManagerCompat fingerprintManager;
|
private FingerprintManagerCompat fingerprintManager;
|
||||||
@ -87,20 +64,34 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
|||||||
private boolean authenticated;
|
private boolean authenticated;
|
||||||
private boolean failure;
|
private boolean failure;
|
||||||
|
|
||||||
|
private KeyCachingService keyCachingService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
Log.i(TAG, "onCreate()");
|
Log.i(TAG, "onCreate()");
|
||||||
dynamicLanguage.onCreate(this);
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
setContentView(R.layout.prompt_passphrase_activity);
|
setContentView(R.layout.prompt_passphrase_activity);
|
||||||
initializeResources();
|
initializeResources();
|
||||||
|
|
||||||
|
// Start and bind to the KeyCachingService instance.
|
||||||
|
Intent bindIntent = new Intent(this, KeyCachingService.class);
|
||||||
|
startService(bindIntent);
|
||||||
|
bindService(bindIntent, new ServiceConnection() {
|
||||||
|
@Override
|
||||||
|
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||||
|
keyCachingService = ((KeyCachingService.KeySetBinder)service).getService();
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onServiceDisconnected(ComponentName name) {
|
||||||
|
keyCachingService = null;
|
||||||
|
}
|
||||||
|
}, Context.BIND_AUTO_CREATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
dynamicLanguage.onResume(this);
|
|
||||||
|
|
||||||
setLockTypeVisibility();
|
setLockTypeVisibility();
|
||||||
|
|
||||||
@ -126,26 +117,6 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
|||||||
setIntent(intent);
|
setIntent(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
|
||||||
MenuInflater inflater = this.getMenuInflater();
|
|
||||||
menu.clear();
|
|
||||||
|
|
||||||
// inflater.inflate(R.menu.log_submit, menu);
|
|
||||||
super.onPrepareOptionsMenu(menu);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
|
||||||
super.onOptionsItemSelected(item);
|
|
||||||
switch (item.getItemId()) {
|
|
||||||
case R.id.menu_submit_debug_logs: handleLogSubmit(); return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityResult(int requestCode, int resultcode, Intent data) {
|
public void onActivityResult(int requestCode, int resultcode, Intent data) {
|
||||||
super.onActivityResult(requestCode, resultcode, data);
|
super.onActivityResult(requestCode, resultcode, data);
|
||||||
@ -159,57 +130,26 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleLogSubmit() {
|
|
||||||
Intent intent = new Intent(this, LogSubmitActivity.class);
|
|
||||||
startActivity(intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handlePassphrase() {
|
|
||||||
try {
|
|
||||||
Editable text = passphraseText.getText();
|
|
||||||
String passphrase = (text == null ? "" : text.toString());
|
|
||||||
MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(this, passphrase);
|
|
||||||
|
|
||||||
setMasterSecret(masterSecret);
|
|
||||||
} catch (InvalidPassphraseException ipe) {
|
|
||||||
passphraseText.setText("");
|
|
||||||
passphraseText.setError(
|
|
||||||
getString(R.string.PassphrasePromptActivity_invalid_passphrase_exclamation));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleAuthenticated() {
|
private void handleAuthenticated() {
|
||||||
try {
|
authenticated = true;
|
||||||
authenticated = true;
|
//TODO Replace with a proper call.
|
||||||
|
keyCachingService.setMasterSecret(new Object());
|
||||||
MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(this, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
|
|
||||||
setMasterSecret(masterSecret);
|
|
||||||
} catch (InvalidPassphraseException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setPassphraseVisibility(boolean visibility) {
|
// Finish and proceed with the next intent.
|
||||||
int cursorPosition = passphraseText.getSelectionStart();
|
Intent nextIntent = getIntent().getParcelableExtra("next_intent");
|
||||||
if (visibility) {
|
if (nextIntent != null) {
|
||||||
passphraseText.setInputType(InputType.TYPE_CLASS_TEXT |
|
startActivity(nextIntent);
|
||||||
InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
|
// try {
|
||||||
} else {
|
// startActivity(nextIntent);
|
||||||
passphraseText.setInputType(InputType.TYPE_CLASS_TEXT |
|
// } catch (java.lang.SecurityException e) {
|
||||||
InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
// Log.w(TAG, "Access permission not passed from PassphraseActivity, retry sharing.");
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
passphraseText.setSelection(cursorPosition);
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeResources() {
|
private void initializeResources() {
|
||||||
|
|
||||||
ImageButton okButton = findViewById(R.id.ok_button);
|
|
||||||
|
|
||||||
showButton = findViewById(R.id.passphrase_visibility);
|
|
||||||
hideButton = findViewById(R.id.passphrase_visibility_off);
|
|
||||||
visibilityToggle = findViewById(R.id.button_toggle);
|
visibilityToggle = findViewById(R.id.button_toggle);
|
||||||
passphraseText = findViewById(R.id.passphrase_edit);
|
|
||||||
passphraseAuthContainer = findViewById(R.id.password_auth_container);
|
|
||||||
fingerprintPrompt = findViewById(R.id.fingerprint_auth_container);
|
fingerprintPrompt = findViewById(R.id.fingerprint_auth_container);
|
||||||
lockScreenButton = findViewById(R.id.lock_screen_auth_container);
|
lockScreenButton = findViewById(R.id.lock_screen_auth_container);
|
||||||
fingerprintManager = FingerprintManagerCompat.from(this);
|
fingerprintManager = FingerprintManagerCompat.from(this);
|
||||||
@ -220,13 +160,6 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
|||||||
hint.setSpan(new RelativeSizeSpan(0.9f), 0, hint.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
|
hint.setSpan(new RelativeSizeSpan(0.9f), 0, hint.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
|
||||||
hint.setSpan(new TypefaceSpan("sans-serif"), 0, hint.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
|
hint.setSpan(new TypefaceSpan("sans-serif"), 0, hint.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
|
||||||
|
|
||||||
passphraseText.setHint(hint);
|
|
||||||
okButton.setOnClickListener(new OkButtonClickListener());
|
|
||||||
showButton.setOnClickListener(new ShowButtonOnClickListener());
|
|
||||||
hideButton.setOnClickListener(new HideButtonOnClickListener());
|
|
||||||
passphraseText.setOnEditorActionListener(new PassphraseActionListener());
|
|
||||||
passphraseText.setImeActionLabel(getString(R.string.prompt_passphrase_activity__unlock), EditorInfo.IME_ACTION_DONE);
|
|
||||||
|
|
||||||
fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
|
fingerprintPrompt.setImageResource(R.drawable.ic_fingerprint_white_48dp);
|
||||||
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.signal_primary), PorterDuff.Mode.SRC_IN);
|
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.signal_primary), PorterDuff.Mode.SRC_IN);
|
||||||
|
|
||||||
@ -235,8 +168,6 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
|||||||
|
|
||||||
private void setLockTypeVisibility() {
|
private void setLockTypeVisibility() {
|
||||||
if (TextSecurePreferences.isScreenLockEnabled(this)) {
|
if (TextSecurePreferences.isScreenLockEnabled(this)) {
|
||||||
passphraseAuthContainer.setVisibility(View.GONE);
|
|
||||||
|
|
||||||
if (fingerprintManager.isHardwareDetected() && fingerprintManager.hasEnrolledFingerprints()) {
|
if (fingerprintManager.isHardwareDetected() && fingerprintManager.hasEnrolledFingerprints()) {
|
||||||
fingerprintPrompt.setVisibility(View.VISIBLE);
|
fingerprintPrompt.setVisibility(View.VISIBLE);
|
||||||
lockScreenButton.setVisibility(View.GONE);
|
lockScreenButton.setVisibility(View.GONE);
|
||||||
@ -245,7 +176,6 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
|||||||
lockScreenButton.setVisibility(View.VISIBLE);
|
lockScreenButton.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
passphraseAuthContainer.setVisibility(View.VISIBLE);
|
|
||||||
fingerprintPrompt.setVisibility(View.GONE);
|
fingerprintPrompt.setVisibility(View.GONE);
|
||||||
lockScreenButton.setVisibility(View.GONE);
|
lockScreenButton.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
@ -266,13 +196,10 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
|||||||
Log.i(TAG, "Listening for fingerprints...");
|
Log.i(TAG, "Listening for fingerprints...");
|
||||||
fingerprintCancellationSignal = new CancellationSignal();
|
fingerprintCancellationSignal = new CancellationSignal();
|
||||||
fingerprintManager.authenticate(null, 0, fingerprintCancellationSignal, fingerprintListener, null);
|
fingerprintManager.authenticate(null, 0, fingerprintCancellationSignal, fingerprintListener, null);
|
||||||
} else if (Build.VERSION.SDK_INT >= 21){
|
} else {
|
||||||
Log.i(TAG, "firing intent...");
|
Log.i(TAG, "firing intent...");
|
||||||
Intent intent = keyguardManager.createConfirmDeviceCredentialIntent("Unlock Session", "");
|
Intent intent = keyguardManager.createConfirmDeviceCredentialIntent("Unlock Session", "");
|
||||||
startActivityForResult(intent, 1);
|
startActivityForResult(intent, 1);
|
||||||
} else {
|
|
||||||
Log.w(TAG, "Not compatible...");
|
|
||||||
handleAuthenticated();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -282,54 +209,6 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class PassphraseActionListener implements TextView.OnEditorActionListener {
|
|
||||||
@Override
|
|
||||||
public boolean onEditorAction(TextView exampleView, int actionId, KeyEvent keyEvent) {
|
|
||||||
if ((keyEvent == null && actionId == EditorInfo.IME_ACTION_DONE) ||
|
|
||||||
(keyEvent != null && keyEvent.getAction() == KeyEvent.ACTION_DOWN &&
|
|
||||||
(actionId == EditorInfo.IME_NULL)))
|
|
||||||
{
|
|
||||||
handlePassphrase();
|
|
||||||
return true;
|
|
||||||
} else if (keyEvent != null && keyEvent.getAction() == KeyEvent.ACTION_UP &&
|
|
||||||
actionId == EditorInfo.IME_NULL)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class OkButtonClickListener implements OnClickListener {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
handlePassphrase();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ShowButtonOnClickListener implements OnClickListener {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
visibilityToggle.display(hideButton);
|
|
||||||
setPassphraseVisibility(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class HideButtonOnClickListener implements OnClickListener {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
visibilityToggle.display(showButton);
|
|
||||||
setPassphraseVisibility(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void cleanup() {
|
|
||||||
this.passphraseText.setText("");
|
|
||||||
System.gc();
|
|
||||||
}
|
|
||||||
|
|
||||||
private class FingerprintListener extends FingerprintManagerCompat.AuthenticationCallback {
|
private class FingerprintListener extends FingerprintManagerCompat.AuthenticationCallback {
|
||||||
@Override
|
@Override
|
||||||
public void onAuthenticationError(int errMsgId, CharSequence errString) {
|
public void onAuthenticationError(int errMsgId, CharSequence errString) {
|
||||||
@ -356,7 +235,6 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
|||||||
@Override
|
@Override
|
||||||
public void onAuthenticationFailed() {
|
public void onAuthenticationFailed() {
|
||||||
Log.w(TAG, "onAuthenticatoinFailed()");
|
Log.w(TAG, "onAuthenticatoinFailed()");
|
||||||
FingerprintManagerCompat.AuthenticationCallback callback = this;
|
|
||||||
|
|
||||||
fingerprintPrompt.setImageResource(R.drawable.ic_close_white_48dp);
|
fingerprintPrompt.setImageResource(R.drawable.ic_close_white_48dp);
|
||||||
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.red_500), PorterDuff.Mode.SRC_IN);
|
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.red_500), PorterDuff.Mode.SRC_IN);
|
||||||
@ -380,6 +258,5 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
|||||||
|
|
||||||
fingerprintPrompt.startAnimation(shake);
|
fingerprintPrompt.startAnimation(shake);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -10,8 +10,6 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
|
||||||
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
|
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
|
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
|
||||||
import org.thoughtcrime.securesms.loki.activities.LandingActivity;
|
import org.thoughtcrime.securesms.loki.activities.LandingActivity;
|
||||||
@ -27,7 +25,6 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
|
|||||||
public static final String LOCALE_EXTRA = "locale_extra";
|
public static final String LOCALE_EXTRA = "locale_extra";
|
||||||
|
|
||||||
private static final int STATE_NORMAL = 0;
|
private static final int STATE_NORMAL = 0;
|
||||||
private static final int STATE_CREATE_PASSPHRASE = 1;
|
|
||||||
private static final int STATE_PROMPT_PASSPHRASE = 2;
|
private static final int STATE_PROMPT_PASSPHRASE = 2;
|
||||||
private static final int STATE_UPGRADE_DATABASE = 3;
|
private static final int STATE_UPGRADE_DATABASE = 3;
|
||||||
private static final int STATE_PROMPT_PUSH_REGISTRATION = 4;
|
private static final int STATE_PROMPT_PUSH_REGISTRATION = 4;
|
||||||
@ -118,7 +115,6 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
|
|||||||
Log.i(TAG, "routeApplicationState(), state: " + state);
|
Log.i(TAG, "routeApplicationState(), state: " + state);
|
||||||
|
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case STATE_CREATE_PASSPHRASE: return getCreatePassphraseIntent();
|
|
||||||
case STATE_PROMPT_PASSPHRASE: return getPromptPassphraseIntent();
|
case STATE_PROMPT_PASSPHRASE: return getPromptPassphraseIntent();
|
||||||
case STATE_UPGRADE_DATABASE: return getUpgradeDatabaseIntent();
|
case STATE_UPGRADE_DATABASE: return getUpgradeDatabaseIntent();
|
||||||
case STATE_WELCOME_SCREEN: return getWelcomeIntent();
|
case STATE_WELCOME_SCREEN: return getWelcomeIntent();
|
||||||
@ -129,9 +125,7 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
|
|||||||
}
|
}
|
||||||
|
|
||||||
private int getApplicationState(boolean locked) {
|
private int getApplicationState(boolean locked) {
|
||||||
if (!MasterSecretUtil.isPassphraseInitialized(this)) {
|
if (locked) {
|
||||||
return STATE_CREATE_PASSPHRASE;
|
|
||||||
} else if (locked) {
|
|
||||||
return STATE_PROMPT_PASSPHRASE;
|
return STATE_PROMPT_PASSPHRASE;
|
||||||
} else if (DatabaseUpgradeActivity.isUpdate(this)) {
|
} else if (DatabaseUpgradeActivity.isUpdate(this)) {
|
||||||
return STATE_UPGRADE_DATABASE;
|
return STATE_UPGRADE_DATABASE;
|
||||||
@ -148,10 +142,6 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Intent getCreatePassphraseIntent() {
|
|
||||||
return getRoutedIntent(PassphraseCreateActivity.class, getIntent());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Intent getPromptPassphraseIntent() {
|
private Intent getPromptPassphraseIntent() {
|
||||||
return getRoutedIntent(PassphrasePromptActivity.class, getIntent());
|
return getRoutedIntent(PassphrasePromptActivity.class, getIntent());
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,6 @@ import org.thoughtcrime.securesms.mms.PartAuthority;
|
|||||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
||||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
|
|
||||||
|
@ -48,8 +48,6 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.appcompat.view.ActionMode;
|
import androidx.appcompat.view.ActionMode;
|
||||||
import androidx.core.app.ActivityCompat;
|
|
||||||
import androidx.core.app.ActivityOptionsCompat;
|
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.loader.app.LoaderManager;
|
import androidx.loader.app.LoaderManager;
|
||||||
import androidx.loader.content.Loader;
|
import androidx.loader.content.Loader;
|
||||||
@ -66,8 +64,6 @@ import org.thoughtcrime.securesms.ShareActivity;
|
|||||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||||
import org.thoughtcrime.securesms.components.ConversationTypingView;
|
import org.thoughtcrime.securesms.components.ConversationTypingView;
|
||||||
import org.thoughtcrime.securesms.components.recyclerview.SmoothScrollingLinearLayoutManager;
|
import org.thoughtcrime.securesms.components.recyclerview.SmoothScrollingLinearLayoutManager;
|
||||||
import org.thoughtcrime.securesms.contactshare.Contact;
|
|
||||||
import org.thoughtcrime.securesms.contactshare.ContactUtil;
|
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationAdapter.HeaderViewHolder;
|
import org.thoughtcrime.securesms.conversation.ConversationAdapter.HeaderViewHolder;
|
||||||
import org.thoughtcrime.securesms.conversation.ConversationAdapter.ItemClickListener;
|
import org.thoughtcrime.securesms.conversation.ConversationAdapter.ItemClickListener;
|
||||||
import org.thoughtcrime.securesms.database.Address;
|
import org.thoughtcrime.securesms.database.Address;
|
||||||
|
@ -1,138 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (C) 2011 Whisper Systems
|
|
||||||
* Copyright (C) 2013 Open Whisper Systems
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package org.thoughtcrime.securesms.crypto;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.util.Base64;
|
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
|
||||||
import org.session.libsignal.libsignal.InvalidKeyException;
|
|
||||||
import org.session.libsignal.libsignal.InvalidMessageException;
|
|
||||||
import org.session.libsignal.libsignal.ecc.Curve;
|
|
||||||
import org.session.libsignal.libsignal.ecc.ECKeyPair;
|
|
||||||
import org.session.libsignal.libsignal.ecc.ECPrivateKey;
|
|
||||||
import org.session.libsignal.libsignal.ecc.ECPublicKey;
|
|
||||||
import org.thoughtcrime.securesms.util.Conversions;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
|
|
||||||
import javax.crypto.Mac;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class is used to asymmetricly encrypt local data. This is used in the case
|
|
||||||
* where TextSecure receives an SMS, but the user's local encryption passphrase is
|
|
||||||
* not cached (either because of a timeout, or because it hasn't yet been entered).
|
|
||||||
*
|
|
||||||
* In this case, we have access to the public key of a local keypair. We encrypt
|
|
||||||
* the message with this, and put it into the DB. When the user enters their passphrase,
|
|
||||||
* we can get access to the private key of the local keypair, decrypt the message, and
|
|
||||||
* replace it into the DB with symmetric encryption.
|
|
||||||
*
|
|
||||||
* The encryption protocol is as follows:
|
|
||||||
*
|
|
||||||
* 1) Generate an ephemeral keypair.
|
|
||||||
* 2) Do ECDH with the public key of the local durable keypair.
|
|
||||||
* 3) Do KMF with the ECDH result to obtain a master secret.
|
|
||||||
* 4) Encrypt the message with that master secret.
|
|
||||||
*
|
|
||||||
* @author Moxie Marlinspike
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class AsymmetricMasterCipher {
|
|
||||||
|
|
||||||
private final AsymmetricMasterSecret asymmetricMasterSecret;
|
|
||||||
|
|
||||||
public AsymmetricMasterCipher(AsymmetricMasterSecret asymmetricMasterSecret) {
|
|
||||||
this.asymmetricMasterSecret = asymmetricMasterSecret;
|
|
||||||
}
|
|
||||||
|
|
||||||
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[][] 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);
|
|
||||||
|
|
||||||
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) {
|
|
||||||
return Base64.encodeBytes(encryptBytes(body.getBytes()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private MasterCipher getMasterCipherForSecret(byte[] secretBytes) {
|
|
||||||
SecretKeySpec cipherKey = deriveCipherKey(secretBytes);
|
|
||||||
SecretKeySpec macKey = deriveMacKey(secretBytes);
|
|
||||||
MasterSecret masterSecret = new MasterSecret(cipherKey, macKey);
|
|
||||||
|
|
||||||
return new MasterCipher(masterSecret);
|
|
||||||
}
|
|
||||||
|
|
||||||
private SecretKeySpec deriveMacKey(byte[] secretBytes) {
|
|
||||||
byte[] digestedBytes = getDigestedBytes(secretBytes, 1);
|
|
||||||
byte[] macKeyBytes = new byte[20];
|
|
||||||
|
|
||||||
System.arraycopy(digestedBytes, 0, macKeyBytes, 0, macKeyBytes.length);
|
|
||||||
return new SecretKeySpec(macKeyBytes, "HmacSHA1");
|
|
||||||
}
|
|
||||||
|
|
||||||
private SecretKeySpec deriveCipherKey(byte[] secretBytes) {
|
|
||||||
byte[] digestedBytes = getDigestedBytes(secretBytes, 0);
|
|
||||||
byte[] cipherKeyBytes = new byte[16];
|
|
||||||
|
|
||||||
System.arraycopy(digestedBytes, 0, cipherKeyBytes, 0, cipherKeyBytes.length);
|
|
||||||
return new SecretKeySpec(cipherKeyBytes, "AES");
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] getDigestedBytes(byte[] secretBytes, int iteration) {
|
|
||||||
try {
|
|
||||||
Mac mac = Mac.getInstance("HmacSHA256");
|
|
||||||
mac.init(new SecretKeySpec(secretBytes, "HmacSHA256"));
|
|
||||||
return mac.doFinal(Conversions.intToByteArray(iteration));
|
|
||||||
} catch (NoSuchAlgorithmException | java.security.InvalidKeyException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -40,15 +40,14 @@ import java.util.List;
|
|||||||
*
|
*
|
||||||
* @author Moxie Marlinspike
|
* @author Moxie Marlinspike
|
||||||
*/
|
*/
|
||||||
|
//TODO AC: Delete
|
||||||
public class IdentityKeyUtil {
|
public class IdentityKeyUtil {
|
||||||
|
|
||||||
|
private static final String MASTER_SECRET_UTIL_PREFERENCES_NAME = "SecureSMS-Preferences";
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static final String TAG = IdentityKeyUtil.class.getSimpleName();
|
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";
|
|
||||||
|
|
||||||
public static final String IDENTITY_PUBLIC_KEY_PREF = "pref_identity_public_v3";
|
public static final String IDENTITY_PUBLIC_KEY_PREF = "pref_identity_public_v3";
|
||||||
public static final String IDENTITY_PRIVATE_KEY_PREF = "pref_identity_private_v3";
|
public static final String IDENTITY_PRIVATE_KEY_PREF = "pref_identity_private_v3";
|
||||||
public static final String ED25519_PUBLIC_KEY = "pref_ed25519_public_key";
|
public static final String ED25519_PUBLIC_KEY = "pref_ed25519_public_key";
|
||||||
@ -56,7 +55,7 @@ public class IdentityKeyUtil {
|
|||||||
public static final String LOKI_SEED = "loki_seed";
|
public static final String LOKI_SEED = "loki_seed";
|
||||||
|
|
||||||
public static boolean hasIdentityKey(Context context) {
|
public static boolean hasIdentityKey(Context context) {
|
||||||
SharedPreferences preferences = context.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0);
|
SharedPreferences preferences = context.getSharedPreferences(MASTER_SECRET_UTIL_PREFERENCES_NAME, 0);
|
||||||
|
|
||||||
return
|
return
|
||||||
preferences.contains(IDENTITY_PUBLIC_KEY_PREF) &&
|
preferences.contains(IDENTITY_PUBLIC_KEY_PREF) &&
|
||||||
@ -87,63 +86,38 @@ public class IdentityKeyUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void generateIdentityKeyPair(Context context) {
|
|
||||||
ECKeyPair keyPair = Curve.generateKeyPair();;
|
|
||||||
IdentityKey publicKey = new IdentityKey(keyPair.getPublicKey());
|
|
||||||
ECPrivateKey privateKey = keyPair.getPrivateKey();
|
|
||||||
save(context, IDENTITY_PUBLIC_KEY_PREF, Base64.encodeBytes(publicKey.serialize()));
|
|
||||||
save(context, IDENTITY_PRIVATE_KEY_PREF, Base64.encodeBytes(privateKey.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.getPublicKey().serialize()));
|
|
||||||
save(context, IDENTITY_PRIVATE_KEY_PREF, Base64.encodeBytes(legacyPair.getPrivateKey().serialize()));
|
|
||||||
|
|
||||||
delete(context, IDENTITY_PUBLIC_KEY_CIPHERTEXT_LEGACY_PREF);
|
|
||||||
delete(context, IDENTITY_PRIVATE_KEY_CIPHERTEXT_LEGACY_PREF);
|
|
||||||
} else {
|
|
||||||
generateIdentityKeyPair(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<BackupProtos.SharedPreference> getBackupRecords(@NonNull Context context) {
|
public static List<BackupProtos.SharedPreference> getBackupRecords(@NonNull Context context) {
|
||||||
SharedPreferences preferences = context.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0);
|
final String prefName = MASTER_SECRET_UTIL_PREFERENCES_NAME;
|
||||||
|
SharedPreferences preferences = context.getSharedPreferences(prefName, 0);
|
||||||
|
|
||||||
LinkedList<BackupProtos.SharedPreference> prefList = new LinkedList<>();
|
LinkedList<BackupProtos.SharedPreference> prefList = new LinkedList<>();
|
||||||
|
|
||||||
prefList.add(BackupProtos.SharedPreference.newBuilder()
|
prefList.add(BackupProtos.SharedPreference.newBuilder()
|
||||||
.setFile(MasterSecretUtil.PREFERENCES_NAME)
|
.setFile(prefName)
|
||||||
.setKey(IDENTITY_PUBLIC_KEY_PREF)
|
.setKey(IDENTITY_PUBLIC_KEY_PREF)
|
||||||
.setValue(preferences.getString(IDENTITY_PUBLIC_KEY_PREF, null))
|
.setValue(preferences.getString(IDENTITY_PUBLIC_KEY_PREF, null))
|
||||||
.build());
|
.build());
|
||||||
prefList.add(BackupProtos.SharedPreference.newBuilder()
|
prefList.add(BackupProtos.SharedPreference.newBuilder()
|
||||||
.setFile(MasterSecretUtil.PREFERENCES_NAME)
|
.setFile(prefName)
|
||||||
.setKey(IDENTITY_PRIVATE_KEY_PREF)
|
.setKey(IDENTITY_PRIVATE_KEY_PREF)
|
||||||
.setValue(preferences.getString(IDENTITY_PRIVATE_KEY_PREF, null))
|
.setValue(preferences.getString(IDENTITY_PRIVATE_KEY_PREF, null))
|
||||||
.build());
|
.build());
|
||||||
if (preferences.contains(ED25519_PUBLIC_KEY)) {
|
if (preferences.contains(ED25519_PUBLIC_KEY)) {
|
||||||
prefList.add(BackupProtos.SharedPreference.newBuilder()
|
prefList.add(BackupProtos.SharedPreference.newBuilder()
|
||||||
.setFile(MasterSecretUtil.PREFERENCES_NAME)
|
.setFile(prefName)
|
||||||
.setKey(ED25519_PUBLIC_KEY)
|
.setKey(ED25519_PUBLIC_KEY)
|
||||||
.setValue(preferences.getString(ED25519_PUBLIC_KEY, null))
|
.setValue(preferences.getString(ED25519_PUBLIC_KEY, null))
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
if (preferences.contains(ED25519_SECRET_KEY)) {
|
if (preferences.contains(ED25519_SECRET_KEY)) {
|
||||||
prefList.add(BackupProtos.SharedPreference.newBuilder()
|
prefList.add(BackupProtos.SharedPreference.newBuilder()
|
||||||
.setFile(MasterSecretUtil.PREFERENCES_NAME)
|
.setFile(prefName)
|
||||||
.setKey(ED25519_SECRET_KEY)
|
.setKey(ED25519_SECRET_KEY)
|
||||||
.setValue(preferences.getString(ED25519_SECRET_KEY, null))
|
.setValue(preferences.getString(ED25519_SECRET_KEY, null))
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
prefList.add(BackupProtos.SharedPreference.newBuilder()
|
prefList.add(BackupProtos.SharedPreference.newBuilder()
|
||||||
.setFile(MasterSecretUtil.PREFERENCES_NAME)
|
.setFile(prefName)
|
||||||
.setKey(LOKI_SEED)
|
.setKey(LOKI_SEED)
|
||||||
.setValue(preferences.getString(LOKI_SEED, null))
|
.setValue(preferences.getString(LOKI_SEED, null))
|
||||||
.build());
|
.build());
|
||||||
@ -151,34 +125,13 @@ public class IdentityKeyUtil {
|
|||||||
return prefList;
|
return prefList;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean hasLegacyIdentityKeys(Context context) {
|
|
||||||
return
|
|
||||||
retrieve(context, IDENTITY_PUBLIC_KEY_CIPHERTEXT_LEGACY_PREF) != null &&
|
|
||||||
retrieve(context, IDENTITY_PRIVATE_KEY_CIPHERTEXT_LEGACY_PREF) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IdentityKeyPair getLegacyIdentityKeyPair(@NonNull Context context,
|
|
||||||
@NonNull MasterSecret masterSecret)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
|
||||||
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)));
|
|
||||||
|
|
||||||
return new IdentityKeyPair(identityKey, privateKey);
|
|
||||||
} catch (IOException | InvalidKeyException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String retrieve(Context context, String key) {
|
public static String retrieve(Context context, String key) {
|
||||||
SharedPreferences preferences = context.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0);
|
SharedPreferences preferences = context.getSharedPreferences(MASTER_SECRET_UTIL_PREFERENCES_NAME, 0);
|
||||||
return preferences.getString(key, null);
|
return preferences.getString(key, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void save(Context context, String key, String value) {
|
public static void save(Context context, String key, String value) {
|
||||||
SharedPreferences preferences = context.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0);
|
SharedPreferences preferences = context.getSharedPreferences(MASTER_SECRET_UTIL_PREFERENCES_NAME, 0);
|
||||||
Editor preferencesEditor = preferences.edit();
|
Editor preferencesEditor = preferences.edit();
|
||||||
|
|
||||||
preferencesEditor.putString(key, value);
|
preferencesEditor.putString(key, value);
|
||||||
@ -186,6 +139,6 @@ public class IdentityKeyUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void delete(Context context, String key) {
|
public static void delete(Context context, String key) {
|
||||||
context.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0).edit().remove(key).commit();
|
context.getSharedPreferences(MASTER_SECRET_UTIL_PREFERENCES_NAME, 0).edit().remove(key).commit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,225 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (C) 2011 Whisper Systems
|
|
||||||
* Copyright (C) 2013 Open Whisper Systems
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package org.thoughtcrime.securesms.crypto;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.util.Base64;
|
|
||||||
import org.thoughtcrime.securesms.util.Hex;
|
|
||||||
import org.session.libsignal.libsignal.InvalidMessageException;
|
|
||||||
import org.session.libsignal.libsignal.ecc.Curve;
|
|
||||||
import org.session.libsignal.libsignal.ecc.ECPrivateKey;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
|
||||||
import java.security.InvalidKeyException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import javax.crypto.BadPaddingException;
|
|
||||||
import javax.crypto.Cipher;
|
|
||||||
import javax.crypto.IllegalBlockSizeException;
|
|
||||||
import javax.crypto.Mac;
|
|
||||||
import javax.crypto.NoSuchPaddingException;
|
|
||||||
import javax.crypto.spec.IvParameterSpec;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class that handles encryption for local storage.
|
|
||||||
*
|
|
||||||
* The protocol format is roughly:
|
|
||||||
*
|
|
||||||
* 1) 16 byte random IV.
|
|
||||||
* 2) AES-CBC(plaintext)
|
|
||||||
* 3) HMAC-SHA1 of 1 and 2
|
|
||||||
*
|
|
||||||
* @author Moxie Marlinspike
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class MasterCipher {
|
|
||||||
|
|
||||||
private static final String TAG = MasterCipher.class.getSimpleName();
|
|
||||||
|
|
||||||
private final MasterSecret masterSecret;
|
|
||||||
private final Cipher encryptingCipher;
|
|
||||||
private final Cipher decryptingCipher;
|
|
||||||
private final Mac hmac;
|
|
||||||
|
|
||||||
public MasterCipher(MasterSecret masterSecret) {
|
|
||||||
try {
|
|
||||||
this.masterSecret = masterSecret;
|
|
||||||
this.encryptingCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
|
||||||
this.decryptingCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
|
||||||
this.hmac = Mac.getInstance("HmacSHA1");
|
|
||||||
} catch (NoSuchPaddingException | NoSuchAlgorithmException nspe) {
|
|
||||||
throw new AssertionError(nspe);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] encryptKey(ECPrivateKey privateKey) {
|
|
||||||
return encryptBytes(privateKey.serialize());
|
|
||||||
}
|
|
||||||
|
|
||||||
public String encryptBody(@NonNull String body) {
|
|
||||||
return encryptAndEncodeBytes(body.getBytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
public String decryptBody(String body) throws InvalidMessageException {
|
|
||||||
return new String(decodeAndDecryptBytes(body));
|
|
||||||
}
|
|
||||||
|
|
||||||
public ECPrivateKey decryptKey(byte[] key)
|
|
||||||
throws org.session.libsignal.libsignal.InvalidKeyException
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
return Curve.decodePrivatePoint(decryptBytes(key));
|
|
||||||
} catch (InvalidMessageException ime) {
|
|
||||||
throw new org.session.libsignal.libsignal.InvalidKeyException(ime);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] decryptBytes(@NonNull byte[] decodedBody) throws InvalidMessageException {
|
|
||||||
try {
|
|
||||||
Mac mac = getMac(masterSecret.getMacKey());
|
|
||||||
byte[] encryptedBody = verifyMacBody(mac, decodedBody);
|
|
||||||
|
|
||||||
Cipher cipher = getDecryptingCipher(masterSecret.getEncryptionKey(), encryptedBody);
|
|
||||||
byte[] encrypted = getDecryptedBody(cipher, encryptedBody);
|
|
||||||
|
|
||||||
return encrypted;
|
|
||||||
} catch (GeneralSecurityException ge) {
|
|
||||||
throw new InvalidMessageException(ge);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] encryptBytes(byte[] body) {
|
|
||||||
try {
|
|
||||||
Cipher cipher = getEncryptingCipher(masterSecret.getEncryptionKey());
|
|
||||||
Mac mac = getMac(masterSecret.getMacKey());
|
|
||||||
|
|
||||||
byte[] encryptedBody = getEncryptedBody(cipher, body);
|
|
||||||
byte[] encryptedAndMacBody = getMacBody(mac, encryptedBody);
|
|
||||||
|
|
||||||
return encryptedAndMacBody;
|
|
||||||
} catch (GeneralSecurityException ge) {
|
|
||||||
Log.w("bodycipher", ge);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean verifyMacFor(String content, byte[] theirMac) {
|
|
||||||
byte[] ourMac = getMacFor(content);
|
|
||||||
Log.i(TAG, "Our Mac: " + Hex.toString(ourMac));
|
|
||||||
Log.i(TAG, "Thr Mac: " + Hex.toString(theirMac));
|
|
||||||
return Arrays.equals(ourMac, theirMac);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getMacFor(String content) {
|
|
||||||
Log.w(TAG, "Macing: " + content);
|
|
||||||
try {
|
|
||||||
Mac mac = getMac(masterSecret.getMacKey());
|
|
||||||
return mac.doFinal(content.getBytes());
|
|
||||||
} catch (GeneralSecurityException ike) {
|
|
||||||
throw new AssertionError(ike);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] decodeAndDecryptBytes(String body) throws InvalidMessageException {
|
|
||||||
try {
|
|
||||||
byte[] decodedBody = Base64.decode(body);
|
|
||||||
return decryptBytes(decodedBody);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new InvalidMessageException("Bad Base64 Encoding...", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String encryptAndEncodeBytes(@NonNull byte[] bytes) {
|
|
||||||
byte[] encryptedAndMacBody = encryptBytes(bytes);
|
|
||||||
return Base64.encodeBytes(encryptedAndMacBody);
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] verifyMacBody(@NonNull Mac hmac, @NonNull byte[] encryptedAndMac) throws InvalidMessageException {
|
|
||||||
if (encryptedAndMac.length < hmac.getMacLength()) {
|
|
||||||
throw new InvalidMessageException("length(encrypted body + MAC) < length(MAC)");
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] encrypted = new byte[encryptedAndMac.length - hmac.getMacLength()];
|
|
||||||
System.arraycopy(encryptedAndMac, 0, encrypted, 0, encrypted.length);
|
|
||||||
|
|
||||||
byte[] remoteMac = new byte[hmac.getMacLength()];
|
|
||||||
System.arraycopy(encryptedAndMac, encryptedAndMac.length - remoteMac.length, remoteMac, 0, remoteMac.length);
|
|
||||||
|
|
||||||
byte[] localMac = hmac.doFinal(encrypted);
|
|
||||||
|
|
||||||
if (!Arrays.equals(remoteMac, localMac))
|
|
||||||
throw new InvalidMessageException("MAC doesen't match.");
|
|
||||||
|
|
||||||
return encrypted;
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] getDecryptedBody(Cipher cipher, byte[] encryptedBody) throws IllegalBlockSizeException, BadPaddingException {
|
|
||||||
return cipher.doFinal(encryptedBody, cipher.getBlockSize(), encryptedBody.length - cipher.getBlockSize());
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] getEncryptedBody(Cipher cipher, byte[] body) throws IllegalBlockSizeException, BadPaddingException {
|
|
||||||
byte[] encrypted = cipher.doFinal(body);
|
|
||||||
byte[] iv = cipher.getIV();
|
|
||||||
|
|
||||||
byte[] ivAndBody = new byte[iv.length + encrypted.length];
|
|
||||||
System.arraycopy(iv, 0, ivAndBody, 0, iv.length);
|
|
||||||
System.arraycopy(encrypted, 0, ivAndBody, iv.length, encrypted.length);
|
|
||||||
|
|
||||||
return ivAndBody;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Mac getMac(SecretKeySpec key) throws NoSuchAlgorithmException, InvalidKeyException {
|
|
||||||
// Mac hmac = Mac.getInstance("HmacSHA1");
|
|
||||||
hmac.init(key);
|
|
||||||
|
|
||||||
return hmac;
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] getMacBody(Mac hmac, byte[] encryptedBody) {
|
|
||||||
byte[] mac = hmac.doFinal(encryptedBody);
|
|
||||||
byte[] encryptedAndMac = new byte[encryptedBody.length + mac.length];
|
|
||||||
|
|
||||||
System.arraycopy(encryptedBody, 0, encryptedAndMac, 0, encryptedBody.length);
|
|
||||||
System.arraycopy(mac, 0, encryptedAndMac, encryptedBody.length, mac.length);
|
|
||||||
|
|
||||||
return encryptedAndMac;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Cipher getDecryptingCipher(SecretKeySpec key, byte[] encryptedBody) throws InvalidKeyException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchPaddingException {
|
|
||||||
// Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
|
||||||
IvParameterSpec iv = new IvParameterSpec(encryptedBody, 0, decryptingCipher.getBlockSize());
|
|
||||||
decryptingCipher.init(Cipher.DECRYPT_MODE, key, iv);
|
|
||||||
|
|
||||||
return decryptingCipher;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Cipher getEncryptingCipher(SecretKeySpec key) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException {
|
|
||||||
// Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
|
||||||
encryptingCipher.init(Cipher.ENCRYPT_MODE, key);
|
|
||||||
|
|
||||||
return encryptingCipher;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,119 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (C) 2011 Whisper Systems
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package org.thoughtcrime.securesms.crypto;
|
|
||||||
|
|
||||||
import android.os.Parcel;
|
|
||||||
import android.os.Parcelable;
|
|
||||||
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When a user first initializes TextSecure, a few secrets
|
|
||||||
* are generated. These are:
|
|
||||||
*
|
|
||||||
* 1) A 128bit symmetric encryption key.
|
|
||||||
* 2) A 160bit symmetric MAC key.
|
|
||||||
* 3) An ECC keypair.
|
|
||||||
*
|
|
||||||
* The first two, along with the ECC keypair's private key, are
|
|
||||||
* then encrypted on disk using PBE.
|
|
||||||
*
|
|
||||||
* This class represents 1 and 2.
|
|
||||||
*
|
|
||||||
* @author Moxie Marlinspike
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class MasterSecret implements Parcelable {
|
|
||||||
|
|
||||||
private final SecretKeySpec encryptionKey;
|
|
||||||
private final SecretKeySpec macKey;
|
|
||||||
|
|
||||||
public static final Parcelable.Creator<MasterSecret> CREATOR = new Parcelable.Creator<MasterSecret>() {
|
|
||||||
@Override
|
|
||||||
public MasterSecret createFromParcel(Parcel in) {
|
|
||||||
return new MasterSecret(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MasterSecret[] newArray(int size) {
|
|
||||||
return new MasterSecret[size];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public MasterSecret(SecretKeySpec encryptionKey, SecretKeySpec macKey) {
|
|
||||||
this.encryptionKey = encryptionKey;
|
|
||||||
this.macKey = macKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
private MasterSecret(Parcel in) {
|
|
||||||
byte[] encryptionKeyBytes = new byte[in.readInt()];
|
|
||||||
in.readByteArray(encryptionKeyBytes);
|
|
||||||
|
|
||||||
byte[] macKeyBytes = new byte[in.readInt()];
|
|
||||||
in.readByteArray(macKeyBytes);
|
|
||||||
|
|
||||||
this.encryptionKey = new SecretKeySpec(encryptionKeyBytes, "AES");
|
|
||||||
this.macKey = new SecretKeySpec(macKeyBytes, "HmacSHA1");
|
|
||||||
|
|
||||||
// SecretKeySpec does an internal copy in its constructor.
|
|
||||||
Arrays.fill(encryptionKeyBytes, (byte) 0x00);
|
|
||||||
Arrays.fill(macKeyBytes, (byte)0x00);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public SecretKeySpec getEncryptionKey() {
|
|
||||||
return this.encryptionKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SecretKeySpec getMacKey() {
|
|
||||||
return this.macKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeToParcel(Parcel out, int flags) {
|
|
||||||
out.writeInt(encryptionKey.getEncoded().length);
|
|
||||||
out.writeByteArray(encryptionKey.getEncoded());
|
|
||||||
out.writeInt(macKey.getEncoded().length);
|
|
||||||
out.writeByteArray(macKey.getEncoded());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int describeContents() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MasterSecret parcelClone() {
|
|
||||||
Parcel thisParcel = Parcel.obtain();
|
|
||||||
Parcel thatParcel = Parcel.obtain();
|
|
||||||
byte[] bytes = null;
|
|
||||||
|
|
||||||
thisParcel.writeValue(this);
|
|
||||||
bytes = thisParcel.marshall();
|
|
||||||
|
|
||||||
thatParcel.unmarshall(bytes, 0, bytes.length);
|
|
||||||
thatParcel.setDataPosition(0);
|
|
||||||
|
|
||||||
MasterSecret that = (MasterSecret)thatParcel.readValue(MasterSecret.class.getClassLoader());
|
|
||||||
|
|
||||||
thisParcel.recycle();
|
|
||||||
thatParcel.recycle();
|
|
||||||
|
|
||||||
return that;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,374 +0,0 @@
|
|||||||
/**
|
|
||||||
* Copyright (C) 2011 Whisper Systems
|
|
||||||
* Copyright (C) 2013 Open Whisper Systems
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package org.thoughtcrime.securesms.crypto;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.util.Base64;
|
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
|
||||||
import org.session.libsignal.libsignal.InvalidKeyException;
|
|
||||||
import org.session.libsignal.libsignal.ecc.Curve;
|
|
||||||
import org.session.libsignal.libsignal.ecc.ECKeyPair;
|
|
||||||
import org.session.libsignal.libsignal.ecc.ECPrivateKey;
|
|
||||||
import org.session.libsignal.libsignal.ecc.ECPublicKey;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.security.spec.InvalidKeySpecException;
|
|
||||||
import java.util.Arrays;
|
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
|
||||||
import javax.crypto.KeyGenerator;
|
|
||||||
import javax.crypto.Mac;
|
|
||||||
import javax.crypto.SecretKey;
|
|
||||||
import javax.crypto.SecretKeyFactory;
|
|
||||||
import javax.crypto.spec.PBEKeySpec;
|
|
||||||
import javax.crypto.spec.PBEParameterSpec;
|
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class for generating and securely storing a MasterSecret.
|
|
||||||
*
|
|
||||||
* @author Moxie Marlinspike
|
|
||||||
*/
|
|
||||||
|
|
||||||
public class MasterSecretUtil {
|
|
||||||
|
|
||||||
public static final String UNENCRYPTED_PASSPHRASE = "unencrypted";
|
|
||||||
public static final String PREFERENCES_NAME = "SecureSMS-Preferences";
|
|
||||||
|
|
||||||
private static final String ASYMMETRIC_LOCAL_PUBLIC_DJB = "asymmetric_master_secret_curve25519_public";
|
|
||||||
private static final String ASYMMETRIC_LOCAL_PRIVATE_DJB = "asymmetric_master_secret_curve25519_private";
|
|
||||||
|
|
||||||
public static MasterSecret changeMasterSecretPassphrase(Context context,
|
|
||||||
MasterSecret masterSecret,
|
|
||||||
String newPassphrase)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
byte[] combinedSecrets = Util.combine(masterSecret.getEncryptionKey().getEncoded(),
|
|
||||||
masterSecret.getMacKey().getEncoded());
|
|
||||||
|
|
||||||
byte[] encryptionSalt = generateSalt();
|
|
||||||
int iterations = generateIterationCount(newPassphrase, encryptionSalt);
|
|
||||||
byte[] encryptedMasterSecret = encryptWithPassphrase(encryptionSalt, iterations, combinedSecrets, newPassphrase);
|
|
||||||
byte[] macSalt = generateSalt();
|
|
||||||
byte[] encryptedAndMacdMasterSecret = macWithPassphrase(macSalt, iterations, encryptedMasterSecret, newPassphrase);
|
|
||||||
|
|
||||||
save(context, "encryption_salt", encryptionSalt);
|
|
||||||
save(context, "mac_salt", macSalt);
|
|
||||||
save(context, "passphrase_iterations", iterations);
|
|
||||||
save(context, "master_secret", encryptedAndMacdMasterSecret);
|
|
||||||
save(context, "passphrase_initialized", true);
|
|
||||||
|
|
||||||
return masterSecret;
|
|
||||||
} catch (GeneralSecurityException gse) {
|
|
||||||
throw new AssertionError(gse);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MasterSecret changeMasterSecretPassphrase(Context context,
|
|
||||||
String originalPassphrase,
|
|
||||||
String newPassphrase)
|
|
||||||
throws InvalidPassphraseException
|
|
||||||
{
|
|
||||||
MasterSecret masterSecret = getMasterSecret(context, originalPassphrase);
|
|
||||||
changeMasterSecretPassphrase(context, masterSecret, newPassphrase);
|
|
||||||
|
|
||||||
return masterSecret;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MasterSecret getMasterSecret(Context context, String passphrase)
|
|
||||||
throws InvalidPassphraseException
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
byte[] encryptedAndMacdMasterSecret = retrieve(context, "master_secret");
|
|
||||||
byte[] macSalt = retrieve(context, "mac_salt");
|
|
||||||
int iterations = retrieve(context, "passphrase_iterations", 100);
|
|
||||||
byte[] encryptedMasterSecret = verifyMac(macSalt, iterations, encryptedAndMacdMasterSecret, passphrase);
|
|
||||||
byte[] encryptionSalt = retrieve(context, "encryption_salt");
|
|
||||||
byte[] combinedSecrets = decryptWithPassphrase(encryptionSalt, iterations, encryptedMasterSecret, passphrase);
|
|
||||||
byte[] encryptionSecret = Util.split(combinedSecrets, 16, 20)[0];
|
|
||||||
byte[] macSecret = Util.split(combinedSecrets, 16, 20)[1];
|
|
||||||
|
|
||||||
return new MasterSecret(new SecretKeySpec(encryptionSecret, "AES"),
|
|
||||||
new SecretKeySpec(macSecret, "HmacSHA1"));
|
|
||||||
} catch (GeneralSecurityException e) {
|
|
||||||
Log.w("keyutil", e);
|
|
||||||
return null; //XXX
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.w("keyutil", e);
|
|
||||||
return null; //XXX
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AsymmetricMasterSecret getAsymmetricMasterSecret(@NonNull Context context,
|
|
||||||
@Nullable MasterSecret masterSecret)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
byte[] djbPublicBytes = retrieve(context, ASYMMETRIC_LOCAL_PUBLIC_DJB);
|
|
||||||
byte[] djbPrivateBytes = retrieve(context, ASYMMETRIC_LOCAL_PRIVATE_DJB);
|
|
||||||
|
|
||||||
ECPublicKey djbPublicKey = null;
|
|
||||||
ECPrivateKey djbPrivateKey = null;
|
|
||||||
|
|
||||||
if (djbPublicBytes != null) {
|
|
||||||
djbPublicKey = Curve.decodePoint(djbPublicBytes, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (masterSecret != null) {
|
|
||||||
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
|
||||||
|
|
||||||
if (djbPrivateBytes != null) {
|
|
||||||
djbPrivateKey = masterCipher.decryptKey(djbPrivateBytes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new AsymmetricMasterSecret(djbPublicKey, djbPrivateKey);
|
|
||||||
} catch (InvalidKeyException | IOException ike) {
|
|
||||||
throw new AssertionError(ike);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static AsymmetricMasterSecret generateAsymmetricMasterSecret(Context context,
|
|
||||||
MasterSecret masterSecret)
|
|
||||||
{
|
|
||||||
MasterCipher masterCipher = new MasterCipher(masterSecret);
|
|
||||||
ECKeyPair keyPair = Curve.generateKeyPair();
|
|
||||||
|
|
||||||
save(context, ASYMMETRIC_LOCAL_PUBLIC_DJB, keyPair.getPublicKey().serialize());
|
|
||||||
save(context, ASYMMETRIC_LOCAL_PRIVATE_DJB, masterCipher.encryptKey(keyPair.getPrivateKey()));
|
|
||||||
|
|
||||||
return new AsymmetricMasterSecret(keyPair.getPublicKey(), keyPair.getPrivateKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MasterSecret generateMasterSecret(Context context, String passphrase) {
|
|
||||||
try {
|
|
||||||
byte[] encryptionSecret = generateEncryptionSecret();
|
|
||||||
byte[] macSecret = generateMacSecret();
|
|
||||||
byte[] masterSecret = Util.combine(encryptionSecret, macSecret);
|
|
||||||
byte[] encryptionSalt = generateSalt();
|
|
||||||
int iterations = generateIterationCount(passphrase, encryptionSalt);
|
|
||||||
byte[] encryptedMasterSecret = encryptWithPassphrase(encryptionSalt, iterations, masterSecret, passphrase);
|
|
||||||
byte[] macSalt = generateSalt();
|
|
||||||
byte[] encryptedAndMacdMasterSecret = macWithPassphrase(macSalt, iterations, encryptedMasterSecret, passphrase);
|
|
||||||
|
|
||||||
save(context, "encryption_salt", encryptionSalt);
|
|
||||||
save(context, "mac_salt", macSalt);
|
|
||||||
save(context, "passphrase_iterations", iterations);
|
|
||||||
save(context, "master_secret", encryptedAndMacdMasterSecret);
|
|
||||||
save(context, "passphrase_initialized", true);
|
|
||||||
|
|
||||||
return new MasterSecret(new SecretKeySpec(encryptionSecret, "AES"),
|
|
||||||
new SecretKeySpec(macSecret, "HmacSHA1"));
|
|
||||||
} catch (GeneralSecurityException e) {
|
|
||||||
Log.w("keyutil", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean hasAsymmericMasterSecret(Context context) {
|
|
||||||
SharedPreferences settings = context.getSharedPreferences(PREFERENCES_NAME, 0);
|
|
||||||
return settings.contains(ASYMMETRIC_LOCAL_PUBLIC_DJB);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isPassphraseInitialized(Context context) {
|
|
||||||
SharedPreferences preferences = context.getSharedPreferences(PREFERENCES_NAME, 0);
|
|
||||||
return preferences.getBoolean("passphrase_initialized", false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void clear(Context context) {
|
|
||||||
context.getSharedPreferences(PREFERENCES_NAME, 0).edit().clear().commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void save(Context context, String key, int value) {
|
|
||||||
if (!context.getSharedPreferences(PREFERENCES_NAME, 0)
|
|
||||||
.edit()
|
|
||||||
.putInt(key, value)
|
|
||||||
.commit())
|
|
||||||
{
|
|
||||||
throw new AssertionError("failed to save a shared pref in MasterSecretUtil");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void save(Context context, String key, byte[] value) {
|
|
||||||
if (!context.getSharedPreferences(PREFERENCES_NAME, 0)
|
|
||||||
.edit()
|
|
||||||
.putString(key, Base64.encodeBytes(value))
|
|
||||||
.commit())
|
|
||||||
{
|
|
||||||
throw new AssertionError("failed to save a shared pref in MasterSecretUtil");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void save(Context context, String key, boolean value) {
|
|
||||||
if (!context.getSharedPreferences(PREFERENCES_NAME, 0)
|
|
||||||
.edit()
|
|
||||||
.putBoolean(key, value)
|
|
||||||
.commit())
|
|
||||||
{
|
|
||||||
throw new AssertionError("failed to save a shared pref in MasterSecretUtil");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] retrieve(Context context, String key) throws IOException {
|
|
||||||
SharedPreferences settings = context.getSharedPreferences(PREFERENCES_NAME, 0);
|
|
||||||
String encodedValue = settings.getString(key, "");
|
|
||||||
|
|
||||||
if (TextUtils.isEmpty(encodedValue)) return null;
|
|
||||||
else return Base64.decode(encodedValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int retrieve(Context context, String key, int defaultValue) throws IOException {
|
|
||||||
SharedPreferences settings = context.getSharedPreferences(PREFERENCES_NAME, 0);
|
|
||||||
return settings.getInt(key, defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] generateEncryptionSecret() {
|
|
||||||
try {
|
|
||||||
KeyGenerator generator = KeyGenerator.getInstance("AES");
|
|
||||||
generator.init(128);
|
|
||||||
|
|
||||||
SecretKey key = generator.generateKey();
|
|
||||||
return key.getEncoded();
|
|
||||||
} catch (NoSuchAlgorithmException ex) {
|
|
||||||
Log.w("keyutil", ex);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] generateMacSecret() {
|
|
||||||
try {
|
|
||||||
KeyGenerator generator = KeyGenerator.getInstance("HmacSHA1");
|
|
||||||
return generator.generateKey().getEncoded();
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
Log.w("keyutil", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] generateSalt() {
|
|
||||||
SecureRandom random = new SecureRandom();
|
|
||||||
byte[] salt = new byte[16];
|
|
||||||
random.nextBytes(salt);
|
|
||||||
|
|
||||||
return salt;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int generateIterationCount(String passphrase, byte[] salt) {
|
|
||||||
int TARGET_ITERATION_TIME = 50; //ms
|
|
||||||
int MINIMUM_ITERATION_COUNT = 100; //default for low-end devices
|
|
||||||
int BENCHMARK_ITERATION_COUNT = 10000; //baseline starting iteration count
|
|
||||||
|
|
||||||
try {
|
|
||||||
PBEKeySpec keyspec = new PBEKeySpec(passphrase.toCharArray(), salt, BENCHMARK_ITERATION_COUNT);
|
|
||||||
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBEWITHSHA1AND128BITAES-CBC-BC");
|
|
||||||
|
|
||||||
long startTime = System.currentTimeMillis();
|
|
||||||
skf.generateSecret(keyspec);
|
|
||||||
long finishTime = System.currentTimeMillis();
|
|
||||||
|
|
||||||
int scaledIterationTarget = (int) (((double)BENCHMARK_ITERATION_COUNT / (double)(finishTime - startTime)) * TARGET_ITERATION_TIME);
|
|
||||||
|
|
||||||
if (scaledIterationTarget < MINIMUM_ITERATION_COUNT) return MINIMUM_ITERATION_COUNT;
|
|
||||||
else return scaledIterationTarget;
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
Log.w("MasterSecretUtil", e);
|
|
||||||
return MINIMUM_ITERATION_COUNT;
|
|
||||||
} catch (InvalidKeySpecException e) {
|
|
||||||
Log.w("MasterSecretUtil", e);
|
|
||||||
return MINIMUM_ITERATION_COUNT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static SecretKey getKeyFromPassphrase(String passphrase, byte[] salt, int iterations)
|
|
||||||
throws GeneralSecurityException
|
|
||||||
{
|
|
||||||
PBEKeySpec keyspec = new PBEKeySpec(passphrase.toCharArray(), salt, iterations);
|
|
||||||
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBEWITHSHA1AND128BITAES-CBC-BC");
|
|
||||||
return skf.generateSecret(keyspec);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Cipher getCipherFromPassphrase(String passphrase, byte[] salt, int iterations, int opMode)
|
|
||||||
throws GeneralSecurityException
|
|
||||||
{
|
|
||||||
SecretKey key = getKeyFromPassphrase(passphrase, salt, iterations);
|
|
||||||
Cipher cipher = Cipher.getInstance(key.getAlgorithm());
|
|
||||||
cipher.init(opMode, key, new PBEParameterSpec(salt, iterations));
|
|
||||||
|
|
||||||
return cipher;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] encryptWithPassphrase(byte[] encryptionSalt, int iterations, byte[] data, String passphrase)
|
|
||||||
throws GeneralSecurityException
|
|
||||||
{
|
|
||||||
Cipher cipher = getCipherFromPassphrase(passphrase, encryptionSalt, iterations, Cipher.ENCRYPT_MODE);
|
|
||||||
return cipher.doFinal(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] decryptWithPassphrase(byte[] encryptionSalt, int iterations, byte[] data, String passphrase)
|
|
||||||
throws GeneralSecurityException, IOException
|
|
||||||
{
|
|
||||||
Cipher cipher = getCipherFromPassphrase(passphrase, encryptionSalt, iterations, Cipher.DECRYPT_MODE);
|
|
||||||
return cipher.doFinal(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Mac getMacForPassphrase(String passphrase, byte[] salt, int iterations)
|
|
||||||
throws GeneralSecurityException
|
|
||||||
{
|
|
||||||
SecretKey key = getKeyFromPassphrase(passphrase, salt, iterations);
|
|
||||||
byte[] pbkdf2 = key.getEncoded();
|
|
||||||
SecretKeySpec hmacKey = new SecretKeySpec(pbkdf2, "HmacSHA1");
|
|
||||||
Mac hmac = Mac.getInstance("HmacSHA1");
|
|
||||||
hmac.init(hmacKey);
|
|
||||||
|
|
||||||
return hmac;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] verifyMac(byte[] macSalt, int iterations, byte[] encryptedAndMacdData, String passphrase) throws InvalidPassphraseException, GeneralSecurityException, IOException {
|
|
||||||
Mac hmac = getMacForPassphrase(passphrase, macSalt, iterations);
|
|
||||||
|
|
||||||
byte[] encryptedData = new byte[encryptedAndMacdData.length - hmac.getMacLength()];
|
|
||||||
System.arraycopy(encryptedAndMacdData, 0, encryptedData, 0, encryptedData.length);
|
|
||||||
|
|
||||||
byte[] givenMac = new byte[hmac.getMacLength()];
|
|
||||||
System.arraycopy(encryptedAndMacdData, encryptedAndMacdData.length-hmac.getMacLength(), givenMac, 0, givenMac.length);
|
|
||||||
|
|
||||||
byte[] localMac = hmac.doFinal(encryptedData);
|
|
||||||
|
|
||||||
if (Arrays.equals(givenMac, localMac)) return encryptedData;
|
|
||||||
else throw new InvalidPassphraseException("MAC Error");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] macWithPassphrase(byte[] macSalt, int iterations, byte[] data, String passphrase) throws GeneralSecurityException {
|
|
||||||
Mac hmac = getMacForPassphrase(passphrase, macSalt, iterations);
|
|
||||||
byte[] mac = hmac.doFinal(data);
|
|
||||||
byte[] result = new byte[data.length + mac.length];
|
|
||||||
|
|
||||||
System.arraycopy(data, 0, result, 0, data.length);
|
|
||||||
System.arraycopy(mac, 0, result, data.length, mac.length);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
@ -17,18 +17,15 @@
|
|||||||
package org.thoughtcrime.securesms.database;
|
package org.thoughtcrime.securesms.database;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import net.sqlcipher.database.SQLiteDatabase;
|
import net.sqlcipher.database.SQLiteDatabase;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.DatabaseUpgradeActivity;
|
|
||||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
|
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
|
||||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
||||||
import org.thoughtcrime.securesms.crypto.DatabaseSecret;
|
import org.thoughtcrime.securesms.crypto.DatabaseSecret;
|
||||||
import org.thoughtcrime.securesms.crypto.DatabaseSecretProvider;
|
import org.thoughtcrime.securesms.crypto.DatabaseSecretProvider;
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
|
||||||
import org.thoughtcrime.securesms.database.helpers.ClassicOpenHelper;
|
|
||||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherMigrationHelper;
|
|
||||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||||
import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase;
|
import org.thoughtcrime.securesms.loki.database.LokiAPIDatabase;
|
||||||
import org.thoughtcrime.securesms.loki.database.LokiBackupFilesDatabase;
|
import org.thoughtcrime.securesms.loki.database.LokiBackupFilesDatabase;
|
||||||
@ -36,7 +33,6 @@ import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase;
|
|||||||
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
|
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
|
||||||
import org.thoughtcrime.securesms.loki.database.LokiUserDatabase;
|
import org.thoughtcrime.securesms.loki.database.LokiUserDatabase;
|
||||||
import org.thoughtcrime.securesms.loki.database.SharedSenderKeysDatabase;
|
import org.thoughtcrime.securesms.loki.database.SharedSenderKeysDatabase;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|
||||||
|
|
||||||
public class DatabaseFactory {
|
public class DatabaseFactory {
|
||||||
|
|
||||||
@ -227,28 +223,4 @@ public class DatabaseFactory {
|
|||||||
this.sskDatabase = new SharedSenderKeysDatabase(context, databaseHelper);
|
this.sskDatabase = new SharedSenderKeysDatabase(context, databaseHelper);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onApplicationLevelUpgrade(@NonNull Context context, @NonNull MasterSecret masterSecret,
|
|
||||||
int fromVersion, DatabaseUpgradeActivity.DatabaseUpgradeListener listener)
|
|
||||||
{
|
|
||||||
databaseHelper.getWritableDatabase();
|
|
||||||
|
|
||||||
ClassicOpenHelper legacyOpenHelper = null;
|
|
||||||
|
|
||||||
if (fromVersion < DatabaseUpgradeActivity.ASYMMETRIC_MASTER_SECRET_FIX_VERSION) {
|
|
||||||
legacyOpenHelper = new ClassicOpenHelper(context);
|
|
||||||
legacyOpenHelper.onApplicationLevelUpgrade(context, masterSecret, fromVersion, listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fromVersion < DatabaseUpgradeActivity.SQLCIPHER && TextSecurePreferences.getNeedsSqlCipherMigration(context)) {
|
|
||||||
if (legacyOpenHelper == null) {
|
|
||||||
legacyOpenHelper = new ClassicOpenHelper(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
SQLCipherMigrationHelper.migrateCiphertext(context, masterSecret,
|
|
||||||
legacyOpenHelper.getWritableDatabase(),
|
|
||||||
databaseHelper.getWritableDatabase(),
|
|
||||||
listener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,254 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.database.helpers;
|
|
||||||
|
|
||||||
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.database.Cursor;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
|
||||||
import android.util.Pair;
|
|
||||||
|
|
||||||
import com.annimon.stream.function.BiFunction;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.DatabaseUpgradeActivity;
|
|
||||||
import network.loki.messenger.R;
|
|
||||||
import org.thoughtcrime.securesms.crypto.AsymmetricMasterCipher;
|
|
||||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
|
||||||
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
|
||||||
import org.thoughtcrime.securesms.service.GenericForegroundService;
|
|
||||||
import org.thoughtcrime.securesms.util.Base64;
|
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|
||||||
import org.session.libsignal.libsignal.InvalidMessageException;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public class SQLCipherMigrationHelper {
|
|
||||||
|
|
||||||
private static final String TAG = SQLCipherMigrationHelper.class.getSimpleName();
|
|
||||||
|
|
||||||
private static final long ENCRYPTION_SYMMETRIC_BIT = 0x80000000;
|
|
||||||
private static final long ENCRYPTION_ASYMMETRIC_BIT = 0x40000000;
|
|
||||||
|
|
||||||
static void migratePlaintext(@NonNull Context context,
|
|
||||||
@NonNull android.database.sqlite.SQLiteDatabase legacyDb,
|
|
||||||
@NonNull net.sqlcipher.database.SQLiteDatabase modernDb)
|
|
||||||
{
|
|
||||||
modernDb.beginTransaction();
|
|
||||||
try {
|
|
||||||
GenericForegroundService.startForegroundTask(context, context.getString(R.string.SQLCipherMigrationHelper_migrating_signal_database));
|
|
||||||
copyTable("identities", legacyDb, modernDb, null);
|
|
||||||
copyTable("push", legacyDb, modernDb, null);
|
|
||||||
copyTable("groups", legacyDb, modernDb, null);
|
|
||||||
copyTable("recipient_preferences", legacyDb, modernDb, null);
|
|
||||||
copyTable("group_receipts", legacyDb, modernDb, null);
|
|
||||||
modernDb.setTransactionSuccessful();
|
|
||||||
} finally {
|
|
||||||
modernDb.endTransaction();
|
|
||||||
GenericForegroundService.stopForegroundTask(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void migrateCiphertext(@NonNull Context context,
|
|
||||||
@NonNull MasterSecret masterSecret,
|
|
||||||
@NonNull android.database.sqlite.SQLiteDatabase legacyDb,
|
|
||||||
@NonNull net.sqlcipher.database.SQLiteDatabase modernDb,
|
|
||||||
@Nullable DatabaseUpgradeActivity.DatabaseUpgradeListener listener)
|
|
||||||
{
|
|
||||||
MasterCipher legacyCipher = new MasterCipher(masterSecret);
|
|
||||||
AsymmetricMasterCipher legacyAsymmetricCipher = new AsymmetricMasterCipher(MasterSecretUtil.getAsymmetricMasterSecret(context, masterSecret));
|
|
||||||
|
|
||||||
modernDb.beginTransaction();
|
|
||||||
|
|
||||||
try {
|
|
||||||
GenericForegroundService.startForegroundTask(context, context.getString(R.string.SQLCipherMigrationHelper_migrating_signal_database));
|
|
||||||
int total = 5000;
|
|
||||||
|
|
||||||
copyTable("sms", legacyDb, modernDb, (row, progress) -> {
|
|
||||||
Pair<Long, String> plaintext = getPlaintextBody(legacyCipher, legacyAsymmetricCipher,
|
|
||||||
row.getAsLong("type"),
|
|
||||||
row.getAsString("body"));
|
|
||||||
|
|
||||||
row.put("body", plaintext.second);
|
|
||||||
row.put("type", plaintext.first);
|
|
||||||
|
|
||||||
if (listener != null && (progress.first % 1000 == 0)) {
|
|
||||||
listener.setProgress(getTotalProgress(0, progress.first, progress.second), total);
|
|
||||||
}
|
|
||||||
|
|
||||||
return row;
|
|
||||||
});
|
|
||||||
|
|
||||||
copyTable("mms", legacyDb, modernDb, (row, progress) -> {
|
|
||||||
Pair<Long, String> plaintext = getPlaintextBody(legacyCipher, legacyAsymmetricCipher,
|
|
||||||
row.getAsLong("msg_box"),
|
|
||||||
row.getAsString("body"));
|
|
||||||
|
|
||||||
row.put("body", plaintext.second);
|
|
||||||
row.put("msg_box", plaintext.first);
|
|
||||||
|
|
||||||
if (listener != null && (progress.first % 1000 == 0)) {
|
|
||||||
listener.setProgress(getTotalProgress(1000, progress.first, progress.second), total);
|
|
||||||
}
|
|
||||||
|
|
||||||
return row;
|
|
||||||
});
|
|
||||||
|
|
||||||
copyTable("part", legacyDb, modernDb, (row, progress) -> {
|
|
||||||
String fileName = row.getAsString("file_name");
|
|
||||||
String mediaKey = row.getAsString("cd");
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!TextUtils.isEmpty(fileName)) {
|
|
||||||
row.put("file_name", legacyCipher.decryptBody(fileName));
|
|
||||||
}
|
|
||||||
} catch (InvalidMessageException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!TextUtils.isEmpty(mediaKey)) {
|
|
||||||
byte[] plaintext;
|
|
||||||
|
|
||||||
if (mediaKey.startsWith("?ASYNC-")) {
|
|
||||||
plaintext = legacyAsymmetricCipher.decryptBytes(Base64.decode(mediaKey.substring("?ASYNC-".length())));
|
|
||||||
} else {
|
|
||||||
plaintext = legacyCipher.decryptBytes(Base64.decode(mediaKey));
|
|
||||||
}
|
|
||||||
|
|
||||||
row.put("cd", Base64.encodeBytes(plaintext));
|
|
||||||
}
|
|
||||||
} catch (IOException | InvalidMessageException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (listener != null && (progress.first % 1000 == 0)) {
|
|
||||||
listener.setProgress(getTotalProgress(2000, progress.first, progress.second), total);
|
|
||||||
}
|
|
||||||
|
|
||||||
return row;
|
|
||||||
});
|
|
||||||
|
|
||||||
copyTable("thread", legacyDb, modernDb, (row, progress) -> {
|
|
||||||
Long snippetType = row.getAsLong("snippet_type");
|
|
||||||
if (snippetType == null) snippetType = 0L;
|
|
||||||
|
|
||||||
Pair<Long, String> plaintext = getPlaintextBody(legacyCipher, legacyAsymmetricCipher,
|
|
||||||
snippetType, row.getAsString("snippet"));
|
|
||||||
|
|
||||||
row.put("snippet", plaintext.second);
|
|
||||||
row.put("snippet_type", plaintext.first);
|
|
||||||
|
|
||||||
if (listener != null && (progress.first % 1000 == 0)) {
|
|
||||||
listener.setProgress(getTotalProgress(3000, progress.first, progress.second), total);
|
|
||||||
}
|
|
||||||
|
|
||||||
return row;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
copyTable("drafts", legacyDb, modernDb, (row, progress) -> {
|
|
||||||
String draftType = row.getAsString("type");
|
|
||||||
String draft = row.getAsString("value");
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!TextUtils.isEmpty(draftType)) row.put("type", legacyCipher.decryptBody(draftType));
|
|
||||||
if (!TextUtils.isEmpty(draft)) row.put("value", legacyCipher.decryptBody(draft));
|
|
||||||
} catch (InvalidMessageException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (listener != null && (progress.first % 1000 == 0)) {
|
|
||||||
listener.setProgress(getTotalProgress(4000, progress.first, progress.second), total);
|
|
||||||
}
|
|
||||||
|
|
||||||
return row;
|
|
||||||
});
|
|
||||||
|
|
||||||
AttachmentSecretProvider.getInstance(context).setClassicKey(context, masterSecret.getEncryptionKey().getEncoded(), masterSecret.getMacKey().getEncoded());
|
|
||||||
TextSecurePreferences.setNeedsSqlCipherMigration(context, false);
|
|
||||||
modernDb.setTransactionSuccessful();
|
|
||||||
} finally {
|
|
||||||
modernDb.endTransaction();
|
|
||||||
GenericForegroundService.stopForegroundTask(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void copyTable(@NonNull String tableName,
|
|
||||||
@NonNull android.database.sqlite.SQLiteDatabase legacyDb,
|
|
||||||
@NonNull net.sqlcipher.database.SQLiteDatabase modernDb,
|
|
||||||
@Nullable BiFunction<ContentValues, Pair<Integer, Integer>, ContentValues> transformer)
|
|
||||||
{
|
|
||||||
Set<String> destinationColumns = getTableColumns(tableName, modernDb);
|
|
||||||
|
|
||||||
try (Cursor cursor = legacyDb.query(tableName, null, null, null, null, null, null)) {
|
|
||||||
int count = (cursor != null) ? cursor.getCount() : 0;
|
|
||||||
int progress = 1;
|
|
||||||
|
|
||||||
while (cursor != null && cursor.moveToNext()) {
|
|
||||||
ContentValues row = new ContentValues();
|
|
||||||
|
|
||||||
for (int i=0;i<cursor.getColumnCount();i++) {
|
|
||||||
String columnName = cursor.getColumnName(i);
|
|
||||||
|
|
||||||
if (destinationColumns.contains(columnName)) {
|
|
||||||
switch (cursor.getType(i)) {
|
|
||||||
case Cursor.FIELD_TYPE_STRING: row.put(columnName, cursor.getString(i)); break;
|
|
||||||
case Cursor.FIELD_TYPE_FLOAT: row.put(columnName, cursor.getFloat(i)); break;
|
|
||||||
case Cursor.FIELD_TYPE_INTEGER: row.put(columnName, cursor.getLong(i)); break;
|
|
||||||
case Cursor.FIELD_TYPE_BLOB: row.put(columnName, cursor.getBlob(i)); break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (transformer != null) {
|
|
||||||
row = transformer.apply(row, new Pair<>(progress++, count));
|
|
||||||
}
|
|
||||||
|
|
||||||
modernDb.insert(tableName, null, row);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Pair<Long, String> getPlaintextBody(@NonNull MasterCipher legacyCipher,
|
|
||||||
@NonNull AsymmetricMasterCipher legacyAsymmetricCipher,
|
|
||||||
long type,
|
|
||||||
@Nullable String body)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
if (!TextUtils.isEmpty(body)) {
|
|
||||||
if ((type & ENCRYPTION_SYMMETRIC_BIT) != 0) body = legacyCipher.decryptBody(body);
|
|
||||||
else if ((type & ENCRYPTION_ASYMMETRIC_BIT) != 0) body = legacyAsymmetricCipher.decryptBody(body);
|
|
||||||
}
|
|
||||||
} catch (InvalidMessageException | IOException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
type &= ~(ENCRYPTION_SYMMETRIC_BIT);
|
|
||||||
type &= ~(ENCRYPTION_ASYMMETRIC_BIT);
|
|
||||||
|
|
||||||
return new Pair<>(type, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Set<String> getTableColumns(String tableName, net.sqlcipher.database.SQLiteDatabase database) {
|
|
||||||
Set<String> results = new HashSet<>();
|
|
||||||
|
|
||||||
try (Cursor cursor = database.rawQuery("PRAGMA table_info(" + tableName + ")", null)) {
|
|
||||||
while (cursor != null && cursor.moveToNext()) {
|
|
||||||
results.add(cursor.getString(1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int getTotalProgress(int sectionOffset, int sectionProgress, int sectionTotal) {
|
|
||||||
double percentOfSectionComplete = ((double)sectionProgress) / ((double)sectionTotal);
|
|
||||||
return sectionOffset + (int)(((double)1000) * percentOfSectionComplete);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +1,8 @@
|
|||||||
package org.thoughtcrime.securesms.database.helpers;
|
package org.thoughtcrime.securesms.database.helpers;
|
||||||
|
|
||||||
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.SystemClock;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
@ -14,10 +10,7 @@ import net.sqlcipher.database.SQLiteDatabase;
|
|||||||
import net.sqlcipher.database.SQLiteDatabaseHook;
|
import net.sqlcipher.database.SQLiteDatabaseHook;
|
||||||
import net.sqlcipher.database.SQLiteOpenHelper;
|
import net.sqlcipher.database.SQLiteOpenHelper;
|
||||||
|
|
||||||
import org.session.libsignal.service.loki.api.opengroups.PublicChat;
|
|
||||||
import org.thoughtcrime.securesms.crypto.DatabaseSecret;
|
import org.thoughtcrime.securesms.crypto.DatabaseSecret;
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
|
||||||
import org.thoughtcrime.securesms.database.Address;
|
|
||||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||||
import org.thoughtcrime.securesms.database.DraftDatabase;
|
import org.thoughtcrime.securesms.database.DraftDatabase;
|
||||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||||
@ -40,44 +33,14 @@ import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase;
|
|||||||
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
|
import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase;
|
||||||
import org.thoughtcrime.securesms.loki.database.LokiUserDatabase;
|
import org.thoughtcrime.securesms.loki.database.LokiUserDatabase;
|
||||||
import org.thoughtcrime.securesms.loki.database.SharedSenderKeysDatabase;
|
import org.thoughtcrime.securesms.loki.database.SharedSenderKeysDatabase;
|
||||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
|
||||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
|
||||||
import org.thoughtcrime.securesms.util.GroupUtil;
|
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static final String TAG = SQLCipherOpenHelper.class.getSimpleName();
|
private static final String TAG = SQLCipherOpenHelper.class.getSimpleName();
|
||||||
|
|
||||||
private static final int RECIPIENT_CALL_RINGTONE_VERSION = 2;
|
// First public release (1.0.0) DB version was 27.
|
||||||
private static final int MIGRATE_PREKEYS_VERSION = 3;
|
// So we have to keep the migrations onwards.
|
||||||
private static final int MIGRATE_SESSIONS_VERSION = 4;
|
|
||||||
private static final int NO_MORE_IMAGE_THUMBNAILS_VERSION = 5;
|
|
||||||
private static final int ATTACHMENT_DIMENSIONS = 6;
|
|
||||||
private static final int QUOTED_REPLIES = 7;
|
|
||||||
private static final int SHARED_CONTACTS = 8;
|
|
||||||
private static final int FULL_TEXT_SEARCH = 9;
|
|
||||||
private static final int BAD_IMPORT_CLEANUP = 10;
|
|
||||||
private static final int QUOTE_MISSING = 11;
|
|
||||||
private static final int NOTIFICATION_CHANNELS = 12;
|
|
||||||
private static final int SECRET_SENDER = 13;
|
|
||||||
private static final int ATTACHMENT_CAPTIONS = 14;
|
|
||||||
private static final int ATTACHMENT_CAPTIONS_FIX = 15;
|
|
||||||
private static final int PREVIEWS = 16;
|
|
||||||
private static final int CONVERSATION_SEARCH = 17;
|
|
||||||
private static final int SELF_ATTACHMENT_CLEANUP = 18;
|
|
||||||
private static final int RECIPIENT_FORCE_SMS_SELECTION = 19;
|
|
||||||
private static final int JOBMANAGER_STRIKES_BACK = 20;
|
|
||||||
private static final int STICKERS = 21;
|
|
||||||
private static final int lokiV1 = 22;
|
|
||||||
private static final int lokiV2 = 23;
|
|
||||||
private static final int lokiV3 = 24;
|
|
||||||
private static final int lokiV4 = 25;
|
|
||||||
private static final int lokiV5 = 26;
|
|
||||||
private static final int lokiV6 = 27;
|
|
||||||
private static final int lokiV7 = 28;
|
private static final int lokiV7 = 28;
|
||||||
private static final int lokiV8 = 29;
|
private static final int lokiV8 = 29;
|
||||||
private static final int lokiV9 = 30;
|
private static final int lokiV9 = 30;
|
||||||
@ -177,20 +140,6 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||||||
executeStatements(db, GroupDatabase.CREATE_INDEXS);
|
executeStatements(db, GroupDatabase.CREATE_INDEXS);
|
||||||
executeStatements(db, GroupReceiptDatabase.CREATE_INDEXES);
|
executeStatements(db, GroupReceiptDatabase.CREATE_INDEXES);
|
||||||
executeStatements(db, StickerDatabase.CREATE_INDEXES);
|
executeStatements(db, StickerDatabase.CREATE_INDEXES);
|
||||||
|
|
||||||
if (context.getDatabasePath(ClassicOpenHelper.NAME).exists()) {
|
|
||||||
ClassicOpenHelper legacyHelper = new ClassicOpenHelper(context);
|
|
||||||
android.database.sqlite.SQLiteDatabase legacyDb = legacyHelper.getWritableDatabase();
|
|
||||||
|
|
||||||
SQLCipherMigrationHelper.migratePlaintext(context, legacyDb, db);
|
|
||||||
|
|
||||||
MasterSecret masterSecret = KeyCachingService.getMasterSecret(context);
|
|
||||||
|
|
||||||
if (masterSecret != null) SQLCipherMigrationHelper.migrateCiphertext(context, masterSecret, legacyDb, db, null);
|
|
||||||
else TextSecurePreferences.setNeedsSqlCipherMigration(context, true);
|
|
||||||
|
|
||||||
SessionStoreMigrationHelper.migrateSessions(context, db);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -210,379 +159,6 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
if (oldVersion < RECIPIENT_CALL_RINGTONE_VERSION) {
|
|
||||||
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN call_ringtone TEXT DEFAULT NULL");
|
|
||||||
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN call_vibrate INTEGER DEFAULT " + RecipientDatabase.VibrateState.DEFAULT.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVersion < MIGRATE_PREKEYS_VERSION) {
|
|
||||||
db.execSQL("CREATE TABLE signed_prekeys (_id INTEGER PRIMARY KEY, key_id INTEGER UNIQUE, public_key TEXT NOT NULL, private_key TEXT NOT NULL, signature TEXT NOT NULL, timestamp INTEGER DEFAULT 0)");
|
|
||||||
db.execSQL("CREATE TABLE one_time_prekeys (_id INTEGER PRIMARY KEY, key_id INTEGER UNIQUE, public_key TEXT NOT NULL, private_key TEXT NOT NULL)");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVersion < MIGRATE_SESSIONS_VERSION) {
|
|
||||||
db.execSQL("CREATE TABLE sessions (_id INTEGER PRIMARY KEY, address TEXT NOT NULL, device INTEGER NOT NULL, record BLOB NOT NULL, UNIQUE(address, device) ON CONFLICT REPLACE)");
|
|
||||||
SessionStoreMigrationHelper.migrateSessions(context, db);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVersion < NO_MORE_IMAGE_THUMBNAILS_VERSION) {
|
|
||||||
ContentValues update = new ContentValues();
|
|
||||||
update.put("thumbnail", (String)null);
|
|
||||||
update.put("aspect_ratio", (String)null);
|
|
||||||
update.put("thumbnail_random", (String)null);
|
|
||||||
|
|
||||||
try (Cursor cursor = db.query("part", new String[] {"_id", "ct", "thumbnail"}, "thumbnail IS NOT NULL", null, null, null, null)) {
|
|
||||||
while (cursor != null && cursor.moveToNext()) {
|
|
||||||
long id = cursor.getLong(cursor.getColumnIndexOrThrow("_id"));
|
|
||||||
String contentType = cursor.getString(cursor.getColumnIndexOrThrow("ct"));
|
|
||||||
|
|
||||||
if (contentType != null && !contentType.startsWith("video")) {
|
|
||||||
String thumbnailPath = cursor.getString(cursor.getColumnIndexOrThrow("thumbnail"));
|
|
||||||
File thumbnailFile = new File(thumbnailPath);
|
|
||||||
thumbnailFile.delete();
|
|
||||||
|
|
||||||
db.update("part", update, "_id = ?", new String[] {String.valueOf(id)});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVersion < ATTACHMENT_DIMENSIONS) {
|
|
||||||
db.execSQL("ALTER TABLE part ADD COLUMN width INTEGER DEFAULT 0");
|
|
||||||
db.execSQL("ALTER TABLE part ADD COLUMN height INTEGER DEFAULT 0");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVersion < QUOTED_REPLIES) {
|
|
||||||
db.execSQL("ALTER TABLE mms ADD COLUMN quote_id INTEGER DEFAULT 0");
|
|
||||||
db.execSQL("ALTER TABLE mms ADD COLUMN quote_author TEXT");
|
|
||||||
db.execSQL("ALTER TABLE mms ADD COLUMN quote_body TEXT");
|
|
||||||
db.execSQL("ALTER TABLE mms ADD COLUMN quote_attachment INTEGER DEFAULT -1");
|
|
||||||
|
|
||||||
db.execSQL("ALTER TABLE part ADD COLUMN quote INTEGER DEFAULT 0");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVersion < SHARED_CONTACTS) {
|
|
||||||
db.execSQL("ALTER TABLE mms ADD COLUMN shared_contacts TEXT");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVersion < FULL_TEXT_SEARCH) {
|
|
||||||
db.execSQL("CREATE VIRTUAL TABLE sms_fts USING fts5(body, content=sms, content_rowid=_id)");
|
|
||||||
db.execSQL("CREATE TRIGGER sms_ai AFTER INSERT ON sms BEGIN\n" +
|
|
||||||
" INSERT INTO sms_fts(rowid, body) VALUES (new._id, new.body);\n" +
|
|
||||||
"END;");
|
|
||||||
db.execSQL("CREATE TRIGGER sms_ad AFTER DELETE ON sms BEGIN\n" +
|
|
||||||
" INSERT INTO sms_fts(sms_fts, rowid, body) VALUES('delete', old._id, old.body);\n" +
|
|
||||||
"END;\n");
|
|
||||||
db.execSQL("CREATE TRIGGER sms_au AFTER UPDATE ON sms BEGIN\n" +
|
|
||||||
" INSERT INTO sms_fts(sms_fts, rowid, body) VALUES('delete', old._id, old.body);\n" +
|
|
||||||
" INSERT INTO sms_fts(rowid, body) VALUES(new._id, new.body);\n" +
|
|
||||||
"END;");
|
|
||||||
|
|
||||||
db.execSQL("CREATE VIRTUAL TABLE mms_fts USING fts5(body, content=mms, content_rowid=_id)");
|
|
||||||
db.execSQL("CREATE TRIGGER mms_ai AFTER INSERT ON mms BEGIN\n" +
|
|
||||||
" INSERT INTO mms_fts(rowid, body) VALUES (new._id, new.body);\n" +
|
|
||||||
"END;");
|
|
||||||
db.execSQL("CREATE TRIGGER mms_ad AFTER DELETE ON mms BEGIN\n" +
|
|
||||||
" INSERT INTO mms_fts(mms_fts, rowid, body) VALUES('delete', old._id, old.body);\n" +
|
|
||||||
"END;\n");
|
|
||||||
db.execSQL("CREATE TRIGGER mms_au AFTER UPDATE ON mms BEGIN\n" +
|
|
||||||
" INSERT INTO mms_fts(mms_fts, rowid, body) VALUES('delete', old._id, old.body);\n" +
|
|
||||||
" INSERT INTO mms_fts(rowid, body) VALUES(new._id, new.body);\n" +
|
|
||||||
"END;");
|
|
||||||
|
|
||||||
Log.i(TAG, "Beginning to build search index.");
|
|
||||||
long start = SystemClock.elapsedRealtime();
|
|
||||||
|
|
||||||
db.execSQL("INSERT INTO sms_fts (rowid, body) SELECT _id, body FROM sms");
|
|
||||||
|
|
||||||
long smsFinished = SystemClock.elapsedRealtime();
|
|
||||||
Log.i(TAG, "Indexing SMS completed in " + (smsFinished - start) + " ms");
|
|
||||||
|
|
||||||
db.execSQL("INSERT INTO mms_fts (rowid, body) SELECT _id, body FROM mms");
|
|
||||||
|
|
||||||
long mmsFinished = SystemClock.elapsedRealtime();
|
|
||||||
Log.i(TAG, "Indexing MMS completed in " + (mmsFinished - smsFinished) + " ms");
|
|
||||||
Log.i(TAG, "Indexing finished. Total time: " + (mmsFinished - start) + " ms");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVersion < BAD_IMPORT_CLEANUP) {
|
|
||||||
String trimmedCondition = " NOT IN (SELECT _id FROM mms)";
|
|
||||||
|
|
||||||
db.delete("group_receipts", "mms_id" + trimmedCondition, null);
|
|
||||||
|
|
||||||
String[] columns = new String[] { "_id", "unique_id", "_data", "thumbnail"};
|
|
||||||
|
|
||||||
try (Cursor cursor = db.query("part", columns, "mid" + trimmedCondition, null, null, null, null)) {
|
|
||||||
while (cursor != null && cursor.moveToNext()) {
|
|
||||||
db.delete("part", "_id = ? AND unique_id = ?", new String[] { String.valueOf(cursor.getLong(0)), String.valueOf(cursor.getLong(1)) });
|
|
||||||
|
|
||||||
String data = cursor.getString(2);
|
|
||||||
String thumbnail = cursor.getString(3);
|
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(data)) {
|
|
||||||
new File(data).delete();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(thumbnail)) {
|
|
||||||
new File(thumbnail).delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: This column only being checked due to upgrade issues as described in #8184
|
|
||||||
if (oldVersion < QUOTE_MISSING && !columnExists(db, "mms", "quote_missing")) {
|
|
||||||
db.execSQL("ALTER TABLE mms ADD COLUMN quote_missing INTEGER DEFAULT 0");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: The column only being checked due to upgrade issues as described in #8184
|
|
||||||
if (oldVersion < NOTIFICATION_CHANNELS && !columnExists(db, "recipient_preferences", "notification_channel")) {
|
|
||||||
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN notification_channel TEXT DEFAULT NULL");
|
|
||||||
NotificationChannels.create(context);
|
|
||||||
|
|
||||||
try (Cursor cursor = db.rawQuery("SELECT recipient_ids, system_display_name, signal_profile_name, notification, vibrate FROM recipient_preferences WHERE notification NOT NULL OR vibrate != 0", null)) {
|
|
||||||
while (cursor != null && cursor.moveToNext()) {
|
|
||||||
String addressString = cursor.getString(cursor.getColumnIndexOrThrow("recipient_ids"));
|
|
||||||
Address address = Address.fromExternal(context, addressString);
|
|
||||||
String systemName = cursor.getString(cursor.getColumnIndexOrThrow("system_display_name"));
|
|
||||||
String profileName = cursor.getString(cursor.getColumnIndexOrThrow("signal_profile_name"));
|
|
||||||
String messageSound = cursor.getString(cursor.getColumnIndexOrThrow("notification"));
|
|
||||||
Uri messageSoundUri = messageSound != null ? Uri.parse(messageSound) : null;
|
|
||||||
int vibrateState = cursor.getInt(cursor.getColumnIndexOrThrow("vibrate"));
|
|
||||||
String displayName = NotificationChannels.getChannelDisplayNameFor(context, systemName, profileName, address);
|
|
||||||
boolean vibrateEnabled = vibrateState == 0 ? TextSecurePreferences.isNotificationVibrateEnabled(context) : vibrateState == 1;
|
|
||||||
|
|
||||||
if (address.isGroup()) {
|
|
||||||
try(Cursor groupCursor = db.rawQuery("SELECT title FROM groups WHERE group_id = ?", new String[] { address.toGroupString() })) {
|
|
||||||
if (groupCursor != null && groupCursor.moveToFirst()) {
|
|
||||||
String title = groupCursor.getString(groupCursor.getColumnIndexOrThrow("title"));
|
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(title)) {
|
|
||||||
displayName = title;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String channelId = NotificationChannels.createChannelFor(context, address, displayName, messageSoundUri, vibrateEnabled);
|
|
||||||
|
|
||||||
ContentValues values = new ContentValues(1);
|
|
||||||
values.put("notification_channel", channelId);
|
|
||||||
db.update("recipient_preferences", values, "recipient_ids = ?", new String[] { addressString });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVersion < SECRET_SENDER) {
|
|
||||||
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN unidentified_access_mode INTEGER DEFAULT 0");
|
|
||||||
db.execSQL("ALTER TABLE push ADD COLUMN server_timestamp INTEGER DEFAULT 0");
|
|
||||||
db.execSQL("ALTER TABLE push ADD COLUMN server_guid TEXT DEFAULT NULL");
|
|
||||||
db.execSQL("ALTER TABLE group_receipts ADD COLUMN unidentified INTEGER DEFAULT 0");
|
|
||||||
db.execSQL("ALTER TABLE mms ADD COLUMN unidentified INTEGER DEFAULT 0");
|
|
||||||
db.execSQL("ALTER TABLE sms ADD COLUMN unidentified INTEGER DEFAULT 0");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVersion < ATTACHMENT_CAPTIONS) {
|
|
||||||
db.execSQL("ALTER TABLE part ADD COLUMN caption TEXT DEFAULT NULL");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4.30.8 included a migration, but not a correct CREATE_TABLE statement, so we need to add
|
|
||||||
// this column if it isn't present.
|
|
||||||
if (oldVersion < ATTACHMENT_CAPTIONS_FIX) {
|
|
||||||
if (!columnExists(db, "part", "caption")) {
|
|
||||||
db.execSQL("ALTER TABLE part ADD COLUMN caption TEXT DEFAULT NULL");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVersion < PREVIEWS) {
|
|
||||||
db.execSQL("ALTER TABLE mms ADD COLUMN previews TEXT");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVersion < CONVERSATION_SEARCH) {
|
|
||||||
db.execSQL("DROP TABLE sms_fts");
|
|
||||||
db.execSQL("DROP TABLE mms_fts");
|
|
||||||
db.execSQL("DROP TRIGGER sms_ai");
|
|
||||||
db.execSQL("DROP TRIGGER sms_au");
|
|
||||||
db.execSQL("DROP TRIGGER sms_ad");
|
|
||||||
db.execSQL("DROP TRIGGER mms_ai");
|
|
||||||
db.execSQL("DROP TRIGGER mms_au");
|
|
||||||
db.execSQL("DROP TRIGGER mms_ad");
|
|
||||||
|
|
||||||
db.execSQL("CREATE VIRTUAL TABLE sms_fts USING fts5(body, thread_id UNINDEXED, content=sms, content_rowid=_id)");
|
|
||||||
db.execSQL("CREATE TRIGGER sms_ai AFTER INSERT ON sms BEGIN\n" +
|
|
||||||
" INSERT INTO sms_fts(rowid, body, thread_id) VALUES (new._id, new.body, new.thread_id);\n" +
|
|
||||||
"END;");
|
|
||||||
db.execSQL("CREATE TRIGGER sms_ad AFTER DELETE ON sms BEGIN\n" +
|
|
||||||
" INSERT INTO sms_fts(sms_fts, rowid, body, thread_id) VALUES('delete', old._id, old.body, old.thread_id);\n" +
|
|
||||||
"END;\n");
|
|
||||||
db.execSQL("CREATE TRIGGER sms_au AFTER UPDATE ON sms BEGIN\n" +
|
|
||||||
" INSERT INTO sms_fts(sms_fts, rowid, body, thread_id) VALUES('delete', old._id, old.body, old.thread_id);\n" +
|
|
||||||
" INSERT INTO sms_fts(rowid, body, thread_id) VALUES(new._id, new.body, new.thread_id);\n" +
|
|
||||||
"END;");
|
|
||||||
|
|
||||||
db.execSQL("CREATE VIRTUAL TABLE mms_fts USING fts5(body, thread_id UNINDEXED, content=mms, content_rowid=_id)");
|
|
||||||
db.execSQL("CREATE TRIGGER mms_ai AFTER INSERT ON mms BEGIN\n" +
|
|
||||||
" INSERT INTO mms_fts(rowid, body, thread_id) VALUES (new._id, new.body, new.thread_id);\n" +
|
|
||||||
"END;");
|
|
||||||
db.execSQL("CREATE TRIGGER mms_ad AFTER DELETE ON mms BEGIN\n" +
|
|
||||||
" INSERT INTO mms_fts(mms_fts, rowid, body, thread_id) VALUES('delete', old._id, old.body, old.thread_id);\n" +
|
|
||||||
"END;\n");
|
|
||||||
db.execSQL("CREATE TRIGGER mms_au AFTER UPDATE ON mms BEGIN\n" +
|
|
||||||
" INSERT INTO mms_fts(mms_fts, rowid, body, thread_id) VALUES('delete', old._id, old.body, old.thread_id);\n" +
|
|
||||||
" INSERT INTO mms_fts(rowid, body, thread_id) VALUES(new._id, new.body, new.thread_id);\n" +
|
|
||||||
"END;");
|
|
||||||
|
|
||||||
Log.i(TAG, "Beginning to build search index.");
|
|
||||||
long start = SystemClock.elapsedRealtime();
|
|
||||||
|
|
||||||
db.execSQL("INSERT INTO sms_fts (rowid, body, thread_id) SELECT _id, body, thread_id FROM sms");
|
|
||||||
|
|
||||||
long smsFinished = SystemClock.elapsedRealtime();
|
|
||||||
Log.i(TAG, "Indexing SMS completed in " + (smsFinished - start) + " ms");
|
|
||||||
|
|
||||||
db.execSQL("INSERT INTO mms_fts (rowid, body, thread_id) SELECT _id, body, thread_id FROM mms");
|
|
||||||
|
|
||||||
long mmsFinished = SystemClock.elapsedRealtime();
|
|
||||||
Log.i(TAG, "Indexing MMS completed in " + (mmsFinished - smsFinished) + " ms");
|
|
||||||
Log.i(TAG, "Indexing finished. Total time: " + (mmsFinished - start) + " ms");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVersion < SELF_ATTACHMENT_CLEANUP) {
|
|
||||||
String localNumber = TextSecurePreferences.getLocalNumber(context);
|
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(localNumber)) {
|
|
||||||
try (Cursor threadCursor = db.rawQuery("SELECT _id FROM thread WHERE recipient_ids = ?", new String[]{ localNumber })) {
|
|
||||||
if (threadCursor != null && threadCursor.moveToFirst()) {
|
|
||||||
long threadId = threadCursor.getLong(0);
|
|
||||||
ContentValues updateValues = new ContentValues(1);
|
|
||||||
|
|
||||||
updateValues.put("pending_push", 0);
|
|
||||||
|
|
||||||
int count = db.update("part", updateValues, "mid IN (SELECT _id FROM mms WHERE thread_id = ?)", new String[]{ String.valueOf(threadId) });
|
|
||||||
Log.i(TAG, "Updated " + count + " self-sent attachments.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVersion < RECIPIENT_FORCE_SMS_SELECTION) {
|
|
||||||
db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN force_sms_selection INTEGER DEFAULT 0");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVersion < JOBMANAGER_STRIKES_BACK) {
|
|
||||||
db.execSQL("CREATE TABLE job_spec(_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
|
||||||
"job_spec_id TEXT UNIQUE, " +
|
|
||||||
"factory_key TEXT, " +
|
|
||||||
"queue_key TEXT, " +
|
|
||||||
"create_time INTEGER, " +
|
|
||||||
"next_run_attempt_time INTEGER, " +
|
|
||||||
"run_attempt INTEGER, " +
|
|
||||||
"max_attempts INTEGER, " +
|
|
||||||
"max_backoff INTEGER, " +
|
|
||||||
"max_instances INTEGER, " +
|
|
||||||
"lifespan INTEGER, " +
|
|
||||||
"serialized_data TEXT, " +
|
|
||||||
"is_running INTEGER)");
|
|
||||||
|
|
||||||
db.execSQL("CREATE TABLE constraint_spec(_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
|
||||||
"job_spec_id TEXT, " +
|
|
||||||
"factory_key TEXT, " +
|
|
||||||
"UNIQUE(job_spec_id, factory_key))");
|
|
||||||
|
|
||||||
db.execSQL("CREATE TABLE dependency_spec(_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
|
||||||
"job_spec_id TEXT, " +
|
|
||||||
"depends_on_job_spec_id TEXT, " +
|
|
||||||
"UNIQUE(job_spec_id, depends_on_job_spec_id))");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVersion < STICKERS) {
|
|
||||||
db.execSQL("CREATE TABLE sticker (_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
|
||||||
"pack_id TEXT NOT NULL, " +
|
|
||||||
"pack_key TEXT NOT NULL, " +
|
|
||||||
"pack_title TEXT NOT NULL, " +
|
|
||||||
"pack_author TEXT NOT NULL, " +
|
|
||||||
"sticker_id INTEGER, " +
|
|
||||||
"cover INTEGER, " +
|
|
||||||
"emoji TEXT NOT NULL, " +
|
|
||||||
"last_used INTEGER, " +
|
|
||||||
"installed INTEGER," +
|
|
||||||
"file_path TEXT NOT NULL, " +
|
|
||||||
"file_length INTEGER, " +
|
|
||||||
"file_random BLOB, " +
|
|
||||||
"UNIQUE(pack_id, sticker_id, cover) ON CONFLICT IGNORE)");
|
|
||||||
|
|
||||||
db.execSQL("CREATE INDEX IF NOT EXISTS sticker_pack_id_index ON sticker (pack_id);");
|
|
||||||
db.execSQL("CREATE INDEX IF NOT EXISTS sticker_sticker_id_index ON sticker (sticker_id);");
|
|
||||||
|
|
||||||
db.execSQL("ALTER TABLE part ADD COLUMN sticker_pack_id TEXT");
|
|
||||||
db.execSQL("ALTER TABLE part ADD COLUMN sticker_pack_key TEXT");
|
|
||||||
db.execSQL("ALTER TABLE part ADD COLUMN sticker_id INTEGER DEFAULT -1");
|
|
||||||
db.execSQL("CREATE INDEX IF NOT EXISTS part_sticker_pack_id_index ON part (sticker_pack_id)");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVersion < lokiV1) {
|
|
||||||
db.execSQL(LokiAPIDatabase.getCreateOpenGroupAuthTokenTableCommand());
|
|
||||||
db.execSQL(LokiAPIDatabase.getCreateLastMessageServerIDTableCommand());
|
|
||||||
db.execSQL(LokiAPIDatabase.getCreateLastDeletionServerIDTableCommand());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVersion < lokiV2) {
|
|
||||||
db.execSQL(LokiUserDatabase.getCreateServerDisplayNameTableCommand());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVersion < lokiV3) {
|
|
||||||
db.execSQL(LokiAPIDatabase.getCreateDeviceLinkCacheCommand());
|
|
||||||
db.execSQL(LokiThreadDatabase.getCreatePublicChatTableCommand());
|
|
||||||
|
|
||||||
db.execSQL("ALTER TABLE groups ADD COLUMN avatar_url TEXT");
|
|
||||||
db.execSQL("ALTER TABLE part ADD COLUMN url TEXT");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVersion < lokiV4) {
|
|
||||||
db.execSQL(LokiMessageDatabase.getCreateMessageToThreadMappingTableCommand());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVersion < lokiV5) {
|
|
||||||
db.execSQL(LokiAPIDatabase.getCreateUserCountTableCommand());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVersion < lokiV6) {
|
|
||||||
// Migrate public chats from __textsecure_group__ to __loki_public_chat_group__
|
|
||||||
try (Cursor lokiPublicChatCursor = db.rawQuery("SELECT public_chat FROM loki_public_chat_database", null)) {
|
|
||||||
while (lokiPublicChatCursor != null && lokiPublicChatCursor.moveToNext()) {
|
|
||||||
String chatString = lokiPublicChatCursor.getString(0);
|
|
||||||
PublicChat publicChat = PublicChat.fromJSON(chatString);
|
|
||||||
if (publicChat != null) {
|
|
||||||
byte[] groupId = publicChat.getId().getBytes();
|
|
||||||
String oldId = GroupUtil.getEncodedId(groupId, false);
|
|
||||||
String newId = GroupUtil.getEncodedOpenGroupId(groupId);
|
|
||||||
ContentValues threadUpdate = new ContentValues();
|
|
||||||
threadUpdate.put("recipient_ids", newId);
|
|
||||||
db.update("thread", threadUpdate, "recipient_ids = ?", new String[]{ oldId });
|
|
||||||
ContentValues groupUpdate = new ContentValues();
|
|
||||||
groupUpdate.put("group_id", newId);
|
|
||||||
db.update("groups", groupUpdate,"group_id = ?", new String[] { oldId });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Migrate RSS feeds from __textsecure_group__ to __loki_rss_feed_group__
|
|
||||||
String[] rssFeedIds = new String[] { "loki.network.feed", "loki.network.messenger-updates.feed" };
|
|
||||||
for (String groupId : rssFeedIds) {
|
|
||||||
String oldId = GroupUtil.getEncodedId(groupId.getBytes(), false);
|
|
||||||
String newId = GroupUtil.getEncodedRSSFeedId(groupId.getBytes());
|
|
||||||
ContentValues threadUpdate = new ContentValues();
|
|
||||||
threadUpdate.put("recipient_ids", newId);
|
|
||||||
db.update("thread", threadUpdate, "recipient_ids = ?", new String[]{ oldId });
|
|
||||||
ContentValues groupUpdate = new ContentValues();
|
|
||||||
groupUpdate.put("group_id", newId);
|
|
||||||
db.update("groups", groupUpdate,"group_id = ?", new String[] { oldId });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add admin field in groups
|
|
||||||
db.execSQL("ALTER TABLE groups ADD COLUMN admins TEXT");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVersion < lokiV7) {
|
if (oldVersion < lokiV7) {
|
||||||
db.execSQL(LokiMessageDatabase.getCreateErrorMessageTableCommand());
|
db.execSQL(LokiMessageDatabase.getCreateErrorMessageTableCommand());
|
||||||
}
|
}
|
||||||
@ -652,10 +228,6 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
|||||||
} finally {
|
} finally {
|
||||||
db.endTransaction();
|
db.endTransaction();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldVersion < MIGRATE_PREKEYS_VERSION) {
|
|
||||||
// PreKeyMigrationHelper.cleanUpPreKeys(context);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public SQLiteDatabase getReadableDatabase() {
|
public SQLiteDatabase getReadableDatabase() {
|
||||||
|
@ -1,109 +0,0 @@
|
|||||||
package org.thoughtcrime.securesms.database.helpers;
|
|
||||||
|
|
||||||
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.content.Context;
|
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
|
||||||
|
|
||||||
import net.sqlcipher.database.SQLiteDatabase;
|
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.database.Address;
|
|
||||||
import org.thoughtcrime.securesms.database.SessionDatabase;
|
|
||||||
import org.thoughtcrime.securesms.util.Conversions;
|
|
||||||
import org.session.libsignal.libsignal.state.SessionRecord;
|
|
||||||
import org.session.libsignal.libsignal.state.SessionState;
|
|
||||||
import org.session.libsignal.libsignal.state.StorageProtos.SessionStructure;
|
|
||||||
import org.session.libsignal.service.api.push.SignalServiceAddress;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
class SessionStoreMigrationHelper {
|
|
||||||
|
|
||||||
private static final String TAG = SessionStoreMigrationHelper.class.getSimpleName();
|
|
||||||
|
|
||||||
private static final String SESSIONS_DIRECTORY_V2 = "sessions-v2";
|
|
||||||
private static final Object FILE_LOCK = new Object();
|
|
||||||
|
|
||||||
private static final int SINGLE_STATE_VERSION = 1;
|
|
||||||
private static final int ARCHIVE_STATES_VERSION = 2;
|
|
||||||
private static final int PLAINTEXT_VERSION = 3;
|
|
||||||
private static final int CURRENT_VERSION = 3;
|
|
||||||
|
|
||||||
static void migrateSessions(Context context, SQLiteDatabase database) {
|
|
||||||
File directory = new File(context.getFilesDir(), SESSIONS_DIRECTORY_V2);
|
|
||||||
|
|
||||||
if (directory.exists()) {
|
|
||||||
File[] sessionFiles = directory.listFiles();
|
|
||||||
|
|
||||||
if (sessionFiles != null) {
|
|
||||||
for (File sessionFile : sessionFiles) {
|
|
||||||
try {
|
|
||||||
String[] parts = sessionFile.getName().split("[.]");
|
|
||||||
Address address = Address.fromSerialized(parts[0]);
|
|
||||||
|
|
||||||
int deviceId;
|
|
||||||
|
|
||||||
if (parts.length > 1) deviceId = Integer.parseInt(parts[1]);
|
|
||||||
else deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID;
|
|
||||||
|
|
||||||
FileInputStream in = new FileInputStream(sessionFile);
|
|
||||||
int versionMarker = readInteger(in);
|
|
||||||
|
|
||||||
if (versionMarker > CURRENT_VERSION) {
|
|
||||||
throw new AssertionError("Unknown version: " + versionMarker + ", " + sessionFile.getAbsolutePath());
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] serialized = readBlob(in);
|
|
||||||
in.close();
|
|
||||||
|
|
||||||
if (versionMarker < PLAINTEXT_VERSION) {
|
|
||||||
throw new AssertionError("Not plaintext: " + versionMarker + ", " + sessionFile.getAbsolutePath());
|
|
||||||
}
|
|
||||||
|
|
||||||
SessionRecord sessionRecord;
|
|
||||||
|
|
||||||
if (versionMarker == SINGLE_STATE_VERSION) {
|
|
||||||
Log.i(TAG, "Migrating single state version: " + sessionFile.getAbsolutePath());
|
|
||||||
SessionStructure sessionStructure = SessionStructure.parseFrom(serialized);
|
|
||||||
SessionState sessionState = new SessionState(sessionStructure);
|
|
||||||
|
|
||||||
sessionRecord = new SessionRecord(sessionState);
|
|
||||||
} else if (versionMarker >= ARCHIVE_STATES_VERSION) {
|
|
||||||
Log.i(TAG, "Migrating session: " + sessionFile.getAbsolutePath());
|
|
||||||
sessionRecord = new SessionRecord(serialized);
|
|
||||||
} else {
|
|
||||||
throw new AssertionError("Unknown version: " + versionMarker + ", " + sessionFile.getAbsolutePath());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
ContentValues contentValues = new ContentValues();
|
|
||||||
contentValues.put(SessionDatabase.ADDRESS, address.serialize());
|
|
||||||
contentValues.put(SessionDatabase.DEVICE, deviceId);
|
|
||||||
contentValues.put(SessionDatabase.RECORD, sessionRecord.serialize());
|
|
||||||
|
|
||||||
database.insert(SessionDatabase.TABLE_NAME, null, contentValues);
|
|
||||||
} catch (NumberFormatException | IOException e) {
|
|
||||||
Log.w(TAG, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] readBlob(FileInputStream in) throws IOException {
|
|
||||||
int length = readInteger(in);
|
|
||||||
byte[] blobBytes = new byte[length];
|
|
||||||
|
|
||||||
in.read(blobBytes, 0, blobBytes.length);
|
|
||||||
return blobBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int readInteger(FileInputStream in) throws IOException {
|
|
||||||
byte[] integer = new byte[4];
|
|
||||||
in.read(integer, 0, integer.length);
|
|
||||||
return Conversions.byteArrayToInt(integer);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -21,7 +21,6 @@ import com.google.android.material.tabs.TabLayout;
|
|||||||
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
|
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity;
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
|
||||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||||
|
|
||||||
@ -42,19 +41,12 @@ public class GiphyActivity extends PassphraseRequiredActionBarActivity
|
|||||||
public static final String EXTRA_WIDTH = "extra_width";
|
public static final String EXTRA_WIDTH = "extra_width";
|
||||||
public static final String EXTRA_HEIGHT = "extra_height";
|
public static final String EXTRA_HEIGHT = "extra_height";
|
||||||
|
|
||||||
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
|
||||||
|
|
||||||
private GiphyGifFragment gifFragment;
|
private GiphyGifFragment gifFragment;
|
||||||
private GiphyStickerFragment stickerFragment;
|
private GiphyStickerFragment stickerFragment;
|
||||||
private boolean forMms;
|
private boolean forMms;
|
||||||
|
|
||||||
private GiphyAdapter.GiphyViewHolder finishingImage;
|
private GiphyAdapter.GiphyViewHolder finishingImage;
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPreCreate() {
|
|
||||||
dynamicLanguage.onCreate(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle bundle, boolean ready) {
|
public void onCreate(Bundle bundle, boolean ready) {
|
||||||
setContentView(R.layout.giphy_activity);
|
setContentView(R.layout.giphy_activity);
|
||||||
|
@ -19,6 +19,7 @@ import java.io.IOException;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
//TODO AC: Delete
|
||||||
public class RefreshAttributesJob extends BaseJob implements InjectableType {
|
public class RefreshAttributesJob extends BaseJob implements InjectableType {
|
||||||
|
|
||||||
public static final String KEY = "RefreshAttributesJob";
|
public static final String KEY = "RefreshAttributesJob";
|
||||||
|
@ -5,17 +5,13 @@ import android.app.KeyguardManager;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.preference.CheckBoxPreference;
|
|
||||||
import androidx.preference.Preference;
|
import androidx.preference.Preference;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
import org.thoughtcrime.securesms.ApplicationContext;
|
||||||
import org.thoughtcrime.securesms.PassphraseChangeActivity;
|
|
||||||
import org.thoughtcrime.securesms.components.SwitchPreferenceCompat;
|
import org.thoughtcrime.securesms.components.SwitchPreferenceCompat;
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
|
||||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
@ -27,8 +23,6 @@ import network.loki.messenger.R;
|
|||||||
|
|
||||||
public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment implements InjectableType {
|
public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment implements InjectableType {
|
||||||
|
|
||||||
private CheckBoxPreference disablePassphrase;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAttach(Activity activity) {
|
public void onAttach(Activity activity) {
|
||||||
super.onAttach(activity);
|
super.onAttach(activity);
|
||||||
@ -39,17 +33,12 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
|
|||||||
public void onCreate(Bundle paramBundle) {
|
public void onCreate(Bundle paramBundle) {
|
||||||
super.onCreate(paramBundle);
|
super.onCreate(paramBundle);
|
||||||
|
|
||||||
disablePassphrase = (CheckBoxPreference) this.findPreference("pref_enable_passphrase_temporary");
|
|
||||||
|
|
||||||
this.findPreference(TextSecurePreferences.SCREEN_LOCK).setOnPreferenceChangeListener(new ScreenLockListener());
|
this.findPreference(TextSecurePreferences.SCREEN_LOCK).setOnPreferenceChangeListener(new ScreenLockListener());
|
||||||
this.findPreference(TextSecurePreferences.SCREEN_LOCK_TIMEOUT).setOnPreferenceClickListener(new ScreenLockTimeoutListener());
|
this.findPreference(TextSecurePreferences.SCREEN_LOCK_TIMEOUT).setOnPreferenceClickListener(new ScreenLockTimeoutListener());
|
||||||
|
|
||||||
this.findPreference(TextSecurePreferences.CHANGE_PASSPHRASE_PREF).setOnPreferenceClickListener(new ChangePassphraseClickListener());
|
|
||||||
this.findPreference(TextSecurePreferences.PASSPHRASE_TIMEOUT_INTERVAL_PREF).setOnPreferenceClickListener(new PassphraseIntervalClickListener());
|
|
||||||
this.findPreference(TextSecurePreferences.READ_RECEIPTS_PREF).setOnPreferenceChangeListener(new ReadReceiptToggleListener());
|
this.findPreference(TextSecurePreferences.READ_RECEIPTS_PREF).setOnPreferenceChangeListener(new ReadReceiptToggleListener());
|
||||||
this.findPreference(TextSecurePreferences.TYPING_INDICATORS).setOnPreferenceChangeListener(new TypingIndicatorsToggleListener());
|
this.findPreference(TextSecurePreferences.TYPING_INDICATORS).setOnPreferenceChangeListener(new TypingIndicatorsToggleListener());
|
||||||
this.findPreference(TextSecurePreferences.LINK_PREVIEWS).setOnPreferenceChangeListener(new LinkPreviewToggleListener());
|
this.findPreference(TextSecurePreferences.LINK_PREVIEWS).setOnPreferenceChangeListener(new LinkPreviewToggleListener());
|
||||||
disablePassphrase.setOnPreferenceChangeListener(new DisablePassphraseClickListener());
|
|
||||||
|
|
||||||
initializeVisibility();
|
initializeVisibility();
|
||||||
}
|
}
|
||||||
@ -62,16 +51,9 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
|
|||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
if (!TextSecurePreferences.isPasswordDisabled(getContext())) initializePassphraseTimeoutSummary();
|
if (TextSecurePreferences.isPasswordDisabled(getContext())) {
|
||||||
else initializeScreenLockTimeoutSummary();
|
initializeScreenLockTimeoutSummary();
|
||||||
|
}
|
||||||
disablePassphrase.setChecked(!TextSecurePreferences.isPasswordDisabled(getActivity()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializePassphraseTimeoutSummary() {
|
|
||||||
int timeoutMinutes = TextSecurePreferences.getPassphraseTimeoutInterval(getActivity());
|
|
||||||
this.findPreference(TextSecurePreferences.PASSPHRASE_TIMEOUT_INTERVAL_PREF)
|
|
||||||
.setSummary(getResources().getQuantityString(R.plurals.AppProtectionPreferenceFragment_minutes, timeoutMinutes, timeoutMinutes));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeScreenLockTimeoutSummary() {
|
private void initializeScreenLockTimeoutSummary() {
|
||||||
@ -87,11 +69,6 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
|
|||||||
|
|
||||||
private void initializeVisibility() {
|
private void initializeVisibility() {
|
||||||
if (TextSecurePreferences.isPasswordDisabled(getContext())) {
|
if (TextSecurePreferences.isPasswordDisabled(getContext())) {
|
||||||
findPreference("pref_enable_passphrase_temporary").setVisible(false);
|
|
||||||
findPreference(TextSecurePreferences.CHANGE_PASSPHRASE_PREF).setVisible(false);
|
|
||||||
findPreference(TextSecurePreferences.PASSPHRASE_TIMEOUT_INTERVAL_PREF).setVisible(false);
|
|
||||||
findPreference(TextSecurePreferences.PASSPHRASE_TIMEOUT_PREF).setVisible(false);
|
|
||||||
|
|
||||||
KeyguardManager keyguardManager = (KeyguardManager)getContext().getSystemService(Context.KEYGUARD_SERVICE);
|
KeyguardManager keyguardManager = (KeyguardManager)getContext().getSystemService(Context.KEYGUARD_SERVICE);
|
||||||
if (!keyguardManager.isKeyguardSecure()) {
|
if (!keyguardManager.isKeyguardSecure()) {
|
||||||
((SwitchPreferenceCompat)findPreference(TextSecurePreferences.SCREEN_LOCK)).setChecked(false);
|
((SwitchPreferenceCompat)findPreference(TextSecurePreferences.SCREEN_LOCK)).setChecked(false);
|
||||||
@ -178,90 +155,4 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CharSequence getSummary(Context context) {
|
|
||||||
final int privacySummaryResId = R.string.ApplicationPreferencesActivity_privacy_summary;
|
|
||||||
final String onRes = context.getString(R.string.ApplicationPreferencesActivity_on);
|
|
||||||
final String offRes = context.getString(R.string.ApplicationPreferencesActivity_off);
|
|
||||||
|
|
||||||
if (TextSecurePreferences.isPasswordDisabled(context) && !TextSecurePreferences.isScreenLockEnabled(context)) {
|
|
||||||
// if (TextSecurePreferences.isRegistrationtLockEnabled(context)) {
|
|
||||||
// return context.getString(privacySummaryResId, offRes, onRes);
|
|
||||||
// } else {
|
|
||||||
return context.getString(privacySummaryResId, offRes, offRes);
|
|
||||||
// }
|
|
||||||
} else {
|
|
||||||
// if (TextSecurePreferences.isRegistrationtLockEnabled(context)) {
|
|
||||||
// return context.getString(privacySummaryResId, onRes, onRes);
|
|
||||||
// } else {
|
|
||||||
return context.getString(privacySummaryResId, onRes, offRes);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ChangePassphraseClickListener implements Preference.OnPreferenceClickListener {
|
|
||||||
@Override
|
|
||||||
public boolean onPreferenceClick(Preference preference) {
|
|
||||||
if (MasterSecretUtil.isPassphraseInitialized(getActivity())) {
|
|
||||||
startActivity(new Intent(getActivity(), PassphraseChangeActivity.class));
|
|
||||||
} else {
|
|
||||||
Toast.makeText(getActivity(),
|
|
||||||
R.string.ApplicationPreferenceActivity_you_havent_set_a_passphrase_yet,
|
|
||||||
Toast.LENGTH_LONG).show();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class PassphraseIntervalClickListener implements Preference.OnPreferenceClickListener {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onPreferenceClick(Preference preference) {
|
|
||||||
new TimeDurationPickerDialog(getContext(), (view, duration) -> {
|
|
||||||
int timeoutMinutes = Math.max((int)TimeUnit.MILLISECONDS.toMinutes(duration), 1);
|
|
||||||
|
|
||||||
TextSecurePreferences.setPassphraseTimeoutInterval(getActivity(), timeoutMinutes);
|
|
||||||
|
|
||||||
initializePassphraseTimeoutSummary();
|
|
||||||
|
|
||||||
}, 0).show();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class DisablePassphraseClickListener implements Preference.OnPreferenceChangeListener {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onPreferenceChange(final Preference preference, Object newValue) {
|
|
||||||
if (((CheckBoxPreference)preference).isChecked()) {
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
|
||||||
builder.setTitle(R.string.ApplicationPreferencesActivity_disable_passphrase);
|
|
||||||
builder.setMessage(R.string.ApplicationPreferencesActivity_this_will_permanently_unlock_signal_and_message_notifications);
|
|
||||||
builder.setIconAttribute(R.attr.dialog_alert_icon);
|
|
||||||
builder.setPositiveButton(R.string.ApplicationPreferencesActivity_disable, (dialog, which) -> {
|
|
||||||
MasterSecretUtil.changeMasterSecretPassphrase(getActivity(),
|
|
||||||
KeyCachingService.getMasterSecret(getContext()),
|
|
||||||
MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
|
|
||||||
|
|
||||||
TextSecurePreferences.setPasswordDisabled(getActivity(), true);
|
|
||||||
((CheckBoxPreference)preference).setChecked(false);
|
|
||||||
|
|
||||||
Intent intent = new Intent(getActivity(), KeyCachingService.class);
|
|
||||||
intent.setAction(KeyCachingService.DISABLE_ACTION);
|
|
||||||
getActivity().startService(intent);
|
|
||||||
|
|
||||||
initializeVisibility();
|
|
||||||
});
|
|
||||||
builder.setNegativeButton(android.R.string.cancel, null);
|
|
||||||
builder.show();
|
|
||||||
} else {
|
|
||||||
Intent intent = new Intent(getActivity(), PassphraseChangeActivity.class);
|
|
||||||
startActivity(intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -34,13 +34,9 @@ import androidx.core.app.NotificationCompat;
|
|||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
import org.thoughtcrime.securesms.ApplicationContext;
|
||||||
import org.thoughtcrime.securesms.DatabaseUpgradeActivity;
|
import org.thoughtcrime.securesms.DatabaseUpgradeActivity;
|
||||||
import org.thoughtcrime.securesms.DummyActivity;
|
import org.thoughtcrime.securesms.DummyActivity;
|
||||||
import org.thoughtcrime.securesms.crypto.InvalidPassphraseException;
|
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
|
||||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
|
||||||
import org.thoughtcrime.securesms.logging.Log;
|
import org.thoughtcrime.securesms.logging.Log;
|
||||||
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
|
import org.thoughtcrime.securesms.loki.activities.HomeActivity;
|
||||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
|
||||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
|
||||||
@ -67,13 +63,12 @@ public class KeyCachingService extends Service {
|
|||||||
private static final String PASSPHRASE_EXPIRED_EVENT = "org.thoughtcrime.securesms.service.action.PASSPHRASE_EXPIRED_EVENT";
|
private static final String PASSPHRASE_EXPIRED_EVENT = "org.thoughtcrime.securesms.service.action.PASSPHRASE_EXPIRED_EVENT";
|
||||||
public static final String CLEAR_KEY_ACTION = "org.thoughtcrime.securesms.service.action.CLEAR_KEY";
|
public static final String CLEAR_KEY_ACTION = "org.thoughtcrime.securesms.service.action.CLEAR_KEY";
|
||||||
public static final String DISABLE_ACTION = "org.thoughtcrime.securesms.service.action.DISABLE";
|
public static final String DISABLE_ACTION = "org.thoughtcrime.securesms.service.action.DISABLE";
|
||||||
public static final String LOCALE_CHANGE_EVENT = "org.thoughtcrime.securesms.service.action.LOCALE_CHANGE_EVENT";
|
|
||||||
|
|
||||||
private DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
|
||||||
|
|
||||||
private final IBinder binder = new KeySetBinder();
|
private final IBinder binder = new KeySetBinder();
|
||||||
|
|
||||||
private static MasterSecret masterSecret;
|
// AC: This is a temporal drop off replacement for the refactoring time being.
|
||||||
|
// This field only indicates if the app was unlocked or not (null means locked).
|
||||||
|
private static Object masterSecret;
|
||||||
|
|
||||||
public KeyCachingService() {}
|
public KeyCachingService() {}
|
||||||
|
|
||||||
@ -81,18 +76,6 @@ public class KeyCachingService extends Service {
|
|||||||
return getMasterSecret(context) == null;
|
return getMasterSecret(context) == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized @Nullable MasterSecret getMasterSecret(Context context) {
|
|
||||||
if (masterSecret == null && (TextSecurePreferences.isPasswordDisabled(context) && !TextSecurePreferences.isScreenLockEnabled(context))) {
|
|
||||||
try {
|
|
||||||
return MasterSecretUtil.getMasterSecret(context, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
|
|
||||||
} catch (InvalidPassphraseException e) {
|
|
||||||
Log.w("KeyCachingService", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return masterSecret;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void onAppForegrounded(@NonNull Context context) {
|
public static void onAppForegrounded(@NonNull Context context) {
|
||||||
ServiceUtil.getAlarmManager(context).cancel(buildExpirationPendingIntent(context));
|
ServiceUtil.getAlarmManager(context).cancel(buildExpirationPendingIntent(context));
|
||||||
}
|
}
|
||||||
@ -101,8 +84,22 @@ public class KeyCachingService extends Service {
|
|||||||
startTimeoutIfAppropriate(context);
|
startTimeoutIfAppropriate(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO AC: Delete
|
||||||
|
public static synchronized @Nullable Object getMasterSecret(Context context) {
|
||||||
|
// if (masterSecret == null && (TextSecurePreferences.isPasswordDisabled(context) && !TextSecurePreferences.isScreenLockEnabled(context))) {
|
||||||
|
// try {
|
||||||
|
// return MasterSecretUtil.getMasterSecret(context, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
|
||||||
|
// } catch (InvalidPassphraseException e) {
|
||||||
|
// Log.w("KeyCachingService", e);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
return masterSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO AC: Delete
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
public void setMasterSecret(final MasterSecret masterSecret) {
|
public void setMasterSecret(final Object masterSecret) {
|
||||||
synchronized (KeyCachingService.class) {
|
synchronized (KeyCachingService.class) {
|
||||||
KeyCachingService.masterSecret = masterSecret;
|
KeyCachingService.masterSecret = masterSecret;
|
||||||
|
|
||||||
@ -132,7 +129,6 @@ public class KeyCachingService extends Service {
|
|||||||
case CLEAR_KEY_ACTION: handleClearKey(); break;
|
case CLEAR_KEY_ACTION: handleClearKey(); break;
|
||||||
case PASSPHRASE_EXPIRED_EVENT: handleClearKey(); break;
|
case PASSPHRASE_EXPIRED_EVENT: handleClearKey(); break;
|
||||||
case DISABLE_ACTION: handleDisableService(); break;
|
case DISABLE_ACTION: handleDisableService(); break;
|
||||||
case LOCALE_CHANGE_EVENT: handleLocaleChanged(); break;
|
|
||||||
case LOCK_TOGGLED_EVENT: handleLockToggled(); break;
|
case LOCK_TOGGLED_EVENT: handleLockToggled(); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -146,12 +142,12 @@ public class KeyCachingService extends Service {
|
|||||||
super.onCreate();
|
super.onCreate();
|
||||||
|
|
||||||
if (TextSecurePreferences.isPasswordDisabled(this) && !TextSecurePreferences.isScreenLockEnabled(this)) {
|
if (TextSecurePreferences.isPasswordDisabled(this) && !TextSecurePreferences.isScreenLockEnabled(this)) {
|
||||||
try {
|
// try {
|
||||||
MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(this, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
|
// MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(this, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
|
||||||
setMasterSecret(masterSecret);
|
setMasterSecret(new Object());
|
||||||
} catch (InvalidPassphraseException e) {
|
// } catch (InvalidPassphraseException e) {
|
||||||
Log.w("KeyCachingService", e);
|
// Log.w("KeyCachingService", e);
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,12 +192,12 @@ public class KeyCachingService extends Service {
|
|||||||
private void handleLockToggled() {
|
private void handleLockToggled() {
|
||||||
stopForeground(true);
|
stopForeground(true);
|
||||||
|
|
||||||
try {
|
// try {
|
||||||
MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(this, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
|
// MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(this, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
|
||||||
setMasterSecret(masterSecret);
|
setMasterSecret(masterSecret);
|
||||||
} catch (InvalidPassphraseException e) {
|
// } catch (InvalidPassphraseException e) {
|
||||||
Log.w(TAG, e);
|
// Log.w(TAG, e);
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleDisableService() {
|
private void handleDisableService() {
|
||||||
@ -212,11 +208,6 @@ public class KeyCachingService extends Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleLocaleChanged() {
|
|
||||||
dynamicLanguage.updateServiceLocale(this);
|
|
||||||
foregroundService();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void startTimeoutIfAppropriate(@NonNull Context context) {
|
private static void startTimeoutIfAppropriate(@NonNull Context context) {
|
||||||
boolean appVisible = ApplicationContext.getInstance(context).isAppVisible();
|
boolean appVisible = ApplicationContext.getInstance(context).isAppVisible();
|
||||||
boolean secretSet = KeyCachingService.masterSecret != null;
|
boolean secretSet = KeyCachingService.masterSecret != null;
|
||||||
|
@ -136,7 +136,7 @@ public class TextSecurePreferences {
|
|||||||
private static final String DATABASE_UNENCRYPTED_SECRET = "pref_database_unencrypted_secret";
|
private static final String DATABASE_UNENCRYPTED_SECRET = "pref_database_unencrypted_secret";
|
||||||
private static final String ATTACHMENT_ENCRYPTED_SECRET = "pref_attachment_encrypted_secret";
|
private static final String ATTACHMENT_ENCRYPTED_SECRET = "pref_attachment_encrypted_secret";
|
||||||
private static final String ATTACHMENT_UNENCRYPTED_SECRET = "pref_attachment_unencrypted_secret";
|
private static final String ATTACHMENT_UNENCRYPTED_SECRET = "pref_attachment_unencrypted_secret";
|
||||||
private static final String NEEDS_SQLCIPHER_MIGRATION = "pref_needs_sql_cipher_migration";
|
private static final String NEEDS_SQLCIPHER_MIGRATION = "pref_needs_sql_cipher_migration"; //TODO AC: Delete
|
||||||
|
|
||||||
private static final String NEXT_PRE_KEY_ID = "pref_next_pre_key_id";
|
private static final String NEXT_PRE_KEY_ID = "pref_next_pre_key_id";
|
||||||
private static final String ACTIVE_SIGNED_PRE_KEY_ID = "pref_active_signed_pre_key_id";
|
private static final String ACTIVE_SIGNED_PRE_KEY_ID = "pref_active_signed_pre_key_id";
|
||||||
|
@ -1,83 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
|
|
||||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="fill_parent"
|
|
||||||
android:fillViewport="true">
|
|
||||||
|
|
||||||
<FrameLayout android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center">
|
|
||||||
|
|
||||||
<LinearLayout android:paddingEnd="16dip"
|
|
||||||
android:paddingStart="16dip"
|
|
||||||
android:paddingTop="10dip"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:visibility="visible"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<EditText android:id="@+id/old_passphrase"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:inputType="textPassword"
|
|
||||||
android:layout_marginBottom="10dip"
|
|
||||||
android:hint="@string/change_passphrase_activity__old_passphrase"
|
|
||||||
android:singleLine="true"/>
|
|
||||||
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<EditText android:id="@+id/new_passphrase"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:inputType="textPassword"
|
|
||||||
android:hint="@string/change_passphrase_activity__new_passphrase"
|
|
||||||
android:singleLine="true"/>
|
|
||||||
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<EditText android:id="@+id/repeat_passphrase"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:inputType="textPassword"
|
|
||||||
android:hint="@string/change_passphrase_activity__repeat_new_passphrase"
|
|
||||||
android:singleLine="true" />
|
|
||||||
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
|
|
||||||
<LinearLayout android:orientation="horizontal"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="30dp">
|
|
||||||
|
|
||||||
<Button android:id="@+id/cancel_button"
|
|
||||||
android:text="@android:string/cancel"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="7dp"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"/>
|
|
||||||
|
|
||||||
<Button android:id="@+id/ok_button"
|
|
||||||
android:text="@android:string/ok"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"/>
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
</FrameLayout>
|
|
||||||
</ScrollView>
|
|
@ -1,31 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
|
|
||||||
<RelativeLayout
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:id="@+id/prompt_layout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/passphrase_edit"
|
|
||||||
style="?android:attr/progressBarStyleLargeInverse"
|
|
||||||
android:layout_width="75dp"
|
|
||||||
android:layout_height="75dp"
|
|
||||||
android:layout_centerInParent="true"
|
|
||||||
android:indeterminate="true"
|
|
||||||
android:padding="10dp"
|
|
||||||
android:visibility="gone"
|
|
||||||
tools:visibility="visible"/>
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/watermark"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="128dp"
|
|
||||||
android:layout_above="@id/passphrase_edit"
|
|
||||||
android:layout_centerInParent="true"
|
|
||||||
android:layout_marginBottom="20dp"
|
|
||||||
android:contentDescription="@string/PassphrasePromptActivity_watermark_content_description"
|
|
||||||
android:src="@drawable/ic_launcher_foreground" />
|
|
||||||
|
|
||||||
</RelativeLayout>
|
|
@ -73,73 +73,7 @@
|
|||||||
android:id="@+id/lock_screen_auth_container"
|
android:id="@+id/lock_screen_auth_container"
|
||||||
android:layout_width="196dp"
|
android:layout_width="196dp"
|
||||||
android:layout_height="@dimen/medium_button_height"
|
android:layout_height="@dimen/medium_button_height"
|
||||||
android:text="Tap to Unlock" />
|
android:text="Tap to Unlock"/>
|
||||||
|
|
||||||
<RelativeLayout android:id="@+id/password_auth_container"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="60dp"
|
|
||||||
tools:visibility="visible">
|
|
||||||
|
|
||||||
<EditText android:id="@+id/passphrase_edit"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="45sp"
|
|
||||||
android:inputType="textPassword"
|
|
||||||
android:layout_marginStart="50dp"
|
|
||||||
android:layout_marginEnd="50dp"
|
|
||||||
android:singleLine="true"
|
|
||||||
android:paddingStart="10dp"
|
|
||||||
android:paddingEnd="40dp"
|
|
||||||
tools:text="password"/>
|
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.AnimatingToggle
|
|
||||||
android:id="@+id/button_toggle"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignRight="@+id/passphrase_edit"
|
|
||||||
android:layout_centerVertical="true"
|
|
||||||
android:layout_gravity="center"
|
|
||||||
android:gravity="center">
|
|
||||||
|
|
||||||
<ImageButton android:id="@+id/passphrase_visibility"
|
|
||||||
android:src="?ic_visibility_on"
|
|
||||||
android:background="@drawable/touch_highlight_background"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingStart="8dp"
|
|
||||||
android:paddingEnd="8dp"
|
|
||||||
android:paddingTop="3dp"
|
|
||||||
android:paddingBottom="3dp"
|
|
||||||
android:layout_centerVertical="true" />
|
|
||||||
|
|
||||||
<ImageButton android:id="@+id/passphrase_visibility_off"
|
|
||||||
android:src="?ic_visibility_off"
|
|
||||||
android:background="@drawable/touch_highlight_background"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingStart="8dp"
|
|
||||||
android:paddingEnd="8dp"
|
|
||||||
android:paddingTop="3dp"
|
|
||||||
android:paddingBottom="3dp"
|
|
||||||
android:layout_centerVertical="true" />
|
|
||||||
|
|
||||||
</org.thoughtcrime.securesms.components.AnimatingToggle>
|
|
||||||
|
|
||||||
<ImageButton android:id="@+id/ok_button"
|
|
||||||
android:src="?ic_arrow_forward"
|
|
||||||
android:contentDescription="@string/PassphrasePromptActivity_ok_button_content_description"
|
|
||||||
android:background="@null"
|
|
||||||
android:text="@string/prompt_passphrase_activity__unlock"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_alignParentEnd="true"
|
|
||||||
android:layout_centerVertical="true"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
|
||||||
android:paddingStart="5dp"
|
|
||||||
android:paddingTop="5dp"
|
|
||||||
android:paddingEnd="10dp"
|
|
||||||
android:paddingBottom="5dp"/>
|
|
||||||
</RelativeLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
@ -1,7 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
|
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item android:title="@string/preferences__submit_debug_log"
|
|
||||||
android:id="@+id/menu_submit_debug_logs"
|
|
||||||
android:icon="@android:drawable/ic_menu_upload" />
|
|
||||||
</menu>
|
|
@ -15,29 +15,29 @@
|
|||||||
android:key="pref_android_screen_lock_timeout"
|
android:key="pref_android_screen_lock_timeout"
|
||||||
android:dependency="pref_android_screen_lock" />
|
android:dependency="pref_android_screen_lock" />
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
|
<!-- <org.thoughtcrime.securesms.components.SwitchPreferenceCompat-->
|
||||||
android:key="pref_enable_passphrase_temporary"
|
<!-- android:key="pref_enable_passphrase_temporary"-->
|
||||||
android:defaultValue="true"
|
<!-- android:defaultValue="true"-->
|
||||||
android:title="@string/preferences__enable_passphrase"
|
<!-- android:title="@string/preferences__enable_passphrase"-->
|
||||||
android:summary="@string/preferences__lock_signal_and_message_notifications_with_a_passphrase" />
|
<!-- android:summary="@string/preferences__lock_signal_and_message_notifications_with_a_passphrase" />-->
|
||||||
|
|
||||||
<Preference
|
<!-- <Preference-->
|
||||||
android:key="pref_change_passphrase"
|
<!-- android:key="pref_change_passphrase"-->
|
||||||
android:title="@string/preferences__change_passphrase"
|
<!-- android:title="@string/preferences__change_passphrase"-->
|
||||||
android:summary="@string/preferences__change_your_passphrase"
|
<!-- android:summary="@string/preferences__change_your_passphrase"-->
|
||||||
android:dependency="pref_enable_passphrase_temporary" />
|
<!-- android:dependency="pref_enable_passphrase_temporary" />-->
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
|
<!-- <org.thoughtcrime.securesms.components.SwitchPreferenceCompat-->
|
||||||
android:defaultValue="false"
|
<!-- android:defaultValue="false"-->
|
||||||
android:key="pref_timeout_passphrase"
|
<!-- android:key="pref_timeout_passphrase"-->
|
||||||
android:title="@string/preferences__inactivity_timeout_passphrase"
|
<!-- android:title="@string/preferences__inactivity_timeout_passphrase"-->
|
||||||
android:summary="@string/preferences__auto_lock_signal_after_a_specified_time_interval_of_inactivity"
|
<!-- android:summary="@string/preferences__auto_lock_signal_after_a_specified_time_interval_of_inactivity"-->
|
||||||
android:dependency="pref_enable_passphrase_temporary" />
|
<!-- android:dependency="pref_enable_passphrase_temporary" />-->
|
||||||
|
|
||||||
<Preference
|
<!-- <Preference-->
|
||||||
android:title="@string/preferences__inactivity_timeout_interval"
|
<!-- android:title="@string/preferences__inactivity_timeout_interval"-->
|
||||||
android:key="pref_timeout_interval"
|
<!-- android:key="pref_timeout_interval"-->
|
||||||
android:dependency="pref_timeout_passphrase" />
|
<!-- android:dependency="pref_timeout_passphrase" />-->
|
||||||
|
|
||||||
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
|
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
|
||||||
android:defaultValue="true"
|
android:defaultValue="true"
|
||||||
|
Loading…
Reference in New Issue
Block a user