summaryrefslogtreecommitdiffstats
path: root/framework/src/domain/mime/mailcrypto.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'framework/src/domain/mime/mailcrypto.cpp')
-rw-r--r--framework/src/domain/mime/mailcrypto.cpp691
1 files changed, 325 insertions, 366 deletions
diff --git a/framework/src/domain/mime/mailcrypto.cpp b/framework/src/domain/mime/mailcrypto.cpp
index 62a71e42..4e20c84b 100644
--- a/framework/src/domain/mime/mailcrypto.cpp
+++ b/framework/src/domain/mime/mailcrypto.cpp
@@ -20,324 +20,28 @@
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 "framework/src/errors.h"
25
26#include <QGpgME/DataProvider>
25#include <QGpgME/EncryptJob> 27#include <QGpgME/EncryptJob>
26#include <QGpgME/SignEncryptJob> 28#include <QGpgME/ExportJob>
27#include <QGpgME/ImportFromKeyserverJob> 29#include <QGpgME/ImportFromKeyserverJob>
28#include <gpgme++/global.h> 30#include <QGpgME/Protocol>
29#include <gpgme++/signingresult.h> 31#include <QGpgME/SignEncryptJob>
32#include <QGpgME/SignJob>
33
34#include <gpgme++/data.h>
30#include <gpgme++/encryptionresult.h> 35#include <gpgme++/encryptionresult.h>
31#include <gpgme++/keylistresult.h> 36#include <gpgme++/global.h>
32#include <gpgme++/importresult.h> 37#include <gpgme++/importresult.h>
33#include <QDebug> 38#include <gpgme++/keylistresult.h>
34 39#include <gpgme++/signingresult.h>
35/*
36 * FIXME:
37 *
38 * This code is WIP.
39 * It currently only implements OpenPGPMIMEFormat for signing.
40 * All the commented code are intentional leftovers that we can clean-up
41 * once all necessary signing mechanisms have been implemented.
42 *
43 * Creating an ecrypted mail:
44 * * get keys (email -> fingreprint -> key)
45 * * Use Kleo::OpenPGPMIMEFormat,
46 *
47 */
48
49// bool chooseCTE()
50// {
51// Q_Q(SinglepartJob);
52
53// auto allowed = KMime::encodingsForData(data);
54
55// if (!q->globalPart()->is8BitAllowed()) {
56// allowed.removeAll(KMime::Headers::CE8Bit);
57// }
58
59// #if 0 //TODO signing
60// // In the following cases only QP and Base64 are allowed:
61// // - the buffer will be OpenPGP/MIME signed and it contains trailing
62// // whitespace (cf. RFC 3156)
63// // - a line starts with "From "
64// if ((willBeSigned && cf.hasTrailingWhitespace()) ||
65// cf.hasLeadingFrom()) {
66// ret.removeAll(DwMime::kCte8bit);
67// ret.removeAll(DwMime::kCte7bit);
68// }
69// #endif
70
71// if (contentTransferEncoding) {
72// // Specific CTE set. Check that our data fits in it.
73// if (!allowed.contains(contentTransferEncoding->encoding())) {
74// q->setError(JobBase::BugError);
75// q->setErrorText(i18n("%1 Content-Transfer-Encoding cannot correctly encode this message.",
76// KMime::nameForEncoding(contentTransferEncoding->encoding())));
77// return false;
78// // TODO improve error message in case 8bit is requested but not allowed.
79// }
80// } else {
81// // No specific CTE set. Choose the best one.
82// Q_ASSERT(!allowed.isEmpty());
83// contentTransferEncoding = new KMime::Headers::ContentTransferEncoding;
84// contentTransferEncoding->setEncoding(allowed.first());
85// }
86// qCDebug(MESSAGECOMPOSER_LOG) << "Settled on encoding" << KMime::nameForEncoding(contentTransferEncoding->encoding());
87// return true;
88// }
89
90KMime::Content *createPart(const QByteArray &encodedBody, const QByteArray &mimeType, const QByteArray &charset)
91{
92 auto resultContent = new KMime::Content;
93
94 auto contentType = new KMime::Headers::ContentType;
95 contentType->setMimeType(mimeType);
96 contentType->setMimeType(charset);
97 // if (!chooseCTE()) {
98 // Q_ASSERT(error());
99 // emitResult();
100 // return;
101 // }
102
103 // Set headers.
104 // if (contentDescription) {
105 // resultContent->setHeader(contentDescription);
106 // }
107 // if (contentDisposition) {
108 // resultContent->setHeader(contentDisposition);
109 // }
110 // if (contentID) {
111 // resultContent->setHeader(contentID);
112 // }
113 // Q_ASSERT(contentTransferEncoding); // chooseCTE() created it if it didn't exist.
114 auto contentTransferEncoding = new KMime::Headers::ContentTransferEncoding;
115 auto allowed = KMime::encodingsForData(encodedBody);
116 Q_ASSERT(!allowed.isEmpty());
117 contentTransferEncoding->setEncoding(allowed.first());
118 resultContent->setHeader(contentTransferEncoding);
119
120 if (contentType) {
121 resultContent->setHeader(contentType);
122 }
123
124 // Set data.
125 resultContent->setBody(encodedBody);
126 return resultContent;
127}
128
129KMime::Content *setBodyAndCTE(QByteArray &encodedBody, KMime::Headers::ContentType *contentType, KMime::Content *ret)
130{
131 // MessageComposer::Composer composer;
132 // MessageComposer::SinglepartJob cteJob(&composer);
133 auto part = createPart(encodedBody, contentType->mimeType(), contentType->charset());
134 part->assemble();
135
136 // cteJob.contentType()->setMimeType(contentType->mimeType());
137 // cteJob.contentType()->setCharset(contentType->charset());
138 // cteJob.setData(encodedBody);
139 // cteJob.exec();
140 // cteJob.content()->assemble();
141
142 ret->contentTransferEncoding()->setEncoding(part->contentTransferEncoding()->encoding());
143 ret->setBody(part->encodedBody());
144
145 return ret;
146}
147
148void makeToplevelContentType(KMime::Content *content, bool sign, const QByteArray &hashAlgo)
149{
150 //Kleo::CryptoMessageFormat format,
151 // switch (format) {
152 // default:
153 // case Kleo::InlineOpenPGPFormat:
154 // case Kleo::OpenPGPMIMEFormat:
155 if (sign) {
156 content->contentType()->setMimeType(QByteArrayLiteral("multipart/signed"));
157 content->contentType()->setParameter(QStringLiteral("protocol"), QStringLiteral("application/pgp-signature"));
158 content->contentType()->setParameter(QStringLiteral("micalg"), QString::fromLatin1(QByteArray(QByteArrayLiteral("pgp-") + hashAlgo)).toLower());
159
160 } else {
161 content->contentType()->setMimeType(QByteArrayLiteral("multipart/encrypted"));
162 content->contentType()->setParameter(QStringLiteral("protocol"), QStringLiteral("application/pgp-encrypted"));
163 }
164 return;
165 // case Kleo::SMIMEFormat:
166 // if (sign) {
167 // qCDebug(MESSAGECOMPOSER_LOG) << "setting headers for SMIME";
168 // content->contentType()->setMimeType(QByteArrayLiteral("multipart/signed"));
169 // content->contentType()->setParameter(QStringLiteral("protocol"), QString::fromAscii("application/pkcs7-signature"));
170 // content->contentType()->setParameter(QStringLiteral("micalg"), QString::fromAscii(hashAlgo).toLower());
171 // return;
172 // }
173 // // fall through (for encryption, there's no difference between
174 // // SMIME and SMIMEOpaque, since there is no mp/encrypted for
175 // // S/MIME)
176 // case Kleo::SMIMEOpaqueFormat:
177
178 // qCDebug(MESSAGECOMPOSER_LOG) << "setting headers for SMIME/opaque";
179 // content->contentType()->setMimeType(QByteArrayLiteral("application/pkcs7-mime"));
180
181 // if (sign) {
182 // content->contentType()->setParameter(QStringLiteral("smime-type"), QString::fromAscii("signed-data"));
183 // } else {
184 // content->contentType()->setParameter(QStringLiteral("smime-type"), QString::fromAscii("enveloped-data"));
185 // }
186 // content->contentType()->setParameter(QStringLiteral("name"), QString::fromAscii("smime.p7m"));
187 // }
188}
189
190void setNestedContentType(KMime::Content *content, bool sign)
191{
192// , Kleo::CryptoMessageFormat format
193 // switch (format) {
194 // case Kleo::OpenPGPMIMEFormat:
195 if (sign) {
196 content->contentType()->setMimeType(QByteArrayLiteral("application/pgp-signature"));
197 content->contentType()->setParameter(QStringLiteral("name"), QString::fromLatin1("signature.asc"));
198 content->contentDescription()->from7BitString("This is a digitally signed message part.");
199 } else {
200 content->contentType()->setMimeType(QByteArrayLiteral("application/octet-stream"));
201 }
202 return;
203 // case Kleo::SMIMEFormat:
204 // if (sign) {
205 // content->contentType()->setMimeType(QByteArrayLiteral("application/pkcs7-signature"));
206 // content->contentType()->setParameter(QStringLiteral("name"), QString::fromAscii("smime.p7s"));
207 // return;
208 // }
209 // // fall through:
210 // default:
211 // case Kleo::InlineOpenPGPFormat:
212 // case Kleo::SMIMEOpaqueFormat:
213 // ;
214 // }
215}
216
217void setNestedContentDisposition(KMime::Content *content, bool sign)
218{
219// Kleo::CryptoMessageFormat format,
220 // if (!sign && format & Kleo::OpenPGPMIMEFormat) {
221 if (!sign) {
222 content->contentDisposition()->setDisposition(KMime::Headers::CDinline);
223 content->contentDisposition()->setFilename(QStringLiteral("msg.asc"));
224 // } else if (sign && format & Kleo::SMIMEFormat) {
225 // content->contentDisposition()->setDisposition(KMime::Headers::CDattachment);
226 // content->contentDisposition()->setFilename(QStringLiteral("smime.p7s"));
227 }
228}
229
230// bool MessageComposer::Util::makeMultiMime(Kleo::CryptoMessageFormat format, bool sign)
231// {
232// switch (format) {
233// default:
234// case Kleo::InlineOpenPGPFormat:
235// case Kleo::SMIMEOpaqueFormat: return false;
236// case Kleo::OpenPGPMIMEFormat: return true;
237// case Kleo::SMIMEFormat: return sign; // only on sign - there's no mp/encrypted for S/MIME
238// }
239// }
240
241KMime::Content *composeHeadersAndBody(KMime::Content *orig, QByteArray encodedBody, bool sign, const QByteArray &hashAlgo)
242{
243 // Kleo::CryptoMessageFormat format,
244 KMime::Content *result = new KMime::Content;
245
246 // called should have tested that the signing/encryption failed
247 Q_ASSERT(!encodedBody.isEmpty());
248
249 // if (!(format & Kleo::InlineOpenPGPFormat)) { // make a MIME message
250 // qDebug() << "making MIME message, format:" << format;
251 makeToplevelContentType(result, sign, hashAlgo);
252
253 // if (makeMultiMime(sign)) { // sign/enc PGPMime, sign SMIME
254 if (true) { // sign/enc PGPMime, sign SMIME
255
256 const QByteArray boundary = KMime::multiPartBoundary();
257 result->contentType()->setBoundary(boundary);
258
259 result->assemble();
260 //qCDebug(MESSAGECOMPOSER_LOG) << "processed header:" << result->head();
261
262 // Build the encapsulated MIME parts.
263 // Build a MIME part holding the code information
264 // taking the body contents returned in ciphertext.
265 KMime::Content *code = new KMime::Content;
266 setNestedContentType(code, sign);
267 setNestedContentDisposition(code, sign);
268
269 if (sign) { // sign PGPMime, sign SMIME
270 // if (format & Kleo::AnySMIME) { // sign SMIME
271 // code->contentTransferEncoding()->setEncoding(KMime::Headers::CEbase64);
272 // code->contentTransferEncoding()->needToEncode();
273 // code->setBody(encodedBody);
274 // } else { // sign PGPMmime
275 setBodyAndCTE(encodedBody, orig->contentType(), code);
276 // }
277 result->addContent(orig);
278 result->addContent(code);
279 } else { // enc PGPMime
280 setBodyAndCTE(encodedBody, orig->contentType(), code);
281
282 // Build a MIME part holding the version information
283 // taking the body contents returned in
284 // structuring.data.bodyTextVersion.
285 KMime::Content *vers = new KMime::Content;
286 vers->contentType()->setMimeType("application/pgp-encrypted");
287 vers->contentDisposition()->setDisposition(KMime::Headers::CDattachment);
288 vers->contentTransferEncoding()->setEncoding(KMime::Headers::CE7Bit);
289 vers->setBody("Version: 1");
290
291 result->addContent(vers);
292 result->addContent(code);
293 }
294 } else { //enc SMIME, sign/enc SMIMEOpaque
295 result->contentTransferEncoding()->setEncoding(KMime::Headers::CEbase64);
296 result->contentDisposition()->setDisposition(KMime::Headers::CDattachment);
297 result->contentDisposition()->setFilename(QStringLiteral("smime.p7m"));
298
299 result->assemble();
300 //qCDebug(MESSAGECOMPOSER_LOG) << "processed header:" << result->head();
301
302 result->setBody(encodedBody);
303 }
304 // } else { // sign/enc PGPInline
305 // result->setHead(orig->head());
306 // result->parse();
307 40
308 // // fixing ContentTransferEncoding 41#include <QDebug>
309 // setBodyAndCTE(encodedBody, orig->contentType(), result);
310 // }
311 result->assemble();
312 return result;
313}
314 42
315// bool binaryHint(Kleo::CryptoMessageFormat f) 43#include <future>
316// { 44#include <utility>
317// switch (f) {
318// case Kleo::SMIMEFormat:
319// case Kleo::SMIMEOpaqueFormat:
320// return true;
321// default:
322// case Kleo::OpenPGPMIMEFormat:
323// case Kleo::InlineOpenPGPFormat:
324// return false;
325// }
326// }
327//
328 // GpgME::SignatureMode signingMode(Kleo::CryptoMessageFormat f)
329 // {
330 // switch (f) {
331 // case Kleo::SMIMEOpaqueFormat:
332 // return GpgME::NormalSignatureMode;
333 // case Kleo::InlineOpenPGPFormat:
334 // return GpgME::Clearsigned;
335 // default:
336 // case Kleo::SMIMEFormat:
337 // case Kleo::OpenPGPMIMEFormat:
338 // return GpgME::Detached;
339 // }
340 // }
341 45
342// replace simple LFs by CRLFs for all MIME supporting CryptPlugs 46// replace simple LFs by CRLFs for all MIME supporting CryptPlugs
343// according to RfC 2633, 3.1.1 Canonicalization 47// according to RfC 2633, 3.1.1 Canonicalization
@@ -404,63 +108,319 @@ static QByteArray canonicalizeContent(KMime::Content *content)
404 108
405} 109}
406 110
407KMime::Content *MailCrypto::processCrypto(KMime::Content *content, const std::vector<GpgME::Key> &signingKeys, const std::vector<GpgME::Key> &encryptionKeys, MailCrypto::Protocol protocol) 111/**
112 * Get the given `key` in the armor format.
113 */
114Expected<GpgME::Error, QByteArray> exportPublicKey(const GpgME::Key &key)
408{ 115{
409 const QGpgME::Protocol *const proto = protocol == MailCrypto::SMIME ? QGpgME::smime() : QGpgME::openpgp(); 116 // Not using the Qt API because it apparently blocks (the `result` signal is never
410 Q_ASSERT(proto); 117 // triggered)
411 118 std::unique_ptr<GpgME::Context> ctx(GpgME::Context::createForProtocol(GpgME::OpenPGP));
412 auto signingMode = GpgME::Detached; 119 ctx->setArmor(true);
413 bool armor = true; 120
414 bool textMode = false; 121 QGpgME::QByteArrayDataProvider dp;
415 const bool sign = !signingKeys.empty(); 122 GpgME::Data data(&dp);
416 const bool encrypt = !encryptionKeys.empty(); 123
417 124 qDebug() << "Exporting public key:" << key.shortKeyID();
418 QByteArray resultContent; 125 auto error = ctx->exportPublicKeys(key.keyID(), data);
419 QByteArray hashAlgo; 126
420 //Trust provided keys and don't check them for validity 127 if (error.code()) {
421 bool alwaysTrust = true; 128 return makeUnexpected(error);
422 if (sign && encrypt) {
423 std::unique_ptr<QGpgME::SignEncryptJob> job(proto->signEncryptJob(armor, textMode));
424 const auto res = job->exec(signingKeys, encryptionKeys, canonicalizeContent(content), alwaysTrust, resultContent);
425 if (res.first.error().code()) {
426 qWarning() << "Signing failed:" << res.first.error().asString();
427 return nullptr;
428 } else {
429 hashAlgo = res.first.createdSignature(0).hashAlgorithmAsString();
430 }
431 if (res.second.error().code()) {
432 qWarning() << "Encryption failed:" << res.second.error().asString();
433 return nullptr;
434 }
435 } else if (sign) {
436 std::unique_ptr<QGpgME::SignJob> job(proto->signJob(armor, textMode));
437 auto result = job->exec(signingKeys, canonicalizeContent(content), signingMode, resultContent);
438 if (result.error().code()) {
439 qWarning() << "Signing failed:" << result.error().asString();
440 return nullptr;
441 }
442 hashAlgo = result.createdSignature(0).hashAlgorithmAsString();
443 } else if (encrypt) {
444 std::unique_ptr<QGpgME::EncryptJob> job(proto->encryptJob(armor, textMode));
445 const auto result = job->exec(encryptionKeys, canonicalizeContent(content), alwaysTrust, resultContent);
446 if (result.error().code()) {
447 qWarning() << "Encryption failed:" << result.error().asString();
448 return nullptr;
449 }
450 hashAlgo = "pgp-sha1";
451 } else {
452 qWarning() << "Not signing or encrypting";
453 return nullptr;
454 } 129 }
455 130
456 return composeHeadersAndBody(content, resultContent, sign, hashAlgo); 131 return dp.data();
457} 132}
458 133
459KMime::Content *MailCrypto::sign(KMime::Content *content, const std::vector<GpgME::Key> &signers) 134/**
135 * Create an Email with `msg` as a body and `key` as an attachment.
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 *
143 * Used by the `createSignedEmail` and `createEncryptedEmail` functions.
144 */
145Expected<GpgME::Error, std::unique_ptr<KMime::Content>>
146appendPublicKey(std::unique_ptr<KMime::Content> msg, const GpgME::Key &key)
460{ 147{
461 return processCrypto(content, signers, {}, OPENPGP); 148 const auto publicKeyExportResult = exportPublicKey(key);
149
150 if (!publicKeyExportResult) {
151 // "Could not export public key"
152 return makeUnexpected(publicKeyExportResult.error());
153 }
154
155 const auto publicKeyData = publicKeyExportResult.value();
156
157 auto result = std::unique_ptr<KMime::Content>(new KMime::Content);
158 result->contentType()->setMimeType("multipart/mixed");
159 result->contentType()->setBoundary(KMime::multiPartBoundary());
160
161 KMime::Content *keyAttachment = new KMime::Content;
162 {
163 keyAttachment->contentType()->setMimeType("application/pgp-keys");
164 keyAttachment->contentDisposition()->setDisposition(KMime::Headers::CDattachment);
165 keyAttachment->contentDisposition()->setFilename(QString("0x") + key.shortKeyID() + ".asc");
166 keyAttachment->setBody(publicKeyData);
167 }
168
169 msg->assemble();
170
171 result->addContent(msg.release());
172 result->addContent(keyAttachment);
173
174 result->assemble();
175
176 return result;
462} 177}
463 178
179Expected<GpgME::Error, QByteArray> encrypt(const QByteArray &content, const std::vector<GpgME::Key> &encryptionKeys)
180{
181 QByteArray resultData;
182
183 const QGpgME::Protocol *const proto = QGpgME::openpgp();
184 std::unique_ptr<QGpgME::EncryptJob> job(proto->encryptJob(/* armor = */ true));
185 const auto result = job->exec(encryptionKeys, content, /* alwaysTrust = */ true, resultData);
186
187 if (result.error().code()) {
188 qWarning() << "Encryption failed:" << result.error().asString();
189 return makeUnexpected(result.error());
190 }
191
192 return resultData;
193}
194
195Expected<GpgME::Error, QByteArray> signAndEncrypt(const QByteArray &content,
196 const std::vector<GpgME::Key> &signingKeys, const std::vector<GpgME::Key> &encryptionKeys)
197{
198 QByteArray resultData;
199
200 const QGpgME::Protocol *const proto = QGpgME::openpgp();
201 std::unique_ptr<QGpgME::SignEncryptJob> job(proto->signEncryptJob(/* armor = */ true));
202 const auto result = job->exec(signingKeys, encryptionKeys, content, /* alwaysTrust = */ true, resultData);
203
204 if (result.first.error().code()) {
205 qWarning() << "Signing failed:" << result.first.error().asString();
206 return makeUnexpected(result.first.error());
207 }
208
209 if (result.second.error().code()) {
210 qWarning() << "Encryption failed:" << result.second.error().asString();
211 return makeUnexpected(result.second.error());
212 }
213
214 return resultData;
215}
216
217/**
218 * Create a message part like this (according to RFC 3156 Section 4):
219 *
220 * - multipart/encrypted
221 * - application/pgp-encrypted (version information)
222 * - application/octet-stream (given encrypted data)
223 *
224 * Should not be used directly since the public key should be attached, hence
225 * the `createEncryptedEmail` function.
226 *
227 * The encrypted data can be generated by the `encrypt` or `signAndEncrypt` functions.
228 */
229std::unique_ptr<KMime::Content> createEncryptedPart(QByteArray encryptedData)
230{
231 auto result = std::unique_ptr<KMime::Content>(new KMime::Content);
232
233 result->contentType()->setMimeType("multipart/encrypted");
234 result->contentType()->setBoundary(KMime::multiPartBoundary());
235 result->contentType()->setParameter("protocol", "application/pgp-encrypted");
236
237 KMime::Content *controlInformation = new KMime::Content;
238 {
239 controlInformation->contentType()->setMimeType("application/pgp-encrypted");
240 controlInformation->contentDescription()->from7BitString("PGP/MIME version identification");
241 controlInformation->setBody("Version: 1");
242
243 result->addContent(controlInformation);
244 }
245
246 KMime::Content *encryptedPartPart = new KMime::Content;
247 {
248 const QString filename = "msg.asc";
249
250 encryptedPartPart->contentType()->setMimeType("application/octet-stream");
251 encryptedPartPart->contentType()->setName(filename, "utf-8");
252
253 encryptedPartPart->contentDescription()->from7BitString("OpenPGP encrypted message");
254
255 encryptedPartPart->contentDisposition()->setDisposition(KMime::Headers::CDinline);
256 encryptedPartPart->contentDisposition()->setFilename(filename);
257
258 encryptedPartPart->setBody(encryptedData);
259
260 result->addContent(encryptedPartPart);
261 }
262
263 return result;
264}
265
266/**
267 * Create an encrypted (optionally signed) email with a public key attached to it.
268 *
269 * Will create a message like this:
270 *
271 * + `multipart/mixed`
272 * - `multipart/encrypted`
273 * + `application/pgp-encrypted
274 * + `application/octet-stream` (a generated encrypted version of the original message)
275 * - `application/pgp-keys` (the public key as attachment, which is the first of the
276 * `signingKeys`)
277 */
278Expected<GpgME::Error, std::unique_ptr<KMime::Content>>
279createEncryptedEmail(KMime::Content *content, const std::vector<GpgME::Key> &encryptionKeys,
280 const GpgME::Key &attachedKey, const std::vector<GpgME::Key> &signingKeys = {})
281{
282 auto contentToEncrypt = canonicalizeContent(content);
283
284 auto encryptionResult = signingKeys.empty() ?
285 encrypt(contentToEncrypt, encryptionKeys) :
286 signAndEncrypt(contentToEncrypt, signingKeys, encryptionKeys);
287
288 if (!encryptionResult) {
289 return makeUnexpected(encryptionResult.error());
290 }
291
292 auto encryptedPart = createEncryptedPart(encryptionResult.value());
293
294 auto publicKeyAppendResult = appendPublicKey(std::move(encryptedPart), attachedKey);
295
296 if(publicKeyAppendResult) {
297 publicKeyAppendResult.value()->assemble();
298 }
299
300 return publicKeyAppendResult;
301}
302
303/**
304 * Sign the given content and returns the signing data and the algorithm used
305 * for integrity check in the "pgp-<algorithm>" format.
306 */
307Expected<GpgME::Error, std::pair<QByteArray, QString>>
308sign(const QByteArray &content, const std::vector<GpgME::Key> &signingKeys)
309{
310 QByteArray resultData;
311
312 const QGpgME::Protocol *const proto = QGpgME::openpgp();
313 std::unique_ptr<QGpgME::SignJob> job(proto->signJob(/* armor = */ true));
314 const auto result = job->exec(signingKeys, content, GpgME::Detached, resultData);
315
316 if (result.error().code()) {
317 qWarning() << "Signing failed:" << result.error().asString();
318 return makeUnexpected(result.error());
319 }
320
321 auto algo = result.createdSignature(0).hashAlgorithmAsString();
322 // RFC 3156 Section 5:
323 // Hash-symbols are constructed [...] by converting the text name to lower
324 // case and prefixing it with the four characters "pgp-".
325 auto micAlg = (QString("pgp-") + algo).toLower();
326
327 return std::pair<QByteArray, QString>{resultData, micAlg};
328}
329
330/**
331 * Create a message part like this (according to RFC 3156 Section 5):
332 *
333 * + `multipart/signed`
334 * - whatever the given original `message` is (should be canonicalized)
335 * - `application/octet-stream` (the given `signature`)
336 *
337 * Should not be used directly since the public key should be attached, hence
338 * the `createSignedEmail` function.
339 *
340 * The signature can be generated by the `sign` function.
341 */
342std::unique_ptr<KMime::Content> createSignedPart(
343 std::unique_ptr<KMime::Content> message, const QByteArray &signature, const QString &micAlg)
344{
345 auto result = std::unique_ptr<KMime::Content>(new KMime::Content);
346
347 result->contentType()->setMimeType("multipart/signed");
348 result->contentType()->setBoundary(KMime::multiPartBoundary());
349 result->contentType()->setParameter("micalg", micAlg);
350 result->contentType()->setParameter("protocol", "application/pgp-signature");
351
352 result->addContent(message.release());
353
354 KMime::Content *signedPartPart = new KMime::Content;
355 {
356 signedPartPart->contentType()->setMimeType("application/pgp-signature");
357 signedPartPart->contentType()->setName("signature.asc", "utf-8");
358
359 signedPartPart->contentDescription()->from7BitString(
360 "This is a digitally signed message part");
361
362 signedPartPart->setBody(signature);
363
364 result->addContent(signedPartPart);
365 }
366
367 return result;
368}
369
370/**
371 * Create a signed email with a public key attached to it.
372 *
373 * Will create a message like this:
374 *
375 * + `multipart/mixed`
376 * - `multipart/signed`
377 * + whatever the given original `content` is (should not be canonalized)
378 * + `application/octet-stream` (a generated signature of the original message)
379 * - `application/pgp-keys` (the public key as attachment, which is the first of the
380 * `signingKeys`)
381 */
382Expected<GpgME::Error, std::unique_ptr<KMime::Content>>
383createSignedEmail(std::unique_ptr<KMime::Content> content,
384 const std::vector<GpgME::Key> &signingKeys, const GpgME::Key &attachedKey)
385{
386 Q_ASSERT(!signingKeys.empty());
387
388 auto contentToSign = canonicalizeContent(content.get());
389
390 auto signingResult = sign(contentToSign, signingKeys);
391
392 if (!signingResult) {
393 return makeUnexpected(signingResult.error());
394 }
395
396 QByteArray signingData;
397 QString micAlg;
398 std::tie(signingData, micAlg) = signingResult.value();
399
400 auto signedPart = createSignedPart(std::move(content), signingData, micAlg);
401
402 auto publicKeyAppendResult = appendPublicKey(std::move(signedPart), attachedKey);
403
404 if (publicKeyAppendResult) {
405 publicKeyAppendResult.value()->assemble();
406 }
407
408 return publicKeyAppendResult;
409}
410
411Expected<GpgME::Error, std::unique_ptr<KMime::Content>>
412MailCrypto::processCrypto(std::unique_ptr<KMime::Content> content, const std::vector<GpgME::Key> &signingKeys,
413 const std::vector<GpgME::Key> &encryptionKeys, const GpgME::Key &attachedKey)
414{
415 if (!encryptionKeys.empty()) {
416 return createEncryptedEmail(content.release(), encryptionKeys, attachedKey, signingKeys);
417 } else if (!signingKeys.empty()) {
418 return createSignedEmail(std::move(content), signingKeys, signingKeys[0]);
419 } else {
420 qWarning() << "Processing cryptography, but neither signing nor encrypting";
421 return content;
422 }
423}
464 424
465void MailCrypto::importKeys(const std::vector<GpgME::Key> &keys) 425void MailCrypto::importKeys(const std::vector<GpgME::Key> &keys)
466{ 426{
@@ -470,7 +430,7 @@ void MailCrypto::importKeys(const std::vector<GpgME::Key> &keys)
470 job->exec(keys); 430 job->exec(keys);
471} 431}
472 432
473static GpgME::KeyListResult listKeys(GpgME::Protocol protocol, const QStringList &patterns, bool secretOnly, int keyListMode, std::vector<GpgME::Key> &keys) 433static GpgME::KeyListResult listKeys(const QStringList &patterns, bool secretOnly, int keyListMode, std::vector<GpgME::Key> &keys)
474{ 434{
475 QByteArrayList list; 435 QByteArrayList list;
476 std::transform(patterns.constBegin(), patterns.constEnd(), std::back_inserter(list), [] (const QString &s) { return s.toUtf8(); }); 436 std::transform(patterns.constBegin(), patterns.constEnd(), std::back_inserter(list), [] (const QString &s) { return s.toUtf8(); });
@@ -479,7 +439,7 @@ static GpgME::KeyListResult listKeys(GpgME::Protocol protocol, const QStringList
479 pattern.push_back(0); 439 pattern.push_back(0);
480 440
481 GpgME::initializeLibrary(); 441 GpgME::initializeLibrary();
482 auto ctx = QSharedPointer<GpgME::Context>{GpgME::Context::createForProtocol(protocol)}; 442 auto ctx = QSharedPointer<GpgME::Context>{GpgME::Context::createForProtocol(GpgME::OpenPGP)};
483 ctx->setKeyListMode(keyListMode); 443 ctx->setKeyListMode(keyListMode);
484 if (const GpgME::Error err = ctx->startKeyListing(pattern.data(), secretOnly)) { 444 if (const GpgME::Error err = ctx->startKeyListing(pattern.data(), secretOnly)) {
485 return GpgME::KeyListResult(0, err); 445 return GpgME::KeyListResult(0, err);
@@ -497,10 +457,10 @@ static GpgME::KeyListResult listKeys(GpgME::Protocol protocol, const QStringList
497 return result; 457 return result;
498} 458}
499 459
500std::vector<GpgME::Key> MailCrypto::findKeys(const QStringList &filter, bool findPrivate, bool remote, Protocol protocol) 460std::vector<GpgME::Key> MailCrypto::findKeys(const QStringList &filter, bool findPrivate, bool remote)
501{ 461{
502 std::vector<GpgME::Key> keys; 462 std::vector<GpgME::Key> keys;
503 GpgME::KeyListResult res = listKeys(protocol == SMIME ? GpgME::CMS : GpgME::OpenPGP, filter, findPrivate, remote ? GpgME::Extern : GpgME::Local, keys); 463 GpgME::KeyListResult res = listKeys(filter, findPrivate, remote ? GpgME::Extern : GpgME::Local, keys);
504 if (res.error()) { 464 if (res.error()) {
505 qWarning() << "Failed to lookup keys: " << res.error().asString(); 465 qWarning() << "Failed to lookup keys: " << res.error().asString();
506 return keys; 466 return keys;
@@ -517,4 +477,3 @@ std::vector<GpgME::Key> MailCrypto::findKeys(const QStringList &filter, bool fin
517 477
518 return keys; 478 return keys;
519} 479}
520