我正在使用 VAPID 和有效负载加密为 WebPush 开发一个纯 Java 实现(我已经为 GCM 和 FCM 实现了)。然而,文档仍然是边际的,而且代码示例仍然不是实质性的。目前我正试图让它在 Chrome 中工作。尽管我使用 VAPID 获得了成功的订阅,但当我发送 Tickle 或 Payload 推送消息时,我会收到 400 UnauthorizedRegistration。我的猜测是它与授权标头或 Crypto-Key 标头有关。到目前为止,这是我为 Tickle 发送的内容(没有有效负载的推送通知):
URL: https://fcm.googleapis.com/fcm/send/xxxxx:xxxxxxxxxxx...
Action: POST/PUT (Both give same result)
With headers:
Authorization: Bearer URLBase64(JWT_HEAD).URLBase64(JWT_Payload).SIGN
Crypto-Key: p265ecdsa=X9.62(PublicKey)
Content-Type: "text/plain;charset=utf8"
Content-Length: 0
TTL: 120
JWT_HEAD="{\"typ\":\"JWT\",\"alg\":\"ES256\"}"
JWT_Payload={
aud: "https://fcm.googleapis.com",
exp: (System.currentTimeMillis() / 1000) + (60 * 60 * 12)),
sub: "mailto:webpush@mydomain.com"
}
SIGN = the "SHA256withECDSA" signature algorithm over: "URLBase64(JWT_HEAD).URLBase64(JWT_Payload)"
我已经从 JWT 中的两个 JSON 中删除了空格,因为规范对空格的使用不是很清楚,这似乎是最安全的做法。再次将 x9.62 解码为 ECPoint 后签名验证,因此 publicKey 似乎有效编码。但是我不断收到回复:
<HTML><HEAD><TITLE>UnauthorizedRegistration</TITLE></HEAD><BODY BGCOLOR="#FFFFFF" TEXT="#000000"><H1>UnauthorizedRegistration</H1><H2>Error 400</H2></BODY></HTML>
根据 FCM 文档,这仅在发生 JSON 错误时才会发生,但是我觉得该规范根本没有涵盖 WebPush。现在我都尝试过在 Java Crypto 提供程序中构建,并且 BC 都产生了相同的结果。
一些代码片段进行澄清:
密钥生成:
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC", "BC");
ECGenParameterSpec spec = new ECGenParameterSpec("secp256r1");
keyGen.initialize(spec, secureRandom);
KeyPair vapidPair = keyGen.generateKeyPair();
x9.62 的 ECPublicKey:
public byte[] toUncompressedPoint(ECPublicKey publicKey){
final ECPoint publicPoint = publicKey.getW();
final int keySizeBytes = (publicKey.getParams().getOrder().bitLength() + Byte.SIZE - 1) / Byte.SIZE;
final byte[] x = publicPoint.getAffineX().toByteArray();
final byte[] y = publicPoint.getAffineY().toByteArray();
final byte[] res = new byte[1 + 2 * keySizeBytes];
int offset = 0;
res[offset++] = 0x04; //Indicating no key compression is used
if(x.length <= keySizeBytes)
System.arraycopy(x, 0, res, offset + keySizeBytes - x.length, x.length);
else if(x.length == keySizeBytes + 1) System.arraycopy(x, 1, res, offset, keySizeBytes);
else throw new IllegalArgumentException("X value is too large!");
offset += keySizeBytes;
if(y.length <= keySizeBytes)
System.arraycopy(y, 0, res, offset + keySizeBytes - y.length, y.length);
else if(y.length == keySizeBytes + 1 && y[0] == 0) System.arraycopy(y, 1, res, offset, keySizeBytes);
else throw new IllegalArgumentException("Y value is too large!");
return res;
}
签署 JWT 声明:
ObjectNode claim = om.createObjectNode();
claim.put("aud", host);
claim.put("exp", (System.currentTimeMillis() / 1000) + (60 * 60 * 12));
claim.put("sub", "mailto:webpush_ops@mydomain.com");
String claimString = claim.toString();
String encHeader = URLBase64.encodeString(VAPID_HEADER, false);
String encPayload = URLBase64.encodeString(claimString, false);
String vapid = null;
ECPublicKey pubKey = (ECPublicKey) vapidPair.getPublic();
byte[] point = toUncompressedPoint(pubKey);
String vapidKey = URLBase64.encodeToString(point, false);
try{
Signature dsa = Signature.getInstance("SHA256withECDSA", "BC");
dsa.initSign(vapidPair.getPrivate());
dsa.update((encHeader + "." + encPayload).getBytes(StandardCharsets.US_ASCII));
byte[] signature = dsa.sign();
vapid = encHeader + "." + encPayload + "." + URLBase64.encodeToString(signature, false);
一些问题萦绕在我的脑海中:
注册回复 JSON 中的 auth 字段是什么?因为据我所知,只有 p256dh 与基于服务器的 KeyPair 一起用于生成加密密钥。
对 ietf 草案 03 的进一步研究在以下部分给出了答案:2.3 链接:https ://datatracker.ietf.org/doc/html/draft-ietf-webpush-encryption-03 此外,Vincent Cheung 的答案中的链接也给出了很好的答案解释
该文档谈到了使用 Bearer/WebPush 和使用 Crypto-Key 标头或 Encryption-Key 标头的 VAPID 的不同标头用法。笏是正确的做法吗?
任何想法为什么 FCM 服务器不断返回: 400 UnauthorizedRegistration ?
有人可以在这个问题中添加 VAPID 标签吗?它似乎还不存在。