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 using namespace KNSCore;
27 
28 static QString gpgExecutable()
29 {
30  QString gpgExe = QStandardPaths::findExecutable(QStringLiteral("gpg"));
31  if (gpgExe.isEmpty()) {
32  gpgExe = QStandardPaths::findExecutable(QStringLiteral("gpg2"));
33  }
34  if (gpgExe.isEmpty()) {
35  return QStringLiteral("gpg");
36  }
37  return gpgExe;
38 }
39 
40 Security::Security()
41 {
42  m_keysRead = false;
43  m_gpgRunning = false;
44  readKeys();
46 }
47 
48 Security::~Security()
49 {
50 }
51 
53 {
54  if (m_gpgRunning) {
55  QTimer::singleShot(5, this, SLOT(readKeys()));
56  return;
57  }
58  m_runMode = List;
59  m_keys.clear();
60  m_process = new QProcess();
61  QStringList arguments;
62  arguments << QStringLiteral("--no-secmem-warning")
63  << QStringLiteral("--no-tty")
64  << QStringLiteral("--with-colon")
65  << QStringLiteral("--list-keys");
66  connect(m_process, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
67  this, &Security::slotFinished);
69  this, &Security::slotReadyReadStandardOutput);
70  m_process->start(gpgExecutable(), arguments);
71  if (!m_process->waitForStarted()) {
72  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>"));
73  delete m_process;
74  m_process = nullptr;
75  } else {
76  m_gpgRunning = true;
77  }
78 }
79 
81 {
82  if (m_gpgRunning) {
83  QTimer::singleShot(5, this, SLOT(readSecretKeys()));
84  return;
85  }
86  m_runMode = ListSecret;
87  m_process = new QProcess();
88  QStringList arguments;
89  arguments << QStringLiteral("--no-secmem-warning")
90  << QStringLiteral("--no-tty")
91  << QStringLiteral("--with-colon")
92  << QStringLiteral("--list-secret-keys");
93  connect(m_process, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
94  this, &Security::slotFinished);
96  this, &Security::slotReadyReadStandardOutput);
97  m_process->start(gpgExecutable(), arguments);
98  if (!m_process->waitForStarted()) {
99  delete m_process;
100  m_process = nullptr;
101  } else {
102  m_gpgRunning = true;
103  }
104 }
105 
106 void Security::slotFinished(int exitCode, QProcess::ExitStatus exitStatus)
107 {
108  if (exitStatus != QProcess::NormalExit) {
109  m_gpgRunning = false;
110  delete m_process;
111  m_process = nullptr;
112  return;
113  }
114  switch (m_runMode) {
115  case ListSecret:
116  m_keysRead = true;
117  break;
118  case Verify: emit validityResult(m_result);
119  break;
120  case Sign: emit fileSigned(m_result);
121  break;
122 
123  }
124  m_gpgRunning = false;
125  delete m_process;
126  m_process = nullptr;
127 
128  Q_UNUSED(exitCode)
129 }
130 
131 void Security::slotReadyReadStandardOutput()
132 {
133  QString data;
134  while (m_process->canReadLine()) {
135  data = QString::fromLocal8Bit(m_process->readLine());
136  switch (m_runMode) {
137  case List:
138  case ListSecret:
139  if (data.startsWith(QLatin1String("pub")) || data.startsWith(QLatin1String("sec"))) {
140  KeyStruct key;
141  if (data.startsWith(QLatin1String("pub"))) {
142  key.secret = false;
143  } else {
144  key.secret = true;
145  }
146 #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
148 #else
149  QStringList line = data.split(QLatin1Char(':'), Qt::KeepEmptyParts);
150 #endif
151  key.id = line[4];
152  QString shortId = key.id.right(8);
153  QString trustStr = line[1];
154  key.trusted = false;
155  if (trustStr == QLatin1Char('u') || trustStr == QLatin1Char('f')) {
156  key.trusted = true;
157  }
158  data = line[9];
159  key.mail = data.section(QLatin1Char('<'), -1, -1);
160  key.mail.chop(1);
161  key.name = data.section(QLatin1Char('<'), 0, 0);
162  if (key.name.contains(QLatin1Char('('))) {
163  key.name = key.name.section(QLatin1Char('('), 0, 0);
164  }
165  m_keys[shortId] = key;
166  }
167  break;
168  case Verify:
169  data = data.section(QLatin1Char(']'), 1, -1).trimmed();
170  if (data.startsWith(QLatin1String("GOODSIG"))) {
171  m_result &= SIGNED_BAD_CLEAR;
172  m_result |= SIGNED_OK;
173  QString id = data.section(QLatin1Char(' '), 1, 1).right(8);
174  if (!m_keys.contains(id)) {
175  m_result |= UNKNOWN;
176  } else {
177  m_signatureKey = m_keys[id];
178  }
179  } else if (data.startsWith(QLatin1String("NO_PUBKEY"))) {
180  m_result &= SIGNED_BAD_CLEAR;
181  m_result |= UNKNOWN;
182  } else if (data.startsWith(QLatin1String("BADSIG"))) {
183  m_result |= SIGNED_BAD;
184  QString id = data.section(QLatin1Char(' '), 1, 1).right(8);
185  if (!m_keys.contains(id)) {
186  m_result |= UNKNOWN;
187  } else {
188  m_signatureKey = m_keys[id];
189  }
190  } else if (data.startsWith(QLatin1String("TRUST_ULTIMATE"))) {
191  m_result &= SIGNED_BAD_CLEAR;
192  m_result |= TRUSTED;
193  }
194  break;
195 
196  case Sign:
197  if (data.contains(QLatin1String("passphrase.enter"))) {
198  KeyStruct key = m_keys[m_secretKey];
199  Question question(Question::PasswordQuestion);
200  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));
201  if(question.ask() == Question::ContinueResponse) {
202  m_process->write(question.response().toLocal8Bit() + '\n');
203  } else {
204  m_result |= BAD_PASSPHRASE;
205  m_process->kill();
206  return;
207  }
208  } else if (data.contains(QLatin1String("BAD_PASSPHRASE"))) {
209  m_result |= BAD_PASSPHRASE;
210  }
211  break;
212  }
213  }
214 }
215 
216 void Security::checkValidity(const QString &filename)
217 {
218  m_fileName = filename;
220 }
221 
223 {
224  if (!m_keysRead || m_gpgRunning) {
225  QTimer::singleShot(5, this, SLOT(slotCheckValidity()));
226  return;
227  }
228  if (m_keys.isEmpty()) {
229  emit validityResult(-1);
230  return;
231  }
232 
233  m_result = 0;
234  m_runMode = Verify;
235  QFileInfo f(m_fileName);
236  //check the MD5 sum
237  QString md5sum;
239  QFile file(m_fileName);
240  if (!m_fileName.isEmpty() && file.open(QIODevice::ReadOnly)) {
241  context.reset();
242  context.addData(&file);
243  md5sum = QString::fromLatin1(context.result().toHex());
244  file.close();
245  }
246  file.setFileName(f.path() + QStringLiteral("/md5sum"));
247  if (file.open(QIODevice::ReadOnly)) {
248  QByteArray md5sum_file;
249  file.readLine(md5sum_file.data(), 50);
250  if (!md5sum_file.isEmpty() && QString::fromLatin1(md5sum_file).startsWith(md5sum)) {
251  m_result |= MD5_OK;
252  }
253  file.close();
254  }
255  m_result |= SIGNED_BAD;
256  m_signatureKey.id = QLatin1String("");
257  m_signatureKey.name = QLatin1String("");
258  m_signatureKey.mail = QLatin1String("");
259  m_signatureKey.trusted = false;
260 
261  //verify the signature
262  m_process = new QProcess();
263  QStringList arguments;
264  arguments << QStringLiteral("--no-secmem-warning")
265  << QStringLiteral("--status-fd=2")
266  << QStringLiteral("--command-fd=0")
267  << QStringLiteral("--verify")
268  << f.path() + QStringLiteral("/signature")
269  << m_fileName;
270  connect(m_process, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
271  this, &Security::slotFinished);
273  this, &Security::slotReadyReadStandardOutput);
274  m_process->start(gpgExecutable(), arguments);
275  if (m_process->waitForStarted()) {
276  m_gpgRunning = true;
277  } else {
278  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  emit validityResult(0);
280  delete m_process;
281  m_process = nullptr;
282  }
283 }
284 
285 void Security::signFile(const QString &fileName)
286 {
287  m_fileName = fileName;
288  slotSignFile();
289 }
290 
292 {
293  if (!m_keysRead || m_gpgRunning) {
294  QTimer::singleShot(5, this, SLOT(slotSignFile()));
295  return;
296  }
297 
298  QStringList secretKeys;
299  for (QMap<QString, KeyStruct>::Iterator it = m_keys.begin(); it != m_keys.end(); ++it) {
300  if (it.value().secret) {
301  secretKeys.append(it.key());
302  }
303  }
304 
305  if (secretKeys.isEmpty()) {
306  emit fileSigned(-1);
307  return;
308  }
309 
310  m_result = 0;
311  QFileInfo f(m_fileName);
312 
313  //create the MD5 sum
314  QString md5sum;
316  QFile file(m_fileName);
317  if (file.open(QIODevice::ReadOnly)) {
318  context.reset();
319  context.addData(&file);
320  md5sum = QString::fromLatin1(context.result().toHex());
321  file.close();
322  }
323  file.setFileName(f.path() + QStringLiteral("/md5sum"));
324  if (file.open(QIODevice::WriteOnly)) {
325  QTextStream stream(&file);
326  stream << md5sum;
327  m_result |= MD5_OK;
328  file.close();
329  }
330 
331  if (secretKeys.count() > 1) {
332  Question question(Question::SelectFromListQuestion);
333  question.setQuestion(i18n("Key used for signing:"));
334  question.setTitle(i18n("Select Signing Key"));
335  question.setList(secretKeys);
336  if(question.ask() == Question::OKResponse) {
337  m_secretKey = question.response();
338  } else {
339  // emit an error to be forwarded to the user for selecting a signing key...
340  emit fileSigned(0);
341  return;
342  }
343  } else {
344  m_secretKey = secretKeys[0];
345  }
346 
347  //verify the signature
348  m_process = new QProcess();
349  QStringList arguments;
350  arguments << QStringLiteral("--no-secmem-warning")
351  << QStringLiteral("--status-fd=2")
352  << QStringLiteral("--command-fd=0")
353  << QStringLiteral("--no-tty")
354  << QStringLiteral("--detach-sign")
355  << QStringLiteral("-u")
356  << m_secretKey
357  << QStringLiteral("-o")
358  << f.path() + QStringLiteral("/signature")
359  << m_fileName;
360  connect(m_process, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
361  this, &Security::slotFinished);
363  this, &Security::slotReadyReadStandardOutput);
364  m_runMode = Sign;
365  m_process->start(gpgExecutable(), arguments);
366  if (m_process->waitForStarted()) {
367  m_gpgRunning = true;
368  } else {
369  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>"));
370  emit fileSigned(0);
371  delete m_process;
372  m_process = nullptr;
373  }
374 }
375 
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:80
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:52
void clear()
void chop(int n)
void slotCheckValidity()
Verifies the integrity and the signature of a tarball file (see m_fileName).
Definition: security.cpp:222
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:285
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:291
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:216
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)
qint64 readLine(char *data, qint64 maxSize)
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Mon Aug 10 2020 22:44:46 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.