Codebase list gpgme1.0 / ac45369
qt: Extend SignKeyJob to create signatures with expiration date * lang/qt/src/signkeyjob.h (SignKeyJob::setExpirationDate): New. * lang/qt/src/qgpgmesignkeyjob.h, lang/qt/src/qgpgmesignkeyjob.cpp (QGpgMESignKeyJob::setExpirationDate): New. * lang/qt/src/qgpgmesignkeyjob.cpp (QGpgMESignKeyJob::Private): Add member m_expiration. (sign_key): Handle expiration date. (QGpgMESignKeyJob::start): Pass expiration date to sign_key. * lang/qt/tests/t-various.cpp (TestVarious::testSignKeyWithoutExpiration, TestVarious::testSignKeyWithExpiration): New. (TestVarious::initTestCase): Add "allow-weak-key-signatures" to gpg.conf. -- This allows Kleopatra (and other users of QGpgme) to create key signatures with expiration date. GnuPG-bug-id: 5336, 5506 Ingo Klöcker 2 years ago
4 changed file(s) with 181 addition(s) and 3 deletion(s). Raw diff Collapse all Expand all
3737
3838 #include "qgpgmesignkeyjob.h"
3939
40 #include <QDate>
4041 #include <QString>
4142
4243 #include "dataprovider.h"
4445 #include "context.h"
4546 #include "data.h"
4647 #include "gpgsignkeyeditinteractor.h"
48
49 #include "qgpgme_debug.h"
4750
4851 #include <cassert>
4952
7376 bool m_dupeOk = false;
7477 QString m_remark;
7578 TrustSignatureProperties m_trustSignature;
79 QDate m_expiration;
7680 };
7781
7882 QGpgMESignKeyJob::QGpgMESignKeyJob(Context *context)
8791 static QGpgMESignKeyJob::result_type sign_key(Context *ctx, const Key &key, const std::vector<unsigned int> &uids,
8892 unsigned int checkLevel, const Key &signer, unsigned int opts,
8993 bool dupeOk, const QString &remark,
90 const TrustSignatureProperties &trustSignature)
94 const TrustSignatureProperties &trustSignature,
95 const QDate &expirationDate)
9196 {
9297 QGpgME::QByteArrayDataProvider dp;
9398 Data data(&dp);
113118 skei->setTrustSignatureScope(trustSignature.scope.toUtf8().toStdString());
114119 }
115120
116 if (!signer.isNull())
121 if (!signer.isNull()) {
117122 if (const Error err = ctx->addSigningKey(signer)) {
118123 return std::make_tuple(err, QString(), Error());
119124 }
125 }
126
127 if (expirationDate.isValid()) {
128 // on 2106-02-07, the Unix time will reach 0xFFFFFFFF; since gpg uses uint32 internally
129 // for the expiration date clip it at 2106-02-06
130 static const QDate maxAllowedDate{2106, 2, 6};
131 const auto clippedExpirationDate = expirationDate <= maxAllowedDate ? expirationDate : maxAllowedDate;
132 if (clippedExpirationDate != expirationDate) {
133 qCWarning(QGPGME_LOG) << "Expiration of certification has been changed to" << clippedExpirationDate;
134 }
135 // use the "days from now" format to specify the expiration date of the certification;
136 // this format is the most appropriate regardless of the local timezone
137 const auto daysFromNow = QDate::currentDate().daysTo(clippedExpirationDate);
138 if (daysFromNow > 0) {
139 const auto certExpire = std::to_string(daysFromNow) + "d";
140 ctx->setFlag("cert-expire", certExpire.c_str());
141 }
142 } else {
143 // explicitly set "cert-expire" to "0" (no expiration) to override default-cert-expire set in gpg.conf
144 ctx->setFlag("cert-expire", "0");
145 }
146
120147 const Error err = ctx->edit(key, std::unique_ptr<EditInteractor> (skei), data);
121148 Error ae;
122149 const QString log = _detail::audit_log_as_html(ctx, ae);
142169 break;
143170 }
144171 run(std::bind(&sign_key, std::placeholders::_1, key, d->m_userIDsToSign, d->m_checkLevel, d->m_signingKey,
145 opts, d->m_dupeOk, d->m_remark, d->m_trustSignature));
172 opts, d->m_dupeOk, d->m_remark, d->m_trustSignature, d->m_expiration));
146173 d->m_started = true;
147174 return Error();
148175 }
196223 d->m_trustSignature = {trust, depth, scope};
197224 }
198225
226 void QGpgMESignKeyJob::setExpirationDate(const QDate &expiration)
227 {
228 assert(!d->m_started);
229 d->m_expiration = expiration;
230 }
231
199232 #include "qgpgmesignkeyjob.moc"
8686 /* from SignKeyJob */
8787 void setTrustSignature(GpgME::TrustSignatureTrust trust, unsigned short depth, const QString &scope) Q_DECL_OVERRIDE;
8888
89 void setExpirationDate(const QDate &expiration) override;
90
8991 private:
9092 class Private;
9193 std::unique_ptr<Private> d;
4545 enum class TrustSignatureTrust : char;
4646 }
4747
48 class QDate;
4849 class QString;
4950
5051 namespace QGpgME
143144 **/
144145 virtual void setTrustSignature(GpgME::TrustSignatureTrust trust, unsigned short depth, const QString &scope) { Q_UNUSED(trust); Q_UNUSED(depth); Q_UNUSED(scope); };
145146
147 /**
148 * Sets the expiration date of the key signature to @a expiration. By default,
149 * key signatures do not expire.
150 *
151 * Note: Expiration dates after 2106-02-06 will be set to 2106-02-06.
152 *
153 * Not pure virtual for ABI compatibility.
154 **/
155 virtual void setExpirationDate(const QDate &expiration) { Q_UNUSED(expiration); }
156
146157 Q_SIGNALS:
147158 void result(const GpgME::Error &result, const QString &auditLogAsHtml = QString(), const GpgME::Error &auditLogError = GpgME::Error());
148159 };
4545 #include "dn.h"
4646 #include "data.h"
4747 #include "dataprovider.h"
48 #include "signkeyjob.h"
4849
4950 #include "t-support.h"
5051
232233 delete ctx;
233234 }
234235
236 void testSignKeyWithoutExpiration()
237 {
238 Error err;
239
240 if (!loopbackSupported()) {
241 return;
242 }
243
244 auto ctx = Context::create(OpenPGP);
245 QVERIFY(ctx);
246
247 // Get the signing key (alfa@example.net)
248 auto seckey = ctx->key("A0FF4590BB6122EDEF6E3C542D727CC768697734", err, true);
249 QVERIFY(!err);
250 QVERIFY(!seckey.isNull());
251
252 // Get the target key (Bob / Bravo Test)
253 auto target = ctx->key("D695676BDCEDCC2CDD6152BCFE180B1DA9E3B0B2", err, false);
254 QVERIFY(!err);
255 QVERIFY(!target.isNull());
256 QVERIFY(target.numUserIDs() > 0);
257
258 // Create the job
259 auto job = std::unique_ptr<SignKeyJob>{openpgp()->signKeyJob()};
260 QVERIFY(job);
261
262 // Hack in the passphrase provider
263 auto jobCtx = Job::context(job.get());
264 TestPassphraseProvider provider;
265 jobCtx->setPassphraseProvider(&provider);
266 jobCtx->setPinentryMode(Context::PinentryLoopback);
267
268 // Setup the job
269 job->setExportable(true);
270 job->setSigningKey(seckey);
271 job->setDupeOk(true);
272
273 connect(job.get(), &SignKeyJob::result,
274 this, [this] (const GpgME::Error &err2, const QString &, const GpgME::Error &) {
275 Q_EMIT asyncDone();
276 if (err2) {
277 if (err2.code() == GPG_ERR_GENERAL) {
278 QFAIL(qPrintable(QString("The SignKeyJob failed with '%1'.\n"
279 "Hint: Run with GPGMEPP_INTERACTOR_DEBUG=stderr to debug the edit interaction.").arg(err2.asString())));
280 } else {
281 QFAIL(qPrintable(QString("The SignKeyJob failed with '%1'.").arg(err2.asString())));
282 }
283 }
284 });
285
286 job->start(target);
287 QSignalSpy spy{this, &TestVarious::asyncDone};
288 QVERIFY(spy.wait(QSIGNALSPY_TIMEOUT));
289
290 // At this point the signature should have been added.
291 target.update();
292 const auto keySignature = target.userID(0).signature(target.userID(0).numSignatures() - 1);
293 QVERIFY(keySignature.neverExpires());
294 }
295
296 void testSignKeyWithExpiration()
297 {
298 Error err;
299
300 if (!loopbackSupported()) {
301 return;
302 }
303
304 auto ctx = Context::create(OpenPGP);
305 QVERIFY(ctx);
306
307 // Get the signing key (alfa@example.net)
308 auto seckey = ctx->key("A0FF4590BB6122EDEF6E3C542D727CC768697734", err, true);
309 QVERIFY(!err);
310 QVERIFY(!seckey.isNull());
311
312 // Get the target key (Bob / Bravo Test)
313 auto target = ctx->key("D695676BDCEDCC2CDD6152BCFE180B1DA9E3B0B2", err, false);
314 QVERIFY(!err);
315 QVERIFY(!target.isNull());
316 QVERIFY(target.numUserIDs() > 0);
317
318 // Create the job
319 auto job = std::unique_ptr<SignKeyJob>{openpgp()->signKeyJob()};
320 QVERIFY(job);
321
322 // Hack in the passphrase provider
323 auto jobCtx = Job::context(job.get());
324 TestPassphraseProvider provider;
325 jobCtx->setPassphraseProvider(&provider);
326 jobCtx->setPinentryMode(Context::PinentryLoopback);
327
328 // Setup the job
329 job->setExportable(true);
330 job->setSigningKey(seckey);
331 job->setDupeOk(true);
332 job->setExpirationDate(QDate{2222, 2, 22});
333
334 connect(job.get(), &SignKeyJob::result,
335 this, [this] (const GpgME::Error &err2, const QString &, const GpgME::Error &) {
336 Q_EMIT asyncDone();
337 if (err2) {
338 if (err2.code() == GPG_ERR_GENERAL) {
339 QFAIL(qPrintable(QString("The SignKeyJob failed with '%1'.\n"
340 "Hint: Run with GPGMEPP_INTERACTOR_DEBUG=stderr to debug the edit interaction.").arg(err2.asString())));
341 } else {
342 QFAIL(qPrintable(QString("The SignKeyJob failed with '%1'.").arg(err2.asString())));
343 }
344 }
345 });
346
347 QTest::ignoreMessage(QtWarningMsg, "Expiration of certification has been changed to QDate(\"2106-02-06\")");
348
349 job->start(target);
350 QSignalSpy spy{this, &TestVarious::asyncDone};
351 QVERIFY(spy.wait(QSIGNALSPY_TIMEOUT));
352
353 // At this point the signature should have been added.
354 target.update();
355 const auto keySignature = target.userID(0).signature(target.userID(0).numSignatures() - 1);
356 QVERIFY(!keySignature.neverExpires());
357 const auto expirationDate = QDateTime::fromSecsSinceEpoch(keySignature.expirationTime()).date();
358 QCOMPARE(expirationDate, QDate(2106, 2, 6)); // expiration date is capped at 2106-02-06
359 }
360
235361 void testVersion()
236362 {
237363 QVERIFY(EngineInfo::Version("2.1.0") < EngineInfo::Version("2.1.1"));
284410 const QString gpgHome = qgetenv("GNUPGHOME");
285411 QVERIFY(copyKeyrings(gpgHome, mDir.path()));
286412 qputenv("GNUPGHOME", mDir.path().toUtf8());
413 QFile conf(mDir.path() + QStringLiteral("/gpg.conf"));
414 QVERIFY(conf.open(QIODevice::WriteOnly));
415 if (GpgME::engineInfo(GpgME::GpgEngine).engineVersion() >= "2.2.18") {
416 conf.write("allow-weak-key-signatures");
417 }
418 conf.close();
287419 }
288420
289421 private: