FIRAppAttestProviderTests.m 48 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093
  1. /*
  2. * Copyright 2021 Google LLC
  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 <OCMock/OCMock.h>
  18. #import "FBLPromise+Testing.h"
  19. #import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppAttestProvider.h"
  20. #import "FirebaseAppCheck/Sources/AppAttestProvider/API/FIRAppAttestAPIService.h"
  21. #import "FirebaseAppCheck/Sources/AppAttestProvider/API/FIRAppAttestAttestationResponse.h"
  22. #import "FirebaseAppCheck/Sources/AppAttestProvider/FIRAppAttestService.h"
  23. #import "FirebaseAppCheck/Sources/AppAttestProvider/Storage/FIRAppAttestArtifactStorage.h"
  24. #import "FirebaseAppCheck/Sources/AppAttestProvider/Storage/FIRAppAttestKeyIDStorage.h"
  25. #import "FirebaseAppCheck/Sources/Core/Utils/FIRAppCheckCryptoUtils.h"
  26. #import "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppCheckToken.h"
  27. #import "FirebaseAppCheck/Sources/AppAttestProvider/Errors/FIRAppAttestRejectionError.h"
  28. #import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h"
  29. #import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckHTTPError.h"
  30. #import "FirebaseCore/Extension/FirebaseCoreInternal.h"
  31. #import "SharedTestUtilities/AppCheckBackoffWrapperFake/FIRAppCheckBackoffWrapperFake.h"
  32. #if FIR_APP_ATTEST_SUPPORTED_TARGETS
  33. FIR_APP_ATTEST_PROVIDER_AVAILABILITY
  34. @interface FIRAppAttestProvider (Tests)
  35. - (instancetype)initWithAppAttestService:(id<FIRAppAttestService>)appAttestService
  36. APIService:(id<FIRAppAttestAPIServiceProtocol>)APIService
  37. keyIDStorage:(id<FIRAppAttestKeyIDStorageProtocol>)keyIDStorage
  38. artifactStorage:(id<FIRAppAttestArtifactStorageProtocol>)artifactStorage
  39. backoffWrapper:(id<FIRAppCheckBackoffWrapperProtocol>)backoffWrapper;
  40. @end
  41. FIR_APP_ATTEST_PROVIDER_AVAILABILITY
  42. @interface FIRAppAttestProviderTests : XCTestCase
  43. @property(nonatomic) FIRAppAttestProvider *provider;
  44. @property(nonatomic) OCMockObject<FIRAppAttestService> *mockAppAttestService;
  45. @property(nonatomic) OCMockObject<FIRAppAttestAPIServiceProtocol> *mockAPIService;
  46. @property(nonatomic) OCMockObject<FIRAppAttestKeyIDStorageProtocol> *mockStorage;
  47. @property(nonatomic) OCMockObject<FIRAppAttestArtifactStorageProtocol> *mockArtifactStorage;
  48. @property(nonatomic) NSData *randomChallenge;
  49. @property(nonatomic) NSData *randomChallengeHash;
  50. @property(nonatomic) FIRAppCheckBackoffWrapperFake *fakeBackoffWrapper;
  51. @end
  52. @implementation FIRAppAttestProviderTests
  53. - (void)setUp {
  54. [super setUp];
  55. self.mockAppAttestService = OCMProtocolMock(@protocol(FIRAppAttestService));
  56. self.mockAPIService = OCMProtocolMock(@protocol(FIRAppAttestAPIServiceProtocol));
  57. self.mockStorage = OCMProtocolMock(@protocol(FIRAppAttestKeyIDStorageProtocol));
  58. self.mockArtifactStorage = OCMProtocolMock(@protocol(FIRAppAttestArtifactStorageProtocol));
  59. self.fakeBackoffWrapper = [[FIRAppCheckBackoffWrapperFake alloc] init];
  60. // Don't backoff by default.
  61. self.fakeBackoffWrapper.isNextOperationAllowed = YES;
  62. self.provider = [[FIRAppAttestProvider alloc] initWithAppAttestService:self.mockAppAttestService
  63. APIService:self.mockAPIService
  64. keyIDStorage:self.mockStorage
  65. artifactStorage:self.mockArtifactStorage
  66. backoffWrapper:self.fakeBackoffWrapper];
  67. self.randomChallenge = [@"random challenge" dataUsingEncoding:NSUTF8StringEncoding];
  68. self.randomChallengeHash =
  69. [[NSData alloc] initWithBase64EncodedString:@"vEq8yE9g+WwfifNqC2wsXN9M3NIDeOKpDBVYLpGbUDY="
  70. options:0];
  71. }
  72. - (void)tearDown {
  73. self.provider = nil;
  74. self.mockArtifactStorage = nil;
  75. self.mockStorage = nil;
  76. self.mockAPIService = nil;
  77. self.mockAppAttestService = nil;
  78. self.fakeBackoffWrapper = nil;
  79. }
  80. #pragma mark - Init tests
  81. #if !TARGET_OS_MACCATALYST
  82. // Keychain dependent logic require additional configuration on Catalyst (enabling Keychain
  83. // sharing). For now, keychain dependent tests are disabled for Catalyst.
  84. - (void)testInitWithValidApp {
  85. FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:@"app_id" GCMSenderID:@"sender_id"];
  86. options.APIKey = @"api_key";
  87. options.projectID = @"project_id";
  88. FIRApp *app = [[FIRApp alloc] initInstanceWithName:@"testInitWithValidApp" options:options];
  89. XCTAssertNotNil([[FIRAppAttestProvider alloc] initWithApp:app]);
  90. }
  91. #endif // !TARGET_OS_MACCATALYST
  92. #pragma mark - Initial handshake (attestation)
  93. - (void)testGetTokenWhenAppAttestIsNotSupported {
  94. NSError *expectedError =
  95. [FIRAppCheckErrorUtil unsupportedAttestationProvider:@"AppAttestProvider"];
  96. // 0.1. Expect backoff wrapper to be used.
  97. self.fakeBackoffWrapper.backoffExpectation = [self expectationWithDescription:@"Backoff"];
  98. // 0.2. Expect default error handler to be used.
  99. XCTestExpectation *errorHandlerExpectation = [self expectationWithDescription:@"Error handler"];
  100. self.fakeBackoffWrapper.defaultErrorHandler = ^FIRAppCheckBackoffType(NSError *_Nonnull error) {
  101. XCTAssertEqualObjects(error, expectedError);
  102. [errorHandlerExpectation fulfill];
  103. return FIRAppCheckBackoffType1Day;
  104. };
  105. // 1. Expect FIRAppAttestService.isSupported.
  106. [OCMExpect([self.mockAppAttestService isSupported]) andReturnValue:@(NO)];
  107. // 2. Don't expect other operations.
  108. OCMReject([self.mockStorage getAppAttestKeyID]);
  109. OCMReject([self.mockAppAttestService generateKeyWithCompletionHandler:OCMOCK_ANY]);
  110. OCMReject([self.mockArtifactStorage getArtifactForKey:OCMOCK_ANY]);
  111. OCMReject([self.mockAPIService getRandomChallenge]);
  112. OCMReject([self.mockStorage setAppAttestKeyID:OCMOCK_ANY]);
  113. OCMReject([self.mockAppAttestService attestKey:OCMOCK_ANY
  114. clientDataHash:OCMOCK_ANY
  115. completionHandler:OCMOCK_ANY]);
  116. OCMReject([self.mockAPIService attestKeyWithAttestation:OCMOCK_ANY
  117. keyID:OCMOCK_ANY
  118. challenge:OCMOCK_ANY]);
  119. // 3. Call get token.
  120. XCTestExpectation *completionExpectation =
  121. [self expectationWithDescription:@"completionExpectation"];
  122. [self.provider
  123. getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) {
  124. [completionExpectation fulfill];
  125. XCTAssertNil(token);
  126. XCTAssertEqualObjects(error, expectedError);
  127. }];
  128. [self waitForExpectations:@[
  129. self.fakeBackoffWrapper.backoffExpectation, errorHandlerExpectation, completionExpectation
  130. ]
  131. timeout:0.5
  132. enforceOrder:YES];
  133. // 4. Verify mocks.
  134. [self verifyAllMocks];
  135. }
  136. - (void)testGetToken_WhenNoExistingKey_Success {
  137. // 0. Expect backoff wrapper to be used.
  138. self.fakeBackoffWrapper.backoffExpectation = [self expectationWithDescription:@"Backoff"];
  139. // 1. Expect FIRAppAttestService.isSupported.
  140. [OCMExpect([self.mockAppAttestService isSupported]) andReturnValue:@(YES)];
  141. // 2. Expect storage getAppAttestKeyID.
  142. FBLPromise *rejectedPromise = [FBLPromise pendingPromise];
  143. NSError *error = [NSError errorWithDomain:@"testGetToken_WhenNoExistingKey_Success"
  144. code:NSNotFound
  145. userInfo:nil];
  146. [rejectedPromise reject:error];
  147. OCMExpect([self.mockStorage getAppAttestKeyID]).andReturn(rejectedPromise);
  148. // 3. Expect App Attest key to be generated.
  149. NSString *generatedKeyID = @"generatedKeyID";
  150. id completionArg = [OCMArg invokeBlockWithArgs:generatedKeyID, [NSNull null], nil];
  151. OCMExpect([self.mockAppAttestService generateKeyWithCompletionHandler:completionArg]);
  152. // 4. Expect the key ID to be stored.
  153. OCMExpect([self.mockStorage setAppAttestKeyID:generatedKeyID])
  154. .andReturn([FBLPromise resolvedWith:generatedKeyID]);
  155. // 5. Expect random challenge to be requested.
  156. OCMExpect([self.mockAPIService getRandomChallenge])
  157. .andReturn([FBLPromise resolvedWith:self.randomChallenge]);
  158. // 6. Expect the key to be attested with the challenge.
  159. NSData *attestationData = [@"attestation data" dataUsingEncoding:NSUTF8StringEncoding];
  160. id attestCompletionArg = [OCMArg invokeBlockWithArgs:attestationData, [NSNull null], nil];
  161. OCMExpect([self.mockAppAttestService attestKey:generatedKeyID
  162. clientDataHash:self.randomChallengeHash
  163. completionHandler:attestCompletionArg]);
  164. // 7. Expect key attestation request to be sent.
  165. FIRAppCheckToken *FACToken = [[FIRAppCheckToken alloc] initWithToken:@"FAC token"
  166. expirationDate:[NSDate date]];
  167. NSData *artifactData = [@"attestation artifact" dataUsingEncoding:NSUTF8StringEncoding];
  168. __auto_type attestKeyResponse =
  169. [[FIRAppAttestAttestationResponse alloc] initWithArtifact:artifactData token:FACToken];
  170. OCMExpect([self.mockAPIService attestKeyWithAttestation:attestationData
  171. keyID:generatedKeyID
  172. challenge:self.randomChallenge])
  173. .andReturn([FBLPromise resolvedWith:attestKeyResponse]);
  174. // 8. Expect the artifact received from Firebase backend to be saved.
  175. OCMExpect([self.mockArtifactStorage setArtifact:artifactData forKey:generatedKeyID])
  176. .andReturn([FBLPromise resolvedWith:artifactData]);
  177. // 9. Call get token.
  178. XCTestExpectation *completionExpectation =
  179. [self expectationWithDescription:@"completionExpectation"];
  180. [self.provider
  181. getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) {
  182. [completionExpectation fulfill];
  183. XCTAssertEqualObjects(token.token, FACToken.token);
  184. XCTAssertEqualObjects(token.expirationDate, FACToken.expirationDate);
  185. XCTAssertNil(error);
  186. }];
  187. [self waitForExpectations:@[ self.fakeBackoffWrapper.backoffExpectation, completionExpectation ]
  188. timeout:0.5
  189. enforceOrder:YES];
  190. // 10. Verify mocks.
  191. [self verifyAllMocks];
  192. // 11. Verify backoff result.
  193. XCTAssertEqualObjects(((FIRAppCheckToken *)self.fakeBackoffWrapper.operationResult).token,
  194. FACToken.token);
  195. }
  196. - (void)testGetToken_WhenExistingUnregisteredKey_Success {
  197. // 0. Expect backoff wrapper to be used.
  198. self.fakeBackoffWrapper.backoffExpectation = [self expectationWithDescription:@"Backoff"];
  199. // 1. Expect FIRAppAttestService.isSupported.
  200. [OCMExpect([self.mockAppAttestService isSupported]) andReturnValue:@(YES)];
  201. // 2. Expect storage getAppAttestKeyID.
  202. NSString *existingKeyID = @"existingKeyID";
  203. OCMExpect([self.mockStorage getAppAttestKeyID])
  204. .andReturn([FBLPromise resolvedWith:existingKeyID]);
  205. // 3. Don't expect App Attest key to be generated.
  206. OCMReject([self.mockAppAttestService generateKeyWithCompletionHandler:OCMOCK_ANY]);
  207. // 4. Don't expect the key ID to be stored.
  208. OCMReject([self.mockStorage setAppAttestKeyID:OCMOCK_ANY]);
  209. // 5. Expect a stored artifact to be requested.
  210. __auto_type rejectedPromise = [self rejectedPromiseWithError:[NSError errorWithDomain:self.name
  211. code:NSNotFound
  212. userInfo:nil]];
  213. OCMExpect([self.mockArtifactStorage getArtifactForKey:existingKeyID]).andReturn(rejectedPromise);
  214. // 6. Expect random challenge to be requested.
  215. OCMExpect([self.mockAPIService getRandomChallenge])
  216. .andReturn([FBLPromise resolvedWith:self.randomChallenge]);
  217. // 7. Expect the key to be attested with the challenge.
  218. NSData *attestationData = [@"attestation data" dataUsingEncoding:NSUTF8StringEncoding];
  219. id attestCompletionArg = [OCMArg invokeBlockWithArgs:attestationData, [NSNull null], nil];
  220. OCMExpect([self.mockAppAttestService attestKey:existingKeyID
  221. clientDataHash:self.randomChallengeHash
  222. completionHandler:attestCompletionArg]);
  223. // 8. Expect key attestation request to be sent.
  224. FIRAppCheckToken *FACToken = [[FIRAppCheckToken alloc] initWithToken:@"FAC token"
  225. expirationDate:[NSDate date]];
  226. NSData *artifactData = [@"attestation artifact" dataUsingEncoding:NSUTF8StringEncoding];
  227. __auto_type attestKeyResponse =
  228. [[FIRAppAttestAttestationResponse alloc] initWithArtifact:artifactData token:FACToken];
  229. OCMExpect([self.mockAPIService attestKeyWithAttestation:attestationData
  230. keyID:existingKeyID
  231. challenge:self.randomChallenge])
  232. .andReturn([FBLPromise resolvedWith:attestKeyResponse]);
  233. // 9. Expect the artifact received from Firebase backend to be saved.
  234. OCMExpect([self.mockArtifactStorage setArtifact:artifactData forKey:existingKeyID])
  235. .andReturn([FBLPromise resolvedWith:artifactData]);
  236. // 10. Call get token.
  237. XCTestExpectation *completionExpectation =
  238. [self expectationWithDescription:@"completionExpectation"];
  239. [self.provider
  240. getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) {
  241. [completionExpectation fulfill];
  242. XCTAssertEqualObjects(token.token, FACToken.token);
  243. XCTAssertEqualObjects(token.expirationDate, FACToken.expirationDate);
  244. XCTAssertNil(error);
  245. }];
  246. [self waitForExpectations:@[ self.fakeBackoffWrapper.backoffExpectation, completionExpectation ]
  247. timeout:0.5
  248. enforceOrder:YES];
  249. // 11. Verify mocks.
  250. [self verifyAllMocks];
  251. // 12. Verify backoff result.
  252. XCTAssertEqualObjects(((FIRAppCheckToken *)self.fakeBackoffWrapper.operationResult).token,
  253. FACToken.token);
  254. }
  255. - (void)testGetToken_WhenUnregisteredKeyAndRandomChallengeError {
  256. // 0. Expect backoff wrapper to be used.
  257. self.fakeBackoffWrapper.backoffExpectation = [self expectationWithDescription:@"Backoff"];
  258. // 1. Expect FIRAppAttestService.isSupported.
  259. [OCMExpect([self.mockAppAttestService isSupported]) andReturnValue:@(YES)];
  260. // 2. Expect storage getAppAttestKeyID.
  261. NSString *existingKeyID = @"existingKeyID";
  262. OCMExpect([self.mockStorage getAppAttestKeyID])
  263. .andReturn([FBLPromise resolvedWith:existingKeyID]);
  264. // 3. Expect a stored artifact to be requested.
  265. __auto_type rejectedPromise = [self rejectedPromiseWithError:[NSError errorWithDomain:self.name
  266. code:NSNotFound
  267. userInfo:nil]];
  268. OCMExpect([self.mockArtifactStorage getArtifactForKey:existingKeyID]).andReturn(rejectedPromise);
  269. // 4. Expect random challenge to be requested.
  270. NSError *challengeError = [self expectRandomChallengeRequestError];
  271. // 5. Don't expect other steps.
  272. OCMReject([self.mockStorage setAppAttestKeyID:OCMOCK_ANY]);
  273. OCMReject([self.mockAppAttestService attestKey:OCMOCK_ANY
  274. clientDataHash:OCMOCK_ANY
  275. completionHandler:OCMOCK_ANY]);
  276. OCMReject([self.mockAPIService attestKeyWithAttestation:OCMOCK_ANY
  277. keyID:OCMOCK_ANY
  278. challenge:OCMOCK_ANY]);
  279. // 6. Call get token.
  280. XCTestExpectation *completionExpectation =
  281. [self expectationWithDescription:@"completionExpectation"];
  282. [self.provider
  283. getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) {
  284. [completionExpectation fulfill];
  285. XCTAssertNil(token);
  286. XCTAssertEqualObjects(error, challengeError);
  287. }];
  288. [self waitForExpectations:@[ self.fakeBackoffWrapper.backoffExpectation, completionExpectation ]
  289. timeout:0.5
  290. enforceOrder:YES];
  291. // 7. Verify mocks.
  292. [self verifyAllMocks];
  293. // 8. Verify backoff error.
  294. XCTAssertEqualObjects(self.fakeBackoffWrapper.operationError, challengeError);
  295. }
  296. - (void)testGetToken_WhenUnregisteredKeyAndKeyAttestationError {
  297. // 0. Expect backoff wrapper to be used.
  298. self.fakeBackoffWrapper.backoffExpectation = [self expectationWithDescription:@"Backoff"];
  299. // 1. Expect FIRAppAttestService.isSupported.
  300. [OCMExpect([self.mockAppAttestService isSupported]) andReturnValue:@(YES)];
  301. // 2. Expect storage getAppAttestKeyID.
  302. NSString *existingKeyID = @"existingKeyID";
  303. OCMExpect([self.mockStorage getAppAttestKeyID])
  304. .andReturn([FBLPromise resolvedWith:existingKeyID]);
  305. // 3. Expect a stored artifact to be requested.
  306. __auto_type rejectedPromise = [self rejectedPromiseWithError:[NSError errorWithDomain:self.name
  307. code:NSNotFound
  308. userInfo:nil]];
  309. OCMExpect([self.mockArtifactStorage getArtifactForKey:existingKeyID]).andReturn(rejectedPromise);
  310. // 4. Expect random challenge to be requested.
  311. OCMExpect([self.mockAPIService getRandomChallenge])
  312. .andReturn([FBLPromise resolvedWith:self.randomChallenge]);
  313. // 5. Expect the key to be attested with the challenge.
  314. NSError *attestationError = [NSError errorWithDomain:@"testGetTokenWhenKeyAttestationError"
  315. code:0
  316. userInfo:nil];
  317. id attestCompletionArg = [OCMArg invokeBlockWithArgs:[NSNull null], attestationError, nil];
  318. OCMExpect([self.mockAppAttestService attestKey:existingKeyID
  319. clientDataHash:self.randomChallengeHash
  320. completionHandler:attestCompletionArg]);
  321. // 6. Don't exchange API request.
  322. OCMReject([self.mockAPIService attestKeyWithAttestation:OCMOCK_ANY
  323. keyID:OCMOCK_ANY
  324. challenge:OCMOCK_ANY]);
  325. // 7. Call get token.
  326. XCTestExpectation *completionExpectation =
  327. [self expectationWithDescription:@"completionExpectation"];
  328. [self.provider
  329. getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) {
  330. [completionExpectation fulfill];
  331. XCTAssertNil(token);
  332. XCTAssertEqualObjects(error, attestationError);
  333. }];
  334. [self waitForExpectations:@[ self.fakeBackoffWrapper.backoffExpectation, completionExpectation ]
  335. timeout:0.5
  336. enforceOrder:YES];
  337. // 8. Verify mocks.
  338. [self verifyAllMocks];
  339. // 9. Verify backoff error.
  340. XCTAssertEqualObjects(self.fakeBackoffWrapper.operationError, attestationError);
  341. }
  342. - (void)testGetToken_WhenUnregisteredKeyAndKeyAttestationExchangeError {
  343. // 0. Expect backoff wrapper to be used.
  344. self.fakeBackoffWrapper.backoffExpectation = [self expectationWithDescription:@"Backoff"];
  345. // 1. Expect FIRAppAttestService.isSupported.
  346. [OCMExpect([self.mockAppAttestService isSupported]) andReturnValue:@(YES)];
  347. // 2. Expect storage getAppAttestKeyID.
  348. NSString *existingKeyID = @"existingKeyID";
  349. OCMExpect([self.mockStorage getAppAttestKeyID])
  350. .andReturn([FBLPromise resolvedWith:existingKeyID]);
  351. // 3. Expect a stored artifact to be requested.
  352. __auto_type rejectedPromise = [self rejectedPromiseWithError:[NSError errorWithDomain:self.name
  353. code:NSNotFound
  354. userInfo:nil]];
  355. OCMExpect([self.mockArtifactStorage getArtifactForKey:existingKeyID]).andReturn(rejectedPromise);
  356. // 4. Expect random challenge to be requested.
  357. OCMExpect([self.mockAPIService getRandomChallenge])
  358. .andReturn([FBLPromise resolvedWith:self.randomChallenge]);
  359. // 5. Expect the key to be attested with the challenge.
  360. NSData *attestationData = [@"attestation data" dataUsingEncoding:NSUTF8StringEncoding];
  361. id attestCompletionArg = [OCMArg invokeBlockWithArgs:attestationData, [NSNull null], nil];
  362. OCMExpect([self.mockAppAttestService attestKey:existingKeyID
  363. clientDataHash:self.randomChallengeHash
  364. completionHandler:attestCompletionArg]);
  365. // 6. Expect exchange request to be sent.
  366. NSError *exchangeError = [NSError errorWithDomain:@"testGetTokenWhenKeyAttestationExchangeError"
  367. code:0
  368. userInfo:nil];
  369. OCMExpect([self.mockAPIService attestKeyWithAttestation:attestationData
  370. keyID:existingKeyID
  371. challenge:self.randomChallenge])
  372. .andReturn([self rejectedPromiseWithError:exchangeError]);
  373. // 7. Call get token.
  374. XCTestExpectation *completionExpectation =
  375. [self expectationWithDescription:@"completionExpectation"];
  376. [self.provider
  377. getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) {
  378. [completionExpectation fulfill];
  379. XCTAssertNil(token);
  380. XCTAssertEqualObjects(error, exchangeError);
  381. }];
  382. [self waitForExpectations:@[ self.fakeBackoffWrapper.backoffExpectation, completionExpectation ]
  383. timeout:0.5
  384. enforceOrder:YES];
  385. // 8. Verify mocks.
  386. [self verifyAllMocks];
  387. // 9. Verify backoff error.
  388. XCTAssertEqualObjects(self.fakeBackoffWrapper.operationError, exchangeError);
  389. }
  390. #pragma mark Rejected Attestation
  391. - (void)testGetToken_WhenAttestationIsRejected_ThenAttestationIsResetAndRetriedOnceSuccess {
  392. // 1. Expect App Attest availability to be requested and stored key ID request to fail.
  393. [self expectAppAttestAvailabilityToBeCheckedAndNotExistingStoredKeyRequested];
  394. // 2. Expect the App Attest key pair to be generated and attested.
  395. NSString *keyID1 = @"keyID1";
  396. NSData *attestationData1 = [[NSUUID UUID].UUIDString dataUsingEncoding:NSUTF8StringEncoding];
  397. [self expectAppAttestKeyGeneratedAndAttestedWithKeyID:keyID1 attestationData:attestationData1];
  398. // 3. Expect exchange request to be sent.
  399. FIRAppCheckHTTPError *APIError = [self attestationRejectionHTTPError];
  400. OCMExpect([self.mockAPIService attestKeyWithAttestation:attestationData1
  401. keyID:keyID1
  402. challenge:self.randomChallenge])
  403. .andReturn([self rejectedPromiseWithError:APIError]);
  404. // 4. Stored attestation to be reset.
  405. [self expectAttestationReset];
  406. // 5. Expect the App Attest key pair to be generated and attested.
  407. NSString *keyID2 = @"keyID2";
  408. NSData *attestationData2 = [[NSUUID UUID].UUIDString dataUsingEncoding:NSUTF8StringEncoding];
  409. [self expectAppAttestKeyGeneratedAndAttestedWithKeyID:keyID2 attestationData:attestationData2];
  410. // 6. Expect exchange request to be sent.
  411. FIRAppCheckToken *FACToken = [[FIRAppCheckToken alloc] initWithToken:@"FAC token"
  412. expirationDate:[NSDate date]];
  413. NSData *artifactData = [@"attestation artifact" dataUsingEncoding:NSUTF8StringEncoding];
  414. __auto_type attestKeyResponse =
  415. [[FIRAppAttestAttestationResponse alloc] initWithArtifact:artifactData token:FACToken];
  416. OCMExpect([self.mockAPIService attestKeyWithAttestation:attestationData2
  417. keyID:keyID2
  418. challenge:self.randomChallenge])
  419. .andReturn([FBLPromise resolvedWith:attestKeyResponse]);
  420. // 7. Expect the artifact received from Firebase backend to be saved.
  421. OCMExpect([self.mockArtifactStorage setArtifact:artifactData forKey:keyID2])
  422. .andReturn([FBLPromise resolvedWith:artifactData]);
  423. // 8. Call get token.
  424. XCTestExpectation *completionExpectation =
  425. [self expectationWithDescription:@"completionExpectation"];
  426. [self.provider
  427. getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) {
  428. [completionExpectation fulfill];
  429. XCTAssertEqualObjects(token.token, FACToken.token);
  430. XCTAssertEqualObjects(token.expirationDate, FACToken.expirationDate);
  431. XCTAssertNil(error);
  432. }];
  433. [self waitForExpectations:@[ completionExpectation ] timeout:0.5 enforceOrder:YES];
  434. // 8. Verify mocks.
  435. [self verifyAllMocks];
  436. }
  437. - (void)testGetToken_WhenAttestationIsRejected_ThenAttestationIsResetAndRetriedOnceError {
  438. // 1. Expect App Attest availability to be requested and stored key ID request to fail.
  439. [self expectAppAttestAvailabilityToBeCheckedAndNotExistingStoredKeyRequested];
  440. // 2. Expect the App Attest key pair to be generated and attested.
  441. NSString *keyID1 = @"keyID1";
  442. NSData *attestationData1 = [[NSUUID UUID].UUIDString dataUsingEncoding:NSUTF8StringEncoding];
  443. [self expectAppAttestKeyGeneratedAndAttestedWithKeyID:keyID1 attestationData:attestationData1];
  444. // 3. Expect exchange request to be sent.
  445. FIRAppCheckHTTPError *APIError = [self attestationRejectionHTTPError];
  446. OCMExpect([self.mockAPIService attestKeyWithAttestation:attestationData1
  447. keyID:keyID1
  448. challenge:self.randomChallenge])
  449. .andReturn([self rejectedPromiseWithError:APIError]);
  450. // 4. Stored attestation to be reset.
  451. [self expectAttestationReset];
  452. // 5. Expect the App Attest key pair to be generated and attested.
  453. NSString *keyID2 = @"keyID2";
  454. NSData *attestationData2 = [[NSUUID UUID].UUIDString dataUsingEncoding:NSUTF8StringEncoding];
  455. [self expectAppAttestKeyGeneratedAndAttestedWithKeyID:keyID2 attestationData:attestationData2];
  456. // 6. Expect exchange request to be sent.
  457. OCMExpect([self.mockAPIService attestKeyWithAttestation:attestationData2
  458. keyID:keyID2
  459. challenge:self.randomChallenge])
  460. .andReturn([self rejectedPromiseWithError:APIError]);
  461. // 7. Stored attestation to be reset.
  462. [self expectAttestationReset];
  463. // 8. Don't expect the artifact received from Firebase backend to be saved.
  464. OCMReject([self.mockArtifactStorage setArtifact:OCMOCK_ANY forKey:OCMOCK_ANY]);
  465. // 9. Call get token.
  466. XCTestExpectation *completionExpectation =
  467. [self expectationWithDescription:@"completionExpectation"];
  468. [self.provider
  469. getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) {
  470. [completionExpectation fulfill];
  471. XCTAssertNil(token);
  472. FIRAppAttestRejectionError *expectedError = [[FIRAppAttestRejectionError alloc] init];
  473. XCTAssertEqualObjects(error, expectedError);
  474. }];
  475. [self waitForExpectations:@[ completionExpectation ] timeout:0.5 enforceOrder:YES];
  476. // 9. Verify mocks.
  477. [self verifyAllMocks];
  478. }
  479. #pragma mark - FAC token refresh (assertion)
  480. - (void)testGetToken_WhenKeyRegistered_Success {
  481. [self assertGetToken_WhenKeyRegistered_Success];
  482. }
  483. - (void)testGetToken_WhenKeyRegisteredAndChallengeRequestError {
  484. // 1. Expect FIRAppAttestService.isSupported.
  485. [OCMExpect([self.mockAppAttestService isSupported]) andReturnValue:@(YES)];
  486. // 2. Expect storage getAppAttestKeyID.
  487. NSString *existingKeyID = @"existingKeyID";
  488. OCMExpect([self.mockStorage getAppAttestKeyID])
  489. .andReturn([FBLPromise resolvedWith:existingKeyID]);
  490. // 3. Expect a stored artifact to be requested.
  491. NSData *storedArtifact = [@"storedArtifact" dataUsingEncoding:NSUTF8StringEncoding];
  492. OCMExpect([self.mockArtifactStorage getArtifactForKey:existingKeyID])
  493. .andReturn([FBLPromise resolvedWith:storedArtifact]);
  494. // 4. Expect random challenge to be requested.
  495. NSError *challengeError = [self expectRandomChallengeRequestError];
  496. // 5. Don't expect assertion to be requested.
  497. OCMReject([self.mockAppAttestService generateAssertion:OCMOCK_ANY
  498. clientDataHash:OCMOCK_ANY
  499. completionHandler:OCMOCK_ANY]);
  500. // 6. Don't expect assertion request to be sent.
  501. OCMReject([self.mockAPIService getAppCheckTokenWithArtifact:OCMOCK_ANY
  502. challenge:OCMOCK_ANY
  503. assertion:OCMOCK_ANY]);
  504. // 7. Call get token.
  505. XCTestExpectation *completionExpectation =
  506. [self expectationWithDescription:@"completionExpectation"];
  507. [self.provider
  508. getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) {
  509. [completionExpectation fulfill];
  510. XCTAssertNil(token);
  511. XCTAssertEqualObjects(error, challengeError);
  512. }];
  513. [self waitForExpectations:@[ completionExpectation ] timeout:0.5];
  514. // 8. Verify mocks.
  515. [self verifyAllMocks];
  516. }
  517. - (void)testGetToken_WhenKeyRegisteredAndGenerateAssertionError {
  518. // 1. Expect FIRAppAttestService.isSupported.
  519. [OCMExpect([self.mockAppAttestService isSupported]) andReturnValue:@(YES)];
  520. // 2. Expect storage getAppAttestKeyID.
  521. NSString *existingKeyID = @"existingKeyID";
  522. OCMExpect([self.mockStorage getAppAttestKeyID])
  523. .andReturn([FBLPromise resolvedWith:existingKeyID]);
  524. // 3. Expect a stored artifact to be requested.
  525. NSData *storedArtifact = [@"storedArtifact" dataUsingEncoding:NSUTF8StringEncoding];
  526. OCMExpect([self.mockArtifactStorage getArtifactForKey:existingKeyID])
  527. .andReturn([FBLPromise resolvedWith:storedArtifact]);
  528. // 4. Expect random challenge to be requested.
  529. OCMExpect([self.mockAPIService getRandomChallenge])
  530. .andReturn([FBLPromise resolvedWith:self.randomChallenge]);
  531. // 5. Don't expect assertion to be requested.
  532. NSError *generateAssertionError =
  533. [NSError errorWithDomain:@"testGetToken_WhenKeyRegisteredAndGenerateAssertionError"
  534. code:0
  535. userInfo:nil];
  536. id completionBlockArg = [OCMArg invokeBlockWithArgs:[NSNull null], generateAssertionError, nil];
  537. OCMExpect([self.mockAppAttestService
  538. generateAssertion:existingKeyID
  539. clientDataHash:[self dataHashForAssertionWithArtifactData:storedArtifact]
  540. completionHandler:completionBlockArg]);
  541. // 6. Don't expect assertion request to be sent.
  542. OCMReject([self.mockAPIService getAppCheckTokenWithArtifact:OCMOCK_ANY
  543. challenge:OCMOCK_ANY
  544. assertion:OCMOCK_ANY]);
  545. // 7. Call get token.
  546. XCTestExpectation *completionExpectation =
  547. [self expectationWithDescription:@"completionExpectation"];
  548. [self.provider
  549. getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) {
  550. [completionExpectation fulfill];
  551. XCTAssertNil(token);
  552. XCTAssertEqualObjects(error, generateAssertionError);
  553. }];
  554. [self waitForExpectations:@[ completionExpectation ] timeout:0.5];
  555. // 8. Verify mocks.
  556. [self verifyAllMocks];
  557. }
  558. - (void)testGetToken_WhenKeyRegisteredAndTokenExchangeRequestError {
  559. // 1. Expect FIRAppAttestService.isSupported.
  560. [OCMExpect([self.mockAppAttestService isSupported]) andReturnValue:@(YES)];
  561. // 2. Expect storage getAppAttestKeyID.
  562. NSString *existingKeyID = @"existingKeyID";
  563. OCMExpect([self.mockStorage getAppAttestKeyID])
  564. .andReturn([FBLPromise resolvedWith:existingKeyID]);
  565. // 3. Expect a stored artifact to be requested.
  566. NSData *storedArtifact = [@"storedArtifact" dataUsingEncoding:NSUTF8StringEncoding];
  567. OCMExpect([self.mockArtifactStorage getArtifactForKey:existingKeyID])
  568. .andReturn([FBLPromise resolvedWith:storedArtifact]);
  569. // 4. Expect random challenge to be requested.
  570. OCMExpect([self.mockAPIService getRandomChallenge])
  571. .andReturn([FBLPromise resolvedWith:self.randomChallenge]);
  572. // 5. Don't expect assertion to be requested.
  573. NSData *assertion = [@"generatedAssertion" dataUsingEncoding:NSUTF8StringEncoding];
  574. id completionBlockArg = [OCMArg invokeBlockWithArgs:assertion, [NSNull null], nil];
  575. OCMExpect([self.mockAppAttestService
  576. generateAssertion:existingKeyID
  577. clientDataHash:[self dataHashForAssertionWithArtifactData:storedArtifact]
  578. completionHandler:completionBlockArg]);
  579. // 6. Expect assertion request to be sent.
  580. NSError *tokenExchangeError =
  581. [NSError errorWithDomain:@"testGetToken_WhenKeyRegisteredAndTokenExchangeRequestError"
  582. code:0
  583. userInfo:nil];
  584. OCMExpect([self.mockAPIService getAppCheckTokenWithArtifact:storedArtifact
  585. challenge:self.randomChallenge
  586. assertion:assertion])
  587. .andReturn([self rejectedPromiseWithError:tokenExchangeError]);
  588. // 7. Call get token.
  589. XCTestExpectation *completionExpectation =
  590. [self expectationWithDescription:@"completionExpectation"];
  591. [self.provider
  592. getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) {
  593. [completionExpectation fulfill];
  594. XCTAssertNil(token);
  595. XCTAssertEqualObjects(error, tokenExchangeError);
  596. }];
  597. [self waitForExpectations:@[ completionExpectation ] timeout:0.5];
  598. // 8. Verify mocks.
  599. [self verifyAllMocks];
  600. }
  601. #pragma mark - Request merging
  602. - (void)testGetToken_WhenCalledSeveralTimesSuccess_ThenThereIsOnlyOneOngoingHandshake {
  603. // 0. Expect backoff wrapper to be used only once.
  604. self.fakeBackoffWrapper.backoffExpectation = [self expectationWithDescription:@"Backoff"];
  605. // 1. Expect FIRAppAttestService.isSupported.
  606. [OCMExpect([self.mockAppAttestService isSupported]) andReturnValue:@(YES)];
  607. // 2. Expect storage getAppAttestKeyID.
  608. NSString *existingKeyID = @"existingKeyID";
  609. OCMExpect([self.mockStorage getAppAttestKeyID])
  610. .andReturn([FBLPromise resolvedWith:existingKeyID]);
  611. // 3. Expect a stored artifact to be requested.
  612. NSData *storedArtifact = [@"storedArtifact" dataUsingEncoding:NSUTF8StringEncoding];
  613. OCMExpect([self.mockArtifactStorage getArtifactForKey:existingKeyID])
  614. .andReturn([FBLPromise resolvedWith:storedArtifact]);
  615. // 4. Expect random challenge to be requested.
  616. // 4.1. Create a pending promise to fulfill later.
  617. FBLPromise<NSData *> *challengeRequestPromise = [FBLPromise pendingPromise];
  618. // 4.2. Stub getRandomChallenge method.
  619. OCMExpect([self.mockAPIService getRandomChallenge]).andReturn(challengeRequestPromise);
  620. // 5. Expect assertion to be requested.
  621. NSData *assertion = [@"generatedAssertion" dataUsingEncoding:NSUTF8StringEncoding];
  622. id completionBlockArg = [OCMArg invokeBlockWithArgs:assertion, [NSNull null], nil];
  623. OCMExpect([self.mockAppAttestService
  624. generateAssertion:existingKeyID
  625. clientDataHash:[self dataHashForAssertionWithArtifactData:storedArtifact]
  626. completionHandler:completionBlockArg]);
  627. // 6. Expect assertion request to be sent.
  628. FIRAppCheckToken *FACToken = [[FIRAppCheckToken alloc] initWithToken:@"FAC token"
  629. expirationDate:[NSDate date]];
  630. OCMExpect([self.mockAPIService getAppCheckTokenWithArtifact:storedArtifact
  631. challenge:self.randomChallenge
  632. assertion:assertion])
  633. .andReturn([FBLPromise resolvedWith:FACToken]);
  634. // 7. Call get token several times.
  635. NSInteger callsCount = 10;
  636. NSMutableArray *completionExpectations = [NSMutableArray arrayWithCapacity:callsCount];
  637. for (NSInteger i = 0; i < callsCount; i++) {
  638. // 7.1 Expect the completion to be called for each get token method called.
  639. XCTestExpectation *completionExpectation = [self
  640. expectationWithDescription:[NSString stringWithFormat:@"completionExpectation%@", @(i)]];
  641. [completionExpectations addObject:completionExpectation];
  642. // 7.2. Call get token.
  643. [self.provider
  644. getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) {
  645. [completionExpectation fulfill];
  646. XCTAssertEqualObjects(token.token, FACToken.token);
  647. XCTAssertEqualObjects(token.expirationDate, FACToken.expirationDate);
  648. XCTAssertNil(error);
  649. }];
  650. }
  651. // 7.3. Resolve get challenge promise to finish the operation.
  652. [challengeRequestPromise fulfill:self.randomChallenge];
  653. // 7.4. Wait for all completions to be called.
  654. NSArray<XCTestExpectation *> *expectations =
  655. [completionExpectations arrayByAddingObject:self.fakeBackoffWrapper.backoffExpectation];
  656. [self waitForExpectations:expectations timeout:1];
  657. // 8. Verify mocks.
  658. [self verifyAllMocks];
  659. // 9. Check another get token call after.
  660. [self assertGetToken_WhenKeyRegistered_Success];
  661. }
  662. - (void)testGetToken_WhenCalledSeveralTimesError_ThenThereIsOnlyOneOngoingHandshake {
  663. // 0. Expect backoff wrapper to be used only once.
  664. self.fakeBackoffWrapper.backoffExpectation = [self expectationWithDescription:@"Backoff"];
  665. // 1. Expect FIRAppAttestService.isSupported.
  666. [OCMExpect([self.mockAppAttestService isSupported]) andReturnValue:@(YES)];
  667. // 2. Expect storage getAppAttestKeyID.
  668. NSString *existingKeyID = @"existingKeyID";
  669. OCMExpect([self.mockStorage getAppAttestKeyID])
  670. .andReturn([FBLPromise resolvedWith:existingKeyID]);
  671. // 3. Expect a stored artifact to be requested.
  672. NSData *storedArtifact = [@"storedArtifact" dataUsingEncoding:NSUTF8StringEncoding];
  673. OCMExpect([self.mockArtifactStorage getArtifactForKey:existingKeyID])
  674. .andReturn([FBLPromise resolvedWith:storedArtifact]);
  675. // 4. Expect random challenge to be requested.
  676. OCMExpect([self.mockAPIService getRandomChallenge])
  677. .andReturn([FBLPromise resolvedWith:self.randomChallenge]);
  678. // 5. Don't expect assertion to be requested.
  679. NSData *assertion = [@"generatedAssertion" dataUsingEncoding:NSUTF8StringEncoding];
  680. id completionBlockArg = [OCMArg invokeBlockWithArgs:assertion, [NSNull null], nil];
  681. OCMExpect([self.mockAppAttestService
  682. generateAssertion:existingKeyID
  683. clientDataHash:[self dataHashForAssertionWithArtifactData:storedArtifact]
  684. completionHandler:completionBlockArg]);
  685. // 6. Expect assertion request to be sent.
  686. // 6.1. Create a pending promise to reject later.
  687. FBLPromise<FIRAppCheckToken *> *assertionRequestPromise = [FBLPromise pendingPromise];
  688. // 6.2. Stub assertion request.
  689. OCMExpect([self.mockAPIService getAppCheckTokenWithArtifact:storedArtifact
  690. challenge:self.randomChallenge
  691. assertion:assertion])
  692. .andReturn(assertionRequestPromise);
  693. // 6.3. Create an expected error to be rejected with later.
  694. NSError *assertionRequestError = [NSError errorWithDomain:self.name code:0 userInfo:nil];
  695. // 7. Call get token several times.
  696. NSInteger callsCount = 10;
  697. NSMutableArray *completionExpectations = [NSMutableArray arrayWithCapacity:callsCount];
  698. for (NSInteger i = 0; i < callsCount; i++) {
  699. // 7.1 Expect the completion to be called for each get token method called.
  700. XCTestExpectation *completionExpectation = [self
  701. expectationWithDescription:[NSString stringWithFormat:@"completionExpectation%@", @(i)]];
  702. [completionExpectations addObject:completionExpectation];
  703. // 7.2. Call get token.
  704. [self.provider
  705. getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) {
  706. [completionExpectation fulfill];
  707. XCTAssertEqualObjects(error, assertionRequestError);
  708. XCTAssertNil(token);
  709. }];
  710. }
  711. // 7.3. Reject get challenge promise to finish the operation.
  712. [assertionRequestPromise reject:assertionRequestError];
  713. // 7.4. Wait for all completions to be called.
  714. NSArray<XCTestExpectation *> *expectations =
  715. [completionExpectations arrayByAddingObject:self.fakeBackoffWrapper.backoffExpectation];
  716. [self waitForExpectations:expectations timeout:1];
  717. // 8. Verify mocks.
  718. [self verifyAllMocks];
  719. // 9. Check another get token call after.
  720. [self assertGetToken_WhenKeyRegistered_Success];
  721. }
  722. #pragma mark - Backoff tests
  723. - (void)testGetTokenBackoff {
  724. // 1. Configure backoff.
  725. self.fakeBackoffWrapper.isNextOperationAllowed = NO;
  726. self.fakeBackoffWrapper.backoffExpectation = [self expectationWithDescription:@"Backoff"];
  727. // 2. Don't expect any operations.
  728. OCMReject([self.mockAppAttestService isSupported]);
  729. OCMReject([self.mockStorage getAppAttestKeyID]);
  730. OCMReject([self.mockAppAttestService generateKeyWithCompletionHandler:OCMOCK_ANY]);
  731. OCMReject([self.mockArtifactStorage getArtifactForKey:OCMOCK_ANY]);
  732. OCMReject([self.mockAPIService getRandomChallenge]);
  733. OCMReject([self.mockStorage setAppAttestKeyID:OCMOCK_ANY]);
  734. OCMReject([self.mockAppAttestService attestKey:OCMOCK_ANY
  735. clientDataHash:OCMOCK_ANY
  736. completionHandler:OCMOCK_ANY]);
  737. OCMReject([self.mockAPIService attestKeyWithAttestation:OCMOCK_ANY
  738. keyID:OCMOCK_ANY
  739. challenge:OCMOCK_ANY]);
  740. // 3. Call get token.
  741. XCTestExpectation *completionExpectation =
  742. [self expectationWithDescription:@"completionExpectation"];
  743. [self.provider
  744. getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) {
  745. [completionExpectation fulfill];
  746. XCTAssertNil(token);
  747. XCTAssertEqualObjects(error, self.fakeBackoffWrapper.backoffError);
  748. }];
  749. [self waitForExpectations:@[ self.fakeBackoffWrapper.backoffExpectation, completionExpectation ]
  750. timeout:0.5
  751. enforceOrder:YES];
  752. // 4. Verify mocks.
  753. [self verifyAllMocks];
  754. }
  755. #pragma mark - Helpers
  756. - (NSData *)dataHashForAssertionWithArtifactData:(NSData *)artifact {
  757. NSMutableData *statement = [artifact mutableCopy];
  758. [statement appendData:self.randomChallenge];
  759. return [FIRAppCheckCryptoUtils sha256HashFromData:statement];
  760. }
  761. - (FBLPromise *)rejectedPromiseWithError:(NSError *)error {
  762. FBLPromise *rejectedPromise = [FBLPromise pendingPromise];
  763. [rejectedPromise reject:error];
  764. return rejectedPromise;
  765. }
  766. - (NSError *)expectRandomChallengeRequestError {
  767. NSError *challengeError = [NSError errorWithDomain:@"testGetToken_WhenRandomChallengeError"
  768. code:NSNotFound
  769. userInfo:nil];
  770. OCMExpect([self.mockAPIService getRandomChallenge])
  771. .andReturn([self rejectedPromiseWithError:challengeError]);
  772. return challengeError;
  773. }
  774. - (void)verifyAllMocks {
  775. OCMVerifyAll(self.mockAppAttestService);
  776. OCMVerifyAll(self.mockAPIService);
  777. OCMVerifyAll(self.mockStorage);
  778. OCMVerifyAll(self.mockArtifactStorage);
  779. }
  780. - (FIRAppCheckHTTPError *)attestationRejectionHTTPError {
  781. NSHTTPURLResponse *response =
  782. [[NSHTTPURLResponse alloc] initWithURL:[NSURL URLWithString:@"http://localhost"]
  783. statusCode:403
  784. HTTPVersion:@"HTTP/1.1"
  785. headerFields:nil];
  786. NSData *responseBody = [@"Could not verify attestation" dataUsingEncoding:NSUTF8StringEncoding];
  787. return [[FIRAppCheckHTTPError alloc] initWithHTTPResponse:response data:responseBody];
  788. }
  789. - (void)assertGetToken_WhenKeyRegistered_Success {
  790. // 0. Expect backoff wrapper to be used.
  791. self.fakeBackoffWrapper.backoffExpectation = [self expectationWithDescription:@"Backoff"];
  792. // 1. Expect FIRAppAttestService.isSupported.
  793. [OCMExpect([self.mockAppAttestService isSupported]) andReturnValue:@(YES)];
  794. // 2. Expect storage getAppAttestKeyID.
  795. NSString *existingKeyID = [NSUUID UUID].UUIDString;
  796. OCMExpect([self.mockStorage getAppAttestKeyID])
  797. .andReturn([FBLPromise resolvedWith:existingKeyID]);
  798. // 3. Expect a stored artifact to be requested.
  799. NSData *storedArtifact = [[NSUUID UUID].UUIDString dataUsingEncoding:NSUTF8StringEncoding];
  800. OCMExpect([self.mockArtifactStorage getArtifactForKey:existingKeyID])
  801. .andReturn([FBLPromise resolvedWith:storedArtifact]);
  802. // 4. Expect random challenge to be requested.
  803. OCMExpect([self.mockAPIService getRandomChallenge])
  804. .andReturn([FBLPromise resolvedWith:self.randomChallenge]);
  805. // 5. Expect assertion to be requested.
  806. NSData *assertion = [[NSUUID UUID].UUIDString dataUsingEncoding:NSUTF8StringEncoding];
  807. id completionBlockArg = [OCMArg invokeBlockWithArgs:assertion, [NSNull null], nil];
  808. OCMExpect([self.mockAppAttestService
  809. generateAssertion:existingKeyID
  810. clientDataHash:[self dataHashForAssertionWithArtifactData:storedArtifact]
  811. completionHandler:completionBlockArg]);
  812. // 6. Expect assertion request to be sent.
  813. FIRAppCheckToken *FACToken = [[FIRAppCheckToken alloc] initWithToken:[NSUUID UUID].UUIDString
  814. expirationDate:[NSDate date]];
  815. OCMExpect([self.mockAPIService getAppCheckTokenWithArtifact:storedArtifact
  816. challenge:self.randomChallenge
  817. assertion:assertion])
  818. .andReturn([FBLPromise resolvedWith:FACToken]);
  819. // 7. Call get token.
  820. XCTestExpectation *completionExpectation =
  821. [self expectationWithDescription:@"completionExpectation"];
  822. [self.provider
  823. getTokenWithCompletion:^(FIRAppCheckToken *_Nullable token, NSError *_Nullable error) {
  824. [completionExpectation fulfill];
  825. XCTAssertEqualObjects(token.token, FACToken.token);
  826. XCTAssertEqualObjects(token.expirationDate, FACToken.expirationDate);
  827. XCTAssertNil(error);
  828. }];
  829. [self waitForExpectations:@[ self.fakeBackoffWrapper.backoffExpectation, completionExpectation ]
  830. timeout:0.5];
  831. // 8. Verify mocks.
  832. [self verifyAllMocks];
  833. // 9. Verify backoff result.
  834. XCTAssertEqualObjects(((FIRAppCheckToken *)self.fakeBackoffWrapper.operationResult).token,
  835. FACToken.token);
  836. }
  837. - (void)expectAppAttestAvailabilityToBeCheckedAndNotExistingStoredKeyRequested {
  838. // 1. Expect FIRAppAttestService.isSupported.
  839. [OCMExpect([self.mockAppAttestService isSupported]) andReturnValue:@(YES)];
  840. // 2. Expect storage getAppAttestKeyID.
  841. FBLPromise *rejectedPromise = [FBLPromise pendingPromise];
  842. NSError *error = [NSError errorWithDomain:@"testGetToken_WhenNoExistingKey_Success"
  843. code:NSNotFound
  844. userInfo:nil];
  845. [rejectedPromise reject:error];
  846. OCMExpect([self.mockStorage getAppAttestKeyID]).andReturn(rejectedPromise);
  847. }
  848. - (void)expectAppAttestKeyGeneratedAndAttestedWithKeyID:(NSString *)keyID
  849. attestationData:(NSData *)attestationData {
  850. // 1. Expect App Attest key to be generated.
  851. id completionArg = [OCMArg invokeBlockWithArgs:keyID, [NSNull null], nil];
  852. OCMExpect([self.mockAppAttestService generateKeyWithCompletionHandler:completionArg]);
  853. // 2. Expect the key ID to be stored.
  854. OCMExpect([self.mockStorage setAppAttestKeyID:keyID]).andReturn([FBLPromise resolvedWith:keyID]);
  855. // 3. Expect random challenge to be requested.
  856. OCMExpect([self.mockAPIService getRandomChallenge])
  857. .andReturn([FBLPromise resolvedWith:self.randomChallenge]);
  858. // 4. Expect the key to be attested with the challenge.
  859. id attestCompletionArg = [OCMArg invokeBlockWithArgs:attestationData, [NSNull null], nil];
  860. OCMExpect([self.mockAppAttestService attestKey:keyID
  861. clientDataHash:self.randomChallengeHash
  862. completionHandler:attestCompletionArg]);
  863. }
  864. - (void)expectAttestationReset {
  865. // 1. Expect stored key ID to be reset.
  866. OCMExpect([self.mockStorage setAppAttestKeyID:nil]).andReturn([FBLPromise resolvedWith:nil]);
  867. // 2. Expect stored attestation artifact to be reset.
  868. OCMExpect([self.mockArtifactStorage setArtifact:nil forKey:@""])
  869. .andReturn([FBLPromise resolvedWith:nil]);
  870. }
  871. @end
  872. #endif // FIR_APP_ATTEST_SUPPORTED_TARGETS