ওয়েব পুশ প্রোটোকল

আমরা দেখেছি যে কীভাবে পুশ মেসেজ ট্রিগার করার জন্য লাইব্রেরি ব্যবহার করা যেতে পারে, কিন্তু এই লাইব্রেরিগুলি ঠিক কী করছে?

এগুলি নেটওয়ার্ক অনুরোধ তৈরি করে এবং এই ধরনের অনুরোধ সঠিক ফর্ম্যাটে আছে কিনা তা নিশ্চিত করে। এই নেটওয়ার্ক রিকোয়েস্টকে সংজ্ঞায়িত করে এমন স্পেসিফিকেশন হল ওয়েব পুশ প্রোটোকল।

আপনার সার্ভার থেকে পুশ পরিষেবায় পুশ মেসেজ পাঠানোর ডায়াগ্রাম।

এই বিভাগে, সার্ভার কীভাবে অ্যাপ্লিকেশন সার্ভার 'কী'-এর মাধ্যমে নিজেকে শনাক্ত করতে পারে এবং কীভাবে এনক্রিপ্ট করা পেলোড এবং সংশ্লিষ্ট ডেটা পাঠানো হয় তা বর্ণনা করা হয়েছে।

This isn't a pretty side of web push and I'm no expert at encryption, but let's look through each piece since it's handy to know what these libraries are doing under the hood.

অ্যাপ্লিকেশন সার্ভার কী

আমরা কোনও ব্যবহারকারীকে সাবস্ক্রাইব করলে, আমরা একটি applicationServerKey পাস করি। এই 'কী' পুশ পরিষেবায় পাস করা হয় এবং যে অ্যাপ্লিকেশন ব্যবহারকারীকে সাবস্ক্রাইব করেছে সেটিই পুশ মেসেজ ট্রিগার করছে কিনা তা চেক করতে ব্যবহার করা হয়।

আমরা যখন কোনও পুশ মেসেজ ট্রিগার করি, তখন আমরা এক সেট হেডার পাঠাই যা পুশ পরিষেবাকে অ্যাপ্লিকেশনটি যাচাই করার অনুমতি দেয়। (এটি VAPID স্পেসিফিকেশন দ্বারা সংজ্ঞায়িত করা হয়।)

এইসবের আসল অর্থ কী এবং ঠিক কী হয়? অ্যাপ্লিকেশন সার্ভার যাচাইকরণের জন্য এইসব ধাপ অনুসরণ করা হয়:

  1. অ্যাপ্লিকেশন সার্ভার কিছু JSON তথ্যকে তার ব্যক্তিগত অ্যাপ্লিকেশন কী দিয়ে সাইন করে।
  2. এই স্বাক্ষরিত তথ্য POST অনুরোধে হেডার হিসেবে পুশ পরিষেবায় পাঠানো হয়।
  3. পুশ পরিষেবাটি pushManager.subscribe() থেকে প্রাপ্ত সর্বজনীন কী ব্যবহার করে যাচাই করে যে প্রাপ্ত তথ্যটি সর্বজনীন কী-এর সাথে সম্পর্কিত ব্যক্তিগত কী দ্বারা স্বাক্ষরিত হয়েছে কিনা। মনে রাখবেন: সর্বজনীন কী হল সাবস্ক্রাইব কলে পাস করা applicationServerKey।
  4. স্বাক্ষরিত তথ্য সঠিক হলে পুশ পরিষেবা ব্যবহারকারীকে পুশ মেসেজ পাঠায়।

এই তথ্য প্রবাহের একটি উদাহরণ নিচে দেওয়া হল। (পাবলিক ও ব্যক্তিগত কী চিহ্নিত করতে নিচে বাঁদিকে দেওয়া লেজেন্ড দেখুন।)

মেসেজ পাঠানোর সময় ব্যক্তিগত অ্যাপ্লিকেশন সার্ভার কী কীভাবে ব্যবহার করা হয় তার ইলাস্ট্রেশন।

অনুরোধের হেডারে যোগ করা "স্বাক্ষরিত তথ্য" হল JSON ওয়েব টোকেন।

