File indexing completed on 2024-11-10 04:50:01

0001 /*
0002  *  SPDX-FileCopyrightText: 2017 Daniel Vrátil <dvratil@kde.org>
0003  *
0004  *  SPDX-License-Identifier: GPL-2.0-only
0005  */
0006 
0007 #include "gpghelper.h"
0008 
0009 #include <QDebug>
0010 #include <QFileInfo>
0011 #include <QProcess>
0012 #include <QTest>
0013 
0014 namespace
0015 {
0016 bool copyRecursively(const QString &src, const QString &dest)
0017 {
0018     QFileInfo srcInfo(src);
0019     if (srcInfo.isDir()) {
0020         QDir destDir(dest);
0021         destDir.cdUp();
0022         if (!destDir.mkdir(QFileInfo(src).fileName())) {
0023             qWarning() << "Failed to create directory" << QFileInfo(src).fileName() << "in" << destDir.path();
0024             return false;
0025         }
0026         QDir srcDir(src);
0027         const auto srcFiles = srcDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System);
0028         for (const auto &fileName : srcFiles) {
0029             const QString srcFile = src + QLatin1Char('/') + fileName;
0030             const QString dstFile = dest + QLatin1Char('/') + fileName;
0031             if (!copyRecursively(srcFile, dstFile)) {
0032                 return false;
0033             }
0034         }
0035     } else {
0036         if (!QFile::copy(src, dest)) {
0037             qWarning() << "Failed to copy" << src << "into" << dest;
0038             return false;
0039         }
0040     }
0041     return true;
0042 }
0043 
0044 QString gpgexe(GPGHelper::CryptoType crypto)
0045 {
0046     return (crypto == GPGHelper::OpenPGP) ? QStringLiteral("gpg2") : QStringLiteral("gpgsm");
0047 }
0048 } // namespace
0049 
0050 GPGHelper::GPGHelper(const QString &templateGnupgHome)
0051     : mValid(false)
0052 {
0053     const auto home = gnupgHome();
0054     mValid = copyRecursively(templateGnupgHome, home);
0055     if (mValid) {
0056         qputenv("GNUPGHOME", home.toUtf8());
0057     }
0058 }
0059 
0060 GPGHelper::~GPGHelper()
0061 {
0062     // shutdown gpg-agent
0063     QProcess gpgshutdown;
0064     auto env = gpgshutdown.processEnvironment();
0065     env.insert(QStringLiteral("GNUPGHOME"), gnupgHome());
0066     gpgshutdown.setProcessEnvironment(env);
0067     gpgshutdown.start(QStringLiteral("gpg-connect-agent"), QStringList());
0068     QVERIFY(gpgshutdown.waitForStarted());
0069     gpgshutdown.write("KILLAGENT");
0070     gpgshutdown.closeWriteChannel();
0071     QVERIFY(gpgshutdown.waitForFinished());
0072 }
0073 
0074 QString GPGHelper::gnupgHome() const
0075 {
0076     return mTmpDir.path() + QStringLiteral("/gpghome");
0077 }
0078 
0079 QByteArray GPGHelper::runGpg(const QByteArray &in, GPGHelper::CryptoType crypto, const QStringList &args) const
0080 {
0081     QProcess gpg;
0082     gpg.setReadChannel(QProcess::StandardOutput);
0083     auto env = gpg.processEnvironment();
0084     env.insert(QStringLiteral("GNUPGHOME"), gnupgHome());
0085     gpg.setProcessEnvironment(env);
0086     gpg.start(gpgexe(crypto), args);
0087     if (!gpg.waitForStarted()) {
0088         return {};
0089     }
0090     gpg.write(in);
0091     gpg.closeWriteChannel();
0092     if (!gpg.waitForReadyRead()) {
0093         return {};
0094     }
0095     const auto out = gpg.readAllStandardOutput();
0096 
0097     if (!gpg.waitForFinished()) {
0098         return {};
0099     }
0100 
0101     return out;
0102 }
0103 
0104 QByteArray GPGHelper::decrypt(const QByteArray &enc, GPGHelper::CryptoType crypto) const
0105 {
0106     return runGpg(enc, crypto, {QStringLiteral("-d")});
0107 }
0108 
0109 QByteArray GPGHelper::encrypt(const QByteArray &dec, GPGHelper::CryptoType crypto) const
0110 {
0111     return runGpg(dec, crypto, {QStringLiteral("-e")});
0112 }
0113 
0114 QString GPGHelper::encryptionKeyFp(const QByteArray &enc, GPGHelper::CryptoType crypto) const
0115 {
0116     const auto data = runGpg(enc, crypto, {QStringLiteral("--fingerprint"), QStringLiteral("--with-colons")});
0117     int idx = data.indexOf("\nfpr:");
0118     if (idx == -1) {
0119         return {};
0120     }
0121 
0122     // Find first non-colon character after "fpr"
0123     for (idx = idx + 4; idx < data.size() && data[idx] == ':'; ++idx) { }
0124     const int end = data.indexOf(':', idx);
0125 
0126     return QString::fromLatin1(data.constData() + idx, end - idx);
0127 }