We recently deployed Attestation on our application, and for a majority of the 40,000 users it works well. We have about six customers who are failing attestation. In digging through debug logs, we're seeing this error "iOS assertion verification failed. Unauthorized access attempted." We're assuming that the UUID is blocked somehow on Apple side but we're stumped as to why. We had a customer come in and we could look at the phone, and best we can tell it's just a generic phone with no jailbroken or any malicious apps. How can we determine if the UUID is blocked?
App Attest
RSS for tagValidate the integrity of your app before your server provides access to sensitive data.
Posts under App Attest tag
26 Posts
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
Hi,
For some reason all implemented (and working before) App Attest code has stopped working. iOS is unable to get attestation returning "Operations could not be completed. (com.apple.devicecheck.error error 4.) (serverUnavailable)"
On https://developer.apple.com/system-status/ I can see green dot but I suspect that infrastructure is not OK. This is happening with multiple of our apps in multiple geographical regions.
Can anyone confirm these problems or know whether it is strictly connected to App Attest service availability?
I tried to send it on the nodejs server I built. No success received 200
My work steps are as follows:
The app executes “DCAppAttestService.shared.attestKey” to get receiptData from the acquired attestation.
The app sends "receiptData.base64EncodedString()" to the server (code-1)
Nodejs code (code-2)
Because the app has been uploaded to TestFlight, I set the server IP to "data.appattest.apple.com"
Is there something wrong with my steps?
code-1
public func attestData(receipt:Data) {
if DCDevice.current.isSupported {
let sesh = URLSession(configuration: .default)
var req = URLRequest(url: URL(string: "http://10.254.239.27:3000/attestationData")!)
print(req)
req.addValue("application/json", forHTTPHeaderField: "Content-Type")
req.httpMethod = "POST"
let data = try! JSONSerialization.data(withJSONObject: ["receipt": receipt.base64EncodedString()], options: [])
req.httpBody = data
let task = sesh.dataTask(with: req, completionHandler: { (data, response, error) in
if let data = data, let jsonString = String(data: data, encoding: .utf8) {
print(jsonString)
}
})
task.resume()
} else {
print("Platform is not supported. Make sure you aren't running in an emulator.")
//self.stopActivity()
}
}
code-2
versionRouter.post('/attestationData', function(req, response) {
console.log("\n\n\n\n\n");
console.log("receiptApi");
var receiptBase64 = req.body.receipt;
if (!receiptBase64) {
return response.status(400).send({ error: 'Missing receipt data' });
}
let binaryReceipt;
if (typeof receiptBase64 === 'string') {
const cleaned = receiptBase64.trim();
binaryReceipt = Buffer.from(cleaned, 'base64');
}
if (Buffer.isBuffer(binaryReceipt)) {
//binaryReceipt = receiptBase64;
console.log("receipt is base64 或 Buffer: "+ Buffer.isBuffer(binaryReceipt));
} else {
console.error('⚠️ receipt is not base64 or Buffer');
response.status(400).send("Receipt format error");
return;
}
var jwToken = jwt.sign({}, cert, { algorithm: 'ES256',header: { typ: undefined }, issuer: teamId, keyid: keyId});
var post_options = {
host: 'data.appattest.apple.com',
port: '443',
path: '/v1/attestationData',
method: 'POST',
headers: {
'Authorization': jwToken,
'Content-Type': 'application/octet-stream',
'Content-Length': binaryReceipt.length
}
};
var post_req = https.request(post_options, function(res) {
res.setEncoding('utf8');
console.log("📨 Apple Response Header:", res.headers);
console.log("📨 Apple StatusCode:", res.statusCode);
var data = "";
res.on('data', function (chunk) {
data += chunk;
});
res.on('end', function() {
console.log(data);
response.send({"status": res.statusCode, data: data});
});
});
post_req.on('error', function(e) {
console.error('error:', e);
response.status(500).send({ error: e.message });
});
post_req.write(binaryReceipt);
post_req.end();
});
Hello, Preparing to use the app attest service documentation suggests we call attestKey(_:clientDataHash:completionHandler:) fewer than 100 requests per second.
I had a question - Our app might theoretically hit this limit on launch. Would Apple be able to relax these limits or is this a hard limit?
Hi everyone,
We are using the App Attest API to securely transition users to our new system. As part of this, we store the Key ID of the attestation key for each user to verify their identity later.
However, we’ve noticed that some users are encountering the error “DCErrorInvalidKey 3” when calling generateAssertion. Importantly, the key was previously successfully attested, and generateAssertion has worked before for these users.
Our questions:
Could this error be caused by an app or iOS update?
Is it problematic to link an attestation key's Key ID directly to a user, or are there scenarios where the key might change or become invalid?
If there’s a way to mitigate this issue or recover affected users, what best practices would you recommend?
Any help or shared experiences would be greatly appreciated! Thanks in advance.
Hi,
When calling generateAssertion on DCAppAttestService.shared, it gives invalidKey error when there was an update for an offloaded app.
The offloading and reinstall always works fine if it is the same version on app store that was offloaded from device,
but if there is an update and the app tries to reuse the keyID from previous installation for generateAssertion, attestation service rejects the key with error code 3 (invalid key) for a significant portion of our user.
In our internal testing it failed for more than a third of the update attempts.
STEPS TO REPRODUCE:
install v1 from app store
generate key using DCAppAttestService.shared.generateKey
Attest this key using DCAppAttestService.shared.attestKey
Send the attestation objection to our server and verify with apple servers
Generate assertions for network calls to backend using DCAppAttestService.shared.generateAssertion with keyID from step 2
Device offloads the app (manually triggered by user, or automatically by iOS)
A new version v2 is published to App Store
Use tries to open the app
Latest version is download from the App Store
App tries to use the keyID from step 2 to generate assertions
DCAppAttestService throws invalidKey error (Error Domain=com.apple.devicecheck.error Code=3)
Step 7 is critical here, if there is no new version of the app, the reinstalled v1 can reuse the key from step 2 without any issues
Is this behaviour expected?
Is there any way we can make sure the key is preserved between offloaded app updates?
Thanks