From f4c8da01dbd769c6203dd63389f0bfda91e0163f Mon Sep 17 00:00:00 2001 From: Minijackson Date: Tue, 6 Mar 2018 16:29:37 +0100 Subject: PoC mail crypto (needs error checking and removing old code) --- framework/src/domain/mime/mailcrypto.cpp | 381 +++++++++++++++++++++++++------ 1 file changed, 309 insertions(+), 72 deletions(-) diff --git a/framework/src/domain/mime/mailcrypto.cpp b/framework/src/domain/mime/mailcrypto.cpp index 2dc2d245..4a26829f 100644 --- a/framework/src/domain/mime/mailcrypto.cpp +++ b/framework/src/domain/mime/mailcrypto.cpp @@ -20,18 +20,27 @@ 02110-1301, USA. */ #include "mailcrypto.h" -#include -#include + +#include #include -#include +#include #include -#include -#include +#include +#include +#include + +#include #include -#include +#include #include +#include +#include + #include +#include +#include + /* * FIXME: * @@ -93,7 +102,7 @@ KMime::Content *createPart(const QByteArray &encodedBody, const QByteArray &mime auto contentType = new KMime::Headers::ContentType; contentType->setMimeType(mimeType); - contentType->setMimeType(charset); + contentType->setCharset(charset); // if (!chooseCTE()) { // Q_ASSERT(error()); // emitResult(); @@ -145,6 +154,284 @@ KMime::Content *setBodyAndCTE(QByteArray &encodedBody, KMime::Headers::ContentTy return ret; } +// replace simple LFs by CRLFs for all MIME supporting CryptPlugs +// according to RfC 2633, 3.1.1 Canonicalization +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 search; + QList 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 an Email with `msg` as a body and `key` as an attachment. + * + * Used by the `createSignedEmail` and `createEncryptedEmail` functions. + */ +KMime::Content *setMessageAndPublicKey(KMime::Content *msg, const GpgME::Key &key) +{ + auto result = new KMime::Content; + result->contentType()->setMimeType("multipart/mixed"); + result->contentType()->setBoundary(KMime::multiPartBoundary()); + + KMime::Content *keyAttachment = new KMime::Content; + { + // Not using the Qt API because it apparently blocks (the `result` signal is never + // triggered) + std::unique_ptr ctx(GpgME::Context::createForProtocol(GpgME::OpenPGP)); + ctx->setArmor(true); + + qDebug() << "Setting up data container"; + QGpgME::QByteArrayDataProvider dp; + GpgME::Data data(&dp); + + qDebug() << "Exporting public key"; + auto error = ctx->exportPublicKeys(key.keyID(), data); + + if (error.code()) { + qWarning() << "Could not export public key:" << error.asString(); + // TODO: XXX: handle errors better + // throw std::runtime_error("Export public key failed"); + delete result; + return msg; + } + + keyAttachment->contentType()->setMimeType("application/pgp-keys"); + keyAttachment->contentDisposition()->setDisposition(KMime::Headers::CDattachment); + keyAttachment->contentDisposition()->setFilename(QString("0x") + key.shortKeyID() + ".asc"); + qDebug() << "Getting data"; + qWarning() << "Data:" << dp.data().constData(); + keyAttachment->setBody(dp.data()); + } + + msg->assemble(); + qWarning() << "Msg:" << msg->encodedContent().constData(); + + result->addContent(msg); + result->addContent(keyAttachment); + + result->assemble(); + qDebug() << "Final message:\n" << result->encodedContent().constData(); + + return result; +} + +QByteArray encrypt(const QByteArray &content, const std::vector &encryptionKeys) +{ + QByteArray resultData; + + const QGpgME::Protocol *const proto = QGpgME::openpgp(); + std::unique_ptr job(proto->encryptJob(/* armor = */ true)); + const auto result = job->exec(encryptionKeys, content, /* alwaysTrust = */ true, resultData); + + if (result.error().code()) { + qWarning() << "Encryption failed:" << result.error().asString(); + // TODO: XXX: handle errors better + // throw std::runtime_error("Signing failed"); + return ""; + } + + return resultData; +} + +QByteArray signAndEncrypt(const QByteArray &content, const std::vector &signingKeys, + const std::vector &encryptionKeys) +{ + QByteArray resultData; + + const QGpgME::Protocol *const proto = QGpgME::openpgp(); + std::unique_ptr job(proto->signEncryptJob(/* armor = */ true)); + const auto result = job->exec(signingKeys, encryptionKeys, content, /* alwaysTrust = */ true, resultData); + + if (result.first.error().code()) { + qWarning() << "Signing failed:" << result.first.error().asString(); + // TODO: XXX: handle errors better + // throw std::runtime_error("Signing failed"); + return ""; + } + + if (result.second.error().code()) { + qWarning() << "Encryption failed:" << result.second.error().asString(); + // TODO: XXX: handle errors better + // throw std::runtime_error("Encryption failed"); + return ""; + } + + return resultData; +} + +// Create a message like this (according to RFC 3156 Section 4): +// +// - multipart/encrypted +// - application/pgp-encrypted (version information) +// - application/octet-stream (given encrypted data) +KMime::Content *createEncryptedPart(QByteArray encryptedData) +{ + KMime::Content *result = new KMime::Content; + + result->contentType()->setMimeType("multipart/encrypted"); + result->contentType()->setBoundary(KMime::multiPartBoundary()); + result->contentType()->setParameter("protocol", "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 = "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; +} + +KMime::Content *createEncryptedEmail(KMime::Content *content, const std::vector &encryptionKeys, + bool sign, const std::vector &signingKeys = {}) +{ + auto contentToEncrypt = canonicalizeContent(content); + + auto encryptedData = sign ? encrypt(contentToEncrypt, encryptionKeys) : + signAndEncrypt(contentToEncrypt, signingKeys, encryptionKeys); + KMime::Content *encryptedPart = createEncryptedPart(encryptedData); + + // TODO: check signingKeys not empty + return setMessageAndPublicKey(encryptedPart, signingKeys[0]); +} + +QByteArray sign(const QByteArray &content, const std::vector &signingKeys) +{ + QByteArray resultData; + + const QGpgME::Protocol *const proto = QGpgME::openpgp(); + std::unique_ptr job(proto->signJob(/* armor = */ true)); + const auto result = job->exec(signingKeys, content, GpgME::Detached, resultData); + + if (result.error().code()) { + qWarning() << "Signing failed:" << result.error().asString(); + // TODO: XXX: handle errors better + // throw std::runtime_error("Signing failed"); + return ""; + } + + return resultData; +} + +// Create a message like this (according to RFC 3156 Section 5): +// +// - multipart/signed +// - whatever the given original message is (should be canonicalized) +// - application/octet-stream (given encrypted data) +KMime::Content *createSignedPart(KMime::Content *message, const QByteArray &signedData, const QString &micAlg) +{ + KMime::Content *result = new KMime::Content; + + result->contentType()->setMimeType("multipart/signed"); + result->contentType()->setBoundary(KMime::multiPartBoundary()); + result->contentType()->setParameter("micalg", micAlg); + result->contentType()->setParameter("protocol", "application/pgp-signature"); + + result->addContent(message); + + KMime::Content *signedPartPart = new KMime::Content; + { + signedPartPart->contentType()->setMimeType("application/pgp-signature"); + signedPartPart->contentType()->setName("signature.asc", "utf-8"); + + signedPartPart->contentDescription()->from7BitString( + "This is a digitally signed message part"); + + signedPartPart->setBody(signedData); + + result->addContent(signedPartPart); + } + + return result; +} + +KMime::Content *createSignedEmail(KMime::Content *content, const std::vector &signingKeys) +{ + auto contentToSign = canonicalizeContent(content); + + auto signedData = sign(contentToSign, signingKeys); + KMime::Content *signedPart = createSignedPart(content, signedData, "TODO: pgp-something"); + + // TODO: check signingKeys not empty + return setMessageAndPublicKey(signedPart, signingKeys[0]); +} + void makeToplevelContentType(KMime::Content *content, bool encrypt, const QByteArray &hashAlgo) { //Kleo::CryptoMessageFormat format, @@ -265,7 +552,10 @@ KMime::Content *composeHeadersAndBody(KMime::Content *orig, QByteArray encodedBo setNestedContentType(code, encrypt); setNestedContentDisposition(code, encrypt); - if (encrypt) { // enc PGPMime (and or sign) + if (encrypt) { // enc PGPMime (and / or sign) + + //addPublicKeyAsAttachment(content, signingKeys[0]); + setBodyAndCTE(encodedBody, orig->contentType(), code); // Build a MIME part holding the version information @@ -338,80 +628,27 @@ KMime::Content *composeHeadersAndBody(KMime::Content *orig, QByteArray encodedBo // } // } -// replace simple LFs by CRLFs for all MIME supporting CryptPlugs -// according to RfC 2633, 3.1.1 Canonicalization -static QByteArray canonicalizeContent(KMime::Content *content) +KMime::Content *MailCrypto::processCrypto(KMime::Content *content, const std::vector &signingKeys, const std::vector &encryptionKeys, MailCrypto::Protocol protocol) { - // 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 search; - QList 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(); - // } + const bool sign = !signingKeys.empty(); -} + if(!encryptionKeys.empty()) { + return createEncryptedEmail(content, encryptionKeys, sign, signingKeys); + } else if(sign) { + return createSignedEmail(content, signingKeys); + } else { + qWarning() << "Processing cryptography, but neither signing nor encrypting"; + return content; + } -KMime::Content *MailCrypto::processCrypto(KMime::Content *content, const std::vector &signingKeys, const std::vector &encryptionKeys, MailCrypto::Protocol protocol) -{ const QGpgME::Protocol *const proto = protocol == MailCrypto::SMIME ? QGpgME::smime() : QGpgME::openpgp(); Q_ASSERT(proto); auto signingMode = GpgME::Detached; bool armor = true; bool textMode = false; - const bool sign = !signingKeys.empty(); + //const bool sign = !signingKeys.empty(); const bool encrypt = !encryptionKeys.empty(); QByteArray resultContent; -- cgit v1.2.3