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