cpp, qt: Include config.h
[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 Intevation GmbH
5
6     QGpgME is free software; you can redistribute it and/or
7     modify it under the terms of the GNU General Public License as
8     published by the Free Software Foundation; either version 2 of the
9     License, or (at your option) any later version.
10
11     QGpgME is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14     General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with this program; if not, write to the Free Software
18     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19
20     In addition, as a special exception, the copyright holders give
21     permission to link the code of this program with any edition of
22     the Qt library by Trolltech AS, Norway (or with modified versions
23     of Qt that use the same license as Qt), and distribute linked
24     combinations including the two.  You must obey the GNU General
25     Public License in all respects for all of the code used other than
26     Qt.  If you modify this file, you may extend this exception to
27     your version of the file, but you are not obligated to do so.  If
28     you do not wish to do so, delete this exception statement from
29     your version.
30 */
31 #ifdef HAVE_CONFIG_H
32  #include "config.h"
33 #endif
34
35 #include <QDebug>
36 #include <QTest>
37 #include <QTemporaryDir>
38 #include "protocol.h"
39 #include "tofuinfo.h"
40 #include "tofupolicyjob.h"
41 #include "verifyopaquejob.h"
42 #include "verificationresult.h"
43 #include "signingresult.h"
44 #include "keylistjob.h"
45 #include "keylistresult.h"
46 #include "qgpgmesignjob.h"
47 #include "key.h"
48 #include "t-support.h"
49 #include "engineinfo.h"
50 #include <iostream>
51
52 using namespace QGpgME;
53 using namespace GpgME;
54
55 static const char testMsg1[] =
56 "-----BEGIN PGP MESSAGE-----\n"
57 "\n"
58 "owGbwMvMwCSoW1RzPCOz3IRxjXQSR0lqcYleSUWJTZOvjVdpcYmCu1+oQmaJIleH\n"
59 "GwuDIBMDGysTSIqBi1MApi+nlGGuwDeHao53HBr+FoVGP3xX+kvuu9fCMJvl6IOf\n"
60 "y1kvP4y+8D5a11ang0udywsA\n"
61 "=Crq6\n"
62 "-----END PGP MESSAGE-----\n";
63
64 class TofuInfoTest: public QGpgMETest
65 {
66     Q_OBJECT
67
68     bool testSupported()
69     {
70         return !(GpgME::engineInfo(GpgME::GpgEngine).engineVersion() < "2.1.16");
71     }
72
73     void testTofuCopy(TofuInfo other, const TofuInfo &orig)
74     {
75         Q_ASSERT(!orig.isNull());
76         Q_ASSERT(!other.isNull());
77         Q_ASSERT(orig.signLast() == other.signLast());
78         Q_ASSERT(orig.signCount() == other.signCount());
79         Q_ASSERT(orig.validity() == other.validity());
80         Q_ASSERT(orig.policy() == other.policy());
81     }
82
83     void signAndVerify(const QString &what, const GpgME::Key &key, int expected)
84     {
85         Context *ctx = Context::createForProtocol(OpenPGP);
86         TestPassphraseProvider provider;
87         ctx->setPassphraseProvider(&provider);
88         ctx->setPinentryMode(Context::PinentryLoopback);
89         auto *job = new QGpgMESignJob(ctx);
90
91         std::vector<Key> keys;
92         keys.push_back(key);
93         QByteArray signedData;
94         auto sigResult = job->exec(keys, what.toUtf8(), NormalSignatureMode, signedData);
95         delete job;
96
97         Q_ASSERT(!sigResult.error());
98         foreach (const auto uid, keys[0].userIDs()) {
99             auto info = uid.tofuInfo();
100             Q_ASSERT(info.signCount() == expected - 1);
101         }
102
103         auto verifyJob = openpgp()->verifyOpaqueJob();
104         QByteArray verified;
105
106         auto result = verifyJob->exec(signedData, verified);
107         delete verifyJob;
108
109         Q_ASSERT(!result.error());
110         Q_ASSERT(verified == what.toUtf8());
111
112         Q_ASSERT(result.numSignatures() == 1);
113         auto sig = result.signatures()[0];
114
115         auto key2 = sig.key();
116         Q_ASSERT(!key.isNull());
117         Q_ASSERT(!strcmp (key2.primaryFingerprint(), key.primaryFingerprint()));
118         Q_ASSERT(!strcmp (key.primaryFingerprint(), sig.fingerprint()));
119         auto stats = key2.userID(0).tofuInfo();
120         Q_ASSERT(!stats.isNull());
121         if (stats.signCount() != expected) {
122             std::cout << "################ Key before verify: "
123                       << key
124                       << "################ Key after verify: "
125                       << key2;
126         }
127         Q_ASSERT(stats.signCount() == expected);
128     }
129
130 private Q_SLOTS:
131     void testTofuNull()
132     {
133         if (!testSupported()) {
134             return;
135         }
136         TofuInfo tofu;
137         Q_ASSERT(tofu.isNull());
138         Q_ASSERT(!tofu.description());
139         Q_ASSERT(!tofu.signCount());
140         Q_ASSERT(!tofu.signLast());
141         Q_ASSERT(!tofu.signFirst());
142         Q_ASSERT(tofu.validity() == TofuInfo::ValidityUnknown);
143         Q_ASSERT(tofu.policy() == TofuInfo::PolicyUnknown);
144     }
145
146     void testTofuInfo()
147     {
148         if (!testSupported()) {
149             return;
150         }
151         auto *job = openpgp()->verifyOpaqueJob(true);
152         const QByteArray data1(testMsg1);
153         QByteArray plaintext;
154
155         auto result = job->exec(data1, plaintext);
156         delete job;
157
158         Q_ASSERT(!result.isNull());
159         Q_ASSERT(!result.error());
160         Q_ASSERT(!strcmp(plaintext.constData(), "Just GNU it!\n"));
161
162         Q_ASSERT(result.numSignatures() == 1);
163         Signature sig = result.signatures()[0];
164         /* TOFU is always marginal */
165         Q_ASSERT(sig.validity() == Signature::Marginal);
166
167         auto stats = sig.key().userID(0).tofuInfo();
168         Q_ASSERT(!stats.isNull());
169         Q_ASSERT(sig.key().primaryFingerprint());
170         Q_ASSERT(sig.fingerprint());
171         Q_ASSERT(!strcmp(sig.key().primaryFingerprint(), sig.fingerprint()));
172         Q_ASSERT(stats.signFirst() == stats.signLast());
173         Q_ASSERT(stats.signCount() == 1);
174         Q_ASSERT(stats.policy() == TofuInfo::PolicyAuto);
175         Q_ASSERT(stats.validity() == TofuInfo::LittleHistory);
176
177         testTofuCopy(stats, stats);
178
179         /* Another verify */
180
181         job = openpgp()->verifyOpaqueJob(true);
182         result = job->exec(data1, plaintext);
183         delete job;
184
185         Q_ASSERT(!result.isNull());
186         Q_ASSERT(!result.error());
187
188         Q_ASSERT(result.numSignatures() == 1);
189         sig = result.signatures()[0];
190         /* TOFU is always marginal */
191         Q_ASSERT(sig.validity() == Signature::Marginal);
192
193         stats = sig.key().userID(0).tofuInfo();
194         Q_ASSERT(!stats.isNull());
195         Q_ASSERT(!strcmp(sig.key().primaryFingerprint(), sig.fingerprint()));
196         Q_ASSERT(stats.signFirst() == stats.signLast());
197         Q_ASSERT(stats.signCount() == 1);
198         Q_ASSERT(stats.policy() == TofuInfo::PolicyAuto);
199         Q_ASSERT(stats.validity() == TofuInfo::LittleHistory);
200
201         /* Verify that another call yields the same result */
202         job = openpgp()->verifyOpaqueJob(true);
203         result = job->exec(data1, plaintext);
204         delete job;
205
206         Q_ASSERT(!result.isNull());
207         Q_ASSERT(!result.error());
208
209         Q_ASSERT(result.numSignatures() == 1);
210         sig = result.signatures()[0];
211         /* TOFU is always marginal */
212         Q_ASSERT(sig.validity() == Signature::Marginal);
213
214         stats = sig.key().userID(0).tofuInfo();
215         Q_ASSERT(!stats.isNull());
216         Q_ASSERT(!strcmp(sig.key().primaryFingerprint(), sig.fingerprint()));
217         Q_ASSERT(stats.signFirst() == stats.signLast());
218         Q_ASSERT(stats.signCount() == 1);
219         Q_ASSERT(stats.policy() == TofuInfo::PolicyAuto);
220         Q_ASSERT(stats.validity() == TofuInfo::LittleHistory);
221     }
222
223     void testTofuSignCount()
224     {
225         if (!testSupported()) {
226             return;
227         }
228         auto *job = openpgp()->keyListJob(false, false, false);
229         job->addMode(GpgME::WithTofu);
230         std::vector<GpgME::Key> keys;
231         GpgME::KeyListResult result = job->exec(QStringList() << QStringLiteral("zulu@example.net"),
232                                                 true, keys);
233         delete job;
234         Q_ASSERT(!keys.empty());
235         Key key = keys[0];
236         Q_ASSERT(!key.isNull());
237
238         /* As we sign & verify quickly here we need different
239          * messages to avoid having them treated as the same
240          * message if they were created within the same second.
241          * Alternatively we could use the same message and wait
242          * a second between each call. But this would slow down
243          * the testsuite. */
244         signAndVerify(QStringLiteral("Hello"), key, 1);
245         key.update();
246         signAndVerify(QStringLiteral("Hello2"), key, 2);
247         key.update();
248         signAndVerify(QStringLiteral("Hello3"), key, 3);
249         key.update();
250         signAndVerify(QStringLiteral("Hello4"), key, 4);
251     }
252
253     void testTofuKeyList()
254     {
255         if (!testSupported()) {
256             return;
257         }
258
259         /* First check that the key has no tofu info. */
260         auto *job = openpgp()->keyListJob(false, false, false);
261         std::vector<GpgME::Key> keys;
262         auto result = job->exec(QStringList() << QStringLiteral("zulu@example.net"),
263                                                  true, keys);
264         delete job;
265         Q_ASSERT(!keys.empty());
266         auto key = keys[0];
267         Q_ASSERT(!key.isNull());
268         Q_ASSERT(key.userID(0).tofuInfo().isNull());
269         auto keyCopy = key;
270         keyCopy.update();
271         auto sigCnt = keyCopy.userID(0).tofuInfo().signCount();
272         signAndVerify(QStringLiteral("Hello5"), keyCopy,
273                       sigCnt + 1);
274         keyCopy.update();
275         signAndVerify(QStringLiteral("Hello6"), keyCopy,
276                       sigCnt + 2);
277
278         /* Now another one but with tofu */
279         job = openpgp()->keyListJob(false, false, false);
280         job->addMode(GpgME::WithTofu);
281         result = job->exec(QStringList() << QStringLiteral("zulu@example.net"),
282                            true, keys);
283         delete job;
284         Q_ASSERT(!result.error());
285         Q_ASSERT(!keys.empty());
286         auto key2 = keys[0];
287         Q_ASSERT(!key2.isNull());
288         auto info = key2.userID(0).tofuInfo();
289         Q_ASSERT(!info.isNull());
290         Q_ASSERT(info.signCount());
291     }
292
293     void testTofuPolicy()
294     {
295         if (!testSupported()) {
296             return;
297         }
298
299         /* First check that the key has no tofu info. */
300         auto *job = openpgp()->keyListJob(false, false, false);
301         std::vector<GpgME::Key> keys;
302         job->addMode(GpgME::WithTofu);
303         auto result = job->exec(QStringList() << QStringLiteral("bravo@example.net"),
304                                                  false, keys);
305
306         if (keys.empty()) {
307             qDebug() << "bravo@example.net not found";
308             qDebug() << "Error: " << result.error().asString();
309             const auto homedir = QString::fromLocal8Bit(qgetenv("GNUPGHOME"));
310             qDebug() << "Homedir is: " << homedir;
311             QFileInfo fi(homedir + "/pubring.gpg");
312             qDebug () << "pubring exists: " << fi.exists() << " readable? "
313                       << fi.isReadable() << " size: " << fi.size();
314             QFileInfo fi2(homedir + "/pubring.kbx");
315             qDebug () << "keybox exists: " << fi2.exists() << " readable? "
316                       << fi2.isReadable() << " size: " << fi2.size();
317
318             result = job->exec(QStringList(), false, keys);
319             foreach (const auto key, keys) {
320                 qDebug() << "Key: " << key.userID(0).name() << " <"
321                          << key.userID(0).email()
322                          << ">\n fpr: " << key.primaryFingerprint();
323             }
324         }
325         Q_ASSERT(!result.error());
326         Q_ASSERT(!keys.empty());
327         auto key = keys[0];
328         Q_ASSERT(!key.isNull());
329         Q_ASSERT(key.userID(0).tofuInfo().policy() != TofuInfo::PolicyBad);
330         auto *tofuJob = openpgp()->tofuPolicyJob();
331         auto err = tofuJob->exec(key, TofuInfo::PolicyBad);
332         Q_ASSERT(!err);
333         result = job->exec(QStringList() << QStringLiteral("bravo@example.net"),
334                                             false, keys);
335         Q_ASSERT(!keys.empty());
336         key = keys[0];
337         Q_ASSERT(key.userID(0).tofuInfo().policy() == TofuInfo::PolicyBad);
338         err = tofuJob->exec(key, TofuInfo::PolicyGood);
339
340         result = job->exec(QStringList() << QStringLiteral("bravo@example.net"),
341                                             false, keys);
342         key = keys[0];
343         Q_ASSERT(key.userID(0).tofuInfo().policy() == TofuInfo::PolicyGood);
344         delete tofuJob;
345         delete job;
346     }
347
348     void initTestCase()
349     {
350         QGpgMETest::initTestCase();
351         const QString gpgHome = qgetenv("GNUPGHOME");
352         qputenv("GNUPGHOME", mDir.path().toUtf8());
353         Q_ASSERT(mDir.isValid());
354         QFile conf(mDir.path() + QStringLiteral("/gpg.conf"));
355         Q_ASSERT(conf.open(QIODevice::WriteOnly));
356         conf.write("trust-model tofu+pgp");
357         conf.close();
358         QFile agentConf(mDir.path() + QStringLiteral("/gpg-agent.conf"));
359         Q_ASSERT(agentConf.open(QIODevice::WriteOnly));
360         agentConf.write("allow-loopback-pinentry");
361         agentConf.close();
362         Q_ASSERT(copyKeyrings(gpgHome, mDir.path()));
363     }
364 private:
365     QTemporaryDir mDir;
366
367 };
368
369 QTEST_MAIN(TofuInfoTest)
370
371 #include "t-tofuinfo.moc"