JSON ওয়েব টোকেন

JSON ওয়েব টোকেন (বা সংক্ষেপে JWT) হল থার্ড-পার্টিকে মেসেজ পাঠানোর এমন একটি উপায় যার মাধ্যমে প্রাপক যাচাই করতে পারেন যে মেসেজটি কে পাঠিয়েছেন।

কোনও থার্ড-পার্টি মেসেজ পেলে, তাকে প্রেরকের সর্বজনীন কী পেতে হবে এবং JWT-এর স্বাক্ষর যাচাই করতে সেটি ব্যবহার করতে হবে। স্বাক্ষরটি সঠিক হলে, JWT অবশ্যই ম্যাচিং ব্যক্তিগত কী দিয়ে স্বাক্ষরিত হতে হবে, তাই এটি প্রত্যাশিত প্রেরকের কাছ থেকে আসতে হবে।

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 ইন্টারসেপ্ট করলেও সেটি আবার ব্যবহার করতে বাধা দেয়। মেয়াদ শেষ হওয়ার সময় সেকেন্ডে টাইমস্ট্যাম্প হিসেবে দেখানো হয় এবং এটি ২৪ ঘণ্টার বেশি হতে পারবে না।

Node.js-এ মেয়াদ শেষ হওয়ার তারিখ সেট করা হয়:

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

এটি ২৪ ঘণ্টার পরিবর্তে ১২ ঘণ্টা যাতে অ্যাপ্লিকেশন পাঠানো ও পুশ পরিষেবার মধ্যে ঘড়ির পার্থক্যের কারণে কোনও সমস্যা এড়ানো যায়।

অবশেষে, sub ভ্যালু হয় URL অথবা mailto ইমেল আইডি হতে হবে। এটি করা হয় যাতে কোনও পুশ পরিষেবার প্রেরকের সাথে যোগাযোগ করার প্রয়োজন হলে, এটি JWT থেকে যোগাযোগের তথ্য খুঁজে পেতে পারে। (এই কারণেই ওয়েব-পুশ লাইব্রেরির জন্য একটি ইমেল আইডি প্রয়োজন)।

JWT তথ্যের মতোই, JWT ডেটা URL-এর জন্য নিরাপদ base64 স্ট্রিং হিসেবে এনকোড করা হয়।

তৃতীয় স্ট্রিং, অর্থাৎ স্বাক্ষর, প্রথম দুটি স্ট্রিং (JWT তথ্য ও JWT ডেটা) নিয়ে ডট অক্ষর দিয়ে যুক্ত করে তৈরি করা হয়, যাকে আমরা "স্বাক্ষরবিহীন টোকেন" বলি এবং এটিতে স্বাক্ষর করা হয়।

স্বাক্ষর করার প্রসেসে ES256 ব্যবহার করে "স্বাক্ষর না করা টোকেন" এনক্রিপ্ট করতে হয়। JWT spec অনুযায়ী, ES256 হল "P-256 কার্ভ এবং SHA-256 হ্যাশ অ্যালগরিদম ব্যবহার করে ECDSA"-এর সংক্ষিপ্ত রূপ। ওয়েব ক্রিপ্টো ব্যবহার করে আপনি এইভাবে স্বাক্ষর তৈরি করতে পারবেন:

// 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 (অর্থাৎ, ডট দ্বারা যুক্ত তিনটি স্ট্রিং), WebPush সহ Authorization হেডার হিসেবে ওয়েব পুশ পরিষেবায় পাঠানো হয়, যেমন:

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

এছাড়াও, ওয়েব পুশ প্রোটোকল অনুযায়ী, সর্বজনীন অ্যাপ্লিকেশন সার্ভার কী অবশ্যই Crypto-Key হেডারে URL-এর জন্য নিরাপদ base64 এনকোড করা স্ট্রিং হিসেবে পাঠাতে হবে, যার শুরুতে p256ecdsa= থাকবে।

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

পেলোড এনক্রিপশন

