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