Protocole Web push

Nous avons vu comment une bibliothèque peut être utilisée pour déclencher des messages push, mais que font exactement ces bibliothèques ?

Elles effectuent des requêtes réseau tout en s'assurant que ces requêtes sont au bon format. La spécification qui définit cette requête réseau est le protocole Web push.

Schéma illustrant l'envoi d'un message push de votre serveur à un service push

Cette section décrit comment le serveur peut s'identifier à l'aide des clés de serveur d'applications, et comment la charge utile chiffrée et les données associées sont envoyées.

Ce n'est pas un côté plutôt intéressant du Web push et je ne suis pas expert en chiffrement, mais examinons chaque élément, car il est utile de savoir ce que font ces bibliothèques en arrière-plan.

Clés du serveur d'applications

Lorsque nous abonnons un utilisateur, nous transmettons un applicationServerKey. Cette clé est transmise au service push et permet de vérifier que l'application à l'origine de l'abonnement à l'utilisateur est également celle qui déclenche les messages push.

Lorsque nous déclenchons un message push, nous envoyons un ensemble d'en-têtes qui permettent au service push d'authentifier l'application. (Ceci est défini par la spécification VAPID.)

Qu'est-ce que cela signifie réellement et que se passe-t-il exactement ? Voici les étapes effectuées pour l'authentification du serveur d'applications:

  1. Le serveur d'application signe certaines informations JSON avec sa clé d'application privée.
  2. Ces informations signées sont envoyées au service push sous la forme d'un en-tête dans une requête POST.
  3. Le service push utilise la clé publique stockée qu'il a reçue de pushManager.subscribe() pour vérifier que les informations reçues sont signées par la clé privée en lien avec la clé publique. Rappel: La clé publique est le applicationServerKey transmis dans l'appel d'abonnement.
  4. Si les informations signées sont valides, le service push envoie le message push à l'utilisateur.

Vous trouverez un exemple de ce flux d'informations ci-dessous. (Notez la légende en bas à gauche qui indique les clés publiques et privées.)

Illustration de l'utilisation de la clé privée du serveur d'applications lors de l'envoi d'un message

Les "informations signées" ajoutées à un en-tête dans la requête sont des jetons Web JSON.

Jeton Web JSON

Un jeton Web JSON (ou JWT en abrégé) est un moyen d'envoyer un message à un tiers afin que le destinataire puisse valider son expéditeur.

Lorsqu'un tiers reçoit un message, il doit obtenir la clé publique de l'expéditeur et l'utiliser pour valider la signature du jeton JWT. Si la signature est valide, le jeton JWT doit avoir été signé avec la clé privée correspondante et doit donc provenir de l'expéditeur attendu.

Il existe de nombreuses bibliothèques sur https://jwt.io/ qui peuvent effectuer la signature pour vous, et je vous recommande de le faire là où vous le pouvez. Pour plus de détails, voyons comment créer manuellement un jeton JWT signé.

Jetons JWT Web push et signés

Un jeton JWT signé n'est qu'une chaîne, bien qu'il puisse être considéré comme trois chaînes reliées par des points.

Illustration des chaînes d'un jeton Web JSON

La première et la deuxième chaîne (informations JWT et données JWT) sont des éléments JSON qui ont été encodés en base64, ce qui signifie qu'elles sont lisibles publiquement.

La première chaîne contient des informations sur le jeton JWT lui-même, indiquant l'algorithme utilisé pour créer la signature.

Les informations JWT pour les notifications Web push doivent contenir les informations suivantes:

{
  "typ": "JWT",
  "alg": "ES256"
}

La deuxième chaîne correspond aux données JWT. Elle fournit des informations sur l'expéditeur du jeton JWT, son destinataire et sa durée de validité.

Pour les notifications Web push, les données sont au format suivant:

{
  "aud": "https://some-push-service.org",
  "exp": "1469618703",
  "sub": "mailto:example@web-push-book.org"
}

La valeur aud correspond à l'"audience", c'est-à-dire à qui est destiné le JWT. Pour le Web push, l'audience est le service push. Nous définissons donc celui-ci sur l'origine du service push.

La valeur exp correspond à l'expiration du jeton JWT. Cela empêche les pirates de pouvoir le réutiliser s'ils l'interceptent. L'expiration est un horodatage exprimé en secondes et ne doit plus être de 24 heures.

