Initial commit
This commit is contained in:
182
iap_notify_v2.go
Normal file
182
iap_notify_v2.go
Normal file
@ -0,0 +1,182 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user