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"