FIRSecureStorage.m 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  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 "FIRSecureStorage.h"
  17. #import <Security/Security.h>
  18. #if __has_include(<FBLPromises/FBLPromises.h>)
  19. #import <FBLPromises/FBLPromises.h>
  20. #else
  21. #import "FBLPromises.h"
  22. #endif
  23. #import "FIRInstallationsErrorUtil.h"
  24. #import "FIRInstallationsKeychainUtils.h"
  25. @interface FIRSecureStorage ()
  26. @property(nonatomic, readonly) dispatch_queue_t keychainQueue;
  27. @property(nonatomic, readonly) dispatch_queue_t inMemoryCacheQueue;
  28. @property(nonatomic, readonly) NSString *service;
  29. @property(nonatomic, readonly) NSCache<NSString *, id<NSSecureCoding>> *inMemoryCache;
  30. @end
  31. @implementation FIRSecureStorage
  32. - (instancetype)init {
  33. NSCache *cache = [[NSCache alloc] init];
  34. // Cache up to 5 installations.
  35. cache.countLimit = 5;
  36. return [self initWithService:@"com.firebase.FIRInstallations.installations" cache:cache];
  37. }
  38. - (instancetype)initWithService:(NSString *)service cache:(NSCache *)cache {
  39. self = [super init];
  40. if (self) {
  41. _keychainQueue = dispatch_queue_create(
  42. "com.firebase.FIRInstallations.FIRSecureStorage.Keychain", DISPATCH_QUEUE_SERIAL);
  43. _inMemoryCacheQueue = dispatch_queue_create(
  44. "com.firebase.FIRInstallations.FIRSecureStorage.InMemoryCache", DISPATCH_QUEUE_SERIAL);
  45. _service = [service copy];
  46. _inMemoryCache = cache;
  47. }
  48. return self;
  49. }
  50. #pragma mark - Public
  51. - (FBLPromise<id<NSSecureCoding>> *)getObjectForKey:(NSString *)key
  52. objectClass:(Class)objectClass
  53. accessGroup:(nullable NSString *)accessGroup {
  54. return [FBLPromise onQueue:self.inMemoryCacheQueue
  55. do:^id _Nullable {
  56. // Return cached object or fail otherwise.
  57. id object = [self.inMemoryCache objectForKey:key];
  58. return object
  59. ?: [[NSError alloc]
  60. initWithDomain:FBLPromiseErrorDomain
  61. code:FBLPromiseErrorCodeValidationFailure
  62. userInfo:nil];
  63. }]
  64. .recover(^id _Nullable(NSError *error) {
  65. // Look for the object in the keychain.
  66. return [self getObjectFromKeychainForKey:key
  67. objectClass:objectClass
  68. accessGroup:accessGroup];
  69. });
  70. }
  71. - (FBLPromise<NSNull *> *)setObject:(id<NSSecureCoding>)object
  72. forKey:(NSString *)key
  73. accessGroup:(nullable NSString *)accessGroup {
  74. return [FBLPromise onQueue:self.inMemoryCacheQueue
  75. do:^id _Nullable {
  76. // Save to the in-memory cache first.
  77. [self.inMemoryCache setObject:object forKey:[key copy]];
  78. return [NSNull null];
  79. }]
  80. .thenOn(self.keychainQueue, ^id(id result) {
  81. // Then store the object to the keychain.
  82. NSDictionary *query = [self keychainQueryWithKey:key accessGroup:accessGroup];
  83. NSError *error;
  84. NSData *encodedObject = [self archiveDataForObject:object error:&error];
  85. if (!encodedObject) {
  86. return error;
  87. }
  88. if (![FIRInstallationsKeychainUtils setItem:encodedObject withQuery:query error:&error]) {
  89. return error;
  90. }
  91. return [NSNull null];
  92. });
  93. }
  94. - (FBLPromise<NSNull *> *)removeObjectForKey:(NSString *)key
  95. accessGroup:(nullable NSString *)accessGroup {
  96. return [FBLPromise onQueue:self.inMemoryCacheQueue
  97. do:^id _Nullable {
  98. [self.inMemoryCache removeObjectForKey:key];
  99. return nil;
  100. }]
  101. .thenOn(self.keychainQueue, ^id(id result) {
  102. NSDictionary *query = [self keychainQueryWithKey:key accessGroup:accessGroup];
  103. NSError *error;
  104. if (![FIRInstallationsKeychainUtils removeItemWithQuery:query error:&error]) {
  105. return error;
  106. }
  107. return [NSNull null];
  108. });
  109. }
  110. #pragma mark - Private
  111. - (FBLPromise<id<NSSecureCoding>> *)getObjectFromKeychainForKey:(NSString *)key
  112. objectClass:(Class)objectClass
  113. accessGroup:(nullable NSString *)accessGroup {
  114. // Look for the object in the keychain.
  115. return [FBLPromise onQueue:self.keychainQueue
  116. do:^id {
  117. NSDictionary *query = [self keychainQueryWithKey:key
  118. accessGroup:accessGroup];
  119. NSError *error;
  120. NSData *encodedObject =
  121. [FIRInstallationsKeychainUtils getItemWithQuery:query error:&error];
  122. if (error) {
  123. return error;
  124. }
  125. if (!encodedObject) {
  126. return nil;
  127. }
  128. id object = [self unarchivedObjectOfClass:objectClass
  129. fromData:encodedObject
  130. error:&error];
  131. if (error) {
  132. return error;
  133. }
  134. return object;
  135. }]
  136. .thenOn(self.inMemoryCacheQueue,
  137. ^id<NSSecureCoding> _Nullable(id<NSSecureCoding> _Nullable object) {
  138. // Save object to the in-memory cache if exists and return the object.
  139. if (object) {
  140. [self.inMemoryCache setObject:object forKey:[key copy]];
  141. }
  142. return object;
  143. });
  144. }
  145. - (void)resetInMemoryCache {
  146. [self.inMemoryCache removeAllObjects];
  147. }
  148. #pragma mark - Keychain
  149. - (NSMutableDictionary<NSString *, id> *)keychainQueryWithKey:(NSString *)key
  150. accessGroup:(nullable NSString *)accessGroup {
  151. NSMutableDictionary<NSString *, id> *query = [NSMutableDictionary dictionary];
  152. query[(__bridge NSString *)kSecClass] = (__bridge NSString *)kSecClassGenericPassword;
  153. query[(__bridge NSString *)kSecAttrService] = self.service;
  154. query[(__bridge NSString *)kSecAttrAccount] = key;
  155. if (accessGroup) {
  156. query[(__bridge NSString *)kSecAttrAccessGroup] = accessGroup;
  157. }
  158. #if TARGET_OS_OSX
  159. if (self.keychainRef) {
  160. query[(__bridge NSString *)kSecUseKeychain] = (__bridge id)(self.keychainRef);
  161. query[(__bridge NSString *)kSecMatchSearchList] = @[ (__bridge id)(self.keychainRef) ];
  162. }
  163. #endif // TARGET_OSX
  164. return query;
  165. }
  166. - (nullable NSData *)archiveDataForObject:(id<NSSecureCoding>)object error:(NSError **)outError {
  167. NSData *archiveData;
  168. if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, *)) {
  169. NSError *error;
  170. archiveData = [NSKeyedArchiver archivedDataWithRootObject:object
  171. requiringSecureCoding:YES
  172. error:&error];
  173. if (error && outError) {
  174. *outError = [FIRInstallationsErrorUtil keyedArchiverErrorWithError:error];
  175. }
  176. } else {
  177. @try {
  178. NSMutableData *data = [NSMutableData data];
  179. #pragma clang diagnostic push
  180. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  181. NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
  182. #pragma clang diagnostic pop
  183. archiver.requiresSecureCoding = YES;
  184. [archiver encodeObject:object forKey:NSKeyedArchiveRootObjectKey];
  185. [archiver finishEncoding];
  186. archiveData = [data copy];
  187. } @catch (NSException *exception) {
  188. if (outError) {
  189. *outError = [FIRInstallationsErrorUtil keyedArchiverErrorWithException:exception];
  190. }
  191. }
  192. }
  193. return archiveData;
  194. }
  195. - (nullable id)unarchivedObjectOfClass:(Class)class
  196. fromData:(NSData *)data
  197. error:(NSError **)outError {
  198. id object;
  199. if (@available(macOS 10.13, iOS 11.0, tvOS 11.0, *)) {
  200. NSError *error;
  201. object = [NSKeyedUnarchiver unarchivedObjectOfClass:class fromData:data error:&error];
  202. if (error && outError) {
  203. *outError = [FIRInstallationsErrorUtil keyedArchiverErrorWithError:error];
  204. }
  205. } else {
  206. @try {
  207. #pragma clang diagnostic push
  208. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  209. NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
  210. #pragma clang diagnostic pop
  211. unarchiver.requiresSecureCoding = YES;
  212. object = [unarchiver decodeObjectOfClass:class forKey:NSKeyedArchiveRootObjectKey];
  213. } @catch (NSException *exception) {
  214. if (outError) {
  215. *outError = [FIRInstallationsErrorUtil keyedArchiverErrorWithException:exception];
  216. }
  217. }
  218. }
  219. return object;
  220. }
  221. @end