diff options
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 | |||