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 // Find out if a hostname matches an SSL certificate's Common Name (including wildcards) 00531 static bool isMatchingHostname(const QString &cnIn, const QString &hostnameIn) 00532 { 00533 const QString cn = cnIn.toLower(); 00534 const QString hostname = hostnameIn.toLower(); 00535 00536 const int wildcard = cn.indexOf(QLatin1Char('*')); 00537 00538 // Check this is a wildcard cert, if not then just compare the strings 00539 if (wildcard < 0) 00540 return cn == hostname; 00541 00542 const int firstCnDot = cn.indexOf(QLatin1Char('.')); 00543 const int secondCnDot = cn.indexOf(QLatin1Char('.'), firstCnDot+1); 00544 00545 // Check at least 3 components 00546 if ((-1 == secondCnDot) || (secondCnDot+1 >= cn.length())) 00547 return false; 00548 00549 // Check * is last character of 1st component (ie. there's a following .) 00550 if (wildcard+1 != firstCnDot) 00551 return false; 00552 00553 // Check only one star 00554 if (cn.lastIndexOf(QLatin1Char('*')) != wildcard) 00555 return false; 00556 00557 // Check characters preceding * (if any) match 00558 if (wildcard && (hostname.leftRef(wildcard) != cn.leftRef(wildcard))) 00559 return false; 00560 00561 // Check characters following first . match 00562 if (hostname.midRef(hostname.indexOf(QLatin1Char('.'))) != cn.midRef(firstCnDot)) 00563 return false; 00564 00565 // Check if the hostname is an IP address, if so then wildcards are not allowed 00566 QHostAddress addr(hostname); 00567 if (!addr.isNull()) 00568 return false; 00569 00570 // Ok, I guess this was a wildcard CN and the hostname matches. 00571 return true; 00572 } 00573 00574 TCPSlaveBase::SslResult TCPSlaveBase::TcpSlaveBasePrivate::startTLSInternal (KTcpSocket::SslVersion version, 00575 const QSslConfiguration& sslConfig, 00576 int waitForEncryptedTimeout) 00577 { 00578 q->selectClientCertificate(); 00579 00580 //setMetaData("ssl_session_id", d->kssl->session()->toString()); 00581 //### we don't support session reuse for now... 00582 usingSSL = true; 00583 #if QT_VERSION >= 0x040800 00584 kDebug(7027) << "Trying SSL handshake with protocol:" << version 00585 << ", SSL compression ON:" << sslConfig.testSslOption(QSsl::SslOptionDisableCompression); 00586 #endif 00587 // Set the SSL version to use... 00588 socket.setAdvertisedSslVersion(version); 00589 00590 // Set SSL configuration information 00591 if (!sslConfig.isNull()) 00592 socket.setSslConfiguration(sslConfig); 00593 00594 /* Usually ignoreSslErrors() would be called in the slot invoked by the sslErrors() 00595 signal but that would mess up the flow of control. We will check for errors 00596 anyway to decide if we want to continue connecting. Otherwise ignoreSslErrors() 00597 before connecting would be very insecure. */ 00598 socket.ignoreSslErrors(); 00599 socket.startClientEncryption(); 00600 const bool encryptionStarted = socket.waitForEncrypted(waitForEncryptedTimeout); 00601 00602 //Set metadata, among other things for the "SSL Details" dialog 00603 KSslCipher cipher = socket.sessionCipher(); 00604 00605 if (!encryptionStarted || socket.encryptionMode() != KTcpSocket::SslClientMode 00606 || cipher.isNull() || cipher.usedBits() == 0 || socket.peerCertificateChain().isEmpty()) { 00607 usingSSL = false; 00608 clearSslMetaData(); 00609 kDebug(7029) << "Initial SSL handshake failed. encryptionStarted is" 00610 << encryptionStarted << ", cipher.isNull() is" << cipher.isNull() 00611 << ", cipher.usedBits() is" << cipher.usedBits() 00612 << ", length of certificate chain is" << socket.peerCertificateChain().count() 00613 << ", the socket says:" << socket.errorString() 00614 << "and the list of SSL errors contains" 00615 << socket.sslErrors().count() << "items."; 00616 Q_FOREACH(const KSslError& sslError, socket.sslErrors()) { 00617 kDebug(7029) << "SSL ERROR: (" << sslError.error() << ")" << sslError.errorString(); 00618 } 00619 return ResultFailed | ResultFailedEarly; 00620 } 00621 00622 kDebug(7029) << "Cipher info - " 00623 << " advertised SSL protocol version" << socket.advertisedSslVersion() 00624 << " negotiated SSL protocol version" << socket.negotiatedSslVersion() 00625 << " authenticationMethod:" << cipher.authenticationMethod() 00626 << " encryptionMethod:" << cipher.encryptionMethod() 00627 << " keyExchangeMethod:" << cipher.keyExchangeMethod() 00628 << " name:" << cipher.name() 00629 << " supportedBits:" << cipher.supportedBits() 00630 << " usedBits:" << cipher.usedBits(); 00631 00632 // Since we connect by IP (cf. KIO::HostInfo) the SSL code will not recognize 00633 // that the site certificate belongs to the domain. We therefore do the 00634 // domain<->certificate matching here. 00635 sslErrors = socket.sslErrors(); 00636 QSslCertificate peerCert = socket.peerCertificateChain().first(); 00637 QMutableListIterator<KSslError> it(sslErrors); 00638 while (it.hasNext()) { 00639 // As of 4.4.0 Qt does not assign a certificate to the QSslError it emits 00640 // *in the case of HostNameMismatch*. A HostNameMismatch, however, will always 00641 // be an error of the peer certificate so we just don't check the error's 00642 // certificate(). 00643 00644 // Remove all HostNameMismatch, we have to redo name checking later. 00645 if (it.next().error() == KSslError::HostNameMismatch) { 00646 it.remove(); 00647 } 00648 } 00649 // Redo name checking here and (re-)insert HostNameMismatch to sslErrors if 00650 // host name does not match any of the names in server certificate. 00651 // QSslSocket may not report HostNameMismatch error, when server 00652 // certificate was issued for the IP we are connecting to. 00653 QStringList domainPatterns(peerCert.subjectInfo(QSslCertificate::CommonName)); 00654 domainPatterns += peerCert.alternateSubjectNames().values(QSsl::DnsEntry); 00655 bool names_match = false; 00656 foreach (const QString &dp, domainPatterns) { 00657 if (isMatchingHostname(dp, host)) { 00658 names_match = true; 00659 break; 00660 } 00661 } 00662 if (!names_match) { 00663 sslErrors.insert(0, KSslError(KSslError::HostNameMismatch, peerCert)); 00664 } 00665 00666 // TODO: review / rewrite / remove the comment 00667 // The app side needs the metadata now for the SSL error dialog (if any) but 00668 // the same metadata will be needed later, too. When "later" arrives the slave 00669 // may actually be connected to a different application that doesn't know 00670 // the metadata the slave sent to the previous application. 00671 // The quite important SSL indicator icon in Konqi's URL bar relies on metadata 00672 // from here, for example. And Konqi will be the second application to connect 00673 // to the slave. 00674 // Therefore we choose to have our metadata and send it, too :) 00675 setSslMetaData(); 00676 q->sendAndKeepMetaData(); 00677 00678 SslResult rc = q->verifyServerCertificate(); 00679 if (rc & ResultFailed) { 00680 usingSSL = false; 00681 clearSslMetaData(); 00682 kDebug(7029) << "server certificate verification failed."; 00683 socket.disconnectFromHost(); //Make the connection fail (cf. ignoreSslErrors()) 00684 return ResultFailed; 00685 } else if (rc & ResultOverridden) { 00686 kDebug(7029) << "server certificate verification failed but continuing at user's request."; 00687 } 00688 00689 //"warn" when starting SSL/TLS 00690 if (q->metaData("ssl_activate_warnings") == "TRUE" 00691 && q->metaData("ssl_was_in_use") == "FALSE" 00692 && sslSettings.warnOnEnter()) { 00693 00694 int msgResult = q->messageBox(i18n("You are about to enter secure mode. " 00695 "All transmissions will be encrypted " 00696 "unless otherwise noted.\nThis means " 00697 "that no third party will be able to " 00698 "easily observe your data in transit."), 00699 WarningYesNo, 00700 i18n("Security Information"), 00701 i18n("Display SSL &Information"), 00702 i18n("C&onnect"), 00703 "WarnOnEnterSSLMode"); 00704 if (msgResult == KMessageBox::Yes) { 00705 q->messageBox(SSLMessageBox /*==the SSL info dialog*/, host); 00706 } 00707 } 00708 00709 return rc; 00710 } 00711 00712 void TCPSlaveBase::selectClientCertificate() 00713 { 00714 #if 0 //hehe 00715 QString certname; // the cert to use this session 00716 bool send = false, prompt = false, save = false, forcePrompt = false; 00717 KSSLCertificateHome::KSSLAuthAction aa; 00718 00719 setMetaData("ssl_using_client_cert", "FALSE"); // we change this if needed 00720 00721 if (metaData("ssl_no_client_cert") == "TRUE") return; 00722 forcePrompt = (metaData("ssl_force_cert_prompt") == "TRUE"); 00723 00724 // Delete the old cert since we're certainly done with it now 00725 if (d->pkcs) { 00726 delete d->pkcs; 00727 d->pkcs = NULL; 00728 } 00729 00730 if (!d->kssl) return; 00731 00732 // Look for a general certificate 00733 if (!forcePrompt) { 00734 certname = KSSLCertificateHome::getDefaultCertificateName(&aa); 00735 switch (aa) { 00736 case KSSLCertificateHome::AuthSend: 00737 send = true; prompt = false; 00738 break; 00739 case KSSLCertificateHome::AuthDont: 00740 send = false; prompt = false; 00741 certname.clear(); 00742 break; 00743 case KSSLCertificateHome::AuthPrompt: 00744 send = false; prompt = true; 00745 break; 00746 default: 00747 break; 00748 } 00749 } 00750 00751 // Look for a certificate on a per-host basis as an override 00752 QString tmpcn = KSSLCertificateHome::getDefaultCertificateName(d->host, &aa); 00753 if (aa != KSSLCertificateHome::AuthNone) { // we must override 00754 switch (aa) { 00755 case KSSLCertificateHome::AuthSend: 00756 send = true; 00757 prompt = false; 00758 certname = tmpcn; 00759 break; 00760 case KSSLCertificateHome::AuthDont: 00761 send = false; 00762 prompt = false; 00763 certname.clear(); 00764 break; 00765 case KSSLCertificateHome::AuthPrompt: 00766 send = false; 00767 prompt = true; 00768 certname = tmpcn; 00769 break; 00770 default: 00771 break; 00772 } 00773 } 00774 00775 // Finally, we allow the application to override anything. 00776 if (hasMetaData("ssl_demand_certificate")) { 00777 certname = metaData("ssl_demand_certificate"); 00778 if (!certname.isEmpty()) { 00779 forcePrompt = false; 00780 prompt = false; 00781 send = true; 00782 } 00783 } 00784 00785 if (certname.isEmpty() && !prompt && !forcePrompt) return; 00786 00787 // Ok, we're supposed to prompt the user.... 00788 if (prompt || forcePrompt) { 00789 QStringList certs = KSSLCertificateHome::getCertificateList(); 00790 00791 QStringList::const_iterator it = certs.begin(); 00792 while (it != certs.end()) { 00793 KSSLPKCS12 *pkcs = KSSLCertificateHome::getCertificateByName(*it); 00794 if (pkcs && (!pkcs->getCertificate() || 00795 !pkcs->getCertificate()->x509V3Extensions().certTypeSSLClient())) { 00796 it = certs.erase(it); 00797 } else { 00798 ++it; 00799 } 00800 delete pkcs; 00801 } 00802 00803 if (certs.isEmpty()) return; // we had nothing else, and prompt failed 00804 00805 if (!QDBusConnection::sessionBus().interface()->isServiceRegistered("org.kde.kio.uiserver")) { 00806 KToolInvocation::startServiceByDesktopPath("kuiserver.desktop", 00807 QStringList()); 00808 } 00809 00810 QDBusInterface uis("org.kde.kio.uiserver", "/UIServer", "org.kde.KIO.UIServer"); 00811 00812 QDBusMessage retVal = uis.call("showSSLCertDialog", d->host, certs, metaData("window-id").toLongLong()); 00813 if (retVal.type() == QDBusMessage::ReplyMessage) { 00814 if (retVal.arguments().at(0).toBool()) { 00815 send = retVal.arguments().at(1).toBool(); 00816 save = retVal.arguments().at(2).toBool(); 00817 certname = retVal.arguments().at(3).toString(); 00818 } 00819 } 00820 } 00821 00822 // The user may have said to not send the certificate, 00823 // but to save the choice 00824 if (!send) { 00825 if (save) { 00826 KSSLCertificateHome::setDefaultCertificate(certname, d->host, 00827 false, false); 00828 } 00829 return; 00830 } 00831 00832 // We're almost committed. If we can read the cert, we'll send it now. 00833 KSSLPKCS12 *pkcs = KSSLCertificateHome::getCertificateByName(certname); 00834 if (!pkcs && KSSLCertificateHome::hasCertificateByName(certname)) { // We need the password 00835 KIO::AuthInfo ai; 00836 bool first = true; 00837 do { 00838 ai.prompt = i18n("Enter the certificate password:"); 00839 ai.caption = i18n("SSL Certificate Password"); 00840 ai.url.setProtocol("kssl"); 00841 ai.url.setHost(certname); 00842 ai.username = certname; 00843 ai.keepPassword = true; 00844 00845 bool showprompt; 00846 if (first) 00847 showprompt = !checkCachedAuthentication(ai); 00848 else 00849 showprompt = true; 00850 if (showprompt) { 00851 if (!openPasswordDialog(ai, first ? QString() : 00852 i18n("Unable to open the certificate. Try a new password?"))) 00853 break; 00854 } 00855 00856 first = false; 00857 pkcs = KSSLCertificateHome::getCertificateByName(certname, ai.password); 00858 } while (!pkcs); 00859 00860 } 00861 00862 // If we could open the certificate, let's send it 00863 if (pkcs) { 00864 if (!d->kssl->setClientCertificate(pkcs)) { 00865 messageBox(Information, i18n("The procedure to set the " 00866 "client certificate for the session " 00867 "failed."), i18n("SSL")); 00868 delete pkcs; // we don't need this anymore 00869 pkcs = 0L; 00870 } else { 00871 kDebug(7029) << "Client SSL certificate is being used."; 00872 setMetaData("ssl_using_client_cert", "TRUE"); 00873 if (save) { 00874 KSSLCertificateHome::setDefaultCertificate(certname, d->host, 00875 true, false); 00876 } 00877 } 00878 d->pkcs = pkcs; 00879 } 00880 #endif 00881 } 00882 00883 TCPSlaveBase::SslResult TCPSlaveBase::verifyServerCertificate() 00884 { 00885 d->sslNoUi = hasMetaData("ssl_no_ui") && (metaData("ssl_no_ui") != "FALSE"); 00886 00887 if (d->sslErrors.isEmpty()) { 00888 return ResultOk; 00889 } else if (d->sslNoUi) { 00890 return ResultFailed; 00891 } 00892 00893 QList<KSslError> fatalErrors = KSslCertificateManager::nonIgnorableErrors(d->sslErrors); 00894 if (!fatalErrors.isEmpty()) { 00895 //TODO message "sorry, fatal error, you can't override it" 00896 return ResultFailed; 00897 } 00898 00899 KSslCertificateManager *const cm = KSslCertificateManager::self(); 00900 KSslCertificateRule rule = cm->rule(d->socket.peerCertificateChain().first(), d->host); 00901 00902 // remove previously seen and acknowledged errors 00903 QList<KSslError> remainingErrors = rule.filterErrors(d->sslErrors); 00904 if (remainingErrors.isEmpty()) { 00905 kDebug(7029) << "Error list empty after removing errors to be ignored. Continuing."; 00906 return ResultOk | ResultOverridden; 00907 } 00908 00909 //### We don't ask to permanently reject the certificate 00910 00911 QString message = i18n("The server failed the authenticity check (%1).\n\n", d->host); 00912 Q_FOREACH (const KSslError &err, d->sslErrors) { 00913 message.append(err.errorString()); 00914 message.append('\n'); 00915 } 00916 message = message.trimmed(); 00917 00918 int msgResult; 00919 do { 00920 msgResult = messageBox(WarningYesNoCancel, message, 00921 i18n("Server Authentication"), 00922 i18n("&Details"), i18n("Co&ntinue")); 00923 if (msgResult == KMessageBox::Yes) { 00924 //Details was chosen- show the certificate and error details 00925 messageBox(SSLMessageBox /*the SSL info dialog*/, d->host); 00926 } else if (msgResult == KMessageBox::Cancel) { 00927 return ResultFailed; 00928 } 00929 //fall through on KMessageBox::No 00930 } while (msgResult == KMessageBox::Yes); 00931 00932 //Save the user's choice to ignore the SSL errors. 00933 00934 msgResult = messageBox(WarningYesNo, 00935 i18n("Would you like to accept this " 00936 "certificate forever without " 00937 "being prompted?"), 00938 i18n("Server Authentication"), 00939 i18n("&Forever"), 00940 i18n("&Current Session only")); 00941 QDateTime ruleExpiry = QDateTime::currentDateTime(); 00942 if (msgResult == KMessageBox::Yes) { 00943 //accept forever ("for a very long time") 00944 ruleExpiry = ruleExpiry.addYears(1000); 00945 } else { 00946 //accept "for a short time", half an hour. 00947 ruleExpiry = ruleExpiry.addSecs(30*60); 00948 } 00949 00950 //TODO special cases for wildcard domain name in the certificate! 00951 //rule = KSslCertificateRule(d->socket.peerCertificateChain().first(), whatever); 00952 00953 rule.setExpiryDateTime(ruleExpiry); 00954 rule.setIgnoredErrors(d->sslErrors); 00955 cm->setRule(rule); 00956 00957 return ResultOk | ResultOverridden; 00958 #if 0 //### need to to do something like the old code about the main and subframe stuff 00959 kDebug(7029) << "SSL HTTP frame the parent? " << metaData("main_frame_request"); 00960 if (!hasMetaData("main_frame_request") || metaData("main_frame_request") == "TRUE") { 00961 // Since we're the parent, we need to teach the child. 00962 setMetaData("ssl_parent_ip", d->ip); 00963 setMetaData("ssl_parent_cert", pc.toString()); 00964 // - Read from cache and see if there is a policy for this 00965 KSSLCertificateCache::KSSLCertificatePolicy cp = 00966 d->certCache->getPolicyByCertificate(pc); 00967 00968 // - validation code 00969 if (ksv != KSSLCertificate::Ok) { 00970 if (d->sslNoUi) { 00971 return -1; 00972 } 00973 00974 if (cp == KSSLCertificateCache::Unknown || 00975 cp == KSSLCertificateCache::Ambiguous) { 00976 cp = KSSLCertificateCache::Prompt; 00977 } else { 00978 // A policy was already set so let's honor that. 00979 permacache = d->certCache->isPermanent(pc); 00980 } 00981 00982 if (!_IPmatchesCN && cp == KSSLCertificateCache::Accept) { 00983 cp = KSSLCertificateCache::Prompt; 00984 // ksv = KSSLCertificate::Ok; 00985 } 00986 00988 00989 // - cache the results 00990 d->certCache->addCertificate(pc, cp, permacache); 00991 if (doAddHost) d->certCache->addHost(pc, d->host); 00992 } else { // Child frame 00993 // - Read from cache and see if there is a policy for this 00994 KSSLCertificateCache::KSSLCertificatePolicy cp = 00995 d->certCache->getPolicyByCertificate(pc); 00996 isChild = true; 00997 00998 // Check the cert and IP to make sure they're the same 00999 // as the parent frame 01000 bool certAndIPTheSame = (d->ip == metaData("ssl_parent_ip") && 01001 pc.toString() == metaData("ssl_parent_cert")); 01002 01003 if (ksv == KSSLCertificate::Ok) { 01004 if (certAndIPTheSame) { // success 01005 rc = 1; 01006 setMetaData("ssl_action", "accept"); 01007 } else { 01008 /* 01009 if (d->sslNoUi) { 01010 return -1; 01011 } 01012 result = messageBox(WarningYesNo, 01013 i18n("The certificate is valid but does not appear to have been assigned to this server. Do you wish to continue loading?"), 01014 i18n("Server Authentication")); 01015 if (result == KMessageBox::Yes) { // success 01016 rc = 1; 01017 setMetaData("ssl_action", "accept"); 01018 } else { // fail 01019 rc = -1; 01020 setMetaData("ssl_action", "reject"); 01021 } 01022 */ 01023 setMetaData("ssl_action", "accept"); 01024 rc = 1; // Let's accept this now. It's bad, but at least the user 01025 // will see potential attacks in KDE3 with the pseudo-lock 01026 // icon on the toolbar, and can investigate with the RMB 01027 } 01028 } else { 01029 if (d->sslNoUi) { 01030 return -1; 01031 } 01032 01033 if (cp == KSSLCertificateCache::Accept) { 01034 if (certAndIPTheSame) { // success 01035 rc = 1; 01036 setMetaData("ssl_action", "accept"); 01037 } else { // fail 01038 result = messageBox(WarningYesNo, 01039 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?"), 01040 i18n("Server Authentication")); 01041 if (result == KMessageBox::Yes) { 01042 rc = 1; 01043 setMetaData("ssl_action", "accept"); 01044 d->certCache->addHost(pc, d->host); 01045 } else { 01046 rc = -1; 01047 setMetaData("ssl_action", "reject"); 01048 } 01049 } 01050 } else if (cp == KSSLCertificateCache::Reject) { // fail 01051 messageBox(Information, i18n("SSL certificate is being rejected as requested. You can disable this in the KDE System Settings."), 01052 i18n("Server Authentication")); 01053 rc = -1; 01054 setMetaData("ssl_action", "reject"); 01055 } else { 01056 01058 01059 return rc; 01060 #endif //#if 0 01061 return ResultOk | ResultOverridden; 01062 } 01063 01064 01065 bool TCPSlaveBase::isConnected() const 01066 { 01067 //QSslSocket::isValid() and therefore KTcpSocket::isValid() are shady... 01068 return d->socket.state() == KTcpSocket::ConnectedState; 01069 } 01070 01071 01072 bool TCPSlaveBase::waitForResponse(int t) 01073 { 01074 if (d->socket.bytesAvailable()) { 01075 return true; 01076 } 01077 return d->socket.waitForReadyRead(t * 1000); 01078 } 01079 01080 void TCPSlaveBase::setBlocking(bool b) 01081 { 01082 if (!b) { 01083 kWarning(7029) << "Caller requested non-blocking mode, but that doesn't work"; 01084 return; 01085 } 01086 d->isBlocking = b; 01087 } 01088 01089 void TCPSlaveBase::virtual_hook(int id, void* data) 01090 { 01091 if (id == SlaveBase::AppConnectionMade) { 01092 d->sendSslMetaData(); 01093 } else { 01094 SlaveBase::virtual_hook(id, data); 01095 } 01096 }
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Wed Nov 28 2012 09:50:38 by doxygen 1.7.5 written by Dimitri van Heesch, © 1997-2006
Documentation copyright © 1996-2012 The KDE developers.
Generated on Wed Nov 28 2012 09:50:38 by doxygen 1.7.5 written by Dimitri van Heesch, © 1997-2006
KDE's Doxygen guidelines are available online.