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