/* * Copyright (C) 2003-2007 Justin Karneges * Copyright (C) 2004-2006 Brad Hards * * 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 #include #include #include #include #include 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 list; PKeyContext *kc = nullptr; PKCS12Context *pix = static_cast(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 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 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 typesLeft = in.keys(); // dedup QList 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 findSameName(const QString &name, const QStringList &list) { QList 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 &items, const QList &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 &items, const QList &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 &items, const QList &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 &items, const QStringList &list, const QList &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(" '); 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 &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> itemCollisions; foreach (const QString &name, names) { // anyone else using this name? const QList items = findSameName(name, names); if (items.count() > 1) { // don't save duplicate collisions bool haveAlready = false; foreach (const QList &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 &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(getContext(QStringLiteral("cert"), provider)); if (c->createSelfSigned(opts, *(static_cast(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(context())->props()->start; } QDateTime Certificate::notValidAfter() const { return static_cast(context())->props()->end; } CertificateInfo Certificate::subjectInfo() const { return d->subjectInfoMap; } CertificateInfoOrdered Certificate::subjectInfoOrdered() const { return static_cast(context())->props()->subject; } CertificateInfo Certificate::issuerInfo() const { return d->issuerInfoMap; } CertificateInfoOrdered Certificate::issuerInfoOrdered() const { return static_cast(context())->props()->issuer; } Constraints Certificate::constraints() const { return static_cast(context())->props()->constraints; } QStringList Certificate::policies() const { return static_cast(context())->props()->policies; } QStringList Certificate::crlLocations() const { return static_cast(context())->props()->crlLocations; } QStringList Certificate::issuerLocations() const { return static_cast(context())->props()->issuerLocations; } QStringList Certificate::ocspLocations() const { return static_cast(context())->props()->ocspLocations; } QString Certificate::commonName() const { return d->subjectInfoMap.value(CommonName); } BigInteger Certificate::serialNumber() const { return static_cast(context())->props()->serial; } PublicKey Certificate::subjectPublicKey() const { PKeyContext *c = static_cast(context())->subjectPublicKey(); PublicKey key; key.change(c); return key; } bool Certificate::isCA() const { return static_cast(context())->props()->isCA; } bool Certificate::isSelfSigned() const { return static_cast(context())->props()->isSelfSigned; } bool Certificate::isIssuerOf(const Certificate &other) const { const CertContext *cc = static_cast(other.context()); return static_cast(context())->isIssuerOf(cc); } int Certificate::pathLimit() const { return static_cast(context())->props()->pathLimit; } SignatureAlgorithm Certificate::signatureAlgorithm() const { return static_cast(context())->props()->sigalgo; } QByteArray Certificate::subjectKeyId() const { return static_cast(context())->props()->subjectId; } QByteArray Certificate::issuerKeyId() const { return static_cast(context())->props()->issuerId; } Validity Certificate::validate(const CertificateCollection &trusted, const CertificateCollection &untrusted, UsageMode u, ValidateFlags vf) const { const QList 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(context())->toDER(); } QString Certificate::toPEM() const { return static_cast(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(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(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(otherCert.context()); return static_cast(context())->compare(other); } void Certificate::change(CertContext *c) { Algorithm::change(c); d->update(static_cast(context())); } Validity Certificate::chain_validate(const CertificateChain &chain, const CertificateCollection &trusted, const QList &untrusted_crls, UsageMode u, ValidateFlags vf) const { QList chain_list; QList trusted_list; QList crl_list; QList chain_certs = chain; QList trusted_certs = trusted.certificates(); QList crls = trusted.crls() + untrusted_crls; for (int n = 0; n < chain_certs.count(); ++n) { CertContext *c = static_cast(chain_certs[n].context()); chain_list += c; } for (int n = 0; n < trusted_certs.count(); ++n) { CertContext *c = static_cast(trusted_certs[n].context()); trusted_list += c; } for (int n = 0; n < crls.count(); ++n) { CRLContext *c = static_cast(crls[n].context()); crl_list += c; } return static_cast(context())->validate_chain(chain_list, trusted_list, crl_list, u, vf); } CertificateChain Certificate::chain_complete(const CertificateChain &chain, const QList &issuers, Validity *result) const { CertificateChain out; QList 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(getContext(QStringLiteral("csr"), provider)); if (c->createRequest(opts, *(static_cast(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(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(context())->props()->format; } CertificateInfo CertificateRequest::subjectInfo() const { return d->subjectInfoMap; } CertificateInfoOrdered CertificateRequest::subjectInfoOrdered() const { return static_cast(context())->props()->subject; } Constraints CertificateRequest::constraints() const { return static_cast(context())->props()->constraints; } QStringList CertificateRequest::policies() const { return static_cast(context())->props()->policies; } PublicKey CertificateRequest::subjectPublicKey() const { PKeyContext *c = static_cast(context())->subjectPublicKey(); PublicKey key; key.change(c); return key; } bool CertificateRequest::isCA() const { return static_cast(context())->props()->isCA; } int CertificateRequest::pathLimit() const { return static_cast(context())->props()->pathLimit; } QString CertificateRequest::challenge() const { return static_cast(context())->props()->challenge; } SignatureAlgorithm CertificateRequest::signatureAlgorithm() const { return static_cast(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(otherCsr.context()); return static_cast(context())->compare(other); } QByteArray CertificateRequest::toDER() const { return static_cast(context())->toDER(); } QString CertificateRequest::toPEM() const { return static_cast(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(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(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(context())->toSPKAC(); } CertificateRequest CertificateRequest::fromString(const QString &s, ConvertResult *result, const QString &provider) { CertificateRequest c; CSRContext *csr = static_cast(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(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(context())->props()->issuer; } int CRL::number() const { return static_cast(context())->props()->number; } QDateTime CRL::thisUpdate() const { return static_cast(context())->props()->thisUpdate; } QDateTime CRL::nextUpdate() const { return static_cast(context())->props()->nextUpdate; } QList CRL::revoked() const { return static_cast(context())->props()->revoked; } SignatureAlgorithm CRL::signatureAlgorithm() const { return static_cast(context())->props()->sigalgo; } QByteArray CRL::issuerKeyId() const { return static_cast(context())->props()->issuerId; } QByteArray CRL::toDER() const { return static_cast(context())->toDER(); } QString CRL::toPEM() const { return static_cast(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(otherCrl.context()); return static_cast(context())->compare(other); } CRL CRL::fromDER(const QByteArray &a, ConvertResult *result, const QString &provider) { CRL c; CRLContext *cc = static_cast(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(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(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 certs; QList 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 CertificateCollection::certificates() const { return d->certs; } QList 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(getContext(QStringLiteral("certcollection"), provider)); QList cert_list; QList crl_list; int n; for (n = 0; n < d->certs.count(); ++n) { CertContext *c = static_cast(d->certs[n].context()); cert_list += c; } for (n = 0; n < d->crls.count(); ++n) { CRLContext *c = static_cast(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 cert_list; QList crl_list; CertCollectionContext *col = static_cast(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(context())->setup(*(static_cast(cert.context())), *(static_cast(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(context())->certificate()); return c; } Certificate CertificateAuthority::signRequest(const CertificateRequest &req, const QDateTime ¬ValidAfter) const { Certificate c; CertContext *cc = static_cast(context())->signRequest( *(static_cast(req.context())), notValidAfter); if (cc) c.change(cc); return c; } CRL CertificateAuthority::createCRL(const QDateTime &nextUpdate) const { CRL crl; CRLContext *cc = static_cast(context())->createCRL(nextUpdate); if (cc) crl.change(cc); return crl; } CRL CertificateAuthority::updateCRL(const CRL &crl, const QList &entries, const QDateTime &nextUpdate) const { CRL new_crl; CRLContext *cc = static_cast(context())->updateCRL( *(static_cast(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(getContext(QStringLiteral("pkcs12"), provider)); QList list; for (int n = 0; n < d->chain.count(); ++n) list.append(static_cast(d->chain[n].context())); const QByteArray buf = pix->toPKCS12(d->name, list, *(static_cast(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(context())->props()->keyId; } QString PGPKey::primaryUserId() const { return static_cast(context())->props()->userIds.first(); } QStringList PGPKey::userIds() const { return static_cast(context())->props()->userIds; } bool PGPKey::isSecret() const { return static_cast(context())->props()->isSecret; } QDateTime PGPKey::creationDate() const { return static_cast(context())->props()->creationDate; } QDateTime PGPKey::expirationDate() const { return static_cast(context())->props()->expirationDate; } QString PGPKey::fingerprint() const { return static_cast(context())->props()->fingerprint; } bool PGPKey::inKeyring() const { return static_cast(context())->props()->inKeyring; } bool PGPKey::isTrusted() const { return static_cast(context())->props()->isTrusted; } QByteArray PGPKey::toArray() const { return static_cast(context())->toBinary(); } QString PGPKey::toString() const { return static_cast(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(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(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"