gpg.cpp (7202B)
1 #include "gpg.h" 2 3 #include <QStandardPaths> 4 #include <QProcess> 5 #include <QIODevice> 6 #include <QRegularExpression> 7 #include <QRegularExpressionMatch> 8 #include <QTimer> 9 #include <QtConcurrent> 10 #include <QFutureWatcher> 11 12 namespace { 13 14 struct GpgExecutable { 15 GpgExecutable(const QString &path, int major, int minor) 16 : path(path), major_version(major), minor_version(minor) 17 {} 18 QString path = {}; 19 int major_version = 0; 20 int minor_version = 0; 21 }; 22 23 GpgExecutable findGpgExecutable() 24 { 25 auto gpgExe = QStandardPaths::findExecutable(QStringLiteral("gpg2")); 26 if (gpgExe.isEmpty()) { 27 gpgExe = QStandardPaths::findExecutable(QStringLiteral("gpg")); 28 } 29 30 QProcess process; 31 process.start(gpgExe, {QStringLiteral("--version")}, QIODevice::ReadOnly); 32 process.waitForFinished(); 33 const auto line = process.readLine(); 34 static const QRegularExpression rex(QStringLiteral("([0-9]+).([0-9]+).([0-9]+)")); 35 const auto match = rex.match(QString::fromUtf8(line)); 36 37 return {gpgExe, match.captured(1).toInt(), match.captured(2).toInt()}; 38 } 39 40 } // namespace 41 42 Gpg::GetKeyTrustTask *Gpg::getKeyTrust(const Key &key) 43 { 44 return new GetKeyTrustTask(key); 45 } 46 47 Gpg::UpdateKeyTrustTask *Gpg::updateKeyTrust(const Key &key, Key::Trust trust) 48 { 49 return new UpdateKeyTrustTask(key, trust); 50 } 51 52 Gpg::EncryptTask *Gpg::encrypt(const QString &file, const Key &key, const QString &content) 53 { 54 return new EncryptTask(file, key, content); 55 } 56 57 Gpg::DecryptTask *Gpg::decrypt(const QString &file, const Key &key, const QString &passphrase) 58 { 59 return new DecryptTask(file, key, passphrase); 60 } 61 62 63 Gpg::Task::Task(QObject *parent) 64 : QObject(parent) 65 { 66 QTimer::singleShot(0, this, &Task::start); 67 } 68 69 bool Gpg::Task::error() const 70 { 71 return !mError.isNull(); 72 } 73 74 QString Gpg::Task::errorString() const 75 { 76 return mError; 77 } 78 79 void Gpg::Task::setError(const QString &error) 80 { 81 mError = error; 82 } 83 84 void Gpg::Task::start() 85 { 86 qDebug() << "Starting task" << this; 87 auto future = QtConcurrent::run(this, &Task::run); 88 auto *watcher = new QFutureWatcher<void>; 89 connect(watcher, &QFutureWatcher<void>::finished, watcher, &QObject::deleteLater); 90 connect(watcher, &QFutureWatcher<void>::finished, this, &Gpg::Task::finished); 91 connect(watcher, &QFutureWatcher<void>::finished, this, &QObject::deleteLater); 92 watcher->setFuture(future); 93 } 94 95 Gpg::GetKeyTrustTask::GetKeyTrustTask(const Key &key) 96 : mKey(key) 97 {} 98 99 Gpg::Key::Trust Gpg::GetKeyTrustTask::trust() const 100 { 101 return mTrust; 102 } 103 104 void Gpg::GetKeyTrustTask::run() 105 { 106 const auto gpg = findGpgExecutable(); 107 QProcess process; 108 process.setProgram(gpg.path); 109 process.setArguments({QStringLiteral("--list-key \"%1\"").arg(mKey.id), QStringLiteral("--with-colons")}); 110 process.start(QIODevice::ReadOnly); 111 process.waitForFinished(); 112 while (!process.atEnd()) { 113 const auto line = process.readLine(); 114 const auto cols = line.split(':'); 115 if (cols.size() < 8) { 116 continue; 117 } 118 if (cols[0] == "uid") { 119 if (cols[1].isEmpty()) { 120 mTrust = Key::Trust::Unknown; 121 } 122 switch (cols[1][0]) { 123 case 'u': 124 mTrust = Key::Trust::Ultimate; 125 break; 126 case 'f': 127 mTrust = Key::Trust::Full; 128 break; 129 case 'm': 130 mTrust = Key::Trust::Marginal; 131 break; 132 case 'n': 133 mTrust = Key::Trust::Never; 134 break; 135 case '-': 136 default: 137 mTrust = Key::Trust::Unknown; 138 break; 139 } 140 break; 141 } 142 } 143 } 144 145 Gpg::UpdateKeyTrustTask::UpdateKeyTrustTask(const Key &key, Key::Trust trust) 146 : Task() 147 , mKey(key) 148 , mTrust(trust) 149 {} 150 151 void Gpg::UpdateKeyTrustTask::run() 152 { 153 const auto gpg = findGpgExecutable(); 154 QProcess process; 155 process.setProgram(gpg.path); 156 process.setArguments({QStringLiteral("--command-fd=1"), 157 QStringLiteral("--status-fd=1"), 158 QStringLiteral("--batch"), 159 QStringLiteral("--edit-key"), 160 mKey.id, 161 QStringLiteral("trust")}); 162 process.start(); 163 process.waitForStarted(); 164 while (process.state() == QProcess::Running) { 165 process.waitForReadyRead(); 166 const auto line = process.readLine(); 167 if (line == "[GNUPG:] GET_LINE edit_ownertrust.value\n") { 168 process.write(QByteArray::number(static_cast<int>(mTrust)) + "\n"); 169 process.closeWriteChannel(); 170 break; 171 } 172 } 173 174 process.waitForFinished(); 175 } 176 177 Gpg::EncryptTask::EncryptTask(const QString &file, const Key &key, const QString &content) 178 : mFile(file), mKey(key), mContent(content) 179 {} 180 181 void Gpg::EncryptTask::run() 182 { 183 const auto gpg = findGpgExecutable(); 184 QProcess process; 185 process.setProgram(gpg.path); 186 process.setArguments({QStringLiteral("--quiet"), 187 QStringLiteral("--status-fd=1"), 188 QStringLiteral("--command-fd=1"), 189 QStringLiteral("--batch"), 190 QStringLiteral("--encrypt"), 191 QStringLiteral("--no-encrypt-to"), 192 QStringLiteral("-r %1").arg(mKey.id), 193 QStringLiteral("-o%1").arg(mFile)}); 194 process.start(); 195 process.waitForStarted(); 196 process.write(mContent.toUtf8()); 197 process.closeWriteChannel(); 198 process.waitForFinished(); 199 if (process.exitCode() != 0) { 200 const auto err = process.readAllStandardError(); 201 qWarning() << "Failed to encrypt data:" << err; 202 setError(QString::fromUtf8(err)); 203 } 204 } 205 206 Gpg::DecryptTask::DecryptTask(const QString &file, const Key &key, const QString &passphrase) 207 : Task(), mFile(file), mPassphrase(passphrase), mKey(key) 208 {} 209 210 QString Gpg::DecryptTask::content() const 211 { 212 return mContent; 213 } 214 215 void Gpg::DecryptTask::run() 216 { 217 const auto gpg = findGpgExecutable(); 218 QProcess process; 219 process.setProgram(gpg.path); 220 QStringList arguments{ 221 QStringLiteral("--quiet"), 222 QStringLiteral("--batch"), 223 QStringLiteral("--decrypt"), 224 QStringLiteral("--no-tty"), 225 QStringLiteral("--command-fd=1"), 226 QStringLiteral("--no-encrypt-to"), 227 QStringLiteral("--compress-algo=none"), 228 QStringLiteral("--passphrase-fd=0") 229 }; 230 231 if (gpg.minor_version >= 1) { 232 arguments << QStringLiteral("--pinentry-mode=loopback"); 233 } 234 235 arguments << QStringLiteral ("-r %1").arg (mKey.id) << mFile; 236 237 process.setArguments(arguments); 238 process.start(); 239 process.waitForStarted(); 240 process.write(mPassphrase.toUtf8()); 241 process.closeWriteChannel(); 242 process.waitForFinished(); 243 if (process.exitCode() != 0) { 244 const auto err = process.readAllStandardError(); 245 qWarning() << "Failed to decrypt data:" << err; 246 setError(QString::fromUtf8(err)); 247 } else { 248 mContent = QString::fromUtf8(process.readAllStandardOutput()); 249 } 250 }