/**
 * 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 <QTest>
#include <QtCrypto>

#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', Qt::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', Qt::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', Qt::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', Qt::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', Qt::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', Qt::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"
