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