3 votes

échec de la notification push php

J'essaie d'envoyer une notification push JWT au point de terminaison pushSubscription. Lorsque je renvoie le résultat, j'obtiens "Invalid JWT Provided "Je n'ai aucune idée de la raison pour laquelle cela ne fonctionne pas. Je génère une signature ECDSA en php et le JWT renvoyé est valide et testé dans jwt.io. NOTE : les clés ne sont pas en production, elles sont fournies pour le contexte.

/*GET ENDPOINT*/
$endpoint_push = json_decode($subscription)->endpoint;
$public_key_push = 'BNQCrj2wbXHBAK1hyjvc9R5zjypBwWG6szD_STnDPy2ORVUqTWZD304JS5LTHK5ywYS2w-aRouH3EjxLG9bWla8';

$token_push = '';

//PREPARE PUSH//

// Create token header as a JSON string
$header = json_encode(['typ' => 'JWT', 'alg' => 'ES256']);

// Create token payload as a JSON string
$payload = json_encode(['aud' => 'https://fcm.googleapis.com', 'exp' => '1516239022', 'sub' => 'mailto:push@example.com']);

// Encode Header to Base64Url String
$base64UrlHeader = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($header));

// Encode Payload to Base64Url String
$base64UrlPayload = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($payload));

// Create Signature Hash

$privateKeyString =
"-----BEGIN EC PARAMETERS-----
BgUrgQQACg==
-----END EC PARAMETERS-----
-----BEGIN EC PRIVATE KEY-----
MHQCAQEEIHI6VMaMwvRag0foPp87+nhby3QrftcEsBHee6sdr0aZoAcGBSuBBAAK
oUQDQgAE91vCtp7tO4FyJbpgSS824PiuLR7LPNdwt+rcIe0uE19RUJz2Jgm8tRRD
HmBVzoQXNxcwVD1HfRMtU0wnUJOuAQ==
-----END EC PRIVATE KEY-----";

$privateKey = openssl_get_privatekey($privateKeyString);

$alg = OPENSSL_ALGO_SHA256;
$signature = null;

openssl_sign($base64UrlHeader . "." . $base64UrlPayload, $signature, $privateKey, $alg);

// Encode Signature to Base64Url String
$base64UrlSignature = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($signature));

// Create JWT
$token_push = $base64UrlHeader . "." . $base64UrlPayload . "." . $base64UrlSignature;

//SEND PUSH//
$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $endpoint_push);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);

$headers = array();
$headers[] = 'Ttl: 60';
$headers[] = 'Content-Length: 0';
$headers[] = 'Authorization: vapid t='.$token_push.', k='.$public_key_push.'';
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

$result = curl_exec($ch);

curl_close($ch);

echo json_encode($result);

1voto

Stampy Points 227

J'ai donc essayé de faire exactement la même chose, et ce qui m'a pris du temps à réaliser, c'est qu'OpenSSL enveloppe la plupart de sa sortie dans une structure formatée ASN.1, que les jetons JWT doivent d'abord dépouiller.

Voici un script fonctionnel qui, je l'espère, pourra être utile à d'autres.

Veuillez noter que ce script n'attache aucune donnée au message push, donc le client web recevra un message push qui n'a pas de contexte, et ils n'ont pas accès (pour autant que je puisse voir) à l'objet JWT non plus, donc ne peuvent pas y mettre de données utiles non plus.

Il existe bien sûr un moyen de joindre des données cryptées à la demande, qui n'est pas abordé ici.

<?php

/*
 * If you get any part of this process wrong, Google gives the really helpful error message "invalid JWT provided".
 * 
 * Mozilla (Firefox) gives a slightly just-as-useful error:
 * {
 *   "code": 401, "errno": 109, "error": "Unauthorized",
 *   "more_info": "http://autopush.readthedocs.io/en/latest/http.html#error-codes",
 *   "message": "Request did not validate Invalid Authorization Header"
 * }
 */

// Generate the keys like this, although you can probably do it in PHP.
// `openssl ecparam -genkey -name prime256v1 -noout -out server-push-ecdh-p256.pem &>/dev/null`;
// `openssl ec -in server-push-ecdh-p256.pem -pubout -out server-push-ecdh-p256.pub &>/dev/null`;

$privk = file_get_contents('server-push-ecdh-p256.pem');
$pubk = file_get_contents('server-push-ecdh-p256.pub');
$endpoint = "https://fcm.googleapis.com/fcm/send/some-really-long-unique-secret-string-that-your-web-client-gets-on-push-subscribe";
$contact = "mailto:your-admin-email-address@example.com";

function base64web_encode($a) {
    return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($a));
}
function base64web_decode($a) {
    return base64_decode(str_replace(['-', '_', ''], ['+', '/', '='], $a));
}

$asn = new phpseclib\File\ASN1();

$header = [
    "typ" => "JWT",
    "alg" => "ES256"
];
$claims = [
    // just the https://hostname part
    "aud" => substr($endpoint, 0, strpos($endpoint, '/', 10)),
    // this push message will be discarded after 24 hours of non-delivery
    "exp" => time() + 86400,
    // who the server can talk to if our push script is causing problems
    "sub" => $contact
];

/*
 * Note these need to be base64 url-safe encoded, not standard base64.
 * @see https://tools.ietf.org/html/rfc4648#section-5
 */
$strHeader = base64web_encode(json_encode($header));
$strPayload = base64web_encode(json_encode($claims));

$toSign = $strHeader . '.' . $strPayload;

$signature = '';
if (!openssl_sign($toSign, $signature, $privk, OPENSSL_ALGO_SHA256)) {
    trigger_error('sign failed: '. openssl_error_string());
}

/*
 * openssl_sign produces a signature which is the hash wrapped in an 
 * ASN.1 structure, so we need to extract the 256-bit raw hash manually. 
 * There's no PHP function to do this, so we use a library.
 */
$xx = $asn->decodeBER($signature);
/** @var \phpseclib\Math\BigInteger $a */
/** @var \phpseclib\Math\BigInteger $b */
$a = $xx[0]['content'][0]['content']; // 128-bits
$b = $xx[0]['content'][1]['content']; // 128-bits
$signature = $a->toBytes() . $b->toBytes();
$strSignature = base64web_encode($signature);

/*
 * This is now a complete JWT object.
 */
$jwt = $strHeader . '.' . $strPayload . '.' . $strSignature;

/*
 * Our PEM formatted public key is wrapped in an ASN.1 structure, so just 
 * like our signature above, lets extract
 * the raw public key part, which is the bit we need.
 */
$xx = $pubk;
$xx = str_replace(['-----BEGIN PUBLIC KEY-----','-----END PUBLIC KEY-----',"\n"], '', $xx);
$xx = base64_decode($xx);
$xx = $asn->decodeBER($xx);
$xx = $xx[0]['content'][1]['content'];
$xx = substr($xx, 1); // need to strip the first char, which is not part of the key
$xx = base64web_encode($xx);
$pubkey = $xx;

/*
 * We need to append the public key used for signing this JWT object, so 
 * the server can validate the JWT and compare the public key against the 
 * push-registration by the client, where we said which public key we would 
 * accept pushes from.
 */
$headers = [
    "Authorization: vapid t=$jwt,k=$pubkey",
    "Content-length: 0",
    "Ttl: 86400",
];

/**
 * Push!
 */
$ch = curl_init($endpoint);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_FAILONERROR, true);
curl_setopt($ch, CURLOPT_POST, 1);
curl_exec($ch);
$ct = curl_multi_getcontent($ch);
echo curl_error($ch);
curl_close($ch);
echo $ct;

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X