File indexing completed on 2024-03-24 05:51:25
0001 /* 0002 SPDX-FileCopyrightText: 2019 Farid Boudedja <farid.boudedja@gmail.com> 0003 0004 SPDX-License-Identifier: GPL-3.0-or-later 0005 */ 0006 0007 #include "isoverifier.h" 0008 0009 #include <QFile> 0010 #include <QDebug> 0011 #include <QFileInfo> 0012 #include <QByteArray> 0013 #include <QStandardPaths> 0014 #include <QCryptographicHash> 0015 #include <QRegularExpression> 0016 #include <QSignalSpy> 0017 0018 #ifdef _USE_GPG 0019 #include <QGpgME/Protocol> 0020 #include <QGpgME/VerifyDetachedJob> 0021 #include <QGpgME/ImportJob> 0022 0023 #include <gpgme++/verificationresult.h> 0024 #include <gpgme++/importresult.h> 0025 #endif 0026 0027 #include <KLocalizedString> 0028 0029 IsoVerifier::IsoVerifier(const QString &filePath) 0030 : m_filePath(filePath), 0031 m_error(), 0032 m_verificationMean(VerificationMean::None) 0033 { 0034 qRegisterMetaType<VerifyResult>(); 0035 } 0036 0037 void IsoVerifier::verifyIso() 0038 { 0039 QFileInfo fileInfo(m_filePath); 0040 QString fileName = fileInfo.fileName(); 0041 QString keyFingerprint; 0042 0043 if (fileName.startsWith("neon-") 0044 && importSigningKey("neon-signing-key.gpg", keyFingerprint)) { 0045 m_verificationMean = VerificationMean::DotSigFile; 0046 } else if (fileName.startsWith("archlinux-") 0047 && importSigningKey("arch-signing-key.gpg", keyFingerprint)) { 0048 m_verificationMean = VerificationMean::DotSigFile; 0049 } else if (fileName.startsWith("kubuntu-") 0050 && importSigningKey("ubuntu-signing-key.gpg", keyFingerprint)) { 0051 m_verificationMean = VerificationMean::Sha256SumsFile; 0052 } else if (fileName.startsWith("ubuntu-") 0053 && importSigningKey("ubuntu-signing-key.gpg", keyFingerprint)) { 0054 m_verificationMean = VerificationMean::Sha256SumsFile; 0055 } else if (fileName.startsWith("netrunner-")) { 0056 m_verificationMean = VerificationMean::Sha256SumInput; 0057 } else { 0058 m_error = i18n("Could not verify as a known distro image."); 0059 m_isIsoValid = VerifyResult::KeyNotFound; 0060 } 0061 0062 switch (m_verificationMean) { 0063 case VerificationMean::DotSigFile: 0064 verifyWithDotSigFile(keyFingerprint); 0065 break; 0066 case VerificationMean::Sha256SumsFile: 0067 verifyWithSha256SumsFile(keyFingerprint); 0068 break; 0069 case VerificationMean::Sha256SumInput: 0070 emit inputRequested(i18n("SHA256 Checksum"), 0071 i18n("Paste the SHA256 checksum for this ISO:")); 0072 break; 0073 default: 0074 emit finished(m_isIsoValid, m_error); 0075 break; 0076 } 0077 } 0078 0079 void IsoVerifier::verifyWithInputText(bool ok, const QString &text) 0080 { 0081 switch (m_verificationMean) { 0082 case VerificationMean::Sha256SumInput: 0083 verifyWithSha256Sum(ok, text); 0084 break; 0085 default: 0086 emit finished(m_isIsoValid, m_error); 0087 break; 0088 } 0089 } 0090 0091 bool IsoVerifier::importSigningKey(const QString &fileName, QString &keyFingerprint) 0092 { 0093 QString signingKeyFile = QStandardPaths::locate(QStandardPaths::AppDataLocation, fileName); 0094 if (signingKeyFile.isEmpty()) { 0095 qDebug() << "error can't find signing key" << signingKeyFile; 0096 return false; 0097 } 0098 0099 QFile signingKey(signingKeyFile); 0100 if (!signingKey.open(QIODevice::ReadOnly)) { 0101 return false; 0102 } 0103 QByteArray signingKeyData = signingKey.readAll(); 0104 0105 #ifdef _USE_GPG 0106 QGpgME::ImportJob *importJob = QGpgME::openpgp()->importJob(); 0107 GpgME::ImportResult importResult = importJob->exec(signingKeyData); 0108 0109 if (!(importResult.numConsidered() == 1 0110 && (importResult.numImported() == 1 0111 || importResult.numUnchanged() == 1))) { 0112 qDebug() << "Could not import gpg signature"; 0113 return false; 0114 } 0115 0116 keyFingerprint = QString(importResult.import(0).fingerprint()); 0117 0118 return true; 0119 #endif 0120 return false; 0121 } 0122 0123 void IsoVerifier::verifyWithDotSigFile(const QString &keyFingerprint) 0124 { 0125 QString sigFilePath = m_filePath + ".sig"; 0126 QFileInfo fileInfo(sigFilePath); 0127 QString sigFileName = fileInfo.fileName(); 0128 if (!QFile::exists(sigFilePath)) { 0129 m_error = i18n("Could not find %1, please download PGP signature file " 0130 "to same directory.", sigFileName); 0131 emit finished(m_isIsoValid, m_error); return; 0132 } 0133 0134 auto signatureFile = std::shared_ptr<QIODevice>(new QFile(sigFilePath)); 0135 if (!signatureFile->open(QIODevice::ReadOnly)) { 0136 m_error = i18n("Could not open signature file"); 0137 emit finished(m_isIsoValid, m_error); return; 0138 } 0139 0140 auto isoFile = std::shared_ptr<QIODevice>(new QFile(m_filePath)); 0141 if (!isoFile->open(QIODevice::ReadOnly)) { 0142 m_error = i18n("Could not open ISO image"); 0143 emit finished(m_isIsoValid, m_error); return; 0144 } 0145 0146 0147 #ifdef _USE_GPG 0148 QGpgME::VerifyDetachedJob *job = QGpgME::openpgp()->verifyDetachedJob(); 0149 connect(job, &QGpgME::VerifyDetachedJob::result, this, [this](GpgME::VerificationResult result) 0150 { 0151 GpgME::Signature signature = result.signature(0); 0152 this->summaryResult = signature.summary(); 0153 Q_EMIT asyncDone(); 0154 }); 0155 job->start(signatureFile, isoFile); 0156 QSignalSpy spy(this, SIGNAL(asyncDone())); 0157 spy.wait(20000); // Set a long timeout as it can take time to read the whole ISO file 0158 0159 if (summaryResult & GpgME::Signature::Valid) { 0160 m_isIsoValid = VerifyResult::Successful; 0161 } else if (summaryResult & GpgME::Signature::KeyRevoked) { 0162 m_error = i18n("Key is revoked."); 0163 m_isIsoValid = VerifyResult::Failed; 0164 } else { 0165 m_error = i18n("Uses wrong signature."); 0166 m_isIsoValid = VerifyResult::Failed; 0167 } 0168 #else 0169 m_error = i18n("This app is built without verification support."); 0170 m_isIsoValid = VerifyResult::KeyNotFound; 0171 #endif 0172 0173 emit finished(m_isIsoValid, m_error); 0174 } 0175 0176 void IsoVerifier::verifyWithSha256SumsFile(const QString &keyFingerprint) 0177 { 0178 QFileInfo fileInfo(m_filePath); 0179 QFile checksumsFile(fileInfo.absolutePath() + "/SHA256SUMS"); 0180 if (!checksumsFile.open(QIODevice::ReadOnly | QIODevice::Text)) { 0181 m_error = i18n("Could not open SHA256SUMS file, please download to same directory"); 0182 emit finished(m_isIsoValid, m_error); return; 0183 } 0184 0185 // Extract checksum from the SHA256SUMS file 0186 QString checksum; 0187 QRegularExpression rx("([abcdef\\d]+).." + fileInfo.fileName()); 0188 QByteArray checksumsData = checksumsFile.readAll(); 0189 0190 QRegularExpressionMatch match = rx.match(checksumsData); 0191 0192 if (match.hasMatch()) { 0193 checksum = match.captured(1); 0194 } else { 0195 m_error = i18n("Could not find checksum in SHA256SUMS file"); 0196 emit finished(m_isIsoValid, m_error); return; 0197 } 0198 0199 // Calculate SHA256 checksum of the ISO image 0200 QCryptographicHash hash(QCryptographicHash::Sha256); 0201 QFile isoFile(m_filePath); 0202 if (!isoFile.open(QIODevice::ReadOnly)) { 0203 m_error = i18n("Could not read ISO image"); 0204 emit finished(m_isIsoValid, m_error); return; 0205 } 0206 if (!hash.addData(&isoFile)) { 0207 m_error = i18n("Could not perform checksum"); 0208 emit finished(m_isIsoValid, m_error); return; 0209 } 0210 QByteArray hashResult = hash.result(); 0211 if (checksum != hashResult.toHex()) { 0212 m_error = i18n("Checksum of .iso file does not match value in SHA256SUMS file"); 0213 emit finished(m_isIsoValid, m_error); return; 0214 } 0215 0216 // Check GPG signature 0217 QString isoFileName = fileInfo.fileName(); 0218 QFile signatureFile(fileInfo.absolutePath() + "/SHA256SUMS.gpg"); 0219 if (!signatureFile.open(QIODevice::ReadOnly)) { 0220 m_error = i18n("Could not find SHA256SUMS.gpg, please download PGP signature file to same directory."); 0221 emit finished(m_isIsoValid, m_error); return; 0222 } 0223 0224 0225 #ifdef _USE_GPG 0226 QByteArray signatureData = signatureFile.readAll(); 0227 QGpgME::VerifyDetachedJob *job = QGpgME::openpgp()->verifyDetachedJob(); 0228 GpgME::VerificationResult result = job->exec(signatureData, checksumsData); 0229 GpgME::Signature signature = result.signature(0); 0230 0231 if (signature.summary() == GpgME::Signature::None 0232 && signature.fingerprint() == keyFingerprint) { 0233 m_isIsoValid = VerifyResult::Successful; 0234 } else if (signature.summary() & GpgME::Signature::Valid) { 0235 m_isIsoValid = VerifyResult::Successful; 0236 } else if (signature.summary() & GpgME::Signature::KeyRevoked) { 0237 m_error = i18n("Key is revoked."); 0238 m_isIsoValid = VerifyResult::Failed; 0239 } else { 0240 m_error = i18n("Uses wrong signature."); 0241 m_isIsoValid = VerifyResult::Failed; 0242 } 0243 #else 0244 m_error = i18n("This app is built without verification support."); 0245 m_isIsoValid = VerifyResult::KeyNotFound; 0246 #endif 0247 emit finished(m_isIsoValid, m_error); 0248 } 0249 0250 void IsoVerifier::verifyWithSha256Sum(bool ok, const QString &checksum) 0251 { 0252 if (ok && !checksum.isEmpty()) { 0253 QCryptographicHash hash(QCryptographicHash::Sha256); 0254 QFile iso(m_filePath); 0255 if (!iso.open(QIODevice::ReadOnly)) { 0256 m_error = i18n("Could not read ISO image"); 0257 goto finish; 0258 } 0259 if (!hash.addData(&iso)) { 0260 m_error = i18n("Could not perform checksum"); 0261 goto finish; 0262 } 0263 QByteArray hashResult = hash.result(); 0264 0265 if (checksum.toLower() == hashResult.toHex().toLower()) { 0266 m_isIsoValid = VerifyResult::Successful; 0267 goto finish; 0268 } else { 0269 m_error = i18n("Checksum did not match"); 0270 m_isIsoValid = VerifyResult::Failed; 0271 goto finish; 0272 } 0273 } 0274 0275 m_error = i18n("Requires an SHA256 checksum"); 0276 0277 finish: 0278 emit finished(m_isIsoValid, m_error); 0279 } 0280 0281 #include "moc_isoverifier.cpp"