KUnifiedPush

contentencryptor.cpp
1/*
2 SPDX-FileCopyrightText: 2025 Volker Krause <vkrause@kde.org>
3 SPDX-License-Identifier: LGPL-2.0-or-later
4*/
5
6#include "contentencryptor_p.h"
7
8#include "../shared/contentencryptionutils_p.h"
9#include "../shared/eckey_p.h"
10
11#include <QtEndian>
12#include <QDebug>
13
14#include <openssl/ec.h>
15#include <openssl/err.h>
16#include <openssl/params.h>
17
18using namespace KUnifiedPush;
19
20ContentEncryptor::ContentEncryptor(const QByteArray &userAgentPublicKey, const QByteArray &authSecret)
21 : m_userAgentPublicKey(userAgentPublicKey)
22 , m_authSecret(authSecret)
23{
24}
25
26ContentEncryptor::~ContentEncryptor() = default;
27
28QByteArray ContentEncryptor::encrypt(const QByteArray &msg)
29{
30 // application server EC keypair
31 const openssl::evp_pkey_ptr key(EVP_EC_gen("prime256v1"));
32 const auto keyPair = ECKey::store(key, EVP_PKEY_PUBLIC_KEY);
33 // user agent EC public key
34 const auto peerKey = ECKey::load(m_userAgentPublicKey);
35 if (!key || !peerKey) {
36 qWarning() << "Failed to load EC keys!";
37 return {};
38 }
39
40 // salt
41 const auto salt = ContentEcryptionUtils::random(CE_SALT_SIZE);
42 if (salt.size() != CE_SALT_SIZE) {
43 return {};
44 }
45
46 // shared ECDH secret
47 const auto ecdh_secret = ContentEcryptionUtils::ecdhSharedSecret(key, peerKey);
48 if (ecdh_secret.isEmpty()) {
49 return {};
50 }
51
52 // determine content encoding key and nonce
53 const auto prk_key = ContentEcryptionUtils::hmacSha256(m_authSecret, ecdh_secret);
54 const QByteArray key_info = QByteArrayView("WebPush: info") + '\x00' + m_userAgentPublicKey + keyPair.publicKey + '\x01';
55 const auto ikm = ContentEcryptionUtils::hmacSha256(prk_key, key_info);
56 const auto prk = ContentEcryptionUtils::hmacSha256(salt, ikm);
57 const auto cek = ContentEcryptionUtils::cek(prk);
58 const auto nonce = ContentEcryptionUtils::nonce(prk);
59
60 // AES 128 GCM decryption with 16 byte AEAD tag
61 const QByteArray input = msg + (char)CE_MESSAGE_PADDING;
62 // TODO inplace into encoded?
63 QByteArray encrypted(input.size() + CE_AEAD_TAG_SIZE + 16, Qt::Uninitialized);
64 int encryptedLen = 0, len = 0;
65
66 openssl::evp_cipher_ctx_ptr aesCtx(EVP_CIPHER_CTX_new());
67 EVP_EncryptInit(aesCtx.get(), EVP_aes_128_gcm(), reinterpret_cast<const uint8_t*>(cek.constData()), reinterpret_cast<const uint8_t*>(nonce.constData()));
68 EVP_EncryptUpdate(aesCtx.get(), reinterpret_cast<uint8_t*>(encrypted.data()), &len, reinterpret_cast<const uint8_t*>(input.constData()), (int)input.size());
69 encryptedLen = len;
70 if (EVP_EncryptFinal(aesCtx.get(), reinterpret_cast<uint8_t*>(encrypted.data() + len), &len) != 1) {
71 qWarning() << ERR_error_string(ERR_get_error(), nullptr);
72 return {};
73 }
74 encrypted.resize(encryptedLen + len + CE_AEAD_TAG_SIZE);
75 if (EVP_CIPHER_CTX_ctrl(aesCtx.get(), EVP_CTRL_GCM_GET_TAG, CE_AEAD_TAG_SIZE, reinterpret_cast<uint8_t*>(encrypted.data() + encrypted.size() - CE_AEAD_TAG_SIZE)) != 1) {
76 qWarning() << ERR_error_string(ERR_get_error(), nullptr);
77 return {};
78 }
79
80 //
81 // created encoded message
82 //
83 QByteArray encoded;
84
85 // 16 byte salt
86 encoded += salt;
87
88 // 4 byte record size
89 const auto rs = qToBigEndian<uint32_t>(CE_RECORD_SIZE);
90 encoded += QByteArrayView(reinterpret_cast<const char*>(&rs), 4);
91
92 // 1 byte key len + application server public key
93 const char idlen = (char)(uint8_t)keyPair.publicKey.size();
94 encoded += idlen;
95 encoded += keyPair.publicKey;
96
97 // actual message
98 encoded += encrypted;
99 return encoded;
100}
Client-side integration with UnifiedPush.
Definition connector.h:14
const char * constData() const const
qsizetype size() 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.