File indexing completed on 2024-09-15 10:31:44
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