वेब पुश प्रोटोकॉल

हमने देखा है कि पुश मैसेज को ट्रिगर करने के लिए, लाइब्रेरी का इस्तेमाल कैसे किया जा सकता है. हालांकि, ये लाइब्रेरी आखिर क्या कर रही हैं?

वे नेटवर्क के अनुरोध कर रहे हैं. साथ ही, यह पक्का कर रहे हैं कि ये अनुरोध सही फ़ॉर्मैट में हों. इस नेटवर्क अनुरोध की जानकारी देने वाला स्पेसिफ़िकेशन, वेब पुश प्रोटोकॉल है.

अपने सर्वर से पुश सेवा पर पुश मैसेज भेजने का डायग्राम

इस सेक्शन में बताया गया है कि सर्वर, ऐप्लिकेशन सर्वर पासकोड की मदद से अपनी पहचान कैसे कर सकता है. साथ ही, एन्क्रिप्ट किए गए पेलोड और उससे जुड़े डेटा को भेजने का तरीका भी बताया गया है.

यह वेब पुश का अच्छा पक्ष नहीं है और मैं एन्क्रिप्शन का विशेषज्ञ नहीं हूं. हालांकि, आइए हर चीज़ को देखें, क्योंकि यह जानना आसान है कि ये लाइब्रेरी अंदरूनी तौर पर क्या कर रही हैं.

ऐप्लिकेशन सर्वर पासकोड

जब हम किसी उपयोगकर्ता की सदस्यता लेते हैं, तो हम applicationServerKey पास करते हैं. इस कुंजी को पुश सेवा को पास किया जाता है. साथ ही, इसका इस्तेमाल यह जांचने के लिए किया जाता है कि उपयोगकर्ता ने जिस ऐप्लिकेशन की सदस्यता ली है वह वही ऐप्लिकेशन है जो पुश मैसेज ट्रिगर कर रहा है.

जब हम कोई पुश मैसेज ट्रिगर करते हैं, तो हम हेडर का एक सेट भेजते हैं. इससे पुश सेवा को ऐप्लिकेशन की पुष्टि करने में मदद मिलती है. (इसके बारे में VAPID स्पेसिफ़िकेशन में बताया गया है.)

इसका क्या मतलब है और क्या होता है? ऐप्लिकेशन सर्वर की पुष्टि करने के लिए, ये चरण अपनाए जाते हैं:

  1. ऐप्लिकेशन सर्वर, निजी ऐप्लिकेशन पासकोड की मदद से, JSON फ़ॉर्मैट में दी गई कुछ जानकारी पर हस्ताक्षर करता है.
  2. हस्ताक्षर की गई यह जानकारी, POST अनुरोध में हेडर के तौर पर पुश सेवा को भेजी जाती है.
  3. पुश सेवा, pushManager.subscribe() से मिली सेव की गई सार्वजनिक कुंजी का इस्तेमाल करती है. इससे यह पता लगाया जाता है कि मिली जानकारी पर, सार्वजनिक कुंजी से जुड़ी निजी कुंजी से हस्ताक्षर किया गया है या नहीं. याद रखें: सार्वजनिक पासकोड, subscribe कॉल में पास किया गया applicationServerKey होता है.
  4. अगर हस्ताक्षर की गई जानकारी मान्य है, तो पुश सेवा उपयोगकर्ता को पुश मैसेज भेजती है.

जानकारी के इस फ़्लो का उदाहरण यहां दिया गया है. (सार्वजनिक और निजी कुंजियों को दिखाने के लिए, सबसे नीचे बाईं ओर दिए गए लेजेंड पर ध्यान दें.)

मैसेज भेजते समय, निजी ऐप्लिकेशन सर्वर पासकोड का इस्तेमाल करने का तरीका दिखाने वाला इलस्ट्रेशन

अनुरोध में हेडर में जोड़ी गई "हस्ताक्षर की गई जानकारी", JSON वेब टोकन होती है.

JSON वेब टोकन

JSON वेब टोकन (या छोटा नाम JWT) का इस्तेमाल, तीसरे पक्ष को मैसेज भेजने के लिए किया जाता है. इससे, मैसेज पाने वाला व्यक्ति यह पुष्टि कर सकता है कि मैसेज किसने भेजा है.

जब किसी तीसरे पक्ष को कोई मैसेज मिलता है, तो उसे मैसेज भेजने वाले की सार्वजनिक कुंजी लेनी होगी. इसके बाद, उस कुंजी का इस्तेमाल करके JWT के हस्ताक्षर की पुष्टि करनी होगी. अगर हस्ताक्षर मान्य है, तो JWT को मैच करने वाली निजी कुंजी से हस्ताक्षर किया गया होगा. इसलिए, यह ईमेल भेजने वाले व्यक्ति का ही होना चाहिए.

https://jwt.io/ पर कई लाइब्रेरी मौजूद हैं, जो आपके लिए हस्ताक्षर कर सकती हैं. हमारा सुझाव है कि जहां भी हो सके, वहां इन लाइब्रेरी का इस्तेमाल करें. पूरी जानकारी के लिए, मैन्युअल तरीके से हस्ताक्षर किया गया JWT बनाने का तरीका देखें.

वेब पुश और हस्ताक्षर किए गए JWT

हस्ताक्षर वाला JWT सिर्फ़ एक स्ट्रिंग होती है. हालांकि, इसे बिंदुओं से जुड़ी तीन स्ट्रिंग के तौर पर भी देखा जा सकता है.

JSON वेब टोकन में मौजूद स्ट्रिंग का इलस्ट्रेशन

पहली और दूसरी स्ट्रिंग (JWT की जानकारी और JWT डेटा), JSON के ऐसे हिस्से हैं जिन्हें Base64 से कोड में बदला गया है. इसका मतलब है कि इन्हें सार्वजनिक तौर पर पढ़ा जा सकता है.

पहली स्ट्रिंग, JWT के बारे में जानकारी होती है. इससे पता चलता है कि हस्ताक्षर बनाने के लिए किस एल्गोरिदम का इस्तेमाल किया गया था.

वेब पुश के लिए JWT की जानकारी में यह जानकारी होनी चाहिए:

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

दूसरी स्ट्रिंग, JWT डेटा है. इससे JWT भेजने वाले व्यक्ति के बारे में जानकारी मिलती है. साथ ही, यह भी पता चलता है कि यह किसके लिए है और यह कितने समय के लिए मान्य है.

वेब पुश के लिए, डेटा का यह फ़ॉर्मैट होगा:

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

aud वैल्यू, "ऑडियंस" होती है. इसका मतलब है कि JWT किसके लिए है. वेब पुश के लिए, ऑडियंस पुश सेवा होती है. इसलिए, हम इसे पुश सेवा के ऑरिजिन पर सेट करते हैं.

exp वैल्यू, JWT की समयसीमा खत्म होने की तारीख होती है. इससे, स्नूपर्स को JWT को फिर से इस्तेमाल करने से रोका जा सकता है. समयसीमा खत्म होने का मतलब है कि टाइमस्टैंप सेकंड में दिया गया हो और वह 24 घंटे से ज़्यादा का न हो.

Node.js में, खत्म होने की तारीख सेट करने के लिए:

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

यह 24 घंटे के बजाय 12 घंटे का होता है, ताकि मैसेज भेजने वाले ऐप्लिकेशन और पुश सेवा के बीच, घड़ी के अंतर से जुड़ी कोई समस्या न आए.

आखिर में, sub वैल्यू को यूआरएल या mailto ईमेल पता होना चाहिए. ऐसा इसलिए किया जाता है, ताकि अगर किसी पुश सेवा को ईमेल भेजने वाले व्यक्ति से संपर्क करना हो, तो वह JWT से संपर्क जानकारी पा सके. (इस वजह से, वेब-पुश लाइब्रेरी को ईमेल पते की ज़रूरत थी).

JWT की जानकारी की तरह ही, JWT डेटा को यूआरएल सेफ़ base64 स्ट्रिंग के तौर पर एन्कोड किया जाता है.

तीसरी स्ट्रिंग, हस्ताक्षर, पहली दो स्ट्रिंग (JWT की जानकारी और JWT डेटा) को बिंदु वर्ण से जोड़ने पर बनती है. इसे हम "बिना हस्ताक्षर वाला टोकन" कहेंगे.

