Kita telah melihat cara library dapat digunakan untuk memicu pesan push,
Nah,
Bagian ini menguraikan cara server mengidentifikasi dirinya dengan kunci server aplikasi
dan cara payload terenkripsi serta data terkait dikirim.
Ini bukan sisi menarik dari push web dan saya bukan pakar enkripsi,
Kunci server aplikasi
Saat membuat pengguna berlangganan,applicationServerKey
.
Saat kita memicu pesan push,
Apa sebenarnya arti semua ini dan apa yang sebenarnya terjadi? Berikut adalah langkah-langkah yang diambil untuk autentikasi server aplikasi:
- Server aplikasi menandatangani beberapa informasi JSON dengan kunci aplikasi pribadi-nya.
- Informasi yang ditandatangani ini dikirim ke layanan push sebagai header dalam permintaan POST.
- Layanan push menggunakan kunci publik tersimpan yang diterima dari
pushManager.
untuk memeriksa apakah informasi yang diterima ditandatangani oleh kunci pribadi yang terkait dengan kunci publik.subscribe() Ingat: Kunci publik adalah applicationServerKey
yang diteruskan ke panggilan subscribe. - Jika informasi yang ditandatangani valid,
layanan push akan mengirimkan pesan push ke pengguna.
Contoh alur informasi ini ada di bawah.
"Informasi yang ditandatangani" yang ditambahkan ke header dalam permintaan adalah Token Web JSON.
Token web JSON
Token web JSON (atau JWT) adalah cara
mengirim pesan ke pihak ketiga sehingga penerima dapat memvalidasi
pengirimnya.
Saat pihak ketiga menerima pesan,
Ada banyak library di https:/
Push web dan JWT yang ditandatangani
JWT yang ditandatangani hanyalah string,
String pertama dan kedua (Info JWT dan data JWT) adalah bagian dari JSON yang telah dienkode base64,
String pertama adalah informasi tentang JWT itu sendiri,
Info JWT untuk push web harus berisi informasi berikut:
{
"typ": "JWT",
"alg": "ES256"
}
String kedua adalah Data JWT.
Untuk push web,
{
"aud": "https://some-push-service.org" ,
"exp": "1469618703",
"sub": "mailto:example@web-push-book.org"
}
Nilai aud
adalah "audiens",
Nilai exp
adalah masa berlaku JWT,
Di Node.
Math. floor(Date. now() / 1000) + 12 * 60 * 60;
Durasinya 12 jam,
Terakhir,sub
harus berupa URL atau alamat email mailto
.
Sama seperti Info JWT,
String ketiga,
Proses penandatanganan memerlukan enkripsi "token tanpa tanda tangan" menggunakan ES256.
// 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);
});
Layanan push dapat memvalidasi JWT menggunakan kunci server aplikasi publik
untuk mendekripsi tanda tangan dan memastikan string yang didekripsi sama
dengan "token tanpa tanda tangan" (yaitu dua string pertama dalam JWT).
JWT yang ditandatangani (yaitu ketiga string yang digabungkan dengan titik),Authorization
dengan WebPush
ditambahkan di awal,
Authorization: 'WebPush [JWT Info].[JWT Data].[Signature]' ;
Web Push Protocol juga menyatakan bahwa kunci server aplikasi publik harus
dikirim dalam header Crypto-Key
sebagai string berenkode base64 yang aman untuk URL dengan
p256ecdsa=
ditambahkan di depannya.
Crypto-Key: p256ecdsa=[URL Safe Base64 Public Application Server Key]
Enkripsi Payload
Selanjutnya,
Pertanyaan umum yang muncul dari siapa saja yang telah menggunakan layanan push lainnya adalah mengapa payload push web
perlu dienkripsi? Dengan aplikasi native,
Salah satu keunggulan push web adalah karena semua layanan push menggunakan
API yang sama (protokol push web),
Enkripsi payload ditentukan dalam spesifikasi
Enkripsi Pesan.
Sebelum melihat langkah-langkah spesifik untuk mengenkripsi payload pesan push,
ECDH dan HKDF
ECDH dan HKDF digunakan di seluruh proses enkripsi dan menawarkan manfaat untuk
tujuan mengenkripsi informasi.
ECDH: Pertukaran kunci Elliptic Curve Diffie-Hellman
Bayangkan Anda memiliki dua orang yang ingin berbagi informasi,
Properti kunci yang berguna yang dihasilkan dengan ECDH adalah Alice dapat menggunakan
kunci pribadinya dan kunci publik Bob untuk membuat nilai rahasia 'X'.
Sepengetahuan saya,
Ini adalah penjelasan tingkat tinggi tentang ECDH.
Dalam hal kode; sebagian besar bahasa /
Di node,
const keyCurve = crypto. createECDH('prime256v1');
keyCurve. generateKeys();
const publicKey = keyCurve. getPublicKey();
const privateKey = keyCurve. getPrivateKey();
HKDF: Fungsi derivasi kunci berbasis HMAC
Wikipedia memiliki deskripsi ringkas tentang HKDF:
HKDF adalah fungsi derivasi kunci berbasis HMAC yang mengubah materi kunci
yang lemah menjadi materi kunci yang kuat secara kriptografis.
Pada dasarnya,
Spesifikasi yang menentukan enkripsi ini memerlukan penggunaan SHA-256 sebagai algoritma hash kita
dan kunci yang dihasilkan untuk HKDF di web push tidak boleh lebih dari 256 bit
(32 byte).
Di node,
// 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);
}
Terima kasih kepada artikel Mat Scale untuk kode contoh ini.
Hal ini secara umum mencakup ECDH
dan HKDF.
ECDH adalah cara yang aman untuk membagikan kunci publik dan membuat secret bersama.
Ini akan digunakan selama enkripsi payload.
Input
Jika ingin mengirim pesan push kepada pengguna dengan payload,
- Payload itu sendiri.
- Secret
auth
dariPushSubscription
. - Kunci
p256dh
dariPushSubscription
.
Kita telah melihat nilai auth
dan p256dh
diambil dari PushSubscription
,
subscription. toJSON(). keys. auth;
subscription. toJSON(). keys. p256dh;
subscription. getKey('auth');
subscription. getKey('p256dh');
Nilai auth
harus diperlakukan sebagai rahasia dan tidak dibagikan di luar aplikasi Anda.
Kunci p256dh
adalah kunci publik,p256dh
sebagai kunci publik langganan.
Tiga nilai ini,auth
,p256dh
,payload
diperlukan sebagai input dan hasil
proses enkripsi adalah payload terenkripsi,
Salt
Salt harus berupa data acak 16 byte.
const salt = crypto. randomBytes(16);
Kunci Publik /
Kunci publik dan pribadi harus dibuat menggunakan kurva elips P-256,
const localKeysCurve = crypto. createECDH('prime256v1');
localKeysCurve. generateKeys();
const localPublicKey = localKeysCurve. getPublicKey();
const localPrivateKey = localKeysCurve. getPrivateKey();
Kita akan menyebut kunci ini sebagai "kunci lokal".
Dengan payload,
Rahasia bersama
Langkah pertama adalah membuat secret bersama menggunakan kunci publik langganan dan kunci pribadi
baru kita (ingat penjelasan ECDH dengan Alice dan Bob? Hanya dengan begitu).
const sharedSecret = localKeysCurve. computeSecret(
subscription. keys. p256dh,
'base64',
);
Ini digunakan pada langkah berikutnya untuk menghitung Kunci Pseudo Random (PRK).
Kunci pseudo-random
Pseudo Random Key (PRK) adalah kombinasi dari secret autentikasi
langganan push,
const authEncBuff = new Buffer('Content-Encoding: auth\0', 'utf8');
const prk = hkdf(subscription. keys. auth, sharedSecret, authEncBuff, 32);
Anda mungkin bertanya-tanya apa kegunaan string Content-Encoding: auth\0
.\0
menambahkan byte dengan nilai 0 ke akhir Buffer.
Kunci Pseudo Random kami hanya menjalankan autentikasi,
Konteks
"Konteks" adalah kumpulan byte yang digunakan untuk menghitung dua nilai nanti di browser
enkripsi.
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,
]);
Buffer konteks akhir adalah label,
Dengan nilai konteks ini,
Kunci enkripsi konten dan nonce
Nonce adalah nilai yang mencegah serangan replay karena hanya boleh digunakan sekali.
Kunci enkripsi konten (CEK) adalah kunci yang pada akhirnya akan digunakan untuk mengenkripsi payload kita.
Pertama,
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]);
Informasi ini dijalankan melalui HKDF yang menggabungkan salt dan PRK dengan nonceInfo dan 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);
Hal ini memberi kita nonce dan kunci enkripsi konten.
Melakukan enkripsi
Setelah memiliki kunci enkripsi konten,
Kami membuat cipher AES128 menggunakan kunci enkripsi konten
sebagai kunci dan nonce adalah vektor inisialisasi.
Di Node,
const cipher = crypto. createCipheriv(
'id-aes128-GCM',
contentEncryptionKey,
nonce,
);
Sebelum mengenkripsi payload,
Anda harus menambahkan padding dua byte untuk menunjukkan panjang padding tambahan.
Misalnya,
const padding = new Buffer(2 + paddingLength);
// The buffer must be only zeros, except the length
padding. fill(0);
padding. writeUInt16BE(paddingLength, 0);
Kemudian,
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()]);
Sekarang kita memiliki payload terenkripsi.
Yang tersisa hanyalah menentukan cara payload ini dikirim ke layanan push.
Header & isi payload terenkripsi
Untuk mengirim payload terenkripsi ini ke layanan push,
Header enkripsi
Header 'Encryption' harus berisi salt yang digunakan untuk mengenkripsi payload.
Salt 16 byte harus dienkode dengan base64 URL yang aman dan ditambahkan ke header Enkripsi,
Encryption: salt=[URL Safe Base64 Encoded Salt]
Header Kunci Kripto
Kita telah melihat bahwa header Crypto-Key
digunakan di bagian 'Kunci Server Aplikasi'
untuk memuat kunci server aplikasi publik.
Header ini juga digunakan untuk membagikan kunci publik lokal yang digunakan untuk mengenkripsi
payload.
Header yang dihasilkan akan terlihat seperti ini:
Crypto-Key: dh=[URL Safe Base64 Encoded Local Public Key String]; p256ecdsa=[URL Safe Base64 Encoded Public Application Server Key]
Header jenis, panjang, & encoding konten
Header Content-Length
adalah jumlah byte dalam payload
terenkripsi.
Content-Length: [Number of Bytes in Encrypted Payload]
Content-Type: 'application/octet-stream'
Content-Encoding: 'aesgcm'
Dengan menetapkan header ini,Content-Type
ditetapkan ke
application/
.
Di NodeJS,
const pushRequest = https. request(httpsOptions, function(pushResponse) {
pushRequest. write(encryptedPayload);
pushRequest. end();
Header lainnya?
Kita telah membahas header yang digunakan untuk Kunci Server Aplikasi /
Ada header tambahan yang digunakan layanan push untuk mengubah perilaku
pesan yang dikirim.
Header TTL
Wajib
TTL
(atau time to live) adalah bilangan bulat yang menentukan jumlah detik
yang Anda inginkan untuk pesan push di layanan push sebelum pesan tersebut
dikirim.TTL
berakhir,
TTL: [Time to live in seconds]
Jika Anda menetapkan TTL
nol,
Secara teknis,TTL
pesan push jika
ingin.TTL
dalam
respons dari layanan push.
Topik
Opsional
Topik adalah string yang dapat digunakan untuk mengganti pesan yang tertunda dengan pesan baru jika memiliki nama topik yang cocok.
Hal ini berguna dalam skenario saat beberapa pesan dikirim saat
perangkat offline,
Urgensi
Opsional
Urgensi menunjukkan kepada layanan push seberapa penting pesan bagi pengguna.
Nilai header ditentukan seperti yang ditunjukkan di bawah.normal
.
Urgency: [very-low | low | normal | high]
Semuanya bersama-sama
Jika ada pertanyaan lebih lanjut tentang cara kerjanya,
Setelah memiliki payload terenkripsi,endpoint
dalam PushSubscription
.
Jadi,
Respons dari layanan push
Setelah membuat permintaan ke layanan push,
Kode Status | Deskripsi |
---|---|
201 | Dibuat. |
429 | Terlalu banyak permintaan. |
400 | Permintaan tidak valid. |
404 | Tidak Ditemukan. |
410 | Hilang. |
413 | Ukuran payload terlalu besar. |
Anda juga dapat membaca standar Web Push (RFC8030) untuk mengetahui informasi selengkapnya tentang kode status HTTP.
Langkah berikutnya
- Ringkasan Notifikasi Push Web
- Cara Kerja Push
- Mendaftarkan Pengguna
- UX Izin
- Mengirim Pesan dengan Library Web Push
- Protokol Web Push
- Menangani Peristiwa Push
- Menampilkan Notifikasi
- Perilaku Notifikasi
- Pola Notifikasi Umum
- FAQ Notifikasi Push
- Masalah Umum dan Bug Pelaporan