mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-27 12:05:22 +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:theme="@style/Theme.TextSecure.DayNight.NoActionBar"
|
||||
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
|
||||
android:name="org.thoughtcrime.securesms.stickers.StickerManagementActivity"
|
||||
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.TypingStatusSender;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
|
||||
import org.thoughtcrime.securesms.crypto.storage.TextSecureSessionStore;
|
||||
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.mentions.MentionsManager;
|
||||
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.SessionManagementProtocolDelegate;
|
||||
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);
|
||||
TextSecurePreferences.clearAll(this);
|
||||
TextSecurePreferences.setWasUnlinked(this, wasUnlinked);
|
||||
MasterSecretUtil.clear(this);
|
||||
// MasterSecretUtil.clear(this);
|
||||
if (!deleteDatabase("signal.db")) {
|
||||
Log.d("Loki", "Failed to delete database.");
|
||||
}
|
||||
|
@ -23,15 +23,10 @@ import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import android.view.View;
|
||||
import android.widget.ProgressBar;
|
||||
|
||||
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.DatabaseFactory;
|
||||
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.jobs.AttachmentDownloadJob;
|
||||
import org.thoughtcrime.securesms.jobs.PushDecryptJob;
|
||||
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
|
||||
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.VersionTracker;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
@ -60,72 +48,72 @@ import network.loki.messenger.R;
|
||||
public class DatabaseUpgradeActivity extends BaseActivity {
|
||||
private static final String TAG = DatabaseUpgradeActivity.class.getSimpleName();
|
||||
|
||||
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 TOFU_IDENTITIES_VERSION = 1; // 50
|
||||
public static final int CURVE25519_VERSION = 2; // 63
|
||||
public static final int ASYMMETRIC_MASTER_SECRET_FIX_VERSION = 3; // 73
|
||||
public static final int NO_V1_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 PUSH_DECRYPT_SERIAL_ID_VERSION = 6; // 131
|
||||
public static final int MIGRATE_SESSION_PLAINTEXT = 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 REDPHONE_SUPPORT_VERSION = 9; // 157
|
||||
public static final int NO_MORE_CANONICAL_DB_VERSION = 10; // 276
|
||||
public static final int PROFILES = 11; // 289
|
||||
public static final int SCREENSHOTS = 12; // 300
|
||||
public static final int PERSISTENT_BLOBS = 13; // 317
|
||||
public static final int INTERNALIZE_CONTACTS = 13; // 317
|
||||
public static final int SQLCIPHER = 14; // 334
|
||||
public static final int SQLCIPHER_COMPLETE = 15; // 352
|
||||
public static final int REMOVE_JOURNAL = 16; // 353
|
||||
public static final int REMOVE_CACHE = 17; // 354
|
||||
public static final int FULL_TEXT_SEARCH = 18; // 358
|
||||
public static final int BAD_IMPORT_CLEANUP = 19; // 373
|
||||
public static final int IMAGE_CACHE_CLEANUP = 20; // 406
|
||||
public static final int WORKMANAGER_MIGRATION = 21; // 408
|
||||
public static final int COLOR_MIGRATION = 22; // 412
|
||||
public static final int UNIDENTIFIED_DELIVERY = 23; // 422
|
||||
public static final int SIGNALING_KEY_DEPRECATION = 24; // 447
|
||||
public static final int CONVERSATION_SEARCH = 25; // 455
|
||||
// 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 TOFU_IDENTITIES_VERSION = 1; // 50
|
||||
// public static final int CURVE25519_VERSION = 2; // 63
|
||||
// public static final int ASYMMETRIC_MASTER_SECRET_FIX_VERSION = 3; // 73
|
||||
// public static final int NO_V1_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 PUSH_DECRYPT_SERIAL_ID_VERSION = 6; // 131
|
||||
// public static final int MIGRATE_SESSION_PLAINTEXT = 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 REDPHONE_SUPPORT_VERSION = 9; // 157
|
||||
// public static final int NO_MORE_CANONICAL_DB_VERSION = 10; // 276
|
||||
// public static final int PROFILES = 11; // 289
|
||||
// public static final int SCREENSHOTS = 12; // 300
|
||||
// public static final int PERSISTENT_BLOBS = 13; // 317
|
||||
// public static final int INTERNALIZE_CONTACTS = 13; // 317
|
||||
// public static final int SQLCIPHER = 14; // 334
|
||||
// public static final int SQLCIPHER_COMPLETE = 15; // 352
|
||||
// public static final int REMOVE_JOURNAL = 16; // 353
|
||||
// public static final int REMOVE_CACHE = 17; // 354
|
||||
// public static final int FULL_TEXT_SEARCH = 18; // 358
|
||||
// public static final int BAD_IMPORT_CLEANUP = 19; // 373
|
||||
// public static final int IMAGE_CACHE_CLEANUP = 20; // 406
|
||||
// public static final int WORKMANAGER_MIGRATION = 21; // 408
|
||||
// public static final int COLOR_MIGRATION = 22; // 412
|
||||
// public static final int UNIDENTIFIED_DELIVERY = 23; // 422
|
||||
// public static final int SIGNALING_KEY_DEPRECATION = 24; // 447
|
||||
// public static final int CONVERSATION_SEARCH = 25; // 455
|
||||
|
||||
private static final SortedSet<Integer> UPGRADE_VERSIONS = new TreeSet<Integer>() {{
|
||||
add(NO_MORE_KEY_EXCHANGE_PREFIX_VERSION);
|
||||
add(TOFU_IDENTITIES_VERSION);
|
||||
add(CURVE25519_VERSION);
|
||||
add(ASYMMETRIC_MASTER_SECRET_FIX_VERSION);
|
||||
add(NO_V1_VERSION);
|
||||
add(SIGNED_PREKEY_VERSION);
|
||||
add(NO_DECRYPT_QUEUE_VERSION);
|
||||
add(PUSH_DECRYPT_SERIAL_ID_VERSION);
|
||||
add(MIGRATE_SESSION_PLAINTEXT);
|
||||
add(MEDIA_DOWNLOAD_CONTROLS_VERSION);
|
||||
add(REDPHONE_SUPPORT_VERSION);
|
||||
add(NO_MORE_CANONICAL_DB_VERSION);
|
||||
add(SCREENSHOTS);
|
||||
add(INTERNALIZE_CONTACTS);
|
||||
add(PERSISTENT_BLOBS);
|
||||
add(SQLCIPHER);
|
||||
add(SQLCIPHER_COMPLETE);
|
||||
add(REMOVE_CACHE);
|
||||
add(FULL_TEXT_SEARCH);
|
||||
add(BAD_IMPORT_CLEANUP);
|
||||
add(IMAGE_CACHE_CLEANUP);
|
||||
add(WORKMANAGER_MIGRATION);
|
||||
add(COLOR_MIGRATION);
|
||||
add(UNIDENTIFIED_DELIVERY);
|
||||
add(SIGNALING_KEY_DEPRECATION);
|
||||
add(CONVERSATION_SEARCH);
|
||||
// add(NO_MORE_KEY_EXCHANGE_PREFIX_VERSION);
|
||||
// add(TOFU_IDENTITIES_VERSION);
|
||||
// add(CURVE25519_VERSION);
|
||||
// add(ASYMMETRIC_MASTER_SECRET_FIX_VERSION);
|
||||
// add(NO_V1_VERSION);
|
||||
// add(SIGNED_PREKEY_VERSION);
|
||||
// add(NO_DECRYPT_QUEUE_VERSION);
|
||||
// add(PUSH_DECRYPT_SERIAL_ID_VERSION);
|
||||
// add(MIGRATE_SESSION_PLAINTEXT);
|
||||
// add(MEDIA_DOWNLOAD_CONTROLS_VERSION);
|
||||
// add(REDPHONE_SUPPORT_VERSION);
|
||||
// add(NO_MORE_CANONICAL_DB_VERSION);
|
||||
// add(SCREENSHOTS);
|
||||
// add(INTERNALIZE_CONTACTS);
|
||||
// add(PERSISTENT_BLOBS);
|
||||
// add(SQLCIPHER);
|
||||
// add(SQLCIPHER_COMPLETE);
|
||||
// add(REMOVE_CACHE);
|
||||
// add(FULL_TEXT_SEARCH);
|
||||
// add(BAD_IMPORT_CLEANUP);
|
||||
// add(IMAGE_CACHE_CLEANUP);
|
||||
// add(WORKMANAGER_MIGRATION);
|
||||
// add(COLOR_MIGRATION);
|
||||
// add(UNIDENTIFIED_DELIVERY);
|
||||
// add(SIGNALING_KEY_DEPRECATION);
|
||||
// add(CONVERSATION_SEARCH);
|
||||
}};
|
||||
|
||||
private MasterSecret masterSecret;
|
||||
// private MasterSecret masterSecret;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle) {
|
||||
super.onCreate(bundle);
|
||||
this.masterSecret = KeyCachingService.getMasterSecret(this);
|
||||
// this.masterSecret = KeyCachingService.getMasterSecret(this);
|
||||
|
||||
if (needsUpgradeTask()) {
|
||||
Log.i("DatabaseUpgradeActivity", "Upgrading...");
|
||||
@ -202,160 +190,160 @@ public class DatabaseUpgradeActivity extends BaseActivity {
|
||||
Context context = DatabaseUpgradeActivity.this.getApplicationContext();
|
||||
|
||||
Log.i("DatabaseUpgradeActivity", "Running background upgrade..");
|
||||
DatabaseFactory.getInstance(DatabaseUpgradeActivity.this)
|
||||
.onApplicationLevelUpgrade(context, masterSecret, params[0], this);
|
||||
// DatabaseFactory.getInstance(DatabaseUpgradeActivity.this)
|
||||
// .onApplicationLevelUpgrade(context, params[0], this);
|
||||
|
||||
if (params[0] < CURVE25519_VERSION) {
|
||||
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");
|
||||
// if (params[0] < CURVE25519_VERSION) {
|
||||
// IdentityKeyUtil.migrateIdentityKeys(context, masterSecret);
|
||||
// }
|
||||
//
|
||||
// JobManager jobManager = ApplicationContext.getInstance(getApplicationContext()).getJobManager();
|
||||
// PersistentStorage storage = new PersistentStorage(getApplicationContext(), "TextSecureJobs", new JavaJobSerializer());
|
||||
// if (params[0] < NO_V1_VERSION) {
|
||||
// File v1sessions = new File(context.getFilesDir(), "sessions");
|
||||
//
|
||||
// for (Job job : storage.getAllUnencrypted()) {
|
||||
// jobManager.add(job);
|
||||
// Log.i(TAG, "Migrated job with class '" + job.getClass().getSimpleName() + "' to run on new JobManager.");
|
||||
// if (v1sessions.exists() && v1sessions.isDirectory()) {
|
||||
// File[] contents = v1sessions.listFiles();
|
||||
//
|
||||
// if (contents != null) {
|
||||
// for (File session : contents) {
|
||||
// session.delete();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// v1sessions.delete();
|
||||
// }
|
||||
// }
|
||||
|
||||
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());
|
||||
}
|
||||
//
|
||||
// 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();
|
||||
//// 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;
|
||||
}
|
||||
|
@ -62,7 +62,6 @@ import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.util.AttachmentUtil;
|
||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
|
||||
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
@ -86,18 +85,11 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity {
|
||||
|
||||
public static final String ADDRESS_EXTRA = "address";
|
||||
|
||||
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||
|
||||
private Toolbar toolbar;
|
||||
private TabLayout tabLayout;
|
||||
private ViewPager viewPager;
|
||||
private Recipient recipient;
|
||||
|
||||
@Override
|
||||
protected void onPreCreate() {
|
||||
dynamicLanguage.onCreate(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle bundle, boolean ready) {
|
||||
setContentView(R.layout.media_overview_activity);
|
||||
@ -109,12 +101,6 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity {
|
||||
this.viewPager.setAdapter(new MediaOverviewPagerAdapter(getSupportFragmentManager()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
dynamicLanguage.onResume(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
super.onOptionsItemSelected(item);
|
||||
@ -172,7 +158,7 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity {
|
||||
|
||||
Bundle args = new Bundle();
|
||||
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);
|
||||
|
||||
|
@ -68,12 +68,12 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
|
||||
import org.thoughtcrime.securesms.util.AttachmentUtil;
|
||||
import org.thoughtcrime.securesms.util.DateUtils;
|
||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
|
||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask.Attachment;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
import java.util.WeakHashMap;
|
||||
|
||||
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 LEFT_IS_RECENT_EXTRA = "left_is_recent";
|
||||
|
||||
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||
|
||||
private ViewPager mediaPager;
|
||||
private View detailsContainer;
|
||||
private TextView caption;
|
||||
@ -120,8 +118,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@Override
|
||||
protected void onCreate(Bundle bundle, boolean ready) {
|
||||
dynamicLanguage.onCreate(this);
|
||||
|
||||
viewModel = new ViewModelProvider(this).get(MediaPreviewViewModel.class);
|
||||
|
||||
setContentView(R.layout.media_preview_activity);
|
||||
@ -172,7 +168,7 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
|
||||
CharSequence relativeTimeSpan;
|
||||
|
||||
if (mediaItem.date > 0) {
|
||||
relativeTimeSpan = DateUtils.getExtendedRelativeTimeSpanString(this,dynamicLanguage.getCurrentLocale(), mediaItem.date);
|
||||
relativeTimeSpan = DateUtils.getExtendedRelativeTimeSpanString(this, Locale.getDefault(), mediaItem.date);
|
||||
} else {
|
||||
relativeTimeSpan = getString(R.string.MediaPreviewActivity_draft);
|
||||
}
|
||||
@ -189,7 +185,6 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
dynamicLanguage.onResume(this);
|
||||
initializeMedia();
|
||||
}
|
||||
|
||||
|
@ -55,7 +55,6 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientModifiedListener;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.util.DateUtils;
|
||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||
import org.thoughtcrime.securesms.util.ExpirationUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.session.libsignal.libsignal.util.guava.Optional;
|
||||
@ -102,15 +101,8 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
|
||||
private ListView recipientsList;
|
||||
private LayoutInflater inflater;
|
||||
|
||||
private DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||
|
||||
private boolean running;
|
||||
|
||||
@Override
|
||||
protected void onPreCreate() {
|
||||
dynamicLanguage.onCreate(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle, boolean ready) {
|
||||
super.onCreate(bundle, ready);
|
||||
@ -125,7 +117,6 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
dynamicLanguage.onResume(this);
|
||||
|
||||
assert getSupportActionBar() != null;
|
||||
getSupportActionBar().setTitle("Message Details");
|
||||
@ -211,7 +202,7 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
|
||||
sentDate.setText("-");
|
||||
receivedContainer.setVisibility(View.GONE);
|
||||
} else {
|
||||
Locale dateLocale = dynamicLanguage.getCurrentLocale();
|
||||
Locale dateLocale = Locale.getDefault();
|
||||
SimpleDateFormat dateFormatter = DateUtils.getDetailedDateFormatter(this, dateLocale);
|
||||
sentDate.setText(dateFormatter.format(new Date(messageRecord.getDateSent())));
|
||||
sentDate.setOnLongClickListener(v -> {
|
||||
@ -271,7 +262,7 @@ public class MessageDetailsActivity extends PassphraseRequiredActionBarActivity
|
||||
toFrom.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));
|
||||
}
|
||||
|
||||
|
@ -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.app.KeyguardManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.os.IBinder;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.style.RelativeSizeSpan;
|
||||
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.OnClickListener;
|
||||
import android.view.WindowManager;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.BounceInterpolator;
|
||||
import android.view.animation.TranslateAnimation;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.core.hardware.fingerprint.FingerprintManagerCompat;
|
||||
import androidx.core.os.CancellationSignal;
|
||||
|
||||
import org.thoughtcrime.securesms.animation.AnimationCompleteListener;
|
||||
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.util.DynamicLanguage;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
import network.loki.messenger.R;
|
||||
|
||||
/**
|
||||
* Activity that prompts for a user's passphrase.
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
public class PassphrasePromptActivity extends PassphraseActivity {
|
||||
//TODO Rename to screen lock activity and refactor to Kotlin.
|
||||
public class PassphrasePromptActivity extends BaseActionBarActivity {
|
||||
|
||||
private static final String TAG = PassphrasePromptActivity.class.getSimpleName();
|
||||
|
||||
private DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||
|
||||
private View passphraseAuthContainer;
|
||||
private ImageView fingerprintPrompt;
|
||||
private Button lockScreenButton;
|
||||
|
||||
private EditText passphraseText;
|
||||
private ImageButton showButton;
|
||||
private ImageButton hideButton;
|
||||
private AnimatingToggle visibilityToggle;
|
||||
|
||||
private FingerprintManagerCompat fingerprintManager;
|
||||
@ -87,20 +64,34 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
||||
private boolean authenticated;
|
||||
private boolean failure;
|
||||
|
||||
private KeyCachingService keyCachingService;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
Log.i(TAG, "onCreate()");
|
||||
dynamicLanguage.onCreate(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.prompt_passphrase_activity);
|
||||
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
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
dynamicLanguage.onResume(this);
|
||||
|
||||
setLockTypeVisibility();
|
||||
|
||||
@ -126,26 +117,6 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
||||
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
|
||||
public void onActivityResult(int requestCode, int resultcode, Intent 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() {
|
||||
try {
|
||||
authenticated = true;
|
||||
|
||||
MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(this, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
|
||||
setMasterSecret(masterSecret);
|
||||
} catch (InvalidPassphraseException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
authenticated = true;
|
||||
//TODO Replace with a proper call.
|
||||
keyCachingService.setMasterSecret(new Object());
|
||||
|
||||
private void setPassphraseVisibility(boolean visibility) {
|
||||
int cursorPosition = passphraseText.getSelectionStart();
|
||||
if (visibility) {
|
||||
passphraseText.setInputType(InputType.TYPE_CLASS_TEXT |
|
||||
InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
|
||||
} else {
|
||||
passphraseText.setInputType(InputType.TYPE_CLASS_TEXT |
|
||||
InputType.TYPE_TEXT_VARIATION_PASSWORD);
|
||||
// Finish and proceed with the next intent.
|
||||
Intent nextIntent = getIntent().getParcelableExtra("next_intent");
|
||||
if (nextIntent != null) {
|
||||
startActivity(nextIntent);
|
||||
// try {
|
||||
// startActivity(nextIntent);
|
||||
// } catch (java.lang.SecurityException e) {
|
||||
// Log.w(TAG, "Access permission not passed from PassphraseActivity, retry sharing.");
|
||||
// }
|
||||
}
|
||||
passphraseText.setSelection(cursorPosition);
|
||||
finish();
|
||||
}
|
||||
|
||||
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);
|
||||
passphraseText = findViewById(R.id.passphrase_edit);
|
||||
passphraseAuthContainer = findViewById(R.id.password_auth_container);
|
||||
fingerprintPrompt = findViewById(R.id.fingerprint_auth_container);
|
||||
lockScreenButton = findViewById(R.id.lock_screen_auth_container);
|
||||
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 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.getBackground().setColorFilter(getResources().getColor(R.color.signal_primary), PorterDuff.Mode.SRC_IN);
|
||||
|
||||
@ -235,8 +168,6 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
||||
|
||||
private void setLockTypeVisibility() {
|
||||
if (TextSecurePreferences.isScreenLockEnabled(this)) {
|
||||
passphraseAuthContainer.setVisibility(View.GONE);
|
||||
|
||||
if (fingerprintManager.isHardwareDetected() && fingerprintManager.hasEnrolledFingerprints()) {
|
||||
fingerprintPrompt.setVisibility(View.VISIBLE);
|
||||
lockScreenButton.setVisibility(View.GONE);
|
||||
@ -245,7 +176,6 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
||||
lockScreenButton.setVisibility(View.VISIBLE);
|
||||
}
|
||||
} else {
|
||||
passphraseAuthContainer.setVisibility(View.VISIBLE);
|
||||
fingerprintPrompt.setVisibility(View.GONE);
|
||||
lockScreenButton.setVisibility(View.GONE);
|
||||
}
|
||||
@ -266,13 +196,10 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
||||
Log.i(TAG, "Listening for fingerprints...");
|
||||
fingerprintCancellationSignal = new CancellationSignal();
|
||||
fingerprintManager.authenticate(null, 0, fingerprintCancellationSignal, fingerprintListener, null);
|
||||
} else if (Build.VERSION.SDK_INT >= 21){
|
||||
} else {
|
||||
Log.i(TAG, "firing intent...");
|
||||
Intent intent = keyguardManager.createConfirmDeviceCredentialIntent("Unlock Session", "");
|
||||
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 {
|
||||
@Override
|
||||
public void onAuthenticationError(int errMsgId, CharSequence errString) {
|
||||
@ -356,7 +235,6 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
Log.w(TAG, "onAuthenticatoinFailed()");
|
||||
FingerprintManagerCompat.AuthenticationCallback callback = this;
|
||||
|
||||
fingerprintPrompt.setImageResource(R.drawable.ic_close_white_48dp);
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -10,8 +10,6 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
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.loki.activities.HomeActivity;
|
||||
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";
|
||||
|
||||
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_UPGRADE_DATABASE = 3;
|
||||
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);
|
||||
|
||||
switch (state) {
|
||||
case STATE_CREATE_PASSPHRASE: return getCreatePassphraseIntent();
|
||||
case STATE_PROMPT_PASSPHRASE: return getPromptPassphraseIntent();
|
||||
case STATE_UPGRADE_DATABASE: return getUpgradeDatabaseIntent();
|
||||
case STATE_WELCOME_SCREEN: return getWelcomeIntent();
|
||||
@ -129,9 +125,7 @@ public abstract class PassphraseRequiredActionBarActivity extends BaseActionBarA
|
||||
}
|
||||
|
||||
private int getApplicationState(boolean locked) {
|
||||
if (!MasterSecretUtil.isPassphraseInitialized(this)) {
|
||||
return STATE_CREATE_PASSPHRASE;
|
||||
} else if (locked) {
|
||||
if (locked) {
|
||||
return STATE_PROMPT_PASSPHRASE;
|
||||
} else if (DatabaseUpgradeActivity.isUpdate(this)) {
|
||||
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() {
|
||||
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.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
|
@ -48,8 +48,6 @@ import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.view.ActionMode;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.app.ActivityOptionsCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.loader.app.LoaderManager;
|
||||
import androidx.loader.content.Loader;
|
||||
@ -66,8 +64,6 @@ import org.thoughtcrime.securesms.ShareActivity;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.components.ConversationTypingView;
|
||||
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.ItemClickListener;
|
||||
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
|
||||
*/
|
||||
|
||||
//TODO AC: Delete
|
||||
public class IdentityKeyUtil {
|
||||
|
||||
private static final String MASTER_SECRET_UTIL_PREFERENCES_NAME = "SecureSMS-Preferences";
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
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_PRIVATE_KEY_PREF = "pref_identity_private_v3";
|
||||
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 boolean hasIdentityKey(Context context) {
|
||||
SharedPreferences preferences = context.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0);
|
||||
SharedPreferences preferences = context.getSharedPreferences(MASTER_SECRET_UTIL_PREFERENCES_NAME, 0);
|
||||
|
||||
return
|
||||
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) {
|
||||
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<>();
|
||||
|
||||
prefList.add(BackupProtos.SharedPreference.newBuilder()
|
||||
.setFile(MasterSecretUtil.PREFERENCES_NAME)
|
||||
.setFile(prefName)
|
||||
.setKey(IDENTITY_PUBLIC_KEY_PREF)
|
||||
.setValue(preferences.getString(IDENTITY_PUBLIC_KEY_PREF, null))
|
||||
.build());
|
||||
prefList.add(BackupProtos.SharedPreference.newBuilder()
|
||||
.setFile(MasterSecretUtil.PREFERENCES_NAME)
|
||||
.setFile(prefName)
|
||||
.setKey(IDENTITY_PRIVATE_KEY_PREF)
|
||||
.setValue(preferences.getString(IDENTITY_PRIVATE_KEY_PREF, null))
|
||||
.build());
|
||||
if (preferences.contains(ED25519_PUBLIC_KEY)) {
|
||||
prefList.add(BackupProtos.SharedPreference.newBuilder()
|
||||
.setFile(MasterSecretUtil.PREFERENCES_NAME)
|
||||
.setFile(prefName)
|
||||
.setKey(ED25519_PUBLIC_KEY)
|
||||
.setValue(preferences.getString(ED25519_PUBLIC_KEY, null))
|
||||
.build());
|
||||
}
|
||||
if (preferences.contains(ED25519_SECRET_KEY)) {
|
||||
prefList.add(BackupProtos.SharedPreference.newBuilder()
|
||||
.setFile(MasterSecretUtil.PREFERENCES_NAME)
|
||||
.setFile(prefName)
|
||||
.setKey(ED25519_SECRET_KEY)
|
||||
.setValue(preferences.getString(ED25519_SECRET_KEY, null))
|
||||
.build());
|
||||
}
|
||||
prefList.add(BackupProtos.SharedPreference.newBuilder()
|
||||
.setFile(MasterSecretUtil.PREFERENCES_NAME)
|
||||
.setFile(prefName)
|
||||
.setKey(LOKI_SEED)
|
||||
.setValue(preferences.getString(LOKI_SEED, null))
|
||||
.build());
|
||||
@ -151,34 +125,13 @@ public class IdentityKeyUtil {
|
||||
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) {
|
||||
SharedPreferences preferences = context.getSharedPreferences(MasterSecretUtil.PREFERENCES_NAME, 0);
|
||||
SharedPreferences preferences = context.getSharedPreferences(MASTER_SECRET_UTIL_PREFERENCES_NAME, 0);
|
||||
return preferences.getString(key, null);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
preferencesEditor.putString(key, value);
|
||||
@ -186,6 +139,6 @@ public class IdentityKeyUtil {
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
|
||||
import org.thoughtcrime.securesms.DatabaseUpgradeActivity;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
||||
import org.thoughtcrime.securesms.crypto.DatabaseSecret;
|
||||
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.loki.database.LokiAPIDatabase;
|
||||
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.LokiUserDatabase;
|
||||
import org.thoughtcrime.securesms.loki.database.SharedSenderKeysDatabase;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
public class DatabaseFactory {
|
||||
|
||||
@ -227,28 +223,4 @@ public class DatabaseFactory {
|
||||
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;
|
||||
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.SystemClock;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
@ -14,10 +10,7 @@ import net.sqlcipher.database.SQLiteDatabase;
|
||||
import net.sqlcipher.database.SQLiteDatabaseHook;
|
||||
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.MasterSecret;
|
||||
import org.thoughtcrime.securesms.database.Address;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.database.DraftDatabase;
|
||||
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.LokiUserDatabase;
|
||||
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 {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = SQLCipherOpenHelper.class.getSimpleName();
|
||||
|
||||
private static final int RECIPIENT_CALL_RINGTONE_VERSION = 2;
|
||||
private static final int MIGRATE_PREKEYS_VERSION = 3;
|
||||
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;
|
||||
// First public release (1.0.0) DB version was 27.
|
||||
// So we have to keep the migrations onwards.
|
||||
private static final int lokiV7 = 28;
|
||||
private static final int lokiV8 = 29;
|
||||
private static final int lokiV9 = 30;
|
||||
@ -177,20 +140,6 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
executeStatements(db, GroupDatabase.CREATE_INDEXS);
|
||||
executeStatements(db, GroupReceiptDatabase.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
|
||||
@ -210,379 +159,6 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
|
||||
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) {
|
||||
db.execSQL(LokiMessageDatabase.getCreateErrorMessageTableCommand());
|
||||
}
|
||||
@ -652,10 +228,6 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
}
|
||||
|
||||
if (oldVersion < MIGRATE_PREKEYS_VERSION) {
|
||||
// PreKeyMigrationHelper.cleanUpPreKeys(context);
|
||||
}
|
||||
}
|
||||
|
||||
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.logging.Log;
|
||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
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_HEIGHT = "extra_height";
|
||||
|
||||
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||
|
||||
private GiphyGifFragment gifFragment;
|
||||
private GiphyStickerFragment stickerFragment;
|
||||
private boolean forMms;
|
||||
|
||||
private GiphyAdapter.GiphyViewHolder finishingImage;
|
||||
|
||||
@Override
|
||||
public void onPreCreate() {
|
||||
dynamicLanguage.onCreate(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle bundle, boolean ready) {
|
||||
setContentView(R.layout.giphy_activity);
|
||||
|
@ -19,6 +19,7 @@ import java.io.IOException;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
//TODO AC: Delete
|
||||
public class RefreshAttributesJob extends BaseJob implements InjectableType {
|
||||
|
||||
public static final String KEY = "RefreshAttributesJob";
|
||||
|
@ -5,17 +5,13 @@ import android.app.KeyguardManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.preference.CheckBoxPreference;
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.PassphraseChangeActivity;
|
||||
import org.thoughtcrime.securesms.components.SwitchPreferenceCompat;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||
import org.thoughtcrime.securesms.dependencies.InjectableType;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
@ -27,8 +23,6 @@ import network.loki.messenger.R;
|
||||
|
||||
public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment implements InjectableType {
|
||||
|
||||
private CheckBoxPreference disablePassphrase;
|
||||
|
||||
@Override
|
||||
public void onAttach(Activity activity) {
|
||||
super.onAttach(activity);
|
||||
@ -39,17 +33,12 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
|
||||
public void onCreate(Bundle 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_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.TYPING_INDICATORS).setOnPreferenceChangeListener(new TypingIndicatorsToggleListener());
|
||||
this.findPreference(TextSecurePreferences.LINK_PREVIEWS).setOnPreferenceChangeListener(new LinkPreviewToggleListener());
|
||||
disablePassphrase.setOnPreferenceChangeListener(new DisablePassphraseClickListener());
|
||||
|
||||
initializeVisibility();
|
||||
}
|
||||
@ -62,16 +51,9 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (!TextSecurePreferences.isPasswordDisabled(getContext())) initializePassphraseTimeoutSummary();
|
||||
else 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));
|
||||
if (TextSecurePreferences.isPasswordDisabled(getContext())) {
|
||||
initializeScreenLockTimeoutSummary();
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeScreenLockTimeoutSummary() {
|
||||
@ -87,11 +69,6 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
|
||||
|
||||
private void initializeVisibility() {
|
||||
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);
|
||||
if (!keyguardManager.isKeyguardSecure()) {
|
||||
((SwitchPreferenceCompat)findPreference(TextSecurePreferences.SCREEN_LOCK)).setChecked(false);
|
||||
@ -178,90 +155,4 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
|
||||
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.DatabaseUpgradeActivity;
|
||||
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.loki.activities.HomeActivity;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
import org.thoughtcrime.securesms.util.DynamicLanguage;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
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";
|
||||
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 LOCALE_CHANGE_EVENT = "org.thoughtcrime.securesms.service.action.LOCALE_CHANGE_EVENT";
|
||||
|
||||
private DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||
|
||||
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() {}
|
||||
|
||||
@ -81,18 +76,6 @@ public class KeyCachingService extends Service {
|
||||
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) {
|
||||
ServiceUtil.getAlarmManager(context).cancel(buildExpirationPendingIntent(context));
|
||||
}
|
||||
@ -101,8 +84,22 @@ public class KeyCachingService extends Service {
|
||||
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")
|
||||
public void setMasterSecret(final MasterSecret masterSecret) {
|
||||
public void setMasterSecret(final Object masterSecret) {
|
||||
synchronized (KeyCachingService.class) {
|
||||
KeyCachingService.masterSecret = masterSecret;
|
||||
|
||||
@ -132,7 +129,6 @@ public class KeyCachingService extends Service {
|
||||
case CLEAR_KEY_ACTION: handleClearKey(); break;
|
||||
case PASSPHRASE_EXPIRED_EVENT: handleClearKey(); break;
|
||||
case DISABLE_ACTION: handleDisableService(); break;
|
||||
case LOCALE_CHANGE_EVENT: handleLocaleChanged(); break;
|
||||
case LOCK_TOGGLED_EVENT: handleLockToggled(); break;
|
||||
}
|
||||
}
|
||||
@ -146,12 +142,12 @@ public class KeyCachingService extends Service {
|
||||
super.onCreate();
|
||||
|
||||
if (TextSecurePreferences.isPasswordDisabled(this) && !TextSecurePreferences.isScreenLockEnabled(this)) {
|
||||
try {
|
||||
MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(this, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
|
||||
setMasterSecret(masterSecret);
|
||||
} catch (InvalidPassphraseException e) {
|
||||
Log.w("KeyCachingService", e);
|
||||
}
|
||||
// try {
|
||||
// MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(this, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
|
||||
setMasterSecret(new Object());
|
||||
// } catch (InvalidPassphraseException e) {
|
||||
// Log.w("KeyCachingService", e);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@ -196,12 +192,12 @@ public class KeyCachingService extends Service {
|
||||
private void handleLockToggled() {
|
||||
stopForeground(true);
|
||||
|
||||
try {
|
||||
MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(this, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
|
||||
// try {
|
||||
// MasterSecret masterSecret = MasterSecretUtil.getMasterSecret(this, MasterSecretUtil.UNENCRYPTED_PASSPHRASE);
|
||||
setMasterSecret(masterSecret);
|
||||
} catch (InvalidPassphraseException e) {
|
||||
Log.w(TAG, e);
|
||||
}
|
||||
// } catch (InvalidPassphraseException e) {
|
||||
// Log.w(TAG, e);
|
||||
// }
|
||||
}
|
||||
|
||||
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) {
|
||||
boolean appVisible = ApplicationContext.getInstance(context).isAppVisible();
|
||||
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 ATTACHMENT_ENCRYPTED_SECRET = "pref_attachment_encrypted_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 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:layout_width="196dp"
|
||||
android:layout_height="@dimen/medium_button_height"
|
||||
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>
|
||||
android:text="Tap to Unlock"/>
|
||||
|
||||
</LinearLayout>
|
||||
</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:dependency="pref_android_screen_lock" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
|
||||
android:key="pref_enable_passphrase_temporary"
|
||||
android:defaultValue="true"
|
||||
android:title="@string/preferences__enable_passphrase"
|
||||
android:summary="@string/preferences__lock_signal_and_message_notifications_with_a_passphrase" />
|
||||
<!-- <org.thoughtcrime.securesms.components.SwitchPreferenceCompat-->
|
||||
<!-- android:key="pref_enable_passphrase_temporary"-->
|
||||
<!-- android:defaultValue="true"-->
|
||||
<!-- android:title="@string/preferences__enable_passphrase"-->
|
||||
<!-- android:summary="@string/preferences__lock_signal_and_message_notifications_with_a_passphrase" />-->
|
||||
|
||||
<Preference
|
||||
android:key="pref_change_passphrase"
|
||||
android:title="@string/preferences__change_passphrase"
|
||||
android:summary="@string/preferences__change_your_passphrase"
|
||||
android:dependency="pref_enable_passphrase_temporary" />
|
||||
<!-- <Preference-->
|
||||
<!-- android:key="pref_change_passphrase"-->
|
||||
<!-- android:title="@string/preferences__change_passphrase"-->
|
||||
<!-- android:summary="@string/preferences__change_your_passphrase"-->
|
||||
<!-- android:dependency="pref_enable_passphrase_temporary" />-->
|
||||
|
||||
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="pref_timeout_passphrase"
|
||||
android:title="@string/preferences__inactivity_timeout_passphrase"
|
||||
android:summary="@string/preferences__auto_lock_signal_after_a_specified_time_interval_of_inactivity"
|
||||
android:dependency="pref_enable_passphrase_temporary" />
|
||||
<!-- <org.thoughtcrime.securesms.components.SwitchPreferenceCompat-->
|
||||
<!-- android:defaultValue="false"-->
|
||||
<!-- android:key="pref_timeout_passphrase"-->
|
||||
<!-- android:title="@string/preferences__inactivity_timeout_passphrase"-->
|
||||
<!-- android:summary="@string/preferences__auto_lock_signal_after_a_specified_time_interval_of_inactivity"-->
|
||||
<!-- android:dependency="pref_enable_passphrase_temporary" />-->
|
||||
|
||||
<Preference
|
||||
android:title="@string/preferences__inactivity_timeout_interval"
|
||||
android:key="pref_timeout_interval"
|
||||
android:dependency="pref_timeout_passphrase" />
|
||||
<!-- <Preference-->
|
||||
<!-- android:title="@string/preferences__inactivity_timeout_interval"-->
|
||||
<!-- android:key="pref_timeout_interval"-->
|
||||
<!-- android:dependency="pref_timeout_passphrase" />-->
|
||||
|
||||
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
|
||||
android:defaultValue="true"
|
||||
|
Loading…
Reference in New Issue
Block a user