Files
aigrammar/iap_notify_v2.go
2024-08-12 04:10:48 +00:00

183 lines
4.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}