2

I'm following the documentation here: https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/PassKit_PG/Updating.html to update a created Apple wallet pass. I'm currently getting the registration endpoint and recording the deviceID and push_token. I'm left with a few questions:

  1. where are the pass type identifier and push token used in the APNs push? For the pass type identifier the doc say in cert, does that mean to add it to the jwt? If so in claims or headers? For the push token it says in communication with APNs. I can logically assume that means the payload, but just want to be sure.
  2. For the APNs push what is required in the fields apns-push-type, apns-topic, and apns-priority fields? My guesses would be the type is background, topic is the pass type identifier for the pass, and priority should not be included. But I haven't gotten it to work with code to that effect.
  3. Is there a way to add the serial number of the pass to the push notification to skip the "Devices Ask for Changed Serial Numbers" step? It would complicate things significantly for my API to know which pass should be updated after losing context, and the user shouldn't have multiple passes for the same identifier from my app anyway.
  4. For the final step "Devices Ask for Latest Version of Passes" what format is the phone expecting the pass data in? It doesn't give any examples. My best guess would be json with a pass-data byte array representing a new signed pkpass with the same serial number but I can't find confirmation anywhere.

I'm currently creating and registering passes and recording the push token. Then sending a push notification in Go like this:

        authorization, err := getAppleBearerAuth()
    endpoint := fmt.Sprintf("https://api.sandbox.push.apple.com/3/device/%s", device_id) //APNs dev endpoint

    //token in payload
    payload := map[string]interface{}{"push_token": push_token}
    payloadJSON, err := json.Marshal(payload)
    if err != nil {
        fmt.Println("Failed to marshal payload:", err)
        return err
    }

    expiration := time.Now().Add(24 * time.Hour).Unix()
    // Set the headers for the request
    headers := map[string]string{
        "Authorization":   *authorization,
        "Content-Type":    "application/json",
        "apns-push-type":  "background",
        "apns-topic":      os.Getenv("APPLE_PASS_TYPE_IDENTIFIER"),
        "apns-expiration": fmt.Sprintf("%d", expiration),
    }

    httpClient := &http.Client{
        Transport: &http.Transport{
            TLSClientConfig: &tls.Config{
                InsecureSkipVerify: true, // Set to false in production
            },
        },
    }

    request, err := http.NewRequest("POST", endpoint, bytes.NewBuffer(payloadJSON))
    if err != nil {
        fmt.Println("Failed to create request:", err)
        return err
    }


    for key, value := range headers {
        request.Header.Set(key, value)
    }

    response, err := httpClient.Do(request)
    if err != nil {
        fmt.Println("Failed to send request:", err)
        return err
    }

    defer response.Body.Close()

    if response.StatusCode == http.StatusOK {
        fmt.Println("Push notification sent successfully.")
    } else {
        fmt.Println("Failed to send the push notification. Status code:", response.StatusCode)
    }
    return nil}


func getAppleBearerAuth() (*string, error) {
    kid := os.Getenv("APPLE_PUSH_KEY")
    iss := os.Getenv("APPLE_TEAM_ID")

    // Create a new JWT token
    token := jwt.New(jwt.SigningMethodES256)

    // Add claims
    claims := token.Claims.(jwt.MapClaims)
    claims["iss"] = iss
    claims["iat"] = time.Now().Unix()

    // Add kid to the header
    token.Header["kid"] = kid

    // Load your ES256 key
    key, err := loadPrivateKey()
    if err != nil {
        return nil, err
    }

    // Sign and get the complete encoded token as a string
    tokenString, err := token.SignedString(key)
    if err != nil {
        return nil, err
    }

    // Add "Bearer " to the token string to make it a bearer token
    bearer := "Bearer " + tokenString
    return &bearer, nil
}

func loadPrivateKey() (interface{}, error) {
    // Here you should implement loading your private key
    // Usually it involves reading from a PEM file and parsing the key

    // Path to the .p12 file
    certPath := os.Getenv("APPLE_CERT")
    // Password for the .p12 file
    certPassword := os.Getenv("PASSKEYPASS")

    p12Bytes, err := ioutil.ReadFile(certPath)
    if err != nil {
        return nil, err
    }

    blocks, err := pkcs12.ToPEM(p12Bytes, certPassword)
    if err != nil {
        return nil, err
    }

    pemData := pem.EncodeToMemory(&pem.Block{
        Type:  "RSA PRIVATE KEY",
        Bytes: blocks[0].Bytes,
    })

    key, err := jwt.ParseRSAPrivateKeyFromPEM(pemData)
    if err != nil {
        return nil, err
    }

    return key, nil
}

It is signed with the same certs the pass is created with. I can tell I'm probably not putting the pass type identifier in the right place, and it's likely why this step isn't working, but I'm not sure where it's supposed to be.

Has anyone managed to update Apple passes from a backend service that could clarify some of this?

  • Can you give more information on precisely what is not working. Where you are extracting the pkcs12 file, it is unlikely that the private key is listed before the public key, yet you are trying to encode the first block as an RSA PRIVATE KEY. – PassKit Jun 28 '23 at 10:40

0 Answers0