1 | /** |
2 | * Copyright (C) 2006-2007 Brad Hards <bradh@frogmouth.net> |
3 | * |
4 | * Redistribution and use in source and binary forms, with or without |
5 | * modification, are permitted provided that the following conditions |
6 | * are met: |
7 | * |
8 | * 1. Redistributions of source code must retain the above copyright |
9 | * notice, this list of conditions and the following disclaimer. |
10 | * 2. Redistributions in binary form must reproduce the above copyright |
11 | * notice, this list of conditions and the following disclaimer in the |
12 | * documentation and/or other materials provided with the distribution. |
13 | * |
14 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
15 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
16 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
17 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
18 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
19 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
20 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
21 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
22 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
23 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
24 | */ |
25 | |
26 | #include <QtCrypto> |
27 | #include <QtTest/QtTest> |
28 | |
29 | #include <cstdlib> |
30 | |
31 | #ifdef QT_STATICPLUGIN |
32 | #include "import_plugins.h" |
33 | #endif |
34 | |
35 | // qt did not introduce qputenv until 4.4, so we'll keep a copy here for 4.2 |
36 | // compat |
37 | bool my_qputenv(const char *varName, const QByteArray &value) |
38 | { |
39 | #if defined(_MSC_VER) && _MSC_VER >= 1400 |
40 | return _putenv_s(varName, value.constData()) == 0; |
41 | #else |
42 | QByteArray buffer(varName); |
43 | buffer += "=" ; |
44 | buffer += value; |
45 | return putenv(string: qstrdup(buffer.constData())) == 0; |
46 | #endif |
47 | } |
48 | |
49 | static int qca_setenv(const char *name, const char *value, int overwrite) |
50 | { |
51 | if (!overwrite && qEnvironmentVariableIsSet(varName: name)) |
52 | return 0; |
53 | |
54 | if (my_qputenv(varName: name, value: QByteArray(value))) |
55 | return 0; // success |
56 | else |
57 | return 1; // error |
58 | } |
59 | |
60 | // Note; in a real application you get this from a user, but this |
61 | // is a useful trick for a unit test. |
62 | // See the qcatool application or keyloader and eventhandler examples |
63 | // for how to do this properly. |
64 | class PGPPassphraseProvider : public QObject |
65 | { |
66 | Q_OBJECT |
67 | public: |
68 | PGPPassphraseProvider(QObject *parent = nullptr) |
69 | : QObject(parent) |
70 | { |
71 | connect(sender: &m_handler, signal: &QCA::EventHandler::eventReady, context: this, slot: &PGPPassphraseProvider::eh_eventReady); |
72 | m_handler.start(); |
73 | } |
74 | |
75 | private Q_SLOTS: |
76 | void eh_eventReady(int id, const QCA::Event &event) |
77 | { |
78 | if (event.type() == QCA::Event::Password) { |
79 | QCA::SecureArray pass("start" ); |
80 | m_handler.submitPassword(id, password: pass); |
81 | } else { |
82 | m_handler.reject(id); |
83 | } |
84 | } |
85 | |
86 | private: |
87 | QCA::EventHandler m_handler; |
88 | }; |
89 | |
90 | class PGPPassphraseProviderThread : public QCA::SyncThread |
91 | { |
92 | Q_OBJECT |
93 | public: |
94 | ~PGPPassphraseProviderThread() override |
95 | { |
96 | stop(); |
97 | } |
98 | |
99 | protected: |
100 | void atStart() override |
101 | { |
102 | prov = new PGPPassphraseProvider; |
103 | } |
104 | |
105 | void atEnd() override |
106 | { |
107 | delete prov; |
108 | } |
109 | |
110 | private: |
111 | PGPPassphraseProvider *prov; |
112 | }; |
113 | |
114 | class PgpUnitTest : public QObject |
115 | { |
116 | Q_OBJECT |
117 | |
118 | private Q_SLOTS: |
119 | void initTestCase(); |
120 | void cleanupTestCase(); |
121 | void testKeyRing(); |
122 | void testMessageSign(); |
123 | void testClearsign(); |
124 | void testDetachedSign(); |
125 | void testSignaturesWithExpiredSubkeys(); |
126 | void testEncryptionWithExpiredSubkeys(); |
127 | }; |
128 | |
129 | void PgpUnitTest::initTestCase() |
130 | { |
131 | // Change current directory to executable directory |
132 | // it is need to find keys*_work directories |
133 | if (!QCoreApplication::applicationDirPath().isEmpty()) |
134 | QDir::setCurrent(QCoreApplication::applicationDirPath()); |
135 | } |
136 | |
137 | void PgpUnitTest::cleanupTestCase() |
138 | { |
139 | } |
140 | |
141 | void PgpUnitTest::testKeyRing() |
142 | { |
143 | QCA::Initializer *qcaInit = new QCA::Initializer; |
144 | |
145 | // We test a small keyring - I downloaded a publically available one from |
146 | QByteArray oldGNUPGHOME = qgetenv(varName: "GNUPGHOME" ); |
147 | // the Amsterdam Internet Exchange. |
148 | if (qca_setenv(name: "GNUPGHOME" , value: "./keys1_work" , overwrite: 1) != 0) { |
149 | QFAIL("Expected to be able to set the GNUPGHOME environment variable, but couldn't" ); |
150 | } |
151 | |
152 | // activate the KeyStoreManager |
153 | QCA::KeyStoreManager::start(); |
154 | |
155 | if (QCA::isSupported(features: QStringList(QStringLiteral("keystorelist" )), QStringLiteral("qca-gnupg" ))) { |
156 | QCA::KeyStoreManager keyManager(this); |
157 | keyManager.waitForBusyFinished(); |
158 | QStringList storeIds = keyManager.keyStores(); |
159 | QVERIFY(storeIds.contains(QStringLiteral("qca-gnupg" ))); |
160 | |
161 | QCA::KeyStore pgpStore(QStringLiteral("qca-gnupg" ), &keyManager); |
162 | QVERIFY(pgpStore.isValid()); |
163 | QCOMPARE(pgpStore.name(), QStringLiteral("GnuPG Keyring" )); |
164 | QCOMPARE(pgpStore.type(), QCA::KeyStore::PGPKeyring); |
165 | QCOMPARE(pgpStore.id(), QStringLiteral("qca-gnupg" )); |
166 | QCOMPARE(pgpStore.isReadOnly(), false); |
167 | QCOMPARE(pgpStore.holdsTrustedCertificates(), false); |
168 | QCOMPARE(pgpStore.holdsIdentities(), true); |
169 | QCOMPARE(pgpStore.holdsPGPPublicKeys(), true); |
170 | |
171 | QList<QCA::KeyStoreEntry> keylist = pgpStore.entryList(); |
172 | QCOMPARE(keylist.count(), 6); |
173 | QStringList nameList; |
174 | foreach (const QCA::KeyStoreEntry key, keylist) { |
175 | QCOMPARE(key.isNull(), false); |
176 | QCOMPARE(key.type(), QCA::KeyStoreEntry::TypePGPPublicKey); |
177 | QCOMPARE(key.id().length(), 16); // 16 hex digits |
178 | QVERIFY(key.keyBundle().isNull()); |
179 | QVERIFY(key.certificate().isNull()); |
180 | QVERIFY(key.crl().isNull()); |
181 | QVERIFY(key.pgpSecretKey().isNull()); |
182 | QCOMPARE(key.pgpPublicKey().isNull(), false); |
183 | |
184 | // We accumulate the names, and check them next |
185 | nameList << key.name(); |
186 | } |
187 | QVERIFY(nameList.contains(QStringLiteral("Steven Bakker <steven.bakker@ams-ix.net>" ))); |
188 | QVERIFY(nameList.contains(QStringLiteral("Romeo Zwart <rz@ams-ix.net>" ))); |
189 | QVERIFY(nameList.contains(QStringLiteral("Arien Vijn <arien.vijn@ams-ix.net>" ))); |
190 | QVERIFY(nameList.contains(QStringLiteral("Niels Bakker <niels.bakker@ams-ix.net>" ))); |
191 | QVERIFY(nameList.contains(QStringLiteral("Henk Steenman <Henk.Steenman@ams-ix.net>" ))); |
192 | QVERIFY(nameList.contains(QStringLiteral("Geert Nijpels <geert.nijpels@ams-ix.net>" ))); |
193 | |
194 | // TODO: We should test removeEntry() and writeEntry() here. |
195 | } |
196 | |
197 | delete qcaInit; |
198 | qcaInit = new QCA::Initializer; |
199 | |
200 | // We now test an empty keyring |
201 | if (qca_setenv(name: "GNUPGHOME" , value: "./keys2_work" , overwrite: 1) != 0) { |
202 | QFAIL("Expected to be able to set the GNUPGHOME environment variable, but couldn't" ); |
203 | } |
204 | |
205 | QCA::KeyStoreManager::start(); |
206 | |
207 | if (QCA::isSupported(features: QStringList(QStringLiteral("keystorelist" )), QStringLiteral("qca-gnupg" ))) { |
208 | QCA::KeyStoreManager keyManager(this); |
209 | keyManager.waitForBusyFinished(); |
210 | QStringList storeIds = keyManager.keyStores(); |
211 | QVERIFY(storeIds.contains(QStringLiteral("qca-gnupg" ))); |
212 | |
213 | QCA::KeyStore pgpStore(QStringLiteral("qca-gnupg" ), &keyManager); |
214 | |
215 | QList<QCA::KeyStoreEntry> keylist = pgpStore.entryList(); |
216 | QCOMPARE(keylist.count(), 0); |
217 | // TODO: We should test removeEntry() and writeEntry() here. |
218 | } |
219 | |
220 | if (false == oldGNUPGHOME.isNull()) { |
221 | qca_setenv(name: "GNUPGHOME" , value: oldGNUPGHOME.data(), overwrite: 1); |
222 | } |
223 | |
224 | delete qcaInit; |
225 | } |
226 | |
227 | void PgpUnitTest::testMessageSign() |
228 | { |
229 | QCA::Initializer qcaInit; |
230 | |
231 | // event handling cannot be used in the same thread as synchronous calls |
232 | // which might require event handling. let's put our event handler in |
233 | // a side thread so that we can write the unit test synchronously. |
234 | PGPPassphraseProviderThread thread; |
235 | thread.start(); |
236 | |
237 | // This keyring has a private / public key pair |
238 | QByteArray oldGNUPGHOME = qgetenv(varName: "GNUPGHOME" ); |
239 | if (0 != qca_setenv(name: "GNUPGHOME" , value: "./keys3_work" , overwrite: 1)) { |
240 | QFAIL("Expected to be able to set the GNUPGHOME environment variable, but couldn't" ); |
241 | } |
242 | |
243 | // activate the KeyStoreManager |
244 | QCA::KeyStoreManager::start(); |
245 | |
246 | QCA::KeyStoreManager keyManager(this); |
247 | keyManager.waitForBusyFinished(); |
248 | |
249 | if (QCA::isSupported(features: QStringList(QStringLiteral("openpgp" )), QStringLiteral("qca-gnupg" )) || |
250 | QCA::isSupported(features: QStringList(QStringLiteral("keystorelist" )), QStringLiteral("qca-gnupg" ))) { |
251 | QStringList storeIds = keyManager.keyStores(); |
252 | QVERIFY(storeIds.contains(QStringLiteral("qca-gnupg" ))); |
253 | |
254 | QCA::KeyStore pgpStore(QStringLiteral("qca-gnupg" ), &keyManager); |
255 | QVERIFY(pgpStore.isValid()); |
256 | |
257 | QList<QCA::KeyStoreEntry> keylist = pgpStore.entryList(); |
258 | QCOMPARE(keylist.count(), 1); |
259 | |
260 | const QCA::KeyStoreEntry &myPGPKey = keylist.at(i: 0); |
261 | QCOMPARE(myPGPKey.isNull(), false); |
262 | QCOMPARE(myPGPKey.name(), |
263 | QStringLiteral("Qca Test Key (This key is only for QCA unit tests) <qca@example.com>" )); |
264 | QCOMPARE(myPGPKey.type(), QCA::KeyStoreEntry::TypePGPSecretKey); |
265 | QCOMPARE(myPGPKey.id(), QStringLiteral("9E946237DAFCCFF4" )); |
266 | QVERIFY(myPGPKey.keyBundle().isNull()); |
267 | QVERIFY(myPGPKey.certificate().isNull()); |
268 | QVERIFY(myPGPKey.crl().isNull()); |
269 | QCOMPARE(myPGPKey.pgpSecretKey().isNull(), false); |
270 | QCOMPARE(myPGPKey.pgpPublicKey().isNull(), false); |
271 | |
272 | // first make the SecureMessageKey |
273 | QCA::SecureMessageKey key; |
274 | key.setPGPSecretKey(myPGPKey.pgpSecretKey()); |
275 | QVERIFY(key.havePrivate()); |
276 | |
277 | // our data to sign |
278 | QByteArray plain = "Hello, world" ; |
279 | |
280 | // let's do it |
281 | QCA::OpenPGP pgp; |
282 | QCA::SecureMessage msg(&pgp); |
283 | msg.setSigner(key); |
284 | msg.setFormat(QCA::SecureMessage::Ascii); |
285 | msg.startSign(m: QCA::SecureMessage::Message); |
286 | msg.update(in: plain); |
287 | msg.end(); |
288 | msg.waitForFinished(msecs: 5000); |
289 | |
290 | #if 0 |
291 | QString str = QCA::KeyStoreManager::diagnosticText(); |
292 | QCA::KeyStoreManager::clearDiagnosticText(); |
293 | QStringList lines = str.split('\n', Qt::SkipEmptyParts); |
294 | for(int n = 0; n < lines.count(); ++n) |
295 | fprintf(stderr, "keystore: %s\n" , qPrintable(lines[n])); |
296 | |
297 | QString out = msg.diagnosticText(); |
298 | QStringList msglines = out.split('\n', Qt::SkipEmptyParts); |
299 | for(int n = 0; n < msglines.count(); ++n) |
300 | fprintf(stderr, "message: %s\n" , qPrintable(msglines[n])); |
301 | #endif |
302 | QByteArray messageData; |
303 | if (msg.success()) { |
304 | messageData = msg.read(); |
305 | } else { |
306 | qDebug() << "Failure:" << msg.errorCode(); |
307 | QFAIL("Failed to sign in Message format" ); |
308 | } |
309 | // qDebug() << "Message format data:" << messageData; |
310 | |
311 | // OK, now lets verify that the result will verify. |
312 | // let's do it |
313 | QCA::OpenPGP pgp2; |
314 | QCA::SecureMessage msg2(&pgp2); |
315 | msg2.setFormat(QCA::SecureMessage::Ascii); |
316 | msg2.startVerify(); |
317 | msg2.update(in: messageData); |
318 | msg2.end(); |
319 | msg2.waitForFinished(msecs: 5000); |
320 | |
321 | QVERIFY(msg2.verifySuccess()); |
322 | |
323 | if (msg2.success()) { |
324 | QCOMPARE(msg2.read(), plain); |
325 | } else { |
326 | qDebug() << "Failure:" << msg2.errorCode(); |
327 | QFAIL("Failed to verify message" ); |
328 | } |
329 | |
330 | // why is this here? |
331 | if (false == oldGNUPGHOME.isNull()) { |
332 | qca_setenv(name: "GNUPGHOME" , value: oldGNUPGHOME.data(), overwrite: 1); |
333 | } |
334 | |
335 | // now test that if we corrupt the message, it no longer |
336 | // verifies correctly. |
337 | messageData.replace(before: 'T', after: 't'); |
338 | messageData.replace(before: 'w', after: 'W'); |
339 | QCA::SecureMessage msg3(&pgp2); |
340 | msg3.setFormat(QCA::SecureMessage::Ascii); |
341 | msg3.startVerify(); |
342 | msg3.update(in: messageData); |
343 | msg3.end(); |
344 | msg3.waitForFinished(msecs: 5000); |
345 | |
346 | QCOMPARE(msg3.verifySuccess(), false); |
347 | QCOMPARE(msg3.errorCode(), QCA::SecureMessage::ErrorUnknown); |
348 | } |
349 | |
350 | if (false == oldGNUPGHOME.isNull()) { |
351 | qca_setenv(name: "GNUPGHOME" , value: oldGNUPGHOME.data(), overwrite: 1); |
352 | } |
353 | } |
354 | |
355 | void PgpUnitTest::testClearsign() |
356 | { |
357 | QCA::Initializer qcaInit; |
358 | |
359 | // event handling cannot be used in the same thread as synchronous calls |
360 | // which might require event handling. let's put our event handler in |
361 | // a side thread so that we can write the unit test synchronously. |
362 | PGPPassphraseProviderThread thread; |
363 | thread.start(); |
364 | |
365 | // This keyring has a private / public key pair |
366 | QByteArray oldGNUPGHOME = qgetenv(varName: "GNUPGHOME" ); |
367 | if (0 != qca_setenv(name: "GNUPGHOME" , value: "./keys3_work" , overwrite: 1)) { |
368 | QFAIL("Expected to be able to set the GNUPGHOME environment variable, but couldn't" ); |
369 | } |
370 | |
371 | // activate the KeyStoreManager |
372 | QCA::KeyStoreManager::start(); |
373 | |
374 | QCA::KeyStoreManager keyManager(this); |
375 | keyManager.waitForBusyFinished(); |
376 | |
377 | if (QCA::isSupported(features: QStringList(QStringLiteral("openpgp" )), QStringLiteral("qca-gnupg" )) || |
378 | QCA::isSupported(features: QStringList(QStringLiteral("keystorelist" )), QStringLiteral("qca-gnupg" ))) { |
379 | QStringList storeIds = keyManager.keyStores(); |
380 | QVERIFY(storeIds.contains(QStringLiteral("qca-gnupg" ))); |
381 | |
382 | QCA::KeyStore pgpStore(QStringLiteral("qca-gnupg" ), &keyManager); |
383 | QVERIFY(pgpStore.isValid()); |
384 | |
385 | QList<QCA::KeyStoreEntry> keylist = pgpStore.entryList(); |
386 | QCOMPARE(keylist.count(), 1); |
387 | |
388 | const QCA::KeyStoreEntry &myPGPKey = keylist.at(i: 0); |
389 | QCOMPARE(myPGPKey.isNull(), false); |
390 | QCOMPARE(myPGPKey.name(), |
391 | QStringLiteral("Qca Test Key (This key is only for QCA unit tests) <qca@example.com>" )); |
392 | QCOMPARE(myPGPKey.type(), QCA::KeyStoreEntry::TypePGPSecretKey); |
393 | QCOMPARE(myPGPKey.id(), QStringLiteral("9E946237DAFCCFF4" )); |
394 | QVERIFY(myPGPKey.keyBundle().isNull()); |
395 | QVERIFY(myPGPKey.certificate().isNull()); |
396 | QVERIFY(myPGPKey.crl().isNull()); |
397 | QCOMPARE(myPGPKey.pgpSecretKey().isNull(), false); |
398 | QCOMPARE(myPGPKey.pgpPublicKey().isNull(), false); |
399 | |
400 | // first make the SecureMessageKey |
401 | QCA::SecureMessageKey key; |
402 | key.setPGPSecretKey(myPGPKey.pgpSecretKey()); |
403 | QVERIFY(key.havePrivate()); |
404 | |
405 | // our data to sign |
406 | QByteArray plain = "Hello, world" ; |
407 | |
408 | // let's do it |
409 | QCA::OpenPGP pgp; |
410 | QCA::SecureMessage msg(&pgp); |
411 | msg.setSigner(key); |
412 | msg.setFormat(QCA::SecureMessage::Ascii); |
413 | msg.startSign(m: QCA::SecureMessage::Clearsign); |
414 | msg.update(in: plain); |
415 | msg.end(); |
416 | msg.waitForFinished(msecs: 5000); |
417 | |
418 | #if 0 |
419 | QString str = QCA::KeyStoreManager::diagnosticText(); |
420 | QCA::KeyStoreManager::clearDiagnosticText(); |
421 | QStringList lines = str.split('\n', Qt::SkipEmptyParts); |
422 | for(int n = 0; n < lines.count(); ++n) |
423 | fprintf(stderr, "keystore: %s\n" , qPrintable(lines[n])); |
424 | |
425 | QString out = msg.diagnosticText(); |
426 | QStringList msglines = out.split('\n', Qt::SkipEmptyParts); |
427 | for(int n = 0; n < msglines.count(); ++n) |
428 | fprintf(stderr, "message: %s\n" , qPrintable(msglines[n])); |
429 | #endif |
430 | |
431 | QByteArray clearsignedData; |
432 | if (msg.success()) { |
433 | clearsignedData = msg.read(); |
434 | } else { |
435 | qDebug() << "Failure:" << msg.errorCode(); |
436 | QFAIL("Failed to clearsign" ); |
437 | } |
438 | |
439 | // OK, now lets verify that the result will verify. |
440 | // let's do it |
441 | QCA::OpenPGP pgp2; |
442 | QCA::SecureMessage msg2(&pgp2); |
443 | msg2.setFormat(QCA::SecureMessage::Ascii); |
444 | msg2.startVerify(); |
445 | msg2.update(in: clearsignedData); |
446 | msg2.end(); |
447 | msg2.waitForFinished(msecs: 5000); |
448 | |
449 | QVERIFY(msg2.verifySuccess()); |
450 | |
451 | if (msg2.success()) { |
452 | // The trimmed() call is needed because clearsigning |
453 | // trashes whitespace |
454 | QCOMPARE(msg2.read().trimmed(), plain.trimmed()); |
455 | } else { |
456 | qDebug() << "Failure:" << msg2.errorCode(); |
457 | QFAIL("Failed to verify clearsigned message" ); |
458 | } |
459 | } |
460 | |
461 | if (false == oldGNUPGHOME.isNull()) { |
462 | qca_setenv(name: "GNUPGHOME" , value: oldGNUPGHOME.data(), overwrite: 1); |
463 | } |
464 | } |
465 | |
466 | void PgpUnitTest::testDetachedSign() |
467 | { |
468 | QCA::Initializer qcaInit; |
469 | |
470 | // event handling cannot be used in the same thread as synchronous calls |
471 | // which might require event handling. let's put our event handler in |
472 | // a side thread so that we can write the unit test synchronously. |
473 | PGPPassphraseProviderThread thread; |
474 | thread.start(); |
475 | |
476 | // This keyring has a private / public key pair |
477 | QByteArray oldGNUPGHOME = qgetenv(varName: "GNUPGHOME" ); |
478 | if (0 != qca_setenv(name: "GNUPGHOME" , value: "./keys3_work" , overwrite: 1)) { |
479 | QFAIL("Expected to be able to set the GNUPGHOME environment variable, but couldn't" ); |
480 | } |
481 | |
482 | // activate the KeyStoreManager |
483 | QCA::KeyStoreManager::start(); |
484 | |
485 | QCA::KeyStoreManager keyManager(this); |
486 | keyManager.waitForBusyFinished(); |
487 | |
488 | if (QCA::isSupported(features: QStringList(QStringLiteral("openpgp" )), QStringLiteral("qca-gnupg" )) || |
489 | QCA::isSupported(features: QStringList(QStringLiteral("keystorelist" )), QStringLiteral("qca-gnupg" ))) { |
490 | QStringList storeIds = keyManager.keyStores(); |
491 | QVERIFY(storeIds.contains(QStringLiteral("qca-gnupg" ))); |
492 | |
493 | QCA::KeyStore pgpStore(QStringLiteral("qca-gnupg" ), &keyManager); |
494 | QVERIFY(pgpStore.isValid()); |
495 | |
496 | QList<QCA::KeyStoreEntry> keylist = pgpStore.entryList(); |
497 | QCOMPARE(keylist.count(), 1); |
498 | |
499 | const QCA::KeyStoreEntry &myPGPKey = keylist.at(i: 0); |
500 | QCOMPARE(myPGPKey.isNull(), false); |
501 | QCOMPARE(myPGPKey.name(), |
502 | QStringLiteral("Qca Test Key (This key is only for QCA unit tests) <qca@example.com>" )); |
503 | QCOMPARE(myPGPKey.type(), QCA::KeyStoreEntry::TypePGPSecretKey); |
504 | QCOMPARE(myPGPKey.id(), QStringLiteral("9E946237DAFCCFF4" )); |
505 | QVERIFY(myPGPKey.keyBundle().isNull()); |
506 | QVERIFY(myPGPKey.certificate().isNull()); |
507 | QVERIFY(myPGPKey.crl().isNull()); |
508 | QCOMPARE(myPGPKey.pgpSecretKey().isNull(), false); |
509 | QCOMPARE(myPGPKey.pgpPublicKey().isNull(), false); |
510 | |
511 | // first make the SecureMessageKey |
512 | QCA::SecureMessageKey key; |
513 | key.setPGPSecretKey(myPGPKey.pgpSecretKey()); |
514 | QVERIFY(key.havePrivate()); |
515 | |
516 | // our data to sign |
517 | QByteArray plain = "Hello, world" ; |
518 | |
519 | // let's do it |
520 | QCA::OpenPGP pgp; |
521 | QCA::SecureMessage msg(&pgp); |
522 | msg.setSigner(key); |
523 | msg.setFormat(QCA::SecureMessage::Ascii); |
524 | msg.startSign(m: QCA::SecureMessage::Detached); |
525 | msg.update(in: plain); |
526 | msg.end(); |
527 | msg.waitForFinished(msecs: 5000); |
528 | |
529 | #if 0 |
530 | QString str = QCA::KeyStoreManager::diagnosticText(); |
531 | QCA::KeyStoreManager::clearDiagnosticText(); |
532 | QStringList lines = str.split('\n', Qt::SkipEmptyParts); |
533 | for(int n = 0; n < lines.count(); ++n) |
534 | fprintf(stderr, "keystore: %s\n" , qPrintable(lines[n])); |
535 | |
536 | QString out = msg.diagnosticText(); |
537 | QStringList msglines = out.split('\n', Qt::SkipEmptyParts); |
538 | for(int n = 0; n < msglines.count(); ++n) |
539 | fprintf(stderr, "message: %s\n" , qPrintable(msglines[n])); |
540 | #endif |
541 | |
542 | QByteArray detachedSignature; |
543 | if (msg.success()) { |
544 | detachedSignature = msg.signature(); |
545 | } else { |
546 | qDebug() << "Failure:" << msg.errorCode(); |
547 | QFAIL("Failed to create detached signature" ); |
548 | } |
549 | |
550 | // qDebug() << "result:" << detachedSignature; |
551 | |
552 | // OK, now lets verify that the resulting signature will verify. |
553 | // let's do it |
554 | QCA::OpenPGP pgp2; |
555 | QCA::SecureMessage msg2(&pgp2); |
556 | msg2.setFormat(QCA::SecureMessage::Ascii); |
557 | msg2.startVerify(detachedSig: detachedSignature); |
558 | msg2.update(in: plain); |
559 | msg2.end(); |
560 | msg2.waitForFinished(msecs: 2000); |
561 | |
562 | QVERIFY(msg2.verifySuccess()); |
563 | |
564 | // If the message is different, it shouldn't verify any more |
565 | QCA::SecureMessage msg3(&pgp2); |
566 | msg3.setFormat(QCA::SecureMessage::Ascii); |
567 | msg3.startVerify(detachedSig: detachedSignature); |
568 | msg3.update(in: plain + '1'); |
569 | msg3.end(); |
570 | msg3.waitForFinished(msecs: 2000); |
571 | |
572 | QCOMPARE(msg3.verifySuccess(), false); |
573 | |
574 | QCOMPARE(msg3.errorCode(), QCA::SecureMessage::ErrorUnknown); |
575 | } |
576 | |
577 | // Restore things to the way they were.... |
578 | if (false == oldGNUPGHOME.isNull()) { |
579 | qca_setenv(name: "GNUPGHOME" , value: oldGNUPGHOME.data(), overwrite: 1); |
580 | } |
581 | } |
582 | |
583 | void PgpUnitTest::testSignaturesWithExpiredSubkeys() |
584 | { |
585 | // This previously failing test tests signatures from |
586 | // keys with expired subkeys, while assuring no loss |
587 | // of functionality. |
588 | |
589 | QCA::Initializer qcaInit; |
590 | |
591 | PGPPassphraseProviderThread thread; |
592 | thread.start(); |
593 | |
594 | QByteArray oldGNUPGHOME = qgetenv(varName: "GNUPGHOME" ); |
595 | if (qca_setenv(name: "GNUPGHOME" , value: "./keys4_expired_subkeys_work" , overwrite: 1) != 0) { |
596 | QFAIL("Expected to be able to set the GNUPGHOME environment variable, but couldn't" ); |
597 | } |
598 | |
599 | QCA::KeyStoreManager::start(); |
600 | |
601 | QCA::KeyStoreManager keyManager(this); |
602 | keyManager.waitForBusyFinished(); |
603 | |
604 | if (QCA::isSupported(features: QStringList(QStringLiteral("openpgp" )), QStringLiteral("qca-gnupg" )) || |
605 | QCA::isSupported(features: QStringList(QStringLiteral("keystorelist" )), QStringLiteral("qca-gnupg" ))) { |
606 | QStringList storeIds = keyManager.keyStores(); |
607 | QVERIFY(storeIds.contains(QStringLiteral("qca-gnupg" ))); |
608 | |
609 | QCA::KeyStore pgpStore(QStringLiteral("qca-gnupg" ), &keyManager); |
610 | QVERIFY(pgpStore.isValid()); |
611 | |
612 | QList<QCA::KeyStoreEntry> keylist = pgpStore.entryList(); |
613 | |
614 | QCA::KeyStoreEntry validKey; |
615 | foreach (const QCA::KeyStoreEntry key, keylist) { |
616 | if (key.id() == QLatin1String("DD773CA7E4E22769" )) { |
617 | validKey = key; |
618 | } |
619 | } |
620 | |
621 | QCOMPARE(validKey.isNull(), false); |
622 | QCOMPARE(validKey.name(), QStringLiteral("QCA Test Key (Unit test key for expired subkeys) <qca@example.com>" )); |
623 | QCOMPARE(validKey.type(), QCA::KeyStoreEntry::TypePGPSecretKey); |
624 | QCOMPARE(validKey.id(), QStringLiteral("DD773CA7E4E22769" )); |
625 | QCOMPARE(validKey.pgpSecretKey().isNull(), false); |
626 | QCOMPARE(validKey.pgpPublicKey().isNull(), false); |
627 | |
628 | // Create a signature with the non-expired primary key first |
629 | QByteArray validMessage("Non-expired key signature" ); |
630 | |
631 | QCA::SecureMessageKey secKey; |
632 | secKey.setPGPSecretKey(validKey.pgpSecretKey()); |
633 | QVERIFY(secKey.havePrivate()); |
634 | |
635 | QCA::OpenPGP pgp; |
636 | QCA::SecureMessage msg(&pgp); |
637 | msg.setSigner(secKey); |
638 | msg.setFormat(QCA::SecureMessage::Ascii); |
639 | msg.startSign(m: QCA::SecureMessage::Clearsign); |
640 | msg.update(in: validMessage); |
641 | msg.end(); |
642 | msg.waitForFinished(msecs: 5000); |
643 | |
644 | QVERIFY(msg.success()); |
645 | |
646 | QByteArray nonExpiredKeySignature = msg.read(); |
647 | |
648 | // Verify it |
649 | QCA::OpenPGP pgp1; |
650 | QCA::SecureMessage msg1(&pgp1); |
651 | msg1.setFormat(QCA::SecureMessage::Ascii); |
652 | msg1.startVerify(); |
653 | msg1.update(in: nonExpiredKeySignature); |
654 | msg1.end(); |
655 | msg1.waitForFinished(msecs: 5000); |
656 | |
657 | QByteArray signedResult = msg1.read(); |
658 | |
659 | QVERIFY(msg1.verifySuccess()); |
660 | QCOMPARE(signedResult.trimmed(), validMessage.trimmed()); |
661 | |
662 | // Test signature made by the expired subkey |
663 | QByteArray expiredKeySignature( |
664 | "-----BEGIN PGP SIGNED MESSAGE-----\n" |
665 | "Hash: SHA1\n" |
666 | "\n" |
667 | "Expired signature\n" |
668 | "-----BEGIN PGP SIGNATURE-----\n" |
669 | "Version: GnuPG v1\n" |
670 | "\n" |
671 | "iQEcBAEBAgAGBQJTaI2VAAoJEPddwMGvBiCrA18H/RMJxnEnyNERd19ffTdSLvHH\n" |
672 | "iwsfTEPmFQSpmCUnmjK2IIMXWTi6ofinGTWEsXKHSTqgytz715Q16OICwnFoeHin\n" |
673 | "0SnsTgi5lH5QcJPJ5PqoRwgAy8vhy73EpYrv7zqLom1Qm/9NeGtNZsohjp0ECFEk\n" |
674 | "4UmZiJF7u5Yn6hBl9btIPRTjI2FrXjr3Zy8jZijRaZMutnz5VveBHCLu00NvJQkG\n" |
675 | "PC/ZvvfGOk9SeaApnrnntfdKJ4geKxoT0+r+Yz1kQ1VKG5Af3JziBwwNID6g/FYv\n" |
676 | "cDK2no5KD7lyASAt6veCDXBdUmNatBO5Au9eE0jJwsSV6LZCpEYEcgBsnfDwxls=\n" |
677 | "=UM6K\n" |
678 | "-----END PGP SIGNATURE-----\n" ); |
679 | |
680 | QCA::OpenPGP pgp2; |
681 | QCA::SecureMessage msg2(&pgp2); |
682 | msg2.setFormat(QCA::SecureMessage::Ascii); |
683 | msg2.startVerify(); |
684 | msg2.update(in: expiredKeySignature); |
685 | msg2.end(); |
686 | msg2.waitForFinished(msecs: 5000); |
687 | |
688 | QCOMPARE(msg2.verifySuccess(), false); |
689 | QCOMPARE(msg2.errorCode(), QCA::SecureMessage::ErrorSignerExpired); |
690 | |
691 | // Test signature made by the revoked subkey |
692 | QByteArray revokedKeySignature( |
693 | "-----BEGIN PGP SIGNED MESSAGE-----\n" |
694 | "Hash: SHA1\n" |
695 | "\n" |
696 | "Revoked signature\n" |
697 | "-----BEGIN PGP SIGNATURE-----\n" |
698 | "Version: GnuPG v1\n" |
699 | "\n" |
700 | "iQEcBAEBAgAGBQJTd2AmAAoJEJwx7xWvfHTUCZMH/310hMg68H9kYWqKO12Qvyl7\n" |
701 | "SlkHBxRsD1sKIBM10qxh6262582mbdAbObVCSFlHVR5NU2tDN5B67J9NU2KnwCcq\n" |
702 | "Ny7Oj06UGEkZRmGA23BZ78w/xhPr2Xg80lckBIfGWCvezjSoAeOonk4WREpqzSUr\n" |
703 | "sXX8iioBh98ySuQp4rtzf1j0sGB2Tui/bZybiLwz+/fBzW9ITSV0OXmeT5BfBhJU\n" |
704 | "XXnOBXDwPUrJQPHxpGpX0s7iyElfZ2ws/PNqUJiWdmxqwLnndn1y5UECDC2T0FpC\n" |
705 | "erOM27tQeEthdrZTjMjV47p1ilNgNJrMI328ovehABYyobK9UlnFHcUnn/1OFRw=\n" |
706 | "=sE1o\n" |
707 | "-----END PGP SIGNATURE-----\n" ); |
708 | |
709 | QCA::OpenPGP pgp3; |
710 | QCA::SecureMessage msg3(&pgp3); |
711 | msg3.setFormat(QCA::SecureMessage::Ascii); |
712 | msg3.startVerify(); |
713 | msg3.update(in: revokedKeySignature); |
714 | msg3.end(); |
715 | msg3.waitForFinished(msecs: 5000); |
716 | |
717 | QCOMPARE(msg3.verifySuccess(), false); |
718 | QCOMPARE(msg3.errorCode(), QCA::SecureMessage::ErrorSignerRevoked); |
719 | |
720 | // Test expired signature |
721 | QByteArray expiredSignature( |
722 | "-----BEGIN PGP SIGNED MESSAGE-----\n" |
723 | "Hash: SHA1\n" |
724 | "\n" |
725 | "Valid key, expired signature\n" |
726 | "-----BEGIN PGP SIGNATURE-----\n" |
727 | "Version: GnuPG v1\n" |
728 | "\n" |
729 | "iQEiBAEBAgAMBQJTkjUvBYMAAVGAAAoJEN13PKfk4idpItIH/1BpFQkPm8fQV0bd\n" |
730 | "37qaXf7IWr7bsPBcb7NjR9EmB6Zl6wnmSKW9mvKvs0ZJ1HxyHx0yC5UQWsgTj3do\n" |
731 | "xGDP4nJvi0L7EDUukZApWu98nFwrnTrLEd+JMwlpYDhtaljq2qQo7u7CsqyoE2cL\n" |
732 | "nRuPkc+lRbDMlqGXk2QFPL8Wu7gW/ndJ8nQ0Dq+22q77Hh1PcyFlggTBxhLA4Svk\n" |
733 | "Hx2I4bUjaUq5P4g9kFeXx6/0n31FCa+uThSkWWjH1OeLEXrUZSH/8nBWcnJ3IGbt\n" |
734 | "W2bmHhTUx3tYt9aHc+1kje2bAT01xN974r+wS/XNT4cWw7ZSSvukEIjjieUMP4eQ\n" |
735 | "S/H6RmM=\n" |
736 | "=9pAJ\n" |
737 | "-----END PGP SIGNATURE-----\n" ); |
738 | |
739 | QCA::OpenPGP pgp4; |
740 | QCA::SecureMessage msg4(&pgp4); |
741 | msg4.setFormat(QCA::SecureMessage::Ascii); |
742 | msg4.startVerify(); |
743 | msg4.update(in: expiredSignature); |
744 | msg4.end(); |
745 | msg4.waitForFinished(msecs: 5000); |
746 | |
747 | QCOMPARE(msg4.verifySuccess(), false); |
748 | QCOMPARE(msg4.errorCode(), QCA::SecureMessage::ErrorSignatureExpired); |
749 | } |
750 | |
751 | if (!oldGNUPGHOME.isNull()) { |
752 | qca_setenv(name: "GNUPGHOME" , value: oldGNUPGHOME.data(), overwrite: 1); |
753 | } |
754 | } |
755 | |
756 | void PgpUnitTest::testEncryptionWithExpiredSubkeys() |
757 | { |
758 | // This previously failing test tests encrypting to |
759 | // keys with expired subkeys, while assuring no loss |
760 | // of functionality. |
761 | |
762 | QCA::Initializer qcaInit; |
763 | |
764 | PGPPassphraseProviderThread thread; |
765 | thread.start(); |
766 | |
767 | QByteArray oldGNUPGHOME = qgetenv(varName: "GNUPGHOME" ); |
768 | if (qca_setenv(name: "GNUPGHOME" , value: "./keys4_expired_subkeys_work" , overwrite: 1) != 0) { |
769 | QFAIL("Expected to be able to set the GNUPGHOME environment variable, but couldn't" ); |
770 | } |
771 | |
772 | QCA::KeyStoreManager::start(); |
773 | |
774 | QCA::KeyStoreManager keyManager(this); |
775 | keyManager.waitForBusyFinished(); |
776 | |
777 | if (QCA::isSupported(features: QStringList(QStringLiteral("openpgp" )), QStringLiteral("qca-gnupg" )) || |
778 | QCA::isSupported(features: QStringList(QStringLiteral("keystorelist" )), QStringLiteral("qca-gnupg" ))) { |
779 | QStringList storeIds = keyManager.keyStores(); |
780 | QVERIFY(storeIds.contains(QStringLiteral("qca-gnupg" ))); |
781 | |
782 | QCA::KeyStore pgpStore(QStringLiteral("qca-gnupg" ), &keyManager); |
783 | QVERIFY(pgpStore.isValid()); |
784 | |
785 | QList<QCA::KeyStoreEntry> keylist = pgpStore.entryList(); |
786 | |
787 | QCA::KeyStoreEntry validKey; |
788 | QCA::KeyStoreEntry expiredKey; |
789 | QCA::KeyStoreEntry revokedKey; |
790 | foreach (const QCA::KeyStoreEntry key, keylist) { |
791 | if (key.id() == QLatin1String("FEF97E4C4C870810" )) { |
792 | validKey = key; |
793 | } else if (key.id() == QLatin1String("DD773CA7E4E22769" )) { |
794 | expiredKey = key; |
795 | } else if (key.id() == QLatin1String("1D6A028CC4F444A9" )) { |
796 | revokedKey = key; |
797 | } |
798 | } |
799 | |
800 | QCOMPARE(validKey.isNull(), false); |
801 | QCOMPARE(validKey.name(), |
802 | QStringLiteral( |
803 | "QCA Test Key 2 (Non-expired encryption key with expired encryption subkey) <qca@example.com>" )); |
804 | QCOMPARE(validKey.type(), QCA::KeyStoreEntry::TypePGPSecretKey); |
805 | QCOMPARE(validKey.id(), QStringLiteral("FEF97E4C4C870810" )); |
806 | QCOMPARE(validKey.pgpSecretKey().isNull(), false); |
807 | QCOMPARE(validKey.pgpPublicKey().isNull(), false); |
808 | |
809 | QCOMPARE(expiredKey.isNull(), false); |
810 | QCOMPARE(expiredKey.name(), |
811 | QStringLiteral("QCA Test Key (Unit test key for expired subkeys) <qca@example.com>" )); |
812 | QCOMPARE(expiredKey.type(), QCA::KeyStoreEntry::TypePGPSecretKey); |
813 | QCOMPARE(expiredKey.id(), QStringLiteral("DD773CA7E4E22769" )); |
814 | QCOMPARE(expiredKey.pgpSecretKey().isNull(), false); |
815 | QCOMPARE(expiredKey.pgpPublicKey().isNull(), false); |
816 | |
817 | QCOMPARE(revokedKey.isNull(), false); |
818 | QCOMPARE(revokedKey.name(), QStringLiteral("QCA Test Key (Revoked unit test key) <qca@example.com>" )); |
819 | QCOMPARE(revokedKey.type(), QCA::KeyStoreEntry::TypePGPSecretKey); |
820 | QCOMPARE(revokedKey.id(), QStringLiteral("1D6A028CC4F444A9" )); |
821 | QCOMPARE(revokedKey.pgpSecretKey().isNull(), false); |
822 | QCOMPARE(revokedKey.pgpPublicKey().isNull(), false); |
823 | |
824 | // Test encrypting to a non-expired key first |
825 | QByteArray nonExpiredMessage("Encrypting to non-expired subkey" ); |
826 | |
827 | QCA::SecureMessageKey key; |
828 | key.setPGPPublicKey(validKey.pgpPublicKey()); |
829 | |
830 | QCA::OpenPGP pgp; |
831 | QCA::SecureMessage msg(&pgp); |
832 | msg.setFormat(QCA::SecureMessage::Ascii); |
833 | msg.setRecipient(key); |
834 | msg.startEncrypt(); |
835 | msg.update(in: nonExpiredMessage); |
836 | msg.end(); |
837 | msg.waitForFinished(msecs: 5000); |
838 | |
839 | QVERIFY(msg.success()); |
840 | QByteArray encResult = msg.read(); |
841 | |
842 | // Decrypt and compare it |
843 | QCA::OpenPGP pgp1; |
844 | QCA::SecureMessage msg1(&pgp1); |
845 | msg1.startDecrypt(); |
846 | msg1.update(in: encResult); |
847 | msg1.end(); |
848 | msg1.waitForFinished(msecs: 5000); |
849 | |
850 | QVERIFY(msg1.success()); |
851 | QByteArray decResult = msg1.read(); |
852 | QCOMPARE(decResult, nonExpiredMessage); |
853 | |
854 | // Test encrypting to the expired key |
855 | QByteArray expiredMessage("Encrypting to the expired key" ); |
856 | |
857 | QCA::SecureMessageKey key2; |
858 | key2.setPGPPublicKey(expiredKey.pgpPublicKey()); |
859 | |
860 | QCA::OpenPGP pgp2; |
861 | QCA::SecureMessage msg2(&pgp2); |
862 | msg2.setFormat(QCA::SecureMessage::Ascii); |
863 | msg2.setRecipient(key2); |
864 | msg2.startEncrypt(); |
865 | msg2.update(in: expiredMessage); |
866 | msg2.end(); |
867 | msg2.waitForFinished(msecs: 5000); |
868 | |
869 | QCOMPARE(msg2.success(), false); |
870 | // Note: If gpg worked as expected, msg.errorCode() should |
871 | // equal QCA::SecureMessage::ErrorEncryptExpired, but currently |
872 | // it omits the reason for failure, so we check for both values. |
873 | QVERIFY((msg2.errorCode() == QCA::SecureMessage::ErrorEncryptExpired) || |
874 | (msg2.errorCode() == QCA::SecureMessage::ErrorEncryptInvalid)); |
875 | |
876 | // Test encrypting to the revoked key |
877 | QByteArray revokedMessage("Encrypting to the revoked key" ); |
878 | |
879 | QCA::SecureMessageKey key3; |
880 | key3.setPGPPublicKey(expiredKey.pgpPublicKey()); |
881 | |
882 | QCA::OpenPGP pgp3; |
883 | QCA::SecureMessage msg3(&pgp3); |
884 | msg3.setFormat(QCA::SecureMessage::Ascii); |
885 | msg3.setRecipient(key3); |
886 | msg3.startEncrypt(); |
887 | msg3.update(in: revokedMessage); |
888 | msg3.end(); |
889 | msg3.waitForFinished(msecs: 5000); |
890 | |
891 | QCOMPARE(msg3.success(), false); |
892 | // Note: If gpg worked as expected, msg.errorCode() should |
893 | // equal QCA::SecureMessage::ErrorEncryptRevoked, but currently |
894 | // it omits the reason for failure, so we check for both values. |
895 | QVERIFY((msg3.errorCode() == QCA::SecureMessage::ErrorEncryptRevoked) || |
896 | (msg3.errorCode() == QCA::SecureMessage::ErrorEncryptInvalid)); |
897 | } |
898 | |
899 | if (!oldGNUPGHOME.isNull()) { |
900 | qca_setenv(name: "GNUPGHOME" , value: oldGNUPGHOME.data(), overwrite: 1); |
901 | } |
902 | } |
903 | |
904 | QTEST_MAIN(PgpUnitTest) |
905 | |
906 | #include "pgpunittest.moc" |
907 | |