Revision control

++ // Copyright (C) 2017 Witekio.
++ // Copyright (C) 2018 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++ // Qt-Security score:significant reason:default
++
++ #include "qcoapqudpconnection_p.h"
++ #include <private/qcoapnamespace_p.h>
++
++ #include <QtCore/qloggingcategory.h>
++ #include <QtNetwork/qnetworkdatagram.h>
++ #include <QtNetwork/private/qabstractsocket_p.h>
++
++ #if QT_CONFIG(dtls)
++ #include <QtNetwork/QDtls>
++ #include <QtNetwork/QSslPreSharedKeyAuthenticator>
++ #include <QtNetwork/QSslConfiguration>
++ #include <QtNetwork/QSslKey>
++ #endif
++
++ QT_BEGIN_NAMESPACE
++
++ /*!
++ \internal
++
++ \class QCoapQUdpConnection
++ \inmodule QtCoap
++
++ \brief The QCoapQUdpConnection class handles the transfer of frames to
++ and from a server.
++
++ \reentrant
++
++ The QCoapQUdpConnection class is used by the QCoapClient class to send
++ requests to a server. It has a socket listening for UDP messages,
++ that is used to send the CoAP frames.
++
++ When a reply is available, the QCoapQUdpConnection object emits a readyRead()
++ signal.
++
++ \sa QCoapClient
++ */
++
++ /*!
++ Constructs a new QCoapQUdpConnection for the given \a securityMode and
++ sets \a parent as the parent object.
++
++ \note Since QtCoap::RawPublicKey is not supported yet, the connection
++ will fall back to the QtCoap::NoSecurity in the QtCoap::RawPublicKey mode.
++ That is, the connection won't be secure in this mode.
++ */
++ QCoapQUdpConnection::QCoapQUdpConnection(QtCoap::SecurityMode securityMode, QObject *parent) :
++ QCoapQUdpConnection(*new QCoapQUdpConnectionPrivate(securityMode), parent)
++ {
++ }
++
++ /*!
++ \internal
++
++ Constructs a new QCoapQUdpConnection as a child of \a parent, with \a dd as
++ its \c d_ptr. This constructor must be used when internally subclassing
++ the QCoapQUdpConnection class.
++ */
++ QCoapQUdpConnection::QCoapQUdpConnection(QCoapQUdpConnectionPrivate &dd, QObject *parent) :
++ QCoapConnection(dd, parent)
++ {
++ Q_D(QCoapQUdpConnection);
++
++ createSocket();
++
++ if (isSecure()) {
++ #if QT_CONFIG(dtls)
++ connect(this, &QCoapConnection::securityConfigurationChanged, this,
++ [this]() {
++ Q_D(QCoapQUdpConnection);
++ d->setSecurityConfiguration(securityConfiguration());
++ });
++
++ auto configuration = QSslConfiguration::defaultDtlsConfiguration();
++
++ switch (d->securityMode) {
++ case QtCoap::SecurityMode::RawPublicKey:
++ qCWarning(lcCoapConnection, "RawPublicKey security is not supported yet,"
++ "disabling security");
++ d->securityMode = QtCoap::SecurityMode::NoSecurity;
++ break;
++ case QtCoap::SecurityMode::PreSharedKey:
++ d->dtls = new QDtls(QSslSocket::SslClientMode, this);
++ configuration.setPeerVerifyMode(QSslSocket::VerifyNone);
++ d->dtls->setDtlsConfiguration(configuration);
++
++ connect(d->dtls.data(), &QDtls::pskRequired, this, &QCoapQUdpConnection::pskRequired);
++ connect(d->dtls.data(), &QDtls::handshakeTimeout,
++ this, &QCoapQUdpConnection::handshakeTimeout);
++ break;
++ case QtCoap::SecurityMode::Certificate:
++ d->dtls = new QDtls(QSslSocket::SslClientMode, this);
++ configuration.setPeerVerifyMode(QSslSocket::VerifyPeer);
++ d->dtls->setDtlsConfiguration(configuration);
++
++ connect(d->dtls.data(), &QDtls::handshakeTimeout,
++ this, &QCoapQUdpConnection::handshakeTimeout);
++ break;
++ default:
++ break;
++ }
++ #else
++ qCWarning(lcCoapConnection, "DTLS is disabled, falling back to QtCoap::NoSecurity mode.");
++ d->securityMode = QtCoap::SecurityMode::NoSecurity;
++ #endif
++ }
++ }
++
++ /*!
++ \internal
++
++ Creates the socket used by the connection and sets it in connection's private
++ class.
++ */
++ void QCoapQUdpConnection::createSocket()
++ {
++ Q_D(QCoapQUdpConnection);
++
++ d->udpSocket = new QUdpSocket(this);
++
++ connect(d->udpSocket.data(), &QUdpSocket::readyRead, this, [this]() {
++ Q_D(QCoapQUdpConnection);
++ d->socketReadyRead();
++ });
++ connect(d->udpSocket.data(), &QUdpSocket::errorOccurred, this,
++ [this](QAbstractSocket::SocketError socketError) {
++ qCWarning(lcCoapConnection) << "CoAP UDP socket error" << socketError
++ << socket()->errorString();
++ emit error(socketError);
++ });
++ }
++
++ QCoapQUdpConnectionPrivate::QCoapQUdpConnectionPrivate(QtCoap::SecurityMode security)
++ : QCoapConnectionPrivate(security)
++ #if QT_CONFIG(dtls)
++ , dtls(nullptr)
++ #endif
++ , udpSocket(nullptr)
++ {
++ }
++
++ QCoapQUdpConnectionPrivate::~QCoapQUdpConnectionPrivate()
++ {
++ #if QT_CONFIG(dtls)
++ if (dtls && dtls->isConnectionEncrypted()) {
++ Q_ASSERT(udpSocket);
++ dtls->shutdown(udpSocket);
++ }
++ #endif
++ }
++
++ /*!
++ \internal
++
++ Binds the socket to a random port and returns \c true if it succeeds.
++ */
++ bool QCoapQUdpConnectionPrivate::bind()
++ {
++ #if QT_CONFIG(networkinterface)
++ // Using the QAbstractSocketPrivate private API to bind to a specific
++ // network interface.
++ auto priv = static_cast<QAbstractSocketPrivate *>(QObjectPrivate::get(socket()));
++ QNetworkInterface *iface = udpNetworkInterface.isValid() ? &udpNetworkInterface : nullptr;
++ return priv->bind(QHostAddress::Any, 0, QAbstractSocket::ShareAddress, iface);
++ #else
++ return socket()->bind(QHostAddress::Any, 0, QAbstractSocket::ShareAddress);
++ #endif
++ }
++
++ /*!
++ \internal
++
++ Binds the socket if it is not already done and emits the bound() signal when
++ the socket is ready.
++ */
++ void QCoapQUdpConnectionPrivate::bindSocket()
++ {
++ Q_Q(QCoapQUdpConnection);
++
++ if (state != QCoapQUdpConnection::ConnectionState::Bound && bind())
++ emit q->bound();
++ }
++
++ /*!
++ \internal
++
++ Prepares the socket for data transmission to the given \a host and
++ \a port by binding the socket. In case of a secure connection also
++ starts the handshake with the server.
++ Emits the bound() signal when the transport is ready.
++ */
++ void QCoapQUdpConnection::bind(const QString &host, quint16 port)
++ {
++ Q_D(QCoapQUdpConnection);
++
++ if (!isSecure()) {
++ d->bindSocket();
++ #if QT_CONFIG(dtls)
++ } else {
++ Q_ASSERT(d->dtls);
++ if (d->dtls->isConnectionEncrypted()) {
++ emit bound();
++ } else if (socket()->state() == QAbstractSocket::UnconnectedState) {
++ d->bind();
++ d->dtls->setPeer(QHostAddress(host), port);
++ if (!d->dtls->doHandshake(d->socket()))
++ qCWarning(lcCoapConnection) << "Handshake error: " << d->dtls->dtlsErrorString();
++ }
++ #else
++ Q_UNUSED(host);
++ Q_UNUSED(port);
++ #endif
++ }
++ }
++
++ /*!
++ \internal
++
++ Sends the given \a data frame to the \a host at the \a port.
++ */
++ void QCoapQUdpConnection::writeData(const QByteArray &data, const QString &host, quint16 port)
++ {
++ Q_D(QCoapQUdpConnection);
++ d->writeToSocket(data, host, port);
++ }
++
++ /*!
++ \internal
++
++ \brief Close the UDP socket
++
++ In the case of a secure connection, this also interrupts any ongoing
++ hand-shake and shuts down the DTLS connection.
++ */
++ void QCoapQUdpConnection::close()
++ {
++ Q_D(QCoapQUdpConnection);
++
++ #if QT_CONFIG(dtls)
++ if (isSecure()) {
++ if (d->dtls->handshakeState() == QDtls::HandshakeInProgress)
++ d->dtls->abortHandshake(d->socket());
++
++ if (d->dtls->isConnectionEncrypted())
++ d->dtls->shutdown(d->socket());
++ }
++ #endif
++ d->socket()->close();
++ }
++
++ /*!
++ \internal
++
++ Sets the QUdpSocket socket \a option to \a value.
++ */
++ void QCoapQUdpConnection::setSocketOption(QAbstractSocket::SocketOption option, const QVariant &value)
++ {
++ Q_D(QCoapQUdpConnection);
++ d->socket()->setSocketOption(option, value);
++ }
++
++ /*!
++ \internal
++
++ Sends the given \a data frame to the \a host at the \a port.
++ */
++ void QCoapQUdpConnectionPrivate::writeToSocket(const QByteArray &data, const QString &host, quint16 port)
++ {
++ #if QT_CONFIG(dtls)
++ Q_Q(QCoapQUdpConnection);
++ #endif
++ if (!socket()->isWritable()) {
++ bool opened = socket()->open(socket()->openMode() | QIODevice::WriteOnly);
++ if (!opened) {
++ qCWarning(lcCoapConnection, "Failed to open the UDP socket with write permission");
++ return;
++ }
++ }
++
++ QHostAddress hostAddress(host);
++ if (hostAddress.isNull()) {
++ qCWarning(lcCoapConnection) << "Invalid host IP address" << host
++ << "- only IPv4/IPv6 destination addresses are supported.";
++ return;
++ }
++
++ const qint64 bytesWritten =
++ #if QT_CONFIG(dtls)
++ q->isSecure() ? (Q_ASSERT(dtls), dtls->writeDatagramEncrypted(socket(), data)) :
++ #endif
++ socket()->writeDatagram(data, hostAddress, port);
++
++ if (bytesWritten < 0)
++ qCWarning(lcCoapConnection) << "Failed to write datagram:" << socket()->errorString();
++ }
++
++ /*!
++ \internal
++
++ This slot reads all data stored in the socket and emits a readyRead()
++ signal for each received datagram.
++ */
++ void QCoapQUdpConnectionPrivate::socketReadyRead()
++ {
++ Q_Q(QCoapQUdpConnection);
++
++ if (!socket()->isReadable()) {
++ bool opened = socket()->open(socket()->openMode() | QIODevice::ReadOnly);
++ if (!opened) {
++ qCWarning(lcCoapConnection, "Failed to open the UDP socket with read permission");
++ return;
++ }
++ }
++
++ while (socket()->hasPendingDatagrams()) {
++ if (!q->isSecure()) {
++ const auto &datagram = socket()->receiveDatagram();
++ if (datagramMatchesNetworkInterface(datagram))
++ emit q->readyRead(datagram.data(), datagram.senderAddress());
++ #if QT_CONFIG(dtls)
++ } else {
++ handleEncryptedDatagram();
++ #endif
++ }
++ }
++ }
++
++ /*!
++ \internal
++
++ Returns the socket.
++ */
++ QUdpSocket *QCoapQUdpConnection::socket() const
++ {
++ Q_D(const QCoapQUdpConnection);
++ return d->udpSocket;
++ }
++
++ /*!
++ \internal
++
++ Sets the DTLS configuration.
++ */
++ void QCoapQUdpConnectionPrivate::setSecurityConfiguration(
++ const QCoapSecurityConfiguration &configuration)
++ {
++ #if QT_CONFIG(dtls)
++ Q_ASSERT(dtls);
++ auto dtlsConfig = dtls->dtlsConfiguration();
++
++ if (!configuration.defaultCipherString().isEmpty()) {
++ dtlsConfig.setBackendConfigurationOption("CipherString",
++ configuration.defaultCipherString());
++ }
++
++ if (!configuration.caCertificates().isEmpty())
++ dtlsConfig.setCaCertificates(configuration.caCertificates().toList());
++
++ if (!configuration.localCertificateChain().isEmpty())
++ dtlsConfig.setLocalCertificateChain(configuration.localCertificateChain().toList());
++
++ if (!configuration.privateKey().isNull()) {
++ if (configuration.privateKey().algorithm() != QSsl::Opaque) {
++ QSslKey privateKey(configuration.privateKey().key(),
++ configuration.privateKey().algorithm(),
++ configuration.privateKey().encodingFormat(),
++ QSsl::PrivateKey,
++ configuration.privateKey().passPhrase());
++ dtlsConfig.setPrivateKey(privateKey);
++ } else if (configuration.privateKey().handle()) {
++ QSslKey opaqueKey(configuration.privateKey().handle());
++ dtlsConfig.setPrivateKey(opaqueKey);
++ } else {
++ qCWarning(lcCoapConnection, "Failed to set private key, the provided key is invalid");
++ }
++ }
++
++ dtls->setDtlsConfiguration(dtlsConfig);
++ #else
++ Q_UNUSED(configuration);
++ #endif
++ }
++
++ /*!
++ \internal
++
++ Checks if the datagram was received on a specified network interface
++ */
++ bool QCoapQUdpConnectionPrivate::datagramMatchesNetworkInterface(
++ const QNetworkDatagram &datagram) const
++ {
++ #if QT_CONFIG(networkinterface)
++ return !udpNetworkInterface.isValid()
++ || uint(udpNetworkInterface.index()) == datagram.interfaceIndex();
++ #else
++ Q_UNUSED(datagram);
++ return true;
++ #endif
++ }
++
++ #if QT_CONFIG(dtls)
++ /*!
++ \internal
++
++ This slot is invoked when PSK authentication is required. It is used
++ for setting the identity and pre shared key in order for the TLS handshake
++ to complete.
++ */
++ void QCoapQUdpConnection::pskRequired(QSslPreSharedKeyAuthenticator *authenticator)
++ {
++ Q_ASSERT(authenticator);
++ authenticator->setIdentity(securityConfiguration().preSharedKeyIdentity());
++ authenticator->setPreSharedKey(securityConfiguration().preSharedKey());
++ }
++
++ /*!
++ \internal
++
++ This slot handles handshake timeouts.
++ */
++ void QCoapQUdpConnection::handshakeTimeout()
++ {
++ Q_D(QCoapQUdpConnection);
++
++ qCWarning(lcCoapConnection, "Handshake timeout, trying to re-transmit");
++ if (d->dtls->handshakeState() == QDtls::HandshakeInProgress &&
++ !d->dtls->handleTimeout(d->udpSocket)) {
++ qCWarning(lcCoapConnection) << "Failed to re-transmit" << d->dtls->dtlsErrorString();
++ }
++ }
++
++ /*!
++ \internal
++
++ Returns a decrypted datagram received from a UDP socket or a std::nullopt
++ if the datagram was received on an incorrect network interface.
++ */
++ std::optional<QNetworkDatagram> QCoapQUdpConnectionPrivate::receiveDatagramDecrypted() const
++ {
++ auto datagram = socket()->receiveDatagram();
++ if (!datagramMatchesNetworkInterface(datagram))
++ return std::nullopt;
++
++ const QByteArray plainText = dtls->decryptDatagram(socket(), datagram.data());
++ datagram.setData(plainText);
++ return datagram;
++ }
++
++ /*!
++ \internal
++
++ If the connection is encrypted, emits the readyRead() signal for
++ the pending datagram. Otherwise starts the DTLS handshake and
++ if it is completed, starts to send the pending request.
++ */
++ void QCoapQUdpConnectionPrivate::handleEncryptedDatagram()
++ {
++ Q_Q(QCoapQUdpConnection);
++
++ Q_ASSERT(dtls);
++
++ if (dtls->isConnectionEncrypted()) {
++ const auto datagram = receiveDatagramDecrypted();
++ if (datagram)
++ emit q->readyRead(datagram->data(), datagram->senderAddress());
++ } else {
++ const auto datagram = socket()->receiveDatagram();
++ if (!datagramMatchesNetworkInterface(datagram))
++ return;
++
++ if (!dtls->doHandshake(socket(), datagram.data())) {
++ qCWarning(lcCoapConnection) << "Handshake error: " << dtls->dtlsErrorString();
++ return;
++ }
++
++ // The handshake is successfully done, emit the bound() signal to inform
++ // listeners that the socket is ready to start transferring data.
++ if (dtls->isConnectionEncrypted())
++ emit q->bound();
++ }
++ }
++
++ #endif // dtls
++
++ QT_END_NAMESPACE