That fuck shit the fascists are using
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}