diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 00aff922..a9108337 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1,294 +1,296 @@ project(KIOCore) include (ConfigureChecks.cmake) configure_file(config-kiocore.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kiocore.h ) configure_file(config-kmountpoint.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kmountpoint.h) # KSSL_HAVE_SSL only used in kssl/ksslsettings.cpp, but currently ifdefed out #find_package(OpenSSL) #set_package_properties(OpenSSL PROPERTIES DESCRIPTION "Support for secure network communications (SSL and TLS)" # URL "http://openssl.org" # TYPE RECOMMENDED # PURPOSE "KDE uses OpenSSL for the bulk of secure communications, including secure web browsing via HTTPS" # ) #if(OPENSSL_FOUND) # set(KSSL_HAVE_SSL 1) # include_directories(${OPENSSL_INCLUDE_DIR}) #endif() set(kiocore_SRCS idleslave.cpp klocalsocket.cpp connectionbackend.cpp connection.cpp connectionserver.cpp krecentdocument.cpp kfileitemlistproperties.cpp tcpslavebase.cpp directorysizejob.cpp forwardingslavebase.cpp chmodjob.cpp kdiskfreespaceinfo.cpp usernotificationhandler.cpp ksambasharedata.cpp ksambashare.cpp knfsshare.cpp kfileitem.cpp davjob.cpp deletejob.cpp copyjob.cpp filejob.cpp mkdirjob.cpp mkpathjob.cpp kpasswdserverloop.cpp kpasswdserverclient.cpp kremoteencoding.cpp sessiondata.cpp slavebase.cpp dataslave.cpp dataprotocol.cpp desktopexecparser.cpp emptytrashjob.cpp authinfo.cpp slaveinterface.cpp slave.cpp job_error.cpp job.cpp filecopyjob.cpp listjob.cpp mimetypejob.cpp multigetjob.cpp restorejob.cpp simplejob.cpp specialjob.cpp statjob.cpp storedtransferjob.cpp transferjob.cpp filesystemfreespacejob.cpp scheduler.cpp slaveconfig.cpp kprotocolmanager.cpp hostinfo.cpp kdirnotify.cpp kurlauthorized.cpp kacl.cpp udsentry.cpp global.cpp metadata.cpp kprotocolinfo.cpp kprotocolinfofactory.cpp jobtracker.cpp jobuidelegateextension.cpp jobuidelegatefactory.cpp kmountpoint.cpp kcoredirlister.cpp faviconscache.cpp ksslcertificatemanager.cpp + ksslerroruidata.cpp ktcpsocket.cpp kssl/ksslsettings.cpp kioglobal_p.cpp batchrenamejob.cpp ) ecm_qt_declare_logging_category(kiocore_SRCS HEADER kiocoredebug.h IDENTIFIER KIO_CORE CATEGORY_NAME kf5.kio.core) if (UNIX) set(kiocore_SRCS ${kiocore_SRCS} klocalsocket_unix.cpp kioglobal_p_unix.cpp ) endif() if (WIN32) set(kiocore_SRCS ${kiocore_SRCS} klocalsocket_win.cpp kioglobal_p_win.cpp ) endif() qt5_add_dbus_interface(kiocore_SRCS org.kde.KSlaveLauncher.xml klauncher_interface) set_source_files_properties(org.kde.KPasswdServer.xml PROPERTIES INCLUDE authinfo.h ) qt5_add_dbus_interface(kiocore_SRCS org.kde.KPasswdServer.xml kpasswdserver_interface) install(FILES org.kde.KDirNotify.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR} RENAME kf5_org.kde.KDirNotify.xml) install(FILES org.kde.KPasswdServer.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR} RENAME kf5_org.kde.KPasswdServer.xml) install(FILES org.kde.KSlaveLauncher.xml DESTINATION ${KDE_INSTALL_DBUSINTERFACEDIR} RENAME kf5_org.kde.KSlaveLauncher.xml) add_library(KF5KIOCore ${kiocore_SRCS}) generate_export_header(KF5KIOCore BASE_NAME KIOCore) add_library(KF5::KIOCore ALIAS KF5KIOCore) target_include_directories(KF5KIOCore PUBLIC "$" # kio_version.h "$" ) target_include_directories(KF5KIOCore INTERFACE "$") target_link_libraries(KF5KIOCore PUBLIC KF5::CoreAddons # KJob KF5::Service Qt5::Network Qt5::Concurrent # QtConcurrentRun in hostinfo.cpp Qt5::DBus PRIVATE Qt5::Xml # davjob.cpp uses QDom KF5::I18n KF5::Crash KF5::DBusAddons # KDEInitInterface ) if (UNIX) target_link_libraries(KF5KIOCore PRIVATE KF5::AuthCore) #SlaveBase uses KAuth::Action endif() if(ACL_FOUND) target_link_libraries(KF5KIOCore PRIVATE ${ACL_LIBS}) endif() set_target_properties(KF5KIOCore PROPERTIES VERSION ${KIO_VERSION_STRING} SOVERSION ${KIO_SOVERSION} EXPORT_NAME KIOCore ) # this should be done by cmake, see bug 371721 if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND Qt5Core_VERSION VERSION_GREATER 5.8.0) add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/moc_predefs.h COMMAND "${CMAKE_CXX_COMPILER}" "-dM" "-E" "-c" "${CMAKE_ROOT}/Modules/CMakeCXXCompilerABI.cpp" > ${CMAKE_CURRENT_BINARY_DIR}/moc_predefs.h ) set_property(TARGET KF5KIOCore APPEND PROPERTY AUTOMOC_MOC_OPTIONS --include ${CMAKE_CURRENT_BINARY_DIR}/moc_predefs.h) set_property(TARGET KF5KIOCore APPEND PROPERTY AUTOGEN_TARGET_DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/moc_predefs.h) endif() # Headers prefixed with KIO/ ecm_generate_headers(KIOCore_CamelCase_HEADERS HEADER_NAMES IdleSlave ConnectionServer TCPSlaveBase DirectorySizeJob ForwardingSlaveBase Job # ### should forward to job_base.h, not job.h... JobTracker Global ChmodJob DeleteJob CopyJob EmptyTrashJob FileJob MkdirJob MkpathJob SlaveBase SlaveConfig HostInfo MetaData UDSEntry JobUiDelegateExtension JobUiDelegateFactory SlaveInterface Slave FileCopyJob ListJob MimetypeJob MultiGetJob RestoreJob SimpleJob SpecialJob StatJob StoredTransferJob TransferJob Scheduler AuthInfo DavJob DesktopExecParser FileSystemFreeSpaceJob BatchRenameJob PREFIX KIO REQUIRED_HEADERS KIO_namespaced_HEADERS ) # Create local forwarding header for kio/job_base.h set(REGULAR_HEADER_NAME ${CMAKE_CURRENT_BINARY_DIR}/kio/job_base.h) if (NOT EXISTS ${REGULAR_HEADER_NAME}) file(WRITE ${REGULAR_HEADER_NAME} "#include \"${CMAKE_CURRENT_SOURCE_DIR}/job_base.h\"\n") endif() install(TARGETS KF5KIOCore EXPORT KF5KIOTargets ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) list(APPEND KIO_namespaced_HEADERS http_slave_defaults.h ioslave_defaults.h job_base.h jobclasses.h ) install(FILES ${KIO_namespaced_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KIOCore/kio COMPONENT Devel ) # Headers not prefixed with KIO/ ecm_generate_headers(KIOCore_HEADERS HEADER_NAMES KACL KUrlAuthorized KCoreDirLister KRemoteEncoding KDirNotify KDiskFreeSpaceInfo KFileItem KFileItemListProperties KMountPoint KNFSShare KSambaShare KSambaShareData KPasswdServerClient KProtocolInfo KProtocolManager KRecentDocument KSslCertificateManager + KSslErrorUiData KTcpSocket REQUIRED_HEADERS KIOCore_HEADERS ) ecm_generate_headers(KIOCore_HEADERS HEADER_NAMES KSSLSettings RELATIVE kssl REQUIRED_HEADERS KIOCore_HEADERS ) install(FILES ${KIOCore_CamelCase_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KIOCore/KIO COMPONENT Devel) install(FILES ksslcertificatemanager_p.h ${KIOCore_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/kiocore_export.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF5}/KIOCore COMPONENT Devel) install(FILES accept-languages.codes DESTINATION ${KDE_INSTALL_CONFDIR}) # make available to ecm_add_qch in parent folder set(KIOCore_QCH_SOURCES ${KIOCore_HEADERS} ${KIO_namespaced_HEADERS} PARENT_SCOPE) include(ECMGeneratePriFile) ecm_generate_pri_file(BASE_NAME KIOCore LIB_NAME KF5KIOCore DEPS "KCoreAddons KService" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KDE_INSTALL_INCLUDEDIR_KF5}/KIOCore) install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) diff --git a/src/core/ksslcertificatemanager.cpp b/src/core/ksslcertificatemanager.cpp index f00b3447..f2ee3670 100644 --- a/src/core/ksslcertificatemanager.cpp +++ b/src/core/ksslcertificatemanager.cpp @@ -1,498 +1,498 @@ /* 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 "ktcpsocket.h" -#include "ktcpsocket_p.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 { foreach (KSslError::Error ignoredError, d->ignoredErrors) if (error == ignoredError) { return true; } return false; } 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) if (!isErrorIgnored(e)) { d->ignoredErrors.append(e); } } void KSslCertificateRule::setIgnoredErrors(const QList &errors) { QList el; el.reserve(errors.size()); for (const KSslError &e : errors) { el.append(e.error()); } setIgnoredErrors(el); } QList KSslCertificateRule::ignoredErrors() const { return d->ignoredErrors; } 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; } //////////////////////////////////////////////////////////////////// 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(QSslSocket::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); foreach (const QString &certFilename, dir.entryList(QDir::Files)) { 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; foreach (const QSslCertificate &cert, deduplicate(QSslSocket::systemCaCertificates())) { ret += KSslCaCertificate(cert, KSslCaCertificate::SystemStore, false); } foreach (const QSslCertificate &cert, QSslCertificate::fromPath(userCertDir + QLatin1Char('*'), QSsl::Pem, QRegExp::Wildcard)) { ret += KSslCaCertificate(cert, KSslCaCertificate::UserStore, false); } KConfig config(QStringLiteral("ksslcablacklist"), KConfig::SimpleConfig); KConfigGroup group = config.group("Blacklist of CA Certificates"); for (int i = 0; i < ret.size(); i++) { if (group.hasKey(ret[i].certHash.constData())) { ret[i].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 _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 new file mode 100644 index 00000000..749afa57 --- /dev/null +++ b/src/core/ksslerroruidata.cpp @@ -0,0 +1,102 @@ +/* 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(); + 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->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->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.h b/src/core/ksslerroruidata.h new file mode 100644 index 00000000..b5e41d68 --- /dev/null +++ b/src/core/ksslerroruidata.h @@ -0,0 +1,74 @@ +/* This file is part of the KDE libraries + Copyright (C) 2007 Thiago Macieira + Copyright (C) 2007 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_H +#define KSSLERRORUIDATA_H + +#include + +template class QList; +class KTcpSocket; +class QNetworkReply; +class QSslError; +class QSslSocket; + +/** + * This class can hold all the necessary data from a KTcpSocket to ask the user + * to continue connecting in the face of SSL errors. + * It can be used to carry the data for the UI over time or over thread boundaries. + * + * @see: KSslCertificateManager::askIgnoreSslErrors() + */ +class KIOCORE_EXPORT KSslErrorUiData +{ +public: + /** + * Default construct an instance with no useful data. + */ + KSslErrorUiData(); + /** + * Create an instance and initialize it with SSL error data from @p socket. + */ + KSslErrorUiData(const KTcpSocket *socket); + /** + * Create an instance and initialize it with SSL error data from @p socket. + */ + KSslErrorUiData(const QSslSocket *socket); + /** + * Create an instance and initialize it with SSL error data from @p reply. + * @since 5.62 + */ + KSslErrorUiData(const QNetworkReply *reply, const QList &sslErrors); + + KSslErrorUiData(const KSslErrorUiData &other); + KSslErrorUiData &operator=(const KSslErrorUiData &); + /** + * Destructor + * @since 4.7 + */ + ~KSslErrorUiData(); + + class Private; +private: + friend class Private; + Private *const d; +}; + +#endif // KSSLERRORUIDATA_H diff --git a/src/core/ktcpsocket_p.h b/src/core/ksslerroruidata_p.h similarity index 87% rename from src/core/ktcpsocket_p.h rename to src/core/ksslerroruidata_p.h index 36c751b2..25e644b0 100644 --- a/src/core/ktcpsocket_p.h +++ b/src/core/ksslerroruidata_p.h @@ -1,41 +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 KTCPSOCKET_P_H -#define KTCPSOCKET_P_H +#ifndef KSSLERRORUIDATA_P_H +#define KSSLERRORUIDATA_P_H + +#include "ksslerroruidata.h" +#include "ktcpsocket.h" + +#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 QString ip; QString host; QString sslProtocol; QString cipher; int usedBits; int bits; }; -#endif // KTCPSOCKET_P_H +#endif // KSSLERRORUIDATA_P_H diff --git a/src/core/ktcpsocket.cpp b/src/core/ktcpsocket.cpp index 2ff652be..2fd5bfe9 100644 --- a/src/core/ktcpsocket.cpp +++ b/src/core/ktcpsocket.cpp @@ -1,1134 +1,1057 @@ /* 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 "ktcpsocket.h" -#include "ktcpsocket_p.h" #include "kiocoredebug.h" #include #include #include #include #include #include #include -#include #include static KTcpSocket::SslVersion kSslVersionFromQ(QSsl::SslProtocol protocol) { switch (protocol) { case QSsl::SslV2: return KTcpSocket::SslV2; case QSsl::SslV3: return KTcpSocket::SslV3; case QSsl::TlsV1_0: return KTcpSocket::TlsV1; case QSsl::TlsV1_1: return KTcpSocket::TlsV1_1; case QSsl::TlsV1_2: return KTcpSocket::TlsV1_2; #if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) case QSsl::TlsV1_3: return KTcpSocket::TlsV1_3; #endif case QSsl::AnyProtocol: return KTcpSocket::AnySslVersion; case QSsl::TlsV1SslV3: return KTcpSocket::TlsV1SslV3; case QSsl::SecureProtocols: return KTcpSocket::SecureProtocols; default: return KTcpSocket::UnknownSslVersion; } } static QSsl::SslProtocol qSslProtocolFromK(KTcpSocket::SslVersion sslVersion) { //### this lowlevel bit-banging is a little dangerous and a likely source of bugs if (sslVersion == KTcpSocket::AnySslVersion) { return QSsl::AnyProtocol; } //does it contain any valid protocol? KTcpSocket::SslVersions validVersions(KTcpSocket::SslV2 | KTcpSocket::SslV3 | KTcpSocket::TlsV1); validVersions |= KTcpSocket::TlsV1_1; validVersions |= KTcpSocket::TlsV1_2; #if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) validVersions |= KTcpSocket::TlsV1_3; #endif validVersions |= KTcpSocket::TlsV1SslV3; validVersions |= KTcpSocket::SecureProtocols; if (!(sslVersion & validVersions)) { return QSsl::UnknownProtocol; } switch (sslVersion) { case KTcpSocket::SslV2: return QSsl::SslV2; case KTcpSocket::SslV3: return QSsl::SslV3; case KTcpSocket::TlsV1_0: return QSsl::TlsV1_0; case KTcpSocket::TlsV1_1: return QSsl::TlsV1_1; case KTcpSocket::TlsV1_2: return QSsl::TlsV1_2; #if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) case KTcpSocket::TlsV1_3: return QSsl::TlsV1_3; #endif case KTcpSocket::TlsV1SslV3: return QSsl::TlsV1SslV3; case KTcpSocket::SecureProtocols: return QSsl::SecureProtocols; default: //QSslSocket doesn't really take arbitrary combinations. It's one or all. return QSsl::AnyProtocol; } } static QString protocolString(QSsl::SslProtocol protocol) { switch (protocol) { case QSsl::SslV2: return QStringLiteral("SSLv2"); case QSsl::SslV3: return QStringLiteral("SSLv3"); case QSsl::TlsV1_0: return QStringLiteral("TLSv1.0"); case QSsl::TlsV1_1: return QStringLiteral("TLSv1.1"); case QSsl::TlsV1_2: return QStringLiteral("TLSv1.2"); #if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) case QSsl::TlsV1_3: return QStringLiteral("TLSv1.3"); #endif default: return QStringLiteral("Unknown");; } } //cipher class converter KSslCipher -> QSslCipher class CipherCc { public: CipherCc() { foreach (const QSslCipher &c, QSslConfiguration::supportedCiphers()) { allCiphers.insert(c.name(), c); } } QSslCipher converted(const KSslCipher &ksc) { return allCiphers.value(ksc.name()); } private: QHash allCiphers; }; class KSslErrorPrivate { public: static KSslError::Error errorFromQSslError(QSslError::SslError e) { switch (e) { case QSslError::NoError: return KSslError::NoError; case QSslError::UnableToGetLocalIssuerCertificate: case QSslError::InvalidCaCertificate: return KSslError::InvalidCertificateAuthorityCertificate; case QSslError::InvalidNotBeforeField: case QSslError::InvalidNotAfterField: case QSslError::CertificateNotYetValid: case QSslError::CertificateExpired: return KSslError::ExpiredCertificate; case QSslError::UnableToDecodeIssuerPublicKey: case QSslError::SubjectIssuerMismatch: case QSslError::AuthorityIssuerSerialNumberMismatch: return KSslError::InvalidCertificate; case QSslError::SelfSignedCertificate: case QSslError::SelfSignedCertificateInChain: return KSslError::SelfSignedCertificate; case QSslError::CertificateRevoked: return KSslError::RevokedCertificate; case QSslError::InvalidPurpose: return KSslError::InvalidCertificatePurpose; case QSslError::CertificateUntrusted: return KSslError::UntrustedCertificate; case QSslError::CertificateRejected: return KSslError::RejectedCertificate; case QSslError::NoPeerCertificate: return KSslError::NoPeerCertificate; case QSslError::HostNameMismatch: return KSslError::HostNameMismatch; case QSslError::UnableToVerifyFirstCertificate: case QSslError::UnableToDecryptCertificateSignature: case QSslError::UnableToGetIssuerCertificate: case QSslError::CertificateSignatureFailed: return KSslError::CertificateSignatureFailed; case QSslError::PathLengthExceeded: return KSslError::PathLengthExceeded; case QSslError::UnspecifiedError: case QSslError::NoSslSupport: default: return KSslError::UnknownError; } } static QString errorString(KSslError::Error e) { switch (e) { case KSslError::NoError: return i18nc("SSL error", "No error"); case KSslError::InvalidCertificateAuthorityCertificate: return i18nc("SSL error", "The certificate authority's certificate is invalid"); case KSslError::ExpiredCertificate: return i18nc("SSL error", "The certificate has expired"); case KSslError::InvalidCertificate: return i18nc("SSL error", "The certificate is invalid"); case KSslError::SelfSignedCertificate: return i18nc("SSL error", "The certificate is not signed by any trusted certificate authority"); case KSslError::RevokedCertificate: return i18nc("SSL error", "The certificate has been revoked"); case KSslError::InvalidCertificatePurpose: return i18nc("SSL error", "The certificate is unsuitable for this purpose"); case KSslError::UntrustedCertificate: return i18nc("SSL error", "The root certificate authority's certificate is not trusted for this purpose"); case KSslError::RejectedCertificate: return i18nc("SSL error", "The certificate authority's certificate is marked to reject this certificate's purpose"); case KSslError::NoPeerCertificate: return i18nc("SSL error", "The peer did not present any certificate"); case KSslError::HostNameMismatch: return i18nc("SSL error", "The certificate does not apply to the given host"); case KSslError::CertificateSignatureFailed: return i18nc("SSL error", "The certificate cannot be verified for internal reasons"); case KSslError::PathLengthExceeded: return i18nc("SSL error", "The certificate chain is too long"); case KSslError::UnknownError: default: return i18nc("SSL error", "Unknown error"); } } KSslError::Error error; QSslCertificate certificate; }; KSslError::KSslError(Error errorCode, const QSslCertificate &certificate) : d(new KSslErrorPrivate()) { d->error = errorCode; d->certificate = certificate; } KSslError::KSslError(const QSslError &other) : d(new KSslErrorPrivate()) { d->error = KSslErrorPrivate::errorFromQSslError(other.error()); d->certificate = other.certificate(); } KSslError::KSslError(const KSslError &other) : d(new KSslErrorPrivate()) { *d = *other.d; } KSslError::~KSslError() { delete d; } KSslError &KSslError::operator=(const KSslError &other) { *d = *other.d; return *this; } KSslError::Error KSslError::error() const { return d->error; } QString KSslError::errorString() const { return KSslErrorPrivate::errorString(d->error); } QSslCertificate KSslError::certificate() const { return d->certificate; } class KTcpSocketPrivate { public: explicit KTcpSocketPrivate(KTcpSocket *qq) : q(qq), certificatesLoaded(false), emittedReadyRead(false) { // create the instance, which sets Qt's static internal cert set to empty. KSslCertificateManager::self(); } KTcpSocket::State state(QAbstractSocket::SocketState s) { switch (s) { case QAbstractSocket::UnconnectedState: return KTcpSocket::UnconnectedState; case QAbstractSocket::HostLookupState: return KTcpSocket::HostLookupState; case QAbstractSocket::ConnectingState: return KTcpSocket::ConnectingState; case QAbstractSocket::ConnectedState: return KTcpSocket::ConnectedState; case QAbstractSocket::ClosingState: return KTcpSocket::ClosingState; case QAbstractSocket::BoundState: case QAbstractSocket::ListeningState: //### these two are not relevant as long as this can't be a server socket default: return KTcpSocket::UnconnectedState; //the closest to "error" } } KTcpSocket::EncryptionMode encryptionMode(QSslSocket::SslMode mode) { switch (mode) { case QSslSocket::SslClientMode: return KTcpSocket::SslClientMode; case QSslSocket::SslServerMode: return KTcpSocket::SslServerMode; default: return KTcpSocket::UnencryptedMode; } } KTcpSocket::Error errorFromAbsSocket(QAbstractSocket::SocketError e) { switch (e) { case QAbstractSocket::ConnectionRefusedError: return KTcpSocket::ConnectionRefusedError; case QAbstractSocket::RemoteHostClosedError: return KTcpSocket::RemoteHostClosedError; case QAbstractSocket::HostNotFoundError: return KTcpSocket::HostNotFoundError; case QAbstractSocket::SocketAccessError: return KTcpSocket::SocketAccessError; case QAbstractSocket::SocketResourceError: return KTcpSocket::SocketResourceError; case QAbstractSocket::SocketTimeoutError: return KTcpSocket::SocketTimeoutError; case QAbstractSocket::NetworkError: return KTcpSocket::NetworkError; case QAbstractSocket::UnsupportedSocketOperationError: return KTcpSocket::UnsupportedSocketOperationError; case QAbstractSocket::SslHandshakeFailedError: return KTcpSocket::SslHandshakeFailedError; case QAbstractSocket::DatagramTooLargeError: //we don't do UDP case QAbstractSocket::AddressInUseError: case QAbstractSocket::SocketAddressNotAvailableError: //### own values if/when we ever get server socket support case QAbstractSocket::ProxyAuthenticationRequiredError: //### maybe we need an enum value for this case QAbstractSocket::UnknownSocketError: default: return KTcpSocket::UnknownError; } } //private slots void reemitSocketError(QAbstractSocket::SocketError e) { q->setErrorString(sock.errorString()); emit q->error(errorFromAbsSocket(e)); } void reemitSslErrors(const QList &errors) { q->setErrorString(sock.errorString()); q->showSslErrors(); //H4X QList kErrors; kErrors.reserve(errors.size()); for (const QSslError &e : errors) { kErrors.append(KSslError(e)); } emit q->sslErrors(kErrors); } void reemitStateChanged(QAbstractSocket::SocketState s) { emit q->stateChanged(state(s)); } void reemitModeChanged(QSslSocket::SslMode m) { emit q->encryptionModeChanged(encryptionMode(m)); } // This method is needed because we might emit readyRead() due to this QIODevice // having some data buffered, so we need to care about blocking, too. //### useless ATM as readyRead() now just calls d->sock.readyRead(). void reemitReadyRead() { if (!emittedReadyRead) { emittedReadyRead = true; emit q->readyRead(); emittedReadyRead = false; } } void maybeLoadCertificates() { if (!certificatesLoaded) { q->setCaCertificates(KSslCertificateManager::self()->caCertificates()); } } KTcpSocket *const q; bool certificatesLoaded; bool emittedReadyRead; QSslSocket sock; QList ciphers; KTcpSocket::SslVersion advertisedSslVersion; CipherCc ccc; }; KTcpSocket::KTcpSocket(QObject *parent) : QIODevice(parent), d(new KTcpSocketPrivate(this)) { d->advertisedSslVersion = SslV3; connect(&d->sock, &QIODevice::aboutToClose, this, &QIODevice::aboutToClose); connect(&d->sock, &QIODevice::bytesWritten, this, &QIODevice::bytesWritten); connect(&d->sock, &QSslSocket::encryptedBytesWritten, this, &KTcpSocket::encryptedBytesWritten); connect(&d->sock, SIGNAL(readyRead()), this, SLOT(reemitReadyRead())); connect(&d->sock, &QAbstractSocket::connected, this, &KTcpSocket::connected); connect(&d->sock, &QSslSocket::encrypted, this, &KTcpSocket::encrypted); connect(&d->sock, &QAbstractSocket::disconnected, this, &KTcpSocket::disconnected); #ifndef QT_NO_NETWORKPROXY connect(&d->sock, &QAbstractSocket::proxyAuthenticationRequired, this, &KTcpSocket::proxyAuthenticationRequired); #endif connect(&d->sock, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(reemitSocketError(QAbstractSocket::SocketError))); connect(&d->sock, SIGNAL(sslErrors(QList)), this, SLOT(reemitSslErrors(QList))); connect(&d->sock, &QAbstractSocket::hostFound, this, &KTcpSocket::hostFound); connect(&d->sock, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(reemitStateChanged(QAbstractSocket::SocketState))); connect(&d->sock, SIGNAL(modeChanged(QSslSocket::SslMode)), this, SLOT(reemitModeChanged(QSslSocket::SslMode))); } KTcpSocket::~KTcpSocket() { delete d; } ////////////////////////////// (mostly) virtuals from QIODevice bool KTcpSocket::atEnd() const { return d->sock.atEnd() && QIODevice::atEnd(); } qint64 KTcpSocket::bytesAvailable() const { return d->sock.bytesAvailable() + QIODevice::bytesAvailable(); } qint64 KTcpSocket::bytesToWrite() const { return d->sock.bytesToWrite(); } bool KTcpSocket::canReadLine() const { return d->sock.canReadLine() || QIODevice::canReadLine(); } void KTcpSocket::close() { d->sock.close(); QIODevice::close(); } bool KTcpSocket::isSequential() const { return true; } bool KTcpSocket::open(QIODevice::OpenMode open) { bool ret = d->sock.open(open); setOpenMode(d->sock.openMode() | QIODevice::Unbuffered); return ret; } bool KTcpSocket::waitForBytesWritten(int msecs) { return d->sock.waitForBytesWritten(msecs); } bool KTcpSocket::waitForReadyRead(int msecs) { return d->sock.waitForReadyRead(msecs); } qint64 KTcpSocket::readData(char *data, qint64 maxSize) { return d->sock.read(data, maxSize); } qint64 KTcpSocket::writeData(const char *data, qint64 maxSize) { return d->sock.write(data, maxSize); } ////////////////////////////// public methods from QAbstractSocket void KTcpSocket::abort() { d->sock.abort(); } void KTcpSocket::connectToHost(const QString &hostName, quint16 port, ProxyPolicy policy) { if (policy == AutoProxy) { //### } d->sock.connectToHost(hostName, port); // there are enough layers of buffers between us and the network, and there is a quirk // in QIODevice that can make it try to readData() twice per read() call if buffered and // reaData() does not deliver enough data the first time. like when the other side is // simply not sending any more data... // this can *apparently* lead to long delays sometimes which stalls applications. // do not want. setOpenMode(d->sock.openMode() | QIODevice::Unbuffered); } void KTcpSocket::connectToHost(const QHostAddress &hostAddress, quint16 port, ProxyPolicy policy) { if (policy == AutoProxy) { //### } d->sock.connectToHost(hostAddress, port); setOpenMode(d->sock.openMode() | QIODevice::Unbuffered); } void KTcpSocket::connectToHost(const QUrl &url, ProxyPolicy policy) { if (policy == AutoProxy) { //### } d->sock.connectToHost(url.host(), url.port()); setOpenMode(d->sock.openMode() | QIODevice::Unbuffered); } void KTcpSocket::disconnectFromHost() { d->sock.disconnectFromHost(); setOpenMode(d->sock.openMode() | QIODevice::Unbuffered); } KTcpSocket::Error KTcpSocket::error() const { return d->errorFromAbsSocket(d->sock.error()); } QList KTcpSocket::sslErrors() const { //### pretty slow; also consider throwing out duplicate error codes. We may get // duplicates even though there were none in the original list because KSslError // has a smallest common denominator range of SSL error codes. const auto qsslErrors = d->sock.sslErrors(); QList ret; ret.reserve(qsslErrors.size()); for (const QSslError &e : qsslErrors) { ret.append(KSslError(e)); } return ret; } bool KTcpSocket::flush() { return d->sock.flush(); } bool KTcpSocket::isValid() const { return d->sock.isValid(); } QHostAddress KTcpSocket::localAddress() const { return d->sock.localAddress(); } QHostAddress KTcpSocket::peerAddress() const { return d->sock.peerAddress(); } QString KTcpSocket::peerName() const { return d->sock.peerName(); } quint16 KTcpSocket::peerPort() const { return d->sock.peerPort(); } #ifndef QT_NO_NETWORKPROXY QNetworkProxy KTcpSocket::proxy() const { return d->sock.proxy(); } #endif qint64 KTcpSocket::readBufferSize() const { return d->sock.readBufferSize(); } #ifndef QT_NO_NETWORKPROXY void KTcpSocket::setProxy(const QNetworkProxy &proxy) { d->sock.setProxy(proxy); } #endif void KTcpSocket::setReadBufferSize(qint64 size) { d->sock.setReadBufferSize(size); } KTcpSocket::State KTcpSocket::state() const { return d->state(d->sock.state()); } bool KTcpSocket::waitForConnected(int msecs) { bool ret = d->sock.waitForConnected(msecs); if (!ret) { setErrorString(d->sock.errorString()); } setOpenMode(d->sock.openMode() | QIODevice::Unbuffered); return ret; } bool KTcpSocket::waitForDisconnected(int msecs) { bool ret = d->sock.waitForDisconnected(msecs); if (!ret) { setErrorString(d->sock.errorString()); } setOpenMode(d->sock.openMode() | QIODevice::Unbuffered); return ret; } ////////////////////////////// public methods from QSslSocket void KTcpSocket::addCaCertificate(const QSslCertificate &certificate) { d->maybeLoadCertificates(); d->sock.addCaCertificate(certificate); } /* bool KTcpSocket::addCaCertificates(const QString &path, QSsl::EncodingFormat format, QRegExp::PatternSyntax syntax) { d->maybeLoadCertificates(); return d->sock.addCaCertificates(path, format, syntax); } */ void KTcpSocket::addCaCertificates(const QList &certificates) { d->maybeLoadCertificates(); d->sock.addCaCertificates(certificates); } QList KTcpSocket::caCertificates() const { d->maybeLoadCertificates(); return d->sock.sslConfiguration().caCertificates(); } QList KTcpSocket::ciphers() const { return d->ciphers; } void KTcpSocket::connectToHostEncrypted(const QString &hostName, quint16 port, OpenMode openMode) { d->maybeLoadCertificates(); d->sock.setProtocol(qSslProtocolFromK(d->advertisedSslVersion)); d->sock.connectToHostEncrypted(hostName, port, openMode); setOpenMode(d->sock.openMode() | QIODevice::Unbuffered); } QSslCertificate KTcpSocket::localCertificate() const { return d->sock.localCertificate(); } QList KTcpSocket::peerCertificateChain() const { return d->sock.peerCertificateChain(); } KSslKey KTcpSocket::privateKey() const { return KSslKey(d->sock.privateKey()); } KSslCipher KTcpSocket::sessionCipher() const { return KSslCipher(d->sock.sessionCipher()); } void KTcpSocket::setCaCertificates(const QList &certificates) { QSslConfiguration configuration = d->sock.sslConfiguration(); configuration.setCaCertificates(certificates); d->sock.setSslConfiguration(configuration); d->certificatesLoaded = true; } void KTcpSocket::setCiphers(const QList &ciphers) { d->ciphers = ciphers; QList cl; cl.reserve(d->ciphers.size()); foreach (const KSslCipher &c, d->ciphers) { cl.append(d->ccc.converted(c)); } QSslConfiguration configuration = d->sock.sslConfiguration(); configuration.setCiphers(cl); d->sock.setSslConfiguration(configuration); } void KTcpSocket::setLocalCertificate(const QSslCertificate &certificate) { d->sock.setLocalCertificate(certificate); } void KTcpSocket::setLocalCertificate(const QString &fileName, QSsl::EncodingFormat format) { d->sock.setLocalCertificate(fileName, format); } void KTcpSocket::setVerificationPeerName(const QString &hostName) { d->sock.setPeerVerifyName(hostName); } void KTcpSocket::setPrivateKey(const KSslKey &key) { // We cannot map KSslKey::Algorithm:Dh to anything in QSsl::KeyAlgorithm. if (key.algorithm() == KSslKey::Dh) { return; } QSslKey _key(key.toDer(), (key.algorithm() == KSslKey::Rsa) ? QSsl::Rsa : QSsl::Dsa, QSsl::Der, (key.secrecy() == KSslKey::PrivateKey) ? QSsl::PrivateKey : QSsl::PublicKey); d->sock.setPrivateKey(_key); } void KTcpSocket::setPrivateKey(const QString &fileName, KSslKey::Algorithm algorithm, QSsl::EncodingFormat format, const QByteArray &passPhrase) { // We cannot map KSslKey::Algorithm:Dh to anything in QSsl::KeyAlgorithm. if (algorithm == KSslKey::Dh) { return; } d->sock.setPrivateKey(fileName, (algorithm == KSslKey::Rsa) ? QSsl::Rsa : QSsl::Dsa, format, passPhrase); } bool KTcpSocket::waitForEncrypted(int msecs) { return d->sock.waitForEncrypted(msecs); } KTcpSocket::EncryptionMode KTcpSocket::encryptionMode() const { return d->encryptionMode(d->sock.mode()); } QVariant KTcpSocket::socketOption(QAbstractSocket::SocketOption options) const { return d->sock.socketOption(options); } void KTcpSocket::setSocketOption(QAbstractSocket::SocketOption options, const QVariant &value) { d->sock.setSocketOption(options, value); } QSslConfiguration KTcpSocket::sslConfiguration() const { return d->sock.sslConfiguration(); } void KTcpSocket::setSslConfiguration(const QSslConfiguration &configuration) { d->sock.setSslConfiguration(configuration); } //slot void KTcpSocket::ignoreSslErrors() { d->sock.ignoreSslErrors(); } //slot void KTcpSocket::startClientEncryption() { d->maybeLoadCertificates(); d->sock.setProtocol(qSslProtocolFromK(d->advertisedSslVersion)); d->sock.startClientEncryption(); } //debugging H4X void KTcpSocket::showSslErrors() { foreach (const QSslError &e, d->sock.sslErrors()) { qCDebug(KIO_CORE) << e.errorString(); } } void KTcpSocket::setAdvertisedSslVersion(KTcpSocket::SslVersion version) { d->advertisedSslVersion = version; } KTcpSocket::SslVersion KTcpSocket::advertisedSslVersion() const { return d->advertisedSslVersion; } KTcpSocket::SslVersion KTcpSocket::negotiatedSslVersion() const { if (!d->sock.isEncrypted()) { return UnknownSslVersion; } return kSslVersionFromQ(d->sock.sessionProtocol()); } QString KTcpSocket::negotiatedSslVersionName() const { if (!d->sock.isEncrypted()) { return QString(); } return protocolString(d->sock.sessionProtocol()); } ////////////////////////////// KSslKey class KSslKeyPrivate { public: KSslKey::Algorithm convertAlgorithm(QSsl::KeyAlgorithm a) { switch (a) { case QSsl::Dsa: return KSslKey::Dsa; default: return KSslKey::Rsa; } } KSslKey::Algorithm algorithm; KSslKey::KeySecrecy secrecy; bool isExportable; QByteArray der; }; KSslKey::KSslKey() : d(new KSslKeyPrivate) { d->algorithm = Rsa; d->secrecy = PublicKey; d->isExportable = true; } KSslKey::KSslKey(const KSslKey &other) : d(new KSslKeyPrivate) { *d = *other.d; } KSslKey::KSslKey(const QSslKey &qsk) : d(new KSslKeyPrivate) { d->algorithm = d->convertAlgorithm(qsk.algorithm()); d->secrecy = (qsk.type() == QSsl::PrivateKey) ? PrivateKey : PublicKey; d->isExportable = true; d->der = qsk.toDer(); } KSslKey::~KSslKey() { delete d; } KSslKey &KSslKey::operator=(const KSslKey &other) { *d = *other.d; return *this; } KSslKey::Algorithm KSslKey::algorithm() const { return d->algorithm; } bool KSslKey::isExportable() const { return d->isExportable; } KSslKey::KeySecrecy KSslKey::secrecy() const { return d->secrecy; } QByteArray KSslKey::toDer() const { return d->der; } ////////////////////////////// KSslCipher //nice-to-have: make implicitly shared class KSslCipherPrivate { public: QString authenticationMethod; QString encryptionMethod; QString keyExchangeMethod; QString name; bool isNull; int supportedBits; int usedBits; }; KSslCipher::KSslCipher() : d(new KSslCipherPrivate) { d->isNull = true; d->supportedBits = 0; d->usedBits = 0; } KSslCipher::KSslCipher(const KSslCipher &other) : d(new KSslCipherPrivate) { *d = *other.d; } KSslCipher::KSslCipher(const QSslCipher &qsc) : d(new KSslCipherPrivate) { d->authenticationMethod = qsc.authenticationMethod(); d->encryptionMethod = qsc.encryptionMethod(); //Qt likes to append the number of bits (usedBits?) to the algorithm, //for example "AES(256)". We only want the pure algorithm name, though. int parenIdx = d->encryptionMethod.indexOf(QLatin1Char('(')); if (parenIdx > 0) { d->encryptionMethod.truncate(parenIdx); } d->keyExchangeMethod = qsc.keyExchangeMethod(); d->name = qsc.name(); d->isNull = qsc.isNull(); d->supportedBits = qsc.supportedBits(); d->usedBits = qsc.usedBits(); } KSslCipher::~KSslCipher() { delete d; } KSslCipher &KSslCipher::operator=(const KSslCipher &other) { *d = *other.d; return *this; } bool KSslCipher::isNull() const { return d->isNull; } QString KSslCipher::authenticationMethod() const { return d->authenticationMethod; } QString KSslCipher::encryptionMethod() const { return d->encryptionMethod; } QString KSslCipher::keyExchangeMethod() const { return d->keyExchangeMethod; } QString KSslCipher::digestMethod() const { //### This is not really backend neutral. It works for OpenSSL and // for RFC compliant names, though. if (d->name.endsWith(QLatin1String("SHA"))) { return QStringLiteral("SHA-1"); } else if (d->name.endsWith(QLatin1String("MD5"))) { return QStringLiteral("MD5"); } else { return QString(); } } QString KSslCipher::name() const { return d->name; } int KSslCipher::supportedBits() const { return d->supportedBits; } int KSslCipher::usedBits() const { return d->usedBits; } //static QList KSslCipher::supportedCiphers() { QList ret; const QList candidates = QSslConfiguration::supportedCiphers(); ret.reserve(candidates.size()); for (const QSslCipher &c : candidates) { ret.append(KSslCipher(c)); } return ret; } -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(); - 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->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->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; -} - #include "moc_ktcpsocket.cpp" diff --git a/src/core/ktcpsocket.h b/src/core/ktcpsocket.h index 8c02fa31..9bdc1286 100644 --- a/src/core/ktcpsocket.h +++ b/src/core/ktcpsocket.h @@ -1,434 +1,392 @@ /* This file is part of the KDE libraries Copyright (C) 2007 Thiago Macieira Copyright (C) 2007 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 KTCPSOCKET_H #define KTCPSOCKET_H #include #include #include "kiocore_export.h" - -class QNetworkReply; +#include "ksslerroruidata.h" /* Notes on QCA::TLS compatibility In order to check for all validation problems as far as possible we need to use: Validity QCA::TLS::peerCertificateValidity() TLS::IdentityResult QCA::TLS::peerIdentityResult() CertificateChain QCA::TLS::peerCertificateChain().validate() - to find the failing cert! TLS::Error QCA::TLS::errorCode() - for more generic (but stil SSL) errors */ class KSslKeyPrivate; class KIOCORE_EXPORT KSslKey { public: enum Algorithm { Rsa = 0, Dsa, Dh }; enum KeySecrecy { PublicKey, PrivateKey }; KSslKey(); KSslKey(const KSslKey &other); KSslKey(const QSslKey &sslKey); ~KSslKey(); KSslKey &operator=(const KSslKey &other); Algorithm algorithm() const; bool isExportable() const; KeySecrecy secrecy() const; QByteArray toDer() const; private: KSslKeyPrivate *const d; }; class KSslCipherPrivate; class KIOCORE_EXPORT KSslCipher { public: KSslCipher(); KSslCipher(const KSslCipher &other); KSslCipher(const QSslCipher &); ~KSslCipher(); KSslCipher &operator=(const KSslCipher &other); bool isNull() const; QString authenticationMethod() const; QString encryptionMethod() const; QString keyExchangeMethod() const; QString digestMethod() const; /* mainly for internal use */ QString name() const; int supportedBits() const; int usedBits() const; static QList supportedCiphers(); private: KSslCipherPrivate *const d; }; class KSslErrorPrivate; class KTcpSocket; class KIOCORE_EXPORT KSslError { public: enum Error { NoError = 0, UnknownError, InvalidCertificateAuthorityCertificate, InvalidCertificate, CertificateSignatureFailed, SelfSignedCertificate, ExpiredCertificate, RevokedCertificate, InvalidCertificatePurpose, RejectedCertificate, UntrustedCertificate, NoPeerCertificate, HostNameMismatch, PathLengthExceeded }; KSslError(KSslError::Error error = NoError, const QSslCertificate &cert = QSslCertificate()); KSslError(const QSslError &error); //### explicit yes or no? KSslError(const KSslError &other); ~KSslError(); KSslError &operator=(const KSslError &other); Error error() const; QString errorString() const; QSslCertificate certificate() const; private: KSslErrorPrivate *const d; }; //consider killing more convenience functions with huge signatures //### do we need setSession() / session() ? //BIG FAT TODO: do we keep openMode() up to date everywhere it can change? //other TODO: limit possible error strings?, SSL key stuff //TODO protocol (or maybe even application?) dependent automatic proxy choice class KTcpSocketPrivate; class QHostAddress; class KIOCORE_EXPORT KTcpSocket: public QIODevice { Q_OBJECT public: enum State { UnconnectedState = 0, HostLookupState, ConnectingState, ConnectedState, BoundState, ListeningState, ClosingState //hmmm, do we need an SslNegotiatingState? }; enum SslVersion { UnknownSslVersion = 0x01, SslV2 = 0x02, SslV3 = 0x04, TlsV1 = 0x08, SslV3_1 = 0x08, TlsV1SslV3 = 0x10, SecureProtocols = 0x20, TlsV1_0 = TlsV1, TlsV1_1 = 0x40, TlsV1_2 = 0x80, TlsV1_3 = 0x100, AnySslVersion = SslV2 | SslV3 | TlsV1 }; Q_DECLARE_FLAGS(SslVersions, SslVersion) enum Error { UnknownError = 0, ConnectionRefusedError, RemoteHostClosedError, HostNotFoundError, SocketAccessError, SocketResourceError, SocketTimeoutError, NetworkError, UnsupportedSocketOperationError, SslHandshakeFailedError ///< @since 4.10.5 }; /* The following is based on reading the OpenSSL interface code of both QSslSocket and QCA::TLS. Barring oversights it should be accurate. The two cases with the question marks apparently will never be emitted by QSslSocket so there is nothing to compare. QSslError::NoError KTcpSocket::NoError QSslError::UnableToGetIssuerCertificate QCA::ErrorSignatureFailed QSslError::UnableToDecryptCertificateSignature QCA::ErrorSignatureFailed QSslError::UnableToDecodeIssuerPublicKey QCA::ErrorInvalidCA QSslError::CertificateSignatureFailed QCA::ErrorSignatureFailed QSslError::CertificateNotYetValid QCA::ErrorExpired QSslError::CertificateExpired QCA::ErrorExpired QSslError::InvalidNotBeforeField QCA::ErrorExpired QSslError::InvalidNotAfterField QCA::ErrorExpired QSslError::SelfSignedCertificate QCA::ErrorSelfSigned QSslError::SelfSignedCertificateInChain QCA::ErrorSelfSigned QSslError::UnableToGetLocalIssuerCertificate QCA::ErrorInvalidCA QSslError::UnableToVerifyFirstCertificate QCA::ErrorSignatureFailed QSslError::CertificateRevoked QCA::ErrorRevoked QSslError::InvalidCaCertificate QCA::ErrorInvalidCA QSslError::PathLengthExceeded QCA::ErrorPathLengthExceeded QSslError::InvalidPurpose QCA::ErrorInvalidPurpose QSslError::CertificateUntrusted QCA::ErrorUntrusted QSslError::CertificateRejected QCA::ErrorRejected QSslError::SubjectIssuerMismatch QCA::TLS::InvalidCertificate ? QSslError::AuthorityIssuerSerialNumberMismatch QCA::TLS::InvalidCertificate ? QSslError::NoPeerCertificate QCA::TLS::NoCertificate QSslError::HostNameMismatch QCA::TLS::HostMismatch QSslError::UnspecifiedError KTcpSocket::UnknownError QSslError::NoSslSupport Never happens :) */ enum EncryptionMode { UnencryptedMode = 0, SslClientMode, SslServerMode //### not implemented }; enum ProxyPolicy { /// Use the proxy that KProtocolManager suggests for the connection parameters given. AutoProxy = 0, /// Use the proxy set by setProxy(), if any; otherwise use no proxy. ManualProxy }; KTcpSocket(QObject *parent = nullptr); ~KTcpSocket(); //from QIODevice //reimplemented virtuals - the ones not reimplemented are OK for us bool atEnd() const override; qint64 bytesAvailable() const override; qint64 bytesToWrite() const override; bool canReadLine() const override; void close() override; bool isSequential() const override; bool open(QIODevice::OpenMode open) override; bool waitForBytesWritten(int msecs) override; //### Document that this actually tries to read *more* data bool waitForReadyRead(int msecs = 30000) override; protected: qint64 readData(char *data, qint64 maxSize) override; qint64 writeData(const char *data, qint64 maxSize) override; Q_SIGNALS: /// @since 4.8.1 /// Forwarded from QSslSocket void encryptedBytesWritten(qint64 written); public: //from QAbstractSocket void abort(); void connectToHost(const QString &hostName, quint16 port, ProxyPolicy policy = AutoProxy); void connectToHost(const QHostAddress &hostAddress, quint16 port, ProxyPolicy policy = AutoProxy); /** * Take the hostname and port from @p url and connect to them. The information from a * full URL enables the most accurate choice of proxy in case of proxy rules that * depend on high-level information like protocol or username. * @see KProtocolManager::proxyForUrl() */ void connectToHost(const QUrl &url, ProxyPolicy policy = AutoProxy); void disconnectFromHost(); Error error() const; //### QAbstractSocket's model is strange. error() should be related to the //current state and *NOT* just report the last error if there was one. QList sslErrors() const; //### the errors returned can only have a subset of all //possible QSslError::SslError enum values depending on backend bool flush(); bool isValid() const; QHostAddress localAddress() const; QHostAddress peerAddress() const; QString peerName() const; quint16 peerPort() const; void setVerificationPeerName(const QString &hostName); #ifndef QT_NO_NETWORKPROXY /** * @see: connectToHost() */ QNetworkProxy proxy() const; #endif qint64 readBufferSize() const; //probably hard to implement correctly #ifndef QT_NO_NETWORKPROXY /** * @see: connectToHost() */ void setProxy(const QNetworkProxy &proxy); //people actually seem to need it #endif void setReadBufferSize(qint64 size); State state() const; bool waitForConnected(int msecs = 30000); bool waitForDisconnected(int msecs = 30000); //from QSslSocket void addCaCertificate(const QSslCertificate &certificate); // bool addCaCertificates(const QString &path, QSsl::EncodingFormat format = QSsl::Pem, // QRegExp::PatternSyntax syntax = QRegExp::FixedString); void addCaCertificates(const QList &certificates); QList caCertificates() const; QList ciphers() const; void connectToHostEncrypted(const QString &hostName, quint16 port, OpenMode openMode = ReadWrite); // bool isEncrypted() const { return encryptionMode() != UnencryptedMode } QSslCertificate localCertificate() const; QList peerCertificateChain() const; KSslKey privateKey() const; KSslCipher sessionCipher() const; void setCaCertificates(const QList &certificates); void setCiphers(const QList &ciphers); //### void setCiphers(const QString &ciphers); //what about i18n? void setLocalCertificate(const QSslCertificate &certificate); void setLocalCertificate(const QString &fileName, QSsl::EncodingFormat format = QSsl::Pem); void setPrivateKey(const KSslKey &key); void setPrivateKey(const QString &fileName, KSslKey::Algorithm algorithm = KSslKey::Rsa, QSsl::EncodingFormat format = QSsl::Pem, const QByteArray &passPhrase = QByteArray()); void setAdvertisedSslVersion(SslVersion version); SslVersion advertisedSslVersion() const; //always equal to last setSslAdvertisedVersion SslVersion negotiatedSslVersion() const; //negotiated version; downgrades are possible. QString negotiatedSslVersionName() const; bool waitForEncrypted(int msecs = 30000); EncryptionMode encryptionMode() const; /** * Returns the state of the socket @p option. * * @see QAbstractSocket::socketOption * * @since 4.5.0 */ QVariant socketOption(QAbstractSocket::SocketOption options) const; /** * Sets the socket @p option to @p value. * * @see QAbstractSocket::setSocketOption * * @since 4.5.0 */ void setSocketOption(QAbstractSocket::SocketOption options, const QVariant &value); /** * Returns the socket's SSL configuration. * * @since 4.8.4 */ QSslConfiguration sslConfiguration() const; /** * Sets the socket's SSL configuration. * * @since 4.8.4 */ void setSslConfiguration(const QSslConfiguration &configuration); Q_SIGNALS: //from QAbstractSocket void connected(); void disconnected(); void error(KTcpSocket::Error); void hostFound(); #ifndef QT_NO_NETWORKPROXY void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator); #endif // only for raw socket state, SSL is separate void stateChanged(KTcpSocket::State); //from QSslSocket void encrypted(); void encryptionModeChanged(EncryptionMode); void sslErrors(const QList &errors); public Q_SLOTS: void ignoreSslErrors(); void startClientEncryption(); // void startServerEncryption(); //not implemented private: Q_PRIVATE_SLOT(d, void reemitReadyRead()) Q_PRIVATE_SLOT(d, void reemitSocketError(QAbstractSocket::SocketError)) Q_PRIVATE_SLOT(d, void reemitSslErrors(const QList &)) Q_PRIVATE_SLOT(d, void reemitStateChanged(QAbstractSocket::SocketState)) Q_PRIVATE_SLOT(d, void reemitModeChanged(QSslSocket::SslMode)) //debugging H4X void showSslErrors(); friend class KTcpSocketPrivate; KTcpSocketPrivate *const d; }; -/** - * This class can hold all the necessary data from a KTcpSocket to ask the user - * to continue connecting in the face of SSL errors. - * It can be used to carry the data for the UI over time or over thread boundaries. - * - * @see: KSslCertificateManager::askIgnoreSslErrors() - */ -class KIOCORE_EXPORT KSslErrorUiData -{ -public: - /** - * Default construct an instance with no useful data. - */ - KSslErrorUiData(); - /** - * Create an instance and initialize it with SSL error data from @p socket. - */ - KSslErrorUiData(const KTcpSocket *socket); - /** - * Create an instance and initialize it with SSL error data from @p socket. - */ - KSslErrorUiData(const QSslSocket *socket); - /** - * Create an instance and initialize it with SSL error data from @p reply. - * @since 5.62 - */ - KSslErrorUiData(const QNetworkReply *reply, const QList &sslErrors); - - KSslErrorUiData(const KSslErrorUiData &other); - KSslErrorUiData &operator=(const KSslErrorUiData &); - /** - * Destructor - * @since 4.7 - */ - ~KSslErrorUiData(); - class Private; -private: - friend class Private; - Private *const d; -}; - #endif // KTCPSOCKET_H diff --git a/src/widgets/sslui.cpp b/src/widgets/sslui.cpp index 3aab6c2b..0754ea1d 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 +#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); 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); 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); foreach (const KSslError &err, 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()); foreach (const QSslCertificate &cert, ud->certificateChain) { QList errors; foreach (const KSslError &error, 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; }