Dans Node.js, le délai d'expiration est défini à l'aide de la commande suivante:

Math.floor(Date.now() / 1000) + 12 * 60 * 60;

Ce délai est de 12 heures au lieu de 24 heures pour éviter tout problème d'horloge entre l'application émettrice et le service push.

Enfin, la valeur sub doit être une URL ou une adresse e-mail mailto. Ainsi, si un service push doit contacter l'expéditeur, il peut trouver les coordonnées à partir du JWT. C'est pourquoi la bibliothèque web-push avait besoin d'une adresse e-mail.

Tout comme les informations JWT, les données JWT sont encodées sous la forme d'une chaîne base64 sécurisée pour les URL.

La troisième chaîne, la signature, résulte de la signature et de la signature des deux premières chaînes (les informations JWT et les données JWT), que l'on joint par un point (appelé "jeton non signé").

Le processus de signature nécessite le chiffrement du "jeton non signé" à l'aide d'ES256. Selon la spécification JWT, ES256 est l'abréviation de ECDSA utilisant la courbe P-256 et l'algorithme de hachage SHA-256. Avec les cryptos Web, vous pouvez créer la signature comme suit:

// Utility function for UTF-8 encoding a string to an ArrayBuffer.
const utf8Encoder = new TextEncoder('utf-8');

// The unsigned token is the concatenation of the URL-safe base64 encoded
// header and body.
const unsignedToken = .....;

// Sign the |unsignedToken| using ES256 (SHA-256 over ECDSA).
const key = {
  kty: 'EC',
  crv: 'P-256',
  x: window.uint8ArrayToBase64Url(
    applicationServerKeys.publicKey.subarray(1, 33)),
  y: window.uint8ArrayToBase64Url(
    applicationServerKeys.publicKey.subarray(33, 65)),
  d: window.uint8ArrayToBase64Url(applicationServerKeys.privateKey),
};

// Sign the |unsignedToken| with the server's private key to generate
// the signature.
return crypto.subtle.importKey('jwk', key, {
  name: 'ECDSA', namedCurve: 'P-256',
}, true, ['sign'])
.then((key) => {
  return crypto.subtle.sign({
    name: 'ECDSA',
    hash: {
      name: 'SHA-256',
    },
  }, key, utf8Encoder.encode(unsignedToken));
})
.then((signature) => {
  console.log('Signature: ', signature);
});

Un service push peut valider un jeton JWT à l'aide de la clé publique du serveur d'applications pour déchiffrer la signature et s'assurer que la chaîne déchiffrée est identique au "jeton non signé" (c'est-à-dire aux deux premières chaînes du jeton JWT).

