-
Notifications
You must be signed in to change notification settings - Fork 11
/
v2.go
165 lines (140 loc) · 4 KB
/
v2.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
package v2
import (
"crypto/ecdsa"
"crypto/x509"
"encoding/base64"
"encoding/json"
"errors"
"strings"
"github.com/golang-jwt/jwt"
)
func New(payload string, appleRootCert string) *AppStoreServerNotification {
asn := &AppStoreServerNotification{}
asn.IsValid = false
asn.IsTest = false
asn.appleRootCert = appleRootCert
asn.parseJwtSignedPayload(payload)
return asn
}
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) {
// get root certificate from x5c header
rootCertStr, err := asn.extractHeaderByIndex(payload, 2)
if err != nil {
panic(err)
}
// get intermediate certificate from x5c header
intermediateCertStr, err := asn.extractHeaderByIndex(payload, 1)
if err != nil {
panic(err)
}
// verify certificates
if err = asn.verifyCertificate(rootCertStr, intermediateCertStr); err != nil {
panic(err)
}
// payload data
notificationPayload := &NotificationPayload{}
_, err = jwt.ParseWithClaims(payload, notificationPayload, func(token *jwt.Token) (interface{}, error) {
return asn.extractPublicKeyFromPayload(payload)
})
if err != nil {
panic(err)
}
asn.Payload = notificationPayload
asn.IsTest = asn.Payload.NotificationType == "TEST"
if asn.IsTest {
asn.IsValid = true
return
}
// 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 {
panic(err)
}
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 {
panic(err)
}
asn.RenewalInfo = renewalInfo
// valid request
asn.IsValid = true
}