हस्ताक्षर करने की प्रोसेस के लिए, ES256 का इस्तेमाल करके "बिना हस्ताक्षर वाला टोकन" एन्क्रिप्ट करना ज़रूरी है. JWT के स्पेसिफ़िकेशन के मुताबिक, ES256 का मतलब है "P-256 कर्व और SHA-256 हैश एल्गोरिदम का इस्तेमाल करने वाला ईसीडीए". वेब क्रिप्टो का इस्तेमाल करके, इस तरह हस्ताक्षर बनाया जा सकता है:

// 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);
});

पुश सेवा, सिग्नेचर को डिक्रिप्ट करने के लिए, सार्वजनिक ऐप्लिकेशन सर्वर पासकोड का इस्तेमाल करके JWT की पुष्टि कर सकती है. साथ ही, यह पक्का कर सकती है कि डिक्रिप्ट की गई स्ट्रिंग, "बिना हस्ताक्षर वाला टोकन" (यानी JWT की पहली दो स्ट्रिंग) जैसी ही है.

हस्ताक्षर वाला JWT (यानी कि बिंदुओं से जुड़ी तीनों स्ट्रिंग), वेब पुश सेवा को Authorization हेडर के तौर पर भेजा जाता है. इसमें WebPush को पहले जोड़ा जाता है, जैसे:

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

वेब पुश प्रोटोकॉल में यह भी बताया गया है कि सार्वजनिक ऐप्लिकेशन सर्वर पासकोड को Crypto-Key हेडर में, यूआरएल सेफ़ base64 कोड में बदली गई स्ट्रिंग के तौर पर भेजा जाना चाहिए. साथ ही, p256ecdsa= को पहले भेजा जाना चाहिए.

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

पेलोड एन्क्रिप्शन

अब देखते हैं कि पुश मैसेज के साथ पेलोड कैसे भेजा जा सकता है, ताकि जब हमारे वेब ऐप्लिकेशन को पुश मैसेज मिले, तो वह मिलने वाले डेटा को ऐक्सेस कर सके.

वेब पुश पेलोड को एन्क्रिप्ट (सुरक्षित) करने की ज़रूरत क्यों है, यह सवाल उन लोगों के मन में आता है जिन्होंने अन्य पुश सेवाओं का इस्तेमाल किया है. नेटिव ऐप्लिकेशन के लिए, पुश मैसेज में डेटा को सादे टेक्स्ट के तौर पर भेजा जा सकता है.

वेब पुश की खास बात यह है कि सभी पुश सेवाएं एक ही एपीआई (वेब पुश प्रोटोकॉल) का इस्तेमाल करती हैं. इसलिए, डेवलपर को यह नहीं देखना पड़ता कि पुश सेवा कौनसी है. हम सही फ़ॉर्मैट में अनुरोध कर सकते हैं और पुश मैसेज भेजे जाने की उम्मीद कर सकते हैं. इसकी एक समस्या यह है कि डेवलपर, ऐसी पुश सेवा पर मैसेज भेज सकते हैं जिस पर भरोसा नहीं किया जा सकता. पेलोड को एन्क्रिप्ट करने पर, पुश सेवा भेजे गए डेटा को नहीं पढ़ सकती. सिर्फ़ ब्राउज़र ही जानकारी को डिक्रिप्ट कर सकता है. इससे उपयोगकर्ता के डेटा को सुरक्षित रखा जाता है.

मैसेज एन्क्रिप्शन के लिए स्पेसिफ़िकेशन में, पेलोड को एन्क्रिप्ट करने के बारे में बताया गया है.

पुश मैसेज के पेलोड को एन्क्रिप्ट करने के खास चरणों को देखने से पहले, हमें कुछ ऐसी तकनीकों के बारे में बताना चाहिए जिनका इस्तेमाल एन्क्रिप्शन की प्रोसेस के दौरान किया जाएगा. (पुश एन्क्रिप्शन के बारे में बेहतरीन लेख लिखने के लिए, मैट स्केल को धन्यवाद.)

ईसीडीएच और एचकेडीएफ़

एईसीएच और एचकेडीएफ़, एन्क्रिप्शन की पूरी प्रोसेस में इस्तेमाल किए जाते हैं. साथ ही, जानकारी को एन्क्रिप्ट करने के लिए फ़ायदे भी देते हैं.

ईसीडीएच: एलिप्टिक कर्व डिफ़ी-हेलमैन कुंजी एक्सचेंज

