之前已經可以用後端的套件去實作推播的伺服器,但那個套件實際上做了哪些事情?
流程如下圖,相關定義可參考連結:
https://datatracker.ietf.org/doc/html/draft-ietf-webpush-encryption
+-------+ +--------------+ +-------------+
| UA | | Push Service | | Application |
+-------+ +--------------+ +-------------+
| | |
| Setup | |
|<====================>| |
| Provide Subscription |
|-------------------------------------------->|
| | |
: : :
| | Push Message |
| Push Message |<---------------------|
|<---------------------| |
| | |
也因為定義了標準協議的關係,所以有了各語言的實作版本:
密鑰會用來檢查訂閱的用戶身分公鑰是否符合,pushManager.subscribe()
會用公鑰來檢查接收到的簽章訊息是否由與公鑰相關的私鑰簽出來的。
簽章訊息的傳遞會透過 JWT 放在 header 進行資料交換,一個 JWT 會由三段字串組成並由 .
號分隔 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdCJ9.zTWqeQdfDM0WKGBFig2-VmUpTLkIQ4DvAJN6_LzDZzU
,前兩個字串(JWT info 和 JWT data)是經過 base64 編碼的 JSON ,所以其實是公開可閱讀的。
想要解密 JWT,可以直接用官方網站提供的介面。
https://jwt.io/
{
"//": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
"alg": "HS256",
"typ": "JWT"
}
https://github.com/web-push-libs/vapid
{
"//": "`eyJuYW1lIjoidGVzdCJ9",
"aud": "https://YourSiteHere.example",
"sub": "mailto://admin@YourSiteHere.example",
"exp": 1457718878
}
最後的 header 就會像是 Authorization: 'WebPush <JWT Info>.<JWT Data>.<Signature>'
推播的訊息不能赤裸裸的傳遞,所以在加密上也有定義相關規範,我也看不是很懂,總體來說就是套了加密、加鹽、金鑰對搭配使用,執行加密的時候最後也加上填充,避免被用長度推斷。
看不懂也沒關係可以看影片,但我相信看完影片可能還是只懂概念。
https://youtu.be/F3zzNa42-tQ
const keyCurve = crypto.createECDH("prime256v1");
keyCurve.generateKeys();
const publicKey = keyCurve.getPublicKey();
const privateKey = keyCurve.getPrivateKey();
function hkdf(salt, ikm, info, length) {
// ikm 加鹽
const keyHmac = crypto.createHmac("sha256", salt);
keyHmac.update(ikm);
const key = keyHmac.digest();
// info 加密
const infoHmac = crypto.createHmac("sha256", key);
infoHmac.update(info);
const ONE_BUFFER = new Buffer(1).fill(1);
infoHmac.update(ONE_BUFFER);
// 長度控制
return infoHmac.digest().slice(0, length);
}
const nonce = hkdf(salt, prk, nonceInfo, 12);
const contentEncryptionKey = hkdf(salt, prk, cekInfo, 16);
使用者裝置:
伺服器: