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