No Description

FIRInstanceIDAuthKeyChain.m 9.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  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
  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
  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. accessibility:(CFTypeRef)accessibility
  155. account:(NSString *)account
  156. handler:(void (^)(NSError *))handler {
  157. if ([service isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier] ||
  158. [account isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier]) {
  159. if (handler) {
  160. handler([NSError errorWithDomain:kFIRInstanceIDKeychainErrorDomain
  161. code:kFIRInstanceIDKeychainErrorBadArguments
  162. userInfo:nil]);
  163. }
  164. return;
  165. }
  166. [self removeItemsMatchingService:service
  167. account:account
  168. handler:^(NSError *error) {
  169. if (error) {
  170. if (handler) {
  171. handler(error);
  172. }
  173. return;
  174. }
  175. if (data.length > 0) {
  176. NSMutableDictionary *keychainQuery =
  177. [self keychainQueryForService:service account:account];
  178. keychainQuery[(__bridge id)kSecValueData] = data;
  179. if (accessibility != NULL) {
  180. keychainQuery[(__bridge id)kSecAttrAccessible] =
  181. (__bridge id)accessibility;
  182. } else {
  183. // Defaults to No backup
  184. keychainQuery[(__bridge id)kSecAttrAccessible] =
  185. (__bridge id)kSecAttrAccessibleAlwaysThisDeviceOnly;
  186. }
  187. [[FIRInstanceIDKeychain sharedInstance]
  188. addItemWithQuery:keychainQuery
  189. handler:handler];
  190. }
  191. }];
  192. // Set the cache value. This must happen after removeItemsMatchingService:account:handler was
  193. // called, so the cache value was reset before setting a new value.
  194. if (_cachedKeychainData[service]) {
  195. if (_cachedKeychainData[service][account]) {
  196. _cachedKeychainData[service][account] = @[ data ];
  197. } else {
  198. [_cachedKeychainData[service] setObject:@[ data ] forKey:account];
  199. }
  200. } else {
  201. [_cachedKeychainData setObject:[@{account : @[ data ]} mutableCopy] forKey:service];
  202. }
  203. }
  204. @end