mirror of
https://github.com/QuasarApp/qca.git
synced 2025-05-14 03:29:32 +00:00
find . \( -name "*.cpp" -or -name "*.h" -or -name "*.c" -or -name "*.cc" \) -exec clang-format -i {} \; If you reached this file doing a git blame, please see README.clang-format (added 2 commits in the future of this one)
907 lines
33 KiB
C++
907 lines
33 KiB
C++
/**
|
|
* Copyright (C) 2006-2007 Brad Hards <bradh@frogmouth.net>
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <QtCrypto>
|
|
#include <QtTest/QtTest>
|
|
|
|
#include <cstdlib>
|
|
|
|
#ifdef QT_STATICPLUGIN
|
|
#include "import_plugins.h"
|
|
#endif
|
|
|
|
// qt did not introduce qputenv until 4.4, so we'll keep a copy here for 4.2
|
|
// compat
|
|
bool my_qputenv(const char *varName, const QByteArray &value)
|
|
{
|
|
#if defined(_MSC_VER) && _MSC_VER >= 1400
|
|
return _putenv_s(varName, value.constData()) == 0;
|
|
#else
|
|
QByteArray buffer(varName);
|
|
buffer += "=";
|
|
buffer += value;
|
|
return putenv(qstrdup(buffer.constData())) == 0;
|
|
#endif
|
|
}
|
|
|
|
static int qca_setenv(const char *name, const char *value, int overwrite)
|
|
{
|
|
if (!overwrite && qEnvironmentVariableIsSet(name))
|
|
return 0;
|
|
|
|
if (my_qputenv(name, QByteArray(value)))
|
|
return 0; // success
|
|
else
|
|
return 1; // error
|
|
}
|
|
|
|
// Note; in a real application you get this from a user, but this
|
|
// is a useful trick for a unit test.
|
|
// See the qcatool application or keyloader and eventhandler examples
|
|
// for how to do this properly.
|
|
class PGPPassphraseProvider : public QObject
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
PGPPassphraseProvider(QObject *parent = nullptr)
|
|
: QObject(parent)
|
|
{
|
|
connect(&m_handler, &QCA::EventHandler::eventReady, this, &PGPPassphraseProvider::eh_eventReady);
|
|
m_handler.start();
|
|
}
|
|
|
|
private Q_SLOTS:
|
|
void eh_eventReady(int id, const QCA::Event &event)
|
|
{
|
|
if (event.type() == QCA::Event::Password) {
|
|
QCA::SecureArray pass("start");
|
|
m_handler.submitPassword(id, pass);
|
|
} else {
|
|
m_handler.reject(id);
|
|
}
|
|
}
|
|
|
|
private:
|
|
QCA::EventHandler m_handler;
|
|
};
|
|
|
|
class PGPPassphraseProviderThread : public QCA::SyncThread
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
~PGPPassphraseProviderThread() override
|
|
{
|
|
stop();
|
|
}
|
|
|
|
protected:
|
|
void atStart() override
|
|
{
|
|
prov = new PGPPassphraseProvider;
|
|
}
|
|
|
|
void atEnd() override
|
|
{
|
|
delete prov;
|
|
}
|
|
|
|
private:
|
|
PGPPassphraseProvider *prov;
|
|
};
|
|
|
|
class PgpUnitTest : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
private Q_SLOTS:
|
|
void initTestCase();
|
|
void cleanupTestCase();
|
|
void testKeyRing();
|
|
void testMessageSign();
|
|
void testClearsign();
|
|
void testDetachedSign();
|
|
void testSignaturesWithExpiredSubkeys();
|
|
void testEncryptionWithExpiredSubkeys();
|
|
};
|
|
|
|
void PgpUnitTest::initTestCase()
|
|
{
|
|
// Change current directory to executable directory
|
|
// it is need to find keys*_work directories
|
|
if (!QCoreApplication::applicationDirPath().isEmpty())
|
|
QDir::setCurrent(QCoreApplication::applicationDirPath());
|
|
}
|
|
|
|
void PgpUnitTest::cleanupTestCase()
|
|
{
|
|
}
|
|
|
|
void PgpUnitTest::testKeyRing()
|
|
{
|
|
QCA::Initializer *qcaInit = new QCA::Initializer;
|
|
|
|
// We test a small keyring - I downloaded a publically available one from
|
|
QByteArray oldGNUPGHOME = qgetenv("GNUPGHOME");
|
|
// the Amsterdam Internet Exchange.
|
|
if (qca_setenv("GNUPGHOME", "./keys1_work", 1) != 0) {
|
|
QFAIL("Expected to be able to set the GNUPGHOME environment variable, but couldn't");
|
|
}
|
|
|
|
// activate the KeyStoreManager
|
|
QCA::KeyStoreManager::start();
|
|
|
|
if (QCA::isSupported(QStringList(QStringLiteral("keystorelist")), QStringLiteral("qca-gnupg"))) {
|
|
QCA::KeyStoreManager keyManager(this);
|
|
keyManager.waitForBusyFinished();
|
|
QStringList storeIds = keyManager.keyStores();
|
|
QVERIFY(storeIds.contains(QStringLiteral("qca-gnupg")));
|
|
|
|
QCA::KeyStore pgpStore(QStringLiteral("qca-gnupg"), &keyManager);
|
|
QVERIFY(pgpStore.isValid());
|
|
QCOMPARE(pgpStore.name(), QStringLiteral("GnuPG Keyring"));
|
|
QCOMPARE(pgpStore.type(), QCA::KeyStore::PGPKeyring);
|
|
QCOMPARE(pgpStore.id(), QStringLiteral("qca-gnupg"));
|
|
QCOMPARE(pgpStore.isReadOnly(), false);
|
|
QCOMPARE(pgpStore.holdsTrustedCertificates(), false);
|
|
QCOMPARE(pgpStore.holdsIdentities(), true);
|
|
QCOMPARE(pgpStore.holdsPGPPublicKeys(), true);
|
|
|
|
QList<QCA::KeyStoreEntry> keylist = pgpStore.entryList();
|
|
QCOMPARE(keylist.count(), 6);
|
|
QStringList nameList;
|
|
foreach (const QCA::KeyStoreEntry key, keylist) {
|
|
QCOMPARE(key.isNull(), false);
|
|
QCOMPARE(key.type(), QCA::KeyStoreEntry::TypePGPPublicKey);
|
|
QCOMPARE(key.id().length(), 16); // 16 hex digits
|
|
QVERIFY(key.keyBundle().isNull());
|
|
QVERIFY(key.certificate().isNull());
|
|
QVERIFY(key.crl().isNull());
|
|
QVERIFY(key.pgpSecretKey().isNull());
|
|
QCOMPARE(key.pgpPublicKey().isNull(), false);
|
|
|
|
// We accumulate the names, and check them next
|
|
nameList << key.name();
|
|
}
|
|
QVERIFY(nameList.contains(QStringLiteral("Steven Bakker <steven.bakker@ams-ix.net>")));
|
|
QVERIFY(nameList.contains(QStringLiteral("Romeo Zwart <rz@ams-ix.net>")));
|
|
QVERIFY(nameList.contains(QStringLiteral("Arien Vijn <arien.vijn@ams-ix.net>")));
|
|
QVERIFY(nameList.contains(QStringLiteral("Niels Bakker <niels.bakker@ams-ix.net>")));
|
|
QVERIFY(nameList.contains(QStringLiteral("Henk Steenman <Henk.Steenman@ams-ix.net>")));
|
|
QVERIFY(nameList.contains(QStringLiteral("Geert Nijpels <geert.nijpels@ams-ix.net>")));
|
|
|
|
// TODO: We should test removeEntry() and writeEntry() here.
|
|
}
|
|
|
|
delete qcaInit;
|
|
qcaInit = new QCA::Initializer;
|
|
|
|
// We now test an empty keyring
|
|
if (qca_setenv("GNUPGHOME", "./keys2_work", 1) != 0) {
|
|
QFAIL("Expected to be able to set the GNUPGHOME environment variable, but couldn't");
|
|
}
|
|
|
|
QCA::KeyStoreManager::start();
|
|
|
|
if (QCA::isSupported(QStringList(QStringLiteral("keystorelist")), QStringLiteral("qca-gnupg"))) {
|
|
QCA::KeyStoreManager keyManager(this);
|
|
keyManager.waitForBusyFinished();
|
|
QStringList storeIds = keyManager.keyStores();
|
|
QVERIFY(storeIds.contains(QStringLiteral("qca-gnupg")));
|
|
|
|
QCA::KeyStore pgpStore(QStringLiteral("qca-gnupg"), &keyManager);
|
|
|
|
QList<QCA::KeyStoreEntry> keylist = pgpStore.entryList();
|
|
QCOMPARE(keylist.count(), 0);
|
|
// TODO: We should test removeEntry() and writeEntry() here.
|
|
}
|
|
|
|
if (false == oldGNUPGHOME.isNull()) {
|
|
qca_setenv("GNUPGHOME", oldGNUPGHOME.data(), 1);
|
|
}
|
|
|
|
delete qcaInit;
|
|
}
|
|
|
|
void PgpUnitTest::testMessageSign()
|
|
{
|
|
QCA::Initializer qcaInit;
|
|
|
|
// event handling cannot be used in the same thread as synchronous calls
|
|
// which might require event handling. let's put our event handler in
|
|
// a side thread so that we can write the unit test synchronously.
|
|
PGPPassphraseProviderThread thread;
|
|
thread.start();
|
|
|
|
// This keyring has a private / public key pair
|
|
QByteArray oldGNUPGHOME = qgetenv("GNUPGHOME");
|
|
if (0 != qca_setenv("GNUPGHOME", "./keys3_work", 1)) {
|
|
QFAIL("Expected to be able to set the GNUPGHOME environment variable, but couldn't");
|
|
}
|
|
|
|
// activate the KeyStoreManager
|
|
QCA::KeyStoreManager::start();
|
|
|
|
QCA::KeyStoreManager keyManager(this);
|
|
keyManager.waitForBusyFinished();
|
|
|
|
if (QCA::isSupported(QStringList(QStringLiteral("openpgp")), QStringLiteral("qca-gnupg")) ||
|
|
QCA::isSupported(QStringList(QStringLiteral("keystorelist")), QStringLiteral("qca-gnupg"))) {
|
|
QStringList storeIds = keyManager.keyStores();
|
|
QVERIFY(storeIds.contains(QStringLiteral("qca-gnupg")));
|
|
|
|
QCA::KeyStore pgpStore(QStringLiteral("qca-gnupg"), &keyManager);
|
|
QVERIFY(pgpStore.isValid());
|
|
|
|
QList<QCA::KeyStoreEntry> keylist = pgpStore.entryList();
|
|
QCOMPARE(keylist.count(), 1);
|
|
|
|
const QCA::KeyStoreEntry &myPGPKey = keylist.at(0);
|
|
QCOMPARE(myPGPKey.isNull(), false);
|
|
QCOMPARE(myPGPKey.name(),
|
|
QStringLiteral("Qca Test Key (This key is only for QCA unit tests) <qca@example.com>"));
|
|
QCOMPARE(myPGPKey.type(), QCA::KeyStoreEntry::TypePGPSecretKey);
|
|
QCOMPARE(myPGPKey.id(), QStringLiteral("9E946237DAFCCFF4"));
|
|
QVERIFY(myPGPKey.keyBundle().isNull());
|
|
QVERIFY(myPGPKey.certificate().isNull());
|
|
QVERIFY(myPGPKey.crl().isNull());
|
|
QCOMPARE(myPGPKey.pgpSecretKey().isNull(), false);
|
|
QCOMPARE(myPGPKey.pgpPublicKey().isNull(), false);
|
|
|
|
// first make the SecureMessageKey
|
|
QCA::SecureMessageKey key;
|
|
key.setPGPSecretKey(myPGPKey.pgpSecretKey());
|
|
QVERIFY(key.havePrivate());
|
|
|
|
// our data to sign
|
|
QByteArray plain = "Hello, world";
|
|
|
|
// let's do it
|
|
QCA::OpenPGP pgp;
|
|
QCA::SecureMessage msg(&pgp);
|
|
msg.setSigner(key);
|
|
msg.setFormat(QCA::SecureMessage::Ascii);
|
|
msg.startSign(QCA::SecureMessage::Message);
|
|
msg.update(plain);
|
|
msg.end();
|
|
msg.waitForFinished(5000);
|
|
|
|
#if 0
|
|
QString str = QCA::KeyStoreManager::diagnosticText();
|
|
QCA::KeyStoreManager::clearDiagnosticText();
|
|
QStringList lines = str.split('\n', QString::SkipEmptyParts);
|
|
for(int n = 0; n < lines.count(); ++n)
|
|
fprintf(stderr, "keystore: %s\n", qPrintable(lines[n]));
|
|
|
|
QString out = msg.diagnosticText();
|
|
QStringList msglines = out.split('\n', QString::SkipEmptyParts);
|
|
for(int n = 0; n < msglines.count(); ++n)
|
|
fprintf(stderr, "message: %s\n", qPrintable(msglines[n]));
|
|
#endif
|
|
QByteArray messageData;
|
|
if (msg.success()) {
|
|
messageData = msg.read();
|
|
} else {
|
|
qDebug() << "Failure:" << msg.errorCode();
|
|
QFAIL("Failed to sign in Message format");
|
|
}
|
|
// qDebug() << "Message format data:" << messageData;
|
|
|
|
// OK, now lets verify that the result will verify.
|
|
// let's do it
|
|
QCA::OpenPGP pgp2;
|
|
QCA::SecureMessage msg2(&pgp2);
|
|
msg2.setFormat(QCA::SecureMessage::Ascii);
|
|
msg2.startVerify();
|
|
msg2.update(messageData);
|
|
msg2.end();
|
|
msg2.waitForFinished(5000);
|
|
|
|
QVERIFY(msg2.verifySuccess());
|
|
|
|
if (msg2.success()) {
|
|
QCOMPARE(msg2.read(), plain);
|
|
} else {
|
|
qDebug() << "Failure:" << msg2.errorCode();
|
|
QFAIL("Failed to verify message");
|
|
}
|
|
|
|
// why is this here?
|
|
if (false == oldGNUPGHOME.isNull()) {
|
|
qca_setenv("GNUPGHOME", oldGNUPGHOME.data(), 1);
|
|
}
|
|
|
|
// now test that if we corrupt the message, it no longer
|
|
// verifies correctly.
|
|
messageData.replace('T', 't');
|
|
messageData.replace('w', 'W');
|
|
QCA::SecureMessage msg3(&pgp2);
|
|
msg3.setFormat(QCA::SecureMessage::Ascii);
|
|
msg3.startVerify();
|
|
msg3.update(messageData);
|
|
msg3.end();
|
|
msg3.waitForFinished(5000);
|
|
|
|
QCOMPARE(msg3.verifySuccess(), false);
|
|
QCOMPARE(msg3.errorCode(), QCA::SecureMessage::ErrorUnknown);
|
|
}
|
|
|
|
if (false == oldGNUPGHOME.isNull()) {
|
|
qca_setenv("GNUPGHOME", oldGNUPGHOME.data(), 1);
|
|
}
|
|
}
|
|
|
|
void PgpUnitTest::testClearsign()
|
|
{
|
|
QCA::Initializer qcaInit;
|
|
|
|
// event handling cannot be used in the same thread as synchronous calls
|
|
// which might require event handling. let's put our event handler in
|
|
// a side thread so that we can write the unit test synchronously.
|
|
PGPPassphraseProviderThread thread;
|
|
thread.start();
|
|
|
|
// This keyring has a private / public key pair
|
|
QByteArray oldGNUPGHOME = qgetenv("GNUPGHOME");
|
|
if (0 != qca_setenv("GNUPGHOME", "./keys3_work", 1)) {
|
|
QFAIL("Expected to be able to set the GNUPGHOME environment variable, but couldn't");
|
|
}
|
|
|
|
// activate the KeyStoreManager
|
|
QCA::KeyStoreManager::start();
|
|
|
|
QCA::KeyStoreManager keyManager(this);
|
|
keyManager.waitForBusyFinished();
|
|
|
|
if (QCA::isSupported(QStringList(QStringLiteral("openpgp")), QStringLiteral("qca-gnupg")) ||
|
|
QCA::isSupported(QStringList(QStringLiteral("keystorelist")), QStringLiteral("qca-gnupg"))) {
|
|
QStringList storeIds = keyManager.keyStores();
|
|
QVERIFY(storeIds.contains(QStringLiteral("qca-gnupg")));
|
|
|
|
QCA::KeyStore pgpStore(QStringLiteral("qca-gnupg"), &keyManager);
|
|
QVERIFY(pgpStore.isValid());
|
|
|
|
QList<QCA::KeyStoreEntry> keylist = pgpStore.entryList();
|
|
QCOMPARE(keylist.count(), 1);
|
|
|
|
const QCA::KeyStoreEntry &myPGPKey = keylist.at(0);
|
|
QCOMPARE(myPGPKey.isNull(), false);
|
|
QCOMPARE(myPGPKey.name(),
|
|
QStringLiteral("Qca Test Key (This key is only for QCA unit tests) <qca@example.com>"));
|
|
QCOMPARE(myPGPKey.type(), QCA::KeyStoreEntry::TypePGPSecretKey);
|
|
QCOMPARE(myPGPKey.id(), QStringLiteral("9E946237DAFCCFF4"));
|
|
QVERIFY(myPGPKey.keyBundle().isNull());
|
|
QVERIFY(myPGPKey.certificate().isNull());
|
|
QVERIFY(myPGPKey.crl().isNull());
|
|
QCOMPARE(myPGPKey.pgpSecretKey().isNull(), false);
|
|
QCOMPARE(myPGPKey.pgpPublicKey().isNull(), false);
|
|
|
|
// first make the SecureMessageKey
|
|
QCA::SecureMessageKey key;
|
|
key.setPGPSecretKey(myPGPKey.pgpSecretKey());
|
|
QVERIFY(key.havePrivate());
|
|
|
|
// our data to sign
|
|
QByteArray plain = "Hello, world";
|
|
|
|
// let's do it
|
|
QCA::OpenPGP pgp;
|
|
QCA::SecureMessage msg(&pgp);
|
|
msg.setSigner(key);
|
|
msg.setFormat(QCA::SecureMessage::Ascii);
|
|
msg.startSign(QCA::SecureMessage::Clearsign);
|
|
msg.update(plain);
|
|
msg.end();
|
|
msg.waitForFinished(5000);
|
|
|
|
#if 0
|
|
QString str = QCA::KeyStoreManager::diagnosticText();
|
|
QCA::KeyStoreManager::clearDiagnosticText();
|
|
QStringList lines = str.split('\n', QString::SkipEmptyParts);
|
|
for(int n = 0; n < lines.count(); ++n)
|
|
fprintf(stderr, "keystore: %s\n", qPrintable(lines[n]));
|
|
|
|
QString out = msg.diagnosticText();
|
|
QStringList msglines = out.split('\n', QString::SkipEmptyParts);
|
|
for(int n = 0; n < msglines.count(); ++n)
|
|
fprintf(stderr, "message: %s\n", qPrintable(msglines[n]));
|
|
#endif
|
|
|
|
QByteArray clearsignedData;
|
|
if (msg.success()) {
|
|
clearsignedData = msg.read();
|
|
} else {
|
|
qDebug() << "Failure:" << msg.errorCode();
|
|
QFAIL("Failed to clearsign");
|
|
}
|
|
|
|
// OK, now lets verify that the result will verify.
|
|
// let's do it
|
|
QCA::OpenPGP pgp2;
|
|
QCA::SecureMessage msg2(&pgp2);
|
|
msg2.setFormat(QCA::SecureMessage::Ascii);
|
|
msg2.startVerify();
|
|
msg2.update(clearsignedData);
|
|
msg2.end();
|
|
msg2.waitForFinished(5000);
|
|
|
|
QVERIFY(msg2.verifySuccess());
|
|
|
|
if (msg2.success()) {
|
|
// The trimmed() call is needed because clearsigning
|
|
// trashes whitespace
|
|
QCOMPARE(msg2.read().trimmed(), plain.trimmed());
|
|
} else {
|
|
qDebug() << "Failure:" << msg2.errorCode();
|
|
QFAIL("Failed to verify clearsigned message");
|
|
}
|
|
}
|
|
|
|
if (false == oldGNUPGHOME.isNull()) {
|
|
qca_setenv("GNUPGHOME", oldGNUPGHOME.data(), 1);
|
|
}
|
|
}
|
|
|
|
void PgpUnitTest::testDetachedSign()
|
|
{
|
|
QCA::Initializer qcaInit;
|
|
|
|
// event handling cannot be used in the same thread as synchronous calls
|
|
// which might require event handling. let's put our event handler in
|
|
// a side thread so that we can write the unit test synchronously.
|
|
PGPPassphraseProviderThread thread;
|
|
thread.start();
|
|
|
|
// This keyring has a private / public key pair
|
|
QByteArray oldGNUPGHOME = qgetenv("GNUPGHOME");
|
|
if (0 != qca_setenv("GNUPGHOME", "./keys3_work", 1)) {
|
|
QFAIL("Expected to be able to set the GNUPGHOME environment variable, but couldn't");
|
|
}
|
|
|
|
// activate the KeyStoreManager
|
|
QCA::KeyStoreManager::start();
|
|
|
|
QCA::KeyStoreManager keyManager(this);
|
|
keyManager.waitForBusyFinished();
|
|
|
|
if (QCA::isSupported(QStringList(QStringLiteral("openpgp")), QStringLiteral("qca-gnupg")) ||
|
|
QCA::isSupported(QStringList(QStringLiteral("keystorelist")), QStringLiteral("qca-gnupg"))) {
|
|
QStringList storeIds = keyManager.keyStores();
|
|
QVERIFY(storeIds.contains(QStringLiteral("qca-gnupg")));
|
|
|
|
QCA::KeyStore pgpStore(QStringLiteral("qca-gnupg"), &keyManager);
|
|
QVERIFY(pgpStore.isValid());
|
|
|
|
QList<QCA::KeyStoreEntry> keylist = pgpStore.entryList();
|
|
QCOMPARE(keylist.count(), 1);
|
|
|
|
const QCA::KeyStoreEntry &myPGPKey = keylist.at(0);
|
|
QCOMPARE(myPGPKey.isNull(), false);
|
|
QCOMPARE(myPGPKey.name(),
|
|
QStringLiteral("Qca Test Key (This key is only for QCA unit tests) <qca@example.com>"));
|
|
QCOMPARE(myPGPKey.type(), QCA::KeyStoreEntry::TypePGPSecretKey);
|
|
QCOMPARE(myPGPKey.id(), QStringLiteral("9E946237DAFCCFF4"));
|
|
QVERIFY(myPGPKey.keyBundle().isNull());
|
|
QVERIFY(myPGPKey.certificate().isNull());
|
|
QVERIFY(myPGPKey.crl().isNull());
|
|
QCOMPARE(myPGPKey.pgpSecretKey().isNull(), false);
|
|
QCOMPARE(myPGPKey.pgpPublicKey().isNull(), false);
|
|
|
|
// first make the SecureMessageKey
|
|
QCA::SecureMessageKey key;
|
|
key.setPGPSecretKey(myPGPKey.pgpSecretKey());
|
|
QVERIFY(key.havePrivate());
|
|
|
|
// our data to sign
|
|
QByteArray plain = "Hello, world";
|
|
|
|
// let's do it
|
|
QCA::OpenPGP pgp;
|
|
QCA::SecureMessage msg(&pgp);
|
|
msg.setSigner(key);
|
|
msg.setFormat(QCA::SecureMessage::Ascii);
|
|
msg.startSign(QCA::SecureMessage::Detached);
|
|
msg.update(plain);
|
|
msg.end();
|
|
msg.waitForFinished(5000);
|
|
|
|
#if 0
|
|
QString str = QCA::KeyStoreManager::diagnosticText();
|
|
QCA::KeyStoreManager::clearDiagnosticText();
|
|
QStringList lines = str.split('\n', QString::SkipEmptyParts);
|
|
for(int n = 0; n < lines.count(); ++n)
|
|
fprintf(stderr, "keystore: %s\n", qPrintable(lines[n]));
|
|
|
|
QString out = msg.diagnosticText();
|
|
QStringList msglines = out.split('\n', QString::SkipEmptyParts);
|
|
for(int n = 0; n < msglines.count(); ++n)
|
|
fprintf(stderr, "message: %s\n", qPrintable(msglines[n]));
|
|
#endif
|
|
|
|
QByteArray detachedSignature;
|
|
if (msg.success()) {
|
|
detachedSignature = msg.signature();
|
|
} else {
|
|
qDebug() << "Failure:" << msg.errorCode();
|
|
QFAIL("Failed to create detached signature");
|
|
}
|
|
|
|
// qDebug() << "result:" << detachedSignature;
|
|
|
|
// OK, now lets verify that the resulting signature will verify.
|
|
// let's do it
|
|
QCA::OpenPGP pgp2;
|
|
QCA::SecureMessage msg2(&pgp2);
|
|
msg2.setFormat(QCA::SecureMessage::Ascii);
|
|
msg2.startVerify(detachedSignature);
|
|
msg2.update(plain);
|
|
msg2.end();
|
|
msg2.waitForFinished(2000);
|
|
|
|
QVERIFY(msg2.verifySuccess());
|
|
|
|
// If the message is different, it shouldn't verify any more
|
|
QCA::SecureMessage msg3(&pgp2);
|
|
msg3.setFormat(QCA::SecureMessage::Ascii);
|
|
msg3.startVerify(detachedSignature);
|
|
msg3.update(plain + '1');
|
|
msg3.end();
|
|
msg3.waitForFinished(2000);
|
|
|
|
QCOMPARE(msg3.verifySuccess(), false);
|
|
|
|
QCOMPARE(msg3.errorCode(), QCA::SecureMessage::ErrorUnknown);
|
|
}
|
|
|
|
// Restore things to the way they were....
|
|
if (false == oldGNUPGHOME.isNull()) {
|
|
qca_setenv("GNUPGHOME", oldGNUPGHOME.data(), 1);
|
|
}
|
|
}
|
|
|
|
void PgpUnitTest::testSignaturesWithExpiredSubkeys()
|
|
{
|
|
// This previously failing test tests signatures from
|
|
// keys with expired subkeys, while assuring no loss
|
|
// of functionality.
|
|
|
|
QCA::Initializer qcaInit;
|
|
|
|
PGPPassphraseProviderThread thread;
|
|
thread.start();
|
|
|
|
QByteArray oldGNUPGHOME = qgetenv("GNUPGHOME");
|
|
if (qca_setenv("GNUPGHOME", "./keys4_expired_subkeys_work", 1) != 0) {
|
|
QFAIL("Expected to be able to set the GNUPGHOME environment variable, but couldn't");
|
|
}
|
|
|
|
QCA::KeyStoreManager::start();
|
|
|
|
QCA::KeyStoreManager keyManager(this);
|
|
keyManager.waitForBusyFinished();
|
|
|
|
if (QCA::isSupported(QStringList(QStringLiteral("openpgp")), QStringLiteral("qca-gnupg")) ||
|
|
QCA::isSupported(QStringList(QStringLiteral("keystorelist")), QStringLiteral("qca-gnupg"))) {
|
|
QStringList storeIds = keyManager.keyStores();
|
|
QVERIFY(storeIds.contains(QStringLiteral("qca-gnupg")));
|
|
|
|
QCA::KeyStore pgpStore(QStringLiteral("qca-gnupg"), &keyManager);
|
|
QVERIFY(pgpStore.isValid());
|
|
|
|
QList<QCA::KeyStoreEntry> keylist = pgpStore.entryList();
|
|
|
|
QCA::KeyStoreEntry validKey;
|
|
foreach (const QCA::KeyStoreEntry key, keylist) {
|
|
if (key.id() == QLatin1String("DD773CA7E4E22769")) {
|
|
validKey = key;
|
|
}
|
|
}
|
|
|
|
QCOMPARE(validKey.isNull(), false);
|
|
QCOMPARE(validKey.name(), QStringLiteral("QCA Test Key (Unit test key for expired subkeys) <qca@example.com>"));
|
|
QCOMPARE(validKey.type(), QCA::KeyStoreEntry::TypePGPSecretKey);
|
|
QCOMPARE(validKey.id(), QStringLiteral("DD773CA7E4E22769"));
|
|
QCOMPARE(validKey.pgpSecretKey().isNull(), false);
|
|
QCOMPARE(validKey.pgpPublicKey().isNull(), false);
|
|
|
|
// Create a signature with the non-expired primary key first
|
|
QByteArray validMessage("Non-expired key signature");
|
|
|
|
QCA::SecureMessageKey secKey;
|
|
secKey.setPGPSecretKey(validKey.pgpSecretKey());
|
|
QVERIFY(secKey.havePrivate());
|
|
|
|
QCA::OpenPGP pgp;
|
|
QCA::SecureMessage msg(&pgp);
|
|
msg.setSigner(secKey);
|
|
msg.setFormat(QCA::SecureMessage::Ascii);
|
|
msg.startSign(QCA::SecureMessage::Clearsign);
|
|
msg.update(validMessage);
|
|
msg.end();
|
|
msg.waitForFinished(5000);
|
|
|
|
QVERIFY(msg.success());
|
|
|
|
QByteArray nonExpiredKeySignature = msg.read();
|
|
|
|
// Verify it
|
|
QCA::OpenPGP pgp1;
|
|
QCA::SecureMessage msg1(&pgp1);
|
|
msg1.setFormat(QCA::SecureMessage::Ascii);
|
|
msg1.startVerify();
|
|
msg1.update(nonExpiredKeySignature);
|
|
msg1.end();
|
|
msg1.waitForFinished(5000);
|
|
|
|
QByteArray signedResult = msg1.read();
|
|
|
|
QVERIFY(msg1.verifySuccess());
|
|
QCOMPARE(signedResult.trimmed(), validMessage.trimmed());
|
|
|
|
// Test signature made by the expired subkey
|
|
QByteArray expiredKeySignature(
|
|
"-----BEGIN PGP SIGNED MESSAGE-----\n"
|
|
"Hash: SHA1\n"
|
|
"\n"
|
|
"Expired signature\n"
|
|
"-----BEGIN PGP SIGNATURE-----\n"
|
|
"Version: GnuPG v1\n"
|
|
"\n"
|
|
"iQEcBAEBAgAGBQJTaI2VAAoJEPddwMGvBiCrA18H/RMJxnEnyNERd19ffTdSLvHH\n"
|
|
"iwsfTEPmFQSpmCUnmjK2IIMXWTi6ofinGTWEsXKHSTqgytz715Q16OICwnFoeHin\n"
|
|
"0SnsTgi5lH5QcJPJ5PqoRwgAy8vhy73EpYrv7zqLom1Qm/9NeGtNZsohjp0ECFEk\n"
|
|
"4UmZiJF7u5Yn6hBl9btIPRTjI2FrXjr3Zy8jZijRaZMutnz5VveBHCLu00NvJQkG\n"
|
|
"PC/ZvvfGOk9SeaApnrnntfdKJ4geKxoT0+r+Yz1kQ1VKG5Af3JziBwwNID6g/FYv\n"
|
|
"cDK2no5KD7lyASAt6veCDXBdUmNatBO5Au9eE0jJwsSV6LZCpEYEcgBsnfDwxls=\n"
|
|
"=UM6K\n"
|
|
"-----END PGP SIGNATURE-----\n");
|
|
|
|
QCA::OpenPGP pgp2;
|
|
QCA::SecureMessage msg2(&pgp2);
|
|
msg2.setFormat(QCA::SecureMessage::Ascii);
|
|
msg2.startVerify();
|
|
msg2.update(expiredKeySignature);
|
|
msg2.end();
|
|
msg2.waitForFinished(5000);
|
|
|
|
QCOMPARE(msg2.verifySuccess(), false);
|
|
QCOMPARE(msg2.errorCode(), QCA::SecureMessage::ErrorSignerExpired);
|
|
|
|
// Test signature made by the revoked subkey
|
|
QByteArray revokedKeySignature(
|
|
"-----BEGIN PGP SIGNED MESSAGE-----\n"
|
|
"Hash: SHA1\n"
|
|
"\n"
|
|
"Revoked signature\n"
|
|
"-----BEGIN PGP SIGNATURE-----\n"
|
|
"Version: GnuPG v1\n"
|
|
"\n"
|
|
"iQEcBAEBAgAGBQJTd2AmAAoJEJwx7xWvfHTUCZMH/310hMg68H9kYWqKO12Qvyl7\n"
|
|
"SlkHBxRsD1sKIBM10qxh6262582mbdAbObVCSFlHVR5NU2tDN5B67J9NU2KnwCcq\n"
|
|
"Ny7Oj06UGEkZRmGA23BZ78w/xhPr2Xg80lckBIfGWCvezjSoAeOonk4WREpqzSUr\n"
|
|
"sXX8iioBh98ySuQp4rtzf1j0sGB2Tui/bZybiLwz+/fBzW9ITSV0OXmeT5BfBhJU\n"
|
|
"XXnOBXDwPUrJQPHxpGpX0s7iyElfZ2ws/PNqUJiWdmxqwLnndn1y5UECDC2T0FpC\n"
|
|
"erOM27tQeEthdrZTjMjV47p1ilNgNJrMI328ovehABYyobK9UlnFHcUnn/1OFRw=\n"
|
|
"=sE1o\n"
|
|
"-----END PGP SIGNATURE-----\n");
|
|
|
|
QCA::OpenPGP pgp3;
|
|
QCA::SecureMessage msg3(&pgp3);
|
|
msg3.setFormat(QCA::SecureMessage::Ascii);
|
|
msg3.startVerify();
|
|
msg3.update(revokedKeySignature);
|
|
msg3.end();
|
|
msg3.waitForFinished(5000);
|
|
|
|
QCOMPARE(msg3.verifySuccess(), false);
|
|
QCOMPARE(msg3.errorCode(), QCA::SecureMessage::ErrorSignerRevoked);
|
|
|
|
// Test expired signature
|
|
QByteArray expiredSignature(
|
|
"-----BEGIN PGP SIGNED MESSAGE-----\n"
|
|
"Hash: SHA1\n"
|
|
"\n"
|
|
"Valid key, expired signature\n"
|
|
"-----BEGIN PGP SIGNATURE-----\n"
|
|
"Version: GnuPG v1\n"
|
|
"\n"
|
|
"iQEiBAEBAgAMBQJTkjUvBYMAAVGAAAoJEN13PKfk4idpItIH/1BpFQkPm8fQV0bd\n"
|
|
"37qaXf7IWr7bsPBcb7NjR9EmB6Zl6wnmSKW9mvKvs0ZJ1HxyHx0yC5UQWsgTj3do\n"
|
|
"xGDP4nJvi0L7EDUukZApWu98nFwrnTrLEd+JMwlpYDhtaljq2qQo7u7CsqyoE2cL\n"
|
|
"nRuPkc+lRbDMlqGXk2QFPL8Wu7gW/ndJ8nQ0Dq+22q77Hh1PcyFlggTBxhLA4Svk\n"
|
|
"Hx2I4bUjaUq5P4g9kFeXx6/0n31FCa+uThSkWWjH1OeLEXrUZSH/8nBWcnJ3IGbt\n"
|
|
"W2bmHhTUx3tYt9aHc+1kje2bAT01xN974r+wS/XNT4cWw7ZSSvukEIjjieUMP4eQ\n"
|
|
"S/H6RmM=\n"
|
|
"=9pAJ\n"
|
|
"-----END PGP SIGNATURE-----\n");
|
|
|
|
QCA::OpenPGP pgp4;
|
|
QCA::SecureMessage msg4(&pgp4);
|
|
msg4.setFormat(QCA::SecureMessage::Ascii);
|
|
msg4.startVerify();
|
|
msg4.update(expiredSignature);
|
|
msg4.end();
|
|
msg4.waitForFinished(5000);
|
|
|
|
QCOMPARE(msg4.verifySuccess(), false);
|
|
QCOMPARE(msg4.errorCode(), QCA::SecureMessage::ErrorSignatureExpired);
|
|
}
|
|
|
|
if (!oldGNUPGHOME.isNull()) {
|
|
qca_setenv("GNUPGHOME", oldGNUPGHOME.data(), 1);
|
|
}
|
|
}
|
|
|
|
void PgpUnitTest::testEncryptionWithExpiredSubkeys()
|
|
{
|
|
// This previously failing test tests encrypting to
|
|
// keys with expired subkeys, while assuring no loss
|
|
// of functionality.
|
|
|
|
QCA::Initializer qcaInit;
|
|
|
|
PGPPassphraseProviderThread thread;
|
|
thread.start();
|
|
|
|
QByteArray oldGNUPGHOME = qgetenv("GNUPGHOME");
|
|
if (qca_setenv("GNUPGHOME", "./keys4_expired_subkeys_work", 1) != 0) {
|
|
QFAIL("Expected to be able to set the GNUPGHOME environment variable, but couldn't");
|
|
}
|
|
|
|
QCA::KeyStoreManager::start();
|
|
|
|
QCA::KeyStoreManager keyManager(this);
|
|
keyManager.waitForBusyFinished();
|
|
|
|
if (QCA::isSupported(QStringList(QStringLiteral("openpgp")), QStringLiteral("qca-gnupg")) ||
|
|
QCA::isSupported(QStringList(QStringLiteral("keystorelist")), QStringLiteral("qca-gnupg"))) {
|
|
QStringList storeIds = keyManager.keyStores();
|
|
QVERIFY(storeIds.contains(QStringLiteral("qca-gnupg")));
|
|
|
|
QCA::KeyStore pgpStore(QStringLiteral("qca-gnupg"), &keyManager);
|
|
QVERIFY(pgpStore.isValid());
|
|
|
|
QList<QCA::KeyStoreEntry> keylist = pgpStore.entryList();
|
|
|
|
QCA::KeyStoreEntry validKey;
|
|
QCA::KeyStoreEntry expiredKey;
|
|
QCA::KeyStoreEntry revokedKey;
|
|
foreach (const QCA::KeyStoreEntry key, keylist) {
|
|
if (key.id() == QLatin1String("FEF97E4C4C870810")) {
|
|
validKey = key;
|
|
} else if (key.id() == QLatin1String("DD773CA7E4E22769")) {
|
|
expiredKey = key;
|
|
} else if (key.id() == QLatin1String("1D6A028CC4F444A9")) {
|
|
revokedKey = key;
|
|
}
|
|
}
|
|
|
|
QCOMPARE(validKey.isNull(), false);
|
|
QCOMPARE(validKey.name(),
|
|
QStringLiteral(
|
|
"QCA Test Key 2 (Non-expired encryption key with expired encryption subkey) <qca@example.com>"));
|
|
QCOMPARE(validKey.type(), QCA::KeyStoreEntry::TypePGPSecretKey);
|
|
QCOMPARE(validKey.id(), QStringLiteral("FEF97E4C4C870810"));
|
|
QCOMPARE(validKey.pgpSecretKey().isNull(), false);
|
|
QCOMPARE(validKey.pgpPublicKey().isNull(), false);
|
|
|
|
QCOMPARE(expiredKey.isNull(), false);
|
|
QCOMPARE(expiredKey.name(),
|
|
QStringLiteral("QCA Test Key (Unit test key for expired subkeys) <qca@example.com>"));
|
|
QCOMPARE(expiredKey.type(), QCA::KeyStoreEntry::TypePGPSecretKey);
|
|
QCOMPARE(expiredKey.id(), QStringLiteral("DD773CA7E4E22769"));
|
|
QCOMPARE(expiredKey.pgpSecretKey().isNull(), false);
|
|
QCOMPARE(expiredKey.pgpPublicKey().isNull(), false);
|
|
|
|
QCOMPARE(revokedKey.isNull(), false);
|
|
QCOMPARE(revokedKey.name(), QStringLiteral("QCA Test Key (Revoked unit test key) <qca@example.com>"));
|
|
QCOMPARE(revokedKey.type(), QCA::KeyStoreEntry::TypePGPSecretKey);
|
|
QCOMPARE(revokedKey.id(), QStringLiteral("1D6A028CC4F444A9"));
|
|
QCOMPARE(revokedKey.pgpSecretKey().isNull(), false);
|
|
QCOMPARE(revokedKey.pgpPublicKey().isNull(), false);
|
|
|
|
// Test encrypting to a non-expired key first
|
|
QByteArray nonExpiredMessage("Encrypting to non-expired subkey");
|
|
|
|
QCA::SecureMessageKey key;
|
|
key.setPGPPublicKey(validKey.pgpPublicKey());
|
|
|
|
QCA::OpenPGP pgp;
|
|
QCA::SecureMessage msg(&pgp);
|
|
msg.setFormat(QCA::SecureMessage::Ascii);
|
|
msg.setRecipient(key);
|
|
msg.startEncrypt();
|
|
msg.update(nonExpiredMessage);
|
|
msg.end();
|
|
msg.waitForFinished(5000);
|
|
|
|
QVERIFY(msg.success());
|
|
QByteArray encResult = msg.read();
|
|
|
|
// Decrypt and compare it
|
|
QCA::OpenPGP pgp1;
|
|
QCA::SecureMessage msg1(&pgp1);
|
|
msg1.startDecrypt();
|
|
msg1.update(encResult);
|
|
msg1.end();
|
|
msg1.waitForFinished(5000);
|
|
|
|
QVERIFY(msg1.success());
|
|
QByteArray decResult = msg1.read();
|
|
QCOMPARE(decResult, nonExpiredMessage);
|
|
|
|
// Test encrypting to the expired key
|
|
QByteArray expiredMessage("Encrypting to the expired key");
|
|
|
|
QCA::SecureMessageKey key2;
|
|
key2.setPGPPublicKey(expiredKey.pgpPublicKey());
|
|
|
|
QCA::OpenPGP pgp2;
|
|
QCA::SecureMessage msg2(&pgp2);
|
|
msg2.setFormat(QCA::SecureMessage::Ascii);
|
|
msg2.setRecipient(key2);
|
|
msg2.startEncrypt();
|
|
msg2.update(expiredMessage);
|
|
msg2.end();
|
|
msg2.waitForFinished(5000);
|
|
|
|
QCOMPARE(msg2.success(), false);
|
|
// Note: If gpg worked as expected, msg.errorCode() should
|
|
// equal QCA::SecureMessage::ErrorEncryptExpired, but currently
|
|
// it omits the reason for failure, so we check for both values.
|
|
QVERIFY((msg2.errorCode() == QCA::SecureMessage::ErrorEncryptExpired) ||
|
|
(msg2.errorCode() == QCA::SecureMessage::ErrorEncryptInvalid));
|
|
|
|
// Test encrypting to the revoked key
|
|
QByteArray revokedMessage("Encrypting to the revoked key");
|
|
|
|
QCA::SecureMessageKey key3;
|
|
key3.setPGPPublicKey(expiredKey.pgpPublicKey());
|
|
|
|
QCA::OpenPGP pgp3;
|
|
QCA::SecureMessage msg3(&pgp3);
|
|
msg3.setFormat(QCA::SecureMessage::Ascii);
|
|
msg3.setRecipient(key3);
|
|
msg3.startEncrypt();
|
|
msg3.update(revokedMessage);
|
|
msg3.end();
|
|
msg3.waitForFinished(5000);
|
|
|
|
QCOMPARE(msg3.success(), false);
|
|
// Note: If gpg worked as expected, msg.errorCode() should
|
|
// equal QCA::SecureMessage::ErrorEncryptRevoked, but currently
|
|
// it omits the reason for failure, so we check for both values.
|
|
QVERIFY((msg3.errorCode() == QCA::SecureMessage::ErrorEncryptRevoked) ||
|
|
(msg3.errorCode() == QCA::SecureMessage::ErrorEncryptInvalid));
|
|
}
|
|
|
|
if (!oldGNUPGHOME.isNull()) {
|
|
qca_setenv("GNUPGHOME", oldGNUPGHOME.data(), 1);
|
|
}
|
|
}
|
|
|
|
QTEST_MAIN(PgpUnitTest)
|
|
|
|
#include "pgpunittest.moc"
|