/*
 * Copyright (C) 2003-2007  Justin Karneges <justin@affinix.com>
 * Copyright (C) 2004-2006  Brad Hards <bradh@frogmouth.net>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301  USA
 *
 */

#include "qca_cert.h"

#include "qca_publickey.h"
#include "qcaprovider.h"

#include <QFile>
#include <QRegExp>
#include <QRegularExpression>
#include <QTextStream>
#include <QUrl>

#include <cstdlib>

namespace QCA {

Provider::Context *getContext(const QString &type, const QString &provider);
Provider::Context *getContext(const QString &type, Provider *p);

// from qca_publickey.cpp
bool         stringToFile(const QString &fileName, const QString &content);
bool         stringFromFile(const QString &fileName, QString *s);
bool         arrayToFile(const QString &fileName, const QByteArray &content);
bool         arrayFromFile(const QString &fileName, QByteArray *a);
bool         ask_passphrase(const QString &fname, void *ptr, SecureArray *answer);
ProviderList allProviders();
Provider *   providerForName(const QString &name);
bool         use_asker_fallback(ConvertResult r);

// last 3 arguments must be valid, and chain must be empty
static bool get_pkcs12_der(const QByteArray & der,
                           const QString &    fileName,
                           void *             ptr,
                           const SecureArray &passphrase,
                           ConvertResult *    result,
                           const QString &    provider,
                           QString *          name,
                           CertificateChain * chain,
                           PrivateKey *       key)
{
    QString              _name;
    QList<CertContext *> list;
    PKeyContext *        kc = nullptr;

    PKCS12Context *pix = static_cast<PKCS12Context *>(getContext(QStringLiteral("pkcs12"), provider));
    ConvertResult  r   = pix->fromPKCS12(der, passphrase, &_name, &list, &kc);

    // error converting without passphrase?  maybe a passphrase is needed
    if (use_asker_fallback(r) && passphrase.isEmpty()) {
        SecureArray pass;
        if (ask_passphrase(fileName, ptr, &pass))
            r = pix->fromPKCS12(der, pass, &_name, &list, &kc);
    }
    delete pix;

    if (result)
        *result = r;

    if (r == ConvertGood) {
        *name = _name;
        for (int n = 0; n < list.count(); ++n) {
            Certificate cert;
            cert.change(list[n]);
            chain->append(cert);
        }
        key->change(kc);
        return true;
    }
    return false;
}

static CertificateInfo orderedToMap(const CertificateInfoOrdered &info)
{
    CertificateInfo out;

    // first, do all but EmailLegacy
    for (int n = 0; n < info.count(); ++n) {
        const CertificateInfoPair &i = info[n];
        if (i.type().known() != EmailLegacy)
            out.insert(i.type(), i.value());
    }

    // lastly, apply EmailLegacy
    for (int n = 0; n < info.count(); ++n) {
        const CertificateInfoPair &i = info[n];
        if (i.type().known() == EmailLegacy) {
            // de-dup
            const QList<QString> emails = out.values(Email);
            if (!emails.contains(i.value()))
                out.insert(Email, i.value());
        }
    }

    return out;
}

static void moveMapValues(CertificateInfo *from, CertificateInfoOrdered *to, const CertificateInfoType &type)
{
    const QList<QString> values = from->values(type);
    from->remove(type);

    // multimap values are stored in reverse.  we'll insert backwards in
    //   order to right them.
    for (int n = values.count() - 1; n >= 0; --n)
        to->append(CertificateInfoPair(type, values[n]));
}

static CertificateInfoOrdered mapToOrdered(const CertificateInfo &info)
{
    CertificateInfo        in = info;
    CertificateInfoOrdered out;

    // have a specific order for some types
    moveMapValues(&in, &out, CommonName);
    moveMapValues(&in, &out, Country);
    moveMapValues(&in, &out, Locality);
    moveMapValues(&in, &out, State);
    moveMapValues(&in, &out, Organization);
    moveMapValues(&in, &out, OrganizationalUnit);
    moveMapValues(&in, &out, Email);
    moveMapValues(&in, &out, URI);
    moveMapValues(&in, &out, DNS);
    moveMapValues(&in, &out, IPAddress);
    moveMapValues(&in, &out, XMPP);

    // get remaining types
    const QList<CertificateInfoType> typesLeft = in.keys();

    // dedup
    QList<CertificateInfoType> types;
    for (int n = 0; n < typesLeft.count(); ++n) {
        if (!types.contains(typesLeft[n]))
            types += typesLeft[n];
    }

    // insert the rest of the types in the order we got them (map order)
    for (int n = 0; n < types.count(); ++n)
        moveMapValues(&in, &out, types[n]);

    Q_ASSERT(in.isEmpty());

    return out;
}

//----------------------------------------------------------------------------
// Global
//----------------------------------------------------------------------------
static const char CommonName_id[]            = "2.5.4.3";
static const char Email_id[]                 = "GeneralName.rfc822Name";
static const char EmailLegacy_id[]           = "1.2.840.113549.1.9.1";
static const char Organization_id[]          = "2.5.4.10";
static const char OrganizationalUnit_id[]    = "2.5.4.11";
static const char Locality_id[]              = "2.5.4.7";
static const char IncorporationLocality_id[] = "1.3.6.1.4.1.311.60.2.1.1";
static const char State_id[]                 = "2.5.4.8";
static const char IncorporationState_id[]    = "1.3.6.1.4.1.311.60.2.1.2";
static const char Country_id[]               = "2.5.4.6";
static const char IncorporationCountry_id[]  = "1.3.6.1.4.1.311.60.2.1.3";
static const char URI_id[]                   = "GeneralName.uniformResourceIdentifier";
static const char DNS_id[]                   = "GeneralName.dNSName";
static const char IPAddress_id[]             = "GeneralName.iPAddress";
static const char XMPP_id[]                  = "1.3.6.1.5.5.7.8.5";

static const char DigitalSignature_id[]   = "KeyUsage.digitalSignature";
static const char NonRepudiation_id[]     = "KeyUsage.nonRepudiation";
static const char KeyEncipherment_id[]    = "KeyUsage.keyEncipherment";
static const char DataEncipherment_id[]   = "KeyUsage.dataEncipherment";
static const char KeyAgreement_id[]       = "KeyUsage.keyAgreement";
static const char KeyCertificateSign_id[] = "KeyUsage.keyCertSign";
static const char CRLSign_id[]            = "KeyUsage.crlSign";
static const char EncipherOnly_id[]       = "KeyUsage.encipherOnly";
static const char DecipherOnly_id[]       = "KeyUsage.decipherOnly";
static const char ServerAuth_id[]         = "1.3.6.1.5.5.7.3.1";
static const char ClientAuth_id[]         = "1.3.6.1.5.5.7.3.2";
static const char CodeSigning_id[]        = "1.3.6.1.5.5.7.3.3";
static const char EmailProtection_id[]    = "1.3.6.1.5.5.7.3.4";
static const char IPSecEndSystem_id[]     = "1.3.6.1.5.5.7.3.5";
static const char IPSecTunnel_id[]        = "1.3.6.1.5.5.7.3.6";
static const char IPSecUser_id[]          = "1.3.6.1.5.5.7.3.7";
static const char TimeStamping_id[]       = "1.3.6.1.5.5.7.3.8";
static const char OCSPSigning_id[]        = "1.3.6.1.5.5.7.3.9";

static QString knownToId(CertificateInfoTypeKnown k)
{
    const char *out = nullptr;
    switch (k) {
    case CommonName:
        out = CommonName_id;
        break;
    case Email:
        out = Email_id;
        break;
    case EmailLegacy:
        out = EmailLegacy_id;
        break;
    case Organization:
        out = Organization_id;
        break;
    case OrganizationalUnit:
        out = OrganizationalUnit_id;
        break;
    case Locality:
        out = Locality_id;
        break;
    case IncorporationLocality:
        out = IncorporationLocality_id;
        break;
    case State:
        out = State_id;
        break;
    case IncorporationState:
        out = IncorporationState_id;
        break;
    case Country:
        out = Country_id;
        break;
    case IncorporationCountry:
        out = IncorporationCountry_id;
        break;
    case URI:
        out = URI_id;
        break;
    case DNS:
        out = DNS_id;
        break;
    case IPAddress:
        out = IPAddress_id;
        break;
    case XMPP:
        out = XMPP_id;
        break;
    }
    Q_ASSERT(out);
    if (!out)
        abort();
    return QString::fromLatin1(out);
}

static int idToKnown(const QString &id)
{
    if (id == QLatin1String(CommonName_id))
        return CommonName;
    else if (id == QLatin1String(Email_id))
        return Email;
    else if (id == QLatin1String(EmailLegacy_id))
        return EmailLegacy;
    else if (id == QLatin1String(Organization_id))
        return Organization;
    else if (id == QLatin1String(OrganizationalUnit_id))
        return OrganizationalUnit;
    else if (id == QLatin1String(Locality_id))
        return Locality;
    else if (id == QLatin1String(IncorporationLocality_id))
        return IncorporationLocality;
    else if (id == QLatin1String(State_id))
        return State;
    else if (id == QLatin1String(IncorporationState_id))
        return IncorporationState;
    else if (id == QLatin1String(Country_id))
        return Country;
    else if (id == QLatin1String(IncorporationCountry_id))
        return IncorporationCountry;
    else if (id == QLatin1String(URI_id))
        return URI;
    else if (id == QLatin1String(DNS_id))
        return DNS;
    else if (id == QLatin1String(IPAddress_id))
        return IPAddress;
    else if (id == QLatin1String(XMPP_id))
        return XMPP;
    else
        return -1;
}

static CertificateInfoType::Section knownToSection(CertificateInfoTypeKnown k)
{
    switch (k) {
    case CommonName:
    case EmailLegacy:
    case Organization:
    case OrganizationalUnit:
    case Locality:
    case IncorporationLocality:
    case State:
    case IncorporationState:
    case Country:
    case IncorporationCountry:
        return CertificateInfoType::DN;
    default:
        break;
    }
    return CertificateInfoType::AlternativeName;
}

static const char *knownToShortName(CertificateInfoTypeKnown k)
{
    switch (k) {
    case CommonName:
        return "CN";
    case Locality:
        return "L";
    case State:
        return "ST";
    case Organization:
        return "O";
    case OrganizationalUnit:
        return "OU";
    case Country:
        return "C";
    case EmailLegacy:
        return "emailAddress";
    default:
        break;
    }
    return nullptr;
}

static QString constraintKnownToId(ConstraintTypeKnown k)
{
    const char *out = nullptr;
    switch (k) {
    case DigitalSignature:
        out = DigitalSignature_id;
        break;
    case NonRepudiation:
        out = NonRepudiation_id;
        break;
    case KeyEncipherment:
        out = KeyEncipherment_id;
        break;
    case DataEncipherment:
        out = DataEncipherment_id;
        break;
    case KeyAgreement:
        out = KeyAgreement_id;
        break;
    case KeyCertificateSign:
        out = KeyCertificateSign_id;
        break;
    case CRLSign:
        out = CRLSign_id;
        break;
    case EncipherOnly:
        out = EncipherOnly_id;
        break;
    case DecipherOnly:
        out = DecipherOnly_id;
        break;
    case ServerAuth:
        out = ServerAuth_id;
        break;
    case ClientAuth:
        out = ClientAuth_id;
        break;
    case CodeSigning:
        out = CodeSigning_id;
        break;
    case EmailProtection:
        out = EmailProtection_id;
        break;
    case IPSecEndSystem:
        out = IPSecEndSystem_id;
        break;
    case IPSecTunnel:
        out = IPSecTunnel_id;
        break;
    case IPSecUser:
        out = IPSecUser_id;
        break;
    case TimeStamping:
        out = TimeStamping_id;
        break;
    case OCSPSigning:
        out = OCSPSigning_id;
        break;
    }
    Q_ASSERT(out);
    if (!out)
        abort();
    return QString::fromLatin1(out);
}

static int constraintIdToKnown(const QString &id)
{
    if (id == QLatin1String(DigitalSignature_id))
        return DigitalSignature;
    else if (id == QLatin1String(NonRepudiation_id))
        return NonRepudiation;
    else if (id == QLatin1String(KeyEncipherment_id))
        return KeyEncipherment;
    else if (id == QLatin1String(DataEncipherment_id))
        return DataEncipherment;
    else if (id == QLatin1String(KeyAgreement_id))
        return KeyAgreement;
    else if (id == QLatin1String(KeyCertificateSign_id))
        return KeyCertificateSign;
    else if (id == QLatin1String(CRLSign_id))
        return CRLSign;
    else if (id == QLatin1String(EncipherOnly_id))
        return EncipherOnly;
    else if (id == QLatin1String(DecipherOnly_id))
        return DecipherOnly;
    else if (id == QLatin1String(ServerAuth_id))
        return ServerAuth;
    else if (id == QLatin1String(ClientAuth_id))
        return ClientAuth;
    else if (id == QLatin1String(CodeSigning_id))
        return CodeSigning;
    else if (id == QLatin1String(EmailProtection_id))
        return EmailProtection;
    else if (id == QLatin1String(IPSecEndSystem_id))
        return IPSecEndSystem;
    else if (id == QLatin1String(IPSecTunnel_id))
        return IPSecTunnel;
    else if (id == QLatin1String(IPSecUser_id))
        return IPSecUser;
    else if (id == QLatin1String(TimeStamping_id))
        return TimeStamping;
    else if (id == QLatin1String(OCSPSigning_id))
        return OCSPSigning;
    else
        return -1;
}

static ConstraintType::Section constraintKnownToSection(ConstraintTypeKnown k)
{
    switch (k) {
    case DigitalSignature:
    case NonRepudiation:
    case KeyEncipherment:
    case DataEncipherment:
    case KeyAgreement:
    case KeyCertificateSign:
    case CRLSign:
    case EncipherOnly:
    case DecipherOnly:
        return ConstraintType::KeyUsage;
    default:
        break;
    }
    return ConstraintType::ExtendedKeyUsage;
}

static QString dnLabel(const CertificateInfoType &type)
{
    const char *str = knownToShortName(type.known());
    if (str)
        return QString::fromLatin1(str);

    const QString id = type.id();
    // is it an oid?
    if (id[0].isDigit())
        return QStringLiteral("OID.") + id;

    return QStringLiteral("qca.") + id;
}

QString orderedToDNString(const CertificateInfoOrdered &in)
{
    QStringList parts;
    foreach (const CertificateInfoPair &i, in) {
        if (i.type().section() != CertificateInfoType::DN)
            continue;

        const QString name = dnLabel(i.type());
        parts += name + QLatin1Char('=') + i.value();
    }
    return parts.join(QStringLiteral(", "));
}

CertificateInfoOrdered orderedDNOnly(const CertificateInfoOrdered &in)
{
    CertificateInfoOrdered out;
    for (int n = 0; n < in.count(); ++n) {
        if (in[n].type().section() == CertificateInfoType::DN)
            out += in[n];
    }
    return out;
}

static QString baseCertName(const CertificateInfo &info)
{
    QString str = info.value(CommonName);
    if (str.isEmpty()) {
        str = info.value(Organization);
        if (str.isEmpty())
            str = QStringLiteral("Unnamed");
    }
    return str;
}

static QList<int> findSameName(const QString &name, const QStringList &list)
{
    QList<int> out;
    for (int n = 0; n < list.count(); ++n) {
        if (list[n] == name)
            out += n;
    }
    return out;
}

static QString
uniqueSubjectValue(const CertificateInfoType &type, const QList<int> &items, const QList<Certificate> &certs, int i)
{
    QStringList vals = certs[items[i]].subjectInfo().values(type);
    if (!vals.isEmpty()) {
        foreach (int n, items) {
            if (n == items[i])
                continue;

            const QStringList other_vals = certs[n].subjectInfo().values(type);
            for (int k = 0; k < vals.count(); ++k) {
                if (other_vals.contains(vals[k])) {
                    vals.removeAt(k);
                    break;
                }
            }

            if (vals.isEmpty())
                break;
        }

        if (!vals.isEmpty())
            return vals[0];
    }

    return QString();
}

static QString uniqueIssuerName(const QList<int> &items, const QList<Certificate> &certs, int i)
{
    const QString val = baseCertName(certs[items[i]].issuerInfo());

    bool found = false;
    foreach (int n, items) {
        if (n == items[i])
            continue;

        const QString other_val = baseCertName(certs[n].issuerInfo());
        if (other_val == val) {
            found = true;
            break;
        }
    }

    if (!found)
        return val;

    return QString();
}

static const char *constraintToString(const ConstraintType &type)
{
    switch (type.known()) {
    case DigitalSignature:
        return "DigitalSignature";
    case NonRepudiation:
        return "NonRepudiation";
    case KeyEncipherment:
        return "KeyEncipherment";
    case DataEncipherment:
        return "DataEncipherment";
    case KeyAgreement:
        return "KeyAgreement";
    case KeyCertificateSign:
        return "KeyCertificateSign";
    case CRLSign:
        return "CRLSign";
    case EncipherOnly:
        return "EncipherOnly";
    case DecipherOnly:
        return "DecipherOnly";
    case ServerAuth:
        return "ServerAuth";
    case ClientAuth:
        return "ClientAuth";
    case CodeSigning:
        return "CodeSigning";
    case EmailProtection:
        return "EmailProtection";
    case IPSecEndSystem:
        return "IPSecEndSystem";
    case IPSecTunnel:
        return "IPSecTunnel";
    case IPSecUser:
        return "IPSecUser";
    case TimeStamping:
        return "TimeStamping";
    case OCSPSigning:
        return "OCSPSigning";
    }
    return nullptr;
}

static QString
uniqueConstraintValue(const ConstraintType &type, const QList<int> &items, const QList<Certificate> &certs, int i)
{
    if (certs[items[i]].constraints().contains(type)) {
        bool found = false;
        foreach (int n, items) {
            if (n == items[i])
                continue;

            Constraints other_vals = certs[n].constraints();
            if (other_vals.contains(type)) {
                found = true;
                break;
            }
        }

        if (!found)
            return QString::fromLatin1(constraintToString(type));
    }

    return QString();
}

static QString makeUniqueName(const QList<int> &items, const QStringList &list, const QList<Certificate> &certs, int i)
{
    QString str, name;

    // different organization?
    str = uniqueSubjectValue(Organization, items, certs, i);
    if (!str.isEmpty()) {
        name = list[items[i]] + QStringLiteral(" of ") + str;
        goto end;
    }

    // different organizational unit?
    str = uniqueSubjectValue(OrganizationalUnit, items, certs, i);
    if (!str.isEmpty()) {
        name = list[items[i]] + QStringLiteral(" of ") + str;
        goto end;
    }

    // different email address?
    str = uniqueSubjectValue(Email, items, certs, i);
    if (!str.isEmpty()) {
        name = list[items[i]] + QStringLiteral(" <") + str + QLatin1Char('>');
        goto end;
    }

    // different xmpp addresses?
    str = uniqueSubjectValue(XMPP, items, certs, i);
    if (!str.isEmpty()) {
        name = list[items[i]] + QStringLiteral(" <xmpp:") + str + QLatin1Char('>');
        goto end;
    }

    // different issuers?
    str = uniqueIssuerName(items, certs, i);
    if (!str.isEmpty()) {
        name = list[items[i]] + QStringLiteral(" by ") + str;
        goto end;
    }

    // different usages?

    // DigitalSignature
    str = uniqueConstraintValue(DigitalSignature, items, certs, i);
    if (!str.isEmpty()) {
        name = list[items[i]] + QStringLiteral(" for ") + str;
        goto end;
    }

    // ClientAuth
    str = uniqueConstraintValue(ClientAuth, items, certs, i);
    if (!str.isEmpty()) {
        name = list[items[i]] + QStringLiteral(" for ") + str;
        goto end;
    }

    // EmailProtection
    str = uniqueConstraintValue(EmailProtection, items, certs, i);
    if (!str.isEmpty()) {
        name = list[items[i]] + QStringLiteral(" for ") + str;
        goto end;
    }

    // DataEncipherment
    str = uniqueConstraintValue(DataEncipherment, items, certs, i);
    if (!str.isEmpty()) {
        name = list[items[i]] + QStringLiteral(" for ") + str;
        goto end;
    }

    // EncipherOnly
    str = uniqueConstraintValue(EncipherOnly, items, certs, i);
    if (!str.isEmpty()) {
        name = list[items[i]] + QStringLiteral(" for ") + str;
        goto end;
    }

    // DecipherOnly
    str = uniqueConstraintValue(DecipherOnly, items, certs, i);
    if (!str.isEmpty()) {
        name = list[items[i]] + QStringLiteral(" for ") + str;
        goto end;
    }

    // if there's nothing easily unique, then do a DN string
    name = certs[items[i]].subjectInfoOrdered().toString();

end:
    return name;
}

QStringList makeFriendlyNames(const QList<Certificate> &list)
{
    QStringList names;

    // give a base name to all certs first
    foreach (const Certificate &cert, list)
        names += baseCertName(cert.subjectInfo());

    // come up with a collision list
    QList<QList<int>> itemCollisions;
    foreach (const QString &name, names) {
        // anyone else using this name?
        const QList<int> items = findSameName(name, names);
        if (items.count() > 1) {
            // don't save duplicate collisions
            bool haveAlready = false;
            foreach (const QList<int> &other, itemCollisions) {
                foreach (int n, items) {
                    if (other.contains(n)) {
                        haveAlready = true;
                        break;
                    }
                }

                if (haveAlready)
                    break;
            }

            if (haveAlready)
                continue;

            itemCollisions += items;
        }
    }

    // resolve collisions by providing extra details
    foreach (const QList<int> &items, itemCollisions) {
        // printf("%d items are using [%s]\n", items.count(), qPrintable(names[items[0]]));

        for (int n = 0; n < items.count(); ++n) {
            names[items[n]] = makeUniqueName(items, names, list, n);
            // printf("  %d: reassigning: [%s]\n", items[n], qPrintable(names[items[n]]));
        }
    }

    return names;
}

//----------------------------------------------------------------------------
// CertificateInfoType
//----------------------------------------------------------------------------
class CertificateInfoType::Private : public QSharedData
{
public:
    CertificateInfoType::Section section;
    int                          known;
    QString                      id;

    Private()
        : section(CertificateInfoType::DN)
        , known(-1)
    {
    }
};

CertificateInfoType::CertificateInfoType()
    : d(new Private)
{
}

CertificateInfoType::CertificateInfoType(CertificateInfoTypeKnown known)
    : d(new Private)
{
    d->section = knownToSection(known);
    d->known   = known;
    d->id      = knownToId(known); // always valid
}

CertificateInfoType::CertificateInfoType(const QString &id, Section section)
    : d(new Private)
{
    d->section = section;
    d->known   = idToKnown(id); // can be -1 for unknown
    d->id      = id;
}

CertificateInfoType::CertificateInfoType(const CertificateInfoType &from)
    : d(from.d)
{
}

CertificateInfoType::~CertificateInfoType()
{
}

CertificateInfoType &CertificateInfoType::operator=(const CertificateInfoType &from)
{
    d = from.d;
    return *this;
}

CertificateInfoType::Section CertificateInfoType::section() const
{
    return d->section;
}

CertificateInfoTypeKnown CertificateInfoType::known() const
{
    return (CertificateInfoTypeKnown)d->known;
}

QString CertificateInfoType::id() const
{
    return d->id;
}

bool CertificateInfoType::operator<(const CertificateInfoType &other) const
{
    // sort by knowns (in enum order), then by ids (in string order)
    if (d->known != -1) {
        if (other.d->known == -1)
            return true;
        else if (d->known < other.d->known)
            return true;
        else
            return false;
    } else {
        if (other.d->known != -1)
            return false;
        else if (d->id < other.d->id)
            return true;
        else
            return false;
    }
}

bool CertificateInfoType::operator==(const CertificateInfoType &other) const
{
    // are both known types?
    if (d->known != -1 && other.d->known != -1) {
        // if so, compare the ints
        if (d->known != other.d->known)
            return false;
    } else {
        // otherwise, compare the string ids
        if (d->id != other.d->id)
            return false;
    }

    if (d->section != other.d->section)
        return false;

    return true;
}

//----------------------------------------------------------------------------
// CertificateInfoPair
//----------------------------------------------------------------------------
class CertificateInfoPair::Private : public QSharedData
{
public:
    CertificateInfoType type;
    QString             value;
};

CertificateInfoPair::CertificateInfoPair()
    : d(new Private)
{
}

CertificateInfoPair::CertificateInfoPair(const CertificateInfoType &type, const QString &value)
    : d(new Private)
{
    d->type  = type;
    d->value = value;
}

CertificateInfoPair::CertificateInfoPair(const CertificateInfoPair &from)
    : d(from.d)
{
}

CertificateInfoPair::~CertificateInfoPair()
{
}

CertificateInfoPair &CertificateInfoPair::operator=(const CertificateInfoPair &from)
{
    d = from.d;
    return *this;
}

CertificateInfoType CertificateInfoPair::type() const
{
    return d->type;
}

QString CertificateInfoPair::value() const
{
    return d->value;
}

bool CertificateInfoPair::operator==(const CertificateInfoPair &other) const
{
    if (d->type == other.d->type && d->value == other.d->value)
        return true;
    return false;
}

//----------------------------------------------------------------------------
// ConstraintType
//----------------------------------------------------------------------------
class ConstraintType::Private : public QSharedData
{
public:
    ConstraintType::Section section;
    int                     known;
    QString                 id;

