Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F35313454
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
41 KB
Subscribers
None
View Options
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 609a58c..fee2717 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -1,202 +1,199 @@
# SPDX-FileCopyrightText: 2023 Carl Schwan <carl.schwan@gnupg.com>
# SPDX-License-Identifier: BSD-3-Clause
ecm_setup_version(PROJECT
VARIABLE_PREFIX MIMETREEPARSER_CORE
VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/mimetreeparser_core_version.h"
PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KPim6MimeTreeParserCoreConfigVersion.cmake"
SOVERSION 6
)
# private library
add_library(mimetreeparserprivate STATIC)
set_target_properties(mimetreeparserprivate PROPERTIES POSITION_INDEPENDENT_CODE ON)
target_sources(mimetreeparserprivate PUBLIC
messagepart.h
messagepart.cpp
crypto.h
crypto.cpp
cryptohelper.h
cryptohelper.cpp
utils.h
utils.cpp
mailtemplates.h
mailtemplates.cpp
mailcrypto.h
mailcrypto.cpp
)
target_link_libraries(mimetreeparserprivate
PUBLIC
KPim6::Mime
KF6::I18n
Qt6::Gui
KF6::Codecs
Qt6::Core5Compat
Gpgme::Gpgme
)
ecm_qt_declare_logging_category(mimetreeparserprivate
HEADER mimetreeparser_core_debug.h
IDENTIFIER MIMETREEPARSER_CORE_LOG
CATEGORY_NAME org.kde.pim.mimetreeparser.core
DESCRIPTION "mimetreeparser (pim lib)"
EXPORT MIMETREEPARSERNG
)
# public dynamic library
add_library(KPim6MimeTreeParserCore)
add_library(KPim6::MimeTreeParserCore
ALIAS KPim6MimeTreeParserCore
)
target_sources(KPim6MimeTreeParserCore PRIVATE
errors.h
async.h
attachmentmodel.h
bodypartformatter.h
bodypartformatterbasefactory.h
bodypartformatterbasefactory_p.h
enums.h
htmlutils.h
messageparser.h
objecttreeparser.h
partmetadata.h
partmodel.h
attachmentmodel.cpp
bodypartformatter.cpp
bodypartformatter_impl.cpp
bodypartformatterbasefactory.cpp
htmlutils.cpp
messageparser.cpp
objecttreeparser.cpp
partmodel.cpp
)
if (COMPILE_WITH_UNITY_CMAKE_SUPPORT)
set_target_properties(KPim6MimeTreeParserCore PROPERTIES UNITY_BUILD ON)
endif()
generate_export_header(KPim6MimeTreeParserCore BASE_NAME mimetreeparser_core)
target_include_directories(KPim6MimeTreeParserCore
INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR}/KPim6/MimeTreeParserCore>")
-target_include_directories(KPim6MimeTreeParserCore
- PUBLIC "$<BUILD_INTERFACE:${MimeTreeParserCore_BINARY_DIR}>")
-
target_link_libraries(KPim6MimeTreeParserCore
PUBLIC
KPim6::Mime
KF6::I18n
Qt6::Gui
PRIVATE
mimetreeparserprivate
)
set_target_properties(KPim6MimeTreeParserCore PROPERTIES
VERSION ${MIMETREEPARSERNG_VERSION}
SOVERSION ${MIMETREEPARSERNG_SOVERSION}
EXPORT_NAME MimeTreeParserCore
)
ecm_generate_pri_file(BASE_NAME MimeTreeParserCore
LIB_NAME KPim6MimeTreeParserCore
DEPS "MimeTreeParserCore"
FILENAME_VAR PRI_FILENAME
)
install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR})
install(TARGETS
KPim6MimeTreeParserCore
EXPORT KPim6MimeTreeParserCoreTargets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}
)
ecm_generate_headers(MimeTreeParserCore_CamelCase_HEADERS
HEADER_NAMES
AttachmentModel
ObjectTreeParser
MessageParser
MessagePart
PartModel
MailCrypto
Crypto
Errors
PartMetaData
MailTemplates
REQUIRED_HEADERS MimeTreeParserCore_HEADERS
PREFIX MimeTreeParserCore
)
install(FILES
${MimeTreeParserCore_CamelCase_HEADERS}
DESTINATION ${KDE_INSTALL_INCLUDEDIR}/KPim6/MimeTreeParserCore/MimeTreeParserCore
COMPONENT Devel
)
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/mimetreeparser_core_export.h
${MimeTreeParserCore_HEADERS}
DESTINATION ${KDE_INSTALL_INCLUDEDIR}/KPim6/MimeTreeParserCore/mimetreeparsercore
COMPONENT Devel
)
if (BUILD_QCH)
ecm_add_qch(
KPim6MimeTreeParserCore_QCH
NAME MimeTreeParserCore
BASE_NAME KPim6MimeTreeParserCore
VERSION ${PIM_VERSION}
ORG_DOMAIN org.kde
# using only public headers, to cover only public API
SOURCES ${MimeTreeParserCore_HEADERS}
MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md"
LINK_QCHS
Qt6Core_QCH
INCLUDE_DIRS
${CMAKE_CURRENT_BINARY_DIR}
BLANK_MACROS
MIMETREEPARSERCORE_EXPORT
TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
COMPONENT Devel
)
endif()
########### CMake Config Files ###########
set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KPim6MimeTreeParserCore")
if (BUILD_QCH)
ecm_install_qch_export(
TARGETS KPim6MimeTreeParserCore_QCH
FILE KPim6MimeTreeParserCoreQchTargets.cmake
DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
COMPONENT Devel
)
set(PACKAGE_INCLUDE_QCHTARGETS "include(\"\${CMAKE_CURRENT_LIST_DIR}/KPim6MimeTreeParserCoreQchTargets.cmake\")")
endif()
configure_package_config_file(
"${CMAKE_CURRENT_SOURCE_DIR}/KPimMimeTreeParserCoreConfig.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/KPim6MimeTreeParserCoreConfig.cmake"
INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR}
)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/KPim6MimeTreeParserCoreConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/KPim6MimeTreeParserCoreConfigVersion.cmake"
DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
COMPONENT Devel
)
install(EXPORT KPim6MimeTreeParserCoreTargets
DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
FILE KPim6MimeTreeParserCoreTargets.cmake
NAMESPACE KPim6::
)
if (BUILD_TESTING)
add_subdirectory(autotests)
endif()
diff --git a/src/core/crypto.cpp b/src/core/crypto.cpp
index 6dbe503..d5a4dd2 100644
--- a/src/core/crypto.cpp
+++ b/src/core/crypto.cpp
@@ -1,598 +1,593 @@
// SPDX-FileCopyrightText: 2009 Constantin Berzan <exit3219@gmail.com>
// SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
// SPDX-FileCopyrightText: 2010 Leo Franchi <lfranchi@kde.org>
// SPDX-FileCopyrightText: 2017 Christian Mollekopf <mollekopf@kolabsys.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "crypto.h"
-#ifndef _WIN32
#include <gpgme.h>
-#endif
#include <QDebug>
#include <QFile>
#include <future>
#include <utility>
using namespace Crypto;
QDebug operator<<(QDebug d, const Key &key)
{
d << key.fingerprint;
return d;
}
QDebug operator<<(QDebug d, const Error &error)
{
d << error.error;
return d;
}
-#ifndef _WIN32
namespace Crypto
{
struct Data {
Data(const QByteArray &buffer)
{
const bool copy = false;
const gpgme_error_t e = gpgme_data_new_from_mem(&data, buffer.constData(), buffer.size(), int(copy));
if (e) {
qWarning() << "Failed to copy data?" << e;
}
}
~Data()
{
gpgme_data_release(data);
}
gpgme_data_t data;
};
}
static gpgme_error_t checkEngine(CryptoProtocol protocol)
{
gpgme_check_version(nullptr);
const gpgme_protocol_t p = protocol == CMS ? GPGME_PROTOCOL_CMS : GPGME_PROTOCOL_OpenPGP;
return gpgme_engine_check_version(p);
}
static std::pair<gpgme_error_t, gpgme_ctx_t> createForProtocol(CryptoProtocol proto)
{
if (auto e = checkEngine(proto)) {
qWarning() << "GPG Engine check failed." << e;
return std::make_pair(e, nullptr);
}
gpgme_ctx_t ctx = nullptr;
if (auto e = gpgme_new(&ctx)) {
return std::make_pair(e, nullptr);
}
switch (proto) {
case OpenPGP:
if (auto e = gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP)) {
gpgme_release(ctx);
return std::make_pair(e, nullptr);
}
break;
case CMS:
if (auto e = gpgme_set_protocol(ctx, GPGME_PROTOCOL_CMS)) {
gpgme_release(ctx);
return std::make_pair(e, nullptr);
}
break;
default:
Q_ASSERT(false);
return std::make_pair(1, nullptr);
}
// We want the output to always be ASCII armored
gpgme_set_armor(ctx, 1);
// Trust new keys
if (auto e = gpgme_set_ctx_flag(ctx, "trust-model", "tofu+pgp")) {
gpgme_release(ctx);
return std::make_pair(e, nullptr);
}
// That's a great way to bring signature verification to a crawl
if (auto e = gpgme_set_ctx_flag(ctx, "auto-key-retrieve", "0")) {
gpgme_release(ctx);
return std::make_pair(e, nullptr);
}
return std::make_pair(GPG_ERR_NO_ERROR, ctx);
}
gpgme_error_t gpgme_passphrase(void *hook, const char *uid_hint, const char *passphrase_info, int prev_was_bad, int fd)
{
Q_UNUSED(hook);
Q_UNUSED(prev_was_bad);
// uid_hint will be something like "CAA5183608F0FB50 Test1 Kolab <test1@kolab.org>" (CAA518... is the key)
// pahhphrase_info will be something like "CAA5183608F0FB50 2E3B7787B1B75920 1 0"
qInfo() << "Requested passphrase for " << (uid_hint ? QByteArray{uid_hint} : QByteArray{})
<< (passphrase_info ? QByteArray{passphrase_info} : QByteArray{});
QFile file;
file.open(fd, QIODevice::WriteOnly);
// FIXME hardcoded as a test
QByteArray passphrase = QByteArray{"test1"} + QByteArray{"\n"};
file.write(passphrase);
file.close();
return 0;
}
namespace Crypto
{
struct Context {
Context(CryptoProtocol protocol = OpenPGP)
{
gpgme_error_t code;
std::tie(code, context) = createForProtocol(protocol);
error = Error{code};
}
~Context()
{
gpgme_release(context);
}
operator bool() const
{
return !error;
}
Error error;
gpgme_ctx_t context;
};
}
static QByteArray toBA(gpgme_data_t out)
{
size_t length = 0;
auto data = gpgme_data_release_and_get_mem(out, &length);
auto outdata = QByteArray{data, static_cast<int>(length)};
gpgme_free(data);
return outdata;
}
static std::vector<Recipient> copyRecipients(gpgme_decrypt_result_t result)
{
std::vector<Recipient> recipients;
for (gpgme_recipient_t r = result->recipients; r; r = r->next) {
recipients.push_back({QByteArray{r->keyid}, r->status != GPG_ERR_NO_SECKEY});
}
return recipients;
}
static std::vector<Signature> copySignatures(gpgme_verify_result_t result)
{
std::vector<Signature> signatures;
for (gpgme_signature_t is = result->signatures; is; is = is->next) {
Signature sig;
sig.fingerprint = QByteArray{is->fpr};
sig.creationTime.setSecsSinceEpoch(is->timestamp);
if (is->summary & GPGME_SIGSUM_VALID) {
sig.result = Signature::Ok;
} else {
sig.result = Signature::Invalid;
if (is->summary & GPGME_SIGSUM_KEY_EXPIRED) {
sig.result = Signature::Expired;
}
if (is->summary & GPGME_SIGSUM_KEY_MISSING) {
sig.result = Signature::KeyNotFound;
}
}
sig.status = {is->status};
sig.isTrusted = is->validity == GPGME_VALIDITY_FULL || is->validity == GPGME_VALIDITY_ULTIMATE;
signatures.push_back(sig);
}
return signatures;
}
VerificationResult Crypto::verifyDetachedSignature(CryptoProtocol protocol, const QByteArray &signature, const QByteArray &text)
{
Context context{protocol};
if (!context) {
qWarning() << "Failed to create context " << context.error;
return {{}, context.error};
}
auto ctx = context.context;
auto err = gpgme_op_verify(ctx, Data{signature}.data, Data{text}.data, nullptr);
gpgme_verify_result_t res = gpgme_op_verify_result(ctx);
return {copySignatures(res), {err}};
}
static DecryptionResult::Result toResult(gpgme_error_t err)
{
if (err == GPG_ERR_NO_DATA) {
return DecryptionResult::NotEncrypted;
} else if (err == GPG_ERR_NO_SECKEY) {
return DecryptionResult::NoSecretKeyError;
} else if (err == GPG_ERR_CANCELED || err == GPG_ERR_INV_PASSPHRASE) {
return DecryptionResult::PassphraseError;
} else if (err == GPG_ERR_NO_ERROR) {
return DecryptionResult::NoSecretKeyError;
}
qWarning() << "unknown error" << err << gpgme_strerror(err);
return DecryptionResult::NoSecretKeyError;
}
VerificationResult Crypto::verifyOpaqueSignature(CryptoProtocol protocol, const QByteArray &signature, QByteArray &outdata)
{
Context context{protocol};
if (!context) {
qWarning() << "Failed to create context " << context.error;
return VerificationResult{{}, context.error};
}
auto ctx = context.context;
gpgme_data_t out;
const gpgme_error_t e = gpgme_data_new(&out);
Q_ASSERT(!e);
auto err = gpgme_op_verify(ctx, Data{signature}.data, nullptr, out);
VerificationResult result{{}, {err}};
if (gpgme_verify_result_t res = gpgme_op_verify_result(ctx)) {
result.signatures = copySignatures(res);
}
outdata = toBA(out);
return result;
}
std::pair<DecryptionResult, VerificationResult> Crypto::decryptAndVerify(CryptoProtocol protocol, const QByteArray &ciphertext, QByteArray &outdata)
{
Context context{protocol};
if (!context) {
qWarning() << "Failed to create context " << gpgme_strerror(context.error);
qWarning() << "returning early";
return std::make_pair(DecryptionResult{{}, {context.error}, DecryptionResult::NoSecretKeyError}, VerificationResult{{}, context.error});
}
auto ctx = context.context;
gpgme_data_t out;
if (gpgme_error_t e = gpgme_data_new(&out)) {
qWarning() << "Failed to allocated data" << e;
}
auto err = gpgme_op_decrypt_verify(ctx, Data{ciphertext}.data, out);
if (err) {
qWarning() << "Failed to decrypt and verify" << Error{err};
// We make sure we don't return any plain-text if the decryption failed to prevent EFAIL
if (err == GPG_ERR_DECRYPT_FAILED) {
return std::make_pair(DecryptionResult{{}, {err}, DecryptionResult::DecryptionError}, VerificationResult{{}, {err}});
}
}
VerificationResult verificationResult{{}, {err}};
if (gpgme_verify_result_t res = gpgme_op_verify_result(ctx)) {
verificationResult.signatures = copySignatures(res);
}
DecryptionResult decryptionResult{{}, {err}};
if (gpgme_decrypt_result_t res = gpgme_op_decrypt_result(ctx)) {
decryptionResult.recipients = copyRecipients(res);
}
decryptionResult.result = toResult(err);
outdata = toBA(out);
return std::make_pair(decryptionResult, verificationResult);
}
static DecryptionResult decryptGPGME(CryptoProtocol protocol, const QByteArray &ciphertext, QByteArray &outdata)
{
Context context{protocol};
if (!context) {
qWarning() << "Failed to create context " << context.error;
return DecryptionResult{{}, context.error};
}
auto ctx = context.context;
gpgme_data_t out;
if (gpgme_error_t e = gpgme_data_new(&out)) {
qWarning() << "Failed to allocated data" << e;
}
auto err = gpgme_op_decrypt(ctx, Data{ciphertext}.data, out);
if (err) {
qWarning() << "Failed to decrypt" << gpgme_strerror(err);
// We make sure we don't return any plain-text if the decryption failed to prevent EFAIL
if (err == GPG_ERR_DECRYPT_FAILED) {
return DecryptionResult{{}, {err}};
}
}
DecryptionResult decryptionResult{{}, {err}};
if (gpgme_decrypt_result_t res = gpgme_op_decrypt_result(ctx)) {
decryptionResult.recipients = copyRecipients(res);
}
decryptionResult.result = toResult(err);
outdata = toBA(out);
return decryptionResult;
}
DecryptionResult Crypto::decrypt(CryptoProtocol protocol, const QByteArray &ciphertext, QByteArray &outdata)
{
return decryptGPGME(protocol, ciphertext, outdata);
}
ImportResult Crypto::importKey(CryptoProtocol protocol, const QByteArray &certData)
{
Context context{protocol};
if (!context) {
qWarning() << "Failed to create context " << context.error;
return {0, 0, 0};
}
if (gpgme_op_import(context.context, Data{certData}.data)) {
qWarning() << "Import failed";
return {0, 0, 0};
}
if (auto result = gpgme_op_import_result(context.context)) {
return {result->considered, result->imported, result->unchanged};
} else {
return {0, 0, 0};
}
}
static bool validateKey(const gpgme_key_t key)
{
if (key->revoked) {
qWarning() << "Key is revoked " << key->fpr;
return false;
}
if (key->expired) {
qWarning() << "Key is expired " << key->fpr;
return false;
}
if (key->disabled) {
qWarning() << "Key is disabled " << key->fpr;
return false;
}
if (key->invalid) {
qWarning() << "Key is invalid " << key->fpr;
return false;
}
return true;
}
static KeyListResult listKeys(CryptoProtocol protocol, const std::vector<const char *> &patterns, bool secretOnly, int keyListMode, bool importKeys)
{
Context context{protocol};
if (!context) {
qWarning() << "Failed to create context " << context.error;
return {{}, context.error};
}
auto ctx = context.context;
gpgme_set_keylist_mode(ctx, keyListMode);
KeyListResult result;
result.error = {GPG_ERR_NO_ERROR};
auto zeroTerminatedPatterns = patterns;
zeroTerminatedPatterns.push_back(nullptr);
if (patterns.size() > 1) {
if (auto err = gpgme_op_keylist_ext_start(ctx, const_cast<const char **>(zeroTerminatedPatterns.data()), int(secretOnly), 0)) {
result.error = {err};
qWarning() << "Error while listing keys:" << result.error;
}
} else if (patterns.size() == 1) {
if (auto err = gpgme_op_keylist_start(ctx, zeroTerminatedPatterns[0], int(secretOnly))) {
result.error = {err};
qWarning() << "Error while listing keys:" << result.error;
}
} else {
if (auto err = gpgme_op_keylist_start(ctx, nullptr, int(secretOnly))) {
result.error = {err};
qWarning() << "Error while listing keys:" << result.error;
}
}
std::vector<gpgme_key_t> listedKeys;
while (true) {
gpgme_key_t key;
if (auto err = gpgme_op_keylist_next(ctx, &key)) {
if (gpgme_err_code(err) != GPG_ERR_EOF) {
qWarning() << "Error after listing keys" << result.error << gpgme_strerror(err);
}
break;
}
listedKeys.push_back(key);
Key k;
if (key->subkeys) {
k.keyId = QByteArray{key->subkeys->keyid};
k.shortKeyId = k.keyId.right(8);
k.fingerprint = QByteArray{key->subkeys->fpr};
}
for (gpgme_user_id_t uid = key->uids; uid; uid = uid->next) {
k.userIds.push_back(UserId{QByteArray{uid->name}, QByteArray{uid->email}, QByteArray{uid->uid}});
}
k.isUsable = validateKey(key);
result.keys.push_back(k);
}
gpgme_op_keylist_end(ctx);
if (importKeys && !listedKeys.empty()) {
listedKeys.push_back(nullptr);
if (auto err = gpgme_op_import_keys(ctx, const_cast<gpgme_key_t *>(listedKeys.data()))) {
qWarning() << "Error while importing keys" << gpgme_strerror(err);
}
}
return result;
}
/**
* Get the given `key` in the armor format.
*/
Expected<Error, QByteArray> Crypto::exportPublicKey(const Key &key)
{
Context context;
if (!context) {
return makeUnexpected(Error{context.error});
}
gpgme_data_t out;
const gpgme_error_t e = gpgme_data_new(&out);
Q_ASSERT(!e);
qDebug() << "Exporting public key:" << key.keyId;
if (auto err = gpgme_op_export(context.context, key.keyId.data(), 0, out)) {
return makeUnexpected(Error{err});
}
return toBA(out);
}
Expected<Error, QByteArray> Crypto::signAndEncrypt(const QByteArray &content, const std::vector<Key> &encryptionKeys, const std::vector<Key> &signingKeys)
{
Context context;
if (!context) {
return makeUnexpected(Error{context.error});
}
for (const auto &signingKey : signingKeys) {
qDebug() << "Signing with " << signingKey;
// TODO do we have to free those again?
gpgme_key_t key;
if (auto e = gpgme_get_key(context.context, signingKey.fingerprint.data(), &key, /*secret*/ false)) {
qWarning() << "Failed to retrieve signing key " << signingKey.fingerprint << Error{e};
return makeUnexpected(Error{e});
} else {
gpgme_signers_add(context.context, key);
}
}
gpgme_key_t *const keys = new gpgme_key_t[encryptionKeys.size() + 1];
gpgme_key_t *keys_it = keys;
for (const auto &k : encryptionKeys) {
qDebug() << "Encrypting to " << k;
gpgme_key_t key;
if (auto e = gpgme_get_key(context.context, k.fingerprint.data(), &key, /*secret*/ false)) {
delete[] keys;
qWarning() << "Failed to retrieve key " << k.fingerprint << Error{e};
return makeUnexpected(Error{e});
} else {
if (!key->can_encrypt || !validateKey(key)) {
qWarning() << "Key cannot be used for encryption " << k.fingerprint;
delete[] keys;
return makeUnexpected(Error{e});
}
*keys_it++ = key;
}
}
*keys_it++ = nullptr;
gpgme_data_t out;
if (auto e = gpgme_data_new(&out)) {
qWarning() << "Failed to allocate output buffer";
delete[] keys;
return makeUnexpected(Error{e});
}
gpgme_error_t err = !signingKeys.empty() ? gpgme_op_encrypt_sign(context.context, keys, GPGME_ENCRYPT_ALWAYS_TRUST, Data{content}.data, out)
: gpgme_op_encrypt(context.context, keys, GPGME_ENCRYPT_ALWAYS_TRUST, Data{content}.data, out);
delete[] keys;
if (err) {
qWarning() << "Encryption failed:" << gpgme_err_code(err);
switch (gpgme_err_code(err)) {
case GPG_ERR_UNUSABLE_PUBKEY:
for (const auto &k : encryptionKeys) {
qWarning() << "Encryption key:" << k;
}
break;
case GPG_ERR_UNUSABLE_SECKEY:
for (const auto &k : signingKeys) {
qWarning() << "Signing key:" << k;
}
break;
default:
break;
}
return makeUnexpected(Error{err});
}
return toBA(out);
}
Expected<Error, std::pair<QByteArray, QString>> Crypto::sign(const QByteArray &content, const std::vector<Key> &signingKeys)
{
Context context;
if (!context) {
return makeUnexpected(Error{context.error});
}
for (const auto &signingKey : signingKeys) {
// TODO do we have to free those again?
gpgme_key_t key;
if (auto e = gpgme_get_key(context.context, signingKey.fingerprint.data(), &key, /*secret*/ false)) {
qWarning() << "Failed to retrieve signing key " << signingKey.fingerprint << Error{e};
return makeUnexpected(Error{e});
} else {
gpgme_signers_add(context.context, key);
}
}
gpgme_data_t out;
const gpgme_error_t e = gpgme_data_new(&out);
Q_ASSERT(!e);
if (auto err = gpgme_op_sign(context.context, Data{content}.data, out, GPGME_SIG_MODE_DETACH)) {
qWarning() << "Signing failed:" << Error{err};
return makeUnexpected(Error{err});
}
const QByteArray algo = [&] {
if (gpgme_sign_result_t res = gpgme_op_sign_result(context.context)) {
if (gpgme_new_signature_t is = res->signatures) {
return QByteArray{gpgme_hash_algo_name(is->hash_algo)};
}
}
return QByteArray{};
}();
// RFC 3156 Section 5:
// Hash-symbols are constructed [...] by converting the text name to lower
// case and prefixing it with the four characters "pgp-".
const auto micAlg = (QStringLiteral("pgp-") + QString::fromUtf8(algo)).toLower();
return std::pair<QByteArray, QString>{toBA(out), micAlg};
}
std::vector<Key> Crypto::findKeys(const QStringList &patterns, bool findPrivate, bool remote)
{
QByteArrayList list;
std::transform(patterns.constBegin(), patterns.constEnd(), std::back_inserter(list), [](const QString &s) {
return s.toUtf8();
});
std::vector<char const *> pattern;
std::transform(list.constBegin(), list.constEnd(), std::back_inserter(pattern), [](const QByteArray &s) {
return s.constData();
});
const KeyListResult res = listKeys(OpenPGP, pattern, findPrivate, remote ? GPGME_KEYLIST_MODE_EXTERN : GPGME_KEYLIST_MODE_LOCAL, remote);
if (res.error) {
qWarning() << "Failed to lookup keys: " << res.error;
return {};
}
qDebug() << "Found " << res.keys.size() << " keys for the patterns: " << patterns;
std::vector<Key> usableKeys;
for (const auto &key : res.keys) {
if (!key.isUsable) {
qWarning() << "Key is not usable: " << key.fingerprint;
continue;
}
qDebug() << "Key:" << key.fingerprint;
for (const auto &userId : key.userIds) {
qDebug() << " userID:" << userId.email;
}
usableKeys.push_back(key);
}
return usableKeys;
}
-
-#endif
diff --git a/src/core/crypto.h b/src/core/crypto.h
index 2e043a6..5f78705 100644
--- a/src/core/crypto.h
+++ b/src/core/crypto.h
@@ -1,106 +1,104 @@
// SPDX-FileCopyrightText: 2016 Christian Mollekopf <mollekopf@kolabsys.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
#pragma once
#include "errors.h"
#include "mimetreeparser_core_export.h"
#include <QByteArray>
#include <QVariant>
#include <QDateTime>
#include <functional>
#include <memory>
namespace Crypto
{
enum CryptoProtocol { UnknownProtocol, OpenPGP, CMS };
struct UserId {
QByteArray name;
QByteArray email;
QByteArray id;
};
struct Key {
QByteArray keyId;
QByteArray shortKeyId;
QByteArray fingerprint;
bool isUsable = false;
std::vector<UserId> userIds;
};
struct Error {
unsigned int error;
operator bool() const
{
return error != 0;
}
};
struct Signature {
QByteArray fingerprint;
Error status;
QDateTime creationTime;
enum Result { Ok, NotVerified, Expired, KeyNotFound, Invalid };
Result result{NotVerified};
bool isTrusted{false};
};
struct VerificationResult {
std::vector<Signature> signatures;
Error error;
};
struct Recipient {
QByteArray keyId;
bool secretKeyAvailable{false};
};
struct DecryptionResult {
std::vector<Recipient> recipients;
Error error;
enum Result { NoError, NotEncrypted, PassphraseError, NoSecretKeyError, DecryptionError };
Result result{NoError};
};
struct KeyListResult {
std::vector<Key> keys;
Error error;
};
struct ImportResult {
int considered;
int imported;
int unchanged;
};
-#ifndef _WIN32
MIMETREEPARSER_CORE_EXPORT std::vector<Key> findKeys(const QStringList &filter, bool findPrivate = false, bool remote = false);
MIMETREEPARSER_CORE_EXPORT Expected<Error, QByteArray> exportPublicKey(const Key &key);
MIMETREEPARSER_CORE_EXPORT ImportResult importKey(CryptoProtocol protocol, const QByteArray &certData);
MIMETREEPARSER_CORE_EXPORT ImportResult importKey(CryptoProtocol protocol, const Key &key);
/**
* Sign the given content and returns the signing data and the algorithm used
* for integrity check in the "pgp-<algorithm>" format.
*/
MIMETREEPARSER_CORE_EXPORT Expected<Error, std::pair<QByteArray, QString>> sign(const QByteArray &content, const std::vector<Key> &signingKeys);
MIMETREEPARSER_CORE_EXPORT Expected<Error, QByteArray>
signAndEncrypt(const QByteArray &content, const std::vector<Key> &encryptionKeys, const std::vector<Key> &signingKeys);
MIMETREEPARSER_CORE_EXPORT std::pair<DecryptionResult, VerificationResult>
decryptAndVerify(CryptoProtocol protocol, const QByteArray &ciphertext, QByteArray &outdata);
MIMETREEPARSER_CORE_EXPORT DecryptionResult decrypt(CryptoProtocol protocol, const QByteArray &ciphertext, QByteArray &outdata);
MIMETREEPARSER_CORE_EXPORT VerificationResult verifyDetachedSignature(CryptoProtocol protocol, const QByteArray &signature, const QByteArray &outdata);
MIMETREEPARSER_CORE_EXPORT VerificationResult verifyOpaqueSignature(CryptoProtocol protocol, const QByteArray &signature, QByteArray &outdata);
};
-#endif
Q_DECLARE_METATYPE(Crypto::Key);
MIMETREEPARSER_CORE_EXPORT QDebug operator<<(QDebug d, const Crypto::Key &);
MIMETREEPARSER_CORE_EXPORT QDebug operator<<(QDebug d, const Crypto::Error &);
diff --git a/src/core/mailcrypto.cpp b/src/core/mailcrypto.cpp
index 7df2fd4..6a3e32b 100644
--- a/src/core/mailcrypto.cpp
+++ b/src/core/mailcrypto.cpp
@@ -1,190 +1,179 @@
// SPDX-FileCopyrightText: 2009 Constantin Berzan <exit3219@gmail.com>
// SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
// SPDX-FileCopyrightText: 2010 Leo Franchi <lfranchi@kde.org>
// SPDX-FileCopyrightText: 2017 Christian Mollekopf <mollekopf@kolabsys.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
//
#include "mailcrypto.h"
#include <QDebug>
#include <future>
#include <utility>
using namespace MailCrypto;
using namespace Crypto;
-#ifdef _WIN32
-Expected<Error, std::unique_ptr<KMime::Content>>
-MailCrypto::processCrypto(std::unique_ptr<KMime::Content> content, const std::vector<Key> &signingKeys, const std::vector<Key> &encryptionKeys)
-{
- Q_UNUSED(content)
- Q_UNUSED(signingKeys)
- Q_UNUSED(encryptionKeys)
- return makeUnexpected(Error{});
-}
-#else
static QByteArray canonicalizeContent(KMime::Content *content)
{
// if (d->format & Kleo::InlineOpenPGPFormat) {
// return d->content->body();
// } else if (!(d->format & Kleo::SMIMEOpaqueFormat)) {
// replace "From " and "--" at the beginning of lines
// with encoded versions according to RfC 3156, 3
// Note: If any line begins with the string "From ", it is strongly
// suggested that either the Quoted-Printable or Base64 MIME encoding
// be applied.
const auto encoding = content->contentTransferEncoding()->encoding();
if ((encoding == KMime::Headers::CEquPr || encoding == KMime::Headers::CE7Bit) && !content->contentType(false)) {
QByteArray body = content->encodedBody();
bool changed = false;
QList<QByteArray> search;
QList<QByteArray> replacements;
search << "From "
<< "from "
<< "-";
replacements << "From=20"
<< "from=20"
<< "=2D";
if (content->contentTransferEncoding()->encoding() == KMime::Headers::CE7Bit) {
for (int i = 0; i < search.size(); ++i) {
const auto pos = body.indexOf(search[i]);
if (pos == 0 || (pos > 0 && body.at(pos - 1) == '\n')) {
changed = true;
break;
}
}
if (changed) {
content->contentTransferEncoding()->setEncoding(KMime::Headers::CEquPr);
content->assemble();
body = content->encodedBody();
}
}
for (int i = 0; i < search.size(); ++i) {
const auto pos = body.indexOf(search[i]);
if (pos == 0 || (pos > 0 && body.at(pos - 1) == '\n')) {
changed = true;
body.replace(pos, search[i].size(), replacements[i]);
}
}
if (changed) {
qDebug() << "Content changed";
content->setBody(body);
content->contentTransferEncoding()->setDecoded(false);
}
}
return KMime::LFtoCRLF(content->encodedContent());
// } else { // SMimeOpaque doesn't need LFtoCRLF, else it gets munged
// return content->encodedContent();
// }
}
/**
* Create a message part like this (according to RFC 3156 Section 4):
*
* - multipart/encrypted
* - application/pgp-encrypted (version information)
* - application/octet-stream (given encrypted data)
*
* The encrypted data can be generated by the `encrypt` or `signAndEncrypt` functions.
*/
std::unique_ptr<KMime::Content> createEncryptedPart(QByteArray encryptedData)
{
auto result = std::unique_ptr<KMime::Content>(new KMime::Content);
result->contentType()->setMimeType("multipart/encrypted");
result->contentType()->setBoundary(KMime::multiPartBoundary());
result->contentType()->setParameter(QStringLiteral("protocol"), QStringLiteral("application/pgp-encrypted"));
KMime::Content *controlInformation = new KMime::Content;
{
controlInformation->contentType()->setMimeType("application/pgp-encrypted");
controlInformation->contentDescription()->from7BitString("PGP/MIME version identification");
controlInformation->setBody("Version: 1");
result->addContent(controlInformation);
}
KMime::Content *encryptedPartPart = new KMime::Content;
{
const QString filename = QStringLiteral("msg.asc");
encryptedPartPart->contentType()->setMimeType("application/octet-stream");
encryptedPartPart->contentType()->setName(filename, "utf-8");
encryptedPartPart->contentDescription()->from7BitString("OpenPGP encrypted message");
encryptedPartPart->contentDisposition()->setDisposition(KMime::Headers::CDinline);
encryptedPartPart->contentDisposition()->setFilename(filename);
encryptedPartPart->setBody(encryptedData);
result->addContent(encryptedPartPart);
}
return result;
}
/**
* Create a message part like this (according to RFC 3156 Section 5):
*
* + `multipart/signed`
* - whatever the given original `message` is (should be canonicalized)
* - `application/octet-stream` (the given `signature`)
*
* The signature can be generated by the `sign` function.
*/
std::unique_ptr<KMime::Content> createSignedPart(std::unique_ptr<KMime::Content> message, const QByteArray &signature, const QString &micAlg)
{
auto result = std::unique_ptr<KMime::Content>(new KMime::Content);
result->contentType()->setMimeType("multipart/signed");
result->contentType()->setBoundary(KMime::multiPartBoundary());
result->contentType()->setParameter(QStringLiteral("micalg"), micAlg);
result->contentType()->setParameter(QStringLiteral("protocol"), QStringLiteral("application/pgp-signature"));
result->addContent(message.release());
KMime::Content *signedPartPart = new KMime::Content;
signedPartPart->contentType()->setMimeType("application/pgp-signature");
signedPartPart->contentType()->setName(QStringLiteral("signature.asc"), "utf-8");
signedPartPart->contentDisposition(true)->setDisposition(KMime::Headers::CDattachment);
signedPartPart->contentDisposition(true)->setFilename(QStringLiteral("signature.asc"));
signedPartPart->contentDescription()->from7BitString("OpenPGP digital signature");
signedPartPart->setBody(signature);
result->addContent(signedPartPart);
return result;
}
Expected<Error, std::unique_ptr<KMime::Content>>
MailCrypto::processCrypto(std::unique_ptr<KMime::Content> content, const std::vector<Key> &signingKeys, const std::vector<Key> &encryptionKeys)
{
if (!encryptionKeys.empty()) {
auto encryptionResult = signAndEncrypt(canonicalizeContent(content.get()), encryptionKeys, signingKeys);
if (!encryptionResult) {
return makeUnexpected(Error{encryptionResult.error()});
}
return createEncryptedPart(encryptionResult.value());
} else if (!signingKeys.empty()) {
auto signingResult = sign(canonicalizeContent(content.get()), signingKeys);
if (!signingResult) {
return makeUnexpected(Error{signingResult.error()});
}
QByteArray signingData;
QString micAlg;
std::tie(signingData, micAlg) = signingResult.value();
return createSignedPart(std::move(content), signingData, micAlg);
} else {
qWarning() << "Processing cryptography, but neither signing nor encrypting";
Q_ASSERT(false);
return content;
}
}
-#endif
diff --git a/src/widgets/CMakeLists.txt b/src/widgets/CMakeLists.txt
index 4c85f12..a125bbe 100644
--- a/src/widgets/CMakeLists.txt
+++ b/src/widgets/CMakeLists.txt
@@ -1,147 +1,146 @@
# SPDX-FileCopyrightText: 2023 Carl Schwan <carl.schwan@gnupg.com>
# SPDX-License-Identifier: BSD-3-Clause
ecm_setup_version(PROJECT
VARIABLE_PREFIX MIMETREEPARSER_WIDGETS
VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/mimetreeparser_widgets_version.h"
PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KPim6MimeTreeParserWidgetsConfigVersion.cmake"
SOVERSION 6
)
add_library(KPim6MimeTreeParserWidgets)
add_library(KPim6::MimeTreeParserWidgets
ALIAS KPim6MimeTreeParserWidgets)
target_sources(KPim6MimeTreeParserWidgets PRIVATE
messageviewer.h
messageviewer.cpp
attachmentview_p.h
attachmentview.cpp
)
ecm_qt_declare_logging_category(KPim6MimeTreeParserWidgets
HEADER mimetreeparser_widgets_debug.h
IDENTIFIER MIMETREEPARSER_WIDGET_LOG
CATEGORY_NAME org.kde.pim.mimetreeparser.widgets
DESCRIPTION "mimetreeparser (pim lib)"
EXPORT MIMETREEPARSER
)
if (COMPILE_WITH_UNITY_CMAKE_SUPPORT)
set_target_properties(KPim6MimeTreeParserWidgets PROPERTIES UNITY_BUILD ON)
endif()
generate_export_header(KPim6MimeTreeParserWidgets BASE_NAME mimetreeparser_widgets)
target_include_directories(KPim6MimeTreeParserWidgets INTERFACE "$<INSTALL_INTERFACE:${KDE_INSTALL_INCLUDEDIR}/KPim6/MimeTreeParserWidgets>")
-target_include_directories(KPim6MimeTreeParserWidgets PUBLIC "$<BUILD_INTERFACE:${MimeTreeParserWidgets_SOURCE_DIR}/src;${MimeTreeParserWidgets_BINARY_DIR}/src>")
target_link_libraries(KPim6MimeTreeParserWidgets
PUBLIC
Qt6::Widgets
KPim6::MimeTreeParserCore
PRIVATE
KF6::Codecs
KF6::I18n
KF6::CalendarCore
KF6::WidgetsAddons
Gpgme::Gpgme
)
set_target_properties(KPim6MimeTreeParserWidgets PROPERTIES
VERSION ${MIMETREEPARSER_WIDGETS_VERSION}
SOVERSION ${MIMETREEPARSER_WIDGETS_SOVERSION}
EXPORT_NAME MimeTreeParserWidgets
)
ecm_generate_pri_file(BASE_NAME MimeTreeParserWidgets
LIB_NAME KPim6MimeTreeParserWidgets
DEPS "MimeTreeParserWidgets"
FILENAME_VAR PRI_FILENAME
)
install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR})
install(TARGETS
KPim6MimeTreeParserWidgets
EXPORT KPim6MimeTreeParserWidgetsTargets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}
)
ecm_generate_headers(MimeTreeParserWidgets_CamelCase_HEADERS
HEADER_NAMES
MessageViewer
REQUIRED_HEADERS MimeTreeParserWidgets_HEADERS
PREFIX MimeTreeParserWidgets
)
install(FILES
${MimeTreeParserWidgets_CamelCase_HEADERS}
DESTINATION ${KDE_INSTALL_INCLUDEDIR}/KPim6/MimeTreeParserWidgets/MimeTreeParserWidgets
COMPONENT Devel
)
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/mimetreeparser_widgets_export.h
${MimeTreeParserWidgets_HEADERS}
DESTINATION ${KDE_INSTALL_INCLUDEDIR}/KPim6/MimeTreeParserWidgets/mimetreeparserwidgets
COMPONENT Devel
)
if (BUILD_QCH)
ecm_add_qch(
KPim6MimeTreeParserWidgets_QCH
NAME MimeTreeParserWidgets
BASE_NAME KPim6MimeTreeParserWidgets
VERSION ${PIM_VERSION}
ORG_DOMAIN org.kde
# using only public headers, to cover only public API
SOURCES ${MimeTreeParserWidgets_HEADERS}
MD_MAINPAGE "${CMAKE_SOURCE_DIR}/README.md"
LINK_QCHS
Qt6Core_QCH
INCLUDE_DIRS
${CMAKE_CURRENT_BINARY_DIR}
BLANK_MACROS
MIMETREEPARSER_WIDGETS_EXPORT
TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR}
COMPONENT Devel
)
endif()
########### CMake Config Files ###########
set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KPim6MimeTreeParserWidgets")
if (BUILD_QCH)
ecm_install_qch_export(
TARGETS KPim6MimeTreeParserWidgets_QCH
FILE KPim6MimeTreeParserWidgetsQchTargets.cmake
DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
COMPONENT Devel
)
set(PACKAGE_INCLUDE_QCHTARGETS "include(\"\${CMAKE_CURRENT_LIST_DIR}/KPim6MimeTreeParserQchTargets.cmake\")")
endif()
configure_package_config_file(
"${CMAKE_CURRENT_SOURCE_DIR}/KPimMimeTreeParserWidgetsConfig.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/KPim6MimeTreeParserWidgetsConfig.cmake"
INSTALL_DESTINATION ${CMAKECONFIG_INSTALL_DIR}
)
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/KPim6MimeTreeParserWidgetsConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/KPim6MimeTreeParserWidgetsConfigVersion.cmake"
DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
COMPONENT Devel
)
install(EXPORT KPim6MimeTreeParserWidgetsTargets
DESTINATION "${CMAKECONFIG_INSTALL_DIR}"
FILE KPim6MimeTreeParserWidgetsTargets.cmake
NAMESPACE KPim6::
)
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/mimetreeparser_widgets_version.h
DESTINATION ${KDE_INSTALL_INCLUDEDIR}/KPim6/MimeTreeParserWidgets
COMPONENT Devel
)
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Thu, Feb 5, 9:40 PM (1 d, 8 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
68/97/ca161f5fbc35a76275d2e260cfb6
Attached To
rMTP MIME Tree Parser
Event Timeline
Log In to Comment