From b1587e89710b6180ea6ce5944ef00c3f4fd2adb2 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Tue, 24 Oct 2023 17:29:54 -0400 Subject: [PATCH] Add failure reasons for App Attest error cases --- .../AppAttestProvider/GACAppAttestProvider.m | 57 ++++++++++++------- .../Core/Errors/GACAppCheckErrorUtil.h | 12 ++++ .../Core/Errors/GACAppCheckErrorUtil.m | 43 +++++++++++++- .../GACAppAttestProviderTests.m | 14 ++++- 4 files changed, 100 insertions(+), 26 deletions(-) diff --git a/AppCheckCore/Sources/AppAttestProvider/GACAppAttestProvider.m b/AppCheckCore/Sources/AppAttestProvider/GACAppAttestProvider.m index b32183f..b9c5411 100644 --- a/AppCheckCore/Sources/AppAttestProvider/GACAppAttestProvider.m +++ b/AppCheckCore/Sources/AppAttestProvider/GACAppAttestProvider.m @@ -318,16 +318,21 @@ - (void)getTokenWithLimitedUse:(BOOL)limitedUse do:^NSData *_Nullable { return [GACAppCheckCryptoUtils sha256HashFromData:challenge]; }] - .thenOn( - self.queue, - ^FBLPromise *(NSData *challengeHash) { - return [FBLPromise onQueue:self.queue - wrapObjectOrErrorCompletion:^(FBLPromiseObjectOrErrorCompletion _Nonnull handler) { - [self.appAttestService attestKey:keyID - clientDataHash:challengeHash - completionHandler:handler]; - }]; - }) + .thenOn(self.queue, + ^FBLPromise *(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 [GACAppCheckErrorUtil appAttestAttestKeyFailedWithError:error + keyId:keyID + clientDataHash:challengeHash]; + }); + }) .thenOn(self.queue, ^FBLPromise *(NSData *attestation) { GACAppAttestKeyAttestationResult *result = [[GACAppAttestKeyAttestationResult alloc] initWithKeyID:keyID @@ -433,17 +438,23 @@ - (void)getTokenWithLimitedUse:(BOOL)limitedUse // 1.2. Get the statement SHA256 hash. return [GACAppCheckCryptoUtils sha256HashFromData:[statementForAssertion copy]]; }] - .thenOn( - self.queue, - ^FBLPromise *(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 *statementHash) { + // 2. Generate App Attest assertion. + return [FBLPromise onQueue:self.queue + wrapObjectOrErrorCompletion:^( + FBLPromiseObjectOrErrorCompletion _Nonnull handler) { + [self.appAttestService generateAssertion:keyID + clientDataHash:statementHash + completionHandler:handler]; + }] + .recoverOn(self.queue, ^id(NSError *error) { + return [GACAppCheckErrorUtil + appAttestGenerateAssertionFailedWithError:error + keyId:keyID + clientDataHash:statementHash]; + }); + }) // 3. Compose the result object. .thenOn(self.queue, ^GACAppAttestAssertionData *(NSData *assertion) { return [[GACAppAttestAssertionData alloc] initWithChallenge:challenge @@ -521,6 +532,10 @@ - (void)getTokenWithLimitedUse:(BOOL)limitedUse wrapObjectOrErrorCompletion:^(FBLPromiseObjectOrErrorCompletion _Nonnull handler) { [self.appAttestService generateKeyWithCompletionHandler:handler]; }] + .recoverOn(self.queue, + ^id(NSError *error) { + return [GACAppCheckErrorUtil appAttestGenerateKeyFailedWithError:error]; + }) .thenOn(self.queue, ^FBLPromise *(NSString *keyID) { return [self.keyIDStorage setAppAttestKeyID:keyID]; }); diff --git a/AppCheckCore/Sources/Core/Errors/GACAppCheckErrorUtil.h b/AppCheckCore/Sources/Core/Errors/GACAppCheckErrorUtil.h index ccc168f..5df000f 100644 --- a/AppCheckCore/Sources/Core/Errors/GACAppCheckErrorUtil.h +++ b/AppCheckCore/Sources/Core/Errors/GACAppCheckErrorUtil.h @@ -49,8 +49,20 @@ void GACAppCheckSetErrorToPointer(NSError *error, NSError **pointer); + (NSError *)unsupportedAttestationProvider:(NSString *)providerName; +// MARK: - App Attest Errors + + (NSError *)appAttestKeyIDNotFound; ++ (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 diff --git a/AppCheckCore/Sources/Core/Errors/GACAppCheckErrorUtil.m b/AppCheckCore/Sources/Core/Errors/GACAppCheckErrorUtil.m index 34d9260..f62d989 100644 --- a/AppCheckCore/Sources/Core/Errors/GACAppCheckErrorUtil.m +++ b/AppCheckCore/Sources/Core/Errors/GACAppCheckErrorUtil.m @@ -105,6 +105,14 @@ + (NSError *)unsupportedAttestationProvider:(NSString *)providerName { underlyingError:nil]; } ++ (NSError *)errorWithFailureReason:(NSString *)failureReason { + return [self appCheckErrorWithCode:GACAppCheckErrorCodeUnknown + failureReason:failureReason + underlyingError:nil]; +} + +#pragma mark - App Attest + + (NSError *)appAttestKeyIDNotFound { NSString *failureReason = [NSString stringWithFormat:@"App attest key ID not found."]; return [self appCheckErrorWithCode:GACAppCheckErrorCodeUnknown @@ -112,10 +120,41 @@ + (NSError *)appAttestKeyIDNotFound { underlyingError:nil]; } -+ (NSError *)errorWithFailureReason:(NSString *)failureReason { ++ (NSError *)appAttestGenerateKeyFailedWithError:(NSError *)error { + NSString *failureReason = @"Failed to generate a new cryptographic key for use with the App " + @"Attest service (`generateKeyWithCompletionHandler:`)."; + // TODO(#31): Add a new error code for this case (e.g., GACAppCheckAppAttestGenerateKeyFailed). return [self appCheckErrorWithCode:GACAppCheckErrorCodeUnknown failureReason:failureReason - underlyingError:nil]; + 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(#31): Add a new error code for this case (e.g., GACAppCheckAppAttestAttestKeyFailed). + return [self appCheckErrorWithCode:GACAppCheckErrorCodeUnknown + 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(#31): Add error code for this case (e.g., GACAppCheckAppAttestGenerateAssertionFailed). + return [self appCheckErrorWithCode:GACAppCheckErrorCodeUnknown + failureReason:failureReason + underlyingError:error]; } #pragma mark - Helpers diff --git a/AppCheckCore/Tests/Unit/AppAttestProvider/GACAppAttestProviderTests.m b/AppCheckCore/Tests/Unit/AppAttestProvider/GACAppAttestProviderTests.m index 7dfda21..c9c3984 100644 --- a/AppCheckCore/Tests/Unit/AppAttestProvider/GACAppAttestProviderTests.m +++ b/AppCheckCore/Tests/Unit/AppAttestProvider/GACAppAttestProviderTests.m @@ -390,6 +390,10 @@ - (void)testGetToken_WhenUnregisteredKeyAndKeyAttestationError { NSError *attestationError = [NSError errorWithDomain:@"testGetTokenWhenKeyAttestationError" code:0 userInfo:nil]; + NSError *expectedError = + [GACAppCheckErrorUtil appAttestAttestKeyFailedWithError:attestationError + keyId:existingKeyID + clientDataHash:self.randomChallengeHash]; id attestCompletionArg = [OCMArg invokeBlockWithArgs:[NSNull null], attestationError, nil]; OCMExpect([self.mockAppAttestService attestKey:existingKeyID clientDataHash:self.randomChallengeHash @@ -410,7 +414,7 @@ - (void)testGetToken_WhenUnregisteredKeyAndKeyAttestationError { [completionExpectation fulfill]; XCTAssertNil(token); - XCTAssertEqualObjects(error, attestationError); + XCTAssertEqualObjects(error, expectedError); }]; [self waitForExpectations:@[ self.fakeBackoffWrapper.backoffExpectation, completionExpectation ] @@ -421,7 +425,7 @@ - (void)testGetToken_WhenUnregisteredKeyAndKeyAttestationError { [self verifyAllMocks]; // 9. Verify backoff error. - XCTAssertEqualObjects(self.fakeBackoffWrapper.operationError, attestationError); + XCTAssertEqualObjects(self.fakeBackoffWrapper.operationError, expectedError); } - (void)testGetToken_WhenUnregisteredKeyAndKeyAttestationExchangeError { @@ -697,6 +701,10 @@ - (void)testGetToken_WhenKeyRegisteredAndGenerateAssertionError { [NSError errorWithDomain:@"testGetToken_WhenKeyRegisteredAndGenerateAssertionError" code:0 userInfo:nil]; + NSError *expectedError = + [GACAppCheckErrorUtil appAttestGenerateAssertionFailedWithError:generateAssertionError + keyId:existingKeyID + clientDataHash:self.randomChallengeHash]; id completionBlockArg = [OCMArg invokeBlockWithArgs:[NSNull null], generateAssertionError, nil]; OCMExpect([self.mockAppAttestService generateAssertion:existingKeyID @@ -718,7 +726,7 @@ - (void)testGetToken_WhenKeyRegisteredAndGenerateAssertionError { [completionExpectation fulfill]; XCTAssertNil(token); - XCTAssertEqualObjects(error, generateAssertionError); + XCTAssertEqualObjects(error, expectedError); }]; [self waitForExpectations:@[ completionExpectation ] timeout:0.5];