File indexing completed on 2023-09-24 04:08:39
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"