summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--framework/src/domain/composercontroller.cpp11
-rw-r--r--framework/src/domain/mime/mailcrypto.cpp568
-rw-r--r--framework/src/domain/mime/mailcrypto.h3
-rw-r--r--framework/src/domain/mime/mailtemplates.cpp9
-rw-r--r--framework/src/domain/mime/mailtemplates.h2
-rw-r--r--framework/src/errors.h46
6 files changed, 192 insertions, 447 deletions
diff --git a/framework/src/domain/composercontroller.cpp b/framework/src/domain/composercontroller.cpp
index 2286a71b..37902164 100644
--- a/framework/src/domain/composercontroller.cpp
+++ b/framework/src/domain/composercontroller.cpp
@@ -463,18 +463,25 @@ KMime::Message::Ptr ComposerController::assembleMessage()
463 }; 463 };
464 }); 464 });
465 465
466 GpgME::Key attachedKey;
466 std::vector<GpgME::Key> signingKeys; 467 std::vector<GpgME::Key> signingKeys;
467 if (getSign()) { 468 if (getSign()) {
468 signingKeys = getPersonalKeys().value<std::vector<GpgME::Key>>(); 469 signingKeys = getPersonalKeys().value<std::vector<GpgME::Key>>();
470 Q_ASSERT(!signingKeys.empty());
471 attachedKey = signingKeys[0];
469 } 472 }
470 std::vector<GpgME::Key> encryptionKeys; 473 std::vector<GpgME::Key> encryptionKeys;
471 if (getEncrypt()) { 474 if (getEncrypt()) {
472 //Encrypt to self so we can read the sent message 475 //Encrypt to self so we can read the sent message
473 encryptionKeys += getPersonalKeys().value<std::vector<GpgME::Key>>(); 476 auto personalKeys = getPersonalKeys().value<std::vector<GpgME::Key>>();
477
478 attachedKey = personalKeys[0];
479
480 encryptionKeys += personalKeys;
474 encryptionKeys += getRecipientKeys(); 481 encryptionKeys += getRecipientKeys();
475 } 482 }
476 483
477 return MailTemplates::createMessage(mExistingMessage, toAddresses, ccAddresses, bccAddresses, getIdentity(), getSubject(), getBody(), getHtmlBody(), attachments, signingKeys, encryptionKeys); 484 return MailTemplates::createMessage(mExistingMessage, toAddresses, ccAddresses, bccAddresses, getIdentity(), getSubject(), getBody(), getHtmlBody(), attachments, signingKeys, encryptionKeys, attachedKey);
478} 485}
479 486
480void ComposerController::send() 487void ComposerController::send()
diff --git a/framework/src/domain/mime/mailcrypto.cpp b/framework/src/domain/mime/mailcrypto.cpp
index 4a26829f..f25c75fe 100644
--- a/framework/src/domain/mime/mailcrypto.cpp
+++ b/framework/src/domain/mime/mailcrypto.cpp
@@ -21,6 +21,8 @@
21*/ 21*/
22#include "mailcrypto.h" 22#include "mailcrypto.h"
23 23
24#include "framework/src/errors.h"
25
24#include <QGpgME/DataProvider> 26#include <QGpgME/DataProvider>
25#include <QGpgME/EncryptJob> 27#include <QGpgME/EncryptJob>
26#include <QGpgME/ExportJob> 28#include <QGpgME/ExportJob>
@@ -41,119 +43,6 @@
41#include <future> 43#include <future>
42#include <utility> 44#include <utility>
43 45
44/*
45 * FIXME:
46 *
47 * This code is WIP.
48 * It currently only implements OpenPGPMIMEFormat for signing.
49 * All the commented code are intentional leftovers that we can clean-up
50 * once all necessary signing mechanisms have been implemented.
51 *
52 * Creating an ecrypted mail:
53 * * get keys (email -> fingreprint -> key)
54 * * Use Kleo::OpenPGPMIMEFormat,
55 *
56 */
57
58// bool chooseCTE()
59// {
60// Q_Q(SinglepartJob);
61
62// auto allowed = KMime::encodingsForData(data);
63
64// if (!q->globalPart()->is8BitAllowed()) {
65// allowed.removeAll(KMime::Headers::CE8Bit);
66// }
67
68// #if 0 //TODO signing
69// // In the following cases only QP and Base64 are allowed:
70// // - the buffer will be OpenPGP/MIME signed and it contains trailing
71// // whitespace (cf. RFC 3156)
72// // - a line starts with "From "
73// if ((willBeSigned && cf.hasTrailingWhitespace()) ||
74// cf.hasLeadingFrom()) {
75// ret.removeAll(DwMime::kCte8bit);
76// ret.removeAll(DwMime::kCte7bit);
77// }
78// #endif
79
80// if (contentTransferEncoding) {
81// // Specific CTE set. Check that our data fits in it.
82// if (!allowed.contains(contentTransferEncoding->encoding())) {
83// q->setError(JobBase::BugError);
84// q->setErrorText(i18n("%1 Content-Transfer-Encoding cannot correctly encode this message.",
85// KMime::nameForEncoding(contentTransferEncoding->encoding())));
86// return false;
87// // TODO improve error message in case 8bit is requested but not allowed.
88// }
89// } else {
90// // No specific CTE set. Choose the best one.
91// Q_ASSERT(!allowed.isEmpty());
92// contentTransferEncoding = new KMime::Headers::ContentTransferEncoding;
93// contentTransferEncoding->setEncoding(allowed.first());
94// }
95// qCDebug(MESSAGECOMPOSER_LOG) << "Settled on encoding" << KMime::nameForEncoding(contentTransferEncoding->encoding());
96// return true;
97// }
98
99KMime::Content *createPart(const QByteArray &encodedBody, const QByteArray &mimeType, const QByteArray &charset)
100{
101 auto resultContent = new KMime::Content;
102
103 auto contentType = new KMime::Headers::ContentType;
104 contentType->setMimeType(mimeType);
105 contentType->setCharset(charset);
106 // if (!chooseCTE()) {
107 // Q_ASSERT(error());
108 // emitResult();
109 // return;
110 // }
111
112 // Set headers.
113 // if (contentDescription) {
114 // resultContent->setHeader(contentDescription);
115 // }
116 // if (contentDisposition) {
117 // resultContent->setHeader(contentDisposition);
118 // }
119 // if (contentID) {
120 // resultContent->setHeader(contentID);
121 // }
122 // Q_ASSERT(contentTransferEncoding); // chooseCTE() created it if it didn't exist.
123 auto contentTransferEncoding = new KMime::Headers::ContentTransferEncoding;
124 auto allowed = KMime::encodingsForData(encodedBody);
125 Q_ASSERT(!allowed.isEmpty());
126 contentTransferEncoding->setEncoding(allowed.first());
127 resultContent->setHeader(contentTransferEncoding);
128
129 if (contentType) {
130 resultContent->setHeader(contentType);
131 }
132
133 // Set data.
134 resultContent->setBody(encodedBody);
135 return resultContent;
136}
137
138KMime::Content *setBodyAndCTE(QByteArray &encodedBody, KMime::Headers::ContentType *contentType, KMime::Content *ret)
139{
140 // MessageComposer::Composer composer;
141 // MessageComposer::SinglepartJob cteJob(&composer);
142 auto part = createPart(encodedBody, contentType->mimeType(), contentType->charset());
143 part->assemble();
144
145 // cteJob.contentType()->setMimeType(contentType->mimeType());
146 // cteJob.contentType()->setCharset(contentType->charset());
147 // cteJob.setData(encodedBody);
148 // cteJob.exec();
149 // cteJob.content()->assemble();
150
151 ret->contentTransferEncoding()->setEncoding(part->contentTransferEncoding()->encoding());
152 ret->setBody(part->encodedBody());
153
154 return ret;
155}
156
157// replace simple LFs by CRLFs for all MIME supporting CryptPlugs 46// replace simple LFs by CRLFs for all MIME supporting CryptPlugs
158// according to RfC 2633, 3.1.1 Canonicalization 47// according to RfC 2633, 3.1.1 Canonicalization
159static QByteArray canonicalizeContent(KMime::Content *content) 48static QByteArray canonicalizeContent(KMime::Content *content)
@@ -220,44 +109,60 @@ static QByteArray canonicalizeContent(KMime::Content *content)
220} 109}
221 110
222/** 111/**
112 * Get the given `key` in the armor format.
113 */
114Expected<GpgME::Error, QByteArray> exportPublicKey(const GpgME::Key &key)
115{
116 // Not using the Qt API because it apparently blocks (the `result` signal is never
117 // triggered)
118 std::unique_ptr<GpgME::Context> ctx(GpgME::Context::createForProtocol(GpgME::OpenPGP));
119 ctx->setArmor(true);
120
121 QGpgME::QByteArrayDataProvider dp;
122 GpgME::Data data(&dp);
123
124 qDebug() << "Exporting public key:" << key.shortKeyID();
125 auto error = ctx->exportPublicKeys(key.keyID(), data);
126
127 if (error.code()) {
128 return makeUnexpected(error);
129 }
130
131 return dp.data();
132}
133
134/**
223 * Create an Email with `msg` as a body and `key` as an attachment. 135 * Create an Email with `msg` as a body and `key` as an attachment.
224 * 136 *
137 * Will create the given structure:
138 *
139 * + `multipart/mixed`
140 * - the given `msg`
141 * - `application/pgp-keys` (the given `key` as attachment)
142 *
225 * Used by the `createSignedEmail` and `createEncryptedEmail` functions. 143 * Used by the `createSignedEmail` and `createEncryptedEmail` functions.
226 */ 144 */
227KMime::Content *setMessageAndPublicKey(KMime::Content *msg, const GpgME::Key &key) 145Expected<GpgME::Error, KMime::Content *> appendPublicKey(KMime::Content *msg, const GpgME::Key &key)
228{ 146{
147 const auto publicKeyExportResult = exportPublicKey(key);
148
149 if(!publicKeyExportResult) {
150 // "Could not export public key"
151 return makeUnexpected(publicKeyExportResult.error());
152 }
153
154 const auto publicKeyData = publicKeyExportResult.value();
155
229 auto result = new KMime::Content; 156 auto result = new KMime::Content;
230 result->contentType()->setMimeType("multipart/mixed"); 157 result->contentType()->setMimeType("multipart/mixed");
231 result->contentType()->setBoundary(KMime::multiPartBoundary()); 158 result->contentType()->setBoundary(KMime::multiPartBoundary());
232 159
233 KMime::Content *keyAttachment = new KMime::Content; 160 KMime::Content *keyAttachment = new KMime::Content;
234 { 161 {
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"); 162 keyAttachment->contentType()->setMimeType("application/pgp-keys");
256 keyAttachment->contentDisposition()->setDisposition(KMime::Headers::CDattachment); 163 keyAttachment->contentDisposition()->setDisposition(KMime::Headers::CDattachment);
257 keyAttachment->contentDisposition()->setFilename(QString("0x") + key.shortKeyID() + ".asc"); 164 keyAttachment->contentDisposition()->setFilename(QString("0x") + key.shortKeyID() + ".asc");
258 qDebug() << "Getting data"; 165 keyAttachment->setBody(publicKeyData);
259 qWarning() << "Data:" << dp.data().constData();
260 keyAttachment->setBody(dp.data());
261 } 166 }
262 167
263 msg->assemble(); 168 msg->assemble();
@@ -272,7 +177,7 @@ KMime::Content *setMessageAndPublicKey(KMime::Content *msg, const GpgME::Key &ke
272 return result; 177 return result;
273} 178}
274 179
275QByteArray encrypt(const QByteArray &content, const std::vector<GpgME::Key> &encryptionKeys) 180Expected<GpgME::Error, QByteArray> encrypt(const QByteArray &content, const std::vector<GpgME::Key> &encryptionKeys)
276{ 181{
277 QByteArray resultData; 182 QByteArray resultData;
278 183
@@ -282,15 +187,13 @@ QByteArray encrypt(const QByteArray &content, const std::vector<GpgME::Key> &enc
282 187
283 if (result.error().code()) { 188 if (result.error().code()) {
284 qWarning() << "Encryption failed:" << result.error().asString(); 189 qWarning() << "Encryption failed:" << result.error().asString();
285 // TODO: XXX: handle errors better 190 return makeUnexpected(result.error());
286 // throw std::runtime_error("Signing failed");
287 return "";
288 } 191 }
289 192
290 return resultData; 193 return resultData;
291} 194}
292 195
293QByteArray signAndEncrypt(const QByteArray &content, const std::vector<GpgME::Key> &signingKeys, 196Expected<GpgME::Error, QByteArray> signAndEncrypt(const QByteArray &content, const std::vector<GpgME::Key> &signingKeys,
294 const std::vector<GpgME::Key> &encryptionKeys) 197 const std::vector<GpgME::Key> &encryptionKeys)
295{ 198{
296 QByteArray resultData; 199 QByteArray resultData;
@@ -301,26 +204,29 @@ QByteArray signAndEncrypt(const QByteArray &content, const std::vector<GpgME::Ke
301 204
302 if (result.first.error().code()) { 205 if (result.first.error().code()) {
303 qWarning() << "Signing failed:" << result.first.error().asString(); 206 qWarning() << "Signing failed:" << result.first.error().asString();
304 // TODO: XXX: handle errors better 207 return makeUnexpected(result.first.error());
305 // throw std::runtime_error("Signing failed");
306 return "";
307 } 208 }
308 209
309 if (result.second.error().code()) { 210 if (result.second.error().code()) {
310 qWarning() << "Encryption failed:" << result.second.error().asString(); 211 qWarning() << "Encryption failed:" << result.second.error().asString();
311 // TODO: XXX: handle errors better 212 return makeUnexpected(result.second.error());
312 // throw std::runtime_error("Encryption failed");
313 return "";
314 } 213 }
315 214
316 return resultData; 215 return resultData;
317} 216}
318 217
319// Create a message like this (according to RFC 3156 Section 4): 218/**
320// 219 * Create a message like this (according to RFC 3156 Section 4):
321// - multipart/encrypted 220 *
322// - application/pgp-encrypted (version information) 221 * - multipart/encrypted
323// - application/octet-stream (given encrypted data) 222 * - application/pgp-encrypted (version information)
223 * - application/octet-stream (given encrypted data)
224 *
225 * Should not be used directly since the public key should be attached, hence
226 * the `createEncryptedEmail` function.
227 *
228 * The encrypted data can be generated by the `encrypt` or `signAndEncrypt` functions.
229 */
324KMime::Content *createEncryptedPart(QByteArray encryptedData) 230KMime::Content *createEncryptedPart(QByteArray encryptedData)
325{ 231{
326 KMime::Content *result = new KMime::Content; 232 KMime::Content *result = new KMime::Content;
@@ -358,20 +264,47 @@ KMime::Content *createEncryptedPart(QByteArray encryptedData)
358 return result; 264 return result;
359} 265}
360 266
361KMime::Content *createEncryptedEmail(KMime::Content *content, const std::vector<GpgME::Key> &encryptionKeys, 267/**
362 bool sign, const std::vector<GpgME::Key> &signingKeys = {}) 268 * Create an encrypted (optionally signed) email with a public key attached to it.
269 *
270 * Will create a message like this:
271 *
272 * + `multipart/mixed`
273 * - `multipart/encrypted`
274 * + `application/pgp-encrypted
275 * + `application/octet-stream` (a generated encrypted version of the original message)
276 * - `application/pgp-keys` (the public key as attachment, which is the first of the
277 * `signingKeys`)
278 */
279Expected<GpgME::Error, KMime::Content *>
280createEncryptedEmail(KMime::Content *content, const std::vector<GpgME::Key> &encryptionKeys,
281 const GpgME::Key &attachedKey, const std::vector<GpgME::Key> &signingKeys = {})
363{ 282{
364 auto contentToEncrypt = canonicalizeContent(content); 283 auto contentToEncrypt = canonicalizeContent(content);
365 284
366 auto encryptedData = sign ? encrypt(contentToEncrypt, encryptionKeys) : 285 auto encryptionResult = signingKeys.empty() ?
286 encrypt(contentToEncrypt, encryptionKeys) :
367 signAndEncrypt(contentToEncrypt, signingKeys, encryptionKeys); 287 signAndEncrypt(contentToEncrypt, signingKeys, encryptionKeys);
288
289 if (!encryptionResult) {
290 return makeUnexpected(encryptionResult.error());
291 }
292
293 auto encryptedData = encryptionResult.value();
294
368 KMime::Content *encryptedPart = createEncryptedPart(encryptedData); 295 KMime::Content *encryptedPart = createEncryptedPart(encryptedData);
369 296
370 // TODO: check signingKeys not empty 297 auto publicKeyAppendResult = appendPublicKey(encryptedPart, attachedKey);
371 return setMessageAndPublicKey(encryptedPart, signingKeys[0]); 298
299 // TODO: this is ugly
300 if (!publicKeyAppendResult) {
301 delete encryptedPart;
302 }
303
304 return publicKeyAppendResult;
372} 305}
373 306
374QByteArray sign(const QByteArray &content, const std::vector<GpgME::Key> &signingKeys) 307Expected<GpgME::Error, QByteArray> sign(const QByteArray &content, const std::vector<GpgME::Key> &signingKeys)
375{ 308{
376 QByteArray resultData; 309 QByteArray resultData;
377 310
@@ -381,20 +314,25 @@ QByteArray sign(const QByteArray &content, const std::vector<GpgME::Key> &signin
381 314
382 if (result.error().code()) { 315 if (result.error().code()) {
383 qWarning() << "Signing failed:" << result.error().asString(); 316 qWarning() << "Signing failed:" << result.error().asString();
384 // TODO: XXX: handle errors better 317 return makeUnexpected(result.error());
385 // throw std::runtime_error("Signing failed");
386 return "";
387 } 318 }
388 319
389 return resultData; 320 return resultData;
390} 321}
391 322
392// Create a message like this (according to RFC 3156 Section 5): 323/**
393// 324 * Create a message like this (according to RFC 3156 Section 5):
394// - multipart/signed 325 *
395// - whatever the given original message is (should be canonicalized) 326 * + `multipart/signed`
396// - application/octet-stream (given encrypted data) 327 * - whatever the given original `message` is (should be canonicalized)
397KMime::Content *createSignedPart(KMime::Content *message, const QByteArray &signedData, const QString &micAlg) 328 * - `application/octet-stream` (the given `signature`)
329 *
330 * Should not be used directly since the public key should be attached, hence
331 * the `createSignedEmail` function.
332 *
333 * The signature can be generated by the `sign` function.
334 */
335KMime::Content *createSignedPart(KMime::Content *message, const QByteArray &signature, const QString &micAlg)
398{ 336{
399 KMime::Content *result = new KMime::Content; 337 KMime::Content *result = new KMime::Content;
400 338
@@ -413,7 +351,7 @@ KMime::Content *createSignedPart(KMime::Content *message, const QByteArray &sign
413 signedPartPart->contentDescription()->from7BitString( 351 signedPartPart->contentDescription()->from7BitString(
414 "This is a digitally signed message part"); 352 "This is a digitally signed message part");
415 353
416 signedPartPart->setBody(signedData); 354 signedPartPart->setBody(signature);
417 355
418 result->addContent(signedPartPart); 356 result->addContent(signedPartPart);
419 } 357 }
@@ -421,283 +359,64 @@ KMime::Content *createSignedPart(KMime::Content *message, const QByteArray &sign
421 return result; 359 return result;
422} 360}
423 361
424KMime::Content *createSignedEmail(KMime::Content *content, const std::vector<GpgME::Key> &signingKeys) 362/**
363 * Create a signed email with a public key attached to it.
364 *
365 * Will create a message like this:
366 *
367 * + `multipart/mixed`
368 * - `multipart/signed`
369 * + whatever the given original `content` is (should not be canonalized)
370 * + `application/octet-stream` (a generated signature of the original message)
371 * - `application/pgp-keys` (the public key as attachment, which is the first of the
372 * `signingKeys`)
373 */
374Expected<GpgME::Error, KMime::Content *> createSignedEmail(KMime::Content *content,
375 const std::vector<GpgME::Key> &signingKeys, const GpgME::Key &attachedKey)
425{ 376{
426 auto contentToSign = canonicalizeContent(content); 377 Q_ASSERT(!signingKeys.empty());
427 378
428 auto signedData = sign(contentToSign, signingKeys); 379 auto contentToSign = canonicalizeContent(content);
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
435void makeToplevelContentType(KMime::Content *content, bool encrypt, const QByteArray &hashAlgo)
436{
437 //Kleo::CryptoMessageFormat format,
438 // switch (format) {
439 // default:
440 // case Kleo::InlineOpenPGPFormat:
441 // case Kleo::OpenPGPMIMEFormat:
442 if (encrypt) {
443 content->contentType()->setMimeType(QByteArrayLiteral("multipart/encrypted"));
444 content->contentType()->setParameter(QStringLiteral("protocol"), QStringLiteral("application/pgp-encrypted"));
445 } else {
446 content->contentType()->setMimeType(QByteArrayLiteral("multipart/signed"));
447 content->contentType()->setParameter(QStringLiteral("protocol"), QStringLiteral("application/pgp-signature"));
448 content->contentType()->setParameter(QStringLiteral("micalg"), QString::fromLatin1(QByteArray(QByteArrayLiteral("pgp-") + hashAlgo)).toLower());
449 }
450 return;
451 // case Kleo::SMIMEFormat:
452 // if (sign) {
453 // qCDebug(MESSAGECOMPOSER_LOG) << "setting headers for SMIME";
454 // content->contentType()->setMimeType(QByteArrayLiteral("multipart/signed"));
455 // content->contentType()->setParameter(QStringLiteral("protocol"), QString::fromAscii("application/pkcs7-signature"));
456 // content->contentType()->setParameter(QStringLiteral("micalg"), QString::fromAscii(hashAlgo).toLower());
457 // return;
458 // }
459 // // fall through (for encryption, there's no difference between
460 // // SMIME and SMIMEOpaque, since there is no mp/encrypted for
461 // // S/MIME)
462 // case Kleo::SMIMEOpaqueFormat:
463
464 // qCDebug(MESSAGECOMPOSER_LOG) << "setting headers for SMIME/opaque";
465 // content->contentType()->setMimeType(QByteArrayLiteral("application/pkcs7-mime"));
466
467 // if (sign) {
468 // content->contentType()->setParameter(QStringLiteral("smime-type"), QString::fromAscii("signed-data"));
469 // } else {
470 // content->contentType()->setParameter(QStringLiteral("smime-type"), QString::fromAscii("enveloped-data"));
471 // }
472 // content->contentType()->setParameter(QStringLiteral("name"), QString::fromAscii("smime.p7m"));
473 // }
474}
475 380
476void setNestedContentType(KMime::Content *content, bool encrypt) 381 auto signingResult = sign(contentToSign, signingKeys);
477{
478// , Kleo::CryptoMessageFormat format
479 // switch (format) {
480 // case Kleo::OpenPGPMIMEFormat:
481 if (encrypt) {
482 content->contentType()->setMimeType(QByteArrayLiteral("application/octet-stream"));
483 } else {
484 content->contentType()->setMimeType(QByteArrayLiteral("application/pgp-signature"));
485 content->contentType()->setParameter(QStringLiteral("name"), QString::fromLatin1("signature.asc"));
486 content->contentDescription()->from7BitString("This is a digitally signed message part.");
487 }
488 return;
489 // case Kleo::SMIMEFormat:
490 // if (sign) {
491 // content->contentType()->setMimeType(QByteArrayLiteral("application/pkcs7-signature"));
492 // content->contentType()->setParameter(QStringLiteral("name"), QString::fromAscii("smime.p7s"));
493 // return;
494 // }
495 // // fall through:
496 // default:
497 // case Kleo::InlineOpenPGPFormat:
498 // case Kleo::SMIMEOpaqueFormat:
499 // ;
500 // }
501}
502 382
503void setNestedContentDisposition(KMime::Content *content, bool encrypt) 383 if (!signingResult) {
504{ 384 return makeUnexpected(signingResult.error());
505// Kleo::CryptoMessageFormat format,
506 // if (!sign && format & Kleo::OpenPGPMIMEFormat) {
507 if (encrypt) {
508 content->contentDisposition()->setDisposition(KMime::Headers::CDinline);
509 content->contentDisposition()->setFilename(QStringLiteral("msg.asc"));
510 // } else if (sign && format & Kleo::SMIMEFormat) {
511 // content->contentDisposition()->setDisposition(KMime::Headers::CDattachment);
512 // content->contentDisposition()->setFilename(QStringLiteral("smime.p7s"));
513 } 385 }
514}
515 386
516// bool MessageComposer::Util::makeMultiMime(Kleo::CryptoMessageFormat format, bool sign) 387 auto signedData = signingResult.value();
517// {
518// switch (format) {
519// default:
520// case Kleo::InlineOpenPGPFormat:
521// case Kleo::SMIMEOpaqueFormat: return false;
522// case Kleo::OpenPGPMIMEFormat: return true;
523// case Kleo::SMIMEFormat: return sign; // only on sign - there's no mp/encrypted for S/MIME
524// }
525// }
526
527KMime::Content *composeHeadersAndBody(KMime::Content *orig, QByteArray encodedBody, bool encrypt, const QByteArray &hashAlgo)
528{
529 // Kleo::CryptoMessageFormat format,
530 KMime::Content *result = new KMime::Content;
531 388
532 // called should have tested that the signing/encryption failed 389 KMime::Content *signedPart = createSignedPart(content, signedData, "TODO: pgp-something");
533 Q_ASSERT(!encodedBody.isEmpty());
534
535 // if (!(format & Kleo::InlineOpenPGPFormat)) { // make a MIME message
536 // qDebug() << "making MIME message, format:" << format;
537 makeToplevelContentType(result, encrypt, hashAlgo);
538
539 // if (makeMultiMime(sign)) { // sign/enc PGPMime, sign SMIME
540 if (true) { // sign/enc PGPMime, sign SMIME
541
542 const QByteArray boundary = KMime::multiPartBoundary();
543 result->contentType()->setBoundary(boundary);
544
545 result->assemble();
546 //qCDebug(MESSAGECOMPOSER_LOG) << "processed header:" << result->head();
547
548 // Build the encapsulated MIME parts.
549 // Build a MIME part holding the code information
550 // taking the body contents returned in ciphertext.
551 KMime::Content *code = new KMime::Content;
552 setNestedContentType(code, encrypt);
553 setNestedContentDisposition(code, encrypt);
554
555 if (encrypt) { // enc PGPMime (and / or sign)
556
557 //addPublicKeyAsAttachment(content, signingKeys[0]);
558
559 setBodyAndCTE(encodedBody, orig->contentType(), code);
560
561 // Build a MIME part holding the version information
562 // taking the body contents returned in
563 // structuring.data.bodyTextVersion.
564 KMime::Content *vers = new KMime::Content;
565 vers->contentType()->setMimeType("application/pgp-encrypted");
566 vers->contentDisposition()->setDisposition(KMime::Headers::CDattachment);
567 vers->contentTransferEncoding()->setEncoding(KMime::Headers::CE7Bit);
568 vers->setBody("Version: 1");
569
570 result->addContent(vers);
571 result->addContent(code);
572 } else { // sign PGPMime, sign SMIME
573 // if (format & Kleo::AnySMIME) { // sign SMIME
574 // code->contentTransferEncoding()->setEncoding(KMime::Headers::CEbase64);
575 // code->contentTransferEncoding()->needToEncode();
576 // code->setBody(encodedBody);
577 // } else { // sign PGPMmime
578 setBodyAndCTE(encodedBody, orig->contentType(), code);
579 // }
580 result->addContent(orig);
581 result->addContent(code);
582 }
583 } else { //enc SMIME, sign/enc SMIMEOpaque
584 result->contentTransferEncoding()->setEncoding(KMime::Headers::CEbase64);
585 result->contentDisposition()->setDisposition(KMime::Headers::CDattachment);
586 result->contentDisposition()->setFilename(QStringLiteral("smime.p7m"));
587 390
588 result->assemble(); 391 auto publicKeyAppendResult = appendPublicKey(signedPart, attachedKey);
589 //qCDebug(MESSAGECOMPOSER_LOG) << "processed header:" << result->head();
590 392
591 result->setBody(encodedBody); 393 // TODO: this is ugly (maybe use a std::unique_ptr for signedPart)
592 } 394 if (!publicKeyAppendResult) {
593 // } else { // sign/enc PGPInline 395 delete signedPart;
594 // result->setHead(orig->head()); 396 }
595 // result->parse();
596 397
597 // // fixing ContentTransferEncoding 398 return publicKeyAppendResult;
598 // setBodyAndCTE(encodedBody, orig->contentType(), result);
599 // }
600 result->assemble();
601 return result;
602} 399}
603 400
604// bool binaryHint(Kleo::CryptoMessageFormat f) 401KMime::Content *MailCrypto::processCrypto(KMime::Content *content,
605// { 402 const std::vector<GpgME::Key> &signingKeys, const std::vector<GpgME::Key> &encryptionKeys,
606// switch (f) { 403 const GpgME::Key &attachedKey, MailCrypto::Protocol protocol)
607// case Kleo::SMIMEFormat:
608// case Kleo::SMIMEOpaqueFormat:
609// return true;
610// default:
611// case Kleo::OpenPGPMIMEFormat:
612// case Kleo::InlineOpenPGPFormat:
613// return false;
614// }
615// }
616//
617 // GpgME::SignatureMode signingMode(Kleo::CryptoMessageFormat f)
618 // {
619 // switch (f) {
620 // case Kleo::SMIMEOpaqueFormat:
621 // return GpgME::NormalSignatureMode;
622 // case Kleo::InlineOpenPGPFormat:
623 // return GpgME::Clearsigned;
624 // default:
625 // case Kleo::SMIMEFormat:
626 // case Kleo::OpenPGPMIMEFormat:
627 // return GpgME::Detached;
628 // }
629 // }
630
631KMime::Content *MailCrypto::processCrypto(KMime::Content *content, const std::vector<GpgME::Key> &signingKeys, const std::vector<GpgME::Key> &encryptionKeys, MailCrypto::Protocol protocol)
632{ 404{
633 405
634 const bool sign = !signingKeys.empty(); 406 qDebug() << "Attaching key:" << attachedKey.shortKeyID() << "from processCrypto";
635 407
636 if(!encryptionKeys.empty()) { 408 if(!encryptionKeys.empty()) {
637 return createEncryptedEmail(content, encryptionKeys, sign, signingKeys); 409 // TODO
638 } else if(sign) { 410 return createEncryptedEmail(content, encryptionKeys, attachedKey, signingKeys).value();
639 return createSignedEmail(content, signingKeys); 411 } else if(!signingKeys.empty()) {
412 // TODO
413 return createSignedEmail(content, signingKeys, signingKeys[0]).value();
640 } else { 414 } else {
641 qWarning() << "Processing cryptography, but neither signing nor encrypting"; 415 qWarning() << "Processing cryptography, but neither signing nor encrypting";
642 return content; 416 return content;
643 } 417 }
644
645 const QGpgME::Protocol *const proto = protocol == MailCrypto::SMIME ? QGpgME::smime() : QGpgME::openpgp();
646 Q_ASSERT(proto);
647
648 auto signingMode = GpgME::Detached;
649 bool armor = true;
650 bool textMode = false;
651 //const bool sign = !signingKeys.empty();
652 const bool encrypt = !encryptionKeys.empty();
653
654 QByteArray resultContent;
655 QByteArray hashAlgo;
656 //Trust provided keys and don't check them for validity
657 bool alwaysTrust = true;
658 if (sign && encrypt) {
659 std::unique_ptr<QGpgME::SignEncryptJob> job(proto->signEncryptJob(armor, textMode));
660 const auto res = job->exec(signingKeys, encryptionKeys, canonicalizeContent(content), alwaysTrust, resultContent);
661 if (res.first.error().code()) {
662 qWarning() << "Signing failed:" << res.first.error().asString();
663 return nullptr;
664 } else {
665 hashAlgo = res.first.createdSignature(0).hashAlgorithmAsString();
666 }
667 if (res.second.error().code()) {
668 qWarning() << "Encryption failed:" << res.second.error().asString();
669 return nullptr;
670 }
671 } else if (sign) {
672 std::unique_ptr<QGpgME::SignJob> job(proto->signJob(armor, textMode));
673 auto result = job->exec(signingKeys, canonicalizeContent(content), signingMode, resultContent);
674 if (result.error().code()) {
675 qWarning() << "Signing failed:" << result.error().asString();
676 return nullptr;
677 }
678 hashAlgo = result.createdSignature(0).hashAlgorithmAsString();
679 } else if (encrypt) {
680 std::unique_ptr<QGpgME::EncryptJob> job(proto->encryptJob(armor, textMode));
681 const auto result = job->exec(encryptionKeys, canonicalizeContent(content), alwaysTrust, resultContent);
682 if (result.error().code()) {
683 qWarning() << "Encryption failed:" << result.error().asString();
684 return nullptr;
685 }
686 hashAlgo = "pgp-sha1";
687 } else {
688 qWarning() << "Not signing or encrypting";
689 return nullptr;
690 }
691
692 return composeHeadersAndBody(content, resultContent, encrypt, hashAlgo);
693}
694
695KMime::Content *MailCrypto::sign(KMime::Content *content, const std::vector<GpgME::Key> &signers)
696{
697 return processCrypto(content, signers, {}, OPENPGP);
698} 418}
699 419
700
701void MailCrypto::importKeys(const std::vector<GpgME::Key> &keys) 420void MailCrypto::importKeys(const std::vector<GpgME::Key> &keys)
702{ 421{
703 const QGpgME::Protocol *const backend = QGpgME::openpgp(); 422 const QGpgME::Protocol *const backend = QGpgME::openpgp();
@@ -753,4 +472,3 @@ std::vector<GpgME::Key> MailCrypto::findKeys(const QStringList &filter, bool fin
753 472
754 return keys; 473 return keys;
755} 474}
756
diff --git a/framework/src/domain/mime/mailcrypto.h b/framework/src/domain/mime/mailcrypto.h
index 0a6c2f4c..724d6427 100644
--- a/framework/src/domain/mime/mailcrypto.h
+++ b/framework/src/domain/mime/mailcrypto.h
@@ -30,8 +30,7 @@ namespace MailCrypto
30 OPENPGP, 30 OPENPGP,
31 SMIME 31 SMIME
32 }; 32 };
33 KMime::Content *processCrypto(KMime::Content *content, const std::vector<GpgME::Key> &signingKeys, const std::vector<GpgME::Key> &encryptionKeys, MailCrypto::Protocol protocol); 33 KMime::Content *processCrypto(KMime::Content *content, const std::vector<GpgME::Key> &signingKeys, const std::vector<GpgME::Key> &encryptionKeys, const GpgME::Key &attachedKey, MailCrypto::Protocol protocol);
34 KMime::Content *sign(KMime::Content *content, const std::vector<GpgME::Key> &signers);
35 std::vector<GpgME::Key> findKeys(const QStringList &filter, bool findPrivate = false, bool remote = false, Protocol protocol = OPENPGP); 34 std::vector<GpgME::Key> findKeys(const QStringList &filter, bool findPrivate = false, bool remote = false, Protocol protocol = OPENPGP);
36 void importKeys(const std::vector<GpgME::Key> &keys); 35 void importKeys(const std::vector<GpgME::Key> &keys);
37}; 36};
diff --git a/framework/src/domain/mime/mailtemplates.cpp b/framework/src/domain/mime/mailtemplates.cpp
index 30f9a48d..399b6aa1 100644
--- a/framework/src/domain/mime/mailtemplates.cpp
+++ b/framework/src/domain/mime/mailtemplates.cpp
@@ -1025,7 +1025,11 @@ static KMime::Types::Mailbox::List stringListToMailboxes(const QStringList &list
1025 return mailboxes; 1025 return mailboxes;
1026} 1026}
1027 1027
1028KMime::Message::Ptr MailTemplates::createMessage(KMime::Message::Ptr existingMessage, const QStringList &to, const QStringList &cc, const QStringList &bcc, const KMime::Types::Mailbox &from, const QString &subject, const QString &body, bool htmlBody, const QList<Attachment> &attachments, const std::vector<GpgME::Key> &signingKeys, const std::vector<GpgME::Key> &encryptionKeys) 1028KMime::Message::Ptr MailTemplates::createMessage(KMime::Message::Ptr existingMessage,
1029 const QStringList &to, const QStringList &cc, const QStringList &bcc,
1030 const KMime::Types::Mailbox &from, const QString &subject, const QString &body, bool htmlBody,
1031 const QList<Attachment> &attachments, const std::vector<GpgME::Key> &signingKeys,
1032 const std::vector<GpgME::Key> &encryptionKeys, const GpgME::Key &attachedKey)
1029{ 1033{
1030 auto mail = existingMessage; 1034 auto mail = existingMessage;
1031 if (!mail) { 1035 if (!mail) {
@@ -1089,7 +1093,8 @@ KMime::Message::Ptr MailTemplates::createMessage(KMime::Message::Ptr existingMes
1089 1093
1090 QByteArray bodyData; 1094 QByteArray bodyData;
1091 if (!signingKeys.empty() || !encryptionKeys.empty()) { 1095 if (!signingKeys.empty() || !encryptionKeys.empty()) {
1092 auto result = MailCrypto::processCrypto(bodyPart.get(), signingKeys, encryptionKeys, MailCrypto::OPENPGP); 1096 auto result = MailCrypto::processCrypto(
1097 bodyPart.get(), signingKeys, encryptionKeys, attachedKey, MailCrypto::OPENPGP);
1093 if (!result) { 1098 if (!result) {
1094 qWarning() << "Signing failed"; 1099 qWarning() << "Signing failed";
1095 return {}; 1100 return {};
diff --git a/framework/src/domain/mime/mailtemplates.h b/framework/src/domain/mime/mailtemplates.h
index 9447e169..154b76a2 100644
--- a/framework/src/domain/mime/mailtemplates.h
+++ b/framework/src/domain/mime/mailtemplates.h
@@ -38,5 +38,5 @@ namespace MailTemplates
38 void forward(const KMime::Message::Ptr &origMsg, const std::function<void(const KMime::Message::Ptr &result)> &callback); 38 void forward(const KMime::Message::Ptr &origMsg, const std::function<void(const KMime::Message::Ptr &result)> &callback);
39 QString plaintextContent(const KMime::Message::Ptr &origMsg); 39 QString plaintextContent(const KMime::Message::Ptr &origMsg);
40 QString body(const KMime::Message::Ptr &msg, bool &isHtml); 40 QString body(const KMime::Message::Ptr &msg, bool &isHtml);
41 KMime::Message::Ptr createMessage(KMime::Message::Ptr existingMessage, const QStringList &to, const QStringList &cc, const QStringList &bcc, const KMime::Types::Mailbox &from, const QString &subject, const QString &body, bool htmlBody, const QList<Attachment> &attachments, const std::vector<GpgME::Key> &signingKeys = {}, const std::vector<GpgME::Key> &encryptionKeys = {}); 41 KMime::Message::Ptr createMessage(KMime::Message::Ptr existingMessage, const QStringList &to, const QStringList &cc, const QStringList &bcc, const KMime::Types::Mailbox &from, const QString &subject, const QString &body, bool htmlBody, const QList<Attachment> &attachments, const std::vector<GpgME::Key> &signingKeys = {}, const std::vector<GpgME::Key> &encryptionKeys = {}, const GpgME::Key &attachedKey = {});
42}; 42};
diff --git a/framework/src/errors.h b/framework/src/errors.h
index f6c6cfab..9c48f32a 100644
--- a/framework/src/errors.h
+++ b/framework/src/errors.h
@@ -20,9 +20,13 @@ public:
20 20
21 // For implicit conversions when doing makeUnexpected(other) 21 // For implicit conversions when doing makeUnexpected(other)
22 template <typename Other> 22 template <typename Other>
23 constexpr explicit Unexpected(const Unexpected<Other> &error) : mValue(error.value()) {} 23 constexpr explicit Unexpected(const Unexpected<Other> &error) : mValue(error.value())
24 {
25 }
24 template <typename Other> 26 template <typename Other>
25 constexpr explicit Unexpected(Unexpected<Other> &&error) : mValue(std::move(error.value())) {} 27 constexpr explicit Unexpected(Unexpected<Other> &&error) : mValue(std::move(error.value()))
28 {
29 }
26 30
27 constexpr const Error &value() const & 31 constexpr const Error &value() const &
28 { 32 {
@@ -82,7 +86,7 @@ protected:
82 86
83 constexpr void copyPartFromOther(const StorageBase &other) 87 constexpr void copyPartFromOther(const StorageBase &other)
84 { 88 {
85 if (isValue) { 89 if (mIsValue) {
86 mValue = other.mValue; 90 mValue = other.mValue;
87 } else { 91 } else {
88 mError = other.mError; 92 mError = other.mError;
@@ -91,40 +95,40 @@ protected:
91 95
92 void movePartFromOther(StorageBase &&other) 96 void movePartFromOther(StorageBase &&other)
93 { 97 {
94 if (isValue) { 98 if (mIsValue) {
95 mValue = std::move(other.mValue); 99 mValue = std::move(other.mValue);
96 } else { 100 } else {
97 mError = std::move(other.mError); 101 mError = std::move(other.mError);
98 } 102 }
99 } 103 }
100 104
101 StorageBase(const StorageBase &other) : isValue(other.isValue) 105 StorageBase(const StorageBase &other) : mIsValue(other.mIsValue)
102 { 106 {
103 copyPartFromOther(other); 107 copyPartFromOther(other);
104 } 108 }
105 109
106 StorageBase(StorageBase &&other) : isValue(other.isValue) 110 StorageBase(StorageBase &&other) : mIsValue(other.mIsValue)
107 { 111 {
108 movePartFromOther(std::move(other)); 112 movePartFromOther(std::move(other));
109 } 113 }
110 114
111 constexpr StorageBase &operator=(const StorageBase &other) 115 constexpr StorageBase &operator=(const StorageBase &other)
112 { 116 {
113 isValue = other.isValue; 117 mIsValue = other.mIsValue;
114 copyPartFromOther(other); 118 copyPartFromOther(other);
115 return *this; 119 return *this;
116 } 120 }
117 121
118 constexpr StorageBase &operator=(StorageBase &&other) 122 constexpr StorageBase &operator=(StorageBase &&other)
119 { 123 {
120 isValue = other.isValue; 124 mIsValue = other.mIsValue;
121 movePartFromOther(other); 125 movePartFromOther(other);
122 return *this; 126 return *this;
123 } 127 }
124 128
125 ~StorageBase() 129 ~StorageBase()
126 { 130 {
127 if (isValue) { 131 if (mIsValue) {
128 mValue.~Type(); 132 mValue.~Type();
129 } else { 133 } else {
130 mError.~Unexpected<Error>(); 134 mError.~Unexpected<Error>();
@@ -135,13 +139,13 @@ protected:
135 139
136 template <typename... Args> 140 template <typename... Args>
137 constexpr StorageBase(tags::Expected, Args &&... args) 141 constexpr StorageBase(tags::Expected, Args &&... args)
138 : mValue(std::forward<Args>(args)...), isValue(true) 142 : mValue(std::forward<Args>(args)...), mIsValue(true)
139 { 143 {
140 } 144 }
141 145
142 template <typename... Args> 146 template <typename... Args>
143 constexpr StorageBase(tags::Unexpected, Args &&... args) 147 constexpr StorageBase(tags::Unexpected, Args &&... args)
144 : mError(std::forward<Args>(args)...), isValue(false) 148 : mError(std::forward<Args>(args)...), mIsValue(false)
145 { 149 {
146 } 150 }
147 151
@@ -150,7 +154,7 @@ protected:
150 Unexpected<Error> mError; 154 Unexpected<Error> mError;
151 Type mValue; 155 Type mValue;
152 }; 156 };
153 bool isValue; 157 bool mIsValue;
154}; 158};
155 159
156// Write functions here when storage related and when Type == void 160// Write functions here when storage related and when Type == void
@@ -158,16 +162,16 @@ template <typename Error>
158struct StorageBase<Error, void> 162struct StorageBase<Error, void>
159{ 163{
160protected: 164protected:
161 constexpr StorageBase(tags::Expected) : isValue(true) {} 165 constexpr StorageBase(tags::Expected) : mIsValue(true) {}
162 166
163 template <typename... Args> 167 template <typename... Args>
164 constexpr StorageBase(tags::Unexpected, Args &&... args) 168 constexpr StorageBase(tags::Unexpected, Args &&... args)
165 : mError(std::forward<Args>(args)...), isValue(false) 169 : mError(std::forward<Args>(args)...), mIsValue(false)
166 { 170 {
167 } 171 }
168 172
169 Unexpected<Error> mError; 173 Unexpected<Error> mError;
170 bool isValue; 174 bool mIsValue;
171}; 175};
172 176
173// Write functions here when storage related, whether Type is void or not 177// Write functions here when storage related, whether Type is void or not
@@ -205,8 +209,11 @@ struct ExpectedBase : detail::Storage<Error, Type>
205 { 209 {
206 } 210 }
207 211
212 // Warning: will crash if this is an error. You should always check this is
213 // an expected value before calling `.value()`
208 constexpr const Type &value() const & 214 constexpr const Type &value() const &
209 { 215 {
216 Q_ASSERT(this->mIsValue);
210 return this->mValue; 217 return this->mValue;
211 } 218 }
212}; 219};
@@ -243,4 +250,13 @@ public:
243 { 250 {
244 return this->mError.value(); 251 return this->mError.value();
245 } 252 }
253
254 constexpr bool isValue() const
255 {
256 return this->mIsValue;
257 }
258 constexpr explicit operator bool() const
259 {
260 return this->mIsValue;
261 }
246}; 262};