এরপর, পুশ মেসেজের সাথে কীভাবে পেলোড পাঠানো যায় তা দেখা যাক, যাতে আমাদের ওয়েব অ্যাপ পুশ মেসেজ পেলে, এটি যে ডেটা পায় তা অ্যাক্সেস করতে পারে।

অন্যান্য পুশ পরিষেবা ব্যবহার করেছেন এমন যে কেউ একটি সাধারণ প্রশ্ন করেন যে, কেন ওয়েব পুশ পেলোড এনক্রিপ্ট করতে হবে? নেটিভ অ্যাপের মাধ্যমে, পুশ মেসেজ প্লেন টেক্সট হিসেবে ডেটা পাঠাতে পারে।

ওয়েব পুশের একটি সুবিধা হল, সব পুশ পরিষেবা একই API (ওয়েব পুশ প্রোটোকল) ব্যবহার করে, তাই ডেভেলপারদের পুশ পরিষেবা নিয়ে চিন্তা করতে হয় না। আমরা সঠিক ফর্ম্যাটে অনুরোধ করে পুশ মেসেজ পাঠানোর আশা করতে পারি। এর খারাপ দিক হল, ডেভেলপাররা সম্ভবত এমন পুশ পরিষেবাতে মেসেজ পাঠাতে পারেন যা বিশ্বাসযোগ্য নয়। পে-লোড এনক্রিপ্ট করার মাধ্যমে, পুশ পরিষেবা পাঠানো ডেটা পড়তে পারে না। শুধুমাত্র ব্রাউজারই তথ্য ডিক্রিপ্ট করতে পারে। এটি ব্যবহারকারীর ডেটা সুরক্ষিত রাখে।

পে-লোডের এনক্রিপশন মেসেজ এনক্রিপশন স্পেসিফিকেশন-এ উল্লেখ করা আছে।

পুশ মেসেজ পেলোড এনক্রিপ্ট করার নির্দিষ্ট ধাপগুলি দেখার আগে, এনক্রিপশন প্রসেসের সময় ব্যবহার করা হবে এমন কিছু কৌশল আমাদের কভার করা উচিত। (পুশ এনক্রিপশন সম্পর্কে ম্যাট স্কেলের চমৎকার নিবন্ধের জন্য তাকে অনেক ধন্যবাদ।)

ECDH এবং HKDF

এনক্রিপশন প্রসেস জুড়ে ECDH ও HKDF, দুটিই ব্যবহার করা হয় এবং তথ্য এনক্রিপ্ট করার উদ্দেশ্যে সুবিধা প্রদান করে।

ECDH: উপবৃত্তাকার কার্ভ ডিফি-হেলম্যান কী এক্সচেঞ্জ

ধরুন, অ্যালিস ও বব নামে দুই ব্যক্তি তথ্য শেয়ার করতে চান। অমিত ও বিমল, দুজনেরই নিজস্ব পাবলিক ও ব্যক্তিগত কী আছে। অমিত ও বিমল একে অপরের সাথে তাদের পাবলিক কী শেয়ার করে।

ECDH-এর মাধ্যমে তৈরি করা কী-এর একটি উপযোগী বৈশিষ্ট্য হল, অ্যালিস তার ব্যক্তিগত কী এবং ববের সর্বজনীন কী ব্যবহার করে গোপন মান 'X' তৈরি করতে পারে। ববও একই কাজ করতে পারেন, তার ব্যক্তিগত কী এবং অ্যালিসের সর্বজনীন কী ব্যবহার করে তিনি স্বাধীনভাবে একই মান 'X' তৈরি করতে পারেন। এর ফলে 'X' একটি শেয়ার্ড সিক্রেট হয়ে যায় এবং অমিত ও বিমলকে শুধু তাদের সর্বজনীন কী শেয়ার করতে হয়। এখন শত্রুঘ্ন ও শ্রুতকীর্তি 'X' ব্যবহার করে নিজেদের মধ্যে মেসেজ এনক্রিপ্ট ও ডিক্রিপ্ট করতে পারবেন।

