another.im-ios/ConversationsClassic/AppData/Client/Client+MartinOMEMO.swift

519 lines
18 KiB
Swift
Raw Normal View History

2024-08-27 13:00:39 +00:00
import Foundation
import GRDB
import Martin
import MartinOMEMO
final class ClientMartinOMEMO {
let credentials: Credentials
2024-08-31 10:10:25 +00:00
private let queue = DispatchQueue(label: "SignalPreKeyRemovalQueue")
private var preKeysMarkedForRemoval: [UInt32] = []
2024-08-27 13:00:39 +00:00
init(_ credentials: Credentials) {
self.credentials = credentials
2024-08-31 12:06:14 +00:00
print("ClientMartinOMEMO init")
}
deinit {
print("ClientMartinOMEMO deinit")
2024-08-27 13:00:39 +00:00
}
var signal: (SignalStorage, SignalContext) {
let signalStorage = SignalStorage(sessionStore: self, preKeyStore: self, signedPreKeyStore: self, identityKeyStore: self, senderKeyStore: self)
// swiftlint:disable:next force_unwrapping
let signalContext = SignalContext(withStorage: signalStorage)!
signalStorage.setup(withContext: signalContext)
2024-08-31 12:35:04 +00:00
_ = regenerateKeys(wipe: false, context: signalContext)
2024-08-27 13:00:39 +00:00
return (signalStorage, signalContext)
}
2024-08-31 12:06:14 +00:00
2024-08-31 12:35:04 +00:00
private func regenerateKeys(wipe: Bool = false, context: SignalContext) -> Bool {
2024-08-31 12:06:14 +00:00
if wipe {
OMEMOSession.wipe(account: credentials.bareJid)
OMEMOPreKey.wipe(account: credentials.bareJid)
OMEMOSignedPreKey.wipe(account: credentials.bareJid)
OMEMOIdentity.wipe(account: credentials.bareJid)
2024-08-31 12:35:04 +00:00
Settings.getFor(credentials.bareJid)?.wipeOmemoRegId()
2024-08-31 12:06:14 +00:00
}
let hasKeyPair = keyPair() != nil
if wipe || localRegistrationId() == 0 || !hasKeyPair {
2024-08-31 12:56:36 +00:00
let regId = context.generateRegistrationId()
let address = SignalAddress(name: credentials.bareJid, deviceId: Int32(regId))
2024-08-31 12:35:04 +00:00
var settings = Settings.getFor(credentials.bareJid)
settings?.omemoRegId = Int(regId)
settings?.save()
2024-08-31 12:56:36 +00:00
guard let keyPair = SignalIdentityKeyPair.generateKeyPair(context: context), let publicKey = keyPair.publicKey else {
return false
}
let fingerprint = publicKey.map { byte -> String in
String(format: "%02x", byte)
}.joined()
2024-08-31 13:23:21 +00:00
return save(address: address, fingerprint: fingerprint, own: true, data: keyPair.serialized())
}
return true
}
2024-08-31 12:56:36 +00:00
2024-08-31 13:23:21 +00:00
private func save(address: SignalAddress, fingerprint: String, own: Bool, data: Data) -> Bool {
guard !OMEMOIdentity.existsFor(account: credentials.bareJid, name: address.name, fingerprint: fingerprint) else {
return false
}
do {
_ = try Database.shared.dbQueue.write { db in
try OMEMOIdentity(
account: credentials.bareJid,
name: address.name,
deviceId: Int(address.deviceId),
fingerprint: fingerprint,
key: data,
own: own,
status: MartinOMEMO.IdentityStatus.trustedActive.rawValue
)
.insert(db)
2024-08-31 12:56:36 +00:00
}
2024-08-31 13:23:21 +00:00
return true
} catch {
logIt(.error, "Error storing identity key: \(error.localizedDescription)")
return false
2024-08-31 12:06:14 +00:00
}
}
2024-08-27 13:00:39 +00:00
}
2024-08-27 14:57:30 +00:00
// MARK: - Session
extension ClientMartinOMEMO: SignalSessionStoreProtocol {
2024-08-27 13:00:39 +00:00
func sessionRecord(forAddress address: MartinOMEMO.SignalAddress) -> Data? {
2024-08-31 10:59:18 +00:00
if let key = OMEMOSession.keyFor(account: credentials.bareJid, name: address.name, deviceId: address.deviceId) {
return Data(base64Encoded: key)
} else {
2024-08-27 14:57:30 +00:00
return nil
}
2024-08-27 13:00:39 +00:00
}
2024-08-27 14:57:30 +00:00
func allDevices(for name: String, activeAndTrusted: Bool) -> [Int32] {
2024-08-31 10:59:18 +00:00
activeAndTrusted ?
OMEMOSession.trustedDevicesIdsFor(account: credentials.bareJid, name: name) :
OMEMOSession.devicesIdsFor(account: credentials.bareJid, name: name)
2024-08-27 13:00:39 +00:00
}
func storeSessionRecord(_ data: Data, forAddress: MartinOMEMO.SignalAddress) -> Bool {
2024-08-27 14:57:30 +00:00
do {
try Database.shared.dbQueue.write { db in
2024-08-31 10:59:18 +00:00
try OMEMOSession(
account: credentials.bareJid,
name: forAddress.name,
deviceId: Int(forAddress.deviceId),
key: data.base64EncodedString()
2024-08-27 14:57:30 +00:00
)
2024-08-31 10:59:18 +00:00
.insert(db)
2024-08-27 14:57:30 +00:00
}
return true
} catch {
2024-08-31 10:59:18 +00:00
logIt(.error, "Error storing session info: \(error.localizedDescription)")
2024-08-27 14:57:30 +00:00
return false
}
2024-08-27 13:00:39 +00:00
}
func containsSessionRecord(forAddress: MartinOMEMO.SignalAddress) -> Bool {
2024-08-31 10:59:18 +00:00
OMEMOSession.keyFor(account: credentials.bareJid, name: forAddress.name, deviceId: forAddress.deviceId) != nil
2024-08-27 13:00:39 +00:00
}
func deleteSessionRecord(forAddress: MartinOMEMO.SignalAddress) -> Bool {
2024-08-27 14:57:30 +00:00
do {
2024-08-31 10:59:18 +00:00
_ = try Database.shared.dbQueue.write { db in
try OMEMOSession
.filter(Column("account") == credentials.bareJid)
.filter(Column("name") == forAddress.name)
.filter(Column("deviceId") == forAddress.deviceId)
.deleteAll(db)
2024-08-27 14:57:30 +00:00
}
return true
} catch {
logIt(.error, "Error fetching chats: \(error.localizedDescription)")
return false
}
2024-08-27 13:00:39 +00:00
}
2024-08-27 14:57:30 +00:00
func deleteAllSessions(for name: String) -> Bool {
do {
2024-08-31 10:59:18 +00:00
_ = try Database.shared.dbQueue.write { db in
try OMEMOSession
.filter(Column("account") == credentials.bareJid)
.filter(Column("name") == name)
.deleteAll(db)
2024-08-27 14:57:30 +00:00
}
return true
} catch {
logIt(.error, "Error fetching chats: \(error.localizedDescription)")
return false
}
2024-08-27 13:00:39 +00:00
}
2024-08-27 14:57:30 +00:00
func sessionsWipe() {
do {
2024-08-31 10:59:18 +00:00
_ = try Database.shared.dbQueue.write { db in
try OMEMOSession
.filter(Column("account") == credentials.bareJid)
.deleteAll(db)
2024-08-27 14:57:30 +00:00
}
} catch {
logIt(.error, "Error fetching chats: \(error.localizedDescription)")
}
}
}
2024-08-31 12:06:14 +00:00
// MARK: - Identity
extension ClientMartinOMEMO: SignalIdentityKeyStoreProtocol {
func keyPair() -> (any MartinOMEMO.SignalIdentityKeyPairProtocol)? {
2024-08-31 12:35:04 +00:00
let deviceId = localRegistrationId()
guard deviceId != 0 else {
return nil
}
2024-08-31 13:00:16 +00:00
do {
let record = try Database.shared.dbQueue.read { db in
try OMEMOIdentity
.filter(Column("account") == credentials.bareJid)
.filter(Column("name") == credentials.bareJid)
.filter(Column("deviceId") == deviceId)
.fetchOne(db)
}
guard let key = record?.key else {
return nil
}
return SignalIdentityKeyPair(fromKeyPairData: key)
} catch {
return nil
}
2024-08-31 12:06:14 +00:00
}
func localRegistrationId() -> UInt32 {
2024-08-31 12:35:04 +00:00
if let settings = Settings.getFor(credentials.bareJid) {
return UInt32(settings.omemoRegId)
} else {
return 0
}
2024-08-31 12:06:14 +00:00
}
func save(identity: MartinOMEMO.SignalAddress, key: (any MartinOMEMO.SignalIdentityKeyProtocol)?) -> Bool {
2024-08-31 13:23:21 +00:00
guard let key = key as SignalIdentityKeyProtocol?, let publicKey = key.publicKey else {
return false
}
let fingerprint = publicKey.map { byte -> String in
String(format: "%02x", byte)
}.joined()
2024-08-31 13:49:54 +00:00
defer {
_ = self.setStatus(.verifiedActive, forIdentity: identity)
}
2024-08-31 13:23:21 +00:00
return save(address: identity, fingerprint: fingerprint, own: true, data: key.serialized())
2024-08-31 12:06:14 +00:00
}
func save(identity: MartinOMEMO.SignalAddress, publicKeyData: Data?) -> Bool {
2024-08-31 13:23:21 +00:00
guard let publicKeyData = publicKeyData else {
return false
}
let fingerprint = publicKeyData.map { byte -> String in
String(format: "%02x", byte)
}.joined()
return save(address: identity, fingerprint: fingerprint, own: false, data: publicKeyData)
2024-08-31 12:06:14 +00:00
}
func isTrusted(identity _: MartinOMEMO.SignalAddress, key _: (any MartinOMEMO.SignalIdentityKeyProtocol)?) -> Bool {
true
}
func isTrusted(identity _: MartinOMEMO.SignalAddress, publicKeyData _: Data?) -> Bool {
true
}
func setStatus(_ status: MartinOMEMO.IdentityStatus, forIdentity: MartinOMEMO.SignalAddress) -> Bool {
2024-08-31 13:49:54 +00:00
if let identity = OMEMOIdentity.getFor(account: credentials.bareJid, name: forIdentity.name, deviceId: forIdentity.deviceId) {
return identity.updateStatus(status.rawValue)
} else {
return false
}
2024-08-31 12:06:14 +00:00
}
func setStatus(active: Bool, forIdentity: MartinOMEMO.SignalAddress) -> Bool {
2024-08-31 13:49:54 +00:00
if let identity = OMEMOIdentity.getFor(account: credentials.bareJid, name: forIdentity.name, deviceId: forIdentity.deviceId) {
let status = IdentityStatus(rawValue: identity.status) ?? .undecidedActive
return identity.updateStatus(active ? status.toActive().rawValue : status.toInactive().rawValue)
} else {
return false
}
2024-08-31 12:06:14 +00:00
}
func identities(forName name: String) -> [MartinOMEMO.Identity] {
2024-08-31 13:49:54 +00:00
OMEMOIdentity.getAllFor(account: credentials.bareJid, name: name)
.compactMap { identity in
guard let status = IdentityStatus(rawValue: identity.status) else {
2024-08-31 12:06:14 +00:00
return nil
}
2024-08-31 13:49:54 +00:00
return MartinOMEMO.Identity(
address: MartinOMEMO.SignalAddress(name: identity.name, deviceId: Int32(identity.deviceId)),
status: status,
fingerprint: identity.fingerprint,
key: identity.key,
own: identity.own
)
2024-08-31 12:06:14 +00:00
}
}
func identityFingerprint(forAddress address: MartinOMEMO.SignalAddress) -> String? {
2024-08-31 13:49:54 +00:00
OMEMOIdentity.getFor(account: credentials.bareJid, name: address.name, deviceId: address.deviceId)?.fingerprint
2024-08-31 12:06:14 +00:00
}
}
2024-08-27 14:57:30 +00:00
// MARK: - PreKey
extension ClientMartinOMEMO: SignalPreKeyStoreProtocol {
2024-08-27 13:00:39 +00:00
func currentPreKeyId() -> UInt32 {
2024-08-27 15:04:20 +00:00
do {
let data = try Database.shared.dbQueue.read { db in
try Row.fetchOne(
db,
sql: "SELECT max(id) FROM omemo_pre_keys WHERE account = :account",
arguments: ["account": credentials.bareJid]
)
}
return data?["id"] ?? 0
} catch {
logIt(.error, "Error fetching chats: \(error.localizedDescription)")
return 0
}
2024-08-27 13:00:39 +00:00
}
func loadPreKey(withId: UInt32) -> Data? {
2024-08-27 15:04:20 +00:00
do {
let data = try Database.shared.dbQueue.read { db in
try Row.fetchOne(
db,
sql: "SELECT key FROM omemo_pre_keys WHERE account = :account AND id = :id",
arguments: ["account": credentials.bareJid, "id": withId]
)
}
return data?["key"]
} catch {
logIt(.error, "Error fetching chats: \(error.localizedDescription)")
return nil
}
2024-08-27 13:00:39 +00:00
}
func storePreKey(_ data: Data, withId: UInt32) -> Bool {
2024-08-27 15:04:20 +00:00
do {
try Database.shared.dbQueue.write { db in
try db.execute(
sql: "INSERT INTO omemo_pre_keys (account, id, key) VALUES (:account, :id, :key)",
arguments: ["account": credentials.bareJid, "id": withId, "key": data]
)
}
return true
} catch {
logIt(.error, "Error fetching chats: \(error.localizedDescription)")
return false
}
2024-08-27 13:00:39 +00:00
}
func containsPreKey(withId: UInt32) -> Bool {
2024-08-27 15:04:20 +00:00
do {
let rec = try Database.shared.dbQueue.read { db in
try Row.fetchOne(
db,
sql: "SELECT key FROM omemo_pre_keys WHERE account = :account AND id = :id",
arguments: ["account": credentials.bareJid, "id": withId]
)
}
return rec != nil
} catch {
logIt(.error, "Error fetching chats: \(error.localizedDescription)")
return false
}
2024-08-27 13:00:39 +00:00
}
func deletePreKey(withId: UInt32) -> Bool {
2024-08-31 10:10:25 +00:00
queue.async {
print("queueing prekey with id \(withId) for removal..")
self.preKeysMarkedForRemoval.append(withId)
2024-08-27 15:04:20 +00:00
}
2024-08-31 10:10:25 +00:00
return true
2024-08-27 13:00:39 +00:00
}
2024-08-27 15:04:20 +00:00
// TODO: Check logic of this function carefully!!!
2024-08-27 13:00:39 +00:00
func flushDeletedPreKeys() -> Bool {
2024-08-31 10:10:25 +00:00
false
// !queue.sync { () -> [UInt32] in
// defer {
// preKeysMarkedForRemoval.removeAll()
// }
// print("removing queued prekeys: \(preKeysMarkedForRemoval)")
// do {
// Database.shared.dbQueue.write { db in
// try db.execute(
// sql: "DETLETE FROM omemo_pre_keys WHERE account = :account AND id IN (:ids)",
// arguments: ["account": credentials.bareJid, "ids": preKeysMarkedForRemoval]
// )
// }
// } catch {
// logIt(.error, "Error fetching chats: \(error.localizedDescription)")
// return [0]
// }
//
// // return preKeysMarkedForRemoval.filter { id in DBOMEMOStore.instance.deletePreKey(forAccount: context!.sessionObject.userBareJid!, withId: id) }
// }.isEmpty
//
//
//
// do {
// try Database.shared.dbQueue.write { db in
// try db.execute(
// sql: """
// DELETE FROM omemo_pre_keys
// WHERE account = :account
// AND id IN (
// SELECT id
// FROM omemo_pre_keys
// WHERE account = :account
// AND id NOT IN (
// SELECT id
// FROM omemo_pre_keys
// WHERE account = :account
// ORDER BY id DESC
// LIMIT 100)
// )
// """,
// arguments: ["account": credentials.bareJid]
// )
// }
// return true
// } catch {
// logIt(.error, "Error fetching chats: \(error.localizedDescription)")
// return false
// }
2024-08-27 15:04:20 +00:00
}
func preKeysWipe() {
do {
try Database.shared.dbQueue.write { db in
try db.execute(
sql: "DELETE FROM omemo_pre_keys WHERE account = :account",
arguments: ["account": credentials.bareJid]
)
}
} catch {
logIt(.error, "Error fetching chats: \(error.localizedDescription)")
}
2024-08-27 13:00:39 +00:00
}
2024-08-27 14:57:30 +00:00
}
2024-08-27 13:00:39 +00:00
2024-08-27 14:57:30 +00:00
// MARK: - SignedPreKey
extension ClientMartinOMEMO: SignalSignedPreKeyStoreProtocol {
2024-08-27 13:00:39 +00:00
func countSignedPreKeys() -> Int {
2024-08-27 15:28:29 +00:00
do {
let data = try Database.shared.dbQueue.read { db in
try Row.fetchOne(
db,
sql: "SELECT count(1) FROM omemo_signed_pre_keys WHERE account = :account",
arguments: ["account": credentials.bareJid]
)
}
return data?["count(1)"] ?? 0
} catch {
logIt(.error, "Error fetching chats: \(error.localizedDescription)")
return 0
}
2024-08-27 13:00:39 +00:00
}
func loadSignedPreKey(withId: UInt32) -> Data? {
2024-08-27 15:28:29 +00:00
do {
let data = try Database.shared.dbQueue.read { db in
try Row.fetchOne(
db,
sql: "SELECT key FROM omemo_signed_pre_keys WHERE account = :account AND id = :id",
arguments: ["account": credentials.bareJid, "id": withId]
)
}
return data?["key"]
} catch {
logIt(.error, "Error fetching chats: \(error.localizedDescription)")
return nil
}
2024-08-27 13:00:39 +00:00
}
func storeSignedPreKey(_ data: Data, withId: UInt32) -> Bool {
2024-08-27 15:28:29 +00:00
do {
try Database.shared.dbQueue.write { db in
try db.execute(
sql: "INSERT INTO omemo_signed_pre_keys (account, id, key) VALUES (:account, :id, :key)",
arguments: ["account": credentials.bareJid, "id": withId, "key": data]
)
}
return true
} catch {
logIt(.error, "Error fetching chats: \(error.localizedDescription)")
return false
}
2024-08-27 13:00:39 +00:00
}
func containsSignedPreKey(withId: UInt32) -> Bool {
2024-08-27 15:28:29 +00:00
do {
let rec = try Database.shared.dbQueue.read { db in
try Row.fetchOne(
db,
sql: "SELECT key FROM omemo_signed_pre_keys WHERE account = :account AND id = :id",
arguments: ["account": credentials.bareJid, "id": withId]
)
}
return rec != nil
} catch {
logIt(.error, "Error fetching chats: \(error.localizedDescription)")
return false
}
2024-08-27 13:00:39 +00:00
}
func deleteSignedPreKey(withId: UInt32) -> Bool {
2024-08-27 15:28:29 +00:00
do {
try Database.shared.dbQueue.write { db in
try db.execute(
sql: "DELETE FROM omemo_signed_pre_keys WHERE account = :account AND id = :id",
arguments: ["account": credentials.bareJid, "id": withId]
)
}
return true
} catch {
logIt(.error, "Error fetching chats: \(error.localizedDescription)")
return false
}
}
func wipeSignedPreKeys() {
do {
try Database.shared.dbQueue.write { db in
try db.execute(
sql: "DELETE FROM omemo_signed_pre_keys WHERE account = :account",
arguments: ["account": credentials.bareJid]
)
}
} catch {
logIt(.error, "Error fetching chats: \(error.localizedDescription)")
}
2024-08-27 13:00:39 +00:00
}
2024-08-27 14:57:30 +00:00
}
2024-08-27 13:00:39 +00:00
2024-08-27 14:57:30 +00:00
// MARK: - SenderKey
extension ClientMartinOMEMO: SignalSenderKeyStoreProtocol {
2024-08-31 09:55:57 +00:00
func storeSenderKey(_: Data, address _: MartinOMEMO.SignalAddress?, groupId _: String?) -> Bool {
false
2024-08-27 13:00:39 +00:00
}
2024-08-31 09:55:57 +00:00
func loadSenderKey(forAddress _: MartinOMEMO.SignalAddress?, groupId _: String?) -> Data? {
nil
2024-08-27 13:00:39 +00:00
}
}