Le jeton JWT signé (c'est-à-dire les trois chaînes reliées par des points) est envoyé au service Web push en tant qu'en-tête Authorization avec le préfixe WebPush, comme ceci:

Authorization: 'WebPush [JWT Info].[JWT Data].[Signature]';

Le protocole Web push indique également que la clé publique du serveur d'applications doit être envoyée dans l'en-tête Crypto-Key en tant que chaîne encodée en base64 sécurisée pour les URL avec le préfixe p256ecdsa=.

Crypto-Key: p256ecdsa=[URL Safe Base64 Public Application Server Key]

Le chiffrement de la charge utile

Voyons maintenant comment envoyer une charge utile avec un message push afin que, lorsque notre application Web reçoit un message push, elle puisse accéder aux données qu'elle reçoit.

Tous les utilisateurs ayant utilisé d'autres services push se demandent souvent pourquoi la charge utile Web push doit-elle être chiffrée ? Avec les applications natives, les messages push peuvent envoyer des données en texte brut.

L'avantage du service push Web est que, comme tous les services push utilisent la même API (le protocole Web push), les développeurs n'ont pas à se soucier de son identité. Nous pouvons envoyer une requête au bon format et nous attendre à ce qu'un message push soit envoyé. L'inconvénient est que les développeurs peuvent éventuellement envoyer des messages à un service push non fiable. En chiffrant la charge utile, un service push ne peut pas lire les données envoyées. Seul le navigateur peut déchiffrer les informations. Cela permet de protéger les données de l'utilisateur.

Le chiffrement de la charge utile est défini dans la spécification Message Encryption.

Avant d'aborder les étapes spécifiques de chiffrement de la charge utile des messages push, nous devrions aborder certaines techniques qui seront utilisées lors du processus de chiffrement. (Massive hat tip to Mat Scales pour son excellent article sur le chiffrement push.)

ECDH et HKDF

Les protocoles ECDH et HKDF sont utilisés tout au long du processus de chiffrement et offrent des avantages pour le chiffrement des informations.

ECDH: échange de clés Diffie-Hellman à courbe elliptique

Imaginez que deux personnes, Alice et Bob, souhaitent partager des informations. Alice et Bob possèdent leurs propres clés publique et privée. Alice et Bob partagent leurs clés publiques entre eux.

La propriété utile des clés générées avec ECDH est qu'Alice peut utiliser sa clé privée et la clé publique de Bob pour créer la valeur de secret "X". Bob peut faire de même, en utilisant sa clé privée et la clé publique d'Alice pour créer indépendamment la même valeur "X". « X » devient alors une clé secrète partagée, et Alice et Bob n 'avaient qu'à partager leur clé publique. Bob et Alice peuvent désormais utiliser "X" pour chiffrer et déchiffrer les messages entre eux.

À ma connaissance, ECDH définit les propriétés des courbes permettant à cette "caractéristique" de créer un secret partagé "X".

Cet article présente l'ECDH dans les grandes lignes. Si vous souhaitez en savoir plus, je vous recommande de regarder cette vidéo.

En termes de code, la plupart des langages et des plates-formes sont fournis avec des bibliothèques pour faciliter la génération de ces clés.

Dans le nœud, nous procéderons comme suit:

const keyCurve = crypto.createECDH('prime256v1');
keyCurve.generateKeys();

const publicKey = keyCurve.getPublicKey();
const privateKey = keyCurve.getPrivateKey();

HKDF: fonction de dérivation de clé basée sur HMAC

Wikipédia propose une brève description de HKDF:

HKDF est une fonction de dérivation de clé basée sur HMAC qui transforme tout matériel de clé faible en matériel de clé fort cryptographique. Il peut, par exemple, permettre de convertir les secrets partagés échangés par Diffie Hellman en matériel de clé adapté au chiffrement, au contrôle d'intégrité ou à l'authentification.

Essentiellement, HKDF prend en compte les entrées qui ne sont pas particulièrement sécurisées et les rendra plus sécurisées.

La spécification définissant ce chiffrement nécessite l'utilisation de SHA-256 comme algorithme de hachage. Les clés obtenues pour HKDF en mode Web push ne doivent pas dépasser 256 bits (32 octets).

Dans le nœud, cela pourrait être implémenté comme suit:

// Simplified HKDF, returning keys up to 32 bytes long
function hkdf(salt, ikm, info, length) {
  // Extract
  const keyHmac = crypto.createHmac('sha256', salt);
  keyHmac.update(ikm);
  const key = keyHmac.digest();

  // Expand
  const infoHmac = crypto.createHmac('sha256', key);
  infoHmac.update(info);

  // A one byte long buffer containing only 0x01
  const ONE_BUFFER = new Buffer(1).fill(1);
  infoHmac.update(ONE_BUFFER);

  return infoHmac.digest().slice(0, length);
}

Pointe du chapeau pour l'article de Mat Scale pour cet exemple de code.

Cet article aborde en détail ECDH et HKDF.

ECDH est un moyen sécurisé de partager des clés publiques et de générer un secret partagé. HKDF est un moyen de prendre du matériel non sécurisé et de le sécuriser.

Il sera utilisé lors du chiffrement de notre charge utile. Voyons maintenant ce que nous prenons comme entrée et comment elle est chiffrée.

Entrées

Lorsque nous voulons envoyer un message push à un utilisateur avec une charge utile, nous avons besoin de trois entrées:

  1. La charge utile elle-même.
  2. Le secret auth de PushSubscription.
  3. La clé p256dh de PushSubscription.

Nous avons constaté que les valeurs auth et p256dh étaient récupérées à partir d'un PushSubscription, mais pour rappel, dans le cas d'un abonnement, nous aurions besoin des valeurs suivantes:

subscription.toJSON().keys.auth;
subscription.toJSON().keys.p256dh;

subscription.getKey('auth');
subscription.getKey('p256dh');

La valeur auth doit être traitée comme un secret et ne doit pas être partagée en dehors de votre application.

La clé p256dh est une clé publique, parfois appelée "clé publique du client". Nous utiliserons ici p256dh comme clé publique d'abonnement. La clé publique d'abonnement est générée par le navigateur. Le navigateur conserve la clé privée et l'utilise pour déchiffrer la charge utile.

Les trois valeurs auth, p256dh et payload sont nécessaires en tant qu'entrées. Le résultat du processus de chiffrement est la charge utile chiffrée, une valeur de salage et une clé publique servant uniquement à chiffrer les données.

Sel

Le salage doit être de 16 octets de données aléatoires. En NodeJS, nous procéderons comme suit pour créer une valeur salt:

const salt = crypto.randomBytes(16);

Clés publiques / privées

Les clés publiques et privées doivent être générées à l'aide d'une courbe elliptique P-256, comme nous le ferions dans Node.js comme suit:

const localKeysCurve = crypto.createECDH('prime256v1');
localKeysCurve.generateKeys();

const localPublicKey = localKeysCurve.getPublicKey();
const localPrivateKey = localKeysCurve.getPrivateKey();

Nous les appellerons "clés locales". Elles ne sont utilisées que pour le chiffrement et n'ont rien à voir avec les clés du serveur d'applications.

Avec la charge utile, la clé secrète d'authentification et la clé publique d'abonnement en tant qu'entrées, ainsi qu'un nouveau salage et un ensemble de clés locales, nous sommes prêts à procéder au chiffrement.

Clé secrète partagée

La première étape consiste à créer une clé secrète partagée à l'aide de la clé publique d'abonnement et de notre nouvelle clé privée (vous souvenez-vous de l'explication ECDH avec Alice et Bob ? en toute simplicité).

const sharedSecret = localKeysCurve.computeSecret(
  subscription.keys.p256dh,
  'base64',
);

Elle sera utilisée à l'étape suivante pour calculer la clé pseudo-aléatoire (PRK).

Clé pseudo-aléatoire

La clé pseudo-aléatoire (PRK) est la combinaison du secret d'authentification de l'abonnement push et du secret partagé que nous venons de créer.

const authEncBuff = new Buffer('Content-Encoding: auth\0', 'utf8');
const prk = hkdf(subscription.keys.auth, sharedSecret, authEncBuff, 32);

Vous vous demandez peut-être à quoi sert la chaîne Content-Encoding: auth\0. Pour résumer, l'objectif n'est pas clair, bien que les navigateurs puissent déchiffrer un message entrant et rechercher l'encodage de contenu attendu. \0 ajoute un octet avec une valeur de 0 à la fin du tampon. Ce comportement est attendu par les navigateurs qui déchiffrent le message, qui attendent alors un nombre d'octets très élevé pour l'encodage du contenu, suivi d'un octet de valeur 0, et enfin des données chiffrées.

Notre clé pseudo-aléatoire exécute simplement l'authentification, le secret partagé et une information d'encodage via HKDF (en renforçant le chiffrement du point de vue).

Contexte

Le "contexte" est un ensemble d'octets utilisé pour calculer deux valeurs ultérieurement dans le navigateur de chiffrement. Il s'agit essentiellement d'un tableau d'octets contenant la clé publique de l'abonnement et la clé publique locale.

const keyLabel = new Buffer('P-256\0', 'utf8');

// Convert subscription public key into a buffer.
const subscriptionPubKey = new Buffer(subscription.keys.p256dh, 'base64');

const subscriptionPubKeyLength = new Uint8Array(2);
subscriptionPubKeyLength[0] = 0;
subscriptionPubKeyLength[1] = subscriptionPubKey.length;

const localPublicKeyLength = new Uint8Array(2);
subscriptionPubKeyLength[0] = 0;
subscriptionPubKeyLength[1] = localPublicKey.length;

const contextBuffer = Buffer.concat([
  keyLabel,
  subscriptionPubKeyLength.buffer,
  subscriptionPubKey,
  localPublicKeyLength.buffer,
  localPublicKey,
]);

Le tampon de contexte final correspond à une étiquette, au nombre d'octets de la clé publique de l'abonnement, suivi de la clé elle-même, puis au nombre d'octets de la clé publique locale, suivi de la clé elle-même.

Avec cette valeur de contexte, nous pouvons l'utiliser dans la création d'un nonce et d'une clé de chiffrement de contenu (CEK).

Clé de chiffrement du contenu et nonce

Un nonce est une valeur qui empêche les attaques par rejeu, car il ne doit être utilisé qu'une seule fois.

La clé de chiffrement du contenu (CEK) est la clé qui sera à terme utilisée pour chiffrer notre charge utile.

Nous devons d'abord créer les octets de données pour le nonce et le CEK, qui sont simplement une chaîne d'encodage de contenu suivie du tampon de contexte que nous venons de calculer:

const nonceEncBuffer = new Buffer('Content-Encoding: nonce\0', 'utf8');
const nonceInfo = Buffer.concat([nonceEncBuffer, contextBuffer]);

const cekEncBuffer = new Buffer('Content-Encoding: aesgcm\0');
const cekInfo = Buffer.concat([cekEncBuffer, contextBuffer]);

Ces informations sont traitées par HKDF en combinant le salage et la PRK avec nonceInfo et cekInfo:

// The nonce should be 12 bytes long
const nonce = hkdf(salt, prk, nonceInfo, 12);

// The CEK should be 16 bytes long
const contentEncryptionKey = hkdf(salt, prk, cekInfo, 16);

Cela nous donne notre nonce et notre clé de chiffrement du contenu.

Effectuer le chiffrement

Maintenant que nous disposons de notre clé de chiffrement du contenu, nous pouvons chiffrer la charge utile.

Nous créons un chiffrement AES128 en utilisant la clé de chiffrement du contenu comme clé et le nonce est un vecteur d'initialisation.

Dans Node, cela se fait comme ceci:

const cipher = crypto.createCipheriv(
  'id-aes128-GCM',
  contentEncryptionKey,
  nonce,
);

Avant de chiffrer la charge utile, nous devons définir la quantité de marge intérieure à ajouter au début de la charge utile. L'ajout d'une marge intérieure empêche les écoutes clandestines de déterminer les "types" de messages en fonction de la taille de la charge utile.

Vous devez ajouter deux octets de marge intérieure pour indiquer la longueur de toute marge intérieure supplémentaire.

Par exemple, si vous n'avez ajouté aucun remplissage, vous aurez deux octets avec la valeur 0, c'est-à-dire qu'il n'existe aucun remplissage, après ces deux octets, vous lirez la charge utile. Si vous avez ajouté 5 octets de marge intérieure, les deux premiers octets auront une valeur de 5, de sorte que le consommateur lit cinq octets supplémentaires, puis commence à lire la charge utile.

const padding = new Buffer(2 + paddingLength);
// The buffer must be only zeros, except the length
padding.fill(0);
padding.writeUInt16BE(paddingLength, 0);

Nous exécutons ensuite notre remplissage et notre charge utile via ce chiffrement.

const result = cipher.update(Buffer.concat(padding, payload));
cipher.final();

// Append the auth tag to the result -
// https://nodejs.org/api/crypto.html#crypto_cipher_getauthtag
const encryptedPayload = Buffer.concat([result, cipher.getAuthTag()]);

Nous avons maintenant notre charge utile chiffrée. C'est super !

Il ne reste plus qu'à déterminer comment cette charge utile est envoyée au service push.

En-têtes et corps de la charge utile chiffrés

Pour envoyer cette charge utile chiffrée au service push, nous devons définir quelques en-têtes différents dans notre requête POST.

En-tête de chiffrement

L'en-tête "Chiffrement" doit contenir la valeur salt utilisée pour chiffrer la charge utile.

La valeur salt de 16 octets doit être encodée au format URL en base64 et ajoutée à l'en-tête de chiffrement, comme suit:

Encryption: salt=[URL Safe Base64 Encoded Salt]

En-tête de clé cryptographique

Nous avons constaté que l'en-tête Crypto-Key était utilisé dans la section "Clés de serveur d'applications" pour contenir la clé publique du serveur d'applications.

Cet en-tête est également utilisé pour partager la clé publique locale utilisée pour chiffrer la charge utile.

L'en-tête obtenu se présente comme suit:

Crypto-Key: dh=[URL Safe Base64 Encoded Local Public Key String]; p256ecdsa=[URL Safe Base64 Encoded Public Application Server Key]

Type de contenu, longueur et en-tête de codage

L'en-tête Content-Length correspond au nombre d'octets dans la charge utile chiffrée. Les en-têtes "Content-Type" et "Content-Encoding" sont des valeurs fixes. comme indiqué ci-dessous.

Content-Length: [Number of Bytes in Encrypted Payload]
Content-Type: 'application/octet-stream'
Content-Encoding: 'aesgcm'

Une fois ces en-têtes définis, nous devons envoyer la charge utile chiffrée dans le corps de la requête. Notez que Content-Type est défini sur application/octet-stream. En effet, la charge utile chiffrée doit être envoyée sous forme de flux d'octets.

En NodeJS, nous procéderions comme suit:

const pushRequest = https.request(httpsOptions, function(pushResponse) {
pushRequest.write(encryptedPayload);
pushRequest.end();

Plus d'en-têtes ?

Nous avons abordé les en-têtes utilisés pour les JWT et les clés de serveur d'applications (c'est-à-dire comment identifier l'application avec le service push), ainsi que les en-têtes utilisés pour envoyer une charge utile chiffrée.

Les services push utilisent des en-têtes supplémentaires pour modifier le comportement des messages envoyés. Certains de ces en-têtes sont obligatoires, d'autres sont facultatifs.

En-tête TTL

Obligatoire

TTL (ou valeur TTL) est un entier spécifiant le nombre de secondes pendant lesquelles vous souhaitez que votre message push soit diffusé sur le service push avant d'être distribué. Lorsque le TTL expire, le message est supprimé de la file d'attente du service de transmission et n'est pas distribué.

TTL: [Time to live in seconds]

Si vous définissez TTL sur zéro, le service push tente de distribuer le message immédiatement, mais si l'appareil n'est pas accessible, votre message est immédiatement supprimé de la file d'attente du service push.

Techniquement, un service push peut réduire la valeur TTL d'un message push s'il le souhaite. Pour savoir si c'est le cas, examinez l'en-tête TTL dans la réponse d'un service push.

Thème

Optional

Les sujets sont des chaînes qui peuvent être utilisées pour remplacer un message en attente par un nouveau message s'ils ont des noms de sujet correspondants.

Cela est utile dans les cas où plusieurs messages sont envoyés alors qu'un appareil est hors connexion et que vous souhaitez qu'un utilisateur ne voie le dernier message que lorsque l'appareil est allumé.

Degré d'urgence

Optional

L'urgence indique au service push l'importance d'un message pour l'utilisateur. Le service push peut l'utiliser pour préserver l'autonomie de la batterie de l'appareil d'un utilisateur en ne réveillant les messages importants que lorsque la batterie est faible.

La valeur de l'en-tête est définie comme indiqué ci-dessous. La valeur par défaut est normal.

Urgency: [very-low | low | normal | high]

Tout réunis

Si vous avez d'autres questions sur le fonctionnement de tout cela, vous pouvez toujours voir comment les bibliothèques déclenchent des messages push sur l'organisation web-push-libs.

Une fois que vous disposez d'une charge utile chiffrée et des en-têtes ci-dessus, il vous suffit d'envoyer une requête POST à endpoint dans un PushSubscription.

Que faire de la réponse à cette requête POST ?

Réponse du service push

Une fois que vous avez envoyé une requête à un service push, vous devez vérifier le code d'état de la réponse, car il vous indique si la requête a abouti ou non.

Status Code Description
201 Création terminée La demande d'envoi d'un message push a été reçue et acceptée.
429 Trop de requêtes. Cela signifie que votre serveur d'applications a atteint une limite de débit avec un service push. Le service push doit inclure un en-tête "Try-After" pour indiquer le délai avant qu'une autre requête puisse être effectuée.
400 Demande incorrecte. Cela signifie généralement que l'un de vos en-têtes n'est pas valide ou est mal formaté.
404 Introuvable. Cela signifie que l'abonnement est arrivé à expiration et qu'il ne peut pas être utilisé. Dans ce cas, vous devez supprimer "PushSubscription" et attendre que le client se réabonne à l'utilisateur.
410 Parti. L'abonnement n'est plus valide et doit être supprimé du serveur d'applications. Cela peut être reproduit en appelant "unsubscribe()" sur un "PushSubscription".
413 La charge utile est trop volumineuse. La taille minimale de la charge utile qu'un service push doit accepter est de 4 096 octets (ou 4 Ko).

Étapes suivantes

Ateliers de programmation