Skip to content

Commit f73ca95

Browse files
authored
[App Check] Add failure reasons for App Attest error cases (#11956)
1 parent 770776d commit f73ca95

File tree

4 files changed

+97
-24
lines changed

4 files changed

+97
-24
lines changed

FirebaseAppCheck/Sources/AppAttestProvider/FIRAppAttestProvider.m

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -280,16 +280,21 @@ - (void)getTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable, NSError *_
280280
do:^NSData *_Nullable {
281281
return [FIRAppCheckCryptoUtils sha256HashFromData:challenge];
282282
}]
283-
.thenOn(
284-
self.queue,
285-
^FBLPromise<NSData *> *(NSData *challengeHash) {
286-
return [FBLPromise onQueue:self.queue
287-
wrapObjectOrErrorCompletion:^(FBLPromiseObjectOrErrorCompletion _Nonnull handler) {
288-
[self.appAttestService attestKey:keyID
289-
clientDataHash:challengeHash
290-
completionHandler:handler];
291-
}];
292-
})
283+
.thenOn(self.queue,
284+
^FBLPromise<NSData *> *(NSData *challengeHash) {
285+
return [FBLPromise onQueue:self.queue
286+
wrapObjectOrErrorCompletion:^(
287+
FBLPromiseObjectOrErrorCompletion _Nonnull handler) {
288+
[self.appAttestService attestKey:keyID
289+
clientDataHash:challengeHash
290+
completionHandler:handler];
291+
}]
292+
.recoverOn(self.queue, ^id(NSError *error) {
293+
return [FIRAppCheckErrorUtil appAttestAttestKeyFailedWithError:error
294+
keyId:keyID
295+
clientDataHash:challengeHash];
296+
});
297+
})
293298
.thenOn(self.queue, ^FBLPromise<FIRAppAttestKeyAttestationResult *> *(NSData *attestation) {
294299
FIRAppAttestKeyAttestationResult *result =
295300
[[FIRAppAttestKeyAttestationResult alloc] initWithKeyID:keyID
@@ -391,17 +396,22 @@ - (void)getTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable, NSError *_
391396
// 1.2. Get the statement SHA256 hash.
392397
return [FIRAppCheckCryptoUtils sha256HashFromData:[statementForAssertion copy]];
393398
}]
394-
.thenOn(
395-
self.queue,
396-
^FBLPromise<NSData *> *(NSData *statementHash) {
397-
// 2. Generate App Attest assertion.
398-
return [FBLPromise onQueue:self.queue
399-
wrapObjectOrErrorCompletion:^(FBLPromiseObjectOrErrorCompletion _Nonnull handler) {
400-
[self.appAttestService generateAssertion:keyID
401-
clientDataHash:statementHash
402-
completionHandler:handler];
403-
}];
404-
})
399+
.thenOn(self.queue,
400+
^FBLPromise<NSData *> *(NSData *statementHash) {
401+
return [FBLPromise onQueue:self.queue
402+
wrapObjectOrErrorCompletion:^(
403+
FBLPromiseObjectOrErrorCompletion _Nonnull handler) {
404+
[self.appAttestService generateAssertion:keyID
405+
clientDataHash:statementHash
406+
completionHandler:handler];
407+
}]
408+
.recoverOn(self.queue, ^id(NSError *error) {
409+
return [FIRAppCheckErrorUtil
410+
appAttestGenerateAssertionFailedWithError:error
411+
keyId:keyID
412+
clientDataHash:statementHash];
413+
});
414+
})
405415
// 3. Compose the result object.
406416
.thenOn(self.queue, ^FIRAppAttestAssertionData *(NSData *assertion) {
407417
return [[FIRAppAttestAssertionData alloc] initWithChallenge:challenge
@@ -479,6 +489,10 @@ - (void)getTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable, NSError *_
479489
wrapObjectOrErrorCompletion:^(FBLPromiseObjectOrErrorCompletion _Nonnull handler) {
480490
[self.appAttestService generateKeyWithCompletionHandler:handler];
481491
}]
492+
.recoverOn(self.queue,
493+
^id(NSError *error) {
494+
return [FIRAppCheckErrorUtil appAttestGenerateKeyFailedWithError:error];
495+
})
482496
.thenOn(self.queue, ^FBLPromise<NSString *> *(NSString *keyID) {
483497
return [self.keyIDStorage setAppAttestKeyID:keyID];
484498
});

FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,18 @@ void FIRAppCheckSetErrorToPointer(NSError *error, NSError **pointer);
5151

5252
+ (NSError *)appAttestKeyIDNotFound;
5353

54+
// MARK: - App Attest Errors
55+
56+
+ (NSError *)appAttestGenerateKeyFailedWithError:(NSError *)error;
57+
58+
+ (NSError *)appAttestAttestKeyFailedWithError:(NSError *)error
59+
keyId:(NSString *)keyId
60+
clientDataHash:(NSData *)clientDataHash;
61+
62+
+ (NSError *)appAttestGenerateAssertionFailedWithError:(NSError *)error
63+
keyId:(NSString *)keyId
64+
clientDataHash:(NSData *)clientDataHash;
65+
5466
@end
5567

5668
NS_ASSUME_NONNULL_END

FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.m

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,45 @@ + (NSError *)errorWithFailureReason:(NSString *)failureReason {
118118
underlyingError:nil];
119119
}
120120

121+
#pragma mark - App Attest
122+
123+
+ (NSError *)appAttestGenerateKeyFailedWithError:(NSError *)error {
124+
NSString *failureReason = @"Failed to generate a new cryptographic key for use with the App "
125+
@"Attest service (`generateKeyWithCompletionHandler:`).";
126+
// TODO(#11967): Add a new error code for this case (e.g., FIRAppCheckAppAttestGenerateKeyFailed).
127+
return [self appCheckErrorWithCode:FIRAppCheckErrorCodeUnknown
128+
failureReason:failureReason
129+
underlyingError:error];
130+
}
131+
132+
+ (NSError *)appAttestAttestKeyFailedWithError:(NSError *)error
133+
keyId:(NSString *)keyId
134+
clientDataHash:(NSData *)clientDataHash {
135+
NSString *failureReason =
136+
[NSString stringWithFormat:@"Failed to attest the validity of the generated cryptographic "
137+
@"key (`attestKey:clientDataHash:completionHandler:`); "
138+
@"keyId.length = %lu, clientDataHash.length = %lu",
139+
keyId.length, clientDataHash.length];
140+
// TODO(#11967): Add a new error code for this case (e.g., FIRAppCheckAppAttestAttestKeyFailed).
141+
return [self appCheckErrorWithCode:FIRAppCheckErrorCodeUnknown
142+
failureReason:failureReason
143+
underlyingError:error];
144+
}
145+
146+
+ (NSError *)appAttestGenerateAssertionFailedWithError:(NSError *)error
147+
keyId:(NSString *)keyId
148+
clientDataHash:(NSData *)clientDataHash {
149+
NSString *failureReason = [NSString
150+
stringWithFormat:@"Failed to create a block of data that demonstrates the legitimacy of the "
151+
@"app instance (`generateAssertion:clientDataHash:completionHandler:`); "
152+
@"keyId.length = %lu, clientDataHash.length = %lu.",
153+
keyId.length, clientDataHash.length];
154+
// TODO(#11967): Add error code for this case (e.g., FIRAppCheckAppAttestGenerateAssertionFailed).
155+
return [self appCheckErrorWithCode:FIRAppCheckErrorCodeUnknown
156+
failureReason:failureReason
157+
underlyingError:error];
158+
}
159+
121160
#pragma mark - Helpers
122161

123162
+ (NSError *)unknownErrorWithError:(NSError *)error {

FirebaseAppCheck/Tests/Unit/AppAttestProvider/FIRAppAttestProviderTests.m

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,10 @@ - (void)testGetToken_WhenUnregisteredKeyAndKeyAttestationError {
393393
NSError *attestationError = [NSError errorWithDomain:@"testGetTokenWhenKeyAttestationError"
394394
code:0
395395
userInfo:nil];
396+
NSError *expectedError =
397+
[FIRAppCheckErrorUtil appAttestAttestKeyFailedWithError:attestationError
398+
keyId:existingKeyID
399+
clientDataHash:self.randomChallengeHash];
396400
id attestCompletionArg = [OCMArg invokeBlockWithArgs:[NSNull null], attestationError, nil];
397401
OCMExpect([self.mockAppAttestService attestKey:existingKeyID
398402
clientDataHash:self.randomChallengeHash
@@ -411,7 +415,7 @@ - (void)testGetToken_WhenUnregisteredKeyAndKeyAttestationError {
411415
[completionExpectation fulfill];
412416

413417
XCTAssertNil(token);
414-
XCTAssertEqualObjects(error, attestationError);
418+
XCTAssertEqualObjects(error, expectedError);
415419
}];
416420

417421
[self waitForExpectations:@[ self.fakeBackoffWrapper.backoffExpectation, completionExpectation ]
@@ -422,7 +426,7 @@ - (void)testGetToken_WhenUnregisteredKeyAndKeyAttestationError {
422426
[self verifyAllMocks];
423427

424428
// 9. Verify backoff error.
425-
XCTAssertEqualObjects(self.fakeBackoffWrapper.operationError, attestationError);
429+
XCTAssertEqualObjects(self.fakeBackoffWrapper.operationError, expectedError);
426430
}
427431

428432
- (void)testGetToken_WhenUnregisteredKeyAndKeyAttestationExchangeError {
@@ -671,6 +675,10 @@ - (void)testGetToken_WhenKeyRegisteredAndGenerateAssertionError {
671675
[NSError errorWithDomain:@"testGetToken_WhenKeyRegisteredAndGenerateAssertionError"
672676
code:0
673677
userInfo:nil];
678+
NSError *expectedError =
679+
[FIRAppCheckErrorUtil appAttestGenerateAssertionFailedWithError:generateAssertionError
680+
keyId:existingKeyID
681+
clientDataHash:self.randomChallengeHash];
674682
id completionBlockArg = [OCMArg invokeBlockWithArgs:[NSNull null], generateAssertionError, nil];
675683
OCMExpect([self.mockAppAttestService
676684
generateAssertion:existingKeyID
@@ -690,7 +698,7 @@ - (void)testGetToken_WhenKeyRegisteredAndGenerateAssertionError {
690698
[completionExpectation fulfill];
691699

692700
XCTAssertNil(token);
693-
XCTAssertEqualObjects(error, generateAssertionError);
701+
XCTAssertEqualObjects(error, expectedError);
694702
}];
695703

696704
[self waitForExpectations:@[ completionExpectation ] timeout:0.5];

0 commit comments

Comments
 (0)