मान लें कि ऐलिस और बॉब, दोनों को जानकारी शेयर करनी है. ऐलिस और बॉब, दोनों के पास अपनी सार्वजनिक और निजी कुंजियां होती हैं. ऐलिस और बॉब, एक-दूसरे के साथ अपनी सार्वजनिक कुंजियां शेयर करते हैं.

ईसीडीएच की मदद से जनरेट की गई कुंजियों की एक खास बात यह है कि ऐलिस, गुप्त वैल्यू 'X' बनाने के लिए, अपनी निजी कुंजी और बॉब की सार्वजनिक कुंजी का इस्तेमाल कर सकती है. बॉब भी अपनी निजी कुंजी और ऐलिस की सार्वजनिक कुंजी का इस्तेमाल करके, वैल्यू 'X' को स्वतंत्र रूप से बना सकता है. इससे 'X' एक शेयर किया गया सीक्रेट कोड बन जाता है और ऐलिस और बॉब को सिर्फ़ अपनी सार्वजनिक कुंजी शेयर करनी होती है. अब बॉब और ऐलिस, एक-दूसरे के मैसेज को एन्क्रिप्ट और डिक्रिप्ट करने के लिए, 'X' का इस्तेमाल कर सकते हैं.

मेरी जानकारी के मुताबिक, ईसीडीएच, कर्व की प्रॉपर्टी तय करता है. इन प्रॉपर्टी की मदद से, शेयर किया गया गुप्त पासवर्ड 'X' बनाने की "सुविधा" मिलती है.

यह ईसीडीएच के बारे में खास जानकारी है. अगर आपको ज़्यादा जानना है, तो हमारा सुझाव है कि यह वीडियो देखें.

कोड के मामले में, ज़्यादातर भाषाओं / प्लैटफ़ॉर्म में लाइब्रेरी होती हैं, ताकि इन कुंजियों को आसानी से जनरेट किया जा सके.

नोड में, हम ये काम करेंगे:

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

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

HKDF: एचएमएसी पर आधारित पासकोड बनाने का फ़ंक्शन

Wikipedia पर HKDF के बारे में कम शब्दों में जानकारी दी गई है:

एचकेडीएफ़, एचएमएसी पर आधारित कुंजी डेरिवेशन फ़ंक्शन है. यह किसी भी कमज़ोर कुंजी के कॉन्टेंट को क्रिप्टोग्राफ़िक तौर पर मज़बूत कुंजी के कॉन्टेंट में बदल देता है. उदाहरण के लिए, इसका इस्तेमाल डिफ़ी हेलमैन के ज़रिए शेयर किए गए गोपनीय डेटा को, एन्क्रिप्शन, इंटिग्रिटी की जांच या पुष्टि करने के लिए इस्तेमाल किए जाने वाले कुंजी कॉन्टेंट में बदलने के लिए किया जा सकता है.

असल में, HKDF ऐसा इनपुट लेगा जो खास तौर पर सुरक्षित नहीं है और उसे ज़्यादा सुरक्षित बना देगा.

इस एन्क्रिप्शन की खास बात यह है कि इसमें हैश एल्गोरिदम के तौर पर SHA-256 का इस्तेमाल करना ज़रूरी है. साथ ही, वेब पुश में HKDF के लिए बनी कुंजियों की लंबाई 256 बिट (32 बाइट) से ज़्यादा नहीं होनी चाहिए.

नोड में इसे इस तरह लागू किया जा सकता है:

// 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);
}

इस उदाहरण के कोड के लिए, Mat Scale के लेख का इस्तेमाल किया गया है.

इसमें ECDH और HKDF के बारे में कम जानकारी दी गई है.

ईसीडीएच, सार्वजनिक कुंजियों को शेयर करने और शेयर किया जाने वाला सीक्रेट कोड जनरेट करने का एक सुरक्षित तरीका है. HKDF, असुरक्षित डेटा को सुरक्षित बनाने का एक तरीका है.

इसका इस्तेमाल, हमारे पेलोड को एन्क्रिप्ट (सुरक्षित) करने के दौरान किया जाएगा. अब आइए देखें कि हम इनपुट के तौर पर क्या लेते हैं और उसे कैसे एन्क्रिप्ट करते हैं.

इनपुट

