You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

236 lines
9.8 KiB

  1. /*
  2. * Copyright 2019 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import "FIRInstanceIDCheckinStore.h"
  17. #import "FIRInstanceIDAuthKeyChain.h"
  18. #import "FIRInstanceIDBackupExcludedPlist.h"
  19. #import "FIRInstanceIDCheckinPreferences+Internal.h"
  20. #import "FIRInstanceIDCheckinPreferences_Private.h"
  21. #import "FIRInstanceIDCheckinService.h"
  22. #import "FIRInstanceIDLogger.h"
  23. #import "FIRInstanceIDUtilities.h"
  24. #import "FIRInstanceIDVersionUtilities.h"
  25. #import "NSError+FIRInstanceID.h"
  26. static NSString *const kFIRInstanceIDCheckinKeychainGeneric = @"com.google.iid";
  27. NSString *const kFIRInstanceIDCheckinKeychainService = @"com.google.iid.checkin";
  28. NSString *const kFIRInstanceIDLegacyCheckinKeychainAccount = @"com.google.iid.checkin-account";
  29. NSString *const kFIRInstanceIDLegacyCheckinKeychainService = @"com.google.iid.checkin-service";
  30. // Checkin plist used to have the deviceID and secret stored in them and that's why they
  31. // had 6 items in it. Since the deviceID and secret have been moved to the keychain
  32. // there would only be 4 items.
  33. static const NSInteger kOldCheckinPlistCount = 6;
  34. @interface FIRInstanceIDCheckinStore ()
  35. @property(nonatomic, readwrite, strong) FIRInstanceIDBackupExcludedPlist *plist;
  36. @property(nonatomic, readwrite, strong) FIRInstanceIDAuthKeychain *keychain;
  37. // Checkin will store items under
  38. // Keychain account: <app bundle id>,
  39. // Keychain service: |kFIRInstanceIDCheckinKeychainService|
  40. @property(nonatomic, readonly) NSString *bundleIdentifierForKeychainAccount;
  41. @end
  42. @implementation FIRInstanceIDCheckinStore
  43. - (instancetype)initWithCheckinPlistFileName:(NSString *)checkinFilename
  44. subDirectoryName:(NSString *)subDirectoryName {
  45. FIRInstanceIDBackupExcludedPlist *plist =
  46. [[FIRInstanceIDBackupExcludedPlist alloc] initWithFileName:checkinFilename
  47. subDirectory:subDirectoryName];
  48. FIRInstanceIDAuthKeychain *keychain =
  49. [[FIRInstanceIDAuthKeychain alloc] initWithIdentifier:kFIRInstanceIDCheckinKeychainGeneric];
  50. return [self initWithCheckinPlist:plist keychain:keychain];
  51. }
  52. - (instancetype)initWithCheckinPlist:(FIRInstanceIDBackupExcludedPlist *)plist
  53. keychain:(FIRInstanceIDAuthKeychain *)keychain {
  54. self = [super init];
  55. if (self) {
  56. _plist = plist;
  57. _keychain = keychain;
  58. }
  59. return self;
  60. }
  61. - (BOOL)hasCheckinPlist {
  62. return [self.plist doesFileExist];
  63. }
  64. - (NSString *)bundleIdentifierForKeychainAccount {
  65. static NSString *bundleIdentifier;
  66. static dispatch_once_t onceToken;
  67. dispatch_once(&onceToken, ^{
  68. bundleIdentifier = FIRInstanceIDAppIdentifier();
  69. });
  70. return bundleIdentifier;
  71. }
  72. - (void)saveCheckinPreferences:(FIRInstanceIDCheckinPreferences *)preferences
  73. handler:(void (^)(NSError *error))handler {
  74. NSDictionary *checkinPlistContents = [preferences checkinPlistContents];
  75. NSString *checkinKeychainContent = [preferences checkinKeychainContent];
  76. if (![checkinKeychainContent length]) {
  77. FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeCheckinStore000,
  78. @"Failed to get checkin keychain content from memory.");
  79. if (handler) {
  80. handler([NSError
  81. errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeRegistrarFailedToCheckIn]);
  82. }
  83. return;
  84. }
  85. if (![checkinPlistContents count]) {
  86. FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeCheckinStore001,
  87. @"Failed to get checkin plist contents from memory.");
  88. if (handler) {
  89. handler([NSError
  90. errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeRegistrarFailedToCheckIn]);
  91. }
  92. return;
  93. }
  94. // Save all other checkin preferences in a plist
  95. NSError *error;
  96. if (![self.plist writeDictionary:checkinPlistContents error:&error]) {
  97. FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeCheckinStore003,
  98. @"Failed to save checkin plist contents."
  99. @"Will delete auth credentials");
  100. [self.keychain removeItemsMatchingService:kFIRInstanceIDCheckinKeychainService
  101. account:self.bundleIdentifierForKeychainAccount
  102. handler:nil];
  103. if (handler) {
  104. handler(error);
  105. }
  106. return;
  107. }
  108. FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeCheckinStoreCheckinPlistSaved,
  109. @"Checkin plist file is saved");
  110. // Save the deviceID and secret in the Keychain
  111. if (!preferences.hasPreCachedAuthCredentials) {
  112. NSData *data = [checkinKeychainContent dataUsingEncoding:NSUTF8StringEncoding];
  113. [self.keychain setData:data
  114. forService:kFIRInstanceIDCheckinKeychainService
  115. account:self.bundleIdentifierForKeychainAccount
  116. handler:^(NSError *error) {
  117. if (error) {
  118. if (handler) {
  119. handler(error);
  120. }
  121. return;
  122. }
  123. if (handler) {
  124. handler(nil);
  125. }
  126. }];
  127. } else {
  128. handler(nil);
  129. }
  130. }
  131. - (void)removeCheckinPreferencesWithHandler:(void (^)(NSError *error))handler {
  132. // Delete the checkin preferences plist first to avoid delay.
  133. NSError *deletePlistError;
  134. if (![self.plist deleteFile:&deletePlistError]) {
  135. handler(deletePlistError);
  136. return;
  137. }
  138. FIRInstanceIDLoggerDebug(kFIRInstanceIDMessageCodeCheckinStoreCheckinPlistDeleted,
  139. @"Deleted checkin plist file.");
  140. // Remove deviceID and secret from Keychain
  141. [self.keychain
  142. removeItemsMatchingService:kFIRInstanceIDCheckinKeychainService
  143. account:self.bundleIdentifierForKeychainAccount
  144. handler:^(NSError *error) {
  145. // Try to remove from old location as well because migration
  146. // is no longer needed. Consider this is either a fresh install
  147. // or an identity wipe.
  148. [self.keychain
  149. removeItemsMatchingService:kFIRInstanceIDLegacyCheckinKeychainService
  150. account:kFIRInstanceIDLegacyCheckinKeychainAccount
  151. handler:nil];
  152. handler(error);
  153. }];
  154. }
  155. - (FIRInstanceIDCheckinPreferences *)cachedCheckinPreferences {
  156. // Query the keychain for deviceID and secret
  157. NSData *item = [self.keychain dataForService:kFIRInstanceIDCheckinKeychainService
  158. account:self.bundleIdentifierForKeychainAccount];
  159. // Check info found in keychain
  160. NSString *checkinKeychainContent = [[NSString alloc] initWithData:item
  161. encoding:NSUTF8StringEncoding];
  162. FIRInstanceIDCheckinPreferences *checkinPreferences =
  163. [FIRInstanceIDCheckinPreferences preferencesFromKeychainContents:checkinKeychainContent];
  164. NSDictionary *checkinPlistContents = [self.plist contentAsDictionary];
  165. NSString *plistDeviceAuthID = checkinPlistContents[kFIRInstanceIDDeviceAuthIdKey];
  166. NSString *plistSecretToken = checkinPlistContents[kFIRInstanceIDSecretTokenKey];
  167. // If deviceID and secret not found in the keychain verify that we don't have them in the
  168. // checkin preferences plist.
  169. if (![checkinPreferences.deviceID length] && ![checkinPreferences.secretToken length]) {
  170. if ([plistDeviceAuthID length] && [plistSecretToken length]) {
  171. // Couldn't find checkin credentials in keychain but found them in the plist.
  172. checkinPreferences =
  173. [[FIRInstanceIDCheckinPreferences alloc] initWithDeviceID:plistDeviceAuthID
  174. secretToken:plistSecretToken];
  175. } else {
  176. // Couldn't find checkin credentials in keychain nor plist
  177. return nil;
  178. }
  179. } else if (kOldCheckinPlistCount == checkinPlistContents.count) {
  180. // same check as above but just to be extra sure that we cover all upgrade cases properly.
  181. // TODO(chliangGoogle): Remove this case, after verifying it's not needed
  182. if ([plistDeviceAuthID length] && [plistSecretToken length]) {
  183. checkinPreferences =
  184. [[FIRInstanceIDCheckinPreferences alloc] initWithDeviceID:plistDeviceAuthID
  185. secretToken:plistSecretToken];
  186. }
  187. }
  188. [checkinPreferences updateWithCheckinPlistContents:checkinPlistContents];
  189. return checkinPreferences;
  190. }
  191. - (void)migrateCheckinItemIfNeeded {
  192. // Check for checkin in the old location, using the legacy keys
  193. // Query the keychain for deviceID and secret
  194. NSData *dataInOldLocation =
  195. [self.keychain dataForService:kFIRInstanceIDLegacyCheckinKeychainService
  196. account:kFIRInstanceIDLegacyCheckinKeychainAccount];
  197. if (dataInOldLocation) {
  198. // Save to new location
  199. [self.keychain setData:dataInOldLocation
  200. forService:kFIRInstanceIDCheckinKeychainService
  201. account:self.bundleIdentifierForKeychainAccount
  202. handler:nil];
  203. // Remove from old location
  204. [self.keychain removeItemsMatchingService:kFIRInstanceIDLegacyCheckinKeychainService
  205. account:kFIRInstanceIDLegacyCheckinKeychainAccount
  206. handler:nil];
  207. }
  208. }
  209. @end