Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(decode): Apple 解析数据后存储凭证原始数据,有助于查验结构体数据一致性 #432

Merged
merged 2 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 83 additions & 60 deletions apple/unsign_jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,98 +21,121 @@ func ExtractClaims(signedPayload string, tran jwt.Claims) (err error) {
if valueOf.Kind() != reflect.Ptr {
return errors.New("tran must be ptr struct")
}

_, err = jwt.ParseWithClaims(signedPayload, tran, func(token *jwt.Token) (any, error) {
return x5cCertVerify(signedPayload)
tokenStr := signedPayload
rootCertStr, err := extractHeaderByIndex(tokenStr, 2)
if err != nil {
return err
}
intermediaCertStr, err := extractHeaderByIndex(tokenStr, 1)
if err != nil {
return err
}
if err = verifyCert(rootCertStr, intermediaCertStr); err != nil {
return err
}
_, err = jwt.ParseWithClaims(tokenStr, tran, func(token *jwt.Token) (any, error) {
return extractPublicKeyFromToken(tokenStr)
})
if err != nil {
return err
}
return nil
}

type header struct {
Alg string `json:"alg"`
X5c []string `json:"x5c"`
}

// 解析 x5c root intermedia user证书
// Per doc: https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.6
func (h *header) certParse() (*x509.Certificate, *x509.Certificate, *x509.Certificate, error) {
if len(h.X5c) != 3 {
return nil, nil, nil, errors.New("invalid x5c format")
}
var certDecode = func(certStr string) (*x509.Certificate, error) {
certBytes, err := base64.StdEncoding.DecodeString(certStr)
if err != nil {
return nil, fmt.Errorf("failed to parse certificate: %w", err)
}
cert, err := x509.ParseCertificate(certBytes)
if err != nil {
return nil, fmt.Errorf("failed to parse certificate: %w", err)
}
return cert, nil
}
root, err := certDecode(h.X5c[2])
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to decode root certificate: %w", err)
// ExtractClaimsToken 解析jws格式数据
//
// Args:
// - signedPayload:string, jws格式数据
// - tran:jwt.Claims, 指针类型的结构体,用于接收解析后的数据
func ExtractClaimsToken(signedPayload string, tran jwt.Claims) (tk *jwt.Token, err error) {
valueOf := reflect.ValueOf(tran)
if valueOf.Kind() != reflect.Ptr {
return nil, errors.New("tran must be ptr struct")
}
interCert, err := certDecode(h.X5c[1])
tokenStr := signedPayload
rootCertStr, err := extractHeaderByIndex(tokenStr, 2)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to decode intermedia certificate: %w", err)
return nil, err
}
userCert, err := certDecode(h.X5c[0])
intermediaCertStr, err := extractHeaderByIndex(tokenStr, 1)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to decode user certificate: %w", err)
return nil, err
}
return root, interCert, userCert, nil
if err = verifyCert(rootCertStr, intermediaCertStr); err != nil {
return nil, err
}
tk, err = jwt.ParseWithClaims(tokenStr, tran, func(token *jwt.Token) (any, error) {
return extractPublicKeyFromToken(tokenStr)
})
return tk, err
}

func (h *header) x5cCertVerify() (*ecdsa.PublicKey, error) {
_, i, u, err := h.certParse()
// Per doc: https://datatracker.ietf.org/doc/html/rfc7515#section-4.1.6
func extractPublicKeyFromToken(tokenStr string) (*ecdsa.PublicKey, error) {
certStr, err := extractHeaderByIndex(tokenStr, 0)
if err != nil {
return nil, err
}
var iPool = x509.NewCertPool()
iPool.AddCert(i)
opts := x509.VerifyOptions{
Roots: rootCertPool,
Intermediates: iPool,
}
_, err = u.Verify(opts)
cert, err := x509.ParseCertificate(certStr)
if err != nil {
return nil, err
}
switch pk := u.PublicKey.(type) {
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")
}
}

// 苹果根ca证书
var rootCertPool *x509.CertPool

func init() {
rootCertPool = x509.NewCertPool()
rootCertPool.AppendCertsFromPEM([]byte(rootPEM))
}

// 验证x5c证书链是否合法
func x5cCertVerify(tokenStr string) (*ecdsa.PublicKey, error) {
tokenArr := strings.Split(tokenStr, ".")
if len(tokenArr) != 3 {
return nil, errors.New("invalid jwt format")
func extractHeaderByIndex(tokenStr string, index int) ([]byte, error) {
if index > 2 {
return nil, errors.New("invalid index")
}
tokenArr := strings.Split(tokenStr, ".")
headerByte, err := base64.RawStdEncoding.DecodeString(tokenArr[0])
if err != nil {
return nil, err
}
h := &header{}
err = json.Unmarshal(headerByte, h)
type Header struct {
Alg string `json:"alg"`
X5c []string `json:"x5c"`
}
header := &Header{}
err = json.Unmarshal(headerByte, header)
if err != nil {
return nil, err
}
if len(header.X5c) < index {
return nil, fmt.Errorf("index[%d] > header.x5c slice len(%d)", index, len(header.X5c))
}
certByte, err := base64.StdEncoding.DecodeString(header.X5c[index])
if err != nil {
return nil, err
}
return certByte, nil
}

func verifyCert(certByte, intermediaCertStr []byte) error {
roots := x509.NewCertPool()
ok := roots.AppendCertsFromPEM([]byte(rootPEM))
if !ok {
return errors.New("failed to parse root certificate")
}
interCert, err := x509.ParseCertificate(intermediaCertStr)
if err != nil {
return nil, fmt.Errorf("invalid jwt.header: %w", err)
return errors.New("failed to parse intermedia certificate")
}
intermedia := x509.NewCertPool()
intermedia.AddCert(interCert)
cert, err := x509.ParseCertificate(certByte)
if err != nil {
return err
}
opts := x509.VerifyOptions{
Roots: roots,
Intermediates: intermedia,
}
return h.x5cCertVerify()
_, err = cert.Verify(opts)
return err
}
67 changes: 67 additions & 0 deletions apple/unsign_jwt_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,70 @@ func TestExtractClaims(t *testing.T) {
}
*/
}

func TestExtractClaimsToken(t *testing.T) {
signedPayload := "eyJhbGciOiJFUzI1NiIsIng1YyI6WyJNSUlFTURDQ0E3YWdBd0lCQWdJUWFQb1BsZHZwU29FSDBsQnJqRFB2OWpBS0JnZ3Foa2pPUFFRREF6QjFNVVF3UWdZRFZRUURERHRCY0hCc1pTQlhiM0pzWkhkcFpHVWdSR1YyWld4dmNHVnlJRkpsYkdGMGFXOXVjeUJEWlhKMGFXWnBZMkYwYVc5dUlFRjFkR2h2Y21sMGVURUxNQWtHQTFVRUN3d0NSell4RXpBUkJnTlZCQW9NQ2tGd2NHeGxJRWx1WXk0eEN6QUpCZ05WQkFZVEFsVlRNQjRYRFRJeE1EZ3lOVEF5TlRBek5Gb1hEVEl6TURreU5EQXlOVEF6TTFvd2daSXhRREErQmdOVkJBTU1OMUJ5YjJRZ1JVTkRJRTFoWXlCQmNIQWdVM1J2Y21VZ1lXNWtJR2xVZFc1bGN5QlRkRzl5WlNCU1pXTmxhWEIwSUZOcFoyNXBibWN4TERBcUJnTlZCQXNNSTBGd2NHeGxJRmR2Y214a2QybGtaU0JFWlhabGJHOXdaWElnVW1Wc1lYUnBiMjV6TVJNd0VRWURWUVFLREFwQmNIQnNaU0JKYm1NdU1Rc3dDUVlEVlFRR0V3SlZVekJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCT29UY2FQY3BlaXBOTDllUTA2dEN1N3BVY3dkQ1hkTjh2R3FhVWpkNThaOHRMeGlVQzBkQmVBK2V1TVlnZ2gxLzVpQWsrRk14VUZtQTJhMXI0YUNaOFNqZ2dJSU1JSUNCREFNQmdOVkhSTUJBZjhFQWpBQU1COEdBMVVkSXdRWU1CYUFGRDh2bENOUjAxREptaWc5N2JCODVjK2xrR0taTUhBR0NDc0dBUVVGQndFQkJHUXdZakF0QmdnckJnRUZCUWN3QW9ZaGFIUjBjRG92TDJObGNuUnpMbUZ3Y0d4bExtTnZiUzkzZDJSeVp6WXVaR1Z5TURFR0NDc0dBUVVGQnpBQmhpVm9kSFJ3T2k4dmIyTnpjQzVoY0hCc1pTNWpiMjB2YjJOemNEQXpMWGQzWkhKbk5qQXlNSUlCSGdZRFZSMGdCSUlCRlRDQ0FSRXdnZ0VOQmdvcWhraUc5Mk5rQlFZQk1JSCtNSUhEQmdnckJnRUZCUWNDQWpDQnRneUJzMUpsYkdsaGJtTmxJRzl1SUhSb2FYTWdZMlZ5ZEdsbWFXTmhkR1VnWW5rZ1lXNTVJSEJoY25SNUlHRnpjM1Z0WlhNZ1lXTmpaWEIwWVc1alpTQnZaaUIwYUdVZ2RHaGxiaUJoY0hCc2FXTmhZbXhsSUhOMFlXNWtZWEprSUhSbGNtMXpJR0Z1WkNCamIyNWthWFJwYjI1eklHOW1JSFZ6WlN3Z1kyVnlkR2xtYVdOaGRHVWdjRzlzYVdONUlHRnVaQ0JqWlhKMGFXWnBZMkYwYVc5dUlIQnlZV04wYVdObElITjBZWFJsYldWdWRITXVNRFlHQ0NzR0FRVUZCd0lCRmlwb2RIUndPaTh2ZDNkM0xtRndjR3hsTG1OdmJTOWpaWEowYVdacFkyRjBaV0YxZEdodmNtbDBlUzh3SFFZRFZSME9CQllFRkNPQ21NQnEvLzFMNWltdlZtcVgxb0NZZXFyTU1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBUUJnb3Foa2lHOTJOa0Jnc0JCQUlGQURBS0JnZ3Foa2pPUFFRREF3Tm9BREJsQWpFQWw0SkI5R0pIaXhQMm51aWJ5VTFrM3dyaTVwc0dJeFBNRTA1c0ZLcTdoUXV6dmJleUJ1ODJGb3p6eG1ienBvZ29BakJMU0ZsMGRaV0lZbDJlalBWK0RpNWZCbktQdThteW1CUXRvRS9IMmJFUzBxQXM4Yk51ZVUzQ0JqamgxbHduRHNJPSIsIk1JSURGakNDQXB5Z0F3SUJBZ0lVSXNHaFJ3cDBjMm52VTRZU3ljYWZQVGp6Yk5jd0NnWUlLb1pJemowRUF3TXdaekViTUJrR0ExVUVBd3dTUVhCd2JHVWdVbTl2ZENCRFFTQXRJRWN6TVNZd0pBWURWUVFMREIxQmNIQnNaU0JEWlhKMGFXWnBZMkYwYVc5dUlFRjFkR2h2Y21sMGVURVRNQkVHQTFVRUNnd0tRWEJ3YkdVZ1NXNWpMakVMTUFrR0ExVUVCaE1DVlZNd0hoY05NakV3TXpFM01qQXpOekV3V2hjTk16WXdNekU1TURBd01EQXdXakIxTVVRd1FnWURWUVFERER0QmNIQnNaU0JYYjNKc1pIZHBaR1VnUkdWMlpXeHZjR1Z5SUZKbGJHRjBhVzl1Y3lCRFpYSjBhV1pwWTJGMGFXOXVJRUYxZEdodmNtbDBlVEVMTUFrR0ExVUVDd3dDUnpZeEV6QVJCZ05WQkFvTUNrRndjR3hsSUVsdVl5NHhDekFKQmdOVkJBWVRBbFZUTUhZd0VBWUhLb1pJemowQ0FRWUZLNEVFQUNJRFlnQUVic1FLQzk0UHJsV21aWG5YZ3R4emRWSkw4VDBTR1luZ0RSR3BuZ24zTjZQVDhKTUViN0ZEaTRiQm1QaENuWjMvc3E2UEYvY0djS1hXc0w1dk90ZVJoeUo0NXgzQVNQN2NPQithYW85MGZjcHhTdi9FWkZibmlBYk5nWkdoSWhwSW80SDZNSUgzTUJJR0ExVWRFd0VCL3dRSU1BWUJBZjhDQVFBd0h3WURWUjBqQkJnd0ZvQVV1N0Rlb1ZnemlKcWtpcG5ldnIzcnI5ckxKS3N3UmdZSUt3WUJCUVVIQVFFRU9qQTRNRFlHQ0NzR0FRVUZCekFCaGlwb2RIUndPaTh2YjJOemNDNWhjSEJzWlM1amIyMHZiMk56Y0RBekxXRndjR3hsY205dmRHTmhaek13TndZRFZSMGZCREF3TGpBc29DcWdLSVltYUhSMGNEb3ZMMk55YkM1aGNIQnNaUzVqYjIwdllYQndiR1Z5YjI5MFkyRm5NeTVqY213d0hRWURWUjBPQkJZRUZEOHZsQ05SMDFESm1pZzk3YkI4NWMrbGtHS1pNQTRHQTFVZER3RUIvd1FFQXdJQkJqQVFCZ29xaGtpRzkyTmtCZ0lCQkFJRkFEQUtCZ2dxaGtqT1BRUURBd05vQURCbEFqQkFYaFNxNUl5S29nTUNQdHc0OTBCYUI2NzdDYUVHSlh1ZlFCL0VxWkdkNkNTamlDdE9udU1UYlhWWG14eGN4ZmtDTVFEVFNQeGFyWlh2TnJreFUzVGtVTUkzM3l6dkZWVlJUNHd4V0pDOTk0T3NkY1o0K1JHTnNZRHlSNWdtZHIwbkRHZz0iLCJNSUlDUXpDQ0FjbWdBd0lCQWdJSUxjWDhpTkxGUzVVd0NnWUlLb1pJemowRUF3TXdaekViTUJrR0ExVUVBd3dTUVhCd2JHVWdVbTl2ZENCRFFTQXRJRWN6TVNZd0pBWURWUVFMREIxQmNIQnNaU0JEWlhKMGFXWnBZMkYwYVc5dUlFRjFkR2h2Y21sMGVURVRNQkVHQTFVRUNnd0tRWEJ3YkdVZ1NXNWpMakVMTUFrR0ExVUVCaE1DVlZNd0hoY05NVFF3TkRNd01UZ3hPVEEyV2hjTk16a3dORE13TVRneE9UQTJXakJuTVJzd0dRWURWUVFEREJKQmNIQnNaU0JTYjI5MElFTkJJQzBnUnpNeEpqQWtCZ05WQkFzTUhVRndjR3hsSUVObGNuUnBabWxqWVhScGIyNGdRWFYwYUc5eWFYUjVNUk13RVFZRFZRUUtEQXBCY0hCc1pTQkpibU11TVFzd0NRWURWUVFHRXdKVlV6QjJNQkFHQnlxR1NNNDlBZ0VHQlN1QkJBQWlBMklBQkpqcEx6MUFjcVR0a3lKeWdSTWMzUkNWOGNXalRuSGNGQmJaRHVXbUJTcDNaSHRmVGpqVHV4eEV0WC8xSDdZeVlsM0o2WVJiVHpCUEVWb0EvVmhZREtYMUR5eE5CMGNUZGRxWGw1ZHZNVnp0SzUxN0lEdll1VlRaWHBta09sRUtNYU5DTUVBd0hRWURWUjBPQkJZRUZMdXczcUZZTTRpYXBJcVozcjY5NjYvYXl5U3JNQThHQTFVZEV3RUIvd1FGTUFNQkFmOHdEZ1lEVlIwUEFRSC9CQVFEQWdFR01Bb0dDQ3FHU000OUJBTURBMmdBTUdVQ01RQ0Q2Y0hFRmw0YVhUUVkyZTN2OUd3T0FFWkx1Tit5UmhIRkQvM21lb3locG12T3dnUFVuUFdUeG5TNGF0K3FJeFVDTUcxbWloREsxQTNVVDgyTlF6NjBpbU9sTTI3amJkb1h0MlFmeUZNbStZaGlkRGtMRjF2TFVhZ002QmdENTZLeUtBPT0iXX0.eyJub3RpZmljYXRpb25UeXBlIjoiQ09OU1VNUFRJT05fUkVRVUVTVCIsIm5vdGlmaWNhdGlvblVVSUQiOiJjYmZmNjk4Ny1iOGQ5LTQzZDgtYjFkYy0wN2ZhOGM5ZmQ5NDUiLCJkYXRhIjp7ImFwcEFwcGxlSWQiOjE2MDE4MzA4MTQsImJ1bmRsZUlkIjoiY29tLmpyamoua2V5c25zIiwiYnVuZGxlVmVyc2lvbiI6IjEiLCJlbnZpcm9ubWVudCI6IlByb2R1Y3Rpb24iLCJzaWduZWRUcmFuc2FjdGlvbkluZm8iOiJleUpoYkdjaU9pSkZVekkxTmlJc0luZzFZeUk2V3lKTlNVbEZUVVJEUTBFM1lXZEJkMGxDUVdkSlVXRlFiMUJzWkhad1UyOUZTREJzUW5KcVJGQjJPV3BCUzBKblozRm9hMnBQVUZGUlJFRjZRakZOVlZGM1VXZFpSRlpSVVVSRVJIUkNZMGhDYzFwVFFsaGlNMHB6V2toa2NGcEhWV2RTUjFZeVdsZDRkbU5IVm5sSlJrcHNZa2RHTUdGWE9YVmplVUpFV2xoS01HRlhXbkJaTWtZd1lWYzVkVWxGUmpGa1IyaDJZMjFzTUdWVVJVeE5RV3RIUVRGVlJVTjNkME5TZWxsNFJYcEJVa0puVGxaQ1FXOU5RMnRHZDJOSGVHeEpSV3gxV1hrMGVFTjZRVXBDWjA1V1FrRlpWRUZzVmxSTlFqUllSRlJKZUUxRVozbE9WRUY1VGxSQmVrNUdiMWhFVkVsNlRVUnJlVTVFUVhsT1ZFRjZUVEZ2ZDJkYVNYaFJSRUVyUW1kT1ZrSkJUVTFPTVVKNVlqSlJaMUpWVGtSSlJURm9XWGxDUW1OSVFXZFZNMUoyWTIxVloxbFhOV3RKUjJ4VlpGYzFiR041UWxSa1J6bDVXbE5DVTFwWFRteGhXRUl3U1VaT2NGb3lOWEJpYldONFRFUkJjVUpuVGxaQ1FYTk5TVEJHZDJOSGVHeEpSbVIyWTIxNGEyUXliR3RhVTBKRldsaGFiR0pIT1hkYVdFbG5WVzFXYzFsWVVuQmlNalY2VFZKTmQwVlJXVVJXVVZGTFJFRndRbU5JUW5OYVUwSktZbTFOZFUxUmMzZERVVmxFVmxGUlIwVjNTbFpWZWtKYVRVSk5SMEo1Y1VkVFRUUTVRV2RGUjBORGNVZFRUVFE1UVhkRlNFRXdTVUZDVDI5VVkyRlFZM0JsYVhCT1REbGxVVEEyZEVOMU4zQlZZM2RrUTFoa1RqaDJSM0ZoVldwa05UaGFPSFJNZUdsVlF6QmtRbVZCSzJWMVRWbG5aMmd4THpWcFFXc3JSazE0VlVadFFUSmhNWEkwWVVOYU9GTnFaMmRKU1UxSlNVTkNSRUZOUW1kT1ZraFNUVUpCWmpoRlFXcEJRVTFDT0VkQk1WVmtTWGRSV1UxQ1lVRkdSRGgyYkVOT1VqQXhSRXB0YVdjNU4ySkNPRFZqSzJ4clIwdGFUVWhCUjBORGMwZEJVVlZHUW5kRlFrSkhVWGRaYWtGMFFtZG5ja0puUlVaQ1VXTjNRVzlaYUdGSVVqQmpSRzkyVERKT2JHTnVVbnBNYlVaM1kwZDRiRXh0VG5aaVV6a3paREpTZVZwNldYVmFSMVo1VFVSRlIwTkRjMGRCVVZWR1FucEJRbWhwVm05a1NGSjNUMms0ZG1JeVRucGpRelZvWTBoQ2MxcFROV3BpTWpCMllqSk9lbU5FUVhwTVdHUXpXa2hLYms1cVFYbE5TVWxDU0dkWlJGWlNNR2RDU1VsQ1JsUkRRMEZTUlhkblowVk9RbWR2Y1docmFVYzVNazVyUWxGWlFrMUpTQ3ROU1VoRVFtZG5ja0puUlVaQ1VXTkRRV3BEUW5SbmVVSnpNVXBzWWtkc2FHSnRUbXhKUnpsMVNVaFNiMkZZVFdkWk1sWjVaRWRzYldGWFRtaGtSMVZuV1c1cloxbFhOVFZKU0VKb1kyNVNOVWxIUm5wak0xWjBXbGhOWjFsWFRtcGFXRUl3V1ZjMWFscFRRblphYVVJd1lVZFZaMlJIYUd4aWFVSm9ZMGhDYzJGWFRtaFpiWGhzU1VoT01GbFhOV3RaV0VwclNVaFNiR050TVhwSlIwWjFXa05DYW1JeU5XdGhXRkp3WWpJMWVrbEhPVzFKU0ZaNldsTjNaMWt5Vm5sa1IyeHRZVmRPYUdSSFZXZGpSemx6WVZkT05VbEhSblZhUTBKcVdsaEtNR0ZYV25CWk1rWXdZVmM1ZFVsSVFubFpWMDR3WVZkT2JFbElUakJaV0ZKc1lsZFdkV1JJVFhWTlJGbEhRME56UjBGUlZVWkNkMGxDUm1sd2IyUklVbmRQYVRoMlpETmtNMHh0Um5kalIzaHNURzFPZG1KVE9XcGFXRW93WVZkYWNGa3lSakJhVjBZeFpFZG9kbU50YkRCbFV6aDNTRkZaUkZaU01FOUNRbGxGUmtOUFEyMU5RbkV2THpGTU5XbHRkbFp0Y1ZneGIwTlpaWEZ5VFUxQk5FZEJNVlZrUkhkRlFpOTNVVVZCZDBsSVowUkJVVUpuYjNGb2EybEhPVEpPYTBKbmMwSkNRVWxHUVVSQlMwSm5aM0ZvYTJwUFVGRlJSRUYzVG05QlJFSnNRV3BGUVd3MFNrSTVSMHBJYVhoUU1tNTFhV0o1VlRGck0zZHlhVFZ3YzBkSmVGQk5SVEExYzBaTGNUZG9VWFY2ZG1KbGVVSjFPREpHYjNwNmVHMWllbkJ2WjI5QmFrSk1VMFpzTUdSYVYwbFpiREpsYWxCV0swUnBOV1pDYmt0UWRUaHRlVzFDVVhSdlJTOUlNbUpGVXpCeFFYTTRZazUxWlZVelEwSnFhbWd4YkhkdVJITkpQU0lzSWsxSlNVUkdha05EUVhCNVowRjNTVUpCWjBsVlNYTkhhRkozY0RCak1tNTJWVFJaVTNsallXWlFWR3A2WWs1amQwTm5XVWxMYjFwSmVtb3dSVUYzVFhkYWVrVmlUVUpyUjBFeFZVVkJkM2RUVVZoQ2QySkhWV2RWYlRsMlpFTkNSRkZUUVhSSlJXTjZUVk5aZDBwQldVUldVVkZNUkVJeFFtTklRbk5hVTBKRVdsaEtNR0ZYV25CWk1rWXdZVmM1ZFVsRlJqRmtSMmgyWTIxc01HVlVSVlJOUWtWSFFURlZSVU5uZDB0UldFSjNZa2RWWjFOWE5XcE1ha1ZNVFVGclIwRXhWVVZDYUUxRFZsWk5kMGhvWTA1TmFrVjNUWHBGTTAxcVFYcE9la1YzVjJoalRrMTZXWGROZWtVMVRVUkJkMDFFUVhkWGFrSXhUVlZSZDFGbldVUldVVkZFUkVSMFFtTklRbk5hVTBKWVlqTktjMXBJWkhCYVIxVm5Va2RXTWxwWGVIWmpSMVo1U1VaS2JHSkhSakJoVnpsMVkzbENSRnBZU2pCaFYxcHdXVEpHTUdGWE9YVkpSVVl4WkVkb2RtTnRiREJsVkVWTVRVRnJSMEV4VlVWRGQzZERVbnBaZUVWNlFWSkNaMDVXUWtGdlRVTnJSbmRqUjNoc1NVVnNkVmw1TkhoRGVrRktRbWRPVmtKQldWUkJiRlpVVFVoWmQwVkJXVWhMYjFwSmVtb3dRMEZSV1VaTE5FVkZRVU5KUkZsblFVVmljMUZMUXprMFVISnNWMjFhV0c1WVozUjRlbVJXU2t3NFZEQlRSMWx1WjBSU1IzQnVaMjR6VGpaUVZEaEtUVVZpTjBaRWFUUmlRbTFRYUVOdVdqTXZjM0UyVUVZdlkwZGpTMWhYYzB3MWRrOTBaVkpvZVVvME5YZ3pRVk5RTjJOUFFpdGhZVzg1TUdaamNIaFRkaTlGV2taaWJtbEJZazVuV2tkb1NXaHdTVzgwU0RaTlNVZ3pUVUpKUjBFeFZXUkZkMFZDTDNkUlNVMUJXVUpCWmpoRFFWRkJkMGgzV1VSV1VqQnFRa0puZDBadlFWVjFOMFJsYjFabmVtbEtjV3RwY0c1bGRuSXpjbkk1Y2t4S1MzTjNVbWRaU1V0M1dVSkNVVlZJUVZGRlJVOXFRVFJOUkZsSFEwTnpSMEZSVlVaQ2VrRkNhR2x3YjJSSVVuZFBhVGgyWWpKT2VtTkROV2hqU0VKeldsTTFhbUl5TUhaaU1rNTZZMFJCZWt4WFJuZGpSM2hzWTIwNWRtUkhUbWhhZWsxM1RuZFpSRlpTTUdaQ1JFRjNUR3BCYzI5RGNXZExTVmx0WVVoU01HTkViM1pNTWs1NVlrTTFhR05JUW5OYVV6VnFZakl3ZGxsWVFuZGlSMVo1WWpJNU1Ga3lSbTVOZVRWcVkyMTNkMGhSV1VSV1VqQlBRa0paUlVaRU9IWnNRMDVTTURGRVNtMXBaemszWWtJNE5XTXJiR3RIUzFwTlFUUkhRVEZWWkVSM1JVSXZkMUZGUVhkSlFrSnFRVkZDWjI5eGFHdHBSemt5VG10Q1owbENRa0ZKUmtGRVFVdENaMmR4YUd0cVQxQlJVVVJCZDA1dlFVUkNiRUZxUWtGWWFGTnhOVWw1UzI5blRVTlFkSGMwT1RCQ1lVSTJOemREWVVWSFNsaDFabEZDTDBWeFdrZGtOa05UYW1sRGRFOXVkVTFVWWxoV1dHMTRlR040Wm10RFRWRkVWRk5RZUdGeVdsaDJUbkpyZUZVelZHdFZUVWt6TTNsNmRrWldWbEpVTkhkNFYwcERPVGswVDNOa1kxbzBLMUpIVG5OWlJIbFNOV2R0WkhJd2JrUkhaejBpTENKTlNVbERVWHBEUTBGamJXZEJkMGxDUVdkSlNVeGpXRGhwVGt4R1V6VlZkME5uV1VsTGIxcEplbW93UlVGM1RYZGFla1ZpVFVKclIwRXhWVVZCZDNkVFVWaENkMkpIVldkVmJUbDJaRU5DUkZGVFFYUkpSV042VFZOWmQwcEJXVVJXVVZGTVJFSXhRbU5JUW5OYVUwSkVXbGhLTUdGWFduQlpNa1l3WVZjNWRVbEZSakZrUjJoMlkyMXNNR1ZVUlZSTlFrVkhRVEZWUlVObmQwdFJXRUozWWtkVloxTlhOV3BNYWtWTVRVRnJSMEV4VlVWQ2FFMURWbFpOZDBob1kwNU5WRkYzVGtSTmQwMVVaM2hQVkVFeVYyaGpUazE2YTNkT1JFMTNUVlJuZUU5VVFUSlhha0p1VFZKemQwZFJXVVJXVVZGRVJFSktRbU5JUW5OYVUwSlRZakk1TUVsRlRrSkpRekJuVW5wTmVFcHFRV3RDWjA1V1FrRnpUVWhWUm5kalIzaHNTVVZPYkdOdVVuQmFiV3hxV1ZoU2NHSXlOR2RSV0ZZd1lVYzVlV0ZZVWpWTlVrMTNSVkZaUkZaUlVVdEVRWEJDWTBoQ2MxcFRRa3BpYlUxMVRWRnpkME5SV1VSV1VWRkhSWGRLVmxWNlFqSk5Ra0ZIUW5seFIxTk5ORGxCWjBWSFFsTjFRa0pCUVdsQk1rbEJRa3BxY0V4Nk1VRmpjVlIwYTNsS2VXZFNUV016VWtOV09HTlhhbFJ1U0dOR1FtSmFSSFZYYlVKVGNETmFTSFJtVkdwcVZIVjRlRVYwV0M4eFNEZFplVmxzTTBvMldWSmlWSHBDVUVWV2IwRXZWbWhaUkV0WU1VUjVlRTVDTUdOVVpHUnhXR3cxWkhaTlZucDBTelV4TjBsRWRsbDFWbFJhV0hCdGEwOXNSVXROWVU1RFRVVkJkMGhSV1VSV1VqQlBRa0paUlVaTWRYY3pjVVpaVFRScFlYQkpjVm96Y2pZNU5qWXZZWGw1VTNKTlFUaEhRVEZWWkVWM1JVSXZkMUZHVFVGTlFrRm1PSGRFWjFsRVZsSXdVRUZSU0M5Q1FWRkVRV2RGUjAxQmIwZERRM0ZIVTAwME9VSkJUVVJCTW1kQlRVZFZRMDFSUTBRMlkwaEZSbXcwWVZoVVVWa3laVE4yT1VkM1QwRkZXa3gxVGl0NVVtaElSa1F2TTIxbGIzbG9jRzEyVDNkblVGVnVVRmRVZUc1VE5HRjBLM0ZKZUZWRFRVY3hiV2xvUkVzeFFUTlZWRGd5VGxGNk5qQnBiVTlzVFRJM2FtSmtiMWgwTWxGbWVVWk5iU3RaYUdsa1JHdE1SakYyVEZWaFowMDJRbWRFTlRaTGVVdEJQVDBpWFgwLmV5SjBjbUZ1YzJGamRHbHZia2xrSWpvaU5URXdNREF4TWpZeE1EY3lPVEl4SWl3aWIzSnBaMmx1WVd4VWNtRnVjMkZqZEdsdmJrbGtJam9pTlRFd01EQXhNall4TURjeU9USXhJaXdpWW5WdVpHeGxTV1FpT2lKamIyMHVhbkpxYWk1clpYbHpibk1pTENKd2NtOWtkV04wU1dRaU9pSmpiMjB1YTJWNWMyNXpMa3BTTVRJd01DSXNJbkIxY21Ob1lYTmxSR0YwWlNJNk1UWTROekl6T1RBek5qQXdNQ3dpYjNKcFoybHVZV3hRZFhKamFHRnpaVVJoZEdVaU9qRTJPRGN5TXprd016WXdNREFzSW5GMVlXNTBhWFI1SWpveExDSjBlWEJsSWpvaVEyOXVjM1Z0WVdKc1pTSXNJbWx1UVhCd1QzZHVaWEp6YUdsd1ZIbHdaU0k2SWxCVlVrTklRVk5GUkNJc0luTnBaMjVsWkVSaGRHVWlPakUyT0RjNU5ERTFNVEkxTnpNc0ltVnVkbWx5YjI1dFpXNTBJam9pVUhKdlpIVmpkR2x2YmlJc0luUnlZVzV6WVdOMGFXOXVVbVZoYzI5dUlqb2lVRlZTUTBoQlUwVWlMQ0p6ZEc5eVpXWnliMjUwSWpvaVEwaE9JaXdpYzNSdmNtVm1jbTl1ZEVsa0lqb2lNVFF6TkRZMUluMC5hM09PRTNGcGxPdF9UQV9nQURzNW1rM2JRNWdKUDJubFdWX2hfN2xKRUMtcjNCZDh0d2ItSzFuQm5KR3hGVW9OeVRibWp3YXZOcDhPUHM5bGw4cGhfZyJ9LCJ2ZXJzaW9uIjoiMi4wIiwic2lnbmVkRGF0ZSI6MTY4Nzk0MTUxMjU2MH0.Tz4bvMOb-AT88is_pobjyOBZxwIKDkyugOQ4BqfTbhrlRNgKaZ-upGRTJlydJrUM9aRfdezRc0-1ekmMwcndcA"
np := &NotificationV2Payload{}
tk, err := ExtractClaimsToken(signedPayload, np)
if err != nil {
xlog.Error(err)
return
}
xlog.Infof("np:%+v", np)
xlog.Infof("np.data:%+v", np.Data)

// Print token segments, verify the integrity of original data.
for i := 0; i < len(tk.TokenSegmentRows); i++ {
xlog.Infof("token_raw[%d]:%s\n", i, tk.TokenSegmentRows[i])
}

ti, err := np.DecodeTransactionInfo()
if err != nil {
xlog.Error(err)
return
}
xlog.Infof("ti:%+v", ti.TransactionId)
xlog.Infof("ti_info:%+v\n", ti)

ot, err := ExtractClaimsToken(np.Data.SignedTransactionInfo, &TransactionInfo{})
if err != nil {
xlog.Error(err)
return
}

// 解析数据[ti_info] 与 原始数据[ot_info] 对比, 确保数据结构体可以完全解析, 版本更新后确认数据一致性场景下会有帮助作用.
xlog.Infof("ot_info:%s\n", ot.TokenSegmentRows[1])

/*
{
"transactionId": "510001261072921",
"originalTransactionId": "510001261072921",
"bundleId": "com.jrjj.keysns",
"productId": "com.keysns.JR1200",
"purchaseDate": 1687239036000,
"originalPurchaseDate": 1687239036000,
"quantity": 1,
"type": "Consumable",
"inAppOwnershipType": "PURCHASED",
"signedDate": 1687941512573,
"environment": "Production",
"transactionReason": "PURCHASE",
"storefront": "CHN",
"storefrontId": "143465"
}
*/
}

func ExampleExtractClaimsToken() {
signedPayload := "eyJhbGciOi......upGRTJlydJrUM9aRfdezRc0-1ekmMwcndcA"
np := &NotificationV2Payload{}
tk, err := ExtractClaimsToken(signedPayload, np)
xlog.Errorf("err:%+v\n", err)
xlog.Infof("token:%+v\n", tk)
xlog.Infof("np:%+v\n", np)

// Output:
// - err: error, invalid message.
// - token: *jwt.Token, &jwt.Token{...}
// - np: *apple.NotificationV2Payload, &apple.NotificationV2Payload{...}
}
Loading