Go + RFC 8219 Message Encryption for Web Push の続き。
RFC 8292 Voluntary Application Server Identification (VAPID) for Web Push 理解のため読みつつ Go での Application Server 側の実装をなぞる。
ここまでの実装により PushService への HTTP リクエストが送信できるようになる。
Application Server Self-Identification
openssl ecparam -name prime256v1 -genkey -noout -out vapid_private.pem
openssl ec -in vapid_private.pem -text -noout -conv_form uncompressed | grep '^priv:' -A 3 | tail -n 3 | tr -d ' \n:' | xxd -r -p | base64
openssl ec -in vapid_private.pem -text -noout -conv_form uncompressed | grep '^pub:' -A 5 | tail -n 5 | tr -d ' \n:' | xxd -r -p | base64
VAPID のため ApplicationServer のキーペアが必要となる。
公開鍵の方は PushManager.subscribe で applicationServerKey
でも同じものを渡す。
func newJwt(endpoint, subject string, expiry int64, priv *ecdsa.PrivateKey) (string, error) {
origin, err := url.Parse(endpoint)
if err != nil {
return "", err
}
audience := fmt.Sprintf("%s://%s", origin.Scheme, origin.Host)
token := jwt.NewWithClaims(jwt.SigningMethodES256, jwt.MapClaims{
"aud": audience,
"exp": expiry,
"sub": subject,
})
return token.SignedString(priv)
}
リクエストに含める JWT には aud
と exp
を最低限指定する。
Application Server Contact Information として sub
を含めても良い。
Authorization: vapid
t=eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJhdWQiOiJodHRwczovL3
B1c2guZXhhbXBsZS5uZXQiLCJleHAiOjE0NTM1MjM3NjgsInN1YiI6Im1ha
Wx0bzpwdXNoQGV4YW1wbGUuY29tIn0.i3CYb7t4xfxCDquptFOepC9GAu_H
LGkMlMuCGSK2rpiUfnK9ojFwDXb1JrErtmysazNjjvW2L9OkSSHzvoD1oA,
k=BA1Hxzyi1RUM1b5wjxsn7nGxAszw2u61m164i3MrAIxHF6YK5h4SDYic-dR
uU_RCPCfA5aq9ojSwk5Y2EmClBPs
これらの情報から上記のような Authorization ヘッダーを生成する。
func addAuthorizationHeader(req *http.Request, endpoint, subject string, expiry int64, pair *ApplicationServerKeyPair) error {
t, err := newJwt(endpoint, subject, expiry, pair.priv)
if err != nil {
return err
}
k := pair.pub
req.Header.Add("Authorization", fmt.Sprintf("vapid t=%s,k=%s", t, k))
return nil
}
Token Parameter ("t") は生成した JWT を指定する。
Public Key Parameter ("k") は base64url エンコーディングされた公開鍵を指定する。
(Go) bytes to ecdsa.PrivateKey
func (pair *ApplicationServerKeyPair) SetBase64StdEncodingPrivateKey(key string) error {
b, err := base64.StdEncoding.DecodeString(key)
if err != nil {
return err
}
d := big.Int{}
pair.priv = &ecdsa.PrivateKey{
PublicKey: ecdsa.PublicKey{Curve: curve},
D: d.SetBytes(b),
}
return nil
}
ecdsa.PrivateKey
を既存のものから生成する方法がいまいち分からなかったが、なんとか動いた。
あるいは ParseECPrivateKeyFromPEM からファイルを pem ファイルからの読み込みこむことも可能そうだった。