package main /* * 来源于: https://github.com/izniburak/appstore-notifications-go * 源码中 parseJwtSignedPayload 对出错直接用了 panic,不能直接引用 * 修改代码以更健壮。 * * */ import ( "crypto/ecdsa" "crypto/x509" "encoding/base64" "encoding/json" "errors" "fmt" "strings" "github.com/golang-jwt/jwt" ) func IAP_Notify_New(payload string, appleRootCert string) (*AppStoreServerNotification, error) { asn := &AppStoreServerNotification{} asn.IsValid = false asn.IsTest = false asn.appleRootCert = appleRootCert if err := asn.parseJwtSignedPayload(payload); err != nil { return nil, err } return asn, nil } func (asn *AppStoreServerNotification) extractHeaderByIndex(payload string, index int) ([]byte, error) { // get header from token payloadArr := strings.Split(payload, ".") // convert header to byte headerByte, err := base64.RawStdEncoding.DecodeString(payloadArr[0]) if err != nil { return nil, err } // bind byte to header structure var header NotificationHeader err = json.Unmarshal(headerByte, &header) if err != nil { return nil, err } // decode x.509 certificate headers to byte certByte, err := base64.StdEncoding.DecodeString(header.X5c[index]) if err != nil { return nil, err } return certByte, nil } func (asn *AppStoreServerNotification) verifyCertificate(certByte []byte, intermediateCert []byte) error { // create certificate pool roots := x509.NewCertPool() // parse and append apple root certificate to the pool ok := roots.AppendCertsFromPEM([]byte(asn.appleRootCert)) if !ok { return errors.New("root certificate couldn't be parsed") } // parse and append intermediate x5c certificate interCert, err := x509.ParseCertificate(intermediateCert) if err != nil { return errors.New("intermediate certificate couldn't be parsed") } intermediate := x509.NewCertPool() intermediate.AddCert(interCert) // parse x5c certificate cert, err := x509.ParseCertificate(certByte) if err != nil { return err } // verify X5c certificate using app store certificate resides in opts opts := x509.VerifyOptions{ Roots: roots, Intermediates: intermediate, } if _, err := cert.Verify(opts); err != nil { return err } return nil } func (asn *AppStoreServerNotification) extractPublicKeyFromPayload(payload string) (*ecdsa.PublicKey, error) { // get certificate from X5c[0] header certStr, err := asn.extractHeaderByIndex(payload, 0) if err != nil { return nil, err } // parse certificate cert, err := x509.ParseCertificate(certStr) if err != nil { return nil, err } // get public key switch pk := cert.PublicKey.(type) { case *ecdsa.PublicKey: return pk, nil default: return nil, errors.New("appstore public key must be of type ecdsa.PublicKey") } } func (asn *AppStoreServerNotification) parseJwtSignedPayload(payload string) error { // get root certificate from x5c header rootCertStr, err := asn.extractHeaderByIndex(payload, 2) if err != nil { return errors.New("extractHeaderByIndex error, in rootCertStr") } // get intermediate certificate from x5c header intermediateCertStr, err := asn.extractHeaderByIndex(payload, 1) if err != nil { return errors.New("extractHeaderByIndex error, in intermediateCertStr") } // verify certificates if err = asn.verifyCertificate(rootCertStr, intermediateCertStr); err != nil { fmt.Printf("verifyCertificate eror: %v\n", err) return errors.New("verifyCertificate eror") } // payload data notificationPayload := &NotificationPayload{} _, err = jwt.ParseWithClaims(payload, notificationPayload, func(token *jwt.Token) (interface{}, error) { return asn.extractPublicKeyFromPayload(payload) }) if err != nil { return errors.New("ParseWithClaims NotificationPayload error") } asn.Payload = notificationPayload asn.IsTest = asn.Payload.NotificationType == "TEST" if asn.IsTest { asn.IsValid = true return nil } // transaction info transactionInfo := &TransactionInfo{} payload = asn.Payload.Data.SignedTransactionInfo _, err = jwt.ParseWithClaims(payload, transactionInfo, func(token *jwt.Token) (interface{}, error) { return asn.extractPublicKeyFromPayload(payload) }) if err != nil { return errors.New("ParseWithClaims SignedTransactionInfo error") } asn.TransactionInfo = transactionInfo // renewal info renewalInfo := &RenewalInfo{} payload = asn.Payload.Data.SignedRenewalInfo _, err = jwt.ParseWithClaims(payload, renewalInfo, func(token *jwt.Token) (interface{}, error) { return asn.extractPublicKeyFromPayload(payload) }) if err != nil { return errors.New("ParseWithClaims SignedRenewalInfo error") } asn.RenewalInfo = renewalInfo // valid request asn.IsValid = true return nil }