Store the local PNI alongside the local ACI and phone number

Save the PNI after registration and when verifying or updating our
current phone number. (Also, show it in the Internal settings like the
phone number and ACI.)
This commit is contained in:
Jordan Rose 2022-01-10 13:21:11 -08:00
parent fa1b3abf17
commit 8a29fdd71c
10 changed files with 139 additions and 67 deletions

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
// Copyright (c) 2022 Open Whisper Systems. All rights reserved.
//
import Foundation
@ -64,7 +64,7 @@ public class AccountManager: NSObject {
}
self.tsAccountManager.phoneNumberAwaitingVerification = e164
case .changePhoneNumber:
// Don't set phoneNumberAwaitingVerification in the "change phone number" flow.
break
@ -135,8 +135,8 @@ public class AccountManager: NSObject {
return firstly {
self.registerForTextSecure(verificationCode: verificationCode, pin: pin, checkForAvailableTransfer: checkForAvailableTransfer)
}.then { response -> Promise<Void> in
assert(response.uuid != nil)
self.tsAccountManager.uuidAwaitingVerification = response.uuid
self.tsAccountManager.uuidAwaitingVerification = response.aci
self.tsAccountManager.pniAwaitingVerification = response.pni
self.databaseStorage.write { transaction in
if !self.tsAccountManager.isReregistering {
@ -298,7 +298,7 @@ public class AccountManager: NSObject {
let serverAuthToken = generateServerAuthToken()
return firstly { () throws -> Promise<UInt32> in
return firstly { () throws -> Promise<VerifySecondaryDeviceResponse> in
let encryptedDeviceName = try DeviceNames.encryptDeviceName(plaintext: deviceName,
identityKeyPair: provisionMessage.identityKeyPair)
@ -306,7 +306,9 @@ public class AccountManager: NSObject {
phoneNumber: provisionMessage.phoneNumber,
authKey: serverAuthToken,
encryptedDeviceName: encryptedDeviceName)
}.done { (deviceId: UInt32) in
}.done { (response: VerifySecondaryDeviceResponse) in
self.tsAccountManager.pniAwaitingVerification = response.pni
self.databaseStorage.write { transaction in
self.identityManager.storeIdentityKeyPair(provisionMessage.identityKeyPair,
transaction: transaction)
@ -321,7 +323,7 @@ public class AccountManager: NSObject {
}
self.tsAccountManager.setStoredServerAuthToken(serverAuthToken,
deviceId: deviceId,
deviceId: response.deviceId,
transaction: transaction)
self.tsAccountManager.setStoredDeviceName(deviceName,
@ -397,7 +399,8 @@ public class AccountManager: NSObject {
}
private struct RegistrationResponse {
var uuid: UUID?
var aci: UUID
var pni: UUID
var hasPreviouslyUsedKBS = false
}
@ -433,19 +436,11 @@ public class AccountManager: NSObject {
throw OWSAssertionError("Missing or invalid params.")
}
var registrationResponse = RegistrationResponse()
let aci: UUID = try params.required(key: "uuid")
let pni: UUID = try params.required(key: "pni")
let hasPreviouslyUsedKBS = try params.optional(key: "storageCapable") ?? false
// TODO UUID: this UUID param should be non-optional when the production service is updated
if let uuidString: String = try params.optional(key: "uuid") {
guard let uuid = UUID(uuidString: uuidString) else {
throw OWSAssertionError("Missing or invalid uuid.")
}
registrationResponse.uuid = uuid
}
registrationResponse.hasPreviouslyUsedKBS = try params.optional(key: "storageCapable") ?? false
return registrationResponse
return RegistrationResponse(aci: aci, pni: pni, hasPreviouslyUsedKBS: hasPreviouslyUsedKBS)
}
}
@ -537,7 +532,7 @@ public class AccountManager: NSObject {
}
return accountServiceClient.getAccountWhoAmI().map(on: .global()) { whoAmIResponse in
let uuid = whoAmIResponse.uuid
let uuid = whoAmIResponse.aci
// It's possible this method could be called multiple times, so we check
// again if it's been set. We dont bother serializing access since it should

View File

@ -73,7 +73,9 @@ class InternalSettingsViewController: OWSTableViewController2 {
infoSection.add(.copyableItem(label: "Local Phone Number", value: tsAccountManager.localNumber))
infoSection.add(.copyableItem(label: "Local UUID", value: tsAccountManager.localUuid?.uuidString))
infoSection.add(.copyableItem(label: "Local ACI", value: tsAccountManager.localUuid?.uuidString))
infoSection.add(.copyableItem(label: "Local PNI", value: tsAccountManager.localPni?.uuidString))
infoSection.add(.copyableItem(label: "Device ID", value: "\(tsAccountManager.storedDeviceId())"))
if let deviceName = tsAccountManager.storedDeviceName() {

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
// Copyright (c) 2022 Open Whisper Systems. All rights reserved.
//
import XCTest
@ -47,7 +47,7 @@ class VerifyingTSAccountManager: FailingTSAccountManager {
override func verifyRegistration(request: TSRequest,
success successBlock: @escaping (Any?) -> Void,
failure failureBlock: @escaping (Error) -> Void) {
successBlock(["uuid": UUID().uuidString])
successBlock(["uuid": UUID().uuidString, "pni": UUID().uuidString])
}
}

View File

@ -815,7 +815,8 @@ extension StorageServiceProtoAccountRecord: Dependencies {
// Linked devices should always take changes from the storage service.
if let uuid = localAddress.uuid {
tsAccountManager.updateLocalPhoneNumber(serviceLocalE164,
uuid: uuid,
aci: uuid,
pni: tsAccountManager.localPni,
shouldUpdateStorageService: false,
transaction: transaction)
} else {

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
// Copyright (c) 2022 Open Whisper Systems. All rights reserved.
//
import Foundation
@ -56,7 +56,7 @@ public class AccountServiceClient: NSObject {
public func verifySecondaryDevice(verificationCode: String,
phoneNumber: String,
authKey: String,
encryptedDeviceName: Data) -> Promise<UInt32> {
encryptedDeviceName: Data) -> Promise<VerifySecondaryDeviceResponse> {
return serviceClient.verifySecondaryDevice(verificationCode: verificationCode, phoneNumber: phoneNumber, authKey: authKey, encryptedDeviceName: encryptedDeviceName)
}
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
// Copyright (c) 2022 Open Whisper Systems. All rights reserved.
//
NS_ASSUME_NONNULL_BEGIN
@ -31,6 +31,7 @@ NSString *NSStringForOWSRegistrationState(OWSRegistrationState value);
@property (nonatomic, nullable) NSString *phoneNumberAwaitingVerification;
@property (nonatomic, nullable) NSUUID *uuidAwaitingVerification;
@property (nonatomic, nullable) NSUUID *pniAwaitingVerification;
#pragma mark - Initializers
@ -64,6 +65,10 @@ NSString *NSStringForOWSRegistrationState(OWSRegistrationState value);
- (nullable NSUUID *)localUuidWithTransaction:(SDSAnyReadTransaction *)transaction NS_SWIFT_NAME(uuid(with:));
@property (readonly, nullable) NSUUID *localPni;
- (nullable NSUUID *)localPniWithTransaction:(SDSAnyReadTransaction *)transaction NS_SWIFT_NAME(pni(with:));
@property (readonly, nullable, class) SignalServiceAddress *localAddress;
@property (readonly, nullable) SignalServiceAddress *localAddress;
@ -154,10 +159,11 @@ NSString *NSStringForOWSRegistrationState(OWSRegistrationState value);
#pragma mark - Change Phone Number
- (void)updateLocalPhoneNumber:(NSString *)phoneNumber
uuid:(NSUUID *)uuid
aci:(NSUUID *)aci
pni:(NSUUID *_Nullable)pni
shouldUpdateStorageService:(BOOL)shouldUpdateStorageService
transaction:(SDSAnyWriteTransaction *)transaction
NS_SWIFT_NAME(updateLocalPhoneNumber(_:uuid:shouldUpdateStorageService:transaction:));
NS_SWIFT_NAME(updateLocalPhoneNumber(_:aci:pni:shouldUpdateStorageService:transaction:));
#pragma mark - Manual Message Fetch

View File

@ -24,6 +24,7 @@ NSNotificationName const NSNotificationNameLocalNumberDidChange = @"NSNotificati
NSString *const TSAccountManager_RegisteredNumberKey = @"TSStorageRegisteredNumberKey";
NSString *const TSAccountManager_RegistrationDateKey = @"TSAccountManager_RegistrationDateKey";
NSString *const TSAccountManager_RegisteredUUIDKey = @"TSStorageRegisteredUUIDKey";
NSString *const TSAccountManager_RegisteredPNIKey = @"TSAccountManager_RegisteredPNIKey";
NSString *const TSAccountManager_IsDeregisteredKey = @"TSAccountManager_IsDeregisteredKey";
NSString *const TSAccountManager_ReregisteringPhoneNumberKey = @"TSAccountManager_ReregisteringPhoneNumberKey";
NSString *const TSAccountManager_ReregisteringUUIDKey = @"TSAccountManager_ReregisteringUUIDKey";
@ -71,6 +72,7 @@ NSString *NSStringForOWSRegistrationState(OWSRegistrationState value)
@property (nonatomic, readonly, nullable) NSString *localNumber;
@property (nonatomic, readonly, nullable) NSUUID *localUuid;
@property (nonatomic, readonly, nullable) NSUUID *localPni;
@property (nonatomic, readonly, nullable) NSString *reregistrationPhoneNumber;
@property (nonatomic, readonly, nullable) NSUUID *reregistrationUUID;
@ -113,6 +115,10 @@ NSString *NSStringForOWSRegistrationState(OWSRegistrationState value)
NSString *_Nullable uuidString = [keyValueStore getString:TSAccountManager_RegisteredUUIDKey
transaction:transaction];
_localUuid = (uuidString != nil ? [[NSUUID alloc] initWithUUIDString:uuidString] : nil);
NSString *_Nullable pniString = [keyValueStore getString:TSAccountManager_RegisteredPNIKey transaction:transaction];
_localPni = (pniString != nil ? [[NSUUID alloc] initWithUUIDString:pniString] : nil);
_reregistrationPhoneNumber = [keyValueStore getString:TSAccountManager_ReregisteringPhoneNumberKey
transaction:transaction];
NSString *_Nullable reregistrationUUIDString = [keyValueStore getString:TSAccountManager_ReregisteringUUIDKey
@ -210,6 +216,7 @@ NSString *NSStringForOWSRegistrationState(OWSRegistrationState value)
@synthesize phoneNumberAwaitingVerification = _phoneNumberAwaitingVerification;
@synthesize uuidAwaitingVerification = _uuidAwaitingVerification;
@synthesize pniAwaitingVerification = _pniAwaitingVerification;
- (instancetype)init
{
@ -267,6 +274,13 @@ NSString *NSStringForOWSRegistrationState(OWSRegistrationState value)
}
}
- (nullable NSUUID *)pniAwaitingVerification
{
@synchronized(self) {
return _pniAwaitingVerification;
}
}
- (void)setPhoneNumberAwaitingVerification:(NSString *_Nullable)phoneNumberAwaitingVerification
{
@synchronized(self) {
@ -285,15 +299,23 @@ NSString *NSStringForOWSRegistrationState(OWSRegistrationState value)
}
}
- (void)setPniAwaitingVerification:(NSUUID *_Nullable)pniAwaitingVerification
{
@synchronized(self) {
_pniAwaitingVerification = pniAwaitingVerification;
}
}
- (void)updateLocalPhoneNumber:(NSString *)phoneNumber
uuid:(NSUUID *)uuid
aci:(NSUUID *)uuid
pni:(NSUUID *_Nullable)pni
shouldUpdateStorageService:(BOOL)shouldUpdateStorageService
transaction:(SDSAnyWriteTransaction *)transaction
{
OWSAssertDebug([PhoneNumber resemblesE164:phoneNumber]);
OWSAssertDebug([NSObject isNullableObject:self.localUuid equalTo:uuid]);
[self storeLocalNumber:phoneNumber uuid:uuid transaction:transaction];
[self storeLocalNumber:phoneNumber aci:uuid pni:pni transaction:transaction];
[transaction addAsyncCompletionOffMain:^{
[self updateAccountAttributes].catch(^(NSError *error) { OWSLogError(@"Error: %@.", error); });
@ -397,22 +419,26 @@ NSString *NSStringForOWSRegistrationState(OWSRegistrationState value)
{
OWSLogInfo(@"");
NSString *phoneNumber;
NSUUID *uuid;
NSUUID *aci;
NSUUID *pni;
@synchronized(self) {
phoneNumber = self.phoneNumberAwaitingVerification;
uuid = self.uuidAwaitingVerification;
aci = self.uuidAwaitingVerification;
pni = self.pniAwaitingVerification;
}
if (!phoneNumber) {
OWSFail(@"phoneNumber was unexpectedly nil");
}
if (!uuid) {
if (!aci) {
OWSFail(@"uuid was unexpectedly nil");
}
// Allow the PNI to be nil.
DatabaseStorageWrite(self.databaseStorage, ^(SDSAnyWriteTransaction *transaction) {
[self storeLocalNumber:phoneNumber uuid:uuid transaction:transaction];
[self storeLocalNumber:phoneNumber aci:aci pni:pni transaction:transaction];
});
// Clear this flag so we don't show the "dropped ydb" ui during future re-registrations.
@ -486,6 +512,28 @@ NSString *NSStringForOWSRegistrationState(OWSRegistrationState value)
return accountState.localUuid;
}
- (nullable NSUUID *)localPni
{
return [self localPniWithAccountState:[self getOrLoadAccountStateWithSneakyTransaction]];
}
- (nullable NSUUID *)localPniWithTransaction:(SDSAnyReadTransaction *)transaction
{
return [self localPniWithAccountState:[self getOrLoadAccountStateWithTransaction:transaction]];
}
- (nullable NSUUID *)localPniWithAccountState:(TSAccountState *)accountState
{
@synchronized(self) {
NSUUID *awaitingVerif = self.pniAwaitingVerification;
if (awaitingVerif) {
return awaitingVerif;
}
}
return accountState.localPni;
}
+ (nullable SignalServiceAddress *)localAddressWithTransaction:(SDSAnyReadTransaction *)transaction
{
return [self.shared localAddressWithTransaction:transaction];
@ -524,7 +572,8 @@ NSString *NSStringForOWSRegistrationState(OWSRegistrationState value)
}
- (void)storeLocalNumber:(NSString *)localNumber
uuid:(NSUUID *)localUuid
aci:(NSUUID *)localAci
pni:(NSUUID *_Nullable)localPni
transaction:(SDSAnyWriteTransaction *)transaction
{
@synchronized (self) {
@ -537,22 +586,32 @@ NSString *NSStringForOWSRegistrationState(OWSRegistrationState value)
[self.keyValueStore setDate:[NSDate new] key:TSAccountManager_RegistrationDateKey transaction:transaction];
if (localUuid == nil) {
OWSFail(@"Missing localUuid.");
if (localAci == nil) {
OWSFail(@"Missing localAci.");
} else {
NSString *localUuidString = localUuid.UUIDString;
NSString *_Nullable localUuidStringOld = [self.keyValueStore getString:TSAccountManager_RegisteredUUIDKey
transaction:transaction];
if (![NSObject isNullableObject:localUuidString equalTo:localUuidStringOld]) {
OWSLogInfo(@"localUuid: %@ -> %@", localUuidStringOld, localUuidString);
NSString *localAciString = localAci.UUIDString;
NSString *_Nullable localAciStringOld = [self.keyValueStore getString:TSAccountManager_RegisteredUUIDKey
transaction:transaction];
if (![localAciString isEqual:localAciStringOld]) {
OWSLogInfo(@"localAci: %@ -> %@", localAciStringOld, localAciString);
}
[self.keyValueStore setString:localUuidString
[self.keyValueStore setString:localAciString
key:TSAccountManager_RegisteredUUIDKey
transaction:transaction];
}
if (localPni) {
NSString *localPniString = localPni.UUIDString;
NSString *_Nullable localPniStringOld = [self.keyValueStore getString:TSAccountManager_RegisteredPNIKey
transaction:transaction];
if (![localPniString isEqual:localPniStringOld]) {
OWSLogInfo(@"localPni: %@ -> %@", localPniStringOld, localPniString);
}
[self.keyValueStore setString:localPniString key:TSAccountManager_RegisteredPNIKey transaction:transaction];
}
// Update the address cache mapping for the local user.
[SSKEnvironment.shared.signalServiceAddressCache updateMappingWithUuid:localUuid phoneNumber:localNumber];
[SSKEnvironment.shared.signalServiceAddressCache updateMappingWithUuid:localAci phoneNumber:localNumber];
[self.keyValueStore removeValueForKey:TSAccountManager_ReregisteringPhoneNumberKey transaction:transaction];
[self.keyValueStore removeValueForKey:TSAccountManager_ReregisteringUUIDKey transaction:transaction];
@ -568,9 +627,10 @@ NSString *NSStringForOWSRegistrationState(OWSRegistrationState value)
self.phoneNumberAwaitingVerification = nil;
self.uuidAwaitingVerification = nil;
self.pniAwaitingVerification = nil;
}
SignalServiceAddress *address = [[SignalServiceAddress alloc] initWithUuid:localUuid phoneNumber:localNumber];
SignalServiceAddress *address = [[SignalServiceAddress alloc] initWithUuid:localAci phoneNumber:localNumber];
[SignalRecipient markRecipientAsRegisteredAndGet:address
trustLevel:SignalRecipientTrustLevelHigh
transaction:transaction];
@ -771,6 +831,7 @@ NSString *NSStringForOWSRegistrationState(OWSRegistrationState value)
@synchronized(self) {
self.phoneNumberAwaitingVerification = nil;
self.uuidAwaitingVerification = nil;
self.pniAwaitingVerification = nil;
[self.keyValueStore removeAllWithTransaction:transaction];
@ -899,7 +960,7 @@ NSString *NSStringForOWSRegistrationState(OWSRegistrationState value)
OWSAssertDebug(uuid != nil);
DatabaseStorageWrite(self.databaseStorage, ^(SDSAnyWriteTransaction *transaction) {
[self storeLocalNumber:localNumber uuid:uuid transaction:transaction];
[self storeLocalNumber:localNumber aci:uuid pni:nil transaction:transaction];
});
}

View File

@ -14,7 +14,7 @@ public enum SignalServiceError: Int, Error {
public protocol SignalServiceClient {
func requestPreauthChallenge(e164: String, pushToken: String, isVoipToken: Bool) -> Promise<Void>
func requestVerificationCode(e164: String, preauthChallenge: String?, captchaToken: String?, transport: TSVerificationTransport) -> Promise<Void>
func verifySecondaryDevice(verificationCode: String, phoneNumber: String, authKey: String, encryptedDeviceName: Data) -> Promise<UInt32>
func verifySecondaryDevice(verificationCode: String, phoneNumber: String, authKey: String, encryptedDeviceName: Data) -> Promise<VerifySecondaryDeviceResponse>
func getAvailablePreKeys() -> Promise<Int>
func registerPreKeys(identityKey: IdentityKey, signedPreKeyRecord: SignedPreKeyRecord, preKeyRecords: [PreKeyRecord]) -> Promise<Void>
func setCurrentSignedPreKey(_ signedPreKey: SignedPreKeyRecord) -> Promise<Void>
@ -134,15 +134,11 @@ public class SignalServiceRestClient: NSObject, SignalServiceClient {
throw OWSAssertionError("Missing or invalid response.")
}
let uuidString: String = try parser.required(key: "uuid")
guard let uuid = UUID(uuidString: uuidString) else {
throw OWSAssertionError("Missing or invalid uuid.")
}
let aci: UUID = try parser.required(key: "uuid")
let pni: UUID = try parser.required(key: "pni")
let e164: String? = try parser.optional(key: "number")
return WhoAmIResponse(uuid: uuid, e164: e164)
return WhoAmIResponse(aci: aci, pni: pni, e164: e164)
}
}
@ -168,7 +164,7 @@ public class SignalServiceRestClient: NSObject, SignalServiceClient {
public func verifySecondaryDevice(verificationCode: String,
phoneNumber: String,
authKey: String,
encryptedDeviceName: Data) -> Promise<UInt32> {
encryptedDeviceName: Data) -> Promise<VerifySecondaryDeviceResponse> {
let request = OWSRequestFactory.verifySecondaryDeviceRequest(verificationCode: verificationCode,
phoneNumber: phoneNumber,
@ -186,8 +182,10 @@ public class SignalServiceRestClient: NSObject, SignalServiceClient {
}
let deviceId: UInt32 = try parser.required(key: "deviceId")
return deviceId
}.recover { error -> Promise<UInt32> in
let pni: UUID = try parser.required(key: "pni")
return VerifySecondaryDeviceResponse(pni: pni, deviceId: deviceId)
}.recover { error -> Promise<VerifySecondaryDeviceResponse> in
if let statusCode = error.httpStatusCode,
statusCode == 409 {
// Convert 409 errors into .obsoleteLinkedDevice
@ -248,6 +246,12 @@ public class SignalServiceRestClient: NSObject, SignalServiceClient {
// MARK: -
public struct WhoAmIResponse {
public let uuid: UUID
public let aci: UUID
public let pni: UUID
public let e164: String?
}
public struct VerifySecondaryDeviceResponse {
public let pni: UUID
public let deviceId: UInt32
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
// Copyright (c) 2022 Open Whisper Systems. All rights reserved.
//
import Foundation
@ -36,6 +36,6 @@ public class FakeAccountServiceClient: AccountServiceClient {
}
public override func getAccountWhoAmI() -> Promise<WhoAmIResponse> {
return Promise { $0.resolve(WhoAmIResponse(uuid: UUID(), e164: nil)) }
return Promise { $0.resolve(WhoAmIResponse(aci: UUID(), pni: UUID(), e164: nil)) }
}
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
// Copyright (c) 2022 Open Whisper Systems. All rights reserved.
//
import Foundation
@ -150,14 +150,16 @@ public class ChangePhoneNumber: NSObject {
guard let localPhoneNumber = tsAccountManager.localNumber else {
throw OWSAssertionError("Missing localPhoneNumber.")
}
let localPni = tsAccountManager.localPni
let serviceUuid = whoAmIResponse.uuid
let serviceUuid = whoAmIResponse.aci
guard let servicePhoneNumber = whoAmIResponse.e164 else {
throw OWSAssertionError("Missing servicePhoneNumber.")
}
guard serviceUuid == localUuid else {
throw OWSAssertionError("Unexpected uuid: \(serviceUuid) != \(localUuid)")
}
let servicePni = whoAmIResponse.pni
Logger.info("localUuid: \(localUuid), localPhoneNumber: \(localPhoneNumber), serviceUuid: \(serviceUuid), servicePhoneNumber: \(servicePhoneNumber)")
@ -168,12 +170,13 @@ public class ChangePhoneNumber: NSObject {
transaction: transaction)
}
if servicePhoneNumber != localPhoneNumber {
Logger.info("Recording new phone number: \(servicePhoneNumber)")
if servicePhoneNumber != localPhoneNumber || servicePni != localPni {
Logger.info("Recording new phone number: \(servicePhoneNumber), pni: \(servicePni)")
Self.databaseStorage.write { transaction in
Self.tsAccountManager.updateLocalPhoneNumber(servicePhoneNumber,
uuid: localUuid,
aci: localUuid,
pni: servicePni,
shouldUpdateStorageService: true,
transaction: transaction)
}