File indexing completed on 2024-05-05 16:13:21

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