From ceb62dd9d0d86bc8327ace116930962cf7fad1e9 Mon Sep 17 00:00:00 2001 From: Melvin Keskin Date: Mon, 9 May 2022 21:45:49 +0200 Subject: Implement XEP-0384: OMEMO Encryption v0.8 This implements XEP-0384 in version v0.8 with a manager and storage classes to be user-implemented for persistant storage. The license of the code is LGPL-2.1-or-later as usual. However since libomemo-c (libsignal-protocol-c) is GPL-3.0, the built binary is always licensed under GPL-3.0. Having our code LGPL licensed will make it avoids relicensing in the future in case we port it to an LGPL compatible omemo library. Closes #133. Co-authored-by: Linus Jahn --- src/client/OmemoCryptoProvider.cpp | 239 +++++++++++++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 src/client/OmemoCryptoProvider.cpp (limited to 'src/client/OmemoCryptoProvider.cpp') diff --git a/src/client/OmemoCryptoProvider.cpp b/src/client/OmemoCryptoProvider.cpp new file mode 100644 index 00000000..e39124d4 --- /dev/null +++ b/src/client/OmemoCryptoProvider.cpp @@ -0,0 +1,239 @@ +// SPDX-FileCopyrightText: 2021 Linus Jahn +// SPDX-FileCopyrightText: 2022 Melvin Keskin +// +// SPDX-License-Identifier: LGPL-2.1-or-later + +#include "OmemoCryptoProvider.h" + +#include "QXmppOmemoManager_p.h" +#include "QXmppUtils_p.h" + +#include +#include + +using namespace QXmpp::Private; + +inline QXmppOmemoManagerPrivate *managerPrivate(void *ptr) +{ + return reinterpret_cast(ptr); +} + +static int random_func(uint8_t *data, size_t len, void *) +{ + generateRandomBytes(data, len); + return 0; +} + +int hmac_sha256_init_func(void **hmac_context, const uint8_t *key, size_t key_len, void *user_data) +{ + auto *d = managerPrivate(user_data); + + if (!QCA::MessageAuthenticationCode::supportedTypes().contains(PAYLOAD_MESSAGE_AUTHENTICATION_CODE_TYPE)) { + d->warning("Message authentication code type '" % QString(PAYLOAD_MESSAGE_AUTHENTICATION_CODE_TYPE) % "' is not supported by this system"); + return -1; + } + + QCA::SymmetricKey authenticationKey(QByteArray(reinterpret_cast(key), key_len)); + *hmac_context = new QCA::MessageAuthenticationCode(PAYLOAD_MESSAGE_AUTHENTICATION_CODE_TYPE, authenticationKey); + return 0; +} + +int hmac_sha256_update_func(void *hmac_context, const uint8_t *data, size_t data_len, void *) +{ + auto *messageAuthenticationCodeGenerator = reinterpret_cast(hmac_context); + messageAuthenticationCodeGenerator->update(QCA::MemoryRegion(QByteArray(reinterpret_cast(data), data_len))); + return 0; +} + +int hmac_sha256_final_func(void *hmac_context, signal_buffer **output, void *user_data) +{ + auto *d = managerPrivate(user_data); + auto *messageAuthenticationCodeGenerator = reinterpret_cast(hmac_context); + + auto messageAuthenticationCode = messageAuthenticationCodeGenerator->final(); + if (!(*output = signal_buffer_create(reinterpret_cast(messageAuthenticationCode.constData()), messageAuthenticationCode.size()))) { + d->warning("Message authentication code could not be loaded"); + return -1; + } + + return 0; +} + +void hmac_sha256_cleanup_func(void *hmac_context, void *) +{ + auto *messageAuthenticationCodeGenerator = reinterpret_cast(hmac_context); + delete messageAuthenticationCodeGenerator; +} + +int sha512_digest_init_func(void **digest_context, void *) +{ + *digest_context = new QCryptographicHash(QCryptographicHash::Sha512); + return 0; +} + +int sha512_digest_update_func(void *digest_context, const uint8_t *data, size_t data_len, void *) +{ + auto *hashGenerator = reinterpret_cast(digest_context); + hashGenerator->addData(reinterpret_cast(data), data_len); + return 0; +} + +int sha512_digest_final_func(void *digest_context, signal_buffer **output, void *user_data) +{ + auto *d = managerPrivate(user_data); + auto *hashGenerator = reinterpret_cast(digest_context); + + auto hash = hashGenerator->result(); + if (!(*output = signal_buffer_create(reinterpret_cast(hash.constData()), hash.size()))) { + d->warning("Hash could not be loaded"); + return -1; + } + + return 0; +} + +void sha512_digest_cleanup_func(void *digest_context, void *) +{ + auto *hashGenerator = reinterpret_cast(digest_context); + delete hashGenerator; +} + +int encrypt_func(signal_buffer **output, + int cipher, + const uint8_t *key, size_t key_len, + const uint8_t *iv, size_t iv_len, + const uint8_t *plaintext, size_t plaintext_len, + void *user_data) +{ + auto *d = managerPrivate(user_data); + + QString cipherName; + + switch (key_len) { + case 128 / 8: + cipherName = QStringLiteral("aes128"); + break; + case 192 / 8: + cipherName = QStringLiteral("aes192"); + break; + case 256 / 8: + cipherName = QStringLiteral("aes256"); + break; + default: + return -1; + } + + QCA::Cipher::Mode mode; + QCA::Cipher::Padding padding; + + switch (cipher) { + case SG_CIPHER_AES_CTR_NOPADDING: + mode = QCA::Cipher::CTR; + padding = QCA::Cipher::NoPadding; + break; + case SG_CIPHER_AES_CBC_PKCS5: + mode = QCA::Cipher::CBC; + padding = QCA::Cipher::PKCS7; + break; + default: + return -2; + } + + const auto encryptionKey = QCA::SymmetricKey(QByteArray(reinterpret_cast(key), key_len)); + const auto initializationVector = QCA::InitializationVector(QByteArray(reinterpret_cast(iv), iv_len)); + QCA::Cipher encryptionCipher(cipherName, mode, padding, QCA::Encode, encryptionKey, initializationVector); + + auto encryptedData = encryptionCipher.process(QCA::MemoryRegion(QByteArray(reinterpret_cast(plaintext), plaintext_len))); + + if (encryptedData.isEmpty()) { + return -3; + } + + if (!(*output = signal_buffer_create(reinterpret_cast(encryptedData.constData()), encryptedData.size()))) { + d->warning("Encrypted data could not be loaded"); + return -4; + } + + return 0; +} + +int decrypt_func(signal_buffer **output, + int cipher, + const uint8_t *key, size_t key_len, + const uint8_t *iv, size_t iv_len, + const uint8_t *ciphertext, size_t ciphertext_len, + void *user_data) +{ + auto *d = managerPrivate(user_data); + + QString cipherName; + + switch (key_len) { + case 128 / 8: + cipherName = QStringLiteral("aes128"); + break; + case 192 / 8: + cipherName = QStringLiteral("aes192"); + break; + case 256 / 8: + cipherName = QStringLiteral("aes256"); + break; + default: + return -1; + } + + QCA::Cipher::Mode mode; + QCA::Cipher::Padding padding; + + switch (cipher) { + case SG_CIPHER_AES_CTR_NOPADDING: + mode = QCA::Cipher::CTR; + padding = QCA::Cipher::NoPadding; + break; + case SG_CIPHER_AES_CBC_PKCS5: + mode = QCA::Cipher::CBC; + padding = QCA::Cipher::PKCS7; + break; + default: + return -2; + } + + const auto encryptionKey = QCA::SymmetricKey(QByteArray(reinterpret_cast(key), key_len)); + const auto initializationVector = QCA::InitializationVector(QByteArray(reinterpret_cast(iv), iv_len)); + QCA::Cipher decryptionCipher(cipherName, mode, padding, QCA::Decode, encryptionKey, initializationVector); + + auto decryptedData = decryptionCipher.process(QCA::MemoryRegion(QByteArray(reinterpret_cast(ciphertext), ciphertext_len))); + + if (decryptedData.isEmpty()) { + return -3; + } + + if (!(*output = signal_buffer_create(reinterpret_cast(decryptedData.constData()), decryptedData.size()))) { + d->warning("Decrypted data could not be loaded"); + return -4; + } + + return 0; +} + +namespace QXmpp::Omemo::Private { + +signal_crypto_provider createOmemoCryptoProvider(QXmppOmemoManagerPrivate *d) +{ + return { + random_func, + hmac_sha256_init_func, + hmac_sha256_update_func, + hmac_sha256_final_func, + hmac_sha256_cleanup_func, + sha512_digest_init_func, + sha512_digest_update_func, + sha512_digest_final_func, + sha512_digest_cleanup_func, + encrypt_func, + decrypt_func, + d, + }; +} + +} // namespace QXmpp::Omemo::Private -- cgit v1.2.3