KNewStuff

security.cpp
1 /*
2  This file is part of KNewStuff2.
3  SPDX-FileCopyrightText: 2004, 2005 Andras Mantia <[email protected]>
4  SPDX-FileCopyrightText: 2007 Josef Spillner <[email protected]>
5 
6  SPDX-License-Identifier: LGPL-2.1-or-later
7 */
8 
9 //app includes
10 #include "security.h"
11 #include "question.h"
12 
13 //qt includes
14 #include <QFile>
15 #include <QFileInfo>
16 #include <QStringList>
17 #include <QTextStream>
18 #include <QTimer>
19 #include <qstandardpaths.h>
20 
21 #include <QCryptographicHash>
22 
23 //kde includes
24 #include <KLocalizedString>
25 
26 #include "knewstuffcore_export.h"
27 
28 using namespace KNSCore;
29 
30 #if KNEWSTUFFCORE_BUILD_DEPRECATED_SINCE(5, 31)
31 static QString gpgExecutable()
32 {
33  QString gpgExe = QStandardPaths::findExecutable(QStringLiteral("gpg"));
34  if (gpgExe.isEmpty()) {
35  gpgExe = QStandardPaths::findExecutable(QStringLiteral("gpg2"));
36  }
37  if (gpgExe.isEmpty()) {
38  return QStringLiteral("gpg");
39  }
40  return gpgExe;
41 }
42 
43 Security::Security()
44 {
45  m_keysRead = false;
46  m_gpgRunning = false;
47  readKeys();
49 }
50 
51 Security::~Security()
52 {
53 }
54 
56 {
57  if (m_gpgRunning) {
59  return;
60  }
61  m_runMode = List;
62  m_keys.clear();
63  m_process = new QProcess();
64  QStringList arguments;
65  arguments << QStringLiteral("--no-secmem-warning")
66  << QStringLiteral("--no-tty")
67  << QStringLiteral("--with-colon")
68  << QStringLiteral("--list-keys");
69  connect(m_process, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
70  this, &Security::slotFinished);
72  this, &Security::slotReadyReadStandardOutput);
73  m_process->start(gpgExecutable(), arguments);
74  if (!m_process->waitForStarted()) {
75  Q_EMIT signalError(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>"));
76  delete m_process;
77  m_process = nullptr;
78  } else {
79  m_gpgRunning = true;
80  }
81 }
82 
84 {
85  if (m_gpgRunning) {
87  return;
88  }
89  m_runMode = ListSecret;
90  m_process = new QProcess();
91  QStringList arguments;
92  arguments << QStringLiteral("--no-secmem-warning")
93  << QStringLiteral("--no-tty")
94  << QStringLiteral("--with-colon")
95  << QStringLiteral("--list-secret-keys");
96  connect(m_process, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
97  this, &Security::slotFinished);
99  this, &Security::slotReadyReadStandardOutput);
100  m_process->start(gpgExecutable(), arguments);
101  if (!m_process->waitForStarted()) {
102  delete m_process;
103  m_process = nullptr;
104  } else {
105  m_gpgRunning = true;
106  }
107 }
108 
109 void Security::slotFinished(int exitCode, QProcess::ExitStatus exitStatus)
110 {
111  if (exitStatus != QProcess::NormalExit) {
112  m_gpgRunning = false;
113  delete m_process;
114  m_process = nullptr;
115  return;
116  }
117  switch (m_runMode) {
118  case ListSecret:
119  m_keysRead = true;
120  break;
121  case Verify: Q_EMIT validityResult(m_result);
122  break;
123  case Sign: Q_EMIT fileSigned(m_result);
124  break;
125 
126  }
127  m_gpgRunning = false;
128  delete m_process;
129  m_process = nullptr;
130 
131  Q_UNUSED(exitCode)
132 }
133 
134 void Security::slotReadyReadStandardOutput()
135 {
136  QString data;
137  while (m_process->canReadLine()) {
138  data = QString::fromLocal8Bit(m_process->readLine());
139  switch (m_runMode) {
140  case List:
141  case ListSecret:
142  if (data.startsWith(QLatin1String("pub")) || data.startsWith(QLatin1String("sec"))) {
143  KeyStruct key;
144  if (data.startsWith(QLatin1String("pub"))) {
145  key.secret = false;
146  } else {
147  key.secret = true;
148  }
149  QStringList line = data.split(QLatin1Char(':'), Qt::KeepEmptyParts);
150  key.id = line[4];
151  QString shortId = key.id.right(8);
152  QString trustStr = line[1];
153  key.trusted = false;
154  if (trustStr == QLatin1Char('u') || trustStr == QLatin1Char('f')) {
155  key.trusted = true;
156  }
157  data = line[9];
158  key.mail = data.section(QLatin1Char('<'), -1, -1);
159  key.mail.chop(1);
160  key.name = data.section(QLatin1Char('<'), 0, 0);
161  if (key.name.contains(QLatin1Char('('))) {
162  key.name = key.name.section(QLatin1Char('('), 0, 0);
163  }
164  m_keys[shortId] = key;
165  }
166  break;
167  case Verify:
168  data = data.section(QLatin1Char(']'), 1, -1).trimmed();
169  if (data.startsWith(QLatin1String("GOODSIG"))) {
170  m_result &= SIGNED_BAD_CLEAR;
171  m_result |= SIGNED_OK;
172  QString id = data.section(QLatin1Char(' '), 1, 1).right(8);
173  if (!m_keys.contains(id)) {
174  m_result |= UNKNOWN;
175  } else {
176  m_signatureKey = m_keys[id];
177  }
178  } else if (data.startsWith(QLatin1String("NO_PUBKEY"))) {
179  m_result &= SIGNED_BAD_CLEAR;
180  m_result |= UNKNOWN;
181  } else if (data.startsWith(QLatin1String("BADSIG"))) {
182  m_result |= SIGNED_BAD;
183  QString id = data.section(QLatin1Char(' '), 1, 1).right(8);
184  if (!m_keys.contains(id)) {
185  m_result |= UNKNOWN;
186  } else {
187  m_signatureKey = m_keys[id];
188  }
189  } else if (data.startsWith(QLatin1String("TRUST_ULTIMATE"))) {
190  m_result &= SIGNED_BAD_CLEAR;
191  m_result |= TRUSTED;
192  }
193  break;
194 
195  case Sign:
196  if (data.contains(QLatin1String("passphrase.enter"))) {
197  KeyStruct key = m_keys[m_secretKey];
198  Question question(Question::PasswordQuestion);
199  question.setQuestion(i18n("<qt>Enter passphrase for key <b>0x%1</b>, belonging to<br /><i>%2&lt;%3&gt;</i><br />:</qt>", m_secretKey, key.name, key.mail));
200  if(question.ask() == Question::ContinueResponse) {
201  m_process->write(question.response().toLocal8Bit() + '\n');
202  } else {
203  m_result |= BAD_PASSPHRASE;
204  m_process->kill();
205  return;
206  }
207  } else if (data.contains(QLatin1String("BAD_PASSPHRASE"))) {
208  m_result |= BAD_PASSPHRASE;
209  }
210  break;
211  }
212  }
213 }
214 
215 void Security::checkValidity(const QString &filename)
216 {
217  m_fileName = filename;
219 }
220 
222 {
223  if (!m_keysRead || m_gpgRunning) {
225  return;
226  }
227  if (m_keys.isEmpty()) {
229  return;
230  }
231 
232  m_result = 0;
233  m_runMode = Verify;
234  QFileInfo f(m_fileName);
235  //check the MD5 sum
236  QString md5sum;
238  QFile file(m_fileName);
239  if (!m_fileName.isEmpty() && file.open(QIODevice::ReadOnly)) {
240  context.reset();
241  context.addData(&file);
242  md5sum = QString::fromLatin1(context.result().toHex());
243  file.close();
244  }
245  file.setFileName(f.path() + QStringLiteral("/md5sum"));
246  if (file.open(QIODevice::ReadOnly)) {
247  QByteArray md5sum_file;
248  file.readLine(md5sum_file.data(), 50);
249  if (!md5sum_file.isEmpty() && QString::fromLatin1(md5sum_file).startsWith(md5sum)) {
250  m_result |= MD5_OK;
251  }
252  file.close();
253  }
254  m_result |= SIGNED_BAD;
255  m_signatureKey.id = QLatin1String("");
256  m_signatureKey.name = QLatin1String("");
257  m_signatureKey.mail = QLatin1String("");
258  m_signatureKey.trusted = false;
259 
260  //verify the signature
261  m_process = new QProcess();
262  QStringList arguments;
263  arguments << QStringLiteral("--no-secmem-warning")
264  << QStringLiteral("--status-fd=2")
265  << QStringLiteral("--command-fd=0")
266  << QStringLiteral("--verify")
267  << f.path() + QStringLiteral("/signature")
268  << m_fileName;
269  connect(m_process, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
270  this, &Security::slotFinished);
272  this, &Security::slotReadyReadStandardOutput);
273  m_process->start(gpgExecutable(), arguments);
274  if (m_process->waitForStarted()) {
275  m_gpgRunning = true;
276  } else {
277  Q_EMIT signalError(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>"));
279  delete m_process;
280  m_process = nullptr;
281  }
282 }
283 
284 void Security::signFile(const QString &fileName)
285 {
286  m_fileName = fileName;
287  slotSignFile();
288 }
289 
291 {
292  if (!m_keysRead || m_gpgRunning) {
294  return;
295  }
296 
297  QStringList secretKeys;
298  for (QMap<QString, KeyStruct>::Iterator it = m_keys.begin(); it != m_keys.end(); ++it) {
299  if (it.value().secret) {
300  secretKeys.append(it.key());
301  }
302  }
303 
304  if (secretKeys.isEmpty()) {
305  Q_EMIT fileSigned(-1);
306  return;
307  }
308 
309  m_result = 0;
310  QFileInfo f(m_fileName);
311 
312  //create the MD5 sum
313  QString md5sum;
315  QFile file(m_fileName);
316  if (file.open(QIODevice::ReadOnly)) {
317  context.reset();
318  context.addData(&file);
319  md5sum = QString::fromLatin1(context.result().toHex());
320  file.close();
321  }
322  file.setFileName(f.path() + QStringLiteral("/md5sum"));
323  if (file.open(QIODevice::WriteOnly)) {
324  QTextStream stream(&file);
325  stream << md5sum;
326  m_result |= MD5_OK;
327  file.close();
328  }
329 
330  if (secretKeys.count() > 1) {
331  Question question(Question::SelectFromListQuestion);
332  question.setQuestion(i18n("Key used for signing:"));
333  question.setTitle(i18n("Select Signing Key"));
334  question.setList(secretKeys);
335  if(question.ask() == Question::OKResponse) {
336  m_secretKey = question.response();
337  } else {
338  // emit an error to be forwarded to the user for selecting a signing key...
339  Q_EMIT fileSigned(0);
340  return;
341  }
342  } else {
343  m_secretKey = secretKeys[0];
344  }
345 
346  //verify the signature
347  m_process = new QProcess();
348  QStringList arguments;
349  arguments << QStringLiteral("--no-secmem-warning")
350  << QStringLiteral("--status-fd=2")
351  << QStringLiteral("--command-fd=0")
352  << QStringLiteral("--no-tty")
353  << QStringLiteral("--detach-sign")
354  << QStringLiteral("-u")
355  << m_secretKey
356  << QStringLiteral("-o")
357  << f.path() + QStringLiteral("/signature")
358  << m_fileName;
359  connect(m_process, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
360  this, &Security::slotFinished);
362  this, &Security::slotReadyReadStandardOutput);
363  m_runMode = Sign;
364  m_process->start(gpgExecutable(), arguments);
365  if (m_process->waitForStarted()) {
366  m_gpgRunning = true;
367  } else {
368  Q_EMIT signalError(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>"));
369  Q_EMIT fileSigned(0);
370  delete m_process;
371  m_process = nullptr;
372  }
373 }
374 #endif
A way to ask a user a question from inside a GUI-less library (like KNewStuffCore) ...
Definition: question.h:41
void kill()
QString path() const const
bool contains(const Key &key) const const
QByteArray toHex() const const
The file is signed with a bad signature.
Definition: security.h:82
void readSecretKeys()
Reads the available secret keys.
Definition: security.cpp:83
Contains the core functionality for handling interaction with NewStuff providers. ...
bool isEmpty() const const
void validityResult(int result)
Sent when the validity check is done.
void setFileName(const QString &name)
The key is unknown.
Definition: security.h:84
QString findExecutable(const QString &executableName, const QStringList &paths)
void readyReadStandardOutput()
void readKeys()
Reads the available public keys.
Definition: security.cpp:55
void clear()
void chop(int n)
void slotCheckValidity()
Verifies the integrity and the signature of a tarball file (see m_fileName).
Definition: security.cpp:221
int count(const T &value) const const
QString fromLocal8Bit(const char *str, int size)
void append(const T &value)
bool isEmpty() const const
bool isEmpty() const const
QString trimmed() const const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
void finished(int exitCode)
QStringList split(const QString &sep, QString::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
void addData(const char *data, int length)
void signFile(const QString &fileName)
Creates a signature and an md5sum file for the fileName and packs everything into a gzipped tarball...
Definition: security.cpp:284
QMap::iterator end()
virtual bool open(QIODevice::OpenMode mode) override
QMap::iterator begin()
QString right(int n) const const
QByteArray toLocal8Bit() const const
The MD5 sum check is OK.
Definition: security.h:80
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
KeepEmptyParts
void fileSigned(int result)
Sent when the signing is done.
bool waitForStarted(int msecs)
void slotSignFile()
Creates a signature and an md5sum file for the m_fileName and packs everything into a gzipped tarball...
Definition: security.cpp:290
The file is signed with a good signature.
Definition: security.h:81
QString i18n(const char *text, const TYPE &arg...)
void checkValidity(const QString &fileName)
Verifies the integrity and the signature of a tarball file.
Definition: security.cpp:215
virtual void close() override
virtual bool canReadLine() const const override
char * data()
used to clear the SIGNED_BAD flag
Definition: security.h:85
QString section(QChar sep, int start, int end, QString::SectionFlags flags) const const
QByteArray result() const const
qint64 write(const char *data, qint64 maxSize)
QString fromLatin1(const char *str, int size)
bool isEmpty() const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
The signature is trusted.
Definition: security.h:83
void start(const QString &program, const QStringList &arguments, QIODevice::OpenMode mode)
Q_EMITQ_EMIT
qint64 readLine(char *data, qint64 maxSize)
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Mon Jan 18 2021 22:43:50 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.