File indexing completed on 2024-05-12 09:30:24

0001 // SPDX-FileCopyrightText: 2009 Constantin Berzan <exit3219@gmail.com>
0002 // SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
0003 // SPDX-FileCopyrightText: 2010 Leo Franchi <lfranchi@kde.org>
0004 // SPDX-FileCopyrightText: 2017 Christian Mollekopf <mollekopf@kolabsys.com>
0005 // SPDX-License-Identifier: LGPL-2.0-or-later
0006 
0007 #include "crypto.h"
0008 
0009 #ifndef _WIN32
0010 #include <gpgme.h>
0011 #endif
0012 
0013 #include <QDateTime>
0014 #include <QDebug>
0015 #include <QFile>
0016 
0017 #include <future>
0018 #include <utility>
0019 
0020 using namespace Crypto;
0021 
0022 QDebug operator<<(QDebug d, const Key &key)
0023 {
0024     d << key.fingerprint;
0025     return d;
0026 }
0027 
0028 QDebug operator<<(QDebug d, const Error &error)
0029 {
0030     d << error.error;
0031     return d;
0032 }
0033 
0034 #ifndef _WIN32
0035 namespace Crypto
0036 {
0037 struct Data {
0038     Data(const QByteArray &buffer)
0039     {
0040         const bool copy = false;
0041         const gpgme_error_t e = gpgme_data_new_from_mem(&data, buffer.constData(), buffer.size(), int(copy));
0042         if (e) {
0043             qWarning() << "Failed to copy data?" << e;
0044         }
0045     }
0046 
0047     ~Data()
0048     {
0049         gpgme_data_release(data);
0050     }
0051     gpgme_data_t data;
0052 };
0053 }
0054 
0055 static gpgme_error_t checkEngine(CryptoProtocol protocol)
0056 {
0057     gpgme_check_version(nullptr);
0058     const gpgme_protocol_t p = protocol == CMS ? GPGME_PROTOCOL_CMS : GPGME_PROTOCOL_OpenPGP;
0059     return gpgme_engine_check_version(p);
0060 }
0061 
0062 static std::pair<gpgme_error_t, gpgme_ctx_t> createForProtocol(CryptoProtocol proto)
0063 {
0064     if (auto e = checkEngine(proto)) {
0065         qWarning() << "GPG Engine check failed." << e;
0066         return std::make_pair(e, nullptr);
0067     }
0068     gpgme_ctx_t ctx = nullptr;
0069     if (auto e = gpgme_new(&ctx)) {
0070         return std::make_pair(e, nullptr);
0071     }
0072 
0073     switch (proto) {
0074     case OpenPGP:
0075         if (auto e = gpgme_set_protocol(ctx, GPGME_PROTOCOL_OpenPGP)) {
0076             gpgme_release(ctx);
0077             return std::make_pair(e, nullptr);
0078         }
0079         break;
0080     case CMS:
0081         if (auto e = gpgme_set_protocol(ctx, GPGME_PROTOCOL_CMS)) {
0082             gpgme_release(ctx);
0083             return std::make_pair(e, nullptr);
0084         }
0085         break;
0086     default:
0087         Q_ASSERT(false);
0088         return std::make_pair(1, nullptr);
0089     }
0090     // We want the output to always be ASCII armored
0091     gpgme_set_armor(ctx, 1);
0092 
0093     // Trust new keys
0094     if (auto e = gpgme_set_ctx_flag(ctx, "trust-model", "tofu+pgp")) {
0095         gpgme_release(ctx);
0096         return std::make_pair(e, nullptr);
0097     }
0098 
0099     // That's a great way to bring signature verification to a crawl
0100     if (auto e = gpgme_set_ctx_flag(ctx, "auto-key-retrieve", "0")) {
0101         gpgme_release(ctx);
0102         return std::make_pair(e, nullptr);
0103     }
0104 
0105     return std::make_pair(GPG_ERR_NO_ERROR, ctx);
0106 }
0107 
0108 gpgme_error_t gpgme_passphrase(void *hook, const char *uid_hint, const char *passphrase_info, int prev_was_bad, int fd)
0109 {
0110     Q_UNUSED(hook);
0111     Q_UNUSED(prev_was_bad);
0112     // uid_hint will be something like "CAA5183608F0FB50 Test1 Kolab <test1@kolab.org>" (CAA518... is the key)
0113     // pahhphrase_info will be something like "CAA5183608F0FB50 2E3B7787B1B75920 1 0"
0114     qInfo() << "Requested passphrase for " << (uid_hint ? QByteArray{uid_hint} : QByteArray{})
0115             << (passphrase_info ? QByteArray{passphrase_info} : QByteArray{});
0116 
0117     QFile file;
0118     file.open(fd, QIODevice::WriteOnly);
0119     // FIXME hardcoded as a test
0120     auto passphrase = QByteArray{"test1"} + QByteArray{"\n"};
0121     file.write(passphrase);
0122     file.close();
0123 
0124     return 0;
0125 }
0126 
0127 namespace Crypto
0128 {
0129 struct Context {
0130     Context(CryptoProtocol protocol = OpenPGP)
0131     {
0132         gpgme_error_t code;
0133         std::tie(code, context) = createForProtocol(protocol);
0134         error = Error{code};
0135     }
0136 
0137     ~Context()
0138     {
0139         gpgme_release(context);
0140     }
0141 
0142     operator bool() const
0143     {
0144         return !error;
0145     }
0146     Error error;
0147     gpgme_ctx_t context;
0148 };
0149 }
0150 
0151 static QByteArray toBA(gpgme_data_t out)
0152 {
0153     size_t length = 0;
0154     auto data = gpgme_data_release_and_get_mem(out, &length);
0155     auto outdata = QByteArray{data, static_cast<int>(length)};
0156     gpgme_free(data);
0157     return outdata;
0158 }
0159 
0160 static std::vector<Recipient> copyRecipients(gpgme_decrypt_result_t result)
0161 {
0162     std::vector<Recipient> recipients;
0163     for (gpgme_recipient_t r = result->recipients; r; r = r->next) {
0164         recipients.push_back({QByteArray{r->keyid}, r->status != GPG_ERR_NO_SECKEY});
0165     }
0166     return recipients;
0167 }
0168 
0169 static std::vector<Signature> copySignatures(gpgme_verify_result_t result)
0170 {
0171     std::vector<Signature> signatures;
0172     for (gpgme_signature_t is = result->signatures; is; is = is->next) {
0173         Signature sig;
0174         sig.fingerprint = QByteArray{is->fpr};
0175         sig.creationTime.setSecsSinceEpoch(is->timestamp);
0176         if (is->summary & GPGME_SIGSUM_VALID) {
0177             sig.result = Signature::Ok;
0178         } else {
0179             sig.result = Signature::Invalid;
0180             if (is->summary & GPGME_SIGSUM_KEY_EXPIRED) {
0181                 sig.result = Signature::Expired;
0182             }
0183             if (is->summary & GPGME_SIGSUM_KEY_MISSING) {
0184                 sig.result = Signature::KeyNotFound;
0185             }
0186         }
0187         sig.status = {is->status};
0188         sig.isTrusted = is->validity == GPGME_VALIDITY_FULL || is->validity == GPGME_VALIDITY_ULTIMATE;
0189         signatures.push_back(sig);
0190     }
0191     return signatures;
0192 }
0193 
0194 VerificationResult Crypto::verifyDetachedSignature(CryptoProtocol protocol, const QByteArray &signature, const QByteArray &text)
0195 {
0196     Context context{protocol};
0197     if (!context) {
0198         qWarning() << "Failed to create context " << context.error;
0199         return {{}, context.error};
0200     }
0201     auto ctx = context.context;
0202 
0203     auto err = gpgme_op_verify(ctx, Data{signature}.data, Data{text}.data, nullptr);
0204     gpgme_verify_result_t res = gpgme_op_verify_result(ctx);
0205     return {copySignatures(res), {err}};
0206 }
0207 
0208 static DecryptionResult::Result toResult(gpgme_error_t err)
0209 {
0210     if (err == GPG_ERR_NO_DATA) {
0211         return DecryptionResult::NotEncrypted;
0212     } else if (err == GPG_ERR_NO_SECKEY) {
0213         return DecryptionResult::NoSecretKeyError;
0214     } else if (err == GPG_ERR_CANCELED || err == GPG_ERR_INV_PASSPHRASE) {
0215         return DecryptionResult::PassphraseError;
0216     }
0217     qWarning() << "unknown error" << err << gpgme_strerror(err);
0218     return DecryptionResult::NoSecretKeyError;
0219 }
0220 
0221 VerificationResult Crypto::verifyOpaqueSignature(CryptoProtocol protocol, const QByteArray &signature, QByteArray &outdata)
0222 {
0223     Context context{protocol};
0224     if (!context) {
0225         qWarning() << "Failed to create context " << context.error;
0226         return VerificationResult{{}, context.error};
0227     }
0228     auto ctx = context.context;
0229 
0230     gpgme_data_t out;
0231     const gpgme_error_t e = gpgme_data_new(&out);
0232     Q_ASSERT(!e);
0233     auto err = gpgme_op_verify(ctx, Data{signature}.data, nullptr, out);
0234 
0235     VerificationResult result{{}, {err}};
0236     if (gpgme_verify_result_t res = gpgme_op_verify_result(ctx)) {
0237         result.signatures = copySignatures(res);
0238     }
0239 
0240     outdata = toBA(out);
0241     return result;
0242 }
0243 
0244 std::pair<DecryptionResult, VerificationResult> Crypto::decryptAndVerify(CryptoProtocol protocol, const QByteArray &ciphertext, QByteArray &outdata)
0245 {
0246     Context context{protocol};
0247     if (!context) {
0248         qWarning() << "Failed to create context " << gpgme_strerror(context.error);
0249         qWarning() << "returning early";
0250         return std::make_pair(DecryptionResult{{}, {context.error}, DecryptionResult::NoSecretKeyError}, VerificationResult{{}, context.error});
0251     }
0252     auto ctx = context.context;
0253 
0254     gpgme_data_t out;
0255     if (gpgme_error_t e = gpgme_data_new(&out)) {
0256         qWarning() << "Failed to allocated data" << e;
0257     }
0258     auto err = gpgme_op_decrypt_verify(ctx, Data{ciphertext}.data, out);
0259     if (err) {
0260         qWarning() << "Failed to decrypt and verify" << Error{err};
0261         // We make sure we don't return any plain-text if the decryption failed to prevent EFAIL
0262         if (err == GPG_ERR_DECRYPT_FAILED) {
0263             return std::make_pair(DecryptionResult{{}, {err}, DecryptionResult::DecryptionError}, VerificationResult{{}, {err}});
0264         }
0265     }
0266 
0267     VerificationResult verificationResult{{}, {err}};
0268     if (gpgme_verify_result_t res = gpgme_op_verify_result(ctx)) {
0269         verificationResult.signatures = copySignatures(res);
0270     }
0271 
0272     DecryptionResult decryptionResult{{}, {err}};
0273     if (gpgme_decrypt_result_t res = gpgme_op_decrypt_result(ctx)) {
0274         decryptionResult.recipients = copyRecipients(res);
0275     }
0276     decryptionResult.result = toResult(err);
0277 
0278     outdata = toBA(out);
0279     return std::make_pair(decryptionResult, verificationResult);
0280 }
0281 
0282 static DecryptionResult decryptGPGME(CryptoProtocol protocol, const QByteArray &ciphertext, QByteArray &outdata)
0283 {
0284     Context context{protocol};
0285     if (!context) {
0286         qWarning() << "Failed to create context " << context.error;
0287         return DecryptionResult{{}, context.error};
0288     }
0289     auto ctx = context.context;
0290 
0291     gpgme_data_t out;
0292     if (gpgme_error_t e = gpgme_data_new(&out)) {
0293         qWarning() << "Failed to allocated data" << e;
0294     }
0295     auto err = gpgme_op_decrypt(ctx, Data{ciphertext}.data, out);
0296     if (err) {
0297         qWarning() << "Failed to decrypt" << gpgme_strerror(err);
0298         // We make sure we don't return any plain-text if the decryption failed to prevent EFAIL
0299         if (err == GPG_ERR_DECRYPT_FAILED) {
0300             return DecryptionResult{{}, {err}};
0301         }
0302     }
0303 
0304     DecryptionResult decryptionResult{{}, {err}};
0305     if (gpgme_decrypt_result_t res = gpgme_op_decrypt_result(ctx)) {
0306         decryptionResult.recipients = copyRecipients(res);
0307     }
0308 
0309     decryptionResult.result = toResult(err);
0310 
0311     outdata = toBA(out);
0312     return decryptionResult;
0313 }
0314 
0315 DecryptionResult Crypto::decrypt(CryptoProtocol protocol, const QByteArray &ciphertext, QByteArray &outdata)
0316 {
0317     return decryptGPGME(protocol, ciphertext, outdata);
0318 }
0319 
0320 ImportResult Crypto::importKey(CryptoProtocol protocol, const QByteArray &certData)
0321 {
0322     Context context{protocol};
0323     if (!context) {
0324         qWarning() << "Failed to create context " << context.error;
0325         return {0, 0, 0};
0326     }
0327     if (gpgme_op_import(context.context, Data{certData}.data)) {
0328         qWarning() << "Import failed";
0329         return {0, 0, 0};
0330     }
0331     if (auto result = gpgme_op_import_result(context.context)) {
0332         return {result->considered, result->imported, result->unchanged};
0333     } else {
0334         return {0, 0, 0};
0335     }
0336 }
0337 
0338 static bool validateKey(const gpgme_key_t key)
0339 {
0340     if (key->revoked) {
0341         qWarning() << "Key is revoked " << key->fpr;
0342         return false;
0343     }
0344     if (key->expired) {
0345         qWarning() << "Key is expired " << key->fpr;
0346         return false;
0347     }
0348     if (key->disabled) {
0349         qWarning() << "Key is disabled " << key->fpr;
0350         return false;
0351     }
0352     if (key->invalid) {
0353         qWarning() << "Key is invalid " << key->fpr;
0354         return false;
0355     }
0356     return true;
0357 }
0358 
0359 static KeyListResult listKeys(CryptoProtocol protocol, const std::vector<const char *> &patterns, bool secretOnly, int keyListMode, bool importKeys)
0360 {
0361     Context context{protocol};
0362     if (!context) {
0363         qWarning() << "Failed to create context " << context.error;
0364         return {{}, context.error};
0365     }
0366     auto ctx = context.context;
0367 
0368     gpgme_set_keylist_mode(ctx, keyListMode);
0369 
0370     KeyListResult result;
0371     result.error = {GPG_ERR_NO_ERROR};
0372     auto zeroTerminatedPatterns = patterns;
0373     zeroTerminatedPatterns.push_back(nullptr);
0374     if (patterns.size() > 1) {
0375         if (auto err = gpgme_op_keylist_ext_start(ctx, const_cast<const char **>(zeroTerminatedPatterns.data()), int(secretOnly), 0)) {
0376             result.error = {err};
0377             qWarning() << "Error while listing keys:" << result.error;
0378         }
0379     } else if (patterns.size() == 1) {
0380         if (auto err = gpgme_op_keylist_start(ctx, zeroTerminatedPatterns[0], int(secretOnly))) {
0381             result.error = {err};
0382             qWarning() << "Error while listing keys:" << result.error;
0383         }
0384     } else {
0385         if (auto err = gpgme_op_keylist_start(ctx, nullptr, int(secretOnly))) {
0386             result.error = {err};
0387             qWarning() << "Error while listing keys:" << result.error;
0388         }
0389     }
0390 
0391     std::vector<gpgme_key_t> listedKeys;
0392     while (true) {
0393         gpgme_key_t key;
0394         if (auto err = gpgme_op_keylist_next(ctx, &key)) {
0395             if (gpgme_err_code(err) != GPG_ERR_EOF) {
0396                 qWarning() << "Error after listing keys" << result.error << gpgme_strerror(err);
0397             }
0398             break;
0399         }
0400 
0401         listedKeys.push_back(key);
0402 
0403         Key k;
0404         if (key->subkeys) {
0405             k.keyId = QByteArray{key->subkeys->keyid};
0406             k.shortKeyId = k.keyId.right(8);
0407             k.fingerprint = QByteArray{key->subkeys->fpr};
0408         }
0409         for (gpgme_user_id_t uid = key->uids; uid; uid = uid->next) {
0410             k.userIds.push_back(UserId{QByteArray{uid->name}, QByteArray{uid->email}, QByteArray{uid->uid}});
0411         }
0412         k.isUsable = validateKey(key);
0413         result.keys.push_back(k);
0414     }
0415     gpgme_op_keylist_end(ctx);
0416 
0417     if (importKeys && !listedKeys.empty()) {
0418         listedKeys.push_back(nullptr);
0419         if (auto err = gpgme_op_import_keys(ctx, const_cast<gpgme_key_t *>(listedKeys.data()))) {
0420             qWarning() << "Error while importing keys" << gpgme_strerror(err);
0421         }
0422     }
0423     return result;
0424 }
0425 
0426 /**
0427  * Get the given `key` in the armor format.
0428  */
0429 Expected<Error, QByteArray> Crypto::exportPublicKey(const Key &key)
0430 {
0431     Context context;
0432     if (!context) {
0433         return makeUnexpected(Error{context.error});
0434     }
0435 
0436     gpgme_data_t out;
0437     const gpgme_error_t e = gpgme_data_new(&out);
0438     Q_ASSERT(!e);
0439 
0440     qDebug() << "Exporting public key:" << key.keyId;
0441     if (auto err = gpgme_op_export(context.context, key.keyId.data(), 0, out)) {
0442         return makeUnexpected(Error{err});
0443     }
0444 
0445     return toBA(out);
0446 }
0447 
0448 Expected<Error, QByteArray> Crypto::signAndEncrypt(const QByteArray &content, const std::vector<Key> &encryptionKeys, const std::vector<Key> &signingKeys)
0449 {
0450     Context context;
0451     if (!context) {
0452         return makeUnexpected(Error{context.error});
0453     }
0454 
0455     for (const auto &signingKey : signingKeys) {
0456         qDebug() << "Signing with " << signingKey;
0457         // TODO do we have to free those again?
0458         gpgme_key_t key;
0459         if (auto e = gpgme_get_key(context.context, signingKey.fingerprint.data(), &key, /*secret*/ false)) {
0460             qWarning() << "Failed to retrieve signing key " << signingKey.fingerprint << Error{e};
0461             return makeUnexpected(Error{e});
0462         } else {
0463             gpgme_signers_add(context.context, key);
0464         }
0465     }
0466 
0467     gpgme_key_t *const keys = new gpgme_key_t[encryptionKeys.size() + 1];
0468     gpgme_key_t *keys_it = keys;
0469     for (const auto &k : encryptionKeys) {
0470         qDebug() << "Encrypting to " << k;
0471         gpgme_key_t key;
0472         if (auto e = gpgme_get_key(context.context, k.fingerprint.data(), &key, /*secret*/ false)) {
0473             delete[] keys;
0474             qWarning() << "Failed to retrieve key " << k.fingerprint << Error{e};
0475             return makeUnexpected(Error{e});
0476         } else {
0477             if (!key->can_encrypt || !validateKey(key)) {
0478                 qWarning() << "Key cannot be used for encryption " << k.fingerprint;
0479                 delete[] keys;
0480                 return makeUnexpected(Error{e});
0481             }
0482             *keys_it++ = key;
0483         }
0484     }
0485     *keys_it++ = nullptr;
0486 
0487     gpgme_data_t out;
0488     if (auto e = gpgme_data_new(&out)) {
0489         qWarning() << "Failed to allocate output buffer";
0490         delete[] keys;
0491         return makeUnexpected(Error{e});
0492     }
0493 
0494     gpgme_error_t err = !signingKeys.empty() ? gpgme_op_encrypt_sign(context.context, keys, GPGME_ENCRYPT_ALWAYS_TRUST, Data{content}.data, out)
0495                                              : gpgme_op_encrypt(context.context, keys, GPGME_ENCRYPT_ALWAYS_TRUST, Data{content}.data, out);
0496     delete[] keys;
0497     if (err) {
0498         qWarning() << "Encryption failed:" << gpgme_err_code(err);
0499         switch (gpgme_err_code(err)) {
0500         case GPG_ERR_UNUSABLE_PUBKEY:
0501             for (const auto &k : encryptionKeys) {
0502                 qWarning() << "Encryption key:" << k;
0503             }
0504             break;
0505         case GPG_ERR_UNUSABLE_SECKEY:
0506             for (const auto &k : signingKeys) {
0507                 qWarning() << "Signing key:" << k;
0508             }
0509             break;
0510         default:
0511             break;
0512         }
0513         return makeUnexpected(Error{err});
0514     }
0515 
0516     return toBA(out);
0517 }
0518 
0519 Expected<Error, std::pair<QByteArray, QString>> Crypto::sign(const QByteArray &content, const std::vector<Key> &signingKeys)
0520 {
0521     Context context;
0522     if (!context) {
0523         return makeUnexpected(Error{context.error});
0524     }
0525 
0526     for (const auto &signingKey : signingKeys) {
0527         // TODO do we have to free those again?
0528         gpgme_key_t key;
0529         if (auto e = gpgme_get_key(context.context, signingKey.fingerprint.data(), &key, /*secret*/ false)) {
0530             qWarning() << "Failed to retrieve signing key " << signingKey.fingerprint << Error{e};
0531             return makeUnexpected(Error{e});
0532         } else {
0533             gpgme_signers_add(context.context, key);
0534         }
0535     }
0536 
0537     gpgme_data_t out;
0538     const gpgme_error_t e = gpgme_data_new(&out);
0539     Q_ASSERT(!e);
0540 
0541     if (auto err = gpgme_op_sign(context.context, Data{content}.data, out, GPGME_SIG_MODE_DETACH)) {
0542         qWarning() << "Signing failed:" << Error{err};
0543         return makeUnexpected(Error{err});
0544     }
0545 
0546     const QByteArray algo = [&] {
0547         if (gpgme_sign_result_t res = gpgme_op_sign_result(context.context)) {
0548             if (gpgme_new_signature_t is = res->signatures) {
0549                 return QByteArray{gpgme_hash_algo_name(is->hash_algo)};
0550             }
0551         }
0552         return QByteArray{};
0553     }();
0554     // RFC 3156 Section 5:
0555     // Hash-symbols are constructed [...] by converting the text name to lower
0556     // case and prefixing it with the four characters "pgp-".
0557     const auto micAlg = (QStringLiteral("pgp-") + QString::fromUtf8(algo)).toLower();
0558 
0559     return std::pair<QByteArray, QString>{toBA(out), micAlg};
0560 }
0561 
0562 std::vector<Key> Crypto::findKeys(const QStringList &patterns, bool findPrivate, bool remote)
0563 {
0564     QByteArrayList list;
0565     std::transform(patterns.constBegin(), patterns.constEnd(), std::back_inserter(list), [](const QString &s) {
0566         return s.toUtf8();
0567     });
0568     std::vector<char const *> pattern;
0569     std::transform(list.constBegin(), list.constEnd(), std::back_inserter(pattern), [](const QByteArray &s) {
0570         return s.constData();
0571     });
0572 
0573     const KeyListResult res = listKeys(OpenPGP, pattern, findPrivate, remote ? GPGME_KEYLIST_MODE_EXTERN : GPGME_KEYLIST_MODE_LOCAL, remote);
0574     if (res.error) {
0575         qWarning() << "Failed to lookup keys: " << res.error;
0576         return {};
0577     }
0578     qDebug() << "Found " << res.keys.size() << " keys for the patterns: " << patterns;
0579 
0580     std::vector<Key> usableKeys;
0581     for (const auto &key : res.keys) {
0582         if (!key.isUsable) {
0583             qWarning() << "Key is not usable: " << key.fingerprint;
0584             continue;
0585         }
0586 
0587         qDebug() << "Key:" << key.fingerprint;
0588         for (const auto &userId : key.userIds) {
0589             qDebug() << "  userID:" << userId.email;
0590         }
0591         usableKeys.push_back(key);
0592     }
0593     return usableKeys;
0594 }
0595 
0596 #endif