আমার জানা মতে, ECDH কার্ভের প্রপার্টিকে সংজ্ঞায়িত করে যা শেয়ার্ড সিক্রেট 'X' তৈরি করার এই "ফিচার" অনুমতি দেয়।

এটি ECDH-এর একটি উচ্চ স্তরের ব্যাখ্যা; আপনি যদি আরও জানতে চান, তাহলে আমি আরও বিস্তারিত ECDH ওভারভিউ ভিডিও দেখার সাজেশন দিচ্ছি।

কোডের ক্ষেত্রে; বেশিরভাগ ভাষা / প্ল্যাটফর্ম লাইব্রেরির সাথে আসে যাতে এই কীগুলি তৈরি করা সহজ হয়।

নোডে আমরা নিম্নলিখিত কাজগুলি করব:

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

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

HKDF: HMAC ভিত্তিক কী ডেরিভেশন ফাংশন

Wikipedia-তে HKDF-এর সংক্ষিপ্ত বিবরণ আছে:

HKDF হল HMAC ভিত্তিক কী ডেরিভেশন ফাংশন যা যেকোনও দুর্বল কী মেটেরিয়ালকে ক্রিপ্টোগ্রাফিকভাবে শক্তিশালী কী মেটেরিয়ালে রূপান্তরিত করে। এটি ব্যবহার করা যেতে পারে, যেমন, ডিফি হেলম্যান এক্সচেঞ্জ করা শেয়ার্ড সিক্রেটকে এনক্রিপশন, ইন্টিগ্রিটি চেকিং বা যাচাইকরণে ব্যবহারের জন্য উপযুক্ত কী মেটেরিয়ালে কনভার্ট করতে।

মূলত, HKDF এমন ইনপুট নেবে যা বিশেষভাবে সুরক্ষিত নয় এবং এটিকে আরও সুরক্ষিত করবে।

এই এনক্রিপশন সংজ্ঞায়িত করার স্পেসিফিকেশনে আমাদের হ্যাশ অ্যালগরিদম হিসেবে SHA-256 ব্যবহার করতে হয় এবং ওয়েব পুশে HKDF-এর জন্য ফলাফল হিসেবে পাওয়া কী ২৫৬ বিটের (৩২ বাইট) বেশি হওয়া চলবে না।

নোডে এটি এইভাবে প্রয়োগ করা যেতে পারে:

// 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-কে কভার করে।

ECDH হল সর্বজনীন কী শেয়ার করা এবং শেয়ার্ড সিক্রেট তৈরি করার একটি নিরাপদ উপায়। 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 প্রয়োজন এবং এনক্রিপশন প্রসেসের ফলাফল হবে এনক্রিপটেড পেলোড, সল্ট ভ্যালু এবং শুধু ডেটা এনক্রিপ্ট করার জন্য ব্যবহৃত পাবলিক কী।

নুন

সল্ট ১৬ বাইটের র‍্যান্ডম ডেটা হতে হবে। NodeJS-এ, সল্ট তৈরি করতে আমরা নিম্নলিখিত কাজগুলি করব:

const salt = crypto.randomBytes(16);

পাবলিক / ব্যক্তিগত 'কী'

পাবলিক ও ব্যক্তিগত কী P-256 উপবৃত্তাকার কার্ভ ব্যবহার করে জেনারেট করতে হবে, যা আমরা Node-এ এইভাবে করব:

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

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

আমরা এই 'কী'গুলিকে "লোকাল কী" হিসেবে উল্লেখ করব। এগুলি শুধুমাত্র এনক্রিপশনের জন্য ব্যবহার করা হয় এবং অ্যাপ্লিকেশন সার্ভার কী-এর সাথে এর কোনও সম্পর্ক নেই।

ইনপুট হিসেবে পে-লোড, অথেন্টিকেশন সিক্রেট ও সাবস্ক্রিপশন পাবলিক কী এবং নতুন তৈরি করা সল্ট ও লোকাল কী-এর সেট ব্যবহার করে আমরা এনক্রিপশন করার জন্য প্রস্তুত।

শেয়ার্ড সিক্রেট

