summaryrefslogtreecommitdiffstats
path: root/framework/src/domain/mime/crypto.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'framework/src/domain/mime/crypto.cpp')
-rw-r--r--framework/src/domain/mime/crypto.cpp442
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
34using namespace Crypto;
35
36QDebug operator<< (QDebug d, const Key &key)
37{
38 d << key.fingerprint;
39 return d;
40}
41
42QDebug operator<< (QDebug d, const Error &error)
43{
44 d << error.errorCode();
45 return d;
46}
47
48struct 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
65static 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
72static 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
104struct 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
126static 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
135static 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
144static 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
161VerificationResult 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
175VerificationResult 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
198std::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
230ImportResult 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
248static 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 */
306Expected<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
325Expected<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
371Expected<Error, std::pair<QByteArray, QString>>
372Crypto::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
415ImportResult Crypto::importKey(const QByteArray &pkey)
416{
417 return importKeys(OpenPGP, pkey);
418}
419
420std::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