如何使WebPush(使用Qt)与Windows Edge浏览器一起使用?

问题描述 投票:0回答:1

我的 Web-Push 实现在 Chrome 和 Firefox 上运行良好,但在 MS Edge 上运行不佳(即使在 % 解码之后)。

问题:如何使其发挥作用?

仅供参考,他们的 URL 以不同的格式显示:

Chrome 网址: “https://fcm.googleapis.com/fcm/send/eBt-ioGXt4A:APA91bGkIsaIeVWAG-cU-UysXKl5pevjnZzIRT0GS69gx0 ZKbnbrOD4FB7m8Ou8ZpS0X0hcEeSLjxYdsYKi896YibgFrocO7L8qDU2SeZfr6L7Pqc2DZ7A_82Qik7PINAF_S2rskKofe"

边缘网址: “https://wns2-pn1p.notify.windows.com/w/?token=BQYAAACbMWA0QtGcCaM00KJntpBBrdmzSvWUnBmgRwUFnM6fJEy SP1Jr0Fi3OU2rgTICDfl2Bsj6jS4eNZLo0FQK1diyqN1v6zi7k9yBijIksEvKegd2q2Z%2bGAIoIt0QfnQyluOGSNgXCrF0jr4 h3Ka5aJUVdl7aBSkkULq5PI7wvGl3mGMD9I3xk71jG%2bjBAwoF2ThvYfefEEpd5xMAJ%2bWLBd3FD56kD1zTplOhwS4Leysw3 SFBXh393%2b8MfDFJCAi%2f0mfKLBy%2fTCuha50GJT7oBJHemhFCi5E2CliZ9dFB2IPCpEa%2bEfgVWHC6GkpRMjUSCs0%3d"

客户端打印错误:

400:“传输错误 - 服务器回复:”

ChatGPT 表示服务器没有义务提供到底出了什么问题的实际文本,以阻止其内部工作。
对于 Chrome,它只是返回 201,并在仪表板中看到一条通知。
请注意,相同的 VAPID 密钥、Endpoint、P256DH、AUTH 等组合适用于已知的 C# 推送通知 API (github)


源代码:从pusha(github)库中,我导出了相关部分并使用Qt框架实现了一个最小的工作示例。可能需要添加 ecec(github) 库的一些文件。完成后,仅使用以下单个源文件就可以正常工作!

web-push.pro

TEMPLATE = app
CONFIG += console c++17
CONFIG -= app_bundle
QT += network
LIBS += -lcrypto -lssl #ssl is optional

SOURCES += \
        ecec/encrypt.c \
        ecec/keys.c \
        ecec/trailer.c \
        main.cpp

HEADERS += \
        ecec/ece.h \
        ecec/keys.h \
        ecec/trailer.h

main.cpp

#include<openssl/pem.h>
#include<QByteArray>
#include<QDateTime>
#include<QDebug>
#include<QCoreApplication>
#include<QFile>
#include<QJsonDocument>
#include<QJsonObject>
#include<QNetworkAccessManager>
#include<QNetworkReply>
#include<ecec/ece.h>


#define ENDPOINT "https://fcm.googleapis.com/fcm/send/eBt-ioGXt4A:APA91bGkIsaIeVWAG-cU-UysXKl5pevjnZzIRT0GS69gx0ZKbnbrOD4FB7m8Ou8ZpS0X0hcEeSLjxYdsYKi896YibgFrocO7L8qDU2SeZfr6L7Pqc2DZ7A_82Qik7PINAF_S2rskKofe"
#define P256DH "BM8Md9QY7egq1UqZytneixPkITMK5556EHBQD0yTZY6eW6eeK89TsdpymT79rIc9xfouL1FLH-ACb9kEuf6iea0"
#define AUTH "w7o5Sip9yBm6ME1C88pebg"
#define VAPID_PRIVATE_KEY "T2blCzCxRzFl2yjI-Q6WLxW1PoiTxSLPExtP9yOxkgM"
#define VAPID_PUBLIC_KEY  "BMRAeeyEY6imWuCktv6NX3o5prv4UWndTUWTEO4dCgmzX8YDgjuIbPslpSM2fdfTbOjmnLIBkJKch2wGnTm8_sY"

#define WEBPUSH_PAYLOAD_KEY_AUD      "aud"
#define WEBPUSH_PAYLOAD_KEY_EXP      "exp"
#define WEBPUSH_PAYLOAD_KEY_SUB      "sub"
#define WEBPUSH_VAPID_KEY_ALG        "alg"
#define WEBPUSH_VAPID_KEY_TYP        "typ"

#define HTTP_DH               ";dh="
#define HTTP_AUTHORIZATION    "authorization"
#define HTTP_CONTENT_ENCODING "content-encoding"
#define HTTP_CONTENT_TYPE     "content-type"
#define HTTP_CONTENT_LENGTH   "content-length"
#define HTTP_CRYPTO_KEY       "crypto-key"
#define HTTP_ENCRYPTION       "encryption"
#define HTTP_P256             "p256ecdsa="
#define HTTP_RS_SALT          "rs=4096;salt="
#define HTTP_TTL              "ttl"

#define QT_URL_ENCODING QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals
#define MAKE_UNIQUE(MALLOC, FREE) std::unique_ptr<std::remove_pointer<decltype(MALLOC)>::type, \
                                                  decltype(&FREE)>{MALLOC, &FREE}

static const QByteArray HTTP_AUTHORIZATION_v = "webPush ",
                        HTTP_ENCODING_v = "aesgcm",
                        HTTP_CONTENT_TYPE_v = "application/octet-stream",
                        HTTP_TTL_v = "3600",
                        WEBPUSH_VAPID_ALG_v = "ES256", // must be in capital
                        WEBPUSH_VAPID_TYP_v = "jwt";

#define SUBSCRIBER "mailto:[email protected]"
#define MY_PAYLOAD "{ \
\"body\": \"Shree Vallabh!\", \
\"tag\": \"AAHLAAD\", \
\"data\": {\"tag\": \"test\"}, \
\"title\": \"Saarathy\" \
}"
#define SYMBOL_DOT "."

struct Subscription
{
  uint8_t m_P256dh[ECE_WEBPUSH_PUBLIC_KEY_LENGTH];
  uint8_t m_Auth[ECE_WEBPUSH_AUTH_SECRET_LENGTH];
};

struct Payload
{
  uint8_t   m_Salt[ECE_SALT_LENGTH], m_PublicKeySender[ECE_WEBPUSH_PUBLIC_KEY_LENGTH];
  QByteArray m_Cipher;
  size_t m_CipherLength;
};


auto
GetJson (QVariantMap map)
{
  auto object = QJsonObject::fromVariantMap(map);
  return QJsonDocument(object).toJson(QJsonDocument::Compact);
}

auto
CreateECKey (const QByteArray& rawKey)
{
  auto pECKey = MAKE_UNIQUE(::EC_KEY_new_by_curve_name(NID_X9_62_prime256v1), ::EC_KEY_free);
  ::EC_KEY_oct2priv(pECKey.get(),
                    reinterpret_cast<const uint8_t*>(rawKey.data()), rawKey.size());
  const ::EC_GROUP* const pGroup = ::EC_KEY_get0_group(pECKey.get());

  auto point = MAKE_UNIQUE(::EC_POINT_new(pGroup), ::EC_POINT_free);
  ::EC_POINT_mul(pGroup, point.get(),
                 ::EC_KEY_get0_private_key(pECKey.get()), nullptr, nullptr, nullptr);
  ::EC_KEY_set_public_key(pECKey.get(), point.get());
  return pECKey;
}

QByteArray
VapidSign (EC_KEY& ecKey,
           const QByteArray& sign)
{
  unsigned char digest[SHA256_DIGEST_LENGTH];
  ::SHA256(reinterpret_cast<const unsigned char*>(sign.data()), sign.size(), digest);

  const auto pSign = MAKE_UNIQUE(::ECDSA_do_sign(digest, SHA256_DIGEST_LENGTH, &ecKey),
                                 ::ECDSA_SIG_free);
  const ::BIGNUM *pR = nullptr, *pS = nullptr;
  ::ECDSA_SIG_get0(pSign.get(), &pR, &pS);

  const auto r_Size = BN_num_bytes(pR), s_Size = BN_num_bytes(pS);
  QByteArray signValue(r_Size + s_Size, 0);
  ::BN_bn2bin(pR, reinterpret_cast<unsigned char*>(signValue.data()));
  ::BN_bn2bin(pS, reinterpret_cast<unsigned char*>(&signValue[r_Size]));
  return signValue;
}

