You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
session-ios/SessionMessagingKit/LibSession/Config Handling/LibSession+GroupKeys.swift

182 lines
7.6 KiB
Swift

// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import GRDB
import SessionUtil
import SessionUtilitiesKit
// MARK: - Size Restrictions
public extension LibSession {
static var sizeAuthDataBytes: Int { 100 }
static var sizeSubaccountBytes: Int { 36 }
static var sizeSubaccountSigBytes: Int { 64 }
static var sizeSubaccountSignatureBytes: Int { 64 }
}
// MARK: - Group Keys Handling
internal extension LibSession {
/// `libSession` manages keys entirely so there is no need for a DB presence
static let columnsRelatedToGroupKeys: [ColumnExpression] = []
}
// MARK: - Incoming Changes
internal extension LibSessionCacheType {
func handleGroupKeysUpdate(
_ db: Database,
in config: LibSession.Config?,
groupSessionId: SessionId
) throws {
guard case .groupKeys(let conf, let infoConf, let membersConf) = config else {
throw LibSessionError.invalidConfigObject
}
/// If the group had been flagged as "expired" (because it got no config messages when initially polling) then receiving a config
/// message means the group is no longer expired, so update it's state
let groupFlaggedAsExpired: Bool = (try? ClosedGroup
.filter(id: groupSessionId.hexString)
.select(.expired)
.asRequest(of: Bool.self)
.fetchOne(db))
.defaulting(to: false)
if groupFlaggedAsExpired {
try ClosedGroup
.filter(id: groupSessionId.hexString)
.updateAllAndConfig(
db,
ClosedGroup.Columns.expired.set(to: false),
using: dependencies
)
}
/// If two admins rekeyed for different member changes at the same time then there is a "key collision" and the "needs rekey" function
/// will return true to indicate that a 3rd `rekey` needs to be made to have a final set of keys which includes all members
///
/// **Note:** We don't check `needsDump` in this case because the local state _could_ be persisted yet still require a `rekey`
/// so we should rely solely on `groups_keys_needs_rekey`
guard groups_keys_needs_rekey(conf) else { return }
// Performing a `rekey` returns the updated key data which we don't use directly, this updated
// key will now be returned by `groups_keys_pending_config` which the `ConfigurationSyncJob` uses
// when generating pending changes for group keys so we don't need to push it directly
var pushResult: UnsafePointer<UInt8>? = nil
var pushResultLen: Int = 0
guard groups_keys_rekey(conf, infoConf, membersConf, &pushResult, &pushResultLen) else {
throw LibSessionError.failedToRekeyGroup
}
}
}
// MARK: - Outgoing Changes
internal extension LibSession {
static func rekey(
_ db: Database,
groupSessionId: SessionId,
using dependencies: Dependencies
) throws {
try dependencies.mutate(cache: .libSession) { cache in
try cache.performAndPushChange(db, for: .groupKeys, sessionId: groupSessionId) { config in
guard case .groupKeys(let conf, let infoConf, let membersConf) = config else {
throw LibSessionError.invalidConfigObject
}
// Performing a `rekey` returns the updated key data which we don't use directly, this updated
// key will now be returned by `groups_keys_pending_config` which the `ConfigurationSyncJob` uses
// when generating pending changes for group keys so we don't need to push it directly
var pushResult: UnsafePointer<UInt8>? = nil
var pushResultLen: Int = 0
guard groups_keys_rekey(conf, infoConf, membersConf, &pushResult, &pushResultLen) else {
throw LibSessionError.failedToRekeyGroup
}
}
}
}
static func keySupplement(
_ db: Database,
groupSessionId: SessionId,
memberIds: Set<String>,
using dependencies: Dependencies
) throws -> Data {
return try dependencies.mutate(cache: .libSession) { cache in
guard case .groupKeys(let conf, _, _) = cache.config(for: .groupKeys, sessionId: groupSessionId) else {
throw LibSessionError.invalidConfigObject
}
return try memberIds.withUnsafeCStrArray { cMemberIds in
/// Performing a `key_supplement` returns the supplemental key changes, since our state doesn't care about the
/// `GROUP_KEYS` needed for other members this change won't result in the `GROUP_KEYS` config going into a pending
/// state or the `ConfigurationSyncJob` getting triggered so return the data so that the caller can push it directly
var cSupplementData: UnsafeMutablePointer<UInt8>!
var cSupplementDataLen: Int = 0
guard
groups_keys_key_supplement(conf, cMemberIds.baseAddress, cMemberIds.count, &cSupplementData, &cSupplementDataLen),
let cSupplementData: UnsafeMutablePointer<UInt8> = cSupplementData
else { throw LibSessionError.failedToKeySupplementGroup }
// Must deallocate on success
let supplementData: Data = Data(
bytes: cSupplementData,
count: cSupplementDataLen
)
cSupplementData.deallocate()
return supplementData
}
}
}
static func loadAdminKey(
_ db: Database,
groupIdentitySeed: Data,
groupSessionId: SessionId,
using dependencies: Dependencies
) throws {
try dependencies.mutate(cache: .libSession) { cache in
/// Disable the admin check because we are about to convert the user to being an admin and it's guaranteed to fail
try cache.withCustomBehaviour(.skipGroupAdminCheck, for: groupSessionId) {
try cache.performAndPushChange(db, for: .groupKeys, sessionId: groupSessionId) { config in
guard case .groupKeys(let conf, let infoConf, let membersConf) = config else {
throw LibSessionError.invalidConfigObject
}
var identitySeed: [UInt8] = Array(groupIdentitySeed)
groups_keys_load_admin_key(conf, &identitySeed, infoConf, membersConf)
try LibSessionError.throwIfNeeded(conf)
}
}
}
}
static func numKeys(
groupSessionId: SessionId,
using dependencies: Dependencies
) throws -> Int {
return try dependencies.mutate(cache: .libSession) { cache in
guard case .groupKeys(let conf, _, _) = cache.config(for: .groupKeys, sessionId: groupSessionId) else {
throw LibSessionError.invalidConfigObject
}
return Int(groups_keys_size(conf))
}
}
static func currentGeneration(
groupSessionId: SessionId,
using dependencies: Dependencies
) throws -> Int {
return try dependencies.mutate(cache: .libSession) { cache in
guard case .groupKeys(let conf, _, _) = cache.config(for: .groupKeys, sessionId: groupSessionId) else {
throw LibSessionError.invalidConfigObject
}
return Int(groups_keys_current_generation(conf))
}
}
}