926728d7612a7d050978986ea0198092537c4c19
[gpgme.git] / lang / qt / tests / t-tofuinfo.cpp
1 /* t-tofuinfo.cpp
2
3     This file is part of qgpgme, the Qt API binding for gpgme
4     Copyright (c) 2016 by Bundesamt für Sicherheit in der Informationstechnik
5     Software engineering by Intevation GmbH
6
7     QGpgME is free software; you can redistribute it and/or
8     modify it under the terms of the GNU General Public License as
9     published by the Free Software Foundation; either version 2 of the
10     License, or (at your option) any later version.
11
12     QGpgME is distributed in the hope that it will be useful,
13     but WITHOUT ANY WARRANTY; without even the implied warranty of
14     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15     General Public License for more details.
16
17     You should have received a copy of the GNU General Public License
18     along with this program; if not, write to the Free Software
19     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
20
21     In addition, as a special exception, the copyright holders give
22     permission to link the code of this program with any edition of
23     the Qt library by Trolltech AS, Norway (or with modified versions
24     of Qt that use the same license as Qt), and distribute linked
25     combinations including the two.  You must obey the GNU General
26     Public License in all respects for all of the code used other than
27     Qt.  If you modify this file, you may extend this exception to
28     your version of the file, but you are not obligated to do so.  If
29     you do not wish to do so, delete this exception statement from
30     your version.
31 */
32 #ifdef HAVE_CONFIG_H
33  #include "config.h"
34 #endif
35
36 #include <QDebug>
37 #include <QTest>
38 #include <QTemporaryDir>
39 #include <QSignalSpy>
40
41 #include "protocol.h"
42 #include "tofuinfo.h"
43 #include "tofupolicyjob.h"
44 #include "verifyopaquejob.h"
45 #include "verificationresult.h"
46 #include "signingresult.h"
47 #include "importjob.h"
48 #include "importresult.h"
49 #include "keylistjob.h"
50 #include "keylistresult.h"
51 #include "qgpgmesignjob.h"
52 #include "key.h"
53 #include "t-support.h"
54 #include "engineinfo.h"
55 #include <iostream>
56
57 using namespace QGpgME;
58 using namespace GpgME;
59
60 static const char testMsg1[] =
61 "-----BEGIN PGP MESSAGE-----\n"
62 "\n"
63 "owGbwMvMwCSoW1RzPCOz3IRxjXQSR0lqcYleSUWJTZOvjVdpcYmCu1+oQmaJIleH\n"
64 "GwuDIBMDGysTSIqBi1MApi+nlGGuwDeHao53HBr+FoVGP3xX+kvuu9fCMJvl6IOf\n"
65 "y1kvP4y+8D5a11ang0udywsA\n"
66 "=Crq6\n"
67 "-----END PGP MESSAGE-----\n";
68
69 static const char conflictKey1[] = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
70 "\n"
71 "mDMEWG+w/hYJKwYBBAHaRw8BAQdAiq1oStvDYg8ZfFs5DgisYJo8dJxD+C/AA21O\n"
72 "K/aif0O0GXRvZnVfY29uZmxpY3RAZXhhbXBsZS5jb22IlgQTFggAPhYhBHoJBLaV\n"
73 "DamYAgoa1L5BwMOl/x88BQJYb7D+AhsDBQkDwmcABQsJCAcCBhUICQoLAgQWAgMB\n"
74 "Ah4BAheAAAoJEL5BwMOl/x88GvwA/0SxkbLyAcshGm2PRrPsFQsSVAfwaSYFVmS2\n"
75 "cMVIw1PfAQDclRH1Z4MpufK07ju4qI33o4s0UFpVRBuSxt7A4P2ZD7g4BFhvsP4S\n"
76 "CisGAQQBl1UBBQEBB0AmVrgaDNJ7K2BSalsRo2EkRJjHGqnp5bBB0tapnF81CQMB\n"
77 "CAeIeAQYFggAIBYhBHoJBLaVDamYAgoa1L5BwMOl/x88BQJYb7D+AhsMAAoJEL5B\n"
78 "wMOl/x88OR0BAMq4/vmJUORRTmzjHcv/DDrQB030DSq666rlckGIKTShAPoDXM9N\n"
79 "0gZK+YzvrinSKZXHmn0aSwmC1/hyPybJPEljBw==\n"
80 "=p2Oj\n"
81 "-----END PGP PUBLIC KEY BLOCK-----\n";
82
83 static const char conflictKey2[] = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n"
84 "\n"
85 "mDMEWG+xShYJKwYBBAHaRw8BAQdA567gPEPJRpqKnZjlFJMRNUqruRviYMyygfF6\n"
86 "6Ok+ygu0GXRvZnVfY29uZmxpY3RAZXhhbXBsZS5jb22IlgQTFggAPhYhBJ5kRh7E\n"
87 "I98w8kgUcmkAfYFvqqHsBQJYb7FKAhsDBQkDwmcABQsJCAcCBhUICQoLAgQWAgMB\n"
88 "Ah4BAheAAAoJEGkAfYFvqqHsYR0BAOz8JjYB4VvGkt6noLS3F5TLfsedGwQkBCw5\n"
89 "znw/vGZsAQD9DSX+ekwdrN56mNO8ISt5uVS7B1ZQtouNBF+nzcwbDbg4BFhvsUoS\n"
90 "CisGAQQBl1UBBQEBB0BFupW8+Xc1ikab8TJqANjQhvFVh6uLsgcK4g9lZgbGXAMB\n"
91 "CAeIeAQYFggAIBYhBJ5kRh7EI98w8kgUcmkAfYFvqqHsBQJYb7FKAhsMAAoJEGkA\n"
92 "fYFvqqHs15ABALdN3uiV/07cJ3RkNb3WPcijGsto+lECDS11dKEwTMFeAQDx+V36\n"
93 "ocbYC/xEuwi3w45oNqGieazzcD/GBbt8OBk3BA==\n"
94 "=45IR\n"
95 "-----END PGP PUBLIC KEY BLOCK-----\n";
96
97 static const char conflictMsg1[] = "-----BEGIN PGP MESSAGE-----\n"
98 "\n"
99 "owGbwMvMwCG2z/HA4aX/5W0YT3MlMUTkb2xPSizi6ihlYRDjYJAVU2Sp4mTZNpV3\n"
100 "5QwmLqkrMLWsTCCFDFycAjCR1vcMf4U0Qrs6qzqfHJ9puGOFduLN2nVmhsumxjBE\n"
101 "mdw4lr1ehIWR4QdLuNBpe86PGx1PtNXfVAzm/hu+vfjCp5BVNjPTM9L0eAA=\n"
102 "=MfBD\n"
103 "-----END PGP MESSAGE-----\n";
104
105 static const char conflictMsg2[] = "-----BEGIN PGP MESSAGE-----\n"
106 "\n"
107 "owGbwMvMwCGWyVDbmL9q4RvG01xJDBH5GyvS8vO5OkpZGMQ4GGTFFFnmpbjJHVG+\n"
108 "b/DJQ6QIppaVCaSQgYtTACaySZHhr/SOPrdFJ89KrcwKY5i1XnflXYf2PK76SafK\n"
109 "tkxXuXzvJAvDX4kCybuqFk3HXCexz2+IrnZ+5X5EqOnuo3ens2cte+uzlhMA\n"
110 "=BIAi\n"
111 "-----END PGP MESSAGE-----\n";
112
113 class TofuInfoTest: public QGpgMETest
114 {
115     Q_OBJECT
116 Q_SIGNALS:
117     void asyncDone();
118
119 private:
120     bool testSupported()
121     {
122         static bool initialized, supported;
123         if (initialized) {
124             return supported;
125         }
126         initialized = true;
127         if (GpgME::engineInfo(GpgME::GpgEngine).engineVersion() < "2.1.16") {
128             return false;
129         }
130         // If the keylist fails here this means that gnupg does not
131         // support tofu at all. It can be disabled at compile time. So no
132         // tests.
133         auto *job = openpgp()->keyListJob(false, false, false);
134         job->addMode(GpgME::WithTofu);
135         std::vector<GpgME::Key> keys;
136         job->exec(QStringList() << QStringLiteral("zulu@example.net"), true, keys);
137         delete job;
138         supported = !keys.empty();
139         return supported;
140     }
141
142     void testTofuCopy(TofuInfo other, const TofuInfo &orig)
143     {
144         QVERIFY(!orig.isNull());
145         QVERIFY(!other.isNull());
146         QVERIFY(orig.signLast() == other.signLast());
147         QVERIFY(orig.signCount() == other.signCount());
148         QVERIFY(orig.validity() == other.validity());
149         QVERIFY(orig.policy() == other.policy());
150     }
151
152     void signAndVerify(const QString &what, const GpgME::Key &key, int expected)
153     {
154         Context *ctx = Context::createForProtocol(OpenPGP);
155         TestPassphraseProvider provider;
156         ctx->setPassphraseProvider(&provider);
157         ctx->setPinentryMode(Context::PinentryLoopback);
158         auto *job = new QGpgMESignJob(ctx);
159
160         std::vector<Key> keys;
161         keys.push_back(key);
162         QByteArray signedData;
163         auto sigResult = job->exec(keys, what.toUtf8(), NormalSignatureMode, signedData);
164         delete job;
165
166         QVERIFY(!sigResult.error());
167         foreach (const auto uid, keys[0].userIDs()) {
168             auto info = uid.tofuInfo();
169             QVERIFY(info.signCount() == expected - 1);
170         }
171
172         auto verifyJob = openpgp()->verifyOpaqueJob();
173         QByteArray verified;
174
175         auto result = verifyJob->exec(signedData, verified);
176         delete verifyJob;
177
178         QVERIFY(!result.error());
179         QVERIFY(verified == what.toUtf8());
180
181         QVERIFY(result.numSignatures() == 1);
182         auto sig = result.signatures()[0];
183
184         auto key2 = sig.key();
185         QVERIFY(!key.isNull());
186         QVERIFY(!strcmp (key2.primaryFingerprint(), key.primaryFingerprint()));
187         QVERIFY(!strcmp (key.primaryFingerprint(), sig.fingerprint()));
188         auto stats = key2.userID(0).tofuInfo();
189         QVERIFY(!stats.isNull());
190         if (stats.signCount() != expected) {
191             std::cout << "################ Key before verify: "
192                       << key
193                       << "################ Key after verify: "
194                       << key2;
195         }
196         QVERIFY(stats.signCount() == expected);
197     }
198
199 private Q_SLOTS:
200     void testTofuNull()
201     {
202         if (!testSupported()) {
203             return;
204         }
205         TofuInfo tofu;
206         QVERIFY(tofu.isNull());
207         QVERIFY(!tofu.description());
208         QVERIFY(!tofu.signCount());
209         QVERIFY(!tofu.signLast());
210         QVERIFY(!tofu.signFirst());
211         QVERIFY(tofu.validity() == TofuInfo::ValidityUnknown);
212         QVERIFY(tofu.policy() == TofuInfo::PolicyUnknown);
213     }
214
215     void testTofuInfo()
216     {
217         if (!testSupported()) {
218             return;
219         }
220         auto *job = openpgp()->verifyOpaqueJob(true);
221         const QByteArray data1(testMsg1);
222         QByteArray plaintext;
223
224         auto ctx = Job::context(job);
225         QVERIFY(ctx);
226         ctx->setSender("alfa@example.net");
227
228         auto result = job->exec(data1, plaintext);
229         delete job;
230
231         QVERIFY(!result.isNull());
232         QVERIFY(!result.error());
233         QVERIFY(!strcmp(plaintext.constData(), "Just GNU it!\n"));
234
235         QVERIFY(result.numSignatures() == 1);
236         Signature sig = result.signatures()[0];
237         /* TOFU is always marginal */
238         QVERIFY(sig.validity() == Signature::Marginal);
239
240         auto stats = sig.key().userID(0).tofuInfo();
241         QVERIFY(!stats.isNull());
242         QVERIFY(sig.key().primaryFingerprint());
243         QVERIFY(sig.fingerprint());
244         QVERIFY(!strcmp(sig.key().primaryFingerprint(), sig.fingerprint()));
245         QVERIFY(stats.signFirst() == stats.signLast());
246         QVERIFY(stats.signCount() == 1);
247         QVERIFY(stats.policy() == TofuInfo::PolicyAuto);
248         QVERIFY(stats.validity() == TofuInfo::LittleHistory);
249
250         testTofuCopy(stats, stats);
251
252         /* Another verify */
253
254         job = openpgp()->verifyOpaqueJob(true);
255         result = job->exec(data1, plaintext);
256         delete job;
257
258         QVERIFY(!result.isNull());
259         QVERIFY(!result.error());
260
261         QVERIFY(result.numSignatures() == 1);
262         sig = result.signatures()[0];
263         /* TOFU is always marginal */
264         QVERIFY(sig.validity() == Signature::Marginal);
265
266         stats = sig.key().userID(0).tofuInfo();
267         QVERIFY(!stats.isNull());
268         QVERIFY(!strcmp(sig.key().primaryFingerprint(), sig.fingerprint()));
269         QVERIFY(stats.signFirst() == stats.signLast());
270         QVERIFY(stats.signCount() == 1);
271         QVERIFY(stats.policy() == TofuInfo::PolicyAuto);
272         QVERIFY(stats.validity() == TofuInfo::LittleHistory);
273
274         /* Verify that another call yields the same result */
275         job = openpgp()->verifyOpaqueJob(true);
276         result = job->exec(data1, plaintext);
277         delete job;
278
279         QVERIFY(!result.isNull());
280         QVERIFY(!result.error());
281
282         QVERIFY(result.numSignatures() == 1);
283         sig = result.signatures()[0];
284         /* TOFU is always marginal */
285         QVERIFY(sig.validity() == Signature::Marginal);
286
287         stats = sig.key().userID(0).tofuInfo();
288         QVERIFY(!stats.isNull());
289         QVERIFY(!strcmp(sig.key().primaryFingerprint(), sig.fingerprint()));
290         QVERIFY(stats.signFirst() == stats.signLast());
291         QVERIFY(stats.signCount() == 1);
292         QVERIFY(stats.policy() == TofuInfo::PolicyAuto);
293         QVERIFY(stats.validity() == TofuInfo::LittleHistory);
294     }
295
296     void testTofuSignCount()
297     {
298         if (!testSupported()) {
299             return;
300         }
301         auto *job = openpgp()->keyListJob(false, false, false);
302         job->addMode(GpgME::WithTofu);
303         std::vector<GpgME::Key> keys;
304         GpgME::KeyListResult result = job->exec(QStringList() << QStringLiteral("zulu@example.net"),
305                                                 true, keys);
306         delete job;
307         QVERIFY(!keys.empty());
308         Key key = keys[0];
309         QVERIFY(!key.isNull());
310
311         /* As we sign & verify quickly here we need different
312          * messages to avoid having them treated as the same
313          * message if they were created within the same second.
314          * Alternatively we could use the same message and wait
315          * a second between each call. But this would slow down
316          * the testsuite. */
317         signAndVerify(QStringLiteral("Hello"), key, 1);
318         key.update();
319         signAndVerify(QStringLiteral("Hello2"), key, 2);
320         key.update();
321         signAndVerify(QStringLiteral("Hello3"), key, 3);
322         key.update();
323         signAndVerify(QStringLiteral("Hello4"), key, 4);
324     }
325
326     void testTofuKeyList()
327     {
328         if (!testSupported()) {
329             return;
330         }
331
332         /* First check that the key has no tofu info. */
333         auto *job = openpgp()->keyListJob(false, false, false);
334         std::vector<GpgME::Key> keys;
335         auto result = job->exec(QStringList() << QStringLiteral("zulu@example.net"),
336                                                  true, keys);
337         delete job;
338         QVERIFY(!keys.empty());
339         auto key = keys[0];
340         QVERIFY(!key.isNull());
341         QVERIFY(key.userID(0).tofuInfo().isNull());
342         auto keyCopy = key;
343         keyCopy.update();
344         auto sigCnt = keyCopy.userID(0).tofuInfo().signCount();
345         signAndVerify(QStringLiteral("Hello5"), keyCopy,
346                       sigCnt + 1);
347         keyCopy.update();
348         signAndVerify(QStringLiteral("Hello6"), keyCopy,
349                       sigCnt + 2);
350
351         /* Now another one but with tofu */
352         job = openpgp()->keyListJob(false, false, false);
353         job->addMode(GpgME::WithTofu);
354         result = job->exec(QStringList() << QStringLiteral("zulu@example.net"),
355                            true, keys);
356         delete job;
357         QVERIFY(!result.error());
358         QVERIFY(!keys.empty());
359         auto key2 = keys[0];
360         QVERIFY(!key2.isNull());
361         auto info = key2.userID(0).tofuInfo();
362         QVERIFY(!info.isNull());
363         QVERIFY(info.signCount());
364     }
365
366     void testTofuPolicy()
367     {
368         if (!testSupported()) {
369             return;
370         }
371
372         /* First check that the key has no tofu info. */
373         auto *job = openpgp()->keyListJob(false, false, false);
374         std::vector<GpgME::Key> keys;
375         job->addMode(GpgME::WithTofu);
376         auto result = job->exec(QStringList() << QStringLiteral("bravo@example.net"),
377                                                  false, keys);
378
379         if (keys.empty()) {
380             qDebug() << "bravo@example.net not found";
381             qDebug() << "Error: " << result.error().asString();
382             const auto homedir = QString::fromLocal8Bit(qgetenv("GNUPGHOME"));
383             qDebug() << "Homedir is: " << homedir;
384             QFileInfo fi(homedir + "/pubring.gpg");
385             qDebug () << "pubring exists: " << fi.exists() << " readable? "
386                       << fi.isReadable() << " size: " << fi.size();
387             QFileInfo fi2(homedir + "/pubring.kbx");
388             qDebug () << "keybox exists: " << fi2.exists() << " readable? "
389                       << fi2.isReadable() << " size: " << fi2.size();
390
391             result = job->exec(QStringList(), false, keys);
392             foreach (const auto key, keys) {
393                 qDebug() << "Key: " << key.userID(0).name() << " <"
394                          << key.userID(0).email()
395                          << ">\n fpr: " << key.primaryFingerprint();
396             }
397         }
398         QVERIFY(!result.error());
399         QVERIFY(!keys.empty());
400         auto key = keys[0];
401         QVERIFY(!key.isNull());
402         QVERIFY(key.userID(0).tofuInfo().policy() != TofuInfo::PolicyBad);
403         auto *tofuJob = openpgp()->tofuPolicyJob();
404         auto err = tofuJob->exec(key, TofuInfo::PolicyBad);
405         QVERIFY(!err);
406         result = job->exec(QStringList() << QStringLiteral("bravo@example.net"),
407                                             false, keys);
408         QVERIFY(!keys.empty());
409         key = keys[0];
410         QVERIFY(key.userID(0).tofuInfo().policy() == TofuInfo::PolicyBad);
411         err = tofuJob->exec(key, TofuInfo::PolicyGood);
412
413         result = job->exec(QStringList() << QStringLiteral("bravo@example.net"),
414                                             false, keys);
415         key = keys[0];
416         QVERIFY(key.userID(0).tofuInfo().policy() == TofuInfo::PolicyGood);
417         delete tofuJob;
418         delete job;
419     }
420
421     void testTofuConflict()
422     {
423         if (!testSupported()) {
424             return;
425         }
426
427         if (GpgME::engineInfo(GpgME::GpgEngine).engineVersion() < "2.1.19") {
428             return;
429         }
430
431         // Import key 1
432         auto importjob = openpgp()->importJob();
433         connect(importjob, &ImportJob::result, this,
434                 [this](ImportResult result, QString, Error)
435         {
436             QVERIFY(!result.error());
437             QVERIFY(!result.imports().empty());
438             QVERIFY(result.numImported());
439             Q_EMIT asyncDone();
440         });
441         importjob->start(QByteArray(conflictKey1));
442         QSignalSpy spy (this, SIGNAL(asyncDone()));
443         QVERIFY(spy.wait());
444
445         // Verify Message 1
446         const QByteArray signedData(conflictMsg1);
447         auto verifyJob = openpgp()->verifyOpaqueJob(true);
448         QByteArray verified;
449         auto result = verifyJob->exec(signedData, verified);
450         delete verifyJob;
451
452         QVERIFY(!result.isNull());
453         QVERIFY(!result.error());
454
455         QVERIFY(result.numSignatures() == 1);
456         auto sig = result.signatures()[0];
457         QVERIFY(sig.validity() == Signature::Marginal);
458
459         auto stats = sig.key().userID(0).tofuInfo();
460         QVERIFY(!stats.isNull());
461         QVERIFY(!strcmp(sig.key().primaryFingerprint(), sig.fingerprint()));
462         QVERIFY(stats.signFirst() == stats.signLast());
463         QVERIFY(stats.signCount() == 1);
464         QVERIFY(stats.policy() == TofuInfo::PolicyAuto);
465         QVERIFY(stats.validity() == TofuInfo::LittleHistory);
466
467         // Import key 2
468         importjob = openpgp()->importJob();
469         connect(importjob, &ImportJob::result, this,
470                 [this](ImportResult result, QString, Error)
471         {
472             QVERIFY(!result.error());
473             QVERIFY(!result.imports().empty());
474             QVERIFY(result.numImported());
475             Q_EMIT asyncDone();
476         });
477         importjob->start(QByteArray(conflictKey2));
478         QSignalSpy spy2 (this, SIGNAL(asyncDone()));
479         QVERIFY(spy2.wait());
480
481         // Verify Message 2
482         const QByteArray signedData2(conflictMsg2);
483         QByteArray verified2;
484         verifyJob = openpgp()->verifyOpaqueJob(true);
485         result = verifyJob->exec(signedData2, verified2);
486         delete verifyJob;
487
488         QVERIFY(!result.isNull());
489         QVERIFY(!result.error());
490
491         QVERIFY(result.numSignatures() == 1);
492         sig = result.signatures()[0];
493         QVERIFY(sig.validity() == Signature::Unknown);
494         // TODO activate when implemented
495         // QVERIFY(sig.summary() == Signature::TofuConflict);
496
497         stats = sig.key().userID(0).tofuInfo();
498         QVERIFY(!stats.isNull());
499         QVERIFY(!strcmp(sig.key().primaryFingerprint(), sig.fingerprint()));
500         QVERIFY(stats.signFirst() == stats.signLast());
501         QVERIFY(stats.signCount() == 1);
502         QVERIFY(stats.policy() == TofuInfo::PolicyAsk);
503         QVERIFY(stats.validity() == TofuInfo::Conflict);
504     }
505
506
507     void initTestCase()
508     {
509         QGpgMETest::initTestCase();
510         const QString gpgHome = qgetenv("GNUPGHOME");
511         qputenv("GNUPGHOME", mDir.path().toUtf8());
512         QVERIFY(mDir.isValid());
513         QFile conf(mDir.path() + QStringLiteral("/gpg.conf"));
514         QVERIFY(conf.open(QIODevice::WriteOnly));
515         conf.write("trust-model tofu+pgp");
516         conf.close();
517         QFile agentConf(mDir.path() + QStringLiteral("/gpg-agent.conf"));
518         QVERIFY(agentConf.open(QIODevice::WriteOnly));
519         agentConf.write("allow-loopback-pinentry");
520         agentConf.close();
521         QVERIFY(copyKeyrings(gpgHome, mDir.path()));
522     }
523 private:
524     QTemporaryDir mDir;
525
526 };
527
528 QTEST_MAIN(TofuInfoTest)
529
530 #include "t-tofuinfo.moc"