mirror of
https://github.com/QuasarApp/qca.git
synced 2025-04-29 04:54:31 +00:00
2976 lines
67 KiB
C++
2976 lines
67 KiB
C++
/*
|
|
* 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 <QTextStream>
|
|
#include <QFile>
|
|
#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("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
|
|
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)
|
|
{
|
|
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
|
|
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(out);
|
|
}
|
|
|
|
static int idToKnown(const QString &id)
|
|
{
|
|
if(id == CommonName_id)
|
|
return CommonName;
|
|
else if(id == Email_id)
|
|
return Email;
|
|
else if(id == EmailLegacy_id)
|
|
return EmailLegacy;
|
|
else if(id == Organization_id)
|
|
return Organization;
|
|
else if(id == OrganizationalUnit_id)
|
|
return OrganizationalUnit;
|
|
else if(id == Locality_id)
|
|
return Locality;
|
|
else if(id == IncorporationLocality_id)
|
|
return IncorporationLocality;
|
|
else if(id == State_id)
|
|
return State;
|
|
else if(id == IncorporationState_id)
|
|
return IncorporationState;
|
|
else if(id == Country_id)
|
|
return Country;
|
|
else if(id == IncorporationCountry_id)
|
|
return IncorporationCountry;
|
|
else if(id == URI_id)
|
|
return URI;
|
|
else if(id == DNS_id)
|
|
return DNS;
|
|
else if(id == IPAddress_id)
|
|
return IPAddress;
|
|
else if(id == 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(out);
|
|
}
|
|
|
|
static int constraintIdToKnown(const QString &id)
|
|
{
|
|
if(id == DigitalSignature_id)
|
|
return DigitalSignature;
|
|
else if(id == NonRepudiation_id)
|
|
return NonRepudiation;
|
|
else if(id == KeyEncipherment_id)
|
|
return KeyEncipherment;
|
|
else if(id == DataEncipherment_id)
|
|
return DataEncipherment;
|
|
else if(id == KeyAgreement_id)
|
|
return KeyAgreement;
|
|
else if(id == KeyCertificateSign_id)
|
|
return KeyCertificateSign;
|
|
else if(id == CRLSign_id)
|
|
return CRLSign;
|
|
else if(id == EncipherOnly_id)
|
|
return EncipherOnly;
|
|
else if(id == DecipherOnly_id)
|
|
return DecipherOnly;
|
|
else if(id == ServerAuth_id)
|
|
return ServerAuth;
|
|
else if(id == ClientAuth_id)
|
|
return ClientAuth;
|
|
else if(id == CodeSigning_id)
|
|
return CodeSigning;
|
|
else if(id == EmailProtection_id)
|
|
return EmailProtection;
|
|
else if(id == IPSecEndSystem_id)
|
|
return IPSecEndSystem;
|
|
else if(id == IPSecTunnel_id)
|
|
return IPSecTunnel;
|
|
else if(id == IPSecUser_id)
|
|
return IPSecUser;
|
|
else if(id == TimeStamping_id)
|
|
return TimeStamping;
|
|
else if(id == 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 str;
|
|
|
|
QString id = type.id();
|
|
// is it an oid?
|
|
if(id[0].isDigit())
|
|
return QString("OID.") + id;
|
|
|
|
return QString("qca.") + id;
|
|
}
|
|
|
|
QString orderedToDNString(const CertificateInfoOrdered &in)
|
|
{
|
|
QStringList parts;
|
|
foreach(const CertificateInfoPair &i, in)
|
|
{
|
|
if(i.type().section() != CertificateInfoType::DN)
|
|
continue;
|
|
|
|
QString name = dnLabel(i.type());
|
|
parts += name + '=' + i.value();
|
|
}
|
|
return parts.join(", ");
|
|
}
|
|
|
|
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 = "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;
|
|
|
|
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)
|
|
{
|
|
QString val = baseCertName(certs[items[i]].issuerInfo());
|
|
|
|
bool found = false;
|
|
foreach(int n, items)
|
|
{
|
|
if(n == items[i])
|
|
continue;
|
|
|
|
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(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]] + QString(" of ") + str;
|
|
goto end;
|
|
}
|
|
|
|
// different organizational unit?
|
|
str = uniqueSubjectValue(OrganizationalUnit, items, certs, i);
|
|
if(!str.isEmpty())
|
|
{
|
|
name = list[items[i]] + QString(" of ") + str;
|
|
goto end;
|
|
}
|
|
|
|
// different email address?
|
|
str = uniqueSubjectValue(Email, items, certs, i);
|
|
if(!str.isEmpty())
|
|
{
|
|
name = list[items[i]] + QString(" <") + str + '>';
|
|
goto end;
|
|
}
|
|
|
|
// different xmpp addresses?
|
|
str = uniqueSubjectValue(XMPP, items, certs, i);
|
|
if(!str.isEmpty())
|
|
{
|
|
name = list[items[i]] + QString(" <xmpp:") + str + '>';
|
|
goto end;
|
|
}
|
|
|
|
// different issuers?
|
|
str = uniqueIssuerName(items, certs, i);
|
|
if(!str.isEmpty())
|
|
{
|
|
name = list[items[i]] + QString(" by ") + str;
|
|
goto end;
|
|
}
|
|
|
|
// different usages?
|
|
|
|
// DigitalSignature
|
|
str = uniqueConstraintValue(DigitalSignature, items, certs, i);
|
|
if(!str.isEmpty())
|
|
{
|
|
name = list[items[i]] + QString(" for ") + str;
|
|
goto end;
|
|
}
|
|
|
|
// ClientAuth
|
|
str = uniqueConstraintValue(ClientAuth, items, certs, i);
|
|
if(!str.isEmpty())
|
|
{
|
|
name = list[items[i]] + QString(" for ") + str;
|
|
goto end;
|
|
}
|
|
|
|
// EmailProtection
|
|
str = uniqueConstraintValue(EmailProtection, items, certs, i);
|
|
if(!str.isEmpty())
|
|
{
|
|
name = list[items[i]] + QString(" for ") + str;
|
|
goto end;
|
|
}
|
|
|
|
// DataEncipherment
|
|
str = uniqueConstraintValue(DataEncipherment, items, certs, i);
|
|
if(!str.isEmpty())
|
|
{
|
|
name = list[items[i]] + QString(" for ") + str;
|
|
goto end;
|
|
}
|
|
|
|
// EncipherOnly
|
|
str = uniqueConstraintValue(EncipherOnly, items, certs, i);
|
|
if(!str.isEmpty())
|
|
{
|
|
name = list[items[i]] + QString(" for ") + str;
|
|
goto end;
|
|
}
|
|
|
|
// DecipherOnly
|
|
str = uniqueConstraintValue(DecipherOnly, items, certs, i);
|
|
if(!str.isEmpty())
|
|
{
|
|
name = list[items[i]] + QString(" 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?
|
|
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(':'))
|
|
{
|
|
QStringList parts = str.split(':', QString::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('.') == -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();
|
|
|
|
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('.'))
|
|
{
|
|
QStringList parts = str.split('.', QString::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] == '.')
|
|
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(QRegExp("[^a-z0-9\\.\\*\\-]").indexIn(name) >= 0)
|
|
return false;
|
|
|
|
// hack into parts, and require at least 1 part
|
|
QStringList parts_name = name.split('.', QString::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('*'))
|
|
return false;
|
|
if(parts_name.count() >= 2 && parts_name[parts_name.count()-2].contains('*'))
|
|
return false;
|
|
|
|
QStringList parts_compare = acedomain.split('.', QString::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] == '[' && name[name.length()-1] == ']')
|
|
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
|
|
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("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
|
|
{
|
|
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("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("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
|
|
{
|
|
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('*'))
|
|
return false;
|
|
|
|
// strip out trailing dot
|
|
if(name.length() > 0 && name[name.length()-1] == '.')
|
|
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("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("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("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("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("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;
|
|
}
|
|
|
|
CRLEntry::CRLEntry(const BigInteger serial, const QDateTime &time, Reason r) // clazy:exclude=function-args-by-ref NOLINT(performance-unnecessary-value-param) TODO make serial const & when we break ABI
|
|
{
|
|
_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("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("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())
|
|
{
|
|
QString line = ts->readLine();
|
|
if(!found)
|
|
{
|
|
if(line.startsWith("-----BEGIN "))
|
|
{
|
|
if(line.contains("CERTIFICATE"))
|
|
{
|
|
found = true;
|
|
pem += line + '\n';
|
|
crl = false;
|
|
}
|
|
else if(line.contains("CRL"))
|
|
{
|
|
found = true;
|
|
pem += line + '\n';
|
|
crl = true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pem += line + '\n';
|
|
if(line.startsWith("-----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("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;
|
|
}
|
|
|
|
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("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("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 ¬ValidAfter) 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("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()));
|
|
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("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("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"
|