diff --git a/src/core/ksslcertificatemanager.cpp b/src/core/ksslcertificatemanager.cpp index f91e56e3..ffabc9b0 100644 --- a/src/core/ksslcertificatemanager.cpp +++ b/src/core/ksslcertificatemanager.cpp @@ -1,537 +1,541 @@ /* This file is part of the KDE project * * Copyright (C) 2007, 2008, 2010 Andreas Hartmetz * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "ksslcertificatemanager.h" #include "ksslcertificatemanager_p.h" #include "ksslerror_p.h" #include "ktcpsocket.h" #include "ksslerroruidata_p.h" #include #include #include #include #include #include #include #include #include #include "kssld_interface.h" /* Config file format: [] = #for example #mail.kdab.net = ExpireUTC 2008-08-20T18:22:14, SelfSigned, Expired #very.old.com = ExpireUTC 2008-08-20T18:22:14, TooWeakEncryption <- not actually planned to implement #clueless.admin.com = ExpireUTC 2008-08-20T18:22:14, HostNameMismatch # #Wildcard syntax #* = ExpireUTC 2008-08-20T18:22:14, SelfSigned #*.kdab.net = ExpireUTC 2008-08-20T18:22:14, SelfSigned #mail.kdab.net = ExpireUTC 2008-08-20T18:22:14, All <- not implemented #* = ExpireUTC 9999-12-31T23:59:59, Reject #we know that something is wrong with that certificate CertificatePEM = #host entries are all lowercase, thus no clashes */ // TODO GUI for managing exception rules class KSslCertificateRulePrivate { public: QSslCertificate certificate; QString hostName; bool isRejected; QDateTime expiryDateTime; QList ignoredErrors; }; KSslCertificateRule::KSslCertificateRule(const QSslCertificate &cert, const QString &hostName) : d(new KSslCertificateRulePrivate()) { d->certificate = cert; d->hostName = hostName; d->isRejected = false; } KSslCertificateRule::KSslCertificateRule(const KSslCertificateRule &other) : d(new KSslCertificateRulePrivate()) { *d = *other.d; } KSslCertificateRule::~KSslCertificateRule() { delete d; } KSslCertificateRule &KSslCertificateRule::operator=(const KSslCertificateRule &other) { *d = *other.d; return *this; } QSslCertificate KSslCertificateRule::certificate() const { return d->certificate; } QString KSslCertificateRule::hostName() const { return d->hostName; } void KSslCertificateRule::setExpiryDateTime(const QDateTime &dateTime) { d->expiryDateTime = dateTime; } QDateTime KSslCertificateRule::expiryDateTime() const { return d->expiryDateTime; } void KSslCertificateRule::setRejected(bool rejected) { d->isRejected = rejected; } bool KSslCertificateRule::isRejected() const { return d->isRejected; } bool KSslCertificateRule::isErrorIgnored(KSslError::Error error) const { return d->ignoredErrors.contains(KSslErrorPrivate::errorFromKSslError(error)); } bool KSslCertificateRule::isErrorIgnored(QSslError::SslError error) const { - return d->ignoredErrors.contains(error); + // ### temporary porting scafolding, remove the KSslError conversion roundtrip once kssld is ported + // until then we need to do this here to avoid comparing QSslErrors that haven't been reduced to the + // lower KSslError level of detail with the values stored as KSslError + //return d->ignoredErrors.contains(error); + return d->ignoredErrors.contains(KSslErrorPrivate::errorFromKSslError(KSslErrorPrivate::errorFromQSslError(error))); } void KSslCertificateRule::setIgnoredErrors(const QList &errors) { d->ignoredErrors.clear(); //### Quadratic runtime, woohoo! Use a QSet if that should ever be an issue. for (KSslError::Error e : errors) { QSslError::SslError error = KSslErrorPrivate::errorFromKSslError(e); if (!isErrorIgnored(error)) { d->ignoredErrors.append(error); } } } void KSslCertificateRule::setIgnoredErrors(const QList &errors) { QList el; el.reserve(errors.size()); for (const KSslError &e : errors) { el.append(e.error()); } setIgnoredErrors(el); } void KSslCertificateRule::setIgnoredErrors(const QList &errors) { d->ignoredErrors.clear(); for (const QSslError &error : errors) { if (!isErrorIgnored(error.error())) { d->ignoredErrors.append(error.error()); } } } QList KSslCertificateRule::ignoredErrors() const { // TODO KF6: return d->ignoredErrors // return d->ignoredErrors; QList errors; errors.reserve(d->ignoredErrors.size()); std::transform(d->ignoredErrors.cbegin(), d->ignoredErrors.cend(), std::back_inserter(errors), KSslErrorPrivate::errorFromQSslError); return errors; } QList KSslCertificateRule::filterErrors(const QList &errors) const { QList ret; for (KSslError::Error error : errors) { if (!isErrorIgnored(error)) { ret.append(error); } } return ret; } QList KSslCertificateRule::filterErrors(const QList &errors) const { QList ret; for (const KSslError &error : errors) { if (!isErrorIgnored(error.error())) { ret.append(error); } } return ret; } QList KSslCertificateRule::filterErrors(const QList &errors) const { QList ret; for (const QSslError &error : errors) { if (!isErrorIgnored(error.error())) { ret.append(error); } } return ret; } //////////////////////////////////////////////////////////////////// static QList deduplicate(const QList &certs) { QSet digests; QList ret; for (const QSslCertificate &cert : certs) { QByteArray digest = cert.digest(); if (!digests.contains(digest)) { digests.insert(digest); ret.append(cert); } } return ret; } KSslCertificateManagerPrivate::KSslCertificateManagerPrivate() : config(QStringLiteral("ksslcertificatemanager"), KConfig::SimpleConfig), iface(new org::kde::KSSLDInterface(QStringLiteral("org.kde.kssld5"), QStringLiteral("/modules/kssld"), QDBusConnection::sessionBus())), isCertListLoaded(false), userCertDir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/kssl/userCaCertificates/")) { } KSslCertificateManagerPrivate::~KSslCertificateManagerPrivate() { delete iface; iface = nullptr; } void KSslCertificateManagerPrivate::loadDefaultCaCertificates() { defaultCaCertificates.clear(); QList certs = deduplicate(QSslConfiguration::systemCaCertificates()); KConfig config(QStringLiteral("ksslcablacklist"), KConfig::SimpleConfig); KConfigGroup group = config.group("Blacklist of CA Certificates"); certs.append(QSslCertificate::fromPath(userCertDir + QLatin1Char('*'), QSsl::Pem, QRegExp::Wildcard)); for (const QSslCertificate &cert : qAsConst(certs)) { const QByteArray digest = cert.digest().toHex(); if (!group.hasKey(digest.constData())) { defaultCaCertificates += cert; } } isCertListLoaded = true; } bool KSslCertificateManagerPrivate::addCertificate(const KSslCaCertificate &in) { //qDebug() << Q_FUNC_INFO; // cannot add a certificate to the system store if (in.store == KSslCaCertificate::SystemStore) { Q_ASSERT(false); return false; } if (knownCerts.contains(in.certHash)) { Q_ASSERT(false); return false; } QString certFilename = userCertDir + QString::fromLatin1(in.certHash); QFile certFile(certFilename); if (!QDir().mkpath(userCertDir) || certFile.open(QIODevice::ReadOnly)) { return false; } if (!certFile.open(QIODevice::WriteOnly)) { return false; } if (certFile.write(in.cert.toPem()) < 1) { return false; } knownCerts.insert(in.certHash); updateCertificateBlacklisted(in); return true; } bool KSslCertificateManagerPrivate::removeCertificate(const KSslCaCertificate &old) { //qDebug() << Q_FUNC_INFO; // cannot remove a certificate from the system store if (old.store == KSslCaCertificate::SystemStore) { Q_ASSERT(false); return false; } if (!QFile::remove(userCertDir + QString::fromLatin1(old.certHash))) { // suppose somebody copied a certificate file into userCertDir without changing the // filename to the digest. // the rest of the code will work fine because it loads all certificate files from // userCertDir without asking for the name, we just can't remove the certificate using // its digest as filename - so search the whole directory. // if the certificate was added with the digest as name *and* with a different name, we // still fail to remove it completely at first try - BAD USER! BAD! bool removed = false; QDir dir(userCertDir); const QStringList dirList = dir.entryList(QDir::Files); for (const QString &certFilename : dirList) { const QString certPath = userCertDir + certFilename; QList certs = QSslCertificate::fromPath(certPath); if (!certs.isEmpty() && certs.at(0).digest().toHex() == old.certHash) { if (QFile::remove(certPath)) { removed = true; } else { // maybe the file is readable but not writable return false; } } } if (!removed) { // looks like the file is not there return false; } } // note that knownCerts *should* need no updating due to the way setAllCertificates() works - // it should never call addCertificate and removeCertificate for the same cert in one run // clean up the blacklist setCertificateBlacklisted(old.certHash, false); return true; } static bool certLessThan(const KSslCaCertificate &cacert1, const KSslCaCertificate &cacert2) { if (cacert1.store != cacert2.store) { // SystemStore is numerically smaller so the system certs come first; this is important // so that system certificates come first in case the user added an already-present // certificate as a user certificate. return cacert1.store < cacert2.store; } return cacert1.certHash < cacert2.certHash; } void KSslCertificateManagerPrivate::setAllCertificates(const QList &certsIn) { Q_ASSERT(knownCerts.isEmpty()); QList in = certsIn; QList old = allCertificates(); std::sort(in.begin(), in.end(), certLessThan); std::sort(old.begin(), old.end(), certLessThan); for (int ii = 0, oi = 0; ii < in.size() || oi < old.size(); ++ii, ++oi) { // look at all elements in both lists, even if we reach the end of one early. if (ii >= in.size()) { removeCertificate(old.at(oi)); continue; } else if (oi >= old.size()) { addCertificate(in.at(ii)); continue; } if (certLessThan(old.at(oi), in.at(ii))) { // the certificate in "old" is not in "in". only advance the index of "old". removeCertificate(old.at(oi)); ii--; } else if (certLessThan(in.at(ii), old.at(oi))) { // the certificate in "in" is not in "old". only advance the index of "in". addCertificate(in.at(ii)); oi--; } else { // in.at(ii) "==" old.at(oi) if (in.at(ii).cert != old.at(oi).cert) { // hash collision, be prudent(?) and don't do anything. } else { knownCerts.insert(old.at(oi).certHash); if (in.at(ii).isBlacklisted != old.at(oi).isBlacklisted) { updateCertificateBlacklisted(in.at(ii)); } } } } knownCerts.clear(); QMutexLocker certListLocker(&certListMutex); isCertListLoaded = false; loadDefaultCaCertificates(); } QList KSslCertificateManagerPrivate::allCertificates() const { //qDebug() << Q_FUNC_INFO; QList ret; const QList list = deduplicate(QSslConfiguration::systemCaCertificates()); for (const QSslCertificate &cert : list) { ret += KSslCaCertificate(cert, KSslCaCertificate::SystemStore, false); } const QList userList = QSslCertificate::fromPath(userCertDir + QLatin1Char('*'), QSsl::Pem, QRegExp::Wildcard); for (const QSslCertificate &cert : userList) { ret += KSslCaCertificate(cert, KSslCaCertificate::UserStore, false); } KConfig config(QStringLiteral("ksslcablacklist"), KConfig::SimpleConfig); KConfigGroup group = config.group("Blacklist of CA Certificates"); for (KSslCaCertificate &cert : ret) { if (group.hasKey(cert.certHash.constData())) { cert.isBlacklisted = true; //qDebug() << "is blacklisted"; } } return ret; } bool KSslCertificateManagerPrivate::updateCertificateBlacklisted(const KSslCaCertificate &cert) { return setCertificateBlacklisted(cert.certHash, cert.isBlacklisted); } bool KSslCertificateManagerPrivate::setCertificateBlacklisted(const QByteArray &certHash, bool isBlacklisted) { //qDebug() << Q_FUNC_INFO << isBlacklisted; KConfig config(QStringLiteral("ksslcablacklist"), KConfig::SimpleConfig); KConfigGroup group = config.group("Blacklist of CA Certificates"); if (isBlacklisted) { // TODO check against certificate list ? group.writeEntry(certHash.constData(), QString()); } else { if (!group.hasKey(certHash.constData())) { return false; } group.deleteEntry(certHash.constData()); } return true; } class KSslCertificateManagerContainer { public: KSslCertificateManager sslCertificateManager; }; Q_GLOBAL_STATIC(KSslCertificateManagerContainer, g_instance) KSslCertificateManager::KSslCertificateManager() : d(new KSslCertificateManagerPrivate()) { } KSslCertificateManager::~KSslCertificateManager() { delete d; } //static KSslCertificateManager *KSslCertificateManager::self() { return &g_instance()->sslCertificateManager; } void KSslCertificateManager::setRule(const KSslCertificateRule &rule) { d->iface->setRule(rule); } void KSslCertificateManager::clearRule(const KSslCertificateRule &rule) { d->iface->clearRule(rule); } void KSslCertificateManager::clearRule(const QSslCertificate &cert, const QString &hostName) { d->iface->clearRule(cert, hostName); } KSslCertificateRule KSslCertificateManager::rule(const QSslCertificate &cert, const QString &hostName) const { return d->iface->rule(cert, hostName); } QList KSslCertificateManager::caCertificates() const { QMutexLocker certLocker(&d->certListMutex); if (!d->isCertListLoaded) { d->loadDefaultCaCertificates(); } return d->defaultCaCertificates; } //static QList KSslCertificateManager::nonIgnorableErrors(const QList &/*e*/) { QList ret; // ### add filtering here... return ret; } //static QList KSslCertificateManager::nonIgnorableErrors(const QList &/*e*/) { QList ret; // ### add filtering here... return ret; } QList KSslCertificateManager::nonIgnorableErrors(const QList &errors) { Q_UNUSED(errors) // ### add filtering here... return {}; } QList _allKsslCaCertificates(KSslCertificateManager *cm) { return KSslCertificateManagerPrivate::get(cm)->allCertificates(); } void _setAllKsslCaCertificates(KSslCertificateManager *cm, const QList &certsIn) { KSslCertificateManagerPrivate::get(cm)->setAllCertificates(certsIn); } #include "moc_kssld_interface.cpp" diff --git a/src/core/ksslerroruidata.cpp b/src/core/ksslerroruidata.cpp index 749afa57..515e1192 100644 --- a/src/core/ksslerroruidata.cpp +++ b/src/core/ksslerroruidata.cpp @@ -1,102 +1,94 @@ /* This file is part of the KDE libraries Copyright (C) 2007, 2008 Andreas Hartmetz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ksslerroruidata.h" #include "ksslerroruidata_p.h" #include "ktcpsocket.h" #include #include #include KSslErrorUiData::KSslErrorUiData() : d(new Private()) { d->usedBits = 0; d->bits = 0; } KSslErrorUiData::KSslErrorUiData(const KTcpSocket *socket) : d(new Private()) { d->certificateChain = socket->peerCertificateChain(); - d->sslErrors = socket->sslErrors(); + const auto ksslErrors = socket->sslErrors(); + d->sslErrors.reserve(ksslErrors.size()); + for (const auto &error : ksslErrors) { + d->sslErrors.push_back(error.sslError()); + } d->ip = socket->peerAddress().toString(); d->host = socket->peerName(); d->sslProtocol = socket->negotiatedSslVersionName(); d->cipher = socket->sessionCipher().name(); d->usedBits = socket->sessionCipher().usedBits(); d->bits = socket->sessionCipher().supportedBits(); } KSslErrorUiData::KSslErrorUiData(const QSslSocket *socket) : d(new Private()) { d->certificateChain = socket->peerCertificateChain(); - - // See KTcpSocket::sslErrors() - const auto qsslErrors = socket->sslErrors(); - d->sslErrors.reserve(qsslErrors.size()); - for (const QSslError &e : qsslErrors) { - d->sslErrors.append(KSslError(e)); - } - + d->sslErrors = socket->sslErrors(); d->ip = socket->peerAddress().toString(); d->host = socket->peerName(); if (socket->isEncrypted()) { d->sslProtocol = socket->sessionCipher().protocolString(); } d->cipher = socket->sessionCipher().name(); d->usedBits = socket->sessionCipher().usedBits(); d->bits = socket->sessionCipher().supportedBits(); } KSslErrorUiData::KSslErrorUiData(const QNetworkReply *reply, const QList &sslErrors) : d(new Private()) { const auto sslConfig = reply->sslConfiguration(); d->certificateChain = sslConfig.peerCertificateChain(); - - d->sslErrors.reserve(sslErrors.size()); - for (const QSslError &e : sslErrors) { - d->sslErrors.append(KSslError(e)); - } - + d->sslErrors = sslErrors; d->host = reply->request().url().host(); d->sslProtocol = sslConfig.sessionCipher().protocolString(); d->cipher = sslConfig.sessionCipher().name(); d->usedBits = sslConfig.sessionCipher().usedBits(); d->bits = sslConfig.sessionCipher().supportedBits(); } KSslErrorUiData::KSslErrorUiData(const KSslErrorUiData &other) : d(new Private(*other.d)) {} KSslErrorUiData::~KSslErrorUiData() { delete d; } KSslErrorUiData &KSslErrorUiData::operator=(const KSslErrorUiData &other) { *d = *other.d; return *this; } diff --git a/src/core/ksslerroruidata_p.h b/src/core/ksslerroruidata_p.h index 25e644b0..844d4fec 100644 --- a/src/core/ksslerroruidata_p.h +++ b/src/core/ksslerroruidata_p.h @@ -1,47 +1,47 @@ /* This file is part of the KDE libraries Copyright (C) 2009 Andreas Hartmetz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KSSLERRORUIDATA_P_H #define KSSLERRORUIDATA_P_H #include "ksslerroruidata.h" -#include "ktcpsocket.h" #include +#include #include class Q_DECL_HIDDEN KSslErrorUiData::Private { public: static const KSslErrorUiData::Private *get(const KSslErrorUiData *uiData) { return uiData->d; } QList certificateChain; - QList sslErrors; // parallel list to certificateChain + QList sslErrors; // parallel list to certificateChain QString ip; QString host; QString sslProtocol; QString cipher; int usedBits; int bits; }; #endif // KSSLERRORUIDATA_P_H diff --git a/src/widgets/sslui.cpp b/src/widgets/sslui.cpp index 0d103a14..ae8516bc 100644 --- a/src/widgets/sslui.cpp +++ b/src/widgets/sslui.cpp @@ -1,135 +1,135 @@ /* This file is part of the KDE project * * Copyright (C) 2009 Andreas Hartmetz * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "sslui.h" #include #include #include #include #include bool KIO::SslUi::askIgnoreSslErrors(const KTcpSocket *socket, RulesStorage storedRules) { KSslErrorUiData uiData(socket); return askIgnoreSslErrors(uiData, storedRules); } bool KIO::SslUi::askIgnoreSslErrors(const KSslErrorUiData &uiData, RulesStorage storedRules) { const KSslErrorUiData::Private *ud = KSslErrorUiData::Private::get(&uiData); if (ud->sslErrors.isEmpty()) { return true; } - QList fatalErrors = KSslCertificateManager::nonIgnorableErrors(ud->sslErrors); + const QList fatalErrors = KSslCertificateManager::nonIgnorableErrors(ud->sslErrors); if (!fatalErrors.isEmpty()) { //TODO message "sorry, fatal error, you can't override it" return false; } if (ud->certificateChain.isEmpty()) { // SSL without certificates is quite useless and should never happen KMessageBox::sorry(nullptr, i18n("The remote host did not send any SSL certificates.\n" "Aborting because the identity of the host cannot be established.")); return false; } KSslCertificateManager *const cm = KSslCertificateManager::self(); KSslCertificateRule rule(ud->certificateChain.first(), ud->host); if (storedRules & RecallRules) { rule = cm->rule(ud->certificateChain.first(), ud->host); // remove previously seen and acknowledged errors - QList remainingErrors = rule.filterErrors(ud->sslErrors); + const QList remainingErrors = rule.filterErrors(ud->sslErrors); if (remainingErrors.isEmpty()) { //qDebug() << "Error list empty after removing errors to be ignored. Continuing."; return true; } } //### We don't ask to permanently reject the certificate QString message = i18n("The server failed the authenticity check (%1).\n\n", ud->host); - for (const KSslError &err : qAsConst(ud->sslErrors)) { + for (const QSslError &err : qAsConst(ud->sslErrors)) { message.append(err.errorString() + QLatin1Char('\n')); } message = message.trimmed(); int msgResult; do { msgResult = KMessageBox::warningYesNoCancel(nullptr, message, i18n("Server Authentication"), KGuiItem(i18n("&Details"), QStringLiteral("help-about")), KGuiItem(i18n("Co&ntinue"), QStringLiteral("arrow-right"))); if (msgResult == KMessageBox::Yes) { //Details was chosen - show the certificate and error details QList > meh; // parallel list to cert list :/ meh.reserve(ud->certificateChain.size()); for (const QSslCertificate &cert : qAsConst(ud->certificateChain)) { QList errors; for (const KSslError &error : qAsConst(ud->sslErrors)) { if (error.certificate() == cert) { // we keep only the error code enum here errors.append(error.error()); } } meh.append(errors); } KSslInfoDialog *dialog = new KSslInfoDialog(); dialog->setSslInfo(ud->certificateChain, ud->ip, ud->host, ud->sslProtocol, ud->cipher, ud->usedBits, ud->bits, meh); dialog->exec(); } else if (msgResult == KMessageBox::Cancel) { return false; } //fall through on KMessageBox::No } while (msgResult == KMessageBox::Yes); if (storedRules & StoreRules) { //Save the user's choice to ignore the SSL errors. msgResult = KMessageBox::warningYesNo(nullptr, i18n("Would you like to accept this " "certificate forever without " "being prompted?"), i18n("Server Authentication"), KGuiItem(i18n("&Forever"), QStringLiteral("flag-green")), KGuiItem(i18n("&Current Session only"), QStringLiteral("chronometer"))); QDateTime ruleExpiry = QDateTime::currentDateTime(); if (msgResult == KMessageBox::Yes) { //accept forever ("for a very long time") ruleExpiry = ruleExpiry.addYears(1000); } else { //accept "for a short time", half an hour. ruleExpiry = ruleExpiry.addSecs(30 * 60); } //TODO special cases for wildcard domain name in the certificate! //rule = KSslCertificateRule(d->socket.peerCertificateChain().first(), whatever); rule.setExpiryDateTime(ruleExpiry); rule.setIgnoredErrors(ud->sslErrors); cm->setRule(rule); } return true; }