FIRInstanceIDKeyPairStoreTest.m 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  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 <XCTest/XCTest.h>
  17. #import "Firebase/InstanceID/FIRInstanceIDBackupExcludedPlist.h"
  18. #import "Firebase/InstanceID/FIRInstanceIDConstants.h"
  19. #import "Firebase/InstanceID/FIRInstanceIDKeyPair.h"
  20. #import "Firebase/InstanceID/FIRInstanceIDKeychain.h"
  21. #import "Firebase/InstanceID/FIRInstanceIDStore.h"
  22. #import <OCMock/OCMock.h>
  23. #import "Firebase/InstanceID/FIRInstanceIDKeyPair.h"
  24. #import "Firebase/InstanceID/FIRInstanceIDKeyPairStore.h"
  25. #import "Firebase/InstanceID/FIRInstanceIDKeyPairUtilities.h"
  26. #import "Firebase/InstanceID/FIRInstanceIDUtilities.h"
  27. @interface FIRInstanceIDKeyPairStore (ExposedForTest)
  28. @property(nonatomic, readwrite, strong) FIRInstanceIDBackupExcludedPlist *plist;
  29. @property(nonatomic, readwrite, strong) FIRInstanceIDKeyPair *keyPair;
  30. + (NSString *)appIDKeyWithSubtype:(NSString *)subtype;
  31. + (NSString *)creationTimeKeyWithSubtype:(NSString *)subtype;
  32. - (FIRInstanceIDKeyPair *)generateAndSaveKeyWithSubtype:(NSString *)subtype
  33. creationTime:(int64_t)creationTime
  34. error:(NSError **)error;
  35. - (FIRInstanceIDKeyPair *)validCachedKeyPairWithSubtype:(NSString *)subtype error:(NSError **)error;
  36. + (NSString *)keyStoreFileName;
  37. - (void)migrateKeyPairCacheIfNeededWithHandler:(void (^)(NSError *error))handler;
  38. @end
  39. @interface FIRInstanceIDKeyPairStoreTest : XCTestCase
  40. @property(nonatomic, readwrite, strong) FIRInstanceIDKeyPairStore *keyPairStore;
  41. @end
  42. @implementation FIRInstanceIDKeyPairStoreTest
  43. - (void)setUp {
  44. [super setUp];
  45. id mockStoreClass = OCMClassMock([FIRInstanceIDKeyPairStore class]);
  46. [[[mockStoreClass stub] andReturn:@"com.google.iid-keypairmanager-test"] keyStoreFileName];
  47. // Should make sure the standard directory is created.
  48. if (![FIRInstanceIDStore hasSubDirectory:kFIRInstanceIDSubDirectoryName]) {
  49. [FIRInstanceIDStore createSubDirectory:kFIRInstanceIDSubDirectoryName];
  50. }
  51. _keyPairStore = [[FIRInstanceIDKeyPairStore alloc] init];
  52. }
  53. - (void)tearDown {
  54. [super tearDown];
  55. NSError *error = nil;
  56. [self.keyPairStore removeKeyPairCreationTimePlistWithError:&error];
  57. [self.keyPairStore deleteSavedKeyPairWithSubtype:kFIRInstanceIDKeyPairSubType handler:nil];
  58. }
  59. /**
  60. * The app identity generated should be 11 chars and start with k, l, m, n. It should
  61. * not have "=" as suffix since we do not allow wrapping.
  62. */
  63. - (void)testIdentity {
  64. NSError *error;
  65. FIRInstanceIDKeyPair *keyPair = [self.keyPairStore loadKeyPairWithError:&error];
  66. NSString *iid = FIRInstanceIDAppIdentity(keyPair);
  67. XCTAssertEqual(11, iid.length);
  68. XCTAssertFalse([iid hasSuffix:@"="]);
  69. }
  70. /**
  71. * All identities should be cleared if the associated keypair plist file is missing.
  72. * This indicates that the app is either a fresh install, or was removed and reinstalled.
  73. *
  74. * If/when iOS changes the behavior of the Keychain to also invalidate items when an app is
  75. * installed, this check will no longer be required (both the plist file and the keychain items
  76. * would be missing).
  77. */
  78. - (void)testIdentityIsInvalidatedWithMissingPlist {
  79. // Mock that the plist doesn't exist, and call the invalidation check. It should
  80. // trigger the identities to be deleted.
  81. id plistMock = OCMPartialMock(self.keyPairStore.plist);
  82. [[[plistMock stub] andReturnValue:OCMOCK_VALUE(NO)] doesFileExist];
  83. // Mock the keypair store, to check if key pair deletes are requested
  84. id storeMock = OCMPartialMock(self.keyPairStore);
  85. // Now trigger a possible invalidation.
  86. [self.keyPairStore invalidateKeyPairsIfNeeded];
  87. // Verify that delete was called
  88. OCMVerify([storeMock deleteSavedKeyPairWithSubtype:[OCMArg any] handler:[OCMArg any]]);
  89. }
  90. - (void)testMigrationWhenPlistExist {
  91. // Mock that the plist doesn't exist, and call the invalidation check. It should
  92. // trigger the identities to be deleted.
  93. id plistMock = OCMPartialMock(self.keyPairStore.plist);
  94. [[[plistMock stub] andReturnValue:OCMOCK_VALUE(YES)] doesFileExist];
  95. // Mock the keypair store, to check if key pair deletes are requested
  96. id storeMock = OCMPartialMock(self.keyPairStore);
  97. // Now trigger a possible invalidation.
  98. [self.keyPairStore invalidateKeyPairsIfNeeded];
  99. // Verify that delete was called
  100. OCMVerify([storeMock migrateKeyPairCacheIfNeededWithHandler:nil]);
  101. }
  102. /**
  103. * The app identity should change when deleted and regenerated.
  104. */
  105. - (void)testResetIdentity {
  106. XCTestExpectation *identityResetExpectation =
  107. [self expectationWithDescription:@"Identity should be reset"];
  108. NSError *error;
  109. FIRInstanceIDKeyPair *keyPair = [self.keyPairStore loadKeyPairWithError:&error];
  110. XCTAssertNil(error);
  111. NSString *iid1 = FIRInstanceIDAppIdentity(keyPair);
  112. [self.keyPairStore
  113. deleteSavedKeyPairWithSubtype:kFIRInstanceIDKeyPairSubType
  114. handler:^(NSError *error) {
  115. XCTAssertNil(error);
  116. [self.keyPairStore removeKeyPairCreationTimePlistWithError:&error];
  117. XCTAssertNil(error);
  118. // regenerate instance-id
  119. FIRInstanceIDKeyPair *keyPair =
  120. [self.keyPairStore loadKeyPairWithError:&error];
  121. XCTAssertNil(error);
  122. NSString *iid2 = FIRInstanceIDAppIdentity(keyPair);
  123. XCTAssertNotEqualObjects(iid1, iid2);
  124. [identityResetExpectation fulfill];
  125. }];
  126. [self waitForExpectationsWithTimeout:1 handler:nil];
  127. }
  128. /**
  129. * We should always cache a valid keypair.
  130. */
  131. - (void)testCachedKeyPair {
  132. NSError *error;
  133. FIRInstanceIDKeyPair *keyPair = [self.keyPairStore loadKeyPairWithError:&error];
  134. XCTAssertNil(error);
  135. NSString *iid1 = FIRInstanceIDAppIdentity(keyPair);
  136. // sleep for some time
  137. [NSThread sleepForTimeInterval:2.0];
  138. keyPair = [self.keyPairStore loadKeyPairWithError:&error];
  139. XCTAssertNil(error);
  140. NSString *iid2 = FIRInstanceIDAppIdentity(keyPair);
  141. XCTAssertTrue([self.keyPairStore hasCachedKeyPairs]);
  142. XCTAssertEqualObjects(iid1, iid2);
  143. }
  144. - (void)testAppIdentity {
  145. NSError *error;
  146. NSString *iid1 = [self.keyPairStore appIdentityWithError:&error];
  147. // sleep for some time
  148. [NSThread sleepForTimeInterval:2.0];
  149. NSString *iid2 = [self.keyPairStore appIdentityWithError:&error];
  150. XCTAssertEqualObjects(iid1, iid2);
  151. }
  152. /**
  153. * Test KeyPair cache. After generating a new keyPair requesting it from the cache
  154. * should be successfull and return the same keyPair.
  155. */
  156. - (void)testKeyPairCache {
  157. NSError *error;
  158. FIRInstanceIDKeyPair *keyPair1 =
  159. [self.keyPairStore generateAndSaveKeyWithSubtype:kFIRInstanceIDKeyPairSubType
  160. creationTime:FIRInstanceIDCurrentTimestampInSeconds()
  161. error:&error];
  162. XCTAssertNotNil(keyPair1);
  163. NSString *iid1 = FIRInstanceIDAppIdentity(keyPair1);
  164. [NSThread sleepForTimeInterval:2.0];
  165. FIRInstanceIDKeyPair *keyPair2 =
  166. [self.keyPairStore validCachedKeyPairWithSubtype:kFIRInstanceIDKeyPairSubType error:&error];
  167. XCTAssertNil(error);
  168. NSString *iid2 = FIRInstanceIDAppIdentity(keyPair2);
  169. XCTAssertEqualObjects(iid1, iid2);
  170. }
  171. /**
  172. * Test that if the Keychain preferences does not store any KeyPair, trying to
  173. * load one from the cache should return nil.
  174. */
  175. - (void)testInvalidKeyPair {
  176. NSError *error;
  177. FIRInstanceIDKeyPair *keyPair =
  178. [self.keyPairStore validCachedKeyPairWithSubtype:kFIRInstanceIDKeyPairSubType error:&error];
  179. XCTAssertFalse([keyPair isValid]);
  180. }
  181. /**
  182. * Test deleting the keyPair from Keychain preferences.
  183. */
  184. - (void)testDeleteKeyPair {
  185. XCTestExpectation *deleteKeyPairExpectation =
  186. [self expectationWithDescription:@"Keypair should be deleted"];
  187. NSError *error;
  188. [self.keyPairStore generateAndSaveKeyWithSubtype:kFIRInstanceIDKeyPairSubType
  189. creationTime:FIRInstanceIDCurrentTimestampInSeconds()
  190. error:&error];
  191. XCTAssertNil(error);
  192. [self.keyPairStore
  193. deleteSavedKeyPairWithSubtype:kFIRInstanceIDKeyPairSubType
  194. handler:^(NSError *error) {
  195. XCTAssertNil(error);
  196. FIRInstanceIDKeyPair *keyPair2 = [self.keyPairStore
  197. validCachedKeyPairWithSubtype:kFIRInstanceIDKeyPairSubType
  198. error:&error];
  199. XCTAssertNotNil(error);
  200. XCTAssertNil(keyPair2);
  201. [deleteKeyPairExpectation fulfill];
  202. }];
  203. [self waitForExpectationsWithTimeout:1 handler:nil];
  204. }
  205. @end