summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--framework/src/domain/mime/mailcrypto.cpp381
1 files 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 @@
20 02110-1301, USA. 20 02110-1301, USA.
21*/ 21*/
22#include "mailcrypto.h" 22#include "mailcrypto.h"
23#include <QGpgME/Protocol> 23
24#include <QGpgME/SignJob> 24#include <QGpgME/DataProvider>
25#include <QGpgME/EncryptJob> 25#include <QGpgME/EncryptJob>
26#include <QGpgME/SignEncryptJob> 26#include <QGpgME/ExportJob>
27#include <QGpgME/ImportFromKeyserverJob> 27#include <QGpgME/ImportFromKeyserverJob>
28#include <gpgme++/global.h> 28#include <QGpgME/Protocol>
29#include <gpgme++/signingresult.h> 29#include <QGpgME/SignEncryptJob>
30#include <QGpgME/SignJob>
31
32#include <gpgme++/data.h>
30#include <gpgme++/encryptionresult.h> 33#include <gpgme++/encryptionresult.h>
31#include <gpgme++/keylistresult.h> 34#include <gpgme++/global.h>
32#include <gpgme++/importresult.h> 35#include <gpgme++/importresult.h>
36#include <gpgme++/keylistresult.h>
37#include <gpgme++/signingresult.h>
38
33#include <QDebug> 39#include <QDebug>
34 40
41#include <future>
42#include <utility>
43
35/* 44/*
36 * FIXME: 45 * FIXME:
37 * 46 *
@@ -93,7 +102,7 @@ KMime::Content *createPart(const QByteArray &encodedBody, const QByteArray &mime
93 102
94 auto contentType = new KMime::Headers::ContentType; 103 auto contentType = new KMime::Headers::ContentType;
95 contentType->setMimeType(mimeType); 104 contentType->setMimeType(mimeType);
96 contentType->setMimeType(charset); 105 contentType->setCharset(charset);
97 // if (!chooseCTE()) { 106 // if (!chooseCTE()) {
98 // Q_ASSERT(error()); 107 // Q_ASSERT(error());
99 // emitResult(); 108 // emitResult();
@@ -145,6 +154,284 @@ KMime::Content *setBodyAndCTE(QByteArray &encodedBody, KMime::Headers::ContentTy
145 return ret; 154 return ret;
146} 155}
147 156
157// replace simple LFs by CRLFs for all MIME supporting CryptPlugs
158// according to RfC 2633, 3.1.1 Canonicalization
159static QByteArray canonicalizeContent(KMime::Content *content)
160{
161 // if (d->format & Kleo::InlineOpenPGPFormat) {
162 // return d->content->body();
163 // } else if (!(d->format & Kleo::SMIMEOpaqueFormat)) {
164
165 // replace "From " and "--" at the beginning of lines
166 // with encoded versions according to RfC 3156, 3
167 // Note: If any line begins with the string "From ", it is strongly
168 // suggested that either the Quoted-Printable or Base64 MIME encoding
169 // be applied.
170 const auto encoding = content->contentTransferEncoding()->encoding();
171 if ((encoding == KMime::Headers::CEquPr || encoding == KMime::Headers::CE7Bit)
172 && !content->contentType(false)) {
173 QByteArray body = content->encodedBody();
174 bool changed = false;
175 QList<QByteArray> search;
176 QList<QByteArray> replacements;
177
178 search << "From "
179 << "from "
180 << "-";
181 replacements << "From=20"
182 << "from=20"
183 << "=2D";
184
185 if (content->contentTransferEncoding()->encoding() == KMime::Headers::CE7Bit) {
186 for (int i = 0; i < search.size(); ++i) {
187 const auto pos = body.indexOf(search[i]);
188 if (pos == 0 || (pos > 0 && body.at(pos - 1) == '\n')) {
189 changed = true;
190 break;
191 }
192 }
193 if (changed) {
194 content->contentTransferEncoding()->setEncoding(KMime::Headers::CEquPr);
195 content->assemble();
196 body = content->encodedBody();
197 }
198 }
199
200 for (int i = 0; i < search.size(); ++i) {
201 const auto pos = body.indexOf(search[i]);
202 if (pos == 0 || (pos > 0 && body.at(pos - 1) == '\n')) {
203 changed = true;
204 body.replace(pos, search[i].size(), replacements[i]);
205 }
206 }
207
208 if (changed) {
209 qDebug() << "Content changed";
210 content->setBody(body);
211 content->contentTransferEncoding()->setDecoded(false);
212 }
213 }
214
215 return KMime::LFtoCRLF(content->encodedContent());
216 // } else { // SMimeOpaque doesn't need LFtoCRLF, else it gets munged
217 // return content->encodedContent();
218 // }
219
220}
221
222/**
223 * Create an Email with `msg` as a body and `key` as an attachment.
224 *
225 * Used by the `createSignedEmail` and `createEncryptedEmail` functions.
226 */
227KMime::Content *setMessageAndPublicKey(KMime::Content *msg, const GpgME::Key &key)
228{
229 auto result = new KMime::Content;
230 result->contentType()->setMimeType("multipart/mixed");
231 result->contentType()->setBoundary(KMime::multiPartBoundary());
232
233 KMime::Content *keyAttachment = new KMime::Content;
234 {
235 // Not using the Qt API because it apparently blocks (the `result` signal is never
236 // triggered)
237 std::unique_ptr<GpgME::Context> ctx(GpgME::Context::createForProtocol(GpgME::OpenPGP));
238 ctx->setArmor(true);
239
240 qDebug() << "Setting up data container";
241 QGpgME::QByteArrayDataProvider dp;
242 GpgME::Data data(&dp);
243
244 qDebug() << "Exporting public key";
245 auto error = ctx->exportPublicKeys(key.keyID(), data);
246
247 if (error.code()) {
248 qWarning() << "Could not export public key:" << error.asString();
249 // TODO: XXX: handle errors better
250 // throw std::runtime_error("Export public key failed");
251 delete result;
252 return msg;
253 }
254
255 keyAttachment->contentType()->setMimeType("application/pgp-keys");
256 keyAttachment->contentDisposition()->setDisposition(KMime::Headers::CDattachment);
257 keyAttachment->contentDisposition()->setFilename(QString("0x") + key.shortKeyID() + ".asc");
258 qDebug() << "Getting data";
259 qWarning() << "Data:" << dp.data().constData();
260 keyAttachment->setBody(dp.data());
261 }
262
263 msg->assemble();
264 qWarning() << "Msg:" << msg->encodedContent().constData();
265
266 result->addContent(msg);
267 result->addContent(keyAttachment);
268
269 result->assemble();
270 qDebug() << "Final message:\n" << result->encodedContent().constData();
271
272 return result;
273}
274
275QByteArray encrypt(const QByteArray &content, const std::vector<GpgME::Key> &encryptionKeys)
276{
277 QByteArray resultData;
278
279 const QGpgME::Protocol *const proto = QGpgME::openpgp();
280 std::unique_ptr<QGpgME::EncryptJob> job(proto->encryptJob(/* armor = */ true));
281 const auto result = job->exec(encryptionKeys, content, /* alwaysTrust = */ true, resultData);
282
283 if (result.error().code()) {
284 qWarning() << "Encryption failed:" << result.error().asString();
285 // TODO: XXX: handle errors better
286 // throw std::runtime_error("Signing failed");
287 return "";
288 }
289
290 return resultData;
291}
292
293QByteArray signAndEncrypt(const QByteArray &content, const std::vector<GpgME::Key> &signingKeys,
294 const std::vector<GpgME::Key> &encryptionKeys)
295{
296 QByteArray resultData;
297
298 const QGpgME::Protocol *const proto = QGpgME::openpgp();
299 std::unique_ptr<QGpgME::SignEncryptJob> job(proto->signEncryptJob(/* armor = */ true));
300 const auto result = job->exec(signingKeys, encryptionKeys, content, /* alwaysTrust = */ true, resultData);
301
302 if (result.first.error().code()) {
303 qWarning() << "Signing failed:" << result.first.error().asString();
304 // TODO: XXX: handle errors better
305 // throw std::runtime_error("Signing failed");
306 return "";
307 }
308
309 if (result.second.error().code()) {
310 qWarning() << "Encryption failed:" << result.second.error().asString();
311 // TODO: XXX: handle errors better
312 // throw std::runtime_error("Encryption failed");
313 return "";
314 }
315
316 return resultData;
317}
318
319// Create a message like this (according to RFC 3156 Section 4):
320//
321// - multipart/encrypted
322// - application/pgp-encrypted (version information)
323// - application/octet-stream (given encrypted data)
324KMime::Content *createEncryptedPart(QByteArray encryptedData)
325{
326 KMime::Content *result = new KMime::Content;
327
328 result->contentType()->setMimeType("multipart/encrypted");
329 result->contentType()->setBoundary(KMime::multiPartBoundary());
330 result->contentType()->setParameter("protocol", "application/pgp-encrypted");
331
332 KMime::Content *controlInformation = new KMime::Content;
333 {
334 controlInformation->contentType()->setMimeType("application/pgp-encrypted");
335 controlInformation->contentDescription()->from7BitString("PGP/MIME version identification");
336 controlInformation->setBody("Version: 1");
337
338 result->addContent(controlInformation);
339 }
340
341 KMime::Content *encryptedPartPart = new KMime::Content;
342 {
343 const QString filename = "msg.asc";
344
345 encryptedPartPart->contentType()->setMimeType("application/octet-stream");
346 encryptedPartPart->contentType()->setName(filename, "utf-8");
347
348 encryptedPartPart->contentDescription()->from7BitString("OpenPGP encrypted message");
349
350 encryptedPartPart->contentDisposition()->setDisposition(KMime::Headers::CDinline);
351 encryptedPartPart->contentDisposition()->setFilename(filename);
352
353 encryptedPartPart->setBody(encryptedData);
354
355 result->addContent(encryptedPartPart);
356 }
357
358 return result;
359}
360
361KMime::Content *createEncryptedEmail(KMime::Content *content, const std::vector<GpgME::Key> &encryptionKeys,
362 bool sign, const std::vector<GpgME::Key> &signingKeys = {})
363{
364 auto contentToEncrypt = canonicalizeContent(content);
365
366 auto encryptedData = sign ? encrypt(contentToEncrypt, encryptionKeys) :
367 signAndEncrypt(contentToEncrypt, signingKeys, encryptionKeys);
368 KMime::Content *encryptedPart = createEncryptedPart(encryptedData);
369
370 // TODO: check signingKeys not empty
371 return setMessageAndPublicKey(encryptedPart, signingKeys[0]);
372}
373
374QByteArray sign(const QByteArray &content, const std::vector<GpgME::Key> &signingKeys)
375{
376 QByteArray resultData;
377
378 const QGpgME::Protocol *const proto = QGpgME::openpgp();
379 std::unique_ptr<QGpgME::SignJob> job(proto->signJob(/* armor = */ true));
380 const auto result = job->exec(signingKeys, content, GpgME::Detached, resultData);
381
382 if (result.error().code()) {
383 qWarning() << "Signing failed:" << result.error().asString();
384 // TODO: XXX: handle errors better
385 // throw std::runtime_error("Signing failed");
386 return "";
387 }
388
389 return resultData;
390}
391
392// Create a message like this (according to RFC 3156 Section 5):
393//
394// - multipart/signed
395// - whatever the given original message is (should be canonicalized)
396// - application/octet-stream (given encrypted data)
397KMime::Content *createSignedPart(KMime::Content *message, const QByteArray &signedData, const QString &micAlg)
398{
399 KMime::Content *result = new KMime::Content;
400
401 result->contentType()->setMimeType("multipart/signed");
402 result->contentType()->setBoundary(KMime::multiPartBoundary());
403 result->contentType()->setParameter("micalg", micAlg);
404 result->contentType()->setParameter("protocol", "application/pgp-signature");
405
406 result->addContent(message);
407
408 KMime::Content *signedPartPart = new KMime::Content;
409 {
410 signedPartPart->contentType()->setMimeType("application/pgp-signature");
411 signedPartPart->contentType()->setName("signature.asc", "utf-8");
412
413 signedPartPart->contentDescription()->from7BitString(
414 "This is a digitally signed message part");
415
416 signedPartPart->setBody(signedData);
417
418 result->addContent(signedPartPart);
419 }
420
421 return result;
422}
423
424KMime::Content *createSignedEmail(KMime::Content *content, const std::vector<GpgME::Key> &signingKeys)
425{
426 auto contentToSign = canonicalizeContent(content);
427
428 auto signedData = sign(contentToSign, signingKeys);
429 KMime::Content *signedPart = createSignedPart(content, signedData, "TODO: pgp-something");
430
431 // TODO: check signingKeys not empty
432 return setMessageAndPublicKey(signedPart, signingKeys[0]);
433}
434
148void makeToplevelContentType(KMime::Content *content, bool encrypt, const QByteArray &hashAlgo) 435void makeToplevelContentType(KMime::Content *content, bool encrypt, const QByteArray &hashAlgo)
149{ 436{
150 //Kleo::CryptoMessageFormat format, 437 //Kleo::CryptoMessageFormat format,
@@ -265,7 +552,10 @@ KMime::Content *composeHeadersAndBody(KMime::Content *orig, QByteArray encodedBo
265 setNestedContentType(code, encrypt); 552 setNestedContentType(code, encrypt);
266 setNestedContentDisposition(code, encrypt); 553 setNestedContentDisposition(code, encrypt);
267 554
268 if (encrypt) { // enc PGPMime (and or sign) 555 if (encrypt) { // enc PGPMime (and / or sign)
556
557 //addPublicKeyAsAttachment(content, signingKeys[0]);
558
269 setBodyAndCTE(encodedBody, orig->contentType(), code); 559 setBodyAndCTE(encodedBody, orig->contentType(), code);
270 560
271 // Build a MIME part holding the version information 561 // Build a MIME part holding the version information
@@ -338,80 +628,27 @@ KMime::Content *composeHeadersAndBody(KMime::Content *orig, QByteArray encodedBo
338 // } 628 // }
339 // } 629 // }
340 630
341// replace simple LFs by CRLFs for all MIME supporting CryptPlugs 631KMime::Content *MailCrypto::processCrypto(KMime::Content *content, const std::vector<GpgME::Key> &signingKeys, const std::vector<GpgME::Key> &encryptionKeys, MailCrypto::Protocol protocol)
342// according to RfC 2633, 3.1.1 Canonicalization
343static QByteArray canonicalizeContent(KMime::Content *content)
344{ 632{
345 // if (d->format & Kleo::InlineOpenPGPFormat) {
346 // return d->content->body();
347 // } else if (!(d->format & Kleo::SMIMEOpaqueFormat)) {
348 633
349 // replace "From " and "--" at the beginning of lines 634 const bool sign = !signingKeys.empty();
350 // with encoded versions according to RfC 3156, 3
351 // Note: If any line begins with the string "From ", it is strongly
352 // suggested that either the Quoted-Printable or Base64 MIME encoding
353 // be applied.
354 const auto encoding = content->contentTransferEncoding()->encoding();
355 if ((encoding == KMime::Headers::CEquPr || encoding == KMime::Headers::CE7Bit)
356 && !content->contentType(false)) {
357 QByteArray body = content->encodedBody();
358 bool changed = false;
359 QList<QByteArray> search;
360 QList<QByteArray> replacements;
361
362 search << "From "
363 << "from "
364 << "-";
365 replacements << "From=20"
366 << "from=20"
367 << "=2D";
368
369 if (content->contentTransferEncoding()->encoding() == KMime::Headers::CE7Bit) {
370 for (int i = 0; i < search.size(); ++i) {
371 const auto pos = body.indexOf(search[i]);
372 if (pos == 0 || (pos > 0 && body.at(pos - 1) == '\n')) {
373 changed = true;
374 break;
375 }
376 }
377 if (changed) {
378 content->contentTransferEncoding()->setEncoding(KMime::Headers::CEquPr);
379 content->assemble();
380 body = content->encodedBody();
381 }
382 }
383
384 for (int i = 0; i < search.size(); ++i) {
385 const auto pos = body.indexOf(search[i]);
386 if (pos == 0 || (pos > 0 && body.at(pos - 1) == '\n')) {
387 changed = true;
388 body.replace(pos, search[i].size(), replacements[i]);
389 }
390 }
391
392 if (changed) {
393 qDebug() << "Content changed";
394 content->setBody(body);
395 content->contentTransferEncoding()->setDecoded(false);
396 }
397 }
398
399 return KMime::LFtoCRLF(content->encodedContent());
400 // } else { // SMimeOpaque doesn't need LFtoCRLF, else it gets munged
401 // return content->encodedContent();
402 // }
403 635
404} 636 if(!encryptionKeys.empty()) {
637 return createEncryptedEmail(content, encryptionKeys, sign, signingKeys);
638 } else if(sign) {
639 return createSignedEmail(content, signingKeys);
640 } else {
641 qWarning() << "Processing cryptography, but neither signing nor encrypting";
642 return content;
643 }
405 644
406KMime::Content *MailCrypto::processCrypto(KMime::Content *content, const std::vector<GpgME::Key> &signingKeys, const std::vector<GpgME::Key> &encryptionKeys, MailCrypto::Protocol protocol)
407{
408 const QGpgME::Protocol *const proto = protocol == MailCrypto::SMIME ? QGpgME::smime() : QGpgME::openpgp(); 645 const QGpgME::Protocol *const proto = protocol == MailCrypto::SMIME ? QGpgME::smime() : QGpgME::openpgp();
409 Q_ASSERT(proto); 646 Q_ASSERT(proto);
410 647
411 auto signingMode = GpgME::Detached; 648 auto signingMode = GpgME::Detached;
412 bool armor = true; 649 bool armor = true;
413 bool textMode = false; 650 bool textMode = false;
414 const bool sign = !signingKeys.empty(); 651 //const bool sign = !signingKeys.empty();
415 const bool encrypt = !encryptionKeys.empty(); 652 const bool encrypt = !encryptionKeys.empty();
416 653
417 QByteArray resultContent; 654 QByteArray resultContent;