File indexing completed on 2024-04-14 15:50:57

0001 /**
0002  * SPDX-FileCopyrightText: (C) 2006 Petri Damsten <damu@iki.fi>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "config.h"
0008 
0009 #ifdef HAVE_LIBGPGME
0010 
0011 #include "debugwindow.h"
0012 #include "global.h"
0013 #include "kgpgme.h"
0014 
0015 #include <QDialogButtonBox>
0016 #include <QLabel>
0017 #include <QPushButton>
0018 #include <QTreeWidget>
0019 #include <QVBoxLayout>
0020 #include <QtCore/QPointer>
0021 #include <QtGui/QPixmap>
0022 
0023 #include <KConfigGroup>
0024 #include <KLocalizedString>
0025 #include <KMessageBox>
0026 #include <KPasswordDialog>
0027 #include <QApplication>
0028 
0029 #include <errno.h>  //For errno
0030 #include <locale.h> //For LC_ALL, etc.
0031 #include <unistd.h> //For write
0032 
0033 // KGpgSelKey class based on class in KGpg with the same name
0034 
0035 class KGpgSelKey : public QDialog
0036 {
0037 private:
0038     QTreeWidget *keysListpr;
0039 
0040 public:
0041     KGpgSelKey(QWidget *parent, const char *name, QString preselected, const KGpgMe &gpg)
0042         : QDialog(parent)
0043     {
0044         // Dialog options
0045         setObjectName(name);
0046         setModal(true);
0047         setWindowTitle(i18n("Private Key List"));
0048 
0049         QWidget *mainWidget = new QWidget(this);
0050         QVBoxLayout *mainLayout = new QVBoxLayout;
0051         setLayout(mainLayout);
0052         mainLayout->addWidget(mainWidget);
0053 
0054         QString keyname;
0055         QVBoxLayout *vbox;
0056         QWidget *page = new QWidget(this);
0057         QLabel *labeltxt;
0058         QPixmap keyPair = QIcon::fromTheme("kgpg_key2").pixmap(20, 20);
0059 
0060         setMinimumSize(350, 100);
0061         keysListpr = new QTreeWidget(page);
0062         keysListpr->setRootIsDecorated(true);
0063         keysListpr->setColumnCount(3);
0064         QStringList headers;
0065         headers << i18n("Name") << i18n("Email") << i18n("ID");
0066         keysListpr->setHeaderLabels(headers);
0067         keysListpr->setAllColumnsShowFocus(true);
0068 
0069         labeltxt = new QLabel(i18n("Choose a secret key:"), page);
0070         vbox = new QVBoxLayout(page);
0071 
0072         KGpgKeyList list = gpg.keys(true);
0073 
0074         for (KGpgKeyList::iterator it = list.begin(); it != list.end(); ++it) {
0075             QString name = gpg.checkForUtf8((*it).name);
0076             QStringList values;
0077             values << name << (*it).email << (*it).id;
0078             QTreeWidgetItem *item = new QTreeWidgetItem(keysListpr, values, 3);
0079             item->setIcon(0, keyPair);
0080             if (preselected == (*it).id) {
0081                 item->setSelected(true);
0082                 keysListpr->setCurrentItem(item);
0083             }
0084         }
0085         if (!keysListpr->currentItem() && keysListpr->topLevelItemCount() > 0) {
0086             keysListpr->topLevelItem(0)->setSelected(true);
0087             keysListpr->setCurrentItem(keysListpr->topLevelItem(0));
0088         }
0089         vbox->addWidget(labeltxt);
0090         vbox->addWidget(keysListpr);
0091         mainLayout->addWidget(page);
0092 
0093         QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
0094         QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
0095         okButton->setDefault(true);
0096         okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
0097         connect(buttonBox, &QDialogButtonBox::accepted, this, &KGpgMe::accept);
0098         connect(buttonBox, &QDialogButtonBox::rejected, this, &KGpgMe::reject);
0099         mainLayout->addWidget(buttonBox);
0100     };
0101 
0102     QString key()
0103     {
0104         QTreeWidgetItem *item = keysListpr->currentItem();
0105 
0106         if (item)
0107             return item->text(2);
0108         return QString();
0109     }
0110 };
0111 
0112 KGpgMe::KGpgMe()
0113     : m_ctx(0)
0114     , m_useGnuPGAgent(true)
0115 {
0116     init(GPGME_PROTOCOL_OpenPGP);
0117     if (gpgme_new(&m_ctx) == GPG_ERR_NO_ERROR) {
0118         gpgme_set_armor(m_ctx, 1);
0119         setPassphraseCb();
0120 
0121         // Set gpg version
0122         gpgme_engine_info_t info;
0123         gpgme_get_engine_info(&info);
0124         while (info != NULL && info->protocol != gpgme_get_protocol(m_ctx)) {
0125             info = info->next;
0126         }
0127 
0128         if (info != NULL) {
0129             QByteArray gpgPath = info->file_name;
0130             gpgPath.replace("gpg2", "gpg"); // require GnuPG v1
0131             gpgme_ctx_set_engine_info(m_ctx, GPGME_PROTOCOL_OpenPGP, gpgPath.data(), NULL);
0132         }
0133 
0134     } else {
0135         m_ctx = 0;
0136     }
0137 }
0138 
0139 KGpgMe::~KGpgMe()
0140 {
0141     if (m_ctx)
0142         gpgme_release(m_ctx);
0143     clearCache();
0144 }
0145 
0146 void KGpgMe::clearCache()
0147 {
0148     if (m_cache.size() > 0) {
0149         m_cache.fill('\0');
0150         m_cache.truncate(0);
0151     }
0152 }
0153 
0154 void KGpgMe::init(gpgme_protocol_t proto)
0155 {
0156     gpgme_error_t err;
0157 
0158     gpgme_check_version("1.0.0"); // require GnuPG v1
0159     setlocale(LC_ALL, "");
0160     gpgme_set_locale(NULL, LC_CTYPE, setlocale(LC_CTYPE, NULL));
0161 #ifndef Q_OS_WIN
0162     gpgme_set_locale(NULL, LC_MESSAGES, setlocale(LC_MESSAGES, NULL));
0163 #endif
0164     err = gpgme_engine_check_version(proto);
0165     if (err) {
0166         static QString lastErrorText;
0167 
0168         QString text = QString("%1: %2").arg(gpgme_strsource(err), gpgme_strerror(err));
0169         if (text != lastErrorText) {
0170             KMessageBox::error(qApp->activeWindow(), text);
0171             lastErrorText = text;
0172         }
0173     }
0174 }
0175 
0176 QString KGpgMe::checkForUtf8(QString txt)
0177 {
0178     // code borrowed from KGpg which borrowed it from gpa
0179     const char *s;
0180 
0181     // Make sure the encoding is UTF-8.
0182     // Test structure suggested by Werner Koch
0183     if (txt.isEmpty())
0184         return QString();
0185 
0186     for (s = txt.toLatin1(); *s && !(*s & 0x80); s++)
0187         ;
0188     if (*s && !strchr(txt.toLatin1(), 0xc3) && (txt.indexOf("\\x") == -1))
0189         return txt;
0190 
0191     // The string is not in UTF-8
0192     // if (strchr (txt.toLatin1(), 0xc3)) return (txt+" +++");
0193     if (txt.indexOf("\\x") == -1)
0194         return QString::fromUtf8(txt.toLatin1());
0195     //        if (!strchr (txt.toLatin1(), 0xc3) || (txt.indexOf("\\x")!=-1)) {
0196     for (int idx = 0; (idx = txt.indexOf("\\x", idx)) >= 0; ++idx) {
0197         char str[2] = "x";
0198         str[0] = (char)QString(txt.mid(idx + 2, 2)).toShort(0, 16);
0199         txt.replace(idx, 4, str);
0200     }
0201     if (!strchr(txt.toLatin1(), 0xc3))
0202         return QString::fromUtf8(txt.toLatin1());
0203     else
0204         return QString::fromUtf8(QString::fromUtf8(txt.toLatin1()).toLatin1());
0205     // perform Utf8 twice, or some keys display badly
0206     return txt;
0207 }
0208 
0209 QString KGpgMe::selectKey(QString previous)
0210 {
0211     QPointer<KGpgSelKey> dlg = new KGpgSelKey(qApp->activeWindow(), "", previous, *this);
0212 
0213     if (dlg->exec())
0214         return dlg->key();
0215     return QString();
0216 }
0217 
0218 // Rest of the code is mainly based in gpgme examples
0219 
0220 KGpgKeyList KGpgMe::keys(bool privateKeys /* = false */) const
0221 {
0222     KGpgKeyList keys;
0223     gpgme_error_t err = 0, err2 = 0;
0224     gpgme_key_t key = 0;
0225     gpgme_keylist_result_t result = 0;
0226 
0227     if (m_ctx) {
0228         err = gpgme_op_keylist_start(m_ctx, NULL, privateKeys);
0229         if (!err) {
0230             while (!(err = gpgme_op_keylist_next(m_ctx, &key))) {
0231                 KGpgKey gpgkey;
0232 
0233                 if (!key->subkeys)
0234                     continue;
0235                 gpgkey.id = key->subkeys->keyid;
0236                 if (key->uids) {
0237                     gpgkey.name = key->uids->name;
0238                     gpgkey.email = key->uids->email;
0239                 }
0240                 keys.append(gpgkey);
0241                 gpgme_key_unref(key);
0242             }
0243 
0244             if (gpg_err_code(err) == GPG_ERR_EOF)
0245                 err = 0;
0246             err2 = gpgme_op_keylist_end(m_ctx);
0247             if (!err)
0248                 err = err2;
0249         }
0250     }
0251 
0252     if (err) {
0253         KMessageBox::error(qApp->activeWindow(), QString("%1: %2").arg(gpgme_strsource(err)).arg(gpgme_strerror(err)));
0254     } else {
0255         result = gpgme_op_keylist_result(m_ctx);
0256         if (result->truncated) {
0257             KMessageBox::error(qApp->activeWindow(), i18n("Key listing unexpectedly truncated."));
0258         }
0259     }
0260     return keys;
0261 }
0262 
0263 bool KGpgMe::encrypt(const QByteArray &inBuffer, unsigned long length, QByteArray *outBuffer, QString keyid /* = QString() */)
0264 {
0265     gpgme_error_t err = 0;
0266     gpgme_data_t in = 0, out = 0;
0267     gpgme_key_t keys[2] = {NULL, NULL};
0268     gpgme_key_t *key = NULL;
0269     gpgme_encrypt_result_t result = 0;
0270 
0271     outBuffer->resize(0);
0272     if (m_ctx) {
0273         err = gpgme_data_new_from_mem(&in, inBuffer.data(), length, 1);
0274         if (!err) {
0275             err = gpgme_data_new(&out);
0276             if (!err) {
0277                 if (keyid.isNull()) {
0278                     key = NULL;
0279                 } else {
0280                     err = gpgme_get_key(m_ctx, keyid.toLatin1(), &keys[0], 0);
0281                     key = keys;
0282                 }
0283 
0284                 if (!err) {
0285                     err = gpgme_op_encrypt(m_ctx, key, GPGME_ENCRYPT_ALWAYS_TRUST, in, out);
0286                     if (!err) {
0287                         result = gpgme_op_encrypt_result(m_ctx);
0288                         if (result->invalid_recipients) {
0289                             KMessageBox::error(qApp->activeWindow(), QString("%1: %2").arg(i18n("That public key is not meant for encryption")).arg(result->invalid_recipients->fpr));
0290                         } else {
0291                             err = readToBuffer(out, outBuffer);
0292                         }
0293                     }
0294                 }
0295             }
0296         }
0297     }
0298     if (err != GPG_ERR_NO_ERROR && err != GPG_ERR_CANCELED) {
0299         KMessageBox::error(qApp->activeWindow(), QString("%1: %2").arg(gpgme_strsource(err)).arg(gpgme_strerror(err)));
0300     }
0301     if (err != GPG_ERR_NO_ERROR) {
0302         DEBUG_WIN << "KGpgMe::encrypt error: " + QString::number(err);
0303         clearCache();
0304     }
0305     if (keys[0])
0306         gpgme_key_unref(keys[0]);
0307     if (in)
0308         gpgme_data_release(in);
0309     if (out)
0310         gpgme_data_release(out);
0311     return (err == GPG_ERR_NO_ERROR);
0312 }
0313 
0314 bool KGpgMe::decrypt(const QByteArray &inBuffer, QByteArray *outBuffer)
0315 {
0316     gpgme_error_t err = 0;
0317     gpgme_data_t in = 0, out = 0;
0318     gpgme_decrypt_result_t result = 0;
0319 
0320     outBuffer->resize(0);
0321     if (m_ctx) {
0322         err = gpgme_data_new_from_mem(&in, inBuffer.data(), inBuffer.size(), 1);
0323         if (!err) {
0324             err = gpgme_data_new(&out);
0325             if (!err) {
0326                 err = gpgme_op_decrypt(m_ctx, in, out);
0327                 if (!err) {
0328                     result = gpgme_op_decrypt_result(m_ctx);
0329                     if (result->unsupported_algorithm) {
0330                         KMessageBox::error(qApp->activeWindow(), QString("%1: %2").arg(i18n("Unsupported algorithm")).arg(result->unsupported_algorithm));
0331                     } else {
0332                         err = readToBuffer(out, outBuffer);
0333                     }
0334                 }
0335             }
0336         }
0337     }
0338     if (err != GPG_ERR_NO_ERROR && err != GPG_ERR_CANCELED) {
0339         KMessageBox::error(qApp->activeWindow(), QString("%1: %2").arg(gpgme_strsource(err)).arg(gpgme_strerror(err)));
0340     }
0341     if (err != GPG_ERR_NO_ERROR)
0342         clearCache();
0343     if (in)
0344         gpgme_data_release(in);
0345     if (out)
0346         gpgme_data_release(out);
0347     return (err == GPG_ERR_NO_ERROR);
0348 }
0349 
0350 #define BUF_SIZE (32 * 1024)
0351 
0352 gpgme_error_t KGpgMe::readToBuffer(gpgme_data_t in, QByteArray *outBuffer) const
0353 {
0354     int ret;
0355     gpgme_error_t err = GPG_ERR_NO_ERROR;
0356 
0357     ret = gpgme_data_seek(in, 0, SEEK_SET);
0358     if (ret) {
0359         err = gpgme_err_code_from_errno(errno);
0360     } else {
0361         char *buf = new char[BUF_SIZE + 2];
0362 
0363         if (buf) {
0364             while ((ret = gpgme_data_read(in, buf, BUF_SIZE)) > 0) {
0365                 uint size = outBuffer->size();
0366                 outBuffer->resize(size + ret);
0367                 memcpy(outBuffer->data() + size, buf, ret);
0368             }
0369             if (ret < 0)
0370                 err = gpgme_err_code_from_errno(errno);
0371             delete[] buf;
0372         }
0373     }
0374     return err;
0375 }
0376 
0377 bool KGpgMe::isGnuPGAgentAvailable()
0378 {
0379     QString agent_info = qgetenv("GPG_AGENT_INFO");
0380 
0381     if (agent_info.indexOf(':') > 0)
0382         return true;
0383     return false;
0384 }
0385 
0386 void KGpgMe::setPassphraseCb()
0387 {
0388     bool agent = false;
0389     QString agent_info;
0390 
0391     agent_info = qgetenv("GPG_AGENT_INFO");
0392 
0393     if (m_useGnuPGAgent) {
0394         if (agent_info.indexOf(':'))
0395             agent = true;
0396         if (agent_info.startsWith(QLatin1String("disable:")))
0397             setenv("GPG_AGENT_INFO", agent_info.mid(8).toLatin1(), 1);
0398     } else {
0399         if (!agent_info.startsWith(QLatin1String("disable:")))
0400             setenv("GPG_AGENT_INFO", "disable:" + agent_info.toLatin1(), 1);
0401     }
0402     if (agent)
0403         gpgme_set_passphrase_cb(m_ctx, 0, 0);
0404     else
0405         gpgme_set_passphrase_cb(m_ctx, passphraseCb, this);
0406 }
0407 
0408 gpgme_error_t KGpgMe::passphraseCb(void *hook, const char *uid_hint, const char *passphrase_info, int last_was_bad, int fd)
0409 {
0410     KGpgMe *gpg = static_cast<KGpgMe *>(hook);
0411     return gpg->passphrase(uid_hint, passphrase_info, last_was_bad, fd);
0412 }
0413 
0414 gpgme_error_t KGpgMe::passphrase(const char *uid_hint, const char * /*passphrase_info*/, int last_was_bad, int fd)
0415 {
0416     QString s;
0417     QString gpg_hint = checkForUtf8(uid_hint);
0418     bool canceled = false;
0419 
0420     if (last_was_bad) {
0421         s += "<b>" + i18n("Wrong password.") + "</b><br><br>\n\n";
0422         clearCache();
0423     }
0424 
0425     if (!m_text.isEmpty())
0426         s += m_text + "<br>";
0427 
0428     if (!gpg_hint.isEmpty())
0429         s += gpg_hint;
0430 
0431     if (m_cache.isEmpty()) {
0432         KPasswordDialog dlg;
0433         dlg.setPrompt(s);
0434 
0435         if (m_saving)
0436             dlg.setWindowTitle(i18n("Please enter a new password:"));
0437 
0438         if (dlg.exec())
0439             m_cache = dlg.password();
0440         else
0441             canceled = true;
0442     }
0443 
0444     if (!canceled)
0445         write(fd, m_cache.data(), m_cache.length());
0446     write(fd, "\n", 1);
0447     return canceled ? GPG_ERR_CANCELED : GPG_ERR_NO_ERROR;
0448 }
0449 #endif // HAVE_LIBGPGME