php - makeappx - uwp unpacker




Validation de la signature Windows Store IAP par rapport au certificat distant, avec PHP (2)

Je passe des jours pour vérifier les reçus et finalement le faire fonctionner ...

<?php
/**
 * Date: 01.11.2013
 * Time: 23:09
 * @author: Philipp Serrer
 */

namespace Ephisa\Service\WindowsStore;

require_once subpath . 'vendor/xmlseclibs/xmlseclibs.php';

use Ephisa\Cache;

class Receipt {

    private $doc;
    private $objXMLSecDSig;
    private $objDSig;

    function __construct($xml, $isFile = false)
    {
        if ($isFile) {
            $xml = file_get_contents($xml);
        }

        // strip unwanted chars - IMPORTANT!!!
        $xml = str_replace(array("\n","\t", "\r"), "", $xml);
        //some (probably mostly WP8) receipts have unnecessary spaces instead of tabs
        $xml = preg_replace('/\s+/', " ", $xml);
        $xml = str_replace("> <", "><", $xml);

        $doc = new \DOMDocument();
        $doc->loadXML($xml);

        $objXMLSecDSig = new \XMLSecurityDSig();
        $objDSig = $objXMLSecDSig->locateSignature($doc);

        if (!$objDSig) {
            throw new InvalidSignatureException();
        }

        //canonicalize
        $objXMLSecDSig->canonicalizeSignedInfo();

        $this->objDSig = $objDSig;
        $this->objXMLSecDSig = $objXMLSecDSig;
        $this->doc = $doc;
    }

    /**
     * Returns the key for verification.
     *
     * @return null|\XMLSecurityKey
     */
    function getKey()
    {
        $objKey = $this->objXMLSecDSig->locateKey();
        $keyInfo = \XMLSecEnc::staticLocateKeyInfo($objKey, $this->objDSig);

        if (!$keyInfo->key) {
            $xpath = new \DOMXPath($this->doc);
            $query = 'string(/Receipt/@CertificateId)';
            $id = $xpath->evaluate($query);

            Cache::instance()->setLifetime(60*60*24*7, 'win-store-cert');
            $cert = Cache::instance()->get($id, 'win-store-cert', function() use ($id) {
                return file_get_contents('https://lic.apps.microsoft.com/licensing/certificateserver/?cid=' . $id);
            });

            $objKey->loadKey($cert, false);
        }

        return $objKey;
    }

    /**
     * Verifies the given receipt
     *
     * @return bool Returns TRUE on success
     */
    function verify()
    {
        try {
            if (!$this->objXMLSecDSig->validateReference()) {
                return false;
            }

            return (bool)$this->objXMLSecDSig->verify($this->getKey());
        }
        catch (\Exception $e)
        {
            // failure...
        }

        return false;
    }
}

Ce code fait partie de mon framework et contient donc du code dépend de framework (Cache), mais je pense que vous avez l'idée principale et comment ça marche. Bien sûr, vous devez inclure php xmlseclibs sur https://github.com/robrichards/xmlseclibs

J'essaie de valider une recette IAP en PHP pour une application Windows Store. Fondamentalement, en essayant de convertir cet exemple de code en PHP http://msdn.microsoft.com/en-us/library/windows/apps/jj649137.aspx . Le récit ressemble à ceci

<Receipt Version="1.0" ReceiptDate="2012-08-30T23:08:52Z" CertificateId="b809e47cd0110a4db043b3f73e83acd917fe1336" ReceiptDeviceId="4e362949-acc3-fe3a-e71b-89893eb4f528">
    <ProductReceipt Id="6bbf4366-6fb2-8be8-7947-92fd5f683530" ProductId="Product1" PurchaseDate="2012-08-30T23:08:52Z" ExpirationDate="2012-09-02T23:08:49Z" ProductType="Durable" AppId="55428GreenlakeApps.CurrentAppSimulatorEventTest_z7q3q7z11crfr" />
    <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
        <SignedInfo>
            <CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
            <SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" />
            <Reference URI="">
                <Transforms>
                    <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
                </Transforms>
                <DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
                <DigestValue>Uvi8jkTYd3HtpMmAMpOm94fLeqmcQ2KCrV1XmSuY1xI=</DigestValue>
            </Reference>
        </SignedInfo>
        <SignatureValue>TT5fDET1X9nBk9/yKEJAjVASKjall3gw8u9N5Uizx4/Le9RtJtv+E9XSMjrOXK/TDicidIPLBjTbcZylYZdGPkMvAIc3/1mdLMZYJc+EXG9IsE9L74LmJ0OqGH5WjGK/UexAXxVBWDtBbDI2JLOaBevYsyy+4hLOcTXDSUA4tXwPa2Bi+BRoUTdYE2mFW7ytOJNEs3jTiHrCK6JRvTyU9lGkNDMNx9loIr+mRks+BSf70KxPtE9XCpCvXyWa/Q1JaIyZI7llCH45Dn4SKFn6L/JBw8G8xSTrZ3sBYBKOnUDbSCfc8ucQX97EyivSPURvTyImmjpsXDm2LBaEgAMADg==</SignatureValue>
    </Signature>
</Receipt>

J'ai récupéré un certificat pour le serveur comme ceci

function getCertificate($certID)
{
    $url  = 'https://lic.apps.microsoft.com/licensing/certificateserver/?cid=' . $certID;
    $path = '/mypath/certs/' . $certID;

    if(!file_exists($path)) {
        $fp = fopen($path, 'w');

        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_FILE, $fp);

        $data = curl_exec($ch);

        curl_close($ch);
        fclose($fp);
    }
    $cert = file_get_contents($path);
    //var_dump(openssl_x509_parse($cert));

    return openssl_x509_read($cert);
}

et je suppose que SignatureValue est ma signature. Pour autant que je puisse le dire à la lecture, la fonction dont j'ai besoin est openssl_verify mais je ne suis pas sûr des paramètres que je devrais utiliser car la vérification échoue toujours.

$data     = $receiptXML->Signature->SignatureValue;
$pubkeyid = openssl_get_publickey($cert);
// state whether signature is okay or not
$ok       = openssl_verify($receipt, $data, $pubkeyid, OPENSSL_ALGO_SHA256);
if($ok == 1) {
    echo "good";
} elseif($ok == 0) {
    echo "bad";
} else {
    echo "ugly, error checking signature";
}
// free the key from memory
openssl_free_key($pubkeyid);

Est-ce que quelqu'un sait où je me suis trompé ici?


Pour commencer, je recommanderais que le certificat soit écrit en mode binaire. Cela le rend moins sujet aux erreurs. Donc ce que je recommanderais est

if(!file_exists($path)) {
        $fp = fopen($path, 'wb');

Je dois présumer ici que $ CERTID aura la valeur de CertificateId du reçu XML. Veuillez renommer comme vous l'avez déclaré dans votre code.

$cert = getCertificate($CERTID)
if($cert == 0) {
    echo "bad";
} else {
    $data = $receiptXML->Signature->SignatureValue;
    $pubkeyid = openssl_get_publickey($cert);
    // state whether signature is okay or not
    $ok = openssl_verify($receiptXML, $data, $pubkeyid, OPENSSL_ALGO_SHA256);
    if($ok == 1) {
        echo "good";
    } elseif($ok == 0) {
        echo "bad";
    } else {
        echo "ugly, error checking signature";
    }

} 

J'espère que cela t'aides :)







openssl