Skip to content

Instantly share code, notes, and snippets.

@thilonel
Last active May 14, 2023 18:46
Show Gist options
  • Save thilonel/7b7285b9bad211cc88a1b8ccfdbe9e0e to your computer and use it in GitHub Desktop.
Save thilonel/7b7285b9bad211cc88a1b8ccfdbe9e0e to your computer and use it in GitHub Desktop.
Apple App Store Server Notification JWT Verification
package app_store_server_notification_verifier
import (
"crypto/ecdsa"
"crypto/x509"
"encoding/base64"
"errors"
"io/ioutil"
"github.com/golang-jwt/jwt"
)
type AppStoreServerNotificationVerifier struct{}
// Certs can be obtained from https://www.apple.com/certificateauthority/
func (verifier *AppStoreServerNotificationVerifier) Verify(tokenString string) (*jwt.Token, error) {
appleRootCertFile, err := ioutil.ReadFile("AppleRootCA-G3.cer")
if err != nil {
return nil, errors.New("failed to read AppleRootCA-G3.cer")
}
appleRootCert, err := x509.ParseCertificate(appleRootCertFile)
if err != nil {
return nil, errors.New("failed to parse appleRootCertFile")
}
appleIntermediateCertFile, err := ioutil.ReadFile("AppleWWDRCAG6.cer")
if err != nil {
return nil, errors.New("failed to read AppleWWDRCAG6.cer")
}
appleIntermediateCert, err := x509.ParseCertificate(appleIntermediateCertFile)
if err != nil {
return nil, errors.New("failed to parse appleIntermediateCertFile")
}
parsedToken, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if token.Method != jwt.SigningMethodES256 {
return nil, errors.New("signing method was not ES256")
}
signingCertEncoded := token.Header["x5c"].([]interface{})[0].(string)
signingCert, err := verifier.extractCertFromX5cHeader(signingCertEncoded)
if err != nil {
return nil, errors.New("failed to get signing cert from x5c header")
}
intermediateCertEncoded := token.Header["x5c"].([]interface{})[1].(string)
intermediateCert, err := verifier.extractCertFromX5cHeader(intermediateCertEncoded)
if err != nil {
return nil, errors.New("failed to get signing cert from x5c header")
}
if !intermediateCert.Equal(appleIntermediateCert) {
return nil, errors.New("intermediate cert does not equal the expected one")
}
rootCertEncoded := token.Header["x5c"].([]interface{})[2].(string)
rootCert, err := verifier.extractCertFromX5cHeader(rootCertEncoded)
if err != nil {
return nil, errors.New("failed to get signing cert from x5c header")
}
if !rootCert.Equal(appleRootCert) {
return nil, errors.New("intermediate cert does not equal the expected one")
}
err = signingCert.CheckSignatureFrom(appleIntermediateCert)
if err != nil {
return nil, errors.New("intermediate cert can't verify the signing cert")
}
err = appleIntermediateCert.CheckSignatureFrom(appleRootCert)
if err != nil {
return nil, errors.New("root cert can't verify the intermediate cert")
}
rootCertPool := x509.NewCertPool()
rootCertPool.AddCert(appleRootCert)
intermediateCertPool := x509.NewCertPool()
intermediateCertPool.AddCert(appleIntermediateCert)
opts := x509.VerifyOptions{
Intermediates: intermediateCertPool,
Roots: rootCertPool,
}
if _, err := signingCert.Verify(opts); err != nil {
return nil, err
}
return signingCert.PublicKey.(*ecdsa.PublicKey), nil
})
if err != nil {
return nil, err
}
return parsedToken, nil
}
func (verifier *AppStoreServerNotificationVerifier) extractCertFromX5cHeader(header string) (*x509.Certificate, error) {
derBytesCert, err := base64.StdEncoding.DecodeString(header)
if err != nil {
return nil, err
}
cert, err := x509.ParseCertificate(derBytesCert)
if err != nil {
return nil, err
}
return cert, nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment