From 12385b9c5a84798270f232d90b1d294bc0383fce Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Thu, 8 Feb 2024 12:53:22 -0500 Subject: [PATCH] Update stores to use a constant accountId for PNIs. --- .../changenumber/ChangeNumberRepository.kt | 1 + .../securesms/database/KyberPreKeyTable.kt | 29 ++++++++----- .../securesms/database/OneTimePreKeyTable.kt | 21 +++++++--- .../securesms/database/SignedPreKeyTable.kt | 18 ++++++-- .../helpers/SignalDatabaseMigrations.kt | 6 ++- .../helpers/migration/V219_PniPreKeyStores.kt | 41 +++++++++++++++++++ .../dependencies/ApplicationDependencies.java | 6 +++ .../protocol/BufferedSignedPreKeyStore.kt | 2 +- 8 files changed, 101 insertions(+), 23 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V219_PniPreKeyStores.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRepository.kt index fe5ac7ca69..4210b8c44e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/settings/app/changenumber/ChangeNumberRepository.kt @@ -233,6 +233,7 @@ class ChangeNumberRepository( SignalStore.account().setE164(e164) SignalStore.account().setPni(pni) + ApplicationDependencies.resetProtocolStores() ApplicationDependencies.getGroupsV2Authorization().clear() diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/KyberPreKeyTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/KyberPreKeyTable.kt index 22da29809d..558ba3e241 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/KyberPreKeyTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/KyberPreKeyTable.kt @@ -49,13 +49,15 @@ class KyberPreKeyTable(context: Context, databaseHelper: SignalDatabase) : Datab val CREATE_INDEXES = arrayOf( "CREATE INDEX IF NOT EXISTS $INDEX_ACCOUNT_KEY ON $TABLE_NAME ($ACCOUNT_ID, $KEY_ID, $LAST_RESORT, $SERIALIZED)" ) + + const val PNI_ACCOUNT_ID = "PNI" } fun get(serviceId: ServiceId, keyId: Int): KyberPreKey? { return readableDatabase .select(LAST_RESORT, SERIALIZED) .from("$TABLE_NAME INDEXED BY $INDEX_ACCOUNT_KEY") - .where("$ACCOUNT_ID = ? AND $KEY_ID = ?", serviceId, keyId) + .where("$ACCOUNT_ID = ? AND $KEY_ID = ?", serviceId.toAccountId(), keyId) .run() .readToSingleObject { cursor -> KyberPreKey( @@ -69,7 +71,7 @@ class KyberPreKeyTable(context: Context, databaseHelper: SignalDatabase) : Datab return readableDatabase .select(LAST_RESORT, SERIALIZED) .from("$TABLE_NAME INDEXED BY $INDEX_ACCOUNT_KEY") - .where("$ACCOUNT_ID = ?", serviceId) + .where("$ACCOUNT_ID = ?", serviceId.toAccountId()) .run() .readToList { cursor -> KyberPreKey( @@ -83,7 +85,7 @@ class KyberPreKeyTable(context: Context, databaseHelper: SignalDatabase) : Datab return readableDatabase .select(LAST_RESORT, SERIALIZED) .from("$TABLE_NAME INDEXED BY $INDEX_ACCOUNT_KEY") - .where("$ACCOUNT_ID = ? AND $LAST_RESORT = ?", serviceId, 1) + .where("$ACCOUNT_ID = ? AND $LAST_RESORT = ?", serviceId.toAccountId(), 1) .run() .readToList { cursor -> KyberPreKey( @@ -96,7 +98,7 @@ class KyberPreKeyTable(context: Context, databaseHelper: SignalDatabase) : Datab fun contains(serviceId: ServiceId, keyId: Int): Boolean { return readableDatabase .exists("$TABLE_NAME INDEXED BY $INDEX_ACCOUNT_KEY") - .where("$ACCOUNT_ID = ? AND $KEY_ID = ?", serviceId, keyId) + .where("$ACCOUNT_ID = ? AND $KEY_ID = ?", serviceId.toAccountId(), keyId) .run() } @@ -104,7 +106,7 @@ class KyberPreKeyTable(context: Context, databaseHelper: SignalDatabase) : Datab writableDatabase .insertInto(TABLE_NAME) .values( - ACCOUNT_ID to serviceId.toString(), + ACCOUNT_ID to serviceId.toAccountId(), KEY_ID to keyId, TIMESTAMP to record.timestamp, SERIALIZED to record.serialize(), @@ -116,14 +118,14 @@ class KyberPreKeyTable(context: Context, databaseHelper: SignalDatabase) : Datab fun deleteIfNotLastResort(serviceId: ServiceId, keyId: Int) { writableDatabase .delete("$TABLE_NAME INDEXED BY $INDEX_ACCOUNT_KEY") - .where("$ACCOUNT_ID = ? AND $KEY_ID = ? AND $LAST_RESORT = ?", serviceId, keyId, 0) + .where("$ACCOUNT_ID = ? AND $KEY_ID = ? AND $LAST_RESORT = ?", serviceId.toAccountId(), keyId, 0) .run() } fun delete(serviceId: ServiceId, keyId: Int) { writableDatabase .delete("$TABLE_NAME INDEXED BY $INDEX_ACCOUNT_KEY") - .where("$ACCOUNT_ID = ? AND $KEY_ID = ?", serviceId, keyId) + .where("$ACCOUNT_ID = ? AND $KEY_ID = ?", serviceId.toAccountId(), keyId) .run() } @@ -131,7 +133,7 @@ class KyberPreKeyTable(context: Context, databaseHelper: SignalDatabase) : Datab writableDatabase .update(TABLE_NAME) .values(STALE_TIMESTAMP to staleTime) - .where("$ACCOUNT_ID = ? AND $STALE_TIMESTAMP = 0 AND $LAST_RESORT = 0", serviceId) + .where("$ACCOUNT_ID = ? AND $STALE_TIMESTAMP = 0 AND $LAST_RESORT = 0", serviceId.toAccountId()) .run() } @@ -161,8 +163,8 @@ class KyberPreKeyTable(context: Context, databaseHelper: SignalDatabase) : Datab LIMIT $minCount ) """, - serviceId, - serviceId + serviceId.toAccountId(), + serviceId.toAccountId() ) .run() @@ -173,4 +175,11 @@ class KyberPreKeyTable(context: Context, databaseHelper: SignalDatabase) : Datab val record: KyberPreKeyRecord, val lastResort: Boolean ) + + private fun ServiceId.toAccountId(): String { + return when (this) { + is ServiceId.ACI -> this.toString() + is ServiceId.PNI -> PNI_ACCOUNT_ID + } + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/OneTimePreKeyTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/OneTimePreKeyTable.kt index c40a4eb8ab..1cac549f05 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/OneTimePreKeyTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/OneTimePreKeyTable.kt @@ -38,10 +38,12 @@ class OneTimePreKeyTable(context: Context, databaseHelper: SignalDatabase) : Dat UNIQUE($ACCOUNT_ID, $KEY_ID) ) """ + + const val PNI_ACCOUNT_ID = "PNI" } fun get(serviceId: ServiceId, keyId: Int): PreKeyRecord? { - readableDatabase.query(TABLE_NAME, null, "$ACCOUNT_ID = ? AND $KEY_ID = ?", SqlUtil.buildArgs(serviceId, keyId), null, null, null).use { cursor -> + readableDatabase.query(TABLE_NAME, null, "$ACCOUNT_ID = ? AND $KEY_ID = ?", SqlUtil.buildArgs(serviceId.toAccountId(), keyId), null, null, null).use { cursor -> if (cursor.moveToFirst()) { try { val publicKey = Curve.decodePoint(Base64.decode(cursor.requireNonNullString(PUBLIC_KEY)), 0) @@ -60,7 +62,7 @@ class OneTimePreKeyTable(context: Context, databaseHelper: SignalDatabase) : Dat fun insert(serviceId: ServiceId, keyId: Int, record: PreKeyRecord) { val contentValues = contentValuesOf( - ACCOUNT_ID to serviceId.toString(), + ACCOUNT_ID to serviceId.toAccountId(), KEY_ID to keyId, PUBLIC_KEY to Base64.encodeWithPadding(record.keyPair.publicKey.serialize()), PRIVATE_KEY to Base64.encodeWithPadding(record.keyPair.privateKey.serialize()) @@ -71,14 +73,14 @@ class OneTimePreKeyTable(context: Context, databaseHelper: SignalDatabase) : Dat fun delete(serviceId: ServiceId, keyId: Int) { val database = databaseHelper.signalWritableDatabase - database.delete(TABLE_NAME, "$ACCOUNT_ID = ? AND $KEY_ID = ?", SqlUtil.buildArgs(serviceId, keyId)) + database.delete(TABLE_NAME, "$ACCOUNT_ID = ? AND $KEY_ID = ?", SqlUtil.buildArgs(serviceId.toAccountId(), keyId)) } fun markAllStaleIfNecessary(serviceId: ServiceId, staleTime: Long) { writableDatabase .update(TABLE_NAME) .values(STALE_TIMESTAMP to staleTime) - .where("$ACCOUNT_ID = ? AND $STALE_TIMESTAMP = 0", serviceId) + .where("$ACCOUNT_ID = ? AND $STALE_TIMESTAMP = 0", serviceId.toAccountId()) .run() } @@ -105,11 +107,18 @@ class OneTimePreKeyTable(context: Context, databaseHelper: SignalDatabase) : Dat LIMIT $minCount ) """, - serviceId, - serviceId + serviceId.toAccountId(), + serviceId.toAccountId() ) .run() Log.i(TAG, "Deleted $count stale one-time EC prekeys.") } + + private fun ServiceId.toAccountId(): String { + return when (this) { + is ServiceId.ACI -> this.toString() + is ServiceId.PNI -> PNI_ACCOUNT_ID + } + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SignedPreKeyTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/SignedPreKeyTable.kt index 01d64f2b9d..3396b316dc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SignedPreKeyTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SignedPreKeyTable.kt @@ -28,6 +28,7 @@ class SignedPreKeyTable(context: Context, databaseHelper: SignalDatabase) : Data const val PRIVATE_KEY = "private_key" const val SIGNATURE = "signature" const val TIMESTAMP = "timestamp" + const val CREATE_TABLE = """ CREATE TABLE $TABLE_NAME ( $ID INTEGER PRIMARY KEY, @@ -40,10 +41,12 @@ class SignedPreKeyTable(context: Context, databaseHelper: SignalDatabase) : Data UNIQUE($ACCOUNT_ID, $KEY_ID) ) """ + + const val PNI_ACCOUNT_ID = "PNI" } fun get(serviceId: ServiceId, keyId: Int): SignedPreKeyRecord? { - readableDatabase.query(TABLE_NAME, null, "$ACCOUNT_ID = ? AND $KEY_ID = ?", SqlUtil.buildArgs(serviceId, keyId), null, null, null).use { cursor -> + readableDatabase.query(TABLE_NAME, null, "$ACCOUNT_ID = ? AND $KEY_ID = ?", SqlUtil.buildArgs(serviceId.toAccountId(), keyId), null, null, null).use { cursor -> if (cursor.moveToFirst()) { try { val publicKey = Curve.decodePoint(Base64.decode(cursor.requireNonNullString(PUBLIC_KEY)), 0) @@ -64,7 +67,7 @@ class SignedPreKeyTable(context: Context, databaseHelper: SignalDatabase) : Data fun getAll(serviceId: ServiceId): List { val results: MutableList = LinkedList() - readableDatabase.query(TABLE_NAME, null, "$ACCOUNT_ID = ?", SqlUtil.buildArgs(serviceId), null, null, null).use { cursor -> + readableDatabase.query(TABLE_NAME, null, "$ACCOUNT_ID = ?", SqlUtil.buildArgs(serviceId.toAccountId()), null, null, null).use { cursor -> while (cursor.moveToNext()) { try { val keyId = cursor.requireInt(KEY_ID) @@ -86,7 +89,7 @@ class SignedPreKeyTable(context: Context, databaseHelper: SignalDatabase) : Data fun insert(serviceId: ServiceId, keyId: Int, record: SignedPreKeyRecord) { val contentValues = contentValuesOf( - ACCOUNT_ID to serviceId.toString(), + ACCOUNT_ID to serviceId.toAccountId(), KEY_ID to keyId, PUBLIC_KEY to Base64.encodeWithPadding(record.keyPair.publicKey.serialize()), PRIVATE_KEY to Base64.encodeWithPadding(record.keyPair.privateKey.serialize()), @@ -97,6 +100,13 @@ class SignedPreKeyTable(context: Context, databaseHelper: SignalDatabase) : Data } fun delete(serviceId: ServiceId, keyId: Int) { - writableDatabase.delete(TABLE_NAME, "$ACCOUNT_ID = ? AND $KEY_ID = ?", SqlUtil.buildArgs(serviceId, keyId)) + writableDatabase.delete(TABLE_NAME, "$ACCOUNT_ID = ? AND $KEY_ID = ?", SqlUtil.buildArgs(serviceId.toAccountId(), keyId)) + } + + private fun ServiceId.toAccountId(): String { + return when (this) { + is ServiceId.ACI -> this.toString() + is ServiceId.PNI -> PNI_ACCOUNT_ID + } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt index 7a7c16578c..528ce3129a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt @@ -76,6 +76,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V215_RemoveAttachme import org.thoughtcrime.securesms.database.helpers.migration.V216_PhoneNumberDiscoverable import org.thoughtcrime.securesms.database.helpers.migration.V217_MessageTableExtrasColumn import org.thoughtcrime.securesms.database.helpers.migration.V218_RecipientPniSignatureVerified +import org.thoughtcrime.securesms.database.helpers.migration.V219_PniPreKeyStores /** * Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness. @@ -154,10 +155,11 @@ object SignalDatabaseMigrations { 215 to V215_RemoveAttachmentUniqueId, 216 to V216_PhoneNumberDiscoverable, 217 to V217_MessageTableExtrasColumn, - 218 to V218_RecipientPniSignatureVerified + 218 to V218_RecipientPniSignatureVerified, + 219 to V219_PniPreKeyStores ) - const val DATABASE_VERSION = 218 + const val DATABASE_VERSION = 219 @JvmStatic fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V219_PniPreKeyStores.kt b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V219_PniPreKeyStores.kt new file mode 100644 index 0000000000..6fe2802e8e --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V219_PniPreKeyStores.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2024 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.database.helpers.migration + +import android.app.Application +import net.zetetic.database.sqlcipher.SQLiteDatabase + +/** + * Changes our PNI prekey stores to use a constant indicating it's for a PNI rather than the specific PNI. + */ +@Suppress("ClassName") +object V219_PniPreKeyStores : SignalDatabaseMigration { + override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { + db.execSQL( + """ + UPDATE one_time_prekeys + SET account_id = "PNI" + WHERE account_id LIKE "PNI:%" + """ + ) + + db.execSQL( + """ + UPDATE signed_prekeys + SET account_id = "PNI" + WHERE account_id LIKE "PNI:%" + """ + ) + + db.execSQL( + """ + UPDATE kyber_prekey + SET account_id = "PNI" + WHERE account_id LIKE "PNI:%" + """ + ) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java index c41060c0e0..446d35b4ea 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/ApplicationDependencies.java @@ -589,6 +589,12 @@ public class ApplicationDependencies { return protocolStore; } + public static void resetProtocolStores() { + synchronized (LOCK) { + protocolStore = null; + } + } + public static @NonNull GiphyMp4Cache getGiphyMp4Cache() { if (giphyMp4Cache == null) { synchronized (LOCK) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/protocol/BufferedSignedPreKeyStore.kt b/app/src/main/java/org/thoughtcrime/securesms/messages/protocol/BufferedSignedPreKeyStore.kt index b42a52075e..11ef5ce026 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/protocol/BufferedSignedPreKeyStore.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/protocol/BufferedSignedPreKeyStore.kt @@ -24,7 +24,7 @@ class BufferedSignedPreKeyStore(private val selfServiceId: ServiceId) : SignedPr @kotlin.jvm.Throws(InvalidKeyIdException::class) override fun loadSignedPreKey(id: Int): SignedPreKeyRecord { return store.computeIfAbsent(id) { - SignalDatabase.signedPreKeys.get(selfServiceId, id) ?: throw InvalidKeyIdException("Missing one-time prekey with ID: $id") + SignalDatabase.signedPreKeys.get(selfServiceId, id) ?: throw InvalidKeyIdException("Missing signed prekey with ID: $id") } }