File indexing completed on 2024-04-28 15:28:59
0001 /* 0002 This file is part of KNewStuff2. 0003 SPDX-FileCopyrightText: 2004, 2005 Andras Mantia <amantia@kde.org> 0004 SPDX-FileCopyrightText: 2007 Josef Spillner <spillner@kde.org> 0005 0006 SPDX-License-Identifier: LGPL-2.1-or-later 0007 */ 0008 0009 // app includes 0010 #include "security.h" 0011 #include "question.h" 0012 0013 // qt includes 0014 #include <QFile> 0015 #include <QFileInfo> 0016 #include <QStringList> 0017 #include <QTextStream> 0018 #include <QTimer> 0019 #include <qstandardpaths.h> 0020 0021 #include <QCryptographicHash> 0022 0023 // kde includes 0024 #include <KLocalizedString> 0025 0026 using namespace KNSCore; 0027 0028 #if KNEWSTUFFCORE_BUILD_DEPRECATED_SINCE(5, 31) 0029 static QString gpgExecutable() 0030 { 0031 QString gpgExe = QStandardPaths::findExecutable(QStringLiteral("gpg")); 0032 if (gpgExe.isEmpty()) { 0033 gpgExe = QStandardPaths::findExecutable(QStringLiteral("gpg2")); 0034 } 0035 if (gpgExe.isEmpty()) { 0036 return QStringLiteral("gpg"); 0037 } 0038 return gpgExe; 0039 } 0040 0041 Security::Security() 0042 { 0043 m_keysRead = false; 0044 m_gpgRunning = false; 0045 readKeys(); 0046 readSecretKeys(); 0047 } 0048 0049 Security::~Security() 0050 { 0051 } 0052 0053 void Security::readKeys() 0054 { 0055 if (m_gpgRunning) { 0056 QTimer::singleShot(5, this, &Security::readKeys); 0057 return; 0058 } 0059 m_runMode = List; 0060 m_keys.clear(); 0061 m_process = new QProcess(); 0062 QStringList arguments; 0063 arguments << QStringLiteral("--no-secmem-warning") << QStringLiteral("--no-tty") << QStringLiteral("--with-colon") << QStringLiteral("--list-keys"); 0064 connect(m_process, &QProcess::finished, this, &Security::slotFinished); 0065 connect(m_process, &QProcess::readyReadStandardOutput, this, &Security::slotReadyReadStandardOutput); 0066 m_process->start(gpgExecutable(), arguments); 0067 if (!m_process->waitForStarted()) { 0068 Q_EMIT signalError( 0069 i18n("<qt>Cannot start <i>gpg</i> and retrieve the available keys. Make sure that <i>gpg</i> is installed, otherwise verification of downloaded " 0070 "resources will not be possible.</qt>")); 0071 delete m_process; 0072 m_process = nullptr; 0073 } else { 0074 m_gpgRunning = true; 0075 } 0076 } 0077 0078 void Security::readSecretKeys() 0079 { 0080 if (m_gpgRunning) { 0081 QTimer::singleShot(5, this, &Security::readSecretKeys); 0082 return; 0083 } 0084 m_runMode = ListSecret; 0085 m_process = new QProcess(); 0086 QStringList arguments; 0087 arguments << QStringLiteral("--no-secmem-warning") << QStringLiteral("--no-tty") << QStringLiteral("--with-colon") << QStringLiteral("--list-secret-keys"); 0088 connect(m_process, &QProcess::finished, this, &Security::slotFinished); 0089 connect(m_process, &QProcess::readyReadStandardOutput, this, &Security::slotReadyReadStandardOutput); 0090 m_process->start(gpgExecutable(), arguments); 0091 if (!m_process->waitForStarted()) { 0092 delete m_process; 0093 m_process = nullptr; 0094 } else { 0095 m_gpgRunning = true; 0096 } 0097 } 0098 0099 void Security::slotFinished(int exitCode, QProcess::ExitStatus exitStatus) 0100 { 0101 if (exitStatus != QProcess::NormalExit) { 0102 m_gpgRunning = false; 0103 delete m_process; 0104 m_process = nullptr; 0105 return; 0106 } 0107 switch (m_runMode) { 0108 case ListSecret: 0109 m_keysRead = true; 0110 break; 0111 case Verify: 0112 Q_EMIT validityResult(m_result); 0113 break; 0114 case Sign: 0115 Q_EMIT fileSigned(m_result); 0116 break; 0117 } 0118 m_gpgRunning = false; 0119 delete m_process; 0120 m_process = nullptr; 0121 0122 Q_UNUSED(exitCode) 0123 } 0124 0125 void Security::slotReadyReadStandardOutput() 0126 { 0127 QString data; 0128 while (m_process->canReadLine()) { 0129 data = QString::fromLocal8Bit(m_process->readLine()); 0130 switch (m_runMode) { 0131 case List: 0132 case ListSecret: 0133 if (data.startsWith(QLatin1String("pub")) || data.startsWith(QLatin1String("sec"))) { 0134 KeyStruct key; 0135 if (data.startsWith(QLatin1String("pub"))) { 0136 key.secret = false; 0137 } else { 0138 key.secret = true; 0139 } 0140 QStringList line = data.split(QLatin1Char(':'), Qt::KeepEmptyParts); 0141 key.id = line[4]; 0142 QString shortId = key.id.right(8); 0143 QString trustStr = line[1]; 0144 key.trusted = false; 0145 if (trustStr == QLatin1Char('u') || trustStr == QLatin1Char('f')) { 0146 key.trusted = true; 0147 } 0148 data = line[9]; 0149 key.mail = data.section(QLatin1Char('<'), -1, -1); 0150 key.mail.chop(1); 0151 key.name = data.section(QLatin1Char('<'), 0, 0); 0152 if (key.name.contains(QLatin1Char('('))) { 0153 key.name = key.name.section(QLatin1Char('('), 0, 0); 0154 } 0155 m_keys[shortId] = key; 0156 } 0157 break; 0158 case Verify: 0159 data = data.section(QLatin1Char(']'), 1, -1).trimmed(); 0160 if (data.startsWith(QLatin1String("GOODSIG"))) { 0161 m_result &= SIGNED_BAD_CLEAR; 0162 m_result |= SIGNED_OK; 0163 QString id = data.section(QLatin1Char(' '), 1, 1).right(8); 0164 if (!m_keys.contains(id)) { 0165 m_result |= UNKNOWN; 0166 } else { 0167 m_signatureKey = m_keys[id]; 0168 } 0169 } else if (data.startsWith(QLatin1String("NO_PUBKEY"))) { 0170 m_result &= SIGNED_BAD_CLEAR; 0171 m_result |= UNKNOWN; 0172 } else if (data.startsWith(QLatin1String("BADSIG"))) { 0173 m_result |= SIGNED_BAD; 0174 QString id = data.section(QLatin1Char(' '), 1, 1).right(8); 0175 if (!m_keys.contains(id)) { 0176 m_result |= UNKNOWN; 0177 } else { 0178 m_signatureKey = m_keys[id]; 0179 } 0180 } else if (data.startsWith(QLatin1String("TRUST_ULTIMATE"))) { 0181 m_result &= SIGNED_BAD_CLEAR; 0182 m_result |= TRUSTED; 0183 } 0184 break; 0185 0186 case Sign: 0187 if (data.contains(QLatin1String("passphrase.enter"))) { 0188 KeyStruct key = m_keys[m_secretKey]; 0189 Question question(Question::PasswordQuestion); 0190 question.setQuestion( 0191 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)); 0192 if (question.ask() == Question::ContinueResponse) { 0193 m_process->write(question.response().toLocal8Bit() + '\n'); 0194 } else { 0195 m_result |= BAD_PASSPHRASE; 0196 m_process->kill(); 0197 return; 0198 } 0199 } else if (data.contains(QLatin1String("BAD_PASSPHRASE"))) { 0200 m_result |= BAD_PASSPHRASE; 0201 } 0202 break; 0203 } 0204 } 0205 } 0206 0207 void Security::checkValidity(const QString &filename) 0208 { 0209 m_fileName = filename; 0210 slotCheckValidity(); 0211 } 0212 0213 void Security::slotCheckValidity() 0214 { 0215 if (!m_keysRead || m_gpgRunning) { 0216 QTimer::singleShot(5, this, &Security::slotCheckValidity); 0217 return; 0218 } 0219 if (m_keys.isEmpty()) { 0220 Q_EMIT validityResult(-1); 0221 return; 0222 } 0223 0224 m_result = 0; 0225 m_runMode = Verify; 0226 QFileInfo f(m_fileName); 0227 // check the MD5 sum 0228 QString md5sum; 0229 QCryptographicHash context(QCryptographicHash::Md5); 0230 QFile file(m_fileName); 0231 if (!m_fileName.isEmpty() && file.open(QIODevice::ReadOnly)) { 0232 context.reset(); 0233 context.addData(&file); 0234 md5sum = QString::fromLatin1(context.result().toHex()); 0235 file.close(); 0236 } 0237 file.setFileName(f.path() + QStringLiteral("/md5sum")); 0238 if (file.open(QIODevice::ReadOnly)) { 0239 QByteArray md5sum_file; 0240 file.readLine(md5sum_file.data(), 50); 0241 if (!md5sum_file.isEmpty() && QString::fromLatin1(md5sum_file).startsWith(md5sum)) { 0242 m_result |= MD5_OK; 0243 } 0244 file.close(); 0245 } 0246 m_result |= SIGNED_BAD; 0247 m_signatureKey.id = QLatin1String(""); 0248 m_signatureKey.name = QLatin1String(""); 0249 m_signatureKey.mail = QLatin1String(""); 0250 m_signatureKey.trusted = false; 0251 0252 // verify the signature 0253 m_process = new QProcess(); 0254 QStringList arguments; 0255 arguments << QStringLiteral("--no-secmem-warning") << QStringLiteral("--status-fd=2") << QStringLiteral("--command-fd=0") << QStringLiteral("--verify") 0256 << f.path() + QStringLiteral("/signature") << m_fileName; 0257 connect(m_process, &QProcess::finished, this, &Security::slotFinished); 0258 connect(m_process, &QProcess::readyReadStandardOutput, this, &Security::slotReadyReadStandardOutput); 0259 m_process->start(gpgExecutable(), arguments); 0260 if (m_process->waitForStarted()) { 0261 m_gpgRunning = true; 0262 } else { 0263 Q_EMIT signalError( 0264 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 " 0265 "resources will not be possible.</qt>")); 0266 Q_EMIT validityResult(0); 0267 delete m_process; 0268 m_process = nullptr; 0269 } 0270 } 0271 0272 void Security::signFile(const QString &fileName) 0273 { 0274 m_fileName = fileName; 0275 slotSignFile(); 0276 } 0277 0278 void Security::slotSignFile() 0279 { 0280 if (!m_keysRead || m_gpgRunning) { 0281 QTimer::singleShot(5, this, &Security::slotSignFile); 0282 return; 0283 } 0284 0285 QStringList secretKeys; 0286 for (QMap<QString, KeyStruct>::Iterator it = m_keys.begin(); it != m_keys.end(); ++it) { 0287 if (it.value().secret) { 0288 secretKeys.append(it.key()); 0289 } 0290 } 0291 0292 if (secretKeys.isEmpty()) { 0293 Q_EMIT fileSigned(-1); 0294 return; 0295 } 0296 0297 m_result = 0; 0298 QFileInfo f(m_fileName); 0299 0300 // create the MD5 sum 0301 QString md5sum; 0302 QCryptographicHash context(QCryptographicHash::Md5); 0303 QFile file(m_fileName); 0304 if (file.open(QIODevice::ReadOnly)) { 0305 context.reset(); 0306 context.addData(&file); 0307 md5sum = QString::fromLatin1(context.result().toHex()); 0308 file.close(); 0309 } 0310 file.setFileName(f.path() + QStringLiteral("/md5sum")); 0311 if (file.open(QIODevice::WriteOnly)) { 0312 QTextStream stream(&file); 0313 stream << md5sum; 0314 m_result |= MD5_OK; 0315 file.close(); 0316 } 0317 0318 if (secretKeys.count() > 1) { 0319 Question question(Question::SelectFromListQuestion); 0320 question.setQuestion(i18n("Key used for signing:")); 0321 question.setTitle(i18n("Select Signing Key")); 0322 question.setList(secretKeys); 0323 if (question.ask() == Question::OKResponse) { 0324 m_secretKey = question.response(); 0325 } else { 0326 // emit an error to be forwarded to the user for selecting a signing key... 0327 Q_EMIT fileSigned(0); 0328 return; 0329 } 0330 } else { 0331 m_secretKey = secretKeys[0]; 0332 } 0333 0334 // verify the signature 0335 m_process = new QProcess(); 0336 QStringList arguments; 0337 arguments << QStringLiteral("--no-secmem-warning") << QStringLiteral("--status-fd=2") << QStringLiteral("--command-fd=0") << QStringLiteral("--no-tty") 0338 << QStringLiteral("--detach-sign") << QStringLiteral("-u") << m_secretKey << QStringLiteral("-o") << f.path() + QStringLiteral("/signature") 0339 << m_fileName; 0340 connect(m_process, &QProcess::finished, this, &Security::slotFinished); 0341 connect(m_process, &QProcess::readyReadStandardOutput, this, &Security::slotReadyReadStandardOutput); 0342 m_runMode = Sign; 0343 m_process->start(gpgExecutable(), arguments); 0344 if (m_process->waitForStarted()) { 0345 m_gpgRunning = true; 0346 } else { 0347 Q_EMIT signalError( 0348 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 " 0349 "possible.</qt>")); 0350 Q_EMIT fileSigned(0); 0351 delete m_process; 0352 m_process = nullptr; 0353 } 0354 } 0355 0356 #include "moc_security.cpp" 0357 0358 #endif