Search code examples
postbackx509skadnetwork

How to verify a Postback with Skadnetwork/Apple


I'm having issues to verify a postback with Skadnetwork/Apple according to the documentation Apple is giving out.

There are two issues I have (at least I think there are only two issues).

  1. \u2063 - I'm using a PHP to grab all the information from a POST and then create the string for later verification. Sadly I'm not sure if the \u2063 should simply be there in the string or if it should there be in some encoded/decoded way.
  2. The Apple Public Key - How should it be used. The version that is seen in the documentation in some decoded/encoded way. Docs say decode base 64 and then create X.509 standard public key from that.

Does anyone have a working example? At the moment I'm complete lost.


Solution

  • Im using NodeJS and it was pretty simple. I took Apple's public key and wrapped it with -----BEGIN PUBLIC KEY-----\n and \n-----END PUBLIC KEY-----.

    -----BEGIN PUBLIC KEY-----
    <APPLE_PUBLIC_KEY_BASE_64 (copy paste from Apple's guide)>
    -----END PUBLIC KEY-----
    
    

    Or you can use NodeJs crypto module to load the public key:

    const key = Buffer.from(`<APPLE_PUBLIC_KEY_BASE_64 (copy paste from Apple's guide)>`, 'base64');
    const publicKey = crypto.createPublicKey({
      key,
      format: 'der',
      type: 'spki',
    });
    

    Then I concatenated the parts of the postback that are needed for the verification with \u2063 separator.

    // ad network version 2.0/2.1
    const message = [
      version,
      adNetworkId,
      campaignId,
      appId,
      transactionId,
      redownload,
    ].join('\u2063');
    

    Then I used NodeJS crypto module to verify the signature:

    const verify = crypto.createVerify('sha256');
    verify.update(message);
    verify.verify(publicKey, signature, 'base64'); // this returns a boolean
    

    This can be done in a similar way with Singular-SKAdNetwork-App ECDSA wrapper class from here

    SEPERATOR = u"\u2063"
    
    postback = {
        "version": "2.1",
        "ad-network-id": "com.example",
        "campaign-id": 42,
        "transaction-id": "6aafb7a5-0170-41b5-bbe4-fe71dedf1e28",
        "app-id": 525463029,
        "attribution-signature": "MEUCID6rbq3qt4GvFaAaynh5/LAcvn1d8CQTRhrZhLIxLKntAiEAo7IrvoMw6u2qDg6Tr5vIsEHXjlLkPlCOL0ojJcEh3Qw=",
        "redownload": True,
        "source-app-id": 1234567891,
        "conversion-value": 20
    }
    
    pub_key = """
    -----BEGIN PUBLIC KEY-----
    MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWdp8GPcGqmhgzEFj9Z2nSpQVddayaPe4FMzqM9wib1+aHaaIzoHoLN9zW4K8y4SPykE3YVK3sVqW6Af0lfx3gg==
    -----END PUBLIC KEY-----
    """
    
    message = (
        postback["version"]
        + SEPERATOR
        + postback["ad-network-id"]
        + SEPERATOR
        + str(postback["campaign-id"])
        + SEPERATOR
        + str(postback["app-id"])
        + SEPERATOR
        + postback["transaction-id"]
        + SEPERATOR
        + str(postback["redownload"]).lower()
        + SEPERATOR
        + str(postback["source-app-id"])
    )
    
    ecdsa = ECDSA(pub_key)
    signature = postback["attribution-signature"]
    ecdsa.verify(message, signature) # this returns a boolean
    

    I hope this will help. I don't have any experience with PHP :/