diff --git a/src/core/ktcpsocket.cpp b/src/core/ktcpsocket.cpp index 626a7946..2ff652be 100644 --- a/src/core/ktcpsocket.cpp +++ b/src/core/ktcpsocket.cpp @@ -1,1115 +1,1134 @@ /* This file is part of the KDE libraries Copyright (C) 2007, 2008 Andreas Hartmetz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ktcpsocket.h" #include "ktcpsocket_p.h" #include "kiocoredebug.h" #include #include #include #include #include #include #include +#include #include static KTcpSocket::SslVersion kSslVersionFromQ(QSsl::SslProtocol protocol) { switch (protocol) { case QSsl::SslV2: return KTcpSocket::SslV2; case QSsl::SslV3: return KTcpSocket::SslV3; case QSsl::TlsV1_0: return KTcpSocket::TlsV1; case QSsl::TlsV1_1: return KTcpSocket::TlsV1_1; case QSsl::TlsV1_2: return KTcpSocket::TlsV1_2; #if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) case QSsl::TlsV1_3: return KTcpSocket::TlsV1_3; #endif case QSsl::AnyProtocol: return KTcpSocket::AnySslVersion; case QSsl::TlsV1SslV3: return KTcpSocket::TlsV1SslV3; case QSsl::SecureProtocols: return KTcpSocket::SecureProtocols; default: return KTcpSocket::UnknownSslVersion; } } static QSsl::SslProtocol qSslProtocolFromK(KTcpSocket::SslVersion sslVersion) { //### this lowlevel bit-banging is a little dangerous and a likely source of bugs if (sslVersion == KTcpSocket::AnySslVersion) { return QSsl::AnyProtocol; } //does it contain any valid protocol? KTcpSocket::SslVersions validVersions(KTcpSocket::SslV2 | KTcpSocket::SslV3 | KTcpSocket::TlsV1); validVersions |= KTcpSocket::TlsV1_1; validVersions |= KTcpSocket::TlsV1_2; #if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) validVersions |= KTcpSocket::TlsV1_3; #endif validVersions |= KTcpSocket::TlsV1SslV3; validVersions |= KTcpSocket::SecureProtocols; if (!(sslVersion & validVersions)) { return QSsl::UnknownProtocol; } switch (sslVersion) { case KTcpSocket::SslV2: return QSsl::SslV2; case KTcpSocket::SslV3: return QSsl::SslV3; case KTcpSocket::TlsV1_0: return QSsl::TlsV1_0; case KTcpSocket::TlsV1_1: return QSsl::TlsV1_1; case KTcpSocket::TlsV1_2: return QSsl::TlsV1_2; #if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) case KTcpSocket::TlsV1_3: return QSsl::TlsV1_3; #endif case KTcpSocket::TlsV1SslV3: return QSsl::TlsV1SslV3; case KTcpSocket::SecureProtocols: return QSsl::SecureProtocols; default: //QSslSocket doesn't really take arbitrary combinations. It's one or all. return QSsl::AnyProtocol; } } static QString protocolString(QSsl::SslProtocol protocol) { switch (protocol) { case QSsl::SslV2: return QStringLiteral("SSLv2"); case QSsl::SslV3: return QStringLiteral("SSLv3"); case QSsl::TlsV1_0: return QStringLiteral("TLSv1.0"); case QSsl::TlsV1_1: return QStringLiteral("TLSv1.1"); case QSsl::TlsV1_2: return QStringLiteral("TLSv1.2"); #if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) case QSsl::TlsV1_3: return QStringLiteral("TLSv1.3"); #endif default: return QStringLiteral("Unknown");; } } //cipher class converter KSslCipher -> QSslCipher class CipherCc { public: CipherCc() { foreach (const QSslCipher &c, QSslConfiguration::supportedCiphers()) { allCiphers.insert(c.name(), c); } } QSslCipher converted(const KSslCipher &ksc) { return allCiphers.value(ksc.name()); } private: QHash allCiphers; }; class KSslErrorPrivate { public: static KSslError::Error errorFromQSslError(QSslError::SslError e) { switch (e) { case QSslError::NoError: return KSslError::NoError; case QSslError::UnableToGetLocalIssuerCertificate: case QSslError::InvalidCaCertificate: return KSslError::InvalidCertificateAuthorityCertificate; case QSslError::InvalidNotBeforeField: case QSslError::InvalidNotAfterField: case QSslError::CertificateNotYetValid: case QSslError::CertificateExpired: return KSslError::ExpiredCertificate; case QSslError::UnableToDecodeIssuerPublicKey: case QSslError::SubjectIssuerMismatch: case QSslError::AuthorityIssuerSerialNumberMismatch: return KSslError::InvalidCertificate; case QSslError::SelfSignedCertificate: case QSslError::SelfSignedCertificateInChain: return KSslError::SelfSignedCertificate; case QSslError::CertificateRevoked: return KSslError::RevokedCertificate; case QSslError::InvalidPurpose: return KSslError::InvalidCertificatePurpose; case QSslError::CertificateUntrusted: return KSslError::UntrustedCertificate; case QSslError::CertificateRejected: return KSslError::RejectedCertificate; case QSslError::NoPeerCertificate: return KSslError::NoPeerCertificate; case QSslError::HostNameMismatch: return KSslError::HostNameMismatch; case QSslError::UnableToVerifyFirstCertificate: case QSslError::UnableToDecryptCertificateSignature: case QSslError::UnableToGetIssuerCertificate: case QSslError::CertificateSignatureFailed: return KSslError::CertificateSignatureFailed; case QSslError::PathLengthExceeded: return KSslError::PathLengthExceeded; case QSslError::UnspecifiedError: case QSslError::NoSslSupport: default: return KSslError::UnknownError; } } static QString errorString(KSslError::Error e) { switch (e) { case KSslError::NoError: return i18nc("SSL error", "No error"); case KSslError::InvalidCertificateAuthorityCertificate: return i18nc("SSL error", "The certificate authority's certificate is invalid"); case KSslError::ExpiredCertificate: return i18nc("SSL error", "The certificate has expired"); case KSslError::InvalidCertificate: return i18nc("SSL error", "The certificate is invalid"); case KSslError::SelfSignedCertificate: return i18nc("SSL error", "The certificate is not signed by any trusted certificate authority"); case KSslError::RevokedCertificate: return i18nc("SSL error", "The certificate has been revoked"); case KSslError::InvalidCertificatePurpose: return i18nc("SSL error", "The certificate is unsuitable for this purpose"); case KSslError::UntrustedCertificate: return i18nc("SSL error", "The root certificate authority's certificate is not trusted for this purpose"); case KSslError::RejectedCertificate: return i18nc("SSL error", "The certificate authority's certificate is marked to reject this certificate's purpose"); case KSslError::NoPeerCertificate: return i18nc("SSL error", "The peer did not present any certificate"); case KSslError::HostNameMismatch: return i18nc("SSL error", "The certificate does not apply to the given host"); case KSslError::CertificateSignatureFailed: return i18nc("SSL error", "The certificate cannot be verified for internal reasons"); case KSslError::PathLengthExceeded: return i18nc("SSL error", "The certificate chain is too long"); case KSslError::UnknownError: default: return i18nc("SSL error", "Unknown error"); } } KSslError::Error error; QSslCertificate certificate; }; KSslError::KSslError(Error errorCode, const QSslCertificate &certificate) : d(new KSslErrorPrivate()) { d->error = errorCode; d->certificate = certificate; } KSslError::KSslError(const QSslError &other) : d(new KSslErrorPrivate()) { d->error = KSslErrorPrivate::errorFromQSslError(other.error()); d->certificate = other.certificate(); } KSslError::KSslError(const KSslError &other) : d(new KSslErrorPrivate()) { *d = *other.d; } KSslError::~KSslError() { delete d; } KSslError &KSslError::operator=(const KSslError &other) { *d = *other.d; return *this; } KSslError::Error KSslError::error() const { return d->error; } QString KSslError::errorString() const { return KSslErrorPrivate::errorString(d->error); } QSslCertificate KSslError::certificate() const { return d->certificate; } class KTcpSocketPrivate { public: explicit KTcpSocketPrivate(KTcpSocket *qq) : q(qq), certificatesLoaded(false), emittedReadyRead(false) { // create the instance, which sets Qt's static internal cert set to empty. KSslCertificateManager::self(); } KTcpSocket::State state(QAbstractSocket::SocketState s) { switch (s) { case QAbstractSocket::UnconnectedState: return KTcpSocket::UnconnectedState; case QAbstractSocket::HostLookupState: return KTcpSocket::HostLookupState; case QAbstractSocket::ConnectingState: return KTcpSocket::ConnectingState; case QAbstractSocket::ConnectedState: return KTcpSocket::ConnectedState; case QAbstractSocket::ClosingState: return KTcpSocket::ClosingState; case QAbstractSocket::BoundState: case QAbstractSocket::ListeningState: //### these two are not relevant as long as this can't be a server socket default: return KTcpSocket::UnconnectedState; //the closest to "error" } } KTcpSocket::EncryptionMode encryptionMode(QSslSocket::SslMode mode) { switch (mode) { case QSslSocket::SslClientMode: return KTcpSocket::SslClientMode; case QSslSocket::SslServerMode: return KTcpSocket::SslServerMode; default: return KTcpSocket::UnencryptedMode; } } KTcpSocket::Error errorFromAbsSocket(QAbstractSocket::SocketError e) { switch (e) { case QAbstractSocket::ConnectionRefusedError: return KTcpSocket::ConnectionRefusedError; case QAbstractSocket::RemoteHostClosedError: return KTcpSocket::RemoteHostClosedError; case QAbstractSocket::HostNotFoundError: return KTcpSocket::HostNotFoundError; case QAbstractSocket::SocketAccessError: return KTcpSocket::SocketAccessError; case QAbstractSocket::SocketResourceError: return KTcpSocket::SocketResourceError; case QAbstractSocket::SocketTimeoutError: return KTcpSocket::SocketTimeoutError; case QAbstractSocket::NetworkError: return KTcpSocket::NetworkError; case QAbstractSocket::UnsupportedSocketOperationError: return KTcpSocket::UnsupportedSocketOperationError; case QAbstractSocket::SslHandshakeFailedError: return KTcpSocket::SslHandshakeFailedError; case QAbstractSocket::DatagramTooLargeError: //we don't do UDP case QAbstractSocket::AddressInUseError: case QAbstractSocket::SocketAddressNotAvailableError: //### own values if/when we ever get server socket support case QAbstractSocket::ProxyAuthenticationRequiredError: //### maybe we need an enum value for this case QAbstractSocket::UnknownSocketError: default: return KTcpSocket::UnknownError; } } //private slots void reemitSocketError(QAbstractSocket::SocketError e) { q->setErrorString(sock.errorString()); emit q->error(errorFromAbsSocket(e)); } void reemitSslErrors(const QList &errors) { q->setErrorString(sock.errorString()); q->showSslErrors(); //H4X QList kErrors; kErrors.reserve(errors.size()); for (const QSslError &e : errors) { kErrors.append(KSslError(e)); } emit q->sslErrors(kErrors); } void reemitStateChanged(QAbstractSocket::SocketState s) { emit q->stateChanged(state(s)); } void reemitModeChanged(QSslSocket::SslMode m) { emit q->encryptionModeChanged(encryptionMode(m)); } // This method is needed because we might emit readyRead() due to this QIODevice // having some data buffered, so we need to care about blocking, too. //### useless ATM as readyRead() now just calls d->sock.readyRead(). void reemitReadyRead() { if (!emittedReadyRead) { emittedReadyRead = true; emit q->readyRead(); emittedReadyRead = false; } } void maybeLoadCertificates() { if (!certificatesLoaded) { q->setCaCertificates(KSslCertificateManager::self()->caCertificates()); } } KTcpSocket *const q; bool certificatesLoaded; bool emittedReadyRead; QSslSocket sock; QList ciphers; KTcpSocket::SslVersion advertisedSslVersion; CipherCc ccc; }; KTcpSocket::KTcpSocket(QObject *parent) : QIODevice(parent), d(new KTcpSocketPrivate(this)) { d->advertisedSslVersion = SslV3; connect(&d->sock, &QIODevice::aboutToClose, this, &QIODevice::aboutToClose); connect(&d->sock, &QIODevice::bytesWritten, this, &QIODevice::bytesWritten); connect(&d->sock, &QSslSocket::encryptedBytesWritten, this, &KTcpSocket::encryptedBytesWritten); connect(&d->sock, SIGNAL(readyRead()), this, SLOT(reemitReadyRead())); connect(&d->sock, &QAbstractSocket::connected, this, &KTcpSocket::connected); connect(&d->sock, &QSslSocket::encrypted, this, &KTcpSocket::encrypted); connect(&d->sock, &QAbstractSocket::disconnected, this, &KTcpSocket::disconnected); #ifndef QT_NO_NETWORKPROXY connect(&d->sock, &QAbstractSocket::proxyAuthenticationRequired, this, &KTcpSocket::proxyAuthenticationRequired); #endif connect(&d->sock, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(reemitSocketError(QAbstractSocket::SocketError))); connect(&d->sock, SIGNAL(sslErrors(QList)), this, SLOT(reemitSslErrors(QList))); connect(&d->sock, &QAbstractSocket::hostFound, this, &KTcpSocket::hostFound); connect(&d->sock, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(reemitStateChanged(QAbstractSocket::SocketState))); connect(&d->sock, SIGNAL(modeChanged(QSslSocket::SslMode)), this, SLOT(reemitModeChanged(QSslSocket::SslMode))); } KTcpSocket::~KTcpSocket() { delete d; } ////////////////////////////// (mostly) virtuals from QIODevice bool KTcpSocket::atEnd() const { return d->sock.atEnd() && QIODevice::atEnd(); } qint64 KTcpSocket::bytesAvailable() const { return d->sock.bytesAvailable() + QIODevice::bytesAvailable(); } qint64 KTcpSocket::bytesToWrite() const { return d->sock.bytesToWrite(); } bool KTcpSocket::canReadLine() const { return d->sock.canReadLine() || QIODevice::canReadLine(); } void KTcpSocket::close() { d->sock.close(); QIODevice::close(); } bool KTcpSocket::isSequential() const { return true; } bool KTcpSocket::open(QIODevice::OpenMode open) { bool ret = d->sock.open(open); setOpenMode(d->sock.openMode() | QIODevice::Unbuffered); return ret; } bool KTcpSocket::waitForBytesWritten(int msecs) { return d->sock.waitForBytesWritten(msecs); } bool KTcpSocket::waitForReadyRead(int msecs) { return d->sock.waitForReadyRead(msecs); } qint64 KTcpSocket::readData(char *data, qint64 maxSize) { return d->sock.read(data, maxSize); } qint64 KTcpSocket::writeData(const char *data, qint64 maxSize) { return d->sock.write(data, maxSize); } ////////////////////////////// public methods from QAbstractSocket void KTcpSocket::abort() { d->sock.abort(); } void KTcpSocket::connectToHost(const QString &hostName, quint16 port, ProxyPolicy policy) { if (policy == AutoProxy) { //### } d->sock.connectToHost(hostName, port); // there are enough layers of buffers between us and the network, and there is a quirk // in QIODevice that can make it try to readData() twice per read() call if buffered and // reaData() does not deliver enough data the first time. like when the other side is // simply not sending any more data... // this can *apparently* lead to long delays sometimes which stalls applications. // do not want. setOpenMode(d->sock.openMode() | QIODevice::Unbuffered); } void KTcpSocket::connectToHost(const QHostAddress &hostAddress, quint16 port, ProxyPolicy policy) { if (policy == AutoProxy) { //### } d->sock.connectToHost(hostAddress, port); setOpenMode(d->sock.openMode() | QIODevice::Unbuffered); } void KTcpSocket::connectToHost(const QUrl &url, ProxyPolicy policy) { if (policy == AutoProxy) { //### } d->sock.connectToHost(url.host(), url.port()); setOpenMode(d->sock.openMode() | QIODevice::Unbuffered); } void KTcpSocket::disconnectFromHost() { d->sock.disconnectFromHost(); setOpenMode(d->sock.openMode() | QIODevice::Unbuffered); } KTcpSocket::Error KTcpSocket::error() const { return d->errorFromAbsSocket(d->sock.error()); } QList KTcpSocket::sslErrors() const { //### pretty slow; also consider throwing out duplicate error codes. We may get // duplicates even though there were none in the original list because KSslError // has a smallest common denominator range of SSL error codes. const auto qsslErrors = d->sock.sslErrors(); QList ret; ret.reserve(qsslErrors.size()); for (const QSslError &e : qsslErrors) { ret.append(KSslError(e)); } return ret; } bool KTcpSocket::flush() { return d->sock.flush(); } bool KTcpSocket::isValid() const { return d->sock.isValid(); } QHostAddress KTcpSocket::localAddress() const { return d->sock.localAddress(); } QHostAddress KTcpSocket::peerAddress() const { return d->sock.peerAddress(); } QString KTcpSocket::peerName() const { return d->sock.peerName(); } quint16 KTcpSocket::peerPort() const { return d->sock.peerPort(); } #ifndef QT_NO_NETWORKPROXY QNetworkProxy KTcpSocket::proxy() const { return d->sock.proxy(); } #endif qint64 KTcpSocket::readBufferSize() const { return d->sock.readBufferSize(); } #ifndef QT_NO_NETWORKPROXY void KTcpSocket::setProxy(const QNetworkProxy &proxy) { d->sock.setProxy(proxy); } #endif void KTcpSocket::setReadBufferSize(qint64 size) { d->sock.setReadBufferSize(size); } KTcpSocket::State KTcpSocket::state() const { return d->state(d->sock.state()); } bool KTcpSocket::waitForConnected(int msecs) { bool ret = d->sock.waitForConnected(msecs); if (!ret) { setErrorString(d->sock.errorString()); } setOpenMode(d->sock.openMode() | QIODevice::Unbuffered); return ret; } bool KTcpSocket::waitForDisconnected(int msecs) { bool ret = d->sock.waitForDisconnected(msecs); if (!ret) { setErrorString(d->sock.errorString()); } setOpenMode(d->sock.openMode() | QIODevice::Unbuffered); return ret; } ////////////////////////////// public methods from QSslSocket void KTcpSocket::addCaCertificate(const QSslCertificate &certificate) { d->maybeLoadCertificates(); d->sock.addCaCertificate(certificate); } /* bool KTcpSocket::addCaCertificates(const QString &path, QSsl::EncodingFormat format, QRegExp::PatternSyntax syntax) { d->maybeLoadCertificates(); return d->sock.addCaCertificates(path, format, syntax); } */ void KTcpSocket::addCaCertificates(const QList &certificates) { d->maybeLoadCertificates(); d->sock.addCaCertificates(certificates); } QList KTcpSocket::caCertificates() const { d->maybeLoadCertificates(); return d->sock.sslConfiguration().caCertificates(); } QList KTcpSocket::ciphers() const { return d->ciphers; } void KTcpSocket::connectToHostEncrypted(const QString &hostName, quint16 port, OpenMode openMode) { d->maybeLoadCertificates(); d->sock.setProtocol(qSslProtocolFromK(d->advertisedSslVersion)); d->sock.connectToHostEncrypted(hostName, port, openMode); setOpenMode(d->sock.openMode() | QIODevice::Unbuffered); } QSslCertificate KTcpSocket::localCertificate() const { return d->sock.localCertificate(); } QList KTcpSocket::peerCertificateChain() const { return d->sock.peerCertificateChain(); } KSslKey KTcpSocket::privateKey() const { return KSslKey(d->sock.privateKey()); } KSslCipher KTcpSocket::sessionCipher() const { return KSslCipher(d->sock.sessionCipher()); } void KTcpSocket::setCaCertificates(const QList &certificates) { QSslConfiguration configuration = d->sock.sslConfiguration(); configuration.setCaCertificates(certificates); d->sock.setSslConfiguration(configuration); d->certificatesLoaded = true; } void KTcpSocket::setCiphers(const QList &ciphers) { d->ciphers = ciphers; QList cl; cl.reserve(d->ciphers.size()); foreach (const KSslCipher &c, d->ciphers) { cl.append(d->ccc.converted(c)); } QSslConfiguration configuration = d->sock.sslConfiguration(); configuration.setCiphers(cl); d->sock.setSslConfiguration(configuration); } void KTcpSocket::setLocalCertificate(const QSslCertificate &certificate) { d->sock.setLocalCertificate(certificate); } void KTcpSocket::setLocalCertificate(const QString &fileName, QSsl::EncodingFormat format) { d->sock.setLocalCertificate(fileName, format); } void KTcpSocket::setVerificationPeerName(const QString &hostName) { d->sock.setPeerVerifyName(hostName); } void KTcpSocket::setPrivateKey(const KSslKey &key) { // We cannot map KSslKey::Algorithm:Dh to anything in QSsl::KeyAlgorithm. if (key.algorithm() == KSslKey::Dh) { return; } QSslKey _key(key.toDer(), (key.algorithm() == KSslKey::Rsa) ? QSsl::Rsa : QSsl::Dsa, QSsl::Der, (key.secrecy() == KSslKey::PrivateKey) ? QSsl::PrivateKey : QSsl::PublicKey); d->sock.setPrivateKey(_key); } void KTcpSocket::setPrivateKey(const QString &fileName, KSslKey::Algorithm algorithm, QSsl::EncodingFormat format, const QByteArray &passPhrase) { // We cannot map KSslKey::Algorithm:Dh to anything in QSsl::KeyAlgorithm. if (algorithm == KSslKey::Dh) { return; } d->sock.setPrivateKey(fileName, (algorithm == KSslKey::Rsa) ? QSsl::Rsa : QSsl::Dsa, format, passPhrase); } bool KTcpSocket::waitForEncrypted(int msecs) { return d->sock.waitForEncrypted(msecs); } KTcpSocket::EncryptionMode KTcpSocket::encryptionMode() const { return d->encryptionMode(d->sock.mode()); } QVariant KTcpSocket::socketOption(QAbstractSocket::SocketOption options) const { return d->sock.socketOption(options); } void KTcpSocket::setSocketOption(QAbstractSocket::SocketOption options, const QVariant &value) { d->sock.setSocketOption(options, value); } QSslConfiguration KTcpSocket::sslConfiguration() const { return d->sock.sslConfiguration(); } void KTcpSocket::setSslConfiguration(const QSslConfiguration &configuration) { d->sock.setSslConfiguration(configuration); } //slot void KTcpSocket::ignoreSslErrors() { d->sock.ignoreSslErrors(); } //slot void KTcpSocket::startClientEncryption() { d->maybeLoadCertificates(); d->sock.setProtocol(qSslProtocolFromK(d->advertisedSslVersion)); d->sock.startClientEncryption(); } //debugging H4X void KTcpSocket::showSslErrors() { foreach (const QSslError &e, d->sock.sslErrors()) { qCDebug(KIO_CORE) << e.errorString(); } } void KTcpSocket::setAdvertisedSslVersion(KTcpSocket::SslVersion version) { d->advertisedSslVersion = version; } KTcpSocket::SslVersion KTcpSocket::advertisedSslVersion() const { return d->advertisedSslVersion; } KTcpSocket::SslVersion KTcpSocket::negotiatedSslVersion() const { if (!d->sock.isEncrypted()) { return UnknownSslVersion; } return kSslVersionFromQ(d->sock.sessionProtocol()); } QString KTcpSocket::negotiatedSslVersionName() const { if (!d->sock.isEncrypted()) { return QString(); } return protocolString(d->sock.sessionProtocol()); } ////////////////////////////// KSslKey class KSslKeyPrivate { public: KSslKey::Algorithm convertAlgorithm(QSsl::KeyAlgorithm a) { switch (a) { case QSsl::Dsa: return KSslKey::Dsa; default: return KSslKey::Rsa; } } KSslKey::Algorithm algorithm; KSslKey::KeySecrecy secrecy; bool isExportable; QByteArray der; }; KSslKey::KSslKey() : d(new KSslKeyPrivate) { d->algorithm = Rsa; d->secrecy = PublicKey; d->isExportable = true; } KSslKey::KSslKey(const KSslKey &other) : d(new KSslKeyPrivate) { *d = *other.d; } KSslKey::KSslKey(const QSslKey &qsk) : d(new KSslKeyPrivate) { d->algorithm = d->convertAlgorithm(qsk.algorithm()); d->secrecy = (qsk.type() == QSsl::PrivateKey) ? PrivateKey : PublicKey; d->isExportable = true; d->der = qsk.toDer(); } KSslKey::~KSslKey() { delete d; } KSslKey &KSslKey::operator=(const KSslKey &other) { *d = *other.d; return *this; } KSslKey::Algorithm KSslKey::algorithm() const { return d->algorithm; } bool KSslKey::isExportable() const { return d->isExportable; } KSslKey::KeySecrecy KSslKey::secrecy() const { return d->secrecy; } QByteArray KSslKey::toDer() const { return d->der; } ////////////////////////////// KSslCipher //nice-to-have: make implicitly shared class KSslCipherPrivate { public: QString authenticationMethod; QString encryptionMethod; QString keyExchangeMethod; QString name; bool isNull; int supportedBits; int usedBits; }; KSslCipher::KSslCipher() : d(new KSslCipherPrivate) { d->isNull = true; d->supportedBits = 0; d->usedBits = 0; } KSslCipher::KSslCipher(const KSslCipher &other) : d(new KSslCipherPrivate) { *d = *other.d; } KSslCipher::KSslCipher(const QSslCipher &qsc) : d(new KSslCipherPrivate) { d->authenticationMethod = qsc.authenticationMethod(); d->encryptionMethod = qsc.encryptionMethod(); //Qt likes to append the number of bits (usedBits?) to the algorithm, //for example "AES(256)". We only want the pure algorithm name, though. int parenIdx = d->encryptionMethod.indexOf(QLatin1Char('(')); if (parenIdx > 0) { d->encryptionMethod.truncate(parenIdx); } d->keyExchangeMethod = qsc.keyExchangeMethod(); d->name = qsc.name(); d->isNull = qsc.isNull(); d->supportedBits = qsc.supportedBits(); d->usedBits = qsc.usedBits(); } KSslCipher::~KSslCipher() { delete d; } KSslCipher &KSslCipher::operator=(const KSslCipher &other) { *d = *other.d; return *this; } bool KSslCipher::isNull() const { return d->isNull; } QString KSslCipher::authenticationMethod() const { return d->authenticationMethod; } QString KSslCipher::encryptionMethod() const { return d->encryptionMethod; } QString KSslCipher::keyExchangeMethod() const { return d->keyExchangeMethod; } QString KSslCipher::digestMethod() const { //### This is not really backend neutral. It works for OpenSSL and // for RFC compliant names, though. if (d->name.endsWith(QLatin1String("SHA"))) { return QStringLiteral("SHA-1"); } else if (d->name.endsWith(QLatin1String("MD5"))) { return QStringLiteral("MD5"); } else { return QString(); } } QString KSslCipher::name() const { return d->name; } int KSslCipher::supportedBits() const { return d->supportedBits; } int KSslCipher::usedBits() const { return d->usedBits; } //static QList KSslCipher::supportedCiphers() { QList ret; const QList candidates = QSslConfiguration::supportedCiphers(); ret.reserve(candidates.size()); for (const QSslCipher &c : candidates) { ret.append(KSslCipher(c)); } return ret; } KSslErrorUiData::KSslErrorUiData() : d(new Private()) { d->usedBits = 0; d->bits = 0; } KSslErrorUiData::KSslErrorUiData(const KTcpSocket *socket) : d(new Private()) { d->certificateChain = socket->peerCertificateChain(); d->sslErrors = socket->sslErrors(); d->ip = socket->peerAddress().toString(); d->host = socket->peerName(); d->sslProtocol = socket->negotiatedSslVersionName(); d->cipher = socket->sessionCipher().name(); d->usedBits = socket->sessionCipher().usedBits(); d->bits = socket->sessionCipher().supportedBits(); } KSslErrorUiData::KSslErrorUiData(const QSslSocket *socket) : d(new Private()) { d->certificateChain = socket->peerCertificateChain(); // See KTcpSocket::sslErrors() const auto qsslErrors = socket->sslErrors(); d->sslErrors.reserve(qsslErrors.size()); for (const QSslError &e : qsslErrors) { d->sslErrors.append(KSslError(e)); } d->ip = socket->peerAddress().toString(); d->host = socket->peerName(); if (socket->isEncrypted()) { d->sslProtocol = socket->sessionCipher().protocolString(); } d->cipher = socket->sessionCipher().name(); d->usedBits = socket->sessionCipher().usedBits(); d->bits = socket->sessionCipher().supportedBits(); } +KSslErrorUiData::KSslErrorUiData(const QNetworkReply *reply, const QList &sslErrors) + : d(new Private()) +{ + const auto sslConfig = reply->sslConfiguration(); + d->certificateChain = sslConfig.peerCertificateChain(); + + d->sslErrors.reserve(sslErrors.size()); + for (const QSslError &e : sslErrors) { + d->sslErrors.append(KSslError(e)); + } + + d->host = reply->request().url().host(); + d->sslProtocol = sslConfig.sessionCipher().protocolString(); + d->cipher = sslConfig.sessionCipher().name(); + d->usedBits = sslConfig.sessionCipher().usedBits(); + d->bits = sslConfig.sessionCipher().supportedBits(); +} + KSslErrorUiData::KSslErrorUiData(const KSslErrorUiData &other) : d(new Private(*other.d)) {} KSslErrorUiData::~KSslErrorUiData() { delete d; } KSslErrorUiData &KSslErrorUiData::operator=(const KSslErrorUiData &other) { *d = *other.d; return *this; } #include "moc_ktcpsocket.cpp" diff --git a/src/core/ktcpsocket.h b/src/core/ktcpsocket.h index 897463a8..8c02fa31 100644 --- a/src/core/ktcpsocket.h +++ b/src/core/ktcpsocket.h @@ -1,426 +1,434 @@ /* This file is part of the KDE libraries Copyright (C) 2007 Thiago Macieira Copyright (C) 2007 Andreas Hartmetz This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KTCPSOCKET_H #define KTCPSOCKET_H #include #include #include "kiocore_export.h" +class QNetworkReply; + /* Notes on QCA::TLS compatibility In order to check for all validation problems as far as possible we need to use: Validity QCA::TLS::peerCertificateValidity() TLS::IdentityResult QCA::TLS::peerIdentityResult() CertificateChain QCA::TLS::peerCertificateChain().validate() - to find the failing cert! TLS::Error QCA::TLS::errorCode() - for more generic (but stil SSL) errors */ class KSslKeyPrivate; class KIOCORE_EXPORT KSslKey { public: enum Algorithm { Rsa = 0, Dsa, Dh }; enum KeySecrecy { PublicKey, PrivateKey }; KSslKey(); KSslKey(const KSslKey &other); KSslKey(const QSslKey &sslKey); ~KSslKey(); KSslKey &operator=(const KSslKey &other); Algorithm algorithm() const; bool isExportable() const; KeySecrecy secrecy() const; QByteArray toDer() const; private: KSslKeyPrivate *const d; }; class KSslCipherPrivate; class KIOCORE_EXPORT KSslCipher { public: KSslCipher(); KSslCipher(const KSslCipher &other); KSslCipher(const QSslCipher &); ~KSslCipher(); KSslCipher &operator=(const KSslCipher &other); bool isNull() const; QString authenticationMethod() const; QString encryptionMethod() const; QString keyExchangeMethod() const; QString digestMethod() const; /* mainly for internal use */ QString name() const; int supportedBits() const; int usedBits() const; static QList supportedCiphers(); private: KSslCipherPrivate *const d; }; class KSslErrorPrivate; class KTcpSocket; class KIOCORE_EXPORT KSslError { public: enum Error { NoError = 0, UnknownError, InvalidCertificateAuthorityCertificate, InvalidCertificate, CertificateSignatureFailed, SelfSignedCertificate, ExpiredCertificate, RevokedCertificate, InvalidCertificatePurpose, RejectedCertificate, UntrustedCertificate, NoPeerCertificate, HostNameMismatch, PathLengthExceeded }; KSslError(KSslError::Error error = NoError, const QSslCertificate &cert = QSslCertificate()); KSslError(const QSslError &error); //### explicit yes or no? KSslError(const KSslError &other); ~KSslError(); KSslError &operator=(const KSslError &other); Error error() const; QString errorString() const; QSslCertificate certificate() const; private: KSslErrorPrivate *const d; }; //consider killing more convenience functions with huge signatures //### do we need setSession() / session() ? //BIG FAT TODO: do we keep openMode() up to date everywhere it can change? //other TODO: limit possible error strings?, SSL key stuff //TODO protocol (or maybe even application?) dependent automatic proxy choice class KTcpSocketPrivate; class QHostAddress; class KIOCORE_EXPORT KTcpSocket: public QIODevice { Q_OBJECT public: enum State { UnconnectedState = 0, HostLookupState, ConnectingState, ConnectedState, BoundState, ListeningState, ClosingState //hmmm, do we need an SslNegotiatingState? }; enum SslVersion { UnknownSslVersion = 0x01, SslV2 = 0x02, SslV3 = 0x04, TlsV1 = 0x08, SslV3_1 = 0x08, TlsV1SslV3 = 0x10, SecureProtocols = 0x20, TlsV1_0 = TlsV1, TlsV1_1 = 0x40, TlsV1_2 = 0x80, TlsV1_3 = 0x100, AnySslVersion = SslV2 | SslV3 | TlsV1 }; Q_DECLARE_FLAGS(SslVersions, SslVersion) enum Error { UnknownError = 0, ConnectionRefusedError, RemoteHostClosedError, HostNotFoundError, SocketAccessError, SocketResourceError, SocketTimeoutError, NetworkError, UnsupportedSocketOperationError, SslHandshakeFailedError ///< @since 4.10.5 }; /* The following is based on reading the OpenSSL interface code of both QSslSocket and QCA::TLS. Barring oversights it should be accurate. The two cases with the question marks apparently will never be emitted by QSslSocket so there is nothing to compare. QSslError::NoError KTcpSocket::NoError QSslError::UnableToGetIssuerCertificate QCA::ErrorSignatureFailed QSslError::UnableToDecryptCertificateSignature QCA::ErrorSignatureFailed QSslError::UnableToDecodeIssuerPublicKey QCA::ErrorInvalidCA QSslError::CertificateSignatureFailed QCA::ErrorSignatureFailed QSslError::CertificateNotYetValid QCA::ErrorExpired QSslError::CertificateExpired QCA::ErrorExpired QSslError::InvalidNotBeforeField QCA::ErrorExpired QSslError::InvalidNotAfterField QCA::ErrorExpired QSslError::SelfSignedCertificate QCA::ErrorSelfSigned QSslError::SelfSignedCertificateInChain QCA::ErrorSelfSigned QSslError::UnableToGetLocalIssuerCertificate QCA::ErrorInvalidCA QSslError::UnableToVerifyFirstCertificate QCA::ErrorSignatureFailed QSslError::CertificateRevoked QCA::ErrorRevoked QSslError::InvalidCaCertificate QCA::ErrorInvalidCA QSslError::PathLengthExceeded QCA::ErrorPathLengthExceeded QSslError::InvalidPurpose QCA::ErrorInvalidPurpose QSslError::CertificateUntrusted QCA::ErrorUntrusted QSslError::CertificateRejected QCA::ErrorRejected QSslError::SubjectIssuerMismatch QCA::TLS::InvalidCertificate ? QSslError::AuthorityIssuerSerialNumberMismatch QCA::TLS::InvalidCertificate ? QSslError::NoPeerCertificate QCA::TLS::NoCertificate QSslError::HostNameMismatch QCA::TLS::HostMismatch QSslError::UnspecifiedError KTcpSocket::UnknownError QSslError::NoSslSupport Never happens :) */ enum EncryptionMode { UnencryptedMode = 0, SslClientMode, SslServerMode //### not implemented }; enum ProxyPolicy { /// Use the proxy that KProtocolManager suggests for the connection parameters given. AutoProxy = 0, /// Use the proxy set by setProxy(), if any; otherwise use no proxy. ManualProxy }; KTcpSocket(QObject *parent = nullptr); ~KTcpSocket(); //from QIODevice //reimplemented virtuals - the ones not reimplemented are OK for us bool atEnd() const override; qint64 bytesAvailable() const override; qint64 bytesToWrite() const override; bool canReadLine() const override; void close() override; bool isSequential() const override; bool open(QIODevice::OpenMode open) override; bool waitForBytesWritten(int msecs) override; //### Document that this actually tries to read *more* data bool waitForReadyRead(int msecs = 30000) override; protected: qint64 readData(char *data, qint64 maxSize) override; qint64 writeData(const char *data, qint64 maxSize) override; Q_SIGNALS: /// @since 4.8.1 /// Forwarded from QSslSocket void encryptedBytesWritten(qint64 written); public: //from QAbstractSocket void abort(); void connectToHost(const QString &hostName, quint16 port, ProxyPolicy policy = AutoProxy); void connectToHost(const QHostAddress &hostAddress, quint16 port, ProxyPolicy policy = AutoProxy); /** * Take the hostname and port from @p url and connect to them. The information from a * full URL enables the most accurate choice of proxy in case of proxy rules that * depend on high-level information like protocol or username. * @see KProtocolManager::proxyForUrl() */ void connectToHost(const QUrl &url, ProxyPolicy policy = AutoProxy); void disconnectFromHost(); Error error() const; //### QAbstractSocket's model is strange. error() should be related to the //current state and *NOT* just report the last error if there was one. QList sslErrors() const; //### the errors returned can only have a subset of all //possible QSslError::SslError enum values depending on backend bool flush(); bool isValid() const; QHostAddress localAddress() const; QHostAddress peerAddress() const; QString peerName() const; quint16 peerPort() const; void setVerificationPeerName(const QString &hostName); #ifndef QT_NO_NETWORKPROXY /** * @see: connectToHost() */ QNetworkProxy proxy() const; #endif qint64 readBufferSize() const; //probably hard to implement correctly #ifndef QT_NO_NETWORKPROXY /** * @see: connectToHost() */ void setProxy(const QNetworkProxy &proxy); //people actually seem to need it #endif void setReadBufferSize(qint64 size); State state() const; bool waitForConnected(int msecs = 30000); bool waitForDisconnected(int msecs = 30000); //from QSslSocket void addCaCertificate(const QSslCertificate &certificate); // bool addCaCertificates(const QString &path, QSsl::EncodingFormat format = QSsl::Pem, // QRegExp::PatternSyntax syntax = QRegExp::FixedString); void addCaCertificates(const QList &certificates); QList caCertificates() const; QList ciphers() const; void connectToHostEncrypted(const QString &hostName, quint16 port, OpenMode openMode = ReadWrite); // bool isEncrypted() const { return encryptionMode() != UnencryptedMode } QSslCertificate localCertificate() const; QList peerCertificateChain() const; KSslKey privateKey() const; KSslCipher sessionCipher() const; void setCaCertificates(const QList &certificates); void setCiphers(const QList &ciphers); //### void setCiphers(const QString &ciphers); //what about i18n? void setLocalCertificate(const QSslCertificate &certificate); void setLocalCertificate(const QString &fileName, QSsl::EncodingFormat format = QSsl::Pem); void setPrivateKey(const KSslKey &key); void setPrivateKey(const QString &fileName, KSslKey::Algorithm algorithm = KSslKey::Rsa, QSsl::EncodingFormat format = QSsl::Pem, const QByteArray &passPhrase = QByteArray()); void setAdvertisedSslVersion(SslVersion version); SslVersion advertisedSslVersion() const; //always equal to last setSslAdvertisedVersion SslVersion negotiatedSslVersion() const; //negotiated version; downgrades are possible. QString negotiatedSslVersionName() const; bool waitForEncrypted(int msecs = 30000); EncryptionMode encryptionMode() const; /** * Returns the state of the socket @p option. * * @see QAbstractSocket::socketOption * * @since 4.5.0 */ QVariant socketOption(QAbstractSocket::SocketOption options) const; /** * Sets the socket @p option to @p value. * * @see QAbstractSocket::setSocketOption * * @since 4.5.0 */ void setSocketOption(QAbstractSocket::SocketOption options, const QVariant &value); /** * Returns the socket's SSL configuration. * * @since 4.8.4 */ QSslConfiguration sslConfiguration() const; /** * Sets the socket's SSL configuration. * * @since 4.8.4 */ void setSslConfiguration(const QSslConfiguration &configuration); Q_SIGNALS: //from QAbstractSocket void connected(); void disconnected(); void error(KTcpSocket::Error); void hostFound(); #ifndef QT_NO_NETWORKPROXY void proxyAuthenticationRequired(const QNetworkProxy &proxy, QAuthenticator *authenticator); #endif // only for raw socket state, SSL is separate void stateChanged(KTcpSocket::State); //from QSslSocket void encrypted(); void encryptionModeChanged(EncryptionMode); void sslErrors(const QList &errors); public Q_SLOTS: void ignoreSslErrors(); void startClientEncryption(); // void startServerEncryption(); //not implemented private: Q_PRIVATE_SLOT(d, void reemitReadyRead()) Q_PRIVATE_SLOT(d, void reemitSocketError(QAbstractSocket::SocketError)) Q_PRIVATE_SLOT(d, void reemitSslErrors(const QList &)) Q_PRIVATE_SLOT(d, void reemitStateChanged(QAbstractSocket::SocketState)) Q_PRIVATE_SLOT(d, void reemitModeChanged(QSslSocket::SslMode)) //debugging H4X void showSslErrors(); friend class KTcpSocketPrivate; KTcpSocketPrivate *const d; }; /** * This class can hold all the necessary data from a KTcpSocket to ask the user * to continue connecting in the face of SSL errors. * It can be used to carry the data for the UI over time or over thread boundaries. * * @see: KSslCertificateManager::askIgnoreSslErrors() */ class KIOCORE_EXPORT KSslErrorUiData { public: /** * Default construct an instance with no useful data. */ KSslErrorUiData(); /** * Create an instance and initialize it with SSL error data from @p socket. */ KSslErrorUiData(const KTcpSocket *socket); /** * Create an instance and initialize it with SSL error data from @p socket. */ KSslErrorUiData(const QSslSocket *socket); + /** + * Create an instance and initialize it with SSL error data from @p reply. + * @since 5.62 + */ + KSslErrorUiData(const QNetworkReply *reply, const QList &sslErrors); + KSslErrorUiData(const KSslErrorUiData &other); KSslErrorUiData &operator=(const KSslErrorUiData &); /** * Destructor * @since 4.7 */ ~KSslErrorUiData(); class Private; private: friend class Private; Private *const d; }; #endif // KTCPSOCKET_H