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
-
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
? -
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. -
Keychain vs UserDefaults: Could the issue be related to using UserDefaults instead of Keychain for key storage? Though this works for 95% of users.
-
Race Conditions: Could there be subtle race conditions or timing issues that only affect certain users/devices?
-
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.