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.

216 lines
9.3 KiB

5 years ago
5 years ago
  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 "FIRInstanceIDAuthKeyChain.h"
  17. #import "FIRInstanceIDKeychain.h"
  18. #import "FIRInstanceIDLogger.h"
  19. /**
  20. * The error type representing why we couldn't read data from the keychain.
  21. */
  22. typedef NS_ENUM(int, FIRInstanceIDKeychainErrorType) {
  23. kFIRInstanceIDKeychainErrorBadArguments = -1301,
  24. };
  25. NSString *const kFIRInstanceIDKeychainWildcardIdentifier = @"*";
  26. @interface FIRInstanceIDAuthKeychain ()
  27. @property(nonatomic, copy) NSString *generic;
  28. // cachedKeychainData is keyed by service and account, the value is an array of NSData.
  29. // It is used to cache the tokens per service, per account, as well as checkin data per service,
  30. // per account inside the keychain.
  31. @property(nonatomic)
  32. NSMutableDictionary<NSString *, NSMutableDictionary<NSString *, NSArray<NSData *> *> *>
  33. *cachedKeychainData;
  34. @end
  35. @implementation FIRInstanceIDAuthKeychain
  36. - (instancetype)initWithIdentifier:(NSString *)identifier {
  37. self = [super init];
  38. if (self) {
  39. _generic = [identifier copy];
  40. _cachedKeychainData = [[NSMutableDictionary alloc] init];
  41. }
  42. return self;
  43. }
  44. + (NSMutableDictionary *)keychainQueryForService:(NSString *)service
  45. account:(NSString *)account
  46. generic:(NSString *)generic {
  47. NSDictionary *query = @{(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword};
  48. NSMutableDictionary *finalQuery = [NSMutableDictionary dictionaryWithDictionary:query];
  49. if ([generic length] && ![kFIRInstanceIDKeychainWildcardIdentifier isEqualToString:generic]) {
  50. finalQuery[(__bridge NSString *)kSecAttrGeneric] = generic;
  51. }
  52. if ([account length] && ![kFIRInstanceIDKeychainWildcardIdentifier isEqualToString:account]) {
  53. finalQuery[(__bridge NSString *)kSecAttrAccount] = account;
  54. }
  55. if ([service length] && ![kFIRInstanceIDKeychainWildcardIdentifier isEqualToString:service]) {
  56. finalQuery[(__bridge NSString *)kSecAttrService] = service;
  57. }
  58. return finalQuery;
  59. }
  60. - (NSMutableDictionary *)keychainQueryForService:(NSString *)service account:(NSString *)account {
  61. return [[self class] keychainQueryForService:service account:account generic:self.generic];
  62. }
  63. - (NSArray<NSData *> *)itemsMatchingService:(NSString *)service account:(NSString *)account {
  64. // If query wildcard service, it asks for all the results, which always query from keychain.
  65. if (![service isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier] &&
  66. ![account isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier] &&
  67. _cachedKeychainData[service][account]) {
  68. // As long as service, account array exist, even it's empty, it means we've queried it before,
  69. // returns the cache value.
  70. return _cachedKeychainData[service][account];
  71. }
  72. NSMutableDictionary *keychainQuery = [self keychainQueryForService:service account:account];
  73. NSMutableArray<NSData *> *results;
  74. keychainQuery[(__bridge id)kSecReturnData] = (__bridge id)kCFBooleanTrue;
  75. #if TARGET_OS_IOS || TARGET_OS_TV
  76. keychainQuery[(__bridge id)kSecReturnAttributes] = (__bridge id)kCFBooleanTrue;
  77. keychainQuery[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitAll;
  78. // FIRInstanceIDKeychain should only take a query and return a result, will handle the query here.
  79. NSArray *passwordInfos =
  80. CFBridgingRelease([[FIRInstanceIDKeychain sharedInstance] itemWithQuery:keychainQuery]);
  81. #elif TARGET_OS_OSX || TARGET_OS_WATCH
  82. keychainQuery[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne;
  83. NSData *passwordInfos =
  84. CFBridgingRelease([[FIRInstanceIDKeychain sharedInstance] itemWithQuery:keychainQuery]);
  85. #endif
  86. if (!passwordInfos) {
  87. // Nothing was found, simply return from this sync block.
  88. // Make sure to label the cache entry empty, signaling that we've queried this entry.
  89. if ([service isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier] ||
  90. [account isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier]) {
  91. // Do not update cache if it's wildcard query.
  92. return @[];
  93. } else if (_cachedKeychainData[service]) {
  94. [_cachedKeychainData[service] setObject:@[] forKey:account];
  95. } else {
  96. [_cachedKeychainData setObject:[@{account : @[]} mutableCopy] forKey:service];
  97. }
  98. return @[];
  99. }
  100. results = [[NSMutableArray alloc] init];
  101. #if TARGET_OS_IOS || TARGET_OS_TV
  102. NSInteger numPasswords = passwordInfos.count;
  103. for (NSUInteger i = 0; i < numPasswords; i++) {
  104. NSDictionary *passwordInfo = [passwordInfos objectAtIndex:i];
  105. if (passwordInfo[(__bridge id)kSecValueData]) {
  106. [results addObject:passwordInfo[(__bridge id)kSecValueData]];
  107. }
  108. }
  109. #elif TARGET_OS_OSX || TARGET_OS_WATCH
  110. [results addObject:passwordInfos];
  111. #endif
  112. // We query the keychain because it didn't exist in cache, now query is done, update the result in
  113. // the cache.
  114. if ([service isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier] ||
  115. [account isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier]) {
  116. // Do not update cache if it's wildcard query.
  117. return [results copy];
  118. } else if (_cachedKeychainData[service]) {
  119. [_cachedKeychainData[service] setObject:[results copy] forKey:account];
  120. } else {
  121. NSMutableDictionary *entry = [@{account : [results copy]} mutableCopy];
  122. [_cachedKeychainData setObject:entry forKey:service];
  123. }
  124. return [results copy];
  125. }
  126. - (NSData *)dataForService:(NSString *)service account:(NSString *)account {
  127. NSArray<NSData *> *items = [self itemsMatchingService:service account:account];
  128. // If items is nil or empty, nil will be returned.
  129. return items.firstObject;
  130. }
  131. - (void)removeItemsMatchingService:(NSString *)service
  132. account:(NSString *)account
  133. handler:(void (^)(NSError *error))handler {
  134. if ([service isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier]) {
  135. // Delete all keychain items.
  136. _cachedKeychainData = [[NSMutableDictionary alloc] init];
  137. } else if ([account isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier]) {
  138. // Delete all entries under service,
  139. if (_cachedKeychainData[service]) {
  140. _cachedKeychainData[service] = [[NSMutableDictionary alloc] init];
  141. }
  142. } else if (_cachedKeychainData[service]) {
  143. // We should keep the service/account entry instead of nil so we know
  144. // it's "empty entry" instead of "not query from keychain yet".
  145. [_cachedKeychainData[service] setObject:@[] forKey:account];
  146. } else {
  147. [_cachedKeychainData setObject:[@{account : @[]} mutableCopy] forKey:service];
  148. }
  149. NSMutableDictionary *keychainQuery = [self keychainQueryForService:service account:account];
  150. [[FIRInstanceIDKeychain sharedInstance] removeItemWithQuery:keychainQuery handler:handler];
  151. }
  152. - (void)setData:(NSData *)data
  153. forService:(NSString *)service
  154. account:(NSString *)account
  155. handler:(void (^)(NSError *))handler {
  156. if ([service isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier] ||
  157. [account isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier]) {
  158. if (handler) {
  159. handler([NSError errorWithDomain:kFIRInstanceIDKeychainErrorDomain
  160. code:kFIRInstanceIDKeychainErrorBadArguments
  161. userInfo:nil]);
  162. }
  163. return;
  164. }
  165. [self removeItemsMatchingService:service
  166. account:account
  167. handler:^(NSError *error) {
  168. if (error) {
  169. if (handler) {
  170. handler(error);
  171. }
  172. return;
  173. }
  174. if (data.length > 0) {
  175. NSMutableDictionary *keychainQuery =
  176. [self keychainQueryForService:service account:account];
  177. keychainQuery[(__bridge id)kSecValueData] = data;
  178. keychainQuery[(__bridge id)kSecAttrAccessible] =
  179. (__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly;
  180. [[FIRInstanceIDKeychain sharedInstance]
  181. addItemWithQuery:keychainQuery
  182. handler:handler];
  183. }
  184. }];
  185. // Set the cache value. This must happen after removeItemsMatchingService:account:handler was
  186. // called, so the cache value was reset before setting a new value.
  187. if (_cachedKeychainData[service]) {
  188. if (_cachedKeychainData[service][account]) {
  189. _cachedKeychainData[service][account] = @[ data ];
  190. } else {
  191. [_cachedKeychainData[service] setObject:@[ data ] forKey:account];
  192. }
  193. } else {
  194. [_cachedKeychainData setObject:[@{account : @[ data ]} mutableCopy] forKey:service];
  195. }
  196. }
  197. @end