iT邦幫忙

2021 iThome 鐵人賽

DAY 26
0
Modern Web

前端三分鐘 X Progressive Web App 30 天製造解密系列 第 26

Progressive Web App 推播協定 (26)

之前已經可以用後端的套件去實作推播的伺服器,但那個套件實際上做了哪些事情?

  • 金鑰對 Application server keys
  • 傳輸內容加密 Payload Encryption
  • Header 參數配置

流程如下圖,相關定義可參考連結:
https://datatracker.ietf.org/doc/html/draft-ietf-webpush-encryption

    +-------+           +--------------+       +-------------+
    |  UA   |           | Push Service |       | Application |
    +-------+           +--------------+       +-------------+
        |                      |                      |
        |        Setup         |                      |
        |<====================>|                      |
        |           Provide Subscription              |
        |-------------------------------------------->|
        |                      |                      |
        :                      :                      :
        |                      |     Push Message     |
        |    Push Message      |<---------------------|
        |<---------------------|                      |
        |                      |                      |

也因為定義了標準協議的關係,所以有了各語言的實作版本:

金鑰對 Application server keys

  • 密鑰: 推播服務使用
  • 公鑰: 用戶端使用

密鑰會用來檢查訂閱的用戶身分公鑰是否符合,pushManager.subscribe() 會用公鑰來檢查接收到的簽章訊息是否由與公鑰相關的私鑰簽出來的。

簽章訊息的傳遞會透過 JWT 放在 header 進行資料交換,一個 JWT 會由三段字串組成並由 . 號分隔 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoidGVzdCJ9.zTWqeQdfDM0WKGBFig2-VmUpTLkIQ4DvAJN6_LzDZzU,前兩個字串(JWT info 和 JWT data)是經過 base64 編碼的 JSON ,所以其實是公開可閱讀的。

想要解密 JWT,可以直接用官方網站提供的介面。
https://jwt.io/

  1. JWT 訊息: 會記錄用哪種算法簽章
{
  "//": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
  "alg": "HS256",
  "typ": "JWT"
}
  1. JWT 資料: 看程式設計上想要帶什麼資料會放在這邊,推播的話會有三個欄位 aud、exp、sub,透過這三個參數和 vapid 產生工具就能夠產生金鑰對
  • aud: 推播服務來源
  • exp: JWT 到期時間
  • sub: 推播訊息的人的聯絡訊息

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>'

傳輸內容加密

推播的訊息不能赤裸裸的傳遞,所以在加密上也有定義相關規範,我也看不是很懂,總體來說就是套了加密、加鹽、金鑰對搭配使用,執行加密的時候最後也加上填充,避免被用長度推斷。

  • ECDH (elliptic-curve Diffie-Hellman): 透過運算去發現原來我們是天生一對,數學假設如下
    • y^2 = x^3 + ax + b
    • 4a^3 + 27b^2 != 0

看不懂也沒關係可以看影片,但我相信看完影片可能還是只懂概念。
https://youtu.be/F3zzNa42-tQ

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

const publicKey = keyCurve.getPublicKey();
const privateKey = keyCurve.getPrivateKey();
  • HKDF (Hashed Message Authentication Code): SHA-256
    • Salt: 16 byte authentication secret
    • IKM: shared secret
    • info
    • length
  • Nonce: 加密通信只能使用一次的數字,可能是一個隨機或偽隨機數,以避免重送攻擊
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);
  • 使用者裝置:

    • ecdh_secret = ECDH(ua_private, as_public)
    • auth_secret = random(16)
    • salt = header 來的
  • 伺服器:

    • ecdh_secret = ECDH(as_private, ua_public)
    • auth_secret = user agent 來的
    • salt = random(16)

Header 參數配置

  • TTL header (time to live): 訊息能在推播服務上存活多久
  • Topic: 同個主題下可以實作舊訊息取代新訊息
  • Urgency: 訊息的重要程度

上一篇
Progressive Web App 推播通知行為 (25)
下一篇
Progressive Web App 針對應用操作介面優化操作體驗 (27)
系列文
前端三分鐘 X Progressive Web App 30 天製造解密30

尚未有邦友留言

立即登入留言