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