File indexing completed on 2023-09-24 06:04:22
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