QByteArray
VapidAuthorize (const QString& endpoint,
                const QString& subscriber,
                const int expiration,
                EC_KEY& ecKey)
{
  const auto audience = endpoint.section('/', 0, 2);
  const auto params = GetJson({{WEBPUSH_PAYLOAD_KEY_AUD, audience},
                               {WEBPUSH_PAYLOAD_KEY_EXP, expiration},
                               {WEBPUSH_PAYLOAD_KEY_SUB, subscriber}}),
             header = GetJson({{WEBPUSH_VAPID_KEY_ALG, WEBPUSH_VAPID_ALG_v},
                               {WEBPUSH_VAPID_KEY_TYP, WEBPUSH_VAPID_TYP_v}}),
             sign = header.toBase64(QT_URL_ENCODING) + SYMBOL_DOT + params.toBase64(QT_URL_ENCODING);
  return sign + SYMBOL_DOT + VapidSign(ecKey, sign).toBase64(QT_URL_ENCODING);
}

size_t
GetCipherLength (const uint32_t rs,
                 const size_t padSize,
                 const size_t padLen,
                 const size_t plaintextLen)
{
  const size_t overhead = padSize + ECE_TAG_LENGTH, dataLen = plaintextLen + padLen,
               maxBlockLen = rs - overhead, numRecords = (dataLen / maxBlockLen) + 1;
  if(rs <= overhead or padLen > SIZE_MAX - plaintextLen or
     numRecords > (SIZE_MAX - dataLen) / overhead)
    return 0;
  return dataLen + (overhead * numRecords);
}

void
WebPush (const QString& endpoint,
         const QByteArray& authorization,
         const QByteArray& vapidPublicKey,
         const Payload& payload)
{
  int argc;
  QCoreApplication app{argc, nullptr};
  const auto dh = QByteArray(reinterpret_cast<const char*>(payload.m_PublicKeySender),
                             ECE_WEBPUSH_PUBLIC_KEY_LENGTH).toBase64(QT_URL_ENCODING),
             salt = QByteArray(reinterpret_cast<const char*>(payload.m_Salt),
                               ECE_SALT_LENGTH).toBase64(QT_URL_ENCODING);

  QNetworkAccessManager networkManager;
  QNetworkRequest request(QUrl{endpoint});
  request.setRawHeader(HTTP_AUTHORIZATION, HTTP_AUTHORIZATION_v + authorization);
  request.setRawHeader(HTTP_CONTENT_ENCODING, HTTP_ENCODING_v);
  request.setRawHeader(HTTP_CONTENT_TYPE, HTTP_CONTENT_TYPE_v);
  request.setRawHeader(HTTP_CONTENT_LENGTH, QByteArray::number(payload.m_Cipher.size()));
  request.setRawHeader(HTTP_CRYPTO_KEY, HTTP_P256 + vapidPublicKey + HTTP_DH + dh);
  request.setRawHeader(HTTP_ENCRYPTION, HTTP_RS_SALT + salt);
  request.setRawHeader(HTTP_TTL, HTTP_TTL_v);

  QNetworkReply* pReply = networkManager.post(request, payload.m_Cipher);
  QObject::connect(pReply, &QNetworkReply::finished, pReply, &QNetworkReply::deleteLater);
  QObject::connect(pReply, &QNetworkReply::finished, pReply,
  [=] ()
  {
    qDebug() << pReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt()
             << "====>" << pReply->errorString();
    ::exit(0);
  });
  app.exec();
}

QByteArray
CreateVapidPrivateKey ()
{
  auto pECKey = MAKE_UNIQUE(::EC_KEY_new_by_curve_name(NID_X9_62_prime256v1), ::EC_KEY_free);
  ::EC_KEY_generate_key(pECKey.get());
  QByteArray privKeyRaw(32, 0);
  ::BN_bn2binpad(::EC_KEY_get0_private_key(pECKey.get()),
                 reinterpret_cast<unsigned char*>(privKeyRaw.data()), 32);
  return privKeyRaw.toBase64(QT_URL_ENCODING);
}

