Thanks for being a part of WWDC25!

How did we do? We’d love to know your thoughts on this year’s conference. Take the survey here

DCError.invalidInput on generateAssertion() - Affecting Small Subset of Users

Issue Summary

I'm encountering a DCError.invalidInput error when calling DCAppAttestService.shared.generateAssertion() in my App Attest implementation. This issue affects only a small subset of users - the majority of users can successfully complete both attestation and assertion flows without any issues. According to Apple Engineer feedback, there might be a small implementation issue in my code.

Key Observations

  • Success Rate: ~95% of users complete the flow successfully
  • Failure Pattern: The remaining ~5% consistently fail at assertion generation
  • Key Length: Logs show key length of 44 characters for both successful and failing cases
  • Consistency: Users who experience the error tend to experience it consistently
  • Platform: Issue observed across different iOS versions and device types

Environment

  • iOS App Attest implementation
  • Using DCAppAttestService for both attestation and assertion
  • Custom relying party server communication
  • Issue affects ~5% of users consistently

Key Implementation Details

1. Attestation Flow (Working)

The attestation process works correctly:

// Generate key and attest (successful for all users)
self.attestService.generateKey { keyId, keyIdError in
    guard keyIdError == nil, let keyId = keyId else {
        return completionHandler(.failure(.dcError(keyIdError as! DCError)))
    }
    // Note: keyId length is consistently 44 characters for both successful and failing users
    
    // Attest key with Apple servers
    self.attestKey(keyId, clientData: clientData) { result in
        // ... verification with RP server
        // Key is successfully stored for ALL users (including those who later fail at assertion)
    }
}

2. Assertion Flow (Failing for ~5% of Users with invalidInput)

The assertion generation fails for a consistent subset of users:

// Get assertion data from RP server
self.assertRelyingParty.getAssertionData(kid, with: data) { result in
    switch result {
    case .success(let receivedData):
        let session = receivedData.session
        let clientData = receivedData.clientData
        let hash = clientData.toSHA256()  // SHA256 hash of client data
        
        // THIS CALL FAILS WITH invalidInput for ~5% of users
        // Same keyId (44 chars) that worked for attestation
        self.attestService.generateAssertion(kid, clientDataHash: hash) { assertion, err in
            guard err == nil, let assertion = assertion else {
                // Error: DCError.invalidInput
                if let err = err as? DCError, err.code == .invalidKey {
                    return reattestAndAssert(.invalidKey, completionHandler)
                } else {
                    return completionHandler(.failure(.dcError(err as! DCError)))
                }
            }
            // ... verification logic
        }
    }
}

3. Client Data Structure

Client data JSON structure (identical for successful and failing users):

// For attestation (works for all users)
let clientData = ["challenge": receivedData.challenge]

// For assertion (fails for ~5% of users with same structure)
var clientData = ["challenge": receivedData.challenge]
if let data = data {  // Additional data for assertion
    clientData["account"] = data["account"]
    clientData["amount"] = data["amount"]
}

4. SHA256 Hash Implementation

extension Data {
    public func toSHA256() -> Data {
        return Data(SHA256.hash(data: self))
    }
}

5. Key Storage Implementation

Using UserDefaults for key storage (works consistently for all users):

private let keyStorageTag = "app-attest-keyid"

func setKey(_ keyId: String) -> Result<(), KeyStorageError> {
    UserDefaults.standard.set(keyId, forKey: keyStorageTag)
    return .success(())
}

func getKey() -> Result<String?, KeyStorageError> {
    let keyId = UserDefaults.standard.string(forKey: keyStorageTag)
    return .success(keyId)
}

Questions

  1. User-Specific Factors: Since this affects only ~5% of users consistently, could there be device-specific, iOS version-specific, or account-specific factors that cause invalidInput?

  2. Key State Validation: Is there any way to validate the state of an attested key before calling generateAssertion()? The key length (44 chars) appears normal for both successful and failing cases.

  3. Keychain vs UserDefaults: Could the issue be related to using UserDefaults instead of Keychain for key storage? Though this works for 95% of users.

  4. Race Conditions: Could there be subtle race conditions or timing issues that only affect certain users/devices?

  5. Error Recovery: Is there a recommended way to handle this error? Should we attempt re-attestation for these users?

Additional Context & Debugging Attempts

  • Consistent Failure: Users who experience this error typically experience it on every attempt
  • Key Validation: Both successful and failing users have identical key formats (44 character strings)
  • Device Diversity: Issue observed across different device models and iOS versions
  • Server Logs: Our server successfully provides challenges and processes attestation for all users
  • Re-attestation: Forcing re-attestation sometimes resolves the issue temporarily, but it often recurs

The fact that 95% of users succeed with identical code suggests there might be some environmental or device-specific factor that we're not accounting for. Any insights into what could cause invalidInput for a subset of users would be invaluable.

It would be interesting to see if there is a correlation between the users who are consistently in this error state and the iOS version they are using.

We had a bug a while back that was causing issues with keys, that has since been fixed, but the users need to update to the new iOS with the fix. Unfortunately for users who are unwilling or unable to update, the issue might be continuing.

If we have a sense of the iOS versions these users are in, we can determine if this could be due to the known issue, or something else we could look at.


Argun Tekant /  DTS Engineer / Core Technologies

@Engineer

Thank you very much for your response — we will continue monitoring this issue.

Currently, a major challenge is that many different types of logs are mixed together in Sentry, so we are working on separating and clarifying these logs by type and status in order to better track the issue.

Regarding the invalidKey error (Error Code 3):

  • In approximately 99% of cases where this error occurs on a device, it does not recover.
  • This means that attestKey() fails permanently on those devices.
  • In the remaining ~1% of cases, attestKey() continues to fail but then inexplicably succeeds at certain moments.
  • We’re trying to understand why these permanent failures happen, especially considering that we are following Apple’s recommended implementation guidelines.

Have you observed similar device-specific issues on your end?

DCError.invalidInput on generateAssertion() - Affecting Small Subset of Users
 
 
Q