FIRMessagingAuthKeychain.m 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  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 "FirebaseMessaging/Sources/Token/FIRMessagingAuthKeychain.h"
  17. #import "FirebaseMessaging/Sources/FIRMessagingLogger.h"
  18. #import "FirebaseMessaging/Sources/Token/FIRMessagingKeychain.h"
  19. /**
  20. * The error type representing why we couldn't read data from the keychain.
  21. */
  22. typedef NS_ENUM(int, FIRMessagingKeychainErrorType) {
  23. kFIRMessagingKeychainErrorBadArguments = -1301,
  24. };
  25. NSString *const kFIRMessagingKeychainWildcardIdentifier = @"*";
  26. @interface FIRMessagingAuthKeychain ()
  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, strong)
  32. NSMutableDictionary<NSString *, NSMutableDictionary<NSString *, NSArray<NSData *> *> *>
  33. *cachedKeychainData;
  34. @end
  35. @implementation FIRMessagingAuthKeychain
  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] && ![kFIRMessagingKeychainWildcardIdentifier isEqualToString:generic]) {
  50. finalQuery[(__bridge NSString *)kSecAttrGeneric] = generic;
  51. }
  52. if ([account length] && ![kFIRMessagingKeychainWildcardIdentifier isEqualToString:account]) {
  53. finalQuery[(__bridge NSString *)kSecAttrAccount] = account;
  54. }
  55. if ([service length] && ![kFIRMessagingKeychainWildcardIdentifier isEqualToString:service]) {
  56. finalQuery[(__bridge NSString *)kSecAttrService] = service;
  57. }
  58. if (@available(iOS 13.0, macOS 10.15, macCatalyst 13.0, tvOS 13.0, watchOS 6.0, *)) {
  59. // Ensures that the keychain query behaves the same across all platforms.
  60. // See go/firebase-macos-keychain-popups for details.
  61. finalQuery[(__bridge id)kSecUseDataProtectionKeychain] = (__bridge id)kCFBooleanTrue;
  62. }
  63. return finalQuery;
  64. }
  65. - (NSMutableDictionary *)keychainQueryForService:(NSString *)service account:(NSString *)account {
  66. return [[self class] keychainQueryForService:service account:account generic:self.generic];
  67. }
  68. - (NSArray<NSData *> *)itemsMatchingService:(NSString *)service account:(NSString *)account {
  69. // If query wildcard service, it asks for all the results, which always query from keychain.
  70. if (![service isEqualToString:kFIRMessagingKeychainWildcardIdentifier] &&
  71. ![account isEqualToString:kFIRMessagingKeychainWildcardIdentifier] &&
  72. _cachedKeychainData[service][account]) {
  73. // As long as service, account array exist, even it's empty, it means we've queried it before,
  74. // returns the cache value.
  75. return _cachedKeychainData[service][account];
  76. }
  77. NSMutableDictionary *keychainQuery = [self keychainQueryForService:service account:account];
  78. NSMutableArray<NSData *> *results;
  79. keychainQuery[(__bridge id)kSecReturnData] = (__bridge id)kCFBooleanTrue;
  80. #if TARGET_OS_IOS || TARGET_OS_TV
  81. keychainQuery[(__bridge id)kSecReturnAttributes] = (__bridge id)kCFBooleanTrue;
  82. keychainQuery[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitAll;
  83. // FIRMessagingKeychain should only take a query and return a result, will handle the query here.
  84. NSArray *passwordInfos =
  85. CFBridgingRelease([[FIRMessagingKeychain sharedInstance] itemWithQuery:keychainQuery]);
  86. #elif TARGET_OS_OSX || TARGET_OS_WATCH
  87. keychainQuery[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne;
  88. NSData *passwordInfos =
  89. CFBridgingRelease([[FIRMessagingKeychain sharedInstance] itemWithQuery:keychainQuery]);
  90. #endif
  91. if (!passwordInfos) {
  92. // Nothing was found, simply return from this sync block.
  93. // Make sure to label the cache entry empty, signaling that we've queried this entry.
  94. if ([service isEqualToString:kFIRMessagingKeychainWildcardIdentifier] ||
  95. [account isEqualToString:kFIRMessagingKeychainWildcardIdentifier]) {
  96. // Do not update cache if it's wildcard query.
  97. return @[];
  98. } else if (_cachedKeychainData[service]) {
  99. [_cachedKeychainData[service] setObject:@[] forKey:account];
  100. } else {
  101. [_cachedKeychainData setObject:[@{account : @[]} mutableCopy] forKey:service];
  102. }
  103. return @[];
  104. }
  105. results = [[NSMutableArray alloc] init];
  106. #if TARGET_OS_IOS || TARGET_OS_TV
  107. NSInteger numPasswords = passwordInfos.count;
  108. for (NSUInteger i = 0; i < numPasswords; i++) {
  109. NSDictionary *passwordInfo = [passwordInfos objectAtIndex:i];
  110. if (passwordInfo[(__bridge id)kSecValueData]) {
  111. [results addObject:passwordInfo[(__bridge id)kSecValueData]];
  112. }
  113. }
  114. #elif TARGET_OS_OSX || TARGET_OS_WATCH
  115. [results addObject:passwordInfos];
  116. #endif
  117. // We query the keychain because it didn't exist in cache, now query is done, update the result in
  118. // the cache.
  119. if ([service isEqualToString:kFIRMessagingKeychainWildcardIdentifier] ||
  120. [account isEqualToString:kFIRMessagingKeychainWildcardIdentifier]) {
  121. // Do not update cache if it's wildcard query.
  122. return [results copy];
  123. } else if (_cachedKeychainData[service]) {
  124. [_cachedKeychainData[service] setObject:[results copy] forKey:account];
  125. } else {
  126. NSMutableDictionary *entry = [@{account : [results copy]} mutableCopy];
  127. [_cachedKeychainData setObject:entry forKey:service];
  128. }
  129. return [results copy];
  130. }
  131. - (NSData *)dataForService:(NSString *)service account:(NSString *)account {
  132. NSArray<NSData *> *items = [self itemsMatchingService:service account:account];
  133. // If items is nil or empty, nil will be returned.
  134. return items.firstObject;
  135. }
  136. - (void)removeItemsMatchingService:(NSString *)service
  137. account:(NSString *)account
  138. handler:(void (^)(NSError *error))handler {
  139. if ([service isEqualToString:kFIRMessagingKeychainWildcardIdentifier]) {
  140. // Delete all keychain items.
  141. _cachedKeychainData = [[NSMutableDictionary alloc] init];
  142. } else if ([account isEqualToString:kFIRMessagingKeychainWildcardIdentifier]) {
  143. // Delete all entries under service,
  144. if (_cachedKeychainData[service]) {
  145. _cachedKeychainData[service] = [[NSMutableDictionary alloc] init];
  146. }
  147. } else if (_cachedKeychainData[service]) {
  148. // We should keep the service/account entry instead of nil so we know
  149. // it's "empty entry" instead of "not query from keychain yet".
  150. [_cachedKeychainData[service] setObject:@[] forKey:account];
  151. } else {
  152. [_cachedKeychainData setObject:[@{account : @[]} mutableCopy] forKey:service];
  153. }
  154. NSMutableDictionary *keychainQuery = [self keychainQueryForService:service account:account];
  155. [[FIRMessagingKeychain sharedInstance] removeItemWithQuery:keychainQuery handler:handler];
  156. }
  157. - (void)setData:(NSData *)data
  158. forService:(NSString *)service
  159. account:(NSString *)account
  160. handler:(void (^)(NSError *))handler {
  161. if ([service isEqualToString:kFIRMessagingKeychainWildcardIdentifier] ||
  162. [account isEqualToString:kFIRMessagingKeychainWildcardIdentifier]) {
  163. if (handler) {
  164. handler([NSError errorWithDomain:kFIRMessagingKeychainErrorDomain
  165. code:kFIRMessagingKeychainErrorBadArguments
  166. userInfo:nil]);
  167. }
  168. return;
  169. }
  170. [self removeItemsMatchingService:service
  171. account:account
  172. handler:^(NSError *error) {
  173. if (error) {
  174. if (handler) {
  175. handler(error);
  176. }
  177. return;
  178. }
  179. if (data.length > 0) {
  180. NSMutableDictionary *keychainQuery =
  181. [self keychainQueryForService:service account:account];
  182. keychainQuery[(__bridge id)kSecValueData] = data;
  183. keychainQuery[(__bridge id)kSecAttrAccessible] =
  184. (__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly;
  185. [[FIRMessagingKeychain sharedInstance] addItemWithQuery:keychainQuery
  186. handler:handler];
  187. }
  188. }];
  189. // Set the cache value. This must happen after removeItemsMatchingService:account:handler was
  190. // called, so the cache value was reset before setting a new value.
  191. if (_cachedKeychainData[service]) {
  192. if (_cachedKeychainData[service][account]) {
  193. _cachedKeychainData[service][account] = @[ data ];
  194. } else {
  195. [_cachedKeychainData[service] setObject:@[ data ] forKey:account];
  196. }
  197. } else {
  198. [_cachedKeychainData setObject:[@{account : @[ data ]} mutableCopy] forKey:service];
  199. }
  200. }
  201. - (void)setCacheData:(NSData *)data forService:(NSString *)service account:(NSString *)account {
  202. if (_cachedKeychainData[service]) {
  203. if (_cachedKeychainData[service][account]) {
  204. _cachedKeychainData[service][account] = @[ data ];
  205. } else {
  206. [_cachedKeychainData[service] setObject:@[ data ] forKey:account];
  207. }
  208. } else {
  209. [_cachedKeychainData setObject:[@{account : @[ data ]} mutableCopy] forKey:service];
  210. }
  211. }
  212. @end