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 KSslCertificateManagerPrivate::KSslCertificateManagerPrivate() 00194 : config(QString::fromLatin1("ksslcertificatemanager"), KConfig::SimpleConfig), 00195 iface(new org::kde::KSSLDInterface(QString::fromLatin1("org.kde.kded"), 00196 QString::fromLatin1("/modules/kssld"), 00197 QDBusConnection::sessionBus())), 00198 isCertListLoaded(false), 00199 userCertDir(KGlobal::dirs()->saveLocation("data", QString::fromLatin1("kssl/userCaCertificates/"))) 00200 { 00201 // set Qt's set to empty; this is protected by the lock in K_GLOBAL_STATIC. 00202 QSslSocket::setDefaultCaCertificates(QList<QSslCertificate>()); 00203 } 00204 00205 KSslCertificateManagerPrivate::~KSslCertificateManagerPrivate() 00206 { 00207 delete iface; 00208 iface = 0; 00209 } 00210 00211 void KSslCertificateManagerPrivate::loadDefaultCaCertificates() 00212 { 00213 defaultCaCertificates.clear(); 00214 00215 if (!KGlobal::hasMainComponent()) { 00216 Q_ASSERT(false); 00217 return; // we need KGlobal::dirs() available 00218 } 00219 00220 QList<QSslCertificate> certs = QSslSocket::systemCaCertificates(); 00221 00222 KConfig config(QString::fromLatin1("ksslcablacklist"), KConfig::SimpleConfig); 00223 KConfigGroup group = config.group("Blacklist of CA Certificates"); 00224 00225 certs.append(QSslCertificate::fromPath(userCertDir + QLatin1String("*"), QSsl::Pem, 00226 QRegExp::Wildcard)); 00227 foreach (const QSslCertificate &cert, certs) { 00228 const QByteArray digest = cert.digest().toHex(); 00229 if (!group.hasKey(digest.constData())) { 00230 defaultCaCertificates += cert; 00231 } 00232 } 00233 00234 isCertListLoaded = true; 00235 } 00236 00237 00238 bool KSslCertificateManagerPrivate::addCertificate(const KSslCaCertificate &in) 00239 { 00240 kDebug(7029); 00241 // cannot add a certificate to the system store 00242 if (in.store == KSslCaCertificate::SystemStore) { 00243 Q_ASSERT(false); 00244 return false; 00245 } 00246 if (knownCerts.contains(in.certHash)) { 00247 Q_ASSERT(false); 00248 return false; 00249 } 00250 00251 QString certFilename = userCertDir + QString::fromLatin1(in.certHash); 00252 kDebug(7029) << certFilename; 00253 QFile certFile(certFilename); 00254 if (certFile.open(QIODevice::ReadOnly)) { 00255 return false; 00256 } 00257 if (!certFile.open(QIODevice::WriteOnly)) { 00258 return false; 00259 } 00260 if (certFile.write(in.cert.toPem()) < 1) { 00261 return false; 00262 } 00263 knownCerts.insert(in.certHash); 00264 00265 updateCertificateBlacklisted(in); 00266 00267 return true; 00268 } 00269 00270 00271 bool KSslCertificateManagerPrivate::removeCertificate(const KSslCaCertificate &old) 00272 { 00273 kDebug(7029); 00274 // cannot remove a certificate from the system store 00275 if (old.store == KSslCaCertificate::SystemStore) { 00276 Q_ASSERT(false); 00277 return false; 00278 } 00279 00280 if (!QFile::remove(userCertDir + QString::fromLatin1(old.certHash))) { 00281 00282 // suppose somebody copied a certificate file into userCertDir without changing the 00283 // filename to the digest. 00284 // the rest of the code will work fine because it loads all certificate files from 00285 // userCertDir without asking for the name, we just can't remove the certificate using 00286 // its digest as filename - so search the whole directory. 00287 // if the certificate was added with the digest as name *and* with a different name, we 00288 // still fail to remove it completely at first try - BAD USER! BAD! 00289 00290 bool removed = false; 00291 QDir dir(userCertDir); 00292 foreach (const QString &certFilename, dir.entryList(QDir::Files)) { 00293 const QString certPath = userCertDir + certFilename; 00294 QList<QSslCertificate> certs = QSslCertificate::fromPath(certPath); 00295 00296 if (!certs.isEmpty() && certs.at(0).digest().toHex() == old.certHash) { 00297 if (QFile::remove(certPath)) { 00298 removed = true; 00299 } else { 00300 // maybe the file is readable but not writable 00301 return false; 00302 } 00303 } 00304 } 00305 if (!removed) { 00306 // looks like the file is not there 00307 return false; 00308 } 00309 } 00310 00311 // note that knownCerts *should* need no updating due to the way setAllCertificates() works - 00312 // it should never call addCertificate and removeCertificate for the same cert in one run 00313 00314 // clean up the blacklist 00315 setCertificateBlacklisted(old.certHash, false); 00316 00317 return true; 00318 } 00319 00320 static bool certLessThan(const KSslCaCertificate &cacert1, const KSslCaCertificate &cacert2) 00321 { 00322 if (cacert1.store != cacert2.store) { 00323 // SystemStore is numerically smaller so the system certs come first; this is important 00324 // so that system certificates come first in case the user added an already-present 00325 // certificate as a user certificate. 00326 return cacert1.store < cacert2.store; 00327 } 00328 return cacert1.certHash < cacert2.certHash; 00329 } 00330 00331 void KSslCertificateManagerPrivate::setAllCertificates(const QList<KSslCaCertificate> &certsIn) 00332 { 00333 Q_ASSERT(knownCerts.isEmpty()); 00334 QList<KSslCaCertificate> in = certsIn; 00335 QList<KSslCaCertificate> old = allCertificates(); 00336 qSort(in.begin(), in.end(), certLessThan); 00337 qSort(old.begin(), old.end(), certLessThan); 00338 00339 for (int ii = 0, oi = 0; ii < in.size() || oi < old.size(); ii++, oi++) { 00340 // look at all elements in both lists, even if we reach the end of one early. 00341 if (ii >= in.size()) { 00342 removeCertificate(old.at(oi)); 00343 continue; 00344 } else if (oi >= old.size()) { 00345 addCertificate(in.at(ii)); 00346 continue; 00347 } 00348 00349 if (certLessThan (old.at(oi), in.at(ii))) { 00350 // the certificate in "old" is not in "in". only advance the index of "old". 00351 removeCertificate(old.at(oi)); 00352 ii--; 00353 } else if (certLessThan(in.at(ii), old.at(oi))) { 00354 // the certificate in "in" is not in "old". only advance the index of "in". 00355 addCertificate(in.at(ii)); 00356 oi--; 00357 } else { // in.at(ii) "==" old.at(oi) 00358 if (in.at(ii).cert != old.at(oi).cert) { 00359 // hash collision, be prudent(?) and don't do anything. 00360 } else { 00361 knownCerts.insert(old.at(oi).certHash); 00362 if (in.at(ii).isBlacklisted != old.at(oi).isBlacklisted) { 00363 updateCertificateBlacklisted(in.at(ii)); 00364 } 00365 } 00366 } 00367 } 00368 knownCerts.clear(); 00369 QMutexLocker certListLocker(&certListMutex); 00370 isCertListLoaded = false; 00371 loadDefaultCaCertificates(); 00372 } 00373 00374 QList<KSslCaCertificate> KSslCertificateManagerPrivate::allCertificates() const 00375 { 00376 kDebug(7029); 00377 QList<KSslCaCertificate> ret; 00378 foreach (const QSslCertificate &cert, QSslSocket::systemCaCertificates()) { 00379 ret += KSslCaCertificate(cert, KSslCaCertificate::SystemStore, false); 00380 } 00381 00382 foreach (const QSslCertificate &cert, QSslCertificate::fromPath(userCertDir + QLatin1String("/*"), 00383 QSsl::Pem, QRegExp::Wildcard)) { 00384 ret += KSslCaCertificate(cert, KSslCaCertificate::UserStore, false); 00385 } 00386 00387 KConfig config(QString::fromLatin1("ksslcablacklist"), KConfig::SimpleConfig); 00388 KConfigGroup group = config.group("Blacklist of CA Certificates"); 00389 for (int i = 0; i < ret.size(); i++) { 00390 if (group.hasKey(ret[i].certHash.constData())) { 00391 ret[i].isBlacklisted = true; 00392 kDebug(7029) << "is blacklisted"; 00393 } 00394 } 00395 00396 return ret; 00397 } 00398 00399 00400 bool KSslCertificateManagerPrivate::updateCertificateBlacklisted(const KSslCaCertificate &cert) 00401 { 00402 return setCertificateBlacklisted(cert.certHash, cert.isBlacklisted); 00403 } 00404 00405 00406 bool KSslCertificateManagerPrivate::setCertificateBlacklisted(const QByteArray &certHash, 00407 bool isBlacklisted) 00408 { 00409 kDebug(7029) << isBlacklisted; 00410 KConfig config(QString::fromLatin1("ksslcablacklist"), KConfig::SimpleConfig); 00411 KConfigGroup group = config.group("Blacklist of CA Certificates"); 00412 if (isBlacklisted) { 00413 // TODO check against certificate list ? 00414 group.writeEntry(certHash.constData(), QString()); 00415 } else { 00416 if (!group.hasKey(certHash.constData())) { 00417 return false; 00418 } 00419 group.deleteEntry(certHash.constData()); 00420 } 00421 00422 return true; 00423 } 00424 00425 00426 class KSslCertificateManagerContainer 00427 { 00428 public: 00429 KSslCertificateManager sslCertificateManager; 00430 }; 00431 00432 K_GLOBAL_STATIC(KSslCertificateManagerContainer, g_instance) 00433 00434 00435 KSslCertificateManager::KSslCertificateManager() 00436 : d(new KSslCertificateManagerPrivate()) 00437 { 00438 // Make sure kded is running 00439 if (!QDBusConnection::sessionBus().interface()->isServiceRegistered(QString::fromLatin1("org.kde.kded"))) { 00440 KToolInvocation::klauncher(); // this calls startKdeinit 00441 } 00442 } 00443 00444 00445 KSslCertificateManager::~KSslCertificateManager() 00446 { 00447 delete d; 00448 } 00449 00450 00451 //static 00452 KSslCertificateManager *KSslCertificateManager::self() 00453 { 00454 return &g_instance->sslCertificateManager; 00455 } 00456 00457 00458 void KSslCertificateManager::setRule(const KSslCertificateRule &rule) 00459 { 00460 d->iface->setRule(rule); 00461 } 00462 00463 00464 void KSslCertificateManager::clearRule(const KSslCertificateRule &rule) 00465 { 00466 d->iface->clearRule(rule); 00467 } 00468 00469 00470 void KSslCertificateManager::clearRule(const QSslCertificate &cert, const QString &hostName) 00471 { 00472 d->iface->clearRule(cert, hostName); 00473 } 00474 00475 00476 KSslCertificateRule KSslCertificateManager::rule(const QSslCertificate &cert, 00477 const QString &hostName) const 00478 { 00479 return d->iface->rule(cert, hostName); 00480 } 00481 00482 00483 QList<QSslCertificate> KSslCertificateManager::caCertificates() const 00484 { 00485 QMutexLocker certLocker(&d->certListMutex); 00486 if (!d->isCertListLoaded) { 00487 d->loadDefaultCaCertificates(); 00488 } 00489 return d->defaultCaCertificates; 00490 } 00491 00492 00493 //static 00494 QList<KSslError> KSslCertificateManager::nonIgnorableErrors(const QList<KSslError> &/*e*/) 00495 { 00496 QList<KSslError> ret; 00497 // ### add filtering here... 00498 return ret; 00499 } 00500 00501 //static 00502 QList<KSslError::Error> KSslCertificateManager::nonIgnorableErrors(const QList<KSslError::Error> &/*e*/) 00503 { 00504 QList<KSslError::Error> ret; 00505 // ### add filtering here... 00506 return ret; 00507 } 00508 00509 QList<KSslCaCertificate> _allKsslCaCertificates(KSslCertificateManager *cm) 00510 { 00511 return KSslCertificateManagerPrivate::get(cm)->allCertificates(); 00512 } 00513 00514 void _setAllKsslCaCertificates(KSslCertificateManager *cm, const QList<KSslCaCertificate> &certsIn) 00515 { 00516 KSslCertificateManagerPrivate::get(cm)->setAllCertificates(certsIn); 00517 } 00518 00519 #include "kssld/kssld_interface.moc"
KDE 4.6 API Reference