KUnifiedPush

vapid.cpp
1/*
2 SPDX-FileCopyrightText: 2025 Volker Krause <vkrause@kde.org>
3 SPDX-License-Identifier: LGPL-2.0-or-later
4*/
5
6#include "vapid_p.h"
7
8#include "../shared/eckey_p.h"
9#include "../shared/opensslpp_p.h"
10
11#include <QJsonDocument>
12#include <QJsonObject>
13
14#include <openssl/err.h>
15
16using namespace Qt::Literals;
17using namespace KUnifiedPush;
18
19Vapid::Vapid(const QByteArray &publicKey, const QByteArray &privateKey)
20 : m_publicKey(publicKey)
21 , m_privateKey(privateKey)
22{
23}
24
25Vapid::~Vapid() = default;
26
27QByteArray Vapid::authorization(const QUrl &endpoint) const
28{
29 const auto header = QByteArray(R"({"typ":"JWT","alg":"ES256"})").toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
30
31 auto url(endpoint);
32 url.setUserName({});
33 url.setPassword({});
34 url.setPath({});
35
36 const auto claimObj = QJsonObject({
37 {"aud"_L1, url.toString()},
38 {"exp"_L1, QDateTime::currentDateTimeUtc().addDuration(std::chrono::hours(12)).toSecsSinceEpoch()},
39 {"sub"_L1, m_contact.isEmpty() ? u"https://invent.kde.org/libraries/kunifiedpush"_s : m_contact}
40 });
42
43 const QByteArray token = header + '.' + claim;
44
45 const auto key = ECKey::load(m_publicKey, m_privateKey);
46 const openssl::evp_md_ctx_ptr mdCtx(EVP_MD_CTX_new());
47 EVP_DigestSignInit(mdCtx.get(), nullptr, EVP_sha256(), nullptr, key.get());
48
49 std::size_t len;
50 if (EVP_DigestSign(mdCtx.get(), nullptr, &len, reinterpret_cast<const uint8_t*>(token.constData()), token.size()) != 1) {
51 qWarning() << "Failed to determine VAPID JWT signature size" << ERR_error_string(ERR_get_error(), nullptr);
52 return {};
53 }
54
55 QByteArray derSignature((qsizetype)len, Qt::Uninitialized);
56 if (EVP_DigestSign(mdCtx.get(), reinterpret_cast<uint8_t*>(derSignature.data()), &len, reinterpret_cast<const uint8_t*>(token.constData()), token.size()) != 1) {
57 qWarning() << "Failed to sign VAPID JWT" << ERR_error_string(ERR_get_error(), nullptr);
58 return {};
59 }
60
61 // convert DER signature to raw r || s format
62 auto *derSigIt = reinterpret_cast<const uint8_t*>(derSignature.constData());
63 openssl::ecdsa_sig_ptr ecdsaSig(d2i_ECDSA_SIG(nullptr, &derSigIt, derSignature.size()));
64
65 QByteArray rawSignature(64, '\0');
66 const auto r = ECDSA_SIG_get0_r(ecdsaSig.get());
67 const auto rSize = BN_num_bytes(r);
68 if (rSize > 32) {
69 qWarning() << "Invalid r size" << rSize;
70 return {};
71 }
72 BN_bn2bin(r, reinterpret_cast<uint8_t*>(rawSignature.data() + 32 - rSize));
73
74 const auto s = ECDSA_SIG_get0_s(ecdsaSig.get());
75 const auto sSize = BN_num_bytes(s);
76 if (rSize > 32) {
77 qWarning() << "Invalid s size" << sSize;
78 return {};
79 }
80 BN_bn2bin(s, reinterpret_cast<uint8_t*>(rawSignature.data() + 64 - sSize));
81
82 // assemble HTTP header argument
83 return "vapid t=" + token + '.' + rawSignature.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)
85}
Client-side integration with UnifiedPush.
Definition connector.h:14
const char * constData() const const
qsizetype size() const const
QByteArray toBase64(Base64Options options) const const
QDateTime addDuration(std::chrono::milliseconds msecs) const const
QDateTime currentDateTimeUtc()
qint64 toSecsSinceEpoch() const const
QByteArray toJson(JsonFormat format) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Apr 25 2025 12:05:39 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.