199 lines
6.9 KiB
Swift
199 lines
6.9 KiB
Swift
//
|
|
// Copyright 2025 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import Foundation
|
|
public import LibSignalClient
|
|
|
|
public struct RegistrationProvisioningEnvelope {
|
|
|
|
public let body: Data
|
|
public let publicKey: Data
|
|
|
|
public init(serializedData: Data) throws {
|
|
let proto = try RegistrationProtos_RegistrationProvisionEnvelope(serializedBytes: serializedData)
|
|
self.body = proto.body
|
|
self.publicKey = proto.publicKey
|
|
}
|
|
}
|
|
|
|
public struct RegistrationProvisioningMessage {
|
|
|
|
public enum Platform {
|
|
case ios
|
|
case android
|
|
}
|
|
|
|
public enum BackupTier: Equatable {
|
|
case free
|
|
case paid
|
|
}
|
|
|
|
public let accountEntropyPool: AccountEntropyPool
|
|
public let aci: Aci
|
|
public let aciIdentityKeyPair: IdentityKeyPair
|
|
public let pniIdentityKeyPair: IdentityKeyPair
|
|
public let phoneNumber: E164
|
|
public let pin: String?
|
|
public let platform: Platform
|
|
public let tier: BackupTier?
|
|
public let backupVersion: UInt64?
|
|
public let backupTimestamp: UInt64?
|
|
public let backupSizeBytes: UInt64?
|
|
public let restoreMethodToken: String?
|
|
public let lastBackupForwardSecrecyToken: LibSignalClient.BackupForwardSecrecyToken?
|
|
public let nextBackupSecretData: BackupNonce.NextSecretMetadata?
|
|
|
|
public init(
|
|
accountEntropyPool: AccountEntropyPool,
|
|
aci: Aci,
|
|
aciIdentityKeyPair: IdentityKeyPair,
|
|
pniIdentityKeyPair: IdentityKeyPair,
|
|
phoneNumber: E164,
|
|
pin: String?,
|
|
tier: BackupTier?,
|
|
backupVersion: UInt64?,
|
|
backupTimestamp: UInt64?,
|
|
backupSizeBytes: UInt64?,
|
|
restoreMethodToken: String?,
|
|
lastBackupForwardSecrecyToken: LibSignalClient.BackupForwardSecrecyToken?,
|
|
nextBackupSecretData: BackupNonce.NextSecretMetadata?,
|
|
) {
|
|
self.platform = .ios
|
|
self.accountEntropyPool = accountEntropyPool
|
|
self.aci = aci
|
|
self.aciIdentityKeyPair = aciIdentityKeyPair
|
|
self.pniIdentityKeyPair = pniIdentityKeyPair
|
|
self.phoneNumber = phoneNumber
|
|
self.pin = pin
|
|
self.tier = tier
|
|
self.backupVersion = backupVersion
|
|
self.backupTimestamp = backupTimestamp
|
|
self.backupSizeBytes = backupSizeBytes
|
|
self.restoreMethodToken = restoreMethodToken
|
|
self.lastBackupForwardSecrecyToken = lastBackupForwardSecrecyToken
|
|
self.nextBackupSecretData = nextBackupSecretData
|
|
}
|
|
|
|
public init(plaintext: Data) throws {
|
|
let proto = try RegistrationProtos_RegistrationProvisionMessage(serializedBytes: plaintext)
|
|
|
|
self.aciIdentityKeyPair = try IdentityKeyPair(
|
|
publicKey: PublicKey(proto.aciIdentityKeyPublic),
|
|
privateKey: PrivateKey(proto.aciIdentityKeyPrivate),
|
|
)
|
|
|
|
self.pniIdentityKeyPair = try IdentityKeyPair(
|
|
publicKey: PublicKey(proto.pniIdentityKeyPublic),
|
|
privateKey: PrivateKey(proto.pniIdentityKeyPrivate),
|
|
)
|
|
|
|
guard
|
|
let accountEntropyPool = proto.accountEntropyPool.nilIfEmpty,
|
|
let aep = try? AccountEntropyPool(key: accountEntropyPool)
|
|
else {
|
|
throw ProvisioningError.invalidProvisionMessage("missing master key from provisioning message")
|
|
}
|
|
self.accountEntropyPool = aep
|
|
|
|
self.aci = try Aci.parseFrom(serviceIdBinary: proto.aci)
|
|
|
|
guard let e164 = E164(proto.e164) else {
|
|
throw ProvisioningError.invalidProvisionMessage("missing number from provisioning message")
|
|
}
|
|
self.phoneNumber = e164
|
|
|
|
self.pin = proto.pin
|
|
|
|
self.platform = proto.platform == .android ? .android : .ios
|
|
|
|
self.tier = proto.tier == .paid ? .paid : .free
|
|
self.backupVersion = proto.backupVersion
|
|
self.backupTimestamp = proto.backupTimestampMs
|
|
self.backupSizeBytes = proto.backupSizeBytes
|
|
|
|
self.restoreMethodToken = proto.restoreMethodToken
|
|
|
|
if let data = proto.lastBackupForwardSecrecyToken.nilIfEmpty {
|
|
self.lastBackupForwardSecrecyToken = try LibSignalClient.BackupForwardSecrecyToken(contents: data)
|
|
} else {
|
|
self.lastBackupForwardSecrecyToken = nil
|
|
}
|
|
|
|
if let data = proto.nextBackupSecretData.nilIfEmpty {
|
|
self.nextBackupSecretData = BackupNonce.NextSecretMetadata(data: data)
|
|
} else {
|
|
self.nextBackupSecretData = nil
|
|
}
|
|
}
|
|
|
|
public func buildEncryptedMessageBody(theirPublicKey: PublicKey) throws -> Data {
|
|
var messageBuilder = RegistrationProtos_RegistrationProvisionMessage()
|
|
|
|
messageBuilder.accountEntropyPool = accountEntropyPool.rawString
|
|
messageBuilder.aci = aci.serviceIdBinary
|
|
messageBuilder.e164 = phoneNumber.stringValue
|
|
if let pin {
|
|
messageBuilder.pin = pin
|
|
}
|
|
|
|
messageBuilder.aciIdentityKeyPublic = aciIdentityKeyPair.publicKey.serialize()
|
|
messageBuilder.aciIdentityKeyPrivate = aciIdentityKeyPair.privateKey.serialize()
|
|
|
|
messageBuilder.pniIdentityKeyPublic = pniIdentityKeyPair.publicKey.serialize()
|
|
messageBuilder.pniIdentityKeyPrivate = pniIdentityKeyPair.privateKey.serialize()
|
|
|
|
messageBuilder.platform = .ios
|
|
|
|
// TODO: [Backups] Check backups are enabled before populating this
|
|
if let tier {
|
|
let protoTier: RegistrationProtos_RegistrationProvisionMessage.Tier
|
|
switch tier {
|
|
case .free: protoTier = .free
|
|
case .paid: protoTier = .paid
|
|
}
|
|
messageBuilder.tier = protoTier
|
|
}
|
|
|
|
if let backupVersion {
|
|
messageBuilder.backupVersion = backupVersion
|
|
}
|
|
|
|
if let backupTimestamp {
|
|
messageBuilder.backupTimestampMs = backupTimestamp
|
|
}
|
|
if let backupSizeBytes {
|
|
messageBuilder.backupSizeBytes = backupSizeBytes
|
|
}
|
|
|
|
if let restoreMethodToken {
|
|
messageBuilder.restoreMethodToken = restoreMethodToken
|
|
}
|
|
|
|
if let lastBackupForwardSecrecyToken {
|
|
messageBuilder.lastBackupForwardSecrecyToken = lastBackupForwardSecrecyToken.serialize()
|
|
}
|
|
if let nextBackupSecretData {
|
|
messageBuilder.nextBackupSecretData = nextBackupSecretData.data
|
|
}
|
|
|
|
let plainTextMessage = try messageBuilder.serializedData()
|
|
|
|
let ourKeyPair = IdentityKeyPair.generate()
|
|
let cipher = ProvisioningCipher(ourKeyPair: ourKeyPair)
|
|
let encryptedMessage: Data
|
|
do {
|
|
encryptedMessage = try cipher.encrypt(plainTextMessage, theirPublicKey: theirPublicKey)
|
|
} catch {
|
|
throw OWSAssertionError("Failed to encrypt registration provisioning message")
|
|
}
|
|
|
|
var envelopeBuilder = RegistrationProtos_RegistrationProvisionEnvelope()
|
|
envelopeBuilder.publicKey = ourKeyPair.publicKey.serialize()
|
|
envelopeBuilder.body = encryptedMessage
|
|
|
|
return try envelopeBuilder.serializedData()
|
|
}
|
|
}
|