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/SignalServiceKit/src/Account/TSAccountManager.m

458 lines
17 KiB
Matlab

10 years ago
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
10 years ago
//
#import "TSAccountManager.h"
10 years ago
#import "NSData+Base64.h"
#import "NSData+hexString.h"
#import "NSNotificationCenter+OWS.h"
10 years ago
#import "NSURLSessionDataTask+StatusCode.h"
#import "OWSError.h"
10 years ago
#import "SecurityUtils.h"
#import "TSNetworkManager.h"
#import "TSPreKeyManager.h"
#import "TSSocketManager.h"
#import "TSStorageManager+SessionStore.h"
#import "YapDatabaseConnection+OWS.h"
10 years ago
NS_ASSUME_NONNULL_BEGIN
NSString *const TSRegistrationErrorDomain = @"TSRegistrationErrorDomain";
NSString *const TSRegistrationErrorUserInfoHTTPStatus = @"TSHTTPStatus";
NSString *const kNSNotificationName_RegistrationStateDidChange = @"kNSNotificationName_RegistrationStateDidChange";
NSString *const kNSNotificationName_LocalNumberDidChange = @"kNSNotificationName_LocalNumberDidChange";
NSString *const TSAccountManager_RegisteredNumberKey = @"TSStorageRegisteredNumberKey";
NSString *const TSAccountManager_LocalRegistrationIdKey = @"TSStorageLocalRegistrationId";
NSString *const TSAccountManager_UserAccountCollection = @"TSStorageUserAccountCollection";
NSString *const TSAccountManager_ServerAuthToken = @"TSStorageServerAuthToken";
NSString *const TSAccountManager_ServerSignalingKey = @"TSStorageServerSignalingKey";
10 years ago
@interface TSAccountManager ()
@property (nonatomic, readonly) BOOL isRegistered;
@property (nonatomic, nullable) NSString *phoneNumberAwaitingVerification;
@property (nonatomic, nullable) NSString *cachedLocalNumber;
@property (nonatomic, readonly) YapDatabaseConnection *dbConnection;
10 years ago
@end
#pragma mark -
10 years ago
@implementation TSAccountManager
@synthesize isRegistered = _isRegistered;
- (instancetype)initWithNetworkManager:(TSNetworkManager *)networkManager
storageManager:(TSStorageManager *)storageManager
{
self = [super init];
if (!self) {
return self;
}
_networkManager = networkManager;
_dbConnection = [storageManager newDatabaseConnection];
OWSSingletonAssert();
return self;
}
+ (instancetype)sharedInstance
{
10 years ago
static dispatch_once_t onceToken;
static id sharedInstance = nil;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] initWithNetworkManager:[TSNetworkManager sharedManager]
storageManager:[TSStorageManager sharedManager]];
10 years ago
});
return sharedInstance;
}
- (void)setPhoneNumberAwaitingVerification:(NSString *_Nullable)phoneNumberAwaitingVerification
{
_phoneNumberAwaitingVerification = phoneNumberAwaitingVerification;
[[NSNotificationCenter defaultCenter] postNotificationNameAsync:kNSNotificationName_LocalNumberDidChange
object:nil
userInfo:nil];
}
- (void)resetForRegistration
{
@synchronized(self)
{
_isRegistered = NO;
_cachedLocalNumber = nil;
_phoneNumberAwaitingVerification = nil;
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
[transaction removeAllObjectsInCollection:TSAccountManager_UserAccountCollection];
}];
}
[[TSStorageManager sharedManager] resetSessionStore];
}
+ (BOOL)isRegistered
{
return [[self sharedInstance] isRegistered];
}
- (BOOL)isRegistered
{
if (_isRegistered) {
return YES;
} else {
@synchronized (self) {
// Cache this once it's true since it's called alot, involves a dbLookup, and once set - it doesn't change.
_isRegistered = [self storedLocalNumber] != nil;
}
}
return _isRegistered;
10 years ago
}
- (void)didRegister
{
DDLogInfo(@"%@ didRegister", self.logTag);
NSString *phoneNumber = self.phoneNumberAwaitingVerification;
10 years ago
if (!phoneNumber) {
@throw [NSException exceptionWithName:@"RegistrationFail" reason:@"Internal Corrupted State" userInfo:nil];
}
[self storeLocalNumber:phoneNumber];
[[NSNotificationCenter defaultCenter] postNotificationNameAsync:kNSNotificationName_RegistrationStateDidChange
object:nil
userInfo:nil];
// Warm these cached values.
[self isRegistered];
[self localNumber];
10 years ago
}
+ (nullable NSString *)localNumber
{
return [[self sharedInstance] localNumber];
}
- (nullable NSString *)localNumber
{
NSString *awaitingVerif = self.phoneNumberAwaitingVerification;
10 years ago
if (awaitingVerif) {
return awaitingVerif;
}
// Cache this since we access this a lot, and once set it will not change.
@synchronized(self)
{
if (self.cachedLocalNumber == nil) {
self.cachedLocalNumber = self.storedLocalNumber;
}
}
return self.cachedLocalNumber;
10 years ago
}
- (nullable NSString *)storedLocalNumber
{
@synchronized (self) {
return [self.dbConnection stringForKey:TSAccountManager_RegisteredNumberKey
inCollection:TSAccountManager_UserAccountCollection];
}
}
- (void)storeLocalNumber:(NSString *)localNumber
{
@synchronized (self) {
[self.dbConnection setObject:localNumber
forKey:TSAccountManager_RegisteredNumberKey
inCollection:TSAccountManager_UserAccountCollection];
}
}
10 years ago
+ (uint32_t)getOrGenerateRegistrationId
{
return [[self sharedInstance] getOrGenerateRegistrationId];
}
- (uint32_t)getOrGenerateRegistrationId
{
@synchronized(self)
{
uint32_t registrationID =
[[self.dbConnection objectForKey:TSAccountManager_LocalRegistrationIdKey
inCollection:TSAccountManager_UserAccountCollection] unsignedIntValue];
10 years ago
if (registrationID == 0) {
registrationID = (uint32_t)arc4random_uniform(16380) + 1;
DDLogWarn(@"%@ Generated a new registrationID: %u", self.logTag, registrationID);
10 years ago
[self.dbConnection setObject:[NSNumber numberWithUnsignedInteger:registrationID]
forKey:TSAccountManager_LocalRegistrationIdKey
inCollection:TSAccountManager_UserAccountCollection];
}
return registrationID;
10 years ago
}
}
- (void)registerForPushNotificationsWithPushToken:(NSString *)pushToken
voipToken:(NSString *)voipToken
success:(void (^)(void))successHandler
failure:(void (^)(NSError *))failureHandler
{
[self registerForPushNotificationsWithPushToken:pushToken
voipToken:voipToken
success:successHandler
failure:failureHandler
remainingRetries:3];
}
- (void)registerForPushNotificationsWithPushToken:(NSString *)pushToken
voipToken:(NSString *)voipToken
success:(void (^)(void))successHandler
failure:(void (^)(NSError *))failureHandler
remainingRetries:(int)remainingRetries
{
TSRegisterForPushRequest *request =
[[TSRegisterForPushRequest alloc] initWithPushIdentifier:pushToken voipIdentifier:voipToken];
[self.networkManager makeRequest:request
10 years ago
success:^(NSURLSessionDataTask *task, id responseObject) {
successHandler();
10 years ago
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
if (remainingRetries > 0) {
[self registerForPushNotificationsWithPushToken:pushToken
voipToken:voipToken
success:successHandler
failure:failureHandler
remainingRetries:remainingRetries - 1];
} else {
if (!IsNSErrorNetworkFailure(error)) {
OWSProdError([OWSAnalyticsEvents accountsErrorRegisterPushTokensFailed]);
}
failureHandler(error);
}
10 years ago
}];
}
+ (void)registerWithPhoneNumber:(NSString *)phoneNumber
success:(void (^)(void))successBlock
failure:(void (^)(NSError *error))failureBlock
10 years ago
smsVerification:(BOOL)isSMS
{
if ([self isRegistered]) {
failureBlock([NSError errorWithDomain:@"tsaccountmanager.verify" code:4000 userInfo:nil]);
return;
}
// The country code of TSAccountManager.phoneNumberAwaitingVerification is used to
// determine whether or not to use domain fronting, so it needs to be set _before_
// we make our verification code request.
TSAccountManager *manager = [self sharedInstance];
manager.phoneNumberAwaitingVerification = phoneNumber;
10 years ago
[[TSNetworkManager sharedManager]
makeRequest:[[TSRequestVerificationCodeRequest alloc]
initWithPhoneNumber:phoneNumber
transport:isSMS ? TSVerificationTransportSMS : TSVerificationTransportVoice]
success:^(NSURLSessionDataTask *task, id responseObject) {
DDLogInfo(@"%@ Successfully requested verification code request for number: %@ method:%@",
self.logTag,
phoneNumber,
isSMS ? @"SMS" : @"Voice");
successBlock();
10 years ago
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
if (!IsNSErrorNetworkFailure(error)) {
OWSProdError([OWSAnalyticsEvents accountsErrorVerificationCodeRequestFailed]);
}
DDLogError(@"%@ Failed to request verification code request with error:%@", self.logTag, error);
failureBlock(error);
10 years ago
}];
}
+ (void)rerequestSMSWithSuccess:(void (^)(void))successBlock failure:(void (^)(NSError *error))failureBlock
{
10 years ago
TSAccountManager *manager = [self sharedInstance];
NSString *number = manager.phoneNumberAwaitingVerification;
assert(number);
[self registerWithPhoneNumber:number success:successBlock failure:failureBlock smsVerification:YES];
}
+ (void)rerequestVoiceWithSuccess:(void (^)(void))successBlock failure:(void (^)(NSError *error))failureBlock
{
10 years ago
TSAccountManager *manager = [self sharedInstance];
NSString *number = manager.phoneNumberAwaitingVerification;
assert(number);
[self registerWithPhoneNumber:number success:successBlock failure:failureBlock smsVerification:NO];
}
- (void)registerForManualMessageFetchingWithSuccess:(void (^)(void))successBlock
failure:(void (^)(NSError *error))failureBlock
{
TSUpdateAttributesRequest *request = [[TSUpdateAttributesRequest alloc] initWithManualMessageFetching:YES];
[self.networkManager makeRequest:request
success:^(NSURLSessionDataTask *_Nonnull task, id _Nonnull responseObject) {
DDLogInfo(@"%@ updated server with account attributes to enableManualFetching", self.logTag);
successBlock();
}
failure:^(NSURLSessionDataTask *_Nonnull task, NSError *_Nonnull error) {
DDLogInfo(@"%@ failed to updat server with account attributes with error: %@", self.logTag, error);
failureBlock(error);
}];
}
- (void)verifyAccountWithCode:(NSString *)verificationCode
success:(void (^)(void))successBlock
failure:(void (^)(NSError *error))failureBlock
{
NSString *authToken = [[self class] generateNewAccountAuthenticationToken];
NSString *signalingKey = [[self class] generateNewSignalingKeyToken];
NSString *phoneNumber = self.phoneNumberAwaitingVerification;
10 years ago
assert(signalingKey);
assert(authToken);
assert(phoneNumber);
TSVerifyCodeRequest *request = [[TSVerifyCodeRequest alloc] initWithVerificationCode:verificationCode
forNumber:phoneNumber
signalingKey:signalingKey
authKey:authToken];
10 years ago
[self.networkManager makeRequest:request
10 years ago
success:^(NSURLSessionDataTask *task, id responseObject) {
NSHTTPURLResponse *response = (NSHTTPURLResponse *)task.response;
long statuscode = response.statusCode;
switch (statuscode) {
case 200:
case 204: {
DDLogInfo(@"%@ Verification code accepted.", self.logTag);
[self storeServerAuthToken:authToken signalingKey:signalingKey];
[TSPreKeyManager registerPreKeysWithMode:RefreshPreKeysMode_SignedAndOneTime
success:successBlock
failure:failureBlock];
break;
}
default: {
DDLogError(@"%@ Unexpected status while verifying code: %ld", self.logTag, statuscode);
NSError *error = OWSErrorMakeUnableToProcessServerResponseError();
10 years ago
failureBlock(error);
break;
}
}
10 years ago
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
if (!IsNSErrorNetworkFailure(error)) {
OWSProdError([OWSAnalyticsEvents accountsErrorVerifyAccountRequestFailed]);
}
DDLogWarn(@"%@ Error verifying code: %@", self.logTag, error.debugDescription);
switch (error.code) {
case 403: {
NSError *userError = OWSErrorWithCodeDescription(OWSErrorCodeUserError,
NSLocalizedString(@"REGISTRATION_VERIFICATION_FAILED_WRONG_CODE_DESCRIPTION",
"Alert body, during registration"));
failureBlock(userError);
break;
}
default: {
DDLogError(@"%@ verifying code failed with unhandled error: %@", self.logTag, error);
failureBlock(error);
break;
}
}
10 years ago
}];
}
#pragma mark Server keying material
+ (NSString *)generateNewAccountAuthenticationToken {
NSData *authToken = [SecurityUtils generateRandomBytes:16];
NSString *authTokenPrint = [[NSData dataWithData:authToken] hexadecimalString];
return authTokenPrint;
}
+ (NSString *)generateNewSignalingKeyToken {
/*The signalingKey is 32 bytes of AES material (256bit AES) and 20 bytes of
* Hmac key material (HmacSHA1) concatenated into a 52 byte slug that is
* base64 encoded. */
NSData *signalingKeyToken = [SecurityUtils generateRandomBytes:52];
NSString *signalingKeyTokenPrint = [[NSData dataWithData:signalingKeyToken] base64EncodedString];
return signalingKeyTokenPrint;
}
+ (nullable NSString *)signalingKey
{
return [[self sharedInstance] signalingKey];
}
- (nullable NSString *)signalingKey
{
return [self.dbConnection stringForKey:TSAccountManager_ServerSignalingKey
inCollection:TSAccountManager_UserAccountCollection];
}
+ (nullable NSString *)serverAuthToken
{
return [[self sharedInstance] serverAuthToken];
}
- (nullable NSString *)serverAuthToken
{
return [self.dbConnection stringForKey:TSAccountManager_ServerAuthToken
inCollection:TSAccountManager_UserAccountCollection];
}
- (void)storeServerAuthToken:(NSString *)authToken signalingKey:(NSString *)signalingKey
{
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[transaction setObject:authToken
forKey:TSAccountManager_ServerAuthToken
inCollection:TSAccountManager_UserAccountCollection];
[transaction setObject:signalingKey
forKey:TSAccountManager_ServerSignalingKey
inCollection:TSAccountManager_UserAccountCollection];
}];
}
+ (void)unregisterTextSecureWithSuccess:(void (^)(void))success failure:(void (^)(NSError *error))failureBlock
{
10 years ago
[[TSNetworkManager sharedManager] makeRequest:[[TSUnregisterAccountRequest alloc] init]
success:^(NSURLSessionDataTask *task, id responseObject) {
DDLogInfo(@"%@ Successfully unregistered", self.logTag);
success();
// This is called from `[AppSettingsViewController proceedToUnregistration]` whose
// success handler calls `[Environment resetAppData]`.
// This method, after calling that success handler, fires
// `kNSNotificationName_RegistrationStateDidChange` which is only safe to fire after
// the data store is reset.
[[NSNotificationCenter defaultCenter]
postNotificationNameAsync:kNSNotificationName_RegistrationStateDidChange
object:nil
userInfo:nil];
10 years ago
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
if (!IsNSErrorNetworkFailure(error)) {
OWSProdError([OWSAnalyticsEvents accountsErrorUnregisterAccountRequestFailed]);
}
DDLogError(@"%@ Failed to unregister with error: %@", self.logTag, error);
failureBlock(error);
10 years ago
}];
}
@end
NS_ASSUME_NONNULL_END