जब हमें किसी उपयोगकर्ता को पेलोड के साथ पुश मैसेज भेजना हो, तो हमें तीन इनपुट की ज़रूरत होती है:

  1. पेलोड.
  2. PushSubscription से auth का रहस्य.
  3. PushSubscription में मौजूद p256dh बटन.

हमें पता चला है कि auth और p256dh वैल्यू, PushSubscription से वापस लाई जा रही हैं. हालांकि, एक छोटा रिमाइंडर देने के लिए, हमें सदस्यता के लिए इन वैल्यू की ज़रूरत होगी:

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

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

auth वैल्यू को गोपनीय माना जाना चाहिए और इसे अपने ऐप्लिकेशन से बाहर शेयर नहीं किया जाना चाहिए.

p256dh कुंजी एक सार्वजनिक कुंजी है. इसे कभी-कभी क्लाइंट सार्वजनिक कुंजी भी कहा जाता है. यहां हम p256dh को सदस्यता की सार्वजनिक कुंजी के तौर पर इस्तेमाल करेंगे. सदस्यता की सार्वजनिक कुंजी, ब्राउज़र जनरेट करता है. ब्राउज़र, निजी पासकोड को गुप्त रखेगा और इसका इस्तेमाल, पेलोड को डिक्रिप्ट करने के लिए करेगा.

इन तीन वैल्यू, auth, p256dh, और payload को इनपुट के तौर पर डालना ज़रूरी है. एन्क्रिप्शन की प्रोसेस के नतीजे के तौर पर, एन्क्रिप्ट किया गया पेलोड, सॉल्ट वैल्यू, और सार्वजनिक कुंजी मिलेगी. इसका इस्तेमाल सिर्फ़ डेटा को एन्क्रिप्ट करने के लिए किया जाता है.

नमक

सॉल्ट, 16 बाइट का कोई भी डेटा हो सकता है. NodeJS में, नमक बनाने के लिए हम यह तरीका अपनाते हैं:

const salt = crypto.randomBytes(16);

सार्वजनिक / निजी कुंजियां

सार्वजनिक और निजी कुंजियों को P-256 एलिप्टिक कर्व का इस्तेमाल करके जनरेट किया जाना चाहिए. हम Node में ऐसा इस तरह करेंगे:

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

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

हम इन कुंजियों को "स्थानीय कुंजियां" कहेंगे. इनका इस्तेमाल सिर्फ़ एन्क्रिप्शन के लिए किया जाता है. इनका ऐप्लिकेशन सर्वर कुंजियों से कोई लेना-देना नहीं है.

इनपुट के तौर पर, पेलोड, पुष्टि करने के लिए इस्तेमाल होने वाला पासवर्ड, और सदस्यता की सार्वजनिक कुंजी के साथ-साथ, हाल ही में जनरेट की गई नमक और स्थानीय कुंजियों के सेट की मदद से, हम एन्क्रिप्शन (सुरक्षित) करने के लिए तैयार हैं.

शेयर किया गया सीक्रेट

पहला चरण, सदस्यता की सार्वजनिक कुंजी और हमारी नई निजी कुंजी का इस्तेमाल करके, शेयर किया जाने वाला सीक्रेट कोड बनाना है. क्या आपको ऐलिस और बॉब के साथ ईसीडीएच के बारे में बताई गई बात याद है? बस इतना ही).

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

इसका इस्तेमाल अगले चरण में, सूडो रैंडम पासकोड (पीआरके) का हिसाब लगाने के लिए किया जाता है.

सूडो रैंडम कुंजी

सूडो रैंडम पासकोड (पीआरके), पुश सदस्यता के पुष्टि करने वाले पासकोड और शेयर किए गए उस पासकोड का कॉम्बिनेशन होता है जिसे हमने अभी बनाया है.

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

आपको शायद यह जानना हो कि Content-Encoding: auth\0 स्ट्रिंग का क्या काम है. कम शब्दों में, इसका कोई खास मकसद नहीं है. हालांकि, ब्राउज़र किसी इनकमिंग मैसेज को डिक्रिप्ट कर सकते हैं और कॉन्टेंट-कोडिंग की उम्मीद कर सकते हैं. \0, बफ़र के आखिर में 0 वैल्यू वाला एक बाइट जोड़ता है. मैसेज को डिक्रिप्ट करने वाले ब्राउज़र को उम्मीद होती है कि कॉन्टेंट को एन्कोड करने के लिए इतनी बाइट की ज़रूरत होगी. इसके बाद, 0 वैल्यू वाला एक बाइट और फिर एन्क्रिप्ट किया गया डेटा होगा.

हमारी स्यूडो रैंडम पासकोड, एचकेडीएफ़ की मदद से पुष्टि, शेयर किया गया सीक्रेट कोड, और कोड में बदली गई जानकारी को चला रही है. इसका मतलब है कि इसे क्रिप्टोग्राफ़िक तरीके से ज़्यादा मज़बूत बनाया जा रहा है.

संदर्भ

"कॉन्टेक्स्ट", बाइट का एक सेट होता है. इसका इस्तेमाल, एन्क्रिप्शन ब्राउज़र में बाद में दो वैल्यू का हिसाब लगाने के लिए किया जाता है. यह असल में बाइट का एक कलेक्शन है, जिसमें सदस्यता की सार्वजनिक कुंजी और स्थानीय सार्वजनिक कुंजी होती है.

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,
]);

आखिरी कॉन्टेक्स्ट बफ़र एक लेबल होता है. इसमें सदस्यता के सार्वजनिक पासकोड में मौजूद बाइट की संख्या के बाद, पासकोड होता है. इसके बाद, स्थानीय सार्वजनिक पासकोड में मौजूद बाइट की संख्या के बाद, पासकोड होता है.

इस कॉन्टेक्स्ट वैल्यू का इस्तेमाल, नॉन्स और कॉन्टेंट एन्क्रिप्शन पासकोड (सीईके) बनाने के लिए किया जा सकता है.

कॉन्टेंट एन्क्रिप्शन पासकोड और नॉन्स

नॉन्स एक ऐसी वैल्यू है जो रीप्ले अटैक को रोकती है. ऐसा इसलिए, क्योंकि इसका इस्तेमाल सिर्फ़ एक बार किया जाना चाहिए.

कॉन्टेंट एन्क्रिप्शन पासकोड (सीईके) वह पासकोड होता है जिसका इस्तेमाल, हमारे पेलोड को एन्क्रिप्ट करने के लिए किया जाएगा.

सबसे पहले, हमें नॉन्स और सीईके के लिए डेटा के बाइट बनाने होंगे. यह सिर्फ़ कॉन्टेंट को एन्कोड करने वाली स्ट्रिंग होती है. इसके बाद, उसमें वह कॉन्टेक्स्ट बफ़र जोड़ा जाता है जिसका हमने अभी हिसाब लगाया है:

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]);

इस जानकारी को HKDF की मदद से चलाया जाता है. इसमें नॉन्स और सीईकेआईनफ़ो के साथ साल्ट और पीआरके को जोड़ा जाता है:

// 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);

इससे हमें नॉन्स और कॉन्टेंट एन्क्रिप्शन पासकोड मिलता है.

एन्क्रिप्ट (सुरक्षित) करना

अब हमारे पास कॉन्टेंट एन्क्रिप्शन पासकोड है, इसलिए हम पेलोड को एन्क्रिप्ट कर सकते हैं.

हम कॉन्टेंट एन्क्रिप्शन पासकोड के तौर पर एईएस128 सिफर बनाते हैं, क्योंकि पासकोड एक इनिशलाइज़ेशन वेक्टर होता है.

Node में, यह इस तरह किया जाता है:

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

अपने पेलोड को एन्क्रिप्ट करने से पहले, हमें यह तय करना होगा कि पेलोड के आगे कितनी पैडिंग जोड़नी है. हम पैडिंग इसलिए जोड़ना चाहते हैं, ताकि पैकेज के साइज़ के आधार पर, मैसेज के "टाइप" का पता लगाने वाले लोगों को रोका जा सके.

अतिरिक्त पैडिंग की लंबाई बताने के लिए, आपको दो बाइट पैडिंग जोड़नी होगी.

उदाहरण के लिए, अगर आपने कोई पैडिंग नहीं जोड़ी है, तो आपके पास वैल्यू 0 वाले दो बाइट होंगे.इसका मतलब है कि कोई पैडिंग मौजूद नहीं है. इन दो बाइट के बाद, आपको पेलोड पढ़ना होगा. अगर आपने पांच बाइट पैडिंग जोड़ी है, तो पहले दो बाइट की वैल्यू पांच होगी. इसलिए, उपभोक्ता पांच और बाइट पढ़ेगा और फिर पेलोड पढ़ना शुरू करेगा.

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

