summaryrefslogtreecommitdiffstats
path: root/framework/src/domain/mime/crypto.cpp
diff options
context:
space:
mode:
authorChristian Mollekopf <chrigi_1@fastmail.fm>2018-05-05 10:39:32 +0200
committerChristian Mollekopf <chrigi_1@fastmail.fm>2018-05-06 17:21:01 +0200
commit01594e68275a09c67b5ee258e2af86598118a6a0 (patch)
tree4f859815f6455906bb656f9cc27ba5d6e4111599 /framework/src/domain/mime/crypto.cpp
parent481cb9f600caf3f45596bf78b5ba2bd07007969c (diff)
downloadkube-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.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