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/crypto.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/crypto.cpp')
-rw-r--r-- | framework/src/domain/mime/crypto.cpp | 442 |
1 files changed, 442 insertions, 0 deletions
diff --git a/framework/src/domain/mime/crypto.cpp b/framework/src/domain/mime/crypto.cpp new file mode 100644 index 00000000..9722dba8 --- /dev/null +++ b/framework/src/domain/mime/crypto.cpp | |||
@@ -0,0 +1,442 @@ | |||
1 | /* | ||
2 | Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com> | ||
3 | Copyright (C) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com | ||
4 | Copyright (c) 2010 Leo Franchi <lfranchi@kde.org> | ||
5 | Copyright (c) 2017 Christian Mollekopf <mollekopf@kolabsys.com> | ||
6 | |||
7 | This library is free software; you can redistribute it and/or modify it | ||
8 | under the terms of the GNU Library General Public License as published by | ||
9 | the Free Software Foundation; either version 2 of the License, or (at your | ||
10 | option) any later version. | ||
11 | |||
12 | This library is distributed in the hope that it will be useful, but WITHOUT | ||
13 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
14 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public | ||
15 | License for more details. | ||
16 | |||
17 | You should have received a copy of the GNU Library General Public License | ||
18 | along with this library; see the file COPYING.LIB. If not, write to the | ||
19 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA | ||
20 | 02110-1301, USA. | ||
21 | */ | ||
22 | #include "crypto.h" | ||
23 | |||
24 | #include "framework/src/errors.h" | ||
25 | |||
26 | #include <gpgme.h> | ||
27 | |||
28 | #include <QDebug> | ||
29 | #include <QDateTime> | ||
30 | |||
31 | #include <future> | ||
32 | #include <utility> | ||
33 | |||
34 | using namespace Crypto; | ||
35 | |||
36 | QDebug operator<< (QDebug d, const Key &key) | ||
37 | { | ||
38 | d << key.fingerprint; | ||
39 | return d; | ||
40 | } | ||
41 | |||
42 | QDebug operator<< (QDebug d, const Error &error) | ||
43 | { | ||
44 | d << error.errorCode(); | ||
45 | return d; | ||
46 | } | ||
47 | |||
48 | struct Data { | ||
49 | Data(const QByteArray &buffer) | ||
50 | { | ||
51 | const bool copy = false; | ||
52 | const gpgme_error_t e = gpgme_data_new_from_mem(&data, buffer.constData(), buffer.size(), int(copy)); | ||
53 | if (e) { | ||
54 | qWarning() << "Failed to copy data?" << e; | ||
55 | } | ||
56 | } | ||
57 | |||
58 | ~Data() | ||
59 | { | ||
60 | gpgme_data_release(data); | ||
61 | } | ||
62 | gpgme_data_t data; | ||
63 | }; | ||
64 | |||
65 | static gpgme_error_t checkEngine(CryptoProtocol protocol) | ||
66 | { | ||
67 | gpgme_check_version(0); | ||
68 | const gpgme_protocol_t p = protocol == CMS ? GPGME_PROTOCOL_CMS : GPGME_PROTOCOL_OpenPGP; | ||
69 | return gpgme_engine_check_version(p); | ||
70 | } | ||
71 | |||
72 | static std::pair<gpgme_error_t, gpgme_ctx_t> createForProtocol(CryptoProtocol proto) | ||
73 | { | ||
74 | if (auto e = checkEngine(proto)) { | ||
75 | qWarning() << "GPG Engine check failed." << e; | ||
76 | return std::make_pair(e, nullptr); | ||
77 | } | ||
78 | gpgme_ctx_t ctx = 0; | ||
79 | if (auto e = gpgme_new(&ctx)) { | ||
80 | return std::make_pair(e, nullptr); | ||
81 | } | ||
82 | |||
83 | switch (proto) { | ||
84 | case OpenPGP: | ||
85 | if (auto e = gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP)) { | ||
86 | gpgme_release(ctx); | ||
87 | return std::make_pair(e, nullptr); | ||
88 | } | ||
89 | break; | ||
90 | case CMS: | ||
91 | if (auto e = gpgme_set_protocol(ctx, GPGME_PROTOCOL_CMS)) { | ||
92 | gpgme_release(ctx); | ||
93 | return std::make_pair(e, nullptr); | ||
94 | } | ||
95 | break; | ||
96 | default: | ||
97 | Q_ASSERT(false); | ||
98 | return std::make_pair(1, nullptr); | ||
99 | } | ||
100 | return std::make_pair(GPG_ERR_NO_ERROR, ctx); | ||
101 | } | ||
102 | |||
103 | |||
104 | struct Context { | ||
105 | Context(CryptoProtocol protocol = OpenPGP) | ||
106 | { | ||
107 | gpgme_error_t code; | ||
108 | std::tie(code, context) = createForProtocol(protocol); | ||
109 | error = Error{code}; | ||
110 | } | ||
111 | |||
112 | ~Context() | ||
113 | { | ||
114 | gpgme_release(context); | ||
115 | } | ||
116 | |||
117 | operator bool() const | ||
118 | { | ||
119 | return !error; | ||
120 | } | ||
121 | Error error; | ||
122 | gpgme_ctx_t context; | ||
123 | }; | ||
124 | |||
125 | |||
126 | static QByteArray toBA(gpgme_data_t out) | ||
127 | { | ||
128 | size_t length = 0; | ||
129 | auto data = gpgme_data_release_and_get_mem (out, &length); | ||
130 | auto outdata = QByteArray{data, static_cast<int>(length)}; | ||
131 | gpgme_free(data); | ||
132 | return outdata; | ||
133 | } | ||
134 | |||
135 | static std::vector<Recipient> copyRecipients(gpgme_decrypt_result_t result) | ||
136 | { | ||
137 | std::vector<Recipient> recipients; | ||
138 | for (gpgme_recipient_t r = result->recipients ; r ; r = r->next) { | ||
139 | recipients.push_back({QByteArray{r->keyid}, {r->status}}); | ||
140 | } | ||
141 | return recipients; | ||
142 | } | ||
143 | |||
144 | static std::vector<Signature> copySignatures(gpgme_verify_result_t result) | ||
145 | { | ||
146 | std::vector<Signature> signatures; | ||
147 | for (gpgme_signature_t is = result->signatures ; is ; is = is->next) { | ||
148 | Signature sig; | ||
149 | sig.fingerprint = QByteArray{is->fpr}; | ||
150 | sig.creationTime.setTime_t(is->timestamp); | ||
151 | sig.summary = is->summary; | ||
152 | sig.status = {is->status}; | ||
153 | sig.validity = is->validity; | ||
154 | sig.validity_reason = is->validity_reason; | ||
155 | signatures.push_back(sig); | ||
156 | } | ||
157 | return signatures; | ||
158 | } | ||
159 | |||
160 | |||
161 | VerificationResult Crypto::verifyDetachedSignature(CryptoProtocol protocol, const QByteArray &signature, const QByteArray &text) | ||
162 | { | ||
163 | Context context{protocol}; | ||
164 | if (!context) { | ||
165 | qWarning() << "Failed to create context " << context.error; | ||
166 | return {{}, context.error}; | ||
167 | } | ||
168 | auto ctx = context.context; | ||
169 | |||
170 | auto err = gpgme_op_verify(ctx, Data{signature}.data, Data{text}.data, 0); | ||
171 | gpgme_verify_result_t res = gpgme_op_verify_result(ctx); | ||
172 | return {copySignatures(res), {err}}; | ||
173 | } | ||
174 | |||
175 | VerificationResult Crypto::verifyOpaqueSignature(CryptoProtocol protocol, const QByteArray &signature, QByteArray &outdata) | ||
176 | { | ||
177 | Context context{protocol}; | ||
178 | if (!context) { | ||
179 | qWarning() << "Failed to create context " << context.error; | ||
180 | return VerificationResult{{}, context.error}; | ||
181 | } | ||
182 | auto ctx = context.context; | ||
183 | |||
184 | gpgme_data_t out; | ||
185 | const gpgme_error_t e = gpgme_data_new(&out); | ||
186 | Q_ASSERT(!e); | ||
187 | auto err = gpgme_op_verify(ctx, Data{signature}.data, 0, out); | ||
188 | |||
189 | VerificationResult result{{}, {err}}; | ||
190 | if (gpgme_verify_result_t res = gpgme_op_verify_result(ctx)) { | ||
191 | result.signatures = copySignatures(res); | ||
192 | } | ||
193 | |||
194 | outdata = toBA(out); | ||
195 | return result; | ||
196 | } | ||
197 | |||
198 | std::pair<DecryptionResult,VerificationResult> Crypto::decryptAndVerify(CryptoProtocol protocol, const QByteArray &ciphertext, QByteArray &outdata) | ||
199 | { | ||
200 | Context context{protocol}; | ||
201 | if (!context) { | ||
202 | qWarning() << "Failed to create context " << context.error; | ||
203 | return std::make_pair(DecryptionResult{{}, context.error}, VerificationResult{{}, context.error}); | ||
204 | } | ||
205 | auto ctx = context.context; | ||
206 | |||
207 | gpgme_data_t out; | ||
208 | if (gpgme_error_t e = gpgme_data_new(&out)) { | ||
209 | qWarning() << "Failed to allocated data" << e; | ||
210 | } | ||
211 | auto err = gpgme_op_decrypt_verify(ctx, Data{ciphertext}.data, out); | ||
212 | if (err) { | ||
213 | qWarning() << "Failed to decrypt and verify" << Error{err}; | ||
214 | } | ||
215 | |||
216 | VerificationResult verificationResult{{}, {err}}; | ||
217 | if (gpgme_verify_result_t res = gpgme_op_verify_result(ctx)) { | ||
218 | verificationResult.signatures = copySignatures(res); | ||
219 | } | ||
220 | |||
221 | DecryptionResult decryptionResult{{}, {err}}; | ||
222 | if (gpgme_decrypt_result_t res = gpgme_op_decrypt_result(ctx)) { | ||
223 | decryptionResult.recipients = copyRecipients(res); | ||
224 | } | ||
225 | |||
226 | outdata = toBA(out); | ||
227 | return std::make_pair(decryptionResult, verificationResult); | ||
228 | } | ||
229 | |||
230 | ImportResult Crypto::importKeys(CryptoProtocol protocol, const QByteArray &certData) | ||
231 | { | ||
232 | Context context{protocol}; | ||
233 | if (!context) { | ||
234 | qWarning() << "Failed to create context " << context.error; | ||
235 | return {0, 0, 0}; | ||
236 | } | ||
237 | if (auto err = gpgme_op_import(context.context, Data{certData}.data)) { | ||
238 | qWarning() << "Import failed"; | ||
239 | return {0, 0, 0}; | ||
240 | } | ||
241 | if (auto result = gpgme_op_import_result(context.context)) { | ||
242 | return {result->considered, result->imported, result->unchanged}; | ||
243 | } else { | ||
244 | return {0, 0, 0}; | ||
245 | } | ||
246 | } | ||
247 | |||
248 | static KeyListResult listKeys(CryptoProtocol protocol, const std::vector<const char*> &patterns, bool secretOnly, int keyListMode) | ||
249 | { | ||
250 | Context context{protocol}; | ||
251 | if (!context) { | ||
252 | qWarning() << "Failed to create context " << context.error; | ||
253 | return {{}, context.error}; | ||
254 | } | ||
255 | auto ctx = context.context; | ||
256 | |||
257 | gpgme_set_keylist_mode(ctx, keyListMode); | ||
258 | |||
259 | KeyListResult result; | ||
260 | result.error = {GPG_ERR_NO_ERROR}; | ||
261 | if (patterns.size() > 1) { | ||
262 | qWarning() << "Listing multiple patterns"; | ||
263 | if (auto err = gpgme_op_keylist_ext_start(ctx, const_cast<const char **>(patterns.data()), int(secretOnly), 0)) { | ||
264 | qWarning() << "Error while listing keys"; | ||
265 | result.error = {err}; | ||
266 | } | ||
267 | } else if (patterns.size() == 1) { | ||
268 | qWarning() << "Listing one patterns " << patterns.data()[0]; | ||
269 | if (auto err = gpgme_op_keylist_start(ctx, patterns.data()[0], int(secretOnly))) { | ||
270 | qWarning() << "Error while listing keys"; | ||
271 | result.error = {err}; | ||
272 | } | ||
273 | } else { | ||
274 | qWarning() << "Listing all"; | ||
275 | if (auto err = gpgme_op_keylist_start(ctx, 0, int(secretOnly))) { | ||
276 | qWarning() << "Error while listing keys"; | ||
277 | result.error = {err}; | ||
278 | } | ||
279 | } | ||
280 | |||
281 | |||
282 | while (true) { | ||
283 | gpgme_key_t key; | ||
284 | if (auto e = gpgme_op_keylist_next(ctx, &key)) { | ||
285 | break; | ||
286 | } | ||
287 | Key k; | ||
288 | if (key->subkeys) { | ||
289 | k.keyId = QByteArray{key->subkeys->keyid}; | ||
290 | k.shortKeyId = k.keyId.right(8); | ||
291 | k.fingerprint = QByteArray{key->subkeys->fpr}; | ||
292 | } | ||
293 | for (gpgme_user_id_t uid = key->uids ; uid ; uid = uid->next) { | ||
294 | k.userIds.push_back(UserId{QByteArray{uid->name}, QByteArray{uid->email}, QByteArray{uid->uid}}); | ||
295 | } | ||
296 | k.isExpired = key->expired; | ||
297 | result.keys.push_back(k); | ||
298 | } | ||
299 | gpgme_op_keylist_end(ctx); | ||
300 | return result; | ||
301 | } | ||
302 | |||
303 | /** | ||
304 | * Get the given `key` in the armor format. | ||
305 | */ | ||
306 | Expected<Error, QByteArray> Crypto::exportPublicKey(const Key &key) | ||
307 | { | ||
308 | Context context; | ||
309 | if (!context) { | ||
310 | return makeUnexpected(Error{context.error}); | ||
311 | } | ||
312 | |||
313 | gpgme_data_t out; | ||
314 | const gpgme_error_t e = gpgme_data_new(&out); | ||
315 | Q_ASSERT(!e); | ||
316 | |||
317 | qDebug() << "Exporting public key:" << key.keyId; | ||
318 | if (auto err = gpgme_op_export(context.context, key.keyId, 0, out)) { | ||
319 | return makeUnexpected(Error{err}); | ||
320 | } | ||
321 | |||
322 | return toBA(out); | ||
323 | } | ||
324 | |||
325 | Expected<Error, QByteArray> Crypto::signAndEncrypt(const QByteArray &content, const std::vector<Key> &encryptionKeys, const std::vector<Key> &signingKeys) | ||
326 | { | ||
327 | Context context; | ||
328 | if (!context) { | ||
329 | return makeUnexpected(Error{context.error}); | ||
330 | } | ||
331 | |||
332 | for (const auto &signingKey : signingKeys) { | ||
333 | //TODO do we have to free those again? | ||
334 | gpgme_key_t key; | ||
335 | if (auto e = gpgme_get_key(context.context, signingKey.fingerprint, &key, /*secret*/ false)) { | ||
336 | qWarning() << "Failed to retrive signing key " << signingKey.fingerprint << e; | ||
337 | } else { | ||
338 | gpgme_signers_add(context.context, key); | ||
339 | } | ||
340 | } | ||
341 | |||
342 | gpgme_key_t * const keys = new gpgme_key_t[encryptionKeys.size() + 1]; | ||
343 | gpgme_key_t * keys_it = keys; | ||
344 | for (const auto &k : encryptionKeys) { | ||
345 | gpgme_key_t key; | ||
346 | if (auto e = gpgme_get_key(context.context, k.fingerprint, &key, /*secret*/ false)) { | ||
347 | qWarning() << "Failed to retrive key " << k.fingerprint << e; | ||
348 | } else { | ||
349 | *keys_it++ = key; | ||
350 | } | ||
351 | } | ||
352 | *keys_it++ = 0; | ||
353 | |||
354 | gpgme_data_t out; | ||
355 | const gpgme_error_t e = gpgme_data_new(&out); | ||
356 | Q_ASSERT(!e); | ||
357 | |||
358 | gpgme_error_t err = !signingKeys.empty() ? | ||
359 | gpgme_op_encrypt_sign(context.context, keys, GPGME_ENCRYPT_ALWAYS_TRUST, Data{content}.data, out) : | ||
360 | gpgme_op_encrypt(context.context, keys, GPGME_ENCRYPT_ALWAYS_TRUST, Data{content}.data, out); | ||
361 | delete[] keys; | ||
362 | if (err) { | ||
363 | qWarning() << "Encryption failed:" << Error{err}; | ||
364 | return makeUnexpected(Error{err}); | ||
365 | } | ||
366 | |||
367 | return toBA(out); | ||
368 | ; | ||
369 | } | ||
370 | |||
371 | Expected<Error, std::pair<QByteArray, QString>> | ||
372 | Crypto::sign(const QByteArray &content, const std::vector<Key> &signingKeys) | ||
373 | { | ||
374 | Context context; | ||
375 | if (!context) { | ||
376 | return makeUnexpected(Error{context.error}); | ||
377 | } | ||
378 | |||
379 | for (const auto &signingKey : signingKeys) { | ||
380 | //TODO do we have to free those again? | ||
381 | gpgme_key_t key; | ||
382 | if (auto e = gpgme_get_key(context.context, signingKey.fingerprint, &key, /*secret*/ false)) { | ||
383 | qWarning() << "Failed to retrive signing key " << signingKey.fingerprint << e; | ||
384 | } else { | ||
385 | gpgme_signers_add(context.context, key); | ||
386 | } | ||
387 | } | ||
388 | |||
389 | gpgme_data_t out; | ||
390 | const gpgme_error_t e = gpgme_data_new(&out); | ||
391 | Q_ASSERT(!e); | ||
392 | |||
393 | if (auto err = gpgme_op_sign(context.context, Data{content}.data, out, GPGME_SIG_MODE_DETACH)) { | ||
394 | qWarning() << "Signing failed:" << Error{err}; | ||
395 | return makeUnexpected(Error{err}); | ||
396 | } | ||
397 | |||
398 | |||
399 | const QByteArray algo = [&] { | ||
400 | if (gpgme_sign_result_t res = gpgme_op_sign_result(context.context)) { | ||
401 | for (gpgme_new_signature_t is = res->signatures ; is ; is = is->next) { | ||
402 | return QByteArray{gpgme_hash_algo_name(is->hash_algo)}; | ||
403 | } | ||
404 | } | ||
405 | return QByteArray{}; | ||
406 | }(); | ||
407 | // RFC 3156 Section 5: | ||
408 | // Hash-symbols are constructed [...] by converting the text name to lower | ||
409 | // case and prefixing it with the four characters "pgp-". | ||
410 | const auto micAlg = (QString("pgp-") + algo).toLower(); | ||
411 | |||
412 | return std::pair<QByteArray, QString>{toBA(out), micAlg}; | ||
413 | } | ||
414 | |||
415 | ImportResult Crypto::importKey(const QByteArray &pkey) | ||
416 | { | ||
417 | return importKeys(OpenPGP, pkey); | ||
418 | } | ||
419 | |||
420 | std::vector<Key> Crypto::findKeys(const QStringList &patterns, bool findPrivate, bool remote) | ||
421 | { | ||
422 | QByteArrayList list; | ||
423 | std::transform(patterns.constBegin(), patterns.constEnd(), std::back_inserter(list), [] (const QString &s) { return s.toUtf8(); }); | ||
424 | std::vector<char const *> pattern; | ||
425 | std::transform(list.constBegin(), list.constEnd(), std::back_inserter(pattern), [] (const QByteArray &s) { return s.constData(); }); | ||
426 | pattern.push_back(0); | ||
427 | |||
428 | const KeyListResult res = listKeys(OpenPGP, pattern, findPrivate, remote ? GPGME_KEYLIST_MODE_EXTERN : GPGME_KEYLIST_MODE_LOCAL); | ||
429 | if (res.error) { | ||
430 | qWarning() << "Failed to lookup keys: " << res.error; | ||
431 | return {}; | ||
432 | } | ||
433 | qDebug() << "got keys:" << res.keys.size(); | ||
434 | for (const auto &key : res.keys) { | ||
435 | qDebug() << "isexpired:" << key.isExpired; | ||
436 | for (const auto &userId : key.userIds) { | ||
437 | qDebug() << "userID:" << userId.email; | ||
438 | } | ||
439 | } | ||
440 | return res.keys; | ||
441 | } | ||
442 | |||