Anonymous
Wie funktioniert WebPush (mit Qt) mit dem Windows Edge-Browser?
Post
by Anonymous » 06 Jan 2025, 05:22
Meine Implementierung von Web-Push funktioniert gut mit Chrome und Firefox, aber nicht mit MS Edge (auch nicht nach %-Dekodierung).
Frage : Anleitung Damit es funktioniert?
Zu Ihrer Information, ihre URLs erscheinen in verschiedenen Formaten:
Chrome-URL:
"
https://fcm.googleapis.com/fcm/send/eBt ... 0GS69gx0ZK bnbrOD4FB7m8Ou8ZpS0X0hcEeSLjxYdsYKi896YibgFrocO7L8qDU2SeZfr6L7Pqc2DZ7A_82Qik7PINAF_S2rskKofe"
Edge-URL:
"
https://wns2-pn1p.notify.windows.com/w/ ... wUFnM6fJEy SP1Jr0Fi3OU2rgTICDfl2Bsj6jS4eNZLo0FQK1diyqN1v6zi7k9yBijIksEvKegd2q2Z%2bGAIoIt0QfnQyluOGSNgXCrF0jr4h3 Ka5aJUVdl7aBSkkULq5PI7wvGl3mGMD9I3xk71jG%2bjBAwoF2ThvYfefEEpd5xMAJ%2bWLBd3FD56kD1zTplOhwS4Leysw3SFBX h393%2b8MfDFJCAi%2f0mfKLBy%2fTCuha50GJT7oBJHemhFCi5E2CliZ9dFB2IPCpEa%2bEfgVWHC6GkpRMjUSCs0%3d"
Auf der Clientseite wird ein Fehler ausgegeben:
400: „Fehler beim Übertragen der Antwort des Servers: „
ChatGPT sagt, dass der Server nicht verpflichtet ist, den tatsächlichen Text dessen anzugeben, was genau schief gelaufen ist, um seine internen Abläufe zu verhindern.
Für Chrome wird einfach 201 zurückgegeben und eine Benachrichtigung wird im angezeigt Dashboard.
Beachten Sie, dass die gleiche Kombination aus VAPID-Schlüsseln, Endpunkt, P256DH, AUTH usw. mit einer bekannten C#-Push-Benachrichtigungs-API (Github) funktioniert.
Quellcode: Aus der Bibliothek pusha(github) habe ich den relevanten Teil abgeleitet und ein minimales Arbeitsbeispiel mit dem Qt-Framework implementiert. Möglicherweise müssen einige Dateien der ecec(github)-Bibliothek hinzugefügt werden. Sobald dies erledigt ist, funktioniert es nur mit der folgenden einzelnen Quelldatei einwandfrei!
web-push.pro
Code: Select all
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
Code: Select all
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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{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(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(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(signValue.data()));
::BN_bn2bin(pS, reinterpret_cast(&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 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(payload.m_PublicKeySender),
ECE_WEBPUSH_PUBLIC_KEY_LENGTH).toBase64(QT_URL_ENCODING),
salt = QByteArray(reinterpret_cast(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() attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt()
1736137363
Anonymous
Meine Implementierung von Web-Push funktioniert gut mit Chrome und Firefox, aber nicht mit MS Edge (auch nicht nach %-Dekodierung). [b]Frage[/b]: Anleitung Damit es funktioniert? Zu Ihrer Information, ihre URLs erscheinen in verschiedenen Formaten: Chrome-URL: "https://fcm.googleapis.com/fcm/send/eBt-ioGXt4A:APA91bGkIsaIeVWAG-cU-UysXKl5pevjnZzIRT0GS69gx0ZK bnbrOD4FB7m8Ou8ZpS0X0hcEeSLjxYdsYKi896YibgFrocO7L8qDU2SeZfr6L7Pqc2DZ7A_82Qik7PINAF_S2rskKofe" Edge-URL: "https://wns2-pn1p.notify.windows.com/w/?token=BQYAAACbMWA0QtGcCaM00KJntpBBrdmzSvWUnBmgRwUFnM6fJEy SP1Jr0Fi3OU2rgTICDfl2Bsj6jS4eNZLo0FQK1diyqN1v6zi7k9yBijIksEvKegd2q2Z%2bGAIoIt0QfnQyluOGSNgXCrF0jr4h3 Ka5aJUVdl7aBSkkULq5PI7wvGl3mGMD9I3xk71jG%2bjBAwoF2ThvYfefEEpd5xMAJ%2bWLBd3FD56kD1zTplOhwS4Leysw3SFBX h393%2b8MfDFJCAi%2f0mfKLBy%2fTCuha50GJT7oBJHemhFCi5E2CliZ9dFB2IPCpEa%2bEfgVWHC6GkpRMjUSCs0%3d" Auf der Clientseite wird ein Fehler ausgegeben: 400: „Fehler beim Übertragen der Antwort des Servers: „ ChatGPT sagt, dass der Server nicht verpflichtet ist, den tatsächlichen Text dessen anzugeben, was genau schief gelaufen ist, um seine internen Abläufe zu verhindern. Für Chrome wird einfach 201 zurückgegeben und eine Benachrichtigung wird im angezeigt Dashboard. Beachten Sie, dass die gleiche Kombination aus VAPID-Schlüsseln, Endpunkt, P256DH, AUTH usw. mit einer bekannten C#-Push-Benachrichtigungs-API (Github) funktioniert. Quellcode: Aus der Bibliothek pusha(github) habe ich den relevanten Teil abgeleitet und ein minimales Arbeitsbeispiel mit dem Qt-Framework implementiert. Möglicherweise müssen einige Dateien der ecec(github)-Bibliothek hinzugefügt werden. Sobald dies erledigt ist, funktioniert es nur mit der folgenden einzelnen Quelldatei einwandfrei! [b]web-push.pro[/b] [code]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 [/code] [b]main.cpp[/b] [code]#include #include #include #include #include #include #include #include #include #include #include #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{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(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(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(signValue.data())); ::BN_bn2bin(pS, reinterpret_cast(&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 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(payload.m_PublicKeySender), ECE_WEBPUSH_PUBLIC_KEY_LENGTH).toBase64(QT_URL_ENCODING), salt = QByteArray(reinterpret_cast(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() attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt()