183 lines
4.6 KiB
Go
183 lines
4.6 KiB
Go
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
|
||
}
|