KIO
tcpslavebase.cpp
Go to the documentation of this file.
00001 /* 00002 * Copyright (C) 2000 Alex Zepeda <zipzippy@sonic.net> 00003 * Copyright (C) 2001-2003 George Staikos <staikos@kde.org> 00004 * Copyright (C) 2001 Dawit Alemayehu <adawit@kde.org> 00005 * Copyright (C) 2007,2008 Andreas Hartmetz <ahartmetz@gmail.com> 00006 * Copyright (C) 2008 Roland Harnau <tau@gmx.eu> 00007 * Copyright (C) 2010 Richard Moore <rich@kde.org> 00008 * 00009 * This file is part of the KDE project 00010 * 00011 * This library is free software; you can redistribute it and/or 00012 * modify it under the terms of the GNU Library General Public 00013 * License as published by the Free Software Foundation; either 00014 * version 2 of the License, or (at your option) any later version. 00015 * 00016 * This library is distributed in the hope that it will be useful, 00017 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00018 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00019 * Library General Public License for more details. 00020 * 00021 * You should have received a copy of the GNU Library General Public License 00022 * along with this library; see the file COPYING.LIB. If not, write to 00023 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00024 * Boston, MA 02110-1301, USA. 00025 */ 00026 00027 #include "tcpslavebase.h" 00028 00029 #include <config.h> 00030 00031 #include <kdebug.h> 00032 #include <kconfiggroup.h> 00033 #include <ksslcertificatemanager.h> 00034 #include <ksslsettings.h> 00035 #include <kmessagebox.h> 00036 #include <klocale.h> 00037 #include <ktoolinvocation.h> 00038 #include <network/ktcpsocket.h> 00039 00040 #include <QtCore/QDataStream> 00041 #include <QtCore/QTime> 00042 #include <QtNetwork/QTcpSocket> 00043 #include <QtNetwork/QHostInfo> 00044 #include <QtNetwork/QSslConfiguration> 00045 #include <QtDBus/QtDBus> 00046 00047 00048 using namespace KIO; 00049 //using namespace KNetwork; 00050 00051 typedef QMap<QString, QString> StringStringMap; 00052 Q_DECLARE_METATYPE(StringStringMap) 00053 00054 namespace KIO { 00055 Q_DECLARE_OPERATORS_FOR_FLAGS(TCPSlaveBase::SslResult) 00056 } 00057 00058 //TODO Proxy support whichever way works; KPAC reportedly does *not* work. 00059 //NOTE kded_proxyscout may or may not be interesting 00060 00061 //TODO resurrect SSL session recycling; this means save the session on disconnect and look 00062 //for a reusable session on connect. Consider how HTTP persistent connections interact with that. 00063 00064 //TODO in case we support SSL-lessness we need static KTcpSocket::sslAvailable() and check it 00065 //in most places we ATM check for d->isSSL. 00066 00067 //TODO check if d->isBlocking is honored everywhere it makes sense 00068 00069 //TODO fold KSSLSetting and KSSLCertificateHome into KSslSettings and use that everywhere. 00070 00071 //TODO recognize partially encrypted websites as "somewhat safe" 00072 00073 /* List of dialogs/messageboxes we need to use (current code location in parentheses) 00074 - Can the "dontAskAgainName" thing be improved? 00075 00076 - "SSLCertDialog" [select client cert] (SlaveInterface) 00077 - Enter password for client certificate (inline) 00078 - Password for client cert was wrong. Please reenter. (inline) 00079 - Setting client cert failed. [doesn't give reason] (inline) 00080 - "SSLInfoDialog" [mostly server cert info] (SlaveInterface) 00081 - You are about to enter secure mode. Security information/Display SSL information/Connect (inline) 00082 - You are about to leave secure mode. Security information/Continue loading/Abort (inline) 00083 - Hostname mismatch: Continue/Details/Cancel (inline) 00084 - IP address mismatch: Continue/Details/Cancel (inline) 00085 - Certificate failed authenticity check: Continue/Details/Cancel (inline) 00086 - Would you like to accept this certificate forever: Yes/No/Current sessions only (inline) 00087 */ 00088 00089 00091 class TCPSlaveBase::TcpSlaveBasePrivate 00092 { 00093 public: 00094 TcpSlaveBasePrivate(TCPSlaveBase* qq) : q(qq) {} 00095 00096 void setSslMetaData() 00097 { 00098 sslMetaData.insert("ssl_in_use", "TRUE"); 00099 KSslCipher cipher = socket.sessionCipher(); 00100 sslMetaData.insert("ssl_protocol_version", socket.negotiatedSslVersionName()); 00101 QString sslCipher = cipher.encryptionMethod() + '\n'; 00102 sslCipher += cipher.authenticationMethod() + '\n'; 00103 sslCipher += cipher.keyExchangeMethod() + '\n'; 00104 sslCipher += cipher.digestMethod(); 00105 sslMetaData.insert("ssl_cipher", sslCipher); 00106 sslMetaData.insert("ssl_cipher_name", cipher.name()); 00107 sslMetaData.insert("ssl_cipher_used_bits", QString::number(cipher.usedBits())); 00108 sslMetaData.insert("ssl_cipher_bits", QString::number(cipher.supportedBits())); 00109 sslMetaData.insert("ssl_peer_ip", ip); 00110 00111 // try to fill in the blanks, i.e. missing certificates, and just assume that 00112 // those belong to the peer (==website or similar) certificate. 00113 for (int i = 0; i < sslErrors.count(); i++) { 00114 if (sslErrors[i].certificate().isNull()) { 00115 sslErrors[i] = KSslError(sslErrors[i].error(), 00116 socket.peerCertificateChain()[0]); 00117 } 00118 } 00119 00120 QString errorStr; 00121 // encode the two-dimensional numeric error list using '\n' and '\t' as outer and inner separators 00122 Q_FOREACH (const QSslCertificate &cert, socket.peerCertificateChain()) { 00123 Q_FOREACH (const KSslError &error, sslErrors) { 00124 if (error.certificate() == cert) { 00125 errorStr += QString::number(static_cast<int>(error.error())) + '\t'; 00126 } 00127 } 00128 if (errorStr.endsWith('\t')) { 00129 errorStr.chop(1); 00130 } 00131 errorStr += '\n'; 00132 } 00133 errorStr.chop(1); 00134 sslMetaData.insert("ssl_cert_errors", errorStr); 00135 00136 QString peerCertChain; 00137 Q_FOREACH (const QSslCertificate &cert, socket.peerCertificateChain()) { 00138 peerCertChain.append(cert.toPem()); 00139 peerCertChain.append('\x01'); 00140 } 00141 peerCertChain.chop(1); 00142 sslMetaData.insert("ssl_peer_chain", peerCertChain); 00143 sendSslMetaData(); 00144 } 00145 00146 void clearSslMetaData() 00147 { 00148 sslMetaData.clear(); 00149 sslMetaData.insert("ssl_in_use", "FALSE"); 00150 sendSslMetaData(); 00151 } 00152 00153 void sendSslMetaData() 00154 { 00155 MetaData::ConstIterator it = sslMetaData.constBegin(); 00156 for (; it != sslMetaData.constEnd(); ++it) { 00157 q->setMetaData(it.key(), it.value()); 00158 } 00159 } 00160 00161 SslResult startTLSInternal(KTcpSocket::SslVersion sslVersion, 00162 const QSslConfiguration& configuration = QSslConfiguration(), 00163 int waitForEncryptedTimeout = -1); 00164 00165 TCPSlaveBase* q; 00166 00167 bool isBlocking; 00168 00169 KTcpSocket socket; 00170 00171 QString host; 00172 QString ip; 00173 quint16 port; 00174 QByteArray serviceName; 00175 00176 KSSLSettings sslSettings; 00177 bool usingSSL; 00178 bool autoSSL; 00179 bool sslNoUi; // If true, we just drop the connection silently 00180 // if SSL certificate check fails in some way. 00181 QList<KSslError> sslErrors; 00182 00183 MetaData sslMetaData; 00184 }; 00185 00186 00187 //### uh, is this a good idea?? 00188 QIODevice *TCPSlaveBase::socket() const 00189 { 00190 return &d->socket; 00191 } 00192 00193 00194 TCPSlaveBase::TCPSlaveBase(const QByteArray &protocol, 00195 const QByteArray &poolSocket, 00196 const QByteArray &appSocket, 00197 bool autoSSL) 00198 : SlaveBase(protocol, poolSocket, appSocket), 00199 d(new TcpSlaveBasePrivate(this)) 00200 { 00201 d->isBlocking = true; 00202 d->port = 0; 00203 d->serviceName = protocol; 00204 d->usingSSL = false; 00205 d->autoSSL = autoSSL; 00206 d->sslNoUi = false; 00207 // Limit the read buffer size to 14 MB (14*1024*1024) (based on the upload limit 00208 // in TransferJob::slotDataReq). See the docs for QAbstractSocket::setReadBufferSize 00209 // and the BR# 187876 to understand why setting this limit is necessary. 00210 d->socket.setReadBufferSize(14680064); 00211 } 00212 00213 00214 TCPSlaveBase::~TCPSlaveBase() 00215 { 00216 delete d; 00217 } 00218 00219 00220 ssize_t TCPSlaveBase::write(const char *data, ssize_t len) 00221 { 00222 ssize_t written = d->socket.write(data, len); 00223 if (written == -1) { 00224 kDebug(7027) << "d->socket.write() returned -1! Socket error is" 00225 << d->socket.error() << ", Socket state is" << d->socket.state(); 00226 } 00227 00228 bool success = false; 00229 if (d->isBlocking) { 00230 // Drain the tx buffer 00231 success = d->socket.waitForBytesWritten(-1); 00232 } else { 00233 // ### I don't know how to make sure that all data does get written at some point 00234 // without doing it now. There is no event loop to do it behind the scenes. 00235 // Polling in the dispatch() loop? Something timeout based? 00236 success = d->socket.waitForBytesWritten(0); 00237 } 00238 00239 d->socket.flush(); //this is supposed to get the data on the wire faster 00240 00241 if (d->socket.state() != KTcpSocket::ConnectedState || !success) { 00242 kDebug(7027) << "Write failed, will return -1! Socket error is" 00243 << d->socket.error() << ", Socket state is" << d->socket.state() 00244 << "Return value of waitForBytesWritten() is" << success; 00245 return -1; 00246 } 00247 00248 return written; 00249 } 00250 00251 00252 ssize_t TCPSlaveBase::read(char* data, ssize_t len) 00253 { 00254 if (d->usingSSL && (d->socket.encryptionMode() != KTcpSocket::SslClientMode)) { 00255 d->clearSslMetaData(); 00256 kDebug(7029) << "lost SSL connection."; 00257 return -1; 00258 } 00259 00260 if (!d->socket.bytesAvailable()) { 00261 const int timeout = d->isBlocking ? -1 : (readTimeout() * 1000); 00262 d->socket.waitForReadyRead(timeout); 00263 } 00264 #if 0 00265 // Do not do this because its only benefit is to cause a nasty side effect 00266 // upstream in Qt. See BR# 260769. 00267 else if (d->socket.encryptionMode() != KTcpSocket::SslClientMode || 00268 QNetworkProxy::applicationProxy().type() == QNetworkProxy::NoProxy) { 00269 // we only do this when it doesn't trigger Qt socket bugs. When it doesn't break anything 00270 // it seems to help performance. 00271 d->socket.waitForReadyRead(0); 00272 } 00273 #endif 00274 return d->socket.read(data, len); 00275 } 00276 00277 00278 ssize_t TCPSlaveBase::readLine(char *data, ssize_t len) 00279 { 00280 if (d->usingSSL && (d->socket.encryptionMode() != KTcpSocket::SslClientMode)) { 00281 d->clearSslMetaData(); 00282 kDebug(7029) << "lost SSL connection."; 00283 return -1; 00284 } 00285 00286 const int timeout = (d->isBlocking ? -1: (readTimeout() * 1000)); 00287 ssize_t readTotal = 0; 00288 do { 00289 if (!d->socket.bytesAvailable()) 00290 d->socket.waitForReadyRead(timeout); 00291 ssize_t readStep = d->socket.readLine(&data[readTotal], len-readTotal); 00292 if (readStep == -1 || (readStep == 0 && d->socket.state() != KTcpSocket::ConnectedState)) { 00293 return -1; 00294 } 00295 readTotal += readStep; 00296 } while (readTotal == 0 || data[readTotal-1] != '\n'); 00297 00298 return readTotal; 00299 } 00300 00301 00302 bool TCPSlaveBase::connectToHost(const QString &/*protocol*/, 00303 const QString &host, 00304 quint16 port) 00305 { 00306 QString errorString; 00307 const int errCode = connectToHost(host, port, &errorString); 00308 if (errCode == 0) 00309 return true; 00310 00311 error(errCode, errorString); 00312 return false; 00313 } 00314 00315 int TCPSlaveBase::connectToHost(const QString& host, quint16 port, QString* errorString) 00316 { 00317 d->clearSslMetaData(); //We have separate connection and SSL setup phases 00318 00319 if (errorString) { 00320 errorString->clear(); // clear prior error messages. 00321 } 00322 00323 d->socket.setVerificationPeerName(host); // Used for ssl certificate verification (SNI) 00324 00325 // - leaving SSL - warn before we even connect 00326 //### see if it makes sense to move this into the HTTP ioslave which is the only 00327 // user. 00328 if (metaData("main_frame_request") == "TRUE" //### this looks *really* unreliable 00329 && metaData("ssl_activate_warnings") == "TRUE" 00330 && metaData("ssl_was_in_use") == "TRUE" 00331 && !d->autoSSL) { 00332 KSSLSettings kss; 00333 if (kss.warnOnLeave()) { 00334 int result = messageBox(i18n("You are about to leave secure " 00335 "mode. Transmissions will no " 00336 "longer be encrypted.\nThis " 00337 "means that a third party could " 00338 "observe your data in transit."), 00339 WarningContinueCancel, 00340 i18n("Security Information"), 00341 i18n("C&ontinue Loading"), QString(), 00342 "WarnOnLeaveSSLMode"); 00343 00344 if (result == KMessageBox::Cancel) { 00345 if (errorString) 00346 *errorString = host; 00347 return ERR_USER_CANCELED; 00348 } 00349 } 00350 } 00351 00352 /* 00353 By default the SSL handshake attempt uses these settings in the order shown: 00354 00355 1.) Protocol: KTcpSocket::SecureProtocols SSL compression: ON (DEFAULT) 00356 2.) Protocol: KTcpSocket::SecureProtocols SSL compression: OFF 00357 3.) Protocol: KTcpSocket::TlsV1 SSL compression: ON 00358 4.) Protocol: KTcpSocket::TlsV1 SSL compression: OFF 00359 5.) Protocol: KTcpSocket::SslV3 SSL compression: ON 00360 6.) Protocol: KTcpSocket::SslV3 SSL compression: OFF 00361 00362 If any combination other than the one marked DEFAULT is used to complete 00363 the SSL handshake, then that combination will be cached using KIO's internal 00364 meta-data mechanism in order to speed up future connections to the same host. 00365 */ 00366 00367 QSslConfiguration sslConfig = d->socket.sslConfiguration(); 00368 #if QT_VERSION >= 0x040800 00369 const bool isSslCompressionDisabled = sslConfig.testSslOption(QSsl::SslOptionDisableCompression); 00370 const bool shouldSslCompressBeDisabled = config()->readEntry("LastUsedSslDisableCompressionFlag", isSslCompressionDisabled); 00371 sslConfig.setSslOption(QSsl::SslOptionDisableCompression, shouldSslCompressBeDisabled); 00372 #endif 00373 00374 const int lastSslVerson = config()->readEntry("LastUsedSslVersion", static_cast<int>(KTcpSocket::SecureProtocols)); 00375 KTcpSocket::SslVersion trySslVersion = static_cast<KTcpSocket::SslVersion>(lastSslVerson); 00376 KTcpSocket::SslVersions alreadyTriedSslVersions = trySslVersion; 00377 00378 const int timeout = (connectTimeout() * 1000); 00379 while (true) { 00380 disconnectFromHost(); //Reset some state, even if we are already disconnected 00381 d->host = host; 00382 00383 d->socket.connectToHost(host, port); 00384 const bool connectOk = d->socket.waitForConnected(timeout > -1 ? timeout : -1); 00385 00386 kDebug(7027) << "Socket: state=" << d->socket.state() 00387 << ", error=" << d->socket.error() 00388 << ", connected?" << connectOk; 00389 00390 if (d->socket.state() != KTcpSocket::ConnectedState) { 00391 if (errorString) 00392 *errorString = host + QLatin1String(": ") + d->socket.errorString(); 00393 switch (d->socket.error()) { 00394 case KTcpSocket::UnsupportedSocketOperationError: 00395 return ERR_UNSUPPORTED_ACTION; 00396 case KTcpSocket::RemoteHostClosedError: 00397 return ERR_CONNECTION_BROKEN; 00398 case KTcpSocket::SocketTimeoutError: 00399 return ERR_SERVER_TIMEOUT; 00400 case KTcpSocket::HostNotFoundError: 00401 return ERR_UNKNOWN_HOST; 00402 default: 00403 return ERR_COULD_NOT_CONNECT; 00404 } 00405 } 00406 00407 //### check for proxyAuthenticationRequiredError 00408 00409 d->ip = d->socket.peerAddress().toString(); 00410 d->port = d->socket.peerPort(); 00411 00412 if (d->autoSSL) { 00413 SslResult res = d->startTLSInternal(trySslVersion, sslConfig, 30000 /*30 secs timeout*/); 00414 if ((res & ResultFailed) && (res & ResultFailedEarly)) { 00415 #if QT_VERSION >= 0x040800 00416 if (!sslConfig.testSslOption(QSsl::SslOptionDisableCompression)) { 00417 sslConfig.setSslOption(QSsl::SslOptionDisableCompression, true); 00418 continue; 00419 } 00420 #endif 00421 00422 if (!(alreadyTriedSslVersions & KTcpSocket::SecureProtocols)) { 00423 trySslVersion = KTcpSocket::SecureProtocols; 00424 alreadyTriedSslVersions |= trySslVersion; 00425 #if QT_VERSION >= 0x040800 00426 sslConfig.setSslOption(QSsl::SslOptionDisableCompression, false); 00427 #endif 00428 continue; 00429 } 00430 00431 if (!(alreadyTriedSslVersions & KTcpSocket::TlsV1)) { 00432 trySslVersion = KTcpSocket::TlsV1; 00433 alreadyTriedSslVersions |= trySslVersion; 00434 #if QT_VERSION >= 0x040800 00435 sslConfig.setSslOption(QSsl::SslOptionDisableCompression, false); 00436 #endif 00437 continue; 00438 } 00439 00440 if (!(alreadyTriedSslVersions & KTcpSocket::SslV3)) { 00441 trySslVersion = KTcpSocket::SslV3; 00442 alreadyTriedSslVersions |= trySslVersion; 00443 #if QT_VERSION >= 0x040800 00444 sslConfig.setSslOption(QSsl::SslOptionDisableCompression, false); 00445 #endif 00446 continue; 00447 } 00448 } 00449 00450 //### SSL 2.0 is (close to) dead and it's a good thing, too. 00451 if (res & ResultFailed) { 00452 if (errorString) 00453 *errorString = i18nc("%1 is a host name", "%1: SSL negotiation failed", host); 00454 return ERR_COULD_NOT_CONNECT; 00455 } 00456 } 00457 // If the SSL handshake was done with anything protocol other than the default, 00458 // save that information so that any subsequent requests do not have to do thesame thing. 00459 if (trySslVersion != KTcpSocket::SecureProtocols && lastSslVerson == KTcpSocket::SecureProtocols) { 00460 setMetaData(QLatin1String("{internal~currenthost}LastUsedSslVersion"), 00461 QString::number(trySslVersion)); 00462 } 00463 #if QT_VERSION >= 0x040800 00464 if (sslConfig.testSslOption(QSsl::SslOptionDisableCompression) && !shouldSslCompressBeDisabled) { 00465 setMetaData(QLatin1String("{internal~currenthost}LastUsedSslDisableCompressionFlag"), 00466 QString::number(true)); 00467 } 00468 #endif 00469 return 0; 00470 } 00471 Q_ASSERT(false); 00472 // Code flow never gets here but let's make the compiler happy. 00473 // More: the stack allocation of QSslSettings seems to be confusing the compiler; 00474 // in fact, any non-POD allocation does. 00475 // even a 'return 0;' directly after the allocation (so before the while(true)) 00476 // is ignored. definitely seems to be a compiler bug? - aseigo 00477 return 0; 00478 } 00479 00480 void TCPSlaveBase::disconnectFromHost() 00481 { 00482 kDebug(7027); 00483 d->host.clear(); 00484 d->ip.clear(); 00485 d->usingSSL = false; 00486 00487 if (d->socket.state() == KTcpSocket::UnconnectedState) { 00488 // discard incoming data - the remote host might have disconnected us in the meantime 00489 // but the visible effect of disconnectFromHost() should stay the same. 00490 d->socket.close(); 00491 return; 00492 } 00493 00494 //### maybe save a session for reuse on SSL shutdown if and when QSslSocket 00495 // does that. QCA::TLS can do it apparently but that is not enough if 00496 // we want to present that as KDE API. Not a big loss in any case. 00497 d->socket.disconnectFromHost(); 00498 if (d->socket.state() != KTcpSocket::UnconnectedState) 00499 d->socket.waitForDisconnected(-1); // wait for unsent data to be sent 00500 d->socket.close(); //whatever that means on a socket 00501 } 00502 00503 bool TCPSlaveBase::isAutoSsl() const 00504 { 00505 return d->autoSSL; 00506 } 00507 00508 bool TCPSlaveBase::isUsingSsl() const 00509 { 00510 return d->usingSSL; 00511 } 00512 00513 quint16 TCPSlaveBase::port() const 00514 { 00515 return d->port; 00516 } 00517 00518 bool TCPSlaveBase::atEnd() const 00519 { 00520 return d->socket.atEnd(); 00521 } 00522 00523 bool TCPSlaveBase::startSsl() 00524 { 00525 if (d->usingSSL) 00526 return false; 00527 return d->startTLSInternal(KTcpSocket::TlsV1) & ResultOk; 00528 } 00529 00530 TCPSlaveBase::SslResult TCPSlaveBase::TcpSlaveBasePrivate::startTLSInternal (KTcpSocket::SslVersion version, 00531 const QSslConfiguration& sslConfig, 00532 int waitForEncryptedTimeout) 00533 { 00534 q->selectClientCertificate(); 00535 00536 //setMetaData("ssl_session_id", d->kssl->session()->toString()); 00537 //### we don't support session reuse for now... 00538 usingSSL = true; 00539 #if QT_VERSION >= 0x040800 00540 kDebug(7027) << "Trying SSL handshake with protocol:" << version 00541 << ", SSL compression ON:" << sslConfig.testSslOption(QSsl::SslOptionDisableCompression); 00542 #endif 00543 // Set the SSL version to use... 00544 socket.setAdvertisedSslVersion(version); 00545 00546 // Set SSL configuration information 00547 if (!sslConfig.isNull()) 00548 socket.setSslConfiguration(sslConfig); 00549 00550 /* Usually ignoreSslErrors() would be called in the slot invoked by the sslErrors() 00551 signal but that would mess up the flow of control. We will check for errors 00552 anyway to decide if we want to continue connecting. Otherwise ignoreSslErrors() 00553 before connecting would be very insecure. */ 00554 socket.ignoreSslErrors(); 00555 socket.startClientEncryption(); 00556 const bool encryptionStarted = socket.waitForEncrypted(waitForEncryptedTimeout); 00557 00558 //Set metadata, among other things for the "SSL Details" dialog 00559 KSslCipher cipher = socket.sessionCipher(); 00560 00561 if (!encryptionStarted || socket.encryptionMode() != KTcpSocket::SslClientMode 00562 || cipher.isNull() || cipher.usedBits() == 0 || socket.peerCertificateChain().isEmpty()) { 00563 usingSSL = false; 00564 clearSslMetaData(); 00565 kDebug(7029) << "Initial SSL handshake failed. encryptionStarted is" 00566 << encryptionStarted << ", cipher.isNull() is" << cipher.isNull() 00567 << ", cipher.usedBits() is" << cipher.usedBits() 00568 << ", length of certificate chain is" << socket.peerCertificateChain().count() 00569 << ", the socket says:" << socket.errorString() 00570 << "and the list of SSL errors contains" 00571 << socket.sslErrors().count() << "items."; 00572 Q_FOREACH(const KSslError& sslError, socket.sslErrors()) { 00573 kDebug(7029) << "SSL ERROR: (" << sslError.error() << ")" << sslError.errorString(); 00574 } 00575 return ResultFailed | ResultFailedEarly; 00576 } 00577 00578 kDebug(7029) << "Cipher info - " 00579 << " advertised SSL protocol version" << socket.advertisedSslVersion() 00580 << " negotiated SSL protocol version" << socket.negotiatedSslVersion() 00581 << " authenticationMethod:" << cipher.authenticationMethod() 00582 << " encryptionMethod:" << cipher.encryptionMethod() 00583 << " keyExchangeMethod:" << cipher.keyExchangeMethod() 00584 << " name:" << cipher.name() 00585 << " supportedBits:" << cipher.supportedBits() 00586 << " usedBits:" << cipher.usedBits(); 00587 00588 sslErrors = socket.sslErrors(); 00589 00590 // TODO: review / rewrite / remove the comment 00591 // The app side needs the metadata now for the SSL error dialog (if any) but 00592 // the same metadata will be needed later, too. When "later" arrives the slave 00593 // may actually be connected to a different application that doesn't know 00594 // the metadata the slave sent to the previous application. 00595 // The quite important SSL indicator icon in Konqi's URL bar relies on metadata 00596 // from here, for example. And Konqi will be the second application to connect 00597 // to the slave. 00598 // Therefore we choose to have our metadata and send it, too :) 00599 setSslMetaData(); 00600 q->sendAndKeepMetaData(); 00601 00602 SslResult rc = q->verifyServerCertificate(); 00603 if (rc & ResultFailed) { 00604 usingSSL = false; 00605 clearSslMetaData(); 00606 kDebug(7029) << "server certificate verification failed."; 00607 socket.disconnectFromHost(); //Make the connection fail (cf. ignoreSslErrors()) 00608 return ResultFailed; 00609 } else if (rc & ResultOverridden) { 00610 kDebug(7029) << "server certificate verification failed but continuing at user's request."; 00611 } 00612 00613 //"warn" when starting SSL/TLS 00614 if (q->metaData("ssl_activate_warnings") == "TRUE" 00615 && q->metaData("ssl_was_in_use") == "FALSE" 00616 && sslSettings.warnOnEnter()) { 00617 00618 int msgResult = q->messageBox(i18n("You are about to enter secure mode. " 00619 "All transmissions will be encrypted " 00620 "unless otherwise noted.\nThis means " 00621 "that no third party will be able to " 00622 "easily observe your data in transit."), 00623 WarningYesNo, 00624 i18n("Security Information"), 00625 i18n("Display SSL &Information"), 00626 i18n("C&onnect"), 00627 "WarnOnEnterSSLMode"); 00628 if (msgResult == KMessageBox::Yes) { 00629 q->messageBox(SSLMessageBox /*==the SSL info dialog*/, host); 00630 } 00631 } 00632 00633 return rc; 00634 } 00635 00636 void TCPSlaveBase::selectClientCertificate() 00637 { 00638 #if 0 //hehe 00639 QString certname; // the cert to use this session 00640 bool send = false, prompt = false, save = false, forcePrompt = false; 00641 KSSLCertificateHome::KSSLAuthAction aa; 00642 00643 setMetaData("ssl_using_client_cert", "FALSE"); // we change this if needed 00644 00645 if (metaData("ssl_no_client_cert") == "TRUE") return; 00646 forcePrompt = (metaData("ssl_force_cert_prompt") == "TRUE"); 00647 00648 // Delete the old cert since we're certainly done with it now 00649 if (d->pkcs) { 00650 delete d->pkcs; 00651 d->pkcs = NULL; 00652 } 00653 00654 if (!d->kssl) return; 00655 00656 // Look for a general certificate 00657 if (!forcePrompt) { 00658 certname = KSSLCertificateHome::getDefaultCertificateName(&aa); 00659 switch (aa) { 00660 case KSSLCertificateHome::AuthSend: 00661 send = true; prompt = false; 00662 break; 00663 case KSSLCertificateHome::AuthDont: 00664 send = false; prompt = false; 00665 certname.clear(); 00666 break; 00667 case KSSLCertificateHome::AuthPrompt: 00668 send = false; prompt = true; 00669 break; 00670 default: 00671 break; 00672 } 00673 } 00674 00675 // Look for a certificate on a per-host basis as an override 00676 QString tmpcn = KSSLCertificateHome::getDefaultCertificateName(d->host, &aa); 00677 if (aa != KSSLCertificateHome::AuthNone) { // we must override 00678 switch (aa) { 00679 case KSSLCertificateHome::AuthSend: 00680 send = true; 00681 prompt = false; 00682 certname = tmpcn; 00683 break; 00684 case KSSLCertificateHome::AuthDont: 00685 send = false; 00686 prompt = false; 00687 certname.clear(); 00688 break; 00689 case KSSLCertificateHome::AuthPrompt: 00690 send = false; 00691 prompt = true; 00692 certname = tmpcn; 00693 break; 00694 default: 00695 break; 00696 } 00697 } 00698 00699 // Finally, we allow the application to override anything. 00700 if (hasMetaData("ssl_demand_certificate")) { 00701 certname = metaData("ssl_demand_certificate"); 00702 if (!certname.isEmpty()) { 00703 forcePrompt = false; 00704 prompt = false; 00705 send = true; 00706 } 00707 } 00708 00709 if (certname.isEmpty() && !prompt && !forcePrompt) return; 00710 00711 // Ok, we're supposed to prompt the user.... 00712 if (prompt || forcePrompt) { 00713 QStringList certs = KSSLCertificateHome::getCertificateList(); 00714 00715 QStringList::const_iterator it = certs.begin(); 00716 while (it != certs.end()) { 00717 KSSLPKCS12 *pkcs = KSSLCertificateHome::getCertificateByName(*it); 00718 if (pkcs && (!pkcs->getCertificate() || 00719 !pkcs->getCertificate()->x509V3Extensions().certTypeSSLClient())) { 00720 it = certs.erase(it); 00721 } else { 00722 ++it; 00723 } 00724 delete pkcs; 00725 } 00726 00727 if (certs.isEmpty()) return; // we had nothing else, and prompt failed 00728 00729 if (!QDBusConnection::sessionBus().interface()->isServiceRegistered("org.kde.kio.uiserver")) { 00730 KToolInvocation::startServiceByDesktopPath("kuiserver.desktop", 00731 QStringList()); 00732 } 00733 00734 QDBusInterface uis("org.kde.kio.uiserver", "/UIServer", "org.kde.KIO.UIServer"); 00735 00736 QDBusMessage retVal = uis.call("showSSLCertDialog", d->host, certs, metaData("window-id").toLongLong()); 00737 if (retVal.type() == QDBusMessage::ReplyMessage) { 00738 if (retVal.arguments().at(0).toBool()) { 00739 send = retVal.arguments().at(1).toBool(); 00740 save = retVal.arguments().at(2).toBool(); 00741 certname = retVal.arguments().at(3).toString(); 00742 } 00743 } 00744 } 00745 00746 // The user may have said to not send the certificate, 00747 // but to save the choice 00748 if (!send) { 00749 if (save) { 00750 KSSLCertificateHome::setDefaultCertificate(certname, d->host, 00751 false, false); 00752 } 00753 return; 00754 } 00755 00756 // We're almost committed. If we can read the cert, we'll send it now. 00757 KSSLPKCS12 *pkcs = KSSLCertificateHome::getCertificateByName(certname); 00758 if (!pkcs && KSSLCertificateHome::hasCertificateByName(certname)) { // We need the password 00759 KIO::AuthInfo ai; 00760 bool first = true; 00761 do { 00762 ai.prompt = i18n("Enter the certificate password:"); 00763 ai.caption = i18n("SSL Certificate Password"); 00764 ai.url.setProtocol("kssl"); 00765 ai.url.setHost(certname); 00766 ai.username = certname; 00767 ai.keepPassword = true; 00768 00769 bool showprompt; 00770 if (first) 00771 showprompt = !checkCachedAuthentication(ai); 00772 else 00773 showprompt = true; 00774 if (showprompt) { 00775 if (!openPasswordDialog(ai, first ? QString() : 00776 i18n("Unable to open the certificate. Try a new password?"))) 00777 break; 00778 } 00779 00780 first = false; 00781 pkcs = KSSLCertificateHome::getCertificateByName(certname, ai.password); 00782 } while (!pkcs); 00783 00784 } 00785 00786 // If we could open the certificate, let's send it 00787 if (pkcs) { 00788 if (!d->kssl->setClientCertificate(pkcs)) { 00789 messageBox(Information, i18n("The procedure to set the " 00790 "client certificate for the session " 00791 "failed."), i18n("SSL")); 00792 delete pkcs; // we don't need this anymore 00793 pkcs = 0L; 00794 } else { 00795 kDebug(7029) << "Client SSL certificate is being used."; 00796 setMetaData("ssl_using_client_cert", "TRUE"); 00797 if (save) { 00798 KSSLCertificateHome::setDefaultCertificate(certname, d->host, 00799 true, false); 00800 } 00801 } 00802 d->pkcs = pkcs; 00803 } 00804 #endif 00805 } 00806 00807 TCPSlaveBase::SslResult TCPSlaveBase::verifyServerCertificate() 00808 { 00809 d->sslNoUi = hasMetaData("ssl_no_ui") && (metaData("ssl_no_ui") != "FALSE"); 00810 00811 if (d->sslErrors.isEmpty()) { 00812 return ResultOk; 00813 } else if (d->sslNoUi) { 00814 return ResultFailed; 00815 } 00816 00817 QList<KSslError> fatalErrors = KSslCertificateManager::nonIgnorableErrors(d->sslErrors); 00818 if (!fatalErrors.isEmpty()) { 00819 //TODO message "sorry, fatal error, you can't override it" 00820 return ResultFailed; 00821 } 00822 00823 KSslCertificateManager *const cm = KSslCertificateManager::self(); 00824 KSslCertificateRule rule = cm->rule(d->socket.peerCertificateChain().first(), d->host); 00825 00826 // remove previously seen and acknowledged errors 00827 QList<KSslError> remainingErrors = rule.filterErrors(d->sslErrors); 00828 if (remainingErrors.isEmpty()) { 00829 kDebug(7029) << "Error list empty after removing errors to be ignored. Continuing."; 00830 return ResultOk | ResultOverridden; 00831 } 00832 00833 //### We don't ask to permanently reject the certificate 00834 00835 QString message = i18n("The server failed the authenticity check (%1).\n\n", d->host); 00836 Q_FOREACH (const KSslError &err, d->sslErrors) { 00837 message.append(err.errorString()); 00838 message.append('\n'); 00839 } 00840 message = message.trimmed(); 00841 00842 int msgResult; 00843 do { 00844 msgResult = messageBox(WarningYesNoCancel, message, 00845 i18n("Server Authentication"), 00846 i18n("&Details"), i18n("Co&ntinue")); 00847 if (msgResult == KMessageBox::Yes) { 00848 //Details was chosen- show the certificate and error details 00849 messageBox(SSLMessageBox /*the SSL info dialog*/, d->host); 00850 } else if (msgResult == KMessageBox::Cancel) { 00851 return ResultFailed; 00852 } 00853 //fall through on KMessageBox::No 00854 } while (msgResult == KMessageBox::Yes); 00855 00856 //Save the user's choice to ignore the SSL errors. 00857 00858 msgResult = messageBox(WarningYesNo, 00859 i18n("Would you like to accept this " 00860 "certificate forever without " 00861 "being prompted?"), 00862 i18n("Server Authentication"), 00863 i18n("&Forever"), 00864 i18n("&Current Session only")); 00865 QDateTime ruleExpiry = QDateTime::currentDateTime(); 00866 if (msgResult == KMessageBox::Yes) { 00867 //accept forever ("for a very long time") 00868 ruleExpiry = ruleExpiry.addYears(1000); 00869 } else { 00870 //accept "for a short time", half an hour. 00871 ruleExpiry = ruleExpiry.addSecs(30*60); 00872 } 00873 00874 //TODO special cases for wildcard domain name in the certificate! 00875 //rule = KSslCertificateRule(d->socket.peerCertificateChain().first(), whatever); 00876 00877 rule.setExpiryDateTime(ruleExpiry); 00878 rule.setIgnoredErrors(d->sslErrors); 00879 cm->setRule(rule); 00880 00881 return ResultOk | ResultOverridden; 00882 #if 0 //### need to to do something like the old code about the main and subframe stuff 00883 kDebug(7029) << "SSL HTTP frame the parent? " << metaData("main_frame_request"); 00884 if (!hasMetaData("main_frame_request") || metaData("main_frame_request") == "TRUE") { 00885 // Since we're the parent, we need to teach the child. 00886 setMetaData("ssl_parent_ip", d->ip); 00887 setMetaData("ssl_parent_cert", pc.toString()); 00888 // - Read from cache and see if there is a policy for this 00889 KSSLCertificateCache::KSSLCertificatePolicy cp = 00890 d->certCache->getPolicyByCertificate(pc); 00891 00892 // - validation code 00893 if (ksv != KSSLCertificate::Ok) { 00894 if (d->sslNoUi) { 00895 return -1; 00896 } 00897 00898 if (cp == KSSLCertificateCache::Unknown || 00899 cp == KSSLCertificateCache::Ambiguous) { 00900 cp = KSSLCertificateCache::Prompt; 00901 } else { 00902 // A policy was already set so let's honor that. 00903 permacache = d->certCache->isPermanent(pc); 00904 } 00905 00906 if (!_IPmatchesCN && cp == KSSLCertificateCache::Accept) { 00907 cp = KSSLCertificateCache::Prompt; 00908 // ksv = KSSLCertificate::Ok; 00909 } 00910 00912 00913 // - cache the results 00914 d->certCache->addCertificate(pc, cp, permacache); 00915 if (doAddHost) d->certCache->addHost(pc, d->host); 00916 } else { // Child frame 00917 // - Read from cache and see if there is a policy for this 00918 KSSLCertificateCache::KSSLCertificatePolicy cp = 00919 d->certCache->getPolicyByCertificate(pc); 00920 isChild = true; 00921 00922 // Check the cert and IP to make sure they're the same 00923 // as the parent frame 00924 bool certAndIPTheSame = (d->ip == metaData("ssl_parent_ip") && 00925 pc.toString() == metaData("ssl_parent_cert")); 00926 00927 if (ksv == KSSLCertificate::Ok) { 00928 if (certAndIPTheSame) { // success 00929 rc = 1; 00930 setMetaData("ssl_action", "accept"); 00931 } else { 00932 /* 00933 if (d->sslNoUi) { 00934 return -1; 00935 } 00936 result = messageBox(WarningYesNo, 00937 i18n("The certificate is valid but does not appear to have been assigned to this server. Do you wish to continue loading?"), 00938 i18n("Server Authentication")); 00939 if (result == KMessageBox::Yes) { // success 00940 rc = 1; 00941 setMetaData("ssl_action", "accept"); 00942 } else { // fail 00943 rc = -1; 00944 setMetaData("ssl_action", "reject"); 00945 } 00946 */ 00947 setMetaData("ssl_action", "accept"); 00948 rc = 1; // Let's accept this now. It's bad, but at least the user 00949 // will see potential attacks in KDE3 with the pseudo-lock 00950 // icon on the toolbar, and can investigate with the RMB 00951 } 00952 } else { 00953 if (d->sslNoUi) { 00954 return -1; 00955 } 00956 00957 if (cp == KSSLCertificateCache::Accept) { 00958 if (certAndIPTheSame) { // success 00959 rc = 1; 00960 setMetaData("ssl_action", "accept"); 00961 } else { // fail 00962 result = messageBox(WarningYesNo, 00963 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?"), 00964 i18n("Server Authentication")); 00965 if (result == KMessageBox::Yes) { 00966 rc = 1; 00967 setMetaData("ssl_action", "accept"); 00968 d->certCache->addHost(pc, d->host); 00969 } else { 00970 rc = -1; 00971 setMetaData("ssl_action", "reject"); 00972 } 00973 } 00974 } else if (cp == KSSLCertificateCache::Reject) { // fail 00975 messageBox(Information, i18n("SSL certificate is being rejected as requested. You can disable this in the KDE System Settings."), 00976 i18n("Server Authentication")); 00977 rc = -1; 00978 setMetaData("ssl_action", "reject"); 00979 } else { 00980 00982 00983 return rc; 00984 #endif //#if 0 00985 return ResultOk | ResultOverridden; 00986 } 00987 00988 00989 bool TCPSlaveBase::isConnected() const 00990 { 00991 //QSslSocket::isValid() and therefore KTcpSocket::isValid() are shady... 00992 return d->socket.state() == KTcpSocket::ConnectedState; 00993 } 00994 00995 00996 bool TCPSlaveBase::waitForResponse(int t) 00997 { 00998 if (d->socket.bytesAvailable()) { 00999 return true; 01000 } 01001 return d->socket.waitForReadyRead(t * 1000); 01002 } 01003 01004 void TCPSlaveBase::setBlocking(bool b) 01005 { 01006 if (!b) { 01007 kWarning(7029) << "Caller requested non-blocking mode, but that doesn't work"; 01008 return; 01009 } 01010 d->isBlocking = b; 01011 } 01012 01013 void TCPSlaveBase::virtual_hook(int id, void* data) 01014 { 01015 if (id == SlaveBase::AppConnectionMade) { 01016 d->sendSslMetaData(); 01017 } else { 01018 SlaveBase::virtual_hook(id, data); 01019 } 01020 }
This file is part of the KDE documentation.
Documentation copyright © 1996-2019 The KDE developers.
Generated on Mon Jan 21 2019 12:35:03 by doxygen 1.7.5.1 written by Dimitri van Heesch, © 1997-2006
Documentation copyright © 1996-2019 The KDE developers.
Generated on Mon Jan 21 2019 12:35:03 by doxygen 1.7.5.1 written by Dimitri van Heesch, © 1997-2006
KDE's Doxygen guidelines are available online.