• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdelibs-4.8.5 API Reference
  • KDE Home
  • Contact Us
 

KIO

  • kio
  • kio
tcpslavebase.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2000 Alex Zepeda <zipzippy@sonic.net>
3  * Copyright (C) 2001-2003 George Staikos <staikos@kde.org>
4  * Copyright (C) 2001 Dawit Alemayehu <adawit@kde.org>
5  * Copyright (C) 2007,2008 Andreas Hartmetz <ahartmetz@gmail.com>
6  * Copyright (C) 2008 Roland Harnau <tau@gmx.eu>
7  * Copyright (C) 2010 Richard Moore <rich@kde.org>
8  *
9  * This file is part of the KDE project
10  *
11  * This library is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Library General Public
13  * License as published by the Free Software Foundation; either
14  * version 2 of the License, or (at your option) any later version.
15  *
16  * This library is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19  * Library General Public License for more details.
20  *
21  * You should have received a copy of the GNU Library General Public License
22  * along with this library; see the file COPYING.LIB. If not, write to
23  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24  * Boston, MA 02110-1301, USA.
25  */
26 
27 #include "tcpslavebase.h"
28 
29 #include <config.h>
30 
31 #include <kdebug.h>
32 #include <kconfiggroup.h>
33 #include <ksslcertificatemanager.h>
34 #include <ksslsettings.h>
35 #include <kmessagebox.h>
36 #include <klocale.h>
37 #include <ktoolinvocation.h>
38 #include <network/ktcpsocket.h>
39 
40 #include <QtCore/QDataStream>
41 #include <QtCore/QTime>
42 #include <QtNetwork/QTcpSocket>
43 #include <QtNetwork/QHostInfo>
44 #include <QtNetwork/QSslConfiguration>
45 #include <QtDBus/QtDBus>
46 
47 
48 using namespace KIO;
49 //using namespace KNetwork;
50 
51 typedef QMap<QString, QString> StringStringMap;
52 Q_DECLARE_METATYPE(StringStringMap)
53 
54 namespace KIO {
55 Q_DECLARE_OPERATORS_FOR_FLAGS(TCPSlaveBase::SslResult)
56 }
57 
58 //TODO Proxy support whichever way works; KPAC reportedly does *not* work.
59 //NOTE kded_proxyscout may or may not be interesting
60 
61 //TODO resurrect SSL session recycling; this means save the session on disconnect and look
62 //for a reusable session on connect. Consider how HTTP persistent connections interact with that.
63 
64 //TODO in case we support SSL-lessness we need static KTcpSocket::sslAvailable() and check it
65 //in most places we ATM check for d->isSSL.
66 
67 //TODO check if d->isBlocking is honored everywhere it makes sense
68 
69 //TODO fold KSSLSetting and KSSLCertificateHome into KSslSettings and use that everywhere.
70 
71 //TODO recognize partially encrypted websites as "somewhat safe"
72 
73 /* List of dialogs/messageboxes we need to use (current code location in parentheses)
74  - Can the "dontAskAgainName" thing be improved?
75 
76  - "SSLCertDialog" [select client cert] (SlaveInterface)
77  - Enter password for client certificate (inline)
78  - Password for client cert was wrong. Please reenter. (inline)
79  - Setting client cert failed. [doesn't give reason] (inline)
80  - "SSLInfoDialog" [mostly server cert info] (SlaveInterface)
81  - You are about to enter secure mode. Security information/Display SSL information/Connect (inline)
82  - You are about to leave secure mode. Security information/Continue loading/Abort (inline)
83  - Hostname mismatch: Continue/Details/Cancel (inline)
84  - IP address mismatch: Continue/Details/Cancel (inline)
85  - Certificate failed authenticity check: Continue/Details/Cancel (inline)
86  - Would you like to accept this certificate forever: Yes/No/Current sessions only (inline)
87  */
88 
89 
91 class TCPSlaveBase::TcpSlaveBasePrivate
92 {
93 public:
94  TcpSlaveBasePrivate(TCPSlaveBase* qq) : q(qq) {}
95 
96  void setSslMetaData()
97  {
98  sslMetaData.insert("ssl_in_use", "TRUE");
99  KSslCipher cipher = socket.sessionCipher();
100  sslMetaData.insert("ssl_protocol_version", socket.negotiatedSslVersionName());
101  QString sslCipher = cipher.encryptionMethod() + '\n';
102  sslCipher += cipher.authenticationMethod() + '\n';
103  sslCipher += cipher.keyExchangeMethod() + '\n';
104  sslCipher += cipher.digestMethod();
105  sslMetaData.insert("ssl_cipher", sslCipher);
106  sslMetaData.insert("ssl_cipher_name", cipher.name());
107  sslMetaData.insert("ssl_cipher_used_bits", QString::number(cipher.usedBits()));
108  sslMetaData.insert("ssl_cipher_bits", QString::number(cipher.supportedBits()));
109  sslMetaData.insert("ssl_peer_ip", ip);
110 
111  // try to fill in the blanks, i.e. missing certificates, and just assume that
112  // those belong to the peer (==website or similar) certificate.
113  for (int i = 0; i < sslErrors.count(); i++) {
114  if (sslErrors[i].certificate().isNull()) {
115  sslErrors[i] = KSslError(sslErrors[i].error(),
116  socket.peerCertificateChain()[0]);
117  }
118  }
119 
120  QString errorStr;
121  // encode the two-dimensional numeric error list using '\n' and '\t' as outer and inner separators
122  Q_FOREACH (const QSslCertificate &cert, socket.peerCertificateChain()) {
123  Q_FOREACH (const KSslError &error, sslErrors) {
124  if (error.certificate() == cert) {
125  errorStr += QString::number(static_cast<int>(error.error())) + '\t';
126  }
127  }
128  if (errorStr.endsWith('\t')) {
129  errorStr.chop(1);
130  }
131  errorStr += '\n';
132  }
133  errorStr.chop(1);
134  sslMetaData.insert("ssl_cert_errors", errorStr);
135 
136  QString peerCertChain;
137  Q_FOREACH (const QSslCertificate &cert, socket.peerCertificateChain()) {
138  peerCertChain.append(cert.toPem());
139  peerCertChain.append('\x01');
140  }
141  peerCertChain.chop(1);
142  sslMetaData.insert("ssl_peer_chain", peerCertChain);
143  sendSslMetaData();
144  }
145 
146  void clearSslMetaData()
147  {
148  sslMetaData.clear();
149  sslMetaData.insert("ssl_in_use", "FALSE");
150  sendSslMetaData();
151  }
152 
153  void sendSslMetaData()
154  {
155  MetaData::ConstIterator it = sslMetaData.constBegin();
156  for (; it != sslMetaData.constEnd(); ++it) {
157  q->setMetaData(it.key(), it.value());
158  }
159  }
160 
161  SslResult startTLSInternal(KTcpSocket::SslVersion sslVersion,
162  const QSslConfiguration& configuration = QSslConfiguration(),
163  int waitForEncryptedTimeout = -1);
164 
165  TCPSlaveBase* q;
166 
167  bool isBlocking;
168 
169  KTcpSocket socket;
170 
171  QString host;
172  QString ip;
173  quint16 port;
174  QByteArray serviceName;
175 
176  KSSLSettings sslSettings;
177  bool usingSSL;
178  bool autoSSL;
179  bool sslNoUi; // If true, we just drop the connection silently
180  // if SSL certificate check fails in some way.
181  QList<KSslError> sslErrors;
182 
183  MetaData sslMetaData;
184 };
185 
186 
187 //### uh, is this a good idea??
188 QIODevice *TCPSlaveBase::socket() const
189 {
190  return &d->socket;
191 }
192 
193 
194 TCPSlaveBase::TCPSlaveBase(const QByteArray &protocol,
195  const QByteArray &poolSocket,
196  const QByteArray &appSocket,
197  bool autoSSL)
198  : SlaveBase(protocol, poolSocket, appSocket),
199  d(new TcpSlaveBasePrivate(this))
200 {
201  d->isBlocking = true;
202  d->port = 0;
203  d->serviceName = protocol;
204  d->usingSSL = false;
205  d->autoSSL = autoSSL;
206  d->sslNoUi = false;
207  // Limit the read buffer size to 14 MB (14*1024*1024) (based on the upload limit
208  // in TransferJob::slotDataReq). See the docs for QAbstractSocket::setReadBufferSize
209  // and the BR# 187876 to understand why setting this limit is necessary.
210  d->socket.setReadBufferSize(14680064);
211 }
212 
213 
214 TCPSlaveBase::~TCPSlaveBase()
215 {
216  delete d;
217 }
218 
219 
220 ssize_t TCPSlaveBase::write(const char *data, ssize_t len)
221 {
222  ssize_t written = d->socket.write(data, len);
223  if (written == -1) {
224  kDebug(7027) << "d->socket.write() returned -1! Socket error is"
225  << d->socket.error() << ", Socket state is" << d->socket.state();
226  }
227 
228  bool success = false;
229  if (d->isBlocking) {
230  // Drain the tx buffer
231  success = d->socket.waitForBytesWritten(-1);
232  } else {
233  // ### I don't know how to make sure that all data does get written at some point
234  // without doing it now. There is no event loop to do it behind the scenes.
235  // Polling in the dispatch() loop? Something timeout based?
236  success = d->socket.waitForBytesWritten(0);
237  }
238 
239  d->socket.flush(); //this is supposed to get the data on the wire faster
240 
241  if (d->socket.state() != KTcpSocket::ConnectedState || !success) {
242  kDebug(7027) << "Write failed, will return -1! Socket error is"
243  << d->socket.error() << ", Socket state is" << d->socket.state()
244  << "Return value of waitForBytesWritten() is" << success;
245  return -1;
246  }
247 
248  return written;
249 }
250 
251 
252 ssize_t TCPSlaveBase::read(char* data, ssize_t len)
253 {
254  if (d->usingSSL && (d->socket.encryptionMode() != KTcpSocket::SslClientMode)) {
255  d->clearSslMetaData();
256  kDebug(7029) << "lost SSL connection.";
257  return -1;
258  }
259 
260  if (!d->socket.bytesAvailable()) {
261  const int timeout = d->isBlocking ? -1 : (readTimeout() * 1000);
262  d->socket.waitForReadyRead(timeout);
263  }
264 #if 0
265  // Do not do this because its only benefit is to cause a nasty side effect
266  // upstream in Qt. See BR# 260769.
267  else if (d->socket.encryptionMode() != KTcpSocket::SslClientMode ||
268  QNetworkProxy::applicationProxy().type() == QNetworkProxy::NoProxy) {
269  // we only do this when it doesn't trigger Qt socket bugs. When it doesn't break anything
270  // it seems to help performance.
271  d->socket.waitForReadyRead(0);
272  }
273 #endif
274  return d->socket.read(data, len);
275 }
276 
277 
278 ssize_t TCPSlaveBase::readLine(char *data, ssize_t len)
279 {
280  if (d->usingSSL && (d->socket.encryptionMode() != KTcpSocket::SslClientMode)) {
281  d->clearSslMetaData();
282  kDebug(7029) << "lost SSL connection.";
283  return -1;
284  }
285 
286  const int timeout = (d->isBlocking ? -1: (readTimeout() * 1000));
287  ssize_t readTotal = 0;
288  do {
289  if (!d->socket.bytesAvailable())
290  d->socket.waitForReadyRead(timeout);
291  ssize_t readStep = d->socket.readLine(&data[readTotal], len-readTotal);
292  if (readStep == -1 || (readStep == 0 && d->socket.state() != KTcpSocket::ConnectedState)) {
293  return -1;
294  }
295  readTotal += readStep;
296  } while (readTotal == 0 || data[readTotal-1] != '\n');
297 
298  return readTotal;
299 }
300 
301 
302 bool TCPSlaveBase::connectToHost(const QString &/*protocol*/,
303  const QString &host,
304  quint16 port)
305 {
306  QString errorString;
307  const int errCode = connectToHost(host, port, &errorString);
308  if (errCode == 0)
309  return true;
310 
311  error(errCode, errorString);
312  return false;
313 }
314 
315 int TCPSlaveBase::connectToHost(const QString& host, quint16 port, QString* errorString)
316 {
317  d->clearSslMetaData(); //We have separate connection and SSL setup phases
318 
319  if (errorString) {
320  errorString->clear(); // clear prior error messages.
321  }
322 
323  d->socket.setVerificationPeerName(host); // Used for ssl certificate verification (SNI)
324 
325  // - leaving SSL - warn before we even connect
326  //### see if it makes sense to move this into the HTTP ioslave which is the only
327  // user.
328  if (metaData("main_frame_request") == "TRUE" //### this looks *really* unreliable
329  && metaData("ssl_activate_warnings") == "TRUE"
330  && metaData("ssl_was_in_use") == "TRUE"
331  && !d->autoSSL) {
332  KSSLSettings kss;
333  if (kss.warnOnLeave()) {
334  int result = messageBox(i18n("You are about to leave secure "
335  "mode. Transmissions will no "
336  "longer be encrypted.\nThis "
337  "means that a third party could "
338  "observe your data in transit."),
339  WarningContinueCancel,
340  i18n("Security Information"),
341  i18n("C&ontinue Loading"), QString(),
342  "WarnOnLeaveSSLMode");
343 
344  if (result == KMessageBox::Cancel) {
345  if (errorString)
346  *errorString = host;
347  return ERR_USER_CANCELED;
348  }
349  }
350  }
351 
352  /*
353  By default the SSL handshake attempt uses these settings in the order shown:
354 
355  1.) Protocol: KTcpSocket::SecureProtocols SSL compression: ON (DEFAULT)
356  2.) Protocol: KTcpSocket::SecureProtocols SSL compression: OFF
357  3.) Protocol: KTcpSocket::TlsV1 SSL compression: ON
358  4.) Protocol: KTcpSocket::TlsV1 SSL compression: OFF
359  5.) Protocol: KTcpSocket::SslV3 SSL compression: ON
360  6.) Protocol: KTcpSocket::SslV3 SSL compression: OFF
361 
362  If any combination other than the one marked DEFAULT is used to complete
363  the SSL handshake, then that combination will be cached using KIO's internal
364  meta-data mechanism in order to speed up future connections to the same host.
365  */
366 
367  QSslConfiguration sslConfig = d->socket.sslConfiguration();
368 #if QT_VERSION >= 0x040800
369  const bool isSslCompressionDisabled = sslConfig.testSslOption(QSsl::SslOptionDisableCompression);
370  const bool shouldSslCompressBeDisabled = config()->readEntry("LastUsedSslDisableCompressionFlag", isSslCompressionDisabled);
371  sslConfig.setSslOption(QSsl::SslOptionDisableCompression, shouldSslCompressBeDisabled);
372 #endif
373 
374  const int lastSslVerson = config()->readEntry("LastUsedSslVersion", static_cast<int>(KTcpSocket::SecureProtocols));
375  KTcpSocket::SslVersion trySslVersion = static_cast<KTcpSocket::SslVersion>(lastSslVerson);
376  KTcpSocket::SslVersions alreadyTriedSslVersions = trySslVersion;
377 
378  const int timeout = (connectTimeout() * 1000);
379  while (true) {
380  disconnectFromHost(); //Reset some state, even if we are already disconnected
381  d->host = host;
382 
383  d->socket.connectToHost(host, port);
384  const bool connectOk = d->socket.waitForConnected(timeout > -1 ? timeout : -1);
385 
386  kDebug(7027) << "Socket: state=" << d->socket.state()
387  << ", error=" << d->socket.error()
388  << ", connected?" << connectOk;
389 
390  if (d->socket.state() != KTcpSocket::ConnectedState) {
391  if (errorString)
392  *errorString = host + QLatin1String(": ") + d->socket.errorString();
393  switch (d->socket.error()) {
394  case KTcpSocket::UnsupportedSocketOperationError:
395  return ERR_UNSUPPORTED_ACTION;
396  case KTcpSocket::RemoteHostClosedError:
397  return ERR_CONNECTION_BROKEN;
398  case KTcpSocket::SocketTimeoutError:
399  return ERR_SERVER_TIMEOUT;
400  case KTcpSocket::HostNotFoundError:
401  return ERR_UNKNOWN_HOST;
402  default:
403  return ERR_COULD_NOT_CONNECT;
404  }
405  }
406 
407  //### check for proxyAuthenticationRequiredError
408 
409  d->ip = d->socket.peerAddress().toString();
410  d->port = d->socket.peerPort();
411 
412  if (d->autoSSL) {
413  SslResult res = d->startTLSInternal(trySslVersion, sslConfig, 30000 /*30 secs timeout*/);
414  if ((res & ResultFailed) && (res & ResultFailedEarly)) {
415 #if QT_VERSION >= 0x040800
416  if (!sslConfig.testSslOption(QSsl::SslOptionDisableCompression)) {
417  sslConfig.setSslOption(QSsl::SslOptionDisableCompression, true);
418  continue;
419  }
420 #endif
421 
422  if (!(alreadyTriedSslVersions & KTcpSocket::SecureProtocols)) {
423  trySslVersion = KTcpSocket::SecureProtocols;
424  alreadyTriedSslVersions |= trySslVersion;
425 #if QT_VERSION >= 0x040800
426  sslConfig.setSslOption(QSsl::SslOptionDisableCompression, false);
427 #endif
428  continue;
429  }
430 
431  if (!(alreadyTriedSslVersions & KTcpSocket::TlsV1)) {
432  trySslVersion = KTcpSocket::TlsV1;
433  alreadyTriedSslVersions |= trySslVersion;
434 #if QT_VERSION >= 0x040800
435  sslConfig.setSslOption(QSsl::SslOptionDisableCompression, false);
436 #endif
437  continue;
438  }
439 
440  if (!(alreadyTriedSslVersions & KTcpSocket::SslV3)) {
441  trySslVersion = KTcpSocket::SslV3;
442  alreadyTriedSslVersions |= trySslVersion;
443 #if QT_VERSION >= 0x040800
444  sslConfig.setSslOption(QSsl::SslOptionDisableCompression, false);
445 #endif
446  continue;
447  }
448  }
449 
450  //### SSL 2.0 is (close to) dead and it's a good thing, too.
451  if (res & ResultFailed) {
452  if (errorString)
453  *errorString = i18nc("%1 is a host name", "%1: SSL negotiation failed", host);
454  return ERR_COULD_NOT_CONNECT;
455  }
456  }
457  // If the SSL handshake was done with anything protocol other than the default,
458  // save that information so that any subsequent requests do not have to do thesame thing.
459  if (trySslVersion != KTcpSocket::SecureProtocols && lastSslVerson == KTcpSocket::SecureProtocols) {
460  setMetaData(QLatin1String("{internal~currenthost}LastUsedSslVersion"),
461  QString::number(trySslVersion));
462  }
463 #if QT_VERSION >= 0x040800
464  if (sslConfig.testSslOption(QSsl::SslOptionDisableCompression) && !shouldSslCompressBeDisabled) {
465  setMetaData(QLatin1String("{internal~currenthost}LastUsedSslDisableCompressionFlag"),
466  QString::number(true));
467  }
468 #endif
469  return 0;
470  }
471  Q_ASSERT(false);
472  // Code flow never gets here but let's make the compiler happy.
473  // More: the stack allocation of QSslSettings seems to be confusing the compiler;
474  // in fact, any non-POD allocation does.
475  // even a 'return 0;' directly after the allocation (so before the while(true))
476  // is ignored. definitely seems to be a compiler bug? - aseigo
477  return 0;
478 }
479 
480 void TCPSlaveBase::disconnectFromHost()
481 {
482  kDebug(7027);
483  d->host.clear();
484  d->ip.clear();
485  d->usingSSL = false;
486 
487  if (d->socket.state() == KTcpSocket::UnconnectedState) {
488  // discard incoming data - the remote host might have disconnected us in the meantime
489  // but the visible effect of disconnectFromHost() should stay the same.
490  d->socket.close();
491  return;
492  }
493 
494  //### maybe save a session for reuse on SSL shutdown if and when QSslSocket
495  // does that. QCA::TLS can do it apparently but that is not enough if
496  // we want to present that as KDE API. Not a big loss in any case.
497  d->socket.disconnectFromHost();
498  if (d->socket.state() != KTcpSocket::UnconnectedState)
499  d->socket.waitForDisconnected(-1); // wait for unsent data to be sent
500  d->socket.close(); //whatever that means on a socket
501 }
502 
503 bool TCPSlaveBase::isAutoSsl() const
504 {
505  return d->autoSSL;
506 }
507 
508 bool TCPSlaveBase::isUsingSsl() const
509 {
510  return d->usingSSL;
511 }
512 
513 quint16 TCPSlaveBase::port() const
514 {
515  return d->port;
516 }
517 
518 bool TCPSlaveBase::atEnd() const
519 {
520  return d->socket.atEnd();
521 }
522 
523 bool TCPSlaveBase::startSsl()
524 {
525  if (d->usingSSL)
526  return false;
527  return d->startTLSInternal(KTcpSocket::TlsV1) & ResultOk;
528 }
529 
530 // Find out if a hostname matches an SSL certificate's Common Name (including wildcards)
531 static bool isMatchingHostname(const QString &cnIn, const QString &hostnameIn)
532 {
533  const QString cn = cnIn.toLower();
534  const QString hostname = hostnameIn.toLower();
535 
536  const int wildcard = cn.indexOf(QLatin1Char('*'));
537 
538  // Check this is a wildcard cert, if not then just compare the strings
539  if (wildcard < 0)
540  return cn == hostname;
541 
542  const int firstCnDot = cn.indexOf(QLatin1Char('.'));
543  const int secondCnDot = cn.indexOf(QLatin1Char('.'), firstCnDot+1);
544 
545  // Check at least 3 components
546  if ((-1 == secondCnDot) || (secondCnDot+1 >= cn.length()))
547  return false;
548 
549  // Check * is last character of 1st component (ie. there's a following .)
550  if (wildcard+1 != firstCnDot)
551  return false;
552 
553  // Check only one star
554  if (cn.lastIndexOf(QLatin1Char('*')) != wildcard)
555  return false;
556 
557  // Check characters preceding * (if any) match
558  if (wildcard && (hostname.leftRef(wildcard) != cn.leftRef(wildcard)))
559  return false;
560 
561  // Check characters following first . match
562  if (hostname.midRef(hostname.indexOf(QLatin1Char('.'))) != cn.midRef(firstCnDot))
563  return false;
564 
565  // Check if the hostname is an IP address, if so then wildcards are not allowed
566  QHostAddress addr(hostname);
567  if (!addr.isNull())
568  return false;
569 
570  // Ok, I guess this was a wildcard CN and the hostname matches.
571  return true;
572 }
573 
574 TCPSlaveBase::SslResult TCPSlaveBase::TcpSlaveBasePrivate::startTLSInternal (KTcpSocket::SslVersion version,
575  const QSslConfiguration& sslConfig,
576  int waitForEncryptedTimeout)
577 {
578  q->selectClientCertificate();
579 
580  //setMetaData("ssl_session_id", d->kssl->session()->toString());
581  //### we don't support session reuse for now...
582  usingSSL = true;
583 #if QT_VERSION >= 0x040800
584  kDebug(7027) << "Trying SSL handshake with protocol:" << version
585  << ", SSL compression ON:" << sslConfig.testSslOption(QSsl::SslOptionDisableCompression);
586 #endif
587  // Set the SSL version to use...
588  socket.setAdvertisedSslVersion(version);
589 
590  // Set SSL configuration information
591  if (!sslConfig.isNull())
592  socket.setSslConfiguration(sslConfig);
593 
594  /* Usually ignoreSslErrors() would be called in the slot invoked by the sslErrors()
595  signal but that would mess up the flow of control. We will check for errors
596  anyway to decide if we want to continue connecting. Otherwise ignoreSslErrors()
597  before connecting would be very insecure. */
598  socket.ignoreSslErrors();
599  socket.startClientEncryption();
600  const bool encryptionStarted = socket.waitForEncrypted(waitForEncryptedTimeout);
601 
602  //Set metadata, among other things for the "SSL Details" dialog
603  KSslCipher cipher = socket.sessionCipher();
604 
605  if (!encryptionStarted || socket.encryptionMode() != KTcpSocket::SslClientMode
606  || cipher.isNull() || cipher.usedBits() == 0 || socket.peerCertificateChain().isEmpty()) {
607  usingSSL = false;
608  clearSslMetaData();
609  kDebug(7029) << "Initial SSL handshake failed. encryptionStarted is"
610  << encryptionStarted << ", cipher.isNull() is" << cipher.isNull()
611  << ", cipher.usedBits() is" << cipher.usedBits()
612  << ", length of certificate chain is" << socket.peerCertificateChain().count()
613  << ", the socket says:" << socket.errorString()
614  << "and the list of SSL errors contains"
615  << socket.sslErrors().count() << "items.";
616  Q_FOREACH(const KSslError& sslError, socket.sslErrors()) {
617  kDebug(7029) << "SSL ERROR: (" << sslError.error() << ")" << sslError.errorString();
618  }
619  return ResultFailed | ResultFailedEarly;
620  }
621 
622  kDebug(7029) << "Cipher info - "
623  << " advertised SSL protocol version" << socket.advertisedSslVersion()
624  << " negotiated SSL protocol version" << socket.negotiatedSslVersion()
625  << " authenticationMethod:" << cipher.authenticationMethod()
626  << " encryptionMethod:" << cipher.encryptionMethod()
627  << " keyExchangeMethod:" << cipher.keyExchangeMethod()
628  << " name:" << cipher.name()
629  << " supportedBits:" << cipher.supportedBits()
630  << " usedBits:" << cipher.usedBits();
631 
632  // Since we connect by IP (cf. KIO::HostInfo) the SSL code will not recognize
633  // that the site certificate belongs to the domain. We therefore do the
634  // domain<->certificate matching here.
635  sslErrors = socket.sslErrors();
636  QSslCertificate peerCert = socket.peerCertificateChain().first();
637  QMutableListIterator<KSslError> it(sslErrors);
638  while (it.hasNext()) {
639  // As of 4.4.0 Qt does not assign a certificate to the QSslError it emits
640  // *in the case of HostNameMismatch*. A HostNameMismatch, however, will always
641  // be an error of the peer certificate so we just don't check the error's
642  // certificate().
643 
644  // Remove all HostNameMismatch, we have to redo name checking later.
645  if (it.next().error() == KSslError::HostNameMismatch) {
646  it.remove();
647  }
648  }
649  // Redo name checking here and (re-)insert HostNameMismatch to sslErrors if
650  // host name does not match any of the names in server certificate.
651  // QSslSocket may not report HostNameMismatch error, when server
652  // certificate was issued for the IP we are connecting to.
653  QStringList domainPatterns(peerCert.subjectInfo(QSslCertificate::CommonName));
654  domainPatterns += peerCert.alternateSubjectNames().values(QSsl::DnsEntry);
655  bool names_match = false;
656  foreach (const QString &dp, domainPatterns) {
657  if (isMatchingHostname(dp, host)) {
658  names_match = true;
659  break;
660  }
661  }
662  if (!names_match) {
663  sslErrors.insert(0, KSslError(KSslError::HostNameMismatch, peerCert));
664  }
665 
666  // TODO: review / rewrite / remove the comment
667  // The app side needs the metadata now for the SSL error dialog (if any) but
668  // the same metadata will be needed later, too. When "later" arrives the slave
669  // may actually be connected to a different application that doesn't know
670  // the metadata the slave sent to the previous application.
671  // The quite important SSL indicator icon in Konqi's URL bar relies on metadata
672  // from here, for example. And Konqi will be the second application to connect
673  // to the slave.
674  // Therefore we choose to have our metadata and send it, too :)
675  setSslMetaData();
676  q->sendAndKeepMetaData();
677 
678  SslResult rc = q->verifyServerCertificate();
679  if (rc & ResultFailed) {
680  usingSSL = false;
681  clearSslMetaData();
682  kDebug(7029) << "server certificate verification failed.";
683  socket.disconnectFromHost(); //Make the connection fail (cf. ignoreSslErrors())
684  return ResultFailed;
685  } else if (rc & ResultOverridden) {
686  kDebug(7029) << "server certificate verification failed but continuing at user's request.";
687  }
688 
689  //"warn" when starting SSL/TLS
690  if (q->metaData("ssl_activate_warnings") == "TRUE"
691  && q->metaData("ssl_was_in_use") == "FALSE"
692  && sslSettings.warnOnEnter()) {
693 
694  int msgResult = q->messageBox(i18n("You are about to enter secure mode. "
695  "All transmissions will be encrypted "
696  "unless otherwise noted.\nThis means "
697  "that no third party will be able to "
698  "easily observe your data in transit."),
699  WarningYesNo,
700  i18n("Security Information"),
701  i18n("Display SSL &Information"),
702  i18n("C&onnect"),
703  "WarnOnEnterSSLMode");
704  if (msgResult == KMessageBox::Yes) {
705  q->messageBox(SSLMessageBox /*==the SSL info dialog*/, host);
706  }
707  }
708 
709  return rc;
710 }
711 
712 void TCPSlaveBase::selectClientCertificate()
713 {
714 #if 0 //hehe
715  QString certname; // the cert to use this session
716  bool send = false, prompt = false, save = false, forcePrompt = false;
717  KSSLCertificateHome::KSSLAuthAction aa;
718 
719  setMetaData("ssl_using_client_cert", "FALSE"); // we change this if needed
720 
721  if (metaData("ssl_no_client_cert") == "TRUE") return;
722  forcePrompt = (metaData("ssl_force_cert_prompt") == "TRUE");
723 
724  // Delete the old cert since we're certainly done with it now
725  if (d->pkcs) {
726  delete d->pkcs;
727  d->pkcs = NULL;
728  }
729 
730  if (!d->kssl) return;
731 
732  // Look for a general certificate
733  if (!forcePrompt) {
734  certname = KSSLCertificateHome::getDefaultCertificateName(&aa);
735  switch (aa) {
736  case KSSLCertificateHome::AuthSend:
737  send = true; prompt = false;
738  break;
739  case KSSLCertificateHome::AuthDont:
740  send = false; prompt = false;
741  certname.clear();
742  break;
743  case KSSLCertificateHome::AuthPrompt:
744  send = false; prompt = true;
745  break;
746  default:
747  break;
748  }
749  }
750 
751  // Look for a certificate on a per-host basis as an override
752  QString tmpcn = KSSLCertificateHome::getDefaultCertificateName(d->host, &aa);
753  if (aa != KSSLCertificateHome::AuthNone) { // we must override
754  switch (aa) {
755  case KSSLCertificateHome::AuthSend:
756  send = true;
757  prompt = false;
758  certname = tmpcn;
759  break;
760  case KSSLCertificateHome::AuthDont:
761  send = false;
762  prompt = false;
763  certname.clear();
764  break;
765  case KSSLCertificateHome::AuthPrompt:
766  send = false;
767  prompt = true;
768  certname = tmpcn;
769  break;
770  default:
771  break;
772  }
773  }
774 
775  // Finally, we allow the application to override anything.
776  if (hasMetaData("ssl_demand_certificate")) {
777  certname = metaData("ssl_demand_certificate");
778  if (!certname.isEmpty()) {
779  forcePrompt = false;
780  prompt = false;
781  send = true;
782  }
783  }
784 
785  if (certname.isEmpty() && !prompt && !forcePrompt) return;
786 
787  // Ok, we're supposed to prompt the user....
788  if (prompt || forcePrompt) {
789  QStringList certs = KSSLCertificateHome::getCertificateList();
790 
791  QStringList::const_iterator it = certs.begin();
792  while (it != certs.end()) {
793  KSSLPKCS12 *pkcs = KSSLCertificateHome::getCertificateByName(*it);
794  if (pkcs && (!pkcs->getCertificate() ||
795  !pkcs->getCertificate()->x509V3Extensions().certTypeSSLClient())) {
796  it = certs.erase(it);
797  } else {
798  ++it;
799  }
800  delete pkcs;
801  }
802 
803  if (certs.isEmpty()) return; // we had nothing else, and prompt failed
804 
805  if (!QDBusConnection::sessionBus().interface()->isServiceRegistered("org.kde.kio.uiserver")) {
806  KToolInvocation::startServiceByDesktopPath("kuiserver.desktop",
807  QStringList());
808  }
809 
810  QDBusInterface uis("org.kde.kio.uiserver", "/UIServer", "org.kde.KIO.UIServer");
811 
812  QDBusMessage retVal = uis.call("showSSLCertDialog", d->host, certs, metaData("window-id").toLongLong());
813  if (retVal.type() == QDBusMessage::ReplyMessage) {
814  if (retVal.arguments().at(0).toBool()) {
815  send = retVal.arguments().at(1).toBool();
816  save = retVal.arguments().at(2).toBool();
817  certname = retVal.arguments().at(3).toString();
818  }
819  }
820  }
821 
822  // The user may have said to not send the certificate,
823  // but to save the choice
824  if (!send) {
825  if (save) {
826  KSSLCertificateHome::setDefaultCertificate(certname, d->host,
827  false, false);
828  }
829  return;
830  }
831 
832  // We're almost committed. If we can read the cert, we'll send it now.
833  KSSLPKCS12 *pkcs = KSSLCertificateHome::getCertificateByName(certname);
834  if (!pkcs && KSSLCertificateHome::hasCertificateByName(certname)) { // We need the password
835  KIO::AuthInfo ai;
836  bool first = true;
837  do {
838  ai.prompt = i18n("Enter the certificate password:");
839  ai.caption = i18n("SSL Certificate Password");
840  ai.url.setProtocol("kssl");
841  ai.url.setHost(certname);
842  ai.username = certname;
843  ai.keepPassword = true;
844 
845  bool showprompt;
846  if (first)
847  showprompt = !checkCachedAuthentication(ai);
848  else
849  showprompt = true;
850  if (showprompt) {
851  if (!openPasswordDialog(ai, first ? QString() :
852  i18n("Unable to open the certificate. Try a new password?")))
853  break;
854  }
855 
856  first = false;
857  pkcs = KSSLCertificateHome::getCertificateByName(certname, ai.password);
858  } while (!pkcs);
859 
860  }
861 
862  // If we could open the certificate, let's send it
863  if (pkcs) {
864  if (!d->kssl->setClientCertificate(pkcs)) {
865  messageBox(Information, i18n("The procedure to set the "
866  "client certificate for the session "
867  "failed."), i18n("SSL"));
868  delete pkcs; // we don't need this anymore
869  pkcs = 0L;
870  } else {
871  kDebug(7029) << "Client SSL certificate is being used.";
872  setMetaData("ssl_using_client_cert", "TRUE");
873  if (save) {
874  KSSLCertificateHome::setDefaultCertificate(certname, d->host,
875  true, false);
876  }
877  }
878  d->pkcs = pkcs;
879  }
880 #endif
881 }
882 
883 TCPSlaveBase::SslResult TCPSlaveBase::verifyServerCertificate()
884 {
885  d->sslNoUi = hasMetaData("ssl_no_ui") && (metaData("ssl_no_ui") != "FALSE");
886 
887  if (d->sslErrors.isEmpty()) {
888  return ResultOk;
889  } else if (d->sslNoUi) {
890  return ResultFailed;
891  }
892 
893  QList<KSslError> fatalErrors = KSslCertificateManager::nonIgnorableErrors(d->sslErrors);
894  if (!fatalErrors.isEmpty()) {
895  //TODO message "sorry, fatal error, you can't override it"
896  return ResultFailed;
897  }
898 
899  KSslCertificateManager *const cm = KSslCertificateManager::self();
900  KSslCertificateRule rule = cm->rule(d->socket.peerCertificateChain().first(), d->host);
901 
902  // remove previously seen and acknowledged errors
903  QList<KSslError> remainingErrors = rule.filterErrors(d->sslErrors);
904  if (remainingErrors.isEmpty()) {
905  kDebug(7029) << "Error list empty after removing errors to be ignored. Continuing.";
906  return ResultOk | ResultOverridden;
907  }
908 
909  //### We don't ask to permanently reject the certificate
910 
911  QString message = i18n("The server failed the authenticity check (%1).\n\n", d->host);
912  Q_FOREACH (const KSslError &err, d->sslErrors) {
913  message.append(err.errorString());
914  message.append('\n');
915  }
916  message = message.trimmed();
917 
918  int msgResult;
919  do {
920  msgResult = messageBox(WarningYesNoCancel, message,
921  i18n("Server Authentication"),
922  i18n("&Details"), i18n("Co&ntinue"));
923  if (msgResult == KMessageBox::Yes) {
924  //Details was chosen- show the certificate and error details
925  messageBox(SSLMessageBox /*the SSL info dialog*/, d->host);
926  } else if (msgResult == KMessageBox::Cancel) {
927  return ResultFailed;
928  }
929  //fall through on KMessageBox::No
930  } while (msgResult == KMessageBox::Yes);
931 
932  //Save the user's choice to ignore the SSL errors.
933 
934  msgResult = messageBox(WarningYesNo,
935  i18n("Would you like to accept this "
936  "certificate forever without "
937  "being prompted?"),
938  i18n("Server Authentication"),
939  i18n("&Forever"),
940  i18n("&Current Session only"));
941  QDateTime ruleExpiry = QDateTime::currentDateTime();
942  if (msgResult == KMessageBox::Yes) {
943  //accept forever ("for a very long time")
944  ruleExpiry = ruleExpiry.addYears(1000);
945  } else {
946  //accept "for a short time", half an hour.
947  ruleExpiry = ruleExpiry.addSecs(30*60);
948  }
949 
950  //TODO special cases for wildcard domain name in the certificate!
951  //rule = KSslCertificateRule(d->socket.peerCertificateChain().first(), whatever);
952 
953  rule.setExpiryDateTime(ruleExpiry);
954  rule.setIgnoredErrors(d->sslErrors);
955  cm->setRule(rule);
956 
957  return ResultOk | ResultOverridden;
958 #if 0 //### need to to do something like the old code about the main and subframe stuff
959  kDebug(7029) << "SSL HTTP frame the parent? " << metaData("main_frame_request");
960  if (!hasMetaData("main_frame_request") || metaData("main_frame_request") == "TRUE") {
961  // Since we're the parent, we need to teach the child.
962  setMetaData("ssl_parent_ip", d->ip);
963  setMetaData("ssl_parent_cert", pc.toString());
964  // - Read from cache and see if there is a policy for this
965  KSSLCertificateCache::KSSLCertificatePolicy cp =
966  d->certCache->getPolicyByCertificate(pc);
967 
968  // - validation code
969  if (ksv != KSSLCertificate::Ok) {
970  if (d->sslNoUi) {
971  return -1;
972  }
973 
974  if (cp == KSSLCertificateCache::Unknown ||
975  cp == KSSLCertificateCache::Ambiguous) {
976  cp = KSSLCertificateCache::Prompt;
977  } else {
978  // A policy was already set so let's honor that.
979  permacache = d->certCache->isPermanent(pc);
980  }
981 
982  if (!_IPmatchesCN && cp == KSSLCertificateCache::Accept) {
983  cp = KSSLCertificateCache::Prompt;
984 // ksv = KSSLCertificate::Ok;
985  }
986 
988 
989  // - cache the results
990  d->certCache->addCertificate(pc, cp, permacache);
991  if (doAddHost) d->certCache->addHost(pc, d->host);
992  } else { // Child frame
993  // - Read from cache and see if there is a policy for this
994  KSSLCertificateCache::KSSLCertificatePolicy cp =
995  d->certCache->getPolicyByCertificate(pc);
996  isChild = true;
997 
998  // Check the cert and IP to make sure they're the same
999  // as the parent frame
1000  bool certAndIPTheSame = (d->ip == metaData("ssl_parent_ip") &&
1001  pc.toString() == metaData("ssl_parent_cert"));
1002 
1003  if (ksv == KSSLCertificate::Ok) {
1004  if (certAndIPTheSame) { // success
1005  rc = 1;
1006  setMetaData("ssl_action", "accept");
1007  } else {
1008  /*
1009  if (d->sslNoUi) {
1010  return -1;
1011  }
1012  result = messageBox(WarningYesNo,
1013  i18n("The certificate is valid but does not appear to have been assigned to this server. Do you wish to continue loading?"),
1014  i18n("Server Authentication"));
1015  if (result == KMessageBox::Yes) { // success
1016  rc = 1;
1017  setMetaData("ssl_action", "accept");
1018  } else { // fail
1019  rc = -1;
1020  setMetaData("ssl_action", "reject");
1021  }
1022  */
1023  setMetaData("ssl_action", "accept");
1024  rc = 1; // Let's accept this now. It's bad, but at least the user
1025  // will see potential attacks in KDE3 with the pseudo-lock
1026  // icon on the toolbar, and can investigate with the RMB
1027  }
1028  } else {
1029  if (d->sslNoUi) {
1030  return -1;
1031  }
1032 
1033  if (cp == KSSLCertificateCache::Accept) {
1034  if (certAndIPTheSame) { // success
1035  rc = 1;
1036  setMetaData("ssl_action", "accept");
1037  } else { // fail
1038  result = messageBox(WarningYesNo,
1039  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?"),
1040  i18n("Server Authentication"));
1041  if (result == KMessageBox::Yes) {
1042  rc = 1;
1043  setMetaData("ssl_action", "accept");
1044  d->certCache->addHost(pc, d->host);
1045  } else {
1046  rc = -1;
1047  setMetaData("ssl_action", "reject");
1048  }
1049  }
1050  } else if (cp == KSSLCertificateCache::Reject) { // fail
1051  messageBox(Information, i18n("SSL certificate is being rejected as requested. You can disable this in the KDE System Settings."),
1052  i18n("Server Authentication"));
1053  rc = -1;
1054  setMetaData("ssl_action", "reject");
1055  } else {
1056 
1058 
1059  return rc;
1060 #endif //#if 0
1061  return ResultOk | ResultOverridden;
1062 }
1063 
1064 
1065 bool TCPSlaveBase::isConnected() const
1066 {
1067  //QSslSocket::isValid() and therefore KTcpSocket::isValid() are shady...
1068  return d->socket.state() == KTcpSocket::ConnectedState;
1069 }
1070 
1071 
1072 bool TCPSlaveBase::waitForResponse(int t)
1073 {
1074  if (d->socket.bytesAvailable()) {
1075  return true;
1076  }
1077  return d->socket.waitForReadyRead(t * 1000);
1078 }
1079 
1080 void TCPSlaveBase::setBlocking(bool b)
1081 {
1082  if (!b) {
1083  kWarning(7029) << "Caller requested non-blocking mode, but that doesn't work";
1084  return;
1085  }
1086  d->isBlocking = b;
1087 }
1088 
1089 void TCPSlaveBase::virtual_hook(int id, void* data)
1090 {
1091  if (id == SlaveBase::AppConnectionMade) {
1092  d->sendSslMetaData();
1093  } else {
1094  SlaveBase::virtual_hook(id, data);
1095  }
1096 }
This file is part of the KDE documentation.
Documentation copyright © 1996-2013 The KDE developers.
Generated on Thu Feb 21 2013 11:10:12 by doxygen 1.8.1 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KIO

Skip menu "KIO"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • Related Pages

kdelibs-4.8.5 API Reference

Skip menu "kdelibs-4.8.5 API Reference"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal