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
37bool 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
49static 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.
64class PGPPassphraseProvider : public QObject
65{
66 Q_OBJECT
67public:
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
75private 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
86private:
87 QCA::EventHandler m_handler;
88};
89
90class PGPPassphraseProviderThread : public QCA::SyncThread
91{
92 Q_OBJECT
93public:
94 ~PGPPassphraseProviderThread() override
95 {
96 stop();
97 }
98
99protected:
100 void atStart() override
101 {
102 prov = new PGPPassphraseProvider;
103 }
104
105 void atEnd() override
106 {
107 delete prov;
108 }
109
110private:
111 PGPPassphraseProvider *prov;
112};
113
114class PgpUnitTest : public QObject
115{
116 Q_OBJECT
117
118private 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
129void 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
137void PgpUnitTest::cleanupTestCase()
138{
139}
140
141void 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
227void 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
355void 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
466void 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
583void 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
756void 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
904QTEST_MAIN(PgpUnitTest)
905
906#include "pgpunittest.moc"
907

source code of qca/unittest/pgpunittest/pgpunittest.cpp