QByteArray
CreateVapidPublicKey (const QByteArray& privateKeyBase64)
{
  const auto privateKey = QByteArray::fromBase64(privateKeyBase64, QT_URL_ENCODING);
  auto pECKey = MAKE_UNIQUE(::EC_KEY_new_by_curve_name(NID_X9_62_prime256v1), ::EC_KEY_free);

  auto pKeyBn = MAKE_UNIQUE(::BN_bin2bn(reinterpret_cast<const unsigned char*>(privateKey.data()),
                                        privateKey.size(), nullptr),
                            ::BN_free);
  ::EC_KEY_set_private_key(pECKey.get(), pKeyBn.get());

  const auto* const pGroup = ::EC_KEY_get0_group(pECKey.get());
  auto pPoint = MAKE_UNIQUE(::EC_POINT_new(pGroup), ::EC_POINT_free);
  ::EC_POINT_mul(pGroup, pPoint.get(), pKeyBn.get(), nullptr, nullptr, nullptr);

  QByteArray publicKey(65, 0);
  publicKey[0] = 0x04;
  ::EC_POINT_point2oct(pGroup, pPoint.get(), POINT_CONVERSION_UNCOMPRESSED,
                    reinterpret_cast<unsigned char*>(publicKey.data()), publicKey.size(), nullptr);
  return publicKey.toBase64(QT_URL_ENCODING);
}


int main ()
{
//  auto privKey = CreateVapidPrivateKey(), pubKey = CreateVapidPublicKey(privKey);
//  qDebug() << privKey << pubKey << "\n=======";

  qDebug() << "OpenSSL version:" << OpenSSL_version(OPENSSL_VERSION);
  QByteArray content = MY_PAYLOAD, p256dh = P256DH, auth = AUTH,
             vapidPrivateKey = VAPID_PRIVATE_KEY, vapidPublicKey = VAPID_PUBLIC_KEY, contentJson;
  QString endpoint = ENDPOINT;
  qDebug() << endpoint << p256dh << auth << vapidPrivateKey << vapidPublicKey;

  endpoint = QUrl::fromPercentEncoding(endpoint.toUtf8());
  p256dh = QByteArray::fromBase64(p256dh, QT_URL_ENCODING);
  auth = QByteArray::fromBase64(auth, QT_URL_ENCODING);
  qDebug() << endpoint << "-----";

  Subscription subscription = {};
  ::memcpy(subscription.m_P256dh, p256dh.data(), sizeof(subscription.m_P256dh));
  ::memcpy(subscription.m_Auth, auth.data(), sizeof(subscription.m_Auth));

  auto pECKey = CreateECKey(QByteArray::fromBase64(vapidPrivateKey, QT_URL_ENCODING));
  const auto authorization = VapidAuthorize(endpoint, SUBSCRIBER,
                                        QDateTime::currentSecsSinceEpoch() + (12 * 3600), *pECKey);

  Payload payload{};
  payload.m_Cipher.resize(payload.m_CipherLength = GetCipherLength(
                 ECE_WEBPUSH_DEFAULT_RS + ECE_TAG_LENGTH, ECE_AESGCM_PAD_SIZE, 0, content.size()));

  ::ece_webpush_aesgcm_encrypt(subscription.m_P256dh, ECE_WEBPUSH_PUBLIC_KEY_LENGTH,
                               subscription.m_Auth, ECE_WEBPUSH_AUTH_SECRET_LENGTH,
                               ECE_WEBPUSH_DEFAULT_RS, 0,
                               reinterpret_cast<const uint8_t*>(content.data()), content.size(),
                               payload.m_Salt, ECE_SALT_LENGTH,
                               payload.m_PublicKeySender, ECE_WEBPUSH_PUBLIC_KEY_LENGTH,
                               reinterpret_cast<uint8_t*>(payload.m_Cipher.data()),
                               &payload.m_CipherLength);

  WebPush(endpoint, authorization, vapidPublicKey, payload);
}

请注意,您的 PAYLOAD 可能会有所不同,即接受 JSON。您可能必须以这样的方式设计worker.js,使其接受与您的框架相关的格式。

c++ qt push-notification microsoft-edge web-push
1个回答
0
投票

可以通过在发送

encryption
标头时进行细微更改来解决此问题。您必须删除
rs=4096;
值之前的字符串
salt

 #define HTTP_RS_SALT "salt="

此外,对于

application/octet-stream
标题,更喜欢使用
content-type
,而不是
application/json

© www.soinside.com 2019 - 2024. All rights reserved.