4
votes

Générer une signature pour les offres d'abonnement - Xcode - Swift

Je voulais demander si quelqu'un a déjà implémenté les nouvelles offres pour l'abonnement inapp (renouvellement automatique), la difficulté de créer côté serveur le système pour créer cette signature en utilisant la clé p8 avec php si possible. J'ai trouvé cela sur la documentation Apple, je ne suis pas sûr de le comprendre: https://developer.apple.com/documentation/storekit/in-app_purchase/generating_a_signature_for_subscription_offers


0 commentaires

5 Réponses :


5
votes

Voici une procédure pas à pas de RevenueCat: Offres d'abonnement iOS

Le message contient beaucoup plus de détails, mais la génération de signature est:

import json
import uuid
import time
import hashlib
import base64

from ecdsa import SigningKey
from ecdsa.util import sigencode_der

bundle_id = 'com.myapp'
key_id = 'XWSXTGQVX2'
product = 'com.myapp.product.a'
offer = 'REFERENCE_CODE' # This is the code set in ASC
application_username = 'user_name' # Should be the same you use when
                                   # making purchases
nonce = uuid.uuid4()
timestamp = int(round(time.time() * 1000))

payload = '\u2063'.join([bundle_id, 
                         key_id, 
                         product, 
                         offer, 
                         application_username, 
                         str(nonce), # Should be lower case
                         str(timestamp)])

# Read the key file
with open('cert.der', 'rb') as myfile:
  der = myfile.read()

signing_key = SigningKey.from_der(der)

signature = signing_key.sign(payload.encode('utf-8'), 
                             hashfunc=hashlib.sha256, 
                             sigencode=sigencode_der)
encoded_signature = base64.b64encode(signature)

print(str(encoded_signature, 'utf-8'), str(nonce), str(timestamp), key_id)

Ceci est juste une preuve de concept. Vous souhaiterez cela sur votre serveur et aurez peut-être une logique pour déterminer, pour un utilisateur donné, si l'offre demandée est appropriée.

Une fois que vous avez généré la signature, le nonce et l'horodatage, renvoyez-les avec le key_id à votre application où vous pouvez créer un SKPaymentDiscount .

Avertissement: Je travaille chez RevenueCat. Nous prenons en charge les offres d'abonnement prêtes à l'emploi avec notre SDK, aucune signature de code requise: https://www.revenuecat.com/2019/04/25/signing-ios-subscription-offers


2 commentaires

Merci d'avoir signalé cet exemple, je l'ai déjà suivi, mais malheureusement le résultat n'est pas positif


Connaissez-vous quelque chose d'exemple avec PHP?



-2
votes

Résultat

J'ai trouvé cet exemple en ligne mais malheureusement le résultat n'est pas positif. Exemple de didacticiel


0 commentaires

-1
votes

J'essaie de créer quelque chose en php, mais cela semble peu pratique, peut-être repérer quelque chose dans l'encodage?

<?php

class Key_offer {

    var $appBundleId;
    var $keyIdentifier;
    var $productIdentifier;
    var $offerIdentifier;
    var $applicationUsername;
    var $nonce;
    var $timestamp;

    function __construct() {
        // Setting Data
        $this->appBundleId = 'bundle-app-id';
        $this->keyIdentifier = '0123456789';
        $this->productIdentifier = $_POST["productIdentifier"] ?? "";
        $this->offerIdentifier = $_POST["offerIdentifier"] ?? "";
        $this->applicationUsername = $_POST["usernameHash"] ?? "";  // usare lo stesso anche nella chiamata che si effettua da Xcode
        $this->nonce = strtolower( $this->gen_uuid() ); // genera UUID formato 4;
        $this->timestamp = time(); // get timeStump
    }

    function rsa_sign($policy, $private_key_filename) {
        $signature = "";
        // load the private key
        $fp = fopen($private_key_filename, "r");
        $priv_key = fread($fp, 8192);
        fclose($fp);
        $pkeyid = openssl_get_privatekey($priv_key);
        // compute signature
        openssl_sign($policy, $signature, $pkeyid);
        // free the key from memory
        openssl_free_key($pkeyid);
        return $signature;
     }

     function gen_uuid() {
        return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
            // 32 bits for "time_low"
            mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),
            // 16 bits for "time_mid"
            mt_rand( 0, 0xffff ),
            // 16 bits for "time_hi_and_version",
            // four most significant bits holds version number 4
            mt_rand( 0, 0x0fff ) | 0x4000,
            // 16 bits, 8 bits for "clk_seq_hi_res",
            // 8 bits for "clk_seq_low",
            // two most significant bits holds zero and one for variant DCE1.1
            mt_rand( 0, 0x3fff ) | 0x8000,
            // 48 bits for "node"
            mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff )
        );
    }

    function get() {
        $text = utf8_encode($this->appBundleId.'\u2063'.$this->keyIdentifier.'\u2063'.$this->productIdentifier.'\u2063'.$this->offerIdentifier.'\u2063'.$this->applicationUsername.'\u2063'.$this->nonce.'\u2063'.$this->timestamp);

        $signature0 = $this->rsa_sign($text, "key.pem"); // SubscriptionKey_43PF4FTV2X.p8
        $signature = hash('sha256', $signature0);
        $array = array(
            'lowUUid' => $this->nonce,
            'timeStump' => $this->timestamp,
            'identifier' => $this->offerIdentifier,
            'keyid' => $this->keyIdentifier,
            'signature' => base64_encode($signature)
        );

        return json_encode($array);
    }


}

$obj = new Key_offer();
echo $obj->get();

?>


1 commentaires

