FIRAppAttestProvider.m 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  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 "FirebaseAppCheck/Sources/Public/FirebaseAppCheck/FIRAppAttestProvider.h"
  17. #import "FirebaseAppCheck/Sources/AppAttestProvider/DCAppAttestService+FIRAppAttestService.h"
  18. #if __has_include(<FBLPromises/FBLPromises.h>)
  19. #import <FBLPromises/FBLPromises.h>
  20. #else
  21. #import "FBLPromises.h"
  22. #endif
  23. #import "FirebaseAppCheck/Sources/AppAttestProvider/API/FIRAppAttestAPIService.h"
  24. #import "FirebaseAppCheck/Sources/AppAttestProvider/API/FIRAppAttestAttestationResponse.h"
  25. #import "FirebaseAppCheck/Sources/AppAttestProvider/FIRAppAttestProviderState.h"
  26. #import "FirebaseAppCheck/Sources/AppAttestProvider/FIRAppAttestService.h"
  27. #import "FirebaseAppCheck/Sources/AppAttestProvider/Storage/FIRAppAttestArtifactStorage.h"
  28. #import "FirebaseAppCheck/Sources/AppAttestProvider/Storage/FIRAppAttestKeyIDStorage.h"
  29. #import "FirebaseAppCheck/Sources/Core/APIService/FIRAppCheckAPIService.h"
  30. #import "FirebaseAppCheck/Sources/Core/Backoff/FIRAppCheckBackoffWrapper.h"
  31. #import "FirebaseAppCheck/Sources/Core/FIRAppCheckLogger.h"
  32. #import "FirebaseAppCheck/Sources/Core/Utils/FIRAppCheckCryptoUtils.h"
  33. #import "FirebaseAppCheck/Sources/AppAttestProvider/Errors/FIRAppAttestRejectionError.h"
  34. #import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h"
  35. #import "FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckHTTPError.h"
  36. #import "FirebaseCore/Extension/FirebaseCoreInternal.h"
  37. NS_ASSUME_NONNULL_BEGIN
  38. /// A data object that contains all key attest data required for FAC token exchange.
  39. @interface FIRAppAttestKeyAttestationResult : NSObject
  40. @property(nonatomic, readonly) NSString *keyID;
  41. @property(nonatomic, readonly) NSData *challenge;
  42. @property(nonatomic, readonly) NSData *attestation;
  43. - (instancetype)initWithKeyID:(NSString *)keyID
  44. challenge:(NSData *)challenge
  45. attestation:(NSData *)attestation;
  46. @end
  47. @implementation FIRAppAttestKeyAttestationResult
  48. - (instancetype)initWithKeyID:(NSString *)keyID
  49. challenge:(NSData *)challenge
  50. attestation:(NSData *)attestation {
  51. self = [super init];
  52. if (self) {
  53. _keyID = keyID;
  54. _challenge = challenge;
  55. _attestation = attestation;
  56. }
  57. return self;
  58. }
  59. @end
  60. /// A data object that contains information required for assertion request.
  61. @interface FIRAppAttestAssertionData : NSObject
  62. @property(nonatomic, readonly) NSData *challenge;
  63. @property(nonatomic, readonly) NSData *artifact;
  64. @property(nonatomic, readonly) NSData *assertion;
  65. - (instancetype)initWithChallenge:(NSData *)challenge
  66. artifact:(NSData *)artifact
  67. assertion:(NSData *)assertion;
  68. @end
  69. @implementation FIRAppAttestAssertionData
  70. - (instancetype)initWithChallenge:(NSData *)challenge
  71. artifact:(NSData *)artifact
  72. assertion:(NSData *)assertion {
  73. self = [super init];
  74. if (self) {
  75. _challenge = challenge;
  76. _artifact = artifact;
  77. _assertion = assertion;
  78. }
  79. return self;
  80. }
  81. @end
  82. @interface FIRAppAttestProvider ()
  83. @property(nonatomic, readonly) id<FIRAppAttestAPIServiceProtocol> APIService;
  84. @property(nonatomic, readonly) id<FIRAppAttestService> appAttestService;
  85. @property(nonatomic, readonly) id<FIRAppAttestKeyIDStorageProtocol> keyIDStorage;
  86. @property(nonatomic, readonly) id<FIRAppAttestArtifactStorageProtocol> artifactStorage;
  87. @property(nonatomic, readonly) id<FIRAppCheckBackoffWrapperProtocol> backoffWrapper;
  88. @property(nonatomic, nullable) FBLPromise<FIRAppCheckToken *> *ongoingGetTokenOperation;
  89. @property(nonatomic, readonly) dispatch_queue_t queue;
  90. @end
  91. @implementation FIRAppAttestProvider
  92. - (instancetype)initWithAppAttestService:(id<FIRAppAttestService>)appAttestService
  93. APIService:(id<FIRAppAttestAPIServiceProtocol>)APIService
  94. keyIDStorage:(id<FIRAppAttestKeyIDStorageProtocol>)keyIDStorage
  95. artifactStorage:(id<FIRAppAttestArtifactStorageProtocol>)artifactStorage
  96. backoffWrapper:(id<FIRAppCheckBackoffWrapperProtocol>)backoffWrapper {
  97. self = [super init];
  98. if (self) {
  99. _appAttestService = appAttestService;
  100. _APIService = APIService;
  101. _keyIDStorage = keyIDStorage;
  102. _artifactStorage = artifactStorage;
  103. _backoffWrapper = backoffWrapper;
  104. _queue = dispatch_queue_create("com.firebase.FIRAppAttestProvider", DISPATCH_QUEUE_SERIAL);
  105. }
  106. return self;
  107. }
  108. - (nullable instancetype)initWithApp:(FIRApp *)app {
  109. #if FIR_APP_ATTEST_SUPPORTED_TARGETS
  110. NSURLSession *URLSession = [NSURLSession
  111. sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]];
  112. FIRAppAttestKeyIDStorage *keyIDStorage =
  113. [[FIRAppAttestKeyIDStorage alloc] initWithAppName:app.name appID:app.options.googleAppID];
  114. FIRAppCheckAPIService *APIService =
  115. [[FIRAppCheckAPIService alloc] initWithURLSession:URLSession
  116. APIKey:app.options.APIKey
  117. appID:app.options.googleAppID
  118. heartbeatLogger:app.heartbeatLogger];
  119. FIRAppAttestAPIService *appAttestAPIService =
  120. [[FIRAppAttestAPIService alloc] initWithAPIService:APIService
  121. projectID:app.options.projectID
  122. appID:app.options.googleAppID];
  123. FIRAppAttestArtifactStorage *artifactStorage =
  124. [[FIRAppAttestArtifactStorage alloc] initWithAppName:app.name
  125. appID:app.options.googleAppID
  126. accessGroup:app.options.appGroupID];
  127. FIRAppCheckBackoffWrapper *backoffWrapper = [[FIRAppCheckBackoffWrapper alloc] init];
  128. return [self initWithAppAttestService:DCAppAttestService.sharedService
  129. APIService:appAttestAPIService
  130. keyIDStorage:keyIDStorage
  131. artifactStorage:artifactStorage
  132. backoffWrapper:backoffWrapper];
  133. #else // FIR_APP_ATTEST_SUPPORTED_TARGETS
  134. return nil;
  135. #endif // FIR_APP_ATTEST_SUPPORTED_TARGETS
  136. }
  137. #pragma mark - FIRAppCheckProvider
  138. - (void)getTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable, NSError *_Nullable))handler {
  139. [self getToken]
  140. // Call the handler with the result.
  141. .then(^FBLPromise *(FIRAppCheckToken *token) {
  142. handler(token, nil);
  143. return nil;
  144. })
  145. .catch(^(NSError *error) {
  146. handler(nil, error);
  147. });
  148. }
  149. - (FBLPromise<FIRAppCheckToken *> *)getToken {
  150. return [FBLPromise onQueue:self.queue
  151. do:^id _Nullable {
  152. if (self.ongoingGetTokenOperation == nil) {
  153. // Kick off a new handshake sequence only when there is not an ongoing
  154. // handshake to avoid race conditions.
  155. self.ongoingGetTokenOperation =
  156. [self createGetTokenSequenceWithBackoffPromise]
  157. // Release the ongoing operation promise on completion.
  158. .then(^FIRAppCheckToken *(FIRAppCheckToken *token) {
  159. self.ongoingGetTokenOperation = nil;
  160. return token;
  161. })
  162. .recover(^NSError *(NSError *error) {
  163. self.ongoingGetTokenOperation = nil;
  164. return error;
  165. });
  166. }
  167. return self.ongoingGetTokenOperation;
  168. }];
  169. }
  170. - (FBLPromise<FIRAppCheckToken *> *)createGetTokenSequenceWithBackoffPromise {
  171. return [self.backoffWrapper
  172. applyBackoffToOperation:^FBLPromise *_Nonnull {
  173. return [self createGetTokenSequencePromise];
  174. }
  175. errorHandler:[self.backoffWrapper defaultAppCheckProviderErrorHandler]];
  176. }
  177. - (FBLPromise<FIRAppCheckToken *> *)createGetTokenSequencePromise {
  178. // Check attestation state to decide on the next steps.
  179. return [self attestationState].thenOn(self.queue, ^id(FIRAppAttestProviderState *attestState) {
  180. switch (attestState.state) {
  181. case FIRAppAttestAttestationStateUnsupported:
  182. FIRAppCheckDebugLog(kFIRLoggerAppCheckMessageCodeAppAttestNotSupported,
  183. @"App Attest is not supported.");
  184. return attestState.appAttestUnsupportedError;
  185. break;
  186. case FIRAppAttestAttestationStateSupportedInitial:
  187. case FIRAppAttestAttestationStateKeyGenerated:
  188. // Initial handshake is required for both the "initial" and the "key generated" states.
  189. return [self initialHandshakeWithKeyID:attestState.appAttestKeyID];
  190. break;
  191. case FIRAppAttestAttestationStateKeyRegistered:
  192. // Refresh FAC token using the existing registered App Attest key pair.
  193. return [self refreshTokenWithKeyID:attestState.appAttestKeyID
  194. artifact:attestState.attestationArtifact];
  195. break;
  196. }
  197. });
  198. }
  199. #pragma mark - Initial handshake sequence (attestation)
  200. - (FBLPromise<FIRAppCheckToken *> *)initialHandshakeWithKeyID:(nullable NSString *)keyID {
  201. // 1. Attest the device. Retry once on 403 from Firebase backend (attestation rejection error).
  202. __block NSString *keyIDForAttempt = keyID;
  203. return [FBLPromise onQueue:self.queue
  204. attempts:1
  205. delay:0
  206. condition:^BOOL(NSInteger attemptCount, NSError *_Nonnull error) {
  207. // Reset keyID before retrying.
  208. keyIDForAttempt = nil;
  209. return [error isKindOfClass:[FIRAppAttestRejectionError class]];
  210. }
  211. retry:^FBLPromise<NSArray * /*[keyID, attestArtifact]*/> *_Nullable {
  212. return [self attestKeyGenerateIfNeededWithID:keyIDForAttempt];
  213. }]
  214. .thenOn(self.queue, ^FBLPromise<FIRAppCheckToken *> *(NSArray *attestationResults) {
  215. // 4. Save the artifact and return the received FAC token.
  216. FIRAppAttestKeyAttestationResult *attestation = attestationResults.firstObject;
  217. FIRAppAttestAttestationResponse *firebaseAttestationResponse =
  218. attestationResults.lastObject;
  219. return [self saveArtifactAndGetAppCheckTokenFromResponse:firebaseAttestationResponse
  220. keyID:attestation.keyID];
  221. });
  222. }
  223. - (FBLPromise<FIRAppCheckToken *> *)saveArtifactAndGetAppCheckTokenFromResponse:
  224. (FIRAppAttestAttestationResponse *)response
  225. keyID:(NSString *)keyID {
  226. return [self.artifactStorage setArtifact:response.artifact forKey:keyID].thenOn(
  227. self.queue, ^FIRAppCheckToken *(id result) {
  228. return response.token;
  229. });
  230. }
  231. - (FBLPromise<FIRAppAttestKeyAttestationResult *> *)attestKey:(NSString *)keyID
  232. challenge:(NSData *)challenge {
  233. return [FBLPromise onQueue:self.queue
  234. do:^NSData *_Nullable {
  235. return [FIRAppCheckCryptoUtils sha256HashFromData:challenge];
  236. }]
  237. .thenOn(
  238. self.queue,
  239. ^FBLPromise<NSData *> *(NSData *challengeHash) {
  240. return [FBLPromise onQueue:self.queue
  241. wrapObjectOrErrorCompletion:^(FBLPromiseObjectOrErrorCompletion _Nonnull handler) {
  242. [self.appAttestService attestKey:keyID
  243. clientDataHash:challengeHash
  244. completionHandler:handler];
  245. }];
  246. })
  247. .thenOn(self.queue, ^FBLPromise<FIRAppAttestKeyAttestationResult *> *(NSData *attestation) {
  248. FIRAppAttestKeyAttestationResult *result =
  249. [[FIRAppAttestKeyAttestationResult alloc] initWithKeyID:keyID
  250. challenge:challenge
  251. attestation:attestation];
  252. return [FBLPromise resolvedWith:result];
  253. });
  254. }
  255. - (FBLPromise<NSArray * /*[keyID, attestArtifact]*/> *)attestKeyGenerateIfNeededWithID:
  256. (nullable NSString *)keyID {
  257. // 1. Request a random challenge and get App Attest key ID concurrently.
  258. return [FBLPromise onQueue:self.queue
  259. all:@[
  260. // 1.1. Request random challenge.
  261. [self.APIService getRandomChallenge],
  262. // 1.2. Get App Attest key ID.
  263. [self generateAppAttestKeyIDIfNeeded:keyID]
  264. ]]
  265. .thenOn(self.queue,
  266. ^FBLPromise<FIRAppAttestKeyAttestationResult *> *(NSArray *challengeAndKeyID) {
  267. // 2. Attest the key.
  268. NSData *challenge = challengeAndKeyID.firstObject;
  269. NSString *keyID = challengeAndKeyID.lastObject;
  270. return [self attestKey:keyID challenge:challenge];
  271. })
  272. .thenOn(self.queue,
  273. ^FBLPromise<NSArray *> *(FIRAppAttestKeyAttestationResult *result) {
  274. // 3. Exchange the attestation to FAC token and pass the results to the next step.
  275. NSArray *attestationResults = @[
  276. // 3.1. Just pass the attestation result to the next step.
  277. [FBLPromise resolvedWith:result],
  278. // 3.2. Exchange the attestation to FAC token.
  279. [self.APIService attestKeyWithAttestation:result.attestation
  280. keyID:result.keyID
  281. challenge:result.challenge]
  282. ];
  283. return [FBLPromise onQueue:self.queue all:attestationResults];
  284. })
  285. .recoverOn(self.queue, ^id(NSError *error) {
  286. // If App Attest attestation was rejected then reset the attestation and throw a specific
  287. // error.
  288. FIRAppCheckHTTPError *HTTPError = (FIRAppCheckHTTPError *)error;
  289. if ([HTTPError isKindOfClass:[FIRAppCheckHTTPError class]] &&
  290. HTTPError.HTTPResponse.statusCode == 403) {
  291. FIRAppCheckDebugLog(kFIRLoggerAppCheckMessageCodeAttestationRejected,
  292. @"App Attest attestation was rejected by backend. The existing "
  293. @"attestation will be reset.");
  294. // Reset the attestation.
  295. return [self resetAttestation].thenOn(self.queue, ^NSError *(id result) {
  296. // Throw the rejection error.
  297. return [[FIRAppAttestRejectionError alloc] init];
  298. });
  299. }
  300. // Otherwise just re-throw the error.
  301. return error;
  302. });
  303. }
  304. /// Resets stored key ID and attestation artifact.
  305. - (FBLPromise<NSNull *> *)resetAttestation {
  306. return [self.keyIDStorage setAppAttestKeyID:nil].thenOn(self.queue, ^id(id result) {
  307. return [self.artifactStorage setArtifact:nil forKey:@""];
  308. });
  309. }
  310. #pragma mark - Token refresh sequence (assertion)
  311. - (FBLPromise<FIRAppCheckToken *> *)refreshTokenWithKeyID:(NSString *)keyID
  312. artifact:(NSData *)artifact {
  313. return [self.APIService getRandomChallenge]
  314. .thenOn(self.queue,
  315. ^FBLPromise<FIRAppAttestAssertionData *> *(NSData *challenge) {
  316. return [self generateAssertionWithKeyID:keyID
  317. artifact:artifact
  318. challenge:challenge];
  319. })
  320. .thenOn(self.queue, ^id(FIRAppAttestAssertionData *assertion) {
  321. return [self.APIService getAppCheckTokenWithArtifact:assertion.artifact
  322. challenge:assertion.challenge
  323. assertion:assertion.assertion];
  324. });
  325. }
  326. - (FBLPromise<FIRAppAttestAssertionData *> *)generateAssertionWithKeyID:(NSString *)keyID
  327. artifact:(NSData *)artifact
  328. challenge:(NSData *)challenge {
  329. // 1. Calculate the statement and its hash for assertion.
  330. return [FBLPromise
  331. onQueue:self.queue
  332. do:^NSData *_Nullable {
  333. // 1.1. Compose statement to generate assertion for.
  334. NSMutableData *statementForAssertion = [artifact mutableCopy];
  335. [statementForAssertion appendData:challenge];
  336. // 1.2. Get the statement SHA256 hash.
  337. return [FIRAppCheckCryptoUtils sha256HashFromData:[statementForAssertion copy]];
  338. }]
  339. .thenOn(
  340. self.queue,
  341. ^FBLPromise<NSData *> *(NSData *statementHash) {
  342. // 2. Generate App Attest assertion.
  343. return [FBLPromise onQueue:self.queue
  344. wrapObjectOrErrorCompletion:^(FBLPromiseObjectOrErrorCompletion _Nonnull handler) {
  345. [self.appAttestService generateAssertion:keyID
  346. clientDataHash:statementHash
  347. completionHandler:handler];
  348. }];
  349. })
  350. // 3. Compose the result object.
  351. .thenOn(self.queue, ^FIRAppAttestAssertionData *(NSData *assertion) {
  352. return [[FIRAppAttestAssertionData alloc] initWithChallenge:challenge
  353. artifact:artifact
  354. assertion:assertion];
  355. });
  356. }
  357. #pragma mark - State handling
  358. - (FBLPromise<FIRAppAttestProviderState *> *)attestationState {
  359. dispatch_queue_t stateQueue =
  360. dispatch_queue_create("FIRAppAttestProvider.state", DISPATCH_QUEUE_SERIAL);
  361. return [FBLPromise
  362. onQueue:stateQueue
  363. do:^id _Nullable {
  364. NSError *error;
  365. // 1. Check if App Attest is supported.
  366. id isSupportedResult = FBLPromiseAwait([self isAppAttestSupported], &error);
  367. if (isSupportedResult == nil) {
  368. return [[FIRAppAttestProviderState alloc] initUnsupportedWithError:error];
  369. }
  370. // 2. Check for stored key ID of the generated App Attest key pair.
  371. NSString *appAttestKeyID =
  372. FBLPromiseAwait([self.keyIDStorage getAppAttestKeyID], &error);
  373. if (appAttestKeyID == nil) {
  374. return [[FIRAppAttestProviderState alloc] initWithSupportedInitialState];
  375. }
  376. // 3. Check for stored attestation artifact received from Firebase backend.
  377. NSData *attestationArtifact =
  378. FBLPromiseAwait([self.artifactStorage getArtifactForKey:appAttestKeyID], &error);
  379. if (attestationArtifact == nil) {
  380. return [[FIRAppAttestProviderState alloc] initWithGeneratedKeyID:appAttestKeyID];
  381. }
  382. // 4. A valid App Attest key pair was generated and registered with Firebase
  383. // backend. Return the corresponding state.
  384. return [[FIRAppAttestProviderState alloc] initWithRegisteredKeyID:appAttestKeyID
  385. artifact:attestationArtifact];
  386. }];
  387. }
  388. #pragma mark - Helpers
  389. /// Returns a resolved promise if App Attest is supported and a rejected promise if it is not.
  390. - (FBLPromise<NSNull *> *)isAppAttestSupported {
  391. if (self.appAttestService.isSupported) {
  392. return [FBLPromise resolvedWith:[NSNull null]];
  393. } else {
  394. NSError *error = [FIRAppCheckErrorUtil unsupportedAttestationProvider:@"AppAttestProvider"];
  395. FBLPromise *rejectedPromise = [FBLPromise pendingPromise];
  396. [rejectedPromise reject:error];
  397. return rejectedPromise;
  398. }
  399. }
  400. /// Generates a new App Attest key associated with the Firebase app if `storedKeyID == nil`.
  401. - (FBLPromise<NSString *> *)generateAppAttestKeyIDIfNeeded:(nullable NSString *)storedKeyID {
  402. if (storedKeyID) {
  403. // The key ID has been fetched already, just return it.
  404. return [FBLPromise resolvedWith:storedKeyID];
  405. } else {
  406. // Generate and save a new key otherwise.
  407. return [self generateAppAttestKey];
  408. }
  409. }
  410. /// Generates and stores App Attest key associated with the Firebase app.
  411. - (FBLPromise<NSString *> *)generateAppAttestKey {
  412. return [FBLPromise onQueue:self.queue
  413. wrapObjectOrErrorCompletion:^(FBLPromiseObjectOrErrorCompletion _Nonnull handler) {
  414. [self.appAttestService generateKeyWithCompletionHandler:handler];
  415. }]
  416. .thenOn(self.queue, ^FBLPromise<NSString *> *(NSString *keyID) {
  417. return [self.keyIDStorage setAppAttestKeyID:keyID];
  418. });
  419. }
  420. @end
  421. NS_ASSUME_NONNULL_END