इसके बाद, हम अपने पैडिंग और पेलोड को इस सिफर से चलाते हैं.

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()]);

अब हमारे पास एन्क्रिप्ट (सुरक्षित) किया गया पेलोड है. वाह!

अब यह तय करना है कि इस पेलोड को पुश सेवा पर कैसे भेजा जाए.

एन्क्रिप्ट (सुरक्षित) किए गए पेलोड हेडर और मुख्य हिस्सा

इस एन्क्रिप्ट किए गए पेलोड को पुश सेवा पर भेजने के लिए, हमें अपने POST अनुरोध में कुछ अलग हेडर तय करने होंगे.

एन्क्रिप्शन हेडर

'एन्क्रिप्शन' हेडर में, पेलोड को एन्क्रिप्ट करने के लिए इस्तेमाल किया जाने वाला सॉल्ट होना चाहिए.

16 बाइट का साल्ट, base64 यूआरएल सेफ़ कोड में होना चाहिए. साथ ही, इसे एन्क्रिप्शन हेडर में जोड़ा जाना चाहिए. जैसे:

Encryption: salt=[URL Safe Base64 Encoded Salt]

क्रिप्टो-की हेडर

हमने देखा कि Crypto-Key हेडर का इस्तेमाल, 'ऐप्लिकेशन सर्वर पासकोड' सेक्शन में किया जाता है, ताकि सार्वजनिक ऐप्लिकेशन सर्वर पासकोड शामिल किया जा सके.

इस हेडर का इस्तेमाल, पेलोड को एन्क्रिप्ट करने के लिए इस्तेमाल की जाने वाली स्थानीय सार्वजनिक कुंजी को शेयर करने के लिए भी किया जाता है.

इससे मिलने वाला हेडर कुछ ऐसा दिखता है:

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

कॉन्टेंट का टाइप, लंबाई, और कोड में बदलने वाले हेडर

Content-Length हेडर, एन्क्रिप्ट किए गए पेलोड में बाइट की संख्या होती है. 'Content-Type' और 'Content-Encoding' हेडर की वैल्यू में बदलाव नहीं किया जा सकता. यह नीचे दिखाया गया है.

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

इन हेडर के सेट होने के बाद, हमें अपने अनुरोध के मुख्य हिस्से के तौर पर एन्क्रिप्ट (सुरक्षित) किया गया पेलोड भेजना होगा. ध्यान दें कि Content-Type, application/octet-stream पर सेट है. ऐसा इसलिए है, क्योंकि एन्क्रिप्ट (सुरक्षित) किए गए पेलोड को बाइट की स्ट्रीम के तौर पर भेजा जाना चाहिए.

NodeJS में, हम ऐसा इस तरह करेंगे:

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

क्या आपको और हेडर चाहिए?

हमने JWT / ऐप्लिकेशन सर्वर कुंजियों के लिए इस्तेमाल किए गए हेडर के बारे में बताया है.जैसे, पुश सेवा की मदद से ऐप्लिकेशन की पहचान कैसे करें. साथ ही, हमने एन्क्रिप्ट किए गए पेलोड को भेजने के लिए इस्तेमाल किए गए हेडर के बारे में भी बताया है.

कुछ और हेडर भी होते हैं, जिनका इस्तेमाल सेवाएं, भेजे गए मैसेज के व्यवहार में बदलाव करने के लिए करती हैं. इनमें से कुछ हेडर ज़रूरी हैं, जबकि कुछ वैकल्पिक हैं.

TTL हेडर

ज़रूरी है

TTL (या 'लाइव रहने का समय') एक पूर्णांक है. इससे यह पता चलता है कि पुश मैसेज डिलीवर होने से पहले, पुश सेवा पर कितने सेकंड तक मौजूद रहना चाहिए. TTL की समयसीमा खत्म होने पर, मैसेज को पुश सेवा की सूची से हटा दिया जाएगा और उसे डिलीवर नहीं किया जाएगा.

TTL: [Time to live in seconds]

अगर TTL को शून्य पर सेट किया जाता है, तो पुश सेवा तुरंत मैसेज डिलीवर करने की कोशिश करेगी. हालांकि, अगर डिवाइस तक नहीं पहुंचा जा सकता, तो आपका मैसेज पुश सेवा की सूची से तुरंत हटा दिया जाएगा.