    Private()
        : section(ConstraintType::KeyUsage)
        , known(-1)
    {
    }
};

ConstraintType::ConstraintType()
    : d(new Private)
{
}

ConstraintType::ConstraintType(ConstraintTypeKnown known)
    : d(new Private)
{
    d->section = constraintKnownToSection(known);
    d->known   = known;
    d->id      = constraintKnownToId(known); // always valid
}

ConstraintType::ConstraintType(const QString &id, Section section)
    : d(new Private)
{
    d->section = section;
    d->known   = constraintIdToKnown(id); // can be -1 for unknown
    d->id      = id;
}

ConstraintType::ConstraintType(const ConstraintType &from)
    : d(from.d)
{
}

ConstraintType::~ConstraintType()
{
}

ConstraintType &ConstraintType::operator=(const ConstraintType &from)
{
    d = from.d;
    return *this;
}

ConstraintType::Section ConstraintType::section() const
{
    return d->section;
}

ConstraintTypeKnown ConstraintType::known() const
{
    return (ConstraintTypeKnown)d->known;
}

QString ConstraintType::id() const
{
    return d->id;
}

bool ConstraintType::operator<(const ConstraintType &other) const
{
    // sort by knowns (in enum order), then by ids (in string order)
    if (d->known != -1) {
        if (other.d->known == -1)
            return true;
        else if (d->known < other.d->known)
            return true;
        else
            return false;
    } else {
        if (other.d->known != -1)
            return false;
        else if (d->id < other.d->id)
            return true;
        else
            return false;
    }
}

bool ConstraintType::operator==(const ConstraintType &other) const
{
    // are both known types?
    if (d->known != -1 && other.d->known != -1) {
        // if so, compare the ints
        if (d->known != other.d->known)
            return false;
    } else {
        // otherwise, compare the string ids
        if (d->id != other.d->id)
            return false;
    }

    if (d->section != other.d->section)
        return false;

    return true;
}

//----------------------------------------------------------------------------
// CertificateOptions
//----------------------------------------------------------------------------
class CertificateOptions::Private
{
public:
    CertificateRequestFormat format;

    QString                challenge;
    CertificateInfoOrdered info;
    CertificateInfo        infoMap;
    Constraints            constraints;
    QStringList            policies;
    QStringList            crlLocations, issuerLocations, ocspLocations;
    bool                   isCA;
    int                    pathLimit;
    BigInteger             serial;
    QDateTime              start, end;

    Private()
        : isCA(false)
        , pathLimit(0)
    {
    }
};

CertificateOptions::CertificateOptions(CertificateRequestFormat f)
{
    d         = new Private;
    d->format = f;
}

CertificateOptions::CertificateOptions(const CertificateOptions &from)
{
    d = new Private(*from.d);
}

CertificateOptions::~CertificateOptions()
{
    delete d;
}

CertificateOptions &CertificateOptions::operator=(const CertificateOptions &from)
{
    *d = *from.d;
    return *this;
}

CertificateRequestFormat CertificateOptions::format() const
{
    return d->format;
}

void CertificateOptions::setFormat(CertificateRequestFormat f)
{
    d->format = f;
}

bool CertificateOptions::isValid() const
{
    // logic from Botan
    if (d->infoMap.value(CommonName).isEmpty() || d->infoMap.value(Country).isEmpty())
        return false;
    if (d->infoMap.value(Country).length() != 2)
        return false;
    if (d->start >= d->end)
        return false;
    return true;
}

QString CertificateOptions::challenge() const
{
    return d->challenge;
}

CertificateInfo CertificateOptions::info() const
{
    return d->infoMap;
}

CertificateInfoOrdered CertificateOptions::infoOrdered() const
{
    return d->info;
}

Constraints CertificateOptions::constraints() const
{
    return d->constraints;
}

QStringList CertificateOptions::policies() const
{
    return d->policies;
}

QStringList CertificateOptions::crlLocations() const
{
    return d->crlLocations;
}

QStringList CertificateOptions::issuerLocations() const
{
    return d->issuerLocations;
}

QStringList CertificateOptions::ocspLocations() const
{
    return d->ocspLocations;
}

bool CertificateOptions::isCA() const
{
    return d->isCA;
}

int CertificateOptions::pathLimit() const
{
    return d->pathLimit;
}

BigInteger CertificateOptions::serialNumber() const
{
    return d->serial;
}

QDateTime CertificateOptions::notValidBefore() const
{
    return d->start;
}

QDateTime CertificateOptions::notValidAfter() const
{
    return d->end;
}

void CertificateOptions::setChallenge(const QString &s)
{
    d->challenge = s;
}

void CertificateOptions::setInfo(const CertificateInfo &info)
{
    d->info    = mapToOrdered(info);
    d->infoMap = info;
}

void CertificateOptions::setInfoOrdered(const CertificateInfoOrdered &info)
{
    d->info    = info;
    d->infoMap = orderedToMap(info);
}

void CertificateOptions::setConstraints(const Constraints &constraints)
{
    d->constraints = constraints;
}

void CertificateOptions::setPolicies(const QStringList &policies)
{
    d->policies = policies;
}

void CertificateOptions::setCRLLocations(const QStringList &locations)
{
    d->crlLocations = locations;
}

void CertificateOptions::setIssuerLocations(const QStringList &locations)
{
    d->issuerLocations = locations;
}

void CertificateOptions::setOCSPLocations(const QStringList &locations)
{
    d->ocspLocations = locations;
}

void CertificateOptions::setAsCA(int pathLimit)
{
    d->isCA      = true;
    d->pathLimit = pathLimit;
}

void CertificateOptions::setAsUser()
{
    d->isCA      = false;
    d->pathLimit = 0;
}

void CertificateOptions::setSerialNumber(const BigInteger &i)
{
    d->serial = i;
}

void CertificateOptions::setValidityPeriod(const QDateTime &start, const QDateTime &end)
{
    d->start = start;
    d->end   = end;
}

//----------------------------------------------------------------------------
// Certificate
//----------------------------------------------------------------------------
// ip address string to binary (msb), adapted from jdns (adapted from qt)
// return: size 4 = ipv4, size 16 = ipv6, size 0 = error
static QByteArray ipaddr_str2bin(const QString &str)
{
    // ipv6
    if (str.contains(QLatin1Char(':'))) {
        const QStringList parts = str.split(QLatin1Char(':'), Qt::KeepEmptyParts);
        if (parts.count() < 3 || parts.count() > 8)
            return QByteArray();

        QByteArray ipv6(16, 0);
        int        at   = 16;
        int        fill = 9 - parts.count();
        for (int n = parts.count() - 1; n >= 0; --n) {
            if (at <= 0)
                return QByteArray();

            if (parts[n].isEmpty()) {
                if (n == parts.count() - 1) {
                    if (!parts[n - 1].isEmpty())
                        return QByteArray();
                    ipv6[--at] = 0;
                    ipv6[--at] = 0;
                } else if (n == 0) {
                    if (!parts[n + 1].isEmpty())
                        return QByteArray();
                    ipv6[--at] = 0;
                    ipv6[--at] = 0;
                } else {
                    for (int i = 0; i < fill; ++i) {
                        if (at <= 0)
                            return QByteArray();
                        ipv6[--at] = 0;
                        ipv6[--at] = 0;
                    }
                }
            } else {
                if (parts[n].indexOf(QLatin1Char('.')) == -1) {
                    bool ok;
                    int  x = parts[n].toInt(&ok, 16);
                    if (!ok || x < 0 || x > 0xffff)
                        return QByteArray();
                    ipv6[--at] = x & 0xff;
                    ipv6[--at] = (x >> 8) & 0xff;
                } else {
                    if (n != parts.count() - 1)
                        return QByteArray();

                    const QByteArray buf = ipaddr_str2bin(parts[n]);
                    if (buf.isEmpty())
                        return QByteArray();

                    ipv6[--at] = buf[3];
                    ipv6[--at] = buf[2];
                    ipv6[--at] = buf[1];
                    ipv6[--at] = buf[0];
                    --fill;
                }
            }
        }

        return ipv6;
    } else if (str.contains(QLatin1Char('.'))) {
        const QStringList parts = str.split(QLatin1Char('.'), Qt::KeepEmptyParts);
        if (parts.count() != 4)
            return QByteArray();

        QByteArray out(4, 0);
        for (int n = 0; n < 4; ++n) {
            bool ok;
            int  x = parts[n].toInt(&ok);
            if (!ok || x < 0 || x > 0xff)
                return QByteArray();
            out[n] = (unsigned char)x;
        }
        return out;
    } else
        return QByteArray();
}

// acedomain must be all lowercase, with no trailing dot or wildcards
static bool cert_match_domain(const QString &certname, const QString &acedomain)
{
    // KSSL strips start/end whitespace, even though such whitespace is
    //   probably not legal anyway. (compat)
    QString name = certname.trimmed();

    // KSSL strips trailing dot, even though the dot is probably not
    //   legal anyway. (compat)
    if (name.length() > 0 && name[name.length() - 1] == QLatin1Char('.'))
        name.truncate(name.length() - 1);

    // after our compatibility modifications, make sure the name isn't
    //   empty.
    if (name.isEmpty())
        return false;

    // lowercase, for later performing case insensitive matching
    name = name.toLower();

    // ensure the cert field contains valid characters only
    if (QRegularExpression(QStringLiteral("[^a-z0-9\\.\\*\\-]")).match(name).hasMatch())
        return false;

    // hack into parts, and require at least 1 part
    const QStringList parts_name = name.split(QLatin1Char('.'), Qt::KeepEmptyParts);
    if (parts_name.isEmpty())
        return false;

    // KSSL checks to make sure the last two parts don't contain
    //   wildcards.  I don't know where it is written that this
    //   should be done, but for compat sake we'll do it.
    if (parts_name[parts_name.count() - 1].contains(QLatin1Char('*')))
        return false;
    if (parts_name.count() >= 2 && parts_name[parts_name.count() - 2].contains(QLatin1Char('*')))
        return false;

    const QStringList parts_compare = acedomain.split(QLatin1Char('.'), Qt::KeepEmptyParts);
    if (parts_compare.isEmpty())
        return false;

    // don't allow empty parts
    foreach (const QString &s, parts_name) {
        if (s.isEmpty())
            return false;
    }
    foreach (const QString &s, parts_compare) {
        if (s.isEmpty())
            return false;
    }

    // RFC2818: "Names may contain the wildcard character * which is
    //   considered to match any single domain name component or
    //   component fragment. E.g., *.a.com matches foo.a.com but not
    //   bar.foo.a.com. f*.com matches foo.com but not bar.com."
    //
    // This means that for the domain to match it must have the
    //   same number of components, wildcards or not.  If there are
    //   wildcards, their scope must only be within the component
    //   they reside in.
    //
    // First, make sure the number of parts is equal.
    if (parts_name.count() != parts_compare.count())
        return false;

    // Now compare each part
    for (int n = 0; n < parts_name.count(); ++n) {
        const QString &p1 = parts_name[n];
        const QString &p2 = parts_compare[n];

        if (!QRegExp(p1, Qt::CaseSensitive, QRegExp::Wildcard).exactMatch(p2))
            return false;
    }

    return true;
}

// ipaddress must be an ipv4 or ipv6 address in binary format
static bool cert_match_ipaddress(const QString &certname, const QByteArray &ipaddress)
{
    // KSSL strips start/end whitespace, even though such whitespace is
    //   probably not legal anyway. (compat)
    QString name = certname.trimmed();

    // KSSL accepts IPv6 in brackets, which is usually done for URIs, but
    //   IMO sounds very strange for a certificate.  We'll follow this
    //   behavior anyway. (compat)
    if (name.length() >= 2 && name[0] == QLatin1Char('[') && name[name.length() - 1] == QLatin1Char(']'))
        name = name.mid(1, name.length() - 2); // chop off brackets

    // after our compatibility modifications, make sure the name isn't
    //   empty.
    if (name.isEmpty())
        return false;

    // convert to binary form
    const QByteArray addr = ipaddr_str2bin(name);
    if (addr.isEmpty())
        return false;

    // not the same?
    if (addr != ipaddress)
        return false;

    return true;
}

class Certificate::Private : public QSharedData
{
public:
    CertificateInfo subjectInfoMap, issuerInfoMap;

    void update(CertContext *c)
    {
        if (c) {
            subjectInfoMap = orderedToMap(c->props()->subject);
            issuerInfoMap  = orderedToMap(c->props()->issuer);
        } else {
            subjectInfoMap = CertificateInfo();
            issuerInfoMap  = CertificateInfo();
        }
    }
};

Certificate::Certificate()
    : d(new Private)
{
}

Certificate::Certificate(const QString &fileName)
    : d(new Private)
{
    *this = fromPEMFile(fileName, nullptr, QString());
}

Certificate::Certificate(const CertificateOptions &opts, const PrivateKey &key, const QString &provider)
    : d(new Private)
{
    CertContext *c = static_cast<CertContext *>(getContext(QStringLiteral("cert"), provider));
    if (c->createSelfSigned(opts, *(static_cast<const PKeyContext *>(key.context()))))
        change(c);
    else
        delete c;
}

Certificate::Certificate(const Certificate &from)
    : Algorithm(from)
    , d(from.d)
{
}

Certificate::~Certificate()
{
}

Certificate &Certificate::operator=(const Certificate &from)
{
    Algorithm::operator=(from);
    d                  = from.d;
    return *this;
}

bool Certificate::isNull() const
{
    return (!context() ? true : false);
}

QDateTime Certificate::notValidBefore() const
{
    return static_cast<const CertContext *>(context())->props()->start;
}

QDateTime Certificate::notValidAfter() const
{
    return static_cast<const CertContext *>(context())->props()->end;
}

CertificateInfo Certificate::subjectInfo() const
{
    return d->subjectInfoMap;
}

CertificateInfoOrdered Certificate::subjectInfoOrdered() const
{
    return static_cast<const CertContext *>(context())->props()->subject;
}

CertificateInfo Certificate::issuerInfo() const
{
    return d->issuerInfoMap;
}

CertificateInfoOrdered Certificate::issuerInfoOrdered() const
{
    return static_cast<const CertContext *>(context())->props()->issuer;
}

Constraints Certificate::constraints() const
{
    return static_cast<const CertContext *>(context())->props()->constraints;
}

QStringList Certificate::policies() const
{
    return static_cast<const CertContext *>(context())->props()->policies;
}

QStringList Certificate::crlLocations() const
{
    return static_cast<const CertContext *>(context())->props()->crlLocations;
}

QStringList Certificate::issuerLocations() const
{
    return static_cast<const CertContext *>(context())->props()->issuerLocations;
}

QStringList Certificate::ocspLocations() const
{
    return static_cast<const CertContext *>(context())->props()->ocspLocations;
}

QString Certificate::commonName() const
{
    return d->subjectInfoMap.value(CommonName);
}

BigInteger Certificate::serialNumber() const
{
    return static_cast<const CertContext *>(context())->props()->serial;
}

PublicKey Certificate::subjectPublicKey() const
{
    PKeyContext *c = static_cast<const CertContext *>(context())->subjectPublicKey();
    PublicKey    key;
    key.change(c);
    return key;
}

bool Certificate::isCA() const
{
    return static_cast<const CertContext *>(context())->props()->isCA;
}

bool Certificate::isSelfSigned() const
{
    return static_cast<const CertContext *>(context())->props()->isSelfSigned;
}

bool Certificate::isIssuerOf(const Certificate &other) const
{
    const CertContext *cc = static_cast<const CertContext *>(other.context());
    return static_cast<const CertContext *>(context())->isIssuerOf(cc);
}

int Certificate::pathLimit() const
{
    return static_cast<const CertContext *>(context())->props()->pathLimit;
}

SignatureAlgorithm Certificate::signatureAlgorithm() const
{
    return static_cast<const CertContext *>(context())->props()->sigalgo;
}

QByteArray Certificate::subjectKeyId() const
{
    return static_cast<const CertContext *>(context())->props()->subjectId;
}

QByteArray Certificate::issuerKeyId() const
{
    return static_cast<const CertContext *>(context())->props()->issuerId;
}

Validity Certificate::validate(const CertificateCollection &trusted,
                               const CertificateCollection &untrusted,
                               UsageMode                    u,
                               ValidateFlags                vf) const
{
    const QList<Certificate> issuers = trusted.certificates() + untrusted.certificates();
    CertificateChain         chain;
    chain += *this;
    Validity result;
    chain = chain.complete(issuers, &result);
    if (result != ValidityGood)
        return result;
    return chain.validate(trusted, untrusted.crls(), u, vf);
}

QByteArray Certificate::toDER() const
{
    return static_cast<const CertContext *>(context())->toDER();
}

QString Certificate::toPEM() const
{
    return static_cast<const CertContext *>(context())->toPEM();
}

bool Certificate::toPEMFile(const QString &fileName) const
{
    return stringToFile(fileName, toPEM());
}

Certificate Certificate::fromDER(const QByteArray &a, ConvertResult *result, const QString &provider)
{
    Certificate   c;
    CertContext * cc = static_cast<CertContext *>(getContext(QStringLiteral("cert"), provider));
    ConvertResult r  = cc->fromDER(a);
    if (result)
        *result = r;
    if (r == ConvertGood)
        c.change(cc);
    else
        delete cc;
    return c;
}

Certificate Certificate::fromPEM(const QString &s, ConvertResult *result, const QString &provider)
{
    Certificate   c;
    CertContext * cc = static_cast<CertContext *>(getContext(QStringLiteral("cert"), provider));
    ConvertResult r  = cc->fromPEM(s);
    if (result)
        *result = r;
    if (r == ConvertGood)
        c.change(cc);
    else
        delete cc;
    return c;
}

Certificate Certificate::fromPEMFile(const QString &fileName, ConvertResult *result, const QString &provider)
{
    QString pem;
    if (!stringFromFile(fileName, &pem)) {
        if (result)
            *result = ErrorFile;
        return Certificate();
    }
    return fromPEM(pem, result, provider);
}

// check for ip addresses in iPAddress, dNSName, then commonName
// for all else, check in dNSName, then commonName
bool Certificate::matchesHostName(const QString &host) const
{
    const QByteArray ipaddr = ipaddr_str2bin(host);
    if (!ipaddr.isEmpty()) // ip address
    {
        // check iPAddress, dNSName, commonName
        const CertificateInfoOrdered subjectInfo = subjectInfoOrdered();
        for (const CertificateInfoPair &p : subjectInfo) {
            const CertificateInfoType type = p.type();
            if (type == IPAddress || type == DNS || type == CommonName) {
                if (cert_match_ipaddress(p.value(), ipaddr))
                    return true;
            }
        }
    } else // domain
    {
        // lowercase
        QString name = host.toLower();

        // ACE
        name = QString::fromLatin1(QUrl::toAce(name));

        // don't allow wildcards in the comparison host
        if (name.contains(QLatin1Char('*')))
            return false;

        // strip out trailing dot
        if (name.length() > 0 && name[name.length() - 1] == QLatin1Char('.'))
            name.truncate(name.length() - 1);

        // make sure the name is not empty after our modifications
        if (name.isEmpty())
            return false;

        // check dNSName, commonName
        const CertificateInfoOrdered subjectInfo = subjectInfoOrdered();
        for (const CertificateInfoPair &p : subjectInfo) {
            const CertificateInfoType type = p.type();
            if (type == DNS || type == CommonName) {
                if (cert_match_domain(p.value(), name))
                    return true;
            }
        }
    }

    return false;
}

bool Certificate::operator==(const Certificate &otherCert) const
{
    if (isNull()) {
        if (otherCert.isNull())
            return true;
        else
            return false;
    } else if (otherCert.isNull())
        return false;

    const CertContext *other = static_cast<const CertContext *>(otherCert.context());
    return static_cast<const CertContext *>(context())->compare(other);
}

void Certificate::change(CertContext *c)
{
    Algorithm::change(c);
    d->update(static_cast<CertContext *>(context()));
}

Validity Certificate::chain_validate(const CertificateChain &     chain,
                                     const CertificateCollection &trusted,
                                     const QList<CRL> &           untrusted_crls,
                                     UsageMode                    u,
                                     ValidateFlags                vf) const
{
    QList<CertContext *> chain_list;
    QList<CertContext *> trusted_list;
    QList<CRLContext *>  crl_list;

    QList<Certificate> chain_certs   = chain;
    QList<Certificate> trusted_certs = trusted.certificates();
    QList<CRL>         crls          = trusted.crls() + untrusted_crls;

    for (int n = 0; n < chain_certs.count(); ++n) {
        CertContext *c = static_cast<CertContext *>(chain_certs[n].context());
        chain_list += c;
    }
    for (int n = 0; n < trusted_certs.count(); ++n) {
        CertContext *c = static_cast<CertContext *>(trusted_certs[n].context());
        trusted_list += c;
    }
    for (int n = 0; n < crls.count(); ++n) {
        CRLContext *c = static_cast<CRLContext *>(crls[n].context());
        crl_list += c;
    }

    return static_cast<const CertContext *>(context())->validate_chain(chain_list, trusted_list, crl_list, u, vf);
}

CertificateChain
Certificate::chain_complete(const CertificateChain &chain, const QList<Certificate> &issuers, Validity *result) const
{
    CertificateChain   out;
    QList<Certificate> pool = issuers + chain.mid(1);
    out += chain.first();
    if (result)
        *result = ValidityGood;
    while (!out.last().isSelfSigned()) {
        // try to get next in chain
        int at = -1;
        for (int n = 0; n < pool.count(); ++n) {
            // QString str = QString("[%1] issued by [%2] ? ").arg(out.last().commonName()).arg(pool[n].commonName());
            if (pool[n].isIssuerOf(out.last())) {
                // printf("%s  yes\n", qPrintable(str));
                at = n;
                break;
            }
            // printf("%s  no\n", qPrintable(str));
        }
        if (at == -1) {
            if (result)
                *result = ErrorInvalidCA;
            break;
        }

        // take it out of the pool
        Certificate next = pool.takeAt(at);

        // make sure it isn't in the chain already (avoid loops)
        if (out.contains(next))
            break;

        // append to the chain
        out += next;
    }
    return out;
}

//----------------------------------------------------------------------------
// CertificateRequest
//----------------------------------------------------------------------------
class CertificateRequest::Private : public QSharedData
{
public:
    CertificateInfo subjectInfoMap;

    void update(CSRContext *c)
    {
        if (c)
            subjectInfoMap = orderedToMap(c->props()->subject);
        else
            subjectInfoMap = CertificateInfo();
    }
};

CertificateRequest::CertificateRequest()
    : d(new Private)
{
}

CertificateRequest::CertificateRequest(const QString &fileName)
    : d(new Private)
{
    *this = fromPEMFile(fileName, nullptr, QString());
}

CertificateRequest::CertificateRequest(const CertificateOptions &opts, const PrivateKey &key, const QString &provider)
    : d(new Private)
{
    CSRContext *c = static_cast<CSRContext *>(getContext(QStringLiteral("csr"), provider));
    if (c->createRequest(opts, *(static_cast<const PKeyContext *>(key.context()))))
        change(c);
    else
        delete c;
}

CertificateRequest::CertificateRequest(const CertificateRequest &from)
    : Algorithm(from)
    , d(from.d)
{
}

CertificateRequest::~CertificateRequest()
{
}

CertificateRequest &CertificateRequest::operator=(const CertificateRequest &from)
{
    Algorithm::operator=(from);
    d                  = from.d;
    return *this;
}

bool CertificateRequest::isNull() const
{
    return (!context() ? true : false);
}

bool CertificateRequest::canUseFormat(CertificateRequestFormat f, const QString &provider)
{
    CSRContext *c  = static_cast<CSRContext *>(getContext(QStringLiteral("csr"), provider));
    bool        ok = c->canUseFormat(f);
    delete c;
    return ok;
}

CertificateRequestFormat CertificateRequest::format() const
{
    if (isNull())
        return PKCS10; // some default so we don't explode
    return static_cast<const CSRContext *>(context())->props()->format;
}

CertificateInfo CertificateRequest::subjectInfo() const
{
    return d->subjectInfoMap;
}

CertificateInfoOrdered CertificateRequest::subjectInfoOrdered() const
{
    return static_cast<const CSRContext *>(context())->props()->subject;
}

Constraints CertificateRequest::constraints() const
{
    return static_cast<const CSRContext *>(context())->props()->constraints;
}

QStringList CertificateRequest::policies() const
{
    return static_cast<const CSRContext *>(context())->props()->policies;
}

PublicKey CertificateRequest::subjectPublicKey() const
{
    PKeyContext *c = static_cast<const CSRContext *>(context())->subjectPublicKey();
    PublicKey    key;
    key.change(c);
    return key;
}

bool CertificateRequest::isCA() const
{
    return static_cast<const CSRContext *>(context())->props()->isCA;
}

int CertificateRequest::pathLimit() const
{
    return static_cast<const CSRContext *>(context())->props()->pathLimit;
}

QString CertificateRequest::challenge() const
{
    return static_cast<const CSRContext *>(context())->props()->challenge;
}

SignatureAlgorithm CertificateRequest::signatureAlgorithm() const
{
    return static_cast<const CSRContext *>(context())->props()->sigalgo;
}

bool CertificateRequest::operator==(const CertificateRequest &otherCsr) const
{
    if (isNull()) {
        if (otherCsr.isNull())
            return true;
        else
            return false;
    } else if (otherCsr.isNull())
        return false;

    const CSRContext *other = static_cast<const CSRContext *>(otherCsr.context());
    return static_cast<const CSRContext *>(context())->compare(other);
}

QByteArray CertificateRequest::toDER() const
{
    return static_cast<const CSRContext *>(context())->toDER();
}

QString CertificateRequest::toPEM() const
{
    return static_cast<const CSRContext *>(context())->toPEM();
}

bool CertificateRequest::toPEMFile(const QString &fileName) const
{
    return stringToFile(fileName, toPEM());
}

CertificateRequest CertificateRequest::fromDER(const QByteArray &a, ConvertResult *result, const QString &provider)
{
    CertificateRequest c;
    CSRContext *       csr = static_cast<CSRContext *>(getContext(QStringLiteral("csr"), provider));
    ConvertResult      r   = csr->fromDER(a);
    if (result)
        *result = r;
    if (r == ConvertGood)
        c.change(csr);
    else
        delete csr;
    return c;
}

CertificateRequest CertificateRequest::fromPEM(const QString &s, ConvertResult *result, const QString &provider)
{
    CertificateRequest c;
    CSRContext *       csr = static_cast<CSRContext *>(getContext(QStringLiteral("csr"), provider));
    ConvertResult      r   = csr->fromPEM(s);
    if (result)
        *result = r;
    if (r == ConvertGood)
        c.change(csr);
    else
        delete csr;
    return c;
}

CertificateRequest
CertificateRequest::fromPEMFile(const QString &fileName, ConvertResult *result, const QString &provider)
{
    QString pem;
    if (!stringFromFile(fileName, &pem)) {
        if (result)
            *result = ErrorFile;
        return CertificateRequest();
    }
    return fromPEM(pem, result, provider);
}

QString CertificateRequest::toString() const
{
    return static_cast<const CSRContext *>(context())->toSPKAC();
}

CertificateRequest CertificateRequest::fromString(const QString &s, ConvertResult *result, const QString &provider)
{
    CertificateRequest c;
    CSRContext *       csr = static_cast<CSRContext *>(getContext(QStringLiteral("csr"), provider));
    ConvertResult      r   = csr->fromSPKAC(s);
    if (result)
        *result = r;
    if (r == ConvertGood)
        c.change(csr);
    else
        delete csr;
    return c;
}

void CertificateRequest::change(CSRContext *c)
{
    Algorithm::change(c);
    d->update(static_cast<CSRContext *>(context()));
}

//----------------------------------------------------------------------------
// CRLEntry
//----------------------------------------------------------------------------
CRLEntry::CRLEntry()
{
    _reason = Unspecified;
}

CRLEntry::CRLEntry(const Certificate &c, Reason r)
{
    _serial = c.serialNumber();
    _time   = QDateTime::currentDateTime();
    _reason = r;
}

// TODO make serial const & when we break ABI
CRLEntry::CRLEntry(
    const BigInteger serial, // clazy:exclude=function-args-by-ref NOLINT(performance-unnecessary-value-param)
    const QDateTime &time,
    Reason           r)
{
    _serial = serial;
    _time   = time;
    _reason = r;
}

CRLEntry::CRLEntry(const CRLEntry &from)
    : _serial(from._serial)
    , _time(from._time)
    , _reason(from._reason)
{
}

CRLEntry::~CRLEntry()
{
}

CRLEntry &CRLEntry::operator=(const CRLEntry &from)
{
    _serial = from._serial;
    _time   = from._time;
    _reason = from._reason;
    return *this;
}

bool CRLEntry::isNull() const
{
    return (_time.isNull());
}

BigInteger CRLEntry::serialNumber() const
{
    return _serial;
}

QDateTime CRLEntry::time() const
{
    return _time;
}

CRLEntry::Reason CRLEntry::reason() const
{
    return _reason;
}

bool CRLEntry::operator==(const CRLEntry &otherEntry) const
{
    if (isNull()) {
        if (otherEntry.isNull())
            return true;
        else
            return false;
    } else if (otherEntry.isNull())
        return false;

    if ((_serial != otherEntry._serial) || (_time != otherEntry._time) || (_reason != otherEntry._reason)) {
        return false;
    }
    return true;
}

bool CRLEntry::operator<(const CRLEntry &otherEntry) const
{
    if (isNull() || otherEntry.isNull())
        return false;

    if (_serial < otherEntry._serial)
        return true;

    return false;
}

//----------------------------------------------------------------------------
// CRL
//----------------------------------------------------------------------------
class CRL::Private : public QSharedData
{
public:
    CertificateInfo issuerInfoMap;

    void update(CRLContext *c)
    {
        if (c)
            issuerInfoMap = orderedToMap(c->props()->issuer);
        else
            issuerInfoMap = CertificateInfo();
    }
};

CRL::CRL()
    : d(new Private)
{
}

CRL::CRL(const CRL &from)
    : Algorithm(from)
    , d(from.d)
{
}

CRL::~CRL()
{
}

CRL &CRL::operator=(const CRL &from)
{
    Algorithm::operator=(from);
    d                  = from.d;
    return *this;
}

bool CRL::isNull() const
{
    return (!context() ? true : false);
}

CertificateInfo CRL::issuerInfo() const
{
    return d->issuerInfoMap;
}

CertificateInfoOrdered CRL::issuerInfoOrdered() const
{
    return static_cast<const CRLContext *>(context())->props()->issuer;
}

int CRL::number() const
{
    return static_cast<const CRLContext *>(context())->props()->number;
}

QDateTime CRL::thisUpdate() const
{
    return static_cast<const CRLContext *>(context())->props()->thisUpdate;
}

QDateTime CRL::nextUpdate() const
{
    return static_cast<const CRLContext *>(context())->props()->nextUpdate;
}

QList<CRLEntry> CRL::revoked() const
{
    return static_cast<const CRLContext *>(context())->props()->revoked;
}

SignatureAlgorithm CRL::signatureAlgorithm() const
{
    return static_cast<const CRLContext *>(context())->props()->sigalgo;
}

QByteArray CRL::issuerKeyId() const
{
    return static_cast<const CRLContext *>(context())->props()->issuerId;
}

QByteArray CRL::toDER() const
{
    return static_cast<const CRLContext *>(context())->toDER();
}

QString CRL::toPEM() const
{
    return static_cast<const CRLContext *>(context())->toPEM();
}

bool CRL::operator==(const CRL &otherCrl) const
{
    if (isNull()) {
        if (otherCrl.isNull())
            return true;
        else
            return false;
    } else if (otherCrl.isNull())
        return false;

    const CRLContext *other = static_cast<const CRLContext *>(otherCrl.context());
    return static_cast<const CRLContext *>(context())->compare(other);
}

CRL CRL::fromDER(const QByteArray &a, ConvertResult *result, const QString &provider)
{
    CRL           c;
    CRLContext *  cc = static_cast<CRLContext *>(getContext(QStringLiteral("crl"), provider));
    ConvertResult r  = cc->fromDER(a);
    if (result)
        *result = r;
    if (r == ConvertGood)
        c.change(cc);
    else
        delete cc;
    return c;
}

CRL CRL::fromPEM(const QString &s, ConvertResult *result, const QString &provider)
{
    CRL           c;
    CRLContext *  cc = static_cast<CRLContext *>(getContext(QStringLiteral("crl"), provider));
    ConvertResult r  = cc->fromPEM(s);
    if (result)
        *result = r;
    if (r == ConvertGood)
        c.change(cc);
    else
        delete cc;
    return c;
}

CRL CRL::fromPEMFile(const QString &fileName, ConvertResult *result, const QString &provider)
{
    QString pem;
    if (!stringFromFile(fileName, &pem)) {
        if (result)
            *result = ErrorFile;
        return CRL();
    }
    return fromPEM(pem, result, provider);
}

bool CRL::toPEMFile(const QString &fileName) const
{
    return stringToFile(fileName, toPEM());
}

void CRL::change(CRLContext *c)
{
    Algorithm::change(c);
    d->update(static_cast<CRLContext *>(context()));
}

//----------------------------------------------------------------------------
// Store
//----------------------------------------------------------------------------
// CRL / X509 CRL
// CERTIFICATE / X509 CERTIFICATE
static QString readNextPem(QTextStream *ts, bool *isCRL)
{
    QString pem;
    bool    crl   = false;
    bool    found = false;
    bool    done  = false;
    while (!ts->atEnd()) {
        const QString line = ts->readLine();
        if (!found) {
            if (line.startsWith(QLatin1String("-----BEGIN "))) {
                if (line.contains(QLatin1String("CERTIFICATE"))) {
                    found = true;
                    pem += line + QLatin1Char('\n');
                    crl = false;
                } else if (line.contains(QLatin1String("CRL"))) {
                    found = true;
                    pem += line + QLatin1Char('\n');
                    crl = true;
                }
            }
        } else {
            pem += line + QLatin1Char('\n');
            if (line.startsWith(QLatin1String("-----END "))) {
                done = true;
                break;
            }
        }
    }
    if (!done)
        return QString();
    if (isCRL)
        *isCRL = crl;
    return pem;
}

class CertificateCollection::Private : public QSharedData
{
public:
    QList<Certificate> certs;
    QList<CRL>         crls;
};

CertificateCollection::CertificateCollection()
    : d(new Private)
{
}

CertificateCollection::CertificateCollection(const CertificateCollection &from)
    : d(from.d)
{
}

CertificateCollection::~CertificateCollection()
{
}

CertificateCollection &CertificateCollection::operator=(const CertificateCollection &from)
{
    d = from.d;
    return *this;
}

void CertificateCollection::addCertificate(const Certificate &cert)
{
    d->certs.append(cert);
}

void CertificateCollection::addCRL(const CRL &crl)
{
    d->crls.append(crl);
}

QList<Certificate> CertificateCollection::certificates() const
{
    return d->certs;
}

QList<CRL> CertificateCollection::crls() const
{
    return d->crls;
}

void CertificateCollection::append(const CertificateCollection &other)
{
    d->certs += other.d->certs;
    d->crls += other.d->crls;
}

CertificateCollection CertificateCollection::operator+(const CertificateCollection &other) const
{
    CertificateCollection c = *this;
    c.append(other);
    return c;
}

CertificateCollection &CertificateCollection::operator+=(const CertificateCollection &other)
{
    append(other);
    return *this;
}

bool CertificateCollection::canUsePKCS7(const QString &provider)
{
    return isSupported("certcollection", provider);
}

bool CertificateCollection::toFlatTextFile(const QString &fileName)
{
    QFile f(fileName);
    if (!f.open(QFile::WriteOnly))
        return false;

    QTextStream ts(&f);
    int         n;
    for (n = 0; n < d->certs.count(); ++n)
        ts << d->certs[n].toPEM();
    for (n = 0; n < d->crls.count(); ++n)
        ts << d->crls[n].toPEM();
    return true;
}

bool CertificateCollection::toPKCS7File(const QString &fileName, const QString &provider)
{
    CertCollectionContext *col =
        static_cast<CertCollectionContext *>(getContext(QStringLiteral("certcollection"), provider));

    QList<CertContext *> cert_list;
    QList<CRLContext *>  crl_list;
    int                  n;
    for (n = 0; n < d->certs.count(); ++n) {
        CertContext *c = static_cast<CertContext *>(d->certs[n].context());
        cert_list += c;
    }
    for (n = 0; n < d->crls.count(); ++n) {
        CRLContext *c = static_cast<CRLContext *>(d->crls[n].context());
        crl_list += c;
    }

    const QByteArray result = col->toPKCS7(cert_list, crl_list);
    delete col;

    return arrayToFile(fileName, result);
}

CertificateCollection
CertificateCollection::fromFlatTextFile(const QString &fileName, ConvertResult *result, const QString &provider)
{
    QFile f(fileName);
    if (!f.open(QFile::ReadOnly)) {
        if (result)
            *result = ErrorFile;
        return CertificateCollection();
    }

    CertificateCollection certs;
    QTextStream           ts(&f);
    while (true) {
        bool    isCRL = false;
        QString pem   = readNextPem(&ts, &isCRL);
        if (pem.isNull())
            break;
        if (isCRL) {
            CRL c = CRL::fromPEM(pem, nullptr, provider);
            if (!c.isNull())
                certs.addCRL(c);
        } else {
            Certificate c = Certificate::fromPEM(pem, nullptr, provider);
            if (!c.isNull())
                certs.addCertificate(c);
        }
    }

    if (result)
        *result = ConvertGood;

    return certs;
}

CertificateCollection
CertificateCollection::fromPKCS7File(const QString &fileName, ConvertResult *result, const QString &provider)
{
    QByteArray der;
    if (!arrayFromFile(fileName, &der)) {
        if (result)
            *result = ErrorFile;
        return CertificateCollection();
    }

    CertificateCollection certs;

    QList<CertContext *>   cert_list;
    QList<CRLContext *>    crl_list;
    CertCollectionContext *col =
        static_cast<CertCollectionContext *>(getContext(QStringLiteral("certcollection"), provider));
    ConvertResult r = col->fromPKCS7(der, &cert_list, &crl_list);
    delete col;

    if (result)
        *result = r;
    if (r == ConvertGood) {
        int n;
        for (n = 0; n < cert_list.count(); ++n) {
            Certificate c;
            c.change(cert_list[n]);
            certs.addCertificate(c);
        }
        for (n = 0; n < crl_list.count(); ++n) {
            CRL c;
            c.change(crl_list[n]);
            certs.addCRL(c);
        }
    }
    return certs;
}

//----------------------------------------------------------------------------
// CertificateAuthority
//----------------------------------------------------------------------------
CertificateAuthority::CertificateAuthority(const Certificate &cert, const PrivateKey &key, const QString &provider)
    : Algorithm(QStringLiteral("ca"), provider)
{
    static_cast<CAContext *>(context())->setup(*(static_cast<const CertContext *>(cert.context())),
                                               *(static_cast<const PKeyContext *>(key.context())));
}

CertificateAuthority::CertificateAuthority(const CertificateAuthority &from)
    : Algorithm(from)
{
}

CertificateAuthority::~CertificateAuthority()
{
}

CertificateAuthority &CertificateAuthority::operator=(const CertificateAuthority &from)
{
    Algorithm::operator=(from);
    return *this;
}

Certificate CertificateAuthority::certificate() const
{
    Certificate c;
    c.change(static_cast<const CAContext *>(context())->certificate());
    return c;
}

Certificate CertificateAuthority::signRequest(const CertificateRequest &req, const QDateTime &notValidAfter) const
{
    Certificate  c;
    CertContext *cc = static_cast<const CAContext *>(context())->signRequest(
        *(static_cast<const CSRContext *>(req.context())), notValidAfter);
    if (cc)
        c.change(cc);
    return c;
}

CRL CertificateAuthority::createCRL(const QDateTime &nextUpdate) const
{
    CRL         crl;
    CRLContext *cc = static_cast<const CAContext *>(context())->createCRL(nextUpdate);
    if (cc)
        crl.change(cc);
    return crl;
}

CRL CertificateAuthority::updateCRL(const CRL &crl, const QList<CRLEntry> &entries, const QDateTime &nextUpdate) const
{
    CRL         new_crl;
    CRLContext *cc = static_cast<const CAContext *>(context())->updateCRL(
        *(static_cast<const CRLContext *>(crl.context())), entries, nextUpdate);
    if (cc)
        new_crl.change(cc);
    return new_crl;
}

//----------------------------------------------------------------------------
// KeyBundle
//----------------------------------------------------------------------------
class KeyBundle::Private : public QSharedData
{
public:
    QString          name;
    CertificateChain chain;
    PrivateKey       key;
};

KeyBundle::KeyBundle()
    : d(new Private)
{
}

KeyBundle::KeyBundle(const QString &fileName, const SecureArray &passphrase)
    : d(new Private)
{
    *this = fromFile(fileName, passphrase, nullptr, QString());
}

KeyBundle::KeyBundle(const KeyBundle &from)
    : d(from.d)
{
}

KeyBundle::~KeyBundle()
{
}

KeyBundle &KeyBundle::operator=(const KeyBundle &from)
{
    d = from.d;
    return *this;
}

bool KeyBundle::isNull() const
{
    return d->chain.isEmpty();
}

QString KeyBundle::name() const
{
    return d->name;
}

CertificateChain KeyBundle::certificateChain() const
{
    return d->chain;
}

PrivateKey KeyBundle::privateKey() const
{
    return d->key;
}

void KeyBundle::setName(const QString &s)
{
    d->name = s;
}

void KeyBundle::setCertificateChainAndKey(const CertificateChain &c, const PrivateKey &key)
{
    d->chain = c;
    d->key   = key;
}

QByteArray KeyBundle::toArray(const SecureArray &passphrase, const QString &provider) const
{
    PKCS12Context *pix = static_cast<PKCS12Context *>(getContext(QStringLiteral("pkcs12"), provider));

    QList<const CertContext *> list;
    for (int n = 0; n < d->chain.count(); ++n)
        list.append(static_cast<const CertContext *>(d->chain[n].context()));
    const QByteArray buf =
        pix->toPKCS12(d->name, list, *(static_cast<const PKeyContext *>(d->key.context())), passphrase);
    delete pix;

    return buf;
}

bool KeyBundle::toFile(const QString &fileName, const SecureArray &passphrase, const QString &provider) const
{
    return arrayToFile(fileName, toArray(passphrase, provider));
}

KeyBundle
KeyBundle::fromArray(const QByteArray &a, const SecureArray &passphrase, ConvertResult *result, const QString &provider)
{
    KeyBundle bundle;
    get_pkcs12_der(
        a, QString(), (void *)&a, passphrase, result, provider, &bundle.d->name, &bundle.d->chain, &bundle.d->key);
    return bundle;
}

KeyBundle KeyBundle::fromFile(const QString &    fileName,
                              const SecureArray &passphrase,
                              ConvertResult *    result,
                              const QString &    provider)
{
    QByteArray der;
    if (!arrayFromFile(fileName, &der)) {
        if (result)
            *result = ErrorFile;
        return KeyBundle();
    }

    KeyBundle bundle;
    get_pkcs12_der(
        der, fileName, nullptr, passphrase, result, provider, &bundle.d->name, &bundle.d->chain, &bundle.d->key);
    return bundle;
}

//----------------------------------------------------------------------------
// PGPKey
//----------------------------------------------------------------------------
PGPKey::PGPKey()
{
}

PGPKey::PGPKey(const QString &fileName)
{
    *this = fromFile(fileName, nullptr, QString());
}

PGPKey::PGPKey(const PGPKey &from)
    : Algorithm(from)
{
}

PGPKey::~PGPKey()
{
}

PGPKey &PGPKey::operator=(const PGPKey &from)
{
    Algorithm::operator=(from);
    return *this;
}

bool PGPKey::isNull() const
{
    return (!context() ? true : false);
}

QString PGPKey::keyId() const
{
    return static_cast<const PGPKeyContext *>(context())->props()->keyId;
}

QString PGPKey::primaryUserId() const
{
    return static_cast<const PGPKeyContext *>(context())->props()->userIds.first();
}

QStringList PGPKey::userIds() const
{
    return static_cast<const PGPKeyContext *>(context())->props()->userIds;
}

bool PGPKey::isSecret() const
{
    return static_cast<const PGPKeyContext *>(context())->props()->isSecret;
}

QDateTime PGPKey::creationDate() const
{
    return static_cast<const PGPKeyContext *>(context())->props()->creationDate;
}

QDateTime PGPKey::expirationDate() const
{
    return static_cast<const PGPKeyContext *>(context())->props()->expirationDate;
}

QString PGPKey::fingerprint() const
{
    return static_cast<const PGPKeyContext *>(context())->props()->fingerprint;
}

bool PGPKey::inKeyring() const
{
    return static_cast<const PGPKeyContext *>(context())->props()->inKeyring;
}

bool PGPKey::isTrusted() const
{
    return static_cast<const PGPKeyContext *>(context())->props()->isTrusted;
}

QByteArray PGPKey::toArray() const
{
    return static_cast<const PGPKeyContext *>(context())->toBinary();
}

QString PGPKey::toString() const
{
    return static_cast<const PGPKeyContext *>(context())->toAscii();
}

bool PGPKey::toFile(const QString &fileName) const
{
    return stringToFile(fileName, toString());
}

PGPKey PGPKey::fromArray(const QByteArray &a, ConvertResult *result, const QString &provider)
{
    PGPKey         k;
    PGPKeyContext *kc = static_cast<PGPKeyContext *>(getContext(QStringLiteral("pgpkey"), provider));
    ConvertResult  r  = kc->fromBinary(a);
    if (result)
        *result = r;
    if (r == ConvertGood)
        k.change(kc);
    else
        delete kc;
    return k;
}

PGPKey PGPKey::fromString(const QString &s, ConvertResult *result, const QString &provider)
{
    PGPKey         k;
    PGPKeyContext *kc = static_cast<PGPKeyContext *>(getContext(QStringLiteral("pgpkey"), provider));
    ConvertResult  r  = kc->fromAscii(s);
    if (result)
        *result = r;
    if (r == ConvertGood)
        k.change(kc);
    else
        delete kc;
    return k;
}

PGPKey PGPKey::fromFile(const QString &fileName, ConvertResult *result, const QString &provider)
{
    QString str;
    if (!stringFromFile(fileName, &str)) {
        if (result)
            *result = ErrorFile;
        return PGPKey();
    }
    return fromString(str, result, provider);
}

//----------------------------------------------------------------------------
// KeyLoader
//----------------------------------------------------------------------------
class KeyLoaderThread : public QThread
{
    Q_OBJECT
public:
    enum Type
    {
        PKPEMFile,
        PKPEM,
        PKDER,
        KBDERFile,
        KBDER
    };

    class In
    {
    public:
        Type        type;
        QString     fileName, pem;
        SecureArray der;
        QByteArray  kbder;
    };

    class Out
    {
    public:
        ConvertResult convertResult;
        PrivateKey    privateKey;
        KeyBundle     keyBundle;
    };

    In  in;
    Out out;

    KeyLoaderThread(QObject *parent = nullptr)
        : QThread(parent)
    {
    }

protected:
    void run() override
    {
        if (in.type == PKPEMFile)
            out.privateKey = PrivateKey::fromPEMFile(in.fileName, SecureArray(), &out.convertResult);
        else if (in.type == PKPEM)
            out.privateKey = PrivateKey::fromPEM(in.pem, SecureArray(), &out.convertResult);
        else if (in.type == PKDER)
            out.privateKey = PrivateKey::fromDER(in.der, SecureArray(), &out.convertResult);
        else if (in.type == KBDERFile)
            out.keyBundle = KeyBundle::fromFile(in.fileName, SecureArray(), &out.convertResult);
        else if (in.type == KBDER)
            out.keyBundle = KeyBundle::fromArray(in.kbder, SecureArray(), &out.convertResult);
    }
};

class KeyLoader::Private : public QObject
{
    Q_OBJECT
public:
    KeyLoader *q;

    bool                 active;
    KeyLoaderThread *    thread;
    KeyLoaderThread::In  in;
    KeyLoaderThread::Out out;

    Private(KeyLoader *_q)
        : QObject(_q)
        , q(_q)
    {
        active = false;
    }

    void reset()
    {
        in  = KeyLoaderThread::In();
        out = KeyLoaderThread::Out();
    }

    void start()
    {
        active = true;
        thread = new KeyLoaderThread(this);
        // used queued for signal-safety
        connect(thread, &KeyLoaderThread::finished, this, &KeyLoader::Private::thread_finished, Qt::QueuedConnection);
        thread->in = in;
        thread->start();
    }

private Q_SLOTS:
    void thread_finished()
    {
        out = thread->out;
        delete thread;
        thread = nullptr;
        active = false;

        emit q->finished();
    }
};

KeyLoader::KeyLoader(QObject *parent)
    : QObject(parent)
{
    d = new Private(this);
}

KeyLoader::~KeyLoader()
{
    delete d;
}

void KeyLoader::loadPrivateKeyFromPEMFile(const QString &fileName)
{
    Q_ASSERT(!d->active);
    if (d->active)
        return;

    d->reset();
    d->in.type     = KeyLoaderThread::PKPEMFile;
    d->in.fileName = fileName;
    d->start();
}

void KeyLoader::loadPrivateKeyFromPEM(const QString &s)
{
    Q_ASSERT(!d->active);
    if (d->active)
        return;

    d->reset();
    d->in.type = KeyLoaderThread::PKPEM;
    d->in.pem  = s;
    d->start();
}

void KeyLoader::loadPrivateKeyFromDER(const SecureArray &a)
{
    Q_ASSERT(!d->active);
    if (d->active)
        return;

    d->reset();
    d->in.type = KeyLoaderThread::PKDER;
    d->in.der  = a;
    d->start();
}

void KeyLoader::loadKeyBundleFromFile(const QString &fileName)
{
    Q_ASSERT(!d->active);
    if (d->active)
        return;

    d->reset();
    d->in.type     = KeyLoaderThread::KBDERFile;
    d->in.fileName = fileName;
    d->start();
}

void KeyLoader::loadKeyBundleFromArray(const QByteArray &a)
{
    Q_ASSERT(!d->active);
    if (d->active)
        return;

    d->reset();
    d->in.type  = KeyLoaderThread::KBDERFile;
    d->in.kbder = a;
    d->start();
}

ConvertResult KeyLoader::convertResult() const
{
    return d->out.convertResult;
}

PrivateKey KeyLoader::privateKey() const
{
    return d->out.privateKey;
}

KeyBundle KeyLoader::keyBundle() const
{
    return d->out.keyBundle;
}

}

#include "qca_cert.moc"
