00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021 #include "security.h"
00022
00023
00024 #include <QtCore/QFile>
00025 #include <QtCore/QFileInfo>
00026 #include <QtCore/QPointer>
00027 #include <QtCore/QStringList>
00028 #include <QtCore/QTextIStream>
00029 #include <QtCore/QTimer>
00030
00031
00032 #include <kdebug.h>
00033 #include <kinputdialog.h>
00034 #include <klocale.h>
00035 #include <kcodecs.h>
00036 #include <kmessagebox.h>
00037 #include <kpassworddialog.h>
00038 #include <kprocess.h>
00039
00040 using namespace KNS;
00041
00042 Security::Security()
00043 {
00044 m_keysRead = false;
00045 m_gpgRunning = false;
00046 readKeys();
00047 readSecretKeys();
00048 }
00049
00050
00051 Security::~Security()
00052 {
00053 }
00054
00055 void Security::readKeys()
00056 {
00057 if (m_gpgRunning) {
00058 QTimer::singleShot(5, this, SLOT(readKeys()));
00059 return;
00060 }
00061 m_runMode = List;
00062 m_keys.clear();
00063 m_process = new KProcess();
00064 *m_process << "gpg"
00065 << "--no-secmem-warning"
00066 << "--no-tty"
00067 << "--with-colon"
00068 << "--list-keys";
00069 connect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)),
00070 this, SLOT(slotFinished(int, QProcess::ExitStatus)));
00071 connect(m_process, SIGNAL(readyReadStandardOutput()),
00072 this, SLOT(slotReadyReadStandardOutput()));
00073 m_process->start();
00074 if (!m_process->waitForStarted()) {
00075 KMessageBox::error(0L, i18n("<qt>Cannot start <i>gpg</i> and retrieve the available keys. Make sure that <i>gpg</i> is installed, otherwise verification of downloaded resources will not be possible.</qt>"));
00076 delete m_process;
00077 m_process = 0;
00078 } else
00079 m_gpgRunning = true;
00080 }
00081
00082 void Security::readSecretKeys()
00083 {
00084 if (m_gpgRunning) {
00085 QTimer::singleShot(5, this, SLOT(readSecretKeys()));
00086 return;
00087 }
00088 m_runMode = ListSecret;
00089 m_process = new KProcess();
00090 *m_process << "gpg"
00091 << "--no-secmem-warning"
00092 << "--no-tty"
00093 << "--with-colon"
00094 << "--list-secret-keys";
00095 connect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)),
00096 this, SLOT(slotFinished(int, QProcess::ExitStatus)));
00097 connect(m_process, SIGNAL(readyReadStandardOutput()),
00098 this, SLOT(slotReadyReadStandardOutput()));
00099 m_process->start();
00100 if (!m_process->waitForStarted()) {
00101 delete m_process;
00102 m_process = 0;
00103 } else
00104 m_gpgRunning = true;
00105 }
00106
00107 void Security::slotFinished(int exitCode, QProcess::ExitStatus exitStatus)
00108 {
00109 if (exitStatus != QProcess::NormalExit) {
00110 m_gpgRunning = false;
00111 delete m_process;
00112 m_process = 0;
00113 return;
00114 }
00115 switch (m_runMode) {
00116 case ListSecret:
00117 m_keysRead = true;
00118 break;
00119 case Verify: emit validityResult(m_result);
00120 break;
00121 case Sign: emit fileSigned(m_result);
00122 break;
00123
00124 }
00125 m_gpgRunning = false;
00126 delete m_process;
00127 m_process = 0;
00128
00129 Q_UNUSED(exitCode);
00130 }
00131
00132 void Security::slotReadyReadStandardOutput()
00133 {
00134 QString data;
00135 while (m_process->canReadLine()) {
00136 data = QString::fromLocal8Bit(m_process->readLine());
00137 switch (m_runMode) {
00138 case List:
00139 case ListSecret:
00140 if (data.startsWith("pub") || data.startsWith("sec")) {
00141 KeyStruct key;
00142 if (data.startsWith("pub"))
00143 key.secret = false;
00144 else
00145 key.secret = true;
00146 QStringList line = data.split(':', QString::KeepEmptyParts);
00147 key.id = line[4];
00148 QString shortId = key.id.right(8);
00149 QString trustStr = line[1];
00150 key.trusted = false;
00151 if (trustStr == "u" || trustStr == "f")
00152 key.trusted = true;
00153 data = line[9];
00154 key.mail = data.section('<', -1, -1);
00155 key.mail.truncate(key.mail.length() - 1);
00156 key.name = data.section('<', 0, 0);
00157 if (key.name.contains("("))
00158 key.name = key.name.section('(', 0, 0);
00159 m_keys[shortId] = key;
00160 }
00161 break;
00162 case Verify:
00163 data = data.section(']', 1, -1).trimmed();
00164 if (data.startsWith("GOODSIG")) {
00165 m_result &= SIGNED_BAD_CLEAR;
00166 m_result |= SIGNED_OK;
00167 QString id = data.section(' ', 1 , 1).right(8);
00168 if (!m_keys.contains(id)) {
00169 m_result |= UNKNOWN;
00170 } else {
00171 m_signatureKey = m_keys[id];
00172 }
00173 } else
00174 if (data.startsWith("NO_PUBKEY")) {
00175 m_result &= SIGNED_BAD_CLEAR;
00176 m_result |= UNKNOWN;
00177 } else
00178 if (data.startsWith("BADSIG")) {
00179 m_result |= SIGNED_BAD;
00180 QString id = data.section(' ', 1 , 1).right(8);
00181 if (!m_keys.contains(id)) {
00182 m_result |= UNKNOWN;
00183 } else {
00184 m_signatureKey = m_keys[id];
00185 }
00186 } else
00187 if (data.startsWith("TRUST_ULTIMATE")) {
00188 m_result &= SIGNED_BAD_CLEAR;
00189 m_result |= TRUSTED;
00190 }
00191 break;
00192
00193 case Sign:
00194 if (data.contains("passphrase.enter")) {
00195 KeyStruct key = m_keys[m_secretKey];
00196 QPointer<KPasswordDialog> dlg = new KPasswordDialog(NULL);
00197 dlg->setPrompt(i18n("<qt>Enter passphrase for key <b>0x%1</b>, belonging to<br /><i>%2<%3></i><br />:</qt>", m_secretKey, key.name, key.mail));
00198 if (dlg->exec()) {
00199 m_process->write(dlg->password().toLocal8Bit() + '\n');
00200 } else {
00201 m_result |= BAD_PASSPHRASE;
00202 m_process->kill();
00203 return;
00204 }
00205 } else
00206 if (data.contains("BAD_PASSPHRASE")) {
00207 m_result |= BAD_PASSPHRASE;
00208 }
00209 break;
00210 }
00211 }
00212 }
00213
00214 void Security::checkValidity(const QString& filename)
00215 {
00216 m_fileName = filename;
00217 slotCheckValidity();
00218 }
00219
00220 void Security::slotCheckValidity()
00221 {
00222 if (!m_keysRead || m_gpgRunning) {
00223 QTimer::singleShot(5, this, SLOT(slotCheckValidity()));
00224 return;
00225 }
00226 if (m_keys.count() == 0) {
00227 emit validityResult(-1);
00228 return;
00229 }
00230
00231 m_result = 0;
00232 m_runMode = Verify;
00233 QFileInfo f(m_fileName);
00234
00235 QString md5sum;
00236 const char* c = "";
00237 KMD5 context(c);
00238 QFile file(m_fileName);
00239 if (file.open(QIODevice::ReadOnly)) {
00240 context.reset();
00241 context.update(file);
00242 md5sum = context.hexDigest();
00243 file.close();
00244 }
00245 file.setFileName(f.path() + "/md5sum");
00246 if (file.open(QIODevice::ReadOnly)) {
00247 QByteArray md5sum_file;
00248 file.readLine(md5sum_file.data(), 50);
00249 if (!md5sum_file.isEmpty() && QString(md5sum_file).startsWith(md5sum))
00250 m_result |= MD5_OK;
00251 file.close();
00252 }
00253 m_result |= SIGNED_BAD;
00254 m_signatureKey.id = "";
00255 m_signatureKey.name = "";
00256 m_signatureKey.mail = "";
00257 m_signatureKey.trusted = false;
00258
00259
00260 m_process = new KProcess();
00261 *m_process << "gpg"
00262 << "--no-secmem-warning"
00263 << "--status-fd=2"
00264 << "--command-fd=0"
00265 << "--verify"
00266 << f.path() + "/signature"
00267 << m_fileName;
00268 connect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)),
00269 this, SLOT(slotFinished(int, QProcess::ExitStatus)));
00270 connect(m_process, SIGNAL(readyReadStandardOutput()),
00271 this, SLOT(slotReadyReadStandardOutput()));
00272 m_process->start();
00273 if (m_process->waitForStarted())
00274 m_gpgRunning = true;
00275 else {
00276 KMessageBox::error(0L, i18n("<qt>Cannot start <i>gpg</i> and check the validity of the file. Make sure that <i>gpg</i> is installed, otherwise verification of downloaded resources will not be possible.</qt>"));
00277 emit validityResult(0);
00278 delete m_process;
00279 m_process = 0;
00280 }
00281 }
00282
00283 void Security::signFile(const QString &fileName)
00284 {
00285 m_fileName = fileName;
00286 slotSignFile();
00287 }
00288
00289 void Security::slotSignFile()
00290 {
00291 if (!m_keysRead || m_gpgRunning) {
00292 QTimer::singleShot(5, this, SLOT(slotSignFile()));
00293 return;
00294 }
00295
00296 QStringList secretKeys;
00297 for (QMap<QString, KeyStruct>::Iterator it = m_keys.begin(); it != m_keys.end(); ++it) {
00298 if (it.value().secret)
00299 secretKeys.append(it.key());
00300 }
00301
00302 if (secretKeys.count() == 0) {
00303 emit fileSigned(-1);
00304 return;
00305 }
00306
00307 m_result = 0;
00308 QFileInfo f(m_fileName);
00309
00310
00311 QString md5sum;
00312 const char* c = "";
00313 KMD5 context(c);
00314 QFile file(m_fileName);
00315 if (file.open(QIODevice::ReadOnly)) {
00316 context.reset();
00317 context.update(file);
00318 md5sum = context.hexDigest();
00319 file.close();
00320 }
00321 file.setFileName(f.path() + "/md5sum");
00322 if (file.open(QIODevice::WriteOnly)) {
00323 QTextStream stream(&file);
00324 stream << md5sum;
00325 m_result |= MD5_OK;
00326 file.close();
00327 }
00328
00329 if (secretKeys.count() > 1) {
00330 bool ok;
00331 secretKeys = KInputDialog::getItemList(i18n("Select Signing Key"), i18n("Key used for signing:"), secretKeys, QStringList(secretKeys[0]), false, &ok);
00332 if (ok)
00333 m_secretKey = secretKeys[0];
00334 else {
00335 emit fileSigned(0);
00336 return;
00337 }
00338 } else
00339 m_secretKey = secretKeys[0];
00340
00341
00342 m_process = new KProcess();
00343 *m_process << "gpg"
00344 << "--no-secmem-warning"
00345 << "--status-fd=2"
00346 << "--command-fd=0"
00347 << "--no-tty"
00348 << "--detach-sign"
00349 << "-u"
00350 << m_secretKey
00351 << "-o"
00352 << f.path() + "/signature"
00353 << m_fileName;
00354 connect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)),
00355 this, SLOT(slotFinished(int, QProcess::ExitStatus)));
00356 connect(m_process, SIGNAL(readyReadStandardOutput()),
00357 this, SLOT(slotReadyReadStandardOutput()));
00358 m_runMode = Sign;
00359 m_process->start();
00360 if (m_process->waitForStarted())
00361 m_gpgRunning = true;
00362 else {
00363 KMessageBox::error(0L, i18n("<qt>Cannot start <i>gpg</i> and sign the file. Make sure that <i>gpg</i> is installed, otherwise signing of the resources will not be possible.</qt>"));
00364 emit fileSigned(0);
00365 delete m_process;
00366 m_process = 0;
00367 }
00368 }
00369
00370 #include "security.moc"