Warning, file /utilities/isoimagewriter/isoimagewriter/isoverifier.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

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"