diff options
author | Christian Mollekopf <chrigi_1@fastmail.fm> | 2018-05-05 10:39:32 +0200 |
---|---|---|
committer | Christian Mollekopf <chrigi_1@fastmail.fm> | 2018-05-06 17:21:01 +0200 |
commit | 01594e68275a09c67b5ee258e2af86598118a6a0 (patch) | |
tree | 4f859815f6455906bb656f9cc27ba5d6e4111599 /framework/src/domain/mime/mailcrypto.cpp | |
parent | 481cb9f600caf3f45596bf78b5ba2bd07007969c (diff) | |
download | kube-01594e68275a09c67b5ee258e2af86598118a6a0.tar.gz kube-01594e68275a09c67b5ee258e2af86598118a6a0.zip |
Port to gpgme only.
QGpgme and Gpgmepp are not readily available, the cmake files buggy, the
buildsystem horrendous and generally just difficult to build on windows.
Given that all they are is a wrapper around gpgme, we're better of
without all the indirections.
What we loose is:
* QGpgme moved the work to separate threads (but we then blocked
anyways), something that we can just do in our own code should we want to.
* QGpgme has a function to prettify dn's that was used to show the
signer. Also something we could bring back should we need to (don't know
where it is useful atm.)
Ported messagepart to gpgme
Almost there
Moved the crypto bits to a separate file
All gpg code is in one place.
All tests passing
Use error codes
Cleanup
Diffstat (limited to 'framework/src/domain/mime/mailcrypto.cpp')
-rw-r--r-- | framework/src/domain/mime/mailcrypto.cpp | 192 |
1 files changed, 4 insertions, 188 deletions
diff --git a/framework/src/domain/mime/mailcrypto.cpp b/framework/src/domain/mime/mailcrypto.cpp index a7f3772a..e429a212 100644 --- a/framework/src/domain/mime/mailcrypto.cpp +++ b/framework/src/domain/mime/mailcrypto.cpp | |||
@@ -22,22 +22,7 @@ | |||
22 | #include "mailcrypto.h" | 22 | #include "mailcrypto.h" |
23 | 23 | ||
24 | #include "framework/src/errors.h" | 24 | #include "framework/src/errors.h" |
25 | 25 | #include "crypto.h" | |
26 | #include <QGpgME/DataProvider> | ||
27 | #include <QGpgME/EncryptJob> | ||
28 | #include <QGpgME/ExportJob> | ||
29 | #include <QGpgME/ImportFromKeyserverJob> | ||
30 | #include <QGpgME/ImportJob> | ||
31 | #include <QGpgME/Protocol> | ||
32 | #include <QGpgME/SignEncryptJob> | ||
33 | #include <QGpgME/SignJob> | ||
34 | |||
35 | #include <gpgme++/data.h> | ||
36 | #include <gpgme++/encryptionresult.h> | ||
37 | #include <gpgme++/global.h> | ||
38 | #include <gpgme++/importresult.h> | ||
39 | #include <gpgme++/keylistresult.h> | ||
40 | #include <gpgme++/signingresult.h> | ||
41 | 26 | ||
42 | #include <QDebug> | 27 | #include <QDebug> |
43 | 28 | ||
@@ -45,22 +30,8 @@ | |||
45 | #include <utility> | 30 | #include <utility> |
46 | 31 | ||
47 | using namespace MailCrypto; | 32 | using namespace MailCrypto; |
33 | using namespace Crypto; | ||
48 | 34 | ||
49 | QDebug operator<< (QDebug d, const MailCrypto::Key &key) | ||
50 | { | ||
51 | d << key.key.primaryFingerprint(); | ||
52 | return d; | ||
53 | } | ||
54 | |||
55 | static std::vector<GpgME::Key> toGpgME(const std::vector<Key> keys) | ||
56 | { | ||
57 | std::vector<GpgME::Key> list; | ||
58 | std::transform(keys.begin(), keys.end(), std::back_inserter(list), [] (const Key &key) { return key.key; }); | ||
59 | return list; | ||
60 | } | ||
61 | |||
62 | // replace simple LFs by CRLFs for all MIME supporting CryptPlugs | ||
63 | // according to RfC 2633, 3.1.1 Canonicalization | ||
64 | static QByteArray canonicalizeContent(KMime::Content *content) | 35 | static QByteArray canonicalizeContent(KMime::Content *content) |
65 | { | 36 | { |
66 | // if (d->format & Kleo::InlineOpenPGPFormat) { | 37 | // if (d->format & Kleo::InlineOpenPGPFormat) { |
@@ -125,29 +96,6 @@ static QByteArray canonicalizeContent(KMime::Content *content) | |||
125 | } | 96 | } |
126 | 97 | ||
127 | /** | 98 | /** |
128 | * Get the given `key` in the armor format. | ||
129 | */ | ||
130 | Expected<Error, QByteArray> exportPublicKey(const Key &key) | ||
131 | { | ||
132 | // Not using the Qt API because it apparently blocks (the `result` signal is never | ||
133 | // triggered) | ||
134 | std::unique_ptr<GpgME::Context> ctx(GpgME::Context::createForProtocol(GpgME::OpenPGP)); | ||
135 | ctx->setArmor(true); | ||
136 | |||
137 | QGpgME::QByteArrayDataProvider dp; | ||
138 | GpgME::Data data(&dp); | ||
139 | |||
140 | qDebug() << "Exporting public key:" << key.key.shortKeyID(); | ||
141 | auto error = ctx->exportPublicKeys(key.key.keyID(), data); | ||
142 | |||
143 | if (error.code()) { | ||
144 | return makeUnexpected(Error{error}); | ||
145 | } | ||
146 | |||
147 | return dp.data(); | ||
148 | } | ||
149 | |||
150 | /** | ||
151 | * Create an Email with `msg` as a body and `key` as an attachment. | 99 | * Create an Email with `msg` as a body and `key` as an attachment. |
152 | * | 100 | * |
153 | * Will create the given structure: | 101 | * Will create the given structure: |
@@ -178,7 +126,7 @@ appendPublicKey(std::unique_ptr<KMime::Content> msg, const Key &key) | |||
178 | { | 126 | { |
179 | keyAttachment->contentType()->setMimeType("application/pgp-keys"); | 127 | keyAttachment->contentType()->setMimeType("application/pgp-keys"); |
180 | keyAttachment->contentDisposition()->setDisposition(KMime::Headers::CDattachment); | 128 | keyAttachment->contentDisposition()->setDisposition(KMime::Headers::CDattachment); |
181 | keyAttachment->contentDisposition()->setFilename(QString("0x") + key.key.shortKeyID() + ".asc"); | 129 | keyAttachment->contentDisposition()->setFilename(QString("0x") + key.shortKeyId + ".asc"); |
182 | keyAttachment->setBody(publicKeyData); | 130 | keyAttachment->setBody(publicKeyData); |
183 | } | 131 | } |
184 | 132 | ||
@@ -192,44 +140,6 @@ appendPublicKey(std::unique_ptr<KMime::Content> msg, const Key &key) | |||
192 | return result; | 140 | return result; |
193 | } | 141 | } |
194 | 142 | ||
195 | Expected<Error, QByteArray> encrypt(const QByteArray &content, const std::vector<Key> &encryptionKeys) | ||
196 | { | ||
197 | QByteArray resultData; | ||
198 | |||
199 | const QGpgME::Protocol *const proto = QGpgME::openpgp(); | ||
200 | std::unique_ptr<QGpgME::EncryptJob> job(proto->encryptJob(/* armor = */ true)); | ||
201 | const auto result = job->exec(toGpgME(encryptionKeys), content, /* alwaysTrust = */ true, resultData); | ||
202 | |||
203 | if (result.error().code()) { | ||
204 | qWarning() << "Encryption failed:" << result.error().asString(); | ||
205 | return makeUnexpected(Error{result.error()}); | ||
206 | } | ||
207 | |||
208 | return resultData; | ||
209 | } | ||
210 | |||
211 | Expected<Error, QByteArray> signAndEncrypt(const QByteArray &content, | ||
212 | const std::vector<Key> &signingKeys, const std::vector<Key> &encryptionKeys) | ||
213 | { | ||
214 | QByteArray resultData; | ||
215 | |||
216 | const QGpgME::Protocol *const proto = QGpgME::openpgp(); | ||
217 | std::unique_ptr<QGpgME::SignEncryptJob> job(proto->signEncryptJob(/* armor = */ true)); | ||
218 | const auto result = job->exec(toGpgME(signingKeys), toGpgME(encryptionKeys), content, /* alwaysTrust = */ true, resultData); | ||
219 | |||
220 | if (result.first.error().code()) { | ||
221 | qWarning() << "Signing failed:" << result.first.error().asString(); | ||
222 | return makeUnexpected(Error{result.first.error()}); | ||
223 | } | ||
224 | |||
225 | if (result.second.error().code()) { | ||
226 | qWarning() << "Encryption failed:" << result.second.error().asString(); | ||
227 | return makeUnexpected(Error{result.second.error()}); | ||
228 | } | ||
229 | |||
230 | return resultData; | ||
231 | } | ||
232 | |||
233 | /** | 143 | /** |
234 | * Create a message part like this (according to RFC 3156 Section 4): | 144 | * Create a message part like this (according to RFC 3156 Section 4): |
235 | * | 145 | * |
@@ -295,11 +205,7 @@ Expected<Error, std::unique_ptr<KMime::Content>> | |||
295 | createEncryptedEmail(KMime::Content *content, const std::vector<Key> &encryptionKeys, | 205 | createEncryptedEmail(KMime::Content *content, const std::vector<Key> &encryptionKeys, |
296 | const Key &attachedKey, const std::vector<Key> &signingKeys = {}) | 206 | const Key &attachedKey, const std::vector<Key> &signingKeys = {}) |
297 | { | 207 | { |
298 | auto contentToEncrypt = canonicalizeContent(content); | 208 | auto encryptionResult = signAndEncrypt(canonicalizeContent(content), encryptionKeys, signingKeys); |
299 | |||
300 | auto encryptionResult = signingKeys.empty() ? | ||
301 | encrypt(contentToEncrypt, encryptionKeys) : | ||
302 | signAndEncrypt(contentToEncrypt, signingKeys, encryptionKeys); | ||
303 | 209 | ||
304 | if (!encryptionResult) { | 210 | if (!encryptionResult) { |
305 | return makeUnexpected(Error{encryptionResult.error()}); | 211 | return makeUnexpected(Error{encryptionResult.error()}); |
@@ -317,33 +223,6 @@ createEncryptedEmail(KMime::Content *content, const std::vector<Key> &encryption | |||
317 | } | 223 | } |
318 | 224 | ||
319 | /** | 225 | /** |
320 | * Sign the given content and returns the signing data and the algorithm used | ||
321 | * for integrity check in the "pgp-<algorithm>" format. | ||
322 | */ | ||
323 | Expected<Error, std::pair<QByteArray, QString>> | ||
324 | sign(const QByteArray &content, const std::vector<Key> &signingKeys) | ||
325 | { | ||
326 | QByteArray resultData; | ||
327 | |||
328 | const QGpgME::Protocol *const proto = QGpgME::openpgp(); | ||
329 | std::unique_ptr<QGpgME::SignJob> job(proto->signJob(/* armor = */ true)); | ||
330 | const auto result = job->exec(toGpgME(signingKeys), content, GpgME::Detached, resultData); | ||
331 | |||
332 | if (result.error().code()) { | ||
333 | qWarning() << "Signing failed:" << result.error().asString(); | ||
334 | return makeUnexpected(Error{result.error()}); | ||
335 | } | ||
336 | |||
337 | auto algo = result.createdSignature(0).hashAlgorithmAsString(); | ||
338 | // RFC 3156 Section 5: | ||
339 | // Hash-symbols are constructed [...] by converting the text name to lower | ||
340 | // case and prefixing it with the four characters "pgp-". | ||
341 | auto micAlg = (QString("pgp-") + algo).toLower(); | ||
342 | |||
343 | return std::pair<QByteArray, QString>{resultData, micAlg}; | ||
344 | } | ||
345 | |||
346 | /** | ||
347 | * Create a message part like this (according to RFC 3156 Section 5): | 226 | * Create a message part like this (according to RFC 3156 Section 5): |
348 | * | 227 | * |
349 | * + `multipart/signed` | 228 | * + `multipart/signed` |
@@ -438,66 +317,3 @@ MailCrypto::processCrypto(std::unique_ptr<KMime::Content> content, const std::ve | |||
438 | } | 317 | } |
439 | } | 318 | } |
440 | 319 | ||
441 | void MailCrypto::importKeys(const std::vector<Key> &keys) | ||
442 | { | ||
443 | const QGpgME::Protocol *const backend = QGpgME::openpgp(); | ||
444 | Q_ASSERT(backend); | ||
445 | auto *job = backend->importFromKeyserverJob(); | ||
446 | job->exec(toGpgME(keys)); | ||
447 | } | ||
448 | |||
449 | MailCrypto::ImportResult MailCrypto::importKey(const QByteArray &pkey) | ||
450 | { | ||
451 | const auto *proto = QGpgME::openpgp(); | ||
452 | std::unique_ptr<QGpgME::ImportJob> job(proto->importJob()); | ||
453 | auto result = job->exec(pkey); | ||
454 | return {result.numConsidered(), result.numImported(), result.numUnchanged()}; | ||
455 | } | ||
456 | |||
457 | static GpgME::KeyListResult listKeys(const QStringList &patterns, bool secretOnly, int keyListMode, std::vector<Key> &keys) | ||
458 | { | ||
459 | QByteArrayList list; | ||
460 | std::transform(patterns.constBegin(), patterns.constEnd(), std::back_inserter(list), [] (const QString &s) { return s.toUtf8(); }); | ||
461 | std::vector<char const *> pattern; | ||
462 | std::transform(list.constBegin(), list.constEnd(), std::back_inserter(pattern), [] (const QByteArray &s) { return s.constData(); }); | ||
463 | pattern.push_back(0); | ||
464 | |||
465 | GpgME::initializeLibrary(); | ||
466 | auto ctx = QSharedPointer<GpgME::Context>{GpgME::Context::createForProtocol(GpgME::OpenPGP)}; | ||
467 | ctx->setKeyListMode(keyListMode); | ||
468 | if (const GpgME::Error err = ctx->startKeyListing(pattern.data(), secretOnly)) { | ||
469 | return GpgME::KeyListResult(0, err); | ||
470 | } | ||
471 | |||
472 | GpgME::Error err; | ||
473 | do { | ||
474 | keys.push_back( Key{ctx->nextKey(err)}); | ||
475 | } while ( !err ); | ||
476 | |||
477 | keys.pop_back(); | ||
478 | |||
479 | const GpgME::KeyListResult result = ctx->endKeyListing(); | ||
480 | ctx->cancelPendingOperation(); | ||
481 | return result; | ||
482 | } | ||
483 | |||
484 | std::vector<Key> MailCrypto::findKeys(const QStringList &filter, bool findPrivate, bool remote) | ||
485 | { | ||
486 | std::vector<Key> keys; | ||
487 | GpgME::KeyListResult res = listKeys(filter, findPrivate, remote ? GpgME::Extern : GpgME::Local, keys); | ||
488 | if (res.error()) { | ||
489 | qWarning() << "Failed to lookup keys: " << res.error().asString(); | ||
490 | return {}; | ||
491 | } | ||
492 | qWarning() << "got keys:" << keys.size(); | ||
493 | |||
494 | for (auto i = keys.begin(); i != keys.end(); ++i) { | ||
495 | qWarning() << "key isnull:" << i->key.isNull() << "isexpired:" << i->key.isExpired(); | ||
496 | qWarning() << "key numuserIds:" << i->key.numUserIDs(); | ||
497 | for (uint k = 0; k < i->key.numUserIDs(); ++k) { | ||
498 | qWarning() << "userIDs:" << i->key.userID(k).email(); | ||
499 | } | ||
500 | } | ||
501 | |||
502 | return keys; | ||
503 | } | ||