Skip to content

Commit

Permalink
[App Check] Add failure reasons for App Attest error cases (#11956)
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewheard authored Oct 19, 2023
1 parent 770776d commit f73ca95
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 24 deletions.
56 changes: 35 additions & 21 deletions FirebaseAppCheck/Sources/AppAttestProvider/FIRAppAttestProvider.m
Original file line number Diff line number Diff line change
Expand Up @@ -280,16 +280,21 @@ - (void)getTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable, NSError *_
do:^NSData *_Nullable {
return [FIRAppCheckCryptoUtils sha256HashFromData:challenge];
}]
.thenOn(
self.queue,
^FBLPromise<NSData *> *(NSData *challengeHash) {
return [FBLPromise onQueue:self.queue
wrapObjectOrErrorCompletion:^(FBLPromiseObjectOrErrorCompletion _Nonnull handler) {
[self.appAttestService attestKey:keyID
clientDataHash:challengeHash
completionHandler:handler];
}];
})
.thenOn(self.queue,
^FBLPromise<NSData *> *(NSData *challengeHash) {
return [FBLPromise onQueue:self.queue
wrapObjectOrErrorCompletion:^(
FBLPromiseObjectOrErrorCompletion _Nonnull handler) {
[self.appAttestService attestKey:keyID
clientDataHash:challengeHash
completionHandler:handler];
}]
.recoverOn(self.queue, ^id(NSError *error) {
return [FIRAppCheckErrorUtil appAttestAttestKeyFailedWithError:error
keyId:keyID
clientDataHash:challengeHash];
});
})
.thenOn(self.queue, ^FBLPromise<FIRAppAttestKeyAttestationResult *> *(NSData *attestation) {
FIRAppAttestKeyAttestationResult *result =
[[FIRAppAttestKeyAttestationResult alloc] initWithKeyID:keyID
Expand Down Expand Up @@ -391,17 +396,22 @@ - (void)getTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable, NSError *_
// 1.2. Get the statement SHA256 hash.
return [FIRAppCheckCryptoUtils sha256HashFromData:[statementForAssertion copy]];
}]
.thenOn(
self.queue,
^FBLPromise<NSData *> *(NSData *statementHash) {
// 2. Generate App Attest assertion.
return [FBLPromise onQueue:self.queue
wrapObjectOrErrorCompletion:^(FBLPromiseObjectOrErrorCompletion _Nonnull handler) {
[self.appAttestService generateAssertion:keyID
clientDataHash:statementHash
completionHandler:handler];
}];
})
.thenOn(self.queue,
^FBLPromise<NSData *> *(NSData *statementHash) {
return [FBLPromise onQueue:self.queue
wrapObjectOrErrorCompletion:^(
FBLPromiseObjectOrErrorCompletion _Nonnull handler) {
[self.appAttestService generateAssertion:keyID
clientDataHash:statementHash
completionHandler:handler];
}]
.recoverOn(self.queue, ^id(NSError *error) {
return [FIRAppCheckErrorUtil
appAttestGenerateAssertionFailedWithError:error
keyId:keyID
clientDataHash:statementHash];
});
})
// 3. Compose the result object.
.thenOn(self.queue, ^FIRAppAttestAssertionData *(NSData *assertion) {
return [[FIRAppAttestAssertionData alloc] initWithChallenge:challenge
Expand Down Expand Up @@ -479,6 +489,10 @@ - (void)getTokenWithCompletion:(void (^)(FIRAppCheckToken *_Nullable, NSError *_
wrapObjectOrErrorCompletion:^(FBLPromiseObjectOrErrorCompletion _Nonnull handler) {
[self.appAttestService generateKeyWithCompletionHandler:handler];
}]
.recoverOn(self.queue,
^id(NSError *error) {
return [FIRAppCheckErrorUtil appAttestGenerateKeyFailedWithError:error];
})
.thenOn(self.queue, ^FBLPromise<NSString *> *(NSString *keyID) {
return [self.keyIDStorage setAppAttestKeyID:keyID];
});
Expand Down
12 changes: 12 additions & 0 deletions FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,18 @@ void FIRAppCheckSetErrorToPointer(NSError *error, NSError **pointer);

+ (NSError *)appAttestKeyIDNotFound;

// MARK: - App Attest Errors

+ (NSError *)appAttestGenerateKeyFailedWithError:(NSError *)error;

+ (NSError *)appAttestAttestKeyFailedWithError:(NSError *)error
keyId:(NSString *)keyId
clientDataHash:(NSData *)clientDataHash;

+ (NSError *)appAttestGenerateAssertionFailedWithError:(NSError *)error
keyId:(NSString *)keyId
clientDataHash:(NSData *)clientDataHash;

@end

NS_ASSUME_NONNULL_END
39 changes: 39 additions & 0 deletions FirebaseAppCheck/Sources/Core/Errors/FIRAppCheckErrorUtil.m
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,45 @@ + (NSError *)errorWithFailureReason:(NSString *)failureReason {
underlyingError:nil];
}

#pragma mark - App Attest

+ (NSError *)appAttestGenerateKeyFailedWithError:(NSError *)error {
NSString *failureReason = @"Failed to generate a new cryptographic key for use with the App "
@"Attest service (`generateKeyWithCompletionHandler:`).";
// TODO(#11967): Add a new error code for this case (e.g., FIRAppCheckAppAttestGenerateKeyFailed).
return [self appCheckErrorWithCode:FIRAppCheckErrorCodeUnknown
failureReason:failureReason
underlyingError:error];
}

+ (NSError *)appAttestAttestKeyFailedWithError:(NSError *)error
keyId:(NSString *)keyId
clientDataHash:(NSData *)clientDataHash {
NSString *failureReason =
[NSString stringWithFormat:@"Failed to attest the validity of the generated cryptographic "
@"key (`attestKey:clientDataHash:completionHandler:`); "
@"keyId.length = %lu, clientDataHash.length = %lu",
keyId.length, clientDataHash.length];
// TODO(#11967): Add a new error code for this case (e.g., FIRAppCheckAppAttestAttestKeyFailed).
return [self appCheckErrorWithCode:FIRAppCheckErrorCodeUnknown
failureReason:failureReason
underlyingError:error];
}

+ (NSError *)appAttestGenerateAssertionFailedWithError:(NSError *)error
keyId:(NSString *)keyId
clientDataHash:(NSData *)clientDataHash {
NSString *failureReason = [NSString
stringWithFormat:@"Failed to create a block of data that demonstrates the legitimacy of the "
@"app instance (`generateAssertion:clientDataHash:completionHandler:`); "
@"keyId.length = %lu, clientDataHash.length = %lu.",
keyId.length, clientDataHash.length];
// TODO(#11967): Add error code for this case (e.g., FIRAppCheckAppAttestGenerateAssertionFailed).
return [self appCheckErrorWithCode:FIRAppCheckErrorCodeUnknown
failureReason:failureReason
underlyingError:error];
}

#pragma mark - Helpers

+ (NSError *)unknownErrorWithError:(NSError *)error {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,10 @@ - (void)testGetToken_WhenUnregisteredKeyAndKeyAttestationError {
NSError *attestationError = [NSError errorWithDomain:@"testGetTokenWhenKeyAttestationError"
code:0
userInfo:nil];
NSError *expectedError =
[FIRAppCheckErrorUtil appAttestAttestKeyFailedWithError:attestationError
keyId:existingKeyID
clientDataHash:self.randomChallengeHash];
id attestCompletionArg = [OCMArg invokeBlockWithArgs:[NSNull null], attestationError, nil];
OCMExpect([self.mockAppAttestService attestKey:existingKeyID
clientDataHash:self.randomChallengeHash
Expand All @@ -411,7 +415,7 @@ - (void)testGetToken_WhenUnregisteredKeyAndKeyAttestationError {
[completionExpectation fulfill];

XCTAssertNil(token);
XCTAssertEqualObjects(error, attestationError);
XCTAssertEqualObjects(error, expectedError);
}];

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

// 9. Verify backoff error.
XCTAssertEqualObjects(self.fakeBackoffWrapper.operationError, attestationError);
XCTAssertEqualObjects(self.fakeBackoffWrapper.operationError, expectedError);
}

- (void)testGetToken_WhenUnregisteredKeyAndKeyAttestationExchangeError {
Expand Down Expand Up @@ -671,6 +675,10 @@ - (void)testGetToken_WhenKeyRegisteredAndGenerateAssertionError {
[NSError errorWithDomain:@"testGetToken_WhenKeyRegisteredAndGenerateAssertionError"
code:0
userInfo:nil];
NSError *expectedError =
[FIRAppCheckErrorUtil appAttestGenerateAssertionFailedWithError:generateAssertionError
keyId:existingKeyID
clientDataHash:self.randomChallengeHash];
id completionBlockArg = [OCMArg invokeBlockWithArgs:[NSNull null], generateAssertionError, nil];
OCMExpect([self.mockAppAttestService
generateAssertion:existingKeyID
Expand All @@ -690,7 +698,7 @@ - (void)testGetToken_WhenKeyRegisteredAndGenerateAssertionError {
[completionExpectation fulfill];

XCTAssertNil(token);
XCTAssertEqualObjects(error, generateAssertionError);
XCTAssertEqualObjects(error, expectedError);
}];

[self waitForExpectations:@[ completionExpectation ] timeout:0.5];
Expand Down

0 comments on commit f73ca95

Please sign in to comment.