KDECore
ksslcertificatemanager.cpp
Go to the documentation of this file.
00001 /* This file is part of the KDE project 00002 * 00003 * Copyright (C) 2007, 2008, 2010 Andreas Hartmetz <ahartmetz@gmail.com> 00004 * 00005 * This library is free software; you can redistribute it and/or 00006 * modify it under the terms of the GNU Library General Public 00007 * License as published by the Free Software Foundation; either 00008 * version 2 of the License, or (at your option) any later version. 00009 * 00010 * This library is distributed in the hope that it will be useful, 00011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00013 * Library General Public License for more details. 00014 * 00015 * You should have received a copy of the GNU Library General Public License 00016 * along with this library; see the file COPYING.LIB. If not, write to 00017 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00018 * Boston, MA 02110-1301, USA. 00019 */ 00020 00021 00022 #include "ksslcertificatemanager.h" 00023 #include "ktcpsocket.h" 00024 #include "ktcpsocket_p.h" 00025 #include <kconfig.h> 00026 #include <kconfiggroup.h> 00027 #include <kdebug.h> 00028 #include <kglobal.h> 00029 #include <klocale.h> 00030 #include <kstandarddirs.h> 00031 #include <ktoolinvocation.h> 00032 00033 #include <QtDBus/QtDBus> 00034 00035 #include "kssld/kssld_interface.h" 00036 #include "ksslcertificatemanager_p.h" 00037 00038 /* 00039 Config file format: 00040 [<MD5-Digest>] 00041 <Host> = <Date> <List of ignored errors> 00042 #for example 00043 #mail.kdab.net = ExpireUTC 2008-08-20T18:22:14, SelfSigned, Expired 00044 #very.old.com = ExpireUTC 2008-08-20T18:22:14, TooWeakEncryption <- not actually planned to implement 00045 #clueless.admin.com = ExpireUTC 2008-08-20T18:22:14, HostNameMismatch 00046 # 00047 #Wildcard syntax 00048 #* = ExpireUTC 2008-08-20T18:22:14, SelfSigned 00049 #*.kdab.net = ExpireUTC 2008-08-20T18:22:14, SelfSigned 00050 #mail.kdab.net = ExpireUTC 2008-08-20T18:22:14, All <- not implemented 00051 #* = ExpireUTC 9999-12-31T23:59:59, Reject #we know that something is wrong with that certificate 00052 CertificatePEM = <PEM-encoded certificate> #host entries are all lowercase, thus no clashes 00053 00054 */ 00055 00056 // TODO GUI for managing exception rules 00057 00058 class KSslCertificateRulePrivate 00059 { 00060 public: 00061 QSslCertificate certificate; 00062 QString hostName; 00063 bool isRejected; 00064 QDateTime expiryDateTime; 00065 QList<KSslError::Error> ignoredErrors; 00066 }; 00067 00068 00069 KSslCertificateRule::KSslCertificateRule(const QSslCertificate &cert, const QString &hostName) 00070 : d(new KSslCertificateRulePrivate()) 00071 { 00072 d->certificate = cert; 00073 d->hostName = hostName; 00074 d->isRejected = false; 00075 } 00076 00077 00078 KSslCertificateRule::KSslCertificateRule(const KSslCertificateRule &other) 00079 : d(new KSslCertificateRulePrivate()) 00080 { 00081 *d = *other.d; 00082 } 00083 00084 00085 KSslCertificateRule::~KSslCertificateRule() 00086 { 00087 delete d; 00088 } 00089 00090 00091 KSslCertificateRule &KSslCertificateRule::operator=(const KSslCertificateRule &other) 00092 { 00093 *d = *other.d; 00094 return *this; 00095 } 00096 00097 00098 QSslCertificate KSslCertificateRule::certificate() const 00099 { 00100 return d->certificate; 00101 } 00102 00103 00104 QString KSslCertificateRule::hostName() const 00105 { 00106 return d->hostName; 00107 } 00108 00109 00110 void KSslCertificateRule::setExpiryDateTime(const QDateTime &dateTime) 00111 { 00112 d->expiryDateTime = dateTime; 00113 } 00114 00115 00116 QDateTime KSslCertificateRule::expiryDateTime() const 00117 { 00118 return d->expiryDateTime; 00119 } 00120 00121 00122 void KSslCertificateRule::setRejected(bool rejected) 00123 { 00124 d->isRejected = rejected; 00125 } 00126 00127 00128 bool KSslCertificateRule::isRejected() const 00129 { 00130 return d->isRejected; 00131 } 00132 00133 00134 bool KSslCertificateRule::isErrorIgnored(KSslError::Error error) const 00135 { 00136 foreach (KSslError::Error ignoredError, d->ignoredErrors) 00137 if (error == ignoredError) 00138 return true; 00139 00140 return false; 00141 } 00142 00143 00144 void KSslCertificateRule::setIgnoredErrors(const QList<KSslError::Error> &errors) 00145 { 00146 d->ignoredErrors.clear(); 00147 //### Quadratic runtime, woohoo! Use a QSet if that should ever be an issue. 00148 foreach(KSslError::Error e, errors) 00149 if (!isErrorIgnored(e)) 00150 d->ignoredErrors.append(e); 00151 } 00152 00153 00154 void KSslCertificateRule::setIgnoredErrors(const QList<KSslError> &errors) 00155 { 00156 QList<KSslError::Error> el; 00157 foreach(const KSslError &e, errors) 00158 el.append(e.error()); 00159 setIgnoredErrors(el); 00160 } 00161 00162 00163 QList<KSslError::Error> KSslCertificateRule::ignoredErrors() const 00164 { 00165 return d->ignoredErrors; 00166 } 00167 00168 00169 QList<KSslError::Error> KSslCertificateRule::filterErrors(const QList<KSslError::Error> &errors) const 00170 { 00171 QList<KSslError::Error> ret; 00172 foreach (KSslError::Error error, errors) { 00173 if (!isErrorIgnored(error)) 00174 ret.append(error); 00175 } 00176 return ret; 00177 } 00178 00179 00180 QList<KSslError> KSslCertificateRule::filterErrors(const QList<KSslError> &errors) const 00181 { 00182 QList<KSslError> ret; 00183 foreach (const KSslError &error, errors) { 00184 if (!isErrorIgnored(error.error())) 00185 ret.append(error); 00186 } 00187 return ret; 00188 } 00189 00190 00192 00193 static QList<QSslCertificate> deduplicate(const QList<QSslCertificate> &certs) 00194 { 00195 QSet<QByteArray> digests; 00196 QList<QSslCertificate> ret; 00197 foreach (const QSslCertificate &cert, certs) { 00198 QByteArray digest = cert.digest(); 00199 if (!digests.contains(digest)) { 00200 digests.insert(digest); 00201 ret.append(cert); 00202 } 00203 } 00204 return ret; 00205 } 00206 00207 KSslCertificateManagerPrivate::KSslCertificateManagerPrivate() 00208 : config(QString::fromLatin1("ksslcertificatemanager"), KConfig::SimpleConfig), 00209 iface(new org::kde::KSSLDInterface(QString::fromLatin1("org.kde.kded"), 00210 QString::fromLatin1("/modules/kssld"), 00211 QDBusConnection::sessionBus())), 00212 isCertListLoaded(false), 00213 userCertDir(KGlobal::dirs()->saveLocation("data", QString::fromLatin1("kssl/userCaCertificates/"))) 00214 { 00215 // set Qt's set to empty; this is protected by the lock in K_GLOBAL_STATIC. 00216 QSslSocket::setDefaultCaCertificates(QList<QSslCertificate>()); 00217 } 00218 00219 KSslCertificateManagerPrivate::~KSslCertificateManagerPrivate() 00220 { 00221 delete iface; 00222 iface = 0; 00223 } 00224 00225 void KSslCertificateManagerPrivate::loadDefaultCaCertificates() 00226 { 00227 defaultCaCertificates.clear(); 00228 00229 if (!KGlobal::hasMainComponent()) { 00230 Q_ASSERT(false); 00231 return; // we need KGlobal::dirs() available 00232 } 00233 00234 QList<QSslCertificate> certs = deduplicate(QSslSocket::systemCaCertificates()); 00235 00236 KConfig config(QString::fromLatin1("ksslcablacklist"), KConfig::SimpleConfig); 00237 KConfigGroup group = config.group("Blacklist of CA Certificates"); 00238 00239 certs.append(QSslCertificate::fromPath(userCertDir + QLatin1String("*"), QSsl::Pem, 00240 QRegExp::Wildcard)); 00241 foreach (const QSslCertificate &cert, certs) { 00242 const QByteArray digest = cert.digest().toHex(); 00243 if (!group.hasKey(digest.constData())) { 00244 defaultCaCertificates += cert; 00245 } 00246 } 00247 00248 isCertListLoaded = true; 00249 } 00250 00251 00252 bool KSslCertificateManagerPrivate::addCertificate(const KSslCaCertificate &in) 00253 { 00254 kDebug(7029); 00255 // cannot add a certificate to the system store 00256 if (in.store == KSslCaCertificate::SystemStore) { 00257 Q_ASSERT(false); 00258 return false; 00259 } 00260 if (knownCerts.contains(in.certHash)) { 00261 Q_ASSERT(false); 00262 return false; 00263 } 00264 00265 QString certFilename = userCertDir + QString::fromLatin1(in.certHash); 00266 kDebug(7029) << certFilename; 00267 QFile certFile(certFilename); 00268 if (certFile.open(QIODevice::ReadOnly)) { 00269 return false; 00270 } 00271 if (!certFile.open(QIODevice::WriteOnly)) { 00272 return false; 00273 } 00274 if (certFile.write(in.cert.toPem()) < 1) { 00275 return false; 00276 } 00277 knownCerts.insert(in.certHash); 00278 00279 updateCertificateBlacklisted(in); 00280 00281 return true; 00282 } 00283 00284 00285 bool KSslCertificateManagerPrivate::removeCertificate(const KSslCaCertificate &old) 00286 { 00287 kDebug(7029); 00288 // cannot remove a certificate from the system store 00289 if (old.store == KSslCaCertificate::SystemStore) { 00290 Q_ASSERT(false); 00291 return false; 00292 } 00293 00294 if (!QFile::remove(userCertDir + QString::fromLatin1(old.certHash))) { 00295 00296 // suppose somebody copied a certificate file into userCertDir without changing the 00297 // filename to the digest. 00298 // the rest of the code will work fine because it loads all certificate files from 00299 // userCertDir without asking for the name, we just can't remove the certificate using 00300 // its digest as filename - so search the whole directory. 00301 // if the certificate was added with the digest as name *and* with a different name, we 00302 // still fail to remove it completely at first try - BAD USER! BAD! 00303 00304 bool removed = false; 00305 QDir dir(userCertDir); 00306 foreach (const QString &certFilename, dir.entryList(QDir::Files)) { 00307 const QString certPath = userCertDir + certFilename; 00308 QList<QSslCertificate> certs = QSslCertificate::fromPath(certPath); 00309 00310 if (!certs.isEmpty() && certs.at(0).digest().toHex() == old.certHash) { 00311 if (QFile::remove(certPath)) { 00312 removed = true; 00313 } else { 00314 // maybe the file is readable but not writable 00315 return false; 00316 } 00317 } 00318 } 00319 if (!removed) { 00320 // looks like the file is not there 00321 return false; 00322 } 00323 } 00324 00325 // note that knownCerts *should* need no updating due to the way setAllCertificates() works - 00326 // it should never call addCertificate and removeCertificate for the same cert in one run 00327 00328 // clean up the blacklist 00329 setCertificateBlacklisted(old.certHash, false); 00330 00331 return true; 00332 } 00333 00334 static bool certLessThan(const KSslCaCertificate &cacert1, const KSslCaCertificate &cacert2) 00335 { 00336 if (cacert1.store != cacert2.store) { 00337 // SystemStore is numerically smaller so the system certs come first; this is important 00338 // so that system certificates come first in case the user added an already-present 00339 // certificate as a user certificate. 00340 return cacert1.store < cacert2.store; 00341 } 00342 return cacert1.certHash < cacert2.certHash; 00343 } 00344 00345 void KSslCertificateManagerPrivate::setAllCertificates(const QList<KSslCaCertificate> &certsIn) 00346 { 00347 Q_ASSERT(knownCerts.isEmpty()); 00348 QList<KSslCaCertificate> in = certsIn; 00349 QList<KSslCaCertificate> old = allCertificates(); 00350 qSort(in.begin(), in.end(), certLessThan); 00351 qSort(old.begin(), old.end(), certLessThan); 00352 00353 for (int ii = 0, oi = 0; ii < in.size() || oi < old.size(); ii++, oi++) { 00354 // look at all elements in both lists, even if we reach the end of one early. 00355 if (ii >= in.size()) { 00356 removeCertificate(old.at(oi)); 00357 continue; 00358 } else if (oi >= old.size()) { 00359 addCertificate(in.at(ii)); 00360 continue; 00361 } 00362 00363 if (certLessThan (old.at(oi), in.at(ii))) { 00364 // the certificate in "old" is not in "in". only advance the index of "old". 00365 removeCertificate(old.at(oi)); 00366 ii--; 00367 } else if (certLessThan(in.at(ii), old.at(oi))) { 00368 // the certificate in "in" is not in "old". only advance the index of "in". 00369 addCertificate(in.at(ii)); 00370 oi--; 00371 } else { // in.at(ii) "==" old.at(oi) 00372 if (in.at(ii).cert != old.at(oi).cert) { 00373 // hash collision, be prudent(?) and don't do anything. 00374 } else { 00375 knownCerts.insert(old.at(oi).certHash); 00376 if (in.at(ii).isBlacklisted != old.at(oi).isBlacklisted) { 00377 updateCertificateBlacklisted(in.at(ii)); 00378 } 00379 } 00380 } 00381 } 00382 knownCerts.clear(); 00383 QMutexLocker certListLocker(&certListMutex); 00384 isCertListLoaded = false; 00385 loadDefaultCaCertificates(); 00386 } 00387 00388 QList<KSslCaCertificate> KSslCertificateManagerPrivate::allCertificates() const 00389 { 00390 kDebug(7029); 00391 QList<KSslCaCertificate> ret; 00392 foreach (const QSslCertificate &cert, deduplicate(QSslSocket::systemCaCertificates())) { 00393 ret += KSslCaCertificate(cert, KSslCaCertificate::SystemStore, false); 00394 } 00395 00396 foreach (const QSslCertificate &cert, QSslCertificate::fromPath(userCertDir + QLatin1String("/*"), 00397 QSsl::Pem, QRegExp::Wildcard)) { 00398 ret += KSslCaCertificate(cert, KSslCaCertificate::UserStore, false); 00399 } 00400 00401 KConfig config(QString::fromLatin1("ksslcablacklist"), KConfig::SimpleConfig); 00402 KConfigGroup group = config.group("Blacklist of CA Certificates"); 00403 for (int i = 0; i < ret.size(); i++) { 00404 if (group.hasKey(ret[i].certHash.constData())) { 00405 ret[i].isBlacklisted = true; 00406 kDebug(7029) << "is blacklisted"; 00407 } 00408 } 00409 00410 return ret; 00411 } 00412 00413 00414 bool KSslCertificateManagerPrivate::updateCertificateBlacklisted(const KSslCaCertificate &cert) 00415 { 00416 return setCertificateBlacklisted(cert.certHash, cert.isBlacklisted); 00417 } 00418 00419 00420 bool KSslCertificateManagerPrivate::setCertificateBlacklisted(const QByteArray &certHash, 00421 bool isBlacklisted) 00422 { 00423 kDebug(7029) << isBlacklisted; 00424 KConfig config(QString::fromLatin1("ksslcablacklist"), KConfig::SimpleConfig); 00425 KConfigGroup group = config.group("Blacklist of CA Certificates"); 00426 if (isBlacklisted) { 00427 // TODO check against certificate list ? 00428 group.writeEntry(certHash.constData(), QString()); 00429 } else { 00430 if (!group.hasKey(certHash.constData())) { 00431 return false; 00432 } 00433 group.deleteEntry(certHash.constData()); 00434 } 00435 00436 return true; 00437 } 00438 00439 00440 class KSslCertificateManagerContainer 00441 { 00442 public: 00443 KSslCertificateManager sslCertificateManager; 00444 }; 00445 00446 K_GLOBAL_STATIC(KSslCertificateManagerContainer, g_instance) 00447 00448 00449 KSslCertificateManager::KSslCertificateManager() 00450 : d(new KSslCertificateManagerPrivate()) 00451 { 00452 // Make sure kded is running 00453 if (!QDBusConnection::sessionBus().interface()->isServiceRegistered(QString::fromLatin1("org.kde.kded"))) { 00454 KToolInvocation::klauncher(); // this calls startKdeinit 00455 } 00456 } 00457 00458 00459 KSslCertificateManager::~KSslCertificateManager() 00460 { 00461 delete d; 00462 } 00463 00464 00465 //static 00466 KSslCertificateManager *KSslCertificateManager::self() 00467 { 00468 return &g_instance->sslCertificateManager; 00469 } 00470 00471 00472 void KSslCertificateManager::setRule(const KSslCertificateRule &rule) 00473 { 00474 d->iface->setRule(rule); 00475 } 00476 00477 00478 void KSslCertificateManager::clearRule(const KSslCertificateRule &rule) 00479 { 00480 d->iface->clearRule(rule); 00481 } 00482 00483 00484 void KSslCertificateManager::clearRule(const QSslCertificate &cert, const QString &hostName) 00485 { 00486 d->iface->clearRule(cert, hostName); 00487 } 00488 00489 00490 KSslCertificateRule KSslCertificateManager::rule(const QSslCertificate &cert, 00491 const QString &hostName) const 00492 { 00493 return d->iface->rule(cert, hostName); 00494 } 00495 00496 00497 QList<QSslCertificate> KSslCertificateManager::caCertificates() const 00498 { 00499 QMutexLocker certLocker(&d->certListMutex); 00500 if (!d->isCertListLoaded) { 00501 d->loadDefaultCaCertificates(); 00502 } 00503 return d->defaultCaCertificates; 00504 } 00505 00506 00507 //static 00508 QList<KSslError> KSslCertificateManager::nonIgnorableErrors(const QList<KSslError> &/*e*/) 00509 { 00510 QList<KSslError> ret; 00511 // ### add filtering here... 00512 return ret; 00513 } 00514 00515 //static 00516 QList<KSslError::Error> KSslCertificateManager::nonIgnorableErrors(const QList<KSslError::Error> &/*e*/) 00517 { 00518 QList<KSslError::Error> ret; 00519 // ### add filtering here... 00520 return ret; 00521 } 00522 00523 QList<KSslCaCertificate> _allKsslCaCertificates(KSslCertificateManager *cm) 00524 { 00525 return KSslCertificateManagerPrivate::get(cm)->allCertificates(); 00526 } 00527 00528 void _setAllKsslCaCertificates(KSslCertificateManager *cm, const QList<KSslCaCertificate> &certsIn) 00529 { 00530 KSslCertificateManagerPrivate::get(cm)->setAllCertificates(certsIn); 00531 } 00532 00533 #include "kssld/kssld_interface.moc"
KDE 4.6 API Reference