diff --git a/src/core/ksslerror_p.h b/src/core/ksslerror_p.h index acfb9662..5c0f7c94 100644 --- a/src/core/ksslerror_p.h +++ b/src/core/ksslerror_p.h @@ -1,36 +1,36 @@ /* 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. */ #ifndef KSSLERROR_P_H #define KSSLERROR_P_H #include "kiocore_export.h" #include "ktcpsocket.h" class KSslErrorPrivate { public: - static KSslError::Error errorFromQSslError(QSslError::SslError e); + KIOCORE_EXPORT static KSslError::Error errorFromQSslError(QSslError::SslError e); KIOCORE_EXPORT static QSslError::SslError errorFromKSslError(KSslError::Error e); QSslError error; }; #endif diff --git a/src/core/tcpslavebase.cpp b/src/core/tcpslavebase.cpp index 5404a5de..5030459a 100644 --- a/src/core/tcpslavebase.cpp +++ b/src/core/tcpslavebase.cpp @@ -1,932 +1,933 @@ /* * Copyright (C) 2000 Alex Zepeda * Copyright (C) 2001-2003 George Staikos * Copyright (C) 2001 Dawit Alemayehu * Copyright (C) 2007,2008 Andreas Hartmetz * Copyright (C) 2008 Roland Harnau * Copyright (C) 2010 Richard Moore * * This file is part of the KDE project * * 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 "tcpslavebase.h" #include "kiocoredebug.h" +#include "ksslerror_p.h" #include #include #include #include #include #include #include using namespace KIO; //using namespace KNetwork; namespace KIO { Q_DECLARE_OPERATORS_FOR_FLAGS(TCPSlaveBase::SslResult) } //TODO Proxy support whichever way works; KPAC reportedly does *not* work. //NOTE kded_proxyscout may or may not be interesting //TODO resurrect SSL session recycling; this means save the session on disconnect and look //for a reusable session on connect. Consider how HTTP persistent connections interact with that. //TODO in case we support SSL-lessness we need static KTcpSocket::sslAvailable() and check it //in most places we ATM check for d->isSSL. //TODO check if d->isBlocking is honored everywhere it makes sense //TODO fold KSSLSetting and KSSLCertificateHome into KSslSettings and use that everywhere. //TODO recognize partially encrypted websites as "somewhat safe" /* List of dialogs/messageboxes we need to use (current code location in parentheses) - Can the "dontAskAgainName" thing be improved? - "SSLCertDialog" [select client cert] (SlaveInterface) - Enter password for client certificate (inline) - Password for client cert was wrong. Please reenter. (inline) - Setting client cert failed. [doesn't give reason] (inline) - "SSLInfoDialog" [mostly server cert info] (SlaveInterface) - You are about to enter secure mode. Security information/Display SSL information/Connect (inline) - You are about to leave secure mode. Security information/Continue loading/Abort (inline) - Hostname mismatch: Continue/Details/Cancel (inline) - IP address mismatch: Continue/Details/Cancel (inline) - Certificate failed authenticity check: Continue/Details/Cancel (inline) - Would you like to accept this certificate forever: Yes/No/Current sessions only (inline) */ /** @internal */ class Q_DECL_HIDDEN TCPSlaveBase::TcpSlaveBasePrivate { public: explicit TcpSlaveBasePrivate(TCPSlaveBase *qq) : q(qq) {} void setSslMetaData() { sslMetaData.insert(QStringLiteral("ssl_in_use"), QStringLiteral("TRUE")); QSslCipher cipher = socket.sessionCipher(); sslMetaData.insert(QStringLiteral("ssl_protocol_version"), cipher.protocolString()); sslMetaData.insert(QStringLiteral("ssl_cipher"), cipher.name()); sslMetaData.insert(QStringLiteral("ssl_cipher_used_bits"), QString::number(cipher.usedBits())); sslMetaData.insert(QStringLiteral("ssl_cipher_bits"), QString::number(cipher.supportedBits())); sslMetaData.insert(QStringLiteral("ssl_peer_ip"), ip); const QList peerCertificateChain = socket.peerCertificateChain(); // try to fill in the blanks, i.e. missing certificates, and just assume that // those belong to the peer (==website or similar) certificate. for (int i = 0; i < sslErrors.count(); i++) { if (sslErrors[i].certificate().isNull()) { sslErrors[i] = QSslError(sslErrors[i].error(), peerCertificateChain[0]); } } QString errorStr; // encode the two-dimensional numeric error list using '\n' and '\t' as outer and inner separators for (const QSslCertificate &cert : peerCertificateChain ) { for (const KSslError &error : qAsConst(sslErrors)) { if (error.certificate() == cert) { - errorStr += QString::number(static_cast(error.error())) + QLatin1Char('\t'); + errorStr += QString::number(static_cast(KSslErrorPrivate::errorFromKSslError(error.error()))) + QLatin1Char('\t'); } } if (errorStr.endsWith(QLatin1Char('\t'))) { errorStr.chop(1); } errorStr += QLatin1Char('\n'); } errorStr.chop(1); sslMetaData.insert(QStringLiteral("ssl_cert_errors"), errorStr); QString peerCertChain; for (const QSslCertificate &cert : peerCertificateChain) { peerCertChain += QString::fromUtf8(cert.toPem()) + QLatin1Char('\x01'); } peerCertChain.chop(1); sslMetaData.insert(QStringLiteral("ssl_peer_chain"), peerCertChain); sendSslMetaData(); } void clearSslMetaData() { sslMetaData.clear(); sslMetaData.insert(QStringLiteral("ssl_in_use"), QStringLiteral("FALSE")); sendSslMetaData(); } void sendSslMetaData() { MetaData::ConstIterator it = sslMetaData.constBegin(); for (; it != sslMetaData.constEnd(); ++it) { q->setMetaData(it.key(), it.value()); } } SslResult startTLSInternal(QSsl::SslProtocol sslVersion, int waitForEncryptedTimeout = -1); TCPSlaveBase * const q; bool isBlocking; QSslSocket socket; QString host; QString ip; quint16 port; QByteArray serviceName; KSSLSettings sslSettings; bool usingSSL; bool autoSSL; bool sslNoUi; // If true, we just drop the connection silently // if SSL certificate check fails in some way. QList sslErrors; MetaData sslMetaData; }; //### uh, is this a good idea?? QIODevice *TCPSlaveBase::socket() const { return &d->socket; } TCPSlaveBase::TCPSlaveBase(const QByteArray &protocol, const QByteArray &poolSocket, const QByteArray &appSocket, bool autoSSL) : SlaveBase(protocol, poolSocket, appSocket), d(new TcpSlaveBasePrivate(this)) { d->isBlocking = true; d->port = 0; d->serviceName = protocol; d->usingSSL = false; d->autoSSL = autoSSL; d->sslNoUi = false; // Limit the read buffer size to 14 MB (14*1024*1024) (based on the upload limit // in TransferJob::slotDataReq). See the docs for QAbstractSocket::setReadBufferSize // and the BR# 187876 to understand why setting this limit is necessary. d->socket.setReadBufferSize(14680064); } TCPSlaveBase::~TCPSlaveBase() { delete d; } ssize_t TCPSlaveBase::write(const char *data, ssize_t len) { ssize_t written = d->socket.write(data, len); if (written == -1) { /*qDebug() << "d->socket.write() returned -1! Socket error is" << d->socket.error() << ", Socket state is" << d->socket.state();*/ } bool success = false; if (d->isBlocking) { // Drain the tx buffer success = d->socket.waitForBytesWritten(-1); } else { // ### I don't know how to make sure that all data does get written at some point // without doing it now. There is no event loop to do it behind the scenes. // Polling in the dispatch() loop? Something timeout based? success = d->socket.waitForBytesWritten(0); } d->socket.flush(); //this is supposed to get the data on the wire faster if (d->socket.state() != QAbstractSocket::ConnectedState || !success) { /*qDebug() << "Write failed, will return -1! Socket error is" << d->socket.error() << ", Socket state is" << d->socket.state() << "Return value of waitForBytesWritten() is" << success;*/ return -1; } return written; } ssize_t TCPSlaveBase::read(char *data, ssize_t len) { if (d->usingSSL && (d->socket.mode() != QSslSocket::SslClientMode)) { d->clearSslMetaData(); //qDebug() << "lost SSL connection."; return -1; } if (!d->socket.bytesAvailable()) { const int timeout = d->isBlocking ? -1 : (readTimeout() * 1000); d->socket.waitForReadyRead(timeout); } #if 0 // Do not do this because its only benefit is to cause a nasty side effect // upstream in Qt. See BR# 260769. else if (d->socket.mode() != QSslSocket::SslClientMode || QNetworkProxy::applicationProxy().type() == QNetworkProxy::NoProxy) { // we only do this when it doesn't trigger Qt socket bugs. When it doesn't break anything // it seems to help performance. d->socket.waitForReadyRead(0); } #endif return d->socket.read(data, len); } ssize_t TCPSlaveBase::readLine(char *data, ssize_t len) { if (d->usingSSL && (d->socket.mode() != QSslSocket::SslClientMode)) { d->clearSslMetaData(); //qDebug() << "lost SSL connection."; return -1; } const int timeout = (d->isBlocking ? -1 : (readTimeout() * 1000)); ssize_t readTotal = 0; do { if (!d->socket.bytesAvailable()) { d->socket.waitForReadyRead(timeout); } ssize_t readStep = d->socket.readLine(&data[readTotal], len - readTotal); if (readStep == -1 || (readStep == 0 && d->socket.state() != QAbstractSocket::ConnectedState)) { return -1; } readTotal += readStep; } while (readTotal == 0 || data[readTotal - 1] != '\n'); return readTotal; } bool TCPSlaveBase::connectToHost(const QString &/*protocol*/, const QString &host, quint16 port) { QString errorString; const int errCode = connectToHost(host, port, &errorString); if (errCode == 0) { return true; } error(errCode, errorString); return false; } int TCPSlaveBase::connectToHost(const QString &host, quint16 port, QString *errorString) { d->clearSslMetaData(); //We have separate connection and SSL setup phases if (errorString) { errorString->clear(); // clear prior error messages. } d->socket.setPeerVerifyName(host); // Used for ssl certificate verification (SNI) // - leaving SSL - warn before we even connect //### see if it makes sense to move this into the HTTP ioslave which is the only // user. if (metaData(QStringLiteral("main_frame_request")) == QLatin1String("TRUE") //### this looks *really* unreliable && metaData(QStringLiteral("ssl_activate_warnings")) == QLatin1String("TRUE") && metaData(QStringLiteral("ssl_was_in_use")) == QLatin1String("TRUE") && !d->autoSSL) { if (d->sslSettings.warnOnLeave()) { int result = messageBox(i18n("You are about to leave secure " "mode. Transmissions will no " "longer be encrypted.\nThis " "means that a third party could " "observe your data in transit."), WarningContinueCancel, i18n("Security Information"), i18n("C&ontinue Loading"), QString(), QStringLiteral("WarnOnLeaveSSLMode")); if (result == SlaveBase::Cancel) { if (errorString) { *errorString = host; } return ERR_USER_CANCELED; } } } const int timeout = (connectTimeout() * 1000); // 20 sec timeout value disconnectFromHost(); //Reset some state, even if we are already disconnected d->host = host; d->socket.connectToHost(host, port); /*const bool connectOk = */d->socket.waitForConnected(timeout > -1 ? timeout : -1); /*qDebug() << "Socket: state=" << d->socket.state() << ", error=" << d->socket.error() << ", connected?" << connectOk;*/ if (d->socket.state() != QAbstractSocket::ConnectedState) { if (errorString) { *errorString = host + QLatin1String(": ") + d->socket.errorString(); } switch (d->socket.error()) { case QAbstractSocket::UnsupportedSocketOperationError: return ERR_UNSUPPORTED_ACTION; case QAbstractSocket::RemoteHostClosedError: return ERR_CONNECTION_BROKEN; case QAbstractSocket::SocketTimeoutError: return ERR_SERVER_TIMEOUT; case QAbstractSocket::HostNotFoundError: return ERR_UNKNOWN_HOST; default: return ERR_CANNOT_CONNECT; } } //### check for proxyAuthenticationRequiredError d->ip = d->socket.peerAddress().toString(); d->port = d->socket.peerPort(); if (d->autoSSL) { const SslResult res = d->startTLSInternal(QSsl::SecureProtocols, timeout); if (res & ResultFailed) { if (errorString) { *errorString = i18nc("%1 is a host name", "%1: SSL negotiation failed", host); } return ERR_CANNOT_CONNECT; } } return 0; } void TCPSlaveBase::disconnectFromHost() { //qDebug(); d->host.clear(); d->ip.clear(); d->usingSSL = false; if (d->socket.state() == QAbstractSocket::UnconnectedState) { // discard incoming data - the remote host might have disconnected us in the meantime // but the visible effect of disconnectFromHost() should stay the same. d->socket.close(); return; } //### maybe save a session for reuse on SSL shutdown if and when QSslSocket // does that. QCA::TLS can do it apparently but that is not enough if // we want to present that as KDE API. Not a big loss in any case. d->socket.disconnectFromHost(); if (d->socket.state() != QAbstractSocket::UnconnectedState) { d->socket.waitForDisconnected(-1); // wait for unsent data to be sent } d->socket.close(); //whatever that means on a socket } bool TCPSlaveBase::isAutoSsl() const { return d->autoSSL; } bool TCPSlaveBase::isUsingSsl() const { return d->usingSSL; } quint16 TCPSlaveBase::port() const { return d->port; } bool TCPSlaveBase::atEnd() const { return d->socket.atEnd(); } bool TCPSlaveBase::startSsl() { if (d->usingSSL) { return false; } return d->startTLSInternal(QSsl::SecureProtocols) & ResultOk; } TCPSlaveBase::SslResult TCPSlaveBase::TcpSlaveBasePrivate::startTLSInternal(QSsl::SslProtocol sslVersion, int waitForEncryptedTimeout) { q->selectClientCertificate(); //setMetaData("ssl_session_id", d->kssl->session()->toString()); //### we don't support session reuse for now... usingSSL = true; // Set the SSL protocol version to use... socket.setProtocol(sslVersion); /* Usually ignoreSslErrors() would be called in the slot invoked by the sslErrors() signal but that would mess up the flow of control. We will check for errors anyway to decide if we want to continue connecting. Otherwise ignoreSslErrors() before connecting would be very insecure. */ socket.ignoreSslErrors(); socket.startClientEncryption(); const bool encryptionStarted = socket.waitForEncrypted(waitForEncryptedTimeout); //Set metadata, among other things for the "SSL Details" dialog QSslCipher cipher = socket.sessionCipher(); if (!encryptionStarted || socket.mode() != QSslSocket::SslClientMode || cipher.isNull() || cipher.usedBits() == 0 || socket.peerCertificateChain().isEmpty()) { usingSSL = false; clearSslMetaData(); /*qDebug() << "Initial SSL handshake failed. encryptionStarted is" << encryptionStarted << ", cipher.isNull() is" << cipher.isNull() << ", cipher.usedBits() is" << cipher.usedBits() << ", length of certificate chain is" << socket.peerCertificateChain().count() << ", the socket says:" << socket.errorString() << "and the list of SSL errors contains" << socket.sslErrors().count() << "items.";*/ /*for (const QSslError &sslError : socket.sslErrors()) { qDebug() << "SSL ERROR: (" << sslError.error() << ")" << sslError.errorString(); }*/ return ResultFailed | ResultFailedEarly; } /*qDebug() << "Cipher info - " << " advertised SSL protocol version" << socket.protocol() << " negotiated SSL protocol version" << socket.sessionProtocol() << " authenticationMethod:" << cipher.authenticationMethod() << " encryptionMethod:" << cipher.encryptionMethod() << " keyExchangeMethod:" << cipher.keyExchangeMethod() << " name:" << cipher.name() << " supportedBits:" << cipher.supportedBits() << " usedBits:" << cipher.usedBits();*/ sslErrors = socket.sslErrors(); // TODO: review / rewrite / remove the comment // The app side needs the metadata now for the SSL error dialog (if any) but // the same metadata will be needed later, too. When "later" arrives the slave // may actually be connected to a different application that doesn't know // the metadata the slave sent to the previous application. // The quite important SSL indicator icon in Konqi's URL bar relies on metadata // from here, for example. And Konqi will be the second application to connect // to the slave. // Therefore we choose to have our metadata and send it, too :) setSslMetaData(); q->sendAndKeepMetaData(); SslResult rc = q->verifyServerCertificate(); if (rc & ResultFailed) { usingSSL = false; clearSslMetaData(); //qDebug() << "server certificate verification failed."; socket.disconnectFromHost(); //Make the connection fail (cf. ignoreSslErrors()) return ResultFailed; } else if (rc & ResultOverridden) { //qDebug() << "server certificate verification failed but continuing at user's request."; } //"warn" when starting SSL/TLS if (q->metaData(QStringLiteral("ssl_activate_warnings")) == QLatin1String("TRUE") && q->metaData(QStringLiteral("ssl_was_in_use")) == QLatin1String("FALSE") && sslSettings.warnOnEnter()) { int msgResult = q->messageBox(i18n("You are about to enter secure mode. " "All transmissions will be encrypted " "unless otherwise noted.\nThis means " "that no third party will be able to " "easily observe your data in transit."), WarningYesNo, i18n("Security Information"), i18n("Display SSL &Information"), i18n("C&onnect"), QStringLiteral("WarnOnEnterSSLMode")); if (msgResult == SlaveBase::Yes) { q->messageBox(SSLMessageBox /*==the SSL info dialog*/, host); } } return rc; } void TCPSlaveBase::selectClientCertificate() { #if 0 //hehe QString certname; // the cert to use this session bool send = false, prompt = false, save = false, forcePrompt = false; KSSLCertificateHome::KSSLAuthAction aa; setMetaData("ssl_using_client_cert", "FALSE"); // we change this if needed if (metaData("ssl_no_client_cert") == "TRUE") { return; } forcePrompt = (metaData("ssl_force_cert_prompt") == "TRUE"); // Delete the old cert since we're certainly done with it now if (d->pkcs) { delete d->pkcs; d->pkcs = NULL; } if (!d->kssl) { return; } // Look for a general certificate if (!forcePrompt) { certname = KSSLCertificateHome::getDefaultCertificateName(&aa); switch (aa) { case KSSLCertificateHome::AuthSend: send = true; prompt = false; break; case KSSLCertificateHome::AuthDont: send = false; prompt = false; certname.clear(); break; case KSSLCertificateHome::AuthPrompt: send = false; prompt = true; break; default: break; } } // Look for a certificate on a per-host basis as an override QString tmpcn = KSSLCertificateHome::getDefaultCertificateName(d->host, &aa); if (aa != KSSLCertificateHome::AuthNone) { // we must override switch (aa) { case KSSLCertificateHome::AuthSend: send = true; prompt = false; certname = tmpcn; break; case KSSLCertificateHome::AuthDont: send = false; prompt = false; certname.clear(); break; case KSSLCertificateHome::AuthPrompt: send = false; prompt = true; certname = tmpcn; break; default: break; } } // Finally, we allow the application to override anything. if (hasMetaData("ssl_demand_certificate")) { certname = metaData("ssl_demand_certificate"); if (!certname.isEmpty()) { forcePrompt = false; prompt = false; send = true; } } if (certname.isEmpty() && !prompt && !forcePrompt) { return; } // Ok, we're supposed to prompt the user.... if (prompt || forcePrompt) { QStringList certs = KSSLCertificateHome::getCertificateList(); QStringList::const_iterator it = certs.begin(); while (it != certs.end()) { KSSLPKCS12 *pkcs = KSSLCertificateHome::getCertificateByName(*it); if (pkcs && (!pkcs->getCertificate() || !pkcs->getCertificate()->x509V3Extensions().certTypeSSLClient())) { it = certs.erase(it); } else { ++it; } delete pkcs; } if (certs.isEmpty()) { return; // we had nothing else, and prompt failed } QDBusConnectionInterface *bus = QDBusConnection::sessionBus().interface(); if (!bus->isServiceRegistered("org.kde.kio.uiserver")) { bus->startService("org.kde.kuiserver"); } QDBusInterface uis("org.kde.kio.uiserver", "/UIServer", "org.kde.KIO.UIServer"); QDBusMessage retVal = uis.call("showSSLCertDialog", d->host, certs, metaData("window-id").toLongLong()); if (retVal.type() == QDBusMessage::ReplyMessage) { if (retVal.arguments().at(0).toBool()) { send = retVal.arguments().at(1).toBool(); save = retVal.arguments().at(2).toBool(); certname = retVal.arguments().at(3).toString(); } } } // The user may have said to not send the certificate, // but to save the choice if (!send) { if (save) { KSSLCertificateHome::setDefaultCertificate(certname, d->host, false, false); } return; } // We're almost committed. If we can read the cert, we'll send it now. KSSLPKCS12 *pkcs = KSSLCertificateHome::getCertificateByName(certname); if (!pkcs && KSSLCertificateHome::hasCertificateByName(certname)) { // We need the password KIO::AuthInfo ai; bool first = true; do { ai.prompt = i18n("Enter the certificate password:"); ai.caption = i18n("SSL Certificate Password"); ai.url.setScheme("kssl"); ai.url.setHost(certname); ai.username = certname; ai.keepPassword = true; bool showprompt; if (first) { showprompt = !checkCachedAuthentication(ai); } else { showprompt = true; } if (showprompt) { if (!openPasswordDialog(ai, first ? QString() : i18n("Unable to open the certificate. Try a new password?"))) { break; } } first = false; pkcs = KSSLCertificateHome::getCertificateByName(certname, ai.password); } while (!pkcs); } // If we could open the certificate, let's send it if (pkcs) { if (!d->kssl->setClientCertificate(pkcs)) { messageBox(Information, i18n("The procedure to set the " "client certificate for the session " "failed."), i18n("SSL")); delete pkcs; // we don't need this anymore pkcs = 0L; } else { //qDebug() << "Client SSL certificate is being used."; setMetaData("ssl_using_client_cert", "TRUE"); if (save) { KSSLCertificateHome::setDefaultCertificate(certname, d->host, true, false); } } d->pkcs = pkcs; } #endif } TCPSlaveBase::SslResult TCPSlaveBase::verifyServerCertificate() { d->sslNoUi = hasMetaData(QStringLiteral("ssl_no_ui")) && (metaData(QStringLiteral("ssl_no_ui")) != QLatin1String("FALSE")); if (d->sslErrors.isEmpty()) { return ResultOk; } else if (d->sslNoUi) { return ResultFailed; } const QList fatalErrors = KSslCertificateManager::nonIgnorableErrors(d->sslErrors); if (!fatalErrors.isEmpty()) { //TODO message "sorry, fatal error, you can't override it" return ResultFailed; } QList peerCertificationChain = d->socket.peerCertificateChain(); KSslCertificateManager *const cm = KSslCertificateManager::self(); KSslCertificateRule rule = cm->rule(peerCertificationChain.first(), d->host); // remove previously seen and acknowledged errors const QList remainingErrors = rule.filterErrors(d->sslErrors); if (remainingErrors.isEmpty()) { //qDebug() << "Error list empty after removing errors to be ignored. Continuing."; return ResultOk | ResultOverridden; } //### We don't ask to permanently reject the certificate QString message = i18n("The server failed the authenticity check (%1).\n\n", d->host); for (const QSslError &err : qAsConst(d->sslErrors)) { message += err.errorString() + QLatin1Char('\n'); } message = message.trimmed(); int msgResult; QDateTime ruleExpiry = QDateTime::currentDateTime(); do { msgResult = messageBox(WarningYesNoCancel, message, i18n("Server Authentication"), i18n("&Details"), i18n("Co&ntinue")); switch (msgResult) { case SlaveBase::Yes: //Details was chosen- show the certificate and error details messageBox(SSLMessageBox /*the SSL info dialog*/, d->host); break; case SlaveBase::No: { //fall through on SlaveBase::No const int result = messageBox(WarningYesNoCancel, i18n("Would you like to accept this " "certificate forever without " "being prompted?"), i18n("Server Authentication"), i18n("&Forever"), i18n("&Current Session only")); if (result == SlaveBase::Yes) { //accept forever ("for a very long time") ruleExpiry = ruleExpiry.addYears(1000); } else if (result == SlaveBase::No) { //accept "for a short time", half an hour. ruleExpiry = ruleExpiry.addSecs(30*60); } else { msgResult = SlaveBase::Yes; } break; } case SlaveBase::Cancel: return ResultFailed; default: qCWarning(KIO_CORE) << "Unexpected MessageBox response received:" << msgResult; return ResultFailed; } } while (msgResult == SlaveBase::Yes); //TODO special cases for wildcard domain name in the certificate! //rule = KSslCertificateRule(d->socket.peerCertificateChain().first(), whatever); rule.setExpiryDateTime(ruleExpiry); rule.setIgnoredErrors(d->sslErrors); cm->setRule(rule); return ResultOk | ResultOverridden; #if 0 //### need to do something like the old code about the main and subframe stuff //qDebug() << "SSL HTTP frame the parent? " << metaData("main_frame_request"); if (!hasMetaData("main_frame_request") || metaData("main_frame_request") == "TRUE") { // Since we're the parent, we need to teach the child. setMetaData("ssl_parent_ip", d->ip); setMetaData("ssl_parent_cert", pc.toString()); // - Read from cache and see if there is a policy for this KSSLCertificateCache::KSSLCertificatePolicy cp = d->certCache->getPolicyByCertificate(pc); // - validation code if (ksv != KSSLCertificate::Ok) { if (d->sslNoUi) { return -1; } if (cp == KSSLCertificateCache::Unknown || cp == KSSLCertificateCache::Ambiguous) { cp = KSSLCertificateCache::Prompt; } else { // A policy was already set so let's honor that. permacache = d->certCache->isPermanent(pc); } if (!_IPmatchesCN && cp == KSSLCertificateCache::Accept) { cp = KSSLCertificateCache::Prompt; // ksv = KSSLCertificate::Ok; } ////// SNIP SNIP ////////// // - cache the results d->certCache->addCertificate(pc, cp, permacache); if (doAddHost) { d->certCache->addHost(pc, d->host); } } else { // Child frame // - Read from cache and see if there is a policy for this KSSLCertificateCache::KSSLCertificatePolicy cp = d->certCache->getPolicyByCertificate(pc); isChild = true; // Check the cert and IP to make sure they're the same // as the parent frame bool certAndIPTheSame = (d->ip == metaData("ssl_parent_ip") && pc.toString() == metaData("ssl_parent_cert")); if (ksv == KSSLCertificate::Ok) { if (certAndIPTheSame) { // success rc = 1; setMetaData("ssl_action", "accept"); } else { /* if (d->sslNoUi) { return -1; } result = messageBox(WarningYesNo, i18n("The certificate is valid but does not appear to have been assigned to this server. Do you wish to continue loading?"), i18n("Server Authentication")); if (result == SlaveBase::Yes) { // success rc = 1; setMetaData("ssl_action", "accept"); } else { // fail rc = -1; setMetaData("ssl_action", "reject"); } */ setMetaData("ssl_action", "accept"); rc = 1; // Let's accept this now. It's bad, but at least the user // will see potential attacks in KDE3 with the pseudo-lock // icon on the toolbar, and can investigate with the RMB } } else { if (d->sslNoUi) { return -1; } if (cp == KSSLCertificateCache::Accept) { if (certAndIPTheSame) { // success rc = 1; setMetaData("ssl_action", "accept"); } else { // fail result = messageBox(WarningYesNo, i18n("You have indicated that you wish to accept this certificate, but it is not issued to the server who is presenting it. Do you wish to continue loading?"), i18n("Server Authentication")); if (result == SlaveBase::Yes) { rc = 1; setMetaData("ssl_action", "accept"); d->certCache->addHost(pc, d->host); } else { rc = -1; setMetaData("ssl_action", "reject"); } } } else if (cp == KSSLCertificateCache::Reject) { // fail messageBox(Information, i18n("SSL certificate is being rejected as requested. You can disable this in the KDE System Settings."), i18n("Server Authentication")); rc = -1; setMetaData("ssl_action", "reject"); } else { //////// SNIP SNIP ////////// return rc; } } } } #endif //#if 0 } bool TCPSlaveBase::isConnected() const { // QSslSocket::isValid() is shady... return d->socket.state() == QAbstractSocket::ConnectedState; } bool TCPSlaveBase::waitForResponse(int t) { if (d->socket.bytesAvailable()) { return true; } return d->socket.waitForReadyRead(t * 1000); } void TCPSlaveBase::setBlocking(bool b) { if (!b) { qCWarning(KIO_CORE) << "Caller requested non-blocking mode, but that doesn't work"; return; } d->isBlocking = b; } void TCPSlaveBase::virtual_hook(int id, void *data) { if (id == SlaveBase::AppConnectionMade) { d->sendSslMetaData(); } else { SlaveBase::virtual_hook(id, data); } } diff --git a/src/widgets/jobuidelegate.cpp b/src/widgets/jobuidelegate.cpp index 268c18d3..0abe569c 100644 --- a/src/widgets/jobuidelegate.cpp +++ b/src/widgets/jobuidelegate.cpp @@ -1,422 +1,422 @@ /* This file is part of the KDE libraries Copyright (C) 2000 Stephan Kulow David Faure Copyright (C) 2006 Kevin Ottens Copyright (C) 2013 Dawit Alemayehu 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 "jobuidelegate.h" #include #include "kio_widgets_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kio/scheduler.h" class Q_DECL_HIDDEN KIO::JobUiDelegate::Private { public: }; KIO::JobUiDelegate::JobUiDelegate() : d(new Private()) { } KIO::JobUiDelegate::~JobUiDelegate() { delete d; } /* Returns the top most window associated with widget. Unlike QWidget::window(), this function does its best to find and return the main application window associated with the given widget. If widget itself is a dialog or its parent is a dialog, and that dialog has a parent widget then this function will iterate through all those widgets to find the top most window, which most of the time is the main window of the application. By contrast, QWidget::window() would simply return the first file dialog it encountered since it is the "next ancestor widget that has (or could have) a window-system frame". */ static QWidget *topLevelWindow(QWidget *widget) { QWidget *w = widget; while (w && w->parentWidget()) { w = w->parentWidget(); } return (w ? w->window() : nullptr); } class JobUiDelegateStatic : public QObject { Q_OBJECT public: void registerWindow(QWidget *wid) { if (!wid) { return; } QWidget *window = topLevelWindow(wid); QObject *obj = static_cast(window); if (!m_windowList.contains(obj)) { // We must store the window Id because by the time // the destroyed signal is emitted we can no longer // access QWidget::winId() (already destructed) WId windowId = window->winId(); m_windowList.insert(obj, windowId); connect(window, &QObject::destroyed, this, &JobUiDelegateStatic::slotUnregisterWindow); QDBusInterface(QStringLiteral("org.kde.kded5"), QStringLiteral("/kded"), QStringLiteral("org.kde.kded5")). call(QDBus::NoBlock, QStringLiteral("registerWindowId"), qlonglong(windowId)); } } public Q_SLOTS: void slotUnregisterWindow(QObject *obj) { if (!obj) { return; } QMap::Iterator it = m_windowList.find(obj); if (it == m_windowList.end()) { return; } WId windowId = it.value(); disconnect(it.key(), &QObject::destroyed, this, &JobUiDelegateStatic::slotUnregisterWindow); m_windowList.erase(it); QDBusInterface(QStringLiteral("org.kde.kded5"), QStringLiteral("/kded"), QStringLiteral("org.kde.kded5")). call(QDBus::NoBlock, QStringLiteral("unregisterWindowId"), qlonglong(windowId)); } private: QMap m_windowList; }; Q_GLOBAL_STATIC(JobUiDelegateStatic, s_static) void KIO::JobUiDelegate::setWindow(QWidget *window) { KDialogJobUiDelegate::setWindow(window); s_static()->registerWindow(window); } void KIO::JobUiDelegate::unregisterWindow(QWidget *window) { s_static()->slotUnregisterWindow(window); } KIO::RenameDialog_Result KIO::JobUiDelegate::askFileRename(KJob *job, const QString &caption, const QUrl &src, const QUrl &dest, KIO::RenameDialog_Options options, QString &newDest, KIO::filesize_t sizeSrc, KIO::filesize_t sizeDest, const QDateTime &ctimeSrc, const QDateTime &ctimeDest, const QDateTime &mtimeSrc, const QDateTime &mtimeDest) { //qDebug() << "job=" << job; // We now do it in process, so that opening the rename dialog // doesn't start uiserver for nothing if progressId=0 (e.g. F2 in konq) KIO::RenameDialog dlg(KJobWidgets::window(job), caption, src, dest, options, sizeSrc, sizeDest, ctimeSrc, ctimeDest, mtimeSrc, mtimeDest); dlg.setWindowModality(Qt::WindowModal); connect(job, &KJob::finished, &dlg, &QDialog::reject); // #192976 KIO::RenameDialog_Result res = static_cast(dlg.exec()); if (res == R_AUTO_RENAME) { newDest = dlg.autoDestUrl().path(); } else { newDest = dlg.newDestUrl().path(); } return res; } KIO::SkipDialog_Result KIO::JobUiDelegate::askSkip(KJob *job, KIO::SkipDialog_Options options, const QString &error_text) { KIO::SkipDialog dlg(KJobWidgets::window(job), options, error_text); dlg.setWindowModality(Qt::WindowModal); connect(job, &KJob::finished, &dlg, &QDialog::reject); // #192976 return static_cast(dlg.exec()); } bool KIO::JobUiDelegate::askDeleteConfirmation(const QList &urls, DeletionType deletionType, ConfirmationType confirmationType) { QString keyName; bool ask = (confirmationType == ForceConfirmation); if (!ask) { KSharedConfigPtr kioConfig = KSharedConfig::openConfig(QStringLiteral("kiorc"), KConfig::NoGlobals); // The default value for confirmations is true for delete and false // for trash. If you change this, please also update: // dolphin/src/settings/general/confirmationssettingspage.cpp bool defaultValue = true; switch (deletionType) { case Delete: keyName = QStringLiteral("ConfirmDelete"); break; case Trash: keyName = QStringLiteral("ConfirmTrash"); defaultValue = false; break; case EmptyTrash: keyName = QStringLiteral("ConfirmEmptyTrash"); break; } ask = kioConfig->group("Confirmations").readEntry(keyName, defaultValue); } if (ask) { QStringList prettyList; prettyList.reserve(urls.size()); for (const QUrl &url : urls) { if (url.scheme() == QLatin1String("trash")) { QString path = url.path(); // HACK (#98983): remove "0-foo". Note that it works better than // displaying KFileItem::name(), for files under a subdir. path.remove(QRegExp(QStringLiteral("^/[0-9]*-"))); prettyList.append(path); } else { prettyList.append(url.toDisplayString(QUrl::PreferLocalFile)); } } int result; QWidget *widget = window(); const KMessageBox::Options options(KMessageBox::Notify | KMessageBox::WindowModal); switch (deletionType) { case Delete: if (prettyList.count() == 1) { result = KMessageBox::warningContinueCancel( widget, xi18nc("@info", "Do you really want to permanently delete this item?%1This action cannot be undone.", prettyList.first()), i18n("Delete Permanently"), KStandardGuiItem::del(), KStandardGuiItem::cancel(), keyName, options); } else { result = KMessageBox::warningContinueCancelList( widget, xi18ncp("@info", "Do you really want to permanently delete this item?This action cannot be undone.", "Do you really want to permanently delete these %1 items?This action cannot be undone.", prettyList.count()), prettyList, i18n("Delete Permanently"), KStandardGuiItem::del(), KStandardGuiItem::cancel(), keyName, options); } break; case EmptyTrash: result = KMessageBox::warningContinueCancel( widget, xi18nc("@info", "Do you want to permanently delete all items from the Trash?This action cannot be undone."), i18n("Delete Permanently"), KGuiItem(i18nc("@action:button", "Empty Trash"), QIcon::fromTheme(QStringLiteral("user-trash"))), KStandardGuiItem::cancel(), keyName, options); break; case Trash: default: if (prettyList.count() == 1) { result = KMessageBox::warningContinueCancel( widget, xi18nc("@info", "Do you really want to move this item to the Trash?%1", prettyList.first()), i18n("Move to Trash"), KGuiItem(i18n("Move to Trash"), QStringLiteral("user-trash")), KStandardGuiItem::cancel(), keyName, options); } else { result = KMessageBox::warningContinueCancelList( widget, i18np("Do you really want to move this item to the Trash?", "Do you really want to move these %1 items to the Trash?", prettyList.count()), prettyList, i18n("Move to Trash"), KGuiItem(i18n("Move to Trash"), QStringLiteral("user-trash")), KStandardGuiItem::cancel(), keyName, options); } } if (!keyName.isEmpty()) { // Check kmessagebox setting... erase & copy to konquerorrc. KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup notificationGroup(config, "Notification Messages"); if (!notificationGroup.readEntry(keyName, true)) { notificationGroup.writeEntry(keyName, true); notificationGroup.sync(); KSharedConfigPtr kioConfig = KSharedConfig::openConfig(QStringLiteral("kiorc"), KConfig::NoGlobals); kioConfig->group("Confirmations").writeEntry(keyName, false); } } return (result == KMessageBox::Continue); } return true; } int KIO::JobUiDelegate::requestMessageBox(KIO::JobUiDelegate::MessageBoxType type, const QString &text, const QString &caption, const QString &buttonYes, const QString &buttonNo, const QString &iconYes, const QString &iconNo, const QString &dontAskAgainName, const KIO::MetaData &sslMetaData) { int result = -1; //qDebug() << type << text << "caption=" << caption; KConfig config(QStringLiteral("kioslaverc")); KMessageBox::setDontShowAgainConfig(&config); const KGuiItem buttonYesGui(buttonYes, iconYes); const KGuiItem buttonNoGui(buttonNo, iconNo); KMessageBox::Options options(KMessageBox::Notify | KMessageBox::WindowModal); switch (type) { case QuestionYesNo: result = KMessageBox::questionYesNo( window(), text, caption, buttonYesGui, buttonNoGui, dontAskAgainName, options); break; case WarningYesNo: result = KMessageBox::warningYesNo( window(), text, caption, buttonYesGui, buttonNoGui, dontAskAgainName, options | KMessageBox::Dangerous); break; case WarningYesNoCancel: result = KMessageBox::warningYesNoCancel( window(), text, caption, buttonYesGui, buttonNoGui, KStandardGuiItem::cancel(), dontAskAgainName, options); break; case WarningContinueCancel: result = KMessageBox::warningContinueCancel( window(), text, caption, buttonYesGui, KStandardGuiItem::cancel(), dontAskAgainName, options); break; case Information: KMessageBox::information(window(), text, caption, dontAskAgainName, options); result = 1; // whatever break; case SSLMessageBox: { QPointer kid(new KSslInfoDialog(window())); //### this is boilerplate code and appears in khtml_part.cpp almost unchanged! const QStringList sl = sslMetaData.value(QStringLiteral("ssl_peer_chain")).split(QLatin1Char('\x01'), QString::SkipEmptyParts); QList certChain; bool decodedOk = true; for (const QString &s : sl) { certChain.append(QSslCertificate(s.toLatin1())); //or is it toLocal8Bit or whatever? if (certChain.last().isNull()) { decodedOk = false; break; } } if (decodedOk) { result = 1; // whatever kid->setSslInfo(certChain, sslMetaData.value(QStringLiteral("ssl_peer_ip")), text, // the URL sslMetaData.value(QStringLiteral("ssl_protocol_version")), sslMetaData.value(QStringLiteral("ssl_cipher")), sslMetaData.value(QStringLiteral("ssl_cipher_used_bits")).toInt(), sslMetaData.value(QStringLiteral("ssl_cipher_bits")).toInt(), - KSslInfoDialog::errorsFromString(sslMetaData.value(QStringLiteral("ssl_cert_errors")))); + KSslInfoDialog::certificateErrorsFromString(sslMetaData.value(QStringLiteral("ssl_cert_errors")))); kid->exec(); } else { result = -1; KMessageBox::information(window(), i18n("The peer SSL certificate chain appears to be corrupt."), i18n("SSL"), QString(), options); } // KSslInfoDialog deletes itself (Qt::WA_DeleteOnClose). delete kid; break; } default: qCWarning(KIO_WIDGETS) << "Unknown type" << type; result = 0; break; } KMessageBox::setDontShowAgainConfig(nullptr); return result; } KIO::ClipboardUpdater *KIO::JobUiDelegate::createClipboardUpdater(Job *job, ClipboardUpdaterMode mode) { if (qobject_cast(qApp)) { return new KIO::ClipboardUpdater(job, mode); } return nullptr; } void KIO::JobUiDelegate::updateUrlInClipboard(const QUrl &src, const QUrl &dest) { if (qobject_cast(qApp)) { KIO::ClipboardUpdater::update(src, dest); } } class KIOWidgetJobUiDelegateFactory : public KIO::JobUiDelegateFactory { public: KJobUiDelegate *createDelegate() const override { return new KIO::JobUiDelegate; } }; Q_GLOBAL_STATIC(KIOWidgetJobUiDelegateFactory, globalUiDelegateFactory) Q_GLOBAL_STATIC(KIO::JobUiDelegate, globalUiDelegate) // Simply linking to this library, creates a GUI job delegate and delegate extension for all KIO jobs static void registerJobUiDelegate() { KIO::setDefaultJobUiDelegateFactory(globalUiDelegateFactory()); KIO::setDefaultJobUiDelegateExtension(globalUiDelegate()); } Q_CONSTRUCTOR_FUNCTION(registerJobUiDelegate) #include "jobuidelegate.moc" diff --git a/src/widgets/ksslinfodialog.cpp b/src/widgets/ksslinfodialog.cpp index 0c66a48e..b3f7ecdb 100644 --- a/src/widgets/ksslinfodialog.cpp +++ b/src/widgets/ksslinfodialog.cpp @@ -1,263 +1,283 @@ /* This file is part of the KDE project * * Copyright (C) 2000,2001 George Staikos * Copyright (C) 2000 Malte Starostik * * 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 "ksslinfodialog.h" #include "ui_sslinfo.h" #include "ksslcertificatebox.h" #include "ksslerror_p.h" #include #include #include #include // BarIcon #include "ktcpsocket.h" class Q_DECL_HIDDEN KSslInfoDialog::KSslInfoDialogPrivate { public: QList certificateChain; QList> certificateErrors; bool isMainPartEncrypted; bool auxPartsEncrypted; Ui::SslInfo ui; KSslCertificateBox *subject; KSslCertificateBox *issuer; }; KSslInfoDialog::KSslInfoDialog(QWidget *parent) : QDialog(parent), d(new KSslInfoDialogPrivate) { setWindowTitle(i18n("KDE SSL Information")); setAttribute(Qt::WA_DeleteOnClose); QVBoxLayout *layout = new QVBoxLayout; setLayout(layout); QWidget *mainWidget = new QWidget(this); d->ui.setupUi(mainWidget); layout->addWidget(mainWidget); d->subject = new KSslCertificateBox(d->ui.certParties); d->issuer = new KSslCertificateBox(d->ui.certParties); d->ui.certParties->addTab(d->subject, i18nc("The receiver of the SSL certificate", "Subject")); d->ui.certParties->addTab(d->issuer, i18nc("The authority that issued the SSL certificate", "Issuer")); d->isMainPartEncrypted = true; d->auxPartsEncrypted = true; updateWhichPartsEncrypted(); QDialogButtonBox *buttonBox = new QDialogButtonBox(this); buttonBox->setStandardButtons(QDialogButtonBox::Close); connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); layout->addWidget(buttonBox); #if 0 if (QSslSocket::supportsSsl()) { if (d->m_secCon) { d->pixmap->setPixmap(BarIcon("security-high")); d->info->setText(i18n("Current connection is secured with SSL.")); } else { d->pixmap->setPixmap(BarIcon("security-low")); d->info->setText(i18n("Current connection is not secured with SSL.")); } } else { d->pixmap->setPixmap(BarIcon("security-low")); d->info->setText(i18n("SSL support is not available in this build of KDE.")); } #endif } KSslInfoDialog::~KSslInfoDialog() { delete d; } void KSslInfoDialog::setMainPartEncrypted(bool mainEncrypted) { d->isMainPartEncrypted = mainEncrypted; updateWhichPartsEncrypted(); } void KSslInfoDialog::setAuxiliaryPartsEncrypted(bool auxEncrypted) { d->auxPartsEncrypted = auxEncrypted; updateWhichPartsEncrypted(); } void KSslInfoDialog::updateWhichPartsEncrypted() { if (d->isMainPartEncrypted) { if (d->auxPartsEncrypted) { d->ui.encryptionIndicator->setPixmap(QIcon::fromTheme(QStringLiteral("security-high")) .pixmap(KIconLoader::SizeSmallMedium)); d->ui.explanation->setText(i18n("Current connection is secured with SSL.")); } else { d->ui.encryptionIndicator->setPixmap(QIcon::fromTheme(QStringLiteral("security-medium")) .pixmap(KIconLoader::SizeSmallMedium)); d->ui.explanation->setText(i18n("The main part of this document is secured " "with SSL, but some parts are not.")); } } else { if (d->auxPartsEncrypted) { d->ui.encryptionIndicator->setPixmap(QIcon::fromTheme(QStringLiteral("security-medium")) .pixmap(KIconLoader::SizeSmallMedium)); d->ui.explanation->setText(i18n("Some of this document is secured with SSL, " "but the main part is not.")); } else { d->ui.encryptionIndicator->setPixmap(QIcon::fromTheme(QStringLiteral("security-low")) .pixmap(KIconLoader::SizeSmallMedium)); d->ui.explanation->setText(i18n("Current connection is not secured with SSL.")); } } } void KSslInfoDialog::setSslInfo(const QList &certificateChain, const QString &ip, const QString &host, const QString &sslProtocol, const QString &cipher, int usedBits, int bits, const QList > &validationErrors) { QList> qValidationErrors; qValidationErrors.reserve(validationErrors.size()); for (const auto &l : validationErrors) { QList qErrors; qErrors.reserve(l.size()); for (const KSslError::Error e : l) { qErrors.push_back(KSslErrorPrivate::errorFromKSslError(e)); } qValidationErrors.push_back(qErrors); } setSslInfo(certificateChain, ip, host, sslProtocol, cipher, usedBits, bits, qValidationErrors); } void KSslInfoDialog::setSslInfo(const QList &certificateChain, const QString &ip, const QString &host, const QString &sslProtocol, const QString &cipher, int usedBits, int bits, const QList> &validationErrors) { d->certificateChain = certificateChain; d->certificateErrors = validationErrors; d->ui.certSelector->clear(); for (const QSslCertificate &cert : certificateChain) { QString name; static const QSslCertificate::SubjectInfo si[] = { QSslCertificate::CommonName, QSslCertificate::Organization, QSslCertificate::OrganizationalUnitName }; for (int j = 0; j < 3 && name.isEmpty(); j++) { name = cert.subjectInfo(si[j]).join(QLatin1String(", ")); } d->ui.certSelector->addItem(name); } if (certificateChain.size() < 2) { d->ui.certSelector->setEnabled(false); } connect(d->ui.certSelector, QOverload::of(&QComboBox::currentIndexChanged), this, &KSslInfoDialog::displayFromChain); if (d->certificateChain.isEmpty()) { d->certificateChain.append(QSslCertificate()); } displayFromChain(0); d->ui.ip->setText(ip); d->ui.address->setText(host); d->ui.sslVersion->setText(sslProtocol); const QStringList cipherInfo = cipher.split(QLatin1Char('\n'), QString::SkipEmptyParts); if (cipherInfo.size() >= 4) { d->ui.encryption->setText(i18nc("%1, using %2 bits of a %3 bit key", "%1, %2 %3", cipherInfo[0], i18ncp("Part of: %1, using %2 bits of a %3 bit key", "using %1 bit", "using %1 bits", usedBits), i18ncp("Part of: %1, using %2 bits of a %3 bit key", "of a %1 bit key", "of a %1 bit key", bits))); d->ui.details->setText(QStringLiteral("Auth = %1, Kx = %2, MAC = %3") .arg(cipherInfo[1], cipherInfo[2], cipherInfo[3])); } else { d->ui.encryption->setText(QString()); d->ui.details->setText(QString()); } } void KSslInfoDialog::displayFromChain(int i) { const QSslCertificate &cert = d->certificateChain[i]; QString trusted; const QList errorsList = d->certificateErrors[i]; if (!errorsList.isEmpty()) { trusted = i18nc("The certificate is not trusted", "NO, there were errors:"); for (QSslError::SslError e : errorsList) { QSslError classError(e); trusted += QLatin1Char('\n') + classError.errorString(); } } else { trusted = i18nc("The certificate is trusted", "Yes"); } d->ui.trusted->setText(trusted); QString vp = i18nc("%1 is the effective date of the certificate, %2 is the expiry date", "%1 to %2", cert.effectiveDate().toString(), cert.expiryDate().toString()); d->ui.validityPeriod->setText(vp); d->ui.serial->setText(QString::fromUtf8(cert.serialNumber())); d->ui.digest->setText(QString::fromUtf8(cert.digest().toHex())); d->ui.sha1Digest->setText(QString::fromUtf8(cert.digest(QCryptographicHash::Sha1).toHex())); d->subject->setCertificate(cert, KSslCertificateBox::Subject); d->issuer->setCertificate(cert, KSslCertificateBox::Issuer); } //static QList > KSslInfoDialog::errorsFromString(const QString &es) { const QStringList sl = es.split(QLatin1Char('\n'), QString::KeepEmptyParts); QList > ret; ret.reserve(sl.size()); for (const QString &s : sl) { QList certErrors; const QStringList sl2 = s.split(QLatin1Char('\t'), QString::SkipEmptyParts); for (const QString &s2 : sl2) { bool didConvert; - KSslError::Error error = static_cast(s2.toInt(&didConvert)); + KSslError::Error error = KSslErrorPrivate::errorFromQSslError(static_cast(s2.toInt(&didConvert))); if (didConvert) { certErrors.append(error); } } ret.append(certErrors); } return ret; } +//static +QList> KSslInfoDialog::certificateErrorsFromString(const QString &errorsString) +{ + const QStringList sl = errorsString.split(QLatin1Char('\n'), QString::KeepEmptyParts); + QList> ret; + ret.reserve(sl.size()); + for (const QString &s : sl) { + QList certErrors; + const QStringList sl2 = s.split(QLatin1Char('\t'), QString::SkipEmptyParts); + for (const QString &s2 : sl2) { + bool didConvert; + QSslError::SslError error = static_cast(s2.toInt(&didConvert)); + if (didConvert) { + certErrors.append(error); + } + } + ret.append(certErrors); + } + return ret; +} diff --git a/src/widgets/ksslinfodialog.h b/src/widgets/ksslinfodialog.h index 3785216f..ec22a4f0 100644 --- a/src/widgets/ksslinfodialog.h +++ b/src/widgets/ksslinfodialog.h @@ -1,125 +1,135 @@ /* This file is part of the KDE project * * Copyright (C) 2000-2003 George Staikos * Copyright (C) 2000 Malte Starostik * * 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 _KSSLINFODIALOG_H #define _KSSLINFODIALOG_H #include #include "kiowidgets_export.h" #include "ktcpsocket.h" // TODO KF6 remove this include /** * KDE SSL Information Dialog * * This class creates a dialog that can be used to display information about * an SSL session. * * There are NO GUARANTEES that KSslInfoDialog will remain binary compatible/ * Contact staikos@kde.org for details if needed. * * @author George Staikos * @see KSSL * @short KDE SSL Information Dialog */ class KIOWIDGETS_EXPORT KSslInfoDialog : public QDialog { Q_OBJECT public: /** * Construct a KSSL Information Dialog * * @param parent the parent widget */ explicit KSslInfoDialog(QWidget *parent = nullptr); /** * Destroy this dialog */ virtual ~KSslInfoDialog(); /** * Tell the dialog if the connection has portions that may not be * secure (ie. a mixture of secure and insecure frames) * * @param isIt true if security is in question */ void setSecurityInQuestion(bool isIt); #if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 64) /** * Set information to display about the SSL connection. * * @param certificateChain the certificate chain leading from the certificate * authority to the peer. * @param ip the ip of the remote host * @param host the remote hostname * @param sslProtocol the version of SSL in use (SSLv2, SSLv3, TLSv1) * @param cipher the cipher in use * @param usedBits the used bits of the key * @param bits the key size of the cipher in use * @param validationErrors errors validating the certificates, if any * @deprecated since 5.64, use the QSslError variant */ KIOCORE_DEPRECATED_VERSION(5, 64, "use the QSslError variant") void setSslInfo(const QList &certificateChain, const QString &ip, const QString &host, const QString &sslProtocol, const QString &cipher, int usedBits, int bits, const QList > &validationErrors); // TODO KF6 remove #endif /** * Set information to display about the SSL connection. * * @param certificateChain the certificate chain leading from the certificate * authority to the peer. * @param ip the ip of the remote host * @param host the remote hostname * @param sslProtocol the version of SSL in use (SSLv2, SSLv3, TLSv1) * @param cipher the cipher in use * @param usedBits the used bits of the key * @param bits the key size of the cipher in use * @param validationErrors errors validating the certificates, if any * @since 5.64 */ void setSslInfo(const QList &certificateChain, const QString &ip, const QString &host, const QString &sslProtocol, const QString &cipher, int usedBits, int bits, const QList> &validationErrors); void setMainPartEncrypted(bool); void setAuxiliaryPartsEncrypted(bool); - static QList > errorsFromString(const QString &s); +#if KIOCORE_ENABLE_DEPRECATED_SINCE(5, 65) + /** @deprecated since 5.65, use certificateErrorsFromString */ + KIOCORE_DEPRECATED_VERSION(5, 65, "use the QSslError variant") + static QList > errorsFromString(const QString &s); // TODO KF6 remove +#endif + /** + * Converts certificate errors as provided in the "ssl_cert_errors" meta data + * to a list of QSslError::SslError values per certificate in the certificate chain. + * @since 5.65 + */ + static QList> certificateErrorsFromString(const QString &errorsString); private: void updateWhichPartsEncrypted(); class KSslInfoDialogPrivate; KSslInfoDialogPrivate *const d; private Q_SLOTS: void displayFromChain(int); }; #endif