As Apple now offers an official solution to this, I thought it's best to bring this up again and give a more detailed explanation on how to solve the problem, in case someone is still searching for it.
Basically, the x5c header always contains 3 certificates but you are only really interested in the first two. The first certificate is the "leaf" certificate and the second one is the "intermediate" certificate. The chain is completed with the root certificate, which you need to download from the Apple PKI (use the Apple Root CA certificates). To verify the chain, you have to verify the leaf certificate with the intermediate certificate and the intermediate certificate with the root certificate from Apple.
Now let me introduce another attack vector: Private signing keys are highly valuable and the more often a key is used, the more likely it is that it might be compromised at some point. Imagine an attacker is able to obtain a private key belonging to a leaf certificate from Apple. They would now be able to sign their own server notifications and not only the signature, but also the certificate chain would be valid, because after all, a valid chain cannot be cryptographically invalidated. The certificate will expire at some point but until then, it would be viewed as valid. That's where the Online Certificate Status Protocol (OCSP) comes in. The OCSP allows you to check online, whether a certificate was revoked. To do this, the certificates contain an OCSP URI with which you can request the status of the certificate. The response itself is signed again with the issuer certificate. In case of the leaf certificate, the intermediate certificate is the issuer, while in case of the intermediate certificate, the root certificate is.
So the full verification process would be as follows:
Download the Apple Root CA certificate and load it into your system as the trusted entity.
Get the first two certificates from the x5c header. The first certificate is the "leaf" and the second is the "intermediate".
Verify the leaf certificate with the intermediate certificate.
Verify the intermediate certificate with the root certificate.
Get the status of the leaf certificate using the OCSP, check that the certificate is explicitly not revoked, and verify that the response is signed with the intermediate certificate.
Get the status of the intermediate certificate using the OCSP, check that the certificate is explicitly not revoked, and verify that the response is signed with the root certificate.
Get the public key from the leaf certificate and verify the payloads signature with it.
Sound complicated? Well, it is. So it's best if you don't implement it yourself but use a secure library instead. Since the WWDC 2023, Apple offers an official library in Swift, Java, Python, and Node.js, and I implemented it in PHP.
Post
Replies
Boosts
Views
Activity
Yes and no. I think you can simulate notifications in the ways you said (which I didn't test), but it is possible to send notifications to the simulator by sending them to the above URL using the simulator's device token, just like you would do it for a real device and in production.
The strange thing about the simulator is: As I said, I tried adding the UNUserNotificationCenterDelegate and all of the methods there are called as expected. When the app is in the foreground while receiving a notification, it calls the userNotificationCenter(_:willPresent:withCompletionHandler:) and when opening the app by tapping on a notification it calls the userNotificationCenter(_:didReceive:withCompletionHandler:) method.
From all the tests I've done, the simulator is acting pretty much like you would expect a real device to. Really the only thing that does not work is the application(_:didReceiveRemoteNotification:fetchCompletionHandler:) method, which is never called.
I am sending the notifications from my local server, meaning I send the notifications to the api.sandbox.push.apple.com:443 URL from a PHP server running on my local machine.
I now tested it on a real device and it is working there. Which is very nice but from what I read, it is supposed to work on the simulator as well. So your reply is kind of the solution, but before I mark it as such, I would love to have someone from Apple check in on this topic to maybe clarify, why this is not working in the simulator and, if it is not a bug or mistake on my side, add a note to the documentation of the method that it is not or might not be called in the simulator to save other developers from the frustration of thinking their code is wrong.
As I am unable to edit my post: My first payload example is missing a "interruption-level": "active" in the "aps"` block.