File indexing completed on 2024-05-05 03:55:42

0001 /*
0002     This file is part of the KDE project
0003     SPDX-FileCopyrightText: 2007, 2008, 2010 Andreas Hartmetz <ahartmetz@gmail.com>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "ksslcertificatemanager.h"
0009 #include "ksslcertificatemanager_p.h"
0010 
0011 #include "kssld_interface.h"
0012 #include "ksslerroruidata_p.h"
0013 
0014 #include <KConfig>
0015 #include <KConfigGroup>
0016 #include <KLocalizedString>
0017 
0018 #include <QDBusConnection>
0019 #include <QDBusConnectionInterface>
0020 #include <QDebug>
0021 #include <QDir>
0022 #include <QFile>
0023 #include <QSslConfiguration>
0024 #include <QStandardPaths>
0025 
0026 #include <set>
0027 
0028 /*
0029   Config file format:
0030 [<MD5-Digest>]
0031 <Host> = <Date> <List of ignored errors>
0032 #for example
0033 #mail.kdab.net =  ExpireUTC 2008-08-20T18:22:14, SelfSigned, Expired
0034 #very.old.com =  ExpireUTC 2008-08-20T18:22:14, TooWeakEncryption <- not actually planned to implement
0035 #clueless.admin.com =  ExpireUTC 2008-08-20T18:22:14, HostNameMismatch
0036 #
0037 #Wildcard syntax
0038 #* = ExpireUTC 2008-08-20T18:22:14, SelfSigned
0039 #*.kdab.net = ExpireUTC 2008-08-20T18:22:14, SelfSigned
0040 #mail.kdab.net = ExpireUTC 2008-08-20T18:22:14, All <- not implemented
0041 #* = ExpireUTC 9999-12-31T23:59:59, Reject  #we know that something is wrong with that certificate
0042 CertificatePEM = <PEM-encoded certificate> #host entries are all lowercase, thus no clashes
0043 
0044  */
0045 
0046 // TODO GUI for managing exception rules
0047 
0048 KSslCertificateRule::KSslCertificateRule(const QSslCertificate &cert, const QString &hostName)
0049     : d(new KSslCertificateRulePrivate())
0050 {
0051     d->certificate = cert;
0052     d->hostName = hostName;
0053     d->isRejected = false;
0054 }
0055 
0056 KSslCertificateRule::KSslCertificateRule(const KSslCertificateRule &other)
0057     : d(new KSslCertificateRulePrivate())
0058 {
0059     *d = *other.d;
0060 }
0061 
0062 KSslCertificateRule::~KSslCertificateRule() = default;
0063 
0064 KSslCertificateRule &KSslCertificateRule::operator=(const KSslCertificateRule &other)
0065 {
0066     *d = *other.d;
0067     return *this;
0068 }
0069 
0070 QSslCertificate KSslCertificateRule::certificate() const
0071 {
0072     return d->certificate;
0073 }
0074 
0075 QString KSslCertificateRule::hostName() const
0076 {
0077     return d->hostName;
0078 }
0079 
0080 void KSslCertificateRule::setExpiryDateTime(const QDateTime &dateTime)
0081 {
0082     d->expiryDateTime = dateTime;
0083 }
0084 
0085 QDateTime KSslCertificateRule::expiryDateTime() const
0086 {
0087     return d->expiryDateTime;
0088 }
0089 
0090 void KSslCertificateRule::setRejected(bool rejected)
0091 {
0092     d->isRejected = rejected;
0093 }
0094 
0095 bool KSslCertificateRule::isRejected() const
0096 {
0097     return d->isRejected;
0098 }
0099 
0100 bool KSslCertificateRule::isErrorIgnored(QSslError::SslError error) const
0101 {
0102     return d->ignoredErrors.contains(error);
0103 }
0104 
0105 void KSslCertificateRule::setIgnoredErrors(const QList<QSslError> &errors)
0106 {
0107     d->ignoredErrors.clear();
0108     for (const QSslError &error : errors) {
0109         if (!isErrorIgnored(error.error())) {
0110             d->ignoredErrors.append(error.error());
0111         }
0112     }
0113 }
0114 
0115 void KSslCertificateRule::setIgnoredErrors(const QList<QSslError::SslError> &errors)
0116 {
0117     d->ignoredErrors.clear();
0118     for (QSslError::SslError error : errors) {
0119         if (!isErrorIgnored(error)) {
0120             d->ignoredErrors.append(error);
0121         }
0122     }
0123 }
0124 
0125 QList<QSslError::SslError> KSslCertificateRule::ignoredErrors() const
0126 {
0127     return d->ignoredErrors;
0128 }
0129 
0130 QList<QSslError> KSslCertificateRule::filterErrors(const QList<QSslError> &errors) const
0131 {
0132     QList<QSslError> ret;
0133     for (const QSslError &error : errors) {
0134         if (!isErrorIgnored(error.error())) {
0135             ret.append(error);
0136         }
0137     }
0138     return ret;
0139 }
0140 
0141 ////////////////////////////////////////////////////////////////////
0142 
0143 static QList<QSslCertificate> deduplicate(const QList<QSslCertificate> &certs)
0144 {
0145     std::set<QByteArray> digests;
0146     QList<QSslCertificate> ret;
0147     for (const QSslCertificate &cert : certs) {
0148         QByteArray digest = cert.digest();
0149         const auto [it, isInserted] = digests.insert(digest);
0150         if (isInserted) {
0151             ret.append(cert);
0152         }
0153     }
0154     return ret;
0155 }
0156 
0157 KSslCertificateManagerPrivate::KSslCertificateManagerPrivate()
0158     : config(QStringLiteral("ksslcertificatemanager"), KConfig::SimpleConfig)
0159     , iface(new org::kde::KSSLDInterface(QStringLiteral("org.kde.kssld6"), QStringLiteral("/modules/kssld"), QDBusConnection::sessionBus()))
0160     , isCertListLoaded(false)
0161     , userCertDir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/kssl/userCaCertificates/"))
0162 {
0163 }
0164 
0165 KSslCertificateManagerPrivate::~KSslCertificateManagerPrivate()
0166 {
0167     delete iface;
0168     iface = nullptr;
0169 }
0170 
0171 void KSslCertificateManagerPrivate::loadDefaultCaCertificates()
0172 {
0173     defaultCaCertificates.clear();
0174 
0175     QList<QSslCertificate> certs = deduplicate(QSslConfiguration::systemCaCertificates());
0176 
0177     KConfig config(QStringLiteral("ksslcablacklist"), KConfig::SimpleConfig);
0178     KConfigGroup group = config.group(QStringLiteral("Blacklist of CA Certificates"));
0179 
0180     certs.append(QSslCertificate::fromPath(userCertDir + QLatin1Char('*'), QSsl::Pem, QSslCertificate::PatternSyntax::Wildcard));
0181 
0182     for (const QSslCertificate &cert : std::as_const(certs)) {
0183         const QByteArray digest = cert.digest().toHex();
0184         if (!group.hasKey(digest.constData())) {
0185             defaultCaCertificates += cert;
0186         }
0187     }
0188 
0189     isCertListLoaded = true;
0190 }
0191 
0192 bool KSslCertificateManagerPrivate::addCertificate(const KSslCaCertificate &in)
0193 {
0194     // qDebug() << Q_FUNC_INFO;
0195     // cannot add a certificate to the system store
0196     if (in.store == KSslCaCertificate::SystemStore) {
0197         Q_ASSERT(false);
0198         return false;
0199     }
0200     if (knownCerts.contains(in.certHash)) {
0201         Q_ASSERT(false);
0202         return false;
0203     }
0204 
0205     QString certFilename = userCertDir + QString::fromLatin1(in.certHash);
0206 
0207     QFile certFile(certFilename);
0208     if (!QDir().mkpath(userCertDir) || certFile.open(QIODevice::ReadOnly)) {
0209         return false;
0210     }
0211     if (!certFile.open(QIODevice::WriteOnly)) {
0212         return false;
0213     }
0214     if (certFile.write(in.cert.toPem()) < 1) {
0215         return false;
0216     }
0217     knownCerts.insert(in.certHash);
0218 
0219     updateCertificateBlacklisted(in);
0220 
0221     return true;
0222 }
0223 
0224 bool KSslCertificateManagerPrivate::removeCertificate(const KSslCaCertificate &old)
0225 {
0226     // qDebug() << Q_FUNC_INFO;
0227     // cannot remove a certificate from the system store
0228     if (old.store == KSslCaCertificate::SystemStore) {
0229         Q_ASSERT(false);
0230         return false;
0231     }
0232 
0233     if (!QFile::remove(userCertDir + QString::fromLatin1(old.certHash))) {
0234         // suppose somebody copied a certificate file into userCertDir without changing the
0235         // filename to the digest.
0236         // the rest of the code will work fine because it loads all certificate files from
0237         // userCertDir without asking for the name, we just can't remove the certificate using
0238         // its digest as filename - so search the whole directory.
0239         // if the certificate was added with the digest as name *and* with a different name, we
0240         // still fail to remove it completely at first try - BAD USER! BAD!
0241 
0242         bool removed = false;
0243         QDir dir(userCertDir);
0244         const QStringList dirList = dir.entryList(QDir::Files);
0245         for (const QString &certFilename : dirList) {
0246             const QString certPath = userCertDir + certFilename;
0247             QList<QSslCertificate> certs = QSslCertificate::fromPath(certPath);
0248 
0249             if (!certs.isEmpty() && certs.at(0).digest().toHex() == old.certHash) {
0250                 if (QFile::remove(certPath)) {
0251                     removed = true;
0252                 } else {
0253                     // maybe the file is readable but not writable
0254                     return false;
0255                 }
0256             }
0257         }
0258         if (!removed) {
0259             // looks like the file is not there
0260             return false;
0261         }
0262     }
0263 
0264     // note that knownCerts *should* need no updating due to the way setAllCertificates() works -
0265     // it should never call addCertificate and removeCertificate for the same cert in one run
0266 
0267     // clean up the blacklist
0268     setCertificateBlacklisted(old.certHash, false);
0269 
0270     return true;
0271 }
0272 
0273 static bool certLessThan(const KSslCaCertificate &cacert1, const KSslCaCertificate &cacert2)
0274 {
0275     if (cacert1.store != cacert2.store) {
0276         // SystemStore is numerically smaller so the system certs come first; this is important
0277         // so that system certificates come first in case the user added an already-present
0278         // certificate as a user certificate.
0279         return cacert1.store < cacert2.store;
0280     }
0281     return cacert1.certHash < cacert2.certHash;
0282 }
0283 
0284 void KSslCertificateManagerPrivate::setAllCertificates(const QList<KSslCaCertificate> &certsIn)
0285 {
0286     Q_ASSERT(knownCerts.isEmpty());
0287     QList<KSslCaCertificate> in = certsIn;
0288     QList<KSslCaCertificate> old = allCertificates();
0289     std::sort(in.begin(), in.end(), certLessThan);
0290     std::sort(old.begin(), old.end(), certLessThan);
0291 
0292     for (int ii = 0, oi = 0; ii < in.size() || oi < old.size(); ++ii, ++oi) {
0293         // look at all elements in both lists, even if we reach the end of one early.
0294         if (ii >= in.size()) {
0295             removeCertificate(old.at(oi));
0296             continue;
0297         } else if (oi >= old.size()) {
0298             addCertificate(in.at(ii));
0299             continue;
0300         }
0301 
0302         if (certLessThan(old.at(oi), in.at(ii))) {
0303             // the certificate in "old" is not in "in". only advance the index of "old".
0304             removeCertificate(old.at(oi));
0305             ii--;
0306         } else if (certLessThan(in.at(ii), old.at(oi))) {
0307             // the certificate in "in" is not in "old". only advance the index of "in".
0308             addCertificate(in.at(ii));
0309             oi--;
0310         } else { // in.at(ii) "==" old.at(oi)
0311             if (in.at(ii).cert != old.at(oi).cert) {
0312                 // hash collision, be prudent(?) and don't do anything.
0313             } else {
0314                 knownCerts.insert(old.at(oi).certHash);
0315                 if (in.at(ii).isBlacklisted != old.at(oi).isBlacklisted) {
0316                     updateCertificateBlacklisted(in.at(ii));
0317                 }
0318             }
0319         }
0320     }
0321     knownCerts.clear();
0322     QMutexLocker certListLocker(&certListMutex);
0323     isCertListLoaded = false;
0324     loadDefaultCaCertificates();
0325 }
0326 
0327 QList<KSslCaCertificate> KSslCertificateManagerPrivate::allCertificates() const
0328 {
0329     // qDebug() << Q_FUNC_INFO;
0330     QList<KSslCaCertificate> ret;
0331     const QList<QSslCertificate> list = deduplicate(QSslConfiguration::systemCaCertificates());
0332     for (const QSslCertificate &cert : list) {
0333         ret += KSslCaCertificate(cert, KSslCaCertificate::SystemStore, false);
0334     }
0335 
0336     const QList<QSslCertificate> userList = QSslCertificate::fromPath(userCertDir + QLatin1Char('*'), QSsl::Pem, QSslCertificate::PatternSyntax::Wildcard);
0337     for (const QSslCertificate &cert : userList) {
0338         ret += KSslCaCertificate(cert, KSslCaCertificate::UserStore, false);
0339     }
0340 
0341     KConfig config(QStringLiteral("ksslcablacklist"), KConfig::SimpleConfig);
0342     KConfigGroup group = config.group(QStringLiteral("Blacklist of CA Certificates"));
0343     for (KSslCaCertificate &cert : ret) {
0344         if (group.hasKey(cert.certHash.constData())) {
0345             cert.isBlacklisted = true;
0346             // qDebug() << "is blacklisted";
0347         }
0348     }
0349 
0350     return ret;
0351 }
0352 
0353 bool KSslCertificateManagerPrivate::updateCertificateBlacklisted(const KSslCaCertificate &cert)
0354 {
0355     return setCertificateBlacklisted(cert.certHash, cert.isBlacklisted);
0356 }
0357 
0358 bool KSslCertificateManagerPrivate::setCertificateBlacklisted(const QByteArray &certHash, bool isBlacklisted)
0359 {
0360     // qDebug() << Q_FUNC_INFO << isBlacklisted;
0361     KConfig config(QStringLiteral("ksslcablacklist"), KConfig::SimpleConfig);
0362     KConfigGroup group = config.group(QStringLiteral("Blacklist of CA Certificates"));
0363     if (isBlacklisted) {
0364         // TODO check against certificate list ?
0365         group.writeEntry(certHash.constData(), QString());
0366     } else {
0367         if (!group.hasKey(certHash.constData())) {
0368             return false;
0369         }
0370         group.deleteEntry(certHash.constData());
0371     }
0372 
0373     return true;
0374 }
0375 
0376 class KSslCertificateManagerContainer
0377 {
0378 public:
0379     KSslCertificateManager sslCertificateManager;
0380 };
0381 
0382 Q_GLOBAL_STATIC(KSslCertificateManagerContainer, g_instance)
0383 
0384 KSslCertificateManager::KSslCertificateManager()
0385     : d(new KSslCertificateManagerPrivate())
0386 {
0387 }
0388 
0389 KSslCertificateManager::~KSslCertificateManager() = default;
0390 
0391 // static
0392 KSslCertificateManager *KSslCertificateManager::self()
0393 {
0394     return &g_instance()->sslCertificateManager;
0395 }
0396 
0397 void KSslCertificateManager::setRule(const KSslCertificateRule &rule)
0398 {
0399     d->iface->setRule(rule);
0400 }
0401 
0402 void KSslCertificateManager::clearRule(const KSslCertificateRule &rule)
0403 {
0404     d->iface->clearRule(rule);
0405 }
0406 
0407 void KSslCertificateManager::clearRule(const QSslCertificate &cert, const QString &hostName)
0408 {
0409     d->iface->clearRule(cert, hostName);
0410 }
0411 
0412 KSslCertificateRule KSslCertificateManager::rule(const QSslCertificate &cert, const QString &hostName) const
0413 {
0414     return d->iface->rule(cert, hostName);
0415 }
0416 
0417 QList<QSslCertificate> KSslCertificateManager::caCertificates() const
0418 {
0419     QMutexLocker certLocker(&d->certListMutex);
0420     if (!d->isCertListLoaded) {
0421         d->loadDefaultCaCertificates();
0422     }
0423     return d->defaultCaCertificates;
0424 }
0425 
0426 QList<QSslError> KSslCertificateManager::nonIgnorableErrors(const QList<QSslError> &errors)
0427 {
0428     QList<QSslError> ret;
0429     // errors not handled in KSSLD
0430     std::copy_if(errors.begin(), errors.end(), std::back_inserter(ret), [](const QSslError &e) {
0431         return e.error() == QSslError::NoPeerCertificate || e.error() == QSslError::PathLengthExceeded || e.error() == QSslError::NoSslSupport;
0432     });
0433     return ret;
0434 }
0435 
0436 QList<KSslCaCertificate> _allKsslCaCertificates(KSslCertificateManager *cm)
0437 {
0438     return KSslCertificateManagerPrivate::get(cm)->allCertificates();
0439 }
0440 
0441 void _setAllKsslCaCertificates(KSslCertificateManager *cm, const QList<KSslCaCertificate> &certsIn)
0442 {
0443     KSslCertificateManagerPrivate::get(cm)->setAllCertificates(certsIn);
0444 }
0445 
0446 #include "moc_kssld_interface.cpp"