diff options
author | Minijackson <minijackson@riseup.net> | 2018-03-06 16:29:37 +0100 |
---|---|---|
committer | Minijackson <minijackson@riseup.net> | 2018-03-08 16:29:10 +0100 |
commit | f4c8da01dbd769c6203dd63389f0bfda91e0163f (patch) | |
tree | bc3b7a96f85fc896de6f9deeb0dca3a02c955759 /framework | |
parent | 7a1be6819dd46a3e3d4be8383ad5fd28895603cd (diff) | |
download | kube-f4c8da01dbd769c6203dd63389f0bfda91e0163f.tar.gz kube-f4c8da01dbd769c6203dd63389f0bfda91e0163f.zip |
PoC mail crypto (needs error checking and removing old code)
Diffstat (limited to 'framework')
-rw-r--r-- | framework/src/domain/mime/mailcrypto.cpp | 381 |
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 | ||
159 | static 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 | */ | ||
227 | KMime::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 | |||
275 | QByteArray 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 | |||
293 | QByteArray 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) | ||
324 | KMime::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 | |||
361 | KMime::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 | |||
374 | QByteArray 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) | ||
397 | KMime::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 | |||
424 | KMime::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 | |||
148 | void makeToplevelContentType(KMime::Content *content, bool encrypt, const QByteArray &hashAlgo) | 435 | void 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 | 631 | KMime::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 | ||
343 | static 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 | ||
406 | KMime::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; |