• Skip to content
  • Skip to link menu
KDE 4.6 API Reference
  • KDE API Reference
  • kdelibs
  • KDE Home
  • Contact Us
 

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"

KDECore

Skip menu "KDECore"
  • Main Page
  • Modules
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.7.3
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal