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