প্রথম ধাপ হল সাবস্ক্রিপশন সর্বজনীন কী এবং আমাদের নতুন ব্যক্তিগত কী ব্যবহার করে একটি শেয়ার্ড সিক্রেট তৈরি করা (অ্যালিস এবং ববের সাথে ECDH ব্যাখ্যা মনে আছে? ঠিক এইরকম)।

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

সিউডো-র‍্যান্ডম কী (PRK) গণনা করার জন্য পরবর্তী ধাপে এটি ব্যবহার করা হয়।

সিউডো র‍্যান্ডম কী

সিউডো র‍্যান্ডম কী (PRK) হল পুশ সাবস্ক্রিপশনের অথেন্টিকেশন সিক্রেট এবং আমাদের তৈরি করা শেয়ার্ড সিক্রেটের কম্বিনেশন।

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

আপনি হয়ত ভাবছেন যে Content-Encoding: auth\0 স্ট্রিংটি কীসের জন্য। সংক্ষেপে, এর কোনও স্পষ্ট উদ্দেশ্য নেই, যদিও ব্রাউজারগুলি ইনকামিং মেসেজ ডিক্রিপ্ট করতে পারে এবং প্রত্যাশিত কন্টেন্ট-এনকোডিং খুঁজতে পারে। \0 বাফারের শেষে ০ ভ্যালু সহ একটি বাইট যোগ করে। মেসেজ ডিক্রিপ্ট করার সময় ব্রাউজার এটিই আশা করে। কন্টেন্ট এনকোডিংয়ের জন্য ব্রাউজার এত বাইট আশা করে, যার পরে 0 ভ্যালু সহ একটি বাইট এবং তারপরে এনক্রিপ্ট করা ডেটা থাকে।

আমাদের সিউডো-র‍্যান্ডম কী HKDF-এর মাধ্যমে শুধু অথেন্টিকেশন, শেয়ার্ড সিক্রেট ও এনকোডিং তথ্যের একটি অংশ রান করছে (অর্থাৎ, এটিকে ক্রিপ্টোগ্রাফিকভাবে আরও শক্তিশালী করছে)।

প্রসঙ্গ

"কনটেক্সট" হল বাইটের একটি সেট যা পরে এনক্রিপশন ব্রাউজারে দুটি মান গণনা করতে ব্যবহৃত হয়। এটি মূলত সাবস্ক্রিপশন পাবলিক কী এবং লোকাল পাবলিক কী সহ বাইটের একটি অ্যারে।

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

চূড়ান্ত কনটেক্সট বাফার হল একটি লেবেল, সাবস্ক্রিপশন সর্বজনীন কীতে বাইটের সংখ্যা, তারপরে কী, তারপরে লোকাল সর্বজনীন কীতে বাইটের সংখ্যা, তারপরে কী।

এই কনটেক্সট ভ্যালুর মাধ্যমে আমরা এটি ননস ও কন্টেন্ট এনক্রিপশন কী (CEK) তৈরি করার জন্য ব্যবহার করতে পারি।

কন্টেন্ট এনক্রিপশন কী ও ননস

নন্স হল এমন একটি ভ্যালু যা রিপ্লে অ্যাটাক প্রতিরোধ করে, কারণ এটি শুধুমাত্র একবারই ব্যবহার করা উচিত।

কন্টেন্ট এনক্রিপশন কী (CEK) হল সেই কী যা শেষ পর্যন্ত আমাদের পেলোড এনক্রিপ্ট করতে ব্যবহার করা হবে।

প্রথমে আমাদের ননস ও CEK-এর জন্য ডেটার বাইট তৈরি করতে হবে, যা হল আমাদের হিসেব করা কনটেক্সট বাফারের পরে কন্টেন্ট এনকোডিং স্ট্রিং:

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-এর মাধ্যমে রান করা হয় যা সল্ট ও PRK-কে nonceInfo ও 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);

এটি আমাদের ননস এবং কন্টেন্ট এনক্রিপশন কী দেয়।

এনক্রিপশন করা

