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.