Last active
May 14, 2023 18:46
-
-
Save thilonel/7b7285b9bad211cc88a1b8ccfdbe9e0e to your computer and use it in GitHub Desktop.
Apple App Store Server Notification JWT Verification
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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