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