File indexing completed on 2024-03-24 05:53:34
0001 /* 0002 SPDX-FileCopyrightText: 2012-2022 Rolf Eike Beer <kde@opensource.sf-tec.de> 0003 SPDX-License-Identifier: GPL-2.0-or-later 0004 */ 0005 0006 #include "kgpgverify.h" 0007 0008 #include "gpgproc.h" 0009 #include "core/KGpgKeyNode.h" 0010 #include "model/kgpgitemmodel.h" 0011 0012 #include <QLocale> 0013 #include <QRegularExpression> 0014 0015 #include <KLocalizedString> 0016 0017 KGpgVerify::KGpgVerify(QObject *parent, const QString &text) 0018 : KGpgTextOrFileTransaction(parent, text), 0019 m_fileIndex(-1) 0020 { 0021 } 0022 0023 KGpgVerify::KGpgVerify(QObject *parent, const QList<QUrl> &files) 0024 : KGpgTextOrFileTransaction(parent, files), 0025 m_fileIndex(0) 0026 { 0027 } 0028 0029 QStringList 0030 KGpgVerify::command() const 0031 { 0032 QStringList ret(QLatin1String("--verify")); 0033 0034 return ret; 0035 } 0036 0037 bool 0038 KGpgVerify::nextLine(const QString &line) 0039 { 0040 if (line.startsWith(QLatin1String("[GNUPG:] NO_PUBKEY "))) { 0041 setSuccess(TS_MISSING_KEY); 0042 m_missingId = line.mid(19).simplified(); 0043 return false; 0044 } 0045 0046 if (line.startsWith(QLatin1String("[GNUPG:] ")) && 0047 line.contains(QLatin1String("SIG"))) { 0048 if (line.startsWith(QLatin1String("[GNUPG:] BADSIG"))) 0049 setSuccess(KGpgVerify::TS_BAD_SIGNATURE); 0050 else 0051 setSuccess(KGpgTransaction::TS_OK); 0052 } 0053 0054 return KGpgTextOrFileTransaction::nextLine(line); 0055 } 0056 0057 void 0058 KGpgVerify::finish() 0059 { 0060 // GnuPG will return error code 2 if it wasn't able to verify the file. 0061 // If it complained about a missing signature before that is fine. 0062 if (((getProcess()->exitCode() == 2) && (getSuccess() == TS_MISSING_KEY)) || 0063 ((getProcess()->exitCode() == 1) && (getSuccess() == TS_BAD_SIGNATURE))) 0064 return; 0065 0066 KGpgTextOrFileTransaction::finish(); 0067 } 0068 0069 static QString 0070 sigTimeMessage(const QString &sigtime) 0071 { 0072 QDateTime stamp; 0073 if (sigtime.contains(QLatin1Char('T'))) { 0074 stamp = QDateTime::fromString(sigtime, Qt::ISODate); 0075 } else { 0076 bool ok; 0077 qint64 secs = sigtime.toLongLong(&ok); 0078 if (ok) 0079 stamp = QDateTime::fromMSecsSinceEpoch(secs * 1000); 0080 } 0081 0082 if (!stamp.isValid()) 0083 return QString(); 0084 0085 return xi18nc("@info first argument is formatted date, second argument is formatted time", 0086 "<para>The signature was created at %1 %2</para>", 0087 QLocale().toString(stamp.date(), QLocale::ShortFormat), 0088 QLocale().toString(stamp.time(), QLocale::ShortFormat)); 0089 } 0090 0091 QString 0092 KGpgVerify::getReport(const QStringList &log, const KGpgItemModel *model) 0093 { 0094 QString result; 0095 // newer versions of GnuPG emit both VALIDSIG and GOODSIG 0096 // for a good signature. Since VALIDSIG has more information 0097 // we use that. 0098 const QRegularExpression validsig(QStringLiteral("^\\[GNUPG:\\] VALIDSIG([ ]+[^ ]+){10,}.*$")); 0099 const bool useGoodSig = (model == nullptr) || (log.indexOf(validsig) == -1); 0100 QString sigtime; // timestamp of signature creation 0101 0102 for (const QString &line : log) { 0103 if (!line.startsWith(QLatin1String("[GNUPG:] "))) 0104 continue; 0105 0106 const QString msg = line.mid(9); 0107 0108 if (!useGoodSig && msg.startsWith(QLatin1String("VALIDSIG "))) { 0109 // from GnuPG source, doc/DETAILS: 0110 // VALIDSIG <fingerprint in hex> <sig_creation_date> <sig-timestamp> 0111 // <expire-timestamp> <sig-version> <reserved> <pubkey-algo> 0112 // <hash-algo> <sig-class> <primary-key-fpr> 0113 const QStringList vsig = msg.mid(9).split(QLatin1Char(' '), Qt::SkipEmptyParts); 0114 Q_ASSERT(vsig.count() >= 10); 0115 0116 const KGpgKeyNode *node = model->findKeyNode(vsig[9]); 0117 0118 if (node != nullptr) { 0119 // ignore for now if this is signed with the primary id (vsig[0] == vsig[9]) or not 0120 if (node->getEmail().isEmpty()) 0121 result += xi18nc("@info Good signature from: NAME , Key ID: HEXID", 0122 "<para>Good signature from:<nl/><emphasis strong='true'>%1</emphasis><nl/>Key ID: %2<nl/></para>", 0123 node->getName(), vsig[9]); 0124 else 0125 result += xi18nc("@info Good signature from: NAME <EMAIL>, Key ID: HEXID", 0126 "<para>Good signature from:<nl/><emphasis strong='true'>%1 <email>%2</email></emphasis><nl/>Key ID: %3</para>", 0127 node->getName(), node->getEmail(), vsig[9]); 0128 0129 result += sigTimeMessage(vsig[2]); 0130 } else { 0131 // this should normally never happen, but one could delete 0132 // the key just after the verification. Brute force solution: 0133 // do the whole report generation again, but this time make 0134 // sure GOODSIG is used. 0135 return getReport(log, nullptr); 0136 } 0137 } else if (msg.startsWith(QLatin1String("UNEXPECTED")) || 0138 msg.startsWith(QLatin1String("NODATA"))) { 0139 result += xi18nc("@info", "No signature found.") + QLatin1Char('\n'); 0140 } else if (useGoodSig && msg.startsWith(QLatin1String("GOODSIG "))) { 0141 int sigpos = msg.indexOf( QLatin1Char(' ') , 8); 0142 const QString keyid = msg.mid(8, sigpos - 8); 0143 0144 // split the name/email pair to give translators more power to handle this 0145 QString email; 0146 QString name = msg.mid(sigpos + 1); 0147 0148 int oPos = name.indexOf(QLatin1Char('<')); 0149 int cPos = name.indexOf(QLatin1Char('>')); 0150 if ((oPos >= 0) && (cPos >= 0)) { 0151 email = name.mid(oPos + 1, cPos - oPos - 1); 0152 name = name.left(oPos).simplified(); 0153 } 0154 0155 if (email.isEmpty()) 0156 result += xi18nc("@info", "<para>Good signature from:<nl/><emphasis strong='true'>%1</emphasis><nl/>Key ID: %2<nl/></para>", 0157 name, keyid); 0158 else 0159 result += xi18nc("@info Good signature from: NAME <EMAIL>, Key ID: HEXID", 0160 "<para>Good signature from:<nl/><emphasis strong='true'>%1 <email>%2</email></emphasis><nl/>Key ID: %3<nl/></para>", 0161 name, email, keyid); 0162 if (!sigtime.isEmpty()) { 0163 result += sigTimeMessage(sigtime); 0164 sigtime.clear(); 0165 } 0166 } else if (msg.startsWith(QLatin1String("SIG_ID "))) { 0167 const QStringList parts = msg.simplified().split(QLatin1Char(' ')); 0168 if (parts.count() > 2) 0169 sigtime = parts[2]; 0170 } else if (msg.startsWith(QLatin1String("BADSIG"))) { 0171 int sigpos = msg.indexOf( QLatin1Char(' '), 7); 0172 result += xi18nc("@info", "<para><emphasis strong='true'>BAD signature</emphasis> from:<nl/> %1<nl/>Key ID: %2<nl/><nl/><emphasis strong='true'>The file is corrupted</emphasis></para>", 0173 msg.mid(sigpos + 1), msg.mid(7, sigpos - 7)); 0174 } else if (msg.startsWith(QLatin1String("TRUST_UNDEFINED"))) { 0175 result += xi18nc("@info", "<para>The signature is valid, but the key is untrusted</para>"); 0176 } else if (msg.startsWith(QLatin1String("TRUST_ULTIMATE"))) { 0177 result += xi18nc("@info", "<para>The signature is valid, and the key is ultimately trusted</para>"); 0178 } 0179 } 0180 0181 return result; 0182 } 0183 0184 QString 0185 KGpgVerify::missingId() const 0186 { 0187 return m_missingId; 0188 }