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