128 lines
5.4 KiB
Swift
128 lines
5.4 KiB
Swift
//
|
|
// Copyright 2025 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import Foundation
|
|
import LibSignalClient
|
|
public import SignalServiceKit
|
|
|
|
public class ProvisioningManager {
|
|
|
|
private let accountKeyStore: AccountKeyStore
|
|
private let db: any DB
|
|
private let deviceManager: OWSDeviceManager
|
|
private let deviceProvisioningService: DeviceProvisioningService
|
|
private let identityManager: OWSIdentityManager
|
|
private let linkAndSyncManager: LinkAndSyncManager
|
|
private let profileManager: ProfileManager
|
|
private let receiptManager: Shims.ReceiptManager
|
|
private let tsAccountManager: TSAccountManager
|
|
|
|
init(
|
|
accountKeyStore: AccountKeyStore,
|
|
db: any DB,
|
|
deviceManager: OWSDeviceManager,
|
|
deviceProvisioningService: DeviceProvisioningService,
|
|
identityManager: OWSIdentityManager,
|
|
linkAndSyncManager: LinkAndSyncManager,
|
|
profileManager: ProfileManager,
|
|
receiptManager: Shims.ReceiptManager,
|
|
tsAccountManager: TSAccountManager,
|
|
) {
|
|
self.accountKeyStore = accountKeyStore
|
|
self.db = db
|
|
self.deviceManager = deviceManager
|
|
self.deviceProvisioningService = deviceProvisioningService
|
|
self.identityManager = identityManager
|
|
self.linkAndSyncManager = linkAndSyncManager
|
|
self.profileManager = profileManager
|
|
self.receiptManager = receiptManager
|
|
self.tsAccountManager = tsAccountManager
|
|
}
|
|
|
|
public func provision(
|
|
with deviceProvisioningUrl: DeviceProvisioningURL,
|
|
shouldLinkNSync: Bool,
|
|
) async throws -> (MessageRootBackupKey?, DeviceProvisioningTokenId) {
|
|
struct ProvisioningState {
|
|
var localIdentifiers: LocalIdentifiers
|
|
var aciIdentityKeyPair: ECKeyPair
|
|
var pniIdentityKeyPair: ECKeyPair
|
|
var areReadReceiptsEnabled: Bool
|
|
var aep: SignalServiceKit.AccountEntropyPool
|
|
var mediaRootBackupKey: MediaRootBackupKey
|
|
var profileKey: Aes256Key
|
|
}
|
|
let provisioningState = await db.awaitableWrite { tx in
|
|
guard let localIdentifiers = tsAccountManager.localIdentifiers(tx: tx) else { owsFail("Can't provision without an aci & phone number.")
|
|
}
|
|
guard let aciIdentityKeyPair = identityManager.identityKeyPair(for: .aci, tx: tx) else {
|
|
owsFail("Can't provision without an aci identity.")
|
|
}
|
|
guard let pniIdentityKeyPair = identityManager.identityKeyPair(for: .pni, tx: tx) else {
|
|
owsFail("Can't provision without a pni identity.")
|
|
}
|
|
let areReadReceiptsEnabled = receiptManager.areReadReceiptsEnabled(tx: tx)
|
|
guard let accountEntropyPool = accountKeyStore.getAccountEntropyPool(tx: tx) else {
|
|
// This should be impossible; the only times you don't have
|
|
// an AEP are during registration.
|
|
owsFail("Can't provision without account entropy pool.")
|
|
}
|
|
let mrbk = accountKeyStore.getOrGenerateMediaRootBackupKey(tx: tx)
|
|
guard let profileKey = profileManager.localUserProfile(tx: tx)?.profileKey else {
|
|
owsFail("Can't provision without a profile key.")
|
|
}
|
|
return ProvisioningState(
|
|
localIdentifiers: localIdentifiers,
|
|
aciIdentityKeyPair: aciIdentityKeyPair,
|
|
pniIdentityKeyPair: pniIdentityKeyPair,
|
|
areReadReceiptsEnabled: areReadReceiptsEnabled,
|
|
aep: accountEntropyPool,
|
|
mediaRootBackupKey: mrbk,
|
|
profileKey: profileKey,
|
|
)
|
|
}
|
|
|
|
let myAci = provisioningState.localIdentifiers.aci
|
|
let myPhoneNumber = provisioningState.localIdentifiers.phoneNumber
|
|
guard let myPni = provisioningState.localIdentifiers.pni else {
|
|
owsFail("Can't provision without a pni.")
|
|
}
|
|
|
|
let ephemeralBackupKey: MessageRootBackupKey?
|
|
if
|
|
shouldLinkNSync,
|
|
deviceProvisioningUrl.capabilities.contains(.linknsync)
|
|
{
|
|
ephemeralBackupKey = linkAndSyncManager.generateEphemeralBackupKey(aci: myAci)
|
|
} else {
|
|
ephemeralBackupKey = nil
|
|
}
|
|
|
|
let provisioningCode = try await deviceProvisioningService.requestDeviceProvisioningCode()
|
|
|
|
let provisioningMessage = LinkingProvisioningMessage(
|
|
aep: provisioningState.aep,
|
|
aci: myAci,
|
|
phoneNumber: myPhoneNumber,
|
|
pni: myPni,
|
|
aciIdentityKeyPair: provisioningState.aciIdentityKeyPair.identityKeyPair,
|
|
pniIdentityKeyPair: provisioningState.pniIdentityKeyPair.identityKeyPair,
|
|
profileKey: provisioningState.profileKey,
|
|
mrbk: provisioningState.mediaRootBackupKey,
|
|
ephemeralBackupKey: ephemeralBackupKey,
|
|
areReadReceiptsEnabled: provisioningState.areReadReceiptsEnabled,
|
|
provisioningCode: provisioningCode.verificationCode,
|
|
)
|
|
|
|
let theirPublicKey = deviceProvisioningUrl.publicKey
|
|
let messageBody = try provisioningMessage.buildEncryptedMessageBody(theirPublicKey: theirPublicKey)
|
|
try await deviceProvisioningService.provisionDevice(
|
|
messageBody: messageBody,
|
|
ephemeralDeviceId: deviceProvisioningUrl.ephemeralDeviceId,
|
|
)
|
|
return (ephemeralBackupKey, provisioningCode.tokenId)
|
|
}
|
|
}
|