That fuck shit the fascists are using
at master 269 lines 10 kB view raw
1/* 2 * Copyright (C) 2011 Whisper Systems 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17package org.tm.archive.database 18 19import android.content.Context 20import androidx.core.content.contentValuesOf 21import org.greenrobot.eventbus.EventBus 22import org.signal.core.util.Base64 23import org.signal.core.util.delete 24import org.signal.core.util.exists 25import org.signal.core.util.firstOrNull 26import org.signal.core.util.logging.Log 27import org.signal.core.util.requireBoolean 28import org.signal.core.util.requireInt 29import org.signal.core.util.requireLong 30import org.signal.core.util.requireNonNullString 31import org.signal.core.util.select 32import org.signal.core.util.toOptional 33import org.signal.core.util.update 34import org.signal.libsignal.protocol.IdentityKey 35import org.tm.archive.database.SignalDatabase.Companion.recipients 36import org.tm.archive.database.model.IdentityRecord 37import org.tm.archive.database.model.IdentityStoreRecord 38import org.tm.archive.dependencies.ApplicationDependencies 39import org.tm.archive.recipients.Recipient 40import org.tm.archive.recipients.RecipientId 41import org.tm.archive.storage.StorageSyncHelper 42import org.tm.archive.util.IdentityUtil 43import org.whispersystems.signalservice.api.push.ServiceId 44import org.whispersystems.signalservice.api.util.UuidUtil 45import java.lang.AssertionError 46import java.util.Optional 47 48class IdentityTable internal constructor(context: Context?, databaseHelper: SignalDatabase?) : DatabaseTable(context, databaseHelper) { 49 50 companion object { 51 private val TAG = Log.tag(IdentityTable::class.java) 52 const val TABLE_NAME = "identities" 53 private const val ID = "_id" 54 const val ADDRESS = "address" 55 const val IDENTITY_KEY = "identity_key" 56 private const val FIRST_USE = "first_use" 57 private const val TIMESTAMP = "timestamp" 58 const val VERIFIED = "verified" 59 private const val NONBLOCKING_APPROVAL = "nonblocking_approval" 60 const val CREATE_TABLE = """ 61 CREATE TABLE $TABLE_NAME ( 62 $ID INTEGER PRIMARY KEY AUTOINCREMENT, 63 $ADDRESS INTEGER UNIQUE, 64 $IDENTITY_KEY TEXT, 65 $FIRST_USE INTEGER DEFAULT 0, 66 $TIMESTAMP INTEGER DEFAULT 0, 67 $VERIFIED INTEGER DEFAULT 0, 68 $NONBLOCKING_APPROVAL INTEGER DEFAULT 0 69 ) 70 """ 71 } 72 73 fun getIdentityStoreRecord(serviceId: ServiceId?): IdentityStoreRecord? { 74 return if (serviceId != null) { 75 getIdentityStoreRecord(serviceId.toString()) 76 } else { 77 null 78 } 79 } 80 81 fun getIdentityStoreRecord(addressName: String): IdentityStoreRecord? { 82 readableDatabase 83 .select() 84 .from(TABLE_NAME) 85 .where("$ADDRESS = ?", addressName) 86 .run() 87 .use { cursor -> 88 if (cursor.moveToFirst()) { 89 return IdentityStoreRecord( 90 addressName = addressName, 91 identityKey = IdentityKey(Base64.decode(cursor.requireNonNullString(IDENTITY_KEY)), 0), 92 verifiedStatus = VerifiedStatus.forState(cursor.requireInt(VERIFIED)), 93 firstUse = cursor.requireBoolean(FIRST_USE), 94 timestamp = cursor.requireLong(TIMESTAMP), 95 nonblockingApproval = cursor.requireBoolean(NONBLOCKING_APPROVAL) 96 ) 97 } else if (UuidUtil.isUuid(addressName)) { 98 val byServiceId = recipients.getByServiceId(ServiceId.parseOrThrow(addressName)) 99 if (byServiceId.isPresent) { 100 val recipient = Recipient.resolved(byServiceId.get()) 101 if (recipient.hasE164() && !UuidUtil.isUuid(recipient.requireE164())) { 102 Log.i(TAG, "Could not find identity for UUID. Attempting E164.") 103 return getIdentityStoreRecord(recipient.requireE164()) 104 } else { 105 Log.i(TAG, "Could not find identity for UUID, and our recipient doesn't have an E164.") 106 } 107 } else { 108 Log.i(TAG, "Could not find identity for UUID, and we don't have a recipient.") 109 } 110 } else { 111 Log.i(TAG, "Could not find identity for E164 either.") 112 } 113 } 114 115 return null 116 } 117 118 fun saveIdentity( 119 addressName: String, 120 recipientId: RecipientId, 121 identityKey: IdentityKey, 122 verifiedStatus: VerifiedStatus, 123 firstUse: Boolean, 124 timestamp: Long, 125 nonBlockingApproval: Boolean 126 ) { 127 saveIdentityInternal(addressName, recipientId, identityKey, verifiedStatus, firstUse, timestamp, nonBlockingApproval) 128 recipients.markNeedsSync(recipientId) 129 StorageSyncHelper.scheduleSyncForDataChange() 130 } 131 132 fun setApproval(addressName: String, recipientId: RecipientId, nonBlockingApproval: Boolean) { 133 val updated = writableDatabase 134 .update(TABLE_NAME) 135 .values(NONBLOCKING_APPROVAL to nonBlockingApproval) 136 .where("$ADDRESS = ?", addressName) 137 .run() 138 139 if (updated > 0) { 140 recipients.markNeedsSync(recipientId) 141 StorageSyncHelper.scheduleSyncForDataChange() 142 } 143 } 144 145 fun setVerified(addressName: String, recipientId: RecipientId, identityKey: IdentityKey, verifiedStatus: VerifiedStatus) { 146 val updated = writableDatabase 147 .update(TABLE_NAME) 148 .values(VERIFIED to verifiedStatus.toInt()) 149 .where("$ADDRESS = ? AND $IDENTITY_KEY = ?", addressName, Base64.encodeWithPadding(identityKey.serialize())) 150 .run() 151 152 if (updated > 0) { 153 val record = getIdentityRecord(addressName) 154 if (record.isPresent) { 155 EventBus.getDefault().post(record.get()) 156 } 157 recipients.markNeedsSync(recipientId) 158 StorageSyncHelper.scheduleSyncForDataChange() 159 } 160 } 161 162 fun updateIdentityAfterSync(addressName: String, recipientId: RecipientId, identityKey: IdentityKey, verifiedStatus: VerifiedStatus) { 163 val existingRecord = getIdentityRecord(addressName) 164 val hadEntry = existingRecord.isPresent 165 val keyMatches = hasMatchingKey(addressName, identityKey) 166 val statusMatches = keyMatches && hasMatchingStatus(addressName, identityKey, verifiedStatus) 167 168 if (!keyMatches || !statusMatches) { 169 saveIdentityInternal(addressName, recipientId, identityKey, verifiedStatus, !hadEntry, System.currentTimeMillis(), nonBlockingApproval = true) 170 171 val record = getIdentityRecord(addressName) 172 if (record.isPresent) { 173 EventBus.getDefault().post(record.get()) 174 } 175 176 ApplicationDependencies.getProtocolStore().aci().identities().invalidate(addressName) 177 } 178 179 if (hadEntry && !keyMatches) { 180 Log.w(TAG, "Updated identity key during storage sync for " + addressName + " | Existing: " + existingRecord.get().identityKey.hashCode() + ", New: " + identityKey.hashCode()) 181 IdentityUtil.markIdentityUpdate(context, recipientId) 182 } 183 } 184 185 fun delete(addressName: String) { 186 writableDatabase 187 .delete(TABLE_NAME) 188 .where("$ADDRESS = ?", addressName) 189 .run() 190 } 191 192 private fun getIdentityRecord(addressName: String): Optional<IdentityRecord> { 193 return readableDatabase 194 .select() 195 .from(TABLE_NAME) 196 .where("$ADDRESS = ?", addressName) 197 .run() 198 .firstOrNull { cursor -> 199 IdentityRecord( 200 recipientId = RecipientId.fromSidOrE164(cursor.requireNonNullString(ADDRESS)), 201 identityKey = IdentityKey(Base64.decode(cursor.requireNonNullString(IDENTITY_KEY)), 0), 202 verifiedStatus = VerifiedStatus.forState(cursor.requireInt(VERIFIED)), 203 firstUse = cursor.requireBoolean(FIRST_USE), 204 timestamp = cursor.requireLong(TIMESTAMP), 205 nonblockingApproval = cursor.requireBoolean(NONBLOCKING_APPROVAL) 206 ) 207 } 208 .toOptional() 209 } 210 211 private fun hasMatchingKey(addressName: String, identityKey: IdentityKey): Boolean { 212 return readableDatabase 213 .exists(TABLE_NAME) 214 .where("$ADDRESS = ? AND $IDENTITY_KEY = ?", addressName, Base64.encodeWithPadding(identityKey.serialize())) 215 .run() 216 } 217 218 private fun hasMatchingStatus(addressName: String, identityKey: IdentityKey, verifiedStatus: VerifiedStatus): Boolean { 219 return readableDatabase 220 .exists(TABLE_NAME) 221 .where("$ADDRESS = ? AND $IDENTITY_KEY = ? AND $VERIFIED = ?", addressName, Base64.encodeWithPadding(identityKey.serialize()), verifiedStatus.toInt()) 222 .run() 223 } 224 225 private fun saveIdentityInternal( 226 addressName: String, 227 recipientId: RecipientId, 228 identityKey: IdentityKey, 229 verifiedStatus: VerifiedStatus, 230 firstUse: Boolean, 231 timestamp: Long, 232 nonBlockingApproval: Boolean 233 ) { 234 val contentValues = contentValuesOf( 235 ADDRESS to addressName, 236 IDENTITY_KEY to Base64.encodeWithPadding(identityKey.serialize()), 237 TIMESTAMP to timestamp, 238 VERIFIED to verifiedStatus.toInt(), 239 NONBLOCKING_APPROVAL to if (nonBlockingApproval) 1 else 0, 240 FIRST_USE to if (firstUse) 1 else 0 241 ) 242 writableDatabase.replace(TABLE_NAME, null, contentValues) 243 EventBus.getDefault().post(IdentityRecord(recipientId, identityKey, verifiedStatus, firstUse, timestamp, nonBlockingApproval)) 244 } 245 246 enum class VerifiedStatus { 247 DEFAULT, VERIFIED, UNVERIFIED; 248 249 fun toInt(): Int { 250 return when (this) { 251 DEFAULT -> 0 252 VERIFIED -> 1 253 UNVERIFIED -> 2 254 } 255 } 256 257 companion object { 258 @JvmStatic 259 fun forState(state: Int): VerifiedStatus { 260 return when (state) { 261 0 -> DEFAULT 262 1 -> VERIFIED 263 2 -> UNVERIFIED 264 else -> throw AssertionError("No such state: $state") 265 } 266 } 267 } 268 } 269}