No Description

FIRInstanceIDAuthKeyChain.m 9.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  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, copy)
  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. keychainQuery[(__bridge id)kSecReturnAttributes] = (__bridge id)kCFBooleanTrue;
  76. keychainQuery[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitAll;
  77. // FIRInstanceIDKeychain should only take a query and return a result, will handle the query here.
  78. CFArrayRef passwordInfos = [[FIRInstanceIDKeychain sharedInstance] itemWithQuery:keychainQuery];
  79. if (!passwordInfos) {
  80. // Nothing was found, simply return from this sync block.
  81. // Make sure to label the cache entry empty, signaling that we've queried this entry.
  82. if ([service isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier] ||
  83. [account isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier]) {
  84. // Do not update cache if it's wildcard query.
  85. return @[];
  86. } else if (_cachedKeychainData[service]) {
  87. [_cachedKeychainData[service] setObject:@[] forKey:account];
  88. } else {
  89. [_cachedKeychainData setObject:[@{account : @[]} mutableCopy] forKey:service];
  90. }
  91. return @[];
  92. }
  93. NSInteger numPasswords = CFArrayGetCount(passwordInfos);
  94. results = [[NSMutableArray alloc] init];
  95. if (0 < numPasswords) {
  96. for (NSUInteger i = 0; i < numPasswords; i++) {
  97. NSDictionary *passwordInfo = [((__bridge NSArray *)passwordInfos) objectAtIndex:i];
  98. if (passwordInfo[(__bridge id)kSecValueData]) {
  99. [results addObject:passwordInfo[(__bridge id)kSecValueData]];
  100. }
  101. }
  102. }
  103. if (passwordInfos != NULL) {
  104. CFRelease(passwordInfos);
  105. }
  106. // We query the keychain because it didn't exist in cache, now query is done, update the result in
  107. // the cache.
  108. if ([service isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier] ||
  109. [account isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier]) {
  110. // Do not update cache if it's wildcard query.
  111. return [results copy];
  112. } else if (_cachedKeychainData[service]) {
  113. [_cachedKeychainData[service] setObject:[results copy] forKey:account];
  114. } else {
  115. NSMutableDictionary *entry = [@{account : [results copy]} mutableCopy];
  116. [_cachedKeychainData setObject:entry forKey:service];
  117. }
  118. return [results copy];
  119. }
  120. - (NSData *)dataForService:(NSString *)service account:(NSString *)account {
  121. NSArray<NSData *> *items = [self itemsMatchingService:service account:account];
  122. // If items is nil or empty, nil will be returned.
  123. return items.firstObject;
  124. }
  125. - (void)removeItemsMatchingService:(NSString *)service
  126. account:(NSString *)account
  127. handler:(void (^)(NSError *error))handler {
  128. if ([service isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier]) {
  129. // Delete all keychain items.
  130. _cachedKeychainData = [[NSMutableDictionary alloc] init];
  131. } else if ([account isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier]) {
  132. // Delete all entries under service,
  133. if (_cachedKeychainData[service]) {
  134. _cachedKeychainData[service] = [[NSMutableDictionary alloc] init];
  135. }
  136. } else if (_cachedKeychainData[service]) {
  137. // We should keep the service/account entry instead of nil so we know
  138. // it's "empty entry" instead of "not query from keychain yet".
  139. [_cachedKeychainData[service] setObject:@[] forKey:account];
  140. } else {
  141. [_cachedKeychainData setObject:[@{account : @[]} mutableCopy] forKey:service];
  142. }
  143. NSMutableDictionary *keychainQuery = [self keychainQueryForService:service account:account];
  144. [[FIRInstanceIDKeychain sharedInstance] removeItemWithQuery:keychainQuery handler:handler];
  145. }
  146. - (void)setData:(NSData *)data
  147. forService:(NSString *)service
  148. accessibility:(CFTypeRef)accessibility
  149. account:(NSString *)account
  150. handler:(void (^)(NSError *))handler {
  151. if ([service isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier] ||
  152. [account isEqualToString:kFIRInstanceIDKeychainWildcardIdentifier]) {
  153. if (handler) {
  154. handler([NSError errorWithDomain:kFIRInstanceIDKeychainErrorDomain
  155. code:kFIRInstanceIDKeychainErrorBadArguments
  156. userInfo:nil]);
  157. }
  158. return;
  159. }
  160. [self removeItemsMatchingService:service
  161. account:account
  162. handler:^(NSError *error) {
  163. if (error) {
  164. if (handler) {
  165. handler(error);
  166. }
  167. return;
  168. }
  169. if (data.length > 0) {
  170. NSMutableDictionary *keychainQuery =
  171. [self keychainQueryForService:service account:account];
  172. keychainQuery[(__bridge id)kSecValueData] = data;
  173. if (accessibility != NULL) {
  174. keychainQuery[(__bridge id)kSecAttrAccessible] =
  175. (__bridge id)accessibility;
  176. } else {
  177. // Defaults to No backup
  178. keychainQuery[(__bridge id)kSecAttrAccessible] =
  179. (__bridge id)kSecAttrAccessibleAlwaysThisDeviceOnly;
  180. }
  181. [[FIRInstanceIDKeychain sharedInstance]
  182. addItemWithQuery:keychainQuery
  183. handler:handler];
  184. }
  185. }];
  186. // Set the cache value. This must happen after removeItemsMatchingService:account:handler was
  187. // called, so the cache value was reset before setting a new value.
  188. if (_cachedKeychainData[service]) {
  189. if (_cachedKeychainData[service][account]) {
  190. _cachedKeychainData[service][account] = @[ data ];
  191. } else {
  192. [_cachedKeychainData[service] setObject:@[ data ] forKey:account];
  193. }
  194. } else {
  195. [_cachedKeychainData setObject:[@{account : @[ data ]} mutableCopy] forKey:service];
  196. }
  197. }
  198. @end