Skip to content

Instantly share code, notes, and snippets.

@lenew
Last active March 17, 2018 18:19
Show Gist options
  • Save lenew/d03cee2be37ed959463646a78f7bfd47 to your computer and use it in GitHub Desktop.
Save lenew/d03cee2be37ed959463646a78f7bfd47 to your computer and use it in GitHub Desktop.
Apple IAP Receipt Extract Tool
/*
* A Http Server to verify an Apple IAP Receipt
* Return with Receipts data
* Usage Example: curl -X POST http://127.0.0.1:5000/verifyReceipt -d '{"receipt-data":"apple receipt base64 data"}'
*/
package main
import (
"crypto/rsa"
"crypto/x509"
"encoding/asn1"
"encoding/json"
"encoding/pem"
"fmt"
"net/http"
"strings"
"github.com/fullsailor/pkcs7"
)
type PemResult struct {
Input []byte
Certificate *x509.Certificate
PrivateKey *rsa.PrivateKey
}
func UnmarshalPemStr(testPEMBlock string) PemResult {
var result PemResult
var derBlock *pem.Block
var pemBlock = []byte(testPEMBlock)
for {
derBlock, pemBlock = pem.Decode(pemBlock)
if derBlock == nil {
break
}
switch derBlock.Type {
case "PKCS7":
result.Input = derBlock.Bytes
case "CERTIFICATE":
result.Certificate, _ = x509.ParseCertificate(derBlock.Bytes)
case "PRIVATE KEY":
result.PrivateKey, _ = x509.ParsePKCS1PrivateKey(derBlock.Bytes)
}
}
return result
}
type IAPAttribute struct {
Type int
Version int
Value []byte
}
type PayloadObject struct {
ApplicationVersion string
ReceiptType string
BundleId string
OriginalApplicationVersion string
CreationDate string
ExpirationDate string
Hash []byte
Receipts []ReceiptObject
}
type ReceiptObject struct {
Quantity int
ProductId string
TransactionId string
PurchaseDate string
OriginalPurchaseDate string
ExpiresDate string
OriginalTransactionId string
ExpireDate string
WebOrderLineItemId int
CancellationDate string
IsInIntroOfferPeriod int
}
type VerifyRequest struct {
Payload string `json:"receipt-data"`
}
func loadAllReceipts(data []byte) ReceiptObject {
var receipts []IAPAttribute
asn1.UnmarshalWithParams(data, &receipts, "set")
var receiptObject ReceiptObject
for _, receipt := range receipts {
if receipt.Type == 1701 {
var val int
asn1.Unmarshal(receipt.Value, &val)
receiptObject.Quantity = val
} else if receipt.Type == 1702 {
var str string
asn1.Unmarshal(receipt.Value, &str)
receiptObject.ProductId = str
} else if receipt.Type == 1703 {
var str string
asn1.Unmarshal(receipt.Value, &str)
receiptObject.TransactionId = str
} else if receipt.Type == 1704 {
var str string
asn1.Unmarshal(receipt.Value, &str)
receiptObject.PurchaseDate = str
} else if receipt.Type == 1705 {
var str string
asn1.Unmarshal(receipt.Value, &str)
receiptObject.OriginalTransactionId = str
} else if receipt.Type == 1706 {
var str string
asn1.Unmarshal(receipt.Value, &str)
receiptObject.OriginalPurchaseDate = str
} else if receipt.Type == 1708 {
var str string
asn1.Unmarshal(receipt.Value, &str)
receiptObject.ExpiresDate = str
} else if receipt.Type == 1711 {
var val int
asn1.Unmarshal(receipt.Value, &val)
receiptObject.WebOrderLineItemId = val
} else if receipt.Type == 1719 {
var val int
asn1.Unmarshal(receipt.Value, &val)
receiptObject.IsInIntroOfferPeriod = val
} else if receipt.Type == 1712 {
var str string
asn1.Unmarshal(receipt.Value, &str)
receiptObject.CancellationDate = str
}
}
return receiptObject
}
func decodePayload(data []byte) PayloadObject {
var payloads2 []IAPAttribute
asn1.UnmarshalWithParams(data, &payloads2, "set")
var payloadObject PayloadObject
for _, el := range payloads2 {
if el.Type == 0 {
var str string
asn1.Unmarshal(el.Value, &str)
payloadObject.ReceiptType = str
} else if el.Type == 2 {
var str string
asn1.Unmarshal(el.Value, &str)
payloadObject.BundleId = str
} else if el.Type == 3 {
var str string
asn1.Unmarshal(el.Value, &str)
payloadObject.ApplicationVersion = str
} else if el.Type == 4 {
} else if el.Type == 5 {
payloadObject.Hash = el.Value
} else if el.Type == 17 {
receipt := loadAllReceipts(el.Value)
payloadObject.Receipts = append(payloadObject.Receipts, receipt)
} else if el.Type == 19 {
var str string
asn1.Unmarshal(el.Value, &str)
payloadObject.OriginalApplicationVersion = str
} else if el.Type == 12 {
var str string
asn1.Unmarshal(el.Value, &str)
payloadObject.CreationDate = str
} else if el.Type == 21 {
var str string
asn1.Unmarshal(el.Value, &str)
payloadObject.ExpirationDate = str
}
}
return payloadObject
}
func verifyCertChain(roots *x509.CertPool, p7 *pkcs7.PKCS7) bool {
opts := x509.VerifyOptions{
Roots: roots,
Intermediates: x509.NewCertPool(),
}
ok := true
for i := len(p7.Certificates) - 1; i >= 0; i-- {
cert := p7.Certificates[i]
if _, err := cert.Verify(opts); err != nil {
ok = false
fmt.Println(ok)
return ok
}
if i>0 {
opts.Intermediates.AddCert(cert)
}
}
return ok
}
func main() {
roots := x509.NewCertPool()
roots.AppendCertsFromPEM([]byte(AppleCA))
http.HandleFunc("/verifyReceipt", func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
w.WriteHeader(http.StatusNotFound)
return
}
var ur VerifyRequest
decoder := json.NewDecoder(r.Body)
decoder.Decode(&ur)
if strings.Count(ur.Payload, "") > 1 {
fixture := UnmarshalPemStr("-----BEGIN PKCS7-----\n" + ur.Payload + "\n-----END PKCS7-----")
p7, err := pkcs7.Parse(fixture.Input)
if err != nil {
fmt.Fprintf(w, `{"error":"Parse encountered unexpected error: %v"}`, err)
}
if err := p7.Verify(); err != nil {
fmt.Fprintf(w, `{"error":"Verify failed with error: %v"}`, err)
return
}
ok := verifyCertChain(roots, p7)
if !ok {
fmt.Fprintf(w, `{"error":"Verify certificates error"}`)
return
}
payloadObject := decodePayload(p7.Content)
b, _ := json.Marshal(payloadObject)
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, string(b))
}
})
http.ListenAndServe(":5000", nil)
fmt.Println("server started")
}
var AppleCA = `
-----BEGIN CERTIFICATE-----
MIIEuzCCA6OgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJVUzET
MBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlv
biBBdXRob3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwHhcNMDYwNDI1MjE0
MDM2WhcNMzUwMjA5MjE0MDM2WjBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBw
bGUgSW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkx
FjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
ggEKAoIBAQDkkakJH5HbHkdQ6wXtXnmELes2oldMVeyLGYne+Uts9QerIjAC6Bg+
+FAJ039BqJj50cpmnCRrEdCju+QbKsMflZ56DKRHi1vUFjczy8QPTc4UadHJGXL1
XQ7Vf1+b8iUDulWPTV0N8WQ1IxVLFVkds5T39pyez1C6wVhQZ48ItCD3y6wsIG9w
tj8BMIy3Q88PnT3zK0koGsj+zrW5DtleHNbLPbU6rfQPDgCSC7EhFi501TwN22IW
q6NxkkdTVcGvL0Gz+PvjcM3mo0xFfh9Ma1CWQYnEdGILEINBhzOKgbEwWOxaBDKM
aLOPHd5lc/9nXmW8Sdh2nzMUZaF3lMktAgMBAAGjggF6MIIBdjAOBgNVHQ8BAf8E
BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9BpR5R2Cf70a40uQKb3
R01/CF4wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4wggERBgNVHSAE
ggEIMIIBBDCCAQAGCSqGSIb3Y2QFATCB8jAqBggrBgEFBQcCARYeaHR0cHM6Ly93
d3cuYXBwbGUuY29tL2FwcGxlY2EvMIHDBggrBgEFBQcCAjCBthqBs1JlbGlhbmNl
IG9uIHRoaXMgY2VydGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0
YW5jZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1zIGFuZCBj
b25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9saWN5IGFuZCBjZXJ0aWZp
Y2F0aW9uIHByYWN0aWNlIHN0YXRlbWVudHMuMA0GCSqGSIb3DQEBBQUAA4IBAQBc
NplMLXi37Yyb3PN3m/J20ncwT8EfhYOFG5k9RzfyqZtAjizUsZAS2L70c5vu0mQP
y3lPNNiiPvl4/2vIB+x9OYOLUyDTOMSxv5pPCmv/K/xZpwUJfBdAVhEedNO3iyM7
R6PVbyTi69G3cN8PReEnyvFteO3ntRcXqNx+IjXKJdXZD9Zr1KIkIxH3oayPc4Fg
xhtbCS+SsvhESPBgOJ4V9T0mZyCKM2r3DYLP3uujL/lTaltkwGMzd/c6ByxW69oP
IQ7aunMZT7XZNn/Bh1XZp5m5MkL72NVxnn6hUrcbvZNCJBIqxw8dtk2cXmPIS4AX
UKqK1drk/NAJBzewdXUh
-----END CERTIFICATE-----
`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment