c++ - How to make WebPush (using Qt) work with Windows Edge browser? - Stack Overflow

admin2025-05-02  0

My implementation of Web-Push works fine with Chrome & Firefox, but not with MS edge (even after % decoding).

Question: How to make it work?

FYI, their URLs appear in different formats:

Chrome URL: ":APA91bGkIsaIeVWAG-cU-UysXKl5pevjnZzIRT0GS69gx0ZKbnbrOD4FB7m8Ou8ZpS0X0hcEeSLjxYdsYKi896YibgFrocO7L8qDU2SeZfr6L7Pqc2DZ7A_82Qik7PINAF_S2rskKofe"

Edge URL: "/?token=BQYAAACbMWA0QtGcCaM00KJntpBBrdmzSvWUnBmgRwUFnM6fJEySP1Jr0Fi3OU2rgTICDfl2Bsj6jS4eNZLo0FQK1diyqN1v6zi7k9yBijIksEvKegd2q2Z%2bGAIoIt0QfnQyluOGSNgXCrF0jr4h3Ka5aJUVdl7aBSkkULq5PI7wvGl3mGMD9I3xk71jG%2bjBAwoF2ThvYfefEEpd5xMAJ%2bWLBd3FD56kD1zTplOhwS4Leysw3SFBXh393%2b8MfDFJCAi%2f0mfKLBy%2fTCuha50GJT7oBJHemhFCi5E2CliZ9dFB2IPCpEa%2bEfgVWHC6GkpRMjUSCs0%3d"

Error printed at the client side:

400: "Error transferring -server replied: "

ChatGPT says that server is not obliged to give the actual text of what exactly went wrong to prevent its internal workings.
For Chrome it simply returns 201 and a notification is seen in the dashboard.
Note that, the same VAPID keys, Endpoint, P256DH, AUTH etc. combination works with a known C# push notification API (github).


Source code: From pusha(github) library, I derived the relevant part and implemented a minimal working example with Qt framework. One may have to add some files of ecec(github) library. Once done, only with below single source file it works fine!

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 ":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);
}

Note that, your PAYLOAD might be different, which is accepting a JSON. You may have to design the worker.js in such a way that it accepts a relevant format for your framework.

My implementation of Web-Push works fine with Chrome & Firefox, but not with MS edge (even after % decoding).

Question: How to make it work?

FYI, their URLs appear in different formats:

Chrome URL: "https://fcm.googleapis.com/fcm/send/eBt-ioGXt4A:APA91bGkIsaIeVWAG-cU-UysXKl5pevjnZzIRT0GS69gx0ZKbnbrOD4FB7m8Ou8ZpS0X0hcEeSLjxYdsYKi896YibgFrocO7L8qDU2SeZfr6L7Pqc2DZ7A_82Qik7PINAF_S2rskKofe"

Edge URL: "https://wns2-pn1p.notify.windows.com/w/?token=BQYAAACbMWA0QtGcCaM00KJntpBBrdmzSvWUnBmgRwUFnM6fJEySP1Jr0Fi3OU2rgTICDfl2Bsj6jS4eNZLo0FQK1diyqN1v6zi7k9yBijIksEvKegd2q2Z%2bGAIoIt0QfnQyluOGSNgXCrF0jr4h3Ka5aJUVdl7aBSkkULq5PI7wvGl3mGMD9I3xk71jG%2bjBAwoF2ThvYfefEEpd5xMAJ%2bWLBd3FD56kD1zTplOhwS4Leysw3SFBXh393%2b8MfDFJCAi%2f0mfKLBy%2fTCuha50GJT7oBJHemhFCi5E2CliZ9dFB2IPCpEa%2bEfgVWHC6GkpRMjUSCs0%3d"

Error printed at the client side:

400: "Error transferring -server replied: "

ChatGPT says that server is not obliged to give the actual text of what exactly went wrong to prevent its internal workings.
For Chrome it simply returns 201 and a notification is seen in the dashboard.
Note that, the same VAPID keys, Endpoint, P256DH, AUTH etc. combination works with a known C# push notification API (github).


Source code: From pusha(github) library, I derived the relevant part and implemented a minimal working example with Qt framework. One may have to add some files of ecec(github) library. Once done, only with below single source file it works fine!

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);
}

Note that, your PAYLOAD might be different, which is accepting a JSON. You may have to design the worker.js in such a way that it accepts a relevant format for your framework.

Share Improve this question edited Jan 6 at 4:22 iammilind asked Jan 2 at 5:04 iammilindiammilind 70.2k38 gold badges184 silver badges358 bronze badges 4
  • 1 Can you explain how it "does not work" with MS edge? You're showing a bit of code that talks to cloud APIs but nothing client-side.. – Botje Commented Jan 2 at 9:12
  • 1 @Botje, I have updated the post with the error code 400. The code itself is the client side which requests the push notification server with a payload. For MS Edge simply no notification is seen. However, if I send the notification with another 3rd party service, the same thing works. – iammilind Commented Jan 2 at 9:43
  • 1 I don't see how the client browser is involved here. The server is clearly unhappy with your request. – Botje Commented Jan 2 at 9:49
  • 1 @Botje, I am not getting your point about the client browser. If you ask the worker.js code, then it's irrelevant & cannot be displayed. I consider the Qt binary as the client and WNS as a [push] server. The Windows Edge browser is the ultimate beneficiary, who would receive the notify. As per the error long printed above, the notification is rejected at the server level itself with 400 error code. The same framework works fiine with GCM push server. – iammilind Commented Jan 3 at 2:14
Add a comment  | 

1 Answer 1

Reset to default 1

This issue can be resolved by making minor changes while sending the encryption header. You must remove the string rs=4096; before the salt value.

 #define HTTP_RS_SALT "salt="

Also, prefer application/octet-stream for the content-type header over application/json.

转载请注明原文地址:http://anycun.com/QandA/1746133384a92036.html