/** * This file is part of GPGFrontend. * * GPGFrontend is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Foobar is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Foobar. If not, see . * * The initial version of the source code is inherited from gpg4usb-team. * Their source code version also complies with GNU General Public License. * * The source code version of this software was modified and released * by Saturneric starting on May 12, 2021. * */ #include "gpg/GpgContext.h" /** * Import key pair * @param inBuffer input byte array * @return Import information */ GpgImportInformation GpgME::GpgContext::importKey(QByteArray inBuffer) { auto *importInformation = new GpgImportInformation(); err = gpgme_data_new_from_mem(&in, inBuffer.data(), inBuffer.size(), 1); checkErr(err); err = gpgme_op_import(mCtx, in); gpgme_import_result_t result; result = gpgme_op_import_result(mCtx); if (result->unchanged) importInformation->unchanged = result->unchanged; if (result->considered) importInformation->considered = result->considered; if (result->no_user_id) importInformation->no_user_id = result->no_user_id; if (result->imported) importInformation->imported = result->imported; if (result->imported_rsa) importInformation->imported_rsa = result->imported_rsa; if (result->unchanged) importInformation->unchanged = result->unchanged; if (result->new_user_ids) importInformation->new_user_ids = result->new_user_ids; if (result->new_sub_keys) importInformation->new_sub_keys = result->new_sub_keys; if (result->new_signatures) importInformation->new_signatures = result->new_signatures; if (result->new_revocations) importInformation->new_revocations = result->new_revocations; if (result->secret_read) importInformation->secret_read = result->secret_read; if (result->secret_imported) importInformation->secret_imported = result->secret_imported; if (result->secret_unchanged) importInformation->secret_unchanged = result->secret_unchanged; if (result->not_imported) importInformation->not_imported = result->not_imported; gpgme_import_status_t status = result->imports; while (status != nullptr) { GpgImportedKey key; key.importStatus = static_cast(status->status); key.fpr = status->fpr; importInformation->importedKeys.emplace_back(key); status = status->next; } checkErr(err); emit signalKeyDBChanged(); gpgme_data_release(in); return *importInformation; } /** * Generate a new key pair * @param params key generation args * @return error information */ gpgme_error_t GpgME::GpgContext::generateKey(GenKeyInfo *params) { auto userid_utf8 = params->getUserid().toUtf8(); const char *userid = userid_utf8.constData(); auto algo_utf8 = (params->getAlgo() + params->getKeySizeStr()).toUtf8(); const char *algo = algo_utf8.constData(); unsigned long expires = QDateTime::currentDateTime().secsTo(params->getExpired()); unsigned int flags = 0; if (!params->isSubKey()) flags |= GPGME_CREATE_CERT; if (params->isAllowEncryption()) flags |= GPGME_CREATE_ENCR; if (params->isAllowSigning()) flags |= GPGME_CREATE_SIGN; if (params->isAllowAuthentication()) flags |= GPGME_CREATE_AUTH; if (params->isNonExpired()) flags |= GPGME_CREATE_NOEXPIRE; if (params->isNoPassPhrase()) flags |= GPGME_CREATE_NOPASSWD; err = gpgme_op_createkey(mCtx, userid, algo, 0, expires, nullptr, flags); if (gpgme_err_code(err) != GPG_ERR_NO_ERROR) { checkErr(err); return err; } else { emit signalKeyDBChanged(); return err; } } /** * Export Key * @param uidList key ids * @param outBuffer output byte array * @return if success */ bool GpgME::GpgContext::exportKeys(QStringList *uidList, QByteArray *outBuffer) { size_t read_bytes; gpgme_data_t dataOut = nullptr; outBuffer->resize(0); if (uidList->count() == 0) { QMessageBox::critical(nullptr, "Export Keys Error", "No Keys Selected"); return false; } for (int i = 0; i < uidList->count(); i++) { err = gpgme_data_new(&dataOut); checkErr(err); err = gpgme_op_export(mCtx, uidList->at(i).toUtf8().constData(), 0, dataOut); checkErr(err); read_bytes = gpgme_data_seek(dataOut, 0, SEEK_END); err = readToBuffer(dataOut, outBuffer); checkErr(err); gpgme_data_release(dataOut); } return true; } /** * Get and store all key pairs info */ void GpgME::GpgContext::fetch_keys() { gpgme_error_t gpgmeError; gpgme_key_t key; qDebug() << "Clear List and Map"; mKeyList.clear(); mKeyMap.clear(); auto &keys = mKeyList; auto &keys_map = mKeyMap; qDebug() << "Set Key Listing Mode"; gpgmeError = gpgme_set_keylist_mode(mCtx, GPGME_KEYLIST_MODE_LOCAL | GPGME_KEYLIST_MODE_WITH_SECRET | GPGME_KEYLIST_MODE_SIGS | GPGME_KEYLIST_MODE_SIG_NOTATIONS | GPGME_KEYLIST_MODE_WITH_TOFU); if (gpg_err_code(gpgmeError) != GPG_ERR_NO_ERROR) { checkErr(gpgmeError); return; } qDebug() << "Operate KeyList Start"; gpgmeError = gpgme_op_keylist_start(mCtx, nullptr, 0); if (gpg_err_code(gpgmeError) != GPG_ERR_NO_ERROR) { checkErr(gpgmeError); return; } qDebug() << "Start Loop"; while ((gpgmeError = gpgme_op_keylist_next(mCtx, &key)) == GPG_ERR_NO_ERROR) { if (!key->subkeys) continue; qDebug() << "Append Key" << key->subkeys->keyid; keys.emplace_back(key); keys_map.insert(keys.back().id, &keys.back()); gpgme_key_unref(key); } if (gpg_err_code(gpgmeError) != GPG_ERR_EOF) { checkErr(gpgmeError); return; } qDebug() << "Operate KeyList End"; gpgmeError = gpgme_op_keylist_end(mCtx); if (gpg_err_code(gpgmeError) != GPG_ERR_NO_ERROR) { checkErr(gpgmeError); return; } gpgmeError = gpgme_op_keylist_end(mCtx); if (gpg_err_code(gpgmeError) != GPG_ERR_NO_ERROR) { checkErr(gpgmeError); return; } mKeyList = keys; } /** * Delete keys * @param uidList key ids */ void GpgME::GpgContext::deleteKeys(QStringList *uidList) { gpgme_error_t error; gpgme_key_t key; for (const auto &tmp : *uidList) { error = gpgme_op_keylist_start(mCtx, tmp.toUtf8().constData(), 0); if (error != GPG_ERR_NO_ERROR) { checkErr(error); continue; } error = gpgme_op_keylist_next(mCtx, &key); if (error != GPG_ERR_NO_ERROR) { checkErr(error); continue; } error = gpgme_op_keylist_end(mCtx); if (error != GPG_ERR_NO_ERROR) { checkErr(error); continue; } error = gpgme_op_delete(mCtx, key, 1); if (error != GPG_ERR_NO_ERROR) { checkErr(error); continue; } } emit signalKeyDBChanged(); } /** * Export keys * @param keys keys used * @param outBuffer output byte array * @return if success */ bool GpgME::GpgContext::exportKeys(const QVector &keys, QByteArray &outBuffer) { size_t read_bytes; gpgme_data_t data_out = nullptr; outBuffer.resize(0); if (keys.empty()) { QMessageBox::critical(nullptr, "Export Keys Error", "No Keys Selected"); return false; } for (const auto &key : keys) { err = gpgme_data_new(&data_out); checkErr(err); err = gpgme_op_export(mCtx, key.id.toUtf8().constData(), 0, data_out); checkErr(err); read_bytes = gpgme_data_seek(data_out, 0, SEEK_END); err = readToBuffer(data_out, &outBuffer); checkErr(err); gpgme_data_release(data_out); } return true; } /** * Set the expire date and time of a key pair(actually the master key) or subkey * @param key target key pair * @param subkey null if master key * @param expires date and time * @return if successful */ bool GpgME::GpgContext::setExpire(const GpgKey &key, const GpgSubKey *subkey, QDateTime *expires) { unsigned long expires_time = 0; if (expires != nullptr) { qDebug() << "Expire Datetime" << expires->toString(); expires_time = QDateTime::currentDateTime().secsTo(*expires); } const char *subfprs = nullptr; if (subkey != nullptr) subfprs = subkey->fpr.toUtf8().constData(); auto gpgmeError = gpgme_op_setexpire(mCtx, key.key_refer, expires_time, subfprs, 0); if (gpgmeError == GPG_ERR_NO_ERROR) { emit signalKeyUpdated(key.id); return true; } else { checkErr(gpgmeError); return false; } } /** * Export the secret key of a key pair(including subkeys) * @param key target key pair * @param outBuffer output byte array * @return if successful */ bool GpgME::GpgContext::exportSecretKey(const GpgKey &key, QByteArray *outBuffer) { qDebug() << "Export Secret Key" << key.id; gpgme_key_t target_key[2] = { key.key_refer, nullptr }; gpgme_data_t dataOut; gpgme_data_new(&dataOut); // export private key to outBuffer gpgme_error_t error = gpgme_op_export_keys(mCtx, target_key, GPGME_EXPORT_MODE_SECRET, dataOut); if (gpgme_err_code(error) != GPG_ERR_NO_ERROR) { checkErr(error); gpgme_data_release(dataOut); return false; } readToBuffer(dataOut, outBuffer); gpgme_data_release(dataOut); return true; } /** * Sign a key pair(actually a certain uid) * @param target target key pair * @param uid target * @param expires expire date and time of the signature * @return if successful */ bool GpgME::GpgContext::signKey(const GpgKey &target, const QString &uid, const QDateTime *expires) { unsigned int flags = 0; unsigned int expires_time_t = 0; if (expires == nullptr) flags |= GPGME_KEYSIGN_NOEXPIRE; else expires_time_t = QDateTime::currentDateTime().secsTo(*expires); auto gpgmeError = gpgme_op_keysign(mCtx, target.key_refer, uid.toUtf8().constData(), expires_time_t, flags); if (gpgmeError == GPG_ERR_NO_ERROR) { emit signalKeyUpdated(target.id); return true; } else { checkErr(gpgmeError); return false; } } /** * Generate revoke cert of a key pair * @param key target key pair * @param outputFileName out file name(path) * @return the process doing this job */ void GpgME::GpgContext::generateRevokeCert(const GpgKey &key, const QString &outputFileName) { executeGpgCommand({ "--command-fd", "0", "--status-fd", "1", //"--no-tty", "-o", outputFileName, "--gen-revoke", key.fpr }, [](QProcess *proc) -> void { qDebug() << "Function Called" << proc; // Code From Gpg4Win while (proc->canReadLine()) { const QString line = QString::fromUtf8(proc->readLine()).trimmed(); if (line == QLatin1String("[GNUPG:] GET_BOOL gen_revoke.okay")) { proc->write("y\n"); } else if (line == QLatin1String( "[GNUPG:] GET_LINE ask_revocation_reason.code")) { proc->write("0\n"); } else if (line == QLatin1String( "[GNUPG:] GET_LINE ask_revocation_reason.text")) { proc->write("\n"); } else if (line == QLatin1String( "[GNUPG:] GET_BOOL openfile.overwrite.okay")) { // We asked before proc->write("y\n"); } else if (line == QLatin1String( "[GNUPG:] GET_BOOL ask_revocation_reason.okay")) { proc->write("y\n"); } } // Code From Gpg4Win } ); }