Vous feriez mieux de ne pas réinventer la roue et d'utiliser les bibliothèques appropriées pour les opérations UUID et cryptographiques. Il existe une excellente bibliothèque PHP pour UUID github.com/ramsey/uuid , installée via composer car le composer require ramsey/uuid . De plus, vous avez besoin de l'algorithme ECDSA pour la signature, et c'est mieux fait avec une bibliothèque, telle que PHPECC ou tout autre (j'ai utilisé celui-ci packagist.org/packages/sop/crypto-bridge ).



2
votes

Je peux confirmer que cela fonctionne:

<?php
use Ramsey\Uuid\Uuid;

class ItunesSignatureGenerator {
    private $appBundleID = 'your.bundle.id';

    private $keyIdentifier = 'ZZZZZZZ';

    private $itunesPrivateKeyPath = '/path/to/the/file.p8;

    /**
     * @see https://developer.apple.com/documentation/storekit/in-app_purchase/generating_a_signature_for_subscription_offers
     *
     * @param $productIdentifier
     * @param $offerIdentifier
     *
     * @return Signature
     */
    public function generateSubscriptionOfferSignature($productIdentifier, $offerIdentifier)
    {
        $nonce = strtolower(Uuid::uuid1()->toString());
        $timestamp = time() * 1000;
        $applicationUsername = 'username';

        $message = implode(
            "\u{2063}",
            [
                $this->appBundleID,
                $this->keyIdentifier,
                $productIdentifier,
                $offerIdentifier,
                $applicationUsername,
                $nonce,
                $timestamp
            ]
        );

        $message = $this->sign($message);

        return new Signature(
            base64_encode($message),
            $nonce,
            $timestamp,
            $this->keyIdentifier
        );
    }

    private function sign($data)
    {
        $signature = '';

        openssl_sign(
            $data,
            $signature,
            openssl_get_privatekey('file://' . $this->itunesPrivateKeyPath),
            OPENSSL_ALGO_SHA256
        );

        return $signature;
    }
}

Nous avons eu un problème côté client car l'appareil était connecté sur 2 comptes iTunes différents, un normal et un sandbox. Cela créait une erreur de signature non valide qui n'a pas fait sens. Nous déconnectons le compte régulier et utilisons simplement le compte sandbox et tout fonctionne.


2 commentaires

Il manque la définition de la classe Signature


La classe de signature est juste un DTO sans logique.



1
votes

J'avais des problèmes avec les offres d'abonnement, mais ce problème sur GitHub m'a aidé à le faire fonctionner. J'ai installé la bibliothèque Sop CryptoBridge (le composer require sop/crypto-bridge ) et cela a finalement fonctionné pour mon client d'application iOS. Voici mon code PHP de travail:

use Sop\CryptoBridge\Crypto;
use Sop\CryptoEncoding\PEM;
use Sop\CryptoTypes\AlgorithmIdentifier\Hash\SHA256AlgorithmIdentifier;
use Sop\CryptoTypes\AlgorithmIdentifier\Signature\SignatureAlgorithmIdentifierFactory;
use Sop\CryptoTypes\Asymmetric\PrivateKeyInfo;

// you can copy your p8 file contents here or just use file_get_contents()
$privateKeyPem = <<<'PEM'
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
PEM;

// load private key
$privateKeyInfo = PrivateKeyInfo::fromPEM(PEM::fromString($privateKeyPem));
// you can also load p8 file like this
// PrivateKeyInfo::fromPEM(PEM::fromFile($pathToP8));

// combine the parameters
$appBundleId = 'com.github.yaronius'; // iOS app bundle ID
$keyIdentifier = 'A1B2C3D4E5'; // Key ID from AppStore Connect
$productIdentifier = 'product_identifier';
$offerIdentifier = 'offer_identifier';
$applicationUsername = 'username'; // same as in the iOS app
$nonce = 'db6ba7a6-9ec2-4504-bcb9-c0dfbdc3d051'; // some UUID in lowercase
$timestamp = time() * 1000; // milliseconds! as stated in the docs

$dataArray = [
    $appBundleId,
    $keyIdentifier,
    $productIdentifier,
    $offerIdentifier,
    $applicationUsername,
    $nonce,
    $timestamp
];
$data = implode("\u{2063}", $dataArray);

// signature algorithm
$algo = SignatureAlgorithmIdentifierFactory::algoForAsymmetricCrypto(
    $privateKeyInfo->algorithmIdentifier(),
    new SHA256AlgorithmIdentifier()
);
// generate signature
$signature = Crypto::getDefault()->sign($data, $privateKeyInfo, $algo);

// encode as base64 encoded DER
$encodedSignature = base64_encode($signature->toDER());
// send signature to your app
echo $encodedSignature;

Gardez à l'esprit quelques points:

  • comme délimiteur, vous devez utiliser le point de code PHP Unicode , c'est-à-dire "\u{2063}" . L'utilisation de '\u2063' ne fonctionnait pas pour moi.
  • $nonce est un UUID en minuscule
  • $timestamp est en millisecondes (ie time() * 1000 ).

Et cela devrait fonctionner comme un charme.


2 commentaires

J'essaie d'installer la bibliothèque que vous avez signalée, mais j'obtiens toujours un message d'erreur / crypto-types 0.2.1 nécessite ext-gmp * -> l'extension PHP demandée gmp est absente de votre système.


Oui, la plupart des bibliothèques cryptographiques nécessitent l'extension PHP GMP (GNU Multiple Precision). Vous pouvez en installer un en suivant les étapes d'ici: stackoverflow.com/questions/40010197/...