तकनीकी तौर पर, पुश सेवा चाहे, तो पुश मैसेज के TTL को कम कर सकती है. पुश सेवा से मिले जवाब में TTL हेडर की जांच करके, यह पता लगाया जा सकता है कि ऐसा हुआ है या नहीं.

विषय

ज़रूरी नहीं

विषय, स्ट्रिंग होती हैं. इनका इस्तेमाल, किसी मैसेज को नए मैसेज से बदलने के लिए किया जा सकता है. हालांकि, इसके लिए ज़रूरी है कि दोनों मैसेज के विषय एक जैसे हों.

यह सुविधा तब काम आती है, जब डिवाइस के ऑफ़लाइन होने पर कई मैसेज भेजे जाते हैं और आपको उपयोगकर्ता को सिर्फ़ डिवाइस के चालू होने पर नया मैसेज दिखाना है.

अत्यावश्यकता

ज़रूरी नहीं

'ज़रूरत' से पुश सेवा को पता चलता है कि उपयोगकर्ता के लिए कोई मैसेज कितना ज़रूरी है. पुश सेवा इस जानकारी का इस्तेमाल करके, उपयोगकर्ता के डिवाइस की बैटरी लाइफ़ को बचा सकती है. इसके लिए, वह सिर्फ़ बैटरी कम होने पर ज़रूरी मैसेज के लिए डिवाइस को चालू करती है.

हेडर वैल्यू को इस तरह से तय किया जाता है, जैसा कि यहां दिखाया गया है. डिफ़ॉल्ट वैल्यूnormal है.

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

सभी जानकारी एक साथ

अगर आपको इस बारे में और सवाल पूछने हैं कि यह सब कैसे काम करता है, तो web-push-libs org पर जाकर देखें कि लाइब्रेरी, पुश मैसेज को कैसे ट्रिगर करती हैं.

एन्क्रिप्ट किया गया पेलोड और ऊपर दिए गए हेडर मिलने के बाद, आपको PushSubscription में endpoint पर सिर्फ़ एक पोस्ट अनुरोध करना होगा.

तो इस POST अनुरोध के जवाब का क्या करें?

पुश सेवा से मिला जवाब

पुश सेवा का अनुरोध करने के बाद, आपको जवाब का स्टेटस कोड देखना होगा. इससे आपको पता चलेगा कि अनुरोध पूरा हुआ है या नहीं.

स्थिति कोड ब्यौरा
201 बनाया गया. पुश मैसेज भेजने का अनुरोध मिल गया है और उसे स्वीकार कर लिया गया है.
429 बहुत ज़्यादा अनुरोध. इसका मतलब है कि आपके ऐप्लिकेशन सर्वर पर, पुश सेवा के लिए तय की गई दर की सीमा पूरी हो गई है. पुश सेवा में 'फिर से कोशिश करें' हेडर शामिल होना चाहिए. इससे यह पता चलता है कि अगला अनुरोध कब किया जा सकता है.
400 अनुरोध अमान्य है. आम तौर पर, इसका मतलब है कि आपका कोई हेडर अमान्य है या उसका फ़ॉर्मैट गलत है.
404 नहीं मिला. इससे पता चलता है कि सदस्यता की समयसीमा खत्म हो गई है और उसका इस्तेमाल नहीं किया जा सकता. इस मामले में, आपको `PushSubscription` को मिटा देना चाहिए और क्लाइंट के उपयोगकर्ता की फिर से सदस्यता लेने का इंतज़ार करना चाहिए.
410 नहीं दिख रहा. सदस्यता अब मान्य नहीं है और इसे ऐप्लिकेशन सर्वर से हटा दिया जाना चाहिए. इसे दोहराने के लिए, `PushSubscription` पर `unsubscribe()` को कॉल करें.
413 पेलोड का साइज़ बहुत बड़ा है. पुश सेवा के लिए, कम से कम 4096 बाइट (या 4 केबी) का पेलोड होना चाहिए.

एचटीटीपी स्टेटस कोड के बारे में ज़्यादा जानकारी के लिए, वेब पुश स्टैंडर्ड (RFC8030) भी पढ़ा जा सकता है.

आगे क्या करना है

कोड लैब