এখন আমাদের কাছে কন্টেন্ট এনক্রিপশন কী আছে, তাই আমরা পেলোড এনক্রিপ্ট করতে পারি।

আমরা কন্টেন্ট এনক্রিপশন কী ব্যবহার করে AES128 সাইফার তৈরি করি কী হিসেবে এবং ননস হল একটি ইনিশিয়ালাইজেশন ভেক্টর।

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 অনুরোধে কয়েকটি আলাদা হেডার নির্ধারণ করতে হবে।

এনক্রিপশন হেডার

'এনক্রিপশন' হেডারে অবশ্যই পেলোড এনক্রিপ্ট করার জন্য ব্যবহৃত সল্ট থাকতে হবে।

১৬ বাইটের সল্ট base64 URL নিরাপদ এনকোড করা হতে হবে এবং এনক্রিপশন হেডারে যোগ করতে হবে, যেমন:

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]

সবকিছু একসাথে

এইসব কীভাবে কাজ করে সেই সম্পর্কে আপনার আরও প্রশ্ন থাকলে, লাইব্রেরি কীভাবে পুশ মেসেজ ট্রিগার করে তা আপনি সবসময় the web-push-libs org-এ দেখতে পারবেন।

আপনার কাছে এনক্রিপ্ট করা পেলোড এবং উপরের হেডার থাকলে, আপনাকে শুধু PushSubscription-এ endpoint-এর জন্য একটি POST অনুরোধ করতে হবে।

তাহলে, এই POST অনুরোধের উত্তর নিয়ে আমরা কী করব?

পুশ পরিষেবা থেকে পাওয়া উত্তর

আপনি পুশ পরিষেবার জন্য অনুরোধ করার পরে, আপনাকে উত্তরের স্ট্যাটাস কোড চেক করতে হবে কারণ এটি আপনাকে জানাবে যে অনুরোধটি সফল হয়েছে কিনা।

স্ট্যাটাস কোড বিবরণ
201 তৈরি করা হয়েছে। পুশ মেসেজ পাঠানোর অনুরোধ পাওয়া এবং গ্রহণ করা হয়েছে।
429 অত্যধিক অনুরোধ। অর্থাৎ, আপনার অ্যাপ্লিকেশন সার্ভার পুশ পরিষেবার সাথে রেট লিমিটে পৌঁছে গেছে। পুশ সার্ভিসে 'Retry-After' হেডার অন্তর্ভুক্ত করা উচিত যাতে বোঝা যায় যে কতক্ষণ পরে আবার অনুরোধ করা যাবে।
400 ভুল অনুরোধ। এর অর্থ হল, আপনার কোনও একটি হেডার ভুল অথবা সঠিকভাবে ফর্ম্যাট করা হয়নি।
404 খুঁজে পাওয়া যায়নি৷ এটি ইঙ্গিত করে যে সাবস্ক্রিপশনের মেয়াদ শেষ হয়ে গেছে এবং এটি ব্যবহার করা যাবে না। এই ক্ষেত্রে, আপনাকে `PushSubscription` মুছে দিতে হবে এবং ক্লায়েন্ট যাতে ব্যবহারকারীকে আবার সাবস্ক্রাইব করে তার জন্য অপেক্ষা করতে হবে।
410 হয়ে গেছে। সাবস্ক্রিপশনটি আর বৈধ নয় এবং অ্যাপ্লিকেশন সার্ভার থেকে এটি সরিয়ে দেওয়া উচিত। `PushSubscription`-এ `unsubscribe()` কল করে এটি আবার তৈরি করা যেতে পারে।
413 পে-লোডের সাইজ খুব বড়। পুশ পরিষেবার জন্য ন্যূনতম পেলোড সাইজ ৪০৯৬ বাইট (বা ৪ কেবি) হতে হবে।

এছাড়াও, HTTP স্ট্যাটাস কোড সম্পর্কে আরও জানতে আপনি ওয়েব পুশ স্ট্যান্ডার্ড (RFC8030) পড়তে পারেন।

এরপর কোথায় যেতে হবে

কোড ল্যাব